diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml new file mode 100644 index 00000000..dc2660e2 --- /dev/null +++ b/.github/workflows/ccpp.yml @@ -0,0 +1,25 @@ +name: C/C++ CI + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: 下载submodule源码 + run: git submodule update --init + + - name: apt-get安装依赖库(非必选) + run: sudo apt-get install -y cmake libmysqlclient-dev libssl-dev libx264-dev libfaac-dev libmp4v2-dev libsdl-dev libavcodec-dev libavutil-dev + + - name: 编译 + run: mkdir -p linux_build && cd linux_build && cmake .. && make -j4 + + - name: 运行MediaServer + run: pwd && cd release/linux/Debug && sudo ./MediaServer -d & + + diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index 73eb9077..628d3b25 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit 73eb9077e19e473c643708cf586517bacaa16302 +Subproject commit 628d3b2527f63b54a5eb38b9e9973254d4a2192b diff --git a/README.md b/README.md index becaaf31..d02ad8e3 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,12 @@ - Apple OSX(Darwin), both 32 and 64bits. - All hardware with x86/x86_64/arm/mips cpu. - Windows. + +## How to build + +It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumbersome, and some features are not compiled by default. + +### Before build - **You must use git to clone the complete code. Do not download the source code by downloading zip package. Otherwise, the sub-module code will not be downloaded by default.You can do it like this:** ``` git clone https://github.com/zlmediakit/ZLMediaKit.git @@ -124,12 +130,6 @@ cd ZLMediaKit git submodule update --init ``` - - -## How to build - -It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumbersome, and some features are not compiled by default. - ### Build on linux - My environment diff --git a/README_CN.md b/README_CN.md index 54199461..a2eba340 100644 --- a/README_CN.md +++ b/README_CN.md @@ -128,6 +128,9 @@ ## 编译要求 - 编译器支持C++11,GCC4.8/Clang3.3/VC2015或以上 - cmake3.2或以上 + +## 编译前必看!!! + - **必须使用git下载完整的代码,不要使用下载zip包的方式下载源码,否则子模块代码默认不下载!你可以像以下这样操作:** ``` git clone https://github.com/zlmediakit/ZLMediaKit.git diff --git a/conf/config.ini b/conf/config.ini index 830acbb3..1ef7c145 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -9,7 +9,7 @@ secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc #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 +cmd=%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s #FFmpeg日志的路径,如果置空则不生成FFmpeg日志 #可以为相对(相对于本可执行程序目录)或绝对路径 log=./ffmpeg/ffmpeg.log diff --git a/server/FFmpegSource.cpp b/server/FFmpegSource.cpp index e55e6dbb..81d98bd5 100644 --- a/server/FFmpegSource.cpp +++ b/server/FFmpegSource.cpp @@ -38,7 +38,7 @@ const char kLog[] = FFmpeg_FIELD"log"; onceToken token([]() { mINI::Instance()[kBin] = trim(System::execute("which ffmpeg")); - mINI::Instance()[kCmd] = "%s -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s"; + 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"; }); } @@ -48,7 +48,6 @@ FFmpegSource::FFmpegSource() { } FFmpegSource::~FFmpegSource() { - NoticeCenter::Instance().delListener(this, Broadcast::kBroadcastStreamNoneReader); DebugL; } @@ -83,6 +82,7 @@ void FFmpegSource::play(const string &src_url,const string &dst_url,int timeout_ if(src){ //推流给自己成功 cb(SockException()); + strongSelf->onGetMediaSource(src); strongSelf->startTimer(timeout_ms); return; } @@ -192,8 +192,7 @@ void FFmpegSource::startTimer(int timeout_ms) { //同步查找流 if (!src) { //流不在线,重新拉流 - strongSelf->play(strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, - [](const SockException &) {}); + strongSelf->play(strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [](const SockException &) {}); } }); } else { @@ -205,29 +204,35 @@ void FFmpegSource::startTimer(int timeout_ms) { } return true; }, _poller); - - NoticeCenter::Instance().delListener(this, Broadcast::kBroadcastStreamNoneReader); - NoticeCenter::Instance().addListener(this, Broadcast::kBroadcastStreamNoneReader,[weakSelf](BroadcastStreamNoneReaderArgs) { - auto strongSelf = weakSelf.lock(); - if (!strongSelf) { - //自身已经销毁 - return; - } - - if(sender.getVhost() != strongSelf->_media_info._vhost || - sender.getApp() != strongSelf->_media_info._app || - sender.getId() != strongSelf->_media_info._streamid){ - //不是自己感兴趣的事件,忽略之 - return; - } - - //该流无人观看,我们停止吧 - if(strongSelf->_onClose){ - strongSelf->_onClose(); - } - }); } void FFmpegSource::setOnClose(const function &cb){ _onClose = cb; -} \ No newline at end of file +} + +bool FFmpegSource::close(MediaSource &sender, bool force) { + auto listener = _listener.lock(); + if(listener && !listener->close(sender,force)){ + //关闭失败 + return false; + } + //该流无人观看,我们停止吧 + if(_onClose){ + _onClose(); + } + return true; +} + +void FFmpegSource::onNoneReader(MediaSource &sender) { + auto listener = _listener.lock(); + if(listener){ + listener->onNoneReader(sender); + }else{ + MediaSourceEvent::onNoneReader(sender); + } +} + +void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) { + _listener = src->getListener(); + src->setListener(shared_from_this()); +} diff --git a/server/FFmpegSource.h b/server/FFmpegSource.h index 6be92fc3..5cf2c4f7 100644 --- a/server/FFmpegSource.h +++ b/server/FFmpegSource.h @@ -39,7 +39,7 @@ using namespace std; using namespace toolkit; using namespace mediakit; -class FFmpegSource : public std::enable_shared_from_this{ +class FFmpegSource : public std::enable_shared_from_this , public MediaSourceEvent{ public: typedef shared_ptr Ptr; typedef function onPlay; @@ -55,6 +55,10 @@ public: private: void findAsync(int maxWaitMS ,const function &cb); void startTimer(int timeout_ms); + void onGetMediaSource(const MediaSource::Ptr &src); + + bool close(MediaSource &sender,bool force) override; + void onNoneReader(MediaSource &sender) override ; private: Process _process; Timer::Ptr _timer; @@ -63,6 +67,7 @@ private: string _src_url; string _dst_url; function _onClose; + std::weak_ptr _listener; }; diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 453db5aa..9a23da09 100644 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -72,6 +72,8 @@ typedef map ApiArgsType; invoker("200 OK", headerOut, val.toStyledString()); \ }); +#define API_ARGS_VALUE sender,headerIn,headerOut,allArgs,val,invoker + #define API_REGIST_INVOKER(field, name, ...) \ s_map_api.emplace("/index/"#field"/"#name,[](API_ARGS,const HttpSession::HttpResponseInvoker &invoker) __VA_ARGS__); @@ -294,6 +296,7 @@ void installWebApi() { obj["delay"] = vecDelay[i++]; val["data"].append(obj); } + val["code"] = API::Success; invoker("200 OK", headerOut, val.toStyledString()); }); }); @@ -311,6 +314,7 @@ void installWebApi() { obj["delay"] = vecDelay[i++]; val["data"].append(obj); } + val["code"] = API::Success; invoker("200 OK", headerOut, val.toStyledString()); }); }); @@ -391,8 +395,6 @@ void installWebApi() { API_REGIST(api,getMediaList,{ CHECK_SECRET(); //获取所有MediaSource列表 - val["code"] = API::Success; - val["msg"] = "success"; MediaSource::for_each_media([&](const string &schema, const string &vhost, const string &app, @@ -416,6 +418,13 @@ void installWebApi() { }); }); + //测试url http://127.0.0.1/index/api/isMediaOnline?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs + API_REGIST(api,isMediaOnline,{ + CHECK_SECRET(); + CHECK_ARGS("schema","vhost","app","stream"); + val["online"] = (bool) (MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"],false)); + }); + //主动关断流,包括关断拉流、推流 //测试url http://127.0.0.1/index/api/close_stream?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1 API_REGIST(api,close_stream,{ @@ -428,14 +437,53 @@ void installWebApi() { allArgs["stream"]); if(src){ bool flag = src->close(allArgs["force"].as()); - val["code"] = flag ? 0 : -1; + val["result"] = flag ? 0 : -1; val["msg"] = flag ? "success" : "close failed"; }else{ - val["code"] = -2; + val["result"] = -2; val["msg"] = "can not find the stream"; } }); + //批量主动关断流,包括关断拉流、推流 + //测试url http://127.0.0.1/index/api/close_streams?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1 + API_REGIST(api,close_streams,{ + CHECK_SECRET(); + //筛选命中个数 + int count_hit = 0; + int count_closed = 0; + list media_list; + MediaSource::for_each_media([&](const string &schema, + const string &vhost, + const string &app, + const string &stream, + const MediaSource::Ptr &media){ + if(!allArgs["schema"].empty() && allArgs["schema"] != schema){ + return; + } + if(!allArgs["vhost"].empty() && allArgs["vhost"] != vhost){ + return; + } + if(!allArgs["app"].empty() && allArgs["app"] != app){ + return; + } + if(!allArgs["stream"].empty() && allArgs["stream"] != stream){ + return; + } + ++count_hit; + media_list.emplace_back(media); + }); + + bool force = allArgs["force"].as(); + for(auto &media : media_list){ + if(media->close(force)){ + ++count_closed; + } + } + val["count_hit"] = count_hit; + val["count_closed"] = count_closed; + }); + //获取所有TcpSession列表信息 //可以根据本地端口和远端ip来筛选 //测试url(筛选某端口下的tcp会话) http://127.0.0.1/index/api/getAllSession?local_port=1935 @@ -446,7 +494,7 @@ void installWebApi() { string &peer_ip = allArgs["peer_ip"]; SessionMap::Instance().for_each_session([&](const string &id,const TcpSession::Ptr &session){ - if(local_port != API::Success && local_port != session->get_local_port()){ + if(local_port != 0 && local_port != session->get_local_port()){ return; } if(!peer_ip.empty() && peer_ip != session->get_peer_ip()){ @@ -470,13 +518,36 @@ void installWebApi() { //踢掉tcp会话 auto session = SessionMap::Instance().get(allArgs["id"]); if(!session){ - val["code"] = API::OtherFailed; - val["msg"] = "can not find the target"; - return; + throw ApiRetException("can not find the target",API::OtherFailed); } session->safeShutdown(); - val["code"] = API::Success; - val["msg"] = "success"; + }); + + + //批量断开tcp连接,比如说可以断开rtsp、rtmp播放器等 + //测试url http://127.0.0.1/index/api/kick_sessions?local_port=1935 + API_REGIST(api,kick_sessions,{ + CHECK_SECRET(); + uint16_t local_port = allArgs["local_port"].as(); + string &peer_ip = allArgs["peer_ip"]; + uint64_t count_hit = 0; + + list session_list; + SessionMap::Instance().for_each_session([&](const string &id,const TcpSession::Ptr &session){ + if(local_port != 0 && local_port != session->get_local_port()){ + return; + } + if(!peer_ip.empty() && peer_ip != session->get_peer_ip()){ + return; + } + session_list.emplace_back(session); + ++count_hit; + }); + + for(auto &session : session_list){ + session->safeShutdown(); + } + val["count_hit"] = (Json::UInt64)count_hit; }); static auto addStreamProxy = [](const string &vhost, @@ -554,7 +625,7 @@ void installWebApi() { }); #if !defined(_WIN32) - static auto addFFmepgSource = [](const string &src_url, + static auto addFFmpegSource = [](const string &src_url, const string &dst_url, int timeout_ms, const function &cb){ @@ -591,7 +662,7 @@ void installWebApi() { auto dst_url = allArgs["dst_url"]; int timeout_ms = allArgs["timeout_ms"]; - addFFmepgSource(src_url,dst_url,timeout_ms,[invoker,val,headerOut](const SockException &ex,const string &key){ + addFFmpegSource(src_url,dst_url,timeout_ms,[invoker,val,headerOut](const SockException &ex,const string &key){ if(ex){ const_cast(val)["code"] = API::OtherFailed; const_cast(val)["msg"] = ex.what(); @@ -602,13 +673,23 @@ void installWebApi() { }); }); - //关闭拉流代理 - //测试url http://127.0.0.1/index/api/delFFmepgSource?key=key - API_REGIST(api,delFFmepgSource,{ + + static auto api_delFFmpegSource = [](API_ARGS,const HttpSession::HttpResponseInvoker &invoker){ CHECK_SECRET(); CHECK_ARGS("key"); lock_guard lck(s_ffmpegMapMtx); val["data"]["flag"] = s_ffmpegMap.erase(allArgs["key"]) == 1; + }; + + //关闭拉流代理 + //测试url http://127.0.0.1/index/api/delFFmepgSource?key=key + API_REGIST(api,delFFmpegSource,{ + api_delFFmpegSource(API_ARGS_VALUE); + }); + + //此处为了兼容之前的拼写错误 + API_REGIST(api,delFFmepgSource,{ + api_delFFmpegSource(API_ARGS_VALUE); }); #endif @@ -677,7 +758,7 @@ void installWebApi() { << allArgs["stream"] << "?vhost=" << allArgs["vhost"]; - addFFmepgSource("http://live.hkstv.hk.lxdns.com/live/hks2/playlist.m3u8",/** ffmpeg拉流支持任意编码格式任意协议 **/ + addFFmpegSource("http://live.hkstv.hk.lxdns.com/live/hks2/playlist.m3u8",/** ffmpeg拉流支持任意编码格式任意协议 **/ dst_url, (1000 * timeout_sec) - 500, [invoker,val,headerOut](const SockException &ex,const string &key){ diff --git a/src/Common/MediaSource.h b/src/Common/MediaSource.h index a54bc8d8..5cfe597d 100644 --- a/src/Common/MediaSource.h +++ b/src/Common/MediaSource.h @@ -167,10 +167,15 @@ public: } listener->onNoneReader(*this); } + virtual void setListener(const std::weak_ptr &listener){ _listener = listener; } + std::weak_ptr getListener(){ + return _listener; + } + template static void for_each_media(FUN && fun){ lock_guard lock(g_mtxMediaSrc); diff --git a/src/Rtsp/RtspPlayer.cpp b/src/Rtsp/RtspPlayer.cpp index be1424d6..aa7dfce3 100644 --- a/src/Rtsp/RtspPlayer.cpp +++ b/src/Rtsp/RtspPlayer.cpp @@ -277,6 +277,13 @@ void RtspPlayer::createUdpSockIfNecessary(int track_idx){ throw std::runtime_error("open rtcp sock failed"); } } + + if(rtpSockRef->get_local_port() % 2 != 0){ + //如果rtp端口不是偶数,那么与rtcp端口互换,目的是兼容一些要求严格的服务器 + Socket::Ptr tmp = rtpSockRef; + rtpSockRef = rtcpSockRef; + rtcpSockRef = tmp; + } }