mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2026-07-07 12:18:12 +08:00
@@ -12,23 +12,17 @@
|
||||
#include "Util/logger.h"
|
||||
#include "Util/base64.h"
|
||||
#include "Extension/AAC.h"
|
||||
#include "Extension/Opus.h"
|
||||
#include "Extension/G711.h"
|
||||
#include "Extension/H264.h"
|
||||
#include "Extension/H265.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
DevChannel::DevChannel(const string &vhost,
|
||||
const string &app,
|
||||
const string &stream_id,
|
||||
float duration,
|
||||
bool enable_rtsp,
|
||||
bool enable_rtmp,
|
||||
bool enable_hls,
|
||||
bool enable_mp4) :
|
||||
MultiMediaSourceMuxer(vhost, app, stream_id, duration, enable_rtsp, enable_rtmp, enable_hls, enable_mp4) {}
|
||||
DevChannel::DevChannel(const string &vhost, const string &app, const string &stream_id,
|
||||
float duration, bool enable_hls, bool enable_mp4) :
|
||||
MultiMediaSourceMuxer(vhost, app, stream_id, duration, true, true, enable_hls, enable_mp4) {}
|
||||
|
||||
DevChannel::~DevChannel() {}
|
||||
|
||||
@@ -109,11 +103,12 @@ void DevChannel::inputH265(const char *data, int len, uint32_t dts, uint32_t pts
|
||||
inputFrame(frame);
|
||||
}
|
||||
|
||||
class AACFrameCacheAble : public AACFrameNoCacheAble{
|
||||
class FrameAutoDelete : public FrameFromPtr{
|
||||
public:
|
||||
template <typename ... ARGS>
|
||||
AACFrameCacheAble(ARGS && ...args) : AACFrameNoCacheAble(std::forward<ARGS>(args)...){};
|
||||
virtual ~AACFrameCacheAble() {
|
||||
FrameAutoDelete(ARGS && ...args) : FrameFromPtr(std::forward<ARGS>(args)...){}
|
||||
|
||||
~FrameAutoDelete() override {
|
||||
delete [] _ptr;
|
||||
};
|
||||
|
||||
@@ -123,31 +118,32 @@ public:
|
||||
};
|
||||
|
||||
void DevChannel::inputAAC(const char *data_without_adts, int len, uint32_t dts, const char *adts_header){
|
||||
if(dts == 0){
|
||||
dts = (uint32_t)_aTicker[1].elapsedTime();
|
||||
if (dts == 0) {
|
||||
dts = (uint32_t) _aTicker[1].elapsedTime();
|
||||
}
|
||||
|
||||
if(adts_header){
|
||||
if(adts_header + 7 == data_without_adts){
|
||||
if (adts_header) {
|
||||
if (adts_header + ADTS_HEADER_LEN == data_without_adts) {
|
||||
//adts头和帧在一起
|
||||
inputFrame(std::make_shared<AACFrameNoCacheAble>((char *)data_without_adts - 7, len + 7, dts, 0, 7));
|
||||
}else{
|
||||
inputFrame(std::make_shared<FrameFromPtr>(_audio->codecId, (char *) data_without_adts - ADTS_HEADER_LEN, len + ADTS_HEADER_LEN, dts, 0, ADTS_HEADER_LEN));
|
||||
} else {
|
||||
//adts头和帧不在一起
|
||||
char *dataWithAdts = new char[len + 7];
|
||||
memcpy(dataWithAdts, adts_header, 7);
|
||||
memcpy(dataWithAdts + 7 , data_without_adts , len);
|
||||
inputFrame(std::make_shared<AACFrameCacheAble>(dataWithAdts, len + 7, dts, 0, 7));
|
||||
char *data_with_adts = new char[len + ADTS_HEADER_LEN];
|
||||
memcpy(data_with_adts, adts_header, ADTS_HEADER_LEN);
|
||||
memcpy(data_with_adts + ADTS_HEADER_LEN, data_without_adts, len);
|
||||
inputFrame(std::make_shared<FrameAutoDelete>(_audio->codecId, data_with_adts, len + ADTS_HEADER_LEN, dts, 0, ADTS_HEADER_LEN));
|
||||
}
|
||||
} else {
|
||||
//没有adts头
|
||||
inputFrame(std::make_shared<FrameFromPtr>(_audio->codecId, (char *) data_without_adts, len, dts, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void DevChannel::inputG711(const char *data, int len, uint32_t dts){
|
||||
void DevChannel::inputAudio(const char *data, int len, uint32_t dts){
|
||||
if (dts == 0) {
|
||||
dts = (uint32_t)_aTicker[1].elapsedTime();
|
||||
dts = (uint32_t) _aTicker[1].elapsedTime();
|
||||
}
|
||||
auto frame = std::make_shared<G711FrameNoCacheAble>((char*)data, len, dts, 0);
|
||||
frame->setCodec(_audio->codecId);
|
||||
inputFrame(frame);
|
||||
inputFrame(std::make_shared<FrameFromPtr>(_audio->codecId, (char *) data, len, dts, 0));
|
||||
}
|
||||
|
||||
void DevChannel::initVideo(const VideoInfo &info) {
|
||||
@@ -165,6 +161,7 @@ void DevChannel::initAudio(const AudioInfo &info) {
|
||||
case CodecAAC : addTrack(std::make_shared<AACTrack>()); break;
|
||||
case CodecG711A :
|
||||
case CodecG711U : addTrack(std::make_shared<G711Track>(info.codecId, info.iSampleRate, info.iChannel, info.iSampleBit)); break;
|
||||
case CodecOpus : addTrack(std::make_shared<OpusTrack>()); break;
|
||||
default: WarnL << "不支持该类型的音频编码类型:" << info.codecId; break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,9 @@
|
||||
#include "Util/util.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "Common/MultiMediaSourceMuxer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
|
||||
#ifdef ENABLE_FAAC
|
||||
#include "Codec/AACEncoder.h"
|
||||
#endif //ENABLE_FAAC
|
||||
@@ -55,16 +53,10 @@ class DevChannel : public MultiMediaSourceMuxer{
|
||||
public:
|
||||
typedef std::shared_ptr<DevChannel> Ptr;
|
||||
//fDuration<=0为直播,否则为点播
|
||||
DevChannel(const string &vhost,
|
||||
const string &app,
|
||||
const string &stream_id,
|
||||
float duration = 0,
|
||||
bool enable_rtsp = true,
|
||||
bool enable_rtmp = true,
|
||||
bool enable_hls = true,
|
||||
bool enable_mp4 = false);
|
||||
DevChannel(const string &vhost, const string &app, const string &stream_id,
|
||||
float duration = 0, bool enable_hls = true, bool enable_mp4 = false);
|
||||
|
||||
virtual ~DevChannel();
|
||||
~DevChannel() override ;
|
||||
|
||||
/**
|
||||
* 初始化视频Track
|
||||
@@ -108,12 +100,12 @@ public:
|
||||
void inputAAC(const char *data_without_adts, int len, uint32_t dts, const char *adts_header);
|
||||
|
||||
/**
|
||||
* G711音频帧
|
||||
* 输入OPUS/G711音频帧
|
||||
* @param data 音频帧
|
||||
* @param len 帧数据长度
|
||||
* @param dts 时间戳,单位毫秒
|
||||
*/
|
||||
void inputG711(const char* data, int len, uint32_t dts);
|
||||
void inputAudio(const char *data, int len, uint32_t dts);
|
||||
|
||||
#ifdef ENABLE_X264
|
||||
/**
|
||||
|
||||
@@ -161,7 +161,7 @@ vector<Track::Ptr> MediaSink::getTracks(bool trackReady) const{
|
||||
}
|
||||
ret.emplace_back(pr.second);
|
||||
}
|
||||
return std::move(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ public:
|
||||
*/
|
||||
virtual void addTrack(const Track::Ptr & track) = 0;
|
||||
|
||||
/**
|
||||
* 添加所有Track完毕
|
||||
*/
|
||||
virtual void addTrackCompleted() {}
|
||||
|
||||
/**
|
||||
* 重置track
|
||||
*/
|
||||
@@ -70,7 +75,7 @@ public:
|
||||
* 这样会增加生成流的延时,如果添加了音视频双Track,那么可以不调用此方法
|
||||
* 否则为了降低流注册延时,请手动调用此方法
|
||||
*/
|
||||
void addTrackCompleted();
|
||||
void addTrackCompleted() override;
|
||||
|
||||
/**
|
||||
* 重置track
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include "MediaSource.h"
|
||||
#include "Record/MP4Reader.h"
|
||||
#include "Util/util.h"
|
||||
@@ -17,16 +16,19 @@
|
||||
using namespace toolkit;
|
||||
namespace mediakit {
|
||||
|
||||
recursive_mutex MediaSource::g_mtxMediaSrc;
|
||||
MediaSource::SchemaVhostAppStreamMap MediaSource::g_mapMediaSrc;
|
||||
recursive_mutex s_media_source_mtx;
|
||||
MediaSource::SchemaVhostAppStreamMap s_media_source_map;
|
||||
|
||||
MediaSource::MediaSource(const string &strSchema, const string &strVhost, const string &strApp, const string &strId) :
|
||||
_strSchema(strSchema), _strApp(strApp), _strId(strId) {
|
||||
if (strVhost.empty()) {
|
||||
_strVhost = DEFAULT_VHOST;
|
||||
MediaSource::MediaSource(const string &schema, const string &vhost, const string &app, const string &stream_id){
|
||||
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
|
||||
if (!enableVhost) {
|
||||
_vhost = DEFAULT_VHOST;
|
||||
} else {
|
||||
_strVhost = strVhost;
|
||||
_vhost = vhost.empty() ? DEFAULT_VHOST : vhost;
|
||||
}
|
||||
_schema = schema;
|
||||
_app = app;
|
||||
_stream_id = stream_id;
|
||||
}
|
||||
|
||||
MediaSource::~MediaSource() {
|
||||
@@ -34,32 +36,28 @@ MediaSource::~MediaSource() {
|
||||
}
|
||||
|
||||
const string& MediaSource::getSchema() const {
|
||||
return _strSchema;
|
||||
return _schema;
|
||||
}
|
||||
|
||||
const string& MediaSource::getVhost() const {
|
||||
return _strVhost;
|
||||
return _vhost;
|
||||
}
|
||||
|
||||
const string& MediaSource::getApp() const {
|
||||
//获取该源的id
|
||||
return _strApp;
|
||||
return _app;
|
||||
}
|
||||
|
||||
const string& MediaSource::getId() const {
|
||||
return _strId;
|
||||
return _stream_id;
|
||||
}
|
||||
|
||||
vector<Track::Ptr> MediaSource::getTracks(bool trackReady) const {
|
||||
auto strongPtr = _track_source.lock();
|
||||
if(strongPtr){
|
||||
return strongPtr->getTracks(trackReady);
|
||||
vector<Track::Ptr> MediaSource::getTracks(bool ready) const {
|
||||
auto listener = _listener.lock();
|
||||
if(!listener){
|
||||
return vector<Track::Ptr>();
|
||||
}
|
||||
return vector<Track::Ptr>();
|
||||
}
|
||||
|
||||
void MediaSource::setTrackSource(const std::weak_ptr<TrackSource> &track_src) {
|
||||
_track_source = track_src;
|
||||
return listener->getTracks(const_cast<MediaSource &>(*this), ready);
|
||||
}
|
||||
|
||||
void MediaSource::setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
||||
@@ -77,12 +75,13 @@ int MediaSource::totalReaderCount(){
|
||||
}
|
||||
return listener->totalReaderCount(*this);
|
||||
}
|
||||
bool MediaSource::seekTo(uint32_t ui32Stamp) {
|
||||
|
||||
bool MediaSource::seekTo(uint32_t stamp) {
|
||||
auto listener = _listener.lock();
|
||||
if(!listener){
|
||||
return false;
|
||||
}
|
||||
return listener->seekTo(*this,ui32Stamp);
|
||||
return listener->seekTo(*this, stamp);
|
||||
}
|
||||
|
||||
bool MediaSource::close(bool force) {
|
||||
@@ -93,19 +92,17 @@ bool MediaSource::close(bool force) {
|
||||
return listener->close(*this,force);
|
||||
}
|
||||
|
||||
void MediaSource::onNoneReader(){
|
||||
void MediaSource::onReaderChanged(int size) {
|
||||
auto listener = _listener.lock();
|
||||
if(!listener){
|
||||
return;
|
||||
}
|
||||
if (listener->totalReaderCount(*this) == 0) {
|
||||
listener->onNoneReader(*this);
|
||||
if (listener) {
|
||||
listener->onReaderChanged(*this, size);
|
||||
}
|
||||
}
|
||||
|
||||
bool MediaSource::setupRecord(Recorder::type type, bool start, const string &custom_path){
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
WarnL << "未设置MediaSource的事件监听者,setupRecord失败:" << getSchema() << "/" << getVhost() << "/" << getApp() << "/" << getId();
|
||||
return false;
|
||||
}
|
||||
return listener->setupRecord(*this, type, start, custom_path);
|
||||
@@ -119,13 +116,30 @@ bool MediaSource::isRecording(Recorder::type type){
|
||||
return listener->isRecording(*this, type);
|
||||
}
|
||||
|
||||
void MediaSource::startSendRtp(const string &dst_url, uint16_t dst_port, uint32_t ssrc, bool is_udp, const function<void(const SockException &ex)> &cb){
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
cb(SockException(Err_other, "尚未设置事件监听器"));
|
||||
return;
|
||||
}
|
||||
return listener->startSendRtp(*this, dst_url, dst_port, ssrc, is_udp, cb);
|
||||
}
|
||||
|
||||
bool MediaSource::stopSendRtp() {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
}
|
||||
return listener->stopSendRtp(*this);
|
||||
}
|
||||
|
||||
void MediaSource::for_each_media(const function<void(const MediaSource::Ptr &src)> &cb) {
|
||||
decltype(g_mapMediaSrc) copy;
|
||||
decltype(s_media_source_map) copy;
|
||||
{
|
||||
//拷贝g_mapMediaSrc后再遍历,考虑到是高频使用的全局单例锁,并且在上锁时会执行回调代码
|
||||
//拷贝s_media_source_map后再遍历,考虑到是高频使用的全局单例锁,并且在上锁时会执行回调代码
|
||||
//很容易导致多个锁交叉死锁的情况,而且该函数使用频率不高,拷贝开销相对来说是可以接受的
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
copy = g_mapMediaSrc;
|
||||
lock_guard<recursive_mutex> lock(s_media_source_mtx);
|
||||
copy = s_media_source_map;
|
||||
}
|
||||
|
||||
for (auto &pr0 : copy) {
|
||||
@@ -180,42 +194,76 @@ static void eraseIfEmpty(MAP &map, IT0 it0, IT1 it1, IT2 it2) {
|
||||
}
|
||||
}
|
||||
|
||||
void MediaSource::findAsync_l(const MediaInfo &info, const std::shared_ptr<TcpSession> &session, bool retry, const function<void(const MediaSource::Ptr &src)> &cb){
|
||||
auto src = MediaSource::find_l(info._schema, info._vhost, info._app, info._streamid, true);
|
||||
static MediaSource::Ptr find_l(const string &schema, const string &vhost_in, const string &app, const string &id, bool create_new) {
|
||||
string vhost = vhost_in;
|
||||
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
|
||||
if(vhost.empty() || !enableVhost){
|
||||
vhost = DEFAULT_VHOST;
|
||||
}
|
||||
|
||||
MediaSource::Ptr ret;
|
||||
{
|
||||
lock_guard<recursive_mutex> lock(s_media_source_mtx);
|
||||
//查找某一媒体源,找到后返回
|
||||
searchMedia(s_media_source_map, schema, vhost, app, id,
|
||||
[&](MediaSource::SchemaVhostAppStreamMap::iterator &it0, MediaSource::VhostAppStreamMap::iterator &it1,
|
||||
MediaSource::AppStreamMap::iterator &it2, MediaSource::StreamMap::iterator &it3) {
|
||||
ret = it3->second.lock();
|
||||
if (!ret) {
|
||||
//该对象已经销毁
|
||||
it2->second.erase(it3);
|
||||
eraseIfEmpty(s_media_source_map, it0, it1, it2);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if(!ret && create_new && schema != HLS_SCHEMA){
|
||||
//未查找媒体源,则读取mp4创建一个
|
||||
//播放hls不触发mp4点播(因为HLS也可以用于录像,不是纯粹的直播)
|
||||
ret = MediaSource::createFromMP4(schema, vhost, app, id);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void findAsync_l(const MediaInfo &info, const std::shared_ptr<TcpSession> &session, bool retry,
|
||||
const function<void(const MediaSource::Ptr &src)> &cb){
|
||||
auto src = find_l(info._schema, info._vhost, info._app, info._streamid, true);
|
||||
if (src || !retry) {
|
||||
cb(src);
|
||||
return;
|
||||
}
|
||||
|
||||
void *listener_tag = session.get();
|
||||
weak_ptr<TcpSession> weakSession = session;
|
||||
weak_ptr<TcpSession> weak_session = session;
|
||||
|
||||
GET_CONFIG(int, maxWaitMS, General::kMaxStreamWaitTimeMS);
|
||||
auto onTimeout = session->getPoller()->doDelayTask(maxWaitMS, [cb, listener_tag]() {
|
||||
auto on_timeout = session->getPoller()->doDelayTask(maxWaitMS, [cb, listener_tag]() {
|
||||
//最多等待一定时间,如果这个时间内,流未注册上,那么返回未找到流
|
||||
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
|
||||
cb(nullptr);
|
||||
return 0;
|
||||
});
|
||||
|
||||
auto cancelAll = [onTimeout, listener_tag]() {
|
||||
auto cancel_all = [on_timeout, listener_tag]() {
|
||||
//取消延时任务,防止多次回调
|
||||
onTimeout->cancel();
|
||||
on_timeout->cancel();
|
||||
//取消媒体注册事件监听
|
||||
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
|
||||
};
|
||||
|
||||
function<void()> closePlayer = [cb, cancelAll]() {
|
||||
cancelAll();
|
||||
function<void()> close_player = [cb, cancel_all]() {
|
||||
cancel_all();
|
||||
//告诉播放器,流不存在,这样会立即断开播放器
|
||||
cb(nullptr);
|
||||
};
|
||||
|
||||
auto onRegist = [weakSession, info, cb, cancelAll](BroadcastMediaChangedArgs) {
|
||||
auto strongSession = weakSession.lock();
|
||||
if (!strongSession) {
|
||||
auto on_regist = [weak_session, info, cb, cancel_all](BroadcastMediaChangedArgs) {
|
||||
auto strong_session = weak_session.lock();
|
||||
if (!strong_session) {
|
||||
//自己已经销毁
|
||||
cancelAll();
|
||||
cancel_all();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -228,11 +276,11 @@ void MediaSource::findAsync_l(const MediaInfo &info, const std::shared_ptr<TcpSe
|
||||
return;
|
||||
}
|
||||
|
||||
cancelAll();
|
||||
cancel_all();
|
||||
|
||||
//播发器请求的流终于注册上了,切换到自己的线程再回复
|
||||
strongSession->async([weakSession, info, cb]() {
|
||||
auto strongSession = weakSession.lock();
|
||||
strong_session->async([weak_session, info, cb]() {
|
||||
auto strongSession = weak_session.lock();
|
||||
if (!strongSession) {
|
||||
return;
|
||||
}
|
||||
@@ -243,9 +291,9 @@ void MediaSource::findAsync_l(const MediaInfo &info, const std::shared_ptr<TcpSe
|
||||
};
|
||||
|
||||
//监听媒体注册事件
|
||||
NoticeCenter::Instance().addListener(listener_tag, Broadcast::kBroadcastMediaChanged, onRegist);
|
||||
NoticeCenter::Instance().addListener(listener_tag, Broadcast::kBroadcastMediaChanged, on_regist);
|
||||
//广播未找到流,此时可以立即去拉流,这样还来得及
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastNotFoundStream, info, static_cast<SockInfo &>(*session), closePlayer);
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastNotFoundStream, info, static_cast<SockInfo &>(*session), close_player);
|
||||
}
|
||||
|
||||
void MediaSource::findAsync(const MediaInfo &info, const std::shared_ptr<TcpSession> &session,const function<void(const Ptr &src)> &cb){
|
||||
@@ -256,232 +304,129 @@ MediaSource::Ptr MediaSource::find(const string &schema, const string &vhost, co
|
||||
return find_l(schema, vhost, app, id, false);
|
||||
}
|
||||
|
||||
MediaSource::Ptr MediaSource::find_l(const string &schema, const string &vhost_tmp, const string &app, const string &id, bool bMake) {
|
||||
string vhost = vhost_tmp;
|
||||
if(vhost.empty()){
|
||||
vhost = DEFAULT_VHOST;
|
||||
MediaSource::Ptr MediaSource::find(const string &vhost, const string &app, const string &stream_id){
|
||||
auto src = MediaSource::find(RTMP_SCHEMA, vhost, app, stream_id);
|
||||
if (src) {
|
||||
return src;
|
||||
}
|
||||
|
||||
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
|
||||
if(!enableVhost){
|
||||
vhost = DEFAULT_VHOST;
|
||||
src = MediaSource::find(RTSP_SCHEMA, vhost, app, stream_id);
|
||||
if (src) {
|
||||
return src;
|
||||
}
|
||||
|
||||
MediaSource::Ptr ret;
|
||||
{
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
//查找某一媒体源,找到后返回
|
||||
searchMedia(g_mapMediaSrc, schema, vhost, app, id, [&](SchemaVhostAppStreamMap::iterator &it0,
|
||||
VhostAppStreamMap::iterator &it1,
|
||||
AppStreamMap::iterator &it2,
|
||||
StreamMap::iterator &it3) {
|
||||
ret = it3->second.lock();
|
||||
if (!ret) {
|
||||
//该对象已经销毁
|
||||
it2->second.erase(it3);
|
||||
eraseIfEmpty(g_mapMediaSrc, it0, it1, it2);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if(!ret && bMake){
|
||||
//未查找媒体源,则创建一个
|
||||
ret = createFromMP4(schema, vhost, app, id);
|
||||
}
|
||||
return ret;
|
||||
return MediaSource::find(HLS_SCHEMA, vhost, app, stream_id);
|
||||
}
|
||||
void MediaSource::regist() {
|
||||
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
|
||||
if(!enableVhost){
|
||||
_strVhost = DEFAULT_VHOST;
|
||||
}
|
||||
//注册该源,注册后服务器才能找到该源
|
||||
{
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
g_mapMediaSrc[_strSchema][_strVhost][_strApp][_strId] = shared_from_this();
|
||||
}
|
||||
_StrPrinter codec_info;
|
||||
auto tracks = getTracks(true);
|
||||
for(auto &track : tracks) {
|
||||
auto codec_type = track->getTrackType();
|
||||
codec_info << track->getCodecName();
|
||||
switch (codec_type) {
|
||||
case TrackAudio : {
|
||||
auto audio_track = dynamic_pointer_cast<AudioTrack>(track);
|
||||
codec_info << "["
|
||||
<< audio_track->getAudioSampleRate() << "/"
|
||||
<< audio_track->getAudioChannel() << "/"
|
||||
<< audio_track->getAudioSampleBit() << "] ";
|
||||
break;
|
||||
}
|
||||
case TrackVideo : {
|
||||
auto video_track = dynamic_pointer_cast<VideoTrack>(track);
|
||||
codec_info << "["
|
||||
<< video_track->getVideoWidth() << "/"
|
||||
<< video_track->getVideoHeight() << "/"
|
||||
<< round(video_track->getVideoFps()) << "] ";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
InfoL << _strSchema << " " << _strVhost << " " << _strApp << " " << _strId << " " << codec_info;
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, true, *this);
|
||||
|
||||
void MediaSource::emitEvent(bool regist){
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
listener->onRegist(*this, true);
|
||||
//触发回调
|
||||
listener->onRegist(*this, regist);
|
||||
}
|
||||
//触发广播
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, regist, *this);
|
||||
InfoL << (regist ? "媒体注册:" : "媒体注销:") << _schema << " " << _vhost << " " << _app << " " << _stream_id;
|
||||
}
|
||||
|
||||
void MediaSource::regist() {
|
||||
{
|
||||
//减小互斥锁临界区
|
||||
lock_guard<recursive_mutex> lock(s_media_source_mtx);
|
||||
s_media_source_map[_schema][_vhost][_app][_stream_id] = shared_from_this();
|
||||
}
|
||||
emitEvent(true);
|
||||
}
|
||||
|
||||
//反注册该源
|
||||
bool MediaSource::unregist() {
|
||||
bool ret;
|
||||
{
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
ret = searchMedia(g_mapMediaSrc, _strSchema, _strVhost, _strApp, _strId,
|
||||
[&](SchemaVhostAppStreamMap::iterator &it0,
|
||||
VhostAppStreamMap::iterator &it1,
|
||||
AppStreamMap::iterator &it2,
|
||||
StreamMap::iterator &it3) {
|
||||
auto strongMedia = it3->second.lock();
|
||||
if (strongMedia && this != strongMedia.get()) {
|
||||
//不是自己,不允许反注册
|
||||
return false;
|
||||
}
|
||||
it2->second.erase(it3);
|
||||
eraseIfEmpty(g_mapMediaSrc, it0, it1, it2);
|
||||
return true;
|
||||
});
|
||||
//减小互斥锁临界区
|
||||
lock_guard<recursive_mutex> lock(s_media_source_mtx);
|
||||
ret = searchMedia(s_media_source_map, _schema, _vhost, _app, _stream_id,
|
||||
[&](SchemaVhostAppStreamMap::iterator &it0, VhostAppStreamMap::iterator &it1,
|
||||
AppStreamMap::iterator &it2, StreamMap::iterator &it3) {
|
||||
auto strong_self = it3->second.lock();
|
||||
if (strong_self && this != strong_self.get()) {
|
||||
//不是自己,不允许反注册
|
||||
return false;
|
||||
}
|
||||
it2->second.erase(it3);
|
||||
eraseIfEmpty(s_media_source_map, it0, it1, it2);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if(ret){
|
||||
InfoL << _strSchema << " " << _strVhost << " " << _strApp << " " << _strId;
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, false, *this);
|
||||
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
listener->onRegist(*this, false);
|
||||
}
|
||||
if (ret) {
|
||||
emitEvent(false);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/////////////////////////////////////MediaInfo//////////////////////////////////////
|
||||
|
||||
void MediaInfo::parse(const string &url){
|
||||
//string url = "rtsp://127.0.0.1:8554/live/id?key=val&a=1&&b=2&vhost=vhost.com";
|
||||
void MediaInfo::parse(const string &url_in){
|
||||
string url = url_in;
|
||||
auto pos = url.find("?");
|
||||
if (pos != string::npos) {
|
||||
_param_strs = url.substr(pos + 1);
|
||||
url.erase(pos);
|
||||
}
|
||||
|
||||
auto schema_pos = url.find("://");
|
||||
if(schema_pos != string::npos){
|
||||
_schema = url.substr(0,schema_pos);
|
||||
}else{
|
||||
if (schema_pos != string::npos) {
|
||||
_schema = url.substr(0, schema_pos);
|
||||
} else {
|
||||
schema_pos = -3;
|
||||
}
|
||||
auto split_vec = split(url.substr(schema_pos + 3),"/");
|
||||
if(split_vec.size() > 0){
|
||||
auto split_vec = split(url.substr(schema_pos + 3), "/");
|
||||
if (split_vec.size() > 0) {
|
||||
auto vhost = split_vec[0];
|
||||
auto pos = vhost.find(":");
|
||||
if(pos != string::npos){
|
||||
_host = _vhost = vhost.substr(0,pos);
|
||||
if (pos != string::npos) {
|
||||
_host = _vhost = vhost.substr(0, pos);
|
||||
_port = vhost.substr(pos + 1);
|
||||
} else{
|
||||
} else {
|
||||
_host = _vhost = vhost;
|
||||
}
|
||||
|
||||
if(_vhost == "localhost" || INADDR_NONE != inet_addr(_vhost.data())){
|
||||
if (_vhost == "localhost" || INADDR_NONE != inet_addr(_vhost.data())) {
|
||||
//如果访问的是localhost或ip,那么则为默认虚拟主机
|
||||
_vhost = DEFAULT_VHOST;
|
||||
}
|
||||
|
||||
}
|
||||
if(split_vec.size() > 1){
|
||||
if (split_vec.size() > 1) {
|
||||
_app = split_vec[1];
|
||||
}
|
||||
if(split_vec.size() > 2){
|
||||
string steamid;
|
||||
for(int i = 2 ; i < split_vec.size() ; ++i){
|
||||
steamid.append(split_vec[i] + "/");
|
||||
if (split_vec.size() > 2) {
|
||||
string stream_id;
|
||||
for (int i = 2; i < split_vec.size(); ++i) {
|
||||
stream_id.append(split_vec[i] + "/");
|
||||
}
|
||||
if(steamid.back() == '/'){
|
||||
steamid.pop_back();
|
||||
}
|
||||
auto pos = steamid.find("?");
|
||||
if(pos != string::npos){
|
||||
_streamid = steamid.substr(0,pos);
|
||||
_param_strs = steamid.substr(pos + 1);
|
||||
auto params = Parser::parseArgs(_param_strs);
|
||||
if(params.find(VHOST_KEY) != params.end()){
|
||||
_vhost = params[VHOST_KEY];
|
||||
}
|
||||
} else{
|
||||
_streamid = steamid;
|
||||
if (stream_id.back() == '/') {
|
||||
stream_id.pop_back();
|
||||
}
|
||||
_streamid = stream_id;
|
||||
}
|
||||
|
||||
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
|
||||
if(!enableVhost || _vhost.empty()){
|
||||
auto params = Parser::parseArgs(_param_strs);
|
||||
if (params.find(VHOST_KEY) != params.end()) {
|
||||
_vhost = params[VHOST_KEY];
|
||||
}
|
||||
|
||||
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
|
||||
if (!enableVhost || _vhost.empty()) {
|
||||
//如果关闭虚拟主机或者虚拟主机为空,则设置虚拟主机为默认
|
||||
_vhost = DEFAULT_VHOST;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////MediaSourceEvent//////////////////////////////////////
|
||||
|
||||
void MediaSourceEvent::onNoneReader(MediaSource &sender){
|
||||
GET_CONFIG(string, recordApp, Record::kAppName);
|
||||
GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS);
|
||||
|
||||
//如果mp4点播, 无人观看时我们强制关闭点播
|
||||
bool is_mp4_vod = sender.getApp() == recordApp;
|
||||
|
||||
//没有任何人观看该视频源,表明该源可以关闭了
|
||||
weak_ptr<MediaSource> weakSender = sender.shared_from_this();
|
||||
_async_close_timer = std::make_shared<Timer>(stream_none_reader_delay / 1000.0, [weakSender,is_mp4_vod]() {
|
||||
auto strongSender = weakSender.lock();
|
||||
if (!strongSender) {
|
||||
//对象已经销毁
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strongSender->totalReaderCount() != 0) {
|
||||
//还有人消费
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!is_mp4_vod){
|
||||
//直播时触发无人观看事件,让开发者自行选择是否关闭
|
||||
WarnL << "无人观看事件:"
|
||||
<< strongSender->getSchema() << "/"
|
||||
<< strongSender->getVhost() << "/"
|
||||
<< strongSender->getApp() << "/"
|
||||
<< strongSender->getId();
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strongSender);
|
||||
}else{
|
||||
//这个是mp4点播,我们自动关闭
|
||||
WarnL << "MP4点播无人观看,自动关闭:"
|
||||
<< strongSender->getSchema() << "/"
|
||||
<< strongSender->getVhost() << "/"
|
||||
<< strongSender->getApp() << "/"
|
||||
<< strongSender->getId();
|
||||
strongSender->close(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}, nullptr);
|
||||
}
|
||||
|
||||
MediaSource::Ptr MediaSource::createFromMP4(const string &schema, const string &vhost, const string &app, const string &stream, const string &filePath , bool checkApp){
|
||||
MediaSource::Ptr MediaSource::createFromMP4(const string &schema, const string &vhost, const string &app, const string &stream, const string &file_path , bool check_app){
|
||||
GET_CONFIG(string, appName, Record::kAppName);
|
||||
if (checkApp && app != appName) {
|
||||
if (check_app && app != appName) {
|
||||
return nullptr;
|
||||
}
|
||||
#ifdef ENABLE_MP4
|
||||
try {
|
||||
MP4Reader::Ptr pReader(new MP4Reader(vhost, app, stream, filePath));
|
||||
MP4Reader::Ptr pReader(new MP4Reader(vhost, app, stream, file_path));
|
||||
pReader->startReadMP4();
|
||||
return MediaSource::find(schema, vhost, app, stream);
|
||||
} catch (std::exception &ex) {
|
||||
@@ -494,6 +439,136 @@ MediaSource::Ptr MediaSource::createFromMP4(const string &schema, const string &
|
||||
#endif //ENABLE_MP4
|
||||
}
|
||||
|
||||
/////////////////////////////////////MediaSourceEvent//////////////////////////////////////
|
||||
|
||||
void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){
|
||||
if (size || totalReaderCount(sender)) {
|
||||
//还有人观看该视频,不触发关闭事件
|
||||
return;
|
||||
}
|
||||
//没有任何人观看该视频源,表明该源可以关闭了
|
||||
GET_CONFIG(string, record_app, Record::kAppName);
|
||||
GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS);
|
||||
//如果mp4点播, 无人观看时我们强制关闭点播
|
||||
bool is_mp4_vod = sender.getApp() == record_app;
|
||||
weak_ptr<MediaSource> weak_sender = sender.shared_from_this();
|
||||
|
||||
_async_close_timer = std::make_shared<Timer>(stream_none_reader_delay / 1000.0, [weak_sender, is_mp4_vod]() {
|
||||
auto strong_sender = weak_sender.lock();
|
||||
if (!strong_sender) {
|
||||
//对象已经销毁
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strong_sender->totalReaderCount()) {
|
||||
//还有人观看该视频,不触发关闭事件
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_mp4_vod) {
|
||||
//直播时触发无人观看事件,让开发者自行选择是否关闭
|
||||
WarnL << "无人观看事件:"
|
||||
<< strong_sender->getSchema() << "/"
|
||||
<< strong_sender->getVhost() << "/"
|
||||
<< strong_sender->getApp() << "/"
|
||||
<< strong_sender->getId();
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender);
|
||||
} else {
|
||||
//这个是mp4点播,我们自动关闭
|
||||
WarnL << "MP4点播无人观看,自动关闭:"
|
||||
<< strong_sender->getSchema() << "/"
|
||||
<< strong_sender->getVhost() << "/"
|
||||
<< strong_sender->getApp() << "/"
|
||||
<< strong_sender->getId();
|
||||
strong_sender->close(false);
|
||||
}
|
||||
return false;
|
||||
}, nullptr);
|
||||
}
|
||||
|
||||
bool MediaSourceEventInterceptor::seekTo(MediaSource &sender, uint32_t stamp) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
}
|
||||
return listener->seekTo(sender, stamp);
|
||||
}
|
||||
|
||||
bool MediaSourceEventInterceptor::close(MediaSource &sender, bool force) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
}
|
||||
return listener->close(sender, force);
|
||||
}
|
||||
|
||||
int MediaSourceEventInterceptor::totalReaderCount(MediaSource &sender) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return sender.readerCount();
|
||||
}
|
||||
return listener->totalReaderCount(sender);
|
||||
}
|
||||
|
||||
void MediaSourceEventInterceptor::onReaderChanged(MediaSource &sender, int size) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
MediaSourceEvent::onReaderChanged(sender, size);
|
||||
} else {
|
||||
listener->onReaderChanged(sender, size);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaSourceEventInterceptor::onRegist(MediaSource &sender, bool regist) {
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
listener->onRegist(sender, regist);
|
||||
}
|
||||
}
|
||||
|
||||
bool MediaSourceEventInterceptor::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
}
|
||||
return listener->setupRecord(sender, type, start, custom_path);
|
||||
}
|
||||
|
||||
bool MediaSourceEventInterceptor::isRecording(MediaSource &sender, Recorder::type type) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
}
|
||||
return listener->isRecording(sender, type);
|
||||
}
|
||||
|
||||
vector<Track::Ptr> MediaSourceEventInterceptor::getTracks(MediaSource &sender, bool trackReady) const {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return vector<Track::Ptr>();
|
||||
}
|
||||
return listener->getTracks(sender, trackReady);
|
||||
}
|
||||
|
||||
void MediaSourceEventInterceptor::startSendRtp(MediaSource &sender, const string &dst_url, uint16_t dst_port, uint32_t ssrc, bool is_udp, const function<void(const SockException &ex)> &cb){
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
listener->startSendRtp(sender, dst_url, dst_port, ssrc, is_udp, cb);
|
||||
} else {
|
||||
MediaSourceEvent::startSendRtp(sender, dst_url, dst_port, ssrc, is_udp, cb);
|
||||
}
|
||||
}
|
||||
|
||||
bool MediaSourceEventInterceptor::stopSendRtp(MediaSource &sender){
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
return listener->stopSendRtp(sender);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/////////////////////////////////////FlushPolicy//////////////////////////////////////
|
||||
|
||||
static bool isFlushAble_default(bool is_video, uint32_t last_stamp, uint32_t new_stamp, int cache_size) {
|
||||
if (new_stamp + 500 < last_stamp) {
|
||||
//时间戳回退比较大(可能seek中),由于rtp中时间戳是pts,是可能存在一定程度的回退的
|
||||
|
||||
@@ -44,33 +44,63 @@ public:
|
||||
virtual ~MediaSourceEvent(){};
|
||||
|
||||
// 通知拖动进度条
|
||||
virtual bool seekTo(MediaSource &sender,uint32_t ui32Stamp){ return false; }
|
||||
// 通知其停止推流
|
||||
virtual bool close(MediaSource &sender,bool force) { return false;}
|
||||
// 观看总人数
|
||||
virtual bool seekTo(MediaSource &sender, uint32_t stamp) { return false; }
|
||||
// 通知其停止产生流
|
||||
virtual bool close(MediaSource &sender, bool force) { return false; }
|
||||
// 获取观看总人数
|
||||
virtual int totalReaderCount(MediaSource &sender) = 0;
|
||||
// 通知观看人数变化
|
||||
virtual void onReaderChanged(MediaSource &sender, int size);
|
||||
//流注册或注销事件
|
||||
virtual void onRegist(MediaSource &sender, bool regist) {};
|
||||
|
||||
////////////////////////仅供MultiMediaSourceMuxer对象继承////////////////////////
|
||||
// 开启或关闭录制
|
||||
virtual bool setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path) { return false; };
|
||||
// 获取录制状态
|
||||
virtual bool isRecording(MediaSource &sender, Recorder::type type) { return false; };
|
||||
// 通知无人观看
|
||||
virtual void onNoneReader(MediaSource &sender);
|
||||
//流注册或注销事件
|
||||
virtual void onRegist(MediaSource &sender, bool regist) {};
|
||||
// 获取所有track相关信息
|
||||
virtual vector<Track::Ptr> getTracks(MediaSource &sender, bool trackReady = true) const { return vector<Track::Ptr>(); };
|
||||
// 开始发送ps-rtp
|
||||
virtual void startSendRtp(MediaSource &sender, const string &dst_url, uint16_t dst_port, uint32_t ssrc, bool is_udp, const function<void(const SockException &ex)> &cb) { cb(SockException(Err_other, "not implemented"));};
|
||||
// 停止发送ps-rtp
|
||||
virtual bool stopSendRtp(MediaSource &sender) {return false; }
|
||||
|
||||
private:
|
||||
Timer::Ptr _async_close_timer;
|
||||
};
|
||||
|
||||
//该对象用于拦截感兴趣的MediaSourceEvent事件
|
||||
class MediaSourceEventInterceptor : public MediaSourceEvent{
|
||||
public:
|
||||
MediaSourceEventInterceptor(){}
|
||||
~MediaSourceEventInterceptor() override {}
|
||||
|
||||
bool seekTo(MediaSource &sender, uint32_t stamp) override;
|
||||
bool close(MediaSource &sender, bool force) override;
|
||||
int totalReaderCount(MediaSource &sender) override;
|
||||
void onReaderChanged(MediaSource &sender, int size) override;
|
||||
void onRegist(MediaSource &sender, bool regist) override;
|
||||
bool setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path) override;
|
||||
bool isRecording(MediaSource &sender, Recorder::type type) override;
|
||||
vector<Track::Ptr> getTracks(MediaSource &sender, bool trackReady = true) const override;
|
||||
void startSendRtp(MediaSource &sender, const string &dst_url, uint16_t dst_port, uint32_t ssrc, bool is_udp, const function<void(const SockException &ex)> &cb) override;
|
||||
bool stopSendRtp(MediaSource &sender) override;
|
||||
|
||||
protected:
|
||||
std::weak_ptr<MediaSourceEvent> _listener;
|
||||
};
|
||||
|
||||
/**
|
||||
* 解析url获取媒体相关信息
|
||||
*/
|
||||
class MediaInfo{
|
||||
public:
|
||||
MediaInfo(){}
|
||||
~MediaInfo(){}
|
||||
MediaInfo(const string &url){ parse(url); }
|
||||
~MediaInfo() {}
|
||||
MediaInfo() {}
|
||||
MediaInfo(const string &url) { parse(url); }
|
||||
void parse(const string &url);
|
||||
|
||||
public:
|
||||
string _schema;
|
||||
string _host;
|
||||
@@ -92,9 +122,11 @@ public:
|
||||
typedef unordered_map<string, AppStreamMap > VhostAppStreamMap;
|
||||
typedef unordered_map<string, VhostAppStreamMap > SchemaVhostAppStreamMap;
|
||||
|
||||
MediaSource(const string &strSchema, const string &strVhost, const string &strApp, const string &strId) ;
|
||||
MediaSource(const string &schema, const string &vhost, const string &app, const string &stream_id) ;
|
||||
virtual ~MediaSource() ;
|
||||
|
||||
////////////////获取MediaSource相关信息////////////////
|
||||
|
||||
// 获取协议类型
|
||||
const string& getSchema() const;
|
||||
// 虚拟主机
|
||||
@@ -104,13 +136,18 @@ public:
|
||||
// 流id
|
||||
const string& getId() const;
|
||||
|
||||
// 设置TrackSource
|
||||
void setTrackSource(const std::weak_ptr<TrackSource> &track_src);
|
||||
// 获取所有Track
|
||||
vector<Track::Ptr> getTracks(bool trackReady = true) const override;
|
||||
vector<Track::Ptr> getTracks(bool ready = true) const override;
|
||||
|
||||
// 获取流当前时间戳
|
||||
virtual uint32_t getTimeStamp(TrackType type) { return 0; };
|
||||
// 设置时间戳
|
||||
virtual void setTimeStamp(uint32_t stamp) {};
|
||||
|
||||
////////////////MediaSourceEvent相关接口实现////////////////
|
||||
|
||||
// 设置监听者
|
||||
virtual void setListener(const std::weak_ptr<MediaSourceEvent> &listener);
|
||||
void setListener(const std::weak_ptr<MediaSourceEvent> &listener);
|
||||
// 获取监听者
|
||||
const std::weak_ptr<MediaSourceEvent>& getListener() const;
|
||||
|
||||
@@ -119,48 +156,52 @@ public:
|
||||
// 观看者个数,包括(hls/rtsp/rtmp)
|
||||
virtual int totalReaderCount();
|
||||
|
||||
// 获取流当前时间戳
|
||||
virtual uint32_t getTimeStamp(TrackType trackType) { return 0; };
|
||||
// 设置时间戳
|
||||
virtual void setTimeStamp(uint32_t uiStamp) {};
|
||||
|
||||
// 拖动进度条
|
||||
bool seekTo(uint32_t ui32Stamp);
|
||||
bool seekTo(uint32_t stamp);
|
||||
// 关闭该流
|
||||
bool close(bool force);
|
||||
// 该流无人观看
|
||||
void onNoneReader();
|
||||
// 该流观看人数变化
|
||||
void onReaderChanged(int size);
|
||||
// 开启或关闭录制
|
||||
virtual bool setupRecord(Recorder::type type, bool start, const string &custom_path);
|
||||
bool setupRecord(Recorder::type type, bool start, const string &custom_path);
|
||||
// 获取录制状态
|
||||
virtual bool isRecording(Recorder::type type);
|
||||
bool isRecording(Recorder::type type);
|
||||
// 开始发送ps-rtp
|
||||
void startSendRtp(const string &dst_url, uint16_t dst_port, uint32_t ssrc, bool is_udp, const function<void(const SockException &ex)> &cb);
|
||||
// 停止发送ps-rtp
|
||||
bool stopSendRtp();
|
||||
|
||||
////////////////static方法,查找或生成MediaSource////////////////
|
||||
|
||||
// 同步查找流
|
||||
static Ptr find(const string &schema, const string &vhost, const string &app, const string &id);
|
||||
|
||||
// 忽略类型,同步查找流,可能返回rtmp/rtsp/hls类型
|
||||
static Ptr find(const string &vhost, const string &app, const string &stream_id);
|
||||
|
||||
// 异步查找流
|
||||
static void findAsync(const MediaInfo &info, const std::shared_ptr<TcpSession> &session, const function<void(const Ptr &src)> &cb);
|
||||
// 遍历所有流
|
||||
static void for_each_media(const function<void(const Ptr &src)> &cb);
|
||||
|
||||
// 从mp4文件生成MediaSource
|
||||
static MediaSource::Ptr createFromMP4(const string &schema, const string &vhost, const string &app, const string &stream, const string &filePath = "", bool checkApp = true);
|
||||
static MediaSource::Ptr createFromMP4(const string &schema, const string &vhost, const string &app, const string &stream, const string &file_path = "", bool check_app = true);
|
||||
|
||||
protected:
|
||||
void regist() ;
|
||||
bool unregist();
|
||||
//媒体注册
|
||||
void regist();
|
||||
|
||||
private:
|
||||
static Ptr find_l(const string &schema, const string &vhost, const string &app, const string &id, bool bMake);
|
||||
static void findAsync_l(const MediaInfo &info, const std::shared_ptr<TcpSession> &session, bool retry, const function<void(const MediaSource::Ptr &src)> &cb);
|
||||
//媒体注销
|
||||
bool unregist();
|
||||
//触发媒体事件
|
||||
void emitEvent(bool regist);
|
||||
|
||||
private:
|
||||
string _strSchema;
|
||||
string _strVhost;
|
||||
string _strApp;
|
||||
string _strId;
|
||||
string _schema;
|
||||
string _vhost;
|
||||
string _app;
|
||||
string _stream_id;
|
||||
std::weak_ptr<MediaSourceEvent> _listener;
|
||||
weak_ptr<TrackSource> _track_source;
|
||||
static SchemaVhostAppStreamMap g_mapMediaSrc;
|
||||
static recursive_mutex g_mtxMediaSrc;
|
||||
};
|
||||
|
||||
///缓存刷新策略类
|
||||
@@ -174,7 +215,7 @@ public:
|
||||
}
|
||||
|
||||
uint32_t getStamp(const RtmpPacket::Ptr &packet) {
|
||||
return packet->timeStamp;
|
||||
return packet->time_stamp;
|
||||
}
|
||||
|
||||
bool isFlushAble(bool is_video, bool is_key, uint32_t new_stamp, int cache_size);
|
||||
@@ -208,6 +249,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void clearCache() {
|
||||
_cache->clear();
|
||||
}
|
||||
|
||||
virtual void onFlush(std::shared_ptr<packet_list> &, bool key_pos) = 0;
|
||||
|
||||
private:
|
||||
@@ -221,9 +266,9 @@ private:
|
||||
}
|
||||
|
||||
private:
|
||||
bool _key_pos = false;
|
||||
policy _policy;
|
||||
std::shared_ptr<packet_list> _cache;
|
||||
bool _key_pos = false;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
@@ -8,36 +8,33 @@
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include "MultiMediaSourceMuxer.h"
|
||||
namespace mediakit {
|
||||
|
||||
///////////////////////////////MultiMuxerPrivate//////////////////////////////////
|
||||
|
||||
MultiMuxerPrivate::~MultiMuxerPrivate() {}
|
||||
MultiMuxerPrivate::MultiMuxerPrivate(const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
float dur_sec,
|
||||
bool enable_rtsp,
|
||||
bool enable_rtmp,
|
||||
bool enable_hls,
|
||||
bool enable_mp4) {
|
||||
MultiMuxerPrivate::MultiMuxerPrivate(const string &vhost, const string &app, const string &stream, float dur_sec,
|
||||
bool enable_rtsp, bool enable_rtmp, bool enable_hls, bool enable_mp4) {
|
||||
_stream_url = vhost + " " + app + " " + stream;
|
||||
if (enable_rtmp) {
|
||||
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleMeta>(dur_sec));
|
||||
_enable_rtxp = true;
|
||||
}
|
||||
if (enable_rtsp) {
|
||||
_rtsp = std::make_shared<RtspMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleSdp>(dur_sec));
|
||||
_enable_rtxp = true;
|
||||
}
|
||||
|
||||
if (enable_hls) {
|
||||
_hls = Recorder::createRecorder(Recorder::type_hls, vhost, app, stream);
|
||||
_enable_record = true;
|
||||
_hls = dynamic_pointer_cast<HlsRecorder>(Recorder::createRecorder(Recorder::type_hls, vhost, app, stream));
|
||||
}
|
||||
|
||||
if (enable_mp4) {
|
||||
_mp4 = Recorder::createRecorder(Recorder::type_mp4, vhost, app, stream);
|
||||
_enable_record = true;
|
||||
}
|
||||
|
||||
_ts = std::make_shared<TSMediaSourceMuxer>(vhost, app, stream);
|
||||
_fmp4 = std::make_shared<FMP4MediaSourceMuxer>(vhost, app, stream);
|
||||
}
|
||||
|
||||
void MultiMuxerPrivate::resetTracks() {
|
||||
@@ -47,6 +44,12 @@ void MultiMuxerPrivate::resetTracks() {
|
||||
if (_rtsp) {
|
||||
_rtsp->resetTracks();
|
||||
}
|
||||
if (_ts) {
|
||||
_ts->resetTracks();
|
||||
}
|
||||
if (_fmp4) {
|
||||
_fmp4->resetTracks();
|
||||
}
|
||||
|
||||
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
||||
auto hls = _hls;
|
||||
@@ -61,24 +64,32 @@ void MultiMuxerPrivate::resetTracks() {
|
||||
}
|
||||
|
||||
void MultiMuxerPrivate::setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener) {
|
||||
_listener = listener;
|
||||
if (_rtmp) {
|
||||
_rtmp->setListener(listener);
|
||||
}
|
||||
|
||||
if (_rtsp) {
|
||||
_rtsp->setListener(listener);
|
||||
}
|
||||
|
||||
auto hls_src = getHlsMediaSource();
|
||||
if (hls_src) {
|
||||
hls_src->setListener(listener);
|
||||
if (_ts) {
|
||||
_ts->setListener(listener);
|
||||
}
|
||||
if (_fmp4) {
|
||||
_fmp4->setListener(listener);
|
||||
}
|
||||
auto hls = _hls;
|
||||
if (hls) {
|
||||
hls->setListener(listener);
|
||||
}
|
||||
_meida_listener = listener;
|
||||
}
|
||||
|
||||
int MultiMuxerPrivate::totalReaderCount() const {
|
||||
auto hls_src = getHlsMediaSource();
|
||||
return (_rtsp ? _rtsp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) + (hls_src ? hls_src->readerCount() : 0);
|
||||
auto hls = _hls;
|
||||
return (_rtsp ? _rtsp->readerCount() : 0) +
|
||||
(_rtmp ? _rtmp->readerCount() : 0) +
|
||||
(_ts ? _ts->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){
|
||||
@@ -95,18 +106,16 @@ bool MultiMuxerPrivate::setupRecord(MediaSource &sender, Recorder::type type, bo
|
||||
case Recorder::type_hls : {
|
||||
if (start && !_hls) {
|
||||
//开始录制
|
||||
_hls = makeRecorder(getTracks(true), type, custom_path, sender);
|
||||
auto hls_src = getHlsMediaSource();
|
||||
if (hls_src) {
|
||||
auto hls = dynamic_pointer_cast<HlsRecorder>(makeRecorder(getTracks(true), type, custom_path, sender));
|
||||
if (hls) {
|
||||
//设置HlsMediaSource的事件监听器
|
||||
hls_src->setListener(_meida_listener);
|
||||
hls_src->setTrackSource(shared_from_this());
|
||||
hls->setListener(_listener);
|
||||
}
|
||||
_hls = hls;
|
||||
} else if (!start && _hls) {
|
||||
//停止录制
|
||||
_hls = nullptr;
|
||||
}
|
||||
_enable_record = _hls || _mp4;
|
||||
return true;
|
||||
}
|
||||
case Recorder::type_mp4 : {
|
||||
@@ -117,7 +126,6 @@ bool MultiMuxerPrivate::setupRecord(MediaSource &sender, Recorder::type type, bo
|
||||
//停止录制
|
||||
_mp4 = nullptr;
|
||||
}
|
||||
_enable_record = _hls || _mp4;
|
||||
return true;
|
||||
}
|
||||
default : return false;
|
||||
@@ -146,7 +154,7 @@ void MultiMuxerPrivate::setTimeStamp(uint32_t stamp) {
|
||||
}
|
||||
|
||||
void MultiMuxerPrivate::setTrackListener(Listener *listener) {
|
||||
_listener = listener;
|
||||
_track_listener = listener;
|
||||
}
|
||||
|
||||
void MultiMuxerPrivate::onTrackReady(const Track::Ptr &track) {
|
||||
@@ -156,6 +164,12 @@ void MultiMuxerPrivate::onTrackReady(const Track::Ptr &track) {
|
||||
if (_rtsp) {
|
||||
_rtsp->addTrack(track);
|
||||
}
|
||||
if (_ts) {
|
||||
_ts->addTrack(track);
|
||||
}
|
||||
if (_fmp4) {
|
||||
_fmp4->addTrack(track);
|
||||
}
|
||||
|
||||
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
||||
auto hls = _hls;
|
||||
@@ -169,7 +183,12 @@ void MultiMuxerPrivate::onTrackReady(const Track::Ptr &track) {
|
||||
}
|
||||
|
||||
bool MultiMuxerPrivate::isEnabled(){
|
||||
return _enable_rtxp || _enable_record;
|
||||
auto hls = _hls;
|
||||
return (_rtmp ? _rtmp->isEnabled() : false) ||
|
||||
(_rtsp ? _rtsp->isEnabled() : false) ||
|
||||
(_ts ? _ts->isEnabled() : false) ||
|
||||
(_fmp4 ? _fmp4->isEnabled() : false) ||
|
||||
(hls ? hls->isEnabled() : false) || _mp4;
|
||||
}
|
||||
|
||||
void MultiMuxerPrivate::onTrackFrame(const Frame::Ptr &frame) {
|
||||
@@ -179,6 +198,13 @@ void MultiMuxerPrivate::onTrackFrame(const Frame::Ptr &frame) {
|
||||
if (_rtsp) {
|
||||
_rtsp->inputFrame(frame);
|
||||
}
|
||||
if (_ts) {
|
||||
_ts->inputFrame(frame);
|
||||
}
|
||||
if (_fmp4) {
|
||||
_fmp4->inputFrame(frame);
|
||||
}
|
||||
|
||||
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
||||
//此处使用智能指针拷贝来确保线程安全,比互斥锁性能更优
|
||||
auto hls = _hls;
|
||||
@@ -191,51 +217,70 @@ void MultiMuxerPrivate::onTrackFrame(const Frame::Ptr &frame) {
|
||||
}
|
||||
}
|
||||
|
||||
static string getTrackInfoStr(const TrackSource *track_src){
|
||||
_StrPrinter codec_info;
|
||||
auto tracks = track_src->getTracks(true);
|
||||
for (auto &track : tracks) {
|
||||
auto codec_type = track->getTrackType();
|
||||
codec_info << track->getCodecName();
|
||||
switch (codec_type) {
|
||||
case TrackAudio : {
|
||||
auto audio_track = dynamic_pointer_cast<AudioTrack>(track);
|
||||
codec_info << "["
|
||||
<< audio_track->getAudioSampleRate() << "/"
|
||||
<< audio_track->getAudioChannel() << "/"
|
||||
<< audio_track->getAudioSampleBit() << "] ";
|
||||
break;
|
||||
}
|
||||
case TrackVideo : {
|
||||
auto video_track = dynamic_pointer_cast<VideoTrack>(track);
|
||||
codec_info << "["
|
||||
<< video_track->getVideoWidth() << "/"
|
||||
<< video_track->getVideoHeight() << "/"
|
||||
<< round(video_track->getVideoFps()) << "] ";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return codec_info;
|
||||
}
|
||||
|
||||
void MultiMuxerPrivate::onAllTrackReady() {
|
||||
if (_rtmp) {
|
||||
_rtmp->setTrackSource(shared_from_this());
|
||||
_rtmp->onAllTrackReady();
|
||||
}
|
||||
if (_rtsp) {
|
||||
_rtsp->setTrackSource(shared_from_this());
|
||||
_rtsp->onAllTrackReady();
|
||||
}
|
||||
|
||||
auto hls_src = getHlsMediaSource();
|
||||
if (hls_src) {
|
||||
hls_src->setTrackSource(shared_from_this());
|
||||
if (_fmp4) {
|
||||
_fmp4->onAllTrackReady();
|
||||
}
|
||||
|
||||
if (_listener) {
|
||||
_listener->onAllTrackReady();
|
||||
if (_track_listener) {
|
||||
_track_listener->onAllTrackReady();
|
||||
}
|
||||
InfoL << "stream: " << _stream_url << " , codec info: " << getTrackInfoStr(this);
|
||||
}
|
||||
|
||||
MediaSource::Ptr MultiMuxerPrivate::getHlsMediaSource() const {
|
||||
auto recorder = dynamic_pointer_cast<HlsRecorder>(_hls);
|
||||
if (recorder) {
|
||||
return recorder->getMediaSource();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////MultiMediaSourceMuxer//////////////////////////////////
|
||||
|
||||
MultiMediaSourceMuxer::~MultiMediaSourceMuxer() {}
|
||||
MultiMediaSourceMuxer::MultiMediaSourceMuxer(const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
float dur_sec,
|
||||
bool enable_rtsp,
|
||||
bool enable_rtmp,
|
||||
bool enable_hls,
|
||||
bool enable_mp4) {
|
||||
|
||||
MultiMediaSourceMuxer::MultiMediaSourceMuxer(const string &vhost, const string &app, const string &stream, float dur_sec,
|
||||
bool enable_rtsp, bool enable_rtmp, bool enable_hls, bool enable_mp4) {
|
||||
_muxer.reset(new MultiMuxerPrivate(vhost, app, stream, dur_sec, enable_rtsp, enable_rtmp, enable_hls, enable_mp4));
|
||||
_muxer->setTrackListener(this);
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener) {
|
||||
_muxer->setMediaListener(shared_from_this());
|
||||
_listener = listener;
|
||||
//拦截事件
|
||||
_muxer->setMediaListener(shared_from_this());
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::setTrackListener(const std::weak_ptr<MultiMuxerPrivate::Listener> &listener) {
|
||||
_track_listener = listener;
|
||||
}
|
||||
|
||||
int MultiMediaSourceMuxer::totalReaderCount() const {
|
||||
@@ -246,62 +291,57 @@ void MultiMediaSourceMuxer::setTimeStamp(uint32_t stamp) {
|
||||
_muxer->setTimeStamp(stamp);
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::setTrackListener(Listener *listener) {
|
||||
_muxer->setTrackListener(listener);
|
||||
}
|
||||
|
||||
vector<Track::Ptr> MultiMediaSourceMuxer::getTracks(bool trackReady) const {
|
||||
vector<Track::Ptr> MultiMediaSourceMuxer::getTracks(MediaSource &sender, bool trackReady) const {
|
||||
return _muxer->getTracks(trackReady);
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::seekTo(MediaSource &sender, uint32_t ui32Stamp) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
}
|
||||
return listener->seekTo(sender, ui32Stamp);
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::close(MediaSource &sender, bool force) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return false;
|
||||
}
|
||||
return listener->close(sender, force);
|
||||
}
|
||||
|
||||
int MultiMediaSourceMuxer::totalReaderCount(MediaSource &sender) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
return _muxer->totalReaderCount();
|
||||
return totalReaderCount();
|
||||
}
|
||||
return listener->totalReaderCount(sender);
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::onNoneReader(MediaSource &sender){
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
MediaSourceEvent::onNoneReader(sender);
|
||||
return;
|
||||
}
|
||||
listener->onNoneReader(sender);
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::onRegist(MediaSource &sender, bool regist){
|
||||
auto listener = _listener.lock();
|
||||
if (listener) {
|
||||
listener->onRegist(sender, regist);
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path) {
|
||||
return _muxer->setupRecord(sender,type,start,custom_path);
|
||||
return _muxer->setupRecord(sender, type, start, custom_path);
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type) {
|
||||
return _muxer->isRecording(sender,type);
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const string &dst_url, uint16_t dst_port, uint32_t ssrc, bool is_udp, const function<void(const SockException &ex)> &cb){
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
auto ps_rtp_sender = std::make_shared<PSRtpSender>(ssrc);
|
||||
weak_ptr<MultiMediaSourceMuxer> weak_self = shared_from_this();
|
||||
ps_rtp_sender->startSend(dst_url, dst_port, is_udp, [weak_self, ps_rtp_sender, cb](const SockException &ex) {
|
||||
cb(ex);
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self || ex) {
|
||||
return;
|
||||
}
|
||||
for (auto &track : strong_self->_muxer->getTracks(false)) {
|
||||
ps_rtp_sender->addTrack(track);
|
||||
}
|
||||
ps_rtp_sender->addTrackCompleted();
|
||||
strong_self->_ps_rtp_sender = ps_rtp_sender;
|
||||
});
|
||||
#else
|
||||
cb(SockException(Err_other, "该功能未启用,编译时请打开ENABLE_RTPPROXY宏"));
|
||||
#endif//ENABLE_RTPPROXY
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::stopSendRtp(MediaSource &sender){
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
if (_ps_rtp_sender) {
|
||||
_ps_rtp_sender = nullptr;
|
||||
return true;
|
||||
}
|
||||
#endif//ENABLE_RTPPROXY
|
||||
return false;
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::addTrack(const Track::Ptr &track) {
|
||||
_muxer->addTrack(track);
|
||||
}
|
||||
@@ -310,6 +350,14 @@ void MultiMediaSourceMuxer::addTrackCompleted() {
|
||||
_muxer->addTrackCompleted();
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::onAllTrackReady(){
|
||||
_muxer->setMediaListener(shared_from_this());
|
||||
auto listener = _track_listener.lock();
|
||||
if(listener){
|
||||
listener->onAllTrackReady();
|
||||
}
|
||||
}
|
||||
|
||||
void MultiMediaSourceMuxer::resetTracks() {
|
||||
_muxer->resetTracks();
|
||||
}
|
||||
@@ -361,27 +409,36 @@ public:
|
||||
return _frame->getCodecId();
|
||||
}
|
||||
private:
|
||||
Frame::Ptr _frame;
|
||||
int64_t _dts;
|
||||
int64_t _pts;
|
||||
Frame::Ptr _frame;
|
||||
};
|
||||
|
||||
void MultiMediaSourceMuxer::inputFrame(const Frame::Ptr &frame) {
|
||||
GET_CONFIG(bool,modify_stamp,General::kModifyStamp);
|
||||
if(!modify_stamp){
|
||||
//未开启时间戳覆盖
|
||||
_muxer->inputFrame(frame);
|
||||
}else{
|
||||
void MultiMediaSourceMuxer::inputFrame(const Frame::Ptr &frame_in) {
|
||||
GET_CONFIG(bool, modify_stamp, General::kModifyStamp);
|
||||
auto frame = frame_in;
|
||||
if (modify_stamp) {
|
||||
//开启了时间戳覆盖
|
||||
FrameModifyStamp::Ptr new_frame = std::make_shared<FrameModifyStamp>(frame,_stamp[frame->getTrackType()]);
|
||||
//输入时间戳覆盖后的帧
|
||||
_muxer->inputFrame(new_frame);
|
||||
frame = std::make_shared<FrameModifyStamp>(frame, _stamp[frame->getTrackType()]);
|
||||
}
|
||||
_muxer->inputFrame(frame);
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
auto ps_rtp_sender = _ps_rtp_sender;
|
||||
if (ps_rtp_sender) {
|
||||
ps_rtp_sender->inputFrame(frame);
|
||||
}
|
||||
#endif //ENABLE_RTPPROXY
|
||||
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::isEnabled(){
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
return (_muxer->isEnabled() || _ps_rtp_sender);
|
||||
#else
|
||||
return _muxer->isEnabled();
|
||||
#endif //ENABLE_RTPPROXY
|
||||
}
|
||||
|
||||
|
||||
}//namespace mediakit
|
||||
}//namespace mediakit
|
||||
|
||||
@@ -10,14 +10,20 @@
|
||||
|
||||
#ifndef ZLMEDIAKIT_MULTIMEDIASOURCEMUXER_H
|
||||
#define ZLMEDIAKIT_MULTIMEDIASOURCEMUXER_H
|
||||
|
||||
#include "Common/Stamp.h"
|
||||
#include "Rtp/PSRtpSender.h"
|
||||
#include "Record/Recorder.h"
|
||||
#include "Record/HlsRecorder.h"
|
||||
#include "Record/HlsMediaSource.h"
|
||||
#include "Rtsp/RtspMediaSourceMuxer.h"
|
||||
#include "Rtmp/RtmpMediaSourceMuxer.h"
|
||||
#include "Record/Recorder.h"
|
||||
#include "Record/HlsMediaSource.h"
|
||||
#include "Record/HlsRecorder.h"
|
||||
#include "TS/TSMediaSourceMuxer.h"
|
||||
#include "FMP4/FMP4MediaSourceMuxer.h"
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
class MultiMuxerPrivate : public MediaSink , public std::enable_shared_from_this<MultiMuxerPrivate>{
|
||||
class MultiMuxerPrivate : public MediaSink, public std::enable_shared_from_this<MultiMuxerPrivate>{
|
||||
public:
|
||||
friend class MultiMediaSourceMuxer;
|
||||
typedef std::shared_ptr<MultiMuxerPrivate> Ptr;
|
||||
@@ -27,17 +33,12 @@ public:
|
||||
virtual ~Listener() = default;
|
||||
virtual void onAllTrackReady() = 0;
|
||||
};
|
||||
~MultiMuxerPrivate() override ;
|
||||
private:
|
||||
MultiMuxerPrivate(const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
float dur_sec,
|
||||
bool enable_rtsp,
|
||||
bool enable_rtmp,
|
||||
bool enable_hls,
|
||||
bool enable_mp4);
|
||||
|
||||
~MultiMuxerPrivate() override;
|
||||
|
||||
private:
|
||||
MultiMuxerPrivate(const string &vhost,const string &app, const string &stream,float dur_sec,
|
||||
bool enable_rtsp, bool enable_rtmp, bool enable_hls, bool enable_mp4);
|
||||
void resetTracks() override;
|
||||
void setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener);
|
||||
int totalReaderCount() const;
|
||||
@@ -46,83 +47,67 @@ private:
|
||||
bool setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path);
|
||||
bool isRecording(MediaSource &sender, Recorder::type type);
|
||||
bool isEnabled();
|
||||
private:
|
||||
void onTrackReady(const Track::Ptr & track) override;
|
||||
void onTrackFrame(const Frame::Ptr &frame) override;
|
||||
void onAllTrackReady() override;
|
||||
MediaSource::Ptr getHlsMediaSource() const;
|
||||
|
||||
private:
|
||||
string _stream_url;
|
||||
Listener *_track_listener = nullptr;
|
||||
RtmpMediaSourceMuxer::Ptr _rtmp;
|
||||
RtspMediaSourceMuxer::Ptr _rtsp;
|
||||
MediaSinkInterface::Ptr _hls;
|
||||
HlsRecorder::Ptr _hls;
|
||||
MediaSinkInterface::Ptr _mp4;
|
||||
Listener *_listener = nullptr;
|
||||
std::weak_ptr<MediaSourceEvent> _meida_listener;
|
||||
bool _enable_rtxp = false;
|
||||
bool _enable_record = false;
|
||||
TSMediaSourceMuxer::Ptr _ts;
|
||||
FMP4MediaSourceMuxer::Ptr _fmp4;
|
||||
std::weak_ptr<MediaSourceEvent> _listener;
|
||||
};
|
||||
|
||||
class MultiMediaSourceMuxer : public MediaSourceEvent, public MediaSinkInterface, public TrackSource, public std::enable_shared_from_this<MultiMediaSourceMuxer>{
|
||||
class MultiMediaSourceMuxer : public MediaSourceEventInterceptor, public MediaSinkInterface, public MultiMuxerPrivate::Listener, public std::enable_shared_from_this<MultiMediaSourceMuxer>{
|
||||
public:
|
||||
typedef MultiMuxerPrivate::Listener Listener;
|
||||
typedef std::shared_ptr<MultiMediaSourceMuxer> Ptr;
|
||||
|
||||
~MultiMediaSourceMuxer() override;
|
||||
MultiMediaSourceMuxer(const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
float dur_sec = 0.0,
|
||||
bool enable_rtsp = true,
|
||||
bool enable_rtmp = true,
|
||||
bool enable_hls = true,
|
||||
bool enable_mp4 = false);
|
||||
MultiMediaSourceMuxer(const string &vhost, const string &app, const string &stream, float dur_sec = 0.0,
|
||||
bool enable_rtsp = true, bool enable_rtmp = true, bool enable_hls = true, bool enable_mp4 = false);
|
||||
|
||||
/**
|
||||
* 设置事件监听器
|
||||
* @param listener
|
||||
* @param listener 监听器
|
||||
*/
|
||||
void setMediaListener(const std::weak_ptr<MediaSourceEvent> &listener);
|
||||
|
||||
/**
|
||||
* 随着Track就绪事件监听器
|
||||
* @param listener 事件监听器
|
||||
*/
|
||||
void setTrackListener(const std::weak_ptr<MultiMuxerPrivate::Listener> &listener);
|
||||
|
||||
/**
|
||||
* 返回总的消费者个数
|
||||
* @return
|
||||
*/
|
||||
int totalReaderCount() const;
|
||||
|
||||
/**
|
||||
* 判断是否生效(是否正在转其他协议)
|
||||
*/
|
||||
bool isEnabled();
|
||||
|
||||
/**
|
||||
* 设置MediaSource时间戳
|
||||
* @param stamp 时间戳
|
||||
*/
|
||||
void setTimeStamp(uint32_t stamp);
|
||||
|
||||
/**
|
||||
* 随着Track就绪事件监听器
|
||||
* @param listener 事件监听器
|
||||
*/
|
||||
void setTrackListener(Listener *listener);
|
||||
/////////////////////////////////MediaSourceEvent override/////////////////////////////////
|
||||
|
||||
/**
|
||||
* 获取所有Track
|
||||
* @param trackReady 是否筛选过滤未就绪的track
|
||||
* @return 所有Track
|
||||
*/
|
||||
vector<Track::Ptr> getTracks(bool trackReady = true) const override;
|
||||
|
||||
/**
|
||||
* 通知拖动进度条
|
||||
* @param sender 事件发送者
|
||||
* @param ui32Stamp 目标时间戳
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool seekTo(MediaSource &sender,uint32_t ui32Stamp) override;
|
||||
|
||||
/**
|
||||
* 通知停止流生成
|
||||
* @param sender 事件发送者
|
||||
* @param force 是否强制关闭
|
||||
* @return 成功与否
|
||||
*/
|
||||
bool close(MediaSource &sender,bool force) override;
|
||||
vector<Track::Ptr> getTracks(MediaSource &sender, bool trackReady = true) const override;
|
||||
|
||||
/**
|
||||
* 观看总人数
|
||||
@@ -131,19 +116,6 @@ public:
|
||||
*/
|
||||
int totalReaderCount(MediaSource &sender) override;
|
||||
|
||||
/**
|
||||
* 触发无人观看事件
|
||||
* @param sender 触发者
|
||||
*/
|
||||
void onNoneReader(MediaSource &sender) override;
|
||||
|
||||
/**
|
||||
* 媒体注册注销事件
|
||||
* @param sender 触发者
|
||||
* @param regist 是否为注册事件
|
||||
*/
|
||||
void onRegist(MediaSource &sender, bool regist) override;
|
||||
|
||||
/**
|
||||
* 设置录制状态
|
||||
* @param type 录制类型
|
||||
@@ -160,18 +132,35 @@ public:
|
||||
*/
|
||||
bool isRecording(MediaSource &sender, Recorder::type type) override;
|
||||
|
||||
/**
|
||||
* 开始发送ps-rtp流
|
||||
* @param dst_url 目标ip或域名
|
||||
* @param dst_port 目标端口
|
||||
* @param ssrc rtp的ssrc
|
||||
* @param is_udp 是否为udp
|
||||
* @param cb 启动成功或失败回调
|
||||
*/
|
||||
void startSendRtp(MediaSource &sender, const string &dst_url, uint16_t dst_port, uint32_t ssrc, bool is_udp, const function<void(const SockException &ex)> &cb) override;
|
||||
|
||||
/**
|
||||
* 停止ps-rtp发送
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool stopSendRtp(MediaSource &sender) override;
|
||||
|
||||
/////////////////////////////////MediaSinkInterface override/////////////////////////////////
|
||||
|
||||
/**
|
||||
* 添加track,内部会调用Track的clone方法
|
||||
* 只会克隆sps pps这些信息 ,而不会克隆Delegate相关关系
|
||||
* @param track
|
||||
* @param track 添加音频或视频轨道
|
||||
*/
|
||||
void addTrack(const Track::Ptr & track) override;
|
||||
void addTrack(const Track::Ptr &track) override;
|
||||
|
||||
/**
|
||||
* 添加track完毕
|
||||
* @param track
|
||||
*/
|
||||
void addTrackCompleted();
|
||||
void addTrackCompleted() override;
|
||||
|
||||
/**
|
||||
* 重置track
|
||||
@@ -184,14 +173,20 @@ public:
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
/////////////////////////////////MultiMuxerPrivate::Listener override/////////////////////////////////
|
||||
|
||||
/**
|
||||
* 判断是否生效(是否正在转其他协议)
|
||||
* 所有track全部就绪
|
||||
*/
|
||||
bool isEnabled();
|
||||
void onAllTrackReady() override;
|
||||
|
||||
private:
|
||||
MultiMuxerPrivate::Ptr _muxer;
|
||||
std::weak_ptr<MediaSourceEvent> _listener;
|
||||
Stamp _stamp[2];
|
||||
MultiMuxerPrivate::Ptr _muxer;
|
||||
std::weak_ptr<MultiMuxerPrivate::Listener> _track_listener;
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
PSRtpSender::Ptr _ps_rtp_sender;
|
||||
#endif //ENABLE_RTPPROXY
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
@@ -44,77 +44,79 @@ void Stamp::setPlayBack(bool playback) {
|
||||
|
||||
void Stamp::syncTo(Stamp &other){
|
||||
_sync_master = &other;
|
||||
_sync_finished = false;
|
||||
}
|
||||
|
||||
//限制dts回退
|
||||
void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) {
|
||||
revise_l(dts,pts,dts_out,pts_out,modifyStamp);
|
||||
if(_sync_finished || modifyStamp || _playback){
|
||||
//自动生成时间戳或回放或同步完毕
|
||||
if(dts_out < 0) { dts_out = 0; }
|
||||
if(pts_out < 0) { pts_out = 0; }
|
||||
revise_l(dts, pts, dts_out, pts_out, modifyStamp);
|
||||
if (_playback) {
|
||||
//回放允许时间戳回退
|
||||
return;
|
||||
}
|
||||
|
||||
if(_sync_master && _sync_master->_last_dts){
|
||||
if (dts_out < _last_dts_out) {
|
||||
WarnL << "dts回退:" << dts_out << " < " << _last_dts_out;
|
||||
dts_out = _last_dts_out;
|
||||
pts_out = _last_pts_out;
|
||||
return;
|
||||
}
|
||||
_last_dts_out = dts_out;
|
||||
_last_pts_out = pts_out;
|
||||
}
|
||||
|
||||
//音视频时间戳同步
|
||||
void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) {
|
||||
revise_l2(dts, pts, dts_out, pts_out, modifyStamp);
|
||||
if (!_sync_master || modifyStamp || _playback) {
|
||||
//自动生成时间戳或回放或同步完毕
|
||||
return;
|
||||
}
|
||||
|
||||
if (_sync_master && _sync_master->_last_dts_in) {
|
||||
//音视频dts当前时间差
|
||||
int64_t dts_diff = _last_dts - _sync_master->_last_dts;
|
||||
if(ABS(dts_diff) < 5000){
|
||||
int64_t dts_diff = _last_dts_in - _sync_master->_last_dts_in;
|
||||
if (ABS(dts_diff) < 5000) {
|
||||
//如果绝对时间戳小于5秒,那么说明他们的起始时间戳是一致的,那么强制同步
|
||||
_last_relativeStamp = _relativeStamp;
|
||||
_relativeStamp = _sync_master->_relativeStamp + dts_diff;
|
||||
_relative_stamp = _sync_master->_relative_stamp + dts_diff;
|
||||
}
|
||||
//下次不用再强制同步
|
||||
_sync_master = nullptr;
|
||||
}
|
||||
|
||||
if (dts_out < 0 || dts_out < _last_relativeStamp) {
|
||||
//相对时间戳小于0,或者小于上次的时间戳,
|
||||
//那么说明是同步时间戳导致的,在这个过渡期内,我们一直返回上次的结果(目的是为了防止时间戳回退)
|
||||
pts_out = _last_relativeStamp + (pts_out - dts_out);
|
||||
dts_out = _last_relativeStamp;
|
||||
} else if(!_sync_master){
|
||||
//音视频同步过渡期完毕
|
||||
_sync_finished = true;
|
||||
}
|
||||
|
||||
if(pts_out < 0){
|
||||
pts_out = dts_out;
|
||||
}
|
||||
}
|
||||
|
||||
void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) {
|
||||
if(!pts){
|
||||
//求取相对时间戳
|
||||
void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) {
|
||||
if (!pts) {
|
||||
//没有播放时间戳,使其赋值为解码时间戳
|
||||
pts = dts;
|
||||
}
|
||||
|
||||
if(_playback){
|
||||
if (_playback) {
|
||||
//这是点播
|
||||
dts_out = dts;
|
||||
pts_out = pts;
|
||||
_relativeStamp = dts_out;
|
||||
_last_dts = dts;
|
||||
_relative_stamp = dts_out;
|
||||
_last_dts_in = dts;
|
||||
return;
|
||||
}
|
||||
|
||||
//pts和dts的差值
|
||||
int pts_dts_diff = pts - dts;
|
||||
|
||||
if(_last_dts != dts){
|
||||
if (_last_dts_in != dts) {
|
||||
//时间戳发生变更
|
||||
if(modifyStamp){
|
||||
if (modifyStamp) {
|
||||
//内部自己生产时间戳
|
||||
_relativeStamp = _ticker.elapsedTime();
|
||||
}else{
|
||||
_relativeStamp += deltaStamp(dts);
|
||||
_relative_stamp = _ticker.elapsedTime();
|
||||
} else {
|
||||
_relative_stamp += deltaStamp(dts);
|
||||
}
|
||||
_last_dts = dts;
|
||||
_last_dts_in = dts;
|
||||
}
|
||||
dts_out = _relativeStamp;
|
||||
dts_out = _relative_stamp;
|
||||
|
||||
//////////////以下是播放时间戳的计算//////////////////
|
||||
if(ABS(pts_dts_diff) > MAX_CTS){
|
||||
if (ABS(pts_dts_diff) > MAX_CTS) {
|
||||
//如果差值太大,则认为由于回环导致时间戳错乱了
|
||||
pts_dts_diff = 0;
|
||||
}
|
||||
@@ -123,11 +125,11 @@ void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_ou
|
||||
}
|
||||
|
||||
void Stamp::setRelativeStamp(int64_t relativeStamp) {
|
||||
_relativeStamp = relativeStamp;
|
||||
_relative_stamp = relativeStamp;
|
||||
}
|
||||
|
||||
int64_t Stamp::getRelativeStamp() const {
|
||||
return _relativeStamp;
|
||||
return _relative_stamp;
|
||||
}
|
||||
|
||||
bool DtsGenerator::getDts(uint32_t pts, uint32_t &dts){
|
||||
|
||||
@@ -29,6 +29,7 @@ public:
|
||||
* @return 时间戳增量
|
||||
*/
|
||||
int64_t deltaStamp(int64_t stamp);
|
||||
|
||||
private:
|
||||
int64_t _last_stamp = 0;
|
||||
};
|
||||
@@ -41,7 +42,7 @@ public:
|
||||
~Stamp() = default;
|
||||
|
||||
/**
|
||||
* 修正时间戳
|
||||
* 求取相对时间戳,同时实现了音视频同步、限制dts回退等功能
|
||||
* @param dts 输入dts,如果为0则根据系统时间戳生成
|
||||
* @param pts 输入pts,如果为0则等于dts
|
||||
* @param dts_out 输出dts
|
||||
@@ -75,15 +76,20 @@ public:
|
||||
void syncTo(Stamp &other);
|
||||
|
||||
private:
|
||||
//主要实现音视频时间戳同步功能
|
||||
void revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp = false);
|
||||
|
||||
//主要实现获取相对时间戳功能
|
||||
void revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp = false);
|
||||
|
||||
private:
|
||||
int64_t _relativeStamp = 0;
|
||||
int64_t _last_relativeStamp = 0;
|
||||
int64_t _last_dts = 0;
|
||||
int64_t _relative_stamp = 0;
|
||||
int64_t _last_dts_in = 0;
|
||||
int64_t _last_dts_out = 0;
|
||||
int64_t _last_pts_out = 0;
|
||||
SmoothTicker _ticker;
|
||||
bool _playback = false;
|
||||
Stamp *_sync_master = nullptr;
|
||||
bool _sync_finished = true;
|
||||
};
|
||||
|
||||
//dts生成器,
|
||||
@@ -93,8 +99,10 @@ public:
|
||||
DtsGenerator() = default;
|
||||
~DtsGenerator() = default;
|
||||
bool getDts(uint32_t pts, uint32_t &dts);
|
||||
|
||||
private:
|
||||
bool getDts_l(uint32_t pts, uint32_t &dts);
|
||||
|
||||
private:
|
||||
uint32_t _dts_pts_offset = 0;
|
||||
uint32_t _last_dts = 0;
|
||||
|
||||
@@ -40,6 +40,7 @@ bool loadIniConfig(const char *ini_path){
|
||||
namespace Broadcast {
|
||||
const string kBroadcastMediaChanged = "kBroadcastMediaChanged";
|
||||
const string kBroadcastRecordMP4 = "kBroadcastRecordMP4";
|
||||
const string kBroadcastRecordTs = "kBroadcastRecoredTs";
|
||||
const string kBroadcastHttpRequest = "kBroadcastHttpRequest";
|
||||
const string kBroadcastHttpAccess = "kBroadcastHttpAccess";
|
||||
const string kBroadcastOnGetRtspRealm = "kBroadcastOnGetRtspRealm";
|
||||
@@ -63,7 +64,6 @@ const string kMaxStreamWaitTimeMS = GENERAL_FIELD"maxStreamWaitMS";
|
||||
const string kEnableVhost = GENERAL_FIELD"enableVhost";
|
||||
const string kAddMuteAudio = GENERAL_FIELD"addMuteAudio";
|
||||
const string kResetWhenRePlay = GENERAL_FIELD"resetWhenRePlay";
|
||||
const string kPublishToRtxp = GENERAL_FIELD"publishToRtxp";
|
||||
const string kPublishToHls = GENERAL_FIELD"publishToHls";
|
||||
const string kPublishToMP4 = GENERAL_FIELD"publishToMP4";
|
||||
const string kMergeWriteMS = GENERAL_FIELD"mergeWriteMS";
|
||||
@@ -76,7 +76,6 @@ onceToken token([](){
|
||||
mINI::Instance()[kEnableVhost] = 0;
|
||||
mINI::Instance()[kAddMuteAudio] = 1;
|
||||
mINI::Instance()[kResetWhenRePlay] = 1;
|
||||
mINI::Instance()[kPublishToRtxp] = 1;
|
||||
mINI::Instance()[kPublishToHls] = 1;
|
||||
mINI::Instance()[kPublishToMP4] = 0;
|
||||
mINI::Instance()[kMergeWriteMS] = 0;
|
||||
@@ -253,6 +252,8 @@ const string kSegmentRetain = HLS_FIELD"segRetain";
|
||||
const string kFileBufSize = HLS_FIELD"fileBufSize";
|
||||
//录制文件路径
|
||||
const string kFilePath = HLS_FIELD"filePath";
|
||||
// 是否广播 ts 切片完成通知
|
||||
const string kBroadcastRecordTs = HLS_FIELD"broadcastRecordTs";
|
||||
|
||||
onceToken token([](){
|
||||
mINI::Instance()[kSegmentDuration] = 2;
|
||||
@@ -260,6 +261,7 @@ onceToken token([](){
|
||||
mINI::Instance()[kSegmentRetain] = 5;
|
||||
mINI::Instance()[kFileBufSize] = 64 * 1024;
|
||||
mINI::Instance()[kFilePath] = "./www";
|
||||
mINI::Instance()[kBroadcastRecordTs] = false;
|
||||
},nullptr);
|
||||
} //namespace Hls
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ bool loadIniConfig(const char *ini_path = nullptr);
|
||||
#define RTSP_SCHEMA "rtsp"
|
||||
#define RTMP_SCHEMA "rtmp"
|
||||
#define HLS_SCHEMA "hls"
|
||||
#define TS_SCHEMA "ts"
|
||||
#define FMP4_SCHEMA "fmp4"
|
||||
#define DEFAULT_VHOST "__defaultVhost__"
|
||||
|
||||
////////////广播名称///////////
|
||||
@@ -58,7 +60,11 @@ extern const string kBroadcastMediaChanged;
|
||||
|
||||
//录制mp4文件成功后广播
|
||||
extern const string kBroadcastRecordMP4;
|
||||
#define BroadcastRecordMP4Args const MP4Info &info
|
||||
#define BroadcastRecordMP4Args const RecordInfo &info
|
||||
|
||||
// 录制 ts 文件后广播
|
||||
extern const string kBroadcastRecordTs;
|
||||
#define BroadcastRecordTsArgs const RecordInfo &info
|
||||
|
||||
//收到http api请求广播
|
||||
extern const string kBroadcastHttpRequest;
|
||||
@@ -86,8 +92,7 @@ extern const string kBroadcastOnRtspAuth;
|
||||
//如果errMessage为空则代表鉴权成功
|
||||
//enableHls: 是否允许转换hls
|
||||
//enableMP4: 是否运行MP4录制
|
||||
//enableRtxp: rtmp推流时是否运行转rtsp;rtsp推流时,是否允许转rtmp
|
||||
typedef std::function<void(const string &errMessage,bool enableRtxp,bool enableHls,bool enableMP4)> PublishAuthInvoker;
|
||||
typedef std::function<void(const string &errMessage, bool enableHls, bool enableMP4)> PublishAuthInvoker;
|
||||
|
||||
//收到rtsp/rtmp推流事件广播,通过该事件控制推流鉴权
|
||||
extern const string kBroadcastMediaPublish;
|
||||
@@ -165,8 +170,6 @@ extern const string kAddMuteAudio;
|
||||
//拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
|
||||
//如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
|
||||
extern const string kResetWhenRePlay;
|
||||
//是否默认推流时转换成rtsp或rtmp,hook接口(on_publish)中可以覆盖该设置
|
||||
extern const string kPublishToRtxp ;
|
||||
//是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置
|
||||
extern const string kPublishToHls ;
|
||||
//是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
|
||||
@@ -284,6 +287,8 @@ extern const string kSegmentRetain;
|
||||
extern const string kFileBufSize;
|
||||
//录制文件路径
|
||||
extern const string kFilePath;
|
||||
// 是否广播 ts 切片完成通知
|
||||
extern const string kBroadcastRecordTs;
|
||||
} //namespace Hls
|
||||
|
||||
////////////Rtp代理相关配置///////////
|
||||
|
||||
@@ -92,6 +92,16 @@ static void parseAacConfig(const string &config, AdtsHeader &adts) {
|
||||
adts.no_raw_data_blocks_in_frame = 0;
|
||||
}
|
||||
|
||||
int getAacFrameLength(const uint8_t *data, int bytes) {
|
||||
uint16_t len;
|
||||
if (bytes < 7) return -1;
|
||||
if (0xFF != data[0] || 0xF0 != (data[1] & 0xF0)) {
|
||||
return -1;
|
||||
}
|
||||
len = ((uint16_t) (data[3] & 0x03) << 11) | ((uint16_t) data[4] << 3) | ((uint16_t) (data[5] >> 5) & 0x07);
|
||||
return len;
|
||||
}
|
||||
|
||||
string makeAacConfig(const uint8_t *hex, int length){
|
||||
#ifndef ENABLE_MP4
|
||||
if (!(hex[0] == 0xFF && (hex[1] & 0xF0) == 0xF0)) {
|
||||
@@ -134,7 +144,7 @@ int dumpAacConfig(const string &config, int length, uint8_t *out, int out_size)
|
||||
#ifndef ENABLE_MP4
|
||||
AdtsHeader header;
|
||||
parseAacConfig(config, header);
|
||||
header.aac_frame_length = length;
|
||||
header.aac_frame_length = ADTS_HEADER_LEN + length;
|
||||
dumpAdtsHeader(header, out);
|
||||
return ADTS_HEADER_LEN;
|
||||
#else
|
||||
|
||||
@@ -18,44 +18,10 @@
|
||||
namespace mediakit{
|
||||
|
||||
string makeAacConfig(const uint8_t *hex, int length);
|
||||
int getAacFrameLength(const uint8_t *hex, int length);
|
||||
int dumpAacConfig(const string &config, int length, uint8_t *out, int out_size);
|
||||
bool parseAacConfig(const string &config, int &samplerate, int &channels);
|
||||
|
||||
/**
|
||||
* aac帧,包含adts头
|
||||
*/
|
||||
class AACFrame : public FrameImp {
|
||||
public:
|
||||
typedef std::shared_ptr<AACFrame> Ptr;
|
||||
AACFrame(){
|
||||
_codecid = CodecAAC;
|
||||
}
|
||||
};
|
||||
|
||||
class AACFrameNoCacheAble : public FrameFromPtr {
|
||||
public:
|
||||
typedef std::shared_ptr<AACFrameNoCacheAble> Ptr;
|
||||
|
||||
AACFrameNoCacheAble(char *ptr,uint32_t size,uint32_t dts,uint32_t pts = 0,int prefix_size = ADTS_HEADER_LEN){
|
||||
_ptr = ptr;
|
||||
_size = size;
|
||||
_dts = dts;
|
||||
_prefix_size = prefix_size;
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return CodecAAC;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool configFrame() const override{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* aac音频通道
|
||||
*/
|
||||
@@ -135,6 +101,28 @@ public:
|
||||
* @param frame 数据帧
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override{
|
||||
if (frame->prefixSize()) {
|
||||
//有adts头,尝试分帧
|
||||
auto ptr = frame->data();
|
||||
auto end = frame->data() + frame->size();
|
||||
while (ptr < end) {
|
||||
auto frame_len = getAacFrameLength((uint8_t *) ptr, end - ptr);
|
||||
if (frame_len < ADTS_HEADER_LEN) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto sub_frame = std::make_shared<FrameInternal<FrameFromPtr> >(frame, (char *) ptr, frame_len, ADTS_HEADER_LEN);
|
||||
ptr += frame_len;
|
||||
sub_frame->setCodecId(CodecAAC);
|
||||
inputFrame_l(sub_frame);
|
||||
}
|
||||
} else {
|
||||
inputFrame_l(frame);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void inputFrame_l(const Frame::Ptr &frame) {
|
||||
if (_cfg.empty()) {
|
||||
//未获取到aac_cfg信息
|
||||
if (frame->prefixSize()) {
|
||||
@@ -151,7 +139,6 @@ public:
|
||||
AudioTrack::inputFrame(frame);
|
||||
}
|
||||
}
|
||||
private:
|
||||
/**
|
||||
* 解析2个字节的aac配置
|
||||
*/
|
||||
|
||||
@@ -21,29 +21,30 @@ static string getAacCfg(const RtmpPacket &thiz) {
|
||||
if (!thiz.isCfgFrame()) {
|
||||
return ret;
|
||||
}
|
||||
if (thiz.strBuf.size() < 4) {
|
||||
if (thiz.buffer.size() < 4) {
|
||||
WarnL << "bad aac cfg!";
|
||||
return ret;
|
||||
}
|
||||
ret = thiz.strBuf.substr(2);
|
||||
ret = thiz.buffer.substr(2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt, bool) {
|
||||
void AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
if (pkt->isCfgFrame()) {
|
||||
_aac_cfg = getAacCfg(*pkt);
|
||||
onGetAAC(nullptr, 0, 0);
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_aac_cfg.empty()) {
|
||||
onGetAAC(pkt->strBuf.data() + 2, pkt->strBuf.size() - 2, pkt->timeStamp);
|
||||
onGetAAC(pkt->buffer.data() + 2, pkt->buffer.size() - 2, pkt->time_stamp);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AACRtmpDecoder::onGetAAC(const char* data, int len, uint32_t stamp) {
|
||||
auto frame = ResourcePoolHelper<AACFrame>::obtainObj();
|
||||
auto frame = ResourcePoolHelper<FrameImp>::obtainObj();
|
||||
frame->_codec_id = CodecAAC;
|
||||
|
||||
//生成adts头
|
||||
char adts_header[32] = {0};
|
||||
auto size = dumpAacConfig(_aac_cfg, len, (uint8_t *) adts_header, sizeof(adts_header));
|
||||
@@ -95,43 +96,43 @@ void AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
|
||||
if(!_aac_cfg.empty()){
|
||||
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
rtmpPkt->strBuf.clear();
|
||||
rtmpPkt->buffer.clear();
|
||||
|
||||
//header
|
||||
uint8_t is_config = false;
|
||||
rtmpPkt->strBuf.push_back(_audio_flv_flags);
|
||||
rtmpPkt->strBuf.push_back(!is_config);
|
||||
rtmpPkt->buffer.push_back(_audio_flv_flags);
|
||||
rtmpPkt->buffer.push_back(!is_config);
|
||||
|
||||
//aac data
|
||||
rtmpPkt->strBuf.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
||||
rtmpPkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
||||
|
||||
rtmpPkt->bodySize = rtmpPkt->strBuf.size();
|
||||
rtmpPkt->chunkId = CHUNK_AUDIO;
|
||||
rtmpPkt->streamId = STREAM_MEDIA;
|
||||
rtmpPkt->timeStamp = frame->dts();
|
||||
rtmpPkt->typeId = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt, false);
|
||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
||||
rtmpPkt->chunk_id = CHUNK_AUDIO;
|
||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||
rtmpPkt->time_stamp = frame->dts();
|
||||
rtmpPkt->type_id = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt);
|
||||
}
|
||||
}
|
||||
|
||||
void AACRtmpEncoder::makeAudioConfigPkt() {
|
||||
_audio_flv_flags = getAudioRtmpFlags(std::make_shared<AACTrack>(_aac_cfg));
|
||||
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
rtmpPkt->strBuf.clear();
|
||||
rtmpPkt->buffer.clear();
|
||||
|
||||
//header
|
||||
uint8_t is_config = true;
|
||||
rtmpPkt->strBuf.push_back(_audio_flv_flags);
|
||||
rtmpPkt->strBuf.push_back(!is_config);
|
||||
rtmpPkt->buffer.push_back(_audio_flv_flags);
|
||||
rtmpPkt->buffer.push_back(!is_config);
|
||||
//aac config
|
||||
rtmpPkt->strBuf.append(_aac_cfg);
|
||||
rtmpPkt->buffer.append(_aac_cfg);
|
||||
|
||||
rtmpPkt->bodySize = rtmpPkt->strBuf.size();
|
||||
rtmpPkt->chunkId = CHUNK_AUDIO;
|
||||
rtmpPkt->streamId = STREAM_MEDIA;
|
||||
rtmpPkt->timeStamp = 0;
|
||||
rtmpPkt->typeId = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt, false);
|
||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
||||
rtmpPkt->chunk_id = CHUNK_AUDIO;
|
||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||
rtmpPkt->time_stamp = 0;
|
||||
rtmpPkt->type_id = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
@@ -19,7 +19,7 @@ namespace mediakit{
|
||||
/**
|
||||
* aac Rtmp转adts类
|
||||
*/
|
||||
class AACRtmpDecoder : public RtmpCodec , public ResourcePoolHelper<AACFrame> {
|
||||
class AACRtmpDecoder : public RtmpCodec , public ResourcePoolHelper<FrameImp> {
|
||||
public:
|
||||
typedef std::shared_ptr<AACRtmpDecoder> Ptr;
|
||||
|
||||
@@ -28,10 +28,9 @@ public:
|
||||
|
||||
/**
|
||||
* 输入Rtmp并解码
|
||||
* @param Rtmp Rtmp数据包
|
||||
* @param key_pos 此参数内部强制转换为false,请忽略之
|
||||
* @param rtmp Rtmp数据包
|
||||
*/
|
||||
bool inputRtmp(const RtmpPacket::Ptr &Rtmp, bool key_pos = false) override;
|
||||
void inputRtmp(const RtmpPacket::Ptr &rtmp) override;
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return CodecAAC;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
|
||||
#include "AACRtp.h"
|
||||
#define AAC_MAX_FRAME_SIZE (2 * 1024)
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
@@ -68,60 +67,73 @@ AACRtpDecoder::AACRtpDecoder(const Track::Ptr &track) {
|
||||
} else {
|
||||
_aac_cfg = aacTrack->getAacCfg();
|
||||
}
|
||||
_frame = obtainFrame();
|
||||
obtainFrame();
|
||||
}
|
||||
|
||||
AACRtpDecoder::AACRtpDecoder() {
|
||||
_frame = obtainFrame();
|
||||
obtainFrame();
|
||||
}
|
||||
|
||||
AACFrame::Ptr AACRtpDecoder::obtainFrame() {
|
||||
void AACRtpDecoder::obtainFrame() {
|
||||
//从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象
|
||||
auto frame = ResourcePoolHelper<AACFrame>::obtainObj();
|
||||
frame->_prefix_size = 0;
|
||||
frame->_buffer.clear();
|
||||
return frame;
|
||||
_frame = ResourcePoolHelper<FrameImp>::obtainObj();
|
||||
_frame->_prefix_size = 0;
|
||||
_frame->_buffer.clear();
|
||||
_frame->_codec_id = CodecAAC;
|
||||
}
|
||||
|
||||
bool AACRtpDecoder::inputRtp(const RtpPacket::Ptr &rtppack, bool key_pos) {
|
||||
//rtp数据开始部分
|
||||
uint8_t *ptr = (uint8_t *) rtppack->data() + rtppack->offset;
|
||||
//rtp数据末尾
|
||||
const uint8_t *end = (uint8_t *) rtppack->data() + rtppack->size();
|
||||
|
||||
uint8_t *end = (uint8_t *) rtppack->data() + rtppack->size();
|
||||
//首2字节表示Au-Header的个数,单位bit,所以除以16得到Au-Header个数
|
||||
const uint16_t au_header_count = ((ptr[0] << 8) | ptr[1]) >> 4;
|
||||
//忽略Au-Header区
|
||||
ptr += 2 + au_header_count * 2;
|
||||
uint16_t au_header_count = ((ptr[0] << 8) | ptr[1]) >> 4;
|
||||
//记录au_header起始指针
|
||||
uint8_t *au_header_ptr = ptr + 2;
|
||||
ptr = au_header_ptr + au_header_count * 2;
|
||||
|
||||
while (ptr < end) {
|
||||
auto size = (uint32_t) (end - ptr);
|
||||
if (size > AAC_MAX_FRAME_SIZE) {
|
||||
size = AAC_MAX_FRAME_SIZE;
|
||||
if (end < ptr) {
|
||||
//数据不够
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_last_dts) {
|
||||
//记录第一个时间戳
|
||||
_last_dts = rtppack->timeStamp;
|
||||
}
|
||||
|
||||
//每个audio unit时间戳增量
|
||||
auto dts_inc = (rtppack->timeStamp - _last_dts) / au_header_count;
|
||||
if (dts_inc < 0 && dts_inc > 100) {
|
||||
//时间戳增量异常,忽略
|
||||
dts_inc = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < au_header_count; ++i) {
|
||||
// 之后的2字节是AU_HEADER,其中高13位表示一帧AAC负载的字节长度,低3位无用
|
||||
uint16_t size = ((au_header_ptr[0] << 8) | au_header_ptr[1]) >> 3;
|
||||
if (ptr + size > end) {
|
||||
//数据不够
|
||||
break;
|
||||
}
|
||||
if (_frame->size() + size > AAC_MAX_FRAME_SIZE) {
|
||||
//数据太多了,先清空
|
||||
|
||||
if (size) {
|
||||
//设置aac数据
|
||||
_frame->_buffer.assign((char *) ptr, size);
|
||||
//设置当前audio unit时间戳
|
||||
_frame->_dts = _last_dts + i * dts_inc;
|
||||
ptr += size;
|
||||
au_header_ptr += 2;
|
||||
flushData();
|
||||
}
|
||||
//追加aac数据
|
||||
_frame->_buffer.append((char *) ptr, size);
|
||||
_frame->_dts = rtppack->timeStamp;
|
||||
ptr += size;
|
||||
}
|
||||
|
||||
if (rtppack->mark) {
|
||||
//最后一个rtp分片
|
||||
flushData();
|
||||
}
|
||||
//记录上次时间戳
|
||||
_last_dts = rtppack->timeStamp;
|
||||
return false;
|
||||
}
|
||||
|
||||
void AACRtpDecoder::flushData() {
|
||||
if (_frame->_buffer.empty()) {
|
||||
//没有有效数据
|
||||
return;
|
||||
}
|
||||
|
||||
//插入adts头
|
||||
char adts_header[32] = {0};
|
||||
auto size = dumpAacConfig(_aac_cfg, _frame->_buffer.size(), (uint8_t *) adts_header, sizeof(adts_header));
|
||||
@@ -131,11 +143,7 @@ void AACRtpDecoder::flushData() {
|
||||
_frame->_prefix_size = size;
|
||||
}
|
||||
RtpCodec::inputFrame(_frame);
|
||||
_frame = obtainFrame();
|
||||
obtainFrame();
|
||||
}
|
||||
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
|
||||
|
||||
}//namespace mediakit
|
||||
@@ -17,7 +17,7 @@ namespace mediakit{
|
||||
/**
|
||||
* aac rtp转adts类
|
||||
*/
|
||||
class AACRtpDecoder : public RtpCodec , public ResourcePoolHelper<AACFrame> {
|
||||
class AACRtpDecoder : public RtpCodec , public ResourcePoolHelper<FrameImp> {
|
||||
public:
|
||||
typedef std::shared_ptr<AACRtpDecoder> Ptr;
|
||||
|
||||
@@ -39,12 +39,13 @@ protected:
|
||||
AACRtpDecoder();
|
||||
|
||||
private:
|
||||
AACFrame::Ptr obtainFrame();
|
||||
void obtainFrame();
|
||||
void flushData();
|
||||
|
||||
private:
|
||||
AACFrame::Ptr _frame;
|
||||
FrameImp::Ptr _frame;
|
||||
string _aac_cfg;
|
||||
uint32_t _last_dts = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
66
src/Extension/CommonRtmp.cpp
Normal file
66
src/Extension/CommonRtmp.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "CommonRtmp.h"
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
CommonRtmpDecoder::CommonRtmpDecoder(CodecId codec) {
|
||||
_codec = codec;
|
||||
obtainFrame();
|
||||
}
|
||||
|
||||
CodecId CommonRtmpDecoder::getCodecId() const {
|
||||
return _codec;
|
||||
}
|
||||
|
||||
void CommonRtmpDecoder::obtainFrame() {
|
||||
//从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象
|
||||
_frame = ResourcePoolHelper<FrameImp>::obtainObj();
|
||||
_frame->_buffer.clear();
|
||||
_frame->_codec_id = _codec;
|
||||
_frame->_prefix_size = 0;
|
||||
}
|
||||
|
||||
void CommonRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &rtmp) {
|
||||
//拷贝负载
|
||||
_frame->_buffer.assign(rtmp->buffer.data() + 1, rtmp->buffer.size() - 1);
|
||||
_frame->_dts = rtmp->time_stamp;
|
||||
//写入环形缓存
|
||||
RtmpCodec::inputFrame(_frame);
|
||||
//创建下一帧
|
||||
obtainFrame();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
CommonRtmpEncoder::CommonRtmpEncoder(const Track::Ptr &track) : CommonRtmpDecoder(track->getCodecId()) {
|
||||
_audio_flv_flags = getAudioRtmpFlags(track);
|
||||
}
|
||||
|
||||
void CommonRtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
if (!_audio_flv_flags) {
|
||||
return;
|
||||
}
|
||||
RtmpPacket::Ptr rtmp = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
rtmp->buffer.clear();
|
||||
//header
|
||||
rtmp->buffer.push_back(_audio_flv_flags);
|
||||
//data
|
||||
rtmp->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
||||
rtmp->body_size = rtmp->buffer.size();
|
||||
rtmp->chunk_id = CHUNK_AUDIO;
|
||||
rtmp->stream_index = STREAM_MEDIA;
|
||||
rtmp->time_stamp = frame->dts();
|
||||
rtmp->type_id = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(rtmp);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
73
src/Extension/CommonRtmp.h
Normal file
73
src/Extension/CommonRtmp.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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_COMMONRTMP_H
|
||||
#define ZLMEDIAKIT_COMMONRTMP_H
|
||||
|
||||
#include "Frame.h"
|
||||
#include "Rtmp/RtmpCodec.h"
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
/**
|
||||
* 通用 rtmp解码类
|
||||
*/
|
||||
class CommonRtmpDecoder : public RtmpCodec , public ResourcePoolHelper<FrameImp> {
|
||||
public:
|
||||
typedef std::shared_ptr<CommonRtmpDecoder> Ptr;
|
||||
|
||||
~CommonRtmpDecoder() override {}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param codec 编码id
|
||||
*/
|
||||
CommonRtmpDecoder(CodecId codec);
|
||||
|
||||
/**
|
||||
* 返回编码类型ID
|
||||
*/
|
||||
CodecId getCodecId() const override;
|
||||
|
||||
/**
|
||||
* 输入Rtmp并解码
|
||||
* @param rtmp Rtmp数据包
|
||||
*/
|
||||
void inputRtmp(const RtmpPacket::Ptr &rtmp) override;
|
||||
|
||||
private:
|
||||
void obtainFrame();
|
||||
|
||||
private:
|
||||
CodecId _codec;
|
||||
FrameImp::Ptr _frame;
|
||||
};
|
||||
|
||||
/**
|
||||
* 通用 rtmp编码类
|
||||
*/
|
||||
class CommonRtmpEncoder : public CommonRtmpDecoder , public ResourcePoolHelper<RtmpPacket> {
|
||||
public:
|
||||
typedef std::shared_ptr<CommonRtmpEncoder> Ptr;
|
||||
|
||||
CommonRtmpEncoder(const Track::Ptr &track);
|
||||
~CommonRtmpEncoder() override{}
|
||||
|
||||
/**
|
||||
* 输入帧数据
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
private:
|
||||
uint8_t _audio_flv_flags = 0;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //ZLMEDIAKIT_COMMONRTMP_H
|
||||
86
src/Extension/CommonRtp.cpp
Normal file
86
src/Extension/CommonRtp.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "CommonRtp.h"
|
||||
|
||||
CommonRtpDecoder::CommonRtpDecoder(CodecId codec, int max_frame_size ){
|
||||
_codec = codec;
|
||||
_max_frame_size = max_frame_size;
|
||||
obtainFrame();
|
||||
}
|
||||
|
||||
CodecId CommonRtpDecoder::getCodecId() const {
|
||||
return _codec;
|
||||
}
|
||||
|
||||
void CommonRtpDecoder::obtainFrame() {
|
||||
_frame = ResourcePoolHelper<FrameImp>::obtainObj();
|
||||
_frame->_buffer.clear();
|
||||
_frame->_prefix_size = 0;
|
||||
_frame->_dts = 0;
|
||||
_frame->_codec_id = _codec;
|
||||
}
|
||||
|
||||
bool CommonRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool){
|
||||
auto payload = rtp->data() + rtp->offset;
|
||||
auto size = rtp->size() - rtp->offset;
|
||||
if (size <= 0) {
|
||||
//无实际负载
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_frame->_dts != rtp->timeStamp || _frame->_buffer.size() > _max_frame_size) {
|
||||
//时间戳发生变化或者缓存超过MAX_FRAME_SIZE,则清空上帧数据
|
||||
if (!_frame->_buffer.empty()) {
|
||||
//有有效帧,则输出
|
||||
RtpCodec::inputFrame(_frame);
|
||||
}
|
||||
|
||||
//新的一帧数据
|
||||
obtainFrame();
|
||||
_frame->_dts = rtp->timeStamp;
|
||||
_drop_flag = false;
|
||||
} else if (_last_seq != 0 && (uint16_t)(_last_seq + 1) != rtp->sequence) {
|
||||
//时间戳未发生变化,但是seq却不连续,说明中间rtp丢包了,那么整帧应该废弃
|
||||
WarnL << "rtp丢包:" << _last_seq << " -> " << rtp->sequence;
|
||||
_drop_flag = true;
|
||||
_frame->_buffer.clear();
|
||||
}
|
||||
|
||||
if (!_drop_flag) {
|
||||
_frame->_buffer.append(payload, size);
|
||||
}
|
||||
|
||||
_last_seq = rtp->sequence;
|
||||
return false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
CommonRtpEncoder::CommonRtpEncoder(CodecId codec, uint32_t ssrc, uint32_t mtu_size,
|
||||
uint32_t sample_rate, uint8_t payload_type, uint8_t interleaved)
|
||||
: CommonRtpDecoder(codec), RtpInfo(ssrc, mtu_size, sample_rate, payload_type, interleaved) {
|
||||
}
|
||||
|
||||
void CommonRtpEncoder::inputFrame(const Frame::Ptr &frame){
|
||||
GET_CONFIG(uint32_t, cycleMS, Rtp::kCycleMS);
|
||||
auto stamp = frame->dts() % cycleMS;
|
||||
auto ptr = frame->data() + frame->prefixSize();
|
||||
auto len = frame->size() - frame->prefixSize();
|
||||
auto remain_size = len;
|
||||
const auto max_rtp_size = _ui32MtuSize - 20;
|
||||
|
||||
while (remain_size > 0) {
|
||||
auto rtp_size = remain_size > max_rtp_size ? max_rtp_size : remain_size;
|
||||
RtpCodec::inputRtp(makeRtp(getTrackType(), ptr, rtp_size, false, stamp), false);
|
||||
ptr += rtp_size;
|
||||
remain_size -= rtp_size;
|
||||
}
|
||||
}
|
||||
85
src/Extension/CommonRtp.h
Normal file
85
src/Extension/CommonRtp.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_COMMONRTP_H
|
||||
#define ZLMEDIAKIT_COMMONRTP_H
|
||||
|
||||
#include "Frame.h"
|
||||
#include "Rtsp/RtpCodec.h"
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
/**
|
||||
* 通用 rtp解码类
|
||||
*/
|
||||
class CommonRtpDecoder : public RtpCodec, public ResourcePoolHelper<FrameImp> {
|
||||
public:
|
||||
typedef std::shared_ptr <CommonRtpDecoder> Ptr;
|
||||
|
||||
~CommonRtpDecoder() override {}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param codec 编码id
|
||||
* @param max_frame_size 允许的最大帧大小
|
||||
*/
|
||||
CommonRtpDecoder(CodecId codec, int max_frame_size = 2 * 1024);
|
||||
|
||||
/**
|
||||
* 返回编码类型ID
|
||||
*/
|
||||
CodecId getCodecId() const override;
|
||||
|
||||
/**
|
||||
* 输入rtp并解码
|
||||
* @param rtp rtp数据包
|
||||
* @param key_pos 此参数内部强制转换为false,请忽略之
|
||||
*/
|
||||
bool inputRtp(const RtpPacket::Ptr &rtp, bool key_pos = false) override;
|
||||
|
||||
private:
|
||||
void obtainFrame();
|
||||
|
||||
private:
|
||||
bool _drop_flag = false;
|
||||
uint16_t _last_seq = 0;
|
||||
int _max_frame_size;
|
||||
CodecId _codec;
|
||||
FrameImp::Ptr _frame;
|
||||
};
|
||||
|
||||
/**
|
||||
* 通用 rtp编码类
|
||||
*/
|
||||
class CommonRtpEncoder : public CommonRtpDecoder, public RtpInfo {
|
||||
public:
|
||||
typedef std::shared_ptr <CommonRtpEncoder> Ptr;
|
||||
|
||||
~CommonRtpEncoder() override {}
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param codec 编码类型
|
||||
* @param ssrc ssrc
|
||||
* @param mtu_size mtu 大小
|
||||
* @param sample_rate 采样率
|
||||
* @param payload_type pt类型
|
||||
* @param interleaved rtsp interleaved 值
|
||||
*/
|
||||
CommonRtpEncoder(CodecId codec, uint32_t ssrc, uint32_t mtu_size, uint32_t sample_rate, uint8_t payload_type, uint8_t interleaved);
|
||||
|
||||
/**
|
||||
* 输入帧数据并编码成rtp
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //ZLMEDIAKIT_COMMONRTP_H
|
||||
@@ -13,11 +13,13 @@
|
||||
#include "H264Rtmp.h"
|
||||
#include "H265Rtmp.h"
|
||||
#include "AACRtmp.h"
|
||||
#include "G711Rtmp.h"
|
||||
#include "CommonRtmp.h"
|
||||
#include "H264Rtp.h"
|
||||
#include "AACRtp.h"
|
||||
#include "G711Rtp.h"
|
||||
#include "H265Rtp.h"
|
||||
#include "CommonRtp.h"
|
||||
#include "Opus.h"
|
||||
#include "G711.h"
|
||||
#include "Common/Parser.h"
|
||||
|
||||
namespace mediakit{
|
||||
@@ -42,6 +44,10 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) {
|
||||
return std::make_shared<AACTrack>(aac_cfg);
|
||||
}
|
||||
|
||||
if (strcasecmp(track->_codec.data(), "opus") == 0) {
|
||||
return std::make_shared<OpusTrack>();
|
||||
}
|
||||
|
||||
if (strcasecmp(track->_codec.data(), "PCMA") == 0) {
|
||||
return std::make_shared<G711Track>(CodecG711A, track->_samplerate, track->_channel, 16);
|
||||
}
|
||||
@@ -114,11 +120,12 @@ RtpCodec::Ptr Factory::getRtpEncoderBySdp(const Sdp::Ptr &sdp) {
|
||||
auto interleaved = sdp->getTrackType() * 2;
|
||||
auto codec_id = sdp->getCodecId();
|
||||
switch (codec_id){
|
||||
case CodecH264 : return std::make_shared<H264RtpEncoder>(ssrc,mtu,sample_rate,pt,interleaved);
|
||||
case CodecH265 : return std::make_shared<H265RtpEncoder>(ssrc,mtu,sample_rate,pt,interleaved);
|
||||
case CodecAAC : return std::make_shared<AACRtpEncoder>(ssrc,mtu,sample_rate,pt,interleaved);
|
||||
case CodecH264 : return std::make_shared<H264RtpEncoder>(ssrc, mtu, sample_rate, pt, interleaved);
|
||||
case CodecH265 : return std::make_shared<H265RtpEncoder>(ssrc, mtu, sample_rate, pt, interleaved);
|
||||
case CodecAAC : return std::make_shared<AACRtpEncoder>(ssrc, mtu, sample_rate, pt, interleaved);
|
||||
case CodecOpus :
|
||||
case CodecG711A :
|
||||
case CodecG711U : return std::make_shared<G711RtpEncoder>(codec_id, ssrc, mtu, sample_rate, pt, interleaved);
|
||||
case CodecG711U : return std::make_shared<CommonRtpEncoder>(codec_id, ssrc, mtu, sample_rate, pt, interleaved);
|
||||
default : WarnL << "暂不支持该CodecId:" << codec_id; return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -128,8 +135,9 @@ RtpCodec::Ptr Factory::getRtpDecoderByTrack(const Track::Ptr &track) {
|
||||
case CodecH264 : return std::make_shared<H264RtpDecoder>();
|
||||
case CodecH265 : return std::make_shared<H265RtpDecoder>();
|
||||
case CodecAAC : return std::make_shared<AACRtpDecoder>(track->clone());
|
||||
case CodecOpus :
|
||||
case CodecG711A :
|
||||
case CodecG711U : return std::make_shared<G711RtpDecoder>(track->getCodecId());
|
||||
case CodecG711U : return std::make_shared<CommonRtpDecoder>(track->getCodecId());
|
||||
default : WarnL << "暂不支持该CodecId:" << track->getCodecName(); return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -137,40 +145,35 @@ RtpCodec::Ptr Factory::getRtpDecoderByTrack(const Track::Ptr &track) {
|
||||
/////////////////////////////rtmp相关///////////////////////////////////////////
|
||||
|
||||
static CodecId getVideoCodecIdByAmf(const AMFValue &val){
|
||||
if (val.type() == AMF_STRING){
|
||||
if (val.type() == AMF_STRING) {
|
||||
auto str = val.as_string();
|
||||
if(str == "avc1"){
|
||||
if (str == "avc1") {
|
||||
return CodecH264;
|
||||
}
|
||||
if(str == "mp4a"){
|
||||
return CodecAAC;
|
||||
}
|
||||
if(str == "hev1" || str == "hvc1"){
|
||||
if (str == "hev1" || str == "hvc1") {
|
||||
return CodecH265;
|
||||
}
|
||||
WarnL << "暂不支持该Amf:" << str;
|
||||
WarnL << "暂不支持该视频Amf:" << str;
|
||||
return CodecInvalid;
|
||||
}
|
||||
|
||||
if (val.type() != AMF_NULL){
|
||||
if (val.type() != AMF_NULL) {
|
||||
auto type_id = val.as_integer();
|
||||
switch (type_id){
|
||||
case FLV_CODEC_H264: return CodecH264;
|
||||
case FLV_CODEC_AAC: return CodecAAC;
|
||||
case FLV_CODEC_H265: return CodecH265;
|
||||
default : WarnL << "暂不支持该Amf:" << type_id; return CodecInvalid;
|
||||
switch (type_id) {
|
||||
case FLV_CODEC_H264 : return CodecH264;
|
||||
case FLV_CODEC_H265 : return CodecH265;
|
||||
default : WarnL << "暂不支持该视频Amf:" << type_id; return CodecInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
return CodecInvalid;
|
||||
}
|
||||
|
||||
|
||||
Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0) {
|
||||
switch (codecId){
|
||||
case CodecH264 : return std::make_shared<H264Track>();
|
||||
case CodecH265 : return std::make_shared<H265Track>();
|
||||
case CodecAAC : return std::make_shared<AACTrack>();
|
||||
case CodecOpus: return std::make_shared<OpusTrack>();
|
||||
case CodecG711A :
|
||||
case CodecG711U : return (sample_rate && channels && sample_bit) ? std::make_shared<G711Track>(codecId, sample_rate, channels, sample_bit) : nullptr;
|
||||
default : WarnL << "暂不支持该CodecId:" << codecId; return nullptr;
|
||||
@@ -191,7 +194,7 @@ static CodecId getAudioCodecIdByAmf(const AMFValue &val) {
|
||||
if (str == "mp4a") {
|
||||
return CodecAAC;
|
||||
}
|
||||
WarnL << "暂不支持该Amf:" << str;
|
||||
WarnL << "暂不支持该音频Amf:" << str;
|
||||
return CodecInvalid;
|
||||
}
|
||||
|
||||
@@ -201,7 +204,8 @@ static CodecId getAudioCodecIdByAmf(const AMFValue &val) {
|
||||
case FLV_CODEC_AAC : return CodecAAC;
|
||||
case FLV_CODEC_G711A : return CodecG711A;
|
||||
case FLV_CODEC_G711U : return CodecG711U;
|
||||
default : WarnL << "暂不支持该Amf:" << type_id; return CodecInvalid;
|
||||
case FLV_CODEC_OPUS : return CodecOpus;
|
||||
default : WarnL << "暂不支持该音频Amf:" << type_id; return CodecInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +225,7 @@ RtmpCodec::Ptr Factory::getRtmpCodecByTrack(const Track::Ptr &track, bool is_enc
|
||||
case CodecH264 : return std::make_shared<H264RtmpEncoder>(track);
|
||||
case CodecAAC : return std::make_shared<AACRtmpEncoder>(track);
|
||||
case CodecH265 : return std::make_shared<H265RtmpEncoder>(track);
|
||||
case CodecOpus : return std::make_shared<CommonRtmpEncoder>(track);
|
||||
case CodecG711A :
|
||||
case CodecG711U : {
|
||||
auto audio_track = dynamic_pointer_cast<AudioTrack>(track);
|
||||
@@ -235,7 +240,7 @@ RtmpCodec::Ptr Factory::getRtmpCodecByTrack(const Track::Ptr &track, bool is_enc
|
||||
<< ",该音频已被忽略";
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_shared<G711RtmpEncoder>(track);
|
||||
return std::make_shared<CommonRtmpEncoder>(track);
|
||||
}
|
||||
default : WarnL << "暂不支持该CodecId:" << track->getCodecName(); return nullptr;
|
||||
}
|
||||
@@ -248,6 +253,7 @@ AMFValue Factory::getAmfByCodecId(CodecId codecId) {
|
||||
case CodecH265: return AMFValue(FLV_CODEC_H265);
|
||||
case CodecG711A: return AMFValue(FLV_CODEC_G711A);
|
||||
case CodecG711U: return AMFValue(FLV_CODEC_G711U);
|
||||
case CodecOpus: return AMFValue(FLV_CODEC_OPUS);
|
||||
default: return AMFValue(AMF_NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,12 +35,12 @@ public:
|
||||
_dts = frame->dts();
|
||||
_pts = frame->pts();
|
||||
_prefix_size = frame->prefixSize();
|
||||
_codecid = frame->getCodecId();
|
||||
_codec_id = frame->getCodecId();
|
||||
_key = frame->keyFrame();
|
||||
_config = frame->configFrame();
|
||||
}
|
||||
|
||||
virtual ~FrameCacheAble() = default;
|
||||
~FrameCacheAble() override = default;
|
||||
|
||||
/**
|
||||
* 可以被缓存
|
||||
@@ -49,10 +49,6 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return _codecid;
|
||||
}
|
||||
|
||||
bool keyFrame() const override{
|
||||
return _key;
|
||||
}
|
||||
@@ -60,10 +56,10 @@ public:
|
||||
bool configFrame() const override{
|
||||
return _config;
|
||||
}
|
||||
|
||||
private:
|
||||
Frame::Ptr _frame;
|
||||
BufferRaw::Ptr _buffer;
|
||||
CodecId _codecid;
|
||||
bool _key;
|
||||
bool _config;
|
||||
};
|
||||
|
||||
@@ -148,7 +148,7 @@ public:
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return _codecid;
|
||||
return _codec_id;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
@@ -160,7 +160,7 @@ public:
|
||||
}
|
||||
|
||||
public:
|
||||
CodecId _codecid = CodecInvalid;
|
||||
CodecId _codec_id = CodecInvalid;
|
||||
string _buffer;
|
||||
uint32_t _dts = 0;
|
||||
uint32_t _pts = 0;
|
||||
@@ -314,9 +314,24 @@ private:
|
||||
class FrameFromPtr : public Frame{
|
||||
public:
|
||||
typedef std::shared_ptr<FrameFromPtr> Ptr;
|
||||
|
||||
FrameFromPtr(CodecId codec_id, char *ptr, uint32_t size, uint32_t dts, uint32_t pts = 0, int prefix_size = 0)
|
||||
: FrameFromPtr(ptr, size, dts, pts, prefix_size) {
|
||||
_codec_id = codec_id;
|
||||
}
|
||||
|
||||
FrameFromPtr(char *ptr, uint32_t size, uint32_t dts, uint32_t pts = 0, int prefix_size = 0){
|
||||
_ptr = ptr;
|
||||
_size = size;
|
||||
_dts = dts;
|
||||
_pts = pts;
|
||||
_prefix_size = prefix_size;
|
||||
}
|
||||
|
||||
char *data() const override{
|
||||
return _ptr;
|
||||
}
|
||||
|
||||
uint32_t size() const override {
|
||||
return _size;
|
||||
}
|
||||
@@ -336,12 +351,80 @@ public:
|
||||
bool cacheAble() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override {
|
||||
if (_codec_id == CodecInvalid) {
|
||||
throw std::invalid_argument("FrameFromPtr对象未设置codec类型");
|
||||
}
|
||||
return _codec_id;
|
||||
}
|
||||
|
||||
void setCodecId(CodecId codec_id) {
|
||||
_codec_id = codec_id;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool configFrame() const override{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
FrameFromPtr() {}
|
||||
|
||||
protected:
|
||||
char *_ptr;
|
||||
uint32_t _size;
|
||||
uint32_t _dts;
|
||||
uint32_t _pts = 0;
|
||||
uint32_t _prefix_size;
|
||||
CodecId _codec_id = CodecInvalid;
|
||||
};
|
||||
|
||||
/**
|
||||
* 该对象可以把Buffer对象转换成可缓存的Frame对象
|
||||
*/
|
||||
template <typename Parent>
|
||||
class FrameWrapper : public Parent{
|
||||
public:
|
||||
~FrameWrapper() = default;
|
||||
|
||||
/**
|
||||
* 构造frame
|
||||
* @param buf 数据缓存
|
||||
* @param dts 解码时间戳
|
||||
* @param pts 显示时间戳
|
||||
* @param prefix 帧前缀长度
|
||||
* @param offset buffer有效数据偏移量
|
||||
*/
|
||||
FrameWrapper(const Buffer::Ptr &buf, int64_t dts, int64_t pts, int prefix, int offset) : Parent(buf->data() + offset, buf->size() - offset, dts, pts, prefix){
|
||||
_buf = buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造frame
|
||||
* @param buf 数据缓存
|
||||
* @param dts 解码时间戳
|
||||
* @param pts 显示时间戳
|
||||
* @param prefix 帧前缀长度
|
||||
* @param offset buffer有效数据偏移量
|
||||
* @param codec 帧类型
|
||||
*/
|
||||
FrameWrapper(const Buffer::Ptr &buf, int64_t dts, int64_t pts, int prefix, int offset, CodecId codec) : Parent(codec, buf->data() + offset, buf->size() - offset, dts, pts, prefix){
|
||||
_buf = buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* 该帧可缓存
|
||||
*/
|
||||
bool cacheAble() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
Buffer::Ptr _buf;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
@@ -16,47 +16,6 @@
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
/**
|
||||
* G711帧
|
||||
*/
|
||||
class G711Frame : public FrameImp {
|
||||
public:
|
||||
G711Frame(){
|
||||
_codecid = CodecG711A;
|
||||
}
|
||||
};
|
||||
|
||||
class G711FrameNoCacheAble : public FrameFromPtr {
|
||||
public:
|
||||
typedef std::shared_ptr<G711FrameNoCacheAble> Ptr;
|
||||
|
||||
G711FrameNoCacheAble(char *ptr,uint32_t size,uint32_t dts, uint32_t pts = 0,int prefix_size = 0){
|
||||
_ptr = ptr;
|
||||
_size = size;
|
||||
_dts = dts;
|
||||
_prefix_size = prefix_size;
|
||||
}
|
||||
|
||||
void setCodec(CodecId codecId){
|
||||
_codecId = codecId;
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return _codecId;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool configFrame() const override{
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
CodecId _codecId;
|
||||
};
|
||||
|
||||
/**
|
||||
* G711音频通道
|
||||
*/
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "G711Rtmp.h"
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
G711RtmpDecoder::G711RtmpDecoder(CodecId codecId) {
|
||||
_frame = obtainFrame();
|
||||
_codecId = codecId;
|
||||
}
|
||||
|
||||
G711Frame::Ptr G711RtmpDecoder::obtainFrame() {
|
||||
//从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象
|
||||
auto frame = ResourcePoolHelper<G711Frame>::obtainObj();
|
||||
frame->_buffer.clear();
|
||||
frame->_codecid = _codecId;
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool G711RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt, bool) {
|
||||
//拷贝G711负载
|
||||
_frame->_buffer.assign(pkt->strBuf.data() + 1, pkt->strBuf.size() - 1);
|
||||
_frame->_dts = pkt->timeStamp;
|
||||
//写入环形缓存
|
||||
RtmpCodec::inputFrame(_frame);
|
||||
_frame = obtainFrame();
|
||||
return false;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
G711RtmpEncoder::G711RtmpEncoder(const Track::Ptr &track) : G711RtmpDecoder(track->getCodecId()) {
|
||||
_audio_flv_flags = getAudioRtmpFlags(track);
|
||||
}
|
||||
|
||||
void G711RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
if(!_audio_flv_flags){
|
||||
return;
|
||||
}
|
||||
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
rtmpPkt->strBuf.clear();
|
||||
//header
|
||||
rtmpPkt->strBuf.push_back(_audio_flv_flags);
|
||||
|
||||
//g711 data
|
||||
rtmpPkt->strBuf.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
||||
rtmpPkt->bodySize = rtmpPkt->strBuf.size();
|
||||
rtmpPkt->chunkId = CHUNK_AUDIO;
|
||||
rtmpPkt->streamId = STREAM_MEDIA;
|
||||
rtmpPkt->timeStamp = frame->dts();
|
||||
rtmpPkt->typeId = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt, false);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* 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_G711RTMPCODEC_H
|
||||
#define ZLMEDIAKIT_G711RTMPCODEC_H
|
||||
|
||||
#include "Rtmp/RtmpCodec.h"
|
||||
#include "Extension/Track.h"
|
||||
#include "Extension/G711.h"
|
||||
|
||||
namespace mediakit{
|
||||
/**
|
||||
* G711 Rtmp转G711 Frame类
|
||||
*/
|
||||
class G711RtmpDecoder : public RtmpCodec , public ResourcePoolHelper<G711Frame> {
|
||||
public:
|
||||
typedef std::shared_ptr<G711RtmpDecoder> Ptr;
|
||||
|
||||
G711RtmpDecoder(CodecId codecId);
|
||||
~G711RtmpDecoder() {}
|
||||
|
||||
/**
|
||||
* 输入Rtmp并解码
|
||||
* @param Rtmp Rtmp数据包
|
||||
* @param key_pos 此参数内部强制转换为false,请忽略之
|
||||
*/
|
||||
bool inputRtmp(const RtmpPacket::Ptr &Rtmp, bool key_pos = false) override;
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return _codecId;
|
||||
}
|
||||
private:
|
||||
G711Frame::Ptr obtainFrame();
|
||||
private:
|
||||
G711Frame::Ptr _frame;
|
||||
CodecId _codecId;
|
||||
};
|
||||
|
||||
/**
|
||||
* G711 RTMP打包类
|
||||
*/
|
||||
class G711RtmpEncoder : public G711RtmpDecoder , public ResourcePoolHelper<RtmpPacket> {
|
||||
public:
|
||||
typedef std::shared_ptr<G711RtmpEncoder> Ptr;
|
||||
|
||||
G711RtmpEncoder(const Track::Ptr &track);
|
||||
~G711RtmpEncoder() {}
|
||||
|
||||
/**
|
||||
* 输入G711 数据
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
private:
|
||||
uint8_t _audio_flv_flags = 0;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#endif //ZLMEDIAKIT_G711RTMPCODEC_H
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "G711Rtp.h"
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
G711RtpDecoder::G711RtpDecoder(CodecId codecid){
|
||||
_codecid = codecid;
|
||||
_frame = obtainFrame();
|
||||
}
|
||||
|
||||
G711Frame::Ptr G711RtpDecoder::obtainFrame() {
|
||||
//从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象
|
||||
auto frame = ResourcePoolHelper<G711Frame>::obtainObj();
|
||||
frame->_buffer.clear();
|
||||
frame->_codecid = _codecid;
|
||||
frame->_dts = 0;
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool G711RtpDecoder::inputRtp(const RtpPacket::Ptr &rtppack, bool) {
|
||||
// 获取rtp数据长度
|
||||
int length = rtppack->size() - rtppack->offset;
|
||||
// 获取rtp数据
|
||||
const char *rtp_packet_buf = rtppack->data() + rtppack->offset;
|
||||
|
||||
if (rtppack->timeStamp != _frame->_dts) {
|
||||
//时间戳变更,清空上一帧
|
||||
onGetG711(_frame);
|
||||
}
|
||||
|
||||
//追加数据
|
||||
_frame->_buffer.append(rtp_packet_buf, length);
|
||||
//赋值时间戳
|
||||
_frame->_dts = rtppack->timeStamp;
|
||||
|
||||
if (rtppack->mark || _frame->_buffer.size() > 10 * 1024) {
|
||||
//标记为mark时,或者内存快溢出时,我们认为这是该帧最后一个包
|
||||
onGetG711(_frame);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void G711RtpDecoder::onGetG711(const G711Frame::Ptr &frame) {
|
||||
if(!frame->_buffer.empty()){
|
||||
//写入环形缓存
|
||||
RtpCodec::inputFrame(frame);
|
||||
_frame = obtainFrame();
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
G711RtpEncoder::G711RtpEncoder(CodecId codecid, uint32_t ui32Ssrc, uint32_t ui32MtuSize,
|
||||
uint32_t ui32SampleRate, uint8_t ui8PayloadType, uint8_t ui8Interleaved) :
|
||||
G711RtpDecoder(codecid),
|
||||
RtpInfo(ui32Ssrc, ui32MtuSize, ui32SampleRate, ui8PayloadType, ui8Interleaved) {
|
||||
}
|
||||
|
||||
void G711RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
GET_CONFIG(uint32_t, cycleMS, Rtp::kCycleMS);
|
||||
auto uiStamp = frame->dts();
|
||||
auto pcData = frame->data() + frame->prefixSize();
|
||||
auto iLen = frame->size() - frame->prefixSize();
|
||||
|
||||
uiStamp %= cycleMS;
|
||||
char *ptr = (char *) pcData;
|
||||
int iSize = iLen;
|
||||
while (iSize > 0) {
|
||||
if (iSize <= _ui32MtuSize - 20) {
|
||||
makeG711Rtp(ptr, iSize, true, uiStamp);
|
||||
break;
|
||||
}
|
||||
makeG711Rtp(ptr, _ui32MtuSize - 20, false, uiStamp);
|
||||
ptr += (_ui32MtuSize - 20);
|
||||
iSize -= (_ui32MtuSize - 20);
|
||||
}
|
||||
}
|
||||
|
||||
void G711RtpEncoder::makeG711Rtp(const void *data, unsigned int len, bool mark, uint32_t uiStamp) {
|
||||
RtpCodec::inputRtp(makeRtp(getTrackType(), data, len, mark, uiStamp), false);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* 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_G711RTPCODEC_H
|
||||
#define ZLMEDIAKIT_G711RTPCODEC_H
|
||||
#include "Rtsp/RtpCodec.h"
|
||||
#include "Extension/G711.h"
|
||||
namespace mediakit{
|
||||
|
||||
/**
|
||||
* rtp转G711类
|
||||
*/
|
||||
class G711RtpDecoder : public RtpCodec , public ResourcePoolHelper<G711Frame> {
|
||||
public:
|
||||
typedef std::shared_ptr<G711RtpDecoder> Ptr;
|
||||
|
||||
G711RtpDecoder(CodecId codecid);
|
||||
~G711RtpDecoder() {}
|
||||
|
||||
/**
|
||||
* 输入rtp并解码
|
||||
* @param rtp rtp数据包
|
||||
* @param key_pos 此参数内部强制转换为false,请忽略之
|
||||
*/
|
||||
bool inputRtp(const RtpPacket::Ptr &rtp, bool key_pos = false) override;
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return _codecid;
|
||||
}
|
||||
|
||||
private:
|
||||
void onGetG711(const G711Frame::Ptr &frame);
|
||||
G711Frame::Ptr obtainFrame();
|
||||
|
||||
private:
|
||||
G711Frame::Ptr _frame;
|
||||
CodecId _codecid;
|
||||
};
|
||||
|
||||
/**
|
||||
* g711 转rtp类
|
||||
*/
|
||||
class G711RtpEncoder : public G711RtpDecoder , public RtpInfo {
|
||||
public:
|
||||
typedef std::shared_ptr<G711RtpEncoder> Ptr;
|
||||
|
||||
/**
|
||||
* @param ui32Ssrc ssrc
|
||||
* @param ui32MtuSize mtu 大小
|
||||
* @param ui32SampleRate 采样率
|
||||
* @param ui8PayloadType pt类型
|
||||
* @param ui8Interleaved rtsp interleaved 值
|
||||
*/
|
||||
G711RtpEncoder(CodecId codecid,
|
||||
uint32_t ui32Ssrc,
|
||||
uint32_t ui32MtuSize,
|
||||
uint32_t ui32SampleRate,
|
||||
uint8_t ui8PayloadType = 0,
|
||||
uint8_t ui8Interleaved = TrackAudio * 2);
|
||||
~G711RtpEncoder() {}
|
||||
|
||||
/**
|
||||
* @param frame g711数据
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
private:
|
||||
void makeG711Rtp(const void *pData, unsigned int uiLen, bool bMark, uint32_t uiStamp);
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#endif //ZLMEDIAKIT_G711RTPCODEC_H
|
||||
@@ -30,14 +30,16 @@ public:
|
||||
typedef std::shared_ptr<H264Frame> Ptr;
|
||||
|
||||
typedef enum {
|
||||
NAL_SPS = 7,
|
||||
NAL_PPS = 8,
|
||||
NAL_IDR = 5,
|
||||
NAL_SEI = 6,
|
||||
NAL_SPS = 7,
|
||||
NAL_PPS = 8,
|
||||
NAL_AUD = 9,
|
||||
NAL_B_P = 1,
|
||||
} NalType;
|
||||
|
||||
H264Frame(){
|
||||
_codecid = CodecH264;
|
||||
_codec_id = CodecH264;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
@@ -68,10 +70,7 @@ public:
|
||||
_dts = dts;
|
||||
_pts = pts;
|
||||
_prefix_size = prefix_size;
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return CodecH264;
|
||||
_codec_id = CodecH264;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
@@ -182,8 +181,8 @@ public:
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override{
|
||||
int type = H264_TYPE(*((uint8_t *)frame->data() + frame->prefixSize()));
|
||||
if(type == H264Frame::NAL_SPS || type == H264Frame::NAL_SEI){
|
||||
//有些设备会把SPS PPS IDR帧当做一个帧打包,所以我们要split一下
|
||||
if(type != H264Frame::NAL_B_P && type != H264Frame::NAL_IDR){
|
||||
//非I/B/P帧情况下,split一下,防止多个帧粘合在一起
|
||||
splitH264(frame->data(), frame->size(), frame->prefixSize(), [&](const char *ptr, int len, int prefix) {
|
||||
H264FrameInternal::Ptr sub_frame = std::make_shared<H264FrameInternal>(frame, (char *)ptr, len, prefix);
|
||||
inputFrame_l(sub_frame);
|
||||
@@ -227,6 +226,10 @@ private:
|
||||
VideoTrack::inputFrame(frame);
|
||||
}
|
||||
break;
|
||||
case H264Frame::NAL_AUD:{
|
||||
//忽略AUD帧;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
VideoTrack::inputFrame(frame);
|
||||
|
||||
@@ -23,10 +23,6 @@ H264Frame::Ptr H264RtmpDecoder::obtainFrame() {
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool H264RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos) {
|
||||
return decodeRtmp(rtmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回不带0x00 00 00 01头的sps
|
||||
* @return
|
||||
@@ -39,18 +35,18 @@ static string getH264SPS(const RtmpPacket &thiz) {
|
||||
if (!thiz.isCfgFrame()) {
|
||||
return ret;
|
||||
}
|
||||
if (thiz.strBuf.size() < 13) {
|
||||
if (thiz.buffer.size() < 13) {
|
||||
WarnL << "bad H264 cfg!";
|
||||
return ret;
|
||||
}
|
||||
uint16_t sps_size ;
|
||||
memcpy(&sps_size, thiz.strBuf.data() + 11,2);
|
||||
memcpy(&sps_size, thiz.buffer.data() + 11, 2);
|
||||
sps_size = ntohs(sps_size);
|
||||
if ((int) thiz.strBuf.size() < 13 + sps_size) {
|
||||
if ((int) thiz.buffer.size() < 13 + sps_size) {
|
||||
WarnL << "bad H264 cfg!";
|
||||
return ret;
|
||||
}
|
||||
ret.assign(thiz.strBuf.data() + 13, sps_size);
|
||||
ret.assign(thiz.buffer.data() + 13, sps_size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -66,60 +62,59 @@ static string getH264PPS(const RtmpPacket &thiz) {
|
||||
if (!thiz.isCfgFrame()) {
|
||||
return ret;
|
||||
}
|
||||
if (thiz.strBuf.size() < 13) {
|
||||
if (thiz.buffer.size() < 13) {
|
||||
WarnL << "bad H264 cfg!";
|
||||
return ret;
|
||||
}
|
||||
uint16_t sps_size ;
|
||||
memcpy(&sps_size,thiz.strBuf.data() + 11,2);
|
||||
memcpy(&sps_size, thiz.buffer.data() + 11, 2);
|
||||
sps_size = ntohs(sps_size);
|
||||
|
||||
if ((int) thiz.strBuf.size() < 13 + sps_size + 1 + 2) {
|
||||
if ((int) thiz.buffer.size() < 13 + sps_size + 1 + 2) {
|
||||
WarnL << "bad H264 cfg!";
|
||||
return ret;
|
||||
}
|
||||
uint16_t pps_size ;
|
||||
memcpy(&pps_size, thiz.strBuf.data() + 13 + sps_size + 1,2);
|
||||
memcpy(&pps_size, thiz.buffer.data() + 13 + sps_size + 1, 2);
|
||||
pps_size = ntohs(pps_size);
|
||||
|
||||
if ((int) thiz.strBuf.size() < 13 + sps_size + 1 + 2 + pps_size) {
|
||||
if ((int) thiz.buffer.size() < 13 + sps_size + 1 + 2 + pps_size) {
|
||||
WarnL << "bad H264 cfg!";
|
||||
return ret;
|
||||
}
|
||||
ret.assign(thiz.strBuf.data() + 13 + sps_size + 1 + 2, pps_size);
|
||||
ret.assign(thiz.buffer.data() + 13 + sps_size + 1 + 2, pps_size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool H264RtmpDecoder::decodeRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
void H264RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
if (pkt->isCfgFrame()) {
|
||||
//缓存sps pps,后续插入到I帧之前
|
||||
_sps = getH264SPS(*pkt);
|
||||
_pps = getH264PPS(*pkt);
|
||||
onGetH264(_sps.data(), _sps.size(), pkt->timeStamp , pkt->timeStamp);
|
||||
onGetH264(_pps.data(), _pps.size(), pkt->timeStamp , pkt->timeStamp);
|
||||
return false;
|
||||
onGetH264(_sps.data(), _sps.size(), pkt->time_stamp , pkt->time_stamp);
|
||||
onGetH264(_pps.data(), _pps.size(), pkt->time_stamp , pkt->time_stamp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkt->strBuf.size() > 9) {
|
||||
uint32_t iTotalLen = pkt->strBuf.size();
|
||||
if (pkt->buffer.size() > 9) {
|
||||
uint32_t iTotalLen = pkt->buffer.size();
|
||||
uint32_t iOffset = 5;
|
||||
uint8_t *cts_ptr = (uint8_t *) (pkt->strBuf.data() + 2);
|
||||
uint8_t *cts_ptr = (uint8_t *) (pkt->buffer.data() + 2);
|
||||
int32_t cts = (((cts_ptr[0] << 16) | (cts_ptr[1] << 8) | (cts_ptr[2])) + 0xff800000) ^ 0xff800000;
|
||||
auto pts = pkt->timeStamp + cts;
|
||||
auto pts = pkt->time_stamp + cts;
|
||||
|
||||
while(iOffset + 4 < iTotalLen){
|
||||
uint32_t iFrameLen;
|
||||
memcpy(&iFrameLen, pkt->strBuf.data() + iOffset, 4);
|
||||
memcpy(&iFrameLen, pkt->buffer.data() + iOffset, 4);
|
||||
iFrameLen = ntohl(iFrameLen);
|
||||
iOffset += 4;
|
||||
if(iFrameLen + iOffset > iTotalLen){
|
||||
break;
|
||||
}
|
||||
onGetH264(pkt->strBuf.data() + iOffset, iFrameLen, pkt->timeStamp , pts);
|
||||
onGetH264(pkt->buffer.data() + iOffset, iFrameLen, pkt->time_stamp , pts);
|
||||
iOffset += iFrameLen;
|
||||
}
|
||||
}
|
||||
return pkt->isVideoKeyFrame();
|
||||
}
|
||||
|
||||
inline void H264RtmpDecoder::onGetH264(const char* pcData, int iLen, uint32_t dts,uint32_t pts) {
|
||||
@@ -190,8 +185,8 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
}
|
||||
}
|
||||
|
||||
if(_lastPacket && _lastPacket->timeStamp != frame->dts()) {
|
||||
RtmpCodec::inputRtmp(_lastPacket, _lastPacket->isVideoKeyFrame());
|
||||
if(_lastPacket && _lastPacket->time_stamp != frame->dts()) {
|
||||
RtmpCodec::inputRtmp(_lastPacket);
|
||||
_lastPacket = nullptr;
|
||||
}
|
||||
|
||||
@@ -202,23 +197,23 @@ void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
flags |= (((frame->configFrame() || frame->keyFrame()) ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4);
|
||||
|
||||
_lastPacket = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
_lastPacket->strBuf.clear();
|
||||
_lastPacket->strBuf.push_back(flags);
|
||||
_lastPacket->strBuf.push_back(!is_config);
|
||||
_lastPacket->buffer.clear();
|
||||
_lastPacket->buffer.push_back(flags);
|
||||
_lastPacket->buffer.push_back(!is_config);
|
||||
auto cts = frame->pts() - frame->dts();
|
||||
cts = htonl(cts);
|
||||
_lastPacket->strBuf.append((char *)&cts + 1, 3);
|
||||
_lastPacket->buffer.append((char *)&cts + 1, 3);
|
||||
|
||||
_lastPacket->chunkId = CHUNK_VIDEO;
|
||||
_lastPacket->streamId = STREAM_MEDIA;
|
||||
_lastPacket->timeStamp = frame->dts();
|
||||
_lastPacket->typeId = MSG_VIDEO;
|
||||
_lastPacket->chunk_id = CHUNK_VIDEO;
|
||||
_lastPacket->stream_index = STREAM_MEDIA;
|
||||
_lastPacket->time_stamp = frame->dts();
|
||||
_lastPacket->type_id = MSG_VIDEO;
|
||||
|
||||
}
|
||||
auto size = htonl(iLen);
|
||||
_lastPacket->strBuf.append((char *) &size, 4);
|
||||
_lastPacket->strBuf.append(pcData, iLen);
|
||||
_lastPacket->bodySize = _lastPacket->strBuf.size();
|
||||
_lastPacket->buffer.append((char *) &size, 4);
|
||||
_lastPacket->buffer.append(pcData, iLen);
|
||||
_lastPacket->body_size = _lastPacket->buffer.size();
|
||||
}
|
||||
|
||||
void H264RtmpEncoder::makeVideoConfigPkt() {
|
||||
@@ -227,39 +222,39 @@ void H264RtmpEncoder::makeVideoConfigPkt() {
|
||||
bool is_config = true;
|
||||
|
||||
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
rtmpPkt->strBuf.clear();
|
||||
rtmpPkt->buffer.clear();
|
||||
|
||||
//header
|
||||
rtmpPkt->strBuf.push_back(flags);
|
||||
rtmpPkt->strBuf.push_back(!is_config);
|
||||
rtmpPkt->buffer.push_back(flags);
|
||||
rtmpPkt->buffer.push_back(!is_config);
|
||||
//cts
|
||||
rtmpPkt->strBuf.append("\x0\x0\x0", 3);
|
||||
rtmpPkt->buffer.append("\x0\x0\x0", 3);
|
||||
|
||||
//AVCDecoderConfigurationRecord start
|
||||
rtmpPkt->strBuf.push_back(1); // version
|
||||
rtmpPkt->strBuf.push_back(_sps[1]); // profile
|
||||
rtmpPkt->strBuf.push_back(_sps[2]); // compat
|
||||
rtmpPkt->strBuf.push_back(_sps[3]); // level
|
||||
rtmpPkt->strBuf.push_back(0xff); // 6 bits reserved + 2 bits nal size length - 1 (11)
|
||||
rtmpPkt->strBuf.push_back(0xe1); // 3 bits reserved + 5 bits number of sps (00001)
|
||||
rtmpPkt->buffer.push_back(1); // version
|
||||
rtmpPkt->buffer.push_back(_sps[1]); // profile
|
||||
rtmpPkt->buffer.push_back(_sps[2]); // compat
|
||||
rtmpPkt->buffer.push_back(_sps[3]); // level
|
||||
rtmpPkt->buffer.push_back(0xff); // 6 bits reserved + 2 bits nal size length - 1 (11)
|
||||
rtmpPkt->buffer.push_back(0xe1); // 3 bits reserved + 5 bits number of sps (00001)
|
||||
//sps
|
||||
uint16_t size = _sps.size();
|
||||
size = htons(size);
|
||||
rtmpPkt->strBuf.append((char *) &size, 2);
|
||||
rtmpPkt->strBuf.append(_sps);
|
||||
rtmpPkt->buffer.append((char *) &size, 2);
|
||||
rtmpPkt->buffer.append(_sps);
|
||||
//pps
|
||||
rtmpPkt->strBuf.push_back(1); // version
|
||||
rtmpPkt->buffer.push_back(1); // version
|
||||
size = _pps.size();
|
||||
size = htons(size);
|
||||
rtmpPkt->strBuf.append((char *) &size, 2);
|
||||
rtmpPkt->strBuf.append(_pps);
|
||||
rtmpPkt->buffer.append((char *) &size, 2);
|
||||
rtmpPkt->buffer.append(_pps);
|
||||
|
||||
rtmpPkt->bodySize = rtmpPkt->strBuf.size();
|
||||
rtmpPkt->chunkId = CHUNK_VIDEO;
|
||||
rtmpPkt->streamId = STREAM_MEDIA;
|
||||
rtmpPkt->timeStamp = 0;
|
||||
rtmpPkt->typeId = MSG_VIDEO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt, false);
|
||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
||||
rtmpPkt->chunk_id = CHUNK_VIDEO;
|
||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||
rtmpPkt->time_stamp = 0;
|
||||
rtmpPkt->type_id = MSG_VIDEO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
@@ -32,17 +32,17 @@ public:
|
||||
/**
|
||||
* 输入264 Rtmp包
|
||||
* @param rtmp Rtmp包
|
||||
* @param key_pos 此参数忽略之
|
||||
*/
|
||||
bool inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos = true) override;
|
||||
void inputRtmp(const RtmpPacket::Ptr &rtmp) override;
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return CodecH264;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool decodeRtmp(const RtmpPacket::Ptr &Rtmp);
|
||||
void onGetH264(const char *pcData, int iLen, uint32_t dts,uint32_t pts);
|
||||
H264Frame::Ptr obtainFrame();
|
||||
|
||||
protected:
|
||||
H264Frame::Ptr _h264frame;
|
||||
string _sps;
|
||||
|
||||
@@ -12,13 +12,6 @@
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned forbidden_zero_bit :1;
|
||||
unsigned nal_ref_idc :2;
|
||||
unsigned type :5;
|
||||
} NALU;
|
||||
|
||||
typedef struct {
|
||||
unsigned S :1;
|
||||
unsigned E :1;
|
||||
@@ -26,15 +19,6 @@ typedef struct {
|
||||
unsigned type :5;
|
||||
} FU;
|
||||
|
||||
static bool MakeNalu(uint8_t in, NALU &nal) {
|
||||
nal.forbidden_zero_bit = in >> 7;
|
||||
if (nal.forbidden_zero_bit) {
|
||||
return false;
|
||||
}
|
||||
nal.nal_ref_idc = (in & 0x60) >> 5;
|
||||
nal.type = in & 0x1f;
|
||||
return true;
|
||||
}
|
||||
static bool MakeFU(uint8_t in, FU &fu) {
|
||||
fu.S = in >> 7;
|
||||
fu.E = (in >> 6) & 0x01;
|
||||
@@ -86,30 +70,28 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
28 FU-A Fragmentation unit 5.8
|
||||
29 FU-B Fragmentation unit 5.8
|
||||
30-31 undefined -
|
||||
|
||||
|
||||
*/
|
||||
const uint8_t *frame = (uint8_t *) rtppack->data() + rtppack->offset;
|
||||
int length = rtppack->size() - rtppack->offset;
|
||||
NALU nal;
|
||||
MakeNalu(*frame, nal);
|
||||
int nal_type = *frame & 0x1F;
|
||||
int nal_suffix = *frame & (~0x1F);
|
||||
|
||||
if (nal.type >= 0 && nal.type < 24) {
|
||||
if (nal_type >= 0 && nal_type < 24) {
|
||||
//a full frame
|
||||
_h264frame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h264frame->_buffer.append((char *)frame, length);
|
||||
_h264frame->_buffer.append((char *) frame, length);
|
||||
_h264frame->_pts = rtppack->timeStamp;
|
||||
auto key = _h264frame->keyFrame();
|
||||
onGetH264(_h264frame);
|
||||
return (key); //i frame
|
||||
}
|
||||
|
||||
switch (nal.type){
|
||||
switch (nal_type){
|
||||
case 24:{
|
||||
// 24 STAP-A 单一时间的组合包
|
||||
bool haveIDR = false;
|
||||
auto ptr = frame + 1;
|
||||
while(true){
|
||||
while (true) {
|
||||
int off = ptr - frame;
|
||||
if (off >= length) {
|
||||
break;
|
||||
@@ -121,14 +103,12 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
if (off + len > length) {
|
||||
break;
|
||||
}
|
||||
if(len >= 10){
|
||||
//过小的帧丢弃
|
||||
NALU nal;
|
||||
MakeNalu(ptr[0], nal);
|
||||
if (len > 0) {
|
||||
//有有效数据
|
||||
_h264frame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h264frame->_buffer.append((char *)ptr, len);
|
||||
_h264frame->_buffer.append((char *) ptr, len);
|
||||
_h264frame->_pts = rtppack->timeStamp;
|
||||
if(nal.type == H264Frame::NAL_IDR){
|
||||
if ((ptr[0] & 0x1F) == H264Frame::NAL_IDR) {
|
||||
haveIDR = true;
|
||||
}
|
||||
onGetH264(_h264frame);
|
||||
@@ -144,10 +124,9 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
MakeFU(frame[1], fu);
|
||||
if (fu.S) {
|
||||
//该帧的第一个rtp包 FU-A start
|
||||
char tmp = (nal.forbidden_zero_bit << 7 | nal.nal_ref_idc << 5 | fu.type);
|
||||
_h264frame->_buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h264frame->_buffer.push_back(tmp);
|
||||
_h264frame->_buffer.append((char *)frame + 2, length - 2);
|
||||
_h264frame->_buffer.push_back(nal_suffix | fu.type);
|
||||
_h264frame->_buffer.append((char *) frame + 2, length - 2);
|
||||
_h264frame->_pts = rtppack->timeStamp;
|
||||
//该函数return时,保存下当前sequence,以便下次对比seq是否连续
|
||||
_lastSeq = rtppack->sequence;
|
||||
@@ -163,20 +142,20 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
|
||||
if (!fu.E) {
|
||||
//该帧的中间rtp包 FU-A mid
|
||||
_h264frame->_buffer.append((char *)frame + 2, length - 2);
|
||||
_h264frame->_buffer.append((char *) frame + 2, length - 2);
|
||||
//该函数return时,保存下当前sequence,以便下次对比seq是否连续
|
||||
_lastSeq = rtppack->sequence;
|
||||
return false;
|
||||
}
|
||||
|
||||
//该帧最后一个rtp包 FU-A end
|
||||
_h264frame->_buffer.append((char *)frame + 2, length - 2);
|
||||
_h264frame->_buffer.append((char *) frame + 2, length - 2);
|
||||
_h264frame->_pts = rtppack->timeStamp;
|
||||
onGetH264(_h264frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
default:{
|
||||
default: {
|
||||
// 29 FU-B 单NAL单元B模式
|
||||
// 25 STAP-B 单一时间的组合包
|
||||
// 26 MTAP16 多个时间的组合包
|
||||
@@ -184,7 +163,7 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
||||
// 0 udef
|
||||
// 30 udef
|
||||
// 31 udef
|
||||
WarnL << "不支持的rtp类型:" << (int)nal.type << " " << rtppack->sequence;
|
||||
WarnL << "不支持的rtp类型:" << (int) nal_type << " " << rtppack->sequence;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -215,63 +194,62 @@ H264RtpEncoder::H264RtpEncoder(uint32_t ui32Ssrc,
|
||||
|
||||
void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
GET_CONFIG(uint32_t,cycleMS,Rtp::kCycleMS);
|
||||
auto pcData = frame->data() + frame->prefixSize();
|
||||
auto uiStamp = frame->pts();
|
||||
auto iLen = frame->size() - frame->prefixSize();
|
||||
//获取NALU的5bit 帧类型
|
||||
unsigned char naluType = H264_TYPE(pcData[0]);
|
||||
auto ptr = frame->data() + frame->prefixSize();
|
||||
auto pts = frame->pts() % cycleMS;
|
||||
auto len = frame->size() - frame->prefixSize();
|
||||
auto nal_type = H264_TYPE(ptr[0]);
|
||||
auto max_rtp_size = _ui32MtuSize - 2;
|
||||
|
||||
uiStamp %= cycleMS;
|
||||
int iSize = _ui32MtuSize - 2;
|
||||
//超过MTU则按照FU-A模式打包
|
||||
if (iLen > iSize) {
|
||||
if (len > max_rtp_size) {
|
||||
//最高位bit为forbidden_zero_bit,
|
||||
//后面2bit为nal_ref_idc(帧重要程度),00:可以丢,11:不能丢
|
||||
//末尾5bit为nalu type,固定为28(FU-A)
|
||||
unsigned char f_nri_flags = (*((unsigned char *) pcData) & 0x60) | 28;
|
||||
unsigned char nal_fu_a = (*((unsigned char *) ptr) & (~0x1F)) | 28;
|
||||
unsigned char s_e_r_flags;
|
||||
bool bFirst = true;
|
||||
bool mark = false;
|
||||
int nOffset = 1;
|
||||
while (!mark) {
|
||||
if (iLen <= nOffset + iSize) {
|
||||
bool fu_a_start = true;
|
||||
bool mark_bit = false;
|
||||
int offset = 1;
|
||||
while (!mark_bit) {
|
||||
if (len <= offset + max_rtp_size) {
|
||||
//已经拆分结束
|
||||
iSize = iLen - nOffset;
|
||||
mark = true;
|
||||
max_rtp_size = len - offset;
|
||||
mark_bit = true;
|
||||
//FU-A end
|
||||
s_e_r_flags = (1 << 6) | naluType;
|
||||
} else if (bFirst) {
|
||||
s_e_r_flags = (1 << 6) | nal_type;
|
||||
} else if (fu_a_start) {
|
||||
//FU-A start
|
||||
s_e_r_flags = (1 << 7) | naluType;
|
||||
s_e_r_flags = (1 << 7) | nal_type;
|
||||
} else {
|
||||
//FU-A mid
|
||||
s_e_r_flags = naluType;
|
||||
s_e_r_flags = nal_type;
|
||||
}
|
||||
|
||||
{
|
||||
//传入nullptr先不做payload的内存拷贝
|
||||
auto rtp = makeRtp(getTrackType(), nullptr, iSize + 2, mark, uiStamp);
|
||||
auto rtp = makeRtp(getTrackType(), nullptr, max_rtp_size + 2, mark_bit, pts);
|
||||
//rtp payload 负载部分
|
||||
uint8_t *payload = (uint8_t*)rtp->data() + rtp->offset;
|
||||
//FU-A 第1个字节
|
||||
payload[0] = f_nri_flags;
|
||||
payload[0] = nal_fu_a;
|
||||
//FU-A 第2个字节
|
||||
payload[1] = s_e_r_flags;
|
||||
//H264 数据
|
||||
memcpy(payload + 2, (unsigned char *) pcData + nOffset, iSize);
|
||||
memcpy(payload + 2, (unsigned char *) ptr + offset, max_rtp_size);
|
||||
//输入到rtp环形缓存
|
||||
RtpCodec::inputRtp(rtp,bFirst && naluType == H264Frame::NAL_IDR);
|
||||
RtpCodec::inputRtp(rtp, fu_a_start && nal_type == H264Frame::NAL_IDR);
|
||||
}
|
||||
nOffset += iSize;
|
||||
bFirst = false;
|
||||
offset += max_rtp_size;
|
||||
fu_a_start = false;
|
||||
}
|
||||
} else {
|
||||
makeH264Rtp(naluType,pcData, iLen, true, true, uiStamp);
|
||||
//如果帧长度不超过mtu, 则按照Single NAL unit packet per H.264 方式打包
|
||||
makeH264Rtp(ptr, len, false, false, pts);
|
||||
}
|
||||
}
|
||||
|
||||
void H264RtpEncoder::makeH264Rtp(int nal_type,const void* data, unsigned int len, bool mark, bool first_packet, uint32_t uiStamp) {
|
||||
RtpCodec::inputRtp(makeRtp(getTrackType(),data,len,mark,uiStamp),first_packet && nal_type == H264Frame::NAL_IDR);
|
||||
void H264RtpEncoder::makeH264Rtp(const void* data, unsigned int len, bool mark, bool gop_pos, uint32_t uiStamp) {
|
||||
RtpCodec::inputRtp(makeRtp(getTrackType(), data, len, mark, uiStamp), gop_pos);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
@@ -78,7 +78,7 @@ public:
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
private:
|
||||
void makeH264Rtp(int nal_type,const void *pData, unsigned int uiLen, bool bMark, bool first_packet, uint32_t uiStamp);
|
||||
void makeH264Rtp(const void *pData, unsigned int uiLen, bool bMark, bool gop_pos, uint32_t uiStamp);
|
||||
};
|
||||
|
||||
}//namespace mediakit{
|
||||
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
} NaleType;
|
||||
|
||||
H265Frame(){
|
||||
_codecid = CodecH265;
|
||||
_codec_id = CodecH265;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
@@ -92,10 +92,7 @@ public:
|
||||
_dts = dts;
|
||||
_pts = pts;
|
||||
_prefix_size = prefix_size;
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override {
|
||||
return CodecH265;
|
||||
_codec_id = CodecH265;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
|
||||
@@ -27,10 +27,6 @@ H265Frame::Ptr H265RtmpDecoder::obtainFrame() {
|
||||
return frame;
|
||||
}
|
||||
|
||||
bool H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos) {
|
||||
return decodeRtmp(rtmp);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_MP4
|
||||
/**
|
||||
* 返回不带0x00 00 00 01头的sps
|
||||
@@ -43,61 +39,60 @@ static bool getH265ConfigFrame(const RtmpPacket &thiz,string &frame) {
|
||||
if (!thiz.isCfgFrame()) {
|
||||
return false;
|
||||
}
|
||||
if (thiz.strBuf.size() < 6) {
|
||||
if (thiz.buffer.size() < 6) {
|
||||
WarnL << "bad H265 cfg!";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto extra = thiz.strBuf.data() + 5;
|
||||
auto bytes = thiz.strBuf.size() - 5;
|
||||
auto extra = thiz.buffer.data() + 5;
|
||||
auto bytes = thiz.buffer.size() - 5;
|
||||
|
||||
struct mpeg4_hevc_t hevc = {0};
|
||||
if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *) extra, bytes, &hevc) > 0) {
|
||||
uint8_t config[1024] = {0};
|
||||
int size = mpeg4_hevc_to_nalu(&hevc, config, sizeof(config));
|
||||
uint8_t *config = new uint8_t[bytes * 2];
|
||||
int size = mpeg4_hevc_to_nalu(&hevc, config, bytes * 2);
|
||||
if (size > 4) {
|
||||
frame.assign((char *) config + 4, size - 4);
|
||||
return true;
|
||||
}
|
||||
delete [] config;
|
||||
return size > 4;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool H265RtmpDecoder::decodeRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
if (pkt->isCfgFrame()) {
|
||||
#ifdef ENABLE_MP4
|
||||
string config;
|
||||
if(getH265ConfigFrame(*pkt,config)){
|
||||
onGetH265(config.data(), config.size(), pkt->timeStamp , pkt->timeStamp);
|
||||
onGetH265(config.data(), config.size(), pkt->time_stamp , pkt->time_stamp);
|
||||
}
|
||||
#else
|
||||
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
||||
#endif
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (pkt->strBuf.size() > 9) {
|
||||
uint32_t iTotalLen = pkt->strBuf.size();
|
||||
if (pkt->buffer.size() > 9) {
|
||||
uint32_t iTotalLen = pkt->buffer.size();
|
||||
uint32_t iOffset = 5;
|
||||
uint8_t *cts_ptr = (uint8_t *) (pkt->strBuf.data() + 2);
|
||||
uint8_t *cts_ptr = (uint8_t *) (pkt->buffer.data() + 2);
|
||||
int32_t cts = (((cts_ptr[0] << 16) | (cts_ptr[1] << 8) | (cts_ptr[2])) + 0xff800000) ^ 0xff800000;
|
||||
auto pts = pkt->timeStamp + cts;
|
||||
auto pts = pkt->time_stamp + cts;
|
||||
|
||||
while(iOffset + 4 < iTotalLen){
|
||||
uint32_t iFrameLen;
|
||||
memcpy(&iFrameLen, pkt->strBuf.data() + iOffset, 4);
|
||||
memcpy(&iFrameLen, pkt->buffer.data() + iOffset, 4);
|
||||
iFrameLen = ntohl(iFrameLen);
|
||||
iOffset += 4;
|
||||
if(iFrameLen + iOffset > iTotalLen){
|
||||
break;
|
||||
}
|
||||
onGetH265(pkt->strBuf.data() + iOffset, iFrameLen, pkt->timeStamp , pts);
|
||||
onGetH265(pkt->buffer.data() + iOffset, iFrameLen, pkt->time_stamp , pts);
|
||||
iOffset += iFrameLen;
|
||||
}
|
||||
}
|
||||
return pkt->isVideoKeyFrame();
|
||||
}
|
||||
|
||||
inline void H265RtmpDecoder::onGetH265(const char* pcData, int iLen, uint32_t dts,uint32_t pts) {
|
||||
@@ -176,8 +171,8 @@ void H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(_lastPacket && _lastPacket->timeStamp != frame->dts()) {
|
||||
RtmpCodec::inputRtmp(_lastPacket, _lastPacket->isVideoKeyFrame());
|
||||
if(_lastPacket && _lastPacket->time_stamp != frame->dts()) {
|
||||
RtmpCodec::inputRtmp(_lastPacket);
|
||||
_lastPacket = nullptr;
|
||||
}
|
||||
|
||||
@@ -188,23 +183,23 @@ void H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
flags |= (((frame->configFrame() || frame->keyFrame()) ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4);
|
||||
|
||||
_lastPacket = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
_lastPacket->strBuf.clear();
|
||||
_lastPacket->strBuf.push_back(flags);
|
||||
_lastPacket->strBuf.push_back(!is_config);
|
||||
_lastPacket->buffer.clear();
|
||||
_lastPacket->buffer.push_back(flags);
|
||||
_lastPacket->buffer.push_back(!is_config);
|
||||
auto cts = frame->pts() - frame->dts();
|
||||
cts = htonl(cts);
|
||||
_lastPacket->strBuf.append((char *)&cts + 1, 3);
|
||||
_lastPacket->buffer.append((char *)&cts + 1, 3);
|
||||
|
||||
_lastPacket->chunkId = CHUNK_VIDEO;
|
||||
_lastPacket->streamId = STREAM_MEDIA;
|
||||
_lastPacket->timeStamp = frame->dts();
|
||||
_lastPacket->typeId = MSG_VIDEO;
|
||||
_lastPacket->chunk_id = CHUNK_VIDEO;
|
||||
_lastPacket->stream_index = STREAM_MEDIA;
|
||||
_lastPacket->time_stamp = frame->dts();
|
||||
_lastPacket->type_id = MSG_VIDEO;
|
||||
|
||||
}
|
||||
auto size = htonl(iLen);
|
||||
_lastPacket->strBuf.append((char *) &size, 4);
|
||||
_lastPacket->strBuf.append(pcData, iLen);
|
||||
_lastPacket->bodySize = _lastPacket->strBuf.size();
|
||||
_lastPacket->buffer.append((char *) &size, 4);
|
||||
_lastPacket->buffer.append(pcData, iLen);
|
||||
_lastPacket->body_size = _lastPacket->buffer.size();
|
||||
}
|
||||
|
||||
void H265RtmpEncoder::makeVideoConfigPkt() {
|
||||
@@ -214,13 +209,13 @@ void H265RtmpEncoder::makeVideoConfigPkt() {
|
||||
bool is_config = true;
|
||||
|
||||
RtmpPacket::Ptr rtmpPkt = ResourcePoolHelper<RtmpPacket>::obtainObj();
|
||||
rtmpPkt->strBuf.clear();
|
||||
rtmpPkt->buffer.clear();
|
||||
|
||||
//header
|
||||
rtmpPkt->strBuf.push_back(flags);
|
||||
rtmpPkt->strBuf.push_back(!is_config);
|
||||
rtmpPkt->buffer.push_back(flags);
|
||||
rtmpPkt->buffer.push_back(!is_config);
|
||||
//cts
|
||||
rtmpPkt->strBuf.append("\x0\x0\x0", 3);
|
||||
rtmpPkt->buffer.append("\x0\x0\x0", 3);
|
||||
|
||||
struct mpeg4_hevc_t hevc = {0};
|
||||
string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps +
|
||||
@@ -235,14 +230,14 @@ void H265RtmpEncoder::makeVideoConfigPkt() {
|
||||
}
|
||||
|
||||
//HEVCDecoderConfigurationRecord
|
||||
rtmpPkt->strBuf.append((char *)extra_data, extra_data_size);
|
||||
rtmpPkt->buffer.append((char *)extra_data, extra_data_size);
|
||||
|
||||
rtmpPkt->bodySize = rtmpPkt->strBuf.size();
|
||||
rtmpPkt->chunkId = CHUNK_VIDEO;
|
||||
rtmpPkt->streamId = STREAM_MEDIA;
|
||||
rtmpPkt->timeStamp = 0;
|
||||
rtmpPkt->typeId = MSG_VIDEO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt, false);
|
||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
||||
rtmpPkt->chunk_id = CHUNK_VIDEO;
|
||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||
rtmpPkt->time_stamp = 0;
|
||||
rtmpPkt->type_id = MSG_VIDEO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt);
|
||||
#else
|
||||
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
||||
#endif
|
||||
|
||||
@@ -32,17 +32,17 @@ public:
|
||||
/**
|
||||
* 输入265 Rtmp包
|
||||
* @param rtmp Rtmp包
|
||||
* @param key_pos 此参数忽略之
|
||||
*/
|
||||
bool inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos = true) override;
|
||||
void inputRtmp(const RtmpPacket::Ptr &rtmp) override;
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return CodecH265;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool decodeRtmp(const RtmpPacket::Ptr &Rtmp);
|
||||
void onGetH265(const char *pcData, int iLen, uint32_t dts,uint32_t pts);
|
||||
H265Frame::Ptr obtainFrame();
|
||||
|
||||
protected:
|
||||
H265Frame::Ptr _h265frame;
|
||||
};
|
||||
|
||||
@@ -200,7 +200,7 @@ void H265RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
bFirst = false;
|
||||
}
|
||||
} else {
|
||||
makeH265Rtp(naluType,pcData, iLen, true, true, uiStamp);
|
||||
makeH265Rtp(naluType,pcData, iLen, false, true, uiStamp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,52 +16,13 @@
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
/**
|
||||
* Opus帧
|
||||
*/
|
||||
class OpusFrame : public FrameImp {
|
||||
public:
|
||||
typedef std::shared_ptr<OpusFrame> Ptr;
|
||||
|
||||
OpusFrame(){
|
||||
_codecid = CodecOpus;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 不可缓存的Opus帧
|
||||
*/
|
||||
class OpusFrameNoCacheAble : public FrameFromPtr {
|
||||
public:
|
||||
typedef std::shared_ptr<OpusFrameNoCacheAble> Ptr;
|
||||
|
||||
OpusFrameNoCacheAble(char *ptr,uint32_t size,uint32_t dts, uint32_t pts = 0,int prefix_size = 0){
|
||||
_ptr = ptr;
|
||||
_size = size;
|
||||
_dts = dts;
|
||||
_prefix_size = prefix_size;
|
||||
}
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return CodecOpus;
|
||||
}
|
||||
|
||||
bool keyFrame() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool configFrame() const override{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opus帧音频通道
|
||||
*/
|
||||
class OpusTrack : public AudioTrackImp{
|
||||
public:
|
||||
typedef std::shared_ptr<OpusTrack> Ptr;
|
||||
OpusTrack(int sample_rate, int channels, int sample_bit) : AudioTrackImp(CodecOpus,sample_rate,channels,sample_bit){}
|
||||
OpusTrack() : AudioTrackImp(CodecOpus,48000,2,16){}
|
||||
|
||||
private:
|
||||
//克隆该Track
|
||||
|
||||
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
|
||||
@@ -13,7 +13,7 @@ namespace mediakit {
|
||||
|
||||
HlsPlayer::HlsPlayer(const EventPoller::Ptr &poller){
|
||||
_segment.setOnSegment([this](const char *data, uint64_t len) { onPacket(data, len); });
|
||||
_poller = poller ? poller : EventPollerPool::Instance().getPoller();
|
||||
setPoller(poller ? poller : EventPollerPool::Instance().getPoller());
|
||||
}
|
||||
|
||||
HlsPlayer::~HlsPlayer() {}
|
||||
@@ -63,6 +63,15 @@ void HlsPlayer::playNextTs(bool force){
|
||||
std::shared_ptr<Ticker> ticker(new Ticker);
|
||||
|
||||
_http_ts_player = std::make_shared<HttpTSPlayer>(getPoller(), false);
|
||||
|
||||
_http_ts_player->setOnCreateSocket([weakSelf](const EventPoller::Ptr &poller) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (strongSelf) {
|
||||
return strongSelf->createSocket();
|
||||
}
|
||||
return Socket::createSocket(poller, true);
|
||||
});
|
||||
|
||||
_http_ts_player->setOnDisconnect([weakSelf, ticker, ts_duration](const SockException &err) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
@@ -84,6 +93,7 @@ void HlsPlayer::playNextTs(bool force){
|
||||
}, strongSelf->getPoller()));
|
||||
}
|
||||
});
|
||||
|
||||
_http_ts_player->setOnPacket([weakSelf](const char *data, uint64_t len) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
@@ -94,9 +104,10 @@ void HlsPlayer::playNextTs(bool force){
|
||||
});
|
||||
|
||||
_http_ts_player->setMethod("GET");
|
||||
if(!(*this)[kNetAdapter].empty()) {
|
||||
if (!(*this)[kNetAdapter].empty()) {
|
||||
_http_ts_player->setNetAdapter((*this)[Client::kNetAdapter]);
|
||||
}
|
||||
|
||||
_http_ts_player->sendRequest(_ts_list.front().url, 2 * _ts_list.front().duration);
|
||||
_ts_list.pop_front();
|
||||
}
|
||||
@@ -254,11 +265,15 @@ void HlsPlayerImp::onAllTrackReady() {
|
||||
}
|
||||
|
||||
void HlsPlayerImp::onPlayResult(const SockException &ex) {
|
||||
if(ex){
|
||||
if (ex) {
|
||||
PlayerImp<HlsPlayer, PlayerBase>::onPlayResult(ex);
|
||||
}else{
|
||||
} else {
|
||||
_frame_cache.clear();
|
||||
_stamp[TrackAudio].setRelativeStamp(0);
|
||||
_stamp[TrackVideo].setRelativeStamp(0);
|
||||
_stamp[TrackAudio].syncTo(_stamp[TrackVideo]);
|
||||
_ticker.resetTime();
|
||||
setPlayPosition(0);
|
||||
|
||||
weak_ptr<HlsPlayerImp> weakSelf = dynamic_pointer_cast<HlsPlayerImp>(shared_from_this());
|
||||
//每50毫秒执行一次
|
||||
_timer = std::make_shared<Timer>(0.05, [weakSelf]() {
|
||||
@@ -288,25 +303,47 @@ void HlsPlayerImp::inputFrame(const Frame::Ptr &frame) {
|
||||
//根据时间戳缓存frame
|
||||
_frame_cache.emplace(dts, Frame::getCacheAbleFrame(frame));
|
||||
|
||||
while (!_frame_cache.empty()) {
|
||||
if (_frame_cache.rbegin()->first - _frame_cache.begin()->first > 30 * 1000) {
|
||||
//缓存超过30秒,强制消费掉
|
||||
if (getBufferMS() > 30 * 1000) {
|
||||
//缓存超过30秒,强制消费至15秒(减少延时或内存占用)
|
||||
while (getBufferMS() > 15 * 1000) {
|
||||
MediaSink::inputFrame(_frame_cache.begin()->second);
|
||||
_frame_cache.erase(_frame_cache.begin());
|
||||
continue;
|
||||
}
|
||||
//缓存小于30秒
|
||||
break;
|
||||
//接着播放缓存中最早的帧
|
||||
setPlayPosition(_frame_cache.begin()->first);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t HlsPlayerImp::getPlayPosition(){
|
||||
return _ticker.elapsedTime() + _ticker_offset;
|
||||
}
|
||||
|
||||
int64_t HlsPlayerImp::getBufferMS(){
|
||||
if(_frame_cache.empty()){
|
||||
return 0;
|
||||
}
|
||||
return _frame_cache.rbegin()->first - _frame_cache.begin()->first;
|
||||
}
|
||||
|
||||
void HlsPlayerImp::setPlayPosition(int64_t pos){
|
||||
_ticker.resetTime();
|
||||
_ticker_offset = pos;
|
||||
}
|
||||
|
||||
void HlsPlayerImp::onTick() {
|
||||
auto it = _frame_cache.begin();
|
||||
while (it != _frame_cache.end()) {
|
||||
if (it->first > _ticker.elapsedTime()) {
|
||||
if (it->first > getPlayPosition()) {
|
||||
//这些帧还未到时间播放
|
||||
break;
|
||||
}
|
||||
|
||||
if (getBufferMS() < 3 * 1000) {
|
||||
//缓存小于3秒,那么降低定时器消费速度(让剩余的数据在3秒后消费完毕)
|
||||
//目的是为了防止定时器长时间干等后,数据瞬间消费完毕
|
||||
setPlayPosition(_frame_cache.begin()->first);
|
||||
}
|
||||
|
||||
//消费掉已经到期的帧
|
||||
MediaSink::inputFrame(it->second);
|
||||
it = _frame_cache.erase(it);
|
||||
|
||||
@@ -138,13 +138,19 @@ private:
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
void onShutdown(const SockException &ex) override;
|
||||
void onTick();
|
||||
|
||||
int64_t getPlayPosition();
|
||||
void setPlayPosition(int64_t pos);
|
||||
int64_t getBufferMS();
|
||||
|
||||
private:
|
||||
TSSegment::onSegment _on_ts;
|
||||
DecoderImp::Ptr _decoder;
|
||||
multimap<int64_t, Frame::Ptr> _frame_cache;
|
||||
Timer::Ptr _timer;
|
||||
int64_t _ticker_offset = 0;
|
||||
Ticker _ticker;
|
||||
Stamp _stamp[2];
|
||||
Timer::Ptr _timer;
|
||||
DecoderImp::Ptr _decoder;
|
||||
TSSegment::onSegment _on_ts;
|
||||
multimap<int64_t, Frame::Ptr> _frame_cache;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
@@ -135,7 +135,7 @@ Buffer::Ptr HttpFileBody::readData(uint32_t size) {
|
||||
//读到数据了
|
||||
ret->setSize(iRead);
|
||||
_offset += iRead;
|
||||
return std::move(ret);
|
||||
return ret;
|
||||
}
|
||||
//读取文件异常,文件真实长度小于声明长度
|
||||
_offset = _max_size;
|
||||
@@ -146,7 +146,7 @@ Buffer::Ptr HttpFileBody::readData(uint32_t size) {
|
||||
//mmap模式
|
||||
auto ret = std::make_shared<BufferMmap>(_map_addr,_offset,size);
|
||||
_offset += size;
|
||||
return std::move(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -100,7 +100,7 @@ void HttpClient::onConnect(const SockException &ex) {
|
||||
}
|
||||
|
||||
//先假设http客户端只会接收一点点数据(只接受http头,节省内存)
|
||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(1 * 1024));
|
||||
getSock()->setReadBuffer(std::make_shared<BufferRaw>(1 * 1024));
|
||||
|
||||
_totalBodySize = 0;
|
||||
_recvedBodySize = 0;
|
||||
@@ -157,7 +157,7 @@ int64_t HttpClient::onRecvHeader(const char *data, uint64_t len) {
|
||||
|
||||
if(_parser["Transfer-Encoding"] == "chunked"){
|
||||
//我们认为这种情况下后面应该有大量的数据过来,加大接收缓存提高性能
|
||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
||||
getSock()->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
||||
|
||||
//如果Transfer-Encoding字段等于chunked,则认为后续的content是不限制长度的
|
||||
_totalBodySize = -1;
|
||||
@@ -185,9 +185,9 @@ int64_t HttpClient::onRecvHeader(const char *data, uint64_t len) {
|
||||
_recvedBodySize = 0;
|
||||
if(_totalBodySize > 0){
|
||||
//根据_totalBodySize设置接收缓存大小
|
||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(MIN(_totalBodySize + 1,256 * 1024)));
|
||||
getSock()->setReadBuffer(std::make_shared<BufferRaw>(MIN(_totalBodySize + 1,256 * 1024)));
|
||||
}else{
|
||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
||||
getSock()->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
||||
@@ -27,7 +27,7 @@ static int kHlsCookieSecond = 60;
|
||||
static const string kCookieName = "ZL_COOKIE";
|
||||
static const string kHlsSuffix = "/hls.m3u8";
|
||||
|
||||
class HttpCookieAttachment{
|
||||
class HttpCookieAttachment {
|
||||
public:
|
||||
HttpCookieAttachment() {};
|
||||
~HttpCookieAttachment() {};
|
||||
@@ -160,7 +160,7 @@ const string &HttpFileManager::getContentType(const char *name) {
|
||||
dot = strrchr(name, '.');
|
||||
static StrCaseMap mapType;
|
||||
static onceToken token([&]() {
|
||||
for (unsigned int i = 0; i < sizeof (s_mime_src) / sizeof (s_mime_src[0]); ++i) {
|
||||
for (unsigned int i = 0; i < sizeof(s_mime_src) / sizeof(s_mime_src[0]); ++i) {
|
||||
mapType.emplace(s_mime_src[i][0], s_mime_src[i][1]);
|
||||
}
|
||||
});
|
||||
@@ -183,8 +183,8 @@ static string searchIndexFile(const string &dir){
|
||||
}
|
||||
set<string> setFile;
|
||||
while ((pDirent = readdir(pDir)) != NULL) {
|
||||
static set<const char *,StrCaseCompare> indexSet = {"index.html","index.htm","index"};
|
||||
if(indexSet.find(pDirent->d_name) != indexSet.end()){
|
||||
static set<const char *, StrCaseCompare> indexSet = {"index.html", "index.htm", "index"};
|
||||
if (indexSet.find(pDirent->d_name) != indexSet.end()) {
|
||||
string ret = pDirent->d_name;
|
||||
closedir(pDir);
|
||||
return ret;
|
||||
@@ -196,16 +196,16 @@ static string searchIndexFile(const string &dir){
|
||||
|
||||
static bool makeFolderMenu(const string &httpPath, const string &strFullPath, string &strRet) {
|
||||
GET_CONFIG(bool, dirMenu, Http::kDirMenu);
|
||||
if(!dirMenu){
|
||||
if (!dirMenu) {
|
||||
//不允许浏览文件夹
|
||||
return false;
|
||||
}
|
||||
string strPathPrefix(strFullPath);
|
||||
string last_dir_name;
|
||||
if(strPathPrefix.back() == '/'){
|
||||
if (strPathPrefix.back() == '/') {
|
||||
strPathPrefix.pop_back();
|
||||
}else{
|
||||
last_dir_name = split(strPathPrefix,"/").back();
|
||||
} else {
|
||||
last_dir_name = split(strPathPrefix, "/").back();
|
||||
}
|
||||
|
||||
if (!File::is_dir(strPathPrefix.data())) {
|
||||
@@ -249,24 +249,24 @@ static bool makeFolderMenu(const string &httpPath, const string &strFullPath, st
|
||||
if (File::is_special_dir(pDirent->d_name)) {
|
||||
continue;
|
||||
}
|
||||
if(pDirent->d_name[0] == '.'){
|
||||
if (pDirent->d_name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
setFile.emplace(pDirent->d_name);
|
||||
}
|
||||
int i = 0;
|
||||
for(auto &strFile :setFile ){
|
||||
for (auto &strFile :setFile) {
|
||||
string strAbsolutePath = strPathPrefix + "/" + strFile;
|
||||
bool isDir = File::is_dir(strAbsolutePath.data());
|
||||
ss << "<li><span>" << i++ << "</span>\t";
|
||||
ss << "<a href=\"";
|
||||
if(!last_dir_name.empty()){
|
||||
if (!last_dir_name.empty()) {
|
||||
ss << last_dir_name << "/" << strFile;
|
||||
}else{
|
||||
} else {
|
||||
ss << strFile;
|
||||
}
|
||||
|
||||
if(isDir){
|
||||
if (isDir) {
|
||||
ss << "/";
|
||||
}
|
||||
ss << "\">";
|
||||
@@ -307,11 +307,16 @@ static bool end_of(const string &str, const string &substr){
|
||||
//拦截hls的播放请求
|
||||
static bool emitHlsPlayed(const Parser &parser, const MediaInfo &mediaInfo, const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender){
|
||||
//访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件
|
||||
Broadcast::AuthInvoker mediaAuthInvoker = [invoker](const string &err){
|
||||
Broadcast::AuthInvoker auth_invoker = [invoker](const string &err) {
|
||||
//cookie有效期为kHlsCookieSecond
|
||||
invoker(err,"",kHlsCookieSecond);
|
||||
invoker(err, "", kHlsCookieSecond);
|
||||
};
|
||||
return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,mediaInfo,mediaAuthInvoker,static_cast<SockInfo &>(sender));
|
||||
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, mediaInfo, auth_invoker, static_cast<SockInfo &>(sender));
|
||||
if (!flag) {
|
||||
//未开启鉴权,那么允许播放
|
||||
auth_invoker("");
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
class SockInfoImp : public SockInfo{
|
||||
@@ -320,23 +325,23 @@ public:
|
||||
SockInfoImp() = default;
|
||||
~SockInfoImp() override = default;
|
||||
|
||||
string get_local_ip() override{
|
||||
string get_local_ip() override {
|
||||
return _local_ip;
|
||||
}
|
||||
|
||||
uint16_t get_local_port() override{
|
||||
uint16_t get_local_port() override {
|
||||
return _local_port;
|
||||
}
|
||||
|
||||
string get_peer_ip() override{
|
||||
string get_peer_ip() override {
|
||||
return _peer_ip;
|
||||
}
|
||||
|
||||
uint16_t get_peer_port() override{
|
||||
uint16_t get_peer_port() override {
|
||||
return _peer_port;
|
||||
}
|
||||
|
||||
string getIdentifier() const override{
|
||||
string getIdentifier() const override {
|
||||
return _identifier;
|
||||
}
|
||||
|
||||
@@ -379,7 +384,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
||||
//上次cookie是限定本目录
|
||||
if (attachment._err_msg.empty()) {
|
||||
//上次鉴权成功
|
||||
if(attachment._is_hls){
|
||||
if (attachment._is_hls) {
|
||||
//如果播放的是hls,那么刷新hls的cookie(获取ts文件也会刷新)
|
||||
cookie->updateTime();
|
||||
cookie_from_header = false;
|
||||
@@ -429,7 +434,7 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
||||
attachment._err_msg = errMsg;
|
||||
//记录访问的是否为hls
|
||||
attachment._is_hls = is_hls;
|
||||
if(is_hls){
|
||||
if (is_hls) {
|
||||
//hls相关信息
|
||||
attachment._hls_data = std::make_shared<HlsCookieData>(mediaInfo, info);
|
||||
//hls未查找MediaSource
|
||||
@@ -437,13 +442,14 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
||||
}
|
||||
(*cookie)[kCookieName].set<HttpCookieAttachment>(std::move(attachment));
|
||||
callback(errMsg, cookie);
|
||||
}else{
|
||||
} else {
|
||||
callback(errMsg, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
if (is_hls && emitHlsPlayed(parser, mediaInfo, accessPathInvoker, sender)) {
|
||||
if (is_hls) {
|
||||
//是hls的播放鉴权,拦截之
|
||||
emitHlsPlayed(parser, mediaInfo, accessPathInvoker, sender);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -459,15 +465,15 @@ static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaI
|
||||
* 发送404 Not Found
|
||||
*/
|
||||
static void sendNotFound(const HttpFileManager::invoker &cb) {
|
||||
GET_CONFIG(string,notFound,Http::kNotFound);
|
||||
cb("404 Not Found","text/html",StrCaseMap(),std::make_shared<HttpStringBody>(notFound));
|
||||
GET_CONFIG(string, notFound, Http::kNotFound);
|
||||
cb("404 Not Found", "text/html", StrCaseMap(), std::make_shared<HttpStringBody>(notFound));
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接文件路径
|
||||
*/
|
||||
static string pathCat(const string &a, const string &b){
|
||||
if(a.back() == '/'){
|
||||
if (a.back() == '/') {
|
||||
return a + b;
|
||||
}
|
||||
return a + '/' + b;
|
||||
@@ -490,7 +496,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
|
||||
return;
|
||||
}
|
||||
|
||||
if(is_hls){
|
||||
if (is_hls) {
|
||||
//hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
|
||||
const_cast<string &>(mediaInfo._schema) = HLS_SCHEMA;
|
||||
replace(const_cast<string &>(mediaInfo._streamid), kHlsSuffix, "");
|
||||
@@ -500,7 +506,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
|
||||
//判断是否有权限访问该文件
|
||||
canAccessPath(sender, parser, mediaInfo, false, [cb, strFile, parser, is_hls, mediaInfo, weakSession , file_exist](const string &errMsg, const HttpServerCookie::Ptr &cookie) {
|
||||
auto strongSession = weakSession.lock();
|
||||
if(!strongSession){
|
||||
if (!strongSession) {
|
||||
//http客户端已经断开,不需要回复
|
||||
return;
|
||||
}
|
||||
@@ -508,6 +514,7 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
|
||||
//文件鉴权失败
|
||||
StrCaseMap headerOut;
|
||||
if (cookie) {
|
||||
auto lck = cookie->getLock();
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
|
||||
}
|
||||
cb("401 Unauthorized", "text/html", headerOut, std::make_shared<HttpStringBody>(errMsg));
|
||||
@@ -517,11 +524,12 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
|
||||
auto response_file = [file_exist](const HttpServerCookie::Ptr &cookie, const HttpFileManager::invoker &cb, const string &strFile, const Parser &parser) {
|
||||
StrCaseMap httpHeader;
|
||||
if (cookie) {
|
||||
auto lck = cookie->getLock();
|
||||
httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get<HttpCookieAttachment>()._path);
|
||||
}
|
||||
HttpSession::HttpResponseInvoker invoker = [&](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) {
|
||||
if (cookie && file_exist) {
|
||||
cookie->getLock();
|
||||
auto lck = cookie->getLock();
|
||||
auto is_hls = (*cookie)[kCookieName].get<HttpCookieAttachment>()._is_hls;
|
||||
if (is_hls) {
|
||||
(*cookie)[kCookieName].get<HttpCookieAttachment>()._hls_data->addByteUsage(body->remainSize());
|
||||
@@ -535,26 +543,47 @@ static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo
|
||||
if (!is_hls) {
|
||||
//不是hls,直接回复文件或404
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
} else {
|
||||
//是hls直播,判断是否存在
|
||||
bool have_find_media_src = false;
|
||||
if(cookie){
|
||||
have_find_media_src = (*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source;
|
||||
if(!have_find_media_src){
|
||||
(*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//是hls直播,判断HLS直播流是否已经注册
|
||||
bool have_find_media_src = false;
|
||||
if (cookie) {
|
||||
auto lck = cookie->getLock();
|
||||
have_find_media_src = (*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source;
|
||||
if (!have_find_media_src) {
|
||||
(*cookie)[kCookieName].get<HttpCookieAttachment>()._have_find_media_source = true;
|
||||
}
|
||||
if(have_find_media_src){
|
||||
//之前该cookie已经通过MediaSource::findAsync查找过了,所以现在只以文件系统查找结果为准
|
||||
}
|
||||
if (have_find_media_src) {
|
||||
//之前该cookie已经通过MediaSource::findAsync查找过了,所以现在只以文件系统查找结果为准
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
return;
|
||||
}
|
||||
//hls文件不存在,我们等待其生成并延后回复
|
||||
MediaSource::findAsync(mediaInfo, strongSession, [response_file, cookie, cb, strFile, parser](const MediaSource::Ptr &src) {
|
||||
if (cookie) {
|
||||
auto lck = cookie->getLock();
|
||||
//尝试添加HlsMediaSource的观看人数(HLS是按需生成的,这样可以触发HLS文件的生成)
|
||||
(*cookie)[kCookieName].get<HttpCookieAttachment>()._hls_data->addByteUsage(0);
|
||||
}
|
||||
if (src && File::is_file(strFile.data())) {
|
||||
//流和m3u8文件都存在,那么直接返回文件
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
return;
|
||||
}
|
||||
//hls文件不存在,我们等待其生成并延后回复
|
||||
MediaSource::findAsync(mediaInfo, strongSession, [response_file, cookie, cb, strFile, parser](const MediaSource::Ptr &src) {
|
||||
//hls已经生成或者超时后仍未生成,那么不管怎么样都返回客户端
|
||||
auto hls = dynamic_pointer_cast<HlsMediaSource>(src);
|
||||
if (!hls) {
|
||||
//流不存在,那么直接返回文件(相当于纯粹的HLS文件服务器,但是会挂起播放器15秒左右(用于等待HLS流的注册))
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
return;
|
||||
}
|
||||
|
||||
//流存在,但是m3u8文件不存在,那么等待生成m3u8文件(HLS源注册后,并不会立即生成HLS文件,有人观看才会按需生成HLS文件)
|
||||
hls->waitForFile([response_file, cookie, cb, strFile, parser]() {
|
||||
response_file(cookie, cb, strFile, parser);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -563,7 +592,7 @@ static string getFilePath(const Parser &parser,const MediaInfo &mediaInfo, TcpSe
|
||||
GET_CONFIG(string, rootPath, Http::kRootPath);
|
||||
auto ret = File::absolutePath(enableVhost ? mediaInfo._vhost + parser.Url() : parser.Url(), rootPath);
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, static_cast<SockInfo &>(sender));
|
||||
return std::move(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -615,13 +644,13 @@ void HttpFileManager::onAccessPath(TcpSession &sender, Parser &parser, const Htt
|
||||
////////////////////////////////////HttpResponseInvokerImp//////////////////////////////////////
|
||||
|
||||
void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const{
|
||||
if(_lambad){
|
||||
_lambad(codeOut,headerOut,body);
|
||||
if (_lambad) {
|
||||
_lambad(codeOut, headerOut, body);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const string &body) const{
|
||||
this->operator()(codeOut,headerOut,std::make_shared<HttpStringBody>(body));
|
||||
this->operator()(codeOut, headerOut, std::make_shared<HttpStringBody>(body));
|
||||
}
|
||||
|
||||
HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda0 &lambda){
|
||||
@@ -629,23 +658,23 @@ HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::Htt
|
||||
}
|
||||
|
||||
HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda1 &lambda){
|
||||
if(!lambda){
|
||||
if (!lambda) {
|
||||
_lambad = nullptr;
|
||||
return;
|
||||
}
|
||||
_lambad = [lambda](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body){
|
||||
_lambad = [lambda](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) {
|
||||
string str;
|
||||
if(body && body->remainSize()){
|
||||
if (body && body->remainSize()) {
|
||||
str = body->readData(body->remainSize())->toString();
|
||||
}
|
||||
lambda(codeOut,headerOut,str);
|
||||
lambda(codeOut, headerOut, str);
|
||||
};
|
||||
}
|
||||
|
||||
void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
||||
const StrCaseMap &responseHeader,
|
||||
const string &filePath) const {
|
||||
StrCaseMap &httpHeader = const_cast<StrCaseMap&>(responseHeader);
|
||||
StrCaseMap &httpHeader = const_cast<StrCaseMap &>(responseHeader);
|
||||
std::shared_ptr<FILE> fp(fopen(filePath.data(), "rb"), [](FILE *fp) {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
@@ -654,8 +683,8 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
||||
|
||||
if (!fp) {
|
||||
//打开文件失败
|
||||
GET_CONFIG(string,notFound,Http::kNotFound);
|
||||
GET_CONFIG(string,charSet,Http::kCharSet);
|
||||
GET_CONFIG(string, notFound, Http::kNotFound);
|
||||
GET_CONFIG(string, charSet, Http::kCharSet);
|
||||
|
||||
auto strContentType = StrPrinter << "text/html; charset=" << charSet << endl;
|
||||
httpHeader["Content-Type"] = strContentType;
|
||||
@@ -665,14 +694,14 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
||||
|
||||
auto &strRange = const_cast<StrCaseMap &>(requestHeader)["Range"];
|
||||
int64_t iRangeStart = 0;
|
||||
int64_t iRangeEnd = 0 ;
|
||||
int64_t iRangeEnd = 0;
|
||||
int64_t fileSize = HttpMultiFormBody::fileSize(fp.get());
|
||||
|
||||
const char *pcHttpResult = NULL;
|
||||
if (strRange.size() == 0) {
|
||||
//全部下载
|
||||
pcHttpResult = "200 OK";
|
||||
iRangeEnd = fileSize - 1;
|
||||
iRangeEnd = fileSize - 1;
|
||||
} else {
|
||||
//分节下载
|
||||
pcHttpResult = "206 Partial Content";
|
||||
|
||||
@@ -8,15 +8,9 @@
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#include <dirent.h>
|
||||
#endif //!defined(_WIN32)
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
|
||||
#include "Common/config.h"
|
||||
#include "strCoding.h"
|
||||
#include "HttpSession.h"
|
||||
@@ -96,10 +90,10 @@ void HttpSession::onRecv(const Buffer::Ptr &pBuf) {
|
||||
}
|
||||
|
||||
void HttpSession::onError(const SockException& err) {
|
||||
if(_is_flv_stream){
|
||||
if(_is_live_stream){
|
||||
uint64_t duration = _ticker.createdTime()/1000;
|
||||
//flv播放器
|
||||
WarnP(this) << "FLV播放器("
|
||||
//flv/ts播放器
|
||||
WarnP(this) << "FLV/TS/FMP4播放器("
|
||||
<< _mediaInfo._vhost << "/"
|
||||
<< _mediaInfo._app << "/"
|
||||
<< _mediaInfo._streamid
|
||||
@@ -107,8 +101,8 @@ void HttpSession::onError(const SockException& err) {
|
||||
<< ",耗时(s):" << duration;
|
||||
|
||||
GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold);
|
||||
if(_ui64TotalBytes > iFlowThreshold * 1024){
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _ui64TotalBytes, duration , true, static_cast<SockInfo &>(*this));
|
||||
if(_total_bytes_usage > iFlowThreshold * 1024){
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration , true, static_cast<SockInfo &>(*this));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -132,7 +126,7 @@ void HttpSession::onManager() {
|
||||
|
||||
bool HttpSession::checkWebSocket(){
|
||||
auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"];
|
||||
if(Sec_WebSocket_Key.empty()){
|
||||
if (Sec_WebSocket_Key.empty()) {
|
||||
return false;
|
||||
}
|
||||
auto Sec_WebSocket_Accept = encodeBase64(SHA1::encode_bin(Sec_WebSocket_Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
|
||||
@@ -141,99 +135,95 @@ bool HttpSession::checkWebSocket(){
|
||||
headerOut["Upgrade"] = "websocket";
|
||||
headerOut["Connection"] = "Upgrade";
|
||||
headerOut["Sec-WebSocket-Accept"] = Sec_WebSocket_Accept;
|
||||
if(!_parser["Sec-WebSocket-Protocol"].empty()){
|
||||
if (!_parser["Sec-WebSocket-Protocol"].empty()) {
|
||||
headerOut["Sec-WebSocket-Protocol"] = _parser["Sec-WebSocket-Protocol"];
|
||||
}
|
||||
|
||||
auto res_cb = [this,headerOut](){
|
||||
_flv_over_websocket = true;
|
||||
sendResponse("101 Switching Protocols",false,nullptr,headerOut,nullptr, true);
|
||||
auto res_cb = [this, headerOut]() {
|
||||
_live_over_websocket = true;
|
||||
sendResponse("101 Switching Protocols", false, nullptr, headerOut, nullptr, true);
|
||||
};
|
||||
|
||||
//判断是否为websocket-flv
|
||||
if(checkLiveFlvStream(res_cb)){
|
||||
if (checkLiveStreamFlv(res_cb)) {
|
||||
//这里是websocket-flv直播请求
|
||||
return true;
|
||||
}
|
||||
|
||||
//如果checkLiveFlvStream返回false,则代表不是websocket-flv,而是普通的websocket连接
|
||||
if(!onWebSocketConnect(_parser)){
|
||||
sendResponse("501 Not Implemented",true, nullptr, headerOut);
|
||||
//判断是否为websocket-ts
|
||||
if (checkLiveStreamTS(res_cb)) {
|
||||
//这里是websocket-ts直播请求
|
||||
return true;
|
||||
}
|
||||
sendResponse("101 Switching Protocols",false, nullptr,headerOut);
|
||||
|
||||
//判断是否为websocket-fmp4
|
||||
if (checkLiveStreamFMP4(res_cb)) {
|
||||
//这里是websocket-fmp4直播请求
|
||||
return true;
|
||||
}
|
||||
|
||||
//这是普通的websocket连接
|
||||
if (!onWebSocketConnect(_parser)) {
|
||||
sendResponse("501 Not Implemented", true, nullptr, headerOut);
|
||||
return true;
|
||||
}
|
||||
sendResponse("101 Switching Protocols", false, nullptr, headerOut, nullptr, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
//http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2
|
||||
//如果url(除去?以及后面的参数)后缀是.flv,那么表明该url是一个http-flv直播。
|
||||
bool HttpSession::checkLiveFlvStream(const function<void()> &cb){
|
||||
auto pos = strrchr(_parser.Url().data(),'.');
|
||||
if(!pos){
|
||||
//未找到".flv"后缀
|
||||
return false;
|
||||
}
|
||||
if(strcasecmp(pos,".flv") != 0){
|
||||
//未找到".flv"后缀
|
||||
bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix, const function<void(const MediaSource::Ptr &src)> &cb){
|
||||
auto pos = strcasestr(_parser.Url().data(), url_suffix.data());
|
||||
if (!pos || pos + url_suffix.size() != 1 + &_parser.Url().back()) {
|
||||
//未找到后缀
|
||||
return false;
|
||||
}
|
||||
|
||||
//这是个.flv的流
|
||||
_mediaInfo.parse(string(RTMP_SCHEMA) + "://" + _parser["Host"] + _parser.FullUrl());
|
||||
if(_mediaInfo._app.empty() || _mediaInfo._streamid.size() < 5){
|
||||
//这是个符合后缀的直播的流
|
||||
_mediaInfo.parse(schema + "://" + _parser["Host"] + _parser.FullUrl());
|
||||
if (_mediaInfo._app.empty() || _mediaInfo._streamid.size() < url_suffix.size() + 1) {
|
||||
//url不合法
|
||||
return false;
|
||||
}
|
||||
_mediaInfo._streamid.erase(_mediaInfo._streamid.size() - 4);//去除.flv后缀
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||
|
||||
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||
//去除后缀
|
||||
bool close_flag = !strcasecmp(_parser["Connection"].data(), "close");
|
||||
//流id去除后缀
|
||||
_mediaInfo._streamid.erase(_mediaInfo._streamid.size() - url_suffix.size());
|
||||
weak_ptr<HttpSession> weak_self = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||
|
||||
//鉴权结果回调
|
||||
auto onRes = [cb, weakSelf, bClose](const string &err){
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
auto onRes = [cb, weak_self, close_flag](const string &err) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
//本对象已经销毁
|
||||
return;
|
||||
}
|
||||
|
||||
if(!err.empty()){
|
||||
if (!err.empty()) {
|
||||
//播放鉴权失败
|
||||
strongSelf->sendResponse("401 Unauthorized", bClose, nullptr, KeyValue(), std::make_shared<HttpStringBody>(err));
|
||||
strong_self->sendResponse("401 Unauthorized", close_flag, nullptr, KeyValue(), std::make_shared<HttpStringBody>(err));
|
||||
return;
|
||||
}
|
||||
|
||||
//异步查找rtmp流
|
||||
MediaSource::findAsync(strongSelf->_mediaInfo, strongSelf, [weakSelf, bClose, cb](const MediaSource::Ptr &src) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
//异步查找直播流
|
||||
MediaSource::findAsync(strong_self->_mediaInfo, strong_self, [weak_self, close_flag, cb](const MediaSource::Ptr &src) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
//本对象已经销毁
|
||||
return;
|
||||
}
|
||||
auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
|
||||
if (!rtmp_src) {
|
||||
if (!src) {
|
||||
//未找到该流
|
||||
strongSelf->sendNotFound(bClose);
|
||||
strong_self->sendNotFound(close_flag);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cb) {
|
||||
//找到rtmp源,发送http头,负载后续发送
|
||||
strongSelf->sendResponse("200 OK", false, "video/x-flv", KeyValue(), nullptr, true);
|
||||
} else {
|
||||
//自定义发送http头
|
||||
cb();
|
||||
}
|
||||
|
||||
//http-flv直播牺牲延时提升发送性能
|
||||
strongSelf->setSocketFlags();
|
||||
strongSelf->start(strongSelf->getPoller(), rtmp_src);
|
||||
strongSelf->_is_flv_stream = true;
|
||||
strong_self->_is_live_stream = true;
|
||||
//触发回调
|
||||
cb(src);
|
||||
});
|
||||
};
|
||||
|
||||
Broadcast::AuthInvoker invoker = [weakSelf, onRes](const string &err) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
Broadcast::AuthInvoker invoker = [weak_self, onRes](const string &err) {
|
||||
auto strongSelf = weak_self.lock();
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
@@ -250,34 +240,142 @@ bool HttpSession::checkLiveFlvStream(const function<void()> &cb){
|
||||
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
|
||||
bool HttpSession::checkLiveStreamTS(const function<void()> &cb){
|
||||
return checkLiveStream(TS_SCHEMA, ".live.ts", [this, cb](const MediaSource::Ptr &src) {
|
||||
auto ts_src = dynamic_pointer_cast<TSMediaSource>(src);
|
||||
assert(ts_src);
|
||||
if (!cb) {
|
||||
//找到源,发送http头,负载后续发送
|
||||
sendResponse("200 OK", false, HttpFileManager::getContentType(".ts").data(), KeyValue(), nullptr, true);
|
||||
} else {
|
||||
//自定义发送http头
|
||||
cb();
|
||||
}
|
||||
|
||||
//直播牺牲延时提升发送性能
|
||||
setSocketFlags();
|
||||
weak_ptr<HttpSession> weak_self = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||
_ts_reader = ts_src->getRing()->attach(getPoller());
|
||||
_ts_reader->setDetachCB([weak_self](){
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
//本对象已经销毁
|
||||
return;
|
||||
}
|
||||
strong_self->shutdown(SockException(Err_shutdown,"ts ring buffer detached"));
|
||||
});
|
||||
_ts_reader->setReadCB([weak_self](const TSMediaSource::RingDataType &ts_list) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
//本对象已经销毁
|
||||
return;
|
||||
}
|
||||
int i = 0;
|
||||
int size = ts_list->size();
|
||||
ts_list->for_each([&](const TSPacket::Ptr &ts) {
|
||||
strong_self->onWrite(ts, ++i == size);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2
|
||||
bool HttpSession::checkLiveStreamFlv(const function<void()> &cb){
|
||||
return checkLiveStream(RTMP_SCHEMA, ".flv", [this, cb](const MediaSource::Ptr &src) {
|
||||
auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
|
||||
assert(rtmp_src);
|
||||
if (!cb) {
|
||||
//找到源,发送http头,负载后续发送
|
||||
sendResponse("200 OK", false, HttpFileManager::getContentType(".flv").data(), KeyValue(), nullptr, true);
|
||||
} else {
|
||||
//自定义发送http头
|
||||
cb();
|
||||
}
|
||||
//直播牺牲延时提升发送性能
|
||||
setSocketFlags();
|
||||
start(getPoller(), rtmp_src);
|
||||
});
|
||||
}
|
||||
|
||||
void HttpSession::Handle_Req_GET(int64_t &content_len) {
|
||||
Handle_Req_GET_l(content_len, true);
|
||||
}
|
||||
|
||||
void HttpSession::Handle_Req_GET_l(int64_t &content_len, bool sendBody) {
|
||||
//先看看是否为WebSocket请求
|
||||
if(checkWebSocket()){
|
||||
if (checkWebSocket()) {
|
||||
content_len = -1;
|
||||
_contentCallBack = [this](const char *data,uint64_t len){
|
||||
WebSocketSplitter::decode((uint8_t *)data,len);
|
||||
_contentCallBack = [this](const char *data, uint64_t len) {
|
||||
WebSocketSplitter::decode((uint8_t *) data, len);
|
||||
//_contentCallBack是可持续的,后面还要处理后续数据
|
||||
return true;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if(emitHttpEvent(false)){
|
||||
if (emitHttpEvent(false)) {
|
||||
//拦截http api事件
|
||||
return;
|
||||
}
|
||||
|
||||
if(checkLiveFlvStream()){
|
||||
if (checkLiveStreamFlv()) {
|
||||
//拦截http-flv播放器
|
||||
return;
|
||||
}
|
||||
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||
if (checkLiveStreamTS()) {
|
||||
//拦截http-ts播放器
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkLiveStreamFMP4()) {
|
||||
//拦截http-fmp4播放器
|
||||
return;
|
||||
}
|
||||
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||
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,
|
||||
const StrCaseMap &responseHeader, const HttpBody::Ptr &body) {
|
||||
@@ -389,7 +487,7 @@ void HttpSession::sendResponse(const char *pcStatus,
|
||||
const char *pcContentType,
|
||||
const HttpSession::KeyValue &header,
|
||||
const HttpBody::Ptr &body,
|
||||
bool is_http_flv ){
|
||||
bool no_content_length ){
|
||||
GET_CONFIG(string,charSet,Http::kCharSet);
|
||||
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
|
||||
|
||||
@@ -400,7 +498,7 @@ void HttpSession::sendResponse(const char *pcStatus,
|
||||
size = body->remainSize();
|
||||
}
|
||||
|
||||
if(is_http_flv){
|
||||
if(no_content_length){
|
||||
//http-flv直播是Keep-Alive类型
|
||||
bClose = false;
|
||||
}else if(size >= INT64_MAX){
|
||||
@@ -425,7 +523,7 @@ void HttpSession::sendResponse(const char *pcStatus,
|
||||
headerOut.emplace(kAccessControlAllowCredentials, "true");
|
||||
}
|
||||
|
||||
if(!is_http_flv && size >= 0 && size < INT64_MAX){
|
||||
if(!no_content_length && size >= 0 && size < INT64_MAX){
|
||||
//文件长度为固定值,且不是http-flv强制设置Content-Length
|
||||
headerOut[kContentLength] = to_string(size);
|
||||
}
|
||||
@@ -475,7 +573,7 @@ void HttpSession::sendResponse(const char *pcStatus,
|
||||
|
||||
//发送http body
|
||||
AsyncSenderData::Ptr data = std::make_shared<AsyncSenderData>(shared_from_this(),body,bClose);
|
||||
_sock->setOnFlush([data](){
|
||||
getSock()->setOnFlush([data](){
|
||||
return AsyncSender::onSocketFlushed(data);
|
||||
});
|
||||
AsyncSender::onSocketFlushed(data);
|
||||
@@ -542,10 +640,10 @@ void HttpSession::Handle_Req_POST(int64_t &content_len) {
|
||||
|
||||
//根据Content-Length设置接收缓存大小
|
||||
if(totalContentLen > 0){
|
||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(MIN(totalContentLen + 1,256 * 1024)));
|
||||
getSock()->setReadBuffer(std::make_shared<BufferRaw>(MIN(totalContentLen + 1,256 * 1024)));
|
||||
}else{
|
||||
//不定长度的Content-Length
|
||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
||||
getSock()->setReadBuffer(std::make_shared<BufferRaw>(256 * 1024));
|
||||
}
|
||||
|
||||
if(totalContentLen > 0 && totalContentLen < maxReqSize ){
|
||||
@@ -609,7 +707,7 @@ void HttpSession::setSocketFlags(){
|
||||
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
||||
if(mergeWriteMS > 0) {
|
||||
//推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高
|
||||
SockUtil::setNoDelay(_sock->rawFD(), false);
|
||||
SockUtil::setNoDelay(getSock()->rawFD(), false);
|
||||
//播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能
|
||||
setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
|
||||
}
|
||||
@@ -622,29 +720,44 @@ void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) {
|
||||
}
|
||||
|
||||
_ticker.resetTime();
|
||||
if(!_flv_over_websocket){
|
||||
_ui64TotalBytes += buffer->size();
|
||||
if (!_live_over_websocket) {
|
||||
_total_bytes_usage += buffer->size();
|
||||
send(buffer);
|
||||
}else{
|
||||
} else {
|
||||
WebSocketHeader header;
|
||||
header._fin = true;
|
||||
header._reserved = 0;
|
||||
header._opcode = WebSocketHeader::BINARY;
|
||||
header._mask_flag = false;
|
||||
WebSocketSplitter::encode(header,buffer);
|
||||
WebSocketSplitter::encode(header, buffer);
|
||||
}
|
||||
|
||||
if(flush){
|
||||
if (flush) {
|
||||
//本次刷新缓存后,下次不用刷新缓存
|
||||
HttpSession::setSendFlushFlag(false);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpSession::onWebSocketEncodeData(const Buffer::Ptr &buffer){
|
||||
_ui64TotalBytes += buffer->size();
|
||||
_total_bytes_usage += buffer->size();
|
||||
send(buffer);
|
||||
}
|
||||
|
||||
void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in){
|
||||
WebSocketHeader& header = const_cast<WebSocketHeader&>(header_in);
|
||||
header._mask_flag = false;
|
||||
|
||||
switch (header._opcode) {
|
||||
case WebSocketHeader::CLOSE: {
|
||||
encode(header, nullptr);
|
||||
shutdown(SockException(Err_shutdown, "recv close request from client"));
|
||||
break;
|
||||
}
|
||||
|
||||
default : break;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpSession::onDetach() {
|
||||
shutdown(SockException(Err_shutdown,"rtmp ring buffer detached"));
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#include "WebSocketSplitter.h"
|
||||
#include "HttpCookieManager.h"
|
||||
#include "HttpFileManager.h"
|
||||
#include "TS/TSMediaSource.h"
|
||||
#include "FMP4/FMP4MediaSource.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
@@ -47,6 +49,7 @@ public:
|
||||
void onError(const SockException &err) override;
|
||||
void onManager() override;
|
||||
static string urlDecode(const string &str);
|
||||
|
||||
protected:
|
||||
//FlvMuxer override
|
||||
void onWrite(const Buffer::Ptr &data, bool flush) override ;
|
||||
@@ -90,35 +93,49 @@ protected:
|
||||
* @param buffer websocket协议数据
|
||||
*/
|
||||
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override;
|
||||
|
||||
/**
|
||||
* 接收到完整的一个webSocket数据包后回调
|
||||
* @param header 数据包包头
|
||||
*/
|
||||
void onWebSocketDecodeComplete(const WebSocketHeader &header_in) override;
|
||||
|
||||
private:
|
||||
void Handle_Req_GET(int64_t &content_len);
|
||||
void Handle_Req_GET_l(int64_t &content_len, bool sendBody);
|
||||
void Handle_Req_POST(int64_t &content_len);
|
||||
void Handle_Req_HEAD(int64_t &content_len);
|
||||
|
||||
bool checkLiveFlvStream(const function<void()> &cb = nullptr);
|
||||
bool checkLiveStream(const string &schema, const string &url_suffix, const function<void(const MediaSource::Ptr &src)> &cb);
|
||||
|
||||
bool checkLiveStreamFlv(const function<void()> &cb = nullptr);
|
||||
bool checkLiveStreamTS(const function<void()> &cb = nullptr);
|
||||
bool checkLiveStreamFMP4(const function<void()> &fmp4_list = nullptr);
|
||||
|
||||
bool checkWebSocket();
|
||||
bool emitHttpEvent(bool doInvoke);
|
||||
void urlDecode(Parser &parser);
|
||||
void sendNotFound(bool bClose);
|
||||
void sendResponse(const char *pcStatus, bool bClose, const char *pcContentType = nullptr,
|
||||
const HttpSession::KeyValue &header = HttpSession::KeyValue(),
|
||||
const HttpBody::Ptr &body = nullptr,bool is_http_flv = false);
|
||||
const HttpBody::Ptr &body = nullptr, bool no_content_length = false);
|
||||
|
||||
//设置socket标志
|
||||
void setSocketFlags();
|
||||
|
||||
private:
|
||||
bool _is_live_stream = false;
|
||||
bool _live_over_websocket = false;
|
||||
//消耗的总流量
|
||||
uint64_t _total_bytes_usage = 0;
|
||||
string _origin;
|
||||
Parser _parser;
|
||||
Ticker _ticker;
|
||||
//消耗的总流量
|
||||
uint64_t _ui64TotalBytes = 0;
|
||||
//flv over http
|
||||
MediaInfo _mediaInfo;
|
||||
TSMediaSource::RingType::RingReader::Ptr _ts_reader;
|
||||
FMP4MediaSource::RingType::RingReader::Ptr _fmp4_reader;
|
||||
//处理content数据的callback
|
||||
function<bool (const char *data,uint64_t len) > _contentCallBack;
|
||||
bool _flv_over_websocket = false;
|
||||
bool _is_flv_stream = false;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
namespace mediakit {
|
||||
|
||||
HttpTSPlayer::HttpTSPlayer(const EventPoller::Ptr &poller, bool split_ts){
|
||||
_segment.setOnSegment([this](const char *data, uint64_t len) { onPacket(data, len); });
|
||||
_poller = poller ? poller : EventPollerPool::Instance().getPoller();
|
||||
_split_ts = split_ts;
|
||||
_segment.setOnSegment([this](const char *data, uint64_t len) { onPacket(data, len); });
|
||||
setPoller(poller ? poller : EventPollerPool::Instance().getPoller());
|
||||
}
|
||||
|
||||
HttpTSPlayer::~HttpTSPlayer() {}
|
||||
@@ -25,8 +25,8 @@ int64_t HttpTSPlayer::onResponseHeader(const string &status, const HttpClient::H
|
||||
shutdown(SockException(Err_other, StrPrinter << "bad http status code:" + status));
|
||||
return 0;
|
||||
}
|
||||
auto contet_type = const_cast< HttpClient::HttpHeader &>(headers)["Content-Type"];
|
||||
if (contet_type.find("video/mp2t") == 0 || contet_type.find("video/mpeg") == 0) {
|
||||
auto content_type = const_cast< HttpClient::HttpHeader &>(headers)["Content-Type"];
|
||||
if (content_type.find("video/mp2t") == 0 || content_type.find("video/mpeg") == 0) {
|
||||
_is_ts_content = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,11 +38,10 @@ public:
|
||||
template<typename ...ArgsType>
|
||||
ClientTypeImp(ArgsType &&...args): ClientType(std::forward<ArgsType>(args)...){}
|
||||
~ClientTypeImp() override {};
|
||||
|
||||
protected:
|
||||
/**
|
||||
* 发送前拦截并打包为websocket协议
|
||||
* @param buf
|
||||
* @return
|
||||
*/
|
||||
int send(const Buffer::Ptr &buf) override{
|
||||
if(_beforeSendCB){
|
||||
@@ -50,6 +49,7 @@ protected:
|
||||
}
|
||||
return ClientType::send(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置发送数据截取回调函数
|
||||
* @param cb 截取回调函数
|
||||
@@ -57,6 +57,7 @@ protected:
|
||||
void setOnBeforeSendCB(const onBeforeSendCB &cb){
|
||||
_beforeSendCB = cb;
|
||||
}
|
||||
|
||||
private:
|
||||
onBeforeSendCB _beforeSendCB;
|
||||
};
|
||||
@@ -73,7 +74,7 @@ public:
|
||||
|
||||
HttpWsClient(ClientTypeImp<ClientType,DataType> &delegate) : _delegate(delegate){
|
||||
_Sec_WebSocket_Key = encodeBase64(SHA1::encode_bin(makeRandStr(16, false)));
|
||||
_poller = delegate.getPoller();
|
||||
setPoller(delegate.getPoller());
|
||||
}
|
||||
~HttpWsClient(){}
|
||||
|
||||
@@ -108,6 +109,7 @@ public:
|
||||
header._mask_flag = true;
|
||||
WebSocketSplitter::encode(header, nullptr);
|
||||
}
|
||||
|
||||
protected:
|
||||
//HttpClientImp override
|
||||
|
||||
@@ -124,6 +126,8 @@ protected:
|
||||
if(Sec_WebSocket_Accept == const_cast<HttpHeader &>(headers)["Sec-WebSocket-Accept"]){
|
||||
//success
|
||||
onWebSocketException(SockException());
|
||||
//防止ws服务器返回Content-Length
|
||||
const_cast<HttpHeader &>(headers).erase("Content-Length");
|
||||
//后续全是websocket负载数据
|
||||
return -1;
|
||||
}
|
||||
@@ -180,7 +184,6 @@ protected:
|
||||
|
||||
/**
|
||||
* tcp连接结果
|
||||
* @param ex
|
||||
*/
|
||||
void onConnect(const SockException &ex) override{
|
||||
if(ex){
|
||||
@@ -194,7 +197,6 @@ protected:
|
||||
|
||||
/**
|
||||
* tcp连接断开
|
||||
* @param ex
|
||||
*/
|
||||
void onErr(const SockException &ex) override{
|
||||
//tcp断开或者shutdown导致的断开
|
||||
@@ -208,7 +210,7 @@ protected:
|
||||
* @param header 数据包头
|
||||
*/
|
||||
void onWebSocketDecodeHeader(const WebSocketHeader &header) override{
|
||||
_payload.clear();
|
||||
_payload_section.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,10 +221,9 @@ protected:
|
||||
* @param recved 已接收数据长度(包含本次数据长度),等于header._payload_len时则接受完毕
|
||||
*/
|
||||
void onWebSocketDecodePayload(const WebSocketHeader &header, const uint8_t *ptr, uint64_t len, uint64_t recved) override{
|
||||
_payload.append((char *)ptr,len);
|
||||
_payload_section.append((char *)ptr,len);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 接收到完整的一个webSocket数据包后回调
|
||||
* @param header 数据包包头
|
||||
@@ -238,28 +239,46 @@ protected:
|
||||
//服务器主动关闭
|
||||
WebSocketSplitter::encode(header,nullptr);
|
||||
shutdown(SockException(Err_eof,"websocket server close the connection"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WebSocketHeader::PING:{
|
||||
//心跳包
|
||||
header._opcode = WebSocketHeader::PONG;
|
||||
WebSocketSplitter::encode(header,std::make_shared<BufferString>(std::move(_payload)));
|
||||
}
|
||||
WebSocketSplitter::encode(header,std::make_shared<BufferString>(std::move(_payload_section)));
|
||||
break;
|
||||
case WebSocketHeader::CONTINUATION:{
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case WebSocketHeader::CONTINUATION:
|
||||
case WebSocketHeader::TEXT:
|
||||
case WebSocketHeader::BINARY:{
|
||||
//接收完毕websocket数据包,触发onRecv事件
|
||||
_delegate.onRecv(std::make_shared<BufferString>(std::move(_payload)));
|
||||
if (!header._fin) {
|
||||
//还有后续分片数据, 我们先缓存数据,所有分片收集完成才一次性输出
|
||||
_payload_cache.append(std::move(_payload_section));
|
||||
if (_payload_cache.size() < MAX_WS_PACKET) {
|
||||
//还有内存容量缓存分片数据
|
||||
break;
|
||||
}
|
||||
//分片缓存太大,需要清空
|
||||
}
|
||||
|
||||
//最后一个包
|
||||
if (_payload_cache.empty()) {
|
||||
//这个包是唯一个分片
|
||||
_delegate.onRecv(std::make_shared<WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_section)));
|
||||
break;
|
||||
}
|
||||
|
||||
//这个包由多个分片组成
|
||||
_payload_cache.append(std::move(_payload_section));
|
||||
_delegate.onRecv(std::make_shared<WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_cache)));
|
||||
_payload_cache.clear();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
_payload.clear();
|
||||
_payload_section.clear();
|
||||
header._mask_flag = flag;
|
||||
}
|
||||
|
||||
@@ -271,6 +290,7 @@ protected:
|
||||
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override{
|
||||
HttpClientImp::send(buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
void onWebSocketException(const SockException &ex){
|
||||
if(!ex){
|
||||
@@ -292,7 +312,7 @@ private:
|
||||
});
|
||||
|
||||
//设置sock,否则shutdown等接口都无效
|
||||
_delegate.setSock(HttpClientImp::_sock);
|
||||
_delegate.setSock(HttpClientImp::getSock());
|
||||
//触发连接成功事件
|
||||
_delegate.onConnect(ex);
|
||||
//拦截websocket数据接收
|
||||
@@ -319,10 +339,10 @@ private:
|
||||
string _Sec_WebSocket_Key;
|
||||
function<void(const char *data, int len)> _onRecv;
|
||||
ClientTypeImp<ClientType,DataType> &_delegate;
|
||||
string _payload;
|
||||
string _payload_section;
|
||||
string _payload_cache;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tcp客户端转WebSocket客户端模板,
|
||||
* 通过该模板,开发者再不修改TcpClient派生类任何代码的情况下快速实现WebSocket协议的包装
|
||||
@@ -365,6 +385,7 @@ public:
|
||||
void startWebSocket(const string &ws_url,float fTimeOutSec = 3){
|
||||
_wsClient->startWsClient(ws_url,fTimeOutSec);
|
||||
}
|
||||
|
||||
private:
|
||||
typename HttpWsClient<ClientType,DataType>::Ptr _wsClient;
|
||||
};
|
||||
|
||||
@@ -78,7 +78,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 通过该模板类可以透明化WebSocket协议,
|
||||
* 用户只要实现WebSock协议下的具体业务协议,譬如基于WebSocket协议的Rtmp协议等
|
||||
@@ -107,8 +106,9 @@ public:
|
||||
|
||||
void attachServer(const TcpServer &server) override{
|
||||
HttpSessionType::attachServer(server);
|
||||
_weakServer = const_cast<TcpServer &>(server).shared_from_this();
|
||||
_weak_server = const_cast<TcpServer &>(server).shared_from_this();
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* websocket客户端连接上事件
|
||||
@@ -117,12 +117,12 @@ protected:
|
||||
*/
|
||||
bool onWebSocketConnect(const Parser &header) override{
|
||||
//创建websocket session类
|
||||
_session = _creator(header, *this,HttpSessionType::_sock);
|
||||
_session = _creator(header, *this,HttpSessionType::getSock());
|
||||
if(!_session){
|
||||
//此url不允许创建websocket连接
|
||||
return false;
|
||||
}
|
||||
auto strongServer = _weakServer.lock();
|
||||
auto strongServer = _weak_server.lock();
|
||||
if(strongServer){
|
||||
_session->attachServer(*strongServer);
|
||||
}
|
||||
@@ -145,24 +145,20 @@ protected:
|
||||
//允许websocket客户端
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始收到一个webSocket数据包
|
||||
* @param packet
|
||||
*/
|
||||
void onWebSocketDecodeHeader(const WebSocketHeader &packet) override{
|
||||
//新包,原来的包残余数据清空掉
|
||||
_remian_data.clear();
|
||||
_payload_section.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 收到websocket数据包负载
|
||||
* @param packet
|
||||
* @param ptr
|
||||
* @param len
|
||||
* @param recved
|
||||
*/
|
||||
void onWebSocketDecodePayload(const WebSocketHeader &packet,const uint8_t *ptr,uint64_t len,uint64_t recved) override {
|
||||
_remian_data.append((char *)ptr,len);
|
||||
_payload_section.append((char *)ptr,len);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,39 +173,60 @@ protected:
|
||||
switch (header._opcode){
|
||||
case WebSocketHeader::CLOSE:{
|
||||
HttpSessionType::encode(header,nullptr);
|
||||
}
|
||||
HttpSessionType::shutdown(SockException(Err_shutdown, "recv close request from client"));
|
||||
break;
|
||||
}
|
||||
|
||||
case WebSocketHeader::PING:{
|
||||
header._opcode = WebSocketHeader::PONG;
|
||||
HttpSessionType::encode(header,std::make_shared<BufferString>(_remian_data));
|
||||
}
|
||||
HttpSessionType::encode(header,std::make_shared<BufferString>(_payload_section));
|
||||
break;
|
||||
case WebSocketHeader::CONTINUATION:{
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case WebSocketHeader::CONTINUATION:
|
||||
case WebSocketHeader::TEXT:
|
||||
case WebSocketHeader::BINARY:{
|
||||
_session->onRecv(std::make_shared<BufferString>(_remian_data));
|
||||
if (!header._fin) {
|
||||
//还有后续分片数据, 我们先缓存数据,所有分片收集完成才一次性输出
|
||||
_payload_cache.append(std::move(_payload_section));
|
||||
if (_payload_cache.size() < MAX_WS_PACKET) {
|
||||
//还有内存容量缓存分片数据
|
||||
break;
|
||||
}
|
||||
//分片缓存太大,需要清空
|
||||
}
|
||||
|
||||
//最后一个包
|
||||
if (_payload_cache.empty()) {
|
||||
//这个包是唯一个分片
|
||||
_session->onRecv(std::make_shared<WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_section)));
|
||||
break;
|
||||
}
|
||||
|
||||
//这个包由多个分片组成
|
||||
_payload_cache.append(std::move(_payload_section));
|
||||
_session->onRecv(std::make_shared<WebSocketBuffer>(header._opcode, header._fin, std::move(_payload_cache)));
|
||||
_payload_cache.clear();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
_remian_data.clear();
|
||||
_payload_section.clear();
|
||||
header._mask_flag = flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送数据进行websocket协议打包后回调
|
||||
* @param buffer
|
||||
* 发送数据进行websocket协议打包后回调
|
||||
*/
|
||||
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override{
|
||||
HttpSessionType::send(buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
string _remian_data;
|
||||
weak_ptr<TcpServer> _weakServer;
|
||||
string _payload_cache;
|
||||
string _payload_section;
|
||||
weak_ptr<TcpServer> _weak_server;
|
||||
TcpSession::Ptr _session;
|
||||
Creator _creator;
|
||||
};
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "Network/Buffer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
//websocket组合包最大不得超过4MB(防止内存爆炸)
|
||||
#define MAX_WS_PACKET (4 * 1024 * 1024)
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class WebSocketHeader {
|
||||
@@ -44,6 +46,7 @@ public:
|
||||
CONTROL_RSVF = 0xF
|
||||
} Type;
|
||||
public:
|
||||
|
||||
WebSocketHeader() : _mask(4){
|
||||
//获取_mask内部buffer的内存地址,该内存是malloc开辟的,地址为随机
|
||||
uint64_t ptr = (uint64_t)(&_mask[0]);
|
||||
@@ -51,6 +54,7 @@ public:
|
||||
_mask.assign((uint8_t*)(&ptr), (uint8_t*)(&ptr) + 4);
|
||||
}
|
||||
virtual ~WebSocketHeader(){}
|
||||
|
||||
public:
|
||||
bool _fin;
|
||||
uint8_t _reserved;
|
||||
@@ -60,6 +64,26 @@ public:
|
||||
vector<uint8_t > _mask;
|
||||
};
|
||||
|
||||
//websocket协议收到的字符串类型缓存,用户协议层获取该数据传输的方式
|
||||
class WebSocketBuffer : public BufferString {
|
||||
public:
|
||||
typedef std::shared_ptr<WebSocketBuffer> Ptr;
|
||||
|
||||
template<typename ...ARGS>
|
||||
WebSocketBuffer(WebSocketHeader::Type headType, bool fin, ARGS &&...args)
|
||||
: _head_type(headType), _fin(fin), BufferString(std::forward<ARGS>(args)...) {}
|
||||
|
||||
~WebSocketBuffer() override {}
|
||||
|
||||
WebSocketHeader::Type headType() const { return _head_type; }
|
||||
|
||||
bool isFinished() const { return _fin; };
|
||||
|
||||
private:
|
||||
WebSocketHeader::Type _head_type;
|
||||
bool _fin;
|
||||
};
|
||||
|
||||
class WebSocketSplitter : public WebSocketHeader{
|
||||
public:
|
||||
WebSocketSplitter(){}
|
||||
@@ -80,6 +104,7 @@ public:
|
||||
* @param buffer 负载数据
|
||||
*/
|
||||
void encode(const WebSocketHeader &header,const Buffer::Ptr &buffer);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* 收到一个webSocket数据包包头,后续将继续触发onWebSocketDecodePayload回调
|
||||
@@ -96,7 +121,6 @@ protected:
|
||||
*/
|
||||
virtual void onWebSocketDecodePayload(const WebSocketHeader &header, const uint8_t *ptr, uint64_t len, uint64_t recved) {};
|
||||
|
||||
|
||||
/**
|
||||
* 接收到完整的一个webSocket数据包后回调
|
||||
* @param header 数据包包头
|
||||
@@ -109,8 +133,10 @@ protected:
|
||||
* @param len 数据指针长度
|
||||
*/
|
||||
virtual void onWebSocketEncodeData(const Buffer::Ptr &buffer){};
|
||||
|
||||
private:
|
||||
void onPayloadData(uint8_t *data, uint64_t len);
|
||||
|
||||
private:
|
||||
string _remain_data;
|
||||
int _mask_offset = 0;
|
||||
|
||||
@@ -17,31 +17,43 @@ using namespace toolkit;
|
||||
namespace mediakit {
|
||||
|
||||
MediaPlayer::MediaPlayer(const EventPoller::Ptr &poller) {
|
||||
_poller = poller;
|
||||
if(!_poller){
|
||||
_poller = EventPollerPool::Instance().getPoller();
|
||||
}
|
||||
_poller = poller ? poller : EventPollerPool::Instance().getPoller();
|
||||
}
|
||||
|
||||
MediaPlayer::~MediaPlayer() {
|
||||
}
|
||||
void MediaPlayer::play(const string &strUrl) {
|
||||
_delegate = PlayerBase::createPlayer(_poller,strUrl);
|
||||
|
||||
static void setOnCreateSocket_l(const std::shared_ptr<PlayerBase> &delegate, const Socket::onCreateSocket &cb){
|
||||
auto helper = dynamic_pointer_cast<SocketHelper>(delegate);
|
||||
if (helper) {
|
||||
helper->setOnCreateSocket(cb);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPlayer::play(const string &url) {
|
||||
_delegate = PlayerBase::createPlayer(_poller, url);
|
||||
assert(_delegate);
|
||||
setOnCreateSocket_l(_delegate, _on_create_socket);
|
||||
_delegate->setOnShutdown(_shutdownCB);
|
||||
_delegate->setOnPlayResult(_playResultCB);
|
||||
_delegate->setOnResume(_resumeCB);
|
||||
_delegate->setMediaSouce(_pMediaSrc);
|
||||
_delegate->mINI::operator=(*this);
|
||||
_delegate->play(strUrl);
|
||||
_delegate->play(url);
|
||||
}
|
||||
|
||||
EventPoller::Ptr MediaPlayer::getPoller(){
|
||||
return _poller;
|
||||
}
|
||||
|
||||
void MediaPlayer::pause(bool bPause) {
|
||||
void MediaPlayer::setOnCreateSocket(Socket::onCreateSocket cb){
|
||||
setOnCreateSocket_l(_delegate, cb);
|
||||
_on_create_socket = std::move(cb);
|
||||
}
|
||||
|
||||
void MediaPlayer::pause(bool pause) {
|
||||
if (_delegate) {
|
||||
_delegate->pause(bPause);
|
||||
_delegate->pause(pause);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,12 +27,15 @@ public:
|
||||
|
||||
MediaPlayer(const EventPoller::Ptr &poller = nullptr);
|
||||
virtual ~MediaPlayer();
|
||||
void play(const string &strUrl) override;
|
||||
void pause(bool bPause) override;
|
||||
void play(const string &url) override;
|
||||
void pause(bool pause) override;
|
||||
void teardown() override;
|
||||
EventPoller::Ptr getPoller();
|
||||
void setOnCreateSocket(Socket::onCreateSocket cb);
|
||||
|
||||
private:
|
||||
EventPoller::Ptr _poller;
|
||||
Socket::onCreateSocket _on_create_socket;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
@@ -105,7 +105,7 @@ vector<Track::Ptr> Demuxer::getTracks(bool trackReady) const {
|
||||
ret.emplace_back(_audioTrack);
|
||||
}
|
||||
}
|
||||
return std::move(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
float Demuxer::getDuration() const {
|
||||
|
||||
@@ -46,31 +46,23 @@ static uint8_t s_mute_adts[] = {0xff, 0xf1, 0x6c, 0x40, 0x2d, 0x3f, 0xfc, 0x00,
|
||||
#define MUTE_ADTS_DATA_LEN sizeof(s_mute_adts)
|
||||
#define MUTE_ADTS_DATA_MS 130
|
||||
|
||||
PlayerProxy::PlayerProxy(const string &strVhost,
|
||||
const string &strApp,
|
||||
const string &strSrc,
|
||||
bool bEnableRtsp,
|
||||
bool bEnableRtmp,
|
||||
bool bEnableHls,
|
||||
bool bEnableMp4,
|
||||
int iRetryCount,
|
||||
const EventPoller::Ptr &poller) : MediaPlayer(poller){
|
||||
_strVhost = strVhost;
|
||||
_strApp = strApp;
|
||||
_strSrc = strSrc;
|
||||
_bEnableRtsp = bEnableRtsp;
|
||||
_bEnableRtmp = bEnableRtmp;
|
||||
_bEnableHls = bEnableHls;
|
||||
_bEnableMp4 = bEnableMp4;
|
||||
_iRetryCount = iRetryCount;
|
||||
PlayerProxy::PlayerProxy(const string &vhost, const string &app, const string &stream_id,
|
||||
bool enable_hls, bool enable_mp4, int retry_count, const EventPoller::Ptr &poller)
|
||||
: MediaPlayer(poller) {
|
||||
_vhost = vhost;
|
||||
_app = app;
|
||||
_stream_id = stream_id;
|
||||
_enable_hls = enable_hls;
|
||||
_enable_mp4 = enable_mp4;
|
||||
_retry_count = retry_count;
|
||||
}
|
||||
|
||||
void PlayerProxy::setPlayCallbackOnce(const function<void(const SockException &ex)> &cb){
|
||||
_playCB = cb;
|
||||
_on_play = cb;
|
||||
}
|
||||
|
||||
void PlayerProxy::setOnClose(const function<void()> &cb){
|
||||
_onClose = cb;
|
||||
_on_close = cb;
|
||||
}
|
||||
|
||||
void PlayerProxy::play(const string &strUrlTmp) {
|
||||
@@ -82,16 +74,16 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(strongSelf->_playCB) {
|
||||
strongSelf->_playCB(err);
|
||||
strongSelf->_playCB = nullptr;
|
||||
if(strongSelf->_on_play) {
|
||||
strongSelf->_on_play(err);
|
||||
strongSelf->_on_play = nullptr;
|
||||
}
|
||||
|
||||
if(!err) {
|
||||
// 播放成功
|
||||
*piFailedCnt = 0;//连续播放失败次数清0
|
||||
strongSelf->onPlaySuccess();
|
||||
}else if(*piFailedCnt < strongSelf->_iRetryCount || strongSelf->_iRetryCount < 0) {
|
||||
}else if(*piFailedCnt < strongSelf->_retry_count || strongSelf->_retry_count < 0) {
|
||||
// 播放失败,延时重试播放
|
||||
strongSelf->rePlay(strUrlTmp,(*piFailedCnt)++);
|
||||
}
|
||||
@@ -101,21 +93,21 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
||||
if(!strongSelf) {
|
||||
return;
|
||||
}
|
||||
if(strongSelf->_mediaMuxer) {
|
||||
auto tracks = strongSelf->getTracks(false);
|
||||
if(strongSelf->_muxer) {
|
||||
auto tracks = strongSelf->MediaPlayer::getTracks(false);
|
||||
for (auto & track : tracks){
|
||||
track->delDelegate(strongSelf->_mediaMuxer.get());
|
||||
track->delDelegate(strongSelf->_muxer.get());
|
||||
}
|
||||
|
||||
GET_CONFIG(bool,resetWhenRePlay,General::kResetWhenRePlay);
|
||||
if (resetWhenRePlay) {
|
||||
strongSelf->_mediaMuxer.reset();
|
||||
strongSelf->_muxer.reset();
|
||||
} else {
|
||||
strongSelf->_mediaMuxer->resetTracks();
|
||||
strongSelf->_muxer->resetTracks();
|
||||
}
|
||||
}
|
||||
//播放异常中断,延时重试播放
|
||||
if(*piFailedCnt < strongSelf->_iRetryCount || strongSelf->_iRetryCount < 0) {
|
||||
if(*piFailedCnt < strongSelf->_retry_count || strongSelf->_retry_count < 0) {
|
||||
strongSelf->rePlay(strUrlTmp,(*piFailedCnt)++);
|
||||
}
|
||||
});
|
||||
@@ -125,14 +117,12 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
||||
if(dynamic_pointer_cast<RtspPlayer>(_delegate)){
|
||||
//rtsp拉流
|
||||
GET_CONFIG(bool,directProxy,Rtsp::kDirectProxy);
|
||||
if(directProxy && _bEnableRtsp){
|
||||
mediaSource = std::make_shared<RtspMediaSource>(_strVhost,_strApp,_strSrc);
|
||||
if(directProxy){
|
||||
mediaSource = std::make_shared<RtspMediaSource>(_vhost, _app, _stream_id);
|
||||
}
|
||||
} else if(dynamic_pointer_cast<RtmpPlayer>(_delegate)){
|
||||
//rtmp拉流
|
||||
if(_bEnableRtmp){
|
||||
mediaSource = std::make_shared<RtmpMediaSource>(_strVhost,_strApp,_strSrc);
|
||||
}
|
||||
//rtmp拉流,rtmp强制直接代理
|
||||
mediaSource = std::make_shared<RtmpMediaSource>(_vhost, _app, _stream_id);
|
||||
}
|
||||
if(mediaSource){
|
||||
setMediaSouce(mediaSource);
|
||||
@@ -143,6 +133,7 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
||||
PlayerProxy::~PlayerProxy() {
|
||||
_timer.reset();
|
||||
}
|
||||
|
||||
void PlayerProxy::rePlay(const string &strUrl,int iFailedCnt){
|
||||
auto iDelay = MAX(2 * 1000, MIN(iFailedCnt * 3000, 60*1000));
|
||||
weak_ptr<PlayerProxy> weakSelf = shared_from_this();
|
||||
@@ -164,16 +155,17 @@ bool PlayerProxy::close(MediaSource &sender,bool force) {
|
||||
}
|
||||
|
||||
//通知其停止推流
|
||||
weak_ptr<PlayerProxy> weakSlef = dynamic_pointer_cast<PlayerProxy>(shared_from_this());
|
||||
getPoller()->async_first([weakSlef]() {
|
||||
auto stronSelf = weakSlef.lock();
|
||||
if (stronSelf) {
|
||||
stronSelf->_mediaMuxer.reset();
|
||||
stronSelf->setMediaSouce(nullptr);
|
||||
stronSelf->teardown();
|
||||
if(stronSelf->_onClose){
|
||||
stronSelf->_onClose();
|
||||
}
|
||||
weak_ptr<PlayerProxy> weakSelf = dynamic_pointer_cast<PlayerProxy>(shared_from_this());
|
||||
getPoller()->async_first([weakSelf]() {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
strongSelf->_muxer.reset();
|
||||
strongSelf->setMediaSouce(nullptr);
|
||||
strongSelf->teardown();
|
||||
if (strongSelf->_on_close) {
|
||||
strongSelf->_on_close();
|
||||
}
|
||||
});
|
||||
WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force;
|
||||
@@ -181,7 +173,7 @@ bool PlayerProxy::close(MediaSource &sender,bool force) {
|
||||
}
|
||||
|
||||
int PlayerProxy::totalReaderCount(){
|
||||
return (_mediaMuxer ? _mediaMuxer->totalReaderCount() : 0) + (_pMediaSrc ? _pMediaSrc->readerCount() : 0);
|
||||
return (_muxer ? _muxer->totalReaderCount() : 0) + (_pMediaSrc ? _pMediaSrc->readerCount() : 0);
|
||||
}
|
||||
|
||||
int PlayerProxy::totalReaderCount(MediaSource &sender) {
|
||||
@@ -193,88 +185,90 @@ public:
|
||||
typedef std::shared_ptr<MuteAudioMaker> Ptr;
|
||||
|
||||
MuteAudioMaker(){};
|
||||
virtual ~MuteAudioMaker(){}
|
||||
~MuteAudioMaker() override {}
|
||||
|
||||
void inputFrame(const Frame::Ptr &frame) override {
|
||||
if(frame->getTrackType() == TrackVideo){
|
||||
auto iAudioIndex = frame->dts() / MUTE_ADTS_DATA_MS;
|
||||
if(_iAudioIndex != iAudioIndex){
|
||||
_iAudioIndex = iAudioIndex;
|
||||
auto aacFrame = std::make_shared<AACFrameCacheAble>((char *)MUTE_ADTS_DATA, MUTE_ADTS_DATA_LEN, _iAudioIndex * MUTE_ADTS_DATA_MS, 0);
|
||||
auto audio_idx = frame->dts() / MUTE_ADTS_DATA_MS;
|
||||
if(_audio_idx != audio_idx){
|
||||
_audio_idx = audio_idx;
|
||||
auto aacFrame = std::make_shared<FrameFromStaticPtr>(CodecAAC, (char *)MUTE_ADTS_DATA, MUTE_ADTS_DATA_LEN, _audio_idx * MUTE_ADTS_DATA_MS, 0 ,ADTS_HEADER_LEN);
|
||||
FrameDispatcher::inputFrame(aacFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
class AACFrameCacheAble : public AACFrameNoCacheAble{
|
||||
class FrameFromStaticPtr : public FrameFromPtr{
|
||||
public:
|
||||
template <typename ... ARGS>
|
||||
AACFrameCacheAble(ARGS && ...args) : AACFrameNoCacheAble(std::forward<ARGS>(args)...){};
|
||||
virtual ~AACFrameCacheAble() = default;
|
||||
FrameFromStaticPtr(ARGS && ...args) : FrameFromPtr(std::forward<ARGS>(args)...) {};
|
||||
~FrameFromStaticPtr() override = default;
|
||||
|
||||
bool cacheAble() const override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
int _iAudioIndex = 0;
|
||||
int _audio_idx = 0;
|
||||
};
|
||||
|
||||
void PlayerProxy::onPlaySuccess() {
|
||||
GET_CONFIG(bool,resetWhenRePlay,General::kResetWhenRePlay);
|
||||
if (dynamic_pointer_cast<RtspMediaSource>(_pMediaSrc)) {
|
||||
//rtsp拉流代理
|
||||
if (resetWhenRePlay || !_mediaMuxer) {
|
||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), false, _bEnableRtmp, _bEnableHls, _bEnableMp4));
|
||||
if (resetWhenRePlay || !_muxer) {
|
||||
_muxer.reset(new MultiMediaSourceMuxer(_vhost, _app, _stream_id, getDuration(), false, true, _enable_hls, _enable_mp4));
|
||||
}
|
||||
} else if (dynamic_pointer_cast<RtmpMediaSource>(_pMediaSrc)) {
|
||||
//rtmp拉流代理
|
||||
if (resetWhenRePlay || !_mediaMuxer) {
|
||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), _bEnableRtsp, false, _bEnableHls, _bEnableMp4));
|
||||
if (resetWhenRePlay || !_muxer) {
|
||||
_muxer.reset(new MultiMediaSourceMuxer(_vhost, _app, _stream_id, getDuration(), true, false, _enable_hls, _enable_mp4));
|
||||
}
|
||||
} else {
|
||||
//其他拉流代理
|
||||
if (resetWhenRePlay || !_mediaMuxer) {
|
||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), _bEnableRtsp, _bEnableRtmp, _bEnableHls, _bEnableMp4));
|
||||
if (resetWhenRePlay || !_muxer) {
|
||||
_muxer.reset(new MultiMediaSourceMuxer(_vhost, _app, _stream_id, getDuration(), true, true, _enable_hls, _enable_mp4));
|
||||
}
|
||||
}
|
||||
_mediaMuxer->setMediaListener(shared_from_this());
|
||||
_muxer->setMediaListener(shared_from_this());
|
||||
|
||||
auto videoTrack = getTrack(TrackVideo,false);
|
||||
if(videoTrack){
|
||||
auto videoTrack = getTrack(TrackVideo, false);
|
||||
if (videoTrack) {
|
||||
//添加视频
|
||||
_mediaMuxer->addTrack(videoTrack);
|
||||
_muxer->addTrack(videoTrack);
|
||||
//视频数据写入_mediaMuxer
|
||||
videoTrack->addDelegate(_mediaMuxer);
|
||||
videoTrack->addDelegate(_muxer);
|
||||
}
|
||||
|
||||
//是否添加静音音频
|
||||
GET_CONFIG(bool,addMuteAudio,General::kAddMuteAudio);
|
||||
GET_CONFIG(bool, addMuteAudio, General::kAddMuteAudio);
|
||||
|
||||
auto audioTrack = getTrack(TrackAudio, false);
|
||||
if(audioTrack){
|
||||
if (audioTrack) {
|
||||
//添加音频
|
||||
_mediaMuxer->addTrack(audioTrack);
|
||||
_muxer->addTrack(audioTrack);
|
||||
//音频数据写入_mediaMuxer
|
||||
audioTrack->addDelegate(_mediaMuxer);
|
||||
}else if(addMuteAudio && videoTrack){
|
||||
audioTrack->addDelegate(_muxer);
|
||||
} else if (addMuteAudio && videoTrack) {
|
||||
//没有音频信息,产生一个静音音频
|
||||
MuteAudioMaker::Ptr audioMaker = std::make_shared<MuteAudioMaker>();
|
||||
//videoTrack把数据写入MuteAudioMaker
|
||||
videoTrack->addDelegate(audioMaker);
|
||||
//添加一个静音Track至_mediaMuxer
|
||||
_mediaMuxer->addTrack(std::make_shared<AACTrack>());
|
||||
_muxer->addTrack(std::make_shared<AACTrack>());
|
||||
//MuteAudioMaker生成静音音频然后写入_mediaMuxer;
|
||||
audioMaker->addDelegate(_mediaMuxer);
|
||||
audioMaker->addDelegate(_muxer);
|
||||
}
|
||||
|
||||
//添加完毕所有track,防止单track情况下最大等待3秒
|
||||
_mediaMuxer->addTrackCompleted();
|
||||
_muxer->addTrackCompleted();
|
||||
|
||||
if(_pMediaSrc){
|
||||
_pMediaSrc->setTrackSource(_mediaMuxer);
|
||||
if (_pMediaSrc) {
|
||||
//让_muxer对象拦截一部分事件(比如说录像相关事件)
|
||||
_pMediaSrc->setListener(_muxer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
@@ -15,32 +15,22 @@
|
||||
#include "Common/Device.h"
|
||||
#include "Player/MediaPlayer.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class PlayerProxy :public MediaPlayer,
|
||||
public std::enable_shared_from_this<PlayerProxy> ,
|
||||
public MediaSourceEvent{
|
||||
class PlayerProxy : public MediaPlayer, public MediaSourceEvent, public std::enable_shared_from_this<PlayerProxy> {
|
||||
public:
|
||||
typedef std::shared_ptr<PlayerProxy> Ptr;
|
||||
|
||||
//如果iRetryCount<0,则一直重试播放;否则重试iRetryCount次数
|
||||
//如果retry_count<0,则一直重试播放;否则重试retry_count次数
|
||||
//默认一直重试
|
||||
PlayerProxy(const string &strVhost,
|
||||
const string &strApp,
|
||||
const string &strSrc,
|
||||
bool bEnableRtsp = true,
|
||||
bool bEnableRtmp = true,
|
||||
bool bEnableHls = true,
|
||||
bool bEnableMp4 = false,
|
||||
int iRetryCount = -1,
|
||||
const EventPoller::Ptr &poller = nullptr);
|
||||
PlayerProxy(const string &vhost, const string &app, const string &stream_id,
|
||||
bool enable_hls = true, bool enable_mp4 = false,
|
||||
int retry_count = -1, const EventPoller::Ptr &poller = nullptr);
|
||||
|
||||
virtual ~PlayerProxy();
|
||||
~PlayerProxy() override;
|
||||
|
||||
/**
|
||||
* 设置play结果回调,只触发一次;在play执行之前有效
|
||||
@@ -64,27 +54,26 @@ public:
|
||||
* 获取观看总人数
|
||||
*/
|
||||
int totalReaderCount() ;
|
||||
|
||||
private:
|
||||
//MediaSourceEvent override
|
||||
bool close(MediaSource &sender,bool force) override;
|
||||
int totalReaderCount(MediaSource &sender) override;
|
||||
void rePlay(const string &strUrl,int iFailedCnt);
|
||||
void onPlaySuccess();
|
||||
|
||||
private:
|
||||
bool _bEnableRtsp;
|
||||
bool _bEnableRtmp;
|
||||
bool _bEnableHls;
|
||||
bool _bEnableMp4;
|
||||
int _iRetryCount;
|
||||
MultiMediaSourceMuxer::Ptr _mediaMuxer;
|
||||
string _strVhost;
|
||||
string _strApp;
|
||||
string _strSrc;
|
||||
bool _enable_hls;
|
||||
bool _enable_mp4;
|
||||
int _retry_count;
|
||||
string _vhost;
|
||||
string _app;
|
||||
string _stream_id;
|
||||
Timer::Ptr _timer;
|
||||
function<void(const SockException &ex)> _playCB;
|
||||
function<void()> _onClose;
|
||||
function<void()> _on_close;
|
||||
function<void(const SockException &ex)> _on_play;
|
||||
MultiMediaSourceMuxer::Ptr _muxer;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
#endif /* SRC_DEVICE_PLAYERPROXY_H_ */
|
||||
|
||||
@@ -19,34 +19,44 @@ namespace mediakit {
|
||||
MediaPusher::MediaPusher(const MediaSource::Ptr &src,
|
||||
const EventPoller::Ptr &poller) {
|
||||
_src = src;
|
||||
_poller = poller;
|
||||
if(!_poller){
|
||||
_poller = EventPollerPool::Instance().getPoller();
|
||||
}
|
||||
_poller = poller ? poller : EventPollerPool::Instance().getPoller();
|
||||
}
|
||||
|
||||
MediaPusher::MediaPusher(const string &schema,
|
||||
const string &strVhost,
|
||||
const string &strApp,
|
||||
const string &strStream,
|
||||
const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
const EventPoller::Ptr &poller) :
|
||||
MediaPusher(MediaSource::find(schema,strVhost,strApp,strStream),poller){
|
||||
MediaPusher(MediaSource::find(schema, vhost, app, stream), poller){
|
||||
}
|
||||
|
||||
MediaPusher::~MediaPusher() {
|
||||
}
|
||||
void MediaPusher::publish(const string &strUrl) {
|
||||
_delegate = PusherBase::createPusher(_poller,_src.lock(),strUrl);
|
||||
|
||||
static void setOnCreateSocket_l(const std::shared_ptr<PusherBase> &delegate, const Socket::onCreateSocket &cb){
|
||||
auto helper = dynamic_pointer_cast<SocketHelper>(delegate);
|
||||
if (helper) {
|
||||
helper->setOnCreateSocket(cb);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaPusher::publish(const string &url) {
|
||||
_delegate = PusherBase::createPusher(_poller, _src.lock(), url);
|
||||
assert(_delegate);
|
||||
setOnCreateSocket_l(_delegate, _on_create_socket);
|
||||
_delegate->setOnShutdown(_shutdownCB);
|
||||
_delegate->setOnPublished(_publishCB);
|
||||
_delegate->mINI::operator=(*this);
|
||||
_delegate->publish(strUrl);
|
||||
_delegate->publish(url);
|
||||
}
|
||||
|
||||
EventPoller::Ptr MediaPusher::getPoller(){
|
||||
return _poller;
|
||||
}
|
||||
|
||||
|
||||
void MediaPusher::setOnCreateSocket(Socket::onCreateSocket cb){
|
||||
setOnCreateSocket_l(_delegate, cb);
|
||||
_on_create_socket = std::move(cb);
|
||||
}
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
@@ -24,20 +24,24 @@ public:
|
||||
typedef std::shared_ptr<MediaPusher> Ptr;
|
||||
|
||||
MediaPusher(const string &schema,
|
||||
const string &strVhost,
|
||||
const string &strApp,
|
||||
const string &strStream,
|
||||
const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
const EventPoller::Ptr &poller = nullptr);
|
||||
|
||||
MediaPusher(const MediaSource::Ptr &src,
|
||||
const EventPoller::Ptr &poller = nullptr);
|
||||
|
||||
virtual ~MediaPusher();
|
||||
void publish(const string &strUrl) override;
|
||||
|
||||
void publish(const string &url) override;
|
||||
EventPoller::Ptr getPoller();
|
||||
void setOnCreateSocket(Socket::onCreateSocket cb);
|
||||
|
||||
private:
|
||||
std::weak_ptr<MediaSource> _src;
|
||||
EventPoller::Ptr _poller;
|
||||
Socket::onCreateSocket _on_create_socket;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
@@ -32,27 +32,27 @@ void HlsMaker::makeIndexFile(bool eof) {
|
||||
}
|
||||
}
|
||||
|
||||
auto sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL;
|
||||
auto sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL;
|
||||
|
||||
string m3u8;
|
||||
snprintf(file_content,sizeof(file_content),
|
||||
"#EXTM3U\n"
|
||||
"#EXT-X-VERSION:3\n"
|
||||
"#EXT-X-ALLOW-CACHE:NO\n"
|
||||
"#EXT-X-TARGETDURATION:%u\n"
|
||||
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
|
||||
(maxSegmentDuration + 999) / 1000,
|
||||
sequence);
|
||||
snprintf(file_content, sizeof(file_content),
|
||||
"#EXTM3U\n"
|
||||
"#EXT-X-VERSION:3\n"
|
||||
"#EXT-X-ALLOW-CACHE:NO\n"
|
||||
"#EXT-X-TARGETDURATION:%u\n"
|
||||
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
|
||||
(maxSegmentDuration + 999) / 1000,
|
||||
sequence);
|
||||
|
||||
m3u8.assign(file_content);
|
||||
|
||||
for (auto &tp : _seg_dur_list) {
|
||||
snprintf(file_content,sizeof(file_content), "#EXTINF:%.3f,\n%s\n", std::get<0>(tp) / 1000.0, std::get<1>(tp).data());
|
||||
snprintf(file_content, sizeof(file_content), "#EXTINF:%.3f,\n%s\n", std::get<0>(tp) / 1000.0, std::get<1>(tp).data());
|
||||
m3u8.append(file_content);
|
||||
}
|
||||
|
||||
if (eof) {
|
||||
snprintf(file_content,sizeof(file_content),"#EXT-X-ENDLIST\n");
|
||||
snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n");
|
||||
m3u8.append(file_content);
|
||||
}
|
||||
onWriteHls(m3u8.data(), m3u8.size());
|
||||
@@ -61,12 +61,15 @@ void HlsMaker::makeIndexFile(bool eof) {
|
||||
|
||||
void HlsMaker::inputData(void *data, uint32_t len, uint32_t timestamp, bool is_idr_fast_packet) {
|
||||
if (data && len) {
|
||||
if(is_idr_fast_packet){
|
||||
if (is_idr_fast_packet) {
|
||||
//尝试切片ts
|
||||
addNewSegment(timestamp);
|
||||
}
|
||||
onWriteSegment((char *) data, len);
|
||||
//记录上次写入数据时间
|
||||
_ticker_last_data.resetTime();
|
||||
if (!_last_file_name.empty()) {
|
||||
//存在切片才写入ts数据
|
||||
onWriteSegment((char *) data, len);
|
||||
_last_timestamp = timestamp;
|
||||
}
|
||||
} else {
|
||||
//resetTracks时触发此逻辑
|
||||
flushLastSegment(true);
|
||||
@@ -74,7 +77,7 @@ void HlsMaker::inputData(void *data, uint32_t len, uint32_t timestamp, bool is_i
|
||||
}
|
||||
|
||||
void HlsMaker::delOldSegment() {
|
||||
if(_seg_number == 0){
|
||||
if (_seg_number == 0) {
|
||||
//如果设置为保留0个切片,则认为是保存为点播
|
||||
return;
|
||||
}
|
||||
@@ -83,15 +86,15 @@ void HlsMaker::delOldSegment() {
|
||||
_seg_dur_list.pop_front();
|
||||
}
|
||||
|
||||
GET_CONFIG(uint32_t,segRetain,Hls::kSegmentRetain);
|
||||
GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain);
|
||||
//但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕
|
||||
if (_file_index > _seg_number + segRetain) {
|
||||
onDelSegment(_file_index - _seg_number - segRetain - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void HlsMaker::addNewSegment(uint32_t) {
|
||||
if(!_last_file_name.empty() && _ticker.elapsedTime() < _seg_duration * 1000){
|
||||
void HlsMaker::addNewSegment(uint32_t stamp) {
|
||||
if (!_last_file_name.empty() && stamp - _last_seg_timestamp < _seg_duration * 1000) {
|
||||
//存在上个切片,并且未到分片时间
|
||||
return;
|
||||
}
|
||||
@@ -100,28 +103,36 @@ void HlsMaker::addNewSegment(uint32_t) {
|
||||
flushLastSegment(_seg_number == 0);
|
||||
//新增切片
|
||||
_last_file_name = onOpenSegment(_file_index++);
|
||||
//重置切片计时器
|
||||
_ticker.resetTime();
|
||||
//记录本次切片的起始时间戳
|
||||
_last_seg_timestamp = stamp;
|
||||
}
|
||||
|
||||
void HlsMaker::flushLastSegment(bool eof){
|
||||
if(_last_file_name.empty()){
|
||||
if (_last_file_name.empty()) {
|
||||
//不存在上个切片
|
||||
return;
|
||||
}
|
||||
//文件创建到最后一次数据写入的时间即为切片长度
|
||||
auto seg_dur = _ticker.elapsedTime() - _ticker_last_data.elapsedTime();
|
||||
if(seg_dur <= 0){
|
||||
auto seg_dur = _last_timestamp - _last_seg_timestamp;
|
||||
if (seg_dur <= 0) {
|
||||
seg_dur = 100;
|
||||
}
|
||||
_seg_dur_list.push_back(std::make_tuple(seg_dur, _last_file_name));
|
||||
_seg_dur_list.push_back(std::make_tuple(seg_dur, std::move(_last_file_name)));
|
||||
_last_file_name.clear();
|
||||
delOldSegment();
|
||||
makeIndexFile(eof);
|
||||
_last_file_name.clear();
|
||||
onFlushLastSegment(seg_dur);
|
||||
}
|
||||
|
||||
bool HlsMaker::isLive() {
|
||||
return _seg_number != 0;
|
||||
}
|
||||
|
||||
void HlsMaker::clear() {
|
||||
_file_index = 0;
|
||||
_last_seg_timestamp = 0;
|
||||
_seg_dur_list.clear();
|
||||
_last_file_name.clear();
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
@@ -39,6 +39,17 @@ public:
|
||||
* @param is_idr_fast_packet 是否为关键帧第一个包
|
||||
*/
|
||||
void inputData(void *data, uint32_t len, uint32_t timestamp, bool is_idr_fast_packet);
|
||||
|
||||
/**
|
||||
* 是否为直播
|
||||
*/
|
||||
bool isLive();
|
||||
|
||||
/**
|
||||
* 清空记录
|
||||
*/
|
||||
void clear();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* 创建ts切片文件回调
|
||||
@@ -68,15 +79,17 @@ protected:
|
||||
virtual void onWriteHls(const char *data, int len) = 0;
|
||||
|
||||
/**
|
||||
* 关闭上个ts切片并且写入m3u8索引
|
||||
* @param eof
|
||||
* 上一个 ts 切片写入完成, 可在这里进行通知处理
|
||||
* @param duration_ms 上一个 ts 切片的时长, 单位为毫秒
|
||||
*/
|
||||
void flushLastSegment(bool eof = false);
|
||||
virtual void onFlushLastSegment(uint32_t duration_ms) {};
|
||||
|
||||
/**
|
||||
* 是否为直播
|
||||
* 关闭上个ts切片并且写入m3u8索引
|
||||
* @param eof HLS直播是否已结束
|
||||
*/
|
||||
bool isLive();
|
||||
void flushLastSegment(bool eof);
|
||||
|
||||
private:
|
||||
/**
|
||||
* 生成m3u8文件
|
||||
@@ -94,12 +107,13 @@ private:
|
||||
* @param timestamp
|
||||
*/
|
||||
void addNewSegment(uint32_t timestamp);
|
||||
|
||||
private:
|
||||
uint32_t _seg_number = 0;
|
||||
float _seg_duration = 0;
|
||||
uint32_t _seg_number = 0;
|
||||
uint32_t _last_timestamp = 0;
|
||||
uint32_t _last_seg_timestamp = 0;
|
||||
uint64_t _file_index = 0;
|
||||
Ticker _ticker;
|
||||
Ticker _ticker_last_data;
|
||||
string _last_file_name;
|
||||
std::deque<tuple<int,string> > _seg_dur_list;
|
||||
};
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <ctime>
|
||||
#include <sys/stat.h>
|
||||
#include "HlsMakerImp.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/uv_errno.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
@@ -24,45 +27,61 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file,
|
||||
_path_hls = m3u8_file;
|
||||
_params = params;
|
||||
_buf_size = bufSize;
|
||||
_file_buf.reset(new char[bufSize],[](char *ptr){
|
||||
_file_buf.reset(new char[bufSize], [](char *ptr) {
|
||||
delete[] ptr;
|
||||
});
|
||||
|
||||
_info.folder = _path_prefix;
|
||||
}
|
||||
|
||||
HlsMakerImp::~HlsMakerImp() {
|
||||
clearCache();
|
||||
}
|
||||
|
||||
void HlsMakerImp::clearCache() {
|
||||
//录制完了
|
||||
flushLastSegment(true);
|
||||
if(isLive()){
|
||||
if (isLive()) {
|
||||
//hls直播才删除文件
|
||||
clear();
|
||||
_file = nullptr;
|
||||
_segment_file_paths.clear();
|
||||
File::delete_file(_path_prefix.data());
|
||||
}
|
||||
}
|
||||
|
||||
string HlsMakerImp::onOpenSegment(int index) {
|
||||
string segment_name , segment_path;
|
||||
string segment_name, segment_path;
|
||||
{
|
||||
auto strDate = getTimeStr("%Y-%m-%d");
|
||||
auto strHour = getTimeStr("%H");
|
||||
auto strTime = getTimeStr("%M-%S");
|
||||
segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << ".ts";
|
||||
segment_path = _path_prefix + "/" + segment_name;
|
||||
if(isLive()){
|
||||
_segment_file_paths.emplace(index,segment_path);
|
||||
segment_path = _path_prefix + "/" + segment_name;
|
||||
if (isLive()) {
|
||||
_segment_file_paths.emplace(index, segment_path);
|
||||
}
|
||||
}
|
||||
_file = makeFile(segment_path, true);
|
||||
if(!_file){
|
||||
WarnL << "create file falied," << segment_path << " " << get_uv_errmsg();
|
||||
|
||||
//保存本切片的元数据
|
||||
_info.start_time = ::time(NULL);
|
||||
_info.file_name = segment_name;
|
||||
_info.file_path = segment_path;
|
||||
_info.url = _info.app + "/" + _info.stream + "/" + segment_name;
|
||||
|
||||
if (!_file) {
|
||||
WarnL << "create file failed," << segment_path << " " << get_uv_errmsg();
|
||||
}
|
||||
if(_params.empty()){
|
||||
return std::move(segment_name);
|
||||
if (_params.empty()) {
|
||||
return segment_name;
|
||||
}
|
||||
return std::move(segment_name + "?" + _params);
|
||||
return segment_name + "?" + _params;
|
||||
}
|
||||
|
||||
void HlsMakerImp::onDelSegment(int index) {
|
||||
auto it = _segment_file_paths.find(index);
|
||||
if(it == _segment_file_paths.end()){
|
||||
if (it == _segment_file_paths.end()) {
|
||||
return;
|
||||
}
|
||||
File::delete_file(it->second.data());
|
||||
@@ -77,27 +96,39 @@ void HlsMakerImp::onWriteSegment(const char *data, int len) {
|
||||
|
||||
void HlsMakerImp::onWriteHls(const char *data, int len) {
|
||||
auto hls = makeFile(_path_hls);
|
||||
if(hls){
|
||||
fwrite(data,len,1,hls.get());
|
||||
if (hls) {
|
||||
fwrite(data, len, 1, hls.get());
|
||||
hls.reset();
|
||||
if(_media_src){
|
||||
_media_src->registHls();
|
||||
if (_media_src) {
|
||||
_media_src->registHls(true);
|
||||
}
|
||||
} else{
|
||||
WarnL << "create hls file falied," << _path_hls << " " << get_uv_errmsg();
|
||||
} else {
|
||||
WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg();
|
||||
}
|
||||
//DebugL << "\r\n" << string(data,len);
|
||||
}
|
||||
|
||||
void HlsMakerImp::onFlushLastSegment(uint32_t duration_ms) {
|
||||
GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs);
|
||||
if (broadcastRecordTs) {
|
||||
//关闭ts文件以便获取正确的文件大小
|
||||
_file = nullptr;
|
||||
_info.time_len = duration_ms / 1000.0;
|
||||
struct stat fileData;
|
||||
stat(_info.file_path.data(), &fileData);
|
||||
_info.file_size = fileData.st_size;
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordTs, _info);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<FILE> HlsMakerImp::makeFile(const string &file,bool setbuf) {
|
||||
std::shared_ptr<FILE> HlsMakerImp::makeFile(const string &file, bool setbuf) {
|
||||
auto file_buf = _file_buf;
|
||||
auto ret= shared_ptr<FILE>(File::create_file(file.data(), "wb"), [file_buf](FILE *fp) {
|
||||
auto ret = shared_ptr<FILE>(File::create_file(file.data(), "wb"), [file_buf](FILE *fp) {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
});
|
||||
if(ret && setbuf){
|
||||
if (ret && setbuf) {
|
||||
setvbuf(ret.get(), _file_buf.get(), _IOFBF, _buf_size);
|
||||
}
|
||||
return ret;
|
||||
@@ -105,9 +136,12 @@ std::shared_ptr<FILE> HlsMakerImp::makeFile(const string &file,bool setbuf) {
|
||||
|
||||
void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const string &stream_id) {
|
||||
_media_src = std::make_shared<HlsMediaSource>(vhost, app, stream_id);
|
||||
_info.app = app;
|
||||
_info.stream = stream_id;
|
||||
_info.vhost = vhost;
|
||||
}
|
||||
|
||||
MediaSource::Ptr HlsMakerImp::getMediaSource() const{
|
||||
HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {
|
||||
return _media_src;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <stdlib.h>
|
||||
#include "HlsMaker.h"
|
||||
#include "HlsMediaSource.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace mediakit {
|
||||
@@ -27,7 +28,8 @@ public:
|
||||
uint32_t bufSize = 64 * 1024,
|
||||
float seg_duration = 5,
|
||||
uint32_t seg_number = 3);
|
||||
virtual ~HlsMakerImp();
|
||||
|
||||
~HlsMakerImp() override;
|
||||
|
||||
/**
|
||||
* 设置媒体信息
|
||||
@@ -41,23 +43,33 @@ public:
|
||||
* 获取MediaSource
|
||||
* @return
|
||||
*/
|
||||
MediaSource::Ptr getMediaSource() const;
|
||||
HlsMediaSource::Ptr getMediaSource() const;
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
void clearCache();
|
||||
|
||||
protected:
|
||||
string onOpenSegment(int index) override ;
|
||||
void onDelSegment(int index) override;
|
||||
void onWriteSegment(const char *data, int len) override;
|
||||
void onWriteHls(const char *data, int len) override;
|
||||
void onFlushLastSegment(uint32_t duration_ms) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<FILE> makeFile(const string &file,bool setbuf = false);
|
||||
|
||||
private:
|
||||
HlsMediaSource::Ptr _media_src;
|
||||
map<int /*index*/,string/*file_path*/> _segment_file_paths;
|
||||
int _buf_size;
|
||||
string _params;
|
||||
string _path_hls;
|
||||
string _path_prefix;
|
||||
RecordInfo _info;
|
||||
std::shared_ptr<FILE> _file;
|
||||
std::shared_ptr<char> _file_buf;
|
||||
string _path_prefix;
|
||||
string _path_hls;
|
||||
string _params;
|
||||
int _buf_size;
|
||||
HlsMediaSource::Ptr _media_src;
|
||||
map<int /*index*/,string/*file_path*/> _segment_file_paths;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
@@ -23,9 +23,7 @@ void HlsCookieData::addReaderCount(){
|
||||
if(!*_added){
|
||||
auto src = dynamic_pointer_cast<HlsMediaSource>(MediaSource::find(HLS_SCHEMA,_info._vhost,_info._app,_info._streamid));
|
||||
if(src){
|
||||
src->modifyReaderCount(true);
|
||||
*_added = true;
|
||||
_src = src;
|
||||
_ring_reader = src->getRing()->attach(EventPollerPool::Instance().getPoller());
|
||||
auto added = _added;
|
||||
_ring_reader->setDetachCB([added](){
|
||||
@@ -38,18 +36,15 @@ void HlsCookieData::addReaderCount(){
|
||||
|
||||
HlsCookieData::~HlsCookieData() {
|
||||
if (*_added) {
|
||||
auto src = _src.lock();
|
||||
if (src) {
|
||||
src->modifyReaderCount(false);
|
||||
}
|
||||
uint64_t duration = (_ticker.createdTime() - _ticker.elapsedTime()) / 1000;
|
||||
WarnL << _sock_info->getIdentifier() << "(" << _sock_info->get_peer_ip() << ":" << _sock_info->get_peer_port() << ") "
|
||||
<< "HLS播放器(" << _info._vhost << "/" << _info._app << "/" << _info._streamid
|
||||
<< ")断开,耗时(s):" << duration;
|
||||
|
||||
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
|
||||
if (_bytes > iFlowThreshold * 1024) {
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _info, _bytes, duration, true, static_cast<SockInfo&>(*_sock_info));
|
||||
uint64_t bytes = _bytes.load();
|
||||
if (bytes > iFlowThreshold * 1024) {
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, static_cast<SockInfo&>(*_sock_info));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,8 @@ public:
|
||||
friend class HlsCookieData;
|
||||
typedef RingBuffer<string> RingType;
|
||||
typedef std::shared_ptr<HlsMediaSource> Ptr;
|
||||
HlsMediaSource(const string &vhost, const string &app, const string &stream_id) : MediaSource(HLS_SCHEMA, vhost, app, stream_id){
|
||||
_readerCount = 0;
|
||||
_ring = std::make_shared<RingType>();
|
||||
}
|
||||
|
||||
virtual ~HlsMediaSource() = default;
|
||||
HlsMediaSource(const string &vhost, const string &app, const string &stream_id) : MediaSource(HLS_SCHEMA, vhost, app, stream_id){}
|
||||
~HlsMediaSource() override = default;
|
||||
|
||||
/**
|
||||
* 获取媒体源的环形缓冲
|
||||
@@ -37,41 +33,57 @@ public:
|
||||
|
||||
/**
|
||||
* 获取播放器个数
|
||||
* @return
|
||||
*/
|
||||
int readerCount() override {
|
||||
return _readerCount.load();
|
||||
return _ring ? _ring->readerCount() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册hls
|
||||
* 生成m3u8文件时触发
|
||||
* @param file_created 是否产生了hls文件
|
||||
*/
|
||||
void registHls(){
|
||||
if(!_registed){
|
||||
void registHls(bool file_created){
|
||||
if (!_is_regist) {
|
||||
_is_regist = true;
|
||||
weak_ptr<HlsMediaSource> weakSelf = dynamic_pointer_cast<HlsMediaSource>(shared_from_this());
|
||||
auto lam = [weakSelf](int size) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
strongSelf->onReaderChanged(size);
|
||||
};
|
||||
_ring = std::make_shared<RingType>(0, std::move(lam));
|
||||
onReaderChanged(0);
|
||||
regist();
|
||||
_registed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* 修改观看者个数
|
||||
* @param add 添加海思删除
|
||||
*/
|
||||
void modifyReaderCount(bool add) {
|
||||
if (add) {
|
||||
++_readerCount;
|
||||
if (!file_created) {
|
||||
//没产生文件
|
||||
return;
|
||||
}
|
||||
|
||||
if (--_readerCount == 0) {
|
||||
onNoneReader();
|
||||
//m3u8文件生成,发送给播放器
|
||||
decltype(_list_cb) copy;
|
||||
{
|
||||
lock_guard<mutex> lck(_mtx_cb);
|
||||
copy.swap(_list_cb);
|
||||
}
|
||||
copy.for_each([](const function<void()> &cb) {
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
void waitForFile(function<void()> cb) {
|
||||
//等待生成m3u8文件
|
||||
lock_guard<mutex> lck(_mtx_cb);
|
||||
_list_cb.emplace_back(std::move(cb));
|
||||
}
|
||||
|
||||
private:
|
||||
atomic_int _readerCount;
|
||||
bool _registed = false;
|
||||
bool _is_regist = false;
|
||||
RingType::Ptr _ring;
|
||||
mutex _mtx_cb;
|
||||
List<function<void()> > _list_cb;
|
||||
};
|
||||
|
||||
class HlsCookieData{
|
||||
@@ -80,13 +92,14 @@ public:
|
||||
HlsCookieData(const MediaInfo &info, const std::shared_ptr<SockInfo> &sock_info);
|
||||
~HlsCookieData();
|
||||
void addByteUsage(uint64_t bytes);
|
||||
|
||||
private:
|
||||
void addReaderCount();
|
||||
|
||||
private:
|
||||
uint64_t _bytes = 0;
|
||||
atomic<uint64_t> _bytes {0};
|
||||
MediaInfo _info;
|
||||
std::shared_ptr<bool> _added;
|
||||
weak_ptr<HlsMediaSource> _src;
|
||||
Ticker _ticker;
|
||||
std::shared_ptr<SockInfo> _sock_info;
|
||||
HlsMediaSource::RingType::RingReader::Ptr _ring_reader;
|
||||
|
||||
@@ -15,37 +15,70 @@
|
||||
#include "TsMuxer.h"
|
||||
namespace mediakit {
|
||||
|
||||
class HlsRecorder
|
||||
#if defined(ENABLE_HLS)
|
||||
: public TsMuxer
|
||||
#endif
|
||||
{
|
||||
class HlsRecorder : public MediaSourceEventInterceptor, public TsMuxer, public std::enable_shared_from_this<HlsRecorder> {
|
||||
public:
|
||||
typedef std::shared_ptr<HlsRecorder> Ptr;
|
||||
HlsRecorder(const string &m3u8_file, const string ¶ms){
|
||||
GET_CONFIG(uint32_t,hlsNum,Hls::kSegmentNum);
|
||||
GET_CONFIG(uint32_t,hlsBufSize,Hls::kFileBufSize);
|
||||
GET_CONFIG(uint32_t,hlsDuration,Hls::kSegmentDuration);
|
||||
_hls = new HlsMakerImp(m3u8_file,params,hlsBufSize,hlsDuration,hlsNum);
|
||||
GET_CONFIG(uint32_t, hlsNum, Hls::kSegmentNum);
|
||||
GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize);
|
||||
GET_CONFIG(uint32_t, hlsDuration, Hls::kSegmentDuration);
|
||||
_hls = std::make_shared<HlsMakerImp>(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum);
|
||||
//清空上次的残余文件
|
||||
_hls->clearCache();
|
||||
}
|
||||
~HlsRecorder(){
|
||||
delete _hls;
|
||||
}
|
||||
void setMediaSource(const string &vhost, const string &app, const string &stream_id){
|
||||
|
||||
~HlsRecorder(){}
|
||||
|
||||
void setMediaSource(const string &vhost, const string &app, const string &stream_id) {
|
||||
_hls->setMediaSource(vhost, app, stream_id);
|
||||
}
|
||||
|
||||
MediaSource::Ptr getMediaSource() const{
|
||||
return _hls->getMediaSource();
|
||||
void setListener(const std::weak_ptr<MediaSourceEvent> &listener) {
|
||||
_listener = listener;
|
||||
_hls->getMediaSource()->setListener(shared_from_this());
|
||||
//先注册媒体流,后续可以按需生成
|
||||
_hls->getMediaSource()->registHls(false);
|
||||
}
|
||||
#if defined(ENABLE_HLS)
|
||||
protected:
|
||||
void onTs(const void *packet, int bytes,uint32_t timestamp,bool is_idr_fast_packet) override {
|
||||
_hls->inputData((char *)packet,bytes,timestamp, is_idr_fast_packet);
|
||||
};
|
||||
#endif
|
||||
|
||||
int readerCount() {
|
||||
return _hls->getMediaSource()->readerCount();
|
||||
}
|
||||
|
||||
void onReaderChanged(MediaSource &sender, int size) override {
|
||||
//hls保留切片个数为0时代表为hls录制(不删除切片),那么不管有无观看者都一直生成hls
|
||||
_enabled = _hls->isLive() ? size : true;
|
||||
if (!size && _hls->isLive()) {
|
||||
//hls直播时,如果无人观看就删除视频缓存,目的是为了防止视频跳跃
|
||||
_clear_cache = true;
|
||||
}
|
||||
MediaSourceEventInterceptor::onReaderChanged(sender, size);
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
//缓存尚未清空时,还允许触发inputFrame函数,以便及时清空缓存
|
||||
return _clear_cache ? true : _enabled;
|
||||
}
|
||||
|
||||
void inputFrame(const Frame::Ptr &frame) override{
|
||||
if (_clear_cache) {
|
||||
_clear_cache = false;
|
||||
_hls->clearCache();
|
||||
}
|
||||
if (_enabled) {
|
||||
TsMuxer::inputFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
HlsMakerImp *_hls;
|
||||
void onTs(const void *packet, int bytes, uint32_t timestamp, bool is_idr_fast_packet) override {
|
||||
_hls->inputData((char *) packet, bytes, timestamp, is_idr_fast_packet);
|
||||
}
|
||||
|
||||
private:
|
||||
//默认不生成hls文件,有播放器时再生成
|
||||
bool _enabled = false;
|
||||
bool _clear_cache = false;
|
||||
std::shared_ptr<HlsMakerImp> _hls;
|
||||
};
|
||||
}//namespace mediakit
|
||||
#endif //HLSRECORDER_H
|
||||
|
||||
@@ -13,34 +13,125 @@
|
||||
#include "Util/File.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Common/config.h"
|
||||
#include "fmp4-writer.h"
|
||||
|
||||
using namespace toolkit;
|
||||
namespace mediakit {
|
||||
|
||||
/////////////////////////////////////////////////mp4_writer_t/////////////////////////////////////////////////
|
||||
|
||||
struct mp4_writer_t {
|
||||
int is_fmp4;
|
||||
union {
|
||||
fmp4_writer_t *fmp4;
|
||||
mov_writer_t *mov;
|
||||
} u;
|
||||
};
|
||||
|
||||
mp4_writer_t* mp4_writer_create(int is_fmp4, const struct mov_buffer_t *buffer, void* param, int flags){
|
||||
mp4_writer_t *mp4 = (mp4_writer_t *) malloc(sizeof(mp4_writer_t));
|
||||
mp4->is_fmp4 = is_fmp4;
|
||||
if (is_fmp4) {
|
||||
mp4->u.fmp4 = fmp4_writer_create(buffer, param, flags);
|
||||
} else {
|
||||
mp4->u.mov = mov_writer_create(buffer, param, flags);
|
||||
}
|
||||
return mp4;
|
||||
}
|
||||
|
||||
void mp4_writer_destroy(mp4_writer_t* mp4){
|
||||
if (mp4->is_fmp4) {
|
||||
fmp4_writer_destroy(mp4->u.fmp4);
|
||||
} else {
|
||||
mov_writer_destroy(mp4->u.mov);
|
||||
}
|
||||
free(mp4);
|
||||
}
|
||||
|
||||
int mp4_writer_add_audio(mp4_writer_t* mp4, uint8_t object, int channel_count, int bits_per_sample, int sample_rate, const void* extra_data, size_t extra_data_size){
|
||||
if (mp4->is_fmp4) {
|
||||
return fmp4_writer_add_audio(mp4->u.fmp4, object, channel_count, bits_per_sample, sample_rate, extra_data, extra_data_size);
|
||||
} else {
|
||||
return mov_writer_add_audio(mp4->u.mov, object, channel_count, bits_per_sample, sample_rate, extra_data, extra_data_size);
|
||||
}
|
||||
}
|
||||
|
||||
int mp4_writer_add_video(mp4_writer_t* mp4, uint8_t object, int width, int height, const void* extra_data, size_t extra_data_size){
|
||||
if (mp4->is_fmp4) {
|
||||
return fmp4_writer_add_video(mp4->u.fmp4, object, width, height, extra_data, extra_data_size);
|
||||
} else {
|
||||
return mov_writer_add_video(mp4->u.mov, object, width, height, extra_data, extra_data_size);
|
||||
}
|
||||
}
|
||||
|
||||
int mp4_writer_add_subtitle(mp4_writer_t* mp4, uint8_t object, const void* extra_data, size_t extra_data_size){
|
||||
if (mp4->is_fmp4) {
|
||||
return fmp4_writer_add_subtitle(mp4->u.fmp4, object, extra_data, extra_data_size);
|
||||
} else {
|
||||
return mov_writer_add_subtitle(mp4->u.mov, object, extra_data, extra_data_size);
|
||||
}
|
||||
}
|
||||
|
||||
int mp4_writer_write(mp4_writer_t* mp4, int track, const void* data, size_t bytes, int64_t pts, int64_t dts, int flags){
|
||||
if (mp4->is_fmp4) {
|
||||
return fmp4_writer_write(mp4->u.fmp4, track, data, bytes, pts, dts, flags);
|
||||
} else {
|
||||
return mov_writer_write(mp4->u.mov, track, data, bytes, pts, dts, flags);
|
||||
}
|
||||
}
|
||||
|
||||
int mp4_writer_write_l(mp4_writer_t* mp4, int track, const void* data, size_t bytes, int64_t pts, int64_t dts, int flags, int add_nalu_size){
|
||||
if (mp4->is_fmp4) {
|
||||
return fmp4_writer_write_l(mp4->u.fmp4, track, data, bytes, pts, dts, flags, add_nalu_size);
|
||||
} else {
|
||||
return mov_writer_write_l(mp4->u.mov, track, data, bytes, pts, dts, flags, add_nalu_size);
|
||||
}
|
||||
}
|
||||
|
||||
int mp4_writer_save_segment(mp4_writer_t* mp4){
|
||||
if (mp4->is_fmp4) {
|
||||
return fmp4_writer_save_segment(mp4->u.fmp4);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int mp4_writer_init_segment(mp4_writer_t* mp4){
|
||||
if (mp4->is_fmp4) {
|
||||
return fmp4_writer_init_segment(mp4->u.fmp4);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////MP4FileIO/////////////////////////////////////////////////
|
||||
|
||||
static struct mov_buffer_t s_io = {
|
||||
[](void* ctx, void* data, uint64_t bytes) {
|
||||
MP4File *thiz = (MP4File *)ctx;
|
||||
return thiz->onRead(data,bytes);
|
||||
[](void *ctx, void *data, uint64_t bytes) {
|
||||
MP4FileIO *thiz = (MP4FileIO *) ctx;
|
||||
return thiz->onRead(data, bytes);
|
||||
},
|
||||
[](void* ctx, const void* data, uint64_t bytes){
|
||||
MP4File *thiz = (MP4File *)ctx;
|
||||
return thiz->onWrite(data,bytes);
|
||||
[](void *ctx, const void *data, uint64_t bytes) {
|
||||
MP4FileIO *thiz = (MP4FileIO *) ctx;
|
||||
return thiz->onWrite(data, bytes);
|
||||
},
|
||||
[](void* ctx, uint64_t offset) {
|
||||
MP4File *thiz = (MP4File *)ctx;
|
||||
[](void *ctx, uint64_t offset) {
|
||||
MP4FileIO *thiz = (MP4FileIO *) ctx;
|
||||
return thiz->onSeek(offset);
|
||||
},
|
||||
[](void* ctx){
|
||||
MP4File *thiz = (MP4File *)ctx;
|
||||
[](void *ctx) {
|
||||
MP4FileIO *thiz = (MP4FileIO *) ctx;
|
||||
return thiz->onTell();
|
||||
}
|
||||
};
|
||||
|
||||
MP4File::Writer MP4File::createWriter(){
|
||||
GET_CONFIG(bool, mp4FastStart, Record::kFastStart);
|
||||
MP4FileIO::Writer MP4FileIO::createWriter(int flags, bool is_fmp4){
|
||||
Writer writer;
|
||||
writer.reset(mov_writer_create(&s_io,this,mp4FastStart ? MOV_FLAG_FASTSTART : 0),[](mov_writer_t *ptr){
|
||||
Ptr self = shared_from_this();
|
||||
//保存自己的强引用,防止提前释放
|
||||
writer.reset(mp4_writer_create(is_fmp4, &s_io,this, flags),[self](mp4_writer_t *ptr){
|
||||
if(ptr){
|
||||
mov_writer_destroy(ptr);
|
||||
mp4_writer_destroy(ptr);
|
||||
}
|
||||
});
|
||||
if(!writer){
|
||||
@@ -49,9 +140,11 @@ MP4File::Writer MP4File::createWriter(){
|
||||
return writer;
|
||||
}
|
||||
|
||||
MP4File::Reader MP4File::createReader(){
|
||||
MP4FileIO::Reader MP4FileIO::createReader(){
|
||||
Reader reader;
|
||||
reader.reset(mov_reader_create(&s_io,this),[](mov_reader_t *ptr){
|
||||
Ptr self = shared_from_this();
|
||||
//保存自己的强引用,防止提前释放
|
||||
reader.reset(mov_reader_create(&s_io,this),[self](mov_reader_t *ptr){
|
||||
if(ptr){
|
||||
mov_reader_destroy(ptr);
|
||||
}
|
||||
@@ -62,15 +155,17 @@ MP4File::Reader MP4File::createReader(){
|
||||
return reader;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////MP4FileDisk/////////////////////////////////////////////////////////
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define fseek64 _fseeki64
|
||||
#define ftell64 _ftelli64
|
||||
#define ftell64 _ftelli64
|
||||
#else
|
||||
#define fseek64 fseek
|
||||
#define ftell64 ftell
|
||||
#define fseek64 fseek
|
||||
#define ftell64 ftell
|
||||
#endif
|
||||
|
||||
void MP4File::openFile(const char *file,const char *mode) {
|
||||
void MP4FileDisk::openFile(const char *file, const char *mode) {
|
||||
//创建文件
|
||||
auto fp = File::create_file(file, mode);
|
||||
if(!fp){
|
||||
@@ -98,28 +193,74 @@ void MP4File::openFile(const char *file,const char *mode) {
|
||||
});
|
||||
}
|
||||
|
||||
void MP4File::closeFile() {
|
||||
void MP4FileDisk::closeFile() {
|
||||
_file = nullptr;
|
||||
}
|
||||
|
||||
int MP4File::onRead(void *data, uint64_t bytes) {
|
||||
int MP4FileDisk::onRead(void *data, uint64_t bytes) {
|
||||
if (bytes == fread(data, 1, bytes, _file.get())){
|
||||
return 0;
|
||||
}
|
||||
return 0 != ferror(_file.get()) ? ferror(_file.get()) : -1 /*EOF*/;
|
||||
}
|
||||
|
||||
int MP4File::onWrite(const void *data, uint64_t bytes) {
|
||||
int MP4FileDisk::onWrite(const void *data, uint64_t bytes) {
|
||||
return bytes == fwrite(data, 1, bytes, _file.get()) ? 0 : ferror(_file.get());
|
||||
}
|
||||
|
||||
int MP4File::onSeek(uint64_t offset) {
|
||||
int MP4FileDisk::onSeek(uint64_t offset) {
|
||||
return fseek64(_file.get(), offset, SEEK_SET);
|
||||
}
|
||||
|
||||
uint64_t MP4File::onTell() {
|
||||
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
|
||||
|
||||
128
src/Record/MP4.h
128
src/Record/MP4.h
@@ -23,27 +23,127 @@
|
||||
using namespace std;
|
||||
namespace mediakit {
|
||||
|
||||
class MP4File {
|
||||
public:
|
||||
friend struct mov_buffer_t;
|
||||
typedef std::shared_ptr<mov_writer_t> Writer;
|
||||
typedef std::shared_ptr<mov_reader_t> Reader;
|
||||
MP4File() = default;
|
||||
virtual ~MP4File() = default;
|
||||
//以下是fmp4/mov的通用接口,简单包装了ireader/media-server的接口
|
||||
typedef struct mp4_writer_t mp4_writer_t;
|
||||
mp4_writer_t* mp4_writer_create(int is_fmp4, const struct mov_buffer_t *buffer, void* param, int flags);
|
||||
void mp4_writer_destroy(mp4_writer_t* mp4);
|
||||
int mp4_writer_add_audio(mp4_writer_t* mp4, uint8_t object, int channel_count, int bits_per_sample, int sample_rate, const void* extra_data, size_t extra_data_size);
|
||||
int mp4_writer_add_video(mp4_writer_t* mp4, uint8_t object, int width, int height, const void* extra_data, size_t extra_data_size);
|
||||
int mp4_writer_add_subtitle(mp4_writer_t* mp4, uint8_t object, const void* extra_data, size_t extra_data_size);
|
||||
int mp4_writer_write(mp4_writer_t* mp4, int track, const void* data, size_t bytes, int64_t pts, int64_t dts, int flags);
|
||||
int mp4_writer_write_l(mp4_writer_t* mp4, int track, const void* data, size_t bytes, int64_t pts, int64_t dts, int flags, int add_nalu_size);
|
||||
int mp4_writer_save_segment(mp4_writer_t* mp4);
|
||||
int mp4_writer_init_segment(mp4_writer_t* mp4);
|
||||
|
||||
Writer createWriter();
|
||||
Reader createReader();
|
||||
void openFile(const char *file,const char *mode);
|
||||
//mp4文件IO的抽象接口类
|
||||
class MP4FileIO : public std::enable_shared_from_this<MP4FileIO> {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<MP4FileIO>;
|
||||
using Writer = std::shared_ptr<mp4_writer_t>;
|
||||
using Reader = std::shared_ptr<mov_reader_t>;
|
||||
|
||||
MP4FileIO() = default;
|
||||
virtual ~MP4FileIO() = default;
|
||||
|
||||
/**
|
||||
* 创建mp4复用器
|
||||
* @param flags 支持0、MOV_FLAG_FASTSTART、MOV_FLAG_SEGMENT
|
||||
* @param is_fmp4 是否为fmp4还是普通mp4
|
||||
* @return mp4复用器
|
||||
*/
|
||||
virtual Writer createWriter(int flags, bool is_fmp4 = false);
|
||||
|
||||
/**
|
||||
* 创建mp4解复用器
|
||||
* @return mp4解复用器
|
||||
*/
|
||||
virtual Reader createReader();
|
||||
|
||||
/**
|
||||
* 获取文件读写位置
|
||||
*/
|
||||
virtual uint64_t onTell() = 0;
|
||||
|
||||
/**
|
||||
* seek至文件某处
|
||||
* @param offset 文件偏移量
|
||||
* @return 是否成功(0成功)
|
||||
*/
|
||||
virtual int onSeek(uint64_t offset) = 0;
|
||||
|
||||
/**
|
||||
* 从文件读取一定数据
|
||||
* @param data 数据存放指针
|
||||
* @param bytes 指针长度
|
||||
* @return 是否成功(0成功)
|
||||
*/
|
||||
virtual int onRead(void *data, uint64_t bytes) = 0;
|
||||
|
||||
/**
|
||||
* 写入文件一定数据
|
||||
* @param data 数据指针
|
||||
* @param bytes 数据长度
|
||||
* @return 是否成功(0成功)
|
||||
*/
|
||||
virtual int onWrite(const void *data, uint64_t bytes) = 0;
|
||||
};
|
||||
|
||||
//磁盘MP4文件类
|
||||
class MP4FileDisk : public MP4FileIO {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<MP4FileDisk>;
|
||||
MP4FileDisk() = default;
|
||||
~MP4FileDisk() override = default;
|
||||
|
||||
/**
|
||||
* 打开磁盘文件
|
||||
* @param file 文件路径
|
||||
* @param mode fopen的方式
|
||||
*/
|
||||
void openFile(const char *file, const char *mode);
|
||||
|
||||
/**
|
||||
* 关闭磁盘文件
|
||||
*/
|
||||
void closeFile();
|
||||
|
||||
int onRead(void* data, uint64_t bytes);
|
||||
int onWrite(const void* data, uint64_t bytes);
|
||||
int onSeek( uint64_t offset);
|
||||
uint64_t onTell();
|
||||
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:
|
||||
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
|
||||
#endif //NABLE_MP4RECORD
|
||||
#endif //ZLMEDIAKIT_MP4_H
|
||||
|
||||
@@ -15,21 +15,24 @@
|
||||
#include "Extension/H264.h"
|
||||
#include "Extension/AAC.h"
|
||||
#include "Extension/G711.h"
|
||||
#include "Extension/Opus.h"
|
||||
using namespace toolkit;
|
||||
namespace mediakit {
|
||||
|
||||
MP4Demuxer::MP4Demuxer(const char *file) {
|
||||
openFile(file,"rb+");
|
||||
_mov_reader = createReader();
|
||||
getAllTracks();
|
||||
_duration_ms = mov_reader_getduration(_mov_reader.get());
|
||||
}
|
||||
MP4Demuxer::MP4Demuxer() {}
|
||||
|
||||
MP4Demuxer::~MP4Demuxer() {
|
||||
_mov_reader = nullptr;
|
||||
closeFile();
|
||||
}
|
||||
|
||||
void MP4Demuxer::openMP4(const string &file){
|
||||
openFile(file.data(),"rb+");
|
||||
_mov_reader = createReader();
|
||||
getAllTracks();
|
||||
_duration_ms = mov_reader_getduration(_mov_reader.get());
|
||||
}
|
||||
|
||||
int MP4Demuxer::getAllTracks() {
|
||||
static mov_reader_trackinfo_t s_on_track = {
|
||||
[](void *param, uint32_t track, uint8_t object, int width, int height, const void *extra, size_t bytes) {
|
||||
@@ -119,14 +122,22 @@ void MP4Demuxer::onAudioTrack(uint32_t track_id, uint8_t object, int channel_cou
|
||||
case MOV_OBJECT_AAC:{
|
||||
auto audio = std::make_shared<AACTrack>(bytes > 0 ? string((char *)extra,bytes) : "");
|
||||
_track_to_codec.emplace(track_id, audio);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MOV_OBJECT_G711a:
|
||||
case MOV_OBJECT_G711u:{
|
||||
auto audio = std::make_shared<G711Track>(object == MOV_OBJECT_G711a ? CodecG711A : CodecG711U, sample_rate, channel_count, bit_per_sample / channel_count );
|
||||
_track_to_codec.emplace(track_id, audio);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MOV_OBJECT_OPUS: {
|
||||
auto audio = std::make_shared<OpusTrack>();
|
||||
_track_to_codec.emplace(track_id, audio);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
WarnL << "不支持该编码类型的MP4,已忽略:" << getObjectName(object);
|
||||
break;
|
||||
@@ -149,6 +160,8 @@ struct Context{
|
||||
BufferRaw::Ptr buffer;
|
||||
};
|
||||
|
||||
#define DATA_OFFSET ADTS_HEADER_LEN
|
||||
|
||||
Frame::Ptr MP4Demuxer::readFrame(bool &keyFrame, bool &eof) {
|
||||
keyFrame = false;
|
||||
eof = false;
|
||||
@@ -163,9 +176,9 @@ Frame::Ptr MP4Demuxer::readFrame(bool &keyFrame, bool &eof) {
|
||||
static mov_onalloc mov_onalloc = [](void *param, int bytes) -> void * {
|
||||
Context *ctx = (Context *) param;
|
||||
ctx->buffer = ctx->thiz->_buffer_pool.obtain();
|
||||
ctx->buffer->setCapacity(bytes + 1);
|
||||
ctx->buffer->setSize(bytes);
|
||||
return ctx->buffer->data();
|
||||
ctx->buffer->setCapacity(bytes + DATA_OFFSET + 1);
|
||||
ctx->buffer->setSize(bytes + DATA_OFFSET);
|
||||
return ctx->buffer->data() + DATA_OFFSET;
|
||||
};
|
||||
|
||||
Context ctx = {this, 0};
|
||||
@@ -189,59 +202,49 @@ Frame::Ptr MP4Demuxer::readFrame(bool &keyFrame, bool &eof) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Parent>
|
||||
class FrameWrapper : public Parent{
|
||||
public:
|
||||
~FrameWrapper() = default;
|
||||
FrameWrapper(const Buffer::Ptr &buf, int64_t pts, int64_t dts, int prefix) : Parent(buf->data(), buf->size(), dts, pts, prefix){
|
||||
_buf = buf;
|
||||
}
|
||||
bool cacheAble() const override {
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
Buffer::Ptr _buf;
|
||||
};
|
||||
|
||||
Frame::Ptr MP4Demuxer::makeFrame(uint32_t track_id, const Buffer::Ptr &buf, int64_t pts, int64_t dts) {
|
||||
auto it = _track_to_codec.find(track_id);
|
||||
if (it == _track_to_codec.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto numBytes = buf->size();
|
||||
auto pBytes = buf->data();
|
||||
auto bytes = buf->size() - DATA_OFFSET;
|
||||
auto data = buf->data() + DATA_OFFSET;
|
||||
auto codec = it->second->getCodecId();
|
||||
switch (codec) {
|
||||
case CodecH264 :
|
||||
case CodecH265 : {
|
||||
uint32_t iOffset = 0;
|
||||
while (iOffset < numBytes) {
|
||||
uint32_t iFrameLen;
|
||||
memcpy(&iFrameLen, pBytes + iOffset, 4);
|
||||
iFrameLen = ntohl(iFrameLen);
|
||||
if (iFrameLen + iOffset + 4 > numBytes) {
|
||||
uint32_t offset = 0;
|
||||
while (offset < bytes) {
|
||||
uint32_t frame_len;
|
||||
memcpy(&frame_len, data + offset, 4);
|
||||
frame_len = ntohl(frame_len);
|
||||
if (frame_len + offset + 4 > bytes) {
|
||||
return nullptr;
|
||||
}
|
||||
memcpy(pBytes + iOffset, "\x0\x0\x0\x1", 4);
|
||||
iOffset += (iFrameLen + 4);
|
||||
memcpy(data + offset, "\x0\x0\x0\x1", 4);
|
||||
offset += (frame_len + 4);
|
||||
}
|
||||
if (codec == CodecH264) {
|
||||
return std::make_shared<FrameWrapper<H264FrameNoCacheAble> >(buf, pts, dts, 4);
|
||||
return std::make_shared<FrameWrapper<H264FrameNoCacheAble> >(buf, dts, pts, 4, DATA_OFFSET);
|
||||
}
|
||||
return std::make_shared<FrameWrapper<H265FrameNoCacheAble> >(buf, pts, dts, 4);
|
||||
return std::make_shared<FrameWrapper<H265FrameNoCacheAble> >(buf, dts, pts, 4, DATA_OFFSET);
|
||||
}
|
||||
|
||||
case CodecAAC :
|
||||
return std::make_shared<FrameWrapper<AACFrameNoCacheAble> >(buf, pts, dts, 0);
|
||||
case CodecAAC: {
|
||||
AACTrack::Ptr track = dynamic_pointer_cast<AACTrack>(it->second);
|
||||
assert(track);
|
||||
//加上adts头
|
||||
dumpAacConfig(track->getAacCfg(), buf->size() - DATA_OFFSET, (uint8_t *) buf->data() + (DATA_OFFSET - ADTS_HEADER_LEN), ADTS_HEADER_LEN);
|
||||
return std::make_shared<FrameWrapper<FrameFromPtr> >(buf, dts, pts, ADTS_HEADER_LEN, DATA_OFFSET - ADTS_HEADER_LEN, codec);
|
||||
}
|
||||
|
||||
case CodecOpus:
|
||||
case CodecG711A:
|
||||
case CodecG711U: {
|
||||
auto frame = std::make_shared<FrameWrapper<G711FrameNoCacheAble> >(buf, pts, dts, 0);
|
||||
frame->setCodec(codec);
|
||||
return frame;
|
||||
return std::make_shared<FrameWrapper<FrameFromPtr> >(buf, dts, pts, 0, DATA_OFFSET, codec);
|
||||
}
|
||||
default:
|
||||
return nullptr;
|
||||
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +256,7 @@ vector<Track::Ptr> MP4Demuxer::getTracks(bool trackReady) const {
|
||||
}
|
||||
ret.push_back(pr.second);
|
||||
}
|
||||
return std::move(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t MP4Demuxer::getDurationMS() const {
|
||||
|
||||
@@ -16,24 +16,60 @@
|
||||
#include "Util/ResourcePool.h"
|
||||
namespace mediakit {
|
||||
|
||||
class MP4Demuxer : public MP4File, public TrackSource{
|
||||
class MP4Demuxer : public MP4FileDisk, public TrackSource{
|
||||
public:
|
||||
typedef std::shared_ptr<MP4Demuxer> Ptr;
|
||||
MP4Demuxer(const char *file);
|
||||
|
||||
/**
|
||||
* 创建mp4解复用器
|
||||
*/
|
||||
MP4Demuxer();
|
||||
~MP4Demuxer() override;
|
||||
|
||||
/**
|
||||
* 打开文件
|
||||
* @param file mp4文件路径
|
||||
*/
|
||||
void openMP4(const string &file);
|
||||
|
||||
/**
|
||||
* 移动时间轴至某处
|
||||
* @param stamp_ms 预期的时间轴位置,单位毫秒
|
||||
* @return 时间轴位置
|
||||
*/
|
||||
int64_t seekTo(int64_t stamp_ms);
|
||||
|
||||
/**
|
||||
* 读取一帧数据
|
||||
* @param keyFrame 是否为关键帧
|
||||
* @param eof 是否文件读取完毕
|
||||
* @return 帧数据,可能为空
|
||||
*/
|
||||
Frame::Ptr readFrame(bool &keyFrame, bool &eof);
|
||||
vector<Track::Ptr> getTracks(bool trackReady) const override ;
|
||||
|
||||
/**
|
||||
* 获取所有Track信息
|
||||
* @param trackReady 是否要求track为就绪状态
|
||||
* @return 所有Track
|
||||
*/
|
||||
vector<Track::Ptr> getTracks(bool trackReady) const override;
|
||||
|
||||
/**
|
||||
* 获取文件长度
|
||||
* @return 文件长度,单位毫秒
|
||||
*/
|
||||
uint64_t getDurationMS() const;
|
||||
|
||||
private:
|
||||
int getAllTracks();
|
||||
void onVideoTrack(uint32_t track_id, uint8_t object, int width, int height, const void* extra, size_t bytes);
|
||||
void onAudioTrack(uint32_t track_id, uint8_t object, int channel_count, int bit_per_sample, int sample_rate, const void* extra, size_t bytes);
|
||||
Frame::Ptr makeFrame(uint32_t track_id, const Buffer::Ptr &buf,int64_t pts, int64_t dts);
|
||||
void onVideoTrack(uint32_t track_id, uint8_t object, int width, int height, const void *extra, size_t bytes);
|
||||
void onAudioTrack(uint32_t track_id, uint8_t object, int channel_count, int bit_per_sample, int sample_rate, const void *extra, size_t bytes);
|
||||
Frame::Ptr makeFrame(uint32_t track_id, const Buffer::Ptr &buf, int64_t pts, int64_t dts);
|
||||
|
||||
private:
|
||||
MP4File::Reader _mov_reader;
|
||||
Reader _mov_reader;
|
||||
uint64_t _duration_ms = 0;
|
||||
map<int, Track::Ptr > _track_to_codec;
|
||||
map<int, Track::Ptr> _track_to_codec;
|
||||
ResourcePool<BufferRaw> _buffer_pool;
|
||||
};
|
||||
|
||||
|
||||
@@ -14,33 +14,57 @@
|
||||
#include "Extension/H264.h"
|
||||
namespace mediakit{
|
||||
|
||||
MP4Muxer::MP4Muxer(const char *file) {
|
||||
_file_name = file;
|
||||
openMP4();
|
||||
}
|
||||
MP4Muxer::MP4Muxer() {}
|
||||
|
||||
MP4Muxer::~MP4Muxer() {
|
||||
closeMP4();
|
||||
}
|
||||
|
||||
void MP4Muxer::openMP4(){
|
||||
void MP4Muxer::openMP4(const string &file){
|
||||
closeMP4();
|
||||
openFile(_file_name.data(), "wb+");
|
||||
_mov_writter = createWriter();
|
||||
_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);
|
||||
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;
|
||||
openMP4();
|
||||
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不存在或初始化失败
|
||||
@@ -48,17 +72,13 @@ void MP4Muxer::inputFrame(const Frame::Ptr &frame) {
|
||||
}
|
||||
|
||||
if (!_started) {
|
||||
//还没开始
|
||||
if (!_have_video) {
|
||||
_started = true;
|
||||
} else {
|
||||
if (frame->getTrackType() != TrackVideo || !frame->keyFrame()) {
|
||||
//如果首帧是音频或者是视频但是不是i帧,那么不能开始写文件
|
||||
return;
|
||||
}
|
||||
//开始写文件
|
||||
_started = true;
|
||||
//该逻辑确保含有视频时,第一帧为关键帧
|
||||
if (_have_video && !frame->keyFrame()) {
|
||||
//含有视频,但是不是关键帧,那么前面的帧丢弃
|
||||
return;
|
||||
}
|
||||
//开始写文件
|
||||
_started = true;
|
||||
}
|
||||
|
||||
//mp4文件时间戳需要从0开始
|
||||
@@ -88,24 +108,23 @@ void MP4Muxer::inputFrame(const Frame::Ptr &frame) {
|
||||
merged.append((char *) &nalu_size, 4);
|
||||
merged.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
||||
});
|
||||
mov_writer_write_l(_mov_writter.get(),
|
||||
mp4_writer_write(_mov_writter.get(),
|
||||
track_info.track_id,
|
||||
merged.data(),
|
||||
merged.size(),
|
||||
pts_out,
|
||||
dts_out,
|
||||
back->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0,
|
||||
1/*我们合并时已经生成了4个字节的MP4格式start code*/);
|
||||
back->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0);
|
||||
} else {
|
||||
//缓存中只有一帧视频
|
||||
mov_writer_write_l(_mov_writter.get(),
|
||||
mp4_writer_write_l(_mov_writter.get(),
|
||||
track_info.track_id,
|
||||
back->data() + back->prefixSize(),
|
||||
back->size() - back->prefixSize(),
|
||||
pts_out,
|
||||
dts_out,
|
||||
back->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0,
|
||||
0/*需要生成头4个字节的MP4格式start code*/);
|
||||
1/*需要生成头4个字节的MP4格式start code*/);
|
||||
}
|
||||
_frameCached.clear();
|
||||
}
|
||||
@@ -115,14 +134,13 @@ void MP4Muxer::inputFrame(const Frame::Ptr &frame) {
|
||||
break;
|
||||
default: {
|
||||
track_info.stamp.revise(frame->dts(), frame->pts(), dts_out, pts_out);
|
||||
mov_writer_write_l(_mov_writter.get(),
|
||||
track_info.track_id,
|
||||
frame->data() + frame->prefixSize(),
|
||||
frame->size() - frame->prefixSize(),
|
||||
pts_out,
|
||||
dts_out,
|
||||
frame->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0,
|
||||
1/*aac或其他类型frame不用添加4个nalu_size的字节*/);
|
||||
mp4_writer_write(_mov_writter.get(),
|
||||
track_info.track_id,
|
||||
frame->data() + frame->prefixSize(),
|
||||
frame->size() - frame->prefixSize(),
|
||||
pts_out,
|
||||
dts_out,
|
||||
frame->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -140,7 +158,7 @@ static uint8_t getObject(CodecId codecId){
|
||||
}
|
||||
}
|
||||
|
||||
void MP4Muxer::stampSync(){
|
||||
void MP4MuxerInterface::stampSync(){
|
||||
if(_codec_to_trackid.size() < 2){
|
||||
return;
|
||||
}
|
||||
@@ -160,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();
|
||||
@@ -182,7 +203,7 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto track_id = mov_writer_add_audio(_mov_writter.get(),
|
||||
auto track_id = mp4_writer_add_audio(_mov_writter.get(),
|
||||
mp4_object,
|
||||
audio_track->getAudioChannel(),
|
||||
audio_track->getAudioSampleBit() * audio_track->getAudioChannel(),
|
||||
@@ -203,7 +224,7 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto track_id = mov_writer_add_audio(_mov_writter.get(),
|
||||
auto track_id = mp4_writer_add_audio(_mov_writter.get(),
|
||||
mp4_object,
|
||||
audio_track->getAudioChannel(),
|
||||
audio_track->getAudioSampleBit() * audio_track->getAudioChannel(),
|
||||
@@ -236,7 +257,7 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto track_id = mov_writer_add_video(_mov_writter.get(),
|
||||
auto track_id = mp4_writer_add_video(_mov_writter.get(),
|
||||
mp4_object,
|
||||
h264_track->getVideoWidth(),
|
||||
h264_track->getVideoHeight(),
|
||||
@@ -271,7 +292,7 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto track_id = mov_writer_add_video(_mov_writter.get(),
|
||||
auto track_id = mp4_writer_add_video(_mov_writter.get(),
|
||||
mp4_object,
|
||||
h265_track->getVideoWidth(),
|
||||
h265_track->getVideoHeight(),
|
||||
@@ -293,5 +314,54 @@ void MP4Muxer::addTrack(const Track::Ptr &track) {
|
||||
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
|
||||
#endif//#ifdef ENABLE_MP4
|
||||
|
||||
@@ -23,17 +23,16 @@
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
class MP4Muxer : public MediaSinkInterface, public MP4File{
|
||||
class MP4MuxerInterface : public MediaSinkInterface {
|
||||
public:
|
||||
typedef std::shared_ptr<MP4Muxer> Ptr;
|
||||
|
||||
MP4Muxer(const char *file);
|
||||
~MP4Muxer() override;
|
||||
MP4MuxerInterface() = default;
|
||||
~MP4MuxerInterface() override = default;
|
||||
|
||||
/**
|
||||
* 添加已经ready状态的track
|
||||
*/
|
||||
void addTrack(const Track::Ptr & track) override;
|
||||
void addTrack(const Track::Ptr &track) override;
|
||||
|
||||
/**
|
||||
* 输入帧
|
||||
*/
|
||||
@@ -42,30 +41,112 @@ 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<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
|
||||
* @param file 文件完整路径
|
||||
*/
|
||||
void openMP4(const string &file);
|
||||
|
||||
/**
|
||||
* 手动关闭文件(对象析构时会自动关闭)
|
||||
*/
|
||||
void closeMP4();
|
||||
|
||||
private:
|
||||
void openMP4();
|
||||
void stampSync();
|
||||
protected:
|
||||
MP4FileIO::Writer createWriter() override;
|
||||
|
||||
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;
|
||||
MP4File::Writer _mov_writter;
|
||||
string _file_name;
|
||||
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
|
||||
|
||||
@@ -29,7 +29,8 @@ MP4Reader::MP4Reader(const string &strVhost,const string &strApp, const string &
|
||||
strFileName = File::absolutePath(strFileName,recordPath);
|
||||
}
|
||||
|
||||
_demuxer = std::make_shared<MP4Demuxer>(strFileName.data());
|
||||
_demuxer = std::make_shared<MP4Demuxer>();
|
||||
_demuxer->openMP4(strFileName);
|
||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(strVhost, strApp, strId, _demuxer->getDurationMS() / 1000.0, true, true, false, false));
|
||||
auto tracks = _demuxer->getTracks(false);
|
||||
if(tracks.empty()){
|
||||
|
||||
@@ -35,6 +35,7 @@ public:
|
||||
* 意思是在文件流化结束之前或中断之前,MP4Reader对象是不会被销毁的(不管有没有被外部对象持有)
|
||||
*/
|
||||
void startReadMP4();
|
||||
|
||||
private:
|
||||
//MediaSourceEvent override
|
||||
bool seekTo(MediaSource &sender,uint32_t ui32Stamp) override;
|
||||
@@ -45,15 +46,16 @@ private:
|
||||
uint32_t getCurrentStamp();
|
||||
void setCurrentStamp(uint32_t ui32Stamp);
|
||||
bool seekTo(uint32_t ui32Stamp);
|
||||
|
||||
private:
|
||||
recursive_mutex _mtx;
|
||||
MultiMediaSourceMuxer::Ptr _mediaMuxer;
|
||||
bool _have_video = false;
|
||||
uint32_t _seek_to;
|
||||
recursive_mutex _mtx;
|
||||
Ticker _seek_ticker;
|
||||
Timer::Ptr _timer;
|
||||
EventPoller::Ptr _poller;
|
||||
MP4Demuxer::Ptr _demuxer;
|
||||
bool _have_video = false;
|
||||
MultiMediaSourceMuxer::Ptr _mediaMuxer;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
@@ -25,10 +25,10 @@ MP4Recorder::MP4Recorder(const string& strPath,
|
||||
const string &strStreamId) {
|
||||
_strPath = strPath;
|
||||
/////record 业务逻辑//////
|
||||
_info.strAppName = strApp;
|
||||
_info.strStreamId = strStreamId;
|
||||
_info.strVhost = strVhost;
|
||||
_info.strFolder = strPath;
|
||||
_info.app = strApp;
|
||||
_info.stream = strStreamId;
|
||||
_info.vhost = strVhost;
|
||||
_info.folder = strPath;
|
||||
}
|
||||
MP4Recorder::~MP4Recorder() {
|
||||
closeFile();
|
||||
@@ -42,26 +42,27 @@ void MP4Recorder::createFile() {
|
||||
auto strFile = _strPath + strDate + "/" + strTime + ".mp4";
|
||||
|
||||
/////record 业务逻辑//////
|
||||
_info.ui64StartedTime = ::time(NULL);
|
||||
_info.strFileName = strTime + ".mp4";
|
||||
_info.strFilePath = strFile;
|
||||
_info.start_time = ::time(NULL);
|
||||
_info.file_name = strTime + ".mp4";
|
||||
_info.file_path = strFile;
|
||||
GET_CONFIG(string,appName,Record::kAppName);
|
||||
_info.strUrl = appName + "/"
|
||||
+ _info.strAppName + "/"
|
||||
+ _info.strStreamId + "/"
|
||||
+ strDate + "/"
|
||||
+ strTime + ".mp4";
|
||||
_info.url = appName + "/"
|
||||
+ _info.app + "/"
|
||||
+ _info.stream + "/"
|
||||
+ strDate + "/"
|
||||
+ strTime + ".mp4";
|
||||
|
||||
try {
|
||||
_muxer = std::make_shared<MP4Muxer>(strFileTmp.data());
|
||||
for(auto &track :_tracks){
|
||||
_muxer = std::make_shared<MP4Muxer>();
|
||||
_muxer->openMP4(strFileTmp);
|
||||
for (auto &track :_tracks) {
|
||||
//添加track
|
||||
_muxer->addTrack(track);
|
||||
}
|
||||
_strFileTmp = strFileTmp;
|
||||
_strFile = strFile;
|
||||
_createFileTicker.resetTime();
|
||||
}catch(std::exception &ex) {
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
}
|
||||
}
|
||||
@@ -73,7 +74,7 @@ void MP4Recorder::asyncClose() {
|
||||
auto info = _info;
|
||||
WorkThreadPool::Instance().getExecutor()->async([muxer,strFileTmp,strFile,info]() {
|
||||
//获取文件录制时间,放在关闭mp4之前是为了忽略关闭mp4执行时间
|
||||
const_cast<MP4Info&>(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime;
|
||||
const_cast<RecordInfo&>(info).time_len = ::time(NULL) - info.start_time;
|
||||
//关闭mp4非常耗时,所以要放在后台线程执行
|
||||
muxer->closeMP4();
|
||||
//临时文件名改成正式文件名,防止mp4未完成时被访问
|
||||
@@ -81,7 +82,7 @@ void MP4Recorder::asyncClose() {
|
||||
//获取文件大小
|
||||
struct stat fileData;
|
||||
stat(strFile.data(), &fileData);
|
||||
const_cast<MP4Info&>(info).ui64FileSize = fileData.st_size;
|
||||
const_cast<RecordInfo&>(info).file_size = fileData.st_size;
|
||||
/////record 业务逻辑//////
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordMP4,info);
|
||||
});
|
||||
|
||||
@@ -20,24 +20,11 @@
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "Common/MediaSink.h"
|
||||
#include "MP4Muxer.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class MP4Info {
|
||||
public:
|
||||
time_t ui64StartedTime; //GMT标准时间,单位秒
|
||||
time_t ui64TimeLen;//录像长度,单位秒
|
||||
off_t ui64FileSize;//文件大小,单位BYTE
|
||||
string strFilePath;//文件路径
|
||||
string strFileName;//文件名称
|
||||
string strFolder;//文件夹路径
|
||||
string strUrl;//播放路径
|
||||
string strAppName;//应用名称
|
||||
string strStreamId;//流ID
|
||||
string strVhost;//vhost
|
||||
};
|
||||
|
||||
#ifdef ENABLE_MP4
|
||||
class MP4Recorder : public MediaSinkInterface{
|
||||
public:
|
||||
@@ -72,7 +59,7 @@ private:
|
||||
string _strFile;
|
||||
string _strFileTmp;
|
||||
Ticker _createFileTicker;
|
||||
MP4Info _info;
|
||||
RecordInfo _info;
|
||||
bool _haveVideo = false;
|
||||
MP4Muxer::Ptr _muxer;
|
||||
list<Track::Ptr> _tracks;
|
||||
|
||||
@@ -79,16 +79,8 @@ std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const st
|
||||
}
|
||||
}
|
||||
|
||||
static MediaSource::Ptr getMediaSource(const string &vhost, const string &app, const string &stream_id){
|
||||
auto src = MediaSource::find(RTMP_SCHEMA, vhost, app, stream_id);
|
||||
if(src){
|
||||
return src;
|
||||
}
|
||||
return MediaSource::find(RTSP_SCHEMA, vhost, app, stream_id);
|
||||
}
|
||||
|
||||
bool Recorder::isRecording(type type, const string &vhost, const string &app, const string &stream_id){
|
||||
auto src = getMediaSource(vhost, app, stream_id);
|
||||
auto src = MediaSource::find(vhost, app, stream_id);
|
||||
if(!src){
|
||||
return false;
|
||||
}
|
||||
@@ -96,15 +88,16 @@ bool Recorder::isRecording(type type, const string &vhost, const string &app, co
|
||||
}
|
||||
|
||||
bool Recorder::startRecord(type type, const string &vhost, const string &app, const string &stream_id,const string &customized_path){
|
||||
auto src = getMediaSource(vhost, app, stream_id);
|
||||
if(!src){
|
||||
auto src = MediaSource::find(vhost, app, stream_id);
|
||||
if (!src) {
|
||||
WarnL << "未找到相关的MediaSource,startRecord失败:" << vhost << "/" << app << "/" << stream_id;
|
||||
return false;
|
||||
}
|
||||
return src->setupRecord(type,true,customized_path);
|
||||
return src->setupRecord(type, true, customized_path);
|
||||
}
|
||||
|
||||
bool Recorder::stopRecord(type type, const string &vhost, const string &app, const string &stream_id){
|
||||
auto src = getMediaSource(vhost, app, stream_id);
|
||||
auto src = MediaSource::find(vhost, app, stream_id);
|
||||
if(!src){
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,20 @@ using namespace std;
|
||||
namespace mediakit {
|
||||
class MediaSinkInterface;
|
||||
|
||||
class RecordInfo {
|
||||
public:
|
||||
time_t start_time; // GMT 标准时间,单位秒
|
||||
float time_len; // 录像长度,单位秒
|
||||
off_t file_size; // 文件大小,单位 BYTE
|
||||
string file_path; // 文件路径
|
||||
string file_name; // 文件名称
|
||||
string folder; // 文件夹路径
|
||||
string url; // 播放路径
|
||||
string app; // 应用名称
|
||||
string stream; // 流 ID
|
||||
string vhost; // 虚拟主机
|
||||
};
|
||||
|
||||
class Recorder{
|
||||
public:
|
||||
typedef enum {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "TsMuxer.h"
|
||||
#if defined(ENABLE_HLS)
|
||||
#include "TsMuxer.h"
|
||||
#include "mpeg-ts-proto.h"
|
||||
#include "mpeg-ts.h"
|
||||
#include "Extension/H264.h"
|
||||
@@ -25,20 +25,20 @@ TsMuxer::~TsMuxer() {
|
||||
}
|
||||
|
||||
void TsMuxer::stampSync(){
|
||||
if(_codec_to_trackid.size() < 2){
|
||||
if (_codec_to_trackid.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
Stamp *audio = nullptr, *video = nullptr;
|
||||
for(auto &pr : _codec_to_trackid){
|
||||
switch (getTrackType((CodecId) pr.first)){
|
||||
for (auto &pr : _codec_to_trackid) {
|
||||
switch (getTrackType((CodecId) pr.first)) {
|
||||
case TrackAudio : audio = &pr.second.stamp; break;
|
||||
case TrackVideo : video = &pr.second.stamp; break;
|
||||
default : break;
|
||||
}
|
||||
}
|
||||
|
||||
if(audio && video){
|
||||
if (audio && video) {
|
||||
//音频时间戳同步于视频,因为音频时间戳被修改后不影响播放
|
||||
audio->syncTo(*video);
|
||||
}
|
||||
@@ -73,6 +73,11 @@ void TsMuxer::addTrack(const Track::Ptr &track) {
|
||||
break;
|
||||
}
|
||||
|
||||
case CodecOpus: {
|
||||
_codec_to_trackid[track->getCodecId()].track_id = mpeg_ts_add_stream(_context, PSI_STREAM_AUDIO_OPUS, nullptr, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
default: WarnL << "mpeg-ts 不支持该编码格式,已忽略:" << track->getCodecName(); break;
|
||||
}
|
||||
|
||||
@@ -82,88 +87,100 @@ void TsMuxer::addTrack(const Track::Ptr &track) {
|
||||
|
||||
void TsMuxer::inputFrame(const Frame::Ptr &frame) {
|
||||
auto it = _codec_to_trackid.find(frame->getCodecId());
|
||||
if(it == _codec_to_trackid.end()){
|
||||
if (it == _codec_to_trackid.end()) {
|
||||
return;
|
||||
}
|
||||
//mp4文件时间戳需要从0开始
|
||||
auto &track_info = it->second;
|
||||
int64_t dts_out, pts_out;
|
||||
_is_idr_fast_packet = !_have_video;
|
||||
switch (frame->getCodecId()){
|
||||
case CodecH264: {
|
||||
int type = H264_TYPE(*((uint8_t *)frame->data() + frame->prefixSize()));
|
||||
if(type == H264Frame::NAL_SEI){
|
||||
int type = H264_TYPE(*((uint8_t *) frame->data() + frame->prefixSize()));
|
||||
if (type == H264Frame::NAL_SEI) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
case CodecH265: {
|
||||
//这里的代码逻辑是让SPS、PPS、IDR这些时间戳相同的帧打包到一起当做一个帧处理,
|
||||
if (!_frameCached.empty() && _frameCached.back()->dts() != frame->dts()) {
|
||||
Frame::Ptr back = _frameCached.back();
|
||||
Buffer::Ptr merged_frame = back;
|
||||
if(_frameCached.size() != 1){
|
||||
if (_frameCached.size() != 1) {
|
||||
string merged;
|
||||
_frameCached.for_each([&](const Frame::Ptr &frame){
|
||||
if(frame->prefixSize()){
|
||||
merged.append(frame->data(),frame->size());
|
||||
} else{
|
||||
merged.append("\x00\x00\x00\x01",4);
|
||||
merged.append(frame->data(),frame->size());
|
||||
_frameCached.for_each([&](const Frame::Ptr &frame) {
|
||||
if (frame->prefixSize()) {
|
||||
merged.append(frame->data(), frame->size());
|
||||
} else {
|
||||
merged.append("\x00\x00\x00\x01", 4);
|
||||
merged.append(frame->data(), frame->size());
|
||||
}
|
||||
if(frame->keyFrame()){
|
||||
if (frame->keyFrame()) {
|
||||
_is_idr_fast_packet = true;
|
||||
}
|
||||
});
|
||||
merged_frame = std::make_shared<BufferString>(std::move(merged));
|
||||
}
|
||||
track_info.stamp.revise(back->dts(),back->pts(),dts_out,pts_out);
|
||||
track_info.stamp.revise(back->dts(), back->pts(), dts_out, pts_out);
|
||||
//取视频时间戳为TS的时间戳
|
||||
_timestamp = dts_out;
|
||||
mpeg_ts_write(_context, track_info.track_id, back->keyFrame() ? 0x0001 : 0, pts_out * 90LL, dts_out * 90LL, merged_frame->data(), merged_frame->size());
|
||||
mpeg_ts_write(_context, track_info.track_id, back->keyFrame() ? 0x0001 : 0, pts_out * 90LL,dts_out * 90LL, merged_frame->data(), merged_frame->size());
|
||||
_frameCached.clear();
|
||||
}
|
||||
_frameCached.emplace_back(Frame::getCacheAbleFrame(frame));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CodecAAC: {
|
||||
if (frame->prefixSize() == 0) {
|
||||
WarnL << "必须提供adts头才能mpeg-ts打包";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
default: {
|
||||
track_info.stamp.revise(frame->dts(),frame->pts(),dts_out,pts_out);
|
||||
_timestamp = dts_out;
|
||||
track_info.stamp.revise(frame->dts(), frame->pts(), dts_out, pts_out);
|
||||
if(!_have_video){
|
||||
//没有视频时,才以音频时间戳为TS的时间戳
|
||||
_timestamp = dts_out;
|
||||
}
|
||||
mpeg_ts_write(_context, track_info.track_id, frame->keyFrame() ? 0x0001 : 0, pts_out * 90LL, dts_out * 90LL, frame->data(), frame->size());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TsMuxer::resetTracks() {
|
||||
_have_video = false;
|
||||
//通知片段中断
|
||||
onTs(nullptr, 0, 0, 0);
|
||||
onTs(nullptr, 0, _timestamp, 0);
|
||||
uninit();
|
||||
init();
|
||||
}
|
||||
|
||||
void TsMuxer::init() {
|
||||
static mpeg_ts_func_t s_func= {
|
||||
[](void* param, size_t bytes){
|
||||
TsMuxer *muxer = (TsMuxer *)param;
|
||||
static mpeg_ts_func_t s_func = {
|
||||
[](void *param, size_t bytes) {
|
||||
TsMuxer *muxer = (TsMuxer *) param;
|
||||
assert(sizeof(TsMuxer::_tsbuf) >= bytes);
|
||||
return (void *)muxer->_tsbuf;
|
||||
return (void *) muxer->_tsbuf;
|
||||
},
|
||||
[](void* param, void* packet){
|
||||
[](void *param, void *packet) {
|
||||
//do nothing
|
||||
},
|
||||
[](void* param, const void* packet, size_t bytes){
|
||||
TsMuxer *muxer = (TsMuxer *)param;
|
||||
muxer->onTs(packet, bytes,muxer->_timestamp,muxer->_is_idr_fast_packet);
|
||||
[](void *param, const void *packet, size_t bytes) {
|
||||
TsMuxer *muxer = (TsMuxer *) param;
|
||||
muxer->onTs(packet, bytes, muxer->_timestamp, muxer->_is_idr_fast_packet);
|
||||
muxer->_is_idr_fast_packet = false;
|
||||
}
|
||||
};
|
||||
if(_context == nullptr){
|
||||
_context = mpeg_ts_create(&s_func,this);
|
||||
if (_context == nullptr) {
|
||||
_context = mpeg_ts_create(&s_func, this);
|
||||
}
|
||||
}
|
||||
|
||||
void TsMuxer::uninit() {
|
||||
if(_context){
|
||||
if (_context) {
|
||||
mpeg_ts_destroy(_context);
|
||||
_context = nullptr;
|
||||
}
|
||||
@@ -171,5 +188,4 @@ void TsMuxer::uninit() {
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#endif// defined(ENABLE_HLS)
|
||||
@@ -11,6 +11,7 @@
|
||||
#ifndef TSMUXER_H
|
||||
#define TSMUXER_H
|
||||
|
||||
#if defined(ENABLE_HLS)
|
||||
#include <unordered_map>
|
||||
#include "Extension/Frame.h"
|
||||
#include "Extension/Track.h"
|
||||
@@ -72,4 +73,25 @@ private:
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#else
|
||||
|
||||
#include "Common/MediaSink.h"
|
||||
|
||||
namespace mediakit {
|
||||
class TsMuxer : public MediaSinkInterface {
|
||||
public:
|
||||
TsMuxer() {}
|
||||
~TsMuxer() override {}
|
||||
void addTrack(const Track::Ptr &track) override {}
|
||||
void resetTracks() override {}
|
||||
void inputFrame(const Frame::Ptr &frame) override {}
|
||||
|
||||
protected:
|
||||
virtual void onTs(const void *packet, int bytes,uint32_t timestamp,bool is_idr_fast_packet) = 0;
|
||||
};
|
||||
}//namespace mediakit
|
||||
|
||||
#endif// defined(ENABLE_HLS)
|
||||
|
||||
#endif //TSMUXER_H
|
||||
@@ -73,10 +73,10 @@ void FlvMuxer::onWriteFlvHeader(const RtmpMediaSource::Ptr &mediaSrc) {
|
||||
bool is_have_audio = false,is_have_video = false;
|
||||
|
||||
mediaSrc->getConfigFrame([&](const RtmpPacket::Ptr &pkt){
|
||||
if(pkt->typeId == MSG_VIDEO){
|
||||
if(pkt->type_id == MSG_VIDEO){
|
||||
is_have_video = true;
|
||||
}
|
||||
if(pkt->typeId == MSG_AUDIO){
|
||||
if(pkt->type_id == MSG_AUDIO){
|
||||
is_have_audio = true;
|
||||
}
|
||||
});
|
||||
@@ -133,16 +133,16 @@ public:
|
||||
#pragma pack(pop)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
void FlvMuxer::onWriteFlvTag(const RtmpPacket::Ptr &pkt, uint32_t ui32TimeStamp , bool flush) {
|
||||
onWriteFlvTag(pkt->typeId,pkt,ui32TimeStamp, flush);
|
||||
void FlvMuxer::onWriteFlvTag(const RtmpPacket::Ptr &pkt, uint32_t time_stamp , bool flush) {
|
||||
onWriteFlvTag(pkt->type_id, pkt, time_stamp, flush);
|
||||
}
|
||||
|
||||
void FlvMuxer::onWriteFlvTag(uint8_t ui8Type, const Buffer::Ptr &buffer, uint32_t ui32TimeStamp, bool flush) {
|
||||
void FlvMuxer::onWriteFlvTag(uint8_t type, const Buffer::Ptr &buffer, uint32_t time_stamp, bool flush) {
|
||||
RtmpTagHeader header;
|
||||
header.type = ui8Type;
|
||||
header.type = type;
|
||||
set_be24(header.data_size, buffer->size());
|
||||
header.timestamp_ex = (uint8_t) ((ui32TimeStamp >> 24) & 0xff);
|
||||
set_be24(header.timestamp,ui32TimeStamp & 0xFFFFFF);
|
||||
header.timestamp_ex = (uint8_t) ((time_stamp >> 24) & 0xff);
|
||||
set_be24(header.timestamp, time_stamp & 0xFFFFFF);
|
||||
//tag header
|
||||
onWrite(std::make_shared<BufferRaw>((char *)&header, sizeof(header)), false);
|
||||
//tag data
|
||||
@@ -154,7 +154,7 @@ void FlvMuxer::onWriteFlvTag(uint8_t ui8Type, const Buffer::Ptr &buffer, uint32_
|
||||
|
||||
void FlvMuxer::onWriteRtmp(const RtmpPacket::Ptr &pkt,bool flush) {
|
||||
int64_t dts_out;
|
||||
_stamp[pkt->typeId % 2].revise(pkt->timeStamp, 0, dts_out, dts_out);
|
||||
_stamp[pkt->type_id % 2].revise(pkt->time_stamp, 0, dts_out, dts_out);
|
||||
onWriteFlvTag(pkt, dts_out,flush);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,21 +25,23 @@ public:
|
||||
FlvMuxer();
|
||||
virtual ~FlvMuxer();
|
||||
void stop();
|
||||
|
||||
protected:
|
||||
void start(const EventPoller::Ptr &poller,const RtmpMediaSource::Ptr &media);
|
||||
void start(const EventPoller::Ptr &poller, const RtmpMediaSource::Ptr &media);
|
||||
virtual void onWrite(const Buffer::Ptr &data, bool flush) = 0;
|
||||
virtual void onDetach() = 0;
|
||||
virtual std::shared_ptr<FlvMuxer> getSharedPtr() = 0;
|
||||
|
||||
private:
|
||||
void onWriteFlvHeader(const RtmpMediaSource::Ptr &media);
|
||||
void onWriteRtmp(const RtmpPacket::Ptr &pkt,bool flush);
|
||||
void onWriteFlvTag(const RtmpPacket::Ptr &pkt, uint32_t ui32TimeStamp, bool flush);
|
||||
void onWriteFlvTag(uint8_t ui8Type, const Buffer::Ptr &buffer, uint32_t ui32TimeStamp, bool flush);
|
||||
void onWriteRtmp(const RtmpPacket::Ptr &pkt, bool flush);
|
||||
void onWriteFlvTag(const RtmpPacket::Ptr &pkt, uint32_t time_stamp, bool flush);
|
||||
void onWriteFlvTag(uint8_t type, const Buffer::Ptr &buffer, uint32_t time_stamp, bool flush);
|
||||
|
||||
private:
|
||||
RtmpMediaSource::RingType::RingReader::Ptr _ring_reader;
|
||||
//时间戳修整器
|
||||
Stamp _stamp[2];
|
||||
|
||||
RtmpMediaSource::RingType::RingReader::Ptr _ring_reader;
|
||||
};
|
||||
|
||||
class FlvRecorder : public FlvMuxer , public std::enable_shared_from_this<FlvRecorder>{
|
||||
@@ -47,12 +49,14 @@ public:
|
||||
typedef std::shared_ptr<FlvRecorder> Ptr;
|
||||
FlvRecorder();
|
||||
virtual ~FlvRecorder();
|
||||
void startRecord(const EventPoller::Ptr &poller,const string &vhost,const string &app,const string &stream,const string &file_path);
|
||||
void startRecord(const EventPoller::Ptr &poller,const RtmpMediaSource::Ptr &media,const string &file_path);
|
||||
void startRecord(const EventPoller::Ptr &poller, const RtmpMediaSource::Ptr &media, const string &file_path);
|
||||
void startRecord(const EventPoller::Ptr &poller, const string &vhost, const string &app, const string &stream, const string &file_path);
|
||||
|
||||
private:
|
||||
virtual void onWrite(const Buffer::Ptr &data, bool flush) override ;
|
||||
virtual void onDetach() override;
|
||||
virtual std::shared_ptr<FlvMuxer> getSharedPtr() override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<FILE> _file;
|
||||
recursive_mutex _file_mtx;
|
||||
@@ -60,5 +64,4 @@ private:
|
||||
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#endif //ZLMEDIAKIT_FLVMUXER_H
|
||||
|
||||
@@ -58,6 +58,14 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){
|
||||
switch (track->getCodecId()){
|
||||
case CodecG711A : flvAudioType = FLV_CODEC_G711A; break;
|
||||
case CodecG711U : flvAudioType = FLV_CODEC_G711U; break;
|
||||
case CodecOpus : {
|
||||
flvAudioType = FLV_CODEC_OPUS;
|
||||
//opus不通过flags获取音频相关信息
|
||||
iSampleRate = 44100;
|
||||
iSampleBit = 16;
|
||||
iChannel = 2;
|
||||
break;
|
||||
}
|
||||
case CodecAAC : {
|
||||
flvAudioType = FLV_CODEC_AAC;
|
||||
//aac不通过flags获取音频相关信息
|
||||
|
||||
108
src/Rtmp/Rtmp.h
108
src/Rtmp/Rtmp.h
@@ -23,9 +23,6 @@
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
#define PORT 1935
|
||||
#define DEFAULT_CHUNK_LEN 128
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#define PACKED __attribute__((packed))
|
||||
#else
|
||||
@@ -33,6 +30,7 @@ using namespace toolkit;
|
||||
#endif //!defined(_WIN32)
|
||||
|
||||
|
||||
#define DEFAULT_CHUNK_LEN 128
|
||||
#define HANDSHAKE_PLAINTEXT 0x03
|
||||
#define RANDOM_LEN (1536 - 8)
|
||||
|
||||
@@ -74,10 +72,12 @@ using namespace toolkit;
|
||||
|
||||
#define FLV_CODEC_AAC 10
|
||||
#define FLV_CODEC_H264 7
|
||||
//金山扩展: https://github.com/ksvc/FFmpeg/wiki
|
||||
#define FLV_CODEC_H265 12
|
||||
#define FLV_CODEC_G711A 7
|
||||
#define FLV_CODEC_G711U 8
|
||||
|
||||
//参考学而思网校: https://github.com/notedit/rtmp/commit/6e314ac5b29611431f8fb5468596b05815743c10
|
||||
#define FLV_CODEC_OPUS 13
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
@@ -89,22 +89,24 @@ class RtmpHandshake {
|
||||
public:
|
||||
RtmpHandshake(uint32_t _time, uint8_t *_random = nullptr) {
|
||||
_time = htonl(_time);
|
||||
memcpy(timeStamp, &_time, 4);
|
||||
memcpy(time_stamp, &_time, 4);
|
||||
if (!_random) {
|
||||
random_generate((char *) random, sizeof(random));
|
||||
} else {
|
||||
memcpy(random, _random, sizeof(random));
|
||||
}
|
||||
}
|
||||
uint8_t timeStamp[4];
|
||||
|
||||
uint8_t time_stamp[4];
|
||||
uint8_t zero[4] = {0};
|
||||
uint8_t random[RANDOM_LEN];
|
||||
void random_generate(char* bytes, int size) {
|
||||
static char cdata[] = { 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72,
|
||||
0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x2d, 0x77, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x2d, 0x77, 0x69,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x40, 0x31, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x6d };
|
||||
|
||||
void random_generate(char *bytes, int size) {
|
||||
static char cdata[] = {0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72,
|
||||
0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x2d, 0x77, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x2d, 0x77, 0x69,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x40, 0x31, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x6d};
|
||||
for (int i = 0; i < size; i++) {
|
||||
bytes[i] = cdata[rand() % (sizeof(cdata) - 1)];
|
||||
}
|
||||
@@ -114,10 +116,10 @@ public:
|
||||
class RtmpHeader {
|
||||
public:
|
||||
uint8_t flags;
|
||||
uint8_t timeStamp[3];
|
||||
uint8_t bodySize[3];
|
||||
uint8_t typeId;
|
||||
uint8_t streamId[4]; /* Note, this is little-endian while others are BE */
|
||||
uint8_t time_stamp[3];
|
||||
uint8_t body_size[3];
|
||||
uint8_t type_id;
|
||||
uint8_t stream_index[4]; /* Note, this is little-endian while others are BE */
|
||||
}PACKED;
|
||||
|
||||
#if defined(_WIN32)
|
||||
@@ -127,22 +129,23 @@ public:
|
||||
class RtmpPacket : public Buffer{
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpPacket> Ptr;
|
||||
uint8_t typeId;
|
||||
uint32_t bodySize = 0;
|
||||
uint32_t timeStamp = 0;
|
||||
bool hasAbsStamp = false;
|
||||
bool hasExtStamp = false;
|
||||
uint32_t deltaStamp = 0;
|
||||
uint32_t streamId;
|
||||
uint32_t chunkId;
|
||||
std::string strBuf;
|
||||
uint8_t type_id;
|
||||
uint32_t body_size = 0;
|
||||
uint32_t time_stamp = 0;
|
||||
bool is_abs_stamp = false;
|
||||
uint32_t ts_field = 0;
|
||||
uint32_t stream_index;
|
||||
uint32_t chunk_id;
|
||||
std::string buffer;
|
||||
|
||||
public:
|
||||
char *data() const override{
|
||||
return (char*)strBuf.data();
|
||||
return (char*)buffer.data();
|
||||
}
|
||||
uint32_t size() const override {
|
||||
return strBuf.size();
|
||||
};
|
||||
return buffer.size();
|
||||
}
|
||||
|
||||
public:
|
||||
RtmpPacket() = default;
|
||||
RtmpPacket(const RtmpPacket &that) = delete;
|
||||
@@ -150,59 +153,64 @@ public:
|
||||
RtmpPacket &operator=(RtmpPacket &&that) = delete;
|
||||
|
||||
RtmpPacket(RtmpPacket &&that){
|
||||
typeId = that.typeId;
|
||||
bodySize = that.bodySize;
|
||||
timeStamp = that.timeStamp;
|
||||
hasAbsStamp = that.hasAbsStamp;
|
||||
hasExtStamp = that.hasExtStamp;
|
||||
deltaStamp = that.deltaStamp;
|
||||
streamId = that.streamId;
|
||||
chunkId = that.chunkId;
|
||||
strBuf = std::move(that.strBuf);
|
||||
type_id = that.type_id;
|
||||
body_size = that.body_size;
|
||||
time_stamp = that.time_stamp;
|
||||
is_abs_stamp = that.is_abs_stamp;
|
||||
ts_field = that.ts_field;
|
||||
stream_index = that.stream_index;
|
||||
chunk_id = that.chunk_id;
|
||||
buffer = std::move(that.buffer);
|
||||
}
|
||||
|
||||
bool isVideoKeyFrame() const {
|
||||
return typeId == MSG_VIDEO && (uint8_t) strBuf[0] >> 4 == FLV_KEY_FRAME && (uint8_t) strBuf[1] == 1;
|
||||
return type_id == MSG_VIDEO && (uint8_t) buffer[0] >> 4 == FLV_KEY_FRAME && (uint8_t) buffer[1] == 1;
|
||||
}
|
||||
|
||||
bool isCfgFrame() const {
|
||||
switch (typeId){
|
||||
case MSG_VIDEO : return strBuf[1] == 0;
|
||||
switch (type_id){
|
||||
case MSG_VIDEO : return buffer[1] == 0;
|
||||
case MSG_AUDIO : {
|
||||
switch (getMediaType()){
|
||||
case FLV_CODEC_AAC : return strBuf[1] == 0;
|
||||
case FLV_CODEC_AAC : return buffer[1] == 0;
|
||||
default : return false;
|
||||
}
|
||||
}
|
||||
default : return false;
|
||||
}
|
||||
}
|
||||
|
||||
int getMediaType() const {
|
||||
switch (typeId) {
|
||||
case MSG_VIDEO : return (uint8_t) strBuf[0] & 0x0F;
|
||||
case MSG_AUDIO : return (uint8_t) strBuf[0] >> 4;
|
||||
switch (type_id) {
|
||||
case MSG_VIDEO : return (uint8_t) buffer[0] & 0x0F;
|
||||
case MSG_AUDIO : return (uint8_t) buffer[0] >> 4;
|
||||
default : return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int getAudioSampleRate() const {
|
||||
if (typeId != MSG_AUDIO) {
|
||||
if (type_id != MSG_AUDIO) {
|
||||
return 0;
|
||||
}
|
||||
int flvSampleRate = ((uint8_t) strBuf[0] & 0x0C) >> 2;
|
||||
int flvSampleRate = ((uint8_t) buffer[0] & 0x0C) >> 2;
|
||||
const static int sampleRate[] = { 5512, 11025, 22050, 44100 };
|
||||
return sampleRate[flvSampleRate];
|
||||
}
|
||||
|
||||
int getAudioSampleBit() const {
|
||||
if (typeId != MSG_AUDIO) {
|
||||
if (type_id != MSG_AUDIO) {
|
||||
return 0;
|
||||
}
|
||||
int flvSampleBit = ((uint8_t) strBuf[0] & 0x02) >> 1;
|
||||
int flvSampleBit = ((uint8_t) buffer[0] & 0x02) >> 1;
|
||||
const static int sampleBit[] = { 8, 16 };
|
||||
return sampleBit[flvSampleBit];
|
||||
}
|
||||
|
||||
int getAudioChannel() const {
|
||||
if (typeId != MSG_AUDIO) {
|
||||
if (type_id != MSG_AUDIO) {
|
||||
return 0;
|
||||
}
|
||||
int flvStereoOrMono = (uint8_t) strBuf[0] & 0x01;
|
||||
int flvStereoOrMono = (uint8_t) buffer[0] & 0x01;
|
||||
const static int channel[] = { 1, 2 };
|
||||
return channel[flvStereoOrMono];
|
||||
}
|
||||
|
||||
@@ -23,14 +23,14 @@ public:
|
||||
typedef std::shared_ptr<RtmpRing> Ptr;
|
||||
typedef RingBuffer<RtmpPacket::Ptr> RingType;
|
||||
|
||||
RtmpRing(){}
|
||||
virtual ~RtmpRing(){}
|
||||
RtmpRing() {}
|
||||
virtual ~RtmpRing() {}
|
||||
|
||||
/**
|
||||
* 获取rtmp环形缓存
|
||||
* @return
|
||||
*/
|
||||
virtual RingType::Ptr getRtmpRing() const{
|
||||
virtual RingType::Ptr getRtmpRing() const {
|
||||
return _rtmpRing;
|
||||
}
|
||||
|
||||
@@ -38,27 +38,24 @@ public:
|
||||
* 设置rtmp环形缓存
|
||||
* @param ring
|
||||
*/
|
||||
virtual void setRtmpRing(const RingType::Ptr &ring){
|
||||
virtual void setRtmpRing(const RingType::Ptr &ring) {
|
||||
_rtmpRing = ring;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入rtmp包
|
||||
* @param rtmp rtmp包
|
||||
* @param key_pos 是否为关键帧
|
||||
* @return 是否为关键帧
|
||||
*/
|
||||
virtual bool inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos){
|
||||
if(_rtmpRing){
|
||||
_rtmpRing->write(rtmp,key_pos);
|
||||
virtual void inputRtmp(const RtmpPacket::Ptr &rtmp) {
|
||||
if (_rtmpRing) {
|
||||
_rtmpRing->write(rtmp, rtmp->isVideoKeyFrame());
|
||||
}
|
||||
return key_pos;
|
||||
}
|
||||
|
||||
protected:
|
||||
RingType::Ptr _rtmpRing;
|
||||
};
|
||||
|
||||
|
||||
class RtmpCodec : public RtmpRing, public FrameDispatcher , public CodecInfo{
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpCodec> Ptr;
|
||||
@@ -69,5 +66,4 @@ public:
|
||||
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#endif //ZLMEDIAKIT_RTMPCODEC_H
|
||||
|
||||
@@ -65,34 +65,32 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
switch (pkt->typeId) {
|
||||
void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
switch (pkt->type_id) {
|
||||
case MSG_VIDEO: {
|
||||
if(!_tryedGetVideoTrack){
|
||||
_tryedGetVideoTrack = true;
|
||||
if (!_try_get_video_track) {
|
||||
_try_get_video_track = true;
|
||||
auto codec = AMFValue(pkt->getMediaType());
|
||||
makeVideoTrack(codec);
|
||||
}
|
||||
if(_videoRtmpDecoder){
|
||||
return _videoRtmpDecoder->inputRtmp(pkt, true);
|
||||
if (_video_rtmp_decoder) {
|
||||
_video_rtmp_decoder->inputRtmp(pkt);
|
||||
}
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
case MSG_AUDIO: {
|
||||
if(!_tryedGetAudioTrack) {
|
||||
_tryedGetAudioTrack = true;
|
||||
if (!_try_get_audio_track) {
|
||||
_try_get_audio_track = true;
|
||||
auto codec = AMFValue(pkt->getMediaType());
|
||||
makeAudioTrack(codec, pkt->getAudioSampleRate(), pkt->getAudioChannel(), pkt->getAudioSampleBit());
|
||||
}
|
||||
if(_audioRtmpDecoder){
|
||||
_audioRtmpDecoder->inputRtmp(pkt, false);
|
||||
return false;
|
||||
if (_audio_rtmp_decoder) {
|
||||
_audio_rtmp_decoder->inputRtmp(pkt);
|
||||
}
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
default : break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,12 +99,12 @@ void RtmpDemuxer::makeVideoTrack(const AMFValue &videoCodec) {
|
||||
_videoTrack = dynamic_pointer_cast<VideoTrack>(Factory::getVideoTrackByAmf(videoCodec));
|
||||
if (_videoTrack) {
|
||||
//生成rtmpCodec对象以便解码rtmp
|
||||
_videoRtmpDecoder = Factory::getRtmpCodecByTrack(_videoTrack, false);
|
||||
if (_videoRtmpDecoder) {
|
||||
_video_rtmp_decoder = Factory::getRtmpCodecByTrack(_videoTrack, false);
|
||||
if (_video_rtmp_decoder) {
|
||||
//设置rtmp解码器代理,生成的frame写入该Track
|
||||
_videoRtmpDecoder->addDelegate(_videoTrack);
|
||||
_video_rtmp_decoder->addDelegate(_videoTrack);
|
||||
onAddTrack(_videoTrack);
|
||||
_tryedGetVideoTrack = true;
|
||||
_try_get_video_track = true;
|
||||
} else {
|
||||
//找不到相应的rtmp解码器,该track无效
|
||||
_videoTrack.reset();
|
||||
@@ -119,12 +117,12 @@ void RtmpDemuxer::makeAudioTrack(const AMFValue &audioCodec,int sample_rate, int
|
||||
_audioTrack = dynamic_pointer_cast<AudioTrack>(Factory::getAudioTrackByAmf(audioCodec, sample_rate, channels, sample_bit));
|
||||
if (_audioTrack) {
|
||||
//生成rtmpCodec对象以便解码rtmp
|
||||
_audioRtmpDecoder = Factory::getRtmpCodecByTrack(_audioTrack, false);
|
||||
if (_audioRtmpDecoder) {
|
||||
_audio_rtmp_decoder = Factory::getRtmpCodecByTrack(_audioTrack, false);
|
||||
if (_audio_rtmp_decoder) {
|
||||
//设置rtmp解码器代理,生成的frame写入该Track
|
||||
_audioRtmpDecoder->addDelegate(_audioTrack);
|
||||
_audio_rtmp_decoder->addDelegate(_audioTrack);
|
||||
onAddTrack(_audioTrack);
|
||||
_tryedGetAudioTrack = true;
|
||||
_try_get_audio_track = true;
|
||||
} else {
|
||||
//找不到相应的rtmp解码器,该track无效
|
||||
_audioTrack.reset();
|
||||
|
||||
@@ -28,24 +28,25 @@ public:
|
||||
typedef std::shared_ptr<RtmpDemuxer> Ptr;
|
||||
|
||||
RtmpDemuxer() = default;
|
||||
virtual ~RtmpDemuxer() = default;
|
||||
~RtmpDemuxer() override = default;
|
||||
|
||||
bool loadMetaData(const AMFValue &metadata);
|
||||
|
||||
/**
|
||||
* 开始解复用
|
||||
* @param pkt rtmp包
|
||||
* @return true 代表是i帧
|
||||
*/
|
||||
bool inputRtmp(const RtmpPacket::Ptr &pkt);
|
||||
void inputRtmp(const RtmpPacket::Ptr &pkt);
|
||||
|
||||
private:
|
||||
void makeVideoTrack(const AMFValue &val);
|
||||
void makeAudioTrack(const AMFValue &val, int sample_rate, int channels, int sample_bit);
|
||||
|
||||
private:
|
||||
bool _tryedGetVideoTrack = false;
|
||||
bool _tryedGetAudioTrack = false;
|
||||
RtmpCodec::Ptr _audioRtmpDecoder;
|
||||
RtmpCodec::Ptr _videoRtmpDecoder;
|
||||
bool _try_get_video_track = false;
|
||||
bool _try_get_audio_track = false;
|
||||
RtmpCodec::Ptr _audio_rtmp_decoder;
|
||||
RtmpCodec::Ptr _video_rtmp_decoder;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
@@ -60,7 +60,7 @@ public:
|
||||
MediaSource(RTMP_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {
|
||||
}
|
||||
|
||||
virtual ~RtmpMediaSource() {}
|
||||
~RtmpMediaSource() override{}
|
||||
|
||||
/**
|
||||
* 获取媒体源的环形缓冲
|
||||
@@ -100,9 +100,8 @@ public:
|
||||
* 设置metadata
|
||||
*/
|
||||
virtual void setMetaData(const AMFValue &metadata) {
|
||||
lock_guard<recursive_mutex> lock(_mtx);
|
||||
_metadata = metadata;
|
||||
if(_ring){
|
||||
if (_ring) {
|
||||
regist();
|
||||
}
|
||||
}
|
||||
@@ -118,25 +117,24 @@ public:
|
||||
/**
|
||||
* 输入rtmp包
|
||||
* @param pkt rtmp包
|
||||
* @param key 是否为关键帧
|
||||
*/
|
||||
void onWrite(const RtmpPacket::Ptr &pkt, bool key = true) override {
|
||||
lock_guard<recursive_mutex> lock(_mtx);
|
||||
if(pkt->typeId == MSG_VIDEO){
|
||||
//有视频,那么启用GOP缓存
|
||||
_have_video = true;
|
||||
void onWrite(const RtmpPacket::Ptr &pkt, bool = true) override {
|
||||
//保存当前时间戳
|
||||
switch (pkt->type_id) {
|
||||
case MSG_VIDEO : _track_stamps[TrackVideo] = pkt->time_stamp, _have_video = true; break;
|
||||
case MSG_AUDIO : _track_stamps[TrackAudio] = pkt->time_stamp; break;
|
||||
default : break;
|
||||
}
|
||||
|
||||
if (pkt->isCfgFrame()) {
|
||||
_config_frame_map[pkt->typeId] = pkt;
|
||||
lock_guard<recursive_mutex> lock(_mtx);
|
||||
_config_frame_map[pkt->type_id] = pkt;
|
||||
return;
|
||||
}
|
||||
|
||||
//保存当前时间戳
|
||||
_track_stamps_map[pkt->typeId] = pkt->timeStamp;
|
||||
|
||||
if (!_ring) {
|
||||
weak_ptr<RtmpMediaSource> weakSelf = dynamic_pointer_cast<RtmpMediaSource>(shared_from_this());
|
||||
auto lam = [weakSelf](const EventPoller::Ptr &, int size, bool) {
|
||||
auto lam = [weakSelf](int size) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
@@ -144,9 +142,8 @@ public:
|
||||
strongSelf->onReaderChanged(size);
|
||||
};
|
||||
|
||||
//rtmp包缓存最大允许512个,如果是纯视频(25fps)大概为20秒数据
|
||||
//但是这个是GOP缓存的上限值,真实的GOP缓存大小等于两个I帧之间的包数的两倍
|
||||
//而且每次遇到I帧,则会清空GOP缓存,所以真实的GOP缓存远小于此值
|
||||
//GOP默认缓冲512组RTMP包,每组RTMP包时间戳相同(如果开启合并写了,那么每组为合并写时间内的RTMP包),
|
||||
//每次遇到关键帧第一个RTMP包,则会清空GOP缓存(因为有新的关键帧了,同样可以实现秒开)
|
||||
_ring = std::make_shared<RingType>(_ring_size,std::move(lam));
|
||||
onReaderChanged(0);
|
||||
|
||||
@@ -154,26 +151,35 @@ public:
|
||||
regist();
|
||||
}
|
||||
}
|
||||
PacketCache<RtmpPacket>::inputPacket(pkt->typeId == MSG_VIDEO, pkt, key);
|
||||
PacketCache<RtmpPacket>::inputPacket(pkt->type_id == MSG_VIDEO, pkt, pkt->isVideoKeyFrame());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间戳
|
||||
*/
|
||||
uint32_t getTimeStamp(TrackType trackType) override {
|
||||
lock_guard<recursive_mutex> lock(_mtx);
|
||||
switch (trackType) {
|
||||
case TrackVideo:
|
||||
return _track_stamps_map[MSG_VIDEO];
|
||||
case TrackAudio:
|
||||
return _track_stamps_map[MSG_AUDIO];
|
||||
default:
|
||||
return MAX(_track_stamps_map[MSG_VIDEO], _track_stamps_map[MSG_AUDIO]);
|
||||
assert(trackType >= TrackInvalid && trackType < TrackMax);
|
||||
if (trackType != TrackInvalid) {
|
||||
//获取某track的时间戳
|
||||
return _track_stamps[trackType];
|
||||
}
|
||||
|
||||
//获取所有track的最小时间戳
|
||||
uint32_t ret = UINT32_MAX;
|
||||
for (auto &stamp : _track_stamps) {
|
||||
if (stamp > 0 && stamp < ret) {
|
||||
ret = stamp;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void clearCache() override{
|
||||
PacketCache<RtmpPacket>::clearCache();
|
||||
_ring->clearCache();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* 批量flush rtmp包时触发该函数
|
||||
* @param rtmp_list rtmp包列表
|
||||
@@ -184,22 +190,14 @@ private:
|
||||
_ring->write(rtmp_list, _have_video ? key_pos : true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 每次增减消费者都会触发该函数
|
||||
*/
|
||||
void onReaderChanged(int size) {
|
||||
if (size == 0) {
|
||||
onNoneReader();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int _ring_size;
|
||||
bool _have_video = false;
|
||||
mutable recursive_mutex _mtx;
|
||||
int _ring_size;
|
||||
uint32_t _track_stamps[TrackMax] = {0};
|
||||
AMFValue _metadata;
|
||||
RingType::Ptr _ring;
|
||||
unordered_map<int, uint32_t> _track_stamps_map;
|
||||
|
||||
mutable recursive_mutex _mtx;
|
||||
unordered_map<int, RtmpPacket::Ptr> _config_frame_map;
|
||||
};
|
||||
|
||||
|
||||
@@ -60,27 +60,12 @@ public:
|
||||
/**
|
||||
* 输入rtmp并解析
|
||||
*/
|
||||
void onWrite(const RtmpPacket::Ptr &pkt,bool key_pos = true) override {
|
||||
if(_all_track_ready && !_muxer->isEnabled()){
|
||||
//获取到所有Track后,并且未开启转协议,那么不需要解复用rtmp
|
||||
key_pos = pkt->isVideoKeyFrame();
|
||||
}else{
|
||||
//需要解复用rtmp
|
||||
key_pos = _demuxer->inputRtmp(pkt);
|
||||
}
|
||||
|
||||
RtmpMediaSource::onWrite(pkt,key_pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置监听器
|
||||
* @param listener
|
||||
*/
|
||||
void setListener(const std::weak_ptr<MediaSourceEvent> &listener) override {
|
||||
RtmpMediaSource::setListener(listener);
|
||||
if(_muxer){
|
||||
_muxer->setMediaListener(listener);
|
||||
void onWrite(const RtmpPacket::Ptr &pkt, bool = true) override {
|
||||
if (!_all_track_ready || _muxer->isEnabled()) {
|
||||
//未获取到所有Track后,或者开启转协议,那么需要解复用rtmp
|
||||
_demuxer->inputRtmp(pkt);
|
||||
}
|
||||
RtmpMediaSource::onWrite(pkt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,44 +75,19 @@ public:
|
||||
return readerCount() + (_muxer ? _muxer->totalReaderCount() : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置录制状态
|
||||
* @param type 录制类型
|
||||
* @param start 开始或停止
|
||||
* @param custom_path 开启录制时,指定自定义路径
|
||||
* @return 是否设置成功
|
||||
*/
|
||||
bool setupRecord(Recorder::type type, bool start, const string &custom_path) override{
|
||||
if(_muxer){
|
||||
return _muxer->setupRecord(*this,type, start, custom_path);
|
||||
}
|
||||
return RtmpMediaSource::setupRecord(type, start, custom_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取录制状态
|
||||
* @param type 录制类型
|
||||
* @return 录制状态
|
||||
*/
|
||||
bool isRecording(Recorder::type type) override{
|
||||
if(_muxer){
|
||||
return _muxer->isRecording(*this,type);
|
||||
}
|
||||
return RtmpMediaSource::isRecording(type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置协议转换
|
||||
* @param enableRtsp 是否转换成rtsp
|
||||
* @param enableHls 是否转换成hls
|
||||
* @param enableMP4 是否mp4录制
|
||||
*/
|
||||
void setProtocolTranslation(bool enableRtsp, bool enableHls, bool enableMP4) {
|
||||
void setProtocolTranslation(bool enableHls, bool enableMP4) {
|
||||
//不重复生成rtmp
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(getVhost(), getApp(), getId(), _demuxer->getDuration(), enableRtsp, false, enableHls, enableMP4);
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(getVhost(), getApp(), getId(), _demuxer->getDuration(), true, false, enableHls, enableMP4);
|
||||
_muxer->setMediaListener(getListener());
|
||||
_muxer->setTrackListener(this);
|
||||
_muxer->setTrackListener(static_pointer_cast<RtmpMediaSourceImp>(shared_from_this()));
|
||||
//让_muxer对象拦截一部分事件(比如说录像相关事件)
|
||||
setListener(_muxer);
|
||||
|
||||
for(auto &track : _demuxer->getTracks(false)){
|
||||
_muxer->addTrack(track);
|
||||
track->addDelegate(_muxer);
|
||||
@@ -148,12 +108,11 @@ public:
|
||||
* _muxer触发的所有Track就绪的事件
|
||||
*/
|
||||
void onAllTrackReady() override{
|
||||
setTrackSource(_muxer);
|
||||
_all_track_ready = true;
|
||||
|
||||
if (_recreate_metadata) {
|
||||
//更新metadata
|
||||
for (auto &track : _muxer->getTracks()) {
|
||||
for (auto &track : _muxer->getTracks(*this)) {
|
||||
Metadata::addTrack(_metadata, track);
|
||||
}
|
||||
RtmpMediaSource::updateMetaData(_metadata);
|
||||
@@ -161,11 +120,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
RtmpDemuxer::Ptr _demuxer;
|
||||
MultiMediaSourceMuxer::Ptr _muxer;
|
||||
AMFValue _metadata;
|
||||
bool _all_track_ready = false;
|
||||
bool _recreate_metadata = false;
|
||||
AMFValue _metadata;
|
||||
RtmpDemuxer::Ptr _demuxer;
|
||||
MultiMediaSourceMuxer::Ptr _muxer;
|
||||
|
||||
};
|
||||
} /* namespace mediakit */
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class RtmpMediaSourceMuxer : public RtmpMuxer {
|
||||
class RtmpMediaSourceMuxer : public RtmpMuxer, public MediaSourceEventInterceptor,
|
||||
public std::enable_shared_from_this<RtmpMediaSourceMuxer> {
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpMediaSourceMuxer> Ptr;
|
||||
|
||||
@@ -24,34 +25,57 @@ public:
|
||||
const string &strApp,
|
||||
const string &strId,
|
||||
const TitleMeta::Ptr &title = nullptr) : RtmpMuxer(title){
|
||||
_mediaSouce = std::make_shared<RtmpMediaSource>(vhost,strApp,strId);
|
||||
getRtmpRing()->setDelegate(_mediaSouce);
|
||||
_media_src = std::make_shared<RtmpMediaSource>(vhost, strApp, strId);
|
||||
getRtmpRing()->setDelegate(_media_src);
|
||||
}
|
||||
virtual ~RtmpMediaSourceMuxer(){}
|
||||
|
||||
~RtmpMediaSourceMuxer() override{}
|
||||
|
||||
void setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
||||
_mediaSouce->setListener(listener);
|
||||
_listener = listener;
|
||||
_media_src->setListener(shared_from_this());
|
||||
}
|
||||
|
||||
void setTimeStamp(uint32_t stamp){
|
||||
_mediaSouce->setTimeStamp(stamp);
|
||||
_media_src->setTimeStamp(stamp);
|
||||
}
|
||||
|
||||
int readerCount() const{
|
||||
return _mediaSouce->readerCount();
|
||||
return _media_src->readerCount();
|
||||
}
|
||||
|
||||
void onAllTrackReady(){
|
||||
makeConfigPacket();
|
||||
_mediaSouce->setMetaData(getMetadata());
|
||||
_media_src->setMetaData(getMetadata());
|
||||
}
|
||||
|
||||
// 设置TrackSource
|
||||
void setTrackSource(const std::weak_ptr<TrackSource> &track_src){
|
||||
_mediaSouce->setTrackSource(track_src);
|
||||
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) {
|
||||
RtmpMuxer::inputFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
//缓存尚未清空时,还允许触发inputFrame函数,以便及时清空缓存
|
||||
return _clear_cache ? true : _enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
RtmpMediaSource::Ptr _mediaSouce;
|
||||
bool _enabled = true;
|
||||
bool _clear_cache = false;
|
||||
RtmpMediaSource::Ptr _media_src;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ RtmpMuxer::RtmpMuxer(const TitleMeta::Ptr &title) {
|
||||
}else{
|
||||
_metadata = title->getMetadata();
|
||||
}
|
||||
_rtmpRing = std::make_shared<RtmpRing::RingType>();
|
||||
_rtmp_ring = std::make_shared<RtmpRing::RingType>();
|
||||
}
|
||||
|
||||
void RtmpMuxer::addTrack(const Track::Ptr &track) {
|
||||
@@ -31,7 +31,7 @@ void RtmpMuxer::addTrack(const Track::Ptr &track) {
|
||||
}
|
||||
|
||||
//设置rtmp输出环形缓存
|
||||
encoder->setRtmpRing(_rtmpRing);
|
||||
encoder->setRtmpRing(_rtmp_ring);
|
||||
|
||||
//添加metadata
|
||||
Metadata::addTrack(_metadata,track);
|
||||
@@ -57,7 +57,7 @@ const AMFValue &RtmpMuxer::getMetadata() const {
|
||||
}
|
||||
|
||||
RtmpRing::RingType::Ptr RtmpMuxer::getRtmpRing() const {
|
||||
return _rtmpRing;
|
||||
return _rtmp_ring;
|
||||
}
|
||||
|
||||
void RtmpMuxer::resetTracks() {
|
||||
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
*/
|
||||
void makeConfigPacket();
|
||||
private:
|
||||
RtmpRing::RingType::Ptr _rtmpRing;
|
||||
RtmpRing::RingType::Ptr _rtmp_ring;
|
||||
AMFValue _metadata;
|
||||
RtmpCodec::Ptr _encoder[TrackMax];
|
||||
};
|
||||
|
||||
@@ -18,8 +18,7 @@ using namespace mediakit::Client;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
RtmpPlayer::RtmpPlayer(const EventPoller::Ptr &poller) : TcpClient(poller) {
|
||||
}
|
||||
RtmpPlayer::RtmpPlayer(const EventPoller::Ptr &poller) : TcpClient(poller) {}
|
||||
|
||||
RtmpPlayer::~RtmpPlayer() {
|
||||
DebugL << endl;
|
||||
@@ -29,97 +28,82 @@ void RtmpPlayer::teardown() {
|
||||
if (alive()) {
|
||||
shutdown(SockException(Err_shutdown,"teardown"));
|
||||
}
|
||||
_strApp.clear();
|
||||
_strStream.clear();
|
||||
_strTcUrl.clear();
|
||||
_pBeatTimer.reset();
|
||||
_pPlayTimer.reset();
|
||||
_pMediaTimer.reset();
|
||||
_iSeekTo = 0;
|
||||
_app.clear();
|
||||
_stream_id.clear();
|
||||
_tc_url.clear();
|
||||
_beat_timer.reset();
|
||||
_play_timer.reset();
|
||||
_rtmp_recv_timer.reset();
|
||||
_seek_ms = 0;
|
||||
RtmpProtocol::reset();
|
||||
|
||||
CLEAR_ARR(_aiFistStamp);
|
||||
CLEAR_ARR(_aiNowStamp);
|
||||
CLEAR_ARR(_fist_stamp);
|
||||
CLEAR_ARR(_now_stamp);
|
||||
|
||||
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
||||
_mapOnResultCB.clear();
|
||||
lock_guard<recursive_mutex> lck2(_mtxOnStatusCB);
|
||||
_dqOnStatusCB.clear();
|
||||
lock_guard<recursive_mutex> lck(_mtx_on_result);
|
||||
_map_on_result.clear();
|
||||
lock_guard<recursive_mutex> lck2(_mtx_on_status);
|
||||
_deque_on_status.clear();
|
||||
}
|
||||
|
||||
void RtmpPlayer::play(const string &strUrl) {
|
||||
teardown();
|
||||
string strHost = FindField(strUrl.data(), "://", "/");
|
||||
_strApp = FindField(strUrl.data(), (strHost + "/").data(), "/");
|
||||
_strStream = FindField(strUrl.data(), (strHost + "/" + _strApp + "/").data(), NULL);
|
||||
_strTcUrl = string("rtmp://") + strHost + "/" + _strApp;
|
||||
string host_url = FindField(strUrl.data(), "://", "/");
|
||||
_app = FindField(strUrl.data(), (host_url + "/").data(), "/");
|
||||
_stream_id = FindField(strUrl.data(), (host_url + "/" + _app + "/").data(), NULL);
|
||||
_tc_url = string("rtmp://") + host_url + "/" + _app;
|
||||
|
||||
if (!_strApp.size() || !_strStream.size()) {
|
||||
onPlayResult_l(SockException(Err_other,"rtmp url非法"),false);
|
||||
if (!_app.size() || !_stream_id.size()) {
|
||||
onPlayResult_l(SockException(Err_other, "rtmp url非法"), false);
|
||||
return;
|
||||
}
|
||||
DebugL << strHost << " " << _strApp << " " << _strStream;
|
||||
DebugL << host_url << " " << _app << " " << _stream_id;
|
||||
|
||||
auto iPort = atoi(FindField(strHost.data(), ":", NULL).data());
|
||||
auto iPort = atoi(FindField(host_url.data(), ":", NULL).data());
|
||||
if (iPort <= 0) {
|
||||
//rtmp 默认端口1935
|
||||
iPort = 1935;
|
||||
} else {
|
||||
//服务器域名
|
||||
strHost = FindField(strHost.data(), NULL, ":");
|
||||
host_url = FindField(host_url.data(), NULL, ":");
|
||||
}
|
||||
if(!(*this)[kNetAdapter].empty()){
|
||||
if (!(*this)[kNetAdapter].empty()) {
|
||||
setNetAdapter((*this)[kNetAdapter]);
|
||||
}
|
||||
|
||||
weak_ptr<RtmpPlayer> weakSelf= dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
float playTimeOutSec = (*this)[kTimeoutMS].as<int>() / 1000.0;
|
||||
_pPlayTimer.reset( new Timer(playTimeOutSec, [weakSelf]() {
|
||||
auto strongSelf=weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
weak_ptr<RtmpPlayer> weak_self = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
float play_timeout_sec = (*this)[kTimeoutMS].as<int>() / 1000.0;
|
||||
_play_timer.reset(new Timer(play_timeout_sec, [weak_self]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return false;
|
||||
}
|
||||
strongSelf->onPlayResult_l(SockException(Err_timeout,"play rtmp timeout"),false);
|
||||
strong_self->onPlayResult_l(SockException(Err_timeout, "play rtmp timeout"), false);
|
||||
return false;
|
||||
},getPoller()));
|
||||
}, getPoller()));
|
||||
|
||||
_metadata_got = false;
|
||||
startConnect(strHost, iPort , playTimeOutSec);
|
||||
startConnect(host_url, iPort, play_timeout_sec);
|
||||
}
|
||||
|
||||
void RtmpPlayer::onErr(const SockException &ex){
|
||||
//定时器_pPlayTimer为空后表明握手结束了
|
||||
onPlayResult_l(ex, !_pPlayTimer);
|
||||
onPlayResult_l(ex, !_play_timer);
|
||||
}
|
||||
|
||||
void RtmpPlayer::onPlayResult_l(const SockException &ex , bool handshakeCompleted) {
|
||||
WarnL << ex.getErrCode() << " " << ex.what();
|
||||
|
||||
if(!ex){
|
||||
//播放成功,恢复rtmp接收超时定时器
|
||||
_mediaTicker.resetTime();
|
||||
weak_ptr<RtmpPlayer> weakSelf = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
int timeoutMS = (*this)[kMediaTimeoutMS].as<int>();
|
||||
//创建rtmp数据接收超时检测定时器
|
||||
_pMediaTimer.reset( new Timer(timeoutMS / 2000.0, [weakSelf,timeoutMS]() {
|
||||
auto strongSelf=weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
return false;
|
||||
}
|
||||
if(strongSelf->_mediaTicker.elapsedTime()> timeoutMS) {
|
||||
//接收rtmp媒体数据超时
|
||||
strongSelf->onPlayResult_l(SockException(Err_timeout,"receive rtmp timeout"),true);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},getPoller()));
|
||||
void RtmpPlayer::onPlayResult_l(const SockException &ex, bool handshake_done) {
|
||||
if (ex.getErrCode() == Err_shutdown) {
|
||||
//主动shutdown的,不触发回调
|
||||
return;
|
||||
}
|
||||
|
||||
if (!handshakeCompleted) {
|
||||
WarnL << ex.getErrCode() << " " << ex.what();
|
||||
if (!handshake_done) {
|
||||
//开始播放阶段
|
||||
_pPlayTimer.reset();
|
||||
onPlayResult(ex);
|
||||
_play_timer.reset();
|
||||
//是否为性能测试模式
|
||||
_benchmark_mode = (*this)[Client::kBenchmarkMode].as<int>();
|
||||
onPlayResult(ex);
|
||||
} else if (ex) {
|
||||
//播放成功后异常断开回调
|
||||
onShutdown(ex);
|
||||
@@ -128,36 +112,58 @@ void RtmpPlayer::onPlayResult_l(const SockException &ex , bool handshakeComplete
|
||||
onResume();
|
||||
}
|
||||
|
||||
if(ex){
|
||||
if (!ex) {
|
||||
//播放成功,恢复rtmp接收超时定时器
|
||||
_rtmp_recv_ticker.resetTime();
|
||||
int timeout_ms = (*this)[kMediaTimeoutMS].as<int>();
|
||||
weak_ptr<RtmpPlayer> weakSelf = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
auto lam = [weakSelf, timeout_ms]() {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
return false;
|
||||
}
|
||||
if (strongSelf->_rtmp_recv_ticker.elapsedTime() > timeout_ms) {
|
||||
//接收rtmp媒体数据超时
|
||||
SockException ex(Err_timeout, "receive rtmp timeout");
|
||||
strongSelf->onPlayResult_l(ex, true);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
//创建rtmp数据接收超时检测定时器
|
||||
_rtmp_recv_timer = std::make_shared<Timer>(timeout_ms / 2000.0, lam, getPoller());
|
||||
} else {
|
||||
teardown();
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpPlayer::onConnect(const SockException &err){
|
||||
if(err.getErrCode() != Err_success) {
|
||||
if (err.getErrCode() != Err_success) {
|
||||
onPlayResult_l(err, false);
|
||||
return;
|
||||
}
|
||||
weak_ptr<RtmpPlayer> weakSelf= dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
startClientSession([weakSelf](){
|
||||
auto strongSelf=weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
weak_ptr<RtmpPlayer> weakSelf = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
startClientSession([weakSelf]() {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
strongSelf->send_connect();
|
||||
});
|
||||
}
|
||||
void RtmpPlayer::onRecv(const Buffer::Ptr &pBuf){
|
||||
|
||||
void RtmpPlayer::onRecv(const Buffer::Ptr &buf){
|
||||
try {
|
||||
if(_benchmark_mode && !_pPlayTimer){
|
||||
if (_benchmark_mode && !_play_timer) {
|
||||
//在性能测试模式下,如果rtmp握手完毕后,不再解析rtmp包
|
||||
_mediaTicker.resetTime();
|
||||
_rtmp_recv_ticker.resetTime();
|
||||
return;
|
||||
}
|
||||
onParseRtmp(pBuf->data(), pBuf->size());
|
||||
onParseRtmp(buf->data(), buf->size());
|
||||
} catch (exception &e) {
|
||||
SockException ex(Err_other, e.what());
|
||||
//定时器_pPlayTimer为空后表明握手结束了
|
||||
onPlayResult_l(ex, !_pPlayTimer);
|
||||
onPlayResult_l(ex, !_play_timer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,8 +173,8 @@ void RtmpPlayer::pause(bool bPause) {
|
||||
|
||||
inline void RtmpPlayer::send_connect() {
|
||||
AMFValue obj(AMF_OBJECT);
|
||||
obj.set("app", _strApp);
|
||||
obj.set("tcUrl", _strTcUrl);
|
||||
obj.set("app", _app);
|
||||
obj.set("tcUrl", _tc_url);
|
||||
//未使用代理
|
||||
obj.set("fpad", false);
|
||||
//参考librtmp,什么作用?
|
||||
@@ -176,18 +182,18 @@ inline void RtmpPlayer::send_connect() {
|
||||
//SUPPORT_VID_CLIENT_SEEK 支持seek
|
||||
obj.set("videoFunction", 1);
|
||||
//只支持aac
|
||||
obj.set("audioCodecs", (double)(0x0400));
|
||||
obj.set("audioCodecs", (double) (0x0400));
|
||||
//只支持H264
|
||||
obj.set("videoCodecs", (double)(0x0080));
|
||||
obj.set("videoCodecs", (double) (0x0080));
|
||||
sendInvoke("connect", obj);
|
||||
addOnResultCB([this](AMFDecoder &dec){
|
||||
addOnResultCB([this](AMFDecoder &dec) {
|
||||
//TraceL << "connect result";
|
||||
dec.load<AMFValue>();
|
||||
auto val = dec.load<AMFValue>();
|
||||
auto level = val["level"].as_string();
|
||||
auto code = val["code"].as_string();
|
||||
if(level != "status"){
|
||||
throw std::runtime_error(StrPrinter <<"connect 失败:" << level << " " << code << endl);
|
||||
if (level != "status") {
|
||||
throw std::runtime_error(StrPrinter << "connect 失败:" << level << " " << code << endl);
|
||||
}
|
||||
send_createStream();
|
||||
});
|
||||
@@ -196,102 +202,103 @@ inline void RtmpPlayer::send_connect() {
|
||||
inline void RtmpPlayer::send_createStream() {
|
||||
AMFValue obj(AMF_NULL);
|
||||
sendInvoke("createStream", obj);
|
||||
addOnResultCB([this](AMFDecoder &dec){
|
||||
addOnResultCB([this](AMFDecoder &dec) {
|
||||
//TraceL << "createStream result";
|
||||
dec.load<AMFValue>();
|
||||
_ui32StreamId = dec.load<int>();
|
||||
_stream_index = dec.load<int>();
|
||||
send_play();
|
||||
});
|
||||
}
|
||||
|
||||
inline void RtmpPlayer::send_play() {
|
||||
AMFEncoder enc;
|
||||
enc << "play" << ++_iReqID << nullptr << _strStream << (double)_ui32StreamId;
|
||||
enc << "play" << ++_send_req_id << nullptr << _stream_id << (double) _stream_index;
|
||||
sendRequest(MSG_CMD, enc.data());
|
||||
auto fun = [this](AMFValue &val){
|
||||
auto fun = [this](AMFValue &val) {
|
||||
//TraceL << "play onStatus";
|
||||
auto level = val["level"].as_string();
|
||||
auto code = val["code"].as_string();
|
||||
if(level != "status"){
|
||||
throw std::runtime_error(StrPrinter <<"play 失败:" << level << " " << code << endl);
|
||||
if (level != "status") {
|
||||
throw std::runtime_error(StrPrinter << "play 失败:" << level << " " << code << endl);
|
||||
}
|
||||
};
|
||||
addOnStatusCB(fun);
|
||||
addOnStatusCB(fun);
|
||||
}
|
||||
|
||||
inline void RtmpPlayer::send_pause(bool bPause) {
|
||||
inline void RtmpPlayer::send_pause(bool pause) {
|
||||
AMFEncoder enc;
|
||||
enc << "pause" << ++_iReqID << nullptr << bPause;
|
||||
enc << "pause" << ++_send_req_id << nullptr << pause;
|
||||
sendRequest(MSG_CMD, enc.data());
|
||||
auto fun = [this,bPause](AMFValue &val){
|
||||
auto fun = [this, pause](AMFValue &val) {
|
||||
//TraceL << "pause onStatus";
|
||||
auto level = val["level"].as_string();
|
||||
auto code = val["code"].as_string();
|
||||
if(level != "status") {
|
||||
if(!bPause){
|
||||
throw std::runtime_error(StrPrinter <<"pause 恢复播放失败:" << level << " " << code << endl);
|
||||
if (level != "status") {
|
||||
if (!pause) {
|
||||
throw std::runtime_error(StrPrinter << "pause 恢复播放失败:" << level << " " << code << endl);
|
||||
}
|
||||
}else{
|
||||
_bPaused = bPause;
|
||||
if(!bPause){
|
||||
} else {
|
||||
_paused = pause;
|
||||
if (!pause) {
|
||||
onPlayResult_l(SockException(Err_success, "resum rtmp success"), true);
|
||||
}else{
|
||||
} else {
|
||||
//暂停播放
|
||||
_pMediaTimer.reset();
|
||||
_rtmp_recv_timer.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
addOnStatusCB(fun);
|
||||
|
||||
_pBeatTimer.reset();
|
||||
if(bPause){
|
||||
_beat_timer.reset();
|
||||
if (pause) {
|
||||
weak_ptr<RtmpPlayer> weakSelf = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
_pBeatTimer.reset(new Timer((*this)[kBeatIntervalMS].as<int>() / 1000.0,[weakSelf](){
|
||||
_beat_timer.reset(new Timer((*this)[kBeatIntervalMS].as<int>() / 1000.0, [weakSelf]() {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf){
|
||||
if (!strongSelf) {
|
||||
return false;
|
||||
}
|
||||
uint32_t timeStamp = ::time(NULL);
|
||||
strongSelf->sendUserControl(CONTROL_PING_REQUEST, timeStamp);
|
||||
return true;
|
||||
},getPoller()));
|
||||
}, getPoller()));
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpPlayer::onCmd_result(AMFDecoder &dec){
|
||||
auto iReqId = dec.load<int>();
|
||||
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
||||
auto it = _mapOnResultCB.find(iReqId);
|
||||
if(it != _mapOnResultCB.end()){
|
||||
auto req_id = dec.load<int>();
|
||||
lock_guard<recursive_mutex> lck(_mtx_on_result);
|
||||
auto it = _map_on_result.find(req_id);
|
||||
if (it != _map_on_result.end()) {
|
||||
it->second(dec);
|
||||
_mapOnResultCB.erase(it);
|
||||
}else{
|
||||
_map_on_result.erase(it);
|
||||
} else {
|
||||
WarnL << "unhandled _result";
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpPlayer::onCmd_onStatus(AMFDecoder &dec) {
|
||||
AMFValue val;
|
||||
while(true){
|
||||
while (true) {
|
||||
val = dec.load<AMFValue>();
|
||||
if(val.type() == AMF_OBJECT){
|
||||
if (val.type() == AMF_OBJECT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(val.type() != AMF_OBJECT){
|
||||
if (val.type() != AMF_OBJECT) {
|
||||
throw std::runtime_error("onStatus:the result object was not found");
|
||||
}
|
||||
|
||||
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
||||
if(_dqOnStatusCB.size()){
|
||||
_dqOnStatusCB.front()(val);
|
||||
_dqOnStatusCB.pop_front();
|
||||
}else{
|
||||
|
||||
lock_guard<recursive_mutex> lck(_mtx_on_status);
|
||||
if (_deque_on_status.size()) {
|
||||
_deque_on_status.front()(val);
|
||||
_deque_on_status.pop_front();
|
||||
} else {
|
||||
auto level = val["level"];
|
||||
auto code = val["code"].as_string();
|
||||
if(level.type() == AMF_STRING){
|
||||
if(level.as_string() != "status"){
|
||||
throw std::runtime_error(StrPrinter <<"onStatus 失败:" << level.as_string() << " " << code << endl);
|
||||
if (level.type() == AMF_STRING) {
|
||||
if (level.as_string() != "status") {
|
||||
throw std::runtime_error(StrPrinter << "onStatus 失败:" << level.as_string() << " " << code << endl);
|
||||
}
|
||||
}
|
||||
//WarnL << "unhandled onStatus:" << code;
|
||||
@@ -301,112 +308,111 @@ void RtmpPlayer::onCmd_onStatus(AMFDecoder &dec) {
|
||||
void RtmpPlayer::onCmd_onMetaData(AMFDecoder &dec) {
|
||||
//TraceL;
|
||||
auto val = dec.load<AMFValue>();
|
||||
if(!onCheckMeta(val)){
|
||||
if (!onCheckMeta(val)) {
|
||||
throw std::runtime_error("onCheckMeta failed");
|
||||
}
|
||||
_metadata_got = true;
|
||||
}
|
||||
|
||||
void RtmpPlayer::onStreamDry(uint32_t ui32StreamId) {
|
||||
//TraceL << ui32StreamId;
|
||||
onPlayResult_l(SockException(Err_other,"rtmp stream dry"), true);
|
||||
void RtmpPlayer::onStreamDry(uint32_t stream_index) {
|
||||
//TraceL << stream_index;
|
||||
onPlayResult_l(SockException(Err_other, "rtmp stream dry"), true);
|
||||
}
|
||||
|
||||
void RtmpPlayer::onMediaData_l(const RtmpPacket::Ptr &packet) {
|
||||
_mediaTicker.resetTime();
|
||||
if(!_pPlayTimer){
|
||||
void RtmpPlayer::onMediaData_l(const RtmpPacket::Ptr &chunk_data) {
|
||||
_rtmp_recv_ticker.resetTime();
|
||||
if (!_play_timer) {
|
||||
//已经触发了onPlayResult事件,直接触发onMediaData事件
|
||||
onMediaData(packet);
|
||||
onMediaData(chunk_data);
|
||||
return;
|
||||
}
|
||||
|
||||
if(packet->isCfgFrame()){
|
||||
if (chunk_data->isCfgFrame()) {
|
||||
//输入配置帧以便初始化完成各个track
|
||||
onMediaData(packet);
|
||||
}else{
|
||||
onMediaData(chunk_data);
|
||||
} else {
|
||||
//先触发onPlayResult事件,这个时候解码器才能初始化完毕
|
||||
onPlayResult_l(SockException(Err_success,"play rtmp success"), false);
|
||||
onPlayResult_l(SockException(Err_success, "play rtmp success"), false);
|
||||
//触发onPlayResult事件后,再把帧数据输入到解码器
|
||||
onMediaData(packet);
|
||||
onMediaData(chunk_data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RtmpPlayer::onRtmpChunk(RtmpPacket &chunkData) {
|
||||
void RtmpPlayer::onRtmpChunk(RtmpPacket &chunk_data) {
|
||||
typedef void (RtmpPlayer::*rtmp_func_ptr)(AMFDecoder &dec);
|
||||
static unordered_map<string, rtmp_func_ptr> s_func_map;
|
||||
static onceToken token([]() {
|
||||
s_func_map.emplace("_error",&RtmpPlayer::onCmd_result);
|
||||
s_func_map.emplace("_result",&RtmpPlayer::onCmd_result);
|
||||
s_func_map.emplace("onStatus",&RtmpPlayer::onCmd_onStatus);
|
||||
s_func_map.emplace("onMetaData",&RtmpPlayer::onCmd_onMetaData);
|
||||
}, []() {});
|
||||
s_func_map.emplace("_error", &RtmpPlayer::onCmd_result);
|
||||
s_func_map.emplace("_result", &RtmpPlayer::onCmd_result);
|
||||
s_func_map.emplace("onStatus", &RtmpPlayer::onCmd_onStatus);
|
||||
s_func_map.emplace("onMetaData", &RtmpPlayer::onCmd_onMetaData);
|
||||
});
|
||||
|
||||
switch (chunkData.typeId) {
|
||||
switch (chunk_data.type_id) {
|
||||
case MSG_CMD:
|
||||
case MSG_CMD3:
|
||||
case MSG_DATA:
|
||||
case MSG_DATA3: {
|
||||
AMFDecoder dec(chunkData.strBuf, 0);
|
||||
AMFDecoder dec(chunk_data.buffer, 0);
|
||||
std::string type = dec.load<std::string>();
|
||||
auto it = s_func_map.find(type);
|
||||
if(it != s_func_map.end()){
|
||||
if (it != s_func_map.end()) {
|
||||
auto fun = it->second;
|
||||
(this->*fun)(dec);
|
||||
}else{
|
||||
} else {
|
||||
WarnL << "can not support cmd:" << type;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MSG_AUDIO:
|
||||
case MSG_VIDEO: {
|
||||
auto idx = chunkData.typeId%2;
|
||||
if (_aNowStampTicker[idx].elapsedTime() > 500) {
|
||||
auto idx = chunk_data.type_id % 2;
|
||||
if (_now_stamp_ticker[idx].elapsedTime() > 500) {
|
||||
//计算播放进度时间轴用
|
||||
_aiNowStamp[idx] = chunkData.timeStamp;
|
||||
_now_stamp[idx] = chunk_data.time_stamp;
|
||||
}
|
||||
if(!_metadata_got){
|
||||
if(!onCheckMeta(TitleMeta().getMetadata())){
|
||||
if (!_metadata_got) {
|
||||
if (!onCheckMeta(TitleMeta().getMetadata())) {
|
||||
throw std::runtime_error("onCheckMeta failed");
|
||||
}
|
||||
_metadata_got = true;
|
||||
}
|
||||
onMediaData_l(std::make_shared<RtmpPacket>(std::move(chunkData)));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//WarnL << "unhandled message:" << (int) chunkData.typeId << hexdump(chunkData.strBuf.data(), chunkData.strBuf.size());
|
||||
onMediaData_l(std::make_shared<RtmpPacket>(std::move(chunk_data)));
|
||||
break;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t RtmpPlayer::getProgressMilliSecond() const{
|
||||
uint32_t iTime[2] = {0,0};
|
||||
for(auto i = 0 ;i < 2 ;i++){
|
||||
iTime[i] = _aiNowStamp[i] - _aiFistStamp[i];
|
||||
uint32_t stamp[2] = {0, 0};
|
||||
for (auto i = 0; i < 2; i++) {
|
||||
stamp[i] = _now_stamp[i] - _fist_stamp[i];
|
||||
}
|
||||
return _iSeekTo + MAX(iTime[0],iTime[1]);
|
||||
return _seek_ms + MAX(stamp[0], stamp[1]);
|
||||
}
|
||||
|
||||
void RtmpPlayer::seekToMilliSecond(uint32_t seekMS){
|
||||
if (_bPaused) {
|
||||
if (_paused) {
|
||||
pause(false);
|
||||
}
|
||||
AMFEncoder enc;
|
||||
enc << "seek" << ++_iReqID << nullptr << seekMS * 1.0;
|
||||
enc << "seek" << ++_send_req_id << nullptr << seekMS * 1.0;
|
||||
sendRequest(MSG_CMD, enc.data());
|
||||
addOnStatusCB([this,seekMS](AMFValue &val) {
|
||||
addOnStatusCB([this, seekMS](AMFValue &val) {
|
||||
//TraceL << "seek result";
|
||||
_aNowStampTicker[0].resetTime();
|
||||
_aNowStampTicker[1].resetTime();
|
||||
_now_stamp_ticker[0].resetTime();
|
||||
_now_stamp_ticker[1].resetTime();
|
||||
int iTimeInc = seekMS - getProgressMilliSecond();
|
||||
for(auto i = 0 ;i < 2 ;i++){
|
||||
_aiFistStamp[i] = _aiNowStamp[i] + iTimeInc;
|
||||
_aiNowStamp[i] = _aiFistStamp[i];
|
||||
for (auto i = 0; i < 2; i++) {
|
||||
_fist_stamp[i] = _now_stamp[i] + iTimeInc;
|
||||
_now_stamp[i] = _fist_stamp[i];
|
||||
}
|
||||
_iSeekTo = seekMS;
|
||||
_seek_ms = seekMS;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef SRC_RTMP_RtmpPlayer2_H_
|
||||
#define SRC_RTMP_RtmpPlayer2_H_
|
||||
#ifndef SRC_RTMP_RtmpPlayer_H_
|
||||
#define SRC_RTMP_RtmpPlayer_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -28,84 +28,90 @@ using namespace toolkit;
|
||||
using namespace mediakit::Client;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
//实现了rtmp播放器协议部分的功能,及数据接收功能
|
||||
class RtmpPlayer:public PlayerBase, public TcpClient, public RtmpProtocol{
|
||||
class RtmpPlayer : public PlayerBase, public TcpClient, public RtmpProtocol {
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpPlayer> Ptr;
|
||||
RtmpPlayer(const EventPoller::Ptr &poller);
|
||||
virtual ~RtmpPlayer();
|
||||
~RtmpPlayer() override;
|
||||
|
||||
void play(const string &strUrl) override;
|
||||
void pause(bool bPause) override;
|
||||
void teardown() override;
|
||||
|
||||
protected:
|
||||
virtual bool onCheckMeta(const AMFValue &val) =0;
|
||||
virtual void onMediaData(const RtmpPacket::Ptr &chunkData) =0;
|
||||
virtual bool onCheckMeta(const AMFValue &val) = 0;
|
||||
virtual void onMediaData(const RtmpPacket::Ptr &chunk_data) = 0;
|
||||
uint32_t getProgressMilliSecond() const;
|
||||
void seekToMilliSecond(uint32_t ms);
|
||||
|
||||
protected:
|
||||
void onMediaData_l(const RtmpPacket::Ptr &chunkData);
|
||||
void onMediaData_l(const RtmpPacket::Ptr &chunk_data);
|
||||
//在获取config帧后才触发onPlayResult_l(而不是收到play命令回复),所以此时所有track都初始化完毕了
|
||||
void onPlayResult_l(const SockException &ex, bool handshakeCompleted);
|
||||
void onPlayResult_l(const SockException &ex, bool handshake_done);
|
||||
|
||||
//form Tcpclient
|
||||
void onRecv(const Buffer::Ptr &pBuf) override;
|
||||
void onRecv(const Buffer::Ptr &buf) override;
|
||||
void onConnect(const SockException &err) override;
|
||||
void onErr(const SockException &ex) override;
|
||||
//from RtmpProtocol
|
||||
void onRtmpChunk(RtmpPacket &chunkData) override;
|
||||
void onStreamDry(uint32_t ui32StreamId) override;
|
||||
void onSendRawData(const Buffer::Ptr &buffer) override{
|
||||
void onRtmpChunk(RtmpPacket &chunk_data) override;
|
||||
void onStreamDry(uint32_t stream_index) override;
|
||||
void onSendRawData(const Buffer::Ptr &buffer) override {
|
||||
send(buffer);
|
||||
}
|
||||
|
||||
template<typename FUN>
|
||||
inline void addOnResultCB(const FUN &fun) {
|
||||
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
||||
_mapOnResultCB.emplace(_iReqID, fun);
|
||||
template<typename FUNC>
|
||||
void addOnResultCB(const FUNC &func) {
|
||||
lock_guard<recursive_mutex> lck(_mtx_on_result);
|
||||
_map_on_result.emplace(_send_req_id, func);
|
||||
}
|
||||
template<typename FUN>
|
||||
inline void addOnStatusCB(const FUN &fun) {
|
||||
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
||||
_dqOnStatusCB.emplace_back(fun);
|
||||
template<typename FUNC>
|
||||
void addOnStatusCB(const FUNC &func) {
|
||||
lock_guard<recursive_mutex> lck(_mtx_on_status);
|
||||
_deque_on_status.emplace_back(func);
|
||||
}
|
||||
|
||||
void onCmd_result(AMFDecoder &dec);
|
||||
void onCmd_onStatus(AMFDecoder &dec);
|
||||
void onCmd_onMetaData(AMFDecoder &dec);
|
||||
|
||||
inline void send_connect();
|
||||
inline void send_createStream();
|
||||
inline void send_play();
|
||||
inline void send_pause(bool bPause);
|
||||
void send_connect();
|
||||
void send_createStream();
|
||||
void send_play();
|
||||
void send_pause(bool pause);
|
||||
|
||||
private:
|
||||
string _strApp;
|
||||
string _strStream;
|
||||
string _strTcUrl;
|
||||
bool _bPaused = false;
|
||||
string _app;
|
||||
string _stream_id;
|
||||
string _tc_url;
|
||||
|
||||
unordered_map<int, function<void(AMFDecoder &dec)> > _mapOnResultCB;
|
||||
recursive_mutex _mtxOnResultCB;
|
||||
deque<function<void(AMFValue &dec)> > _dqOnStatusCB;
|
||||
recursive_mutex _mtxOnStatusCB;
|
||||
|
||||
//超时功能实现
|
||||
Ticker _mediaTicker;
|
||||
std::shared_ptr<Timer> _pMediaTimer;
|
||||
std::shared_ptr<Timer> _pPlayTimer;
|
||||
//心跳定时器
|
||||
std::shared_ptr<Timer> _pBeatTimer;
|
||||
|
||||
//播放进度控制
|
||||
uint32_t _iSeekTo = 0;
|
||||
uint32_t _aiFistStamp[2] = { 0, 0 };
|
||||
uint32_t _aiNowStamp[2] = { 0, 0 };
|
||||
Ticker _aNowStampTicker[2];
|
||||
bool _paused = false;
|
||||
bool _metadata_got = false;
|
||||
//是否为性能测试模式
|
||||
bool _benchmark_mode = false;
|
||||
|
||||
//播放进度控制
|
||||
uint32_t _seek_ms = 0;
|
||||
uint32_t _fist_stamp[2] = {0, 0};
|
||||
uint32_t _now_stamp[2] = {0, 0};
|
||||
Ticker _now_stamp_ticker[2];
|
||||
|
||||
recursive_mutex _mtx_on_result;
|
||||
recursive_mutex _mtx_on_status;
|
||||
deque<function<void(AMFValue &dec)> > _deque_on_status;
|
||||
unordered_map<int, function<void(AMFDecoder &dec)> > _map_on_result;
|
||||
|
||||
//rtmp接收超时计时器
|
||||
Ticker _rtmp_recv_ticker;
|
||||
//心跳发送定时器
|
||||
std::shared_ptr<Timer> _beat_timer;
|
||||
//播放超时定时器
|
||||
std::shared_ptr<Timer> _play_timer;
|
||||
//rtmp接收超时定时器
|
||||
std::shared_ptr<Timer> _rtmp_recv_timer;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
#endif /* SRC_RTMP_RtmpPlayer2_H_ */
|
||||
#endif /* SRC_RTMP_RtmpPlayer_H_ */
|
||||
|
||||
@@ -27,51 +27,59 @@ namespace mediakit {
|
||||
class RtmpPlayerImp: public PlayerImp<RtmpPlayer,RtmpDemuxer> {
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpPlayerImp> Ptr;
|
||||
RtmpPlayerImp(const EventPoller::Ptr &poller) : PlayerImp<RtmpPlayer,RtmpDemuxer>(poller){};
|
||||
virtual ~RtmpPlayerImp(){
|
||||
DebugL<<endl;
|
||||
};
|
||||
float getProgress() const override{
|
||||
if(getDuration() > 0){
|
||||
|
||||
RtmpPlayerImp(const EventPoller::Ptr &poller) : PlayerImp<RtmpPlayer, RtmpDemuxer>(poller) {};
|
||||
|
||||
~RtmpPlayerImp() override {
|
||||
DebugL << endl;
|
||||
}
|
||||
|
||||
float getProgress() const override {
|
||||
if (getDuration() > 0) {
|
||||
return getProgressMilliSecond() / (getDuration() * 1000);
|
||||
}
|
||||
return PlayerBase::getProgress();
|
||||
};
|
||||
void seekTo(float fProgress) override{
|
||||
fProgress = MAX(float(0),MIN(fProgress,float(1.0)));
|
||||
seekToMilliSecond(fProgress * getDuration() * 1000);
|
||||
};
|
||||
void play(const string &strUrl) override {
|
||||
PlayerImp<RtmpPlayer,RtmpDemuxer>::play(strUrl);
|
||||
}
|
||||
|
||||
void seekTo(float fProgress) override {
|
||||
fProgress = MAX(float(0), MIN(fProgress, float(1.0)));
|
||||
seekToMilliSecond(fProgress * getDuration() * 1000);
|
||||
}
|
||||
|
||||
void play(const string &strUrl) override {
|
||||
PlayerImp<RtmpPlayer, RtmpDemuxer>::play(strUrl);
|
||||
}
|
||||
|
||||
private:
|
||||
//派生类回调函数
|
||||
bool onCheckMeta(const AMFValue &val) override {
|
||||
_pRtmpMediaSrc = dynamic_pointer_cast<RtmpMediaSource>(_pMediaSrc);
|
||||
if(_pRtmpMediaSrc){
|
||||
_pRtmpMediaSrc->setMetaData(val);
|
||||
_rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(_pMediaSrc);
|
||||
if (_rtmp_src) {
|
||||
_rtmp_src->setMetaData(val);
|
||||
_set_meta_data = true;
|
||||
}
|
||||
_delegate.reset(new RtmpDemuxer);
|
||||
_delegate->loadMetaData(val);
|
||||
return true;
|
||||
}
|
||||
|
||||
void onMediaData(const RtmpPacket::Ptr &chunkData) override {
|
||||
if(_pRtmpMediaSrc){
|
||||
if(!_set_meta_data && !chunkData->isCfgFrame()){
|
||||
if (_rtmp_src) {
|
||||
if (!_set_meta_data && !chunkData->isCfgFrame()) {
|
||||
_set_meta_data = true;
|
||||
_pRtmpMediaSrc->setMetaData(TitleMeta().getMetadata());
|
||||
_rtmp_src->setMetaData(TitleMeta().getMetadata());
|
||||
}
|
||||
_pRtmpMediaSrc->onWrite(chunkData);
|
||||
_rtmp_src->onWrite(chunkData);
|
||||
}
|
||||
if(!_delegate){
|
||||
if (!_delegate) {
|
||||
//这个流没有metadata
|
||||
_delegate.reset(new RtmpDemuxer());
|
||||
}
|
||||
_delegate->inputRtmp(chunkData);
|
||||
}
|
||||
|
||||
private:
|
||||
RtmpMediaSource::Ptr _pRtmpMediaSrc;
|
||||
RtmpMediaSource::Ptr _rtmp_src;
|
||||
bool _set_meta_data = false;
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,45 +31,42 @@ class RtmpProtocol {
|
||||
public:
|
||||
RtmpProtocol();
|
||||
virtual ~RtmpProtocol();
|
||||
|
||||
void onParseRtmp(const char *data, int size);
|
||||
//作为客户端发送c0c1,等待s0s1s2并且回调
|
||||
void startClientSession(const function<void()> &cb);
|
||||
void onParseRtmp(const char *pcRawData,int iSize);
|
||||
void reset();
|
||||
|
||||
protected:
|
||||
virtual void onSendRawData(const Buffer::Ptr &buffer) = 0;
|
||||
virtual void onRtmpChunk(RtmpPacket &chunkData) = 0;
|
||||
virtual void onStreamBegin(uint32_t ui32StreamId){
|
||||
_ui32StreamId = ui32StreamId;
|
||||
virtual void onRtmpChunk(RtmpPacket &chunk_data) = 0;
|
||||
virtual void onStreamBegin(uint32_t stream_index){
|
||||
_stream_index = stream_index;
|
||||
}
|
||||
virtual void onStreamEof(uint32_t ui32StreamId){};
|
||||
virtual void onStreamDry(uint32_t ui32StreamId){};
|
||||
protected:
|
||||
void sendAcknowledgement(uint32_t ui32Size);
|
||||
void sendAcknowledgementSize(uint32_t ui32Size);
|
||||
void sendPeerBandwidth(uint32_t ui32Size);
|
||||
void sendChunkSize(uint32_t ui32Size);
|
||||
void sendPingRequest(uint32_t ui32TimeStamp = ::time(NULL));
|
||||
void sendPingResponse(uint32_t ui32TimeStamp = ::time(NULL));
|
||||
void sendSetBufferLength(uint32_t ui32StreamId, uint32_t ui32Length);
|
||||
void sendUserControl(uint16_t ui16EventType, uint32_t ui32EventData);
|
||||
void sendUserControl(uint16_t ui16EventType, const string &strEventData);
|
||||
virtual void onStreamEof(uint32_t stream_index){};
|
||||
virtual void onStreamDry(uint32_t stream_index){};
|
||||
|
||||
void sendInvoke(const string &strCmd, const AMFValue &val);
|
||||
void sendRequest(int iCmd, const string &str);
|
||||
void sendResponse(int iType, const string &str);
|
||||
void sendRtmp(uint8_t ui8Type, uint32_t ui32StreamId, const std::string &strBuf, uint32_t ui32TimeStamp, int iChunkID);
|
||||
void sendRtmp(uint8_t ui8Type, uint32_t ui32StreamId, const Buffer::Ptr &buffer, uint32_t ui32TimeStamp, int iChunkID);
|
||||
protected:
|
||||
int _iReqID = 0;
|
||||
uint32_t _ui32StreamId = STREAM_CONTROL;
|
||||
int _iNowStreamID = 0;
|
||||
int _iNowChunkID = 0;
|
||||
bool _bDataStarted = false;
|
||||
inline BufferRaw::Ptr obtainBuffer();
|
||||
inline BufferRaw::Ptr obtainBuffer(const void *data, int len);
|
||||
//ResourcePool<BufferRaw,MAX_SEND_PKT> _bufferPool;
|
||||
void reset();
|
||||
BufferRaw::Ptr obtainBuffer();
|
||||
BufferRaw::Ptr obtainBuffer(const void *data, int len);
|
||||
|
||||
void sendAcknowledgement(uint32_t size);
|
||||
void sendAcknowledgementSize(uint32_t size);
|
||||
void sendPeerBandwidth(uint32_t size);
|
||||
void sendChunkSize(uint32_t size);
|
||||
void sendPingRequest(uint32_t ti = ::time(NULL));
|
||||
void sendPingResponse(uint32_t time_stamp = ::time(NULL));
|
||||
void sendSetBufferLength(uint32_t stream_index, uint32_t len);
|
||||
void sendUserControl(uint16_t event_type, uint32_t event_data);
|
||||
void sendUserControl(uint16_t event_type, const string &event_data);
|
||||
void sendInvoke(const string &cmd, const AMFValue &val);
|
||||
void sendRequest(int cmd, const string &str);
|
||||
void sendResponse(int type, const string &str);
|
||||
void sendRtmp(uint8_t type, uint32_t stream_index, const std::string &buffer, uint32_t stamp, int chunk_id);
|
||||
void sendRtmp(uint8_t type, uint32_t stream_index, const Buffer::Ptr &buffer, uint32_t stamp, int chunk_id);
|
||||
|
||||
private:
|
||||
void handle_S0S1S2(const function<void()> &cb);
|
||||
void handle_S0S1S2(const function<void()> &func);
|
||||
void handle_C0C1();
|
||||
void handle_C1_simple();
|
||||
#ifdef ENABLE_OPENSSL
|
||||
@@ -82,26 +79,32 @@ private:
|
||||
|
||||
void handle_C2();
|
||||
void handle_rtmp();
|
||||
void handle_rtmpChunk(RtmpPacket &chunkData);
|
||||
void handle_rtmpChunk(RtmpPacket &chunk_data);
|
||||
|
||||
protected:
|
||||
int _send_req_id = 0;
|
||||
uint32_t _stream_index = STREAM_CONTROL;
|
||||
|
||||
private:
|
||||
int _now_stream_index = 0;
|
||||
int _now_chunk_id = 0;
|
||||
bool _data_started = false;
|
||||
////////////ChunkSize////////////
|
||||
size_t _iChunkLenIn = DEFAULT_CHUNK_LEN;
|
||||
size_t _iChunkLenOut = DEFAULT_CHUNK_LEN;
|
||||
size_t _chunk_size_in = DEFAULT_CHUNK_LEN;
|
||||
size_t _chunk_size_out = DEFAULT_CHUNK_LEN;
|
||||
////////////Acknowledgement////////////
|
||||
uint32_t _ui32ByteSent = 0;
|
||||
uint32_t _ui32LastSent = 0;
|
||||
uint32_t _ui32WinSize = 0;
|
||||
uint32_t _bytes_sent = 0;
|
||||
uint32_t _bytes_sent_last = 0;
|
||||
uint32_t _windows_size = 0;
|
||||
///////////PeerBandwidth///////////
|
||||
uint32_t _ui32Bandwidth = 2500000;
|
||||
uint8_t _ui8LimitType = 2;
|
||||
////////////Chunk////////////
|
||||
unordered_map<int, RtmpPacket> _mapChunkData;
|
||||
uint32_t _bandwidth = 2500000;
|
||||
uint8_t _band_limit_type = 2;
|
||||
//////////Rtmp parser//////////
|
||||
string _strRcvBuf;
|
||||
function<void()> _nextHandle;
|
||||
string _recv_data_buf;
|
||||
function<void()> _next_step_func;
|
||||
////////////Chunk////////////
|
||||
unordered_map<int, RtmpPacket> _map_chunk_data;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
#endif /* SRC_RTMP_RTMPPROTOCOL_H_ */
|
||||
|
||||
@@ -18,141 +18,147 @@ using namespace mediakit::Client;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
RtmpPusher::RtmpPusher(const EventPoller::Ptr &poller,const RtmpMediaSource::Ptr &src) : TcpClient(poller){
|
||||
_pMediaSrc=src;
|
||||
RtmpPusher::RtmpPusher(const EventPoller::Ptr &poller, const RtmpMediaSource::Ptr &src) : TcpClient(poller){
|
||||
_publish_src = src;
|
||||
}
|
||||
|
||||
RtmpPusher::~RtmpPusher() {
|
||||
teardown();
|
||||
DebugL << endl;
|
||||
}
|
||||
|
||||
void RtmpPusher::teardown() {
|
||||
if (alive()) {
|
||||
_strApp.clear();
|
||||
_strStream.clear();
|
||||
_strTcUrl.clear();
|
||||
_app.clear();
|
||||
_stream_id.clear();
|
||||
_tc_url.clear();
|
||||
{
|
||||
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
||||
_mapOnResultCB.clear();
|
||||
lock_guard<recursive_mutex> lck(_mtx_on_result);
|
||||
_map_on_result.clear();
|
||||
}
|
||||
{
|
||||
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
||||
_dqOnStatusCB.clear();
|
||||
lock_guard<recursive_mutex> lck(_mtx_on_status);
|
||||
_deque_on_status.clear();
|
||||
}
|
||||
_pPublishTimer.reset();
|
||||
_publish_timer.reset();
|
||||
reset();
|
||||
shutdown(SockException(Err_shutdown,"teardown"));
|
||||
shutdown(SockException(Err_shutdown, "teardown"));
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpPusher::onPublishResult(const SockException &ex,bool handshakeCompleted) {
|
||||
if(!handshakeCompleted){
|
||||
void RtmpPusher::onPublishResult(const SockException &ex, bool handshake_done) {
|
||||
if (ex.getErrCode() == Err_shutdown) {
|
||||
//主动shutdown的,不触发回调
|
||||
return;
|
||||
}
|
||||
if (!handshake_done) {
|
||||
//播放结果回调
|
||||
_pPublishTimer.reset();
|
||||
if(_onPublished){
|
||||
_onPublished(ex);
|
||||
_publish_timer.reset();
|
||||
if (_on_published) {
|
||||
_on_published(ex);
|
||||
}
|
||||
} else {
|
||||
//播放成功后异常断开回调
|
||||
if(_onShutdown){
|
||||
_onShutdown(ex);
|
||||
if (_on_shutdown) {
|
||||
_on_shutdown(ex);
|
||||
}
|
||||
}
|
||||
|
||||
if(ex){
|
||||
if (ex) {
|
||||
teardown();
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpPusher::publish(const string &strUrl) {
|
||||
void RtmpPusher::publish(const string &url) {
|
||||
teardown();
|
||||
string strHost = FindField(strUrl.data(), "://", "/");
|
||||
_strApp = FindField(strUrl.data(), (strHost + "/").data(), "/");
|
||||
_strStream = FindField(strUrl.data(), (strHost + "/" + _strApp + "/").data(), NULL);
|
||||
_strTcUrl = string("rtmp://") + strHost + "/" + _strApp;
|
||||
string host_url = FindField(url.data(), "://", "/");
|
||||
_app = FindField(url.data(), (host_url + "/").data(), "/");
|
||||
_stream_id = FindField(url.data(), (host_url + "/" + _app + "/").data(), NULL);
|
||||
_tc_url = string("rtmp://") + host_url + "/" + _app;
|
||||
|
||||
if (!_strApp.size() || !_strStream.size()) {
|
||||
onPublishResult(SockException(Err_other,"rtmp url非法"),false);
|
||||
if (!_app.size() || !_stream_id.size()) {
|
||||
onPublishResult(SockException(Err_other, "rtmp url非法"), false);
|
||||
return;
|
||||
}
|
||||
DebugL << strHost << " " << _strApp << " " << _strStream;
|
||||
DebugL << host_url << " " << _app << " " << _stream_id;
|
||||
|
||||
auto iPort = atoi(FindField(strHost.data(), ":", NULL).data());
|
||||
auto iPort = atoi(FindField(host_url.data(), ":", NULL).data());
|
||||
if (iPort <= 0) {
|
||||
//rtmp 默认端口1935
|
||||
iPort = 1935;
|
||||
} else {
|
||||
//服务器域名
|
||||
strHost = FindField(strHost.data(), NULL, ":");
|
||||
host_url = FindField(host_url.data(), NULL, ":");
|
||||
}
|
||||
|
||||
weak_ptr<RtmpPusher> weakSelf = dynamic_pointer_cast<RtmpPusher>(shared_from_this());
|
||||
float publishTimeOutSec = (*this)[kTimeoutMS].as<int>() / 1000.0;
|
||||
_pPublishTimer.reset( new Timer(publishTimeOutSec, [weakSelf]() {
|
||||
auto strongSelf=weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
_publish_timer.reset(new Timer(publishTimeOutSec, [weakSelf]() {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
return false;
|
||||
}
|
||||
strongSelf->onPublishResult(SockException(Err_timeout,"publish rtmp timeout"), false);
|
||||
strongSelf->onPublishResult(SockException(Err_timeout, "publish rtmp timeout"), false);
|
||||
return false;
|
||||
},getPoller()));
|
||||
}, getPoller()));
|
||||
|
||||
if(!(*this)[kNetAdapter].empty()){
|
||||
if (!(*this)[kNetAdapter].empty()) {
|
||||
setNetAdapter((*this)[kNetAdapter]);
|
||||
}
|
||||
|
||||
startConnect(strHost, iPort);
|
||||
startConnect(host_url, iPort);
|
||||
}
|
||||
|
||||
void RtmpPusher::onErr(const SockException &ex){
|
||||
//定时器_pPublishTimer为空后表明握手结束了
|
||||
onPublishResult(ex,!_pPublishTimer);
|
||||
onPublishResult(ex, !_publish_timer);
|
||||
}
|
||||
|
||||
void RtmpPusher::onConnect(const SockException &err){
|
||||
if(err) {
|
||||
onPublishResult(err,false);
|
||||
if (err) {
|
||||
onPublishResult(err, false);
|
||||
return;
|
||||
}
|
||||
//推流器不需要多大的接收缓存,节省内存占用
|
||||
_sock->setReadBuffer(std::make_shared<BufferRaw>(1 * 1024));
|
||||
getSock()->setReadBuffer(std::make_shared<BufferRaw>(1 * 1024));
|
||||
|
||||
weak_ptr<RtmpPusher> weakSelf = dynamic_pointer_cast<RtmpPusher>(shared_from_this());
|
||||
startClientSession([weakSelf](){
|
||||
auto strongSelf=weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
weak_ptr<RtmpPusher> weak_self = dynamic_pointer_cast<RtmpPusher>(shared_from_this());
|
||||
startClientSession([weak_self]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
|
||||
strongSelf->sendChunkSize(60000);
|
||||
strongSelf->send_connect();
|
||||
strong_self->sendChunkSize(60000);
|
||||
strong_self->send_connect();
|
||||
});
|
||||
}
|
||||
void RtmpPusher::onRecv(const Buffer::Ptr &pBuf){
|
||||
|
||||
void RtmpPusher::onRecv(const Buffer::Ptr &buf){
|
||||
try {
|
||||
onParseRtmp(pBuf->data(), pBuf->size());
|
||||
onParseRtmp(buf->data(), buf->size());
|
||||
} catch (exception &e) {
|
||||
SockException ex(Err_other, e.what());
|
||||
//定时器_pPublishTimer为空后表明握手结束了
|
||||
onPublishResult(ex,!_pPublishTimer);
|
||||
onPublishResult(ex, !_publish_timer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inline void RtmpPusher::send_connect() {
|
||||
AMFValue obj(AMF_OBJECT);
|
||||
obj.set("app", _strApp);
|
||||
obj.set("app", _app);
|
||||
obj.set("type", "nonprivate");
|
||||
obj.set("tcUrl", _strTcUrl);
|
||||
obj.set("swfUrl", _strTcUrl);
|
||||
obj.set("tcUrl", _tc_url);
|
||||
obj.set("swfUrl", _tc_url);
|
||||
sendInvoke("connect", obj);
|
||||
addOnResultCB([this](AMFDecoder &dec){
|
||||
addOnResultCB([this](AMFDecoder &dec) {
|
||||
//TraceL << "connect result";
|
||||
dec.load<AMFValue>();
|
||||
auto val = dec.load<AMFValue>();
|
||||
auto level = val["level"].as_string();
|
||||
auto code = val["code"].as_string();
|
||||
if(level != "status"){
|
||||
throw std::runtime_error(StrPrinter <<"connect 失败:" << level << " " << code << endl);
|
||||
if (level != "status") {
|
||||
throw std::runtime_error(StrPrinter << "connect 失败:" << level << " " << code << endl);
|
||||
}
|
||||
send_createStream();
|
||||
});
|
||||
@@ -161,23 +167,24 @@ inline void RtmpPusher::send_connect() {
|
||||
inline void RtmpPusher::send_createStream() {
|
||||
AMFValue obj(AMF_NULL);
|
||||
sendInvoke("createStream", obj);
|
||||
addOnResultCB([this](AMFDecoder &dec){
|
||||
addOnResultCB([this](AMFDecoder &dec) {
|
||||
//TraceL << "createStream result";
|
||||
dec.load<AMFValue>();
|
||||
_ui32StreamId = dec.load<int>();
|
||||
_stream_index = dec.load<int>();
|
||||
send_publish();
|
||||
});
|
||||
}
|
||||
|
||||
inline void RtmpPusher::send_publish() {
|
||||
AMFEncoder enc;
|
||||
enc << "publish" << ++_iReqID << nullptr << _strStream << _strApp ;
|
||||
enc << "publish" << ++_send_req_id << nullptr << _stream_id << _app;
|
||||
sendRequest(MSG_CMD, enc.data());
|
||||
|
||||
addOnStatusCB([this](AMFValue &val) {
|
||||
auto level = val["level"].as_string();
|
||||
auto code = val["code"].as_string();
|
||||
if(level != "status") {
|
||||
throw std::runtime_error(StrPrinter <<"publish 失败:" << level << " " << code << endl);
|
||||
if (level != "status") {
|
||||
throw std::runtime_error(StrPrinter << "publish 失败:" << level << " " << code << endl);
|
||||
}
|
||||
//start send media
|
||||
send_metaData();
|
||||
@@ -185,122 +192,124 @@ inline void RtmpPusher::send_publish() {
|
||||
}
|
||||
|
||||
inline void RtmpPusher::send_metaData(){
|
||||
auto src = _pMediaSrc.lock();
|
||||
auto src = _publish_src.lock();
|
||||
if (!src) {
|
||||
throw std::runtime_error("the media source was released");
|
||||
}
|
||||
|
||||
AMFEncoder enc;
|
||||
enc << "@setDataFrame" << "onMetaData" << src->getMetaData();
|
||||
enc << "@setDataFrame" << "onMetaData" << src->getMetaData();
|
||||
sendRequest(MSG_DATA, enc.data());
|
||||
|
||||
src->getConfigFrame([&](const RtmpPacket::Ptr &pkt){
|
||||
sendRtmp(pkt->typeId, _ui32StreamId, pkt, pkt->timeStamp, pkt->chunkId );
|
||||
|
||||
src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) {
|
||||
sendRtmp(pkt->type_id, _stream_index, pkt, pkt->time_stamp, pkt->chunk_id);
|
||||
});
|
||||
|
||||
_pRtmpReader = src->getRing()->attach(getPoller());
|
||||
weak_ptr<RtmpPusher> weakSelf = dynamic_pointer_cast<RtmpPusher>(shared_from_this());
|
||||
_pRtmpReader->setReadCB([weakSelf](const RtmpMediaSource::RingDataType &pkt){
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
|
||||
_rtmp_reader = src->getRing()->attach(getPoller());
|
||||
weak_ptr<RtmpPusher> weak_self = dynamic_pointer_cast<RtmpPusher>(shared_from_this());
|
||||
_rtmp_reader->setReadCB([weak_self](const RtmpMediaSource::RingDataType &pkt) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
int size = pkt->size();
|
||||
strongSelf->setSendFlushFlag(false);
|
||||
pkt->for_each([&](const RtmpPacket::Ptr &rtmp){
|
||||
if(++i == size){
|
||||
strongSelf->setSendFlushFlag(true);
|
||||
strong_self->setSendFlushFlag(false);
|
||||
pkt->for_each([&](const RtmpPacket::Ptr &rtmp) {
|
||||
if (++i == size) {
|
||||
strong_self->setSendFlushFlag(true);
|
||||
}
|
||||
strongSelf->sendRtmp(rtmp->typeId, strongSelf->_ui32StreamId, rtmp, rtmp->timeStamp, rtmp->chunkId);
|
||||
strong_self->sendRtmp(rtmp->type_id, strong_self->_stream_index, rtmp, rtmp->time_stamp, rtmp->chunk_id);
|
||||
});
|
||||
});
|
||||
_pRtmpReader->setDetachCB([weakSelf](){
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(strongSelf){
|
||||
strongSelf->onPublishResult(SockException(Err_other,"媒体源被释放"), !strongSelf->_pPublishTimer);
|
||||
_rtmp_reader->setDetachCB([weak_self]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (strong_self) {
|
||||
strong_self->onPublishResult(SockException(Err_other, "媒体源被释放"), !strong_self->_publish_timer);
|
||||
}
|
||||
});
|
||||
onPublishResult(SockException(Err_success,"success"), false);
|
||||
onPublishResult(SockException(Err_success, "success"), false);
|
||||
//提升发送性能
|
||||
setSocketFlags();
|
||||
}
|
||||
|
||||
void RtmpPusher::setSocketFlags(){
|
||||
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
||||
if(mergeWriteMS > 0) {
|
||||
if (mergeWriteMS > 0) {
|
||||
//提高发送性能
|
||||
setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
|
||||
SockUtil::setNoDelay(_sock->rawFD(), false);
|
||||
SockUtil::setNoDelay(getSock()->rawFD(), false);
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpPusher::onCmd_result(AMFDecoder &dec){
|
||||
auto iReqId = dec.load<int>();
|
||||
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
||||
auto it = _mapOnResultCB.find(iReqId);
|
||||
if(it != _mapOnResultCB.end()){
|
||||
auto req_id = dec.load<int>();
|
||||
lock_guard<recursive_mutex> lck(_mtx_on_result);
|
||||
auto it = _map_on_result.find(req_id);
|
||||
if (it != _map_on_result.end()) {
|
||||
it->second(dec);
|
||||
_mapOnResultCB.erase(it);
|
||||
}else{
|
||||
_map_on_result.erase(it);
|
||||
} else {
|
||||
WarnL << "unhandled _result";
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpPusher::onCmd_onStatus(AMFDecoder &dec) {
|
||||
AMFValue val;
|
||||
while(true){
|
||||
while (true) {
|
||||
val = dec.load<AMFValue>();
|
||||
if(val.type() == AMF_OBJECT){
|
||||
if (val.type() == AMF_OBJECT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(val.type() != AMF_OBJECT){
|
||||
if (val.type() != AMF_OBJECT) {
|
||||
throw std::runtime_error("onStatus:the result object was not found");
|
||||
}
|
||||
|
||||
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
||||
if(_dqOnStatusCB.size()){
|
||||
_dqOnStatusCB.front()(val);
|
||||
_dqOnStatusCB.pop_front();
|
||||
}else{
|
||||
lock_guard<recursive_mutex> lck(_mtx_on_status);
|
||||
if (_deque_on_status.size()) {
|
||||
_deque_on_status.front()(val);
|
||||
_deque_on_status.pop_front();
|
||||
} else {
|
||||
auto level = val["level"];
|
||||
auto code = val["code"].as_string();
|
||||
if(level.type() == AMF_STRING){
|
||||
if(level.as_string() != "status"){
|
||||
throw std::runtime_error(StrPrinter <<"onStatus 失败:" << level.as_string() << " " << code << endl);
|
||||
if (level.type() == AMF_STRING) {
|
||||
if (level.as_string() != "status") {
|
||||
throw std::runtime_error(StrPrinter << "onStatus 失败:" << level.as_string() << " " << code << endl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpPusher::onRtmpChunk(RtmpPacket &chunkData) {
|
||||
switch (chunkData.typeId) {
|
||||
void RtmpPusher::onRtmpChunk(RtmpPacket &chunk_data) {
|
||||
switch (chunk_data.type_id) {
|
||||
case MSG_CMD:
|
||||
case MSG_CMD3: {
|
||||
typedef void (RtmpPusher::*rtmpCMDHandle)(AMFDecoder &dec);
|
||||
static unordered_map<string, rtmpCMDHandle> g_mapCmd;
|
||||
static onceToken token([]() {
|
||||
g_mapCmd.emplace("_error",&RtmpPusher::onCmd_result);
|
||||
g_mapCmd.emplace("_result",&RtmpPusher::onCmd_result);
|
||||
g_mapCmd.emplace("onStatus",&RtmpPusher::onCmd_onStatus);
|
||||
}, []() {});
|
||||
g_mapCmd.emplace("_error", &RtmpPusher::onCmd_result);
|
||||
g_mapCmd.emplace("_result", &RtmpPusher::onCmd_result);
|
||||
g_mapCmd.emplace("onStatus", &RtmpPusher::onCmd_onStatus);
|
||||
});
|
||||
|
||||
AMFDecoder dec(chunkData.strBuf, 0);
|
||||
AMFDecoder dec(chunk_data.buffer, 0);
|
||||
std::string type = dec.load<std::string>();
|
||||
auto it = g_mapCmd.find(type);
|
||||
if(it != g_mapCmd.end()){
|
||||
if (it != g_mapCmd.end()) {
|
||||
auto fun = it->second;
|
||||
(this->*fun)(dec);
|
||||
}else{
|
||||
} else {
|
||||
WarnL << "can not support cmd:" << type;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
//WarnL << "unhandled message:" << (int) chunkData.typeId << hexdump(chunkData.strBuf.data(), chunkData.strBuf.size());
|
||||
//WarnL << "unhandled message:" << (int) chunk_data.type_id << hexdump(chunk_data.buffer.data(), chunk_data.buffer.size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -18,46 +18,47 @@
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class RtmpPusher: public RtmpProtocol , public TcpClient , public PusherBase{
|
||||
class RtmpPusher : public RtmpProtocol, public TcpClient, public PusherBase {
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpPusher> Ptr;
|
||||
RtmpPusher(const EventPoller::Ptr &poller,const RtmpMediaSource::Ptr &src);
|
||||
virtual ~RtmpPusher();
|
||||
|
||||
void publish(const string &strUrl) override ;
|
||||
~RtmpPusher() override;
|
||||
|
||||
void publish(const string &url) override ;
|
||||
void teardown() override;
|
||||
|
||||
void setOnPublished(const Event &cb) override {
|
||||
_onPublished = cb;
|
||||
_on_published = cb;
|
||||
}
|
||||
|
||||
void setOnShutdown(const Event &cb) override{
|
||||
_onShutdown = cb;
|
||||
_on_shutdown = cb;
|
||||
}
|
||||
|
||||
protected:
|
||||
//for Tcpclient override
|
||||
void onRecv(const Buffer::Ptr &pBuf) override;
|
||||
void onRecv(const Buffer::Ptr &buf) override;
|
||||
void onConnect(const SockException &err) override;
|
||||
void onErr(const SockException &ex) override;
|
||||
|
||||
//for RtmpProtocol override
|
||||
void onRtmpChunk(RtmpPacket &chunkData) override;
|
||||
void onRtmpChunk(RtmpPacket &chunk_data) override;
|
||||
void onSendRawData(const Buffer::Ptr &buffer) override{
|
||||
send(buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
void onPublishResult(const SockException &ex,bool handshakeCompleted);
|
||||
void onPublishResult(const SockException &ex, bool handshake_done);
|
||||
|
||||
template<typename FUN>
|
||||
inline void addOnResultCB(const FUN &fun) {
|
||||
lock_guard<recursive_mutex> lck(_mtxOnResultCB);
|
||||
_mapOnResultCB.emplace(_iReqID, fun);
|
||||
lock_guard<recursive_mutex> lck(_mtx_on_result);
|
||||
_map_on_result.emplace(_send_req_id, fun);
|
||||
}
|
||||
template<typename FUN>
|
||||
inline void addOnStatusCB(const FUN &fun) {
|
||||
lock_guard<recursive_mutex> lck(_mtxOnStatusCB);
|
||||
_dqOnStatusCB.emplace_back(fun);
|
||||
lock_guard<recursive_mutex> lck(_mtx_on_status);
|
||||
_deque_on_status.emplace_back(fun);
|
||||
}
|
||||
|
||||
void onCmd_result(AMFDecoder &dec);
|
||||
@@ -69,23 +70,25 @@ private:
|
||||
inline void send_publish();
|
||||
inline void send_metaData();
|
||||
void setSocketFlags();
|
||||
private:
|
||||
string _strApp;
|
||||
string _strStream;
|
||||
string _strTcUrl;
|
||||
|
||||
unordered_map<int, function<void(AMFDecoder &dec)> > _mapOnResultCB;
|
||||
recursive_mutex _mtxOnResultCB;
|
||||
deque<function<void(AMFValue &dec)> > _dqOnStatusCB;
|
||||
recursive_mutex _mtxOnStatusCB;
|
||||
//超时功能实现
|
||||
std::shared_ptr<Timer> _pPublishTimer;
|
||||
//源
|
||||
std::weak_ptr<RtmpMediaSource> _pMediaSrc;
|
||||
RtmpMediaSource::RingType::RingReader::Ptr _pRtmpReader;
|
||||
private:
|
||||
string _app;
|
||||
string _stream_id;
|
||||
string _tc_url;
|
||||
|
||||
recursive_mutex _mtx_on_result;
|
||||
recursive_mutex _mtx_on_status;
|
||||
deque<function<void(AMFValue &dec)> > _deque_on_status;
|
||||
unordered_map<int, function<void(AMFDecoder &dec)> > _map_on_result;
|
||||
|
||||
//事件监听
|
||||
Event _onShutdown;
|
||||
Event _onPublished;
|
||||
Event _on_shutdown;
|
||||
Event _on_published;
|
||||
|
||||
//推流超时定时器
|
||||
std::shared_ptr<Timer> _publish_timer;
|
||||
std::weak_ptr<RtmpMediaSource> _publish_src;
|
||||
RtmpMediaSource::RingType::RingReader::Ptr _rtmp_reader;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user