mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-02 01:21:33 +08:00
commit
6518e5d448
@ -188,7 +188,7 @@ API_EXPORT void API_CALL mk_player_seektoByPos(mk_player ctx, int seekPos)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void mk_player_set_on_event(mk_player ctx, on_mk_play_event cb, void *user_data, int type) {
|
static void mk_player_set_on_event(mk_player ctx, on_mk_play_event cb, void *user_data, int type) {
|
||||||
assert(ctx && cb);
|
assert(ctx);
|
||||||
MediaPlayerForC &obj = **((MediaPlayerForC::Ptr *)ctx);
|
MediaPlayerForC &obj = **((MediaPlayerForC::Ptr *)ctx);
|
||||||
obj.setOnEvent(cb,user_data, type);
|
obj.setOnEvent(cb,user_data, type);
|
||||||
}
|
}
|
||||||
@ -202,7 +202,7 @@ API_EXPORT void API_CALL mk_player_set_on_shutdown(mk_player ctx, on_mk_play_eve
|
|||||||
}
|
}
|
||||||
|
|
||||||
API_EXPORT void API_CALL mk_player_set_on_data(mk_player ctx, on_mk_play_data cb, void *user_data) {
|
API_EXPORT void API_CALL mk_player_set_on_data(mk_player ctx, on_mk_play_data cb, void *user_data) {
|
||||||
assert(ctx && cb);
|
assert(ctx);
|
||||||
MediaPlayerForC &obj = **((MediaPlayerForC::Ptr *)ctx);
|
MediaPlayerForC &obj = **((MediaPlayerForC::Ptr *)ctx);
|
||||||
obj.setOnData(cb,user_data);
|
obj.setOnData(cb,user_data);
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,6 @@ maxStreamWaitMS=15000
|
|||||||
#某个流无人观看时,触发hook.on_stream_none_reader事件的最大等待时间,单位毫秒
|
#某个流无人观看时,触发hook.on_stream_none_reader事件的最大等待时间,单位毫秒
|
||||||
#在配合hook.on_stream_none_reader事件时,可以做到无人观看自动停止拉流或停止接收推流
|
#在配合hook.on_stream_none_reader事件时,可以做到无人观看自动停止拉流或停止接收推流
|
||||||
streamNoneReaderDelayMS=20000
|
streamNoneReaderDelayMS=20000
|
||||||
#是否开启低延时模式,该模式下禁用MSG_MORE,启用TCP_NODEALY,延时将降低,但数据发送性能将降低
|
|
||||||
ultraLowDelay=1
|
|
||||||
#拉流代理是否添加静音音频(直接拉流模式本协议无效)
|
#拉流代理是否添加静音音频(直接拉流模式本协议无效)
|
||||||
addMuteAudio=1
|
addMuteAudio=1
|
||||||
#拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
|
#拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
|
||||||
@ -43,8 +41,8 @@ publishToHls=1
|
|||||||
#是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
|
#是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
|
||||||
publishToMP4=0
|
publishToMP4=0
|
||||||
#合并写缓存大小(单位毫秒),合并写指服务器缓存一定的数据后才会一次性写入socket,这样能提高性能,但是会提高延时
|
#合并写缓存大小(单位毫秒),合并写指服务器缓存一定的数据后才会一次性写入socket,这样能提高性能,但是会提高延时
|
||||||
#在开启低延时模式后,该参数不起作用
|
#开启后会同时关闭TCP_NODELAY并开启MSG_MORE
|
||||||
mergeWriteMS=300
|
mergeWriteMS=0
|
||||||
|
|
||||||
[hls]
|
[hls]
|
||||||
#hls写文件的buf大小,调整参数可以提高文件io性能
|
#hls写文件的buf大小,调整参数可以提高文件io性能
|
||||||
@ -150,6 +148,8 @@ keepAliveSecond=15
|
|||||||
modifyStamp=0
|
modifyStamp=0
|
||||||
#rtmp服务器监听端口
|
#rtmp服务器监听端口
|
||||||
port=1935
|
port=1935
|
||||||
|
#rtmps服务器监听地址
|
||||||
|
sslport=19350
|
||||||
|
|
||||||
[rtp]
|
[rtp]
|
||||||
#音频mtu大小,该参数限制rtp最大字节数,推荐不要超过1400
|
#音频mtu大小,该参数限制rtp最大字节数,推荐不要超过1400
|
||||||
|
@ -75,8 +75,10 @@ onceToken token1([](){
|
|||||||
namespace Rtmp {
|
namespace Rtmp {
|
||||||
#define RTMP_FIELD "rtmp."
|
#define RTMP_FIELD "rtmp."
|
||||||
const string kPort = RTMP_FIELD"port";
|
const string kPort = RTMP_FIELD"port";
|
||||||
|
const string kSSLPort = RTMP_FIELD"sslport";
|
||||||
onceToken token1([](){
|
onceToken token1([](){
|
||||||
mINI::Instance()[kPort] = 1935;
|
mINI::Instance()[kPort] = 1935;
|
||||||
|
mINI::Instance()[kSSLPort] = 19350;
|
||||||
},nullptr);
|
},nullptr);
|
||||||
} //namespace RTMP
|
} //namespace RTMP
|
||||||
|
|
||||||
@ -255,9 +257,10 @@ int start_main(int argc,char *argv[]) {
|
|||||||
uint16_t rtspPort = mINI::Instance()[Rtsp::kPort];
|
uint16_t rtspPort = mINI::Instance()[Rtsp::kPort];
|
||||||
uint16_t rtspsPort = mINI::Instance()[Rtsp::kSSLPort];
|
uint16_t rtspsPort = mINI::Instance()[Rtsp::kSSLPort];
|
||||||
uint16_t rtmpPort = mINI::Instance()[Rtmp::kPort];
|
uint16_t rtmpPort = mINI::Instance()[Rtmp::kPort];
|
||||||
|
uint16_t rtmpsPort = mINI::Instance()[Rtmp::kSSLPort];
|
||||||
uint16_t httpPort = mINI::Instance()[Http::kPort];
|
uint16_t httpPort = mINI::Instance()[Http::kPort];
|
||||||
uint16_t httpsPort = mINI::Instance()[Http::kSSLPort];
|
uint16_t httpsPort = mINI::Instance()[Http::kSSLPort];
|
||||||
uint16_t rtp_proxy = mINI::Instance()[RtpProxy::kPort];
|
uint16_t rtpPort = mINI::Instance()[RtpProxy::kPort];
|
||||||
|
|
||||||
//设置poller线程数,该函数必须在使用ZLToolKit网络相关对象之前调用才能生效
|
//设置poller线程数,该函数必须在使用ZLToolKit网络相关对象之前调用才能生效
|
||||||
EventPollerPool::setPoolSize(threads);
|
EventPollerPool::setPoolSize(threads);
|
||||||
@ -265,38 +268,51 @@ int start_main(int argc,char *argv[]) {
|
|||||||
//简单的telnet服务器,可用于服务器调试,但是不能使用23端口,否则telnet上了莫名其妙的现象
|
//简单的telnet服务器,可用于服务器调试,但是不能使用23端口,否则telnet上了莫名其妙的现象
|
||||||
//测试方法:telnet 127.0.0.1 9000
|
//测试方法:telnet 127.0.0.1 9000
|
||||||
TcpServer::Ptr shellSrv(new TcpServer());
|
TcpServer::Ptr shellSrv(new TcpServer());
|
||||||
|
|
||||||
|
//rtsp[s]服务器, 可用于诸如亚马逊echo show这样的设备访问
|
||||||
TcpServer::Ptr rtspSrv(new TcpServer());
|
TcpServer::Ptr rtspSrv(new TcpServer());
|
||||||
TcpServer::Ptr rtmpSrv(new TcpServer());
|
|
||||||
TcpServer::Ptr httpSrv(new TcpServer());
|
|
||||||
//如果支持ssl,还可以开启https服务器
|
|
||||||
TcpServer::Ptr httpsSrv(new TcpServer());
|
|
||||||
//支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问
|
|
||||||
TcpServer::Ptr rtspSSLSrv(new TcpServer());
|
TcpServer::Ptr rtspSSLSrv(new TcpServer());
|
||||||
|
|
||||||
|
//rtmp[s]服务器
|
||||||
|
TcpServer::Ptr rtmpSrv(new TcpServer());
|
||||||
|
TcpServer::Ptr rtmpsSrv(new TcpServer());
|
||||||
|
|
||||||
|
//http[s]服务器
|
||||||
|
TcpServer::Ptr httpSrv(new TcpServer());
|
||||||
|
TcpServer::Ptr httpsSrv(new TcpServer());
|
||||||
|
|
||||||
#if defined(ENABLE_RTPPROXY)
|
#if defined(ENABLE_RTPPROXY)
|
||||||
|
//GB28181 rtp推流端口,支持UDP/TCP
|
||||||
UdpRecver recver;
|
UdpRecver recver;
|
||||||
TcpServer::Ptr tcpRtpServer(new TcpServer());
|
TcpServer::Ptr tcpRtpServer(new TcpServer());
|
||||||
#endif//defined(ENABLE_RTPPROXY)
|
#endif//defined(ENABLE_RTPPROXY)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//rtsp服务器,端口默认554
|
//rtsp服务器,端口默认554
|
||||||
rtspSrv->start<RtspSession>(rtspPort);//默认554
|
if(rtspPort) { rtspSrv->start<RtspSession>(rtspPort); }
|
||||||
//rtsps服务器,端口默认322
|
//rtsps服务器,端口默认322
|
||||||
rtspSSLSrv->start<RtspSessionWithSSL>(rtspsPort);
|
if(rtspsPort) { rtspSSLSrv->start<RtspSessionWithSSL>(rtspsPort); }
|
||||||
|
|
||||||
//rtmp服务器,端口默认1935
|
//rtmp服务器,端口默认1935
|
||||||
rtmpSrv->start<RtmpSession>(rtmpPort);
|
if(rtmpPort) { rtmpSrv->start<RtmpSession>(rtmpPort); }
|
||||||
|
//rtmps服务器,端口默认19350
|
||||||
|
if(rtmpsPort) { rtmpsSrv->start<RtmpSessionWithSSL>(rtmpsPort); }
|
||||||
|
|
||||||
//http服务器,端口默认80
|
//http服务器,端口默认80
|
||||||
httpSrv->start<HttpSession>(httpPort);
|
if(httpPort) { httpSrv->start<HttpSession>(httpPort); }
|
||||||
//https服务器,端口默认443
|
//https服务器,端口默认443
|
||||||
httpsSrv->start<HttpsSession>(httpsPort);
|
if(httpsPort) { httpsSrv->start<HttpsSession>(httpsPort); }
|
||||||
|
|
||||||
//telnet远程调试服务器
|
//telnet远程调试服务器
|
||||||
shellSrv->start<ShellSession>(shellPort);
|
if(shellPort) { shellSrv->start<ShellSession>(shellPort); }
|
||||||
|
|
||||||
#if defined(ENABLE_RTPPROXY)
|
#if defined(ENABLE_RTPPROXY)
|
||||||
|
if(rtpPort){
|
||||||
//创建rtp udp服务器
|
//创建rtp udp服务器
|
||||||
recver.initSock(rtp_proxy);
|
recver.initSock(rtpPort);
|
||||||
//创建rtp tcp服务器
|
//创建rtp tcp服务器
|
||||||
tcpRtpServer->start<RtpSession>(rtp_proxy);
|
tcpRtpServer->start<RtpSession>(rtpPort);
|
||||||
|
}
|
||||||
#endif//defined(ENABLE_RTPPROXY)
|
#endif//defined(ENABLE_RTPPROXY)
|
||||||
|
|
||||||
}catch (std::exception &ex){
|
}catch (std::exception &ex){
|
||||||
|
@ -516,9 +516,8 @@ static bool isFlushAble_merge(bool is_audio, uint32_t last_stamp, uint32_t new_s
|
|||||||
|
|
||||||
bool FlushPolicy::isFlushAble(uint32_t new_stamp, int cache_size) {
|
bool FlushPolicy::isFlushAble(uint32_t new_stamp, int cache_size) {
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
GET_CONFIG(bool, ultraLowDelay, General::kUltraLowDelay);
|
|
||||||
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
||||||
if (ultraLowDelay || mergeWriteMS <= 0) {
|
if (mergeWriteMS <= 0) {
|
||||||
//关闭了合并写或者合并写阈值小于等于0
|
//关闭了合并写或者合并写阈值小于等于0
|
||||||
ret = isFlushAble_default(_is_audio, _last_stamp, new_stamp, cache_size);
|
ret = isFlushAble_default(_is_audio, _last_stamp, new_stamp, cache_size);
|
||||||
} else {
|
} else {
|
||||||
|
@ -22,17 +22,21 @@ MultiMuxerPrivate::MultiMuxerPrivate(const string &vhost,
|
|||||||
bool enable_mp4) {
|
bool enable_mp4) {
|
||||||
if (enable_rtmp) {
|
if (enable_rtmp) {
|
||||||
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleMeta>(dur_sec));
|
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleMeta>(dur_sec));
|
||||||
|
_enable_rtxp = true;
|
||||||
}
|
}
|
||||||
if (enable_rtsp) {
|
if (enable_rtsp) {
|
||||||
_rtsp = std::make_shared<RtspMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleSdp>(dur_sec));
|
_rtsp = std::make_shared<RtspMediaSourceMuxer>(vhost, app, stream, std::make_shared<TitleSdp>(dur_sec));
|
||||||
|
_enable_rtxp = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enable_hls) {
|
if (enable_hls) {
|
||||||
_hls = Recorder::createRecorder(Recorder::type_hls, vhost, app, stream);
|
_hls = Recorder::createRecorder(Recorder::type_hls, vhost, app, stream);
|
||||||
|
_enable_record = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enable_mp4) {
|
if (enable_mp4) {
|
||||||
_mp4 = Recorder::createRecorder(Recorder::type_mp4, vhost, app, stream);
|
_mp4 = Recorder::createRecorder(Recorder::type_mp4, vhost, app, stream);
|
||||||
|
_enable_record = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,8 +81,8 @@ int MultiMuxerPrivate::totalReaderCount() const {
|
|||||||
return (_rtsp ? _rtsp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) + (hls_src ? hls_src->readerCount() : 0);
|
return (_rtsp ? _rtsp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) + (hls_src ? hls_src->readerCount() : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::shared_ptr<MediaSinkInterface> makeRecorder(const vector<Track::Ptr> &tracks, Recorder::type type, MediaSource &sender){
|
static std::shared_ptr<MediaSinkInterface> makeRecorder(const vector<Track::Ptr> &tracks, Recorder::type type, const string &custom_path, MediaSource &sender){
|
||||||
auto recorder = Recorder::createRecorder(type, sender.getVhost(), sender.getApp(), sender.getId());
|
auto recorder = Recorder::createRecorder(type, sender.getVhost(), sender.getApp(), sender.getId(), custom_path);
|
||||||
for (auto &track : tracks) {
|
for (auto &track : tracks) {
|
||||||
recorder->addTrack(track);
|
recorder->addTrack(track);
|
||||||
}
|
}
|
||||||
@ -91,7 +95,7 @@ bool MultiMuxerPrivate::setupRecord(MediaSource &sender, Recorder::type type, bo
|
|||||||
case Recorder::type_hls : {
|
case Recorder::type_hls : {
|
||||||
if (start && !_hls) {
|
if (start && !_hls) {
|
||||||
//开始录制
|
//开始录制
|
||||||
_hls = makeRecorder(getTracks(true), type, sender);
|
_hls = makeRecorder(getTracks(true), type, custom_path, sender);
|
||||||
auto hls_src = getHlsMediaSource();
|
auto hls_src = getHlsMediaSource();
|
||||||
if (hls_src) {
|
if (hls_src) {
|
||||||
//设置HlsMediaSource的事件监听器
|
//设置HlsMediaSource的事件监听器
|
||||||
@ -102,20 +106,21 @@ bool MultiMuxerPrivate::setupRecord(MediaSource &sender, Recorder::type type, bo
|
|||||||
//停止录制
|
//停止录制
|
||||||
_hls = nullptr;
|
_hls = nullptr;
|
||||||
}
|
}
|
||||||
|
_enable_record = _hls || _mp4;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case Recorder::type_mp4 : {
|
case Recorder::type_mp4 : {
|
||||||
if (start && !_mp4) {
|
if (start && !_mp4) {
|
||||||
//开始录制
|
//开始录制
|
||||||
_mp4 = makeRecorder(getTracks(true), type, sender);;
|
_mp4 = makeRecorder(getTracks(true), type, custom_path, sender);
|
||||||
} else if (!start && _mp4) {
|
} else if (!start && _mp4) {
|
||||||
//停止录制
|
//停止录制
|
||||||
_mp4 = nullptr;
|
_mp4 = nullptr;
|
||||||
}
|
}
|
||||||
|
_enable_record = _hls || _mp4;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
default:
|
default : return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,6 +168,10 @@ void MultiMuxerPrivate::onTrackReady(const Track::Ptr &track) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MultiMuxerPrivate::isEnabled(){
|
||||||
|
return _enable_rtxp || _enable_record;
|
||||||
|
}
|
||||||
|
|
||||||
void MultiMuxerPrivate::onTrackFrame(const Frame::Ptr &frame) {
|
void MultiMuxerPrivate::onTrackFrame(const Frame::Ptr &frame) {
|
||||||
if (_rtmp) {
|
if (_rtmp) {
|
||||||
_rtmp->inputFrame(frame);
|
_rtmp->inputFrame(frame);
|
||||||
@ -293,4 +302,9 @@ void MultiMediaSourceMuxer::inputFrame(const Frame::Ptr &frame) {
|
|||||||
_muxer->inputFrame(frame);
|
_muxer->inputFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MultiMediaSourceMuxer::isEnabled(){
|
||||||
|
return _muxer->isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
@ -45,6 +45,7 @@ private:
|
|||||||
void setTrackListener(Listener *listener);
|
void setTrackListener(Listener *listener);
|
||||||
bool setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path);
|
bool setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path);
|
||||||
bool isRecording(MediaSource &sender, Recorder::type type);
|
bool isRecording(MediaSource &sender, Recorder::type type);
|
||||||
|
bool isEnabled();
|
||||||
private:
|
private:
|
||||||
void onTrackReady(const Track::Ptr & track) override;
|
void onTrackReady(const Track::Ptr & track) override;
|
||||||
void onTrackFrame(const Frame::Ptr &frame) override;
|
void onTrackFrame(const Frame::Ptr &frame) override;
|
||||||
@ -57,6 +58,8 @@ private:
|
|||||||
MediaSinkInterface::Ptr _mp4;
|
MediaSinkInterface::Ptr _mp4;
|
||||||
Listener *_listener = nullptr;
|
Listener *_listener = nullptr;
|
||||||
std::weak_ptr<MediaSourceEvent> _meida_listener;
|
std::weak_ptr<MediaSourceEvent> _meida_listener;
|
||||||
|
bool _enable_rtxp = false;
|
||||||
|
bool _enable_record = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MultiMediaSourceMuxer : public MediaSourceEvent, public MediaSinkInterface, public TrackSource, public std::enable_shared_from_this<MultiMediaSourceMuxer>{
|
class MultiMediaSourceMuxer : public MediaSourceEvent, public MediaSinkInterface, public TrackSource, public std::enable_shared_from_this<MultiMediaSourceMuxer>{
|
||||||
@ -167,6 +170,11 @@ public:
|
|||||||
* @param frame 帧
|
* @param frame 帧
|
||||||
*/
|
*/
|
||||||
void inputFrame(const Frame::Ptr &frame) override;
|
void inputFrame(const Frame::Ptr &frame) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否生效(是否正在转其他协议)
|
||||||
|
*/
|
||||||
|
bool isEnabled();
|
||||||
private:
|
private:
|
||||||
MultiMuxerPrivate::Ptr _muxer;
|
MultiMuxerPrivate::Ptr _muxer;
|
||||||
std::weak_ptr<MediaSourceEvent> _listener;
|
std::weak_ptr<MediaSourceEvent> _listener;
|
||||||
|
@ -96,58 +96,84 @@ int64_t Stamp::getRelativeStamp() const {
|
|||||||
return _relativeStamp;
|
return _relativeStamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool DtsGenerator::getDts(uint32_t pts, uint32_t &dts){
|
bool DtsGenerator::getDts(uint32_t pts, uint32_t &dts){
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
if(pts == _last_pts){
|
if (pts == _last_pts) {
|
||||||
//pts未变,返回上次结果
|
//pts未变,说明dts也不会变,返回上次dts
|
||||||
if(_last_dts){
|
if (_last_dts) {
|
||||||
dts = _last_dts;
|
dts = _last_dts;
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
return ret;
|
} else {
|
||||||
}
|
//pts变了,尝试计算dts
|
||||||
|
ret = getDts_l(pts, dts);
|
||||||
ret = getDts_l(pts,dts);
|
if (ret) {
|
||||||
if(ret){
|
//获取到了dts,保存本次结果
|
||||||
//保存本次结果
|
|
||||||
_last_dts = dts;
|
_last_dts = dts;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
//pts排序列队长度还不知道,也就是不知道有没有B帧,
|
||||||
|
//那么先强制dts == pts,这样可能导致有B帧的情况下,起始画面有几帧回退
|
||||||
|
dts = pts;
|
||||||
|
}
|
||||||
|
|
||||||
//记录上次pts
|
//记录上次pts
|
||||||
_last_pts = pts;
|
_last_pts = pts;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//该算法核心思想是对pts进行排序,排序好的pts就是dts。
|
||||||
|
//排序有一定的滞后性,那么需要加上排序导致的时间戳偏移量
|
||||||
bool DtsGenerator::getDts_l(uint32_t pts, uint32_t &dts){
|
bool DtsGenerator::getDts_l(uint32_t pts, uint32_t &dts){
|
||||||
if(_sorter_max_size == 1){
|
if(_sorter_max_size == 1){
|
||||||
//没有B帧
|
//没有B帧,dts就等于pts
|
||||||
dts = pts;
|
dts = pts;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!_sorter_max_size){
|
if(!_sorter_max_size){
|
||||||
|
//尚未计算出pts排序列队长度(也就是P帧间B帧个数)
|
||||||
if(pts > _last_max_pts){
|
if(pts > _last_max_pts){
|
||||||
|
//pts时间戳增加了,那么说明这帧画面不是B帧(说明是P帧或关键帧)
|
||||||
if(_frames_since_last_max_pts && _count_sorter_max_size++ > 0){
|
if(_frames_since_last_max_pts && _count_sorter_max_size++ > 0){
|
||||||
|
//已经出现多次非B帧的情况,那么我们就能知道P帧间B帧的个数
|
||||||
_sorter_max_size = _frames_since_last_max_pts;
|
_sorter_max_size = _frames_since_last_max_pts;
|
||||||
|
//我们记录P帧间时间间隔(也就是多个B帧时间戳增量累计)
|
||||||
_dts_pts_offset = (pts - _last_max_pts) / 2;
|
_dts_pts_offset = (pts - _last_max_pts) / 2;
|
||||||
}
|
}
|
||||||
|
//遇到P帧或关键帧,连续B帧计数清零
|
||||||
_frames_since_last_max_pts = 0;
|
_frames_since_last_max_pts = 0;
|
||||||
|
//记录上次非B帧的pts时间戳(同时也是dts),用于统计连续B帧时间戳增量
|
||||||
_last_max_pts = pts;
|
_last_max_pts = pts;
|
||||||
}
|
}
|
||||||
|
//如果pts时间戳小于上一个P帧,那么断定这个是B帧,我们记录B帧连续个数
|
||||||
++_frames_since_last_max_pts;
|
++_frames_since_last_max_pts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//pts放入排序缓存列队,缓存列队最大等于连续B帧个数
|
||||||
_pts_sorter.emplace(pts);
|
_pts_sorter.emplace(pts);
|
||||||
|
|
||||||
if(_sorter_max_size && _pts_sorter.size() > _sorter_max_size){
|
if(_sorter_max_size && _pts_sorter.size() > _sorter_max_size){
|
||||||
|
//如果启用了pts排序(意味着存在B帧),并且pts排序缓存列队长度大于连续B帧个数,
|
||||||
|
//意味着后续的pts都会比最早的pts大,那么说明可以取出最早的pts了,这个pts将当做该帧的dts基准
|
||||||
auto it = _pts_sorter.begin();
|
auto it = _pts_sorter.begin();
|
||||||
|
|
||||||
|
//由于该pts是前面偏移了个_sorter_max_size帧的pts(也就是那帧画面的dts),
|
||||||
|
//那么我们加上时间戳偏移量,基本等于该帧的dts
|
||||||
dts = *it + _dts_pts_offset;
|
dts = *it + _dts_pts_offset;
|
||||||
if(dts > pts){
|
if(dts > pts){
|
||||||
//dts不能大于pts(基本不可能到达这个逻辑)
|
//dts不能大于pts(基本不可能到达这个逻辑)
|
||||||
dts = pts;
|
dts = pts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//pts排序缓存出列
|
||||||
_pts_sorter.erase(it);
|
_pts_sorter.erase(it);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//排序缓存尚未满
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,6 @@ const string kFlowThreshold = GENERAL_FIELD"flowThreshold";
|
|||||||
const string kStreamNoneReaderDelayMS = GENERAL_FIELD"streamNoneReaderDelayMS";
|
const string kStreamNoneReaderDelayMS = GENERAL_FIELD"streamNoneReaderDelayMS";
|
||||||
const string kMaxStreamWaitTimeMS = GENERAL_FIELD"maxStreamWaitMS";
|
const string kMaxStreamWaitTimeMS = GENERAL_FIELD"maxStreamWaitMS";
|
||||||
const string kEnableVhost = GENERAL_FIELD"enableVhost";
|
const string kEnableVhost = GENERAL_FIELD"enableVhost";
|
||||||
const string kUltraLowDelay = GENERAL_FIELD"ultraLowDelay";
|
|
||||||
const string kAddMuteAudio = GENERAL_FIELD"addMuteAudio";
|
const string kAddMuteAudio = GENERAL_FIELD"addMuteAudio";
|
||||||
const string kResetWhenRePlay = GENERAL_FIELD"resetWhenRePlay";
|
const string kResetWhenRePlay = GENERAL_FIELD"resetWhenRePlay";
|
||||||
const string kPublishToRtxp = GENERAL_FIELD"publishToRtxp";
|
const string kPublishToRtxp = GENERAL_FIELD"publishToRtxp";
|
||||||
@ -74,13 +73,12 @@ onceToken token([](){
|
|||||||
mINI::Instance()[kStreamNoneReaderDelayMS] = 20 * 1000;
|
mINI::Instance()[kStreamNoneReaderDelayMS] = 20 * 1000;
|
||||||
mINI::Instance()[kMaxStreamWaitTimeMS] = 15 * 1000;
|
mINI::Instance()[kMaxStreamWaitTimeMS] = 15 * 1000;
|
||||||
mINI::Instance()[kEnableVhost] = 0;
|
mINI::Instance()[kEnableVhost] = 0;
|
||||||
mINI::Instance()[kUltraLowDelay] = 1;
|
|
||||||
mINI::Instance()[kAddMuteAudio] = 1;
|
mINI::Instance()[kAddMuteAudio] = 1;
|
||||||
mINI::Instance()[kResetWhenRePlay] = 1;
|
mINI::Instance()[kResetWhenRePlay] = 1;
|
||||||
mINI::Instance()[kPublishToRtxp] = 1;
|
mINI::Instance()[kPublishToRtxp] = 1;
|
||||||
mINI::Instance()[kPublishToHls] = 1;
|
mINI::Instance()[kPublishToHls] = 1;
|
||||||
mINI::Instance()[kPublishToMP4] = 0;
|
mINI::Instance()[kPublishToMP4] = 0;
|
||||||
mINI::Instance()[kMergeWriteMS] = 300;
|
mINI::Instance()[kMergeWriteMS] = 0;
|
||||||
},nullptr);
|
},nullptr);
|
||||||
|
|
||||||
}//namespace General
|
}//namespace General
|
||||||
|
@ -160,8 +160,6 @@ extern const string kStreamNoneReaderDelayMS;
|
|||||||
extern const string kMaxStreamWaitTimeMS;
|
extern const string kMaxStreamWaitTimeMS;
|
||||||
//是否启动虚拟主机
|
//是否启动虚拟主机
|
||||||
extern const string kEnableVhost;
|
extern const string kEnableVhost;
|
||||||
//超低延时模式,默认打开,打开后会降低延时但是转发性能会稍差
|
|
||||||
extern const string kUltraLowDelay;
|
|
||||||
//拉流代理时是否添加静音音频
|
//拉流代理时是否添加静音音频
|
||||||
extern const string kAddMuteAudio;
|
extern const string kAddMuteAudio;
|
||||||
//拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
|
//拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
|
||||||
@ -174,7 +172,7 @@ extern const string kPublishToHls ;
|
|||||||
//是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
|
//是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置
|
||||||
extern const string kPublishToMP4 ;
|
extern const string kPublishToMP4 ;
|
||||||
//合并写缓存大小(单位毫秒),合并写指服务器缓存一定的数据后才会一次性写入socket,这样能提高性能,但是会提高延时
|
//合并写缓存大小(单位毫秒),合并写指服务器缓存一定的数据后才会一次性写入socket,这样能提高性能,但是会提高延时
|
||||||
//在开启低延时模式后,该参数不起作用
|
//开启后会同时关闭TCP_NODELAY并开启MSG_MORE
|
||||||
extern const string kMergeWriteMS ;
|
extern const string kMergeWriteMS ;
|
||||||
}//namespace General
|
}//namespace General
|
||||||
|
|
||||||
|
@ -50,6 +50,9 @@ void splitH264(const char *ptr, int len, const std::function<void(const char *,
|
|||||||
while(true) {
|
while(true) {
|
||||||
auto next_nal = memfind(nal + 3,end - nal - 3,"\x0\x0\x1",3);
|
auto next_nal = memfind(nal + 3,end - nal - 3,"\x0\x0\x1",3);
|
||||||
if(next_nal){
|
if(next_nal){
|
||||||
|
if(*(next_nal - 1) == 0x00){
|
||||||
|
next_nal -= 1;
|
||||||
|
}
|
||||||
cb(nal,next_nal - nal);
|
cb(nal,next_nal - nal);
|
||||||
nal = next_nal;
|
nal = next_nal;
|
||||||
continue;
|
continue;
|
||||||
@ -59,6 +62,18 @@ void splitH264(const char *ptr, int len, const std::function<void(const char *,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
//splitH264函数测试程序
|
||||||
|
static onceToken s_token([](){
|
||||||
|
char buf[] = "\x00\x00\x00\x01\x12\x23\x34\x45\x56"
|
||||||
|
"\x00\x00\x00\x01\x12\x23\x34\x45\x56"
|
||||||
|
"\x00\x00\x00\x01\x12\x23\x34\x45\x56"
|
||||||
|
"\x00\x00\x01\x12\x23\x34\x45\x56";
|
||||||
|
splitH264(buf, sizeof(buf) - 1, [](const char *ptr, int len){
|
||||||
|
cout << hexdump(ptr, len) << endl;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
#endif //0
|
||||||
|
|
||||||
Sdp::Ptr H264Track::getSdp() {
|
Sdp::Ptr H264Track::getSdp() {
|
||||||
if(!ready()){
|
if(!ready()){
|
||||||
|
@ -191,19 +191,10 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void H264RtpDecoder::onGetH264(const H264Frame::Ptr &frame) {
|
void H264RtpDecoder::onGetH264(const H264Frame::Ptr &frame) {
|
||||||
auto flag = _dts_generator.getDts(frame->_pts,frame->_dts);
|
//rtsp没有dts,那么根据pts排序算法生成dts
|
||||||
if(!flag){
|
_dts_generator.getDts(frame->_pts,frame->_dts);
|
||||||
if(frame->configFrame() || frame->keyFrame()){
|
|
||||||
flag = true;
|
|
||||||
frame->_dts = frame->_pts;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//根据pts计算dts
|
|
||||||
if(flag){
|
|
||||||
//写入环形缓存
|
//写入环形缓存
|
||||||
RtpCodec::inputFrame(frame);
|
RtpCodec::inputFrame(frame);
|
||||||
}
|
|
||||||
_h264frame = obtainFrame();
|
_h264frame = obtainFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,9 @@ public:
|
|||||||
NAL_IDR_W_RADL = 19,
|
NAL_IDR_W_RADL = 19,
|
||||||
NAL_IDR_N_LP = 20,
|
NAL_IDR_N_LP = 20,
|
||||||
NAL_CRA_NUT = 21,
|
NAL_CRA_NUT = 21,
|
||||||
|
NAL_RSV_IRAP_VCL22 = 22,
|
||||||
|
NAL_RSV_IRAP_VCL23 = 23,
|
||||||
|
|
||||||
NAL_VPS = 32,
|
NAL_VPS = 32,
|
||||||
NAL_SPS = 33,
|
NAL_SPS = 33,
|
||||||
NAL_PPS = 34,
|
NAL_PPS = 34,
|
||||||
@ -101,17 +104,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool isKeyFrame(int type) {
|
static bool isKeyFrame(int type) {
|
||||||
switch (type) {
|
return type >= NAL_BLA_W_LP && type <= NAL_RSV_IRAP_VCL23;
|
||||||
case NAL_BLA_N_LP:
|
|
||||||
case NAL_BLA_W_LP:
|
|
||||||
case NAL_BLA_W_RADL:
|
|
||||||
case NAL_CRA_NUT:
|
|
||||||
case NAL_IDR_N_LP:
|
|
||||||
case NAL_IDR_W_RADL:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -127,18 +127,10 @@ bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void H265RtpDecoder::onGetH265(const H265Frame::Ptr &frame) {
|
void H265RtpDecoder::onGetH265(const H265Frame::Ptr &frame) {
|
||||||
//计算dts
|
//rtsp没有dts,那么根据pts排序算法生成dts
|
||||||
auto flag = _dts_generator.getDts(frame->_pts,frame->_dts);
|
_dts_generator.getDts(frame->_pts,frame->_dts);
|
||||||
if(!flag){
|
|
||||||
if(frame->configFrame() || frame->keyFrame()){
|
|
||||||
flag = true;
|
|
||||||
frame->_dts = frame->_pts;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(flag){
|
|
||||||
//写入环形缓存
|
//写入环形缓存
|
||||||
RtpCodec::inputFrame(frame);
|
RtpCodec::inputFrame(frame);
|
||||||
}
|
|
||||||
_h265frame = obtainFrame();
|
_h265frame = obtainFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -606,8 +606,8 @@ void HttpSession::sendNotFound(bool bClose) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::setSocketFlags(){
|
void HttpSession::setSocketFlags(){
|
||||||
GET_CONFIG(bool,ultraLowDelay,General::kUltraLowDelay);
|
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
||||||
if(!ultraLowDelay) {
|
if(mergeWriteMS > 0) {
|
||||||
//推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高
|
//推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高
|
||||||
SockUtil::setNoDelay(_sock->rawFD(), false);
|
SockUtil::setNoDelay(_sock->rawFD(), false);
|
||||||
//播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能
|
//播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能
|
||||||
|
@ -56,7 +56,7 @@ string Recorder::getRecordPath(Recorder::type type, const string &vhost, const s
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path){
|
std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path){
|
||||||
auto path = Recorder::getRecordPath(type, vhost, app, stream_id);
|
auto path = Recorder::getRecordPath(type, vhost, app, stream_id, customized_path);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Recorder::type_hls: {
|
case Recorder::type_hls: {
|
||||||
#if defined(ENABLE_HLS)
|
#if defined(ENABLE_HLS)
|
||||||
|
@ -57,7 +57,14 @@ public:
|
|||||||
* 输入rtmp并解析
|
* 输入rtmp并解析
|
||||||
*/
|
*/
|
||||||
void onWrite(const RtmpPacket::Ptr &pkt,bool key_pos = true) override {
|
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);
|
key_pos = _demuxer->inputRtmp(pkt);
|
||||||
|
}
|
||||||
|
|
||||||
RtmpMediaSource::onWrite(pkt,key_pos);
|
RtmpMediaSource::onWrite(pkt,key_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,10 +145,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
void onAllTrackReady() override{
|
void onAllTrackReady() override{
|
||||||
setTrackSource(_muxer);
|
setTrackSource(_muxer);
|
||||||
|
_all_track_ready = true;
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
RtmpDemuxer::Ptr _demuxer;
|
RtmpDemuxer::Ptr _demuxer;
|
||||||
MultiMediaSourceMuxer::Ptr _muxer;
|
MultiMediaSourceMuxer::Ptr _muxer;
|
||||||
|
bool _all_track_ready = false;
|
||||||
};
|
};
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
|
||||||
|
@ -228,8 +228,8 @@ inline void RtmpPusher::send_metaData(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPusher::setSocketFlags(){
|
void RtmpPusher::setSocketFlags(){
|
||||||
GET_CONFIG(bool,ultraLowDelay,General::kUltraLowDelay);
|
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
||||||
if(!ultraLowDelay) {
|
if(mergeWriteMS > 0) {
|
||||||
//提高发送性能
|
//提高发送性能
|
||||||
setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
|
setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
|
||||||
SockUtil::setNoDelay(_sock->rawFD(), false);
|
SockUtil::setNoDelay(_sock->rawFD(), false);
|
||||||
|
@ -529,8 +529,8 @@ int RtmpSession::totalReaderCount(MediaSource &sender) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RtmpSession::setSocketFlags(){
|
void RtmpSession::setSocketFlags(){
|
||||||
GET_CONFIG(bool,ultraLowDelay,General::kUltraLowDelay);
|
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
||||||
if(!ultraLowDelay) {
|
if(mergeWriteMS > 0) {
|
||||||
//推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高
|
//推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高
|
||||||
SockUtil::setNoDelay(_sock->rawFD(), false);
|
SockUtil::setNoDelay(_sock->rawFD(), false);
|
||||||
//播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能
|
//播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能
|
||||||
|
@ -91,6 +91,12 @@ private:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支持ssl加密的rtmp服务器
|
||||||
|
*/
|
||||||
|
typedef TcpSessionWithSSL<RtmpSession> RtmpSessionWithSSL;
|
||||||
|
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
|
||||||
#endif /* SRC_RTMP_RTMPSESSION_H_ */
|
#endif /* SRC_RTMP_RTMPSESSION_H_ */
|
||||||
|
@ -48,7 +48,14 @@ public:
|
|||||||
* 输入rtp并解析
|
* 输入rtp并解析
|
||||||
*/
|
*/
|
||||||
void onWrite(const RtpPacket::Ptr &rtp, bool key_pos) override {
|
void onWrite(const RtpPacket::Ptr &rtp, bool key_pos) override {
|
||||||
|
if(_all_track_ready && !_muxer->isEnabled()){
|
||||||
|
//获取到所有Track后,并且未开启转协议,那么不需要解复用rtp
|
||||||
|
//在关闭rtp解复用后,无法知道是否为关键帧,这样会导致无法秒开,或者开播花屏
|
||||||
|
key_pos = rtp->type == TrackVideo;
|
||||||
|
}else{
|
||||||
|
//需要解复用rtp
|
||||||
key_pos = _demuxer->inputRtp(rtp);
|
key_pos = _demuxer->inputRtp(rtp);
|
||||||
|
}
|
||||||
RtspMediaSource::onWrite(rtp, key_pos);
|
RtspMediaSource::onWrite(rtp, key_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,10 +136,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
void onAllTrackReady() override{
|
void onAllTrackReady() override{
|
||||||
setTrackSource(_muxer);
|
setTrackSource(_muxer);
|
||||||
|
_all_track_ready = true;
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
RtspDemuxer::Ptr _demuxer;
|
RtspDemuxer::Ptr _demuxer;
|
||||||
MultiMediaSourceMuxer::Ptr _muxer;
|
MultiMediaSourceMuxer::Ptr _muxer;
|
||||||
|
bool _all_track_ready = false;
|
||||||
};
|
};
|
||||||
} /* namespace mediakit */
|
} /* namespace mediakit */
|
||||||
|
|
||||||
|
@ -392,8 +392,8 @@ void RtspPusher::sendRecord() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RtspPusher::setSocketFlags(){
|
void RtspPusher::setSocketFlags(){
|
||||||
GET_CONFIG(bool,ultraLowDelay,General::kUltraLowDelay);
|
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
||||||
if(!ultraLowDelay) {
|
if(mergeWriteMS > 0) {
|
||||||
//提高发送性能
|
//提高发送性能
|
||||||
setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
|
setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
|
||||||
SockUtil::setNoDelay(_sock->rawFD(), false);
|
SockUtil::setNoDelay(_sock->rawFD(), false);
|
||||||
|
@ -1236,8 +1236,8 @@ void RtspSession::sendSenderReport(bool overTcp,int iTrackIndex) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RtspSession::setSocketFlags(){
|
void RtspSession::setSocketFlags(){
|
||||||
GET_CONFIG(bool,ultraLowDelay,General::kUltraLowDelay);
|
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
||||||
if(!ultraLowDelay) {
|
if(mergeWriteMS > 0) {
|
||||||
//推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高
|
//推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高
|
||||||
SockUtil::setNoDelay(_sock->rawFD(), false);
|
SockUtil::setNoDelay(_sock->rawFD(), false);
|
||||||
//播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能
|
//播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能
|
||||||
|
Loading…
Reference in New Issue
Block a user