commit
af6c10083b
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -24,7 +24,11 @@
|
|||
##############################################################################
|
||||
|
||||
# jsoncpp
|
||||
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/src/lib_json JSONCPP_SRC_LIST)
|
||||
file(GLOB JSONCPP_SRC_LIST
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/include/json/*.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/src/lib_json/*.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/src/lib_json/*.h)
|
||||
|
||||
add_library(jsoncpp STATIC ${JSONCPP_SRC_LIST})
|
||||
target_compile_options(jsoncpp
|
||||
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
||||
|
|
@ -43,19 +47,14 @@ 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)
|
||||
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>"
|
||||
|
|
@ -68,19 +67,24 @@ if(ENABLE_MP4)
|
|||
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>")
|
||||
|
||||
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)
|
||||
|
|
@ -104,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 b30eeca034456417dfca72aa0d2258031013a5e6
|
||||
Subproject commit b11582c38e8dbbb8d93ca9ce33c9a0b0cd58f59a
|
||||
11
AUTHORS
11
AUTHORS
|
|
@ -74,3 +74,14 @@ WuPeng <wp@zafu.edu.cn>
|
|||
[朱如洪 ](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)
|
||||
|
|
@ -39,8 +39,10 @@ option(ENABLE_FAAC "Enable FAAC" OFF)
|
|||
option(ENABLE_FFMPEG "Enable FFmpeg" OFF)
|
||||
option(ENABLE_HLS "Enable HLS" ON)
|
||||
option(ENABLE_JEMALLOC_STATIC "Enable static linking to the jemalloc library" OFF)
|
||||
option(ENABLE_JEMALLOC_DUMP "Enable jemalloc to dump malloc statistics" 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 +202,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)
|
||||
|
|
@ -334,7 +336,11 @@ if(ENABLE_JEMALLOC_STATIC)
|
|||
if(NOT EXISTS ${DEP_ROOT_DIR})
|
||||
file(MAKE_DIRECTORY ${DEP_ROOT_DIR})
|
||||
endif()
|
||||
|
||||
if (ENABLE_JEMALLOC_DUMP)
|
||||
set(ENABLE_JEMALLOC_STAT ON)
|
||||
else ()
|
||||
set(ENABLE_JEMALLOC_STAT OFF)
|
||||
endif ()
|
||||
include(Jemalloc)
|
||||
include_directories(SYSTEM ${DEP_ROOT_DIR}/${JEMALLOC_NAME}/include/jemalloc)
|
||||
link_directories(${DEP_ROOT_DIR}/${JEMALLOC_NAME}/lib)
|
||||
|
|
@ -348,6 +354,12 @@ if(JEMALLOC_FOUND)
|
|||
message(STATUS "found library: ${JEMALLOC_LIBRARIES}")
|
||||
include_directories(${JEMALLOC_INCLUDE_DIR})
|
||||
update_cached_list(MK_LINK_LIBRARIES ${JEMALLOC_LIBRARIES})
|
||||
add_definitions(-DUSE_JEMALLOC)
|
||||
message(STATUS "jemalloc will be used to avoid memory fragmentation")
|
||||
if (ENABLE_JEMALLOC_DUMP)
|
||||
add_definitions(-DENABLE_JEMALLOC_DUMP)
|
||||
message(STATUS "jemalloc will save memory usage statistics when the program exits")
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
# 查找 openssl 是否安装
|
||||
|
|
@ -449,11 +461,6 @@ if(ENABLE_API)
|
|||
add_subdirectory(api)
|
||||
endif()
|
||||
|
||||
# IOS 不编译可执行程序
|
||||
if(IOS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
##############################################################################
|
||||
|
||||
if(ENABLE_PLAYER AND ENABLE_FFMPEG)
|
||||
|
|
@ -461,13 +468,20 @@ if(ENABLE_PLAYER AND ENABLE_FFMPEG)
|
|||
endif()
|
||||
|
||||
#MediaServer主程序
|
||||
add_subdirectory(server)
|
||||
if(ENABLE_SERVER)
|
||||
add_subdirectory(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()
|
||||
|
||||
# IOS 不编译可执行程序
|
||||
if(IOS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
#cpp测试demo程序
|
||||
if (ENABLE_TESTS)
|
||||
add_subdirectory(tests)
|
||||
|
|
|
|||
74
README.md
74
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这样的设备
|
||||
|
|
@ -63,14 +63,16 @@
|
|||
- RTMP[S] 发布服务器,支持录制发布流
|
||||
- RTMP[S] 播放器,支持RTMP代理,支持生成静音音频
|
||||
- RTMP[S] 推流客户端
|
||||
- 支持http[s]-flv直播
|
||||
- 支持http[s]-flv直播服务器
|
||||
- 支持http[s]-flv直播播放器
|
||||
- 支持websocket-flv直播
|
||||
- 支持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,29 +170,35 @@ bash build_docker_images.sh
|
|||
|
||||
## 合作项目
|
||||
|
||||
- 可视化管理网站
|
||||
- [最新的前后端分离web项目,支持webrtc播放](https://github.com/langmansh/AKStreamNVR)
|
||||
- [基于ZLMediaKit主线的管理WEB网站](https://gitee.com/kkkkk5G/MediaServerUI)
|
||||
- 视频管理平台
|
||||
- [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管理网站
|
||||
- [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)
|
||||
|
||||
- 流媒体管理平台
|
||||
- [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)
|
||||
- [基于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)
|
||||
|
||||
## 授权协议
|
||||
|
||||
|
|
@ -202,19 +210,20 @@ 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% />
|
||||
|
||||
|
||||
## 怎么提问?
|
||||
|
||||
如果要对项目有相关疑问,建议您这么做:
|
||||
|
||||
- 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).
|
||||
|
||||
## 特别感谢
|
||||
|
||||
|
|
@ -309,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开发与调试:
|
||||
|
||||
[](https://jb.gg/OpenSourceSupport)
|
||||
|
||||
## 使用案例
|
||||
|
||||
|
|
|
|||
25
README_en.md
25
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
|
||||
|
|
@ -62,14 +62,16 @@
|
|||
- RTMP[S] publishing server, supports recording and publishing streams
|
||||
- RTMP[S] player, supports RTMP proxy, supports generating silent audio
|
||||
- RTMP[S] push client
|
||||
- Supports http[s]-flv live streaming
|
||||
- Supports http[s]-flv live streaming server
|
||||
- Supports http[s]-flv live streaming player
|
||||
- Supports websocket-flv live streaming
|
||||
- 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
|
||||
|
|
@ -348,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)
|
||||
|
|
@ -362,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
|
||||
|
||||
|
|
@ -478,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:
|
||||
|
||||
[](https://jb.gg/OpenSourceSupport)
|
||||
|
||||
## Use Cases
|
||||
|
||||
|
|
|
|||
|
|
@ -30,13 +30,6 @@ file(GLOB API_SRC_LIST
|
|||
|
||||
set(LINK_LIBRARIES ${MK_LINK_LIBRARIES})
|
||||
|
||||
if(IOS)
|
||||
add_library(mk_api STATIC ${API_SRC_LIST})
|
||||
target_link_libraries(mk_api
|
||||
PRIVATE ${LINK_LIBRARIES})
|
||||
return()
|
||||
endif ()
|
||||
|
||||
set(COMPILE_DEFINITIONS ${MK_COMPILE_DEFINITIONS})
|
||||
|
||||
if (MSVC)
|
||||
|
|
@ -46,6 +39,8 @@ endif ()
|
|||
if(ENABLE_API_STATIC_LIB)
|
||||
add_library(mk_api STATIC ${API_SRC_LIST})
|
||||
list(APPEND COMPILE_DEFINITIONS MediaKitApi_STATIC)
|
||||
elseif(IOS)
|
||||
add_library(mk_api STATIC ${API_SRC_LIST})
|
||||
else()
|
||||
add_library(mk_api SHARED ${API_SRC_LIST})
|
||||
endif()
|
||||
|
|
@ -74,8 +69,6 @@ generate_export_header(mk_api
|
|||
STATIC_DEFINE MediaKitApi_STATIC
|
||||
EXPORT_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/mk_export.h")
|
||||
|
||||
add_subdirectory(tests)
|
||||
|
||||
file(GLOB API_HEADER_LIST include/*.h ${CMAKE_CURRENT_BINARY_DIR}/*.h)
|
||||
install(FILES ${API_HEADER_LIST}
|
||||
DESTINATION ${INSTALL_PATH_INCLUDE})
|
||||
|
|
@ -83,3 +76,12 @@ install(TARGETS mk_api
|
|||
ARCHIVE DESTINATION ${INSTALL_PATH_LIB}
|
||||
LIBRARY DESTINATION ${INSTALL_PATH_LIB}
|
||||
RUNTIME DESTINATION ${INSTALL_PATH_RUNTIME})
|
||||
|
||||
# IOS 跳过测试代码
|
||||
if(IOS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if (ENABLE_TESTS)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#define MK_EVENT_OBJECTS_H
|
||||
#include "mk_common.h"
|
||||
#include "mk_tcp.h"
|
||||
#include "mk_track.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
|
@ -95,6 +96,13 @@ API_EXPORT const char* API_CALL mk_media_source_get_stream(const mk_media_source
|
|||
API_EXPORT int API_CALL mk_media_source_get_reader_count(const mk_media_source ctx);
|
||||
//MediaSource::totalReaderCount()
|
||||
API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_source ctx);
|
||||
// get track count from MediaSource
|
||||
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());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,17 +86,17 @@ API_EXPORT const char* API_CALL mk_mp4_info_get_stream(const mk_mp4_info ctx){
|
|||
API_EXPORT const char* API_CALL mk_parser_get_method(const mk_parser ctx){
|
||||
assert(ctx);
|
||||
Parser *parser = (Parser *)ctx;
|
||||
return parser->Method().c_str();
|
||||
return parser->method().c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_parser_get_url(const mk_parser ctx){
|
||||
assert(ctx);
|
||||
Parser *parser = (Parser *)ctx;
|
||||
return parser->Url().c_str();
|
||||
return parser->url().c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_parser_get_url_params(const mk_parser ctx){
|
||||
assert(ctx);
|
||||
Parser *parser = (Parser *)ctx;
|
||||
return parser->Params().c_str();
|
||||
return parser->params().c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_parser_get_url_param(const mk_parser ctx,const char *key){
|
||||
assert(ctx && key);
|
||||
|
|
@ -106,7 +106,7 @@ API_EXPORT const char* API_CALL mk_parser_get_url_param(const mk_parser ctx,cons
|
|||
API_EXPORT const char* API_CALL mk_parser_get_tail(const mk_parser ctx){
|
||||
assert(ctx);
|
||||
Parser *parser = (Parser *)ctx;
|
||||
return parser->Tail().c_str();
|
||||
return parser->protocol().c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_parser_get_header(const mk_parser ctx,const char *key){
|
||||
assert(ctx && key);
|
||||
|
|
@ -117,9 +117,9 @@ API_EXPORT const char* API_CALL mk_parser_get_content(const mk_parser ctx, size_
|
|||
assert(ctx);
|
||||
Parser *parser = (Parser *)ctx;
|
||||
if(length){
|
||||
*length = parser->Content().size();
|
||||
*length = parser->content().size();
|
||||
}
|
||||
return parser->Content().c_str();
|
||||
return parser->content().c_str();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////MediaInfo/////////////////////////////////////////////
|
||||
|
|
@ -174,17 +174,17 @@ API_EXPORT const char* API_CALL mk_media_source_get_schema(const mk_media_source
|
|||
API_EXPORT const char* API_CALL mk_media_source_get_vhost(const mk_media_source ctx){
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
return src->getVhost().c_str();
|
||||
return src->getMediaTuple().vhost.c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_media_source_get_app(const mk_media_source ctx){
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
return src->getApp().c_str();
|
||||
return src->getMediaTuple().app.c_str();
|
||||
}
|
||||
API_EXPORT const char* API_CALL mk_media_source_get_stream(const mk_media_source ctx){
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
return src->getId().c_str();
|
||||
return src->getMediaTuple().stream.c_str();
|
||||
}
|
||||
API_EXPORT int API_CALL mk_media_source_get_reader_count(const mk_media_source ctx){
|
||||
assert(ctx);
|
||||
|
|
@ -198,6 +198,32 @@ API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_so
|
|||
return src->totalReaderCount();
|
||||
}
|
||||
|
||||
API_EXPORT int API_CALL mk_media_source_get_track_count(const mk_media_source ctx) {
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
return src->getTracks(false).size();
|
||||
}
|
||||
|
||||
API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx, int index) {
|
||||
assert(ctx);
|
||||
MediaSource *src = (MediaSource *)ctx;
|
||||
auto tracks = src->getTracks(false);
|
||||
if (index < 0 && index >= tracks.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
|
@ -108,7 +108,7 @@ API_EXPORT void API_CALL mk_http_requester_add_header(mk_http_requester ctx,cons
|
|||
API_EXPORT const char* API_CALL mk_http_requester_get_response_status(mk_http_requester ctx){
|
||||
assert(ctx);
|
||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
||||
return (*obj)->response().Url().c_str();
|
||||
return (*obj)->response().status().c_str();
|
||||
}
|
||||
|
||||
API_EXPORT const char* API_CALL mk_http_requester_get_response_header(mk_http_requester ctx,const char *key){
|
||||
|
|
@ -121,9 +121,9 @@ API_EXPORT const char* API_CALL mk_http_requester_get_response_body(mk_http_requ
|
|||
assert(ctx);
|
||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
||||
if(length){
|
||||
*length = (*obj)->response().Content().size();
|
||||
*length = (*obj)->response().content().size();
|
||||
}
|
||||
return (*obj)->response().Content().c_str();
|
||||
return (*obj)->response().content().c_str();
|
||||
}
|
||||
|
||||
API_EXPORT mk_parser API_CALL mk_http_requester_get_response(mk_http_requester ctx){
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,15 +43,3 @@ foreach(TEST_SRC ${TEST_SRC_LIST})
|
|||
target_link_libraries(${exe_name} mk_api)
|
||||
target_compile_options(${exe_name} PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
||||
endforeach()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "mk_mediakit.h"
|
||||
|
||||
typedef struct {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "mk_mediakit.h"
|
||||
#define LOG_LEV 4
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,34 @@
|
|||
# Download and build Jemalloc
|
||||
|
||||
set(JEMALLOC_VERSION 5.2.1)
|
||||
set(JEMALLOC_VERSION 5.3.0)
|
||||
set(JEMALLOC_NAME jemalloc-${JEMALLOC_VERSION})
|
||||
set(JEMALLOC_TAR_PATH ${DEP_ROOT_DIR}/${JEMALLOC_NAME}.tar.bz2)
|
||||
|
||||
list(APPEND jemalloc_CONFIG_ARGS --disable-initial-exec-tls)
|
||||
list(APPEND jemalloc_CONFIG_ARGS --without-export)
|
||||
#list(APPEND jemalloc_CONFIG_ARGS --without-export)
|
||||
if (ENABLE_JEMALLOC_STAT)
|
||||
list(APPEND jemalloc_CONFIG_ARGS --enable-stats)
|
||||
message(STATUS "Jemalloc stats enabled")
|
||||
else ()
|
||||
list(APPEND jemalloc_CONFIG_ARGS --disable-stats)
|
||||
message(STATUS "Jemalloc stats disabled")
|
||||
endif ()
|
||||
list(APPEND jemalloc_CONFIG_ARGS --disable-libdl)
|
||||
#list(APPEND jemalloc_CONFIG_ARGS --disable-cxx)
|
||||
#list(APPEND jemalloc_CONFIG_ARGS --with-jemalloc-prefix=je_)
|
||||
#list(APPEND jemalloc_CONFIG_ARGS --enable-debug)
|
||||
|
||||
if(NOT EXISTS ${JEMALLOC_TAR_PATH})
|
||||
message(STATUS "Downloading ${JEMALLOC_NAME}...")
|
||||
file(DOWNLOAD https://github.com/jemalloc/jemalloc/releases/download/${JEMALLOC_VERSION}/${JEMALLOC_NAME}.tar.bz2
|
||||
${JEMALLOC_TAR_PATH})
|
||||
set(JEMALLOC_URL https://github.com/jemalloc/jemalloc/releases/download/${JEMALLOC_VERSION}/${JEMALLOC_NAME}.tar.bz2)
|
||||
message(STATUS "Downloading ${JEMALLOC_NAME} from ${JEMALLOC_URL}")
|
||||
file(DOWNLOAD ${JEMALLOC_URL} ${JEMALLOC_TAR_PATH} SHOW_PROGRESS STATUS JEMALLOC_DOWNLOAD_STATUS LOG JEMALLOC_DOWNLOAD_LOG)
|
||||
list(GET JEMALLOC_DOWNLOAD_STATUS 0 JEMALLOC_DOWNLOAD_STATUS_CODE)
|
||||
if(NOT JEMALLOC_DOWNLOAD_STATUS_CODE EQUAL 0)
|
||||
file(REMOVE ${JEMALLOC_TAR_PATH})
|
||||
message(STATUS "${JEMALLOC_DOWNLOAD_LOG}")
|
||||
message(FATAL_ERROR "${JEMALLOC_NAME} download failed! error is ${JEMALLOC_DOWNLOAD_STATUS}")
|
||||
return()
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
SET( DIR_CONTAINING_JEMALLOC ${DEP_ROOT_DIR}/${JEMALLOC_NAME} )
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ bin=/usr/bin/ffmpeg
|
|||
#FFmpeg拉流再推流的命令模板,通过该模板可以设置再编码的一些参数
|
||||
cmd=%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s
|
||||
#FFmpeg生成截图的命令,可以通过修改该配置改变截图分辨率或质量
|
||||
snap=%s -i %s -y -f mjpeg -t 0.001 %s
|
||||
snap=%s -i %s -y -f mjpeg -frames:v 1 %s
|
||||
#FFmpeg日志的路径,如果置空则不生成FFmpeg日志
|
||||
#可以为相对(相对于本可执行程序目录)或绝对路径
|
||||
log=./ffmpeg/ffmpeg.log
|
||||
|
|
@ -32,18 +32,28 @@ restart_sec=0
|
|||
#转协议相关开关;如果addStreamProxy api和on_publish hook回复未指定转协议参数,则采用这些配置项
|
||||
[protocol]
|
||||
#转协议时,是否开启帧级时间戳覆盖
|
||||
modify_stamp=0
|
||||
# 0:采用源视频流绝对时间戳,不做任何改变
|
||||
# 1:采用zlmediakit接收数据时的系统时间戳(有平滑处理)
|
||||
# 2:采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正
|
||||
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
|
||||
|
|
@ -121,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
|
||||
|
|
@ -132,9 +142,6 @@ deleteDelaySec=10
|
|||
segKeep=0
|
||||
|
||||
[hook]
|
||||
#在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然
|
||||
#该配置项的目的是为了开发者自己调试测试,该参数暴露后会有泄露隐私的安全隐患
|
||||
admin_params=secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
|
||||
#是否启用hook事件,启用后,推拉流都将进行鉴权
|
||||
enable=0
|
||||
#播放器或推流器使用流量事件,置空则关闭
|
||||
|
|
@ -147,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
|
||||
|
|
@ -159,12 +166,16 @@ 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事件可以完成按需拉流
|
||||
on_stream_not_found=https://127.0.0.1/index/hook/on_stream_not_found
|
||||
#服务器启动报告,可以用于服务器的崩溃重启事件监听
|
||||
on_server_started=https://127.0.0.1/index/hook/on_server_started
|
||||
#服务器退出报告,当服务器正常退出时触发
|
||||
on_server_exited=https://127.0.0.1/index/hook/on_server_exited
|
||||
#server保活上报
|
||||
on_server_keepalive=https://127.0.0.1/index/hook/on_server_keepalive
|
||||
#发送rtp(startSendRtp)被动关闭时回调
|
||||
|
|
@ -232,6 +243,8 @@ forbidCacheSuffix=
|
|||
forwarded_ip_header=
|
||||
#默认允许所有跨域请求
|
||||
allow_cross_domains=1
|
||||
#允许访问http api和http文件索引的ip地址范围白名单,置空情况下不做限制
|
||||
allow_ip_range=::1,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地址
|
||||
|
|
@ -261,8 +274,6 @@ handshakeSecond=15
|
|||
#rtmp超时时间,如果该时间内未收到客户端的数据,
|
||||
#或者tcp发送缓存超过这个时间,则会断开连接,单位秒
|
||||
keepAliveSecond=15
|
||||
#在接收rtmp推流时,是否重新生成时间戳(很多推流器的时间戳着实很烂)
|
||||
modifyStamp=0
|
||||
#rtmp服务器监听端口
|
||||
port=1935
|
||||
#rtmps服务器监听地址
|
||||
|
|
@ -278,6 +289,9 @@ videoMtuSize=1400
|
|||
rtpMaxSize=10
|
||||
# rtp 打包时,低延迟开关,默认关闭(为0),h264存在一帧多个slice(NAL)的情况,在这种情况下,如果开启可能会导致画面花屏
|
||||
lowLatency=0
|
||||
# H264 rtp打包模式是否采用stap-a模式(为了在老版本浏览器上兼容webrtc)还是采用Single NAL unit packet per H.264 模式
|
||||
# 有些老的rtsp设备不支持stap-a rtp,设置此配置为0可提高兼容性
|
||||
h264_stap_a=1
|
||||
|
||||
[rtp_proxy]
|
||||
#导出调试数据(包括rtp/ps/h264)至该目录,置空则关闭数据导出
|
||||
|
|
@ -359,6 +373,10 @@ port=554
|
|||
sslport=0
|
||||
#rtsp 转发是否使用低延迟模式,当开启时,不会缓存rtp包,来提高并发,可以降低一帧的延迟
|
||||
lowLatency=0
|
||||
#强制协商rtp传输方式 (0:TCP,1:UDP,2:MULTICAST,-1:不限制)
|
||||
#当客户端发起RTSP SETUP的时候如果传输类型和此配置不一致则返回461 Unsupported transport
|
||||
#迫使客户端重新SETUP并切换到对应协议。目前支持FFMPEG和VLC
|
||||
rtpTransportType=-1
|
||||
[shell]
|
||||
#调试telnet服务器接受最大bufffer大小
|
||||
maxReqSize=1024
|
||||
|
|
|
|||
144
default.pem
144
default.pem
|
|
@ -1,89 +1,89 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAjTFnNz2btUHR/K8TegU2n06lXsQuW4AfsMGthBLvnz2zdgL2
|
||||
uVxfRCuleoRpECEV9I80ibTXAJlouRLbnhLHto8gzyLeQue9/DtxTEGcS7y2aMha
|
||||
0wAoUcOBv7tAITSnFepGHXoYgyU7HOCvn96U1bzqLTOhxOCH/xy003dwoEC7+Pjl
|
||||
dsWs/16cpvYiZV/dZvzDDKIpHSIvQy1whi6N0SDpzz/ncThn1z8xcJDx0I1jVR76
|
||||
juP7TttbyhqJkO+fOlLn4yP9K5wZ/dPFJn2+bQRCrzGc4SM7J5YOq8ckTTTbZtSp
|
||||
9yPpm7O5QyfdzePhOpRrjWroLsqaDOPV2UQlzwIDAQABAoIBADCWTh8P19vdnR3X
|
||||
v5uPXLcgkL7WQt+g7Qbd91CKVaRWTsHvDilGVNA4Ntc85oyy3gPNHfa/YPdnU0bQ
|
||||
6vtwGgLEKTWumY6rgdDhQcFMmLTlaV4QiFSw6q8MWMN6c/yZSmA7wMoXAIVs0/VB
|
||||
ip44sb4Fpw5MBMCjxZjwL3fP09WJPlUqx09vVo7eH8rFwLBikmn982IzRigAx1I8
|
||||
TX0wkdqvv33MSxBXPMQIrwPqjf2arxWFzb6vp6yolYbMZtgORF9gznWABRy3oY50
|
||||
9jFkTkbxZFlSMVuF7nlM0WJj5Q9/IelBqpozODWUVvB+6inCqkxNLkbh0ISbpXWC
|
||||
16gUZfUCgYEAxWo3FRNBrNXhVD5h2N4ApyUXkZ5UYIY5zbsHEJCrPjooh9uHu9kh
|
||||
xXh5v11J/7TV9BfwLZ4qRbDBH4fq0DKEOXOZRLY5Lo4KbrYmlEDCabuJdmwwHeGh
|
||||
S5K37F5z/+zPz9KWkKN+9Rg32xdLxh0969O77GnvuBrhzASpVsF6ZFMCgYEAtxf1
|
||||
eVg4Kxzuy0AWs+CisSVQc+5CbZ9teKA5fli2EVSmL5dsrKatVTIDghudJgQTU6cr
|
||||
zP9I20K11jeqIoK5saQXH3CzogN6aDuKssq4rDbvVSZ09Zry6N1WMz9GPe31zEYw
|
||||
sdU1w7vUw+l3unFfWOP4oZm0MH+na61V1YohCRUCgYANlp0J/1RS8DndUZnskoNa
|
||||
/eucY1iNeE+8QHZhBoQy+U/W4h56qJxxejRvHp28UxczAP7QNQXV3C++2t0nzYJa
|
||||
bgGLwDs5YB+JtVH8fGSlYHo6w4GgXOp8SDIOvAWiBQvc0zL367kOZ8dYdkcJ8PNV
|
||||
KzLROA1/D6KhJ2T8ir7A7wKBgQCjVVxGw8xXqZfc+W9HSD3aic8bnJDl+jNOSKEB
|
||||
dWH2U+1sx0jLPGWketlmV/v4zenv1lHcrl/wObK9RysfXj8JmbiG86NMBI5OLc+t
|
||||
b+sOtnMLIyNzdqb71Xfwf6HJ3V5IvNTzz6AG3KkRnFSSnlDQm45RmyyDl11jUV4h
|
||||
APg3gQKBgBzFeuKWnaTZz1FQBr5Ytl9gtxBRMl+49jtkqyzErJYFHe0MTWeD/1xj
|
||||
mEC/7UERYWhIQF1L4ah6c0QkecR3F1s9/IYK/QHsnSJFwRyFuMas6StCERsDq5oQ
|
||||
GWpXAmw7JTa8OYwxVjORdXY25Iwv6rEr6iUYBWZrkhoWYBySWpSZ
|
||||
MIIEowIBAAKCAQEAtgg0vsIc8bIMb9hAO1Lwq2TVNYMiD928WCVwq7Bj3B9LM8ci
|
||||
fN+mBxHz35SeC9JnQysbKAf8VVfVi7GVzwFq4VaRreoIl8DoMbBORb2iKxfLXKEH
|
||||
S283bbl17csAYHTTBqFsKnO3kKvYoDHRKFX0T0rzZibI0ACQiylW+ALE3fVS0hFI
|
||||
8fmoZnIAOyQ15RLp7KPO4bMRVdZaOqRUZeW0YzbvOBeGZKbx3bXiscL9RwfGSxlF
|
||||
MCLahaJsBrVDo4/IGDRCmSydK8k/41MHd86pGPzCixBx7cqtKO/6+cVeEuGxfJPf
|
||||
JVuxNq6vkRSt2HxCc6i2iagA2uL/WYdg3Pa/4wIDAQABAoIBAADFrCObAzBrRu46
|
||||
hps50NeJR/ZAJibXE/NzxTSVPPc0EseXcqgA8t1Y0CYEpV77d4CrcCQNVJ6wDrHX
|
||||
AQGtydxG17tbIMo0AUgkrVBSa5uvMCembzd8s0l93egyUkAWfsaqbKEJeJ/eer7D
|
||||
N1Xqd2zWro2iYHuxZOuSM1I+AMPIQsmYJ71w6/h9YpQh436Vd+zNQ5k/nWpLHihT
|
||||
VB2ECrJ36IbuiYo3UbSr9gQjyBSMkk/oUqO4jonkb6L7r0mqHXNeblycg99/m6i7
|
||||
O5c5DQKMhzqibwvNNf6uvWCcLKfF5Kqzzf9DKR3/pYOBQrVTA24l4UFsfTdEKUNS
|
||||
a8W3P8ECgYEA6CQOG15V9upc2nPzfFwgftGyomSMYH54PkSFdr2R4djyXkyil6Ik
|
||||
efK3E+lKr9YnzwcLw3csPmVt3lqSgixQUMcyXXrhCttfk/qzSJkI+UZPQE+SrNeW
|
||||
0c+blQOzVcfbNRu248iGFaRx+5qA6PMH4UZTgn7e6nXoPUgRp4ryI/MCgYEAyL24
|
||||
R7uMSuPQBRJFU84Lu+Rv4lkKdCYSLuQtMZly74m11iG6e+EHJQx0C3eexrC8LhOV
|
||||
Sm4xTlwVrYQ+IdW51bhAwwHcnzGUzpbESJSDK5ZTd/P5daz8yt8ZaGbUFxNEsxTr
|
||||
ElKPRcjJH5CRuyYr24DYg+CpMGdlF0N6Pcx5IFECgYAedlzDiqWNOUPmBsE02IIL
|
||||
IklmtfsVzoLI6QT6h/XUxTtI1JWhgE15EzijDEIYwOmIaUxJ4iGULos0Wn5PRrFj
|
||||
aEBbs/xECHWKXaOZKzvaOje8ILUGqWPJNI0eCNZHs2o4leJyEaZGwMWUVroD16B5
|
||||
F1luDmgCLGbFY+etLLaJsQKBgB40VbcNZDWcg59PuXi7pw5Vd/RB243QcKn3kUlG
|
||||
QoICYYbfulSLbmzHq+pRzGUvEJGKRstVOzwEJQrfvA2RQA4FVFFDRXP6nN5c1xno
|
||||
prf3PYXuAtoO9lZ8LTGFT2JNdufPPPOb0oz4gjKqqRLU0oKLp4hoVGzBEffnIkyM
|
||||
KKmRAoGBAIGXh4gvxzEQMgGzfKfNuxKCT9SEhsg7NU++Iey3qn4G4t+jIWOt2Gi7
|
||||
5+y49JWoGq6DL+2ZVVw6Cn6wd9tfzDKD5GhvIztK0z1+wqpFOL4M8bwqJDOKgsZ3
|
||||
PCPASbxPgMyNCjRhvxBuscCr+dRFYDUrirOK9EUPyO9EoNTPPN9a
|
||||
-----END RSA PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGAjCCBOqgAwIBAgIQAiXv68Xco/vd9YeB4g3HLjANBgkqhkiG9w0BAQsFADBu
|
||||
MIIGBTCCBO2gAwIBAgIQDNIYeWoFoT3jxF2+HmEbTDANBgkqhkiG9w0BAQsFADBu
|
||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||
d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
|
||||
RFYgVExTIENBIC0gRzEwHhcNMjIwOTE4MDAwMDAwWhcNMjMwOTE4MjM1OTU5WjAh
|
||||
RFYgVExTIENBIC0gRzIwHhcNMjMwOTI4MDAwMDAwWhcNMjQwOTI3MjM1OTU5WjAh
|
||||
MR8wHQYDVQQDExZkZWZhdWx0LnpsbWVkaWFraXQuY29tMIIBIjANBgkqhkiG9w0B
|
||||
AQEFAAOCAQ8AMIIBCgKCAQEAjTFnNz2btUHR/K8TegU2n06lXsQuW4AfsMGthBLv
|
||||
nz2zdgL2uVxfRCuleoRpECEV9I80ibTXAJlouRLbnhLHto8gzyLeQue9/DtxTEGc
|
||||
S7y2aMha0wAoUcOBv7tAITSnFepGHXoYgyU7HOCvn96U1bzqLTOhxOCH/xy003dw
|
||||
oEC7+PjldsWs/16cpvYiZV/dZvzDDKIpHSIvQy1whi6N0SDpzz/ncThn1z8xcJDx
|
||||
0I1jVR76juP7TttbyhqJkO+fOlLn4yP9K5wZ/dPFJn2+bQRCrzGc4SM7J5YOq8ck
|
||||
TTTbZtSp9yPpm7O5QyfdzePhOpRrjWroLsqaDOPV2UQlzwIDAQABo4IC5zCCAuMw
|
||||
HwYDVR0jBBgwFoAUVXRPsnJP9WC6UNHX5lFcmgGHGtcwHQYDVR0OBBYEFPnRZrfz
|
||||
q/QAf5u4Xp4eGWvhMdvfMCEGA1UdEQQaMBiCFmRlZmF1bHQuemxtZWRpYWtpdC5j
|
||||
b20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
|
||||
AjA+BgNVHSAENzA1MDMGBmeBDAECATApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3
|
||||
LmRpZ2ljZXJ0LmNvbS9DUFMwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY
|
||||
AQEFAAOCAQ8AMIIBCgKCAQEAtgg0vsIc8bIMb9hAO1Lwq2TVNYMiD928WCVwq7Bj
|
||||
3B9LM8cifN+mBxHz35SeC9JnQysbKAf8VVfVi7GVzwFq4VaRreoIl8DoMbBORb2i
|
||||
KxfLXKEHS283bbl17csAYHTTBqFsKnO3kKvYoDHRKFX0T0rzZibI0ACQiylW+ALE
|
||||
3fVS0hFI8fmoZnIAOyQ15RLp7KPO4bMRVdZaOqRUZeW0YzbvOBeGZKbx3bXiscL9
|
||||
RwfGSxlFMCLahaJsBrVDo4/IGDRCmSydK8k/41MHd86pGPzCixBx7cqtKO/6+cVe
|
||||
EuGxfJPfJVuxNq6vkRSt2HxCc6i2iagA2uL/WYdg3Pa/4wIDAQABo4IC6jCCAuYw
|
||||
HwYDVR0jBBgwFoAUeN+RkF/u3qz2xXXr1UxVU+8kSrYwHQYDVR0OBBYEFHmEMVp9
|
||||
9EHIPWA2U1iLKogCosGFMCEGA1UdEQQaMBiCFmRlZmF1bHQuemxtZWRpYWtpdC5j
|
||||
b20wPgYDVR0gBDcwNTAzBgZngQwBAgEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3
|
||||
dy5kaWdpY2VydC5jb20vQ1BTMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggr
|
||||
BgEFBQcDAQYIKwYBBQUHAwIwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY
|
||||
aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEoGCCsGAQUFBzAChj5odHRwOi8vY2Fj
|
||||
ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcx
|
||||
LmNydDAJBgNVHRMEAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgDoPtDa
|
||||
PvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAAAYNQt3JvAAAEAwBHMEUCIEaO
|
||||
G4ffzzaE6OMqiu6PUr+Y+wO2tsXCkGt1jt04Ix1qAiEAhNZwqFACieds1ZbY3r/p
|
||||
wlF3iFbhqp+kNfPzon7kwc8AdgA1zxkbv7FsV78PrUxtQsu7ticgJlHqP+Eq76gD
|
||||
wzvWTAAAAYNQt3JVAAAEAwBHMEUCIBOErqyKvihAEKItLWG/Plgtxh/hCTMsE+t5
|
||||
+MfsAQLCAiEA76d50S4iy1wxya+8IUASVlKStaHNqBkJAS+Oadxs2sMAdwCzc3cH
|
||||
4YRQ+GOG1gWp3BEJSnktsWcMC4fc8AMOeTalmgAAAYNQt3LIAAAEAwBIMEYCIQC/
|
||||
kfFCpwF76sw/Qx3sxR8b3srW+Ds0k/6VrIIDZcYV5gIhAKkLmuyeDvzulp0y4f0t
|
||||
GDgIN/OoURq6CuHA67UJlsWzMA0GCSqGSIb3DQEBCwUAA4IBAQB0BwVxPRihSdPJ
|
||||
FUPLQ+ClHy9O/UisnRD7NadQQtbcMXn6L9Lwd0f2la0ytLQAKHADOZDA08KfQ5qW
|
||||
B19OeQOlTwp2nhY2ZvoLEG+paeh0gYxIgD76APnd/m3g2H7GeW144ymjPcZRoldj
|
||||
ZKYSdzStJJIFYXzL3FR9wjkMc4xOEes/IY5PFtj8OT8CFf7zl0R7L2Vcw9RGYi9u
|
||||
vLjGwwJW9kXTX8UlKXFyjJN0ZyrmxBQHq5uNtigx8xy6HtMnPsc58tp1IqitIELp
|
||||
HIur2XrRPBJA5XtpDg3AE8bXhRTM8oFMPL0UoSFWyWRYGgBo1Msc10dpXPtmbgIc
|
||||
pPW8w+2c
|
||||
ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcy
|
||||
LmNydDAMBgNVHRMBAf8EAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgDu
|
||||
zdBk1dsazsVct520zROiModGfLzs3sNRSFlGcR+1mwAAAYravqonAAAEAwBHMEUC
|
||||
IQDX+gqsd7I0yzjkhgp2YrccUlTx4wkFptFvmQxeChImRgIgJdgJa2Uamd790BCI
|
||||
/CZwSqmRlor5eU8exAixdcopYpcAdwBIsONr2qZHNA/lagL6nTDrHFIBy1bdLIHZ
|
||||
u7+rOdiEcwAAAYravqqCAAAEAwBIMEYCIQCP6rkKg2FlF92CyMbVMk3ESh/9gVaM
|
||||
tRsv5I//i5IVigIhAINHERhy7812wR47fwmvqWDjxyOB1ZodU7WA9D5L/1bVAHYA
|
||||
2ra/az+1tiKfm8K7XGvocJFxbLtRhIU0vaQ9MEjX+6sAAAGK2r6qQQAABAMARzBF
|
||||
AiAiz3bp/j4SlnVxKg1HZY+YdUboi+kaKf5G8X6aFLIqUgIhAPPCm5UN05p7Oqrc
|
||||
sP/wdHDB7O/2AbUksYSLhidmwfmhMA0GCSqGSIb3DQEBCwUAA4IBAQBmaG51jU1E
|
||||
MsgT1VzutQUXglEvJGVf54cA+0TSfjfnP1n9ALdKjGxHL3KBh4UkPx5zdE5//FUX
|
||||
dacua6BQEWSCmMtYL0CFieFnLGXh0mgkfvRaP6+3xe6TkJ4kuyJkMS9YMDpVl80F
|
||||
2GLlE09EsZ3Xk9+SCpmWOPLOCDFURbwpc5ht+acROfzYJQyCY0L8EGbyL5/q9oMn
|
||||
ugRGh4oyGvXgKvFIPzpZkaOmb0b63/uBc5JkiyQhuFdYaS2cLOwupXmCtIHL4Od6
|
||||
OU8/8smT8NEkD7d3lUijtc84q2TihW7ebT7RtOco49PDvFP/7w28QjxM8Ohv9/Gz
|
||||
Xyta8ICQVwmK
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh
|
||||
MIIEqjCCA5KgAwIBAgIQDeD/te5iy2EQn2CMnO1e0zANBgkqhkiG9w0BAQsFADBh
|
||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
|
||||
QTAeFw0xNzExMjcxMjQ2MTBaFw0yNzExMjcxMjQ2MTBaMG4xCzAJBgNVBAYTAlVT
|
||||
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
|
||||
MjAeFw0xNzExMjcxMjQ2NDBaFw0yNzExMjcxMjQ2NDBaMG4xCzAJBgNVBAYTAlVT
|
||||
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
|
||||
b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH
|
||||
MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPeP6wkab41dyQh6mKc
|
||||
oHqt3jRIxW5MDvf9QyiOR7VfFwK656es0UFiIb74N9pRntzF1UgYzDGu3ppZVMdo
|
||||
lbxhm6dWS9OK/lFehKNT0OYI9aqk6F+U7cA6jxSC+iDBPXwdF4rs3KRyp3aQn6pj
|
||||
pp1yr7IB6Y4zv72Ee/PlZ/6rK6InC6WpK0nPVOYR7n9iDuPe1E4IxUMBH/T33+3h
|
||||
yuH3dvfgiWUOUkjdpMbyxX+XNle5uEIiyBsi4IvbcTCh8ruifCIi5mDXkZrnMT8n
|
||||
wfYCV6v6kDdXkbgGRLKsR4pucbJtbKqIkUGxuZI2t7pfewKRc5nWecvDBZf3+p1M
|
||||
pA8CAwEAAaOCAU8wggFLMB0GA1UdDgQWBBRVdE+yck/1YLpQ0dfmUVyaAYca1zAf
|
||||
BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYw
|
||||
MjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO8Uf46i/nr7pkgTDqnE
|
||||
eSIfCFqvPnUq3aF1tMJ5hh9MnO6Lmt5UdHfBGwC9Si+XjK12cjZgxObsL6Rg1njv
|
||||
NhAMJ4JunN0JGGRJGSevbJsA3sc68nbPQzuKp5Jc8vpryp2mts38pSCXorPR+sch
|
||||
QisKA7OSQ1MjcFN0d7tbrceWFNbzgL2csJVQeogOBGSe/KZEIZw6gXLKeFe7mupn
|
||||
NYJROi2iC11+HuF79iAttMc32Cv6UOxixY/3ZV+LzpLnklFq98XORgwkIJL1HuvP
|
||||
ha8yvb+W6JislZJL+HLFtidoxmI7Qm3ZyIV66W533DsGFimFJkz3y0GeHWuSVMbI
|
||||
lfsCAwEAAaOCAU8wggFLMB0GA1UdDgQWBBR435GQX+7erPbFdevVTFVT7yRKtjAf
|
||||
BgNVHSMEGDAWgBROIlQgGJXm427mD/r6uRLtBhePOTAOBgNVHQ8BAf8EBAMCAYYw
|
||||
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C
|
||||
AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
|
||||
Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu
|
||||
Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG
|
||||
Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG
|
||||
/WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT
|
||||
MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAK3Gp6/aGq7aBZsxf/oQ+TD/B
|
||||
SwW3AU4ETK+GQf2kFzYZkby5SFrHdPomunx2HBzViUchGoofGgg7gHW0W3MlQAXW
|
||||
M0r5LUvStcr82QDWYNPaUy4taCQmyaJ+VB+6wxHstSigOlSNF2a6vg4rgexixeiV
|
||||
4YSB03Yqp2t3TeZHM9ESfkus74nQyW7pRGezj+TC44xCagCQQOzzNmzEAP2SnCrJ
|
||||
sNE2DpRVMnL8J6xBRdjmOsC3N6cQuKuRXbzByVBjCqAA8t1L0I+9wXJerLPyErjy
|
||||
rMKWaBFLmfK/AHNF4ZihwPGOc7w6UHczBZXH5RFzJNnww+WnKuTPI0HfnVH8lg==
|
||||
MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAoBs1eCLKakLtVRPFRjBIJ9LJ
|
||||
L0s8ZWum8U8/1TMVkQMBn+CPb5xnCD0GSA6L/V0ZFrMNqBirrr5B241OesECvxIi
|
||||
98bZ90h9+q/X5eMyOD35f8YTaEMpdnQCnawIwiHx06/0BfiTj+b/XQih+mqt3ZXe
|
||||
xNCJqKexdiB2IWGSKcgahPacWkk/BAQFisKIFYEqHzV974S3FAz/8LIfD58xnsEN
|
||||
GfzyIDkH3JrwYZ8caPTf6ZX9M1GrISN8HnWTtdNCH2xEajRa/h9ZBXjUyFKQrGk2
|
||||
n2hcLrfZSbynEC/pSw/ET7H5nWwckjmAJ1l9fcnbqkU/pf6uMQmnfl0JQjJNSg==
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -56,8 +56,7 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
if (argc < 3) {
|
||||
ErrorL << "\r\n测试方法:./test_player rtxp_url rtp_type\r\n"
|
||||
<< "例如:./test_player rtsp://admin:123456@127.0.0.1/live/0 0\r\n"
|
||||
<< endl;
|
||||
<< "例如:./test_player rtsp://admin:123456@127.0.0.1/live/0 0\r\n";
|
||||
return 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
|
||||
},
|
||||
{
|
||||
|
|
@ -582,7 +588,13 @@
|
|||
{
|
||||
"key": "modify_stamp",
|
||||
"value": null,
|
||||
"description": "是否重新计算时间戳",
|
||||
"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,11 +34,17 @@ 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()
|
||||
|
||||
add_executable(MediaServer ${MediaServer_SRC_LIST})
|
||||
# IOS 不编译可执行程序,只做依赖库
|
||||
if(IOS)
|
||||
add_library(MediaServer STATIC ${MediaServer_SRC_LIST})
|
||||
else()
|
||||
add_executable(MediaServer ${MediaServer_SRC_LIST})
|
||||
endif()
|
||||
|
||||
target_compile_definitions(MediaServer
|
||||
PRIVATE ${COMPILE_DEFINITIONS})
|
||||
target_compile_options(MediaServer
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -39,7 +40,7 @@ onceToken token([]() {
|
|||
//ffmpeg日志保存路径
|
||||
mINI::Instance()[kLog] = "./ffmpeg/ffmpeg.log";
|
||||
mINI::Instance()[kCmd] = "%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s";
|
||||
mINI::Instance()[kSnap] = "%s -i %s -y -f mjpeg -t 0.001 %s";
|
||||
mINI::Instance()[kSnap] = "%s -i %s -y -f mjpeg -frames:v 1 %s";
|
||||
mINI::Instance()[kRestartSec] = 0;
|
||||
});
|
||||
}
|
||||
|
|
@ -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,122 +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 ||
|
||||
sender.getVhost() != strongSelf->_media_info.vhost ||
|
||||
sender.getApp() != strongSelf->_media_info.app ||
|
||||
sender.getId() != strongSelf->_media_info.stream) {
|
||||
//不是自己感兴趣的事件,忽略之
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -224,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);
|
||||
});
|
||||
}
|
||||
|
|
@ -296,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);
|
||||
}
|
||||
|
|
@ -320,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;
|
||||
}
|
||||
|
|
@ -348,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;
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ static int cloneFunc(void *ptr) {
|
|||
|
||||
#endif
|
||||
|
||||
void Process::run(const string &cmd, string &log_file) {
|
||||
void Process::run(const string &cmd, string log_file) {
|
||||
kill(2000);
|
||||
#ifdef _WIN32
|
||||
STARTUPINFO si = { 0 };
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class Process {
|
|||
public:
|
||||
Process();
|
||||
~Process();
|
||||
void run(const std::string &cmd, std::string &log_file);
|
||||
void run(const std::string &cmd, std::string log_file);
|
||||
void kill(int max_delay,bool force = false);
|
||||
bool wait(bool block = true);
|
||||
int exit_code();
|
||||
|
|
|
|||
|
|
@ -22,10 +22,11 @@
|
|||
#include <map>
|
||||
#include <iostream>
|
||||
|
||||
#include "Common/JemallocUtil.h"
|
||||
#include "Common/macros.h"
|
||||
#include "System.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/uv_errno.h"
|
||||
#include "System.h"
|
||||
#include "Common/macros.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
|
@ -55,6 +56,16 @@ string System::execute(const string &cmd) {
|
|||
|
||||
static constexpr int MAX_STACK_FRAMES = 128;
|
||||
|
||||
static void save_jemalloc_stats() {
|
||||
string jemalloc_status = JemallocUtil::get_malloc_stats();
|
||||
if (jemalloc_status.empty()) {
|
||||
return;
|
||||
}
|
||||
ofstream out(StrPrinter << exeDir() << "/jemalloc.json", ios::out | ios::binary | ios::trunc);
|
||||
out << jemalloc_status;
|
||||
out.flush();
|
||||
}
|
||||
|
||||
static void sig_crash(int sig) {
|
||||
signal(sig, SIG_DFL);
|
||||
void *array[MAX_STACK_FRAMES];
|
||||
|
|
@ -126,6 +137,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) {
|
||||
|
|
@ -143,6 +160,12 @@ void System::startDaemon(bool &kill_parent_if_failed) {
|
|||
}
|
||||
|
||||
void System::systemSetup(){
|
||||
|
||||
#ifdef ENABLE_JEMALLOC_DUMP
|
||||
//Save memory report when program exits
|
||||
atexit(save_jemalloc_stats);
|
||||
#endif //ENABLE_JEMALLOC_DUMP
|
||||
|
||||
#if !defined(_WIN32)
|
||||
struct rlimit rlim,rlim_new;
|
||||
if (getrlimit(RLIMIT_CORE, &rlim)==0) {
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ static HttpApi toApi(const function<void(API_ARGS_JSON_ASYNC)> &cb) {
|
|||
//参数解析成json对象然后处理
|
||||
Json::Value args;
|
||||
Json::Reader reader;
|
||||
reader.parse(parser.Content(), args);
|
||||
reader.parse(parser.content(), args);
|
||||
|
||||
cb(sender, headerOut, HttpAllArgs<decltype(args)>(parser, args), val, invoker);
|
||||
};
|
||||
|
|
@ -152,7 +152,7 @@ static HttpApi toApi(const function<void(API_ARGS_STRING_ASYNC)> &cb) {
|
|||
Json::Value val;
|
||||
val["code"] = API::Success;
|
||||
|
||||
cb(sender, headerOut, HttpAllArgs<string>(parser, (string &)parser.Content()), val, invoker);
|
||||
cb(sender, headerOut, HttpAllArgs<string>(parser, (string &)parser.content()), val, invoker);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -191,13 +191,13 @@ void api_regist(const string &api_path, const function<void(API_ARGS_STRING_ASYN
|
|||
static ApiArgsType getAllArgs(const Parser &parser) {
|
||||
ApiArgsType allArgs;
|
||||
if (parser["Content-Type"].find("application/x-www-form-urlencoded") == 0) {
|
||||
auto contentArgs = parser.parseArgs(parser.Content());
|
||||
auto contentArgs = parser.parseArgs(parser.content());
|
||||
for (auto &pr : contentArgs) {
|
||||
allArgs[pr.first] = HttpSession::urlDecode(pr.second);
|
||||
}
|
||||
} else if (parser["Content-Type"].find("application/json") == 0) {
|
||||
try {
|
||||
stringstream ss(parser.Content());
|
||||
stringstream ss(parser.content());
|
||||
Value jsonArgs;
|
||||
ss >> jsonArgs;
|
||||
auto keys = jsonArgs.getMemberNames();
|
||||
|
|
@ -231,7 +231,7 @@ static inline void addHttpListener(){
|
|||
GET_CONFIG(bool, api_debug, API::kApiDebug);
|
||||
//注册监听kBroadcastHttpRequest事件
|
||||
NoticeCenter::Instance().addListener(&web_api_tag, Broadcast::kBroadcastHttpRequest, [](BroadcastHttpRequestArgs) {
|
||||
auto it = s_map_api.find(parser.Url());
|
||||
auto it = s_map_api.find(parser.url());
|
||||
if (it == s_map_api.end()) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -247,15 +247,15 @@ static inline void addHttpListener(){
|
|||
size = body->remainSize();
|
||||
}
|
||||
|
||||
LogContextCapture log(getLogger(), toolkit::LTrace, __FILE__, "http api debug", __LINE__);
|
||||
log << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n";
|
||||
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";
|
||||
|
||||
for (auto &pr : parser.getHeader()) {
|
||||
log << pr.first << " : " << pr.second << "\r\n";
|
||||
}
|
||||
|
||||
auto &content = parser.Content();
|
||||
auto &content = parser.content();
|
||||
log << "# content:\r\n" << (content.size() > 4 * 1024 ? content.substr(0, 4 * 1024) : content) << "\r\n";
|
||||
|
||||
if (size > 0 && size < 4 * 1024) {
|
||||
|
|
@ -321,12 +321,16 @@ static void fillSockInfo(Value& val, SockInfo* info) {
|
|||
val["identifier"] = info->getIdentifier();
|
||||
}
|
||||
|
||||
void dumpMediaTuple(const MediaTuple &tuple, Json::Value& item) {
|
||||
item[VHOST_KEY] = tuple.vhost;
|
||||
item["app"] = tuple.app;
|
||||
item["stream"] = tuple.stream;
|
||||
}
|
||||
|
||||
Value makeMediaSourceJson(MediaSource &media){
|
||||
Value item;
|
||||
item["schema"] = media.getSchema();
|
||||
item[VHOST_KEY] = media.getVhost();
|
||||
item["app"] = media.getApp();
|
||||
item["stream"] = media.getId();
|
||||
dumpMediaTuple(media.getMediaTuple(), item);
|
||||
item["createStamp"] = (Json::UInt64) media.getCreateStamp();
|
||||
item["aliveSecond"] = (Json::UInt64) media.getAliveSecond();
|
||||
item["bytesSpeed"] = media.getBytesSpeed();
|
||||
|
|
@ -533,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;
|
||||
}
|
||||
//添加拉流代理
|
||||
|
|
@ -584,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();
|
||||
|
|
@ -602,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();
|
||||
|
|
@ -648,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;
|
||||
}
|
||||
|
|
@ -656,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;
|
||||
|
|
@ -785,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();
|
||||
|
|
@ -1563,7 +1588,7 @@ void installWebApi() {
|
|||
}
|
||||
|
||||
//找到截图
|
||||
auto tm = FindField(path.data() + scan_path.size(), nullptr, ".jpeg");
|
||||
auto tm = findSubString(path.data() + scan_path.size(), nullptr, ".jpeg");
|
||||
if (atoll(tm.data()) + expire_sec < time(NULL)) {
|
||||
//截图已经过期,改名,以便再次请求时,可以返回老截图
|
||||
rename(path.data(), new_snap.data());
|
||||
|
|
@ -1637,7 +1662,7 @@ void installWebApi() {
|
|||
CHECK_ARGS("app", "stream");
|
||||
|
||||
return StrPrinter << RTC_SCHEMA << "://" << _args["Host"] << "/" << _args["app"] << "/"
|
||||
<< _args["stream"] << "?" << _args.getParser().Params() + "&session=" + _session_id;
|
||||
<< _args["stream"] << "?" << _args.getParser().params() + "&session=" + _session_id;
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
@ -1700,7 +1725,7 @@ void installWebApi() {
|
|||
|
||||
api_regist(delete_webrtc_url, [](API_ARGS_MAP_ASYNC) {
|
||||
CHECK_ARGS("id", "token");
|
||||
CHECK(allArgs.getParser().Method() == "DELETE", "http method is not DELETE: " + allArgs.getParser().Method());
|
||||
CHECK(allArgs.getParser().method() == "DELETE", "http method is not DELETE: " + allArgs.getParser().method());
|
||||
auto obj = WebRtcTransportManager::Instance().getItem(allArgs["id"]);
|
||||
if (!obj) {
|
||||
invoker(404, headerOut, "id not found");
|
||||
|
|
@ -1886,24 +1911,28 @@ void installWebApi() {
|
|||
void unInstallWebApi(){
|
||||
{
|
||||
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
||||
s_proxyMap.clear();
|
||||
auto proxyMap(std::move(s_proxyMap));
|
||||
proxyMap.clear();
|
||||
}
|
||||
|
||||
{
|
||||
lock_guard<recursive_mutex> lck(s_ffmpegMapMtx);
|
||||
s_ffmpegMap.clear();
|
||||
auto ffmpegMap(std::move(s_ffmpegMap));
|
||||
ffmpegMap.clear();
|
||||
}
|
||||
|
||||
{
|
||||
lock_guard<recursive_mutex> lck(s_proxyPusherMapMtx);
|
||||
s_proxyPusherMap.clear();
|
||||
auto proxyPusherMap(std::move(s_proxyPusherMap));
|
||||
proxyPusherMap.clear();
|
||||
}
|
||||
|
||||
{
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
RtpSelector::Instance().clear();
|
||||
lock_guard<recursive_mutex> lck(s_rtpServerMapMtx);
|
||||
s_rtpServerMap.clear();
|
||||
auto rtpServerMap(std::move(s_rtpServerMap));
|
||||
rtpServerMap.clear();
|
||||
#endif
|
||||
}
|
||||
NoticeCenter::Instance().delListener(&web_api_tag);
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
@ -44,10 +45,10 @@ const string kOnShellLogin = HOOK_FIELD "on_shell_login";
|
|||
const string kOnStreamNoneReader = HOOK_FIELD "on_stream_none_reader";
|
||||
const string kOnHttpAccess = HOOK_FIELD "on_http_access";
|
||||
const string kOnServerStarted = HOOK_FIELD "on_server_started";
|
||||
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";
|
||||
|
|
@ -69,13 +70,14 @@ static onceToken token([]() {
|
|||
mINI::Instance()[kOnStreamNoneReader] = "";
|
||||
mINI::Instance()[kOnHttpAccess] = "";
|
||||
mINI::Instance()[kOnServerStarted] = "";
|
||||
mINI::Instance()[kOnServerExited] = "";
|
||||
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
|
||||
|
||||
|
|
@ -100,14 +102,14 @@ static void parse_http_response(const SockException &ex, const Parser &res, cons
|
|||
fun(Json::nullValue, errStr, should_retry);
|
||||
return;
|
||||
}
|
||||
if (res.Url() != "200") {
|
||||
auto errStr = StrPrinter << "[bad http status code]:" << res.Url() << endl;
|
||||
if (res.status() != "200") {
|
||||
auto errStr = StrPrinter << "[bad http status code]:" << res.status() << endl;
|
||||
fun(Json::nullValue, errStr, should_retry);
|
||||
return;
|
||||
}
|
||||
Value result;
|
||||
try {
|
||||
stringstream ss(res.Content());
|
||||
stringstream ss(res.content());
|
||||
ss >> result;
|
||||
} catch (std::exception &ex) {
|
||||
auto errStr = StrPrinter << "[parse json failed]:" << ex.what() << endl;
|
||||
|
|
@ -164,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);
|
||||
|
|
@ -213,12 +219,12 @@ void do_http_hook(const string &url, const ArgsType &body, const function<void(c
|
|||
do_http_hook(url, body, func, hook_retry);
|
||||
}
|
||||
|
||||
void dumpMediaTuple(const MediaTuple &tuple, Json::Value& item);
|
||||
|
||||
static ArgsType make_json(const MediaInfo &args) {
|
||||
ArgsType body;
|
||||
body["schema"] = args.schema;
|
||||
body[VHOST_KEY] = args.vhost;
|
||||
body["app"] = args.app;
|
||||
body["stream"] = args.stream;
|
||||
dumpMediaTuple(args, body);
|
||||
body["params"] = args.param_strs;
|
||||
return body;
|
||||
}
|
||||
|
|
@ -238,6 +244,18 @@ static void reportServerStarted() {
|
|||
do_http_hook(hook_server_started, body, nullptr);
|
||||
}
|
||||
|
||||
static void reportServerExited() {
|
||||
GET_CONFIG(bool, hook_enable, Hook::kEnable);
|
||||
GET_CONFIG(string, hook_server_exited, Hook::kOnServerExited);
|
||||
if (!hook_enable || hook_server_exited.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ArgsType body;
|
||||
// 执行hook
|
||||
do_http_hook(hook_server_exited, body, nullptr);
|
||||
}
|
||||
|
||||
// 服务器定时保活定时器
|
||||
static Timer::Ptr g_keepalive_timer;
|
||||
static void reportServerKeepalive() {
|
||||
|
|
@ -317,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;
|
||||
}
|
||||
|
|
@ -346,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;
|
||||
}
|
||||
|
|
@ -360,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);
|
||||
|
|
@ -379,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;
|
||||
|
|
@ -427,23 +444,37 @@ 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);
|
||||
body["regist"] = bRegist;
|
||||
} else {
|
||||
body["schema"] = sender.getSchema();
|
||||
body[VHOST_KEY] = sender.getVhost();
|
||||
body["app"] = sender.getApp();
|
||||
body["stream"] = sender.getId();
|
||||
dumpMediaTuple(sender.getMediaTuple(), body);
|
||||
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) {
|
||||
|
|
@ -503,9 +534,7 @@ void installWebHook() {
|
|||
body["file_name"] = info.file_name;
|
||||
body["folder"] = info.folder;
|
||||
body["url"] = info.url;
|
||||
body["app"] = info.app;
|
||||
body["stream"] = info.stream;
|
||||
body[VHOST_KEY] = info.vhost;
|
||||
dumpMediaTuple(info, body);
|
||||
return body;
|
||||
};
|
||||
|
||||
|
|
@ -532,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;
|
||||
}
|
||||
|
|
@ -561,9 +590,7 @@ void installWebHook() {
|
|||
|
||||
ArgsType body;
|
||||
body["schema"] = sender.getSchema();
|
||||
body[VHOST_KEY] = sender.getVhost();
|
||||
body["app"] = sender.getApp();
|
||||
body["stream"] = sender.getId();
|
||||
dumpMediaTuple(sender.getMediaTuple(), body);
|
||||
weak_ptr<MediaSource> weakSrc = sender.shared_from_this();
|
||||
// 执行hook
|
||||
do_http_hook(hook_stream_none_reader, body, [weakSrc](const Value &obj, const string &err) {
|
||||
|
|
@ -577,16 +604,14 @@ 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;
|
||||
}
|
||||
|
||||
ArgsType body;
|
||||
body[VHOST_KEY] = sender.getVhost();
|
||||
body["app"] = sender.getApp();
|
||||
body["stream"] = sender.getStreamId();
|
||||
dumpMediaTuple(sender.getMediaTuple(), body);
|
||||
body["ssrc"] = ssrc;
|
||||
body["originType"] = (int)sender.getOriginType(MediaSource::NullMediaSource());
|
||||
body["originTypeStr"] = getOriginTypeString(sender.getOriginType(MediaSource::NullMediaSource()));
|
||||
|
|
@ -614,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文件访问鉴权,那么允许访问,但是每次访问都要鉴权;
|
||||
// 因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权)
|
||||
if (!HttpFileManager::isIPAllowed(sender.get_peer_ip())) {
|
||||
invoker("Your ip is not allowed to access the service.", "", 0);
|
||||
} else {
|
||||
invoker("", "", 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -632,7 +656,7 @@ void installWebHook() {
|
|||
body["id"] = sender.getIdentifier();
|
||||
body["path"] = path;
|
||||
body["is_dir"] = is_dir;
|
||||
body["params"] = parser.Params();
|
||||
body["params"] = parser.params();
|
||||
for (auto &pr : parser.getHeader()) {
|
||||
body[string("header.") + pr.first] = pr.second;
|
||||
}
|
||||
|
|
@ -650,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;
|
||||
|
|
@ -676,3 +700,7 @@ void unInstallWebHook() {
|
|||
g_keepalive_timer.reset();
|
||||
NoticeCenter::Instance().delListener(&web_hook_tag);
|
||||
}
|
||||
|
||||
void onProcessExited() {
|
||||
reportServerExited();
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ extern const std::string kTimeoutSec;
|
|||
|
||||
void installWebHook();
|
||||
void unInstallWebHook();
|
||||
void onProcessExited();
|
||||
/**
|
||||
* 触发http hook请求
|
||||
* @param url 请求地址
|
||||
|
|
|
|||
|
|
@ -179,6 +179,29 @@ public:
|
|||
throw ExitException();
|
||||
});
|
||||
#endif
|
||||
(*_parser) << Option(0,/*该选项简称,如果是\x00则说明无简称*/
|
||||
"log-slice",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/
|
||||
Option::ArgRequired,/*该选项后面必须跟值*/
|
||||
"100",/*该选项默认值*/
|
||||
true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
|
||||
"最大保存日志切片个数",/*该选项说明文字*/
|
||||
nullptr);
|
||||
|
||||
(*_parser) << Option(0,/*该选项简称,如果是\x00则说明无简称*/
|
||||
"log-size",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/
|
||||
Option::ArgRequired,/*该选项后面必须跟值*/
|
||||
"256",/*该选项默认值*/
|
||||
true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
|
||||
"单个日志切片最大容量,单位MB",/*该选项说明文字*/
|
||||
nullptr);
|
||||
|
||||
(*_parser) << Option(0,/*该选项简称,如果是\x00则说明无简称*/
|
||||
"log-dir",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/
|
||||
Option::ArgRequired,/*该选项后面必须跟值*/
|
||||
(exeDir() + "log/").data(),/*该选项默认值*/
|
||||
true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
|
||||
"日志保存文件夹路径",/*该选项说明文字*/
|
||||
nullptr);
|
||||
}
|
||||
|
||||
~CMD_main() override{}
|
||||
|
|
@ -213,9 +236,11 @@ int start_main(int argc,char *argv[]) {
|
|||
//设置日志
|
||||
Logger::Instance().add(std::make_shared<ConsoleChannel>("ConsoleChannel", logLevel));
|
||||
#if !defined(ANDROID)
|
||||
auto fileChannel = std::make_shared<FileChannel>("FileChannel", exeDir() + "log/", logLevel);
|
||||
auto fileChannel = std::make_shared<FileChannel>("FileChannel", cmd_main["log-dir"], logLevel);
|
||||
// 日志最多保存天数
|
||||
fileChannel->setMaxDay(cmd_main["max_day"]);
|
||||
fileChannel->setFileMaxCount(cmd_main["log-slice"]);
|
||||
fileChannel->setFileMaxSize(cmd_main["log-size"]);
|
||||
Logger::Instance().add(fileChannel);
|
||||
#endif // !defined(ANDROID)
|
||||
|
||||
|
|
@ -326,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
|
||||
|
|
@ -363,8 +396,7 @@ int start_main(int argc,char *argv[]) {
|
|||
#endif//defined(ENABLE_SRT)
|
||||
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << "端口占用或无权限:" << ex.what() << endl;
|
||||
ErrorL << "程序启动失败,请修改配置文件中端口号后重试!" << endl;
|
||||
ErrorL << "Start server failed: " << ex.what();
|
||||
sleep(1);
|
||||
#if !defined(_WIN32)
|
||||
if (pid != getpid() && kill_parent_if_failed) {
|
||||
|
|
@ -384,9 +416,15 @@ int start_main(int argc,char *argv[]) {
|
|||
static semaphore sem;
|
||||
signal(SIGINT, [](int) {
|
||||
InfoL << "SIGINT:exit";
|
||||
signal(SIGINT, SIG_IGN);// 设置退出信号
|
||||
signal(SIGINT, SIG_IGN); // 设置退出信号
|
||||
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()); });
|
||||
|
|
@ -395,6 +433,8 @@ int start_main(int argc,char *argv[]) {
|
|||
}
|
||||
unInstallWebApi();
|
||||
unInstallWebHook();
|
||||
onProcessExited();
|
||||
|
||||
//休眠1秒再退出,防止资源释放顺序错误
|
||||
InfoL << "程序退出中,请等待...";
|
||||
sleep(1);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
|
||||
#include "JemallocUtil.h"
|
||||
#include "Util/logger.h"
|
||||
#ifdef USE_JEMALLOC
|
||||
#include <iostream>
|
||||
#include <jemalloc/jemalloc.h>
|
||||
#endif
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
void set_profile_active(bool active) {
|
||||
#ifdef USE_JEMALLOC
|
||||
int err = mallctl("prof.active", nullptr, nullptr, (void *)&active, sizeof(active));
|
||||
if (err != 0) {
|
||||
WarnL << "mallctl failed with: " << err;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void JemallocUtil::enable_profiling() {
|
||||
set_profile_active(true);
|
||||
}
|
||||
void JemallocUtil::disable_profiling() {
|
||||
set_profile_active(false);
|
||||
}
|
||||
void JemallocUtil::dump(const std::string &file_name) {
|
||||
#ifdef USE_JEMALLOC
|
||||
auto *c_str = file_name.c_str();
|
||||
int err = mallctl("prof.dump", nullptr, nullptr, &c_str, sizeof(const char *));
|
||||
if (err != 0) {
|
||||
std::cerr << "mallctl failed with: " << err << std::endl;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
std::string JemallocUtil::get_malloc_stats() {
|
||||
#ifdef USE_JEMALLOC
|
||||
std::string res;
|
||||
malloc_stats_print([](void *opaque, const char *s) { ((std::string *)opaque)->append(s); }, &res, "J");
|
||||
return res;
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
void JemallocUtil::some_malloc_stats(const std::function<void(const char *, uint64_t)> &fn) {
|
||||
#ifdef USE_JEMALLOC
|
||||
constexpr std::array<const char *, 8> STATS = {
|
||||
"stats.allocated", "stats.active", "stats.metadata", "stats.metadata_thp",
|
||||
"stats.resident", "stats.mapped", "stats.retained", "stats.zero_reallocs",
|
||||
};
|
||||
|
||||
for (const char *stat : STATS) {
|
||||
size_t value;
|
||||
size_t len = sizeof(value);
|
||||
auto err = mallctl(stat, &value, &len, nullptr, 0);
|
||||
if (err != 0) {
|
||||
ErrorL << "Failed reading " << stat << ": " << err;
|
||||
continue;
|
||||
}
|
||||
fn(stat, value);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} // namespace mediakit
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved.
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit).
|
||||
*
|
||||
* Use of this source code is governed by MIT license that can be found in the
|
||||
* LICENSE file in the root of the source tree. All contributing project authors
|
||||
* may be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#ifndef ZLMEDIAKIT_JEMALLOCUTIL_H
|
||||
#define ZLMEDIAKIT_JEMALLOCUTIL_H
|
||||
#include <functional>
|
||||
#include <string>
|
||||
namespace mediakit {
|
||||
class JemallocUtil {
|
||||
public:
|
||||
JemallocUtil() = default;
|
||||
~JemallocUtil() = default;
|
||||
|
||||
static void enable_profiling();
|
||||
|
||||
static void disable_profiling();
|
||||
|
||||
static void dump(const std::string &file_name);
|
||||
static std::string get_malloc_stats();
|
||||
static void some_malloc_stats(const std::function<void(const char *, uint64_t)> &fn);
|
||||
};
|
||||
} // namespace mediakit
|
||||
#endif // ZLMEDIAKIT_JEMALLOCUTIL_H
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -53,12 +55,14 @@ string getOriginTypeString(MediaOriginType type){
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ProtocolOption::ProtocolOption() {
|
||||
GET_CONFIG(bool, s_modify_stamp, Protocol::kModifyStamp);
|
||||
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;
|
||||
|
|
@ -124,24 +130,11 @@ MediaSource::MediaSource(const string &schema, const MediaTuple& tuple): _tuple(
|
|||
}
|
||||
|
||||
MediaSource::~MediaSource() {
|
||||
try {
|
||||
unregist();
|
||||
}
|
||||
|
||||
const string& MediaSource::getSchema() const {
|
||||
return _schema;
|
||||
}
|
||||
|
||||
const string& MediaSource::getVhost() const {
|
||||
return _tuple.vhost;
|
||||
}
|
||||
|
||||
const string& MediaSource::getApp() const {
|
||||
//获取该源的id
|
||||
return _tuple.app;
|
||||
}
|
||||
|
||||
const string& MediaSource::getId() const {
|
||||
return _tuple.stream;
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << "Exception occurred: " << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<void> MediaSource::getOwnership() {
|
||||
|
|
@ -183,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) {
|
||||
std::weak_ptr<MediaSourceEvent> MediaSource::getListener() const {
|
||||
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;
|
||||
}
|
||||
|
||||
int MediaSource::totalReaderCount(){
|
||||
|
|
@ -288,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();
|
||||
|
|
@ -456,9 +442,7 @@ static void findAsync_l(const MediaInfo &info, const std::shared_ptr<Session> &s
|
|||
auto on_register = [weak_session, info, cb_once, cancel_all, poller](BroadcastMediaChangedArgs) {
|
||||
if (!bRegist ||
|
||||
sender.getSchema() != info.schema ||
|
||||
sender.getVhost() != info.vhost ||
|
||||
sender.getApp() != info.app ||
|
||||
sender.getId() != info.stream) {
|
||||
!equalMediaTuple(sender.getMediaTuple(), info)) {
|
||||
//不是自己感兴趣的事件,忽略之
|
||||
return;
|
||||
}
|
||||
|
|
@ -485,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) {
|
||||
|
|
@ -515,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();
|
||||
}
|
||||
|
||||
|
|
@ -575,6 +559,9 @@ bool MediaSource::unregist() {
|
|||
return ret;
|
||||
}
|
||||
|
||||
bool equalMediaTuple(const MediaTuple& a, const MediaTuple& b) {
|
||||
return a.vhost == b.vhost && a.app == b.app && a.stream == b.stream;
|
||||
}
|
||||
/////////////////////////////////////MediaInfo//////////////////////////////////////
|
||||
|
||||
void MediaInfo::parse(const std::string &url_in){
|
||||
|
|
@ -659,7 +646,7 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){
|
|||
GET_CONFIG(string, record_app, Record::kAppName);
|
||||
GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS);
|
||||
//如果mp4点播, 无人观看时我们强制关闭点播
|
||||
bool is_mp4_vod = sender.getApp() == record_app;
|
||||
bool is_mp4_vod = sender.getMediaTuple().app == record_app;
|
||||
weak_ptr<MediaSource> weak_sender = sender.shared_from_this();
|
||||
|
||||
_async_close_timer = std::make_shared<Timer>(stream_none_reader_delay / 1000.0f, [weak_sender, is_mp4_vod]() {
|
||||
|
|
@ -675,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();
|
||||
|
|
@ -790,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:
|
||||
|
|
@ -136,17 +139,30 @@ class ProtocolOption {
|
|||
public:
|
||||
ProtocolOption();
|
||||
|
||||
//时间戳修复这一路流标志位
|
||||
bool modify_stamp;
|
||||
enum {
|
||||
kModifyStampOff = 0, // 采用源视频流绝对时间戳,不做任何改变
|
||||
kModifyStampSystem = 1, // 采用zlmediakit接收数据时的系统时间戳(有平滑处理)
|
||||
kModifyStampRelative = 2 // 采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正
|
||||
};
|
||||
// 时间戳类型
|
||||
int modify_stamp;
|
||||
|
||||
//转协议是否开启音频
|
||||
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
|
||||
|
|
@ -179,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);
|
||||
|
|
@ -205,6 +226,7 @@ public:
|
|||
GET_OPT_VALUE(mp4_save_path);
|
||||
|
||||
GET_OPT_VALUE(hls_save_path);
|
||||
GET_OPT_VALUE(stream_replace);
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
@ -244,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;
|
||||
|
|
@ -268,6 +291,8 @@ public:
|
|||
std::string param_strs;
|
||||
};
|
||||
|
||||
bool equalMediaTuple(const MediaTuple& a, const MediaTuple& b);
|
||||
|
||||
/**
|
||||
* 媒体源,任何rtsp/rtmp的直播流都源自该对象
|
||||
*/
|
||||
|
|
@ -282,21 +307,15 @@ public:
|
|||
////////////////获取MediaSource相关信息////////////////
|
||||
|
||||
// 获取协议类型
|
||||
const std::string& getSchema() const;
|
||||
// 虚拟主机
|
||||
const std::string& getVhost() const;
|
||||
// 应用名
|
||||
const std::string& getApp() const;
|
||||
// 流id
|
||||
const std::string& getId() const;
|
||||
const std::string& getSchema() const {
|
||||
return _schema;
|
||||
}
|
||||
|
||||
const MediaTuple& getMediaTuple() const {
|
||||
return _tuple;
|
||||
}
|
||||
|
||||
std::string shortUrl() const { return _tuple.shortUrl(); }
|
||||
|
||||
std::string getUrl() const { return _schema + "://" + shortUrl(); }
|
||||
std::string getUrl() const { return _schema + "://" + _tuple.shortUrl(); }
|
||||
|
||||
//获取对象所有权
|
||||
std::shared_ptr<void> getOwnership();
|
||||
|
|
@ -321,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或者文件路径
|
||||
|
|
@ -363,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,16 +71,12 @@ static string getTrackInfoStr(const TrackSource *track_src){
|
|||
return std::move(codec_info);
|
||||
}
|
||||
|
||||
const std::string &MultiMediaSourceMuxer::getVhost() const {
|
||||
return _tuple.vhost;
|
||||
const ProtocolOption &MultiMediaSourceMuxer::getOption() const {
|
||||
return _option;
|
||||
}
|
||||
|
||||
const std::string &MultiMediaSourceMuxer::getApp() const {
|
||||
return _tuple.app;
|
||||
}
|
||||
|
||||
const std::string &MultiMediaSourceMuxer::getStreamId() const {
|
||||
return _tuple.stream;
|
||||
const MediaTuple &MultiMediaSourceMuxer::getMediaTuple() const {
|
||||
return _tuple;
|
||||
}
|
||||
|
||||
std::string MultiMediaSourceMuxer::shortUrl() const {
|
||||
|
|
@ -91,9 +88,18 @@ 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;
|
||||
if (dur_sec > 0.01) {
|
||||
// 点播
|
||||
_stamp[TrackVideo].setPlayBack();
|
||||
_stamp[TrackAudio].setPlayBack();
|
||||
}
|
||||
|
||||
if (option.enable_rtmp) {
|
||||
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(_tuple, option, std::make_shared<TitleMeta>(dur_sec));
|
||||
|
|
@ -104,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);
|
||||
|
|
@ -135,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,15 +158,13 @@ void MultiMediaSourceMuxer::setTrackListener(const std::weak_ptr<Listener> &list
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -187,6 +192,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录制,触发观看人数变化相关事件
|
||||
|
|
@ -222,19 +228,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -266,7 +312,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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -323,6 +369,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) {
|
||||
|
|
@ -334,20 +384,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;
|
||||
}
|
||||
|
|
@ -357,16 +404,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();
|
||||
|
|
@ -378,6 +436,11 @@ void MultiMediaSourceMuxer::onAllTrackReady() {
|
|||
createGopCacheIfNeed();
|
||||
}
|
||||
#endif
|
||||
auto tracks = getTracks(false);
|
||||
if (tracks.size() >= 2) {
|
||||
// 音频时间戳同步于视频,因为音频时间戳被修改后不影响播放
|
||||
_stamp[TrackAudio].syncTo(_stamp[TrackVideo]);
|
||||
}
|
||||
InfoL << "stream: " << shortUrl() << " , codec info: " << getTrackInfoStr(this);
|
||||
}
|
||||
|
||||
|
|
@ -409,29 +472,25 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
|
||||
auto frame = frame_in;
|
||||
if (_option.modify_stamp) {
|
||||
//开启了时间戳覆盖
|
||||
frame = std::make_shared<FrameStamp>(frame, _stamp[frame->getTrackType()],true);
|
||||
if (_option.modify_stamp != ProtocolOption::kModifyStampOff) {
|
||||
// 时间戳不采用原始的绝对时间戳
|
||||
frame = std::make_shared<FrameStamp>(frame, _stamp[frame->getTrackType()], _option.modify_stamp);
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
|
|
@ -445,23 +504,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开始处
|
||||
|
|
@ -483,15 +539,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
|
||||
|
|
|
|||
|
|
@ -126,12 +126,13 @@ public:
|
|||
*/
|
||||
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
|
||||
|
||||
const std::string& getVhost() const;
|
||||
const std::string& getApp() const;
|
||||
const std::string& getStreamId() const;
|
||||
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:
|
||||
|
|
@ -167,18 +168,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;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,19 +10,22 @@
|
|||
|
||||
#include <cinttypes>
|
||||
#include "Parser.h"
|
||||
#include "strCoding.h"
|
||||
#include "macros.h"
|
||||
#include "Network/sockutil.h"
|
||||
#include "Common/macros.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit{
|
||||
namespace mediakit {
|
||||
|
||||
string FindField(const char* buf, const char* start, const char *end ,size_t bufSize) {
|
||||
if(bufSize <=0 ){
|
||||
bufSize = strlen(buf);
|
||||
string findSubString(const char *buf, const char *start, const char *end, size_t buf_size) {
|
||||
if (buf_size <= 0) {
|
||||
buf_size = strlen(buf);
|
||||
}
|
||||
const char *msg_start = buf, *msg_end = buf + bufSize;
|
||||
auto msg_start = buf;
|
||||
auto msg_end = buf + buf_size;
|
||||
size_t len = 0;
|
||||
if (start != NULL) {
|
||||
len = strlen(start);
|
||||
|
|
@ -41,126 +44,147 @@ string FindField(const char* buf, const char* start, const char *end ,size_t buf
|
|||
return string(msg_start, msg_end);
|
||||
}
|
||||
|
||||
void Parser::Parse(const char *buf) {
|
||||
//解析
|
||||
const char *start = buf;
|
||||
Clear();
|
||||
void Parser::parse(const char *buf, size_t size) {
|
||||
clear();
|
||||
auto ptr = buf;
|
||||
while (true) {
|
||||
auto line = FindField(start, NULL, "\r\n");
|
||||
if (line.size() == 0) {
|
||||
break;
|
||||
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 (start == buf) {
|
||||
_strMethod = FindField(line.data(), NULL, " ");
|
||||
auto strFullUrl = FindField(line.data(), " ", " ");
|
||||
auto args_pos = strFullUrl.find('?');
|
||||
if (args_pos != string::npos) {
|
||||
_strUrl = strFullUrl.substr(0, args_pos);
|
||||
_params = strFullUrl.substr(args_pos + 1);
|
||||
_mapUrlArgs = parseArgs(_params);
|
||||
if (ptr == buf) {
|
||||
auto blank = strchr(ptr, ' ');
|
||||
CHECK(blank > ptr && blank < next_line);
|
||||
_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);
|
||||
auto pos = _url.find('?');
|
||||
if (pos != string::npos) {
|
||||
_params = _url.substr(pos + 1);
|
||||
_url_args = parseArgs(_params);
|
||||
_url = _url.substr(0, pos);
|
||||
}
|
||||
_protocol = std::string(next_blank + 1, next_line);
|
||||
} else {
|
||||
_strUrl = strFullUrl;
|
||||
}
|
||||
_strTail = FindField(line.data(), (strFullUrl + " ").data(), NULL);
|
||||
auto pos = strchr(ptr, ':');
|
||||
CHECK(pos > ptr && pos < next_line);
|
||||
std::string key { ptr, static_cast<std::size_t>(pos - ptr) };
|
||||
std::string value;
|
||||
if (pos[1] == ' ') {
|
||||
value.assign(pos + 2, next_line);
|
||||
} else {
|
||||
auto field = FindField(line.data(), NULL, ": ");
|
||||
auto value = FindField(line.data(), ": ", NULL);
|
||||
if (field.size() != 0) {
|
||||
_mapHeaders.emplace_force(field, value);
|
||||
value.assign(pos + 1, next_line);
|
||||
}
|
||||
_headers.emplace_force(trim(std::move(key)), trim(std::move(value)));
|
||||
}
|
||||
start = start + line.size() + 2;
|
||||
if (strncmp(start, "\r\n", 2) == 0) { //协议解析完毕
|
||||
_strContent = FindField(start, "\r\n", NULL);
|
||||
ptr = next_line + offset;
|
||||
if (strncmp(ptr, "\r\n", 2) == 0) { // 协议解析完毕
|
||||
_content.assign(ptr + 2, buf + size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const string &Parser::Method() const {
|
||||
return _strMethod;
|
||||
const string &Parser::method() const {
|
||||
return _method;
|
||||
}
|
||||
|
||||
const string &Parser::Url() const {
|
||||
return _strUrl;
|
||||
const string &Parser::url() const {
|
||||
return _url;
|
||||
}
|
||||
|
||||
string Parser::FullUrl() const {
|
||||
const std::string &Parser::status() const {
|
||||
return url();
|
||||
}
|
||||
|
||||
string Parser::fullUrl() const {
|
||||
if (_params.empty()) {
|
||||
return _strUrl;
|
||||
return _url;
|
||||
}
|
||||
return _strUrl + "?" + _params;
|
||||
return _url + "?" + _params;
|
||||
}
|
||||
|
||||
const string &Parser::Tail() const {
|
||||
return _strTail;
|
||||
const string &Parser::protocol() const {
|
||||
return _protocol;
|
||||
}
|
||||
|
||||
const std::string &Parser::statusStr() const {
|
||||
return protocol();
|
||||
}
|
||||
|
||||
static std::string kNull;
|
||||
|
||||
const string &Parser::operator[](const char *name) const {
|
||||
auto it = _mapHeaders.find(name);
|
||||
if (it == _mapHeaders.end()) {
|
||||
return _strNull;
|
||||
auto it = _headers.find(name);
|
||||
if (it == _headers.end()) {
|
||||
return kNull;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const string &Parser::Content() const {
|
||||
return _strContent;
|
||||
const string &Parser::content() const {
|
||||
return _content;
|
||||
}
|
||||
|
||||
void Parser::Clear() {
|
||||
_strMethod.clear();
|
||||
_strUrl.clear();
|
||||
void Parser::clear() {
|
||||
_method.clear();
|
||||
_url.clear();
|
||||
_params.clear();
|
||||
_strTail.clear();
|
||||
_strContent.clear();
|
||||
_mapHeaders.clear();
|
||||
_mapUrlArgs.clear();
|
||||
_protocol.clear();
|
||||
_content.clear();
|
||||
_headers.clear();
|
||||
_url_args.clear();
|
||||
}
|
||||
|
||||
const string &Parser::Params() const {
|
||||
const string &Parser::params() const {
|
||||
return _params;
|
||||
}
|
||||
|
||||
void Parser::setUrl(string url) {
|
||||
this->_strUrl = std::move(url);
|
||||
_url = std::move(url);
|
||||
}
|
||||
|
||||
void Parser::setContent(string content) {
|
||||
this->_strContent = std::move(content);
|
||||
_content = std::move(content);
|
||||
}
|
||||
|
||||
StrCaseMap &Parser::getHeader() const {
|
||||
return _mapHeaders;
|
||||
return _headers;
|
||||
}
|
||||
|
||||
StrCaseMap &Parser::getUrlArgs() const {
|
||||
return _mapUrlArgs;
|
||||
return _url_args;
|
||||
}
|
||||
|
||||
StrCaseMap Parser::parseArgs(const string &str, const char *pair_delim, const char *key_delim) {
|
||||
StrCaseMap ret;
|
||||
auto arg_vec = split(str, pair_delim);
|
||||
for (string &key_val : arg_vec) {
|
||||
for (auto &key_val : arg_vec) {
|
||||
if (key_val.empty()) {
|
||||
//忽略
|
||||
// 忽略
|
||||
continue;
|
||||
}
|
||||
auto key = trim(FindField(key_val.data(), NULL, key_delim));
|
||||
if (!key.empty()) {
|
||||
auto val = trim(FindField(key_val.data(), key_delim, NULL));
|
||||
ret.emplace_force(key, val);
|
||||
auto pos = key_val.find(key_delim);
|
||||
if (pos != string::npos) {
|
||||
auto key = trim(std::string(key_val, 0, pos));
|
||||
auto val = trim(key_val.substr(pos + strlen(key_delim)));
|
||||
ret.emplace_force(std::move(key), std::move(val));
|
||||
} else {
|
||||
trim(key_val);
|
||||
if (!key_val.empty()) {
|
||||
ret.emplace_force(key_val, "");
|
||||
ret.emplace_force(std::move(key_val), "");
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
std::string Parser::merge_url(const string &base_url, const string &path) {
|
||||
//以base_url为基础, 合并path路径生成新的url, path支持相对路径和绝对路径
|
||||
|
||||
std::string Parser::mergeUrl(const string &base_url, const string &path) {
|
||||
// 以base_url为基础, 合并path路径生成新的url, path支持相对路径和绝对路径
|
||||
if (base_url.empty()) {
|
||||
return path;
|
||||
}
|
||||
|
|
@ -234,43 +258,45 @@ std::string Parser::merge_url(const string &base_url, const string &path) {
|
|||
}
|
||||
return final_url.str();
|
||||
}
|
||||
|
||||
void RtspUrl::parse(const string &strUrl) {
|
||||
auto schema = FindField(strUrl.data(), nullptr, "://");
|
||||
auto schema = findSubString(strUrl.data(), nullptr, "://");
|
||||
bool is_ssl = strcasecmp(schema.data(), "rtsps") == 0;
|
||||
//查找"://"与"/"之间的字符串,用于提取用户名密码
|
||||
auto middle_url = FindField(strUrl.data(), "://", "/");
|
||||
// 查找"://"与"/"之间的字符串,用于提取用户名密码
|
||||
auto middle_url = findSubString(strUrl.data(), "://", "/");
|
||||
if (middle_url.empty()) {
|
||||
middle_url = FindField(strUrl.data(), "://", nullptr);
|
||||
middle_url = findSubString(strUrl.data(), "://", nullptr);
|
||||
}
|
||||
auto pos = middle_url.rfind('@');
|
||||
if (pos == string::npos) {
|
||||
//并没有用户名密码
|
||||
// 并没有用户名密码
|
||||
return setup(is_ssl, strUrl, "", "");
|
||||
}
|
||||
|
||||
//包含用户名密码
|
||||
// 包含用户名密码
|
||||
auto user_pwd = middle_url.substr(0, pos);
|
||||
auto suffix = strUrl.substr(schema.size() + 3 + pos + 1);
|
||||
auto url = StrPrinter << "rtsp://" << suffix << endl;
|
||||
if (user_pwd.find(":") == string::npos) {
|
||||
return setup(is_ssl, url, user_pwd, "");
|
||||
}
|
||||
auto user = FindField(user_pwd.data(), nullptr, ":");
|
||||
auto pwd = FindField(user_pwd.data(), ":", nullptr);
|
||||
auto user = findSubString(user_pwd.data(), nullptr, ":");
|
||||
auto pwd = findSubString(user_pwd.data(), ":", nullptr);
|
||||
return setup(is_ssl, url, user, pwd);
|
||||
}
|
||||
|
||||
void RtspUrl::setup(bool is_ssl, const string &url, const string &user, const string &passwd) {
|
||||
auto ip = FindField(url.data(), "://", "/");
|
||||
auto ip = findSubString(url.data(), "://", "/");
|
||||
if (ip.empty()) {
|
||||
ip = split(FindField(url.data(), "://", NULL), "?")[0];
|
||||
ip = split(findSubString(url.data(), "://", NULL), "?")[0];
|
||||
}
|
||||
uint16_t port = is_ssl ? 322 : 554;
|
||||
splitUrl(ip, ip, port);
|
||||
|
||||
|
||||
_url = std::move(url);
|
||||
_user = std::move(user);
|
||||
_passwd = std::move(passwd);
|
||||
_user = strCoding::UrlDecode(std::move(user));
|
||||
_passwd = strCoding::UrlDecode(std::move(passwd));
|
||||
_host = std::move(ip);
|
||||
_port = port;
|
||||
_is_ssl = is_ssl;
|
||||
|
|
@ -289,7 +315,7 @@ void splitUrl(const std::string &url, std::string &host, uint16_t &port) {
|
|||
CHECK(!url.empty(), "empty url");
|
||||
auto pos = url.rfind(':');
|
||||
if (pos == string::npos || url.back() == ']') {
|
||||
//没有冒号,未指定端口;或者是纯粹的ipv6地址
|
||||
// 没有冒号,未指定端口;或者是纯粹的ipv6地址
|
||||
host = url;
|
||||
checkHost(host);
|
||||
return;
|
||||
|
|
@ -299,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([](){
|
||||
|
|
@ -312,4 +337,4 @@ static onceToken token([](){
|
|||
});
|
||||
#endif
|
||||
|
||||
}//namespace mediakit
|
||||
} // namespace mediakit
|
||||
|
|
|
|||
|
|
@ -17,15 +17,13 @@
|
|||
|
||||
namespace mediakit {
|
||||
|
||||
//从字符串中提取子字符串
|
||||
std::string FindField(const char *buf, const char *start, const char *end, size_t bufSize = 0);
|
||||
//把url解析为主机地址和端口号,兼容ipv4/ipv6/dns
|
||||
void splitUrl(const std::string &url, std::string &host, uint16_t& port);
|
||||
// 从字符串中提取子字符串
|
||||
std::string findSubString(const char *buf, const char *start, const char *end, size_t buf_size = 0);
|
||||
// 把url解析为主机地址和端口号,兼容ipv4/ipv6/dns
|
||||
void splitUrl(const std::string &url, std::string &host, uint16_t &port);
|
||||
|
||||
struct StrCaseCompare {
|
||||
bool operator()(const std::string &__x, const std::string &__y) const {
|
||||
return strcasecmp(__x.data(), __y.data()) < 0;
|
||||
}
|
||||
bool operator()(const std::string &__x, const std::string &__y) const { return strcasecmp(__x.data(), __y.data()) < 0; }
|
||||
};
|
||||
|
||||
class StrCaseMap : public std::multimap<std::string, std::string, StrCaseCompare> {
|
||||
|
|
@ -42,84 +40,87 @@ public:
|
|||
return it->second;
|
||||
}
|
||||
|
||||
template<typename V>
|
||||
void emplace(const std::string &k, V &&v) {
|
||||
template <typename K, typename V>
|
||||
void emplace(K &&k, V &&v) {
|
||||
auto it = find(k);
|
||||
if (it != end()) {
|
||||
return;
|
||||
}
|
||||
Super::emplace(k, std::forward<V>(v));
|
||||
Super::emplace(std::forward<K>(k), std::forward<V>(v));
|
||||
}
|
||||
|
||||
template<typename V>
|
||||
void emplace_force(const std::string k, V &&v) {
|
||||
Super::emplace(k, std::forward<V>(v));
|
||||
template <typename K, typename V>
|
||||
void emplace_force(K &&k, V &&v) {
|
||||
Super::emplace(std::forward<K>(k), std::forward<V>(v));
|
||||
}
|
||||
};
|
||||
|
||||
//rtsp/http/sip解析类
|
||||
// rtsp/http/sip解析类
|
||||
class Parser {
|
||||
public:
|
||||
Parser() = default;
|
||||
~Parser() = default;
|
||||
|
||||
//解析信令
|
||||
void Parse(const char *buf);
|
||||
// 解析http/rtsp/sip请求,需要确保buf以\0结尾
|
||||
void parse(const char *buf, size_t size);
|
||||
|
||||
//获取命令字
|
||||
const std::string &Method() const;
|
||||
// 获取命令字,如GET/POST
|
||||
const std::string &method() const;
|
||||
|
||||
//获取中间url,不包含?后面的参数
|
||||
const std::string &Url() const;
|
||||
// 请求时,获取中间url,不包含?后面的参数
|
||||
const std::string &url() const;
|
||||
// 回复时,获取状态码,如200/404
|
||||
const std::string &status() const;
|
||||
|
||||
//获取中间url,包含?后面的参数
|
||||
std::string FullUrl() const;
|
||||
// 获取中间url,包含?后面的参数
|
||||
std::string fullUrl() const;
|
||||
|
||||
//获取命令协议名
|
||||
const std::string &Tail() const;
|
||||
// 请求时,获取协议名,如HTTP/1.1
|
||||
const std::string &protocol() const;
|
||||
// 回复时,获取状态字符串,如 OK/Not Found
|
||||
const std::string &statusStr() const;
|
||||
|
||||
//根据header key名,获取请求header value值
|
||||
// 根据header key名,获取请求header value值
|
||||
const std::string &operator[](const char *name) const;
|
||||
|
||||
//获取http body或sdp
|
||||
const std::string &Content() const;
|
||||
// 获取http body或sdp
|
||||
const std::string &content() const;
|
||||
|
||||
//清空,为了重用
|
||||
void Clear();
|
||||
// 清空,为了重用
|
||||
void clear();
|
||||
|
||||
//获取?后面的参数
|
||||
const std::string &Params() const;
|
||||
// 获取?后面的参数
|
||||
const std::string ¶ms() const;
|
||||
|
||||
//重新设置url
|
||||
// 重新设置url
|
||||
void setUrl(std::string url);
|
||||
|
||||
//重新设置content
|
||||
// 重新设置content
|
||||
void setContent(std::string content);
|
||||
|
||||
//获取header列表
|
||||
// 获取header列表
|
||||
StrCaseMap &getHeader() const;
|
||||
|
||||
//获取url参数列表
|
||||
// 获取url参数列表
|
||||
StrCaseMap &getUrlArgs() const;
|
||||
|
||||
//解析?后面的参数
|
||||
// 解析?后面的参数
|
||||
static StrCaseMap parseArgs(const std::string &str, const char *pair_delim = "&", const char *key_delim = "=");
|
||||
|
||||
static std::string merge_url(const std::string &base_url, const std::string &path);
|
||||
static std::string mergeUrl(const std::string &base_url, const std::string &path);
|
||||
|
||||
private:
|
||||
std::string _strMethod;
|
||||
std::string _strUrl;
|
||||
std::string _strTail;
|
||||
std::string _strContent;
|
||||
std::string _strNull;
|
||||
std::string _method;
|
||||
std::string _url;
|
||||
std::string _protocol;
|
||||
std::string _content;
|
||||
std::string _params;
|
||||
mutable StrCaseMap _mapHeaders;
|
||||
mutable StrCaseMap _mapUrlArgs;
|
||||
mutable StrCaseMap _headers;
|
||||
mutable StrCaseMap _url_args;
|
||||
};
|
||||
|
||||
//解析rtsp url的工具类
|
||||
class RtspUrl{
|
||||
// 解析rtsp url的工具类
|
||||
class RtspUrl {
|
||||
public:
|
||||
bool _is_ssl;
|
||||
uint16_t _port;
|
||||
|
|
@ -134,9 +135,9 @@ public:
|
|||
void parse(const std::string &url);
|
||||
|
||||
private:
|
||||
void setup(bool,const std::string &, const std::string &, const std::string &);
|
||||
void setup(bool, const std::string &, const std::string &, const std::string &);
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
} // namespace mediakit
|
||||
|
||||
#endif //ZLMEDIAKIT_PARSER_H
|
||||
#endif // ZLMEDIAKIT_PARSER_H
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
#include "Stamp.h"
|
||||
|
||||
//时间戳最大允许跳变3秒,主要是防止网络抖动导致的跳变
|
||||
// 时间戳最大允许跳变3秒,主要是防止网络抖动导致的跳变
|
||||
#define MAX_DELTA_STAMP (3 * 1000)
|
||||
#define STAMP_LOOP_DELTA (60 * 1000)
|
||||
#define MAX_CTS 500
|
||||
|
|
@ -25,51 +25,52 @@ int64_t DeltaStamp::relativeStamp(int64_t stamp) {
|
|||
return _relative_stamp;
|
||||
}
|
||||
|
||||
int64_t DeltaStamp::relativeStamp(){
|
||||
int64_t DeltaStamp::relativeStamp() {
|
||||
return _relative_stamp;
|
||||
}
|
||||
|
||||
int64_t DeltaStamp::deltaStamp(int64_t stamp) {
|
||||
if(!_last_stamp){
|
||||
//第一次计算时间戳增量,时间戳增量为0
|
||||
if(stamp){
|
||||
if (!_last_stamp) {
|
||||
// 第一次计算时间戳增量,时间戳增量为0
|
||||
if (stamp) {
|
||||
_last_stamp = stamp;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t ret = stamp - _last_stamp;
|
||||
if(ret >= 0){
|
||||
//时间戳增量为正,返回之
|
||||
if (ret >= 0) {
|
||||
// 时间戳增量为正,返回之
|
||||
_last_stamp = stamp;
|
||||
//在直播情况下,时间戳增量不得大于MAX_DELTA_STAMP
|
||||
return ret < MAX_DELTA_STAMP ? ret : 0;
|
||||
// 在直播情况下,时间戳增量不得大于MAX_DELTA_STAMP,否则强制相对时间戳加1
|
||||
return ret < MAX_DELTA_STAMP ? ret : 1;
|
||||
}
|
||||
|
||||
//时间戳增量为负,说明时间戳回环了或回退了
|
||||
// 时间戳增量为负,说明时间戳回环了或回退了
|
||||
_last_stamp = stamp;
|
||||
//如果时间戳回退不多,那么返回负值
|
||||
return -ret < MAX_CTS ? ret : 0;
|
||||
|
||||
// 如果时间戳回退不多,那么返回负值,否则返回加1
|
||||
return -ret < MAX_DELTA_STAMP ? ret : 1;
|
||||
}
|
||||
|
||||
void Stamp::setPlayBack(bool playback) {
|
||||
_playback = playback;
|
||||
}
|
||||
|
||||
void Stamp::syncTo(Stamp &other){
|
||||
void Stamp::syncTo(Stamp &other) {
|
||||
_sync_master = &other;
|
||||
}
|
||||
|
||||
//限制dts回退
|
||||
void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) {
|
||||
// 限制dts回退
|
||||
void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) {
|
||||
revise_l(dts, pts, dts_out, pts_out, modifyStamp);
|
||||
if (_playback) {
|
||||
//回放允许时间戳回退
|
||||
// 回放允许时间戳回退
|
||||
return;
|
||||
}
|
||||
|
||||
if (dts_out < _last_dts_out) {
|
||||
// WarnL << "dts回退:" << dts_out << " < " << _last_dts_out;
|
||||
// WarnL << "dts回退:" << dts_out << " < " << _last_dts_out;
|
||||
dts_out = _last_dts_out;
|
||||
pts_out = _last_pts_out;
|
||||
return;
|
||||
|
|
@ -78,35 +79,35 @@ void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,
|
|||
_last_pts_out = pts_out;
|
||||
}
|
||||
|
||||
//音视频时间戳同步
|
||||
void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) {
|
||||
// 音视频时间戳同步
|
||||
void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) {
|
||||
revise_l2(dts, pts, dts_out, pts_out, modifyStamp);
|
||||
if (!_sync_master || modifyStamp || _playback) {
|
||||
//自动生成时间戳或回放或同步完毕
|
||||
// 自动生成时间戳或回放或同步完毕
|
||||
return;
|
||||
}
|
||||
|
||||
if (_sync_master && _sync_master->_last_dts_in) {
|
||||
//音视频dts当前时间差
|
||||
// 音视频dts当前时间差
|
||||
int64_t dts_diff = _last_dts_in - _sync_master->_last_dts_in;
|
||||
if (ABS(dts_diff) < 5000) {
|
||||
//如果绝对时间戳小于5秒,那么说明他们的起始时间戳是一致的,那么强制同步
|
||||
// 如果绝对时间戳小于5秒,那么说明他们的起始时间戳是一致的,那么强制同步
|
||||
_relative_stamp = _sync_master->_relative_stamp + dts_diff;
|
||||
}
|
||||
//下次不用再强制同步
|
||||
// 下次不用再强制同步
|
||||
_sync_master = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
//求取相对时间戳
|
||||
void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) {
|
||||
// 求取相对时间戳
|
||||
void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) {
|
||||
if (!pts) {
|
||||
//没有播放时间戳,使其赋值为解码时间戳
|
||||
// 没有播放时间戳,使其赋值为解码时间戳
|
||||
pts = dts;
|
||||
}
|
||||
|
||||
if (_playback) {
|
||||
//这是点播
|
||||
// 这是点播
|
||||
dts_out = dts;
|
||||
pts_out = pts;
|
||||
_relative_stamp = dts_out;
|
||||
|
|
@ -114,13 +115,13 @@ void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_o
|
|||
return;
|
||||
}
|
||||
|
||||
//pts和dts的差值
|
||||
// pts和dts的差值
|
||||
int64_t pts_dts_diff = pts - dts;
|
||||
|
||||
if (_last_dts_in != dts) {
|
||||
//时间戳发生变更
|
||||
// 时间戳发生变更
|
||||
if (modifyStamp) {
|
||||
//内部自己生产时间戳
|
||||
// 内部自己生产时间戳
|
||||
_relative_stamp = _ticker.elapsedTime();
|
||||
} else {
|
||||
_relative_stamp += deltaStamp(dts);
|
||||
|
|
@ -131,7 +132,7 @@ void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_o
|
|||
|
||||
//////////////以下是播放时间戳的计算//////////////////
|
||||
if (ABS(pts_dts_diff) > MAX_CTS) {
|
||||
//如果差值太大,则认为由于回环导致时间戳错乱了
|
||||
// 如果差值太大,则认为由于回环导致时间戳错乱了
|
||||
pts_dts_diff = 0;
|
||||
}
|
||||
|
||||
|
|
@ -146,156 +147,157 @@ int64_t Stamp::getRelativeStamp() const {
|
|||
return _relative_stamp;
|
||||
}
|
||||
|
||||
bool DtsGenerator::getDts(uint64_t pts, uint64_t &dts){
|
||||
bool DtsGenerator::getDts(uint64_t pts, uint64_t &dts) {
|
||||
bool ret = false;
|
||||
if (pts == _last_pts) {
|
||||
//pts未变,说明dts也不会变,返回上次dts
|
||||
// pts未变,说明dts也不会变,返回上次dts
|
||||
if (_last_dts) {
|
||||
dts = _last_dts;
|
||||
ret = true;
|
||||
}
|
||||
} else {
|
||||
//pts变了,尝试计算dts
|
||||
// pts变了,尝试计算dts
|
||||
ret = getDts_l(pts, dts);
|
||||
if (ret) {
|
||||
//获取到了dts,保存本次结果
|
||||
// 获取到了dts,保存本次结果
|
||||
_last_dts = dts;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
//pts排序列队长度还不知道,也就是不知道有没有B帧,
|
||||
//那么先强制dts == pts,这样可能导致有B帧的情况下,起始画面有几帧回退
|
||||
// pts排序列队长度还不知道,也就是不知道有没有B帧,
|
||||
// 那么先强制dts == pts,这样可能导致有B帧的情况下,起始画面有几帧回退
|
||||
dts = pts;
|
||||
}
|
||||
|
||||
//记录上次pts
|
||||
// 记录上次pts
|
||||
_last_pts = pts;
|
||||
return ret;
|
||||
}
|
||||
|
||||
//该算法核心思想是对pts进行排序,排序好的pts就是dts。
|
||||
//排序有一定的滞后性,那么需要加上排序导致的时间戳偏移量
|
||||
bool DtsGenerator::getDts_l(uint64_t pts, uint64_t &dts){
|
||||
if(_sorter_max_size == 1){
|
||||
//没有B帧,dts就等于pts
|
||||
// 该算法核心思想是对pts进行排序,排序好的pts就是dts。
|
||||
// 排序有一定的滞后性,那么需要加上排序导致的时间戳偏移量
|
||||
bool DtsGenerator::getDts_l(uint64_t pts, uint64_t &dts) {
|
||||
if (_sorter_max_size == 1) {
|
||||
// 没有B帧,dts就等于pts
|
||||
dts = pts;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!_sorter_max_size){
|
||||
//尚未计算出pts排序列队长度(也就是P帧间B帧个数)
|
||||
if(pts > _last_max_pts){
|
||||
//pts时间戳增加了,那么说明这帧画面不是B帧(说明是P帧或关键帧)
|
||||
if(_frames_since_last_max_pts && _count_sorter_max_size++ > 0){
|
||||
//已经出现多次非B帧的情况,那么我们就能知道P帧间B帧的个数
|
||||
if (!_sorter_max_size) {
|
||||
// 尚未计算出pts排序列队长度(也就是P帧间B帧个数)
|
||||
if (pts > _last_max_pts) {
|
||||
// pts时间戳增加了,那么说明这帧画面不是B帧(说明是P帧或关键帧)
|
||||
if (_frames_since_last_max_pts && _count_sorter_max_size++ > 0) {
|
||||
// 已经出现多次非B帧的情况,那么我们就能知道P帧间B帧的个数
|
||||
_sorter_max_size = _frames_since_last_max_pts;
|
||||
//我们记录P帧间时间间隔(也就是多个B帧时间戳增量累计)
|
||||
// 我们记录P帧间时间间隔(也就是多个B帧时间戳增量累计)
|
||||
_dts_pts_offset = (pts - _last_max_pts);
|
||||
//除以2,防止dts大于pts
|
||||
// 除以2,防止dts大于pts
|
||||
_dts_pts_offset /= 2;
|
||||
}
|
||||
//遇到P帧或关键帧,连续B帧计数清零
|
||||
// 遇到P帧或关键帧,连续B帧计数清零
|
||||
_frames_since_last_max_pts = 0;
|
||||
//记录上次非B帧的pts时间戳(同时也是dts),用于统计连续B帧时间戳增量
|
||||
// 记录上次非B帧的pts时间戳(同时也是dts),用于统计连续B帧时间戳增量
|
||||
_last_max_pts = pts;
|
||||
}
|
||||
//如果pts时间戳小于上一个P帧,那么断定这个是B帧,我们记录B帧连续个数
|
||||
// 如果pts时间戳小于上一个P帧,那么断定这个是B帧,我们记录B帧连续个数
|
||||
++_frames_since_last_max_pts;
|
||||
}
|
||||
|
||||
//pts放入排序缓存列队,缓存列队最大等于连续B帧个数
|
||||
// pts放入排序缓存列队,缓存列队最大等于连续B帧个数
|
||||
_pts_sorter.emplace(pts);
|
||||
|
||||
if(_sorter_max_size && _pts_sorter.size() > _sorter_max_size){
|
||||
//如果启用了pts排序(意味着存在B帧),并且pts排序缓存列队长度大于连续B帧个数,
|
||||
//意味着后续的pts都会比最早的pts大,那么说明可以取出最早的pts了,这个pts将当做该帧的dts基准
|
||||
if (_sorter_max_size && _pts_sorter.size() > _sorter_max_size) {
|
||||
// 如果启用了pts排序(意味着存在B帧),并且pts排序缓存列队长度大于连续B帧个数,
|
||||
// 意味着后续的pts都会比最早的pts大,那么说明可以取出最早的pts了,这个pts将当做该帧的dts基准
|
||||
auto it = _pts_sorter.begin();
|
||||
|
||||
//由于该pts是前面偏移了个_sorter_max_size帧的pts(也就是那帧画面的dts),
|
||||
//那么我们加上时间戳偏移量,基本等于该帧的dts
|
||||
// 由于该pts是前面偏移了个_sorter_max_size帧的pts(也就是那帧画面的dts),
|
||||
// 那么我们加上时间戳偏移量,基本等于该帧的dts
|
||||
dts = *it + _dts_pts_offset;
|
||||
if(dts > pts){
|
||||
//dts不能大于pts(基本不可能到达这个逻辑)
|
||||
if (dts > pts) {
|
||||
// dts不能大于pts(基本不可能到达这个逻辑)
|
||||
dts = pts;
|
||||
}
|
||||
|
||||
//pts排序缓存出列
|
||||
// pts排序缓存出列
|
||||
_pts_sorter.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
//排序缓存尚未满
|
||||
// 排序缓存尚未满
|
||||
return false;
|
||||
}
|
||||
|
||||
void NtpStamp::setNtpStamp(uint32_t rtp_stamp, uint64_t ntp_stamp_ms) {
|
||||
update(rtp_stamp, ntp_stamp_ms);
|
||||
}
|
||||
|
||||
void NtpStamp::update(uint32_t rtp_stamp, uint64_t ntp_stamp_ms) {
|
||||
if (ntp_stamp_ms == 0) {
|
||||
//实测发现有些rtsp服务器发送的rtp时间戳和ntp时间戳一直为0
|
||||
if (!ntp_stamp_ms || !rtp_stamp) {
|
||||
// 实测发现有些rtsp服务器发送的rtp时间戳和ntp时间戳一直为0
|
||||
WarnL << "Invalid sender report rtcp, ntp_stamp_ms = " << ntp_stamp_ms << ", rtp_stamp = " << rtp_stamp;
|
||||
return;
|
||||
}
|
||||
update(rtp_stamp, ntp_stamp_ms * 1000);
|
||||
}
|
||||
|
||||
void NtpStamp::update(uint32_t rtp_stamp, uint64_t ntp_stamp_us) {
|
||||
_last_rtp_stamp = rtp_stamp;
|
||||
_last_ntp_stamp_ms = ntp_stamp_ms;
|
||||
_last_ntp_stamp_us = ntp_stamp_us;
|
||||
}
|
||||
|
||||
uint64_t NtpStamp::getNtpStamp(uint32_t rtp_stamp, uint32_t sample_rate) {
|
||||
if (rtp_stamp == _last_rtp_stamp) {
|
||||
return _last_ntp_stamp_ms;
|
||||
return _last_ntp_stamp_us / 1000;
|
||||
}
|
||||
return getNtpStamp_l(rtp_stamp, sample_rate);
|
||||
return getNtpStampUS(rtp_stamp, sample_rate) / 1000;
|
||||
}
|
||||
|
||||
uint64_t NtpStamp::getNtpStamp_l(uint32_t rtp_stamp, uint32_t sample_rate) {
|
||||
if (!_last_ntp_stamp_ms) {
|
||||
//尚未收到sender report rtcp包,那么赋值为本地系统时间戳吧
|
||||
update(rtp_stamp, getCurrentMillisecond(true));
|
||||
uint64_t NtpStamp::getNtpStampUS(uint32_t rtp_stamp, uint32_t sample_rate) {
|
||||
if (!_last_ntp_stamp_us) {
|
||||
// 尚未收到sender report rtcp包,那么赋值为本地系统时间戳吧
|
||||
update(rtp_stamp, getCurrentMicrosecond(true));
|
||||
}
|
||||
|
||||
//rtp时间戳正增长
|
||||
// rtp时间戳正增长
|
||||
if (rtp_stamp >= _last_rtp_stamp) {
|
||||
auto diff = static_cast<int>((rtp_stamp - _last_rtp_stamp) / (sample_rate / 1000.0f));
|
||||
if (diff < MAX_DELTA_STAMP) {
|
||||
//时间戳正常增长
|
||||
update(rtp_stamp, _last_ntp_stamp_ms + diff);
|
||||
return _last_ntp_stamp_ms;
|
||||
auto diff_us = static_cast<int64_t>((rtp_stamp - _last_rtp_stamp) / (sample_rate / 1000000.0f));
|
||||
if (diff_us < MAX_DELTA_STAMP * 1000) {
|
||||
// 时间戳正常增长
|
||||
update(rtp_stamp, _last_ntp_stamp_us + diff_us);
|
||||
return _last_ntp_stamp_us;
|
||||
}
|
||||
|
||||
//时间戳大幅跳跃
|
||||
uint64_t loop_delta = STAMP_LOOP_DELTA * sample_rate / 1000;
|
||||
if (_last_rtp_stamp < loop_delta && rtp_stamp > UINT32_MAX - loop_delta) {
|
||||
//应该是rtp时间戳溢出+乱序
|
||||
uint64_t max_rtp_ms = uint64_t(UINT32_MAX) * 1000 / sample_rate;
|
||||
return _last_ntp_stamp_ms + diff - max_rtp_ms;
|
||||
// 时间戳大幅跳跃
|
||||
uint64_t loop_delta_hz = STAMP_LOOP_DELTA * sample_rate / 1000;
|
||||
if (_last_rtp_stamp < loop_delta_hz && rtp_stamp > UINT32_MAX - loop_delta_hz) {
|
||||
// 应该是rtp时间戳溢出+乱序
|
||||
uint64_t max_rtp_us = uint64_t(UINT32_MAX) * 1000000 / sample_rate;
|
||||
return _last_ntp_stamp_us + diff_us - max_rtp_us;
|
||||
}
|
||||
//不明原因的时间戳大幅跳跃,直接返回上次值
|
||||
// 不明原因的时间戳大幅跳跃,直接返回上次值
|
||||
WarnL << "rtp stamp abnormal increased:" << _last_rtp_stamp << " -> " << rtp_stamp;
|
||||
update(rtp_stamp, _last_ntp_stamp_ms);
|
||||
return _last_ntp_stamp_ms;
|
||||
update(rtp_stamp, _last_ntp_stamp_us);
|
||||
return _last_ntp_stamp_us;
|
||||
}
|
||||
|
||||
//rtp时间戳负增长
|
||||
auto diff = static_cast<int>((_last_rtp_stamp - rtp_stamp) / (sample_rate / 1000.0f));
|
||||
if (diff < MAX_DELTA_STAMP) {
|
||||
//正常范围的时间戳回退,说明收到rtp乱序了
|
||||
return _last_ntp_stamp_ms - diff;
|
||||
// rtp时间戳负增长
|
||||
auto diff_us = static_cast<int64_t>((_last_rtp_stamp - rtp_stamp) / (sample_rate / 1000000.0f));
|
||||
if (diff_us < MAX_DELTA_STAMP * 1000) {
|
||||
// 正常范围的时间戳回退,说明收到rtp乱序了
|
||||
return _last_ntp_stamp_us - diff_us;
|
||||
}
|
||||
|
||||
//时间戳大幅度回退
|
||||
uint64_t loop_delta = STAMP_LOOP_DELTA * sample_rate / 1000;
|
||||
if (rtp_stamp < loop_delta && _last_rtp_stamp > UINT32_MAX - loop_delta) {
|
||||
//确定是时间戳溢出
|
||||
uint64_t max_rtp_ms = uint64_t(UINT32_MAX) * 1000 / sample_rate;
|
||||
update(rtp_stamp, _last_ntp_stamp_ms + (max_rtp_ms - diff));
|
||||
return _last_ntp_stamp_ms;
|
||||
// 时间戳大幅度回退
|
||||
uint64_t loop_delta_hz = STAMP_LOOP_DELTA * sample_rate / 1000;
|
||||
if (rtp_stamp < loop_delta_hz && _last_rtp_stamp > UINT32_MAX - loop_delta_hz) {
|
||||
// 确定是时间戳溢出
|
||||
uint64_t max_rtp_us = uint64_t(UINT32_MAX) * 1000000 / sample_rate;
|
||||
update(rtp_stamp, _last_ntp_stamp_us + (max_rtp_us - diff_us));
|
||||
return _last_ntp_stamp_us;
|
||||
}
|
||||
//不明原因的时间戳回退,直接返回上次值
|
||||
// 不明原因的时间戳回退,直接返回上次值
|
||||
WarnL << "rtp stamp abnormal reduced:" << _last_rtp_stamp << " -> " << rtp_stamp;
|
||||
update(rtp_stamp, _last_ntp_stamp_ms);
|
||||
return _last_ntp_stamp_ms;
|
||||
update(rtp_stamp, _last_ntp_stamp_us);
|
||||
return _last_ntp_stamp_us;
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
} // namespace mediakit
|
||||
|
|
|
|||
|
|
@ -125,12 +125,12 @@ public:
|
|||
uint64_t getNtpStamp(uint32_t rtp_stamp, uint32_t sample_rate);
|
||||
|
||||
private:
|
||||
void update(uint32_t rtp_stamp, uint64_t ntp_stamp_ms);
|
||||
uint64_t getNtpStamp_l(uint32_t rtp_stamp, uint32_t sample_rate);
|
||||
void update(uint32_t rtp_stamp, uint64_t ntp_stamp_us);
|
||||
uint64_t getNtpStampUS(uint32_t rtp_stamp, uint32_t sample_rate);
|
||||
|
||||
private:
|
||||
uint32_t _last_rtp_stamp = 0;
|
||||
uint64_t _last_ntp_stamp_ms = 0;
|
||||
uint64_t _last_ntp_stamp_us = 0;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
#include "Common/config.h"
|
||||
#include "MediaSource.h"
|
||||
#include "Util/NoticeCenter.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/onceToken.h"
|
||||
|
|
@ -30,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;
|
||||
|
|
@ -98,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";
|
||||
|
|
@ -120,12 +123,14 @@ const string kTSDemand = PROTOCOL_FIELD "ts_demand";
|
|||
const string kFMP4Demand = PROTOCOL_FIELD "fmp4_demand";
|
||||
|
||||
static onceToken token([]() {
|
||||
mINI::Instance()[kModifyStamp] = 0;
|
||||
mINI::Instance()[kModifyStamp] = (int)ProtocolOption::kModifyStampRelative;
|
||||
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;
|
||||
|
|
@ -160,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;
|
||||
|
|
@ -188,6 +194,7 @@ static onceToken token([]() {
|
|||
mINI::Instance()[kForbidCacheSuffix] = "";
|
||||
mINI::Instance()[kForwardedIpHeader] = "";
|
||||
mINI::Instance()[kAllowCrossDomains] = 1;
|
||||
mINI::Instance()[kAllowIPRange] = "::1,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
|
||||
|
|
@ -208,6 +215,7 @@ const string kHandshakeSecond = RTSP_FIELD "handshakeSecond";
|
|||
const string kKeepAliveSecond = RTSP_FIELD "keepAliveSecond";
|
||||
const string kDirectProxy = RTSP_FIELD "directProxy";
|
||||
const string kLowLatency = RTSP_FIELD"lowLatency";
|
||||
const string kRtpTransportType = RTSP_FIELD"rtpTransportType";
|
||||
|
||||
static onceToken token([]() {
|
||||
// 默认Md5方式认证
|
||||
|
|
@ -216,18 +224,17 @@ static onceToken token([]() {
|
|||
mINI::Instance()[kKeepAliveSecond] = 15;
|
||||
mINI::Instance()[kDirectProxy] = 1;
|
||||
mINI::Instance()[kLowLatency] = 0;
|
||||
mINI::Instance()[kRtpTransportType] = -1;
|
||||
});
|
||||
} // namespace Rtsp
|
||||
|
||||
////////////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;
|
||||
});
|
||||
|
|
@ -241,15 +248,15 @@ const string kVideoMtuSize = RTP_FIELD "videoMtuSize";
|
|||
const string kAudioMtuSize = RTP_FIELD "audioMtuSize";
|
||||
// rtp包最大长度限制,单位是KB
|
||||
const string kRtpMaxSize = RTP_FIELD "rtpMaxSize";
|
||||
|
||||
const string kLowLatency = RTP_FIELD "lowLatency";
|
||||
const string kH264StapA = RTP_FIELD "h264_stap_a";
|
||||
|
||||
static onceToken token([]() {
|
||||
mINI::Instance()[kVideoMtuSize] = 1400;
|
||||
mINI::Instance()[kAudioMtuSize] = 600;
|
||||
mINI::Instance()[kRtpMaxSize] = 10;
|
||||
mINI::Instance()[kLowLatency] = 0;
|
||||
|
||||
mINI::Instance()[kH264StapA] = 1;
|
||||
});
|
||||
} // namespace Rtp
|
||||
|
||||
|
|
|
|||
|
|
@ -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配置///////////
|
||||
|
|
@ -273,6 +281,11 @@ extern const std::string kDirectProxy;
|
|||
|
||||
// rtsp 转发是否使用低延迟模式,当开启时,不会缓存rtp包,来提高并发,可以降低一帧的延迟
|
||||
extern const std::string kLowLatency;
|
||||
|
||||
//强制协商rtp传输方式 (0:TCP,1:UDP,2:MULTICAST,-1:不限制)
|
||||
//当客户端发起RTSP SETUP的时候如果传输类型和此配置不一致则返回461 Unsupport Transport
|
||||
//迫使客户端重新SETUP并切换到对应协议。目前支持FFMPEG和VLC
|
||||
extern const std::string kRtpTransportType;
|
||||
} // namespace Rtsp
|
||||
|
||||
////////////RTMP服务器配置///////////
|
||||
|
|
@ -293,6 +306,8 @@ extern const std::string kAudioMtuSize;
|
|||
extern const std::string kRtpMaxSize;
|
||||
// rtp 打包时,低延迟开关,默认关闭(为0),h264存在一帧多个slice(NAL)的情况,在这种情况下,如果开启可能会导致画面花屏
|
||||
extern const std::string kLowLatency;
|
||||
//H264 rtp打包模式是否采用stap-a模式(为了在老版本浏览器上兼容webrtc)还是采用Single NAL unit packet per H.264 模式
|
||||
extern const std::string kH264StapA;
|
||||
} // namespace Rtp
|
||||
|
||||
////////////组播配置///////////
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,10 +34,10 @@ bool CommonRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool){
|
|||
return false;
|
||||
}
|
||||
auto payload = rtp->getPayload();
|
||||
auto stamp = rtp->getStampMS();
|
||||
auto stamp = rtp->getStamp();
|
||||
auto seq = rtp->getSeq();
|
||||
|
||||
if (_frame->_dts != stamp || _frame->_buffer.size() > _max_frame_size) {
|
||||
if (_last_stamp != stamp || _frame->_buffer.size() > _max_frame_size) {
|
||||
//时间戳发生变化或者缓存超过MAX_FRAME_SIZE,则清空上帧数据
|
||||
if (!_frame->_buffer.empty()) {
|
||||
//有有效帧,则输出
|
||||
|
|
@ -46,7 +46,8 @@ bool CommonRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool){
|
|||
|
||||
//新的一帧数据
|
||||
obtainFrame();
|
||||
_frame->_dts = stamp;
|
||||
_frame->_dts = rtp->getStampMS();
|
||||
_last_stamp = stamp;
|
||||
_drop_flag = false;
|
||||
} else if (_last_seq != 0 && (uint16_t)(_last_seq + 1) != seq) {
|
||||
//时间戳未发生变化,但是seq却不连续,说明中间rtp丢包了,那么整帧应该废弃
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ private:
|
|||
private:
|
||||
bool _drop_flag = false;
|
||||
uint16_t _last_seq = 0;
|
||||
uint64_t _last_stamp = 0;
|
||||
size_t _max_frame_size;
|
||||
CodecId _codec;
|
||||
FrameImp::Ptr _frame;
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) {
|
|||
case CodecOpus : return std::make_shared<OpusTrack>();
|
||||
|
||||
case CodecAAC : {
|
||||
string aac_cfg_str = FindField(track->_fmtp.data(), "config=", ";");
|
||||
string aac_cfg_str = findSubString(track->_fmtp.data(), "config=", ";");
|
||||
if (aac_cfg_str.empty()) {
|
||||
aac_cfg_str = FindField(track->_fmtp.data(), "config=", nullptr);
|
||||
aac_cfg_str = findSubString(track->_fmtp.data(), "config=", nullptr);
|
||||
}
|
||||
if (aac_cfg_str.empty()) {
|
||||
//如果sdp中获取不到aac config信息,那么在rtp也无法获取,那么忽略该Track
|
||||
|
|
@ -67,8 +67,8 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) {
|
|||
//a=fmtp:96 packetization-mode=1;profile-level-id=42C01F;sprop-parameter-sets=Z0LAH9oBQBboQAAAAwBAAAAPI8YMqA==,aM48gA==
|
||||
auto map = Parser::parseArgs(track->_fmtp, ";", "=");
|
||||
auto sps_pps = map["sprop-parameter-sets"];
|
||||
string base64_SPS = FindField(sps_pps.data(), NULL, ",");
|
||||
string base64_PPS = FindField(sps_pps.data(), ",", NULL);
|
||||
string base64_SPS = findSubString(sps_pps.data(), NULL, ",");
|
||||
string base64_PPS = findSubString(sps_pps.data(), ",", NULL);
|
||||
auto sps = decodeBase64(base64_SPS);
|
||||
auto pps = decodeBase64(base64_PPS);
|
||||
if (sps.empty() || pps.empty()) {
|
||||
|
|
@ -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对象
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "H265.h"
|
||||
#include "Common/Parser.h"
|
||||
#include "Common/Stamp.h"
|
||||
#include "Common/MediaSource.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
|
@ -31,11 +32,11 @@ Frame::Ptr Frame::getCacheAbleFrame(const Frame::Ptr &frame){
|
|||
return std::make_shared<FrameCacheAble>(frame);
|
||||
}
|
||||
|
||||
FrameStamp::FrameStamp(Frame::Ptr frame, Stamp &stamp, bool modify_stamp)
|
||||
FrameStamp::FrameStamp(Frame::Ptr frame, Stamp &stamp, int modify_stamp)
|
||||
{
|
||||
_frame = std::move(frame);
|
||||
//覆盖时间戳
|
||||
stamp.revise(_frame->dts(), _frame->pts(), _dts, _pts, modify_stamp);
|
||||
// kModifyStampSystem时采用系统时间戳,kModifyStampRelative采用相对时间戳
|
||||
stamp.revise(_frame->dts(), _frame->pts(), _dts, _pts, modify_stamp == ProtocolOption::kModifyStampSystem);
|
||||
}
|
||||
|
||||
TrackType getTrackType(CodecId codecId) {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ typedef enum {
|
|||
XX(CodecVP8, TrackVideo, 7, "VP8", PSI_STREAM_VP8) \
|
||||
XX(CodecVP9, TrackVideo, 8, "VP9", PSI_STREAM_VP9) \
|
||||
XX(CodecAV1, TrackVideo, 9, "AV1", PSI_STREAM_AV1) \
|
||||
XX(CodecJPEG, TrackVideo, 10, "JPEG", PSI_STREAM_JPEG_2000)
|
||||
XX(CodecJPEG, TrackVideo, 10, "JPEG", PSI_STREAM_RESERVED)
|
||||
|
||||
typedef enum {
|
||||
CodecInvalid = -1,
|
||||
|
|
@ -492,7 +492,7 @@ private:
|
|||
class FrameStamp : public Frame {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<FrameStamp>;
|
||||
FrameStamp(Frame::Ptr frame, Stamp &stamp, bool modify_stamp);
|
||||
FrameStamp(Frame::Ptr frame, Stamp &stamp, int modify_stamp);
|
||||
~FrameStamp() override {}
|
||||
|
||||
uint64_t dts() const override { return (uint64_t)_dts; }
|
||||
|
|
|
|||
|
|
@ -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,23 +156,18 @@ 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;
|
||||
// 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;
|
||||
if (cts < 0) {
|
||||
cts = 0;
|
||||
}
|
||||
//cts
|
||||
// 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
|
||||
// 输出rtmp packet
|
||||
RtmpCodec::inputRtmp(_rtmp_packet);
|
||||
_rtmp_packet = nullptr;
|
||||
}, &_rtmp_packet->buffer);
|
||||
|
|
@ -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();
|
||||
|
|
@ -209,8 +205,8 @@ void H264RtpEncoder::insertConfigFrame(uint64_t pts){
|
|||
|
||||
void H264RtpEncoder::packRtp(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){
|
||||
if (len + 3 <= getMaxSize()) {
|
||||
//STAP-A模式打包小于MTU
|
||||
packRtpStapA(ptr, len, pts, is_mark, gop_pos);
|
||||
// 采用STAP-A/Single NAL unit packet per H.264 模式
|
||||
packRtpSmallFrame(ptr, len, pts, is_mark, gop_pos);
|
||||
} else {
|
||||
//STAP-A模式打包会大于MTU,所以采用FU-A模式
|
||||
packRtpFu(ptr, len, pts, is_mark, gop_pos);
|
||||
|
|
@ -220,8 +216,8 @@ void H264RtpEncoder::packRtp(const char *ptr, size_t len, uint64_t pts, bool is_
|
|||
void H264RtpEncoder::packRtpFu(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){
|
||||
auto packet_size = getMaxSize() - 2;
|
||||
if (len <= packet_size + 1) {
|
||||
//小于FU-A打包最小字节长度要求,采用STAP-A模式
|
||||
packRtpStapA(ptr, len, pts, is_mark, gop_pos);
|
||||
// 小于FU-A打包最小字节长度要求,采用STAP-A/Single NAL unit packet per H.264 模式
|
||||
packRtpSmallFrame(ptr, len, pts, is_mark, gop_pos);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -257,8 +253,17 @@ void H264RtpEncoder::packRtpFu(const char *ptr, size_t len, uint64_t pts, bool i
|
|||
}
|
||||
}
|
||||
|
||||
void H264RtpEncoder::packRtpSmallFrame(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos) {
|
||||
GET_CONFIG(bool, h264_stap_a, Rtp::kH264StapA);
|
||||
if (h264_stap_a) {
|
||||
packRtpStapA(data, len, pts, is_mark, gop_pos);
|
||||
} else {
|
||||
packRtpSingleNalu(data, len, pts, is_mark, gop_pos);
|
||||
}
|
||||
}
|
||||
|
||||
void H264RtpEncoder::packRtpStapA(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){
|
||||
//如果帧长度不超过mtu,为了兼容性 webrtc,采用STAP-A模式打包
|
||||
// 如果帧长度不超过mtu,为了兼容性 webrtc,采用STAP-A模式打包
|
||||
auto rtp = makeRtp(getTrackType(), nullptr, len + 3, is_mark, pts);
|
||||
uint8_t *payload = rtp->getPayload();
|
||||
//STAP-A
|
||||
|
|
@ -270,6 +275,11 @@ void H264RtpEncoder::packRtpStapA(const char *ptr, size_t len, uint64_t pts, boo
|
|||
RtpCodec::inputRtp(rtp, gop_pos);
|
||||
}
|
||||
|
||||
void H264RtpEncoder::packRtpSingleNalu(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos) {
|
||||
// Single NAL unit packet per H.264 模式
|
||||
RtpCodec::inputRtp(makeRtp(getTrackType(), data, len, is_mark, pts), gop_pos);
|
||||
}
|
||||
|
||||
bool H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||
auto ptr = frame->data() + frame->prefixSize();
|
||||
switch (H264_TYPE(ptr[0])) {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public:
|
|||
using Ptr = std::shared_ptr<H264RtpDecoder>;
|
||||
|
||||
H264RtpDecoder();
|
||||
~H264RtpDecoder() {}
|
||||
~H264RtpDecoder() override = default;
|
||||
|
||||
/**
|
||||
* 输入264 rtp包
|
||||
|
|
@ -77,7 +77,8 @@ public:
|
|||
uint32_t sample_rate = 90000,
|
||||
uint8_t pt = 96,
|
||||
uint8_t interleaved = TrackVideo * 2);
|
||||
~H264RtpEncoder() {}
|
||||
|
||||
~H264RtpEncoder() override = default;
|
||||
|
||||
/**
|
||||
* 输入264帧
|
||||
|
|
@ -96,6 +97,8 @@ private:
|
|||
void packRtp(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos);
|
||||
void packRtpFu(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos);
|
||||
void packRtpStapA(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos);
|
||||
void packRtpSingleNalu(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos);
|
||||
void packRtpSmallFrame(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos);
|
||||
|
||||
private:
|
||||
Frame::Ptr _sps;
|
||||
|
|
|
|||
|
|
@ -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(getH265ConfigFrame(*pkt,config)){
|
||||
onGetH265(config.data(), config.size(), pkt->time_stamp , pkt->time_stamp);
|
||||
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);
|
||||
}
|
||||
#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,28 +235,23 @@ 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;
|
||||
// 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;
|
||||
if (cts < 0) {
|
||||
cts = 0;
|
||||
}
|
||||
//cts
|
||||
// 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
|
||||
// 输出rtmp packet
|
||||
RtmpCodec::inputRtmp(_rtmp_packet);
|
||||
_rtmp_packet = nullptr;
|
||||
}, &_rtmp_packet->buffer);
|
||||
|
|
@ -204,21 +259,18 @@ bool H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
|||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ bool HlsParser::parse(const string &http_url, const string &m3u8) {
|
|||
|
||||
if ((_is_m3u8_inner || extinf_dur != 0) && line[0] != '#') {
|
||||
segment.duration = extinf_dur;
|
||||
segment.url = Parser::merge_url(http_url, line);
|
||||
segment.url = Parser::mergeUrl(http_url, line);
|
||||
if (!_is_m3u8_inner) {
|
||||
//ts按照先后顺序排序
|
||||
ts_map.emplace(index++, segment);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace mediakit {
|
|||
void HttpClient::sendRequest(const string &url) {
|
||||
clearResponse();
|
||||
_url = url;
|
||||
auto protocol = FindField(url.data(), NULL, "://");
|
||||
auto protocol = findSubString(url.data(), NULL, "://");
|
||||
uint16_t port;
|
||||
bool is_https;
|
||||
if (strcasecmp(protocol.data(), "http") == 0) {
|
||||
|
|
@ -35,11 +35,11 @@ void HttpClient::sendRequest(const string &url) {
|
|||
throw std::invalid_argument(strErr);
|
||||
}
|
||||
|
||||
auto host = FindField(url.data(), "://", "/");
|
||||
auto host = findSubString(url.data(), "://", "/");
|
||||
if (host.empty()) {
|
||||
host = FindField(url.data(), "://", NULL);
|
||||
host = findSubString(url.data(), "://", NULL);
|
||||
}
|
||||
_path = FindField(url.data(), host.data(), NULL);
|
||||
_path = findSubString(url.data(), host.data(), NULL);
|
||||
if (_path.empty()) {
|
||||
_path = "/";
|
||||
}
|
||||
|
|
@ -100,7 +100,7 @@ void HttpClient::clearResponse() {
|
|||
_header_recved = false;
|
||||
_recved_body_size = 0;
|
||||
_total_body_size = 0;
|
||||
_parser.Clear();
|
||||
_parser.clear();
|
||||
_chunked_splitter = nullptr;
|
||||
_wait_header.resetTime();
|
||||
_wait_body.resetTime();
|
||||
|
|
@ -181,20 +181,20 @@ void HttpClient::onError(const SockException &ex) {
|
|||
}
|
||||
|
||||
ssize_t HttpClient::onRecvHeader(const char *data, size_t len) {
|
||||
_parser.Parse(data);
|
||||
if (_parser.Url() == "302" || _parser.Url() == "301" || _parser.Url() == "303") {
|
||||
auto new_url = Parser::merge_url(_url, _parser["Location"]);
|
||||
_parser.parse(data, len);
|
||||
if (_parser.status() == "302" || _parser.status() == "301" || _parser.status() == "303") {
|
||||
auto new_url = Parser::mergeUrl(_url, _parser["Location"]);
|
||||
if (new_url.empty()) {
|
||||
throw invalid_argument("未找到Location字段(跳转url)");
|
||||
}
|
||||
if (onRedirectUrl(new_url, _parser.Url() == "302")) {
|
||||
if (onRedirectUrl(new_url, _parser.status() == "302")) {
|
||||
HttpClient::sendRequest(new_url);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
checkCookie(_parser.getHeader());
|
||||
onResponseHeader(_parser.Url(), _parser.getHeader());
|
||||
onResponseHeader(_parser.status(), _parser.getHeader());
|
||||
_header_recved = true;
|
||||
|
||||
if (_parser["Transfer-Encoding"] == "chunked") {
|
||||
|
|
@ -361,8 +361,8 @@ void HttpClient::checkCookie(HttpClient::HttpHeader &headers) {
|
|||
int index = 0;
|
||||
auto arg_vec = split(it_set_cookie->second, ";");
|
||||
for (string &key_val : arg_vec) {
|
||||
auto key = FindField(key_val.data(), NULL, "=");
|
||||
auto val = FindField(key_val.data(), "=", NULL);
|
||||
auto key = findSubString(key_val.data(), NULL, "=");
|
||||
auto val = findSubString(key_val.data(), "=", NULL);
|
||||
|
||||
if (index++ == 0) {
|
||||
cookie->setKeyVal(key, val);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
#include "HttpRequestSplitter.h"
|
||||
#include "HttpCookie.h"
|
||||
#include "HttpChunkedSplitter.h"
|
||||
#include "strCoding.h"
|
||||
#include "Common/strCoding.h"
|
||||
#include "HttpBody.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ using namespace toolkit;
|
|||
|
||||
namespace mediakit{
|
||||
|
||||
const char *getHttpStatusMessage(int status) {
|
||||
const char *HttpConst::getHttpStatusMessage(int status) {
|
||||
switch (status) {
|
||||
case 100: return "Continue";
|
||||
case 101: return "Switching Protocol";
|
||||
|
|
@ -196,7 +196,7 @@ static const char *s_mime_src[][2] = {
|
|||
{"avi", "video/x-msvideo"},
|
||||
};
|
||||
|
||||
const string &getHttpContentType(const char *name) {
|
||||
const string& HttpConst::getHttpContentType(const char *name) {
|
||||
const char *dot;
|
||||
dot = strrchr(name, '.');
|
||||
static StrCaseMap mapType;
|
||||
|
|
|
|||
|
|
@ -15,19 +15,25 @@
|
|||
|
||||
namespace mediakit{
|
||||
|
||||
/**
|
||||
class HttpConst {
|
||||
public:
|
||||
HttpConst() = delete;
|
||||
~HttpConst() = delete;
|
||||
|
||||
/**
|
||||
* 根据http错误代码获取字符说明
|
||||
* @param status 譬如404
|
||||
* @return 错误代码字符说明,譬如Not Found
|
||||
*/
|
||||
const char *getHttpStatusMessage(int status);
|
||||
static const char *getHttpStatusMessage(int status);
|
||||
|
||||
/**
|
||||
/**
|
||||
* 根据文件后缀返回http mime
|
||||
* @param name 文件后缀,譬如html
|
||||
* @return mime值,譬如text/html
|
||||
*/
|
||||
const std::string &getHttpContentType(const char *name);
|
||||
static const std::string &getHttpContentType(const char *name);
|
||||
};
|
||||
|
||||
}//mediakit
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ bool HttpServerCookie::isExpired() {
|
|||
return _ticker.elapsedTime() > _max_elapsed * 1000;
|
||||
}
|
||||
|
||||
void HttpServerCookie::setAttach(std::shared_ptr<void> attach) {
|
||||
void HttpServerCookie::setAttach(toolkit::Any attach) {
|
||||
_attach = std::move(attach);
|
||||
}
|
||||
|
||||
|
|
@ -114,8 +114,7 @@ void HttpCookieManager::onManager() {
|
|||
}
|
||||
}
|
||||
|
||||
HttpServerCookie::Ptr HttpCookieManager::addCookie(const string &cookie_name, const string &uid_in,
|
||||
uint64_t max_elapsed, std::shared_ptr<void> attach, int max_client) {
|
||||
HttpServerCookie::Ptr HttpCookieManager::addCookie(const string &cookie_name, const string &uid_in, uint64_t max_elapsed, toolkit::Any attach, int max_client) {
|
||||
lock_guard<recursive_mutex> lck(_mtx_cookie);
|
||||
auto cookie = _generator.obtain();
|
||||
auto uid = uid_in.empty() ? cookie : uid_in;
|
||||
|
|
@ -158,9 +157,9 @@ HttpServerCookie::Ptr HttpCookieManager::getCookie(const string &cookie_name, co
|
|||
if (it == http_header.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto cookie = FindField(it->second.data(), (cookie_name + "=").data(), ";");
|
||||
auto cookie = findSubString(it->second.data(), (cookie_name + "=").data(), ";");
|
||||
if (cookie.empty()) {
|
||||
cookie = FindField(it->second.data(), (cookie_name + "=").data(), nullptr);
|
||||
cookie = findSubString(it->second.data(), (cookie_name + "=").data(), nullptr);
|
||||
}
|
||||
if (cookie.empty()) {
|
||||
return nullptr;
|
||||
|
|
|
|||
|
|
@ -85,14 +85,14 @@ public:
|
|||
/**
|
||||
* 设置附加数据
|
||||
*/
|
||||
void setAttach(std::shared_ptr<void> attach);
|
||||
void setAttach(toolkit::Any attach);
|
||||
|
||||
/*
|
||||
* 获取附加数据
|
||||
*/
|
||||
template <class T>
|
||||
T& getAttach() {
|
||||
return *static_cast<T *>(_attach.get());
|
||||
return _attach.get<T>();
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
@ -104,7 +104,7 @@ private:
|
|||
std::string _cookie_uuid;
|
||||
uint64_t _max_elapsed;
|
||||
toolkit::Ticker _ticker;
|
||||
std::shared_ptr<void> _attach;
|
||||
toolkit::Any _attach;
|
||||
std::weak_ptr<HttpCookieManager> _manager;
|
||||
};
|
||||
|
||||
|
|
@ -163,7 +163,7 @@ public:
|
|||
*/
|
||||
HttpServerCookie::Ptr addCookie(
|
||||
const std::string &cookie_name, const std::string &uid, uint64_t max_elapsed = COOKIE_DEFAULT_LIFE,
|
||||
std::shared_ptr<void> attach = nullptr,
|
||||
toolkit::Any = toolkit::Any{},
|
||||
int max_client = 1);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
#include "Record/HlsMediaSource.h"
|
||||
#include "Common/Parser.h"
|
||||
#include "Common/config.h"
|
||||
#include "strCoding.h"
|
||||
#include "Common/strCoding.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
|
@ -31,12 +31,16 @@ namespace mediakit {
|
|||
// 每次访问一次该cookie,那么将重新刷新cookie有效期
|
||||
// 假如播放器在60秒内都未访问该cookie,那么将重新触发hls播放鉴权
|
||||
static int kHlsCookieSecond = 60;
|
||||
static int kFindSrcIntervalSecond = 3;
|
||||
static const string kCookieName = "ZL_COOKIE";
|
||||
static const string kHlsSuffix = "/hls.m3u8";
|
||||
static const string kHlsFMP4Suffix = "/hls.fmp4.m3u8";
|
||||
|
||||
struct HttpCookieAttachment {
|
||||
//是否已经查找到过MediaSource
|
||||
// 是否已经查找到过MediaSource
|
||||
bool _find_src = false;
|
||||
// 查找MediaSource计时
|
||||
Ticker _find_src_ticker;
|
||||
//cookie生效作用域,本cookie只对该目录下的文件生效
|
||||
string _path;
|
||||
//上次鉴权失败信息,为空则上次鉴权成功
|
||||
|
|
@ -46,7 +50,112 @@ struct HttpCookieAttachment {
|
|||
};
|
||||
|
||||
const string &HttpFileManager::getContentType(const char *name) {
|
||||
return getHttpContentType(name);
|
||||
return HttpConst::getHttpContentType(name);
|
||||
}
|
||||
|
||||
namespace {
|
||||
class UInt128 {
|
||||
public:
|
||||
UInt128() = default;
|
||||
|
||||
UInt128(const struct sockaddr_storage &storage) {
|
||||
_family = storage.ss_family;
|
||||
memset(_bytes, 0, 16);
|
||||
switch (storage.ss_family) {
|
||||
case AF_INET: {
|
||||
memcpy(_bytes, &(reinterpret_cast<const struct sockaddr_in &>(storage).sin_addr), 4);
|
||||
break;
|
||||
}
|
||||
case AF_INET6: {
|
||||
memcpy(_bytes, &(reinterpret_cast<const struct sockaddr_in6 &>(storage).sin6_addr), 16);
|
||||
break;
|
||||
}
|
||||
default: CHECK(false, "Invalid socket family"); break;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const UInt128 &that) const { return _family == that._family && !memcmp(_bytes, that._bytes, 16); }
|
||||
|
||||
bool operator<=(const UInt128 &that) const { return *this < that || *this == that; }
|
||||
|
||||
bool operator>=(const UInt128 &that) const { return *this > that || *this == that; }
|
||||
|
||||
bool operator>(const UInt128 &that) const { return that < *this; }
|
||||
|
||||
bool operator<(const UInt128 &that) const {
|
||||
auto sz = _family == AF_INET ? 4 : 16;
|
||||
for (int i = 0; i < sz; ++i) {
|
||||
if (_bytes[i] < that._bytes[i]) {
|
||||
return true;
|
||||
} else if (_bytes[i] > that._bytes[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
operator bool() const { return _family != -1; }
|
||||
|
||||
bool same_type(const UInt128 &that) const { return _family == that._family; }
|
||||
|
||||
private:
|
||||
int _family = -1;
|
||||
uint8_t _bytes[16];
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
static UInt128 get_ip_uint64(const std::string &ip) {
|
||||
try {
|
||||
return UInt128(SockUtil::make_sockaddr(ip.data(), 0));
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
}
|
||||
return UInt128();
|
||||
}
|
||||
|
||||
bool HttpFileManager::isIPAllowed(const std::string &ip) {
|
||||
using IPRangs = std::vector<std::pair<UInt128 /*min_ip*/, UInt128 /*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 && ip_min.same_type(ip_max)) {
|
||||
ret.emplace_back(ip_min, ip_max);
|
||||
} else {
|
||||
WarnL << "Invalid ip range or family: " << item;
|
||||
}
|
||||
} else if (range.size() == 1) {
|
||||
auto ip = get_ip_uint64(trim(range[0]));
|
||||
if (ip) {
|
||||
ret.emplace_back(ip, ip);
|
||||
} else {
|
||||
WarnL << "Invalid ip: " << item;
|
||||
}
|
||||
} 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.same_type(range.first) && ip_int >= range.first && ip_int <= range.second) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static string searchIndexFile(const string &dir){
|
||||
|
|
@ -57,7 +166,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 +297,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("");
|
||||
|
|
@ -240,8 +349,8 @@ public:
|
|||
static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo &media_info, bool is_dir,
|
||||
const function<void(const string &err_msg, const HttpServerCookie::Ptr &cookie)> &callback) {
|
||||
//获取用户唯一id
|
||||
auto uid = parser.Params();
|
||||
auto path = parser.Url();
|
||||
auto uid = parser.params();
|
||||
auto path = parser.url();
|
||||
|
||||
//先根据http头中的cookie字段获取cookie
|
||||
HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, parser.getHeader());
|
||||
|
|
@ -268,7 +377,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
|
|||
return;
|
||||
}
|
||||
//上次鉴权失败,但是如果url参数发生变更,那么也重新鉴权下
|
||||
if (parser.Params().empty() || parser.Params() == cookie->getUid()) {
|
||||
if (parser.params().empty() || parser.params() == cookie->getUid()) {
|
||||
//url参数未变,或者本来就没有url参数,那么判断本次请求为重复请求,无访问权限
|
||||
callback(attach._err_msg, update_cookie ? cookie : nullptr);
|
||||
return;
|
||||
|
|
@ -278,7 +387,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();
|
||||
|
|
@ -308,7 +417,9 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
|
|||
// hls相关信息
|
||||
attach->_hls_data = std::make_shared<HlsCookieData>(media_info, info);
|
||||
}
|
||||
callback(err_msg, HttpCookieManager::Instance().addCookie(kCookieName, uid, life_second, attach));
|
||||
toolkit::Any any;
|
||||
any.set(std::move(attach));
|
||||
callback(err_msg, HttpCookieManager::Instance().addCookie(kCookieName, uid, life_second, std::move(any)));
|
||||
} else {
|
||||
callback(err_msg, nullptr);
|
||||
}
|
||||
|
|
@ -320,10 +431,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 +466,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 +474,13 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
|
|||
}
|
||||
if (is_hls) {
|
||||
// hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
|
||||
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());
|
||||
|
|
@ -421,14 +537,15 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
|
|||
return;
|
||||
}
|
||||
|
||||
auto src = cookie->getAttach<HttpCookieAttachment>()._hls_data->getMediaSource();
|
||||
auto &attach = cookie->getAttach<HttpCookieAttachment>();
|
||||
auto src = attach._hls_data->getMediaSource();
|
||||
if (src) {
|
||||
//直接从内存获取m3u8索引文件(而不是从文件系统)
|
||||
// 直接从内存获取m3u8索引文件(而不是从文件系统)
|
||||
response_file(cookie, cb, file_path, parser, src->getIndexFile());
|
||||
return;
|
||||
}
|
||||
if (cookie->getAttach<HttpCookieAttachment>()._find_src) {
|
||||
//查找过MediaSource,但是流已经注销了,不用再查找
|
||||
if (attach._find_src && attach._find_src_ticker.elapsedTime() < kFindSrcIntervalSecond * 1000) {
|
||||
// 最近已经查找过MediaSource了,为了防止频繁查找导致占用全局互斥锁的问题,我们尝试直接从磁盘返回hls索引文件
|
||||
response_file(cookie, cb, file_path, parser);
|
||||
return;
|
||||
}
|
||||
|
|
@ -444,11 +561,14 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
|
|||
|
||||
auto &attach = cookie->getAttach<HttpCookieAttachment>();
|
||||
attach._hls_data->setMediaSource(hls);
|
||||
//添加HlsMediaSource的观看人数(HLS是按需生成的,这样可以触发HLS文件的生成)
|
||||
// 添加HlsMediaSource的观看人数(HLS是按需生成的,这样可以触发HLS文件的生成)
|
||||
attach._hls_data->addByteUsage(0);
|
||||
//标记找到MediaSource
|
||||
// 标记找到MediaSource
|
||||
attach._find_src = true;
|
||||
|
||||
// 重置查找MediaSource计时
|
||||
attach._find_src_ticker.resetTime();
|
||||
|
||||
// m3u8文件可能不存在, 等待m3u8索引文件按需生成
|
||||
hls->getIndexFile([response_file, file_path, cookie, cb, parser](const string &file) {
|
||||
response_file(cookie, cb, file_path, parser, file);
|
||||
|
|
@ -457,7 +577,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) {
|
||||
|
|
@ -469,11 +589,11 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess
|
|||
if (it != virtualPathMap.end()) {
|
||||
//访问的是virtualPath
|
||||
path = it->second;
|
||||
url = parser.Url().substr(1 + media_info.app.size());
|
||||
url = parser.url().substr(1 + media_info.app.size());
|
||||
} else {
|
||||
//访问的是rootPath
|
||||
path = rootPath;
|
||||
url = parser.Url();
|
||||
url = parser.url();
|
||||
}
|
||||
for (auto &ch : url) {
|
||||
if (ch == '\\') {
|
||||
|
|
@ -482,7 +602,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;
|
||||
}
|
||||
|
||||
|
|
@ -493,7 +620,7 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess
|
|||
* @param cb 回调对象
|
||||
*/
|
||||
void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFileManager::invoker &cb) {
|
||||
auto fullUrl = string(HTTP_SCHEMA) + "://" + parser["Host"] + parser.FullUrl();
|
||||
auto fullUrl = string(HTTP_SCHEMA) + "://" + parser["Host"] + parser.fullUrl();
|
||||
MediaInfo media_info(fullUrl);
|
||||
auto file_path = getFilePath(parser, media_info, sender);
|
||||
if (file_path.size() == 0) {
|
||||
|
|
@ -504,15 +631,18 @@ 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));
|
||||
if (!File::is_dir(file_path.data())) {
|
||||
// 不是文件夹
|
||||
parser.setUrl(pathCat(parser.url(), indexFile));
|
||||
accessFile(sender, parser, media_info, file_path, cb);
|
||||
return;
|
||||
}
|
||||
}
|
||||
string strMenu;
|
||||
//生成文件夹菜单索引
|
||||
if (!makeFolderMenu(parser.Url(), file_path, strMenu)) {
|
||||
if (!makeFolderMenu(parser.url(), file_path, strMenu)) {
|
||||
//文件夹不存在
|
||||
sendNotFound(cb);
|
||||
return;
|
||||
|
|
@ -600,8 +730,8 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
|||
if (!strRange.empty()) {
|
||||
//分节下载
|
||||
code = 206;
|
||||
auto iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data());
|
||||
auto iRangeEnd = atoll(FindField(strRange.data(), "-", nullptr).data());
|
||||
auto iRangeStart = atoll(findSubString(strRange.data(), "bytes=", "-").data());
|
||||
auto iRangeEnd = atoll(findSubString(strRange.data(), "-", nullptr).data());
|
||||
auto fileSize = fileBody->remainSize();
|
||||
if (iRangeEnd == 0) {
|
||||
iRangeEnd = fileSize - 1;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
#include <sys/stat.h>
|
||||
#include <algorithm>
|
||||
#include "Common/config.h"
|
||||
#include "strCoding.h"
|
||||
#include "Common/strCoding.h"
|
||||
#include "HttpSession.h"
|
||||
#include "HttpConst.h"
|
||||
#include "Util/base64.h"
|
||||
|
|
@ -24,20 +24,20 @@ using namespace toolkit;
|
|||
namespace mediakit {
|
||||
|
||||
HttpSession::HttpSession(const Socket::Ptr &pSock) : Session(pSock) {
|
||||
GET_CONFIG(uint32_t,keep_alive_sec,Http::kKeepAliveSecond);
|
||||
GET_CONFIG(uint32_t, keep_alive_sec, Http::kKeepAliveSecond);
|
||||
pSock->setSendTimeOutSecond(keep_alive_sec);
|
||||
}
|
||||
|
||||
HttpSession::~HttpSession() = default;
|
||||
|
||||
void HttpSession::Handle_Req_HEAD(ssize_t &content_len){
|
||||
//暂时全部返回200 OK,因为HTTP GET存在按需生成流的操作,所以不能按照HTTP GET的流程返回
|
||||
//如果直接返回404,那么又会导致按需生成流的逻辑失效,所以HTTP HEAD在静态文件或者已存在资源时才有效
|
||||
//对于按需生成流的直播场景并不适用
|
||||
void HttpSession::onHttpRequest_HEAD() {
|
||||
// 暂时全部返回200 OK,因为HTTP GET存在按需生成流的操作,所以不能按照HTTP GET的流程返回
|
||||
// 如果直接返回404,那么又会导致按需生成流的逻辑失效,所以HTTP HEAD在静态文件或者已存在资源时才有效
|
||||
// 对于按需生成流的直播场景并不适用
|
||||
sendResponse(200, false);
|
||||
}
|
||||
|
||||
void HttpSession::Handle_Req_OPTIONS(ssize_t &content_len) {
|
||||
void HttpSession::onHttpRequest_OPTIONS() {
|
||||
KeyValue header;
|
||||
header.emplace("Allow", "GET, POST, HEAD, OPTIONS");
|
||||
GET_CONFIG(bool, allow_cross_domains, Http::kAllowCrossDomains);
|
||||
|
|
@ -52,83 +52,140 @@ void HttpSession::Handle_Req_OPTIONS(ssize_t &content_len) {
|
|||
sendResponse(200, true, nullptr, header);
|
||||
}
|
||||
|
||||
ssize_t HttpSession::onRecvHeader(const char *header,size_t len) {
|
||||
typedef void (HttpSession::*HttpCMDHandle)(ssize_t &);
|
||||
static unordered_map<string, HttpCMDHandle> s_func_map;
|
||||
ssize_t HttpSession::onRecvHeader(const char *header, size_t len) {
|
||||
using func_type = void (HttpSession::*)();
|
||||
static unordered_map<string, func_type> s_func_map;
|
||||
static onceToken token([]() {
|
||||
s_func_map.emplace("GET",&HttpSession::Handle_Req_GET);
|
||||
s_func_map.emplace("DELETE",&HttpSession::Handle_Req_GET);
|
||||
s_func_map.emplace("POST",&HttpSession::Handle_Req_POST);
|
||||
s_func_map.emplace("HEAD",&HttpSession::Handle_Req_HEAD);
|
||||
s_func_map.emplace("OPTIONS",&HttpSession::Handle_Req_OPTIONS);
|
||||
}, nullptr);
|
||||
s_func_map.emplace("GET", &HttpSession::onHttpRequest_GET);
|
||||
s_func_map.emplace("POST", &HttpSession::onHttpRequest_POST);
|
||||
// DELETE命令用于whip/whep用,只用于触发http api
|
||||
s_func_map.emplace("DELETE", &HttpSession::onHttpRequest_POST);
|
||||
s_func_map.emplace("HEAD", &HttpSession::onHttpRequest_HEAD);
|
||||
s_func_map.emplace("OPTIONS", &HttpSession::onHttpRequest_OPTIONS);
|
||||
});
|
||||
|
||||
_parser.Parse(header);
|
||||
CHECK(_parser.Url()[0] == '/');
|
||||
_parser.parse(header, len);
|
||||
CHECK(_parser.url()[0] == '/');
|
||||
|
||||
urlDecode(_parser);
|
||||
string cmd = _parser.Method();
|
||||
auto &cmd = _parser.method();
|
||||
auto it = s_func_map.find(cmd);
|
||||
if (it == s_func_map.end()) {
|
||||
WarnP(this) << "不支持该命令:" << cmd;
|
||||
WarnP(this) << "Http method not supported: " << cmd;
|
||||
sendResponse(405, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
//跨域
|
||||
_origin = _parser["Origin"];
|
||||
size_t content_len;
|
||||
auto &content_len_str = _parser["Content-Length"];
|
||||
if (content_len_str.empty()) {
|
||||
if (it->first == "POST") {
|
||||
// Http post未指定长度,我们认为是不定长的body
|
||||
WarnL << "Received http post request without content-length, consider it to be unlimited length";
|
||||
content_len = SIZE_MAX;
|
||||
} else {
|
||||
content_len = 0;
|
||||
}
|
||||
} else {
|
||||
// 已经指定长度
|
||||
content_len = atoll(content_len_str.data());
|
||||
}
|
||||
|
||||
//默认后面数据不是content而是header
|
||||
ssize_t content_len = 0;
|
||||
(this->*(it->second))(content_len);
|
||||
if (content_len == 0) {
|
||||
//// 没有body的情况,直接触发回调 ////
|
||||
(this->*(it->second))();
|
||||
_parser.clear();
|
||||
// 如果设置了_on_recv_body, 那么说明后续要处理body
|
||||
return _on_recv_body ? -1 : 0;
|
||||
}
|
||||
|
||||
//清空解析器节省内存
|
||||
_parser.Clear();
|
||||
//返回content长度
|
||||
return content_len;
|
||||
GET_CONFIG(size_t, maxReqSize, Http::kMaxReqSize);
|
||||
if (content_len > maxReqSize) {
|
||||
//// 不定长body或超大body ////
|
||||
if (content_len != SIZE_MAX) {
|
||||
WarnL << "Http body size is too huge: " << content_len << " > " << maxReqSize
|
||||
<< ", please set " << Http::kMaxReqSize << " in config.ini file.";
|
||||
}
|
||||
|
||||
size_t received = 0;
|
||||
auto parser = std::move(_parser);
|
||||
_on_recv_body = [this, parser, received, content_len](const char *data, size_t len) mutable {
|
||||
received += len;
|
||||
onRecvUnlimitedContent(parser, data, len, content_len, received);
|
||||
if (received < content_len) {
|
||||
// 还没收满
|
||||
return true;
|
||||
}
|
||||
|
||||
// 收满了
|
||||
setContentLen(0);
|
||||
return false;
|
||||
};
|
||||
// 声明后续都是body;Http body在本对象缓冲,不通过HttpRequestSplitter保存
|
||||
return -1;
|
||||
}
|
||||
|
||||
//// body size明确指定且小于最大值的情况 ////
|
||||
auto body = std::make_shared<std::string>();
|
||||
// 预留一定的内存buffer,防止频繁的内存拷贝
|
||||
body->reserve(content_len);
|
||||
|
||||
_on_recv_body = [this, body, content_len, it](const char *data, size_t len) mutable {
|
||||
body->append(data, len);
|
||||
if (body->size() < content_len) {
|
||||
// 未收满数据
|
||||
return true;
|
||||
}
|
||||
|
||||
// 收集body完毕
|
||||
_parser.setContent(std::move(*body));
|
||||
(this->*(it->second))();
|
||||
_parser.clear();
|
||||
|
||||
// 后续是header
|
||||
setContentLen(0);
|
||||
return false;
|
||||
};
|
||||
|
||||
// 声明后续都是body;Http body在本对象缓冲,不通过HttpRequestSplitter保存
|
||||
return -1;
|
||||
}
|
||||
|
||||
void HttpSession::onRecvContent(const char *data,size_t len) {
|
||||
if(_contentCallBack){
|
||||
if(!_contentCallBack(data,len)){
|
||||
_contentCallBack = nullptr;
|
||||
}
|
||||
void HttpSession::onRecvContent(const char *data, size_t len) {
|
||||
if (_on_recv_body && !_on_recv_body(data, len)) {
|
||||
_on_recv_body = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpSession::onRecv(const Buffer::Ptr &pBuf) {
|
||||
_ticker.resetTime();
|
||||
input(pBuf->data(),pBuf->size());
|
||||
input(pBuf->data(), pBuf->size());
|
||||
}
|
||||
|
||||
void HttpSession::onError(const SockException& err) {
|
||||
void HttpSession::onError(const SockException &err) {
|
||||
if (_is_live_stream) {
|
||||
//flv/ts播放器
|
||||
// flv/ts播放器
|
||||
uint64_t duration = _ticker.createdTime() / 1000;
|
||||
WarnP(this) << "FLV/TS/FMP4播放器("
|
||||
<< _mediaInfo.shortUrl()
|
||||
<< ")断开:" << err
|
||||
<< ",耗时(s):" << duration;
|
||||
WarnP(this) << "FLV/TS/FMP4播放器(" << _mediaInfo.shortUrl() << ")断开:" << err << ",耗时(s):" << duration;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpSession::onManager() {
|
||||
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
|
||||
GET_CONFIG(uint32_t, keepAliveSec, Http::kKeepAliveSecond);
|
||||
|
||||
if(_ticker.elapsedTime() > keepAliveSec * 1000){
|
||||
//1分钟超时
|
||||
shutdown(SockException(Err_timeout,"session timeout"));
|
||||
if (_ticker.elapsedTime() > keepAliveSec * 1000) {
|
||||
// 1分钟超时
|
||||
shutdown(SockException(Err_timeout, "session timeout"));
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpSession::checkWebSocket(){
|
||||
bool HttpSession::checkWebSocket() {
|
||||
auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"];
|
||||
if (Sec_WebSocket_Key.empty()) {
|
||||
return false;
|
||||
|
|
@ -148,25 +205,31 @@ bool HttpSession::checkWebSocket(){
|
|||
sendResponse(101, false, nullptr, headerOut, nullptr, true);
|
||||
};
|
||||
|
||||
//判断是否为websocket-flv
|
||||
if (checkLiveStreamFlv(res_cb)) {
|
||||
//这里是websocket-flv直播请求
|
||||
auto res_cb_flv = [this, headerOut]() mutable {
|
||||
_live_over_websocket = true;
|
||||
headerOut.emplace("Cache-Control", "no-store");
|
||||
sendResponse(101, false, nullptr, headerOut, nullptr, true);
|
||||
};
|
||||
|
||||
// 判断是否为websocket-flv
|
||||
if (checkLiveStreamFlv(res_cb_flv)) {
|
||||
// 这里是websocket-flv直播请求
|
||||
return true;
|
||||
}
|
||||
|
||||
//判断是否为websocket-ts
|
||||
// 判断是否为websocket-ts
|
||||
if (checkLiveStreamTS(res_cb)) {
|
||||
//这里是websocket-ts直播请求
|
||||
// 这里是websocket-ts直播请求
|
||||
return true;
|
||||
}
|
||||
|
||||
//判断是否为websocket-fmp4
|
||||
// 判断是否为websocket-fmp4
|
||||
if (checkLiveStreamFMP4(res_cb)) {
|
||||
//这里是websocket-fmp4直播请求
|
||||
// 这里是websocket-fmp4直播请求
|
||||
return true;
|
||||
}
|
||||
|
||||
//这是普通的websocket连接
|
||||
// 这是普通的websocket连接
|
||||
if (!onWebSocketConnect(_parser)) {
|
||||
sendResponse(501, true, nullptr, headerOut);
|
||||
return true;
|
||||
|
|
@ -175,8 +238,8 @@ bool HttpSession::checkWebSocket(){
|
|||
return true;
|
||||
}
|
||||
|
||||
bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix, const function<void(const MediaSource::Ptr &src)> &cb){
|
||||
std::string url = _parser.Url();
|
||||
bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix, const function<void(const MediaSource::Ptr &src)> &cb) {
|
||||
std::string url = _parser.url();
|
||||
auto it = _parser.getUrlArgs().find("schema");
|
||||
if (it != _parser.getUrlArgs().end()) {
|
||||
if (strcasecmp(it->second.c_str(), schema.c_str())) {
|
||||
|
|
@ -186,57 +249,57 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffi
|
|||
} else {
|
||||
auto prefix_size = url_suffix.size();
|
||||
if (url.size() < prefix_size || strcasecmp(url.data() + (url.size() - prefix_size), url_suffix.data())) {
|
||||
//未找到后缀
|
||||
// 未找到后缀
|
||||
return false;
|
||||
}
|
||||
// url去除特殊后缀
|
||||
url.resize(url.size() - prefix_size);
|
||||
}
|
||||
|
||||
//带参数的url
|
||||
if (!_parser.Params().empty()) {
|
||||
// 带参数的url
|
||||
if (!_parser.params().empty()) {
|
||||
url += "?";
|
||||
url += _parser.Params();
|
||||
url += _parser.params();
|
||||
}
|
||||
|
||||
//解析带上协议+参数完整的url
|
||||
// 解析带上协议+参数完整的url
|
||||
_mediaInfo.parse(schema + "://" + _parser["Host"] + url);
|
||||
|
||||
if (_mediaInfo.app.empty() || _mediaInfo.stream.empty()) {
|
||||
//url不合法
|
||||
// url不合法
|
||||
return false;
|
||||
}
|
||||
|
||||
bool close_flag = !strcasecmp(_parser["Connection"].data(), "close");
|
||||
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
||||
|
||||
//鉴权结果回调
|
||||
// 鉴权结果回调
|
||||
auto onRes = [cb, weak_self, close_flag](const string &err) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
//本对象已经销毁
|
||||
// 本对象已经销毁
|
||||
return;
|
||||
}
|
||||
|
||||
if (!err.empty()) {
|
||||
//播放鉴权失败
|
||||
// 播放鉴权失败
|
||||
strong_self->sendResponse(401, close_flag, nullptr, KeyValue(), std::make_shared<HttpStringBody>(err));
|
||||
return;
|
||||
}
|
||||
|
||||
//异步查找直播流
|
||||
// 异步查找直播流
|
||||
MediaSource::findAsync(strong_self->_mediaInfo, strong_self, [weak_self, close_flag, cb](const MediaSource::Ptr &src) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
//本对象已经销毁
|
||||
// 本对象已经销毁
|
||||
return;
|
||||
}
|
||||
if (!src) {
|
||||
//未找到该流
|
||||
// 未找到该流
|
||||
strong_self->sendNotFound(close_flag);
|
||||
} else {
|
||||
strong_self->_is_live_stream = true;
|
||||
//触发回调
|
||||
// 触发回调
|
||||
cb(src);
|
||||
}
|
||||
});
|
||||
|
|
@ -248,38 +311,42 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffi
|
|||
}
|
||||
};
|
||||
|
||||
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("");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//http-fmp4 链接格式:http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2
|
||||
bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb){
|
||||
// http-fmp4 链接格式:http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2
|
||||
bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb) {
|
||||
return checkLiveStream(FMP4_SCHEMA, ".live.mp4", [this, cb](const MediaSource::Ptr &src) {
|
||||
auto fmp4_src = dynamic_pointer_cast<FMP4MediaSource>(src);
|
||||
assert(fmp4_src);
|
||||
if (!cb) {
|
||||
//找到源,发送http头,负载后续发送
|
||||
// 找到源,发送http头,负载后续发送
|
||||
sendResponse(200, false, HttpFileManager::getContentType(".mp4").data(), KeyValue(), nullptr, true);
|
||||
} else {
|
||||
//自定义发送http头
|
||||
// 自定义发送http头
|
||||
cb();
|
||||
}
|
||||
|
||||
//直播牺牲延时提升发送性能
|
||||
// 直播牺牲延时提升发送性能
|
||||
setSocketFlags();
|
||||
onWrite(std::make_shared<BufferString>(fmp4_src->getInitSegment()), true);
|
||||
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) {
|
||||
//本对象已经销毁
|
||||
// 本对象已经销毁
|
||||
return;
|
||||
}
|
||||
strong_self->shutdown(SockException(Err_shutdown, "fmp4 ring buffer detached"));
|
||||
|
|
@ -287,83 +354,84 @@ bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb){
|
|||
_fmp4_reader->setReadCB([weak_self](const FMP4MediaSource::RingDataType &fmp4_list) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
//本对象已经销毁
|
||||
// 本对象已经销毁
|
||||
return;
|
||||
}
|
||||
size_t i = 0;
|
||||
auto size = fmp4_list->size();
|
||||
fmp4_list->for_each([&](const FMP4Packet::Ptr &ts) {
|
||||
strong_self->onWrite(ts, ++i == size);
|
||||
});
|
||||
fmp4_list->for_each([&](const FMP4Packet::Ptr &ts) { strong_self->onWrite(ts, ++i == size); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//http-ts 链接格式:http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2
|
||||
bool HttpSession::checkLiveStreamTS(const function<void()> &cb){
|
||||
// http-ts 链接格式:http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2
|
||||
bool HttpSession::checkLiveStreamTS(const function<void()> &cb) {
|
||||
return checkLiveStream(TS_SCHEMA, ".live.ts", [this, cb](const MediaSource::Ptr &src) {
|
||||
auto ts_src = dynamic_pointer_cast<TSMediaSource>(src);
|
||||
assert(ts_src);
|
||||
if (!cb) {
|
||||
//找到源,发送http头,负载后续发送
|
||||
// 找到源,发送http头,负载后续发送
|
||||
sendResponse(200, false, HttpFileManager::getContentType(".ts").data(), KeyValue(), nullptr, true);
|
||||
} else {
|
||||
//自定义发送http头
|
||||
// 自定义发送http头
|
||||
cb();
|
||||
}
|
||||
|
||||
//直播牺牲延时提升发送性能
|
||||
// 直播牺牲延时提升发送性能
|
||||
setSocketFlags();
|
||||
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->setDetachCB([weak_self](){
|
||||
_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) {
|
||||
//本对象已经销毁
|
||||
// 本对象已经销毁
|
||||
return;
|
||||
}
|
||||
strong_self->shutdown(SockException(Err_shutdown,"ts ring buffer detached"));
|
||||
strong_self->shutdown(SockException(Err_shutdown, "ts ring buffer detached"));
|
||||
});
|
||||
_ts_reader->setReadCB([weak_self](const TSMediaSource::RingDataType &ts_list) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
//本对象已经销毁
|
||||
// 本对象已经销毁
|
||||
return;
|
||||
}
|
||||
size_t i = 0;
|
||||
auto size = ts_list->size();
|
||||
ts_list->for_each([&](const TSPacket::Ptr &ts) {
|
||||
strong_self->onWrite(ts, ++i == size);
|
||||
});
|
||||
ts_list->for_each([&](const TSPacket::Ptr &ts) { strong_self->onWrite(ts, ++i == size); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//http-flv 链接格式:http://vhost-url:port/app/streamid.live.flv?key1=value1&key2=value2
|
||||
bool HttpSession::checkLiveStreamFlv(const function<void()> &cb){
|
||||
// http-flv 链接格式:http://vhost-url:port/app/streamid.live.flv?key1=value1&key2=value2
|
||||
bool HttpSession::checkLiveStreamFlv(const function<void()> &cb) {
|
||||
auto start_pts = atoll(_parser.getUrlArgs()["starPts"].data());
|
||||
return checkLiveStream(RTMP_SCHEMA, ".live.flv", [this, cb, start_pts](const MediaSource::Ptr &src) {
|
||||
auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
|
||||
assert(rtmp_src);
|
||||
if (!cb) {
|
||||
//找到源,发送http头,负载后续发送
|
||||
sendResponse(200, false, HttpFileManager::getContentType(".flv").data(), KeyValue(), nullptr, true);
|
||||
// 找到源,发送http头,负载后续发送
|
||||
KeyValue headerOut;
|
||||
headerOut["Cache-Control"] = "no-store";
|
||||
sendResponse(200, false, HttpFileManager::getContentType(".flv").data(), headerOut, nullptr, true);
|
||||
} else {
|
||||
//自定义发送http头
|
||||
// 自定义发送http头
|
||||
cb();
|
||||
}
|
||||
//直播牺牲延时提升发送性能
|
||||
// 直播牺牲延时提升发送性能
|
||||
setSocketFlags();
|
||||
|
||||
//非H264/AAC时打印警告日志,防止用户提无效问题
|
||||
// 非H264/AAC时打印警告日志,防止用户提无效问题
|
||||
auto tracks = src->getTracks(false);
|
||||
for (auto &track : tracks) {
|
||||
switch (track->getCodecId()) {
|
||||
case CodecH264:
|
||||
case CodecAAC:
|
||||
break;
|
||||
case CodecAAC: break;
|
||||
default: {
|
||||
WarnP(this) << "flv播放器一般只支持H264和AAC编码,该编码格式可能不被播放器支持:" << track->getCodecName();
|
||||
break;
|
||||
|
|
@ -375,43 +443,39 @@ bool HttpSession::checkLiveStreamFlv(const function<void()> &cb){
|
|||
});
|
||||
}
|
||||
|
||||
void HttpSession::Handle_Req_GET(ssize_t &content_len) {
|
||||
Handle_Req_GET_l(content_len, true);
|
||||
}
|
||||
|
||||
void HttpSession::Handle_Req_GET_l(ssize_t &content_len, bool sendBody) {
|
||||
//先看看是否为WebSocket请求
|
||||
void HttpSession::onHttpRequest_GET() {
|
||||
// 先看看是否为WebSocket请求
|
||||
if (checkWebSocket()) {
|
||||
content_len = -1;
|
||||
_contentCallBack = [this](const char *data, size_t len) {
|
||||
WebSocketSplitter::decode((uint8_t *) data, len);
|
||||
//_contentCallBack是可持续的,后面还要处理后续数据
|
||||
// 后续都是websocket body数据
|
||||
_on_recv_body = [this](const char *data, size_t len) {
|
||||
WebSocketSplitter::decode((uint8_t *)data, len);
|
||||
// _contentCallBack是可持续的,后面还要处理后续数据
|
||||
return true;
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
if (emitHttpEvent(false)) {
|
||||
//拦截http api事件
|
||||
// 拦截http api事件
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkLiveStreamFlv()) {
|
||||
//拦截http-flv播放器
|
||||
// 拦截http-flv播放器
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkLiveStreamTS()) {
|
||||
//拦截http-ts播放器
|
||||
// 拦截http-ts播放器
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkLiveStreamFMP4()) {
|
||||
//拦截http-fmp4播放器
|
||||
// 拦截http-fmp4播放器
|
||||
return;
|
||||
}
|
||||
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(), "close");
|
||||
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
||||
HttpFileManager::onAccessPath(*this, _parser, [weak_self, bClose](int code, const string &content_type,
|
||||
const StrCaseMap &responseHeader, const HttpBody::Ptr &body) {
|
||||
|
|
@ -460,7 +524,7 @@ public:
|
|||
static bool onSocketFlushed(const AsyncSenderData::Ptr &data) {
|
||||
if (data->_read_complete) {
|
||||
if (data->_close_when_complete) {
|
||||
//发送完毕需要关闭socket
|
||||
// 发送完毕需要关闭socket
|
||||
shutdown(data->_session.lock());
|
||||
}
|
||||
return false;
|
||||
|
|
@ -470,13 +534,13 @@ public:
|
|||
data->_body->readDataAsync(sendBufSize, [data](const Buffer::Ptr &sendBuf) {
|
||||
auto session = data->_session.lock();
|
||||
if (!session) {
|
||||
//本对象已经销毁
|
||||
// 本对象已经销毁
|
||||
return;
|
||||
}
|
||||
session->async([data, sendBuf]() {
|
||||
auto session = data->_session.lock();
|
||||
if (!session) {
|
||||
//本对象已经销毁
|
||||
// 本对象已经销毁
|
||||
return;
|
||||
}
|
||||
onRequestData(data, session, sendBuf);
|
||||
|
|
@ -489,14 +553,14 @@ private:
|
|||
static void onRequestData(const AsyncSenderData::Ptr &data, const std::shared_ptr<HttpSession> &session, const Buffer::Ptr &sendBuf) {
|
||||
session->_ticker.resetTime();
|
||||
if (sendBuf && session->send(sendBuf) != -1) {
|
||||
//文件还未读完,还需要继续发送
|
||||
// 文件还未读完,还需要继续发送
|
||||
if (!session->isSocketBusy()) {
|
||||
//socket还可写,继续请求数据
|
||||
// socket还可写,继续请求数据
|
||||
onSocketFlushed(data);
|
||||
}
|
||||
return;
|
||||
}
|
||||
//文件写完了
|
||||
// 文件写完了
|
||||
data->_read_complete = true;
|
||||
if (!session->isSocketBusy() && data->_close_when_complete) {
|
||||
shutdown(session);
|
||||
|
|
@ -504,34 +568,25 @@ private:
|
|||
}
|
||||
|
||||
static void shutdown(const std::shared_ptr<HttpSession> &session) {
|
||||
if(session){
|
||||
if (session) {
|
||||
session->shutdown(SockException(Err_shutdown, StrPrinter << "close connection after send http body completed."));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static const string kDate = "Date";
|
||||
static const string kServer = "Server";
|
||||
static const string kConnection = "Connection";
|
||||
static const string kKeepAlive = "Keep-Alive";
|
||||
static const string kContentType = "Content-Type";
|
||||
static const string kContentLength = "Content-Length";
|
||||
static const string kAccessControlAllowOrigin = "Access-Control-Allow-Origin";
|
||||
static const string kAccessControlAllowCredentials = "Access-Control-Allow-Credentials";
|
||||
|
||||
void HttpSession::sendResponse(int code,
|
||||
bool bClose,
|
||||
const char *pcContentType,
|
||||
const HttpSession::KeyValue &header,
|
||||
const HttpBody::Ptr &body,
|
||||
bool no_content_length ){
|
||||
GET_CONFIG(string,charSet,Http::kCharSet);
|
||||
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
|
||||
bool no_content_length) {
|
||||
GET_CONFIG(string, charSet, Http::kCharSet);
|
||||
GET_CONFIG(uint32_t, keepAliveSec, Http::kKeepAliveSecond);
|
||||
|
||||
//body默认为空
|
||||
// body默认为空
|
||||
int64_t size = 0;
|
||||
if (body && body->remainSize()) {
|
||||
//有body,获取body大小
|
||||
// 有body,获取body大小
|
||||
size = body->remainSize();
|
||||
}
|
||||
|
||||
|
|
@ -539,52 +594,53 @@ void HttpSession::sendResponse(int code,
|
|||
// http-flv直播是Keep-Alive类型
|
||||
bClose = false;
|
||||
} else if ((size_t)size >= SIZE_MAX || size < 0) {
|
||||
//不固定长度的body,那么发送完body后应该关闭socket,以便浏览器做下载完毕的判断
|
||||
// 不固定长度的body,那么发送完body后应该关闭socket,以便浏览器做下载完毕的判断
|
||||
bClose = true;
|
||||
}
|
||||
|
||||
HttpSession::KeyValue &headerOut = const_cast<HttpSession::KeyValue &>(header);
|
||||
headerOut.emplace(kDate, dateStr());
|
||||
headerOut.emplace(kServer, kServerName);
|
||||
headerOut.emplace(kConnection, bClose ? "close" : "keep-alive");
|
||||
headerOut.emplace("Date", dateStr());
|
||||
headerOut.emplace("Server", kServerName);
|
||||
headerOut.emplace("Connection", bClose ? "close" : "keep-alive");
|
||||
|
||||
GET_CONFIG(bool, allow_cross_domains, Http::kAllowCrossDomains);
|
||||
if (allow_cross_domains) {
|
||||
headerOut.emplace("Access-Control-Allow-Origin", "*");
|
||||
headerOut.emplace("Access-Control-Allow-Credentials", "true");
|
||||
}
|
||||
|
||||
if (!bClose) {
|
||||
string keepAliveString = "timeout=";
|
||||
keepAliveString += to_string(keepAliveSec);
|
||||
keepAliveString += ", max=100";
|
||||
headerOut.emplace(kKeepAlive, std::move(keepAliveString));
|
||||
}
|
||||
|
||||
if (!_origin.empty()) {
|
||||
//设置跨域
|
||||
headerOut.emplace(kAccessControlAllowOrigin, _origin);
|
||||
headerOut.emplace(kAccessControlAllowCredentials, "true");
|
||||
headerOut.emplace("Keep-Alive", std::move(keepAliveString));
|
||||
}
|
||||
|
||||
if (!no_content_length && size >= 0 && (size_t)size < SIZE_MAX) {
|
||||
//文件长度为固定值,且不是http-flv强制设置Content-Length
|
||||
headerOut[kContentLength] = to_string(size);
|
||||
// 文件长度为固定值,且不是http-flv强制设置Content-Length
|
||||
headerOut["Content-Length"] = to_string(size);
|
||||
}
|
||||
|
||||
if (size && !pcContentType) {
|
||||
//有body时,设置缺省类型
|
||||
// 有body时,设置缺省类型
|
||||
pcContentType = "text/plain";
|
||||
}
|
||||
|
||||
if ((size || no_content_length) && pcContentType) {
|
||||
//有body时,设置文件类型
|
||||
// 有body时,设置文件类型
|
||||
string strContentType = pcContentType;
|
||||
strContentType += "; charset=";
|
||||
strContentType += charSet;
|
||||
headerOut.emplace(kContentType, std::move(strContentType));
|
||||
headerOut.emplace("Content-Type", std::move(strContentType));
|
||||
}
|
||||
|
||||
//发送http头
|
||||
// 发送http头
|
||||
string str;
|
||||
str.reserve(256);
|
||||
str += "HTTP/1.1 ";
|
||||
str += to_string(code);
|
||||
str += ' ';
|
||||
str += getHttpStatusMessage(code);
|
||||
str += HttpConst::getHttpStatusMessage(code);
|
||||
str += "\r\n";
|
||||
for (auto &pr : header) {
|
||||
str += pr.first;
|
||||
|
|
@ -597,9 +653,9 @@ void HttpSession::sendResponse(int code,
|
|||
_ticker.resetTime();
|
||||
|
||||
if (!size) {
|
||||
//没有body
|
||||
// 没有body
|
||||
if (bClose) {
|
||||
shutdown(SockException(Err_shutdown,StrPrinter << "close connection after send http header completed with status code:" << code));
|
||||
shutdown(SockException(Err_shutdown, StrPrinter << "close connection after send http header completed with status code:" << code));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -614,20 +670,20 @@ void HttpSession::sendResponse(int code,
|
|||
|
||||
GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize);
|
||||
if (body->remainSize() > sendBufSize) {
|
||||
//文件下载提升发送性能
|
||||
// 文件下载提升发送性能
|
||||
setSocketFlags();
|
||||
}
|
||||
|
||||
//发送http body
|
||||
// 发送http body
|
||||
AsyncSenderData::Ptr data = std::make_shared<AsyncSenderData>(static_pointer_cast<HttpSession>(shared_from_this()), body, bClose);
|
||||
getSock()->setOnFlush([data]() { return AsyncSender::onSocketFlushed(data); });
|
||||
AsyncSender::onSocketFlushed(data);
|
||||
}
|
||||
|
||||
string HttpSession::urlDecode(const string &str){
|
||||
string HttpSession::urlDecode(const string &str) {
|
||||
auto ret = strCoding::UrlDecode(str);
|
||||
#ifdef _WIN32
|
||||
GET_CONFIG(string,charSet,Http::kCharSet);
|
||||
GET_CONFIG(string, charSet, Http::kCharSet);
|
||||
bool isGb2312 = !strcasecmp(charSet.data(), "gb2312");
|
||||
if (isGb2312) {
|
||||
ret = strCoding::UTF8ToGB2312(ret);
|
||||
|
|
@ -636,117 +692,51 @@ string HttpSession::urlDecode(const string &str){
|
|||
return ret;
|
||||
}
|
||||
|
||||
void HttpSession::urlDecode(Parser &parser){
|
||||
parser.setUrl(urlDecode(parser.Url()));
|
||||
for(auto &pr : _parser.getUrlArgs()){
|
||||
void HttpSession::urlDecode(Parser &parser) {
|
||||
parser.setUrl(urlDecode(parser.url()));
|
||||
for (auto &pr : _parser.getUrlArgs()) {
|
||||
const_cast<string &>(pr.second) = urlDecode(pr.second);
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpSession::emitHttpEvent(bool doInvoke){
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||
bool HttpSession::emitHttpEvent(bool doInvoke) {
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(), "close");
|
||||
/////////////////////异步回复Invoker///////////////////////////////
|
||||
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
||||
HttpResponseInvoker invoker = [weak_self,bClose](int code, const KeyValue &headerOut, const HttpBody::Ptr &body){
|
||||
HttpResponseInvoker invoker = [weak_self, bClose](int code, const KeyValue &headerOut, const HttpBody::Ptr &body) {
|
||||
auto strong_self = weak_self.lock();
|
||||
if(!strong_self) {
|
||||
if (!strong_self) {
|
||||
return;
|
||||
}
|
||||
strong_self->async([weak_self, bClose, code, headerOut, body]() {
|
||||
auto strong_self = weak_self.lock();
|
||||
if (!strong_self) {
|
||||
//本对象已经销毁
|
||||
// 本对象已经销毁
|
||||
return;
|
||||
}
|
||||
strong_self->sendResponse(code, bClose, nullptr, headerOut, body);
|
||||
});
|
||||
};
|
||||
///////////////////广播HTTP事件///////////////////////////
|
||||
bool consumed = false;//该事件是否被消费
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpRequest,_parser,invoker,consumed,static_cast<SockInfo &>(*this));
|
||||
if(!consumed && doInvoke){
|
||||
//该事件无人消费,所以返回404
|
||||
invoker(404,KeyValue(), HttpBody::Ptr());
|
||||
bool consumed = false; // 该事件是否被消费
|
||||
NOTICE_EMIT(BroadcastHttpRequestArgs, Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, *this);
|
||||
if (!consumed && doInvoke) {
|
||||
// 该事件无人消费,所以返回404
|
||||
invoker(404, KeyValue(), HttpBody::Ptr());
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
std::string HttpSession::get_peer_ip() {
|
||||
GET_CONFIG(string, forwarded_ip_header, Http::kForwardedIpHeader);
|
||||
if(!forwarded_ip_header.empty() && !_parser.getHeader()[forwarded_ip_header].empty()){
|
||||
if (!forwarded_ip_header.empty() && !_parser.getHeader()[forwarded_ip_header].empty()) {
|
||||
return _parser.getHeader()[forwarded_ip_header];
|
||||
}
|
||||
return Session::get_peer_ip();
|
||||
}
|
||||
|
||||
void HttpSession::Handle_Req_POST(ssize_t &content_len) {
|
||||
GET_CONFIG(size_t,maxReqSize,Http::kMaxReqSize);
|
||||
|
||||
ssize_t totalContentLen = _parser["Content-Length"].empty() ? -1 : atoll(_parser["Content-Length"].data());
|
||||
|
||||
if(totalContentLen == 0){
|
||||
//content为空
|
||||
//emitHttpEvent内部会选择是否关闭连接
|
||||
void HttpSession::onHttpRequest_POST() {
|
||||
emitHttpEvent(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if(totalContentLen > 0 && (size_t)totalContentLen < maxReqSize ){
|
||||
//返回固定长度的content
|
||||
content_len = totalContentLen;
|
||||
auto parserCopy = _parser;
|
||||
_contentCallBack = [this,parserCopy](const char *data,size_t len){
|
||||
//恢复http头
|
||||
_parser = parserCopy;
|
||||
//设置content
|
||||
_parser.setContent(string(data,len));
|
||||
//触发http事件,emitHttpEvent内部会选择是否关闭连接
|
||||
emitHttpEvent(true);
|
||||
//清空数据,节省内存
|
||||
_parser.Clear();
|
||||
//content已经接收完毕
|
||||
return false;
|
||||
};
|
||||
}else{
|
||||
//返回不固定长度的content或者超过长度限制的content
|
||||
content_len = -1;
|
||||
auto parserCopy = _parser;
|
||||
std::shared_ptr<size_t> recvedContentLen = std::make_shared<size_t>(0);
|
||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
||||
|
||||
_contentCallBack = [this,parserCopy,totalContentLen,recvedContentLen,bClose](const char *data,size_t len){
|
||||
*(recvedContentLen) += len;
|
||||
if (totalContentLen < 0) {
|
||||
//不固定长度的content,源源不断接收数据
|
||||
onRecvUnlimitedContent(parserCopy, data, len, SIZE_MAX, *(recvedContentLen));
|
||||
return true;
|
||||
}
|
||||
|
||||
//长度超过限制的content
|
||||
onRecvUnlimitedContent(parserCopy,data,len,totalContentLen,*(recvedContentLen));
|
||||
|
||||
if(*(recvedContentLen) < (size_t)totalContentLen){
|
||||
//数据还没接收完毕
|
||||
//_contentCallBack是可持续的,后面还要处理后续content数据
|
||||
return true;
|
||||
}
|
||||
|
||||
//数据接收完毕
|
||||
if(!bClose){
|
||||
//keep-alive类型连接
|
||||
//content接收完毕,后续都是http header
|
||||
setContentLen(0);
|
||||
//content已经接收完毕
|
||||
return false;
|
||||
}
|
||||
|
||||
//连接类型是close类型,收完content就关闭连接
|
||||
shutdown(SockException(Err_shutdown,"recv http content completed"));
|
||||
//content已经接收完毕
|
||||
return false ;
|
||||
};
|
||||
}
|
||||
//有后续content数据要处理,暂时不关闭连接
|
||||
}
|
||||
|
||||
void HttpSession::sendNotFound(bool bClose) {
|
||||
|
|
@ -754,19 +744,19 @@ void HttpSession::sendNotFound(bool bClose) {
|
|||
sendResponse(404, bClose, "text/html", KeyValue(), std::make_shared<HttpStringBody>(notFound));
|
||||
}
|
||||
|
||||
void HttpSession::setSocketFlags(){
|
||||
void HttpSession::setSocketFlags() {
|
||||
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
||||
if(mergeWriteMS > 0) {
|
||||
//推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高
|
||||
if (mergeWriteMS > 0) {
|
||||
// 推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高
|
||||
SockUtil::setNoDelay(getSock()->rawFD(), false);
|
||||
//播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能
|
||||
// 播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能
|
||||
setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) {
|
||||
if(flush){
|
||||
//需要flush那么一次刷新缓存
|
||||
if (flush) {
|
||||
// 需要flush那么一次刷新缓存
|
||||
HttpSession::setSendFlushFlag(true);
|
||||
}
|
||||
|
||||
|
|
@ -784,18 +774,18 @@ void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) {
|
|||
}
|
||||
|
||||
if (flush) {
|
||||
//本次刷新缓存后,下次不用刷新缓存
|
||||
// 本次刷新缓存后,下次不用刷新缓存
|
||||
HttpSession::setSendFlushFlag(false);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpSession::onWebSocketEncodeData(Buffer::Ptr buffer){
|
||||
void HttpSession::onWebSocketEncodeData(Buffer::Ptr buffer) {
|
||||
_total_bytes_usage += buffer->size();
|
||||
send(std::move(buffer));
|
||||
}
|
||||
|
||||
void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in){
|
||||
WebSocketHeader& header = const_cast<WebSocketHeader&>(header_in);
|
||||
void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in) {
|
||||
WebSocketHeader &header = const_cast<WebSocketHeader &>(header_in);
|
||||
header._mask_flag = false;
|
||||
|
||||
switch (header._opcode) {
|
||||
|
|
@ -805,15 +795,15 @@ void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in){
|
|||
break;
|
||||
}
|
||||
|
||||
default : break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpSession::onDetach() {
|
||||
shutdown(SockException(Err_shutdown,"rtmp ring buffer detached"));
|
||||
shutdown(SockException(Err_shutdown, "rtmp ring buffer detached"));
|
||||
}
|
||||
|
||||
std::shared_ptr<FlvMuxer> HttpSession::getSharedPtr(){
|
||||
std::shared_ptr<FlvMuxer> HttpSession::getSharedPtr() {
|
||||
return dynamic_pointer_cast<FlvMuxer>(shared_from_this());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -101,11 +101,10 @@ protected:
|
|||
std::string get_peer_ip() override;
|
||||
|
||||
private:
|
||||
void Handle_Req_GET(ssize_t &content_len);
|
||||
void Handle_Req_GET_l(ssize_t &content_len, bool sendBody);
|
||||
void Handle_Req_POST(ssize_t &content_len);
|
||||
void Handle_Req_HEAD(ssize_t &content_len);
|
||||
void Handle_Req_OPTIONS(ssize_t &content_len);
|
||||
void onHttpRequest_GET();
|
||||
void onHttpRequest_POST();
|
||||
void onHttpRequest_HEAD();
|
||||
void onHttpRequest_OPTIONS();
|
||||
|
||||
bool checkLiveStream(const std::string &schema, const std::string &url_suffix, const std::function<void(const MediaSource::Ptr &src)> &cb);
|
||||
|
||||
|
|
@ -132,13 +131,12 @@ private:
|
|||
bool _live_over_websocket = false;
|
||||
//消耗的总流量
|
||||
uint64_t _total_bytes_usage = 0;
|
||||
std::string _origin;
|
||||
Parser _parser;
|
||||
toolkit::Ticker _ticker;
|
||||
TSMediaSource::RingType::RingReader::Ptr _ts_reader;
|
||||
FMP4MediaSource::RingType::RingReader::Ptr _fmp4_reader;
|
||||
//处理content数据的callback
|
||||
std::function<bool (const char *data,size_t len) > _contentCallBack;
|
||||
std::function<bool (const char *data,size_t len) > _on_recv_body;
|
||||
};
|
||||
|
||||
using HttpsSession = toolkit::SessionWithSSL<HttpSession>;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include "PlayerBase.h"
|
||||
#include "Rtsp/RtspPlayerImp.h"
|
||||
#include "Rtmp/RtmpPlayerImp.h"
|
||||
#include "Rtmp/FlvPlayer.h"
|
||||
#include "Http/HlsPlayer.h"
|
||||
#include "Http/TsPlayerImp.h"
|
||||
|
||||
|
|
@ -20,15 +21,16 @@ using namespace toolkit;
|
|||
|
||||
namespace mediakit {
|
||||
|
||||
PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller, const string &url_in) {
|
||||
static auto releasePlayer = [](PlayerBase *ptr) {
|
||||
onceToken token(nullptr, [&]() {
|
||||
delete ptr;
|
||||
});
|
||||
PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &in_poller, const string &url_in) {
|
||||
auto poller = in_poller ? in_poller : EventPollerPool::Instance().getPoller();
|
||||
static auto releasePlayer = [poller](PlayerBase *ptr) {
|
||||
poller->async([ptr]() {
|
||||
onceToken token(nullptr, [&]() { delete ptr; });
|
||||
ptr->teardown();
|
||||
});
|
||||
};
|
||||
string url = url_in;
|
||||
string prefix = FindField(url.data(), NULL, "://");
|
||||
string prefix = findSubString(url.data(), NULL, "://");
|
||||
auto pos = url.find('?');
|
||||
if (pos != string::npos) {
|
||||
//去除?后面的字符串
|
||||
|
|
@ -53,9 +55,13 @@ PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller, const s
|
|||
if ((strcasecmp("http", prefix.data()) == 0 || strcasecmp("https", prefix.data()) == 0)) {
|
||||
if (end_with(url, ".m3u8") || end_with(url_in, ".m3u8")) {
|
||||
return PlayerBase::Ptr(new HlsPlayerImp(poller), releasePlayer);
|
||||
} else if (end_with(url, ".ts") || end_with(url_in, ".ts")) {
|
||||
}
|
||||
if (end_with(url, ".ts") || end_with(url_in, ".ts")) {
|
||||
return PlayerBase::Ptr(new TsPlayerImp(poller), releasePlayer);
|
||||
}
|
||||
if (end_with(url, ".flv") || end_with(url_in, ".flv")) {
|
||||
return PlayerBase::Ptr(new FlvPlayerImp(poller), releasePlayer);
|
||||
}
|
||||
}
|
||||
|
||||
throw std::invalid_argument("not supported play schema:" + url_in);
|
||||
|
|
|
|||
|
|
@ -202,7 +202,11 @@ PlayerProxy::~PlayerProxy() {
|
|||
_timer.reset();
|
||||
// 避免析构时, 忘记回调api请求
|
||||
if (_on_play) {
|
||||
try {
|
||||
_on_play(SockException(Err_shutdown, "player proxy close"));
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << "Exception occurred: " << ex.what();
|
||||
}
|
||||
_on_play = nullptr;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,7 @@ MediaPusher::MediaPusher(const string &schema,
|
|||
MediaPusher(MediaSource::find(schema, vhost, app, stream), poller){
|
||||
}
|
||||
|
||||
MediaPusher::~MediaPusher() {
|
||||
}
|
||||
MediaPusher::~MediaPusher() = default;
|
||||
|
||||
static void setOnCreateSocket_l(const std::shared_ptr<PusherBase> &delegate, const Socket::onCreateSocket &cb){
|
||||
auto helper = dynamic_pointer_cast<SocketHelper>(delegate);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ PusherBase::Ptr PusherBase::createPusher(const EventPoller::Ptr &poller,
|
|||
});
|
||||
ptr->teardown();
|
||||
};
|
||||
std::string prefix = FindField(url.data(), NULL, "://");
|
||||
std::string prefix = findSubString(url.data(), NULL, "://");
|
||||
|
||||
if (strcasecmp("rtsps",prefix.data()) == 0) {
|
||||
return PusherBase::Ptr(new TcpClientWithSSL<RtspPusherImp>(poller, std::dynamic_pointer_cast<RtspMediaSource>(src)), releasePusher);
|
||||
|
|
|
|||
|
|
@ -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,85 +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() {
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
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) {
|
||||
// 录像点播支持时移
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -150,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;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue