mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-26 12:37:09 +08:00
Merge branch 'master' into dev
This commit is contained in:
commit
228cbb1127
13
.github/workflows/docker.yml
vendored
13
.github/workflows/docker.yml
vendored
@ -1,6 +1,17 @@
|
||||
name: Docker
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
- "feature/*"
|
||||
- "release/*"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- "master"
|
||||
- "feature/*"
|
||||
- "release/*"
|
||||
|
||||
env:
|
||||
# Use docker.io for Docker Hub if empty
|
||||
|
@ -47,44 +47,44 @@ set(MediaServer_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/media-server")
|
||||
# TODO: 补一个函数处理各种库
|
||||
|
||||
# 添加 mov、flv 库用于 MP4 录制
|
||||
if(ENABLE_MP4)
|
||||
message(STATUS "ENABLE_MP4 defined")
|
||||
|
||||
if (ENABLE_MP4 OR ENABLE_HLS_FMP4)
|
||||
# MOV
|
||||
set(MediaServer_MOV_ROOT ${MediaServer_ROOT}/libmov)
|
||||
aux_source_directory(${MediaServer_MOV_ROOT}/include MOV_SRC_LIST)
|
||||
aux_source_directory(${MediaServer_MOV_ROOT}/source MOV_SRC_LIST)
|
||||
aux_source_directory(${MediaServer_MOV_ROOT}/source MOV_SRC_LIST)
|
||||
add_library(mov STATIC ${MOV_SRC_LIST})
|
||||
add_library(MediaServer::mov ALIAS mov)
|
||||
target_compile_definitions(mov
|
||||
PUBLIC -DENABLE_MP4)
|
||||
target_compile_options(mov
|
||||
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
||||
target_compile_options(mov PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
||||
target_include_directories(mov
|
||||
PRIVATE
|
||||
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>"
|
||||
PUBLIC
|
||||
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>")
|
||||
PRIVATE
|
||||
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>"
|
||||
PUBLIC
|
||||
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>")
|
||||
|
||||
# FLV
|
||||
set(MediaServer_FLV_ROOT ${MediaServer_ROOT}/libflv)
|
||||
aux_source_directory(${MediaServer_FLV_ROOT}/include FLV_SRC_LIST)
|
||||
aux_source_directory(${MediaServer_FLV_ROOT}/source FLV_SRC_LIST)
|
||||
aux_source_directory(${MediaServer_FLV_ROOT}/source FLV_SRC_LIST)
|
||||
add_library(flv STATIC ${FLV_SRC_LIST})
|
||||
add_library(MediaServer::flv ALIAS flv)
|
||||
target_compile_options(flv
|
||||
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
||||
target_compile_options(flv PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
||||
target_include_directories(flv
|
||||
PRIVATE
|
||||
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>"
|
||||
PUBLIC
|
||||
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>")
|
||||
PRIVATE
|
||||
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>"
|
||||
PUBLIC
|
||||
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>")
|
||||
|
||||
update_cached_list(MK_LINK_LIBRARIES
|
||||
MediaServer::flv MediaServer::mov)
|
||||
update_cached_list(MK_COMPILE_DEFINITIONS
|
||||
ENABLE_MP4)
|
||||
endif()
|
||||
update_cached_list(MK_LINK_LIBRARIES MediaServer::flv MediaServer::mov)
|
||||
|
||||
if (ENABLE_MP4)
|
||||
message(STATUS "ENABLE_MP4 defined")
|
||||
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_MP4)
|
||||
endif ()
|
||||
if (ENABLE_HLS_FMP4)
|
||||
message(STATUS "ENABLE_HLS_FMP4 defined")
|
||||
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS_FMP4)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# 添加 mpeg 用于支持 ts 生成
|
||||
if(ENABLE_RTPPROXY OR ENABLE_HLS)
|
||||
@ -108,9 +108,11 @@ if(ENABLE_RTPPROXY OR ENABLE_HLS)
|
||||
|
||||
update_cached_list(MK_LINK_LIBRARIES MediaServer::mpeg)
|
||||
if(ENABLE_RTPPROXY)
|
||||
message(STATUS "ENABLE_RTPPROXY defined")
|
||||
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_RTPPROXY)
|
||||
endif()
|
||||
if(ENABLE_HLS)
|
||||
message(STATUS "ENABLE_HLS defined")
|
||||
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS)
|
||||
endif()
|
||||
endif()
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit e4744a0a523817356f2ec995ee5a732264c31629
|
||||
Subproject commit 5d74e09b8c84cccc46036ed2ef1a62f670c423d4
|
13
AUTHORS
13
AUTHORS
@ -73,4 +73,15 @@ WuPeng <wp@zafu.edu.cn>
|
||||
[ljx0305](https://github.com/ljx0305)
|
||||
[朱如洪 ](https://github.com/zhu410289616)
|
||||
[lijin](https://github.com/1461521844lijin)
|
||||
[PioLing](https://github.com/PioLing)
|
||||
[PioLing](https://github.com/PioLing)
|
||||
[BackT0TheFuture](https://github.com/BackT0TheFuture)
|
||||
[perara](https://github.com/perara)
|
||||
[codeRATny](https://github.com/codeRATny)
|
||||
[dengjfzh](https://github.com/dengjfzh)
|
||||
[百鸣](https://github.com/ixingqiao)
|
||||
[fruit Juice](https://github.com/xuandu)
|
||||
[tbago](https://github.com/tbago)
|
||||
[Luosh](https://github.com/Luosh)
|
||||
[linxiaoyan87](https://github.com/linxiaoyan)
|
||||
[waken](https://github.com/mc373906408)
|
||||
[Deepslient](https://github.com/Deepslient)
|
@ -41,6 +41,7 @@ option(ENABLE_HLS "Enable HLS" ON)
|
||||
option(ENABLE_JEMALLOC_STATIC "Enable static linking to the jemalloc library" OFF)
|
||||
option(ENABLE_MEM_DEBUG "Enable Memory Debug" OFF)
|
||||
option(ENABLE_MP4 "Enable MP4" ON)
|
||||
option(ENABLE_HLS_FMP4 "Enable HLS-FMP4" ON)
|
||||
option(ENABLE_MSVC_MT "Enable MSVC Mt/Mtd lib" ON)
|
||||
option(ENABLE_MYSQL "Enable MySQL" OFF)
|
||||
option(ENABLE_OPENSSL "Enable OpenSSL" ON)
|
||||
@ -200,8 +201,8 @@ if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
|
||||
endif()
|
||||
|
||||
# mediakit 以及各个 runtime 依赖
|
||||
update_cached_list(MK_LINK_LIBRARIES "")
|
||||
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_VERSION)
|
||||
update_cached(MK_LINK_LIBRARIES "")
|
||||
update_cached(MK_COMPILE_DEFINITIONS ENABLE_VERSION)
|
||||
|
||||
if (DISABLE_REPORT)
|
||||
update_cached_list(MK_COMPILE_DEFINITIONS DISABLE_REPORT)
|
||||
@ -461,7 +462,7 @@ if(ENABLE_SERVER)
|
||||
endif()
|
||||
|
||||
# Android 会 add_subdirectory 并依赖该变量
|
||||
if(ENABLE_SERVER_LIB)
|
||||
if(ENABLE_SERVER_LIB AND NOT CMAKE_PARENT_LIST_FILE STREQUAL CMAKE_CURRENT_LIST_FILE)
|
||||
set(MK_LINK_LIBRARIES ${MK_LINK_LIBRARIES} PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
|
75
README.md
75
README.md
@ -46,7 +46,7 @@
|
||||
|
||||
## 功能清单
|
||||
### 功能一览
|
||||
<img width="800" alt="功能一览" src="https://user-images.githubusercontent.com/11495632/190864440-91c45f8f-480f-43db-8110-5bb44e6300ff.png">
|
||||
<img width="800" alt="功能一览" src="https://github.com/ZLMediaKit/ZLMediaKit/assets/11495632/481ea769-5b27-495e-bf7d-31191e6af9d2">
|
||||
|
||||
- RTSP[S]
|
||||
- RTSP[S] 服务器,支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备
|
||||
@ -69,9 +69,10 @@
|
||||
- 支持H264/H265/AAC/G711/OPUS编码,其他编码能转发但不能转协议
|
||||
- 支持[RTMP-H265](https://github.com/ksvc/FFmpeg/wiki)
|
||||
- 支持[RTMP-OPUS](https://github.com/ZLMediaKit/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81)
|
||||
- 支持[enhanced-rtmp(H265)](https://github.com/veovera/enhanced-rtmp)
|
||||
|
||||
- HLS
|
||||
- 支持HLS文件生成,自带HTTP文件服务器
|
||||
- 支持HLS文件(mpegts/fmp4)生成,自带HTTP文件服务器
|
||||
- 通过cookie追踪技术,可以模拟HLS播放为长连接,可以实现HLS按需拉流、播放统计等业务
|
||||
- 支持HLS播发器,支持拉流HLS转rtsp/rtmp/mp4
|
||||
- 支持H264/H265/AAC/G711/OPUS编码
|
||||
@ -168,30 +169,36 @@ bash build_docker_images.sh
|
||||
```
|
||||
|
||||
## 合作项目
|
||||
|
||||
- 视频管理平台
|
||||
- [wvp-GB28181-pro](https://github.com/648540858/wvp-GB28181-pro) java实现的开箱即用的GB28181协议视频平台
|
||||
- [AKStream](https://github.com/chatop2020/AKStream) c#实现的全功能的软NVR接口/GB28181平台
|
||||
- [BXC_SipServer](https://github.com/any12345com/BXC_SipServer) c++实现的国标GB28181流媒体信令服务器
|
||||
- [gosip](https://github.com/panjjo/gosip) golang实现的GB28181服务器
|
||||
- [FreeEhome](https://github.com/tsingeye/FreeEhome) golang实现的海康ehome服务器
|
||||
|
||||
- 播放器
|
||||
- [h265web.js](https://github.com/numberwolf/h265web.js) 基于wasm支持H265的播放器,支持本项目多种专属协议
|
||||
- [jessibuca](https://github.com/langhuihui/jessibuca) 基于wasm支持H265的播放器
|
||||
- [wsPlayer](https://github.com/v354412101/wsPlayer) 基于MSE的websocket-fmp4播放器
|
||||
- [BXC_gb28181Player](https://github.com/any12345com/BXC_gb28181Player) C++开发的支持国标GB28181协议的视频流播放器
|
||||
|
||||
- 可视化管理网站
|
||||
- [最新的前后端分离web项目,支持webrtc播放](https://github.com/langmansh/AKStreamNVR)
|
||||
- [基于ZLMediaKit主线的管理WEB网站](https://gitee.com/kkkkk5G/MediaServerUI)
|
||||
- [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI)
|
||||
- [一个非常漂亮的可视化后台管理系统](https://github.com/MingZhuLiu/ZLMediaServerManagent)
|
||||
|
||||
- 流媒体管理平台
|
||||
- [GB28181完整解决方案,自带web管理网站,支持webrtc、h265播放](https://github.com/648540858/wvp-GB28181-pro)
|
||||
- [功能强大的流媒体控制管理接口平台,支持GB28181](https://github.com/chatop2020/AKStream)
|
||||
- [Go实现的GB28181服务器](https://github.com/panjjo/gosip)
|
||||
- [node-js版本的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http)
|
||||
- [Go实现的海康ehome服务器](https://github.com/tsingeye/FreeEhome)
|
||||
|
||||
- 客户端
|
||||
- [c sdk完整c#包装库](https://github.com/malegend/ZLMediaKit.Autogen)
|
||||
- WEB管理网站
|
||||
- [AKStreamNVR](https://github.com/langmansh/AKStreamNVR) 前后端分离web项目,支持webrtc播放
|
||||
|
||||
- SDK
|
||||
- [c# sdk](https://github.com/malegend/ZLMediaKit.Autogen) 本项目c sdk完整c#包装库
|
||||
- [metaRTC](https://github.com/metartc/metaRTC) 全国产纯c webrtc sdk
|
||||
|
||||
- 其他项目(已停止更新)
|
||||
- [NodeJS实现的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http)
|
||||
- [基于ZLMediaKit主线的管理WEB网站 ](https://gitee.com/kkkkk5G/MediaServerUI)
|
||||
- [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI)
|
||||
- [一个非常漂亮的可视化后台管理系统](https://github.com/MingZhuLiu/ZLMediaServerManagent)
|
||||
- [基于C SDK实现的推流客户端](https://github.com/hctym1995/ZLM_ApiDemo)
|
||||
- [C#版本的Http API与Hook](https://github.com/chengxiaosheng/ZLMediaKit.HttpApi)
|
||||
- [DotNetCore的RESTful客户端](https://github.com/MingZhuLiu/ZLMediaKit.DotNetCore.Sdk)
|
||||
|
||||
- 播放器
|
||||
- [基于wasm支持H265的播放器](https://github.com/numberwolf/h265web.js)
|
||||
- [基于MSE的websocket-fmp4播放器](https://github.com/v354412101/wsPlayer)
|
||||
- [全国产webrtc sdk(metaRTC)](https://github.com/metartc/metaRTC)
|
||||
|
||||
## 授权协议
|
||||
|
||||
@ -203,9 +210,12 @@ bash build_docker_images.sh
|
||||
## 联系方式
|
||||
|
||||
- 邮箱:<1213642868@qq.com>(本项目相关或流媒体相关问题请走issue流程,否则恕不邮件答复)
|
||||
- QQ群:两个qq群已满员(共4000人),后续将不再新建qq群,用户可加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364)提问以支持本项目。
|
||||
- 关注微信公众号:
|
||||
- 请关注微信公众号获取最新消息推送:
|
||||
<img src=https://user-images.githubusercontent.com/11495632/232451702-4c50bc72-84d8-4c94-af2b-57290088ba7a.png width=15% />
|
||||
|
||||
- 也可以自愿有偿加入知识星球咨询和获取资料:
|
||||
<img src= https://user-images.githubusercontent.com/11495632/231946329-aa8517b0-3cf5-49cf-8c75-a93ed58cb9d2.png width=30% />
|
||||
|
||||
|
||||
## 怎么提问?
|
||||
|
||||
@ -213,9 +223,7 @@ bash build_docker_images.sh
|
||||
|
||||
- 1、仔细看下readme、wiki,如果有必要可以查看下issue.
|
||||
- 2、如果您的问题还没解决,可以提issue.
|
||||
- 3、有些问题,如果不具备参考性的,无需在issue提的,可以在qq群提.
|
||||
- 4、QQ私聊一般不接受无偿技术咨询和支持([为什么不提倡QQ私聊](https://github.com/ZLMediaKit/ZLMediaKit/wiki/%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E5%BB%BA%E8%AE%AEQQ%E7%A7%81%E8%81%8A%E5%92%A8%E8%AF%A2%E9%97%AE%E9%A2%98%EF%BC%9F)).
|
||||
- 5、如果需要获取更及时贴心的技术支持,可以有偿加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364).
|
||||
- 3、如果需要获取更及时贴心的技术支持,可以有偿加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364).
|
||||
|
||||
## 特别感谢
|
||||
|
||||
@ -310,6 +318,21 @@ bash build_docker_images.sh
|
||||
[朱如洪 ](https://github.com/zhu410289616)
|
||||
[lijin](https://github.com/1461521844lijin)
|
||||
[PioLing](https://github.com/PioLing)
|
||||
[BackT0TheFuture](https://github.com/BackT0TheFuture)
|
||||
[perara](https://github.com/perara)
|
||||
[codeRATny](https://github.com/codeRATny)
|
||||
[dengjfzh](https://github.com/dengjfzh)
|
||||
[百鸣](https://github.com/ixingqiao)
|
||||
[fruit Juice](https://github.com/xuandu)
|
||||
[tbago](https://github.com/tbago)
|
||||
[Luosh](https://github.com/Luosh)
|
||||
[linxiaoyan87](https://github.com/linxiaoyan)
|
||||
[waken](https://github.com/mc373906408)
|
||||
[Deepslient](https://github.com/Deepslient)
|
||||
|
||||
同时感谢JetBrains对开源项目的支持,本项目使用CLion开发与调试:
|
||||
|
||||
[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/CLion.svg)](https://jb.gg/OpenSourceSupport)
|
||||
|
||||
## 使用案例
|
||||
|
||||
|
22
README_en.md
22
README_en.md
@ -45,7 +45,7 @@
|
||||
|
||||
## Feature List
|
||||
### Overview of Features
|
||||
<img width="800" alt="Overview of Features" src="https://user-images.githubusercontent.com/11495632/190864440-91c45f8f-480f-43db-8110-5bb44e6300ff.png">
|
||||
<img width="800" alt="Overview of Features" src="https://github.com/ZLMediaKit/ZLMediaKit/assets/11495632/481ea769-5b27-495e-bf7d-31191e6af9d2">
|
||||
|
||||
- RTSP[S]
|
||||
- RTSP[S] server, supports RTMP/MP4/HLS to RTSP[S] conversion, supports devices such as Amazon Echo Show
|
||||
@ -68,9 +68,10 @@
|
||||
- Supports H264/H265/AAC/G711/OPUS encoding. Other encodings can be forwarded but cannot be converted to protocol
|
||||
- Supports [RTMP-H265](https://github.com/ksvc/FFmpeg/wiki)
|
||||
- Supports [RTMP-OPUS](https://github.com/ZLMediaKit/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81)
|
||||
- Supports [enhanced-rtmp(H265)](https://github.com/veovera/enhanced-rtmp)
|
||||
|
||||
- HLS
|
||||
- Supports HLS file generation and comes with an HTTP file server
|
||||
- Supports HLS file(mpegts/fmp4) generation and comes with an HTTP file server
|
||||
- Through cookie tracking technology, it can simulate HLS playback as a long connection, which can achieve HLS on-demand pulling, playback statistics, and other businesses
|
||||
- Supports HLS player and can pull HLS to rtsp/rtmp/mp4
|
||||
- Supports H264/H265/AAC/G711/OPUS encoding
|
||||
@ -349,6 +350,7 @@ bash build_docker_images.sh
|
||||
- Media management platform
|
||||
- [GB28181 complete solution with web management website, supporting webrtc and h265 playback](https://github.com/648540858/wvp-GB28181-pro)
|
||||
- [Powerful media control and management interface platform, supporting GB28181](https://github.com/chatop2020/AKStream)
|
||||
- [GB28181 server implemented in C++](https://github.com/any12345com/BXC_SipServer)
|
||||
- [GB28181 server implemented in Go](https://github.com/panjjo/gosip)
|
||||
- [Node-js version of GB28181 platform](https://gitee.com/hfwudao/GB28181_Node_Http)
|
||||
- [Hikvision ehome server implemented in Go](https://github.com/tsingeye/FreeEhome)
|
||||
@ -363,6 +365,7 @@ bash build_docker_images.sh
|
||||
- [Player supporting H265 based on wasm](https://github.com/numberwolf/h265web.js)
|
||||
- [WebSocket-fmp4 player based on MSE](https://github.com/v354412101/wsPlayer)
|
||||
- [Domestic webrtc sdk(metaRTC)](https://github.com/metartc/metaRTC)
|
||||
- [GB28181 player implemented in C++](https://github.com/any12345com/BXC_gb28181Player)
|
||||
|
||||
## License
|
||||
|
||||
@ -479,6 +482,21 @@ Thanks to all those who have supported this project in various ways, including b
|
||||
[朱如洪 ](https://github.com/zhu410289616)
|
||||
[lijin](https://github.com/1461521844lijin)
|
||||
[PioLing](https://github.com/PioLing)
|
||||
[BackT0TheFuture](https://github.com/BackT0TheFuture)
|
||||
[perara](https://github.com/perara)
|
||||
[codeRATny](https://github.com/codeRATny)
|
||||
[dengjfzh](https://github.com/dengjfzh)
|
||||
[百鸣](https://github.com/ixingqiao)
|
||||
[fruit Juice](https://github.com/xuandu)
|
||||
[tbago](https://github.com/tbago)
|
||||
[Luosh](https://github.com/Luosh)
|
||||
[linxiaoyan87](https://github.com/linxiaoyan)
|
||||
[waken](https://github.com/mc373906408)
|
||||
[Deepslient](https://github.com/Deepslient)
|
||||
|
||||
Also thank to JetBrains for their support for open source project, we developed and debugged zlmediakit with CLion:
|
||||
|
||||
[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/CLion.svg)](https://jb.gg/OpenSourceSupport)
|
||||
|
||||
## Use Cases
|
||||
|
||||
|
@ -166,6 +166,17 @@ typedef struct {
|
||||
*/
|
||||
void (API_CALL *on_mk_log)(int level, const char *file, int line, const char *function, const char *message);
|
||||
|
||||
/**
|
||||
* 发送rtp流失败回调,适用于mk_media_source_start_send_rtp/mk_media_start_send_rtp接口触发的rtp发送
|
||||
* @param vhost 虚拟主机
|
||||
* @param app 应用名
|
||||
* @param stream 流id
|
||||
* @param ssrc ssrc的10进制打印,通过atoi转换为整型
|
||||
* @param err 错误代码
|
||||
* @param msg 错误提示
|
||||
*/
|
||||
void(API_CALL *on_mk_media_send_rtp_stop)(const char *vhost, const char *app, const char *stream, const char *ssrc, int err, const char *msg);
|
||||
|
||||
} mk_events;
|
||||
|
||||
|
||||
|
@ -100,6 +100,9 @@ API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_so
|
||||
API_EXPORT int API_CALL mk_media_source_get_track_count(const mk_media_source ctx);
|
||||
// copy track reference by index from MediaSource, please use mk_track_unref to release it
|
||||
API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx, int index);
|
||||
// MediaSource::broadcastMessage
|
||||
API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len);
|
||||
|
||||
/**
|
||||
* 直播源在ZLMediaKit中被称作为MediaSource,
|
||||
* 目前支持3种,分别是RtmpMediaSource、RtspMediaSource、HlsMediaSource
|
||||
|
@ -117,6 +117,108 @@ API_EXPORT uint64_t API_CALL mk_frame_get_pts(mk_frame frame);
|
||||
*/
|
||||
API_EXPORT uint32_t API_CALL mk_frame_get_flags(mk_frame frame);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef struct mk_buffer_t *mk_buffer;
|
||||
typedef struct mk_frame_merger_t *mk_frame_merger;
|
||||
|
||||
/**
|
||||
* 创建帧合并器
|
||||
* @param type 起始头类型,0: none, 1: h264_prefix/AnnexB(0x 00 00 00 01), 2: mp4_nal_size(avcC)
|
||||
* @return 帧合并器
|
||||
*/
|
||||
API_EXPORT mk_frame_merger API_CALL mk_frame_merger_create(int type);
|
||||
|
||||
/**
|
||||
* 销毁帧合并器
|
||||
* @param ctx 对象指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_frame_merger_release(mk_frame_merger ctx);
|
||||
|
||||
/**
|
||||
* 清空merger对象缓冲,方便复用
|
||||
* @param ctx 对象指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_frame_merger_clear(mk_frame_merger ctx);
|
||||
|
||||
/**
|
||||
* 合并帧回调函数
|
||||
* @param user_data 用户数据指针
|
||||
* @param dts 解码时间戳
|
||||
* @param pts 显示时间戳
|
||||
* @param buffer 合并后数据buffer对象
|
||||
* @param have_key_frame 合并后数据中是否包含关键帧
|
||||
*/
|
||||
typedef void(API_CALL *on_mk_frame_merger)(void *user_data, uint64_t dts, uint64_t pts, mk_buffer buffer, int have_key_frame);
|
||||
|
||||
/**
|
||||
* 输入frame到merger对象并合并
|
||||
* @param ctx 对象指针
|
||||
* @param frame 帧数据
|
||||
* @param cb 帧合并回调函数
|
||||
* @param user_data 帧合并回调函数用户数据指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_frame_merger_input(mk_frame_merger ctx, mk_frame frame, on_mk_frame_merger cb, void *user_data);
|
||||
|
||||
/**
|
||||
* 强制flush merger对象缓冲,调用此api前需要确保先调用mk_frame_merger_input函数并且回调参数有效
|
||||
* @param ctx 对象指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_frame_merger_flush(mk_frame_merger ctx);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef struct mk_mpeg_muxer_t *mk_mpeg_muxer;
|
||||
|
||||
/**
|
||||
* mpeg-ps/ts 打包器输出回调函数
|
||||
* @param user_data 设置回调时的用户数据指针
|
||||
* @param muxer 对象
|
||||
* @param frame 帧数据
|
||||
* @param size 帧数据长度
|
||||
* @param timestamp 时间戳
|
||||
* @param key_pos 是否关键帧
|
||||
*/
|
||||
typedef void(API_CALL *on_mk_mpeg_muxer_frame)(void *user_data, mk_mpeg_muxer muxer, const char *frame, size_t size, uint64_t timestamp, int key_pos);
|
||||
|
||||
/**
|
||||
* mpeg-ps/ts 打包器
|
||||
* @param cb 打包回调函数
|
||||
* @param user_data 回调用户数据指针
|
||||
* @param is_ps 是否是ps
|
||||
* @return 打包器对象
|
||||
*/
|
||||
API_EXPORT mk_mpeg_muxer API_CALL mk_mpeg_muxer_create(on_mk_mpeg_muxer_frame cb, void *user_data, int is_ps);
|
||||
|
||||
/**
|
||||
* 删除mpeg-ps/ts 打包器
|
||||
* @param ctx 打包器
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_mpeg_muxer_release(mk_mpeg_muxer ctx);
|
||||
|
||||
/**
|
||||
* 添加音视频track
|
||||
* @param ctx mk_mpeg_muxer对象
|
||||
* @param track mk_track对象,音视频轨道
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_mpeg_muxer_init_track(mk_mpeg_muxer ctx, void* track);
|
||||
|
||||
/**
|
||||
* 初始化track完毕后调用此函数,
|
||||
* 在单track(只有音频或视频)时,因为ZLMediaKit不知道后续是否还要添加track,所以会多等待3秒钟
|
||||
* 如果产生的流是单Track类型,请调用此函数以便加快流生成速度,当然不调用该函数,影响也不大(会多等待3秒)
|
||||
* @param ctx 对象指针
|
||||
*/
|
||||
API_EXPORT void API_CALL mk_mpeg_muxer_init_complete(mk_mpeg_muxer ctx);
|
||||
|
||||
/**
|
||||
* 输入frame对象
|
||||
* @param ctx mk_mpeg_muxer对象
|
||||
* @param frame 帧对象
|
||||
* @return 1代表成功,0失败
|
||||
*/
|
||||
API_EXPORT int API_CALL mk_mpeg_muxer_input_frame(mk_mpeg_muxer ctx, mk_frame frame);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -58,7 +58,7 @@ API_EXPORT int API_CALL mk_recorder_is_recording(int type, const char *vhost, co
|
||||
|
||||
/**
|
||||
* 开始录制
|
||||
* @param type 0:hls,1:MP4
|
||||
* @param type 0:hls-ts,1:MP4,2:hls-fmp4,3:http-fmp4,4:http-ts
|
||||
* @param vhost 虚拟主机
|
||||
* @param app 应用名
|
||||
* @param stream 流id
|
||||
@ -70,7 +70,7 @@ API_EXPORT int API_CALL mk_recorder_start(int type, const char *vhost, const cha
|
||||
|
||||
/**
|
||||
* 停止录制
|
||||
* @param type 0:hls,1:MP4
|
||||
* @param type 0:hls-ts,1:MP4,2:hls-fmp4,3:http-fmp4,4:http-ts
|
||||
* @param vhost 虚拟主机
|
||||
* @param app 应用名
|
||||
* @param stream 流id
|
||||
|
@ -159,7 +159,7 @@ API_EXPORT void API_CALL mk_set_option(const char *key, const char *val) {
|
||||
}
|
||||
mINI::Instance()[key] = val;
|
||||
//广播配置文件热加载
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
|
||||
NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig);
|
||||
}
|
||||
|
||||
API_EXPORT const char * API_CALL mk_get_option(const char *key)
|
||||
|
@ -160,6 +160,13 @@ API_EXPORT void API_CALL mk_events_listen(const mk_events *events){
|
||||
s_events.on_mk_log((int) ctx->_level, ctx->_file.data(), ctx->_line, ctx->_function.data(), log.data());
|
||||
}
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&s_tag, Broadcast::kBroadcastSendRtpStopped,[](BroadcastSendRtpStoppedArgs){
|
||||
if (s_events.on_mk_media_send_rtp_stop) {
|
||||
s_events.on_mk_media_send_rtp_stop(sender.getMediaTuple().vhost.c_str(), sender.getMediaTuple().app.c_str(),
|
||||
sender.getMediaTuple().stream.c_str(), ssrc.c_str(), ex.getErrCode(), ex.what());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -214,6 +214,16 @@ API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx
|
||||
return (mk_track) new Track::Ptr(std::move(tracks[index]));
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len) {
|
||||
assert(ctx && msg && len);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
|
||||
Any any;
|
||||
Buffer::Ptr buffer = std::make_shared<BufferLikeString>(std::string(msg, len));
|
||||
any.set(std::move(buffer));
|
||||
return src->broadcastMessage(any);
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_media_source_close(const mk_media_source ctx,int force){
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
|
@ -9,10 +9,12 @@
|
||||
*/
|
||||
|
||||
#include "mk_frame.h"
|
||||
#include "mk_track.h"
|
||||
#include "Extension/Frame.h"
|
||||
#include "Extension/H264.h"
|
||||
#include "Extension/H265.h"
|
||||
#include "Extension/AAC.h"
|
||||
#include "Record/MPEG.h"
|
||||
|
||||
using namespace mediakit;
|
||||
|
||||
@ -178,3 +180,92 @@ API_EXPORT uint32_t API_CALL mk_frame_get_flags(mk_frame frame) {
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
API_EXPORT mk_frame_merger API_CALL mk_frame_merger_create(int type) {
|
||||
return reinterpret_cast<mk_frame_merger>(new FrameMerger(type));
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_frame_merger_release(mk_frame_merger ctx) {
|
||||
assert(ctx);
|
||||
delete reinterpret_cast<FrameMerger *>(ctx);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_frame_merger_clear(mk_frame_merger ctx) {
|
||||
assert(ctx);
|
||||
reinterpret_cast<FrameMerger *>(ctx)->clear();
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_frame_merger_flush(mk_frame_merger ctx) {
|
||||
assert(ctx);
|
||||
reinterpret_cast<FrameMerger *>(ctx)->flush();
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_frame_merger_input(mk_frame_merger ctx, mk_frame frame, on_mk_frame_merger cb, void *user_data) {
|
||||
assert(ctx && frame && cb);
|
||||
reinterpret_cast<FrameMerger *>(ctx)->inputFrame(*((Frame::Ptr *) frame), [cb, user_data](uint64_t dts, uint64_t pts, const toolkit::Buffer::Ptr &buffer, bool have_key_frame) {
|
||||
cb(user_data, dts, pts, (mk_buffer)(&buffer), have_key_frame);
|
||||
});
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
class MpegMuxerForC : public MpegMuxer {
|
||||
public:
|
||||
using onMuxer = std::function<void(const char *frame, size_t size, uint64_t timestamp, int key_pos)>;
|
||||
MpegMuxerForC(bool is_ps) : MpegMuxer(is_ps) {
|
||||
_cb = nullptr;
|
||||
}
|
||||
~MpegMuxerForC() { MpegMuxer::flush(); };
|
||||
|
||||
void setOnMuxer(onMuxer cb) {
|
||||
_cb = std::move(cb);
|
||||
}
|
||||
|
||||
private:
|
||||
void onWrite(std::shared_ptr<toolkit::Buffer> buffer, uint64_t timestamp, bool key_pos) override {
|
||||
if (_cb) {
|
||||
if (!buffer) {
|
||||
_cb(nullptr, 0, timestamp, key_pos);
|
||||
} else {
|
||||
_cb(buffer->data(), buffer->size(), timestamp, key_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
onMuxer _cb;
|
||||
};
|
||||
|
||||
API_EXPORT mk_mpeg_muxer API_CALL mk_mpeg_muxer_create(on_mk_mpeg_muxer_frame cb, void *user_data, int is_ps){
|
||||
assert(cb);
|
||||
auto ret = new MpegMuxerForC(is_ps);
|
||||
std::shared_ptr<void> ptr(user_data, [](void *) {});
|
||||
ret->setOnMuxer([cb, ptr, ret](const char *frame, size_t size, uint64_t timestamp, int key_pos) {
|
||||
cb(ptr.get(), reinterpret_cast<mk_mpeg_muxer>(ret), frame, size, timestamp, key_pos);
|
||||
});
|
||||
return reinterpret_cast<mk_mpeg_muxer>(ret);
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_mpeg_muxer_release(mk_mpeg_muxer ctx){
|
||||
assert(ctx);
|
||||
auto ptr = reinterpret_cast<MpegMuxerForC *>(ctx);
|
||||
delete ptr;
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_mpeg_muxer_init_track(mk_mpeg_muxer ctx, void* track) {
|
||||
assert(ctx && track);
|
||||
auto ptr = reinterpret_cast<MpegMuxerForC *>(ctx);
|
||||
ptr->addTrack(*((Track::Ptr *) track));
|
||||
}
|
||||
|
||||
API_EXPORT void API_CALL mk_mpeg_muxer_init_complete(mk_mpeg_muxer ctx) {
|
||||
assert(ctx);
|
||||
auto ptr = reinterpret_cast<MpegMuxerForC *>(ctx);
|
||||
ptr->addTrackCompleted();
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_mpeg_muxer_input_frame(mk_mpeg_muxer ctx, mk_frame frame){
|
||||
assert(ctx && frame);
|
||||
auto ptr = reinterpret_cast<MpegMuxerForC *>(ctx);
|
||||
return ptr->inputFrame(*((Frame::Ptr *) frame));
|
||||
}
|
@ -47,21 +47,25 @@ static inline bool isRecording(Recorder::type type, const string &vhost, const s
|
||||
return src->isRecording(type);
|
||||
}
|
||||
|
||||
static inline bool startRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id,const string &customized_path, size_t max_second){
|
||||
static inline bool startRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path, size_t max_second) {
|
||||
auto src = MediaSource::find(vhost, app, stream_id);
|
||||
if (!src) {
|
||||
WarnL << "未找到相关的MediaSource,startRecord失败:" << vhost << "/" << app << "/" << stream_id;
|
||||
return false;
|
||||
}
|
||||
return src->setupRecord(type, true, customized_path, max_second);
|
||||
bool ret;
|
||||
src->getOwnerPoller()->sync([&]() { ret = src->setupRecord(type, true, customized_path, max_second); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline bool stopRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id){
|
||||
static inline bool stopRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id) {
|
||||
auto src = MediaSource::find(vhost, app, stream_id);
|
||||
if(!src){
|
||||
if (!src) {
|
||||
return false;
|
||||
}
|
||||
return src->setupRecord(type, false, "", 0);
|
||||
bool ret;
|
||||
src->getOwnerPoller()->sync([&]() { ret = src->setupRecord(type, false, "", 0); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_recorder_is_recording(int type, const char *vhost, const char *app, const char *stream){
|
||||
|
@ -65,7 +65,7 @@ API_EXPORT mk_ini API_CALL mk_ini_default() {
|
||||
static void emit_ini_file_reload(mk_ini ini) {
|
||||
if (ini == mk_ini_default()) {
|
||||
// 广播配置文件热加载
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
|
||||
NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,13 +40,20 @@ modify_stamp=2
|
||||
enable_audio=1
|
||||
#添加acc静音音频,在关闭音频时,此开关无效
|
||||
add_mute_audio=1
|
||||
#无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)
|
||||
#此配置置1时,此流如果无人观看,将不触发on_none_reader hook回调,
|
||||
#而是将直接关闭流
|
||||
auto_close=0
|
||||
|
||||
#推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。
|
||||
#置0关闭此特性(推流断开会导致立即断开播放器)
|
||||
#此参数不应大于播放器超时时间;单位毫秒
|
||||
continue_push_ms=15000
|
||||
|
||||
#是否开启转换为hls
|
||||
#是否开启转换为hls(mpegts)
|
||||
enable_hls=1
|
||||
#是否开启转换为hls(fmp4)
|
||||
enable_hls_fmp4=0
|
||||
#是否开启MP4录制
|
||||
enable_mp4=0
|
||||
#是否开启转换为rtsp/webrtc
|
||||
@ -124,7 +131,7 @@ segDur=2
|
||||
segNum=3
|
||||
#HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数
|
||||
segRetain=5
|
||||
#是否广播 ts 切片完成通知
|
||||
#是否广播 hls切片(ts/fmp4)完成通知(on_record_ts)
|
||||
broadcastRecordTs=0
|
||||
#直播hls文件删除延时,单位秒,issue: #913
|
||||
deleteDelaySec=10
|
||||
@ -135,9 +142,6 @@ deleteDelaySec=10
|
||||
segKeep=0
|
||||
|
||||
[hook]
|
||||
#在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然
|
||||
#该配置项的目的是为了开发者自己调试测试,该参数暴露后会有泄露隐私的安全隐患
|
||||
admin_params=secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
|
||||
#是否启用hook事件,启用后,推拉流都将进行鉴权
|
||||
enable=0
|
||||
#播放器或推流器使用流量事件,置空则关闭
|
||||
@ -150,7 +154,7 @@ 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
|
||||
# 录制 hls ts 切片完成事件
|
||||
# 录制 hls ts(或fmp4) 切片完成事件
|
||||
on_record_ts=https://127.0.0.1/index/hook/on_record_ts
|
||||
#rtsp播放鉴权事件,此事件中比对rtsp的用户名密码
|
||||
on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth
|
||||
@ -162,6 +166,8 @@ on_rtsp_realm=https://127.0.0.1/index/hook/on_rtsp_realm
|
||||
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
|
||||
#过滤on_stream_changed hook的协议类型,可以选择只监听某些感兴趣的协议;置空则不过滤协议
|
||||
stream_changed_schemas=rtsp/rtmp/fmp4/ts/hls/hls.fmp4
|
||||
#无人观看流事件,通过该事件,可以选择是否关闭无人观看的流。配合general.streamNoneReaderDelayMS选项一起使用
|
||||
on_stream_none_reader=https://127.0.0.1/index/hook/on_stream_none_reader
|
||||
#播放时,未找到流事件,通过配合hook.on_stream_none_reader事件可以完成按需拉流
|
||||
@ -237,6 +243,8 @@ forbidCacheSuffix=
|
||||
forwarded_ip_header=
|
||||
#默认允许所有跨域请求
|
||||
allow_cross_domains=1
|
||||
#允许访问http api和http文件索引的ip地址范围白名单,置空情况下不做限制
|
||||
allow_ip_range=127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255
|
||||
|
||||
[multicast]
|
||||
#rtp组播截止组播ip地址
|
||||
@ -266,8 +274,6 @@ handshakeSecond=15
|
||||
#rtmp超时时间,如果该时间内未收到客户端的数据,
|
||||
#或者tcp发送缓存超过这个时间,则会断开连接,单位秒
|
||||
keepAliveSecond=15
|
||||
#在接收rtmp推流时,是否重新生成时间戳(很多推流器的时间戳着实很烂)
|
||||
modifyStamp=0
|
||||
#rtmp服务器监听端口
|
||||
port=1935
|
||||
#rtmps服务器监听地址
|
||||
|
@ -128,4 +128,4 @@ WORKDIR /opt/zlm
|
||||
VOLUME [ "/opt/zlm/conf/","/opt/zlm/log/","opt/zlm/ffmpeg/"]
|
||||
COPY --from=build /opt/build /
|
||||
ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH TZ=Asia/Shanghai
|
||||
CMD ./MediaServer -c ./conf/config.ini
|
||||
CMD ["./MediaServer", "-c" , "./conf/config.ini"]
|
@ -41,4 +41,4 @@ RUN cmake -DCMAKE_BUILD_TYPE=Release .. && \
|
||||
make
|
||||
|
||||
ENV PATH /opt/media/ZLMediaKit/release/linux/Release/:$PATH
|
||||
CMD MediaServer
|
||||
CMD ["MediaServer"]
|
||||
|
@ -60,4 +60,4 @@ RUN apt-get update && \
|
||||
WORKDIR /opt/media/bin/
|
||||
COPY --from=build /opt/media/ZLMediaKit/release/linux/Release/MediaServer /opt/media/bin/MediaServer
|
||||
ENV PATH /opt/media/bin:$PATH
|
||||
CMD MediaServer
|
||||
CMD ["MediaServer"]
|
||||
|
@ -42,4 +42,4 @@ RUN cmake -DCMAKE_BUILD_TYPE=Release .. && \
|
||||
make
|
||||
|
||||
ENV PATH /opt/media/ZLMediaKit/release/linux/Release:$PATH
|
||||
CMD MediaServer
|
||||
CMD ["MediaServer"]
|
||||
|
@ -60,4 +60,4 @@ RUN apt-get update && \
|
||||
WORKDIR /opt/media/bin/
|
||||
COPY --from=build /opt/media/ZLMediaKit/release/linux/Release/MediaServer /opt/media/bin/MediaServer
|
||||
ENV PATH /opt/media/bin:$PATH
|
||||
CMD MediaServer
|
||||
CMD ["MediaServer"]
|
||||
|
@ -83,4 +83,4 @@ COPY --from=build /opt/media/ZLMediaKit/release/linux/${MODEL}/MediaServer /opt/
|
||||
COPY --from=build /opt/media/ZLMediaKit/release/linux/${MODEL}/config.ini /opt/media/conf/
|
||||
COPY --from=build /opt/media/ZLMediaKit/www/ /opt/media/bin/www/
|
||||
ENV PATH /opt/media/bin:$PATH
|
||||
CMD ["sh","-c","./MediaServer -s default.pem -c ../conf/config.ini -l 0"]
|
||||
CMD ["./MediaServer","-s", "default.pem", "-c", "../conf/config.ini", "-l","0"]
|
||||
|
@ -25,7 +25,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -51,7 +51,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -77,7 +77,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -103,7 +103,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -129,7 +129,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -155,7 +155,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "api.apiDebug",
|
||||
@ -186,7 +186,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -212,7 +212,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "schema",
|
||||
@ -262,7 +262,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "schema",
|
||||
@ -314,7 +314,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "schema",
|
||||
@ -366,7 +366,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "local_port",
|
||||
@ -404,7 +404,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "id",
|
||||
@ -435,7 +435,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "local_port",
|
||||
@ -473,7 +473,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
@ -516,7 +516,13 @@
|
||||
{
|
||||
"key": "enable_hls",
|
||||
"value": null,
|
||||
"description": "是否转hls",
|
||||
"description": "是否转hls-ts",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "enable_hls_fmp4",
|
||||
"value": null,
|
||||
"description": "是否转hls-fmp4",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
@ -584,6 +590,12 @@
|
||||
"value": null,
|
||||
"description": "是否修改原始时间戳,默认值2;取值范围:0.采用源视频流绝对时间戳,不做任何改变;1.采用zlmediakit接收数据时的系统时间戳(有平滑处理);2.采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正",
|
||||
"disabled": true
|
||||
},
|
||||
{
|
||||
"key": "auto_close",
|
||||
"value": null,
|
||||
"description": "无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)",
|
||||
"disabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -609,7 +621,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "key",
|
||||
@ -640,7 +652,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "schema",
|
||||
@ -709,7 +721,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "key",
|
||||
@ -740,7 +752,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "src_url",
|
||||
@ -797,7 +809,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "key",
|
||||
@ -827,7 +839,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "schema",
|
||||
@ -873,7 +885,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "schema",
|
||||
@ -900,6 +912,56 @@
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "广播webrtc datachannel消息(broadcastMessage)",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{ZLMediaKit_URL}}/index/api/broadcastMessage?secret={{ZLMediaKit_secret}}&schema=rtsp&vhost={{defaultVhost}}&app=live&stream=test&msg=Hello zlmediakit123",
|
||||
"host": [
|
||||
"{{ZLMediaKit_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"index",
|
||||
"api",
|
||||
"broadcastMessage"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "schema",
|
||||
"value": "rtsp",
|
||||
"description": "协议,例如 rtsp或rtmp,目前仅支持rtsp协议"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
"value": "{{defaultVhost}}",
|
||||
"description": "虚拟主机,例如__defaultVhost__"
|
||||
},
|
||||
{
|
||||
"key": "app",
|
||||
"value": "live",
|
||||
"description": "应用名,例如 live"
|
||||
},
|
||||
{
|
||||
"key": "stream",
|
||||
"value": "test",
|
||||
"description": "流id,例如 test"
|
||||
},
|
||||
{
|
||||
"key": "msg",
|
||||
"value": "Hello ZLMediakit"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "获取流信息(getMediaInfo)",
|
||||
"request": {
|
||||
@ -919,7 +981,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "schema",
|
||||
@ -965,7 +1027,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
@ -1016,7 +1078,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
@ -1062,7 +1124,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "type",
|
||||
@ -1120,7 +1182,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
@ -1166,7 +1228,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
@ -1212,7 +1274,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "type",
|
||||
@ -1258,7 +1320,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "type",
|
||||
@ -1304,7 +1366,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "url",
|
||||
@ -1345,7 +1407,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "stream_id",
|
||||
@ -1376,7 +1438,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "port",
|
||||
@ -1435,7 +1497,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "dst_url",
|
||||
@ -1476,7 +1538,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "stream_id",
|
||||
@ -1507,7 +1569,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "stream_id",
|
||||
@ -1543,7 +1605,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "stream_id",
|
||||
@ -1574,7 +1636,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "stream_id",
|
||||
@ -1605,7 +1667,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1631,7 +1693,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
@ -1734,7 +1796,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
@ -1822,7 +1884,7 @@
|
||||
{
|
||||
"key": "secret",
|
||||
"value": "{{ZLMediaKit_secret}}",
|
||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
||||
"description": "api操作密钥(配置文件配置)"
|
||||
},
|
||||
{
|
||||
"key": "vhost",
|
||||
|
@ -34,7 +34,7 @@ if(ENABLE_SERVER_LIB)
|
||||
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
||||
target_link_libraries(MediaServer
|
||||
PRIVATE ${MK_LINK_LIBRARIES})
|
||||
update_cached(MK_LINK_LIBRARIES MediaServer)
|
||||
update_cached_list(MK_LINK_LIBRARIES MediaServer)
|
||||
return()
|
||||
endif()
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "FFmpegSource.h"
|
||||
#include "Common/config.h"
|
||||
#include "Common/MediaSource.h"
|
||||
#include "Common/MultiMediaSourceMuxer.h"
|
||||
#include "Util/File.h"
|
||||
#include "System.h"
|
||||
#include "Thread/WorkThreadPool.h"
|
||||
@ -70,10 +71,10 @@ void FFmpegSource::setupRecordFlag(bool enable_hls, bool enable_mp4){
|
||||
_enable_mp4 = enable_mp4;
|
||||
}
|
||||
|
||||
void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,const string &dst_url,int timeout_ms,const onPlay &cb) {
|
||||
GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin);
|
||||
GET_CONFIG(string,ffmpeg_cmd_default,FFmpeg::kCmd);
|
||||
GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog);
|
||||
void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url, const string &dst_url, int timeout_ms, const onPlay &cb) {
|
||||
GET_CONFIG(string, ffmpeg_bin, FFmpeg::kBin);
|
||||
GET_CONFIG(string, ffmpeg_cmd_default, FFmpeg::kCmd);
|
||||
GET_CONFIG(string, ffmpeg_log, FFmpeg::kLog);
|
||||
|
||||
_src_url = src_url;
|
||||
_dst_url = dst_url;
|
||||
@ -91,120 +92,114 @@ void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,cons
|
||||
auto cmd_it = mINI::Instance().find(ffmpeg_cmd_key);
|
||||
if (cmd_it != mINI::Instance().end()) {
|
||||
ffmpeg_cmd = cmd_it->second;
|
||||
} else{
|
||||
} else {
|
||||
WarnL << "配置文件中,ffmpeg命令模板(" << ffmpeg_cmd_key << ")不存在,已采用默认模板(" << ffmpeg_cmd_default << ")";
|
||||
}
|
||||
}
|
||||
|
||||
char cmd[2048] = {0};
|
||||
char cmd[2048] = { 0 };
|
||||
snprintf(cmd, sizeof(cmd), ffmpeg_cmd.data(), File::absolutePath("", ffmpeg_bin).data(), src_url.data(), dst_url.data());
|
||||
auto log_file = ffmpeg_log.empty() ? "" : File::absolutePath("", ffmpeg_log);
|
||||
_process.run(cmd, log_file);
|
||||
InfoL << cmd;
|
||||
|
||||
if (is_local_ip(_media_info.host)) {
|
||||
//推流给自己的,通过判断流是否注册上来判断是否正常
|
||||
// 推流给自己的,通过判断流是否注册上来判断是否正常
|
||||
if (_media_info.schema != RTSP_SCHEMA && _media_info.schema != RTMP_SCHEMA) {
|
||||
cb(SockException(Err_other,"本服务只支持rtmp/rtsp推流"));
|
||||
cb(SockException(Err_other, "本服务只支持rtmp/rtsp推流"));
|
||||
return;
|
||||
}
|
||||
weak_ptr<FFmpegSource> weakSelf = shared_from_this();
|
||||
findAsync(timeout_ms,[cb,weakSelf,timeout_ms](const MediaSource::Ptr &src){
|
||||
findAsync(timeout_ms, [cb, weakSelf, timeout_ms](const MediaSource::Ptr &src) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf){
|
||||
//自己已经销毁
|
||||
if (!strongSelf) {
|
||||
// 自己已经销毁
|
||||
return;
|
||||
}
|
||||
if(src){
|
||||
//推流给自己成功
|
||||
if (src) {
|
||||
// 推流给自己成功
|
||||
cb(SockException());
|
||||
strongSelf->onGetMediaSource(src);
|
||||
strongSelf->startTimer(timeout_ms);
|
||||
return;
|
||||
}
|
||||
//推流失败
|
||||
if(!strongSelf->_process.wait(false)){
|
||||
//ffmpeg进程已经退出
|
||||
cb(SockException(Err_other,StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code()));
|
||||
if (!strongSelf->_process.wait(false)) {
|
||||
// ffmpeg进程已经退出
|
||||
cb(SockException(Err_other, StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code()));
|
||||
return;
|
||||
}
|
||||
//ffmpeg进程还在线,但是等待推流超时
|
||||
cb(SockException(Err_other,"等待超时"));
|
||||
// ffmpeg进程还在线,但是等待推流超时
|
||||
cb(SockException(Err_other, "等待超时"));
|
||||
});
|
||||
} else{
|
||||
//推流给其他服务器的,通过判断FFmpeg进程是否在线判断是否成功
|
||||
weak_ptr<FFmpegSource> weakSelf = shared_from_this();
|
||||
_timer = std::make_shared<Timer>(timeout_ms / 1000.0f,[weakSelf,cb,timeout_ms](){
|
||||
_timer = std::make_shared<Timer>(timeout_ms / 1000.0f, [weakSelf, cb, timeout_ms]() {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf){
|
||||
//自身已经销毁
|
||||
if (!strongSelf) {
|
||||
// 自身已经销毁
|
||||
return false;
|
||||
}
|
||||
//FFmpeg还在线,那么我们认为推流成功
|
||||
if(strongSelf->_process.wait(false)){
|
||||
// FFmpeg还在线,那么我们认为推流成功
|
||||
if (strongSelf->_process.wait(false)) {
|
||||
cb(SockException());
|
||||
strongSelf->startTimer(timeout_ms);
|
||||
return false;
|
||||
}
|
||||
//ffmpeg进程已经退出
|
||||
cb(SockException(Err_other,StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code()));
|
||||
// ffmpeg进程已经退出
|
||||
cb(SockException(Err_other, StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code()));
|
||||
return false;
|
||||
},_poller);
|
||||
}, _poller);
|
||||
}
|
||||
}
|
||||
|
||||
void FFmpegSource::findAsync(int maxWaitMS, const function<void(const MediaSource::Ptr &src)> &cb) {
|
||||
auto src = MediaSource::find(_media_info.schema,
|
||||
_media_info.vhost,
|
||||
_media_info.app,
|
||||
_media_info.stream);
|
||||
if(src || !maxWaitMS){
|
||||
auto src = MediaSource::find(_media_info.schema, _media_info.vhost, _media_info.app, _media_info.stream);
|
||||
if (src || !maxWaitMS) {
|
||||
cb(src);
|
||||
return;
|
||||
}
|
||||
|
||||
void *listener_tag = this;
|
||||
//若干秒后执行等待媒体注册超时回调
|
||||
auto onRegistTimeout = _poller->doDelayTask(maxWaitMS,[cb,listener_tag](){
|
||||
//取消监听该事件
|
||||
NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged);
|
||||
// 若干秒后执行等待媒体注册超时回调
|
||||
auto onRegistTimeout = _poller->doDelayTask(maxWaitMS, [cb, listener_tag]() {
|
||||
// 取消监听该事件
|
||||
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
|
||||
cb(nullptr);
|
||||
return 0;
|
||||
});
|
||||
|
||||
weak_ptr<FFmpegSource> weakSelf = shared_from_this();
|
||||
auto onRegist = [listener_tag,weakSelf,cb,onRegistTimeout](BroadcastMediaChangedArgs) {
|
||||
auto onRegist = [listener_tag, weakSelf, cb, onRegistTimeout](BroadcastMediaChangedArgs) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
//本身已经销毁,取消延时任务
|
||||
if (!strongSelf) {
|
||||
// 本身已经销毁,取消延时任务
|
||||
onRegistTimeout->cancel();
|
||||
NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged);
|
||||
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bRegist ||
|
||||
sender.getSchema() != strongSelf->_media_info.schema ||
|
||||
if (!bRegist || sender.getSchema() != strongSelf->_media_info.schema ||
|
||||
!equalMediaTuple(sender.getMediaTuple(), strongSelf->_media_info)) {
|
||||
//不是自己感兴趣的事件,忽略之
|
||||
// 不是自己感兴趣的事件,忽略之
|
||||
return;
|
||||
}
|
||||
|
||||
//查找的流终于注册上了;取消延时任务,防止多次回调
|
||||
// 查找的流终于注册上了;取消延时任务,防止多次回调
|
||||
onRegistTimeout->cancel();
|
||||
//取消事件监听
|
||||
NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged);
|
||||
// 取消事件监听
|
||||
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
|
||||
|
||||
//切换到自己的线程再回复
|
||||
strongSelf->_poller->async([weakSelf,cb](){
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf) {
|
||||
return;
|
||||
// 切换到自己的线程再回复
|
||||
strongSelf->_poller->async([weakSelf, cb]() {
|
||||
if (auto strongSelf = weakSelf.lock()) {
|
||||
// 再找一遍媒体源,一般能找到
|
||||
strongSelf->findAsync(0, cb);
|
||||
}
|
||||
//再找一遍媒体源,一般能找到
|
||||
strongSelf->findAsync(0,cb);
|
||||
}, false);
|
||||
};
|
||||
//监听媒体注册事件
|
||||
// 监听媒体注册事件
|
||||
NoticeCenter::Instance().addListener(listener_tag, Broadcast::kBroadcastMediaChanged, onRegist);
|
||||
}
|
||||
|
||||
@ -222,49 +217,49 @@ void FFmpegSource::startTimer(int timeout_ms) {
|
||||
}
|
||||
bool needRestart = ffmpeg_restart_sec > 0 && strongSelf->_replay_ticker.elapsedTime() > ffmpeg_restart_sec * 1000;
|
||||
if (is_local_ip(strongSelf->_media_info.host)) {
|
||||
//推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常
|
||||
// 推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常
|
||||
strongSelf->findAsync(0, [&](const MediaSource::Ptr &src) {
|
||||
//同步查找流
|
||||
// 同步查找流
|
||||
if (!src || needRestart) {
|
||||
if(needRestart){
|
||||
if (needRestart) {
|
||||
strongSelf->_replay_ticker.resetTime();
|
||||
if(strongSelf->_process.wait(false)){
|
||||
//FFmpeg进程还在运行,超时就关闭它
|
||||
if (strongSelf->_process.wait(false)) {
|
||||
// FFmpeg进程还在运行,超时就关闭它
|
||||
strongSelf->_process.kill(2000);
|
||||
}
|
||||
InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url;
|
||||
}
|
||||
//流不在线,重新拉流, 这里原先是10秒超时,实际发现10秒不够,改成20秒了
|
||||
if(strongSelf->_replay_ticker.elapsedTime() > 20 * 1000){
|
||||
//上次重试时间超过10秒,那么再重试FFmpeg拉流
|
||||
// 流不在线,重新拉流, 这里原先是10秒超时,实际发现10秒不够,改成20秒了
|
||||
if (strongSelf->_replay_ticker.elapsedTime() > 20 * 1000) {
|
||||
// 上次重试时间超过10秒,那么再重试FFmpeg拉流
|
||||
strongSelf->_replay_ticker.resetTime();
|
||||
strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [](const SockException &) {});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//推流给其他服务器的,我们通过判断FFmpeg进程是否在线,如果FFmpeg推流中断,那么它应该会自动退出
|
||||
// 推流给其他服务器的,我们通过判断FFmpeg进程是否在线,如果FFmpeg推流中断,那么它应该会自动退出
|
||||
if (!strongSelf->_process.wait(false) || needRestart) {
|
||||
if(needRestart){
|
||||
if (needRestart) {
|
||||
strongSelf->_replay_ticker.resetTime();
|
||||
if(strongSelf->_process.wait(false)){
|
||||
//FFmpeg进程还在运行,超时就关闭它
|
||||
if (strongSelf->_process.wait(false)) {
|
||||
// FFmpeg进程还在运行,超时就关闭它
|
||||
strongSelf->_process.kill(2000);
|
||||
}
|
||||
InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url;
|
||||
}
|
||||
//ffmpeg不在线,重新拉流
|
||||
// ffmpeg不在线,重新拉流
|
||||
strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [weakSelf](const SockException &ex) {
|
||||
if(!ex){
|
||||
//没有错误
|
||||
if (!ex) {
|
||||
// 没有错误
|
||||
return;
|
||||
}
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
//自身已经销毁
|
||||
// 自身已经销毁
|
||||
return;
|
||||
}
|
||||
//上次重试时间超过10秒,那么再重试FFmpeg拉流
|
||||
// 上次重试时间超过10秒,那么再重试FFmpeg拉流
|
||||
strongSelf->startTimer(10 * 1000);
|
||||
});
|
||||
}
|
||||
@ -294,20 +289,17 @@ MediaOriginType FFmpegSource::getOriginType(MediaSource &sender) const{
|
||||
return MediaOriginType::ffmpeg_pull;
|
||||
}
|
||||
|
||||
string FFmpegSource::getOriginUrl(MediaSource &sender) const{
|
||||
string FFmpegSource::getOriginUrl(MediaSource &sender) const {
|
||||
return _src_url;
|
||||
}
|
||||
|
||||
std::shared_ptr<SockInfo> FFmpegSource::getOriginSock(MediaSource &sender) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) {
|
||||
auto listener = src->getListener(true);
|
||||
if (listener.lock().get() != this) {
|
||||
auto muxer = src->getMuxer();
|
||||
auto listener = muxer ? muxer->getDelegate() : nullptr;
|
||||
if (listener && listener.get() != this) {
|
||||
//防止多次进入onGetMediaSource函数导致无限递归调用的bug
|
||||
setDelegate(listener);
|
||||
src->setListener(shared_from_this());
|
||||
muxer->setDelegate(shared_from_this());
|
||||
if (_enable_hls) {
|
||||
src->setupRecord(Recorder::type_hls, true, "", 0);
|
||||
}
|
||||
@ -318,14 +310,14 @@ void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) {
|
||||
}
|
||||
|
||||
void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float timeout_sec, const onSnap &cb) {
|
||||
GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin);
|
||||
GET_CONFIG(string,ffmpeg_snap,FFmpeg::kSnap);
|
||||
GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog);
|
||||
GET_CONFIG(string, ffmpeg_bin, FFmpeg::kBin);
|
||||
GET_CONFIG(string, ffmpeg_snap, FFmpeg::kSnap);
|
||||
GET_CONFIG(string, ffmpeg_log, FFmpeg::kLog);
|
||||
Ticker ticker;
|
||||
WorkThreadPool::Instance().getPoller()->async([timeout_sec, play_url,save_path,cb, ticker](){
|
||||
WorkThreadPool::Instance().getPoller()->async([timeout_sec, play_url, save_path, cb, ticker]() {
|
||||
auto elapsed_ms = ticker.elapsedTime();
|
||||
if (elapsed_ms > timeout_sec * 1000) {
|
||||
//超时,后台线程负载太高,当代太久才启动该任务
|
||||
// 超时,后台线程负载太高,当代太久才启动该任务
|
||||
cb(false, "wait work poller schedule snap task timeout");
|
||||
return;
|
||||
}
|
||||
@ -346,13 +338,12 @@ void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float
|
||||
return 0;
|
||||
});
|
||||
|
||||
//等待FFmpeg进程退出
|
||||
// 等待FFmpeg进程退出
|
||||
process->wait(true);
|
||||
// FFmpeg进程退出了可以取消定时器了
|
||||
delayTask->cancel();
|
||||
//执行回调函数
|
||||
// 执行回调函数
|
||||
bool success = process->exit_code() == 0 && File::fileSize(save_path.data());
|
||||
cb(success, (!success && !log_file.empty()) ? File::loadFile(log_file.data()) : "");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
namespace FFmpeg {
|
||||
extern const std::string kSnap;
|
||||
extern const std::string kBin;
|
||||
}
|
||||
|
||||
class FFmpegSnap {
|
||||
@ -79,8 +80,6 @@ private:
|
||||
mediakit::MediaOriginType getOriginType(mediakit::MediaSource &sender) const override;
|
||||
//获取媒体源url或者文件路径
|
||||
std::string getOriginUrl(mediakit::MediaSource &sender) const override;
|
||||
// 获取媒体源客户端相关信息
|
||||
std::shared_ptr<toolkit::SockInfo> getOriginSock(mediakit::MediaSource &sender) const override;
|
||||
|
||||
private:
|
||||
bool _enable_hls = false;
|
||||
|
@ -126,6 +126,12 @@ void System::startDaemon(bool &kill_parent_if_failed) {
|
||||
exit(0);
|
||||
});
|
||||
|
||||
signal(SIGTERM,[](int) {
|
||||
WarnL << "收到主动退出信号,关闭父进程与子进程";
|
||||
kill(pid, SIGINT);
|
||||
exit(0);
|
||||
});
|
||||
|
||||
do {
|
||||
int status = 0;
|
||||
if (waitpid(pid, &status, 0) >= 0) {
|
||||
|
@ -247,7 +247,7 @@ static inline void addHttpListener(){
|
||||
size = body->remainSize();
|
||||
}
|
||||
|
||||
LogContextCapture log(getLogger(), toolkit::LTrace, __FILE__, "http api debug", __LINE__);
|
||||
LogContextCapture log(getLogger(), toolkit::LDebug, __FILE__, "http api debug", __LINE__);
|
||||
log << "\r\n# request:\r\n" << parser.method() << " " << parser.fullUrl() << "\r\n";
|
||||
log << "# header:\r\n";
|
||||
|
||||
@ -537,7 +537,7 @@ void addStreamProxy(const string &vhost, const string &app, const string &stream
|
||||
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
||||
if (s_proxyMap.find(key) != s_proxyMap.end()) {
|
||||
//已经在拉流了
|
||||
cb(SockException(Err_success), key);
|
||||
cb(SockException(Err_other, "This stream already exists"), key);
|
||||
return;
|
||||
}
|
||||
//添加拉流代理
|
||||
@ -588,7 +588,8 @@ void installWebApi() {
|
||||
|
||||
//获取线程负载
|
||||
//测试url http://127.0.0.1/index/api/getThreadsLoad
|
||||
api_regist("/index/api/getThreadsLoad",[](API_ARGS_MAP_ASYNC){
|
||||
api_regist("/index/api/getThreadsLoad", [](API_ARGS_MAP_ASYNC) {
|
||||
CHECK_SECRET();
|
||||
EventPollerPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
|
||||
Value val;
|
||||
auto vec = EventPollerPool::Instance().getExecutorLoad();
|
||||
@ -606,7 +607,8 @@ void installWebApi() {
|
||||
|
||||
//获取后台工作线程负载
|
||||
//测试url http://127.0.0.1/index/api/getWorkThreadsLoad
|
||||
api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC){
|
||||
api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC) {
|
||||
CHECK_SECRET();
|
||||
WorkThreadPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
|
||||
Value val;
|
||||
auto vec = WorkThreadPool::Instance().getExecutorLoad();
|
||||
@ -652,6 +654,10 @@ void installWebApi() {
|
||||
continue;
|
||||
#endif
|
||||
}
|
||||
if (pr.first == FFmpeg::kBin) {
|
||||
WarnL << "Configuration named " << FFmpeg::kBin << " is not allowed to be set by setServerConfig api.";
|
||||
continue;
|
||||
}
|
||||
if (ini[pr.first] == pr.second) {
|
||||
continue;
|
||||
}
|
||||
@ -660,7 +666,7 @@ void installWebApi() {
|
||||
++changed;
|
||||
}
|
||||
if (changed > 0) {
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
|
||||
NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig);
|
||||
ini.dumpFile(g_ini_file);
|
||||
}
|
||||
val["changed"] = changed;
|
||||
@ -789,25 +795,40 @@ void installWebApi() {
|
||||
throw ApiRetException("can not find the stream", API::NotFound);
|
||||
}
|
||||
src->getPlayerList(
|
||||
[=](const std::list<std::shared_ptr<void>> &info_list) mutable {
|
||||
[=](const std::list<toolkit::Any> &info_list) mutable {
|
||||
val["code"] = API::Success;
|
||||
auto &data = val["data"];
|
||||
data = Value(arrayValue);
|
||||
for (auto &info : info_list) {
|
||||
auto obj = static_pointer_cast<Value>(info);
|
||||
data.append(std::move(*obj));
|
||||
auto &obj = info.get<Value>();
|
||||
data.append(std::move(obj));
|
||||
}
|
||||
invoker(200, headerOut, val.toStyledString());
|
||||
},
|
||||
[](std::shared_ptr<void> &&info) -> std::shared_ptr<void> {
|
||||
[](toolkit::Any &&info) -> toolkit::Any {
|
||||
auto obj = std::make_shared<Value>();
|
||||
auto session = static_pointer_cast<Session>(info);
|
||||
fillSockInfo(*obj, session.get());
|
||||
(*obj)["typeid"] = toolkit::demangle(typeid(*session).name());
|
||||
return obj;
|
||||
auto &sock = info.get<SockInfo>();
|
||||
fillSockInfo(*obj, &sock);
|
||||
(*obj)["typeid"] = toolkit::demangle(typeid(sock).name());
|
||||
toolkit::Any ret;
|
||||
ret.set(obj);
|
||||
return ret;
|
||||
});
|
||||
});
|
||||
|
||||
api_regist("/index/api/broadcastMessage", [](API_ARGS_MAP) {
|
||||
CHECK_SECRET();
|
||||
CHECK_ARGS("schema", "vhost", "app", "stream", "msg");
|
||||
auto src = MediaSource::find(allArgs["schema"], allArgs["vhost"], allArgs["app"], allArgs["stream"]);
|
||||
if (!src) {
|
||||
throw ApiRetException("can not find the stream", API::NotFound);
|
||||
}
|
||||
Any any;
|
||||
Buffer::Ptr buffer = std::make_shared<BufferLikeString>(allArgs["msg"]);
|
||||
any.set(std::move(buffer));
|
||||
src->broadcastMessage(any);
|
||||
});
|
||||
|
||||
//测试url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs
|
||||
api_regist("/index/api/getMediaInfo",[](API_ARGS_MAP_ASYNC){
|
||||
CHECK_SECRET();
|
||||
|
@ -44,6 +44,8 @@ typedef enum {
|
||||
OtherFailed = -1,//业务代码执行失败,
|
||||
Success = 0//执行成功
|
||||
} ApiErr;
|
||||
|
||||
extern const std::string kSecret;
|
||||
}//namespace API
|
||||
|
||||
class ApiRetException: public std::runtime_error {
|
||||
@ -219,14 +221,19 @@ bool checkArgs(Args &args, const First &first, const KeyTypes &...keys) {
|
||||
throw InvalidArgsException("缺少必要参数:" #__VA_ARGS__); \
|
||||
}
|
||||
|
||||
//检查http参数中是否附带secret密钥的宏,127.0.0.1的ip不检查密钥
|
||||
// 检查http参数中是否附带secret密钥的宏,127.0.0.1的ip不检查密钥
|
||||
// 同时检测是否在ip白名单内
|
||||
#define CHECK_SECRET() \
|
||||
if(sender.get_peer_ip() != "127.0.0.1"){ \
|
||||
do { \
|
||||
auto ip = sender.get_peer_ip(); \
|
||||
if (!HttpFileManager::isIPAllowed(ip)) { \
|
||||
throw AuthException("Your ip is not allowed to access the service."); \
|
||||
} \
|
||||
CHECK_ARGS("secret"); \
|
||||
if(api_secret != allArgs["secret"]){ \
|
||||
if (api_secret != allArgs["secret"]) { \
|
||||
throw AuthException("secret错误"); \
|
||||
} \
|
||||
}
|
||||
} while(false);
|
||||
|
||||
void installWebApi();
|
||||
void unInstallWebApi();
|
||||
|
@ -37,6 +37,7 @@ const string kOnFlowReport = HOOK_FIELD "on_flow_report";
|
||||
const string kOnRtspRealm = HOOK_FIELD "on_rtsp_realm";
|
||||
const string kOnRtspAuth = HOOK_FIELD "on_rtsp_auth";
|
||||
const string kOnStreamChanged = HOOK_FIELD "on_stream_changed";
|
||||
const string kStreamChangedSchemas = HOOK_FIELD "stream_changed_schemas";
|
||||
const string kOnStreamNotFound = HOOK_FIELD "on_stream_not_found";
|
||||
const string kOnRecordMp4 = HOOK_FIELD "on_record_mp4";
|
||||
const string kOnRecordTs = HOOK_FIELD "on_record_ts";
|
||||
@ -48,7 +49,6 @@ const string kOnServerExited = HOOK_FIELD "on_server_exited";
|
||||
const string kOnServerKeepalive = HOOK_FIELD "on_server_keepalive";
|
||||
const string kOnSendRtpStopped = HOOK_FIELD "on_send_rtp_stopped";
|
||||
const string kOnRtpServerTimeout = HOOK_FIELD "on_rtp_server_timeout";
|
||||
const string kAdminParams = HOOK_FIELD "admin_params";
|
||||
const string kAliveInterval = HOOK_FIELD "alive_interval";
|
||||
const string kRetry = HOOK_FIELD "retry";
|
||||
const string kRetryDelay = HOOK_FIELD "retry_delay";
|
||||
@ -74,10 +74,10 @@ static onceToken token([]() {
|
||||
mINI::Instance()[kOnServerKeepalive] = "";
|
||||
mINI::Instance()[kOnSendRtpStopped] = "";
|
||||
mINI::Instance()[kOnRtpServerTimeout] = "";
|
||||
mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
|
||||
mINI::Instance()[kAliveInterval] = 30.0;
|
||||
mINI::Instance()[kRetry] = 1;
|
||||
mINI::Instance()[kRetryDelay] = 3.0;
|
||||
mINI::Instance()[kStreamChangedSchemas] = "rtsp/rtmp/fmp4/ts/hls/hls.fmp4";
|
||||
});
|
||||
} // namespace Hook
|
||||
|
||||
@ -166,12 +166,16 @@ string getVhost(const HttpArgs &value) {
|
||||
return val != value.end() ? val->second : "";
|
||||
}
|
||||
|
||||
static atomic<uint64_t> s_hook_index { 0 };
|
||||
|
||||
void do_http_hook(const string &url, const ArgsType &body, const function<void(const Value &, const string &)> &func, uint32_t retry) {
|
||||
GET_CONFIG(string, mediaServerId, General::kMediaServerId);
|
||||
GET_CONFIG(float, hook_timeoutSec, Hook::kTimeoutSec);
|
||||
GET_CONFIG(float, retry_delay, Hook::kRetryDelay);
|
||||
|
||||
const_cast<ArgsType &>(body)["mediaServerId"] = mediaServerId;
|
||||
const_cast<ArgsType &>(body)["hook_index"] = s_hook_index++;
|
||||
|
||||
auto requester = std::make_shared<HttpRequester>();
|
||||
requester->setMethod("POST");
|
||||
auto bodyStr = to_string(body);
|
||||
@ -331,11 +335,10 @@ static mINI jsonToMini(const Value &obj) {
|
||||
|
||||
void installWebHook() {
|
||||
GET_CONFIG(bool, hook_enable, Hook::kEnable);
|
||||
GET_CONFIG(string, hook_adminparams, Hook::kAdminParams);
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) {
|
||||
GET_CONFIG(string, hook_publish, Hook::kOnPublish);
|
||||
if (!hook_enable || args.param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1") {
|
||||
if (!hook_enable || hook_publish.empty()) {
|
||||
invoker("", ProtocolOption());
|
||||
return;
|
||||
}
|
||||
@ -360,7 +363,7 @@ void installWebHook() {
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) {
|
||||
GET_CONFIG(string, hook_play, Hook::kOnPlay);
|
||||
if (!hook_enable || args.param_strs == hook_adminparams || hook_play.empty() || sender.get_peer_ip() == "127.0.0.1") {
|
||||
if (!hook_enable || hook_play.empty()) {
|
||||
invoker("");
|
||||
return;
|
||||
}
|
||||
@ -374,7 +377,7 @@ void installWebHook() {
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) {
|
||||
GET_CONFIG(string, hook_flowreport, Hook::kOnFlowReport);
|
||||
if (!hook_enable || args.param_strs == hook_adminparams || hook_flowreport.empty() || sender.get_peer_ip() == "127.0.0.1") {
|
||||
if (!hook_enable || hook_flowreport.empty()) {
|
||||
return;
|
||||
}
|
||||
auto body = make_json(args);
|
||||
@ -393,7 +396,7 @@ void installWebHook() {
|
||||
// 监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastOnGetRtspRealm, [](BroadcastOnGetRtspRealmArgs) {
|
||||
GET_CONFIG(string, hook_rtsp_realm, Hook::kOnRtspRealm);
|
||||
if (!hook_enable || args.param_strs == hook_adminparams || hook_rtsp_realm.empty() || sender.get_peer_ip() == "127.0.0.1") {
|
||||
if (!hook_enable || hook_rtsp_realm.empty()) {
|
||||
// 无需认证
|
||||
invoker("");
|
||||
return;
|
||||
@ -441,10 +444,26 @@ void installWebHook() {
|
||||
|
||||
// 监听rtsp、rtmp源注册或注销事件
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaChanged, [](BroadcastMediaChangedArgs) {
|
||||
GET_CONFIG(string, hook_stream_chaned, Hook::kOnStreamChanged);
|
||||
if (!hook_enable || hook_stream_chaned.empty()) {
|
||||
GET_CONFIG(string, hook_stream_changed, Hook::kOnStreamChanged);
|
||||
if (!hook_enable || hook_stream_changed.empty()) {
|
||||
return;
|
||||
}
|
||||
GET_CONFIG_FUNC(std::set<std::string>, stream_changed_set, Hook::kStreamChangedSchemas, [](const std::string &str) {
|
||||
std::set<std::string> ret;
|
||||
auto vec = split(str, "/");
|
||||
for (auto &schema : vec) {
|
||||
trim(schema);
|
||||
if (!schema.empty()) {
|
||||
ret.emplace(schema);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
if (!stream_changed_set.empty() && stream_changed_set.find(sender.getSchema()) == stream_changed_set.end()) {
|
||||
// 该协议注册注销事件被忽略
|
||||
return;
|
||||
}
|
||||
|
||||
ArgsType body;
|
||||
if (bRegist) {
|
||||
body = makeMediaSourceJson(sender);
|
||||
@ -455,7 +474,7 @@ void installWebHook() {
|
||||
body["regist"] = bRegist;
|
||||
}
|
||||
// 执行hook
|
||||
do_http_hook(hook_stream_chaned, body, nullptr);
|
||||
do_http_hook(hook_stream_changed, body, nullptr);
|
||||
});
|
||||
|
||||
GET_CONFIG_FUNC(vector<string>, origin_urls, Cluster::kOriginUrl, [](const string &str) {
|
||||
@ -542,7 +561,7 @@ void installWebHook() {
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastShellLogin, [](BroadcastShellLoginArgs) {
|
||||
GET_CONFIG(string, hook_shell_login, Hook::kOnShellLogin);
|
||||
if (!hook_enable || hook_shell_login.empty() || sender.get_peer_ip() == "127.0.0.1") {
|
||||
if (!hook_enable || hook_shell_login.empty()) {
|
||||
invoker("");
|
||||
return;
|
||||
}
|
||||
@ -585,7 +604,7 @@ void installWebHook() {
|
||||
});
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastSendRtpStopped, [](BroadcastSendRtpStopped) {
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastSendRtpStopped, [](BroadcastSendRtpStoppedArgs) {
|
||||
GET_CONFIG(string, hook_send_rtp_stopped, Hook::kOnSendRtpStopped);
|
||||
if (!hook_enable || hook_send_rtp_stopped.empty()) {
|
||||
return;
|
||||
@ -620,15 +639,14 @@ void installWebHook() {
|
||||
// 追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastHttpAccess, [](BroadcastHttpAccessArgs) {
|
||||
GET_CONFIG(string, hook_http_access, Hook::kOnHttpAccess);
|
||||
if (sender.get_peer_ip() == "127.0.0.1" || parser.params() == hook_adminparams) {
|
||||
// 如果是本机或超级管理员访问,那么不做访问鉴权;权限有效期1个小时
|
||||
invoker("", "", 60 * 60);
|
||||
return;
|
||||
}
|
||||
if (!hook_enable || hook_http_access.empty()) {
|
||||
// 未开启http文件访问鉴权,那么允许访问,但是每次访问都要鉴权;
|
||||
// 因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权)
|
||||
invoker("", "", 0);
|
||||
if (!HttpFileManager::isIPAllowed(sender.get_peer_ip())) {
|
||||
invoker("Your ip is not allowed to access the service.", "", 0);
|
||||
} else {
|
||||
invoker("", "", 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -656,7 +674,7 @@ void installWebHook() {
|
||||
});
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeout) {
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeoutArgs) {
|
||||
GET_CONFIG(string, rtp_server_timeout, Hook::kOnRtpServerTimeout);
|
||||
if (!hook_enable || rtp_server_timeout.empty()) {
|
||||
return;
|
||||
|
@ -351,6 +351,14 @@ int start_main(int argc,char *argv[]) {
|
||||
#endif //defined(ENABLE_SRT)
|
||||
|
||||
try {
|
||||
auto &secret = mINI::Instance()[API::kSecret];
|
||||
if (secret == "035c73f7-bb6b-4889-a715-d9eb2d1925cc" || secret.empty()) {
|
||||
// 使用默认secret被禁止启动
|
||||
secret = makeRandStr(32, true);
|
||||
mINI::Instance().dumpFile(g_ini_file);
|
||||
WarnL << "The " << API::kSecret << " is invalid, modified it to: " << secret
|
||||
<< ", saved config file: " << g_ini_file;
|
||||
}
|
||||
//rtsp服务器,端口默认554
|
||||
if (rtspPort) { rtspSrv->start<RtspSession>(rtspPort); }
|
||||
//rtsps服务器,端口默认322
|
||||
@ -388,8 +396,7 @@ int start_main(int argc,char *argv[]) {
|
||||
#endif//defined(ENABLE_SRT)
|
||||
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << "端口占用或无权限:" << ex.what();
|
||||
ErrorL << "程序启动失败,请修改配置文件中端口号后重试!";
|
||||
ErrorL << "Start server failed: " << ex.what();
|
||||
sleep(1);
|
||||
#if !defined(_WIN32)
|
||||
if (pid != getpid() && kill_parent_if_failed) {
|
||||
@ -413,6 +420,12 @@ int start_main(int argc,char *argv[]) {
|
||||
sem.post();
|
||||
}); // 设置退出信号
|
||||
|
||||
signal(SIGTERM,[](int) {
|
||||
WarnL << "SIGTERM:exit";
|
||||
signal(SIGTERM, SIG_IGN);
|
||||
sem.post();
|
||||
});
|
||||
|
||||
#if !defined(_WIN32)
|
||||
signal(SIGHUP, [](int) { mediakit::loadIniConfig(g_ini_file.data()); });
|
||||
#endif
|
||||
|
@ -436,6 +436,7 @@ FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std:
|
||||
av_dict_set(&dict, "zerolatency", "1", 0);
|
||||
av_dict_set(&dict, "strict", "-2", 0);
|
||||
|
||||
#ifdef AV_CODEC_CAP_TRUNCATED
|
||||
if (codec->capabilities & AV_CODEC_CAP_TRUNCATED) {
|
||||
/* we do not send complete frames */
|
||||
_context->flags |= AV_CODEC_FLAG_TRUNCATED;
|
||||
@ -443,6 +444,7 @@ FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std:
|
||||
// 此时业务层应该需要合帧
|
||||
_do_merger = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
int ret = avcodec_open2(_context.get(), codec, &dict);
|
||||
av_dict_free(&dict);
|
||||
|
@ -15,8 +15,10 @@
|
||||
#include "MediaSource.h"
|
||||
#include "Common/config.h"
|
||||
#include "Common/Parser.h"
|
||||
#include "Common/MultiMediaSourceMuxer.h"
|
||||
#include "Record/MP4Reader.h"
|
||||
#include "PacketCache.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
@ -56,9 +58,11 @@ ProtocolOption::ProtocolOption() {
|
||||
GET_CONFIG(int, s_modify_stamp, Protocol::kModifyStamp);
|
||||
GET_CONFIG(bool, s_enabel_audio, Protocol::kEnableAudio);
|
||||
GET_CONFIG(bool, s_add_mute_audio, Protocol::kAddMuteAudio);
|
||||
GET_CONFIG(bool, s_auto_close, Protocol::kAutoClose);
|
||||
GET_CONFIG(uint32_t, s_continue_push_ms, Protocol::kContinuePushMS);
|
||||
|
||||
GET_CONFIG(bool, s_enable_hls, Protocol::kEnableHls);
|
||||
GET_CONFIG(bool, s_enable_hls_fmp4, Protocol::kEnableHlsFmp4);
|
||||
GET_CONFIG(bool, s_enable_mp4, Protocol::kEnableMP4);
|
||||
GET_CONFIG(bool, s_enable_rtsp, Protocol::kEnableRtsp);
|
||||
GET_CONFIG(bool, s_enable_rtmp, Protocol::kEnableRtmp);
|
||||
@ -80,9 +84,11 @@ ProtocolOption::ProtocolOption() {
|
||||
modify_stamp = s_modify_stamp;
|
||||
enable_audio = s_enabel_audio;
|
||||
add_mute_audio = s_add_mute_audio;
|
||||
auto_close = s_auto_close;
|
||||
continue_push_ms = s_continue_push_ms;
|
||||
|
||||
enable_hls = s_enable_hls;
|
||||
enable_hls_fmp4 = s_enable_hls_fmp4;
|
||||
enable_mp4 = s_enable_mp4;
|
||||
enable_rtsp = s_enable_rtsp;
|
||||
enable_rtmp = s_enable_rtmp;
|
||||
@ -170,20 +176,8 @@ void MediaSource::setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
std::weak_ptr<MediaSourceEvent> MediaSource::getListener(bool next) const{
|
||||
if (!next) {
|
||||
return _listener;
|
||||
}
|
||||
|
||||
auto listener = dynamic_pointer_cast<MediaSourceEventInterceptor>(_listener.lock());
|
||||
if (!listener) {
|
||||
//不是MediaSourceEventInterceptor对象或者对象已经销毁
|
||||
return _listener;
|
||||
}
|
||||
//获取被拦截的对象
|
||||
auto next_obj = listener->getDelegate();
|
||||
//有则返回之
|
||||
return next_obj ? next_obj : _listener;
|
||||
std::weak_ptr<MediaSourceEvent> MediaSource::getListener() const {
|
||||
return _listener;
|
||||
}
|
||||
|
||||
int MediaSource::totalReaderCount(){
|
||||
@ -275,6 +269,11 @@ toolkit::EventPoller::Ptr MediaSource::getOwnerPoller() {
|
||||
throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed: " + getUrl());
|
||||
}
|
||||
|
||||
std::shared_ptr<MultiMediaSourceMuxer> MediaSource::getMuxer() {
|
||||
auto listener = _listener.lock();
|
||||
return listener ? listener->getMuxer(*this) : nullptr;
|
||||
}
|
||||
|
||||
void MediaSource::onReaderChanged(int size) {
|
||||
try {
|
||||
weak_ptr<MediaSource> weak_self = shared_from_this();
|
||||
@ -470,7 +469,7 @@ static void findAsync_l(const MediaInfo &info, const std::shared_ptr<Session> &s
|
||||
});
|
||||
};
|
||||
//广播未找到流,此时可以立即去拉流,这样还来得及
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastNotFoundStream, info, static_cast<SockInfo &>(*session), close_player);
|
||||
NOTICE_EMIT(BroadcastNotFoundStreamArgs, Broadcast::kBroadcastNotFoundStream, info, *session, close_player);
|
||||
}
|
||||
|
||||
void MediaSource::findAsync(const MediaInfo &info, const std::shared_ptr<Session> &session, const function<void (const Ptr &)> &cb) {
|
||||
@ -500,7 +499,7 @@ void MediaSource::emitEvent(bool regist){
|
||||
listener->onRegist(*this, regist);
|
||||
}
|
||||
//触发广播
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, regist, *this);
|
||||
NOTICE_EMIT(BroadcastMediaChangedArgs, Broadcast::kBroadcastMediaChanged, regist, *this);
|
||||
InfoL << (regist ? "媒体注册:" : "媒体注销:") << getUrl();
|
||||
}
|
||||
|
||||
@ -663,8 +662,15 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){
|
||||
}
|
||||
|
||||
if (!is_mp4_vod) {
|
||||
//直播时触发无人观看事件,让开发者自行选择是否关闭
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender);
|
||||
auto muxer = strong_sender->getMuxer();
|
||||
if (muxer && muxer->getOption().auto_close) {
|
||||
// 此流被标记为无人观看自动关闭流
|
||||
WarnL << "Auto cloe stream when none reader: " << strong_sender->getUrl();
|
||||
strong_sender->close(false);
|
||||
} else {
|
||||
// 直播时触发无人观看事件,让开发者自行选择是否关闭
|
||||
NOTICE_EMIT(BroadcastStreamNoneReaderArgs, Broadcast::kBroadcastStreamNoneReader, *strong_sender);
|
||||
}
|
||||
} else {
|
||||
//这个是mp4点播,我们自动关闭
|
||||
WarnL << "MP4点播无人观看,自动关闭:" << strong_sender->getUrl();
|
||||
@ -778,6 +784,11 @@ toolkit::EventPoller::Ptr MediaSourceEventInterceptor::getOwnerPoller(MediaSourc
|
||||
throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed");
|
||||
}
|
||||
|
||||
std::shared_ptr<MultiMediaSourceMuxer> MediaSourceEventInterceptor::getMuxer(MediaSource &sender) {
|
||||
auto listener = _listener.lock();
|
||||
return listener ? listener->getMuxer(sender) : nullptr;
|
||||
}
|
||||
|
||||
bool MediaSourceEventInterceptor::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) {
|
||||
auto listener = _listener.lock();
|
||||
if (!listener) {
|
||||
|
@ -41,6 +41,7 @@ enum class MediaOriginType : uint8_t {
|
||||
std::string getOriginTypeString(MediaOriginType type);
|
||||
|
||||
class MediaSource;
|
||||
class MultiMediaSourceMuxer;
|
||||
class MediaSourceEvent {
|
||||
public:
|
||||
friend class MediaSource;
|
||||
@ -88,6 +89,8 @@ public:
|
||||
virtual bool isRecording(MediaSource &sender, Recorder::type type) { return false; }
|
||||
// 获取所有track相关信息
|
||||
virtual std::vector<Track::Ptr> getMediaTracks(MediaSource &sender, bool trackReady = true) const { return std::vector<Track::Ptr>(); };
|
||||
// 获取MultiMediaSourceMuxer对象
|
||||
virtual std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) { return nullptr; }
|
||||
|
||||
class SendRtpArgs {
|
||||
public:
|
||||
@ -148,11 +151,18 @@ public:
|
||||
bool enable_audio;
|
||||
//添加静音音频,在关闭音频时,此开关无效
|
||||
bool add_mute_audio;
|
||||
// 无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)
|
||||
// 此配置置1时,此流如果无人观看,将不触发on_none_reader hook回调,
|
||||
// 而是将直接关闭流
|
||||
bool auto_close;
|
||||
|
||||
//断连续推延时,单位毫秒,默认采用配置文件
|
||||
uint32_t continue_push_ms;
|
||||
|
||||
//是否开启转换为hls
|
||||
//是否开启转换为hls(mpegts)
|
||||
bool enable_hls;
|
||||
//是否开启转换为hls(fmp4)
|
||||
bool enable_hls_fmp4;
|
||||
//是否开启MP4录制
|
||||
bool enable_mp4;
|
||||
//是否开启转换为rtsp/webrtc
|
||||
@ -185,15 +195,20 @@ public:
|
||||
//hls录制保存路径
|
||||
std::string hls_save_path;
|
||||
|
||||
// 支持通过on_publish返回值替换stream_id
|
||||
std::string stream_replace;
|
||||
|
||||
template <typename MAP>
|
||||
ProtocolOption(const MAP &allArgs) : ProtocolOption() {
|
||||
#define GET_OPT_VALUE(key) getArgsValue(allArgs, #key, key)
|
||||
GET_OPT_VALUE(modify_stamp);
|
||||
GET_OPT_VALUE(enable_audio);
|
||||
GET_OPT_VALUE(add_mute_audio);
|
||||
GET_OPT_VALUE(auto_close);
|
||||
GET_OPT_VALUE(continue_push_ms);
|
||||
|
||||
GET_OPT_VALUE(enable_hls);
|
||||
GET_OPT_VALUE(enable_hls_fmp4);
|
||||
GET_OPT_VALUE(enable_mp4);
|
||||
GET_OPT_VALUE(enable_rtsp);
|
||||
GET_OPT_VALUE(enable_rtmp);
|
||||
@ -211,6 +226,7 @@ public:
|
||||
GET_OPT_VALUE(mp4_save_path);
|
||||
|
||||
GET_OPT_VALUE(hls_save_path);
|
||||
GET_OPT_VALUE(stream_replace);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -250,6 +266,7 @@ public:
|
||||
bool stopSendRtp(MediaSource &sender, const std::string &ssrc) override;
|
||||
float getLossRate(MediaSource &sender, TrackType type) override;
|
||||
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
|
||||
std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) override;
|
||||
|
||||
private:
|
||||
std::weak_ptr<MediaSourceEvent> _listener;
|
||||
@ -323,19 +340,21 @@ public:
|
||||
// 设置监听者
|
||||
virtual void setListener(const std::weak_ptr<MediaSourceEvent> &listener);
|
||||
// 获取监听者
|
||||
std::weak_ptr<MediaSourceEvent> getListener(bool next = false) const;
|
||||
std::weak_ptr<MediaSourceEvent> getListener() const;
|
||||
|
||||
// 本协议获取观看者个数,可能返回本协议的观看人数,也可能返回总人数
|
||||
virtual int readerCount() = 0;
|
||||
// 观看者个数,包括(hls/rtsp/rtmp)
|
||||
virtual int totalReaderCount();
|
||||
// 获取播放器列表
|
||||
virtual void getPlayerList(const std::function<void(const std::list<std::shared_ptr<void>> &info_list)> &cb,
|
||||
const std::function<std::shared_ptr<void>(std::shared_ptr<void> &&info)> &on_change) {
|
||||
virtual void getPlayerList(const std::function<void(const std::list<toolkit::Any> &info_list)> &cb,
|
||||
const std::function<toolkit::Any(toolkit::Any &&info)> &on_change) {
|
||||
assert(cb);
|
||||
cb(std::list<std::shared_ptr<void>>());
|
||||
cb(std::list<toolkit::Any>());
|
||||
}
|
||||
|
||||
virtual bool broadcastMessage(const toolkit::Any &data) { return false; }
|
||||
|
||||
// 获取媒体源类型
|
||||
MediaOriginType getOriginType() const;
|
||||
// 获取媒体源url或者文件路径
|
||||
@ -365,6 +384,8 @@ public:
|
||||
float getLossRate(mediakit::TrackType type);
|
||||
// 获取所在线程
|
||||
toolkit::EventPoller::Ptr getOwnerPoller();
|
||||
// 获取MultiMediaSourceMuxer对象
|
||||
std::shared_ptr<MultiMediaSourceMuxer> getMuxer();
|
||||
|
||||
////////////////static方法,查找或生成MediaSource////////////////
|
||||
|
||||
|
@ -37,6 +37,7 @@ static std::shared_ptr<MediaSinkInterface> makeRecorder(MediaSource &sender, con
|
||||
for (auto &track : tracks) {
|
||||
recorder->addTrack(track);
|
||||
}
|
||||
recorder->addTrackCompleted();
|
||||
return recorder;
|
||||
}
|
||||
|
||||
@ -70,6 +71,14 @@ static string getTrackInfoStr(const TrackSource *track_src){
|
||||
return std::move(codec_info);
|
||||
}
|
||||
|
||||
const ProtocolOption &MultiMediaSourceMuxer::getOption() const {
|
||||
return _option;
|
||||
}
|
||||
|
||||
const MediaTuple &MultiMediaSourceMuxer::getMediaTuple() const {
|
||||
return _tuple;
|
||||
}
|
||||
|
||||
std::string MultiMediaSourceMuxer::shortUrl() const {
|
||||
auto ret = getOriginUrl(MediaSource::NullMediaSource());
|
||||
if (!ret.empty()) {
|
||||
@ -79,6 +88,10 @@ std::string MultiMediaSourceMuxer::shortUrl() const {
|
||||
}
|
||||
|
||||
MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_sec, const ProtocolOption &option): _tuple(tuple) {
|
||||
if (!option.stream_replace.empty()) {
|
||||
// 支持在on_publish hook中替换stream_id
|
||||
_tuple.stream = option.stream_replace;
|
||||
}
|
||||
_poller = EventPollerPool::Instance().getPoller();
|
||||
_create_in_poller = _poller->isCurrentThread();
|
||||
_option = option;
|
||||
@ -97,17 +110,18 @@ MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_
|
||||
if (option.enable_hls) {
|
||||
_hls = dynamic_pointer_cast<HlsRecorder>(Recorder::createRecorder(Recorder::type_hls, _tuple, option));
|
||||
}
|
||||
if (option.enable_hls_fmp4) {
|
||||
_hls_fmp4 = dynamic_pointer_cast<HlsFMP4Recorder>(Recorder::createRecorder(Recorder::type_hls_fmp4, _tuple, option));
|
||||
}
|
||||
if (option.enable_mp4) {
|
||||
_mp4 = Recorder::createRecorder(Recorder::type_mp4, _tuple, option);
|
||||
}
|
||||
if (option.enable_ts) {
|
||||
_ts = std::make_shared<TSMediaSourceMuxer>(_tuple, option);
|
||||
_ts = dynamic_pointer_cast<TSMediaSourceMuxer>(Recorder::createRecorder(Recorder::type_ts, _tuple, option));
|
||||
}
|
||||
#if defined(ENABLE_MP4)
|
||||
if (option.enable_fmp4) {
|
||||
_fmp4 = std::make_shared<FMP4MediaSourceMuxer>(_tuple, option);
|
||||
_fmp4 = dynamic_pointer_cast<FMP4MediaSourceMuxer>(Recorder::createRecorder(Recorder::type_fmp4, _tuple, option));
|
||||
}
|
||||
#endif
|
||||
|
||||
//音频相关设置
|
||||
enableAudio(option.enable_audio);
|
||||
@ -128,14 +142,14 @@ void MultiMediaSourceMuxer::setMediaListener(const std::weak_ptr<MediaSourceEven
|
||||
if (_ts) {
|
||||
_ts->setListener(self);
|
||||
}
|
||||
#if defined(ENABLE_MP4)
|
||||
if (_fmp4) {
|
||||
_fmp4->setListener(self);
|
||||
}
|
||||
#endif
|
||||
auto hls = _hls;
|
||||
if (hls) {
|
||||
hls->setListener(self);
|
||||
if (_hls_fmp4) {
|
||||
_hls_fmp4->setListener(self);
|
||||
}
|
||||
if (_hls) {
|
||||
_hls->setListener(self);
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,15 +162,13 @@ std::weak_ptr<MultiMediaSourceMuxer::Listener> MultiMediaSourceMuxer::getTrackLi
|
||||
}
|
||||
|
||||
int MultiMediaSourceMuxer::totalReaderCount() const {
|
||||
auto hls = _hls;
|
||||
return (_rtsp ? _rtsp->readerCount() : 0) +
|
||||
(_rtmp ? _rtmp->readerCount() : 0) +
|
||||
(_ts ? _ts->readerCount() : 0) +
|
||||
#if defined(ENABLE_MP4)
|
||||
(_fmp4 ? _fmp4->readerCount() : 0) +
|
||||
#endif
|
||||
(_mp4 ? _option.mp4_as_player : 0) +
|
||||
(hls ? hls->readerCount() : 0) +
|
||||
(_hls ? _hls->readerCount() : 0) +
|
||||
(_hls_fmp4 ? _hls_fmp4->readerCount() : 0) +
|
||||
(_ring ? _ring->readerCount() : 0);
|
||||
}
|
||||
|
||||
@ -184,6 +196,7 @@ int MultiMediaSourceMuxer::totalReaderCount(MediaSource &sender) {
|
||||
|
||||
//此函数可能跨线程调用
|
||||
bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) {
|
||||
CHECK(getOwnerPoller(MediaSource::NullMediaSource())->isCurrentThread(), "Can only call setupRecord in it's owner poller");
|
||||
onceToken token(nullptr, [&]() {
|
||||
if (_option.mp4_as_player && type == Recorder::type_mp4) {
|
||||
//开启关闭mp4录制,触发观看人数变化相关事件
|
||||
@ -219,19 +232,59 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case Recorder::type_hls_fmp4: {
|
||||
if (start && !_hls_fmp4) {
|
||||
//开始录制
|
||||
_option.hls_save_path = custom_path;
|
||||
auto hls = dynamic_pointer_cast<HlsFMP4Recorder>(makeRecorder(sender, getTracks(), type, _option));
|
||||
if (hls) {
|
||||
//设置HlsMediaSource的事件监听器
|
||||
hls->setListener(shared_from_this());
|
||||
}
|
||||
_hls_fmp4 = hls;
|
||||
} else if (!start && _hls_fmp4) {
|
||||
//停止录制
|
||||
_hls_fmp4 = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case Recorder::type_fmp4: {
|
||||
if (start && !_fmp4) {
|
||||
auto fmp4 = dynamic_pointer_cast<FMP4MediaSourceMuxer>(makeRecorder(sender, getTracks(), type, _option));
|
||||
if (fmp4) {
|
||||
fmp4->setListener(shared_from_this());
|
||||
}
|
||||
_fmp4 = fmp4;
|
||||
} else if (!start && _fmp4) {
|
||||
_fmp4 = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case Recorder::type_ts: {
|
||||
if (start && !_ts) {
|
||||
auto ts = dynamic_pointer_cast<TSMediaSourceMuxer>(makeRecorder(sender, getTracks(), type, _option));
|
||||
if (ts) {
|
||||
ts->setListener(shared_from_this());
|
||||
}
|
||||
_ts = ts;
|
||||
} else if (!start && _ts) {
|
||||
_ts = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default : return false;
|
||||
}
|
||||
}
|
||||
|
||||
//此函数可能跨线程调用
|
||||
bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type) {
|
||||
switch (type){
|
||||
case Recorder::type_hls :
|
||||
return !!_hls;
|
||||
case Recorder::type_mp4 :
|
||||
return !!_mp4;
|
||||
default:
|
||||
return false;
|
||||
switch (type) {
|
||||
case Recorder::type_hls: return !!_hls;
|
||||
case Recorder::type_mp4: return !!_mp4;
|
||||
case Recorder::type_hls_fmp4: return !!_hls_fmp4;
|
||||
case Recorder::type_fmp4: return !!_fmp4;
|
||||
case Recorder::type_ts: return !!_ts;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,7 +316,7 @@ void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const MediaSourceE
|
||||
strong_self->getOwnerPoller(MediaSource::NullMediaSource())->async([=]() {
|
||||
WarnL << "stream:" << strong_self->shortUrl() << " stop send rtp:" << ssrc << ", reason:" << ex;
|
||||
strong_self->_rtp_sender.erase(ssrc);
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastSendRtpStopped, *strong_self, ssrc, ex);
|
||||
NOTICE_EMIT(BroadcastSendRtpStoppedArgs, Broadcast::kBroadcastSendRtpStopped, *strong_self, ssrc, ex);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -320,6 +373,10 @@ EventPoller::Ptr MultiMediaSourceMuxer::getOwnerPoller(MediaSource &sender) {
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<MultiMediaSourceMuxer> MultiMediaSourceMuxer::getMuxer(MediaSource &sender) {
|
||||
return shared_from_this();
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) {
|
||||
bool ret = false;
|
||||
if (_rtmp) {
|
||||
@ -331,20 +388,17 @@ bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) {
|
||||
if (_ts) {
|
||||
ret = _ts->addTrack(track) ? true : ret;
|
||||
}
|
||||
#if defined(ENABLE_MP4)
|
||||
if (_fmp4) {
|
||||
ret = _fmp4->addTrack(track) ? true : ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
||||
auto hls = _hls;
|
||||
if (hls) {
|
||||
ret = hls->addTrack(track) ? true : ret;
|
||||
if (_hls) {
|
||||
ret = _hls->addTrack(track) ? true : ret;
|
||||
}
|
||||
auto mp4 = _mp4;
|
||||
if (mp4) {
|
||||
ret = mp4->addTrack(track) ? true : ret;
|
||||
if (_hls_fmp4) {
|
||||
ret = _hls_fmp4->addTrack(track) ? true : ret;
|
||||
}
|
||||
if (_mp4) {
|
||||
ret = _mp4->addTrack(track) ? true : ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@ -354,16 +408,27 @@ void MultiMediaSourceMuxer::onAllTrackReady() {
|
||||
setMediaListener(getDelegate());
|
||||
|
||||
if (_rtmp) {
|
||||
_rtmp->onAllTrackReady();
|
||||
_rtmp->addTrackCompleted();
|
||||
}
|
||||
if (_rtsp) {
|
||||
_rtsp->onAllTrackReady();
|
||||
_rtsp->addTrackCompleted();
|
||||
}
|
||||
if (_ts) {
|
||||
_ts->addTrackCompleted();
|
||||
}
|
||||
if (_mp4) {
|
||||
_mp4->addTrackCompleted();
|
||||
}
|
||||
#if defined(ENABLE_MP4)
|
||||
if (_fmp4) {
|
||||
_fmp4->onAllTrackReady();
|
||||
_fmp4->addTrackCompleted();
|
||||
}
|
||||
#endif
|
||||
if (_hls) {
|
||||
_hls->addTrackCompleted();
|
||||
}
|
||||
if (_hls_fmp4) {
|
||||
_hls_fmp4->addTrackCompleted();
|
||||
}
|
||||
|
||||
auto listener = _track_listener.lock();
|
||||
if (listener) {
|
||||
listener->onAllTrackReady();
|
||||
@ -411,21 +476,17 @@ void MultiMediaSourceMuxer::resetTracks() {
|
||||
if (_ts) {
|
||||
_ts->resetTracks();
|
||||
}
|
||||
#if defined(ENABLE_MP4)
|
||||
if (_fmp4) {
|
||||
_fmp4->resetTracks();
|
||||
}
|
||||
#endif
|
||||
|
||||
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
||||
auto hls = _hls;
|
||||
if (hls) {
|
||||
hls->resetTracks();
|
||||
if (_hls_fmp4) {
|
||||
_hls_fmp4->resetTracks();
|
||||
}
|
||||
|
||||
auto mp4 = _mp4;
|
||||
if (mp4) {
|
||||
mp4->resetTracks();
|
||||
if (_hls) {
|
||||
_hls->resetTracks();
|
||||
}
|
||||
if (_mp4) {
|
||||
_mp4->resetTracks();
|
||||
}
|
||||
}
|
||||
|
||||
@ -447,23 +508,20 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
|
||||
ret = _ts->inputFrame(frame) ? true : ret;
|
||||
}
|
||||
|
||||
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
||||
//此处使用智能指针拷贝来确保线程安全,比互斥锁性能更优
|
||||
auto hls = _hls;
|
||||
if (hls) {
|
||||
ret = hls->inputFrame(frame) ? true : ret;
|
||||
}
|
||||
auto mp4 = _mp4;
|
||||
if (mp4) {
|
||||
ret = mp4->inputFrame(frame) ? true : ret;
|
||||
if (_hls) {
|
||||
ret = _hls->inputFrame(frame) ? true : ret;
|
||||
}
|
||||
|
||||
#if defined(ENABLE_MP4)
|
||||
if (_hls_fmp4) {
|
||||
ret = _hls_fmp4->inputFrame(frame) ? true : ret;
|
||||
}
|
||||
|
||||
if (_mp4) {
|
||||
ret = _mp4->inputFrame(frame) ? true : ret;
|
||||
}
|
||||
if (_fmp4) {
|
||||
ret = _fmp4->inputFrame(frame) ? true : ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_ring) {
|
||||
if (frame->getTrackType() == TrackVideo) {
|
||||
// 视频时,遇到第一帧配置帧或关键帧则标记为gop开始处
|
||||
@ -485,15 +543,14 @@ bool MultiMediaSourceMuxer::isEnabled(){
|
||||
if (!_is_enable || _last_check.elapsedTime() > stream_none_reader_delay_ms) {
|
||||
//无人观看时,每次检查是否真的无人观看
|
||||
//有人观看时,则延迟一定时间检查一遍是否无人观看了(节省性能)
|
||||
auto hls = _hls;
|
||||
_is_enable = (_rtmp ? _rtmp->isEnabled() : false) ||
|
||||
(_rtsp ? _rtsp->isEnabled() : false) ||
|
||||
(_ts ? _ts->isEnabled() : false) ||
|
||||
#if defined(ENABLE_MP4)
|
||||
(_fmp4 ? _fmp4->isEnabled() : false) ||
|
||||
#endif
|
||||
(_ring ? (bool)_ring->readerCount() : false) ||
|
||||
(hls ? hls->isEnabled() : false) || _mp4;
|
||||
(_hls ? _hls->isEnabled() : false) ||
|
||||
(_hls_fmp4 ? _hls_fmp4->isEnabled() : false) ||
|
||||
_mp4;
|
||||
|
||||
if (_is_enable) {
|
||||
//无人观看时,不刷新计时器,因为无人观看时每次都会检查一遍,所以刷新计数器无意义且浪费cpu
|
||||
|
@ -131,9 +131,13 @@ public:
|
||||
*/
|
||||
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
|
||||
|
||||
const MediaTuple& getMediaTuple() const {
|
||||
return _tuple;
|
||||
}
|
||||
/**
|
||||
* 获取本对象
|
||||
*/
|
||||
std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) override;
|
||||
|
||||
const ProtocolOption &getOption() const;
|
||||
const MediaTuple &getMediaTuple() const;
|
||||
std::string shortUrl() const;
|
||||
|
||||
protected:
|
||||
@ -169,18 +173,14 @@ private:
|
||||
toolkit::Ticker _last_check;
|
||||
Stamp _stamp[2];
|
||||
std::weak_ptr<Listener> _track_listener;
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
std::unordered_map<std::string, RingType::RingReader::Ptr> _rtp_sender;
|
||||
#endif //ENABLE_RTPPROXY
|
||||
|
||||
#if defined(ENABLE_MP4)
|
||||
FMP4MediaSourceMuxer::Ptr _fmp4;
|
||||
#endif
|
||||
RtmpMediaSourceMuxer::Ptr _rtmp;
|
||||
RtspMediaSourceMuxer::Ptr _rtsp;
|
||||
TSMediaSourceMuxer::Ptr _ts;
|
||||
MediaSinkInterface::Ptr _mp4;
|
||||
HlsRecorder::Ptr _hls;
|
||||
HlsFMP4Recorder::Ptr _hls_fmp4;
|
||||
toolkit::EventPoller::Ptr _poller;
|
||||
RingType::Ptr _ring;
|
||||
|
||||
|
@ -48,12 +48,17 @@ void Parser::parse(const char *buf, size_t size) {
|
||||
clear();
|
||||
auto ptr = buf;
|
||||
while (true) {
|
||||
auto next_line = strstr(ptr, "\r\n");
|
||||
CHECK(next_line);
|
||||
auto next_line = strchr(ptr, '\n');
|
||||
auto offset = 1;
|
||||
CHECK(next_line && next_line > ptr);
|
||||
if (*(next_line - 1) == '\r') {
|
||||
next_line -= 1;
|
||||
offset = 2;
|
||||
}
|
||||
if (ptr == buf) {
|
||||
auto blank = strchr(ptr, ' ');
|
||||
CHECK(blank > ptr && blank < next_line);
|
||||
_method = std::string(ptr, blank);
|
||||
_method = std::string(ptr, blank - ptr);
|
||||
auto next_blank = strchr(blank + 1, ' ');
|
||||
CHECK(next_blank && next_blank < next_line);
|
||||
_url.assign(blank + 1, next_blank);
|
||||
@ -67,7 +72,7 @@ void Parser::parse(const char *buf, size_t size) {
|
||||
} else {
|
||||
auto pos = strchr(ptr, ':');
|
||||
CHECK(pos > ptr && pos < next_line);
|
||||
std::string key { ptr, pos };
|
||||
std::string key { ptr, static_cast<std::size_t>(pos - ptr) };
|
||||
std::string value;
|
||||
if (pos[1] == ' ') {
|
||||
value.assign(pos + 2, next_line);
|
||||
@ -76,7 +81,7 @@ void Parser::parse(const char *buf, size_t size) {
|
||||
}
|
||||
_headers.emplace_force(trim(std::move(key)), trim(std::move(value)));
|
||||
}
|
||||
ptr = next_line + 2;
|
||||
ptr = next_line + offset;
|
||||
if (strncmp(ptr, "\r\n", 2) == 0) { // 协议解析完毕
|
||||
_content.assign(ptr + 2, buf + size);
|
||||
break;
|
||||
@ -320,7 +325,6 @@ void splitUrl(const std::string &url, std::string &host, uint16_t &port) {
|
||||
host = url.substr(0, pos);
|
||||
checkHost(host);
|
||||
}
|
||||
|
||||
#if 0
|
||||
//测试代码
|
||||
static onceToken token([](){
|
||||
|
@ -31,7 +31,7 @@ bool loadIniConfig(const char *ini_path) {
|
||||
}
|
||||
try {
|
||||
mINI::Instance().parseFile(ini);
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
|
||||
NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig);
|
||||
return true;
|
||||
} catch (std::exception &) {
|
||||
InfoL << "dump ini file to:" << ini;
|
||||
@ -99,9 +99,11 @@ namespace Protocol {
|
||||
const string kModifyStamp = PROTOCOL_FIELD "modify_stamp";
|
||||
const string kEnableAudio = PROTOCOL_FIELD "enable_audio";
|
||||
const string kAddMuteAudio = PROTOCOL_FIELD "add_mute_audio";
|
||||
const string kAutoClose = PROTOCOL_FIELD "auto_close";
|
||||
const string kContinuePushMS = PROTOCOL_FIELD "continue_push_ms";
|
||||
|
||||
const string kEnableHls = PROTOCOL_FIELD "enable_hls";
|
||||
const string kEnableHlsFmp4 = PROTOCOL_FIELD "enable_hls_fmp4";
|
||||
const string kEnableMP4 = PROTOCOL_FIELD "enable_mp4";
|
||||
const string kEnableRtsp = PROTOCOL_FIELD "enable_rtsp";
|
||||
const string kEnableRtmp = PROTOCOL_FIELD "enable_rtmp";
|
||||
@ -125,8 +127,10 @@ static onceToken token([]() {
|
||||
mINI::Instance()[kEnableAudio] = 1;
|
||||
mINI::Instance()[kAddMuteAudio] = 1;
|
||||
mINI::Instance()[kContinuePushMS] = 15000;
|
||||
mINI::Instance()[kAutoClose] = 0;
|
||||
|
||||
mINI::Instance()[kEnableHls] = 1;
|
||||
mINI::Instance()[kEnableHlsFmp4] = 0;
|
||||
mINI::Instance()[kEnableMP4] = 0;
|
||||
mINI::Instance()[kEnableRtsp] = 1;
|
||||
mINI::Instance()[kEnableRtmp] = 1;
|
||||
@ -161,6 +165,7 @@ const string kDirMenu = HTTP_FIELD "dirMenu";
|
||||
const string kForbidCacheSuffix = HTTP_FIELD "forbidCacheSuffix";
|
||||
const string kForwardedIpHeader = HTTP_FIELD "forwarded_ip_header";
|
||||
const string kAllowCrossDomains = HTTP_FIELD "allow_cross_domains";
|
||||
const string kAllowIPRange = HTTP_FIELD "allow_ip_range";
|
||||
|
||||
static onceToken token([]() {
|
||||
mINI::Instance()[kSendBufSize] = 64 * 1024;
|
||||
@ -189,6 +194,7 @@ static onceToken token([]() {
|
||||
mINI::Instance()[kForbidCacheSuffix] = "";
|
||||
mINI::Instance()[kForwardedIpHeader] = "";
|
||||
mINI::Instance()[kAllowCrossDomains] = 1;
|
||||
mINI::Instance()[kAllowIPRange] = "127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255";
|
||||
});
|
||||
|
||||
} // namespace Http
|
||||
@ -225,12 +231,10 @@ static onceToken token([]() {
|
||||
////////////RTMP服务器配置///////////
|
||||
namespace Rtmp {
|
||||
#define RTMP_FIELD "rtmp."
|
||||
const string kModifyStamp = RTMP_FIELD "modifyStamp";
|
||||
const string kHandshakeSecond = RTMP_FIELD "handshakeSecond";
|
||||
const string kKeepAliveSecond = RTMP_FIELD "keepAliveSecond";
|
||||
|
||||
static onceToken token([]() {
|
||||
mINI::Instance()[kModifyStamp] = false;
|
||||
mINI::Instance()[kHandshakeSecond] = 15;
|
||||
mINI::Instance()[kKeepAliveSecond] = 15;
|
||||
});
|
||||
|
@ -99,7 +99,7 @@ extern const std::string kBroadcastStreamNoneReader;
|
||||
|
||||
// rtp推流被动停止时触发
|
||||
extern const std::string kBroadcastSendRtpStopped;
|
||||
#define BroadcastSendRtpStopped MultiMediaSourceMuxer &sender, const std::string &ssrc, const SockException &ex
|
||||
#define BroadcastSendRtpStoppedArgs MultiMediaSourceMuxer &sender, const std::string &ssrc, const SockException &ex
|
||||
|
||||
// 更新配置文件事件广播,执行loadIniConfig函数加载配置文件成功后会触发该广播
|
||||
extern const std::string kBroadcastReloadConfig;
|
||||
@ -107,7 +107,7 @@ extern const std::string kBroadcastReloadConfig;
|
||||
|
||||
// rtp server 超时
|
||||
extern const std::string KBroadcastRtpServerTimeout;
|
||||
#define BroadcastRtpServerTimeout uint16_t &local_port, const string &stream_id,int &tcp_mode, bool &re_use_port, uint32_t &ssrc
|
||||
#define BroadcastRtpServerTimeoutArgs uint16_t &local_port, const string &stream_id,int &tcp_mode, bool &re_use_port, uint32_t &ssrc
|
||||
|
||||
#define ReloadConfigTag ((void *)(0xFF))
|
||||
#define RELOAD_KEY(arg, key) \
|
||||
@ -190,11 +190,17 @@ extern const std::string kModifyStamp;
|
||||
extern const std::string kEnableAudio;
|
||||
//添加静音音频,在关闭音频时,此开关无效
|
||||
extern const std::string kAddMuteAudio;
|
||||
// 无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)
|
||||
// 此配置置1时,此流如果无人观看,将不触发on_none_reader hook回调,
|
||||
// 而是将直接关闭流
|
||||
extern const std::string kAutoClose;
|
||||
//断连续推延时,单位毫秒,默认采用配置文件
|
||||
extern const std::string kContinuePushMS;
|
||||
|
||||
//是否开启转换为hls
|
||||
//是否开启转换为hls(mpegts)
|
||||
extern const std::string kEnableHls;
|
||||
//是否开启转换为hls(fmp4)
|
||||
extern const std::string kEnableHlsFmp4;
|
||||
//是否开启MP4录制
|
||||
extern const std::string kEnableMP4;
|
||||
//是否开启转换为rtsp/webrtc
|
||||
@ -248,6 +254,8 @@ extern const std::string kForbidCacheSuffix;
|
||||
extern const std::string kForwardedIpHeader;
|
||||
// 是否允许所有跨域请求
|
||||
extern const std::string kAllowCrossDomains;
|
||||
// 允许访问http api和http文件索引的ip地址范围白名单,置空情况下不做限制
|
||||
extern const std::string kAllowIPRange;
|
||||
} // namespace Http
|
||||
|
||||
////////////SHELL配置///////////
|
||||
|
@ -32,14 +32,6 @@
|
||||
#define __LITTLE_ENDIAN LITTLE_ENDIAN
|
||||
#endif
|
||||
|
||||
#ifndef PACKED
|
||||
#if !defined(_WIN32)
|
||||
#define PACKED __attribute__((packed))
|
||||
#else
|
||||
#define PACKED
|
||||
#endif //! defined(_WIN32)
|
||||
#endif
|
||||
|
||||
#ifndef CHECK
|
||||
#define CHECK(exp, ...) ::mediakit::Assert_ThrowCpp(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__)
|
||||
#endif // CHECK
|
||||
@ -67,6 +59,7 @@
|
||||
#define HLS_SCHEMA "hls"
|
||||
#define TS_SCHEMA "ts"
|
||||
#define FMP4_SCHEMA "fmp4"
|
||||
#define HLS_FMP4_SCHEMA "hls.fmp4"
|
||||
#define SRT_SCHEMA "srt"
|
||||
#define DEFAULT_VHOST "__defaultVhost__"
|
||||
|
||||
|
@ -246,7 +246,7 @@ AACTrack::AACTrack(const string &aac_cfg) {
|
||||
onReady();
|
||||
}
|
||||
|
||||
const string &AACTrack::getAacCfg() const {
|
||||
const string &AACTrack::getConfig() const {
|
||||
return _cfg;
|
||||
}
|
||||
|
||||
@ -342,7 +342,7 @@ Sdp::Ptr AACTrack::getSdp() {
|
||||
WarnL << getCodecName() << " Track未准备好";
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_shared<AACSdp>(getAacCfg(), getAudioSampleRate(), getAudioChannel(), getBitRate() / 1024);
|
||||
return std::make_shared<AACSdp>(getConfig(), getAudioSampleRate(), getAudioChannel(), getBitRate() / 1024);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
@ -44,7 +44,7 @@ public:
|
||||
/**
|
||||
* 获取aac 配置信息
|
||||
*/
|
||||
const std::string &getAacCfg() const;
|
||||
const std::string &getConfig() const;
|
||||
|
||||
bool ready() override;
|
||||
CodecId getCodecId() const override;
|
||||
|
@ -16,12 +16,9 @@ using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
static string getAacCfg(const RtmpPacket &thiz) {
|
||||
static string getConfig(const RtmpPacket &thiz) {
|
||||
string ret;
|
||||
if (thiz.getMediaType() != FLV_CODEC_AAC) {
|
||||
return ret;
|
||||
}
|
||||
if (!thiz.isCfgFrame()) {
|
||||
if ((RtmpAudioCodec)thiz.getRtmpCodecId() != RtmpAudioCodec::aac) {
|
||||
return ret;
|
||||
}
|
||||
if (thiz.buffer.size() < 4) {
|
||||
@ -33,8 +30,8 @@ static string getAacCfg(const RtmpPacket &thiz) {
|
||||
}
|
||||
|
||||
void AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
if (pkt->isCfgFrame()) {
|
||||
_aac_cfg = getAacCfg(*pkt);
|
||||
if (pkt->isConfigFrame()) {
|
||||
_aac_cfg = getConfig(*pkt);
|
||||
if (!_aac_cfg.empty()) {
|
||||
onGetAAC(nullptr, 0, 0);
|
||||
}
|
||||
@ -82,7 +79,7 @@ AACRtmpEncoder::AACRtmpEncoder(const Track::Ptr &track) {
|
||||
void AACRtmpEncoder::makeConfigPacket() {
|
||||
if (_track && _track->ready()) {
|
||||
//从track中和获取aac配置信息
|
||||
_aac_cfg = _track->getAacCfg();
|
||||
_aac_cfg = _track->getConfig();
|
||||
}
|
||||
|
||||
if (!_aac_cfg.empty()) {
|
||||
@ -93,51 +90,45 @@ void AACRtmpEncoder::makeConfigPacket() {
|
||||
bool AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
if (_aac_cfg.empty()) {
|
||||
if (frame->prefixSize()) {
|
||||
//包含adts头,从adts头获取aac配置信息
|
||||
_aac_cfg = makeAacConfig((uint8_t *) (frame->data()), frame->prefixSize());
|
||||
// 包含adts头,从adts头获取aac配置信息
|
||||
_aac_cfg = makeAacConfig((uint8_t *)(frame->data()), frame->prefixSize());
|
||||
}
|
||||
makeConfigPacket();
|
||||
}
|
||||
|
||||
if(_aac_cfg.empty()){
|
||||
if (_aac_cfg.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto rtmpPkt = RtmpPacket::create();
|
||||
//header
|
||||
uint8_t is_config = false;
|
||||
rtmpPkt->buffer.push_back(_audio_flv_flags);
|
||||
rtmpPkt->buffer.push_back(!is_config);
|
||||
|
||||
//aac data
|
||||
rtmpPkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
||||
|
||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
||||
rtmpPkt->chunk_id = CHUNK_AUDIO;
|
||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||
rtmpPkt->time_stamp = frame->dts();
|
||||
rtmpPkt->type_id = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt);
|
||||
auto pkt = RtmpPacket::create();
|
||||
// header
|
||||
pkt->buffer.push_back(_audio_flv_flags);
|
||||
pkt->buffer.push_back((uint8_t)RtmpAACPacketType::aac_raw);
|
||||
// aac data
|
||||
pkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
||||
pkt->body_size = pkt->buffer.size();
|
||||
pkt->chunk_id = CHUNK_AUDIO;
|
||||
pkt->stream_index = STREAM_MEDIA;
|
||||
pkt->time_stamp = frame->dts();
|
||||
pkt->type_id = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(pkt);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AACRtmpEncoder::makeAudioConfigPkt() {
|
||||
_audio_flv_flags = getAudioRtmpFlags(std::make_shared<AACTrack>(_aac_cfg));
|
||||
auto rtmpPkt = RtmpPacket::create();
|
||||
|
||||
//header
|
||||
uint8_t is_config = true;
|
||||
rtmpPkt->buffer.push_back(_audio_flv_flags);
|
||||
rtmpPkt->buffer.push_back(!is_config);
|
||||
//aac config
|
||||
rtmpPkt->buffer.append(_aac_cfg);
|
||||
|
||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
||||
rtmpPkt->chunk_id = CHUNK_AUDIO;
|
||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||
rtmpPkt->time_stamp = 0;
|
||||
rtmpPkt->type_id = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt);
|
||||
auto pkt = RtmpPacket::create();
|
||||
// header
|
||||
pkt->buffer.push_back(_audio_flv_flags);
|
||||
pkt->buffer.push_back((uint8_t)RtmpAACPacketType::aac_config_header);
|
||||
// aac config
|
||||
pkt->buffer.append(_aac_cfg);
|
||||
pkt->body_size = pkt->buffer.size();
|
||||
pkt->chunk_id = CHUNK_AUDIO;
|
||||
pkt->stream_index = STREAM_MEDIA;
|
||||
pkt->time_stamp = 0;
|
||||
pkt->type_id = MSG_AUDIO;
|
||||
RtmpCodec::inputRtmp(pkt);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
@ -64,7 +64,7 @@ AACRtpDecoder::AACRtpDecoder(const Track::Ptr &track) {
|
||||
if (!aacTrack || !aacTrack->ready()) {
|
||||
WarnL << "该aac track无效!";
|
||||
} else {
|
||||
_aac_cfg = aacTrack->getAacCfg();
|
||||
_aac_cfg = aacTrack->getConfig();
|
||||
}
|
||||
obtainFrame();
|
||||
}
|
||||
|
@ -201,17 +201,20 @@ static CodecId getVideoCodecIdByAmf(const AMFValue &val){
|
||||
}
|
||||
|
||||
if (val.type() != AMF_NULL) {
|
||||
auto type_id = val.as_integer();
|
||||
auto type_id = (RtmpVideoCodec)val.as_integer();
|
||||
switch (type_id) {
|
||||
case FLV_CODEC_H264 : return CodecH264;
|
||||
case FLV_CODEC_H265 : return CodecH265;
|
||||
default : WarnL << "暂不支持该视频Amf:" << type_id; return CodecInvalid;
|
||||
case RtmpVideoCodec::h264: return CodecH264;
|
||||
case RtmpVideoCodec::fourcc_hevc:
|
||||
case RtmpVideoCodec::h265: return CodecH265;
|
||||
case RtmpVideoCodec::fourcc_av1: return CodecAV1;
|
||||
case RtmpVideoCodec::fourcc_vp9: return CodecVP9;
|
||||
default: WarnL << "暂不支持该视频Amf:" << (int)type_id; return CodecInvalid;
|
||||
}
|
||||
}
|
||||
return CodecInvalid;
|
||||
}
|
||||
|
||||
Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0) {
|
||||
Track::Ptr Factory::getTrackByCodecId(CodecId codecId, int sample_rate, int channels, int sample_bit) {
|
||||
switch (codecId){
|
||||
case CodecH264 : return std::make_shared<H264Track>();
|
||||
case CodecH265 : return std::make_shared<H265Track>();
|
||||
@ -243,13 +246,13 @@ static CodecId getAudioCodecIdByAmf(const AMFValue &val) {
|
||||
}
|
||||
|
||||
if (val.type() != AMF_NULL) {
|
||||
auto type_id = val.as_integer();
|
||||
auto type_id = (RtmpAudioCodec)val.as_integer();
|
||||
switch (type_id) {
|
||||
case FLV_CODEC_AAC : return CodecAAC;
|
||||
case FLV_CODEC_G711A : return CodecG711A;
|
||||
case FLV_CODEC_G711U : return CodecG711U;
|
||||
case FLV_CODEC_OPUS : return CodecOpus;
|
||||
default : WarnL << "暂不支持该音频Amf:" << type_id; return CodecInvalid;
|
||||
case RtmpAudioCodec::aac : return CodecAAC;
|
||||
case RtmpAudioCodec::g711a : return CodecG711A;
|
||||
case RtmpAudioCodec::g711u : return CodecG711U;
|
||||
case RtmpAudioCodec::opus : return CodecOpus;
|
||||
default : WarnL << "暂不支持该音频Amf:" << (int)type_id; return CodecInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,13 +294,13 @@ RtmpCodec::Ptr Factory::getRtmpCodecByTrack(const Track::Ptr &track, bool is_enc
|
||||
}
|
||||
|
||||
AMFValue Factory::getAmfByCodecId(CodecId codecId) {
|
||||
switch (codecId){
|
||||
case CodecAAC: return AMFValue(FLV_CODEC_AAC);
|
||||
case CodecH264: return AMFValue(FLV_CODEC_H264);
|
||||
case CodecH265: return AMFValue(FLV_CODEC_H265);
|
||||
case CodecG711A: return AMFValue(FLV_CODEC_G711A);
|
||||
case CodecG711U: return AMFValue(FLV_CODEC_G711U);
|
||||
case CodecOpus: return AMFValue(FLV_CODEC_OPUS);
|
||||
switch (codecId) {
|
||||
case CodecAAC: return AMFValue((int)RtmpAudioCodec::aac);
|
||||
case CodecH264: return AMFValue((int)RtmpVideoCodec::h264);
|
||||
case CodecH265: return AMFValue((int)RtmpVideoCodec::h265);
|
||||
case CodecG711A: return AMFValue((int)RtmpAudioCodec::g711a);
|
||||
case CodecG711U: return AMFValue((int)RtmpAudioCodec::g711u);
|
||||
case CodecOpus: return AMFValue((int)RtmpAudioCodec::opus);
|
||||
default: return AMFValue(AMF_NULL);
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,16 @@ namespace mediakit{
|
||||
|
||||
class Factory {
|
||||
public:
|
||||
|
||||
/**
|
||||
* 根据codec_id 获取track
|
||||
* @param codecId 编码id
|
||||
* @param sample_rate 采样率,视频固定为90000
|
||||
* @param channels 音频通道数
|
||||
* @param sample_bit 音频采样位数
|
||||
*/
|
||||
static Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0);
|
||||
|
||||
////////////////////////////////rtsp相关//////////////////////////////////
|
||||
/**
|
||||
* 根据sdp生成Track对象
|
||||
|
@ -12,9 +12,10 @@
|
||||
#include "SPSParser.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/base64.h"
|
||||
#include "Common/config.h"
|
||||
|
||||
using namespace toolkit;
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
@ -248,7 +249,14 @@ public:
|
||||
_printer << "b=AS:" << bitrate << "\r\n";
|
||||
}
|
||||
_printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << 90000 << "\r\n";
|
||||
_printer << "a=fmtp:" << payload_type << " packetization-mode=1; profile-level-id=";
|
||||
|
||||
/**
|
||||
Single NAI Unit Mode = 0. // Single NAI mode (Only nals from 1-23 are allowed)
|
||||
Non Interleaved Mode = 1,// Non-interleaved Mode: 1-23,24 (STAP-A),28 (FU-A) are allowed
|
||||
Interleaved Mode = 2, // 25 (STAP-B),26 (MTAP16),27 (MTAP24),28 (EU-A),and 29 (EU-B) are allowed.
|
||||
**/
|
||||
GET_CONFIG(bool, h264_stap_a, Rtp::kH264StapA);
|
||||
_printer << "a=fmtp:" << payload_type << " packetization-mode=" << h264_stap_a << "; profile-level-id=";
|
||||
|
||||
uint32_t profile_level_id = 0;
|
||||
if (strSPS.length() >= 4) { // sanity check
|
||||
|
@ -30,10 +30,7 @@ H264Frame::Ptr H264RtmpDecoder::obtainFrame() {
|
||||
* 返回不带0x00 00 00 01头的sps pps
|
||||
*/
|
||||
static bool getH264Config(const RtmpPacket &thiz, string &sps, string &pps) {
|
||||
if (thiz.getMediaType() != FLV_CODEC_H264) {
|
||||
return false;
|
||||
}
|
||||
if (!thiz.isCfgFrame()) {
|
||||
if ((RtmpVideoCodec)thiz.getRtmpCodecId() != RtmpVideoCodec::h264) {
|
||||
return false;
|
||||
}
|
||||
if (thiz.buffer.size() < 13) {
|
||||
@ -59,7 +56,7 @@ static bool getH264Config(const RtmpPacket &thiz, string &sps, string &pps) {
|
||||
}
|
||||
|
||||
void H264RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
if (pkt->isCfgFrame()) {
|
||||
if (pkt->isConfigFrame()) {
|
||||
//缓存sps pps,后续插入到I帧之前
|
||||
if (!getH264Config(*pkt, _sps, _pps)) {
|
||||
WarnL << "get h264 sps/pps failed, rtmp packet is: " << hexdump(pkt->data(), pkt->size());
|
||||
@ -159,26 +156,21 @@ bool H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
}
|
||||
|
||||
return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) {
|
||||
//flags
|
||||
_rtmp_packet->buffer[0] = FLV_CODEC_H264 | ((have_key_frame ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4);
|
||||
//not config
|
||||
_rtmp_packet->buffer[1] = true;
|
||||
int32_t cts = pts - dts;
|
||||
if (cts < 0) {
|
||||
cts = 0;
|
||||
}
|
||||
//cts
|
||||
set_be24(&_rtmp_packet->buffer[2], cts);
|
||||
|
||||
_rtmp_packet->time_stamp = dts;
|
||||
_rtmp_packet->body_size = _rtmp_packet->buffer.size();
|
||||
_rtmp_packet->chunk_id = CHUNK_VIDEO;
|
||||
_rtmp_packet->stream_index = STREAM_MEDIA;
|
||||
_rtmp_packet->type_id = MSG_VIDEO;
|
||||
//输出rtmp packet
|
||||
RtmpCodec::inputRtmp(_rtmp_packet);
|
||||
_rtmp_packet = nullptr;
|
||||
}, &_rtmp_packet->buffer);
|
||||
// flags
|
||||
_rtmp_packet->buffer[0] = (uint8_t)RtmpVideoCodec::h264 | ((uint8_t)(have_key_frame ? RtmpFrameType::key_frame : RtmpFrameType::inter_frame) << 4);
|
||||
_rtmp_packet->buffer[1] = (uint8_t)RtmpH264PacketType::h264_nalu;
|
||||
int32_t cts = pts - dts;
|
||||
// cts
|
||||
set_be24(&_rtmp_packet->buffer[2], cts);
|
||||
_rtmp_packet->time_stamp = dts;
|
||||
_rtmp_packet->body_size = _rtmp_packet->buffer.size();
|
||||
_rtmp_packet->chunk_id = CHUNK_VIDEO;
|
||||
_rtmp_packet->stream_index = STREAM_MEDIA;
|
||||
_rtmp_packet->type_id = MSG_VIDEO;
|
||||
// 输出rtmp packet
|
||||
RtmpCodec::inputRtmp(_rtmp_packet);
|
||||
_rtmp_packet = nullptr;
|
||||
}, &_rtmp_packet->buffer);
|
||||
}
|
||||
|
||||
void H264RtmpEncoder::makeVideoConfigPkt() {
|
||||
@ -186,42 +178,39 @@ void H264RtmpEncoder::makeVideoConfigPkt() {
|
||||
WarnL << "sps长度不足4字节";
|
||||
return;
|
||||
}
|
||||
int8_t flags = FLV_CODEC_H264;
|
||||
flags |= (FLV_KEY_FRAME << 4);
|
||||
bool is_config = true;
|
||||
|
||||
auto rtmpPkt = RtmpPacket::create();
|
||||
//header
|
||||
rtmpPkt->buffer.push_back(flags);
|
||||
rtmpPkt->buffer.push_back(!is_config);
|
||||
//cts
|
||||
rtmpPkt->buffer.append("\x0\x0\x0", 3);
|
||||
|
||||
//AVCDecoderConfigurationRecord start
|
||||
rtmpPkt->buffer.push_back(1); // version
|
||||
rtmpPkt->buffer.push_back(_sps[1]); // profile
|
||||
rtmpPkt->buffer.push_back(_sps[2]); // compat
|
||||
rtmpPkt->buffer.push_back(_sps[3]); // level
|
||||
rtmpPkt->buffer.push_back((char)0xff); // 6 bits reserved + 2 bits nal size length - 1 (11)
|
||||
rtmpPkt->buffer.push_back((char)0xe1); // 3 bits reserved + 5 bits number of sps (00001)
|
||||
//sps
|
||||
auto flags = (uint8_t)RtmpVideoCodec::h264;
|
||||
flags |= ((uint8_t)RtmpFrameType::key_frame << 4);
|
||||
auto pkt = RtmpPacket::create();
|
||||
// header
|
||||
pkt->buffer.push_back(flags);
|
||||
pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header);
|
||||
// cts
|
||||
pkt->buffer.append("\x0\x0\x0", 3);
|
||||
// AVCDecoderConfigurationRecord start
|
||||
pkt->buffer.push_back(1); // version
|
||||
pkt->buffer.push_back(_sps[1]); // profile
|
||||
pkt->buffer.push_back(_sps[2]); // compat
|
||||
pkt->buffer.push_back(_sps[3]); // level
|
||||
pkt->buffer.push_back((char)0xff); // 6 bits reserved + 2 bits nal size length - 1 (11)
|
||||
pkt->buffer.push_back((char)0xe1); // 3 bits reserved + 5 bits number of sps (00001)
|
||||
// sps
|
||||
uint16_t size = (uint16_t)_sps.size();
|
||||
size = htons(size);
|
||||
rtmpPkt->buffer.append((char *) &size, 2);
|
||||
rtmpPkt->buffer.append(_sps);
|
||||
//pps
|
||||
rtmpPkt->buffer.push_back(1); // version
|
||||
pkt->buffer.append((char *)&size, 2);
|
||||
pkt->buffer.append(_sps);
|
||||
// pps
|
||||
pkt->buffer.push_back(1); // version
|
||||
size = (uint16_t)_pps.size();
|
||||
size = htons(size);
|
||||
rtmpPkt->buffer.append((char *) &size, 2);
|
||||
rtmpPkt->buffer.append(_pps);
|
||||
pkt->buffer.append((char *)&size, 2);
|
||||
pkt->buffer.append(_pps);
|
||||
|
||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
||||
rtmpPkt->chunk_id = CHUNK_VIDEO;
|
||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||
rtmpPkt->time_stamp = 0;
|
||||
rtmpPkt->type_id = MSG_VIDEO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt);
|
||||
pkt->body_size = pkt->buffer.size();
|
||||
pkt->chunk_id = CHUNK_VIDEO;
|
||||
pkt->stream_index = STREAM_MEDIA;
|
||||
pkt->time_stamp = 0;
|
||||
pkt->type_id = MSG_VIDEO;
|
||||
RtmpCodec::inputRtmp(pkt);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
|
@ -13,9 +13,7 @@
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
#if defined(_WIN32)
|
||||
#pragma pack(push, 1)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
class FuFlags {
|
||||
public:
|
||||
@ -30,11 +28,9 @@ public:
|
||||
unsigned end_bit: 1;
|
||||
unsigned start_bit: 1;
|
||||
#endif
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
#if defined(_WIN32)
|
||||
#pragma pack(pop)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
H264RtpDecoder::H264RtpDecoder() {
|
||||
_frame = obtainFrame();
|
||||
|
@ -12,12 +12,12 @@
|
||||
#include "H265Rtmp.h"
|
||||
#ifdef ENABLE_MP4
|
||||
#include "mpeg4-hevc.h"
|
||||
#endif//ENABLE_MP4
|
||||
#endif // ENABLE_MP4
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit{
|
||||
namespace mediakit {
|
||||
|
||||
H265RtmpDecoder::H265RtmpDecoder() {
|
||||
_h265frame = obtainFrame();
|
||||
@ -30,46 +30,105 @@ H265Frame::Ptr H265RtmpDecoder::obtainFrame() {
|
||||
}
|
||||
|
||||
#ifdef ENABLE_MP4
|
||||
|
||||
static bool decode_HEVCDecoderConfigurationRecord(uint8_t *extra, size_t bytes, string &frame) {
|
||||
struct mpeg4_hevc_t hevc;
|
||||
memset(&hevc, 0, sizeof(hevc));
|
||||
if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *)extra, bytes, &hevc) > 0) {
|
||||
uint8_t *config = new uint8_t[bytes * 2];
|
||||
int size = mpeg4_hevc_to_nalu(&hevc, config, bytes * 2);
|
||||
if (size > 4) {
|
||||
frame.assign((char *)config + 4, size - 4);
|
||||
}
|
||||
delete[] config;
|
||||
return size > 4;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回不带0x00 00 00 01头的sps
|
||||
* @return
|
||||
*/
|
||||
static bool getH265ConfigFrame(const RtmpPacket &thiz,string &frame) {
|
||||
if (thiz.getMediaType() != FLV_CODEC_H265) {
|
||||
return false;
|
||||
}
|
||||
if (!thiz.isCfgFrame()) {
|
||||
static bool getH265ConfigFrame(const RtmpPacket &thiz, string &frame) {
|
||||
if ((RtmpVideoCodec)thiz.getRtmpCodecId() != RtmpVideoCodec::h265) {
|
||||
return false;
|
||||
}
|
||||
if (thiz.buffer.size() < 6) {
|
||||
WarnL << "bad H265 cfg!";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto extra = thiz.buffer.data() + 5;
|
||||
auto bytes = thiz.buffer.size() - 5;
|
||||
|
||||
struct mpeg4_hevc_t hevc;
|
||||
memset(&hevc, 0, sizeof(hevc));
|
||||
if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *) extra, bytes, &hevc) > 0) {
|
||||
uint8_t *config = new uint8_t[bytes * 2];
|
||||
int size = mpeg4_hevc_to_nalu(&hevc, config, bytes * 2);
|
||||
if (size > 4) {
|
||||
frame.assign((char *) config + 4, size - 4);
|
||||
}
|
||||
delete [] config;
|
||||
return size > 4;
|
||||
}
|
||||
return false;
|
||||
return decode_HEVCDecoderConfigurationRecord((uint8_t *)thiz.buffer.data() + 5, thiz.buffer.size() - 5, frame);
|
||||
}
|
||||
#endif
|
||||
|
||||
void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
if (pkt->isCfgFrame()) {
|
||||
if (_info.codec == CodecInvalid) {
|
||||
// 先判断是否为增强型rtmp
|
||||
parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &_info);
|
||||
}
|
||||
|
||||
if (_info.is_enhanced) {
|
||||
// 增强型rtmp
|
||||
parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &_info);
|
||||
if (!_info.is_enhanced || _info.codec != CodecH265) {
|
||||
throw std::invalid_argument("Invalid enhanced-rtmp hevc packet!");
|
||||
}
|
||||
auto data = (uint8_t *)pkt->data() + 5;
|
||||
auto size = pkt->size() - 5;
|
||||
switch (_info.video.pkt_type) {
|
||||
case RtmpPacketType::PacketTypeSequenceStart: {
|
||||
#ifdef ENABLE_MP4
|
||||
string config;
|
||||
if (decode_HEVCDecoderConfigurationRecord(data, size, config)) {
|
||||
onGetH265(config.data(), config.size(), pkt->time_stamp, pkt->time_stamp);
|
||||
}
|
||||
#else
|
||||
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
case RtmpPacketType::PacketTypeCodedFramesX:
|
||||
case RtmpPacketType::PacketTypeCodedFrames: {
|
||||
auto pts = pkt->time_stamp;
|
||||
if (RtmpPacketType::PacketTypeCodedFrames == _info.video.pkt_type) {
|
||||
// SI24 = [CompositionTime Offset]
|
||||
CHECK(size > 7);
|
||||
int32_t cts = (((data[0] << 16) | (data[1] << 8) | (data[2])) + 0xff800000) ^ 0xff800000;
|
||||
pts += cts;
|
||||
data += 3;
|
||||
size -= 3;
|
||||
}
|
||||
splitFrame(data, size, pkt->time_stamp, pts);
|
||||
break;
|
||||
}
|
||||
|
||||
case RtmpPacketType::PacketTypeMetadata: {
|
||||
// The body does not contain video data. The body is an AMF encoded metadata.
|
||||
// The metadata will be represented by a series of [name, value] pairs.
|
||||
// For now the only defined [name, value] pair is [“colorInfo”, Object]
|
||||
// See Metadata Frame section for more details of this object.
|
||||
//
|
||||
// For a deeper understanding of the encoding please see description
|
||||
// of SCRIPTDATA and SSCRIPTDATAVALUE in the FLV file spec.
|
||||
// DATA = [“colorInfo”, Object]
|
||||
break;
|
||||
}
|
||||
case RtmpPacketType::PacketTypeSequenceEnd: {
|
||||
// signals end of sequence
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 国内扩展(12) H265 rtmp
|
||||
if (pkt->isConfigFrame()) {
|
||||
#ifdef ENABLE_MP4
|
||||
string config;
|
||||
if(getH265ConfigFrame(*pkt,config)){
|
||||
onGetH265(config.data(), config.size(), pkt->time_stamp , pkt->time_stamp);
|
||||
if (getH265ConfigFrame(*pkt, config)) {
|
||||
onGetH265(config.data(), config.size(), pkt->time_stamp, pkt->time_stamp);
|
||||
}
|
||||
#else
|
||||
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
||||
@ -78,41 +137,42 @@ void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
}
|
||||
|
||||
if (pkt->buffer.size() > 9) {
|
||||
auto total_len = pkt->buffer.size();
|
||||
size_t offset = 5;
|
||||
uint8_t *cts_ptr = (uint8_t *) (pkt->buffer.data() + 2);
|
||||
uint8_t *cts_ptr = (uint8_t *)(pkt->buffer.data() + 2);
|
||||
int32_t cts = (((cts_ptr[0] << 16) | (cts_ptr[1] << 8) | (cts_ptr[2])) + 0xff800000) ^ 0xff800000;
|
||||
auto pts = pkt->time_stamp + cts;
|
||||
while (offset + 4 < total_len) {
|
||||
uint32_t frame_len;
|
||||
memcpy(&frame_len, pkt->buffer.data() + offset, 4);
|
||||
frame_len = ntohl(frame_len);
|
||||
offset += 4;
|
||||
if (frame_len + offset > total_len) {
|
||||
break;
|
||||
}
|
||||
onGetH265(pkt->buffer.data() + offset, frame_len, pkt->time_stamp, pts);
|
||||
offset += frame_len;
|
||||
}
|
||||
splitFrame((uint8_t *)pkt->data() + 5, pkt->size() - 5, pkt->time_stamp, pts);
|
||||
}
|
||||
}
|
||||
|
||||
inline void H265RtmpDecoder::onGetH265(const char* pcData, size_t iLen, uint32_t dts,uint32_t pts) {
|
||||
if(iLen == 0){
|
||||
void H265RtmpDecoder::splitFrame(const uint8_t *data, size_t size, uint32_t dts, uint32_t pts) {
|
||||
auto end = data + size;
|
||||
while (data + 4 < end) {
|
||||
uint32_t frame_len = load_be32(data);
|
||||
data += 4;
|
||||
if (data + frame_len > end) {
|
||||
break;
|
||||
}
|
||||
onGetH265((const char *)data, frame_len, dts, pts);
|
||||
data += frame_len;
|
||||
}
|
||||
}
|
||||
|
||||
inline void H265RtmpDecoder::onGetH265(const char *data, size_t size, uint32_t dts, uint32_t pts) {
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
#if 1
|
||||
_h265frame->_dts = dts;
|
||||
_h265frame->_pts = pts;
|
||||
_h265frame->_buffer.assign("\x00\x00\x00\x01", 4); //添加265头
|
||||
_h265frame->_buffer.append(pcData, iLen);
|
||||
_h265frame->_buffer.assign("\x00\x00\x00\x01", 4); // 添加265头
|
||||
_h265frame->_buffer.append(data, size);
|
||||
|
||||
//写入环形缓存
|
||||
// 写入环形缓存
|
||||
RtmpCodec::inputFrame(_h265frame);
|
||||
_h265frame = obtainFrame();
|
||||
#else
|
||||
//防止内存拷贝,这样产生的265帧不会有0x00 00 01头
|
||||
auto frame = std::make_shared<H265FrameNoCacheAble>((char *)pcData,iLen,dts,pts,0);
|
||||
// 防止内存拷贝,这样产生的265帧不会有0x00 00 01头
|
||||
auto frame = std::make_shared<H265FrameNoCacheAble>((char *)data, size, dts, pts, 0);
|
||||
RtmpCodec::inputFrame(frame);
|
||||
#endif
|
||||
}
|
||||
@ -123,16 +183,16 @@ H265RtmpEncoder::H265RtmpEncoder(const Track::Ptr &track) {
|
||||
_track = dynamic_pointer_cast<H265Track>(track);
|
||||
}
|
||||
|
||||
void H265RtmpEncoder::makeConfigPacket(){
|
||||
void H265RtmpEncoder::makeConfigPacket() {
|
||||
if (_track && _track->ready()) {
|
||||
//尝试从track中获取sps pps信息
|
||||
// 尝试从track中获取sps pps信息
|
||||
_sps = _track->getSps();
|
||||
_pps = _track->getPps();
|
||||
_vps = _track->getVps();
|
||||
}
|
||||
|
||||
if (!_sps.empty() && !_pps.empty() && !_vps.empty()) {
|
||||
//获取到sps/pps
|
||||
// 获取到sps/pps
|
||||
makeVideoConfigPkt();
|
||||
_got_config_frame = true;
|
||||
}
|
||||
@ -175,50 +235,42 @@ bool H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
|
||||
if (!_rtmp_packet) {
|
||||
_rtmp_packet = RtmpPacket::create();
|
||||
//flags/not_config/cts预占位
|
||||
// flags/not_config/cts预占位
|
||||
_rtmp_packet->buffer.resize(5);
|
||||
}
|
||||
|
||||
return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) {
|
||||
//flags
|
||||
_rtmp_packet->buffer[0] = FLV_CODEC_H265 | ((have_key_frame ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4);
|
||||
//not config
|
||||
_rtmp_packet->buffer[1] = true;
|
||||
int32_t cts = pts - dts;
|
||||
if (cts < 0) {
|
||||
cts = 0;
|
||||
}
|
||||
//cts
|
||||
set_be24(&_rtmp_packet->buffer[2], cts);
|
||||
|
||||
_rtmp_packet->time_stamp = dts;
|
||||
_rtmp_packet->body_size = _rtmp_packet->buffer.size();
|
||||
_rtmp_packet->chunk_id = CHUNK_VIDEO;
|
||||
_rtmp_packet->stream_index = STREAM_MEDIA;
|
||||
_rtmp_packet->type_id = MSG_VIDEO;
|
||||
//输出rtmp packet
|
||||
RtmpCodec::inputRtmp(_rtmp_packet);
|
||||
_rtmp_packet = nullptr;
|
||||
// flags
|
||||
_rtmp_packet->buffer[0] = (uint8_t)RtmpVideoCodec::h265 | ((uint8_t)(have_key_frame ? RtmpFrameType::key_frame : RtmpFrameType::inter_frame) << 4);
|
||||
_rtmp_packet->buffer[1] = (uint8_t)RtmpH264PacketType::h264_nalu;
|
||||
int32_t cts = pts - dts;
|
||||
// cts
|
||||
set_be24(&_rtmp_packet->buffer[2], cts);
|
||||
_rtmp_packet->time_stamp = dts;
|
||||
_rtmp_packet->body_size = _rtmp_packet->buffer.size();
|
||||
_rtmp_packet->chunk_id = CHUNK_VIDEO;
|
||||
_rtmp_packet->stream_index = STREAM_MEDIA;
|
||||
_rtmp_packet->type_id = MSG_VIDEO;
|
||||
// 输出rtmp packet
|
||||
RtmpCodec::inputRtmp(_rtmp_packet);
|
||||
_rtmp_packet = nullptr;
|
||||
}, &_rtmp_packet->buffer);
|
||||
}
|
||||
|
||||
void H265RtmpEncoder::makeVideoConfigPkt() {
|
||||
#ifdef ENABLE_MP4
|
||||
int8_t flags = FLV_CODEC_H265;
|
||||
flags |= (FLV_KEY_FRAME << 4);
|
||||
bool is_config = true;
|
||||
auto rtmpPkt = RtmpPacket::create();
|
||||
//header
|
||||
rtmpPkt->buffer.push_back(flags);
|
||||
rtmpPkt->buffer.push_back(!is_config);
|
||||
//cts
|
||||
rtmpPkt->buffer.append("\x0\x0\x0", 3);
|
||||
auto flags = (uint8_t)RtmpVideoCodec::h265;
|
||||
flags |= ((uint8_t)RtmpFrameType::key_frame << 4);
|
||||
auto pkt = RtmpPacket::create();
|
||||
// header
|
||||
pkt->buffer.push_back(flags);
|
||||
pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header);
|
||||
// cts
|
||||
pkt->buffer.append("\x0\x0\x0", 3);
|
||||
|
||||
struct mpeg4_hevc_t hevc;
|
||||
memset(&hevc, 0, sizeof(hevc));
|
||||
string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps +
|
||||
string("\x00\x00\x00\x01", 4) + _sps +
|
||||
string("\x00\x00\x00\x01", 4) + _pps;
|
||||
string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps + string("\x00\x00\x00\x01", 4) + _sps + string("\x00\x00\x00\x01", 4) + _pps;
|
||||
h265_annexbtomp4(&hevc, vps_sps_pps.data(), (int)vps_sps_pps.size(), NULL, 0, NULL, NULL);
|
||||
uint8_t extra_data[1024];
|
||||
int extra_data_size = mpeg4_hevc_decoder_configuration_record_save(&hevc, extra_data, sizeof(extra_data));
|
||||
@ -226,17 +278,17 @@ void H265RtmpEncoder::makeVideoConfigPkt() {
|
||||
WarnL << "生成H265 extra_data 失败";
|
||||
return;
|
||||
}
|
||||
//HEVCDecoderConfigurationRecord
|
||||
rtmpPkt->buffer.append((char *)extra_data, extra_data_size);
|
||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
||||
rtmpPkt->chunk_id = CHUNK_VIDEO;
|
||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
||||
rtmpPkt->time_stamp = 0;
|
||||
rtmpPkt->type_id = MSG_VIDEO;
|
||||
RtmpCodec::inputRtmp(rtmpPkt);
|
||||
// HEVCDecoderConfigurationRecord
|
||||
pkt->buffer.append((char *)extra_data, extra_data_size);
|
||||
pkt->body_size = pkt->buffer.size();
|
||||
pkt->chunk_id = CHUNK_VIDEO;
|
||||
pkt->stream_index = STREAM_MEDIA;
|
||||
pkt->time_stamp = 0;
|
||||
pkt->type_id = MSG_VIDEO;
|
||||
RtmpCodec::inputRtmp(pkt);
|
||||
#else
|
||||
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
||||
#endif
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
} // namespace mediakit
|
||||
|
@ -15,7 +15,7 @@
|
||||
#include "Extension/Track.h"
|
||||
#include "Extension/H265.h"
|
||||
|
||||
namespace mediakit{
|
||||
namespace mediakit {
|
||||
/**
|
||||
* h265 Rtmp解码类
|
||||
* 将 h265 over rtmp 解复用出 h265-Frame
|
||||
@ -25,7 +25,7 @@ public:
|
||||
using Ptr = std::shared_ptr<H265RtmpDecoder>;
|
||||
|
||||
H265RtmpDecoder();
|
||||
~H265RtmpDecoder() {}
|
||||
~H265RtmpDecoder() = default;
|
||||
|
||||
/**
|
||||
* 输入265 Rtmp包
|
||||
@ -33,22 +33,23 @@ public:
|
||||
*/
|
||||
void inputRtmp(const RtmpPacket::Ptr &rtmp) override;
|
||||
|
||||
CodecId getCodecId() const override{
|
||||
return CodecH265;
|
||||
}
|
||||
CodecId getCodecId() const override { return CodecH265; }
|
||||
|
||||
protected:
|
||||
void onGetH265(const char *pcData, size_t iLen, uint32_t dts,uint32_t pts);
|
||||
H265Frame::Ptr obtainFrame();
|
||||
|
||||
void onGetH265(const char *data, size_t size, uint32_t dts, uint32_t pts);
|
||||
void splitFrame(const uint8_t *data, size_t size, uint32_t dts, uint32_t pts);
|
||||
|
||||
protected:
|
||||
RtmpPacketInfo _info;
|
||||
H265Frame::Ptr _h265frame;
|
||||
};
|
||||
|
||||
/**
|
||||
* 265 Rtmp打包类
|
||||
*/
|
||||
class H265RtmpEncoder : public H265RtmpDecoder{
|
||||
class H265RtmpEncoder : public H265RtmpDecoder {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<H265RtmpEncoder>;
|
||||
|
||||
@ -87,9 +88,9 @@ private:
|
||||
std::string _pps;
|
||||
H265Track::Ptr _track;
|
||||
RtmpPacket::Ptr _rtmp_packet;
|
||||
FrameMerger _merger{FrameMerger::mp4_nal_size};
|
||||
FrameMerger _merger { FrameMerger::mp4_nal_size };
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
} // namespace mediakit
|
||||
|
||||
#endif //ZLMEDIAKIT_H265RTMPCODEC_H
|
||||
#endif // ZLMEDIAKIT_H265RTMPCODEC_H
|
||||
|
@ -51,8 +51,8 @@ public:
|
||||
return _ring;
|
||||
}
|
||||
|
||||
void getPlayerList(const std::function<void(const std::list<std::shared_ptr<void>> &info_list)> &cb,
|
||||
const std::function<std::shared_ptr<void>(std::shared_ptr<void> &&info)> &on_change) override {
|
||||
void getPlayerList(const std::function<void(const std::list<toolkit::Any> &info_list)> &cb,
|
||||
const std::function<toolkit::Any(toolkit::Any &&info)> &on_change) override {
|
||||
_ring->getInfoList(cb, on_change);
|
||||
}
|
||||
|
||||
|
@ -11,8 +11,6 @@
|
||||
#ifndef ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
|
||||
#define ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
|
||||
|
||||
#if defined(ENABLE_MP4)
|
||||
|
||||
#include "FMP4MediaSource.h"
|
||||
#include "Record/MP4Muxer.h"
|
||||
|
||||
@ -63,7 +61,8 @@ public:
|
||||
return _option.fmp4_demand ? (_clear_cache ? true : _enabled) : true;
|
||||
}
|
||||
|
||||
void onAllTrackReady() {
|
||||
void addTrackCompleted() override {
|
||||
MP4MuxerMemory::addTrackCompleted();
|
||||
_media_src->setInitSegment(getInitSegment());
|
||||
}
|
||||
|
||||
@ -86,5 +85,4 @@ private:
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#endif// defined(ENABLE_MP4)
|
||||
#endif //ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
|
||||
|
@ -71,6 +71,11 @@ void HlsPlayer::teardown() {
|
||||
|
||||
void HlsPlayer::fetchSegment() {
|
||||
if (_ts_list.empty()) {
|
||||
// 如果是点播文件,播放列表为空代表文件播放结束,关闭播放器: #2628
|
||||
if(!HlsParser::isLive()){
|
||||
teardown();
|
||||
return;
|
||||
}
|
||||
//播放列表为空,那么立即重新下载m3u8文件
|
||||
_timer.reset();
|
||||
fetchIndexFile();
|
||||
@ -121,18 +126,21 @@ void HlsPlayer::fetchSegment() {
|
||||
WarnL << "Download ts segment " << url << " failed:" << err;
|
||||
if (err.getErrCode() == Err_timeout) {
|
||||
strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple + 1, MAX_TIMEOUT_MULTIPLE);
|
||||
}else{
|
||||
strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple -1 , MIN_TIMEOUT_MULTIPLE);
|
||||
} else {
|
||||
strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple - 1, MIN_TIMEOUT_MULTIPLE);
|
||||
}
|
||||
}
|
||||
//提前半秒下载好
|
||||
auto delay = duration - ticker.elapsedTime() / 1000.0f - 0.5;
|
||||
if (delay <= 0) {
|
||||
//延时最小10ms
|
||||
delay = 10;
|
||||
// 提前0.5秒下载好,支持点播文件控制下载速度: #2628
|
||||
auto delay = duration - 0.5 - ticker.elapsedTime() / 1000.0f;
|
||||
if (delay > 2.0) {
|
||||
// 提前1秒下载
|
||||
delay -= 1.0;
|
||||
} else if (delay <= 0) {
|
||||
// 延时最小10ms
|
||||
delay = 0.01;
|
||||
}
|
||||
//延时下载下一个切片
|
||||
strong_self->_timer_ts.reset(new Timer(delay / 1000.0f, [weak_self]() {
|
||||
// 延时下载下一个切片
|
||||
strong_self->_timer_ts.reset(new Timer(delay, [weak_self]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (strong_self) {
|
||||
strong_self->fetchSegment();
|
||||
|
@ -33,6 +33,7 @@ namespace mediakit {
|
||||
static int kHlsCookieSecond = 60;
|
||||
static const string kCookieName = "ZL_COOKIE";
|
||||
static const string kHlsSuffix = "/hls.m3u8";
|
||||
static const string kHlsFMP4Suffix = "/hls.fmp4.m3u8";
|
||||
|
||||
struct HttpCookieAttachment {
|
||||
//是否已经查找到过MediaSource
|
||||
@ -49,6 +50,67 @@ const string &HttpFileManager::getContentType(const char *name) {
|
||||
return HttpConst::getHttpContentType(name);
|
||||
}
|
||||
|
||||
#ifndef ntohll
|
||||
static uint64_t ntohll(uint64_t val) {
|
||||
return (((uint64_t)ntohl(val)) << 32) + ntohl(val >> 32);
|
||||
}
|
||||
#endif
|
||||
|
||||
static uint64_t get_ip_uint64(const std::string &ip) {
|
||||
try {
|
||||
auto storage = SockUtil::make_sockaddr(ip.data(), 0);
|
||||
if (storage.ss_family == AF_INET) {
|
||||
return ntohl(reinterpret_cast<uint32_t &>(reinterpret_cast<struct sockaddr_in &>(storage).sin_addr));
|
||||
}
|
||||
if (storage.ss_family == AF_INET6) {
|
||||
return ntohll(reinterpret_cast<uint64_t &>(reinterpret_cast<struct sockaddr_in6 &>(storage).sin6_addr));
|
||||
}
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool HttpFileManager::isIPAllowed(const std::string &ip) {
|
||||
using IPRangs = std::vector<std::pair<uint64_t /*min_ip*/, uint64_t /*max_ip*/>>;
|
||||
GET_CONFIG_FUNC(IPRangs, allow_ip_range, Http::kAllowIPRange, [](const string &str) -> IPRangs {
|
||||
IPRangs ret;
|
||||
auto vec = split(str, ",");
|
||||
for (auto &item : vec) {
|
||||
if (trim(item).empty()) {
|
||||
continue;
|
||||
}
|
||||
auto range = split(item, "-");
|
||||
if (range.size() == 2) {
|
||||
auto ip_min = get_ip_uint64(trim(range[0]));
|
||||
auto ip_max = get_ip_uint64(trim(range[1]));
|
||||
if (ip_min && ip_max) {
|
||||
ret.emplace_back(ip_min, ip_max);
|
||||
}
|
||||
} else if (range.size() == 1) {
|
||||
auto ip = get_ip_uint64(trim(range[0]));
|
||||
if (ip) {
|
||||
ret.emplace_back(ip, ip);
|
||||
}
|
||||
} else {
|
||||
WarnL << "Invalid ip range: " << item;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
||||
if (allow_ip_range.empty()) {
|
||||
return true;
|
||||
}
|
||||
auto ip_int = get_ip_uint64(ip);
|
||||
for (auto &range : allow_ip_range) {
|
||||
if (ip_int >= range.first && ip_int <= range.second) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static string searchIndexFile(const string &dir){
|
||||
DIR *pDir;
|
||||
dirent *pDirent;
|
||||
@ -57,7 +119,7 @@ static string searchIndexFile(const string &dir){
|
||||
}
|
||||
set<string> setFile;
|
||||
while ((pDirent = readdir(pDir)) != NULL) {
|
||||
static set<const char *, StrCaseCompare> indexSet = {"index.html", "index.htm", "index"};
|
||||
static set<const char *, StrCaseCompare> indexSet = {"index.html", "index.htm"};
|
||||
if (indexSet.find(pDirent->d_name) != indexSet.end()) {
|
||||
string ret = pDirent->d_name;
|
||||
closedir(pDir);
|
||||
@ -188,7 +250,7 @@ static bool emitHlsPlayed(const Parser &parser, const MediaInfo &media_info, con
|
||||
//cookie有效期为kHlsCookieSecond
|
||||
invoker(err, "", kHlsCookieSecond);
|
||||
};
|
||||
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, static_cast<SockInfo &>(sender));
|
||||
bool flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, sender);
|
||||
if (!flag) {
|
||||
//未开启鉴权,那么允许播放
|
||||
auth_invoker("");
|
||||
@ -278,7 +340,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
|
||||
HttpCookieManager::Instance().delCookie(cookie);
|
||||
}
|
||||
|
||||
bool is_hls = media_info.schema == HLS_SCHEMA;
|
||||
bool is_hls = media_info.schema == HLS_SCHEMA || media_info.schema == HLS_FMP4_SCHEMA;
|
||||
|
||||
SockInfoImp::Ptr info = std::make_shared<SockInfoImp>();
|
||||
info->_identifier = sender.getIdentifier();
|
||||
@ -320,10 +382,10 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
|
||||
return;
|
||||
}
|
||||
|
||||
//事件未被拦截,则认为是http下载请求
|
||||
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, static_cast<SockInfo &>(sender));
|
||||
// 事件未被拦截,则认为是http下载请求
|
||||
bool flag = NOTICE_EMIT(BroadcastHttpAccessArgs, Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, sender);
|
||||
if (!flag) {
|
||||
//此事件无人监听,我们默认都有权限访问
|
||||
// 此事件无人监听,我们默认都有权限访问
|
||||
callback("", nullptr);
|
||||
}
|
||||
}
|
||||
@ -355,7 +417,7 @@ static string pathCat(const string &a, const string &b){
|
||||
* @param cb 回调对象
|
||||
*/
|
||||
static void accessFile(Session &sender, const Parser &parser, const MediaInfo &media_info, const string &file_path, const HttpFileManager::invoker &cb) {
|
||||
bool is_hls = end_with(file_path, kHlsSuffix);
|
||||
bool is_hls = end_with(file_path, kHlsSuffix) || end_with(file_path, kHlsFMP4Suffix);
|
||||
if (!is_hls && !File::fileExist(file_path.data())) {
|
||||
//文件不存在且不是hls,那么直接返回404
|
||||
sendNotFound(cb);
|
||||
@ -363,8 +425,13 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
|
||||
}
|
||||
if (is_hls) {
|
||||
// hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
|
||||
const_cast<string &>(media_info.schema) = HLS_SCHEMA;
|
||||
replace(const_cast<string &>(media_info.stream), kHlsSuffix, "");
|
||||
if (end_with(file_path, kHlsSuffix)) {
|
||||
const_cast<string &>(media_info.schema) = HLS_SCHEMA;
|
||||
replace(const_cast<string &>(media_info.stream), kHlsSuffix, "");
|
||||
} else {
|
||||
const_cast<string &>(media_info.schema) = HLS_FMP4_SCHEMA;
|
||||
replace(const_cast<string &>(media_info.stream), kHlsFMP4Suffix, "");
|
||||
}
|
||||
}
|
||||
|
||||
weak_ptr<Session> weakSession = static_pointer_cast<Session>(sender.shared_from_this());
|
||||
@ -457,7 +524,7 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
|
||||
});
|
||||
}
|
||||
|
||||
static string getFilePath(const Parser &parser,const MediaInfo &media_info, Session &sender){
|
||||
static string getFilePath(const Parser &parser,const MediaInfo &media_info, Session &sender) {
|
||||
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
|
||||
GET_CONFIG(string, rootPath, Http::kRootPath);
|
||||
GET_CONFIG_FUNC(StrCaseMap, virtualPathMap, Http::kVirtualPath, [](const string &str) {
|
||||
@ -482,7 +549,14 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess
|
||||
}
|
||||
}
|
||||
auto ret = File::absolutePath(enableVhost ? media_info.vhost + url : url, path);
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, static_cast<SockInfo &>(sender));
|
||||
auto http_root = File::absolutePath(enableVhost ? media_info.vhost + "/" : "/", path);
|
||||
if (!start_with(ret, http_root)) {
|
||||
// 访问的http文件不得在http根目录之外
|
||||
throw std::runtime_error("Attempting to access files outside of the http root directory");
|
||||
}
|
||||
// 替换url,防止返回的目录索引网页被注入非法内容
|
||||
const_cast<Parser&>(parser).setUrl("/" + ret.substr(http_root.size()));
|
||||
NOTICE_EMIT(BroadcastHttpBeforeAccessArgs, Broadcast::kBroadcastHttpBeforeAccess, parser, ret, sender);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -504,11 +578,14 @@ void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFi
|
||||
if (File::is_dir(file_path.data())) {
|
||||
auto indexFile = searchIndexFile(file_path);
|
||||
if (!indexFile.empty()) {
|
||||
//发现该文件夹下有index文件
|
||||
// 发现该文件夹下有index文件
|
||||
file_path = pathCat(file_path, indexFile);
|
||||
parser.setUrl(pathCat(parser.url(), indexFile));
|
||||
accessFile(sender, parser, media_info, file_path, cb);
|
||||
return;
|
||||
if (!File::is_dir(file_path.data())) {
|
||||
// 不是文件夹
|
||||
parser.setUrl(pathCat(parser.url(), indexFile));
|
||||
accessFile(sender, parser, media_info, file_path, cb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
string strMenu;
|
||||
//生成文件夹菜单索引
|
||||
@ -621,4 +698,4 @@ HttpResponseInvokerImp::operator bool(){
|
||||
}
|
||||
|
||||
|
||||
}//namespace mediakit
|
||||
}//namespace mediakit
|
||||
|
@ -62,6 +62,13 @@ public:
|
||||
* @return mime值
|
||||
*/
|
||||
static const std::string &getContentType(const char *name);
|
||||
|
||||
/**
|
||||
* 该ip是否再白名单中
|
||||
* @param ip 支持ipv4和ipv6
|
||||
*/
|
||||
static bool isIPAllowed(const std::string &ip);
|
||||
|
||||
private:
|
||||
HttpFileManager() = delete;
|
||||
~HttpFileManager() = delete;
|
||||
|
@ -91,7 +91,7 @@ void HttpRequestSplitter::input(const char *data,size_t len) {
|
||||
_remain_data.assign(ptr, _remain_data_size);
|
||||
return;
|
||||
}
|
||||
//收到content数据,并且接受content完毕
|
||||
//收到content数据,并且接收content完毕
|
||||
onRecvContent(ptr,_content_len);
|
||||
|
||||
_remain_data_size -= _content_len;
|
||||
|
@ -271,7 +271,7 @@ static void sendReport() {
|
||||
}
|
||||
|
||||
static toolkit::onceToken s_token([]() {
|
||||
NoticeCenter::Instance().addListener(nullptr, "kBroadcastEventPollerPoolStarted", [](EventPollerPool &pool, size_t &size) {
|
||||
NoticeCenter::Instance().addListener(nullptr, "kBroadcastEventPollerPoolStarted", [](EventPollerPoolOnStartedArgs) {
|
||||
// 第一次汇报在程序启动后5分钟
|
||||
pool.getPoller()->doDelayTask(5 * 60 * 1000, []() {
|
||||
sendReport();
|
||||
|
@ -170,7 +170,7 @@ void HttpSession::onError(const SockException &err) {
|
||||
|
||||
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
|
||||
if (_total_bytes_usage >= iFlowThreshold * 1024) {
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration, true, static_cast<SockInfo &>(*this));
|
||||
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration, true, *this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -311,7 +311,7 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix
|
||||
}
|
||||
};
|
||||
|
||||
auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _mediaInfo, invoker, static_cast<SockInfo &>(*this));
|
||||
auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _mediaInfo, invoker, *this);
|
||||
if (!flag) {
|
||||
// 该事件无人监听,默认不鉴权
|
||||
onRes("");
|
||||
@ -338,7 +338,11 @@ bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb) {
|
||||
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
||||
fmp4_src->pause(false);
|
||||
_fmp4_reader = fmp4_src->getRing()->attach(getPoller());
|
||||
_fmp4_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); });
|
||||
_fmp4_reader->setGetInfoCB([weak_self]() {
|
||||
Any ret;
|
||||
ret.set(static_pointer_cast<SockInfo>(weak_self.lock()));
|
||||
return ret;
|
||||
});
|
||||
_fmp4_reader->setDetachCB([weak_self]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
@ -378,7 +382,11 @@ bool HttpSession::checkLiveStreamTS(const function<void()> &cb) {
|
||||
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
||||
ts_src->pause(false);
|
||||
_ts_reader = ts_src->getRing()->attach(getPoller());
|
||||
_ts_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); });
|
||||
_ts_reader->setGetInfoCB([weak_self]() {
|
||||
Any ret;
|
||||
ret.set(static_pointer_cast<SockInfo>(weak_self.lock()));
|
||||
return ret;
|
||||
});
|
||||
_ts_reader->setDetachCB([weak_self]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
@ -711,7 +719,7 @@ bool HttpSession::emitHttpEvent(bool doInvoke) {
|
||||
};
|
||||
///////////////////广播HTTP事件///////////////////////////
|
||||
bool consumed = false; // 该事件是否被消费
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, static_cast<SockInfo &>(*this));
|
||||
NOTICE_EMIT(BroadcastHttpRequestArgs, Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, *this);
|
||||
if (!consumed && doInvoke) {
|
||||
// 该事件无人消费,所以返回404
|
||||
invoker(404, KeyValue(), HttpBody::Ptr());
|
||||
|
@ -8,6 +8,7 @@
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include <iomanip>
|
||||
#include "HlsMaker.h"
|
||||
#include "Common/config.h"
|
||||
|
||||
@ -15,83 +16,76 @@ using namespace std;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number, bool seg_keep) {
|
||||
HlsMaker::HlsMaker(bool is_fmp4, float seg_duration, uint32_t seg_number, bool seg_keep) {
|
||||
_is_fmp4 = is_fmp4;
|
||||
//最小允许设置为0,0个切片代表点播
|
||||
_seg_number = seg_number;
|
||||
_seg_duration = seg_duration;
|
||||
_seg_keep = seg_keep;
|
||||
}
|
||||
|
||||
HlsMaker::~HlsMaker() = default;
|
||||
|
||||
void HlsMaker::makeIndexFile(bool eof) {
|
||||
char file_content[1024];
|
||||
int maxSegmentDuration = 0;
|
||||
|
||||
for (auto &tp : _seg_dur_list) {
|
||||
int dur = std::get<0>(tp);
|
||||
if (dur > maxSegmentDuration) {
|
||||
maxSegmentDuration = dur;
|
||||
}
|
||||
}
|
||||
auto index_seq = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL;
|
||||
|
||||
auto sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL;
|
||||
|
||||
string m3u8;
|
||||
if (_seg_number == 0) {
|
||||
// 录像点播支持时移
|
||||
snprintf(file_content, sizeof(file_content),
|
||||
"#EXTM3U\n"
|
||||
"#EXT-X-PLAYLIST-TYPE:EVENT\n"
|
||||
"#EXT-X-VERSION:4\n"
|
||||
"#EXT-X-TARGETDURATION:%u\n"
|
||||
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
|
||||
(maxSegmentDuration + 999) / 1000,
|
||||
sequence);
|
||||
string index_str;
|
||||
index_str.reserve(2048);
|
||||
index_str += "#EXTM3U\n";
|
||||
index_str += (_is_fmp4 ? "#EXT-X-VERSION:7\n" : "#EXT-X-VERSION:4\n");
|
||||
if (_seg_number == 0) {
|
||||
index_str += "#EXT-X-PLAYLIST-TYPE:EVENT\n";
|
||||
} else {
|
||||
snprintf(file_content, sizeof(file_content),
|
||||
"#EXTM3U\n"
|
||||
"#EXT-X-VERSION:3\n"
|
||||
"#EXT-X-ALLOW-CACHE:NO\n"
|
||||
"#EXT-X-TARGETDURATION:%u\n"
|
||||
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
|
||||
(maxSegmentDuration + 999) / 1000,
|
||||
sequence);
|
||||
index_str += "#EXT-X-ALLOW-CACHE:NO\n";
|
||||
}
|
||||
index_str += "#EXT-X-TARGETDURATION:" + std::to_string((maxSegmentDuration + 999) / 1000) + "\n";
|
||||
index_str += "#EXT-X-MEDIA-SEQUENCE:" + std::to_string(index_seq) + "\n";
|
||||
if (_is_fmp4) {
|
||||
index_str += "#EXT-X-MAP:URI=\"init.mp4\"\n";
|
||||
}
|
||||
|
||||
m3u8.assign(file_content);
|
||||
|
||||
stringstream ss;
|
||||
for (auto &tp : _seg_dur_list) {
|
||||
snprintf(file_content, sizeof(file_content), "#EXTINF:%.3f,\n%s\n", std::get<0>(tp) / 1000.0, std::get<1>(tp).data());
|
||||
m3u8.append(file_content);
|
||||
ss << "#EXTINF:" << std::setprecision(3) << std::get<0>(tp) / 1000.0 << ",\n" << std::get<1>(tp) << "\n";
|
||||
}
|
||||
index_str += ss.str();
|
||||
|
||||
if (eof) {
|
||||
snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n");
|
||||
m3u8.append(file_content);
|
||||
index_str += "#EXT-X-ENDLIST\n";
|
||||
}
|
||||
onWriteHls(m3u8);
|
||||
onWriteHls(index_str);
|
||||
}
|
||||
|
||||
void HlsMaker::inputInitSegment(const char *data, size_t len) {
|
||||
if (!_is_fmp4) {
|
||||
throw std::invalid_argument("Only fmp4-hls can input init segment");
|
||||
}
|
||||
onWriteInitSegment(data, len);
|
||||
}
|
||||
|
||||
void HlsMaker::inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) {
|
||||
void HlsMaker::inputData(const char *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) {
|
||||
if (data && len) {
|
||||
if (timestamp < _last_timestamp) {
|
||||
//时间戳回退了,切片时长重新计时
|
||||
WarnL << "stamp reduce: " << _last_timestamp << " -> " << timestamp;
|
||||
// 时间戳回退了,切片时长重新计时
|
||||
WarnL << "Timestamp reduce: " << _last_timestamp << " -> " << timestamp;
|
||||
_last_seg_timestamp = _last_timestamp = timestamp;
|
||||
}
|
||||
if (is_idr_fast_packet) {
|
||||
//尝试切片ts
|
||||
// 尝试切片ts
|
||||
addNewSegment(timestamp);
|
||||
}
|
||||
if (!_last_file_name.empty()) {
|
||||
//存在切片才写入ts数据
|
||||
onWriteSegment((char *) data, len);
|
||||
// 存在切片才写入ts数据
|
||||
onWriteSegment(data, len);
|
||||
_last_timestamp = timestamp;
|
||||
}
|
||||
} else {
|
||||
//resetTracks时触发此逻辑
|
||||
// resetTracks时触发此逻辑
|
||||
flushLastSegment(false);
|
||||
}
|
||||
}
|
||||
@ -148,14 +142,18 @@ void HlsMaker::flushLastSegment(bool eof){
|
||||
makeIndexFile(eof);
|
||||
}
|
||||
|
||||
bool HlsMaker::isLive() {
|
||||
bool HlsMaker::isLive() const {
|
||||
return _seg_number != 0;
|
||||
}
|
||||
|
||||
bool HlsMaker::isKeep() {
|
||||
bool HlsMaker::isKeep() const {
|
||||
return _seg_keep;
|
||||
}
|
||||
|
||||
bool HlsMaker::isFmp4() const {
|
||||
return _is_fmp4;
|
||||
}
|
||||
|
||||
void HlsMaker::clear() {
|
||||
_file_index = 0;
|
||||
_last_timestamp = 0;
|
||||
|
@ -21,12 +21,13 @@ namespace mediakit {
|
||||
class HlsMaker {
|
||||
public:
|
||||
/**
|
||||
* @param is_fmp4 使用fmp4还是mpegts
|
||||
* @param seg_duration 切片文件长度
|
||||
* @param seg_number 切片个数
|
||||
* @param seg_keep 是否保留切片文件
|
||||
*/
|
||||
HlsMaker(float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
|
||||
virtual ~HlsMaker();
|
||||
HlsMaker(bool is_fmp4 = false, float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
|
||||
virtual ~HlsMaker() = default;
|
||||
|
||||
/**
|
||||
* 写入ts数据
|
||||
@ -35,17 +36,29 @@ public:
|
||||
* @param timestamp 毫秒时间戳
|
||||
* @param is_idr_fast_packet 是否为关键帧第一个包
|
||||
*/
|
||||
void inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet);
|
||||
void inputData(const char *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet);
|
||||
|
||||
/**
|
||||
* 输入fmp4 init segment
|
||||
* @param data 数据
|
||||
* @param len 数据长度
|
||||
*/
|
||||
void inputInitSegment(const char *data, size_t len);
|
||||
|
||||
/**
|
||||
* 是否为直播
|
||||
*/
|
||||
bool isLive();
|
||||
bool isLive() const;
|
||||
|
||||
/**
|
||||
* 是否保留切片文件
|
||||
*/
|
||||
bool isKeep();
|
||||
bool isKeep() const;
|
||||
|
||||
/**
|
||||
* 是否采用fmp4切片还是mpegts
|
||||
*/
|
||||
bool isFmp4() const;
|
||||
|
||||
/**
|
||||
* 清空记录
|
||||
@ -66,6 +79,13 @@ protected:
|
||||
*/
|
||||
virtual void onDelSegment(uint64_t index) = 0;
|
||||
|
||||
/**
|
||||
* 写init.mp4切片文件回调
|
||||
* @param data
|
||||
* @param len
|
||||
*/
|
||||
virtual void onWriteInitSegment(const char *data, size_t len) = 0;
|
||||
|
||||
/**
|
||||
* 写ts切片文件回调
|
||||
* @param data
|
||||
@ -109,6 +129,7 @@ private:
|
||||
void addNewSegment(uint64_t timestamp);
|
||||
|
||||
private:
|
||||
bool _is_fmp4 = false;
|
||||
float _seg_duration = 0;
|
||||
uint32_t _seg_number = 0;
|
||||
bool _seg_keep = false;
|
||||
|
@ -21,21 +21,14 @@ using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
HlsMakerImp::HlsMakerImp(const string &m3u8_file,
|
||||
const string ¶ms,
|
||||
uint32_t bufSize,
|
||||
float seg_duration,
|
||||
uint32_t seg_number,
|
||||
bool seg_keep):HlsMaker(seg_duration, seg_number, seg_keep) {
|
||||
HlsMakerImp::HlsMakerImp(bool is_fmp4, const string &m3u8_file, const string ¶ms, uint32_t bufSize, float seg_duration,
|
||||
uint32_t seg_number, bool seg_keep) : HlsMaker(is_fmp4, seg_duration, seg_number, seg_keep) {
|
||||
_poller = EventPollerPool::Instance().getPoller();
|
||||
_path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/'));
|
||||
_path_hls = m3u8_file;
|
||||
_params = params;
|
||||
_buf_size = bufSize;
|
||||
_file_buf.reset(new char[bufSize], [](char *ptr) {
|
||||
delete[] ptr;
|
||||
});
|
||||
|
||||
_file_buf.reset(new char[bufSize], [](char *ptr) { delete[] ptr; });
|
||||
_info.folder = _path_prefix;
|
||||
}
|
||||
|
||||
@ -53,9 +46,9 @@ void HlsMakerImp::clearCache() {
|
||||
}
|
||||
|
||||
void HlsMakerImp::clearCache(bool immediately, bool eof) {
|
||||
//录制完了
|
||||
// 录制完了
|
||||
flushLastSegment(eof);
|
||||
if (!isLive()||isKeep()) {
|
||||
if (!isLive() || isKeep()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -63,7 +56,7 @@ void HlsMakerImp::clearCache(bool immediately, bool eof) {
|
||||
_file = nullptr;
|
||||
_segment_file_paths.clear();
|
||||
|
||||
//hls直播才删除文件
|
||||
// hls直播才删除文件
|
||||
GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec);
|
||||
if (!delay || immediately) {
|
||||
File::delete_file(_path_prefix.data());
|
||||
@ -82,7 +75,7 @@ string HlsMakerImp::onOpenSegment(uint64_t index) {
|
||||
auto strDate = getTimeStr("%Y-%m-%d");
|
||||
auto strHour = getTimeStr("%H");
|
||||
auto strTime = getTimeStr("%M-%S");
|
||||
segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << ".ts";
|
||||
segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << (isFmp4() ? ".mp4" : ".ts");
|
||||
segment_path = _path_prefix + "/" + segment_name;
|
||||
if (isLive()) {
|
||||
_segment_file_paths.emplace(index, segment_path);
|
||||
@ -90,14 +83,14 @@ string HlsMakerImp::onOpenSegment(uint64_t index) {
|
||||
}
|
||||
_file = makeFile(segment_path, true);
|
||||
|
||||
//保存本切片的元数据
|
||||
// 保存本切片的元数据
|
||||
_info.start_time = ::time(NULL);
|
||||
_info.file_name = segment_name;
|
||||
_info.file_path = segment_path;
|
||||
_info.url = _info.app + "/" + _info.stream + "/" + segment_name;
|
||||
|
||||
if (!_file) {
|
||||
WarnL << "create file failed," << segment_path << " " << get_uv_errmsg();
|
||||
WarnL << "Create file failed," << segment_path << " " << get_uv_errmsg();
|
||||
}
|
||||
if (_params.empty()) {
|
||||
return segment_name;
|
||||
@ -114,6 +107,18 @@ void HlsMakerImp::onDelSegment(uint64_t index) {
|
||||
_segment_file_paths.erase(it);
|
||||
}
|
||||
|
||||
void HlsMakerImp::onWriteInitSegment(const char *data, size_t len) {
|
||||
string init_seg_path = _path_prefix + "/init.mp4";
|
||||
_file = makeFile(init_seg_path, true);
|
||||
|
||||
if (_file) {
|
||||
fwrite(data, len, 1, _file.get());
|
||||
_file = nullptr;
|
||||
} else {
|
||||
WarnL << "Create file failed," << init_seg_path << " " << get_uv_errmsg();
|
||||
}
|
||||
}
|
||||
|
||||
void HlsMakerImp::onWriteSegment(const char *data, size_t len) {
|
||||
if (_file) {
|
||||
fwrite(data, len, 1, _file.get());
|
||||
@ -132,20 +137,19 @@ void HlsMakerImp::onWriteHls(const std::string &data) {
|
||||
_media_src->setIndexFile(data);
|
||||
}
|
||||
} else {
|
||||
WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg();
|
||||
WarnL << "Create hls file failed," << _path_hls << " " << get_uv_errmsg();
|
||||
}
|
||||
//DebugL << "\r\n" << string(data,len);
|
||||
}
|
||||
|
||||
void HlsMakerImp::onFlushLastSegment(uint64_t duration_ms) {
|
||||
//关闭并flush文件到磁盘
|
||||
// 关闭并flush文件到磁盘
|
||||
_file = nullptr;
|
||||
|
||||
GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs);
|
||||
if (broadcastRecordTs) {
|
||||
_info.time_len = duration_ms / 1000.0f;
|
||||
_info.file_size = File::fileSize(_info.file_path.data());
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordTs, _info);
|
||||
NOTICE_EMIT(BroadcastRecordTsArgs, Broadcast::kBroadcastRecordTs, _info);
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,11 +170,11 @@ void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const s
|
||||
_info.app = app;
|
||||
_info.stream = stream_id;
|
||||
_info.vhost = vhost;
|
||||
_media_src = std::make_shared<HlsMediaSource>(_info);
|
||||
_media_src = std::make_shared<HlsMediaSource>(isFmp4() ? HLS_FMP4_SCHEMA : HLS_SCHEMA, _info);
|
||||
}
|
||||
|
||||
HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {
|
||||
return _media_src;
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
} // namespace mediakit
|
@ -19,15 +19,10 @@
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class HlsMakerImp : public HlsMaker{
|
||||
class HlsMakerImp : public HlsMaker {
|
||||
public:
|
||||
HlsMakerImp(const std::string &m3u8_file,
|
||||
const std::string ¶ms,
|
||||
uint32_t bufSize = 64 * 1024,
|
||||
float seg_duration = 5,
|
||||
uint32_t seg_number = 3,
|
||||
bool seg_keep = false);
|
||||
|
||||
HlsMakerImp(bool is_fmp4, const std::string &m3u8_file, const std::string ¶ms, uint32_t bufSize = 64 * 1024,
|
||||
float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
|
||||
~HlsMakerImp() override;
|
||||
|
||||
/**
|
||||
@ -52,6 +47,7 @@ public:
|
||||
protected:
|
||||
std::string onOpenSegment(uint64_t index) override ;
|
||||
void onDelSegment(uint64_t index) override;
|
||||
void onWriteInitSegment(const char *data, size_t len) override;
|
||||
void onWriteSegment(const char *data, size_t len) override;
|
||||
void onWriteHls(const std::string &data) override;
|
||||
void onFlushLastSegment(uint64_t duration_ms) override;
|
||||
|
@ -33,6 +33,12 @@ void HlsCookieData::addReaderCount() {
|
||||
// HlsMediaSource已经销毁
|
||||
*added = false;
|
||||
});
|
||||
auto info = _sock_info;
|
||||
_ring_reader->setGetInfoCB([info]() {
|
||||
Any ret;
|
||||
ret.set(info);
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,7 +53,7 @@ HlsCookieData::~HlsCookieData() {
|
||||
uint64_t bytes = _bytes.load();
|
||||
if (bytes >= iFlowThreshold * 1024) {
|
||||
try {
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, static_cast<SockInfo &>(*_sock_info));
|
||||
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, *_sock_info);
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << "Exception occurred: " << ex.what();
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ public:
|
||||
using RingType = toolkit::RingBuffer<std::string>;
|
||||
using Ptr = std::shared_ptr<HlsMediaSource>;
|
||||
|
||||
HlsMediaSource(const MediaTuple& tuple): MediaSource(HLS_SCHEMA, tuple) {}
|
||||
HlsMediaSource(const std::string &schema, const MediaTuple &tuple) : MediaSource(schema, tuple) {}
|
||||
~HlsMediaSource() override = default;
|
||||
|
||||
/**
|
||||
@ -58,6 +58,11 @@ public:
|
||||
|
||||
void onSegmentSize(size_t bytes) { _speed[TrackVideo] += bytes; }
|
||||
|
||||
void getPlayerList(const std::function<void(const std::list<toolkit::Any> &info_list)> &cb,
|
||||
const std::function<toolkit::Any(toolkit::Any &&info)> &on_change) override {
|
||||
_ring->getInfoList(cb, on_change);
|
||||
}
|
||||
|
||||
private:
|
||||
RingType::Ptr _ring;
|
||||
std::string _index_file;
|
||||
|
@ -13,27 +13,27 @@
|
||||
|
||||
#include "HlsMakerImp.h"
|
||||
#include "MPEG.h"
|
||||
#include "MP4Muxer.h"
|
||||
#include "Common/config.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class HlsRecorder final : public MediaSourceEventInterceptor, public MpegMuxer, public std::enable_shared_from_this<HlsRecorder> {
|
||||
template <typename Muxer>
|
||||
class HlsRecorderBase : public MediaSourceEventInterceptor, public Muxer, public std::enable_shared_from_this<HlsRecorderBase<Muxer> > {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<HlsRecorder>;
|
||||
|
||||
HlsRecorder(const std::string &m3u8_file, const std::string ¶ms, const ProtocolOption &option) : MpegMuxer(false) {
|
||||
HlsRecorderBase(bool is_fmp4, const std::string &m3u8_file, const std::string ¶ms, const ProtocolOption &option) {
|
||||
GET_CONFIG(uint32_t, hlsNum, Hls::kSegmentNum);
|
||||
GET_CONFIG(bool, hlsKeep, Hls::kSegmentKeep);
|
||||
GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize);
|
||||
GET_CONFIG(float, hlsDuration, Hls::kSegmentDuration);
|
||||
|
||||
_option = option;
|
||||
_hls = std::make_shared<HlsMakerImp>(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep);
|
||||
//清空上次的残余文件
|
||||
_hls = std::make_shared<HlsMakerImp>(is_fmp4, m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep);
|
||||
// 清空上次的残余文件
|
||||
_hls->clearCache();
|
||||
}
|
||||
|
||||
~HlsRecorder() { MpegMuxer::flush(); };
|
||||
~HlsRecorderBase() override = default;
|
||||
|
||||
void setMediaSource(const MediaTuple& tuple) {
|
||||
_hls->setMediaSource(tuple.vhost, tuple.app, tuple.stream);
|
||||
@ -41,7 +41,7 @@ public:
|
||||
|
||||
void setListener(const std::weak_ptr<MediaSourceEvent> &listener) {
|
||||
setDelegate(listener);
|
||||
_hls->getMediaSource()->setListener(shared_from_this());
|
||||
_hls->getMediaSource()->setListener(this->shared_from_this());
|
||||
}
|
||||
|
||||
int readerCount() { return _hls->getMediaSource()->readerCount(); }
|
||||
@ -64,7 +64,7 @@ public:
|
||||
_hls->getMediaSource()->setIndexFile("");
|
||||
}
|
||||
if (_enabled || !_option.hls_demand) {
|
||||
return MpegMuxer::inputFrame(frame);
|
||||
return Muxer::inputFrame(frame);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -74,20 +74,54 @@ public:
|
||||
return _option.hls_demand ? (_clear_cache ? true : _enabled) : true;
|
||||
}
|
||||
|
||||
private:
|
||||
void onWrite(std::shared_ptr<toolkit::Buffer> buffer, uint64_t timestamp, bool key_pos) override {
|
||||
if (!buffer) {
|
||||
_hls->inputData(nullptr, 0, timestamp, key_pos);
|
||||
} else {
|
||||
_hls->inputData(buffer->data(), buffer->size(), timestamp, key_pos);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
protected:
|
||||
bool _enabled = true;
|
||||
bool _clear_cache = false;
|
||||
ProtocolOption _option;
|
||||
std::shared_ptr<HlsMakerImp> _hls;
|
||||
};
|
||||
|
||||
class HlsRecorder final : public HlsRecorderBase<MpegMuxer> {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<HlsRecorder>;
|
||||
template <typename ...ARGS>
|
||||
HlsRecorder(ARGS && ...args) : HlsRecorderBase<MpegMuxer>(false, std::forward<ARGS>(args)...) {}
|
||||
~HlsRecorder() override { this->flush(); }
|
||||
|
||||
private:
|
||||
void onWrite(std::shared_ptr<toolkit::Buffer> buffer, uint64_t timestamp, bool key_pos) override {
|
||||
if (!buffer) {
|
||||
// reset tracks
|
||||
_hls->inputData(nullptr, 0, timestamp, key_pos);
|
||||
} else {
|
||||
_hls->inputData(buffer->data(), buffer->size(), timestamp, key_pos);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class HlsFMP4Recorder final : public HlsRecorderBase<MP4MuxerMemory> {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<HlsFMP4Recorder>;
|
||||
template <typename ...ARGS>
|
||||
HlsFMP4Recorder(ARGS && ...args) : HlsRecorderBase<MP4MuxerMemory>(true, std::forward<ARGS>(args)...) {}
|
||||
~HlsFMP4Recorder() override { this->flush(); }
|
||||
|
||||
void addTrackCompleted() override {
|
||||
HlsRecorderBase<MP4MuxerMemory>::addTrackCompleted();
|
||||
auto data = getInitSegment();
|
||||
_hls->inputInitSegment(data.data(), data.size());
|
||||
}
|
||||
|
||||
private:
|
||||
void onSegmentData(std::string buffer, uint64_t timestamp, bool key_pos) override {
|
||||
if (buffer.empty()) {
|
||||
// reset tracks
|
||||
_hls->inputData(nullptr, 0, timestamp, key_pos);
|
||||
} else {
|
||||
_hls->inputData((char *)buffer.data(), buffer.size(), timestamp, key_pos);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //HLSRECORDER_H
|
||||
|
@ -8,7 +8,8 @@
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_MP4
|
||||
#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
|
||||
|
||||
#include "MP4.h"
|
||||
#include "Util/File.h"
|
||||
#include "Util/logger.h"
|
||||
@ -176,4 +177,4 @@ int MP4FileMemory::onWrite(const void *data, size_t bytes){
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //NABLE_MP4RECORD
|
||||
#endif // defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
|
||||
|
@ -11,7 +11,7 @@
|
||||
#ifndef ZLMEDIAKIT_MP4_H
|
||||
#define ZLMEDIAKIT_MP4_H
|
||||
|
||||
#ifdef ENABLE_MP4
|
||||
#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@ -136,5 +136,5 @@ private:
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //NABLE_MP4RECORD
|
||||
#endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
|
||||
#endif //ZLMEDIAKIT_MP4_H
|
||||
|
@ -102,7 +102,7 @@ void MP4Demuxer::onVideoTrack(uint32_t track, uint8_t object, int width, int hei
|
||||
uint8_t config[1024 * 10] = {0};
|
||||
int size = mpeg4_avc_to_nalu(&avc, config, sizeof(config));
|
||||
if (size > 0) {
|
||||
video->inputFrame(std::make_shared<H264FrameNoCacheAble>((char *)config, size, 0, 4));
|
||||
video->inputFrame(std::make_shared<H264FrameNoCacheAble>((char *)config, size, 0, 0,4));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -117,7 +117,7 @@ void MP4Demuxer::onVideoTrack(uint32_t track, uint8_t object, int width, int hei
|
||||
uint8_t config[1024 * 10] = {0};
|
||||
int size = mpeg4_hevc_to_nalu(&hevc, config, sizeof(config));
|
||||
if (size > 0) {
|
||||
video->inputFrame(std::make_shared<H265FrameNoCacheAble>((char *) config, size, 0, 4));
|
||||
video->inputFrame(std::make_shared<H265FrameNoCacheAble>((char *) config, size, 0, 0,4));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -247,7 +247,7 @@ Frame::Ptr MP4Demuxer::makeFrame(uint32_t track_id, const Buffer::Ptr &buf, int6
|
||||
AACTrack::Ptr track = dynamic_pointer_cast<AACTrack>(it->second);
|
||||
assert(track);
|
||||
//加上adts头
|
||||
dumpAacConfig(track->getAacCfg(), buf->size() - DATA_OFFSET, (uint8_t *) buf->data() + (DATA_OFFSET - ADTS_HEADER_LEN), ADTS_HEADER_LEN);
|
||||
dumpAacConfig(track->getConfig(), buf->size() - DATA_OFFSET, (uint8_t *) buf->data() + (DATA_OFFSET - ADTS_HEADER_LEN), ADTS_HEADER_LEN);
|
||||
ret = std::make_shared<FrameWrapper<FrameFromPtr> >(buf, (uint64_t)dts, (uint64_t)pts, ADTS_HEADER_LEN, DATA_OFFSET - ADTS_HEADER_LEN, codec);
|
||||
break;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_MP4
|
||||
#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
|
||||
|
||||
#include "MP4Muxer.h"
|
||||
#include "Extension/AAC.h"
|
||||
@ -234,8 +234,8 @@ bool MP4MuxerInterface::addTrack(const Track::Ptr &track) {
|
||||
audio_track->getAudioChannel(),
|
||||
audio_track->getAudioSampleBit() * audio_track->getAudioChannel(),
|
||||
audio_track->getAudioSampleRate(),
|
||||
audio_track->getAacCfg().data(),
|
||||
audio_track->getAacCfg().size());
|
||||
audio_track->getConfig().data(),
|
||||
audio_track->getConfig().size());
|
||||
if (track_id < 0) {
|
||||
WarnL << "添加AAC Track失败:" << track_id;
|
||||
return false;
|
||||
@ -377,24 +377,24 @@ bool MP4MuxerMemory::inputFrame(const Frame::Ptr &frame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool key_frame = frame->keyFrame();
|
||||
|
||||
//flush切片
|
||||
// flush切片
|
||||
saveSegment();
|
||||
|
||||
auto data = _memory_file->getAndClearMemory();
|
||||
if (!data.empty()) {
|
||||
//输出切片数据
|
||||
onSegmentData(std::move(data), frame->dts(), _key_frame);
|
||||
// 输出切片数据
|
||||
onSegmentData(std::move(data), _last_dst, _key_frame);
|
||||
_key_frame = false;
|
||||
}
|
||||
|
||||
if (key_frame) {
|
||||
if (frame->keyFrame()) {
|
||||
_key_frame = true;
|
||||
}
|
||||
|
||||
if (frame->getTrackType() == TrackVideo || !haveVideo()) {
|
||||
_last_dst = frame->dts();
|
||||
}
|
||||
return MP4MuxerInterface::inputFrame(frame);
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
#endif//#ifdef ENABLE_MP4
|
||||
#endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
|
||||
|
@ -11,13 +11,13 @@
|
||||
#ifndef ZLMEDIAKIT_MP4MUXER_H
|
||||
#define ZLMEDIAKIT_MP4MUXER_H
|
||||
|
||||
#ifdef ENABLE_MP4
|
||||
#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
|
||||
|
||||
#include "Common/MediaSink.h"
|
||||
#include "Common/Stamp.h"
|
||||
#include "MP4.h"
|
||||
|
||||
namespace mediakit{
|
||||
namespace mediakit {
|
||||
|
||||
class MP4MuxerInterface : public MediaSinkInterface {
|
||||
public:
|
||||
@ -147,11 +147,39 @@ protected:
|
||||
|
||||
private:
|
||||
bool _key_frame = false;
|
||||
uint64_t _last_dst = 0;
|
||||
std::string _init_segment;
|
||||
MP4FileMemory::Ptr _memory_file;
|
||||
};
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
}//namespace mediakit
|
||||
#endif//#ifdef ENABLE_MP4
|
||||
#else
|
||||
|
||||
#include "Common/MediaSink.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class MP4MuxerMemory : public MediaSinkInterface {
|
||||
public:
|
||||
MP4MuxerMemory() = default;
|
||||
~MP4MuxerMemory() override = default;
|
||||
|
||||
bool addTrack(const Track::Ptr & track) override { return false; }
|
||||
bool inputFrame(const Frame::Ptr &frame) override { return false; }
|
||||
const std::string &getInitSegment() { static std::string kNull; return kNull; };
|
||||
|
||||
protected:
|
||||
/**
|
||||
* 输出fmp4切片回调函数
|
||||
* @param std::string 切片内容
|
||||
* @param stamp 切片末尾时间戳
|
||||
* @param key_frame 是否有关键帧
|
||||
*/
|
||||
virtual void onSegmentData(std::string string, uint64_t stamp, bool key_frame) = 0;
|
||||
};
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
#endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4)
|
||||
#endif //ZLMEDIAKIT_MP4MUXER_H
|
||||
|
@ -184,8 +184,10 @@ bool MP4Reader::speed(MediaSource &sender, float speed) {
|
||||
WarnL << "播放速度取值范围非法:" << speed;
|
||||
return false;
|
||||
}
|
||||
//设置播放速度后应该恢复播放
|
||||
pause(sender, false);
|
||||
//_seek_ticker重置,赋值_seek_to
|
||||
setCurrentStamp(getCurrentStamp());
|
||||
// 设置播放速度后应该恢复播放
|
||||
_paused = false;
|
||||
if (_speed == speed) {
|
||||
return true;
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ void MP4Recorder::createFile() {
|
||||
|
||||
try {
|
||||
_muxer = std::make_shared<MP4Muxer>();
|
||||
TraceL << "Open tmp mp4 file: " << full_path_tmp;
|
||||
_muxer->openMP4(full_path_tmp);
|
||||
for (auto &track :_tracks) {
|
||||
//添加track
|
||||
@ -71,10 +72,13 @@ void MP4Recorder::asyncClose() {
|
||||
auto full_path_tmp = _full_path_tmp;
|
||||
auto full_path = _full_path;
|
||||
auto info = _info;
|
||||
TraceL << "Start close tmp mp4 file: " << full_path_tmp;
|
||||
WorkThreadPool::Instance().getExecutor()->async([muxer, full_path_tmp, full_path, info]() mutable {
|
||||
info.time_len = muxer->getDuration() / 1000.0f;
|
||||
// 关闭mp4可能非常耗时,所以要放在后台线程执行
|
||||
TraceL << "Closing tmp mp4 file: " << full_path_tmp;
|
||||
muxer->closeMP4();
|
||||
TraceL << "Closed tmp mp4 file: " << full_path_tmp;
|
||||
if (!full_path_tmp.empty()) {
|
||||
// 获取文件大小
|
||||
info.file_size = File::fileSize(full_path_tmp.data());
|
||||
@ -86,8 +90,9 @@ void MP4Recorder::asyncClose() {
|
||||
// 临时文件名改成正式文件名,防止mp4未完成时被访问
|
||||
rename(full_path_tmp.data(), full_path.data());
|
||||
}
|
||||
TraceL << "Emit mp4 record event: " << full_path;
|
||||
//触发mp4录制切片生成事件
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordMP4, info);
|
||||
NOTICE_EMIT(BroadcastRecordMP4Args, Broadcast::kBroadcastRecordMP4, info);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ namespace mediakit {
|
||||
//该类用于产生MPEG-TS/MPEG-PS
|
||||
class MpegMuxer : public MediaSinkInterface {
|
||||
public:
|
||||
MpegMuxer(bool is_ps);
|
||||
MpegMuxer(bool is_ps = false);
|
||||
~MpegMuxer() override;
|
||||
|
||||
/**
|
||||
@ -86,7 +86,7 @@ namespace mediakit {
|
||||
|
||||
class MpegMuxer : public MediaSinkInterface {
|
||||
public:
|
||||
MpegMuxer(bool is_ps) {}
|
||||
MpegMuxer(bool is_ps = false) {}
|
||||
~MpegMuxer() override = default;
|
||||
bool addTrack(const Track::Ptr &track) override { return false; }
|
||||
void resetTracks() override {}
|
||||
|
@ -14,7 +14,8 @@
|
||||
#include "Common/MediaSource.h"
|
||||
#include "MP4Recorder.h"
|
||||
#include "HlsRecorder.h"
|
||||
#include "Util/File.h"
|
||||
#include "FMP4/FMP4MediaSourceMuxer.h"
|
||||
#include "TS/TSMediaSourceMuxer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
@ -53,6 +54,20 @@ string Recorder::getRecordPath(Recorder::type type, const MediaTuple& tuple, con
|
||||
}
|
||||
return File::absolutePath(mp4FilePath, recordPath);
|
||||
}
|
||||
case Recorder::type_hls_fmp4: {
|
||||
GET_CONFIG(string, hlsPath, Protocol::kHlsSavePath);
|
||||
string m3u8FilePath;
|
||||
if (enableVhost) {
|
||||
m3u8FilePath = tuple.shortUrl() + "/hls.fmp4.m3u8";
|
||||
} else {
|
||||
m3u8FilePath = tuple.app + "/" + tuple.stream + "/hls.fmp4.m3u8";
|
||||
}
|
||||
// Here we use the customized file path.
|
||||
if (!customized_path.empty()) {
|
||||
return File::absolutePath(m3u8FilePath, customized_path);
|
||||
}
|
||||
return File::absolutePath(m3u8FilePath, hlsPath);
|
||||
}
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@ -82,6 +97,34 @@ std::shared_ptr<MediaSinkInterface> Recorder::createRecorder(type type, const Me
|
||||
#endif
|
||||
}
|
||||
|
||||
case Recorder::type_hls_fmp4: {
|
||||
#if defined(ENABLE_HLS_FMP4)
|
||||
auto path = Recorder::getRecordPath(type, tuple, option.hls_save_path);
|
||||
GET_CONFIG(bool, enable_vhost, General::kEnableVhost);
|
||||
auto ret = std::make_shared<HlsFMP4Recorder>(path, enable_vhost ? string(VHOST_KEY) + "=" + tuple.vhost : "", option);
|
||||
ret->setMediaSource(tuple);
|
||||
return ret;
|
||||
#else
|
||||
throw std::invalid_argument("hls.fmp4相关功能未打开,请开启ENABLE_HLS_FMP4宏后编译再测试");
|
||||
#endif
|
||||
}
|
||||
|
||||
case Recorder::type_fmp4: {
|
||||
#if defined(ENABLE_HLS_FMP4) || defined(ENABLE_MP4)
|
||||
return std::make_shared<FMP4MediaSourceMuxer>(tuple, option);
|
||||
#else
|
||||
throw std::invalid_argument("fmp4相关功能未打开,请开启ENABLE_HLS_FMP4或ENABLE_MP4宏后编译再测试");
|
||||
#endif
|
||||
}
|
||||
|
||||
case Recorder::type_ts: {
|
||||
#if defined(ENABLE_HLS) || defined(ENABLE_RTPPROXY)
|
||||
return std::make_shared<TSMediaSourceMuxer>(tuple, option);
|
||||
#else
|
||||
throw std::invalid_argument("mpegts相关功能未打开,请开启ENABLE_HLS或ENABLE_RTPPROXY宏后编译再测试");
|
||||
#endif
|
||||
}
|
||||
|
||||
default: throw std::invalid_argument("未知的录制类型");
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,13 @@ public:
|
||||
// 录制hls
|
||||
type_hls = 0,
|
||||
// 录制MP4
|
||||
type_mp4 = 1
|
||||
type_mp4 = 1,
|
||||
// 录制hls.fmp4
|
||||
type_hls_fmp4 = 2,
|
||||
// fmp4直播
|
||||
type_fmp4 = 3,
|
||||
// ts直播
|
||||
type_ts = 4,
|
||||
} type;
|
||||
|
||||
/**
|
||||
|
@ -19,9 +19,7 @@
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
#if defined(_WIN32)
|
||||
#pragma pack(push, 1)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
// http://www.networksorcery.com/enp/protocol/rtcp.htm
|
||||
#define RTCP_PT_MAP(XX) \
|
||||
@ -235,7 +233,7 @@ private:
|
||||
*/
|
||||
void net2Host(size_t size);
|
||||
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -272,7 +270,7 @@ private:
|
||||
* 网络字节序转换为主机字节序
|
||||
*/
|
||||
void net2Host();
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
/*
|
||||
* 6.4.1 SR: Sender Report RTCP Packet
|
||||
@ -371,7 +369,7 @@ private:
|
||||
* @param size 字节长度,防止内存越界
|
||||
*/
|
||||
void net2Host(size_t size);
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -441,7 +439,7 @@ private:
|
||||
*/
|
||||
std::string dumpString() const;
|
||||
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -512,7 +510,7 @@ private:
|
||||
* 网络字节序转换为主机字节序
|
||||
*/
|
||||
void net2Host();
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
// Source description
|
||||
class RtcpSdes : public RtcpHeader {
|
||||
@ -548,7 +546,7 @@ private:
|
||||
* @param size 字节长度,防止内存越界
|
||||
*/
|
||||
void net2Host(size_t size);
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
// https://tools.ietf.org/html/rfc4585#section-6.1
|
||||
// 6.1. Common Packet Format for Feedback Messages
|
||||
@ -624,7 +622,7 @@ private:
|
||||
|
||||
private:
|
||||
static std::shared_ptr<RtcpFB> create_l(RtcpType type, int fmt, const void *fci, size_t fci_len);
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
// BYE
|
||||
/*
|
||||
@ -684,7 +682,7 @@ private:
|
||||
* @param size 字节长度,防止内存越界
|
||||
*/
|
||||
void net2Host(size_t size);
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
/*
|
||||
0 1 2 3
|
||||
@ -738,7 +736,7 @@ private:
|
||||
*/
|
||||
void net2Host(size_t size);
|
||||
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@ -777,7 +775,7 @@ private:
|
||||
* @param size 字节长度,防止内存越界
|
||||
*/
|
||||
void net2Host();
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
class RtcpXRDLRR : public RtcpHeader {
|
||||
public:
|
||||
@ -814,11 +812,9 @@ private:
|
||||
*/
|
||||
void net2Host(size_t size);
|
||||
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
#if defined(_WIN32)
|
||||
#pragma pack(pop)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
} // namespace mediakit
|
||||
#endif // ZLMEDIAKIT_RTCP_H
|
||||
|
@ -240,7 +240,7 @@ public:
|
||||
RunLengthChunk(SymbolStatus status, uint16_t run_length);
|
||||
// 打印本对象
|
||||
string dumpString() const;
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
RunLengthChunk::RunLengthChunk(SymbolStatus status, uint16_t run_length) {
|
||||
type = 0;
|
||||
@ -291,7 +291,7 @@ public:
|
||||
StatusVecChunk(bool symbol_bit, const vector<SymbolStatus> &status);
|
||||
// 打印本对象
|
||||
string dumpString() const;
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
StatusVecChunk::StatusVecChunk(bool symbol_bit, const vector<SymbolStatus> &status) {
|
||||
CHECK(status.size() << symbol_bit <= 14);
|
||||
|
@ -55,7 +55,7 @@ public:
|
||||
|
||||
private:
|
||||
uint32_t data;
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
#if 0
|
||||
//PSFB fmt = 3
|
||||
@ -97,7 +97,7 @@ public:
|
||||
uint8_t padding;
|
||||
|
||||
static size_t constexpr kSize = 8;
|
||||
} PACKED;
|
||||
};
|
||||
#endif
|
||||
|
||||
// PSFB fmt = 4
|
||||
@ -125,7 +125,7 @@ private:
|
||||
uint32_t ssrc;
|
||||
uint8_t seq_number;
|
||||
uint8_t reserved[3];
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
#if 0
|
||||
//PSFB fmt = 5
|
||||
@ -147,7 +147,7 @@ public:
|
||||
|
||||
private:
|
||||
uint8_t data[kSize];
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
//PSFB fmt = 6
|
||||
//https://tools.ietf.org/html/rfc5104#section-4.3.2.1
|
||||
@ -160,7 +160,7 @@ private:
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
class FCI_TSTN : public FCI_TSTR{
|
||||
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
//PSFB fmt = 7
|
||||
//https://tools.ietf.org/html/rfc5104#section-4.3.4.1
|
||||
@ -183,7 +183,7 @@ public:
|
||||
|
||||
private:
|
||||
uint8_t data[kSize];
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -233,7 +233,7 @@ private:
|
||||
// SSRC feedback (32 bits) Consists of one or more SSRC entries which
|
||||
// this feedback message applies to.
|
||||
uint32_t ssrc_feedback[1];
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////// RTPFB ////////////////////////////////////////////////////
|
||||
|
||||
@ -265,7 +265,7 @@ private:
|
||||
uint16_t pid;
|
||||
// bitmask of following lost packets (BLP): 16 bits
|
||||
uint16_t blp;
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
#if 0
|
||||
//RTPFB fmt = 3
|
||||
@ -300,7 +300,7 @@ private:
|
||||
// to the description in section 4.2.1.2. The value is an
|
||||
// unsigned integer [0..511].
|
||||
uint32_t max_tbr;
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
//RTPFB fmt = 4
|
||||
// https://tools.ietf.org/html/rfc5104#section-4.2.2.1
|
||||
@ -314,7 +314,7 @@ private:
|
||||
class FCI_TMMBN : public FCI_TMMBR{
|
||||
public:
|
||||
|
||||
} PACKED;
|
||||
};
|
||||
#endif
|
||||
|
||||
enum class SymbolStatus : uint8_t {
|
||||
@ -374,7 +374,7 @@ private:
|
||||
uint8_t ref_time[3];
|
||||
// feedback packet count,反馈包号,本包是第几个transport-cc包,每次加1 |
|
||||
uint8_t fb_pkt_count;
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
} // namespace mediakit
|
||||
#endif // ZLMEDIAKIT_RTCPFCI_H
|
||||
|
@ -46,7 +46,11 @@ void FlvMuxer::start(const EventPoller::Ptr &poller, const RtmpMediaSource::Ptr
|
||||
std::weak_ptr<FlvMuxer> weak_self = getSharedPtr();
|
||||
media->pause(false);
|
||||
_ring_reader = media->getRing()->attach(poller);
|
||||
_ring_reader->setGetInfoCB([weak_self]() { return dynamic_pointer_cast<HttpSession>(weak_self.lock()); });
|
||||
_ring_reader->setGetInfoCB([weak_self]() {
|
||||
Any ret;
|
||||
ret.set(dynamic_pointer_cast<SockInfo>(weak_self.lock()));
|
||||
return ret;
|
||||
});
|
||||
_ring_reader->setDetachCB([weak_self]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
@ -107,14 +111,12 @@ void FlvMuxer::onWriteFlvHeader(const RtmpMediaSource::Ptr &src) {
|
||||
//flv header
|
||||
onWrite(buffer, false);
|
||||
|
||||
auto &metadata = src->getMetaData();
|
||||
if (metadata) {
|
||||
//在有metadata的情况下才发送metadata
|
||||
//其实metadata没什么用,有些推流器不产生metadata
|
||||
// metadata
|
||||
src->getMetaData([&](const AMFValue &metadata) {
|
||||
AMFEncoder invoke;
|
||||
invoke << "onMetaData" << metadata;
|
||||
onWriteFlvTag(MSG_DATA, std::make_shared<BufferString>(invoke.data()), 0, false);
|
||||
}
|
||||
});
|
||||
|
||||
//config frame
|
||||
src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) {
|
||||
|
@ -62,7 +62,7 @@ bool FlvPlayer::onRecvMetadata(const AMFValue &metadata) {
|
||||
}
|
||||
|
||||
void FlvPlayer::onRecvRtmpPacket(RtmpPacket::Ptr packet) {
|
||||
if (!_play_result && !packet->isCfgFrame()) {
|
||||
if (!_play_result && !packet->isConfigFrame()) {
|
||||
_play_result = true;
|
||||
onPlayResult(SockException(Err_success, "play http-flv success"));
|
||||
}
|
||||
|
@ -91,21 +91,26 @@ void FlvSplitter::onRecvContent(const char *data, size_t len) {
|
||||
case MSG_DATA3: {
|
||||
BufferLikeString buffer(string(data, len));
|
||||
AMFDecoder dec(buffer, _type == MSG_DATA3 ? 3 : 0);
|
||||
std::string type = dec.load<std::string>();
|
||||
auto first = dec.load<AMFValue>();
|
||||
bool flag = true;
|
||||
if (type == "@setDataFrame") {
|
||||
std::string type = dec.load<std::string>();
|
||||
if (type == "onMetaData") {
|
||||
if (first.type() == AMFType::AMF_STRING) {
|
||||
auto type = first.as_string();
|
||||
if (type == "@setDataFrame") {
|
||||
type = dec.load<std::string>();
|
||||
if (type == "onMetaData") {
|
||||
flag = onRecvMetadata(dec.load<AMFValue>());
|
||||
} else {
|
||||
WarnL << "unknown type:" << type;
|
||||
}
|
||||
} else if (type == "onMetaData") {
|
||||
flag = onRecvMetadata(dec.load<AMFValue>());
|
||||
} else {
|
||||
WarnL << "unknown type:" << type;
|
||||
WarnL << "unknown notify:" << type;
|
||||
}
|
||||
} else if (type == "onMetaData") {
|
||||
flag = onRecvMetadata(dec.load<AMFValue>());
|
||||
} else {
|
||||
WarnL << "unknown notify:" << type;
|
||||
WarnL << "Parse flv script data failed, invalid amf value: " << first.to_string();
|
||||
}
|
||||
if(!flag){
|
||||
if (!flag) {
|
||||
throw std::invalid_argument("check rtmp metadata failed");
|
||||
}
|
||||
return;
|
||||
|
@ -10,10 +10,10 @@
|
||||
|
||||
#include "Rtmp.h"
|
||||
#include "Extension/Factory.h"
|
||||
namespace mediakit{
|
||||
|
||||
TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::map<std::string, std::string> &header)
|
||||
{
|
||||
namespace mediakit {
|
||||
|
||||
TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::map<std::string, std::string> &header) {
|
||||
_metadata.set("duration", dur_sec);
|
||||
_metadata.set("fileSize", (int)fileSize);
|
||||
_metadata.set("title", std::string("Streamed by ") + kServerName);
|
||||
@ -22,14 +22,14 @@ TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::map<std::string,
|
||||
}
|
||||
}
|
||||
|
||||
VideoMeta::VideoMeta(const VideoTrack::Ptr &video){
|
||||
if(video->getVideoWidth() > 0 ){
|
||||
VideoMeta::VideoMeta(const VideoTrack::Ptr &video) {
|
||||
if (video->getVideoWidth() > 0) {
|
||||
_metadata.set("width", video->getVideoWidth());
|
||||
}
|
||||
if(video->getVideoHeight() > 0 ){
|
||||
if (video->getVideoHeight() > 0) {
|
||||
_metadata.set("height", video->getVideoHeight());
|
||||
}
|
||||
if(video->getVideoFps() > 0 ){
|
||||
if (video->getVideoFps() > 0) {
|
||||
_metadata.set("framerate", video->getVideoFps());
|
||||
}
|
||||
if (video->getBitRate()) {
|
||||
@ -39,26 +39,26 @@ VideoMeta::VideoMeta(const VideoTrack::Ptr &video){
|
||||
_metadata.set("videocodecid", Factory::getAmfByCodecId(_codecId));
|
||||
}
|
||||
|
||||
AudioMeta::AudioMeta(const AudioTrack::Ptr &audio){
|
||||
AudioMeta::AudioMeta(const AudioTrack::Ptr &audio) {
|
||||
if (audio->getBitRate()) {
|
||||
_metadata.set("audiodatarate", audio->getBitRate() / 1024);
|
||||
}
|
||||
if(audio->getAudioSampleRate() > 0){
|
||||
if (audio->getAudioSampleRate() > 0) {
|
||||
_metadata.set("audiosamplerate", audio->getAudioSampleRate());
|
||||
}
|
||||
if(audio->getAudioSampleBit() > 0){
|
||||
if (audio->getAudioSampleBit() > 0) {
|
||||
_metadata.set("audiosamplesize", audio->getAudioSampleBit());
|
||||
}
|
||||
if(audio->getAudioChannel() > 0){
|
||||
if (audio->getAudioChannel() > 0) {
|
||||
_metadata.set("stereo", audio->getAudioChannel() > 1);
|
||||
}
|
||||
_codecId = audio->getCodecId();
|
||||
_metadata.set("audiocodecid", Factory::getAmfByCodecId(_codecId));
|
||||
}
|
||||
|
||||
uint8_t getAudioRtmpFlags(const Track::Ptr &track){
|
||||
switch (track->getTrackType()){
|
||||
case TrackAudio : {
|
||||
uint8_t getAudioRtmpFlags(const Track::Ptr &track) {
|
||||
switch (track->getTrackType()) {
|
||||
case TrackAudio: {
|
||||
auto audioTrack = std::dynamic_pointer_cast<AudioTrack>(track);
|
||||
if (!audioTrack) {
|
||||
WarnL << "获取AudioTrack失败";
|
||||
@ -68,21 +68,21 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){
|
||||
auto iChannel = audioTrack->getAudioChannel();
|
||||
auto iSampleBit = audioTrack->getAudioSampleBit();
|
||||
|
||||
uint8_t flvAudioType ;
|
||||
switch (track->getCodecId()){
|
||||
case CodecG711A : flvAudioType = FLV_CODEC_G711A; break;
|
||||
case CodecG711U : flvAudioType = FLV_CODEC_G711U; break;
|
||||
case CodecOpus : {
|
||||
flvAudioType = FLV_CODEC_OPUS;
|
||||
//opus不通过flags获取音频相关信息
|
||||
uint8_t flvAudioType;
|
||||
switch (track->getCodecId()) {
|
||||
case CodecG711A: flvAudioType = (uint8_t)RtmpAudioCodec::g711a; break;
|
||||
case CodecG711U: flvAudioType = (uint8_t)RtmpAudioCodec::g711u; break;
|
||||
case CodecOpus: {
|
||||
flvAudioType = (uint8_t)RtmpAudioCodec::opus;
|
||||
// opus不通过flags获取音频相关信息
|
||||
iSampleRate = 44100;
|
||||
iSampleBit = 16;
|
||||
iChannel = 2;
|
||||
break;
|
||||
}
|
||||
case CodecAAC : {
|
||||
flvAudioType = FLV_CODEC_AAC;
|
||||
//aac不通过flags获取音频相关信息
|
||||
case CodecAAC: {
|
||||
flvAudioType = (uint8_t)RtmpAudioCodec::aac;
|
||||
// aac不通过flags获取音频相关信息
|
||||
iSampleRate = 44100;
|
||||
iSampleBit = 16;
|
||||
iChannel = 2;
|
||||
@ -93,23 +93,15 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){
|
||||
|
||||
uint8_t flvSampleRate;
|
||||
switch (iSampleRate) {
|
||||
case 44100:
|
||||
flvSampleRate = 3;
|
||||
break;
|
||||
case 22050:
|
||||
flvSampleRate = 2;
|
||||
break;
|
||||
case 11025:
|
||||
flvSampleRate = 1;
|
||||
break;
|
||||
case 44100: flvSampleRate = 3; break;
|
||||
case 22050: flvSampleRate = 2; break;
|
||||
case 11025: flvSampleRate = 1; break;
|
||||
case 16000: // nellymoser only
|
||||
case 8000: // nellymoser only
|
||||
case 5512: // not MP3
|
||||
flvSampleRate = 0;
|
||||
break;
|
||||
default:
|
||||
WarnL << "FLV does not support sample rate " << iSampleRate << " ,choose from (44100, 22050, 11025)";
|
||||
return 0;
|
||||
default: WarnL << "FLV does not support sample rate " << iSampleRate << " ,choose from (44100, 22050, 11025)"; return 0;
|
||||
}
|
||||
|
||||
uint8_t flvStereoOrMono = (iChannel > 1);
|
||||
@ -117,32 +109,28 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){
|
||||
return (flvAudioType << 4) | (flvSampleRate << 2) | (flvSampleBit << 1) | flvStereoOrMono;
|
||||
}
|
||||
|
||||
default : return 0;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Metadata::addTrack(AMFValue &metadata, const Track::Ptr &track) {
|
||||
Metadata::Ptr new_metadata;
|
||||
switch (track->getTrackType()) {
|
||||
case TrackVideo: {
|
||||
new_metadata = std::make_shared<VideoMeta>(std::dynamic_pointer_cast<VideoTrack>(track));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TrackAudio: {
|
||||
new_metadata = std::make_shared<AudioMeta>(std::dynamic_pointer_cast<AudioTrack>(track));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
default: return;
|
||||
}
|
||||
|
||||
new_metadata->getMetadata().object_for_each([&](const std::string &key, const AMFValue &value) {
|
||||
metadata.set(key, value);
|
||||
});
|
||||
new_metadata->getMetadata().object_for_each([&](const std::string &key, const AMFValue &value) { metadata.set(key, value); });
|
||||
}
|
||||
|
||||
RtmpPacket::Ptr RtmpPacket::create(){
|
||||
RtmpPacket::Ptr RtmpPacket::create() {
|
||||
#if 0
|
||||
static ResourcePool<RtmpPacket> packet_pool;
|
||||
static onceToken token([]() {
|
||||
@ -156,8 +144,7 @@ RtmpPacket::Ptr RtmpPacket::create(){
|
||||
#endif
|
||||
}
|
||||
|
||||
void RtmpPacket::clear()
|
||||
{
|
||||
void RtmpPacket::clear() {
|
||||
is_abs_stamp = false;
|
||||
time_stamp = 0;
|
||||
ts_field = 0;
|
||||
@ -165,36 +152,56 @@ void RtmpPacket::clear()
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
bool RtmpPacket::isVideoKeyFrame() const
|
||||
{
|
||||
return type_id == MSG_VIDEO && (uint8_t)buffer[0] >> 4 == FLV_KEY_FRAME && (uint8_t)buffer[1] == 1;
|
||||
bool RtmpPacket::isVideoKeyFrame() const {
|
||||
if (type_id != MSG_VIDEO) {
|
||||
return false;
|
||||
}
|
||||
RtmpFrameType frame_type;
|
||||
if (buffer[0] >> 7) {
|
||||
// IsExHeader == 1
|
||||
frame_type = (RtmpFrameType)((buffer[0] >> 4) & 0x07);
|
||||
} else {
|
||||
// IsExHeader == 0
|
||||
frame_type = (RtmpFrameType)(buffer[0] >> 4);
|
||||
}
|
||||
return frame_type == RtmpFrameType::key_frame;
|
||||
}
|
||||
|
||||
bool RtmpPacket::isCfgFrame() const
|
||||
{
|
||||
bool RtmpPacket::isConfigFrame() const {
|
||||
switch (type_id) {
|
||||
case MSG_VIDEO: return buffer[1] == 0;
|
||||
case MSG_AUDIO: {
|
||||
switch (getMediaType()) {
|
||||
case FLV_CODEC_AAC: return buffer[1] == 0;
|
||||
default: return false;
|
||||
case MSG_AUDIO: {
|
||||
return (RtmpAudioCodec)getRtmpCodecId() == RtmpAudioCodec::aac && (RtmpAACPacketType)buffer[1] == RtmpAACPacketType::aac_config_header;
|
||||
}
|
||||
}
|
||||
default: return false;
|
||||
case MSG_VIDEO: {
|
||||
if (!isVideoKeyFrame()) {
|
||||
return false;
|
||||
}
|
||||
if (buffer[0] >> 7) {
|
||||
// IsExHeader == 1
|
||||
return (RtmpPacketType)(buffer[0] & 0x0f) == RtmpPacketType::PacketTypeSequenceStart;
|
||||
}
|
||||
// IsExHeader == 0
|
||||
switch ((RtmpVideoCodec)getRtmpCodecId()) {
|
||||
case RtmpVideoCodec::h265:
|
||||
case RtmpVideoCodec::h264: {
|
||||
return (RtmpH264PacketType)buffer[1] == RtmpH264PacketType::h264_config_header;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
int RtmpPacket::getMediaType() const
|
||||
{
|
||||
int RtmpPacket::getRtmpCodecId() const {
|
||||
switch (type_id) {
|
||||
case MSG_VIDEO: return (uint8_t)buffer[0] & 0x0F;
|
||||
case MSG_AUDIO: return (uint8_t)buffer[0] >> 4;
|
||||
default: return 0;
|
||||
case MSG_VIDEO: return (uint8_t)buffer[0] & 0x0F;
|
||||
case MSG_AUDIO: return (uint8_t)buffer[0] >> 4;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int RtmpPacket::getAudioSampleRate() const
|
||||
{
|
||||
int RtmpPacket::getAudioSampleRate() const {
|
||||
if (type_id != MSG_AUDIO) {
|
||||
return 0;
|
||||
}
|
||||
@ -203,8 +210,7 @@ int RtmpPacket::getAudioSampleRate() const
|
||||
return sampleRate[flvSampleRate];
|
||||
}
|
||||
|
||||
int RtmpPacket::getAudioSampleBit() const
|
||||
{
|
||||
int RtmpPacket::getAudioSampleBit() const {
|
||||
if (type_id != MSG_AUDIO) {
|
||||
return 0;
|
||||
}
|
||||
@ -213,8 +219,7 @@ int RtmpPacket::getAudioSampleBit() const
|
||||
return sampleBit[flvSampleBit];
|
||||
}
|
||||
|
||||
int RtmpPacket::getAudioChannel() const
|
||||
{
|
||||
int RtmpPacket::getAudioChannel() const {
|
||||
if (type_id != MSG_AUDIO) {
|
||||
return 0;
|
||||
}
|
||||
@ -223,8 +228,7 @@ int RtmpPacket::getAudioChannel() const
|
||||
return channel[flvStereoOrMono];
|
||||
}
|
||||
|
||||
RtmpPacket & RtmpPacket::operator=(const RtmpPacket &that)
|
||||
{
|
||||
RtmpPacket &RtmpPacket::operator=(const RtmpPacket &that) {
|
||||
is_abs_stamp = that.is_abs_stamp;
|
||||
stream_index = that.stream_index;
|
||||
body_size = that.body_size;
|
||||
@ -234,32 +238,101 @@ RtmpPacket & RtmpPacket::operator=(const RtmpPacket &that)
|
||||
return *this;
|
||||
}
|
||||
|
||||
RtmpHandshake::RtmpHandshake(uint32_t _time, uint8_t *_random /*= nullptr*/)
|
||||
{
|
||||
RtmpHandshake::RtmpHandshake(uint32_t _time, uint8_t *_random /*= nullptr*/) {
|
||||
_time = htonl(_time);
|
||||
memcpy(time_stamp, &_time, 4);
|
||||
if (!_random) {
|
||||
random_generate((char *)random, sizeof(random));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
memcpy(random, _random, sizeof(random));
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpHandshake::random_generate(char *bytes, int size)
|
||||
{
|
||||
static char cdata[] = { 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72,
|
||||
0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x2d, 0x77, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x2d, 0x77, 0x69,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x40, 0x31, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x6d };
|
||||
void RtmpHandshake::random_generate(char *bytes, int size) {
|
||||
static char cdata[] = { 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72, 0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x6e,
|
||||
0x6c, 0x69, 0x6e, 0x2d, 0x77, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x40, 0x31, 0x32, 0x36, 0x2e, 0x63,
|
||||
0x6f, 0x6d };
|
||||
for (int i = 0; i < size; i++) {
|
||||
bytes[i] = cdata[rand() % (sizeof(cdata) - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct RtmpVideoHeaderEnhanced {
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
uint8_t enhanced : 1;
|
||||
uint8_t frame_type : 3;
|
||||
uint8_t pkt_type : 4;
|
||||
uint32_t fourcc;
|
||||
#else
|
||||
uint8_t pkt_type : 4;
|
||||
uint8_t frame_type : 3;
|
||||
uint8_t enhanced : 1;
|
||||
uint32_t fourcc;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct RtmpVideoHeaderClassic {
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
uint8_t frame_type : 4;
|
||||
uint8_t codec_id : 4;
|
||||
uint8_t h264_pkt_type;
|
||||
#else
|
||||
uint8_t codec_id : 4;
|
||||
uint8_t frame_type : 4;
|
||||
uint8_t h264_pkt_type;
|
||||
#endif
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info) {
|
||||
RtmpPacketInfo save;
|
||||
info = info ? info : &save;
|
||||
info->codec = CodecInvalid;
|
||||
|
||||
CHECK(size > 0);
|
||||
RtmpVideoHeaderEnhanced *enhanced_header = (RtmpVideoHeaderEnhanced *)data;
|
||||
if (enhanced_header->enhanced) {
|
||||
// IsExHeader == 1
|
||||
CHECK(size >= 5, "Invalid rtmp buffer size: ", size);
|
||||
info->is_enhanced = true;
|
||||
info->video.frame_type = (RtmpFrameType)(enhanced_header->frame_type);
|
||||
info->video.pkt_type = (RtmpPacketType)(enhanced_header->pkt_type);
|
||||
|
||||
switch ((RtmpVideoCodec)ntohl(enhanced_header->fourcc)) {
|
||||
case RtmpVideoCodec::fourcc_av1: info->codec = CodecAV1; break;
|
||||
case RtmpVideoCodec::fourcc_vp9: info->codec = CodecVP9; break;
|
||||
case RtmpVideoCodec::fourcc_hevc: info->codec = CodecH265; break;
|
||||
default: WarnL << "Rtmp video codec not supported: " << std::string((char *)data + 1, 4);
|
||||
}
|
||||
} else {
|
||||
// IsExHeader == 0
|
||||
RtmpVideoHeaderClassic *classic_header = (RtmpVideoHeaderClassic *)data;
|
||||
info->is_enhanced = false;
|
||||
info->video.frame_type = (RtmpFrameType)(classic_header->frame_type);
|
||||
switch ((RtmpVideoCodec)(classic_header->codec_id)) {
|
||||
case RtmpVideoCodec::h264: {
|
||||
CHECK(size >= 1, "Invalid rtmp buffer size: ", size);
|
||||
info->codec = CodecH264;
|
||||
info->video.h264_pkt_type = (RtmpH264PacketType)classic_header->h264_pkt_type;
|
||||
break;
|
||||
}
|
||||
case RtmpVideoCodec::h265: {
|
||||
CHECK(size >= 1, "Invalid rtmp buffer size: ", size);
|
||||
info->codec = CodecH265;
|
||||
info->video.h264_pkt_type = (RtmpH264PacketType)classic_header->h264_pkt_type;
|
||||
break;
|
||||
}
|
||||
default: WarnL << "Rtmp video codec not supported: " << (int)classic_header->codec_id; break;
|
||||
}
|
||||
}
|
||||
return info->codec;
|
||||
}
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
namespace toolkit {
|
||||
StatisticImp(mediakit::RtmpPacket);
|
||||
StatisticImp(mediakit::RtmpPacket);
|
||||
}
|
155
src/Rtmp/Rtmp.h
155
src/Rtmp/Rtmp.h
@ -18,13 +18,6 @@
|
||||
#include "Network/Buffer.h"
|
||||
#include "Extension/Track.h"
|
||||
|
||||
#if !defined(_WIN32)
|
||||
#define PACKED __attribute__((packed))
|
||||
#else
|
||||
#define PACKED
|
||||
#endif //!defined(_WIN32)
|
||||
|
||||
|
||||
#define DEFAULT_CHUNK_LEN 128
|
||||
#define HANDSHAKE_PLAINTEXT 0x03
|
||||
#define RANDOM_LEN (1536 - 8)
|
||||
@ -63,23 +56,9 @@
|
||||
#define CHUNK_AUDIO 6 /*音频chunkID*/
|
||||
#define CHUNK_VIDEO 7 /*视频chunkID*/
|
||||
|
||||
#define FLV_KEY_FRAME 1
|
||||
#define FLV_INTER_FRAME 2
|
||||
|
||||
#define FLV_CODEC_AAC 10
|
||||
#define FLV_CODEC_H264 7
|
||||
//金山扩展: https://github.com/ksvc/FFmpeg/wiki
|
||||
#define FLV_CODEC_H265 12
|
||||
#define FLV_CODEC_G711A 7
|
||||
#define FLV_CODEC_G711U 8
|
||||
//参考学而思网校: https://github.com/notedit/rtmp/commit/6e314ac5b29611431f8fb5468596b05815743c10
|
||||
#define FLV_CODEC_OPUS 13
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
#if defined(_WIN32)
|
||||
#pragma pack(push, 1)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
class RtmpHandshake {
|
||||
public:
|
||||
@ -93,7 +72,7 @@ public:
|
||||
|
||||
void create_complex_c0c1();
|
||||
|
||||
}PACKED;
|
||||
};
|
||||
|
||||
class RtmpHeader {
|
||||
public:
|
||||
@ -109,7 +88,7 @@ public:
|
||||
uint8_t body_size[3];
|
||||
uint8_t type_id;
|
||||
uint8_t stream_index[4]; /* Note, this is little-endian while others are BE */
|
||||
}PACKED;
|
||||
};
|
||||
|
||||
class FLVHeader {
|
||||
public:
|
||||
@ -142,7 +121,7 @@ public:
|
||||
uint32_t length;
|
||||
//固定为0
|
||||
uint32_t previous_tag_size0;
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
class RtmpTagHeader {
|
||||
public:
|
||||
@ -151,11 +130,9 @@ public:
|
||||
uint8_t timestamp[3] = {0};
|
||||
uint8_t timestamp_ex = 0;
|
||||
uint8_t streamid[3] = {0}; /* Always 0. */
|
||||
} PACKED;
|
||||
};
|
||||
|
||||
#if defined(_WIN32)
|
||||
#pragma pack(pop)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
class RtmpPacket : public toolkit::Buffer{
|
||||
public:
|
||||
@ -182,11 +159,15 @@ public:
|
||||
|
||||
void clear();
|
||||
|
||||
// video config frame和key frame都返回true
|
||||
// 用于gop缓存定位
|
||||
bool isVideoKeyFrame() const;
|
||||
bool isCfgFrame() const;
|
||||
|
||||
int getMediaType() const;
|
||||
// aac config或h264/h265 config返回true,支持增强型rtmp
|
||||
// 用于缓存解码配置信息
|
||||
bool isConfigFrame() const;
|
||||
|
||||
int getRtmpCodecId() const;
|
||||
int getAudioSampleRate() const;
|
||||
int getAudioSampleBit() const;
|
||||
int getAudioChannel() const;
|
||||
@ -269,5 +250,121 @@ private:
|
||||
//根据音频track获取flags
|
||||
uint8_t getAudioRtmpFlags(const Track::Ptr &track);
|
||||
|
||||
////////////////// rtmp video //////////////////////////
|
||||
//https://rtmp.veriskope.com/pdf/video_file_format_spec_v10_1.pdf
|
||||
|
||||
// UB [4]; Type of video frame.
|
||||
enum class RtmpFrameType : uint8_t {
|
||||
reserved = 0,
|
||||
key_frame = 1, // key frame (for AVC, a seekable frame)
|
||||
inter_frame = 2, // inter frame (for AVC, a non-seekable frame)
|
||||
disposable_inter_frame = 3, // disposable inter frame (H.263 only)
|
||||
generated_key_frame = 4, // generated key frame (reserved for server use only)
|
||||
video_info_frame = 5, // video info/command frame
|
||||
};
|
||||
|
||||
#define MKBETAG(a, b, c, d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24))
|
||||
|
||||
// UB [4]; Codec Identifier.
|
||||
enum class RtmpVideoCodec : uint32_t {
|
||||
h263 = 2, // Sorenson H.263
|
||||
screen_video = 3, // Screen video
|
||||
vp6 = 4, // On2 VP6
|
||||
vp6_alpha = 5, // On2 VP6 with alpha channel
|
||||
screen_video2 = 6, // Screen video version 2
|
||||
h264 = 7, // avc
|
||||
h265 = 12, // 国内扩展
|
||||
|
||||
// 增强型rtmp FourCC
|
||||
fourcc_vp9 = MKBETAG('v', 'p', '0', '9'),
|
||||
fourcc_av1 = MKBETAG('a', 'v', '0', '1'),
|
||||
fourcc_hevc = MKBETAG('h', 'v', 'c', '1')
|
||||
};
|
||||
|
||||
// UI8;
|
||||
enum class RtmpH264PacketType : uint8_t {
|
||||
h264_config_header = 0, // AVC or HEVC sequence header(sps/pps)
|
||||
h264_nalu = 1, // AVC or HEVC NALU
|
||||
h264_end_seq = 2, // AVC or HEVC end of sequence (lower level NALU sequence ender is not REQUIRED or supported)
|
||||
};
|
||||
|
||||
// https://github.com/veovera/enhanced-rtmp/blob/main/enhanced-rtmp.pdf
|
||||
// UB[4]
|
||||
enum class RtmpPacketType : uint8_t {
|
||||
PacketTypeSequenceStart = 0,
|
||||
PacketTypeCodedFrames = 1,
|
||||
PacketTypeSequenceEnd = 2,
|
||||
|
||||
// CompositionTime Offset is implied to equal zero. This is
|
||||
// an optimization to save putting SI24 composition time value of zero on
|
||||
// the wire. See pseudo code below in the VideoTagBody section
|
||||
PacketTypeCodedFramesX = 3,
|
||||
|
||||
// VideoTagBody does not contain video data. VideoTagBody
|
||||
// instead contains an AMF encoded metadata. See Metadata Frame
|
||||
// section for an illustration of its usage. As an example, the metadata
|
||||
// can be HDR information. This is a good way to signal HDR
|
||||
// information. This also opens up future ways to express additional
|
||||
// metadata that is meant for the next video sequence.
|
||||
//
|
||||
// note: presence of PacketTypeMetadata means that FrameType
|
||||
// flags at the top of this table should be ignored
|
||||
PacketTypeMetadata = 4,
|
||||
|
||||
// Carriage of bitstream in MPEG-2 TS format
|
||||
// note: PacketTypeSequenceStart and PacketTypeMPEG2TSSequenceStart
|
||||
// are mutually exclusive
|
||||
PacketTypeMPEG2TSSequenceStart = 5,
|
||||
};
|
||||
|
||||
////////////////// rtmp audio //////////////////////////
|
||||
//https://rtmp.veriskope.com/pdf/video_file_format_spec_v10_1.pdf
|
||||
|
||||
// UB [4]; Format of SoundData
|
||||
enum class RtmpAudioCodec : uint8_t {
|
||||
/**
|
||||
0 = Linear PCM, platform endian
|
||||
1 = ADPCM
|
||||
2 = MP3
|
||||
3 = Linear PCM, little endian
|
||||
4 = Nellymoser 16 kHz mono
|
||||
5 = Nellymoser 8 kHz mono
|
||||
6 = Nellymoser
|
||||
7 = G.711 A-law logarithmic PCM
|
||||
8 = G.711 mu-law logarithmic PCM
|
||||
9 = reserved
|
||||
10 = AAC
|
||||
11 = Speex
|
||||
14 = MP3 8 kHz
|
||||
15 = Device-specific sound
|
||||
*/
|
||||
g711a = 7,
|
||||
g711u = 8,
|
||||
aac = 10,
|
||||
opus = 13 // 国内扩展
|
||||
};
|
||||
|
||||
// UI8;
|
||||
enum class RtmpAACPacketType : uint8_t {
|
||||
aac_config_header = 0, // AAC sequence header
|
||||
aac_raw = 1, // AAC raw
|
||||
};
|
||||
|
||||
////////////////////////////////////////////
|
||||
|
||||
struct RtmpPacketInfo {
|
||||
CodecId codec = CodecInvalid;
|
||||
bool is_enhanced;
|
||||
union {
|
||||
struct {
|
||||
RtmpFrameType frame_type;
|
||||
RtmpPacketType pkt_type; // enhanced = true
|
||||
RtmpH264PacketType h264_pkt_type; // enhanced = false
|
||||
} video;
|
||||
};
|
||||
};
|
||||
// https://github.com/veovera/enhanced-rtmp
|
||||
CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info = nullptr);
|
||||
|
||||
}//namespace mediakit
|
||||
#endif//__rtmp_h
|
||||
|
@ -19,12 +19,12 @@ size_t RtmpDemuxer::trackCount(const AMFValue &metadata) {
|
||||
size_t ret = 0;
|
||||
metadata.object_for_each([&](const string &key, const AMFValue &val) {
|
||||
if (key == "videocodecid") {
|
||||
//找到视频
|
||||
// 找到视频
|
||||
++ret;
|
||||
return;
|
||||
}
|
||||
if (key == "audiocodecid") {
|
||||
//找到音频
|
||||
// 找到音频
|
||||
++ret;
|
||||
return;
|
||||
}
|
||||
@ -32,7 +32,7 @@ size_t RtmpDemuxer::trackCount(const AMFValue &metadata) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool RtmpDemuxer::loadMetaData(const AMFValue &val){
|
||||
bool RtmpDemuxer::loadMetaData(const AMFValue &val) {
|
||||
bool ret = false;
|
||||
try {
|
||||
int audiosamplerate = 0;
|
||||
@ -60,12 +60,12 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){
|
||||
return;
|
||||
}
|
||||
if (key == "videocodecid") {
|
||||
//找到视频
|
||||
// 找到视频
|
||||
videocodecid = &val;
|
||||
return;
|
||||
}
|
||||
if (key == "audiocodecid") {
|
||||
//找到音频
|
||||
// 找到音频
|
||||
audiocodecid = &val;
|
||||
return;
|
||||
}
|
||||
@ -79,12 +79,12 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){
|
||||
}
|
||||
});
|
||||
if (videocodecid) {
|
||||
//有视频
|
||||
// 有视频
|
||||
ret = true;
|
||||
makeVideoTrack(*videocodecid, videodatarate * 1024);
|
||||
}
|
||||
if (audiocodecid) {
|
||||
//有音频
|
||||
// 有音频
|
||||
ret = true;
|
||||
makeAudioTrack(*audiocodecid, audiosamplerate, audiochannels, audiosamplesize, audiodatarate * 1024);
|
||||
}
|
||||
@ -93,7 +93,7 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
//metadata中存在track相关的描述,那么我们根据metadata判断有多少个track
|
||||
// metadata中存在track相关的描述,那么我们根据metadata判断有多少个track
|
||||
addTrackCompleted();
|
||||
}
|
||||
return ret;
|
||||
@ -108,8 +108,8 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
case MSG_VIDEO: {
|
||||
if (!_try_get_video_track) {
|
||||
_try_get_video_track = true;
|
||||
auto codec = AMFValue(pkt->getMediaType());
|
||||
makeVideoTrack(codec, 0);
|
||||
auto codec_id = parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size());
|
||||
makeVideoTrack(Factory::getTrackByCodecId(codec_id), 0);
|
||||
}
|
||||
if (_video_rtmp_decoder) {
|
||||
_video_rtmp_decoder->inputRtmp(pkt);
|
||||
@ -120,7 +120,7 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
case MSG_AUDIO: {
|
||||
if (!_try_get_audio_track) {
|
||||
_try_get_audio_track = true;
|
||||
auto codec = AMFValue(pkt->getMediaType());
|
||||
auto codec = AMFValue(pkt->getRtmpCodecId());
|
||||
makeAudioTrack(codec, pkt->getAudioSampleRate(), pkt->getAudioChannel(), pkt->getAudioSampleBit(), 0);
|
||||
}
|
||||
if (_audio_rtmp_decoder) {
|
||||
@ -128,51 +128,55 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
default : break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpDemuxer::makeVideoTrack(const AMFValue &videoCodec, int bit_rate) {
|
||||
makeVideoTrack(Factory::getVideoTrackByAmf(videoCodec), bit_rate);
|
||||
}
|
||||
|
||||
void RtmpDemuxer::makeVideoTrack(const Track::Ptr &track, int bit_rate) {
|
||||
if (_video_rtmp_decoder) {
|
||||
return;
|
||||
}
|
||||
//生成Track对象
|
||||
_video_track = dynamic_pointer_cast<VideoTrack>(Factory::getVideoTrackByAmf(videoCodec));
|
||||
// 生成Track对象
|
||||
_video_track = dynamic_pointer_cast<VideoTrack>(track);
|
||||
if (!_video_track) {
|
||||
return;
|
||||
}
|
||||
//生成rtmpCodec对象以便解码rtmp
|
||||
// 生成rtmpCodec对象以便解码rtmp
|
||||
_video_rtmp_decoder = Factory::getRtmpCodecByTrack(_video_track, false);
|
||||
if (!_video_rtmp_decoder) {
|
||||
//找不到相应的rtmp解码器,该track无效
|
||||
// 找不到相应的rtmp解码器,该track无效
|
||||
_video_track.reset();
|
||||
return;
|
||||
}
|
||||
_video_track->setBitRate(bit_rate);
|
||||
//设置rtmp解码器代理,生成的frame写入该Track
|
||||
// 设置rtmp解码器代理,生成的frame写入该Track
|
||||
_video_rtmp_decoder->addDelegate(_video_track);
|
||||
addTrack(_video_track);
|
||||
_try_get_video_track = true;
|
||||
}
|
||||
|
||||
void RtmpDemuxer::makeAudioTrack(const AMFValue &audioCodec,int sample_rate, int channels, int sample_bit, int bit_rate) {
|
||||
void RtmpDemuxer::makeAudioTrack(const AMFValue &audioCodec, int sample_rate, int channels, int sample_bit, int bit_rate) {
|
||||
if (_audio_rtmp_decoder) {
|
||||
return;
|
||||
}
|
||||
//生成Track对象
|
||||
// 生成Track对象
|
||||
_audio_track = dynamic_pointer_cast<AudioTrack>(Factory::getAudioTrackByAmf(audioCodec, sample_rate, channels, sample_bit));
|
||||
if (!_audio_track) {
|
||||
return;
|
||||
}
|
||||
//生成rtmpCodec对象以便解码rtmp
|
||||
// 生成rtmpCodec对象以便解码rtmp
|
||||
_audio_rtmp_decoder = Factory::getRtmpCodecByTrack(_audio_track, false);
|
||||
if (!_audio_rtmp_decoder) {
|
||||
//找不到相应的rtmp解码器,该track无效
|
||||
// 找不到相应的rtmp解码器,该track无效
|
||||
_audio_track.reset();
|
||||
return;
|
||||
}
|
||||
_audio_track->setBitRate(bit_rate);
|
||||
//设置rtmp解码器代理,生成的frame写入该Track
|
||||
// 设置rtmp解码器代理,生成的frame写入该Track
|
||||
_audio_rtmp_decoder->addDelegate(_audio_track);
|
||||
addTrack(_audio_track);
|
||||
_try_get_audio_track = true;
|
||||
|
@ -45,6 +45,7 @@ public:
|
||||
|
||||
private:
|
||||
void makeVideoTrack(const AMFValue &val, int bit_rate);
|
||||
void makeVideoTrack(const Track::Ptr &val, int bit_rate);
|
||||
void makeAudioTrack(const AMFValue &val, int sample_rate, int channels, int sample_bit, int bit_rate);
|
||||
|
||||
private:
|
||||
|
@ -57,8 +57,8 @@ public:
|
||||
return _ring;
|
||||
}
|
||||
|
||||
void getPlayerList(const std::function<void(const std::list<std::shared_ptr<void>> &info_list)> &cb,
|
||||
const std::function<std::shared_ptr<void>(std::shared_ptr<void> &&info)> &on_change) override {
|
||||
void getPlayerList(const std::function<void(const std::list<toolkit::Any> &info_list)> &cb,
|
||||
const std::function<toolkit::Any(toolkit::Any &&info)> &on_change) override {
|
||||
_ring->getInfoList(cb, on_change);
|
||||
}
|
||||
|
||||
@ -73,42 +73,29 @@ public:
|
||||
/**
|
||||
* 获取metadata
|
||||
*/
|
||||
const AMFValue &getMetaData() const {
|
||||
template <typename FUNC>
|
||||
void getMetaData(const FUNC &func) const {
|
||||
std::lock_guard<std::recursive_mutex> lock(_mtx);
|
||||
return _metadata;
|
||||
if (_metadata) {
|
||||
func(_metadata);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有的config帧
|
||||
*/
|
||||
template<typename FUNC>
|
||||
void getConfigFrame(const FUNC &f) {
|
||||
template <typename FUNC>
|
||||
void getConfigFrame(const FUNC &func) {
|
||||
std::lock_guard<std::recursive_mutex> lock(_mtx);
|
||||
for (auto &pr : _config_frame_map) {
|
||||
f(pr.second);
|
||||
func(pr.second);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置metadata
|
||||
*/
|
||||
virtual void setMetaData(const AMFValue &metadata) {
|
||||
_metadata = metadata;
|
||||
_metadata.set("title", std::string("Streamed by ") + kServerName);
|
||||
_have_video = _metadata["videocodecid"];
|
||||
_have_audio = _metadata["audiocodecid"];
|
||||
if (_ring) {
|
||||
regist();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新metadata
|
||||
*/
|
||||
void updateMetaData(const AMFValue &metadata) {
|
||||
std::lock_guard<std::recursive_mutex> lock(_mtx);
|
||||
_metadata = metadata;
|
||||
}
|
||||
virtual void setMetaData(const AMFValue &metadata);
|
||||
|
||||
/**
|
||||
* 输入rtmp包
|
||||
|
@ -2,15 +2,15 @@
|
||||
#include "RtmpMediaSourceImp.h"
|
||||
|
||||
namespace mediakit {
|
||||
uint32_t RtmpMediaSource::getTimeStamp(TrackType trackType)
|
||||
{
|
||||
|
||||
uint32_t RtmpMediaSource::getTimeStamp(TrackType trackType) {
|
||||
assert(trackType >= TrackInvalid && trackType < TrackMax);
|
||||
if (trackType != TrackInvalid) {
|
||||
//获取某track的时间戳
|
||||
// 获取某track的时间戳
|
||||
return _track_stamps[trackType];
|
||||
}
|
||||
|
||||
//获取所有track的最小时间戳
|
||||
// 获取所有track的最小时间戳
|
||||
uint32_t ret = UINT32_MAX;
|
||||
for (auto &stamp : _track_stamps) {
|
||||
if (stamp > 0 && stamp < ret) {
|
||||
@ -20,38 +20,61 @@ uint32_t RtmpMediaSource::getTimeStamp(TrackType trackType)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/)
|
||||
{
|
||||
bool is_video = pkt->type_id == MSG_VIDEO;
|
||||
_speed[is_video ? TrackVideo : TrackAudio] += pkt->size();
|
||||
//保存当前时间戳
|
||||
switch (pkt->type_id) {
|
||||
case MSG_VIDEO: _track_stamps[TrackVideo] = pkt->time_stamp, _have_video = true; break;
|
||||
case MSG_AUDIO: _track_stamps[TrackAudio] = pkt->time_stamp, _have_audio = true; break;
|
||||
default: break;
|
||||
void RtmpMediaSource::setMetaData(const AMFValue &metadata) {
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(_mtx);
|
||||
_metadata = metadata;
|
||||
_metadata.set("title", std::string("Streamed by ") + kServerName);
|
||||
}
|
||||
|
||||
if (pkt->isCfgFrame()) {
|
||||
_have_video = _metadata["videocodecid"];
|
||||
_have_audio = _metadata["audiocodecid"];
|
||||
if (_ring) {
|
||||
regist();
|
||||
|
||||
AMFEncoder enc;
|
||||
enc << "onMetaData" << _metadata;
|
||||
RtmpPacket::Ptr packet = RtmpPacket::create();
|
||||
packet->buffer = enc.data();
|
||||
packet->type_id = MSG_DATA;
|
||||
packet->time_stamp = 0;
|
||||
packet->chunk_id = CHUNK_CLIENT_REQUEST_AFTER;
|
||||
packet->stream_index = STREAM_MEDIA;
|
||||
onWrite(std::move(packet));
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) {
|
||||
bool is_video = pkt->type_id == MSG_VIDEO;
|
||||
_speed[is_video ? TrackVideo : TrackAudio] += pkt->size();
|
||||
// 保存当前时间戳
|
||||
switch (pkt->type_id) {
|
||||
case MSG_VIDEO: _track_stamps[TrackVideo] = pkt->time_stamp, _have_video = true; break;
|
||||
case MSG_AUDIO: _track_stamps[TrackAudio] = pkt->time_stamp, _have_audio = true; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (pkt->isConfigFrame()) {
|
||||
std::lock_guard<std::recursive_mutex> lock(_mtx);
|
||||
_config_frame_map[pkt->type_id] = pkt;
|
||||
if (!_ring) {
|
||||
//注册后收到config帧更新到各播放器
|
||||
// 注册后收到config帧更新到各播放器
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_ring) {
|
||||
std::weak_ptr<RtmpMediaSource> weakSelf = std::static_pointer_cast<RtmpMediaSource>(shared_from_this());
|
||||
auto lam = [weakSelf](int size) {
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
std::weak_ptr<RtmpMediaSource> weak_self = std::static_pointer_cast<RtmpMediaSource>(shared_from_this());
|
||||
auto lam = [weak_self](int size) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
strongSelf->onReaderChanged(size);
|
||||
strong_self->onReaderChanged(size);
|
||||
};
|
||||
|
||||
//GOP默认缓冲512组RTMP包,每组RTMP包时间戳相同(如果开启合并写了,那么每组为合并写时间内的RTMP包),
|
||||
//每次遇到关键帧第一个RTMP包,则会清空GOP缓存(因为有新的关键帧了,同样可以实现秒开)
|
||||
// GOP默认缓冲512组RTMP包,每组RTMP包时间戳相同(如果开启合并写了,那么每组为合并写时间内的RTMP包),
|
||||
// 每次遇到关键帧第一个RTMP包,则会清空GOP缓存(因为有新的关键帧了,同样可以实现秒开)
|
||||
_ring = std::make_shared<RingType>(_ring_size, std::move(lam));
|
||||
if (_metadata) {
|
||||
regist();
|
||||
@ -62,47 +85,42 @@ void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/)
|
||||
PacketCache<RtmpPacket>::inputPacket(stamp, is_video, std::move(pkt), key);
|
||||
}
|
||||
|
||||
|
||||
RtmpMediaSourceImp::RtmpMediaSourceImp(const MediaTuple& tuple, int ringSize) : RtmpMediaSource(tuple, ringSize)
|
||||
{
|
||||
RtmpMediaSourceImp::RtmpMediaSourceImp(const MediaTuple &tuple, int ringSize)
|
||||
: RtmpMediaSource(tuple, ringSize) {
|
||||
_demuxer = std::make_shared<RtmpDemuxer>();
|
||||
_demuxer->setTrackListener(this);
|
||||
}
|
||||
|
||||
void RtmpMediaSourceImp::setMetaData(const AMFValue &metadata)
|
||||
{
|
||||
void RtmpMediaSourceImp::setMetaData(const AMFValue &metadata) {
|
||||
if (!_demuxer->loadMetaData(metadata)) {
|
||||
//该metadata无效,需要重新生成
|
||||
// 该metadata无效,需要重新生成
|
||||
_metadata = metadata;
|
||||
_recreate_metadata = true;
|
||||
}
|
||||
RtmpMediaSource::setMetaData(metadata);
|
||||
}
|
||||
|
||||
void RtmpMediaSourceImp::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/)
|
||||
{
|
||||
void RtmpMediaSourceImp::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) {
|
||||
if (!_all_track_ready || _muxer->isEnabled()) {
|
||||
//未获取到所有Track后,或者开启转协议,那么需要解复用rtmp
|
||||
// 未获取到所有Track后,或者开启转协议,那么需要解复用rtmp
|
||||
_demuxer->inputRtmp(pkt);
|
||||
}
|
||||
RtmpMediaSource::onWrite(std::move(pkt));
|
||||
}
|
||||
|
||||
int RtmpMediaSourceImp::totalReaderCount()
|
||||
{
|
||||
int RtmpMediaSourceImp::totalReaderCount() {
|
||||
return readerCount() + (_muxer ? _muxer->totalReaderCount() : 0);
|
||||
}
|
||||
|
||||
void RtmpMediaSourceImp::setProtocolOption(const ProtocolOption &option)
|
||||
{
|
||||
//不重复生成rtmp
|
||||
void RtmpMediaSourceImp::setProtocolOption(const ProtocolOption &option) {
|
||||
// 不重复生成rtmp
|
||||
_option = option;
|
||||
//不重复生成rtmp协议
|
||||
// 不重复生成rtmp协议
|
||||
_option.enable_rtmp = false;
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(_tuple, _demuxer->getDuration(), _option);
|
||||
_muxer->setMediaListener(getListener());
|
||||
_muxer->setTrackListener(std::static_pointer_cast<RtmpMediaSourceImp>(shared_from_this()));
|
||||
//让_muxer对象拦截一部分事件(比如说录像相关事件)
|
||||
// 让_muxer对象拦截一部分事件(比如说录像相关事件)
|
||||
MediaSource::setListener(_muxer);
|
||||
|
||||
for (auto &track : _demuxer->getTracks(false)) {
|
||||
@ -111,8 +129,7 @@ void RtmpMediaSourceImp::setProtocolOption(const ProtocolOption &option)
|
||||
}
|
||||
}
|
||||
|
||||
bool RtmpMediaSourceImp::addTrack(const Track::Ptr &track)
|
||||
{
|
||||
bool RtmpMediaSourceImp::addTrack(const Track::Ptr &track) {
|
||||
if (_muxer) {
|
||||
if (_muxer->addTrack(track)) {
|
||||
track->addDelegate(_muxer);
|
||||
@ -122,45 +139,38 @@ bool RtmpMediaSourceImp::addTrack(const Track::Ptr &track)
|
||||
return false;
|
||||
}
|
||||
|
||||
void RtmpMediaSourceImp::addTrackCompleted()
|
||||
{
|
||||
void RtmpMediaSourceImp::addTrackCompleted() {
|
||||
if (_muxer) {
|
||||
_muxer->addTrackCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpMediaSourceImp::resetTracks()
|
||||
{
|
||||
void RtmpMediaSourceImp::resetTracks() {
|
||||
if (_muxer) {
|
||||
_muxer->resetTracks();
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpMediaSourceImp::onAllTrackReady()
|
||||
{
|
||||
void RtmpMediaSourceImp::onAllTrackReady() {
|
||||
_all_track_ready = true;
|
||||
|
||||
if (_recreate_metadata) {
|
||||
//更新metadata
|
||||
// 更新metadata
|
||||
for (auto &track : _muxer->getTracks()) {
|
||||
Metadata::addTrack(_metadata, track);
|
||||
}
|
||||
RtmpMediaSource::updateMetaData(_metadata);
|
||||
RtmpMediaSource::setMetaData(_metadata);
|
||||
}
|
||||
}
|
||||
|
||||
void RtmpMediaSourceImp::setListener(const std::weak_ptr<MediaSourceEvent> &listener)
|
||||
{
|
||||
void RtmpMediaSourceImp::setListener(const std::weak_ptr<MediaSourceEvent> &listener) {
|
||||
if (_muxer) {
|
||||
//_muxer对象不能处理的事件再给listener处理
|
||||
_muxer->setMediaListener(listener);
|
||||
}
|
||||
else {
|
||||
//未创建_muxer对象,事件全部给listener处理
|
||||
} else {
|
||||
// 未创建_muxer对象,事件全部给listener处理
|
||||
MediaSource::setListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
} // namespace mediakit
|
||||
|
@ -44,7 +44,8 @@ public:
|
||||
return _media_src->readerCount();
|
||||
}
|
||||
|
||||
void onAllTrackReady(){
|
||||
void addTrackCompleted() override {
|
||||
RtmpMuxer::addTrackCompleted();
|
||||
makeConfigPacket();
|
||||
_media_src->setMetaData(getMetadata());
|
||||
}
|
||||
|
@ -191,6 +191,13 @@ void RtmpPlayer::send_connect() {
|
||||
obj.set("audioCodecs", (double) (0x0400));
|
||||
//只支持H264
|
||||
obj.set("videoCodecs", (double) (0x0080));
|
||||
|
||||
AMFValue fourCcList(AMF_STRICT_ARRAY);
|
||||
fourCcList.add("av01");
|
||||
fourCcList.add("vp09");
|
||||
fourCcList.add("hvc1");
|
||||
obj.set("fourCcList", fourCcList);
|
||||
|
||||
sendInvoke("connect", obj);
|
||||
addOnResultCB([this](AMFDecoder &dec) {
|
||||
//TraceL << "connect result";
|
||||
@ -332,7 +339,7 @@ void RtmpPlayer::onMediaData_l(RtmpPacket::Ptr chunk_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chunk_data->isCfgFrame()) {
|
||||
if (chunk_data->isConfigFrame()) {
|
||||
//输入配置帧以便初始化完成各个track
|
||||
onRtmpPacket(chunk_data);
|
||||
} else {
|
||||
|
@ -135,6 +135,13 @@ void RtmpPusher::send_connect() {
|
||||
obj.set("type", "nonprivate");
|
||||
obj.set("tcUrl", _tc_url);
|
||||
obj.set("swfUrl", _tc_url);
|
||||
|
||||
AMFValue fourCcList(AMF_STRICT_ARRAY);
|
||||
fourCcList.add("av01");
|
||||
fourCcList.add("vp09");
|
||||
fourCcList.add("hvc1");
|
||||
obj.set("fourCcList", fourCcList);
|
||||
|
||||
sendInvoke("connect", obj);
|
||||
addOnResultCB([this](AMFDecoder &dec) {
|
||||
//TraceL << "connect result";
|
||||
@ -183,10 +190,14 @@ void RtmpPusher::send_metaData(){
|
||||
throw std::runtime_error("the media source was released");
|
||||
}
|
||||
|
||||
AMFEncoder enc;
|
||||
enc << "@setDataFrame" << "onMetaData" << src->getMetaData();
|
||||
sendRequest(MSG_DATA, enc.data());
|
||||
// metadata
|
||||
src->getMetaData([&](const AMFValue &metadata) {
|
||||
AMFEncoder enc;
|
||||
enc << "@setDataFrame" << "onMetaData" << metadata;
|
||||
sendRequest(MSG_DATA, enc.data());
|
||||
});
|
||||
|
||||
// config frame
|
||||
src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) {
|
||||
sendRtmp(pkt->type_id, _stream_index, pkt, pkt->time_stamp, pkt->chunk_id);
|
||||
});
|
||||
@ -207,7 +218,16 @@ void RtmpPusher::send_metaData(){
|
||||
if (++i == size) {
|
||||
strong_self->setSendFlushFlag(true);
|
||||
}
|
||||
strong_self->sendRtmp(rtmp->type_id, strong_self->_stream_index, rtmp, rtmp->time_stamp, rtmp->chunk_id);
|
||||
if (rtmp->type_id == MSG_DATA) {
|
||||
// update metadata
|
||||
AMFEncoder enc;
|
||||
enc << "@setDataFrame";
|
||||
auto pkt = enc.data();
|
||||
pkt.append(rtmp->data(), rtmp->size());
|
||||
strong_self->sendRequest(MSG_DATA, pkt);
|
||||
} else {
|
||||
strong_self->sendRtmp(rtmp->type_id, strong_self->_stream_index, rtmp, rtmp->time_stamp, rtmp->chunk_id);
|
||||
}
|
||||
});
|
||||
});
|
||||
_rtmp_reader->setDetachCB([weak_self]() {
|
||||
|
@ -37,7 +37,7 @@ void RtmpSession::onError(const SockException& err) {
|
||||
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
|
||||
|
||||
if (_total_bytes >= iFlowThreshold * 1024) {
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, is_player, static_cast<SockInfo &>(*this));
|
||||
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, is_player, *this);
|
||||
}
|
||||
|
||||
//如果是主动关闭的,那么不延迟注销
|
||||
@ -240,7 +240,7 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) {
|
||||
on_res(err, option);
|
||||
});
|
||||
};
|
||||
auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtmp_push, _media_info, invoker, static_cast<SockInfo &>(*this));
|
||||
auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtmp_push, _media_info, invoker, *this);
|
||||
if(!flag){
|
||||
//该事件无人监听,默认鉴权成功
|
||||
on_res("", ProtocolOption());
|
||||
@ -316,17 +316,14 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr
|
||||
"description", "Now published." ,
|
||||
"details", _media_info.stream,
|
||||
"clientid", "0"});
|
||||
|
||||
auto &metadata = src->getMetaData();
|
||||
if(metadata){
|
||||
//在有metadata的情况下才发送metadata
|
||||
//其实metadata没什么用,有些推流器不产生metadata
|
||||
// onMetaData
|
||||
// metadata
|
||||
src->getMetaData([&](const AMFValue &metadata) {
|
||||
invoke.clear();
|
||||
invoke << "onMetaData" << metadata;
|
||||
sendResponse(MSG_DATA, invoke.data());
|
||||
}
|
||||
});
|
||||
|
||||
// config frame
|
||||
src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) {
|
||||
onSendMedia(pkt);
|
||||
});
|
||||
@ -334,7 +331,11 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr
|
||||
src->pause(false);
|
||||
_ring_reader = src->getRing()->attach(getPoller());
|
||||
weak_ptr<RtmpSession> weak_self = static_pointer_cast<RtmpSession>(shared_from_this());
|
||||
_ring_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); });
|
||||
_ring_reader->setGetInfoCB([weak_self]() {
|
||||
Any ret;
|
||||
ret.set(static_pointer_cast<SockInfo>(weak_self.lock()));
|
||||
return ret;
|
||||
});
|
||||
_ring_reader->setReadCB([weak_self](const RtmpMediaSource::RingDataType &pkt) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
@ -355,6 +356,7 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
strong_self->sendUserControl(CONTROL_STREAM_EOF/*or CONTROL_STREAM_DRY ?*/, STREAM_MEDIA);
|
||||
strong_self->shutdown(SockException(Err_shutdown,"rtmp ring buffer detached"));
|
||||
});
|
||||
src->pause(false);
|
||||
@ -408,7 +410,7 @@ void RtmpSession::doPlay(AMFDecoder &dec){
|
||||
});
|
||||
};
|
||||
|
||||
auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _media_info, invoker, static_cast<SockInfo &>(*this));
|
||||
auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _media_info, invoker, *this);
|
||||
if (!flag) {
|
||||
// 该事件无人监听,默认不鉴权
|
||||
doPlayResponse("", [token](bool) {});
|
||||
@ -506,6 +508,7 @@ void RtmpSession::setMetaData(AMFDecoder &dec) {
|
||||
throw std::runtime_error("can only set metadata");
|
||||
}
|
||||
_push_metadata = dec.load<AMFValue>();
|
||||
_set_meta_data = false;
|
||||
}
|
||||
|
||||
void RtmpSession::onProcessCmd(AMFDecoder &dec) {
|
||||
@ -553,6 +556,7 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) {
|
||||
} else if (type == "onMetaData") {
|
||||
//兼容某些不规范的推流器
|
||||
_push_metadata = dec.load<AMFValue>();
|
||||
_set_meta_data = false;
|
||||
} else {
|
||||
TraceP(this) << "unknown notify:" << type;
|
||||
}
|
||||
@ -562,7 +566,14 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) {
|
||||
case MSG_AUDIO:
|
||||
case MSG_VIDEO: {
|
||||
if (!_push_src) {
|
||||
WarnL << "Not a rtmp push!";
|
||||
if (_ring_reader) {
|
||||
throw std::runtime_error("Rtmp player send media packets");
|
||||
}
|
||||
if (packet->isConfigFrame()) {
|
||||
auto id = packet->type_id;
|
||||
_push_config_packets.emplace(id, std::move(packet));
|
||||
}
|
||||
WarnL << "Rtmp pusher send media packet before handshake completed!";
|
||||
return;
|
||||
}
|
||||
|
||||
@ -570,6 +581,12 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) {
|
||||
_set_meta_data = true;
|
||||
_push_src->setMetaData(_push_metadata ? _push_metadata : TitleMeta().getMetadata());
|
||||
}
|
||||
if (!_push_config_packets.empty()) {
|
||||
for (auto &pr : _push_config_packets) {
|
||||
_push_src->onWrite(std::move(pr.second));
|
||||
}
|
||||
_push_config_packets.clear();
|
||||
}
|
||||
_push_src->onWrite(std::move(packet));
|
||||
break;
|
||||
}
|
||||
|
@ -97,6 +97,7 @@ private:
|
||||
MediaInfo _media_info;
|
||||
std::weak_ptr<RtmpMediaSource> _play_src;
|
||||
AMFValue _push_metadata;
|
||||
std::map<uint8_t, RtmpPacket::Ptr> _push_config_packets;
|
||||
RtmpMediaSourceImp::Ptr _push_src;
|
||||
std::shared_ptr<void> _push_src_ownership;
|
||||
RtmpMediaSource::RingType::RingReader::Ptr _ring_reader;
|
||||
|
@ -67,7 +67,7 @@ RtpProcess::~RtpProcess() {
|
||||
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
|
||||
if (_total_bytes >= iFlowThreshold * 1024) {
|
||||
try {
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, false, static_cast<SockInfo &>(*this));
|
||||
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, false, *this);
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << "Exception occurred: " << ex.what();
|
||||
}
|
||||
@ -210,31 +210,27 @@ void RtpProcess::setOnDetach(function<void()> cb) {
|
||||
}
|
||||
|
||||
string RtpProcess::get_peer_ip() {
|
||||
if (!_addr) {
|
||||
try {
|
||||
return _addr ? SockUtil::inet_ntoa((sockaddr *)_addr.get()) : "::";
|
||||
} catch (std::exception &ex) {
|
||||
return "::";
|
||||
}
|
||||
return SockUtil::inet_ntoa((sockaddr *)_addr.get());
|
||||
}
|
||||
|
||||
uint16_t RtpProcess::get_peer_port() {
|
||||
if (!_addr) {
|
||||
try {
|
||||
return _addr ? SockUtil::inet_port((sockaddr *)_addr.get()) : 0;
|
||||
} catch (std::exception &ex) {
|
||||
return 0;
|
||||
}
|
||||
return SockUtil::inet_port((sockaddr *)_addr.get());
|
||||
}
|
||||
|
||||
string RtpProcess::get_local_ip() {
|
||||
if (_sock) {
|
||||
return _sock->get_local_ip();
|
||||
}
|
||||
return "::";
|
||||
return _sock ? _sock->get_local_ip() : "::";
|
||||
}
|
||||
|
||||
uint16_t RtpProcess::get_local_port() {
|
||||
if (_sock) {
|
||||
return _sock->get_local_port();
|
||||
}
|
||||
return 0;
|
||||
return _sock ? _sock->get_local_port() : 0;
|
||||
}
|
||||
|
||||
string RtpProcess::getIdentifier() const {
|
||||
@ -270,9 +266,9 @@ void RtpProcess::emitOnPublish() {
|
||||
};
|
||||
|
||||
//触发推流鉴权事件
|
||||
auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtp_push, _media_info, invoker, static_cast<SockInfo &>(*this));
|
||||
auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtp_push, _media_info, invoker, *this);
|
||||
if (!flag) {
|
||||
//该事件无人监听,默认不鉴权
|
||||
// 该事件无人监听,默认不鉴权
|
||||
invoker("", ProtocolOption());
|
||||
}
|
||||
}
|
||||
@ -305,4 +301,4 @@ float RtpProcess::getLossRate(MediaSource &sender, TrackType type) {
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
#endif//defined(ENABLE_RTPPROXY)
|
||||
#endif//defined(ENABLE_RTPPROXY)
|
||||
|
@ -102,8 +102,8 @@ public:
|
||||
process->setOnDetach(std::move(strong_self->_on_detach));
|
||||
}
|
||||
if (!process) { // process 未创建,触发rtp server 超时事件
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::KBroadcastRtpServerTimeout, strong_self->_local_port, strong_self->_stream_id,
|
||||
(int)strong_self->_tcp_mode, strong_self->_re_use_port, strong_self->_ssrc);
|
||||
NOTICE_EMIT(BroadcastRtpServerTimeoutArgs, Broadcast::KBroadcastRtpServerTimeout, strong_self->_local_port, strong_self->_stream_id,
|
||||
(int)strong_self->_tcp_mode, strong_self->_re_use_port, strong_self->_ssrc);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
@ -39,7 +39,7 @@ RtpPacket::Ptr RtpInfo::makeRtp(TrackType type, const void* data, size_t len, bo
|
||||
++_seq;
|
||||
header->stamp = htonl(uint64_t(stamp) * _sample_rate / 1000);
|
||||
header->ssrc = htonl(_ssrc);
|
||||
|
||||
rtp->ntp_stamp = stamp;
|
||||
//有效负载
|
||||
if (data) {
|
||||
memcpy(&ptr[RtpPacket::kRtpHeaderSize + RtpPacket::kRtpTcpHeaderSize], data, len);
|
||||
|
@ -71,7 +71,7 @@ RtpPacket::Ptr RtpTrack::inputRtp(TrackType type, int sample_rate, uint8_t *ptr,
|
||||
} else {
|
||||
//ssrc错误
|
||||
if (_ssrc_alive.elapsedTime() < 3 * 1000) {
|
||||
//接受正确ssrc的rtp在10秒内,那么我们认为存在多路rtp,忽略掉ssrc不匹配的rtp
|
||||
//接收正确ssrc的rtp在10秒内,那么我们认为存在多路rtp,忽略掉ssrc不匹配的rtp
|
||||
WarnL << "ssrc mismatch, rtp dropped:" << ssrc << " != " << _ssrc;
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cinttypes>
|
||||
#include <random>
|
||||
#include "Rtsp.h"
|
||||
#include "Common/Parser.h"
|
||||
#include "Common/config.h"
|
||||
@ -22,7 +23,8 @@ namespace mediakit {
|
||||
|
||||
int RtpPayload::getClockRate(int pt) {
|
||||
switch (pt) {
|
||||
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return clock_rate;
|
||||
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \
|
||||
case value: return clock_rate;
|
||||
RTP_PT_MAP(SWITCH_CASE)
|
||||
#undef SWITCH_CASE
|
||||
default: return 90000;
|
||||
@ -31,7 +33,8 @@ int RtpPayload::getClockRate(int pt) {
|
||||
|
||||
TrackType RtpPayload::getTrackType(int pt) {
|
||||
switch (pt) {
|
||||
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return type;
|
||||
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \
|
||||
case value: return type;
|
||||
RTP_PT_MAP(SWITCH_CASE)
|
||||
#undef SWITCH_CASE
|
||||
default: return TrackInvalid;
|
||||
@ -40,7 +43,8 @@ TrackType RtpPayload::getTrackType(int pt) {
|
||||
|
||||
int RtpPayload::getAudioChannel(int pt) {
|
||||
switch (pt) {
|
||||
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return channel;
|
||||
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \
|
||||
case value: return channel;
|
||||
RTP_PT_MAP(SWITCH_CASE)
|
||||
#undef SWITCH_CASE
|
||||
default: return 1;
|
||||
@ -49,7 +53,8 @@ int RtpPayload::getAudioChannel(int pt) {
|
||||
|
||||
const char *RtpPayload::getName(int pt) {
|
||||
switch (pt) {
|
||||
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return #name;
|
||||
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \
|
||||
case value: return #name;
|
||||
RTP_PT_MAP(SWITCH_CASE)
|
||||
#undef SWITCH_CASE
|
||||
default: return "unknown payload type";
|
||||
@ -58,7 +63,8 @@ const char *RtpPayload::getName(int pt) {
|
||||
|
||||
CodecId RtpPayload::getCodecId(int pt) {
|
||||
switch (pt) {
|
||||
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return codec_id;
|
||||
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \
|
||||
case value: return codec_id;
|
||||
RTP_PT_MAP(SWITCH_CASE)
|
||||
#undef SWITCH_CASE
|
||||
default: return CodecInvalid;
|
||||
@ -85,7 +91,8 @@ static void getAttrSdp(const multimap<string, string> &attr, _StrPrinter &printe
|
||||
|
||||
string SdpTrack::getName() const {
|
||||
switch (_pt) {
|
||||
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return #name;
|
||||
#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \
|
||||
case value: return #name;
|
||||
RTP_PT_MAP(SWITCH_CASE)
|
||||
#undef SWITCH_CASE
|
||||
default: return _codec;
|
||||
@ -94,7 +101,7 @@ string SdpTrack::getName() const {
|
||||
|
||||
string SdpTrack::getControlUrl(const string &base_url) const {
|
||||
if (_control.find("://") != string::npos) {
|
||||
//以rtsp://开头
|
||||
// 以rtsp://开头
|
||||
return _control;
|
||||
}
|
||||
return base_url + "/" + _control;
|
||||
@ -197,14 +204,14 @@ void SdpParser::load(const string &sdp) {
|
||||
auto &track = *track_ptr;
|
||||
auto it = track._attr.find("range");
|
||||
if (it != track._attr.end()) {
|
||||
char name[16] = {0}, start[16] = {0}, end[16] = {0};
|
||||
char name[16] = { 0 }, start[16] = { 0 }, end[16] = { 0 };
|
||||
int ret = sscanf(it->second.data(), "%15[^=]=%15[^-]-%15s", name, start, end);
|
||||
if (3 == ret || 2 == ret) {
|
||||
if (strcmp(start, "now") == 0) {
|
||||
strcpy(start, "0");
|
||||
}
|
||||
track._start = (float) atof(start);
|
||||
track._end = (float) atof(end);
|
||||
track._start = (float)atof(start);
|
||||
track._end = (float)atof(end);
|
||||
track._duration = track._end - track._start;
|
||||
}
|
||||
}
|
||||
@ -212,11 +219,11 @@ void SdpParser::load(const string &sdp) {
|
||||
for (it = track._attr.find("rtpmap"); it != track._attr.end() && it->first == "rtpmap";) {
|
||||
auto &rtpmap = it->second;
|
||||
int pt, samplerate, channel;
|
||||
char codec[16] = {0};
|
||||
char codec[16] = { 0 };
|
||||
|
||||
sscanf(rtpmap.data(), "%d", &pt);
|
||||
if (track._pt != pt && track._pt != 0xff) {
|
||||
//pt不匹配
|
||||
// pt不匹配
|
||||
it = track._attr.erase(it);
|
||||
continue;
|
||||
}
|
||||
@ -230,18 +237,18 @@ void SdpParser::load(const string &sdp) {
|
||||
track._samplerate = samplerate;
|
||||
}
|
||||
if (!track._samplerate && track._type == TrackVideo) {
|
||||
//未设置视频采样率时,赋值为90000
|
||||
// 未设置视频采样率时,赋值为90000
|
||||
track._samplerate = 90000;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
for (it = track._attr.find("fmtp"); it != track._attr.end() && it->first == "fmtp"; ) {
|
||||
for (it = track._attr.find("fmtp"); it != track._attr.end() && it->first == "fmtp";) {
|
||||
auto &fmtp = it->second;
|
||||
int pt;
|
||||
sscanf(fmtp.data(), "%d", &pt);
|
||||
if (track._pt != pt && track._pt != 0xff) {
|
||||
//pt不匹配
|
||||
// pt不匹配
|
||||
it = track._attr.erase(it);
|
||||
continue;
|
||||
}
|
||||
@ -315,8 +322,17 @@ string SdpParser::toString() const {
|
||||
return title + video + audio;
|
||||
}
|
||||
|
||||
template<int type>
|
||||
class PortManager : public std::enable_shared_from_this<PortManager<type> > {
|
||||
std::string SdpParser::getControlUrl(const std::string &url) const {
|
||||
auto title_track = getTrack(TrackTitle);
|
||||
if (title_track && title_track->_control.find("://") != string::npos) {
|
||||
// 以rtsp://开头
|
||||
return title_track->_control;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
template <int type>
|
||||
class PortManager : public std::enable_shared_from_this<PortManager<type>> {
|
||||
public:
|
||||
PortManager() {
|
||||
static auto func = [](const string &str, int index) {
|
||||
@ -326,11 +342,11 @@ public:
|
||||
};
|
||||
GET_CONFIG_FUNC(uint16_t, s_min_port, RtpProxy::kPortRange, [](const string &str) { return func(str, 0); });
|
||||
GET_CONFIG_FUNC(uint16_t, s_max_port, RtpProxy::kPortRange, [](const string &str) { return func(str, 1); });
|
||||
assert(s_max_port >= s_min_port + 36 -1);
|
||||
assert(s_max_port >= s_min_port + 36 - 1);
|
||||
setRange((s_min_port + 1) / 2, s_max_port / 2);
|
||||
}
|
||||
|
||||
static PortManager& Instance() {
|
||||
static PortManager &Instance() {
|
||||
static auto instance = std::make_shared<PortManager>();
|
||||
return *instance;
|
||||
}
|
||||
@ -344,12 +360,12 @@ public:
|
||||
}
|
||||
if (is_udp) {
|
||||
if (!sock0->bindUdpSock(2 * *sock_pair, local_ip.data(), re_use_port)) {
|
||||
//分配端口失败
|
||||
// 分配端口失败
|
||||
throw runtime_error("open udp socket[0] failed");
|
||||
}
|
||||
|
||||
if (!sock1->bindUdpSock(2 * *sock_pair + 1, local_ip.data(), re_use_port)) {
|
||||
//分配端口失败
|
||||
// 分配端口失败
|
||||
throw runtime_error("open udp socket[1] failed");
|
||||
}
|
||||
|
||||
@ -359,12 +375,12 @@ public:
|
||||
sock1->setOnAccept(on_cycle);
|
||||
} else {
|
||||
if (!sock0->listen(2 * *sock_pair, local_ip.data())) {
|
||||
//分配端口失败
|
||||
// 分配端口失败
|
||||
throw runtime_error("listen tcp socket[0] failed");
|
||||
}
|
||||
|
||||
if (!sock1->listen(2 * *sock_pair + 1, local_ip.data())) {
|
||||
//分配端口失败
|
||||
// 分配端口失败
|
||||
throw runtime_error("listen tcp socket[1] failed");
|
||||
}
|
||||
|
||||
@ -377,9 +393,13 @@ public:
|
||||
|
||||
private:
|
||||
void setRange(uint16_t start_pos, uint16_t end_pos) {
|
||||
std::mt19937 rng(std::random_device {}());
|
||||
lock_guard<recursive_mutex> lck(_pool_mtx);
|
||||
auto it = _port_pair_pool.begin();
|
||||
while (start_pos < end_pos) {
|
||||
_port_pair_pool.emplace_back(start_pos++);
|
||||
// 随机端口排序,防止重启后导致分配的端口重复
|
||||
_port_pair_pool.insert(it, start_pos++);
|
||||
it = _port_pair_pool.begin() + (rng() % (1 + _port_pair_pool.size()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -400,7 +420,7 @@ private:
|
||||
return;
|
||||
}
|
||||
InfoL << "return port to pool:" << 2 * pos << "-" << 2 * pos + 1;
|
||||
//回收端口号
|
||||
// 回收端口号
|
||||
lock_guard<recursive_mutex> lck(strong_self->_pool_mtx);
|
||||
strong_self->_port_pair_pool.emplace_back(pos);
|
||||
});
|
||||
@ -416,7 +436,7 @@ void makeSockPair(std::pair<Socket::Ptr, Socket::Ptr> &pair, const string &local
|
||||
int try_count = 0;
|
||||
while (true) {
|
||||
try {
|
||||
//udp和tcp端口池使用相同算法和范围分配,但是互不相干
|
||||
// udp和tcp端口池使用相同算法和范围分配,但是互不相干
|
||||
if (is_udp) {
|
||||
PortManager<0>::Instance().makeSockPair(pair, local_ip, re_use_port, is_udp);
|
||||
} else {
|
||||
@ -433,9 +453,9 @@ void makeSockPair(std::pair<Socket::Ptr, Socket::Ptr> &pair, const string &local
|
||||
}
|
||||
|
||||
string printSSRC(uint32_t ui32Ssrc) {
|
||||
char tmp[9] = {0};
|
||||
char tmp[9] = { 0 };
|
||||
ui32Ssrc = htonl(ui32Ssrc);
|
||||
uint8_t *pSsrc = (uint8_t *) &ui32Ssrc;
|
||||
uint8_t *pSsrc = (uint8_t *)&ui32Ssrc;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
sprintf(tmp + 2 * i, "%02X", pSsrc[i]);
|
||||
}
|
||||
@ -447,7 +467,7 @@ bool isRtp(const char *buf, size_t size) {
|
||||
return false;
|
||||
}
|
||||
RtpHeader *header = (RtpHeader *)buf;
|
||||
return ((header->pt < 64) || (header->pt >= 96));
|
||||
return ((header->pt < 64) || (header->pt >= 96)) && header->version == RtpPacket::kRtpVersion;
|
||||
}
|
||||
|
||||
bool isRtcp(const char *buf, size_t size) {
|
||||
@ -470,12 +490,10 @@ Buffer::Ptr makeRtpOverTcpPrefix(uint16_t size, uint8_t interleaved) {
|
||||
return rtp_tcp;
|
||||
}
|
||||
|
||||
#define AV_RB16(x) \
|
||||
((((const uint8_t*)(x))[0] << 8) | \
|
||||
((const uint8_t*)(x))[1])
|
||||
#define AV_RB16(x) ((((const uint8_t *)(x))[0] << 8) | ((const uint8_t *)(x))[1])
|
||||
|
||||
size_t RtpHeader::getCsrcSize() const {
|
||||
//每个csrc占用4字节
|
||||
// 每个csrc占用4字节
|
||||
return csrc << 2;
|
||||
}
|
||||
|
||||
@ -487,18 +505,18 @@ uint8_t *RtpHeader::getCsrcData() {
|
||||
}
|
||||
|
||||
size_t RtpHeader::getExtSize() const {
|
||||
//rtp有ext
|
||||
// rtp有ext
|
||||
if (!ext) {
|
||||
return 0;
|
||||
}
|
||||
auto ext_ptr = &payload + getCsrcSize();
|
||||
//uint16_t reserved = AV_RB16(ext_ptr);
|
||||
//每个ext占用4字节
|
||||
// uint16_t reserved = AV_RB16(ext_ptr);
|
||||
// 每个ext占用4字节
|
||||
return AV_RB16(ext_ptr + 2) << 2;
|
||||
}
|
||||
|
||||
uint16_t RtpHeader::getExtReserved() const {
|
||||
//rtp有ext
|
||||
// rtp有ext
|
||||
if (!ext) {
|
||||
return 0;
|
||||
}
|
||||
@ -511,12 +529,12 @@ uint8_t *RtpHeader::getExtData() {
|
||||
return nullptr;
|
||||
}
|
||||
auto ext_ptr = &payload + getCsrcSize();
|
||||
//多出的4个字节分别为reserved、ext_len
|
||||
// 多出的4个字节分别为reserved、ext_len
|
||||
return ext_ptr + 4;
|
||||
}
|
||||
|
||||
size_t RtpHeader::getPayloadOffset() const {
|
||||
//有ext时,还需要忽略reserved、ext_len 4个字节
|
||||
// 有ext时,还需要忽略reserved、ext_len 4个字节
|
||||
return getCsrcSize() + (ext ? (4 + getExtSize()) : 0);
|
||||
}
|
||||
|
||||
@ -528,7 +546,7 @@ size_t RtpHeader::getPaddingSize(size_t rtp_size) const {
|
||||
if (!padding) {
|
||||
return 0;
|
||||
}
|
||||
auto end = (uint8_t *) this + rtp_size - 1;
|
||||
auto end = (uint8_t *)this + rtp_size - 1;
|
||||
return *end;
|
||||
}
|
||||
|
||||
@ -539,12 +557,12 @@ ssize_t RtpHeader::getPayloadSize(size_t rtp_size) const {
|
||||
|
||||
string RtpHeader::dumpString(size_t rtp_size) const {
|
||||
_StrPrinter printer;
|
||||
printer << "version:" << (int) version << "\r\n";
|
||||
printer << "version:" << (int)version << "\r\n";
|
||||
printer << "padding:" << getPaddingSize(rtp_size) << "\r\n";
|
||||
printer << "ext:" << getExtSize() << "\r\n";
|
||||
printer << "csrc:" << getCsrcSize() << "\r\n";
|
||||
printer << "mark:" << (int) mark << "\r\n";
|
||||
printer << "pt:" << (int) pt << "\r\n";
|
||||
printer << "mark:" << (int)mark << "\r\n";
|
||||
printer << "pt:" << (int)pt << "\r\n";
|
||||
printer << "seq:" << ntohs(seq) << "\r\n";
|
||||
printer << "stamp:" << ntohl(stamp) << "\r\n";
|
||||
printer << "ssrc:" << ntohl(ssrc) << "\r\n";
|
||||
@ -557,16 +575,16 @@ string RtpHeader::dumpString(size_t rtp_size) const {
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
RtpHeader *RtpPacket::getHeader() {
|
||||
//需除去rtcp over tcp 4个字节长度
|
||||
return (RtpHeader *) (data() + RtpPacket::kRtpTcpHeaderSize);
|
||||
// 需除去rtcp over tcp 4个字节长度
|
||||
return (RtpHeader *)(data() + RtpPacket::kRtpTcpHeaderSize);
|
||||
}
|
||||
|
||||
const RtpHeader *RtpPacket::getHeader() const {
|
||||
return (RtpHeader *) (data() + RtpPacket::kRtpTcpHeaderSize);
|
||||
return (RtpHeader *)(data() + RtpPacket::kRtpTcpHeaderSize);
|
||||
}
|
||||
|
||||
string RtpPacket::dumpString() const {
|
||||
return ((RtpPacket *) this)->getHeader()->dumpString(size() - RtpPacket::kRtpTcpHeaderSize);
|
||||
return ((RtpPacket *)this)->getHeader()->dumpString(size() - RtpPacket::kRtpTcpHeaderSize);
|
||||
}
|
||||
|
||||
uint16_t RtpPacket::getSeq() const {
|
||||
@ -590,7 +608,7 @@ uint8_t *RtpPacket::getPayload() {
|
||||
}
|
||||
|
||||
size_t RtpPacket::getPayloadSize() const {
|
||||
//需除去rtcp over tcp 4个字节长度
|
||||
// 需除去rtcp over tcp 4个字节长度
|
||||
return getHeader()->getPayloadSize(size() - kRtpTcpHeaderSize);
|
||||
}
|
||||
|
||||
@ -608,23 +626,22 @@ RtpPacket::Ptr RtpPacket::create() {
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构造title类型sdp
|
||||
* @param dur_sec rtsp点播时长,0代表直播,单位秒
|
||||
* @param header 自定义sdp描述
|
||||
* @param version sdp版本
|
||||
*/
|
||||
* 构造title类型sdp
|
||||
* @param dur_sec rtsp点播时长,0代表直播,单位秒
|
||||
* @param header 自定义sdp描述
|
||||
* @param version sdp版本
|
||||
*/
|
||||
|
||||
TitleSdp::TitleSdp(float dur_sec, const std::map<std::string, std::string>& header, int version) : Sdp(0, 0) {
|
||||
TitleSdp::TitleSdp(float dur_sec, const std::map<std::string, std::string> &header, int version)
|
||||
: Sdp(0, 0) {
|
||||
_printer << "v=" << version << "\r\n";
|
||||
|
||||
if (!header.empty()) {
|
||||
for (auto &pr : header) {
|
||||
_printer << pr.first << "=" << pr.second << "\r\n";
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
_printer << "o=- 0 0 IN IP4 0.0.0.0\r\n";
|
||||
_printer << "s=Streamed by " << kServerName << "\r\n";
|
||||
_printer << "c=IN IP4 0.0.0.0\r\n";
|
||||
@ -632,18 +649,17 @@ TitleSdp::TitleSdp(float dur_sec, const std::map<std::string, std::string>& head
|
||||
}
|
||||
|
||||
if (dur_sec <= 0) {
|
||||
//直播
|
||||
// 直播
|
||||
_printer << "a=range:npt=now-\r\n";
|
||||
}
|
||||
else {
|
||||
//点播
|
||||
} else {
|
||||
// 点播
|
||||
_dur_sec = dur_sec;
|
||||
_printer << "a=range:npt=0-" << dur_sec << "\r\n";
|
||||
}
|
||||
_printer << "a=control:*\r\n";
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
} // namespace mediakit
|
||||
|
||||
namespace toolkit {
|
||||
StatisticImp(mediakit::RtpPacket);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user