This commit is contained in:
xingqiao 2023-10-13 09:31:04 +08:00
commit caf7c85090
175 changed files with 4535 additions and 2377 deletions

View File

@ -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

View File

@ -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,19 +47,14 @@ set(MediaServer_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/media-server")
# TODO: # TODO:
# movflv MP4 # movflv 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>"
@ -68,18 +67,23 @@ if(ENABLE_MP4)
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")
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 () endif ()
# mpeg ts # mpeg ts
@ -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

11
AUTHORS
View File

@ -74,3 +74,14 @@ WuPeng <wp@zafu.edu.cn>
[朱如洪 ](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)

View File

@ -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
if(ENABLE_SERVER)
add_subdirectory(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()
#cppdemo #cppdemo
if (ENABLE_TESTS) if (ENABLE_TESTS)
add_subdirectory(tests) add_subdirectory(tests)

View File

@ -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编码
@ -168,29 +170,35 @@ bash build_docker_images.sh
## 合作项目 ## 合作项目
- 可视化管理网站 - 视频管理平台
- [最新的前后端分离web项目,支持webrtc播放](https://github.com/langmansh/AKStreamNVR) - [wvp-GB28181-pro](https://github.com/648540858/wvp-GB28181-pro) java实现的开箱即用的GB28181协议视频平台
- [AKStream](https://github.com/chatop2020/AKStream) c#实现的全功能的软NVR接口/GB28181平台
- [BXC_SipServer](https://github.com/any12345com/BXC_SipServer) c++实现的国标GB28181流媒体信令服务器
- [gosip](https://github.com/panjjo/gosip) golang实现的GB28181服务器
- [FreeEhome](https://github.com/tsingeye/FreeEhome) golang实现的海康ehome服务器
- 播放器
- [h265web.js](https://github.com/numberwolf/h265web.js) 基于wasm支持H265的播放器支持本项目多种专属协议
- [jessibuca](https://github.com/langhuihui/jessibuca) 基于wasm支持H265的播放器
- [wsPlayer](https://github.com/v354412101/wsPlayer) 基于MSE的websocket-fmp4播放器
- [BXC_gb28181Player](https://github.com/any12345com/BXC_gb28181Player) C++开发的支持国标GB28181协议的视频流播放器
- WEB管理网站
- [AKStreamNVR](https://github.com/langmansh/AKStreamNVR) 前后端分离web项目,支持webrtc播放
- SDK
- [c# sdk](https://github.com/malegend/ZLMediaKit.Autogen) 本项目c sdk完整c#包装库
- [metaRTC](https://github.com/metartc/metaRTC) 全国产纯c webrtc sdk
- 其他项目(已停止更新)
- [NodeJS实现的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http)
- [基于ZLMediaKit主线的管理WEB网站 ](https://gitee.com/kkkkk5G/MediaServerUI) - [基于ZLMediaKit主线的管理WEB网站 ](https://gitee.com/kkkkk5G/MediaServerUI)
- [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI) - [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI)
- [一个非常漂亮的可视化后台管理系统](https://github.com/MingZhuLiu/ZLMediaServerManagent) - [一个非常漂亮的可视化后台管理系统](https://github.com/MingZhuLiu/ZLMediaServerManagent)
- 流媒体管理平台
- [GB28181完整解决方案,自带web管理网站,支持webrtc、h265播放](https://github.com/648540858/wvp-GB28181-pro)
- [功能强大的流媒体控制管理接口平台,支持GB28181](https://github.com/chatop2020/AKStream)
- [Go实现的GB28181服务器](https://github.com/panjjo/gosip)
- [node-js版本的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http)
- [Go实现的海康ehome服务器](https://github.com/tsingeye/FreeEhome)
- 客户端
- [c sdk完整c#包装库](https://github.com/malegend/ZLMediaKit.Autogen)
- [基于C SDK实现的推流客户端](https://github.com/hctym1995/ZLM_ApiDemo) - [基于C 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,19 +210,20 @@ 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% />
## 怎么提问? ## 怎么提问?
如果要对项目有相关疑问,建议您这么做: 如果要对项目有相关疑问,建议您这么做:
- 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开发与调试
[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/CLion.svg)](https://jb.gg/OpenSourceSupport)
## 使用案例 ## 使用案例

View File

@ -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:
[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/CLion.svg)](https://jb.gg/OpenSourceSupport)
## Use Cases ## Use Cases

View File

@ -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()

View File

@ -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;

View File

@ -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
* 3RtmpMediaSourceRtspMediaSourceHlsMediaSource * 3RtmpMediaSourceRtspMediaSourceHlsMediaSource

View File

@ -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不知道后续是否还要添加track3
* 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 10
*/
API_EXPORT int API_CALL mk_mpeg_muxer_input_frame(mk_mpeg_muxer ctx, mk_frame frame);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -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

View File

@ -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)

View File

@ -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());
}
});
}); });
} }

View File

@ -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;

View File

@ -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));
}

View File

@ -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){

View File

@ -53,7 +53,9 @@ static inline bool startRecord(Recorder::type type, const string &vhost, const
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) {
@ -61,7 +63,9 @@ static inline bool stopRecord(Recorder::type type, const string &vhost, const st
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){

View File

@ -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);
} }
} }

View File

@ -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()

View File

@ -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 {

View File

@ -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

View File

@ -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} )

View File

@ -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 打包时低延迟开关默认关闭为0h264存在一帧多个sliceNAL的情况在这种情况下如果开启可能会导致画面花屏 # rtp 打包时低延迟开关默认关闭为0h264存在一帧多个sliceNAL的情况在这种情况下如果开启可能会导致画面花屏
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

View File

@ -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-----

View File

@ -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"]

View File

@ -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"]

View File

@ -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"]

View File

@ -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"]

View File

@ -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"]

View File

@ -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"]

View File

@ -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;
} }

View File

@ -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",

View File

@ -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()
# IOS
if(IOS)
add_library(MediaServer STATIC ${MediaServer_SRC_LIST})
else()
add_executable(MediaServer ${MediaServer_SRC_LIST}) 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

View File

@ -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;
}); });
} }
@ -154,10 +155,7 @@ void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,cons
} }
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,
_media_info.app,
_media_info.stream);
if (src || !maxWaitMS) { if (src || !maxWaitMS) {
cb(src); cb(src);
return; return;
@ -182,11 +180,8 @@ void FFmpegSource::findAsync(int maxWaitMS, const function<void(const MediaSourc
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;
} }
@ -198,12 +193,10 @@ void FFmpegSource::findAsync(int maxWaitMS, const function<void(const MediaSourc
// 切换到自己的线程再回复 // 切换到自己的线程再回复
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);
}; };
// 监听媒体注册事件 // 监听媒体注册事件
@ -300,16 +293,13 @@ 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);
} }
@ -357,4 +347,3 @@ void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float
cb(success, (!success && !log_file.empty()) ? File::loadFile(log_file.data()) : ""); cb(success, (!success && !log_file.empty()) ? File::loadFile(log_file.data()) : "");
}); });
} }

View File

@ -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;

View File

@ -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 };

View File

@ -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();

View File

@ -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) {

View File

@ -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;
} }
//添加拉流代理 //添加拉流代理
@ -585,6 +589,7 @@ 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();
@ -603,6 +608,7 @@ 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");

View File

@ -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 {
@ -220,13 +222,18 @@ bool checkArgs(Args &args, const First &first, const KeyTypes &...keys) {
} }
// 检查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();

View File

@ -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文件访问鉴权那么允许访问但是每次访问都要鉴权
// 因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权) // 因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权)
if (!HttpFileManager::isIPAllowed(sender.get_peer_ip())) {
invoker("Your ip is not allowed to access the service.", "", 0);
} else {
invoker("", "", 0); 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();
}

View File

@ -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

View File

@ -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) {
@ -388,6 +420,12 @@ int start_main(int argc,char *argv[]) {
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()); });
#endif #endif
@ -395,6 +433,8 @@ int start_main(int argc,char *argv[]) {
} }
unInstallWebApi(); unInstallWebApi();
unInstallWebHook(); unInstallWebHook();
onProcessExited();
//休眠1秒再退出防止资源释放顺序错误 //休眠1秒再退出防止资源释放顺序错误
InfoL << "程序退出中,请等待..."; InfoL << "程序退出中,请等待...";
sleep(1); sleep(1);

View File

@ -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);

View File

@ -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

30
src/Common/JemallocUtil.h Normal file
View File

@ -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

View File

@ -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() {
try {
unregist(); unregist();
} catch (std::exception &ex) {
WarnL << "Exception occurred: " << ex.what();
} }
const string& MediaSource::getSchema() const {
return _schema;
}
const string& MediaSource::getVhost() const {
return _tuple.vhost;
}
const string& MediaSource::getApp() const {
//获取该源的id
return _tuple.app;
}
const string& MediaSource::getId() const {
return _tuple.stream;
} }
std::shared_ptr<void> MediaSource::getOwnership() { std::shared_ptr<void> MediaSource::getOwnership() {
@ -183,22 +176,10 @@ 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(){
auto listener = _listener.lock(); auto listener = _listener.lock();
if(!listener){ if(!listener){
@ -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();
if (muxer && muxer->getOption().auto_close) {
// 此流被标记为无人观看自动关闭流
WarnL << "Auto cloe stream when none reader: " << strong_sender->getUrl();
strong_sender->close(false);
} else {
// 直播时触发无人观看事件,让开发者自行选择是否关闭 // 直播时触发无人观看事件,让开发者自行选择是否关闭
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender); 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) {

View File

@ -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////////////////

View File

@ -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,6 +228,46 @@ 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;
} }
} }
@ -229,12 +275,12 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type
//此函数可能跨线程调用 //此函数可能跨线程调用
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

View File

@ -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;

View File

@ -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,125 +44,146 @@ 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('?');
if (pos != string::npos) {
_params = _url.substr(pos + 1);
_url_args = parseArgs(_params);
_url = _url.substr(0, pos);
}
_protocol = std::string(next_blank + 1, next_line);
} else { } else {
_strUrl = strFullUrl; auto pos = strchr(ptr, ':');
} CHECK(pos > ptr && pos < next_line);
_strTail = FindField(line.data(), (strFullUrl + " ").data(), NULL); std::string key { ptr, static_cast<std::size_t>(pos - ptr) };
std::string value;
if (pos[1] == ' ') {
value.assign(pos + 2, next_line);
} else { } else {
auto field = FindField(line.data(), NULL, ": "); value.assign(pos + 1, next_line);
auto value = FindField(line.data(), ": ", NULL);
if (field.size() != 0) {
_mapHeaders.emplace_force(field, value);
} }
_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) {
std::string Parser::mergeUrl(const string &base_url, const string &path) {
// 以base_url为基础, 合并path路径生成新的url, path支持相对路径和绝对路径 // 以base_url为基础, 合并path路径生成新的url, path支持相对路径和绝对路径
if (base_url.empty()) { if (base_url.empty()) {
return path; return path;
@ -234,13 +258,14 @@ 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) {
@ -255,22 +280,23 @@ void RtspUrl::parse(const string &strUrl) {
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;
@ -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([](){

View File

@ -18,14 +18,12 @@
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,18 +40,18 @@ 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));
} }
}; };
@ -63,32 +61,36 @@ 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 &params() const;
// 重新设置url // 重新设置url
void setUrl(std::string url); void setUrl(std::string url);
@ -105,17 +107,16 @@ public:
// 解析?后面的参数 // 解析?后面的参数
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的工具类

View File

@ -42,14 +42,15 @@ int64_t DeltaStamp::deltaStamp(int64_t 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) {
@ -230,72 +231,73 @@ bool DtsGenerator::getDts_l(uint64_t pts, uint64_t &dts){
} }
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) {
}
void NtpStamp::update(uint32_t rtp_stamp, uint64_t ntp_stamp_ms) {
if (ntp_stamp_ms == 0) {
// 实测发现有些rtsp服务器发送的rtp时间戳和ntp时间戳一直为0 // 实测发现有些rtsp服务器发送的rtp时间戳和ntp时间戳一直为0
WarnL << "Invalid sender report rtcp, ntp_stamp_ms = " << ntp_stamp_ms << ", rtp_stamp = " << rtp_stamp;
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

View File

@ -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

View File

@ -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

View File

@ -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 打包时低延迟开关默认关闭为0h264存在一帧多个sliceNAL的情况在这种情况下如果开启可能会导致画面花屏 // rtp 打包时低延迟开关默认关闭为0h264存在一帧多个sliceNAL的情况在这种情况下如果开启可能会导致画面花屏
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
////////////组播配置/////////// ////////////组播配置///////////

View File

@ -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__"

View File

@ -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

View File

@ -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;

View File

@ -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()) {
@ -103,41 +100,35 @@ bool AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) {
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 // aac data
rtmpPkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); pkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize());
pkt->body_size = pkt->buffer.size();
rtmpPkt->body_size = rtmpPkt->buffer.size(); pkt->chunk_id = CHUNK_AUDIO;
rtmpPkt->chunk_id = CHUNK_AUDIO; pkt->stream_index = STREAM_MEDIA;
rtmpPkt->stream_index = STREAM_MEDIA; pkt->time_stamp = frame->dts();
rtmpPkt->time_stamp = frame->dts(); pkt->type_id = MSG_AUDIO;
rtmpPkt->type_id = MSG_AUDIO; RtmpCodec::inputRtmp(pkt);
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
uint8_t is_config = true; pkt->buffer.push_back(_audio_flv_flags);
rtmpPkt->buffer.push_back(_audio_flv_flags); pkt->buffer.push_back((uint8_t)RtmpAACPacketType::aac_config_header);
rtmpPkt->buffer.push_back(!is_config);
// aac config // aac config
rtmpPkt->buffer.append(_aac_cfg); pkt->buffer.append(_aac_cfg);
pkt->body_size = pkt->buffer.size();
rtmpPkt->body_size = rtmpPkt->buffer.size(); pkt->chunk_id = CHUNK_AUDIO;
rtmpPkt->chunk_id = CHUNK_AUDIO; pkt->stream_index = STREAM_MEDIA;
rtmpPkt->stream_index = STREAM_MEDIA; pkt->time_stamp = 0;
rtmpPkt->time_stamp = 0; pkt->type_id = MSG_AUDIO;
rtmpPkt->type_id = MSG_AUDIO; RtmpCodec::inputRtmp(pkt);
RtmpCodec::inputRtmp(rtmpPkt);
} }
}//namespace mediakit }//namespace mediakit

View File

@ -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();
} }

View File

@ -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丢包了那么整帧应该废弃

View File

@ -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;

View File

@ -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;
} }
} }
@ -292,12 +295,12 @@ 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);
} }
} }

View File

@ -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对象

View File

@ -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) {

View File

@ -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; }

View File

@ -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-2324 (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

View File

@ -30,10 +30,7 @@ H264Frame::Ptr H264RtmpDecoder::obtainFrame() {
* 0x00 00 00 01sps pps * 0x00 00 00 01sps 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());
@ -160,16 +157,11 @@ 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;
if (cts < 0) {
cts = 0;
}
// cts // cts
set_be24(&_rtmp_packet->buffer[2], cts); set_be24(&_rtmp_packet->buffer[2], cts);
_rtmp_packet->time_stamp = dts; _rtmp_packet->time_stamp = dts;
_rtmp_packet->body_size = _rtmp_packet->buffer.size(); _rtmp_packet->body_size = _rtmp_packet->buffer.size();
_rtmp_packet->chunk_id = CHUNK_VIDEO; _rtmp_packet->chunk_id = CHUNK_VIDEO;
@ -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();
auto rtmpPkt = RtmpPacket::create();
// header // header
rtmpPkt->buffer.push_back(flags); pkt->buffer.push_back(flags);
rtmpPkt->buffer.push_back(!is_config); pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header);
// cts // cts
rtmpPkt->buffer.append("\x0\x0\x0", 3); pkt->buffer.append("\x0\x0\x0", 3);
// AVCDecoderConfigurationRecord start // AVCDecoderConfigurationRecord start
rtmpPkt->buffer.push_back(1); // version pkt->buffer.push_back(1); // version
rtmpPkt->buffer.push_back(_sps[1]); // profile pkt->buffer.push_back(_sps[1]); // profile
rtmpPkt->buffer.push_back(_sps[2]); // compat pkt->buffer.push_back(_sps[2]); // compat
rtmpPkt->buffer.push_back(_sps[3]); // level pkt->buffer.push_back(_sps[3]); // level
rtmpPkt->buffer.push_back((char)0xff); // 6 bits reserved + 2 bits nal size length - 1 (11) pkt->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) pkt->buffer.push_back((char)0xe1); // 3 bits reserved + 5 bits number of sps (00001)
// sps // 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

View File

@ -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,6 +253,15 @@ 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);
@ -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])) {

View File

@ -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,7 +77,8 @@ 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
@ -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;

View File

@ -30,25 +30,8 @@ H265Frame::Ptr H265RtmpDecoder::obtainFrame() {
} }
#ifdef ENABLE_MP4 #ifdef ENABLE_MP4
/**
* 0x00 00 00 01sps
* @return
*/
static bool getH265ConfigFrame(const RtmpPacket &thiz,string &frame) {
if (thiz.getMediaType() != FLV_CODEC_H265) {
return false;
}
if (!thiz.isCfgFrame()) {
return false;
}
if (thiz.buffer.size() < 6) {
WarnL << "bad H265 cfg!";
return false;
}
auto extra = thiz.buffer.data() + 5;
auto bytes = thiz.buffer.size() - 5;
static bool decode_HEVCDecoderConfigurationRecord(uint8_t *extra, size_t bytes, string &frame) {
struct mpeg4_hevc_t hevc; struct mpeg4_hevc_t hevc;
memset(&hevc, 0, sizeof(hevc)); memset(&hevc, 0, sizeof(hevc));
if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *)extra, bytes, &hevc) > 0) { if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *)extra, bytes, &hevc) > 0) {
@ -62,10 +45,86 @@ static bool getH265ConfigFrame(const RtmpPacket &thiz,string &frame) {
} }
return false; return false;
} }
/**
* 0x00 00 00 01sps
*/
static bool getH265ConfigFrame(const RtmpPacket &thiz, string &frame) {
if ((RtmpVideoCodec)thiz.getRtmpCodecId() != RtmpVideoCodec::h265) {
return false;
}
if (thiz.buffer.size() < 6) {
WarnL << "bad H265 cfg!";
return false;
}
return decode_HEVCDecoderConfigurationRecord((uint8_t *)thiz.buffer.data() + 5, thiz.buffer.size() - 5, frame);
}
#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)) {
@ -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();
size_t offset = 5;
uint8_t *cts_ptr = (uint8_t *)(pkt->buffer.data() + 2); uint8_t *cts_ptr = (uint8_t *)(pkt->buffer.data() + 2);
int32_t cts = (((cts_ptr[0] << 16) | (cts_ptr[1] << 8) | (cts_ptr[2])) + 0xff800000) ^ 0xff800000; 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
} }
@ -181,16 +241,11 @@ bool H265RtmpEncoder::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_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;
if (cts < 0) {
cts = 0;
}
// cts // cts
set_be24(&_rtmp_packet->buffer[2], cts); set_be24(&_rtmp_packet->buffer[2], cts);
_rtmp_packet->time_stamp = dts; _rtmp_packet->time_stamp = dts;
_rtmp_packet->body_size = _rtmp_packet->buffer.size(); _rtmp_packet->body_size = _rtmp_packet->buffer.size();
_rtmp_packet->chunk_id = CHUNK_VIDEO; _rtmp_packet->chunk_id = CHUNK_VIDEO;
@ -204,21 +259,18 @@ bool H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) {
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
rtmpPkt->buffer.push_back(flags); pkt->buffer.push_back(flags);
rtmpPkt->buffer.push_back(!is_config); pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header);
// cts // cts
rtmpPkt->buffer.append("\x0\x0\x0", 3); pkt->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));
@ -227,13 +279,13 @@ void H265RtmpEncoder::makeVideoConfigPkt() {
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

View File

@ -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,15 +33,16 @@ 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;
}; };

View File

@ -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);
} }

View File

@ -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

View File

@ -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);

View File

@ -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();
@ -125,14 +130,17 @@ void HlsPlayer::fetchSegment() {
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) {
// 提前1秒下载
delay -= 1.0;
} else if (delay <= 0) {
// 延时最小10ms // 延时最小10ms
delay = 10; 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();

View File

@ -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);

View File

@ -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 {

View File

@ -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;

View File

@ -15,19 +15,25 @@
namespace mediakit{ namespace mediakit{
class HttpConst {
public:
HttpConst() = delete;
~HttpConst() = delete;
/** /**
* http错误代码获取字符说明 * http错误代码获取字符说明
* @param status 404 * @param status 404
* @return Not Found * @return Not Found
*/ */
const char *getHttpStatusMessage(int status); static const char *getHttpStatusMessage(int status);
/** /**
* http mime * http mime
* @param name html * @param name html
* @return mime值text/html * @return mime值text/html
*/ */
const std::string &getHttpContentType(const char *name); static const std::string &getHttpContentType(const char *name);
};
}//mediakit }//mediakit

View File

@ -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;

View File

@ -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);
/** /**

View File

@ -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);
} }
@ -321,7 +432,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo
} }
// 事件未被拦截则认为是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
if (end_with(file_path, kHlsSuffix)) {
const_cast<string &>(media_info.schema) = HLS_SCHEMA; const_cast<string &>(media_info.schema) = HLS_SCHEMA;
replace(const_cast<string &>(media_info.stream), kHlsSuffix, ""); 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;
} }
@ -449,6 +566,9 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m
// 标记找到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);
@ -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) {
@ -506,13 +633,16 @@ void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFi
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())) {
// 不是文件夹
parser.setUrl(pathCat(parser.url(), indexFile));
accessFile(sender, parser, media_info, file_path, cb); accessFile(sender, parser, media_info, file_path, cb);
return; 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;

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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"
@ -30,14 +30,14 @@ HttpSession::HttpSession(const Socket::Ptr &pSock) : Session(pSock) {
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);
@ -53,46 +53,107 @@ void HttpSession::Handle_Req_OPTIONS(ssize_t &content_len) {
} }
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;
};
// 声明后续都是bodyHttp 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;
};
// 声明后续都是bodyHttp 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;
}
} }
} }
@ -105,15 +166,11 @@ 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;
} }
@ -148,10 +205,10 @@ bool HttpSession::checkWebSocket(){
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
@ -182,7 +239,7 @@ bool HttpSession::checkWebSocket(){
} }
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())) {
@ -200,9 +257,9 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffi
} }
// 带参数的url // 带参数的url
if (!_parser.Params().empty()) { if (!_parser.params().empty()) {
url += "?"; url += "?";
url += _parser.Params(); url += _parser.params();
} }
// 解析带上协议+参数完整的url // 解析带上协议+参数完整的url
@ -254,7 +311,7 @@ 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("");
@ -281,7 +338,11 @@ bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb){
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) {
@ -298,9 +359,7 @@ bool HttpSession::checkLiveStreamFMP4(const function<void()> &cb){
} }
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);
});
}); });
}); });
} }
@ -323,7 +382,11 @@ bool HttpSession::checkLiveStreamTS(const function<void()> &cb){
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]() {
Any ret;
ret.set(static_pointer_cast<SockInfo>(weak_self.lock()));
return ret;
});
_ts_reader->setDetachCB([weak_self]() { _ts_reader->setDetachCB([weak_self]() {
auto strong_self = weak_self.lock(); auto strong_self = weak_self.lock();
if (!strong_self) { if (!strong_self) {
@ -340,9 +403,7 @@ bool HttpSession::checkLiveStreamTS(const function<void()> &cb){
} }
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);
});
}); });
}); });
} }
@ -370,8 +431,7 @@ bool HttpSession::checkLiveStreamFlv(const function<void()> &cb){
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,15 +443,11 @@ 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);
}
void HttpSession::Handle_Req_GET_l(ssize_t &content_len, bool sendBody) {
// 先看看是否为WebSocket请求 // 先看看是否为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;
@ -518,15 +574,6 @@ private:
} }
}; };
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,
@ -552,25 +599,26 @@ void HttpSession::sendResponse(int code,
} }
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) {
@ -583,7 +631,7 @@ void HttpSession::sendResponse(int code,
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头
@ -592,7 +640,7 @@ void HttpSession::sendResponse(int code,
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;
@ -645,7 +693,7 @@ string HttpSession::urlDecode(const string &str){
} }
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);
} }
@ -671,7 +719,7 @@ bool HttpSession::emitHttpEvent(bool doInvoke){
}; };
///////////////////广播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());
@ -687,74 +735,8 @@ std::string HttpSession::get_peer_ip() {
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);
ssize_t totalContentLen = _parser["Content-Length"].empty() ? -1 : atoll(_parser["Content-Length"].data());
if(totalContentLen == 0){
//content为空
//emitHttpEvent内部会选择是否关闭连接
emitHttpEvent(true); 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) {

View File

@ -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>;

View File

@ -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);

View File

@ -202,7 +202,11 @@ PlayerProxy::~PlayerProxy() {
_timer.reset(); _timer.reset();
// 避免析构时, 忘记回调api请求 // 避免析构时, 忘记回调api请求
if (_on_play) { if (_on_play) {
try {
_on_play(SockException(Err_shutdown, "player proxy close")); _on_play(SockException(Err_shutdown, "player proxy close"));
} catch (std::exception &ex) {
WarnL << "Exception occurred: " << ex.what();
}
_on_play = nullptr; _on_play = nullptr;
} }
} }

View File

@ -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);

View File

@ -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);

View File

@ -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,72 +16,63 @@ 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;
//最小允许设置为00个切片代表点播 //最小允许设置为00个切片代表点播
_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";
index_str += (_is_fmp4 ? "#EXT-X-VERSION:7\n" : "#EXT-X-VERSION:4\n");
if (_seg_number == 0) { if (_seg_number == 0) {
// 录像点播支持时移 index_str += "#EXT-X-PLAYLIST-TYPE:EVENT\n";
snprintf(file_content, sizeof(file_content),
"#EXTM3U\n"
"#EXT-X-PLAYLIST-TYPE:EVENT\n"
"#EXT-X-VERSION:4\n"
"#EXT-X-TARGETDURATION:%u\n"
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
(maxSegmentDuration + 999) / 1000,
sequence);
} 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) {
@ -89,7 +81,7 @@ void HlsMaker::inputData(void *data, size_t len, uint64_t timestamp, bool is_idr
} }
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 {
@ -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;

View File

@ -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;

View File

@ -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 &params, uint32_t bufSize, float seg_duration,
const string &params, 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;
} }
@ -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);
@ -97,7 +90,7 @@ string HlsMakerImp::onOpenSegment(uint64_t index) {
_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,9 +137,8 @@ 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) {
@ -145,7 +149,7 @@ void HlsMakerImp::onFlushLastSegment(uint64_t duration_ms) {
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,7 +170,7 @@ 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 {

View File

@ -21,13 +21,8 @@ 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 &params, uint32_t bufSize = 64 * 1024,
const std::string &params, 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