commit
a3c8cb11b3
|
|
@ -0,0 +1 @@
|
|||
release/ filter=lfs diff=lfs merge=lfs -text
|
||||
|
|
@ -33,4 +33,5 @@
|
|||
|
||||
/cmake-build-debug/
|
||||
/.idea/
|
||||
/c_wrapper/.idea/
|
||||
/c_wrapper/.idea/
|
||||
/release/mac/Debug/
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 2bb234006c852b1d1a61a0e9a7f39dde7105fe34
|
||||
Subproject commit 57e7c83d5667b1e06fb8f5ca73dbe3f04a9fc67f
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 6df71e01c174cdfe69e597cc4acb766a20b28620
|
||||
Subproject commit 40edf6243d9d99676062062efdec203b24a178aa
|
||||
|
|
@ -2,13 +2,34 @@
|
|||
cmake_minimum_required(VERSION 3.1.3)
|
||||
#使能c++11
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
|
||||
#加载自定义模块
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake")
|
||||
#设置库文件路径
|
||||
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
|
||||
#设置可执行程序路径
|
||||
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
|
||||
|
||||
#set(CMAKE_BUILD_TYPE "Release")
|
||||
|
||||
if(${CMAKE_BUILD_TYPE} MATCHES "Release")
|
||||
message(STATUS "Release版本")
|
||||
set(BuildType "Release")
|
||||
else()
|
||||
set(BuildType "Debug")
|
||||
message(STATUS "Debug版本")
|
||||
endif()
|
||||
|
||||
#设置bin和lib库目录
|
||||
set(RELEASE_DIR ${CMAKE_SOURCE_DIR}/release)
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
SET(LIBRARY_OUTPUT_PATH ${RELEASE_DIR}/linux/${BuildType})
|
||||
SET(EXECUTABLE_OUTPUT_PATH ${RELEASE_DIR}/linux/${BuildType})
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
SET(LIBRARY_OUTPUT_PATH ${RELEASE_DIR}/windows/${BuildType})
|
||||
SET(EXECUTABLE_OUTPUT_PATH ${RELEASE_DIR}/windows/${BuildType})
|
||||
elseif (CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
SET(LIBRARY_OUTPUT_PATH ${RELEASE_DIR}/mac/${BuildType})
|
||||
SET(EXECUTABLE_OUTPUT_PATH ${RELEASE_DIR}/mac/${BuildType})
|
||||
endif ()
|
||||
|
||||
LINK_DIRECTORIES(${LIBRARY_OUTPUT_PATH})
|
||||
|
||||
|
||||
#设置工程源码根目录
|
||||
set(ToolKit_Root ${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/ZLToolKit/src)
|
||||
|
|
@ -106,7 +127,7 @@ add_library(zltoolkit STATIC ${ToolKit_src_list})
|
|||
add_library(zlmediakit STATIC ${MediaKit_src_list})
|
||||
|
||||
|
||||
set(VS_FALGS "/wd4819 /wd4996 /wd4018 /wd4267 /wd4244 /wd4101 /wd4828 /wd4309 /wd4573 /utf-8" )
|
||||
set(VS_FALGS "/wd4819 /wd4996 /wd4018 /wd4267 /wd4244 /wd4101 /wd4828 /wd4309 /wd4573" )
|
||||
#libmpeg
|
||||
if(ENABLE_HLS)
|
||||
aux_source_directory(${MediaServer_Root}/libmpeg/include src_mpeg)
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -13,6 +13,7 @@
|
|||
- Well performance and stable test,can be used commercially.
|
||||
- Support linux, macos, ios, android, Windows Platforms.
|
||||
- Very low latency(lower then one second), video opened immediately.
|
||||
- **Now Support websocket-flv!**
|
||||
|
||||
## Features
|
||||
|
||||
|
|
@ -116,7 +117,12 @@
|
|||
- Apple OSX(Darwin), both 32 and 64bits.
|
||||
- All hardware with x86/x86_64/arm/mips cpu.
|
||||
- Windows.
|
||||
- **You must use git to clone the complete code. Do not download the source code by downloading zip package. Otherwise, the sub-module code will not be downloaded by default.**
|
||||
- **You must use git to clone the complete code. Do not download the source code by downloading zip package. Otherwise, the sub-module code will not be downloaded by default.You can do it like this:**
|
||||
```
|
||||
git clone https://github.com/zlmediakit/ZLMediaKit.git
|
||||
cd ZLMediaKit
|
||||
git submodule update --init
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
|
@ -231,7 +237,7 @@ It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumber
|
|||
## Usage
|
||||
|
||||
- As server:
|
||||
```
|
||||
```cpp
|
||||
TcpServer::Ptr rtspSrv(new TcpServer());
|
||||
TcpServer::Ptr rtmpSrv(new TcpServer());
|
||||
TcpServer::Ptr httpSrv(new TcpServer());
|
||||
|
|
@ -244,7 +250,7 @@ It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumber
|
|||
```
|
||||
|
||||
- As player:
|
||||
```
|
||||
```cpp
|
||||
MediaPlayer::Ptr player(new MediaPlayer());
|
||||
weak_ptr<MediaPlayer> weakPlayer = player;
|
||||
player->setOnPlayResult([weakPlayer](const SockException &ex) {
|
||||
|
|
@ -273,7 +279,7 @@ It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumber
|
|||
player->play("rtsp://admin:jzan123456@192.168.0.122/");
|
||||
```
|
||||
- As proxy server:
|
||||
```
|
||||
```cpp
|
||||
//support rtmp and rtsp url
|
||||
//just support H264+AAC
|
||||
auto urlList = {"rtmp://live.hkstv.hk.lxdns.com/live/hks",
|
||||
|
|
@ -288,7 +294,7 @@ It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumber
|
|||
```
|
||||
|
||||
- As puser:
|
||||
```
|
||||
```cpp
|
||||
PlayerProxy::Ptr player(new PlayerProxy("app","stream"));
|
||||
player->play("rtmp://live.hkstv.hk.lxdns.com/live/hks");
|
||||
|
||||
|
|
|
|||
16
README_CN.md
16
README_CN.md
|
|
@ -11,6 +11,7 @@
|
|||
- 代码经过大量的稳定性、性能测试,可满足商用服务器项目。
|
||||
- 支持linux、macos、ios、android、windows平台
|
||||
- 支持画面秒开(GOP缓存)、极低延时(1秒内)
|
||||
- **支持websocket-flv直播**
|
||||
- [ZLMediaKit高并发实现原理](https://github.com/xiongziliang/ZLMediaKit/wiki/ZLMediaKit%E9%AB%98%E5%B9%B6%E5%8F%91%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86)
|
||||
|
||||
## 项目定位
|
||||
|
|
@ -127,7 +128,12 @@
|
|||
## 编译要求
|
||||
- 编译器支持C++11,GCC4.8/Clang3.3/VC2015或以上
|
||||
- cmake3.2或以上
|
||||
- **必须使用git下载完整的代码,不要使用下载zip包的方式下载源码,否则子模块代码默认不下载!**
|
||||
- **必须使用git下载完整的代码,不要使用下载zip包的方式下载源码,否则子模块代码默认不下载!你可以像以下这样操作:**
|
||||
```
|
||||
git clone https://github.com/zlmediakit/ZLMediaKit.git
|
||||
cd ZLMediaKit
|
||||
git submodule update --init
|
||||
```
|
||||
|
||||
## 编译(Linux)
|
||||
- 我的编译环境
|
||||
|
|
@ -219,7 +225,7 @@
|
|||
```
|
||||
## 使用方法
|
||||
- 作为服务器:
|
||||
```
|
||||
```cpp
|
||||
TcpServer::Ptr rtspSrv(new TcpServer());
|
||||
TcpServer::Ptr rtmpSrv(new TcpServer());
|
||||
TcpServer::Ptr httpSrv(new TcpServer());
|
||||
|
|
@ -232,7 +238,7 @@
|
|||
```
|
||||
|
||||
- 作为播放器:
|
||||
```
|
||||
```cpp
|
||||
MediaPlayer::Ptr player(new MediaPlayer());
|
||||
weak_ptr<MediaPlayer> weakPlayer = player;
|
||||
player->setOnPlayResult([weakPlayer](const SockException &ex) {
|
||||
|
|
@ -261,7 +267,7 @@
|
|||
player->play("rtsp://admin:jzan123456@192.168.0.122/");
|
||||
```
|
||||
- 作为代理服务器:
|
||||
```
|
||||
```cpp
|
||||
//support rtmp and rtsp url
|
||||
//just support H264+AAC
|
||||
auto urlList = {"rtmp://live.hkstv.hk.lxdns.com/live/hks",
|
||||
|
|
@ -285,7 +291,7 @@
|
|||
```
|
||||
|
||||
- 作为推流客户端器:
|
||||
```
|
||||
```cpp
|
||||
PlayerProxy::Ptr player(new PlayerProxy("app","stream"));
|
||||
//拉一个流,生成一个RtmpMediaSource,源的名称是"app/stream"
|
||||
//你也可以以其他方式生成RtmpMediaSource,比如说MP4文件(请研读MediaReader代码)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,163 @@
|
|||
[api]
|
||||
#是否调试http api,启用调试后,会打印每次http请求的内容和回复
|
||||
apiDebug=1
|
||||
#一些比较敏感的http api在访问时需要提供secret,否则无权限调用
|
||||
#如果是通过127.0.0.1访问,那么可以不提供secret
|
||||
secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
|
||||
|
||||
[ffmpeg]
|
||||
#FFmpeg可执行程序路径
|
||||
bin=/usr/local/bin/ffmpeg
|
||||
#FFmpeg拉流再推流的命令模板,通过该模板可以设置再编码的一些参数
|
||||
cmd=%s -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s
|
||||
#FFmpeg日志的路径,如果置空则不生成FFmpeg日志
|
||||
log=/Users/xzl/git/ZLMediaKit/release/mac/Release/ffmpeg/ffmpeg.log
|
||||
|
||||
[general]
|
||||
#是否启用虚拟主机
|
||||
enableVhost=1
|
||||
#播放器或推流器在断开后会触发hook.on_flow_report事件(使用多少流量事件),
|
||||
#flowThreshold参数控制触发hook.on_flow_report事件阈值,使用流量超过该阈值后才触发,单位KB
|
||||
flowThreshold=1024
|
||||
#播放最多等待时间,单位毫秒
|
||||
#播放在播放某个流时,如果该流不存在,
|
||||
#ZLMediaKit会最多让播放器等待maxStreamWaitMS毫秒
|
||||
#如果在这个时间内,该流注册成功,那么会立即返回播放器播放成功
|
||||
#否则返回播放器未找到该流,该机制的目的是可以先播放再推流
|
||||
maxStreamWaitMS=5000
|
||||
#某个流无人观看时,触发hook.on_stream_none_reader事件的最大等待时间,单位毫秒
|
||||
#在配合hook.on_stream_none_reader事件时,可以做到无人观看自动停止拉流或停止接收推流
|
||||
streamNoneReaderDelayMS=5000
|
||||
|
||||
[hls]
|
||||
#hls写文件的buf大小,调整参数可以提高文件io性能
|
||||
fileBufSize=65536
|
||||
#hls保存文件路径
|
||||
filePath=/Users/xzl/git/ZLMediaKit/release/mac/Release/httpRoot
|
||||
#hls最大切片时间
|
||||
segDur=3
|
||||
#m3u8索引中,hls保留切片个数(实际保留切片个数大2~3个)
|
||||
segNum=3
|
||||
|
||||
[hook]
|
||||
#在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然
|
||||
#该配置项的目的是为了开发者自己调试测试,该参数暴露后会有泄露隐私的安全隐患
|
||||
admin_params=secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
|
||||
#是否启用hook事件,启用后,推拉流都将进行鉴权
|
||||
enable=0
|
||||
#播放器或推流器使用流量事件,置空则关闭
|
||||
on_flow_report=https://127.0.0.1/index/hook/on_flow_report
|
||||
#访问http文件鉴权事件,置空则关闭鉴权
|
||||
on_http_access=https://127.0.0.1/index/hook/on_http_access
|
||||
#播放鉴权事件,置空则关闭鉴权
|
||||
on_play=https://127.0.0.1/index/hook/on_play
|
||||
#推流鉴权事件,置空则关闭鉴权
|
||||
on_publish=https://127.0.0.1/index/hook/on_publish
|
||||
#录制mp4切片完成事件
|
||||
on_record_mp4=https://127.0.0.1/index/hook/on_record_mp4
|
||||
#rtsp播放鉴权事件,此事件中比对rtsp的用户名密码
|
||||
on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth
|
||||
#rtsp播放是否开启鉴权事件,置空则关闭rtsp鉴权。rtsp播放鉴权还支持url方式鉴权
|
||||
#建议开发者统一采用url参数方式鉴权,rtsp用户名密码鉴权一般在设备上用的比较多
|
||||
on_rtsp_realm=https://127.0.0.1/index/hook/on_rtsp_realm
|
||||
#远程telnet调试鉴权事件
|
||||
on_shell_login=https://127.0.0.1/index/hook/on_shell_login
|
||||
#直播流注册或注销事件
|
||||
on_stream_changed=https://127.0.0.1/index/hook/on_stream_changed
|
||||
#无人观看流事件,通过该事件,可以选择是否关闭无人观看的流。配合general.streamNoneReaderDelayMS选项一起使用
|
||||
on_stream_none_reader=https://127.0.0.1/index/hook/on_stream_none_reader
|
||||
#播放时,未找到流事件,通过配合hook.on_stream_none_reader事件可以完成按需拉流
|
||||
on_stream_not_found=https://127.0.0.1/index/hook/on_stream_not_found
|
||||
#hook api最大等待回复时间,单位秒
|
||||
timeoutSec=10
|
||||
|
||||
[http]
|
||||
#http服务器字符编码,windows上默认gb2312
|
||||
charSet=utf-8
|
||||
#http链接超时时间
|
||||
keepAliveSecond=10
|
||||
#keep-alive类型的链接最多复用次数
|
||||
maxReqCount=100
|
||||
#http请求体最大字节数,如果post的body太大,则不适合缓存body在内存
|
||||
maxReqSize=4096
|
||||
#404网页内容,用户可以自定义404网页
|
||||
notFound=<html><head><title>404 Not Found</title></head><body bgcolor="white"><center><h1>您访问的资源不存在!</h1></center><hr><center>ZLMediaKit-4.0</center></body></html>
|
||||
#http服务器监听端口
|
||||
port=80
|
||||
#http文件服务器根目录
|
||||
rootPath=/Users/xzl/git/ZLMediaKit/release/mac/Release/httpRoot
|
||||
#http文件服务器读文件缓存大小,单位BYTE,调整该参数可以优化文件io性能
|
||||
sendBufSize=65536
|
||||
#https服务器监听端口
|
||||
sslport=443
|
||||
|
||||
[multicast]
|
||||
#rtp组播截止组播ip地址
|
||||
addrMax=239.255.255.255
|
||||
#rtp组播起始组播ip地址
|
||||
addrMin=239.0.0.0
|
||||
#组播udp ttl
|
||||
udpTTL=64
|
||||
|
||||
[record]
|
||||
#mp4录制或mp4点播的应用名,通过限制应用名,可以防止随意点播
|
||||
appName=record
|
||||
#mp4录制写文件缓存,单位BYTE,调整参数可以提高文件io性能
|
||||
fileBufSize=65536
|
||||
#mp4录制保存路径
|
||||
filePath=/Users/xzl/git/ZLMediaKit/release/mac/Release/httpRoot
|
||||
#mp4录制切片时间,单位秒
|
||||
fileSecond=3600
|
||||
#mp4点播每次流化数据量,单位毫秒,
|
||||
#减少该值可以让点播数据发送量更平滑,增大该值则更节省cpu资源
|
||||
sampleMS=100
|
||||
|
||||
[rtmp]
|
||||
#rtmp必须在此时间内完成握手,否则服务器会断开链接,单位秒
|
||||
handshakeSecond=15
|
||||
#rtmp超时时间,如果该时间内未收到客户端的数据,
|
||||
#或者tcp发送缓存超过这个时间,则会断开连接,单位秒
|
||||
keepAliveSecond=15
|
||||
#在接收rtmp推流时,是否重新生成时间戳(很多推流器的时间戳着实很烂)
|
||||
modifyStamp=1
|
||||
#rtmp服务器监听端口
|
||||
port=1935
|
||||
|
||||
[rtp]
|
||||
#音频mtu大小,该参数限制rtp最大字节数,推荐不要超过1400
|
||||
#加大该值会明显增加直播延时
|
||||
audioMtuSize=600
|
||||
#如果rtp的序列号连续clearCount次有序,那么rtp将不再排序(目的减少rtp排序导致的延时)
|
||||
clearCount=10
|
||||
#rtp时间戳回环时间,单位毫秒
|
||||
cycleMS=46800000
|
||||
#rtp排序map缓存大小,加大该值可能会增大延时,但是rtp乱序问题会减小
|
||||
maxRtpCount=50
|
||||
#视频mtu大小,该参数限制rtp最大字节数,推荐不要超过1400
|
||||
videoMtuSize=1400
|
||||
|
||||
[rtsp]
|
||||
#rtsp专有鉴权方式是采用base64还是md5方式
|
||||
authBasic=0
|
||||
#rtsp拉流代理是否是直接代理模式
|
||||
#直接代理后支持任意编码格式,但是会导致GOP缓存无法定位到I帧,可能会导致开播花屏
|
||||
#并且如果是tcp方式拉流,如果rtp大于mtu会导致无法使用udp方式代理
|
||||
#假定您的拉流源地址不是264或265或AAC,那么你可以使用直接代理的方式来支持rtsp代理
|
||||
#默认开启rtsp直接代理,rtmp由于没有这些问题,是强制开启直接代理的
|
||||
directProxy=1
|
||||
#rtsp必须在此时间内完成握手,否则服务器会断开链接,单位秒
|
||||
handshakeSecond=15
|
||||
#rtsp超时时间,如果该时间内未收到客户端的数据,
|
||||
#或者tcp发送缓存超过这个时间,则会断开连接,单位秒
|
||||
keepAliveSecond=15
|
||||
#rtsp服务器监听地址
|
||||
port=554
|
||||
#rtsps服务器监听地址
|
||||
sslport=322
|
||||
|
||||
[shell]
|
||||
#调试telnet服务器接受最大bufffer大小
|
||||
maxReqSize=1024
|
||||
#调试telnet服务器监听端口
|
||||
port=9000
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,9 @@
|
|||
执行可执行程序时,请在终端输入:
|
||||
|
||||
```
|
||||
export LD_LIBRARY_PATH=./
|
||||
./MediaServer -d &
|
||||
```
|
||||
|
||||
如果由于so动态库链接失败导致运行不起来,请重建so库软链接
|
||||
如果由于端口权限问题导致启动失败,请修改配置文件中端口号,或者以root权限运行
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,9 @@
|
|||
执行可执行程序时,请在终端输入:
|
||||
|
||||
```
|
||||
export DYLD_LIBRARY_PATH=./
|
||||
./MediaServer -d &
|
||||
```
|
||||
|
||||
如果由于so动态库链接失败导致运行不起来,请重建so库软链接
|
||||
如果由于端口权限问题导致启动失败,请修改配置文件中端口号,或者以root权限运行
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,3 @@
|
|||
执行可执行程序时,可以直接双击MediaServer运行
|
||||
|
||||
如果由于端口权限问题导致启动失败,请修改配置文件(.ini后缀的文件)中端口号,然后再运行
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -74,7 +74,7 @@ const string kOnHttpAccess = HOOK_FIELD"on_http_access";
|
|||
const string kAdminParams = HOOK_FIELD"admin_params";
|
||||
|
||||
onceToken token([](){
|
||||
mINI::Instance()[kEnable] = true;
|
||||
mINI::Instance()[kEnable] = false;
|
||||
mINI::Instance()[kTimeoutSec] = 10;
|
||||
mINI::Instance()[kOnPublish] = "https://127.0.0.1/index/hook/on_publish";
|
||||
mINI::Instance()[kOnPlay] = "https://127.0.0.1/index/hook/on_play";
|
||||
|
|
@ -318,7 +318,7 @@ void installWebHook(){
|
|||
do_http_hook(hook_stream_not_found,body, nullptr);
|
||||
});
|
||||
|
||||
#ifdef ENABLE_MP4V2
|
||||
#ifdef ENABLE_MP4RECORD
|
||||
//录制mp4文件成功后广播
|
||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastRecordMP4,[](BroadcastRecordMP4Args){
|
||||
if(!hook_enable || hook_record_mp4.empty()){
|
||||
|
|
@ -338,7 +338,7 @@ void installWebHook(){
|
|||
//执行hook
|
||||
do_http_hook(hook_record_mp4,body, nullptr);
|
||||
});
|
||||
#endif //ENABLE_MP4V2
|
||||
#endif //ENABLE_MP4RECORD
|
||||
|
||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastShellLogin,[](BroadcastShellLoginArgs){
|
||||
if(!hook_enable || hook_shell_login.empty() || sender.get_peer_ip() == "127.0.0.1"){
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ void MediaSink::inputFrame(const Frame::Ptr &frame) {
|
|||
it->second->inputFrame(frame);
|
||||
|
||||
if(!_allTrackReady && !_trackReadyCallback.empty() && it->second->ready()){
|
||||
//Track由未就绪状态装换成就绪状态,我们就触发onTrackReady回调
|
||||
//Track由未就绪状态转换成就绪状态,我们就触发onTrackReady回调
|
||||
auto it_callback = _trackReadyCallback.find(codec_id);
|
||||
if(it_callback != _trackReadyCallback.end()){
|
||||
it_callback->second();
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ MediaSource::Ptr MediaSource::find(
|
|||
|
||||
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
|
||||
MediaSource::Ptr ret;
|
||||
//查找某一媒体源,找到后返回
|
||||
searchMedia(schema, vhost, app, id,
|
||||
[&](SchemaVhostAppStreamMap::iterator &it0 ,
|
||||
VhostAppStreamMap::iterator &it1,
|
||||
|
|
@ -138,7 +139,7 @@ MediaSource::Ptr MediaSource::find(
|
|||
return true;
|
||||
});
|
||||
if(!ret && bMake){
|
||||
//查找某一媒体源,找到后返回
|
||||
//未查找媒体源,则创建一个
|
||||
ret = MediaReader::onMakeMediaSource(schema, vhost,app,id);
|
||||
}
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -186,6 +186,14 @@ public:
|
|||
}
|
||||
|
||||
virtual int readerCount() = 0;
|
||||
|
||||
/**
|
||||
* 获取track
|
||||
* @return
|
||||
*/
|
||||
virtual vector<Track::Ptr> getTracks(bool trackReady) const{
|
||||
return vector<Track::Ptr>(0);
|
||||
}
|
||||
protected:
|
||||
void regist() ;
|
||||
bool unregist() ;
|
||||
|
|
|
|||
|
|
@ -161,13 +161,16 @@ namespace Rtsp {
|
|||
const string kAuthBasic = RTSP_FIELD"authBasic";
|
||||
const string kHandshakeSecond = RTSP_FIELD"handshakeSecond";
|
||||
const string kKeepAliveSecond = RTSP_FIELD"keepAliveSecond";
|
||||
const string kDirectProxy = RTSP_FIELD"directProxy";;
|
||||
const string kDirectProxy = RTSP_FIELD"directProxy";
|
||||
const string kModifyStamp = RTSP_FIELD"modifyStamp";
|
||||
|
||||
onceToken token([](){
|
||||
//默认Md5方式认证
|
||||
mINI::Instance()[kAuthBasic] = 0;
|
||||
mINI::Instance()[kHandshakeSecond] = 15;
|
||||
mINI::Instance()[kKeepAliveSecond] = 15;
|
||||
mINI::Instance()[kDirectProxy] = 1;
|
||||
mINI::Instance()[kModifyStamp] = true;
|
||||
},nullptr);
|
||||
|
||||
} //namespace Rtsp
|
||||
|
|
|
|||
|
|
@ -209,6 +209,8 @@ extern const string kKeepAliveSecond;
|
|||
//假定您的拉流源地址不是264或265或AAC,那么你可以使用直接代理的方式来支持rtsp代理
|
||||
//默认开启rtsp直接代理,rtmp由于没有这些问题,是强制开启直接代理的
|
||||
extern const string kDirectProxy;
|
||||
//rtsp推流是否修改时间戳
|
||||
extern const string kModifyStamp;
|
||||
} //namespace Rtsp
|
||||
|
||||
////////////RTMP服务器配置///////////
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ string makeAdtsConfig(const uint8_t *pcAdts);
|
|||
void getAACInfo(const AACFrame &adts,int &iSampleRate,int &iChannel);
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* aac帧,包含adts头
|
||||
*/
|
||||
class AACFrame : public Frame {
|
||||
|
|
@ -156,10 +156,10 @@ public:
|
|||
* @param aac_cfg aac两个字节的配置信息
|
||||
*/
|
||||
AACTrack(const string &aac_cfg){
|
||||
if(aac_cfg.size() != 2){
|
||||
throw std::invalid_argument("adts配置必须为2个字节");
|
||||
if(aac_cfg.size() < 2){
|
||||
throw std::invalid_argument("adts配置必须最少2个字节");
|
||||
}
|
||||
_cfg = aac_cfg;
|
||||
_cfg = aac_cfg.substr(0,2);
|
||||
onReady();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ using namespace toolkit;
|
|||
namespace mediakit{
|
||||
/**
|
||||
* h264 Rtmp解码类
|
||||
* 将 h264 over rtmp 解复用出 h264-Frame
|
||||
*/
|
||||
class H264RtmpDecoder : public RtmpCodec ,public ResourcePoolHelper<H264Frame> {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -90,7 +90,24 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
|||
* Type==7:SPS frame
|
||||
* Type==8:PPS frame
|
||||
*/
|
||||
/*
|
||||
RTF3984 5.2节 Common Structure of the RTP Payload Format
|
||||
Table 1. Summary of NAL unit types and their payload structures
|
||||
|
||||
Type Packet Type name Section
|
||||
---------------------------------------------------------
|
||||
0 undefined -
|
||||
1-23 NAL unit Single NAL unit packet per H.264 5.6
|
||||
24 STAP-A Single-time aggregation packet 5.7.1
|
||||
25 STAP-B Single-time aggregation packet 5.7.1
|
||||
26 MTAP16 Multi-time aggregation packet 5.7.2
|
||||
27 MTAP24 Multi-time aggregation packet 5.7.2
|
||||
28 FU-A Fragmentation unit 5.8
|
||||
29 FU-B Fragmentation unit 5.8
|
||||
30-31 undefined -
|
||||
|
||||
|
||||
*/
|
||||
const uint8_t *frame = (uint8_t *) rtppack->data() + rtppack->offset;
|
||||
int length = rtppack->size() - rtppack->offset;
|
||||
NALU nal;
|
||||
|
|
@ -145,7 +162,7 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
|||
FU fu;
|
||||
MakeFU(frame[1], fu);
|
||||
if (fu.S) {
|
||||
//该帧的第一个rtp包
|
||||
//该帧的第一个rtp包 FU-A start
|
||||
char tmp = (nal.forbidden_zero_bit << 7 | nal.nal_ref_idc << 5 | fu.type);
|
||||
_h264frame->buffer.assign("\x0\x0\x0\x1", 4);
|
||||
_h264frame->buffer.push_back(tmp);
|
||||
|
|
@ -164,14 +181,14 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
|
|||
}
|
||||
|
||||
if (!fu.E) {
|
||||
//该帧的中间rtp包
|
||||
//该帧的中间rtp包 FU-A mid
|
||||
_h264frame->buffer.append((char *)frame + 2, length - 2);
|
||||
//该函数return时,保存下当前sequence,以便下次对比seq是否连续
|
||||
_lastSeq = rtppack->sequence;
|
||||
return false;
|
||||
}
|
||||
|
||||
//该帧最后一个rtp包
|
||||
//该帧最后一个rtp包 FU-A end
|
||||
_h264frame->buffer.append((char *)frame + 2, length - 2);
|
||||
_h264frame->timeStamp = rtppack->timeStamp;
|
||||
auto key = _h264frame->keyFrame();
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ namespace mediakit{
|
|||
|
||||
/**
|
||||
* h264 rtp解码类
|
||||
* 将 h264 over rtsp-rtp 解复用出 h264-Frame
|
||||
* rfc3984
|
||||
*/
|
||||
class H264RtpDecoder : public RtpCodec , public ResourcePoolHelper<H264Frame> {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ namespace mediakit{
|
|||
|
||||
/**
|
||||
* h265 rtp解码类
|
||||
* 将 h265 over rtsp-rtp 解复用出 h265-Frame
|
||||
* 《草案(H265-over-RTP)draft-ietf-payload-rtp-h265-07.pdf》
|
||||
*/
|
||||
class H265RtpDecoder : public RtpCodec , public ResourcePoolHelper<H265Frame> {
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -439,7 +439,8 @@ static inline unsigned int showBitsLong(void *pvHandle, int iN)
|
|||
|
||||
if (iN <= 32) {
|
||||
return showBits(ptPtr, iN);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include "Util/mini.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "Network/Socket.h"
|
||||
#include "Common/Parser.h"
|
||||
|
|
@ -47,7 +48,7 @@ class HttpCookieManager;
|
|||
/**
|
||||
* cookie对象,用于保存cookie的一些相关属性
|
||||
*/
|
||||
class HttpServerCookie : public map<string,string> , public noncopyable{
|
||||
class HttpServerCookie : public AnyStorage , public noncopyable{
|
||||
public:
|
||||
typedef std::shared_ptr<HttpServerCookie> Ptr;
|
||||
/**
|
||||
|
|
@ -108,6 +109,8 @@ public:
|
|||
* @return
|
||||
*/
|
||||
std::shared_ptr<lock_guard<mutex> > getLock();
|
||||
|
||||
|
||||
private:
|
||||
string cookieExpireTime() const ;
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -211,13 +211,25 @@ inline bool HttpSession::checkWebSocket(){
|
|||
if(!_parser["Sec-WebSocket-Protocol"].empty()){
|
||||
headerOut["Sec-WebSocket-Protocol"] = _parser["Sec-WebSocket-Protocol"];
|
||||
}
|
||||
sendResponse("101 Switching Protocols",headerOut,"");
|
||||
checkLiveFlvStream(true);
|
||||
|
||||
auto res_cb = [this,headerOut](){
|
||||
_flv_over_websocket = true;
|
||||
sendResponse("101 Switching Protocols",headerOut,"");
|
||||
};
|
||||
|
||||
//判断是否为websocket-flv
|
||||
if(checkLiveFlvStream(res_cb)){
|
||||
//这里是websocket-flv直播请求
|
||||
return true;
|
||||
}
|
||||
|
||||
//如果checkLiveFlvStream返回false,则代表不是websocket-flv,而是普通的websocket连接
|
||||
sendResponse("101 Switching Protocols",headerOut,"");
|
||||
return true;
|
||||
}
|
||||
//http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2
|
||||
//如果url(除去?以及后面的参数)后缀是.flv,那么表明该url是一个http-flv直播。
|
||||
inline bool HttpSession::checkLiveFlvStream(bool over_websocket){
|
||||
inline bool HttpSession::checkLiveFlvStream(const function<void()> &cb){
|
||||
auto pos = strrchr(_parser.Url().data(),'.');
|
||||
if(!pos){
|
||||
//未找到".flv"后缀
|
||||
|
|
@ -240,7 +252,7 @@ inline bool HttpSession::checkLiveFlvStream(bool over_websocket){
|
|||
bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt);
|
||||
|
||||
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||
MediaSource::findAsync(_mediaInfo,weakSelf.lock(), true,[weakSelf,bClose,this,over_websocket](const MediaSource::Ptr &src){
|
||||
MediaSource::findAsync(_mediaInfo,weakSelf.lock(), true,[weakSelf,bClose,this,cb](const MediaSource::Ptr &src){
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf){
|
||||
//本对象已经销毁
|
||||
|
|
@ -249,35 +261,32 @@ inline bool HttpSession::checkLiveFlvStream(bool over_websocket){
|
|||
auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
|
||||
if(!rtmp_src){
|
||||
//未找到该流
|
||||
if(!over_websocket){
|
||||
sendNotFound(bClose);
|
||||
}
|
||||
sendNotFound(bClose);
|
||||
if(bClose){
|
||||
shutdown(SockException(Err_shutdown,"flv stream not found"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
//找到流了
|
||||
auto onRes = [this,rtmp_src,over_websocket](const string &err){
|
||||
auto onRes = [this,rtmp_src,cb](const string &err){
|
||||
bool authSuccess = err.empty();
|
||||
if(!authSuccess){
|
||||
if(!over_websocket){
|
||||
sendResponse("401 Unauthorized", makeHttpHeader(true,err.size()),err);
|
||||
}
|
||||
sendResponse("401 Unauthorized", makeHttpHeader(true,err.size()),err);
|
||||
shutdown(SockException(Err_shutdown,StrPrinter << "401 Unauthorized:" << err));
|
||||
return ;
|
||||
}
|
||||
|
||||
if(!over_websocket) {
|
||||
if(!cb) {
|
||||
//找到rtmp源,发送http头,负载后续发送
|
||||
sendResponse("200 OK", makeHttpHeader(false, 0, get_mime_type(".flv")), "");
|
||||
}else{
|
||||
cb();
|
||||
}
|
||||
|
||||
//开始发送rtmp负载
|
||||
//关闭tcp_nodelay ,优化性能
|
||||
SockUtil::setNoDelay(_sock->rawFD(),false);
|
||||
(*this) << SocketFlags(kSockFlags);
|
||||
_flv_over_websocket = over_websocket;
|
||||
try{
|
||||
start(getPoller(),rtmp_src);
|
||||
}catch (std::exception &ex){
|
||||
|
|
@ -403,7 +412,7 @@ inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const f
|
|||
//上次鉴权失败,如果url发生变更,那么也重新鉴权
|
||||
if (_parser.Params().empty() || _parser.Params() == cookie->getUid()) {
|
||||
//url参数未变,那么判断无权限访问
|
||||
callback(accessErr.empty() ? "无权限访问该目录" : accessErr, nullptr);
|
||||
callback(accessErr.empty() ? "无权限访问该目录" : accessErr.get<string>(), nullptr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -427,9 +436,9 @@ inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const f
|
|||
//对cookie上锁
|
||||
auto lck = cookie->getLock();
|
||||
//记录用户能访问的路径
|
||||
(*cookie)[kCookiePathKey] = cookie_path;
|
||||
(*cookie)[kCookiePathKey].set<string>(cookie_path);
|
||||
//记录能否访问
|
||||
(*cookie)[kAccessErrKey] = errMsg;
|
||||
(*cookie)[kAccessErrKey].set<string>(errMsg);
|
||||
}
|
||||
|
||||
auto strongSelf = weakSelf.lock();
|
||||
|
|
@ -480,7 +489,8 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
|
|||
}
|
||||
|
||||
//再看看是否为http-flv直播请求
|
||||
if(checkLiveFlvStream(false)){
|
||||
if(checkLiveFlvStream()){
|
||||
//若是,return!
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -520,7 +530,7 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
|
|||
}
|
||||
auto headerOut = makeHttpHeader(bClose,strMeun.size());
|
||||
if(cookie){
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey]);
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get<string>());
|
||||
}
|
||||
sendResponse(errMsg.empty() ? "200 OK" : "401 Unauthorized" , headerOut, strMeun);
|
||||
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access folder");
|
||||
|
|
@ -555,7 +565,7 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
|
|||
if(!errMsg.empty()){
|
||||
auto headerOut = makeHttpHeader(bClose,errMsg.size());
|
||||
if(cookie){
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey]);
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get<string>());
|
||||
}
|
||||
sendResponse("401 Unauthorized" , headerOut, errMsg);
|
||||
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file failed");
|
||||
|
|
@ -954,12 +964,12 @@ void HttpSession::onWrite(const Buffer::Ptr &buffer) {
|
|||
header._reserved = 0;
|
||||
header._opcode = WebSocketHeader::BINARY;
|
||||
header._mask_flag = false;
|
||||
WebSocketSplitter::encode(header,(uint8_t *)buffer->data(),buffer->size());
|
||||
WebSocketSplitter::encode(header,buffer);
|
||||
}
|
||||
|
||||
void HttpSession::onWebSocketEncodeData(const uint8_t *ptr,uint64_t len){
|
||||
_ui64TotalBytes += len;
|
||||
SocketHelper::send((char *)ptr,len);
|
||||
void HttpSession::onWebSocketEncodeData(const Buffer::Ptr &buffer){
|
||||
_ui64TotalBytes += buffer->size();
|
||||
send(buffer);
|
||||
}
|
||||
|
||||
void HttpSession::onDetach() {
|
||||
|
|
|
|||
|
|
@ -104,14 +104,13 @@ protected:
|
|||
|
||||
/**
|
||||
* 发送数据进行websocket协议打包后回调
|
||||
* @param ptr
|
||||
* @param len
|
||||
* @param buffer
|
||||
*/
|
||||
void onWebSocketEncodeData(const uint8_t *ptr,uint64_t len) override;
|
||||
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override;
|
||||
private:
|
||||
inline void Handle_Req_GET(int64_t &content_len);
|
||||
inline void Handle_Req_POST(int64_t &content_len);
|
||||
inline bool checkLiveFlvStream(bool over_websocket = false);
|
||||
inline bool checkLiveFlvStream(const function<void()> &cb = nullptr);
|
||||
inline bool checkWebSocket();
|
||||
inline bool emitHttpEvent(bool doInvoke);
|
||||
inline void urlDecode(Parser &parser);
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ protected:
|
|||
header._reserved = 0;
|
||||
header._opcode = WebSocketHeader::TEXT;
|
||||
header._mask_flag = false;
|
||||
strongSelf->WebSocketSplitter::encode(header,(uint8_t *)buf->data(),buf->size());
|
||||
strongSelf->WebSocketSplitter::encode(header,buf);
|
||||
}
|
||||
return buf->size();
|
||||
});
|
||||
|
|
@ -118,12 +118,12 @@ protected:
|
|||
|
||||
switch (header._opcode){
|
||||
case WebSocketHeader::CLOSE:{
|
||||
HttpSessionType::encode(header,nullptr,0);
|
||||
HttpSessionType::encode(header,nullptr);
|
||||
}
|
||||
break;
|
||||
case WebSocketHeader::PING:{
|
||||
const_cast<WebSocketHeader&>(header)._opcode = WebSocketHeader::PONG;
|
||||
HttpSessionType::encode(header,(uint8_t *)_remian_data.data(),_remian_data.size());
|
||||
HttpSessionType::encode(header,std::make_shared<BufferString>(_remian_data));
|
||||
}
|
||||
break;
|
||||
case WebSocketHeader::CONTINUATION:{
|
||||
|
|
@ -132,8 +132,7 @@ protected:
|
|||
break;
|
||||
case WebSocketHeader::TEXT:
|
||||
case WebSocketHeader::BINARY:{
|
||||
BufferString::Ptr buffer = std::make_shared<BufferString>(_remian_data);
|
||||
_session->onRecv(buffer);
|
||||
_session->onRecv(std::make_shared<BufferString>(_remian_data));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
@ -145,11 +144,10 @@ protected:
|
|||
|
||||
/**
|
||||
* 发送数据进行websocket协议打包后回调
|
||||
* @param ptr
|
||||
* @param len
|
||||
* @param buffer
|
||||
*/
|
||||
void onWebSocketEncodeData(const uint8_t *ptr,uint64_t len) override{
|
||||
SocketHelper::send((char *)ptr,len);
|
||||
void onWebSocketEncodeData(const Buffer::Ptr &buffer) override{
|
||||
SocketHelper::send(buffer);
|
||||
}
|
||||
private:
|
||||
typedef function<int(const Buffer::Ptr &buf)> onBeforeSendCB;
|
||||
|
|
|
|||
|
|
@ -164,9 +164,9 @@ void WebSocketSplitter::onPlayloadData(uint8_t *ptr, uint64_t len) {
|
|||
onWebSocketDecodePlayload(*this, _mask_flag ? ptr - len : ptr, len, _playload_offset);
|
||||
}
|
||||
|
||||
void WebSocketSplitter::encode(const WebSocketHeader &header,uint8_t *data, const uint64_t len) {
|
||||
void WebSocketSplitter::encode(const WebSocketHeader &header,const Buffer::Ptr &buffer) {
|
||||
string ret;
|
||||
|
||||
uint64_t len = buffer ? buffer->size() : 0;
|
||||
uint8_t byte = header._fin << 7 | ((header._reserved & 0x07) << 4) | (header._opcode & 0x0F) ;
|
||||
ret.push_back(byte);
|
||||
|
||||
|
|
@ -195,16 +195,16 @@ void WebSocketSplitter::encode(const WebSocketHeader &header,uint8_t *data, cons
|
|||
ret.append((char *)header._mask.data(),4);
|
||||
}
|
||||
|
||||
onWebSocketEncodeData((uint8_t*)ret.data(),ret.size());
|
||||
onWebSocketEncodeData(std::make_shared<BufferString>(std::move(ret)));
|
||||
|
||||
if(len > 0){
|
||||
if(mask_flag){
|
||||
uint8_t *ptr = data;
|
||||
uint8_t *ptr = (uint8_t*)buffer->data();
|
||||
for(int i = 0; i < len ; ++i,++ptr){
|
||||
*(ptr) ^= header._mask[i % 4];
|
||||
}
|
||||
}
|
||||
onWebSocketEncodeData(data,len);
|
||||
onWebSocketEncodeData(buffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,10 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
using namespace std;
|
||||
#include "Network/Buffer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
|
|
@ -85,12 +87,10 @@ public:
|
|||
/**
|
||||
* 编码一个数据包
|
||||
* 将触发2次onWebSocketEncodeData回调
|
||||
* 第一次是数据头,第二次是负载数据
|
||||
* @param header 数据头
|
||||
* @param data 负载数据
|
||||
* @param len 负载数据长度
|
||||
* @param buffer 负载数据
|
||||
*/
|
||||
void encode(const WebSocketHeader &header,uint8_t *data,const uint64_t len);
|
||||
void encode(const WebSocketHeader &header,const Buffer::Ptr &buffer);
|
||||
protected:
|
||||
/**
|
||||
* 收到一个webSocket数据包包头,后续将继续触发onWebSocketDecodePlayload回调
|
||||
|
|
@ -119,7 +119,7 @@ protected:
|
|||
* @param ptr 数据指针
|
||||
* @param len 数据指针长度
|
||||
*/
|
||||
virtual void onWebSocketEncodeData(const uint8_t *ptr,uint64_t len){};
|
||||
virtual void onWebSocketEncodeData(const Buffer::Ptr &buffer){};
|
||||
private:
|
||||
void onPlayloadData(uint8_t *data,uint64_t len);
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -135,9 +135,7 @@ void MP4Muxer::onTrackReady(const Track::Ptr &track) {
|
|||
WarnL << "添加AAC Track失败:" << track_id;
|
||||
return;
|
||||
}
|
||||
track_info info;
|
||||
info.track_id = track_id;
|
||||
_codec_to_trackid[track->getCodecId()] = info;
|
||||
_codec_to_trackid[track->getCodecId()].track_id = track_id;
|
||||
}
|
||||
break;
|
||||
case CodecH264: {
|
||||
|
|
@ -170,9 +168,7 @@ void MP4Muxer::onTrackReady(const Track::Ptr &track) {
|
|||
WarnL << "添加H264 Track失败:" << track_id;
|
||||
return;
|
||||
}
|
||||
track_info info;
|
||||
info.track_id = track_id;
|
||||
_codec_to_trackid[track->getCodecId()] = info;
|
||||
_codec_to_trackid[track->getCodecId()].track_id = track_id;
|
||||
}
|
||||
break;
|
||||
case CodecH265: {
|
||||
|
|
@ -205,9 +201,7 @@ void MP4Muxer::onTrackReady(const Track::Ptr &track) {
|
|||
WarnL << "添加H265 Track失败:" << track_id;
|
||||
return;
|
||||
}
|
||||
track_info info;
|
||||
info.track_id = track_id;
|
||||
_codec_to_trackid[track->getCodecId()] = info;
|
||||
_codec_to_trackid[track->getCodecId()].track_id = track_id;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
#include <ctime>
|
||||
#include <sys/stat.h>
|
||||
#include "Common/config.h"
|
||||
#include "Mp4Maker.h"
|
||||
#include "MP4Recorder.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/NoticeCenter.h"
|
||||
#include "Thread/WorkThreadPool.h"
|
||||
|
|
@ -53,7 +53,7 @@ string timeStr(const char *fmt) {
|
|||
return buffer;
|
||||
}
|
||||
|
||||
Mp4Maker::Mp4Maker(const string& strPath,
|
||||
MP4Recorder::MP4Recorder(const string& strPath,
|
||||
const string &strVhost,
|
||||
const string &strApp,
|
||||
const string &strStreamId) {
|
||||
|
|
@ -64,11 +64,11 @@ Mp4Maker::Mp4Maker(const string& strPath,
|
|||
_info.strVhost = strVhost;
|
||||
_info.strFolder = strPath;
|
||||
}
|
||||
Mp4Maker::~Mp4Maker() {
|
||||
MP4Recorder::~MP4Recorder() {
|
||||
closeFile();
|
||||
}
|
||||
|
||||
void Mp4Maker::createFile() {
|
||||
void MP4Recorder::createFile() {
|
||||
closeFile();
|
||||
auto strDate = timeStr("%Y-%m-%d");
|
||||
auto strTime = timeStr("%H-%M-%S");
|
||||
|
|
@ -100,7 +100,7 @@ void Mp4Maker::createFile() {
|
|||
}
|
||||
}
|
||||
|
||||
void Mp4Maker::asyncClose() {
|
||||
void MP4Recorder::asyncClose() {
|
||||
auto muxer = _muxer;
|
||||
auto strFileTmp = _strFileTmp;
|
||||
auto strFile = _strFile;
|
||||
|
|
@ -121,14 +121,14 @@ void Mp4Maker::asyncClose() {
|
|||
});
|
||||
}
|
||||
|
||||
void Mp4Maker::closeFile() {
|
||||
void MP4Recorder::closeFile() {
|
||||
if (_muxer) {
|
||||
asyncClose();
|
||||
_muxer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Mp4Maker::onTrackFrame(const Frame::Ptr &frame) {
|
||||
void MP4Recorder::onTrackFrame(const Frame::Ptr &frame) {
|
||||
GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);
|
||||
if(!_muxer || ((_createFileTicker.elapsedTime() > recordSec * 1000) &&
|
||||
(!_haveVideo || (_haveVideo && frame->keyFrame()))) ){
|
||||
|
|
@ -145,7 +145,7 @@ void Mp4Maker::onTrackFrame(const Frame::Ptr &frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void Mp4Maker::onTrackReady(const Track::Ptr & track){
|
||||
void MP4Recorder::onTrackReady(const Track::Ptr & track){
|
||||
//保存所有的track,为创建MP4MuxerFile做准备
|
||||
_tracks.emplace_back(track);
|
||||
if(track->getTrackType() == TrackVideo){
|
||||
|
|
@ -55,14 +55,14 @@ public:
|
|||
string strStreamId;//流ID
|
||||
string strVhost;//vhost
|
||||
};
|
||||
class Mp4Maker : public MediaSink{
|
||||
class MP4Recorder : public MediaSink{
|
||||
public:
|
||||
typedef std::shared_ptr<Mp4Maker> Ptr;
|
||||
Mp4Maker(const string &strPath,
|
||||
typedef std::shared_ptr<MP4Recorder> Ptr;
|
||||
MP4Recorder(const string &strPath,
|
||||
const string &strVhost ,
|
||||
const string &strApp,
|
||||
const string &strStreamId);
|
||||
virtual ~Mp4Maker();
|
||||
virtual ~MP4Recorder();
|
||||
private:
|
||||
/**
|
||||
* 某Track输出frame,在onAllTrackReady触发后才会调用此方法
|
||||
|
|
@ -58,15 +58,15 @@ MediaRecorder::MediaRecorder(const string &strVhost_tmp,
|
|||
string m3u8FilePath;
|
||||
if(enableVhost){
|
||||
m3u8FilePath = hlsPath + "/" + strVhost + "/" + strApp + "/" + strId + "/hls.m3u8";
|
||||
_hlsMaker.reset(new HlsRecorder(m3u8FilePath,string(VHOST_KEY) + "=" + strVhost ,hlsBufSize, hlsDuration, hlsNum));
|
||||
_hlsRecorder.reset(new HlsRecorder(m3u8FilePath,string(VHOST_KEY) + "=" + strVhost ,hlsBufSize, hlsDuration, hlsNum));
|
||||
}else{
|
||||
m3u8FilePath = hlsPath + "/" + strApp + "/" + strId + "/hls.m3u8";
|
||||
_hlsMaker.reset(new HlsRecorder(m3u8FilePath,"",hlsBufSize, hlsDuration, hlsNum));
|
||||
_hlsRecorder.reset(new HlsRecorder(m3u8FilePath,"",hlsBufSize, hlsDuration, hlsNum));
|
||||
}
|
||||
}
|
||||
#endif //defined(ENABLE_HLS)
|
||||
|
||||
#if defined(ENABLE_MP4V2)
|
||||
#if defined(ENABLE_MP4RECORD)
|
||||
GET_CONFIG(string,recordPath,Record::kFilePath);
|
||||
GET_CONFIG(string,recordAppName,Record::kAppName);
|
||||
|
||||
|
|
@ -77,9 +77,9 @@ MediaRecorder::MediaRecorder(const string &strVhost_tmp,
|
|||
} else {
|
||||
mp4FilePath = recordPath + "/" + recordAppName + "/" + strApp + "/" + strId + "/";
|
||||
}
|
||||
_mp4Maker.reset(new Mp4Maker(mp4FilePath,strVhost,strApp,strId));
|
||||
_mp4Recorder.reset(new MP4Recorder(mp4FilePath,strVhost,strApp,strId));
|
||||
}
|
||||
#endif //defined(ENABLE_MP4V2)
|
||||
#endif //defined(ENABLE_MP4RECORD)
|
||||
}
|
||||
|
||||
MediaRecorder::~MediaRecorder() {
|
||||
|
|
@ -87,28 +87,28 @@ MediaRecorder::~MediaRecorder() {
|
|||
|
||||
void MediaRecorder::inputFrame(const Frame::Ptr &frame) {
|
||||
#if defined(ENABLE_HLS)
|
||||
if (_hlsMaker) {
|
||||
_hlsMaker->inputFrame(frame);
|
||||
}
|
||||
#endif //defined(ENABLE_HLS)
|
||||
|
||||
#if defined(ENABLE_MP4V2)
|
||||
if (_mp4Maker) {
|
||||
_mp4Maker->inputFrame(frame);
|
||||
}
|
||||
#endif //defined(ENABLE_MP4V2)
|
||||
}
|
||||
|
||||
void MediaRecorder::addTrack(const Track::Ptr &track) {
|
||||
#if defined(ENABLE_HLS)
|
||||
if (_hlsMaker) {
|
||||
_hlsMaker->addTrack(track);
|
||||
if (_hlsRecorder) {
|
||||
_hlsRecorder->inputFrame(frame);
|
||||
}
|
||||
#endif //defined(ENABLE_HLS)
|
||||
|
||||
#if defined(ENABLE_MP4RECORD)
|
||||
if (_mp4Maker) {
|
||||
_mp4Maker->addTrack(track);
|
||||
if (_mp4Recorder) {
|
||||
_mp4Recorder->inputFrame(frame);
|
||||
}
|
||||
#endif //defined(ENABLE_MP4RECORD)
|
||||
}
|
||||
|
||||
void MediaRecorder::addTrack(const Track::Ptr &track) {
|
||||
#if defined(ENABLE_HLS)
|
||||
if (_hlsRecorder) {
|
||||
_hlsRecorder->addTrack(track);
|
||||
}
|
||||
#endif //defined(ENABLE_HLS)
|
||||
|
||||
#if defined(ENABLE_MP4RECORD)
|
||||
if (_mp4Recorder) {
|
||||
_mp4Recorder->addTrack(track);
|
||||
}
|
||||
#endif //defined(ENABLE_MP4RECORD)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
#include <memory>
|
||||
#include "Player/PlayerBase.h"
|
||||
#include "Common/MediaSink.h"
|
||||
#include "Mp4Maker.h"
|
||||
#include "MP4Recorder.h"
|
||||
#include "HlsRecorder.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
|
@ -61,11 +61,11 @@ public:
|
|||
void addTrack(const Track::Ptr & track) override;
|
||||
private:
|
||||
#if defined(ENABLE_HLS)
|
||||
std::shared_ptr<HlsRecorder> _hlsMaker;
|
||||
std::shared_ptr<HlsRecorder> _hlsRecorder;
|
||||
#endif //defined(ENABLE_HLS)
|
||||
|
||||
#if defined(ENABLE_MP4RECORD)
|
||||
std::shared_ptr<Mp4Maker> _mp4Maker;
|
||||
std::shared_ptr<MP4Recorder> _mp4Recorder;
|
||||
#endif //defined(ENABLE_MP4RECORD)
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -29,16 +29,22 @@
|
|||
namespace mediakit {
|
||||
|
||||
void Stamp::revise(uint32_t dts, uint32_t pts, int64_t &dts_out, int64_t &pts_out) {
|
||||
if(!pts){
|
||||
//没有播放时间戳,使其赋值为解码时间戳
|
||||
pts = dts;
|
||||
}
|
||||
//pts和dts的差值
|
||||
int pts_dts_diff = pts - dts;
|
||||
|
||||
if(_first){
|
||||
//记录第一次时间戳,后面好计算时间戳增量
|
||||
_start_dts = dts;
|
||||
_first = false;
|
||||
_ticker = std::make_shared<SmoothTicker>();
|
||||
_ticker.resetTime();
|
||||
}
|
||||
//pts和dts的差值
|
||||
int pts_dts_diff = pts - dts;
|
||||
if(_modifyStamp){
|
||||
dts = _ticker->elapsedTime();
|
||||
if (!dts) {
|
||||
//没有解码时间戳,我们生成解码时间戳
|
||||
dts = _ticker.elapsedTime();
|
||||
}
|
||||
|
||||
//相对时间戳
|
||||
|
|
@ -60,11 +66,6 @@ void Stamp::revise(uint32_t dts, uint32_t pts, int64_t &dts_out, int64_t &pts_ou
|
|||
_dts_inc = dts_out;
|
||||
|
||||
//////////////以下是播放时间戳的计算//////////////////
|
||||
if(!pts){
|
||||
//没有播放时间戳
|
||||
pts = dts;
|
||||
}
|
||||
|
||||
if(pts_dts_diff > 200 || pts_dts_diff < -200){
|
||||
//如果差值大于200毫秒,则认为由于回环导致时间戳错乱了
|
||||
pts_dts_diff = 0;
|
||||
|
|
|
|||
|
|
@ -33,17 +33,18 @@ using namespace toolkit;
|
|||
|
||||
namespace mediakit {
|
||||
|
||||
//该类解决时间戳回环、回退问题
|
||||
//计算相对时间戳或者产生平滑时间戳
|
||||
class Stamp {
|
||||
public:
|
||||
Stamp(bool modifyStamp = false) {_modifyStamp = modifyStamp;};
|
||||
Stamp() = default;
|
||||
~Stamp() = default;
|
||||
void revise(uint32_t dts, uint32_t pts, int64_t &dts_out, int64_t &pts_out);
|
||||
private:
|
||||
int64_t _start_dts = 0;
|
||||
int64_t _dts_inc = 0;
|
||||
bool _first = true;
|
||||
bool _modifyStamp;
|
||||
std::shared_ptr<SmoothTicker> _ticker;
|
||||
SmoothTicker _ticker;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
|
|
|||
|
|
@ -42,19 +42,13 @@ TsMuxer::~TsMuxer() {
|
|||
void TsMuxer::addTrack(const Track::Ptr &track) {
|
||||
switch (track->getCodecId()){
|
||||
case CodecH264: {
|
||||
track_info info;
|
||||
info.track_id = mpeg_ts_add_stream(_context, PSI_STREAM_H264, nullptr, 0);
|
||||
_codec_to_trackid[track->getCodecId()] = info;
|
||||
_codec_to_trackid[track->getCodecId()].track_id = mpeg_ts_add_stream(_context, PSI_STREAM_H264, nullptr, 0);
|
||||
} break;
|
||||
case CodecH265: {
|
||||
track_info info;
|
||||
info.track_id = mpeg_ts_add_stream(_context, PSI_STREAM_H265, nullptr, 0);
|
||||
_codec_to_trackid[track->getCodecId()] = info;
|
||||
_codec_to_trackid[track->getCodecId()].track_id = mpeg_ts_add_stream(_context, PSI_STREAM_H265, nullptr, 0);
|
||||
}break;
|
||||
case CodecAAC: {
|
||||
track_info info;
|
||||
info.track_id = mpeg_ts_add_stream(_context, PSI_STREAM_AAC, nullptr, 0);
|
||||
_codec_to_trackid[track->getCodecId()] = info;
|
||||
_codec_to_trackid[track->getCodecId()].track_id = mpeg_ts_add_stream(_context, PSI_STREAM_AAC, nullptr, 0);
|
||||
}break;
|
||||
default:
|
||||
break;
|
||||
|
|
@ -73,38 +67,28 @@ void TsMuxer::inputFrame(const Frame::Ptr &frame) {
|
|||
switch (frame->getCodecId()){
|
||||
case CodecH265:
|
||||
case CodecH264: {
|
||||
|
||||
Buffer::Ptr merged_frame ;
|
||||
if(frame->configFrame()){
|
||||
//配置帧,缓存后直接返回,以便下次输入关键帧时使用
|
||||
_config_frame_cache.append("\x00\x00\x00\x01",4);
|
||||
_config_frame_cache.append(frame->data() + frame->prefixSize(),frame->size() - frame->prefixSize());
|
||||
break;
|
||||
}
|
||||
|
||||
if(frame->keyFrame()){
|
||||
//关键帧
|
||||
if(!_config_frame_cache.empty()){
|
||||
//有配置帧,那么配置帧合并关键帧后输入ts打包
|
||||
_config_frame_cache.append("\x00\x00\x00\x01",4);
|
||||
_config_frame_cache.append(frame->data() + frame->prefixSize(),frame->size() - frame->prefixSize());
|
||||
merged_frame = std::make_shared<BufferString>(std::move(_config_frame_cache));
|
||||
_config_frame_cache.clear();
|
||||
}else{
|
||||
//这是非第一个的关键帧(h265有多种关键帧)
|
||||
merged_frame = frame;
|
||||
//这里的代码逻辑是让SPS、PPS、IDR这些时间戳相同的帧打包到一起当做一个帧处理,
|
||||
if (!_frameCached.empty() && _frameCached.back()->dts() != frame->dts()) {
|
||||
Frame::Ptr back = _frameCached.back();
|
||||
Buffer::Ptr merged_frame = back;
|
||||
if(_frameCached.size() != 1){
|
||||
string merged;
|
||||
_frameCached.for_each([&](const Frame::Ptr &frame){
|
||||
if(frame->prefixSize()){
|
||||
merged.append(frame->data(),frame->size());
|
||||
} else{
|
||||
merged.append("\x00\x00\x00\x01",4);
|
||||
merged.append(frame->data(),frame->size());
|
||||
}
|
||||
});
|
||||
merged_frame = std::make_shared<BufferString>(std::move(merged));
|
||||
}
|
||||
}else{
|
||||
//这里是普通帧,例如B/P,
|
||||
merged_frame = frame;
|
||||
//sps、pps这些配置帧清空掉
|
||||
_config_frame_cache.clear();
|
||||
track_info.stamp.revise(back->dts(),back->pts(),dts_out,pts_out);
|
||||
_timestamp = dts_out;
|
||||
mpeg_ts_write(_context, track_info.track_id, back->keyFrame() ? 0x0001 : 0, pts_out * 90LL, dts_out * 90LL, merged_frame->data(), merged_frame->size());
|
||||
_frameCached.clear();
|
||||
}
|
||||
|
||||
//输入到ts文件
|
||||
track_info.stamp.revise(frame->dts(),frame->pts(),dts_out,pts_out);
|
||||
_timestamp = dts_out;
|
||||
mpeg_ts_write(_context, track_info.track_id, frame->keyFrame() ? 0x0001 : 0, pts_out * 90LL, dts_out * 90LL, merged_frame->data(), merged_frame->size());
|
||||
_frameCached.emplace_back(Frame::getCacheAbleFrame(frame));
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
|
|
|
|||
|
|
@ -38,12 +38,12 @@ using namespace toolkit;
|
|||
|
||||
namespace mediakit {
|
||||
|
||||
class TsMuxer {
|
||||
class TsMuxer : public MediaSink {
|
||||
public:
|
||||
TsMuxer();
|
||||
virtual ~TsMuxer();
|
||||
void addTrack(const Track::Ptr &track) ;
|
||||
void inputFrame(const Frame::Ptr &frame) ;
|
||||
void addTrack(const Track::Ptr &track) override;
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
protected:
|
||||
virtual void onTs(const void *packet, int bytes,uint32_t timestamp,int flags) = 0;
|
||||
void resetTracks();
|
||||
|
|
@ -60,7 +60,7 @@ private:
|
|||
Stamp stamp;
|
||||
};
|
||||
unordered_map<int,track_info> _codec_to_trackid;
|
||||
string _config_frame_cache;
|
||||
List<Frame::Ptr> _frameCached;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
|
|
|||
|
|
@ -76,8 +76,6 @@ void FlvMuxer::start(const EventPoller::Ptr &poller,const RtmpMediaSource::Ptr &
|
|||
}
|
||||
|
||||
void FlvMuxer::onWriteFlvHeader(const RtmpMediaSource::Ptr &mediaSrc) {
|
||||
CLEAR_ARR(_aui32FirstStamp);
|
||||
|
||||
//发送flv文件头
|
||||
char flv_file_header[] = "FLV\x1\x5\x0\x0\x0\x9"; // have audio and have video
|
||||
bool is_have_audio = false,is_have_video = false;
|
||||
|
|
@ -158,20 +156,9 @@ void FlvMuxer::onWriteFlvTag(uint8_t ui8Type, const Buffer::Ptr &buffer, uint32_
|
|||
}
|
||||
|
||||
void FlvMuxer::onWriteRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
auto modifiedStamp = pkt->timeStamp;
|
||||
auto &firstStamp = _aui32FirstStamp[pkt->typeId % 2];
|
||||
if(!firstStamp){
|
||||
firstStamp = modifiedStamp;
|
||||
}
|
||||
if(modifiedStamp >= firstStamp){
|
||||
//计算时间戳增量
|
||||
modifiedStamp -= firstStamp;
|
||||
}else{
|
||||
//发生回环,重新计算时间戳增量
|
||||
CLEAR_ARR(_aui32FirstStamp);
|
||||
modifiedStamp = 0;
|
||||
}
|
||||
onWriteFlvTag(pkt, modifiedStamp);
|
||||
int64_t dts_out;
|
||||
_stamp[pkt->typeId % 2].revise(pkt->timeStamp, 0, dts_out, dts_out);
|
||||
onWriteFlvTag(pkt, dts_out);
|
||||
}
|
||||
|
||||
void FlvMuxer::stop() {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include "Rtmp/Rtmp.h"
|
||||
#include "Rtmp/RtmpMediaSource.h"
|
||||
#include "Network/Socket.h"
|
||||
#include "MediaFile/Stamp.h"
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
|
@ -52,7 +53,8 @@ private:
|
|||
void onWriteFlvTag(uint8_t ui8Type, const Buffer::Ptr &buffer, uint32_t ui32TimeStamp);
|
||||
private:
|
||||
RtmpMediaSource::RingType::RingReader::Ptr _ring_reader;
|
||||
uint32_t _aui32FirstStamp[2] = {0};
|
||||
//时间戳修整器
|
||||
Stamp _stamp[2];
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ using namespace toolkit;
|
|||
using namespace mediakit::Client;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
//实现了rtmp播放器协议部分的功能,及数据接收功能
|
||||
class RtmpPlayer:public PlayerBase, public TcpClient, public RtmpProtocol{
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpPlayer> Ptr;
|
||||
|
|
@ -63,11 +63,11 @@ protected:
|
|||
void onMediaData_l(const RtmpPacket::Ptr &chunkData);
|
||||
void onPlayResult_l(const SockException &ex);
|
||||
|
||||
//for Tcpclient
|
||||
//form Tcpclient
|
||||
void onRecv(const Buffer::Ptr &pBuf) override;
|
||||
void onConnect(const SockException &err) override;
|
||||
void onErr(const SockException &ex) override;
|
||||
//fro RtmpProtocol
|
||||
//from RtmpProtocol
|
||||
void onRtmpChunk(RtmpPacket &chunkData) override;
|
||||
void onStreamDry(uint32_t ui32StreamId) override;
|
||||
void onSendRawData(const Buffer::Ptr &buffer) override{
|
||||
|
|
|
|||
|
|
@ -532,10 +532,6 @@ void RtmpProtocol::handle_rtmp() {
|
|||
static const size_t HEADER_LENGTH[] = { 12, 8, 4, 1 };
|
||||
size_t iHeaderLen = HEADER_LENGTH[flags >> 6];
|
||||
_iNowChunkID = flags & 0x3f;
|
||||
if(_iNowChunkID >10){
|
||||
int i=0;
|
||||
i++;
|
||||
}
|
||||
switch (_iNowChunkID) {
|
||||
case 0: {
|
||||
//0 值表示二字节形式,并且 ID 范围 64 - 319
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ void RtmpSession::onError(const SockException& err) {
|
|||
}
|
||||
|
||||
void RtmpSession::onManager() {
|
||||
GET_CONFIG(uint32_t,handshake_sec,Rtmp::kKeepAliveSecond);
|
||||
GET_CONFIG(uint32_t,handshake_sec,Rtmp::kHandshakeSecond);
|
||||
GET_CONFIG(uint32_t,keep_alive_sec,Rtmp::kKeepAliveSecond);
|
||||
|
||||
if (_ticker.createdTime() > handshake_sec * 1000) {
|
||||
|
|
@ -397,20 +397,20 @@ void RtmpSession::setMetaData(AMFDecoder &dec) {
|
|||
|
||||
void RtmpSession::onProcessCmd(AMFDecoder &dec) {
|
||||
typedef void (RtmpSession::*rtmpCMDHandle)(AMFDecoder &dec);
|
||||
static unordered_map<string, rtmpCMDHandle> g_mapCmd;
|
||||
static unordered_map<string, rtmpCMDHandle> s_cmd_functions;
|
||||
static onceToken token([]() {
|
||||
g_mapCmd.emplace("connect",&RtmpSession::onCmd_connect);
|
||||
g_mapCmd.emplace("createStream",&RtmpSession::onCmd_createStream);
|
||||
g_mapCmd.emplace("publish",&RtmpSession::onCmd_publish);
|
||||
g_mapCmd.emplace("deleteStream",&RtmpSession::onCmd_deleteStream);
|
||||
g_mapCmd.emplace("play",&RtmpSession::onCmd_play);
|
||||
g_mapCmd.emplace("play2",&RtmpSession::onCmd_play2);
|
||||
g_mapCmd.emplace("seek",&RtmpSession::onCmd_seek);
|
||||
g_mapCmd.emplace("pause",&RtmpSession::onCmd_pause);}, []() {});
|
||||
s_cmd_functions.emplace("connect",&RtmpSession::onCmd_connect);
|
||||
s_cmd_functions.emplace("createStream",&RtmpSession::onCmd_createStream);
|
||||
s_cmd_functions.emplace("publish",&RtmpSession::onCmd_publish);
|
||||
s_cmd_functions.emplace("deleteStream",&RtmpSession::onCmd_deleteStream);
|
||||
s_cmd_functions.emplace("play",&RtmpSession::onCmd_play);
|
||||
s_cmd_functions.emplace("play2",&RtmpSession::onCmd_play2);
|
||||
s_cmd_functions.emplace("seek",&RtmpSession::onCmd_seek);
|
||||
s_cmd_functions.emplace("pause",&RtmpSession::onCmd_pause);}, []() {});
|
||||
|
||||
std::string method = dec.load<std::string>();
|
||||
auto it = g_mapCmd.find(method);
|
||||
if (it == g_mapCmd.end()) {
|
||||
auto it = s_cmd_functions.find(method);
|
||||
if (it == s_cmd_functions.end()) {
|
||||
TraceP(this) << "can not support cmd:" << method;
|
||||
return;
|
||||
}
|
||||
|
|
@ -444,10 +444,12 @@ void RtmpSession::onRtmpChunk(RtmpPacket &chunkData) {
|
|||
throw std::runtime_error("Not a rtmp publisher!");
|
||||
}
|
||||
GET_CONFIG(bool,rtmp_modify_stamp,Rtmp::kModifyStamp);
|
||||
if(rtmp_modify_stamp){
|
||||
chunkData.timeStamp = _stampTicker[chunkData.typeId % 2].elapsedTime();
|
||||
}
|
||||
_pPublisherSrc->onWrite(std::make_shared<RtmpPacket>(std::move(chunkData)));
|
||||
if(rtmp_modify_stamp){
|
||||
int64_t dts_out;
|
||||
_stamp[chunkData.typeId % 2].revise(0, 0, dts_out, dts_out);
|
||||
chunkData.timeStamp = dts_out;
|
||||
}
|
||||
_pPublisherSrc->onWrite(std::make_shared<RtmpPacket>(std::move(chunkData)));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
@ -473,20 +475,10 @@ void RtmpSession::onCmd_seek(AMFDecoder &dec) {
|
|||
}
|
||||
|
||||
void RtmpSession::onSendMedia(const RtmpPacket::Ptr &pkt) {
|
||||
auto modifiedStamp = pkt->timeStamp;
|
||||
auto &firstStamp = _aui32FirstStamp[pkt->typeId % 2];
|
||||
if(!firstStamp){
|
||||
firstStamp = modifiedStamp;
|
||||
}
|
||||
if(modifiedStamp >= firstStamp){
|
||||
//计算时间戳增量
|
||||
modifiedStamp -= firstStamp;
|
||||
}else{
|
||||
//发生回环,重新计算时间戳增量
|
||||
CLEAR_ARR(_aui32FirstStamp);
|
||||
modifiedStamp = 0;
|
||||
}
|
||||
sendRtmp(pkt->typeId, pkt->streamId, pkt, modifiedStamp, pkt->chunkId);
|
||||
//rtmp播放器时间戳从零开始
|
||||
int64_t dts_out;
|
||||
_stamp[pkt->typeId % 2].revise(pkt->timeStamp, 0, dts_out, dts_out);
|
||||
sendRtmp(pkt->typeId, pkt->streamId, pkt, dts_out, pkt->chunkId);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@
|
|||
#include "Util/util.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "Network/TcpSession.h"
|
||||
#include "MediaFile/Stamp.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
|
@ -88,11 +90,11 @@ private:
|
|||
MediaInfo _mediaInfo;
|
||||
double _dNowReqID = 0;
|
||||
Ticker _ticker;//数据接收时间
|
||||
SmoothTicker _stampTicker[2];//时间戳生产器
|
||||
RingBuffer<RtmpPacket::Ptr>::RingReader::Ptr _pRingReader;
|
||||
std::shared_ptr<RtmpMediaSource> _pPublisherSrc;
|
||||
std::weak_ptr<RtmpMediaSource> _pPlayerSrc;
|
||||
uint32_t _aui32FirstStamp[2] = {0};
|
||||
//时间戳修整器
|
||||
Stamp _stamp[2];
|
||||
//消耗的总流量
|
||||
uint64_t _ui64TotalBytes = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -96,6 +96,17 @@ public:
|
|||
int readerCount() override {
|
||||
return RtmpMediaSource::readerCount() + (_muxer ? _muxer->readerCount() : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取track
|
||||
* @return
|
||||
*/
|
||||
vector<Track::Ptr> getTracks(bool trackReady) const override {
|
||||
if(!_demuxer){
|
||||
return this->RtmpMediaSource::getTracks(trackReady);
|
||||
}
|
||||
return _demuxer->getTracks(trackReady);
|
||||
}
|
||||
private:
|
||||
RtmpDemuxer::Ptr _demuxer;
|
||||
MultiMediaSourceMuxer::Ptr _muxer;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
enum AMFType {
|
||||
AMF_NUMBER,
|
||||
AMF_INTEGER,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue