新增支持HTTP-fMP4 WebSocket-fMP4直播

This commit is contained in:
xiongziliang 2020-09-20 19:45:37 +08:00
parent 4ce1a25f09
commit d971eccf92
13 changed files with 576 additions and 37 deletions

View File

@ -13,7 +13,7 @@
## 项目特点 ## 项目特点
- 基于C++11开发避免使用裸指针代码稳定可靠性能优越。 - 基于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模式开发并发性能优越支持海量客户端连接。 - 使用多路复用/多线程/异步网络IO模式开发并发性能优越支持海量客户端连接。
- 代码经过长期大量的稳定性、性能测试,已经在线上商用验证已久。 - 代码经过长期大量的稳定性、性能测试,已经在线上商用验证已久。
- 支持linux、macos、ios、android、windows全平台。 - 支持linux、macos、ios、android、windows全平台。
@ -64,6 +64,10 @@
- 支持http[s]-ts直播 - 支持http[s]-ts直播
- 支持ws[s]-ts直播 - 支持ws[s]-ts直播
- fMP4
- 支持http[s]-fmp4直播
- 支持ws[s]-fmp4直播
- HTTP[S]与WebSocket - HTTP[S]与WebSocket
- 服务器支持`目录索引生成`,`文件下载`,`表单提交请求` - 服务器支持`目录索引生成`,`文件下载`,`表单提交请求`
- 客户端提供`文件下载器(支持断点续传)`,`接口请求器`,`文件上传器` - 客户端提供`文件下载器(支持断点续传)`,`接口请求器`,`文件上传器`

View File

@ -11,7 +11,7 @@
## Why ZLMediaKit? ## 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. - 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 threadextreme performance. - Multiplexing asynchronous network IO based on epoll and multi threadextreme performance.
- Well performance and stable test,can be used commercially. - Well performance and stable test,can be used commercially.
- Support linux, macos, ios, android, Windows Platforms. - Support linux, macos, ios, android, Windows Platforms.
@ -45,6 +45,9 @@
- TS - TS
- Support HTTP-TS/WebSocket-TS sever. - Support HTTP-TS/WebSocket-TS sever.
- fMP4
- Support HTTP-fMP4/WebSocket-fMP4 sever.
- HTTP[S] - HTTP[S]
- HTTP server,suppor directory meun、RESTful http api. - HTTP server,suppor directory meun、RESTful http api.
- HTTP client,downloader,uploader,and http api requester. - HTTP client,downloader,uploader,and http api requester.

View File

@ -34,6 +34,7 @@ MultiMuxerPrivate::MultiMuxerPrivate(const string &vhost, const string &app, con
} }
_ts = std::make_shared<TSMediaSourceMuxer>(vhost, app, stream); _ts = std::make_shared<TSMediaSourceMuxer>(vhost, app, stream);
_fmp4 = std::make_shared<FMP4MediaSourceMuxer>(vhost, app, stream);
} }
void MultiMuxerPrivate::resetTracks() { void MultiMuxerPrivate::resetTracks() {
@ -46,6 +47,9 @@ void MultiMuxerPrivate::resetTracks() {
if (_ts) { if (_ts) {
_ts->resetTracks(); _ts->resetTracks();
} }
if (_fmp4) {
_fmp4->resetTracks();
}
//拷贝智能指针目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 //拷贝智能指针目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
auto hls = _hls; auto hls = _hls;
@ -70,6 +74,9 @@ void MultiMuxerPrivate::setMediaListener(const std::weak_ptr<MediaSourceEvent> &
if (_ts) { if (_ts) {
_ts->setListener(listener); _ts->setListener(listener);
} }
if (_fmp4) {
_fmp4->setListener(listener);
}
auto hls = _hls; auto hls = _hls;
if (hls) { if (hls) {
hls->setListener(listener); hls->setListener(listener);
@ -81,6 +88,7 @@ int MultiMuxerPrivate::totalReaderCount() const {
return (_rtsp ? _rtsp->readerCount() : 0) + return (_rtsp ? _rtsp->readerCount() : 0) +
(_rtmp ? _rtmp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) +
(_ts ? _ts->readerCount() : 0) + (_ts ? _ts->readerCount() : 0) +
(_fmp4 ? _fmp4->readerCount() : 0) +
(hls ? hls->readerCount() : 0); (hls ? hls->readerCount() : 0);
} }
@ -159,6 +167,9 @@ void MultiMuxerPrivate::onTrackReady(const Track::Ptr &track) {
if (_ts) { if (_ts) {
_ts->addTrack(track); _ts->addTrack(track);
} }
if (_fmp4) {
_fmp4->addTrack(track);
}
//拷贝智能指针目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 //拷贝智能指针目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
auto hls = _hls; auto hls = _hls;
@ -176,6 +187,7 @@ bool MultiMuxerPrivate::isEnabled(){
return (_rtmp ? _rtmp->isEnabled() : false) || return (_rtmp ? _rtmp->isEnabled() : false) ||
(_rtsp ? _rtsp->isEnabled() : false) || (_rtsp ? _rtsp->isEnabled() : false) ||
(_ts ? _ts->isEnabled() : false) || (_ts ? _ts->isEnabled() : false) ||
(_fmp4 ? _fmp4->isEnabled() : false) ||
(hls ? hls->isEnabled() : false) || _mp4; (hls ? hls->isEnabled() : false) || _mp4;
} }
@ -189,6 +201,9 @@ void MultiMuxerPrivate::onTrackFrame(const Frame::Ptr &frame) {
if (_ts) { if (_ts) {
_ts->inputFrame(frame); _ts->inputFrame(frame);
} }
if (_fmp4) {
_fmp4->inputFrame(frame);
}
//拷贝智能指针目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 //拷贝智能指针目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
//此处使用智能指针拷贝来确保线程安全,比互斥锁性能更优 //此处使用智能指针拷贝来确保线程安全,比互斥锁性能更优
@ -239,6 +254,9 @@ void MultiMuxerPrivate::onAllTrackReady() {
if (_rtsp) { if (_rtsp) {
_rtsp->onAllTrackReady(); _rtsp->onAllTrackReady();
} }
if (_fmp4) {
_fmp4->onAllTrackReady();
}
if (_track_listener) { if (_track_listener) {
_track_listener->onAllTrackReady(); _track_listener->onAllTrackReady();
} }

View File

@ -19,6 +19,7 @@
#include "Rtsp/RtspMediaSourceMuxer.h" #include "Rtsp/RtspMediaSourceMuxer.h"
#include "Rtmp/RtmpMediaSourceMuxer.h" #include "Rtmp/RtmpMediaSourceMuxer.h"
#include "TS/TSMediaSourceMuxer.h" #include "TS/TSMediaSourceMuxer.h"
#include "FMP4/FMP4MediaSourceMuxer.h"
namespace mediakit{ namespace mediakit{
@ -58,6 +59,7 @@ private:
HlsRecorder::Ptr _hls; HlsRecorder::Ptr _hls;
MediaSinkInterface::Ptr _mp4; MediaSinkInterface::Ptr _mp4;
TSMediaSourceMuxer::Ptr _ts; TSMediaSourceMuxer::Ptr _ts;
FMP4MediaSourceMuxer::Ptr _fmp4;
std::weak_ptr<MediaSourceEvent> _listener; std::weak_ptr<MediaSourceEvent> _listener;
}; };

View File

@ -48,6 +48,7 @@ bool loadIniConfig(const char *ini_path = nullptr);
#define RTMP_SCHEMA "rtmp" #define RTMP_SCHEMA "rtmp"
#define HLS_SCHEMA "hls" #define HLS_SCHEMA "hls"
#define TS_SCHEMA "ts" #define TS_SCHEMA "ts"
#define FMP4_SCHEMA "fmp4"
#define DEFAULT_VHOST "__defaultVhost__" #define DEFAULT_VHOST "__defaultVhost__"
////////////广播名称/////////// ////////////广播名称///////////

148
src/FMP4/FMP4MediaSource.h Normal file
View File

@ -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<FMP4Packet>;
template<typename ...ARGS>
FMP4Packet(ARGS && ...args) : BufferString(std::forward<ARGS>(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<FMP4Packet::Ptr>, public PacketCache<FMP4Packet, FMP4FlushPolicy>{
public:
using Ptr = std::shared_ptr<FMP4MediaSource>;
using RingDataType = std::shared_ptr<List<FMP4Packet::Ptr> >;
using RingType = RingBuffer<RingDataType>;
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<FMP4Packet, FMP4FlushPolicy>::inputPacket(true, packet, key);
}
/**
* GOP缓存
*/
void clearCache() override {
PacketCache<FMP4Packet, FMP4FlushPolicy>::clearCache();
_ring->clearCache();
}
private:
void createRing(){
weak_ptr<FMP4MediaSource> weak_self = dynamic_pointer_cast<FMP4MediaSource>(shared_from_this());
_ring = std::make_shared<RingType>(_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<List<FMP4Packet::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

View File

@ -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<FMP4MediaSourceMuxer> {
public:
using Ptr = std::shared_ptr<FMP4MediaSourceMuxer>;
FMP4MediaSourceMuxer(const string &vhost,
const string &app,
const string &stream_id) {
_media_src = std::make_shared<FMP4MediaSource>(vhost, app, stream_id);
}
~FMP4MediaSourceMuxer() override = default;
void setListener(const std::weak_ptr<MediaSourceEvent> &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<FMP4Packet>(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

View File

@ -93,7 +93,7 @@ void HttpSession::onError(const SockException& err) {
if(_is_live_stream){ if(_is_live_stream){
uint64_t duration = _ticker.createdTime()/1000; uint64_t duration = _ticker.createdTime()/1000;
//flv/ts播放器 //flv/ts播放器
WarnP(this) << "FLV/TS播放器(" WarnP(this) << "FLV/TS/FMP4播放器("
<< _mediaInfo._vhost << "/" << _mediaInfo._vhost << "/"
<< _mediaInfo._app << "/" << _mediaInfo._app << "/"
<< _mediaInfo._streamid << _mediaInfo._streamid
@ -156,6 +156,12 @@ bool HttpSession::checkWebSocket(){
return true; return true;
} }
//判断是否为websocket-fmp4
if (checkLiveStreamFMP4(res_cb)) {
//这里是websocket-fmp4直播请求
return true;
}
//这是普通的websocket连接 //这是普通的websocket连接
if (!onWebSocketConnect(_parser)) { if (!onWebSocketConnect(_parser)) {
sendResponse("501 Not Implemented", true, nullptr, headerOut); sendResponse("501 Not Implemented", true, nullptr, headerOut);
@ -234,15 +240,55 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffi
return true; return true;
} }
//http-fmp4 链接格式:http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2
bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb){
return checkLiveStream(FMP4_SCHEMA, ".live.mp4", [this, cb](const MediaSource::Ptr &src) {
auto fmp4_src = dynamic_pointer_cast<FMP4MediaSource>(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<BufferString>(fmp4_src->getInitSegment()), true);
weak_ptr<HttpSession> weak_self = dynamic_pointer_cast<HttpSession>(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 //http-ts 链接格式:http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2
//如果url(除去?以及后面的参数)后缀是.ts,那么表明该url是一个http-ts直播。
bool HttpSession::checkLiveStreamTS(const function<void()> &cb){ bool HttpSession::checkLiveStreamTS(const function<void()> &cb){
return checkLiveStream(TS_SCHEMA, ".live.ts", [this, cb](const MediaSource::Ptr &src) { return checkLiveStream(TS_SCHEMA, ".live.ts", [this, cb](const MediaSource::Ptr &src) {
auto ts_src = dynamic_pointer_cast<TSMediaSource>(src); auto ts_src = dynamic_pointer_cast<TSMediaSource>(src);
assert(ts_src); assert(ts_src);
if (!cb) { if (!cb) {
//找到源发送http头负载后续发送 //找到源发送http头负载后续发送
sendResponse("200 OK", false, "video/mp2t", KeyValue(), nullptr, true); sendResponse("200 OK", false, HttpFileManager::getContentType(".ts").data(), KeyValue(), nullptr, true);
} else { } else {
//自定义发送http头 //自定义发送http头
cb(); cb();
@ -276,14 +322,13 @@ bool HttpSession::checkLiveStreamTS(const function<void()> &cb){
} }
//http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2 //http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2
//如果url(除去?以及后面的参数)后缀是.flv,那么表明该url是一个http-flv直播。
bool HttpSession::checkLiveStreamFlv(const function<void()> &cb){ bool HttpSession::checkLiveStreamFlv(const function<void()> &cb){
return checkLiveStream(RTMP_SCHEMA, ".flv", [this, cb](const MediaSource::Ptr &src) { return checkLiveStream(RTMP_SCHEMA, ".flv", [this, cb](const MediaSource::Ptr &src) {
auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src); auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
assert(rtmp_src); assert(rtmp_src);
if (!cb) { if (!cb) {
//找到源发送http头负载后续发送 //找到源发送http头负载后续发送
sendResponse("200 OK", false, "video/x-flv", KeyValue(), nullptr, true); sendResponse("200 OK", false, HttpFileManager::getContentType(".flv").data(), KeyValue(), nullptr, true);
} else { } else {
//自定义发送http头 //自定义发送http头
cb(); cb();
@ -325,6 +370,11 @@ void HttpSession::Handle_Req_GET_l(int64_t &content_len, bool sendBody) {
return; return;
} }
if (checkLiveStreamFMP4()) {
//拦截http-fmp4播放器
return;
}
bool bClose = !strcasecmp(_parser["Connection"].data(),"close"); bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this()); weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
HttpFileManager::onAccessPath(*this, _parser, [weakSelf, bClose](const string &status_code, const string &content_type, HttpFileManager::onAccessPath(*this, _parser, [weakSelf, bClose](const string &status_code, const string &content_type,

View File

@ -20,6 +20,7 @@
#include "HttpCookieManager.h" #include "HttpCookieManager.h"
#include "HttpFileManager.h" #include "HttpFileManager.h"
#include "TS/TSMediaSource.h" #include "TS/TSMediaSource.h"
#include "FMP4/FMP4MediaSource.h"
using namespace std; using namespace std;
using namespace toolkit; using namespace toolkit;
@ -109,6 +110,7 @@ private:
bool checkLiveStreamFlv(const function<void()> &cb = nullptr); bool checkLiveStreamFlv(const function<void()> &cb = nullptr);
bool checkLiveStreamTS(const function<void()> &cb = nullptr); bool checkLiveStreamTS(const function<void()> &cb = nullptr);
bool checkLiveStreamFMP4(const function<void()> &fmp4_list = nullptr);
bool checkWebSocket(); bool checkWebSocket();
bool emitHttpEvent(bool doInvoke); bool emitHttpEvent(bool doInvoke);
@ -131,6 +133,7 @@ private:
Ticker _ticker; Ticker _ticker;
MediaInfo _mediaInfo; MediaInfo _mediaInfo;
TSMediaSource::RingType::RingReader::Ptr _ts_reader; TSMediaSource::RingType::RingReader::Ptr _ts_reader;
FMP4MediaSource::RingType::RingReader::Ptr _fmp4_reader;
//处理content数据的callback //处理content数据的callback
function<bool (const char *data,uint64_t len) > _contentCallBack; function<bool (const char *data,uint64_t len) > _contentCallBack;
}; };

View File

@ -216,5 +216,51 @@ uint64_t MP4FileDisk::onTell() {
return ftell64(_file.get()); 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 }//namespace mediakit
#endif //NABLE_MP4RECORD #endif //NABLE_MP4RECORD

View File

@ -117,6 +117,33 @@ private:
std::shared_ptr<FILE> _file; std::shared_ptr<FILE> _file;
}; };
class MP4FileMemory : public MP4FileIO{
public:
using Ptr = std::shared_ptr<MP4FileMemory>;
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 }//namespace mediakit
#endif //NABLE_MP4RECORD #endif //NABLE_MP4RECORD
#endif //ZLMEDIAKIT_MP4_H #endif //ZLMEDIAKIT_MP4_H

View File

@ -21,26 +21,50 @@ MP4Muxer::~MP4Muxer() {
} }
void MP4Muxer::openMP4(const string &file){ void MP4Muxer::openMP4(const string &file){
_file_name = file;
closeMP4(); closeMP4();
openFile(_file_name.data(), "wb+"); _file_name = file;
_mp4_file = std::make_shared<MP4FileDisk>();
_mp4_file->openFile(_file_name.data(), "wb+");
}
MP4FileIO::Writer MP4Muxer::createWriter(){
GET_CONFIG(bool, mp4FastStart, Record::kFastStart); 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(){ void MP4Muxer::closeMP4(){
_mov_writter = nullptr; MP4MuxerInterface::resetTracks();
closeFile(); _mp4_file = nullptr;
} }
void MP4Muxer::resetTracks() { void MP4Muxer::resetTracks() {
_codec_to_trackid.clear(); MP4MuxerInterface::resetTracks();
_started = false;
_have_video = false;
openMP4(_file_name); 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()); auto it = _codec_to_trackid.find(frame->getCodecId());
if(it == _codec_to_trackid.end()){ if(it == _codec_to_trackid.end()){
//该Track不存在或初始化失败 //该Track不存在或初始化失败
@ -134,7 +158,7 @@ static uint8_t getObject(CodecId codecId){
} }
} }
void MP4Muxer::stampSync(){ void MP4MuxerInterface::stampSync(){
if(_codec_to_trackid.size() < 2){ if(_codec_to_trackid.size() < 2){
return; 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()); auto mp4_object = getObject(track->getCodecId());
if (!mp4_object) { if (!mp4_object) {
WarnL << "MP4录制不支持该编码格式:" << track->getCodecName(); WarnL << "MP4录制不支持该编码格式:" << track->getCodecName();
@ -287,5 +314,54 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
stampSync(); stampSync();
} }
/////////////////////////////////////////// MP4MuxerMemory /////////////////////////////////////////////
MP4MuxerMemory::MP4MuxerMemory() {
_memory_file = std::make_shared<MP4FileMemory>();
}
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<MP4FileMemory>();
_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 }//namespace mediakit
#endif//#ifdef ENABLE_MP4 #endif//#ifdef ENABLE_MP4

View File

@ -23,17 +23,16 @@
namespace mediakit{ namespace mediakit{
class MP4Muxer : public MediaSinkInterface, public MP4FileDisk{ class MP4MuxerInterface : public MediaSinkInterface {
public: public:
typedef std::shared_ptr<MP4Muxer> Ptr; MP4MuxerInterface() = default;
~MP4MuxerInterface() override = default;
MP4Muxer();
~MP4Muxer() override;
/** /**
* ready状态的track * ready状态的track
*/ */
void addTrack(const Track::Ptr &track) override; void addTrack(const Track::Ptr &track) override;
/** /**
* *
*/ */
@ -44,6 +43,51 @@ public:
*/ */
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<Frame::Ptr> _frameCached;
unordered_map<int, track_info> _codec_to_trackid;
};
class MP4Muxer : public MP4MuxerInterface{
public:
typedef std::shared_ptr<MP4Muxer> Ptr;
MP4Muxer();
~MP4Muxer() override;
/**
* track
*/
void resetTracks() override;
/** /**
* mp4 * mp4
* @param file * @param file
@ -55,22 +99,54 @@ public:
*/ */
void closeMP4(); void closeMP4();
private: protected:
void stampSync(); MP4FileIO::Writer createWriter() override;
private: private:
struct track_info {
int track_id = -1;
Stamp stamp;
};
unordered_map<int, track_info> _codec_to_trackid;
List<Frame::Ptr> _frameCached;
bool _started = false;
bool _have_video = false;
string _file_name; 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 }//namespace mediakit
#endif//#ifdef ENABLE_MP4 #endif//#ifdef ENABLE_MP4
#endif //ZLMEDIAKIT_MP4MUXER_H #endif //ZLMEDIAKIT_MP4MUXER_H