diff --git a/.gitattributes b/.gitattributes index 292d29d3..b6649863 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ release/ filter=lfs diff=lfs merge=lfs -text *.a filter=lfs diff=lfs merge=lfs -text +*.h linguist-language=cpp +*.c linguist-language=cpp diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..4bf33d7a --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,5 @@ +# These are supported funding model platforms +custom: ['https://www.paypal.me/xiachu'] +ko_fi: xiachu +issuehunt: xiongziliang +liberapay: xiachu diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index 665f53b6..4a6029b7 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit 665f53b6a4385e2312d3bf09aa305d7e3bf079e6 +Subproject commit 4a6029b74b4f2339e32b8c546388de51e4ec1bcb diff --git a/conf/config.ini b/conf/config.ini index 28112f69..cbbcb96d 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -6,12 +6,13 @@ apiDebug=1 secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc [ffmpeg] -#FFmpeg可执行程序路径 +#FFmpeg可执行程序绝对路径 bin=/usr/local/bin/ffmpeg #FFmpeg拉流再推流的命令模板,通过该模板可以设置再编码的一些参数 cmd=%s -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s #FFmpeg日志的路径,如果置空则不生成FFmpeg日志 -log=/Users/xzl/git/ZLMediaKit/release/mac/Release/ffmpeg/ffmpeg.log +#可以为相对(相对于本可执行程序目录)或绝对路径 +log=./ffmpeg/ffmpeg.log [general] #是否启用虚拟主机 @@ -40,7 +41,8 @@ resetWhenRePlay=1 #hls写文件的buf大小,调整参数可以提高文件io性能 fileBufSize=65536 #hls保存文件路径 -filePath=/Users/xzl/git/ZLMediaKit/release/mac/Release/httpRoot +#可以为相对(相对于本可执行程序目录)或绝对路径 +filePath=./httpRoot #hls最大切片时间 segDur=3 #m3u8索引中,hls保留切片个数(实际保留切片个数大2~3个) @@ -93,7 +95,8 @@ notFound=404 Not Found> 8; + if(exit_code_ptr){ + *exit_code_ptr = (status & 0xFF00) >> 8; + } + if (p < 0) { + WarnL << "waitpid failed, pid=" << pid << ", err=" << get_uv_errmsg(); + return false; + } + if (p > 0) { + InfoL << "process terminated, pid=" << pid << ", exit code=" << exit_code; + return false; + } + //WarnL << "process is running, pid=" << _pid; + return true; +} + +static void s_kill(pid_t pid,int max_delay,bool force){ + if (pid <= 0) { + //pid无效 + return; + } + + if (::kill(pid, force ? SIGKILL : SIGTERM) == -1) { + //进程可能已经退出了 + WarnL << "kill process " << pid << " failed:" << get_uv_errmsg(); + return; + } + + if(force){ + //发送SIGKILL信号后,阻塞等待退出 + s_wait(pid, NULL, true); + DebugL << "force kill " << pid << " success!"; + return; + } + + //发送SIGTERM信号后,2秒后检查子进程是否已经退出 + WorkThreadPool::Instance().getPoller()->doDelayTask(max_delay,[pid](){ + if (!s_wait(pid, nullptr, false)) { + //进程已经退出了 + return 0; + } + //进程还在运行 + WarnL << "process still working,force kill it:" << pid; + s_kill(pid,0, true); + return 0; + }); +} + +void Process::kill(int max_delay,bool force) { if (_pid <= 0) { return; } - if (::kill(_pid, SIGTERM) == -1) { - WarnL << "kill process " << _pid << " falied,err:" << get_uv_errmsg(); - } else { - //等待子进程退出 - auto pid = _pid; - EventPollerPool::Instance().getPoller()->doDelayTask(max_delay,[pid](){ - //最多等待2秒,2秒后强制杀掉程序 - if (waitpid(pid, NULL, WNOHANG) == 0) { - ::kill(pid, SIGKILL); - WarnL << "force kill process " << pid; - } - return 0; - }); - } + s_kill(_pid,max_delay,force); _pid = -1; } @@ -134,28 +181,10 @@ Process::~Process() { kill(2000); } -Process::Process() { -} +Process::Process() {} bool Process::wait(bool block) { - if (_pid <= 0) { - return false; - } - int status = 0; - pid_t p = waitpid(_pid, &status, block ? 0 : WNOHANG); - - _exit_code = (status & 0xFF00) >> 8; - if (p < 0) { - WarnL << "waitpid failed, pid=" << _pid << ", err=" << get_uv_errmsg(); - return false; - } - if (p > 0) { - InfoL << "process terminated, pid=" << _pid << ", exit code=" << _exit_code; - return false; - } - - //WarnL << "process is running, pid=" << _pid; - return true; + return s_wait(_pid,&_exit_code,block); } int Process::exit_code() { diff --git a/server/Process.h b/server/Process.h index b0b994d9..cce8470a 100644 --- a/server/Process.h +++ b/server/Process.h @@ -36,7 +36,7 @@ public: Process(); ~Process(); void run(const string &cmd,const string &log_file); - void kill(int max_delay); + void kill(int max_delay,bool force = false); bool wait(bool block = true); int exit_code(); private: diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 3209b439..6a040011 100644 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -45,6 +45,7 @@ #include "Util/MD5.h" #include "WebApi.h" #include "WebHook.h" +#include "Thread/WorkThreadPool.h" #if !defined(_WIN32) #include "FFmpegSource.h" @@ -281,6 +282,23 @@ void installWebApi() { }); }); + //获取后台工作线程负载 + //测试url http://127.0.0.1/index/api/getWorkThreadsLoad + API_REGIST_INVOKER(api, getWorkThreadsLoad, { + WorkThreadPool::Instance().getExecutorDelay([invoker, headerOut](const vector &vecDelay) { + Value val; + auto vec = WorkThreadPool::Instance().getExecutorLoad(); + int i = 0; + for (auto load : vec) { + Value obj(objectValue); + obj["load"] = load; + obj["delay"] = vecDelay[i++]; + val["data"].append(obj); + } + invoker("200 OK", headerOut, val.toStyledString()); + }); + }); + //获取服务器配置 //测试url http://127.0.0.1/index/api/getServerConfig API_REGIST(api, getServerConfig, { diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 1f6763ac..15540bcd 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -122,7 +122,7 @@ const string kMaxReqCount = HTTP_FIELD"maxReqCount"; const string kCharSet = HTTP_FIELD"charSet"; //http 服务器根目录 -#define HTTP_ROOT_PATH (exeDir() + "httpRoot") +#define HTTP_ROOT_PATH "./httpRoot" const string kRootPath = HTTP_FIELD"rootPath"; //http 404错误提示内容 diff --git a/src/Extension/Factory.cpp b/src/Extension/Factory.cpp index 763ba27c..d65dbb56 100644 --- a/src/Extension/Factory.cpp +++ b/src/Extension/Factory.cpp @@ -41,8 +41,8 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) { aac_cfg_str = FindField(track->_fmtp.data(), "config=", ";"); } if (aac_cfg_str.empty()) { - //延后获取adts头 - return std::make_shared(); + //如果sdp中获取不到aac config信息,那么在rtp也无法获取,那么忽略该Track + return nullptr; } string aac_cfg; diff --git a/src/Http/HttpSession.cpp b/src/Http/HttpSession.cpp index 02283d04..e9ab7e81 100644 --- a/src/Http/HttpSession.cpp +++ b/src/Http/HttpSession.cpp @@ -169,21 +169,32 @@ void HttpSession::onRecv(const Buffer::Ptr &pBuf) { } void HttpSession::onError(const SockException& err) { + if(_is_flv_stream){ + //flv播放器 + WarnP(this) << "播放器(" + << _mediaInfo._vhost << "/" + << _mediaInfo._app << "/" + << _mediaInfo._streamid + << ")断开:" << err.what(); + + GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold); + if(_ui64TotalBytes > iFlowThreshold * 1024){ + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, + _mediaInfo, + _ui64TotalBytes, + _ticker.createdTime()/1000, + true, + *this); + } + return; + } + + //http客户端 if(_ticker.createdTime() < 10 * 1000){ TraceP(this) << err.what(); }else{ WarnP(this) << err.what(); } - - GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold); - if(_ui64TotalBytes > iFlowThreshold * 1024){ - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, - _mediaInfo, - _ui64TotalBytes, - _ticker.createdTime()/1000, - true, - *this); - } } void HttpSession::onManager() { @@ -291,6 +302,7 @@ bool HttpSession::checkLiveFlvStream(const function &cb){ try{ start(getPoller(),rtmp_src); + _is_flv_stream = true; }catch (std::exception &ex){ //该rtmp源不存在 shutdown(SockException(Err_shutdown,"rtmp mediasource released")); @@ -375,10 +387,7 @@ static bool checkHls(BroadcastHttpAccessArgs){ return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,args_copy,mediaAuthInvoker,sender); } -void HttpSession::canAccessPath(const string &path_in,bool is_dir,const function &callback_in){ - auto path = path_in; - replace(const_cast(path),"//","/"); - +void HttpSession::canAccessPath(const string &path,bool is_dir,const function &callback_in){ auto callback = [callback_in,this](const string &errMsg,const HttpServerCookie::Ptr &cookie){ try { callback_in(errMsg,cookie); @@ -507,7 +516,7 @@ void HttpSession::Handle_Req_GET(int64_t &content_len) { GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount); GET_CONFIG(bool,enableVhost,General::kEnableVhost); GET_CONFIG(string,rootPath,Http::kRootPath); - string strFile = enableVhost ? rootPath + "/" + _mediaInfo._vhost + _parser.Url() :rootPath + _parser.Url(); + auto strFile = File::absolutePath(enableVhost ? _mediaInfo._vhost + _parser.Url() : _parser.Url(),rootPath); bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt); do{ diff --git a/src/Http/HttpSession.h b/src/Http/HttpSession.h index e6199668..19445780 100644 --- a/src/Http/HttpSession.h +++ b/src/Http/HttpSession.h @@ -160,6 +160,7 @@ private: //处理content数据的callback function _contentCallBack; bool _flv_over_websocket = false; + bool _is_flv_stream = false; }; diff --git a/src/MediaFile/MP4Recorder.cpp b/src/MediaFile/MP4Recorder.cpp index 658243c9..0f36561c 100644 --- a/src/MediaFile/MP4Recorder.cpp +++ b/src/MediaFile/MP4Recorder.cpp @@ -153,6 +153,14 @@ void MP4Recorder::onTrackReady(const Track::Ptr & track){ } } +void MP4Recorder::resetTracks() { + closeFile(); + _tracks.clear(); + _haveVideo = false; + _createFileTicker.resetTime(); + MediaSink::resetTracks(); +} + } /* namespace mediakit */ diff --git a/src/MediaFile/MP4Recorder.h b/src/MediaFile/MP4Recorder.h index ca97cb3f..0c619461 100644 --- a/src/MediaFile/MP4Recorder.h +++ b/src/MediaFile/MP4Recorder.h @@ -63,6 +63,11 @@ public: const string &strApp, const string &strStreamId); virtual ~MP4Recorder(); + + /** + * 重置所有Track + */ + void resetTracks() override; private: /** * 某Track输出frame,在onAllTrackReady触发后才会调用此方法 diff --git a/src/MediaFile/MediaReader.cpp b/src/MediaFile/MediaReader.cpp index 22683b4e..eae430bd 100644 --- a/src/MediaFile/MediaReader.cpp +++ b/src/MediaFile/MediaReader.cpp @@ -44,10 +44,11 @@ MediaReader::MediaReader(const string &strVhost,const string &strApp, const stri GET_CONFIG(string,recordPath,Record::kFilePath); GET_CONFIG(bool,enableVhost,General::kEnableVhost); if(enableVhost){ - strFileName = recordPath + "/" + strVhost + "/" + strApp + "/" + strId; + strFileName = strVhost + "/" + strApp + "/" + strId; }else{ - strFileName = recordPath + "/" + strApp + "/" + strId; + strFileName = strApp + "/" + strId; } + strFileName = File::absolutePath(strFileName,recordPath); } _hMP4File = MP4Read(strFileName.data()); diff --git a/src/MediaFile/MediaRecorder.cpp b/src/MediaFile/MediaRecorder.cpp index 3f348778..e422b6e5 100644 --- a/src/MediaFile/MediaRecorder.cpp +++ b/src/MediaFile/MediaRecorder.cpp @@ -56,13 +56,15 @@ MediaRecorder::MediaRecorder(const string &strVhost_tmp, #if defined(ENABLE_HLS) if(enableHls) { string m3u8FilePath; + string params; if(enableVhost){ - m3u8FilePath = hlsPath + "/" + strVhost + "/" + strApp + "/" + strId + "/hls.m3u8"; - _hlsRecorder.reset(new HlsRecorder(m3u8FilePath,string(VHOST_KEY) + "=" + strVhost ,hlsBufSize, hlsDuration, hlsNum)); + m3u8FilePath = strVhost + "/" + strApp + "/" + strId + "/hls.m3u8"; + params = string(VHOST_KEY) + "=" + strVhost; }else{ - m3u8FilePath = hlsPath + "/" + strApp + "/" + strId + "/hls.m3u8"; - _hlsRecorder.reset(new HlsRecorder(m3u8FilePath,"",hlsBufSize, hlsDuration, hlsNum)); + m3u8FilePath = strApp + "/" + strId + "/hls.m3u8"; } + m3u8FilePath = File::absolutePath(m3u8FilePath,hlsPath); + _hlsRecorder.reset(new HlsRecorder(m3u8FilePath,params,hlsBufSize, hlsDuration, hlsNum)); } #endif //defined(ENABLE_HLS) @@ -73,10 +75,11 @@ MediaRecorder::MediaRecorder(const string &strVhost_tmp, if(enableMp4){ string mp4FilePath; if(enableVhost){ - mp4FilePath = recordPath + "/" + strVhost + "/" + recordAppName + "/" + strApp + "/" + strId + "/"; + mp4FilePath = strVhost + "/" + recordAppName + "/" + strApp + "/" + strId + "/"; } else { - mp4FilePath = recordPath + "/" + recordAppName + "/" + strApp + "/" + strId + "/"; + mp4FilePath = recordAppName + "/" + strApp + "/" + strId + "/"; } + mp4FilePath = File::absolutePath(mp4FilePath,recordPath); _mp4Recorder.reset(new MP4Recorder(mp4FilePath,strVhost,strApp,strId)); } #endif //defined(ENABLE_MP4RECORD) diff --git a/src/Rtmp/RtmpSession.cpp b/src/Rtmp/RtmpSession.cpp index 2cd38528..62787b1d 100644 --- a/src/Rtmp/RtmpSession.cpp +++ b/src/Rtmp/RtmpSession.cpp @@ -44,13 +44,17 @@ RtmpSession::~RtmpSession() { } void RtmpSession::onError(const SockException& err) { - WarnP(this) << err.what(); + bool isPlayer = !_pPublisherSrc; + WarnP(this) << (isPlayer ? "播放器(" : "推流器(") + << _mediaInfo._vhost << "/" + << _mediaInfo._app << "/" + << _mediaInfo._streamid + << ")断开:" << err.what(); //流量统计事件广播 GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold); if(_ui64TotalBytes > iFlowThreshold * 1024){ - bool isPlayer = !_pPublisherSrc; NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _ui64TotalBytes, diff --git a/src/Rtsp/RtspSession.cpp b/src/Rtsp/RtspSession.cpp index efd0dc7e..3985ebec 100644 --- a/src/Rtsp/RtspSession.cpp +++ b/src/Rtsp/RtspSession.cpp @@ -85,7 +85,13 @@ RtspSession::~RtspSession() { } void RtspSession::onError(const SockException& err) { - WarnP(this) << err.what(); + bool isPlayer = !_pushSrc; + WarnP(this) << (isPlayer ? "播放器(" : "推流器(") + << _mediaInfo._vhost << "/" + << _mediaInfo._app << "/" + << _mediaInfo._streamid + << ")断开:" << err.what(); + if (_rtpType == Rtsp::RTP_MULTICAST) { //取消UDP端口监听 UDPServer::Instance().stopListenPeer(get_peer_ip().data(), this); @@ -100,7 +106,6 @@ void RtspSession::onError(const SockException& err) { //流量统计事件广播 GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold); if(_ui64TotalBytes > iFlowThreshold * 1024){ - bool isPlayer = !_pushSrc; NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _ui64TotalBytes,