commit
e2d548284a
|
|
@ -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 &
|
||||
|
||||
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 73eb9077e19e473c643708cf586517bacaa16302
|
||||
Subproject commit 628d3b2527f63b54a5eb38b9e9973254d4a2192b
|
||||
12
README.md
12
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
|
||||
|
|
|
|||
|
|
@ -128,6 +128,9 @@
|
|||
## 编译要求
|
||||
- 编译器支持C++11,GCC4.8/Clang3.3/VC2015或以上
|
||||
- cmake3.2或以上
|
||||
|
||||
## 编译前必看!!!
|
||||
|
||||
- **必须使用git下载完整的代码,不要使用下载zip包的方式下载源码,否则子模块代码默认不下载!你可以像以下这样操作:**
|
||||
```
|
||||
git clone https://github.com/zlmediakit/ZLMediaKit.git
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<void()> &cb){
|
||||
_onClose = cb;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ using namespace std;
|
|||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
class FFmpegSource : public std::enable_shared_from_this<FFmpegSource>{
|
||||
class FFmpegSource : public std::enable_shared_from_this<FFmpegSource> , public MediaSourceEvent{
|
||||
public:
|
||||
typedef shared_ptr<FFmpegSource> Ptr;
|
||||
typedef function<void(const SockException &ex)> onPlay;
|
||||
|
|
@ -55,6 +55,10 @@ public:
|
|||
private:
|
||||
void findAsync(int maxWaitMS ,const function<void(const MediaSource::Ptr &src)> &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<void()> _onClose;
|
||||
std::weak_ptr<MediaSourceEvent> _listener;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ typedef map<string,variant,StrCaseCompare> 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<bool>());
|
||||
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<MediaSource::Ptr> 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<bool>();
|
||||
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<uint16_t>();
|
||||
string &peer_ip = allArgs["peer_ip"];
|
||||
uint64_t count_hit = 0;
|
||||
|
||||
list<TcpSession::Ptr> 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<void(const SockException &ex,const string &key)> &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<Value &>(val)["code"] = API::OtherFailed;
|
||||
const_cast<Value &>(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<decltype(s_ffmpegMapMtx)> 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){
|
||||
|
|
|
|||
|
|
@ -167,10 +167,15 @@ public:
|
|||
}
|
||||
listener->onNoneReader(*this);
|
||||
}
|
||||
|
||||
virtual void setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
std::weak_ptr<MediaSourceEvent> getListener(){
|
||||
return _listener;
|
||||
}
|
||||
|
||||
template <typename FUN>
|
||||
static void for_each_media(FUN && fun){
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue