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,