mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-25 20:27:34 +08:00
新增支持HTTP-fMP4 WebSocket-fMP4直播
This commit is contained in:
parent
4ce1a25f09
commit
d971eccf92
@ -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全平台。
|
||||||
@ -63,6 +63,10 @@
|
|||||||
- TS
|
- TS
|
||||||
- 支持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
|
||||||
- 服务器支持`目录索引生成`,`文件下载`,`表单提交请求`
|
- 服务器支持`目录索引生成`,`文件下载`,`表单提交请求`
|
||||||
|
@ -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 thread,extreme performance.
|
- Multiplexing asynchronous network IO based on epoll and multi thread,extreme 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.
|
||||||
@ -44,6 +44,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.
|
||||||
|
@ -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,7 +88,8 @@ 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) +
|
||||||
(hls ? hls->readerCount() : 0) ;
|
(_fmp4 ? _fmp4->readerCount() : 0) +
|
||||||
|
(hls ? hls->readerCount() : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::shared_ptr<MediaSinkInterface> makeRecorder(const vector<Track::Ptr> &tracks, Recorder::type type, const string &custom_path, MediaSource &sender){
|
static std::shared_ptr<MediaSinkInterface> makeRecorder(const vector<Track::Ptr> &tracks, Recorder::type type, const string &custom_path, MediaSource &sender){
|
||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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
148
src/FMP4/FMP4MediaSource.h
Normal 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
|
85
src/FMP4/FMP4MediaSourceMuxer.h
Normal file
85
src/FMP4/FMP4MediaSourceMuxer.h
Normal 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
|
@ -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,
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入帧
|
* 输入帧
|
||||||
*/
|
*/
|
||||||
@ -42,7 +41,52 @@ public:
|
|||||||
/**
|
/**
|
||||||
* 重置所有track
|
* 重置所有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<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
|
||||||
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user