mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-29 14:45:55 +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
|
segNum=3
|
||||||
#HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数
|
#HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数
|
||||||
segRetain=5
|
segRetain=5
|
||||||
# 是否广播 ts 切片完成通知
|
#是否广播 ts 切片完成通知
|
||||||
broadcastRecordTs=0
|
broadcastRecordTs=0
|
||||||
|
#直播hls文件删除延时,单位秒,issue: #913
|
||||||
|
deleteDelaySec=0
|
||||||
|
|
||||||
[hook]
|
[hook]
|
||||||
#在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然
|
#在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
%global use_devtoolset 0
|
%global use_devtoolset 0
|
||||||
%bcond_without faac
|
%bcond_without faac
|
||||||
%bcond_without x264
|
%bcond_without x264
|
||||||
|
%bcond_without webrtc
|
||||||
%else
|
%else
|
||||||
%global use_devtoolset 1
|
%global use_devtoolset 1
|
||||||
%bcond_with faac
|
%bcond_with faac
|
||||||
%bcond_with x264
|
%bcond_with x264
|
||||||
|
%bcond_with webrtc
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
%bcond_without openssl
|
%bcond_without openssl
|
||||||
@ -22,8 +24,12 @@ URL: https://github.com/xia-chu/ZLMediaKit
|
|||||||
Source0: %{name}-%{version}.tar.xz
|
Source0: %{name}-%{version}.tar.xz
|
||||||
|
|
||||||
%if %{with openssl}
|
%if %{with openssl}
|
||||||
|
%if 0%{?rhel} <= 7 && %{with webrtc}
|
||||||
|
BuildRequires: openssl11-devel
|
||||||
|
%else
|
||||||
BuildRequires: openssl-devel
|
BuildRequires: openssl-devel
|
||||||
%endif
|
%endif
|
||||||
|
%endif
|
||||||
|
|
||||||
%if %{with mysql}
|
%if %{with mysql}
|
||||||
BuildRequires: mysql-devel
|
BuildRequires: mysql-devel
|
||||||
@ -37,6 +43,10 @@ BuildRequires: faac-devel
|
|||||||
BuildRequires: x264-devel
|
BuildRequires: x264-devel
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
|
%if %{with webrtc}
|
||||||
|
BuildRequires: libsrtp-devel >= 2.0
|
||||||
|
%endif
|
||||||
|
|
||||||
%if 0%{?use_devtoolset}
|
%if 0%{?use_devtoolset}
|
||||||
BuildRequires: devtoolset-8-gcc-c++
|
BuildRequires: devtoolset-8-gcc-c++
|
||||||
%endif
|
%endif
|
||||||
@ -88,6 +98,10 @@ pushd %{_target_platform}
|
|||||||
-DENABLE_MYSQL:BOOL=%{with mysql} \
|
-DENABLE_MYSQL:BOOL=%{with mysql} \
|
||||||
-DENABLE_FAAC:BOOL=%{with faac} \
|
-DENABLE_FAAC:BOOL=%{with faac} \
|
||||||
-DENABLE_X264:BOOL=%{with x264} \
|
-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_MP4:BOOL=ON \
|
||||||
-DENABLE_RTPPROXY:BOOL=ON \
|
-DENABLE_RTPPROXY:BOOL=ON \
|
||||||
-DENABLE_API:BOOL=ON \
|
-DENABLE_API:BOOL=ON \
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"info": {
|
"info": {
|
||||||
"_postman_id": "ff20487b-d269-40c3-b811-44bc643a3b74",
|
"_postman_id": "fe6cdfbd-531d-45e6-87e5-d460ce9e6328",
|
||||||
"name": "ZLMediaKit",
|
"name": "ZLMediaKit",
|
||||||
"description": "媒体服务器",
|
"description": "媒体服务器",
|
||||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||||
@ -518,6 +518,12 @@
|
|||||||
"value": "10",
|
"value": "10",
|
||||||
"description": "拉流超时时间,单位秒,float类型",
|
"description": "拉流超时时间,单位秒,float类型",
|
||||||
"disabled": true
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "retry_count",
|
||||||
|
"value": null,
|
||||||
|
"description": "拉流重试次数,不传此参数或传值<=0时,则无限重试",
|
||||||
|
"disabled": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -555,6 +561,106 @@
|
|||||||
},
|
},
|
||||||
"response": []
|
"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)",
|
"name": "添加FFmpeg拉流代理(addFFmpegSource)",
|
||||||
"request": {
|
"request": {
|
||||||
@ -786,7 +892,7 @@
|
|||||||
"method": "GET",
|
"method": "GET",
|
||||||
"header": [],
|
"header": [],
|
||||||
"url": {
|
"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": [
|
"host": [
|
||||||
"{{ZLMediaKit_URL}}"
|
"{{ZLMediaKit_URL}}"
|
||||||
],
|
],
|
||||||
@ -824,14 +930,14 @@
|
|||||||
{
|
{
|
||||||
"key": "customized_path",
|
"key": "customized_path",
|
||||||
"value": null,
|
"value": null,
|
||||||
"disabled": true,
|
"description": "录像文件保存自定义根目录,为空则采用配置文件设置",
|
||||||
"description": "录像文件保存自定义根目录,为空则采用配置文件设置"
|
"disabled": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "max_second",
|
"key": "max_second",
|
||||||
"value": "1000",
|
"value": "1000",
|
||||||
"disabled": true,
|
"description": "MP4录制的切片时间大小,单位秒",
|
||||||
"description": "MP4录制的切片时间大小,单位秒"
|
"disabled": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1281,7 +1387,6 @@
|
|||||||
{
|
{
|
||||||
"listen": "prerequest",
|
"listen": "prerequest",
|
||||||
"script": {
|
"script": {
|
||||||
"id": "90757ea3-58c0-4f84-8000-513ed7088bbc",
|
|
||||||
"type": "text/javascript",
|
"type": "text/javascript",
|
||||||
"exec": [
|
"exec": [
|
||||||
""
|
""
|
||||||
@ -1291,7 +1396,6 @@
|
|||||||
{
|
{
|
||||||
"listen": "test",
|
"listen": "test",
|
||||||
"script": {
|
"script": {
|
||||||
"id": "0ddf2b8e-9932-409d-a055-1ab3b7765600",
|
|
||||||
"type": "text/javascript",
|
"type": "text/javascript",
|
||||||
"exec": [
|
"exec": [
|
||||||
""
|
""
|
||||||
@ -1301,20 +1405,16 @@
|
|||||||
],
|
],
|
||||||
"variable": [
|
"variable": [
|
||||||
{
|
{
|
||||||
"id": "ce426571-eb1e-4067-8901-01978c982fed",
|
|
||||||
"key": "ZLMediaKit_URL",
|
"key": "ZLMediaKit_URL",
|
||||||
"value": "zlmediakit.com:8880"
|
"value": "zlmediakit.com:8880"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2d3dfd4a-a39c-47d8-a3e9-37d80352ea5f",
|
|
||||||
"key": "ZLMediaKit_secret",
|
"key": "ZLMediaKit_secret",
|
||||||
"value": "035c73f7-bb6b-4889-a715-d9eb2d1925cc"
|
"value": "035c73f7-bb6b-4889-a715-d9eb2d1925cc"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "0aacc473-3a2e-4ef9-b415-e86ce71e0c42",
|
|
||||||
"key": "defaultVhost",
|
"key": "defaultVhost",
|
||||||
"value": "__defaultVhost__"
|
"value": "__defaultVhost__"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"protocolProfileBehavior": {}
|
|
||||||
}
|
}
|
@ -27,6 +27,7 @@
|
|||||||
#include "Network/TcpServer.h"
|
#include "Network/TcpServer.h"
|
||||||
#include "Network/UdpServer.h"
|
#include "Network/UdpServer.h"
|
||||||
#include "Player/PlayerProxy.h"
|
#include "Player/PlayerProxy.h"
|
||||||
|
#include "Pusher/PusherProxy.h"
|
||||||
#include "Util/MD5.h"
|
#include "Util/MD5.h"
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
#include "WebHook.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 recursive_mutex s_proxyMapMtx;
|
||||||
|
|
||||||
|
//推流代理器列表
|
||||||
|
static unordered_map<string, PusherProxy::Ptr> s_proxyPusherMap;
|
||||||
|
static recursive_mutex s_proxyPusherMapMtx;
|
||||||
|
|
||||||
//FFmpeg拉流代理器列表
|
//FFmpeg拉流代理器列表
|
||||||
static unordered_map<string ,FFmpegSource::Ptr> s_ffmpegMap;
|
static unordered_map<string, FFmpegSource::Ptr> s_ffmpegMap;
|
||||||
static recursive_mutex s_ffmpegMapMtx;
|
static recursive_mutex s_ffmpegMapMtx;
|
||||||
|
|
||||||
#if defined(ENABLE_RTPPROXY)
|
#if defined(ENABLE_RTPPROXY)
|
||||||
@ -275,10 +280,15 @@ static unordered_map<string, RtpServer::Ptr> s_rtpServerMap;
|
|||||||
static recursive_mutex s_rtpServerMapMtx;
|
static recursive_mutex s_rtpServerMapMtx;
|
||||||
#endif
|
#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;
|
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 makeMediaSourceJson(MediaSource &media){
|
||||||
Value item;
|
Value item;
|
||||||
item["schema"] = media.getSchema();
|
item["schema"] = media.getSchema();
|
||||||
@ -634,14 +644,103 @@ void installWebApi() {
|
|||||||
val["count_hit"] = (Json::UInt64)count_hit;
|
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,
|
static auto addStreamProxy = [](const string &vhost,
|
||||||
const string &app,
|
const string &app,
|
||||||
const string &stream,
|
const string &stream,
|
||||||
const string &url,
|
const string &url,
|
||||||
|
int retry_count,
|
||||||
bool enable_hls,
|
bool enable_hls,
|
||||||
bool enable_mp4,
|
bool enable_mp4,
|
||||||
int rtp_type,
|
int rtp_type,
|
||||||
float timeoutSec,
|
float timeout_sec,
|
||||||
const function<void(const SockException &ex,const string &key)> &cb){
|
const function<void(const SockException &ex,const string &key)> &cb){
|
||||||
auto key = getProxyKey(vhost,app,stream);
|
auto key = getProxyKey(vhost,app,stream);
|
||||||
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
||||||
@ -651,15 +750,15 @@ void installWebApi() {
|
|||||||
return;
|
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;
|
s_proxyMap[key] = player;
|
||||||
|
|
||||||
//指定RTP over TCP(播放rtsp时有效)
|
//指定RTP over TCP(播放rtsp时有效)
|
||||||
(*player)[kRtpType] = rtp_type;
|
(*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["app"],
|
||||||
allArgs["stream"],
|
allArgs["stream"],
|
||||||
allArgs["url"],
|
allArgs["url"],
|
||||||
|
allArgs["retry_count"],
|
||||||
allArgs["enable_hls"],/* 是否hls转发 */
|
allArgs["enable_hls"],/* 是否hls转发 */
|
||||||
allArgs["enable_mp4"],/* 是否MP4录制 */
|
allArgs["enable_mp4"],/* 是否MP4录制 */
|
||||||
allArgs["rtp_type"],
|
allArgs["rtp_type"],
|
||||||
@ -1265,6 +1365,7 @@ void installWebApi() {
|
|||||||
allArgs["stream"],
|
allArgs["stream"],
|
||||||
/** 支持rtsp和rtmp方式拉流 ,rtsp支持h265/h264/aac,rtmp仅支持h264/aac **/
|
/** 支持rtsp和rtmp方式拉流 ,rtsp支持h265/h264/aac,rtmp仅支持h264/aac **/
|
||||||
"rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov",
|
"rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov",
|
||||||
|
-1,/*无限重试*/
|
||||||
true,/* 开启hls转发 */
|
true,/* 开启hls转发 */
|
||||||
false,/* 禁用MP4录制 */
|
false,/* 禁用MP4录制 */
|
||||||
0,//rtp over tcp方式拉流
|
0,//rtp over tcp方式拉流
|
||||||
|
@ -260,6 +260,8 @@ const string kFileBufSize = HLS_FIELD"fileBufSize";
|
|||||||
const string kFilePath = HLS_FIELD"filePath";
|
const string kFilePath = HLS_FIELD"filePath";
|
||||||
// 是否广播 ts 切片完成通知
|
// 是否广播 ts 切片完成通知
|
||||||
const string kBroadcastRecordTs = HLS_FIELD"broadcastRecordTs";
|
const string kBroadcastRecordTs = HLS_FIELD"broadcastRecordTs";
|
||||||
|
//hls直播文件删除延时,单位秒
|
||||||
|
const string kDeleteDelaySec = HLS_FIELD"deleteDelaySec";
|
||||||
|
|
||||||
onceToken token([](){
|
onceToken token([](){
|
||||||
mINI::Instance()[kSegmentDuration] = 2;
|
mINI::Instance()[kSegmentDuration] = 2;
|
||||||
@ -268,6 +270,7 @@ onceToken token([](){
|
|||||||
mINI::Instance()[kFileBufSize] = 64 * 1024;
|
mINI::Instance()[kFileBufSize] = 64 * 1024;
|
||||||
mINI::Instance()[kFilePath] = "./www";
|
mINI::Instance()[kFilePath] = "./www";
|
||||||
mINI::Instance()[kBroadcastRecordTs] = false;
|
mINI::Instance()[kBroadcastRecordTs] = false;
|
||||||
|
mINI::Instance()[kDeleteDelaySec] = 0;
|
||||||
},nullptr);
|
},nullptr);
|
||||||
} //namespace Hls
|
} //namespace Hls
|
||||||
|
|
||||||
|
@ -293,6 +293,8 @@ extern const string kFileBufSize;
|
|||||||
extern const string kFilePath;
|
extern const string kFilePath;
|
||||||
// 是否广播 ts 切片完成通知
|
// 是否广播 ts 切片完成通知
|
||||||
extern const string kBroadcastRecordTs;
|
extern const string kBroadcastRecordTs;
|
||||||
|
//hls直播文件删除延时,单位秒
|
||||||
|
extern const string kDeleteDelaySec;
|
||||||
} //namespace Hls
|
} //namespace Hls
|
||||||
|
|
||||||
////////////Rtp代理相关配置///////////
|
////////////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,
|
uint32_t bufSize,
|
||||||
float seg_duration,
|
float seg_duration,
|
||||||
uint32_t seg_number) : HlsMaker(seg_duration, seg_number) {
|
uint32_t seg_number) : HlsMaker(seg_duration, seg_number) {
|
||||||
|
_poller = EventPollerPool::Instance().getPoller();
|
||||||
_path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/'));
|
_path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/'));
|
||||||
_path_hls = m3u8_file;
|
_path_hls = m3u8_file;
|
||||||
_params = params;
|
_params = params;
|
||||||
@ -35,18 +36,30 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file,
|
|||||||
}
|
}
|
||||||
|
|
||||||
HlsMakerImp::~HlsMakerImp() {
|
HlsMakerImp::~HlsMakerImp() {
|
||||||
clearCache();
|
clearCache(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HlsMakerImp::clearCache() {
|
void HlsMakerImp::clearCache(bool immediately) {
|
||||||
//录制完了
|
//录制完了
|
||||||
flushLastSegment(true);
|
flushLastSegment(true);
|
||||||
if (isLive()) {
|
if (!isLive()) {
|
||||||
//hls直播才删除文件
|
return;
|
||||||
clear();
|
}
|
||||||
_file = nullptr;
|
|
||||||
_segment_file_paths.clear();
|
clear();
|
||||||
|
_file = nullptr;
|
||||||
|
_segment_file_paths.clear();
|
||||||
|
|
||||||
|
//hls直播才删除文件
|
||||||
|
GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec);
|
||||||
|
if (!delay || immediately) {
|
||||||
File::delete_file(_path_prefix.data());
|
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:
|
protected:
|
||||||
string onOpenSegment(uint64_t index) override ;
|
string onOpenSegment(uint64_t index) override ;
|
||||||
@ -69,6 +70,7 @@ private:
|
|||||||
std::shared_ptr<FILE> _file;
|
std::shared_ptr<FILE> _file;
|
||||||
std::shared_ptr<char> _file_buf;
|
std::shared_ptr<char> _file_buf;
|
||||||
HlsMediaSource::Ptr _media_src;
|
HlsMediaSource::Ptr _media_src;
|
||||||
|
EventPoller::Ptr _poller;
|
||||||
map<uint64_t/*index*/,string/*file_path*/> _segment_file_paths;
|
map<uint64_t/*index*/,string/*file_path*/> _segment_file_paths;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -115,7 +115,14 @@ public:
|
|||||||
|
|
||||||
class RtmpHeader {
|
class RtmpHeader {
|
||||||
public:
|
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 time_stamp[3];
|
||||||
uint8_t body_size[3];
|
uint8_t body_size[3];
|
||||||
uint8_t type_id;
|
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));
|
buffer_header->setSize(sizeof(RtmpHeader));
|
||||||
//对rtmp头赋值,如果使用整形赋值,在arm android上可能由于数据对齐导致总线错误的问题
|
//对rtmp头赋值,如果使用整形赋值,在arm android上可能由于数据对齐导致总线错误的问题
|
||||||
RtmpHeader *header = (RtmpHeader *) buffer_header->data();
|
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;
|
header->type_id = type;
|
||||||
set_be24(header->time_stamp, ext_stamp ? 0xFFFFFF : stamp);
|
set_be24(header->time_stamp, ext_stamp ? 0xFFFFFF : stamp);
|
||||||
set_be24(header->body_size, (uint32_t)buf->size());
|
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();
|
BufferRaw::Ptr buffer_flags = obtainBuffer();
|
||||||
buffer_flags->setCapacity(1);
|
buffer_flags->setCapacity(1);
|
||||||
buffer_flags->setSize(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 offset = 0;
|
||||||
size_t totalSize = sizeof(RtmpHeader);
|
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);
|
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) {
|
const char* RtmpProtocol::handle_rtmp(const char *data, size_t len) {
|
||||||
auto ptr = data;
|
auto ptr = data;
|
||||||
while (len) {
|
while (len) {
|
||||||
int offset = 0;
|
size_t offset = 0;
|
||||||
uint8_t flags = ptr[0];
|
auto header = (RtmpHeader *) ptr;
|
||||||
size_t header_len = HEADER_LENGTH[flags >> 6];
|
auto header_len = HEADER_LENGTH[header->fmt];
|
||||||
_now_chunk_id = flags & 0x3f;
|
_now_chunk_id = header->chunk_id;
|
||||||
switch (_now_chunk_id) {
|
switch (_now_chunk_id) {
|
||||||
case 0: {
|
case 0: {
|
||||||
//0 值表示二字节形式,并且 ID 范围 64 - 319
|
//0 值表示二字节形式,并且 ID 范围 64 - 319
|
||||||
@ -565,7 +568,7 @@ const char* RtmpProtocol::handle_rtmp(const char *data, size_t len) {
|
|||||||
//need more data
|
//need more data
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
RtmpHeader &header = *((RtmpHeader *) (ptr + offset));
|
header = (RtmpHeader *) (ptr + offset);
|
||||||
auto &pr = _map_chunk_data[_now_chunk_id];
|
auto &pr = _map_chunk_data[_now_chunk_id];
|
||||||
auto &now_packet = pr.first;
|
auto &now_packet = pr.first;
|
||||||
auto &last_packet = pr.second;
|
auto &last_packet = pr.second;
|
||||||
@ -583,12 +586,12 @@ const char* RtmpProtocol::handle_rtmp(const char *data, size_t len) {
|
|||||||
switch (header_len) {
|
switch (header_len) {
|
||||||
case 12:
|
case 12:
|
||||||
chunk_data.is_abs_stamp = true;
|
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:
|
case 8:
|
||||||
chunk_data.body_size = load_be24(header.body_size);
|
chunk_data.body_size = load_be24(header->body_size);
|
||||||
chunk_data.type_id = header.type_id;
|
chunk_data.type_id = header->type_id;
|
||||||
case 4:
|
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;
|
auto time_stamp = chunk_data.ts_field;
|
||||||
|
@ -47,6 +47,7 @@ void RtmpPusher::teardown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RtmpPusher::onPublishResult(const SockException &ex, bool handshake_done) {
|
void RtmpPusher::onPublishResult(const SockException &ex, bool handshake_done) {
|
||||||
|
DebugL << ex.what();
|
||||||
if (ex.getErrCode() == Err_shutdown) {
|
if (ex.getErrCode() == Err_shutdown) {
|
||||||
//主动shutdown的,不触发回调
|
//主动shutdown的,不触发回调
|
||||||
return;
|
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){
|
void DecoderImp::onStream(int stream, int codecid, const void *extra, size_t bytes, int finish){
|
||||||
switch (codecid) {
|
switch (codecid) {
|
||||||
case PSI_STREAM_H264: {
|
case PSI_STREAM_H264: {
|
||||||
InfoL << "got video track: H264";
|
|
||||||
onTrack(std::make_shared<H264Track>());
|
onTrack(std::make_shared<H264Track>());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case PSI_STREAM_H265: {
|
case PSI_STREAM_H265: {
|
||||||
InfoL << "got video track: H265";
|
|
||||||
onTrack(std::make_shared<H265Track>());
|
onTrack(std::make_shared<H265Track>());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case PSI_STREAM_AAC: {
|
case PSI_STREAM_AAC: {
|
||||||
InfoL<< "got audio track: AAC";
|
|
||||||
onTrack(std::make_shared<AACTrack>());
|
onTrack(std::make_shared<AACTrack>());
|
||||||
break;
|
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_G711A:
|
||||||
case PSI_STREAM_AUDIO_G711U: {
|
case PSI_STREAM_AUDIO_G711U: {
|
||||||
auto codec = codecid == PSI_STREAM_AUDIO_G711A ? CodecG711A : CodecG711U;
|
auto codec = codecid == PSI_STREAM_AUDIO_G711A ? CodecG711A : CodecG711U;
|
||||||
InfoL << "got audio track: G711";
|
|
||||||
//G711传统只支持 8000/1/16的规格,FFmpeg貌似做了扩展,但是这里不管它了
|
//G711传统只支持 8000/1/16的规格,FFmpeg貌似做了扩展,但是这里不管它了
|
||||||
onTrack(std::make_shared<G711Track>(codec, 8000, 1, 16));
|
onTrack(std::make_shared<G711Track>(codec, 8000, 1, 16));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case PSI_STREAM_AUDIO_OPUS: {
|
case PSI_STREAM_AUDIO_OPUS: {
|
||||||
InfoL << "got audio track: opus";
|
|
||||||
onTrack(std::make_shared<OpusTrack>());
|
onTrack(std::make_shared<OpusTrack>());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -223,8 +218,11 @@ void DecoderImp::onStream(int stream,int codecid,const void *extra,size_t bytes,
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
void DecoderImp::onTrack(const Track::Ptr &track) {
|
void DecoderImp::onTrack(const Track::Ptr &track) {
|
||||||
_tracks[track->getTrackType()] = track;
|
if (!_tracks[track->getTrackType()]) {
|
||||||
_sink->addTrack(track);
|
_tracks[track->getTrackType()] = track;
|
||||||
|
_sink->addTrack(track);
|
||||||
|
InfoL << "got track: " << track->getCodecName();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DecoderImp::onFrame(const Frame::Ptr &frame) {
|
void DecoderImp::onFrame(const Frame::Ptr &frame) {
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
#include "Http/HttpTSPlayer.h"
|
#include "Http/HttpTSPlayer.h"
|
||||||
#include "Extension/CommonRtp.h"
|
#include "Extension/CommonRtp.h"
|
||||||
#include "Extension/H264Rtp.h"
|
#include "Extension/H264Rtp.h"
|
||||||
|
#include "Extension/Factory.h"
|
||||||
|
#include "Extension/Opus.h"
|
||||||
|
|
||||||
namespace mediakit{
|
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;
|
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) {
|
GB28181Process::GB28181Process(const MediaInfo &media_info, MediaSinkInterface *interface) {
|
||||||
assert(interface);
|
assert(interface);
|
||||||
_media_info = media_info;
|
_media_info = media_info;
|
||||||
@ -30,26 +66,80 @@ GB28181Process::GB28181Process(const MediaInfo &media_info, MediaSinkInterface *
|
|||||||
|
|
||||||
GB28181Process::~GB28181Process() {}
|
GB28181Process::~GB28181Process() {}
|
||||||
|
|
||||||
bool GB28181Process::inputRtp(bool, const char *data, size_t data_len) {
|
void GB28181Process::onRtpSorted(RtpPacket::Ptr rtp) {
|
||||||
return handleOneRtp(0, TrackVideo, 90000, (unsigned char *) data, data_len);
|
_rtp_decoder[rtp->getHeader()->pt]->inputRtp(rtp, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GB28181Process::onRtpSorted(RtpPacket::Ptr rtp, int) {
|
bool GB28181Process::inputRtp(bool, const char *data, size_t data_len) {
|
||||||
auto pt = rtp->getHeader()->pt;
|
RtpHeader *header = (RtpHeader *) data;
|
||||||
if (!_rtp_decoder) {
|
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) {
|
switch (pt) {
|
||||||
case 98: {
|
case 100: {
|
||||||
//H264负载
|
//opus负载
|
||||||
_rtp_decoder = std::make_shared<H264RtpDecoder>();
|
ref = std::make_shared<RtpReceiverImp>(48000,[this](RtpPacket::Ptr rtp) {
|
||||||
_interface->addTrack(std::make_shared<H264Track>());
|
onRtpSorted(std::move(rtp));
|
||||||
|
});
|
||||||
|
|
||||||
|
auto track = std::make_shared<OpusTrack>();
|
||||||
|
_interface->addTrack(track);
|
||||||
|
_rtp_decoder[pt] = Factory::getRtpDecoderByTrack(track);
|
||||||
break;
|
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: {
|
default: {
|
||||||
if (pt != 33 && pt != 96) {
|
if (pt != 33 && pt != 96) {
|
||||||
WarnL << "rtp payload type未识别(" << (int) pt << "),已按ts或ps负载处理";
|
WarnL << "rtp payload type未识别(" << (int) pt << "),已按ts或ps负载处理";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ref = std::make_shared<RtpReceiverImp>(90000,[this](RtpPacket::Ptr rtp) {
|
||||||
|
onRtpSorted(std::move(rtp));
|
||||||
|
});
|
||||||
|
|
||||||
//ts或ps负载
|
//ts或ps负载
|
||||||
_rtp_decoder = std::make_shared<CommonRtpDecoder>(CodecInvalid, 32 * 1024);
|
_rtp_decoder[pt] = std::make_shared<CommonRtpDecoder>(CodecInvalid, 32 * 1024);
|
||||||
//设置dump目录
|
//设置dump目录
|
||||||
GET_CONFIG(string, dump_dir, RtpProxy::kDumpDir);
|
GET_CONFIG(string, dump_dir, RtpProxy::kDumpDir);
|
||||||
if (!dump_dir.empty()) {
|
if (!dump_dir.empty()) {
|
||||||
@ -65,13 +155,12 @@ void GB28181Process::onRtpSorted(RtpPacket::Ptr rtp, int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//设置frame回调
|
//设置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);
|
onRtpDecode(frame);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
//解码rtp
|
return ref->inputRtp(TrackVideo, (unsigned char *) data, data_len);
|
||||||
_rtp_decoder->inputRtp(rtp, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *GB28181Process::onSearchPacketTail(const char *packet,size_t bytes){
|
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) {
|
void GB28181Process::onRtpDecode(const Frame::Ptr &frame) {
|
||||||
if (frame->getCodecId() == CodecH264) {
|
if (frame->getCodecId() != CodecInvalid) {
|
||||||
//这是H264
|
//这里不是ps或ts
|
||||||
_interface->inputFrame(frame);
|
_interface->inputFrame(frame);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,8 @@
|
|||||||
|
|
||||||
namespace mediakit{
|
namespace mediakit{
|
||||||
|
|
||||||
class GB28181Process : public HttpRequestSplitter, public RtpReceiver, public ProcessInterface{
|
class RtpReceiverImp;
|
||||||
|
class GB28181Process : public HttpRequestSplitter, public ProcessInterface{
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<GB28181Process> Ptr;
|
typedef std::shared_ptr<GB28181Process> Ptr;
|
||||||
GB28181Process(const MediaInfo &media_info, MediaSinkInterface *interface);
|
GB28181Process(const MediaInfo &media_info, MediaSinkInterface *interface);
|
||||||
@ -36,7 +37,7 @@ public:
|
|||||||
bool inputRtp(bool, const char *data, size_t data_len) override;
|
bool inputRtp(bool, const char *data, size_t data_len) override;
|
||||||
|
|
||||||
protected:
|
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;
|
const char *onSearchPacketTail(const char *data,size_t len) override;
|
||||||
ssize_t onRecvHeader(const char *data,size_t len) override { return 0; };
|
ssize_t onRecvHeader(const char *data,size_t len) override { return 0; };
|
||||||
|
|
||||||
@ -48,7 +49,8 @@ private:
|
|||||||
DecoderImp::Ptr _decoder;
|
DecoderImp::Ptr _decoder;
|
||||||
MediaSinkInterface *_interface;
|
MediaSinkInterface *_interface;
|
||||||
std::shared_ptr<FILE> _save_file_ps;
|
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
|
}//namespace mediakit
|
||||||
|
@ -13,14 +13,17 @@
|
|||||||
#include "RtpProcess.h"
|
#include "RtpProcess.h"
|
||||||
#include "Http/HttpTSPlayer.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 {
|
namespace mediakit {
|
||||||
|
|
||||||
RtpProcess::RtpProcess(const string &stream_id) {
|
RtpProcess::RtpProcess(const string &stream_id) {
|
||||||
_media_info._schema = RTP_APP_NAME;
|
_media_info._schema = kRtpAppName;
|
||||||
_media_info._vhost = DEFAULT_VHOST;
|
_media_info._vhost = DEFAULT_VHOST;
|
||||||
_media_info._app = RTP_APP_NAME;
|
_media_info._app = kRtpAppName;
|
||||||
_media_info._streamid = stream_id;
|
_media_info._streamid = stream_id;
|
||||||
|
|
||||||
GET_CONFIG(string, dump_dir, RtpProxy::kDumpDir);
|
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();
|
emitOnPublish();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_muxer) {
|
|
||||||
//无权限推流
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_total_bytes += len;
|
_total_bytes += len;
|
||||||
if (_save_file_rtp) {
|
if (_save_file_rtp) {
|
||||||
uint16_t size = (uint16_t)len;
|
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);
|
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();
|
_last_frame_time.resetTime();
|
||||||
return false;
|
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) {
|
void RtpProcess::inputFrame(const Frame::Ptr &frame) {
|
||||||
_last_frame_time.resetTime();
|
|
||||||
_dts = frame->dts();
|
_dts = frame->dts();
|
||||||
if (_save_file_video && frame->getTrackType() == TrackVideo) {
|
if (_save_file_video && frame->getTrackType() == TrackVideo) {
|
||||||
fwrite((uint8_t *) frame->data(), frame->size(), 1, _save_file_video.get());
|
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) {
|
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() {
|
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() {
|
bool RtpProcess::alive() {
|
||||||
@ -197,19 +230,20 @@ void RtpProcess::setListener(const std::weak_ptr<MediaSourceEvent> &listener) {
|
|||||||
void RtpProcess::emitOnPublish() {
|
void RtpProcess::emitOnPublish() {
|
||||||
weak_ptr<RtpProcess> weak_self = shared_from_this();
|
weak_ptr<RtpProcess> weak_self = shared_from_this();
|
||||||
Broadcast::PublishAuthInvoker invoker = [weak_self](const string &err, bool enableHls, bool enableMP4) {
|
Broadcast::PublishAuthInvoker invoker = [weak_self](const string &err, bool enableHls, bool enableMP4) {
|
||||||
auto strongSelf = weak_self.lock();
|
auto strong_self = weak_self.lock();
|
||||||
if (!strongSelf) {
|
if (!strong_self) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (err.empty()) {
|
if (err.empty()) {
|
||||||
strongSelf->_muxer = std::make_shared<MultiMediaSourceMuxer>(strongSelf->_media_info._vhost,
|
strong_self->_muxer = std::make_shared<MultiMediaSourceMuxer>(strong_self->_media_info._vhost,
|
||||||
strongSelf->_media_info._app,
|
strong_self->_media_info._app,
|
||||||
strongSelf->_media_info._streamid, 0.0f,
|
strong_self->_media_info._streamid, 0.0f,
|
||||||
true, true, enableHls, enableMP4);
|
true, true, enableHls, enableMP4);
|
||||||
strongSelf->_muxer->setMediaListener(strongSelf);
|
strong_self->_muxer->setMediaListener(strong_self);
|
||||||
InfoP(strongSelf) << "允许RTP推流";
|
strong_self->doCachedFunc();
|
||||||
|
InfoP(strong_self) << "允许RTP推流";
|
||||||
} else {
|
} else {
|
||||||
WarnP(strongSelf) << "禁止RTP推流:" << err;
|
WarnP(strong_self) << "禁止RTP推流:" << err;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -79,6 +79,7 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void emitOnPublish();
|
void emitOnPublish();
|
||||||
|
void doCachedFunc();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t _dts = 0;
|
uint32_t _dts = 0;
|
||||||
@ -95,6 +96,8 @@ private:
|
|||||||
atomic_bool _stop_rtp_check{false};
|
atomic_bool _stop_rtp_check{false};
|
||||||
atomic_flag _busy_flag{false};
|
atomic_flag _busy_flag{false};
|
||||||
Ticker _last_check_alive;
|
Ticker _last_check_alive;
|
||||||
|
recursive_mutex _func_mtx;
|
||||||
|
deque<function<void()> > _cached_func;
|
||||||
};
|
};
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
||||||
|
@ -95,6 +95,7 @@ void RtspPusher::publish(const string &url_str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RtspPusher::onPublishResult(const SockException &ex, bool handshake_done) {
|
void RtspPusher::onPublishResult(const SockException &ex, bool handshake_done) {
|
||||||
|
DebugL << ex.what();
|
||||||
if (ex.getErrCode() == Err_shutdown) {
|
if (ex.getErrCode() == Err_shutdown) {
|
||||||
//主动shutdown的,不触发回调
|
//主动shutdown的,不触发回调
|
||||||
return;
|
return;
|
||||||
|
@ -390,7 +390,7 @@ string RtpExt::getSdesMid() const {
|
|||||||
|
|
||||||
|
|
||||||
//https://tools.ietf.org/html/draft-ietf-avtext-rid-06
|
//https://tools.ietf.org/html/draft-ietf-avtext-rid-06
|
||||||
//用于simulecast
|
//用于simulcast
|
||||||
//3.1. RTCP 'RtpStreamId' SDES Extension
|
//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
|
// 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 "Sdp.h"
|
||||||
#include "Rtsp/Rtsp.h"
|
#include "Rtsp/Rtsp.h"
|
||||||
#include <inttypes.h>
|
#include <cinttypes>
|
||||||
using namespace mediakit;
|
using namespace mediakit;
|
||||||
|
|
||||||
using onCreateSdpItem = function<SdpItem::Ptr(const string &key, const string &value)>;
|
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) {
|
void SdpAttrCandidate::parse(const string &str) {
|
||||||
char foundation_buf[32] = {0};
|
char foundation_buf[40] = {0};
|
||||||
char transport_buf[16] = {0};
|
char transport_buf[16] = {0};
|
||||||
char address_buf[32] = {0};
|
char address_buf[32] = {0};
|
||||||
char type_buf[16] = {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)) {
|
foundation_buf, &component, transport_buf, &priority, address_buf, &port, type_buf)) {
|
||||||
SDP_THROW();
|
SDP_THROW();
|
||||||
}
|
}
|
||||||
|
@ -408,14 +408,7 @@ void WebRtcTransportImp::onStartWebRTC() {
|
|||||||
info->offer_ssrc_rtx = m_offer->getRtxSSRC();
|
info->offer_ssrc_rtx = m_offer->getRtxSSRC();
|
||||||
info->plan_rtp = &m_answer.plan[0];;
|
info->plan_rtp = &m_answer.plan[0];;
|
||||||
info->plan_rtx = m_answer.getRelatedRtxPlan(info->plan_rtp->pt);
|
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->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
|
//send ssrc --> RtpPayloadInfo
|
||||||
_rtp_info_ssrc[info->answer_ssrc_rtp] = std::make_pair(false, info);
|
_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;
|
auto rtx = it->second.first;
|
||||||
if (!rtx) {
|
if (!rtx) {
|
||||||
auto &info = it->second.second;
|
auto &info = it->second.second;
|
||||||
info->rtcp_context_recv->onRtcp(sr);
|
auto it = info->rtcp_context_recv.find(sr->ssrc);
|
||||||
auto rr = info->rtcp_context_recv->createRtcpRR(info->answer_ssrc_rtp, info->offer_ssrc_rtp);
|
if (it != info->rtcp_context_recv.end()) {
|
||||||
sendRtcpPacket(rr->data(), rr->size(), true);
|
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 {
|
} else {
|
||||||
WarnL << "未识别的sr rtcp包:" << rtcp->dumpString();
|
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);
|
auto ext_map = RtpExt::getExtValue(header);
|
||||||
for (auto &pr : ext_map) {
|
for (auto &pr : ext_map) {
|
||||||
if (is_recv) {
|
if (is_recv) {
|
||||||
@ -690,6 +688,35 @@ void WebRtcTransportImp::changeRtpExtId(const RtpPayloadInfo *info, const RtpHea
|
|||||||
pr.second.setType(it->second);
|
pr.second.setType(it->second);
|
||||||
//重新赋值ext id为 ext type,作为后面处理ext的统一中间类型
|
//重新赋值ext id为 ext type,作为后面处理ext的统一中间类型
|
||||||
pr.second.setExtId((uint8_t) it->second);
|
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 {
|
} else {
|
||||||
pr.second.setType((RtpExtType) pr.first);
|
pr.second.setType((RtpExtType) pr.first);
|
||||||
auto it = _rtp_ext_type_to_id.find((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;
|
RtpHeader *rtp = (RtpHeader *) buf;
|
||||||
|
auto ssrc = ntohl(rtp->ssrc);
|
||||||
|
|
||||||
//根据接收到的rtp的pt信息,找到该流的信息
|
//根据接收到的rtp的pt信息,找到该流的信息
|
||||||
auto it = _rtp_info_pt.find(rtp->pt);
|
auto it = _rtp_info_pt.find(rtp->pt);
|
||||||
if (it == _rtp_info_pt.end()) {
|
if (it == _rtp_info_pt.end()) {
|
||||||
@ -732,18 +761,39 @@ void WebRtcTransportImp::onRtp_l(const char *buf, size_t len, bool rtx) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
auto &ref = info->receiver[ssrc];
|
||||||
if (!rtx) {
|
if (!rtx) {
|
||||||
//统计rtp接受情况,便于生成nack rtcp包
|
//统计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;
|
auto stamp_ms = ntohl(rtp->stamp) * uint64_t(1000) / info->plan_rtp->sample_rate;
|
||||||
|
|
||||||
//统计rtp收到的情况,好做rr汇报
|
//统计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
|
//解析并排序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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -754,11 +804,22 @@ void WebRtcTransportImp::onRtp_l(const char *buf, size_t len, bool rtx) {
|
|||||||
if (size < 2) {
|
if (size < 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//修改ext id至统一
|
||||||
|
changeRtpExtId(*info, rtp, true, true);
|
||||||
|
|
||||||
//前两个字节是原始的rtp的seq
|
//前两个字节是原始的rtp的seq
|
||||||
auto origin_seq = payload[0] << 8 | payload[1];
|
auto origin_seq = payload[0] << 8 | payload[1];
|
||||||
InfoL << "received rtx rtp: " << origin_seq;
|
|
||||||
rtp->seq = htons(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;
|
rtp->pt = info->plan_rtp->pt;
|
||||||
memmove((uint8_t *) buf + 2, buf, payload - (uint8_t *) buf);
|
memmove((uint8_t *) buf + 2, buf, payload - (uint8_t *) buf);
|
||||||
buf += 2;
|
buf += 2;
|
||||||
@ -766,16 +827,17 @@ void WebRtcTransportImp::onRtp_l(const char *buf, size_t len, bool rtx) {
|
|||||||
onRtp_l(buf, len, true);
|
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);
|
auto rtcp = RtcpFB::create(RTPFBType::RTCP_RTPFB_NACK, &nack, FCI_NACK::kSize);
|
||||||
rtcp->ssrc = htons(info.answer_ssrc_rtp);
|
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);
|
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) {
|
if (info.media->type == TrackVideo && _pli_ticker.elapsedTime() > 2000) {
|
||||||
//定期发送pli请求关键帧,方便非rtc等协议
|
//定期发送pli请求关键帧,方便非rtc等协议
|
||||||
_pli_ticker.resetTime();
|
_pli_ticker.resetTime();
|
||||||
@ -789,7 +851,24 @@ void WebRtcTransportImp::onSortedRtp(RtpPayloadInfo &info, RtpPacket::Ptr rtp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_push_src) {
|
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) {
|
if (!pr->first || !pr->second->plan_rtx) {
|
||||||
//普通的rtp,或者不支持rtx, 修改目标pt和ssrc
|
//普通的rtp,或者不支持rtx, 修改目标pt和ssrc
|
||||||
changeRtpExtId(pr->second, header, false, false);
|
changeRtpExtId(*pr->second, header, false, false);
|
||||||
header->pt = pr->second->plan_rtp->pt;
|
header->pt = pr->second->plan_rtp->pt;
|
||||||
header->ssrc = htonl(pr->second->answer_ssrc_rtp);
|
header->ssrc = htonl(pr->second->answer_ssrc_rtp);
|
||||||
} else {
|
} else {
|
||||||
//重传的rtp, rtx
|
//重传的rtp, rtx
|
||||||
changeRtpExtId(pr->second, header, false, true);
|
changeRtpExtId(*pr->second, header, false, true);
|
||||||
header->pt = pr->second->plan_rtx->pt;
|
header->pt = pr->second->plan_rtx->pt;
|
||||||
if (pr->second->answer_ssrc_rtx) {
|
if (pr->second->answer_ssrc_rtx) {
|
||||||
//有rtx单独的ssrc,有些情况下,浏览器支持rtx,但是未指定rtx单独的ssrc
|
//有rtx单独的ssrc,有些情况下,浏览器支持rtx,但是未指定rtx单独的ssrc
|
||||||
|
@ -350,15 +350,17 @@ private:
|
|||||||
uint32_t answer_ssrc_rtx = 0;
|
uint32_t answer_ssrc_rtx = 0;
|
||||||
const RtcMedia *media;
|
const RtcMedia *media;
|
||||||
NackList nack_list;
|
NackList nack_list;
|
||||||
NackContext nack_ctx;
|
|
||||||
RtcpContext::Ptr rtcp_context_recv;
|
|
||||||
RtcpContext::Ptr rtcp_context_send;
|
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 onSortedRtp(RtpPayloadInfo &info, const string &rid, RtpPacket::Ptr rtp);
|
||||||
void onSendNack(RtpPayloadInfo &info, const FCI_NACK &nack);
|
void onSendNack(RtpPayloadInfo &info, const FCI_NACK &nack, uint32_t ssrc);
|
||||||
void changeRtpExtId(const RtpPayloadInfo *info, const RtpHeader *header, bool is_recv, bool is_rtx = false) const;
|
void changeRtpExtId(RtpPayloadInfo &info, const RtpHeader *header, bool is_recv, bool is_rtx = false, string *rid_ptr = nullptr) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint16_t _rtx_seq[2] = {0, 0};
|
uint16_t _rtx_seq[2] = {0, 0};
|
||||||
@ -378,6 +380,7 @@ private:
|
|||||||
Socket::Ptr _socket;
|
Socket::Ptr _socket;
|
||||||
//推流的rtsp源
|
//推流的rtsp源
|
||||||
RtspMediaSource::Ptr _push_src;
|
RtspMediaSource::Ptr _push_src;
|
||||||
|
unordered_map<string/*rid*/, RtspMediaSource::Ptr> _push_src_simulcast;
|
||||||
//播放的rtsp源
|
//播放的rtsp源
|
||||||
RtspMediaSource::Ptr _play_src;
|
RtspMediaSource::Ptr _play_src;
|
||||||
//播放rtsp源的reader对象
|
//播放rtsp源的reader对象
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# chrome的sdp
|
|
||||||
v=0
|
v=0
|
||||||
o=- 403371946498103831 2 IN IP4 127.0.0.1
|
o=- 1520777637155103417 2 IN IP4 127.0.0.1
|
||||||
s=-
|
s=-
|
||||||
t=0 0
|
t=0 0
|
||||||
a=group:BUNDLE 0 1
|
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
|
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
|
c=IN IP4 0.0.0.0
|
||||||
a=rtcp:9 IN IP4 0.0.0.0
|
a=rtcp:9 IN IP4 0.0.0.0
|
||||||
a=ice-ufrag:pW4Z
|
a=ice-ufrag:ma0v
|
||||||
a=ice-pwd:S38S++HW3eTcPTyytsNI1XVp
|
a=ice-pwd:H8+DMsvKNQE+Qz1uS7cZby1+
|
||||||
a=ice-options:trickle
|
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=setup:actpass
|
||||||
a=mid:0
|
a=mid:0
|
||||||
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
|
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:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
|
||||||
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
|
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
|
||||||
a=sendrecv
|
a=sendrecv
|
||||||
a=msid:- a3c6a137-1291-45cd-b985-07a9bd365452
|
a=msid:- 7bf9dab3-79e9-4969-b3d2-44beae8b4286
|
||||||
a=rtcp-mux
|
a=rtcp-mux
|
||||||
a=rtpmap:111 opus/48000/2
|
a=rtpmap:111 opus/48000/2
|
||||||
a=rtcp-fb:111 transport-cc
|
a=rtcp-fb:111 transport-cc
|
||||||
@ -39,17 +38,17 @@ a=rtpmap:110 telephone-event/48000
|
|||||||
a=rtpmap:112 telephone-event/32000
|
a=rtpmap:112 telephone-event/32000
|
||||||
a=rtpmap:113 telephone-event/16000
|
a=rtpmap:113 telephone-event/16000
|
||||||
a=rtpmap:126 telephone-event/8000
|
a=rtpmap:126 telephone-event/8000
|
||||||
a=ssrc:3626257331 cname:JSFJMbaE9Pu5tevN
|
a=ssrc:3454457472 cname:kbZgD5tgXGqTwvD1
|
||||||
a=ssrc:3626257331 msid:- a3c6a137-1291-45cd-b985-07a9bd365452
|
a=ssrc:3454457472 msid:- 7bf9dab3-79e9-4969-b3d2-44beae8b4286
|
||||||
a=ssrc:3626257331 mslabel:-
|
a=ssrc:3454457472 mslabel:-
|
||||||
a=ssrc:3626257331 label:a3c6a137-1291-45cd-b985-07a9bd365452
|
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
|
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
|
c=IN IP4 0.0.0.0
|
||||||
a=rtcp:9 IN IP4 0.0.0.0
|
a=rtcp:9 IN IP4 0.0.0.0
|
||||||
a=ice-ufrag:pW4Z
|
a=ice-ufrag:ma0v
|
||||||
a=ice-pwd:S38S++HW3eTcPTyytsNI1XVp
|
a=ice-pwd:H8+DMsvKNQE+Qz1uS7cZby1+
|
||||||
a=ice-options:trickle
|
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=setup:actpass
|
||||||
a=mid:1
|
a=mid:1
|
||||||
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
|
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:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
|
||||||
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
|
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
|
||||||
a=sendrecv
|
a=sendrecv
|
||||||
a=msid:- 261e5384-9cf6-479d-9d59-aaf924d1a2ea
|
a=msid:- fa2b9d11-98f5-4ea9-bf0a-6098069c6940
|
||||||
a=rtcp-mux
|
a=rtcp-mux
|
||||||
a=rtcp-rsize
|
a=rtcp-rsize
|
||||||
a=rtpmap:96 VP8/90000
|
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 ccm fir
|
||||||
a=rtcp-fb:124 nack
|
a=rtcp-fb:124 nack
|
||||||
a=rtcp-fb:124 nack pli
|
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=rtpmap:119 rtx/90000
|
||||||
a=fmtp:119 apt=124
|
a=fmtp:119 apt=124
|
||||||
a=rtpmap:123 H264/90000
|
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 ccm fir
|
||||||
a=rtcp-fb:123 nack
|
a=rtcp-fb:123 nack
|
||||||
a=rtcp-fb:123 nack pli
|
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=rtpmap:118 rtx/90000
|
||||||
a=fmtp:118 apt=123
|
a=fmtp:118 apt=123
|
||||||
a=rtpmap:114 red/90000
|
a=rtpmap:114 red/90000
|
||||||
@ -163,93 +162,3 @@ a=rid:q send
|
|||||||
a=rid:h send
|
a=rid:h send
|
||||||
a=rid:f send
|
a=rid:f send
|
||||||
a=simulcast:send q;h;f
|
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}
|
|
||||||
|
@ -31,7 +31,7 @@ ZLMediaKit的WebRTC相关功能目前仅供测试与开发,现在还不成熟
|
|||||||
|
|
||||||
- 1、完善webrtc rtcp相关功能,包括丢包重传、带宽检测等功能。
|
- 1、完善webrtc rtcp相关功能,包括丢包重传、带宽检测等功能。
|
||||||
- 2、实现rtp重传等相关功能。
|
- 2、实现rtp重传等相关功能。
|
||||||
- 3、实现simulecast相关功能。
|
- 3、实现simulcast相关功能。
|
||||||
- 4、fec、rtp扩展等其他功能。
|
- 4、fec、rtp扩展等其他功能。
|
||||||
- 5、如果精力允许,逐步替换MediaSoup相关代码,改用自有版权代码。
|
- 5、如果精力允许,逐步替换MediaSoup相关代码,改用自有版权代码。
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ var ZLMRTCClient = (function (exports) {
|
|||||||
const Events$1 = {
|
const Events$1 = {
|
||||||
WEBRTC_NOT_SUPPORT: 'WEBRTC_NOT_SUPPORT',
|
WEBRTC_NOT_SUPPORT: 'WEBRTC_NOT_SUPPORT',
|
||||||
WEBRTC_ICE_CANDIDATE_ERROR: 'WEBRTC_ICE_CANDIDATE_ERROR',
|
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_REMOTE_STREAMS: 'WEBRTC_ON_REMOTE_STREAMS',
|
||||||
WEBRTC_ON_LOCAL_STREAM: 'WEBRTC_ON_LOCAL_STREAM',
|
WEBRTC_ON_LOCAL_STREAM: 'WEBRTC_ON_LOCAL_STREAM',
|
||||||
CAPTURE_STREAM_FAILED: 'CAPTURE_STREAM_FAILED'
|
CAPTURE_STREAM_FAILED: 'CAPTURE_STREAM_FAILED'
|
||||||
@ -7285,7 +7285,7 @@ var ZLMRTCClient = (function (exports) {
|
|||||||
debug: false,
|
debug: false,
|
||||||
// if output debug log
|
// if output debug log
|
||||||
zlmsdpUrl: '',
|
zlmsdpUrl: '',
|
||||||
simulecast: false,
|
simulcast: false,
|
||||||
useCamera: true,
|
useCamera: true,
|
||||||
audioEnable: true,
|
audioEnable: true,
|
||||||
videoEnable: true,
|
videoEnable: true,
|
||||||
@ -7342,16 +7342,16 @@ var ZLMRTCClient = (function (exports) {
|
|||||||
let ret = response.data; //JSON.parse(response.data);
|
let ret = response.data; //JSON.parse(response.data);
|
||||||
|
|
||||||
if (ret.code != 0) {
|
if (ret.code != 0) {
|
||||||
// mean failed for offer/anwser exchange
|
// mean failed for offer/answer exchange
|
||||||
this.dispatch(Events$1.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, ret);
|
this.dispatch(Events$1.WEBRTC_OFFER_ANSWER_EXCHANGE_FAILED, ret);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let anwser = {};
|
let answer = {};
|
||||||
anwser.sdp = ret.sdp;
|
answer.sdp = ret.sdp;
|
||||||
anwser.type = 'answer';
|
answer.type = 'answer';
|
||||||
log(this.TAG, 'answer:', ret.sdp);
|
log(this.TAG, 'answer:', ret.sdp);
|
||||||
this.pc.setRemoteDescription(anwser).then(() => {
|
this.pc.setRemoteDescription(answer).then(() => {
|
||||||
log(this.TAG, 'set remote sucess');
|
log(this.TAG, 'set remote sucess');
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
error(this.TAG, e);
|
error(this.TAG, e);
|
||||||
@ -7398,7 +7398,7 @@ var ZLMRTCClient = (function (exports) {
|
|||||||
sendEncodings: []
|
sendEncodings: []
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.options.simulecast && stream.getVideoTracks().length > 0) {
|
if (this.options.simulcast && stream.getVideoTracks().length > 0) {
|
||||||
VideoTransceiverInit.sendEncodings = [{
|
VideoTransceiverInit.sendEncodings = [{
|
||||||
rid: 'q',
|
rid: 'q',
|
||||||
active: true,
|
active: true,
|
||||||
@ -7449,16 +7449,16 @@ var ZLMRTCClient = (function (exports) {
|
|||||||
let ret = response.data; //JSON.parse(response.data);
|
let ret = response.data; //JSON.parse(response.data);
|
||||||
|
|
||||||
if (ret.code != 0) {
|
if (ret.code != 0) {
|
||||||
// mean failed for offer/anwser exchange
|
// mean failed for offer/answer exchange
|
||||||
this.dispatch(Events$1.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, ret);
|
this.dispatch(Events$1.WEBRTC_OFFER_ANSWER_EXCHANGE_FAILED, ret);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let anwser = {};
|
let answer = {};
|
||||||
anwser.sdp = ret.sdp;
|
answer.sdp = ret.sdp;
|
||||||
anwser.type = 'answer';
|
answer.type = 'answer';
|
||||||
log(this.TAG, 'answer:', ret.sdp);
|
log(this.TAG, 'answer:', ret.sdp);
|
||||||
this.pc.setRemoteDescription(anwser).then(() => {
|
this.pc.setRemoteDescription(answer).then(() => {
|
||||||
log(this.TAG, 'set remote sucess');
|
log(this.TAG, 'set remote sucess');
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
error(this.TAG, e);
|
error(this.TAG, e);
|
||||||
|
File diff suppressed because one or more lines are too long
@ -26,8 +26,8 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<label for="simulecast">simulecast:</label>
|
<label for="simulcast">simulcast:</label>
|
||||||
<input type="checkbox" id='simulecast' checked="checked">
|
<input type="checkbox" id='simulcast' checked="checked">
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label for="useCamera">useCamera:</label>
|
<label for="useCamera">useCamera:</label>
|
||||||
@ -46,14 +46,14 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<label for="methond">methond(play or push):</label>
|
<label for="method">method(play or push):</label>
|
||||||
<input type="radio" name="methond" value="push" >push
|
<input type="radio" name="method" value="push" >push
|
||||||
<input type="radio" name="methond" value="play" checked = true>play
|
<input type="radio" name="method" value="play" checked = true>play
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<label for="resilution">resolution:</label>
|
<label for="resolution">resolution:</label>
|
||||||
<select id="resilution">
|
<select id="resolution">
|
||||||
|
|
||||||
</select>
|
</select>
|
||||||
</p>
|
</p>
|
||||||
@ -69,7 +69,7 @@
|
|||||||
var recvOnly = true
|
var recvOnly = true
|
||||||
var resArr = []
|
var resArr = []
|
||||||
|
|
||||||
document.getElementsByName("methond").forEach((el,idx)=>{
|
document.getElementsByName("method").forEach((el,idx)=>{
|
||||||
el.onclick=function(e){
|
el.onclick=function(e){
|
||||||
if(el.value == "play")
|
if(el.value == "play")
|
||||||
{
|
{
|
||||||
@ -91,14 +91,14 @@
|
|||||||
opt = document.createElement('option');
|
opt = document.createElement('option');
|
||||||
opt.text = r.label +"("+r.width+"x"+r.height+")";
|
opt.text = r.label +"("+r.width+"x"+r.height+")";
|
||||||
opt.value = r;
|
opt.value = r;
|
||||||
document.getElementById("resilution").add(opt,null)
|
document.getElementById("resolution").add(opt,null)
|
||||||
|
|
||||||
//console.log(opt.text.match(/\d+/g))
|
//console.log(opt.text.match(/\d+/g))
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
function start_play(){
|
function start_play(){
|
||||||
let elr = document.getElementById("resilution");
|
let elr = document.getElementById("resolution");
|
||||||
let res = elr.options[elr.selectedIndex].text.match(/\d+/g);
|
let res = elr.options[elr.selectedIndex].text.match(/\d+/g);
|
||||||
let h = parseInt(res.pop());
|
let h = parseInt(res.pop());
|
||||||
let w = parseInt(res.pop());
|
let w = parseInt(res.pop());
|
||||||
@ -108,7 +108,7 @@
|
|||||||
element: document.getElementById('video'),// video 标签
|
element: document.getElementById('video'),// video 标签
|
||||||
debug: true,// 是否打印日志
|
debug: true,// 是否打印日志
|
||||||
zlmsdpUrl:document.getElementById('streamUrl').value,//流地址
|
zlmsdpUrl:document.getElementById('streamUrl').value,//流地址
|
||||||
simulecast:false,//document.getElementById('simulecast').checked,
|
simulcast:document.getElementById('simulcast').checked,
|
||||||
useCamera:document.getElementById('useCamera').checked,
|
useCamera:document.getElementById('useCamera').checked,
|
||||||
audioEnable:document.getElementById('audioEnable').checked,
|
audioEnable:document.getElementById('audioEnable').checked,
|
||||||
videoEnable:document.getElementById('videoEnable').checked,
|
videoEnable:document.getElementById('videoEnable').checked,
|
||||||
@ -127,9 +127,9 @@
|
|||||||
console.log('播放成功',e.streams)
|
console.log('播放成功',e.streams)
|
||||||
});
|
});
|
||||||
|
|
||||||
player.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED,function(e)
|
player.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANSWER_EXCHANGE_FAILED,function(e)
|
||||||
{// offer anwser 交换失败
|
{// offer answer 交换失败
|
||||||
console.log('offer anwser 交换失败',e)
|
console.log('offer answer 交换失败',e)
|
||||||
stop();
|
stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -139,7 +139,7 @@
|
|||||||
document.getElementById('selfVideo').srcObject=s;
|
document.getElementById('selfVideo').srcObject=s;
|
||||||
document.getElementById('selfVideo').muted = true;
|
document.getElementById('selfVideo').muted = true;
|
||||||
|
|
||||||
//console.log('offer anwser 交换失败',e)
|
//console.log('offer answer 交换失败',e)
|
||||||
});
|
});
|
||||||
|
|
||||||
player.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED,function(s)
|
player.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED,function(s)
|
||||||
@ -152,7 +152,7 @@
|
|||||||
function start()
|
function start()
|
||||||
{
|
{
|
||||||
stop();
|
stop();
|
||||||
let elr = document.getElementById("resilution");
|
let elr = document.getElementById("resolution");
|
||||||
let res = elr.options[elr.selectedIndex].text.match(/\d+/g);
|
let res = elr.options[elr.selectedIndex].text.match(/\d+/g);
|
||||||
let h = parseInt(res.pop());
|
let h = parseInt(res.pop());
|
||||||
let w = parseInt(res.pop());
|
let w = parseInt(res.pop());
|
||||||
|
Loading…
Reference in New Issue
Block a user