This commit is contained in:
xgj 2021-06-24 11:53:15 +08:00
commit e9d28c1386
29 changed files with 800 additions and 263 deletions

8
.github/ISSUE_TEMPLATE/requirement.md vendored Normal file
View File

@ -0,0 +1,8 @@
---
name: issue创建要求
about: 不符合模板要求不便定位问题,可能会被管理员直接关闭
title: ""
labels: ''
assignees: ''
---

View File

@ -94,6 +94,8 @@ segNum=3
segRetain=5
#是否广播 ts 切片完成通知
broadcastRecordTs=0
#直播hls文件删除延时单位秒issue: #913
deleteDelaySec=0
[hook]
#在推流时如果url参数匹对admin_params那么可以不经过hook鉴权直接推流成功播放时亦然

View File

@ -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 \

View File

@ -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推流时推流方式0tcp1udp",
"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": {}
]
}

View File

@ -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"
@ -265,6 +266,10 @@ static inline void addHttpListener(){
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 recursive_mutex s_ffmpegMapMtx;
@ -279,6 +284,11 @@ static inline string getProxyKey(const string &vhost,const string &app,const str
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方式拉流

View File

@ -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

View File

@ -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代理相关配置///////////

View 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
View 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

View File

@ -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直播才删除文件
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;
});
}
}

View File

@ -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;
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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) {
if (!_tracks[track->getTrackType()]) {
_tracks[track->getTrackType()] = track;
_sink->addTrack(track);
InfoL << "got track: " << track->getCodecName();
}
}
void DecoderImp::onFrame(const Frame::Ptr &frame) {

View File

@ -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;
}

View File

@ -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

View File

@ -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());
}
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) {
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() {
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;
}
};

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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();
}

View File

@ -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);
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至统一
changeRtpExtId(info.get(), rtp, true, rtx);
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;
}
}
//解析并排序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);
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

View File

@ -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对象

View File

@ -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
@ -163,93 +162,3 @@ 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}

View File

@ -31,7 +31,7 @@ ZLMediaKit的WebRTC相关功能目前仅供测试与开发现在还不成熟
- 1、完善webrtc rtcp相关功能包括丢包重传、带宽检测等功能。
- 2、实现rtp重传等相关功能。
- 3、实现simulecast相关功能。
- 3、实现simulcast相关功能。
- 4、fec、rtp扩展等其他功能。
- 5、如果精力允许逐步替换MediaSoup相关代码改用自有版权代码。

View File

@ -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

View File

@ -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());