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的高性能运营级流媒体服务框架
[![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开发避免使用裸指针代码稳定可靠同时跨平台移植简单方便代码清晰简洁。
- 打包多种流媒体协议(RTSP/RTMP/HLS/HTTP-FLV/Websocket-FLV支持协议间的互相转换提供一站式的服务。
@ -29,6 +17,7 @@ git submodule update --init
- 移动嵌入式跨平台流媒体解决方案。
- 商用级流媒体服务器。
- 网络编程二次开发SDK。
- 免费NVR(rtxp拉流代理、分发)。
## 功能清单
@ -362,8 +351,9 @@ git submodule update --init
由于使用本项目而产生的商业纠纷或侵权行为一概与本项项目及开发者无关,请自行承担法律风险。
## 联系方式
- 邮箱:<771730766@qq.com>(本项目相关或流媒体相关问题请走issue流程否则恕不邮件答复)
- QQ群542509000
- 邮箱:<18675721@qq.com>(本项目相关或流媒体相关问题请走issue流程否则恕不邮件答复)
- QQ群542509000 (ZLMediakit)
- QQ群936467414 (FreeNVR)
## 怎么提问?
如果要对项目有相关疑问,建议您这么做:
@ -372,12 +362,6 @@ git submodule update --init
- 3、有些问题如果不具备参考性的无需在issue提的可以在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})
if (CMAKE_SYSTEM_NAME MATCHES "Windows")
set(MediaServer_src_list ./WebApi.cpp ./WebHook.cpp main.cpp)
else()
file(GLOB MediaServer_src_list ./*.cpp ./*.h)
endif()
file(GLOB MediaServer_src_list ./*.cpp ./*.h)
#message(STATUS ${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";
onceToken token([]() {
mINI::Instance()[kBin] = trim(System::execute("which ffmpeg"));
mINI::Instance()[kCmd] = "%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s";
mINI::Instance()[kLog] = "./ffmpeg/ffmpeg.log";
#ifdef _WIN32
string strFFmpeg = "where ffmpeg";
#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;
}
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_cmd,FFmpeg::kCmd);
@ -62,8 +67,8 @@ void FFmpegSource::play(const string &src_url,const string &dst_url,int timeout_
_media_info.parse(dst_url);
char cmd[1024] = {0};
snprintf(cmd, sizeof(cmd),ffmpeg_cmd.data(),ffmpeg_bin.data(),src_url.data(),dst_url.data());
_process.run(cmd,File::absolutePath("",ffmpeg_log));
snprintf(cmd, sizeof(cmd), ffmpeg_cmd.data(), ffmpeg_bin.data(), src_url.data(), dst_url.data());
_process.run(cmd, ffmpeg_log.empty() ? "" : File::absolutePath("", ffmpeg_log));
InfoL << cmd;
if(_media_info._host == "127.0.0.1"){

View File

@ -26,8 +26,15 @@
#include <limits.h>
#include <sys/stat.h>
#ifndef _WIN32
#include <sys/resource.h>
#include <unistd.h>
#else
//#include <TlHelp32.h>
#include <windows.h>
#endif
#include <stdexcept>
#include <signal.h>
#include "Util/util.h"
@ -39,75 +46,109 @@
using namespace toolkit;
void Process::run(const string &cmd, const string &log_file_tmp) {
kill(2000);
_pid = fork();
if (_pid < 0) {
throw std::runtime_error(StrPrinter << "fork child process falied,err:" << get_uv_errmsg());
}
if (_pid == 0) {
//子进程关闭core文件生成
struct rlimit rlim = {0,0};
setrlimit(RLIMIT_CORE, &rlim);
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后面没有空格
//在启动子进程时暂时禁用SIGINT、SIGTERM信号
// ignore the SIGINT and SIGTERM
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si)); //结构体初始化;
ZeroMemory(&pi, sizeof(pi));
string log_file ;
if(log_file_tmp.empty()){
log_file = "/dev/null";
}else{
log_file = StrPrinter << log_file_tmp << "." << getpid();
}
LPTSTR lpDir = const_cast<char*>(cmd .data());
int log_fd = -1;
int flags = O_CREAT | O_WRONLY | O_APPEND;
mode_t mode = S_IRWXO | S_IRWXG | S_IRWXU;// S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
File::createfile_path(log_file.data(), mode);
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));
} else {
// dup to stdout and stderr.
if (dup2(log_fd, STDOUT_FILENO) < 0) {
fprintf(stderr, "dup2 stdout file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
}
if (dup2(log_fd, STDERR_FILENO) < 0) {
fprintf(stderr, "dup2 stderr file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
}
// close log fd
::close(log_fd);
}
fprintf(stderr, "\r\n\r\n#### pid=%d,cmd=%s #####\r\n\r\n", getpid(), cmd.data());
if (CreateProcess(NULL, lpDir, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
{
//下面两行关闭句柄,解除本进程和新进程的关系,不然有可能 不小心调用TerminateProcess函数关掉子进程
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
// close other fds
// TODO: do in right way.
for (int i = 3; i < 1024; i++) {
::close(i);
}
_pid = pi.dwProcessId;
InfoL << "start child proces " << _pid;
}
else
{
WarnL << "start child proces fail: " << GetLastError();
}
#else
_pid = fork();
if (_pid < 0) {
throw std::runtime_error(StrPrinter << "fork child process falied,err:" << get_uv_errmsg());
}
if (_pid == 0) {
//子进程关闭core文件生成
struct rlimit rlim = { 0,0 };
setrlimit(RLIMIT_CORE, &rlim);
auto params = split(cmd, " ");
// memory leak in child process, it's ok.
char **charpv_params = new char *[params.size() + 1];
for (int i = 0; i < (int) params.size(); i++) {
std::string &p = params[i];
charpv_params[i] = (char *) p.data();
}
// EOF: NULL
charpv_params[params.size()] = NULL;
//在启动子进程时暂时禁用SIGINT、SIGTERM信号
// ignore the SIGINT and SIGTERM
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
// TODO: execv or execvp
auto ret = execv(params[0].c_str(), charpv_params);
if (ret < 0) {
fprintf(stderr, "fork process failed, errno=%d(%s)\r\n", errno, strerror(errno));
}
exit(ret);
}
string log_file;
if (log_file_tmp.empty()) {
log_file = "/dev/null";
}
else {
log_file = StrPrinter << log_file_tmp << "." << getpid();
}
InfoL << "start child proces " << _pid;
int log_fd = -1;
int flags = O_CREAT | O_WRONLY | O_APPEND;
mode_t mode = S_IRWXO | S_IRWXG | S_IRWXU;// S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
File::createfile_path(log_file.data(), mode);
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));
}
else {
// dup to stdout and stderr.
if (dup2(log_fd, STDOUT_FILENO) < 0) {
fprintf(stderr, "dup2 stdout file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
}
if (dup2(log_fd, STDERR_FILENO) < 0) {
fprintf(stderr, "dup2 stderr file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
}
// close log fd
::close(log_fd);
}
fprintf(stderr, "\r\n\r\n#### pid=%d,cmd=%s #####\r\n\r\n", getpid(), cmd.data());
// close other fds
// TODO: do in right way.
for (int i = 3; i < 1024; i++) {
::close(i);
}
auto params = split(cmd, " ");
// memory leak in child process, it's ok.
char **charpv_params = new char *[params.size() + 1];
for (int i = 0; i < (int)params.size(); i++) {
std::string &p = params[i];
charpv_params[i] = (char *)p.data();
}
// EOF: NULL
charpv_params[params.size()] = NULL;
// TODO: execv or execvp
auto ret = execv(params[0].c_str(), charpv_params);
if (ret < 0) {
fprintf(stderr, "fork process failed, errno=%d(%s)\r\n", errno, strerror(errno));
}
exit(ret);
}
InfoL << "start child proces " << _pid;
#endif // _WIN32
}
/**
*
* @param pid
@ -120,20 +161,31 @@ static bool s_wait(pid_t pid,int *exit_code_ptr,bool block) {
return false;
}
int status = 0;
pid_t p = waitpid(pid, &status, block ? 0 : WNOHANG);
int exit_code = (status & 0xFF00) >> 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;
#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);
int exit_code = (status & 0xFF00) >> 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;
#endif // _WIN32
return true;
}
@ -142,12 +194,27 @@ static void s_kill(pid_t pid,int max_delay,bool force){
//pid无效
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) {
//进程可能已经退出了
WarnL << "kill process " << pid << " failed:" << get_uv_errmsg();
return;
}
#endif // _WIN32
if (::kill(pid, force ? SIGKILL : SIGTERM) == -1) {
//进程可能已经退出了
WarnL << "kill process " << pid << " failed:" << get_uv_errmsg();
return;
}
if(force){
//发送SIGKILL信号后阻塞等待退出

View File

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

View File

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

View File

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

View File

@ -239,7 +239,7 @@ int main(int argc,char *argv[]) {
//这里是拉流地址支持rtmp/rtsp协议负载必须是H264+AAC
//如果是其他不识别的音视频将会被忽略(譬如说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://admin:jzan123456@192.168.0.122/"*/};
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
//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时有效)
(*player)[kRtpType] = Rtsp::RTP_TCP;
//开始播放,如果播放失败或者播放中止,将会自动重试若干次,重试次数在配置文件中配置,默认一直重试