From 306e202ca10d7b3606245af37a2e146493126b5d Mon Sep 17 00:00:00 2001 From: Luke Date: Tue, 7 Jan 2020 11:09:11 +0800 Subject: [PATCH] =?UTF-8?q?1.=E6=B7=BB=E5=8A=A0windows=E4=B8=8Bffmpeg?= =?UTF-8?q?=E6=8B=89=E6=B5=81=E5=88=86=E5=8F=91=E6=94=AF=E6=8C=81=EF=BC=8C?= =?UTF-8?q?=E7=9B=AE=E5=89=8D=E4=B8=8D=E6=98=AF=E5=BE=88=E5=AE=8C=E5=96=84?= =?UTF-8?q?=EF=BC=8C=E5=90=8E=E7=BB=AD=E5=86=8D=E4=BF=AE=E6=94=B9=202.serv?= =?UTF-8?q?er/system=E4=B8=8B=E6=9C=AA=E5=85=BC=E5=AE=B9=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3rdpart/ZLToolKit | 2 +- README.md | 24 +---- server/CMakeLists.txt | 7 +- server/FFmpegSource.cpp | 17 +-- server/Process.cpp | 225 ++++++++++++++++++++++++++-------------- server/Process.h | 7 ++ server/System.cpp | 72 ++++++++++--- server/WebApi.cpp | 9 -- tests/test_server.cpp | 4 +- 9 files changed, 230 insertions(+), 137 deletions(-) diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index ba5a796d..7cb850d7 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit ba5a796d583cd2906e06ec15c0d942e484ea1dbf +Subproject commit 7cb850d79ba798e6d1f0bba7c655d38d2865ed92 diff --git a/README.md b/README.md index c5c63216..7c1d07b2 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 5bc42a8d..092a4221 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -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}) diff --git a/server/FFmpegSource.cpp b/server/FFmpegSource.cpp index e76df0ed..ac692a73 100644 --- a/server/FFmpegSource.cpp +++ b/server/FFmpegSource.cpp @@ -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"){ diff --git a/server/Process.cpp b/server/Process.cpp index e215dc81..b1d2438d 100644 --- a/server/Process.cpp +++ b/server/Process.cpp @@ -26,8 +26,15 @@ #include #include + +#ifndef _WIN32 #include #include +#else +//#include +#include +#endif + #include #include #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(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信号后,阻塞等待退出 diff --git a/server/Process.h b/server/Process.h index cce8470a..68e40ac1 100644 --- a/server/Process.h +++ b/server/Process.h @@ -26,9 +26,16 @@ #ifndef IPTV_PROCESS_H #define IPTV_PROCESS_H + +#ifdef _WIN32 +typedef int pid_t; +#else #include +#endif // _WIN32 + #include #include + using namespace std; class Process { diff --git a/server/System.cpp b/server/System.cpp index dc9fdb15..930d2261 100644 --- a/server/System.cpp +++ b/server/System.cpp @@ -27,14 +27,19 @@ #include "System.h" #include #include -#include #include +#ifdef _WIN32 + +#else +#include #include #include #include -#ifndef ANDROID +#ifndef ANDROID #include #endif +#endif + #include #include #include @@ -247,16 +252,38 @@ bool System::getNetworkUsage(vector &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 } diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 04dd3715..b2eb6713 100644 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -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 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 lck(s_ffmpegMapMtx); s_ffmpegMap.clear(); } -#endif } \ No newline at end of file diff --git a/tests/test_server.cpp b/tests/test_server.cpp index cb6dc2da..02d67a1c 100644 --- a/tests/test_server.cpp +++ b/tests/test_server.cpp @@ -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 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; //开始播放,如果播放失败或者播放中止,将会自动重试若干次,重试次数在配置文件中配置,默认一直重试