mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-22 19:00:01 +08:00
重新mp4录制
This commit is contained in:
parent
12e1068a39
commit
383a14a76d
@ -1 +1 @@
|
|||||||
Subproject commit a32fe104c0290bc01a7ee73d50b568e8d9ded931
|
Subproject commit 6df71e01c174cdfe69e597cc4acb766a20b28620
|
@ -79,6 +79,10 @@ public:
|
|||||||
bool keyFrame() const override {
|
bool keyFrame() const override {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool configFrame() const override{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
public:
|
public:
|
||||||
unsigned int syncword = 0; //12 bslbf 同步字The bit string ‘1111 1111 1111’,说明一个ADTS帧的开始
|
unsigned int syncword = 0; //12 bslbf 同步字The bit string ‘1111 1111 1111’,说明一个ADTS帧的开始
|
||||||
unsigned int id; //1 bslbf MPEG 标示符, 设置为1
|
unsigned int id; //1 bslbf MPEG 标示符, 设置为1
|
||||||
@ -127,6 +131,10 @@ public:
|
|||||||
bool keyFrame() const override {
|
bool keyFrame() const override {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool configFrame() const override{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} ;
|
} ;
|
||||||
|
|
||||||
|
|
||||||
|
@ -117,6 +117,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual bool keyFrame() const = 0;
|
virtual bool keyFrame() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为配置帧,譬如sps pps vps
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
virtual bool configFrame() const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否可以缓存
|
* 是否可以缓存
|
||||||
*/
|
*/
|
||||||
@ -371,6 +377,7 @@ public:
|
|||||||
_trackType = frame->getTrackType();
|
_trackType = frame->getTrackType();
|
||||||
_codec = frame->getCodecId();
|
_codec = frame->getCodecId();
|
||||||
_key = frame->keyFrame();
|
_key = frame->keyFrame();
|
||||||
|
_config = frame->configFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~FrameCacheAble() = default;
|
virtual ~FrameCacheAble() = default;
|
||||||
@ -394,12 +401,17 @@ public:
|
|||||||
bool keyFrame() const override{
|
bool keyFrame() const override{
|
||||||
return _key;
|
return _key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool configFrame() const override{
|
||||||
|
return _config;
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
Frame::Ptr _frame;
|
Frame::Ptr _frame;
|
||||||
BufferRaw::Ptr _buffer;
|
BufferRaw::Ptr _buffer;
|
||||||
TrackType _trackType;
|
TrackType _trackType;
|
||||||
CodecId _codec;
|
CodecId _codec;
|
||||||
bool _key;
|
bool _key;
|
||||||
|
bool _config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,6 +82,16 @@ public:
|
|||||||
bool keyFrame() const override {
|
bool keyFrame() const override {
|
||||||
return H264_TYPE(buffer[iPrefixSize]) == H264Frame::NAL_IDR;
|
return H264_TYPE(buffer[iPrefixSize]) == H264Frame::NAL_IDR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool configFrame() const override{
|
||||||
|
switch(H264_TYPE(buffer[iPrefixSize]) ){
|
||||||
|
case H264Frame::NAL_SPS:
|
||||||
|
case H264Frame::NAL_PPS:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
public:
|
public:
|
||||||
uint32_t timeStamp;
|
uint32_t timeStamp;
|
||||||
uint32_t ptsStamp = 0;
|
uint32_t ptsStamp = 0;
|
||||||
@ -118,6 +128,16 @@ public:
|
|||||||
bool keyFrame() const override {
|
bool keyFrame() const override {
|
||||||
return H264_TYPE(_ptr[_prefixSize]) == H264Frame::NAL_IDR;
|
return H264_TYPE(_ptr[_prefixSize]) == H264Frame::NAL_IDR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool configFrame() const override{
|
||||||
|
switch(H264_TYPE(_ptr[_prefixSize])){
|
||||||
|
case H264Frame::NAL_SPS:
|
||||||
|
case H264Frame::NAL_PPS:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,6 +86,10 @@ public:
|
|||||||
return timeStamp;
|
return timeStamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t pts() const override {
|
||||||
|
return ptsStamp ? ptsStamp : timeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t prefixSize() const override {
|
uint32_t prefixSize() const override {
|
||||||
return iPrefixSize;
|
return iPrefixSize;
|
||||||
}
|
}
|
||||||
@ -99,7 +103,18 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool keyFrame() const override {
|
bool keyFrame() const override {
|
||||||
return isKeyFrame(type);
|
return isKeyFrame(H265_TYPE(buffer[iPrefixSize]));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool configFrame() const override{
|
||||||
|
switch(H265_TYPE(buffer[iPrefixSize])){
|
||||||
|
case H265Frame::NAL_VPS:
|
||||||
|
case H265Frame::NAL_SPS:
|
||||||
|
case H265Frame::NAL_PPS:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isKeyFrame(int type) {
|
static bool isKeyFrame(int type) {
|
||||||
@ -117,9 +132,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
uint16_t sequence;
|
|
||||||
uint32_t timeStamp;
|
uint32_t timeStamp;
|
||||||
unsigned char type;
|
uint32_t ptsStamp = 0;
|
||||||
string buffer;
|
string buffer;
|
||||||
uint32_t iPrefixSize = 4;
|
uint32_t iPrefixSize = 4;
|
||||||
};
|
};
|
||||||
@ -146,8 +160,18 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool keyFrame() const override {
|
bool keyFrame() const override {
|
||||||
int type = H265_TYPE(((uint8_t *) _ptr)[_prefixSize]);
|
return H265Frame::isKeyFrame(H265_TYPE(((uint8_t *) _ptr)[_prefixSize]));
|
||||||
return H265Frame::isKeyFrame(type);
|
}
|
||||||
|
|
||||||
|
bool configFrame() const override{
|
||||||
|
switch(H265_TYPE(((uint8_t *) _ptr)[_prefixSize])){
|
||||||
|
case H265Frame::NAL_VPS:
|
||||||
|
case H265Frame::NAL_SPS:
|
||||||
|
case H265Frame::NAL_PPS:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -328,7 +352,6 @@ private:
|
|||||||
}
|
}
|
||||||
if(!_vps.empty()){
|
if(!_vps.empty()){
|
||||||
auto vpsFrame = std::make_shared<H265Frame>();
|
auto vpsFrame = std::make_shared<H265Frame>();
|
||||||
vpsFrame->type = H265Frame::NAL_VPS;
|
|
||||||
vpsFrame->iPrefixSize = 4;
|
vpsFrame->iPrefixSize = 4;
|
||||||
vpsFrame->buffer.assign("\x0\x0\x0\x1", 4);
|
vpsFrame->buffer.assign("\x0\x0\x0\x1", 4);
|
||||||
vpsFrame->buffer.append(_vps);
|
vpsFrame->buffer.append(_vps);
|
||||||
@ -337,7 +360,6 @@ private:
|
|||||||
}
|
}
|
||||||
if (!_sps.empty()) {
|
if (!_sps.empty()) {
|
||||||
auto spsFrame = std::make_shared<H265Frame>();
|
auto spsFrame = std::make_shared<H265Frame>();
|
||||||
spsFrame->type = H265Frame::NAL_SPS;
|
|
||||||
spsFrame->iPrefixSize = 4;
|
spsFrame->iPrefixSize = 4;
|
||||||
spsFrame->buffer.assign("\x0\x0\x0\x1", 4);
|
spsFrame->buffer.assign("\x0\x0\x0\x1", 4);
|
||||||
spsFrame->buffer.append(_sps);
|
spsFrame->buffer.append(_sps);
|
||||||
@ -347,7 +369,6 @@ private:
|
|||||||
|
|
||||||
if (!_pps.empty()) {
|
if (!_pps.empty()) {
|
||||||
auto ppsFrame = std::make_shared<H265Frame>();
|
auto ppsFrame = std::make_shared<H265Frame>();
|
||||||
ppsFrame->type = H265Frame::NAL_PPS;
|
|
||||||
ppsFrame->iPrefixSize = 4;
|
ppsFrame->iPrefixSize = 4;
|
||||||
ppsFrame->buffer.assign("\x0\x0\x0\x1", 4);
|
ppsFrame->buffer.assign("\x0\x0\x0\x1", 4);
|
||||||
ppsFrame->buffer.append(_pps);
|
ppsFrame->buffer.append(_pps);
|
||||||
|
@ -99,56 +99,56 @@ bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
|||||||
// fragmentation unit (FU)
|
// fragmentation unit (FU)
|
||||||
FU fu;
|
FU fu;
|
||||||
MakeFU(frame[2], fu);
|
MakeFU(frame[2], fu);
|
||||||
if (fu.S == 1) {
|
if (fu.S) {
|
||||||
//FU-A start
|
//该帧的第一个rtp包
|
||||||
_h265frame->buffer.assign("\x0\x0\x0\x1", 4);
|
_h265frame->buffer.assign("\x0\x0\x0\x1", 4);
|
||||||
_h265frame->buffer.push_back(fu.type << 1);
|
_h265frame->buffer.push_back(fu.type << 1);
|
||||||
_h265frame->buffer.push_back(0x01);
|
_h265frame->buffer.push_back(0x01);
|
||||||
_h265frame->buffer.append((char *) frame + 3, length - 3);
|
_h265frame->buffer.append((char *) frame + 3, length - 3);
|
||||||
_h265frame->type = fu.type;
|
|
||||||
_h265frame->timeStamp = rtppack->timeStamp;
|
_h265frame->timeStamp = rtppack->timeStamp;
|
||||||
_h265frame->sequence = rtppack->sequence;
|
//该函数return时,保存下当前sequence,以便下次对比seq是否连续
|
||||||
|
_lastSeq = rtppack->sequence;
|
||||||
return (_h265frame->keyFrame()); //i frame
|
return (_h265frame->keyFrame()); //i frame
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rtppack->sequence != (uint16_t) (_h265frame->sequence + 1)) {
|
if (rtppack->sequence != _lastSeq + 1 && rtppack->sequence != 0) {
|
||||||
|
//中间的或末尾的rtp包,其seq必须连续(如果回环了则判定为连续),否则说明rtp丢包,那么该帧不完整,必须得丢弃
|
||||||
_h265frame->buffer.clear();
|
_h265frame->buffer.clear();
|
||||||
WarnL << "丢包,帧废弃:" << rtppack->sequence << "," << _h265frame->sequence;
|
WarnL << "rtp sequence不连续: " << rtppack->sequence << " != " << _lastSeq << " + 1,该帧被废弃";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_h265frame->sequence = rtppack->sequence;
|
|
||||||
if (fu.E == 1) {
|
if (!fu.E) {
|
||||||
//FU-A end
|
//该帧的中间rtp包
|
||||||
_h265frame->buffer.append((char *) frame + 3, length - 3);
|
_h265frame->buffer.append((char *) frame + 3, length - 3);
|
||||||
_h265frame->timeStamp = rtppack->timeStamp;
|
//该函数return时,保存下当前sequence,以便下次对比seq是否连续
|
||||||
auto isIDR = _h265frame->keyFrame();
|
_lastSeq = rtppack->sequence;
|
||||||
onGetH265(_h265frame);
|
return false;
|
||||||
return isIDR;
|
|
||||||
}
|
}
|
||||||
//FU-A mid
|
|
||||||
|
//该帧最后一个rtp包
|
||||||
_h265frame->buffer.append((char *) frame + 3, length - 3);
|
_h265frame->buffer.append((char *) frame + 3, length - 3);
|
||||||
return false;
|
_h265frame->timeStamp = rtppack->timeStamp;
|
||||||
|
auto key = _h265frame->keyFrame();
|
||||||
|
onGetH265(_h265frame);
|
||||||
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
default: // 4.4.1. Single NAL Unit Packets (p24)
|
default: // 4.4.1. Single NAL Unit Packets (p24)
|
||||||
//a full frame
|
//a full frame
|
||||||
_h265frame->buffer.assign("\x0\x0\x0\x1", 4);
|
_h265frame->buffer.assign("\x0\x0\x0\x1", 4);
|
||||||
_h265frame->buffer.append((char *)frame, length);
|
_h265frame->buffer.append((char *)frame, length);
|
||||||
_h265frame->type = nal;
|
|
||||||
_h265frame->timeStamp = rtppack->timeStamp;
|
_h265frame->timeStamp = rtppack->timeStamp;
|
||||||
_h265frame->sequence = rtppack->sequence;
|
auto key = _h265frame->keyFrame();
|
||||||
auto isIDR = _h265frame->keyFrame();
|
|
||||||
onGetH265(_h265frame);
|
onGetH265(_h265frame);
|
||||||
return (isIDR); //i frame
|
return key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void H265RtpDecoder::onGetH265(const H265Frame::Ptr &frame) {
|
void H265RtpDecoder::onGetH265(const H265Frame::Ptr &frame) {
|
||||||
//写入环形缓存
|
//写入环形缓存
|
||||||
auto lastSeq = _h265frame->sequence;
|
|
||||||
RtpCodec::inputFrame(frame);
|
RtpCodec::inputFrame(frame);
|
||||||
_h265frame = obtainFrame();
|
_h265frame = obtainFrame();
|
||||||
_h265frame->sequence = lastSeq;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,6 +65,7 @@ private:
|
|||||||
H265Frame::Ptr obtainFrame();
|
H265Frame::Ptr obtainFrame();
|
||||||
private:
|
private:
|
||||||
H265Frame::Ptr _h265frame;
|
H265Frame::Ptr _h265frame;
|
||||||
|
int _lastSeq = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
230
src/MediaFile/MP4Muxer.cpp
Normal file
230
src/MediaFile/MP4Muxer.cpp
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
||||||
|
*
|
||||||
|
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef ENABLE_MP4RECORD
|
||||||
|
|
||||||
|
#include "MP4Muxer.h"
|
||||||
|
#include "Util/File.h"
|
||||||
|
|
||||||
|
namespace mediakit{
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
#define fseek64 _fseeki64
|
||||||
|
#define ftell64 _ftelli64
|
||||||
|
#else
|
||||||
|
#define fseek64 fseek
|
||||||
|
#define ftell64 ftell
|
||||||
|
#endif
|
||||||
|
|
||||||
|
MP4MuxerBase::MP4MuxerBase(int flags) {
|
||||||
|
static struct mov_buffer_t s_io = {
|
||||||
|
[](void* ctx, void* data, uint64_t bytes) {
|
||||||
|
MP4MuxerBase *thiz = (MP4MuxerBase *)ctx;
|
||||||
|
return thiz->onRead(data,bytes);
|
||||||
|
},
|
||||||
|
[](void* ctx, const void* data, uint64_t bytes){
|
||||||
|
MP4MuxerBase *thiz = (MP4MuxerBase *)ctx;
|
||||||
|
return thiz->onWrite(data,bytes);
|
||||||
|
},
|
||||||
|
[](void* ctx, uint64_t offset) {
|
||||||
|
MP4MuxerBase *thiz = (MP4MuxerBase *)ctx;
|
||||||
|
return thiz->onSeek(offset);
|
||||||
|
},
|
||||||
|
[](void* ctx){
|
||||||
|
MP4MuxerBase *thiz = (MP4MuxerBase *)ctx;
|
||||||
|
return thiz->onTell();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_mov_writter.reset(mov_writer_create(&s_io,this,flags),[](mov_writer_t *ptr){
|
||||||
|
if(ptr){
|
||||||
|
mov_writer_destroy(ptr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////
|
||||||
|
|
||||||
|
MP4Muxer::MP4Muxer(int flags) : MP4MuxerBase(flags) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MP4Muxer::onTrackFrame(const Frame::Ptr &frame) {
|
||||||
|
if(frame->configFrame()){
|
||||||
|
//忽略配置帧
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto it = _codec_to_trackid.find(frame->getCodecId());
|
||||||
|
if(it == _codec_to_trackid.end()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mov_writer_write_l(_mov_writter.get(),
|
||||||
|
it->second,
|
||||||
|
frame->data() + frame->prefixSize(),
|
||||||
|
frame->size() - frame->prefixSize(),
|
||||||
|
frame->pts(),
|
||||||
|
frame->dts(),
|
||||||
|
frame->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MP4Muxer::onTrackReady(const Track::Ptr &track) {
|
||||||
|
switch (track->getCodecId()) {
|
||||||
|
case CodecAAC: {
|
||||||
|
auto aac_track = dynamic_pointer_cast<AACTrack>(track);
|
||||||
|
if (!aac_track) {
|
||||||
|
WarnL << "不是AAC Track";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto track_id = mov_writer_add_audio(_mov_writter.get(),
|
||||||
|
MOV_OBJECT_AAC,
|
||||||
|
aac_track->getAudioChannel(),
|
||||||
|
aac_track->getAudioSampleBit(),
|
||||||
|
aac_track->getAudioSampleRate(),
|
||||||
|
aac_track->getAacCfg().data(), 2);
|
||||||
|
_codec_to_trackid[track->getCodecId()] = track_id;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CodecH264: {
|
||||||
|
auto h264_track = dynamic_pointer_cast<H264Track>(track);
|
||||||
|
if (!h264_track) {
|
||||||
|
WarnL << "不是H264 Track";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mpeg4_avc_t avc;
|
||||||
|
string sps_pps = string("\x00\x00\x00\x01", 4) + h264_track->getSps() +
|
||||||
|
string("\x00\x00\x00\x01", 4) + h264_track->getPps();
|
||||||
|
h264_annexbtomp4(&avc, sps_pps.data(), sps_pps.size(), NULL, 0, NULL);
|
||||||
|
|
||||||
|
uint8_t extra_data[1024];
|
||||||
|
int extra_data_size = mpeg4_avc_decoder_configuration_record_save(&avc, extra_data, sizeof(extra_data));
|
||||||
|
if (extra_data_size == -1) {
|
||||||
|
WarnL << "生成H264 extra_data 失败";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto track_id = mov_writer_add_video(_mov_writter.get(),
|
||||||
|
MOV_OBJECT_H264,
|
||||||
|
h264_track->getVideoWidth(),
|
||||||
|
h264_track->getVideoHeight(),
|
||||||
|
extra_data,
|
||||||
|
extra_data_size);
|
||||||
|
_codec_to_trackid[track->getCodecId()] = track_id;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CodecH265: {
|
||||||
|
auto h265_track = dynamic_pointer_cast<H265Track>(track);
|
||||||
|
if (!h265_track) {
|
||||||
|
WarnL << "不是H265 Track";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mpeg4_hevc_t hevc;
|
||||||
|
string vps_sps_pps = string("\x00\x00\x00\x01", 4) + h265_track->getVps() +
|
||||||
|
string("\x00\x00\x00\x01", 4) + h265_track->getSps() +
|
||||||
|
string("\x00\x00\x00\x01", 4) + h265_track->getPps();
|
||||||
|
h265_annexbtomp4(&hevc, vps_sps_pps.data(), vps_sps_pps.size(), NULL, 0, NULL);
|
||||||
|
|
||||||
|
uint8_t extra_data[1024];
|
||||||
|
int extra_data_size = mpeg4_hevc_decoder_configuration_record_save(&hevc, extra_data, sizeof(extra_data));
|
||||||
|
if (extra_data_size == -1) {
|
||||||
|
WarnL << "生成H265 extra_data 失败";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto track_id = mov_writer_add_video(_mov_writter.get(),
|
||||||
|
MOV_OBJECT_HEVC,
|
||||||
|
h265_track->getVideoWidth(),
|
||||||
|
h265_track->getVideoHeight(),
|
||||||
|
extra_data,
|
||||||
|
extra_data_size);
|
||||||
|
_codec_to_trackid[track->getCodecId()] = track_id;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
WarnL << "MP4录制不支持该编码格式:" << track->getCodecId();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FILE_BUF_SIZE (64 * 1024)
|
||||||
|
|
||||||
|
MP4MuxerFile::MP4MuxerFile(const char *file) {
|
||||||
|
//创建文件
|
||||||
|
auto fp = File::createfile_file(file,"wb");
|
||||||
|
if(!fp){
|
||||||
|
throw std::runtime_error(string("打开文件失败:") + file);
|
||||||
|
}
|
||||||
|
|
||||||
|
//新建文件io缓存
|
||||||
|
std::shared_ptr<char> file_buf(new char[FILE_BUF_SIZE],[](char *ptr){
|
||||||
|
if(ptr){
|
||||||
|
delete [] ptr;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(file_buf){
|
||||||
|
//设置文件io缓存
|
||||||
|
setvbuf(fp, file_buf.get(), _IOFBF, FILE_BUF_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
//创建智能指针
|
||||||
|
_file.reset(fp,[file_buf](FILE *fp) {
|
||||||
|
fclose(fp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int MP4MuxerFile::onRead(void *data, uint64_t bytes) {
|
||||||
|
return fread(data, 1, bytes, _file.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
int MP4MuxerFile::onWrite(const void *data, uint64_t bytes) {
|
||||||
|
return fwrite(data, 1, bytes, _file.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
#define fseek64 _fseeki64
|
||||||
|
#define ftell64 _ftelli64
|
||||||
|
#else
|
||||||
|
#define fseek64 fseek
|
||||||
|
#define ftell64 ftell
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int MP4MuxerFile::onSeek(uint64_t offset) {
|
||||||
|
return fseek64(_file.get(), offset, SEEK_SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t MP4MuxerFile::onTell() {
|
||||||
|
return ftell64(_file.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}//namespace mediakit
|
||||||
|
|
||||||
|
#endif//#ifdef ENABLE_MP4RECORD
|
98
src/MediaFile/MP4Muxer.h
Normal file
98
src/MediaFile/MP4Muxer.h
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
||||||
|
*
|
||||||
|
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ZLMEDIAKIT_MP4MUXER_H
|
||||||
|
#define ZLMEDIAKIT_MP4MUXER_H
|
||||||
|
|
||||||
|
#ifdef ENABLE_MP4RECORD
|
||||||
|
|
||||||
|
#include "Common/MediaSink.h"
|
||||||
|
#include "mov-writer.h"
|
||||||
|
#include "mpeg4-hevc.h"
|
||||||
|
#include "mpeg4-avc.h"
|
||||||
|
#include "mpeg4-aac.h"
|
||||||
|
#include "mov-buffer.h"
|
||||||
|
#include "mov-format.h"
|
||||||
|
#include "Extension/AAC.h"
|
||||||
|
#include "Extension/H264.h"
|
||||||
|
#include "Extension/H265.h"
|
||||||
|
|
||||||
|
namespace mediakit{
|
||||||
|
|
||||||
|
class MP4MuxerBase{
|
||||||
|
public:
|
||||||
|
MP4MuxerBase(int flags = MOV_FLAG_FASTSTART);
|
||||||
|
virtual ~MP4MuxerBase() = default;
|
||||||
|
protected:
|
||||||
|
virtual int onRead(void* data, uint64_t bytes) = 0;
|
||||||
|
virtual int onWrite(const void* data, uint64_t bytes) = 0;
|
||||||
|
virtual int onSeek( uint64_t offset) = 0;
|
||||||
|
virtual uint64_t onTell() = 0;
|
||||||
|
protected:
|
||||||
|
std::shared_ptr<mov_writer_t> _mov_writter;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MP4Muxer : public MediaSink , public MP4MuxerBase{
|
||||||
|
public:
|
||||||
|
MP4Muxer(int flags = MOV_FLAG_FASTSTART) ;
|
||||||
|
~MP4Muxer() override = default;
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* 某track已经准备好,其ready()状态返回true,
|
||||||
|
* 此时代表可以获取其例如sps pps等相关信息了
|
||||||
|
* @param track
|
||||||
|
*/
|
||||||
|
void onTrackReady(const Track::Ptr & track) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 某Track输出frame,在onAllTrackReady触发后才会调用此方法
|
||||||
|
* @param frame
|
||||||
|
*/
|
||||||
|
void onTrackFrame(const Frame::Ptr &frame) override;
|
||||||
|
private:
|
||||||
|
map<CodecId,int> _codec_to_trackid;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class MP4MuxerFile : public MP4Muxer {
|
||||||
|
public:
|
||||||
|
typedef std::shared_ptr<MP4MuxerFile> Ptr;
|
||||||
|
MP4MuxerFile(const char *file);
|
||||||
|
~MP4MuxerFile() = default;
|
||||||
|
protected:
|
||||||
|
int onRead(void* data, uint64_t bytes) override;
|
||||||
|
int onWrite(const void* data, uint64_t bytes) override;
|
||||||
|
int onSeek( uint64_t offset) override;
|
||||||
|
uint64_t onTell() override ;
|
||||||
|
private:
|
||||||
|
std::shared_ptr<FILE> _file;
|
||||||
|
};
|
||||||
|
|
||||||
|
}//namespace mediakit
|
||||||
|
|
||||||
|
#endif//#ifdef ENABLE_MP4RECORD
|
||||||
|
|
||||||
|
#endif //ZLMEDIAKIT_MP4MUXER_H
|
@ -106,11 +106,11 @@ void MediaRecorder::addTrack(const Track::Ptr &track) {
|
|||||||
}
|
}
|
||||||
#endif //defined(ENABLE_HLS)
|
#endif //defined(ENABLE_HLS)
|
||||||
|
|
||||||
#if defined(ENABLE_MP4V2)
|
#if defined(ENABLE_MP4RECORD)
|
||||||
if (_mp4Maker) {
|
if (_mp4Maker) {
|
||||||
_mp4Maker->addTrack(track);
|
_mp4Maker->addTrack(track);
|
||||||
}
|
}
|
||||||
#endif //defined(ENABLE_MP4V2)
|
#endif //defined(ENABLE_MP4RECORD)
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -64,9 +64,9 @@ private:
|
|||||||
std::shared_ptr<HlsRecorder> _hlsMaker;
|
std::shared_ptr<HlsRecorder> _hlsMaker;
|
||||||
#endif //defined(ENABLE_HLS)
|
#endif //defined(ENABLE_HLS)
|
||||||
|
|
||||||
#if defined(ENABLE_MP4V2)
|
#if defined(ENABLE_MP4RECORD)
|
||||||
std::shared_ptr<Mp4Maker> _mp4Maker;
|
std::shared_ptr<Mp4Maker> _mp4Maker;
|
||||||
#endif //defined(ENABLE_MP4V2)
|
#endif //defined(ENABLE_MP4RECORD)
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -29,68 +29,14 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include "Common/config.h"
|
#include "Common/config.h"
|
||||||
#include "Mp4Maker.h"
|
#include "Mp4Maker.h"
|
||||||
#include "MediaRecorder.h"
|
|
||||||
#include "Util/File.h"
|
|
||||||
#include "Util/mini.h"
|
|
||||||
#include "Util/util.h"
|
#include "Util/util.h"
|
||||||
#include "Util/NoticeCenter.h"
|
#include "Util/NoticeCenter.h"
|
||||||
#include "Extension/H264.h"
|
|
||||||
#include "Extension/H265.h"
|
|
||||||
#include "Extension/AAC.h"
|
|
||||||
#include "Thread/WorkThreadPool.h"
|
#include "Thread/WorkThreadPool.h"
|
||||||
|
|
||||||
|
|
||||||
#include "mov-buffer.h"
|
|
||||||
#include "mov-format.h"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
|
||||||
#define fseek64 _fseeki64
|
|
||||||
#define ftell64 _ftelli64
|
|
||||||
#else
|
|
||||||
#define fseek64 fseek
|
|
||||||
#define ftell64 ftell
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int movfileRead(void* fp, void* data, uint64_t bytes)
|
|
||||||
{
|
|
||||||
if (bytes == fread(data, 1, bytes, (FILE*)fp))
|
|
||||||
return 0;
|
|
||||||
return 0 != ferror((FILE*)fp) ? ferror((FILE*)fp) : -1 /*EOF*/;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int movfileWrite(void* fp, const void* data, uint64_t bytes)
|
|
||||||
{
|
|
||||||
return bytes == fwrite(data, 1, bytes, (FILE*)fp) ? 0 : ferror((FILE*)fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int movfileSeek(void* fp, uint64_t offset)
|
|
||||||
{
|
|
||||||
return fseek64((FILE*)fp, offset, SEEK_SET);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t movfileTell(void* fp)
|
|
||||||
{
|
|
||||||
return ftell64((FILE*)fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
const struct mov_buffer_t* movfileBuffer(void)
|
|
||||||
{
|
|
||||||
static struct mov_buffer_t s_io = {
|
|
||||||
movfileRead,
|
|
||||||
movfileWrite,
|
|
||||||
movfileSeek,
|
|
||||||
movfileTell,
|
|
||||||
};
|
|
||||||
return &s_io;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
string timeStr(const char *fmt) {
|
string timeStr(const char *fmt) {
|
||||||
std::tm tm_snapshot;
|
std::tm tm_snapshot;
|
||||||
auto time = ::time(NULL);
|
auto time = ::time(NULL);
|
||||||
@ -119,203 +65,11 @@ Mp4Maker::Mp4Maker(const string& strPath,
|
|||||||
_info.strStreamId = strStreamId;
|
_info.strStreamId = strStreamId;
|
||||||
_info.strVhost = strVhost;
|
_info.strVhost = strVhost;
|
||||||
_info.strFolder = strPath;
|
_info.strFolder = strPath;
|
||||||
|
|
||||||
memset(&_movH265info, 0, sizeof(_movH265info));
|
|
||||||
_movH265info.videoTrack = -1;
|
|
||||||
_movH265info.audioTrack = -1;
|
|
||||||
|
|
||||||
//----record 业务逻辑----//
|
|
||||||
}
|
}
|
||||||
Mp4Maker::~Mp4Maker() {
|
Mp4Maker::~Mp4Maker() {
|
||||||
closeFile();
|
closeFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mp4Maker::inputH264(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp){
|
|
||||||
auto iType = H264_TYPE(((uint8_t*)pData)[4]);
|
|
||||||
|
|
||||||
if (H264Frame::NAL_B_P <= iType && iType <= H264Frame::NAL_IDR){
|
|
||||||
|
|
||||||
int64_t iTimeInc = (int64_t)ui32TimeStamp - (int64_t)_ui32LastVideoTime;
|
|
||||||
iTimeInc = MAX(0,MIN(iTimeInc,500));
|
|
||||||
if( (iTimeInc == 0 || iTimeInc == 500) && H264Frame::NAL_IDR != iType){
|
|
||||||
WarnL << "abnormal time stamp increment:" << ui32TimeStamp << " " << _ui32LastVideoTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( _strLastVideo.size() ){
|
|
||||||
//如果出现SPS PPS黏连的帧,那么在这里先处理
|
|
||||||
inputH264_l((char *) _strLastVideo.data(), _strLastVideo.size(), _ui32LastVideoTime);
|
|
||||||
_strLastVideo = "";
|
|
||||||
}
|
|
||||||
inputH264_l((char *) pData, ui32Length, ui32TimeStamp);
|
|
||||||
_ui32LastVideoTime = ui32TimeStamp;
|
|
||||||
}else{
|
|
||||||
//SPS PPS 进入等待组合为一帧
|
|
||||||
_strLastVideo.append((char *)pData,ui32Length);
|
|
||||||
_ui32LastVideoTime = ui32TimeStamp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mp4Maker::inputH265(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp){
|
|
||||||
|
|
||||||
auto iType = H265_TYPE(((uint8_t*)pData)[4]);
|
|
||||||
if (iType <= H265Frame::NAL_IDR_W_RADL ){
|
|
||||||
|
|
||||||
int64_t iTimeInc = (int64_t)ui32TimeStamp - (int64_t)_ui32LastVideoTime;
|
|
||||||
iTimeInc = MAX(0,MIN(iTimeInc,500));
|
|
||||||
if((iTimeInc == 0 || iTimeInc == 500) && H265Frame::NAL_IDR_W_RADL != iType){
|
|
||||||
WarnL << "abnormal time stamp increment:" << ui32TimeStamp << " " << _ui32LastVideoTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( _strLastVideo.size() ){
|
|
||||||
//如果出现SPS PPS VPS黏连的帧,那么在这里先处理
|
|
||||||
inputH265_l((char *) _strLastVideo.data(), _strLastVideo.size(), _ui32LastVideoTime);
|
|
||||||
_strLastVideo = "";
|
|
||||||
}
|
|
||||||
inputH265_l((char *) pData, ui32Length, ui32TimeStamp);
|
|
||||||
_ui32LastVideoTime = ui32TimeStamp;
|
|
||||||
}else{
|
|
||||||
_strLastVideo.append((char *)pData,ui32Length);
|
|
||||||
_ui32LastVideoTime = ui32TimeStamp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mp4Maker::inputAAC(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp){
|
|
||||||
int64_t iTimeInc = (int64_t)ui32TimeStamp - (int64_t)_ui32LastAudioTime;
|
|
||||||
iTimeInc = MAX(0,MIN(iTimeInc,500));
|
|
||||||
if(iTimeInc == 0 || iTimeInc == 500){
|
|
||||||
WarnL << "abnormal time stamp increment:" << ui32TimeStamp << " " << _ui32LastAudioTime;
|
|
||||||
}
|
|
||||||
inputAAC_l((char *) pData, ui32Length, ui32TimeStamp);
|
|
||||||
_ui32LastAudioTime = ui32TimeStamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mp4Maker::inputH264_l(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp) {
|
|
||||||
GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);
|
|
||||||
auto iType = H264_TYPE(((uint8_t*)pData)[4]);
|
|
||||||
int32_t compositionTime;
|
|
||||||
if( iType >= H264Frame::NAL_IDR && (_movH265info.pMov == NULL || _ticker.elapsedTime() > recordSec * 1000)){
|
|
||||||
//在I帧率处新建MP4文件
|
|
||||||
//如果文件未创建或者文件超过10分钟则创建新文件
|
|
||||||
//每一个录制的MP4文件时间戳都要从0开始
|
|
||||||
_startPts = ui32TimeStamp;
|
|
||||||
createFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
char *pNualData = (char *)pData;
|
|
||||||
if (_movH265info.pMov!=NULL){
|
|
||||||
int vcl;
|
|
||||||
if (_movH265info.videoTrack < 0){
|
|
||||||
//解析解析SPS PPS,未添加track的时候执行
|
|
||||||
int n = h264_annexbtomp4(&_movH265info.avc, pData, ui32Length, _sBbuffer, sizeof(_sBbuffer), &vcl);
|
|
||||||
if (_movH265info.avc.nb_sps < 1 || _movH265info.avc.nb_pps < 1){
|
|
||||||
return; // waiting for sps/pps
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t sExtraData[64 * 1024];
|
|
||||||
int extraDataSize = mpeg4_avc_decoder_configuration_record_save(&_movH265info.avc, sExtraData, sizeof(sExtraData));
|
|
||||||
if (extraDataSize <= 0){
|
|
||||||
// invalid HVCC
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: waiting for key frame ???
|
|
||||||
_movH265info.videoTrack = mov_writer_add_video(_movH265info.pMov,
|
|
||||||
MOV_OBJECT_H264,
|
|
||||||
_movH265info.width,
|
|
||||||
_movH265info.height,
|
|
||||||
sExtraData,
|
|
||||||
extraDataSize);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( iType <= H264Frame::NAL_IDR ){
|
|
||||||
uint8_t *ptr = (uint8_t*)pData;
|
|
||||||
ptr[0] = (uint8_t)((ui32Length-4 >> 24) & 0xFF);
|
|
||||||
ptr[1] = (uint8_t)((ui32Length-4 >> 16) & 0xFF);
|
|
||||||
ptr[2] = (uint8_t)((ui32Length-4 >> 8) & 0xFF);
|
|
||||||
ptr[3] = (uint8_t)((ui32Length-4 >> 0) & 0xFF);
|
|
||||||
uint32_t ui32Pts = ui32TimeStamp < _movH265info.startPts ? 0 : ui32TimeStamp-_movH265info.startPts;
|
|
||||||
mov_writer_write(_movH265info.pMov,
|
|
||||||
_movH265info.videoTrack, pData, ui32Length,
|
|
||||||
ui32Pts,
|
|
||||||
ui32Pts,
|
|
||||||
iType == H264Frame::NAL_IDR ? MOV_AV_FLAG_KEYFREAME : 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mp4Maker::inputH265_l(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp) {
|
|
||||||
GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);
|
|
||||||
|
|
||||||
int32_t compositionTime;
|
|
||||||
auto iType = H265_TYPE(((uint8_t*)pData)[4]);
|
|
||||||
if( iType >= H265Frame::NAL_IDR_W_RADL && (_movH265info.pMov == NULL || _ticker.elapsedTime() > recordSec * 1000)){
|
|
||||||
_h265Record = 1;
|
|
||||||
//每一个录制的MP4文件时间戳都要从0开始
|
|
||||||
_startPts = ui32TimeStamp;
|
|
||||||
|
|
||||||
//在I帧率处新建MP4文件
|
|
||||||
//如果文件未创建或者文件超过最长时间则创建新文件
|
|
||||||
createFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
char *pNualData = (char *)pData;
|
|
||||||
if (_movH265info.pMov!=NULL){
|
|
||||||
int vcl;
|
|
||||||
if (_movH265info.videoTrack < 0){
|
|
||||||
//解析解析VPS SPS PPS,未添加track的时候执行
|
|
||||||
int n = h265_annexbtomp4(&_movH265info.hevc, pData, ui32Length, _sBbuffer, sizeof(_sBbuffer), &vcl);
|
|
||||||
if (_movH265info.hevc.numOfArrays < 1){
|
|
||||||
return; // waiting for vps/sps/pps
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t sExtraData[64 * 1024];
|
|
||||||
int extraDataSize = mpeg4_hevc_decoder_configuration_record_save(&_movH265info.hevc, sExtraData, sizeof(sExtraData));
|
|
||||||
if (extraDataSize <= 0){
|
|
||||||
// invalid HVCC
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO: waiting for key frame ???
|
|
||||||
_movH265info.videoTrack = mov_writer_add_video(_movH265info.pMov, MOV_OBJECT_HEVC, _movH265info.width, _movH265info.height, sExtraData, extraDataSize);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( iType <= H265Frame::NAL_IDR_W_RADL )
|
|
||||||
{
|
|
||||||
uint8_t *ptr = (uint8_t*)pData;
|
|
||||||
ptr[0] = (uint8_t)((ui32Length-4 >> 24) & 0xFF);
|
|
||||||
ptr[1] = (uint8_t)((ui32Length-4 >> 16) & 0xFF);
|
|
||||||
ptr[2] = (uint8_t)((ui32Length-4 >> 8) & 0xFF);
|
|
||||||
ptr[3] = (uint8_t)((ui32Length-4 >> 0) & 0xFF);
|
|
||||||
|
|
||||||
uint32_t ui32Pts = ui32TimeStamp < _movH265info.startPts ? 0 : ui32TimeStamp-_movH265info.startPts;
|
|
||||||
mov_writer_write(_movH265info.pMov,
|
|
||||||
_movH265info.videoTrack,
|
|
||||||
pData,
|
|
||||||
ui32Length,
|
|
||||||
ui32Pts,
|
|
||||||
ui32Pts,
|
|
||||||
iType == H265Frame::NAL_IDR_W_RADL ? MOV_AV_FLAG_KEYFREAME : 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mp4Maker::inputAAC_l(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp) {
|
|
||||||
GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);
|
|
||||||
if (!_haveVideo && (_movH265info.pMov == NULL || _ticker.elapsedTime() > recordSec * 1000)) {
|
|
||||||
_startPts = ui32TimeStamp;
|
|
||||||
createFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-1 != _movH265info.audioTrack && _movH265info.pMov != NULL){
|
|
||||||
|
|
||||||
uint32_t ui32Pts = ui32TimeStamp < _movH265info.startPts ? 0 : ui32TimeStamp-_movH265info.startPts;
|
|
||||||
mov_writer_write(_movH265info.pMov, _movH265info.audioTrack, (uint8_t*)pData, ui32Length, ui32Pts, ui32Pts, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mp4Maker::createFile() {
|
void Mp4Maker::createFile() {
|
||||||
closeFile();
|
closeFile();
|
||||||
auto strDate = timeStr("%Y-%m-%d");
|
auto strDate = timeStr("%Y-%m-%d");
|
||||||
@ -327,82 +81,39 @@ void Mp4Maker::createFile() {
|
|||||||
_info.ui64StartedTime = ::time(NULL);
|
_info.ui64StartedTime = ::time(NULL);
|
||||||
_info.strFileName = strTime + ".mp4";
|
_info.strFileName = strTime + ".mp4";
|
||||||
_info.strFilePath = strFile;
|
_info.strFilePath = strFile;
|
||||||
|
|
||||||
GET_CONFIG(string,appName,Record::kAppName);
|
GET_CONFIG(string,appName,Record::kAppName);
|
||||||
|
|
||||||
_info.strUrl = appName + "/"
|
_info.strUrl = appName + "/"
|
||||||
+ _info.strAppName + "/"
|
+ _info.strAppName + "/"
|
||||||
+ _info.strStreamId + "/"
|
+ _info.strStreamId + "/"
|
||||||
+ strDate + "/"
|
+ strDate + "/"
|
||||||
+ strTime + ".mp4";
|
+ strTime + ".mp4";
|
||||||
|
|
||||||
//----record 业务逻辑----//
|
try {
|
||||||
|
_muxer = std::make_shared<MP4MuxerFile>(strFileTmp.data());
|
||||||
#if !defined(_WIN32)
|
for(auto &track :_tracks){
|
||||||
File::createfile_path(strFileTmp.data(), S_IRWXO | S_IRWXG | S_IRWXU);
|
if(track){
|
||||||
#else
|
//添加track
|
||||||
File::createfile_path(strFileTmp.data(), 0);
|
_muxer->addTrack(track);
|
||||||
#endif
|
}
|
||||||
|
|
||||||
memset(&_movH265info, 0, sizeof(_movH265info));
|
|
||||||
_movH265info.videoTrack = -1;
|
|
||||||
_movH265info.audioTrack = -1;
|
|
||||||
_movH265info.width = 0;
|
|
||||||
_movH265info.height = 0;
|
|
||||||
_movH265info.pFile = fopen(strFileTmp.data(), "wb+");
|
|
||||||
_movH265info.startPts = _startPts;
|
|
||||||
_movH265info.pMov = mov_writer_create(movfileBuffer(), _movH265info.pFile, 0/*MOV_FLAG_FASTSTART*/);
|
|
||||||
|
|
||||||
_strFileTmp = strFileTmp;
|
|
||||||
_strFile = strFile;
|
|
||||||
_ticker.resetTime();
|
|
||||||
|
|
||||||
if ( _h265Record ){
|
|
||||||
auto videoTrack = dynamic_pointer_cast<H265Track>(getTrack(TrackVideo));
|
|
||||||
if(videoTrack){
|
|
||||||
_movH265info.width = videoTrack->getVideoWidth();
|
|
||||||
_movH265info.height = videoTrack->getVideoHeight();
|
|
||||||
}
|
}
|
||||||
}else{
|
_strFileTmp = strFileTmp;
|
||||||
auto videoTrack = dynamic_pointer_cast<H264Track>(getTrack(TrackVideo));
|
_strFile = strFile;
|
||||||
if(videoTrack){
|
_createFileTicker.resetTime();
|
||||||
// auto &sps = videoTrack->getSps();
|
}catch(std::exception &ex) {
|
||||||
// auto &pps = videoTrack->getPps();
|
WarnL << ex.what();
|
||||||
_movH265info.width = videoTrack->getVideoWidth();
|
|
||||||
_movH265info.height = videoTrack->getVideoHeight();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( _movH265info.width <=0 || _movH265info.height <= 0 )
|
|
||||||
WarnL << "分辨率获取失败,MP4录制异常";
|
|
||||||
|
|
||||||
auto audioTrack = dynamic_pointer_cast<AACTrack>(getTrack(TrackAudio));
|
|
||||||
if(audioTrack){
|
|
||||||
_audioSampleRate = audioTrack->getAudioSampleRate();
|
|
||||||
_audioChannel = audioTrack->getAudioChannel();
|
|
||||||
uint8_t extra_data[64 * 1024];
|
|
||||||
_movH265info.audioTrack = mov_writer_add_audio(_movH265info.pMov, MOV_OBJECT_AAC, _audioChannel, 16, _audioSampleRate, audioTrack->getAacCfg().data(), 2);
|
|
||||||
if (-1 == _movH265info.audioTrack)
|
|
||||||
WarnL << "添加音频通道失败:" << strFileTmp;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mp4Maker::asyncClose() {
|
void Mp4Maker::asyncClose() {
|
||||||
|
auto muxer = _muxer;
|
||||||
auto strFileTmp = _strFileTmp;
|
auto strFileTmp = _strFileTmp;
|
||||||
auto strFile = _strFile;
|
auto strFile = _strFile;
|
||||||
auto info = _info;
|
auto info = _info;
|
||||||
|
WorkThreadPool::Instance().getExecutor()->async([muxer,strFileTmp,strFile,info]() {
|
||||||
FILE *pFile = _movH265info.pFile;
|
//获取文件录制时间,放在关闭mp4之前是为了忽略关闭mp4执行时间
|
||||||
mov_writer_t* hMp4 = _movH265info.pMov;
|
|
||||||
WorkThreadPool::Instance().getExecutor()->async([hMp4,strFileTmp,strFile,info,pFile]() {
|
|
||||||
//获取文件录制时间,放在MP4Close之前是为了忽略MP4Close执行时间
|
|
||||||
const_cast<Mp4Info&>(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime;
|
const_cast<Mp4Info&>(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime;
|
||||||
//MP4Close非常耗时,所以要放在后台线程执行
|
//关闭mp4非常耗时,所以要放在后台线程执行
|
||||||
if (hMp4 != NULL ){
|
const_cast<MP4MuxerFile::Ptr &>(muxer).reset();
|
||||||
mov_writer_destroy(hMp4);
|
|
||||||
fclose(pFile);
|
|
||||||
DebugL << "fclose end";
|
|
||||||
}
|
|
||||||
|
|
||||||
//临时文件名改成正式文件名,防止mp4未完成时被访问
|
//临时文件名改成正式文件名,防止mp4未完成时被访问
|
||||||
rename(strFileTmp.data(),strFile.data());
|
rename(strFileTmp.data(),strFile.data());
|
||||||
//获取文件大小
|
//获取文件大小
|
||||||
@ -415,42 +126,37 @@ void Mp4Maker::asyncClose() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Mp4Maker::closeFile() {
|
void Mp4Maker::closeFile() {
|
||||||
if (_movH265info.pMov != NULL) {
|
if (_muxer) {
|
||||||
asyncClose();
|
asyncClose();
|
||||||
memset(&_movH265info, 0, sizeof(_movH265info));
|
_muxer = nullptr;
|
||||||
_movH265info.pMov = NULL;
|
}
|
||||||
_movH265info.videoTrack = -1;
|
|
||||||
_movH265info.audioTrack = -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mp4Maker::onTrackFrame(const Frame::Ptr &frame) {
|
void Mp4Maker::onTrackFrame(const Frame::Ptr &frame) {
|
||||||
switch (frame->getCodecId()){
|
GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);
|
||||||
case CodecH264:{
|
if(!_muxer || ((_createFileTicker.elapsedTime() > recordSec * 1000) &&
|
||||||
//需要带00 00 00 01,方便在mov_writer_write的时候字节修改这4Byte为长度信息
|
(!_haveVideo || (_haveVideo && frame->keyFrame()))) ){
|
||||||
inputH264(frame->data() , frame->size(),frame->pts());
|
//成立条件
|
||||||
}
|
//1、_muxer为空
|
||||||
break;
|
//2、到了切片时间,并且只有音频
|
||||||
case CodecAAC:{
|
//3、到了切片时间,有视频并且遇到视频的关键帧
|
||||||
//不需要ADTS头
|
createFile();
|
||||||
inputAAC(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(),frame->pts());
|
}
|
||||||
}
|
|
||||||
break;
|
if(_muxer){
|
||||||
case CodecH265:{
|
//生成mp4文件
|
||||||
//需要带00 00 00 01,方便在mov_writer_write的时候字节修改这4Byte为长度信息
|
_muxer->inputFrame(frame);
|
||||||
inputH265(frame->data() , frame->size(),frame->pts());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mp4Maker::onAllTrackReady() {
|
void Mp4Maker::onAllTrackReady() {
|
||||||
|
//保存所有的track,为创建MP4MuxerFile做准备
|
||||||
|
_tracks.emplace_back(getTrack(TrackVideo));
|
||||||
|
_tracks.emplace_back(getTrack(TrackAudio));
|
||||||
_haveVideo = getTrack(TrackVideo).operator bool();
|
_haveVideo = getTrack(TrackVideo).operator bool();
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
|
||||||
|
|
||||||
#endif //ENABLE_MP4RECORD
|
#endif //ENABLE_MP4RECORD
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
||||||
@ -37,14 +37,7 @@
|
|||||||
#include "Util/TimeTicker.h"
|
#include "Util/TimeTicker.h"
|
||||||
#include "Util/TimeTicker.h"
|
#include "Util/TimeTicker.h"
|
||||||
#include "Common/MediaSink.h"
|
#include "Common/MediaSink.h"
|
||||||
#include "Extension/Track.h"
|
#include "MP4Muxer.h"
|
||||||
|
|
||||||
|
|
||||||
#include "mov-writer.h"
|
|
||||||
#include "mpeg4-hevc.h"
|
|
||||||
#include "mpeg4-avc.h"
|
|
||||||
|
|
||||||
|
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
@ -62,20 +55,6 @@ public:
|
|||||||
string strStreamId;//流ID
|
string strStreamId;//流ID
|
||||||
string strVhost;//vhost
|
string strVhost;//vhost
|
||||||
};
|
};
|
||||||
|
|
||||||
class MovH265Info {
|
|
||||||
public:
|
|
||||||
mov_writer_t* pMov;
|
|
||||||
struct mpeg4_hevc_t hevc;
|
|
||||||
struct mpeg4_avc_t avc;
|
|
||||||
int videoTrack;
|
|
||||||
int audioTrack;
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
uint32_t startPts;
|
|
||||||
FILE * pFile;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Mp4Maker : public MediaSink{
|
class Mp4Maker : public MediaSink{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<Mp4Maker> Ptr;
|
typedef std::shared_ptr<Mp4Maker> Ptr;
|
||||||
@ -99,38 +78,15 @@ private:
|
|||||||
void createFile();
|
void createFile();
|
||||||
void closeFile();
|
void closeFile();
|
||||||
void asyncClose();
|
void asyncClose();
|
||||||
|
|
||||||
//时间戳:参考频率1000
|
|
||||||
void inputH264(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp);
|
|
||||||
void inputH265(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp);
|
|
||||||
//时间戳:参考频率1000
|
|
||||||
void inputAAC(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp);
|
|
||||||
|
|
||||||
void inputH264_l(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp);
|
|
||||||
void inputH265_l(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp);
|
|
||||||
void inputAAC_l(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp);
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
MovH265Info _movH265info;
|
|
||||||
int _h265Record = 0;
|
|
||||||
uint32_t _startPts;
|
|
||||||
|
|
||||||
uint8_t _sBbuffer[2 * 1024 * 1024];
|
|
||||||
string _strPath;
|
string _strPath;
|
||||||
string _strFile;
|
string _strFile;
|
||||||
string _strFileTmp;
|
string _strFileTmp;
|
||||||
Ticker _ticker;
|
Ticker _createFileTicker;
|
||||||
|
|
||||||
string _strLastVideo;
|
|
||||||
string _strLastAudio;
|
|
||||||
|
|
||||||
uint32_t _ui32LastVideoTime = 0;
|
|
||||||
uint32_t _ui32LastAudioTime = 0;
|
|
||||||
Mp4Info _info;
|
Mp4Info _info;
|
||||||
|
|
||||||
bool _haveVideo = false;
|
bool _haveVideo = false;
|
||||||
int _audioSampleRate;
|
MP4MuxerFile::Ptr _muxer;
|
||||||
int _audioChannel;
|
list<Track::Ptr> _tracks;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
@ -31,16 +31,17 @@
|
|||||||
#include "Extension/Frame.h"
|
#include "Extension/Frame.h"
|
||||||
#include "Extension/Track.h"
|
#include "Extension/Track.h"
|
||||||
#include "Util/File.h"
|
#include "Util/File.h"
|
||||||
|
#include "Common/MediaSink.h"
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
class TsMuxer {
|
class TsMuxer : public MediaSink{
|
||||||
public:
|
public:
|
||||||
TsMuxer();
|
TsMuxer();
|
||||||
virtual ~TsMuxer();
|
virtual ~TsMuxer();
|
||||||
void addTrack(const Track::Ptr &track);
|
void addTrack(const Track::Ptr &track) override;
|
||||||
void inputFrame(const Frame::Ptr &frame);
|
void inputFrame(const Frame::Ptr &frame) override ;
|
||||||
protected:
|
protected:
|
||||||
virtual void onTs(const void *packet, int bytes,uint32_t timestamp,int flags) = 0;
|
virtual void onTs(const void *packet, int bytes,uint32_t timestamp,int flags) = 0;
|
||||||
void resetTracks();
|
void resetTracks();
|
||||||
|
Loading…
Reference in New Issue
Block a user