From d971eccf92b4b6a3705446ce2fde864464016726 Mon Sep 17 00:00:00 2001 From: xiongziliang <771730766@qq.com> Date: Sun, 20 Sep 2020 19:45:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81HTTP-fMP4?= =?UTF-8?q?=20WebSocket-fMP4=E7=9B=B4=E6=92=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- README_en.md | 5 +- src/Common/MultiMediaSourceMuxer.cpp | 20 +++- src/Common/MultiMediaSourceMuxer.h | 2 + src/Common/config.h | 1 + src/FMP4/FMP4MediaSource.h | 148 +++++++++++++++++++++++++++ src/FMP4/FMP4MediaSourceMuxer.h | 85 +++++++++++++++ src/Http/HttpSession.cpp | 60 ++++++++++- src/Http/HttpSession.h | 3 + src/Record/MP4.cpp | 46 +++++++++ src/Record/MP4.h | 27 +++++ src/Record/MP4Muxer.cpp | 98 ++++++++++++++++-- src/Record/MP4Muxer.h | 112 ++++++++++++++++---- 13 files changed, 576 insertions(+), 37 deletions(-) create mode 100644 src/FMP4/FMP4MediaSource.h create mode 100644 src/FMP4/FMP4MediaSourceMuxer.h diff --git a/README.md b/README.md index 4461ed8a..82293146 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ ## 项目特点 - 基于C++11开发,避免使用裸指针,代码稳定可靠,性能优越。 -- 支持多种协议(RTSP/RTMP/HLS/HTTP-FLV/Websocket-FLV/GB28181/HTTP-TS/Websocket-TS/MP4),支持协议互转。 +- 支持多种协议(RTSP/RTMP/HLS/HTTP-FLV/Websocket-FLV/GB28181/HTTP-TS/Websocket-TS/HTTP-fMP4/Websocket-fMP4/MP4),支持协议互转。 - 使用多路复用/多线程/异步网络IO模式开发,并发性能优越,支持海量客户端连接。 - 代码经过长期大量的稳定性、性能测试,已经在线上商用验证已久。 - 支持linux、macos、ios、android、windows全平台。 @@ -63,6 +63,10 @@ - TS - 支持http[s]-ts直播 - 支持ws[s]-ts直播 + +- fMP4 + - 支持http[s]-fmp4直播 + - 支持ws[s]-fmp4直播 - HTTP[S]与WebSocket - 服务器支持`目录索引生成`,`文件下载`,`表单提交请求` diff --git a/README_en.md b/README_en.md index dc521043..7193f7b2 100644 --- a/README_en.md +++ b/README_en.md @@ -11,7 +11,7 @@ ## Why ZLMediaKit? - Developed based on C++ 11, the code is stable and reliable, avoiding the use of raw pointers, cross-platform porting is simple and convenient, and the code is clear and concise. -- Support rich streaming media protocols(`RTSP/RTMP/HLS/HTTP-FLV/WebSocket-flv/HTTP-TS/WebSocket-TS`),and support Inter-protocol conversion. +- Support rich streaming media protocols(`RTSP/RTMP/HLS/HTTP-FLV/WebSocket-flv/HTTP-TS/WebSocket-TS/HTTP-fMP4/Websocket-fMP4/MP4`),and support Inter-protocol conversion. - Multiplexing asynchronous network IO based on epoll and multi thread,extreme performance. - Well performance and stable test,can be used commercially. - Support linux, macos, ios, android, Windows Platforms. @@ -44,6 +44,9 @@ - TS - Support HTTP-TS/WebSocket-TS sever. + +- fMP4 + - Support HTTP-fMP4/WebSocket-fMP4 sever. - HTTP[S] - HTTP server,suppor directory meun、RESTful http api. diff --git a/src/Common/MultiMediaSourceMuxer.cpp b/src/Common/MultiMediaSourceMuxer.cpp index caeac717..9ef5a678 100644 --- a/src/Common/MultiMediaSourceMuxer.cpp +++ b/src/Common/MultiMediaSourceMuxer.cpp @@ -34,6 +34,7 @@ MultiMuxerPrivate::MultiMuxerPrivate(const string &vhost, const string &app, con } _ts = std::make_shared(vhost, app, stream); + _fmp4 = std::make_shared(vhost, app, stream); } void MultiMuxerPrivate::resetTracks() { @@ -46,6 +47,9 @@ void MultiMuxerPrivate::resetTracks() { if (_ts) { _ts->resetTracks(); } + if (_fmp4) { + _fmp4->resetTracks(); + } //拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 auto hls = _hls; @@ -70,6 +74,9 @@ void MultiMuxerPrivate::setMediaListener(const std::weak_ptr & if (_ts) { _ts->setListener(listener); } + if (_fmp4) { + _fmp4->setListener(listener); + } auto hls = _hls; if (hls) { hls->setListener(listener); @@ -81,7 +88,8 @@ int MultiMuxerPrivate::totalReaderCount() const { return (_rtsp ? _rtsp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) + (_ts ? _ts->readerCount() : 0) + - (hls ? hls->readerCount() : 0) ; + (_fmp4 ? _fmp4->readerCount() : 0) + + (hls ? hls->readerCount() : 0); } static std::shared_ptr makeRecorder(const vector &tracks, Recorder::type type, const string &custom_path, MediaSource &sender){ @@ -159,6 +167,9 @@ void MultiMuxerPrivate::onTrackReady(const Track::Ptr &track) { if (_ts) { _ts->addTrack(track); } + if (_fmp4) { + _fmp4->addTrack(track); + } //拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 auto hls = _hls; @@ -176,6 +187,7 @@ bool MultiMuxerPrivate::isEnabled(){ return (_rtmp ? _rtmp->isEnabled() : false) || (_rtsp ? _rtsp->isEnabled() : false) || (_ts ? _ts->isEnabled() : false) || + (_fmp4 ? _fmp4->isEnabled() : false) || (hls ? hls->isEnabled() : false) || _mp4; } @@ -189,6 +201,9 @@ void MultiMuxerPrivate::onTrackFrame(const Frame::Ptr &frame) { if (_ts) { _ts->inputFrame(frame); } + if (_fmp4) { + _fmp4->inputFrame(frame); + } //拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 //此处使用智能指针拷贝来确保线程安全,比互斥锁性能更优 @@ -239,6 +254,9 @@ void MultiMuxerPrivate::onAllTrackReady() { if (_rtsp) { _rtsp->onAllTrackReady(); } + if (_fmp4) { + _fmp4->onAllTrackReady(); + } if (_track_listener) { _track_listener->onAllTrackReady(); } diff --git a/src/Common/MultiMediaSourceMuxer.h b/src/Common/MultiMediaSourceMuxer.h index b1c64751..04833b06 100644 --- a/src/Common/MultiMediaSourceMuxer.h +++ b/src/Common/MultiMediaSourceMuxer.h @@ -19,6 +19,7 @@ #include "Rtsp/RtspMediaSourceMuxer.h" #include "Rtmp/RtmpMediaSourceMuxer.h" #include "TS/TSMediaSourceMuxer.h" +#include "FMP4/FMP4MediaSourceMuxer.h" namespace mediakit{ @@ -58,6 +59,7 @@ private: HlsRecorder::Ptr _hls; MediaSinkInterface::Ptr _mp4; TSMediaSourceMuxer::Ptr _ts; + FMP4MediaSourceMuxer::Ptr _fmp4; std::weak_ptr _listener; }; diff --git a/src/Common/config.h b/src/Common/config.h index 49e13615..c352b1f7 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -48,6 +48,7 @@ bool loadIniConfig(const char *ini_path = nullptr); #define RTMP_SCHEMA "rtmp" #define HLS_SCHEMA "hls" #define TS_SCHEMA "ts" +#define FMP4_SCHEMA "fmp4" #define DEFAULT_VHOST "__defaultVhost__" ////////////广播名称/////////// diff --git a/src/FMP4/FMP4MediaSource.h b/src/FMP4/FMP4MediaSource.h new file mode 100644 index 00000000..129d7e24 --- /dev/null +++ b/src/FMP4/FMP4MediaSource.h @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_FMP4MEDIASOURCE_H +#define ZLMEDIAKIT_FMP4MEDIASOURCE_H + +#include "Common/MediaSource.h" +using namespace toolkit; +#define FMP4_GOP_SIZE 512 + +namespace mediakit { + +//FMP4直播数据包 +class FMP4Packet : public BufferString{ +public: + using Ptr = std::shared_ptr; + + template + FMP4Packet(ARGS && ...args) : BufferString(std::forward(args)...) {}; + ~FMP4Packet() override = default; + +public: + uint32_t time_stamp = 0; +}; + +//FMP4直播合并写策略类 +class FMP4FlushPolicy : public FlushPolicy{ +public: + FMP4FlushPolicy() = default; + ~FMP4FlushPolicy() = default; + + uint32_t getStamp(const FMP4Packet::Ptr &packet) { + return packet->time_stamp; + } +}; + +//FMP4直播源 +class FMP4MediaSource : public MediaSource, public RingDelegate, public PacketCache{ +public: + using Ptr = std::shared_ptr; + using RingDataType = std::shared_ptr >; + using RingType = RingBuffer; + + FMP4MediaSource(const string &vhost, + const string &app, + const string &stream_id, + int ring_size = FMP4_GOP_SIZE) : MediaSource(FMP4_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {} + + ~FMP4MediaSource() override = default; + + /** + * 获取媒体源的环形缓冲 + */ + const RingType::Ptr &getRing() const { + return _ring; + } + + /** + * 获取fmp4 init segment + */ + const string &getInitSegment() const{ + return _init_segment; + } + + /** + * 设置fmp4 init segment + * @param str init segment + */ + void setInitSegment(string str) { + _init_segment = std::move(str); + if (_ring) { + regist(); + } + } + + /** + * 获取播放器个数 + */ + int readerCount() override { + return _ring ? _ring->readerCount() : 0; + } + + /** + * 输入FMP4包 + * @param packet FMP4包 + * @param key 是否为关键帧第一个包 + */ + void onWrite(const FMP4Packet::Ptr &packet, bool key) override { + if (!_ring) { + createRing(); + } + if (key) { + _have_video = true; + } + PacketCache::inputPacket(true, packet, key); + } + + /** + * 情况GOP缓存 + */ + void clearCache() override { + PacketCache::clearCache(); + _ring->clearCache(); + } + +private: + void createRing(){ + weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + _ring = std::make_shared(_ring_size, [weak_self](int size) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->onReaderChanged(size); + }); + onReaderChanged(0); + if (!_init_segment.empty()) { + regist(); + } + } + + /** + * 合并写回调 + * @param packet_list 合并写缓存列队 + * @param key_pos 是否包含关键帧 + */ + void onFlush(std::shared_ptr > &packet_list, bool key_pos) override { + //如果不存在视频,那么就没有存在GOP缓存的意义,所以确保一直清空GOP缓存 + _ring->write(packet_list, _have_video ? key_pos : true); + } + +private: + bool _have_video = false; + int _ring_size; + string _init_segment; + RingType::Ptr _ring; +}; + + +}//namespace mediakit +#endif //ZLMEDIAKIT_FMP4MEDIASOURCE_H diff --git a/src/FMP4/FMP4MediaSourceMuxer.h b/src/FMP4/FMP4MediaSourceMuxer.h new file mode 100644 index 00000000..4333d088 --- /dev/null +++ b/src/FMP4/FMP4MediaSourceMuxer.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H +#define ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H + +#include "FMP4MediaSource.h" +#include "Record/MP4Muxer.h" + +namespace mediakit { + +class FMP4MediaSourceMuxer : public MP4MuxerMemory, public MediaSourceEventInterceptor, + public std::enable_shared_from_this { +public: + using Ptr = std::shared_ptr; + + FMP4MediaSourceMuxer(const string &vhost, + const string &app, + const string &stream_id) { + _media_src = std::make_shared(vhost, app, stream_id); + } + + ~FMP4MediaSourceMuxer() override = default; + + void setListener(const std::weak_ptr &listener){ + _listener = listener; + _media_src->setListener(shared_from_this()); + } + + int readerCount() const{ + return _media_src->readerCount(); + } + + void onReaderChanged(MediaSource &sender, int size) override { + _enabled = size; + if (!size) { + _clear_cache = true; + } + MediaSourceEventInterceptor::onReaderChanged(sender, size); + } + + void inputFrame(const Frame::Ptr &frame) override { + if (_clear_cache) { + _clear_cache = false; + _media_src->clearCache(); + } + if (_enabled) { + MP4MuxerMemory::inputFrame(frame); + } + } + + bool isEnabled() { + //缓存尚未清空时,还允许触发inputFrame函数,以便及时清空缓存 + return _clear_cache ? true : _enabled; + } + + void onAllTrackReady() { + _media_src->setInitSegment(getInitSegment()); + } + +protected: + void onSegmentData(const string &string, uint32_t stamp, bool key_frame) override { + if (string.empty()) { + return; + } + FMP4Packet::Ptr packet = std::make_shared(std::move(string)); + packet->time_stamp = stamp; + _media_src->onWrite(packet, key_frame); + } + +private: + bool _enabled = true; + bool _clear_cache = false; + FMP4MediaSource::Ptr _media_src; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H diff --git a/src/Http/HttpSession.cpp b/src/Http/HttpSession.cpp index bc145759..ba996d93 100644 --- a/src/Http/HttpSession.cpp +++ b/src/Http/HttpSession.cpp @@ -93,7 +93,7 @@ void HttpSession::onError(const SockException& err) { if(_is_live_stream){ uint64_t duration = _ticker.createdTime()/1000; //flv/ts播放器 - WarnP(this) << "FLV/TS播放器(" + WarnP(this) << "FLV/TS/FMP4播放器(" << _mediaInfo._vhost << "/" << _mediaInfo._app << "/" << _mediaInfo._streamid @@ -156,6 +156,12 @@ bool HttpSession::checkWebSocket(){ return true; } + //判断是否为websocket-fmp4 + if (checkLiveStreamFMP4(res_cb)) { + //这里是websocket-fmp4直播请求 + return true; + } + //这是普通的websocket连接 if (!onWebSocketConnect(_parser)) { sendResponse("501 Not Implemented", true, nullptr, headerOut); @@ -234,15 +240,55 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffi return true; } +//http-fmp4 链接格式:http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2 +bool HttpSession::checkLiveStreamFMP4(const function &cb){ + return checkLiveStream(FMP4_SCHEMA, ".live.mp4", [this, cb](const MediaSource::Ptr &src) { + auto fmp4_src = dynamic_pointer_cast(src); + assert(fmp4_src); + if (!cb) { + //找到源,发送http头,负载后续发送 + sendResponse("200 OK", false, HttpFileManager::getContentType(".mp4").data(), KeyValue(), nullptr, true); + } else { + //自定义发送http头 + cb(); + } + + //直播牺牲延时提升发送性能 + setSocketFlags(); + onWrite(std::make_shared(fmp4_src->getInitSegment()), true); + weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + _fmp4_reader = fmp4_src->getRing()->attach(getPoller()); + _fmp4_reader->setDetachCB([weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + //本对象已经销毁 + return; + } + strong_self->shutdown(SockException(Err_shutdown, "fmp4 ring buffer detached")); + }); + _fmp4_reader->setReadCB([weak_self](const FMP4MediaSource::RingDataType &fmp4_list) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + //本对象已经销毁 + return; + } + int i = 0; + int size = fmp4_list->size(); + fmp4_list->for_each([&](const FMP4Packet::Ptr &ts) { + strong_self->onWrite(ts, ++i == size); + }); + }); + }); +} + //http-ts 链接格式:http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2 -//如果url(除去?以及后面的参数)后缀是.ts,那么表明该url是一个http-ts直播。 bool HttpSession::checkLiveStreamTS(const function &cb){ return checkLiveStream(TS_SCHEMA, ".live.ts", [this, cb](const MediaSource::Ptr &src) { auto ts_src = dynamic_pointer_cast(src); assert(ts_src); if (!cb) { //找到源,发送http头,负载后续发送 - sendResponse("200 OK", false, "video/mp2t", KeyValue(), nullptr, true); + sendResponse("200 OK", false, HttpFileManager::getContentType(".ts").data(), KeyValue(), nullptr, true); } else { //自定义发送http头 cb(); @@ -276,14 +322,13 @@ bool HttpSession::checkLiveStreamTS(const function &cb){ } //http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2 -//如果url(除去?以及后面的参数)后缀是.flv,那么表明该url是一个http-flv直播。 bool HttpSession::checkLiveStreamFlv(const function &cb){ return checkLiveStream(RTMP_SCHEMA, ".flv", [this, cb](const MediaSource::Ptr &src) { auto rtmp_src = dynamic_pointer_cast(src); assert(rtmp_src); if (!cb) { //找到源,发送http头,负载后续发送 - sendResponse("200 OK", false, "video/x-flv", KeyValue(), nullptr, true); + sendResponse("200 OK", false, HttpFileManager::getContentType(".flv").data(), KeyValue(), nullptr, true); } else { //自定义发送http头 cb(); @@ -325,6 +370,11 @@ void HttpSession::Handle_Req_GET_l(int64_t &content_len, bool sendBody) { return; } + if (checkLiveStreamFMP4()) { + //拦截http-fmp4播放器 + return; + } + bool bClose = !strcasecmp(_parser["Connection"].data(),"close"); weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); HttpFileManager::onAccessPath(*this, _parser, [weakSelf, bClose](const string &status_code, const string &content_type, diff --git a/src/Http/HttpSession.h b/src/Http/HttpSession.h index 7430a06e..f1df5a8d 100644 --- a/src/Http/HttpSession.h +++ b/src/Http/HttpSession.h @@ -20,6 +20,7 @@ #include "HttpCookieManager.h" #include "HttpFileManager.h" #include "TS/TSMediaSource.h" +#include "FMP4/FMP4MediaSource.h" using namespace std; using namespace toolkit; @@ -109,6 +110,7 @@ private: bool checkLiveStreamFlv(const function &cb = nullptr); bool checkLiveStreamTS(const function &cb = nullptr); + bool checkLiveStreamFMP4(const function &fmp4_list = nullptr); bool checkWebSocket(); bool emitHttpEvent(bool doInvoke); @@ -131,6 +133,7 @@ private: Ticker _ticker; MediaInfo _mediaInfo; TSMediaSource::RingType::RingReader::Ptr _ts_reader; + FMP4MediaSource::RingType::RingReader::Ptr _fmp4_reader; //处理content数据的callback function _contentCallBack; }; diff --git a/src/Record/MP4.cpp b/src/Record/MP4.cpp index 750a257d..684e4be3 100644 --- a/src/Record/MP4.cpp +++ b/src/Record/MP4.cpp @@ -216,5 +216,51 @@ uint64_t MP4FileDisk::onTell() { return ftell64(_file.get()); } +/////////////////////////////////////////////////////MP4FileMemory///////////////////////////////////////////////////////// + +string MP4FileMemory::getAndClearMemory(){ + string ret; + ret.swap(_memory); + _offset = 0; + return ret; +} + +uint64_t MP4FileMemory::fileSize() const{ + return _memory.size(); +} + +uint64_t MP4FileMemory::onTell(){ + return _offset; +} + +int MP4FileMemory::onSeek(uint64_t offset){ + if (offset > _memory.size()) { + return -1; + } + _offset = offset; + return 0; +} + +int MP4FileMemory::onRead(void *data, uint64_t bytes){ + if (_offset >= _memory.size()) { + //EOF + return -1; + } + bytes = MIN(bytes, _memory.size() - _offset); + memcpy(data, _memory.data(), bytes); + _offset += bytes; + return 0; +} + +int MP4FileMemory::onWrite(const void *data, uint64_t bytes){ + if (_offset + bytes > _memory.size()) { + //需要扩容 + _memory.resize(_offset + bytes); + } + memcpy((uint8_t *) _memory.data() + _offset, data, bytes); + _offset += bytes; + return 0; +} + }//namespace mediakit #endif //NABLE_MP4RECORD diff --git a/src/Record/MP4.h b/src/Record/MP4.h index 244a83f0..3b7ae1c3 100644 --- a/src/Record/MP4.h +++ b/src/Record/MP4.h @@ -117,6 +117,33 @@ private: std::shared_ptr _file; }; +class MP4FileMemory : public MP4FileIO{ +public: + using Ptr = std::shared_ptr; + MP4FileMemory() = default; + ~MP4FileMemory() override = default; + + /** + * 获取文件大小 + */ + uint64_t fileSize() const; + + /** + * 获取并清空文件缓存 + */ + string getAndClearMemory(); + +protected: + uint64_t onTell() override; + int onSeek(uint64_t offset) override; + int onRead(void *data, uint64_t bytes) override; + int onWrite(const void *data, uint64_t bytes) override; + +private: + uint64_t _offset = 0; + string _memory; +}; + }//namespace mediakit #endif //NABLE_MP4RECORD #endif //ZLMEDIAKIT_MP4_H diff --git a/src/Record/MP4Muxer.cpp b/src/Record/MP4Muxer.cpp index 1fe8c355..471b6e09 100644 --- a/src/Record/MP4Muxer.cpp +++ b/src/Record/MP4Muxer.cpp @@ -21,26 +21,50 @@ MP4Muxer::~MP4Muxer() { } void MP4Muxer::openMP4(const string &file){ - _file_name = file; closeMP4(); - openFile(_file_name.data(), "wb+"); + _file_name = file; + _mp4_file = std::make_shared(); + _mp4_file->openFile(_file_name.data(), "wb+"); +} + +MP4FileIO::Writer MP4Muxer::createWriter(){ GET_CONFIG(bool, mp4FastStart, Record::kFastStart); - _mov_writter = createWriter(mp4FastStart ? MOV_FLAG_FASTSTART : 0, false); + return _mp4_file->createWriter(mp4FastStart ? MOV_FLAG_FASTSTART : 0, false); } void MP4Muxer::closeMP4(){ - _mov_writter = nullptr; - closeFile(); + MP4MuxerInterface::resetTracks(); + _mp4_file = nullptr; } void MP4Muxer::resetTracks() { - _codec_to_trackid.clear(); - _started = false; - _have_video = false; + MP4MuxerInterface::resetTracks(); openMP4(_file_name); } -void MP4Muxer::inputFrame(const Frame::Ptr &frame) { +/////////////////////////////////////////// MP4MuxerInterface ///////////////////////////////////////////// + +void MP4MuxerInterface::saveSegment(){ + mp4_writer_save_segment(_mov_writter.get()); +} + +void MP4MuxerInterface::initSegment(){ + mp4_writer_init_segment(_mov_writter.get()); +} + +bool MP4MuxerInterface::haveVideo() const{ + return _have_video; +} + +void MP4MuxerInterface::resetTracks() { + _started = false; + _have_video = false; + _mov_writter = nullptr; + _frameCached.clear(); + _codec_to_trackid.clear(); +} + +void MP4MuxerInterface::inputFrame(const Frame::Ptr &frame) { auto it = _codec_to_trackid.find(frame->getCodecId()); if(it == _codec_to_trackid.end()){ //该Track不存在或初始化失败 @@ -134,7 +158,7 @@ static uint8_t getObject(CodecId codecId){ } } -void MP4Muxer::stampSync(){ +void MP4MuxerInterface::stampSync(){ if(_codec_to_trackid.size() < 2){ return; } @@ -154,7 +178,10 @@ void MP4Muxer::stampSync(){ } } -void MP4Muxer::addTrack(const Track::Ptr &track) { +void MP4MuxerInterface::addTrack(const Track::Ptr &track) { + if (!_mov_writter) { + _mov_writter = createWriter(); + } auto mp4_object = getObject(track->getCodecId()); if (!mp4_object) { WarnL << "MP4录制不支持该编码格式:" << track->getCodecName(); @@ -287,5 +314,54 @@ void MP4Muxer::addTrack(const Track::Ptr &track) { stampSync(); } +/////////////////////////////////////////// MP4MuxerMemory ///////////////////////////////////////////// + +MP4MuxerMemory::MP4MuxerMemory() { + _memory_file = std::make_shared(); +} + +MP4FileIO::Writer MP4MuxerMemory::createWriter() { + return _memory_file->createWriter(MOV_FLAG_SEGMENT, true); +} + +const string &MP4MuxerMemory::getInitSegment(){ + if (_init_segment.empty()) { + initSegment(); + saveSegment(); + _init_segment = _memory_file->getAndClearMemory(); + } + return _init_segment; +} + +void MP4MuxerMemory::resetTracks(){ + MP4MuxerInterface::resetTracks(); + _memory_file = std::make_shared(); + _init_segment.clear(); +} + +void MP4MuxerMemory::inputFrame(const Frame::Ptr &frame){ + if (_init_segment.empty()) { + //尚未生成init segment + return; + } + + bool key_frame = frame->keyFrame(); + if (_ticker.elapsedTime() > 50 || key_frame) { + //遇到关键帧或者超过50ms则切片 + _ticker.resetTime(); + //flush切片 + saveSegment(); + //输出切片数据 + onSegmentData(_memory_file->getAndClearMemory(), frame->dts(), _key_frame); + _key_frame = false; + } + + if (key_frame) { + _key_frame = true; + } + MP4MuxerInterface::inputFrame(frame); +} + + }//namespace mediakit #endif//#ifdef ENABLE_MP4 diff --git a/src/Record/MP4Muxer.h b/src/Record/MP4Muxer.h index 26153fea..479e98d5 100644 --- a/src/Record/MP4Muxer.h +++ b/src/Record/MP4Muxer.h @@ -23,17 +23,16 @@ namespace mediakit{ -class MP4Muxer : public MediaSinkInterface, public MP4FileDisk{ +class MP4MuxerInterface : public MediaSinkInterface { public: - typedef std::shared_ptr Ptr; - - MP4Muxer(); - ~MP4Muxer() override; + MP4MuxerInterface() = default; + ~MP4MuxerInterface() override = default; /** * 添加已经ready状态的track */ - void addTrack(const Track::Ptr & track) override; + void addTrack(const Track::Ptr &track) override; + /** * 输入帧 */ @@ -42,7 +41,52 @@ public: /** * 重置所有track */ - void resetTracks() override ; + void resetTracks() override; + + /** + * 是否包含视频 + */ + bool haveVideo() const; + + /** + * 保存fmp4分片 + */ + void saveSegment(); + + /** + * 创建新切片 + */ + void initSegment(); + +protected: + virtual MP4FileIO::Writer createWriter() = 0; + +private: + void stampSync(); + +private: + bool _started = false; + bool _have_video = false; + MP4FileIO::Writer _mov_writter; + struct track_info { + int track_id = -1; + Stamp stamp; + }; + List _frameCached; + unordered_map _codec_to_trackid; +}; + +class MP4Muxer : public MP4MuxerInterface{ +public: + typedef std::shared_ptr Ptr; + + MP4Muxer(); + ~MP4Muxer() override; + + /** + * 重置所有track + */ + void resetTracks() override; /** * 打开mp4 @@ -55,22 +99,54 @@ public: */ void closeMP4(); -private: - void stampSync(); +protected: + MP4FileIO::Writer createWriter() override; private: - struct track_info { - int track_id = -1; - Stamp stamp; - }; - unordered_map _codec_to_trackid; - List _frameCached; - bool _started = false; - bool _have_video = false; string _file_name; - Writer _mov_writter; + MP4FileDisk::Ptr _mp4_file; }; +class MP4MuxerMemory : public MP4MuxerInterface{ +public: + MP4MuxerMemory(); + ~MP4MuxerMemory() override = default; + + /** + * 重置所有track + */ + void resetTracks() override; + + /** + * 输入帧 + */ + void inputFrame(const Frame::Ptr &frame) override; + + /** + * 获取fmp4 init segment + */ + const string &getInitSegment(); + +protected: + /** + * 输出fmp4切片回调函数 + * @param string 切片内容 + * @param stamp 切片末尾时间戳 + * @param key_frame 是否有关键帧 + */ + virtual void onSegmentData(const string &string, uint32_t stamp, bool key_frame) = 0; + +protected: + MP4FileIO::Writer createWriter() override; + +private: + bool _key_frame = false; + Ticker _ticker; + string _init_segment; + MP4FileMemory::Ptr _memory_file; +}; + + }//namespace mediakit #endif//#ifdef ENABLE_MP4 #endif //ZLMEDIAKIT_MP4MUXER_H