mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-25 20:27:34 +08:00
Merge branch 'dev' of https://gitee.com/xia-chu/ZLMediaKit into dev
This commit is contained in:
commit
e9d28c1386
8
.github/ISSUE_TEMPLATE/requirement.md
vendored
Normal file
8
.github/ISSUE_TEMPLATE/requirement.md
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
name: issue创建要求
|
||||
about: 不符合模板要求不便定位问题,可能会被管理员直接关闭
|
||||
title: ""
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
@ -92,8 +92,10 @@ segDur=2
|
||||
segNum=3
|
||||
#HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数
|
||||
segRetain=5
|
||||
# 是否广播 ts 切片完成通知
|
||||
#是否广播 ts 切片完成通知
|
||||
broadcastRecordTs=0
|
||||
#直播hls文件删除延时,单位秒,issue: #913
|
||||
deleteDelaySec=0
|
||||
|
||||
[hook]
|
||||
#在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然
|
||||
|
@ -2,10 +2,12 @@
|
||||
%global use_devtoolset 0
|
||||
%bcond_without faac
|
||||
%bcond_without x264
|
||||
%bcond_without webrtc
|
||||
%else
|
||||
%global use_devtoolset 1
|
||||
%bcond_with faac
|
||||
%bcond_with x264
|
||||
%bcond_with webrtc
|
||||
%endif
|
||||
|
||||
%bcond_without openssl
|
||||
@ -22,8 +24,12 @@ URL: https://github.com/xia-chu/ZLMediaKit
|
||||
Source0: %{name}-%{version}.tar.xz
|
||||
|
||||
%if %{with openssl}
|
||||
%if 0%{?rhel} <= 7 && %{with webrtc}
|
||||
BuildRequires: openssl11-devel
|
||||
%else
|
||||
BuildRequires: openssl-devel
|
||||
%endif
|
||||
%endif
|
||||
|
||||
%if %{with mysql}
|
||||
BuildRequires: mysql-devel
|
||||
@ -37,6 +43,10 @@ BuildRequires: faac-devel
|
||||
BuildRequires: x264-devel
|
||||
%endif
|
||||
|
||||
%if %{with webrtc}
|
||||
BuildRequires: libsrtp-devel >= 2.0
|
||||
%endif
|
||||
|
||||
%if 0%{?use_devtoolset}
|
||||
BuildRequires: devtoolset-8-gcc-c++
|
||||
%endif
|
||||
@ -88,6 +98,10 @@ pushd %{_target_platform}
|
||||
-DENABLE_MYSQL:BOOL=%{with mysql} \
|
||||
-DENABLE_FAAC:BOOL=%{with faac} \
|
||||
-DENABLE_X264:BOOL=%{with x264} \
|
||||
-DENABLE_WEBRTC:BOOL=%{with webrtc} \
|
||||
%if %{with webrtc} && 0%{?rhel} <= 7
|
||||
-DOPENSSL_ROOT_DIR:STRING="/usr/lib64/openssl11;/usr/include/openssl11" \
|
||||
%endif
|
||||
-DENABLE_MP4:BOOL=ON \
|
||||
-DENABLE_RTPPROXY:BOOL=ON \
|
||||
-DENABLE_API:BOOL=ON \
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "ff20487b-d269-40c3-b811-44bc643a3b74",
|
||||
"_postman_id": "fe6cdfbd-531d-45e6-87e5-d460ce9e6328",
|
||||
"name": "ZLMediaKit",
|
||||
"description": "媒体服务器",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
@ -518,6 +518,12 @@
|
||||
"value": "10",
|
||||
"description": "拉流超时时间,单位秒,float类型",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "retry_count",
|
||||
"value": null,
|
||||
"description": "拉流重试次数,不传此参数或传值<=0时,则无限重试",
|
||||
"disabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -555,6 +561,106 @@
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "添加rtsp/rtmp推流(addStreamPusherProxy)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/addStreamPusherProxy?secret={{ZLMediaKit_secret}}&schema=rtmp&vhost={{defaultVhost}}&app=live&stream=test&dst_url=rtmp://127.0.0.1/live/push",
|
||||
"host": [
|
||||
"{{ZLMediaKit_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"index",
|
||||
"api",
|
||||
"addStreamPusherProxy"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
},
|
||||
{
|
||||
"key": "schema",
|
||||
"value": "rtmp",
|
||||
"description": "推流协议,支持rtsp、rtmp,大小写敏感"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
"value": "{{defaultVhost}}",
|
||||
"description": "已注册流的虚拟主机,一般为__defaultVhost__"
|
||||
},
|
||||
{
|
||||
"key": "app",
|
||||
"value": "live",
|
||||
"description": "已注册流的应用名,例如live"
|
||||
},
|
||||
{
|
||||
"key": "stream",
|
||||
"value": "test",
|
||||
"description": "已注册流的id名,例如test"
|
||||
},
|
||||
{
|
||||
"key": "dst_url",
|
||||
"value": "rtmp://127.0.0.1/live/push",
|
||||
"description": "推流地址,需要与schema字段协议一致"
|
||||
},
|
||||
{
|
||||
"key": "rtp_type",
|
||||
"value": "0",
|
||||
"description": "rtsp推流时,推流方式,0:tcp,1:udp",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "timeout_sec",
|
||||
"value": "10",
|
||||
"description": "推流超时时间,单位秒,float类型",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "retry_count",
|
||||
"value": null,
|
||||
"description": "推流重试次数,不传此参数或传值<=0时,则无限重试",
|
||||
"disabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "关闭推流(delStreamPusherProxy)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/delStreamPusherProxy?secret={{ZLMediaKit_secret}}&key=__defaultVhost__/live/test",
|
||||
"host": [
|
||||
"{{ZLMediaKit_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"index",
|
||||
"api",
|
||||
"delStreamPusherProxy"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
},
|
||||
{
|
||||
"key": "key",
|
||||
"value": "__defaultVhost__/live/test",
|
||||
"description": "addStreamPusherProxy接口返回的key"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "添加FFmpeg拉流代理(addFFmpegSource)",
|
||||
"request": {
|
||||
@ -786,7 +892,7 @@
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/startRecord?secret={{ZLMediaKit_secret}}&type=1&vhost={{defaultVhost}}&app=live&stream=obs&customized_path",
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/startRecord?secret={{ZLMediaKit_secret}}&type=1&vhost={{defaultVhost}}&app=live&stream=obs",
|
||||
"host": [
|
||||
"{{ZLMediaKit_URL}}"
|
||||
],
|
||||
@ -824,14 +930,14 @@
|
||||
{
|
||||
"key": "customized_path",
|
||||
"value": null,
|
||||
"disabled": true,
|
||||
"description": "录像文件保存自定义根目录,为空则采用配置文件设置"
|
||||
"description": "录像文件保存自定义根目录,为空则采用配置文件设置",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "max_second",
|
||||
"value": "1000",
|
||||
"disabled": true,
|
||||
"description": "MP4录制的切片时间大小,单位秒"
|
||||
"description": "MP4录制的切片时间大小,单位秒",
|
||||
"disabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1281,7 +1387,6 @@
|
||||
{
|
||||
"listen": "prerequest",
|
||||
"script": {
|
||||
"id": "90757ea3-58c0-4f84-8000-513ed7088bbc",
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
""
|
||||
@ -1291,7 +1396,6 @@
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"id": "0ddf2b8e-9932-409d-a055-1ab3b7765600",
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
""
|
||||
@ -1301,20 +1405,16 @@
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"id": "ce426571-eb1e-4067-8901-01978c982fed",
|
||||
"key": "ZLMediaKit_URL",
|
||||
"value": "zlmediakit.com:8880"
|
||||
},
|
||||
{
|
||||
"id": "2d3dfd4a-a39c-47d8-a3e9-37d80352ea5f",
|
||||
"key": "ZLMediaKit_secret",
|
||||
"value": "035c73f7-bb6b-4889-a715-d9eb2d1925cc"
|
||||
},
|
||||
{
|
||||
"id": "0aacc473-3a2e-4ef9-b415-e86ce71e0c42",
|
||||
"key": "defaultVhost",
|
||||
"value": "__defaultVhost__"
|
||||
}
|
||||
],
|
||||
"protocolProfileBehavior": {}
|
||||
]
|
||||
}
|
@ -27,6 +27,7 @@
|
||||
#include "Network/TcpServer.h"
|
||||
#include "Network/UdpServer.h"
|
||||
#include "Player/PlayerProxy.h"
|
||||
#include "Pusher/PusherProxy.h"
|
||||
#include "Util/MD5.h"
|
||||
#include "WebApi.h"
|
||||
#include "WebHook.h"
|
||||
@ -262,11 +263,15 @@ static inline void addHttpListener(){
|
||||
}
|
||||
|
||||
//拉流代理器列表
|
||||
static unordered_map<string ,PlayerProxy::Ptr> s_proxyMap;
|
||||
static unordered_map<string, PlayerProxy::Ptr> s_proxyMap;
|
||||
static recursive_mutex s_proxyMapMtx;
|
||||
|
||||
//推流代理器列表
|
||||
static unordered_map<string, PusherProxy::Ptr> s_proxyPusherMap;
|
||||
static recursive_mutex s_proxyPusherMapMtx;
|
||||
|
||||
//FFmpeg拉流代理器列表
|
||||
static unordered_map<string ,FFmpegSource::Ptr> s_ffmpegMap;
|
||||
static unordered_map<string, FFmpegSource::Ptr> s_ffmpegMap;
|
||||
static recursive_mutex s_ffmpegMapMtx;
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
@ -275,10 +280,15 @@ static unordered_map<string, RtpServer::Ptr> s_rtpServerMap;
|
||||
static recursive_mutex s_rtpServerMapMtx;
|
||||
#endif
|
||||
|
||||
static inline string getProxyKey(const string &vhost,const string &app,const string &stream){
|
||||
static inline string getProxyKey(const string &vhost, const string &app, const string &stream) {
|
||||
return vhost + "/" + app + "/" + stream;
|
||||
}
|
||||
|
||||
static inline string getPusherKey(const string &schema, const string &vhost, const string &app, const string &stream,
|
||||
const string &dst_url) {
|
||||
return schema + "/" + vhost + "/" + app + "/" + stream + "/" + MD5(dst_url).hexdigest();
|
||||
}
|
||||
|
||||
Value makeMediaSourceJson(MediaSource &media){
|
||||
Value item;
|
||||
item["schema"] = media.getSchema();
|
||||
@ -634,14 +644,103 @@ void installWebApi() {
|
||||
val["count_hit"] = (Json::UInt64)count_hit;
|
||||
});
|
||||
|
||||
static auto addStreamPusherProxy = [](const string &schema,
|
||||
const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
const string &url,
|
||||
int retry_count,
|
||||
int rtp_type,
|
||||
float timeout_sec,
|
||||
const function<void(const SockException &ex, const string &key)> &cb) {
|
||||
auto key = getPusherKey(schema, vhost, app, stream, url);
|
||||
auto src = MediaSource::find(schema, vhost, app, stream);
|
||||
if (!src) {
|
||||
cb(SockException(Err_other, "can not find the source stream"), key);
|
||||
return;
|
||||
}
|
||||
lock_guard<recursive_mutex> lck(s_proxyPusherMapMtx);
|
||||
if (s_proxyPusherMap.find(key) != s_proxyPusherMap.end()) {
|
||||
//已经在推流了
|
||||
cb(SockException(Err_success), key);
|
||||
return;
|
||||
}
|
||||
|
||||
//添加推流代理
|
||||
PusherProxy::Ptr pusher(new PusherProxy(src, retry_count ? retry_count : -1));
|
||||
s_proxyPusherMap[key] = pusher;
|
||||
|
||||
//指定RTP over TCP(播放rtsp时有效)
|
||||
(*pusher)[kRtpType] = rtp_type;
|
||||
|
||||
if (timeout_sec > 0.1) {
|
||||
//推流握手超时时间
|
||||
(*pusher)[kTimeoutMS] = timeout_sec * 1000;
|
||||
}
|
||||
|
||||
//开始推流,如果推流失败或者推流中止,将会自动重试若干次,默认一直重试
|
||||
pusher->setPushCallbackOnce([cb, key, url](const SockException &ex) {
|
||||
if (ex) {
|
||||
WarnL << "Push " << url << " failed, key: " << key << ", err: " << ex.what();
|
||||
lock_guard<recursive_mutex> lck(s_proxyPusherMapMtx);
|
||||
s_proxyPusherMap.erase(key);
|
||||
}
|
||||
cb(ex, key);
|
||||
});
|
||||
|
||||
//被主动关闭推流
|
||||
pusher->setOnClose([key, url](const SockException &ex) {
|
||||
WarnL << "Push " << url << " failed, key: " << key << ", err: " << ex.what();
|
||||
lock_guard<recursive_mutex> lck(s_proxyPusherMapMtx);
|
||||
s_proxyPusherMap.erase(key);
|
||||
});
|
||||
pusher->publish(url);
|
||||
};
|
||||
|
||||
//动态添加rtsp/rtmp推流代理
|
||||
//测试url http://127.0.0.1/index/api/addStreamPusherProxy?schema=rtmp&vhost=__defaultVhost__&app=proxy&stream=0&dst_url=rtmp://127.0.0.1/live/obs
|
||||
api_regist("/index/api/addStreamPusherProxy", [](API_ARGS_MAP_ASYNC) {
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("schema", "vhost", "app", "stream", "dst_url");
|
||||
auto dst_url = allArgs["dst_url"];
|
||||
addStreamPusherProxy(allArgs["schema"],
|
||||
allArgs["vhost"],
|
||||
allArgs["app"],
|
||||
allArgs["stream"],
|
||||
allArgs["dst_url"],
|
||||
allArgs["retry_count"],
|
||||
allArgs["rtp_type"],
|
||||
allArgs["timeout_sec"],
|
||||
[invoker, val, headerOut, dst_url](const SockException &ex, const string &key) mutable {
|
||||
if (ex) {
|
||||
val["code"] = API::OtherFailed;
|
||||
val["msg"] = ex.what();
|
||||
} else {
|
||||
val["data"]["key"] = key;
|
||||
InfoL << "Publish success, please play with player:" << dst_url;
|
||||
}
|
||||
invoker(200, headerOut, val.toStyledString());
|
||||
});
|
||||
});
|
||||
|
||||
//关闭推流代理
|
||||
//测试url http://127.0.0.1/index/api/delStreamPusherProxy?key=__defaultVhost__/proxy/0
|
||||
api_regist("/index/api/delStreamPusherProxy", [](API_ARGS_MAP) {
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("key");
|
||||
lock_guard<recursive_mutex> lck(s_proxyPusherMapMtx);
|
||||
val["data"]["flag"] = s_proxyPusherMap.erase(allArgs["key"]) == 1;
|
||||
});
|
||||
|
||||
static auto addStreamProxy = [](const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
const string &url,
|
||||
int retry_count,
|
||||
bool enable_hls,
|
||||
bool enable_mp4,
|
||||
int rtp_type,
|
||||
float timeoutSec,
|
||||
float timeout_sec,
|
||||
const function<void(const SockException &ex,const string &key)> &cb){
|
||||
auto key = getProxyKey(vhost,app,stream);
|
||||
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
||||
@ -651,15 +750,15 @@ void installWebApi() {
|
||||
return;
|
||||
}
|
||||
//添加拉流代理
|
||||
PlayerProxy::Ptr player(new PlayerProxy(vhost, app, stream, enable_hls, enable_mp4));
|
||||
PlayerProxy::Ptr player(new PlayerProxy(vhost, app, stream, enable_hls, enable_mp4, retry_count ? retry_count : -1));
|
||||
s_proxyMap[key] = player;
|
||||
|
||||
|
||||
//指定RTP over TCP(播放rtsp时有效)
|
||||
(*player)[kRtpType] = rtp_type;
|
||||
|
||||
if (timeoutSec > 0.1) {
|
||||
if (timeout_sec > 0.1) {
|
||||
//播放握手超时时间
|
||||
(*player)[kTimeoutMS] = timeoutSec * 1000;
|
||||
(*player)[kTimeoutMS] = timeout_sec * 1000;
|
||||
}
|
||||
|
||||
//开始播放,如果播放失败或者播放中止,将会自动重试若干次,默认一直重试
|
||||
@ -688,6 +787,7 @@ void installWebApi() {
|
||||
allArgs["app"],
|
||||
allArgs["stream"],
|
||||
allArgs["url"],
|
||||
allArgs["retry_count"],
|
||||
allArgs["enable_hls"],/* 是否hls转发 */
|
||||
allArgs["enable_mp4"],/* 是否MP4录制 */
|
||||
allArgs["rtp_type"],
|
||||
@ -1265,6 +1365,7 @@ void installWebApi() {
|
||||
allArgs["stream"],
|
||||
/** 支持rtsp和rtmp方式拉流 ,rtsp支持h265/h264/aac,rtmp仅支持h264/aac **/
|
||||
"rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov",
|
||||
-1,/*无限重试*/
|
||||
true,/* 开启hls转发 */
|
||||
false,/* 禁用MP4录制 */
|
||||
0,//rtp over tcp方式拉流
|
||||
|
@ -260,6 +260,8 @@ const string kFileBufSize = HLS_FIELD"fileBufSize";
|
||||
const string kFilePath = HLS_FIELD"filePath";
|
||||
// 是否广播 ts 切片完成通知
|
||||
const string kBroadcastRecordTs = HLS_FIELD"broadcastRecordTs";
|
||||
//hls直播文件删除延时,单位秒
|
||||
const string kDeleteDelaySec = HLS_FIELD"deleteDelaySec";
|
||||
|
||||
onceToken token([](){
|
||||
mINI::Instance()[kSegmentDuration] = 2;
|
||||
@ -268,6 +270,7 @@ onceToken token([](){
|
||||
mINI::Instance()[kFileBufSize] = 64 * 1024;
|
||||
mINI::Instance()[kFilePath] = "./www";
|
||||
mINI::Instance()[kBroadcastRecordTs] = false;
|
||||
mINI::Instance()[kDeleteDelaySec] = 0;
|
||||
},nullptr);
|
||||
} //namespace Hls
|
||||
|
||||
|
@ -293,6 +293,8 @@ extern const string kFileBufSize;
|
||||
extern const string kFilePath;
|
||||
// 是否广播 ts 切片完成通知
|
||||
extern const string kBroadcastRecordTs;
|
||||
//hls直播文件删除延时,单位秒
|
||||
extern const string kDeleteDelaySec;
|
||||
} //namespace Hls
|
||||
|
||||
////////////Rtp代理相关配置///////////
|
||||
|
99
src/Pusher/PusherProxy.cpp
Normal file
99
src/Pusher/PusherProxy.cpp
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "PusherProxy.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
PusherProxy::PusherProxy(const MediaSource::Ptr &src, int retry_count, const EventPoller::Ptr &poller)
|
||||
: MediaPusher(src, poller){
|
||||
_retry_count = retry_count;
|
||||
_on_close = [](const SockException &) {};
|
||||
_weak_src = src;
|
||||
}
|
||||
|
||||
PusherProxy::~PusherProxy() {
|
||||
_timer.reset();
|
||||
}
|
||||
|
||||
void PusherProxy::setPushCallbackOnce(const function<void(const SockException &ex)> &cb) {
|
||||
_on_publish = cb;
|
||||
}
|
||||
|
||||
void PusherProxy::setOnClose(const function<void(const SockException &ex)> &cb) {
|
||||
_on_close = cb;
|
||||
}
|
||||
|
||||
void PusherProxy::publish(const string &dst_url) {
|
||||
std::weak_ptr<PusherProxy> weak_self = shared_from_this();
|
||||
std::shared_ptr<int> failed_cnt(new int(0));
|
||||
|
||||
setOnPublished([weak_self, dst_url, failed_cnt](const SockException &err) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strong_self->_on_publish) {
|
||||
strong_self->_on_publish(err);
|
||||
strong_self->_on_publish = nullptr;
|
||||
}
|
||||
|
||||
auto src = strong_self->_weak_src.lock();
|
||||
if (!err) {
|
||||
// 推流成功
|
||||
*failed_cnt = 0;
|
||||
InfoL << "Publish " << dst_url << " success";
|
||||
} else if (src && (*failed_cnt < strong_self->_retry_count || strong_self->_retry_count < 0)) {
|
||||
// 推流失败,延时重试推送
|
||||
strong_self->rePublish(dst_url, (*failed_cnt)++);
|
||||
} else {
|
||||
//如果媒体源已经注销, 或达到了最大重试次数,回调关闭
|
||||
strong_self->_on_close(err);
|
||||
}
|
||||
});
|
||||
|
||||
setOnShutdown([weak_self, dst_url, failed_cnt](const SockException &err) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto src = strong_self->_weak_src.lock();
|
||||
//推流异常中断,延时重试播放
|
||||
if (src && (*failed_cnt < strong_self->_retry_count || strong_self->_retry_count < 0)) {
|
||||
strong_self->rePublish(dst_url, (*failed_cnt)++);
|
||||
} else {
|
||||
//如果媒体源已经注销, 或达到了最大重试次数,回调关闭
|
||||
strong_self->_on_close(err);
|
||||
}
|
||||
});
|
||||
|
||||
MediaPusher::publish(dst_url);
|
||||
}
|
||||
|
||||
void PusherProxy::rePublish(const string &dst_url, int failed_cnt) {
|
||||
auto delay = MAX(2 * 1000, MIN(failed_cnt * 3000, 60 * 1000));
|
||||
weak_ptr<PusherProxy> weak_self = shared_from_this();
|
||||
_timer = std::make_shared<Timer>(delay / 1000.0f, [weak_self, dst_url, failed_cnt]() {
|
||||
//推流失败次数越多,则延时越长
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return false;
|
||||
}
|
||||
WarnL << "推流重试[" << failed_cnt << "]:" << dst_url;
|
||||
strong_self->MediaPusher::publish(dst_url);
|
||||
return false;
|
||||
}, getPoller());
|
||||
}
|
||||
|
||||
} /* namespace mediakit */
|
63
src/Pusher/PusherProxy.h
Normal file
63
src/Pusher/PusherProxy.h
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef SRC_DEVICE_PUSHERPROXY_H
|
||||
#define SRC_DEVICE_PUSHERPROXY_H
|
||||
|
||||
#include "Pusher/MediaPusher.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class PusherProxy : public MediaPusher, public std::enable_shared_from_this<PusherProxy> {
|
||||
public:
|
||||
typedef std::shared_ptr<PusherProxy> Ptr;
|
||||
|
||||
// 如果retry_count<0,则一直重试播放;否则重试retry_count次数
|
||||
// 默认一直重试,创建此对象时候,需要外部保证MediaSource存在
|
||||
PusherProxy(const MediaSource::Ptr &src, int retry_count = -1, const EventPoller::Ptr &poller = nullptr);
|
||||
~PusherProxy() override;
|
||||
|
||||
/**
|
||||
* 设置push结果回调,只触发一次;在publish执行之前有效
|
||||
* @param cb 回调对象
|
||||
*/
|
||||
void setPushCallbackOnce(const function<void(const SockException &ex)> &cb);
|
||||
|
||||
/**
|
||||
* 设置主动关闭回调
|
||||
* @param cb 回调对象
|
||||
*/
|
||||
void setOnClose(const function<void(const SockException &ex)> &cb);
|
||||
|
||||
/**
|
||||
* 开始拉流播放
|
||||
* @param dstUrl 目标推流地址
|
||||
*/
|
||||
void publish(const string& dstUrl) override;
|
||||
|
||||
private:
|
||||
// 重推逻辑函数
|
||||
void rePublish(const string &dstUrl, int iFailedCnt);
|
||||
|
||||
private:
|
||||
int _retry_count;
|
||||
Timer::Ptr _timer;
|
||||
std::weak_ptr<MediaSource> _weak_src;
|
||||
function<void(const SockException &ex)> _on_close;
|
||||
function<void(const SockException &ex)> _on_publish;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
#endif //SRC_DEVICE_PUSHERPROXY_H
|
@ -23,6 +23,7 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file,
|
||||
uint32_t bufSize,
|
||||
float seg_duration,
|
||||
uint32_t seg_number) : HlsMaker(seg_duration, seg_number) {
|
||||
_poller = EventPollerPool::Instance().getPoller();
|
||||
_path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/'));
|
||||
_path_hls = m3u8_file;
|
||||
_params = params;
|
||||
@ -35,18 +36,30 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file,
|
||||
}
|
||||
|
||||
HlsMakerImp::~HlsMakerImp() {
|
||||
clearCache();
|
||||
clearCache(false);
|
||||
}
|
||||
|
||||
void HlsMakerImp::clearCache() {
|
||||
void HlsMakerImp::clearCache(bool immediately) {
|
||||
//录制完了
|
||||
flushLastSegment(true);
|
||||
if (isLive()) {
|
||||
//hls直播才删除文件
|
||||
clear();
|
||||
_file = nullptr;
|
||||
_segment_file_paths.clear();
|
||||
if (!isLive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
clear();
|
||||
_file = nullptr;
|
||||
_segment_file_paths.clear();
|
||||
|
||||
//hls直播才删除文件
|
||||
GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec);
|
||||
if (!delay || immediately) {
|
||||
File::delete_file(_path_prefix.data());
|
||||
} else {
|
||||
auto path_prefix = _path_prefix;
|
||||
_poller->doDelayTask(delay * 1000, [path_prefix]() {
|
||||
File::delete_file(path_prefix.data());
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,8 +47,9 @@ public:
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
* @param immediately 时候立即删除
|
||||
*/
|
||||
void clearCache();
|
||||
void clearCache(bool immediately = true);
|
||||
|
||||
protected:
|
||||
string onOpenSegment(uint64_t index) override ;
|
||||
@ -69,6 +70,7 @@ private:
|
||||
std::shared_ptr<FILE> _file;
|
||||
std::shared_ptr<char> _file_buf;
|
||||
HlsMediaSource::Ptr _media_src;
|
||||
EventPoller::Ptr _poller;
|
||||
map<uint64_t/*index*/,string/*file_path*/> _segment_file_paths;
|
||||
};
|
||||
|
||||
|
@ -115,7 +115,14 @@ public:
|
||||
|
||||
class RtmpHeader {
|
||||
public:
|
||||
uint8_t flags;
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
uint8_t fmt : 2;
|
||||
uint8_t chunk_id : 6;
|
||||
#else
|
||||
uint8_t chunk_id : 6;
|
||||
//0、1、2、3分别对应 12、8、4、1长度
|
||||
uint8_t fmt : 2;
|
||||
#endif
|
||||
uint8_t time_stamp[3];
|
||||
uint8_t body_size[3];
|
||||
uint8_t type_id;
|
||||
|
@ -210,7 +210,8 @@ void RtmpProtocol::sendRtmp(uint8_t type, uint32_t stream_index, const Buffer::P
|
||||
buffer_header->setSize(sizeof(RtmpHeader));
|
||||
//对rtmp头赋值,如果使用整形赋值,在arm android上可能由于数据对齐导致总线错误的问题
|
||||
RtmpHeader *header = (RtmpHeader *) buffer_header->data();
|
||||
header->flags = (chunk_id & 0x3f) | (0 << 6);
|
||||
header->fmt = 0;
|
||||
header->chunk_id = chunk_id;
|
||||
header->type_id = type;
|
||||
set_be24(header->time_stamp, ext_stamp ? 0xFFFFFF : stamp);
|
||||
set_be24(header->body_size, (uint32_t)buf->size());
|
||||
@ -232,7 +233,9 @@ void RtmpProtocol::sendRtmp(uint8_t type, uint32_t stream_index, const Buffer::P
|
||||
BufferRaw::Ptr buffer_flags = obtainBuffer();
|
||||
buffer_flags->setCapacity(1);
|
||||
buffer_flags->setSize(1);
|
||||
buffer_flags->data()[0] = (chunk_id & 0x3f) | (3 << 6);
|
||||
header = (RtmpHeader *) buffer_flags->data();
|
||||
header->fmt = 3;
|
||||
header->chunk_id = chunk_id;
|
||||
|
||||
size_t offset = 0;
|
||||
size_t totalSize = sizeof(RtmpHeader);
|
||||
@ -523,15 +526,15 @@ const char* RtmpProtocol::handle_C2(const char *data, size_t len) {
|
||||
return handle_rtmp(data + C1_HANDSHARK_SIZE, len - C1_HANDSHARK_SIZE);
|
||||
}
|
||||
|
||||
static const size_t HEADER_LENGTH[] = {12, 8, 4, 1};
|
||||
static constexpr size_t HEADER_LENGTH[] = {12, 8, 4, 1};
|
||||
|
||||
const char* RtmpProtocol::handle_rtmp(const char *data, size_t len) {
|
||||
auto ptr = data;
|
||||
while (len) {
|
||||
int offset = 0;
|
||||
uint8_t flags = ptr[0];
|
||||
size_t header_len = HEADER_LENGTH[flags >> 6];
|
||||
_now_chunk_id = flags & 0x3f;
|
||||
size_t offset = 0;
|
||||
auto header = (RtmpHeader *) ptr;
|
||||
auto header_len = HEADER_LENGTH[header->fmt];
|
||||
_now_chunk_id = header->chunk_id;
|
||||
switch (_now_chunk_id) {
|
||||
case 0: {
|
||||
//0 值表示二字节形式,并且 ID 范围 64 - 319
|
||||
@ -565,7 +568,7 @@ const char* RtmpProtocol::handle_rtmp(const char *data, size_t len) {
|
||||
//need more data
|
||||
return ptr;
|
||||
}
|
||||
RtmpHeader &header = *((RtmpHeader *) (ptr + offset));
|
||||
header = (RtmpHeader *) (ptr + offset);
|
||||
auto &pr = _map_chunk_data[_now_chunk_id];
|
||||
auto &now_packet = pr.first;
|
||||
auto &last_packet = pr.second;
|
||||
@ -583,12 +586,12 @@ const char* RtmpProtocol::handle_rtmp(const char *data, size_t len) {
|
||||
switch (header_len) {
|
||||
case 12:
|
||||
chunk_data.is_abs_stamp = true;
|
||||
chunk_data.stream_index = load_le32(header.stream_index);
|
||||
chunk_data.stream_index = load_le32(header->stream_index);
|
||||
case 8:
|
||||
chunk_data.body_size = load_be24(header.body_size);
|
||||
chunk_data.type_id = header.type_id;
|
||||
chunk_data.body_size = load_be24(header->body_size);
|
||||
chunk_data.type_id = header->type_id;
|
||||
case 4:
|
||||
chunk_data.ts_field = load_be24(header.time_stamp);
|
||||
chunk_data.ts_field = load_be24(header->time_stamp);
|
||||
}
|
||||
|
||||
auto time_stamp = chunk_data.ts_field;
|
||||
|
@ -47,6 +47,7 @@ void RtmpPusher::teardown() {
|
||||
}
|
||||
|
||||
void RtmpPusher::onPublishResult(const SockException &ex, bool handshake_done) {
|
||||
DebugL << ex.what();
|
||||
if (ex.getErrCode() == Err_shutdown) {
|
||||
//主动shutdown的,不触发回调
|
||||
return;
|
||||
|
@ -103,19 +103,16 @@ static const char *getCodecName(int codec_id) {
|
||||
void DecoderImp::onStream(int stream, int codecid, const void *extra, size_t bytes, int finish){
|
||||
switch (codecid) {
|
||||
case PSI_STREAM_H264: {
|
||||
InfoL << "got video track: H264";
|
||||
onTrack(std::make_shared<H264Track>());
|
||||
break;
|
||||
}
|
||||
|
||||
case PSI_STREAM_H265: {
|
||||
InfoL << "got video track: H265";
|
||||
onTrack(std::make_shared<H265Track>());
|
||||
break;
|
||||
}
|
||||
|
||||
case PSI_STREAM_AAC: {
|
||||
InfoL<< "got audio track: AAC";
|
||||
onTrack(std::make_shared<AACTrack>());
|
||||
break;
|
||||
}
|
||||
@ -123,14 +120,12 @@ void DecoderImp::onStream(int stream, int codecid, const void *extra, size_t byt
|
||||
case PSI_STREAM_AUDIO_G711A:
|
||||
case PSI_STREAM_AUDIO_G711U: {
|
||||
auto codec = codecid == PSI_STREAM_AUDIO_G711A ? CodecG711A : CodecG711U;
|
||||
InfoL << "got audio track: G711";
|
||||
//G711传统只支持 8000/1/16的规格,FFmpeg貌似做了扩展,但是这里不管它了
|
||||
onTrack(std::make_shared<G711Track>(codec, 8000, 1, 16));
|
||||
break;
|
||||
}
|
||||
|
||||
case PSI_STREAM_AUDIO_OPUS: {
|
||||
InfoL << "got audio track: opus";
|
||||
onTrack(std::make_shared<OpusTrack>());
|
||||
break;
|
||||
}
|
||||
@ -223,8 +218,11 @@ void DecoderImp::onStream(int stream,int codecid,const void *extra,size_t bytes,
|
||||
#endif
|
||||
|
||||
void DecoderImp::onTrack(const Track::Ptr &track) {
|
||||
_tracks[track->getTrackType()] = track;
|
||||
_sink->addTrack(track);
|
||||
if (!_tracks[track->getTrackType()]) {
|
||||
_tracks[track->getTrackType()] = track;
|
||||
_sink->addTrack(track);
|
||||
InfoL << "got track: " << track->getCodecName();
|
||||
}
|
||||
}
|
||||
|
||||
void DecoderImp::onFrame(const Frame::Ptr &frame) {
|
||||
|
@ -14,6 +14,8 @@
|
||||
#include "Http/HttpTSPlayer.h"
|
||||
#include "Extension/CommonRtp.h"
|
||||
#include "Extension/H264Rtp.h"
|
||||
#include "Extension/Factory.h"
|
||||
#include "Extension/Opus.h"
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
@ -22,6 +24,40 @@ static inline bool checkTS(const uint8_t *packet, size_t bytes){
|
||||
return bytes % TS_PACKET_SIZE == 0 && packet[0] == TS_SYNC_BYTE;
|
||||
}
|
||||
|
||||
class RtpReceiverImp : public RtpReceiver {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<RtpReceiverImp>;
|
||||
RtpReceiverImp(int sample_rate, function<void(RtpPacket::Ptr rtp)> cb, function<void(const RtpPacket::Ptr &rtp)> cb_before = nullptr){
|
||||
_sample_rate = sample_rate;
|
||||
_on_sort = std::move(cb);
|
||||
_on_before_sort = std::move(cb_before);
|
||||
}
|
||||
|
||||
~RtpReceiverImp() override = default;
|
||||
|
||||
bool inputRtp(TrackType type, uint8_t *ptr, size_t len){
|
||||
return handleOneRtp((int) type, type, _sample_rate, ptr, len);
|
||||
}
|
||||
|
||||
protected:
|
||||
void onRtpSorted(RtpPacket::Ptr rtp, int track_index) override {
|
||||
_on_sort(std::move(rtp));
|
||||
}
|
||||
|
||||
void onBeforeRtpSorted(const RtpPacket::Ptr &rtp, int track_index) override {
|
||||
if (_on_before_sort) {
|
||||
_on_before_sort(rtp);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int _sample_rate;
|
||||
function<void(RtpPacket::Ptr rtp)> _on_sort;
|
||||
function<void(const RtpPacket::Ptr &rtp)> _on_before_sort;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
GB28181Process::GB28181Process(const MediaInfo &media_info, MediaSinkInterface *interface) {
|
||||
assert(interface);
|
||||
_media_info = media_info;
|
||||
@ -30,26 +66,80 @@ GB28181Process::GB28181Process(const MediaInfo &media_info, MediaSinkInterface *
|
||||
|
||||
GB28181Process::~GB28181Process() {}
|
||||
|
||||
bool GB28181Process::inputRtp(bool, const char *data, size_t data_len) {
|
||||
return handleOneRtp(0, TrackVideo, 90000, (unsigned char *) data, data_len);
|
||||
void GB28181Process::onRtpSorted(RtpPacket::Ptr rtp) {
|
||||
_rtp_decoder[rtp->getHeader()->pt]->inputRtp(rtp, false);
|
||||
}
|
||||
|
||||
void GB28181Process::onRtpSorted(RtpPacket::Ptr rtp, int) {
|
||||
auto pt = rtp->getHeader()->pt;
|
||||
if (!_rtp_decoder) {
|
||||
bool GB28181Process::inputRtp(bool, const char *data, size_t data_len) {
|
||||
RtpHeader *header = (RtpHeader *) data;
|
||||
auto pt = header->pt;
|
||||
auto &ref = _rtp_receiver[pt];
|
||||
if (!ref) {
|
||||
if (_rtp_receiver.size() > 2) {
|
||||
//防止pt类型太多导致内存溢出
|
||||
throw std::invalid_argument("rtp pt类型不得超过2种!");
|
||||
}
|
||||
switch (pt) {
|
||||
case 98: {
|
||||
//H264负载
|
||||
_rtp_decoder = std::make_shared<H264RtpDecoder>();
|
||||
_interface->addTrack(std::make_shared<H264Track>());
|
||||
case 100: {
|
||||
//opus负载
|
||||
ref = std::make_shared<RtpReceiverImp>(48000,[this](RtpPacket::Ptr rtp) {
|
||||
onRtpSorted(std::move(rtp));
|
||||
});
|
||||
|
||||
auto track = std::make_shared<OpusTrack>();
|
||||
_interface->addTrack(track);
|
||||
_rtp_decoder[pt] = Factory::getRtpDecoderByTrack(track);
|
||||
break;
|
||||
}
|
||||
|
||||
case 99: {
|
||||
//H265负载
|
||||
ref = std::make_shared<RtpReceiverImp>(90000,[this](RtpPacket::Ptr rtp) {
|
||||
onRtpSorted(std::move(rtp));
|
||||
});
|
||||
|
||||
auto track = std::make_shared<H265Track>();
|
||||
_interface->addTrack(track);
|
||||
_rtp_decoder[pt] = Factory::getRtpDecoderByTrack(track);
|
||||
break;
|
||||
}
|
||||
case 98: {
|
||||
//H264负载
|
||||
ref = std::make_shared<RtpReceiverImp>(90000,[this](RtpPacket::Ptr rtp) {
|
||||
onRtpSorted(std::move(rtp));
|
||||
});
|
||||
|
||||
auto track = std::make_shared<H264Track>();
|
||||
_interface->addTrack(track);
|
||||
_rtp_decoder[pt] = Factory::getRtpDecoderByTrack(track);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0:
|
||||
//CodecG711U
|
||||
case 8: {
|
||||
//CodecG711A
|
||||
ref = std::make_shared<RtpReceiverImp>(8000,[this](RtpPacket::Ptr rtp) {
|
||||
onRtpSorted(std::move(rtp));
|
||||
});
|
||||
|
||||
auto track = std::make_shared<G711Track>(pt == 0 ? CodecG711U : CodecG711A, 8000, 1, 16);
|
||||
_interface->addTrack(track);
|
||||
_rtp_decoder[pt] = Factory::getRtpDecoderByTrack(track);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
if (pt != 33 && pt != 96) {
|
||||
WarnL << "rtp payload type未识别(" << (int) pt << "),已按ts或ps负载处理";
|
||||
}
|
||||
|
||||
ref = std::make_shared<RtpReceiverImp>(90000,[this](RtpPacket::Ptr rtp) {
|
||||
onRtpSorted(std::move(rtp));
|
||||
});
|
||||
|
||||
//ts或ps负载
|
||||
_rtp_decoder = std::make_shared<CommonRtpDecoder>(CodecInvalid, 32 * 1024);
|
||||
_rtp_decoder[pt] = std::make_shared<CommonRtpDecoder>(CodecInvalid, 32 * 1024);
|
||||
//设置dump目录
|
||||
GET_CONFIG(string, dump_dir, RtpProxy::kDumpDir);
|
||||
if (!dump_dir.empty()) {
|
||||
@ -65,13 +155,12 @@ void GB28181Process::onRtpSorted(RtpPacket::Ptr rtp, int) {
|
||||
}
|
||||
|
||||
//设置frame回调
|
||||
_rtp_decoder->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([this](const Frame::Ptr &frame) {
|
||||
_rtp_decoder[pt]->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([this](const Frame::Ptr &frame) {
|
||||
onRtpDecode(frame);
|
||||
}));
|
||||
}
|
||||
|
||||
//解码rtp
|
||||
_rtp_decoder->inputRtp(rtp, false);
|
||||
return ref->inputRtp(TrackVideo, (unsigned char *) data, data_len);
|
||||
}
|
||||
|
||||
const char *GB28181Process::onSearchPacketTail(const char *packet,size_t bytes){
|
||||
@ -96,8 +185,8 @@ const char *GB28181Process::onSearchPacketTail(const char *packet,size_t bytes){
|
||||
}
|
||||
|
||||
void GB28181Process::onRtpDecode(const Frame::Ptr &frame) {
|
||||
if (frame->getCodecId() == CodecH264) {
|
||||
//这是H264
|
||||
if (frame->getCodecId() != CodecInvalid) {
|
||||
//这里不是ps或ts
|
||||
_interface->inputFrame(frame);
|
||||
return;
|
||||
}
|
||||
|
@ -21,7 +21,8 @@
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
class GB28181Process : public HttpRequestSplitter, public RtpReceiver, public ProcessInterface{
|
||||
class RtpReceiverImp;
|
||||
class GB28181Process : public HttpRequestSplitter, public ProcessInterface{
|
||||
public:
|
||||
typedef std::shared_ptr<GB28181Process> Ptr;
|
||||
GB28181Process(const MediaInfo &media_info, MediaSinkInterface *interface);
|
||||
@ -36,7 +37,7 @@ public:
|
||||
bool inputRtp(bool, const char *data, size_t data_len) override;
|
||||
|
||||
protected:
|
||||
void onRtpSorted(RtpPacket::Ptr rtp, int track_index) override ;
|
||||
void onRtpSorted(RtpPacket::Ptr rtp);
|
||||
const char *onSearchPacketTail(const char *data,size_t len) override;
|
||||
ssize_t onRecvHeader(const char *data,size_t len) override { return 0; };
|
||||
|
||||
@ -48,7 +49,8 @@ private:
|
||||
DecoderImp::Ptr _decoder;
|
||||
MediaSinkInterface *_interface;
|
||||
std::shared_ptr<FILE> _save_file_ps;
|
||||
std::shared_ptr<RtpCodec> _rtp_decoder;
|
||||
unordered_map<uint8_t, std::shared_ptr<RtpCodec> > _rtp_decoder;
|
||||
unordered_map<uint8_t, std::shared_ptr<RtpReceiverImp> > _rtp_receiver;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
@ -13,14 +13,17 @@
|
||||
#include "RtpProcess.h"
|
||||
#include "Http/HttpTSPlayer.h"
|
||||
|
||||
#define RTP_APP_NAME "rtp"
|
||||
static constexpr char kRtpAppName[] = "rtp";
|
||||
//在创建_muxer对象前(也就是推流鉴权成功前),需要先缓存frame,这样可以防止丢包,提高体验
|
||||
//但是同时需要控制缓冲长度,防止内存溢出。200帧数据,大概有10秒数据,应该足矣等待鉴权hook返回
|
||||
static constexpr size_t kMaxCachedFrame = 200;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
RtpProcess::RtpProcess(const string &stream_id) {
|
||||
_media_info._schema = RTP_APP_NAME;
|
||||
_media_info._schema = kRtpAppName;
|
||||
_media_info._vhost = DEFAULT_VHOST;
|
||||
_media_info._app = RTP_APP_NAME;
|
||||
_media_info._app = kRtpAppName;
|
||||
_media_info._streamid = stream_id;
|
||||
|
||||
GET_CONFIG(string, dump_dir, RtpProxy::kDumpDir);
|
||||
@ -78,11 +81,6 @@ bool RtpProcess::inputRtp(bool is_udp, const Socket::Ptr &sock, const char *data
|
||||
emitOnPublish();
|
||||
}
|
||||
|
||||
if (!_muxer) {
|
||||
//无权限推流
|
||||
return false;
|
||||
}
|
||||
|
||||
_total_bytes += len;
|
||||
if (_save_file_rtp) {
|
||||
uint16_t size = (uint16_t)len;
|
||||
@ -95,7 +93,7 @@ bool RtpProcess::inputRtp(bool is_udp, const Socket::Ptr &sock, const char *data
|
||||
}
|
||||
|
||||
GET_CONFIG(string, dump_dir, RtpProxy::kDumpDir);
|
||||
if (!_muxer->isEnabled() && !dts_out && dump_dir.empty()) {
|
||||
if (_muxer && !_muxer->isEnabled() && !dts_out && dump_dir.empty()) {
|
||||
//无人访问、且不取时间戳、不导出调试文件时,我们可以直接丢弃数据
|
||||
_last_frame_time.resetTime();
|
||||
return false;
|
||||
@ -109,20 +107,55 @@ bool RtpProcess::inputRtp(bool is_udp, const Socket::Ptr &sock, const char *data
|
||||
}
|
||||
|
||||
void RtpProcess::inputFrame(const Frame::Ptr &frame) {
|
||||
_last_frame_time.resetTime();
|
||||
_dts = frame->dts();
|
||||
if (_save_file_video && frame->getTrackType() == TrackVideo) {
|
||||
fwrite((uint8_t *) frame->data(), frame->size(), 1, _save_file_video.get());
|
||||
}
|
||||
_muxer->inputFrame(frame);
|
||||
if (_muxer) {
|
||||
_last_frame_time.resetTime();
|
||||
_muxer->inputFrame(frame);
|
||||
} else {
|
||||
if (_cached_func.size() > kMaxCachedFrame) {
|
||||
WarnL << "cached frame of track(" << frame->getCodecName() << ") is too much, now dropped";
|
||||
return;
|
||||
}
|
||||
auto frame_cached = Frame::getCacheAbleFrame(frame);
|
||||
lock_guard<recursive_mutex> lck(_func_mtx);
|
||||
_cached_func.emplace_back([this, frame_cached]() {
|
||||
_last_frame_time.resetTime();
|
||||
_muxer->inputFrame(frame_cached);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void RtpProcess::addTrack(const Track::Ptr &track) {
|
||||
_muxer->addTrack(track);
|
||||
if (_muxer) {
|
||||
_muxer->addTrack(track);
|
||||
} else {
|
||||
lock_guard<recursive_mutex> lck(_func_mtx);
|
||||
_cached_func.emplace_back([this, track]() {
|
||||
_muxer->addTrack(track);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void RtpProcess::addTrackCompleted() {
|
||||
_muxer->addTrackCompleted();
|
||||
if (_muxer) {
|
||||
_muxer->addTrackCompleted();
|
||||
} else {
|
||||
lock_guard<recursive_mutex> lck(_func_mtx);
|
||||
_cached_func.emplace_back([this]() {
|
||||
_muxer->addTrackCompleted();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void RtpProcess::doCachedFunc() {
|
||||
lock_guard<recursive_mutex> lck(_func_mtx);
|
||||
for (auto &func : _cached_func) {
|
||||
func();
|
||||
}
|
||||
_cached_func.clear();
|
||||
}
|
||||
|
||||
bool RtpProcess::alive() {
|
||||
@ -197,19 +230,20 @@ void RtpProcess::setListener(const std::weak_ptr<MediaSourceEvent> &listener) {
|
||||
void RtpProcess::emitOnPublish() {
|
||||
weak_ptr<RtpProcess> weak_self = shared_from_this();
|
||||
Broadcast::PublishAuthInvoker invoker = [weak_self](const string &err, bool enableHls, bool enableMP4) {
|
||||
auto strongSelf = weak_self.lock();
|
||||
if (!strongSelf) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
if (err.empty()) {
|
||||
strongSelf->_muxer = std::make_shared<MultiMediaSourceMuxer>(strongSelf->_media_info._vhost,
|
||||
strongSelf->_media_info._app,
|
||||
strongSelf->_media_info._streamid, 0.0f,
|
||||
strong_self->_muxer = std::make_shared<MultiMediaSourceMuxer>(strong_self->_media_info._vhost,
|
||||
strong_self->_media_info._app,
|
||||
strong_self->_media_info._streamid, 0.0f,
|
||||
true, true, enableHls, enableMP4);
|
||||
strongSelf->_muxer->setMediaListener(strongSelf);
|
||||
InfoP(strongSelf) << "允许RTP推流";
|
||||
strong_self->_muxer->setMediaListener(strong_self);
|
||||
strong_self->doCachedFunc();
|
||||
InfoP(strong_self) << "允许RTP推流";
|
||||
} else {
|
||||
WarnP(strongSelf) << "禁止RTP推流:" << err;
|
||||
WarnP(strong_self) << "禁止RTP推流:" << err;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -79,6 +79,7 @@ protected:
|
||||
|
||||
private:
|
||||
void emitOnPublish();
|
||||
void doCachedFunc();
|
||||
|
||||
private:
|
||||
uint32_t _dts = 0;
|
||||
@ -95,6 +96,8 @@ private:
|
||||
atomic_bool _stop_rtp_check{false};
|
||||
atomic_flag _busy_flag{false};
|
||||
Ticker _last_check_alive;
|
||||
recursive_mutex _func_mtx;
|
||||
deque<function<void()> > _cached_func;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
@ -95,6 +95,7 @@ void RtspPusher::publish(const string &url_str) {
|
||||
}
|
||||
|
||||
void RtspPusher::onPublishResult(const SockException &ex, bool handshake_done) {
|
||||
DebugL << ex.what();
|
||||
if (ex.getErrCode() == Err_shutdown) {
|
||||
//主动shutdown的,不触发回调
|
||||
return;
|
||||
|
@ -390,7 +390,7 @@ string RtpExt::getSdesMid() const {
|
||||
|
||||
|
||||
//https://tools.ietf.org/html/draft-ietf-avtext-rid-06
|
||||
//用于simulecast
|
||||
//用于simulcast
|
||||
//3.1. RTCP 'RtpStreamId' SDES Extension
|
||||
//
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
#include "Sdp.h"
|
||||
#include "Rtsp/Rtsp.h"
|
||||
#include <inttypes.h>
|
||||
#include <cinttypes>
|
||||
using namespace mediakit;
|
||||
|
||||
using onCreateSdpItem = function<SdpItem::Ptr(const string &key, const string &value)>;
|
||||
@ -704,12 +704,13 @@ string SdpAttrSctpMap::toString() const {
|
||||
}
|
||||
|
||||
void SdpAttrCandidate::parse(const string &str) {
|
||||
char foundation_buf[32] = {0};
|
||||
char foundation_buf[40] = {0};
|
||||
char transport_buf[16] = {0};
|
||||
char address_buf[32] = {0};
|
||||
char type_buf[16] = {0};
|
||||
|
||||
if (7 != sscanf(str.data(), "%31[^ ] %" SCNu32 " %15[^ ] %" SCNu32 " %31[^ ] %" SCNu16 " typ %15[^ ]",
|
||||
// https://datatracker.ietf.org/doc/html/rfc5245#section-15.1
|
||||
if (7 != sscanf(str.data(), "%32[^ ] %" SCNu32 " %15[^ ] %" SCNu32 " %31[^ ] %" SCNu16 " typ %15[^ ]",
|
||||
foundation_buf, &component, transport_buf, &priority, address_buf, &port, type_buf)) {
|
||||
SDP_THROW();
|
||||
}
|
||||
|
@ -408,14 +408,7 @@ void WebRtcTransportImp::onStartWebRTC() {
|
||||
info->offer_ssrc_rtx = m_offer->getRtxSSRC();
|
||||
info->plan_rtp = &m_answer.plan[0];;
|
||||
info->plan_rtx = m_answer.getRelatedRtxPlan(info->plan_rtp->pt);
|
||||
info->rtcp_context_recv = std::make_shared<RtcpContext>(info->plan_rtp->sample_rate, true);
|
||||
info->rtcp_context_send = std::make_shared<RtcpContext>(info->plan_rtp->sample_rate, false);
|
||||
info->receiver = std::make_shared<RtpReceiverImp>([info, this](RtpPacket::Ptr rtp) mutable {
|
||||
onSortedRtp(*info, std::move(rtp));
|
||||
});
|
||||
info->nack_ctx.setOnNack([info, this](const FCI_NACK &nack) mutable {
|
||||
onSendNack(*info, nack);
|
||||
});
|
||||
|
||||
//send ssrc --> RtpPayloadInfo
|
||||
_rtp_info_ssrc[info->answer_ssrc_rtp] = std::make_pair(false, info);
|
||||
@ -599,9 +592,14 @@ void WebRtcTransportImp::onRtcp(const char *buf, size_t len) {
|
||||
auto rtx = it->second.first;
|
||||
if (!rtx) {
|
||||
auto &info = it->second.second;
|
||||
info->rtcp_context_recv->onRtcp(sr);
|
||||
auto rr = info->rtcp_context_recv->createRtcpRR(info->answer_ssrc_rtp, info->offer_ssrc_rtp);
|
||||
sendRtcpPacket(rr->data(), rr->size(), true);
|
||||
auto it = info->rtcp_context_recv.find(sr->ssrc);
|
||||
if (it != info->rtcp_context_recv.end()) {
|
||||
it->second->onRtcp(sr);
|
||||
auto rr = it->second->createRtcpRR(info->answer_ssrc_rtp, sr->ssrc);
|
||||
sendRtcpPacket(rr->data(), rr->size(), true);
|
||||
} else {
|
||||
WarnL << "未识别的sr rtcp包:" << rtcp->dumpString();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
WarnL << "未识别的sr rtcp包:" << rtcp->dumpString();
|
||||
@ -677,7 +675,7 @@ void WebRtcTransportImp::onRtcp(const char *buf, size_t len) {
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
void WebRtcTransportImp::changeRtpExtId(const RtpPayloadInfo *info, const RtpHeader *header, bool is_recv, bool is_rtx) const{
|
||||
void WebRtcTransportImp::changeRtpExtId(RtpPayloadInfo &info, const RtpHeader *header, bool is_recv, bool is_rtx, string *rid_ptr) const{
|
||||
auto ext_map = RtpExt::getExtValue(header);
|
||||
for (auto &pr : ext_map) {
|
||||
if (is_recv) {
|
||||
@ -690,6 +688,35 @@ void WebRtcTransportImp::changeRtpExtId(const RtpPayloadInfo *info, const RtpHea
|
||||
pr.second.setType(it->second);
|
||||
//重新赋值ext id为 ext type,作为后面处理ext的统一中间类型
|
||||
pr.second.setExtId((uint8_t) it->second);
|
||||
switch(it->second){
|
||||
case RtpExtType::sdes_repaired_rtp_stream_id :
|
||||
case RtpExtType::sdes_rtp_stream_id : {
|
||||
auto ssrc = ntohl(header->ssrc);
|
||||
auto rid = it->second == RtpExtType::sdes_rtp_stream_id ? pr.second.getRtpStreamId() : pr.second.getRepairedRtpStreamId();
|
||||
//根据rid获取rtp或rtx的ssrc
|
||||
auto &ssrc_ref = is_rtx ? info.rid_ssrc[rid].second : info.rid_ssrc[rid].first;
|
||||
if (!ssrc_ref) {
|
||||
//ssrc未赋值,赋值
|
||||
ssrc_ref = ssrc;
|
||||
DebugL << (is_rtx ? "got rid of rtx:" : "got rid:") << rid << ", ssrc:" << ssrc;
|
||||
}
|
||||
if (is_rtx) {
|
||||
//rtx ssrc --> rtp ssrc
|
||||
auto &rtp_ssrc_ref = info.rtx_ssrc_to_rtp_ssrc[ssrc];
|
||||
if (!rtp_ssrc_ref && info.rid_ssrc[rid].first) {
|
||||
//未找到rtx到rtp ssrc的映射关系,且已经获取rtp的ssrc,那么设置映射关系
|
||||
rtp_ssrc_ref = info.rid_ssrc[rid].first;
|
||||
DebugL << "got ssrc of rid:" << rid << ", [rtx-rtp]:" << ssrc << "-" << rtp_ssrc_ref;
|
||||
}
|
||||
}
|
||||
if (rid_ptr) {
|
||||
*rid_ptr = rid;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default : break;
|
||||
}
|
||||
} else {
|
||||
pr.second.setType((RtpExtType) pr.first);
|
||||
auto it = _rtp_ext_type_to_id.find((RtpExtType) pr.first);
|
||||
@ -715,6 +742,8 @@ void WebRtcTransportImp::onRtp_l(const char *buf, size_t len, bool rtx) {
|
||||
}
|
||||
|
||||
RtpHeader *rtp = (RtpHeader *) buf;
|
||||
auto ssrc = ntohl(rtp->ssrc);
|
||||
|
||||
//根据接收到的rtp的pt信息,找到该流的信息
|
||||
auto it = _rtp_info_pt.find(rtp->pt);
|
||||
if (it == _rtp_info_pt.end()) {
|
||||
@ -732,18 +761,39 @@ void WebRtcTransportImp::onRtp_l(const char *buf, size_t len, bool rtx) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
auto &ref = info->receiver[ssrc];
|
||||
if (!rtx) {
|
||||
//统计rtp接受情况,便于生成nack rtcp包
|
||||
info->nack_ctx.received(seq);
|
||||
info->nack_ctx[ssrc].received(seq);
|
||||
//时间戳转换成毫秒
|
||||
auto stamp_ms = ntohl(rtp->stamp) * uint64_t(1000) / info->plan_rtp->sample_rate;
|
||||
|
||||
//统计rtp收到的情况,好做rr汇报
|
||||
info->rtcp_context_recv->onRtp(seq, stamp_ms, len);
|
||||
auto &cxt_ref = info->rtcp_context_recv[ssrc];
|
||||
if (!cxt_ref) {
|
||||
cxt_ref = std::make_shared<RtcpContext>(info->plan_rtp->sample_rate, true);
|
||||
}
|
||||
cxt_ref->onRtp(seq, stamp_ms, len);
|
||||
|
||||
//修改ext id至统一
|
||||
string rid;
|
||||
changeRtpExtId(*info, rtp, true, false, &rid);
|
||||
|
||||
if (!ref) {
|
||||
ref = std::make_shared<RtpReceiverImp>([info, this, rid](RtpPacket::Ptr rtp) mutable {
|
||||
onSortedRtp(*info, rid, std::move(rtp));
|
||||
});
|
||||
info->nack_ctx[ssrc].setOnNack([info, this, ssrc](const FCI_NACK &nack) mutable {
|
||||
onSendNack(*info, nack, ssrc);
|
||||
});
|
||||
//recv simulcast ssrc --> RtpPayloadInfo
|
||||
_rtp_info_ssrc[ssrc] = std::make_pair(false, info);
|
||||
InfoL << "receive rtp of ssrc:" << ssrc;
|
||||
}
|
||||
}
|
||||
//修改ext id至统一
|
||||
changeRtpExtId(info.get(), rtp, true, rtx);
|
||||
//解析并排序rtp
|
||||
info->receiver->inputRtp(info->media->type, info->plan_rtp->sample_rate, (uint8_t *) buf, len);
|
||||
assert(ref);
|
||||
ref->inputRtp(info->media->type, info->plan_rtp->sample_rate, (uint8_t *) buf, len);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -754,11 +804,22 @@ void WebRtcTransportImp::onRtp_l(const char *buf, size_t len, bool rtx) {
|
||||
if (size < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
//修改ext id至统一
|
||||
changeRtpExtId(*info, rtp, true, true);
|
||||
|
||||
//前两个字节是原始的rtp的seq
|
||||
auto origin_seq = payload[0] << 8 | payload[1];
|
||||
InfoL << "received rtx rtp: " << origin_seq;
|
||||
rtp->seq = htons(origin_seq);
|
||||
rtp->ssrc = htonl(info->offer_ssrc_rtp);
|
||||
if (info->offer_ssrc_rtp) {
|
||||
//非simulcast或音频
|
||||
rtp->ssrc = htonl(info->offer_ssrc_rtp);
|
||||
TraceL << "received rtx rtp,ssrc: " << ssrc << ", seq:" << origin_seq << ", pt:" << (int)rtp->pt;
|
||||
} else {
|
||||
//todo simulcast下,辅码流通过rtx传输?
|
||||
//simulcast情况下,根据rtx的ssrc查找rtp的ssrc
|
||||
rtp->ssrc = htonl(info->rtx_ssrc_to_rtp_ssrc[ntohl(rtp->ssrc)]);
|
||||
}
|
||||
rtp->pt = info->plan_rtp->pt;
|
||||
memmove((uint8_t *) buf + 2, buf, payload - (uint8_t *) buf);
|
||||
buf += 2;
|
||||
@ -766,16 +827,17 @@ void WebRtcTransportImp::onRtp_l(const char *buf, size_t len, bool rtx) {
|
||||
onRtp_l(buf, len, true);
|
||||
}
|
||||
|
||||
void WebRtcTransportImp::onSendNack(RtpPayloadInfo &info, const FCI_NACK &nack) {
|
||||
void WebRtcTransportImp::onSendNack(RtpPayloadInfo &info, const FCI_NACK &nack, uint32_t ssrc) {
|
||||
auto rtcp = RtcpFB::create(RTPFBType::RTCP_RTPFB_NACK, &nack, FCI_NACK::kSize);
|
||||
rtcp->ssrc = htons(info.answer_ssrc_rtp);
|
||||
rtcp->ssrc_media = htonl(info.offer_ssrc_rtp);
|
||||
rtcp->ssrc_media = htonl(ssrc);
|
||||
DebugL << htonl(ssrc) << " " << nack.getPid();
|
||||
sendRtcpPacket((char *) rtcp.get(), rtcp->getSize(), true);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
void WebRtcTransportImp::onSortedRtp(RtpPayloadInfo &info, RtpPacket::Ptr rtp) {
|
||||
void WebRtcTransportImp::onSortedRtp(RtpPayloadInfo &info, const string &rid, RtpPacket::Ptr rtp) {
|
||||
if (info.media->type == TrackVideo && _pli_ticker.elapsedTime() > 2000) {
|
||||
//定期发送pli请求关键帧,方便非rtc等协议
|
||||
_pli_ticker.resetTime();
|
||||
@ -789,7 +851,24 @@ void WebRtcTransportImp::onSortedRtp(RtpPayloadInfo &info, RtpPacket::Ptr rtp) {
|
||||
}
|
||||
|
||||
if (_push_src) {
|
||||
_push_src->onWrite(std::move(rtp), false);
|
||||
if (rtp->type == TrackAudio) {
|
||||
//音频
|
||||
for (auto &pr : _push_src_simulcast) {
|
||||
pr.second->onWrite(rtp, false);
|
||||
}
|
||||
} else {
|
||||
//视频
|
||||
auto &src = _push_src_simulcast[rid];
|
||||
if (!src) {
|
||||
auto stream_id = rid.empty() ? _push_src->getId() : _push_src->getId() + "_" + rid;
|
||||
auto src_imp = std::make_shared<RtspMediaSourceImp>(_push_src->getVhost(), _push_src->getApp(), stream_id);
|
||||
src_imp->setSdp(_push_src->getSdp());
|
||||
src_imp->setProtocolTranslation(_push_src->isRecording(Recorder::type_hls),_push_src->isRecording(Recorder::type_mp4));
|
||||
src_imp->setListener(shared_from_this());
|
||||
src = src_imp;
|
||||
}
|
||||
src->onWrite(std::move(rtp), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -826,12 +905,12 @@ void WebRtcTransportImp::onBeforeEncryptRtp(const char *buf, size_t &len, void *
|
||||
|
||||
if (!pr->first || !pr->second->plan_rtx) {
|
||||
//普通的rtp,或者不支持rtx, 修改目标pt和ssrc
|
||||
changeRtpExtId(pr->second, header, false, false);
|
||||
changeRtpExtId(*pr->second, header, false, false);
|
||||
header->pt = pr->second->plan_rtp->pt;
|
||||
header->ssrc = htonl(pr->second->answer_ssrc_rtp);
|
||||
} else {
|
||||
//重传的rtp, rtx
|
||||
changeRtpExtId(pr->second, header, false, true);
|
||||
changeRtpExtId(*pr->second, header, false, true);
|
||||
header->pt = pr->second->plan_rtx->pt;
|
||||
if (pr->second->answer_ssrc_rtx) {
|
||||
//有rtx单独的ssrc,有些情况下,浏览器支持rtx,但是未指定rtx单独的ssrc
|
||||
|
@ -350,15 +350,17 @@ private:
|
||||
uint32_t answer_ssrc_rtx = 0;
|
||||
const RtcMedia *media;
|
||||
NackList nack_list;
|
||||
NackContext nack_ctx;
|
||||
RtcpContext::Ptr rtcp_context_recv;
|
||||
RtcpContext::Ptr rtcp_context_send;
|
||||
std::shared_ptr<RtpReceiverImp> receiver;
|
||||
unordered_map<string/*rid*/, std::pair<uint32_t/*rtp ssrc*/, uint32_t/*rtx ssrc*/> > rid_ssrc;
|
||||
unordered_map<uint32_t/*rtx ssrc*/, uint32_t/*rtp ssrc*/> rtx_ssrc_to_rtp_ssrc;
|
||||
unordered_map<uint32_t/*simulcast ssrc*/, NackContext> nack_ctx;
|
||||
unordered_map<uint32_t/*simulcast ssrc*/, RtcpContext::Ptr> rtcp_context_recv;
|
||||
unordered_map<uint32_t/*simulcast ssrc*/, std::shared_ptr<RtpReceiverImp> > receiver;
|
||||
};
|
||||
|
||||
void onSortedRtp(RtpPayloadInfo &info, RtpPacket::Ptr rtp);
|
||||
void onSendNack(RtpPayloadInfo &info, const FCI_NACK &nack);
|
||||
void changeRtpExtId(const RtpPayloadInfo *info, const RtpHeader *header, bool is_recv, bool is_rtx = false) const;
|
||||
void onSortedRtp(RtpPayloadInfo &info, const string &rid, RtpPacket::Ptr rtp);
|
||||
void onSendNack(RtpPayloadInfo &info, const FCI_NACK &nack, uint32_t ssrc);
|
||||
void changeRtpExtId(RtpPayloadInfo &info, const RtpHeader *header, bool is_recv, bool is_rtx = false, string *rid_ptr = nullptr) const;
|
||||
|
||||
private:
|
||||
uint16_t _rtx_seq[2] = {0, 0};
|
||||
@ -378,6 +380,7 @@ private:
|
||||
Socket::Ptr _socket;
|
||||
//推流的rtsp源
|
||||
RtspMediaSource::Ptr _push_src;
|
||||
unordered_map<string/*rid*/, RtspMediaSource::Ptr> _push_src_simulcast;
|
||||
//播放的rtsp源
|
||||
RtspMediaSource::Ptr _play_src;
|
||||
//播放rtsp源的reader对象
|
||||
|
@ -1,6 +1,5 @@
|
||||
# chrome的sdp
|
||||
v=0
|
||||
o=- 403371946498103831 2 IN IP4 127.0.0.1
|
||||
o=- 1520777637155103417 2 IN IP4 127.0.0.1
|
||||
s=-
|
||||
t=0 0
|
||||
a=group:BUNDLE 0 1
|
||||
@ -9,10 +8,10 @@ a=msid-semantic: WMS
|
||||
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
|
||||
c=IN IP4 0.0.0.0
|
||||
a=rtcp:9 IN IP4 0.0.0.0
|
||||
a=ice-ufrag:pW4Z
|
||||
a=ice-pwd:S38S++HW3eTcPTyytsNI1XVp
|
||||
a=ice-ufrag:ma0v
|
||||
a=ice-pwd:H8+DMsvKNQE+Qz1uS7cZby1+
|
||||
a=ice-options:trickle
|
||||
a=fingerprint:sha-256 04:32:7B:56:7D:F7:D4:EC:65:7C:04:6C:F8:0B:03:F0:35:A9:1A:C3:43:3E:18:95:67:E6:0D:D1:EE:C9:16:8C
|
||||
a=fingerprint:sha-256 F1:30:4D:FE:6B:9A:BE:B4:31:65:30:C1:67:87:2F:74:23:7A:06:31:B0:49:DE:44:53:69:27:30:86:1F:E0:6C
|
||||
a=setup:actpass
|
||||
a=mid:0
|
||||
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
|
||||
@ -22,7 +21,7 @@ a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
|
||||
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
|
||||
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
|
||||
a=sendrecv
|
||||
a=msid:- a3c6a137-1291-45cd-b985-07a9bd365452
|
||||
a=msid:- 7bf9dab3-79e9-4969-b3d2-44beae8b4286
|
||||
a=rtcp-mux
|
||||
a=rtpmap:111 opus/48000/2
|
||||
a=rtcp-fb:111 transport-cc
|
||||
@ -39,17 +38,17 @@ a=rtpmap:110 telephone-event/48000
|
||||
a=rtpmap:112 telephone-event/32000
|
||||
a=rtpmap:113 telephone-event/16000
|
||||
a=rtpmap:126 telephone-event/8000
|
||||
a=ssrc:3626257331 cname:JSFJMbaE9Pu5tevN
|
||||
a=ssrc:3626257331 msid:- a3c6a137-1291-45cd-b985-07a9bd365452
|
||||
a=ssrc:3626257331 mslabel:-
|
||||
a=ssrc:3626257331 label:a3c6a137-1291-45cd-b985-07a9bd365452
|
||||
a=ssrc:3454457472 cname:kbZgD5tgXGqTwvD1
|
||||
a=ssrc:3454457472 msid:- 7bf9dab3-79e9-4969-b3d2-44beae8b4286
|
||||
a=ssrc:3454457472 mslabel:-
|
||||
a=ssrc:3454457472 label:7bf9dab3-79e9-4969-b3d2-44beae8b4286
|
||||
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 35 36 124 119 123 118 114 115 116
|
||||
c=IN IP4 0.0.0.0
|
||||
a=rtcp:9 IN IP4 0.0.0.0
|
||||
a=ice-ufrag:pW4Z
|
||||
a=ice-pwd:S38S++HW3eTcPTyytsNI1XVp
|
||||
a=ice-ufrag:ma0v
|
||||
a=ice-pwd:H8+DMsvKNQE+Qz1uS7cZby1+
|
||||
a=ice-options:trickle
|
||||
a=fingerprint:sha-256 04:32:7B:56:7D:F7:D4:EC:65:7C:04:6C:F8:0B:03:F0:35:A9:1A:C3:43:3E:18:95:67:E6:0D:D1:EE:C9:16:8C
|
||||
a=fingerprint:sha-256 F1:30:4D:FE:6B:9A:BE:B4:31:65:30:C1:67:87:2F:74:23:7A:06:31:B0:49:DE:44:53:69:27:30:86:1F:E0:6C
|
||||
a=setup:actpass
|
||||
a=mid:1
|
||||
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
|
||||
@ -64,7 +63,7 @@ a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
|
||||
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
|
||||
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
|
||||
a=sendrecv
|
||||
a=msid:- 261e5384-9cf6-479d-9d59-aaf924d1a2ea
|
||||
a=msid:- fa2b9d11-98f5-4ea9-bf0a-6098069c6940
|
||||
a=rtcp-mux
|
||||
a=rtcp-rsize
|
||||
a=rtpmap:96 VP8/90000
|
||||
@ -143,7 +142,7 @@ a=rtcp-fb:124 transport-cc
|
||||
a=rtcp-fb:124 ccm fir
|
||||
a=rtcp-fb:124 nack
|
||||
a=rtcp-fb:124 nack pli
|
||||
a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f
|
||||
a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d0032
|
||||
a=rtpmap:119 rtx/90000
|
||||
a=fmtp:119 apt=124
|
||||
a=rtpmap:123 H264/90000
|
||||
@ -152,7 +151,7 @@ a=rtcp-fb:123 transport-cc
|
||||
a=rtcp-fb:123 ccm fir
|
||||
a=rtcp-fb:123 nack
|
||||
a=rtcp-fb:123 nack pli
|
||||
a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f
|
||||
a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032
|
||||
a=rtpmap:118 rtx/90000
|
||||
a=fmtp:118 apt=123
|
||||
a=rtpmap:114 red/90000
|
||||
@ -162,94 +161,4 @@ a=rtpmap:116 ulpfec/90000
|
||||
a=rid:q send
|
||||
a=rid:h send
|
||||
a=rid:f send
|
||||
a=simulcast:send q;h;f
|
||||
|
||||
#firefox的sdp
|
||||
v=0
|
||||
o=mozilla...THIS_IS_SDPARTA-88.0.1 3954544078885279475 0 IN IP4 0.0.0.0
|
||||
s=-
|
||||
t=0 0
|
||||
a=fingerprint:sha-256 9B:4F:D1:D2:A5:ED:08:BC:E8:D7:DD:D8:59:2C:E6:3D:19:F9:4C:67:9C:D9:9B:7B:C9:47:7A:3A:1F:05:C8:96
|
||||
a=group:BUNDLE 0 1
|
||||
a=ice-options:trickle
|
||||
a=msid-semantic:WMS *
|
||||
m=audio 9 UDP/TLS/RTP/SAVPF 109 9 0 8 101
|
||||
c=IN IP4 0.0.0.0
|
||||
a=sendrecv
|
||||
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
|
||||
a=extmap:2/recvonly urn:ietf:params:rtp-hdrext:csrc-audio-level
|
||||
a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid
|
||||
a=fmtp:109 maxplaybackrate=48000;stereo=1;useinbandfec=1
|
||||
a=fmtp:101 0-15
|
||||
a=ice-pwd:92a9ced6d734f7ff2a45cde8b29572a9
|
||||
a=ice-ufrag:b986b945
|
||||
a=mid:0
|
||||
a=msid:- {ea61729a-c244-4c79-aeb7-b57765fefa26}
|
||||
a=rtcp-mux
|
||||
a=rtpmap:109 opus/48000/2
|
||||
a=rtpmap:9 G722/8000/1
|
||||
a=rtpmap:0 PCMU/8000
|
||||
a=rtpmap:8 PCMA/8000
|
||||
a=rtpmap:101 telephone-event/8000/1
|
||||
a=setup:actpass
|
||||
a=ssrc:3000327501 cname:{12e7c547-559e-46e2-94db-f1ad474c95dc}
|
||||
m=video 9 UDP/TLS/RTP/SAVPF 120 124 121 125 126 127 97 98
|
||||
c=IN IP4 0.0.0.0
|
||||
a=sendrecv
|
||||
a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid
|
||||
a=extmap:4 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
|
||||
a=extmap:5 urn:ietf:params:rtp-hdrext:toffset
|
||||
a=extmap:6/recvonly http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
|
||||
a=extmap:7 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
|
||||
a=extmap:8/sendonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
|
||||
a=extmap:9/sendonly urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
|
||||
a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1
|
||||
a=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1
|
||||
a=fmtp:120 max-fs=12288;max-fr=60
|
||||
a=fmtp:124 apt=120
|
||||
a=fmtp:121 max-fs=12288;max-fr=60
|
||||
a=fmtp:125 apt=121
|
||||
a=fmtp:127 apt=126
|
||||
a=fmtp:98 apt=97
|
||||
a=ice-pwd:92a9ced6d734f7ff2a45cde8b29572a9
|
||||
a=ice-ufrag:b986b945
|
||||
a=mid:1
|
||||
a=msid:- {3bfe1b80-20eb-4b42-b8b7-fac45fb281bf}
|
||||
a=rid:q send
|
||||
a=rid:h send
|
||||
a=rid:f send
|
||||
a=rtcp-fb:120 nack
|
||||
a=rtcp-fb:120 nack pli
|
||||
a=rtcp-fb:120 ccm fir
|
||||
a=rtcp-fb:120 goog-remb
|
||||
a=rtcp-fb:120 transport-cc
|
||||
a=rtcp-fb:121 nack
|
||||
a=rtcp-fb:121 nack pli
|
||||
a=rtcp-fb:121 ccm fir
|
||||
a=rtcp-fb:121 goog-remb
|
||||
a=rtcp-fb:121 transport-cc
|
||||
a=rtcp-fb:126 nack
|
||||
a=rtcp-fb:126 nack pli
|
||||
a=rtcp-fb:126 ccm fir
|
||||
a=rtcp-fb:126 goog-remb
|
||||
a=rtcp-fb:126 transport-cc
|
||||
a=rtcp-fb:97 nack
|
||||
a=rtcp-fb:97 nack pli
|
||||
a=rtcp-fb:97 ccm fir
|
||||
a=rtcp-fb:97 goog-remb
|
||||
a=rtcp-fb:97 transport-cc
|
||||
a=rtcp-mux
|
||||
a=rtcp-rsize
|
||||
a=rtpmap:120 VP8/90000
|
||||
a=rtpmap:124 rtx/90000
|
||||
a=rtpmap:121 VP9/90000
|
||||
a=rtpmap:125 rtx/90000
|
||||
a=rtpmap:126 H264/90000
|
||||
a=rtpmap:127 rtx/90000
|
||||
a=rtpmap:97 H264/90000
|
||||
a=rtpmap:98 rtx/90000
|
||||
a=setup:actpass
|
||||
a=simulcast:send q;h;f
|
||||
a=ssrc:2581133096 cname:{12e7c547-559e-46e2-94db-f1ad474c95dc}
|
||||
a=ssrc:773854125 cname:{12e7c547-559e-46e2-94db-f1ad474c95dc}
|
||||
a=ssrc:4100728001 cname:{12e7c547-559e-46e2-94db-f1ad474c95dc}
|
||||
a=simulcast:send q;h;f
|
@ -31,7 +31,7 @@ ZLMediaKit的WebRTC相关功能目前仅供测试与开发,现在还不成熟
|
||||
|
||||
- 1、完善webrtc rtcp相关功能,包括丢包重传、带宽检测等功能。
|
||||
- 2、实现rtp重传等相关功能。
|
||||
- 3、实现simulecast相关功能。
|
||||
- 3、实现simulcast相关功能。
|
||||
- 4、fec、rtp扩展等其他功能。
|
||||
- 5、如果精力允许,逐步替换MediaSoup相关代码,改用自有版权代码。
|
||||
|
||||
|
@ -4,7 +4,7 @@ var ZLMRTCClient = (function (exports) {
|
||||
const Events$1 = {
|
||||
WEBRTC_NOT_SUPPORT: 'WEBRTC_NOT_SUPPORT',
|
||||
WEBRTC_ICE_CANDIDATE_ERROR: 'WEBRTC_ICE_CANDIDATE_ERROR',
|
||||
WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED: 'WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED',
|
||||
WEBRTC_OFFER_ANSWER_EXCHANGE_FAILED: 'WEBRTC_OFFER_ANSWER_EXCHANGE_FAILED',
|
||||
WEBRTC_ON_REMOTE_STREAMS: 'WEBRTC_ON_REMOTE_STREAMS',
|
||||
WEBRTC_ON_LOCAL_STREAM: 'WEBRTC_ON_LOCAL_STREAM',
|
||||
CAPTURE_STREAM_FAILED: 'CAPTURE_STREAM_FAILED'
|
||||
@ -7285,7 +7285,7 @@ var ZLMRTCClient = (function (exports) {
|
||||
debug: false,
|
||||
// if output debug log
|
||||
zlmsdpUrl: '',
|
||||
simulecast: false,
|
||||
simulcast: false,
|
||||
useCamera: true,
|
||||
audioEnable: true,
|
||||
videoEnable: true,
|
||||
@ -7342,16 +7342,16 @@ var ZLMRTCClient = (function (exports) {
|
||||
let ret = response.data; //JSON.parse(response.data);
|
||||
|
||||
if (ret.code != 0) {
|
||||
// mean failed for offer/anwser exchange
|
||||
this.dispatch(Events$1.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, ret);
|
||||
// mean failed for offer/answer exchange
|
||||
this.dispatch(Events$1.WEBRTC_OFFER_ANSWER_EXCHANGE_FAILED, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
let anwser = {};
|
||||
anwser.sdp = ret.sdp;
|
||||
anwser.type = 'answer';
|
||||
let answer = {};
|
||||
answer.sdp = ret.sdp;
|
||||
answer.type = 'answer';
|
||||
log(this.TAG, 'answer:', ret.sdp);
|
||||
this.pc.setRemoteDescription(anwser).then(() => {
|
||||
this.pc.setRemoteDescription(answer).then(() => {
|
||||
log(this.TAG, 'set remote sucess');
|
||||
}).catch(e => {
|
||||
error(this.TAG, e);
|
||||
@ -7398,7 +7398,7 @@ var ZLMRTCClient = (function (exports) {
|
||||
sendEncodings: []
|
||||
};
|
||||
|
||||
if (this.options.simulecast && stream.getVideoTracks().length > 0) {
|
||||
if (this.options.simulcast && stream.getVideoTracks().length > 0) {
|
||||
VideoTransceiverInit.sendEncodings = [{
|
||||
rid: 'q',
|
||||
active: true,
|
||||
@ -7449,16 +7449,16 @@ var ZLMRTCClient = (function (exports) {
|
||||
let ret = response.data; //JSON.parse(response.data);
|
||||
|
||||
if (ret.code != 0) {
|
||||
// mean failed for offer/anwser exchange
|
||||
this.dispatch(Events$1.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, ret);
|
||||
// mean failed for offer/answer exchange
|
||||
this.dispatch(Events$1.WEBRTC_OFFER_ANSWER_EXCHANGE_FAILED, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
let anwser = {};
|
||||
anwser.sdp = ret.sdp;
|
||||
anwser.type = 'answer';
|
||||
let answer = {};
|
||||
answer.sdp = ret.sdp;
|
||||
answer.type = 'answer';
|
||||
log(this.TAG, 'answer:', ret.sdp);
|
||||
this.pc.setRemoteDescription(anwser).then(() => {
|
||||
this.pc.setRemoteDescription(answer).then(() => {
|
||||
log(this.TAG, 'set remote sucess');
|
||||
}).catch(e => {
|
||||
error(this.TAG, e);
|
||||
|
File diff suppressed because one or more lines are too long
@ -26,8 +26,8 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="simulecast">simulecast:</label>
|
||||
<input type="checkbox" id='simulecast' checked="checked">
|
||||
<label for="simulcast">simulcast:</label>
|
||||
<input type="checkbox" id='simulcast' checked="checked">
|
||||
</p>
|
||||
<p>
|
||||
<label for="useCamera">useCamera:</label>
|
||||
@ -46,14 +46,14 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="methond">methond(play or push):</label>
|
||||
<input type="radio" name="methond" value="push" >push
|
||||
<input type="radio" name="methond" value="play" checked = true>play
|
||||
<label for="method">method(play or push):</label>
|
||||
<input type="radio" name="method" value="push" >push
|
||||
<input type="radio" name="method" value="play" checked = true>play
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="resilution">resolution:</label>
|
||||
<select id="resilution">
|
||||
<label for="resolution">resolution:</label>
|
||||
<select id="resolution">
|
||||
|
||||
</select>
|
||||
</p>
|
||||
@ -69,7 +69,7 @@
|
||||
var recvOnly = true
|
||||
var resArr = []
|
||||
|
||||
document.getElementsByName("methond").forEach((el,idx)=>{
|
||||
document.getElementsByName("method").forEach((el,idx)=>{
|
||||
el.onclick=function(e){
|
||||
if(el.value == "play")
|
||||
{
|
||||
@ -91,14 +91,14 @@
|
||||
opt = document.createElement('option');
|
||||
opt.text = r.label +"("+r.width+"x"+r.height+")";
|
||||
opt.value = r;
|
||||
document.getElementById("resilution").add(opt,null)
|
||||
document.getElementById("resolution").add(opt,null)
|
||||
|
||||
//console.log(opt.text.match(/\d+/g))
|
||||
|
||||
|
||||
})
|
||||
function start_play(){
|
||||
let elr = document.getElementById("resilution");
|
||||
let elr = document.getElementById("resolution");
|
||||
let res = elr.options[elr.selectedIndex].text.match(/\d+/g);
|
||||
let h = parseInt(res.pop());
|
||||
let w = parseInt(res.pop());
|
||||
@ -108,7 +108,7 @@
|
||||
element: document.getElementById('video'),// video 标签
|
||||
debug: true,// 是否打印日志
|
||||
zlmsdpUrl:document.getElementById('streamUrl').value,//流地址
|
||||
simulecast:false,//document.getElementById('simulecast').checked,
|
||||
simulcast:document.getElementById('simulcast').checked,
|
||||
useCamera:document.getElementById('useCamera').checked,
|
||||
audioEnable:document.getElementById('audioEnable').checked,
|
||||
videoEnable:document.getElementById('videoEnable').checked,
|
||||
@ -127,9 +127,9 @@
|
||||
console.log('播放成功',e.streams)
|
||||
});
|
||||
|
||||
player.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED,function(e)
|
||||
{// offer anwser 交换失败
|
||||
console.log('offer anwser 交换失败',e)
|
||||
player.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANSWER_EXCHANGE_FAILED,function(e)
|
||||
{// offer answer 交换失败
|
||||
console.log('offer answer 交换失败',e)
|
||||
stop();
|
||||
});
|
||||
|
||||
@ -139,7 +139,7 @@
|
||||
document.getElementById('selfVideo').srcObject=s;
|
||||
document.getElementById('selfVideo').muted = true;
|
||||
|
||||
//console.log('offer anwser 交换失败',e)
|
||||
//console.log('offer answer 交换失败',e)
|
||||
});
|
||||
|
||||
player.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED,function(s)
|
||||
@ -152,7 +152,7 @@
|
||||
function start()
|
||||
{
|
||||
stop();
|
||||
let elr = document.getElementById("resilution");
|
||||
let elr = document.getElementById("resolution");
|
||||
let res = elr.options[elr.selectedIndex].text.match(/\d+/g);
|
||||
let h = parseInt(res.pop());
|
||||
let w = parseInt(res.pop());
|
||||
|
Loading…
Reference in New Issue
Block a user