1.添加windows下ffmpeg拉流分发支持,目前不是很完善,后续再修改

2.server/system下未兼容完成
This commit is contained in:
Luke 2020-01-07 11:09:11 +08:00
parent d082955510
commit 306e202ca1
9 changed files with 230 additions and 137 deletions

@ -1 +1 @@
Subproject commit ba5a796d583cd2906e06ec15c0d942e484ea1dbf Subproject commit 7cb850d79ba798e6d1f0bba7c655d38d2865ed92

View File

@ -1,17 +1,5 @@
![logo](https://raw.githubusercontent.com/zlmediakit/ZLMediaKit/master/logo.png)
[english readme](https://github.com/xiongziliang/ZLMediaKit/blob/master/README_en.md)
# 一个基于C++11的高性能运营级流媒体服务框架 # 一个基于C++11的高性能运营级流媒体服务框架
[![Build Status](https://travis-ci.org/xiongziliang/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xiongziliang/ZLMediaKit)
## 国内用户请使用gitee镜像下载
```
git clone --depth 1 https://gitee.com/xiahcu/ZLMediaKit
cd ZLMediaKit
git submodule update --init
```
## 项目特点 ## 项目特点
- 基于C++11开发避免使用裸指针代码稳定可靠同时跨平台移植简单方便代码清晰简洁。 - 基于C++11开发避免使用裸指针代码稳定可靠同时跨平台移植简单方便代码清晰简洁。
- 打包多种流媒体协议(RTSP/RTMP/HLS/HTTP-FLV/Websocket-FLV支持协议间的互相转换提供一站式的服务。 - 打包多种流媒体协议(RTSP/RTMP/HLS/HTTP-FLV/Websocket-FLV支持协议间的互相转换提供一站式的服务。
@ -29,6 +17,7 @@ git submodule update --init
- 移动嵌入式跨平台流媒体解决方案。 - 移动嵌入式跨平台流媒体解决方案。
- 商用级流媒体服务器。 - 商用级流媒体服务器。
- 网络编程二次开发SDK。 - 网络编程二次开发SDK。
- 免费NVR(rtxp拉流代理、分发)。
## 功能清单 ## 功能清单
@ -362,8 +351,9 @@ git submodule update --init
由于使用本项目而产生的商业纠纷或侵权行为一概与本项项目及开发者无关,请自行承担法律风险。 由于使用本项目而产生的商业纠纷或侵权行为一概与本项项目及开发者无关,请自行承担法律风险。
## 联系方式 ## 联系方式
- 邮箱:<771730766@qq.com>(本项目相关或流媒体相关问题请走issue流程否则恕不邮件答复) - 邮箱:<18675721@qq.com>(本项目相关或流媒体相关问题请走issue流程否则恕不邮件答复)
- QQ群542509000 - QQ群542509000 (ZLMediakit)
- QQ群936467414 (FreeNVR)
## 怎么提问? ## 怎么提问?
如果要对项目有相关疑问,建议您这么做: 如果要对项目有相关疑问,建议您这么做:
@ -372,12 +362,6 @@ git submodule update --init
- 3、有些问题如果不具备参考性的无需在issue提的可以在qq群提. - 3、有些问题如果不具备参考性的无需在issue提的可以在qq群提.
- 4、QQ私聊一般不接受无偿技术咨询和支持(谈谈人生理想还是可以的😂),毕竟精力有限,谢谢理解. - 4、QQ私聊一般不接受无偿技术咨询和支持(谈谈人生理想还是可以的😂),毕竟精力有限,谢谢理解.
## 捐赠
欢迎捐赠以便更好的推动项目的发展,谢谢您的支持!
[支付宝](https://raw.githubusercontent.com/xiongziliang/other/master/IMG_3919.JPG)
[微信](https://raw.githubusercontent.com/xiongziliang/other/master/IMG_3920.JPG)

View File

@ -3,12 +3,7 @@ file(GLOB jsoncpp_src_list ../3rdpart/jsoncpp/*.cpp ../3rdpart/jsoncpp/*.h )
add_library(jsoncpp STATIC ${jsoncpp_src_list}) add_library(jsoncpp STATIC ${jsoncpp_src_list})
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
set(MediaServer_src_list ./WebApi.cpp ./WebHook.cpp main.cpp)
else()
file(GLOB MediaServer_src_list ./*.cpp ./*.h) file(GLOB MediaServer_src_list ./*.cpp ./*.h)
endif()
#message(STATUS ${MediaServer_src_list}) #message(STATUS ${MediaServer_src_list})
add_executable(MediaServer ${MediaServer_src_list}) add_executable(MediaServer ${MediaServer_src_list})

View File

@ -37,9 +37,15 @@ const char kCmd[] = FFmpeg_FIELD"cmd";
const char kLog[] = FFmpeg_FIELD"log"; const char kLog[] = FFmpeg_FIELD"log";
onceToken token([]() { onceToken token([]() {
mINI::Instance()[kBin] = trim(System::execute("which ffmpeg")); #ifdef _WIN32
mINI::Instance()[kCmd] = "%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s"; string strFFmpeg = "where ffmpeg";
mINI::Instance()[kLog] = "./ffmpeg/ffmpeg.log"; #else
string strFFmpeg = "which ffmpeg";
#endif
mINI::Instance()[kBin] = trim(System::execute(strFFmpeg)); //todo暂定如此配置文件无此配置的话改为环境变量的ffmpeg
mINI::Instance()[kCmd] = "%s -re -i \"%s\" -loglevel quiet -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s "; //防止url中特殊字符
mINI::Instance()[kLog] = "./ffmpeg/ffmpeg.log"; //win下建议使用ffmpeg/ffmpeg.log
}); });
} }
@ -51,7 +57,6 @@ FFmpegSource::~FFmpegSource() {
DebugL; DebugL;
} }
void FFmpegSource::play(const string &src_url,const string &dst_url,int timeout_ms,const onPlay &cb) { void FFmpegSource::play(const string &src_url,const string &dst_url,int timeout_ms,const onPlay &cb) {
GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin); GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin);
GET_CONFIG(string,ffmpeg_cmd,FFmpeg::kCmd); GET_CONFIG(string,ffmpeg_cmd,FFmpeg::kCmd);
@ -63,7 +68,7 @@ void FFmpegSource::play(const string &src_url,const string &dst_url,int timeout_
char cmd[1024] = {0}; char cmd[1024] = {0};
snprintf(cmd, sizeof(cmd), ffmpeg_cmd.data(), ffmpeg_bin.data(), src_url.data(), dst_url.data()); snprintf(cmd, sizeof(cmd), ffmpeg_cmd.data(), ffmpeg_bin.data(), src_url.data(), dst_url.data());
_process.run(cmd,File::absolutePath("",ffmpeg_log)); _process.run(cmd, ffmpeg_log.empty() ? "" : File::absolutePath("", ffmpeg_log));
InfoL << cmd; InfoL << cmd;
if(_media_info._host == "127.0.0.1"){ if(_media_info._host == "127.0.0.1"){

View File

@ -26,8 +26,15 @@
#include <limits.h> #include <limits.h>
#include <sys/stat.h> #include <sys/stat.h>
#ifndef _WIN32
#include <sys/resource.h> #include <sys/resource.h>
#include <unistd.h> #include <unistd.h>
#else
//#include <TlHelp32.h>
#include <windows.h>
#endif
#include <stdexcept> #include <stdexcept>
#include <signal.h> #include <signal.h>
#include "Util/util.h" #include "Util/util.h"
@ -40,6 +47,39 @@ using namespace toolkit;
void Process::run(const string &cmd, const string &log_file_tmp) { void Process::run(const string &cmd, const string &log_file_tmp) {
kill(2000); kill(2000);
#ifdef _WIN32
// string log_file = "";
// if (log_file_tmp.empty())
// {
// log_file = R"( >2&1)";
// }
// else
// {
// log_file = log_file_tmp + R"( >2&1)";
// }
// string strCmd = cmd + " " + log_file; //防止cmd后面没有空格
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si)); //结构体初始化;
ZeroMemory(&pi, sizeof(pi));
LPTSTR lpDir = const_cast<char*>(cmd .data());
if (CreateProcess(NULL, lpDir, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
{
//下面两行关闭句柄,解除本进程和新进程的关系,不然有可能 不小心调用TerminateProcess函数关掉子进程
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
_pid = pi.dwProcessId;
InfoL << "start child proces " << _pid;
}
else
{
WarnL << "start child proces fail: " << GetLastError();
}
#else
_pid = fork(); _pid = fork();
if (_pid < 0) { if (_pid < 0) {
throw std::runtime_error(StrPrinter << "fork child process falied,err:" << get_uv_errmsg()); throw std::runtime_error(StrPrinter << "fork child process falied,err:" << get_uv_errmsg());
@ -57,7 +97,8 @@ void Process::run(const string &cmd, const string &log_file_tmp) {
string log_file; string log_file;
if (log_file_tmp.empty()) { if (log_file_tmp.empty()) {
log_file = "/dev/null"; log_file = "/dev/null";
}else{ }
else {
log_file = StrPrinter << log_file_tmp << "." << getpid(); log_file = StrPrinter << log_file_tmp << "." << getpid();
} }
@ -67,7 +108,8 @@ void Process::run(const string &cmd, const string &log_file_tmp) {
File::createfile_path(log_file.data(), mode); File::createfile_path(log_file.data(), mode);
if ((log_fd = ::open(log_file.c_str(), flags, mode)) < 0) { if ((log_fd = ::open(log_file.c_str(), flags, mode)) < 0) {
fprintf(stderr, "open log file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno)); fprintf(stderr, "open log file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
} else { }
else {
// dup to stdout and stderr. // dup to stdout and stderr.
if (dup2(log_fd, STDOUT_FILENO) < 0) { if (dup2(log_fd, STDOUT_FILENO) < 0) {
fprintf(stderr, "dup2 stdout file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno)); fprintf(stderr, "dup2 stdout file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
@ -103,11 +145,10 @@ void Process::run(const string &cmd, const string &log_file_tmp) {
} }
exit(ret); exit(ret);
} }
InfoL << "start child proces " << _pid; InfoL << "start child proces " << _pid;
#endif // _WIN32
} }
/** /**
* *
* @param pid * @param pid
@ -120,6 +161,15 @@ static bool s_wait(pid_t pid,int *exit_code_ptr,bool block) {
return false; return false;
} }
int status = 0; int status = 0;
#ifdef _WIN32
HANDLE hProcess = NULL;
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); //打开目标进程
if (hProcess == NULL) {
return false;
}
CloseHandle(hProcess);
#else
pid_t p = waitpid(pid, &status, block ? 0 : WNOHANG); pid_t p = waitpid(pid, &status, block ? 0 : WNOHANG);
int exit_code = (status & 0xFF00) >> 8; int exit_code = (status & 0xFF00) >> 8;
if (exit_code_ptr) { if (exit_code_ptr) {
@ -133,7 +183,9 @@ static bool s_wait(pid_t pid,int *exit_code_ptr,bool block) {
InfoL << "process terminated, pid=" << pid << ", exit code=" << exit_code; InfoL << "process terminated, pid=" << pid << ", exit code=" << exit_code;
return false; return false;
} }
//WarnL << "process is running, pid=" << _pid; WarnL << "process is running, pid=" << _pid;
#endif // _WIN32
return true; return true;
} }
@ -142,12 +194,27 @@ static void s_kill(pid_t pid,int max_delay,bool force){
//pid无效 //pid无效
return; return;
} }
#ifdef _WIN32
HANDLE hProcess = NULL;
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); //打开目标进程
if (hProcess == NULL)
{
WarnL << "\nOpen Process fAiled: " << GetLastError();
return;
}
DWORD ret = TerminateProcess(hProcess, 0); //结束目标进程
if (ret == 0)
{
WarnL << GetLastError;
}
#else
if (::kill(pid, force ? SIGKILL : SIGTERM) == -1) { if (::kill(pid, force ? SIGKILL : SIGTERM) == -1) {
//进程可能已经退出了 //进程可能已经退出了
WarnL << "kill process " << pid << " failed:" << get_uv_errmsg(); WarnL << "kill process " << pid << " failed:" << get_uv_errmsg();
return; return;
} }
#endif // _WIN32
if(force){ if(force){
//发送SIGKILL信号后阻塞等待退出 //发送SIGKILL信号后阻塞等待退出

View File

@ -26,9 +26,16 @@
#ifndef IPTV_PROCESS_H #ifndef IPTV_PROCESS_H
#define IPTV_PROCESS_H #define IPTV_PROCESS_H
#ifdef _WIN32
typedef int pid_t;
#else
#include <sys/wait.h> #include <sys/wait.h>
#endif // _WIN32
#include <fcntl.h> #include <fcntl.h>
#include <string> #include <string>
using namespace std; using namespace std;
class Process { class Process {

View File

@ -27,14 +27,19 @@
#include "System.h" #include "System.h"
#include <stdlib.h> #include <stdlib.h>
#include <signal.h> #include <signal.h>
#include <arpa/inet.h>
#include <limits.h> #include <limits.h>
#ifdef _WIN32
#else
#include <arpa/inet.h>
#include <sys/resource.h> #include <sys/resource.h>
#include <unistd.h> #include <unistd.h>
#include <sys/wait.h> #include <sys/wait.h>
#ifndef ANDROID #ifndef ANDROID
#include <execinfo.h> #include <execinfo.h>
#endif #endif
#endif
#include <map> #include <map>
#include <string> #include <string>
#include <iostream> #include <iostream>
@ -247,16 +252,38 @@ bool System::getNetworkUsage(vector<NetworkUsage> &usage) {
bool System::getTcpUsage(System::TcpUsage &usage) { bool System::getTcpUsage(System::TcpUsage &usage) {
usage.established = atoi(trim(System::execute("netstat -na|grep ESTABLISHED|wc -l")).data()); string strEstab;
usage.syn_recv = atoi(trim(System::execute("netstat -na|grep SYN_RECV|wc -l")).data()); string strSynRecv;
usage.time_wait = atoi(trim(System::execute("netstat -na|grep TIME_WAIT|wc -l")).data()); string strTimeWait;
usage.close_wait = atoi(trim(System::execute("netstat -na|grep CLOSE_WAIT|wc -l")).data()); string strCloseWait;
#ifdef _WIN32
//使用R"()" 可以不用转义
strEstab = R"(netstat -na|find /i /c "ESTABLISHED")";
strSynRecv = R"(netstat -na|find /i /c "SYN_RECV")";
strTimeWait = R"(netstat -na|find /i /c "TIME_WAIT")";
strCloseWait = R"(netstat -na|find /i /c "CLOSE_WAIT")";
#else
strEstab = "netstat -na|grep ESTABLISHED|wc -l";
strSynRecv = "netstat -na|grep SYN_RECV|wc -l";
strTimeWait = "netstat -na|grep TIME_WAIT|wc -l";
strCloseWait = "netstat -na|grep CLOSE_WAIT|wc -l";
#endif
usage.established = atoi(trim(System::execute(strEstab)).data());
usage.syn_recv = atoi(trim(System::execute(strSynRecv)).data());
usage.time_wait = atoi(trim(System::execute(strTimeWait)).data());
usage.close_wait = atoi(trim(System::execute(strCloseWait)).data());
return true; return true;
} }
string System::execute(const string &cmd) { string System::execute(const string &cmd) {
// DebugL << cmd; // DebugL << cmd;
FILE *fPipe = popen(cmd.data(), "r"); FILE *fPipe = NULL;
#ifdef _WIN32
fPipe = _popen(cmd.data(), "r");
#else
fPipe = popen(cmd.data(), "r");
#endif
if(!fPipe){ if(!fPipe){
return ""; return "";
} }
@ -265,7 +292,12 @@ string System::execute(const string &cmd) {
while(fgets(buff, sizeof(buff) - 1, fPipe)){ while(fgets(buff, sizeof(buff) - 1, fPipe)){
ret.append(buff); ret.append(buff);
} }
#ifdef _WIN32
_pclose(fPipe);
#else
pclose(fPipe); pclose(fPipe);
#endif
return ret; return ret;
} }
@ -274,8 +306,11 @@ static string addr2line(const string &address) {
return System::execute(cmd); return System::execute(cmd);
} }
#ifndef ANDROID
static void sig_crash(int sig) { static void sig_crash(int sig) {
#ifdef _WIN32
#else
#ifndef ANDROID
signal(sig, SIG_DFL); signal(sig, SIG_DFL);
void *array[MAX_STACK_FRAMES]; void *array[MAX_STACK_FRAMES];
int size = backtrace(array, MAX_STACK_FRAMES); int size = backtrace(array, MAX_STACK_FRAMES);
@ -296,12 +331,14 @@ static void sig_crash(int sig) {
free(strings); free(strings);
NoticeCenter::Instance().emitEvent(kBroadcastOnCrashDump,sig,stack); NoticeCenter::Instance().emitEvent(kBroadcastOnCrashDump,sig,stack);
#endif//#ifndef ANDROID
#endif // _WIN32
} }
#endif//#ifndef ANDROID
void System::startDaemon() { void System::startDaemon() {
#ifdef _WIN32
#else
static pid_t pid; static pid_t pid;
do{ do{
pid = fork(); pid = fork();
@ -336,14 +373,17 @@ void System::startDaemon() {
DebugL << "waitpid被中断:" << get_uv_errmsg(); DebugL << "waitpid被中断:" << get_uv_errmsg();
}while (true); }while (true);
}while (true); }while (true);
#endif // _WIN32
} }
static string currentDateTime(){ static string currentDateTime(){
time_t ts = time(NULL); time_t ts = time(NULL);
std::tm tm_snapshot; std::tm tm_snapshot;
#ifndef _WIN32
localtime_r(&ts, &tm_snapshot); localtime_r(&ts, &tm_snapshot);
#else
localtime_s(&tm_snapshot, &ts);
#endif // !_WIN32
char buffer[1024] = {0}; char buffer[1024] = {0};
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm_snapshot); std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm_snapshot);
@ -351,6 +391,9 @@ static string currentDateTime(){
} }
void System::systemSetup(){ void System::systemSetup(){
#ifdef _WIN32
#else
struct rlimit rlim,rlim_new; struct rlimit rlim,rlim_new;
if (getrlimit(RLIMIT_CORE, &rlim)==0) { if (getrlimit(RLIMIT_CORE, &rlim)==0) {
rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY; rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY;
@ -394,5 +437,6 @@ void System::systemSetup(){
cerr << stack_info << endl; cerr << stack_info << endl;
}); });
#endif
} }

View File

@ -47,10 +47,7 @@
#include "WebHook.h" #include "WebHook.h"
#include "Thread/WorkThreadPool.h" #include "Thread/WorkThreadPool.h"
#include "Rtp/RtpSelector.h" #include "Rtp/RtpSelector.h"
#if !defined(_WIN32)
#include "FFmpegSource.h" #include "FFmpegSource.h"
#endif//!defined(_WIN32)
using namespace Json; using namespace Json;
using namespace toolkit; using namespace toolkit;
@ -268,10 +265,8 @@ static inline string getProxyKey(const string &vhost,const string &app,const str
return vhost + "/" + app + "/" + stream; return vhost + "/" + app + "/" + stream;
} }
#if !defined(_WIN32)
static unordered_map<string ,FFmpegSource::Ptr> s_ffmpegMap; static unordered_map<string ,FFmpegSource::Ptr> s_ffmpegMap;
static recursive_mutex s_ffmpegMapMtx; static recursive_mutex s_ffmpegMapMtx;
#endif//#if !defined(_WIN32)
/** /**
* api接口 * api接口
@ -646,7 +641,6 @@ void installWebApi() {
val["data"]["flag"] = s_proxyMap.erase(allArgs["key"]) == 1; val["data"]["flag"] = s_proxyMap.erase(allArgs["key"]) == 1;
}); });
#if !defined(_WIN32)
static auto addFFmpegSource = [](const string &src_url, static auto addFFmpegSource = [](const string &src_url,
const string &dst_url, const string &dst_url,
int timeout_ms, int timeout_ms,
@ -713,7 +707,6 @@ void installWebApi() {
API_REGIST(api,delFFmepgSource,{ API_REGIST(api,delFFmepgSource,{
api_delFFmpegSource(API_ARGS_VALUE); api_delFFmpegSource(API_ARGS_VALUE);
}); });
#endif
//新增http api下载可执行程序文件接口 //新增http api下载可执行程序文件接口
//测试url http://127.0.0.1/index/api/downloadBin //测试url http://127.0.0.1/index/api/downloadBin
@ -932,10 +925,8 @@ void unInstallWebApi(){
s_proxyMap.clear(); s_proxyMap.clear();
} }
#if !defined(_WIN32)
{ {
lock_guard<recursive_mutex> lck(s_ffmpegMapMtx); lock_guard<recursive_mutex> lck(s_ffmpegMapMtx);
s_ffmpegMap.clear(); s_ffmpegMap.clear();
} }
#endif
} }

View File

@ -239,7 +239,7 @@ int main(int argc,char *argv[]) {
//这里是拉流地址支持rtmp/rtsp协议负载必须是H264+AAC //这里是拉流地址支持rtmp/rtsp协议负载必须是H264+AAC
//如果是其他不识别的音视频将会被忽略(譬如说h264+adpcm转发后会去除音频) //如果是其他不识别的音视频将会被忽略(譬如说h264+adpcm转发后会去除音频)
auto urlList = {"rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov" auto urlList = {"rtsp://admin:admin123@192.168.1.64:554/cam/realmonitor?channel=1&subtype=1"
//rtsp链接支持输入用户名密码 //rtsp链接支持输入用户名密码
/*"rtsp://admin:jzan123456@192.168.0.122/"*/}; /*"rtsp://admin:jzan123456@192.168.0.122/"*/};
map<string, PlayerProxy::Ptr> proxyMap; map<string, PlayerProxy::Ptr> proxyMap;
@ -258,7 +258,7 @@ int main(int argc,char *argv[]) {
//rtsp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4 //rtsp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4
//rtmp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4 //rtmp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4
PlayerProxy::Ptr player(new PlayerProxy(DEFAULT_VHOST, "live", to_string(i).data())); PlayerProxy::Ptr player(new PlayerProxy(DEFAULT_VHOST, "live", std::string("chn") + to_string(i).data()));
//指定RTP over TCP(播放rtsp时有效) //指定RTP over TCP(播放rtsp时有效)
(*player)[kRtpType] = Rtsp::RTP_TCP; (*player)[kRtpType] = Rtsp::RTP_TCP;
//开始播放,如果播放失败或者播放中止,将会自动重试若干次,重试次数在配置文件中配置,默认一直重试 //开始播放,如果播放失败或者播放中止,将会自动重试若干次,重试次数在配置文件中配置,默认一直重试