Merge branch 'master' of https://github.com/ixingqiao/ZLMediaKit
This commit is contained in:
commit
caf7c85090
|
|
@ -1,6 +1,17 @@
|
||||||
name: Docker
|
name: Docker
|
||||||
|
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "master"
|
||||||
|
- "feature/*"
|
||||||
|
- "release/*"
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "master"
|
||||||
|
- "feature/*"
|
||||||
|
- "release/*"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Use docker.io for Docker Hub if empty
|
# Use docker.io for Docker Hub if empty
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,11 @@
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# jsoncpp
|
# 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})
|
add_library(jsoncpp STATIC ${JSONCPP_SRC_LIST})
|
||||||
target_compile_options(jsoncpp
|
target_compile_options(jsoncpp
|
||||||
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
||||||
|
|
@ -43,44 +47,44 @@ set(MediaServer_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/media-server")
|
||||||
# TODO: 补一个函数处理各种库
|
# TODO: 补一个函数处理各种库
|
||||||
|
|
||||||
# 添加 mov、flv 库用于 MP4 录制
|
# 添加 mov、flv 库用于 MP4 录制
|
||||||
if(ENABLE_MP4)
|
if (ENABLE_MP4 OR ENABLE_HLS_FMP4)
|
||||||
message(STATUS "ENABLE_MP4 defined")
|
|
||||||
|
|
||||||
# MOV
|
# MOV
|
||||||
set(MediaServer_MOV_ROOT ${MediaServer_ROOT}/libmov)
|
set(MediaServer_MOV_ROOT ${MediaServer_ROOT}/libmov)
|
||||||
aux_source_directory(${MediaServer_MOV_ROOT}/include MOV_SRC_LIST)
|
aux_source_directory(${MediaServer_MOV_ROOT}/include MOV_SRC_LIST)
|
||||||
aux_source_directory(${MediaServer_MOV_ROOT}/source MOV_SRC_LIST)
|
aux_source_directory(${MediaServer_MOV_ROOT}/source MOV_SRC_LIST)
|
||||||
add_library(mov STATIC ${MOV_SRC_LIST})
|
add_library(mov STATIC ${MOV_SRC_LIST})
|
||||||
add_library(MediaServer::mov ALIAS mov)
|
add_library(MediaServer::mov ALIAS mov)
|
||||||
target_compile_definitions(mov
|
target_compile_options(mov PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
||||||
PUBLIC -DENABLE_MP4)
|
|
||||||
target_compile_options(mov
|
|
||||||
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
|
||||||
target_include_directories(mov
|
target_include_directories(mov
|
||||||
PRIVATE
|
PRIVATE
|
||||||
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>"
|
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>"
|
||||||
PUBLIC
|
PUBLIC
|
||||||
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>")
|
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>")
|
||||||
|
|
||||||
# FLV
|
# FLV
|
||||||
set(MediaServer_FLV_ROOT ${MediaServer_ROOT}/libflv)
|
set(MediaServer_FLV_ROOT ${MediaServer_ROOT}/libflv)
|
||||||
aux_source_directory(${MediaServer_FLV_ROOT}/include FLV_SRC_LIST)
|
aux_source_directory(${MediaServer_FLV_ROOT}/include FLV_SRC_LIST)
|
||||||
aux_source_directory(${MediaServer_FLV_ROOT}/source FLV_SRC_LIST)
|
aux_source_directory(${MediaServer_FLV_ROOT}/source FLV_SRC_LIST)
|
||||||
add_library(flv STATIC ${FLV_SRC_LIST})
|
add_library(flv STATIC ${FLV_SRC_LIST})
|
||||||
add_library(MediaServer::flv ALIAS flv)
|
add_library(MediaServer::flv ALIAS flv)
|
||||||
target_compile_options(flv
|
target_compile_options(flv PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
||||||
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
|
||||||
target_include_directories(flv
|
target_include_directories(flv
|
||||||
PRIVATE
|
PRIVATE
|
||||||
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>"
|
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>"
|
||||||
PUBLIC
|
PUBLIC
|
||||||
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>")
|
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>")
|
||||||
|
|
||||||
update_cached_list(MK_LINK_LIBRARIES
|
update_cached_list(MK_LINK_LIBRARIES MediaServer::flv MediaServer::mov)
|
||||||
MediaServer::flv MediaServer::mov)
|
|
||||||
update_cached_list(MK_COMPILE_DEFINITIONS
|
if (ENABLE_MP4)
|
||||||
ENABLE_MP4)
|
message(STATUS "ENABLE_MP4 defined")
|
||||||
endif()
|
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 生成
|
# 添加 mpeg 用于支持 ts 生成
|
||||||
if(ENABLE_RTPPROXY OR ENABLE_HLS)
|
if(ENABLE_RTPPROXY OR ENABLE_HLS)
|
||||||
|
|
@ -104,9 +108,11 @@ if(ENABLE_RTPPROXY OR ENABLE_HLS)
|
||||||
|
|
||||||
update_cached_list(MK_LINK_LIBRARIES MediaServer::mpeg)
|
update_cached_list(MK_LINK_LIBRARIES MediaServer::mpeg)
|
||||||
if(ENABLE_RTPPROXY)
|
if(ENABLE_RTPPROXY)
|
||||||
|
message(STATUS "ENABLE_RTPPROXY defined")
|
||||||
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_RTPPROXY)
|
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_RTPPROXY)
|
||||||
endif()
|
endif()
|
||||||
if(ENABLE_HLS)
|
if(ENABLE_HLS)
|
||||||
|
message(STATUS "ENABLE_HLS defined")
|
||||||
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS)
|
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit b30eeca034456417dfca72aa0d2258031013a5e6
|
Subproject commit b11582c38e8dbbb8d93ca9ce33c9a0b0cd58f59a
|
||||||
13
AUTHORS
13
AUTHORS
|
|
@ -73,4 +73,15 @@ WuPeng <wp@zafu.edu.cn>
|
||||||
[ljx0305](https://github.com/ljx0305)
|
[ljx0305](https://github.com/ljx0305)
|
||||||
[朱如洪 ](https://github.com/zhu410289616)
|
[朱如洪 ](https://github.com/zhu410289616)
|
||||||
[lijin](https://github.com/1461521844lijin)
|
[lijin](https://github.com/1461521844lijin)
|
||||||
[PioLing](https://github.com/PioLing)
|
[PioLing](https://github.com/PioLing)
|
||||||
|
[BackT0TheFuture](https://github.com/BackT0TheFuture)
|
||||||
|
[perara](https://github.com/perara)
|
||||||
|
[codeRATny](https://github.com/codeRATny)
|
||||||
|
[dengjfzh](https://github.com/dengjfzh)
|
||||||
|
[百鸣](https://github.com/ixingqiao)
|
||||||
|
[fruit Juice](https://github.com/xuandu)
|
||||||
|
[tbago](https://github.com/tbago)
|
||||||
|
[Luosh](https://github.com/Luosh)
|
||||||
|
[linxiaoyan87](https://github.com/linxiaoyan)
|
||||||
|
[waken](https://github.com/mc373906408)
|
||||||
|
[Deepslient](https://github.com/Deepslient)
|
||||||
|
|
@ -39,8 +39,10 @@ option(ENABLE_FAAC "Enable FAAC" OFF)
|
||||||
option(ENABLE_FFMPEG "Enable FFmpeg" OFF)
|
option(ENABLE_FFMPEG "Enable FFmpeg" OFF)
|
||||||
option(ENABLE_HLS "Enable HLS" ON)
|
option(ENABLE_HLS "Enable HLS" ON)
|
||||||
option(ENABLE_JEMALLOC_STATIC "Enable static linking to the jemalloc library" OFF)
|
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_MEM_DEBUG "Enable Memory Debug" OFF)
|
||||||
option(ENABLE_MP4 "Enable MP4" ON)
|
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_MSVC_MT "Enable MSVC Mt/Mtd lib" ON)
|
||||||
option(ENABLE_MYSQL "Enable MySQL" OFF)
|
option(ENABLE_MYSQL "Enable MySQL" OFF)
|
||||||
option(ENABLE_OPENSSL "Enable OpenSSL" ON)
|
option(ENABLE_OPENSSL "Enable OpenSSL" ON)
|
||||||
|
|
@ -200,8 +202,8 @@ if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# mediakit 以及各个 runtime 依赖
|
# mediakit 以及各个 runtime 依赖
|
||||||
update_cached_list(MK_LINK_LIBRARIES "")
|
update_cached(MK_LINK_LIBRARIES "")
|
||||||
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_VERSION)
|
update_cached(MK_COMPILE_DEFINITIONS ENABLE_VERSION)
|
||||||
|
|
||||||
if (DISABLE_REPORT)
|
if (DISABLE_REPORT)
|
||||||
update_cached_list(MK_COMPILE_DEFINITIONS DISABLE_REPORT)
|
update_cached_list(MK_COMPILE_DEFINITIONS DISABLE_REPORT)
|
||||||
|
|
@ -334,7 +336,11 @@ if(ENABLE_JEMALLOC_STATIC)
|
||||||
if(NOT EXISTS ${DEP_ROOT_DIR})
|
if(NOT EXISTS ${DEP_ROOT_DIR})
|
||||||
file(MAKE_DIRECTORY ${DEP_ROOT_DIR})
|
file(MAKE_DIRECTORY ${DEP_ROOT_DIR})
|
||||||
endif()
|
endif()
|
||||||
|
if (ENABLE_JEMALLOC_DUMP)
|
||||||
|
set(ENABLE_JEMALLOC_STAT ON)
|
||||||
|
else ()
|
||||||
|
set(ENABLE_JEMALLOC_STAT OFF)
|
||||||
|
endif ()
|
||||||
include(Jemalloc)
|
include(Jemalloc)
|
||||||
include_directories(SYSTEM ${DEP_ROOT_DIR}/${JEMALLOC_NAME}/include/jemalloc)
|
include_directories(SYSTEM ${DEP_ROOT_DIR}/${JEMALLOC_NAME}/include/jemalloc)
|
||||||
link_directories(${DEP_ROOT_DIR}/${JEMALLOC_NAME}/lib)
|
link_directories(${DEP_ROOT_DIR}/${JEMALLOC_NAME}/lib)
|
||||||
|
|
@ -348,6 +354,12 @@ if(JEMALLOC_FOUND)
|
||||||
message(STATUS "found library: ${JEMALLOC_LIBRARIES}")
|
message(STATUS "found library: ${JEMALLOC_LIBRARIES}")
|
||||||
include_directories(${JEMALLOC_INCLUDE_DIR})
|
include_directories(${JEMALLOC_INCLUDE_DIR})
|
||||||
update_cached_list(MK_LINK_LIBRARIES ${JEMALLOC_LIBRARIES})
|
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()
|
endif()
|
||||||
|
|
||||||
# 查找 openssl 是否安装
|
# 查找 openssl 是否安装
|
||||||
|
|
@ -449,11 +461,6 @@ if(ENABLE_API)
|
||||||
add_subdirectory(api)
|
add_subdirectory(api)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# IOS 不编译可执行程序
|
|
||||||
if(IOS)
|
|
||||||
return()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
if(ENABLE_PLAYER AND ENABLE_FFMPEG)
|
if(ENABLE_PLAYER AND ENABLE_FFMPEG)
|
||||||
|
|
@ -461,13 +468,20 @@ if(ENABLE_PLAYER AND ENABLE_FFMPEG)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
#MediaServer主程序
|
#MediaServer主程序
|
||||||
add_subdirectory(server)
|
if(ENABLE_SERVER)
|
||||||
|
add_subdirectory(server)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Android 会 add_subdirectory 并依赖该变量
|
# 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)
|
set(MK_LINK_LIBRARIES ${MK_LINK_LIBRARIES} PARENT_SCOPE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# IOS 不编译可执行程序
|
||||||
|
if(IOS)
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
#cpp测试demo程序
|
#cpp测试demo程序
|
||||||
if (ENABLE_TESTS)
|
if (ENABLE_TESTS)
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
|
|
|
||||||
78
README.md
78
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]
|
||||||
- RTSP[S] 服务器,支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备
|
- RTSP[S] 服务器,支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备
|
||||||
|
|
@ -63,14 +63,16 @@
|
||||||
- RTMP[S] 发布服务器,支持录制发布流
|
- RTMP[S] 发布服务器,支持录制发布流
|
||||||
- RTMP[S] 播放器,支持RTMP代理,支持生成静音音频
|
- RTMP[S] 播放器,支持RTMP代理,支持生成静音音频
|
||||||
- RTMP[S] 推流客户端
|
- RTMP[S] 推流客户端
|
||||||
- 支持http[s]-flv直播
|
- 支持http[s]-flv直播服务器
|
||||||
|
- 支持http[s]-flv直播播放器
|
||||||
- 支持websocket-flv直播
|
- 支持websocket-flv直播
|
||||||
- 支持H264/H265/AAC/G711/OPUS编码,其他编码能转发但不能转协议
|
- 支持H264/H265/AAC/G711/OPUS编码,其他编码能转发但不能转协议
|
||||||
- 支持[RTMP-H265](https://github.com/ksvc/FFmpeg/wiki)
|
- 支持[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)
|
- 支持[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
|
||||||
- 支持HLS文件生成,自带HTTP文件服务器
|
- 支持HLS文件(mpegts/fmp4)生成,自带HTTP文件服务器
|
||||||
- 通过cookie追踪技术,可以模拟HLS播放为长连接,可以实现HLS按需拉流、播放统计等业务
|
- 通过cookie追踪技术,可以模拟HLS播放为长连接,可以实现HLS按需拉流、播放统计等业务
|
||||||
- 支持HLS播发器,支持拉流HLS转rtsp/rtmp/mp4
|
- 支持HLS播发器,支持拉流HLS转rtsp/rtmp/mp4
|
||||||
- 支持H264/H265/AAC/G711/OPUS编码
|
- 支持H264/H265/AAC/G711/OPUS编码
|
||||||
|
|
@ -167,30 +169,36 @@ bash build_docker_images.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## 合作项目
|
## 合作项目
|
||||||
|
|
||||||
|
- 视频管理平台
|
||||||
|
- [wvp-GB28181-pro](https://github.com/648540858/wvp-GB28181-pro) java实现的开箱即用的GB28181协议视频平台
|
||||||
|
- [AKStream](https://github.com/chatop2020/AKStream) c#实现的全功能的软NVR接口/GB28181平台
|
||||||
|
- [BXC_SipServer](https://github.com/any12345com/BXC_SipServer) c++实现的国标GB28181流媒体信令服务器
|
||||||
|
- [gosip](https://github.com/panjjo/gosip) golang实现的GB28181服务器
|
||||||
|
- [FreeEhome](https://github.com/tsingeye/FreeEhome) golang实现的海康ehome服务器
|
||||||
|
|
||||||
|
- 播放器
|
||||||
|
- [h265web.js](https://github.com/numberwolf/h265web.js) 基于wasm支持H265的播放器,支持本项目多种专属协议
|
||||||
|
- [jessibuca](https://github.com/langhuihui/jessibuca) 基于wasm支持H265的播放器
|
||||||
|
- [wsPlayer](https://github.com/v354412101/wsPlayer) 基于MSE的websocket-fmp4播放器
|
||||||
|
- [BXC_gb28181Player](https://github.com/any12345com/BXC_gb28181Player) C++开发的支持国标GB28181协议的视频流播放器
|
||||||
|
|
||||||
- 可视化管理网站
|
- WEB管理网站
|
||||||
- [最新的前后端分离web项目,支持webrtc播放](https://github.com/langmansh/AKStreamNVR)
|
- [AKStreamNVR](https://github.com/langmansh/AKStreamNVR) 前后端分离web项目,支持webrtc播放
|
||||||
- [基于ZLMediaKit主线的管理WEB网站](https://gitee.com/kkkkk5G/MediaServerUI)
|
|
||||||
- [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI)
|
- SDK
|
||||||
- [一个非常漂亮的可视化后台管理系统](https://github.com/MingZhuLiu/ZLMediaServerManagent)
|
- [c# sdk](https://github.com/malegend/ZLMediaKit.Autogen) 本项目c sdk完整c#包装库
|
||||||
|
- [metaRTC](https://github.com/metartc/metaRTC) 全国产纯c webrtc sdk
|
||||||
- 流媒体管理平台
|
|
||||||
- [GB28181完整解决方案,自带web管理网站,支持webrtc、h265播放](https://github.com/648540858/wvp-GB28181-pro)
|
- 其他项目(已停止更新)
|
||||||
- [功能强大的流媒体控制管理接口平台,支持GB28181](https://github.com/chatop2020/AKStream)
|
- [NodeJS实现的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http)
|
||||||
- [Go实现的GB28181服务器](https://github.com/panjjo/gosip)
|
- [基于ZLMediaKit主线的管理WEB网站 ](https://gitee.com/kkkkk5G/MediaServerUI)
|
||||||
- [node-js版本的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http)
|
- [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI)
|
||||||
- [Go实现的海康ehome服务器](https://github.com/tsingeye/FreeEhome)
|
- [一个非常漂亮的可视化后台管理系统](https://github.com/MingZhuLiu/ZLMediaServerManagent)
|
||||||
|
|
||||||
- 客户端
|
|
||||||
- [c sdk完整c#包装库](https://github.com/malegend/ZLMediaKit.Autogen)
|
|
||||||
- [基于C SDK实现的推流客户端](https://github.com/hctym1995/ZLM_ApiDemo)
|
- [基于C SDK实现的推流客户端](https://github.com/hctym1995/ZLM_ApiDemo)
|
||||||
- [C#版本的Http API与Hook](https://github.com/chengxiaosheng/ZLMediaKit.HttpApi)
|
- [C#版本的Http API与Hook](https://github.com/chengxiaosheng/ZLMediaKit.HttpApi)
|
||||||
- [DotNetCore的RESTful客户端](https://github.com/MingZhuLiu/ZLMediaKit.DotNetCore.Sdk)
|
- [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,9 +210,12 @@ bash build_docker_images.sh
|
||||||
## 联系方式
|
## 联系方式
|
||||||
|
|
||||||
- 邮箱:<1213642868@qq.com>(本项目相关或流媒体相关问题请走issue流程,否则恕不邮件答复)
|
- 邮箱:<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/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% />
|
||||||
|
|
||||||
|
|
||||||
## 怎么提问?
|
## 怎么提问?
|
||||||
|
|
||||||
|
|
@ -212,9 +223,7 @@ bash build_docker_images.sh
|
||||||
|
|
||||||
- 1、仔细看下readme、wiki,如果有必要可以查看下issue.
|
- 1、仔细看下readme、wiki,如果有必要可以查看下issue.
|
||||||
- 2、如果您的问题还没解决,可以提issue.
|
- 2、如果您的问题还没解决,可以提issue.
|
||||||
- 3、有些问题,如果不具备参考性的,无需在issue提的,可以在qq群提.
|
- 3、如果需要获取更及时贴心的技术支持,可以有偿加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364).
|
||||||
- 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).
|
|
||||||
|
|
||||||
## 特别感谢
|
## 特别感谢
|
||||||
|
|
||||||
|
|
@ -309,6 +318,21 @@ bash build_docker_images.sh
|
||||||
[朱如洪 ](https://github.com/zhu410289616)
|
[朱如洪 ](https://github.com/zhu410289616)
|
||||||
[lijin](https://github.com/1461521844lijin)
|
[lijin](https://github.com/1461521844lijin)
|
||||||
[PioLing](https://github.com/PioLing)
|
[PioLing](https://github.com/PioLing)
|
||||||
|
[BackT0TheFuture](https://github.com/BackT0TheFuture)
|
||||||
|
[perara](https://github.com/perara)
|
||||||
|
[codeRATny](https://github.com/codeRATny)
|
||||||
|
[dengjfzh](https://github.com/dengjfzh)
|
||||||
|
[百鸣](https://github.com/ixingqiao)
|
||||||
|
[fruit Juice](https://github.com/xuandu)
|
||||||
|
[tbago](https://github.com/tbago)
|
||||||
|
[Luosh](https://github.com/Luosh)
|
||||||
|
[linxiaoyan87](https://github.com/linxiaoyan)
|
||||||
|
[waken](https://github.com/mc373906408)
|
||||||
|
[Deepslient](https://github.com/Deepslient)
|
||||||
|
|
||||||
|
同时感谢JetBrains对开源项目的支持,本项目使用CLion开发与调试:
|
||||||
|
|
||||||
|
[](https://jb.gg/OpenSourceSupport)
|
||||||
|
|
||||||
## 使用案例
|
## 使用案例
|
||||||
|
|
||||||
|
|
|
||||||
25
README_en.md
25
README_en.md
|
|
@ -45,7 +45,7 @@
|
||||||
|
|
||||||
## Feature List
|
## Feature List
|
||||||
### Overview of Features
|
### 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]
|
||||||
- RTSP[S] server, supports RTMP/MP4/HLS to RTSP[S] conversion, supports devices such as Amazon Echo Show
|
- 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] publishing server, supports recording and publishing streams
|
||||||
- RTMP[S] player, supports RTMP proxy, supports generating silent audio
|
- RTMP[S] player, supports RTMP proxy, supports generating silent audio
|
||||||
- RTMP[S] push client
|
- 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 websocket-flv live streaming
|
||||||
- Supports H264/H265/AAC/G711/OPUS encoding. Other encodings can be forwarded but cannot be converted to protocol
|
- 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-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 [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
|
- 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
|
- 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 HLS player and can pull HLS to rtsp/rtmp/mp4
|
||||||
- Supports H264/H265/AAC/G711/OPUS encoding
|
- Supports H264/H265/AAC/G711/OPUS encoding
|
||||||
|
|
@ -348,6 +350,7 @@ bash build_docker_images.sh
|
||||||
- Media management platform
|
- Media management platform
|
||||||
- [GB28181 complete solution with web management website, supporting webrtc and h265 playback](https://github.com/648540858/wvp-GB28181-pro)
|
- [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)
|
- [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)
|
- [GB28181 server implemented in Go](https://github.com/panjjo/gosip)
|
||||||
- [Node-js version of GB28181 platform](https://gitee.com/hfwudao/GB28181_Node_Http)
|
- [Node-js version of GB28181 platform](https://gitee.com/hfwudao/GB28181_Node_Http)
|
||||||
- [Hikvision ehome server implemented in Go](https://github.com/tsingeye/FreeEhome)
|
- [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)
|
- [Player supporting H265 based on wasm](https://github.com/numberwolf/h265web.js)
|
||||||
- [WebSocket-fmp4 player based on MSE](https://github.com/v354412101/wsPlayer)
|
- [WebSocket-fmp4 player based on MSE](https://github.com/v354412101/wsPlayer)
|
||||||
- [Domestic webrtc sdk(metaRTC)](https://github.com/metartc/metaRTC)
|
- [Domestic webrtc sdk(metaRTC)](https://github.com/metartc/metaRTC)
|
||||||
|
- [GB28181 player implemented in C++](https://github.com/any12345com/BXC_gb28181Player)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
@ -478,6 +482,21 @@ Thanks to all those who have supported this project in various ways, including b
|
||||||
[朱如洪 ](https://github.com/zhu410289616)
|
[朱如洪 ](https://github.com/zhu410289616)
|
||||||
[lijin](https://github.com/1461521844lijin)
|
[lijin](https://github.com/1461521844lijin)
|
||||||
[PioLing](https://github.com/PioLing)
|
[PioLing](https://github.com/PioLing)
|
||||||
|
[BackT0TheFuture](https://github.com/BackT0TheFuture)
|
||||||
|
[perara](https://github.com/perara)
|
||||||
|
[codeRATny](https://github.com/codeRATny)
|
||||||
|
[dengjfzh](https://github.com/dengjfzh)
|
||||||
|
[百鸣](https://github.com/ixingqiao)
|
||||||
|
[fruit Juice](https://github.com/xuandu)
|
||||||
|
[tbago](https://github.com/tbago)
|
||||||
|
[Luosh](https://github.com/Luosh)
|
||||||
|
[linxiaoyan87](https://github.com/linxiaoyan)
|
||||||
|
[waken](https://github.com/mc373906408)
|
||||||
|
[Deepslient](https://github.com/Deepslient)
|
||||||
|
|
||||||
|
Also thank to JetBrains for their support for open source project, we developed and debugged zlmediakit with CLion:
|
||||||
|
|
||||||
|
[](https://jb.gg/OpenSourceSupport)
|
||||||
|
|
||||||
## Use Cases
|
## Use Cases
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,6 @@ file(GLOB API_SRC_LIST
|
||||||
|
|
||||||
set(LINK_LIBRARIES ${MK_LINK_LIBRARIES})
|
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})
|
set(COMPILE_DEFINITIONS ${MK_COMPILE_DEFINITIONS})
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
|
|
@ -46,6 +39,8 @@ endif ()
|
||||||
if(ENABLE_API_STATIC_LIB)
|
if(ENABLE_API_STATIC_LIB)
|
||||||
add_library(mk_api STATIC ${API_SRC_LIST})
|
add_library(mk_api STATIC ${API_SRC_LIST})
|
||||||
list(APPEND COMPILE_DEFINITIONS MediaKitApi_STATIC)
|
list(APPEND COMPILE_DEFINITIONS MediaKitApi_STATIC)
|
||||||
|
elseif(IOS)
|
||||||
|
add_library(mk_api STATIC ${API_SRC_LIST})
|
||||||
else()
|
else()
|
||||||
add_library(mk_api SHARED ${API_SRC_LIST})
|
add_library(mk_api SHARED ${API_SRC_LIST})
|
||||||
endif()
|
endif()
|
||||||
|
|
@ -74,8 +69,6 @@ generate_export_header(mk_api
|
||||||
STATIC_DEFINE MediaKitApi_STATIC
|
STATIC_DEFINE MediaKitApi_STATIC
|
||||||
EXPORT_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/mk_export.h")
|
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)
|
file(GLOB API_HEADER_LIST include/*.h ${CMAKE_CURRENT_BINARY_DIR}/*.h)
|
||||||
install(FILES ${API_HEADER_LIST}
|
install(FILES ${API_HEADER_LIST}
|
||||||
DESTINATION ${INSTALL_PATH_INCLUDE})
|
DESTINATION ${INSTALL_PATH_INCLUDE})
|
||||||
|
|
@ -83,3 +76,12 @@ install(TARGETS mk_api
|
||||||
ARCHIVE DESTINATION ${INSTALL_PATH_LIB}
|
ARCHIVE DESTINATION ${INSTALL_PATH_LIB}
|
||||||
LIBRARY DESTINATION ${INSTALL_PATH_LIB}
|
LIBRARY DESTINATION ${INSTALL_PATH_LIB}
|
||||||
RUNTIME DESTINATION ${INSTALL_PATH_RUNTIME})
|
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);
|
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;
|
} mk_events;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#define MK_EVENT_OBJECTS_H
|
#define MK_EVENT_OBJECTS_H
|
||||||
#include "mk_common.h"
|
#include "mk_common.h"
|
||||||
#include "mk_tcp.h"
|
#include "mk_tcp.h"
|
||||||
|
#include "mk_track.h"
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#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);
|
API_EXPORT int API_CALL mk_media_source_get_reader_count(const mk_media_source ctx);
|
||||||
//MediaSource::totalReaderCount()
|
//MediaSource::totalReaderCount()
|
||||||
API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_source ctx);
|
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,
|
* 直播源在ZLMediaKit中被称作为MediaSource,
|
||||||
* 目前支持3种,分别是RtmpMediaSource、RtspMediaSource、HlsMediaSource
|
* 目前支持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);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#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 vhost 虚拟主机
|
||||||
* @param app 应用名
|
* @param app 应用名
|
||||||
* @param stream 流id
|
* @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 vhost 虚拟主机
|
||||||
* @param app 应用名
|
* @param app 应用名
|
||||||
* @param stream 流id
|
* @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;
|
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)
|
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());
|
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){
|
API_EXPORT const char* API_CALL mk_parser_get_method(const mk_parser ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
Parser *parser = (Parser *)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){
|
API_EXPORT const char* API_CALL mk_parser_get_url(const mk_parser ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
Parser *parser = (Parser *)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){
|
API_EXPORT const char* API_CALL mk_parser_get_url_params(const mk_parser ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
Parser *parser = (Parser *)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){
|
API_EXPORT const char* API_CALL mk_parser_get_url_param(const mk_parser ctx,const char *key){
|
||||||
assert(ctx && 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){
|
API_EXPORT const char* API_CALL mk_parser_get_tail(const mk_parser ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
Parser *parser = (Parser *)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){
|
API_EXPORT const char* API_CALL mk_parser_get_header(const mk_parser ctx,const char *key){
|
||||||
assert(ctx && 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);
|
assert(ctx);
|
||||||
Parser *parser = (Parser *)ctx;
|
Parser *parser = (Parser *)ctx;
|
||||||
if(length){
|
if(length){
|
||||||
*length = parser->Content().size();
|
*length = parser->content().size();
|
||||||
}
|
}
|
||||||
return parser->Content().c_str();
|
return parser->content().c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////MediaInfo/////////////////////////////////////////////
|
///////////////////////////////////////////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){
|
API_EXPORT const char* API_CALL mk_media_source_get_vhost(const mk_media_source ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MediaSource *src = (MediaSource *)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){
|
API_EXPORT const char* API_CALL mk_media_source_get_app(const mk_media_source ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MediaSource *src = (MediaSource *)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){
|
API_EXPORT const char* API_CALL mk_media_source_get_stream(const mk_media_source ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MediaSource *src = (MediaSource *)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){
|
API_EXPORT int API_CALL mk_media_source_get_reader_count(const mk_media_source ctx){
|
||||||
assert(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();
|
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){
|
API_EXPORT int API_CALL mk_media_source_close(const mk_media_source ctx,int force){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
MediaSource *src = (MediaSource *)ctx;
|
MediaSource *src = (MediaSource *)ctx;
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "mk_frame.h"
|
#include "mk_frame.h"
|
||||||
|
#include "mk_track.h"
|
||||||
#include "Extension/Frame.h"
|
#include "Extension/Frame.h"
|
||||||
#include "Extension/H264.h"
|
#include "Extension/H264.h"
|
||||||
#include "Extension/H265.h"
|
#include "Extension/H265.h"
|
||||||
#include "Extension/AAC.h"
|
#include "Extension/AAC.h"
|
||||||
|
#include "Record/MPEG.h"
|
||||||
|
|
||||||
using namespace mediakit;
|
using namespace mediakit;
|
||||||
|
|
||||||
|
|
@ -178,3 +180,92 @@ API_EXPORT uint32_t API_CALL mk_frame_get_flags(mk_frame frame) {
|
||||||
}
|
}
|
||||||
return ret;
|
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){
|
API_EXPORT const char* API_CALL mk_http_requester_get_response_status(mk_http_requester ctx){
|
||||||
assert(ctx);
|
assert(ctx);
|
||||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)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){
|
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);
|
assert(ctx);
|
||||||
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx;
|
||||||
if(length){
|
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){
|
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);
|
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);
|
auto src = MediaSource::find(vhost, app, stream_id);
|
||||||
if (!src) {
|
if (!src) {
|
||||||
WarnL << "未找到相关的MediaSource,startRecord失败:" << vhost << "/" << app << "/" << stream_id;
|
WarnL << "未找到相关的MediaSource,startRecord失败:" << vhost << "/" << app << "/" << stream_id;
|
||||||
return false;
|
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);
|
auto src = MediaSource::find(vhost, app, stream_id);
|
||||||
if(!src){
|
if (!src) {
|
||||||
return false;
|
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){
|
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) {
|
static void emit_ini_file_reload(mk_ini ini) {
|
||||||
if (ini == mk_ini_default()) {
|
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_link_libraries(${exe_name} mk_api)
|
||||||
target_compile_options(${exe_name} PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
target_compile_options(${exe_name} PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include "mk_mediakit.h"
|
#include "mk_mediakit.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include "mk_mediakit.h"
|
#include "mk_mediakit.h"
|
||||||
#define LOG_LEV 4
|
#define LOG_LEV 4
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,34 @@
|
||||||
# Download and build Jemalloc
|
# Download and build Jemalloc
|
||||||
|
|
||||||
set(JEMALLOC_VERSION 5.2.1)
|
set(JEMALLOC_VERSION 5.3.0)
|
||||||
set(JEMALLOC_NAME jemalloc-${JEMALLOC_VERSION})
|
set(JEMALLOC_NAME jemalloc-${JEMALLOC_VERSION})
|
||||||
set(JEMALLOC_TAR_PATH ${DEP_ROOT_DIR}/${JEMALLOC_NAME}.tar.bz2)
|
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 --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)
|
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-libdl)
|
||||||
#list(APPEND jemalloc_CONFIG_ARGS --disable-cxx)
|
#list(APPEND jemalloc_CONFIG_ARGS --disable-cxx)
|
||||||
#list(APPEND jemalloc_CONFIG_ARGS --with-jemalloc-prefix=je_)
|
#list(APPEND jemalloc_CONFIG_ARGS --with-jemalloc-prefix=je_)
|
||||||
#list(APPEND jemalloc_CONFIG_ARGS --enable-debug)
|
#list(APPEND jemalloc_CONFIG_ARGS --enable-debug)
|
||||||
|
|
||||||
if(NOT EXISTS ${JEMALLOC_TAR_PATH})
|
if(NOT EXISTS ${JEMALLOC_TAR_PATH})
|
||||||
message(STATUS "Downloading ${JEMALLOC_NAME}...")
|
set(JEMALLOC_URL https://github.com/jemalloc/jemalloc/releases/download/${JEMALLOC_VERSION}/${JEMALLOC_NAME}.tar.bz2)
|
||||||
file(DOWNLOAD https://github.com/jemalloc/jemalloc/releases/download/${JEMALLOC_VERSION}/${JEMALLOC_NAME}.tar.bz2
|
message(STATUS "Downloading ${JEMALLOC_NAME} from ${JEMALLOC_URL}")
|
||||||
${JEMALLOC_TAR_PATH})
|
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()
|
endif()
|
||||||
|
|
||||||
SET( DIR_CONTAINING_JEMALLOC ${DEP_ROOT_DIR}/${JEMALLOC_NAME} )
|
SET( DIR_CONTAINING_JEMALLOC ${DEP_ROOT_DIR}/${JEMALLOC_NAME} )
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ bin=/usr/bin/ffmpeg
|
||||||
#FFmpeg拉流再推流的命令模板,通过该模板可以设置再编码的一些参数
|
#FFmpeg拉流再推流的命令模板,通过该模板可以设置再编码的一些参数
|
||||||
cmd=%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s
|
cmd=%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s
|
||||||
#FFmpeg生成截图的命令,可以通过修改该配置改变截图分辨率或质量
|
#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日志
|
#FFmpeg日志的路径,如果置空则不生成FFmpeg日志
|
||||||
#可以为相对(相对于本可执行程序目录)或绝对路径
|
#可以为相对(相对于本可执行程序目录)或绝对路径
|
||||||
log=./ffmpeg/ffmpeg.log
|
log=./ffmpeg/ffmpeg.log
|
||||||
|
|
@ -32,18 +32,28 @@ restart_sec=0
|
||||||
#转协议相关开关;如果addStreamProxy api和on_publish hook回复未指定转协议参数,则采用这些配置项
|
#转协议相关开关;如果addStreamProxy api和on_publish hook回复未指定转协议参数,则采用这些配置项
|
||||||
[protocol]
|
[protocol]
|
||||||
#转协议时,是否开启帧级时间戳覆盖
|
#转协议时,是否开启帧级时间戳覆盖
|
||||||
modify_stamp=0
|
# 0:采用源视频流绝对时间戳,不做任何改变
|
||||||
|
# 1:采用zlmediakit接收数据时的系统时间戳(有平滑处理)
|
||||||
|
# 2:采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正
|
||||||
|
modify_stamp=2
|
||||||
#转协议是否开启音频
|
#转协议是否开启音频
|
||||||
enable_audio=1
|
enable_audio=1
|
||||||
#添加acc静音音频,在关闭音频时,此开关无效
|
#添加acc静音音频,在关闭音频时,此开关无效
|
||||||
add_mute_audio=1
|
add_mute_audio=1
|
||||||
|
#无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)
|
||||||
|
#此配置置1时,此流如果无人观看,将不触发on_none_reader hook回调,
|
||||||
|
#而是将直接关闭流
|
||||||
|
auto_close=0
|
||||||
|
|
||||||
#推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。
|
#推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。
|
||||||
#置0关闭此特性(推流断开会导致立即断开播放器)
|
#置0关闭此特性(推流断开会导致立即断开播放器)
|
||||||
#此参数不应大于播放器超时时间;单位毫秒
|
#此参数不应大于播放器超时时间;单位毫秒
|
||||||
continue_push_ms=15000
|
continue_push_ms=15000
|
||||||
|
|
||||||
#是否开启转换为hls
|
#是否开启转换为hls(mpegts)
|
||||||
enable_hls=1
|
enable_hls=1
|
||||||
|
#是否开启转换为hls(fmp4)
|
||||||
|
enable_hls_fmp4=0
|
||||||
#是否开启MP4录制
|
#是否开启MP4录制
|
||||||
enable_mp4=0
|
enable_mp4=0
|
||||||
#是否开启转换为rtsp/webrtc
|
#是否开启转换为rtsp/webrtc
|
||||||
|
|
@ -121,7 +131,7 @@ segDur=2
|
||||||
segNum=3
|
segNum=3
|
||||||
#HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数
|
#HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数
|
||||||
segRetain=5
|
segRetain=5
|
||||||
#是否广播 ts 切片完成通知
|
#是否广播 hls切片(ts/fmp4)完成通知(on_record_ts)
|
||||||
broadcastRecordTs=0
|
broadcastRecordTs=0
|
||||||
#直播hls文件删除延时,单位秒,issue: #913
|
#直播hls文件删除延时,单位秒,issue: #913
|
||||||
deleteDelaySec=10
|
deleteDelaySec=10
|
||||||
|
|
@ -132,9 +142,6 @@ deleteDelaySec=10
|
||||||
segKeep=0
|
segKeep=0
|
||||||
|
|
||||||
[hook]
|
[hook]
|
||||||
#在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然
|
|
||||||
#该配置项的目的是为了开发者自己调试测试,该参数暴露后会有泄露隐私的安全隐患
|
|
||||||
admin_params=secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
|
|
||||||
#是否启用hook事件,启用后,推拉流都将进行鉴权
|
#是否启用hook事件,启用后,推拉流都将进行鉴权
|
||||||
enable=0
|
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
|
on_publish=https://127.0.0.1/index/hook/on_publish
|
||||||
#录制mp4切片完成事件
|
#录制mp4切片完成事件
|
||||||
on_record_mp4=https://127.0.0.1/index/hook/on_record_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
|
on_record_ts=https://127.0.0.1/index/hook/on_record_ts
|
||||||
#rtsp播放鉴权事件,此事件中比对rtsp的用户名密码
|
#rtsp播放鉴权事件,此事件中比对rtsp的用户名密码
|
||||||
on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth
|
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_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=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选项一起使用
|
#无人观看流事件,通过该事件,可以选择是否关闭无人观看的流。配合general.streamNoneReaderDelayMS选项一起使用
|
||||||
on_stream_none_reader=https://127.0.0.1/index/hook/on_stream_none_reader
|
on_stream_none_reader=https://127.0.0.1/index/hook/on_stream_none_reader
|
||||||
#播放时,未找到流事件,通过配合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_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_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保活上报
|
#server保活上报
|
||||||
on_server_keepalive=https://127.0.0.1/index/hook/on_server_keepalive
|
on_server_keepalive=https://127.0.0.1/index/hook/on_server_keepalive
|
||||||
#发送rtp(startSendRtp)被动关闭时回调
|
#发送rtp(startSendRtp)被动关闭时回调
|
||||||
|
|
@ -232,6 +243,8 @@ forbidCacheSuffix=
|
||||||
forwarded_ip_header=
|
forwarded_ip_header=
|
||||||
#默认允许所有跨域请求
|
#默认允许所有跨域请求
|
||||||
allow_cross_domains=1
|
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]
|
[multicast]
|
||||||
#rtp组播截止组播ip地址
|
#rtp组播截止组播ip地址
|
||||||
|
|
@ -261,8 +274,6 @@ handshakeSecond=15
|
||||||
#rtmp超时时间,如果该时间内未收到客户端的数据,
|
#rtmp超时时间,如果该时间内未收到客户端的数据,
|
||||||
#或者tcp发送缓存超过这个时间,则会断开连接,单位秒
|
#或者tcp发送缓存超过这个时间,则会断开连接,单位秒
|
||||||
keepAliveSecond=15
|
keepAliveSecond=15
|
||||||
#在接收rtmp推流时,是否重新生成时间戳(很多推流器的时间戳着实很烂)
|
|
||||||
modifyStamp=0
|
|
||||||
#rtmp服务器监听端口
|
#rtmp服务器监听端口
|
||||||
port=1935
|
port=1935
|
||||||
#rtmps服务器监听地址
|
#rtmps服务器监听地址
|
||||||
|
|
@ -278,6 +289,9 @@ videoMtuSize=1400
|
||||||
rtpMaxSize=10
|
rtpMaxSize=10
|
||||||
# rtp 打包时,低延迟开关,默认关闭(为0),h264存在一帧多个slice(NAL)的情况,在这种情况下,如果开启可能会导致画面花屏
|
# rtp 打包时,低延迟开关,默认关闭(为0),h264存在一帧多个slice(NAL)的情况,在这种情况下,如果开启可能会导致画面花屏
|
||||||
lowLatency=0
|
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_proxy]
|
||||||
#导出调试数据(包括rtp/ps/h264)至该目录,置空则关闭数据导出
|
#导出调试数据(包括rtp/ps/h264)至该目录,置空则关闭数据导出
|
||||||
|
|
@ -359,6 +373,10 @@ port=554
|
||||||
sslport=0
|
sslport=0
|
||||||
#rtsp 转发是否使用低延迟模式,当开启时,不会缓存rtp包,来提高并发,可以降低一帧的延迟
|
#rtsp 转发是否使用低延迟模式,当开启时,不会缓存rtp包,来提高并发,可以降低一帧的延迟
|
||||||
lowLatency=0
|
lowLatency=0
|
||||||
|
#强制协商rtp传输方式 (0:TCP,1:UDP,2:MULTICAST,-1:不限制)
|
||||||
|
#当客户端发起RTSP SETUP的时候如果传输类型和此配置不一致则返回461 Unsupported transport
|
||||||
|
#迫使客户端重新SETUP并切换到对应协议。目前支持FFMPEG和VLC
|
||||||
|
rtpTransportType=-1
|
||||||
[shell]
|
[shell]
|
||||||
#调试telnet服务器接受最大bufffer大小
|
#调试telnet服务器接受最大bufffer大小
|
||||||
maxReqSize=1024
|
maxReqSize=1024
|
||||||
|
|
|
||||||
146
default.pem
146
default.pem
|
|
@ -1,89 +1,89 @@
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
MIIEowIBAAKCAQEAjTFnNz2btUHR/K8TegU2n06lXsQuW4AfsMGthBLvnz2zdgL2
|
MIIEowIBAAKCAQEAtgg0vsIc8bIMb9hAO1Lwq2TVNYMiD928WCVwq7Bj3B9LM8ci
|
||||||
uVxfRCuleoRpECEV9I80ibTXAJlouRLbnhLHto8gzyLeQue9/DtxTEGcS7y2aMha
|
fN+mBxHz35SeC9JnQysbKAf8VVfVi7GVzwFq4VaRreoIl8DoMbBORb2iKxfLXKEH
|
||||||
0wAoUcOBv7tAITSnFepGHXoYgyU7HOCvn96U1bzqLTOhxOCH/xy003dwoEC7+Pjl
|
S283bbl17csAYHTTBqFsKnO3kKvYoDHRKFX0T0rzZibI0ACQiylW+ALE3fVS0hFI
|
||||||
dsWs/16cpvYiZV/dZvzDDKIpHSIvQy1whi6N0SDpzz/ncThn1z8xcJDx0I1jVR76
|
8fmoZnIAOyQ15RLp7KPO4bMRVdZaOqRUZeW0YzbvOBeGZKbx3bXiscL9RwfGSxlF
|
||||||
juP7TttbyhqJkO+fOlLn4yP9K5wZ/dPFJn2+bQRCrzGc4SM7J5YOq8ckTTTbZtSp
|
MCLahaJsBrVDo4/IGDRCmSydK8k/41MHd86pGPzCixBx7cqtKO/6+cVeEuGxfJPf
|
||||||
9yPpm7O5QyfdzePhOpRrjWroLsqaDOPV2UQlzwIDAQABAoIBADCWTh8P19vdnR3X
|
JVuxNq6vkRSt2HxCc6i2iagA2uL/WYdg3Pa/4wIDAQABAoIBAADFrCObAzBrRu46
|
||||||
v5uPXLcgkL7WQt+g7Qbd91CKVaRWTsHvDilGVNA4Ntc85oyy3gPNHfa/YPdnU0bQ
|
hps50NeJR/ZAJibXE/NzxTSVPPc0EseXcqgA8t1Y0CYEpV77d4CrcCQNVJ6wDrHX
|
||||||
6vtwGgLEKTWumY6rgdDhQcFMmLTlaV4QiFSw6q8MWMN6c/yZSmA7wMoXAIVs0/VB
|
AQGtydxG17tbIMo0AUgkrVBSa5uvMCembzd8s0l93egyUkAWfsaqbKEJeJ/eer7D
|
||||||
ip44sb4Fpw5MBMCjxZjwL3fP09WJPlUqx09vVo7eH8rFwLBikmn982IzRigAx1I8
|
N1Xqd2zWro2iYHuxZOuSM1I+AMPIQsmYJ71w6/h9YpQh436Vd+zNQ5k/nWpLHihT
|
||||||
TX0wkdqvv33MSxBXPMQIrwPqjf2arxWFzb6vp6yolYbMZtgORF9gznWABRy3oY50
|
VB2ECrJ36IbuiYo3UbSr9gQjyBSMkk/oUqO4jonkb6L7r0mqHXNeblycg99/m6i7
|
||||||
9jFkTkbxZFlSMVuF7nlM0WJj5Q9/IelBqpozODWUVvB+6inCqkxNLkbh0ISbpXWC
|
O5c5DQKMhzqibwvNNf6uvWCcLKfF5Kqzzf9DKR3/pYOBQrVTA24l4UFsfTdEKUNS
|
||||||
16gUZfUCgYEAxWo3FRNBrNXhVD5h2N4ApyUXkZ5UYIY5zbsHEJCrPjooh9uHu9kh
|
a8W3P8ECgYEA6CQOG15V9upc2nPzfFwgftGyomSMYH54PkSFdr2R4djyXkyil6Ik
|
||||||
xXh5v11J/7TV9BfwLZ4qRbDBH4fq0DKEOXOZRLY5Lo4KbrYmlEDCabuJdmwwHeGh
|
efK3E+lKr9YnzwcLw3csPmVt3lqSgixQUMcyXXrhCttfk/qzSJkI+UZPQE+SrNeW
|
||||||
S5K37F5z/+zPz9KWkKN+9Rg32xdLxh0969O77GnvuBrhzASpVsF6ZFMCgYEAtxf1
|
0c+blQOzVcfbNRu248iGFaRx+5qA6PMH4UZTgn7e6nXoPUgRp4ryI/MCgYEAyL24
|
||||||
eVg4Kxzuy0AWs+CisSVQc+5CbZ9teKA5fli2EVSmL5dsrKatVTIDghudJgQTU6cr
|
R7uMSuPQBRJFU84Lu+Rv4lkKdCYSLuQtMZly74m11iG6e+EHJQx0C3eexrC8LhOV
|
||||||
zP9I20K11jeqIoK5saQXH3CzogN6aDuKssq4rDbvVSZ09Zry6N1WMz9GPe31zEYw
|
Sm4xTlwVrYQ+IdW51bhAwwHcnzGUzpbESJSDK5ZTd/P5daz8yt8ZaGbUFxNEsxTr
|
||||||
sdU1w7vUw+l3unFfWOP4oZm0MH+na61V1YohCRUCgYANlp0J/1RS8DndUZnskoNa
|
ElKPRcjJH5CRuyYr24DYg+CpMGdlF0N6Pcx5IFECgYAedlzDiqWNOUPmBsE02IIL
|
||||||
/eucY1iNeE+8QHZhBoQy+U/W4h56qJxxejRvHp28UxczAP7QNQXV3C++2t0nzYJa
|
IklmtfsVzoLI6QT6h/XUxTtI1JWhgE15EzijDEIYwOmIaUxJ4iGULos0Wn5PRrFj
|
||||||
bgGLwDs5YB+JtVH8fGSlYHo6w4GgXOp8SDIOvAWiBQvc0zL367kOZ8dYdkcJ8PNV
|
aEBbs/xECHWKXaOZKzvaOje8ILUGqWPJNI0eCNZHs2o4leJyEaZGwMWUVroD16B5
|
||||||
KzLROA1/D6KhJ2T8ir7A7wKBgQCjVVxGw8xXqZfc+W9HSD3aic8bnJDl+jNOSKEB
|
F1luDmgCLGbFY+etLLaJsQKBgB40VbcNZDWcg59PuXi7pw5Vd/RB243QcKn3kUlG
|
||||||
dWH2U+1sx0jLPGWketlmV/v4zenv1lHcrl/wObK9RysfXj8JmbiG86NMBI5OLc+t
|
QoICYYbfulSLbmzHq+pRzGUvEJGKRstVOzwEJQrfvA2RQA4FVFFDRXP6nN5c1xno
|
||||||
b+sOtnMLIyNzdqb71Xfwf6HJ3V5IvNTzz6AG3KkRnFSSnlDQm45RmyyDl11jUV4h
|
prf3PYXuAtoO9lZ8LTGFT2JNdufPPPOb0oz4gjKqqRLU0oKLp4hoVGzBEffnIkyM
|
||||||
APg3gQKBgBzFeuKWnaTZz1FQBr5Ytl9gtxBRMl+49jtkqyzErJYFHe0MTWeD/1xj
|
KKmRAoGBAIGXh4gvxzEQMgGzfKfNuxKCT9SEhsg7NU++Iey3qn4G4t+jIWOt2Gi7
|
||||||
mEC/7UERYWhIQF1L4ah6c0QkecR3F1s9/IYK/QHsnSJFwRyFuMas6StCERsDq5oQ
|
5+y49JWoGq6DL+2ZVVw6Cn6wd9tfzDKD5GhvIztK0z1+wqpFOL4M8bwqJDOKgsZ3
|
||||||
GWpXAmw7JTa8OYwxVjORdXY25Iwv6rEr6iUYBWZrkhoWYBySWpSZ
|
PCPASbxPgMyNCjRhvxBuscCr+dRFYDUrirOK9EUPyO9EoNTPPN9a
|
||||||
-----END RSA PRIVATE KEY-----
|
-----END RSA PRIVATE KEY-----
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIGAjCCBOqgAwIBAgIQAiXv68Xco/vd9YeB4g3HLjANBgkqhkiG9w0BAQsFADBu
|
MIIGBTCCBO2gAwIBAgIQDNIYeWoFoT3jxF2+HmEbTDANBgkqhkiG9w0BAQsFADBu
|
||||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||||
d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
|
d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
|
||||||
RFYgVExTIENBIC0gRzEwHhcNMjIwOTE4MDAwMDAwWhcNMjMwOTE4MjM1OTU5WjAh
|
RFYgVExTIENBIC0gRzIwHhcNMjMwOTI4MDAwMDAwWhcNMjQwOTI3MjM1OTU5WjAh
|
||||||
MR8wHQYDVQQDExZkZWZhdWx0LnpsbWVkaWFraXQuY29tMIIBIjANBgkqhkiG9w0B
|
MR8wHQYDVQQDExZkZWZhdWx0LnpsbWVkaWFraXQuY29tMIIBIjANBgkqhkiG9w0B
|
||||||
AQEFAAOCAQ8AMIIBCgKCAQEAjTFnNz2btUHR/K8TegU2n06lXsQuW4AfsMGthBLv
|
AQEFAAOCAQ8AMIIBCgKCAQEAtgg0vsIc8bIMb9hAO1Lwq2TVNYMiD928WCVwq7Bj
|
||||||
nz2zdgL2uVxfRCuleoRpECEV9I80ibTXAJlouRLbnhLHto8gzyLeQue9/DtxTEGc
|
3B9LM8cifN+mBxHz35SeC9JnQysbKAf8VVfVi7GVzwFq4VaRreoIl8DoMbBORb2i
|
||||||
S7y2aMha0wAoUcOBv7tAITSnFepGHXoYgyU7HOCvn96U1bzqLTOhxOCH/xy003dw
|
KxfLXKEHS283bbl17csAYHTTBqFsKnO3kKvYoDHRKFX0T0rzZibI0ACQiylW+ALE
|
||||||
oEC7+PjldsWs/16cpvYiZV/dZvzDDKIpHSIvQy1whi6N0SDpzz/ncThn1z8xcJDx
|
3fVS0hFI8fmoZnIAOyQ15RLp7KPO4bMRVdZaOqRUZeW0YzbvOBeGZKbx3bXiscL9
|
||||||
0I1jVR76juP7TttbyhqJkO+fOlLn4yP9K5wZ/dPFJn2+bQRCrzGc4SM7J5YOq8ck
|
RwfGSxlFMCLahaJsBrVDo4/IGDRCmSydK8k/41MHd86pGPzCixBx7cqtKO/6+cVe
|
||||||
TTTbZtSp9yPpm7O5QyfdzePhOpRrjWroLsqaDOPV2UQlzwIDAQABo4IC5zCCAuMw
|
EuGxfJPfJVuxNq6vkRSt2HxCc6i2iagA2uL/WYdg3Pa/4wIDAQABo4IC6jCCAuYw
|
||||||
HwYDVR0jBBgwFoAUVXRPsnJP9WC6UNHX5lFcmgGHGtcwHQYDVR0OBBYEFPnRZrfz
|
HwYDVR0jBBgwFoAUeN+RkF/u3qz2xXXr1UxVU+8kSrYwHQYDVR0OBBYEFHmEMVp9
|
||||||
q/QAf5u4Xp4eGWvhMdvfMCEGA1UdEQQaMBiCFmRlZmF1bHQuemxtZWRpYWtpdC5j
|
9EHIPWA2U1iLKogCosGFMCEGA1UdEQQaMBiCFmRlZmF1bHQuemxtZWRpYWtpdC5j
|
||||||
b20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
|
b20wPgYDVR0gBDcwNTAzBgZngQwBAgEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3
|
||||||
AjA+BgNVHSAENzA1MDMGBmeBDAECATApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3
|
dy5kaWdpY2VydC5jb20vQ1BTMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggr
|
||||||
LmRpZ2ljZXJ0LmNvbS9DUFMwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY
|
BgEFBQcDAQYIKwYBBQUHAwIwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY
|
||||||
aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEoGCCsGAQUFBzAChj5odHRwOi8vY2Fj
|
aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEoGCCsGAQUFBzAChj5odHRwOi8vY2Fj
|
||||||
ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcx
|
ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcy
|
||||||
LmNydDAJBgNVHRMEAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgDoPtDa
|
LmNydDAMBgNVHRMBAf8EAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgDu
|
||||||
PvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAAAYNQt3JvAAAEAwBHMEUCIEaO
|
zdBk1dsazsVct520zROiModGfLzs3sNRSFlGcR+1mwAAAYravqonAAAEAwBHMEUC
|
||||||
G4ffzzaE6OMqiu6PUr+Y+wO2tsXCkGt1jt04Ix1qAiEAhNZwqFACieds1ZbY3r/p
|
IQDX+gqsd7I0yzjkhgp2YrccUlTx4wkFptFvmQxeChImRgIgJdgJa2Uamd790BCI
|
||||||
wlF3iFbhqp+kNfPzon7kwc8AdgA1zxkbv7FsV78PrUxtQsu7ticgJlHqP+Eq76gD
|
/CZwSqmRlor5eU8exAixdcopYpcAdwBIsONr2qZHNA/lagL6nTDrHFIBy1bdLIHZ
|
||||||
wzvWTAAAAYNQt3JVAAAEAwBHMEUCIBOErqyKvihAEKItLWG/Plgtxh/hCTMsE+t5
|
u7+rOdiEcwAAAYravqqCAAAEAwBIMEYCIQCP6rkKg2FlF92CyMbVMk3ESh/9gVaM
|
||||||
+MfsAQLCAiEA76d50S4iy1wxya+8IUASVlKStaHNqBkJAS+Oadxs2sMAdwCzc3cH
|
tRsv5I//i5IVigIhAINHERhy7812wR47fwmvqWDjxyOB1ZodU7WA9D5L/1bVAHYA
|
||||||
4YRQ+GOG1gWp3BEJSnktsWcMC4fc8AMOeTalmgAAAYNQt3LIAAAEAwBIMEYCIQC/
|
2ra/az+1tiKfm8K7XGvocJFxbLtRhIU0vaQ9MEjX+6sAAAGK2r6qQQAABAMARzBF
|
||||||
kfFCpwF76sw/Qx3sxR8b3srW+Ds0k/6VrIIDZcYV5gIhAKkLmuyeDvzulp0y4f0t
|
AiAiz3bp/j4SlnVxKg1HZY+YdUboi+kaKf5G8X6aFLIqUgIhAPPCm5UN05p7Oqrc
|
||||||
GDgIN/OoURq6CuHA67UJlsWzMA0GCSqGSIb3DQEBCwUAA4IBAQB0BwVxPRihSdPJ
|
sP/wdHDB7O/2AbUksYSLhidmwfmhMA0GCSqGSIb3DQEBCwUAA4IBAQBmaG51jU1E
|
||||||
FUPLQ+ClHy9O/UisnRD7NadQQtbcMXn6L9Lwd0f2la0ytLQAKHADOZDA08KfQ5qW
|
MsgT1VzutQUXglEvJGVf54cA+0TSfjfnP1n9ALdKjGxHL3KBh4UkPx5zdE5//FUX
|
||||||
B19OeQOlTwp2nhY2ZvoLEG+paeh0gYxIgD76APnd/m3g2H7GeW144ymjPcZRoldj
|
dacua6BQEWSCmMtYL0CFieFnLGXh0mgkfvRaP6+3xe6TkJ4kuyJkMS9YMDpVl80F
|
||||||
ZKYSdzStJJIFYXzL3FR9wjkMc4xOEes/IY5PFtj8OT8CFf7zl0R7L2Vcw9RGYi9u
|
2GLlE09EsZ3Xk9+SCpmWOPLOCDFURbwpc5ht+acROfzYJQyCY0L8EGbyL5/q9oMn
|
||||||
vLjGwwJW9kXTX8UlKXFyjJN0ZyrmxBQHq5uNtigx8xy6HtMnPsc58tp1IqitIELp
|
ugRGh4oyGvXgKvFIPzpZkaOmb0b63/uBc5JkiyQhuFdYaS2cLOwupXmCtIHL4Od6
|
||||||
HIur2XrRPBJA5XtpDg3AE8bXhRTM8oFMPL0UoSFWyWRYGgBo1Msc10dpXPtmbgIc
|
OU8/8smT8NEkD7d3lUijtc84q2TihW7ebT7RtOco49PDvFP/7w28QjxM8Ohv9/Gz
|
||||||
pPW8w+2c
|
Xyta8ICQVwmK
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh
|
MIIEqjCCA5KgAwIBAgIQDeD/te5iy2EQn2CMnO1e0zANBgkqhkiG9w0BAQsFADBh
|
||||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||||
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
|
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
|
||||||
QTAeFw0xNzExMjcxMjQ2MTBaFw0yNzExMjcxMjQ2MTBaMG4xCzAJBgNVBAYTAlVT
|
MjAeFw0xNzExMjcxMjQ2NDBaFw0yNzExMjcxMjQ2NDBaMG4xCzAJBgNVBAYTAlVT
|
||||||
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
|
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
|
||||||
b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH
|
b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH
|
||||||
MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPeP6wkab41dyQh6mKc
|
MjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO8Uf46i/nr7pkgTDqnE
|
||||||
oHqt3jRIxW5MDvf9QyiOR7VfFwK656es0UFiIb74N9pRntzF1UgYzDGu3ppZVMdo
|
eSIfCFqvPnUq3aF1tMJ5hh9MnO6Lmt5UdHfBGwC9Si+XjK12cjZgxObsL6Rg1njv
|
||||||
lbxhm6dWS9OK/lFehKNT0OYI9aqk6F+U7cA6jxSC+iDBPXwdF4rs3KRyp3aQn6pj
|
NhAMJ4JunN0JGGRJGSevbJsA3sc68nbPQzuKp5Jc8vpryp2mts38pSCXorPR+sch
|
||||||
pp1yr7IB6Y4zv72Ee/PlZ/6rK6InC6WpK0nPVOYR7n9iDuPe1E4IxUMBH/T33+3h
|
QisKA7OSQ1MjcFN0d7tbrceWFNbzgL2csJVQeogOBGSe/KZEIZw6gXLKeFe7mupn
|
||||||
yuH3dvfgiWUOUkjdpMbyxX+XNle5uEIiyBsi4IvbcTCh8ruifCIi5mDXkZrnMT8n
|
NYJROi2iC11+HuF79iAttMc32Cv6UOxixY/3ZV+LzpLnklFq98XORgwkIJL1HuvP
|
||||||
wfYCV6v6kDdXkbgGRLKsR4pucbJtbKqIkUGxuZI2t7pfewKRc5nWecvDBZf3+p1M
|
ha8yvb+W6JislZJL+HLFtidoxmI7Qm3ZyIV66W533DsGFimFJkz3y0GeHWuSVMbI
|
||||||
pA8CAwEAAaOCAU8wggFLMB0GA1UdDgQWBBRVdE+yck/1YLpQ0dfmUVyaAYca1zAf
|
lfsCAwEAAaOCAU8wggFLMB0GA1UdDgQWBBR435GQX+7erPbFdevVTFVT7yRKtjAf
|
||||||
BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYw
|
BgNVHSMEGDAWgBROIlQgGJXm427mD/r6uRLtBhePOTAOBgNVHQ8BAf8EBAMCAYYw
|
||||||
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C
|
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C
|
||||||
AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
|
AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
|
||||||
Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu
|
Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu
|
||||||
Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG
|
Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG
|
||||||
/WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT
|
/WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT
|
||||||
MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAK3Gp6/aGq7aBZsxf/oQ+TD/B
|
MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAoBs1eCLKakLtVRPFRjBIJ9LJ
|
||||||
SwW3AU4ETK+GQf2kFzYZkby5SFrHdPomunx2HBzViUchGoofGgg7gHW0W3MlQAXW
|
L0s8ZWum8U8/1TMVkQMBn+CPb5xnCD0GSA6L/V0ZFrMNqBirrr5B241OesECvxIi
|
||||||
M0r5LUvStcr82QDWYNPaUy4taCQmyaJ+VB+6wxHstSigOlSNF2a6vg4rgexixeiV
|
98bZ90h9+q/X5eMyOD35f8YTaEMpdnQCnawIwiHx06/0BfiTj+b/XQih+mqt3ZXe
|
||||||
4YSB03Yqp2t3TeZHM9ESfkus74nQyW7pRGezj+TC44xCagCQQOzzNmzEAP2SnCrJ
|
xNCJqKexdiB2IWGSKcgahPacWkk/BAQFisKIFYEqHzV974S3FAz/8LIfD58xnsEN
|
||||||
sNE2DpRVMnL8J6xBRdjmOsC3N6cQuKuRXbzByVBjCqAA8t1L0I+9wXJerLPyErjy
|
GfzyIDkH3JrwYZ8caPTf6ZX9M1GrISN8HnWTtdNCH2xEajRa/h9ZBXjUyFKQrGk2
|
||||||
rMKWaBFLmfK/AHNF4ZihwPGOc7w6UHczBZXH5RFzJNnww+WnKuTPI0HfnVH8lg==
|
n2hcLrfZSbynEC/pSw/ET7H5nWwckjmAJ1l9fcnbqkU/pf6uMQmnfl0JQjJNSg==
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -128,4 +128,4 @@ WORKDIR /opt/zlm
|
||||||
VOLUME [ "/opt/zlm/conf/","/opt/zlm/log/","opt/zlm/ffmpeg/"]
|
VOLUME [ "/opt/zlm/conf/","/opt/zlm/log/","opt/zlm/ffmpeg/"]
|
||||||
COPY --from=build /opt/build /
|
COPY --from=build /opt/build /
|
||||||
ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH TZ=Asia/Shanghai
|
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
|
make
|
||||||
|
|
||||||
ENV PATH /opt/media/ZLMediaKit/release/linux/Release/:$PATH
|
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/
|
WORKDIR /opt/media/bin/
|
||||||
COPY --from=build /opt/media/ZLMediaKit/release/linux/Release/MediaServer /opt/media/bin/MediaServer
|
COPY --from=build /opt/media/ZLMediaKit/release/linux/Release/MediaServer /opt/media/bin/MediaServer
|
||||||
ENV PATH /opt/media/bin:$PATH
|
ENV PATH /opt/media/bin:$PATH
|
||||||
CMD MediaServer
|
CMD ["MediaServer"]
|
||||||
|
|
|
||||||
|
|
@ -42,4 +42,4 @@ RUN cmake -DCMAKE_BUILD_TYPE=Release .. && \
|
||||||
make
|
make
|
||||||
|
|
||||||
ENV PATH /opt/media/ZLMediaKit/release/linux/Release:$PATH
|
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/
|
WORKDIR /opt/media/bin/
|
||||||
COPY --from=build /opt/media/ZLMediaKit/release/linux/Release/MediaServer /opt/media/bin/MediaServer
|
COPY --from=build /opt/media/ZLMediaKit/release/linux/Release/MediaServer /opt/media/bin/MediaServer
|
||||||
ENV PATH /opt/media/bin:$PATH
|
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/release/linux/${MODEL}/config.ini /opt/media/conf/
|
||||||
COPY --from=build /opt/media/ZLMediaKit/www/ /opt/media/bin/www/
|
COPY --from=build /opt/media/ZLMediaKit/www/ /opt/media/bin/www/
|
||||||
ENV PATH /opt/media/bin:$PATH
|
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) {
|
if (argc < 3) {
|
||||||
ErrorL << "\r\n测试方法:./test_player rtxp_url rtp_type\r\n"
|
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"
|
<< "例如:./test_player rtsp://admin:123456@127.0.0.1/live/0 0\r\n";
|
||||||
<< endl;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +103,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +129,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -155,7 +155,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "api.apiDebug",
|
"key": "api.apiDebug",
|
||||||
|
|
@ -186,7 +186,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -212,7 +212,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "schema",
|
"key": "schema",
|
||||||
|
|
@ -262,7 +262,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "schema",
|
"key": "schema",
|
||||||
|
|
@ -314,7 +314,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "schema",
|
"key": "schema",
|
||||||
|
|
@ -366,7 +366,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "local_port",
|
"key": "local_port",
|
||||||
|
|
@ -404,7 +404,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "id",
|
"key": "id",
|
||||||
|
|
@ -435,7 +435,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "local_port",
|
"key": "local_port",
|
||||||
|
|
@ -473,7 +473,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "vhost",
|
"key": "vhost",
|
||||||
|
|
@ -516,7 +516,13 @@
|
||||||
{
|
{
|
||||||
"key": "enable_hls",
|
"key": "enable_hls",
|
||||||
"value": null,
|
"value": null,
|
||||||
"description": "是否转hls",
|
"description": "是否转hls-ts",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "enable_hls_fmp4",
|
||||||
|
"value": null,
|
||||||
|
"description": "是否转hls-fmp4",
|
||||||
"disabled": true
|
"disabled": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -582,7 +588,13 @@
|
||||||
{
|
{
|
||||||
"key": "modify_stamp",
|
"key": "modify_stamp",
|
||||||
"value": null,
|
"value": null,
|
||||||
"description": "是否重新计算时间戳",
|
"description": "是否修改原始时间戳,默认值2;取值范围:0.采用源视频流绝对时间戳,不做任何改变;1.采用zlmediakit接收数据时的系统时间戳(有平滑处理);2.采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "auto_close",
|
||||||
|
"value": null,
|
||||||
|
"description": "无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)",
|
||||||
"disabled": true
|
"disabled": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -609,7 +621,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "key",
|
"key": "key",
|
||||||
|
|
@ -640,7 +652,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "schema",
|
"key": "schema",
|
||||||
|
|
@ -709,7 +721,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "key",
|
"key": "key",
|
||||||
|
|
@ -740,7 +752,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "src_url",
|
"key": "src_url",
|
||||||
|
|
@ -797,7 +809,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "key",
|
"key": "key",
|
||||||
|
|
@ -827,7 +839,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "schema",
|
"key": "schema",
|
||||||
|
|
@ -873,7 +885,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "schema",
|
"key": "schema",
|
||||||
|
|
@ -900,6 +912,56 @@
|
||||||
},
|
},
|
||||||
"response": []
|
"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)",
|
"name": "获取流信息(getMediaInfo)",
|
||||||
"request": {
|
"request": {
|
||||||
|
|
@ -919,7 +981,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "schema",
|
"key": "schema",
|
||||||
|
|
@ -965,7 +1027,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "vhost",
|
"key": "vhost",
|
||||||
|
|
@ -1016,7 +1078,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "vhost",
|
"key": "vhost",
|
||||||
|
|
@ -1062,7 +1124,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "type",
|
"key": "type",
|
||||||
|
|
@ -1120,7 +1182,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "vhost",
|
"key": "vhost",
|
||||||
|
|
@ -1166,7 +1228,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "vhost",
|
"key": "vhost",
|
||||||
|
|
@ -1212,7 +1274,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "type",
|
"key": "type",
|
||||||
|
|
@ -1258,7 +1320,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "type",
|
"key": "type",
|
||||||
|
|
@ -1304,7 +1366,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "url",
|
"key": "url",
|
||||||
|
|
@ -1345,7 +1407,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "stream_id",
|
"key": "stream_id",
|
||||||
|
|
@ -1376,7 +1438,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "port",
|
"key": "port",
|
||||||
|
|
@ -1435,7 +1497,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "dst_url",
|
"key": "dst_url",
|
||||||
|
|
@ -1476,7 +1538,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "stream_id",
|
"key": "stream_id",
|
||||||
|
|
@ -1507,7 +1569,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "stream_id",
|
"key": "stream_id",
|
||||||
|
|
@ -1543,7 +1605,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "stream_id",
|
"key": "stream_id",
|
||||||
|
|
@ -1574,7 +1636,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "stream_id",
|
"key": "stream_id",
|
||||||
|
|
@ -1605,7 +1667,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1631,7 +1693,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "vhost",
|
"key": "vhost",
|
||||||
|
|
@ -1734,7 +1796,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "vhost",
|
"key": "vhost",
|
||||||
|
|
@ -1822,7 +1884,7 @@
|
||||||
{
|
{
|
||||||
"key": "secret",
|
"key": "secret",
|
||||||
"value": "{{ZLMediaKit_secret}}",
|
"value": "{{ZLMediaKit_secret}}",
|
||||||
"description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数"
|
"description": "api操作密钥(配置文件配置)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "vhost",
|
"key": "vhost",
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,17 @@ if(ENABLE_SERVER_LIB)
|
||||||
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
PRIVATE ${COMPILE_OPTIONS_DEFAULT})
|
||||||
target_link_libraries(MediaServer
|
target_link_libraries(MediaServer
|
||||||
PRIVATE ${MK_LINK_LIBRARIES})
|
PRIVATE ${MK_LINK_LIBRARIES})
|
||||||
update_cached(MK_LINK_LIBRARIES MediaServer)
|
update_cached_list(MK_LINK_LIBRARIES MediaServer)
|
||||||
return()
|
return()
|
||||||
endif()
|
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
|
target_compile_definitions(MediaServer
|
||||||
PRIVATE ${COMPILE_DEFINITIONS})
|
PRIVATE ${COMPILE_DEFINITIONS})
|
||||||
target_compile_options(MediaServer
|
target_compile_options(MediaServer
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include "FFmpegSource.h"
|
#include "FFmpegSource.h"
|
||||||
#include "Common/config.h"
|
#include "Common/config.h"
|
||||||
#include "Common/MediaSource.h"
|
#include "Common/MediaSource.h"
|
||||||
|
#include "Common/MultiMediaSourceMuxer.h"
|
||||||
#include "Util/File.h"
|
#include "Util/File.h"
|
||||||
#include "System.h"
|
#include "System.h"
|
||||||
#include "Thread/WorkThreadPool.h"
|
#include "Thread/WorkThreadPool.h"
|
||||||
|
|
@ -39,7 +40,7 @@ onceToken token([]() {
|
||||||
//ffmpeg日志保存路径
|
//ffmpeg日志保存路径
|
||||||
mINI::Instance()[kLog] = "./ffmpeg/ffmpeg.log";
|
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()[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;
|
mINI::Instance()[kRestartSec] = 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -70,10 +71,10 @@ void FFmpegSource::setupRecordFlag(bool enable_hls, bool enable_mp4){
|
||||||
_enable_mp4 = 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) {
|
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_bin, FFmpeg::kBin);
|
||||||
GET_CONFIG(string,ffmpeg_cmd_default,FFmpeg::kCmd);
|
GET_CONFIG(string, ffmpeg_cmd_default, FFmpeg::kCmd);
|
||||||
GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog);
|
GET_CONFIG(string, ffmpeg_log, FFmpeg::kLog);
|
||||||
|
|
||||||
_src_url = src_url;
|
_src_url = src_url;
|
||||||
_dst_url = dst_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);
|
auto cmd_it = mINI::Instance().find(ffmpeg_cmd_key);
|
||||||
if (cmd_it != mINI::Instance().end()) {
|
if (cmd_it != mINI::Instance().end()) {
|
||||||
ffmpeg_cmd = cmd_it->second;
|
ffmpeg_cmd = cmd_it->second;
|
||||||
} else{
|
} else {
|
||||||
WarnL << "配置文件中,ffmpeg命令模板(" << ffmpeg_cmd_key << ")不存在,已采用默认模板(" << ffmpeg_cmd_default << ")";
|
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());
|
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);
|
auto log_file = ffmpeg_log.empty() ? "" : File::absolutePath("", ffmpeg_log);
|
||||||
_process.run(cmd, log_file);
|
_process.run(cmd, log_file);
|
||||||
InfoL << cmd;
|
InfoL << cmd;
|
||||||
|
|
||||||
if (is_local_ip(_media_info.host)) {
|
if (is_local_ip(_media_info.host)) {
|
||||||
//推流给自己的,通过判断流是否注册上来判断是否正常
|
// 推流给自己的,通过判断流是否注册上来判断是否正常
|
||||||
if (_media_info.schema != RTSP_SCHEMA && _media_info.schema != RTMP_SCHEMA) {
|
if (_media_info.schema != RTSP_SCHEMA && _media_info.schema != RTMP_SCHEMA) {
|
||||||
cb(SockException(Err_other,"本服务只支持rtmp/rtsp推流"));
|
cb(SockException(Err_other, "本服务只支持rtmp/rtsp推流"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
weak_ptr<FFmpegSource> weakSelf = shared_from_this();
|
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();
|
auto strongSelf = weakSelf.lock();
|
||||||
if(!strongSelf){
|
if (!strongSelf) {
|
||||||
//自己已经销毁
|
// 自己已经销毁
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(src){
|
if (src) {
|
||||||
//推流给自己成功
|
// 推流给自己成功
|
||||||
cb(SockException());
|
cb(SockException());
|
||||||
strongSelf->onGetMediaSource(src);
|
strongSelf->onGetMediaSource(src);
|
||||||
strongSelf->startTimer(timeout_ms);
|
strongSelf->startTimer(timeout_ms);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//推流失败
|
//推流失败
|
||||||
if(!strongSelf->_process.wait(false)){
|
if (!strongSelf->_process.wait(false)) {
|
||||||
//ffmpeg进程已经退出
|
// ffmpeg进程已经退出
|
||||||
cb(SockException(Err_other,StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code()));
|
cb(SockException(Err_other, StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//ffmpeg进程还在线,但是等待推流超时
|
// ffmpeg进程还在线,但是等待推流超时
|
||||||
cb(SockException(Err_other,"等待超时"));
|
cb(SockException(Err_other, "等待超时"));
|
||||||
});
|
});
|
||||||
} else{
|
} else{
|
||||||
//推流给其他服务器的,通过判断FFmpeg进程是否在线判断是否成功
|
//推流给其他服务器的,通过判断FFmpeg进程是否在线判断是否成功
|
||||||
weak_ptr<FFmpegSource> weakSelf = shared_from_this();
|
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();
|
auto strongSelf = weakSelf.lock();
|
||||||
if(!strongSelf){
|
if (!strongSelf) {
|
||||||
//自身已经销毁
|
// 自身已经销毁
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//FFmpeg还在线,那么我们认为推流成功
|
// FFmpeg还在线,那么我们认为推流成功
|
||||||
if(strongSelf->_process.wait(false)){
|
if (strongSelf->_process.wait(false)) {
|
||||||
cb(SockException());
|
cb(SockException());
|
||||||
strongSelf->startTimer(timeout_ms);
|
strongSelf->startTimer(timeout_ms);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//ffmpeg进程已经退出
|
// ffmpeg进程已经退出
|
||||||
cb(SockException(Err_other,StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code()));
|
cb(SockException(Err_other, StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code()));
|
||||||
return false;
|
return false;
|
||||||
},_poller);
|
}, _poller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFmpegSource::findAsync(int maxWaitMS, const function<void(const MediaSource::Ptr &src)> &cb) {
|
void FFmpegSource::findAsync(int maxWaitMS, const function<void(const MediaSource::Ptr &src)> &cb) {
|
||||||
auto src = MediaSource::find(_media_info.schema,
|
auto src = MediaSource::find(_media_info.schema, _media_info.vhost, _media_info.app, _media_info.stream);
|
||||||
_media_info.vhost,
|
if (src || !maxWaitMS) {
|
||||||
_media_info.app,
|
|
||||||
_media_info.stream);
|
|
||||||
if(src || !maxWaitMS){
|
|
||||||
cb(src);
|
cb(src);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *listener_tag = this;
|
void *listener_tag = this;
|
||||||
//若干秒后执行等待媒体注册超时回调
|
// 若干秒后执行等待媒体注册超时回调
|
||||||
auto onRegistTimeout = _poller->doDelayTask(maxWaitMS,[cb,listener_tag](){
|
auto onRegistTimeout = _poller->doDelayTask(maxWaitMS, [cb, listener_tag]() {
|
||||||
//取消监听该事件
|
// 取消监听该事件
|
||||||
NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged);
|
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
|
||||||
cb(nullptr);
|
cb(nullptr);
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
weak_ptr<FFmpegSource> weakSelf = shared_from_this();
|
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();
|
auto strongSelf = weakSelf.lock();
|
||||||
if(!strongSelf) {
|
if (!strongSelf) {
|
||||||
//本身已经销毁,取消延时任务
|
// 本身已经销毁,取消延时任务
|
||||||
onRegistTimeout->cancel();
|
onRegistTimeout->cancel();
|
||||||
NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged);
|
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bRegist ||
|
if (!bRegist || sender.getSchema() != strongSelf->_media_info.schema ||
|
||||||
sender.getSchema() != strongSelf->_media_info.schema ||
|
!equalMediaTuple(sender.getMediaTuple(), strongSelf->_media_info)) {
|
||||||
sender.getVhost() != strongSelf->_media_info.vhost ||
|
// 不是自己感兴趣的事件,忽略之
|
||||||
sender.getApp() != strongSelf->_media_info.app ||
|
|
||||||
sender.getId() != strongSelf->_media_info.stream) {
|
|
||||||
//不是自己感兴趣的事件,忽略之
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//查找的流终于注册上了;取消延时任务,防止多次回调
|
// 查找的流终于注册上了;取消延时任务,防止多次回调
|
||||||
onRegistTimeout->cancel();
|
onRegistTimeout->cancel();
|
||||||
//取消事件监听
|
// 取消事件监听
|
||||||
NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged);
|
NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged);
|
||||||
|
|
||||||
//切换到自己的线程再回复
|
// 切换到自己的线程再回复
|
||||||
strongSelf->_poller->async([weakSelf,cb](){
|
strongSelf->_poller->async([weakSelf, cb]() {
|
||||||
auto strongSelf = weakSelf.lock();
|
if (auto strongSelf = weakSelf.lock()) {
|
||||||
if(!strongSelf) {
|
// 再找一遍媒体源,一般能找到
|
||||||
return;
|
strongSelf->findAsync(0, cb);
|
||||||
}
|
}
|
||||||
//再找一遍媒体源,一般能找到
|
|
||||||
strongSelf->findAsync(0,cb);
|
|
||||||
}, false);
|
}, false);
|
||||||
};
|
};
|
||||||
//监听媒体注册事件
|
// 监听媒体注册事件
|
||||||
NoticeCenter::Instance().addListener(listener_tag, Broadcast::kBroadcastMediaChanged, onRegist);
|
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;
|
bool needRestart = ffmpeg_restart_sec > 0 && strongSelf->_replay_ticker.elapsedTime() > ffmpeg_restart_sec * 1000;
|
||||||
if (is_local_ip(strongSelf->_media_info.host)) {
|
if (is_local_ip(strongSelf->_media_info.host)) {
|
||||||
//推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常
|
// 推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常
|
||||||
strongSelf->findAsync(0, [&](const MediaSource::Ptr &src) {
|
strongSelf->findAsync(0, [&](const MediaSource::Ptr &src) {
|
||||||
//同步查找流
|
// 同步查找流
|
||||||
if (!src || needRestart) {
|
if (!src || needRestart) {
|
||||||
if(needRestart){
|
if (needRestart) {
|
||||||
strongSelf->_replay_ticker.resetTime();
|
strongSelf->_replay_ticker.resetTime();
|
||||||
if(strongSelf->_process.wait(false)){
|
if (strongSelf->_process.wait(false)) {
|
||||||
//FFmpeg进程还在运行,超时就关闭它
|
// FFmpeg进程还在运行,超时就关闭它
|
||||||
strongSelf->_process.kill(2000);
|
strongSelf->_process.kill(2000);
|
||||||
}
|
}
|
||||||
InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url;
|
InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url;
|
||||||
}
|
}
|
||||||
//流不在线,重新拉流, 这里原先是10秒超时,实际发现10秒不够,改成20秒了
|
// 流不在线,重新拉流, 这里原先是10秒超时,实际发现10秒不够,改成20秒了
|
||||||
if(strongSelf->_replay_ticker.elapsedTime() > 20 * 1000){
|
if (strongSelf->_replay_ticker.elapsedTime() > 20 * 1000) {
|
||||||
//上次重试时间超过10秒,那么再重试FFmpeg拉流
|
// 上次重试时间超过10秒,那么再重试FFmpeg拉流
|
||||||
strongSelf->_replay_ticker.resetTime();
|
strongSelf->_replay_ticker.resetTime();
|
||||||
strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [](const SockException &) {});
|
strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [](const SockException &) {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
//推流给其他服务器的,我们通过判断FFmpeg进程是否在线,如果FFmpeg推流中断,那么它应该会自动退出
|
// 推流给其他服务器的,我们通过判断FFmpeg进程是否在线,如果FFmpeg推流中断,那么它应该会自动退出
|
||||||
if (!strongSelf->_process.wait(false) || needRestart) {
|
if (!strongSelf->_process.wait(false) || needRestart) {
|
||||||
if(needRestart){
|
if (needRestart) {
|
||||||
strongSelf->_replay_ticker.resetTime();
|
strongSelf->_replay_ticker.resetTime();
|
||||||
if(strongSelf->_process.wait(false)){
|
if (strongSelf->_process.wait(false)) {
|
||||||
//FFmpeg进程还在运行,超时就关闭它
|
// FFmpeg进程还在运行,超时就关闭它
|
||||||
strongSelf->_process.kill(2000);
|
strongSelf->_process.kill(2000);
|
||||||
}
|
}
|
||||||
InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url;
|
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) {
|
strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [weakSelf](const SockException &ex) {
|
||||||
if(!ex){
|
if (!ex) {
|
||||||
//没有错误
|
// 没有错误
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto strongSelf = weakSelf.lock();
|
auto strongSelf = weakSelf.lock();
|
||||||
if (!strongSelf) {
|
if (!strongSelf) {
|
||||||
//自身已经销毁
|
// 自身已经销毁
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//上次重试时间超过10秒,那么再重试FFmpeg拉流
|
// 上次重试时间超过10秒,那么再重试FFmpeg拉流
|
||||||
strongSelf->startTimer(10 * 1000);
|
strongSelf->startTimer(10 * 1000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -296,20 +289,17 @@ MediaOriginType FFmpegSource::getOriginType(MediaSource &sender) const{
|
||||||
return MediaOriginType::ffmpeg_pull;
|
return MediaOriginType::ffmpeg_pull;
|
||||||
}
|
}
|
||||||
|
|
||||||
string FFmpegSource::getOriginUrl(MediaSource &sender) const{
|
string FFmpegSource::getOriginUrl(MediaSource &sender) const {
|
||||||
return _src_url;
|
return _src_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<SockInfo> FFmpegSource::getOriginSock(MediaSource &sender) const {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) {
|
void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) {
|
||||||
auto listener = src->getListener(true);
|
auto muxer = src->getMuxer();
|
||||||
if (listener.lock().get() != this) {
|
auto listener = muxer ? muxer->getDelegate() : nullptr;
|
||||||
|
if (listener && listener.get() != this) {
|
||||||
//防止多次进入onGetMediaSource函数导致无限递归调用的bug
|
//防止多次进入onGetMediaSource函数导致无限递归调用的bug
|
||||||
setDelegate(listener);
|
setDelegate(listener);
|
||||||
src->setListener(shared_from_this());
|
muxer->setDelegate(shared_from_this());
|
||||||
if (_enable_hls) {
|
if (_enable_hls) {
|
||||||
src->setupRecord(Recorder::type_hls, true, "", 0);
|
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) {
|
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_bin, FFmpeg::kBin);
|
||||||
GET_CONFIG(string,ffmpeg_snap,FFmpeg::kSnap);
|
GET_CONFIG(string, ffmpeg_snap, FFmpeg::kSnap);
|
||||||
GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog);
|
GET_CONFIG(string, ffmpeg_log, FFmpeg::kLog);
|
||||||
Ticker ticker;
|
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();
|
auto elapsed_ms = ticker.elapsedTime();
|
||||||
if (elapsed_ms > timeout_sec * 1000) {
|
if (elapsed_ms > timeout_sec * 1000) {
|
||||||
//超时,后台线程负载太高,当代太久才启动该任务
|
// 超时,后台线程负载太高,当代太久才启动该任务
|
||||||
cb(false, "wait work poller schedule snap task timeout");
|
cb(false, "wait work poller schedule snap task timeout");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -348,13 +338,12 @@ void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
//等待FFmpeg进程退出
|
// 等待FFmpeg进程退出
|
||||||
process->wait(true);
|
process->wait(true);
|
||||||
// FFmpeg进程退出了可以取消定时器了
|
// FFmpeg进程退出了可以取消定时器了
|
||||||
delayTask->cancel();
|
delayTask->cancel();
|
||||||
//执行回调函数
|
// 执行回调函数
|
||||||
bool success = process->exit_code() == 0 && File::fileSize(save_path.data());
|
bool success = process->exit_code() == 0 && File::fileSize(save_path.data());
|
||||||
cb(success, (!success && !log_file.empty()) ? File::loadFile(log_file.data()) : "");
|
cb(success, (!success && !log_file.empty()) ? File::loadFile(log_file.data()) : "");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
namespace FFmpeg {
|
namespace FFmpeg {
|
||||||
extern const std::string kSnap;
|
extern const std::string kSnap;
|
||||||
|
extern const std::string kBin;
|
||||||
}
|
}
|
||||||
|
|
||||||
class FFmpegSnap {
|
class FFmpegSnap {
|
||||||
|
|
@ -79,8 +80,6 @@ private:
|
||||||
mediakit::MediaOriginType getOriginType(mediakit::MediaSource &sender) const override;
|
mediakit::MediaOriginType getOriginType(mediakit::MediaSource &sender) const override;
|
||||||
//获取媒体源url或者文件路径
|
//获取媒体源url或者文件路径
|
||||||
std::string getOriginUrl(mediakit::MediaSource &sender) const override;
|
std::string getOriginUrl(mediakit::MediaSource &sender) const override;
|
||||||
// 获取媒体源客户端相关信息
|
|
||||||
std::shared_ptr<toolkit::SockInfo> getOriginSock(mediakit::MediaSource &sender) const override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _enable_hls = false;
|
bool _enable_hls = false;
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ static int cloneFunc(void *ptr) {
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void Process::run(const string &cmd, string &log_file) {
|
void Process::run(const string &cmd, string log_file) {
|
||||||
kill(2000);
|
kill(2000);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
STARTUPINFO si = { 0 };
|
STARTUPINFO si = { 0 };
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class Process {
|
||||||
public:
|
public:
|
||||||
Process();
|
Process();
|
||||||
~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);
|
void kill(int max_delay,bool force = false);
|
||||||
bool wait(bool block = true);
|
bool wait(bool block = true);
|
||||||
int exit_code();
|
int exit_code();
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,11 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "Common/JemallocUtil.h"
|
||||||
|
#include "Common/macros.h"
|
||||||
|
#include "System.h"
|
||||||
#include "Util/logger.h"
|
#include "Util/logger.h"
|
||||||
#include "Util/uv_errno.h"
|
#include "Util/uv_errno.h"
|
||||||
#include "System.h"
|
|
||||||
#include "Common/macros.h"
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
@ -55,6 +56,16 @@ string System::execute(const string &cmd) {
|
||||||
|
|
||||||
static constexpr int MAX_STACK_FRAMES = 128;
|
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) {
|
static void sig_crash(int sig) {
|
||||||
signal(sig, SIG_DFL);
|
signal(sig, SIG_DFL);
|
||||||
void *array[MAX_STACK_FRAMES];
|
void *array[MAX_STACK_FRAMES];
|
||||||
|
|
@ -126,6 +137,12 @@ void System::startDaemon(bool &kill_parent_if_failed) {
|
||||||
exit(0);
|
exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
signal(SIGTERM,[](int) {
|
||||||
|
WarnL << "收到主动退出信号,关闭父进程与子进程";
|
||||||
|
kill(pid, SIGINT);
|
||||||
|
exit(0);
|
||||||
|
});
|
||||||
|
|
||||||
do {
|
do {
|
||||||
int status = 0;
|
int status = 0;
|
||||||
if (waitpid(pid, &status, 0) >= 0) {
|
if (waitpid(pid, &status, 0) >= 0) {
|
||||||
|
|
@ -143,6 +160,12 @@ void System::startDaemon(bool &kill_parent_if_failed) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::systemSetup(){
|
void System::systemSetup(){
|
||||||
|
|
||||||
|
#ifdef ENABLE_JEMALLOC_DUMP
|
||||||
|
//Save memory report when program exits
|
||||||
|
atexit(save_jemalloc_stats);
|
||||||
|
#endif //ENABLE_JEMALLOC_DUMP
|
||||||
|
|
||||||
#if !defined(_WIN32)
|
#if !defined(_WIN32)
|
||||||
struct rlimit rlim,rlim_new;
|
struct rlimit rlim,rlim_new;
|
||||||
if (getrlimit(RLIMIT_CORE, &rlim)==0) {
|
if (getrlimit(RLIMIT_CORE, &rlim)==0) {
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ static HttpApi toApi(const function<void(API_ARGS_JSON_ASYNC)> &cb) {
|
||||||
//参数解析成json对象然后处理
|
//参数解析成json对象然后处理
|
||||||
Json::Value args;
|
Json::Value args;
|
||||||
Json::Reader reader;
|
Json::Reader reader;
|
||||||
reader.parse(parser.Content(), args);
|
reader.parse(parser.content(), args);
|
||||||
|
|
||||||
cb(sender, headerOut, HttpAllArgs<decltype(args)>(parser, args), val, invoker);
|
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;
|
Json::Value val;
|
||||||
val["code"] = API::Success;
|
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) {
|
static ApiArgsType getAllArgs(const Parser &parser) {
|
||||||
ApiArgsType allArgs;
|
ApiArgsType allArgs;
|
||||||
if (parser["Content-Type"].find("application/x-www-form-urlencoded") == 0) {
|
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) {
|
for (auto &pr : contentArgs) {
|
||||||
allArgs[pr.first] = HttpSession::urlDecode(pr.second);
|
allArgs[pr.first] = HttpSession::urlDecode(pr.second);
|
||||||
}
|
}
|
||||||
} else if (parser["Content-Type"].find("application/json") == 0) {
|
} else if (parser["Content-Type"].find("application/json") == 0) {
|
||||||
try {
|
try {
|
||||||
stringstream ss(parser.Content());
|
stringstream ss(parser.content());
|
||||||
Value jsonArgs;
|
Value jsonArgs;
|
||||||
ss >> jsonArgs;
|
ss >> jsonArgs;
|
||||||
auto keys = jsonArgs.getMemberNames();
|
auto keys = jsonArgs.getMemberNames();
|
||||||
|
|
@ -231,7 +231,7 @@ static inline void addHttpListener(){
|
||||||
GET_CONFIG(bool, api_debug, API::kApiDebug);
|
GET_CONFIG(bool, api_debug, API::kApiDebug);
|
||||||
//注册监听kBroadcastHttpRequest事件
|
//注册监听kBroadcastHttpRequest事件
|
||||||
NoticeCenter::Instance().addListener(&web_api_tag, Broadcast::kBroadcastHttpRequest, [](BroadcastHttpRequestArgs) {
|
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()) {
|
if (it == s_map_api.end()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -247,15 +247,15 @@ static inline void addHttpListener(){
|
||||||
size = body->remainSize();
|
size = body->remainSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
LogContextCapture log(getLogger(), toolkit::LTrace, __FILE__, "http api debug", __LINE__);
|
LogContextCapture log(getLogger(), toolkit::LDebug, __FILE__, "http api debug", __LINE__);
|
||||||
log << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n";
|
log << "\r\n# request:\r\n" << parser.method() << " " << parser.fullUrl() << "\r\n";
|
||||||
log << "# header:\r\n";
|
log << "# header:\r\n";
|
||||||
|
|
||||||
for (auto &pr : parser.getHeader()) {
|
for (auto &pr : parser.getHeader()) {
|
||||||
log << pr.first << " : " << pr.second << "\r\n";
|
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";
|
log << "# content:\r\n" << (content.size() > 4 * 1024 ? content.substr(0, 4 * 1024) : content) << "\r\n";
|
||||||
|
|
||||||
if (size > 0 && size < 4 * 1024) {
|
if (size > 0 && size < 4 * 1024) {
|
||||||
|
|
@ -321,12 +321,16 @@ static void fillSockInfo(Value& val, SockInfo* info) {
|
||||||
val["identifier"] = info->getIdentifier();
|
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 makeMediaSourceJson(MediaSource &media){
|
||||||
Value item;
|
Value item;
|
||||||
item["schema"] = media.getSchema();
|
item["schema"] = media.getSchema();
|
||||||
item[VHOST_KEY] = media.getVhost();
|
dumpMediaTuple(media.getMediaTuple(), item);
|
||||||
item["app"] = media.getApp();
|
|
||||||
item["stream"] = media.getId();
|
|
||||||
item["createStamp"] = (Json::UInt64) media.getCreateStamp();
|
item["createStamp"] = (Json::UInt64) media.getCreateStamp();
|
||||||
item["aliveSecond"] = (Json::UInt64) media.getAliveSecond();
|
item["aliveSecond"] = (Json::UInt64) media.getAliveSecond();
|
||||||
item["bytesSpeed"] = media.getBytesSpeed();
|
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);
|
lock_guard<recursive_mutex> lck(s_proxyMapMtx);
|
||||||
if (s_proxyMap.find(key) != s_proxyMap.end()) {
|
if (s_proxyMap.find(key) != s_proxyMap.end()) {
|
||||||
//已经在拉流了
|
//已经在拉流了
|
||||||
cb(SockException(Err_success), key);
|
cb(SockException(Err_other, "This stream already exists"), key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//添加拉流代理
|
//添加拉流代理
|
||||||
|
|
@ -584,7 +588,8 @@ void installWebApi() {
|
||||||
|
|
||||||
//获取线程负载
|
//获取线程负载
|
||||||
//测试url http://127.0.0.1/index/api/getThreadsLoad
|
//测试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) {
|
EventPollerPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
|
||||||
Value val;
|
Value val;
|
||||||
auto vec = EventPollerPool::Instance().getExecutorLoad();
|
auto vec = EventPollerPool::Instance().getExecutorLoad();
|
||||||
|
|
@ -602,7 +607,8 @@ void installWebApi() {
|
||||||
|
|
||||||
//获取后台工作线程负载
|
//获取后台工作线程负载
|
||||||
//测试url http://127.0.0.1/index/api/getWorkThreadsLoad
|
//测试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) {
|
WorkThreadPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
|
||||||
Value val;
|
Value val;
|
||||||
auto vec = WorkThreadPool::Instance().getExecutorLoad();
|
auto vec = WorkThreadPool::Instance().getExecutorLoad();
|
||||||
|
|
@ -648,6 +654,10 @@ void installWebApi() {
|
||||||
continue;
|
continue;
|
||||||
#endif
|
#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) {
|
if (ini[pr.first] == pr.second) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -656,7 +666,7 @@ void installWebApi() {
|
||||||
++changed;
|
++changed;
|
||||||
}
|
}
|
||||||
if (changed > 0) {
|
if (changed > 0) {
|
||||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
|
NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig);
|
||||||
ini.dumpFile(g_ini_file);
|
ini.dumpFile(g_ini_file);
|
||||||
}
|
}
|
||||||
val["changed"] = changed;
|
val["changed"] = changed;
|
||||||
|
|
@ -785,25 +795,40 @@ void installWebApi() {
|
||||||
throw ApiRetException("can not find the stream", API::NotFound);
|
throw ApiRetException("can not find the stream", API::NotFound);
|
||||||
}
|
}
|
||||||
src->getPlayerList(
|
src->getPlayerList(
|
||||||
[=](const std::list<std::shared_ptr<void>> &info_list) mutable {
|
[=](const std::list<toolkit::Any> &info_list) mutable {
|
||||||
val["code"] = API::Success;
|
val["code"] = API::Success;
|
||||||
auto &data = val["data"];
|
auto &data = val["data"];
|
||||||
data = Value(arrayValue);
|
data = Value(arrayValue);
|
||||||
for (auto &info : info_list) {
|
for (auto &info : info_list) {
|
||||||
auto obj = static_pointer_cast<Value>(info);
|
auto &obj = info.get<Value>();
|
||||||
data.append(std::move(*obj));
|
data.append(std::move(obj));
|
||||||
}
|
}
|
||||||
invoker(200, headerOut, val.toStyledString());
|
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 obj = std::make_shared<Value>();
|
||||||
auto session = static_pointer_cast<Session>(info);
|
auto &sock = info.get<SockInfo>();
|
||||||
fillSockInfo(*obj, session.get());
|
fillSockInfo(*obj, &sock);
|
||||||
(*obj)["typeid"] = toolkit::demangle(typeid(*session).name());
|
(*obj)["typeid"] = toolkit::demangle(typeid(sock).name());
|
||||||
return obj;
|
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
|
//测试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){
|
api_regist("/index/api/getMediaInfo",[](API_ARGS_MAP_ASYNC){
|
||||||
CHECK_SECRET();
|
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)) {
|
if (atoll(tm.data()) + expire_sec < time(NULL)) {
|
||||||
//截图已经过期,改名,以便再次请求时,可以返回老截图
|
//截图已经过期,改名,以便再次请求时,可以返回老截图
|
||||||
rename(path.data(), new_snap.data());
|
rename(path.data(), new_snap.data());
|
||||||
|
|
@ -1637,7 +1662,7 @@ void installWebApi() {
|
||||||
CHECK_ARGS("app", "stream");
|
CHECK_ARGS("app", "stream");
|
||||||
|
|
||||||
return StrPrinter << RTC_SCHEMA << "://" << _args["Host"] << "/" << _args["app"] << "/"
|
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:
|
private:
|
||||||
|
|
@ -1700,7 +1725,7 @@ void installWebApi() {
|
||||||
|
|
||||||
api_regist(delete_webrtc_url, [](API_ARGS_MAP_ASYNC) {
|
api_regist(delete_webrtc_url, [](API_ARGS_MAP_ASYNC) {
|
||||||
CHECK_ARGS("id", "token");
|
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"]);
|
auto obj = WebRtcTransportManager::Instance().getItem(allArgs["id"]);
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
invoker(404, headerOut, "id not found");
|
invoker(404, headerOut, "id not found");
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ typedef enum {
|
||||||
OtherFailed = -1,//业务代码执行失败,
|
OtherFailed = -1,//业务代码执行失败,
|
||||||
Success = 0//执行成功
|
Success = 0//执行成功
|
||||||
} ApiErr;
|
} ApiErr;
|
||||||
|
|
||||||
|
extern const std::string kSecret;
|
||||||
}//namespace API
|
}//namespace API
|
||||||
|
|
||||||
class ApiRetException: public std::runtime_error {
|
class ApiRetException: public std::runtime_error {
|
||||||
|
|
@ -219,14 +221,19 @@ bool checkArgs(Args &args, const First &first, const KeyTypes &...keys) {
|
||||||
throw InvalidArgsException("缺少必要参数:" #__VA_ARGS__); \
|
throw InvalidArgsException("缺少必要参数:" #__VA_ARGS__); \
|
||||||
}
|
}
|
||||||
|
|
||||||
//检查http参数中是否附带secret密钥的宏,127.0.0.1的ip不检查密钥
|
// 检查http参数中是否附带secret密钥的宏,127.0.0.1的ip不检查密钥
|
||||||
|
// 同时检测是否在ip白名单内
|
||||||
#define CHECK_SECRET() \
|
#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"); \
|
CHECK_ARGS("secret"); \
|
||||||
if(api_secret != allArgs["secret"]){ \
|
if (api_secret != allArgs["secret"]) { \
|
||||||
throw AuthException("secret错误"); \
|
throw AuthException("secret错误"); \
|
||||||
} \
|
} \
|
||||||
}
|
} while(false);
|
||||||
|
|
||||||
void installWebApi();
|
void installWebApi();
|
||||||
void unInstallWebApi();
|
void unInstallWebApi();
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ const string kOnFlowReport = HOOK_FIELD "on_flow_report";
|
||||||
const string kOnRtspRealm = HOOK_FIELD "on_rtsp_realm";
|
const string kOnRtspRealm = HOOK_FIELD "on_rtsp_realm";
|
||||||
const string kOnRtspAuth = HOOK_FIELD "on_rtsp_auth";
|
const string kOnRtspAuth = HOOK_FIELD "on_rtsp_auth";
|
||||||
const string kOnStreamChanged = HOOK_FIELD "on_stream_changed";
|
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 kOnStreamNotFound = HOOK_FIELD "on_stream_not_found";
|
||||||
const string kOnRecordMp4 = HOOK_FIELD "on_record_mp4";
|
const string kOnRecordMp4 = HOOK_FIELD "on_record_mp4";
|
||||||
const string kOnRecordTs = HOOK_FIELD "on_record_ts";
|
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 kOnStreamNoneReader = HOOK_FIELD "on_stream_none_reader";
|
||||||
const string kOnHttpAccess = HOOK_FIELD "on_http_access";
|
const string kOnHttpAccess = HOOK_FIELD "on_http_access";
|
||||||
const string kOnServerStarted = HOOK_FIELD "on_server_started";
|
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 kOnServerKeepalive = HOOK_FIELD "on_server_keepalive";
|
||||||
const string kOnSendRtpStopped = HOOK_FIELD "on_send_rtp_stopped";
|
const string kOnSendRtpStopped = HOOK_FIELD "on_send_rtp_stopped";
|
||||||
const string kOnRtpServerTimeout = HOOK_FIELD "on_rtp_server_timeout";
|
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 kAliveInterval = HOOK_FIELD "alive_interval";
|
||||||
const string kRetry = HOOK_FIELD "retry";
|
const string kRetry = HOOK_FIELD "retry";
|
||||||
const string kRetryDelay = HOOK_FIELD "retry_delay";
|
const string kRetryDelay = HOOK_FIELD "retry_delay";
|
||||||
|
|
@ -69,13 +70,14 @@ static onceToken token([]() {
|
||||||
mINI::Instance()[kOnStreamNoneReader] = "";
|
mINI::Instance()[kOnStreamNoneReader] = "";
|
||||||
mINI::Instance()[kOnHttpAccess] = "";
|
mINI::Instance()[kOnHttpAccess] = "";
|
||||||
mINI::Instance()[kOnServerStarted] = "";
|
mINI::Instance()[kOnServerStarted] = "";
|
||||||
|
mINI::Instance()[kOnServerExited] = "";
|
||||||
mINI::Instance()[kOnServerKeepalive] = "";
|
mINI::Instance()[kOnServerKeepalive] = "";
|
||||||
mINI::Instance()[kOnSendRtpStopped] = "";
|
mINI::Instance()[kOnSendRtpStopped] = "";
|
||||||
mINI::Instance()[kOnRtpServerTimeout] = "";
|
mINI::Instance()[kOnRtpServerTimeout] = "";
|
||||||
mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
|
|
||||||
mINI::Instance()[kAliveInterval] = 30.0;
|
mINI::Instance()[kAliveInterval] = 30.0;
|
||||||
mINI::Instance()[kRetry] = 1;
|
mINI::Instance()[kRetry] = 1;
|
||||||
mINI::Instance()[kRetryDelay] = 3.0;
|
mINI::Instance()[kRetryDelay] = 3.0;
|
||||||
|
mINI::Instance()[kStreamChangedSchemas] = "rtsp/rtmp/fmp4/ts/hls/hls.fmp4";
|
||||||
});
|
});
|
||||||
} // namespace Hook
|
} // namespace Hook
|
||||||
|
|
||||||
|
|
@ -100,14 +102,14 @@ static void parse_http_response(const SockException &ex, const Parser &res, cons
|
||||||
fun(Json::nullValue, errStr, should_retry);
|
fun(Json::nullValue, errStr, should_retry);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (res.Url() != "200") {
|
if (res.status() != "200") {
|
||||||
auto errStr = StrPrinter << "[bad http status code]:" << res.Url() << endl;
|
auto errStr = StrPrinter << "[bad http status code]:" << res.status() << endl;
|
||||||
fun(Json::nullValue, errStr, should_retry);
|
fun(Json::nullValue, errStr, should_retry);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Value result;
|
Value result;
|
||||||
try {
|
try {
|
||||||
stringstream ss(res.Content());
|
stringstream ss(res.content());
|
||||||
ss >> result;
|
ss >> result;
|
||||||
} catch (std::exception &ex) {
|
} catch (std::exception &ex) {
|
||||||
auto errStr = StrPrinter << "[parse json failed]:" << ex.what() << endl;
|
auto errStr = StrPrinter << "[parse json failed]:" << ex.what() << endl;
|
||||||
|
|
@ -164,12 +166,16 @@ string getVhost(const HttpArgs &value) {
|
||||||
return val != value.end() ? val->second : "";
|
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) {
|
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(string, mediaServerId, General::kMediaServerId);
|
||||||
GET_CONFIG(float, hook_timeoutSec, Hook::kTimeoutSec);
|
GET_CONFIG(float, hook_timeoutSec, Hook::kTimeoutSec);
|
||||||
GET_CONFIG(float, retry_delay, Hook::kRetryDelay);
|
GET_CONFIG(float, retry_delay, Hook::kRetryDelay);
|
||||||
|
|
||||||
const_cast<ArgsType &>(body)["mediaServerId"] = mediaServerId;
|
const_cast<ArgsType &>(body)["mediaServerId"] = mediaServerId;
|
||||||
|
const_cast<ArgsType &>(body)["hook_index"] = s_hook_index++;
|
||||||
|
|
||||||
auto requester = std::make_shared<HttpRequester>();
|
auto requester = std::make_shared<HttpRequester>();
|
||||||
requester->setMethod("POST");
|
requester->setMethod("POST");
|
||||||
auto bodyStr = to_string(body);
|
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);
|
do_http_hook(url, body, func, hook_retry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dumpMediaTuple(const MediaTuple &tuple, Json::Value& item);
|
||||||
|
|
||||||
static ArgsType make_json(const MediaInfo &args) {
|
static ArgsType make_json(const MediaInfo &args) {
|
||||||
ArgsType body;
|
ArgsType body;
|
||||||
body["schema"] = args.schema;
|
body["schema"] = args.schema;
|
||||||
body[VHOST_KEY] = args.vhost;
|
dumpMediaTuple(args, body);
|
||||||
body["app"] = args.app;
|
|
||||||
body["stream"] = args.stream;
|
|
||||||
body["params"] = args.param_strs;
|
body["params"] = args.param_strs;
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
@ -238,6 +244,18 @@ static void reportServerStarted() {
|
||||||
do_http_hook(hook_server_started, body, nullptr);
|
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 Timer::Ptr g_keepalive_timer;
|
||||||
static void reportServerKeepalive() {
|
static void reportServerKeepalive() {
|
||||||
|
|
@ -317,11 +335,10 @@ static mINI jsonToMini(const Value &obj) {
|
||||||
|
|
||||||
void installWebHook() {
|
void installWebHook() {
|
||||||
GET_CONFIG(bool, hook_enable, Hook::kEnable);
|
GET_CONFIG(bool, hook_enable, Hook::kEnable);
|
||||||
GET_CONFIG(string, hook_adminparams, Hook::kAdminParams);
|
|
||||||
|
|
||||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) {
|
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) {
|
||||||
GET_CONFIG(string, hook_publish, Hook::kOnPublish);
|
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());
|
invoker("", ProtocolOption());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -346,7 +363,7 @@ void installWebHook() {
|
||||||
|
|
||||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) {
|
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) {
|
||||||
GET_CONFIG(string, hook_play, Hook::kOnPlay);
|
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("");
|
invoker("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -360,7 +377,7 @@ void installWebHook() {
|
||||||
|
|
||||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) {
|
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) {
|
||||||
GET_CONFIG(string, hook_flowreport, Hook::kOnFlowReport);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
auto body = make_json(args);
|
auto body = make_json(args);
|
||||||
|
|
@ -379,7 +396,7 @@ void installWebHook() {
|
||||||
// 监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问
|
// 监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问
|
||||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastOnGetRtspRealm, [](BroadcastOnGetRtspRealmArgs) {
|
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastOnGetRtspRealm, [](BroadcastOnGetRtspRealmArgs) {
|
||||||
GET_CONFIG(string, hook_rtsp_realm, Hook::kOnRtspRealm);
|
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("");
|
invoker("");
|
||||||
return;
|
return;
|
||||||
|
|
@ -427,23 +444,37 @@ void installWebHook() {
|
||||||
|
|
||||||
// 监听rtsp、rtmp源注册或注销事件
|
// 监听rtsp、rtmp源注册或注销事件
|
||||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaChanged, [](BroadcastMediaChangedArgs) {
|
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaChanged, [](BroadcastMediaChangedArgs) {
|
||||||
GET_CONFIG(string, hook_stream_chaned, Hook::kOnStreamChanged);
|
GET_CONFIG(string, hook_stream_changed, Hook::kOnStreamChanged);
|
||||||
if (!hook_enable || hook_stream_chaned.empty()) {
|
if (!hook_enable || hook_stream_changed.empty()) {
|
||||||
return;
|
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;
|
ArgsType body;
|
||||||
if (bRegist) {
|
if (bRegist) {
|
||||||
body = makeMediaSourceJson(sender);
|
body = makeMediaSourceJson(sender);
|
||||||
body["regist"] = bRegist;
|
body["regist"] = bRegist;
|
||||||
} else {
|
} else {
|
||||||
body["schema"] = sender.getSchema();
|
body["schema"] = sender.getSchema();
|
||||||
body[VHOST_KEY] = sender.getVhost();
|
dumpMediaTuple(sender.getMediaTuple(), body);
|
||||||
body["app"] = sender.getApp();
|
|
||||||
body["stream"] = sender.getId();
|
|
||||||
body["regist"] = bRegist;
|
body["regist"] = bRegist;
|
||||||
}
|
}
|
||||||
// 执行hook
|
// 执行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) {
|
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["file_name"] = info.file_name;
|
||||||
body["folder"] = info.folder;
|
body["folder"] = info.folder;
|
||||||
body["url"] = info.url;
|
body["url"] = info.url;
|
||||||
body["app"] = info.app;
|
dumpMediaTuple(info, body);
|
||||||
body["stream"] = info.stream;
|
|
||||||
body[VHOST_KEY] = info.vhost;
|
|
||||||
return body;
|
return body;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -532,7 +561,7 @@ void installWebHook() {
|
||||||
|
|
||||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastShellLogin, [](BroadcastShellLoginArgs) {
|
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastShellLogin, [](BroadcastShellLoginArgs) {
|
||||||
GET_CONFIG(string, hook_shell_login, Hook::kOnShellLogin);
|
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("");
|
invoker("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -561,9 +590,7 @@ void installWebHook() {
|
||||||
|
|
||||||
ArgsType body;
|
ArgsType body;
|
||||||
body["schema"] = sender.getSchema();
|
body["schema"] = sender.getSchema();
|
||||||
body[VHOST_KEY] = sender.getVhost();
|
dumpMediaTuple(sender.getMediaTuple(), body);
|
||||||
body["app"] = sender.getApp();
|
|
||||||
body["stream"] = sender.getId();
|
|
||||||
weak_ptr<MediaSource> weakSrc = sender.shared_from_this();
|
weak_ptr<MediaSource> weakSrc = sender.shared_from_this();
|
||||||
// 执行hook
|
// 执行hook
|
||||||
do_http_hook(hook_stream_none_reader, body, [weakSrc](const Value &obj, const string &err) {
|
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);
|
GET_CONFIG(string, hook_send_rtp_stopped, Hook::kOnSendRtpStopped);
|
||||||
if (!hook_enable || hook_send_rtp_stopped.empty()) {
|
if (!hook_enable || hook_send_rtp_stopped.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArgsType body;
|
ArgsType body;
|
||||||
body[VHOST_KEY] = sender.getVhost();
|
dumpMediaTuple(sender.getMediaTuple(), body);
|
||||||
body["app"] = sender.getApp();
|
|
||||||
body["stream"] = sender.getStreamId();
|
|
||||||
body["ssrc"] = ssrc;
|
body["ssrc"] = ssrc;
|
||||||
body["originType"] = (int)sender.getOriginType(MediaSource::NullMediaSource());
|
body["originType"] = (int)sender.getOriginType(MediaSource::NullMediaSource());
|
||||||
body["originTypeStr"] = getOriginTypeString(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) {
|
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastHttpAccess, [](BroadcastHttpAccessArgs) {
|
||||||
GET_CONFIG(string, hook_http_access, Hook::kOnHttpAccess);
|
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()) {
|
if (!hook_enable || hook_http_access.empty()) {
|
||||||
// 未开启http文件访问鉴权,那么允许访问,但是每次访问都要鉴权;
|
// 未开启http文件访问鉴权,那么允许访问,但是每次访问都要鉴权;
|
||||||
// 因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权)
|
// 因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权)
|
||||||
invoker("", "", 0);
|
if (!HttpFileManager::isIPAllowed(sender.get_peer_ip())) {
|
||||||
|
invoker("Your ip is not allowed to access the service.", "", 0);
|
||||||
|
} else {
|
||||||
|
invoker("", "", 0);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -632,7 +656,7 @@ void installWebHook() {
|
||||||
body["id"] = sender.getIdentifier();
|
body["id"] = sender.getIdentifier();
|
||||||
body["path"] = path;
|
body["path"] = path;
|
||||||
body["is_dir"] = is_dir;
|
body["is_dir"] = is_dir;
|
||||||
body["params"] = parser.Params();
|
body["params"] = parser.params();
|
||||||
for (auto &pr : parser.getHeader()) {
|
for (auto &pr : parser.getHeader()) {
|
||||||
body[string("header.") + pr.first] = pr.second;
|
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);
|
GET_CONFIG(string, rtp_server_timeout, Hook::kOnRtpServerTimeout);
|
||||||
if (!hook_enable || rtp_server_timeout.empty()) {
|
if (!hook_enable || rtp_server_timeout.empty()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -676,3 +700,7 @@ void unInstallWebHook() {
|
||||||
g_keepalive_timer.reset();
|
g_keepalive_timer.reset();
|
||||||
NoticeCenter::Instance().delListener(&web_hook_tag);
|
NoticeCenter::Instance().delListener(&web_hook_tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onProcessExited() {
|
||||||
|
reportServerExited();
|
||||||
|
}
|
||||||
|
|
@ -31,6 +31,7 @@ extern const std::string kTimeoutSec;
|
||||||
|
|
||||||
void installWebHook();
|
void installWebHook();
|
||||||
void unInstallWebHook();
|
void unInstallWebHook();
|
||||||
|
void onProcessExited();
|
||||||
/**
|
/**
|
||||||
* 触发http hook请求
|
* 触发http hook请求
|
||||||
* @param url 请求地址
|
* @param url 请求地址
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,29 @@ public:
|
||||||
throw ExitException();
|
throw ExitException();
|
||||||
});
|
});
|
||||||
#endif
|
#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{}
|
~CMD_main() override{}
|
||||||
|
|
@ -213,9 +236,11 @@ int start_main(int argc,char *argv[]) {
|
||||||
//设置日志
|
//设置日志
|
||||||
Logger::Instance().add(std::make_shared<ConsoleChannel>("ConsoleChannel", logLevel));
|
Logger::Instance().add(std::make_shared<ConsoleChannel>("ConsoleChannel", logLevel));
|
||||||
#if !defined(ANDROID)
|
#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->setMaxDay(cmd_main["max_day"]);
|
||||||
|
fileChannel->setFileMaxCount(cmd_main["log-slice"]);
|
||||||
|
fileChannel->setFileMaxSize(cmd_main["log-size"]);
|
||||||
Logger::Instance().add(fileChannel);
|
Logger::Instance().add(fileChannel);
|
||||||
#endif // !defined(ANDROID)
|
#endif // !defined(ANDROID)
|
||||||
|
|
||||||
|
|
@ -326,6 +351,14 @@ int start_main(int argc,char *argv[]) {
|
||||||
#endif //defined(ENABLE_SRT)
|
#endif //defined(ENABLE_SRT)
|
||||||
|
|
||||||
try {
|
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
|
//rtsp服务器,端口默认554
|
||||||
if (rtspPort) { rtspSrv->start<RtspSession>(rtspPort); }
|
if (rtspPort) { rtspSrv->start<RtspSession>(rtspPort); }
|
||||||
//rtsps服务器,端口默认322
|
//rtsps服务器,端口默认322
|
||||||
|
|
@ -363,8 +396,7 @@ int start_main(int argc,char *argv[]) {
|
||||||
#endif//defined(ENABLE_SRT)
|
#endif//defined(ENABLE_SRT)
|
||||||
|
|
||||||
} catch (std::exception &ex) {
|
} catch (std::exception &ex) {
|
||||||
WarnL << "端口占用或无权限:" << ex.what() << endl;
|
ErrorL << "Start server failed: " << ex.what();
|
||||||
ErrorL << "程序启动失败,请修改配置文件中端口号后重试!" << endl;
|
|
||||||
sleep(1);
|
sleep(1);
|
||||||
#if !defined(_WIN32)
|
#if !defined(_WIN32)
|
||||||
if (pid != getpid() && kill_parent_if_failed) {
|
if (pid != getpid() && kill_parent_if_failed) {
|
||||||
|
|
@ -384,9 +416,15 @@ int start_main(int argc,char *argv[]) {
|
||||||
static semaphore sem;
|
static semaphore sem;
|
||||||
signal(SIGINT, [](int) {
|
signal(SIGINT, [](int) {
|
||||||
InfoL << "SIGINT:exit";
|
InfoL << "SIGINT:exit";
|
||||||
signal(SIGINT, SIG_IGN);// 设置退出信号
|
signal(SIGINT, SIG_IGN); // 设置退出信号
|
||||||
sem.post();
|
sem.post();
|
||||||
});// 设置退出信号
|
}); // 设置退出信号
|
||||||
|
|
||||||
|
signal(SIGTERM,[](int) {
|
||||||
|
WarnL << "SIGTERM:exit";
|
||||||
|
signal(SIGTERM, SIG_IGN);
|
||||||
|
sem.post();
|
||||||
|
});
|
||||||
|
|
||||||
#if !defined(_WIN32)
|
#if !defined(_WIN32)
|
||||||
signal(SIGHUP, [](int) { mediakit::loadIniConfig(g_ini_file.data()); });
|
signal(SIGHUP, [](int) { mediakit::loadIniConfig(g_ini_file.data()); });
|
||||||
|
|
@ -395,6 +433,8 @@ int start_main(int argc,char *argv[]) {
|
||||||
}
|
}
|
||||||
unInstallWebApi();
|
unInstallWebApi();
|
||||||
unInstallWebHook();
|
unInstallWebHook();
|
||||||
|
onProcessExited();
|
||||||
|
|
||||||
//休眠1秒再退出,防止资源释放顺序错误
|
//休眠1秒再退出,防止资源释放顺序错误
|
||||||
InfoL << "程序退出中,请等待...";
|
InfoL << "程序退出中,请等待...";
|
||||||
sleep(1);
|
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, "zerolatency", "1", 0);
|
||||||
av_dict_set(&dict, "strict", "-2", 0);
|
av_dict_set(&dict, "strict", "-2", 0);
|
||||||
|
|
||||||
|
#ifdef AV_CODEC_CAP_TRUNCATED
|
||||||
if (codec->capabilities & AV_CODEC_CAP_TRUNCATED) {
|
if (codec->capabilities & AV_CODEC_CAP_TRUNCATED) {
|
||||||
/* we do not send complete frames */
|
/* we do not send complete frames */
|
||||||
_context->flags |= AV_CODEC_FLAG_TRUNCATED;
|
_context->flags |= AV_CODEC_FLAG_TRUNCATED;
|
||||||
|
|
@ -443,6 +444,7 @@ FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std:
|
||||||
// 此时业务层应该需要合帧
|
// 此时业务层应该需要合帧
|
||||||
_do_merger = true;
|
_do_merger = true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
int ret = avcodec_open2(_context.get(), codec, &dict);
|
int ret = avcodec_open2(_context.get(), codec, &dict);
|
||||||
av_dict_free(&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 "MediaSource.h"
|
||||||
#include "Common/config.h"
|
#include "Common/config.h"
|
||||||
#include "Common/Parser.h"
|
#include "Common/Parser.h"
|
||||||
|
#include "Common/MultiMediaSourceMuxer.h"
|
||||||
#include "Record/MP4Reader.h"
|
#include "Record/MP4Reader.h"
|
||||||
#include "PacketCache.h"
|
#include "PacketCache.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
|
|
@ -53,12 +55,14 @@ string getOriginTypeString(MediaOriginType type){
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
ProtocolOption::ProtocolOption() {
|
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_enabel_audio, Protocol::kEnableAudio);
|
||||||
GET_CONFIG(bool, s_add_mute_audio, Protocol::kAddMuteAudio);
|
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(uint32_t, s_continue_push_ms, Protocol::kContinuePushMS);
|
||||||
|
|
||||||
GET_CONFIG(bool, s_enable_hls, Protocol::kEnableHls);
|
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_mp4, Protocol::kEnableMP4);
|
||||||
GET_CONFIG(bool, s_enable_rtsp, Protocol::kEnableRtsp);
|
GET_CONFIG(bool, s_enable_rtsp, Protocol::kEnableRtsp);
|
||||||
GET_CONFIG(bool, s_enable_rtmp, Protocol::kEnableRtmp);
|
GET_CONFIG(bool, s_enable_rtmp, Protocol::kEnableRtmp);
|
||||||
|
|
@ -80,9 +84,11 @@ ProtocolOption::ProtocolOption() {
|
||||||
modify_stamp = s_modify_stamp;
|
modify_stamp = s_modify_stamp;
|
||||||
enable_audio = s_enabel_audio;
|
enable_audio = s_enabel_audio;
|
||||||
add_mute_audio = s_add_mute_audio;
|
add_mute_audio = s_add_mute_audio;
|
||||||
|
auto_close = s_auto_close;
|
||||||
continue_push_ms = s_continue_push_ms;
|
continue_push_ms = s_continue_push_ms;
|
||||||
|
|
||||||
enable_hls = s_enable_hls;
|
enable_hls = s_enable_hls;
|
||||||
|
enable_hls_fmp4 = s_enable_hls_fmp4;
|
||||||
enable_mp4 = s_enable_mp4;
|
enable_mp4 = s_enable_mp4;
|
||||||
enable_rtsp = s_enable_rtsp;
|
enable_rtsp = s_enable_rtsp;
|
||||||
enable_rtmp = s_enable_rtmp;
|
enable_rtmp = s_enable_rtmp;
|
||||||
|
|
@ -124,24 +130,11 @@ MediaSource::MediaSource(const string &schema, const MediaTuple& tuple): _tuple(
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaSource::~MediaSource() {
|
MediaSource::~MediaSource() {
|
||||||
unregist();
|
try {
|
||||||
}
|
unregist();
|
||||||
|
} catch (std::exception &ex) {
|
||||||
const string& MediaSource::getSchema() const {
|
WarnL << "Exception occurred: " << ex.what();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<void> MediaSource::getOwnership() {
|
std::shared_ptr<void> MediaSource::getOwnership() {
|
||||||
|
|
@ -183,20 +176,8 @@ void MediaSource::setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
||||||
_listener = listener;
|
_listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::weak_ptr<MediaSourceEvent> MediaSource::getListener(bool next) const{
|
std::weak_ptr<MediaSourceEvent> MediaSource::getListener() const {
|
||||||
if (!next) {
|
return _listener;
|
||||||
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(){
|
int MediaSource::totalReaderCount(){
|
||||||
|
|
@ -288,6 +269,11 @@ toolkit::EventPoller::Ptr MediaSource::getOwnerPoller() {
|
||||||
throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed: " + getUrl());
|
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) {
|
void MediaSource::onReaderChanged(int size) {
|
||||||
try {
|
try {
|
||||||
weak_ptr<MediaSource> weak_self = shared_from_this();
|
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) {
|
auto on_register = [weak_session, info, cb_once, cancel_all, poller](BroadcastMediaChangedArgs) {
|
||||||
if (!bRegist ||
|
if (!bRegist ||
|
||||||
sender.getSchema() != info.schema ||
|
sender.getSchema() != info.schema ||
|
||||||
sender.getVhost() != info.vhost ||
|
!equalMediaTuple(sender.getMediaTuple(), info)) {
|
||||||
sender.getApp() != info.app ||
|
|
||||||
sender.getId() != info.stream) {
|
|
||||||
//不是自己感兴趣的事件,忽略之
|
//不是自己感兴趣的事件,忽略之
|
||||||
return;
|
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) {
|
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);
|
listener->onRegist(*this, regist);
|
||||||
}
|
}
|
||||||
//触发广播
|
//触发广播
|
||||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, regist, *this);
|
NOTICE_EMIT(BroadcastMediaChangedArgs, Broadcast::kBroadcastMediaChanged, regist, *this);
|
||||||
InfoL << (regist ? "媒体注册:" : "媒体注销:") << getUrl();
|
InfoL << (regist ? "媒体注册:" : "媒体注销:") << getUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -575,6 +559,9 @@ bool MediaSource::unregist() {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool equalMediaTuple(const MediaTuple& a, const MediaTuple& b) {
|
||||||
|
return a.vhost == b.vhost && a.app == b.app && a.stream == b.stream;
|
||||||
|
}
|
||||||
/////////////////////////////////////MediaInfo//////////////////////////////////////
|
/////////////////////////////////////MediaInfo//////////////////////////////////////
|
||||||
|
|
||||||
void MediaInfo::parse(const std::string &url_in){
|
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(string, record_app, Record::kAppName);
|
||||||
GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS);
|
GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS);
|
||||||
//如果mp4点播, 无人观看时我们强制关闭点播
|
//如果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();
|
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]() {
|
_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) {
|
if (!is_mp4_vod) {
|
||||||
//直播时触发无人观看事件,让开发者自行选择是否关闭
|
auto muxer = strong_sender->getMuxer();
|
||||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender);
|
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 {
|
} else {
|
||||||
//这个是mp4点播,我们自动关闭
|
//这个是mp4点播,我们自动关闭
|
||||||
WarnL << "MP4点播无人观看,自动关闭:" << strong_sender->getUrl();
|
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");
|
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) {
|
bool MediaSourceEventInterceptor::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) {
|
||||||
auto listener = _listener.lock();
|
auto listener = _listener.lock();
|
||||||
if (!listener) {
|
if (!listener) {
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ enum class MediaOriginType : uint8_t {
|
||||||
std::string getOriginTypeString(MediaOriginType type);
|
std::string getOriginTypeString(MediaOriginType type);
|
||||||
|
|
||||||
class MediaSource;
|
class MediaSource;
|
||||||
|
class MultiMediaSourceMuxer;
|
||||||
class MediaSourceEvent {
|
class MediaSourceEvent {
|
||||||
public:
|
public:
|
||||||
friend class MediaSource;
|
friend class MediaSource;
|
||||||
|
|
@ -88,6 +89,8 @@ public:
|
||||||
virtual bool isRecording(MediaSource &sender, Recorder::type type) { return false; }
|
virtual bool isRecording(MediaSource &sender, Recorder::type type) { return false; }
|
||||||
// 获取所有track相关信息
|
// 获取所有track相关信息
|
||||||
virtual std::vector<Track::Ptr> getMediaTracks(MediaSource &sender, bool trackReady = true) const { return std::vector<Track::Ptr>(); };
|
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 {
|
class SendRtpArgs {
|
||||||
public:
|
public:
|
||||||
|
|
@ -136,17 +139,30 @@ class ProtocolOption {
|
||||||
public:
|
public:
|
||||||
ProtocolOption();
|
ProtocolOption();
|
||||||
|
|
||||||
//时间戳修复这一路流标志位
|
enum {
|
||||||
bool modify_stamp;
|
kModifyStampOff = 0, // 采用源视频流绝对时间戳,不做任何改变
|
||||||
|
kModifyStampSystem = 1, // 采用zlmediakit接收数据时的系统时间戳(有平滑处理)
|
||||||
|
kModifyStampRelative = 2 // 采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正
|
||||||
|
};
|
||||||
|
// 时间戳类型
|
||||||
|
int modify_stamp;
|
||||||
|
|
||||||
//转协议是否开启音频
|
//转协议是否开启音频
|
||||||
bool enable_audio;
|
bool enable_audio;
|
||||||
//添加静音音频,在关闭音频时,此开关无效
|
//添加静音音频,在关闭音频时,此开关无效
|
||||||
bool add_mute_audio;
|
bool add_mute_audio;
|
||||||
|
// 无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)
|
||||||
|
// 此配置置1时,此流如果无人观看,将不触发on_none_reader hook回调,
|
||||||
|
// 而是将直接关闭流
|
||||||
|
bool auto_close;
|
||||||
|
|
||||||
//断连续推延时,单位毫秒,默认采用配置文件
|
//断连续推延时,单位毫秒,默认采用配置文件
|
||||||
uint32_t continue_push_ms;
|
uint32_t continue_push_ms;
|
||||||
|
|
||||||
//是否开启转换为hls
|
//是否开启转换为hls(mpegts)
|
||||||
bool enable_hls;
|
bool enable_hls;
|
||||||
|
//是否开启转换为hls(fmp4)
|
||||||
|
bool enable_hls_fmp4;
|
||||||
//是否开启MP4录制
|
//是否开启MP4录制
|
||||||
bool enable_mp4;
|
bool enable_mp4;
|
||||||
//是否开启转换为rtsp/webrtc
|
//是否开启转换为rtsp/webrtc
|
||||||
|
|
@ -179,15 +195,20 @@ public:
|
||||||
//hls录制保存路径
|
//hls录制保存路径
|
||||||
std::string hls_save_path;
|
std::string hls_save_path;
|
||||||
|
|
||||||
|
// 支持通过on_publish返回值替换stream_id
|
||||||
|
std::string stream_replace;
|
||||||
|
|
||||||
template <typename MAP>
|
template <typename MAP>
|
||||||
ProtocolOption(const MAP &allArgs) : ProtocolOption() {
|
ProtocolOption(const MAP &allArgs) : ProtocolOption() {
|
||||||
#define GET_OPT_VALUE(key) getArgsValue(allArgs, #key, key)
|
#define GET_OPT_VALUE(key) getArgsValue(allArgs, #key, key)
|
||||||
GET_OPT_VALUE(modify_stamp);
|
GET_OPT_VALUE(modify_stamp);
|
||||||
GET_OPT_VALUE(enable_audio);
|
GET_OPT_VALUE(enable_audio);
|
||||||
GET_OPT_VALUE(add_mute_audio);
|
GET_OPT_VALUE(add_mute_audio);
|
||||||
|
GET_OPT_VALUE(auto_close);
|
||||||
GET_OPT_VALUE(continue_push_ms);
|
GET_OPT_VALUE(continue_push_ms);
|
||||||
|
|
||||||
GET_OPT_VALUE(enable_hls);
|
GET_OPT_VALUE(enable_hls);
|
||||||
|
GET_OPT_VALUE(enable_hls_fmp4);
|
||||||
GET_OPT_VALUE(enable_mp4);
|
GET_OPT_VALUE(enable_mp4);
|
||||||
GET_OPT_VALUE(enable_rtsp);
|
GET_OPT_VALUE(enable_rtsp);
|
||||||
GET_OPT_VALUE(enable_rtmp);
|
GET_OPT_VALUE(enable_rtmp);
|
||||||
|
|
@ -205,6 +226,7 @@ public:
|
||||||
GET_OPT_VALUE(mp4_save_path);
|
GET_OPT_VALUE(mp4_save_path);
|
||||||
|
|
||||||
GET_OPT_VALUE(hls_save_path);
|
GET_OPT_VALUE(hls_save_path);
|
||||||
|
GET_OPT_VALUE(stream_replace);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -244,6 +266,7 @@ public:
|
||||||
bool stopSendRtp(MediaSource &sender, const std::string &ssrc) override;
|
bool stopSendRtp(MediaSource &sender, const std::string &ssrc) override;
|
||||||
float getLossRate(MediaSource &sender, TrackType type) override;
|
float getLossRate(MediaSource &sender, TrackType type) override;
|
||||||
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
|
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
|
||||||
|
std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::weak_ptr<MediaSourceEvent> _listener;
|
std::weak_ptr<MediaSourceEvent> _listener;
|
||||||
|
|
@ -268,6 +291,8 @@ public:
|
||||||
std::string param_strs;
|
std::string param_strs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool equalMediaTuple(const MediaTuple& a, const MediaTuple& b);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 媒体源,任何rtsp/rtmp的直播流都源自该对象
|
* 媒体源,任何rtsp/rtmp的直播流都源自该对象
|
||||||
*/
|
*/
|
||||||
|
|
@ -282,21 +307,15 @@ public:
|
||||||
////////////////获取MediaSource相关信息////////////////
|
////////////////获取MediaSource相关信息////////////////
|
||||||
|
|
||||||
// 获取协议类型
|
// 获取协议类型
|
||||||
const std::string& getSchema() const;
|
const std::string& getSchema() const {
|
||||||
// 虚拟主机
|
return _schema;
|
||||||
const std::string& getVhost() const;
|
}
|
||||||
// 应用名
|
|
||||||
const std::string& getApp() const;
|
|
||||||
// 流id
|
|
||||||
const std::string& getId() const;
|
|
||||||
|
|
||||||
const MediaTuple& getMediaTuple() const {
|
const MediaTuple& getMediaTuple() const {
|
||||||
return _tuple;
|
return _tuple;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string shortUrl() const { return _tuple.shortUrl(); }
|
std::string getUrl() const { return _schema + "://" + _tuple.shortUrl(); }
|
||||||
|
|
||||||
std::string getUrl() const { return _schema + "://" + shortUrl(); }
|
|
||||||
|
|
||||||
//获取对象所有权
|
//获取对象所有权
|
||||||
std::shared_ptr<void> getOwnership();
|
std::shared_ptr<void> getOwnership();
|
||||||
|
|
@ -321,19 +340,21 @@ public:
|
||||||
// 设置监听者
|
// 设置监听者
|
||||||
virtual void setListener(const std::weak_ptr<MediaSourceEvent> &listener);
|
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;
|
virtual int readerCount() = 0;
|
||||||
// 观看者个数,包括(hls/rtsp/rtmp)
|
// 观看者个数,包括(hls/rtsp/rtmp)
|
||||||
virtual int totalReaderCount();
|
virtual int totalReaderCount();
|
||||||
// 获取播放器列表
|
// 获取播放器列表
|
||||||
virtual void getPlayerList(const std::function<void(const std::list<std::shared_ptr<void>> &info_list)> &cb,
|
virtual void getPlayerList(const std::function<void(const std::list<toolkit::Any> &info_list)> &cb,
|
||||||
const std::function<std::shared_ptr<void>(std::shared_ptr<void> &&info)> &on_change) {
|
const std::function<toolkit::Any(toolkit::Any &&info)> &on_change) {
|
||||||
assert(cb);
|
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;
|
MediaOriginType getOriginType() const;
|
||||||
// 获取媒体源url或者文件路径
|
// 获取媒体源url或者文件路径
|
||||||
|
|
@ -363,6 +384,8 @@ public:
|
||||||
float getLossRate(mediakit::TrackType type);
|
float getLossRate(mediakit::TrackType type);
|
||||||
// 获取所在线程
|
// 获取所在线程
|
||||||
toolkit::EventPoller::Ptr getOwnerPoller();
|
toolkit::EventPoller::Ptr getOwnerPoller();
|
||||||
|
// 获取MultiMediaSourceMuxer对象
|
||||||
|
std::shared_ptr<MultiMediaSourceMuxer> getMuxer();
|
||||||
|
|
||||||
////////////////static方法,查找或生成MediaSource////////////////
|
////////////////static方法,查找或生成MediaSource////////////////
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ static std::shared_ptr<MediaSinkInterface> makeRecorder(MediaSource &sender, con
|
||||||
for (auto &track : tracks) {
|
for (auto &track : tracks) {
|
||||||
recorder->addTrack(track);
|
recorder->addTrack(track);
|
||||||
}
|
}
|
||||||
|
recorder->addTrackCompleted();
|
||||||
return recorder;
|
return recorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,16 +71,12 @@ static string getTrackInfoStr(const TrackSource *track_src){
|
||||||
return std::move(codec_info);
|
return std::move(codec_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string &MultiMediaSourceMuxer::getVhost() const {
|
const ProtocolOption &MultiMediaSourceMuxer::getOption() const {
|
||||||
return _tuple.vhost;
|
return _option;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string &MultiMediaSourceMuxer::getApp() const {
|
const MediaTuple &MultiMediaSourceMuxer::getMediaTuple() const {
|
||||||
return _tuple.app;
|
return _tuple;
|
||||||
}
|
|
||||||
|
|
||||||
const std::string &MultiMediaSourceMuxer::getStreamId() const {
|
|
||||||
return _tuple.stream;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string MultiMediaSourceMuxer::shortUrl() const {
|
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) {
|
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();
|
_poller = EventPollerPool::Instance().getPoller();
|
||||||
_create_in_poller = _poller->isCurrentThread();
|
_create_in_poller = _poller->isCurrentThread();
|
||||||
_option = option;
|
_option = option;
|
||||||
|
if (dur_sec > 0.01) {
|
||||||
|
// 点播
|
||||||
|
_stamp[TrackVideo].setPlayBack();
|
||||||
|
_stamp[TrackAudio].setPlayBack();
|
||||||
|
}
|
||||||
|
|
||||||
if (option.enable_rtmp) {
|
if (option.enable_rtmp) {
|
||||||
_rtmp = std::make_shared<RtmpMediaSourceMuxer>(_tuple, option, std::make_shared<TitleMeta>(dur_sec));
|
_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) {
|
if (option.enable_hls) {
|
||||||
_hls = dynamic_pointer_cast<HlsRecorder>(Recorder::createRecorder(Recorder::type_hls, _tuple, option));
|
_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) {
|
if (option.enable_mp4) {
|
||||||
_mp4 = Recorder::createRecorder(Recorder::type_mp4, _tuple, option);
|
_mp4 = Recorder::createRecorder(Recorder::type_mp4, _tuple, option);
|
||||||
}
|
}
|
||||||
if (option.enable_ts) {
|
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) {
|
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);
|
enableAudio(option.enable_audio);
|
||||||
|
|
@ -135,14 +142,14 @@ void MultiMediaSourceMuxer::setMediaListener(const std::weak_ptr<MediaSourceEven
|
||||||
if (_ts) {
|
if (_ts) {
|
||||||
_ts->setListener(self);
|
_ts->setListener(self);
|
||||||
}
|
}
|
||||||
#if defined(ENABLE_MP4)
|
|
||||||
if (_fmp4) {
|
if (_fmp4) {
|
||||||
_fmp4->setListener(self);
|
_fmp4->setListener(self);
|
||||||
}
|
}
|
||||||
#endif
|
if (_hls_fmp4) {
|
||||||
auto hls = _hls;
|
_hls_fmp4->setListener(self);
|
||||||
if (hls) {
|
}
|
||||||
hls->setListener(self);
|
if (_hls) {
|
||||||
|
_hls->setListener(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,15 +158,13 @@ void MultiMediaSourceMuxer::setTrackListener(const std::weak_ptr<Listener> &list
|
||||||
}
|
}
|
||||||
|
|
||||||
int MultiMediaSourceMuxer::totalReaderCount() const {
|
int MultiMediaSourceMuxer::totalReaderCount() const {
|
||||||
auto hls = _hls;
|
|
||||||
return (_rtsp ? _rtsp->readerCount() : 0) +
|
return (_rtsp ? _rtsp->readerCount() : 0) +
|
||||||
(_rtmp ? _rtmp->readerCount() : 0) +
|
(_rtmp ? _rtmp->readerCount() : 0) +
|
||||||
(_ts ? _ts->readerCount() : 0) +
|
(_ts ? _ts->readerCount() : 0) +
|
||||||
#if defined(ENABLE_MP4)
|
|
||||||
(_fmp4 ? _fmp4->readerCount() : 0) +
|
(_fmp4 ? _fmp4->readerCount() : 0) +
|
||||||
#endif
|
|
||||||
(_mp4 ? _option.mp4_as_player : 0) +
|
(_mp4 ? _option.mp4_as_player : 0) +
|
||||||
(hls ? hls->readerCount() : 0) +
|
(_hls ? _hls->readerCount() : 0) +
|
||||||
|
(_hls_fmp4 ? _hls_fmp4->readerCount() : 0) +
|
||||||
(_ring ? _ring->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) {
|
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, [&]() {
|
onceToken token(nullptr, [&]() {
|
||||||
if (_option.mp4_as_player && type == Recorder::type_mp4) {
|
if (_option.mp4_as_player && type == Recorder::type_mp4) {
|
||||||
//开启关闭mp4录制,触发观看人数变化相关事件
|
//开启关闭mp4录制,触发观看人数变化相关事件
|
||||||
|
|
@ -222,19 +228,59 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type
|
||||||
}
|
}
|
||||||
return true;
|
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;
|
default : return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//此函数可能跨线程调用
|
//此函数可能跨线程调用
|
||||||
bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type) {
|
bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type) {
|
||||||
switch (type){
|
switch (type) {
|
||||||
case Recorder::type_hls :
|
case Recorder::type_hls: return !!_hls;
|
||||||
return !!_hls;
|
case Recorder::type_mp4: return !!_mp4;
|
||||||
case Recorder::type_mp4 :
|
case Recorder::type_hls_fmp4: return !!_hls_fmp4;
|
||||||
return !!_mp4;
|
case Recorder::type_fmp4: return !!_fmp4;
|
||||||
default:
|
case Recorder::type_ts: return !!_ts;
|
||||||
return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,7 +312,7 @@ void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const MediaSourceE
|
||||||
strong_self->getOwnerPoller(MediaSource::NullMediaSource())->async([=]() {
|
strong_self->getOwnerPoller(MediaSource::NullMediaSource())->async([=]() {
|
||||||
WarnL << "stream:" << strong_self->shortUrl() << " stop send rtp:" << ssrc << ", reason:" << ex;
|
WarnL << "stream:" << strong_self->shortUrl() << " stop send rtp:" << ssrc << ", reason:" << ex;
|
||||||
strong_self->_rtp_sender.erase(ssrc);
|
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 MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) {
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
if (_rtmp) {
|
if (_rtmp) {
|
||||||
|
|
@ -334,20 +384,17 @@ bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) {
|
||||||
if (_ts) {
|
if (_ts) {
|
||||||
ret = _ts->addTrack(track) ? true : ret;
|
ret = _ts->addTrack(track) ? true : ret;
|
||||||
}
|
}
|
||||||
#if defined(ENABLE_MP4)
|
|
||||||
if (_fmp4) {
|
if (_fmp4) {
|
||||||
ret = _fmp4->addTrack(track) ? true : ret;
|
ret = _fmp4->addTrack(track) ? true : ret;
|
||||||
}
|
}
|
||||||
#endif
|
if (_hls) {
|
||||||
|
ret = _hls->addTrack(track) ? true : ret;
|
||||||
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
|
||||||
auto hls = _hls;
|
|
||||||
if (hls) {
|
|
||||||
ret = hls->addTrack(track) ? true : ret;
|
|
||||||
}
|
}
|
||||||
auto mp4 = _mp4;
|
if (_hls_fmp4) {
|
||||||
if (mp4) {
|
ret = _hls_fmp4->addTrack(track) ? true : ret;
|
||||||
ret = mp4->addTrack(track) ? true : ret;
|
}
|
||||||
|
if (_mp4) {
|
||||||
|
ret = _mp4->addTrack(track) ? true : ret;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
@ -357,16 +404,27 @@ void MultiMediaSourceMuxer::onAllTrackReady() {
|
||||||
setMediaListener(getDelegate());
|
setMediaListener(getDelegate());
|
||||||
|
|
||||||
if (_rtmp) {
|
if (_rtmp) {
|
||||||
_rtmp->onAllTrackReady();
|
_rtmp->addTrackCompleted();
|
||||||
}
|
}
|
||||||
if (_rtsp) {
|
if (_rtsp) {
|
||||||
_rtsp->onAllTrackReady();
|
_rtsp->addTrackCompleted();
|
||||||
|
}
|
||||||
|
if (_ts) {
|
||||||
|
_ts->addTrackCompleted();
|
||||||
|
}
|
||||||
|
if (_mp4) {
|
||||||
|
_mp4->addTrackCompleted();
|
||||||
}
|
}
|
||||||
#if defined(ENABLE_MP4)
|
|
||||||
if (_fmp4) {
|
if (_fmp4) {
|
||||||
_fmp4->onAllTrackReady();
|
_fmp4->addTrackCompleted();
|
||||||
}
|
}
|
||||||
#endif
|
if (_hls) {
|
||||||
|
_hls->addTrackCompleted();
|
||||||
|
}
|
||||||
|
if (_hls_fmp4) {
|
||||||
|
_hls_fmp4->addTrackCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
auto listener = _track_listener.lock();
|
auto listener = _track_listener.lock();
|
||||||
if (listener) {
|
if (listener) {
|
||||||
listener->onAllTrackReady();
|
listener->onAllTrackReady();
|
||||||
|
|
@ -378,6 +436,11 @@ void MultiMediaSourceMuxer::onAllTrackReady() {
|
||||||
createGopCacheIfNeed();
|
createGopCacheIfNeed();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
auto tracks = getTracks(false);
|
||||||
|
if (tracks.size() >= 2) {
|
||||||
|
// 音频时间戳同步于视频,因为音频时间戳被修改后不影响播放
|
||||||
|
_stamp[TrackAudio].syncTo(_stamp[TrackVideo]);
|
||||||
|
}
|
||||||
InfoL << "stream: " << shortUrl() << " , codec info: " << getTrackInfoStr(this);
|
InfoL << "stream: " << shortUrl() << " , codec info: " << getTrackInfoStr(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -409,29 +472,25 @@ void MultiMediaSourceMuxer::resetTracks() {
|
||||||
if (_ts) {
|
if (_ts) {
|
||||||
_ts->resetTracks();
|
_ts->resetTracks();
|
||||||
}
|
}
|
||||||
#if defined(ENABLE_MP4)
|
|
||||||
if (_fmp4) {
|
if (_fmp4) {
|
||||||
_fmp4->resetTracks();
|
_fmp4->resetTracks();
|
||||||
}
|
}
|
||||||
#endif
|
if (_hls_fmp4) {
|
||||||
|
_hls_fmp4->resetTracks();
|
||||||
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
|
||||||
auto hls = _hls;
|
|
||||||
if (hls) {
|
|
||||||
hls->resetTracks();
|
|
||||||
}
|
}
|
||||||
|
if (_hls) {
|
||||||
auto mp4 = _mp4;
|
_hls->resetTracks();
|
||||||
if (mp4) {
|
}
|
||||||
mp4->resetTracks();
|
if (_mp4) {
|
||||||
|
_mp4->resetTracks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
|
bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
|
||||||
auto frame = frame_in;
|
auto frame = frame_in;
|
||||||
if (_option.modify_stamp) {
|
if (_option.modify_stamp != ProtocolOption::kModifyStampOff) {
|
||||||
//开启了时间戳覆盖
|
// 时间戳不采用原始的绝对时间戳
|
||||||
frame = std::make_shared<FrameStamp>(frame, _stamp[frame->getTrackType()],true);
|
frame = std::make_shared<FrameStamp>(frame, _stamp[frame->getTrackType()], _option.modify_stamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
@ -445,23 +504,20 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) {
|
||||||
ret = _ts->inputFrame(frame) ? true : ret;
|
ret = _ts->inputFrame(frame) ? true : ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
//拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题
|
if (_hls) {
|
||||||
//此处使用智能指针拷贝来确保线程安全,比互斥锁性能更优
|
ret = _hls->inputFrame(frame) ? true : ret;
|
||||||
auto hls = _hls;
|
|
||||||
if (hls) {
|
|
||||||
ret = hls->inputFrame(frame) ? true : ret;
|
|
||||||
}
|
|
||||||
auto mp4 = _mp4;
|
|
||||||
if (mp4) {
|
|
||||||
ret = mp4->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) {
|
if (_fmp4) {
|
||||||
ret = _fmp4->inputFrame(frame) ? true : ret;
|
ret = _fmp4->inputFrame(frame) ? true : ret;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
if (_ring) {
|
if (_ring) {
|
||||||
if (frame->getTrackType() == TrackVideo) {
|
if (frame->getTrackType() == TrackVideo) {
|
||||||
// 视频时,遇到第一帧配置帧或关键帧则标记为gop开始处
|
// 视频时,遇到第一帧配置帧或关键帧则标记为gop开始处
|
||||||
|
|
@ -483,15 +539,14 @@ bool MultiMediaSourceMuxer::isEnabled(){
|
||||||
if (!_is_enable || _last_check.elapsedTime() > stream_none_reader_delay_ms) {
|
if (!_is_enable || _last_check.elapsedTime() > stream_none_reader_delay_ms) {
|
||||||
//无人观看时,每次检查是否真的无人观看
|
//无人观看时,每次检查是否真的无人观看
|
||||||
//有人观看时,则延迟一定时间检查一遍是否无人观看了(节省性能)
|
//有人观看时,则延迟一定时间检查一遍是否无人观看了(节省性能)
|
||||||
auto hls = _hls;
|
|
||||||
_is_enable = (_rtmp ? _rtmp->isEnabled() : false) ||
|
_is_enable = (_rtmp ? _rtmp->isEnabled() : false) ||
|
||||||
(_rtsp ? _rtsp->isEnabled() : false) ||
|
(_rtsp ? _rtsp->isEnabled() : false) ||
|
||||||
(_ts ? _ts->isEnabled() : false) ||
|
(_ts ? _ts->isEnabled() : false) ||
|
||||||
#if defined(ENABLE_MP4)
|
|
||||||
(_fmp4 ? _fmp4->isEnabled() : false) ||
|
(_fmp4 ? _fmp4->isEnabled() : false) ||
|
||||||
#endif
|
|
||||||
(_ring ? (bool)_ring->readerCount() : false) ||
|
(_ring ? (bool)_ring->readerCount() : false) ||
|
||||||
(hls ? hls->isEnabled() : false) || _mp4;
|
(_hls ? _hls->isEnabled() : false) ||
|
||||||
|
(_hls_fmp4 ? _hls_fmp4->isEnabled() : false) ||
|
||||||
|
_mp4;
|
||||||
|
|
||||||
if (_is_enable) {
|
if (_is_enable) {
|
||||||
//无人观看时,不刷新计时器,因为无人观看时每次都会检查一遍,所以刷新计数器无意义且浪费cpu
|
//无人观看时,不刷新计时器,因为无人观看时每次都会检查一遍,所以刷新计数器无意义且浪费cpu
|
||||||
|
|
|
||||||
|
|
@ -126,12 +126,13 @@ public:
|
||||||
*/
|
*/
|
||||||
toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
|
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 {
|
std::shared_ptr<MultiMediaSourceMuxer> getMuxer(MediaSource &sender) override;
|
||||||
return _tuple;
|
|
||||||
}
|
const ProtocolOption &getOption() const;
|
||||||
|
const MediaTuple &getMediaTuple() const;
|
||||||
std::string shortUrl() const;
|
std::string shortUrl() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
@ -167,18 +168,14 @@ private:
|
||||||
toolkit::Ticker _last_check;
|
toolkit::Ticker _last_check;
|
||||||
Stamp _stamp[2];
|
Stamp _stamp[2];
|
||||||
std::weak_ptr<Listener> _track_listener;
|
std::weak_ptr<Listener> _track_listener;
|
||||||
#if defined(ENABLE_RTPPROXY)
|
|
||||||
std::unordered_map<std::string, RingType::RingReader::Ptr> _rtp_sender;
|
std::unordered_map<std::string, RingType::RingReader::Ptr> _rtp_sender;
|
||||||
#endif //ENABLE_RTPPROXY
|
|
||||||
|
|
||||||
#if defined(ENABLE_MP4)
|
|
||||||
FMP4MediaSourceMuxer::Ptr _fmp4;
|
FMP4MediaSourceMuxer::Ptr _fmp4;
|
||||||
#endif
|
|
||||||
RtmpMediaSourceMuxer::Ptr _rtmp;
|
RtmpMediaSourceMuxer::Ptr _rtmp;
|
||||||
RtspMediaSourceMuxer::Ptr _rtsp;
|
RtspMediaSourceMuxer::Ptr _rtsp;
|
||||||
TSMediaSourceMuxer::Ptr _ts;
|
TSMediaSourceMuxer::Ptr _ts;
|
||||||
MediaSinkInterface::Ptr _mp4;
|
MediaSinkInterface::Ptr _mp4;
|
||||||
HlsRecorder::Ptr _hls;
|
HlsRecorder::Ptr _hls;
|
||||||
|
HlsFMP4Recorder::Ptr _hls_fmp4;
|
||||||
toolkit::EventPoller::Ptr _poller;
|
toolkit::EventPoller::Ptr _poller;
|
||||||
RingType::Ptr _ring;
|
RingType::Ptr _ring;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,19 +10,22 @@
|
||||||
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include "Parser.h"
|
#include "Parser.h"
|
||||||
|
#include "strCoding.h"
|
||||||
#include "macros.h"
|
#include "macros.h"
|
||||||
#include "Network/sockutil.h"
|
#include "Network/sockutil.h"
|
||||||
|
#include "Common/macros.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit{
|
namespace mediakit {
|
||||||
|
|
||||||
string FindField(const char* buf, const char* start, const char *end ,size_t bufSize) {
|
string findSubString(const char *buf, const char *start, const char *end, size_t buf_size) {
|
||||||
if(bufSize <=0 ){
|
if (buf_size <= 0) {
|
||||||
bufSize = strlen(buf);
|
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;
|
size_t len = 0;
|
||||||
if (start != NULL) {
|
if (start != NULL) {
|
||||||
len = strlen(start);
|
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);
|
return string(msg_start, msg_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::Parse(const char *buf) {
|
void Parser::parse(const char *buf, size_t size) {
|
||||||
//解析
|
clear();
|
||||||
const char *start = buf;
|
auto ptr = buf;
|
||||||
Clear();
|
|
||||||
while (true) {
|
while (true) {
|
||||||
auto line = FindField(start, NULL, "\r\n");
|
auto next_line = strchr(ptr, '\n');
|
||||||
if (line.size() == 0) {
|
auto offset = 1;
|
||||||
break;
|
CHECK(next_line && next_line > ptr);
|
||||||
|
if (*(next_line - 1) == '\r') {
|
||||||
|
next_line -= 1;
|
||||||
|
offset = 2;
|
||||||
}
|
}
|
||||||
if (start == buf) {
|
if (ptr == buf) {
|
||||||
_strMethod = FindField(line.data(), NULL, " ");
|
auto blank = strchr(ptr, ' ');
|
||||||
auto strFullUrl = FindField(line.data(), " ", " ");
|
CHECK(blank > ptr && blank < next_line);
|
||||||
auto args_pos = strFullUrl.find('?');
|
_method = std::string(ptr, blank - ptr);
|
||||||
if (args_pos != string::npos) {
|
auto next_blank = strchr(blank + 1, ' ');
|
||||||
_strUrl = strFullUrl.substr(0, args_pos);
|
CHECK(next_blank && next_blank < next_line);
|
||||||
_params = strFullUrl.substr(args_pos + 1);
|
_url.assign(blank + 1, next_blank);
|
||||||
_mapUrlArgs = parseArgs(_params);
|
auto pos = _url.find('?');
|
||||||
} else {
|
if (pos != string::npos) {
|
||||||
_strUrl = strFullUrl;
|
_params = _url.substr(pos + 1);
|
||||||
|
_url_args = parseArgs(_params);
|
||||||
|
_url = _url.substr(0, pos);
|
||||||
}
|
}
|
||||||
_strTail = FindField(line.data(), (strFullUrl + " ").data(), NULL);
|
_protocol = std::string(next_blank + 1, next_line);
|
||||||
} else {
|
} else {
|
||||||
auto field = FindField(line.data(), NULL, ": ");
|
auto pos = strchr(ptr, ':');
|
||||||
auto value = FindField(line.data(), ": ", NULL);
|
CHECK(pos > ptr && pos < next_line);
|
||||||
if (field.size() != 0) {
|
std::string key { ptr, static_cast<std::size_t>(pos - ptr) };
|
||||||
_mapHeaders.emplace_force(field, value);
|
std::string value;
|
||||||
|
if (pos[1] == ' ') {
|
||||||
|
value.assign(pos + 2, next_line);
|
||||||
|
} else {
|
||||||
|
value.assign(pos + 1, next_line);
|
||||||
}
|
}
|
||||||
|
_headers.emplace_force(trim(std::move(key)), trim(std::move(value)));
|
||||||
}
|
}
|
||||||
start = start + line.size() + 2;
|
ptr = next_line + offset;
|
||||||
if (strncmp(start, "\r\n", 2) == 0) { //协议解析完毕
|
if (strncmp(ptr, "\r\n", 2) == 0) { // 协议解析完毕
|
||||||
_strContent = FindField(start, "\r\n", NULL);
|
_content.assign(ptr + 2, buf + size);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const string &Parser::Method() const {
|
const string &Parser::method() const {
|
||||||
return _strMethod;
|
return _method;
|
||||||
}
|
}
|
||||||
|
|
||||||
const string &Parser::Url() const {
|
const string &Parser::url() const {
|
||||||
return _strUrl;
|
return _url;
|
||||||
}
|
}
|
||||||
|
|
||||||
string Parser::FullUrl() const {
|
const std::string &Parser::status() const {
|
||||||
|
return url();
|
||||||
|
}
|
||||||
|
|
||||||
|
string Parser::fullUrl() const {
|
||||||
if (_params.empty()) {
|
if (_params.empty()) {
|
||||||
return _strUrl;
|
return _url;
|
||||||
}
|
}
|
||||||
return _strUrl + "?" + _params;
|
return _url + "?" + _params;
|
||||||
}
|
}
|
||||||
|
|
||||||
const string &Parser::Tail() const {
|
const string &Parser::protocol() const {
|
||||||
return _strTail;
|
return _protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string &Parser::statusStr() const {
|
||||||
|
return protocol();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string kNull;
|
||||||
|
|
||||||
const string &Parser::operator[](const char *name) const {
|
const string &Parser::operator[](const char *name) const {
|
||||||
auto it = _mapHeaders.find(name);
|
auto it = _headers.find(name);
|
||||||
if (it == _mapHeaders.end()) {
|
if (it == _headers.end()) {
|
||||||
return _strNull;
|
return kNull;
|
||||||
}
|
}
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
const string &Parser::Content() const {
|
const string &Parser::content() const {
|
||||||
return _strContent;
|
return _content;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::Clear() {
|
void Parser::clear() {
|
||||||
_strMethod.clear();
|
_method.clear();
|
||||||
_strUrl.clear();
|
_url.clear();
|
||||||
_params.clear();
|
_params.clear();
|
||||||
_strTail.clear();
|
_protocol.clear();
|
||||||
_strContent.clear();
|
_content.clear();
|
||||||
_mapHeaders.clear();
|
_headers.clear();
|
||||||
_mapUrlArgs.clear();
|
_url_args.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
const string &Parser::Params() const {
|
const string &Parser::params() const {
|
||||||
return _params;
|
return _params;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::setUrl(string url) {
|
void Parser::setUrl(string url) {
|
||||||
this->_strUrl = std::move(url);
|
_url = std::move(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::setContent(string content) {
|
void Parser::setContent(string content) {
|
||||||
this->_strContent = std::move(content);
|
_content = std::move(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
StrCaseMap &Parser::getHeader() const {
|
StrCaseMap &Parser::getHeader() const {
|
||||||
return _mapHeaders;
|
return _headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
StrCaseMap &Parser::getUrlArgs() const {
|
StrCaseMap &Parser::getUrlArgs() const {
|
||||||
return _mapUrlArgs;
|
return _url_args;
|
||||||
}
|
}
|
||||||
|
|
||||||
StrCaseMap Parser::parseArgs(const string &str, const char *pair_delim, const char *key_delim) {
|
StrCaseMap Parser::parseArgs(const string &str, const char *pair_delim, const char *key_delim) {
|
||||||
StrCaseMap ret;
|
StrCaseMap ret;
|
||||||
auto arg_vec = split(str, pair_delim);
|
auto arg_vec = split(str, pair_delim);
|
||||||
for (string &key_val : arg_vec) {
|
for (auto &key_val : arg_vec) {
|
||||||
if (key_val.empty()) {
|
if (key_val.empty()) {
|
||||||
//忽略
|
// 忽略
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto key = trim(FindField(key_val.data(), NULL, key_delim));
|
auto pos = key_val.find(key_delim);
|
||||||
if (!key.empty()) {
|
if (pos != string::npos) {
|
||||||
auto val = trim(FindField(key_val.data(), key_delim, NULL));
|
auto key = trim(std::string(key_val, 0, pos));
|
||||||
ret.emplace_force(key, val);
|
auto val = trim(key_val.substr(pos + strlen(key_delim)));
|
||||||
|
ret.emplace_force(std::move(key), std::move(val));
|
||||||
} else {
|
} else {
|
||||||
trim(key_val);
|
trim(key_val);
|
||||||
if (!key_val.empty()) {
|
if (!key_val.empty()) {
|
||||||
ret.emplace_force(key_val, "");
|
ret.emplace_force(std::move(key_val), "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
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()) {
|
if (base_url.empty()) {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
@ -234,43 +258,45 @@ std::string Parser::merge_url(const string &base_url, const string &path) {
|
||||||
}
|
}
|
||||||
return final_url.str();
|
return final_url.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtspUrl::parse(const string &strUrl) {
|
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;
|
bool is_ssl = strcasecmp(schema.data(), "rtsps") == 0;
|
||||||
//查找"://"与"/"之间的字符串,用于提取用户名密码
|
// 查找"://"与"/"之间的字符串,用于提取用户名密码
|
||||||
auto middle_url = FindField(strUrl.data(), "://", "/");
|
auto middle_url = findSubString(strUrl.data(), "://", "/");
|
||||||
if (middle_url.empty()) {
|
if (middle_url.empty()) {
|
||||||
middle_url = FindField(strUrl.data(), "://", nullptr);
|
middle_url = findSubString(strUrl.data(), "://", nullptr);
|
||||||
}
|
}
|
||||||
auto pos = middle_url.rfind('@');
|
auto pos = middle_url.rfind('@');
|
||||||
if (pos == string::npos) {
|
if (pos == string::npos) {
|
||||||
//并没有用户名密码
|
// 并没有用户名密码
|
||||||
return setup(is_ssl, strUrl, "", "");
|
return setup(is_ssl, strUrl, "", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
//包含用户名密码
|
// 包含用户名密码
|
||||||
auto user_pwd = middle_url.substr(0, pos);
|
auto user_pwd = middle_url.substr(0, pos);
|
||||||
auto suffix = strUrl.substr(schema.size() + 3 + pos + 1);
|
auto suffix = strUrl.substr(schema.size() + 3 + pos + 1);
|
||||||
auto url = StrPrinter << "rtsp://" << suffix << endl;
|
auto url = StrPrinter << "rtsp://" << suffix << endl;
|
||||||
if (user_pwd.find(":") == string::npos) {
|
if (user_pwd.find(":") == string::npos) {
|
||||||
return setup(is_ssl, url, user_pwd, "");
|
return setup(is_ssl, url, user_pwd, "");
|
||||||
}
|
}
|
||||||
auto user = FindField(user_pwd.data(), nullptr, ":");
|
auto user = findSubString(user_pwd.data(), nullptr, ":");
|
||||||
auto pwd = FindField(user_pwd.data(), ":", nullptr);
|
auto pwd = findSubString(user_pwd.data(), ":", nullptr);
|
||||||
return setup(is_ssl, url, user, pwd);
|
return setup(is_ssl, url, user, pwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtspUrl::setup(bool is_ssl, const string &url, const string &user, const string &passwd) {
|
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()) {
|
if (ip.empty()) {
|
||||||
ip = split(FindField(url.data(), "://", NULL), "?")[0];
|
ip = split(findSubString(url.data(), "://", NULL), "?")[0];
|
||||||
}
|
}
|
||||||
uint16_t port = is_ssl ? 322 : 554;
|
uint16_t port = is_ssl ? 322 : 554;
|
||||||
splitUrl(ip, ip, port);
|
splitUrl(ip, ip, port);
|
||||||
|
|
||||||
|
|
||||||
_url = std::move(url);
|
_url = std::move(url);
|
||||||
_user = std::move(user);
|
_user = strCoding::UrlDecode(std::move(user));
|
||||||
_passwd = std::move(passwd);
|
_passwd = strCoding::UrlDecode(std::move(passwd));
|
||||||
_host = std::move(ip);
|
_host = std::move(ip);
|
||||||
_port = port;
|
_port = port;
|
||||||
_is_ssl = is_ssl;
|
_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");
|
CHECK(!url.empty(), "empty url");
|
||||||
auto pos = url.rfind(':');
|
auto pos = url.rfind(':');
|
||||||
if (pos == string::npos || url.back() == ']') {
|
if (pos == string::npos || url.back() == ']') {
|
||||||
//没有冒号,未指定端口;或者是纯粹的ipv6地址
|
// 没有冒号,未指定端口;或者是纯粹的ipv6地址
|
||||||
host = url;
|
host = url;
|
||||||
checkHost(host);
|
checkHost(host);
|
||||||
return;
|
return;
|
||||||
|
|
@ -299,7 +325,6 @@ void splitUrl(const std::string &url, std::string &host, uint16_t &port) {
|
||||||
host = url.substr(0, pos);
|
host = url.substr(0, pos);
|
||||||
checkHost(host);
|
checkHost(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
//测试代码
|
//测试代码
|
||||||
static onceToken token([](){
|
static onceToken token([](){
|
||||||
|
|
@ -312,4 +337,4 @@ static onceToken token([](){
|
||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}//namespace mediakit
|
} // namespace mediakit
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,13 @@
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
//从字符串中提取子字符串
|
// 从字符串中提取子字符串
|
||||||
std::string FindField(const char *buf, const char *start, const char *end, size_t bufSize = 0);
|
std::string findSubString(const char *buf, const char *start, const char *end, size_t buf_size = 0);
|
||||||
//把url解析为主机地址和端口号,兼容ipv4/ipv6/dns
|
// 把url解析为主机地址和端口号,兼容ipv4/ipv6/dns
|
||||||
void splitUrl(const std::string &url, std::string &host, uint16_t& port);
|
void splitUrl(const std::string &url, std::string &host, uint16_t &port);
|
||||||
|
|
||||||
struct StrCaseCompare {
|
struct StrCaseCompare {
|
||||||
bool operator()(const std::string &__x, const std::string &__y) const {
|
bool operator()(const std::string &__x, const std::string &__y) const { return strcasecmp(__x.data(), __y.data()) < 0; }
|
||||||
return strcasecmp(__x.data(), __y.data()) < 0;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class StrCaseMap : public std::multimap<std::string, std::string, StrCaseCompare> {
|
class StrCaseMap : public std::multimap<std::string, std::string, StrCaseCompare> {
|
||||||
|
|
@ -42,84 +40,87 @@ public:
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename V>
|
template <typename K, typename V>
|
||||||
void emplace(const std::string &k, V &&v) {
|
void emplace(K &&k, V &&v) {
|
||||||
auto it = find(k);
|
auto it = find(k);
|
||||||
if (it != end()) {
|
if (it != end()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Super::emplace(k, std::forward<V>(v));
|
Super::emplace(std::forward<K>(k), std::forward<V>(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename V>
|
template <typename K, typename V>
|
||||||
void emplace_force(const std::string k, V &&v) {
|
void emplace_force(K &&k, V &&v) {
|
||||||
Super::emplace(k, std::forward<V>(v));
|
Super::emplace(std::forward<K>(k), std::forward<V>(v));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//rtsp/http/sip解析类
|
// rtsp/http/sip解析类
|
||||||
class Parser {
|
class Parser {
|
||||||
public:
|
public:
|
||||||
Parser() = default;
|
Parser() = default;
|
||||||
~Parser() = default;
|
~Parser() = default;
|
||||||
|
|
||||||
//解析信令
|
// 解析http/rtsp/sip请求,需要确保buf以\0结尾
|
||||||
void Parse(const char *buf);
|
void parse(const char *buf, size_t size);
|
||||||
|
|
||||||
//获取命令字
|
// 获取命令字,如GET/POST
|
||||||
const std::string &Method() const;
|
const std::string &method() const;
|
||||||
|
|
||||||
//获取中间url,不包含?后面的参数
|
// 请求时,获取中间url,不包含?后面的参数
|
||||||
const std::string &Url() const;
|
const std::string &url() const;
|
||||||
|
// 回复时,获取状态码,如200/404
|
||||||
|
const std::string &status() const;
|
||||||
|
|
||||||
//获取中间url,包含?后面的参数
|
// 获取中间url,包含?后面的参数
|
||||||
std::string FullUrl() const;
|
std::string fullUrl() const;
|
||||||
|
|
||||||
//获取命令协议名
|
// 请求时,获取协议名,如HTTP/1.1
|
||||||
const std::string &Tail() const;
|
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;
|
const std::string &operator[](const char *name) const;
|
||||||
|
|
||||||
//获取http body或sdp
|
// 获取http body或sdp
|
||||||
const std::string &Content() const;
|
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);
|
void setUrl(std::string url);
|
||||||
|
|
||||||
//重新设置content
|
// 重新设置content
|
||||||
void setContent(std::string content);
|
void setContent(std::string content);
|
||||||
|
|
||||||
//获取header列表
|
// 获取header列表
|
||||||
StrCaseMap &getHeader() const;
|
StrCaseMap &getHeader() const;
|
||||||
|
|
||||||
//获取url参数列表
|
// 获取url参数列表
|
||||||
StrCaseMap &getUrlArgs() const;
|
StrCaseMap &getUrlArgs() const;
|
||||||
|
|
||||||
//解析?后面的参数
|
// 解析?后面的参数
|
||||||
static StrCaseMap parseArgs(const std::string &str, const char *pair_delim = "&", const char *key_delim = "=");
|
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:
|
private:
|
||||||
std::string _strMethod;
|
std::string _method;
|
||||||
std::string _strUrl;
|
std::string _url;
|
||||||
std::string _strTail;
|
std::string _protocol;
|
||||||
std::string _strContent;
|
std::string _content;
|
||||||
std::string _strNull;
|
|
||||||
std::string _params;
|
std::string _params;
|
||||||
mutable StrCaseMap _mapHeaders;
|
mutable StrCaseMap _headers;
|
||||||
mutable StrCaseMap _mapUrlArgs;
|
mutable StrCaseMap _url_args;
|
||||||
};
|
};
|
||||||
|
|
||||||
//解析rtsp url的工具类
|
// 解析rtsp url的工具类
|
||||||
class RtspUrl{
|
class RtspUrl {
|
||||||
public:
|
public:
|
||||||
bool _is_ssl;
|
bool _is_ssl;
|
||||||
uint16_t _port;
|
uint16_t _port;
|
||||||
|
|
@ -134,9 +135,9 @@ public:
|
||||||
void parse(const std::string &url);
|
void parse(const std::string &url);
|
||||||
|
|
||||||
private:
|
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"
|
#include "Stamp.h"
|
||||||
|
|
||||||
//时间戳最大允许跳变3秒,主要是防止网络抖动导致的跳变
|
// 时间戳最大允许跳变3秒,主要是防止网络抖动导致的跳变
|
||||||
#define MAX_DELTA_STAMP (3 * 1000)
|
#define MAX_DELTA_STAMP (3 * 1000)
|
||||||
#define STAMP_LOOP_DELTA (60 * 1000)
|
#define STAMP_LOOP_DELTA (60 * 1000)
|
||||||
#define MAX_CTS 500
|
#define MAX_CTS 500
|
||||||
|
|
@ -25,51 +25,52 @@ int64_t DeltaStamp::relativeStamp(int64_t stamp) {
|
||||||
return _relative_stamp;
|
return _relative_stamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t DeltaStamp::relativeStamp(){
|
int64_t DeltaStamp::relativeStamp() {
|
||||||
return _relative_stamp;
|
return _relative_stamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t DeltaStamp::deltaStamp(int64_t stamp) {
|
int64_t DeltaStamp::deltaStamp(int64_t stamp) {
|
||||||
if(!_last_stamp){
|
if (!_last_stamp) {
|
||||||
//第一次计算时间戳增量,时间戳增量为0
|
// 第一次计算时间戳增量,时间戳增量为0
|
||||||
if(stamp){
|
if (stamp) {
|
||||||
_last_stamp = stamp;
|
_last_stamp = stamp;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t ret = stamp - _last_stamp;
|
int64_t ret = stamp - _last_stamp;
|
||||||
if(ret >= 0){
|
if (ret >= 0) {
|
||||||
//时间戳增量为正,返回之
|
// 时间戳增量为正,返回之
|
||||||
_last_stamp = stamp;
|
_last_stamp = stamp;
|
||||||
//在直播情况下,时间戳增量不得大于MAX_DELTA_STAMP
|
// 在直播情况下,时间戳增量不得大于MAX_DELTA_STAMP,否则强制相对时间戳加1
|
||||||
return ret < MAX_DELTA_STAMP ? ret : 0;
|
return ret < MAX_DELTA_STAMP ? ret : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
//时间戳增量为负,说明时间戳回环了或回退了
|
// 时间戳增量为负,说明时间戳回环了或回退了
|
||||||
_last_stamp = stamp;
|
_last_stamp = stamp;
|
||||||
//如果时间戳回退不多,那么返回负值
|
|
||||||
return -ret < MAX_CTS ? ret : 0;
|
// 如果时间戳回退不多,那么返回负值,否则返回加1
|
||||||
|
return -ret < MAX_DELTA_STAMP ? ret : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stamp::setPlayBack(bool playback) {
|
void Stamp::setPlayBack(bool playback) {
|
||||||
_playback = playback;
|
_playback = playback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Stamp::syncTo(Stamp &other){
|
void Stamp::syncTo(Stamp &other) {
|
||||||
_sync_master = &other;
|
_sync_master = &other;
|
||||||
}
|
}
|
||||||
|
|
||||||
//限制dts回退
|
// 限制dts回退
|
||||||
void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) {
|
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);
|
revise_l(dts, pts, dts_out, pts_out, modifyStamp);
|
||||||
if (_playback) {
|
if (_playback) {
|
||||||
//回放允许时间戳回退
|
// 回放允许时间戳回退
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dts_out < _last_dts_out) {
|
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;
|
dts_out = _last_dts_out;
|
||||||
pts_out = _last_pts_out;
|
pts_out = _last_pts_out;
|
||||||
return;
|
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;
|
_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);
|
revise_l2(dts, pts, dts_out, pts_out, modifyStamp);
|
||||||
if (!_sync_master || modifyStamp || _playback) {
|
if (!_sync_master || modifyStamp || _playback) {
|
||||||
//自动生成时间戳或回放或同步完毕
|
// 自动生成时间戳或回放或同步完毕
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_sync_master && _sync_master->_last_dts_in) {
|
if (_sync_master && _sync_master->_last_dts_in) {
|
||||||
//音视频dts当前时间差
|
// 音视频dts当前时间差
|
||||||
int64_t dts_diff = _last_dts_in - _sync_master->_last_dts_in;
|
int64_t dts_diff = _last_dts_in - _sync_master->_last_dts_in;
|
||||||
if (ABS(dts_diff) < 5000) {
|
if (ABS(dts_diff) < 5000) {
|
||||||
//如果绝对时间戳小于5秒,那么说明他们的起始时间戳是一致的,那么强制同步
|
// 如果绝对时间戳小于5秒,那么说明他们的起始时间戳是一致的,那么强制同步
|
||||||
_relative_stamp = _sync_master->_relative_stamp + dts_diff;
|
_relative_stamp = _sync_master->_relative_stamp + dts_diff;
|
||||||
}
|
}
|
||||||
//下次不用再强制同步
|
// 下次不用再强制同步
|
||||||
_sync_master = nullptr;
|
_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) {
|
if (!pts) {
|
||||||
//没有播放时间戳,使其赋值为解码时间戳
|
// 没有播放时间戳,使其赋值为解码时间戳
|
||||||
pts = dts;
|
pts = dts;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_playback) {
|
if (_playback) {
|
||||||
//这是点播
|
// 这是点播
|
||||||
dts_out = dts;
|
dts_out = dts;
|
||||||
pts_out = pts;
|
pts_out = pts;
|
||||||
_relative_stamp = dts_out;
|
_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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//pts和dts的差值
|
// pts和dts的差值
|
||||||
int64_t pts_dts_diff = pts - dts;
|
int64_t pts_dts_diff = pts - dts;
|
||||||
|
|
||||||
if (_last_dts_in != dts) {
|
if (_last_dts_in != dts) {
|
||||||
//时间戳发生变更
|
// 时间戳发生变更
|
||||||
if (modifyStamp) {
|
if (modifyStamp) {
|
||||||
//内部自己生产时间戳
|
// 内部自己生产时间戳
|
||||||
_relative_stamp = _ticker.elapsedTime();
|
_relative_stamp = _ticker.elapsedTime();
|
||||||
} else {
|
} else {
|
||||||
_relative_stamp += deltaStamp(dts);
|
_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) {
|
if (ABS(pts_dts_diff) > MAX_CTS) {
|
||||||
//如果差值太大,则认为由于回环导致时间戳错乱了
|
// 如果差值太大,则认为由于回环导致时间戳错乱了
|
||||||
pts_dts_diff = 0;
|
pts_dts_diff = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,156 +147,157 @@ int64_t Stamp::getRelativeStamp() const {
|
||||||
return _relative_stamp;
|
return _relative_stamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DtsGenerator::getDts(uint64_t pts, uint64_t &dts){
|
bool DtsGenerator::getDts(uint64_t pts, uint64_t &dts) {
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
if (pts == _last_pts) {
|
if (pts == _last_pts) {
|
||||||
//pts未变,说明dts也不会变,返回上次dts
|
// pts未变,说明dts也不会变,返回上次dts
|
||||||
if (_last_dts) {
|
if (_last_dts) {
|
||||||
dts = _last_dts;
|
dts = _last_dts;
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//pts变了,尝试计算dts
|
// pts变了,尝试计算dts
|
||||||
ret = getDts_l(pts, dts);
|
ret = getDts_l(pts, dts);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
//获取到了dts,保存本次结果
|
// 获取到了dts,保存本次结果
|
||||||
_last_dts = dts;
|
_last_dts = dts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
//pts排序列队长度还不知道,也就是不知道有没有B帧,
|
// pts排序列队长度还不知道,也就是不知道有没有B帧,
|
||||||
//那么先强制dts == pts,这样可能导致有B帧的情况下,起始画面有几帧回退
|
// 那么先强制dts == pts,这样可能导致有B帧的情况下,起始画面有几帧回退
|
||||||
dts = pts;
|
dts = pts;
|
||||||
}
|
}
|
||||||
|
|
||||||
//记录上次pts
|
// 记录上次pts
|
||||||
_last_pts = pts;
|
_last_pts = pts;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
//该算法核心思想是对pts进行排序,排序好的pts就是dts。
|
// 该算法核心思想是对pts进行排序,排序好的pts就是dts。
|
||||||
//排序有一定的滞后性,那么需要加上排序导致的时间戳偏移量
|
// 排序有一定的滞后性,那么需要加上排序导致的时间戳偏移量
|
||||||
bool DtsGenerator::getDts_l(uint64_t pts, uint64_t &dts){
|
bool DtsGenerator::getDts_l(uint64_t pts, uint64_t &dts) {
|
||||||
if(_sorter_max_size == 1){
|
if (_sorter_max_size == 1) {
|
||||||
//没有B帧,dts就等于pts
|
// 没有B帧,dts就等于pts
|
||||||
dts = pts;
|
dts = pts;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!_sorter_max_size){
|
if (!_sorter_max_size) {
|
||||||
//尚未计算出pts排序列队长度(也就是P帧间B帧个数)
|
// 尚未计算出pts排序列队长度(也就是P帧间B帧个数)
|
||||||
if(pts > _last_max_pts){
|
if (pts > _last_max_pts) {
|
||||||
//pts时间戳增加了,那么说明这帧画面不是B帧(说明是P帧或关键帧)
|
// pts时间戳增加了,那么说明这帧画面不是B帧(说明是P帧或关键帧)
|
||||||
if(_frames_since_last_max_pts && _count_sorter_max_size++ > 0){
|
if (_frames_since_last_max_pts && _count_sorter_max_size++ > 0) {
|
||||||
//已经出现多次非B帧的情况,那么我们就能知道P帧间B帧的个数
|
// 已经出现多次非B帧的情况,那么我们就能知道P帧间B帧的个数
|
||||||
_sorter_max_size = _frames_since_last_max_pts;
|
_sorter_max_size = _frames_since_last_max_pts;
|
||||||
//我们记录P帧间时间间隔(也就是多个B帧时间戳增量累计)
|
// 我们记录P帧间时间间隔(也就是多个B帧时间戳增量累计)
|
||||||
_dts_pts_offset = (pts - _last_max_pts);
|
_dts_pts_offset = (pts - _last_max_pts);
|
||||||
//除以2,防止dts大于pts
|
// 除以2,防止dts大于pts
|
||||||
_dts_pts_offset /= 2;
|
_dts_pts_offset /= 2;
|
||||||
}
|
}
|
||||||
//遇到P帧或关键帧,连续B帧计数清零
|
// 遇到P帧或关键帧,连续B帧计数清零
|
||||||
_frames_since_last_max_pts = 0;
|
_frames_since_last_max_pts = 0;
|
||||||
//记录上次非B帧的pts时间戳(同时也是dts),用于统计连续B帧时间戳增量
|
// 记录上次非B帧的pts时间戳(同时也是dts),用于统计连续B帧时间戳增量
|
||||||
_last_max_pts = pts;
|
_last_max_pts = pts;
|
||||||
}
|
}
|
||||||
//如果pts时间戳小于上一个P帧,那么断定这个是B帧,我们记录B帧连续个数
|
// 如果pts时间戳小于上一个P帧,那么断定这个是B帧,我们记录B帧连续个数
|
||||||
++_frames_since_last_max_pts;
|
++_frames_since_last_max_pts;
|
||||||
}
|
}
|
||||||
|
|
||||||
//pts放入排序缓存列队,缓存列队最大等于连续B帧个数
|
// pts放入排序缓存列队,缓存列队最大等于连续B帧个数
|
||||||
_pts_sorter.emplace(pts);
|
_pts_sorter.emplace(pts);
|
||||||
|
|
||||||
if(_sorter_max_size && _pts_sorter.size() > _sorter_max_size){
|
if (_sorter_max_size && _pts_sorter.size() > _sorter_max_size) {
|
||||||
//如果启用了pts排序(意味着存在B帧),并且pts排序缓存列队长度大于连续B帧个数,
|
// 如果启用了pts排序(意味着存在B帧),并且pts排序缓存列队长度大于连续B帧个数,
|
||||||
//意味着后续的pts都会比最早的pts大,那么说明可以取出最早的pts了,这个pts将当做该帧的dts基准
|
// 意味着后续的pts都会比最早的pts大,那么说明可以取出最早的pts了,这个pts将当做该帧的dts基准
|
||||||
auto it = _pts_sorter.begin();
|
auto it = _pts_sorter.begin();
|
||||||
|
|
||||||
//由于该pts是前面偏移了个_sorter_max_size帧的pts(也就是那帧画面的dts),
|
// 由于该pts是前面偏移了个_sorter_max_size帧的pts(也就是那帧画面的dts),
|
||||||
//那么我们加上时间戳偏移量,基本等于该帧的dts
|
// 那么我们加上时间戳偏移量,基本等于该帧的dts
|
||||||
dts = *it + _dts_pts_offset;
|
dts = *it + _dts_pts_offset;
|
||||||
if(dts > pts){
|
if (dts > pts) {
|
||||||
//dts不能大于pts(基本不可能到达这个逻辑)
|
// dts不能大于pts(基本不可能到达这个逻辑)
|
||||||
dts = pts;
|
dts = pts;
|
||||||
}
|
}
|
||||||
|
|
||||||
//pts排序缓存出列
|
// pts排序缓存出列
|
||||||
_pts_sorter.erase(it);
|
_pts_sorter.erase(it);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//排序缓存尚未满
|
// 排序缓存尚未满
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NtpStamp::setNtpStamp(uint32_t rtp_stamp, uint64_t ntp_stamp_ms) {
|
void NtpStamp::setNtpStamp(uint32_t rtp_stamp, uint64_t ntp_stamp_ms) {
|
||||||
update(rtp_stamp, ntp_stamp_ms);
|
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;
|
||||||
void NtpStamp::update(uint32_t rtp_stamp, uint64_t ntp_stamp_ms) {
|
|
||||||
if (ntp_stamp_ms == 0) {
|
|
||||||
//实测发现有些rtsp服务器发送的rtp时间戳和ntp时间戳一直为0
|
|
||||||
return;
|
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_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) {
|
uint64_t NtpStamp::getNtpStamp(uint32_t rtp_stamp, uint32_t sample_rate) {
|
||||||
if (rtp_stamp == _last_rtp_stamp) {
|
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) {
|
uint64_t NtpStamp::getNtpStampUS(uint32_t rtp_stamp, uint32_t sample_rate) {
|
||||||
if (!_last_ntp_stamp_ms) {
|
if (!_last_ntp_stamp_us) {
|
||||||
//尚未收到sender report rtcp包,那么赋值为本地系统时间戳吧
|
// 尚未收到sender report rtcp包,那么赋值为本地系统时间戳吧
|
||||||
update(rtp_stamp, getCurrentMillisecond(true));
|
update(rtp_stamp, getCurrentMicrosecond(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
//rtp时间戳正增长
|
// rtp时间戳正增长
|
||||||
if (rtp_stamp >= _last_rtp_stamp) {
|
if (rtp_stamp >= _last_rtp_stamp) {
|
||||||
auto diff = static_cast<int>((rtp_stamp - _last_rtp_stamp) / (sample_rate / 1000.0f));
|
auto diff_us = static_cast<int64_t>((rtp_stamp - _last_rtp_stamp) / (sample_rate / 1000000.0f));
|
||||||
if (diff < MAX_DELTA_STAMP) {
|
if (diff_us < MAX_DELTA_STAMP * 1000) {
|
||||||
//时间戳正常增长
|
// 时间戳正常增长
|
||||||
update(rtp_stamp, _last_ntp_stamp_ms + diff);
|
update(rtp_stamp, _last_ntp_stamp_us + diff_us);
|
||||||
return _last_ntp_stamp_ms;
|
return _last_ntp_stamp_us;
|
||||||
}
|
}
|
||||||
|
|
||||||
//时间戳大幅跳跃
|
// 时间戳大幅跳跃
|
||||||
uint64_t loop_delta = STAMP_LOOP_DELTA * sample_rate / 1000;
|
uint64_t loop_delta_hz = STAMP_LOOP_DELTA * sample_rate / 1000;
|
||||||
if (_last_rtp_stamp < loop_delta && rtp_stamp > UINT32_MAX - loop_delta) {
|
if (_last_rtp_stamp < loop_delta_hz && rtp_stamp > UINT32_MAX - loop_delta_hz) {
|
||||||
//应该是rtp时间戳溢出+乱序
|
// 应该是rtp时间戳溢出+乱序
|
||||||
uint64_t max_rtp_ms = uint64_t(UINT32_MAX) * 1000 / sample_rate;
|
uint64_t max_rtp_us = uint64_t(UINT32_MAX) * 1000000 / sample_rate;
|
||||||
return _last_ntp_stamp_ms + diff - max_rtp_ms;
|
return _last_ntp_stamp_us + diff_us - max_rtp_us;
|
||||||
}
|
}
|
||||||
//不明原因的时间戳大幅跳跃,直接返回上次值
|
// 不明原因的时间戳大幅跳跃,直接返回上次值
|
||||||
WarnL << "rtp stamp abnormal increased:" << _last_rtp_stamp << " -> " << rtp_stamp;
|
WarnL << "rtp stamp abnormal increased:" << _last_rtp_stamp << " -> " << rtp_stamp;
|
||||||
update(rtp_stamp, _last_ntp_stamp_ms);
|
update(rtp_stamp, _last_ntp_stamp_us);
|
||||||
return _last_ntp_stamp_ms;
|
return _last_ntp_stamp_us;
|
||||||
}
|
}
|
||||||
|
|
||||||
//rtp时间戳负增长
|
// rtp时间戳负增长
|
||||||
auto diff = static_cast<int>((_last_rtp_stamp - rtp_stamp) / (sample_rate / 1000.0f));
|
auto diff_us = static_cast<int64_t>((_last_rtp_stamp - rtp_stamp) / (sample_rate / 1000000.0f));
|
||||||
if (diff < MAX_DELTA_STAMP) {
|
if (diff_us < MAX_DELTA_STAMP * 1000) {
|
||||||
//正常范围的时间戳回退,说明收到rtp乱序了
|
// 正常范围的时间戳回退,说明收到rtp乱序了
|
||||||
return _last_ntp_stamp_ms - diff;
|
return _last_ntp_stamp_us - diff_us;
|
||||||
}
|
}
|
||||||
|
|
||||||
//时间戳大幅度回退
|
// 时间戳大幅度回退
|
||||||
uint64_t loop_delta = STAMP_LOOP_DELTA * sample_rate / 1000;
|
uint64_t loop_delta_hz = STAMP_LOOP_DELTA * sample_rate / 1000;
|
||||||
if (rtp_stamp < loop_delta && _last_rtp_stamp > UINT32_MAX - loop_delta) {
|
if (rtp_stamp < loop_delta_hz && _last_rtp_stamp > UINT32_MAX - loop_delta_hz) {
|
||||||
//确定是时间戳溢出
|
// 确定是时间戳溢出
|
||||||
uint64_t max_rtp_ms = uint64_t(UINT32_MAX) * 1000 / sample_rate;
|
uint64_t max_rtp_us = uint64_t(UINT32_MAX) * 1000000 / sample_rate;
|
||||||
update(rtp_stamp, _last_ntp_stamp_ms + (max_rtp_ms - diff));
|
update(rtp_stamp, _last_ntp_stamp_us + (max_rtp_us - diff_us));
|
||||||
return _last_ntp_stamp_ms;
|
return _last_ntp_stamp_us;
|
||||||
}
|
}
|
||||||
//不明原因的时间戳回退,直接返回上次值
|
// 不明原因的时间戳回退,直接返回上次值
|
||||||
WarnL << "rtp stamp abnormal reduced:" << _last_rtp_stamp << " -> " << rtp_stamp;
|
WarnL << "rtp stamp abnormal reduced:" << _last_rtp_stamp << " -> " << rtp_stamp;
|
||||||
update(rtp_stamp, _last_ntp_stamp_ms);
|
update(rtp_stamp, _last_ntp_stamp_us);
|
||||||
return _last_ntp_stamp_ms;
|
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);
|
uint64_t getNtpStamp(uint32_t rtp_stamp, uint32_t sample_rate);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void update(uint32_t rtp_stamp, uint64_t ntp_stamp_ms);
|
void update(uint32_t rtp_stamp, uint64_t ntp_stamp_us);
|
||||||
uint64_t getNtpStamp_l(uint32_t rtp_stamp, uint32_t sample_rate);
|
uint64_t getNtpStampUS(uint32_t rtp_stamp, uint32_t sample_rate);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t _last_rtp_stamp = 0;
|
uint32_t _last_rtp_stamp = 0;
|
||||||
uint64_t _last_ntp_stamp_ms = 0;
|
uint64_t _last_ntp_stamp_us = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Common/config.h"
|
#include "Common/config.h"
|
||||||
|
#include "MediaSource.h"
|
||||||
#include "Util/NoticeCenter.h"
|
#include "Util/NoticeCenter.h"
|
||||||
#include "Util/logger.h"
|
#include "Util/logger.h"
|
||||||
#include "Util/onceToken.h"
|
#include "Util/onceToken.h"
|
||||||
|
|
@ -30,7 +31,7 @@ bool loadIniConfig(const char *ini_path) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
mINI::Instance().parseFile(ini);
|
mINI::Instance().parseFile(ini);
|
||||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
|
NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig);
|
||||||
return true;
|
return true;
|
||||||
} catch (std::exception &) {
|
} catch (std::exception &) {
|
||||||
InfoL << "dump ini file to:" << ini;
|
InfoL << "dump ini file to:" << ini;
|
||||||
|
|
@ -98,9 +99,11 @@ namespace Protocol {
|
||||||
const string kModifyStamp = PROTOCOL_FIELD "modify_stamp";
|
const string kModifyStamp = PROTOCOL_FIELD "modify_stamp";
|
||||||
const string kEnableAudio = PROTOCOL_FIELD "enable_audio";
|
const string kEnableAudio = PROTOCOL_FIELD "enable_audio";
|
||||||
const string kAddMuteAudio = PROTOCOL_FIELD "add_mute_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 kContinuePushMS = PROTOCOL_FIELD "continue_push_ms";
|
||||||
|
|
||||||
const string kEnableHls = PROTOCOL_FIELD "enable_hls";
|
const string kEnableHls = PROTOCOL_FIELD "enable_hls";
|
||||||
|
const string kEnableHlsFmp4 = PROTOCOL_FIELD "enable_hls_fmp4";
|
||||||
const string kEnableMP4 = PROTOCOL_FIELD "enable_mp4";
|
const string kEnableMP4 = PROTOCOL_FIELD "enable_mp4";
|
||||||
const string kEnableRtsp = PROTOCOL_FIELD "enable_rtsp";
|
const string kEnableRtsp = PROTOCOL_FIELD "enable_rtsp";
|
||||||
const string kEnableRtmp = PROTOCOL_FIELD "enable_rtmp";
|
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";
|
const string kFMP4Demand = PROTOCOL_FIELD "fmp4_demand";
|
||||||
|
|
||||||
static onceToken token([]() {
|
static onceToken token([]() {
|
||||||
mINI::Instance()[kModifyStamp] = 0;
|
mINI::Instance()[kModifyStamp] = (int)ProtocolOption::kModifyStampRelative;
|
||||||
mINI::Instance()[kEnableAudio] = 1;
|
mINI::Instance()[kEnableAudio] = 1;
|
||||||
mINI::Instance()[kAddMuteAudio] = 1;
|
mINI::Instance()[kAddMuteAudio] = 1;
|
||||||
mINI::Instance()[kContinuePushMS] = 15000;
|
mINI::Instance()[kContinuePushMS] = 15000;
|
||||||
|
mINI::Instance()[kAutoClose] = 0;
|
||||||
|
|
||||||
mINI::Instance()[kEnableHls] = 1;
|
mINI::Instance()[kEnableHls] = 1;
|
||||||
|
mINI::Instance()[kEnableHlsFmp4] = 0;
|
||||||
mINI::Instance()[kEnableMP4] = 0;
|
mINI::Instance()[kEnableMP4] = 0;
|
||||||
mINI::Instance()[kEnableRtsp] = 1;
|
mINI::Instance()[kEnableRtsp] = 1;
|
||||||
mINI::Instance()[kEnableRtmp] = 1;
|
mINI::Instance()[kEnableRtmp] = 1;
|
||||||
|
|
@ -160,6 +165,7 @@ const string kDirMenu = HTTP_FIELD "dirMenu";
|
||||||
const string kForbidCacheSuffix = HTTP_FIELD "forbidCacheSuffix";
|
const string kForbidCacheSuffix = HTTP_FIELD "forbidCacheSuffix";
|
||||||
const string kForwardedIpHeader = HTTP_FIELD "forwarded_ip_header";
|
const string kForwardedIpHeader = HTTP_FIELD "forwarded_ip_header";
|
||||||
const string kAllowCrossDomains = HTTP_FIELD "allow_cross_domains";
|
const string kAllowCrossDomains = HTTP_FIELD "allow_cross_domains";
|
||||||
|
const string kAllowIPRange = HTTP_FIELD "allow_ip_range";
|
||||||
|
|
||||||
static onceToken token([]() {
|
static onceToken token([]() {
|
||||||
mINI::Instance()[kSendBufSize] = 64 * 1024;
|
mINI::Instance()[kSendBufSize] = 64 * 1024;
|
||||||
|
|
@ -188,6 +194,7 @@ static onceToken token([]() {
|
||||||
mINI::Instance()[kForbidCacheSuffix] = "";
|
mINI::Instance()[kForbidCacheSuffix] = "";
|
||||||
mINI::Instance()[kForwardedIpHeader] = "";
|
mINI::Instance()[kForwardedIpHeader] = "";
|
||||||
mINI::Instance()[kAllowCrossDomains] = 1;
|
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
|
} // namespace Http
|
||||||
|
|
@ -208,6 +215,7 @@ const string kHandshakeSecond = RTSP_FIELD "handshakeSecond";
|
||||||
const string kKeepAliveSecond = RTSP_FIELD "keepAliveSecond";
|
const string kKeepAliveSecond = RTSP_FIELD "keepAliveSecond";
|
||||||
const string kDirectProxy = RTSP_FIELD "directProxy";
|
const string kDirectProxy = RTSP_FIELD "directProxy";
|
||||||
const string kLowLatency = RTSP_FIELD"lowLatency";
|
const string kLowLatency = RTSP_FIELD"lowLatency";
|
||||||
|
const string kRtpTransportType = RTSP_FIELD"rtpTransportType";
|
||||||
|
|
||||||
static onceToken token([]() {
|
static onceToken token([]() {
|
||||||
// 默认Md5方式认证
|
// 默认Md5方式认证
|
||||||
|
|
@ -216,18 +224,17 @@ static onceToken token([]() {
|
||||||
mINI::Instance()[kKeepAliveSecond] = 15;
|
mINI::Instance()[kKeepAliveSecond] = 15;
|
||||||
mINI::Instance()[kDirectProxy] = 1;
|
mINI::Instance()[kDirectProxy] = 1;
|
||||||
mINI::Instance()[kLowLatency] = 0;
|
mINI::Instance()[kLowLatency] = 0;
|
||||||
|
mINI::Instance()[kRtpTransportType] = -1;
|
||||||
});
|
});
|
||||||
} // namespace Rtsp
|
} // namespace Rtsp
|
||||||
|
|
||||||
////////////RTMP服务器配置///////////
|
////////////RTMP服务器配置///////////
|
||||||
namespace Rtmp {
|
namespace Rtmp {
|
||||||
#define RTMP_FIELD "rtmp."
|
#define RTMP_FIELD "rtmp."
|
||||||
const string kModifyStamp = RTMP_FIELD "modifyStamp";
|
|
||||||
const string kHandshakeSecond = RTMP_FIELD "handshakeSecond";
|
const string kHandshakeSecond = RTMP_FIELD "handshakeSecond";
|
||||||
const string kKeepAliveSecond = RTMP_FIELD "keepAliveSecond";
|
const string kKeepAliveSecond = RTMP_FIELD "keepAliveSecond";
|
||||||
|
|
||||||
static onceToken token([]() {
|
static onceToken token([]() {
|
||||||
mINI::Instance()[kModifyStamp] = false;
|
|
||||||
mINI::Instance()[kHandshakeSecond] = 15;
|
mINI::Instance()[kHandshakeSecond] = 15;
|
||||||
mINI::Instance()[kKeepAliveSecond] = 15;
|
mINI::Instance()[kKeepAliveSecond] = 15;
|
||||||
});
|
});
|
||||||
|
|
@ -241,15 +248,15 @@ const string kVideoMtuSize = RTP_FIELD "videoMtuSize";
|
||||||
const string kAudioMtuSize = RTP_FIELD "audioMtuSize";
|
const string kAudioMtuSize = RTP_FIELD "audioMtuSize";
|
||||||
// rtp包最大长度限制,单位是KB
|
// rtp包最大长度限制,单位是KB
|
||||||
const string kRtpMaxSize = RTP_FIELD "rtpMaxSize";
|
const string kRtpMaxSize = RTP_FIELD "rtpMaxSize";
|
||||||
|
|
||||||
const string kLowLatency = RTP_FIELD "lowLatency";
|
const string kLowLatency = RTP_FIELD "lowLatency";
|
||||||
|
const string kH264StapA = RTP_FIELD "h264_stap_a";
|
||||||
|
|
||||||
static onceToken token([]() {
|
static onceToken token([]() {
|
||||||
mINI::Instance()[kVideoMtuSize] = 1400;
|
mINI::Instance()[kVideoMtuSize] = 1400;
|
||||||
mINI::Instance()[kAudioMtuSize] = 600;
|
mINI::Instance()[kAudioMtuSize] = 600;
|
||||||
mINI::Instance()[kRtpMaxSize] = 10;
|
mINI::Instance()[kRtpMaxSize] = 10;
|
||||||
mINI::Instance()[kLowLatency] = 0;
|
mINI::Instance()[kLowLatency] = 0;
|
||||||
|
mINI::Instance()[kH264StapA] = 1;
|
||||||
});
|
});
|
||||||
} // namespace Rtp
|
} // namespace Rtp
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ extern const std::string kBroadcastStreamNoneReader;
|
||||||
|
|
||||||
// rtp推流被动停止时触发
|
// rtp推流被动停止时触发
|
||||||
extern const std::string kBroadcastSendRtpStopped;
|
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函数加载配置文件成功后会触发该广播
|
// 更新配置文件事件广播,执行loadIniConfig函数加载配置文件成功后会触发该广播
|
||||||
extern const std::string kBroadcastReloadConfig;
|
extern const std::string kBroadcastReloadConfig;
|
||||||
|
|
@ -107,7 +107,7 @@ extern const std::string kBroadcastReloadConfig;
|
||||||
|
|
||||||
// rtp server 超时
|
// rtp server 超时
|
||||||
extern const std::string KBroadcastRtpServerTimeout;
|
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 ReloadConfigTag ((void *)(0xFF))
|
||||||
#define RELOAD_KEY(arg, key) \
|
#define RELOAD_KEY(arg, key) \
|
||||||
|
|
@ -190,11 +190,17 @@ extern const std::string kModifyStamp;
|
||||||
extern const std::string kEnableAudio;
|
extern const std::string kEnableAudio;
|
||||||
//添加静音音频,在关闭音频时,此开关无效
|
//添加静音音频,在关闭音频时,此开关无效
|
||||||
extern const std::string kAddMuteAudio;
|
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;
|
extern const std::string kContinuePushMS;
|
||||||
|
|
||||||
//是否开启转换为hls
|
//是否开启转换为hls(mpegts)
|
||||||
extern const std::string kEnableHls;
|
extern const std::string kEnableHls;
|
||||||
|
//是否开启转换为hls(fmp4)
|
||||||
|
extern const std::string kEnableHlsFmp4;
|
||||||
//是否开启MP4录制
|
//是否开启MP4录制
|
||||||
extern const std::string kEnableMP4;
|
extern const std::string kEnableMP4;
|
||||||
//是否开启转换为rtsp/webrtc
|
//是否开启转换为rtsp/webrtc
|
||||||
|
|
@ -248,6 +254,8 @@ extern const std::string kForbidCacheSuffix;
|
||||||
extern const std::string kForwardedIpHeader;
|
extern const std::string kForwardedIpHeader;
|
||||||
// 是否允许所有跨域请求
|
// 是否允许所有跨域请求
|
||||||
extern const std::string kAllowCrossDomains;
|
extern const std::string kAllowCrossDomains;
|
||||||
|
// 允许访问http api和http文件索引的ip地址范围白名单,置空情况下不做限制
|
||||||
|
extern const std::string kAllowIPRange;
|
||||||
} // namespace Http
|
} // namespace Http
|
||||||
|
|
||||||
////////////SHELL配置///////////
|
////////////SHELL配置///////////
|
||||||
|
|
@ -273,6 +281,11 @@ extern const std::string kDirectProxy;
|
||||||
|
|
||||||
// rtsp 转发是否使用低延迟模式,当开启时,不会缓存rtp包,来提高并发,可以降低一帧的延迟
|
// rtsp 转发是否使用低延迟模式,当开启时,不会缓存rtp包,来提高并发,可以降低一帧的延迟
|
||||||
extern const std::string kLowLatency;
|
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
|
} // namespace Rtsp
|
||||||
|
|
||||||
////////////RTMP服务器配置///////////
|
////////////RTMP服务器配置///////////
|
||||||
|
|
@ -293,6 +306,8 @@ extern const std::string kAudioMtuSize;
|
||||||
extern const std::string kRtpMaxSize;
|
extern const std::string kRtpMaxSize;
|
||||||
// rtp 打包时,低延迟开关,默认关闭(为0),h264存在一帧多个slice(NAL)的情况,在这种情况下,如果开启可能会导致画面花屏
|
// rtp 打包时,低延迟开关,默认关闭(为0),h264存在一帧多个slice(NAL)的情况,在这种情况下,如果开启可能会导致画面花屏
|
||||||
extern const std::string kLowLatency;
|
extern const std::string kLowLatency;
|
||||||
|
//H264 rtp打包模式是否采用stap-a模式(为了在老版本浏览器上兼容webrtc)还是采用Single NAL unit packet per H.264 模式
|
||||||
|
extern const std::string kH264StapA;
|
||||||
} // namespace Rtp
|
} // namespace Rtp
|
||||||
|
|
||||||
////////////组播配置///////////
|
////////////组播配置///////////
|
||||||
|
|
|
||||||
|
|
@ -32,14 +32,6 @@
|
||||||
#define __LITTLE_ENDIAN LITTLE_ENDIAN
|
#define __LITTLE_ENDIAN LITTLE_ENDIAN
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef PACKED
|
|
||||||
#if !defined(_WIN32)
|
|
||||||
#define PACKED __attribute__((packed))
|
|
||||||
#else
|
|
||||||
#define PACKED
|
|
||||||
#endif //! defined(_WIN32)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef CHECK
|
#ifndef CHECK
|
||||||
#define CHECK(exp, ...) ::mediakit::Assert_ThrowCpp(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__)
|
#define CHECK(exp, ...) ::mediakit::Assert_ThrowCpp(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__)
|
||||||
#endif // CHECK
|
#endif // CHECK
|
||||||
|
|
@ -67,6 +59,7 @@
|
||||||
#define HLS_SCHEMA "hls"
|
#define HLS_SCHEMA "hls"
|
||||||
#define TS_SCHEMA "ts"
|
#define TS_SCHEMA "ts"
|
||||||
#define FMP4_SCHEMA "fmp4"
|
#define FMP4_SCHEMA "fmp4"
|
||||||
|
#define HLS_FMP4_SCHEMA "hls.fmp4"
|
||||||
#define SRT_SCHEMA "srt"
|
#define SRT_SCHEMA "srt"
|
||||||
#define DEFAULT_VHOST "__defaultVhost__"
|
#define DEFAULT_VHOST "__defaultVhost__"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,7 @@ AACTrack::AACTrack(const string &aac_cfg) {
|
||||||
onReady();
|
onReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
const string &AACTrack::getAacCfg() const {
|
const string &AACTrack::getConfig() const {
|
||||||
return _cfg;
|
return _cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,7 +342,7 @@ Sdp::Ptr AACTrack::getSdp() {
|
||||||
WarnL << getCodecName() << " Track未准备好";
|
WarnL << getCodecName() << " Track未准备好";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return std::make_shared<AACSdp>(getAacCfg(), getAudioSampleRate(), getAudioChannel(), getBitRate() / 1024);
|
return std::make_shared<AACSdp>(getConfig(), getAudioSampleRate(), getAudioChannel(), getBitRate() / 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
||||||
|
|
@ -44,7 +44,7 @@ public:
|
||||||
/**
|
/**
|
||||||
* 获取aac 配置信息
|
* 获取aac 配置信息
|
||||||
*/
|
*/
|
||||||
const std::string &getAacCfg() const;
|
const std::string &getConfig() const;
|
||||||
|
|
||||||
bool ready() override;
|
bool ready() override;
|
||||||
CodecId getCodecId() const override;
|
CodecId getCodecId() const override;
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,9 @@ using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
static string getAacCfg(const RtmpPacket &thiz) {
|
static string getConfig(const RtmpPacket &thiz) {
|
||||||
string ret;
|
string ret;
|
||||||
if (thiz.getMediaType() != FLV_CODEC_AAC) {
|
if ((RtmpAudioCodec)thiz.getRtmpCodecId() != RtmpAudioCodec::aac) {
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
if (!thiz.isCfgFrame()) {
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
if (thiz.buffer.size() < 4) {
|
if (thiz.buffer.size() < 4) {
|
||||||
|
|
@ -33,8 +30,8 @@ static string getAacCfg(const RtmpPacket &thiz) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
void AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||||
if (pkt->isCfgFrame()) {
|
if (pkt->isConfigFrame()) {
|
||||||
_aac_cfg = getAacCfg(*pkt);
|
_aac_cfg = getConfig(*pkt);
|
||||||
if (!_aac_cfg.empty()) {
|
if (!_aac_cfg.empty()) {
|
||||||
onGetAAC(nullptr, 0, 0);
|
onGetAAC(nullptr, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +79,7 @@ AACRtmpEncoder::AACRtmpEncoder(const Track::Ptr &track) {
|
||||||
void AACRtmpEncoder::makeConfigPacket() {
|
void AACRtmpEncoder::makeConfigPacket() {
|
||||||
if (_track && _track->ready()) {
|
if (_track && _track->ready()) {
|
||||||
//从track中和获取aac配置信息
|
//从track中和获取aac配置信息
|
||||||
_aac_cfg = _track->getAacCfg();
|
_aac_cfg = _track->getConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_aac_cfg.empty()) {
|
if (!_aac_cfg.empty()) {
|
||||||
|
|
@ -93,51 +90,45 @@ void AACRtmpEncoder::makeConfigPacket() {
|
||||||
bool AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
bool AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||||
if (_aac_cfg.empty()) {
|
if (_aac_cfg.empty()) {
|
||||||
if (frame->prefixSize()) {
|
if (frame->prefixSize()) {
|
||||||
//包含adts头,从adts头获取aac配置信息
|
// 包含adts头,从adts头获取aac配置信息
|
||||||
_aac_cfg = makeAacConfig((uint8_t *) (frame->data()), frame->prefixSize());
|
_aac_cfg = makeAacConfig((uint8_t *)(frame->data()), frame->prefixSize());
|
||||||
}
|
}
|
||||||
makeConfigPacket();
|
makeConfigPacket();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_aac_cfg.empty()){
|
if (_aac_cfg.empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto rtmpPkt = RtmpPacket::create();
|
auto pkt = RtmpPacket::create();
|
||||||
//header
|
// header
|
||||||
uint8_t is_config = false;
|
pkt->buffer.push_back(_audio_flv_flags);
|
||||||
rtmpPkt->buffer.push_back(_audio_flv_flags);
|
pkt->buffer.push_back((uint8_t)RtmpAACPacketType::aac_raw);
|
||||||
rtmpPkt->buffer.push_back(!is_config);
|
// aac data
|
||||||
|
pkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
||||||
//aac data
|
pkt->body_size = pkt->buffer.size();
|
||||||
rtmpPkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
|
pkt->chunk_id = CHUNK_AUDIO;
|
||||||
|
pkt->stream_index = STREAM_MEDIA;
|
||||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
pkt->time_stamp = frame->dts();
|
||||||
rtmpPkt->chunk_id = CHUNK_AUDIO;
|
pkt->type_id = MSG_AUDIO;
|
||||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
RtmpCodec::inputRtmp(pkt);
|
||||||
rtmpPkt->time_stamp = frame->dts();
|
|
||||||
rtmpPkt->type_id = MSG_AUDIO;
|
|
||||||
RtmpCodec::inputRtmp(rtmpPkt);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AACRtmpEncoder::makeAudioConfigPkt() {
|
void AACRtmpEncoder::makeAudioConfigPkt() {
|
||||||
_audio_flv_flags = getAudioRtmpFlags(std::make_shared<AACTrack>(_aac_cfg));
|
_audio_flv_flags = getAudioRtmpFlags(std::make_shared<AACTrack>(_aac_cfg));
|
||||||
auto rtmpPkt = RtmpPacket::create();
|
auto pkt = RtmpPacket::create();
|
||||||
|
// header
|
||||||
//header
|
pkt->buffer.push_back(_audio_flv_flags);
|
||||||
uint8_t is_config = true;
|
pkt->buffer.push_back((uint8_t)RtmpAACPacketType::aac_config_header);
|
||||||
rtmpPkt->buffer.push_back(_audio_flv_flags);
|
// aac config
|
||||||
rtmpPkt->buffer.push_back(!is_config);
|
pkt->buffer.append(_aac_cfg);
|
||||||
//aac config
|
pkt->body_size = pkt->buffer.size();
|
||||||
rtmpPkt->buffer.append(_aac_cfg);
|
pkt->chunk_id = CHUNK_AUDIO;
|
||||||
|
pkt->stream_index = STREAM_MEDIA;
|
||||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
pkt->time_stamp = 0;
|
||||||
rtmpPkt->chunk_id = CHUNK_AUDIO;
|
pkt->type_id = MSG_AUDIO;
|
||||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
RtmpCodec::inputRtmp(pkt);
|
||||||
rtmpPkt->time_stamp = 0;
|
|
||||||
rtmpPkt->type_id = MSG_AUDIO;
|
|
||||||
RtmpCodec::inputRtmp(rtmpPkt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
||||||
|
|
@ -64,7 +64,7 @@ AACRtpDecoder::AACRtpDecoder(const Track::Ptr &track) {
|
||||||
if (!aacTrack || !aacTrack->ready()) {
|
if (!aacTrack || !aacTrack->ready()) {
|
||||||
WarnL << "该aac track无效!";
|
WarnL << "该aac track无效!";
|
||||||
} else {
|
} else {
|
||||||
_aac_cfg = aacTrack->getAacCfg();
|
_aac_cfg = aacTrack->getConfig();
|
||||||
}
|
}
|
||||||
obtainFrame();
|
obtainFrame();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,10 @@ bool CommonRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto payload = rtp->getPayload();
|
auto payload = rtp->getPayload();
|
||||||
auto stamp = rtp->getStampMS();
|
auto stamp = rtp->getStamp();
|
||||||
auto seq = rtp->getSeq();
|
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,则清空上帧数据
|
//时间戳发生变化或者缓存超过MAX_FRAME_SIZE,则清空上帧数据
|
||||||
if (!_frame->_buffer.empty()) {
|
if (!_frame->_buffer.empty()) {
|
||||||
//有有效帧,则输出
|
//有有效帧,则输出
|
||||||
|
|
@ -46,7 +46,8 @@ bool CommonRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool){
|
||||||
|
|
||||||
//新的一帧数据
|
//新的一帧数据
|
||||||
obtainFrame();
|
obtainFrame();
|
||||||
_frame->_dts = stamp;
|
_frame->_dts = rtp->getStampMS();
|
||||||
|
_last_stamp = stamp;
|
||||||
_drop_flag = false;
|
_drop_flag = false;
|
||||||
} else if (_last_seq != 0 && (uint16_t)(_last_seq + 1) != seq) {
|
} else if (_last_seq != 0 && (uint16_t)(_last_seq + 1) != seq) {
|
||||||
//时间戳未发生变化,但是seq却不连续,说明中间rtp丢包了,那么整帧应该废弃
|
//时间戳未发生变化,但是seq却不连续,说明中间rtp丢包了,那么整帧应该废弃
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ private:
|
||||||
private:
|
private:
|
||||||
bool _drop_flag = false;
|
bool _drop_flag = false;
|
||||||
uint16_t _last_seq = 0;
|
uint16_t _last_seq = 0;
|
||||||
|
uint64_t _last_stamp = 0;
|
||||||
size_t _max_frame_size;
|
size_t _max_frame_size;
|
||||||
CodecId _codec;
|
CodecId _codec;
|
||||||
FrameImp::Ptr _frame;
|
FrameImp::Ptr _frame;
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,9 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) {
|
||||||
case CodecOpus : return std::make_shared<OpusTrack>();
|
case CodecOpus : return std::make_shared<OpusTrack>();
|
||||||
|
|
||||||
case CodecAAC : {
|
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()) {
|
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()) {
|
if (aac_cfg_str.empty()) {
|
||||||
//如果sdp中获取不到aac config信息,那么在rtp也无法获取,那么忽略该Track
|
//如果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==
|
//a=fmtp:96 packetization-mode=1;profile-level-id=42C01F;sprop-parameter-sets=Z0LAH9oBQBboQAAAAwBAAAAPI8YMqA==,aM48gA==
|
||||||
auto map = Parser::parseArgs(track->_fmtp, ";", "=");
|
auto map = Parser::parseArgs(track->_fmtp, ";", "=");
|
||||||
auto sps_pps = map["sprop-parameter-sets"];
|
auto sps_pps = map["sprop-parameter-sets"];
|
||||||
string base64_SPS = FindField(sps_pps.data(), NULL, ",");
|
string base64_SPS = findSubString(sps_pps.data(), NULL, ",");
|
||||||
string base64_PPS = FindField(sps_pps.data(), ",", NULL);
|
string base64_PPS = findSubString(sps_pps.data(), ",", NULL);
|
||||||
auto sps = decodeBase64(base64_SPS);
|
auto sps = decodeBase64(base64_SPS);
|
||||||
auto pps = decodeBase64(base64_PPS);
|
auto pps = decodeBase64(base64_PPS);
|
||||||
if (sps.empty() || pps.empty()) {
|
if (sps.empty() || pps.empty()) {
|
||||||
|
|
@ -201,17 +201,20 @@ static CodecId getVideoCodecIdByAmf(const AMFValue &val){
|
||||||
}
|
}
|
||||||
|
|
||||||
if (val.type() != AMF_NULL) {
|
if (val.type() != AMF_NULL) {
|
||||||
auto type_id = val.as_integer();
|
auto type_id = (RtmpVideoCodec)val.as_integer();
|
||||||
switch (type_id) {
|
switch (type_id) {
|
||||||
case FLV_CODEC_H264 : return CodecH264;
|
case RtmpVideoCodec::h264: return CodecH264;
|
||||||
case FLV_CODEC_H265 : return CodecH265;
|
case RtmpVideoCodec::fourcc_hevc:
|
||||||
default : WarnL << "暂不支持该视频Amf:" << type_id; return CodecInvalid;
|
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;
|
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){
|
switch (codecId){
|
||||||
case CodecH264 : return std::make_shared<H264Track>();
|
case CodecH264 : return std::make_shared<H264Track>();
|
||||||
case CodecH265 : return std::make_shared<H265Track>();
|
case CodecH265 : return std::make_shared<H265Track>();
|
||||||
|
|
@ -243,13 +246,13 @@ static CodecId getAudioCodecIdByAmf(const AMFValue &val) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (val.type() != AMF_NULL) {
|
if (val.type() != AMF_NULL) {
|
||||||
auto type_id = val.as_integer();
|
auto type_id = (RtmpAudioCodec)val.as_integer();
|
||||||
switch (type_id) {
|
switch (type_id) {
|
||||||
case FLV_CODEC_AAC : return CodecAAC;
|
case RtmpAudioCodec::aac : return CodecAAC;
|
||||||
case FLV_CODEC_G711A : return CodecG711A;
|
case RtmpAudioCodec::g711a : return CodecG711A;
|
||||||
case FLV_CODEC_G711U : return CodecG711U;
|
case RtmpAudioCodec::g711u : return CodecG711U;
|
||||||
case FLV_CODEC_OPUS : return CodecOpus;
|
case RtmpAudioCodec::opus : return CodecOpus;
|
||||||
default : WarnL << "暂不支持该音频Amf:" << type_id; return CodecInvalid;
|
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) {
|
AMFValue Factory::getAmfByCodecId(CodecId codecId) {
|
||||||
switch (codecId){
|
switch (codecId) {
|
||||||
case CodecAAC: return AMFValue(FLV_CODEC_AAC);
|
case CodecAAC: return AMFValue((int)RtmpAudioCodec::aac);
|
||||||
case CodecH264: return AMFValue(FLV_CODEC_H264);
|
case CodecH264: return AMFValue((int)RtmpVideoCodec::h264);
|
||||||
case CodecH265: return AMFValue(FLV_CODEC_H265);
|
case CodecH265: return AMFValue((int)RtmpVideoCodec::h265);
|
||||||
case CodecG711A: return AMFValue(FLV_CODEC_G711A);
|
case CodecG711A: return AMFValue((int)RtmpAudioCodec::g711a);
|
||||||
case CodecG711U: return AMFValue(FLV_CODEC_G711U);
|
case CodecG711U: return AMFValue((int)RtmpAudioCodec::g711u);
|
||||||
case CodecOpus: return AMFValue(FLV_CODEC_OPUS);
|
case CodecOpus: return AMFValue((int)RtmpAudioCodec::opus);
|
||||||
default: return AMFValue(AMF_NULL);
|
default: return AMFValue(AMF_NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,16 @@ namespace mediakit{
|
||||||
|
|
||||||
class Factory {
|
class Factory {
|
||||||
public:
|
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相关//////////////////////////////////
|
////////////////////////////////rtsp相关//////////////////////////////////
|
||||||
/**
|
/**
|
||||||
* 根据sdp生成Track对象
|
* 根据sdp生成Track对象
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include "H265.h"
|
#include "H265.h"
|
||||||
#include "Common/Parser.h"
|
#include "Common/Parser.h"
|
||||||
#include "Common/Stamp.h"
|
#include "Common/Stamp.h"
|
||||||
|
#include "Common/MediaSource.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
@ -31,11 +32,11 @@ Frame::Ptr Frame::getCacheAbleFrame(const Frame::Ptr &frame){
|
||||||
return std::make_shared<FrameCacheAble>(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);
|
_frame = std::move(frame);
|
||||||
//覆盖时间戳
|
// kModifyStampSystem时采用系统时间戳,kModifyStampRelative采用相对时间戳
|
||||||
stamp.revise(_frame->dts(), _frame->pts(), _dts, _pts, modify_stamp);
|
stamp.revise(_frame->dts(), _frame->pts(), _dts, _pts, modify_stamp == ProtocolOption::kModifyStampSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackType getTrackType(CodecId codecId) {
|
TrackType getTrackType(CodecId codecId) {
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ typedef enum {
|
||||||
XX(CodecVP8, TrackVideo, 7, "VP8", PSI_STREAM_VP8) \
|
XX(CodecVP8, TrackVideo, 7, "VP8", PSI_STREAM_VP8) \
|
||||||
XX(CodecVP9, TrackVideo, 8, "VP9", PSI_STREAM_VP9) \
|
XX(CodecVP9, TrackVideo, 8, "VP9", PSI_STREAM_VP9) \
|
||||||
XX(CodecAV1, TrackVideo, 9, "AV1", PSI_STREAM_AV1) \
|
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 {
|
typedef enum {
|
||||||
CodecInvalid = -1,
|
CodecInvalid = -1,
|
||||||
|
|
@ -492,7 +492,7 @@ private:
|
||||||
class FrameStamp : public Frame {
|
class FrameStamp : public Frame {
|
||||||
public:
|
public:
|
||||||
using Ptr = std::shared_ptr<FrameStamp>;
|
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 {}
|
~FrameStamp() override {}
|
||||||
|
|
||||||
uint64_t dts() const override { return (uint64_t)_dts; }
|
uint64_t dts() const override { return (uint64_t)_dts; }
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,10 @@
|
||||||
#include "SPSParser.h"
|
#include "SPSParser.h"
|
||||||
#include "Util/logger.h"
|
#include "Util/logger.h"
|
||||||
#include "Util/base64.h"
|
#include "Util/base64.h"
|
||||||
|
#include "Common/config.h"
|
||||||
|
|
||||||
using namespace toolkit;
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
|
|
@ -248,7 +249,14 @@ public:
|
||||||
_printer << "b=AS:" << bitrate << "\r\n";
|
_printer << "b=AS:" << bitrate << "\r\n";
|
||||||
}
|
}
|
||||||
_printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << 90000 << "\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;
|
uint32_t profile_level_id = 0;
|
||||||
if (strSPS.length() >= 4) { // sanity check
|
if (strSPS.length() >= 4) { // sanity check
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,7 @@ H264Frame::Ptr H264RtmpDecoder::obtainFrame() {
|
||||||
* 返回不带0x00 00 00 01头的sps pps
|
* 返回不带0x00 00 00 01头的sps pps
|
||||||
*/
|
*/
|
||||||
static bool getH264Config(const RtmpPacket &thiz, string &sps, string &pps) {
|
static bool getH264Config(const RtmpPacket &thiz, string &sps, string &pps) {
|
||||||
if (thiz.getMediaType() != FLV_CODEC_H264) {
|
if ((RtmpVideoCodec)thiz.getRtmpCodecId() != RtmpVideoCodec::h264) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!thiz.isCfgFrame()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (thiz.buffer.size() < 13) {
|
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) {
|
void H264RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||||
if (pkt->isCfgFrame()) {
|
if (pkt->isConfigFrame()) {
|
||||||
//缓存sps pps,后续插入到I帧之前
|
//缓存sps pps,后续插入到I帧之前
|
||||||
if (!getH264Config(*pkt, _sps, _pps)) {
|
if (!getH264Config(*pkt, _sps, _pps)) {
|
||||||
WarnL << "get h264 sps/pps failed, rtmp packet is: " << hexdump(pkt->data(), pkt->size());
|
WarnL << "get h264 sps/pps failed, rtmp packet is: " << hexdump(pkt->data(), pkt->size());
|
||||||
|
|
@ -159,26 +156,21 @@ bool H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) {
|
return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) {
|
||||||
//flags
|
// flags
|
||||||
_rtmp_packet->buffer[0] = FLV_CODEC_H264 | ((have_key_frame ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4);
|
_rtmp_packet->buffer[0] = (uint8_t)RtmpVideoCodec::h264 | ((uint8_t)(have_key_frame ? RtmpFrameType::key_frame : RtmpFrameType::inter_frame) << 4);
|
||||||
//not config
|
_rtmp_packet->buffer[1] = (uint8_t)RtmpH264PacketType::h264_nalu;
|
||||||
_rtmp_packet->buffer[1] = true;
|
int32_t cts = pts - dts;
|
||||||
int32_t cts = pts - dts;
|
// cts
|
||||||
if (cts < 0) {
|
set_be24(&_rtmp_packet->buffer[2], cts);
|
||||||
cts = 0;
|
_rtmp_packet->time_stamp = dts;
|
||||||
}
|
_rtmp_packet->body_size = _rtmp_packet->buffer.size();
|
||||||
//cts
|
_rtmp_packet->chunk_id = CHUNK_VIDEO;
|
||||||
set_be24(&_rtmp_packet->buffer[2], cts);
|
_rtmp_packet->stream_index = STREAM_MEDIA;
|
||||||
|
_rtmp_packet->type_id = MSG_VIDEO;
|
||||||
_rtmp_packet->time_stamp = dts;
|
// 输出rtmp packet
|
||||||
_rtmp_packet->body_size = _rtmp_packet->buffer.size();
|
RtmpCodec::inputRtmp(_rtmp_packet);
|
||||||
_rtmp_packet->chunk_id = CHUNK_VIDEO;
|
_rtmp_packet = nullptr;
|
||||||
_rtmp_packet->stream_index = STREAM_MEDIA;
|
}, &_rtmp_packet->buffer);
|
||||||
_rtmp_packet->type_id = MSG_VIDEO;
|
|
||||||
//输出rtmp packet
|
|
||||||
RtmpCodec::inputRtmp(_rtmp_packet);
|
|
||||||
_rtmp_packet = nullptr;
|
|
||||||
}, &_rtmp_packet->buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void H264RtmpEncoder::makeVideoConfigPkt() {
|
void H264RtmpEncoder::makeVideoConfigPkt() {
|
||||||
|
|
@ -186,42 +178,39 @@ void H264RtmpEncoder::makeVideoConfigPkt() {
|
||||||
WarnL << "sps长度不足4字节";
|
WarnL << "sps长度不足4字节";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int8_t flags = FLV_CODEC_H264;
|
auto flags = (uint8_t)RtmpVideoCodec::h264;
|
||||||
flags |= (FLV_KEY_FRAME << 4);
|
flags |= ((uint8_t)RtmpFrameType::key_frame << 4);
|
||||||
bool is_config = true;
|
auto pkt = RtmpPacket::create();
|
||||||
|
// header
|
||||||
auto rtmpPkt = RtmpPacket::create();
|
pkt->buffer.push_back(flags);
|
||||||
//header
|
pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header);
|
||||||
rtmpPkt->buffer.push_back(flags);
|
// cts
|
||||||
rtmpPkt->buffer.push_back(!is_config);
|
pkt->buffer.append("\x0\x0\x0", 3);
|
||||||
//cts
|
// AVCDecoderConfigurationRecord start
|
||||||
rtmpPkt->buffer.append("\x0\x0\x0", 3);
|
pkt->buffer.push_back(1); // version
|
||||||
|
pkt->buffer.push_back(_sps[1]); // profile
|
||||||
//AVCDecoderConfigurationRecord start
|
pkt->buffer.push_back(_sps[2]); // compat
|
||||||
rtmpPkt->buffer.push_back(1); // version
|
pkt->buffer.push_back(_sps[3]); // level
|
||||||
rtmpPkt->buffer.push_back(_sps[1]); // profile
|
pkt->buffer.push_back((char)0xff); // 6 bits reserved + 2 bits nal size length - 1 (11)
|
||||||
rtmpPkt->buffer.push_back(_sps[2]); // compat
|
pkt->buffer.push_back((char)0xe1); // 3 bits reserved + 5 bits number of sps (00001)
|
||||||
rtmpPkt->buffer.push_back(_sps[3]); // level
|
// sps
|
||||||
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
|
|
||||||
uint16_t size = (uint16_t)_sps.size();
|
uint16_t size = (uint16_t)_sps.size();
|
||||||
size = htons(size);
|
size = htons(size);
|
||||||
rtmpPkt->buffer.append((char *) &size, 2);
|
pkt->buffer.append((char *)&size, 2);
|
||||||
rtmpPkt->buffer.append(_sps);
|
pkt->buffer.append(_sps);
|
||||||
//pps
|
// pps
|
||||||
rtmpPkt->buffer.push_back(1); // version
|
pkt->buffer.push_back(1); // version
|
||||||
size = (uint16_t)_pps.size();
|
size = (uint16_t)_pps.size();
|
||||||
size = htons(size);
|
size = htons(size);
|
||||||
rtmpPkt->buffer.append((char *) &size, 2);
|
pkt->buffer.append((char *)&size, 2);
|
||||||
rtmpPkt->buffer.append(_pps);
|
pkt->buffer.append(_pps);
|
||||||
|
|
||||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
pkt->body_size = pkt->buffer.size();
|
||||||
rtmpPkt->chunk_id = CHUNK_VIDEO;
|
pkt->chunk_id = CHUNK_VIDEO;
|
||||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
pkt->stream_index = STREAM_MEDIA;
|
||||||
rtmpPkt->time_stamp = 0;
|
pkt->time_stamp = 0;
|
||||||
rtmpPkt->type_id = MSG_VIDEO;
|
pkt->type_id = MSG_VIDEO;
|
||||||
RtmpCodec::inputRtmp(rtmpPkt);
|
RtmpCodec::inputRtmp(pkt);
|
||||||
}
|
}
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,7 @@
|
||||||
|
|
||||||
namespace mediakit{
|
namespace mediakit{
|
||||||
|
|
||||||
#if defined(_WIN32)
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
#endif // defined(_WIN32)
|
|
||||||
|
|
||||||
class FuFlags {
|
class FuFlags {
|
||||||
public:
|
public:
|
||||||
|
|
@ -30,11 +28,9 @@ public:
|
||||||
unsigned end_bit: 1;
|
unsigned end_bit: 1;
|
||||||
unsigned start_bit: 1;
|
unsigned start_bit: 1;
|
||||||
#endif
|
#endif
|
||||||
} PACKED;
|
};
|
||||||
|
|
||||||
#if defined(_WIN32)
|
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
#endif // defined(_WIN32)
|
|
||||||
|
|
||||||
H264RtpDecoder::H264RtpDecoder() {
|
H264RtpDecoder::H264RtpDecoder() {
|
||||||
_frame = obtainFrame();
|
_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){
|
void H264RtpEncoder::packRtp(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){
|
||||||
if (len + 3 <= getMaxSize()) {
|
if (len + 3 <= getMaxSize()) {
|
||||||
//STAP-A模式打包小于MTU
|
// 采用STAP-A/Single NAL unit packet per H.264 模式
|
||||||
packRtpStapA(ptr, len, pts, is_mark, gop_pos);
|
packRtpSmallFrame(ptr, len, pts, is_mark, gop_pos);
|
||||||
} else {
|
} else {
|
||||||
//STAP-A模式打包会大于MTU,所以采用FU-A模式
|
//STAP-A模式打包会大于MTU,所以采用FU-A模式
|
||||||
packRtpFu(ptr, len, pts, is_mark, gop_pos);
|
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){
|
void H264RtpEncoder::packRtpFu(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){
|
||||||
auto packet_size = getMaxSize() - 2;
|
auto packet_size = getMaxSize() - 2;
|
||||||
if (len <= packet_size + 1) {
|
if (len <= packet_size + 1) {
|
||||||
//小于FU-A打包最小字节长度要求,采用STAP-A模式
|
// 小于FU-A打包最小字节长度要求,采用STAP-A/Single NAL unit packet per H.264 模式
|
||||||
packRtpStapA(ptr, len, pts, is_mark, gop_pos);
|
packRtpSmallFrame(ptr, len, pts, is_mark, gop_pos);
|
||||||
return;
|
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){
|
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);
|
auto rtp = makeRtp(getTrackType(), nullptr, len + 3, is_mark, pts);
|
||||||
uint8_t *payload = rtp->getPayload();
|
uint8_t *payload = rtp->getPayload();
|
||||||
//STAP-A
|
//STAP-A
|
||||||
|
|
@ -270,6 +275,11 @@ void H264RtpEncoder::packRtpStapA(const char *ptr, size_t len, uint64_t pts, boo
|
||||||
RtpCodec::inputRtp(rtp, gop_pos);
|
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) {
|
bool H264RtpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||||
auto ptr = frame->data() + frame->prefixSize();
|
auto ptr = frame->data() + frame->prefixSize();
|
||||||
switch (H264_TYPE(ptr[0])) {
|
switch (H264_TYPE(ptr[0])) {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ public:
|
||||||
using Ptr = std::shared_ptr<H264RtpDecoder>;
|
using Ptr = std::shared_ptr<H264RtpDecoder>;
|
||||||
|
|
||||||
H264RtpDecoder();
|
H264RtpDecoder();
|
||||||
~H264RtpDecoder() {}
|
~H264RtpDecoder() override = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入264 rtp包
|
* 输入264 rtp包
|
||||||
|
|
@ -77,9 +77,10 @@ public:
|
||||||
uint32_t sample_rate = 90000,
|
uint32_t sample_rate = 90000,
|
||||||
uint8_t pt = 96,
|
uint8_t pt = 96,
|
||||||
uint8_t interleaved = TrackVideo * 2);
|
uint8_t interleaved = TrackVideo * 2);
|
||||||
~H264RtpEncoder() {}
|
|
||||||
|
|
||||||
/**
|
~H264RtpEncoder() override = default;
|
||||||
|
|
||||||
|
/**
|
||||||
* 输入264帧
|
* 输入264帧
|
||||||
* @param frame 帧数据,必须
|
* @param frame 帧数据,必须
|
||||||
*/
|
*/
|
||||||
|
|
@ -96,6 +97,8 @@ private:
|
||||||
void packRtp(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos);
|
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 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 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:
|
private:
|
||||||
Frame::Ptr _sps;
|
Frame::Ptr _sps;
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,12 @@
|
||||||
#include "H265Rtmp.h"
|
#include "H265Rtmp.h"
|
||||||
#ifdef ENABLE_MP4
|
#ifdef ENABLE_MP4
|
||||||
#include "mpeg4-hevc.h"
|
#include "mpeg4-hevc.h"
|
||||||
#endif//ENABLE_MP4
|
#endif // ENABLE_MP4
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit{
|
namespace mediakit {
|
||||||
|
|
||||||
H265RtmpDecoder::H265RtmpDecoder() {
|
H265RtmpDecoder::H265RtmpDecoder() {
|
||||||
_h265frame = obtainFrame();
|
_h265frame = obtainFrame();
|
||||||
|
|
@ -30,46 +30,105 @@ H265Frame::Ptr H265RtmpDecoder::obtainFrame() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_MP4
|
#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
|
* 返回不带0x00 00 00 01头的sps
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
static bool getH265ConfigFrame(const RtmpPacket &thiz,string &frame) {
|
static bool getH265ConfigFrame(const RtmpPacket &thiz, string &frame) {
|
||||||
if (thiz.getMediaType() != FLV_CODEC_H265) {
|
if ((RtmpVideoCodec)thiz.getRtmpCodecId() != RtmpVideoCodec::h265) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!thiz.isCfgFrame()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (thiz.buffer.size() < 6) {
|
if (thiz.buffer.size() < 6) {
|
||||||
WarnL << "bad H265 cfg!";
|
WarnL << "bad H265 cfg!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return decode_HEVCDecoderConfigurationRecord((uint8_t *)thiz.buffer.data() + 5, thiz.buffer.size() - 5, frame);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||||
if (pkt->isCfgFrame()) {
|
if (_info.codec == CodecInvalid) {
|
||||||
|
// 先判断是否为增强型rtmp
|
||||||
|
parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_info.is_enhanced) {
|
||||||
|
// 增强型rtmp
|
||||||
|
parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &_info);
|
||||||
|
if (!_info.is_enhanced || _info.codec != CodecH265) {
|
||||||
|
throw std::invalid_argument("Invalid enhanced-rtmp hevc packet!");
|
||||||
|
}
|
||||||
|
auto data = (uint8_t *)pkt->data() + 5;
|
||||||
|
auto size = pkt->size() - 5;
|
||||||
|
switch (_info.video.pkt_type) {
|
||||||
|
case RtmpPacketType::PacketTypeSequenceStart: {
|
||||||
|
#ifdef ENABLE_MP4
|
||||||
|
string config;
|
||||||
|
if (decode_HEVCDecoderConfigurationRecord(data, size, config)) {
|
||||||
|
onGetH265(config.data(), config.size(), pkt->time_stamp, pkt->time_stamp);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RtmpPacketType::PacketTypeCodedFramesX:
|
||||||
|
case RtmpPacketType::PacketTypeCodedFrames: {
|
||||||
|
auto pts = pkt->time_stamp;
|
||||||
|
if (RtmpPacketType::PacketTypeCodedFrames == _info.video.pkt_type) {
|
||||||
|
// SI24 = [CompositionTime Offset]
|
||||||
|
CHECK(size > 7);
|
||||||
|
int32_t cts = (((data[0] << 16) | (data[1] << 8) | (data[2])) + 0xff800000) ^ 0xff800000;
|
||||||
|
pts += cts;
|
||||||
|
data += 3;
|
||||||
|
size -= 3;
|
||||||
|
}
|
||||||
|
splitFrame(data, size, pkt->time_stamp, pts);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RtmpPacketType::PacketTypeMetadata: {
|
||||||
|
// The body does not contain video data. The body is an AMF encoded metadata.
|
||||||
|
// The metadata will be represented by a series of [name, value] pairs.
|
||||||
|
// For now the only defined [name, value] pair is [“colorInfo”, Object]
|
||||||
|
// See Metadata Frame section for more details of this object.
|
||||||
|
//
|
||||||
|
// For a deeper understanding of the encoding please see description
|
||||||
|
// of SCRIPTDATA and SSCRIPTDATAVALUE in the FLV file spec.
|
||||||
|
// DATA = [“colorInfo”, Object]
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RtmpPacketType::PacketTypeSequenceEnd: {
|
||||||
|
// signals end of sequence
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 国内扩展(12) H265 rtmp
|
||||||
|
if (pkt->isConfigFrame()) {
|
||||||
#ifdef ENABLE_MP4
|
#ifdef ENABLE_MP4
|
||||||
string config;
|
string config;
|
||||||
if(getH265ConfigFrame(*pkt,config)){
|
if (getH265ConfigFrame(*pkt, config)) {
|
||||||
onGetH265(config.data(), config.size(), pkt->time_stamp , pkt->time_stamp);
|
onGetH265(config.data(), config.size(), pkt->time_stamp, pkt->time_stamp);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
||||||
|
|
@ -78,41 +137,42 @@ void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pkt->buffer.size() > 9) {
|
if (pkt->buffer.size() > 9) {
|
||||||
auto total_len = pkt->buffer.size();
|
uint8_t *cts_ptr = (uint8_t *)(pkt->buffer.data() + 2);
|
||||||
size_t offset = 5;
|
|
||||||
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;
|
int32_t cts = (((cts_ptr[0] << 16) | (cts_ptr[1] << 8) | (cts_ptr[2])) + 0xff800000) ^ 0xff800000;
|
||||||
auto pts = pkt->time_stamp + cts;
|
auto pts = pkt->time_stamp + cts;
|
||||||
while (offset + 4 < total_len) {
|
splitFrame((uint8_t *)pkt->data() + 5, pkt->size() - 5, pkt->time_stamp, pts);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void H265RtmpDecoder::onGetH265(const char* pcData, size_t iLen, uint32_t dts,uint32_t pts) {
|
void H265RtmpDecoder::splitFrame(const uint8_t *data, size_t size, uint32_t dts, uint32_t pts) {
|
||||||
if(iLen == 0){
|
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;
|
return;
|
||||||
}
|
}
|
||||||
#if 1
|
#if 1
|
||||||
_h265frame->_dts = dts;
|
_h265frame->_dts = dts;
|
||||||
_h265frame->_pts = pts;
|
_h265frame->_pts = pts;
|
||||||
_h265frame->_buffer.assign("\x00\x00\x00\x01", 4); //添加265头
|
_h265frame->_buffer.assign("\x00\x00\x00\x01", 4); // 添加265头
|
||||||
_h265frame->_buffer.append(pcData, iLen);
|
_h265frame->_buffer.append(data, size);
|
||||||
|
|
||||||
//写入环形缓存
|
// 写入环形缓存
|
||||||
RtmpCodec::inputFrame(_h265frame);
|
RtmpCodec::inputFrame(_h265frame);
|
||||||
_h265frame = obtainFrame();
|
_h265frame = obtainFrame();
|
||||||
#else
|
#else
|
||||||
//防止内存拷贝,这样产生的265帧不会有0x00 00 01头
|
// 防止内存拷贝,这样产生的265帧不会有0x00 00 01头
|
||||||
auto frame = std::make_shared<H265FrameNoCacheAble>((char *)pcData,iLen,dts,pts,0);
|
auto frame = std::make_shared<H265FrameNoCacheAble>((char *)data, size, dts, pts, 0);
|
||||||
RtmpCodec::inputFrame(frame);
|
RtmpCodec::inputFrame(frame);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
@ -123,16 +183,16 @@ H265RtmpEncoder::H265RtmpEncoder(const Track::Ptr &track) {
|
||||||
_track = dynamic_pointer_cast<H265Track>(track);
|
_track = dynamic_pointer_cast<H265Track>(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
void H265RtmpEncoder::makeConfigPacket(){
|
void H265RtmpEncoder::makeConfigPacket() {
|
||||||
if (_track && _track->ready()) {
|
if (_track && _track->ready()) {
|
||||||
//尝试从track中获取sps pps信息
|
// 尝试从track中获取sps pps信息
|
||||||
_sps = _track->getSps();
|
_sps = _track->getSps();
|
||||||
_pps = _track->getPps();
|
_pps = _track->getPps();
|
||||||
_vps = _track->getVps();
|
_vps = _track->getVps();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_sps.empty() && !_pps.empty() && !_vps.empty()) {
|
if (!_sps.empty() && !_pps.empty() && !_vps.empty()) {
|
||||||
//获取到sps/pps
|
// 获取到sps/pps
|
||||||
makeVideoConfigPkt();
|
makeVideoConfigPkt();
|
||||||
_got_config_frame = true;
|
_got_config_frame = true;
|
||||||
}
|
}
|
||||||
|
|
@ -175,50 +235,42 @@ bool H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
|
||||||
|
|
||||||
if (!_rtmp_packet) {
|
if (!_rtmp_packet) {
|
||||||
_rtmp_packet = RtmpPacket::create();
|
_rtmp_packet = RtmpPacket::create();
|
||||||
//flags/not_config/cts预占位
|
// flags/not_config/cts预占位
|
||||||
_rtmp_packet->buffer.resize(5);
|
_rtmp_packet->buffer.resize(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) {
|
return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) {
|
||||||
//flags
|
// flags
|
||||||
_rtmp_packet->buffer[0] = FLV_CODEC_H265 | ((have_key_frame ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4);
|
_rtmp_packet->buffer[0] = (uint8_t)RtmpVideoCodec::h265 | ((uint8_t)(have_key_frame ? RtmpFrameType::key_frame : RtmpFrameType::inter_frame) << 4);
|
||||||
//not config
|
_rtmp_packet->buffer[1] = (uint8_t)RtmpH264PacketType::h264_nalu;
|
||||||
_rtmp_packet->buffer[1] = true;
|
int32_t cts = pts - dts;
|
||||||
int32_t cts = pts - dts;
|
// cts
|
||||||
if (cts < 0) {
|
set_be24(&_rtmp_packet->buffer[2], cts);
|
||||||
cts = 0;
|
_rtmp_packet->time_stamp = dts;
|
||||||
}
|
_rtmp_packet->body_size = _rtmp_packet->buffer.size();
|
||||||
//cts
|
_rtmp_packet->chunk_id = CHUNK_VIDEO;
|
||||||
set_be24(&_rtmp_packet->buffer[2], cts);
|
_rtmp_packet->stream_index = STREAM_MEDIA;
|
||||||
|
_rtmp_packet->type_id = MSG_VIDEO;
|
||||||
_rtmp_packet->time_stamp = dts;
|
// 输出rtmp packet
|
||||||
_rtmp_packet->body_size = _rtmp_packet->buffer.size();
|
RtmpCodec::inputRtmp(_rtmp_packet);
|
||||||
_rtmp_packet->chunk_id = CHUNK_VIDEO;
|
_rtmp_packet = nullptr;
|
||||||
_rtmp_packet->stream_index = STREAM_MEDIA;
|
|
||||||
_rtmp_packet->type_id = MSG_VIDEO;
|
|
||||||
//输出rtmp packet
|
|
||||||
RtmpCodec::inputRtmp(_rtmp_packet);
|
|
||||||
_rtmp_packet = nullptr;
|
|
||||||
}, &_rtmp_packet->buffer);
|
}, &_rtmp_packet->buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void H265RtmpEncoder::makeVideoConfigPkt() {
|
void H265RtmpEncoder::makeVideoConfigPkt() {
|
||||||
#ifdef ENABLE_MP4
|
#ifdef ENABLE_MP4
|
||||||
int8_t flags = FLV_CODEC_H265;
|
auto flags = (uint8_t)RtmpVideoCodec::h265;
|
||||||
flags |= (FLV_KEY_FRAME << 4);
|
flags |= ((uint8_t)RtmpFrameType::key_frame << 4);
|
||||||
bool is_config = true;
|
auto pkt = RtmpPacket::create();
|
||||||
auto rtmpPkt = RtmpPacket::create();
|
// header
|
||||||
//header
|
pkt->buffer.push_back(flags);
|
||||||
rtmpPkt->buffer.push_back(flags);
|
pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header);
|
||||||
rtmpPkt->buffer.push_back(!is_config);
|
// cts
|
||||||
//cts
|
pkt->buffer.append("\x0\x0\x0", 3);
|
||||||
rtmpPkt->buffer.append("\x0\x0\x0", 3);
|
|
||||||
|
|
||||||
struct mpeg4_hevc_t hevc;
|
struct mpeg4_hevc_t hevc;
|
||||||
memset(&hevc, 0, sizeof(hevc));
|
memset(&hevc, 0, sizeof(hevc));
|
||||||
string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps +
|
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("\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);
|
h265_annexbtomp4(&hevc, vps_sps_pps.data(), (int)vps_sps_pps.size(), NULL, 0, NULL, NULL);
|
||||||
uint8_t extra_data[1024];
|
uint8_t extra_data[1024];
|
||||||
int extra_data_size = mpeg4_hevc_decoder_configuration_record_save(&hevc, extra_data, sizeof(extra_data));
|
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 失败";
|
WarnL << "生成H265 extra_data 失败";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//HEVCDecoderConfigurationRecord
|
// HEVCDecoderConfigurationRecord
|
||||||
rtmpPkt->buffer.append((char *)extra_data, extra_data_size);
|
pkt->buffer.append((char *)extra_data, extra_data_size);
|
||||||
rtmpPkt->body_size = rtmpPkt->buffer.size();
|
pkt->body_size = pkt->buffer.size();
|
||||||
rtmpPkt->chunk_id = CHUNK_VIDEO;
|
pkt->chunk_id = CHUNK_VIDEO;
|
||||||
rtmpPkt->stream_index = STREAM_MEDIA;
|
pkt->stream_index = STREAM_MEDIA;
|
||||||
rtmpPkt->time_stamp = 0;
|
pkt->time_stamp = 0;
|
||||||
rtmpPkt->type_id = MSG_VIDEO;
|
pkt->type_id = MSG_VIDEO;
|
||||||
RtmpCodec::inputRtmp(rtmpPkt);
|
RtmpCodec::inputRtmp(pkt);
|
||||||
#else
|
#else
|
||||||
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善";
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
}//namespace mediakit
|
} // namespace mediakit
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
#include "Extension/Track.h"
|
#include "Extension/Track.h"
|
||||||
#include "Extension/H265.h"
|
#include "Extension/H265.h"
|
||||||
|
|
||||||
namespace mediakit{
|
namespace mediakit {
|
||||||
/**
|
/**
|
||||||
* h265 Rtmp解码类
|
* h265 Rtmp解码类
|
||||||
* 将 h265 over rtmp 解复用出 h265-Frame
|
* 将 h265 over rtmp 解复用出 h265-Frame
|
||||||
|
|
@ -25,7 +25,7 @@ public:
|
||||||
using Ptr = std::shared_ptr<H265RtmpDecoder>;
|
using Ptr = std::shared_ptr<H265RtmpDecoder>;
|
||||||
|
|
||||||
H265RtmpDecoder();
|
H265RtmpDecoder();
|
||||||
~H265RtmpDecoder() {}
|
~H265RtmpDecoder() = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 输入265 Rtmp包
|
* 输入265 Rtmp包
|
||||||
|
|
@ -33,22 +33,23 @@ public:
|
||||||
*/
|
*/
|
||||||
void inputRtmp(const RtmpPacket::Ptr &rtmp) override;
|
void inputRtmp(const RtmpPacket::Ptr &rtmp) override;
|
||||||
|
|
||||||
CodecId getCodecId() const override{
|
CodecId getCodecId() const override { return CodecH265; }
|
||||||
return CodecH265;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void onGetH265(const char *pcData, size_t iLen, uint32_t dts,uint32_t pts);
|
|
||||||
H265Frame::Ptr obtainFrame();
|
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:
|
protected:
|
||||||
|
RtmpPacketInfo _info;
|
||||||
H265Frame::Ptr _h265frame;
|
H265Frame::Ptr _h265frame;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 265 Rtmp打包类
|
* 265 Rtmp打包类
|
||||||
*/
|
*/
|
||||||
class H265RtmpEncoder : public H265RtmpDecoder{
|
class H265RtmpEncoder : public H265RtmpDecoder {
|
||||||
public:
|
public:
|
||||||
using Ptr = std::shared_ptr<H265RtmpEncoder>;
|
using Ptr = std::shared_ptr<H265RtmpEncoder>;
|
||||||
|
|
||||||
|
|
@ -87,9 +88,9 @@ private:
|
||||||
std::string _pps;
|
std::string _pps;
|
||||||
H265Track::Ptr _track;
|
H265Track::Ptr _track;
|
||||||
RtmpPacket::Ptr _rtmp_packet;
|
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;
|
return _ring;
|
||||||
}
|
}
|
||||||
|
|
||||||
void getPlayerList(const std::function<void(const std::list<std::shared_ptr<void>> &info_list)> &cb,
|
void getPlayerList(const std::function<void(const std::list<toolkit::Any> &info_list)> &cb,
|
||||||
const std::function<std::shared_ptr<void>(std::shared_ptr<void> &&info)> &on_change) override {
|
const std::function<toolkit::Any(toolkit::Any &&info)> &on_change) override {
|
||||||
_ring->getInfoList(cb, on_change);
|
_ring->getInfoList(cb, on_change);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@
|
||||||
#ifndef ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
|
#ifndef ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
|
||||||
#define ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
|
#define ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
|
||||||
|
|
||||||
#if defined(ENABLE_MP4)
|
|
||||||
|
|
||||||
#include "FMP4MediaSource.h"
|
#include "FMP4MediaSource.h"
|
||||||
#include "Record/MP4Muxer.h"
|
#include "Record/MP4Muxer.h"
|
||||||
|
|
||||||
|
|
@ -63,7 +61,8 @@ public:
|
||||||
return _option.fmp4_demand ? (_clear_cache ? true : _enabled) : true;
|
return _option.fmp4_demand ? (_clear_cache ? true : _enabled) : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onAllTrackReady() {
|
void addTrackCompleted() override {
|
||||||
|
MP4MuxerMemory::addTrackCompleted();
|
||||||
_media_src->setInitSegment(getInitSegment());
|
_media_src->setInitSegment(getInitSegment());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,5 +85,4 @@ private:
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
||||||
|
|
||||||
#endif// defined(ENABLE_MP4)
|
|
||||||
#endif //ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H
|
#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] != '#') {
|
if ((_is_m3u8_inner || extinf_dur != 0) && line[0] != '#') {
|
||||||
segment.duration = extinf_dur;
|
segment.duration = extinf_dur;
|
||||||
segment.url = Parser::merge_url(http_url, line);
|
segment.url = Parser::mergeUrl(http_url, line);
|
||||||
if (!_is_m3u8_inner) {
|
if (!_is_m3u8_inner) {
|
||||||
//ts按照先后顺序排序
|
//ts按照先后顺序排序
|
||||||
ts_map.emplace(index++, segment);
|
ts_map.emplace(index++, segment);
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,11 @@ void HlsPlayer::teardown() {
|
||||||
|
|
||||||
void HlsPlayer::fetchSegment() {
|
void HlsPlayer::fetchSegment() {
|
||||||
if (_ts_list.empty()) {
|
if (_ts_list.empty()) {
|
||||||
|
// 如果是点播文件,播放列表为空代表文件播放结束,关闭播放器: #2628
|
||||||
|
if(!HlsParser::isLive()){
|
||||||
|
teardown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
//播放列表为空,那么立即重新下载m3u8文件
|
//播放列表为空,那么立即重新下载m3u8文件
|
||||||
_timer.reset();
|
_timer.reset();
|
||||||
fetchIndexFile();
|
fetchIndexFile();
|
||||||
|
|
@ -121,18 +126,21 @@ void HlsPlayer::fetchSegment() {
|
||||||
WarnL << "Download ts segment " << url << " failed:" << err;
|
WarnL << "Download ts segment " << url << " failed:" << err;
|
||||||
if (err.getErrCode() == Err_timeout) {
|
if (err.getErrCode() == Err_timeout) {
|
||||||
strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple + 1, MAX_TIMEOUT_MULTIPLE);
|
strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple + 1, MAX_TIMEOUT_MULTIPLE);
|
||||||
}else{
|
} else {
|
||||||
strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple -1 , MIN_TIMEOUT_MULTIPLE);
|
strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple - 1, MIN_TIMEOUT_MULTIPLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//提前半秒下载好
|
// 提前0.5秒下载好,支持点播文件控制下载速度: #2628
|
||||||
auto delay = duration - ticker.elapsedTime() / 1000.0f - 0.5;
|
auto delay = duration - 0.5 - ticker.elapsedTime() / 1000.0f;
|
||||||
if (delay <= 0) {
|
if (delay > 2.0) {
|
||||||
//延时最小10ms
|
// 提前1秒下载
|
||||||
delay = 10;
|
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();
|
auto strong_self = weak_self.lock();
|
||||||
if (strong_self) {
|
if (strong_self) {
|
||||||
strong_self->fetchSegment();
|
strong_self->fetchSegment();
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ namespace mediakit {
|
||||||
void HttpClient::sendRequest(const string &url) {
|
void HttpClient::sendRequest(const string &url) {
|
||||||
clearResponse();
|
clearResponse();
|
||||||
_url = url;
|
_url = url;
|
||||||
auto protocol = FindField(url.data(), NULL, "://");
|
auto protocol = findSubString(url.data(), NULL, "://");
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
bool is_https;
|
bool is_https;
|
||||||
if (strcasecmp(protocol.data(), "http") == 0) {
|
if (strcasecmp(protocol.data(), "http") == 0) {
|
||||||
|
|
@ -35,11 +35,11 @@ void HttpClient::sendRequest(const string &url) {
|
||||||
throw std::invalid_argument(strErr);
|
throw std::invalid_argument(strErr);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto host = FindField(url.data(), "://", "/");
|
auto host = findSubString(url.data(), "://", "/");
|
||||||
if (host.empty()) {
|
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()) {
|
if (_path.empty()) {
|
||||||
_path = "/";
|
_path = "/";
|
||||||
}
|
}
|
||||||
|
|
@ -100,7 +100,7 @@ void HttpClient::clearResponse() {
|
||||||
_header_recved = false;
|
_header_recved = false;
|
||||||
_recved_body_size = 0;
|
_recved_body_size = 0;
|
||||||
_total_body_size = 0;
|
_total_body_size = 0;
|
||||||
_parser.Clear();
|
_parser.clear();
|
||||||
_chunked_splitter = nullptr;
|
_chunked_splitter = nullptr;
|
||||||
_wait_header.resetTime();
|
_wait_header.resetTime();
|
||||||
_wait_body.resetTime();
|
_wait_body.resetTime();
|
||||||
|
|
@ -181,20 +181,20 @@ void HttpClient::onError(const SockException &ex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t HttpClient::onRecvHeader(const char *data, size_t len) {
|
ssize_t HttpClient::onRecvHeader(const char *data, size_t len) {
|
||||||
_parser.Parse(data);
|
_parser.parse(data, len);
|
||||||
if (_parser.Url() == "302" || _parser.Url() == "301" || _parser.Url() == "303") {
|
if (_parser.status() == "302" || _parser.status() == "301" || _parser.status() == "303") {
|
||||||
auto new_url = Parser::merge_url(_url, _parser["Location"]);
|
auto new_url = Parser::mergeUrl(_url, _parser["Location"]);
|
||||||
if (new_url.empty()) {
|
if (new_url.empty()) {
|
||||||
throw invalid_argument("未找到Location字段(跳转url)");
|
throw invalid_argument("未找到Location字段(跳转url)");
|
||||||
}
|
}
|
||||||
if (onRedirectUrl(new_url, _parser.Url() == "302")) {
|
if (onRedirectUrl(new_url, _parser.status() == "302")) {
|
||||||
HttpClient::sendRequest(new_url);
|
HttpClient::sendRequest(new_url);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkCookie(_parser.getHeader());
|
checkCookie(_parser.getHeader());
|
||||||
onResponseHeader(_parser.Url(), _parser.getHeader());
|
onResponseHeader(_parser.status(), _parser.getHeader());
|
||||||
_header_recved = true;
|
_header_recved = true;
|
||||||
|
|
||||||
if (_parser["Transfer-Encoding"] == "chunked") {
|
if (_parser["Transfer-Encoding"] == "chunked") {
|
||||||
|
|
@ -361,8 +361,8 @@ void HttpClient::checkCookie(HttpClient::HttpHeader &headers) {
|
||||||
int index = 0;
|
int index = 0;
|
||||||
auto arg_vec = split(it_set_cookie->second, ";");
|
auto arg_vec = split(it_set_cookie->second, ";");
|
||||||
for (string &key_val : arg_vec) {
|
for (string &key_val : arg_vec) {
|
||||||
auto key = FindField(key_val.data(), NULL, "=");
|
auto key = findSubString(key_val.data(), NULL, "=");
|
||||||
auto val = FindField(key_val.data(), "=", NULL);
|
auto val = findSubString(key_val.data(), "=", NULL);
|
||||||
|
|
||||||
if (index++ == 0) {
|
if (index++ == 0) {
|
||||||
cookie->setKeyVal(key, val);
|
cookie->setKeyVal(key, val);
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
#include "HttpRequestSplitter.h"
|
#include "HttpRequestSplitter.h"
|
||||||
#include "HttpCookie.h"
|
#include "HttpCookie.h"
|
||||||
#include "HttpChunkedSplitter.h"
|
#include "HttpChunkedSplitter.h"
|
||||||
#include "strCoding.h"
|
#include "Common/strCoding.h"
|
||||||
#include "HttpBody.h"
|
#include "HttpBody.h"
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit{
|
namespace mediakit{
|
||||||
|
|
||||||
const char *getHttpStatusMessage(int status) {
|
const char *HttpConst::getHttpStatusMessage(int status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 100: return "Continue";
|
case 100: return "Continue";
|
||||||
case 101: return "Switching Protocol";
|
case 101: return "Switching Protocol";
|
||||||
|
|
@ -196,7 +196,7 @@ static const char *s_mime_src[][2] = {
|
||||||
{"avi", "video/x-msvideo"},
|
{"avi", "video/x-msvideo"},
|
||||||
};
|
};
|
||||||
|
|
||||||
const string &getHttpContentType(const char *name) {
|
const string& HttpConst::getHttpContentType(const char *name) {
|
||||||
const char *dot;
|
const char *dot;
|
||||||
dot = strrchr(name, '.');
|
dot = strrchr(name, '.');
|
||||||
static StrCaseMap mapType;
|
static StrCaseMap mapType;
|
||||||
|
|
|
||||||
|
|
@ -15,19 +15,25 @@
|
||||||
|
|
||||||
namespace mediakit{
|
namespace mediakit{
|
||||||
|
|
||||||
/**
|
class HttpConst {
|
||||||
* 根据http错误代码获取字符说明
|
public:
|
||||||
* @param status 譬如404
|
HttpConst() = delete;
|
||||||
* @return 错误代码字符说明,譬如Not Found
|
~HttpConst() = delete;
|
||||||
*/
|
|
||||||
const char *getHttpStatusMessage(int status);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据文件后缀返回http mime
|
* 根据http错误代码获取字符说明
|
||||||
* @param name 文件后缀,譬如html
|
* @param status 譬如404
|
||||||
* @return mime值,譬如text/html
|
* @return 错误代码字符说明,譬如Not Found
|
||||||
*/
|
*/
|
||||||
const std::string &getHttpContentType(const char *name);
|
static const char *getHttpStatusMessage(int status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据文件后缀返回http mime
|
||||||
|
* @param name 文件后缀,譬如html
|
||||||
|
* @return mime值,譬如text/html
|
||||||
|
*/
|
||||||
|
static const std::string &getHttpContentType(const char *name);
|
||||||
|
};
|
||||||
|
|
||||||
}//mediakit
|
}//mediakit
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ bool HttpServerCookie::isExpired() {
|
||||||
return _ticker.elapsedTime() > _max_elapsed * 1000;
|
return _ticker.elapsedTime() > _max_elapsed * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpServerCookie::setAttach(std::shared_ptr<void> attach) {
|
void HttpServerCookie::setAttach(toolkit::Any attach) {
|
||||||
_attach = std::move(attach);
|
_attach = std::move(attach);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,8 +114,7 @@ void HttpCookieManager::onManager() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpServerCookie::Ptr HttpCookieManager::addCookie(const string &cookie_name, const string &uid_in,
|
HttpServerCookie::Ptr HttpCookieManager::addCookie(const string &cookie_name, const string &uid_in, uint64_t max_elapsed, toolkit::Any attach, int max_client) {
|
||||||
uint64_t max_elapsed, std::shared_ptr<void> attach, int max_client) {
|
|
||||||
lock_guard<recursive_mutex> lck(_mtx_cookie);
|
lock_guard<recursive_mutex> lck(_mtx_cookie);
|
||||||
auto cookie = _generator.obtain();
|
auto cookie = _generator.obtain();
|
||||||
auto uid = uid_in.empty() ? cookie : uid_in;
|
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()) {
|
if (it == http_header.end()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
auto cookie = FindField(it->second.data(), (cookie_name + "=").data(), ";");
|
auto cookie = findSubString(it->second.data(), (cookie_name + "=").data(), ";");
|
||||||
if (cookie.empty()) {
|
if (cookie.empty()) {
|
||||||
cookie = FindField(it->second.data(), (cookie_name + "=").data(), nullptr);
|
cookie = findSubString(it->second.data(), (cookie_name + "=").data(), nullptr);
|
||||||
}
|
}
|
||||||
if (cookie.empty()) {
|
if (cookie.empty()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
|
||||||
|
|
@ -85,14 +85,14 @@ public:
|
||||||
/**
|
/**
|
||||||
* 设置附加数据
|
* 设置附加数据
|
||||||
*/
|
*/
|
||||||
void setAttach(std::shared_ptr<void> attach);
|
void setAttach(toolkit::Any attach);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 获取附加数据
|
* 获取附加数据
|
||||||
*/
|
*/
|
||||||
template <class T>
|
template <class T>
|
||||||
T& getAttach() {
|
T& getAttach() {
|
||||||
return *static_cast<T *>(_attach.get());
|
return _attach.get<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -104,7 +104,7 @@ private:
|
||||||
std::string _cookie_uuid;
|
std::string _cookie_uuid;
|
||||||
uint64_t _max_elapsed;
|
uint64_t _max_elapsed;
|
||||||
toolkit::Ticker _ticker;
|
toolkit::Ticker _ticker;
|
||||||
std::shared_ptr<void> _attach;
|
toolkit::Any _attach;
|
||||||
std::weak_ptr<HttpCookieManager> _manager;
|
std::weak_ptr<HttpCookieManager> _manager;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -163,7 +163,7 @@ public:
|
||||||
*/
|
*/
|
||||||
HttpServerCookie::Ptr addCookie(
|
HttpServerCookie::Ptr addCookie(
|
||||||
const std::string &cookie_name, const std::string &uid, uint64_t max_elapsed = COOKIE_DEFAULT_LIFE,
|
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);
|
int max_client = 1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
#include "Record/HlsMediaSource.h"
|
#include "Record/HlsMediaSource.h"
|
||||||
#include "Common/Parser.h"
|
#include "Common/Parser.h"
|
||||||
#include "Common/config.h"
|
#include "Common/config.h"
|
||||||
#include "strCoding.h"
|
#include "Common/strCoding.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
|
|
@ -31,12 +31,16 @@ namespace mediakit {
|
||||||
// 每次访问一次该cookie,那么将重新刷新cookie有效期
|
// 每次访问一次该cookie,那么将重新刷新cookie有效期
|
||||||
// 假如播放器在60秒内都未访问该cookie,那么将重新触发hls播放鉴权
|
// 假如播放器在60秒内都未访问该cookie,那么将重新触发hls播放鉴权
|
||||||
static int kHlsCookieSecond = 60;
|
static int kHlsCookieSecond = 60;
|
||||||
|
static int kFindSrcIntervalSecond = 3;
|
||||||
static const string kCookieName = "ZL_COOKIE";
|
static const string kCookieName = "ZL_COOKIE";
|
||||||
static const string kHlsSuffix = "/hls.m3u8";
|
static const string kHlsSuffix = "/hls.m3u8";
|
||||||
|
static const string kHlsFMP4Suffix = "/hls.fmp4.m3u8";
|
||||||
|
|
||||||
struct HttpCookieAttachment {
|
struct HttpCookieAttachment {
|
||||||
//是否已经查找到过MediaSource
|
// 是否已经查找到过MediaSource
|
||||||
bool _find_src = false;
|
bool _find_src = false;
|
||||||
|
// 查找MediaSource计时
|
||||||
|
Ticker _find_src_ticker;
|
||||||
//cookie生效作用域,本cookie只对该目录下的文件生效
|
//cookie生效作用域,本cookie只对该目录下的文件生效
|
||||||
string _path;
|
string _path;
|
||||||
//上次鉴权失败信息,为空则上次鉴权成功
|
//上次鉴权失败信息,为空则上次鉴权成功
|
||||||
|
|
@ -46,7 +50,112 @@ struct HttpCookieAttachment {
|
||||||
};
|
};
|
||||||
|
|
||||||
const string &HttpFileManager::getContentType(const char *name) {
|
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){
|
static string searchIndexFile(const string &dir){
|
||||||
|
|
@ -57,7 +166,7 @@ static string searchIndexFile(const string &dir){
|
||||||
}
|
}
|
||||||
set<string> setFile;
|
set<string> setFile;
|
||||||
while ((pDirent = readdir(pDir)) != NULL) {
|
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()) {
|
if (indexSet.find(pDirent->d_name) != indexSet.end()) {
|
||||||
string ret = pDirent->d_name;
|
string ret = pDirent->d_name;
|
||||||
closedir(pDir);
|
closedir(pDir);
|
||||||
|
|
@ -188,7 +297,7 @@ static bool emitHlsPlayed(const Parser &parser, const MediaInfo &media_info, con
|
||||||
//cookie有效期为kHlsCookieSecond
|
//cookie有效期为kHlsCookieSecond
|
||||||
invoker(err, "", 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) {
|
if (!flag) {
|
||||||
//未开启鉴权,那么允许播放
|
//未开启鉴权,那么允许播放
|
||||||
auth_invoker("");
|
auth_invoker("");
|
||||||
|
|
@ -240,8 +349,8 @@ public:
|
||||||
static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo &media_info, bool is_dir,
|
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) {
|
const function<void(const string &err_msg, const HttpServerCookie::Ptr &cookie)> &callback) {
|
||||||
//获取用户唯一id
|
//获取用户唯一id
|
||||||
auto uid = parser.Params();
|
auto uid = parser.params();
|
||||||
auto path = parser.Url();
|
auto path = parser.url();
|
||||||
|
|
||||||
//先根据http头中的cookie字段获取cookie
|
//先根据http头中的cookie字段获取cookie
|
||||||
HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, parser.getHeader());
|
HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, parser.getHeader());
|
||||||
|
|
@ -268,7 +377,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//上次鉴权失败,但是如果url参数发生变更,那么也重新鉴权下
|
//上次鉴权失败,但是如果url参数发生变更,那么也重新鉴权下
|
||||||
if (parser.Params().empty() || parser.Params() == cookie->getUid()) {
|
if (parser.params().empty() || parser.params() == cookie->getUid()) {
|
||||||
//url参数未变,或者本来就没有url参数,那么判断本次请求为重复请求,无访问权限
|
//url参数未变,或者本来就没有url参数,那么判断本次请求为重复请求,无访问权限
|
||||||
callback(attach._err_msg, update_cookie ? cookie : nullptr);
|
callback(attach._err_msg, update_cookie ? cookie : nullptr);
|
||||||
return;
|
return;
|
||||||
|
|
@ -278,7 +387,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
|
||||||
HttpCookieManager::Instance().delCookie(cookie);
|
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>();
|
SockInfoImp::Ptr info = std::make_shared<SockInfoImp>();
|
||||||
info->_identifier = sender.getIdentifier();
|
info->_identifier = sender.getIdentifier();
|
||||||
|
|
@ -308,7 +417,9 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
|
||||||
// hls相关信息
|
// hls相关信息
|
||||||
attach->_hls_data = std::make_shared<HlsCookieData>(media_info, info);
|
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 {
|
} else {
|
||||||
callback(err_msg, nullptr);
|
callback(err_msg, nullptr);
|
||||||
}
|
}
|
||||||
|
|
@ -320,10 +431,10 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//事件未被拦截,则认为是http下载请求
|
// 事件未被拦截,则认为是http下载请求
|
||||||
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, static_cast<SockInfo &>(sender));
|
bool flag = NOTICE_EMIT(BroadcastHttpAccessArgs, Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, sender);
|
||||||
if (!flag) {
|
if (!flag) {
|
||||||
//此事件无人监听,我们默认都有权限访问
|
// 此事件无人监听,我们默认都有权限访问
|
||||||
callback("", nullptr);
|
callback("", nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -355,7 +466,7 @@ static string pathCat(const string &a, const string &b){
|
||||||
* @param cb 回调对象
|
* @param cb 回调对象
|
||||||
*/
|
*/
|
||||||
static void accessFile(Session &sender, const Parser &parser, const MediaInfo &media_info, const string &file_path, const HttpFileManager::invoker &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())) {
|
if (!is_hls && !File::fileExist(file_path.data())) {
|
||||||
//文件不存在且不是hls,那么直接返回404
|
//文件不存在且不是hls,那么直接返回404
|
||||||
sendNotFound(cb);
|
sendNotFound(cb);
|
||||||
|
|
@ -363,8 +474,13 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
|
||||||
}
|
}
|
||||||
if (is_hls) {
|
if (is_hls) {
|
||||||
// hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
|
// hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS
|
||||||
const_cast<string &>(media_info.schema) = HLS_SCHEMA;
|
if (end_with(file_path, kHlsSuffix)) {
|
||||||
replace(const_cast<string &>(media_info.stream), 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());
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto src = cookie->getAttach<HttpCookieAttachment>()._hls_data->getMediaSource();
|
auto &attach = cookie->getAttach<HttpCookieAttachment>();
|
||||||
|
auto src = attach._hls_data->getMediaSource();
|
||||||
if (src) {
|
if (src) {
|
||||||
//直接从内存获取m3u8索引文件(而不是从文件系统)
|
// 直接从内存获取m3u8索引文件(而不是从文件系统)
|
||||||
response_file(cookie, cb, file_path, parser, src->getIndexFile());
|
response_file(cookie, cb, file_path, parser, src->getIndexFile());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cookie->getAttach<HttpCookieAttachment>()._find_src) {
|
if (attach._find_src && attach._find_src_ticker.elapsedTime() < kFindSrcIntervalSecond * 1000) {
|
||||||
//查找过MediaSource,但是流已经注销了,不用再查找
|
// 最近已经查找过MediaSource了,为了防止频繁查找导致占用全局互斥锁的问题,我们尝试直接从磁盘返回hls索引文件
|
||||||
response_file(cookie, cb, file_path, parser);
|
response_file(cookie, cb, file_path, parser);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -444,11 +561,14 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
|
||||||
|
|
||||||
auto &attach = cookie->getAttach<HttpCookieAttachment>();
|
auto &attach = cookie->getAttach<HttpCookieAttachment>();
|
||||||
attach._hls_data->setMediaSource(hls);
|
attach._hls_data->setMediaSource(hls);
|
||||||
//添加HlsMediaSource的观看人数(HLS是按需生成的,这样可以触发HLS文件的生成)
|
// 添加HlsMediaSource的观看人数(HLS是按需生成的,这样可以触发HLS文件的生成)
|
||||||
attach._hls_data->addByteUsage(0);
|
attach._hls_data->addByteUsage(0);
|
||||||
//标记找到MediaSource
|
// 标记找到MediaSource
|
||||||
attach._find_src = true;
|
attach._find_src = true;
|
||||||
|
|
||||||
|
// 重置查找MediaSource计时
|
||||||
|
attach._find_src_ticker.resetTime();
|
||||||
|
|
||||||
// m3u8文件可能不存在, 等待m3u8索引文件按需生成
|
// m3u8文件可能不存在, 等待m3u8索引文件按需生成
|
||||||
hls->getIndexFile([response_file, file_path, cookie, cb, parser](const string &file) {
|
hls->getIndexFile([response_file, file_path, cookie, cb, parser](const string &file) {
|
||||||
response_file(cookie, cb, file_path, parser, 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(bool, enableVhost, General::kEnableVhost);
|
||||||
GET_CONFIG(string, rootPath, Http::kRootPath);
|
GET_CONFIG(string, rootPath, Http::kRootPath);
|
||||||
GET_CONFIG_FUNC(StrCaseMap, virtualPathMap, Http::kVirtualPath, [](const string &str) {
|
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()) {
|
if (it != virtualPathMap.end()) {
|
||||||
//访问的是virtualPath
|
//访问的是virtualPath
|
||||||
path = it->second;
|
path = it->second;
|
||||||
url = parser.Url().substr(1 + media_info.app.size());
|
url = parser.url().substr(1 + media_info.app.size());
|
||||||
} else {
|
} else {
|
||||||
//访问的是rootPath
|
//访问的是rootPath
|
||||||
path = rootPath;
|
path = rootPath;
|
||||||
url = parser.Url();
|
url = parser.url();
|
||||||
}
|
}
|
||||||
for (auto &ch : url) {
|
for (auto &ch : url) {
|
||||||
if (ch == '\\') {
|
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);
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -493,7 +620,7 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess
|
||||||
* @param cb 回调对象
|
* @param cb 回调对象
|
||||||
*/
|
*/
|
||||||
void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFileManager::invoker &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);
|
MediaInfo media_info(fullUrl);
|
||||||
auto file_path = getFilePath(parser, media_info, sender);
|
auto file_path = getFilePath(parser, media_info, sender);
|
||||||
if (file_path.size() == 0) {
|
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())) {
|
if (File::is_dir(file_path.data())) {
|
||||||
auto indexFile = searchIndexFile(file_path);
|
auto indexFile = searchIndexFile(file_path);
|
||||||
if (!indexFile.empty()) {
|
if (!indexFile.empty()) {
|
||||||
//发现该文件夹下有index文件
|
// 发现该文件夹下有index文件
|
||||||
file_path = pathCat(file_path, indexFile);
|
file_path = pathCat(file_path, indexFile);
|
||||||
parser.setUrl(pathCat(parser.Url(), indexFile));
|
if (!File::is_dir(file_path.data())) {
|
||||||
accessFile(sender, parser, media_info, file_path, cb);
|
// 不是文件夹
|
||||||
return;
|
parser.setUrl(pathCat(parser.url(), indexFile));
|
||||||
|
accessFile(sender, parser, media_info, file_path, cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
string strMenu;
|
string strMenu;
|
||||||
//生成文件夹菜单索引
|
//生成文件夹菜单索引
|
||||||
if (!makeFolderMenu(parser.Url(), file_path, strMenu)) {
|
if (!makeFolderMenu(parser.url(), file_path, strMenu)) {
|
||||||
//文件夹不存在
|
//文件夹不存在
|
||||||
sendNotFound(cb);
|
sendNotFound(cb);
|
||||||
return;
|
return;
|
||||||
|
|
@ -600,8 +730,8 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader,
|
||||||
if (!strRange.empty()) {
|
if (!strRange.empty()) {
|
||||||
//分节下载
|
//分节下载
|
||||||
code = 206;
|
code = 206;
|
||||||
auto iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data());
|
auto iRangeStart = atoll(findSubString(strRange.data(), "bytes=", "-").data());
|
||||||
auto iRangeEnd = atoll(FindField(strRange.data(), "-", nullptr).data());
|
auto iRangeEnd = atoll(findSubString(strRange.data(), "-", nullptr).data());
|
||||||
auto fileSize = fileBody->remainSize();
|
auto fileSize = fileBody->remainSize();
|
||||||
if (iRangeEnd == 0) {
|
if (iRangeEnd == 0) {
|
||||||
iRangeEnd = fileSize - 1;
|
iRangeEnd = fileSize - 1;
|
||||||
|
|
@ -621,4 +751,4 @@ HttpResponseInvokerImp::operator bool(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}//namespace mediakit
|
}//namespace mediakit
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,13 @@ public:
|
||||||
* @return mime值
|
* @return mime值
|
||||||
*/
|
*/
|
||||||
static const std::string &getContentType(const char *name);
|
static const std::string &getContentType(const char *name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 该ip是否再白名单中
|
||||||
|
* @param ip 支持ipv4和ipv6
|
||||||
|
*/
|
||||||
|
static bool isIPAllowed(const std::string &ip);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HttpFileManager() = delete;
|
HttpFileManager() = delete;
|
||||||
~HttpFileManager() = delete;
|
~HttpFileManager() = delete;
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ void HttpRequestSplitter::input(const char *data,size_t len) {
|
||||||
_remain_data.assign(ptr, _remain_data_size);
|
_remain_data.assign(ptr, _remain_data_size);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//收到content数据,并且接受content完毕
|
//收到content数据,并且接收content完毕
|
||||||
onRecvContent(ptr,_content_len);
|
onRecvContent(ptr,_content_len);
|
||||||
|
|
||||||
_remain_data_size -= _content_len;
|
_remain_data_size -= _content_len;
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,7 @@ static void sendReport() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static toolkit::onceToken s_token([]() {
|
static toolkit::onceToken s_token([]() {
|
||||||
NoticeCenter::Instance().addListener(nullptr, "kBroadcastEventPollerPoolStarted", [](EventPollerPool &pool, size_t &size) {
|
NoticeCenter::Instance().addListener(nullptr, "kBroadcastEventPollerPoolStarted", [](EventPollerPoolOnStartedArgs) {
|
||||||
// 第一次汇报在程序启动后5分钟
|
// 第一次汇报在程序启动后5分钟
|
||||||
pool.getPoller()->doDelayTask(5 * 60 * 1000, []() {
|
pool.getPoller()->doDelayTask(5 * 60 * 1000, []() {
|
||||||
sendReport();
|
sendReport();
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include "Common/config.h"
|
#include "Common/config.h"
|
||||||
#include "strCoding.h"
|
#include "Common/strCoding.h"
|
||||||
#include "HttpSession.h"
|
#include "HttpSession.h"
|
||||||
#include "HttpConst.h"
|
#include "HttpConst.h"
|
||||||
#include "Util/base64.h"
|
#include "Util/base64.h"
|
||||||
|
|
@ -24,20 +24,20 @@ using namespace toolkit;
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
HttpSession::HttpSession(const Socket::Ptr &pSock) : Session(pSock) {
|
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);
|
pSock->setSendTimeOutSecond(keep_alive_sec);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpSession::~HttpSession() = default;
|
HttpSession::~HttpSession() = default;
|
||||||
|
|
||||||
void HttpSession::Handle_Req_HEAD(ssize_t &content_len){
|
void HttpSession::onHttpRequest_HEAD() {
|
||||||
//暂时全部返回200 OK,因为HTTP GET存在按需生成流的操作,所以不能按照HTTP GET的流程返回
|
// 暂时全部返回200 OK,因为HTTP GET存在按需生成流的操作,所以不能按照HTTP GET的流程返回
|
||||||
//如果直接返回404,那么又会导致按需生成流的逻辑失效,所以HTTP HEAD在静态文件或者已存在资源时才有效
|
// 如果直接返回404,那么又会导致按需生成流的逻辑失效,所以HTTP HEAD在静态文件或者已存在资源时才有效
|
||||||
//对于按需生成流的直播场景并不适用
|
// 对于按需生成流的直播场景并不适用
|
||||||
sendResponse(200, false);
|
sendResponse(200, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::Handle_Req_OPTIONS(ssize_t &content_len) {
|
void HttpSession::onHttpRequest_OPTIONS() {
|
||||||
KeyValue header;
|
KeyValue header;
|
||||||
header.emplace("Allow", "GET, POST, HEAD, OPTIONS");
|
header.emplace("Allow", "GET, POST, HEAD, OPTIONS");
|
||||||
GET_CONFIG(bool, allow_cross_domains, Http::kAllowCrossDomains);
|
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);
|
sendResponse(200, true, nullptr, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t HttpSession::onRecvHeader(const char *header,size_t len) {
|
ssize_t HttpSession::onRecvHeader(const char *header, size_t len) {
|
||||||
typedef void (HttpSession::*HttpCMDHandle)(ssize_t &);
|
using func_type = void (HttpSession::*)();
|
||||||
static unordered_map<string, HttpCMDHandle> s_func_map;
|
static unordered_map<string, func_type> s_func_map;
|
||||||
static onceToken token([]() {
|
static onceToken token([]() {
|
||||||
s_func_map.emplace("GET",&HttpSession::Handle_Req_GET);
|
s_func_map.emplace("GET", &HttpSession::onHttpRequest_GET);
|
||||||
s_func_map.emplace("DELETE",&HttpSession::Handle_Req_GET);
|
s_func_map.emplace("POST", &HttpSession::onHttpRequest_POST);
|
||||||
s_func_map.emplace("POST",&HttpSession::Handle_Req_POST);
|
// DELETE命令用于whip/whep用,只用于触发http api
|
||||||
s_func_map.emplace("HEAD",&HttpSession::Handle_Req_HEAD);
|
s_func_map.emplace("DELETE", &HttpSession::onHttpRequest_POST);
|
||||||
s_func_map.emplace("OPTIONS",&HttpSession::Handle_Req_OPTIONS);
|
s_func_map.emplace("HEAD", &HttpSession::onHttpRequest_HEAD);
|
||||||
}, nullptr);
|
s_func_map.emplace("OPTIONS", &HttpSession::onHttpRequest_OPTIONS);
|
||||||
|
});
|
||||||
|
|
||||||
_parser.Parse(header);
|
_parser.parse(header, len);
|
||||||
CHECK(_parser.Url()[0] == '/');
|
CHECK(_parser.url()[0] == '/');
|
||||||
|
|
||||||
urlDecode(_parser);
|
urlDecode(_parser);
|
||||||
string cmd = _parser.Method();
|
auto &cmd = _parser.method();
|
||||||
auto it = s_func_map.find(cmd);
|
auto it = s_func_map.find(cmd);
|
||||||
if (it == s_func_map.end()) {
|
if (it == s_func_map.end()) {
|
||||||
WarnP(this) << "不支持该命令:" << cmd;
|
WarnP(this) << "Http method not supported: " << cmd;
|
||||||
sendResponse(405, true);
|
sendResponse(405, true);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
//跨域
|
size_t content_len;
|
||||||
_origin = _parser["Origin"];
|
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
|
if (content_len == 0) {
|
||||||
ssize_t content_len = 0;
|
//// 没有body的情况,直接触发回调 ////
|
||||||
(this->*(it->second))(content_len);
|
(this->*(it->second))();
|
||||||
|
_parser.clear();
|
||||||
|
// 如果设置了_on_recv_body, 那么说明后续要处理body
|
||||||
|
return _on_recv_body ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
//清空解析器节省内存
|
GET_CONFIG(size_t, maxReqSize, Http::kMaxReqSize);
|
||||||
_parser.Clear();
|
if (content_len > maxReqSize) {
|
||||||
//返回content长度
|
//// 不定长body或超大body ////
|
||||||
return content_len;
|
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) {
|
void HttpSession::onRecvContent(const char *data, size_t len) {
|
||||||
if(_contentCallBack){
|
if (_on_recv_body && !_on_recv_body(data, len)) {
|
||||||
if(!_contentCallBack(data,len)){
|
_on_recv_body = nullptr;
|
||||||
_contentCallBack = nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::onRecv(const Buffer::Ptr &pBuf) {
|
void HttpSession::onRecv(const Buffer::Ptr &pBuf) {
|
||||||
_ticker.resetTime();
|
_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) {
|
if (_is_live_stream) {
|
||||||
//flv/ts播放器
|
// flv/ts播放器
|
||||||
uint64_t duration = _ticker.createdTime() / 1000;
|
uint64_t duration = _ticker.createdTime() / 1000;
|
||||||
WarnP(this) << "FLV/TS/FMP4播放器("
|
WarnP(this) << "FLV/TS/FMP4播放器(" << _mediaInfo.shortUrl() << ")断开:" << err << ",耗时(s):" << duration;
|
||||||
<< _mediaInfo.shortUrl()
|
|
||||||
<< ")断开:" << err
|
|
||||||
<< ",耗时(s):" << duration;
|
|
||||||
|
|
||||||
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
|
GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold);
|
||||||
if (_total_bytes_usage >= iFlowThreshold * 1024) {
|
if (_total_bytes_usage >= iFlowThreshold * 1024) {
|
||||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage,
|
NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration, true, *this);
|
||||||
duration, true, static_cast<SockInfo &>(*this));
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::onManager() {
|
void HttpSession::onManager() {
|
||||||
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
|
GET_CONFIG(uint32_t, keepAliveSec, Http::kKeepAliveSecond);
|
||||||
|
|
||||||
if(_ticker.elapsedTime() > keepAliveSec * 1000){
|
if (_ticker.elapsedTime() > keepAliveSec * 1000) {
|
||||||
//1分钟超时
|
// 1分钟超时
|
||||||
shutdown(SockException(Err_timeout,"session timeout"));
|
shutdown(SockException(Err_timeout, "session timeout"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpSession::checkWebSocket(){
|
bool HttpSession::checkWebSocket() {
|
||||||
auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"];
|
auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"];
|
||||||
if (Sec_WebSocket_Key.empty()) {
|
if (Sec_WebSocket_Key.empty()) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -147,32 +204,32 @@ bool HttpSession::checkWebSocket(){
|
||||||
_live_over_websocket = true;
|
_live_over_websocket = true;
|
||||||
sendResponse(101, false, nullptr, headerOut, nullptr, true);
|
sendResponse(101, false, nullptr, headerOut, nullptr, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto res_cb_flv = [this, header = std::move(headerOut)]() mutable {
|
auto res_cb_flv = [this, headerOut]() mutable {
|
||||||
_live_over_websocket = true;
|
_live_over_websocket = true;
|
||||||
header.emplace("Cache-Control", "no-store");
|
headerOut.emplace("Cache-Control", "no-store");
|
||||||
sendResponse(101, false, nullptr, header, nullptr, true);
|
sendResponse(101, false, nullptr, headerOut, nullptr, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
//判断是否为websocket-flv
|
// 判断是否为websocket-flv
|
||||||
if (checkLiveStreamFlv(res_cb_flv)) {
|
if (checkLiveStreamFlv(res_cb_flv)) {
|
||||||
//这里是websocket-flv直播请求
|
// 这里是websocket-flv直播请求
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//判断是否为websocket-ts
|
// 判断是否为websocket-ts
|
||||||
if (checkLiveStreamTS(res_cb)) {
|
if (checkLiveStreamTS(res_cb)) {
|
||||||
//这里是websocket-ts直播请求
|
// 这里是websocket-ts直播请求
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//判断是否为websocket-fmp4
|
// 判断是否为websocket-fmp4
|
||||||
if (checkLiveStreamFMP4(res_cb)) {
|
if (checkLiveStreamFMP4(res_cb)) {
|
||||||
//这里是websocket-fmp4直播请求
|
// 这里是websocket-fmp4直播请求
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//这是普通的websocket连接
|
// 这是普通的websocket连接
|
||||||
if (!onWebSocketConnect(_parser)) {
|
if (!onWebSocketConnect(_parser)) {
|
||||||
sendResponse(501, true, nullptr, headerOut);
|
sendResponse(501, true, nullptr, headerOut);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -181,8 +238,8 @@ bool HttpSession::checkWebSocket(){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix, const function<void(const MediaSource::Ptr &src)> &cb){
|
bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix, const function<void(const MediaSource::Ptr &src)> &cb) {
|
||||||
std::string url = _parser.Url();
|
std::string url = _parser.url();
|
||||||
auto it = _parser.getUrlArgs().find("schema");
|
auto it = _parser.getUrlArgs().find("schema");
|
||||||
if (it != _parser.getUrlArgs().end()) {
|
if (it != _parser.getUrlArgs().end()) {
|
||||||
if (strcasecmp(it->second.c_str(), schema.c_str())) {
|
if (strcasecmp(it->second.c_str(), schema.c_str())) {
|
||||||
|
|
@ -192,57 +249,57 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffi
|
||||||
} else {
|
} else {
|
||||||
auto prefix_size = url_suffix.size();
|
auto prefix_size = url_suffix.size();
|
||||||
if (url.size() < prefix_size || strcasecmp(url.data() + (url.size() - prefix_size), url_suffix.data())) {
|
if (url.size() < prefix_size || strcasecmp(url.data() + (url.size() - prefix_size), url_suffix.data())) {
|
||||||
//未找到后缀
|
// 未找到后缀
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// url去除特殊后缀
|
// url去除特殊后缀
|
||||||
url.resize(url.size() - prefix_size);
|
url.resize(url.size() - prefix_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
//带参数的url
|
// 带参数的url
|
||||||
if (!_parser.Params().empty()) {
|
if (!_parser.params().empty()) {
|
||||||
url += "?";
|
url += "?";
|
||||||
url += _parser.Params();
|
url += _parser.params();
|
||||||
}
|
}
|
||||||
|
|
||||||
//解析带上协议+参数完整的url
|
// 解析带上协议+参数完整的url
|
||||||
_mediaInfo.parse(schema + "://" + _parser["Host"] + url);
|
_mediaInfo.parse(schema + "://" + _parser["Host"] + url);
|
||||||
|
|
||||||
if (_mediaInfo.app.empty() || _mediaInfo.stream.empty()) {
|
if (_mediaInfo.app.empty() || _mediaInfo.stream.empty()) {
|
||||||
//url不合法
|
// url不合法
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool close_flag = !strcasecmp(_parser["Connection"].data(), "close");
|
bool close_flag = !strcasecmp(_parser["Connection"].data(), "close");
|
||||||
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
||||||
|
|
||||||
//鉴权结果回调
|
// 鉴权结果回调
|
||||||
auto onRes = [cb, weak_self, close_flag](const string &err) {
|
auto onRes = [cb, weak_self, close_flag](const string &err) {
|
||||||
auto strong_self = weak_self.lock();
|
auto strong_self = weak_self.lock();
|
||||||
if (!strong_self) {
|
if (!strong_self) {
|
||||||
//本对象已经销毁
|
// 本对象已经销毁
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!err.empty()) {
|
if (!err.empty()) {
|
||||||
//播放鉴权失败
|
// 播放鉴权失败
|
||||||
strong_self->sendResponse(401, close_flag, nullptr, KeyValue(), std::make_shared<HttpStringBody>(err));
|
strong_self->sendResponse(401, close_flag, nullptr, KeyValue(), std::make_shared<HttpStringBody>(err));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//异步查找直播流
|
// 异步查找直播流
|
||||||
MediaSource::findAsync(strong_self->_mediaInfo, strong_self, [weak_self, close_flag, cb](const MediaSource::Ptr &src) {
|
MediaSource::findAsync(strong_self->_mediaInfo, strong_self, [weak_self, close_flag, cb](const MediaSource::Ptr &src) {
|
||||||
auto strong_self = weak_self.lock();
|
auto strong_self = weak_self.lock();
|
||||||
if (!strong_self) {
|
if (!strong_self) {
|
||||||
//本对象已经销毁
|
// 本对象已经销毁
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!src) {
|
if (!src) {
|
||||||
//未找到该流
|
// 未找到该流
|
||||||
strong_self->sendNotFound(close_flag);
|
strong_self->sendNotFound(close_flag);
|
||||||
} else {
|
} else {
|
||||||
strong_self->_is_live_stream = true;
|
strong_self->_is_live_stream = true;
|
||||||
//触发回调
|
// 触发回调
|
||||||
cb(src);
|
cb(src);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -254,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) {
|
if (!flag) {
|
||||||
//该事件无人监听,默认不鉴权
|
// 该事件无人监听,默认不鉴权
|
||||||
onRes("");
|
onRes("");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//http-fmp4 链接格式:http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2
|
// http-fmp4 链接格式:http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2
|
||||||
bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb){
|
bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb) {
|
||||||
return checkLiveStream(FMP4_SCHEMA, ".live.mp4", [this, cb](const MediaSource::Ptr &src) {
|
return checkLiveStream(FMP4_SCHEMA, ".live.mp4", [this, cb](const MediaSource::Ptr &src) {
|
||||||
auto fmp4_src = dynamic_pointer_cast<FMP4MediaSource>(src);
|
auto fmp4_src = dynamic_pointer_cast<FMP4MediaSource>(src);
|
||||||
assert(fmp4_src);
|
assert(fmp4_src);
|
||||||
if (!cb) {
|
if (!cb) {
|
||||||
//找到源,发送http头,负载后续发送
|
// 找到源,发送http头,负载后续发送
|
||||||
sendResponse(200, false, HttpFileManager::getContentType(".mp4").data(), KeyValue(), nullptr, true);
|
sendResponse(200, false, HttpFileManager::getContentType(".mp4").data(), KeyValue(), nullptr, true);
|
||||||
} else {
|
} else {
|
||||||
//自定义发送http头
|
// 自定义发送http头
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
//直播牺牲延时提升发送性能
|
// 直播牺牲延时提升发送性能
|
||||||
setSocketFlags();
|
setSocketFlags();
|
||||||
onWrite(std::make_shared<BufferString>(fmp4_src->getInitSegment()), true);
|
onWrite(std::make_shared<BufferString>(fmp4_src->getInitSegment()), true);
|
||||||
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
||||||
fmp4_src->pause(false);
|
fmp4_src->pause(false);
|
||||||
_fmp4_reader = fmp4_src->getRing()->attach(getPoller());
|
_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]() {
|
_fmp4_reader->setDetachCB([weak_self]() {
|
||||||
auto strong_self = weak_self.lock();
|
auto strong_self = weak_self.lock();
|
||||||
if (!strong_self) {
|
if (!strong_self) {
|
||||||
//本对象已经销毁
|
// 本对象已经销毁
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
strong_self->shutdown(SockException(Err_shutdown, "fmp4 ring buffer detached"));
|
strong_self->shutdown(SockException(Err_shutdown, "fmp4 ring buffer detached"));
|
||||||
|
|
@ -293,85 +354,84 @@ bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb){
|
||||||
_fmp4_reader->setReadCB([weak_self](const FMP4MediaSource::RingDataType &fmp4_list) {
|
_fmp4_reader->setReadCB([weak_self](const FMP4MediaSource::RingDataType &fmp4_list) {
|
||||||
auto strong_self = weak_self.lock();
|
auto strong_self = weak_self.lock();
|
||||||
if (!strong_self) {
|
if (!strong_self) {
|
||||||
//本对象已经销毁
|
// 本对象已经销毁
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
auto size = fmp4_list->size();
|
auto size = fmp4_list->size();
|
||||||
fmp4_list->for_each([&](const FMP4Packet::Ptr &ts) {
|
fmp4_list->for_each([&](const FMP4Packet::Ptr &ts) { strong_self->onWrite(ts, ++i == size); });
|
||||||
strong_self->onWrite(ts, ++i == size);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//http-ts 链接格式:http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2
|
// http-ts 链接格式:http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2
|
||||||
bool HttpSession::checkLiveStreamTS(const function<void()> &cb){
|
bool HttpSession::checkLiveStreamTS(const function<void()> &cb) {
|
||||||
return checkLiveStream(TS_SCHEMA, ".live.ts", [this, cb](const MediaSource::Ptr &src) {
|
return checkLiveStream(TS_SCHEMA, ".live.ts", [this, cb](const MediaSource::Ptr &src) {
|
||||||
auto ts_src = dynamic_pointer_cast<TSMediaSource>(src);
|
auto ts_src = dynamic_pointer_cast<TSMediaSource>(src);
|
||||||
assert(ts_src);
|
assert(ts_src);
|
||||||
if (!cb) {
|
if (!cb) {
|
||||||
//找到源,发送http头,负载后续发送
|
// 找到源,发送http头,负载后续发送
|
||||||
sendResponse(200, false, HttpFileManager::getContentType(".ts").data(), KeyValue(), nullptr, true);
|
sendResponse(200, false, HttpFileManager::getContentType(".ts").data(), KeyValue(), nullptr, true);
|
||||||
} else {
|
} else {
|
||||||
//自定义发送http头
|
// 自定义发送http头
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
//直播牺牲延时提升发送性能
|
// 直播牺牲延时提升发送性能
|
||||||
setSocketFlags();
|
setSocketFlags();
|
||||||
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
||||||
ts_src->pause(false);
|
ts_src->pause(false);
|
||||||
_ts_reader = ts_src->getRing()->attach(getPoller());
|
_ts_reader = ts_src->getRing()->attach(getPoller());
|
||||||
_ts_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); });
|
_ts_reader->setGetInfoCB([weak_self]() {
|
||||||
_ts_reader->setDetachCB([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();
|
auto strong_self = weak_self.lock();
|
||||||
if (!strong_self) {
|
if (!strong_self) {
|
||||||
//本对象已经销毁
|
// 本对象已经销毁
|
||||||
return;
|
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) {
|
_ts_reader->setReadCB([weak_self](const TSMediaSource::RingDataType &ts_list) {
|
||||||
auto strong_self = weak_self.lock();
|
auto strong_self = weak_self.lock();
|
||||||
if (!strong_self) {
|
if (!strong_self) {
|
||||||
//本对象已经销毁
|
// 本对象已经销毁
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
auto size = ts_list->size();
|
auto size = ts_list->size();
|
||||||
ts_list->for_each([&](const TSPacket::Ptr &ts) {
|
ts_list->for_each([&](const TSPacket::Ptr &ts) { strong_self->onWrite(ts, ++i == size); });
|
||||||
strong_self->onWrite(ts, ++i == size);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//http-flv 链接格式:http://vhost-url:port/app/streamid.live.flv?key1=value1&key2=value2
|
// http-flv 链接格式:http://vhost-url:port/app/streamid.live.flv?key1=value1&key2=value2
|
||||||
bool HttpSession::checkLiveStreamFlv(const function<void()> &cb){
|
bool HttpSession::checkLiveStreamFlv(const function<void()> &cb) {
|
||||||
auto start_pts = atoll(_parser.getUrlArgs()["starPts"].data());
|
auto start_pts = atoll(_parser.getUrlArgs()["starPts"].data());
|
||||||
return checkLiveStream(RTMP_SCHEMA, ".live.flv", [this, cb, start_pts](const MediaSource::Ptr &src) {
|
return checkLiveStream(RTMP_SCHEMA, ".live.flv", [this, cb, start_pts](const MediaSource::Ptr &src) {
|
||||||
auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
|
auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
|
||||||
assert(rtmp_src);
|
assert(rtmp_src);
|
||||||
if (!cb) {
|
if (!cb) {
|
||||||
//找到源,发送http头,负载后续发送
|
// 找到源,发送http头,负载后续发送
|
||||||
KeyValue headerOut;
|
KeyValue headerOut;
|
||||||
headerOut["Cache-Control"] = "no-store";
|
headerOut["Cache-Control"] = "no-store";
|
||||||
sendResponse(200, false, HttpFileManager::getContentType(".flv").data(), headerOut, nullptr, true);
|
sendResponse(200, false, HttpFileManager::getContentType(".flv").data(), headerOut, nullptr, true);
|
||||||
} else {
|
} else {
|
||||||
//自定义发送http头
|
// 自定义发送http头
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
//直播牺牲延时提升发送性能
|
// 直播牺牲延时提升发送性能
|
||||||
setSocketFlags();
|
setSocketFlags();
|
||||||
|
|
||||||
//非H264/AAC时打印警告日志,防止用户提无效问题
|
// 非H264/AAC时打印警告日志,防止用户提无效问题
|
||||||
auto tracks = src->getTracks(false);
|
auto tracks = src->getTracks(false);
|
||||||
for (auto &track : tracks) {
|
for (auto &track : tracks) {
|
||||||
switch (track->getCodecId()) {
|
switch (track->getCodecId()) {
|
||||||
case CodecH264:
|
case CodecH264:
|
||||||
case CodecAAC:
|
case CodecAAC: break;
|
||||||
break;
|
|
||||||
default: {
|
default: {
|
||||||
WarnP(this) << "flv播放器一般只支持H264和AAC编码,该编码格式可能不被播放器支持:" << track->getCodecName();
|
WarnP(this) << "flv播放器一般只支持H264和AAC编码,该编码格式可能不被播放器支持:" << track->getCodecName();
|
||||||
break;
|
break;
|
||||||
|
|
@ -383,46 +443,42 @@ bool HttpSession::checkLiveStreamFlv(const function<void()> &cb){
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::Handle_Req_GET(ssize_t &content_len) {
|
void HttpSession::onHttpRequest_GET() {
|
||||||
Handle_Req_GET_l(content_len, true);
|
// 先看看是否为WebSocket请求
|
||||||
}
|
|
||||||
|
|
||||||
void HttpSession::Handle_Req_GET_l(ssize_t &content_len, bool sendBody) {
|
|
||||||
//先看看是否为WebSocket请求
|
|
||||||
if (checkWebSocket()) {
|
if (checkWebSocket()) {
|
||||||
content_len = -1;
|
// 后续都是websocket body数据
|
||||||
_contentCallBack = [this](const char *data, size_t len) {
|
_on_recv_body = [this](const char *data, size_t len) {
|
||||||
WebSocketSplitter::decode((uint8_t *) data, len);
|
WebSocketSplitter::decode((uint8_t *)data, len);
|
||||||
//_contentCallBack是可持续的,后面还要处理后续数据
|
// _contentCallBack是可持续的,后面还要处理后续数据
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (emitHttpEvent(false)) {
|
if (emitHttpEvent(false)) {
|
||||||
//拦截http api事件
|
// 拦截http api事件
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkLiveStreamFlv()) {
|
if (checkLiveStreamFlv()) {
|
||||||
//拦截http-flv播放器
|
// 拦截http-flv播放器
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkLiveStreamTS()) {
|
if (checkLiveStreamTS()) {
|
||||||
//拦截http-ts播放器
|
// 拦截http-ts播放器
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkLiveStreamFMP4()) {
|
if (checkLiveStreamFMP4()) {
|
||||||
//拦截http-fmp4播放器
|
// 拦截http-fmp4播放器
|
||||||
return;
|
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());
|
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,
|
HttpFileManager::onAccessPath(*this, _parser, [weak_self, bClose](int code, const string &content_type,
|
||||||
const StrCaseMap &responseHeader, const HttpBody::Ptr &body) {
|
const StrCaseMap &responseHeader, const HttpBody::Ptr &body) {
|
||||||
auto strong_self = weak_self.lock();
|
auto strong_self = weak_self.lock();
|
||||||
if (!strong_self) {
|
if (!strong_self) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -468,7 +524,7 @@ public:
|
||||||
static bool onSocketFlushed(const AsyncSenderData::Ptr &data) {
|
static bool onSocketFlushed(const AsyncSenderData::Ptr &data) {
|
||||||
if (data->_read_complete) {
|
if (data->_read_complete) {
|
||||||
if (data->_close_when_complete) {
|
if (data->_close_when_complete) {
|
||||||
//发送完毕需要关闭socket
|
// 发送完毕需要关闭socket
|
||||||
shutdown(data->_session.lock());
|
shutdown(data->_session.lock());
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -478,13 +534,13 @@ public:
|
||||||
data->_body->readDataAsync(sendBufSize, [data](const Buffer::Ptr &sendBuf) {
|
data->_body->readDataAsync(sendBufSize, [data](const Buffer::Ptr &sendBuf) {
|
||||||
auto session = data->_session.lock();
|
auto session = data->_session.lock();
|
||||||
if (!session) {
|
if (!session) {
|
||||||
//本对象已经销毁
|
// 本对象已经销毁
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
session->async([data, sendBuf]() {
|
session->async([data, sendBuf]() {
|
||||||
auto session = data->_session.lock();
|
auto session = data->_session.lock();
|
||||||
if (!session) {
|
if (!session) {
|
||||||
//本对象已经销毁
|
// 本对象已经销毁
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onRequestData(data, session, sendBuf);
|
onRequestData(data, session, sendBuf);
|
||||||
|
|
@ -497,14 +553,14 @@ private:
|
||||||
static void onRequestData(const AsyncSenderData::Ptr &data, const std::shared_ptr<HttpSession> &session, const Buffer::Ptr &sendBuf) {
|
static void onRequestData(const AsyncSenderData::Ptr &data, const std::shared_ptr<HttpSession> &session, const Buffer::Ptr &sendBuf) {
|
||||||
session->_ticker.resetTime();
|
session->_ticker.resetTime();
|
||||||
if (sendBuf && session->send(sendBuf) != -1) {
|
if (sendBuf && session->send(sendBuf) != -1) {
|
||||||
//文件还未读完,还需要继续发送
|
// 文件还未读完,还需要继续发送
|
||||||
if (!session->isSocketBusy()) {
|
if (!session->isSocketBusy()) {
|
||||||
//socket还可写,继续请求数据
|
// socket还可写,继续请求数据
|
||||||
onSocketFlushed(data);
|
onSocketFlushed(data);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//文件写完了
|
// 文件写完了
|
||||||
data->_read_complete = true;
|
data->_read_complete = true;
|
||||||
if (!session->isSocketBusy() && data->_close_when_complete) {
|
if (!session->isSocketBusy() && data->_close_when_complete) {
|
||||||
shutdown(session);
|
shutdown(session);
|
||||||
|
|
@ -512,34 +568,25 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
static void shutdown(const std::shared_ptr<HttpSession> &session) {
|
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."));
|
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,
|
void HttpSession::sendResponse(int code,
|
||||||
bool bClose,
|
bool bClose,
|
||||||
const char *pcContentType,
|
const char *pcContentType,
|
||||||
const HttpSession::KeyValue &header,
|
const HttpSession::KeyValue &header,
|
||||||
const HttpBody::Ptr &body,
|
const HttpBody::Ptr &body,
|
||||||
bool no_content_length ){
|
bool no_content_length) {
|
||||||
GET_CONFIG(string,charSet,Http::kCharSet);
|
GET_CONFIG(string, charSet, Http::kCharSet);
|
||||||
GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond);
|
GET_CONFIG(uint32_t, keepAliveSec, Http::kKeepAliveSecond);
|
||||||
|
|
||||||
//body默认为空
|
// body默认为空
|
||||||
int64_t size = 0;
|
int64_t size = 0;
|
||||||
if (body && body->remainSize()) {
|
if (body && body->remainSize()) {
|
||||||
//有body,获取body大小
|
// 有body,获取body大小
|
||||||
size = body->remainSize();
|
size = body->remainSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -547,52 +594,53 @@ void HttpSession::sendResponse(int code,
|
||||||
// http-flv直播是Keep-Alive类型
|
// http-flv直播是Keep-Alive类型
|
||||||
bClose = false;
|
bClose = false;
|
||||||
} else if ((size_t)size >= SIZE_MAX || size < 0) {
|
} else if ((size_t)size >= SIZE_MAX || size < 0) {
|
||||||
//不固定长度的body,那么发送完body后应该关闭socket,以便浏览器做下载完毕的判断
|
// 不固定长度的body,那么发送完body后应该关闭socket,以便浏览器做下载完毕的判断
|
||||||
bClose = true;
|
bClose = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpSession::KeyValue &headerOut = const_cast<HttpSession::KeyValue &>(header);
|
HttpSession::KeyValue &headerOut = const_cast<HttpSession::KeyValue &>(header);
|
||||||
headerOut.emplace(kDate, dateStr());
|
headerOut.emplace("Date", dateStr());
|
||||||
headerOut.emplace(kServer, kServerName);
|
headerOut.emplace("Server", kServerName);
|
||||||
headerOut.emplace(kConnection, bClose ? "close" : "keep-alive");
|
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) {
|
if (!bClose) {
|
||||||
string keepAliveString = "timeout=";
|
string keepAliveString = "timeout=";
|
||||||
keepAliveString += to_string(keepAliveSec);
|
keepAliveString += to_string(keepAliveSec);
|
||||||
keepAliveString += ", max=100";
|
keepAliveString += ", max=100";
|
||||||
headerOut.emplace(kKeepAlive, std::move(keepAliveString));
|
headerOut.emplace("Keep-Alive", std::move(keepAliveString));
|
||||||
}
|
|
||||||
|
|
||||||
if (!_origin.empty()) {
|
|
||||||
//设置跨域
|
|
||||||
headerOut.emplace(kAccessControlAllowOrigin, _origin);
|
|
||||||
headerOut.emplace(kAccessControlAllowCredentials, "true");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!no_content_length && size >= 0 && (size_t)size < SIZE_MAX) {
|
if (!no_content_length && size >= 0 && (size_t)size < SIZE_MAX) {
|
||||||
//文件长度为固定值,且不是http-flv强制设置Content-Length
|
// 文件长度为固定值,且不是http-flv强制设置Content-Length
|
||||||
headerOut[kContentLength] = to_string(size);
|
headerOut["Content-Length"] = to_string(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size && !pcContentType) {
|
if (size && !pcContentType) {
|
||||||
//有body时,设置缺省类型
|
// 有body时,设置缺省类型
|
||||||
pcContentType = "text/plain";
|
pcContentType = "text/plain";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((size || no_content_length) && pcContentType) {
|
if ((size || no_content_length) && pcContentType) {
|
||||||
//有body时,设置文件类型
|
// 有body时,设置文件类型
|
||||||
string strContentType = pcContentType;
|
string strContentType = pcContentType;
|
||||||
strContentType += "; charset=";
|
strContentType += "; charset=";
|
||||||
strContentType += charSet;
|
strContentType += charSet;
|
||||||
headerOut.emplace(kContentType, std::move(strContentType));
|
headerOut.emplace("Content-Type", std::move(strContentType));
|
||||||
}
|
}
|
||||||
|
|
||||||
//发送http头
|
// 发送http头
|
||||||
string str;
|
string str;
|
||||||
str.reserve(256);
|
str.reserve(256);
|
||||||
str += "HTTP/1.1 ";
|
str += "HTTP/1.1 ";
|
||||||
str += to_string(code);
|
str += to_string(code);
|
||||||
str += ' ';
|
str += ' ';
|
||||||
str += getHttpStatusMessage(code);
|
str += HttpConst::getHttpStatusMessage(code);
|
||||||
str += "\r\n";
|
str += "\r\n";
|
||||||
for (auto &pr : header) {
|
for (auto &pr : header) {
|
||||||
str += pr.first;
|
str += pr.first;
|
||||||
|
|
@ -605,9 +653,9 @@ void HttpSession::sendResponse(int code,
|
||||||
_ticker.resetTime();
|
_ticker.resetTime();
|
||||||
|
|
||||||
if (!size) {
|
if (!size) {
|
||||||
//没有body
|
// 没有body
|
||||||
if (bClose) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -622,20 +670,20 @@ void HttpSession::sendResponse(int code,
|
||||||
|
|
||||||
GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize);
|
GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize);
|
||||||
if (body->remainSize() > sendBufSize) {
|
if (body->remainSize() > sendBufSize) {
|
||||||
//文件下载提升发送性能
|
// 文件下载提升发送性能
|
||||||
setSocketFlags();
|
setSocketFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
//发送http body
|
// 发送http body
|
||||||
AsyncSenderData::Ptr data = std::make_shared<AsyncSenderData>(static_pointer_cast<HttpSession>(shared_from_this()), body, bClose);
|
AsyncSenderData::Ptr data = std::make_shared<AsyncSenderData>(static_pointer_cast<HttpSession>(shared_from_this()), body, bClose);
|
||||||
getSock()->setOnFlush([data]() { return AsyncSender::onSocketFlushed(data); });
|
getSock()->setOnFlush([data]() { return AsyncSender::onSocketFlushed(data); });
|
||||||
AsyncSender::onSocketFlushed(data);
|
AsyncSender::onSocketFlushed(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
string HttpSession::urlDecode(const string &str){
|
string HttpSession::urlDecode(const string &str) {
|
||||||
auto ret = strCoding::UrlDecode(str);
|
auto ret = strCoding::UrlDecode(str);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
GET_CONFIG(string,charSet,Http::kCharSet);
|
GET_CONFIG(string, charSet, Http::kCharSet);
|
||||||
bool isGb2312 = !strcasecmp(charSet.data(), "gb2312");
|
bool isGb2312 = !strcasecmp(charSet.data(), "gb2312");
|
||||||
if (isGb2312) {
|
if (isGb2312) {
|
||||||
ret = strCoding::UTF8ToGB2312(ret);
|
ret = strCoding::UTF8ToGB2312(ret);
|
||||||
|
|
@ -644,117 +692,51 @@ string HttpSession::urlDecode(const string &str){
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::urlDecode(Parser &parser){
|
void HttpSession::urlDecode(Parser &parser) {
|
||||||
parser.setUrl(urlDecode(parser.Url()));
|
parser.setUrl(urlDecode(parser.url()));
|
||||||
for(auto &pr : _parser.getUrlArgs()){
|
for (auto &pr : _parser.getUrlArgs()) {
|
||||||
const_cast<string &>(pr.second) = urlDecode(pr.second);
|
const_cast<string &>(pr.second) = urlDecode(pr.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpSession::emitHttpEvent(bool doInvoke){
|
bool HttpSession::emitHttpEvent(bool doInvoke) {
|
||||||
bool bClose = !strcasecmp(_parser["Connection"].data(),"close");
|
bool bClose = !strcasecmp(_parser["Connection"].data(), "close");
|
||||||
/////////////////////异步回复Invoker///////////////////////////////
|
/////////////////////异步回复Invoker///////////////////////////////
|
||||||
weak_ptr<HttpSession> weak_self = static_pointer_cast<HttpSession>(shared_from_this());
|
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();
|
auto strong_self = weak_self.lock();
|
||||||
if(!strong_self) {
|
if (!strong_self) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
strong_self->async([weak_self, bClose, code, headerOut, body]() {
|
strong_self->async([weak_self, bClose, code, headerOut, body]() {
|
||||||
auto strong_self = weak_self.lock();
|
auto strong_self = weak_self.lock();
|
||||||
if (!strong_self) {
|
if (!strong_self) {
|
||||||
//本对象已经销毁
|
// 本对象已经销毁
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
strong_self->sendResponse(code, bClose, nullptr, headerOut, body);
|
strong_self->sendResponse(code, bClose, nullptr, headerOut, body);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
///////////////////广播HTTP事件///////////////////////////
|
///////////////////广播HTTP事件///////////////////////////
|
||||||
bool consumed = false;//该事件是否被消费
|
bool consumed = false; // 该事件是否被消费
|
||||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpRequest,_parser,invoker,consumed,static_cast<SockInfo &>(*this));
|
NOTICE_EMIT(BroadcastHttpRequestArgs, Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, *this);
|
||||||
if(!consumed && doInvoke){
|
if (!consumed && doInvoke) {
|
||||||
//该事件无人消费,所以返回404
|
// 该事件无人消费,所以返回404
|
||||||
invoker(404,KeyValue(), HttpBody::Ptr());
|
invoker(404, KeyValue(), HttpBody::Ptr());
|
||||||
}
|
}
|
||||||
return consumed;
|
return consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string HttpSession::get_peer_ip() {
|
std::string HttpSession::get_peer_ip() {
|
||||||
GET_CONFIG(string, forwarded_ip_header, Http::kForwardedIpHeader);
|
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 _parser.getHeader()[forwarded_ip_header];
|
||||||
}
|
}
|
||||||
return Session::get_peer_ip();
|
return Session::get_peer_ip();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::Handle_Req_POST(ssize_t &content_len) {
|
void HttpSession::onHttpRequest_POST() {
|
||||||
GET_CONFIG(size_t,maxReqSize,Http::kMaxReqSize);
|
emitHttpEvent(true);
|
||||||
|
|
||||||
ssize_t totalContentLen = _parser["Content-Length"].empty() ? -1 : atoll(_parser["Content-Length"].data());
|
|
||||||
|
|
||||||
if(totalContentLen == 0){
|
|
||||||
//content为空
|
|
||||||
//emitHttpEvent内部会选择是否关闭连接
|
|
||||||
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) {
|
void HttpSession::sendNotFound(bool bClose) {
|
||||||
|
|
@ -762,19 +744,19 @@ void HttpSession::sendNotFound(bool bClose) {
|
||||||
sendResponse(404, bClose, "text/html", KeyValue(), std::make_shared<HttpStringBody>(notFound));
|
sendResponse(404, bClose, "text/html", KeyValue(), std::make_shared<HttpStringBody>(notFound));
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::setSocketFlags(){
|
void HttpSession::setSocketFlags() {
|
||||||
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS);
|
||||||
if(mergeWriteMS > 0) {
|
if (mergeWriteMS > 0) {
|
||||||
//推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高
|
// 推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高
|
||||||
SockUtil::setNoDelay(getSock()->rawFD(), false);
|
SockUtil::setNoDelay(getSock()->rawFD(), false);
|
||||||
//播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能
|
// 播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能
|
||||||
setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
|
setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) {
|
void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) {
|
||||||
if(flush){
|
if (flush) {
|
||||||
//需要flush那么一次刷新缓存
|
// 需要flush那么一次刷新缓存
|
||||||
HttpSession::setSendFlushFlag(true);
|
HttpSession::setSendFlushFlag(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -792,18 +774,18 @@ void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flush) {
|
if (flush) {
|
||||||
//本次刷新缓存后,下次不用刷新缓存
|
// 本次刷新缓存后,下次不用刷新缓存
|
||||||
HttpSession::setSendFlushFlag(false);
|
HttpSession::setSendFlushFlag(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::onWebSocketEncodeData(Buffer::Ptr buffer){
|
void HttpSession::onWebSocketEncodeData(Buffer::Ptr buffer) {
|
||||||
_total_bytes_usage += buffer->size();
|
_total_bytes_usage += buffer->size();
|
||||||
send(std::move(buffer));
|
send(std::move(buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in){
|
void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in) {
|
||||||
WebSocketHeader& header = const_cast<WebSocketHeader&>(header_in);
|
WebSocketHeader &header = const_cast<WebSocketHeader &>(header_in);
|
||||||
header._mask_flag = false;
|
header._mask_flag = false;
|
||||||
|
|
||||||
switch (header._opcode) {
|
switch (header._opcode) {
|
||||||
|
|
@ -813,15 +795,15 @@ void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default : break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::onDetach() {
|
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());
|
return dynamic_pointer_cast<FlvMuxer>(shared_from_this());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,11 +101,10 @@ protected:
|
||||||
std::string get_peer_ip() override;
|
std::string get_peer_ip() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Handle_Req_GET(ssize_t &content_len);
|
void onHttpRequest_GET();
|
||||||
void Handle_Req_GET_l(ssize_t &content_len, bool sendBody);
|
void onHttpRequest_POST();
|
||||||
void Handle_Req_POST(ssize_t &content_len);
|
void onHttpRequest_HEAD();
|
||||||
void Handle_Req_HEAD(ssize_t &content_len);
|
void onHttpRequest_OPTIONS();
|
||||||
void Handle_Req_OPTIONS(ssize_t &content_len);
|
|
||||||
|
|
||||||
bool checkLiveStream(const std::string &schema, const std::string &url_suffix, const std::function<void(const MediaSource::Ptr &src)> &cb);
|
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;
|
bool _live_over_websocket = false;
|
||||||
//消耗的总流量
|
//消耗的总流量
|
||||||
uint64_t _total_bytes_usage = 0;
|
uint64_t _total_bytes_usage = 0;
|
||||||
std::string _origin;
|
|
||||||
Parser _parser;
|
Parser _parser;
|
||||||
toolkit::Ticker _ticker;
|
toolkit::Ticker _ticker;
|
||||||
TSMediaSource::RingType::RingReader::Ptr _ts_reader;
|
TSMediaSource::RingType::RingReader::Ptr _ts_reader;
|
||||||
FMP4MediaSource::RingType::RingReader::Ptr _fmp4_reader;
|
FMP4MediaSource::RingType::RingReader::Ptr _fmp4_reader;
|
||||||
//处理content数据的callback
|
//处理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>;
|
using HttpsSession = toolkit::SessionWithSSL<HttpSession>;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include "PlayerBase.h"
|
#include "PlayerBase.h"
|
||||||
#include "Rtsp/RtspPlayerImp.h"
|
#include "Rtsp/RtspPlayerImp.h"
|
||||||
#include "Rtmp/RtmpPlayerImp.h"
|
#include "Rtmp/RtmpPlayerImp.h"
|
||||||
|
#include "Rtmp/FlvPlayer.h"
|
||||||
#include "Http/HlsPlayer.h"
|
#include "Http/HlsPlayer.h"
|
||||||
#include "Http/TsPlayerImp.h"
|
#include "Http/TsPlayerImp.h"
|
||||||
|
|
||||||
|
|
@ -20,15 +21,16 @@ using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller, const string &url_in) {
|
PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &in_poller, const string &url_in) {
|
||||||
static auto releasePlayer = [](PlayerBase *ptr) {
|
auto poller = in_poller ? in_poller : EventPollerPool::Instance().getPoller();
|
||||||
onceToken token(nullptr, [&]() {
|
static auto releasePlayer = [poller](PlayerBase *ptr) {
|
||||||
delete ptr;
|
poller->async([ptr]() {
|
||||||
|
onceToken token(nullptr, [&]() { delete ptr; });
|
||||||
|
ptr->teardown();
|
||||||
});
|
});
|
||||||
ptr->teardown();
|
|
||||||
};
|
};
|
||||||
string url = url_in;
|
string url = url_in;
|
||||||
string prefix = FindField(url.data(), NULL, "://");
|
string prefix = findSubString(url.data(), NULL, "://");
|
||||||
auto pos = url.find('?');
|
auto pos = url.find('?');
|
||||||
if (pos != string::npos) {
|
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 ((strcasecmp("http", prefix.data()) == 0 || strcasecmp("https", prefix.data()) == 0)) {
|
||||||
if (end_with(url, ".m3u8") || end_with(url_in, ".m3u8")) {
|
if (end_with(url, ".m3u8") || end_with(url_in, ".m3u8")) {
|
||||||
return PlayerBase::Ptr(new HlsPlayerImp(poller), releasePlayer);
|
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);
|
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);
|
throw std::invalid_argument("not supported play schema:" + url_in);
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,11 @@ PlayerProxy::~PlayerProxy() {
|
||||||
_timer.reset();
|
_timer.reset();
|
||||||
// 避免析构时, 忘记回调api请求
|
// 避免析构时, 忘记回调api请求
|
||||||
if (_on_play) {
|
if (_on_play) {
|
||||||
_on_play(SockException(Err_shutdown, "player proxy close"));
|
try {
|
||||||
|
_on_play(SockException(Err_shutdown, "player proxy close"));
|
||||||
|
} catch (std::exception &ex) {
|
||||||
|
WarnL << "Exception occurred: " << ex.what();
|
||||||
|
}
|
||||||
_on_play = nullptr;
|
_on_play = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,7 @@ MediaPusher::MediaPusher(const string &schema,
|
||||||
MediaPusher(MediaSource::find(schema, vhost, app, stream), poller){
|
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){
|
static void setOnCreateSocket_l(const std::shared_ptr<PusherBase> &delegate, const Socket::onCreateSocket &cb){
|
||||||
auto helper = dynamic_pointer_cast<SocketHelper>(delegate);
|
auto helper = dynamic_pointer_cast<SocketHelper>(delegate);
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ PusherBase::Ptr PusherBase::createPusher(const EventPoller::Ptr &poller,
|
||||||
});
|
});
|
||||||
ptr->teardown();
|
ptr->teardown();
|
||||||
};
|
};
|
||||||
std::string prefix = FindField(url.data(), NULL, "://");
|
std::string prefix = findSubString(url.data(), NULL, "://");
|
||||||
|
|
||||||
if (strcasecmp("rtsps",prefix.data()) == 0) {
|
if (strcasecmp("rtsps",prefix.data()) == 0) {
|
||||||
return PusherBase::Ptr(new TcpClientWithSSL<RtspPusherImp>(poller, std::dynamic_pointer_cast<RtspMediaSource>(src)), releasePusher);
|
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.
|
* may be found in the AUTHORS file in the root of the source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <iomanip>
|
||||||
#include "HlsMaker.h"
|
#include "HlsMaker.h"
|
||||||
#include "Common/config.h"
|
#include "Common/config.h"
|
||||||
|
|
||||||
|
|
@ -15,85 +16,76 @@ using namespace std;
|
||||||
|
|
||||||
namespace mediakit {
|
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个切片代表点播
|
//最小允许设置为0,0个切片代表点播
|
||||||
_seg_number = seg_number;
|
_seg_number = seg_number;
|
||||||
_seg_duration = seg_duration;
|
_seg_duration = seg_duration;
|
||||||
_seg_keep = seg_keep;
|
_seg_keep = seg_keep;
|
||||||
}
|
}
|
||||||
|
|
||||||
HlsMaker::~HlsMaker() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void HlsMaker::makeIndexFile(bool eof) {
|
void HlsMaker::makeIndexFile(bool eof) {
|
||||||
char file_content[1024];
|
|
||||||
int maxSegmentDuration = 0;
|
int maxSegmentDuration = 0;
|
||||||
|
|
||||||
for (auto &tp : _seg_dur_list) {
|
for (auto &tp : _seg_dur_list) {
|
||||||
int dur = std::get<0>(tp);
|
int dur = std::get<0>(tp);
|
||||||
if (dur > maxSegmentDuration) {
|
if (dur > maxSegmentDuration) {
|
||||||
maxSegmentDuration = dur;
|
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 index_str;
|
||||||
|
index_str.reserve(2048);
|
||||||
string m3u8;
|
index_str += "#EXTM3U\n";
|
||||||
if (_seg_number == 0) {
|
index_str += (_is_fmp4 ? "#EXT-X-VERSION:7\n" : "#EXT-X-VERSION:4\n");
|
||||||
// 录像点播支持时移
|
if (_seg_number == 0) {
|
||||||
snprintf(file_content, sizeof(file_content),
|
index_str += "#EXT-X-PLAYLIST-TYPE:EVENT\n";
|
||||||
"#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);
|
|
||||||
} else {
|
} else {
|
||||||
snprintf(file_content, sizeof(file_content),
|
index_str += "#EXT-X-ALLOW-CACHE:NO\n";
|
||||||
"#EXTM3U\n"
|
}
|
||||||
"#EXT-X-VERSION:3\n"
|
index_str += "#EXT-X-TARGETDURATION:" + std::to_string((maxSegmentDuration + 999) / 1000) + "\n";
|
||||||
"#EXT-X-ALLOW-CACHE:NO\n"
|
index_str += "#EXT-X-MEDIA-SEQUENCE:" + std::to_string(index_seq) + "\n";
|
||||||
"#EXT-X-TARGETDURATION:%u\n"
|
if (_is_fmp4) {
|
||||||
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
|
index_str += "#EXT-X-MAP:URI=\"init.mp4\"\n";
|
||||||
(maxSegmentDuration + 999) / 1000,
|
|
||||||
sequence);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m3u8.assign(file_content);
|
|
||||||
|
|
||||||
|
stringstream ss;
|
||||||
for (auto &tp : _seg_dur_list) {
|
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());
|
ss << "#EXTINF:" << std::setprecision(3) << std::get<0>(tp) / 1000.0 << ",\n" << std::get<1>(tp) << "\n";
|
||||||
m3u8.append(file_content);
|
|
||||||
}
|
}
|
||||||
|
index_str += ss.str();
|
||||||
|
|
||||||
if (eof) {
|
if (eof) {
|
||||||
snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n");
|
index_str += "#EXT-X-ENDLIST\n";
|
||||||
m3u8.append(file_content);
|
|
||||||
}
|
}
|
||||||
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 (data && len) {
|
||||||
if (timestamp < _last_timestamp) {
|
if (timestamp < _last_timestamp) {
|
||||||
//时间戳回退了,切片时长重新计时
|
// 时间戳回退了,切片时长重新计时
|
||||||
WarnL << "stamp reduce: " << _last_timestamp << " -> " << timestamp;
|
WarnL << "Timestamp reduce: " << _last_timestamp << " -> " << timestamp;
|
||||||
_last_seg_timestamp = _last_timestamp = timestamp;
|
_last_seg_timestamp = _last_timestamp = timestamp;
|
||||||
}
|
}
|
||||||
if (is_idr_fast_packet) {
|
if (is_idr_fast_packet) {
|
||||||
//尝试切片ts
|
// 尝试切片ts
|
||||||
addNewSegment(timestamp);
|
addNewSegment(timestamp);
|
||||||
}
|
}
|
||||||
if (!_last_file_name.empty()) {
|
if (!_last_file_name.empty()) {
|
||||||
//存在切片才写入ts数据
|
// 存在切片才写入ts数据
|
||||||
onWriteSegment((char *) data, len);
|
onWriteSegment(data, len);
|
||||||
_last_timestamp = timestamp;
|
_last_timestamp = timestamp;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//resetTracks时触发此逻辑
|
// resetTracks时触发此逻辑
|
||||||
flushLastSegment(false);
|
flushLastSegment(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -150,14 +142,18 @@ void HlsMaker::flushLastSegment(bool eof){
|
||||||
makeIndexFile(eof);
|
makeIndexFile(eof);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HlsMaker::isLive() {
|
bool HlsMaker::isLive() const {
|
||||||
return _seg_number != 0;
|
return _seg_number != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HlsMaker::isKeep() {
|
bool HlsMaker::isKeep() const {
|
||||||
return _seg_keep;
|
return _seg_keep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HlsMaker::isFmp4() const {
|
||||||
|
return _is_fmp4;
|
||||||
|
}
|
||||||
|
|
||||||
void HlsMaker::clear() {
|
void HlsMaker::clear() {
|
||||||
_file_index = 0;
|
_file_index = 0;
|
||||||
_last_timestamp = 0;
|
_last_timestamp = 0;
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,13 @@ namespace mediakit {
|
||||||
class HlsMaker {
|
class HlsMaker {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
|
* @param is_fmp4 使用fmp4还是mpegts
|
||||||
* @param seg_duration 切片文件长度
|
* @param seg_duration 切片文件长度
|
||||||
* @param seg_number 切片个数
|
* @param seg_number 切片个数
|
||||||
* @param seg_keep 是否保留切片文件
|
* @param seg_keep 是否保留切片文件
|
||||||
*/
|
*/
|
||||||
HlsMaker(float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
|
HlsMaker(bool is_fmp4 = false, float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
|
||||||
virtual ~HlsMaker();
|
virtual ~HlsMaker() = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 写入ts数据
|
* 写入ts数据
|
||||||
|
|
@ -35,17 +36,29 @@ public:
|
||||||
* @param timestamp 毫秒时间戳
|
* @param timestamp 毫秒时间戳
|
||||||
* @param is_idr_fast_packet 是否为关键帧第一个包
|
* @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;
|
virtual void onDelSegment(uint64_t index) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写init.mp4切片文件回调
|
||||||
|
* @param data
|
||||||
|
* @param len
|
||||||
|
*/
|
||||||
|
virtual void onWriteInitSegment(const char *data, size_t len) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 写ts切片文件回调
|
* 写ts切片文件回调
|
||||||
* @param data
|
* @param data
|
||||||
|
|
@ -109,6 +129,7 @@ private:
|
||||||
void addNewSegment(uint64_t timestamp);
|
void addNewSegment(uint64_t timestamp);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool _is_fmp4 = false;
|
||||||
float _seg_duration = 0;
|
float _seg_duration = 0;
|
||||||
uint32_t _seg_number = 0;
|
uint32_t _seg_number = 0;
|
||||||
bool _seg_keep = false;
|
bool _seg_keep = false;
|
||||||
|
|
|
||||||
|
|
@ -21,21 +21,14 @@ using namespace toolkit;
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
HlsMakerImp::HlsMakerImp(const string &m3u8_file,
|
HlsMakerImp::HlsMakerImp(bool is_fmp4, const string &m3u8_file, const string ¶ms, uint32_t bufSize, float seg_duration,
|
||||||
const string ¶ms,
|
uint32_t seg_number, bool seg_keep) : HlsMaker(is_fmp4, seg_duration, seg_number, seg_keep) {
|
||||||
uint32_t bufSize,
|
|
||||||
float seg_duration,
|
|
||||||
uint32_t seg_number,
|
|
||||||
bool seg_keep):HlsMaker(seg_duration, seg_number, seg_keep) {
|
|
||||||
_poller = EventPollerPool::Instance().getPoller();
|
_poller = EventPollerPool::Instance().getPoller();
|
||||||
_path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/'));
|
_path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/'));
|
||||||
_path_hls = m3u8_file;
|
_path_hls = m3u8_file;
|
||||||
_params = params;
|
_params = params;
|
||||||
_buf_size = bufSize;
|
_buf_size = bufSize;
|
||||||
_file_buf.reset(new char[bufSize], [](char *ptr) {
|
_file_buf.reset(new char[bufSize], [](char *ptr) { delete[] ptr; });
|
||||||
delete[] ptr;
|
|
||||||
});
|
|
||||||
|
|
||||||
_info.folder = _path_prefix;
|
_info.folder = _path_prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,9 +46,9 @@ void HlsMakerImp::clearCache() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HlsMakerImp::clearCache(bool immediately, bool eof) {
|
void HlsMakerImp::clearCache(bool immediately, bool eof) {
|
||||||
//录制完了
|
// 录制完了
|
||||||
flushLastSegment(eof);
|
flushLastSegment(eof);
|
||||||
if (!isLive()||isKeep()) {
|
if (!isLive() || isKeep()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,7 +56,7 @@ void HlsMakerImp::clearCache(bool immediately, bool eof) {
|
||||||
_file = nullptr;
|
_file = nullptr;
|
||||||
_segment_file_paths.clear();
|
_segment_file_paths.clear();
|
||||||
|
|
||||||
//hls直播才删除文件
|
// hls直播才删除文件
|
||||||
GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec);
|
GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec);
|
||||||
if (!delay || immediately) {
|
if (!delay || immediately) {
|
||||||
File::delete_file(_path_prefix.data());
|
File::delete_file(_path_prefix.data());
|
||||||
|
|
@ -82,7 +75,7 @@ string HlsMakerImp::onOpenSegment(uint64_t index) {
|
||||||
auto strDate = getTimeStr("%Y-%m-%d");
|
auto strDate = getTimeStr("%Y-%m-%d");
|
||||||
auto strHour = getTimeStr("%H");
|
auto strHour = getTimeStr("%H");
|
||||||
auto strTime = getTimeStr("%M-%S");
|
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;
|
segment_path = _path_prefix + "/" + segment_name;
|
||||||
if (isLive()) {
|
if (isLive()) {
|
||||||
_segment_file_paths.emplace(index, segment_path);
|
_segment_file_paths.emplace(index, segment_path);
|
||||||
|
|
@ -90,14 +83,14 @@ string HlsMakerImp::onOpenSegment(uint64_t index) {
|
||||||
}
|
}
|
||||||
_file = makeFile(segment_path, true);
|
_file = makeFile(segment_path, true);
|
||||||
|
|
||||||
//保存本切片的元数据
|
// 保存本切片的元数据
|
||||||
_info.start_time = ::time(NULL);
|
_info.start_time = ::time(NULL);
|
||||||
_info.file_name = segment_name;
|
_info.file_name = segment_name;
|
||||||
_info.file_path = segment_path;
|
_info.file_path = segment_path;
|
||||||
_info.url = _info.app + "/" + _info.stream + "/" + segment_name;
|
_info.url = _info.app + "/" + _info.stream + "/" + segment_name;
|
||||||
|
|
||||||
if (!_file) {
|
if (!_file) {
|
||||||
WarnL << "create file failed," << segment_path << " " << get_uv_errmsg();
|
WarnL << "Create file failed," << segment_path << " " << get_uv_errmsg();
|
||||||
}
|
}
|
||||||
if (_params.empty()) {
|
if (_params.empty()) {
|
||||||
return segment_name;
|
return segment_name;
|
||||||
|
|
@ -114,6 +107,18 @@ void HlsMakerImp::onDelSegment(uint64_t index) {
|
||||||
_segment_file_paths.erase(it);
|
_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) {
|
void HlsMakerImp::onWriteSegment(const char *data, size_t len) {
|
||||||
if (_file) {
|
if (_file) {
|
||||||
fwrite(data, len, 1, _file.get());
|
fwrite(data, len, 1, _file.get());
|
||||||
|
|
@ -132,20 +137,19 @@ void HlsMakerImp::onWriteHls(const std::string &data) {
|
||||||
_media_src->setIndexFile(data);
|
_media_src->setIndexFile(data);
|
||||||
}
|
}
|
||||||
} else {
|
} 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) {
|
void HlsMakerImp::onFlushLastSegment(uint64_t duration_ms) {
|
||||||
//关闭并flush文件到磁盘
|
// 关闭并flush文件到磁盘
|
||||||
_file = nullptr;
|
_file = nullptr;
|
||||||
|
|
||||||
GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs);
|
GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs);
|
||||||
if (broadcastRecordTs) {
|
if (broadcastRecordTs) {
|
||||||
_info.time_len = duration_ms / 1000.0f;
|
_info.time_len = duration_ms / 1000.0f;
|
||||||
_info.file_size = File::fileSize(_info.file_path.data());
|
_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.app = app;
|
||||||
_info.stream = stream_id;
|
_info.stream = stream_id;
|
||||||
_info.vhost = vhost;
|
_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 {
|
HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const {
|
||||||
return _media_src;
|
return _media_src;
|
||||||
}
|
}
|
||||||
|
|
||||||
}//namespace mediakit
|
} // namespace mediakit
|
||||||
|
|
@ -19,15 +19,10 @@
|
||||||
|
|
||||||
namespace mediakit {
|
namespace mediakit {
|
||||||
|
|
||||||
class HlsMakerImp : public HlsMaker{
|
class HlsMakerImp : public HlsMaker {
|
||||||
public:
|
public:
|
||||||
HlsMakerImp(const std::string &m3u8_file,
|
HlsMakerImp(bool is_fmp4, const std::string &m3u8_file, const std::string ¶ms, uint32_t bufSize = 64 * 1024,
|
||||||
const std::string ¶ms,
|
float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
|
||||||
uint32_t bufSize = 64 * 1024,
|
|
||||||
float seg_duration = 5,
|
|
||||||
uint32_t seg_number = 3,
|
|
||||||
bool seg_keep = false);
|
|
||||||
|
|
||||||
~HlsMakerImp() override;
|
~HlsMakerImp() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,6 +47,7 @@ public:
|
||||||
protected:
|
protected:
|
||||||
std::string onOpenSegment(uint64_t index) override ;
|
std::string onOpenSegment(uint64_t index) override ;
|
||||||
void onDelSegment(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 onWriteSegment(const char *data, size_t len) override;
|
||||||
void onWriteHls(const std::string &data) override;
|
void onWriteHls(const std::string &data) override;
|
||||||
void onFlushLastSegment(uint64_t duration_ms) 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