From 383a14a76d01843496d33c92403ce759e89437d9 Mon Sep 17 00:00:00 2001 From: xiongziliang <771730766@qq.com> Date: Thu, 1 Aug 2019 18:49:04 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=96=B0mp4=E5=BD=95=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3rdpart/media-server | 2 +- src/Extension/AAC.h | 8 + src/Extension/Frame.h | 12 ++ src/Extension/H264.h | 20 ++ src/Extension/H265.h | 37 +++- src/Extension/H265Rtp.cpp | 42 ++-- src/Extension/H265Rtp.h | 1 + src/MediaFile/MP4Muxer.cpp | 230 ++++++++++++++++++++ src/MediaFile/MP4Muxer.h | 98 +++++++++ src/MediaFile/MediaRecorder.cpp | 4 +- src/MediaFile/MediaRecorder.h | 4 +- src/MediaFile/Mp4Maker.cpp | 368 ++++---------------------------- src/MediaFile/Mp4Maker.h | 54 +---- src/MediaFile/TsMuxer.h | 7 +- 14 files changed, 470 insertions(+), 417 deletions(-) create mode 100644 src/MediaFile/MP4Muxer.cpp create mode 100644 src/MediaFile/MP4Muxer.h diff --git a/3rdpart/media-server b/3rdpart/media-server index a32fe104..6df71e01 160000 --- a/3rdpart/media-server +++ b/3rdpart/media-server @@ -1 +1 @@ -Subproject commit a32fe104c0290bc01a7ee73d50b568e8d9ded931 +Subproject commit 6df71e01c174cdfe69e597cc4acb766a20b28620 diff --git a/src/Extension/AAC.h b/src/Extension/AAC.h index e445b7fe..2af513f6 100644 --- a/src/Extension/AAC.h +++ b/src/Extension/AAC.h @@ -79,6 +79,10 @@ public: bool keyFrame() const override { return false; } + + bool configFrame() const override{ + return false; + } public: unsigned int syncword = 0; //12 bslbf 同步字The bit string ‘1111 1111 1111’,说明一个ADTS帧的开始 unsigned int id; //1 bslbf MPEG 标示符, 设置为1 @@ -127,6 +131,10 @@ public: bool keyFrame() const override { return false; } + + bool configFrame() const override{ + return false; + } } ; diff --git a/src/Extension/Frame.h b/src/Extension/Frame.h index 5d49753a..79b5d02a 100644 --- a/src/Extension/Frame.h +++ b/src/Extension/Frame.h @@ -117,6 +117,12 @@ public: */ virtual bool keyFrame() const = 0; + /** + * 是否为配置帧,譬如sps pps vps + * @return + */ + virtual bool configFrame() const = 0; + /** * 是否可以缓存 */ @@ -371,6 +377,7 @@ public: _trackType = frame->getTrackType(); _codec = frame->getCodecId(); _key = frame->keyFrame(); + _config = frame->configFrame(); } virtual ~FrameCacheAble() = default; @@ -394,12 +401,17 @@ public: bool keyFrame() const override{ return _key; } + + bool configFrame() const override{ + return _config; + } private: Frame::Ptr _frame; BufferRaw::Ptr _buffer; TrackType _trackType; CodecId _codec; bool _key; + bool _config; }; diff --git a/src/Extension/H264.h b/src/Extension/H264.h index 54e28e07..4cb91135 100644 --- a/src/Extension/H264.h +++ b/src/Extension/H264.h @@ -82,6 +82,16 @@ public: bool keyFrame() const override { 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: uint32_t timeStamp; uint32_t ptsStamp = 0; @@ -118,6 +128,16 @@ public: bool keyFrame() const override { 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; + } + } }; /** diff --git a/src/Extension/H265.h b/src/Extension/H265.h index 7063d470..7557b628 100644 --- a/src/Extension/H265.h +++ b/src/Extension/H265.h @@ -86,6 +86,10 @@ public: return timeStamp; } + uint32_t pts() const override { + return ptsStamp ? ptsStamp : timeStamp; + } + uint32_t prefixSize() const override { return iPrefixSize; } @@ -99,7 +103,18 @@ public: } 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) { @@ -117,9 +132,8 @@ public: } public: - uint16_t sequence; uint32_t timeStamp; - unsigned char type; + uint32_t ptsStamp = 0; string buffer; uint32_t iPrefixSize = 4; }; @@ -146,8 +160,18 @@ public: } bool keyFrame() const override { - int type = H265_TYPE(((uint8_t *) _ptr)[_prefixSize]); - return H265Frame::isKeyFrame(type); + return H265Frame::isKeyFrame(H265_TYPE(((uint8_t *) _ptr)[_prefixSize])); + } + + 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()){ auto vpsFrame = std::make_shared(); - vpsFrame->type = H265Frame::NAL_VPS; vpsFrame->iPrefixSize = 4; vpsFrame->buffer.assign("\x0\x0\x0\x1", 4); vpsFrame->buffer.append(_vps); @@ -337,7 +360,6 @@ private: } if (!_sps.empty()) { auto spsFrame = std::make_shared(); - spsFrame->type = H265Frame::NAL_SPS; spsFrame->iPrefixSize = 4; spsFrame->buffer.assign("\x0\x0\x0\x1", 4); spsFrame->buffer.append(_sps); @@ -347,7 +369,6 @@ private: if (!_pps.empty()) { auto ppsFrame = std::make_shared(); - ppsFrame->type = H265Frame::NAL_PPS; ppsFrame->iPrefixSize = 4; ppsFrame->buffer.assign("\x0\x0\x0\x1", 4); ppsFrame->buffer.append(_pps); diff --git a/src/Extension/H265Rtp.cpp b/src/Extension/H265Rtp.cpp index 9d34d925..4fc00042 100644 --- a/src/Extension/H265Rtp.cpp +++ b/src/Extension/H265Rtp.cpp @@ -99,56 +99,56 @@ bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { // fragmentation unit (FU) FU fu; MakeFU(frame[2], fu); - if (fu.S == 1) { - //FU-A start + if (fu.S) { + //该帧的第一个rtp包 _h265frame->buffer.assign("\x0\x0\x0\x1", 4); _h265frame->buffer.push_back(fu.type << 1); _h265frame->buffer.push_back(0x01); _h265frame->buffer.append((char *) frame + 3, length - 3); - _h265frame->type = fu.type; _h265frame->timeStamp = rtppack->timeStamp; - _h265frame->sequence = rtppack->sequence; + //该函数return时,保存下当前sequence,以便下次对比seq是否连续 + _lastSeq = rtppack->sequence; 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(); - WarnL << "丢包,帧废弃:" << rtppack->sequence << "," << _h265frame->sequence; + WarnL << "rtp sequence不连续: " << rtppack->sequence << " != " << _lastSeq << " + 1,该帧被废弃"; return false; } - _h265frame->sequence = rtppack->sequence; - if (fu.E == 1) { - //FU-A end + + if (!fu.E) { + //该帧的中间rtp包 _h265frame->buffer.append((char *) frame + 3, length - 3); - _h265frame->timeStamp = rtppack->timeStamp; - auto isIDR = _h265frame->keyFrame(); - onGetH265(_h265frame); - return isIDR; + //该函数return时,保存下当前sequence,以便下次对比seq是否连续 + _lastSeq = rtppack->sequence; + return false; } - //FU-A mid + + //该帧最后一个rtp包 _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) //a full frame _h265frame->buffer.assign("\x0\x0\x0\x1", 4); _h265frame->buffer.append((char *)frame, length); - _h265frame->type = nal; _h265frame->timeStamp = rtppack->timeStamp; - _h265frame->sequence = rtppack->sequence; - auto isIDR = _h265frame->keyFrame(); + auto key = _h265frame->keyFrame(); onGetH265(_h265frame); - return (isIDR); //i frame + return key; } } void H265RtpDecoder::onGetH265(const H265Frame::Ptr &frame) { //写入环形缓存 - auto lastSeq = _h265frame->sequence; RtpCodec::inputFrame(frame); _h265frame = obtainFrame(); - _h265frame->sequence = lastSeq; } diff --git a/src/Extension/H265Rtp.h b/src/Extension/H265Rtp.h index 9c42937b..f243baf2 100644 --- a/src/Extension/H265Rtp.h +++ b/src/Extension/H265Rtp.h @@ -65,6 +65,7 @@ private: H265Frame::Ptr obtainFrame(); private: H265Frame::Ptr _h265frame; + int _lastSeq = 0; }; /** diff --git a/src/MediaFile/MP4Muxer.cpp b/src/MediaFile/MP4Muxer.cpp new file mode 100644 index 00000000..f9f736df --- /dev/null +++ b/src/MediaFile/MP4Muxer.cpp @@ -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(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(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(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 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 diff --git a/src/MediaFile/MP4Muxer.h b/src/MediaFile/MP4Muxer.h new file mode 100644 index 00000000..80d82a54 --- /dev/null +++ b/src/MediaFile/MP4Muxer.h @@ -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_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 _codec_to_trackid; +}; + + +class MP4MuxerFile : public MP4Muxer { +public: + typedef std::shared_ptr 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; +}; + +}//namespace mediakit + +#endif//#ifdef ENABLE_MP4RECORD + +#endif //ZLMEDIAKIT_MP4MUXER_H diff --git a/src/MediaFile/MediaRecorder.cpp b/src/MediaFile/MediaRecorder.cpp index be08867d..71eac5e4 100644 --- a/src/MediaFile/MediaRecorder.cpp +++ b/src/MediaFile/MediaRecorder.cpp @@ -106,11 +106,11 @@ void MediaRecorder::addTrack(const Track::Ptr &track) { } #endif //defined(ENABLE_HLS) -#if defined(ENABLE_MP4V2) +#if defined(ENABLE_MP4RECORD) if (_mp4Maker) { _mp4Maker->addTrack(track); } -#endif //defined(ENABLE_MP4V2) +#endif //defined(ENABLE_MP4RECORD) } } /* namespace mediakit */ diff --git a/src/MediaFile/MediaRecorder.h b/src/MediaFile/MediaRecorder.h index b1b72119..da06885e 100644 --- a/src/MediaFile/MediaRecorder.h +++ b/src/MediaFile/MediaRecorder.h @@ -64,9 +64,9 @@ private: std::shared_ptr _hlsMaker; #endif //defined(ENABLE_HLS) -#if defined(ENABLE_MP4V2) +#if defined(ENABLE_MP4RECORD) std::shared_ptr _mp4Maker; -#endif //defined(ENABLE_MP4V2) +#endif //defined(ENABLE_MP4RECORD) }; } /* namespace mediakit */ diff --git a/src/MediaFile/Mp4Maker.cpp b/src/MediaFile/Mp4Maker.cpp index 96d6af99..d4f814ee 100644 --- a/src/MediaFile/Mp4Maker.cpp +++ b/src/MediaFile/Mp4Maker.cpp @@ -29,68 +29,14 @@ #include #include "Common/config.h" #include "Mp4Maker.h" -#include "MediaRecorder.h" -#include "Util/File.h" -#include "Util/mini.h" #include "Util/util.h" #include "Util/NoticeCenter.h" -#include "Extension/H264.h" -#include "Extension/H265.h" -#include "Extension/AAC.h" #include "Thread/WorkThreadPool.h" - -#include "mov-buffer.h" -#include "mov-format.h" - - - using namespace toolkit; 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) { std::tm tm_snapshot; auto time = ::time(NULL); @@ -119,203 +65,11 @@ Mp4Maker::Mp4Maker(const string& strPath, _info.strStreamId = strStreamId; _info.strVhost = strVhost; _info.strFolder = strPath; - - memset(&_movH265info, 0, sizeof(_movH265info)); - _movH265info.videoTrack = -1; - _movH265info.audioTrack = -1; - - //----record 业务逻辑----// } Mp4Maker::~Mp4Maker() { 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() { closeFile(); auto strDate = timeStr("%Y-%m-%d"); @@ -327,82 +81,39 @@ void Mp4Maker::createFile() { _info.ui64StartedTime = ::time(NULL); _info.strFileName = strTime + ".mp4"; _info.strFilePath = strFile; - GET_CONFIG(string,appName,Record::kAppName); - _info.strUrl = appName + "/" + _info.strAppName + "/" + _info.strStreamId + "/" + strDate + "/" + strTime + ".mp4"; - //----record 业务逻辑----// - -#if !defined(_WIN32) - File::createfile_path(strFileTmp.data(), S_IRWXO | S_IRWXG | S_IRWXU); -#else - File::createfile_path(strFileTmp.data(), 0); -#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(getTrack(TrackVideo)); - if(videoTrack){ - _movH265info.width = videoTrack->getVideoWidth(); - _movH265info.height = videoTrack->getVideoHeight(); + try { + _muxer = std::make_shared(strFileTmp.data()); + for(auto &track :_tracks){ + if(track){ + //添加track + _muxer->addTrack(track); + } } - }else{ - auto videoTrack = dynamic_pointer_cast(getTrack(TrackVideo)); - if(videoTrack){ -// auto &sps = videoTrack->getSps(); -// auto &pps = videoTrack->getPps(); - _movH265info.width = videoTrack->getVideoWidth(); - _movH265info.height = videoTrack->getVideoHeight(); - } - } - if ( _movH265info.width <=0 || _movH265info.height <= 0 ) - WarnL << "分辨率获取失败,MP4录制异常"; - - auto audioTrack = dynamic_pointer_cast(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; + _strFileTmp = strFileTmp; + _strFile = strFile; + _createFileTicker.resetTime(); + }catch(std::exception &ex) { + WarnL << ex.what(); } } void Mp4Maker::asyncClose() { + auto muxer = _muxer; auto strFileTmp = _strFileTmp; auto strFile = _strFile; auto info = _info; - - FILE *pFile = _movH265info.pFile; - mov_writer_t* hMp4 = _movH265info.pMov; - WorkThreadPool::Instance().getExecutor()->async([hMp4,strFileTmp,strFile,info,pFile]() { - //获取文件录制时间,放在MP4Close之前是为了忽略MP4Close执行时间 + WorkThreadPool::Instance().getExecutor()->async([muxer,strFileTmp,strFile,info]() { + //获取文件录制时间,放在关闭mp4之前是为了忽略关闭mp4执行时间 const_cast(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime; - //MP4Close非常耗时,所以要放在后台线程执行 - if (hMp4 != NULL ){ - mov_writer_destroy(hMp4); - fclose(pFile); - DebugL << "fclose end"; - } - + //关闭mp4非常耗时,所以要放在后台线程执行 + const_cast(muxer).reset(); //临时文件名改成正式文件名,防止mp4未完成时被访问 rename(strFileTmp.data(),strFile.data()); //获取文件大小 @@ -415,42 +126,37 @@ void Mp4Maker::asyncClose() { } void Mp4Maker::closeFile() { - if (_movH265info.pMov != NULL) { + if (_muxer) { asyncClose(); - memset(&_movH265info, 0, sizeof(_movH265info)); - _movH265info.pMov = NULL; - _movH265info.videoTrack = -1; - _movH265info.audioTrack = -1; - } + _muxer = nullptr; + } } void Mp4Maker::onTrackFrame(const Frame::Ptr &frame) { - switch (frame->getCodecId()){ - case CodecH264:{ - //需要带00 00 00 01,方便在mov_writer_write的时候字节修改这4Byte为长度信息 - inputH264(frame->data() , frame->size(),frame->pts()); - } - break; - case CodecAAC:{ - //不需要ADTS头 - inputAAC(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(),frame->pts()); - } - break; - case CodecH265:{ - //需要带00 00 00 01,方便在mov_writer_write的时候字节修改这4Byte为长度信息 - inputH265(frame->data() , frame->size(),frame->pts()); - } - break; - default: - break; + GET_CONFIG(uint32_t,recordSec,Record::kFileSecond); + if(!_muxer || ((_createFileTicker.elapsedTime() > recordSec * 1000) && + (!_haveVideo || (_haveVideo && frame->keyFrame()))) ){ + //成立条件 + //1、_muxer为空 + //2、到了切片时间,并且只有音频 + //3、到了切片时间,有视频并且遇到视频的关键帧 + createFile(); + } + + if(_muxer){ + //生成mp4文件 + _muxer->inputFrame(frame); } } void Mp4Maker::onAllTrackReady() { + //保存所有的track,为创建MP4MuxerFile做准备 + _tracks.emplace_back(getTrack(TrackVideo)); + _tracks.emplace_back(getTrack(TrackAudio)); _haveVideo = getTrack(TrackVideo).operator bool(); } } /* namespace mediakit */ -#endif //ENABLE_MP4RECORD \ No newline at end of file +#endif //ENABLE_MP4RECORD diff --git a/src/MediaFile/Mp4Maker.h b/src/MediaFile/Mp4Maker.h index 4967690d..1d46a7fa 100644 --- a/src/MediaFile/Mp4Maker.h +++ b/src/MediaFile/Mp4Maker.h @@ -1,4 +1,4 @@ -/* + /* * MIT License * * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> @@ -37,14 +37,7 @@ #include "Util/TimeTicker.h" #include "Util/TimeTicker.h" #include "Common/MediaSink.h" -#include "Extension/Track.h" - - -#include "mov-writer.h" -#include "mpeg4-hevc.h" -#include "mpeg4-avc.h" - - +#include "MP4Muxer.h" using namespace toolkit; namespace mediakit { @@ -62,20 +55,6 @@ public: string strStreamId;//流ID 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{ public: typedef std::shared_ptr Ptr; @@ -99,38 +78,15 @@ private: void createFile(); void closeFile(); 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: - - MovH265Info _movH265info; - int _h265Record = 0; - uint32_t _startPts; - - uint8_t _sBbuffer[2 * 1024 * 1024]; string _strPath; string _strFile; string _strFileTmp; - Ticker _ticker; - - string _strLastVideo; - string _strLastAudio; - - uint32_t _ui32LastVideoTime = 0; - uint32_t _ui32LastAudioTime = 0; + Ticker _createFileTicker; Mp4Info _info; - bool _haveVideo = false; - int _audioSampleRate; - int _audioChannel; + MP4MuxerFile::Ptr _muxer; + list _tracks; }; } /* namespace mediakit */ diff --git a/src/MediaFile/TsMuxer.h b/src/MediaFile/TsMuxer.h index 88dae724..8185307f 100644 --- a/src/MediaFile/TsMuxer.h +++ b/src/MediaFile/TsMuxer.h @@ -31,16 +31,17 @@ #include "Extension/Frame.h" #include "Extension/Track.h" #include "Util/File.h" +#include "Common/MediaSink.h" using namespace toolkit; namespace mediakit { -class TsMuxer { +class TsMuxer : public MediaSink{ public: TsMuxer(); virtual ~TsMuxer(); - void addTrack(const Track::Ptr &track); - void inputFrame(const Frame::Ptr &frame); + void addTrack(const Track::Ptr &track) override; + void inputFrame(const Frame::Ptr &frame) override ; protected: virtual void onTs(const void *packet, int bytes,uint32_t timestamp,int flags) = 0; void resetTracks();