Merge branch 'ZLMediaKit:master' into master
This commit is contained in:
commit
6f834863ed
|
|
@ -1 +1 @@
|
|||
Subproject commit 12c2ec7b07dce1b6a0543d9979f5c5231f69e828
|
||||
Subproject commit ad44a16c99834540b397774ad6c7f3f8ed619d56
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 3dc623a899eee3810587fb267dbff770b626a55b
|
||||
Subproject commit a8a80e0738b052aa5671ef82a295ef388bd28e13
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
#include "mk_common.h"
|
||||
#include "mk_tcp.h"
|
||||
#include "mk_track.h"
|
||||
#include "mk_util.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
|
@ -317,6 +318,8 @@ API_EXPORT void API_CALL mk_publish_auth_invoker_do(const mk_publish_auth_invoke
|
|||
int enable_hls,
|
||||
int enable_mp4);
|
||||
|
||||
API_EXPORT void API_CALL mk_publish_auth_invoker_do2(const mk_publish_auth_invoker ctx, const char *err_msg, mk_ini option);
|
||||
|
||||
/**
|
||||
* 克隆mk_publish_auth_invoker对象,通过克隆对象为堆对象,可以实现跨线程异步执行mk_publish_auth_invoker_do
|
||||
* 如果是同步执行mk_publish_auth_invoker_do,那么没必要克隆对象
|
||||
|
|
|
|||
|
|
@ -461,6 +461,13 @@ API_EXPORT void API_CALL mk_publish_auth_invoker_do(const mk_publish_auth_invoke
|
|||
(*invoker)(err_msg ? err_msg : "", option);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_publish_auth_invoker_do2(const mk_publish_auth_invoker ctx, const char *err_msg, mk_ini ini) {
|
||||
assert(ctx);
|
||||
Broadcast::PublishAuthInvoker *invoker = (Broadcast::PublishAuthInvoker *)ctx;
|
||||
ProtocolOption option(ini ? *((mINI *)ini) : mINI{} );
|
||||
(*invoker)(err_msg ? err_msg : "", option);
|
||||
}
|
||||
|
||||
API_EXPORT mk_publish_auth_invoker API_CALL mk_publish_auth_invoker_clone(const mk_publish_auth_invoker ctx){
|
||||
assert(ctx);
|
||||
Broadcast::PublishAuthInvoker *invoker = (Broadcast::PublishAuthInvoker *)ctx;
|
||||
|
|
|
|||
18
dockerfile
18
dockerfile
|
|
@ -1,4 +1,4 @@
|
|||
FROM ubuntu:18.04 AS build
|
||||
FROM ubuntu:20.04 AS build
|
||||
ARG MODEL
|
||||
#shell,rtmp,rtsp,rtsps,http,https,rtp
|
||||
EXPOSE 1935/tcp
|
||||
|
|
@ -31,24 +31,31 @@ RUN apt-get update && \
|
|||
gcc \
|
||||
g++ \
|
||||
libavcodec-dev libavutil-dev libswscale-dev libresample-dev \
|
||||
libsdl-dev libusrsctp-dev \
|
||||
gdb && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean -y && \
|
||||
wget https://github.com/cisco/libsrtp/archive/v2.2.0.tar.gz -O libsrtp-2.2.0.tar.gz && tar xfv libsrtp-2.2.0.tar.gz && \
|
||||
cd libsrtp-2.2.0 && ./configure --enable-openssl && make -j $(nproc) && make install && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN mkdir -p /opt/media
|
||||
COPY . /opt/media/ZLMediaKit
|
||||
WORKDIR /opt/media/ZLMediaKit
|
||||
|
||||
# 3rdpart init
|
||||
WORKDIR /opt/media/ZLMediaKit/3rdpart
|
||||
RUN wget https://mirror.ghproxy.com/https://github.com/cisco/libsrtp/archive/v2.3.0.tar.gz -O libsrtp-2.3.0.tar.gz && \
|
||||
tar xfv libsrtp-2.3.0.tar.gz && \
|
||||
mv libsrtp-2.3.0 libsrtp && \
|
||||
cd libsrtp && ./configure --enable-openssl && make -j $(nproc) && make install
|
||||
#RUN git submodule update --init --recursive && \
|
||||
|
||||
RUN mkdir -p build release/linux/${MODEL}/
|
||||
|
||||
WORKDIR /opt/media/ZLMediaKit/build
|
||||
RUN cmake -DCMAKE_BUILD_TYPE=${MODEL} -DENABLE_WEBRTC=true -DENABLE_FFMPEG=true -DENABLE_TESTS=false -DENABLE_API=false .. && \
|
||||
make -j $(nproc)
|
||||
|
||||
FROM ubuntu:18.04
|
||||
FROM ubuntu:20.04
|
||||
ARG MODEL
|
||||
|
||||
# ADD sources.list /etc/apt/sources.list
|
||||
|
|
@ -68,6 +75,7 @@ RUN apt-get update && \
|
|||
gcc \
|
||||
g++ \
|
||||
libavcodec-dev libavutil-dev libswscale-dev libresample-dev \
|
||||
libsdl-dev libusrsctp-dev \
|
||||
gdb && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean -y && \
|
||||
|
|
|
|||
|
|
@ -2048,6 +2048,142 @@
|
|||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "点播mp4文件(loadMP4File)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/loadMP4File?secret={{ZLMediaKit_secret}}&vhost={{defaultVhost}}&app=live&stream=test&file_path=/path/to/mp4/file.mp4",
|
||||
"host": [
|
||||
"{{ZLMediaKit_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"index",
|
||||
"api",
|
||||
"loadMP4File"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
"value": "{{defaultVhost}}",
|
||||
"description": "添加的流的虚拟主机,例如__defaultVhost__"
|
||||
},
|
||||
{
|
||||
"key": "app",
|
||||
"value": "live",
|
||||
"description": "添加的流的应用名,例如live"
|
||||
},
|
||||
{
|
||||
"key": "stream",
|
||||
"value": "test",
|
||||
"description": "添加的流的id名,例如test"
|
||||
},
|
||||
{
|
||||
"key": "file_path",
|
||||
"value": "/path/to/mp4/file.mp4",
|
||||
"description": "mp4文件绝对路径"
|
||||
},
|
||||
{
|
||||
"key": "file_repeat",
|
||||
"value": "1",
|
||||
"description": "是否循环点播mp4文件,如果配置文件已经开启循环点播,此参数无效",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "enable_hls",
|
||||
"value": "",
|
||||
"description": "是否转hls-ts",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "enable_hls_fmp4",
|
||||
"value": "",
|
||||
"description": "是否转hls-fmp4",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "enable_mp4",
|
||||
"value": "",
|
||||
"description": "是否mp4录制,默认不开启(覆盖配置文件)",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "enable_rtsp",
|
||||
"value": "1",
|
||||
"description": "是否转协议为rtsp/webrtc",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "enable_rtmp",
|
||||
"value": "1",
|
||||
"description": "是否转协议为rtmp/flv",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "enable_ts",
|
||||
"value": "1",
|
||||
"description": "是否转协议为http-ts/ws-ts",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "enable_fmp4",
|
||||
"value": "1",
|
||||
"description": "是否转协议为http-fmp4/ws-fmp4",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "enable_audio",
|
||||
"value": "1",
|
||||
"description": "转协议是否开启音频",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "add_mute_audio",
|
||||
"value": "1",
|
||||
"description": "转协议无音频时,是否添加静音aac音频",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "mp4_save_path",
|
||||
"value": "",
|
||||
"description": "mp4录制保存根目录,置空使用默认目录",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "mp4_max_second",
|
||||
"value": "1800",
|
||||
"description": "mp4录制切片大小,单位秒",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "hls_save_path",
|
||||
"value": "",
|
||||
"description": "hls保存根目录,置空使用默认目录",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "modify_stamp",
|
||||
"value": "",
|
||||
"description": "是否修改原始时间戳,默认值2;取值范围:0.采用源视频流绝对时间戳,不做任何改变;1.采用zlmediakit接收数据时的系统时间戳(有平滑处理);2.采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "auto_close",
|
||||
"value": "",
|
||||
"description": "无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close);强制开启,此参数不生效",
|
||||
"disabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
],
|
||||
"event": [
|
||||
|
|
|
|||
|
|
@ -11,44 +11,52 @@
|
|||
#include <sys/stat.h>
|
||||
#include <math.h>
|
||||
#include <signal.h>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include "Util/util.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/onceToken.h"
|
||||
#include "Util/NoticeCenter.h"
|
||||
#include "Util/File.h"
|
||||
#ifdef ENABLE_MYSQL
|
||||
#include "Util/SqlPool.h"
|
||||
#endif //ENABLE_MYSQL
|
||||
#include "Common/config.h"
|
||||
#include "Common/MediaSource.h"
|
||||
#include "Http/HttpRequester.h"
|
||||
#include "Http/HttpSession.h"
|
||||
#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"
|
||||
#include "Thread/WorkThreadPool.h"
|
||||
#include "Rtp/RtpSelector.h"
|
||||
#include "FFmpegSource.h"
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
#include "Rtp/RtpServer.h"
|
||||
#endif
|
||||
#ifdef ENABLE_WEBRTC
|
||||
#include "../webrtc/WebRtcPlayer.h"
|
||||
#include "../webrtc/WebRtcPusher.h"
|
||||
#include "../webrtc/WebRtcEchoTest.h"
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <iostream>
|
||||
#include <tchar.h>
|
||||
#endif // _WIN32
|
||||
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include "Util/MD5.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/File.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/onceToken.h"
|
||||
#include "Util/NoticeCenter.h"
|
||||
#include "Network/TcpServer.h"
|
||||
#include "Network/UdpServer.h"
|
||||
#include "Thread/WorkThreadPool.h"
|
||||
|
||||
#ifdef ENABLE_MYSQL
|
||||
#include "Util/SqlPool.h"
|
||||
#endif //ENABLE_MYSQL
|
||||
|
||||
#include "WebApi.h"
|
||||
#include "WebHook.h"
|
||||
#include "FFmpegSource.h"
|
||||
|
||||
#include "Common/config.h"
|
||||
#include "Common/MediaSource.h"
|
||||
#include "Http/HttpSession.h"
|
||||
#include "Http/HttpRequester.h"
|
||||
#include "Player/PlayerProxy.h"
|
||||
#include "Pusher/PusherProxy.h"
|
||||
#include "Rtp/RtpSelector.h"
|
||||
#include "Record/MP4Reader.h"
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
#include "Rtp/RtpServer.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_WEBRTC
|
||||
#include "../webrtc/WebRtcPlayer.h"
|
||||
#include "../webrtc/WebRtcPusher.h"
|
||||
#include "../webrtc/WebRtcEchoTest.h"
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_VERSION)
|
||||
#include "version.h"
|
||||
#endif
|
||||
|
|
@ -1777,6 +1785,23 @@ void installWebApi() {
|
|||
});
|
||||
#endif
|
||||
|
||||
api_regist("/index/api/loadMP4File", [](API_ARGS_MAP) {
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("vhost", "app", "stream", "file_path");
|
||||
|
||||
ProtocolOption option;
|
||||
// 默认解复用mp4不生成mp4
|
||||
option.enable_mp4 = false;
|
||||
// 但是如果参数明确指定开启mp4, 那么也允许之
|
||||
option.load(allArgs);
|
||||
// 强制无人观看时自动关闭
|
||||
option.auto_close = true;
|
||||
|
||||
auto reader = std::make_shared<MP4Reader>(allArgs["vhost"], allArgs["app"], allArgs["stream"], allArgs["file_path"], option);
|
||||
// sample_ms设置为0,从配置文件加载;file_repeat可以指定,如果配置文件也指定循环解复用,那么强制开启
|
||||
reader->startReadMP4(0, true, allArgs["file_repeat"]);
|
||||
});
|
||||
|
||||
////////////以下是注册的Hook API////////////
|
||||
api_regist("/index/hook/on_publish",[](API_ARGS_JSON){
|
||||
//开始推流事件
|
||||
|
|
|
|||
|
|
@ -577,8 +577,8 @@ void installWebHook() {
|
|||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastStreamNoneReader, [](BroadcastStreamNoneReaderArgs) {
|
||||
if (!origin_urls.empty()) {
|
||||
// 边沿站无人观看时立即停止溯源
|
||||
if (!origin_urls.empty() && sender.getOriginType() == MediaOriginType::pull) {
|
||||
// 边沿站无人观看时如果是拉流的则立即停止溯源
|
||||
sender.close(false);
|
||||
WarnL << "无人观看主动关闭流:" << sender.getOriginUrl();
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "JemallocUtil.h"
|
||||
#include "Util/logger.h"
|
||||
#include <cstdint>
|
||||
#ifdef USE_JEMALLOC
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#define ZLMEDIAKIT_JEMALLOCUTIL_H
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
namespace mediakit {
|
||||
class JemallocUtil {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -202,6 +202,11 @@ public:
|
|||
|
||||
template <typename MAP>
|
||||
ProtocolOption(const MAP &allArgs) : ProtocolOption() {
|
||||
load(allArgs);
|
||||
}
|
||||
|
||||
template <typename MAP>
|
||||
void load(const MAP &allArgs) {
|
||||
#define GET_OPT_VALUE(key) getArgsValue(allArgs, #key, key)
|
||||
GET_OPT_VALUE(modify_stamp);
|
||||
GET_OPT_VALUE(enable_audio);
|
||||
|
|
|
|||
|
|
@ -525,6 +525,8 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
|
|||
ret = _fmp4->inputFrame(frame) ? true : ret;
|
||||
}
|
||||
if (_ring) {
|
||||
// 此场景由于直接转发,可能存在切换线程引起的数据被缓存在管道,所以需要CacheAbleFrame
|
||||
frame = Frame::getCacheAbleFrame(frame);
|
||||
if (frame->getTrackType() == TrackVideo) {
|
||||
// 视频时,遇到第一帧配置帧或关键帧则标记为gop开始处
|
||||
auto video_key_pos = frame->keyFrame() || frame->configFrame();
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
#include <cinttypes>
|
||||
#include "Parser.h"
|
||||
#include "strCoding.h"
|
||||
#include "macros.h"
|
||||
#include "Util/base64.h"
|
||||
#include "Network/sockutil.h"
|
||||
#include "Common/macros.h"
|
||||
|
||||
|
|
@ -325,6 +325,25 @@ void splitUrl(const std::string &url, std::string &host, uint16_t &port) {
|
|||
host = url.substr(0, pos);
|
||||
checkHost(host);
|
||||
}
|
||||
|
||||
void parseProxyUrl(const std::string &proxy_url, std::string &proxy_host, uint16_t &proxy_port, std::string &proxy_auth) {
|
||||
// 判断是否包含http://, 如果是则去掉
|
||||
std::string host;
|
||||
auto pos = proxy_url.find("://");
|
||||
if (pos != string::npos) {
|
||||
host = proxy_url.substr(pos + 3);
|
||||
} else {
|
||||
host = proxy_url;
|
||||
}
|
||||
// 判断是否包含用户名和密码
|
||||
pos = host.rfind('@');
|
||||
if (pos != string::npos) {
|
||||
proxy_auth = encodeBase64(host.substr(0, pos));
|
||||
host = host.substr(pos + 1, host.size());
|
||||
}
|
||||
splitUrl(host, proxy_host, proxy_port);
|
||||
}
|
||||
|
||||
#if 0
|
||||
//测试代码
|
||||
static onceToken token([](){
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ namespace mediakit {
|
|||
std::string findSubString(const char *buf, const char *start, const char *end, size_t buf_size = 0);
|
||||
// 把url解析为主机地址和端口号,兼容ipv4/ipv6/dns
|
||||
void splitUrl(const std::string &url, std::string &host, uint16_t &port);
|
||||
// 解析proxy url,仅支持http
|
||||
void parseProxyUrl(const std::string &proxy_url, std::string &proxy_host, uint16_t &proxy_port, std::string &proxy_auth);
|
||||
|
||||
struct StrCaseCompare {
|
||||
bool operator()(const std::string &__x, const std::string &__y) const { return strcasecmp(__x.data(), __y.data()) < 0; }
|
||||
|
|
|
|||
|
|
@ -20,8 +20,13 @@ using namespace toolkit;
|
|||
|
||||
namespace mediakit {
|
||||
|
||||
int64_t DeltaStamp::relativeStamp(int64_t stamp) {
|
||||
_relative_stamp += deltaStamp(stamp);
|
||||
DeltaStamp::DeltaStamp() {
|
||||
// 时间戳最大允许跳跃300ms
|
||||
_max_delta = 300;
|
||||
}
|
||||
|
||||
int64_t DeltaStamp::relativeStamp(int64_t stamp, bool enable_rollback) {
|
||||
_relative_stamp += deltaStamp(stamp, enable_rollback);
|
||||
return _relative_stamp;
|
||||
}
|
||||
|
||||
|
|
@ -29,7 +34,7 @@ int64_t DeltaStamp::relativeStamp() {
|
|||
return _relative_stamp;
|
||||
}
|
||||
|
||||
int64_t DeltaStamp::deltaStamp(int64_t stamp) {
|
||||
int64_t DeltaStamp::deltaStamp(int64_t stamp, bool enable_rollback) {
|
||||
if (!_last_stamp) {
|
||||
// 第一次计算时间戳增量,时间戳增量为0
|
||||
if (stamp) {
|
||||
|
|
@ -43,14 +48,25 @@ int64_t DeltaStamp::deltaStamp(int64_t stamp) {
|
|||
// 时间戳增量为正,返回之
|
||||
_last_stamp = stamp;
|
||||
// 在直播情况下,时间戳增量不得大于MAX_DELTA_STAMP,否则强制相对时间戳加1
|
||||
return ret < MAX_DELTA_STAMP ? ret : 1;
|
||||
if (ret > _max_delta) {
|
||||
needSync();
|
||||
return 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 时间戳增量为负,说明时间戳回环了或回退了
|
||||
_last_stamp = stamp;
|
||||
if (!enable_rollback || -ret > _max_delta) {
|
||||
// 不允许回退或者回退太多了, 强制时间戳加1
|
||||
needSync();
|
||||
return 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 如果时间戳回退不多,那么返回负值,否则返回加1
|
||||
return -ret < MAX_DELTA_STAMP ? ret : 1;
|
||||
void DeltaStamp::setMaxDelta(size_t max_delta) {
|
||||
_max_delta = max_delta;
|
||||
}
|
||||
|
||||
void Stamp::setPlayBack(bool playback) {
|
||||
|
|
@ -58,9 +74,18 @@ void Stamp::setPlayBack(bool playback) {
|
|||
}
|
||||
|
||||
void Stamp::syncTo(Stamp &other) {
|
||||
_need_sync = true;
|
||||
_sync_master = &other;
|
||||
}
|
||||
|
||||
void Stamp::needSync() {
|
||||
_need_sync = true;
|
||||
}
|
||||
|
||||
void Stamp::enableRollback(bool flag) {
|
||||
_enable_rollback = flag;
|
||||
}
|
||||
|
||||
// 限制dts回退
|
||||
void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) {
|
||||
revise_l(dts, pts, dts_out, pts_out, modifyStamp);
|
||||
|
|
@ -87,15 +112,26 @@ void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_ou
|
|||
return;
|
||||
}
|
||||
|
||||
if (_sync_master && _sync_master->_last_dts_in) {
|
||||
// 需要同步时间戳
|
||||
if (_sync_master && _sync_master->_last_dts_in && (_need_sync || _sync_master->_need_sync)) {
|
||||
// 音视频dts当前时间差
|
||||
int64_t dts_diff = _last_dts_in - _sync_master->_last_dts_in;
|
||||
if (ABS(dts_diff) < 5000) {
|
||||
// 如果绝对时间戳小于5秒,那么说明他们的起始时间戳是一致的,那么强制同步
|
||||
_relative_stamp = _sync_master->_relative_stamp + dts_diff;
|
||||
auto target_stamp = _sync_master->_relative_stamp + dts_diff;
|
||||
if (target_stamp > _relative_stamp || _enable_rollback) {
|
||||
// 强制同步后,时间戳增加跳跃了,或允许回退
|
||||
TraceL << "Relative stamp changed: " << _relative_stamp << " -> " << target_stamp;
|
||||
_relative_stamp = target_stamp;
|
||||
} else {
|
||||
// 不允许回退, 则让另外一个Track的时间戳增长
|
||||
target_stamp = _relative_stamp - dts_diff;
|
||||
TraceL << "Relative stamp changed: " << _sync_master->_relative_stamp << " -> " << target_stamp;
|
||||
_sync_master->_relative_stamp = target_stamp;
|
||||
}
|
||||
}
|
||||
// 下次不用再强制同步
|
||||
_sync_master = nullptr;
|
||||
_need_sync = false;
|
||||
_sync_master->_need_sync = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +160,7 @@ void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_o
|
|||
// 内部自己生产时间戳
|
||||
_relative_stamp = _ticker.elapsedTime();
|
||||
} else {
|
||||
_relative_stamp += deltaStamp(dts);
|
||||
_relative_stamp += deltaStamp(dts, _enable_rollback);
|
||||
}
|
||||
_last_dts_in = dts;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,19 +19,27 @@ namespace mediakit {
|
|||
|
||||
class DeltaStamp{
|
||||
public:
|
||||
DeltaStamp() = default;
|
||||
DeltaStamp();
|
||||
~DeltaStamp() = default;
|
||||
|
||||
/**
|
||||
* 计算时间戳增量
|
||||
* @param stamp 绝对时间戳
|
||||
* @param enable_rollback 是否允许相当时间戳回退
|
||||
* @return 时间戳增量
|
||||
*/
|
||||
int64_t deltaStamp(int64_t stamp);
|
||||
int64_t relativeStamp(int64_t stamp);
|
||||
int64_t deltaStamp(int64_t stamp, bool enable_rollback = true);
|
||||
int64_t relativeStamp(int64_t stamp, bool enable_rollback = true);
|
||||
int64_t relativeStamp();
|
||||
|
||||
private:
|
||||
// 设置最大允许回退或跳跃幅度
|
||||
void setMaxDelta(size_t max_delta);
|
||||
|
||||
protected:
|
||||
virtual void needSync() {}
|
||||
|
||||
protected:
|
||||
int _max_delta;
|
||||
int64_t _last_stamp = 0;
|
||||
int64_t _relative_stamp = 0;
|
||||
};
|
||||
|
|
@ -77,6 +85,11 @@ public:
|
|||
*/
|
||||
void syncTo(Stamp &other);
|
||||
|
||||
/**
|
||||
* 是否允许时间戳回退
|
||||
*/
|
||||
void enableRollback(bool flag);
|
||||
|
||||
private:
|
||||
//主要实现音视频时间戳同步功能
|
||||
void revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp = false);
|
||||
|
|
@ -84,13 +97,18 @@ private:
|
|||
//主要实现获取相对时间戳功能
|
||||
void revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp = false);
|
||||
|
||||
void needSync() override;
|
||||
|
||||
private:
|
||||
bool _playback = false;
|
||||
bool _need_sync = false;
|
||||
// 默认不允许时间戳回滚
|
||||
bool _enable_rollback = false;
|
||||
int64_t _relative_stamp = 0;
|
||||
int64_t _last_dts_in = 0;
|
||||
int64_t _last_dts_out = 0;
|
||||
int64_t _last_pts_out = 0;
|
||||
toolkit::SmoothTicker _ticker;
|
||||
bool _playback = false;
|
||||
Stamp *_sync_master = nullptr;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -353,6 +353,7 @@ const string kBeatIntervalMS = "beat_interval_ms";
|
|||
const string kBenchmarkMode = "benchmark_mode";
|
||||
const string kWaitTrackReady = "wait_track_ready";
|
||||
const string kPlayTrack = "play_track";
|
||||
const string kProxyUrl = "proxy_url";
|
||||
} // namespace Client
|
||||
|
||||
} // namespace mediakit
|
||||
|
|
|
|||
|
|
@ -403,6 +403,8 @@ extern const std::string kWaitTrackReady;
|
|||
// rtsp播放指定track,可选项有0(不指定,默认)、1(视频)、2(音频)
|
||||
// 设置方法:player[Client::kPlayTrack] = 0/1/2;
|
||||
extern const std::string kPlayTrack;
|
||||
//设置代理url,目前只支持http协议
|
||||
extern const std::string kProxyUrl;
|
||||
} // namespace Client
|
||||
} // namespace mediakit
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ HlsPlayer::HlsPlayer(const EventPoller::Ptr &poller) {
|
|||
void HlsPlayer::play(const string &url) {
|
||||
_play_result = false;
|
||||
_play_url = url;
|
||||
setProxyUrl((*this)[Client::kProxyUrl]);
|
||||
fetchIndexFile();
|
||||
}
|
||||
|
||||
|
|
@ -88,6 +89,7 @@ void HlsPlayer::fetchSegment() {
|
|||
weak_ptr<HlsPlayer> weak_self = static_pointer_cast<HlsPlayer>(shared_from_this());
|
||||
if (!_http_ts_player) {
|
||||
_http_ts_player = std::make_shared<HttpTSPlayer>(getPoller());
|
||||
_http_ts_player->setProxyUrl((*this)[Client::kProxyUrl]);
|
||||
_http_ts_player->setOnCreateSocket([weak_self](const EventPoller::Ptr &poller) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (strong_self) {
|
||||
|
|
|
|||
|
|
@ -78,12 +78,15 @@ void HttpClient::sendRequest(const string &url) {
|
|||
printer.pop_back();
|
||||
_header.emplace("Cookie", printer);
|
||||
}
|
||||
|
||||
if (!alive() || host_changed) {
|
||||
startConnect(host, port, _wait_header_ms / 1000.0f);
|
||||
if (isUsedProxy()) {
|
||||
startConnect(_proxy_host, _proxy_port, _wait_header_ms / 1000.0f);
|
||||
} else {
|
||||
SockException ex;
|
||||
onConnect_l(ex);
|
||||
if (!alive() || host_changed) {
|
||||
startConnect(host, port, _wait_header_ms / 1000.0f);
|
||||
} else {
|
||||
SockException ex;
|
||||
onConnect_l(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,15 +161,23 @@ void HttpClient::onConnect_l(const SockException &ex) {
|
|||
onResponseCompleted_l(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
_StrPrinter printer;
|
||||
printer << _method + " " << _path + " HTTP/1.1\r\n";
|
||||
for (auto &pr : _header) {
|
||||
printer << pr.first + ": ";
|
||||
printer << pr.second + "\r\n";
|
||||
//不使用代理或者代理服务器已经连接成功
|
||||
if (_proxy_connected || !isUsedProxy()) {
|
||||
printer << _method + " " << _path + " HTTP/1.1\r\n";
|
||||
for (auto &pr : _header) {
|
||||
printer << pr.first + ": ";
|
||||
printer << pr.second + "\r\n";
|
||||
}
|
||||
_header.clear();
|
||||
_path.clear();
|
||||
} else {
|
||||
printer << "CONNECT " << _last_host << " HTTP/1.1\r\n";
|
||||
printer << "Proxy-Connection: keep-alive\r\n";
|
||||
if (!_proxy_auth.empty()) {
|
||||
printer << "Proxy-Authorization: Basic " << _proxy_auth << "\r\n";
|
||||
}
|
||||
}
|
||||
_header.clear();
|
||||
_path.clear();
|
||||
SockSender::send(printer << "\r\n");
|
||||
onFlush();
|
||||
}
|
||||
|
|
@ -401,4 +412,28 @@ void HttpClient::setCompleteTimeout(size_t timeout_ms) {
|
|||
_wait_complete_ms = timeout_ms;
|
||||
}
|
||||
|
||||
bool HttpClient::isUsedProxy() const {
|
||||
return _used_proxy;
|
||||
}
|
||||
|
||||
bool HttpClient::isProxyConnected() const {
|
||||
return _proxy_connected;
|
||||
}
|
||||
|
||||
void HttpClient::setProxyUrl(string proxy_url) {
|
||||
_proxy_url = std::move(proxy_url);
|
||||
if (!_proxy_url.empty()) {
|
||||
parseProxyUrl(_proxy_url, _proxy_host, _proxy_port, _proxy_auth);
|
||||
_used_proxy = true;
|
||||
} else {
|
||||
_used_proxy = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpClient::checkProxyConnected(const char *data, size_t len) {
|
||||
auto ret = strstr(data, "HTTP/1.1 200 Connection established");
|
||||
_proxy_connected = ret != nullptr;
|
||||
return _proxy_connected;
|
||||
}
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
|
|
|||
|
|
@ -141,6 +141,11 @@ public:
|
|||
*/
|
||||
void setCompleteTimeout(size_t timeout_ms);
|
||||
|
||||
/**
|
||||
* 设置http代理url
|
||||
*/
|
||||
void setProxyUrl(std::string proxy_url);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* 收到http回复头
|
||||
|
|
@ -181,11 +186,16 @@ protected:
|
|||
void onFlush() override;
|
||||
void onManager() override;
|
||||
|
||||
void clearResponse();
|
||||
|
||||
bool checkProxyConnected(const char *data, size_t len);
|
||||
bool isUsedProxy() const;
|
||||
bool isProxyConnected() const;
|
||||
|
||||
private:
|
||||
void onResponseCompleted_l(const toolkit::SockException &ex);
|
||||
void onConnect_l(const toolkit::SockException &ex);
|
||||
void checkCookie(HttpHeader &headers);
|
||||
void clearResponse();
|
||||
|
||||
private:
|
||||
//for http response
|
||||
|
|
@ -215,6 +225,13 @@ private:
|
|||
toolkit::Ticker _wait_header;
|
||||
toolkit::Ticker _wait_body;
|
||||
toolkit::Ticker _wait_complete;
|
||||
|
||||
bool _used_proxy = false;
|
||||
bool _proxy_connected = false;
|
||||
uint16_t _proxy_port;
|
||||
std::string _proxy_url;
|
||||
std::string _proxy_host;
|
||||
std::string _proxy_auth;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
|
|
|||
|
|
@ -15,13 +15,30 @@ using namespace toolkit;
|
|||
namespace mediakit {
|
||||
|
||||
void HttpClientImp::onConnect(const SockException &ex) {
|
||||
if (!isHttps()) {
|
||||
//https 302跳转 http时,需要关闭ssl
|
||||
if (isUsedProxy() && !isProxyConnected()) {
|
||||
// 连接代理服务器
|
||||
setDoNotUseSSL();
|
||||
HttpClient::onConnect(ex);
|
||||
} else {
|
||||
TcpClientWithSSL<HttpClient>::onConnect(ex);
|
||||
if (!isHttps()) {
|
||||
// https 302跳转 http时,需要关闭ssl
|
||||
setDoNotUseSSL();
|
||||
HttpClient::onConnect(ex);
|
||||
} else {
|
||||
TcpClientWithSSL<HttpClient>::onConnect(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t HttpClientImp::onRecvHeader(const char *data, size_t len) {
|
||||
if (isUsedProxy() && !isProxyConnected()) {
|
||||
if (checkProxyConnected(data, len)) {
|
||||
clearResponse();
|
||||
onConnect(SockException(Err_success, "proxy connected"));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return HttpClient::onRecvHeader(data, len);
|
||||
}
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ public:
|
|||
|
||||
protected:
|
||||
void onConnect(const toolkit::SockException &ex) override;
|
||||
ssize_t onRecvHeader(const char *data, size_t len) override;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ void TsPlayer::play(const string &url) {
|
|||
TraceL << "play http-ts: " << url;
|
||||
_play_result = false;
|
||||
_benchmark_mode = (*this)[Client::kBenchmarkMode].as<int>();
|
||||
setProxyUrl((*this)[Client::kProxyUrl]);
|
||||
setHeaderTimeout((*this)[Client::kTimeoutMS].as<int>());
|
||||
setBodyTimeout((*this)[Client::kMediaTimeoutMS].as<int>());
|
||||
setMethod("GET");
|
||||
|
|
|
|||
|
|
@ -21,6 +21,20 @@ using namespace toolkit;
|
|||
namespace mediakit {
|
||||
|
||||
MP4Reader::MP4Reader(const std::string &vhost, const std::string &app, const std::string &stream_id, const string &file_path) {
|
||||
ProtocolOption option;
|
||||
// 读取mp4文件并流化时,不重复生成mp4/hls文件
|
||||
option.enable_mp4 = false;
|
||||
option.enable_hls = false;
|
||||
option.enable_hls_fmp4 = false;
|
||||
|
||||
setup(vhost, app, stream_id, file_path, option);
|
||||
}
|
||||
|
||||
MP4Reader::MP4Reader(const std::string &vhost, const std::string &app, const std::string &stream_id, const string &file_path, const ProtocolOption &option) {
|
||||
setup(vhost, app, stream_id, file_path, option);
|
||||
}
|
||||
|
||||
void MP4Reader::setup(const std::string &vhost, const std::string &app, const std::string &stream_id, const std::string &file_path, const ProtocolOption &option) {
|
||||
//读写文件建议放在后台线程
|
||||
auto tuple = MediaTuple{vhost, app, stream_id};
|
||||
_poller = WorkThreadPool::Instance().getPoller();
|
||||
|
|
@ -42,10 +56,7 @@ MP4Reader::MP4Reader(const std::string &vhost, const std::string &app, const std
|
|||
if (tuple.stream.empty()) {
|
||||
return;
|
||||
}
|
||||
ProtocolOption option;
|
||||
//读取mp4文件并流化时,不重复生成mp4/hls文件
|
||||
option.enable_mp4 = false;
|
||||
option.enable_hls = false;
|
||||
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(tuple, _demuxer->getDurationMS() / 1000.0f, option);
|
||||
auto tracks = _demuxer->getTracks(false);
|
||||
if (tracks.empty()) {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,12 @@ public:
|
|||
* @param stream_id 流id,置空时,只解复用mp4,但是不生成MediaSource
|
||||
* @param file_path 文件路径,如果为空则根据配置文件和上面参数自动生成,否则使用指定的文件
|
||||
*/
|
||||
MP4Reader(const std::string &vhost, const std::string &app, const std::string &stream_id, const std::string &file_path = "");
|
||||
MP4Reader(const std::string &vhost, const std::string &app, const std::string &stream_id,
|
||||
const std::string &file_path = "");
|
||||
|
||||
MP4Reader(const std::string &vhost, const std::string &app, const std::string &stream_id,
|
||||
const std::string &file_path, const ProtocolOption &option);
|
||||
|
||||
~MP4Reader() override = default;
|
||||
|
||||
/**
|
||||
|
|
@ -66,6 +71,8 @@ private:
|
|||
void setCurrentStamp(uint32_t stamp);
|
||||
bool seekTo(uint32_t stamp_seek);
|
||||
|
||||
void setup(const std::string &vhost, const std::string &app, const std::string &stream_id, const std::string &file_path, const ProtocolOption &option);
|
||||
|
||||
private:
|
||||
bool _file_repeat = false;
|
||||
bool _have_video = false;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ FlvPlayer::FlvPlayer(const EventPoller::Ptr &poller) {
|
|||
void FlvPlayer::play(const string &url) {
|
||||
TraceL << "play http-flv: " << url;
|
||||
_play_result = false;
|
||||
setProxyUrl((*this)[Client::kProxyUrl]);
|
||||
setHeaderTimeout((*this)[Client::kTimeoutMS].as<int>());
|
||||
setBodyTimeout((*this)[Client::kMediaTimeoutMS].as<int>());
|
||||
setMethod("GET");
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ void RtmpPlayer::play(const string &url) {
|
|||
}
|
||||
DebugL << host_url << " " << _app << " " << _stream_id;
|
||||
|
||||
uint16_t port = 1935;
|
||||
uint16_t port = start_with(url, "rtmps") ? 443 : 1935;
|
||||
splitUrl(host_url, host_url, port);
|
||||
|
||||
if (!(*this)[Client::kNetAdapter].empty()) {
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ void RtmpPusher::publish(const string &url) {
|
|||
}
|
||||
DebugL << host_url << " " << _app << " " << _stream_id;
|
||||
|
||||
uint16_t port = 1935;
|
||||
uint16_t port = start_with(url, "rtmps") ? 443 : 1935;
|
||||
splitUrl(host_url, host_url, port);
|
||||
|
||||
weak_ptr<RtmpPusher> weakSelf = static_pointer_cast<RtmpPusher>(shared_from_this());
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ bool RtpProcess::inputRtp(bool is_udp, const Socket::Ptr &sock, const char *data
|
|||
WarnP(this) << "Not rtp packet";
|
||||
return false;
|
||||
}
|
||||
if (!_auth_err.empty()) {
|
||||
throw toolkit::SockException(toolkit::Err_other, _auth_err);
|
||||
}
|
||||
if (_sock != sock) {
|
||||
// 第一次运行本函数
|
||||
bool first = !_sock;
|
||||
|
|
@ -260,6 +263,7 @@ void RtpProcess::emitOnPublish() {
|
|||
strong_self->doCachedFunc();
|
||||
InfoP(strong_self) << "允许RTP推流";
|
||||
} else {
|
||||
strong_self->_auth_err = err;
|
||||
WarnP(strong_self) << "禁止RTP推流:" << err;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ private:
|
|||
|
||||
private:
|
||||
bool _only_audio = false;
|
||||
std::string _auth_err;
|
||||
uint64_t _dts = 0;
|
||||
uint64_t _total_bytes = 0;
|
||||
std::unique_ptr<sockaddr_storage> _addr;
|
||||
|
|
|
|||
|
|
@ -173,15 +173,8 @@ void RtpSender::createRtcpSocket() {
|
|||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_storage addr;
|
||||
//目标rtp端口
|
||||
SockUtil::get_sock_peer_addr(_socket_rtp->rawFD(), addr);
|
||||
//绑定目标rtcp端口(目标rtp端口 + 1)
|
||||
switch (addr.ss_family) {
|
||||
case AF_INET: ((sockaddr_in *)&addr)->sin_port = htons(ntohs(((sockaddr_in *)&addr)->sin_port) + 1); break;
|
||||
case AF_INET6: ((sockaddr_in6 *)&addr)->sin6_port = htons(ntohs(((sockaddr_in6 *)&addr)->sin6_port) + 1); break;
|
||||
default: assert(0); break;
|
||||
}
|
||||
// 绑定目标rtcp端口(目标rtp端口 + 1)
|
||||
auto addr = SockUtil::make_sockaddr(_socket_rtp->get_peer_ip().data(), _socket_rtp->get_peer_port() + 1);
|
||||
_socket_rtcp->bindPeerAddr((struct sockaddr *)&addr, 0, true);
|
||||
|
||||
_rtcp_context = std::make_shared<RtcpContextForSend>();
|
||||
|
|
|
|||
|
|
@ -139,8 +139,15 @@ void RtpSession::onRtpPacket(const char *data, size_t len) {
|
|||
} else {
|
||||
throw;
|
||||
}
|
||||
} catch (...) {
|
||||
throw;
|
||||
} catch (std::exception &ex) {
|
||||
if (!_is_udp) {
|
||||
// tcp情况下立即断开连接
|
||||
throw;
|
||||
}
|
||||
// udp情况下延时断开连接(等待超时自动关闭),防止频繁创建销毁RtpSession对象
|
||||
WarnP(this) << ex.what();
|
||||
_delay_close = true;
|
||||
return;
|
||||
}
|
||||
_ticker.resetTime();
|
||||
}
|
||||
|
|
@ -164,8 +171,23 @@ static const char *findSSRC(const char *data, ssize_t len, uint32_t ssrc) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
static const char *findPsHeaderFlag(const char *data, ssize_t len) {
|
||||
for (ssize_t i = 2; i <= len - 4; ++i) {
|
||||
auto ptr = (const uint8_t *) data + i;
|
||||
//PsHeader 0x000001ba、PsSystemHeader0x000001bb(关键帧标识)
|
||||
if (ptr[0] == (0x00) && ptr[1] == (0x00) && ptr[2] == (0x01) && ptr[3] == (0xbb)) {
|
||||
return (const char *) ptr;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//rtp长度到ssrc间的长度固定为10
|
||||
static size_t constexpr kSSRCOffset = 2 + 4 + 4;
|
||||
// rtp长度到ps header间的长度固定为14 (暂时不采用找ps header,采用找system header代替)
|
||||
// rtp长度到ps system header间的长度固定为20 (关键帧标识)
|
||||
static size_t constexpr kPSHeaderOffset = 2 + 4 + 4 + 4 + 20;
|
||||
|
||||
const char *RtpSession::onSearchPacketTail(const char *data, size_t len) {
|
||||
if (!_search_rtp) {
|
||||
|
|
@ -173,12 +195,26 @@ const char *RtpSession::onSearchPacketTail(const char *data, size_t len) {
|
|||
return RtpSplitter::onSearchPacketTail(data, len);
|
||||
}
|
||||
if (!_process) {
|
||||
throw SockException(Err_shutdown, "ssrc未获取到,无法通过ssrc恢复tcp上下文");
|
||||
InfoL << "ssrc未获取到,无法通过ssrc恢复tcp上下文;尝试搜索PsSystemHeader恢复tcp上下文。";
|
||||
auto rtp_ptr1 = searchByPsHeaderFlag(data,len);
|
||||
return rtp_ptr1;
|
||||
}
|
||||
auto rtp_ptr0 = searchBySSRC(data,len);
|
||||
if(rtp_ptr0) {
|
||||
return rtp_ptr0;
|
||||
}
|
||||
// ssrc搜索失败继续尝试搜索ps header flag
|
||||
auto rtp_ptr2 = searchByPsHeaderFlag(data,len);
|
||||
return rtp_ptr2;
|
||||
}
|
||||
|
||||
const char *RtpSession::searchBySSRC(const char *data, size_t len) {
|
||||
InfoL << "尝试rtp搜索ssrc..._ssrc=" << _ssrc;
|
||||
//搜索第一个rtp的ssrc
|
||||
auto ssrc_ptr0 = findSSRC(data, len, _ssrc);
|
||||
if (!ssrc_ptr0) {
|
||||
//未搜索到任意rtp,返回数据不够
|
||||
InfoL << "rtp搜索ssrc失败(第一个数据不够),丢弃rtp数据为:" << len;
|
||||
return nullptr;
|
||||
}
|
||||
//这两个字节是第一个rtp的长度字段
|
||||
|
|
@ -189,13 +225,14 @@ const char *RtpSession::onSearchPacketTail(const char *data, size_t len) {
|
|||
auto ssrc_ptr1 = findSSRC(ssrc_ptr0 + rtp_len, data + (ssize_t) len - ssrc_ptr0 - rtp_len, _ssrc);
|
||||
if (!ssrc_ptr1) {
|
||||
//未搜索到第二个rtp,返回数据不够
|
||||
InfoL << "rtp搜索ssrc失败(第二个数据不够),丢弃rtp数据为:" << len;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//两个ssrc的间隔正好等于rtp的长度(外加rtp长度字段),那么说明找到rtp
|
||||
auto ssrc_offset = ssrc_ptr1 - ssrc_ptr0;
|
||||
if (ssrc_offset == rtp_len + 2 || ssrc_offset == rtp_len + 4) {
|
||||
InfoL << "rtp搜索成功,tcp上下文恢复成功,丢弃的rtp残余数据为:" << rtp_len_ptr - data;
|
||||
InfoL << "rtp搜索ssrc成功,tcp上下文恢复成功,丢弃的rtp残余数据为:" << rtp_len_ptr - data;
|
||||
_search_rtp_finished = true;
|
||||
if (rtp_len_ptr == data) {
|
||||
//停止搜索rtp,否则会进入死循环
|
||||
|
|
@ -208,5 +245,32 @@ const char *RtpSession::onSearchPacketTail(const char *data, size_t len) {
|
|||
return ssrc_ptr1 - kSSRCOffset;
|
||||
}
|
||||
|
||||
const char *RtpSession::searchByPsHeaderFlag(const char *data, size_t len) {
|
||||
InfoL << "尝试rtp搜索PsSystemHeaderFlag..._ssrc=" << _ssrc;
|
||||
// 搜索rtp中的第一个PsHeaderFlag
|
||||
auto ps_header_flag_ptr = findPsHeaderFlag(data,len);
|
||||
if (!ps_header_flag_ptr) {
|
||||
InfoL << "rtp搜索flag失败,丢弃rtp数据为:" << len;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
auto rtp_ptr = ps_header_flag_ptr - kPSHeaderOffset;
|
||||
_search_rtp_finished = true;
|
||||
if (rtp_ptr == data) {
|
||||
//停止搜索rtp,否则会进入死循环
|
||||
_search_rtp = false;
|
||||
}
|
||||
InfoL << "rtp搜索flag成功,tcp上下文恢复成功,丢弃的rtp残余数据为:" << rtp_ptr - data;
|
||||
|
||||
// TODO or Not ? 更新设置ssrc
|
||||
uint32_t rtp_ssrc = 0;
|
||||
RtpSelector::getSSRC(rtp_ptr+2, len, rtp_ssrc);
|
||||
_ssrc = rtp_ssrc;
|
||||
InfoL << "设置_ssrc为:" << _ssrc;
|
||||
// RtpServer::updateSSRC(uint32_t ssrc)
|
||||
return rtp_ptr;
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
#endif//defined(ENABLE_RTPPROXY)
|
||||
|
|
@ -41,6 +41,10 @@ protected:
|
|||
void onRtpPacket(const char *data, size_t len) override;
|
||||
// RtpSplitter override
|
||||
const char *onSearchPacketTail(const char *data, size_t len) override;
|
||||
// 搜寻SSRC
|
||||
const char *searchBySSRC(const char *data, size_t len);
|
||||
// 搜寻PS包里的关键帧标头
|
||||
const char *searchByPsHeaderFlag(const char *data, size_t len);
|
||||
|
||||
private:
|
||||
bool _delay_close = false;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
#define ENABLE_NTP_STAMP 0
|
||||
#define ENABLE_NTP_STAMP 1
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
|
|
@ -78,6 +78,10 @@ bool RtspMuxer::addTrack(const Track::Ptr &track) {
|
|||
//添加其sdp
|
||||
_sdp.append(sdp->getSdp());
|
||||
trySyncTrack();
|
||||
|
||||
// rtp的时间戳是pts,允许回退
|
||||
_stamp[TrackAudio].enableRollback(true);
|
||||
_stamp[TrackVideo].enableRollback(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -358,7 +358,10 @@ void RtspPusher::updateRtcpContext(const RtpPacket::Ptr &rtp){
|
|||
auto &ticker = _rtcp_send_ticker[track_index];
|
||||
auto &rtcp_ctx = _rtcp_context[track_index];
|
||||
rtcp_ctx->onRtp(rtp->getSeq(), rtp->getStamp(), rtp->ntp_stamp, rtp->sample_rate, rtp->size() - RtpPacket::kRtpTcpHeaderSize);
|
||||
|
||||
if (!rtp->ntp_stamp && !rtp->getStamp()) {
|
||||
// 忽略时间戳都为0的rtp
|
||||
return;
|
||||
}
|
||||
//send rtcp every 5 second
|
||||
if (ticker.elapsedTime() > 5 * 1000) {
|
||||
ticker.resetTime();
|
||||
|
|
|
|||
|
|
@ -1193,6 +1193,10 @@ void RtspSession::updateRtcpContext(const RtpPacket::Ptr &rtp){
|
|||
int track_index = getTrackIndexByTrackType(rtp->type);
|
||||
auto &rtcp_ctx = _rtcp_context[track_index];
|
||||
rtcp_ctx->onRtp(rtp->getSeq(), rtp->getStamp(), rtp->ntp_stamp, rtp->sample_rate, rtp->size() - RtpPacket::kRtpTcpHeaderSize);
|
||||
if (!rtp->ntp_stamp && !rtp->getStamp()) {
|
||||
// 忽略时间戳都为0的rtp
|
||||
return;
|
||||
}
|
||||
|
||||
auto &ticker = _rtcp_send_tickers[track_index];
|
||||
//send rtcp every 5 second
|
||||
|
|
|
|||
|
|
@ -11,12 +11,9 @@
|
|||
#include <signal.h>
|
||||
#include <iostream>
|
||||
#include "Util/logger.h"
|
||||
#include "Util/NoticeCenter.h"
|
||||
#include "Poller/EventPoller.h"
|
||||
#include "Player/PlayerProxy.h"
|
||||
#include "Rtmp/RtmpPusher.h"
|
||||
#include "Common/config.h"
|
||||
#include "Common/Parser.h"
|
||||
#include "Poller/EventPoller.h"
|
||||
#include "Pusher/MediaPusher.h"
|
||||
#include "Record/MP4Reader.h"
|
||||
|
||||
|
|
@ -24,113 +21,101 @@ using namespace std;
|
|||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
//推流器,保持强引用
|
||||
MediaPusher::Ptr g_pusher;
|
||||
Timer::Ptr g_timer;
|
||||
MediaSource::Ptr g_src;
|
||||
|
||||
//声明函数
|
||||
//推流失败或断开延迟2秒后重试推流
|
||||
void rePushDelay(const EventPoller::Ptr &poller,
|
||||
const string &schema,
|
||||
const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
const string &filePath,
|
||||
const string &url);
|
||||
|
||||
//创建推流器并开始推流
|
||||
void createPusher(const EventPoller::Ptr &poller,
|
||||
const string &schema,
|
||||
const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
const string &filePath,
|
||||
const string &url) {
|
||||
if (!g_src) {
|
||||
//不限制APP名,并且指定文件绝对路径
|
||||
g_src = MediaSource::createFromMP4(schema, vhost, app, stream, filePath, false);
|
||||
}
|
||||
if (!g_src) {
|
||||
//文件不存在
|
||||
WarnL << "MP4文件不存在:" << filePath;
|
||||
return;
|
||||
}
|
||||
|
||||
//创建推流器并绑定一个MediaSource
|
||||
g_pusher.reset(new MediaPusher(g_src, poller));
|
||||
//可以指定rtsp推流方式,支持tcp和udp方式,默认tcp
|
||||
//(*g_pusher)[Client::kRtpType] = Rtsp::RTP_UDP;
|
||||
|
||||
//设置推流中断处理逻辑
|
||||
g_pusher->setOnShutdown([poller, schema, vhost, app, stream, filePath, url](const SockException &ex) {
|
||||
WarnL << "Server connection is closed:" << ex.getErrCode() << " " << ex.what();
|
||||
//重新推流
|
||||
rePushDelay(poller, schema, vhost, app, stream, filePath, url);
|
||||
});
|
||||
|
||||
//设置发布结果处理逻辑
|
||||
g_pusher->setOnPublished([poller, schema, vhost, app, stream, filePath, url](const SockException &ex) {
|
||||
if (ex) {
|
||||
WarnL << "Publish fail:" << ex.getErrCode() << " " << ex.what();
|
||||
//如果发布失败,就重试
|
||||
rePushDelay(poller, schema, vhost, app, stream, filePath, url);
|
||||
} else {
|
||||
InfoL << "Publish success,Please play with player:" << url;
|
||||
}
|
||||
});
|
||||
g_pusher->publish(url);
|
||||
}
|
||||
|
||||
//推流失败或断开延迟2秒后重试推流
|
||||
void rePushDelay(const EventPoller::Ptr &poller,
|
||||
const string &schema,
|
||||
const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
const string &filePath,
|
||||
const string &url) {
|
||||
g_timer = std::make_shared<Timer>(2.0f, [poller, schema, vhost, app, stream, filePath, url]() {
|
||||
InfoL << "Re-Publishing...";
|
||||
//重新推流
|
||||
createPusher(poller, schema, vhost, app, stream, filePath, url);
|
||||
//此任务不重复
|
||||
return false;
|
||||
}, poller);
|
||||
}
|
||||
|
||||
//这里才是真正执行main函数,你可以把函数名(domain)改成main,然后就可以输入自定义url了
|
||||
int domain(const string &filePath, const string &pushUrl) {
|
||||
//设置日志
|
||||
// 这里才是真正执行main函数,你可以把函数名(domain)改成main,然后就可以输入自定义url了
|
||||
int domain(const string &file, const string &url) {
|
||||
// 设置日志
|
||||
Logger::Instance().add(std::make_shared<ConsoleChannel>());
|
||||
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
|
||||
//循环点播mp4文件
|
||||
mINI::Instance()[Record::kFileRepeat] = 1;
|
||||
mINI::Instance()[Protocol::kHlsDemand] = 1;
|
||||
mINI::Instance()[Protocol::kTSDemand] = 1;
|
||||
mINI::Instance()[Protocol::kFMP4Demand] = 1;
|
||||
//mINI::Instance()[Protocol::kRtspDemand] = 1;
|
||||
//mINI::Instance()[Protocol::kRtmpDemand] = 1;
|
||||
|
||||
// 关闭所有转协议
|
||||
mINI::Instance()[Protocol::kEnableMP4] = 0;
|
||||
mINI::Instance()[Protocol::kEnableFMP4] = 0;
|
||||
mINI::Instance()[Protocol::kEnableHls] = 0;
|
||||
mINI::Instance()[Protocol::kEnableHlsFmp4] = 0;
|
||||
mINI::Instance()[Protocol::kEnableTS] = 0;
|
||||
mINI::Instance()[Protocol::kEnableRtsp] = 0;
|
||||
mINI::Instance()[Protocol::kEnableRtmp] = 0;
|
||||
|
||||
// 根据url获取媒体协议类型,注意大小写
|
||||
auto schema = strToLower(findSubString(url.data(), nullptr, "://").substr(0, 4));
|
||||
|
||||
// 只开启推流协议对应的转协议
|
||||
mINI::Instance()["protocol.enable_" + schema] = 1;
|
||||
|
||||
// 从mp4文件加载生成MediaSource对象
|
||||
auto reader = std::make_shared<MP4Reader>(DEFAULT_VHOST, "live", "stream", file);
|
||||
// 开始加载mp4,ref_self设置为false,这样reader对象设置为nullptr就能注销了,file_repeat可以设置为空,这样文件读完了就停止推流了
|
||||
reader->startReadMP4(100, false, true);
|
||||
auto src = MediaSource::find(schema, DEFAULT_VHOST, "live", "stream", false);
|
||||
|
||||
if (!src) {
|
||||
// 文件不存在
|
||||
WarnL << "File not existed: " << file;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 选择一个poller线程绑定
|
||||
auto poller = EventPollerPool::Instance().getPoller();
|
||||
//vhost/app/stream可以随便自己填,现在不限制app应用名了
|
||||
createPusher(poller, findSubString(pushUrl.data(), nullptr, "://").substr(0, 4), DEFAULT_VHOST, "live", "stream", filePath, pushUrl);
|
||||
//设置退出信号处理函数
|
||||
// 创建推流器并绑定一个MediaSource
|
||||
auto pusher = std::make_shared<MediaPusher>(src, poller);
|
||||
|
||||
std::weak_ptr<MediaSource> weak_src = src;
|
||||
// src用完了,可以直接置空,防止main函数持有它(MP4Reader持有它即可)
|
||||
src = nullptr;
|
||||
|
||||
// 可以指定rtsp推流方式,支持tcp和udp方式,默认tcp
|
||||
//(*pusher)[Client::kRtpType] = Rtsp::RTP_UDP;
|
||||
|
||||
// 设置推流中断处理逻辑
|
||||
std::weak_ptr<MediaPusher> weak_pusher = pusher;
|
||||
pusher->setOnShutdown([poller, url, weak_pusher, weak_src](const SockException &ex) {
|
||||
if (!weak_src.lock()) {
|
||||
// 媒体注销导致的推流中断,不在重试推流
|
||||
WarnL << "MediaSource released:" << ex << ", publish stopped";
|
||||
return;
|
||||
}
|
||||
WarnL << "Server connection is closed:" << ex << ", republish after 2 seconds";
|
||||
// 重新推流, 2秒后重试
|
||||
poller->doDelayTask(2 * 1000, [weak_pusher, url]() {
|
||||
if (auto strong_push = weak_pusher.lock()) {
|
||||
strong_push->publish(url);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
|
||||
// 设置发布结果处理逻辑
|
||||
pusher->setOnPublished([poller, weak_pusher, url](const SockException &ex) {
|
||||
if (!ex) {
|
||||
InfoL << "Publish success, please play with player:" << url;
|
||||
return;
|
||||
}
|
||||
|
||||
WarnL << "Publish fail:" << ex << ", republish after 2 seconds";
|
||||
// 如果发布失败,就重试
|
||||
poller->doDelayTask(2 * 1000, [weak_pusher, url]() {
|
||||
if (auto strong_push = weak_pusher.lock()) {
|
||||
strong_push->publish(url);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
pusher->publish(url);
|
||||
|
||||
// sleep(5);
|
||||
// reader 置空可以终止推流相关资源
|
||||
// reader = nullptr;
|
||||
|
||||
// 设置退出信号处理函数
|
||||
static semaphore sem;
|
||||
signal(SIGINT, [](int) { sem.post(); });// 设置退出信号
|
||||
signal(SIGINT, [](int) { sem.post(); }); // 设置退出信号
|
||||
sem.wait();
|
||||
g_pusher.reset();
|
||||
g_timer.reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
//可以使用test_server生成的mp4文件
|
||||
//文件使用绝对路径,推流url支持rtsp和rtmp
|
||||
return domain("/home/work/test2.mp4", "rtmp://127.0.0.1/live/rtsp_push");
|
||||
// 可以使用test_server生成的mp4文件
|
||||
// 文件使用绝对路径,推流url支持rtsp和rtmp
|
||||
// return domain("/Users/xiongziliang/Downloads/mp4/Quantum.mp4", "rtsp://127.0.0.1/live/rtsp_push");
|
||||
return domain(argv[1], argv[2]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,145 @@
|
|||
# -*- coding:utf-8 -*-
|
||||
import copy
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import json
|
||||
|
||||
|
||||
def check_installed(command: str) -> bool:
|
||||
"""
|
||||
check command is installed
|
||||
:param command:
|
||||
:return:
|
||||
"""
|
||||
if os.system(f"command -v {command} > /dev/null") == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def run_cmd(cmd: str, assert_success=False, capture_output=False, env=None) -> bool:
|
||||
"""
|
||||
run cmd
|
||||
:param cmd:
|
||||
:param assert_success:
|
||||
:param env:
|
||||
:param capture_output:
|
||||
:param env:
|
||||
:return:
|
||||
"""
|
||||
if not env:
|
||||
env = os.environ.copy()
|
||||
result = subprocess.run(cmd, shell=True, env=env, capture_output=capture_output)
|
||||
# Assert the command ran successfully
|
||||
if assert_success and result.returncode != 0:
|
||||
print("Command '" + cmd + "' failed with exit status code '" + str(
|
||||
result.returncode) + "'.\n\nExiting now.\nTry running the script again.")
|
||||
print(result.stderr.decode())
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
sys.exit(1)
|
||||
return True
|
||||
|
||||
|
||||
def check_dependencies() -> None:
|
||||
"""
|
||||
check dependencies
|
||||
:return:
|
||||
"""
|
||||
if not check_installed("p2o"):
|
||||
print()
|
||||
print("p2o is not installed, please install it first!")
|
||||
print("If you use npm, you can install it by the following command:")
|
||||
print("npm install -g postman-to-openapi")
|
||||
print()
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("p2o is installed")
|
||||
|
||||
|
||||
def get_version() -> str:
|
||||
"""
|
||||
get version
|
||||
:return:
|
||||
"""
|
||||
if os.path.isfile("../../cmake-build-debug/version.h"):
|
||||
print("Found version.h in cmake-build-debug")
|
||||
version_h_path = "../../cmake-build-debug/version.h"
|
||||
elif os.path.isfile("../../cmake-build-release/version.h"):
|
||||
print("Found version.h in cmake-build-release")
|
||||
version_h_path = "../../cmake-build-release/version.h"
|
||||
else:
|
||||
print("version.h not found")
|
||||
print("Please compile first")
|
||||
exit()
|
||||
with open(version_h_path, 'r') as f:
|
||||
content = f.read()
|
||||
commit_hash = re.search(r'define COMMIT_HASH (.*)', content).group(1)
|
||||
commit_time = re.search(r'define COMMIT_TIME (.*)', content).group(1)
|
||||
branch_name = re.search(r'define BRANCH_NAME (.*)', content).group(1)
|
||||
build_time = re.search(r'define BUILD_TIME (.*)', content).group(1)
|
||||
version = f"ZLMediaKit(git hash:{commit_hash}/{commit_time},branch:{branch_name},build time:{build_time})"
|
||||
print(f"version: {version}")
|
||||
return version
|
||||
|
||||
|
||||
def get_secret() -> str:
|
||||
"""
|
||||
get secret from default config file or user config file
|
||||
:return:
|
||||
"""
|
||||
default_postman = json.load(open("../../postman/127.0.0.1.postman_environment.json", 'r'))
|
||||
secret = "035c73f7-bb6b-4889-a715-d9eb2d1925cc"
|
||||
for item in default_postman["values"]:
|
||||
if item["key"] == "ZLMediaKit_secret":
|
||||
secret = item["value"]
|
||||
break
|
||||
for root, dirs, files in os.walk("../../release/"):
|
||||
for file in files:
|
||||
if file == "config.ini":
|
||||
config_path = os.path.join(root, file)
|
||||
with open(config_path, 'r') as f:
|
||||
content = f.read()
|
||||
secret = re.search(r'secret=(.*)', content).group(1)
|
||||
return secret
|
||||
|
||||
|
||||
def update_options(version: str, secret: str) -> None:
|
||||
"""
|
||||
update options
|
||||
:param version:
|
||||
:param secret:
|
||||
:return:
|
||||
"""
|
||||
print("update options")
|
||||
options = json.load(open("./options.json", 'r'))
|
||||
options["info"]["version"] = version
|
||||
options["additionalVars"]["ZLMediaKit_secret"] = secret
|
||||
json.dump(options, open("./options.json", 'w'), indent=4)
|
||||
|
||||
|
||||
def generate() -> None:
|
||||
"""
|
||||
generate
|
||||
:return:
|
||||
"""
|
||||
print("generate")
|
||||
run_cmd("p2o ../../postman/ZLMediaKit.postman_collection.json -f ../../www/swagger/openapi.json -o ./options.json",
|
||||
True, True)
|
||||
openapi = json.load(open("../../www/swagger/openapi.json", 'r'))
|
||||
for path in openapi["paths"]:
|
||||
openapi["paths"][path]["post"] = copy.deepcopy(openapi["paths"][path]["get"])
|
||||
openapi["paths"][path]["post"]["tags"] = ["POST"]
|
||||
# save
|
||||
json.dump(openapi, open("../../www/swagger/openapi.json", 'w'), indent=4)
|
||||
print("generate success")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_dependencies()
|
||||
version = get_version()
|
||||
secret = get_secret()
|
||||
update_options(version, secret)
|
||||
generate()
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"info": {
|
||||
"title": "ZLMediaKit HTTP API",
|
||||
"version": "ZLMediaKit(git hash:\"a78ca2e\"/\"2023-11-17T11:12:51+08:00\",branch:\"patch-63\",build time:\"2023-11-23T14:35:02\")",
|
||||
"description": "You can test the HTTP API provided by ZlMediaKit here. For usage documentation, please refer to [here](https://docs.zlmediakit.com/guide/media_server/restful_api.html)",
|
||||
"termsOfService": "https://docs.zlmediakit.com",
|
||||
"license": {
|
||||
"name": "MIT",
|
||||
"url": "https://docs.zlmediakit.com/more/license.html"
|
||||
},
|
||||
"contact": {
|
||||
"name": "Contact Support",
|
||||
"url": "https://docs.zlmediakit.com/more/contact.html",
|
||||
"email": "1213642868@qq.com"
|
||||
},
|
||||
"xLogo": {
|
||||
"url": "/logo.png",
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"altText": "ZLMediaKit"
|
||||
}
|
||||
},
|
||||
"defaultTag": "GET",
|
||||
"outputFormat": "json",
|
||||
"replaceVars": true,
|
||||
"servers": [
|
||||
{
|
||||
"url": "/",
|
||||
"description": "Localhost"
|
||||
}
|
||||
],
|
||||
"externalDocs": {
|
||||
"description": "ZLMediaKit Documentation",
|
||||
"url": "https://docs.zlmediakit.com"
|
||||
},
|
||||
"additionalVars": {
|
||||
"defaultVhost": "__defaultVhost__",
|
||||
"ZLMediaKit_secret": "1oV1R5Z9xlrjH4QN7GXNvS5IUaYtuFgX",
|
||||
"ZLMediaKit_URL": ""
|
||||
}
|
||||
}
|
||||
|
|
@ -258,7 +258,7 @@ uint64_t NackContext::reSendNack() {
|
|||
for (auto it = nack_rtp.begin(); it != nack_rtp.end();) {
|
||||
if (pid == -1) {
|
||||
pid = *it;
|
||||
vec.resize(FCI_NACK::kBitSize, false);
|
||||
vec.assign(FCI_NACK::kBitSize, false);
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ const char* sockTypeStr(Session* session) {
|
|||
|
||||
WebRtcTransport::WebRtcTransport(const EventPoller::Ptr &poller) {
|
||||
_poller = poller;
|
||||
_identifier = "zlm_" + to_string(++s_key);
|
||||
_identifier = "zlm" + to_string(++s_key);
|
||||
_packet_pool.setSize(64);
|
||||
}
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 665 B |
Binary file not shown.
|
After Width: | Height: | Size: 628 B |
|
|
@ -0,0 +1,16 @@
|
|||
html {
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
|
||||
<link rel="stylesheet" type="text/css" href="index.css" />
|
||||
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
|
||||
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
||||
<script src="./swagger-initializer.js" charset="UTF-8"> </script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<title>Swagger UI: OAuth2 Redirect</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1).replace('?', '&');
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&");
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value);
|
||||
}
|
||||
) : {};
|
||||
|
||||
isValid = qp.state === sentState;
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorization_code"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg;
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
||||
if (document.readyState !== 'loading') {
|
||||
run();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
run();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,20 @@
|
|||
window.onload = function() {
|
||||
//<editor-fold desc="Changeable Configuration Block">
|
||||
|
||||
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
|
||||
window.ui = SwaggerUIBundle({
|
||||
url: "/swagger/openapi.json",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
});
|
||||
|
||||
//</editor-fold>
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -155,44 +155,47 @@
|
|||
audioEnable:document.getElementById('audioEnable').checked,
|
||||
videoEnable:document.getElementById('videoEnable').checked,
|
||||
recvOnly:recvOnly,
|
||||
resolution:{w:w,h:h},
|
||||
resolution:{w,h},
|
||||
usedatachannel:document.getElementById('datachannel').checked,
|
||||
}
|
||||
);
|
||||
|
||||
player.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR,function(e)
|
||||
{// ICE 协商出错
|
||||
console.log('ICE 协商出错');
|
||||
{
|
||||
// ICE 协商出错
|
||||
console.log('ICE 协商出错');
|
||||
});
|
||||
|
||||
player.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,function(e)
|
||||
{//获取到了远端流,可以播放
|
||||
console.log('播放成功',e.streams);
|
||||
{
|
||||
//获取到了远端流,可以播放
|
||||
console.log('播放成功',e.streams);
|
||||
});
|
||||
|
||||
player.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED,function(e)
|
||||
{// offer anwser 交换失败
|
||||
console.log('offer anwser 交换失败',e);
|
||||
stop();
|
||||
{
|
||||
// offer anwser 交换失败
|
||||
console.log('offer anwser 交换失败',e);
|
||||
stop();
|
||||
});
|
||||
|
||||
player.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM,function(s)
|
||||
{// 获取到了本地流
|
||||
|
||||
{
|
||||
// 获取到了本地流
|
||||
document.getElementById('selfVideo').srcObject=s;
|
||||
document.getElementById('selfVideo').muted = true;
|
||||
|
||||
//console.log('offer anwser 交换失败',e)
|
||||
//console.log('offer anwser 交换失败',e)
|
||||
});
|
||||
|
||||
player.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED,function(s)
|
||||
{// 获取本地流失败
|
||||
|
||||
{
|
||||
// 获取本地流失败
|
||||
console.log('获取本地流失败');
|
||||
});
|
||||
|
||||
player.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE,function(state)
|
||||
{// RTC 状态变化 ,详情参考 https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionState
|
||||
{
|
||||
// RTC 状态变化 ,详情参考 https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionState
|
||||
console.log('当前状态==>',state);
|
||||
});
|
||||
|
||||
|
|
@ -267,19 +270,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
function on_click_to_play(app, stream) {
|
||||
console.log(`on_click_to_play: ${app}/${stream}`);
|
||||
var url = `${document.location.protocol}//${window.location.host}/index/api/webrtc?app=${app}&stream=${stream}&type=play`;
|
||||
document.getElementById('streamUrl').value = url;
|
||||
start();
|
||||
}
|
||||
|
||||
function clearStreamList() {
|
||||
let content = document.getElementById("olstreamlist");
|
||||
while (content.hasChildNodes()) {
|
||||
content.removeChild(content.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
function fillStreamList(json) {
|
||||
clearStreamList();
|
||||
if (json.code != 0 || !json.data) {
|
||||
|
|
@ -308,6 +312,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getData(url) {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET'
|
||||
|
|
@ -316,11 +321,13 @@
|
|||
//console.log(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
function get_media_list() {
|
||||
let url = document.location.protocol+"//"+window.location.host+"/index/api/getMediaList?secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
|
||||
let json = getData(url);
|
||||
json.then((json)=> fillStreamList(json));
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
// get_media_list();
|
||||
}, 5000);
|
||||
|
|
|
|||
Loading…
Reference in New Issue