diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index db67603d..c18493ac 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,6 +1,17 @@ name: Docker -on: [push, pull_request] +on: + push: + branches: + - "master" + - "feature/*" + - "release/*" + + pull_request: + branches: + - "master" + - "feature/*" + - "release/*" env: # Use docker.io for Docker Hub if empty diff --git a/3rdpart/CMakeLists.txt b/3rdpart/CMakeLists.txt index 44e3a551..c90129d3 100644 --- a/3rdpart/CMakeLists.txt +++ b/3rdpart/CMakeLists.txt @@ -24,7 +24,11 @@ ############################################################################## # jsoncpp -aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/src/lib_json JSONCPP_SRC_LIST) +file(GLOB JSONCPP_SRC_LIST + ${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/include/json/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/src/lib_json/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/src/lib_json/*.h) + add_library(jsoncpp STATIC ${JSONCPP_SRC_LIST}) target_compile_options(jsoncpp PRIVATE ${COMPILE_OPTIONS_DEFAULT}) @@ -43,44 +47,44 @@ set(MediaServer_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/media-server") # TODO: 补一个函数处理各种库 # 添加 mov、flv 库用于 MP4 录制 -if(ENABLE_MP4) - message(STATUS "ENABLE_MP4 defined") - +if (ENABLE_MP4 OR ENABLE_HLS_FMP4) # MOV set(MediaServer_MOV_ROOT ${MediaServer_ROOT}/libmov) aux_source_directory(${MediaServer_MOV_ROOT}/include MOV_SRC_LIST) - aux_source_directory(${MediaServer_MOV_ROOT}/source MOV_SRC_LIST) + aux_source_directory(${MediaServer_MOV_ROOT}/source MOV_SRC_LIST) add_library(mov STATIC ${MOV_SRC_LIST}) add_library(MediaServer::mov ALIAS mov) - target_compile_definitions(mov - PUBLIC -DENABLE_MP4) - target_compile_options(mov - PRIVATE ${COMPILE_OPTIONS_DEFAULT}) + target_compile_options(mov PRIVATE ${COMPILE_OPTIONS_DEFAULT}) target_include_directories(mov - PRIVATE - "$" - PUBLIC - "$") + PRIVATE + "$" + PUBLIC + "$") # FLV set(MediaServer_FLV_ROOT ${MediaServer_ROOT}/libflv) aux_source_directory(${MediaServer_FLV_ROOT}/include FLV_SRC_LIST) - aux_source_directory(${MediaServer_FLV_ROOT}/source FLV_SRC_LIST) + aux_source_directory(${MediaServer_FLV_ROOT}/source FLV_SRC_LIST) add_library(flv STATIC ${FLV_SRC_LIST}) add_library(MediaServer::flv ALIAS flv) - target_compile_options(flv - PRIVATE ${COMPILE_OPTIONS_DEFAULT}) + target_compile_options(flv PRIVATE ${COMPILE_OPTIONS_DEFAULT}) target_include_directories(flv - PRIVATE - "$" - PUBLIC - "$") + PRIVATE + "$" + PUBLIC + "$") - update_cached_list(MK_LINK_LIBRARIES - MediaServer::flv MediaServer::mov) - update_cached_list(MK_COMPILE_DEFINITIONS - ENABLE_MP4) -endif() + update_cached_list(MK_LINK_LIBRARIES MediaServer::flv MediaServer::mov) + + if (ENABLE_MP4) + message(STATUS "ENABLE_MP4 defined") + update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_MP4) + endif () + if (ENABLE_HLS_FMP4) + message(STATUS "ENABLE_HLS_FMP4 defined") + update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS_FMP4) + endif () +endif () # 添加 mpeg 用于支持 ts 生成 if(ENABLE_RTPPROXY OR ENABLE_HLS) @@ -104,9 +108,11 @@ if(ENABLE_RTPPROXY OR ENABLE_HLS) update_cached_list(MK_LINK_LIBRARIES MediaServer::mpeg) if(ENABLE_RTPPROXY) + message(STATUS "ENABLE_RTPPROXY defined") update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_RTPPROXY) endif() if(ENABLE_HLS) + message(STATUS "ENABLE_HLS defined") update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS) endif() endif() diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index b30eeca0..b11582c3 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit b30eeca034456417dfca72aa0d2258031013a5e6 +Subproject commit b11582c38e8dbbb8d93ca9ce33c9a0b0cd58f59a diff --git a/AUTHORS b/AUTHORS index 4b5e43fc..57928af2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -73,4 +73,15 @@ WuPeng [ljx0305](https://github.com/ljx0305) [朱如洪 ](https://github.com/zhu410289616) [lijin](https://github.com/1461521844lijin) -[PioLing](https://github.com/PioLing) \ No newline at end of file +[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) \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b110377..39c6c517 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,8 +39,10 @@ option(ENABLE_FAAC "Enable FAAC" OFF) option(ENABLE_FFMPEG "Enable FFmpeg" OFF) option(ENABLE_HLS "Enable HLS" ON) option(ENABLE_JEMALLOC_STATIC "Enable static linking to the jemalloc library" OFF) +option(ENABLE_JEMALLOC_DUMP "Enable jemalloc to dump malloc statistics" OFF) option(ENABLE_MEM_DEBUG "Enable Memory Debug" OFF) option(ENABLE_MP4 "Enable MP4" ON) +option(ENABLE_HLS_FMP4 "Enable HLS-FMP4" ON) option(ENABLE_MSVC_MT "Enable MSVC Mt/Mtd lib" ON) option(ENABLE_MYSQL "Enable MySQL" OFF) option(ENABLE_OPENSSL "Enable OpenSSL" ON) @@ -200,8 +202,8 @@ if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") endif() # mediakit 以及各个 runtime 依赖 -update_cached_list(MK_LINK_LIBRARIES "") -update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_VERSION) +update_cached(MK_LINK_LIBRARIES "") +update_cached(MK_COMPILE_DEFINITIONS ENABLE_VERSION) if (DISABLE_REPORT) update_cached_list(MK_COMPILE_DEFINITIONS DISABLE_REPORT) @@ -334,7 +336,11 @@ if(ENABLE_JEMALLOC_STATIC) if(NOT EXISTS ${DEP_ROOT_DIR}) file(MAKE_DIRECTORY ${DEP_ROOT_DIR}) endif() - + if (ENABLE_JEMALLOC_DUMP) + set(ENABLE_JEMALLOC_STAT ON) + else () + set(ENABLE_JEMALLOC_STAT OFF) + endif () include(Jemalloc) include_directories(SYSTEM ${DEP_ROOT_DIR}/${JEMALLOC_NAME}/include/jemalloc) link_directories(${DEP_ROOT_DIR}/${JEMALLOC_NAME}/lib) @@ -348,6 +354,12 @@ if(JEMALLOC_FOUND) message(STATUS "found library: ${JEMALLOC_LIBRARIES}") include_directories(${JEMALLOC_INCLUDE_DIR}) update_cached_list(MK_LINK_LIBRARIES ${JEMALLOC_LIBRARIES}) + add_definitions(-DUSE_JEMALLOC) + message(STATUS "jemalloc will be used to avoid memory fragmentation") + if (ENABLE_JEMALLOC_DUMP) + add_definitions(-DENABLE_JEMALLOC_DUMP) + message(STATUS "jemalloc will save memory usage statistics when the program exits") + endif () endif() # 查找 openssl 是否安装 @@ -449,11 +461,6 @@ if(ENABLE_API) add_subdirectory(api) endif() -# IOS 不编译可执行程序 -if(IOS) - return() -endif() - ############################################################################## if(ENABLE_PLAYER AND ENABLE_FFMPEG) @@ -461,13 +468,20 @@ if(ENABLE_PLAYER AND ENABLE_FFMPEG) endif() #MediaServer主程序 -add_subdirectory(server) +if(ENABLE_SERVER) + add_subdirectory(server) +endif() # Android 会 add_subdirectory 并依赖该变量 -if(ENABLE_SERVER_LIB) +if(ENABLE_SERVER_LIB AND NOT CMAKE_PARENT_LIST_FILE STREQUAL CMAKE_CURRENT_LIST_FILE) set(MK_LINK_LIBRARIES ${MK_LINK_LIBRARIES} PARENT_SCOPE) endif() +# IOS 不编译可执行程序 +if(IOS) + return() +endif() + #cpp测试demo程序 if (ENABLE_TESTS) add_subdirectory(tests) diff --git a/README.md b/README.md index fa9f50d5..97fb5751 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ ## 功能清单 ### 功能一览 -功能一览 +功能一览 - RTSP[S] - RTSP[S] 服务器,支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备 @@ -63,14 +63,16 @@ - RTMP[S] 发布服务器,支持录制发布流 - RTMP[S] 播放器,支持RTMP代理,支持生成静音音频 - RTMP[S] 推流客户端 - - 支持http[s]-flv直播 + - 支持http[s]-flv直播服务器 + - 支持http[s]-flv直播播放器 - 支持websocket-flv直播 - 支持H264/H265/AAC/G711/OPUS编码,其他编码能转发但不能转协议 - 支持[RTMP-H265](https://github.com/ksvc/FFmpeg/wiki) - 支持[RTMP-OPUS](https://github.com/ZLMediaKit/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81) + - 支持[enhanced-rtmp(H265)](https://github.com/veovera/enhanced-rtmp) - HLS - - 支持HLS文件生成,自带HTTP文件服务器 + - 支持HLS文件(mpegts/fmp4)生成,自带HTTP文件服务器 - 通过cookie追踪技术,可以模拟HLS播放为长连接,可以实现HLS按需拉流、播放统计等业务 - 支持HLS播发器,支持拉流HLS转rtsp/rtmp/mp4 - 支持H264/H265/AAC/G711/OPUS编码 @@ -167,30 +169,36 @@ bash build_docker_images.sh ``` ## 合作项目 + + - 视频管理平台 + - [wvp-GB28181-pro](https://github.com/648540858/wvp-GB28181-pro) java实现的开箱即用的GB28181协议视频平台 + - [AKStream](https://github.com/chatop2020/AKStream) c#实现的全功能的软NVR接口/GB28181平台 + - [BXC_SipServer](https://github.com/any12345com/BXC_SipServer) c++实现的国标GB28181流媒体信令服务器 + - [gosip](https://github.com/panjjo/gosip) golang实现的GB28181服务器 + - [FreeEhome](https://github.com/tsingeye/FreeEhome) golang实现的海康ehome服务器 + + - 播放器 + - [h265web.js](https://github.com/numberwolf/h265web.js) 基于wasm支持H265的播放器,支持本项目多种专属协议 + - [jessibuca](https://github.com/langhuihui/jessibuca) 基于wasm支持H265的播放器 + - [wsPlayer](https://github.com/v354412101/wsPlayer) 基于MSE的websocket-fmp4播放器 + - [BXC_gb28181Player](https://github.com/any12345com/BXC_gb28181Player) C++开发的支持国标GB28181协议的视频流播放器 - - 可视化管理网站 - - [最新的前后端分离web项目,支持webrtc播放](https://github.com/langmansh/AKStreamNVR) - - [基于ZLMediaKit主线的管理WEB网站](https://gitee.com/kkkkk5G/MediaServerUI) - - [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI) - - [一个非常漂亮的可视化后台管理系统](https://github.com/MingZhuLiu/ZLMediaServerManagent) - - - 流媒体管理平台 - - [GB28181完整解决方案,自带web管理网站,支持webrtc、h265播放](https://github.com/648540858/wvp-GB28181-pro) - - [功能强大的流媒体控制管理接口平台,支持GB28181](https://github.com/chatop2020/AKStream) - - [Go实现的GB28181服务器](https://github.com/panjjo/gosip) - - [node-js版本的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http) - - [Go实现的海康ehome服务器](https://github.com/tsingeye/FreeEhome) - - - 客户端 - - [c sdk完整c#包装库](https://github.com/malegend/ZLMediaKit.Autogen) +- WEB管理网站 + - [AKStreamNVR](https://github.com/langmansh/AKStreamNVR) 前后端分离web项目,支持webrtc播放 + + - SDK + - [c# sdk](https://github.com/malegend/ZLMediaKit.Autogen) 本项目c sdk完整c#包装库 + - [metaRTC](https://github.com/metartc/metaRTC) 全国产纯c webrtc sdk + + - 其他项目(已停止更新) + - [NodeJS实现的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http) + - [基于ZLMediaKit主线的管理WEB网站 ](https://gitee.com/kkkkk5G/MediaServerUI) + - [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI) + - [一个非常漂亮的可视化后台管理系统](https://github.com/MingZhuLiu/ZLMediaServerManagent) - [基于C SDK实现的推流客户端](https://github.com/hctym1995/ZLM_ApiDemo) - [C#版本的Http API与Hook](https://github.com/chengxiaosheng/ZLMediaKit.HttpApi) - [DotNetCore的RESTful客户端](https://github.com/MingZhuLiu/ZLMediaKit.DotNetCore.Sdk) - - 播放器 - - [基于wasm支持H265的播放器](https://github.com/numberwolf/h265web.js) - - [基于MSE的websocket-fmp4播放器](https://github.com/v354412101/wsPlayer) - - [全国产webrtc sdk(metaRTC)](https://github.com/metartc/metaRTC) ## 授权协议 @@ -202,9 +210,12 @@ bash build_docker_images.sh ## 联系方式 - 邮箱:<1213642868@qq.com>(本项目相关或流媒体相关问题请走issue流程,否则恕不邮件答复) - - QQ群:两个qq群已满员(共4000人),后续将不再新建qq群,用户可加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364)提问以支持本项目。 - - 关注微信公众号: + - 请关注微信公众号获取最新消息推送: + + - 也可以自愿有偿加入知识星球咨询和获取资料: + + ## 怎么提问? @@ -212,9 +223,7 @@ bash build_docker_images.sh - 1、仔细看下readme、wiki,如果有必要可以查看下issue. - 2、如果您的问题还没解决,可以提issue. - - 3、有些问题,如果不具备参考性的,无需在issue提的,可以在qq群提. - - 4、QQ私聊一般不接受无偿技术咨询和支持([为什么不提倡QQ私聊](https://github.com/ZLMediaKit/ZLMediaKit/wiki/%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E5%BB%BA%E8%AE%AEQQ%E7%A7%81%E8%81%8A%E5%92%A8%E8%AF%A2%E9%97%AE%E9%A2%98%EF%BC%9F)). - - 5、如果需要获取更及时贴心的技术支持,可以有偿加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364). + - 3、如果需要获取更及时贴心的技术支持,可以有偿加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364). ## 特别感谢 @@ -309,6 +318,21 @@ bash build_docker_images.sh [朱如洪 ](https://github.com/zhu410289616) [lijin](https://github.com/1461521844lijin) [PioLing](https://github.com/PioLing) +[BackT0TheFuture](https://github.com/BackT0TheFuture) +[perara](https://github.com/perara) +[codeRATny](https://github.com/codeRATny) +[dengjfzh](https://github.com/dengjfzh) +[百鸣](https://github.com/ixingqiao) +[fruit Juice](https://github.com/xuandu) +[tbago](https://github.com/tbago) +[Luosh](https://github.com/Luosh) +[linxiaoyan87](https://github.com/linxiaoyan) +[waken](https://github.com/mc373906408) +[Deepslient](https://github.com/Deepslient) + +同时感谢JetBrains对开源项目的支持,本项目使用CLion开发与调试: + +[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/CLion.svg)](https://jb.gg/OpenSourceSupport) ## 使用案例 diff --git a/README_en.md b/README_en.md index b7410f8b..5eee72d1 100644 --- a/README_en.md +++ b/README_en.md @@ -45,7 +45,7 @@ ## Feature List ### Overview of Features -Overview of Features +Overview of Features - RTSP[S] - RTSP[S] server, supports RTMP/MP4/HLS to RTSP[S] conversion, supports devices such as Amazon Echo Show @@ -62,14 +62,16 @@ - RTMP[S] publishing server, supports recording and publishing streams - RTMP[S] player, supports RTMP proxy, supports generating silent audio - RTMP[S] push client - - Supports http[s]-flv live streaming + - Supports http[s]-flv live streaming server + - Supports http[s]-flv live streaming player - Supports websocket-flv live streaming - Supports H264/H265/AAC/G711/OPUS encoding. Other encodings can be forwarded but cannot be converted to protocol - Supports [RTMP-H265](https://github.com/ksvc/FFmpeg/wiki) - Supports [RTMP-OPUS](https://github.com/ZLMediaKit/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81) + - Supports [enhanced-rtmp(H265)](https://github.com/veovera/enhanced-rtmp) - HLS - - Supports HLS file generation and comes with an HTTP file server + - Supports HLS file(mpegts/fmp4) generation and comes with an HTTP file server - Through cookie tracking technology, it can simulate HLS playback as a long connection, which can achieve HLS on-demand pulling, playback statistics, and other businesses - Supports HLS player and can pull HLS to rtsp/rtmp/mp4 - Supports H264/H265/AAC/G711/OPUS encoding @@ -348,6 +350,7 @@ bash build_docker_images.sh - Media management platform - [GB28181 complete solution with web management website, supporting webrtc and h265 playback](https://github.com/648540858/wvp-GB28181-pro) - [Powerful media control and management interface platform, supporting GB28181](https://github.com/chatop2020/AKStream) + - [GB28181 server implemented in C++](https://github.com/any12345com/BXC_SipServer) - [GB28181 server implemented in Go](https://github.com/panjjo/gosip) - [Node-js version of GB28181 platform](https://gitee.com/hfwudao/GB28181_Node_Http) - [Hikvision ehome server implemented in Go](https://github.com/tsingeye/FreeEhome) @@ -362,6 +365,7 @@ bash build_docker_images.sh - [Player supporting H265 based on wasm](https://github.com/numberwolf/h265web.js) - [WebSocket-fmp4 player based on MSE](https://github.com/v354412101/wsPlayer) - [Domestic webrtc sdk(metaRTC)](https://github.com/metartc/metaRTC) + - [GB28181 player implemented in C++](https://github.com/any12345com/BXC_gb28181Player) ## License @@ -478,6 +482,21 @@ Thanks to all those who have supported this project in various ways, including b [朱如洪 ](https://github.com/zhu410289616) [lijin](https://github.com/1461521844lijin) [PioLing](https://github.com/PioLing) +[BackT0TheFuture](https://github.com/BackT0TheFuture) +[perara](https://github.com/perara) +[codeRATny](https://github.com/codeRATny) +[dengjfzh](https://github.com/dengjfzh) +[百鸣](https://github.com/ixingqiao) +[fruit Juice](https://github.com/xuandu) +[tbago](https://github.com/tbago) +[Luosh](https://github.com/Luosh) +[linxiaoyan87](https://github.com/linxiaoyan) +[waken](https://github.com/mc373906408) +[Deepslient](https://github.com/Deepslient) + +Also thank to JetBrains for their support for open source project, we developed and debugged zlmediakit with CLion: + +[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/CLion.svg)](https://jb.gg/OpenSourceSupport) ## Use Cases diff --git a/api/CMakeLists.txt b/api/CMakeLists.txt index c7962a0f..102a03a6 100644 --- a/api/CMakeLists.txt +++ b/api/CMakeLists.txt @@ -30,13 +30,6 @@ file(GLOB API_SRC_LIST set(LINK_LIBRARIES ${MK_LINK_LIBRARIES}) -if(IOS) - add_library(mk_api STATIC ${API_SRC_LIST}) - target_link_libraries(mk_api - PRIVATE ${LINK_LIBRARIES}) - return() -endif () - set(COMPILE_DEFINITIONS ${MK_COMPILE_DEFINITIONS}) if (MSVC) @@ -46,6 +39,8 @@ endif () if(ENABLE_API_STATIC_LIB) add_library(mk_api STATIC ${API_SRC_LIST}) list(APPEND COMPILE_DEFINITIONS MediaKitApi_STATIC) +elseif(IOS) + add_library(mk_api STATIC ${API_SRC_LIST}) else() add_library(mk_api SHARED ${API_SRC_LIST}) endif() @@ -74,8 +69,6 @@ generate_export_header(mk_api STATIC_DEFINE MediaKitApi_STATIC EXPORT_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/mk_export.h") -add_subdirectory(tests) - file(GLOB API_HEADER_LIST include/*.h ${CMAKE_CURRENT_BINARY_DIR}/*.h) install(FILES ${API_HEADER_LIST} DESTINATION ${INSTALL_PATH_INCLUDE}) @@ -83,3 +76,12 @@ install(TARGETS mk_api ARCHIVE DESTINATION ${INSTALL_PATH_LIB} LIBRARY DESTINATION ${INSTALL_PATH_LIB} RUNTIME DESTINATION ${INSTALL_PATH_RUNTIME}) + +# IOS 跳过测试代码 +if(IOS) + return() +endif() + +if (ENABLE_TESTS) + add_subdirectory(tests) +endif() diff --git a/api/include/mk_events.h b/api/include/mk_events.h index c628a0ec..0db68a5f 100644 --- a/api/include/mk_events.h +++ b/api/include/mk_events.h @@ -166,6 +166,17 @@ typedef struct { */ void (API_CALL *on_mk_log)(int level, const char *file, int line, const char *function, const char *message); + /** + * 发送rtp流失败回调,适用于mk_media_source_start_send_rtp/mk_media_start_send_rtp接口触发的rtp发送 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream 流id + * @param ssrc ssrc的10进制打印,通过atoi转换为整型 + * @param err 错误代码 + * @param msg 错误提示 + */ + void(API_CALL *on_mk_media_send_rtp_stop)(const char *vhost, const char *app, const char *stream, const char *ssrc, int err, const char *msg); + } mk_events; diff --git a/api/include/mk_events_objects.h b/api/include/mk_events_objects.h index c114c783..3dc9a2c3 100644 --- a/api/include/mk_events_objects.h +++ b/api/include/mk_events_objects.h @@ -12,6 +12,7 @@ #define MK_EVENT_OBJECTS_H #include "mk_common.h" #include "mk_tcp.h" +#include "mk_track.h" #ifdef __cplusplus extern "C" { #endif @@ -95,6 +96,13 @@ API_EXPORT const char* API_CALL mk_media_source_get_stream(const mk_media_source API_EXPORT int API_CALL mk_media_source_get_reader_count(const mk_media_source ctx); //MediaSource::totalReaderCount() API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_source ctx); +// get track count from MediaSource +API_EXPORT int API_CALL mk_media_source_get_track_count(const mk_media_source ctx); +// copy track reference by index from MediaSource, please use mk_track_unref to release it +API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx, int index); +// MediaSource::broadcastMessage +API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len); + /** * 直播源在ZLMediaKit中被称作为MediaSource, * 目前支持3种,分别是RtmpMediaSource、RtspMediaSource、HlsMediaSource diff --git a/api/include/mk_frame.h b/api/include/mk_frame.h index 40ba4a4d..62498096 100644 --- a/api/include/mk_frame.h +++ b/api/include/mk_frame.h @@ -117,6 +117,108 @@ API_EXPORT uint64_t API_CALL mk_frame_get_pts(mk_frame frame); */ API_EXPORT uint32_t API_CALL mk_frame_get_flags(mk_frame frame); +////////////////////////////////////////////////////////////////////// + +typedef struct mk_buffer_t *mk_buffer; +typedef struct mk_frame_merger_t *mk_frame_merger; + +/** + * 创建帧合并器 + * @param type 起始头类型,0: none, 1: h264_prefix/AnnexB(0x 00 00 00 01), 2: mp4_nal_size(avcC) + * @return 帧合并器 + */ +API_EXPORT mk_frame_merger API_CALL mk_frame_merger_create(int type); + +/** + * 销毁帧合并器 + * @param ctx 对象指针 + */ +API_EXPORT void API_CALL mk_frame_merger_release(mk_frame_merger ctx); + +/** + * 清空merger对象缓冲,方便复用 + * @param ctx 对象指针 + */ +API_EXPORT void API_CALL mk_frame_merger_clear(mk_frame_merger ctx); + +/** + * 合并帧回调函数 + * @param user_data 用户数据指针 + * @param dts 解码时间戳 + * @param pts 显示时间戳 + * @param buffer 合并后数据buffer对象 + * @param have_key_frame 合并后数据中是否包含关键帧 + */ +typedef void(API_CALL *on_mk_frame_merger)(void *user_data, uint64_t dts, uint64_t pts, mk_buffer buffer, int have_key_frame); + +/** + * 输入frame到merger对象并合并 + * @param ctx 对象指针 + * @param frame 帧数据 + * @param cb 帧合并回调函数 + * @param user_data 帧合并回调函数用户数据指针 + */ +API_EXPORT void API_CALL mk_frame_merger_input(mk_frame_merger ctx, mk_frame frame, on_mk_frame_merger cb, void *user_data); + +/** + * 强制flush merger对象缓冲,调用此api前需要确保先调用mk_frame_merger_input函数并且回调参数有效 + * @param ctx 对象指针 + */ +API_EXPORT void API_CALL mk_frame_merger_flush(mk_frame_merger ctx); + +////////////////////////////////////////////////////////////////////// + +typedef struct mk_mpeg_muxer_t *mk_mpeg_muxer; + +/** + * mpeg-ps/ts 打包器输出回调函数 + * @param user_data 设置回调时的用户数据指针 + * @param muxer 对象 + * @param frame 帧数据 + * @param size 帧数据长度 + * @param timestamp 时间戳 + * @param key_pos 是否关键帧 + */ +typedef void(API_CALL *on_mk_mpeg_muxer_frame)(void *user_data, mk_mpeg_muxer muxer, const char *frame, size_t size, uint64_t timestamp, int key_pos); + +/** + * mpeg-ps/ts 打包器 + * @param cb 打包回调函数 + * @param user_data 回调用户数据指针 + * @param is_ps 是否是ps + * @return 打包器对象 + */ +API_EXPORT mk_mpeg_muxer API_CALL mk_mpeg_muxer_create(on_mk_mpeg_muxer_frame cb, void *user_data, int is_ps); + +/** + * 删除mpeg-ps/ts 打包器 + * @param ctx 打包器 + */ +API_EXPORT void API_CALL mk_mpeg_muxer_release(mk_mpeg_muxer ctx); + +/** + * 添加音视频track + * @param ctx mk_mpeg_muxer对象 + * @param track mk_track对象,音视频轨道 + */ +API_EXPORT void API_CALL mk_mpeg_muxer_init_track(mk_mpeg_muxer ctx, void* track); + +/** + * 初始化track完毕后调用此函数, + * 在单track(只有音频或视频)时,因为ZLMediaKit不知道后续是否还要添加track,所以会多等待3秒钟 + * 如果产生的流是单Track类型,请调用此函数以便加快流生成速度,当然不调用该函数,影响也不大(会多等待3秒) + * @param ctx 对象指针 + */ +API_EXPORT void API_CALL mk_mpeg_muxer_init_complete(mk_mpeg_muxer ctx); + +/** + * 输入frame对象 + * @param ctx mk_mpeg_muxer对象 + * @param frame 帧对象 + * @return 1代表成功,0失败 + */ +API_EXPORT int API_CALL mk_mpeg_muxer_input_frame(mk_mpeg_muxer ctx, mk_frame frame); + #ifdef __cplusplus } #endif diff --git a/api/include/mk_recorder.h b/api/include/mk_recorder.h index c9053db5..a23e50f4 100644 --- a/api/include/mk_recorder.h +++ b/api/include/mk_recorder.h @@ -58,7 +58,7 @@ API_EXPORT int API_CALL mk_recorder_is_recording(int type, const char *vhost, co /** * 开始录制 - * @param type 0:hls,1:MP4 + * @param type 0:hls-ts,1:MP4,2:hls-fmp4,3:http-fmp4,4:http-ts * @param vhost 虚拟主机 * @param app 应用名 * @param stream 流id @@ -70,7 +70,7 @@ API_EXPORT int API_CALL mk_recorder_start(int type, const char *vhost, const cha /** * 停止录制 - * @param type 0:hls,1:MP4 + * @param type 0:hls-ts,1:MP4,2:hls-fmp4,3:http-fmp4,4:http-ts * @param vhost 虚拟主机 * @param app 应用名 * @param stream 流id diff --git a/api/source/mk_common.cpp b/api/source/mk_common.cpp index 1604af61..a45068ce 100644 --- a/api/source/mk_common.cpp +++ b/api/source/mk_common.cpp @@ -159,7 +159,7 @@ API_EXPORT void API_CALL mk_set_option(const char *key, const char *val) { } mINI::Instance()[key] = val; //广播配置文件热加载 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig); + NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig); } API_EXPORT const char * API_CALL mk_get_option(const char *key) diff --git a/api/source/mk_events.cpp b/api/source/mk_events.cpp index 43e3362c..6e81ccc5 100644 --- a/api/source/mk_events.cpp +++ b/api/source/mk_events.cpp @@ -160,6 +160,13 @@ API_EXPORT void API_CALL mk_events_listen(const mk_events *events){ s_events.on_mk_log((int) ctx->_level, ctx->_file.data(), ctx->_line, ctx->_function.data(), log.data()); } }); + + NoticeCenter::Instance().addListener(&s_tag, Broadcast::kBroadcastSendRtpStopped,[](BroadcastSendRtpStoppedArgs){ + if (s_events.on_mk_media_send_rtp_stop) { + s_events.on_mk_media_send_rtp_stop(sender.getMediaTuple().vhost.c_str(), sender.getMediaTuple().app.c_str(), + sender.getMediaTuple().stream.c_str(), ssrc.c_str(), ex.getErrCode(), ex.what()); + } + }); }); } diff --git a/api/source/mk_events_objects.cpp b/api/source/mk_events_objects.cpp index 1903fa05..3833017f 100644 --- a/api/source/mk_events_objects.cpp +++ b/api/source/mk_events_objects.cpp @@ -86,17 +86,17 @@ API_EXPORT const char* API_CALL mk_mp4_info_get_stream(const mk_mp4_info ctx){ API_EXPORT const char* API_CALL mk_parser_get_method(const mk_parser ctx){ assert(ctx); Parser *parser = (Parser *)ctx; - return parser->Method().c_str(); + return parser->method().c_str(); } API_EXPORT const char* API_CALL mk_parser_get_url(const mk_parser ctx){ assert(ctx); Parser *parser = (Parser *)ctx; - return parser->Url().c_str(); + return parser->url().c_str(); } API_EXPORT const char* API_CALL mk_parser_get_url_params(const mk_parser ctx){ assert(ctx); Parser *parser = (Parser *)ctx; - return parser->Params().c_str(); + return parser->params().c_str(); } API_EXPORT const char* API_CALL mk_parser_get_url_param(const mk_parser ctx,const char *key){ assert(ctx && key); @@ -106,7 +106,7 @@ API_EXPORT const char* API_CALL mk_parser_get_url_param(const mk_parser ctx,cons API_EXPORT const char* API_CALL mk_parser_get_tail(const mk_parser ctx){ assert(ctx); Parser *parser = (Parser *)ctx; - return parser->Tail().c_str(); + return parser->protocol().c_str(); } API_EXPORT const char* API_CALL mk_parser_get_header(const mk_parser ctx,const char *key){ assert(ctx && key); @@ -117,9 +117,9 @@ API_EXPORT const char* API_CALL mk_parser_get_content(const mk_parser ctx, size_ assert(ctx); Parser *parser = (Parser *)ctx; if(length){ - *length = parser->Content().size(); + *length = parser->content().size(); } - return parser->Content().c_str(); + return parser->content().c_str(); } ///////////////////////////////////////////MediaInfo///////////////////////////////////////////// @@ -174,17 +174,17 @@ API_EXPORT const char* API_CALL mk_media_source_get_schema(const mk_media_source API_EXPORT const char* API_CALL mk_media_source_get_vhost(const mk_media_source ctx){ assert(ctx); MediaSource *src = (MediaSource *)ctx; - return src->getVhost().c_str(); + return src->getMediaTuple().vhost.c_str(); } API_EXPORT const char* API_CALL mk_media_source_get_app(const mk_media_source ctx){ assert(ctx); MediaSource *src = (MediaSource *)ctx; - return src->getApp().c_str(); + return src->getMediaTuple().app.c_str(); } API_EXPORT const char* API_CALL mk_media_source_get_stream(const mk_media_source ctx){ assert(ctx); MediaSource *src = (MediaSource *)ctx; - return src->getId().c_str(); + return src->getMediaTuple().stream.c_str(); } API_EXPORT int API_CALL mk_media_source_get_reader_count(const mk_media_source ctx){ assert(ctx); @@ -198,6 +198,32 @@ API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_so return src->totalReaderCount(); } +API_EXPORT int API_CALL mk_media_source_get_track_count(const mk_media_source ctx) { + assert(ctx); + MediaSource *src = (MediaSource *)ctx; + return src->getTracks(false).size(); +} + +API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx, int index) { + assert(ctx); + MediaSource *src = (MediaSource *)ctx; + auto tracks = src->getTracks(false); + if (index < 0 && index >= tracks.size()) { + return nullptr; + } + return (mk_track) new Track::Ptr(std::move(tracks[index])); +} + +API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len) { + assert(ctx && msg && len); + MediaSource *src = (MediaSource *)ctx; + + Any any; + Buffer::Ptr buffer = std::make_shared(std::string(msg, len)); + any.set(std::move(buffer)); + return src->broadcastMessage(any); +} + API_EXPORT int API_CALL mk_media_source_close(const mk_media_source ctx,int force){ assert(ctx); MediaSource *src = (MediaSource *)ctx; diff --git a/api/source/mk_frame.cpp b/api/source/mk_frame.cpp index 8c2e4ff6..7a7cdb85 100644 --- a/api/source/mk_frame.cpp +++ b/api/source/mk_frame.cpp @@ -9,10 +9,12 @@ */ #include "mk_frame.h" +#include "mk_track.h" #include "Extension/Frame.h" #include "Extension/H264.h" #include "Extension/H265.h" #include "Extension/AAC.h" +#include "Record/MPEG.h" using namespace mediakit; @@ -178,3 +180,92 @@ API_EXPORT uint32_t API_CALL mk_frame_get_flags(mk_frame frame) { } return ret; } + +API_EXPORT mk_frame_merger API_CALL mk_frame_merger_create(int type) { + return reinterpret_cast(new FrameMerger(type)); +} + +API_EXPORT void API_CALL mk_frame_merger_release(mk_frame_merger ctx) { + assert(ctx); + delete reinterpret_cast(ctx); +} + +API_EXPORT void API_CALL mk_frame_merger_clear(mk_frame_merger ctx) { + assert(ctx); + reinterpret_cast(ctx)->clear(); +} + +API_EXPORT void API_CALL mk_frame_merger_flush(mk_frame_merger ctx) { + assert(ctx); + reinterpret_cast(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(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; + 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 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 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(ret), frame, size, timestamp, key_pos); + }); + return reinterpret_cast(ret); +} + +API_EXPORT void API_CALL mk_mpeg_muxer_release(mk_mpeg_muxer ctx){ + assert(ctx); + auto ptr = reinterpret_cast(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(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(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(ctx); + return ptr->inputFrame(*((Frame::Ptr *) frame)); +} \ No newline at end of file diff --git a/api/source/mk_httpclient.cpp b/api/source/mk_httpclient.cpp index 9c52cd33..43de7a33 100755 --- a/api/source/mk_httpclient.cpp +++ b/api/source/mk_httpclient.cpp @@ -108,7 +108,7 @@ API_EXPORT void API_CALL mk_http_requester_add_header(mk_http_requester ctx,cons API_EXPORT const char* API_CALL mk_http_requester_get_response_status(mk_http_requester ctx){ assert(ctx); HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx; - return (*obj)->response().Url().c_str(); + return (*obj)->response().status().c_str(); } API_EXPORT const char* API_CALL mk_http_requester_get_response_header(mk_http_requester ctx,const char *key){ @@ -121,9 +121,9 @@ API_EXPORT const char* API_CALL mk_http_requester_get_response_body(mk_http_requ assert(ctx); HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx; if(length){ - *length = (*obj)->response().Content().size(); + *length = (*obj)->response().content().size(); } - return (*obj)->response().Content().c_str(); + return (*obj)->response().content().c_str(); } API_EXPORT mk_parser API_CALL mk_http_requester_get_response(mk_http_requester ctx){ diff --git a/api/source/mk_recorder.cpp b/api/source/mk_recorder.cpp index e16a64c4..f99d7130 100644 --- a/api/source/mk_recorder.cpp +++ b/api/source/mk_recorder.cpp @@ -47,21 +47,25 @@ static inline bool isRecording(Recorder::type type, const string &vhost, const s return src->isRecording(type); } -static inline bool startRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id,const string &customized_path, size_t max_second){ +static inline bool startRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path, size_t max_second) { auto src = MediaSource::find(vhost, app, stream_id); if (!src) { WarnL << "未找到相关的MediaSource,startRecord失败:" << vhost << "/" << app << "/" << stream_id; return false; } - return src->setupRecord(type, true, customized_path, max_second); + bool ret; + src->getOwnerPoller()->sync([&]() { ret = src->setupRecord(type, true, customized_path, max_second); }); + return ret; } -static inline bool stopRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id){ +static inline bool stopRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id) { auto src = MediaSource::find(vhost, app, stream_id); - if(!src){ + if (!src) { return false; } - return src->setupRecord(type, false, "", 0); + bool ret; + src->getOwnerPoller()->sync([&]() { ret = src->setupRecord(type, false, "", 0); }); + return ret; } API_EXPORT int API_CALL mk_recorder_is_recording(int type, const char *vhost, const char *app, const char *stream){ diff --git a/api/source/mk_util.cpp b/api/source/mk_util.cpp index 6c775529..5c5d3197 100644 --- a/api/source/mk_util.cpp +++ b/api/source/mk_util.cpp @@ -65,7 +65,7 @@ API_EXPORT mk_ini API_CALL mk_ini_default() { static void emit_ini_file_reload(mk_ini ini) { if (ini == mk_ini_default()) { // 广播配置文件热加载 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig); + NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig); } } diff --git a/api/tests/CMakeLists.txt b/api/tests/CMakeLists.txt index f2875ff6..8108b71b 100644 --- a/api/tests/CMakeLists.txt +++ b/api/tests/CMakeLists.txt @@ -43,15 +43,3 @@ foreach(TEST_SRC ${TEST_SRC_LIST}) target_link_libraries(${exe_name} mk_api) target_compile_options(${exe_name} PRIVATE ${COMPILE_OPTIONS_DEFAULT}) endforeach() - - - - - - - - - - - - diff --git a/api/tests/pusher.c b/api/tests/pusher.c index a6f3cd2d..2014fd09 100644 --- a/api/tests/pusher.c +++ b/api/tests/pusher.c @@ -9,6 +9,7 @@ */ #include +#include #include "mk_mediakit.h" typedef struct { diff --git a/api/tests/server.c b/api/tests/server.c index 8b283b6b..d331519c 100644 --- a/api/tests/server.c +++ b/api/tests/server.c @@ -9,6 +9,7 @@ */ #include +#include #include "mk_mediakit.h" #define LOG_LEV 4 diff --git a/cmake/Jemalloc.cmake b/cmake/Jemalloc.cmake index 94d46b64..38847e58 100644 --- a/cmake/Jemalloc.cmake +++ b/cmake/Jemalloc.cmake @@ -1,21 +1,34 @@ # Download and build Jemalloc -set(JEMALLOC_VERSION 5.2.1) +set(JEMALLOC_VERSION 5.3.0) set(JEMALLOC_NAME jemalloc-${JEMALLOC_VERSION}) set(JEMALLOC_TAR_PATH ${DEP_ROOT_DIR}/${JEMALLOC_NAME}.tar.bz2) list(APPEND jemalloc_CONFIG_ARGS --disable-initial-exec-tls) -list(APPEND jemalloc_CONFIG_ARGS --without-export) +#list(APPEND jemalloc_CONFIG_ARGS --without-export) +if (ENABLE_JEMALLOC_STAT) + list(APPEND jemalloc_CONFIG_ARGS --enable-stats) + message(STATUS "Jemalloc stats enabled") +else () list(APPEND jemalloc_CONFIG_ARGS --disable-stats) + message(STATUS "Jemalloc stats disabled") +endif () list(APPEND jemalloc_CONFIG_ARGS --disable-libdl) #list(APPEND jemalloc_CONFIG_ARGS --disable-cxx) #list(APPEND jemalloc_CONFIG_ARGS --with-jemalloc-prefix=je_) #list(APPEND jemalloc_CONFIG_ARGS --enable-debug) if(NOT EXISTS ${JEMALLOC_TAR_PATH}) - message(STATUS "Downloading ${JEMALLOC_NAME}...") - file(DOWNLOAD https://github.com/jemalloc/jemalloc/releases/download/${JEMALLOC_VERSION}/${JEMALLOC_NAME}.tar.bz2 - ${JEMALLOC_TAR_PATH}) + set(JEMALLOC_URL https://github.com/jemalloc/jemalloc/releases/download/${JEMALLOC_VERSION}/${JEMALLOC_NAME}.tar.bz2) + message(STATUS "Downloading ${JEMALLOC_NAME} from ${JEMALLOC_URL}") + file(DOWNLOAD ${JEMALLOC_URL} ${JEMALLOC_TAR_PATH} SHOW_PROGRESS STATUS JEMALLOC_DOWNLOAD_STATUS LOG JEMALLOC_DOWNLOAD_LOG) + list(GET JEMALLOC_DOWNLOAD_STATUS 0 JEMALLOC_DOWNLOAD_STATUS_CODE) + if(NOT JEMALLOC_DOWNLOAD_STATUS_CODE EQUAL 0) + file(REMOVE ${JEMALLOC_TAR_PATH}) + message(STATUS "${JEMALLOC_DOWNLOAD_LOG}") + message(FATAL_ERROR "${JEMALLOC_NAME} download failed! error is ${JEMALLOC_DOWNLOAD_STATUS}") + return() + endif () endif() SET( DIR_CONTAINING_JEMALLOC ${DEP_ROOT_DIR}/${JEMALLOC_NAME} ) diff --git a/conf/config.ini b/conf/config.ini index d2b2c0dd..dc343b32 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -22,7 +22,7 @@ bin=/usr/bin/ffmpeg #FFmpeg拉流再推流的命令模板,通过该模板可以设置再编码的一些参数 cmd=%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s #FFmpeg生成截图的命令,可以通过修改该配置改变截图分辨率或质量 -snap=%s -i %s -y -f mjpeg -t 0.001 %s +snap=%s -i %s -y -f mjpeg -frames:v 1 %s #FFmpeg日志的路径,如果置空则不生成FFmpeg日志 #可以为相对(相对于本可执行程序目录)或绝对路径 log=./ffmpeg/ffmpeg.log @@ -32,18 +32,28 @@ restart_sec=0 #转协议相关开关;如果addStreamProxy api和on_publish hook回复未指定转协议参数,则采用这些配置项 [protocol] #转协议时,是否开启帧级时间戳覆盖 -modify_stamp=0 +# 0:采用源视频流绝对时间戳,不做任何改变 +# 1:采用zlmediakit接收数据时的系统时间戳(有平滑处理) +# 2:采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正 +modify_stamp=2 #转协议是否开启音频 enable_audio=1 #添加acc静音音频,在关闭音频时,此开关无效 add_mute_audio=1 +#无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close) +#此配置置1时,此流如果无人观看,将不触发on_none_reader hook回调, +#而是将直接关闭流 +auto_close=0 + #推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。 #置0关闭此特性(推流断开会导致立即断开播放器) #此参数不应大于播放器超时时间;单位毫秒 continue_push_ms=15000 -#是否开启转换为hls +#是否开启转换为hls(mpegts) enable_hls=1 +#是否开启转换为hls(fmp4) +enable_hls_fmp4=0 #是否开启MP4录制 enable_mp4=0 #是否开启转换为rtsp/webrtc @@ -121,7 +131,7 @@ segDur=2 segNum=3 #HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数 segRetain=5 -#是否广播 ts 切片完成通知 +#是否广播 hls切片(ts/fmp4)完成通知(on_record_ts) broadcastRecordTs=0 #直播hls文件删除延时,单位秒,issue: #913 deleteDelaySec=10 @@ -132,9 +142,6 @@ deleteDelaySec=10 segKeep=0 [hook] -#在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然 -#该配置项的目的是为了开发者自己调试测试,该参数暴露后会有泄露隐私的安全隐患 -admin_params=secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc #是否启用hook事件,启用后,推拉流都将进行鉴权 enable=0 #播放器或推流器使用流量事件,置空则关闭 @@ -147,7 +154,7 @@ on_play=https://127.0.0.1/index/hook/on_play on_publish=https://127.0.0.1/index/hook/on_publish #录制mp4切片完成事件 on_record_mp4=https://127.0.0.1/index/hook/on_record_mp4 -# 录制 hls ts 切片完成事件 +# 录制 hls ts(或fmp4) 切片完成事件 on_record_ts=https://127.0.0.1/index/hook/on_record_ts #rtsp播放鉴权事件,此事件中比对rtsp的用户名密码 on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth @@ -159,12 +166,16 @@ on_rtsp_realm=https://127.0.0.1/index/hook/on_rtsp_realm on_shell_login=https://127.0.0.1/index/hook/on_shell_login #直播流注册或注销事件 on_stream_changed=https://127.0.0.1/index/hook/on_stream_changed +#过滤on_stream_changed hook的协议类型,可以选择只监听某些感兴趣的协议;置空则不过滤协议 +stream_changed_schemas=rtsp/rtmp/fmp4/ts/hls/hls.fmp4 #无人观看流事件,通过该事件,可以选择是否关闭无人观看的流。配合general.streamNoneReaderDelayMS选项一起使用 on_stream_none_reader=https://127.0.0.1/index/hook/on_stream_none_reader #播放时,未找到流事件,通过配合hook.on_stream_none_reader事件可以完成按需拉流 on_stream_not_found=https://127.0.0.1/index/hook/on_stream_not_found #服务器启动报告,可以用于服务器的崩溃重启事件监听 on_server_started=https://127.0.0.1/index/hook/on_server_started +#服务器退出报告,当服务器正常退出时触发 +on_server_exited=https://127.0.0.1/index/hook/on_server_exited #server保活上报 on_server_keepalive=https://127.0.0.1/index/hook/on_server_keepalive #发送rtp(startSendRtp)被动关闭时回调 @@ -232,6 +243,8 @@ forbidCacheSuffix= forwarded_ip_header= #默认允许所有跨域请求 allow_cross_domains=1 +#允许访问http api和http文件索引的ip地址范围白名单,置空情况下不做限制 +allow_ip_range=::1,127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255 [multicast] #rtp组播截止组播ip地址 @@ -261,8 +274,6 @@ handshakeSecond=15 #rtmp超时时间,如果该时间内未收到客户端的数据, #或者tcp发送缓存超过这个时间,则会断开连接,单位秒 keepAliveSecond=15 -#在接收rtmp推流时,是否重新生成时间戳(很多推流器的时间戳着实很烂) -modifyStamp=0 #rtmp服务器监听端口 port=1935 #rtmps服务器监听地址 @@ -278,6 +289,9 @@ videoMtuSize=1400 rtpMaxSize=10 # rtp 打包时,低延迟开关,默认关闭(为0),h264存在一帧多个slice(NAL)的情况,在这种情况下,如果开启可能会导致画面花屏 lowLatency=0 +# H264 rtp打包模式是否采用stap-a模式(为了在老版本浏览器上兼容webrtc)还是采用Single NAL unit packet per H.264 模式 +# 有些老的rtsp设备不支持stap-a rtp,设置此配置为0可提高兼容性 +h264_stap_a=1 [rtp_proxy] #导出调试数据(包括rtp/ps/h264)至该目录,置空则关闭数据导出 @@ -359,6 +373,10 @@ port=554 sslport=0 #rtsp 转发是否使用低延迟模式,当开启时,不会缓存rtp包,来提高并发,可以降低一帧的延迟 lowLatency=0 +#强制协商rtp传输方式 (0:TCP,1:UDP,2:MULTICAST,-1:不限制) +#当客户端发起RTSP SETUP的时候如果传输类型和此配置不一致则返回461 Unsupported transport +#迫使客户端重新SETUP并切换到对应协议。目前支持FFMPEG和VLC +rtpTransportType=-1 [shell] #调试telnet服务器接受最大bufffer大小 maxReqSize=1024 diff --git a/default.pem b/default.pem index 9bfab500..e0ec1132 100644 --- a/default.pem +++ b/default.pem @@ -1,89 +1,89 @@ -----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAjTFnNz2btUHR/K8TegU2n06lXsQuW4AfsMGthBLvnz2zdgL2 -uVxfRCuleoRpECEV9I80ibTXAJlouRLbnhLHto8gzyLeQue9/DtxTEGcS7y2aMha -0wAoUcOBv7tAITSnFepGHXoYgyU7HOCvn96U1bzqLTOhxOCH/xy003dwoEC7+Pjl -dsWs/16cpvYiZV/dZvzDDKIpHSIvQy1whi6N0SDpzz/ncThn1z8xcJDx0I1jVR76 -juP7TttbyhqJkO+fOlLn4yP9K5wZ/dPFJn2+bQRCrzGc4SM7J5YOq8ckTTTbZtSp -9yPpm7O5QyfdzePhOpRrjWroLsqaDOPV2UQlzwIDAQABAoIBADCWTh8P19vdnR3X -v5uPXLcgkL7WQt+g7Qbd91CKVaRWTsHvDilGVNA4Ntc85oyy3gPNHfa/YPdnU0bQ -6vtwGgLEKTWumY6rgdDhQcFMmLTlaV4QiFSw6q8MWMN6c/yZSmA7wMoXAIVs0/VB -ip44sb4Fpw5MBMCjxZjwL3fP09WJPlUqx09vVo7eH8rFwLBikmn982IzRigAx1I8 -TX0wkdqvv33MSxBXPMQIrwPqjf2arxWFzb6vp6yolYbMZtgORF9gznWABRy3oY50 -9jFkTkbxZFlSMVuF7nlM0WJj5Q9/IelBqpozODWUVvB+6inCqkxNLkbh0ISbpXWC -16gUZfUCgYEAxWo3FRNBrNXhVD5h2N4ApyUXkZ5UYIY5zbsHEJCrPjooh9uHu9kh -xXh5v11J/7TV9BfwLZ4qRbDBH4fq0DKEOXOZRLY5Lo4KbrYmlEDCabuJdmwwHeGh -S5K37F5z/+zPz9KWkKN+9Rg32xdLxh0969O77GnvuBrhzASpVsF6ZFMCgYEAtxf1 -eVg4Kxzuy0AWs+CisSVQc+5CbZ9teKA5fli2EVSmL5dsrKatVTIDghudJgQTU6cr -zP9I20K11jeqIoK5saQXH3CzogN6aDuKssq4rDbvVSZ09Zry6N1WMz9GPe31zEYw -sdU1w7vUw+l3unFfWOP4oZm0MH+na61V1YohCRUCgYANlp0J/1RS8DndUZnskoNa -/eucY1iNeE+8QHZhBoQy+U/W4h56qJxxejRvHp28UxczAP7QNQXV3C++2t0nzYJa -bgGLwDs5YB+JtVH8fGSlYHo6w4GgXOp8SDIOvAWiBQvc0zL367kOZ8dYdkcJ8PNV -KzLROA1/D6KhJ2T8ir7A7wKBgQCjVVxGw8xXqZfc+W9HSD3aic8bnJDl+jNOSKEB -dWH2U+1sx0jLPGWketlmV/v4zenv1lHcrl/wObK9RysfXj8JmbiG86NMBI5OLc+t -b+sOtnMLIyNzdqb71Xfwf6HJ3V5IvNTzz6AG3KkRnFSSnlDQm45RmyyDl11jUV4h -APg3gQKBgBzFeuKWnaTZz1FQBr5Ytl9gtxBRMl+49jtkqyzErJYFHe0MTWeD/1xj -mEC/7UERYWhIQF1L4ah6c0QkecR3F1s9/IYK/QHsnSJFwRyFuMas6StCERsDq5oQ -GWpXAmw7JTa8OYwxVjORdXY25Iwv6rEr6iUYBWZrkhoWYBySWpSZ +MIIEowIBAAKCAQEAtgg0vsIc8bIMb9hAO1Lwq2TVNYMiD928WCVwq7Bj3B9LM8ci +fN+mBxHz35SeC9JnQysbKAf8VVfVi7GVzwFq4VaRreoIl8DoMbBORb2iKxfLXKEH +S283bbl17csAYHTTBqFsKnO3kKvYoDHRKFX0T0rzZibI0ACQiylW+ALE3fVS0hFI +8fmoZnIAOyQ15RLp7KPO4bMRVdZaOqRUZeW0YzbvOBeGZKbx3bXiscL9RwfGSxlF +MCLahaJsBrVDo4/IGDRCmSydK8k/41MHd86pGPzCixBx7cqtKO/6+cVeEuGxfJPf +JVuxNq6vkRSt2HxCc6i2iagA2uL/WYdg3Pa/4wIDAQABAoIBAADFrCObAzBrRu46 +hps50NeJR/ZAJibXE/NzxTSVPPc0EseXcqgA8t1Y0CYEpV77d4CrcCQNVJ6wDrHX +AQGtydxG17tbIMo0AUgkrVBSa5uvMCembzd8s0l93egyUkAWfsaqbKEJeJ/eer7D +N1Xqd2zWro2iYHuxZOuSM1I+AMPIQsmYJ71w6/h9YpQh436Vd+zNQ5k/nWpLHihT +VB2ECrJ36IbuiYo3UbSr9gQjyBSMkk/oUqO4jonkb6L7r0mqHXNeblycg99/m6i7 +O5c5DQKMhzqibwvNNf6uvWCcLKfF5Kqzzf9DKR3/pYOBQrVTA24l4UFsfTdEKUNS +a8W3P8ECgYEA6CQOG15V9upc2nPzfFwgftGyomSMYH54PkSFdr2R4djyXkyil6Ik +efK3E+lKr9YnzwcLw3csPmVt3lqSgixQUMcyXXrhCttfk/qzSJkI+UZPQE+SrNeW +0c+blQOzVcfbNRu248iGFaRx+5qA6PMH4UZTgn7e6nXoPUgRp4ryI/MCgYEAyL24 +R7uMSuPQBRJFU84Lu+Rv4lkKdCYSLuQtMZly74m11iG6e+EHJQx0C3eexrC8LhOV +Sm4xTlwVrYQ+IdW51bhAwwHcnzGUzpbESJSDK5ZTd/P5daz8yt8ZaGbUFxNEsxTr +ElKPRcjJH5CRuyYr24DYg+CpMGdlF0N6Pcx5IFECgYAedlzDiqWNOUPmBsE02IIL +IklmtfsVzoLI6QT6h/XUxTtI1JWhgE15EzijDEIYwOmIaUxJ4iGULos0Wn5PRrFj +aEBbs/xECHWKXaOZKzvaOje8ILUGqWPJNI0eCNZHs2o4leJyEaZGwMWUVroD16B5 +F1luDmgCLGbFY+etLLaJsQKBgB40VbcNZDWcg59PuXi7pw5Vd/RB243QcKn3kUlG +QoICYYbfulSLbmzHq+pRzGUvEJGKRstVOzwEJQrfvA2RQA4FVFFDRXP6nN5c1xno +prf3PYXuAtoO9lZ8LTGFT2JNdufPPPOb0oz4gjKqqRLU0oKLp4hoVGzBEffnIkyM +KKmRAoGBAIGXh4gvxzEQMgGzfKfNuxKCT9SEhsg7NU++Iey3qn4G4t+jIWOt2Gi7 +5+y49JWoGq6DL+2ZVVw6Cn6wd9tfzDKD5GhvIztK0z1+wqpFOL4M8bwqJDOKgsZ3 +PCPASbxPgMyNCjRhvxBuscCr+dRFYDUrirOK9EUPyO9EoNTPPN9a -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- -MIIGAjCCBOqgAwIBAgIQAiXv68Xco/vd9YeB4g3HLjANBgkqhkiG9w0BAQsFADBu +MIIGBTCCBO2gAwIBAgIQDNIYeWoFoT3jxF2+HmEbTDANBgkqhkiG9w0BAQsFADBu MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg -RFYgVExTIENBIC0gRzEwHhcNMjIwOTE4MDAwMDAwWhcNMjMwOTE4MjM1OTU5WjAh +RFYgVExTIENBIC0gRzIwHhcNMjMwOTI4MDAwMDAwWhcNMjQwOTI3MjM1OTU5WjAh MR8wHQYDVQQDExZkZWZhdWx0LnpsbWVkaWFraXQuY29tMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEAjTFnNz2btUHR/K8TegU2n06lXsQuW4AfsMGthBLv -nz2zdgL2uVxfRCuleoRpECEV9I80ibTXAJlouRLbnhLHto8gzyLeQue9/DtxTEGc -S7y2aMha0wAoUcOBv7tAITSnFepGHXoYgyU7HOCvn96U1bzqLTOhxOCH/xy003dw -oEC7+PjldsWs/16cpvYiZV/dZvzDDKIpHSIvQy1whi6N0SDpzz/ncThn1z8xcJDx -0I1jVR76juP7TttbyhqJkO+fOlLn4yP9K5wZ/dPFJn2+bQRCrzGc4SM7J5YOq8ck -TTTbZtSp9yPpm7O5QyfdzePhOpRrjWroLsqaDOPV2UQlzwIDAQABo4IC5zCCAuMw -HwYDVR0jBBgwFoAUVXRPsnJP9WC6UNHX5lFcmgGHGtcwHQYDVR0OBBYEFPnRZrfz -q/QAf5u4Xp4eGWvhMdvfMCEGA1UdEQQaMBiCFmRlZmF1bHQuemxtZWRpYWtpdC5j -b20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD -AjA+BgNVHSAENzA1MDMGBmeBDAECATApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3 -LmRpZ2ljZXJ0LmNvbS9DUFMwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY +AQEFAAOCAQ8AMIIBCgKCAQEAtgg0vsIc8bIMb9hAO1Lwq2TVNYMiD928WCVwq7Bj +3B9LM8cifN+mBxHz35SeC9JnQysbKAf8VVfVi7GVzwFq4VaRreoIl8DoMbBORb2i +KxfLXKEHS283bbl17csAYHTTBqFsKnO3kKvYoDHRKFX0T0rzZibI0ACQiylW+ALE +3fVS0hFI8fmoZnIAOyQ15RLp7KPO4bMRVdZaOqRUZeW0YzbvOBeGZKbx3bXiscL9 +RwfGSxlFMCLahaJsBrVDo4/IGDRCmSydK8k/41MHd86pGPzCixBx7cqtKO/6+cVe +EuGxfJPfJVuxNq6vkRSt2HxCc6i2iagA2uL/WYdg3Pa/4wIDAQABo4IC6jCCAuYw +HwYDVR0jBBgwFoAUeN+RkF/u3qz2xXXr1UxVU+8kSrYwHQYDVR0OBBYEFHmEMVp9 +9EHIPWA2U1iLKogCosGFMCEGA1UdEQQaMBiCFmRlZmF1bHQuemxtZWRpYWtpdC5j +b20wPgYDVR0gBDcwNTAzBgZngQwBAgEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3 +dy5kaWdpY2VydC5jb20vQ1BTMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggr +BgEFBQcDAQYIKwYBBQUHAwIwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEoGCCsGAQUFBzAChj5odHRwOi8vY2Fj -ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcx -LmNydDAJBgNVHRMEAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgDoPtDa -PvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAAAYNQt3JvAAAEAwBHMEUCIEaO -G4ffzzaE6OMqiu6PUr+Y+wO2tsXCkGt1jt04Ix1qAiEAhNZwqFACieds1ZbY3r/p -wlF3iFbhqp+kNfPzon7kwc8AdgA1zxkbv7FsV78PrUxtQsu7ticgJlHqP+Eq76gD -wzvWTAAAAYNQt3JVAAAEAwBHMEUCIBOErqyKvihAEKItLWG/Plgtxh/hCTMsE+t5 -+MfsAQLCAiEA76d50S4iy1wxya+8IUASVlKStaHNqBkJAS+Oadxs2sMAdwCzc3cH -4YRQ+GOG1gWp3BEJSnktsWcMC4fc8AMOeTalmgAAAYNQt3LIAAAEAwBIMEYCIQC/ -kfFCpwF76sw/Qx3sxR8b3srW+Ds0k/6VrIIDZcYV5gIhAKkLmuyeDvzulp0y4f0t -GDgIN/OoURq6CuHA67UJlsWzMA0GCSqGSIb3DQEBCwUAA4IBAQB0BwVxPRihSdPJ -FUPLQ+ClHy9O/UisnRD7NadQQtbcMXn6L9Lwd0f2la0ytLQAKHADOZDA08KfQ5qW -B19OeQOlTwp2nhY2ZvoLEG+paeh0gYxIgD76APnd/m3g2H7GeW144ymjPcZRoldj -ZKYSdzStJJIFYXzL3FR9wjkMc4xOEes/IY5PFtj8OT8CFf7zl0R7L2Vcw9RGYi9u -vLjGwwJW9kXTX8UlKXFyjJN0ZyrmxBQHq5uNtigx8xy6HtMnPsc58tp1IqitIELp -HIur2XrRPBJA5XtpDg3AE8bXhRTM8oFMPL0UoSFWyWRYGgBo1Msc10dpXPtmbgIc -pPW8w+2c +ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcy +LmNydDAMBgNVHRMBAf8EAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgDu +zdBk1dsazsVct520zROiModGfLzs3sNRSFlGcR+1mwAAAYravqonAAAEAwBHMEUC +IQDX+gqsd7I0yzjkhgp2YrccUlTx4wkFptFvmQxeChImRgIgJdgJa2Uamd790BCI +/CZwSqmRlor5eU8exAixdcopYpcAdwBIsONr2qZHNA/lagL6nTDrHFIBy1bdLIHZ +u7+rOdiEcwAAAYravqqCAAAEAwBIMEYCIQCP6rkKg2FlF92CyMbVMk3ESh/9gVaM +tRsv5I//i5IVigIhAINHERhy7812wR47fwmvqWDjxyOB1ZodU7WA9D5L/1bVAHYA +2ra/az+1tiKfm8K7XGvocJFxbLtRhIU0vaQ9MEjX+6sAAAGK2r6qQQAABAMARzBF +AiAiz3bp/j4SlnVxKg1HZY+YdUboi+kaKf5G8X6aFLIqUgIhAPPCm5UN05p7Oqrc +sP/wdHDB7O/2AbUksYSLhidmwfmhMA0GCSqGSIb3DQEBCwUAA4IBAQBmaG51jU1E +MsgT1VzutQUXglEvJGVf54cA+0TSfjfnP1n9ALdKjGxHL3KBh4UkPx5zdE5//FUX +dacua6BQEWSCmMtYL0CFieFnLGXh0mgkfvRaP6+3xe6TkJ4kuyJkMS9YMDpVl80F +2GLlE09EsZ3Xk9+SCpmWOPLOCDFURbwpc5ht+acROfzYJQyCY0L8EGbyL5/q9oMn +ugRGh4oyGvXgKvFIPzpZkaOmb0b63/uBc5JkiyQhuFdYaS2cLOwupXmCtIHL4Od6 +OU8/8smT8NEkD7d3lUijtc84q2TihW7ebT7RtOco49PDvFP/7w28QjxM8Ohv9/Gz +Xyta8ICQVwmK -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh +MIIEqjCCA5KgAwIBAgIQDeD/te5iy2EQn2CMnO1e0zANBgkqhkiG9w0BAQsFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0xNzExMjcxMjQ2MTBaFw0yNzExMjcxMjQ2MTBaMG4xCzAJBgNVBAYTAlVT +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xNzExMjcxMjQ2NDBaFw0yNzExMjcxMjQ2NDBaMG4xCzAJBgNVBAYTAlVT MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH -MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPeP6wkab41dyQh6mKc -oHqt3jRIxW5MDvf9QyiOR7VfFwK656es0UFiIb74N9pRntzF1UgYzDGu3ppZVMdo -lbxhm6dWS9OK/lFehKNT0OYI9aqk6F+U7cA6jxSC+iDBPXwdF4rs3KRyp3aQn6pj -pp1yr7IB6Y4zv72Ee/PlZ/6rK6InC6WpK0nPVOYR7n9iDuPe1E4IxUMBH/T33+3h -yuH3dvfgiWUOUkjdpMbyxX+XNle5uEIiyBsi4IvbcTCh8ruifCIi5mDXkZrnMT8n -wfYCV6v6kDdXkbgGRLKsR4pucbJtbKqIkUGxuZI2t7pfewKRc5nWecvDBZf3+p1M -pA8CAwEAAaOCAU8wggFLMB0GA1UdDgQWBBRVdE+yck/1YLpQ0dfmUVyaAYca1zAf -BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYw +MjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO8Uf46i/nr7pkgTDqnE +eSIfCFqvPnUq3aF1tMJ5hh9MnO6Lmt5UdHfBGwC9Si+XjK12cjZgxObsL6Rg1njv +NhAMJ4JunN0JGGRJGSevbJsA3sc68nbPQzuKp5Jc8vpryp2mts38pSCXorPR+sch +QisKA7OSQ1MjcFN0d7tbrceWFNbzgL2csJVQeogOBGSe/KZEIZw6gXLKeFe7mupn +NYJROi2iC11+HuF79iAttMc32Cv6UOxixY/3ZV+LzpLnklFq98XORgwkIJL1HuvP +ha8yvb+W6JislZJL+HLFtidoxmI7Qm3ZyIV66W533DsGFimFJkz3y0GeHWuSVMbI +lfsCAwEAAaOCAU8wggFLMB0GA1UdDgQWBBR435GQX+7erPbFdevVTFVT7yRKtjAf +BgNVHSMEGDAWgBROIlQgGJXm427mD/r6uRLtBhePOTAOBgNVHQ8BAf8EBAMCAYYw HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu -Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG +Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG /WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT -MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAK3Gp6/aGq7aBZsxf/oQ+TD/B -SwW3AU4ETK+GQf2kFzYZkby5SFrHdPomunx2HBzViUchGoofGgg7gHW0W3MlQAXW -M0r5LUvStcr82QDWYNPaUy4taCQmyaJ+VB+6wxHstSigOlSNF2a6vg4rgexixeiV -4YSB03Yqp2t3TeZHM9ESfkus74nQyW7pRGezj+TC44xCagCQQOzzNmzEAP2SnCrJ -sNE2DpRVMnL8J6xBRdjmOsC3N6cQuKuRXbzByVBjCqAA8t1L0I+9wXJerLPyErjy -rMKWaBFLmfK/AHNF4ZihwPGOc7w6UHczBZXH5RFzJNnww+WnKuTPI0HfnVH8lg== ------END CERTIFICATE----- +MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAoBs1eCLKakLtVRPFRjBIJ9LJ +L0s8ZWum8U8/1TMVkQMBn+CPb5xnCD0GSA6L/V0ZFrMNqBirrr5B241OesECvxIi +98bZ90h9+q/X5eMyOD35f8YTaEMpdnQCnawIwiHx06/0BfiTj+b/XQih+mqt3ZXe +xNCJqKexdiB2IWGSKcgahPacWkk/BAQFisKIFYEqHzV974S3FAz/8LIfD58xnsEN +GfzyIDkH3JrwYZ8caPTf6ZX9M1GrISN8HnWTtdNCH2xEajRa/h9ZBXjUyFKQrGk2 +n2hcLrfZSbynEC/pSw/ET7H5nWwckjmAJ1l9fcnbqkU/pf6uMQmnfl0JQjJNSg== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/docker/centos7/Dockerfile.runtime b/docker/centos7/Dockerfile.runtime index 8f588de5..c7d9f990 100644 --- a/docker/centos7/Dockerfile.runtime +++ b/docker/centos7/Dockerfile.runtime @@ -128,4 +128,4 @@ WORKDIR /opt/zlm VOLUME [ "/opt/zlm/conf/","/opt/zlm/log/","opt/zlm/ffmpeg/"] COPY --from=build /opt/build / ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH TZ=Asia/Shanghai -CMD ./MediaServer -c ./conf/config.ini \ No newline at end of file +CMD ["./MediaServer", "-c" , "./conf/config.ini"] \ No newline at end of file diff --git a/docker/ubuntu16.04/Dockerfile.devel b/docker/ubuntu16.04/Dockerfile.devel index 476cca53..65ff0958 100644 --- a/docker/ubuntu16.04/Dockerfile.devel +++ b/docker/ubuntu16.04/Dockerfile.devel @@ -41,4 +41,4 @@ RUN cmake -DCMAKE_BUILD_TYPE=Release .. && \ make ENV PATH /opt/media/ZLMediaKit/release/linux/Release/:$PATH -CMD MediaServer +CMD ["MediaServer"] diff --git a/docker/ubuntu16.04/Dockerfile.runtime b/docker/ubuntu16.04/Dockerfile.runtime index 4b84e017..bc869e92 100644 --- a/docker/ubuntu16.04/Dockerfile.runtime +++ b/docker/ubuntu16.04/Dockerfile.runtime @@ -60,4 +60,4 @@ RUN apt-get update && \ WORKDIR /opt/media/bin/ COPY --from=build /opt/media/ZLMediaKit/release/linux/Release/MediaServer /opt/media/bin/MediaServer ENV PATH /opt/media/bin:$PATH -CMD MediaServer +CMD ["MediaServer"] diff --git a/docker/ubuntu18.04/Dockerfile.devel b/docker/ubuntu18.04/Dockerfile.devel index 05109cee..0a61d86c 100644 --- a/docker/ubuntu18.04/Dockerfile.devel +++ b/docker/ubuntu18.04/Dockerfile.devel @@ -42,4 +42,4 @@ RUN cmake -DCMAKE_BUILD_TYPE=Release .. && \ make ENV PATH /opt/media/ZLMediaKit/release/linux/Release:$PATH -CMD MediaServer +CMD ["MediaServer"] diff --git a/docker/ubuntu18.04/Dockerfile.runtime b/docker/ubuntu18.04/Dockerfile.runtime index ce3b6496..0a1bae65 100644 --- a/docker/ubuntu18.04/Dockerfile.runtime +++ b/docker/ubuntu18.04/Dockerfile.runtime @@ -60,4 +60,4 @@ RUN apt-get update && \ WORKDIR /opt/media/bin/ COPY --from=build /opt/media/ZLMediaKit/release/linux/Release/MediaServer /opt/media/bin/MediaServer ENV PATH /opt/media/bin:$PATH -CMD MediaServer +CMD ["MediaServer"] diff --git a/dockerfile b/dockerfile index 3297b86e..84851901 100644 --- a/dockerfile +++ b/dockerfile @@ -83,4 +83,4 @@ COPY --from=build /opt/media/ZLMediaKit/release/linux/${MODEL}/MediaServer /opt/ COPY --from=build /opt/media/ZLMediaKit/release/linux/${MODEL}/config.ini /opt/media/conf/ COPY --from=build /opt/media/ZLMediaKit/www/ /opt/media/bin/www/ ENV PATH /opt/media/bin:$PATH -CMD ["sh","-c","./MediaServer -s default.pem -c ../conf/config.ini -l 0"] +CMD ["./MediaServer","-s", "default.pem", "-c", "../conf/config.ini", "-l","0"] diff --git a/player/test_player.cpp b/player/test_player.cpp index 126280a0..faf29af7 100644 --- a/player/test_player.cpp +++ b/player/test_player.cpp @@ -56,8 +56,7 @@ int main(int argc, char *argv[]) { if (argc < 3) { ErrorL << "\r\n测试方法:./test_player rtxp_url rtp_type\r\n" - << "例如:./test_player rtsp://admin:123456@127.0.0.1/live/0 0\r\n" - << endl; + << "例如:./test_player rtsp://admin:123456@127.0.0.1/live/0 0\r\n"; return 0; } diff --git a/postman/ZLMediaKit.postman_collection.json b/postman/ZLMediaKit.postman_collection.json index 044461e0..fdda9d2a 100644 --- a/postman/ZLMediaKit.postman_collection.json +++ b/postman/ZLMediaKit.postman_collection.json @@ -25,7 +25,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -51,7 +51,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -77,7 +77,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -103,7 +103,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -129,7 +129,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -155,7 +155,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "api.apiDebug", @@ -186,7 +186,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -212,7 +212,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -262,7 +262,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -314,7 +314,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -366,7 +366,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "local_port", @@ -404,7 +404,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "id", @@ -435,7 +435,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "local_port", @@ -473,7 +473,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -516,7 +516,13 @@ { "key": "enable_hls", "value": null, - "description": "是否转hls", + "description": "是否转hls-ts", + "disabled": true + }, + { + "key": "enable_hls_fmp4", + "value": null, + "description": "是否转hls-fmp4", "disabled": true }, { @@ -582,7 +588,13 @@ { "key": "modify_stamp", "value": null, - "description": "是否重新计算时间戳", + "description": "是否修改原始时间戳,默认值2;取值范围:0.采用源视频流绝对时间戳,不做任何改变;1.采用zlmediakit接收数据时的系统时间戳(有平滑处理);2.采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正", + "disabled": true + }, + { + "key": "auto_close", + "value": null, + "description": "无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)", "disabled": true } ] @@ -609,7 +621,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "key", @@ -640,7 +652,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -709,7 +721,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "key", @@ -740,7 +752,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "src_url", @@ -797,7 +809,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "key", @@ -827,7 +839,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -873,7 +885,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -900,6 +912,56 @@ }, "response": [] }, + { + "name": "广播webrtc datachannel消息(broadcastMessage)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{ZLMediaKit_URL}}/index/api/broadcastMessage?secret={{ZLMediaKit_secret}}&schema=rtsp&vhost={{defaultVhost}}&app=live&stream=test&msg=Hello zlmediakit123", + "host": [ + "{{ZLMediaKit_URL}}" + ], + "path": [ + "index", + "api", + "broadcastMessage" + ], + "query": [ + { + "key": "secret", + "value": "{{ZLMediaKit_secret}}", + "description": "api操作密钥(配置文件配置)" + }, + { + "key": "schema", + "value": "rtsp", + "description": "协议,例如 rtsp或rtmp,目前仅支持rtsp协议" + }, + { + "key": "vhost", + "value": "{{defaultVhost}}", + "description": "虚拟主机,例如__defaultVhost__" + }, + { + "key": "app", + "value": "live", + "description": "应用名,例如 live" + }, + { + "key": "stream", + "value": "test", + "description": "流id,例如 test" + }, + { + "key": "msg", + "value": "Hello ZLMediakit" + } + ] + } + }, + "response": [] + }, { "name": "获取流信息(getMediaInfo)", "request": { @@ -919,7 +981,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -965,7 +1027,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1016,7 +1078,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1062,7 +1124,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "type", @@ -1120,7 +1182,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1166,7 +1228,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1212,7 +1274,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "type", @@ -1258,7 +1320,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "type", @@ -1304,7 +1366,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "url", @@ -1345,7 +1407,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "stream_id", @@ -1376,7 +1438,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "port", @@ -1435,7 +1497,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "dst_url", @@ -1476,7 +1538,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "stream_id", @@ -1507,7 +1569,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "stream_id", @@ -1543,7 +1605,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "stream_id", @@ -1574,7 +1636,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "stream_id", @@ -1605,7 +1667,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -1631,7 +1693,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1734,7 +1796,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1822,7 +1884,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index f0dea82b..63abc05d 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -34,11 +34,17 @@ if(ENABLE_SERVER_LIB) PRIVATE ${COMPILE_OPTIONS_DEFAULT}) target_link_libraries(MediaServer PRIVATE ${MK_LINK_LIBRARIES}) - update_cached(MK_LINK_LIBRARIES MediaServer) + update_cached_list(MK_LINK_LIBRARIES MediaServer) return() endif() -add_executable(MediaServer ${MediaServer_SRC_LIST}) +# IOS 不编译可执行程序,只做依赖库 +if(IOS) + add_library(MediaServer STATIC ${MediaServer_SRC_LIST}) +else() + add_executable(MediaServer ${MediaServer_SRC_LIST}) +endif() + target_compile_definitions(MediaServer PRIVATE ${COMPILE_DEFINITIONS}) target_compile_options(MediaServer diff --git a/server/FFmpegSource.cpp b/server/FFmpegSource.cpp index 5e7fc246..8363950c 100644 --- a/server/FFmpegSource.cpp +++ b/server/FFmpegSource.cpp @@ -11,6 +11,7 @@ #include "FFmpegSource.h" #include "Common/config.h" #include "Common/MediaSource.h" +#include "Common/MultiMediaSourceMuxer.h" #include "Util/File.h" #include "System.h" #include "Thread/WorkThreadPool.h" @@ -39,7 +40,7 @@ onceToken token([]() { //ffmpeg日志保存路径 mINI::Instance()[kLog] = "./ffmpeg/ffmpeg.log"; mINI::Instance()[kCmd] = "%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s"; - mINI::Instance()[kSnap] = "%s -i %s -y -f mjpeg -t 0.001 %s"; + mINI::Instance()[kSnap] = "%s -i %s -y -f mjpeg -frames:v 1 %s"; mINI::Instance()[kRestartSec] = 0; }); } @@ -70,10 +71,10 @@ void FFmpegSource::setupRecordFlag(bool enable_hls, bool enable_mp4){ _enable_mp4 = enable_mp4; } -void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,const string &dst_url,int timeout_ms,const onPlay &cb) { - GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin); - GET_CONFIG(string,ffmpeg_cmd_default,FFmpeg::kCmd); - GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog); +void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url, const string &dst_url, int timeout_ms, const onPlay &cb) { + GET_CONFIG(string, ffmpeg_bin, FFmpeg::kBin); + GET_CONFIG(string, ffmpeg_cmd_default, FFmpeg::kCmd); + GET_CONFIG(string, ffmpeg_log, FFmpeg::kLog); _src_url = src_url; _dst_url = dst_url; @@ -91,122 +92,114 @@ void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,cons auto cmd_it = mINI::Instance().find(ffmpeg_cmd_key); if (cmd_it != mINI::Instance().end()) { ffmpeg_cmd = cmd_it->second; - } else{ + } else { WarnL << "配置文件中,ffmpeg命令模板(" << ffmpeg_cmd_key << ")不存在,已采用默认模板(" << ffmpeg_cmd_default << ")"; } } - char cmd[2048] = {0}; + char cmd[2048] = { 0 }; snprintf(cmd, sizeof(cmd), ffmpeg_cmd.data(), File::absolutePath("", ffmpeg_bin).data(), src_url.data(), dst_url.data()); auto log_file = ffmpeg_log.empty() ? "" : File::absolutePath("", ffmpeg_log); _process.run(cmd, log_file); InfoL << cmd; if (is_local_ip(_media_info.host)) { - //推流给自己的,通过判断流是否注册上来判断是否正常 + // 推流给自己的,通过判断流是否注册上来判断是否正常 if (_media_info.schema != RTSP_SCHEMA && _media_info.schema != RTMP_SCHEMA) { - cb(SockException(Err_other,"本服务只支持rtmp/rtsp推流")); + cb(SockException(Err_other, "本服务只支持rtmp/rtsp推流")); return; } weak_ptr weakSelf = shared_from_this(); - findAsync(timeout_ms,[cb,weakSelf,timeout_ms](const MediaSource::Ptr &src){ + findAsync(timeout_ms, [cb, weakSelf, timeout_ms](const MediaSource::Ptr &src) { auto strongSelf = weakSelf.lock(); - if(!strongSelf){ - //自己已经销毁 + if (!strongSelf) { + // 自己已经销毁 return; } - if(src){ - //推流给自己成功 + if (src) { + // 推流给自己成功 cb(SockException()); strongSelf->onGetMediaSource(src); strongSelf->startTimer(timeout_ms); return; } //推流失败 - if(!strongSelf->_process.wait(false)){ - //ffmpeg进程已经退出 - cb(SockException(Err_other,StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code())); + if (!strongSelf->_process.wait(false)) { + // ffmpeg进程已经退出 + cb(SockException(Err_other, StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code())); return; } - //ffmpeg进程还在线,但是等待推流超时 - cb(SockException(Err_other,"等待超时")); + // ffmpeg进程还在线,但是等待推流超时 + cb(SockException(Err_other, "等待超时")); }); } else{ //推流给其他服务器的,通过判断FFmpeg进程是否在线判断是否成功 weak_ptr weakSelf = shared_from_this(); - _timer = std::make_shared(timeout_ms / 1000.0f,[weakSelf,cb,timeout_ms](){ + _timer = std::make_shared(timeout_ms / 1000.0f, [weakSelf, cb, timeout_ms]() { auto strongSelf = weakSelf.lock(); - if(!strongSelf){ - //自身已经销毁 + if (!strongSelf) { + // 自身已经销毁 return false; } - //FFmpeg还在线,那么我们认为推流成功 - if(strongSelf->_process.wait(false)){ + // FFmpeg还在线,那么我们认为推流成功 + if (strongSelf->_process.wait(false)) { cb(SockException()); strongSelf->startTimer(timeout_ms); return false; } - //ffmpeg进程已经退出 - cb(SockException(Err_other,StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code())); + // ffmpeg进程已经退出 + cb(SockException(Err_other, StrPrinter << "ffmpeg已经退出,exit code = " << strongSelf->_process.exit_code())); return false; - },_poller); + }, _poller); } } void FFmpegSource::findAsync(int maxWaitMS, const function &cb) { - auto src = MediaSource::find(_media_info.schema, - _media_info.vhost, - _media_info.app, - _media_info.stream); - if(src || !maxWaitMS){ + auto src = MediaSource::find(_media_info.schema, _media_info.vhost, _media_info.app, _media_info.stream); + if (src || !maxWaitMS) { cb(src); return; } void *listener_tag = this; - //若干秒后执行等待媒体注册超时回调 - auto onRegistTimeout = _poller->doDelayTask(maxWaitMS,[cb,listener_tag](){ - //取消监听该事件 - NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged); + // 若干秒后执行等待媒体注册超时回调 + auto onRegistTimeout = _poller->doDelayTask(maxWaitMS, [cb, listener_tag]() { + // 取消监听该事件 + NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged); cb(nullptr); return 0; }); weak_ptr weakSelf = shared_from_this(); - auto onRegist = [listener_tag,weakSelf,cb,onRegistTimeout](BroadcastMediaChangedArgs) { + auto onRegist = [listener_tag, weakSelf, cb, onRegistTimeout](BroadcastMediaChangedArgs) { auto strongSelf = weakSelf.lock(); - if(!strongSelf) { - //本身已经销毁,取消延时任务 + if (!strongSelf) { + // 本身已经销毁,取消延时任务 onRegistTimeout->cancel(); - NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged); + NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged); return; } - if (!bRegist || - sender.getSchema() != strongSelf->_media_info.schema || - sender.getVhost() != strongSelf->_media_info.vhost || - sender.getApp() != strongSelf->_media_info.app || - sender.getId() != strongSelf->_media_info.stream) { - //不是自己感兴趣的事件,忽略之 + if (!bRegist || sender.getSchema() != strongSelf->_media_info.schema || + !equalMediaTuple(sender.getMediaTuple(), strongSelf->_media_info)) { + // 不是自己感兴趣的事件,忽略之 return; } - //查找的流终于注册上了;取消延时任务,防止多次回调 + // 查找的流终于注册上了;取消延时任务,防止多次回调 onRegistTimeout->cancel(); - //取消事件监听 - NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged); + // 取消事件监听 + NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged); - //切换到自己的线程再回复 - strongSelf->_poller->async([weakSelf,cb](){ - auto strongSelf = weakSelf.lock(); - if(!strongSelf) { - return; + // 切换到自己的线程再回复 + strongSelf->_poller->async([weakSelf, cb]() { + if (auto strongSelf = weakSelf.lock()) { + // 再找一遍媒体源,一般能找到 + strongSelf->findAsync(0, cb); } - //再找一遍媒体源,一般能找到 - strongSelf->findAsync(0,cb); }, false); }; - //监听媒体注册事件 + // 监听媒体注册事件 NoticeCenter::Instance().addListener(listener_tag, Broadcast::kBroadcastMediaChanged, onRegist); } @@ -224,49 +217,49 @@ void FFmpegSource::startTimer(int timeout_ms) { } bool needRestart = ffmpeg_restart_sec > 0 && strongSelf->_replay_ticker.elapsedTime() > ffmpeg_restart_sec * 1000; if (is_local_ip(strongSelf->_media_info.host)) { - //推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常 + // 推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常 strongSelf->findAsync(0, [&](const MediaSource::Ptr &src) { - //同步查找流 + // 同步查找流 if (!src || needRestart) { - if(needRestart){ + if (needRestart) { strongSelf->_replay_ticker.resetTime(); - if(strongSelf->_process.wait(false)){ - //FFmpeg进程还在运行,超时就关闭它 + if (strongSelf->_process.wait(false)) { + // FFmpeg进程还在运行,超时就关闭它 strongSelf->_process.kill(2000); } InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url; } - //流不在线,重新拉流, 这里原先是10秒超时,实际发现10秒不够,改成20秒了 - if(strongSelf->_replay_ticker.elapsedTime() > 20 * 1000){ - //上次重试时间超过10秒,那么再重试FFmpeg拉流 + // 流不在线,重新拉流, 这里原先是10秒超时,实际发现10秒不够,改成20秒了 + if (strongSelf->_replay_ticker.elapsedTime() > 20 * 1000) { + // 上次重试时间超过10秒,那么再重试FFmpeg拉流 strongSelf->_replay_ticker.resetTime(); strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [](const SockException &) {}); } } }); } else { - //推流给其他服务器的,我们通过判断FFmpeg进程是否在线,如果FFmpeg推流中断,那么它应该会自动退出 + // 推流给其他服务器的,我们通过判断FFmpeg进程是否在线,如果FFmpeg推流中断,那么它应该会自动退出 if (!strongSelf->_process.wait(false) || needRestart) { - if(needRestart){ + if (needRestart) { strongSelf->_replay_ticker.resetTime(); - if(strongSelf->_process.wait(false)){ - //FFmpeg进程还在运行,超时就关闭它 + if (strongSelf->_process.wait(false)) { + // FFmpeg进程还在运行,超时就关闭它 strongSelf->_process.kill(2000); } InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url; } - //ffmpeg不在线,重新拉流 + // ffmpeg不在线,重新拉流 strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [weakSelf](const SockException &ex) { - if(!ex){ - //没有错误 + if (!ex) { + // 没有错误 return; } auto strongSelf = weakSelf.lock(); if (!strongSelf) { - //自身已经销毁 + // 自身已经销毁 return; } - //上次重试时间超过10秒,那么再重试FFmpeg拉流 + // 上次重试时间超过10秒,那么再重试FFmpeg拉流 strongSelf->startTimer(10 * 1000); }); } @@ -296,20 +289,17 @@ MediaOriginType FFmpegSource::getOriginType(MediaSource &sender) const{ return MediaOriginType::ffmpeg_pull; } -string FFmpegSource::getOriginUrl(MediaSource &sender) const{ +string FFmpegSource::getOriginUrl(MediaSource &sender) const { return _src_url; } -std::shared_ptr FFmpegSource::getOriginSock(MediaSource &sender) const { - return nullptr; -} - void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) { - auto listener = src->getListener(true); - if (listener.lock().get() != this) { + auto muxer = src->getMuxer(); + auto listener = muxer ? muxer->getDelegate() : nullptr; + if (listener && listener.get() != this) { //防止多次进入onGetMediaSource函数导致无限递归调用的bug setDelegate(listener); - src->setListener(shared_from_this()); + muxer->setDelegate(shared_from_this()); if (_enable_hls) { src->setupRecord(Recorder::type_hls, true, "", 0); } @@ -320,14 +310,14 @@ void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) { } void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float timeout_sec, const onSnap &cb) { - GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin); - GET_CONFIG(string,ffmpeg_snap,FFmpeg::kSnap); - GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog); + GET_CONFIG(string, ffmpeg_bin, FFmpeg::kBin); + GET_CONFIG(string, ffmpeg_snap, FFmpeg::kSnap); + GET_CONFIG(string, ffmpeg_log, FFmpeg::kLog); Ticker ticker; - WorkThreadPool::Instance().getPoller()->async([timeout_sec, play_url,save_path,cb, ticker](){ + WorkThreadPool::Instance().getPoller()->async([timeout_sec, play_url, save_path, cb, ticker]() { auto elapsed_ms = ticker.elapsedTime(); if (elapsed_ms > timeout_sec * 1000) { - //超时,后台线程负载太高,当代太久才启动该任务 + // 超时,后台线程负载太高,当代太久才启动该任务 cb(false, "wait work poller schedule snap task timeout"); return; } @@ -348,13 +338,12 @@ void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float return 0; }); - //等待FFmpeg进程退出 + // 等待FFmpeg进程退出 process->wait(true); // FFmpeg进程退出了可以取消定时器了 delayTask->cancel(); - //执行回调函数 + // 执行回调函数 bool success = process->exit_code() == 0 && File::fileSize(save_path.data()); cb(success, (!success && !log_file.empty()) ? File::loadFile(log_file.data()) : ""); }); } - diff --git a/server/FFmpegSource.h b/server/FFmpegSource.h index e85df3ec..8efb7ced 100644 --- a/server/FFmpegSource.h +++ b/server/FFmpegSource.h @@ -20,6 +20,7 @@ namespace FFmpeg { extern const std::string kSnap; + extern const std::string kBin; } class FFmpegSnap { @@ -79,8 +80,6 @@ private: mediakit::MediaOriginType getOriginType(mediakit::MediaSource &sender) const override; //获取媒体源url或者文件路径 std::string getOriginUrl(mediakit::MediaSource &sender) const override; - // 获取媒体源客户端相关信息 - std::shared_ptr getOriginSock(mediakit::MediaSource &sender) const override; private: bool _enable_hls = false; diff --git a/server/Process.cpp b/server/Process.cpp index 7ddebb08..140282d9 100644 --- a/server/Process.cpp +++ b/server/Process.cpp @@ -108,7 +108,7 @@ static int cloneFunc(void *ptr) { #endif -void Process::run(const string &cmd, string &log_file) { +void Process::run(const string &cmd, string log_file) { kill(2000); #ifdef _WIN32 STARTUPINFO si = { 0 }; diff --git a/server/Process.h b/server/Process.h index d514edb5..06f55345 100644 --- a/server/Process.h +++ b/server/Process.h @@ -26,7 +26,7 @@ class Process { public: Process(); ~Process(); - void run(const std::string &cmd, std::string &log_file); + void run(const std::string &cmd, std::string log_file); void kill(int max_delay,bool force = false); bool wait(bool block = true); int exit_code(); diff --git a/server/System.cpp b/server/System.cpp index aae4c792..c06d34e1 100644 --- a/server/System.cpp +++ b/server/System.cpp @@ -22,10 +22,11 @@ #include #include +#include "Common/JemallocUtil.h" +#include "Common/macros.h" +#include "System.h" #include "Util/logger.h" #include "Util/uv_errno.h" -#include "System.h" -#include "Common/macros.h" using namespace std; using namespace toolkit; @@ -55,6 +56,16 @@ string System::execute(const string &cmd) { static constexpr int MAX_STACK_FRAMES = 128; +static void save_jemalloc_stats() { + string jemalloc_status = JemallocUtil::get_malloc_stats(); + if (jemalloc_status.empty()) { + return; + } + ofstream out(StrPrinter << exeDir() << "/jemalloc.json", ios::out | ios::binary | ios::trunc); + out << jemalloc_status; + out.flush(); +} + static void sig_crash(int sig) { signal(sig, SIG_DFL); void *array[MAX_STACK_FRAMES]; @@ -126,6 +137,12 @@ void System::startDaemon(bool &kill_parent_if_failed) { exit(0); }); + signal(SIGTERM,[](int) { + WarnL << "收到主动退出信号,关闭父进程与子进程"; + kill(pid, SIGINT); + exit(0); + }); + do { int status = 0; if (waitpid(pid, &status, 0) >= 0) { @@ -143,6 +160,12 @@ void System::startDaemon(bool &kill_parent_if_failed) { } void System::systemSetup(){ + +#ifdef ENABLE_JEMALLOC_DUMP + //Save memory report when program exits + atexit(save_jemalloc_stats); +#endif //ENABLE_JEMALLOC_DUMP + #if !defined(_WIN32) struct rlimit rlim,rlim_new; if (getrlimit(RLIMIT_CORE, &rlim)==0) { diff --git a/server/WebApi.cpp b/server/WebApi.cpp index f1bc4f8f..7c8e35c0 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -130,7 +130,7 @@ static HttpApi toApi(const function &cb) { //参数解析成json对象然后处理 Json::Value args; Json::Reader reader; - reader.parse(parser.Content(), args); + reader.parse(parser.content(), args); cb(sender, headerOut, HttpAllArgs(parser, args), val, invoker); }; @@ -152,7 +152,7 @@ static HttpApi toApi(const function &cb) { Json::Value val; val["code"] = API::Success; - cb(sender, headerOut, HttpAllArgs(parser, (string &)parser.Content()), val, invoker); + cb(sender, headerOut, HttpAllArgs(parser, (string &)parser.content()), val, invoker); }; } @@ -191,13 +191,13 @@ void api_regist(const string &api_path, const function> jsonArgs; auto keys = jsonArgs.getMemberNames(); @@ -231,7 +231,7 @@ static inline void addHttpListener(){ GET_CONFIG(bool, api_debug, API::kApiDebug); //注册监听kBroadcastHttpRequest事件 NoticeCenter::Instance().addListener(&web_api_tag, Broadcast::kBroadcastHttpRequest, [](BroadcastHttpRequestArgs) { - auto it = s_map_api.find(parser.Url()); + auto it = s_map_api.find(parser.url()); if (it == s_map_api.end()) { return; } @@ -247,15 +247,15 @@ static inline void addHttpListener(){ size = body->remainSize(); } - LogContextCapture log(getLogger(), toolkit::LTrace, __FILE__, "http api debug", __LINE__); - log << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n"; + LogContextCapture log(getLogger(), toolkit::LDebug, __FILE__, "http api debug", __LINE__); + log << "\r\n# request:\r\n" << parser.method() << " " << parser.fullUrl() << "\r\n"; log << "# header:\r\n"; for (auto &pr : parser.getHeader()) { log << pr.first << " : " << pr.second << "\r\n"; } - auto &content = parser.Content(); + auto &content = parser.content(); log << "# content:\r\n" << (content.size() > 4 * 1024 ? content.substr(0, 4 * 1024) : content) << "\r\n"; if (size > 0 && size < 4 * 1024) { @@ -321,12 +321,16 @@ static void fillSockInfo(Value& val, SockInfo* info) { val["identifier"] = info->getIdentifier(); } +void dumpMediaTuple(const MediaTuple &tuple, Json::Value& item) { + item[VHOST_KEY] = tuple.vhost; + item["app"] = tuple.app; + item["stream"] = tuple.stream; +} + Value makeMediaSourceJson(MediaSource &media){ Value item; item["schema"] = media.getSchema(); - item[VHOST_KEY] = media.getVhost(); - item["app"] = media.getApp(); - item["stream"] = media.getId(); + dumpMediaTuple(media.getMediaTuple(), item); item["createStamp"] = (Json::UInt64) media.getCreateStamp(); item["aliveSecond"] = (Json::UInt64) media.getAliveSecond(); item["bytesSpeed"] = media.getBytesSpeed(); @@ -533,7 +537,7 @@ void addStreamProxy(const string &vhost, const string &app, const string &stream lock_guard lck(s_proxyMapMtx); if (s_proxyMap.find(key) != s_proxyMap.end()) { //已经在拉流了 - cb(SockException(Err_success), key); + cb(SockException(Err_other, "This stream already exists"), key); return; } //添加拉流代理 @@ -584,7 +588,8 @@ void installWebApi() { //获取线程负载 //测试url http://127.0.0.1/index/api/getThreadsLoad - api_regist("/index/api/getThreadsLoad",[](API_ARGS_MAP_ASYNC){ + api_regist("/index/api/getThreadsLoad", [](API_ARGS_MAP_ASYNC) { + CHECK_SECRET(); EventPollerPool::Instance().getExecutorDelay([invoker, headerOut](const vector &vecDelay) { Value val; auto vec = EventPollerPool::Instance().getExecutorLoad(); @@ -602,7 +607,8 @@ void installWebApi() { //获取后台工作线程负载 //测试url http://127.0.0.1/index/api/getWorkThreadsLoad - api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC){ + api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC) { + CHECK_SECRET(); WorkThreadPool::Instance().getExecutorDelay([invoker, headerOut](const vector &vecDelay) { Value val; auto vec = WorkThreadPool::Instance().getExecutorLoad(); @@ -648,6 +654,10 @@ void installWebApi() { continue; #endif } + if (pr.first == FFmpeg::kBin) { + WarnL << "Configuration named " << FFmpeg::kBin << " is not allowed to be set by setServerConfig api."; + continue; + } if (ini[pr.first] == pr.second) { continue; } @@ -656,7 +666,7 @@ void installWebApi() { ++changed; } if (changed > 0) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig); + NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig); ini.dumpFile(g_ini_file); } val["changed"] = changed; @@ -785,25 +795,40 @@ void installWebApi() { throw ApiRetException("can not find the stream", API::NotFound); } src->getPlayerList( - [=](const std::list> &info_list) mutable { + [=](const std::list &info_list) mutable { val["code"] = API::Success; auto &data = val["data"]; data = Value(arrayValue); for (auto &info : info_list) { - auto obj = static_pointer_cast(info); - data.append(std::move(*obj)); + auto &obj = info.get(); + data.append(std::move(obj)); } invoker(200, headerOut, val.toStyledString()); }, - [](std::shared_ptr &&info) -> std::shared_ptr { + [](toolkit::Any &&info) -> toolkit::Any { auto obj = std::make_shared(); - auto session = static_pointer_cast(info); - fillSockInfo(*obj, session.get()); - (*obj)["typeid"] = toolkit::demangle(typeid(*session).name()); - return obj; + auto &sock = info.get(); + fillSockInfo(*obj, &sock); + (*obj)["typeid"] = toolkit::demangle(typeid(sock).name()); + toolkit::Any ret; + ret.set(obj); + return ret; }); }); + api_regist("/index/api/broadcastMessage", [](API_ARGS_MAP) { + CHECK_SECRET(); + CHECK_ARGS("schema", "vhost", "app", "stream", "msg"); + auto src = MediaSource::find(allArgs["schema"], allArgs["vhost"], allArgs["app"], allArgs["stream"]); + if (!src) { + throw ApiRetException("can not find the stream", API::NotFound); + } + Any any; + Buffer::Ptr buffer = std::make_shared(allArgs["msg"]); + any.set(std::move(buffer)); + src->broadcastMessage(any); + }); + //测试url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs api_regist("/index/api/getMediaInfo",[](API_ARGS_MAP_ASYNC){ CHECK_SECRET(); @@ -1563,7 +1588,7 @@ void installWebApi() { } //找到截图 - auto tm = FindField(path.data() + scan_path.size(), nullptr, ".jpeg"); + auto tm = findSubString(path.data() + scan_path.size(), nullptr, ".jpeg"); if (atoll(tm.data()) + expire_sec < time(NULL)) { //截图已经过期,改名,以便再次请求时,可以返回老截图 rename(path.data(), new_snap.data()); @@ -1637,7 +1662,7 @@ void installWebApi() { CHECK_ARGS("app", "stream"); return StrPrinter << RTC_SCHEMA << "://" << _args["Host"] << "/" << _args["app"] << "/" - << _args["stream"] << "?" << _args.getParser().Params() + "&session=" + _session_id; + << _args["stream"] << "?" << _args.getParser().params() + "&session=" + _session_id; } private: @@ -1700,7 +1725,7 @@ void installWebApi() { api_regist(delete_webrtc_url, [](API_ARGS_MAP_ASYNC) { CHECK_ARGS("id", "token"); - CHECK(allArgs.getParser().Method() == "DELETE", "http method is not DELETE: " + allArgs.getParser().Method()); + CHECK(allArgs.getParser().method() == "DELETE", "http method is not DELETE: " + allArgs.getParser().method()); auto obj = WebRtcTransportManager::Instance().getItem(allArgs["id"]); if (!obj) { invoker(404, headerOut, "id not found"); @@ -1886,24 +1911,28 @@ void installWebApi() { void unInstallWebApi(){ { lock_guard lck(s_proxyMapMtx); - s_proxyMap.clear(); + auto proxyMap(std::move(s_proxyMap)); + proxyMap.clear(); } { lock_guard lck(s_ffmpegMapMtx); - s_ffmpegMap.clear(); + auto ffmpegMap(std::move(s_ffmpegMap)); + ffmpegMap.clear(); } { lock_guard lck(s_proxyPusherMapMtx); - s_proxyPusherMap.clear(); + auto proxyPusherMap(std::move(s_proxyPusherMap)); + proxyPusherMap.clear(); } { #if defined(ENABLE_RTPPROXY) RtpSelector::Instance().clear(); lock_guard lck(s_rtpServerMapMtx); - s_rtpServerMap.clear(); + auto rtpServerMap(std::move(s_rtpServerMap)); + rtpServerMap.clear(); #endif } NoticeCenter::Instance().delListener(&web_api_tag); diff --git a/server/WebApi.h b/server/WebApi.h index 15210d53..460d0132 100755 --- a/server/WebApi.h +++ b/server/WebApi.h @@ -44,6 +44,8 @@ typedef enum { OtherFailed = -1,//业务代码执行失败, Success = 0//执行成功 } ApiErr; + +extern const std::string kSecret; }//namespace API class ApiRetException: public std::runtime_error { @@ -219,14 +221,19 @@ bool checkArgs(Args &args, const First &first, const KeyTypes &...keys) { throw InvalidArgsException("缺少必要参数:" #__VA_ARGS__); \ } -//检查http参数中是否附带secret密钥的宏,127.0.0.1的ip不检查密钥 +// 检查http参数中是否附带secret密钥的宏,127.0.0.1的ip不检查密钥 +// 同时检测是否在ip白名单内 #define CHECK_SECRET() \ - if(sender.get_peer_ip() != "127.0.0.1"){ \ + do { \ + auto ip = sender.get_peer_ip(); \ + if (!HttpFileManager::isIPAllowed(ip)) { \ + throw AuthException("Your ip is not allowed to access the service."); \ + } \ CHECK_ARGS("secret"); \ - if(api_secret != allArgs["secret"]){ \ + if (api_secret != allArgs["secret"]) { \ throw AuthException("secret错误"); \ } \ - } + } while(false); void installWebApi(); void unInstallWebApi(); diff --git a/server/WebHook.cpp b/server/WebHook.cpp index e4b381ac..d05c4f60 100755 --- a/server/WebHook.cpp +++ b/server/WebHook.cpp @@ -37,6 +37,7 @@ const string kOnFlowReport = HOOK_FIELD "on_flow_report"; const string kOnRtspRealm = HOOK_FIELD "on_rtsp_realm"; const string kOnRtspAuth = HOOK_FIELD "on_rtsp_auth"; const string kOnStreamChanged = HOOK_FIELD "on_stream_changed"; +const string kStreamChangedSchemas = HOOK_FIELD "stream_changed_schemas"; const string kOnStreamNotFound = HOOK_FIELD "on_stream_not_found"; const string kOnRecordMp4 = HOOK_FIELD "on_record_mp4"; const string kOnRecordTs = HOOK_FIELD "on_record_ts"; @@ -44,10 +45,10 @@ const string kOnShellLogin = HOOK_FIELD "on_shell_login"; const string kOnStreamNoneReader = HOOK_FIELD "on_stream_none_reader"; const string kOnHttpAccess = HOOK_FIELD "on_http_access"; const string kOnServerStarted = HOOK_FIELD "on_server_started"; +const string kOnServerExited = HOOK_FIELD "on_server_exited"; const string kOnServerKeepalive = HOOK_FIELD "on_server_keepalive"; const string kOnSendRtpStopped = HOOK_FIELD "on_send_rtp_stopped"; const string kOnRtpServerTimeout = HOOK_FIELD "on_rtp_server_timeout"; -const string kAdminParams = HOOK_FIELD "admin_params"; const string kAliveInterval = HOOK_FIELD "alive_interval"; const string kRetry = HOOK_FIELD "retry"; const string kRetryDelay = HOOK_FIELD "retry_delay"; @@ -69,13 +70,14 @@ static onceToken token([]() { mINI::Instance()[kOnStreamNoneReader] = ""; mINI::Instance()[kOnHttpAccess] = ""; mINI::Instance()[kOnServerStarted] = ""; + mINI::Instance()[kOnServerExited] = ""; mINI::Instance()[kOnServerKeepalive] = ""; mINI::Instance()[kOnSendRtpStopped] = ""; mINI::Instance()[kOnRtpServerTimeout] = ""; - mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc"; mINI::Instance()[kAliveInterval] = 30.0; mINI::Instance()[kRetry] = 1; mINI::Instance()[kRetryDelay] = 3.0; + mINI::Instance()[kStreamChangedSchemas] = "rtsp/rtmp/fmp4/ts/hls/hls.fmp4"; }); } // namespace Hook @@ -100,14 +102,14 @@ static void parse_http_response(const SockException &ex, const Parser &res, cons fun(Json::nullValue, errStr, should_retry); return; } - if (res.Url() != "200") { - auto errStr = StrPrinter << "[bad http status code]:" << res.Url() << endl; + if (res.status() != "200") { + auto errStr = StrPrinter << "[bad http status code]:" << res.status() << endl; fun(Json::nullValue, errStr, should_retry); return; } Value result; try { - stringstream ss(res.Content()); + stringstream ss(res.content()); ss >> result; } catch (std::exception &ex) { auto errStr = StrPrinter << "[parse json failed]:" << ex.what() << endl; @@ -164,12 +166,16 @@ string getVhost(const HttpArgs &value) { return val != value.end() ? val->second : ""; } +static atomic s_hook_index { 0 }; + void do_http_hook(const string &url, const ArgsType &body, const function &func, uint32_t retry) { GET_CONFIG(string, mediaServerId, General::kMediaServerId); GET_CONFIG(float, hook_timeoutSec, Hook::kTimeoutSec); GET_CONFIG(float, retry_delay, Hook::kRetryDelay); const_cast(body)["mediaServerId"] = mediaServerId; + const_cast(body)["hook_index"] = s_hook_index++; + auto requester = std::make_shared(); requester->setMethod("POST"); auto bodyStr = to_string(body); @@ -213,12 +219,12 @@ void do_http_hook(const string &url, const ArgsType &body, const function, stream_changed_set, Hook::kStreamChangedSchemas, [](const std::string &str) { + std::set ret; + auto vec = split(str, "/"); + for (auto &schema : vec) { + trim(schema); + if (!schema.empty()) { + ret.emplace(schema); + } + } + return ret; + }); + if (!stream_changed_set.empty() && stream_changed_set.find(sender.getSchema()) == stream_changed_set.end()) { + // 该协议注册注销事件被忽略 + return; + } + ArgsType body; if (bRegist) { body = makeMediaSourceJson(sender); body["regist"] = bRegist; } else { body["schema"] = sender.getSchema(); - body[VHOST_KEY] = sender.getVhost(); - body["app"] = sender.getApp(); - body["stream"] = sender.getId(); + dumpMediaTuple(sender.getMediaTuple(), body); body["regist"] = bRegist; } // 执行hook - do_http_hook(hook_stream_chaned, body, nullptr); + do_http_hook(hook_stream_changed, body, nullptr); }); GET_CONFIG_FUNC(vector, origin_urls, Cluster::kOriginUrl, [](const string &str) { @@ -503,9 +534,7 @@ void installWebHook() { body["file_name"] = info.file_name; body["folder"] = info.folder; body["url"] = info.url; - body["app"] = info.app; - body["stream"] = info.stream; - body[VHOST_KEY] = info.vhost; + dumpMediaTuple(info, body); return body; }; @@ -532,7 +561,7 @@ void installWebHook() { NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastShellLogin, [](BroadcastShellLoginArgs) { GET_CONFIG(string, hook_shell_login, Hook::kOnShellLogin); - if (!hook_enable || hook_shell_login.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_shell_login.empty()) { invoker(""); return; } @@ -561,9 +590,7 @@ void installWebHook() { ArgsType body; body["schema"] = sender.getSchema(); - body[VHOST_KEY] = sender.getVhost(); - body["app"] = sender.getApp(); - body["stream"] = sender.getId(); + dumpMediaTuple(sender.getMediaTuple(), body); weak_ptr weakSrc = sender.shared_from_this(); // 执行hook do_http_hook(hook_stream_none_reader, body, [weakSrc](const Value &obj, const string &err) { @@ -577,16 +604,14 @@ void installWebHook() { }); }); - NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastSendRtpStopped, [](BroadcastSendRtpStopped) { + NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastSendRtpStopped, [](BroadcastSendRtpStoppedArgs) { GET_CONFIG(string, hook_send_rtp_stopped, Hook::kOnSendRtpStopped); if (!hook_enable || hook_send_rtp_stopped.empty()) { return; } ArgsType body; - body[VHOST_KEY] = sender.getVhost(); - body["app"] = sender.getApp(); - body["stream"] = sender.getStreamId(); + dumpMediaTuple(sender.getMediaTuple(), body); body["ssrc"] = ssrc; body["originType"] = (int)sender.getOriginType(MediaSource::NullMediaSource()); body["originTypeStr"] = getOriginTypeString(sender.getOriginType(MediaSource::NullMediaSource())); @@ -614,15 +639,14 @@ void installWebHook() { // 追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能 NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastHttpAccess, [](BroadcastHttpAccessArgs) { GET_CONFIG(string, hook_http_access, Hook::kOnHttpAccess); - if (sender.get_peer_ip() == "127.0.0.1" || parser.Params() == hook_adminparams) { - // 如果是本机或超级管理员访问,那么不做访问鉴权;权限有效期1个小时 - invoker("", "", 60 * 60); - return; - } if (!hook_enable || hook_http_access.empty()) { // 未开启http文件访问鉴权,那么允许访问,但是每次访问都要鉴权; // 因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权) - invoker("", "", 0); + if (!HttpFileManager::isIPAllowed(sender.get_peer_ip())) { + invoker("Your ip is not allowed to access the service.", "", 0); + } else { + invoker("", "", 0); + } return; } @@ -632,7 +656,7 @@ void installWebHook() { body["id"] = sender.getIdentifier(); body["path"] = path; body["is_dir"] = is_dir; - body["params"] = parser.Params(); + body["params"] = parser.params(); for (auto &pr : parser.getHeader()) { body[string("header.") + pr.first] = pr.second; } @@ -650,7 +674,7 @@ void installWebHook() { }); }); - NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeout) { + NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeoutArgs) { GET_CONFIG(string, rtp_server_timeout, Hook::kOnRtpServerTimeout); if (!hook_enable || rtp_server_timeout.empty()) { return; @@ -676,3 +700,7 @@ void unInstallWebHook() { g_keepalive_timer.reset(); NoticeCenter::Instance().delListener(&web_hook_tag); } + +void onProcessExited() { + reportServerExited(); +} \ No newline at end of file diff --git a/server/WebHook.h b/server/WebHook.h index ea99f736..c75d05c3 100755 --- a/server/WebHook.h +++ b/server/WebHook.h @@ -31,6 +31,7 @@ extern const std::string kTimeoutSec; void installWebHook(); void unInstallWebHook(); +void onProcessExited(); /** * 触发http hook请求 * @param url 请求地址 diff --git a/server/main.cpp b/server/main.cpp index 9a190935..70b6348f 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -179,6 +179,29 @@ public: throw ExitException(); }); #endif + (*_parser) << Option(0,/*该选项简称,如果是\x00则说明无简称*/ + "log-slice",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "100",/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "最大保存日志切片个数",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option(0,/*该选项简称,如果是\x00则说明无简称*/ + "log-size",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "256",/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "单个日志切片最大容量,单位MB",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option(0,/*该选项简称,如果是\x00则说明无简称*/ + "log-dir",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + (exeDir() + "log/").data(),/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "日志保存文件夹路径",/*该选项说明文字*/ + nullptr); } ~CMD_main() override{} @@ -213,9 +236,11 @@ int start_main(int argc,char *argv[]) { //设置日志 Logger::Instance().add(std::make_shared("ConsoleChannel", logLevel)); #if !defined(ANDROID) - auto fileChannel = std::make_shared("FileChannel", exeDir() + "log/", logLevel); + auto fileChannel = std::make_shared("FileChannel", cmd_main["log-dir"], logLevel); // 日志最多保存天数 fileChannel->setMaxDay(cmd_main["max_day"]); + fileChannel->setFileMaxCount(cmd_main["log-slice"]); + fileChannel->setFileMaxSize(cmd_main["log-size"]); Logger::Instance().add(fileChannel); #endif // !defined(ANDROID) @@ -326,6 +351,14 @@ int start_main(int argc,char *argv[]) { #endif //defined(ENABLE_SRT) try { + auto &secret = mINI::Instance()[API::kSecret]; + if (secret == "035c73f7-bb6b-4889-a715-d9eb2d1925cc" || secret.empty()) { + // 使用默认secret被禁止启动 + secret = makeRandStr(32, true); + mINI::Instance().dumpFile(g_ini_file); + WarnL << "The " << API::kSecret << " is invalid, modified it to: " << secret + << ", saved config file: " << g_ini_file; + } //rtsp服务器,端口默认554 if (rtspPort) { rtspSrv->start(rtspPort); } //rtsps服务器,端口默认322 @@ -363,8 +396,7 @@ int start_main(int argc,char *argv[]) { #endif//defined(ENABLE_SRT) } catch (std::exception &ex) { - WarnL << "端口占用或无权限:" << ex.what() << endl; - ErrorL << "程序启动失败,请修改配置文件中端口号后重试!" << endl; + ErrorL << "Start server failed: " << ex.what(); sleep(1); #if !defined(_WIN32) if (pid != getpid() && kill_parent_if_failed) { @@ -384,9 +416,15 @@ int start_main(int argc,char *argv[]) { static semaphore sem; signal(SIGINT, [](int) { InfoL << "SIGINT:exit"; - signal(SIGINT, SIG_IGN);// 设置退出信号 + signal(SIGINT, SIG_IGN); // 设置退出信号 sem.post(); - });// 设置退出信号 + }); // 设置退出信号 + + signal(SIGTERM,[](int) { + WarnL << "SIGTERM:exit"; + signal(SIGTERM, SIG_IGN); + sem.post(); + }); #if !defined(_WIN32) signal(SIGHUP, [](int) { mediakit::loadIniConfig(g_ini_file.data()); }); @@ -395,6 +433,8 @@ int start_main(int argc,char *argv[]) { } unInstallWebApi(); unInstallWebHook(); + onProcessExited(); + //休眠1秒再退出,防止资源释放顺序错误 InfoL << "程序退出中,请等待..."; sleep(1); diff --git a/src/Codec/Transcode.cpp b/src/Codec/Transcode.cpp index bab515cc..25f9e77c 100644 --- a/src/Codec/Transcode.cpp +++ b/src/Codec/Transcode.cpp @@ -436,6 +436,7 @@ FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std: av_dict_set(&dict, "zerolatency", "1", 0); av_dict_set(&dict, "strict", "-2", 0); +#ifdef AV_CODEC_CAP_TRUNCATED if (codec->capabilities & AV_CODEC_CAP_TRUNCATED) { /* we do not send complete frames */ _context->flags |= AV_CODEC_FLAG_TRUNCATED; @@ -443,6 +444,7 @@ FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std: // 此时业务层应该需要合帧 _do_merger = true; } +#endif int ret = avcodec_open2(_context.get(), codec, &dict); av_dict_free(&dict); diff --git a/src/Common/JemallocUtil.cpp b/src/Common/JemallocUtil.cpp new file mode 100644 index 00000000..fa2de64a --- /dev/null +++ b/src/Common/JemallocUtil.cpp @@ -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 +#include +#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 &fn) { +#ifdef USE_JEMALLOC + constexpr std::array 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 \ No newline at end of file diff --git a/src/Common/JemallocUtil.h b/src/Common/JemallocUtil.h new file mode 100644 index 00000000..796a58bb --- /dev/null +++ b/src/Common/JemallocUtil.h @@ -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 +#include +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 &fn); +}; +} // namespace mediakit +#endif // ZLMEDIAKIT_JEMALLOCUTIL_H diff --git a/src/Common/MediaSource.cpp b/src/Common/MediaSource.cpp index fb3a21ce..1977b616 100644 --- a/src/Common/MediaSource.cpp +++ b/src/Common/MediaSource.cpp @@ -15,8 +15,10 @@ #include "MediaSource.h" #include "Common/config.h" #include "Common/Parser.h" +#include "Common/MultiMediaSourceMuxer.h" #include "Record/MP4Reader.h" #include "PacketCache.h" + using namespace std; using namespace toolkit; @@ -53,12 +55,14 @@ string getOriginTypeString(MediaOriginType type){ ////////////////////////////////////////////////////////////////////////////////////////////////////////////// ProtocolOption::ProtocolOption() { - GET_CONFIG(bool, s_modify_stamp, Protocol::kModifyStamp); + GET_CONFIG(int, s_modify_stamp, Protocol::kModifyStamp); GET_CONFIG(bool, s_enabel_audio, Protocol::kEnableAudio); GET_CONFIG(bool, s_add_mute_audio, Protocol::kAddMuteAudio); + GET_CONFIG(bool, s_auto_close, Protocol::kAutoClose); GET_CONFIG(uint32_t, s_continue_push_ms, Protocol::kContinuePushMS); GET_CONFIG(bool, s_enable_hls, Protocol::kEnableHls); + GET_CONFIG(bool, s_enable_hls_fmp4, Protocol::kEnableHlsFmp4); GET_CONFIG(bool, s_enable_mp4, Protocol::kEnableMP4); GET_CONFIG(bool, s_enable_rtsp, Protocol::kEnableRtsp); GET_CONFIG(bool, s_enable_rtmp, Protocol::kEnableRtmp); @@ -80,9 +84,11 @@ ProtocolOption::ProtocolOption() { modify_stamp = s_modify_stamp; enable_audio = s_enabel_audio; add_mute_audio = s_add_mute_audio; + auto_close = s_auto_close; continue_push_ms = s_continue_push_ms; enable_hls = s_enable_hls; + enable_hls_fmp4 = s_enable_hls_fmp4; enable_mp4 = s_enable_mp4; enable_rtsp = s_enable_rtsp; enable_rtmp = s_enable_rtmp; @@ -124,24 +130,11 @@ MediaSource::MediaSource(const string &schema, const MediaTuple& tuple): _tuple( } MediaSource::~MediaSource() { - unregist(); -} - -const string& MediaSource::getSchema() const { - return _schema; -} - -const string& MediaSource::getVhost() const { - return _tuple.vhost; -} - -const string& MediaSource::getApp() const { - //获取该源的id - return _tuple.app; -} - -const string& MediaSource::getId() const { - return _tuple.stream; + try { + unregist(); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } } std::shared_ptr MediaSource::getOwnership() { @@ -183,20 +176,8 @@ void MediaSource::setListener(const std::weak_ptr &listener){ _listener = listener; } -std::weak_ptr MediaSource::getListener(bool next) const{ - if (!next) { - return _listener; - } - - auto listener = dynamic_pointer_cast(_listener.lock()); - if (!listener) { - //不是MediaSourceEventInterceptor对象或者对象已经销毁 - return _listener; - } - //获取被拦截的对象 - auto next_obj = listener->getDelegate(); - //有则返回之 - return next_obj ? next_obj : _listener; +std::weak_ptr MediaSource::getListener() const { + return _listener; } int MediaSource::totalReaderCount(){ @@ -288,6 +269,11 @@ toolkit::EventPoller::Ptr MediaSource::getOwnerPoller() { throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed: " + getUrl()); } +std::shared_ptr MediaSource::getMuxer() { + auto listener = _listener.lock(); + return listener ? listener->getMuxer(*this) : nullptr; +} + void MediaSource::onReaderChanged(int size) { try { weak_ptr weak_self = shared_from_this(); @@ -456,9 +442,7 @@ static void findAsync_l(const MediaInfo &info, const std::shared_ptr &s auto on_register = [weak_session, info, cb_once, cancel_all, poller](BroadcastMediaChangedArgs) { if (!bRegist || sender.getSchema() != info.schema || - sender.getVhost() != info.vhost || - sender.getApp() != info.app || - sender.getId() != info.stream) { + !equalMediaTuple(sender.getMediaTuple(), info)) { //不是自己感兴趣的事件,忽略之 return; } @@ -485,7 +469,7 @@ static void findAsync_l(const MediaInfo &info, const std::shared_ptr &s }); }; //广播未找到流,此时可以立即去拉流,这样还来得及 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastNotFoundStream, info, static_cast(*session), close_player); + NOTICE_EMIT(BroadcastNotFoundStreamArgs, Broadcast::kBroadcastNotFoundStream, info, *session, close_player); } void MediaSource::findAsync(const MediaInfo &info, const std::shared_ptr &session, const function &cb) { @@ -515,7 +499,7 @@ void MediaSource::emitEvent(bool regist){ listener->onRegist(*this, regist); } //触发广播 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, regist, *this); + NOTICE_EMIT(BroadcastMediaChangedArgs, Broadcast::kBroadcastMediaChanged, regist, *this); InfoL << (regist ? "媒体注册:" : "媒体注销:") << getUrl(); } @@ -575,6 +559,9 @@ bool MediaSource::unregist() { return ret; } +bool equalMediaTuple(const MediaTuple& a, const MediaTuple& b) { + return a.vhost == b.vhost && a.app == b.app && a.stream == b.stream; +} /////////////////////////////////////MediaInfo////////////////////////////////////// void MediaInfo::parse(const std::string &url_in){ @@ -659,7 +646,7 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){ GET_CONFIG(string, record_app, Record::kAppName); GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS); //如果mp4点播, 无人观看时我们强制关闭点播 - bool is_mp4_vod = sender.getApp() == record_app; + bool is_mp4_vod = sender.getMediaTuple().app == record_app; weak_ptr weak_sender = sender.shared_from_this(); _async_close_timer = std::make_shared(stream_none_reader_delay / 1000.0f, [weak_sender, is_mp4_vod]() { @@ -675,8 +662,15 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){ } if (!is_mp4_vod) { - //直播时触发无人观看事件,让开发者自行选择是否关闭 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender); + auto muxer = strong_sender->getMuxer(); + if (muxer && muxer->getOption().auto_close) { + // 此流被标记为无人观看自动关闭流 + WarnL << "Auto cloe stream when none reader: " << strong_sender->getUrl(); + strong_sender->close(false); + } else { + // 直播时触发无人观看事件,让开发者自行选择是否关闭 + NOTICE_EMIT(BroadcastStreamNoneReaderArgs, Broadcast::kBroadcastStreamNoneReader, *strong_sender); + } } else { //这个是mp4点播,我们自动关闭 WarnL << "MP4点播无人观看,自动关闭:" << strong_sender->getUrl(); @@ -790,6 +784,11 @@ toolkit::EventPoller::Ptr MediaSourceEventInterceptor::getOwnerPoller(MediaSourc throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed"); } +std::shared_ptr MediaSourceEventInterceptor::getMuxer(MediaSource &sender) { + auto listener = _listener.lock(); + return listener ? listener->getMuxer(sender) : nullptr; +} + bool MediaSourceEventInterceptor::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) { auto listener = _listener.lock(); if (!listener) { diff --git a/src/Common/MediaSource.h b/src/Common/MediaSource.h index df855293..bcf75a09 100644 --- a/src/Common/MediaSource.h +++ b/src/Common/MediaSource.h @@ -41,6 +41,7 @@ enum class MediaOriginType : uint8_t { std::string getOriginTypeString(MediaOriginType type); class MediaSource; +class MultiMediaSourceMuxer; class MediaSourceEvent { public: friend class MediaSource; @@ -88,6 +89,8 @@ public: virtual bool isRecording(MediaSource &sender, Recorder::type type) { return false; } // 获取所有track相关信息 virtual std::vector getMediaTracks(MediaSource &sender, bool trackReady = true) const { return std::vector(); }; + // 获取MultiMediaSourceMuxer对象 + virtual std::shared_ptr getMuxer(MediaSource &sender) { return nullptr; } class SendRtpArgs { public: @@ -136,17 +139,30 @@ class ProtocolOption { public: ProtocolOption(); - //时间戳修复这一路流标志位 - bool modify_stamp; + enum { + kModifyStampOff = 0, // 采用源视频流绝对时间戳,不做任何改变 + kModifyStampSystem = 1, // 采用zlmediakit接收数据时的系统时间戳(有平滑处理) + kModifyStampRelative = 2 // 采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正 + }; + // 时间戳类型 + int modify_stamp; + //转协议是否开启音频 bool enable_audio; //添加静音音频,在关闭音频时,此开关无效 bool add_mute_audio; + // 无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close) + // 此配置置1时,此流如果无人观看,将不触发on_none_reader hook回调, + // 而是将直接关闭流 + bool auto_close; + //断连续推延时,单位毫秒,默认采用配置文件 uint32_t continue_push_ms; - //是否开启转换为hls + //是否开启转换为hls(mpegts) bool enable_hls; + //是否开启转换为hls(fmp4) + bool enable_hls_fmp4; //是否开启MP4录制 bool enable_mp4; //是否开启转换为rtsp/webrtc @@ -179,15 +195,20 @@ public: //hls录制保存路径 std::string hls_save_path; + // 支持通过on_publish返回值替换stream_id + std::string stream_replace; + template ProtocolOption(const MAP &allArgs) : ProtocolOption() { #define GET_OPT_VALUE(key) getArgsValue(allArgs, #key, key) GET_OPT_VALUE(modify_stamp); GET_OPT_VALUE(enable_audio); GET_OPT_VALUE(add_mute_audio); + GET_OPT_VALUE(auto_close); GET_OPT_VALUE(continue_push_ms); GET_OPT_VALUE(enable_hls); + GET_OPT_VALUE(enable_hls_fmp4); GET_OPT_VALUE(enable_mp4); GET_OPT_VALUE(enable_rtsp); GET_OPT_VALUE(enable_rtmp); @@ -205,6 +226,7 @@ public: GET_OPT_VALUE(mp4_save_path); GET_OPT_VALUE(hls_save_path); + GET_OPT_VALUE(stream_replace); } private: @@ -244,6 +266,7 @@ public: bool stopSendRtp(MediaSource &sender, const std::string &ssrc) override; float getLossRate(MediaSource &sender, TrackType type) override; toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; + std::shared_ptr getMuxer(MediaSource &sender) override; private: std::weak_ptr _listener; @@ -268,6 +291,8 @@ public: std::string param_strs; }; +bool equalMediaTuple(const MediaTuple& a, const MediaTuple& b); + /** * 媒体源,任何rtsp/rtmp的直播流都源自该对象 */ @@ -282,21 +307,15 @@ public: ////////////////获取MediaSource相关信息//////////////// // 获取协议类型 - const std::string& getSchema() const; - // 虚拟主机 - const std::string& getVhost() const; - // 应用名 - const std::string& getApp() const; - // 流id - const std::string& getId() const; + const std::string& getSchema() const { + return _schema; + } const MediaTuple& getMediaTuple() const { return _tuple; } - std::string shortUrl() const { return _tuple.shortUrl(); } - - std::string getUrl() const { return _schema + "://" + shortUrl(); } + std::string getUrl() const { return _schema + "://" + _tuple.shortUrl(); } //获取对象所有权 std::shared_ptr getOwnership(); @@ -321,19 +340,21 @@ public: // 设置监听者 virtual void setListener(const std::weak_ptr &listener); // 获取监听者 - std::weak_ptr getListener(bool next = false) const; + std::weak_ptr getListener() const; // 本协议获取观看者个数,可能返回本协议的观看人数,也可能返回总人数 virtual int readerCount() = 0; // 观看者个数,包括(hls/rtsp/rtmp) virtual int totalReaderCount(); // 获取播放器列表 - virtual void getPlayerList(const std::function> &info_list)> &cb, - const std::function(std::shared_ptr &&info)> &on_change) { + virtual void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) { assert(cb); - cb(std::list>()); + cb(std::list()); } + virtual bool broadcastMessage(const toolkit::Any &data) { return false; } + // 获取媒体源类型 MediaOriginType getOriginType() const; // 获取媒体源url或者文件路径 @@ -363,6 +384,8 @@ public: float getLossRate(mediakit::TrackType type); // 获取所在线程 toolkit::EventPoller::Ptr getOwnerPoller(); + // 获取MultiMediaSourceMuxer对象 + std::shared_ptr getMuxer(); ////////////////static方法,查找或生成MediaSource//////////////// diff --git a/src/Common/MultiMediaSourceMuxer.cpp b/src/Common/MultiMediaSourceMuxer.cpp index 75c79c27..bbb9b357 100644 --- a/src/Common/MultiMediaSourceMuxer.cpp +++ b/src/Common/MultiMediaSourceMuxer.cpp @@ -37,6 +37,7 @@ static std::shared_ptr makeRecorder(MediaSource &sender, con for (auto &track : tracks) { recorder->addTrack(track); } + recorder->addTrackCompleted(); return recorder; } @@ -70,16 +71,12 @@ static string getTrackInfoStr(const TrackSource *track_src){ return std::move(codec_info); } -const std::string &MultiMediaSourceMuxer::getVhost() const { - return _tuple.vhost; +const ProtocolOption &MultiMediaSourceMuxer::getOption() const { + return _option; } -const std::string &MultiMediaSourceMuxer::getApp() const { - return _tuple.app; -} - -const std::string &MultiMediaSourceMuxer::getStreamId() const { - return _tuple.stream; +const MediaTuple &MultiMediaSourceMuxer::getMediaTuple() const { + return _tuple; } std::string MultiMediaSourceMuxer::shortUrl() const { @@ -91,9 +88,18 @@ std::string MultiMediaSourceMuxer::shortUrl() const { } MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_sec, const ProtocolOption &option): _tuple(tuple) { + if (!option.stream_replace.empty()) { + // 支持在on_publish hook中替换stream_id + _tuple.stream = option.stream_replace; + } _poller = EventPollerPool::Instance().getPoller(); _create_in_poller = _poller->isCurrentThread(); _option = option; + if (dur_sec > 0.01) { + // 点播 + _stamp[TrackVideo].setPlayBack(); + _stamp[TrackAudio].setPlayBack(); + } if (option.enable_rtmp) { _rtmp = std::make_shared(_tuple, option, std::make_shared(dur_sec)); @@ -104,17 +110,18 @@ MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_ if (option.enable_hls) { _hls = dynamic_pointer_cast(Recorder::createRecorder(Recorder::type_hls, _tuple, option)); } + if (option.enable_hls_fmp4) { + _hls_fmp4 = dynamic_pointer_cast(Recorder::createRecorder(Recorder::type_hls_fmp4, _tuple, option)); + } if (option.enable_mp4) { _mp4 = Recorder::createRecorder(Recorder::type_mp4, _tuple, option); } if (option.enable_ts) { - _ts = std::make_shared(_tuple, option); + _ts = dynamic_pointer_cast(Recorder::createRecorder(Recorder::type_ts, _tuple, option)); } -#if defined(ENABLE_MP4) if (option.enable_fmp4) { - _fmp4 = std::make_shared(_tuple, option); + _fmp4 = dynamic_pointer_cast(Recorder::createRecorder(Recorder::type_fmp4, _tuple, option)); } -#endif //音频相关设置 enableAudio(option.enable_audio); @@ -135,14 +142,14 @@ void MultiMediaSourceMuxer::setMediaListener(const std::weak_ptrsetListener(self); } -#if defined(ENABLE_MP4) if (_fmp4) { _fmp4->setListener(self); } -#endif - auto hls = _hls; - if (hls) { - hls->setListener(self); + if (_hls_fmp4) { + _hls_fmp4->setListener(self); + } + if (_hls) { + _hls->setListener(self); } } @@ -151,15 +158,13 @@ void MultiMediaSourceMuxer::setTrackListener(const std::weak_ptr &list } int MultiMediaSourceMuxer::totalReaderCount() const { - auto hls = _hls; return (_rtsp ? _rtsp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) + (_ts ? _ts->readerCount() : 0) + - #if defined(ENABLE_MP4) (_fmp4 ? _fmp4->readerCount() : 0) + - #endif (_mp4 ? _option.mp4_as_player : 0) + - (hls ? hls->readerCount() : 0) + + (_hls ? _hls->readerCount() : 0) + + (_hls_fmp4 ? _hls_fmp4->readerCount() : 0) + (_ring ? _ring->readerCount() : 0); } @@ -187,6 +192,7 @@ int MultiMediaSourceMuxer::totalReaderCount(MediaSource &sender) { //此函数可能跨线程调用 bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) { + CHECK(getOwnerPoller(MediaSource::NullMediaSource())->isCurrentThread(), "Can only call setupRecord in it's owner poller"); onceToken token(nullptr, [&]() { if (_option.mp4_as_player && type == Recorder::type_mp4) { //开启关闭mp4录制,触发观看人数变化相关事件 @@ -222,19 +228,59 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type } return true; } + case Recorder::type_hls_fmp4: { + if (start && !_hls_fmp4) { + //开始录制 + _option.hls_save_path = custom_path; + auto hls = dynamic_pointer_cast(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(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(makeRecorder(sender, getTracks(), type, _option)); + if (ts) { + ts->setListener(shared_from_this()); + } + _ts = ts; + } else if (!start && _ts) { + _ts = nullptr; + } + return true; + } default : return false; } } //此函数可能跨线程调用 bool MultiMediaSourceMuxer::isRecording(MediaSource &sender, Recorder::type type) { - switch (type){ - case Recorder::type_hls : - return !!_hls; - case Recorder::type_mp4 : - return !!_mp4; - default: - return false; + switch (type) { + case Recorder::type_hls: return !!_hls; + case Recorder::type_mp4: return !!_mp4; + case Recorder::type_hls_fmp4: return !!_hls_fmp4; + case Recorder::type_fmp4: return !!_fmp4; + case Recorder::type_ts: return !!_ts; + default: return false; } } @@ -266,7 +312,7 @@ void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const MediaSourceE strong_self->getOwnerPoller(MediaSource::NullMediaSource())->async([=]() { WarnL << "stream:" << strong_self->shortUrl() << " stop send rtp:" << ssrc << ", reason:" << ex; strong_self->_rtp_sender.erase(ssrc); - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastSendRtpStopped, *strong_self, ssrc, ex); + NOTICE_EMIT(BroadcastSendRtpStoppedArgs, Broadcast::kBroadcastSendRtpStopped, *strong_self, ssrc, ex); }); } }); @@ -323,6 +369,10 @@ EventPoller::Ptr MultiMediaSourceMuxer::getOwnerPoller(MediaSource &sender) { } } +std::shared_ptr MultiMediaSourceMuxer::getMuxer(MediaSource &sender) { + return shared_from_this(); +} + bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) { bool ret = false; if (_rtmp) { @@ -334,20 +384,17 @@ bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) { if (_ts) { ret = _ts->addTrack(track) ? true : ret; } -#if defined(ENABLE_MP4) if (_fmp4) { ret = _fmp4->addTrack(track) ? true : ret; } -#endif - - //拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 - auto hls = _hls; - if (hls) { - ret = hls->addTrack(track) ? true : ret; + if (_hls) { + ret = _hls->addTrack(track) ? true : ret; } - auto mp4 = _mp4; - if (mp4) { - ret = mp4->addTrack(track) ? true : ret; + if (_hls_fmp4) { + ret = _hls_fmp4->addTrack(track) ? true : ret; + } + if (_mp4) { + ret = _mp4->addTrack(track) ? true : ret; } return ret; } @@ -357,16 +404,27 @@ void MultiMediaSourceMuxer::onAllTrackReady() { setMediaListener(getDelegate()); if (_rtmp) { - _rtmp->onAllTrackReady(); + _rtmp->addTrackCompleted(); } if (_rtsp) { - _rtsp->onAllTrackReady(); + _rtsp->addTrackCompleted(); + } + if (_ts) { + _ts->addTrackCompleted(); + } + if (_mp4) { + _mp4->addTrackCompleted(); } -#if defined(ENABLE_MP4) if (_fmp4) { - _fmp4->onAllTrackReady(); + _fmp4->addTrackCompleted(); } -#endif + if (_hls) { + _hls->addTrackCompleted(); + } + if (_hls_fmp4) { + _hls_fmp4->addTrackCompleted(); + } + auto listener = _track_listener.lock(); if (listener) { listener->onAllTrackReady(); @@ -378,6 +436,11 @@ void MultiMediaSourceMuxer::onAllTrackReady() { createGopCacheIfNeed(); } #endif + auto tracks = getTracks(false); + if (tracks.size() >= 2) { + // 音频时间戳同步于视频,因为音频时间戳被修改后不影响播放 + _stamp[TrackAudio].syncTo(_stamp[TrackVideo]); + } InfoL << "stream: " << shortUrl() << " , codec info: " << getTrackInfoStr(this); } @@ -409,29 +472,25 @@ void MultiMediaSourceMuxer::resetTracks() { if (_ts) { _ts->resetTracks(); } -#if defined(ENABLE_MP4) if (_fmp4) { _fmp4->resetTracks(); } -#endif - - //拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 - auto hls = _hls; - if (hls) { - hls->resetTracks(); + if (_hls_fmp4) { + _hls_fmp4->resetTracks(); } - - auto mp4 = _mp4; - if (mp4) { - mp4->resetTracks(); + if (_hls) { + _hls->resetTracks(); + } + if (_mp4) { + _mp4->resetTracks(); } } bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) { auto frame = frame_in; - if (_option.modify_stamp) { - //开启了时间戳覆盖 - frame = std::make_shared(frame, _stamp[frame->getTrackType()],true); + if (_option.modify_stamp != ProtocolOption::kModifyStampOff) { + // 时间戳不采用原始的绝对时间戳 + frame = std::make_shared(frame, _stamp[frame->getTrackType()], _option.modify_stamp); } bool ret = false; @@ -445,23 +504,20 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) { ret = _ts->inputFrame(frame) ? true : ret; } - //拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 - //此处使用智能指针拷贝来确保线程安全,比互斥锁性能更优 - auto hls = _hls; - if (hls) { - ret = hls->inputFrame(frame) ? true : ret; - } - auto mp4 = _mp4; - if (mp4) { - ret = mp4->inputFrame(frame) ? true : ret; + if (_hls) { + ret = _hls->inputFrame(frame) ? true : ret; } -#if defined(ENABLE_MP4) + if (_hls_fmp4) { + ret = _hls_fmp4->inputFrame(frame) ? true : ret; + } + + if (_mp4) { + ret = _mp4->inputFrame(frame) ? true : ret; + } if (_fmp4) { ret = _fmp4->inputFrame(frame) ? true : ret; } -#endif - if (_ring) { if (frame->getTrackType() == TrackVideo) { // 视频时,遇到第一帧配置帧或关键帧则标记为gop开始处 @@ -483,15 +539,14 @@ bool MultiMediaSourceMuxer::isEnabled(){ if (!_is_enable || _last_check.elapsedTime() > stream_none_reader_delay_ms) { //无人观看时,每次检查是否真的无人观看 //有人观看时,则延迟一定时间检查一遍是否无人观看了(节省性能) - auto hls = _hls; _is_enable = (_rtmp ? _rtmp->isEnabled() : false) || (_rtsp ? _rtsp->isEnabled() : false) || (_ts ? _ts->isEnabled() : false) || - #if defined(ENABLE_MP4) (_fmp4 ? _fmp4->isEnabled() : false) || - #endif (_ring ? (bool)_ring->readerCount() : false) || - (hls ? hls->isEnabled() : false) || _mp4; + (_hls ? _hls->isEnabled() : false) || + (_hls_fmp4 ? _hls_fmp4->isEnabled() : false) || + _mp4; if (_is_enable) { //无人观看时,不刷新计时器,因为无人观看时每次都会检查一遍,所以刷新计数器无意义且浪费cpu diff --git a/src/Common/MultiMediaSourceMuxer.h b/src/Common/MultiMediaSourceMuxer.h index 0e4f6a5b..4db34d42 100644 --- a/src/Common/MultiMediaSourceMuxer.h +++ b/src/Common/MultiMediaSourceMuxer.h @@ -126,12 +126,13 @@ public: */ toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; - const std::string& getVhost() const; - const std::string& getApp() const; - const std::string& getStreamId() const; - const MediaTuple& getMediaTuple() const { - return _tuple; - } + /** + * 获取本对象 + */ + std::shared_ptr getMuxer(MediaSource &sender) override; + + const ProtocolOption &getOption() const; + const MediaTuple &getMediaTuple() const; std::string shortUrl() const; protected: @@ -167,18 +168,14 @@ private: toolkit::Ticker _last_check; Stamp _stamp[2]; std::weak_ptr _track_listener; -#if defined(ENABLE_RTPPROXY) std::unordered_map _rtp_sender; -#endif //ENABLE_RTPPROXY - -#if defined(ENABLE_MP4) FMP4MediaSourceMuxer::Ptr _fmp4; -#endif RtmpMediaSourceMuxer::Ptr _rtmp; RtspMediaSourceMuxer::Ptr _rtsp; TSMediaSourceMuxer::Ptr _ts; MediaSinkInterface::Ptr _mp4; HlsRecorder::Ptr _hls; + HlsFMP4Recorder::Ptr _hls_fmp4; toolkit::EventPoller::Ptr _poller; RingType::Ptr _ring; diff --git a/src/Common/Parser.cpp b/src/Common/Parser.cpp index 2620ba31..9c073f2c 100644 --- a/src/Common/Parser.cpp +++ b/src/Common/Parser.cpp @@ -10,19 +10,22 @@ #include #include "Parser.h" +#include "strCoding.h" #include "macros.h" #include "Network/sockutil.h" +#include "Common/macros.h" using namespace std; using namespace toolkit; -namespace mediakit{ +namespace mediakit { -string FindField(const char* buf, const char* start, const char *end ,size_t bufSize) { - if(bufSize <=0 ){ - bufSize = strlen(buf); +string findSubString(const char *buf, const char *start, const char *end, size_t buf_size) { + if (buf_size <= 0) { + buf_size = strlen(buf); } - const char *msg_start = buf, *msg_end = buf + bufSize; + auto msg_start = buf; + auto msg_end = buf + buf_size; size_t len = 0; if (start != NULL) { len = strlen(start); @@ -41,126 +44,147 @@ string FindField(const char* buf, const char* start, const char *end ,size_t buf return string(msg_start, msg_end); } -void Parser::Parse(const char *buf) { - //解析 - const char *start = buf; - Clear(); +void Parser::parse(const char *buf, size_t size) { + clear(); + auto ptr = buf; while (true) { - auto line = FindField(start, NULL, "\r\n"); - if (line.size() == 0) { - break; + auto next_line = strchr(ptr, '\n'); + auto offset = 1; + CHECK(next_line && next_line > ptr); + if (*(next_line - 1) == '\r') { + next_line -= 1; + offset = 2; } - if (start == buf) { - _strMethod = FindField(line.data(), NULL, " "); - auto strFullUrl = FindField(line.data(), " ", " "); - auto args_pos = strFullUrl.find('?'); - if (args_pos != string::npos) { - _strUrl = strFullUrl.substr(0, args_pos); - _params = strFullUrl.substr(args_pos + 1); - _mapUrlArgs = parseArgs(_params); - } else { - _strUrl = strFullUrl; + if (ptr == buf) { + auto blank = strchr(ptr, ' '); + CHECK(blank > ptr && blank < next_line); + _method = std::string(ptr, blank - ptr); + auto next_blank = strchr(blank + 1, ' '); + CHECK(next_blank && next_blank < next_line); + _url.assign(blank + 1, next_blank); + auto pos = _url.find('?'); + if (pos != string::npos) { + _params = _url.substr(pos + 1); + _url_args = parseArgs(_params); + _url = _url.substr(0, pos); } - _strTail = FindField(line.data(), (strFullUrl + " ").data(), NULL); + _protocol = std::string(next_blank + 1, next_line); } else { - auto field = FindField(line.data(), NULL, ": "); - auto value = FindField(line.data(), ": ", NULL); - if (field.size() != 0) { - _mapHeaders.emplace_force(field, value); + auto pos = strchr(ptr, ':'); + CHECK(pos > ptr && pos < next_line); + std::string key { ptr, static_cast(pos - ptr) }; + std::string value; + if (pos[1] == ' ') { + value.assign(pos + 2, next_line); + } else { + value.assign(pos + 1, next_line); } + _headers.emplace_force(trim(std::move(key)), trim(std::move(value))); } - start = start + line.size() + 2; - if (strncmp(start, "\r\n", 2) == 0) { //协议解析完毕 - _strContent = FindField(start, "\r\n", NULL); + ptr = next_line + offset; + if (strncmp(ptr, "\r\n", 2) == 0) { // 协议解析完毕 + _content.assign(ptr + 2, buf + size); break; } } } -const string &Parser::Method() const { - return _strMethod; +const string &Parser::method() const { + return _method; } -const string &Parser::Url() const { - return _strUrl; +const string &Parser::url() const { + return _url; } -string Parser::FullUrl() const { +const std::string &Parser::status() const { + return url(); +} + +string Parser::fullUrl() const { if (_params.empty()) { - return _strUrl; + return _url; } - return _strUrl + "?" + _params; + return _url + "?" + _params; } -const string &Parser::Tail() const { - return _strTail; +const string &Parser::protocol() const { + return _protocol; } +const std::string &Parser::statusStr() const { + return protocol(); +} + +static std::string kNull; + const string &Parser::operator[](const char *name) const { - auto it = _mapHeaders.find(name); - if (it == _mapHeaders.end()) { - return _strNull; + auto it = _headers.find(name); + if (it == _headers.end()) { + return kNull; } return it->second; } -const string &Parser::Content() const { - return _strContent; +const string &Parser::content() const { + return _content; } -void Parser::Clear() { - _strMethod.clear(); - _strUrl.clear(); +void Parser::clear() { + _method.clear(); + _url.clear(); _params.clear(); - _strTail.clear(); - _strContent.clear(); - _mapHeaders.clear(); - _mapUrlArgs.clear(); + _protocol.clear(); + _content.clear(); + _headers.clear(); + _url_args.clear(); } -const string &Parser::Params() const { +const string &Parser::params() const { return _params; } void Parser::setUrl(string url) { - this->_strUrl = std::move(url); + _url = std::move(url); } void Parser::setContent(string content) { - this->_strContent = std::move(content); + _content = std::move(content); } StrCaseMap &Parser::getHeader() const { - return _mapHeaders; + return _headers; } StrCaseMap &Parser::getUrlArgs() const { - return _mapUrlArgs; + return _url_args; } StrCaseMap Parser::parseArgs(const string &str, const char *pair_delim, const char *key_delim) { StrCaseMap ret; auto arg_vec = split(str, pair_delim); - for (string &key_val : arg_vec) { + for (auto &key_val : arg_vec) { if (key_val.empty()) { - //忽略 + // 忽略 continue; } - auto key = trim(FindField(key_val.data(), NULL, key_delim)); - if (!key.empty()) { - auto val = trim(FindField(key_val.data(), key_delim, NULL)); - ret.emplace_force(key, val); + auto pos = key_val.find(key_delim); + if (pos != string::npos) { + auto key = trim(std::string(key_val, 0, pos)); + auto val = trim(key_val.substr(pos + strlen(key_delim))); + ret.emplace_force(std::move(key), std::move(val)); } else { trim(key_val); if (!key_val.empty()) { - ret.emplace_force(key_val, ""); + ret.emplace_force(std::move(key_val), ""); } } } return ret; } -std::string Parser::merge_url(const string &base_url, const string &path) { - //以base_url为基础, 合并path路径生成新的url, path支持相对路径和绝对路径 + +std::string Parser::mergeUrl(const string &base_url, const string &path) { + // 以base_url为基础, 合并path路径生成新的url, path支持相对路径和绝对路径 if (base_url.empty()) { return path; } @@ -234,43 +258,45 @@ std::string Parser::merge_url(const string &base_url, const string &path) { } return final_url.str(); } + void RtspUrl::parse(const string &strUrl) { - auto schema = FindField(strUrl.data(), nullptr, "://"); + auto schema = findSubString(strUrl.data(), nullptr, "://"); bool is_ssl = strcasecmp(schema.data(), "rtsps") == 0; - //查找"://"与"/"之间的字符串,用于提取用户名密码 - auto middle_url = FindField(strUrl.data(), "://", "/"); + // 查找"://"与"/"之间的字符串,用于提取用户名密码 + auto middle_url = findSubString(strUrl.data(), "://", "/"); if (middle_url.empty()) { - middle_url = FindField(strUrl.data(), "://", nullptr); + middle_url = findSubString(strUrl.data(), "://", nullptr); } auto pos = middle_url.rfind('@'); if (pos == string::npos) { - //并没有用户名密码 + // 并没有用户名密码 return setup(is_ssl, strUrl, "", ""); } - //包含用户名密码 + // 包含用户名密码 auto user_pwd = middle_url.substr(0, pos); auto suffix = strUrl.substr(schema.size() + 3 + pos + 1); auto url = StrPrinter << "rtsp://" << suffix << endl; if (user_pwd.find(":") == string::npos) { return setup(is_ssl, url, user_pwd, ""); } - auto user = FindField(user_pwd.data(), nullptr, ":"); - auto pwd = FindField(user_pwd.data(), ":", nullptr); + auto user = findSubString(user_pwd.data(), nullptr, ":"); + auto pwd = findSubString(user_pwd.data(), ":", nullptr); return setup(is_ssl, url, user, pwd); } void RtspUrl::setup(bool is_ssl, const string &url, const string &user, const string &passwd) { - auto ip = FindField(url.data(), "://", "/"); + auto ip = findSubString(url.data(), "://", "/"); if (ip.empty()) { - ip = split(FindField(url.data(), "://", NULL), "?")[0]; + ip = split(findSubString(url.data(), "://", NULL), "?")[0]; } uint16_t port = is_ssl ? 322 : 554; splitUrl(ip, ip, port); + _url = std::move(url); - _user = std::move(user); - _passwd = std::move(passwd); + _user = strCoding::UrlDecode(std::move(user)); + _passwd = strCoding::UrlDecode(std::move(passwd)); _host = std::move(ip); _port = port; _is_ssl = is_ssl; @@ -289,7 +315,7 @@ void splitUrl(const std::string &url, std::string &host, uint16_t &port) { CHECK(!url.empty(), "empty url"); auto pos = url.rfind(':'); if (pos == string::npos || url.back() == ']') { - //没有冒号,未指定端口;或者是纯粹的ipv6地址 + // 没有冒号,未指定端口;或者是纯粹的ipv6地址 host = url; checkHost(host); return; @@ -299,7 +325,6 @@ void splitUrl(const std::string &url, std::string &host, uint16_t &port) { host = url.substr(0, pos); checkHost(host); } - #if 0 //测试代码 static onceToken token([](){ @@ -312,4 +337,4 @@ static onceToken token([](){ }); #endif -}//namespace mediakit +} // namespace mediakit diff --git a/src/Common/Parser.h b/src/Common/Parser.h index 6f312664..3741f4bc 100644 --- a/src/Common/Parser.h +++ b/src/Common/Parser.h @@ -17,15 +17,13 @@ namespace mediakit { -//从字符串中提取子字符串 -std::string FindField(const char *buf, const char *start, const char *end, size_t bufSize = 0); -//把url解析为主机地址和端口号,兼容ipv4/ipv6/dns -void splitUrl(const std::string &url, std::string &host, uint16_t& port); +// 从字符串中提取子字符串 +std::string findSubString(const char *buf, const char *start, const char *end, size_t buf_size = 0); +// 把url解析为主机地址和端口号,兼容ipv4/ipv6/dns +void splitUrl(const std::string &url, std::string &host, uint16_t &port); struct StrCaseCompare { - bool operator()(const std::string &__x, const std::string &__y) const { - return strcasecmp(__x.data(), __y.data()) < 0; - } + bool operator()(const std::string &__x, const std::string &__y) const { return strcasecmp(__x.data(), __y.data()) < 0; } }; class StrCaseMap : public std::multimap { @@ -42,84 +40,87 @@ public: return it->second; } - template - void emplace(const std::string &k, V &&v) { + template + void emplace(K &&k, V &&v) { auto it = find(k); if (it != end()) { return; } - Super::emplace(k, std::forward(v)); + Super::emplace(std::forward(k), std::forward(v)); } - template - void emplace_force(const std::string k, V &&v) { - Super::emplace(k, std::forward(v)); + template + void emplace_force(K &&k, V &&v) { + Super::emplace(std::forward(k), std::forward(v)); } }; -//rtsp/http/sip解析类 +// rtsp/http/sip解析类 class Parser { public: Parser() = default; ~Parser() = default; - //解析信令 - void Parse(const char *buf); + // 解析http/rtsp/sip请求,需要确保buf以\0结尾 + void parse(const char *buf, size_t size); - //获取命令字 - const std::string &Method() const; + // 获取命令字,如GET/POST + const std::string &method() const; - //获取中间url,不包含?后面的参数 - const std::string &Url() const; + // 请求时,获取中间url,不包含?后面的参数 + const std::string &url() const; + // 回复时,获取状态码,如200/404 + const std::string &status() const; - //获取中间url,包含?后面的参数 - std::string FullUrl() const; + // 获取中间url,包含?后面的参数 + std::string fullUrl() const; - //获取命令协议名 - const std::string &Tail() const; + // 请求时,获取协议名,如HTTP/1.1 + const std::string &protocol() const; + // 回复时,获取状态字符串,如 OK/Not Found + const std::string &statusStr() const; - //根据header key名,获取请求header value值 + // 根据header key名,获取请求header value值 const std::string &operator[](const char *name) const; - //获取http body或sdp - const std::string &Content() const; + // 获取http body或sdp + const std::string &content() const; - //清空,为了重用 - void Clear(); + // 清空,为了重用 + void clear(); - //获取?后面的参数 - const std::string &Params() const; + // 获取?后面的参数 + const std::string ¶ms() const; - //重新设置url + // 重新设置url void setUrl(std::string url); - //重新设置content + // 重新设置content void setContent(std::string content); - //获取header列表 + // 获取header列表 StrCaseMap &getHeader() const; - //获取url参数列表 + // 获取url参数列表 StrCaseMap &getUrlArgs() const; - //解析?后面的参数 + // 解析?后面的参数 static StrCaseMap parseArgs(const std::string &str, const char *pair_delim = "&", const char *key_delim = "="); - static std::string merge_url(const std::string &base_url, const std::string &path); + static std::string mergeUrl(const std::string &base_url, const std::string &path); private: - std::string _strMethod; - std::string _strUrl; - std::string _strTail; - std::string _strContent; - std::string _strNull; + std::string _method; + std::string _url; + std::string _protocol; + std::string _content; std::string _params; - mutable StrCaseMap _mapHeaders; - mutable StrCaseMap _mapUrlArgs; + mutable StrCaseMap _headers; + mutable StrCaseMap _url_args; }; -//解析rtsp url的工具类 -class RtspUrl{ +// 解析rtsp url的工具类 +class RtspUrl { public: bool _is_ssl; uint16_t _port; @@ -134,9 +135,9 @@ public: void parse(const std::string &url); private: - void setup(bool,const std::string &, const std::string &, const std::string &); + void setup(bool, const std::string &, const std::string &, const std::string &); }; -}//namespace mediakit +} // namespace mediakit -#endif //ZLMEDIAKIT_PARSER_H +#endif // ZLMEDIAKIT_PARSER_H diff --git a/src/Common/Stamp.cpp b/src/Common/Stamp.cpp index df7f7368..2656a176 100644 --- a/src/Common/Stamp.cpp +++ b/src/Common/Stamp.cpp @@ -10,7 +10,7 @@ #include "Stamp.h" -//时间戳最大允许跳变3秒,主要是防止网络抖动导致的跳变 +// 时间戳最大允许跳变3秒,主要是防止网络抖动导致的跳变 #define MAX_DELTA_STAMP (3 * 1000) #define STAMP_LOOP_DELTA (60 * 1000) #define MAX_CTS 500 @@ -25,51 +25,52 @@ int64_t DeltaStamp::relativeStamp(int64_t stamp) { return _relative_stamp; } -int64_t DeltaStamp::relativeStamp(){ +int64_t DeltaStamp::relativeStamp() { return _relative_stamp; } int64_t DeltaStamp::deltaStamp(int64_t stamp) { - if(!_last_stamp){ - //第一次计算时间戳增量,时间戳增量为0 - if(stamp){ + if (!_last_stamp) { + // 第一次计算时间戳增量,时间戳增量为0 + if (stamp) { _last_stamp = stamp; } return 0; } int64_t ret = stamp - _last_stamp; - if(ret >= 0){ - //时间戳增量为正,返回之 + if (ret >= 0) { + // 时间戳增量为正,返回之 _last_stamp = stamp; - //在直播情况下,时间戳增量不得大于MAX_DELTA_STAMP - return ret < MAX_DELTA_STAMP ? ret : 0; + // 在直播情况下,时间戳增量不得大于MAX_DELTA_STAMP,否则强制相对时间戳加1 + return ret < MAX_DELTA_STAMP ? ret : 1; } - //时间戳增量为负,说明时间戳回环了或回退了 + // 时间戳增量为负,说明时间戳回环了或回退了 _last_stamp = stamp; - //如果时间戳回退不多,那么返回负值 - return -ret < MAX_CTS ? ret : 0; + + // 如果时间戳回退不多,那么返回负值,否则返回加1 + return -ret < MAX_DELTA_STAMP ? ret : 1; } void Stamp::setPlayBack(bool playback) { _playback = playback; } -void Stamp::syncTo(Stamp &other){ +void Stamp::syncTo(Stamp &other) { _sync_master = &other; } -//限制dts回退 -void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) { +// 限制dts回退 +void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) { revise_l(dts, pts, dts_out, pts_out, modifyStamp); if (_playback) { - //回放允许时间戳回退 + // 回放允许时间戳回退 return; } if (dts_out < _last_dts_out) { -// WarnL << "dts回退:" << dts_out << " < " << _last_dts_out; + // WarnL << "dts回退:" << dts_out << " < " << _last_dts_out; dts_out = _last_dts_out; pts_out = _last_pts_out; return; @@ -78,35 +79,35 @@ void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, _last_pts_out = pts_out; } -//音视频时间戳同步 -void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) { +// 音视频时间戳同步 +void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) { revise_l2(dts, pts, dts_out, pts_out, modifyStamp); if (!_sync_master || modifyStamp || _playback) { - //自动生成时间戳或回放或同步完毕 + // 自动生成时间戳或回放或同步完毕 return; } if (_sync_master && _sync_master->_last_dts_in) { - //音视频dts当前时间差 + // 音视频dts当前时间差 int64_t dts_diff = _last_dts_in - _sync_master->_last_dts_in; if (ABS(dts_diff) < 5000) { - //如果绝对时间戳小于5秒,那么说明他们的起始时间戳是一致的,那么强制同步 + // 如果绝对时间戳小于5秒,那么说明他们的起始时间戳是一致的,那么强制同步 _relative_stamp = _sync_master->_relative_stamp + dts_diff; } - //下次不用再强制同步 + // 下次不用再强制同步 _sync_master = nullptr; } } -//求取相对时间戳 -void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) { +// 求取相对时间戳 +void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) { if (!pts) { - //没有播放时间戳,使其赋值为解码时间戳 + // 没有播放时间戳,使其赋值为解码时间戳 pts = dts; } if (_playback) { - //这是点播 + // 这是点播 dts_out = dts; pts_out = pts; _relative_stamp = dts_out; @@ -114,13 +115,13 @@ void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_o return; } - //pts和dts的差值 + // pts和dts的差值 int64_t pts_dts_diff = pts - dts; if (_last_dts_in != dts) { - //时间戳发生变更 + // 时间戳发生变更 if (modifyStamp) { - //内部自己生产时间戳 + // 内部自己生产时间戳 _relative_stamp = _ticker.elapsedTime(); } else { _relative_stamp += deltaStamp(dts); @@ -131,7 +132,7 @@ void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_o //////////////以下是播放时间戳的计算////////////////// if (ABS(pts_dts_diff) > MAX_CTS) { - //如果差值太大,则认为由于回环导致时间戳错乱了 + // 如果差值太大,则认为由于回环导致时间戳错乱了 pts_dts_diff = 0; } @@ -146,156 +147,157 @@ int64_t Stamp::getRelativeStamp() const { return _relative_stamp; } -bool DtsGenerator::getDts(uint64_t pts, uint64_t &dts){ +bool DtsGenerator::getDts(uint64_t pts, uint64_t &dts) { bool ret = false; if (pts == _last_pts) { - //pts未变,说明dts也不会变,返回上次dts + // pts未变,说明dts也不会变,返回上次dts if (_last_dts) { dts = _last_dts; ret = true; } } else { - //pts变了,尝试计算dts + // pts变了,尝试计算dts ret = getDts_l(pts, dts); if (ret) { - //获取到了dts,保存本次结果 + // 获取到了dts,保存本次结果 _last_dts = dts; } } if (!ret) { - //pts排序列队长度还不知道,也就是不知道有没有B帧, - //那么先强制dts == pts,这样可能导致有B帧的情况下,起始画面有几帧回退 + // pts排序列队长度还不知道,也就是不知道有没有B帧, + // 那么先强制dts == pts,这样可能导致有B帧的情况下,起始画面有几帧回退 dts = pts; } - //记录上次pts + // 记录上次pts _last_pts = pts; return ret; } -//该算法核心思想是对pts进行排序,排序好的pts就是dts。 -//排序有一定的滞后性,那么需要加上排序导致的时间戳偏移量 -bool DtsGenerator::getDts_l(uint64_t pts, uint64_t &dts){ - if(_sorter_max_size == 1){ - //没有B帧,dts就等于pts +// 该算法核心思想是对pts进行排序,排序好的pts就是dts。 +// 排序有一定的滞后性,那么需要加上排序导致的时间戳偏移量 +bool DtsGenerator::getDts_l(uint64_t pts, uint64_t &dts) { + if (_sorter_max_size == 1) { + // 没有B帧,dts就等于pts dts = pts; return true; } - if(!_sorter_max_size){ - //尚未计算出pts排序列队长度(也就是P帧间B帧个数) - if(pts > _last_max_pts){ - //pts时间戳增加了,那么说明这帧画面不是B帧(说明是P帧或关键帧) - if(_frames_since_last_max_pts && _count_sorter_max_size++ > 0){ - //已经出现多次非B帧的情况,那么我们就能知道P帧间B帧的个数 + if (!_sorter_max_size) { + // 尚未计算出pts排序列队长度(也就是P帧间B帧个数) + if (pts > _last_max_pts) { + // pts时间戳增加了,那么说明这帧画面不是B帧(说明是P帧或关键帧) + if (_frames_since_last_max_pts && _count_sorter_max_size++ > 0) { + // 已经出现多次非B帧的情况,那么我们就能知道P帧间B帧的个数 _sorter_max_size = _frames_since_last_max_pts; - //我们记录P帧间时间间隔(也就是多个B帧时间戳增量累计) + // 我们记录P帧间时间间隔(也就是多个B帧时间戳增量累计) _dts_pts_offset = (pts - _last_max_pts); - //除以2,防止dts大于pts + // 除以2,防止dts大于pts _dts_pts_offset /= 2; } - //遇到P帧或关键帧,连续B帧计数清零 + // 遇到P帧或关键帧,连续B帧计数清零 _frames_since_last_max_pts = 0; - //记录上次非B帧的pts时间戳(同时也是dts),用于统计连续B帧时间戳增量 + // 记录上次非B帧的pts时间戳(同时也是dts),用于统计连续B帧时间戳增量 _last_max_pts = pts; } - //如果pts时间戳小于上一个P帧,那么断定这个是B帧,我们记录B帧连续个数 + // 如果pts时间戳小于上一个P帧,那么断定这个是B帧,我们记录B帧连续个数 ++_frames_since_last_max_pts; } - //pts放入排序缓存列队,缓存列队最大等于连续B帧个数 + // pts放入排序缓存列队,缓存列队最大等于连续B帧个数 _pts_sorter.emplace(pts); - if(_sorter_max_size && _pts_sorter.size() > _sorter_max_size){ - //如果启用了pts排序(意味着存在B帧),并且pts排序缓存列队长度大于连续B帧个数, - //意味着后续的pts都会比最早的pts大,那么说明可以取出最早的pts了,这个pts将当做该帧的dts基准 + if (_sorter_max_size && _pts_sorter.size() > _sorter_max_size) { + // 如果启用了pts排序(意味着存在B帧),并且pts排序缓存列队长度大于连续B帧个数, + // 意味着后续的pts都会比最早的pts大,那么说明可以取出最早的pts了,这个pts将当做该帧的dts基准 auto it = _pts_sorter.begin(); - //由于该pts是前面偏移了个_sorter_max_size帧的pts(也就是那帧画面的dts), - //那么我们加上时间戳偏移量,基本等于该帧的dts + // 由于该pts是前面偏移了个_sorter_max_size帧的pts(也就是那帧画面的dts), + // 那么我们加上时间戳偏移量,基本等于该帧的dts dts = *it + _dts_pts_offset; - if(dts > pts){ - //dts不能大于pts(基本不可能到达这个逻辑) + if (dts > pts) { + // dts不能大于pts(基本不可能到达这个逻辑) dts = pts; } - //pts排序缓存出列 + // pts排序缓存出列 _pts_sorter.erase(it); return true; } - //排序缓存尚未满 + // 排序缓存尚未满 return false; } void NtpStamp::setNtpStamp(uint32_t rtp_stamp, uint64_t ntp_stamp_ms) { - update(rtp_stamp, ntp_stamp_ms); -} - -void NtpStamp::update(uint32_t rtp_stamp, uint64_t ntp_stamp_ms) { - if (ntp_stamp_ms == 0) { - //实测发现有些rtsp服务器发送的rtp时间戳和ntp时间戳一直为0 + if (!ntp_stamp_ms || !rtp_stamp) { + // 实测发现有些rtsp服务器发送的rtp时间戳和ntp时间戳一直为0 + WarnL << "Invalid sender report rtcp, ntp_stamp_ms = " << ntp_stamp_ms << ", rtp_stamp = " << rtp_stamp; return; } + update(rtp_stamp, ntp_stamp_ms * 1000); +} + +void NtpStamp::update(uint32_t rtp_stamp, uint64_t ntp_stamp_us) { _last_rtp_stamp = rtp_stamp; - _last_ntp_stamp_ms = ntp_stamp_ms; + _last_ntp_stamp_us = ntp_stamp_us; } uint64_t NtpStamp::getNtpStamp(uint32_t rtp_stamp, uint32_t sample_rate) { if (rtp_stamp == _last_rtp_stamp) { - return _last_ntp_stamp_ms; + return _last_ntp_stamp_us / 1000; } - return getNtpStamp_l(rtp_stamp, sample_rate); + return getNtpStampUS(rtp_stamp, sample_rate) / 1000; } -uint64_t NtpStamp::getNtpStamp_l(uint32_t rtp_stamp, uint32_t sample_rate) { - if (!_last_ntp_stamp_ms) { - //尚未收到sender report rtcp包,那么赋值为本地系统时间戳吧 - update(rtp_stamp, getCurrentMillisecond(true)); +uint64_t NtpStamp::getNtpStampUS(uint32_t rtp_stamp, uint32_t sample_rate) { + if (!_last_ntp_stamp_us) { + // 尚未收到sender report rtcp包,那么赋值为本地系统时间戳吧 + update(rtp_stamp, getCurrentMicrosecond(true)); } - //rtp时间戳正增长 + // rtp时间戳正增长 if (rtp_stamp >= _last_rtp_stamp) { - auto diff = static_cast((rtp_stamp - _last_rtp_stamp) / (sample_rate / 1000.0f)); - if (diff < MAX_DELTA_STAMP) { - //时间戳正常增长 - update(rtp_stamp, _last_ntp_stamp_ms + diff); - return _last_ntp_stamp_ms; + auto diff_us = static_cast((rtp_stamp - _last_rtp_stamp) / (sample_rate / 1000000.0f)); + if (diff_us < MAX_DELTA_STAMP * 1000) { + // 时间戳正常增长 + update(rtp_stamp, _last_ntp_stamp_us + diff_us); + return _last_ntp_stamp_us; } - //时间戳大幅跳跃 - uint64_t loop_delta = STAMP_LOOP_DELTA * sample_rate / 1000; - if (_last_rtp_stamp < loop_delta && rtp_stamp > UINT32_MAX - loop_delta) { - //应该是rtp时间戳溢出+乱序 - uint64_t max_rtp_ms = uint64_t(UINT32_MAX) * 1000 / sample_rate; - return _last_ntp_stamp_ms + diff - max_rtp_ms; + // 时间戳大幅跳跃 + uint64_t loop_delta_hz = STAMP_LOOP_DELTA * sample_rate / 1000; + if (_last_rtp_stamp < loop_delta_hz && rtp_stamp > UINT32_MAX - loop_delta_hz) { + // 应该是rtp时间戳溢出+乱序 + uint64_t max_rtp_us = uint64_t(UINT32_MAX) * 1000000 / sample_rate; + return _last_ntp_stamp_us + diff_us - max_rtp_us; } - //不明原因的时间戳大幅跳跃,直接返回上次值 + // 不明原因的时间戳大幅跳跃,直接返回上次值 WarnL << "rtp stamp abnormal increased:" << _last_rtp_stamp << " -> " << rtp_stamp; - update(rtp_stamp, _last_ntp_stamp_ms); - return _last_ntp_stamp_ms; + update(rtp_stamp, _last_ntp_stamp_us); + return _last_ntp_stamp_us; } - //rtp时间戳负增长 - auto diff = static_cast((_last_rtp_stamp - rtp_stamp) / (sample_rate / 1000.0f)); - if (diff < MAX_DELTA_STAMP) { - //正常范围的时间戳回退,说明收到rtp乱序了 - return _last_ntp_stamp_ms - diff; + // rtp时间戳负增长 + auto diff_us = static_cast((_last_rtp_stamp - rtp_stamp) / (sample_rate / 1000000.0f)); + if (diff_us < MAX_DELTA_STAMP * 1000) { + // 正常范围的时间戳回退,说明收到rtp乱序了 + return _last_ntp_stamp_us - diff_us; } - //时间戳大幅度回退 - uint64_t loop_delta = STAMP_LOOP_DELTA * sample_rate / 1000; - if (rtp_stamp < loop_delta && _last_rtp_stamp > UINT32_MAX - loop_delta) { - //确定是时间戳溢出 - uint64_t max_rtp_ms = uint64_t(UINT32_MAX) * 1000 / sample_rate; - update(rtp_stamp, _last_ntp_stamp_ms + (max_rtp_ms - diff)); - return _last_ntp_stamp_ms; + // 时间戳大幅度回退 + uint64_t loop_delta_hz = STAMP_LOOP_DELTA * sample_rate / 1000; + if (rtp_stamp < loop_delta_hz && _last_rtp_stamp > UINT32_MAX - loop_delta_hz) { + // 确定是时间戳溢出 + uint64_t max_rtp_us = uint64_t(UINT32_MAX) * 1000000 / sample_rate; + update(rtp_stamp, _last_ntp_stamp_us + (max_rtp_us - diff_us)); + return _last_ntp_stamp_us; } - //不明原因的时间戳回退,直接返回上次值 + // 不明原因的时间戳回退,直接返回上次值 WarnL << "rtp stamp abnormal reduced:" << _last_rtp_stamp << " -> " << rtp_stamp; - update(rtp_stamp, _last_ntp_stamp_ms); - return _last_ntp_stamp_ms; + update(rtp_stamp, _last_ntp_stamp_us); + return _last_ntp_stamp_us; } -}//namespace mediakit +} // namespace mediakit diff --git a/src/Common/Stamp.h b/src/Common/Stamp.h index cc2dcae0..03cb7bb7 100644 --- a/src/Common/Stamp.h +++ b/src/Common/Stamp.h @@ -125,12 +125,12 @@ public: uint64_t getNtpStamp(uint32_t rtp_stamp, uint32_t sample_rate); private: - void update(uint32_t rtp_stamp, uint64_t ntp_stamp_ms); - uint64_t getNtpStamp_l(uint32_t rtp_stamp, uint32_t sample_rate); + void update(uint32_t rtp_stamp, uint64_t ntp_stamp_us); + uint64_t getNtpStampUS(uint32_t rtp_stamp, uint32_t sample_rate); private: uint32_t _last_rtp_stamp = 0; - uint64_t _last_ntp_stamp_ms = 0; + uint64_t _last_ntp_stamp_us = 0; }; }//namespace mediakit diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 1654c61c..6e75e27d 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -9,6 +9,7 @@ */ #include "Common/config.h" +#include "MediaSource.h" #include "Util/NoticeCenter.h" #include "Util/logger.h" #include "Util/onceToken.h" @@ -30,7 +31,7 @@ bool loadIniConfig(const char *ini_path) { } try { mINI::Instance().parseFile(ini); - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig); + NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig); return true; } catch (std::exception &) { InfoL << "dump ini file to:" << ini; @@ -98,9 +99,11 @@ namespace Protocol { const string kModifyStamp = PROTOCOL_FIELD "modify_stamp"; const string kEnableAudio = PROTOCOL_FIELD "enable_audio"; const string kAddMuteAudio = PROTOCOL_FIELD "add_mute_audio"; +const string kAutoClose = PROTOCOL_FIELD "auto_close"; const string kContinuePushMS = PROTOCOL_FIELD "continue_push_ms"; const string kEnableHls = PROTOCOL_FIELD "enable_hls"; +const string kEnableHlsFmp4 = PROTOCOL_FIELD "enable_hls_fmp4"; const string kEnableMP4 = PROTOCOL_FIELD "enable_mp4"; const string kEnableRtsp = PROTOCOL_FIELD "enable_rtsp"; const string kEnableRtmp = PROTOCOL_FIELD "enable_rtmp"; @@ -120,12 +123,14 @@ const string kTSDemand = PROTOCOL_FIELD "ts_demand"; const string kFMP4Demand = PROTOCOL_FIELD "fmp4_demand"; static onceToken token([]() { - mINI::Instance()[kModifyStamp] = 0; + mINI::Instance()[kModifyStamp] = (int)ProtocolOption::kModifyStampRelative; mINI::Instance()[kEnableAudio] = 1; mINI::Instance()[kAddMuteAudio] = 1; mINI::Instance()[kContinuePushMS] = 15000; + mINI::Instance()[kAutoClose] = 0; mINI::Instance()[kEnableHls] = 1; + mINI::Instance()[kEnableHlsFmp4] = 0; mINI::Instance()[kEnableMP4] = 0; mINI::Instance()[kEnableRtsp] = 1; mINI::Instance()[kEnableRtmp] = 1; @@ -160,6 +165,7 @@ const string kDirMenu = HTTP_FIELD "dirMenu"; const string kForbidCacheSuffix = HTTP_FIELD "forbidCacheSuffix"; const string kForwardedIpHeader = HTTP_FIELD "forwarded_ip_header"; const string kAllowCrossDomains = HTTP_FIELD "allow_cross_domains"; +const string kAllowIPRange = HTTP_FIELD "allow_ip_range"; static onceToken token([]() { mINI::Instance()[kSendBufSize] = 64 * 1024; @@ -188,6 +194,7 @@ static onceToken token([]() { mINI::Instance()[kForbidCacheSuffix] = ""; mINI::Instance()[kForwardedIpHeader] = ""; mINI::Instance()[kAllowCrossDomains] = 1; + mINI::Instance()[kAllowIPRange] = "::1,127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255"; }); } // namespace Http @@ -208,6 +215,7 @@ const string kHandshakeSecond = RTSP_FIELD "handshakeSecond"; const string kKeepAliveSecond = RTSP_FIELD "keepAliveSecond"; const string kDirectProxy = RTSP_FIELD "directProxy"; const string kLowLatency = RTSP_FIELD"lowLatency"; +const string kRtpTransportType = RTSP_FIELD"rtpTransportType"; static onceToken token([]() { // 默认Md5方式认证 @@ -216,18 +224,17 @@ static onceToken token([]() { mINI::Instance()[kKeepAliveSecond] = 15; mINI::Instance()[kDirectProxy] = 1; mINI::Instance()[kLowLatency] = 0; + mINI::Instance()[kRtpTransportType] = -1; }); } // namespace Rtsp ////////////RTMP服务器配置/////////// namespace Rtmp { #define RTMP_FIELD "rtmp." -const string kModifyStamp = RTMP_FIELD "modifyStamp"; const string kHandshakeSecond = RTMP_FIELD "handshakeSecond"; const string kKeepAliveSecond = RTMP_FIELD "keepAliveSecond"; static onceToken token([]() { - mINI::Instance()[kModifyStamp] = false; mINI::Instance()[kHandshakeSecond] = 15; mINI::Instance()[kKeepAliveSecond] = 15; }); @@ -241,15 +248,15 @@ const string kVideoMtuSize = RTP_FIELD "videoMtuSize"; const string kAudioMtuSize = RTP_FIELD "audioMtuSize"; // rtp包最大长度限制,单位是KB const string kRtpMaxSize = RTP_FIELD "rtpMaxSize"; - const string kLowLatency = RTP_FIELD "lowLatency"; +const string kH264StapA = RTP_FIELD "h264_stap_a"; static onceToken token([]() { mINI::Instance()[kVideoMtuSize] = 1400; mINI::Instance()[kAudioMtuSize] = 600; mINI::Instance()[kRtpMaxSize] = 10; mINI::Instance()[kLowLatency] = 0; - + mINI::Instance()[kH264StapA] = 1; }); } // namespace Rtp diff --git a/src/Common/config.h b/src/Common/config.h index ea874d64..341f1d6c 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -99,7 +99,7 @@ extern const std::string kBroadcastStreamNoneReader; // rtp推流被动停止时触发 extern const std::string kBroadcastSendRtpStopped; -#define BroadcastSendRtpStopped MultiMediaSourceMuxer &sender, const std::string &ssrc, const SockException &ex +#define BroadcastSendRtpStoppedArgs MultiMediaSourceMuxer &sender, const std::string &ssrc, const SockException &ex // 更新配置文件事件广播,执行loadIniConfig函数加载配置文件成功后会触发该广播 extern const std::string kBroadcastReloadConfig; @@ -107,7 +107,7 @@ extern const std::string kBroadcastReloadConfig; // rtp server 超时 extern const std::string KBroadcastRtpServerTimeout; -#define BroadcastRtpServerTimeout uint16_t &local_port, const string &stream_id,int &tcp_mode, bool &re_use_port, uint32_t &ssrc +#define BroadcastRtpServerTimeoutArgs uint16_t &local_port, const string &stream_id,int &tcp_mode, bool &re_use_port, uint32_t &ssrc #define ReloadConfigTag ((void *)(0xFF)) #define RELOAD_KEY(arg, key) \ @@ -190,11 +190,17 @@ extern const std::string kModifyStamp; extern const std::string kEnableAudio; //添加静音音频,在关闭音频时,此开关无效 extern const std::string kAddMuteAudio; +// 无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close) +// 此配置置1时,此流如果无人观看,将不触发on_none_reader hook回调, +// 而是将直接关闭流 +extern const std::string kAutoClose; //断连续推延时,单位毫秒,默认采用配置文件 extern const std::string kContinuePushMS; -//是否开启转换为hls +//是否开启转换为hls(mpegts) extern const std::string kEnableHls; +//是否开启转换为hls(fmp4) +extern const std::string kEnableHlsFmp4; //是否开启MP4录制 extern const std::string kEnableMP4; //是否开启转换为rtsp/webrtc @@ -248,6 +254,8 @@ extern const std::string kForbidCacheSuffix; extern const std::string kForwardedIpHeader; // 是否允许所有跨域请求 extern const std::string kAllowCrossDomains; +// 允许访问http api和http文件索引的ip地址范围白名单,置空情况下不做限制 +extern const std::string kAllowIPRange; } // namespace Http ////////////SHELL配置/////////// @@ -273,6 +281,11 @@ extern const std::string kDirectProxy; // rtsp 转发是否使用低延迟模式,当开启时,不会缓存rtp包,来提高并发,可以降低一帧的延迟 extern const std::string kLowLatency; + +//强制协商rtp传输方式 (0:TCP,1:UDP,2:MULTICAST,-1:不限制) +//当客户端发起RTSP SETUP的时候如果传输类型和此配置不一致则返回461 Unsupport Transport +//迫使客户端重新SETUP并切换到对应协议。目前支持FFMPEG和VLC +extern const std::string kRtpTransportType; } // namespace Rtsp ////////////RTMP服务器配置/////////// @@ -293,6 +306,8 @@ extern const std::string kAudioMtuSize; extern const std::string kRtpMaxSize; // rtp 打包时,低延迟开关,默认关闭(为0),h264存在一帧多个slice(NAL)的情况,在这种情况下,如果开启可能会导致画面花屏 extern const std::string kLowLatency; +//H264 rtp打包模式是否采用stap-a模式(为了在老版本浏览器上兼容webrtc)还是采用Single NAL unit packet per H.264 模式 +extern const std::string kH264StapA; } // namespace Rtp ////////////组播配置/////////// diff --git a/src/Common/macros.h b/src/Common/macros.h index dd9ad64d..8c86fc10 100644 --- a/src/Common/macros.h +++ b/src/Common/macros.h @@ -32,14 +32,6 @@ #define __LITTLE_ENDIAN LITTLE_ENDIAN #endif -#ifndef PACKED -#if !defined(_WIN32) -#define PACKED __attribute__((packed)) -#else -#define PACKED -#endif //! defined(_WIN32) -#endif - #ifndef CHECK #define CHECK(exp, ...) ::mediakit::Assert_ThrowCpp(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__) #endif // CHECK @@ -67,6 +59,7 @@ #define HLS_SCHEMA "hls" #define TS_SCHEMA "ts" #define FMP4_SCHEMA "fmp4" +#define HLS_FMP4_SCHEMA "hls.fmp4" #define SRT_SCHEMA "srt" #define DEFAULT_VHOST "__defaultVhost__" diff --git a/src/Http/strCoding.cpp b/src/Common/strCoding.cpp similarity index 100% rename from src/Http/strCoding.cpp rename to src/Common/strCoding.cpp diff --git a/src/Http/strCoding.h b/src/Common/strCoding.h similarity index 100% rename from src/Http/strCoding.h rename to src/Common/strCoding.h diff --git a/src/Extension/AAC.cpp b/src/Extension/AAC.cpp index ea57d643..1047a8b3 100644 --- a/src/Extension/AAC.cpp +++ b/src/Extension/AAC.cpp @@ -246,7 +246,7 @@ AACTrack::AACTrack(const string &aac_cfg) { onReady(); } -const string &AACTrack::getAacCfg() const { +const string &AACTrack::getConfig() const { return _cfg; } @@ -342,7 +342,7 @@ Sdp::Ptr AACTrack::getSdp() { WarnL << getCodecName() << " Track未准备好"; return nullptr; } - return std::make_shared(getAacCfg(), getAudioSampleRate(), getAudioChannel(), getBitRate() / 1024); + return std::make_shared(getConfig(), getAudioSampleRate(), getAudioChannel(), getBitRate() / 1024); } }//namespace mediakit \ No newline at end of file diff --git a/src/Extension/AAC.h b/src/Extension/AAC.h index ad7479f6..b95fc6f2 100644 --- a/src/Extension/AAC.h +++ b/src/Extension/AAC.h @@ -44,7 +44,7 @@ public: /** * 获取aac 配置信息 */ - const std::string &getAacCfg() const; + const std::string &getConfig() const; bool ready() override; CodecId getCodecId() const override; diff --git a/src/Extension/AACRtmp.cpp b/src/Extension/AACRtmp.cpp index 1454a727..f1fdbab9 100644 --- a/src/Extension/AACRtmp.cpp +++ b/src/Extension/AACRtmp.cpp @@ -16,12 +16,9 @@ using namespace toolkit; namespace mediakit { -static string getAacCfg(const RtmpPacket &thiz) { +static string getConfig(const RtmpPacket &thiz) { string ret; - if (thiz.getMediaType() != FLV_CODEC_AAC) { - return ret; - } - if (!thiz.isCfgFrame()) { + if ((RtmpAudioCodec)thiz.getRtmpCodecId() != RtmpAudioCodec::aac) { return ret; } if (thiz.buffer.size() < 4) { @@ -33,8 +30,8 @@ static string getAacCfg(const RtmpPacket &thiz) { } void AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { - if (pkt->isCfgFrame()) { - _aac_cfg = getAacCfg(*pkt); + if (pkt->isConfigFrame()) { + _aac_cfg = getConfig(*pkt); if (!_aac_cfg.empty()) { onGetAAC(nullptr, 0, 0); } @@ -82,7 +79,7 @@ AACRtmpEncoder::AACRtmpEncoder(const Track::Ptr &track) { void AACRtmpEncoder::makeConfigPacket() { if (_track && _track->ready()) { //从track中和获取aac配置信息 - _aac_cfg = _track->getAacCfg(); + _aac_cfg = _track->getConfig(); } if (!_aac_cfg.empty()) { @@ -93,51 +90,45 @@ void AACRtmpEncoder::makeConfigPacket() { bool AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) { if (_aac_cfg.empty()) { if (frame->prefixSize()) { - //包含adts头,从adts头获取aac配置信息 - _aac_cfg = makeAacConfig((uint8_t *) (frame->data()), frame->prefixSize()); + // 包含adts头,从adts头获取aac配置信息 + _aac_cfg = makeAacConfig((uint8_t *)(frame->data()), frame->prefixSize()); } makeConfigPacket(); } - if(_aac_cfg.empty()){ + if (_aac_cfg.empty()) { return false; } - auto rtmpPkt = RtmpPacket::create(); - //header - uint8_t is_config = false; - rtmpPkt->buffer.push_back(_audio_flv_flags); - rtmpPkt->buffer.push_back(!is_config); - - //aac data - rtmpPkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); - - rtmpPkt->body_size = rtmpPkt->buffer.size(); - rtmpPkt->chunk_id = CHUNK_AUDIO; - rtmpPkt->stream_index = STREAM_MEDIA; - rtmpPkt->time_stamp = frame->dts(); - rtmpPkt->type_id = MSG_AUDIO; - RtmpCodec::inputRtmp(rtmpPkt); + auto pkt = RtmpPacket::create(); + // header + pkt->buffer.push_back(_audio_flv_flags); + pkt->buffer.push_back((uint8_t)RtmpAACPacketType::aac_raw); + // aac data + pkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_AUDIO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = frame->dts(); + pkt->type_id = MSG_AUDIO; + RtmpCodec::inputRtmp(pkt); return true; } void AACRtmpEncoder::makeAudioConfigPkt() { _audio_flv_flags = getAudioRtmpFlags(std::make_shared(_aac_cfg)); - auto rtmpPkt = RtmpPacket::create(); - - //header - uint8_t is_config = true; - rtmpPkt->buffer.push_back(_audio_flv_flags); - rtmpPkt->buffer.push_back(!is_config); - //aac config - rtmpPkt->buffer.append(_aac_cfg); - - rtmpPkt->body_size = rtmpPkt->buffer.size(); - rtmpPkt->chunk_id = CHUNK_AUDIO; - rtmpPkt->stream_index = STREAM_MEDIA; - rtmpPkt->time_stamp = 0; - rtmpPkt->type_id = MSG_AUDIO; - RtmpCodec::inputRtmp(rtmpPkt); + auto pkt = RtmpPacket::create(); + // header + pkt->buffer.push_back(_audio_flv_flags); + pkt->buffer.push_back((uint8_t)RtmpAACPacketType::aac_config_header); + // aac config + pkt->buffer.append(_aac_cfg); + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_AUDIO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = 0; + pkt->type_id = MSG_AUDIO; + RtmpCodec::inputRtmp(pkt); } }//namespace mediakit \ No newline at end of file diff --git a/src/Extension/AACRtp.cpp b/src/Extension/AACRtp.cpp index 97c84ff6..b5c5ab39 100644 --- a/src/Extension/AACRtp.cpp +++ b/src/Extension/AACRtp.cpp @@ -64,7 +64,7 @@ AACRtpDecoder::AACRtpDecoder(const Track::Ptr &track) { if (!aacTrack || !aacTrack->ready()) { WarnL << "该aac track无效!"; } else { - _aac_cfg = aacTrack->getAacCfg(); + _aac_cfg = aacTrack->getConfig(); } obtainFrame(); } diff --git a/src/Extension/CommonRtp.cpp b/src/Extension/CommonRtp.cpp index 9ba9b682..68767a6f 100644 --- a/src/Extension/CommonRtp.cpp +++ b/src/Extension/CommonRtp.cpp @@ -34,10 +34,10 @@ bool CommonRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool){ return false; } auto payload = rtp->getPayload(); - auto stamp = rtp->getStampMS(); + auto stamp = rtp->getStamp(); auto seq = rtp->getSeq(); - if (_frame->_dts != stamp || _frame->_buffer.size() > _max_frame_size) { + if (_last_stamp != stamp || _frame->_buffer.size() > _max_frame_size) { //时间戳发生变化或者缓存超过MAX_FRAME_SIZE,则清空上帧数据 if (!_frame->_buffer.empty()) { //有有效帧,则输出 @@ -46,7 +46,8 @@ bool CommonRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool){ //新的一帧数据 obtainFrame(); - _frame->_dts = stamp; + _frame->_dts = rtp->getStampMS(); + _last_stamp = stamp; _drop_flag = false; } else if (_last_seq != 0 && (uint16_t)(_last_seq + 1) != seq) { //时间戳未发生变化,但是seq却不连续,说明中间rtp丢包了,那么整帧应该废弃 diff --git a/src/Extension/CommonRtp.h b/src/Extension/CommonRtp.h index 7df29af8..6553207e 100644 --- a/src/Extension/CommonRtp.h +++ b/src/Extension/CommonRtp.h @@ -50,6 +50,7 @@ private: private: bool _drop_flag = false; uint16_t _last_seq = 0; + uint64_t _last_stamp = 0; size_t _max_frame_size; CodecId _codec; FrameImp::Ptr _frame; diff --git a/src/Extension/Factory.cpp b/src/Extension/Factory.cpp index c85f920c..63a2bbd5 100644 --- a/src/Extension/Factory.cpp +++ b/src/Extension/Factory.cpp @@ -45,9 +45,9 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) { case CodecOpus : return std::make_shared(); case CodecAAC : { - string aac_cfg_str = FindField(track->_fmtp.data(), "config=", ";"); + string aac_cfg_str = findSubString(track->_fmtp.data(), "config=", ";"); if (aac_cfg_str.empty()) { - aac_cfg_str = FindField(track->_fmtp.data(), "config=", nullptr); + aac_cfg_str = findSubString(track->_fmtp.data(), "config=", nullptr); } if (aac_cfg_str.empty()) { //如果sdp中获取不到aac config信息,那么在rtp也无法获取,那么忽略该Track @@ -67,8 +67,8 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) { //a=fmtp:96 packetization-mode=1;profile-level-id=42C01F;sprop-parameter-sets=Z0LAH9oBQBboQAAAAwBAAAAPI8YMqA==,aM48gA== auto map = Parser::parseArgs(track->_fmtp, ";", "="); auto sps_pps = map["sprop-parameter-sets"]; - string base64_SPS = FindField(sps_pps.data(), NULL, ","); - string base64_PPS = FindField(sps_pps.data(), ",", NULL); + string base64_SPS = findSubString(sps_pps.data(), NULL, ","); + string base64_PPS = findSubString(sps_pps.data(), ",", NULL); auto sps = decodeBase64(base64_SPS); auto pps = decodeBase64(base64_PPS); if (sps.empty() || pps.empty()) { @@ -201,17 +201,20 @@ static CodecId getVideoCodecIdByAmf(const AMFValue &val){ } if (val.type() != AMF_NULL) { - auto type_id = val.as_integer(); + auto type_id = (RtmpVideoCodec)val.as_integer(); switch (type_id) { - case FLV_CODEC_H264 : return CodecH264; - case FLV_CODEC_H265 : return CodecH265; - default : WarnL << "暂不支持该视频Amf:" << type_id; return CodecInvalid; + case RtmpVideoCodec::h264: return CodecH264; + case RtmpVideoCodec::fourcc_hevc: + case RtmpVideoCodec::h265: return CodecH265; + case RtmpVideoCodec::fourcc_av1: return CodecAV1; + case RtmpVideoCodec::fourcc_vp9: return CodecVP9; + default: WarnL << "暂不支持该视频Amf:" << (int)type_id; return CodecInvalid; } } return CodecInvalid; } -Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0) { +Track::Ptr Factory::getTrackByCodecId(CodecId codecId, int sample_rate, int channels, int sample_bit) { switch (codecId){ case CodecH264 : return std::make_shared(); case CodecH265 : return std::make_shared(); @@ -243,13 +246,13 @@ static CodecId getAudioCodecIdByAmf(const AMFValue &val) { } if (val.type() != AMF_NULL) { - auto type_id = val.as_integer(); + auto type_id = (RtmpAudioCodec)val.as_integer(); switch (type_id) { - case FLV_CODEC_AAC : return CodecAAC; - case FLV_CODEC_G711A : return CodecG711A; - case FLV_CODEC_G711U : return CodecG711U; - case FLV_CODEC_OPUS : return CodecOpus; - default : WarnL << "暂不支持该音频Amf:" << type_id; return CodecInvalid; + case RtmpAudioCodec::aac : return CodecAAC; + case RtmpAudioCodec::g711a : return CodecG711A; + case RtmpAudioCodec::g711u : return CodecG711U; + case RtmpAudioCodec::opus : return CodecOpus; + default : WarnL << "暂不支持该音频Amf:" << (int)type_id; return CodecInvalid; } } @@ -291,13 +294,13 @@ RtmpCodec::Ptr Factory::getRtmpCodecByTrack(const Track::Ptr &track, bool is_enc } AMFValue Factory::getAmfByCodecId(CodecId codecId) { - switch (codecId){ - case CodecAAC: return AMFValue(FLV_CODEC_AAC); - case CodecH264: return AMFValue(FLV_CODEC_H264); - case CodecH265: return AMFValue(FLV_CODEC_H265); - case CodecG711A: return AMFValue(FLV_CODEC_G711A); - case CodecG711U: return AMFValue(FLV_CODEC_G711U); - case CodecOpus: return AMFValue(FLV_CODEC_OPUS); + switch (codecId) { + case CodecAAC: return AMFValue((int)RtmpAudioCodec::aac); + case CodecH264: return AMFValue((int)RtmpVideoCodec::h264); + case CodecH265: return AMFValue((int)RtmpVideoCodec::h265); + case CodecG711A: return AMFValue((int)RtmpAudioCodec::g711a); + case CodecG711U: return AMFValue((int)RtmpAudioCodec::g711u); + case CodecOpus: return AMFValue((int)RtmpAudioCodec::opus); default: return AMFValue(AMF_NULL); } } diff --git a/src/Extension/Factory.h b/src/Extension/Factory.h index 6b0c3c0a..d0045865 100644 --- a/src/Extension/Factory.h +++ b/src/Extension/Factory.h @@ -21,6 +21,16 @@ namespace mediakit{ class Factory { public: + + /** + * 根据codec_id 获取track + * @param codecId 编码id + * @param sample_rate 采样率,视频固定为90000 + * @param channels 音频通道数 + * @param sample_bit 音频采样位数 + */ + static Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0); + ////////////////////////////////rtsp相关////////////////////////////////// /** * 根据sdp生成Track对象 diff --git a/src/Extension/Frame.cpp b/src/Extension/Frame.cpp index 7cc3cb41..152eea1f 100644 --- a/src/Extension/Frame.cpp +++ b/src/Extension/Frame.cpp @@ -13,6 +13,7 @@ #include "H265.h" #include "Common/Parser.h" #include "Common/Stamp.h" +#include "Common/MediaSource.h" using namespace std; using namespace toolkit; @@ -31,11 +32,11 @@ Frame::Ptr Frame::getCacheAbleFrame(const Frame::Ptr &frame){ return std::make_shared(frame); } -FrameStamp::FrameStamp(Frame::Ptr frame, Stamp &stamp, bool modify_stamp) +FrameStamp::FrameStamp(Frame::Ptr frame, Stamp &stamp, int modify_stamp) { _frame = std::move(frame); - //覆盖时间戳 - stamp.revise(_frame->dts(), _frame->pts(), _dts, _pts, modify_stamp); + // kModifyStampSystem时采用系统时间戳,kModifyStampRelative采用相对时间戳 + stamp.revise(_frame->dts(), _frame->pts(), _dts, _pts, modify_stamp == ProtocolOption::kModifyStampSystem); } TrackType getTrackType(CodecId codecId) { diff --git a/src/Extension/Frame.h b/src/Extension/Frame.h index 4328e544..3609ff41 100644 --- a/src/Extension/Frame.h +++ b/src/Extension/Frame.h @@ -40,7 +40,7 @@ typedef enum { XX(CodecVP8, TrackVideo, 7, "VP8", PSI_STREAM_VP8) \ XX(CodecVP9, TrackVideo, 8, "VP9", PSI_STREAM_VP9) \ XX(CodecAV1, TrackVideo, 9, "AV1", PSI_STREAM_AV1) \ - XX(CodecJPEG, TrackVideo, 10, "JPEG", PSI_STREAM_JPEG_2000) + XX(CodecJPEG, TrackVideo, 10, "JPEG", PSI_STREAM_RESERVED) typedef enum { CodecInvalid = -1, @@ -492,7 +492,7 @@ private: class FrameStamp : public Frame { public: using Ptr = std::shared_ptr; - FrameStamp(Frame::Ptr frame, Stamp &stamp, bool modify_stamp); + FrameStamp(Frame::Ptr frame, Stamp &stamp, int modify_stamp); ~FrameStamp() override {} uint64_t dts() const override { return (uint64_t)_dts; } diff --git a/src/Extension/H264.cpp b/src/Extension/H264.cpp index 2395159b..eeccd600 100644 --- a/src/Extension/H264.cpp +++ b/src/Extension/H264.cpp @@ -12,9 +12,10 @@ #include "SPSParser.h" #include "Util/logger.h" #include "Util/base64.h" +#include "Common/config.h" -using namespace toolkit; using namespace std; +using namespace toolkit; namespace mediakit { @@ -248,7 +249,14 @@ public: _printer << "b=AS:" << bitrate << "\r\n"; } _printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << 90000 << "\r\n"; - _printer << "a=fmtp:" << payload_type << " packetization-mode=1; profile-level-id="; + + /** + Single NAI Unit Mode = 0. // Single NAI mode (Only nals from 1-23 are allowed) + Non Interleaved Mode = 1,// Non-interleaved Mode: 1-23,24 (STAP-A),28 (FU-A) are allowed + Interleaved Mode = 2, // 25 (STAP-B),26 (MTAP16),27 (MTAP24),28 (EU-A),and 29 (EU-B) are allowed. + **/ + GET_CONFIG(bool, h264_stap_a, Rtp::kH264StapA); + _printer << "a=fmtp:" << payload_type << " packetization-mode=" << h264_stap_a << "; profile-level-id="; uint32_t profile_level_id = 0; if (strSPS.length() >= 4) { // sanity check diff --git a/src/Extension/H264Rtmp.cpp b/src/Extension/H264Rtmp.cpp index f85ae425..9e62896d 100644 --- a/src/Extension/H264Rtmp.cpp +++ b/src/Extension/H264Rtmp.cpp @@ -30,10 +30,7 @@ H264Frame::Ptr H264RtmpDecoder::obtainFrame() { * 返回不带0x00 00 00 01头的sps pps */ static bool getH264Config(const RtmpPacket &thiz, string &sps, string &pps) { - if (thiz.getMediaType() != FLV_CODEC_H264) { - return false; - } - if (!thiz.isCfgFrame()) { + if ((RtmpVideoCodec)thiz.getRtmpCodecId() != RtmpVideoCodec::h264) { return false; } if (thiz.buffer.size() < 13) { @@ -59,7 +56,7 @@ static bool getH264Config(const RtmpPacket &thiz, string &sps, string &pps) { } void H264RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { - if (pkt->isCfgFrame()) { + if (pkt->isConfigFrame()) { //缓存sps pps,后续插入到I帧之前 if (!getH264Config(*pkt, _sps, _pps)) { WarnL << "get h264 sps/pps failed, rtmp packet is: " << hexdump(pkt->data(), pkt->size()); @@ -159,26 +156,21 @@ bool H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) { } return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) { - //flags - _rtmp_packet->buffer[0] = FLV_CODEC_H264 | ((have_key_frame ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4); - //not config - _rtmp_packet->buffer[1] = true; - int32_t cts = pts - dts; - if (cts < 0) { - cts = 0; - } - //cts - set_be24(&_rtmp_packet->buffer[2], cts); - - _rtmp_packet->time_stamp = dts; - _rtmp_packet->body_size = _rtmp_packet->buffer.size(); - _rtmp_packet->chunk_id = CHUNK_VIDEO; - _rtmp_packet->stream_index = STREAM_MEDIA; - _rtmp_packet->type_id = MSG_VIDEO; - //输出rtmp packet - RtmpCodec::inputRtmp(_rtmp_packet); - _rtmp_packet = nullptr; - }, &_rtmp_packet->buffer); + // flags + _rtmp_packet->buffer[0] = (uint8_t)RtmpVideoCodec::h264 | ((uint8_t)(have_key_frame ? RtmpFrameType::key_frame : RtmpFrameType::inter_frame) << 4); + _rtmp_packet->buffer[1] = (uint8_t)RtmpH264PacketType::h264_nalu; + int32_t cts = pts - dts; + // cts + set_be24(&_rtmp_packet->buffer[2], cts); + _rtmp_packet->time_stamp = dts; + _rtmp_packet->body_size = _rtmp_packet->buffer.size(); + _rtmp_packet->chunk_id = CHUNK_VIDEO; + _rtmp_packet->stream_index = STREAM_MEDIA; + _rtmp_packet->type_id = MSG_VIDEO; + // 输出rtmp packet + RtmpCodec::inputRtmp(_rtmp_packet); + _rtmp_packet = nullptr; + }, &_rtmp_packet->buffer); } void H264RtmpEncoder::makeVideoConfigPkt() { @@ -186,42 +178,39 @@ void H264RtmpEncoder::makeVideoConfigPkt() { WarnL << "sps长度不足4字节"; return; } - int8_t flags = FLV_CODEC_H264; - flags |= (FLV_KEY_FRAME << 4); - bool is_config = true; - - auto rtmpPkt = RtmpPacket::create(); - //header - rtmpPkt->buffer.push_back(flags); - rtmpPkt->buffer.push_back(!is_config); - //cts - rtmpPkt->buffer.append("\x0\x0\x0", 3); - - //AVCDecoderConfigurationRecord start - rtmpPkt->buffer.push_back(1); // version - rtmpPkt->buffer.push_back(_sps[1]); // profile - rtmpPkt->buffer.push_back(_sps[2]); // compat - rtmpPkt->buffer.push_back(_sps[3]); // level - rtmpPkt->buffer.push_back((char)0xff); // 6 bits reserved + 2 bits nal size length - 1 (11) - rtmpPkt->buffer.push_back((char)0xe1); // 3 bits reserved + 5 bits number of sps (00001) - //sps + auto flags = (uint8_t)RtmpVideoCodec::h264; + flags |= ((uint8_t)RtmpFrameType::key_frame << 4); + auto pkt = RtmpPacket::create(); + // header + pkt->buffer.push_back(flags); + pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header); + // cts + pkt->buffer.append("\x0\x0\x0", 3); + // AVCDecoderConfigurationRecord start + pkt->buffer.push_back(1); // version + pkt->buffer.push_back(_sps[1]); // profile + pkt->buffer.push_back(_sps[2]); // compat + pkt->buffer.push_back(_sps[3]); // level + pkt->buffer.push_back((char)0xff); // 6 bits reserved + 2 bits nal size length - 1 (11) + pkt->buffer.push_back((char)0xe1); // 3 bits reserved + 5 bits number of sps (00001) + // sps uint16_t size = (uint16_t)_sps.size(); size = htons(size); - rtmpPkt->buffer.append((char *) &size, 2); - rtmpPkt->buffer.append(_sps); - //pps - rtmpPkt->buffer.push_back(1); // version + pkt->buffer.append((char *)&size, 2); + pkt->buffer.append(_sps); + // pps + pkt->buffer.push_back(1); // version size = (uint16_t)_pps.size(); size = htons(size); - rtmpPkt->buffer.append((char *) &size, 2); - rtmpPkt->buffer.append(_pps); + pkt->buffer.append((char *)&size, 2); + pkt->buffer.append(_pps); - rtmpPkt->body_size = rtmpPkt->buffer.size(); - rtmpPkt->chunk_id = CHUNK_VIDEO; - rtmpPkt->stream_index = STREAM_MEDIA; - rtmpPkt->time_stamp = 0; - rtmpPkt->type_id = MSG_VIDEO; - RtmpCodec::inputRtmp(rtmpPkt); + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_VIDEO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = 0; + pkt->type_id = MSG_VIDEO; + RtmpCodec::inputRtmp(pkt); } }//namespace mediakit diff --git a/src/Extension/H264Rtp.cpp b/src/Extension/H264Rtp.cpp index f12218a4..28c775c6 100644 --- a/src/Extension/H264Rtp.cpp +++ b/src/Extension/H264Rtp.cpp @@ -13,9 +13,7 @@ namespace mediakit{ -#if defined(_WIN32) #pragma pack(push, 1) -#endif // defined(_WIN32) class FuFlags { public: @@ -30,11 +28,9 @@ public: unsigned end_bit: 1; unsigned start_bit: 1; #endif -} PACKED; +}; -#if defined(_WIN32) #pragma pack(pop) -#endif // defined(_WIN32) H264RtpDecoder::H264RtpDecoder() { _frame = obtainFrame(); @@ -209,8 +205,8 @@ void H264RtpEncoder::insertConfigFrame(uint64_t pts){ void H264RtpEncoder::packRtp(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){ if (len + 3 <= getMaxSize()) { - //STAP-A模式打包小于MTU - packRtpStapA(ptr, len, pts, is_mark, gop_pos); + // 采用STAP-A/Single NAL unit packet per H.264 模式 + packRtpSmallFrame(ptr, len, pts, is_mark, gop_pos); } else { //STAP-A模式打包会大于MTU,所以采用FU-A模式 packRtpFu(ptr, len, pts, is_mark, gop_pos); @@ -220,8 +216,8 @@ void H264RtpEncoder::packRtp(const char *ptr, size_t len, uint64_t pts, bool is_ void H264RtpEncoder::packRtpFu(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){ auto packet_size = getMaxSize() - 2; if (len <= packet_size + 1) { - //小于FU-A打包最小字节长度要求,采用STAP-A模式 - packRtpStapA(ptr, len, pts, is_mark, gop_pos); + // 小于FU-A打包最小字节长度要求,采用STAP-A/Single NAL unit packet per H.264 模式 + packRtpSmallFrame(ptr, len, pts, is_mark, gop_pos); return; } @@ -257,8 +253,17 @@ void H264RtpEncoder::packRtpFu(const char *ptr, size_t len, uint64_t pts, bool i } } +void H264RtpEncoder::packRtpSmallFrame(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos) { + GET_CONFIG(bool, h264_stap_a, Rtp::kH264StapA); + if (h264_stap_a) { + packRtpStapA(data, len, pts, is_mark, gop_pos); + } else { + packRtpSingleNalu(data, len, pts, is_mark, gop_pos); + } +} + void H264RtpEncoder::packRtpStapA(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){ - //如果帧长度不超过mtu,为了兼容性 webrtc,采用STAP-A模式打包 + // 如果帧长度不超过mtu,为了兼容性 webrtc,采用STAP-A模式打包 auto rtp = makeRtp(getTrackType(), nullptr, len + 3, is_mark, pts); uint8_t *payload = rtp->getPayload(); //STAP-A @@ -270,6 +275,11 @@ void H264RtpEncoder::packRtpStapA(const char *ptr, size_t len, uint64_t pts, boo RtpCodec::inputRtp(rtp, gop_pos); } +void H264RtpEncoder::packRtpSingleNalu(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos) { + // Single NAL unit packet per H.264 模式 + RtpCodec::inputRtp(makeRtp(getTrackType(), data, len, is_mark, pts), gop_pos); +} + bool H264RtpEncoder::inputFrame(const Frame::Ptr &frame) { auto ptr = frame->data() + frame->prefixSize(); switch (H264_TYPE(ptr[0])) { diff --git a/src/Extension/H264Rtp.h b/src/Extension/H264Rtp.h index 77bca5d0..31200cb8 100644 --- a/src/Extension/H264Rtp.h +++ b/src/Extension/H264Rtp.h @@ -28,7 +28,7 @@ public: using Ptr = std::shared_ptr; H264RtpDecoder(); - ~H264RtpDecoder() {} + ~H264RtpDecoder() override = default; /** * 输入264 rtp包 @@ -77,9 +77,10 @@ public: uint32_t sample_rate = 90000, uint8_t pt = 96, uint8_t interleaved = TrackVideo * 2); - ~H264RtpEncoder() {} - /** + ~H264RtpEncoder() override = default; + + /** * 输入264帧 * @param frame 帧数据,必须 */ @@ -96,6 +97,8 @@ private: void packRtp(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos); void packRtpFu(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos); void packRtpStapA(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos); + void packRtpSingleNalu(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos); + void packRtpSmallFrame(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos); private: Frame::Ptr _sps; diff --git a/src/Extension/H265Rtmp.cpp b/src/Extension/H265Rtmp.cpp index 493edac8..af685d36 100644 --- a/src/Extension/H265Rtmp.cpp +++ b/src/Extension/H265Rtmp.cpp @@ -12,12 +12,12 @@ #include "H265Rtmp.h" #ifdef ENABLE_MP4 #include "mpeg4-hevc.h" -#endif//ENABLE_MP4 +#endif // ENABLE_MP4 using namespace std; using namespace toolkit; -namespace mediakit{ +namespace mediakit { H265RtmpDecoder::H265RtmpDecoder() { _h265frame = obtainFrame(); @@ -30,46 +30,105 @@ H265Frame::Ptr H265RtmpDecoder::obtainFrame() { } #ifdef ENABLE_MP4 + +static bool decode_HEVCDecoderConfigurationRecord(uint8_t *extra, size_t bytes, string &frame) { + struct mpeg4_hevc_t hevc; + memset(&hevc, 0, sizeof(hevc)); + if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *)extra, bytes, &hevc) > 0) { + uint8_t *config = new uint8_t[bytes * 2]; + int size = mpeg4_hevc_to_nalu(&hevc, config, bytes * 2); + if (size > 4) { + frame.assign((char *)config + 4, size - 4); + } + delete[] config; + return size > 4; + } + return false; +} + /** * 返回不带0x00 00 00 01头的sps - * @return */ -static bool getH265ConfigFrame(const RtmpPacket &thiz,string &frame) { - if (thiz.getMediaType() != FLV_CODEC_H265) { - return false; - } - if (!thiz.isCfgFrame()) { +static bool getH265ConfigFrame(const RtmpPacket &thiz, string &frame) { + if ((RtmpVideoCodec)thiz.getRtmpCodecId() != RtmpVideoCodec::h265) { return false; } if (thiz.buffer.size() < 6) { WarnL << "bad H265 cfg!"; return false; } - - auto extra = thiz.buffer.data() + 5; - auto bytes = thiz.buffer.size() - 5; - - struct mpeg4_hevc_t hevc; - memset(&hevc, 0, sizeof(hevc)); - if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *) extra, bytes, &hevc) > 0) { - uint8_t *config = new uint8_t[bytes * 2]; - int size = mpeg4_hevc_to_nalu(&hevc, config, bytes * 2); - if (size > 4) { - frame.assign((char *) config + 4, size - 4); - } - delete [] config; - return size > 4; - } - return false; + return decode_HEVCDecoderConfigurationRecord((uint8_t *)thiz.buffer.data() + 5, thiz.buffer.size() - 5, frame); } #endif void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { - if (pkt->isCfgFrame()) { + if (_info.codec == CodecInvalid) { + // 先判断是否为增强型rtmp + parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &_info); + } + + if (_info.is_enhanced) { + // 增强型rtmp + parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &_info); + if (!_info.is_enhanced || _info.codec != CodecH265) { + throw std::invalid_argument("Invalid enhanced-rtmp hevc packet!"); + } + auto data = (uint8_t *)pkt->data() + 5; + auto size = pkt->size() - 5; + switch (_info.video.pkt_type) { + case RtmpPacketType::PacketTypeSequenceStart: { +#ifdef ENABLE_MP4 + string config; + if (decode_HEVCDecoderConfigurationRecord(data, size, config)) { + onGetH265(config.data(), config.size(), pkt->time_stamp, pkt->time_stamp); + } +#else + WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善"; +#endif + break; + } + + case RtmpPacketType::PacketTypeCodedFramesX: + case RtmpPacketType::PacketTypeCodedFrames: { + auto pts = pkt->time_stamp; + if (RtmpPacketType::PacketTypeCodedFrames == _info.video.pkt_type) { + // SI24 = [CompositionTime Offset] + CHECK(size > 7); + int32_t cts = (((data[0] << 16) | (data[1] << 8) | (data[2])) + 0xff800000) ^ 0xff800000; + pts += cts; + data += 3; + size -= 3; + } + splitFrame(data, size, pkt->time_stamp, pts); + break; + } + + case RtmpPacketType::PacketTypeMetadata: { + // The body does not contain video data. The body is an AMF encoded metadata. + // The metadata will be represented by a series of [name, value] pairs. + // For now the only defined [name, value] pair is [“colorInfo”, Object] + // See Metadata Frame section for more details of this object. + // + // For a deeper understanding of the encoding please see description + // of SCRIPTDATA and SSCRIPTDATAVALUE in the FLV file spec. + // DATA = [“colorInfo”, Object] + break; + } + case RtmpPacketType::PacketTypeSequenceEnd: { + // signals end of sequence + break; + } + default: break; + } + return; + } + + // 国内扩展(12) H265 rtmp + if (pkt->isConfigFrame()) { #ifdef ENABLE_MP4 string config; - if(getH265ConfigFrame(*pkt,config)){ - onGetH265(config.data(), config.size(), pkt->time_stamp , pkt->time_stamp); + if (getH265ConfigFrame(*pkt, config)) { + onGetH265(config.data(), config.size(), pkt->time_stamp, pkt->time_stamp); } #else WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善"; @@ -78,41 +137,42 @@ void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { } if (pkt->buffer.size() > 9) { - auto total_len = pkt->buffer.size(); - size_t offset = 5; - uint8_t *cts_ptr = (uint8_t *) (pkt->buffer.data() + 2); + uint8_t *cts_ptr = (uint8_t *)(pkt->buffer.data() + 2); int32_t cts = (((cts_ptr[0] << 16) | (cts_ptr[1] << 8) | (cts_ptr[2])) + 0xff800000) ^ 0xff800000; auto pts = pkt->time_stamp + cts; - while (offset + 4 < total_len) { - uint32_t frame_len; - memcpy(&frame_len, pkt->buffer.data() + offset, 4); - frame_len = ntohl(frame_len); - offset += 4; - if (frame_len + offset > total_len) { - break; - } - onGetH265(pkt->buffer.data() + offset, frame_len, pkt->time_stamp, pts); - offset += frame_len; - } + splitFrame((uint8_t *)pkt->data() + 5, pkt->size() - 5, pkt->time_stamp, pts); } } -inline void H265RtmpDecoder::onGetH265(const char* pcData, size_t iLen, uint32_t dts,uint32_t pts) { - if(iLen == 0){ +void H265RtmpDecoder::splitFrame(const uint8_t *data, size_t size, uint32_t dts, uint32_t pts) { + auto end = data + size; + while (data + 4 < end) { + uint32_t frame_len = load_be32(data); + data += 4; + if (data + frame_len > end) { + break; + } + onGetH265((const char *)data, frame_len, dts, pts); + data += frame_len; + } +} + +inline void H265RtmpDecoder::onGetH265(const char *data, size_t size, uint32_t dts, uint32_t pts) { + if (size == 0) { return; } #if 1 _h265frame->_dts = dts; _h265frame->_pts = pts; - _h265frame->_buffer.assign("\x00\x00\x00\x01", 4); //添加265头 - _h265frame->_buffer.append(pcData, iLen); + _h265frame->_buffer.assign("\x00\x00\x00\x01", 4); // 添加265头 + _h265frame->_buffer.append(data, size); - //写入环形缓存 + // 写入环形缓存 RtmpCodec::inputFrame(_h265frame); _h265frame = obtainFrame(); #else - //防止内存拷贝,这样产生的265帧不会有0x00 00 01头 - auto frame = std::make_shared((char *)pcData,iLen,dts,pts,0); + // 防止内存拷贝,这样产生的265帧不会有0x00 00 01头 + auto frame = std::make_shared((char *)data, size, dts, pts, 0); RtmpCodec::inputFrame(frame); #endif } @@ -123,16 +183,16 @@ H265RtmpEncoder::H265RtmpEncoder(const Track::Ptr &track) { _track = dynamic_pointer_cast(track); } -void H265RtmpEncoder::makeConfigPacket(){ +void H265RtmpEncoder::makeConfigPacket() { if (_track && _track->ready()) { - //尝试从track中获取sps pps信息 + // 尝试从track中获取sps pps信息 _sps = _track->getSps(); _pps = _track->getPps(); _vps = _track->getVps(); } if (!_sps.empty() && !_pps.empty() && !_vps.empty()) { - //获取到sps/pps + // 获取到sps/pps makeVideoConfigPkt(); _got_config_frame = true; } @@ -175,50 +235,42 @@ bool H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) { if (!_rtmp_packet) { _rtmp_packet = RtmpPacket::create(); - //flags/not_config/cts预占位 + // flags/not_config/cts预占位 _rtmp_packet->buffer.resize(5); } return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) { - //flags - _rtmp_packet->buffer[0] = FLV_CODEC_H265 | ((have_key_frame ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4); - //not config - _rtmp_packet->buffer[1] = true; - int32_t cts = pts - dts; - if (cts < 0) { - cts = 0; - } - //cts - set_be24(&_rtmp_packet->buffer[2], cts); - - _rtmp_packet->time_stamp = dts; - _rtmp_packet->body_size = _rtmp_packet->buffer.size(); - _rtmp_packet->chunk_id = CHUNK_VIDEO; - _rtmp_packet->stream_index = STREAM_MEDIA; - _rtmp_packet->type_id = MSG_VIDEO; - //输出rtmp packet - RtmpCodec::inputRtmp(_rtmp_packet); - _rtmp_packet = nullptr; + // flags + _rtmp_packet->buffer[0] = (uint8_t)RtmpVideoCodec::h265 | ((uint8_t)(have_key_frame ? RtmpFrameType::key_frame : RtmpFrameType::inter_frame) << 4); + _rtmp_packet->buffer[1] = (uint8_t)RtmpH264PacketType::h264_nalu; + int32_t cts = pts - dts; + // cts + set_be24(&_rtmp_packet->buffer[2], cts); + _rtmp_packet->time_stamp = dts; + _rtmp_packet->body_size = _rtmp_packet->buffer.size(); + _rtmp_packet->chunk_id = CHUNK_VIDEO; + _rtmp_packet->stream_index = STREAM_MEDIA; + _rtmp_packet->type_id = MSG_VIDEO; + // 输出rtmp packet + RtmpCodec::inputRtmp(_rtmp_packet); + _rtmp_packet = nullptr; }, &_rtmp_packet->buffer); } void H265RtmpEncoder::makeVideoConfigPkt() { #ifdef ENABLE_MP4 - int8_t flags = FLV_CODEC_H265; - flags |= (FLV_KEY_FRAME << 4); - bool is_config = true; - auto rtmpPkt = RtmpPacket::create(); - //header - rtmpPkt->buffer.push_back(flags); - rtmpPkt->buffer.push_back(!is_config); - //cts - rtmpPkt->buffer.append("\x0\x0\x0", 3); + auto flags = (uint8_t)RtmpVideoCodec::h265; + flags |= ((uint8_t)RtmpFrameType::key_frame << 4); + auto pkt = RtmpPacket::create(); + // header + pkt->buffer.push_back(flags); + pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header); + // cts + pkt->buffer.append("\x0\x0\x0", 3); struct mpeg4_hevc_t hevc; memset(&hevc, 0, sizeof(hevc)); - string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps + - string("\x00\x00\x00\x01", 4) + _sps + - string("\x00\x00\x00\x01", 4) + _pps; + string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps + string("\x00\x00\x00\x01", 4) + _sps + string("\x00\x00\x00\x01", 4) + _pps; h265_annexbtomp4(&hevc, vps_sps_pps.data(), (int)vps_sps_pps.size(), NULL, 0, NULL, NULL); uint8_t extra_data[1024]; int extra_data_size = mpeg4_hevc_decoder_configuration_record_save(&hevc, extra_data, sizeof(extra_data)); @@ -226,17 +278,17 @@ void H265RtmpEncoder::makeVideoConfigPkt() { WarnL << "生成H265 extra_data 失败"; return; } - //HEVCDecoderConfigurationRecord - rtmpPkt->buffer.append((char *)extra_data, extra_data_size); - rtmpPkt->body_size = rtmpPkt->buffer.size(); - rtmpPkt->chunk_id = CHUNK_VIDEO; - rtmpPkt->stream_index = STREAM_MEDIA; - rtmpPkt->time_stamp = 0; - rtmpPkt->type_id = MSG_VIDEO; - RtmpCodec::inputRtmp(rtmpPkt); + // HEVCDecoderConfigurationRecord + pkt->buffer.append((char *)extra_data, extra_data_size); + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_VIDEO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = 0; + pkt->type_id = MSG_VIDEO; + RtmpCodec::inputRtmp(pkt); #else WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善"; #endif } -}//namespace mediakit +} // namespace mediakit diff --git a/src/Extension/H265Rtmp.h b/src/Extension/H265Rtmp.h index 593bb753..527728f6 100644 --- a/src/Extension/H265Rtmp.h +++ b/src/Extension/H265Rtmp.h @@ -15,7 +15,7 @@ #include "Extension/Track.h" #include "Extension/H265.h" -namespace mediakit{ +namespace mediakit { /** * h265 Rtmp解码类 * 将 h265 over rtmp 解复用出 h265-Frame @@ -25,7 +25,7 @@ public: using Ptr = std::shared_ptr; H265RtmpDecoder(); - ~H265RtmpDecoder() {} + ~H265RtmpDecoder() = default; /** * 输入265 Rtmp包 @@ -33,22 +33,23 @@ public: */ void inputRtmp(const RtmpPacket::Ptr &rtmp) override; - CodecId getCodecId() const override{ - return CodecH265; - } + CodecId getCodecId() const override { return CodecH265; } protected: - void onGetH265(const char *pcData, size_t iLen, uint32_t dts,uint32_t pts); H265Frame::Ptr obtainFrame(); + void onGetH265(const char *data, size_t size, uint32_t dts, uint32_t pts); + void splitFrame(const uint8_t *data, size_t size, uint32_t dts, uint32_t pts); + protected: + RtmpPacketInfo _info; H265Frame::Ptr _h265frame; }; /** * 265 Rtmp打包类 */ -class H265RtmpEncoder : public H265RtmpDecoder{ +class H265RtmpEncoder : public H265RtmpDecoder { public: using Ptr = std::shared_ptr; @@ -87,9 +88,9 @@ private: std::string _pps; H265Track::Ptr _track; RtmpPacket::Ptr _rtmp_packet; - FrameMerger _merger{FrameMerger::mp4_nal_size}; + FrameMerger _merger { FrameMerger::mp4_nal_size }; }; -}//namespace mediakit +} // namespace mediakit -#endif //ZLMEDIAKIT_H265RTMPCODEC_H +#endif // ZLMEDIAKIT_H265RTMPCODEC_H diff --git a/src/FMP4/FMP4MediaSource.h b/src/FMP4/FMP4MediaSource.h index 53359e30..13674ebe 100644 --- a/src/FMP4/FMP4MediaSource.h +++ b/src/FMP4/FMP4MediaSource.h @@ -51,8 +51,8 @@ public: return _ring; } - void getPlayerList(const std::function> &info_list)> &cb, - const std::function(std::shared_ptr &&info)> &on_change) override { + void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) override { _ring->getInfoList(cb, on_change); } diff --git a/src/FMP4/FMP4MediaSourceMuxer.h b/src/FMP4/FMP4MediaSourceMuxer.h index 47ea5fd0..314951c9 100644 --- a/src/FMP4/FMP4MediaSourceMuxer.h +++ b/src/FMP4/FMP4MediaSourceMuxer.h @@ -11,8 +11,6 @@ #ifndef ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H #define ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H -#if defined(ENABLE_MP4) - #include "FMP4MediaSource.h" #include "Record/MP4Muxer.h" @@ -63,7 +61,8 @@ public: return _option.fmp4_demand ? (_clear_cache ? true : _enabled) : true; } - void onAllTrackReady() { + void addTrackCompleted() override { + MP4MuxerMemory::addTrackCompleted(); _media_src->setInitSegment(getInitSegment()); } @@ -86,5 +85,4 @@ private: }//namespace mediakit -#endif// defined(ENABLE_MP4) #endif //ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H diff --git a/src/Http/HlsParser.cpp b/src/Http/HlsParser.cpp index c7b45e3e..5cbd9195 100644 --- a/src/Http/HlsParser.cpp +++ b/src/Http/HlsParser.cpp @@ -37,7 +37,7 @@ bool HlsParser::parse(const string &http_url, const string &m3u8) { if ((_is_m3u8_inner || extinf_dur != 0) && line[0] != '#') { segment.duration = extinf_dur; - segment.url = Parser::merge_url(http_url, line); + segment.url = Parser::mergeUrl(http_url, line); if (!_is_m3u8_inner) { //ts按照先后顺序排序 ts_map.emplace(index++, segment); diff --git a/src/Http/HlsPlayer.cpp b/src/Http/HlsPlayer.cpp index 1de45cf8..c6509c0c 100644 --- a/src/Http/HlsPlayer.cpp +++ b/src/Http/HlsPlayer.cpp @@ -71,6 +71,11 @@ void HlsPlayer::teardown() { void HlsPlayer::fetchSegment() { if (_ts_list.empty()) { + // 如果是点播文件,播放列表为空代表文件播放结束,关闭播放器: #2628 + if(!HlsParser::isLive()){ + teardown(); + return; + } //播放列表为空,那么立即重新下载m3u8文件 _timer.reset(); fetchIndexFile(); @@ -121,18 +126,21 @@ void HlsPlayer::fetchSegment() { WarnL << "Download ts segment " << url << " failed:" << err; if (err.getErrCode() == Err_timeout) { strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple + 1, MAX_TIMEOUT_MULTIPLE); - }else{ - strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple -1 , MIN_TIMEOUT_MULTIPLE); + } else { + strong_self->_timeout_multiple = MAX(strong_self->_timeout_multiple - 1, MIN_TIMEOUT_MULTIPLE); } } - //提前半秒下载好 - auto delay = duration - ticker.elapsedTime() / 1000.0f - 0.5; - if (delay <= 0) { - //延时最小10ms - delay = 10; + // 提前0.5秒下载好,支持点播文件控制下载速度: #2628 + auto delay = duration - 0.5 - ticker.elapsedTime() / 1000.0f; + if (delay > 2.0) { + // 提前1秒下载 + delay -= 1.0; + } else if (delay <= 0) { + // 延时最小10ms + delay = 0.01; } - //延时下载下一个切片 - strong_self->_timer_ts.reset(new Timer(delay / 1000.0f, [weak_self]() { + // 延时下载下一个切片 + strong_self->_timer_ts.reset(new Timer(delay, [weak_self]() { auto strong_self = weak_self.lock(); if (strong_self) { strong_self->fetchSegment(); diff --git a/src/Http/HttpClient.cpp b/src/Http/HttpClient.cpp index 143ae04d..328872e6 100644 --- a/src/Http/HttpClient.cpp +++ b/src/Http/HttpClient.cpp @@ -21,7 +21,7 @@ namespace mediakit { void HttpClient::sendRequest(const string &url) { clearResponse(); _url = url; - auto protocol = FindField(url.data(), NULL, "://"); + auto protocol = findSubString(url.data(), NULL, "://"); uint16_t port; bool is_https; if (strcasecmp(protocol.data(), "http") == 0) { @@ -35,11 +35,11 @@ void HttpClient::sendRequest(const string &url) { throw std::invalid_argument(strErr); } - auto host = FindField(url.data(), "://", "/"); + auto host = findSubString(url.data(), "://", "/"); if (host.empty()) { - host = FindField(url.data(), "://", NULL); + host = findSubString(url.data(), "://", NULL); } - _path = FindField(url.data(), host.data(), NULL); + _path = findSubString(url.data(), host.data(), NULL); if (_path.empty()) { _path = "/"; } @@ -100,7 +100,7 @@ void HttpClient::clearResponse() { _header_recved = false; _recved_body_size = 0; _total_body_size = 0; - _parser.Clear(); + _parser.clear(); _chunked_splitter = nullptr; _wait_header.resetTime(); _wait_body.resetTime(); @@ -181,20 +181,20 @@ void HttpClient::onError(const SockException &ex) { } ssize_t HttpClient::onRecvHeader(const char *data, size_t len) { - _parser.Parse(data); - if (_parser.Url() == "302" || _parser.Url() == "301" || _parser.Url() == "303") { - auto new_url = Parser::merge_url(_url, _parser["Location"]); + _parser.parse(data, len); + if (_parser.status() == "302" || _parser.status() == "301" || _parser.status() == "303") { + auto new_url = Parser::mergeUrl(_url, _parser["Location"]); if (new_url.empty()) { throw invalid_argument("未找到Location字段(跳转url)"); } - if (onRedirectUrl(new_url, _parser.Url() == "302")) { + if (onRedirectUrl(new_url, _parser.status() == "302")) { HttpClient::sendRequest(new_url); return 0; } } checkCookie(_parser.getHeader()); - onResponseHeader(_parser.Url(), _parser.getHeader()); + onResponseHeader(_parser.status(), _parser.getHeader()); _header_recved = true; if (_parser["Transfer-Encoding"] == "chunked") { @@ -361,8 +361,8 @@ void HttpClient::checkCookie(HttpClient::HttpHeader &headers) { int index = 0; auto arg_vec = split(it_set_cookie->second, ";"); for (string &key_val : arg_vec) { - auto key = FindField(key_val.data(), NULL, "="); - auto val = FindField(key_val.data(), "=", NULL); + auto key = findSubString(key_val.data(), NULL, "="); + auto val = findSubString(key_val.data(), "=", NULL); if (index++ == 0) { cookie->setKeyVal(key, val); diff --git a/src/Http/HttpClient.h b/src/Http/HttpClient.h index c82804d4..497f4a1d 100644 --- a/src/Http/HttpClient.h +++ b/src/Http/HttpClient.h @@ -22,7 +22,7 @@ #include "HttpRequestSplitter.h" #include "HttpCookie.h" #include "HttpChunkedSplitter.h" -#include "strCoding.h" +#include "Common/strCoding.h" #include "HttpBody.h" namespace mediakit { diff --git a/src/Http/HttpConst.cpp b/src/Http/HttpConst.cpp index 3e4116a7..0f7409ac 100644 --- a/src/Http/HttpConst.cpp +++ b/src/Http/HttpConst.cpp @@ -18,7 +18,7 @@ using namespace toolkit; namespace mediakit{ -const char *getHttpStatusMessage(int status) { +const char *HttpConst::getHttpStatusMessage(int status) { switch (status) { case 100: return "Continue"; case 101: return "Switching Protocol"; @@ -196,7 +196,7 @@ static const char *s_mime_src[][2] = { {"avi", "video/x-msvideo"}, }; -const string &getHttpContentType(const char *name) { +const string& HttpConst::getHttpContentType(const char *name) { const char *dot; dot = strrchr(name, '.'); static StrCaseMap mapType; diff --git a/src/Http/HttpConst.h b/src/Http/HttpConst.h index b68f8be5..bced1a7e 100644 --- a/src/Http/HttpConst.h +++ b/src/Http/HttpConst.h @@ -15,19 +15,25 @@ namespace mediakit{ -/** - * 根据http错误代码获取字符说明 - * @param status 譬如404 - * @return 错误代码字符说明,譬如Not Found - */ -const char *getHttpStatusMessage(int status); +class HttpConst { +public: + HttpConst() = delete; + ~HttpConst() = delete; -/** - * 根据文件后缀返回http mime - * @param name 文件后缀,譬如html - * @return mime值,譬如text/html - */ -const std::string &getHttpContentType(const char *name); + /** + * 根据http错误代码获取字符说明 + * @param status 譬如404 + * @return 错误代码字符说明,譬如Not Found + */ + static const char *getHttpStatusMessage(int status); + + /** + * 根据文件后缀返回http mime + * @param name 文件后缀,譬如html + * @return mime值,譬如text/html + */ + static const std::string &getHttpContentType(const char *name); +}; }//mediakit diff --git a/src/Http/HttpCookieManager.cpp b/src/Http/HttpCookieManager.cpp index 2ce0f188..e829423d 100644 --- a/src/Http/HttpCookieManager.cpp +++ b/src/Http/HttpCookieManager.cpp @@ -61,7 +61,7 @@ bool HttpServerCookie::isExpired() { return _ticker.elapsedTime() > _max_elapsed * 1000; } -void HttpServerCookie::setAttach(std::shared_ptr attach) { +void HttpServerCookie::setAttach(toolkit::Any attach) { _attach = std::move(attach); } @@ -114,8 +114,7 @@ void HttpCookieManager::onManager() { } } -HttpServerCookie::Ptr HttpCookieManager::addCookie(const string &cookie_name, const string &uid_in, - uint64_t max_elapsed, std::shared_ptr attach, int max_client) { +HttpServerCookie::Ptr HttpCookieManager::addCookie(const string &cookie_name, const string &uid_in, uint64_t max_elapsed, toolkit::Any attach, int max_client) { lock_guard lck(_mtx_cookie); auto cookie = _generator.obtain(); auto uid = uid_in.empty() ? cookie : uid_in; @@ -158,9 +157,9 @@ HttpServerCookie::Ptr HttpCookieManager::getCookie(const string &cookie_name, co if (it == http_header.end()) { return nullptr; } - auto cookie = FindField(it->second.data(), (cookie_name + "=").data(), ";"); + auto cookie = findSubString(it->second.data(), (cookie_name + "=").data(), ";"); if (cookie.empty()) { - cookie = FindField(it->second.data(), (cookie_name + "=").data(), nullptr); + cookie = findSubString(it->second.data(), (cookie_name + "=").data(), nullptr); } if (cookie.empty()) { return nullptr; diff --git a/src/Http/HttpCookieManager.h b/src/Http/HttpCookieManager.h index e55c2086..53775b79 100644 --- a/src/Http/HttpCookieManager.h +++ b/src/Http/HttpCookieManager.h @@ -85,14 +85,14 @@ public: /** * 设置附加数据 */ - void setAttach(std::shared_ptr attach); + void setAttach(toolkit::Any attach); /* * 获取附加数据 */ template T& getAttach() { - return *static_cast(_attach.get()); + return _attach.get(); } private: @@ -104,7 +104,7 @@ private: std::string _cookie_uuid; uint64_t _max_elapsed; toolkit::Ticker _ticker; - std::shared_ptr _attach; + toolkit::Any _attach; std::weak_ptr _manager; }; @@ -163,7 +163,7 @@ public: */ HttpServerCookie::Ptr addCookie( const std::string &cookie_name, const std::string &uid, uint64_t max_elapsed = COOKIE_DEFAULT_LIFE, - std::shared_ptr attach = nullptr, + toolkit::Any = toolkit::Any{}, int max_client = 1); /** diff --git a/src/Http/HttpFileManager.cpp b/src/Http/HttpFileManager.cpp index 92f349fc..1eaa9ec1 100644 --- a/src/Http/HttpFileManager.cpp +++ b/src/Http/HttpFileManager.cpp @@ -20,7 +20,7 @@ #include "Record/HlsMediaSource.h" #include "Common/Parser.h" #include "Common/config.h" -#include "strCoding.h" +#include "Common/strCoding.h" using namespace std; using namespace toolkit; @@ -31,12 +31,16 @@ namespace mediakit { // 每次访问一次该cookie,那么将重新刷新cookie有效期 // 假如播放器在60秒内都未访问该cookie,那么将重新触发hls播放鉴权 static int kHlsCookieSecond = 60; +static int kFindSrcIntervalSecond = 3; static const string kCookieName = "ZL_COOKIE"; static const string kHlsSuffix = "/hls.m3u8"; +static const string kHlsFMP4Suffix = "/hls.fmp4.m3u8"; struct HttpCookieAttachment { - //是否已经查找到过MediaSource + // 是否已经查找到过MediaSource bool _find_src = false; + // 查找MediaSource计时 + Ticker _find_src_ticker; //cookie生效作用域,本cookie只对该目录下的文件生效 string _path; //上次鉴权失败信息,为空则上次鉴权成功 @@ -46,7 +50,112 @@ struct HttpCookieAttachment { }; const string &HttpFileManager::getContentType(const char *name) { - return getHttpContentType(name); + return HttpConst::getHttpContentType(name); +} + +namespace { +class UInt128 { +public: + UInt128() = default; + + UInt128(const struct sockaddr_storage &storage) { + _family = storage.ss_family; + memset(_bytes, 0, 16); + switch (storage.ss_family) { + case AF_INET: { + memcpy(_bytes, &(reinterpret_cast(storage).sin_addr), 4); + break; + } + case AF_INET6: { + memcpy(_bytes, &(reinterpret_cast(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>; + GET_CONFIG_FUNC(IPRangs, allow_ip_range, Http::kAllowIPRange, [](const string &str) -> IPRangs { + IPRangs ret; + auto vec = split(str, ","); + for (auto &item : vec) { + if (trim(item).empty()) { + continue; + } + auto range = split(item, "-"); + if (range.size() == 2) { + auto ip_min = get_ip_uint64(trim(range[0])); + auto ip_max = get_ip_uint64(trim(range[1])); + if (ip_min && ip_max && ip_min.same_type(ip_max)) { + ret.emplace_back(ip_min, ip_max); + } else { + WarnL << "Invalid ip range or family: " << item; + } + } else if (range.size() == 1) { + auto ip = get_ip_uint64(trim(range[0])); + if (ip) { + ret.emplace_back(ip, ip); + } else { + WarnL << "Invalid ip: " << item; + } + } else { + WarnL << "Invalid ip range: " << item; + } + } + return ret; + }); + + if (allow_ip_range.empty()) { + return true; + } + auto ip_int = get_ip_uint64(ip); + for (auto &range : allow_ip_range) { + if (ip_int.same_type(range.first) && ip_int >= range.first && ip_int <= range.second) { + return true; + } + } + return false; } static string searchIndexFile(const string &dir){ @@ -57,7 +166,7 @@ static string searchIndexFile(const string &dir){ } set setFile; while ((pDirent = readdir(pDir)) != NULL) { - static set indexSet = {"index.html", "index.htm", "index"}; + static set indexSet = {"index.html", "index.htm"}; if (indexSet.find(pDirent->d_name) != indexSet.end()) { string ret = pDirent->d_name; closedir(pDir); @@ -188,7 +297,7 @@ static bool emitHlsPlayed(const Parser &parser, const MediaInfo &media_info, con //cookie有效期为kHlsCookieSecond invoker(err, "", kHlsCookieSecond); }; - bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, static_cast(sender)); + bool flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, sender); if (!flag) { //未开启鉴权,那么允许播放 auth_invoker(""); @@ -240,8 +349,8 @@ public: static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo &media_info, bool is_dir, const function &callback) { //获取用户唯一id - auto uid = parser.Params(); - auto path = parser.Url(); + auto uid = parser.params(); + auto path = parser.url(); //先根据http头中的cookie字段获取cookie HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, parser.getHeader()); @@ -268,7 +377,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo return; } //上次鉴权失败,但是如果url参数发生变更,那么也重新鉴权下 - if (parser.Params().empty() || parser.Params() == cookie->getUid()) { + if (parser.params().empty() || parser.params() == cookie->getUid()) { //url参数未变,或者本来就没有url参数,那么判断本次请求为重复请求,无访问权限 callback(attach._err_msg, update_cookie ? cookie : nullptr); return; @@ -278,7 +387,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo HttpCookieManager::Instance().delCookie(cookie); } - bool is_hls = media_info.schema == HLS_SCHEMA; + bool is_hls = media_info.schema == HLS_SCHEMA || media_info.schema == HLS_FMP4_SCHEMA; SockInfoImp::Ptr info = std::make_shared(); info->_identifier = sender.getIdentifier(); @@ -308,7 +417,9 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo // hls相关信息 attach->_hls_data = std::make_shared(media_info, info); } - callback(err_msg, HttpCookieManager::Instance().addCookie(kCookieName, uid, life_second, attach)); + toolkit::Any any; + any.set(std::move(attach)); + callback(err_msg, HttpCookieManager::Instance().addCookie(kCookieName, uid, life_second, std::move(any))); } else { callback(err_msg, nullptr); } @@ -320,10 +431,10 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo return; } - //事件未被拦截,则认为是http下载请求 - bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, static_cast(sender)); + // 事件未被拦截,则认为是http下载请求 + bool flag = NOTICE_EMIT(BroadcastHttpAccessArgs, Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, sender); if (!flag) { - //此事件无人监听,我们默认都有权限访问 + // 此事件无人监听,我们默认都有权限访问 callback("", nullptr); } } @@ -355,7 +466,7 @@ static string pathCat(const string &a, const string &b){ * @param cb 回调对象 */ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &media_info, const string &file_path, const HttpFileManager::invoker &cb) { - bool is_hls = end_with(file_path, kHlsSuffix); + bool is_hls = end_with(file_path, kHlsSuffix) || end_with(file_path, kHlsFMP4Suffix); if (!is_hls && !File::fileExist(file_path.data())) { //文件不存在且不是hls,那么直接返回404 sendNotFound(cb); @@ -363,8 +474,13 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m } if (is_hls) { // hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS - const_cast(media_info.schema) = HLS_SCHEMA; - replace(const_cast(media_info.stream), kHlsSuffix, ""); + if (end_with(file_path, kHlsSuffix)) { + const_cast(media_info.schema) = HLS_SCHEMA; + replace(const_cast(media_info.stream), kHlsSuffix, ""); + } else { + const_cast(media_info.schema) = HLS_FMP4_SCHEMA; + replace(const_cast(media_info.stream), kHlsFMP4Suffix, ""); + } } weak_ptr weakSession = static_pointer_cast(sender.shared_from_this()); @@ -421,14 +537,15 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m return; } - auto src = cookie->getAttach()._hls_data->getMediaSource(); + auto &attach = cookie->getAttach(); + auto src = attach._hls_data->getMediaSource(); if (src) { - //直接从内存获取m3u8索引文件(而不是从文件系统) + // 直接从内存获取m3u8索引文件(而不是从文件系统) response_file(cookie, cb, file_path, parser, src->getIndexFile()); return; } - if (cookie->getAttach()._find_src) { - //查找过MediaSource,但是流已经注销了,不用再查找 + if (attach._find_src && attach._find_src_ticker.elapsedTime() < kFindSrcIntervalSecond * 1000) { + // 最近已经查找过MediaSource了,为了防止频繁查找导致占用全局互斥锁的问题,我们尝试直接从磁盘返回hls索引文件 response_file(cookie, cb, file_path, parser); return; } @@ -444,11 +561,14 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m auto &attach = cookie->getAttach(); attach._hls_data->setMediaSource(hls); - //添加HlsMediaSource的观看人数(HLS是按需生成的,这样可以触发HLS文件的生成) + // 添加HlsMediaSource的观看人数(HLS是按需生成的,这样可以触发HLS文件的生成) attach._hls_data->addByteUsage(0); - //标记找到MediaSource + // 标记找到MediaSource attach._find_src = true; + // 重置查找MediaSource计时 + attach._find_src_ticker.resetTime(); + // m3u8文件可能不存在, 等待m3u8索引文件按需生成 hls->getIndexFile([response_file, file_path, cookie, cb, parser](const string &file) { response_file(cookie, cb, file_path, parser, file); @@ -457,7 +577,7 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m }); } -static string getFilePath(const Parser &parser,const MediaInfo &media_info, Session &sender){ +static string getFilePath(const Parser &parser,const MediaInfo &media_info, Session &sender) { GET_CONFIG(bool, enableVhost, General::kEnableVhost); GET_CONFIG(string, rootPath, Http::kRootPath); GET_CONFIG_FUNC(StrCaseMap, virtualPathMap, Http::kVirtualPath, [](const string &str) { @@ -469,11 +589,11 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess if (it != virtualPathMap.end()) { //访问的是virtualPath path = it->second; - url = parser.Url().substr(1 + media_info.app.size()); + url = parser.url().substr(1 + media_info.app.size()); } else { //访问的是rootPath path = rootPath; - url = parser.Url(); + url = parser.url(); } for (auto &ch : url) { if (ch == '\\') { @@ -482,7 +602,14 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess } } auto ret = File::absolutePath(enableVhost ? media_info.vhost + url : url, path); - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, static_cast(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).setUrl("/" + ret.substr(http_root.size())); + NOTICE_EMIT(BroadcastHttpBeforeAccessArgs, Broadcast::kBroadcastHttpBeforeAccess, parser, ret, sender); return ret; } @@ -493,7 +620,7 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess * @param cb 回调对象 */ void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFileManager::invoker &cb) { - auto fullUrl = string(HTTP_SCHEMA) + "://" + parser["Host"] + parser.FullUrl(); + auto fullUrl = string(HTTP_SCHEMA) + "://" + parser["Host"] + parser.fullUrl(); MediaInfo media_info(fullUrl); auto file_path = getFilePath(parser, media_info, sender); if (file_path.size() == 0) { @@ -504,15 +631,18 @@ void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFi if (File::is_dir(file_path.data())) { auto indexFile = searchIndexFile(file_path); if (!indexFile.empty()) { - //发现该文件夹下有index文件 + // 发现该文件夹下有index文件 file_path = pathCat(file_path, indexFile); - parser.setUrl(pathCat(parser.Url(), indexFile)); - accessFile(sender, parser, media_info, file_path, cb); - return; + if (!File::is_dir(file_path.data())) { + // 不是文件夹 + parser.setUrl(pathCat(parser.url(), indexFile)); + accessFile(sender, parser, media_info, file_path, cb); + return; + } } string strMenu; //生成文件夹菜单索引 - if (!makeFolderMenu(parser.Url(), file_path, strMenu)) { + if (!makeFolderMenu(parser.url(), file_path, strMenu)) { //文件夹不存在 sendNotFound(cb); return; @@ -600,8 +730,8 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader, if (!strRange.empty()) { //分节下载 code = 206; - auto iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data()); - auto iRangeEnd = atoll(FindField(strRange.data(), "-", nullptr).data()); + auto iRangeStart = atoll(findSubString(strRange.data(), "bytes=", "-").data()); + auto iRangeEnd = atoll(findSubString(strRange.data(), "-", nullptr).data()); auto fileSize = fileBody->remainSize(); if (iRangeEnd == 0) { iRangeEnd = fileSize - 1; @@ -621,4 +751,4 @@ HttpResponseInvokerImp::operator bool(){ } -}//namespace mediakit \ No newline at end of file +}//namespace mediakit diff --git a/src/Http/HttpFileManager.h b/src/Http/HttpFileManager.h index 330b9fa1..a3c0c1f3 100644 --- a/src/Http/HttpFileManager.h +++ b/src/Http/HttpFileManager.h @@ -62,6 +62,13 @@ public: * @return mime值 */ static const std::string &getContentType(const char *name); + + /** + * 该ip是否再白名单中 + * @param ip 支持ipv4和ipv6 + */ + static bool isIPAllowed(const std::string &ip); + private: HttpFileManager() = delete; ~HttpFileManager() = delete; diff --git a/src/Http/HttpRequestSplitter.cpp b/src/Http/HttpRequestSplitter.cpp index 4d859686..a7883f2f 100644 --- a/src/Http/HttpRequestSplitter.cpp +++ b/src/Http/HttpRequestSplitter.cpp @@ -91,7 +91,7 @@ void HttpRequestSplitter::input(const char *data,size_t len) { _remain_data.assign(ptr, _remain_data_size); return; } - //收到content数据,并且接受content完毕 + //收到content数据,并且接收content完毕 onRecvContent(ptr,_content_len); _remain_data_size -= _content_len; diff --git a/src/Http/HttpRequester.cpp b/src/Http/HttpRequester.cpp index 3e403df2..8bd09950 100644 --- a/src/Http/HttpRequester.cpp +++ b/src/Http/HttpRequester.cpp @@ -271,7 +271,7 @@ static void sendReport() { } static toolkit::onceToken s_token([]() { - NoticeCenter::Instance().addListener(nullptr, "kBroadcastEventPollerPoolStarted", [](EventPollerPool &pool, size_t &size) { + NoticeCenter::Instance().addListener(nullptr, "kBroadcastEventPollerPoolStarted", [](EventPollerPoolOnStartedArgs) { // 第一次汇报在程序启动后5分钟 pool.getPoller()->doDelayTask(5 * 60 * 1000, []() { sendReport(); diff --git a/src/Http/HttpSession.cpp b/src/Http/HttpSession.cpp index 9f2ce375..69407928 100644 --- a/src/Http/HttpSession.cpp +++ b/src/Http/HttpSession.cpp @@ -12,7 +12,7 @@ #include #include #include "Common/config.h" -#include "strCoding.h" +#include "Common/strCoding.h" #include "HttpSession.h" #include "HttpConst.h" #include "Util/base64.h" @@ -24,20 +24,20 @@ using namespace toolkit; namespace mediakit { HttpSession::HttpSession(const Socket::Ptr &pSock) : Session(pSock) { - GET_CONFIG(uint32_t,keep_alive_sec,Http::kKeepAliveSecond); + GET_CONFIG(uint32_t, keep_alive_sec, Http::kKeepAliveSecond); pSock->setSendTimeOutSecond(keep_alive_sec); } HttpSession::~HttpSession() = default; -void HttpSession::Handle_Req_HEAD(ssize_t &content_len){ - //暂时全部返回200 OK,因为HTTP GET存在按需生成流的操作,所以不能按照HTTP GET的流程返回 - //如果直接返回404,那么又会导致按需生成流的逻辑失效,所以HTTP HEAD在静态文件或者已存在资源时才有效 - //对于按需生成流的直播场景并不适用 +void HttpSession::onHttpRequest_HEAD() { + // 暂时全部返回200 OK,因为HTTP GET存在按需生成流的操作,所以不能按照HTTP GET的流程返回 + // 如果直接返回404,那么又会导致按需生成流的逻辑失效,所以HTTP HEAD在静态文件或者已存在资源时才有效 + // 对于按需生成流的直播场景并不适用 sendResponse(200, false); } -void HttpSession::Handle_Req_OPTIONS(ssize_t &content_len) { +void HttpSession::onHttpRequest_OPTIONS() { KeyValue header; header.emplace("Allow", "GET, POST, HEAD, OPTIONS"); GET_CONFIG(bool, allow_cross_domains, Http::kAllowCrossDomains); @@ -52,83 +52,140 @@ void HttpSession::Handle_Req_OPTIONS(ssize_t &content_len) { sendResponse(200, true, nullptr, header); } -ssize_t HttpSession::onRecvHeader(const char *header,size_t len) { - typedef void (HttpSession::*HttpCMDHandle)(ssize_t &); - static unordered_map s_func_map; +ssize_t HttpSession::onRecvHeader(const char *header, size_t len) { + using func_type = void (HttpSession::*)(); + static unordered_map s_func_map; static onceToken token([]() { - s_func_map.emplace("GET",&HttpSession::Handle_Req_GET); - s_func_map.emplace("DELETE",&HttpSession::Handle_Req_GET); - s_func_map.emplace("POST",&HttpSession::Handle_Req_POST); - s_func_map.emplace("HEAD",&HttpSession::Handle_Req_HEAD); - s_func_map.emplace("OPTIONS",&HttpSession::Handle_Req_OPTIONS); - }, nullptr); + s_func_map.emplace("GET", &HttpSession::onHttpRequest_GET); + s_func_map.emplace("POST", &HttpSession::onHttpRequest_POST); + // DELETE命令用于whip/whep用,只用于触发http api + s_func_map.emplace("DELETE", &HttpSession::onHttpRequest_POST); + s_func_map.emplace("HEAD", &HttpSession::onHttpRequest_HEAD); + s_func_map.emplace("OPTIONS", &HttpSession::onHttpRequest_OPTIONS); + }); - _parser.Parse(header); - CHECK(_parser.Url()[0] == '/'); + _parser.parse(header, len); + CHECK(_parser.url()[0] == '/'); urlDecode(_parser); - string cmd = _parser.Method(); + auto &cmd = _parser.method(); auto it = s_func_map.find(cmd); if (it == s_func_map.end()) { - WarnP(this) << "不支持该命令:" << cmd; + WarnP(this) << "Http method not supported: " << cmd; sendResponse(405, true); return 0; } - //跨域 - _origin = _parser["Origin"]; + size_t content_len; + auto &content_len_str = _parser["Content-Length"]; + if (content_len_str.empty()) { + if (it->first == "POST") { + // Http post未指定长度,我们认为是不定长的body + WarnL << "Received http post request without content-length, consider it to be unlimited length"; + content_len = SIZE_MAX; + } else { + content_len = 0; + } + } else { + // 已经指定长度 + content_len = atoll(content_len_str.data()); + } - //默认后面数据不是content而是header - ssize_t content_len = 0; - (this->*(it->second))(content_len); + if (content_len == 0) { + //// 没有body的情况,直接触发回调 //// + (this->*(it->second))(); + _parser.clear(); + // 如果设置了_on_recv_body, 那么说明后续要处理body + return _on_recv_body ? -1 : 0; + } - //清空解析器节省内存 - _parser.Clear(); - //返回content长度 - return content_len; + GET_CONFIG(size_t, maxReqSize, Http::kMaxReqSize); + if (content_len > maxReqSize) { + //// 不定长body或超大body //// + if (content_len != SIZE_MAX) { + WarnL << "Http body size is too huge: " << content_len << " > " << maxReqSize + << ", please set " << Http::kMaxReqSize << " in config.ini file."; + } + + size_t received = 0; + auto parser = std::move(_parser); + _on_recv_body = [this, parser, received, content_len](const char *data, size_t len) mutable { + received += len; + onRecvUnlimitedContent(parser, data, len, content_len, received); + if (received < content_len) { + // 还没收满 + return true; + } + + // 收满了 + setContentLen(0); + return false; + }; + // 声明后续都是body;Http body在本对象缓冲,不通过HttpRequestSplitter保存 + return -1; + } + + //// body size明确指定且小于最大值的情况 //// + auto body = std::make_shared(); + // 预留一定的内存buffer,防止频繁的内存拷贝 + body->reserve(content_len); + + _on_recv_body = [this, body, content_len, it](const char *data, size_t len) mutable { + body->append(data, len); + if (body->size() < content_len) { + // 未收满数据 + return true; + } + + // 收集body完毕 + _parser.setContent(std::move(*body)); + (this->*(it->second))(); + _parser.clear(); + + // 后续是header + setContentLen(0); + return false; + }; + + // 声明后续都是body;Http body在本对象缓冲,不通过HttpRequestSplitter保存 + return -1; } -void HttpSession::onRecvContent(const char *data,size_t len) { - if(_contentCallBack){ - if(!_contentCallBack(data,len)){ - _contentCallBack = nullptr; - } +void HttpSession::onRecvContent(const char *data, size_t len) { + if (_on_recv_body && !_on_recv_body(data, len)) { + _on_recv_body = nullptr; } } void HttpSession::onRecv(const Buffer::Ptr &pBuf) { _ticker.resetTime(); - input(pBuf->data(),pBuf->size()); + input(pBuf->data(), pBuf->size()); } -void HttpSession::onError(const SockException& err) { +void HttpSession::onError(const SockException &err) { if (_is_live_stream) { - //flv/ts播放器 + // flv/ts播放器 uint64_t duration = _ticker.createdTime() / 1000; - WarnP(this) << "FLV/TS/FMP4播放器(" - << _mediaInfo.shortUrl() - << ")断开:" << err - << ",耗时(s):" << duration; + WarnP(this) << "FLV/TS/FMP4播放器(" << _mediaInfo.shortUrl() << ")断开:" << err << ",耗时(s):" << duration; GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_total_bytes_usage >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, - duration, true, static_cast(*this)); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration, true, *this); } return; } } void HttpSession::onManager() { - GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond); + GET_CONFIG(uint32_t, keepAliveSec, Http::kKeepAliveSecond); - if(_ticker.elapsedTime() > keepAliveSec * 1000){ - //1分钟超时 - shutdown(SockException(Err_timeout,"session timeout")); + if (_ticker.elapsedTime() > keepAliveSec * 1000) { + // 1分钟超时 + shutdown(SockException(Err_timeout, "session timeout")); } } -bool HttpSession::checkWebSocket(){ +bool HttpSession::checkWebSocket() { auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"]; if (Sec_WebSocket_Key.empty()) { return false; @@ -148,25 +205,31 @@ bool HttpSession::checkWebSocket(){ sendResponse(101, false, nullptr, headerOut, nullptr, true); }; - //判断是否为websocket-flv - if (checkLiveStreamFlv(res_cb)) { - //这里是websocket-flv直播请求 + auto res_cb_flv = [this, headerOut]() mutable { + _live_over_websocket = true; + headerOut.emplace("Cache-Control", "no-store"); + sendResponse(101, false, nullptr, headerOut, nullptr, true); + }; + + // 判断是否为websocket-flv + if (checkLiveStreamFlv(res_cb_flv)) { + // 这里是websocket-flv直播请求 return true; } - //判断是否为websocket-ts + // 判断是否为websocket-ts if (checkLiveStreamTS(res_cb)) { - //这里是websocket-ts直播请求 + // 这里是websocket-ts直播请求 return true; } - //判断是否为websocket-fmp4 + // 判断是否为websocket-fmp4 if (checkLiveStreamFMP4(res_cb)) { - //这里是websocket-fmp4直播请求 + // 这里是websocket-fmp4直播请求 return true; } - //这是普通的websocket连接 + // 这是普通的websocket连接 if (!onWebSocketConnect(_parser)) { sendResponse(501, true, nullptr, headerOut); return true; @@ -175,8 +238,8 @@ bool HttpSession::checkWebSocket(){ return true; } -bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix, const function &cb){ - std::string url = _parser.Url(); +bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix, const function &cb) { + std::string url = _parser.url(); auto it = _parser.getUrlArgs().find("schema"); if (it != _parser.getUrlArgs().end()) { if (strcasecmp(it->second.c_str(), schema.c_str())) { @@ -186,57 +249,57 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffi } else { auto prefix_size = url_suffix.size(); if (url.size() < prefix_size || strcasecmp(url.data() + (url.size() - prefix_size), url_suffix.data())) { - //未找到后缀 + // 未找到后缀 return false; } // url去除特殊后缀 url.resize(url.size() - prefix_size); } - //带参数的url - if (!_parser.Params().empty()) { + // 带参数的url + if (!_parser.params().empty()) { url += "?"; - url += _parser.Params(); + url += _parser.params(); } - //解析带上协议+参数完整的url + // 解析带上协议+参数完整的url _mediaInfo.parse(schema + "://" + _parser["Host"] + url); if (_mediaInfo.app.empty() || _mediaInfo.stream.empty()) { - //url不合法 + // url不合法 return false; } bool close_flag = !strcasecmp(_parser["Connection"].data(), "close"); weak_ptr weak_self = static_pointer_cast(shared_from_this()); - //鉴权结果回调 + // 鉴权结果回调 auto onRes = [cb, weak_self, close_flag](const string &err) { auto strong_self = weak_self.lock(); if (!strong_self) { - //本对象已经销毁 + // 本对象已经销毁 return; } if (!err.empty()) { - //播放鉴权失败 + // 播放鉴权失败 strong_self->sendResponse(401, close_flag, nullptr, KeyValue(), std::make_shared(err)); return; } - //异步查找直播流 + // 异步查找直播流 MediaSource::findAsync(strong_self->_mediaInfo, strong_self, [weak_self, close_flag, cb](const MediaSource::Ptr &src) { auto strong_self = weak_self.lock(); if (!strong_self) { - //本对象已经销毁 + // 本对象已经销毁 return; } if (!src) { - //未找到该流 + // 未找到该流 strong_self->sendNotFound(close_flag); } else { strong_self->_is_live_stream = true; - //触发回调 + // 触发回调 cb(src); } }); @@ -248,38 +311,42 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffi } }; - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _mediaInfo, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _mediaInfo, invoker, *this); if (!flag) { - //该事件无人监听,默认不鉴权 + // 该事件无人监听,默认不鉴权 onRes(""); } return true; } -//http-fmp4 链接格式:http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2 -bool HttpSession::checkLiveStreamFMP4(const function &cb){ +// http-fmp4 链接格式:http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2 +bool HttpSession::checkLiveStreamFMP4(const function &cb) { return checkLiveStream(FMP4_SCHEMA, ".live.mp4", [this, cb](const MediaSource::Ptr &src) { auto fmp4_src = dynamic_pointer_cast(src); assert(fmp4_src); if (!cb) { - //找到源,发送http头,负载后续发送 + // 找到源,发送http头,负载后续发送 sendResponse(200, false, HttpFileManager::getContentType(".mp4").data(), KeyValue(), nullptr, true); } else { - //自定义发送http头 + // 自定义发送http头 cb(); } - //直播牺牲延时提升发送性能 + // 直播牺牲延时提升发送性能 setSocketFlags(); onWrite(std::make_shared(fmp4_src->getInitSegment()), true); weak_ptr weak_self = static_pointer_cast(shared_from_this()); fmp4_src->pause(false); _fmp4_reader = fmp4_src->getRing()->attach(getPoller()); - _fmp4_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); }); + _fmp4_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(static_pointer_cast(weak_self.lock())); + return ret; + }); _fmp4_reader->setDetachCB([weak_self]() { auto strong_self = weak_self.lock(); if (!strong_self) { - //本对象已经销毁 + // 本对象已经销毁 return; } strong_self->shutdown(SockException(Err_shutdown, "fmp4 ring buffer detached")); @@ -287,83 +354,84 @@ bool HttpSession::checkLiveStreamFMP4(const function &cb){ _fmp4_reader->setReadCB([weak_self](const FMP4MediaSource::RingDataType &fmp4_list) { auto strong_self = weak_self.lock(); if (!strong_self) { - //本对象已经销毁 + // 本对象已经销毁 return; } size_t i = 0; auto size = fmp4_list->size(); - fmp4_list->for_each([&](const FMP4Packet::Ptr &ts) { - strong_self->onWrite(ts, ++i == size); - }); + fmp4_list->for_each([&](const FMP4Packet::Ptr &ts) { strong_self->onWrite(ts, ++i == size); }); }); }); } -//http-ts 链接格式:http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2 -bool HttpSession::checkLiveStreamTS(const function &cb){ +// http-ts 链接格式:http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2 +bool HttpSession::checkLiveStreamTS(const function &cb) { return checkLiveStream(TS_SCHEMA, ".live.ts", [this, cb](const MediaSource::Ptr &src) { auto ts_src = dynamic_pointer_cast(src); assert(ts_src); if (!cb) { - //找到源,发送http头,负载后续发送 + // 找到源,发送http头,负载后续发送 sendResponse(200, false, HttpFileManager::getContentType(".ts").data(), KeyValue(), nullptr, true); } else { - //自定义发送http头 + // 自定义发送http头 cb(); } - //直播牺牲延时提升发送性能 + // 直播牺牲延时提升发送性能 setSocketFlags(); weak_ptr weak_self = static_pointer_cast(shared_from_this()); ts_src->pause(false); _ts_reader = ts_src->getRing()->attach(getPoller()); - _ts_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); }); - _ts_reader->setDetachCB([weak_self](){ + _ts_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(static_pointer_cast(weak_self.lock())); + return ret; + }); + _ts_reader->setDetachCB([weak_self]() { auto strong_self = weak_self.lock(); if (!strong_self) { - //本对象已经销毁 + // 本对象已经销毁 return; } - strong_self->shutdown(SockException(Err_shutdown,"ts ring buffer detached")); + strong_self->shutdown(SockException(Err_shutdown, "ts ring buffer detached")); }); _ts_reader->setReadCB([weak_self](const TSMediaSource::RingDataType &ts_list) { auto strong_self = weak_self.lock(); if (!strong_self) { - //本对象已经销毁 + // 本对象已经销毁 return; } size_t i = 0; auto size = ts_list->size(); - ts_list->for_each([&](const TSPacket::Ptr &ts) { - strong_self->onWrite(ts, ++i == size); - }); + ts_list->for_each([&](const TSPacket::Ptr &ts) { strong_self->onWrite(ts, ++i == size); }); }); }); } -//http-flv 链接格式:http://vhost-url:port/app/streamid.live.flv?key1=value1&key2=value2 -bool HttpSession::checkLiveStreamFlv(const function &cb){ +// http-flv 链接格式:http://vhost-url:port/app/streamid.live.flv?key1=value1&key2=value2 +bool HttpSession::checkLiveStreamFlv(const function &cb) { auto start_pts = atoll(_parser.getUrlArgs()["starPts"].data()); return checkLiveStream(RTMP_SCHEMA, ".live.flv", [this, cb, start_pts](const MediaSource::Ptr &src) { auto rtmp_src = dynamic_pointer_cast(src); assert(rtmp_src); if (!cb) { - //找到源,发送http头,负载后续发送 - sendResponse(200, false, HttpFileManager::getContentType(".flv").data(), KeyValue(), nullptr, true); + // 找到源,发送http头,负载后续发送 + KeyValue headerOut; + headerOut["Cache-Control"] = "no-store"; + sendResponse(200, false, HttpFileManager::getContentType(".flv").data(), headerOut, nullptr, true); } else { - //自定义发送http头 + // 自定义发送http头 cb(); } - //直播牺牲延时提升发送性能 + // 直播牺牲延时提升发送性能 setSocketFlags(); - //非H264/AAC时打印警告日志,防止用户提无效问题 + // 非H264/AAC时打印警告日志,防止用户提无效问题 auto tracks = src->getTracks(false); for (auto &track : tracks) { switch (track->getCodecId()) { case CodecH264: - case CodecAAC: - break; + case CodecAAC: break; default: { WarnP(this) << "flv播放器一般只支持H264和AAC编码,该编码格式可能不被播放器支持:" << track->getCodecName(); break; @@ -375,46 +443,42 @@ bool HttpSession::checkLiveStreamFlv(const function &cb){ }); } -void HttpSession::Handle_Req_GET(ssize_t &content_len) { - Handle_Req_GET_l(content_len, true); -} - -void HttpSession::Handle_Req_GET_l(ssize_t &content_len, bool sendBody) { - //先看看是否为WebSocket请求 +void HttpSession::onHttpRequest_GET() { + // 先看看是否为WebSocket请求 if (checkWebSocket()) { - content_len = -1; - _contentCallBack = [this](const char *data, size_t len) { - WebSocketSplitter::decode((uint8_t *) data, len); - //_contentCallBack是可持续的,后面还要处理后续数据 + // 后续都是websocket body数据 + _on_recv_body = [this](const char *data, size_t len) { + WebSocketSplitter::decode((uint8_t *)data, len); + // _contentCallBack是可持续的,后面还要处理后续数据 return true; }; return; } if (emitHttpEvent(false)) { - //拦截http api事件 + // 拦截http api事件 return; } if (checkLiveStreamFlv()) { - //拦截http-flv播放器 + // 拦截http-flv播放器 return; } if (checkLiveStreamTS()) { - //拦截http-ts播放器 + // 拦截http-ts播放器 return; } if (checkLiveStreamFMP4()) { - //拦截http-fmp4播放器 + // 拦截http-fmp4播放器 return; } - bool bClose = !strcasecmp(_parser["Connection"].data(),"close"); + bool bClose = !strcasecmp(_parser["Connection"].data(), "close"); weak_ptr weak_self = static_pointer_cast(shared_from_this()); HttpFileManager::onAccessPath(*this, _parser, [weak_self, bClose](int code, const string &content_type, - const StrCaseMap &responseHeader, const HttpBody::Ptr &body) { + const StrCaseMap &responseHeader, const HttpBody::Ptr &body) { auto strong_self = weak_self.lock(); if (!strong_self) { return; @@ -460,7 +524,7 @@ public: static bool onSocketFlushed(const AsyncSenderData::Ptr &data) { if (data->_read_complete) { if (data->_close_when_complete) { - //发送完毕需要关闭socket + // 发送完毕需要关闭socket shutdown(data->_session.lock()); } return false; @@ -470,13 +534,13 @@ public: data->_body->readDataAsync(sendBufSize, [data](const Buffer::Ptr &sendBuf) { auto session = data->_session.lock(); if (!session) { - //本对象已经销毁 + // 本对象已经销毁 return; } session->async([data, sendBuf]() { auto session = data->_session.lock(); if (!session) { - //本对象已经销毁 + // 本对象已经销毁 return; } onRequestData(data, session, sendBuf); @@ -489,14 +553,14 @@ private: static void onRequestData(const AsyncSenderData::Ptr &data, const std::shared_ptr &session, const Buffer::Ptr &sendBuf) { session->_ticker.resetTime(); if (sendBuf && session->send(sendBuf) != -1) { - //文件还未读完,还需要继续发送 + // 文件还未读完,还需要继续发送 if (!session->isSocketBusy()) { - //socket还可写,继续请求数据 + // socket还可写,继续请求数据 onSocketFlushed(data); } return; } - //文件写完了 + // 文件写完了 data->_read_complete = true; if (!session->isSocketBusy() && data->_close_when_complete) { shutdown(session); @@ -504,34 +568,25 @@ private: } static void shutdown(const std::shared_ptr &session) { - if(session){ + if (session) { session->shutdown(SockException(Err_shutdown, StrPrinter << "close connection after send http body completed.")); } } }; -static const string kDate = "Date"; -static const string kServer = "Server"; -static const string kConnection = "Connection"; -static const string kKeepAlive = "Keep-Alive"; -static const string kContentType = "Content-Type"; -static const string kContentLength = "Content-Length"; -static const string kAccessControlAllowOrigin = "Access-Control-Allow-Origin"; -static const string kAccessControlAllowCredentials = "Access-Control-Allow-Credentials"; - void HttpSession::sendResponse(int code, bool bClose, const char *pcContentType, const HttpSession::KeyValue &header, const HttpBody::Ptr &body, - bool no_content_length ){ - GET_CONFIG(string,charSet,Http::kCharSet); - GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond); + bool no_content_length) { + GET_CONFIG(string, charSet, Http::kCharSet); + GET_CONFIG(uint32_t, keepAliveSec, Http::kKeepAliveSecond); - //body默认为空 + // body默认为空 int64_t size = 0; if (body && body->remainSize()) { - //有body,获取body大小 + // 有body,获取body大小 size = body->remainSize(); } @@ -539,52 +594,53 @@ void HttpSession::sendResponse(int code, // http-flv直播是Keep-Alive类型 bClose = false; } else if ((size_t)size >= SIZE_MAX || size < 0) { - //不固定长度的body,那么发送完body后应该关闭socket,以便浏览器做下载完毕的判断 + // 不固定长度的body,那么发送完body后应该关闭socket,以便浏览器做下载完毕的判断 bClose = true; } HttpSession::KeyValue &headerOut = const_cast(header); - headerOut.emplace(kDate, dateStr()); - headerOut.emplace(kServer, kServerName); - headerOut.emplace(kConnection, bClose ? "close" : "keep-alive"); + headerOut.emplace("Date", dateStr()); + headerOut.emplace("Server", kServerName); + headerOut.emplace("Connection", bClose ? "close" : "keep-alive"); + + GET_CONFIG(bool, allow_cross_domains, Http::kAllowCrossDomains); + if (allow_cross_domains) { + headerOut.emplace("Access-Control-Allow-Origin", "*"); + headerOut.emplace("Access-Control-Allow-Credentials", "true"); + } + if (!bClose) { string keepAliveString = "timeout="; keepAliveString += to_string(keepAliveSec); keepAliveString += ", max=100"; - headerOut.emplace(kKeepAlive, std::move(keepAliveString)); - } - - if (!_origin.empty()) { - //设置跨域 - headerOut.emplace(kAccessControlAllowOrigin, _origin); - headerOut.emplace(kAccessControlAllowCredentials, "true"); + headerOut.emplace("Keep-Alive", std::move(keepAliveString)); } if (!no_content_length && size >= 0 && (size_t)size < SIZE_MAX) { - //文件长度为固定值,且不是http-flv强制设置Content-Length - headerOut[kContentLength] = to_string(size); + // 文件长度为固定值,且不是http-flv强制设置Content-Length + headerOut["Content-Length"] = to_string(size); } if (size && !pcContentType) { - //有body时,设置缺省类型 + // 有body时,设置缺省类型 pcContentType = "text/plain"; } if ((size || no_content_length) && pcContentType) { - //有body时,设置文件类型 + // 有body时,设置文件类型 string strContentType = pcContentType; strContentType += "; charset="; strContentType += charSet; - headerOut.emplace(kContentType, std::move(strContentType)); + headerOut.emplace("Content-Type", std::move(strContentType)); } - //发送http头 + // 发送http头 string str; str.reserve(256); str += "HTTP/1.1 "; str += to_string(code); str += ' '; - str += getHttpStatusMessage(code); + str += HttpConst::getHttpStatusMessage(code); str += "\r\n"; for (auto &pr : header) { str += pr.first; @@ -597,9 +653,9 @@ void HttpSession::sendResponse(int code, _ticker.resetTime(); if (!size) { - //没有body + // 没有body if (bClose) { - shutdown(SockException(Err_shutdown,StrPrinter << "close connection after send http header completed with status code:" << code)); + shutdown(SockException(Err_shutdown, StrPrinter << "close connection after send http header completed with status code:" << code)); } return; } @@ -614,20 +670,20 @@ void HttpSession::sendResponse(int code, GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize); if (body->remainSize() > sendBufSize) { - //文件下载提升发送性能 + // 文件下载提升发送性能 setSocketFlags(); } - //发送http body + // 发送http body AsyncSenderData::Ptr data = std::make_shared(static_pointer_cast(shared_from_this()), body, bClose); getSock()->setOnFlush([data]() { return AsyncSender::onSocketFlushed(data); }); AsyncSender::onSocketFlushed(data); } -string HttpSession::urlDecode(const string &str){ +string HttpSession::urlDecode(const string &str) { auto ret = strCoding::UrlDecode(str); #ifdef _WIN32 - GET_CONFIG(string,charSet,Http::kCharSet); + GET_CONFIG(string, charSet, Http::kCharSet); bool isGb2312 = !strcasecmp(charSet.data(), "gb2312"); if (isGb2312) { ret = strCoding::UTF8ToGB2312(ret); @@ -636,117 +692,51 @@ string HttpSession::urlDecode(const string &str){ return ret; } -void HttpSession::urlDecode(Parser &parser){ - parser.setUrl(urlDecode(parser.Url())); - for(auto &pr : _parser.getUrlArgs()){ +void HttpSession::urlDecode(Parser &parser) { + parser.setUrl(urlDecode(parser.url())); + for (auto &pr : _parser.getUrlArgs()) { const_cast(pr.second) = urlDecode(pr.second); } } -bool HttpSession::emitHttpEvent(bool doInvoke){ - bool bClose = !strcasecmp(_parser["Connection"].data(),"close"); +bool HttpSession::emitHttpEvent(bool doInvoke) { + bool bClose = !strcasecmp(_parser["Connection"].data(), "close"); /////////////////////异步回复Invoker/////////////////////////////// weak_ptr weak_self = static_pointer_cast(shared_from_this()); - HttpResponseInvoker invoker = [weak_self,bClose](int code, const KeyValue &headerOut, const HttpBody::Ptr &body){ + HttpResponseInvoker invoker = [weak_self, bClose](int code, const KeyValue &headerOut, const HttpBody::Ptr &body) { auto strong_self = weak_self.lock(); - if(!strong_self) { + if (!strong_self) { return; } strong_self->async([weak_self, bClose, code, headerOut, body]() { auto strong_self = weak_self.lock(); if (!strong_self) { - //本对象已经销毁 + // 本对象已经销毁 return; } strong_self->sendResponse(code, bClose, nullptr, headerOut, body); }); }; ///////////////////广播HTTP事件/////////////////////////// - bool consumed = false;//该事件是否被消费 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpRequest,_parser,invoker,consumed,static_cast(*this)); - if(!consumed && doInvoke){ - //该事件无人消费,所以返回404 - invoker(404,KeyValue(), HttpBody::Ptr()); + bool consumed = false; // 该事件是否被消费 + NOTICE_EMIT(BroadcastHttpRequestArgs, Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, *this); + if (!consumed && doInvoke) { + // 该事件无人消费,所以返回404 + invoker(404, KeyValue(), HttpBody::Ptr()); } return consumed; } std::string HttpSession::get_peer_ip() { GET_CONFIG(string, forwarded_ip_header, Http::kForwardedIpHeader); - if(!forwarded_ip_header.empty() && !_parser.getHeader()[forwarded_ip_header].empty()){ + if (!forwarded_ip_header.empty() && !_parser.getHeader()[forwarded_ip_header].empty()) { return _parser.getHeader()[forwarded_ip_header]; } return Session::get_peer_ip(); } -void HttpSession::Handle_Req_POST(ssize_t &content_len) { - GET_CONFIG(size_t,maxReqSize,Http::kMaxReqSize); - - ssize_t totalContentLen = _parser["Content-Length"].empty() ? -1 : atoll(_parser["Content-Length"].data()); - - if(totalContentLen == 0){ - //content为空 - //emitHttpEvent内部会选择是否关闭连接 - 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 recvedContentLen = std::make_shared(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::onHttpRequest_POST() { + emitHttpEvent(true); } void HttpSession::sendNotFound(bool bClose) { @@ -754,19 +744,19 @@ void HttpSession::sendNotFound(bool bClose) { sendResponse(404, bClose, "text/html", KeyValue(), std::make_shared(notFound)); } -void HttpSession::setSocketFlags(){ +void HttpSession::setSocketFlags() { GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS); - if(mergeWriteMS > 0) { - //推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高 + if (mergeWriteMS > 0) { + // 推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高 SockUtil::setNoDelay(getSock()->rawFD(), false); - //播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能 + // 播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能 setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE); } } void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) { - if(flush){ - //需要flush那么一次刷新缓存 + if (flush) { + // 需要flush那么一次刷新缓存 HttpSession::setSendFlushFlag(true); } @@ -784,18 +774,18 @@ void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) { } if (flush) { - //本次刷新缓存后,下次不用刷新缓存 + // 本次刷新缓存后,下次不用刷新缓存 HttpSession::setSendFlushFlag(false); } } -void HttpSession::onWebSocketEncodeData(Buffer::Ptr buffer){ +void HttpSession::onWebSocketEncodeData(Buffer::Ptr buffer) { _total_bytes_usage += buffer->size(); send(std::move(buffer)); } -void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in){ - WebSocketHeader& header = const_cast(header_in); +void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in) { + WebSocketHeader &header = const_cast(header_in); header._mask_flag = false; switch (header._opcode) { @@ -805,15 +795,15 @@ void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in){ break; } - default : break; + default: break; } } void HttpSession::onDetach() { - shutdown(SockException(Err_shutdown,"rtmp ring buffer detached")); + shutdown(SockException(Err_shutdown, "rtmp ring buffer detached")); } -std::shared_ptr HttpSession::getSharedPtr(){ +std::shared_ptr HttpSession::getSharedPtr() { return dynamic_pointer_cast(shared_from_this()); } diff --git a/src/Http/HttpSession.h b/src/Http/HttpSession.h index 68b2030d..513f8b63 100644 --- a/src/Http/HttpSession.h +++ b/src/Http/HttpSession.h @@ -101,11 +101,10 @@ protected: std::string get_peer_ip() override; private: - void Handle_Req_GET(ssize_t &content_len); - void Handle_Req_GET_l(ssize_t &content_len, bool sendBody); - void Handle_Req_POST(ssize_t &content_len); - void Handle_Req_HEAD(ssize_t &content_len); - void Handle_Req_OPTIONS(ssize_t &content_len); + void onHttpRequest_GET(); + void onHttpRequest_POST(); + void onHttpRequest_HEAD(); + void onHttpRequest_OPTIONS(); bool checkLiveStream(const std::string &schema, const std::string &url_suffix, const std::function &cb); @@ -132,13 +131,12 @@ private: bool _live_over_websocket = false; //消耗的总流量 uint64_t _total_bytes_usage = 0; - std::string _origin; Parser _parser; toolkit::Ticker _ticker; TSMediaSource::RingType::RingReader::Ptr _ts_reader; FMP4MediaSource::RingType::RingReader::Ptr _fmp4_reader; //处理content数据的callback - std::function _contentCallBack; + std::function _on_recv_body; }; using HttpsSession = toolkit::SessionWithSSL; diff --git a/src/Player/PlayerBase.cpp b/src/Player/PlayerBase.cpp index 31d8e051..3f465bd7 100644 --- a/src/Player/PlayerBase.cpp +++ b/src/Player/PlayerBase.cpp @@ -12,6 +12,7 @@ #include "PlayerBase.h" #include "Rtsp/RtspPlayerImp.h" #include "Rtmp/RtmpPlayerImp.h" +#include "Rtmp/FlvPlayer.h" #include "Http/HlsPlayer.h" #include "Http/TsPlayerImp.h" @@ -20,15 +21,16 @@ using namespace toolkit; namespace mediakit { -PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller, const string &url_in) { - static auto releasePlayer = [](PlayerBase *ptr) { - onceToken token(nullptr, [&]() { - delete ptr; +PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &in_poller, const string &url_in) { + auto poller = in_poller ? in_poller : EventPollerPool::Instance().getPoller(); + static auto releasePlayer = [poller](PlayerBase *ptr) { + poller->async([ptr]() { + onceToken token(nullptr, [&]() { delete ptr; }); + ptr->teardown(); }); - ptr->teardown(); }; string url = url_in; - string prefix = FindField(url.data(), NULL, "://"); + string prefix = findSubString(url.data(), NULL, "://"); auto pos = url.find('?'); if (pos != string::npos) { //去除?后面的字符串 @@ -53,9 +55,13 @@ PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller, const s if ((strcasecmp("http", prefix.data()) == 0 || strcasecmp("https", prefix.data()) == 0)) { if (end_with(url, ".m3u8") || end_with(url_in, ".m3u8")) { return PlayerBase::Ptr(new HlsPlayerImp(poller), releasePlayer); - } else if (end_with(url, ".ts") || end_with(url_in, ".ts")) { + } + if (end_with(url, ".ts") || end_with(url_in, ".ts")) { return PlayerBase::Ptr(new TsPlayerImp(poller), releasePlayer); } + if (end_with(url, ".flv") || end_with(url_in, ".flv")) { + return PlayerBase::Ptr(new FlvPlayerImp(poller), releasePlayer); + } } throw std::invalid_argument("not supported play schema:" + url_in); diff --git a/src/Player/PlayerProxy.cpp b/src/Player/PlayerProxy.cpp index 36b4d850..2a319ff8 100644 --- a/src/Player/PlayerProxy.cpp +++ b/src/Player/PlayerProxy.cpp @@ -202,7 +202,11 @@ PlayerProxy::~PlayerProxy() { _timer.reset(); // 避免析构时, 忘记回调api请求 if (_on_play) { - _on_play(SockException(Err_shutdown, "player proxy close")); + try { + _on_play(SockException(Err_shutdown, "player proxy close")); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } _on_play = nullptr; } } diff --git a/src/Pusher/MediaPusher.cpp b/src/Pusher/MediaPusher.cpp index 865b4d2a..f0ea401b 100644 --- a/src/Pusher/MediaPusher.cpp +++ b/src/Pusher/MediaPusher.cpp @@ -31,8 +31,7 @@ MediaPusher::MediaPusher(const string &schema, MediaPusher(MediaSource::find(schema, vhost, app, stream), poller){ } -MediaPusher::~MediaPusher() { -} +MediaPusher::~MediaPusher() = default; static void setOnCreateSocket_l(const std::shared_ptr &delegate, const Socket::onCreateSocket &cb){ auto helper = dynamic_pointer_cast(delegate); diff --git a/src/Pusher/PusherBase.cpp b/src/Pusher/PusherBase.cpp index fa0d68e8..eddbf7b8 100644 --- a/src/Pusher/PusherBase.cpp +++ b/src/Pusher/PusherBase.cpp @@ -26,7 +26,7 @@ PusherBase::Ptr PusherBase::createPusher(const EventPoller::Ptr &poller, }); ptr->teardown(); }; - std::string prefix = FindField(url.data(), NULL, "://"); + std::string prefix = findSubString(url.data(), NULL, "://"); if (strcasecmp("rtsps",prefix.data()) == 0) { return PusherBase::Ptr(new TcpClientWithSSL(poller, std::dynamic_pointer_cast(src)), releasePusher); diff --git a/src/Record/HlsMaker.cpp b/src/Record/HlsMaker.cpp index df4cb8de..31f705b8 100644 --- a/src/Record/HlsMaker.cpp +++ b/src/Record/HlsMaker.cpp @@ -8,6 +8,7 @@ * may be found in the AUTHORS file in the root of the source tree. */ +#include #include "HlsMaker.h" #include "Common/config.h" @@ -15,85 +16,76 @@ using namespace std; namespace mediakit { -HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number, bool seg_keep) { +HlsMaker::HlsMaker(bool is_fmp4, float seg_duration, uint32_t seg_number, bool seg_keep) { + _is_fmp4 = is_fmp4; //最小允许设置为0,0个切片代表点播 _seg_number = seg_number; _seg_duration = seg_duration; _seg_keep = seg_keep; } -HlsMaker::~HlsMaker() { -} - - void HlsMaker::makeIndexFile(bool eof) { - char file_content[1024]; int maxSegmentDuration = 0; - for (auto &tp : _seg_dur_list) { int dur = std::get<0>(tp); if (dur > maxSegmentDuration) { maxSegmentDuration = dur; } } + auto index_seq = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL; - auto sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL; - - string m3u8; - if (_seg_number == 0) { - // 录像点播支持时移 - snprintf(file_content, sizeof(file_content), - "#EXTM3U\n" - "#EXT-X-PLAYLIST-TYPE:EVENT\n" - "#EXT-X-VERSION:4\n" - "#EXT-X-TARGETDURATION:%u\n" - "#EXT-X-MEDIA-SEQUENCE:%llu\n", - (maxSegmentDuration + 999) / 1000, - sequence); + string index_str; + index_str.reserve(2048); + index_str += "#EXTM3U\n"; + index_str += (_is_fmp4 ? "#EXT-X-VERSION:7\n" : "#EXT-X-VERSION:4\n"); + if (_seg_number == 0) { + index_str += "#EXT-X-PLAYLIST-TYPE:EVENT\n"; } else { - snprintf(file_content, sizeof(file_content), - "#EXTM3U\n" - "#EXT-X-VERSION:3\n" - "#EXT-X-ALLOW-CACHE:NO\n" - "#EXT-X-TARGETDURATION:%u\n" - "#EXT-X-MEDIA-SEQUENCE:%llu\n", - (maxSegmentDuration + 999) / 1000, - sequence); + index_str += "#EXT-X-ALLOW-CACHE:NO\n"; + } + index_str += "#EXT-X-TARGETDURATION:" + std::to_string((maxSegmentDuration + 999) / 1000) + "\n"; + index_str += "#EXT-X-MEDIA-SEQUENCE:" + std::to_string(index_seq) + "\n"; + if (_is_fmp4) { + index_str += "#EXT-X-MAP:URI=\"init.mp4\"\n"; } - - m3u8.assign(file_content); + stringstream ss; for (auto &tp : _seg_dur_list) { - snprintf(file_content, sizeof(file_content), "#EXTINF:%.3f,\n%s\n", std::get<0>(tp) / 1000.0, std::get<1>(tp).data()); - m3u8.append(file_content); + ss << "#EXTINF:" << std::setprecision(3) << std::get<0>(tp) / 1000.0 << ",\n" << std::get<1>(tp) << "\n"; } + index_str += ss.str(); if (eof) { - snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n"); - m3u8.append(file_content); + index_str += "#EXT-X-ENDLIST\n"; } - onWriteHls(m3u8); + onWriteHls(index_str); } +void HlsMaker::inputInitSegment(const char *data, size_t len) { + if (!_is_fmp4) { + throw std::invalid_argument("Only fmp4-hls can input init segment"); + } + onWriteInitSegment(data, len); +} -void HlsMaker::inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) { +void HlsMaker::inputData(const char *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) { if (data && len) { if (timestamp < _last_timestamp) { - //时间戳回退了,切片时长重新计时 - WarnL << "stamp reduce: " << _last_timestamp << " -> " << timestamp; + // 时间戳回退了,切片时长重新计时 + WarnL << "Timestamp reduce: " << _last_timestamp << " -> " << timestamp; _last_seg_timestamp = _last_timestamp = timestamp; } if (is_idr_fast_packet) { - //尝试切片ts + // 尝试切片ts addNewSegment(timestamp); } if (!_last_file_name.empty()) { - //存在切片才写入ts数据 - onWriteSegment((char *) data, len); + // 存在切片才写入ts数据 + onWriteSegment(data, len); _last_timestamp = timestamp; } } else { - //resetTracks时触发此逻辑 + // resetTracks时触发此逻辑 flushLastSegment(false); } } @@ -150,14 +142,18 @@ void HlsMaker::flushLastSegment(bool eof){ makeIndexFile(eof); } -bool HlsMaker::isLive() { +bool HlsMaker::isLive() const { return _seg_number != 0; } -bool HlsMaker::isKeep() { +bool HlsMaker::isKeep() const { return _seg_keep; } +bool HlsMaker::isFmp4() const { + return _is_fmp4; +} + void HlsMaker::clear() { _file_index = 0; _last_timestamp = 0; diff --git a/src/Record/HlsMaker.h b/src/Record/HlsMaker.h index be20ba1b..185a19d1 100644 --- a/src/Record/HlsMaker.h +++ b/src/Record/HlsMaker.h @@ -21,12 +21,13 @@ namespace mediakit { class HlsMaker { public: /** + * @param is_fmp4 使用fmp4还是mpegts * @param seg_duration 切片文件长度 * @param seg_number 切片个数 * @param seg_keep 是否保留切片文件 */ - HlsMaker(float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false); - virtual ~HlsMaker(); + HlsMaker(bool is_fmp4 = false, float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false); + virtual ~HlsMaker() = default; /** * 写入ts数据 @@ -35,17 +36,29 @@ public: * @param timestamp 毫秒时间戳 * @param is_idr_fast_packet 是否为关键帧第一个包 */ - void inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet); + void inputData(const char *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet); + + /** + * 输入fmp4 init segment + * @param data 数据 + * @param len 数据长度 + */ + void inputInitSegment(const char *data, size_t len); /** * 是否为直播 */ - bool isLive(); + bool isLive() const; /** * 是否保留切片文件 */ - bool isKeep(); + bool isKeep() const; + + /** + * 是否采用fmp4切片还是mpegts + */ + bool isFmp4() const; /** * 清空记录 @@ -66,6 +79,13 @@ protected: */ virtual void onDelSegment(uint64_t index) = 0; + /** + * 写init.mp4切片文件回调 + * @param data + * @param len + */ + virtual void onWriteInitSegment(const char *data, size_t len) = 0; + /** * 写ts切片文件回调 * @param data @@ -109,6 +129,7 @@ private: void addNewSegment(uint64_t timestamp); private: + bool _is_fmp4 = false; float _seg_duration = 0; uint32_t _seg_number = 0; bool _seg_keep = false; diff --git a/src/Record/HlsMakerImp.cpp b/src/Record/HlsMakerImp.cpp index 4a140ac6..2ba99f76 100644 --- a/src/Record/HlsMakerImp.cpp +++ b/src/Record/HlsMakerImp.cpp @@ -21,21 +21,14 @@ using namespace toolkit; namespace mediakit { -HlsMakerImp::HlsMakerImp(const string &m3u8_file, - const string ¶ms, - uint32_t bufSize, - float seg_duration, - uint32_t seg_number, - bool seg_keep):HlsMaker(seg_duration, seg_number, seg_keep) { +HlsMakerImp::HlsMakerImp(bool is_fmp4, const string &m3u8_file, const string ¶ms, uint32_t bufSize, float seg_duration, + uint32_t seg_number, bool seg_keep) : HlsMaker(is_fmp4, seg_duration, seg_number, seg_keep) { _poller = EventPollerPool::Instance().getPoller(); _path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/')); _path_hls = m3u8_file; _params = params; _buf_size = bufSize; - _file_buf.reset(new char[bufSize], [](char *ptr) { - delete[] ptr; - }); - + _file_buf.reset(new char[bufSize], [](char *ptr) { delete[] ptr; }); _info.folder = _path_prefix; } @@ -53,9 +46,9 @@ void HlsMakerImp::clearCache() { } void HlsMakerImp::clearCache(bool immediately, bool eof) { - //录制完了 + // 录制完了 flushLastSegment(eof); - if (!isLive()||isKeep()) { + if (!isLive() || isKeep()) { return; } @@ -63,7 +56,7 @@ void HlsMakerImp::clearCache(bool immediately, bool eof) { _file = nullptr; _segment_file_paths.clear(); - //hls直播才删除文件 + // hls直播才删除文件 GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec); if (!delay || immediately) { File::delete_file(_path_prefix.data()); @@ -82,7 +75,7 @@ string HlsMakerImp::onOpenSegment(uint64_t index) { auto strDate = getTimeStr("%Y-%m-%d"); auto strHour = getTimeStr("%H"); auto strTime = getTimeStr("%M-%S"); - segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << ".ts"; + segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << (isFmp4() ? ".mp4" : ".ts"); segment_path = _path_prefix + "/" + segment_name; if (isLive()) { _segment_file_paths.emplace(index, segment_path); @@ -90,14 +83,14 @@ string HlsMakerImp::onOpenSegment(uint64_t index) { } _file = makeFile(segment_path, true); - //保存本切片的元数据 + // 保存本切片的元数据 _info.start_time = ::time(NULL); _info.file_name = segment_name; _info.file_path = segment_path; _info.url = _info.app + "/" + _info.stream + "/" + segment_name; if (!_file) { - WarnL << "create file failed," << segment_path << " " << get_uv_errmsg(); + WarnL << "Create file failed," << segment_path << " " << get_uv_errmsg(); } if (_params.empty()) { return segment_name; @@ -114,6 +107,18 @@ void HlsMakerImp::onDelSegment(uint64_t index) { _segment_file_paths.erase(it); } +void HlsMakerImp::onWriteInitSegment(const char *data, size_t len) { + string init_seg_path = _path_prefix + "/init.mp4"; + _file = makeFile(init_seg_path, true); + + if (_file) { + fwrite(data, len, 1, _file.get()); + _file = nullptr; + } else { + WarnL << "Create file failed," << init_seg_path << " " << get_uv_errmsg(); + } +} + void HlsMakerImp::onWriteSegment(const char *data, size_t len) { if (_file) { fwrite(data, len, 1, _file.get()); @@ -132,20 +137,19 @@ void HlsMakerImp::onWriteHls(const std::string &data) { _media_src->setIndexFile(data); } } else { - WarnL << "create hls file failed," << _path_hls << " " << get_uv_errmsg(); + WarnL << "Create hls file failed," << _path_hls << " " << get_uv_errmsg(); } - //DebugL << "\r\n" << string(data,len); } void HlsMakerImp::onFlushLastSegment(uint64_t duration_ms) { - //关闭并flush文件到磁盘 + // 关闭并flush文件到磁盘 _file = nullptr; GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs); if (broadcastRecordTs) { _info.time_len = duration_ms / 1000.0f; _info.file_size = File::fileSize(_info.file_path.data()); - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordTs, _info); + NOTICE_EMIT(BroadcastRecordTsArgs, Broadcast::kBroadcastRecordTs, _info); } } @@ -166,11 +170,11 @@ void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const s _info.app = app; _info.stream = stream_id; _info.vhost = vhost; - _media_src = std::make_shared(_info); + _media_src = std::make_shared(isFmp4() ? HLS_FMP4_SCHEMA : HLS_SCHEMA, _info); } HlsMediaSource::Ptr HlsMakerImp::getMediaSource() const { return _media_src; } -}//namespace mediakit \ No newline at end of file +} // namespace mediakit \ No newline at end of file diff --git a/src/Record/HlsMakerImp.h b/src/Record/HlsMakerImp.h index 6b9acffa..07bef1d6 100644 --- a/src/Record/HlsMakerImp.h +++ b/src/Record/HlsMakerImp.h @@ -19,15 +19,10 @@ namespace mediakit { -class HlsMakerImp : public HlsMaker{ +class HlsMakerImp : public HlsMaker { public: - HlsMakerImp(const std::string &m3u8_file, - const std::string ¶ms, - uint32_t bufSize = 64 * 1024, - float seg_duration = 5, - uint32_t seg_number = 3, - bool seg_keep = false); - + HlsMakerImp(bool is_fmp4, const std::string &m3u8_file, const std::string ¶ms, uint32_t bufSize = 64 * 1024, + float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false); ~HlsMakerImp() override; /** @@ -52,6 +47,7 @@ public: protected: std::string onOpenSegment(uint64_t index) override ; void onDelSegment(uint64_t index) override; + void onWriteInitSegment(const char *data, size_t len) override; void onWriteSegment(const char *data, size_t len) override; void onWriteHls(const std::string &data) override; void onFlushLastSegment(uint64_t duration_ms) override; diff --git a/src/Record/HlsMediaSource.cpp b/src/Record/HlsMediaSource.cpp index d6d4f4b4..f3adb775 100644 --- a/src/Record/HlsMediaSource.cpp +++ b/src/Record/HlsMediaSource.cpp @@ -33,6 +33,12 @@ void HlsCookieData::addReaderCount() { // HlsMediaSource已经销毁 *added = false; }); + auto info = _sock_info; + _ring_reader->setGetInfoCB([info]() { + Any ret; + ret.set(info); + return ret; + }); } } } @@ -46,7 +52,11 @@ HlsCookieData::~HlsCookieData() { GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); uint64_t bytes = _bytes.load(); if (bytes >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, static_cast(*_sock_info)); + try { + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, *_sock_info); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } } } } diff --git a/src/Record/HlsMediaSource.h b/src/Record/HlsMediaSource.h index a1149a7f..1e0ecd14 100644 --- a/src/Record/HlsMediaSource.h +++ b/src/Record/HlsMediaSource.h @@ -25,7 +25,7 @@ public: using RingType = toolkit::RingBuffer; using Ptr = std::shared_ptr; - HlsMediaSource(const MediaTuple& tuple): MediaSource(HLS_SCHEMA, tuple) {} + HlsMediaSource(const std::string &schema, const MediaTuple &tuple) : MediaSource(schema, tuple) {} ~HlsMediaSource() override = default; /** @@ -58,6 +58,11 @@ public: void onSegmentSize(size_t bytes) { _speed[TrackVideo] += bytes; } + void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) override { + _ring->getInfoList(cb, on_change); + } + private: RingType::Ptr _ring; std::string _index_file; diff --git a/src/Record/HlsRecorder.h b/src/Record/HlsRecorder.h index 1ec13f86..aa1a4960 100644 --- a/src/Record/HlsRecorder.h +++ b/src/Record/HlsRecorder.h @@ -13,27 +13,27 @@ #include "HlsMakerImp.h" #include "MPEG.h" +#include "MP4Muxer.h" #include "Common/config.h" namespace mediakit { -class HlsRecorder final : public MediaSourceEventInterceptor, public MpegMuxer, public std::enable_shared_from_this { +template +class HlsRecorderBase : public MediaSourceEventInterceptor, public Muxer, public std::enable_shared_from_this > { public: - using Ptr = std::shared_ptr; - - HlsRecorder(const std::string &m3u8_file, const std::string ¶ms, const ProtocolOption &option) : MpegMuxer(false) { + HlsRecorderBase(bool is_fmp4, const std::string &m3u8_file, const std::string ¶ms, const ProtocolOption &option) { GET_CONFIG(uint32_t, hlsNum, Hls::kSegmentNum); GET_CONFIG(bool, hlsKeep, Hls::kSegmentKeep); GET_CONFIG(uint32_t, hlsBufSize, Hls::kFileBufSize); GET_CONFIG(float, hlsDuration, Hls::kSegmentDuration); _option = option; - _hls = std::make_shared(m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep); - //清空上次的残余文件 + _hls = std::make_shared(is_fmp4, m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep); + // 清空上次的残余文件 _hls->clearCache(); } - ~HlsRecorder() { MpegMuxer::flush(); }; + ~HlsRecorderBase() override = default; void setMediaSource(const MediaTuple& tuple) { _hls->setMediaSource(tuple.vhost, tuple.app, tuple.stream); @@ -41,7 +41,7 @@ public: void setListener(const std::weak_ptr &listener) { setDelegate(listener); - _hls->getMediaSource()->setListener(shared_from_this()); + _hls->getMediaSource()->setListener(this->shared_from_this()); } int readerCount() { return _hls->getMediaSource()->readerCount(); } @@ -64,7 +64,7 @@ public: _hls->getMediaSource()->setIndexFile(""); } if (_enabled || !_option.hls_demand) { - return MpegMuxer::inputFrame(frame); + return Muxer::inputFrame(frame); } return false; } @@ -74,20 +74,54 @@ public: return _option.hls_demand ? (_clear_cache ? true : _enabled) : true; } -private: - void onWrite(std::shared_ptr buffer, uint64_t timestamp, bool key_pos) override { - if (!buffer) { - _hls->inputData(nullptr, 0, timestamp, key_pos); - } else { - _hls->inputData(buffer->data(), buffer->size(), timestamp, key_pos); - } - } - -private: +protected: bool _enabled = true; bool _clear_cache = false; ProtocolOption _option; std::shared_ptr _hls; }; + +class HlsRecorder final : public HlsRecorderBase { +public: + using Ptr = std::shared_ptr; + template + HlsRecorder(ARGS && ...args) : HlsRecorderBase(false, std::forward(args)...) {} + ~HlsRecorder() override { this->flush(); } + +private: + void onWrite(std::shared_ptr buffer, uint64_t timestamp, bool key_pos) override { + if (!buffer) { + // reset tracks + _hls->inputData(nullptr, 0, timestamp, key_pos); + } else { + _hls->inputData(buffer->data(), buffer->size(), timestamp, key_pos); + } + } +}; + +class HlsFMP4Recorder final : public HlsRecorderBase { +public: + using Ptr = std::shared_ptr; + template + HlsFMP4Recorder(ARGS && ...args) : HlsRecorderBase(true, std::forward(args)...) {} + ~HlsFMP4Recorder() override { this->flush(); } + + void addTrackCompleted() override { + HlsRecorderBase::addTrackCompleted(); + auto data = getInitSegment(); + _hls->inputInitSegment(data.data(), data.size()); + } + +private: + void onSegmentData(std::string buffer, uint64_t timestamp, bool key_pos) override { + if (buffer.empty()) { + // reset tracks + _hls->inputData(nullptr, 0, timestamp, key_pos); + } else { + _hls->inputData((char *)buffer.data(), buffer.size(), timestamp, key_pos); + } + } +}; + }//namespace mediakit #endif //HLSRECORDER_H diff --git a/src/Record/MP4.cpp b/src/Record/MP4.cpp index 8d92c256..10f1b0ee 100644 --- a/src/Record/MP4.cpp +++ b/src/Record/MP4.cpp @@ -8,7 +8,8 @@ * may be found in the AUTHORS file in the root of the source tree. */ -#ifdef ENABLE_MP4 +#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) + #include "MP4.h" #include "Util/File.h" #include "Util/logger.h" @@ -176,4 +177,4 @@ int MP4FileMemory::onWrite(const void *data, size_t bytes){ } }//namespace mediakit -#endif //NABLE_MP4RECORD +#endif // defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) diff --git a/src/Record/MP4.h b/src/Record/MP4.h index 14bc1ca7..63e7af9c 100644 --- a/src/Record/MP4.h +++ b/src/Record/MP4.h @@ -11,7 +11,7 @@ #ifndef ZLMEDIAKIT_MP4_H #define ZLMEDIAKIT_MP4_H -#ifdef ENABLE_MP4 +#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) #include #include @@ -136,5 +136,5 @@ private: }; }//namespace mediakit -#endif //NABLE_MP4RECORD +#endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) #endif //ZLMEDIAKIT_MP4_H diff --git a/src/Record/MP4Demuxer.cpp b/src/Record/MP4Demuxer.cpp index a1c657fd..87936137 100644 --- a/src/Record/MP4Demuxer.cpp +++ b/src/Record/MP4Demuxer.cpp @@ -16,12 +16,14 @@ #include "Extension/AAC.h" #include "Extension/G711.h" #include "Extension/Opus.h" -using namespace toolkit; +#include "Extension/JPEG.h" + using namespace std; +using namespace toolkit; namespace mediakit { -MP4Demuxer::MP4Demuxer() {} +MP4Demuxer::MP4Demuxer() = default; MP4Demuxer::~MP4Demuxer() { closeMP4(); @@ -102,11 +104,12 @@ void MP4Demuxer::onVideoTrack(uint32_t track, uint8_t object, int width, int hei uint8_t config[1024 * 10] = {0}; int size = mpeg4_avc_to_nalu(&avc, config, sizeof(config)); if (size > 0) { - video->inputFrame(std::make_shared((char *)config, size, 0, 4)); + video->inputFrame(std::make_shared((char *)config, size, 0, 0,4)); } } - } break; + } + case MOV_OBJECT_HEVC: { auto video = std::make_shared(); _track_to_codec.emplace(track,video); @@ -117,14 +120,19 @@ void MP4Demuxer::onVideoTrack(uint32_t track, uint8_t object, int width, int hei uint8_t config[1024 * 10] = {0}; int size = mpeg4_hevc_to_nalu(&hevc, config, sizeof(config)); if (size > 0) { - video->inputFrame(std::make_shared((char *) config, size, 0, 4)); + video->inputFrame(std::make_shared((char *) config, size, 0, 0,4)); } } + break; } + + case MOV_OBJECT_JPEG: { + auto video = std::make_shared(); + _track_to_codec.emplace(track,video); break; - default: - WarnL << "不支持该编码类型的MP4,已忽略:" << getObjectName(object); - break; + } + + default: WarnL << "不支持该编码类型的MP4,已忽略:" << getObjectName(object); break; } } @@ -243,11 +251,16 @@ Frame::Ptr MP4Demuxer::makeFrame(uint32_t track_id, const Buffer::Ptr &buf, int6 break; } + case CodecJPEG: { + ret = std::make_shared(buf, (uint64_t)dts, 0, DATA_OFFSET); + break; + } + case CodecAAC: { AACTrack::Ptr track = dynamic_pointer_cast(it->second); assert(track); //加上adts头 - dumpAacConfig(track->getAacCfg(), buf->size() - DATA_OFFSET, (uint8_t *) buf->data() + (DATA_OFFSET - ADTS_HEADER_LEN), ADTS_HEADER_LEN); + dumpAacConfig(track->getConfig(), buf->size() - DATA_OFFSET, (uint8_t *) buf->data() + (DATA_OFFSET - ADTS_HEADER_LEN), ADTS_HEADER_LEN); ret = std::make_shared >(buf, (uint64_t)dts, (uint64_t)pts, ADTS_HEADER_LEN, DATA_OFFSET - ADTS_HEADER_LEN, codec); break; } @@ -283,4 +296,4 @@ uint64_t MP4Demuxer::getDurationMS() const { } }//namespace mediakit -#endif// ENABLE_MP4 \ No newline at end of file +#endif// ENABLE_MP4 diff --git a/src/Record/MP4Muxer.cpp b/src/Record/MP4Muxer.cpp index cde87b2c..c79cc132 100644 --- a/src/Record/MP4Muxer.cpp +++ b/src/Record/MP4Muxer.cpp @@ -8,7 +8,7 @@ * may be found in the AUTHORS file in the root of the source tree. */ -#ifdef ENABLE_MP4 +#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) #include "MP4Muxer.h" #include "Extension/AAC.h" @@ -234,8 +234,8 @@ bool MP4MuxerInterface::addTrack(const Track::Ptr &track) { audio_track->getAudioChannel(), audio_track->getAudioSampleBit() * audio_track->getAudioChannel(), audio_track->getAudioSampleRate(), - audio_track->getAacCfg().data(), - audio_track->getAacCfg().size()); + audio_track->getConfig().data(), + audio_track->getConfig().size()); if (track_id < 0) { WarnL << "添加AAC Track失败:" << track_id; return false; @@ -377,24 +377,24 @@ bool MP4MuxerMemory::inputFrame(const Frame::Ptr &frame) { return false; } - bool key_frame = frame->keyFrame(); - - //flush切片 + // flush切片 saveSegment(); auto data = _memory_file->getAndClearMemory(); if (!data.empty()) { - //输出切片数据 - onSegmentData(std::move(data), frame->dts(), _key_frame); + // 输出切片数据 + onSegmentData(std::move(data), _last_dst, _key_frame); _key_frame = false; } - if (key_frame) { + if (frame->keyFrame()) { _key_frame = true; } - + if (frame->getTrackType() == TrackVideo || !haveVideo()) { + _last_dst = frame->dts(); + } return MP4MuxerInterface::inputFrame(frame); } }//namespace mediakit -#endif//#ifdef ENABLE_MP4 +#endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) diff --git a/src/Record/MP4Muxer.h b/src/Record/MP4Muxer.h index 83b939f6..3b48ad7c 100644 --- a/src/Record/MP4Muxer.h +++ b/src/Record/MP4Muxer.h @@ -11,13 +11,13 @@ #ifndef ZLMEDIAKIT_MP4MUXER_H #define ZLMEDIAKIT_MP4MUXER_H -#ifdef ENABLE_MP4 +#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) #include "Common/MediaSink.h" #include "Common/Stamp.h" #include "MP4.h" -namespace mediakit{ +namespace mediakit { class MP4MuxerInterface : public MediaSinkInterface { public: @@ -147,11 +147,39 @@ protected: private: bool _key_frame = false; + uint64_t _last_dst = 0; std::string _init_segment; MP4FileMemory::Ptr _memory_file; }; +} // namespace mediakit -}//namespace mediakit -#endif//#ifdef ENABLE_MP4 +#else + +#include "Common/MediaSink.h" + +namespace mediakit { + +class MP4MuxerMemory : public MediaSinkInterface { +public: + MP4MuxerMemory() = default; + ~MP4MuxerMemory() override = default; + + bool addTrack(const Track::Ptr & track) override { return false; } + bool inputFrame(const Frame::Ptr &frame) override { return false; } + const std::string &getInitSegment() { static std::string kNull; return kNull; }; + +protected: + /** + * 输出fmp4切片回调函数 + * @param std::string 切片内容 + * @param stamp 切片末尾时间戳 + * @param key_frame 是否有关键帧 + */ + virtual void onSegmentData(std::string string, uint64_t stamp, bool key_frame) = 0; +}; + +} // namespace mediakit + +#endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) #endif //ZLMEDIAKIT_MP4MUXER_H diff --git a/src/Record/MP4Reader.cpp b/src/Record/MP4Reader.cpp index 2d4105e0..2def3781 100644 --- a/src/Record/MP4Reader.cpp +++ b/src/Record/MP4Reader.cpp @@ -180,12 +180,14 @@ bool MP4Reader::pause(MediaSource &sender, bool pause) { } bool MP4Reader::speed(MediaSource &sender, float speed) { - if (speed < 0.1 && speed > 20) { + if (speed < 0.1 || speed > 20) { WarnL << "播放速度取值范围非法:" << speed; return false; } - //设置播放速度后应该恢复播放 - pause(sender, false); + //_seek_ticker重置,赋值_seek_to + setCurrentStamp(getCurrentStamp()); + // 设置播放速度后应该恢复播放 + _paused = false; if (_speed == speed) { return true; } diff --git a/src/Record/MP4Recorder.cpp b/src/Record/MP4Recorder.cpp index fd7c8955..5373b8a1 100644 --- a/src/Record/MP4Recorder.cpp +++ b/src/Record/MP4Recorder.cpp @@ -41,19 +41,20 @@ MP4Recorder::~MP4Recorder() { void MP4Recorder::createFile() { closeFile(); auto date = getTimeStr("%Y-%m-%d"); - auto time = getTimeStr("%H-%M-%S"); - auto full_path_tmp = _folder_path + date + "/." + time + ".mp4"; - auto full_path = _folder_path + date + "/" + time + ".mp4"; + auto file_name = getTimeStr("%H-%M-%S") + "-" + std::to_string(_file_index++) + ".mp4"; + auto full_path = _folder_path + date + "/" + file_name; + auto full_path_tmp = _folder_path + date + "/." + file_name; /////record 业务逻辑////// _info.start_time = ::time(NULL); - _info.file_name = time + ".mp4"; + _info.file_name = file_name; _info.file_path = full_path; GET_CONFIG(string, appName, Record::kAppName); - _info.url = appName + "/" + _info.app + "/" + _info.stream + "/" + date + "/" + time + ".mp4"; + _info.url = appName + "/" + _info.app + "/" + _info.stream + "/" + date + "/" + file_name; try { _muxer = std::make_shared(); + TraceL << "Open tmp mp4 file: " << full_path_tmp; _muxer->openMP4(full_path_tmp); for (auto &track :_tracks) { //添加track @@ -71,10 +72,13 @@ void MP4Recorder::asyncClose() { auto full_path_tmp = _full_path_tmp; auto full_path = _full_path; auto info = _info; + TraceL << "Start close tmp mp4 file: " << full_path_tmp; WorkThreadPool::Instance().getExecutor()->async([muxer, full_path_tmp, full_path, info]() mutable { info.time_len = muxer->getDuration() / 1000.0f; // 关闭mp4可能非常耗时,所以要放在后台线程执行 + TraceL << "Closing tmp mp4 file: " << full_path_tmp; muxer->closeMP4(); + TraceL << "Closed tmp mp4 file: " << full_path_tmp; if (!full_path_tmp.empty()) { // 获取文件大小 info.file_size = File::fileSize(full_path_tmp.data()); @@ -86,8 +90,9 @@ void MP4Recorder::asyncClose() { // 临时文件名改成正式文件名,防止mp4未完成时被访问 rename(full_path_tmp.data(), full_path.data()); } + TraceL << "Emit mp4 record event: " << full_path; //触发mp4录制切片生成事件 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordMP4, info); + NOTICE_EMIT(BroadcastRecordMP4Args, Broadcast::kBroadcastRecordMP4, info); }); } diff --git a/src/Record/MP4Recorder.h b/src/Record/MP4Recorder.h index 69175238..3e0c8013 100644 --- a/src/Record/MP4Recorder.h +++ b/src/Record/MP4Recorder.h @@ -57,13 +57,14 @@ private: private: bool _have_video = false; size_t _max_second; + uint64_t _last_dts = 0; + uint64_t _file_index = 0; std::string _folder_path; std::string _full_path; std::string _full_path_tmp; RecordInfo _info; MP4Muxer::Ptr _muxer; std::list _tracks; - uint64_t _last_dts = 0; }; #endif ///ENABLE_MP4 diff --git a/src/Record/MPEG.cpp b/src/Record/MPEG.cpp index 985cf98f..153fd796 100644 --- a/src/Record/MPEG.cpp +++ b/src/Record/MPEG.cpp @@ -30,19 +30,18 @@ MpegMuxer::~MpegMuxer() { releaseContext(); } -#define XX(name, type, value, str, mpeg_id) \ - case name : { \ - if (mpeg_id == PSI_STREAM_RESERVED) { \ - break; \ - } \ - _codec_to_trackid[track->getCodecId()] = mpeg_muxer_add_stream((::mpeg_muxer_t *)_context, mpeg_id, nullptr, 0); \ - return true; \ +#define XX(name, type, value, str, mpeg_id) \ + case name: { \ + if (mpeg_id == PSI_STREAM_RESERVED) { \ + break; \ + } \ + if (track->getTrackType() == TrackVideo) { \ + _have_video = true; \ + } \ + _codec_to_trackid[track->getCodecId()] = mpeg_muxer_add_stream((::mpeg_muxer_t *)_context, mpeg_id, nullptr, 0); \ + return true; \ } - bool MpegMuxer::addTrack(const Track::Ptr &track) { - if (track->getTrackType() == TrackVideo) { - _have_video = true; - } switch (track->getCodecId()) { CODEC_MAP(XX) default: break; @@ -85,6 +84,11 @@ bool MpegMuxer::inputFrame(const Frame::Ptr &frame) { //没有视频时,才以音频时间戳为TS的时间戳 _timestamp = frame->dts(); } + + if(frame->getTrackType() == TrackType::TrackVideo){ + _key_pos = frame->keyFrame(); + _timestamp = frame->dts(); + } _max_cache_size = 512 + 1.2 * frame->size(); mpeg_muxer_input((::mpeg_muxer_t *)_context, track_id, frame->keyFrame() ? 0x0001 : 0, frame->pts() * 90LL, frame->dts() * 90LL, frame->data(), frame->size()); flushCache(); diff --git a/src/Record/MPEG.h b/src/Record/MPEG.h index 1dd69d48..567d1b74 100644 --- a/src/Record/MPEG.h +++ b/src/Record/MPEG.h @@ -25,7 +25,7 @@ namespace mediakit { //该类用于产生MPEG-TS/MPEG-PS class MpegMuxer : public MediaSinkInterface { public: - MpegMuxer(bool is_ps); + MpegMuxer(bool is_ps = false); ~MpegMuxer() override; /** @@ -86,7 +86,7 @@ namespace mediakit { class MpegMuxer : public MediaSinkInterface { public: - MpegMuxer(bool is_ps) {} + MpegMuxer(bool is_ps = false) {} ~MpegMuxer() override = default; bool addTrack(const Track::Ptr &track) override { return false; } void resetTracks() override {} diff --git a/src/Record/Recorder.cpp b/src/Record/Recorder.cpp index d9cd75f7..6183ff1c 100644 --- a/src/Record/Recorder.cpp +++ b/src/Record/Recorder.cpp @@ -14,7 +14,8 @@ #include "Common/MediaSource.h" #include "MP4Recorder.h" #include "HlsRecorder.h" -#include "Util/File.h" +#include "FMP4/FMP4MediaSourceMuxer.h" +#include "TS/TSMediaSourceMuxer.h" using namespace std; using namespace toolkit; @@ -53,6 +54,20 @@ string Recorder::getRecordPath(Recorder::type type, const MediaTuple& tuple, con } return File::absolutePath(mp4FilePath, recordPath); } + case Recorder::type_hls_fmp4: { + GET_CONFIG(string, hlsPath, Protocol::kHlsSavePath); + string m3u8FilePath; + if (enableVhost) { + m3u8FilePath = tuple.shortUrl() + "/hls.fmp4.m3u8"; + } else { + m3u8FilePath = tuple.app + "/" + tuple.stream + "/hls.fmp4.m3u8"; + } + // Here we use the customized file path. + if (!customized_path.empty()) { + return File::absolutePath(m3u8FilePath, customized_path); + } + return File::absolutePath(m3u8FilePath, hlsPath); + } default: return ""; } @@ -82,6 +97,34 @@ std::shared_ptr Recorder::createRecorder(type type, const Me #endif } + case Recorder::type_hls_fmp4: { +#if defined(ENABLE_HLS_FMP4) + auto path = Recorder::getRecordPath(type, tuple, option.hls_save_path); + GET_CONFIG(bool, enable_vhost, General::kEnableVhost); + auto ret = std::make_shared(path, enable_vhost ? string(VHOST_KEY) + "=" + tuple.vhost : "", option); + ret->setMediaSource(tuple); + return ret; +#else + throw std::invalid_argument("hls.fmp4相关功能未打开,请开启ENABLE_HLS_FMP4宏后编译再测试"); +#endif + } + + case Recorder::type_fmp4: { +#if defined(ENABLE_HLS_FMP4) || defined(ENABLE_MP4) + return std::make_shared(tuple, option); +#else + throw std::invalid_argument("fmp4相关功能未打开,请开启ENABLE_HLS_FMP4或ENABLE_MP4宏后编译再测试"); +#endif + } + + case Recorder::type_ts: { +#if defined(ENABLE_HLS) || defined(ENABLE_RTPPROXY) + return std::make_shared(tuple, option); +#else + throw std::invalid_argument("mpegts相关功能未打开,请开启ENABLE_HLS或ENABLE_RTPPROXY宏后编译再测试"); +#endif + } + default: throw std::invalid_argument("未知的录制类型"); } } diff --git a/src/Record/Recorder.h b/src/Record/Recorder.h index 7ccbc04e..42763b32 100644 --- a/src/Record/Recorder.h +++ b/src/Record/Recorder.h @@ -44,7 +44,13 @@ public: // 录制hls type_hls = 0, // 录制MP4 - type_mp4 = 1 + type_mp4 = 1, + // 录制hls.fmp4 + type_hls_fmp4 = 2, + // fmp4直播 + type_fmp4 = 3, + // ts直播 + type_ts = 4, } type; /** diff --git a/src/Rtcp/Rtcp.h b/src/Rtcp/Rtcp.h index 9275506c..3511f4a6 100644 --- a/src/Rtcp/Rtcp.h +++ b/src/Rtcp/Rtcp.h @@ -19,9 +19,7 @@ namespace mediakit { -#if defined(_WIN32) #pragma pack(push, 1) -#endif // defined(_WIN32) // http://www.networksorcery.com/enp/protocol/rtcp.htm #define RTCP_PT_MAP(XX) \ @@ -235,7 +233,7 @@ private: */ void net2Host(size_t size); -} PACKED; +}; ///////////////////////////////////////////////////////////////////////////// @@ -272,7 +270,7 @@ private: * 网络字节序转换为主机字节序 */ void net2Host(); -} PACKED; +}; /* * 6.4.1 SR: Sender Report RTCP Packet @@ -371,7 +369,7 @@ private: * @param size 字节长度,防止内存越界 */ void net2Host(size_t size); -} PACKED; +}; ///////////////////////////////////////////////////////////////////////////// @@ -441,7 +439,7 @@ private: */ std::string dumpString() const; -} PACKED; +}; ///////////////////////////////////////////////////////////////////////////// @@ -512,7 +510,7 @@ private: * 网络字节序转换为主机字节序 */ void net2Host(); -} PACKED; +}; // Source description class RtcpSdes : public RtcpHeader { @@ -548,7 +546,7 @@ private: * @param size 字节长度,防止内存越界 */ void net2Host(size_t size); -} PACKED; +}; // https://tools.ietf.org/html/rfc4585#section-6.1 // 6.1. Common Packet Format for Feedback Messages @@ -624,7 +622,7 @@ private: private: static std::shared_ptr create_l(RtcpType type, int fmt, const void *fci, size_t fci_len); -} PACKED; +}; // BYE /* @@ -684,7 +682,7 @@ private: * @param size 字节长度,防止内存越界 */ void net2Host(size_t size); -} PACKED; +}; /* 0 1 2 3 @@ -738,7 +736,7 @@ private: */ void net2Host(size_t size); -} PACKED; +}; /* @@ -777,7 +775,7 @@ private: * @param size 字节长度,防止内存越界 */ void net2Host(); -} PACKED; +}; class RtcpXRDLRR : public RtcpHeader { public: @@ -814,11 +812,9 @@ private: */ void net2Host(size_t size); -} PACKED; +}; -#if defined(_WIN32) #pragma pack(pop) -#endif // defined(_WIN32) } // namespace mediakit #endif // ZLMEDIAKIT_RTCP_H diff --git a/src/Rtcp/RtcpFCI.cpp b/src/Rtcp/RtcpFCI.cpp index c6508217..8ed27670 100644 --- a/src/Rtcp/RtcpFCI.cpp +++ b/src/Rtcp/RtcpFCI.cpp @@ -240,7 +240,7 @@ public: RunLengthChunk(SymbolStatus status, uint16_t run_length); // 打印本对象 string dumpString() const; -} PACKED; +}; RunLengthChunk::RunLengthChunk(SymbolStatus status, uint16_t run_length) { type = 0; @@ -291,7 +291,7 @@ public: StatusVecChunk(bool symbol_bit, const vector &status); // 打印本对象 string dumpString() const; -} PACKED; +}; StatusVecChunk::StatusVecChunk(bool symbol_bit, const vector &status) { CHECK(status.size() << symbol_bit <= 14); diff --git a/src/Rtcp/RtcpFCI.h b/src/Rtcp/RtcpFCI.h index 745dc16a..5ce36aa2 100644 --- a/src/Rtcp/RtcpFCI.h +++ b/src/Rtcp/RtcpFCI.h @@ -55,7 +55,7 @@ public: private: uint32_t data; -} PACKED; +}; #if 0 //PSFB fmt = 3 @@ -97,7 +97,7 @@ public: uint8_t padding; static size_t constexpr kSize = 8; -} PACKED; +}; #endif // PSFB fmt = 4 @@ -125,7 +125,7 @@ private: uint32_t ssrc; uint8_t seq_number; uint8_t reserved[3]; -} PACKED; +}; #if 0 //PSFB fmt = 5 @@ -147,7 +147,7 @@ public: private: uint8_t data[kSize]; -} PACKED; +}; //PSFB fmt = 6 //https://tools.ietf.org/html/rfc5104#section-4.3.2.1 @@ -160,7 +160,7 @@ private: // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ class FCI_TSTN : public FCI_TSTR{ -} PACKED; +}; //PSFB fmt = 7 //https://tools.ietf.org/html/rfc5104#section-4.3.4.1 @@ -183,7 +183,7 @@ public: private: uint8_t data[kSize]; -} PACKED; +}; #endif @@ -233,7 +233,7 @@ private: // SSRC feedback (32 bits) Consists of one or more SSRC entries which // this feedback message applies to. uint32_t ssrc_feedback[1]; -} PACKED; +}; /////////////////////////////////////////// RTPFB //////////////////////////////////////////////////// @@ -265,7 +265,7 @@ private: uint16_t pid; // bitmask of following lost packets (BLP): 16 bits uint16_t blp; -} PACKED; +}; #if 0 //RTPFB fmt = 3 @@ -300,7 +300,7 @@ private: // to the description in section 4.2.1.2. The value is an // unsigned integer [0..511]. uint32_t max_tbr; -} PACKED; +}; //RTPFB fmt = 4 // https://tools.ietf.org/html/rfc5104#section-4.2.2.1 @@ -314,7 +314,7 @@ private: class FCI_TMMBN : public FCI_TMMBR{ public: -} PACKED; +}; #endif enum class SymbolStatus : uint8_t { @@ -374,7 +374,7 @@ private: uint8_t ref_time[3]; // feedback packet count,反馈包号,本包是第几个transport-cc包,每次加1 | uint8_t fb_pkt_count; -} PACKED; +}; } // namespace mediakit #endif // ZLMEDIAKIT_RTCPFCI_H diff --git a/src/Rtmp/FlvMuxer.cpp b/src/Rtmp/FlvMuxer.cpp index bd64acf9..b2d1a6ad 100644 --- a/src/Rtmp/FlvMuxer.cpp +++ b/src/Rtmp/FlvMuxer.cpp @@ -46,7 +46,11 @@ void FlvMuxer::start(const EventPoller::Ptr &poller, const RtmpMediaSource::Ptr std::weak_ptr weak_self = getSharedPtr(); media->pause(false); _ring_reader = media->getRing()->attach(poller); - _ring_reader->setGetInfoCB([weak_self]() { return dynamic_pointer_cast(weak_self.lock()); }); + _ring_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(dynamic_pointer_cast(weak_self.lock())); + return ret; + }); _ring_reader->setDetachCB([weak_self]() { auto strong_self = weak_self.lock(); if (!strong_self) { @@ -97,26 +101,22 @@ void FlvMuxer::onWriteFlvHeader(const RtmpMediaSource::Ptr &src) { header->flv[0] = 'F'; header->flv[1] = 'L'; header->flv[2] = 'V'; - header->version = 1; - header->length = htonl(9); + header->version = FLVHeader::kFlvVersion; + header->length = htonl(FLVHeader::kFlvHeaderLength); header->have_video = src->haveVideo(); header->have_audio = src->haveAudio(); + //memset时已经赋值为0 + //header->previous_tag_size0 = 0; //flv header onWrite(buffer, false); - //PreviousTagSize0 Always 0 - auto size = htonl(0); - onWrite(obtainBuffer((char *) &size, 4), false); - - auto &metadata = src->getMetaData(); - if (metadata) { - //在有metadata的情况下才发送metadata - //其实metadata没什么用,有些推流器不产生metadata + // metadata + src->getMetaData([&](const AMFValue &metadata) { AMFEncoder invoke; invoke << "onMetaData" << metadata; onWriteFlvTag(MSG_DATA, std::make_shared(invoke.data()), 0, false); - } + }); //config frame src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) { diff --git a/src/Rtmp/FlvPlayer.cpp b/src/Rtmp/FlvPlayer.cpp new file mode 100644 index 00000000..0095eb04 --- /dev/null +++ b/src/Rtmp/FlvPlayer.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/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 "FlvPlayer.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +FlvPlayer::FlvPlayer(const EventPoller::Ptr &poller) { + setPoller(poller); +} + +void FlvPlayer::play(const string &url) { + TraceL << "play http-flv: " << url; + _play_result = false; + setHeaderTimeout((*this)[Client::kTimeoutMS].as()); + setBodyTimeout((*this)[Client::kMediaTimeoutMS].as()); + setMethod("GET"); + sendRequest(url); +} + +void FlvPlayer::onResponseHeader(const string &status, const HttpClient::HttpHeader &header) { + if (status != "200" && status != "206") { + // http状态码不符合预期 + throw invalid_argument("bad http status code:" + status); + } + + auto content_type = const_cast(header)["Content-Type"]; + if (content_type.find("video/x-flv") != 0) { + throw invalid_argument("content type not http-flv: " + content_type); + } +} + +void FlvPlayer::teardown() { + HttpClientImp::shutdown(); +} + +void FlvPlayer::onResponseCompleted(const SockException &ex) { + if (!_play_result) { + _play_result = true; + onPlayResult(ex); + } else { + onShutdown(ex); + } +} + +void FlvPlayer::onResponseBody(const char *buf, size_t size) { + if (!_benchmark_mode) { + // 性能测试模式不做数据解析,节省cpu + FlvSplitter::input(buf, size); + } +} + +bool FlvPlayer::onRecvMetadata(const AMFValue &metadata) { + return onMetadata(metadata); +} + +void FlvPlayer::onRecvRtmpPacket(RtmpPacket::Ptr packet) { + if (!_play_result && !packet->isConfigFrame()) { + _play_result = true; + _benchmark_mode = (*this)[Client::kBenchmarkMode].as(); + onPlayResult(SockException(Err_success, "play http-flv success")); + } + onRtmpPacket(std::move(packet)); +} + +}//mediakit \ No newline at end of file diff --git a/src/Rtmp/FlvPlayer.h b/src/Rtmp/FlvPlayer.h new file mode 100644 index 00000000..2e79dac2 --- /dev/null +++ b/src/Rtmp/FlvPlayer.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/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_FLVPLAYER_H +#define ZLMEDIAKIT_FLVPLAYER_H + +#include "FlvSplitter.h" +#include "Http/HttpClientImp.h" +#include "Player/PlayerBase.h" + +namespace mediakit { + +class FlvPlayer : public PlayerBase, public HttpClientImp, private FlvSplitter { +public: + FlvPlayer(const toolkit::EventPoller::Ptr &poller); + ~FlvPlayer() override = default; + + void play(const std::string &url) override; + void teardown() override; + +protected: + void onResponseHeader(const std::string &status, const HttpHeader &header) override; + void onResponseCompleted(const toolkit::SockException &ex) override; + void onResponseBody(const char *buf, size_t size) override; + +protected: + virtual void onRtmpPacket(RtmpPacket::Ptr packet) = 0; + virtual bool onMetadata(const AMFValue &metadata) = 0; + +private: + bool onRecvMetadata(const AMFValue &metadata) override; + void onRecvRtmpPacket(RtmpPacket::Ptr packet) override; + +private: + bool _play_result = false; + bool _benchmark_mode = false; +}; + +using FlvPlayerImp = FlvPlayerBase; + +}//namespace mediakit +#endif //ZLMEDIAKIT_FLVPLAYER_H diff --git a/src/Rtmp/FlvSplitter.cpp b/src/Rtmp/FlvSplitter.cpp new file mode 100644 index 00000000..3e99d9ef --- /dev/null +++ b/src/Rtmp/FlvSplitter.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/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 "FlvSplitter.h" +#include "utils.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +const char *FlvSplitter::onSearchPacketTail(const char *data, size_t len) { + if (!_flv_started) { + //还没获取到flv头 + if (len < sizeof(FLVHeader)) { + //数据不够 + return nullptr; + } + return data + sizeof(FLVHeader); + } + + //获取到flv头,处理tag数据 + if (len < sizeof(RtmpTagHeader)) { + //数据不够 + return nullptr; + } + return data + sizeof(RtmpTagHeader); +} + +ssize_t FlvSplitter::onRecvHeader(const char *data, size_t len) { + if (!_flv_started) { + //获取到flv头了 + auto header = reinterpret_cast(data); + if (memcmp(header->flv, "FLV", 3)) { + throw std::invalid_argument("不是flv容器格式!"); + } + if (header->version != FLVHeader::kFlvVersion) { + throw std::invalid_argument("flv头中version字段不正确"); + } + if (!header->have_video && !header->have_audio) { + throw std::invalid_argument("flv头中声明音频和视频都不存在"); + } + if (FLVHeader::kFlvHeaderLength != ntohl(header->length)) { + throw std::invalid_argument("flv头中length字段非法"); + } + if (0 != ntohl(header->previous_tag_size0)) { + throw std::invalid_argument("flv头中previous tag size字段非法"); + } + onRecvFlvHeader(*header); + _flv_started = true; + return 0; + } + + //获取到flv头,处理tag数据 + auto tag = reinterpret_cast(data); + auto data_size = load_be24(tag->data_size); + _type = tag->type; + _time_stamp = load_be24(tag->timestamp); + _time_stamp |= (tag->timestamp_ex << 24); + return data_size + 4/*PreviousTagSize*/; +} + +void FlvSplitter::onRecvContent(const char *data, size_t len) { + len -= 4; + auto previous_tag_size = load_be32(data + len); + if (len != previous_tag_size - sizeof(RtmpTagHeader)) { + WarnL << "flv previous tag size 字段非法:" << len << " != " << previous_tag_size - sizeof(RtmpTagHeader); + } + RtmpPacket::Ptr packet; + switch (_type) { + case MSG_AUDIO : { + packet = RtmpPacket::create(); + packet->chunk_id = CHUNK_AUDIO; + packet->stream_index = STREAM_MEDIA; + break; + } + case MSG_VIDEO: { + packet = RtmpPacket::create(); + packet->chunk_id = CHUNK_VIDEO; + packet->stream_index = STREAM_MEDIA; + break; + } + + case MSG_DATA: + case MSG_DATA3: { + BufferLikeString buffer(string(data, len)); + AMFDecoder dec(buffer, _type == MSG_DATA3 ? 3 : 0); + auto first = dec.load(); + bool flag = true; + if (first.type() == AMFType::AMF_STRING) { + auto type = first.as_string(); + if (type == "@setDataFrame") { + type = dec.load(); + if (type == "onMetaData") { + flag = onRecvMetadata(dec.load()); + } else { + WarnL << "unknown type:" << type; + } + } else if (type == "onMetaData") { + flag = onRecvMetadata(dec.load()); + } else { + WarnL << "unknown notify:" << type; + } + } else { + WarnL << "Parse flv script data failed, invalid amf value: " << first.to_string(); + } + if (!flag) { + throw std::invalid_argument("check rtmp metadata failed"); + } + return; + } + + default: WarnL << "不识别的flv msg type:" << (int) _type; return; + } + + packet->time_stamp = _time_stamp; + packet->type_id = _type; + packet->body_size = len; + packet->buffer.assign(data, len); + onRecvRtmpPacket(std::move(packet)); +} + + +}//namespace mediakit \ No newline at end of file diff --git a/src/Rtmp/FlvSplitter.h b/src/Rtmp/FlvSplitter.h new file mode 100644 index 00000000..78f2d1d5 --- /dev/null +++ b/src/Rtmp/FlvSplitter.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/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_FLVSPLITTER_H +#define ZLMEDIAKIT_FLVSPLITTER_H + +#include "Rtmp.h" +#include "Http/HttpRequestSplitter.h" +#include "RtmpPlayerImp.h" + +namespace mediakit { + +class FlvSplitter : public HttpRequestSplitter { +public: + FlvSplitter() = default; + ~FlvSplitter() = default; + +protected: + void onRecvContent(const char *data,size_t len) override; + ssize_t onRecvHeader(const char *data,size_t len) override; + const char *onSearchPacketTail(const char *data, size_t len) override; + +protected: + virtual void onRecvFlvHeader(const FLVHeader &header) {}; + virtual bool onRecvMetadata(const AMFValue &metadata) = 0; + virtual void onRecvRtmpPacket(RtmpPacket::Ptr packet) = 0; + +private: + bool _flv_started = false; + uint8_t _type; + uint32_t _time_stamp; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_FLVSPLITTER_H diff --git a/src/Rtmp/Rtmp.cpp b/src/Rtmp/Rtmp.cpp index 6ef1698a..140afeed 100644 --- a/src/Rtmp/Rtmp.cpp +++ b/src/Rtmp/Rtmp.cpp @@ -10,10 +10,10 @@ #include "Rtmp.h" #include "Extension/Factory.h" -namespace mediakit{ -TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::map &header) -{ +namespace mediakit { + +TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::map &header) { _metadata.set("duration", dur_sec); _metadata.set("fileSize", (int)fileSize); _metadata.set("title", std::string("Streamed by ") + kServerName); @@ -22,14 +22,14 @@ TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::mapgetVideoWidth() > 0 ){ +VideoMeta::VideoMeta(const VideoTrack::Ptr &video) { + if (video->getVideoWidth() > 0) { _metadata.set("width", video->getVideoWidth()); } - if(video->getVideoHeight() > 0 ){ + if (video->getVideoHeight() > 0) { _metadata.set("height", video->getVideoHeight()); } - if(video->getVideoFps() > 0 ){ + if (video->getVideoFps() > 0) { _metadata.set("framerate", video->getVideoFps()); } if (video->getBitRate()) { @@ -39,26 +39,26 @@ VideoMeta::VideoMeta(const VideoTrack::Ptr &video){ _metadata.set("videocodecid", Factory::getAmfByCodecId(_codecId)); } -AudioMeta::AudioMeta(const AudioTrack::Ptr &audio){ +AudioMeta::AudioMeta(const AudioTrack::Ptr &audio) { if (audio->getBitRate()) { _metadata.set("audiodatarate", audio->getBitRate() / 1024); } - if(audio->getAudioSampleRate() > 0){ + if (audio->getAudioSampleRate() > 0) { _metadata.set("audiosamplerate", audio->getAudioSampleRate()); } - if(audio->getAudioSampleBit() > 0){ + if (audio->getAudioSampleBit() > 0) { _metadata.set("audiosamplesize", audio->getAudioSampleBit()); } - if(audio->getAudioChannel() > 0){ + if (audio->getAudioChannel() > 0) { _metadata.set("stereo", audio->getAudioChannel() > 1); } _codecId = audio->getCodecId(); _metadata.set("audiocodecid", Factory::getAmfByCodecId(_codecId)); } -uint8_t getAudioRtmpFlags(const Track::Ptr &track){ - switch (track->getTrackType()){ - case TrackAudio : { +uint8_t getAudioRtmpFlags(const Track::Ptr &track) { + switch (track->getTrackType()) { + case TrackAudio: { auto audioTrack = std::dynamic_pointer_cast(track); if (!audioTrack) { WarnL << "获取AudioTrack失败"; @@ -68,21 +68,21 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){ auto iChannel = audioTrack->getAudioChannel(); auto iSampleBit = audioTrack->getAudioSampleBit(); - uint8_t flvAudioType ; - switch (track->getCodecId()){ - case CodecG711A : flvAudioType = FLV_CODEC_G711A; break; - case CodecG711U : flvAudioType = FLV_CODEC_G711U; break; - case CodecOpus : { - flvAudioType = FLV_CODEC_OPUS; - //opus不通过flags获取音频相关信息 + uint8_t flvAudioType; + switch (track->getCodecId()) { + case CodecG711A: flvAudioType = (uint8_t)RtmpAudioCodec::g711a; break; + case CodecG711U: flvAudioType = (uint8_t)RtmpAudioCodec::g711u; break; + case CodecOpus: { + flvAudioType = (uint8_t)RtmpAudioCodec::opus; + // opus不通过flags获取音频相关信息 iSampleRate = 44100; iSampleBit = 16; iChannel = 2; break; } - case CodecAAC : { - flvAudioType = FLV_CODEC_AAC; - //aac不通过flags获取音频相关信息 + case CodecAAC: { + flvAudioType = (uint8_t)RtmpAudioCodec::aac; + // aac不通过flags获取音频相关信息 iSampleRate = 44100; iSampleBit = 16; iChannel = 2; @@ -93,23 +93,15 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){ uint8_t flvSampleRate; switch (iSampleRate) { - case 44100: - flvSampleRate = 3; - break; - case 22050: - flvSampleRate = 2; - break; - case 11025: - flvSampleRate = 1; - break; + case 44100: flvSampleRate = 3; break; + case 22050: flvSampleRate = 2; break; + case 11025: flvSampleRate = 1; break; case 16000: // nellymoser only case 8000: // nellymoser only case 5512: // not MP3 flvSampleRate = 0; break; - default: - WarnL << "FLV does not support sample rate " << iSampleRate << " ,choose from (44100, 22050, 11025)"; - return 0; + default: WarnL << "FLV does not support sample rate " << iSampleRate << " ,choose from (44100, 22050, 11025)"; return 0; } uint8_t flvStereoOrMono = (iChannel > 1); @@ -117,32 +109,28 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){ return (flvAudioType << 4) | (flvSampleRate << 2) | (flvSampleBit << 1) | flvStereoOrMono; } - default : return 0; + default: return 0; } } - void Metadata::addTrack(AMFValue &metadata, const Track::Ptr &track) { Metadata::Ptr new_metadata; switch (track->getTrackType()) { case TrackVideo: { new_metadata = std::make_shared(std::dynamic_pointer_cast(track)); - } break; + } case TrackAudio: { new_metadata = std::make_shared(std::dynamic_pointer_cast(track)); - } break; - default: - return; + } + default: return; } - new_metadata->getMetadata().object_for_each([&](const std::string &key, const AMFValue &value) { - metadata.set(key, value); - }); + new_metadata->getMetadata().object_for_each([&](const std::string &key, const AMFValue &value) { metadata.set(key, value); }); } -RtmpPacket::Ptr RtmpPacket::create(){ +RtmpPacket::Ptr RtmpPacket::create() { #if 0 static ResourcePool packet_pool; static onceToken token([]() { @@ -156,8 +144,7 @@ RtmpPacket::Ptr RtmpPacket::create(){ #endif } -void RtmpPacket::clear() -{ +void RtmpPacket::clear() { is_abs_stamp = false; time_stamp = 0; ts_field = 0; @@ -165,36 +152,56 @@ void RtmpPacket::clear() buffer.clear(); } -bool RtmpPacket::isVideoKeyFrame() const -{ - return type_id == MSG_VIDEO && (uint8_t)buffer[0] >> 4 == FLV_KEY_FRAME && (uint8_t)buffer[1] == 1; +bool RtmpPacket::isVideoKeyFrame() const { + if (type_id != MSG_VIDEO) { + return false; + } + RtmpFrameType frame_type; + if (buffer[0] >> 7) { + // IsExHeader == 1 + frame_type = (RtmpFrameType)((buffer[0] >> 4) & 0x07); + } else { + // IsExHeader == 0 + frame_type = (RtmpFrameType)(buffer[0] >> 4); + } + return frame_type == RtmpFrameType::key_frame; } -bool RtmpPacket::isCfgFrame() const -{ +bool RtmpPacket::isConfigFrame() const { switch (type_id) { - case MSG_VIDEO: return buffer[1] == 0; - case MSG_AUDIO: { - switch (getMediaType()) { - case FLV_CODEC_AAC: return buffer[1] == 0; - default: return false; + case MSG_AUDIO: { + return (RtmpAudioCodec)getRtmpCodecId() == RtmpAudioCodec::aac && (RtmpAACPacketType)buffer[1] == RtmpAACPacketType::aac_config_header; } - } - default: return false; + case MSG_VIDEO: { + if (!isVideoKeyFrame()) { + return false; + } + if (buffer[0] >> 7) { + // IsExHeader == 1 + return (RtmpPacketType)(buffer[0] & 0x0f) == RtmpPacketType::PacketTypeSequenceStart; + } + // IsExHeader == 0 + switch ((RtmpVideoCodec)getRtmpCodecId()) { + case RtmpVideoCodec::h265: + case RtmpVideoCodec::h264: { + return (RtmpH264PacketType)buffer[1] == RtmpH264PacketType::h264_config_header; + } + default: return false; + } + } + default: return false; } } -int RtmpPacket::getMediaType() const -{ +int RtmpPacket::getRtmpCodecId() const { switch (type_id) { - case MSG_VIDEO: return (uint8_t)buffer[0] & 0x0F; - case MSG_AUDIO: return (uint8_t)buffer[0] >> 4; - default: return 0; + case MSG_VIDEO: return (uint8_t)buffer[0] & 0x0F; + case MSG_AUDIO: return (uint8_t)buffer[0] >> 4; + default: return 0; } } -int RtmpPacket::getAudioSampleRate() const -{ +int RtmpPacket::getAudioSampleRate() const { if (type_id != MSG_AUDIO) { return 0; } @@ -203,8 +210,7 @@ int RtmpPacket::getAudioSampleRate() const return sampleRate[flvSampleRate]; } -int RtmpPacket::getAudioSampleBit() const -{ +int RtmpPacket::getAudioSampleBit() const { if (type_id != MSG_AUDIO) { return 0; } @@ -213,8 +219,7 @@ int RtmpPacket::getAudioSampleBit() const return sampleBit[flvSampleBit]; } -int RtmpPacket::getAudioChannel() const -{ +int RtmpPacket::getAudioChannel() const { if (type_id != MSG_AUDIO) { return 0; } @@ -223,8 +228,7 @@ int RtmpPacket::getAudioChannel() const return channel[flvStereoOrMono]; } -RtmpPacket & RtmpPacket::operator=(const RtmpPacket &that) -{ +RtmpPacket &RtmpPacket::operator=(const RtmpPacket &that) { is_abs_stamp = that.is_abs_stamp; stream_index = that.stream_index; body_size = that.body_size; @@ -234,32 +238,101 @@ RtmpPacket & RtmpPacket::operator=(const RtmpPacket &that) return *this; } -RtmpHandshake::RtmpHandshake(uint32_t _time, uint8_t *_random /*= nullptr*/) -{ +RtmpHandshake::RtmpHandshake(uint32_t _time, uint8_t *_random /*= nullptr*/) { _time = htonl(_time); memcpy(time_stamp, &_time, 4); if (!_random) { random_generate((char *)random, sizeof(random)); - } - else { + } else { memcpy(random, _random, sizeof(random)); } } -void RtmpHandshake::random_generate(char *bytes, int size) -{ - static char cdata[] = { 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72, - 0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x2d, 0x77, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x2d, 0x77, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x40, 0x31, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x6d }; +void RtmpHandshake::random_generate(char *bytes, int size) { + static char cdata[] = { 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72, 0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x6e, + 0x6c, 0x69, 0x6e, 0x2d, 0x77, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x40, 0x31, 0x32, 0x36, 0x2e, 0x63, + 0x6f, 0x6d }; for (int i = 0; i < size; i++) { bytes[i] = cdata[rand() % (sizeof(cdata) - 1)]; } } -}//namespace mediakit +#pragma pack(push, 1) + +struct RtmpVideoHeaderEnhanced { +#if __BYTE_ORDER == __BIG_ENDIAN + uint8_t enhanced : 1; + uint8_t frame_type : 3; + uint8_t pkt_type : 4; + uint32_t fourcc; +#else + uint8_t pkt_type : 4; + uint8_t frame_type : 3; + uint8_t enhanced : 1; + uint32_t fourcc; +#endif +}; + +struct RtmpVideoHeaderClassic { +#if __BYTE_ORDER == __BIG_ENDIAN + uint8_t frame_type : 4; + uint8_t codec_id : 4; + uint8_t h264_pkt_type; +#else + uint8_t codec_id : 4; + uint8_t frame_type : 4; + uint8_t h264_pkt_type; +#endif +}; + +#pragma pack(pop) + +CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info) { + RtmpPacketInfo save; + info = info ? info : &save; + info->codec = CodecInvalid; + + CHECK(size > 0); + RtmpVideoHeaderEnhanced *enhanced_header = (RtmpVideoHeaderEnhanced *)data; + if (enhanced_header->enhanced) { + // IsExHeader == 1 + CHECK(size >= 5, "Invalid rtmp buffer size: ", size); + info->is_enhanced = true; + info->video.frame_type = (RtmpFrameType)(enhanced_header->frame_type); + info->video.pkt_type = (RtmpPacketType)(enhanced_header->pkt_type); + + switch ((RtmpVideoCodec)ntohl(enhanced_header->fourcc)) { + case RtmpVideoCodec::fourcc_av1: info->codec = CodecAV1; break; + case RtmpVideoCodec::fourcc_vp9: info->codec = CodecVP9; break; + case RtmpVideoCodec::fourcc_hevc: info->codec = CodecH265; break; + default: WarnL << "Rtmp video codec not supported: " << std::string((char *)data + 1, 4); + } + } else { + // IsExHeader == 0 + RtmpVideoHeaderClassic *classic_header = (RtmpVideoHeaderClassic *)data; + info->is_enhanced = false; + info->video.frame_type = (RtmpFrameType)(classic_header->frame_type); + switch ((RtmpVideoCodec)(classic_header->codec_id)) { + case RtmpVideoCodec::h264: { + CHECK(size >= 1, "Invalid rtmp buffer size: ", size); + info->codec = CodecH264; + info->video.h264_pkt_type = (RtmpH264PacketType)classic_header->h264_pkt_type; + break; + } + case RtmpVideoCodec::h265: { + CHECK(size >= 1, "Invalid rtmp buffer size: ", size); + info->codec = CodecH265; + info->video.h264_pkt_type = (RtmpH264PacketType)classic_header->h264_pkt_type; + break; + } + default: WarnL << "Rtmp video codec not supported: " << (int)classic_header->codec_id; break; + } + } + return info->codec; +} + +} // namespace mediakit namespace toolkit { - StatisticImp(mediakit::RtmpPacket); +StatisticImp(mediakit::RtmpPacket); } \ No newline at end of file diff --git a/src/Rtmp/Rtmp.h b/src/Rtmp/Rtmp.h index 55802549..66908af2 100644 --- a/src/Rtmp/Rtmp.h +++ b/src/Rtmp/Rtmp.h @@ -18,13 +18,6 @@ #include "Network/Buffer.h" #include "Extension/Track.h" -#if !defined(_WIN32) -#define PACKED __attribute__((packed)) -#else -#define PACKED -#endif //!defined(_WIN32) - - #define DEFAULT_CHUNK_LEN 128 #define HANDSHAKE_PLAINTEXT 0x03 #define RANDOM_LEN (1536 - 8) @@ -63,23 +56,9 @@ #define CHUNK_AUDIO 6 /*音频chunkID*/ #define CHUNK_VIDEO 7 /*视频chunkID*/ -#define FLV_KEY_FRAME 1 -#define FLV_INTER_FRAME 2 - -#define FLV_CODEC_AAC 10 -#define FLV_CODEC_H264 7 -//金山扩展: https://github.com/ksvc/FFmpeg/wiki -#define FLV_CODEC_H265 12 -#define FLV_CODEC_G711A 7 -#define FLV_CODEC_G711U 8 -//参考学而思网校: https://github.com/notedit/rtmp/commit/6e314ac5b29611431f8fb5468596b05815743c10 -#define FLV_CODEC_OPUS 13 - namespace mediakit { -#if defined(_WIN32) #pragma pack(push, 1) -#endif // defined(_WIN32) class RtmpHandshake { public: @@ -93,7 +72,7 @@ public: void create_complex_c0c1(); -}PACKED; +}; class RtmpHeader { public: @@ -109,10 +88,12 @@ public: uint8_t body_size[3]; uint8_t type_id; uint8_t stream_index[4]; /* Note, this is little-endian while others are BE */ -}PACKED; +}; class FLVHeader { public: + static constexpr uint8_t kFlvVersion = 1; + static constexpr uint8_t kFlvHeaderLength = 9; //FLV char flv[3]; //File version (for example, 0x01 for FLV version 1) @@ -138,7 +119,9 @@ public: #endif //The length of this header in bytes,固定为9 uint32_t length; -} PACKED; + //固定为0 + uint32_t previous_tag_size0; +}; class RtmpTagHeader { public: @@ -147,11 +130,9 @@ public: uint8_t timestamp[3] = {0}; uint8_t timestamp_ex = 0; uint8_t streamid[3] = {0}; /* Always 0. */ -} PACKED; +}; -#if defined(_WIN32) #pragma pack(pop) -#endif // defined(_WIN32) class RtmpPacket : public toolkit::Buffer{ public: @@ -178,11 +159,15 @@ public: void clear(); + // video config frame和key frame都返回true + // 用于gop缓存定位 bool isVideoKeyFrame() const; - bool isCfgFrame() const; - int getMediaType() const; + // aac config或h264/h265 config返回true,支持增强型rtmp + // 用于缓存解码配置信息 + bool isConfigFrame() const; + int getRtmpCodecId() const; int getAudioSampleRate() const; int getAudioSampleBit() const; int getAudioChannel() const; @@ -265,5 +250,121 @@ private: //根据音频track获取flags uint8_t getAudioRtmpFlags(const Track::Ptr &track); +////////////////// rtmp video ////////////////////////// +//https://rtmp.veriskope.com/pdf/video_file_format_spec_v10_1.pdf + +// UB [4]; Type of video frame. +enum class RtmpFrameType : uint8_t { + reserved = 0, + key_frame = 1, // key frame (for AVC, a seekable frame) + inter_frame = 2, // inter frame (for AVC, a non-seekable frame) + disposable_inter_frame = 3, // disposable inter frame (H.263 only) + generated_key_frame = 4, // generated key frame (reserved for server use only) + video_info_frame = 5, // video info/command frame +}; + +#define MKBETAG(a, b, c, d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24)) + +// UB [4]; Codec Identifier. +enum class RtmpVideoCodec : uint32_t { + h263 = 2, // Sorenson H.263 + screen_video = 3, // Screen video + vp6 = 4, // On2 VP6 + vp6_alpha = 5, // On2 VP6 with alpha channel + screen_video2 = 6, // Screen video version 2 + h264 = 7, // avc + h265 = 12, // 国内扩展 + + // 增强型rtmp FourCC + fourcc_vp9 = MKBETAG('v', 'p', '0', '9'), + fourcc_av1 = MKBETAG('a', 'v', '0', '1'), + fourcc_hevc = MKBETAG('h', 'v', 'c', '1') +}; + +// UI8; +enum class RtmpH264PacketType : uint8_t { + h264_config_header = 0, // AVC or HEVC sequence header(sps/pps) + h264_nalu = 1, // AVC or HEVC NALU + h264_end_seq = 2, // AVC or HEVC end of sequence (lower level NALU sequence ender is not REQUIRED or supported) +}; + +// https://github.com/veovera/enhanced-rtmp/blob/main/enhanced-rtmp.pdf +// UB[4] +enum class RtmpPacketType : uint8_t { + PacketTypeSequenceStart = 0, + PacketTypeCodedFrames = 1, + PacketTypeSequenceEnd = 2, + + // CompositionTime Offset is implied to equal zero. This is + // an optimization to save putting SI24 composition time value of zero on + // the wire. See pseudo code below in the VideoTagBody section + PacketTypeCodedFramesX = 3, + + // VideoTagBody does not contain video data. VideoTagBody + // instead contains an AMF encoded metadata. See Metadata Frame + // section for an illustration of its usage. As an example, the metadata + // can be HDR information. This is a good way to signal HDR + // information. This also opens up future ways to express additional + // metadata that is meant for the next video sequence. + // + // note: presence of PacketTypeMetadata means that FrameType + // flags at the top of this table should be ignored + PacketTypeMetadata = 4, + + // Carriage of bitstream in MPEG-2 TS format + // note: PacketTypeSequenceStart and PacketTypeMPEG2TSSequenceStart + // are mutually exclusive + PacketTypeMPEG2TSSequenceStart = 5, +}; + +////////////////// rtmp audio ////////////////////////// +//https://rtmp.veriskope.com/pdf/video_file_format_spec_v10_1.pdf + +// UB [4]; Format of SoundData +enum class RtmpAudioCodec : uint8_t { + /** + 0 = Linear PCM, platform endian + 1 = ADPCM + 2 = MP3 + 3 = Linear PCM, little endian + 4 = Nellymoser 16 kHz mono + 5 = Nellymoser 8 kHz mono + 6 = Nellymoser + 7 = G.711 A-law logarithmic PCM + 8 = G.711 mu-law logarithmic PCM + 9 = reserved + 10 = AAC + 11 = Speex + 14 = MP3 8 kHz + 15 = Device-specific sound + */ + g711a = 7, + g711u = 8, + aac = 10, + opus = 13 // 国内扩展 +}; + +// UI8; +enum class RtmpAACPacketType : uint8_t { + aac_config_header = 0, // AAC sequence header + aac_raw = 1, // AAC raw +}; + +//////////////////////////////////////////// + +struct RtmpPacketInfo { + CodecId codec = CodecInvalid; + bool is_enhanced; + union { + struct { + RtmpFrameType frame_type; + RtmpPacketType pkt_type; // enhanced = true + RtmpH264PacketType h264_pkt_type; // enhanced = false + } video; + }; +}; +// https://github.com/veovera/enhanced-rtmp +CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info = nullptr); + }//namespace mediakit #endif//__rtmp_h diff --git a/src/Rtmp/RtmpDemuxer.cpp b/src/Rtmp/RtmpDemuxer.cpp index b91dd503..20d8f052 100644 --- a/src/Rtmp/RtmpDemuxer.cpp +++ b/src/Rtmp/RtmpDemuxer.cpp @@ -19,12 +19,12 @@ size_t RtmpDemuxer::trackCount(const AMFValue &metadata) { size_t ret = 0; metadata.object_for_each([&](const string &key, const AMFValue &val) { if (key == "videocodecid") { - //找到视频 + // 找到视频 ++ret; return; } if (key == "audiocodecid") { - //找到音频 + // 找到音频 ++ret; return; } @@ -32,7 +32,7 @@ size_t RtmpDemuxer::trackCount(const AMFValue &metadata) { return ret; } -bool RtmpDemuxer::loadMetaData(const AMFValue &val){ +bool RtmpDemuxer::loadMetaData(const AMFValue &val) { bool ret = false; try { int audiosamplerate = 0; @@ -60,12 +60,12 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){ return; } if (key == "videocodecid") { - //找到视频 + // 找到视频 videocodecid = &val; return; } if (key == "audiocodecid") { - //找到音频 + // 找到音频 audiocodecid = &val; return; } @@ -79,12 +79,12 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){ } }); if (videocodecid) { - //有视频 + // 有视频 ret = true; makeVideoTrack(*videocodecid, videodatarate * 1024); } if (audiocodecid) { - //有音频 + // 有音频 ret = true; makeAudioTrack(*audiocodecid, audiosamplerate, audiochannels, audiosamplesize, audiodatarate * 1024); } @@ -93,7 +93,7 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){ } if (ret) { - //metadata中存在track相关的描述,那么我们根据metadata判断有多少个track + // metadata中存在track相关的描述,那么我们根据metadata判断有多少个track addTrackCompleted(); } return ret; @@ -108,8 +108,8 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) { case MSG_VIDEO: { if (!_try_get_video_track) { _try_get_video_track = true; - auto codec = AMFValue(pkt->getMediaType()); - makeVideoTrack(codec, 0); + auto codec_id = parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size()); + makeVideoTrack(Factory::getTrackByCodecId(codec_id), 0); } if (_video_rtmp_decoder) { _video_rtmp_decoder->inputRtmp(pkt); @@ -120,7 +120,7 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) { case MSG_AUDIO: { if (!_try_get_audio_track) { _try_get_audio_track = true; - auto codec = AMFValue(pkt->getMediaType()); + auto codec = AMFValue(pkt->getRtmpCodecId()); makeAudioTrack(codec, pkt->getAudioSampleRate(), pkt->getAudioChannel(), pkt->getAudioSampleBit(), 0); } if (_audio_rtmp_decoder) { @@ -128,51 +128,55 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) { } break; } - default : break; + default: break; } } void RtmpDemuxer::makeVideoTrack(const AMFValue &videoCodec, int bit_rate) { + makeVideoTrack(Factory::getVideoTrackByAmf(videoCodec), bit_rate); +} + +void RtmpDemuxer::makeVideoTrack(const Track::Ptr &track, int bit_rate) { if (_video_rtmp_decoder) { return; } - //生成Track对象 - _video_track = dynamic_pointer_cast(Factory::getVideoTrackByAmf(videoCodec)); + // 生成Track对象 + _video_track = dynamic_pointer_cast(track); if (!_video_track) { return; } - //生成rtmpCodec对象以便解码rtmp + // 生成rtmpCodec对象以便解码rtmp _video_rtmp_decoder = Factory::getRtmpCodecByTrack(_video_track, false); if (!_video_rtmp_decoder) { - //找不到相应的rtmp解码器,该track无效 + // 找不到相应的rtmp解码器,该track无效 _video_track.reset(); return; } _video_track->setBitRate(bit_rate); - //设置rtmp解码器代理,生成的frame写入该Track + // 设置rtmp解码器代理,生成的frame写入该Track _video_rtmp_decoder->addDelegate(_video_track); addTrack(_video_track); _try_get_video_track = true; } -void RtmpDemuxer::makeAudioTrack(const AMFValue &audioCodec,int sample_rate, int channels, int sample_bit, int bit_rate) { +void RtmpDemuxer::makeAudioTrack(const AMFValue &audioCodec, int sample_rate, int channels, int sample_bit, int bit_rate) { if (_audio_rtmp_decoder) { return; } - //生成Track对象 + // 生成Track对象 _audio_track = dynamic_pointer_cast(Factory::getAudioTrackByAmf(audioCodec, sample_rate, channels, sample_bit)); if (!_audio_track) { return; } - //生成rtmpCodec对象以便解码rtmp + // 生成rtmpCodec对象以便解码rtmp _audio_rtmp_decoder = Factory::getRtmpCodecByTrack(_audio_track, false); if (!_audio_rtmp_decoder) { - //找不到相应的rtmp解码器,该track无效 + // 找不到相应的rtmp解码器,该track无效 _audio_track.reset(); return; } _audio_track->setBitRate(bit_rate); - //设置rtmp解码器代理,生成的frame写入该Track + // 设置rtmp解码器代理,生成的frame写入该Track _audio_rtmp_decoder->addDelegate(_audio_track); addTrack(_audio_track); _try_get_audio_track = true; diff --git a/src/Rtmp/RtmpDemuxer.h b/src/Rtmp/RtmpDemuxer.h index 31b0ed2d..4cf0412b 100644 --- a/src/Rtmp/RtmpDemuxer.h +++ b/src/Rtmp/RtmpDemuxer.h @@ -45,6 +45,7 @@ public: private: void makeVideoTrack(const AMFValue &val, int bit_rate); + void makeVideoTrack(const Track::Ptr &val, int bit_rate); void makeAudioTrack(const AMFValue &val, int sample_rate, int channels, int sample_bit, int bit_rate); private: diff --git a/src/Rtmp/RtmpMediaSource.h b/src/Rtmp/RtmpMediaSource.h index 5c862a36..732cbb85 100644 --- a/src/Rtmp/RtmpMediaSource.h +++ b/src/Rtmp/RtmpMediaSource.h @@ -57,8 +57,8 @@ public: return _ring; } - void getPlayerList(const std::function> &info_list)> &cb, - const std::function(std::shared_ptr &&info)> &on_change) override { + void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) override { _ring->getInfoList(cb, on_change); } @@ -73,42 +73,29 @@ public: /** * 获取metadata */ - const AMFValue &getMetaData() const { + template + void getMetaData(const FUNC &func) const { std::lock_guard lock(_mtx); - return _metadata; + if (_metadata) { + func(_metadata); + } } /** * 获取所有的config帧 */ - template - void getConfigFrame(const FUNC &f) { + template + void getConfigFrame(const FUNC &func) { std::lock_guard lock(_mtx); for (auto &pr : _config_frame_map) { - f(pr.second); + func(pr.second); } } /** * 设置metadata */ - virtual void setMetaData(const AMFValue &metadata) { - _metadata = metadata; - _metadata.set("title", std::string("Streamed by ") + kServerName); - _have_video = _metadata["videocodecid"]; - _have_audio = _metadata["audiocodecid"]; - if (_ring) { - regist(); - } - } - - /** - * 更新metadata - */ - void updateMetaData(const AMFValue &metadata) { - std::lock_guard lock(_mtx); - _metadata = metadata; - } + virtual void setMetaData(const AMFValue &metadata); /** * 输入rtmp包 diff --git a/src/Rtmp/RtmpMediaSourceImp.cpp b/src/Rtmp/RtmpMediaSourceImp.cpp index ce64226a..4a6f8975 100644 --- a/src/Rtmp/RtmpMediaSourceImp.cpp +++ b/src/Rtmp/RtmpMediaSourceImp.cpp @@ -2,15 +2,15 @@ #include "RtmpMediaSourceImp.h" namespace mediakit { -uint32_t RtmpMediaSource::getTimeStamp(TrackType trackType) -{ + +uint32_t RtmpMediaSource::getTimeStamp(TrackType trackType) { assert(trackType >= TrackInvalid && trackType < TrackMax); if (trackType != TrackInvalid) { - //获取某track的时间戳 + // 获取某track的时间戳 return _track_stamps[trackType]; } - //获取所有track的最小时间戳 + // 获取所有track的最小时间戳 uint32_t ret = UINT32_MAX; for (auto &stamp : _track_stamps) { if (stamp > 0 && stamp < ret) { @@ -20,38 +20,61 @@ uint32_t RtmpMediaSource::getTimeStamp(TrackType trackType) return ret; } -void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) -{ - bool is_video = pkt->type_id == MSG_VIDEO; - _speed[is_video ? TrackVideo : TrackAudio] += pkt->size(); - //保存当前时间戳 - switch (pkt->type_id) { - case MSG_VIDEO: _track_stamps[TrackVideo] = pkt->time_stamp, _have_video = true; break; - case MSG_AUDIO: _track_stamps[TrackAudio] = pkt->time_stamp, _have_audio = true; break; - default: break; +void RtmpMediaSource::setMetaData(const AMFValue &metadata) { + { + std::lock_guard lock(_mtx); + _metadata = metadata; + _metadata.set("title", std::string("Streamed by ") + kServerName); } - if (pkt->isCfgFrame()) { + _have_video = _metadata["videocodecid"]; + _have_audio = _metadata["audiocodecid"]; + if (_ring) { + regist(); + + AMFEncoder enc; + enc << "onMetaData" << _metadata; + RtmpPacket::Ptr packet = RtmpPacket::create(); + packet->buffer = enc.data(); + packet->type_id = MSG_DATA; + packet->time_stamp = 0; + packet->chunk_id = CHUNK_CLIENT_REQUEST_AFTER; + packet->stream_index = STREAM_MEDIA; + onWrite(std::move(packet)); + } +} + +void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) { + bool is_video = pkt->type_id == MSG_VIDEO; + _speed[is_video ? TrackVideo : TrackAudio] += pkt->size(); + // 保存当前时间戳 + switch (pkt->type_id) { + case MSG_VIDEO: _track_stamps[TrackVideo] = pkt->time_stamp, _have_video = true; break; + case MSG_AUDIO: _track_stamps[TrackAudio] = pkt->time_stamp, _have_audio = true; break; + default: break; + } + + if (pkt->isConfigFrame()) { std::lock_guard lock(_mtx); _config_frame_map[pkt->type_id] = pkt; if (!_ring) { - //注册后收到config帧更新到各播放器 + // 注册后收到config帧更新到各播放器 return; } } if (!_ring) { - std::weak_ptr weakSelf = std::static_pointer_cast(shared_from_this()); - auto lam = [weakSelf](int size) { - auto strongSelf = weakSelf.lock(); - if (!strongSelf) { + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); + auto lam = [weak_self](int size) { + auto strong_self = weak_self.lock(); + if (!strong_self) { return; } - strongSelf->onReaderChanged(size); + strong_self->onReaderChanged(size); }; - //GOP默认缓冲512组RTMP包,每组RTMP包时间戳相同(如果开启合并写了,那么每组为合并写时间内的RTMP包), - //每次遇到关键帧第一个RTMP包,则会清空GOP缓存(因为有新的关键帧了,同样可以实现秒开) + // GOP默认缓冲512组RTMP包,每组RTMP包时间戳相同(如果开启合并写了,那么每组为合并写时间内的RTMP包), + // 每次遇到关键帧第一个RTMP包,则会清空GOP缓存(因为有新的关键帧了,同样可以实现秒开) _ring = std::make_shared(_ring_size, std::move(lam)); if (_metadata) { regist(); @@ -62,47 +85,42 @@ void RtmpMediaSource::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) PacketCache::inputPacket(stamp, is_video, std::move(pkt), key); } - -RtmpMediaSourceImp::RtmpMediaSourceImp(const MediaTuple& tuple, int ringSize) : RtmpMediaSource(tuple, ringSize) -{ +RtmpMediaSourceImp::RtmpMediaSourceImp(const MediaTuple &tuple, int ringSize) + : RtmpMediaSource(tuple, ringSize) { _demuxer = std::make_shared(); _demuxer->setTrackListener(this); } -void RtmpMediaSourceImp::setMetaData(const AMFValue &metadata) -{ +void RtmpMediaSourceImp::setMetaData(const AMFValue &metadata) { if (!_demuxer->loadMetaData(metadata)) { - //该metadata无效,需要重新生成 + // 该metadata无效,需要重新生成 _metadata = metadata; _recreate_metadata = true; } RtmpMediaSource::setMetaData(metadata); } -void RtmpMediaSourceImp::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) -{ +void RtmpMediaSourceImp::onWrite(RtmpPacket::Ptr pkt, bool /*= true*/) { if (!_all_track_ready || _muxer->isEnabled()) { - //未获取到所有Track后,或者开启转协议,那么需要解复用rtmp + // 未获取到所有Track后,或者开启转协议,那么需要解复用rtmp _demuxer->inputRtmp(pkt); } RtmpMediaSource::onWrite(std::move(pkt)); } -int RtmpMediaSourceImp::totalReaderCount() -{ +int RtmpMediaSourceImp::totalReaderCount() { return readerCount() + (_muxer ? _muxer->totalReaderCount() : 0); } -void RtmpMediaSourceImp::setProtocolOption(const ProtocolOption &option) -{ - //不重复生成rtmp +void RtmpMediaSourceImp::setProtocolOption(const ProtocolOption &option) { + // 不重复生成rtmp _option = option; - //不重复生成rtmp协议 + // 不重复生成rtmp协议 _option.enable_rtmp = false; _muxer = std::make_shared(_tuple, _demuxer->getDuration(), _option); _muxer->setMediaListener(getListener()); _muxer->setTrackListener(std::static_pointer_cast(shared_from_this())); - //让_muxer对象拦截一部分事件(比如说录像相关事件) + // 让_muxer对象拦截一部分事件(比如说录像相关事件) MediaSource::setListener(_muxer); for (auto &track : _demuxer->getTracks(false)) { @@ -111,8 +129,7 @@ void RtmpMediaSourceImp::setProtocolOption(const ProtocolOption &option) } } -bool RtmpMediaSourceImp::addTrack(const Track::Ptr &track) -{ +bool RtmpMediaSourceImp::addTrack(const Track::Ptr &track) { if (_muxer) { if (_muxer->addTrack(track)) { track->addDelegate(_muxer); @@ -122,45 +139,38 @@ bool RtmpMediaSourceImp::addTrack(const Track::Ptr &track) return false; } -void RtmpMediaSourceImp::addTrackCompleted() -{ +void RtmpMediaSourceImp::addTrackCompleted() { if (_muxer) { _muxer->addTrackCompleted(); } } -void RtmpMediaSourceImp::resetTracks() -{ +void RtmpMediaSourceImp::resetTracks() { if (_muxer) { _muxer->resetTracks(); } } -void RtmpMediaSourceImp::onAllTrackReady() -{ +void RtmpMediaSourceImp::onAllTrackReady() { _all_track_ready = true; if (_recreate_metadata) { - //更新metadata + // 更新metadata for (auto &track : _muxer->getTracks()) { Metadata::addTrack(_metadata, track); } - RtmpMediaSource::updateMetaData(_metadata); + RtmpMediaSource::setMetaData(_metadata); } } -void RtmpMediaSourceImp::setListener(const std::weak_ptr &listener) -{ +void RtmpMediaSourceImp::setListener(const std::weak_ptr &listener) { if (_muxer) { //_muxer对象不能处理的事件再给listener处理 _muxer->setMediaListener(listener); - } - else { - //未创建_muxer对象,事件全部给listener处理 + } else { + // 未创建_muxer对象,事件全部给listener处理 MediaSource::setListener(listener); } } -} - - +} // namespace mediakit diff --git a/src/Rtmp/RtmpMediaSourceMuxer.h b/src/Rtmp/RtmpMediaSourceMuxer.h index 51f8b46c..3f6b8bd4 100644 --- a/src/Rtmp/RtmpMediaSourceMuxer.h +++ b/src/Rtmp/RtmpMediaSourceMuxer.h @@ -44,7 +44,8 @@ public: return _media_src->readerCount(); } - void onAllTrackReady(){ + void addTrackCompleted() override { + RtmpMuxer::addTrackCompleted(); makeConfigPacket(); _media_src->setMetaData(getMetadata()); } diff --git a/src/Rtmp/RtmpPlayer.cpp b/src/Rtmp/RtmpPlayer.cpp index ffaf0dfc..42914cda 100644 --- a/src/Rtmp/RtmpPlayer.cpp +++ b/src/Rtmp/RtmpPlayer.cpp @@ -27,7 +27,7 @@ namespace mediakit { RtmpPlayer::RtmpPlayer(const EventPoller::Ptr &poller) : TcpClient(poller) {} RtmpPlayer::~RtmpPlayer() { - DebugL << endl; + DebugL; } void RtmpPlayer::teardown() { @@ -52,14 +52,14 @@ void RtmpPlayer::teardown() { void RtmpPlayer::play(const string &url) { teardown(); - string host_url = FindField(url.data(), "://", "/"); + string host_url = findSubString(url.data(), "://", "/"); { auto pos = url.find_last_of('/'); if (pos != string::npos) { _stream_id = url.substr(pos + 1); } } - _app = FindField(url.data(), (host_url + "/").data(), ("/" + _stream_id).data()); + _app = findSubString(url.data(), (host_url + "/").data(), ("/" + _stream_id).data()); _tc_url = string("rtmp://") + host_url + "/" + _app; if (!_app.size() || !_stream_id.size()) { @@ -191,6 +191,13 @@ void RtmpPlayer::send_connect() { obj.set("audioCodecs", (double) (0x0400)); //只支持H264 obj.set("videoCodecs", (double) (0x0080)); + + AMFValue fourCcList(AMF_STRICT_ARRAY); + fourCcList.add("av01"); + fourCcList.add("vp09"); + fourCcList.add("hvc1"); + obj.set("fourCcList", fourCcList); + sendInvoke("connect", obj); addOnResultCB([this](AMFDecoder &dec) { //TraceL << "connect result"; @@ -313,8 +320,8 @@ void RtmpPlayer::onCmd_onStatus(AMFDecoder &dec) { void RtmpPlayer::onCmd_onMetaData(AMFDecoder &dec) { //TraceL; auto val = dec.load(); - if (!onCheckMeta(val)) { - throw std::runtime_error("onCheckMeta failed"); + if (!onMetadata(val)) { + throw std::runtime_error("onMetadata failed"); } _metadata_got = true; } @@ -328,18 +335,18 @@ void RtmpPlayer::onMediaData_l(RtmpPacket::Ptr chunk_data) { _rtmp_recv_ticker.resetTime(); if (!_play_timer) { //已经触发了onPlayResult事件,直接触发onMediaData事件 - onMediaData(chunk_data); + onRtmpPacket(chunk_data); return; } - if (chunk_data->isCfgFrame()) { + if (chunk_data->isConfigFrame()) { //输入配置帧以便初始化完成各个track - onMediaData(chunk_data); + onRtmpPacket(chunk_data); } else { //先触发onPlayResult事件,这个时候解码器才能初始化完毕 onPlayResult_l(SockException(Err_success, "play rtmp success"), false); //触发onPlayResult事件后,再把帧数据输入到解码器 - onMediaData(chunk_data); + onRtmpPacket(chunk_data); } } @@ -379,8 +386,8 @@ void RtmpPlayer::onRtmpChunk(RtmpPacket::Ptr packet) { _now_stamp[idx] = chunk_data.time_stamp; } if (!_metadata_got) { - if (!onCheckMeta(TitleMeta().getMetadata())) { - throw std::runtime_error("onCheckMeta failed"); + if (!onMetadata(TitleMeta().getMetadata())) { + throw std::runtime_error("onMetadata failed"); } _metadata_got = true; } @@ -420,49 +427,4 @@ void RtmpPlayer::seekToMilliSecond(uint32_t seekMS){ }); } -//////////////////////////////////////////// -float RtmpPlayerImp::getDuration() const -{ - return _demuxer ? _demuxer->getDuration() : 0; -} - -std::vector RtmpPlayerImp::getTracks(bool ready /*= true*/) const -{ - return _demuxer ? _demuxer->getTracks(ready) : Super::getTracks(ready); -} - -bool RtmpPlayerImp::onCheckMeta(const AMFValue &val) -{ - //无metadata或metadata中无track信息时,需要从数据包中获取track - _wait_track_ready = (*this)[Client::kWaitTrackReady].as() || RtmpDemuxer::trackCount(val) == 0; - onCheckMeta_l(val); - return true; -} - -void RtmpPlayerImp::onMediaData(RtmpPacket::Ptr chunkData) -{ - if (!_demuxer) { - //有些rtmp流没metadata - onCheckMeta_l(TitleMeta().getMetadata()); - } - _demuxer->inputRtmp(chunkData); - if (_rtmp_src) { - _rtmp_src->onWrite(std::move(chunkData)); - } -} - -void RtmpPlayerImp::onCheckMeta_l(const AMFValue &val) -{ - _rtmp_src = std::dynamic_pointer_cast(_media_src); - if (_rtmp_src) { - _rtmp_src->setMetaData(val); - } - if (_demuxer) { - return; - } - _demuxer = std::make_shared(); - //TraceL<<" _wait_track_ready "<<_wait_track_ready; - _demuxer->setTrackListener(this, _wait_track_ready); - _demuxer->loadMetaData(val); -} } /* namespace mediakit */ diff --git a/src/Rtmp/RtmpPlayer.h b/src/Rtmp/RtmpPlayer.h index 0a45f1b5..75abf7f9 100644 --- a/src/Rtmp/RtmpPlayer.h +++ b/src/Rtmp/RtmpPlayer.h @@ -37,8 +37,8 @@ public: void teardown() override; protected: - virtual bool onCheckMeta(const AMFValue &val) = 0; - virtual void onMediaData(RtmpPacket::Ptr chunk_data) = 0; + virtual bool onMetadata(const AMFValue &val) = 0; + virtual void onRtmpPacket(RtmpPacket::Ptr chunk_data) = 0; uint32_t getProgressMilliSecond() const; void seekToMilliSecond(uint32_t ms); diff --git a/src/Rtmp/RtmpPlayerImp.h b/src/Rtmp/RtmpPlayerImp.h index c09d460b..ecf9a34f 100644 --- a/src/Rtmp/RtmpPlayerImp.h +++ b/src/Rtmp/RtmpPlayerImp.h @@ -13,21 +13,100 @@ #include #include +#include "Common/config.h" #include "RtmpPlayer.h" -#include "RtmpDemuxer.h" #include "RtmpMediaSource.h" +#include "RtmpDemuxer.h" +#include "Poller/Timer.h" +#include "Util/TimeTicker.h" namespace mediakit { -class RtmpPlayerImp: public PlayerImp, private TrackListener { +template +class FlvPlayerBase: public PlayerImp, private TrackListener { +public: + using Ptr = std::shared_ptr; + using Super = PlayerImp; + + FlvPlayerBase(const toolkit::EventPoller::Ptr &poller) : Super(poller) {}; + + ~FlvPlayerBase() override { + DebugL << std::endl; + } + + float getDuration() const override { + return _demuxer ? _demuxer->getDuration() : 0; + } + + std::vector getTracks(bool ready = true) const override { + return _demuxer ? _demuxer->getTracks(ready) : Super::getTracks(ready); + } + +private: + //派生类回调函数 + bool onMetadata(const AMFValue &val) override { + //无metadata或metadata中无track信息时,需要从数据包中获取track + _wait_track_ready = this->Super::operator[](Client::kWaitTrackReady).template as() || RtmpDemuxer::trackCount(val) == 0; + onCheckMeta_l(val); + return true; + } + + void onRtmpPacket(RtmpPacket::Ptr chunkData) override { + if (!_demuxer) { + //有些rtmp流没metadata + onCheckMeta_l(TitleMeta().getMetadata()); + } + _demuxer->inputRtmp(chunkData); + if (_rtmp_src) { + _rtmp_src->onWrite(std::move(chunkData)); + } + } + + void onPlayResult(const toolkit::SockException &ex) override { + if (!_wait_track_ready || ex) { + Super::onPlayResult(ex); + return; + } + } + + bool addTrack(const Track::Ptr &track) override { return true; } + + void addTrackCompleted() override { + if (_wait_track_ready) { + Super::onPlayResult(toolkit::SockException(toolkit::Err_success, "play success")); + } + } + +private: + void onCheckMeta_l(const AMFValue &val) { + _rtmp_src = std::dynamic_pointer_cast(this->Super::_media_src); + if (_rtmp_src) { + _rtmp_src->setMetaData(val); + } + if(_demuxer){ + return; + } + _demuxer = std::make_shared(); + //TraceL<<" _wait_track_ready "<<_wait_track_ready; + _demuxer->setTrackListener(this, _wait_track_ready); + _demuxer->loadMetaData(val); + } + +private: + bool _wait_track_ready = true; + RtmpDemuxer::Ptr _demuxer; + RtmpMediaSource::Ptr _rtmp_src; +}; + +class RtmpPlayerImp: public FlvPlayerBase { public: using Ptr = std::shared_ptr; - using Super = PlayerImp; + using Super = FlvPlayerBase; RtmpPlayerImp(const toolkit::EventPoller::Ptr &poller) : Super(poller) {}; ~RtmpPlayerImp() override { - DebugL << std::endl; + DebugL; } float getProgress() const override { @@ -46,39 +125,6 @@ public: uint32_t pos = MAX(float(0), MIN(seekPos, getDuration())) * 1000; seekToMilliSecond(pos); } - - float getDuration() const override; - - std::vector getTracks(bool ready = true) const override; - -private: - //派生类回调函数 - bool onCheckMeta(const AMFValue &val) override; - - void onMediaData(RtmpPacket::Ptr chunkData) override; - - void onPlayResult(const toolkit::SockException &ex) override { - if (!_wait_track_ready || ex) { - Super::onPlayResult(ex); - return; - } - } - - bool addTrack(const Track::Ptr &track) override { return true; } - - void addTrackCompleted() override { - if (_wait_track_ready) { - Super::onPlayResult(toolkit::SockException(toolkit::Err_success, "play success")); - } - } - -private: - void onCheckMeta_l(const AMFValue &val); - -private: - bool _wait_track_ready = true; - RtmpDemuxer::Ptr _demuxer; - RtmpMediaSource::Ptr _rtmp_src; }; diff --git a/src/Rtmp/RtmpPusher.cpp b/src/Rtmp/RtmpPusher.cpp index 31143dcf..f401cf34 100644 --- a/src/Rtmp/RtmpPusher.cpp +++ b/src/Rtmp/RtmpPusher.cpp @@ -27,7 +27,7 @@ RtmpPusher::RtmpPusher(const EventPoller::Ptr &poller, const RtmpMediaSource::Pt RtmpPusher::~RtmpPusher() { teardown(); - DebugL << endl; + DebugL; } void RtmpPusher::teardown() { @@ -65,9 +65,9 @@ void RtmpPusher::onPublishResult_l(const SockException &ex, bool handshake_done) void RtmpPusher::publish(const string &url) { teardown(); - string host_url = FindField(url.data(), "://", "/"); - _app = FindField(url.data(), (host_url + "/").data(), "/"); - _stream_id = FindField(url.data(), (host_url + "/" + _app + "/").data(), NULL); + string host_url = findSubString(url.data(), "://", "/"); + _app = findSubString(url.data(), (host_url + "/").data(), "/"); + _stream_id = findSubString(url.data(), (host_url + "/" + _app + "/").data(), NULL); _tc_url = string("rtmp://") + host_url + "/" + _app; if (!_app.size() || !_stream_id.size()) { @@ -135,6 +135,13 @@ void RtmpPusher::send_connect() { obj.set("type", "nonprivate"); obj.set("tcUrl", _tc_url); obj.set("swfUrl", _tc_url); + + AMFValue fourCcList(AMF_STRICT_ARRAY); + fourCcList.add("av01"); + fourCcList.add("vp09"); + fourCcList.add("hvc1"); + obj.set("fourCcList", fourCcList); + sendInvoke("connect", obj); addOnResultCB([this](AMFDecoder &dec) { //TraceL << "connect result"; @@ -183,10 +190,14 @@ void RtmpPusher::send_metaData(){ throw std::runtime_error("the media source was released"); } - AMFEncoder enc; - enc << "@setDataFrame" << "onMetaData" << src->getMetaData(); - sendRequest(MSG_DATA, enc.data()); + // metadata + src->getMetaData([&](const AMFValue &metadata) { + AMFEncoder enc; + enc << "@setDataFrame" << "onMetaData" << metadata; + sendRequest(MSG_DATA, enc.data()); + }); + // config frame src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) { sendRtmp(pkt->type_id, _stream_index, pkt, pkt->time_stamp, pkt->chunk_id); }); @@ -207,7 +218,16 @@ void RtmpPusher::send_metaData(){ if (++i == size) { strong_self->setSendFlushFlag(true); } - strong_self->sendRtmp(rtmp->type_id, strong_self->_stream_index, rtmp, rtmp->time_stamp, rtmp->chunk_id); + if (rtmp->type_id == MSG_DATA) { + // update metadata + AMFEncoder enc; + enc << "@setDataFrame"; + auto pkt = enc.data(); + pkt.append(rtmp->data(), rtmp->size()); + strong_self->sendRequest(MSG_DATA, pkt); + } else { + strong_self->sendRtmp(rtmp->type_id, strong_self->_stream_index, rtmp, rtmp->time_stamp, rtmp->chunk_id); + } }); }); _rtmp_reader->setDetachCB([weak_self]() { diff --git a/src/Rtmp/RtmpSession.cpp b/src/Rtmp/RtmpSession.cpp index 56a799cb..fa3058be 100644 --- a/src/Rtmp/RtmpSession.cpp +++ b/src/Rtmp/RtmpSession.cpp @@ -36,7 +36,7 @@ void RtmpSession::onError(const SockException& err) { GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_total_bytes >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, is_player, static_cast(*this)); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, is_player, *this); } //如果是主动关闭的,那么不延迟注销 @@ -215,7 +215,7 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) { on_res(err, option); }); }; - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtmp_push, _media_info, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtmp_push, _media_info, invoker, *this); if(!flag){ //该事件无人监听,默认鉴权成功 on_res("", ProtocolOption()); @@ -291,17 +291,14 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr "description", "Now published." , "details", _media_info.stream, "clientid", "0"}); - - auto &metadata = src->getMetaData(); - if(metadata){ - //在有metadata的情况下才发送metadata - //其实metadata没什么用,有些推流器不产生metadata - // onMetaData + // metadata + src->getMetaData([&](const AMFValue &metadata) { invoke.clear(); invoke << "onMetaData" << metadata; sendResponse(MSG_DATA, invoke.data()); - } + }); + // config frame src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) { onSendMedia(pkt); }); @@ -309,7 +306,11 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr src->pause(false); _ring_reader = src->getRing()->attach(getPoller()); weak_ptr weak_self = static_pointer_cast(shared_from_this()); - _ring_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); }); + _ring_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(static_pointer_cast(weak_self.lock())); + return ret; + }); _ring_reader->setReadCB([weak_self](const RtmpMediaSource::RingDataType &pkt) { auto strong_self = weak_self.lock(); if (!strong_self) { @@ -330,6 +331,7 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr if (!strong_self) { return; } + strong_self->sendUserControl(CONTROL_STREAM_EOF/*or CONTROL_STREAM_DRY ?*/, STREAM_MEDIA); strong_self->shutdown(SockException(Err_shutdown,"rtmp ring buffer detached")); }); src->pause(false); @@ -383,7 +385,7 @@ void RtmpSession::doPlay(AMFDecoder &dec){ }); }; - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _media_info, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _media_info, invoker, *this); if (!flag) { // 该事件无人监听,默认不鉴权 doPlayResponse("", [token](bool) {}); @@ -481,6 +483,7 @@ void RtmpSession::setMetaData(AMFDecoder &dec) { throw std::runtime_error("can only set metadata"); } _push_metadata = dec.load(); + _set_meta_data = false; } void RtmpSession::onProcessCmd(AMFDecoder &dec) { @@ -528,6 +531,7 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) { } else if (type == "onMetaData") { //兼容某些不规范的推流器 _push_metadata = dec.load(); + _set_meta_data = false; } else { TraceP(this) << "unknown notify:" << type; } @@ -537,7 +541,14 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) { case MSG_AUDIO: case MSG_VIDEO: { if (!_push_src) { - WarnL << "Not a rtmp push!"; + if (_ring_reader) { + throw std::runtime_error("Rtmp player send media packets"); + } + if (packet->isConfigFrame()) { + auto id = packet->type_id; + _push_config_packets.emplace(id, std::move(packet)); + } + WarnL << "Rtmp pusher send media packet before handshake completed!"; return; } @@ -545,6 +556,12 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) { _set_meta_data = true; _push_src->setMetaData(_push_metadata ? _push_metadata : TitleMeta().getMetadata()); } + if (!_push_config_packets.empty()) { + for (auto &pr : _push_config_packets) { + _push_src->onWrite(std::move(pr.second)); + } + _push_config_packets.clear(); + } _push_src->onWrite(std::move(packet)); break; } diff --git a/src/Rtmp/RtmpSession.h b/src/Rtmp/RtmpSession.h index f9c2bf2a..f51b567e 100644 --- a/src/Rtmp/RtmpSession.h +++ b/src/Rtmp/RtmpSession.h @@ -97,6 +97,7 @@ private: MediaInfo _media_info; std::weak_ptr _play_src; AMFValue _push_metadata; + std::map _push_config_packets; RtmpMediaSourceImp::Ptr _push_src; std::shared_ptr _push_src_ownership; RtmpMediaSource::RingType::RingReader::Ptr _ring_reader; diff --git a/src/Rtp/RtpProcess.cpp b/src/Rtp/RtpProcess.cpp index 9700e09c..5e61ccf4 100644 --- a/src/Rtp/RtpProcess.cpp +++ b/src/Rtp/RtpProcess.cpp @@ -66,7 +66,11 @@ RtpProcess::~RtpProcess() { //流量统计事件广播 GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_total_bytes >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, false, static_cast(*this)); + try { + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, false, *this); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } } } @@ -206,31 +210,27 @@ void RtpProcess::setOnDetach(function cb) { } string RtpProcess::get_peer_ip() { - if (!_addr) { + try { + return _addr ? SockUtil::inet_ntoa((sockaddr *)_addr.get()) : "::"; + } catch (std::exception &ex) { return "::"; } - return SockUtil::inet_ntoa((sockaddr *)_addr.get()); } uint16_t RtpProcess::get_peer_port() { - if (!_addr) { + try { + return _addr ? SockUtil::inet_port((sockaddr *)_addr.get()) : 0; + } catch (std::exception &ex) { return 0; } - return SockUtil::inet_port((sockaddr *)_addr.get()); } string RtpProcess::get_local_ip() { - if (_sock) { - return _sock->get_local_ip(); - } - return "::"; + return _sock ? _sock->get_local_ip() : "::"; } uint16_t RtpProcess::get_local_port() { - if (_sock) { - return _sock->get_local_port(); - } - return 0; + return _sock ? _sock->get_local_port() : 0; } string RtpProcess::getIdentifier() const { @@ -266,9 +266,9 @@ void RtpProcess::emitOnPublish() { }; //触发推流鉴权事件 - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtp_push, _media_info, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtp_push, _media_info, invoker, *this); if (!flag) { - //该事件无人监听,默认不鉴权 + // 该事件无人监听,默认不鉴权 invoker("", ProtocolOption()); } } @@ -301,4 +301,4 @@ float RtpProcess::getLossRate(MediaSource &sender, TrackType type) { } }//namespace mediakit -#endif//defined(ENABLE_RTPPROXY) \ No newline at end of file +#endif//defined(ENABLE_RTPPROXY) diff --git a/src/Rtp/RtpSender.cpp b/src/Rtp/RtpSender.cpp index a15884f0..a102d5e2 100644 --- a/src/Rtp/RtpSender.cpp +++ b/src/Rtp/RtpSender.cpp @@ -28,7 +28,11 @@ RtpSender::RtpSender(EventPoller::Ptr poller) { } RtpSender::~RtpSender() { - flush(); + try { + flush(); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } } void RtpSender::startSend(const MediaSourceEvent::SendRtpArgs &args, const function &cb){ diff --git a/src/Rtp/RtpServer.cpp b/src/Rtp/RtpServer.cpp index c6b38b28..342e8382 100644 --- a/src/Rtp/RtpServer.cpp +++ b/src/Rtp/RtpServer.cpp @@ -102,8 +102,8 @@ public: process->setOnDetach(std::move(strong_self->_on_detach)); } if (!process) { // process 未创建,触发rtp server 超时事件 - NoticeCenter::Instance().emitEvent(Broadcast::KBroadcastRtpServerTimeout, strong_self->_local_port, strong_self->_stream_id, - (int)strong_self->_tcp_mode, strong_self->_re_use_port, strong_self->_ssrc); + NOTICE_EMIT(BroadcastRtpServerTimeoutArgs, Broadcast::KBroadcastRtpServerTimeout, strong_self->_local_port, strong_self->_stream_id, + (int)strong_self->_tcp_mode, strong_self->_re_use_port, strong_self->_ssrc); } } return 0; diff --git a/src/Rtsp/RtpCodec.cpp b/src/Rtsp/RtpCodec.cpp index 21257bce..6d1d4a9c 100644 --- a/src/Rtsp/RtpCodec.cpp +++ b/src/Rtsp/RtpCodec.cpp @@ -39,7 +39,7 @@ RtpPacket::Ptr RtpInfo::makeRtp(TrackType type, const void* data, size_t len, bo ++_seq; header->stamp = htonl(uint64_t(stamp) * _sample_rate / 1000); header->ssrc = htonl(_ssrc); - + rtp->ntp_stamp = stamp; //有效负载 if (data) { memcpy(&ptr[RtpPacket::kRtpHeaderSize + RtpPacket::kRtpTcpHeaderSize], data, len); diff --git a/src/Rtsp/RtpReceiver.cpp b/src/Rtsp/RtpReceiver.cpp index 363a3175..7e669138 100644 --- a/src/Rtsp/RtpReceiver.cpp +++ b/src/Rtsp/RtpReceiver.cpp @@ -71,7 +71,7 @@ RtpPacket::Ptr RtpTrack::inputRtp(TrackType type, int sample_rate, uint8_t *ptr, } else { //ssrc错误 if (_ssrc_alive.elapsedTime() < 3 * 1000) { - //接受正确ssrc的rtp在10秒内,那么我们认为存在多路rtp,忽略掉ssrc不匹配的rtp + //接收正确ssrc的rtp在10秒内,那么我们认为存在多路rtp,忽略掉ssrc不匹配的rtp WarnL << "ssrc mismatch, rtp dropped:" << ssrc << " != " << _ssrc; return nullptr; } diff --git a/src/Rtsp/Rtsp.cpp b/src/Rtsp/Rtsp.cpp index b80a88f7..daf09680 100644 --- a/src/Rtsp/Rtsp.cpp +++ b/src/Rtsp/Rtsp.cpp @@ -10,6 +10,7 @@ #include #include +#include #include "Rtsp.h" #include "Common/Parser.h" #include "Common/config.h" @@ -22,7 +23,8 @@ namespace mediakit { int RtpPayload::getClockRate(int pt) { switch (pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return clock_rate; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return clock_rate; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return 90000; @@ -31,7 +33,8 @@ int RtpPayload::getClockRate(int pt) { TrackType RtpPayload::getTrackType(int pt) { switch (pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return type; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return type; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return TrackInvalid; @@ -40,7 +43,8 @@ TrackType RtpPayload::getTrackType(int pt) { int RtpPayload::getAudioChannel(int pt) { switch (pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return channel; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return channel; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return 1; @@ -49,7 +53,8 @@ int RtpPayload::getAudioChannel(int pt) { const char *RtpPayload::getName(int pt) { switch (pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return #name; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return #name; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return "unknown payload type"; @@ -58,7 +63,8 @@ const char *RtpPayload::getName(int pt) { CodecId RtpPayload::getCodecId(int pt) { switch (pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return codec_id; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return codec_id; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return CodecInvalid; @@ -85,7 +91,8 @@ static void getAttrSdp(const multimap &attr, _StrPrinter &printe string SdpTrack::getName() const { switch (_pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return #name; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return #name; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return _codec; @@ -94,7 +101,7 @@ string SdpTrack::getName() const { string SdpTrack::getControlUrl(const string &base_url) const { if (_control.find("://") != string::npos) { - //以rtsp://开头 + // 以rtsp://开头 return _control; } return base_url + "/" + _control; @@ -180,11 +187,11 @@ void SdpParser::load(const string &sdp) { break; } case 'a': { - string attr = FindField(opt_val.data(), nullptr, ":"); + string attr = findSubString(opt_val.data(), nullptr, ":"); if (attr.empty()) { track->_attr.emplace(opt_val, ""); } else { - track->_attr.emplace(attr, FindField(opt_val.data(), ":", nullptr)); + track->_attr.emplace(attr, findSubString(opt_val.data(), ":", nullptr)); } break; } @@ -197,14 +204,14 @@ void SdpParser::load(const string &sdp) { auto &track = *track_ptr; auto it = track._attr.find("range"); if (it != track._attr.end()) { - char name[16] = {0}, start[16] = {0}, end[16] = {0}; + char name[16] = { 0 }, start[16] = { 0 }, end[16] = { 0 }; int ret = sscanf(it->second.data(), "%15[^=]=%15[^-]-%15s", name, start, end); if (3 == ret || 2 == ret) { if (strcmp(start, "now") == 0) { strcpy(start, "0"); } - track._start = (float) atof(start); - track._end = (float) atof(end); + track._start = (float)atof(start); + track._end = (float)atof(end); track._duration = track._end - track._start; } } @@ -212,11 +219,11 @@ void SdpParser::load(const string &sdp) { for (it = track._attr.find("rtpmap"); it != track._attr.end() && it->first == "rtpmap";) { auto &rtpmap = it->second; int pt, samplerate, channel; - char codec[16] = {0}; + char codec[16] = { 0 }; sscanf(rtpmap.data(), "%d", &pt); if (track._pt != pt && track._pt != 0xff) { - //pt不匹配 + // pt不匹配 it = track._attr.erase(it); continue; } @@ -230,22 +237,22 @@ void SdpParser::load(const string &sdp) { track._samplerate = samplerate; } if (!track._samplerate && track._type == TrackVideo) { - //未设置视频采样率时,赋值为90000 + // 未设置视频采样率时,赋值为90000 track._samplerate = 90000; } ++it; } - for (it = track._attr.find("fmtp"); it != track._attr.end() && it->first == "fmtp"; ) { + for (it = track._attr.find("fmtp"); it != track._attr.end() && it->first == "fmtp";) { auto &fmtp = it->second; int pt; sscanf(fmtp.data(), "%d", &pt); if (track._pt != pt && track._pt != 0xff) { - //pt不匹配 + // pt不匹配 it = track._attr.erase(it); continue; } - track._fmtp = FindField(fmtp.data(), " ", nullptr); + track._fmtp = findSubString(fmtp.data(), " ", nullptr); ++it; } @@ -315,8 +322,17 @@ string SdpParser::toString() const { return title + video + audio; } -template -class PortManager : public std::enable_shared_from_this > { +std::string SdpParser::getControlUrl(const std::string &url) const { + auto title_track = getTrack(TrackTitle); + if (title_track && title_track->_control.find("://") != string::npos) { + // 以rtsp://开头 + return title_track->_control; + } + return url; +} + +template +class PortManager : public std::enable_shared_from_this> { public: PortManager() { static auto func = [](const string &str, int index) { @@ -326,11 +342,11 @@ public: }; GET_CONFIG_FUNC(uint16_t, s_min_port, RtpProxy::kPortRange, [](const string &str) { return func(str, 0); }); GET_CONFIG_FUNC(uint16_t, s_max_port, RtpProxy::kPortRange, [](const string &str) { return func(str, 1); }); - assert(s_max_port >= s_min_port + 36 -1); + assert(s_max_port >= s_min_port + 36 - 1); setRange((s_min_port + 1) / 2, s_max_port / 2); } - static PortManager& Instance() { + static PortManager &Instance() { static auto instance = std::make_shared(); return *instance; } @@ -344,12 +360,12 @@ public: } if (is_udp) { if (!sock0->bindUdpSock(2 * *sock_pair, local_ip.data(), re_use_port)) { - //分配端口失败 + // 分配端口失败 throw runtime_error("open udp socket[0] failed"); } if (!sock1->bindUdpSock(2 * *sock_pair + 1, local_ip.data(), re_use_port)) { - //分配端口失败 + // 分配端口失败 throw runtime_error("open udp socket[1] failed"); } @@ -359,12 +375,12 @@ public: sock1->setOnAccept(on_cycle); } else { if (!sock0->listen(2 * *sock_pair, local_ip.data())) { - //分配端口失败 + // 分配端口失败 throw runtime_error("listen tcp socket[0] failed"); } if (!sock1->listen(2 * *sock_pair + 1, local_ip.data())) { - //分配端口失败 + // 分配端口失败 throw runtime_error("listen tcp socket[1] failed"); } @@ -377,9 +393,13 @@ public: private: void setRange(uint16_t start_pos, uint16_t end_pos) { + std::mt19937 rng(std::random_device {}()); lock_guard lck(_pool_mtx); + auto it = _port_pair_pool.begin(); while (start_pos < end_pos) { - _port_pair_pool.emplace_back(start_pos++); + // 随机端口排序,防止重启后导致分配的端口重复 + _port_pair_pool.insert(it, start_pos++); + it = _port_pair_pool.begin() + (rng() % (1 + _port_pair_pool.size())); } } @@ -400,7 +420,7 @@ private: return; } InfoL << "return port to pool:" << 2 * pos << "-" << 2 * pos + 1; - //回收端口号 + // 回收端口号 lock_guard lck(strong_self->_pool_mtx); strong_self->_port_pair_pool.emplace_back(pos); }); @@ -416,7 +436,7 @@ void makeSockPair(std::pair &pair, const string &local int try_count = 0; while (true) { try { - //udp和tcp端口池使用相同算法和范围分配,但是互不相干 + // udp和tcp端口池使用相同算法和范围分配,但是互不相干 if (is_udp) { PortManager<0>::Instance().makeSockPair(pair, local_ip, re_use_port, is_udp); } else { @@ -433,9 +453,9 @@ void makeSockPair(std::pair &pair, const string &local } string printSSRC(uint32_t ui32Ssrc) { - char tmp[9] = {0}; + char tmp[9] = { 0 }; ui32Ssrc = htonl(ui32Ssrc); - uint8_t *pSsrc = (uint8_t *) &ui32Ssrc; + uint8_t *pSsrc = (uint8_t *)&ui32Ssrc; for (int i = 0; i < 4; i++) { sprintf(tmp + 2 * i, "%02X", pSsrc[i]); } @@ -447,7 +467,7 @@ bool isRtp(const char *buf, size_t size) { return false; } RtpHeader *header = (RtpHeader *)buf; - return ((header->pt < 64) || (header->pt >= 96)); + return ((header->pt < 64) || (header->pt >= 96)) && header->version == RtpPacket::kRtpVersion; } bool isRtcp(const char *buf, size_t size) { @@ -470,12 +490,10 @@ Buffer::Ptr makeRtpOverTcpPrefix(uint16_t size, uint8_t interleaved) { return rtp_tcp; } -#define AV_RB16(x) \ - ((((const uint8_t*)(x))[0] << 8) | \ - ((const uint8_t*)(x))[1]) +#define AV_RB16(x) ((((const uint8_t *)(x))[0] << 8) | ((const uint8_t *)(x))[1]) size_t RtpHeader::getCsrcSize() const { - //每个csrc占用4字节 + // 每个csrc占用4字节 return csrc << 2; } @@ -487,18 +505,18 @@ uint8_t *RtpHeader::getCsrcData() { } size_t RtpHeader::getExtSize() const { - //rtp有ext + // rtp有ext if (!ext) { return 0; } auto ext_ptr = &payload + getCsrcSize(); - //uint16_t reserved = AV_RB16(ext_ptr); - //每个ext占用4字节 + // uint16_t reserved = AV_RB16(ext_ptr); + // 每个ext占用4字节 return AV_RB16(ext_ptr + 2) << 2; } uint16_t RtpHeader::getExtReserved() const { - //rtp有ext + // rtp有ext if (!ext) { return 0; } @@ -511,12 +529,12 @@ uint8_t *RtpHeader::getExtData() { return nullptr; } auto ext_ptr = &payload + getCsrcSize(); - //多出的4个字节分别为reserved、ext_len + // 多出的4个字节分别为reserved、ext_len return ext_ptr + 4; } size_t RtpHeader::getPayloadOffset() const { - //有ext时,还需要忽略reserved、ext_len 4个字节 + // 有ext时,还需要忽略reserved、ext_len 4个字节 return getCsrcSize() + (ext ? (4 + getExtSize()) : 0); } @@ -528,7 +546,7 @@ size_t RtpHeader::getPaddingSize(size_t rtp_size) const { if (!padding) { return 0; } - auto end = (uint8_t *) this + rtp_size - 1; + auto end = (uint8_t *)this + rtp_size - 1; return *end; } @@ -539,12 +557,12 @@ ssize_t RtpHeader::getPayloadSize(size_t rtp_size) const { string RtpHeader::dumpString(size_t rtp_size) const { _StrPrinter printer; - printer << "version:" << (int) version << "\r\n"; + printer << "version:" << (int)version << "\r\n"; printer << "padding:" << getPaddingSize(rtp_size) << "\r\n"; printer << "ext:" << getExtSize() << "\r\n"; printer << "csrc:" << getCsrcSize() << "\r\n"; - printer << "mark:" << (int) mark << "\r\n"; - printer << "pt:" << (int) pt << "\r\n"; + printer << "mark:" << (int)mark << "\r\n"; + printer << "pt:" << (int)pt << "\r\n"; printer << "seq:" << ntohs(seq) << "\r\n"; printer << "stamp:" << ntohl(stamp) << "\r\n"; printer << "ssrc:" << ntohl(ssrc) << "\r\n"; @@ -557,16 +575,16 @@ string RtpHeader::dumpString(size_t rtp_size) const { /////////////////////////////////////////////////////////////////////// RtpHeader *RtpPacket::getHeader() { - //需除去rtcp over tcp 4个字节长度 - return (RtpHeader *) (data() + RtpPacket::kRtpTcpHeaderSize); + // 需除去rtcp over tcp 4个字节长度 + return (RtpHeader *)(data() + RtpPacket::kRtpTcpHeaderSize); } const RtpHeader *RtpPacket::getHeader() const { - return (RtpHeader *) (data() + RtpPacket::kRtpTcpHeaderSize); + return (RtpHeader *)(data() + RtpPacket::kRtpTcpHeaderSize); } string RtpPacket::dumpString() const { - return ((RtpPacket *) this)->getHeader()->dumpString(size() - RtpPacket::kRtpTcpHeaderSize); + return ((RtpPacket *)this)->getHeader()->dumpString(size() - RtpPacket::kRtpTcpHeaderSize); } uint16_t RtpPacket::getSeq() const { @@ -590,7 +608,7 @@ uint8_t *RtpPacket::getPayload() { } size_t RtpPacket::getPayloadSize() const { - //需除去rtcp over tcp 4个字节长度 + // 需除去rtcp over tcp 4个字节长度 return getHeader()->getPayloadSize(size() - kRtpTcpHeaderSize); } @@ -608,23 +626,22 @@ RtpPacket::Ptr RtpPacket::create() { #endif } - /** -* 构造title类型sdp -* @param dur_sec rtsp点播时长,0代表直播,单位秒 -* @param header 自定义sdp描述 -* @param version sdp版本 -*/ + * 构造title类型sdp + * @param dur_sec rtsp点播时长,0代表直播,单位秒 + * @param header 自定义sdp描述 + * @param version sdp版本 + */ -TitleSdp::TitleSdp(float dur_sec, const std::map& header, int version) : Sdp(0, 0) { +TitleSdp::TitleSdp(float dur_sec, const std::map &header, int version) + : Sdp(0, 0) { _printer << "v=" << version << "\r\n"; if (!header.empty()) { for (auto &pr : header) { _printer << pr.first << "=" << pr.second << "\r\n"; } - } - else { + } else { _printer << "o=- 0 0 IN IP4 0.0.0.0\r\n"; _printer << "s=Streamed by " << kServerName << "\r\n"; _printer << "c=IN IP4 0.0.0.0\r\n"; @@ -632,18 +649,17 @@ TitleSdp::TitleSdp(float dur_sec, const std::map& head } if (dur_sec <= 0) { - //直播 + // 直播 _printer << "a=range:npt=now-\r\n"; - } - else { - //点播 + } else { + // 点播 _dur_sec = dur_sec; _printer << "a=range:npt=0-" << dur_sec << "\r\n"; } _printer << "a=control:*\r\n"; } -}//namespace mediakit +} // namespace mediakit namespace toolkit { StatisticImp(mediakit::RtpPacket); diff --git a/src/Rtsp/Rtsp.h b/src/Rtsp/Rtsp.h index 6bb19d8d..dc34f918 100644 --- a/src/Rtsp/Rtsp.h +++ b/src/Rtsp/Rtsp.h @@ -11,13 +11,13 @@ #ifndef RTSP_RTSP_H_ #define RTSP_RTSP_H_ -#include -#include -#include -#include #include "Common/macros.h" #include "Extension/Frame.h" #include "Network/Socket.h" +#include +#include +#include +#include namespace mediakit { @@ -29,148 +29,141 @@ typedef enum { RTP_MULTICAST = 2, } eRtpType; -#define RTP_PT_MAP(XX) \ - XX(PCMU, TrackAudio, 0, 8000, 1, CodecG711U) \ - XX(GSM, TrackAudio , 3, 8000, 1, CodecInvalid) \ - XX(G723, TrackAudio, 4, 8000, 1, CodecInvalid) \ - XX(DVI4_8000, TrackAudio, 5, 8000, 1, CodecInvalid) \ - XX(DVI4_16000, TrackAudio, 6, 16000, 1, CodecInvalid) \ - XX(LPC, TrackAudio, 7, 8000, 1, CodecInvalid) \ - XX(PCMA, TrackAudio, 8, 8000, 1, CodecG711A) \ - XX(G722, TrackAudio, 9, 8000, 1, CodecInvalid) \ - XX(L16_Stereo, TrackAudio, 10, 44100, 2, CodecInvalid) \ - XX(L16_Mono, TrackAudio, 11, 44100, 1, CodecInvalid) \ - XX(QCELP, TrackAudio, 12, 8000, 1, CodecInvalid) \ - XX(CN, TrackAudio, 13, 8000, 1, CodecInvalid) \ - XX(MPA, TrackAudio, 14, 90000, 1, CodecInvalid) \ - XX(G728, TrackAudio, 15, 8000, 1, CodecInvalid) \ - XX(DVI4_11025, TrackAudio, 16, 11025, 1, CodecInvalid) \ - XX(DVI4_22050, TrackAudio, 17, 22050, 1, CodecInvalid) \ - XX(G729, TrackAudio, 18, 8000, 1, CodecInvalid) \ - XX(CelB, TrackVideo, 25, 90000, 1, CodecInvalid) \ - XX(JPEG, TrackVideo, 26, 90000, 1, CodecJPEG) \ - XX(nv, TrackVideo, 28, 90000, 1, CodecInvalid) \ - XX(H261, TrackVideo, 31, 90000, 1, CodecInvalid) \ - XX(MPV, TrackVideo, 32, 90000, 1, CodecInvalid) \ - XX(MP2T, TrackVideo, 33, 90000, 1, CodecInvalid) \ - XX(H263, TrackVideo, 34, 90000, 1, CodecInvalid) \ +#define RTP_PT_MAP(XX) \ + XX(PCMU, TrackAudio, 0, 8000, 1, CodecG711U) \ + XX(GSM, TrackAudio, 3, 8000, 1, CodecInvalid) \ + XX(G723, TrackAudio, 4, 8000, 1, CodecInvalid) \ + XX(DVI4_8000, TrackAudio, 5, 8000, 1, CodecInvalid) \ + XX(DVI4_16000, TrackAudio, 6, 16000, 1, CodecInvalid) \ + XX(LPC, TrackAudio, 7, 8000, 1, CodecInvalid) \ + XX(PCMA, TrackAudio, 8, 8000, 1, CodecG711A) \ + XX(G722, TrackAudio, 9, 8000, 1, CodecInvalid) \ + XX(L16_Stereo, TrackAudio, 10, 44100, 2, CodecInvalid) \ + XX(L16_Mono, TrackAudio, 11, 44100, 1, CodecInvalid) \ + XX(QCELP, TrackAudio, 12, 8000, 1, CodecInvalid) \ + XX(CN, TrackAudio, 13, 8000, 1, CodecInvalid) \ + XX(MPA, TrackAudio, 14, 90000, 1, CodecInvalid) \ + XX(G728, TrackAudio, 15, 8000, 1, CodecInvalid) \ + XX(DVI4_11025, TrackAudio, 16, 11025, 1, CodecInvalid) \ + XX(DVI4_22050, TrackAudio, 17, 22050, 1, CodecInvalid) \ + XX(G729, TrackAudio, 18, 8000, 1, CodecInvalid) \ + XX(CelB, TrackVideo, 25, 90000, 1, CodecInvalid) \ + XX(JPEG, TrackVideo, 26, 90000, 1, CodecJPEG) \ + XX(nv, TrackVideo, 28, 90000, 1, CodecInvalid) \ + XX(H261, TrackVideo, 31, 90000, 1, CodecInvalid) \ + XX(MPV, TrackVideo, 32, 90000, 1, CodecInvalid) \ + XX(MP2T, TrackVideo, 33, 90000, 1, CodecInvalid) \ + XX(H263, TrackVideo, 34, 90000, 1, CodecInvalid) typedef enum { -#define ENUM_DEF(name, type, value, clock_rate, channel, codec_id) PT_ ## name = value, +#define ENUM_DEF(name, type, value, clock_rate, channel, codec_id) PT_##name = value, RTP_PT_MAP(ENUM_DEF) #undef ENUM_DEF - PT_MAX = 128 + PT_MAX + = 128 } PayloadType; -}; +}; // namespace Rtsp -#if defined(_WIN32) #pragma pack(push, 1) -#endif // defined(_WIN32) class RtpHeader { public: #if __BYTE_ORDER == __BIG_ENDIAN - //版本号,固定为2 - uint32_t version: 2; - //padding - uint32_t padding: 1; - //扩展 - uint32_t ext: 1; - //csrc - uint32_t csrc: 4; - //mark - uint32_t mark: 1; - //负载类型 - uint32_t pt: 7; + // 版本号,固定为2 + uint32_t version : 2; + // padding + uint32_t padding : 1; + // 扩展 + uint32_t ext : 1; + // csrc + uint32_t csrc : 4; + // mark + uint32_t mark : 1; + // 负载类型 + uint32_t pt : 7; #else - //csrc - uint32_t csrc: 4; - //扩展 - uint32_t ext: 1; - //padding - uint32_t padding: 1; - //版本号,固定为2 - uint32_t version: 2; - //负载类型 - uint32_t pt: 7; - //mark - uint32_t mark: 1; + // csrc + uint32_t csrc : 4; + // 扩展 + uint32_t ext : 1; + // padding + uint32_t padding : 1; + // 版本号,固定为2 + uint32_t version : 2; + // 负载类型 + uint32_t pt : 7; + // mark + uint32_t mark : 1; #endif - //序列号 - uint32_t seq: 16; - //时间戳 + // 序列号 + uint32_t seq : 16; + // 时间戳 uint32_t stamp; - //ssrc + // ssrc uint32_t ssrc; - //负载,如果有csrc和ext,前面为 4 * csrc + (4 + 4 * ext_len) + // 负载,如果有csrc和ext,前面为 4 * csrc + (4 + 4 * ext_len) uint8_t payload; public: - //返回csrc字段字节长度 + // 返回csrc字段字节长度 size_t getCsrcSize() const; - //返回csrc字段首地址,不存在时返回nullptr + // 返回csrc字段首地址,不存在时返回nullptr uint8_t *getCsrcData(); - //返回ext字段字节长度 + // 返回ext字段字节长度 size_t getExtSize() const; - //返回ext reserved值 + // 返回ext reserved值 uint16_t getExtReserved() const; - //返回ext段首地址,不存在时返回nullptr + // 返回ext段首地址,不存在时返回nullptr uint8_t *getExtData(); - //返回有效负载指针,跳过csrc、ext - uint8_t* getPayloadData(); - //返回有效负载总长度,不包括csrc、ext、padding + // 返回有效负载指针,跳过csrc、ext + uint8_t *getPayloadData(); + // 返回有效负载总长度,不包括csrc、ext、padding ssize_t getPayloadSize(size_t rtp_size) const; - //打印调试信息 + // 打印调试信息 std::string dumpString(size_t rtp_size) const; private: - //返回有效负载偏移量 + // 返回有效负载偏移量 size_t getPayloadOffset() const; - //返回padding长度 + // 返回padding长度 size_t getPaddingSize(size_t rtp_size) const; -} PACKED; +}; -#if defined(_WIN32) #pragma pack(pop) -#endif // defined(_WIN32) -//此rtp为rtp over tcp形式,需要忽略前4个字节 -class RtpPacket : public toolkit::BufferRaw{ +// 此rtp为rtp over tcp形式,需要忽略前4个字节 +class RtpPacket : public toolkit::BufferRaw { public: using Ptr = std::shared_ptr; - enum { - kRtpVersion = 2, - kRtpHeaderSize = 12, - kRtpTcpHeaderSize = 4 - }; + enum { kRtpVersion = 2, kRtpHeaderSize = 12, kRtpTcpHeaderSize = 4 }; - //获取rtp头 - RtpHeader* getHeader(); - const RtpHeader* getHeader() const; + // 获取rtp头 + RtpHeader *getHeader(); + const RtpHeader *getHeader() const; - //打印调试信息 + // 打印调试信息 std::string dumpString() const; - //主机字节序的seq + // 主机字节序的seq uint16_t getSeq() const; uint32_t getStamp() const; - //主机字节序的时间戳,已经转换为毫秒 + // 主机字节序的时间戳,已经转换为毫秒 uint64_t getStampMS(bool ntp = true) const; - //主机字节序的ssrc + // 主机字节序的ssrc uint32_t getSSRC() const; - //有效负载,跳过csrc、ext - uint8_t* getPayload(); - //有效负载长度,不包括csrc、ext、padding + // 有效负载,跳过csrc、ext + uint8_t *getPayload(); + // 有效负载长度,不包括csrc、ext、padding size_t getPayloadSize() const; - //音视频类型 + // 音视频类型 TrackType type; - //音频为采样率,视频一般为90000 + // 音频为采样率,视频一般为90000 uint32_t sample_rate; - //ntp时间戳 + // ntp时间戳 uint64_t ntp_stamp; static Ptr create(); @@ -180,7 +173,7 @@ private: RtpPacket() = default; private: - //对象个数统计 + // 对象个数统计 toolkit::ObjectStatistic _statistic; }; @@ -229,7 +222,7 @@ public: uint8_t _interleaved = 0; uint16_t _seq = 0; uint32_t _ssrc = 0; - //时间戳,单位毫秒 + // 时间戳,单位毫秒 uint32_t _time_stamp = 0; }; @@ -246,15 +239,16 @@ public: SdpTrack::Ptr getTrack(TrackType type) const; std::vector getAvailableTrack() const; std::string toString() const; + std::string getControlUrl(const std::string &url) const; private: std::vector _track_vec; }; /** -* rtsp sdp基类 -*/ -class Sdp : public CodecInfo{ + * rtsp sdp基类 + */ +class Sdp : public CodecInfo { public: using Ptr = std::shared_ptr; @@ -263,7 +257,7 @@ public: * @param sample_rate 采样率 * @param payload_type pt类型 */ - Sdp(uint32_t sample_rate, uint8_t payload_type){ + Sdp(uint32_t sample_rate, uint8_t payload_type) { _sample_rate = sample_rate; _payload_type = payload_type; } @@ -274,23 +268,19 @@ public: * 获取sdp字符串 * @return */ - virtual std::string getSdp() const = 0; + virtual std::string getSdp() const = 0; /** * 获取pt * @return */ - uint8_t getPayloadType() const{ - return _payload_type; - } + uint8_t getPayloadType() const { return _payload_type; } /** * 获取采样率 * @return */ - uint32_t getSampleRate() const{ - return _sample_rate; - } + uint32_t getSampleRate() const { return _sample_rate; } private: uint8_t _payload_type; @@ -298,9 +288,9 @@ private: }; /** -* sdp中除音视频外的其他描述部分 -*/ -class TitleSdp : public Sdp{ + * sdp中除音视频外的其他描述部分 + */ +class TitleSdp : public Sdp { public: using Ptr = std::shared_ptr; /** @@ -309,36 +299,28 @@ public: * @param header 自定义sdp描述 * @param version sdp版本 */ - TitleSdp(float dur_sec = 0, - const std::map &header = std::map(), - int version = 0); + TitleSdp(float dur_sec = 0, const std::map &header = std::map(), int version = 0); - std::string getSdp() const override { - return _printer; - } + std::string getSdp() const override { return _printer; } - CodecId getCodecId() const override { - return CodecInvalid; - } + CodecId getCodecId() const override { return CodecInvalid; } - float getDuration() const { - return _dur_sec; - } + float getDuration() const { return _dur_sec; } private: float _dur_sec = 0; toolkit::_StrPrinter _printer; }; -//创建rtp over tcp4个字节的头 +// 创建rtp over tcp4个字节的头 toolkit::Buffer::Ptr makeRtpOverTcpPrefix(uint16_t size, uint8_t interleaved); -//创建rtp-rtcp端口对 +// 创建rtp-rtcp端口对 void makeSockPair(std::pair &pair, const std::string &local_ip, bool re_use_port = false, bool is_udp = true); -//十六进制方式打印ssrc +// 十六进制方式打印ssrc std::string printSSRC(uint32_t ui32Ssrc); bool isRtp(const char *buf, size_t size); -bool isRtcp(const char *buf, size_t size); +bool isRtcp(const char *buf, size_t size); -} //namespace mediakit -#endif //RTSP_RTSP_H_ +} // namespace mediakit +#endif // RTSP_RTSP_H_ diff --git a/src/Rtsp/RtspMediaSource.h b/src/Rtsp/RtspMediaSource.h index f70396d8..bac7fcf9 100644 --- a/src/Rtsp/RtspMediaSource.h +++ b/src/Rtsp/RtspMediaSource.h @@ -53,11 +53,18 @@ public: return _ring; } - void getPlayerList(const std::function> &info_list)> &cb, - const std::function(std::shared_ptr &&info)> &on_change) override { + void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) override { + assert(_ring); _ring->getInfoList(cb, on_change); } + bool broadcastMessage(const toolkit::Any &data) override { + assert(_ring); + _ring->sendMessage(data); + return true; + } + /** * 获取播放器个数 */ diff --git a/src/Rtsp/RtspMediaSourceMuxer.h b/src/Rtsp/RtspMediaSourceMuxer.h index c772149a..955c6341 100644 --- a/src/Rtsp/RtspMediaSourceMuxer.h +++ b/src/Rtsp/RtspMediaSourceMuxer.h @@ -44,7 +44,8 @@ public: _media_src->setTimeStamp(stamp); } - void onAllTrackReady(){ + void addTrackCompleted() override { + RtspMuxer::addTrackCompleted(); _media_src->setSdp(getSdp()); } diff --git a/src/Rtsp/RtspMuxer.cpp b/src/Rtsp/RtspMuxer.cpp index 755b7f5a..6f5e4f8f 100644 --- a/src/Rtsp/RtspMuxer.cpp +++ b/src/Rtsp/RtspMuxer.cpp @@ -14,9 +14,12 @@ using namespace std; using namespace toolkit; +#define ENABLE_NTP_STAMP 0 + namespace mediakit { void RtspMuxer::onRtp(RtpPacket::Ptr in, bool is_key) { +#if ENABLE_NTP_STAMP if (_live) { if (_rtp_stamp[in->type] != in->getHeader()->stamp) { //rtp时间戳变化才计算ntp,节省cpu资源 @@ -34,6 +37,7 @@ void RtspMuxer::onRtp(RtpPacket::Ptr in, bool is_key) { //点播情况下设置ntp时间戳为rtp时间戳加基准ntp时间戳 in->ntp_stamp = _ntp_stamp_start + (in->getStamp() * uint64_t(1000) / in->sample_rate); } +#endif _rtpRing->write(std::move(in), is_key); } @@ -49,7 +53,10 @@ RtspMuxer::RtspMuxer(const TitleSdp::Ptr &title) { _rtpInterceptor->setDelegate(std::make_shared([this](RtpPacket::Ptr in, bool is_key) { onRtp(std::move(in), is_key); })); + +#if ENABLE_NTP_STAMP _ntp_stamp_start = getCurrentMillisecond(true); +#endif } bool RtspMuxer::addTrack(const Track::Ptr &track) { @@ -75,10 +82,12 @@ bool RtspMuxer::addTrack(const Track::Ptr &track) { } void RtspMuxer::trySyncTrack() { +#if ENABLE_NTP_STAMP if (_encoder[TrackAudio] && _encoder[TrackVideo]) { //音频时间戳同步于视频,因为音频时间戳被修改后不影响播放 _stamp[TrackAudio].syncTo(_stamp[TrackVideo]); } +#endif } bool RtspMuxer::inputFrame(const Frame::Ptr &frame) { diff --git a/src/Rtsp/RtspPlayer.cpp b/src/Rtsp/RtspPlayer.cpp index 67d0b281..f6a13fda 100644 --- a/src/Rtsp/RtspPlayer.cpp +++ b/src/Rtsp/RtspPlayer.cpp @@ -8,49 +8,44 @@ * may be found in the AUTHORS file in the root of the source tree. */ -#include -#include -#include -#include -#include "Common/config.h" #include "RtspPlayer.h" -#include "Util/MD5.h" -#include "Util/base64.h" +#include "Common/config.h" #include "Rtcp/Rtcp.h" #include "Rtcp/RtcpContext.h" -#include "RtspMediaSource.h" #include "RtspDemuxer.h" +#include "RtspMediaSource.h" #include "RtspPlayerImp.h" +#include "Util/MD5.h" +#include "Util/base64.h" +#include +#include +#include +#include using namespace toolkit; using namespace std; namespace mediakit { -enum PlayType { - type_play = 0, - type_pause, - type_seek, - type_speed -}; +enum PlayType { type_play = 0, type_pause, type_seek, type_speed }; -RtspPlayer::RtspPlayer(const EventPoller::Ptr &poller) : TcpClient(poller){ -} +RtspPlayer::RtspPlayer(const EventPoller::Ptr &poller) + : TcpClient(poller) {} RtspPlayer::~RtspPlayer(void) { - DebugL << endl; + DebugL; } -void RtspPlayer::sendTeardown(){ +void RtspPlayer::sendTeardown() { if (alive()) { - if (!_content_base.empty()) { - sendRtspRequest("TEARDOWN", _content_base); + if (!_control_url.empty()) { + sendRtspRequest("TEARDOWN", _control_url); } shutdown(SockException(Err_shutdown, "teardown")); } } -void RtspPlayer::teardown(){ +void RtspPlayer::teardown() { sendTeardown(); _md5_nonce.clear(); _realm.clear(); @@ -69,7 +64,7 @@ void RtspPlayer::teardown(){ _on_response = nullptr; } -void RtspPlayer::play(const string &strUrl){ +void RtspPlayer::play(const string &strUrl) { RtspUrl url; try { url.parse(strUrl); @@ -94,32 +89,35 @@ void RtspPlayer::play(const string &strUrl){ weak_ptr weakSelf = static_pointer_cast(shared_from_this()); float playTimeOutSec = (*this)[Client::kTimeoutMS].as() / 1000.0f; - _play_check_timer.reset(new Timer(playTimeOutSec, [weakSelf]() { - auto strongSelf=weakSelf.lock(); - if(!strongSelf) { + _play_check_timer.reset(new Timer( + playTimeOutSec, + [weakSelf]() { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return false; + } + strongSelf->onPlayResult_l(SockException(Err_timeout, "play rtsp timeout"), false); return false; - } - strongSelf->onPlayResult_l(SockException(Err_timeout,"play rtsp timeout"),false); - return false; - }, getPoller())); + }, + getPoller())); - if(!(*this)[Client::kNetAdapter].empty()){ + if (!(*this)[Client::kNetAdapter].empty()) { setNetAdapter((*this)[Client::kNetAdapter]); } startConnect(url._host, url._port, playTimeOutSec); } -void RtspPlayer::onConnect(const SockException &err){ - if(err.getErrCode() != Err_success) { - onPlayResult_l(err,false); +void RtspPlayer::onConnect(const SockException &err) { + if (err.getErrCode() != Err_success) { + onPlayResult_l(err, false); return; } sendOptions(); } -void RtspPlayer::onRecv(const Buffer::Ptr& buf) { - if(_benchmark_mode && !_play_check_timer){ - //在性能测试模式下,如果rtsp握手完毕后,不再解析rtp包 +void RtspPlayer::onRecv(const Buffer::Ptr &buf) { + if (_benchmark_mode && !_play_check_timer) { + // 在性能测试模式下,如果rtsp握手完毕后,不再解析rtp包 _rtp_recv_ticker.resetTime(); return; } @@ -132,21 +130,21 @@ void RtspPlayer::onRecv(const Buffer::Ptr& buf) { } void RtspPlayer::onError(const SockException &ex) { - //定时器_pPlayTimer为空后表明握手结束了 - onPlayResult_l(ex,!_play_check_timer); + // 定时器_pPlayTimer为空后表明握手结束了 + onPlayResult_l(ex, !_play_check_timer); } // from live555 bool RtspPlayer::handleAuthenticationFailure(const string ¶msStr) { - if(!_realm.empty()){ - //已经认证过了 + if (!_realm.empty()) { + // 已经认证过了 return false; } char *realm = new char[paramsStr.size()]; char *nonce = new char[paramsStr.size()]; char *stale = new char[paramsStr.size()]; - onceToken token(nullptr,[&](){ + onceToken token(nullptr, [&]() { delete[] realm; delete[] nonce; delete[] stale; @@ -169,41 +167,43 @@ bool RtspPlayer::handleAuthenticationFailure(const string ¶msStr) { return false; } -bool RtspPlayer::handleResponse(const string &cmd, const Parser &parser){ +bool RtspPlayer::handleResponse(const string &cmd, const Parser &parser) { string authInfo = parser["WWW-Authenticate"]; - //发送DESCRIBE命令后的回复 - if ((parser.Url() == "401") && handleAuthenticationFailure(authInfo)) { + // 发送DESCRIBE命令后的回复 + if ((parser.status() == "401") && handleAuthenticationFailure(authInfo)) { sendOptions(); return false; } - if(parser.Url() == "302" || parser.Url() == "301"){ + if (parser.status() == "302" || parser.status() == "301") { auto newUrl = parser["Location"]; - if(newUrl.empty()){ + if (newUrl.empty()) { throw std::runtime_error("未找到Location字段(跳转url)"); } play(newUrl); return false; } - if (parser.Url() != "200") { - throw std::runtime_error(StrPrinter << cmd << ":" << parser.Url() << " " << parser.Tail() << endl); + if (parser.status() != "200") { + throw std::runtime_error(StrPrinter << cmd << ":" << parser.status() << " " << parser.statusStr() << endl); } return true; } -void RtspPlayer::handleResDESCRIBE(const Parser& parser) { +void RtspPlayer::handleResDESCRIBE(const Parser &parser) { if (!handleResponse("DESCRIBE", parser)) { return; } _content_base = parser["Content-Base"]; - if(_content_base.empty()){ + if (_content_base.empty()) { _content_base = _play_url; } if (_content_base.back() == '/') { _content_base.pop_back(); } - //解析sdp - SdpParser sdpParser(parser.Content()); + // 解析sdp + SdpParser sdpParser(parser.content()); + + _control_url = sdpParser.getControlUrl(_content_base); string sdp; auto play_track = (TrackType)((int)(*this)[Client::kPlayTrack] - 1); @@ -224,16 +224,16 @@ void RtspPlayer::handleResDESCRIBE(const Parser& parser) { } _rtcp_context.clear(); for (auto &track : _sdp_track) { - if(track->_pt != 0xff){ - setPayloadType(_rtcp_context.size(),track->_pt); + if (track->_pt != 0xff) { + setPayloadType(_rtcp_context.size(), track->_pt); } _rtcp_context.emplace_back(std::make_shared()); } sendSetup(0); } -//有必要的情况下创建udp端口 -void RtspPlayer::createUdpSockIfNecessary(int track_idx){ +// 有必要的情况下创建udp端口 +void RtspPlayer::createUdpSockIfNecessary(int track_idx) { auto &rtpSockRef = _rtp_sock[track_idx]; auto &rtcpSockRef = _rtcp_sock[track_idx]; if (!rtpSockRef || !rtcpSockRef) { @@ -244,41 +244,38 @@ void RtspPlayer::createUdpSockIfNecessary(int track_idx){ } } -//发送SETUP命令 +// 发送SETUP命令 void RtspPlayer::sendSetup(unsigned int track_idx) { _on_response = std::bind(&RtspPlayer::handleResSETUP, this, placeholders::_1, track_idx); auto &track = _sdp_track[track_idx]; auto control_url = track->getControlUrl(_content_base); switch (_rtp_type) { case Rtsp::RTP_TCP: { - sendRtspRequest("SETUP", control_url, {"Transport", StrPrinter << "RTP/AVP/TCP;unicast;interleaved=" << track->_type * 2 << "-" << track->_type * 2 + 1}); - } - break; + sendRtspRequest( + "SETUP", control_url, { "Transport", StrPrinter << "RTP/AVP/TCP;unicast;interleaved=" << track->_type * 2 << "-" << track->_type * 2 + 1 }); + } break; case Rtsp::RTP_MULTICAST: { - sendRtspRequest("SETUP", control_url, {"Transport", "RTP/AVP;multicast"}); - } - break; + sendRtspRequest("SETUP", control_url, { "Transport", "RTP/AVP;multicast" }); + } break; case Rtsp::RTP_UDP: { createUdpSockIfNecessary(track_idx); - sendRtspRequest("SETUP", control_url, {"Transport", - StrPrinter << "RTP/AVP;unicast;client_port=" - << _rtp_sock[track_idx]->get_local_port() << "-" - << _rtcp_sock[track_idx]->get_local_port()}); - } - break; - default: - break; + sendRtspRequest( + "SETUP", control_url, + { "Transport", + StrPrinter << "RTP/AVP;unicast;client_port=" << _rtp_sock[track_idx]->get_local_port() << "-" << _rtcp_sock[track_idx]->get_local_port() }); + } break; + default: break; } } void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int track_idx) { - if (parser.Url() != "200") { - throw std::runtime_error(StrPrinter << "SETUP:" << parser.Url() << " " << parser.Tail() << endl); + if (parser.status() != "200") { + throw std::runtime_error(StrPrinter << "SETUP:" << parser.status() << " " << parser.statusStr() << endl); } if (track_idx == 0) { _session_id = parser["Session"]; _session_id.append(";"); - _session_id = FindField(_session_id.data(), nullptr, ";"); + _session_id = findSubString(_session_id.data(), nullptr, ";"); } auto strTransport = parser["Transport"]; @@ -292,9 +289,9 @@ void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int track_idx) { auto transport_map = Parser::parseArgs(strTransport, ";", "="); RtspSplitter::enableRecvRtp(_rtp_type == Rtsp::RTP_TCP); string ssrc = transport_map["ssrc"]; - if(!ssrc.empty()){ + if (!ssrc.empty()) { sscanf(ssrc.data(), "%x", &_sdp_track[track_idx]->_ssrc); - } else{ + } else { _sdp_track[track_idx]->_ssrc = 0; } @@ -310,47 +307,47 @@ void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int track_idx) { auto &pRtcpSockRef = _rtcp_sock[track_idx]; if (_rtp_type == Rtsp::RTP_MULTICAST) { - //udp组播 - auto multiAddr = transport_map["destination"]; + // udp组播 + auto multiAddr = transport_map["destination"]; pRtpSockRef = createSocket(); - //目前组播仅支持ipv4 + // 目前组播仅支持ipv4 if (!pRtpSockRef->bindUdpSock(rtp_port, "0.0.0.0")) { pRtpSockRef.reset(); throw std::runtime_error("open udp sock err"); } auto fd = pRtpSockRef->rawFD(); - if (-1 == SockUtil::joinMultiAddrFilter(fd, multiAddr.data(), get_peer_ip().data(),get_local_ip().data())) { - SockUtil::joinMultiAddr(fd, multiAddr.data(),get_local_ip().data()); + if (-1 == SockUtil::joinMultiAddrFilter(fd, multiAddr.data(), get_peer_ip().data(), get_local_ip().data())) { + SockUtil::joinMultiAddr(fd, multiAddr.data(), get_local_ip().data()); } - //设置rtcp发送端口 + // 设置rtcp发送端口 pRtcpSockRef = createSocket(); - //目前组播仅支持ipv4 + // 目前组播仅支持ipv4 if (!pRtcpSockRef->bindUdpSock(0, "0.0.0.0")) { - //分配端口失败 + // 分配端口失败 throw runtime_error("open udp socket failed"); } - //设置发送地址和发送端口 + // 设置发送地址和发送端口 auto dst = SockUtil::make_sockaddr(get_peer_ip().data(), rtcp_port); pRtcpSockRef->bindPeerAddr((struct sockaddr *)&(dst)); } else { createUdpSockIfNecessary(track_idx); - //udp单播 + // udp单播 auto dst = SockUtil::make_sockaddr(get_peer_ip().data(), rtp_port); pRtpSockRef->bindPeerAddr((struct sockaddr *)&(dst)); - //发送rtp打洞包 + // 发送rtp打洞包 pRtpSockRef->send("\xce\xfa\xed\xfe", 4); dst = SockUtil::make_sockaddr(get_peer_ip().data(), rtcp_port); - //设置rtcp发送目标,为后续发送rtcp做准备 + // 设置rtcp发送目标,为后续发送rtcp做准备 pRtcpSockRef->bindPeerAddr((struct sockaddr *)&(dst)); } auto peer_ip = get_peer_ip(); weak_ptr weakSelf = static_pointer_cast(shared_from_this()); - //设置rtp over udp接收回调处理函数 - pRtpSockRef->setOnRead([peer_ip, track_idx, weakSelf](const Buffer::Ptr &buf, struct sockaddr *addr , int addr_len) { + // 设置rtp over udp接收回调处理函数 + pRtpSockRef->setOnRead([peer_ip, track_idx, weakSelf](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { auto strongSelf = weakSelf.lock(); if (!strongSelf) { return; @@ -359,13 +356,13 @@ void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int track_idx) { WarnL << "收到其他地址的rtp数据:" << SockUtil::inet_ntoa(addr); return; } - strongSelf->handleOneRtp(track_idx, strongSelf->_sdp_track[track_idx]->_type, - strongSelf->_sdp_track[track_idx]->_samplerate, (uint8_t *) buf->data(), buf->size()); + strongSelf->handleOneRtp( + track_idx, strongSelf->_sdp_track[track_idx]->_type, strongSelf->_sdp_track[track_idx]->_samplerate, (uint8_t *)buf->data(), buf->size()); }); - if(pRtcpSockRef) { - //设置rtcp over udp接收回调处理函数 - pRtcpSockRef->setOnRead([peer_ip, track_idx, weakSelf](const Buffer::Ptr &buf, struct sockaddr *addr , int addr_len) { + if (pRtcpSockRef) { + // 设置rtcp over udp接收回调处理函数 + pRtcpSockRef->setOnRead([peer_ip, track_idx, weakSelf](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { auto strongSelf = weakSelf.lock(); if (!strongSelf) { return; @@ -374,68 +371,66 @@ void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int track_idx) { WarnL << "收到其他地址的rtcp数据:" << SockUtil::inet_ntoa(addr); return; } - strongSelf->onRtcpPacket(track_idx, strongSelf->_sdp_track[track_idx], (uint8_t *) buf->data(), buf->size()); + strongSelf->onRtcpPacket(track_idx, strongSelf->_sdp_track[track_idx], (uint8_t *)buf->data(), buf->size()); }); } } if (track_idx < _sdp_track.size() - 1) { - //需要继续发送SETUP命令 + // 需要继续发送SETUP命令 sendSetup(track_idx + 1); return; } - //所有setup命令发送完毕 - //发送play命令 + // 所有setup命令发送完毕 + // 发送play命令 sendPause(type_play, 0); } void RtspPlayer::sendDescribe() { - //发送DESCRIBE命令后处理函数:handleResDESCRIBE + // 发送DESCRIBE命令后处理函数:handleResDESCRIBE _on_response = std::bind(&RtspPlayer::handleResDESCRIBE, this, placeholders::_1); - sendRtspRequest("DESCRIBE", _play_url, {"Accept", "application/sdp"}); + sendRtspRequest("DESCRIBE", _play_url, { "Accept", "application/sdp" }); } -void RtspPlayer::sendOptions(){ - _on_response = [this](const Parser& parser){ +void RtspPlayer::sendOptions() { + _on_response = [this](const Parser &parser) { if (!handleResponse("OPTIONS", parser)) { return; } - //获取服务器支持的命令 + // 获取服务器支持的命令 _supported_cmd.clear(); - auto public_val = split(parser["Public"],","); - for(auto &cmd : public_val){ + auto public_val = split(parser["Public"], ","); + for (auto &cmd : public_val) { trim(cmd); _supported_cmd.emplace(cmd); } - //发送Describe请求,获取sdp + // 发送Describe请求,获取sdp sendDescribe(); }; sendRtspRequest("OPTIONS", _play_url); } -void RtspPlayer::sendKeepAlive(){ +void RtspPlayer::sendKeepAlive() { _on_response = [](const Parser &parser) {}; - if(_supported_cmd.find("GET_PARAMETER") != _supported_cmd.end()){ - //支持GET_PARAMETER,用此命令保活 - sendRtspRequest("GET_PARAMETER", _content_base); - }else{ - //不支持GET_PARAMETER,用OPTIONS命令保活 + if (_supported_cmd.find("GET_PARAMETER") != _supported_cmd.end()) { + // 支持GET_PARAMETER,用此命令保活 + sendRtspRequest("GET_PARAMETER", _control_url); + } else { + // 不支持GET_PARAMETER,用OPTIONS命令保活 sendRtspRequest("OPTIONS", _play_url); } } -void RtspPlayer::sendPause(int type , uint32_t seekMS){ +void RtspPlayer::sendPause(int type, uint32_t seekMS) { _on_response = std::bind(&RtspPlayer::handleResPAUSE, this, placeholders::_1, type); - //开启或暂停rtsp - switch (type){ - case type_pause: - sendRtspRequest("PAUSE", _content_base); - break; + // 开启或暂停rtsp + switch (type) { + case type_pause: sendRtspRequest("PAUSE", _control_url, {}); break; case type_play: - sendRtspRequest("PLAY", _content_base); - break; + // sendRtspRequest("PLAY", _content_base); + // break; case type_seek: - sendRtspRequest("PLAY", _content_base, {"Range",StrPrinter << "npt=" << setiosflags(ios::fixed) << setprecision(2) << seekMS / 1000.0 << "-"}); + sendRtspRequest("PLAY", _control_url, { "Range", StrPrinter << "npt=" << setiosflags(ios::fixed) << setprecision(2) << seekMS / 1000.0 << "-" }); break; default: WarnL << "unknown type : " << type; @@ -449,38 +444,34 @@ void RtspPlayer::pause(bool bPause) { } void RtspPlayer::speed(float speed) { - sendRtspRequest("PLAY", _content_base, {"Scale", StrPrinter << speed}); + sendRtspRequest("PLAY", _control_url, { "Scale", StrPrinter << speed }); } -void RtspPlayer::handleResPAUSE(const Parser& parser,int type) { - if (parser.Url() != "200") { +void RtspPlayer::handleResPAUSE(const Parser &parser, int type) { + if (parser.status() != "200") { switch (type) { - case type_pause: - WarnL << "Pause failed:" << parser.Url() << " " << parser.Tail() << endl; - break; + case type_pause: WarnL << "Pause failed:" << parser.status() << " " << parser.statusStr(); break; case type_play: - WarnL << "Play failed:" << parser.Url() << " " << parser.Tail() << endl; - onPlayResult_l(SockException(Err_shutdown, StrPrinter << "rtsp play failed:" << parser.Url() << " " << parser.Tail() ), !_play_check_timer); - break; - case type_seek: - WarnL << "Seek failed:" << parser.Url() << " " << parser.Tail() << endl; + WarnL << "Play failed:" << parser.status() << " " << parser.statusStr(); + onPlayResult_l(SockException(Err_other, StrPrinter << "rtsp play failed:" << parser.status() << " " << parser.statusStr()), !_play_check_timer); break; + case type_seek: WarnL << "Seek failed:" << parser.status() << " " << parser.statusStr(); break; } return; } if (type == type_pause) { - //暂停成功! + // 暂停成功! _rtp_check_timer.reset(); return; } - //play或seek成功 + // play或seek成功 uint32_t iSeekTo = 0; - //修正时间轴 + // 修正时间轴 auto strRange = parser["Range"]; if (strRange.size()) { - auto strStart = FindField(strRange.data(), "npt=", "-"); + auto strStart = findSubString(strRange.data(), "npt=", "-"); if (strStart == "now") { strStart = "0"; } @@ -492,60 +483,70 @@ void RtspPlayer::handleResPAUSE(const Parser& parser,int type) { } void RtspPlayer::onWholeRtspPacket(Parser &parser) { + if (!start_with(parser.method(), "RTSP")) { + // 不是rtsp回复,忽略 + WarnL << "Not rtsp response: " << parser.method(); + return; + } try { decltype(_on_response) func; _on_response.swap(func); - if(func){ + if (func) { func(parser); } - parser.Clear(); + parser.clear(); } catch (std::exception &err) { - //定时器_pPlayTimer为空后表明握手结束了 - onPlayResult_l(SockException(Err_other, err.what()),!_play_check_timer); + // 定时器_pPlayTimer为空后表明握手结束了 + onPlayResult_l(SockException(Err_other, err.what()), !_play_check_timer); } } void RtspPlayer::onRtpPacket(const char *data, size_t len) { int trackIdx = -1; uint8_t interleaved = data[1]; - if(interleaved %2 == 0){ + if (interleaved % 2 == 0) { trackIdx = getTrackIndexByInterleaved(interleaved); if (trackIdx == -1) { return; } - handleOneRtp(trackIdx, _sdp_track[trackIdx]->_type, _sdp_track[trackIdx]->_samplerate, (uint8_t *)data + RtpPacket::kRtpTcpHeaderSize, len - RtpPacket::kRtpTcpHeaderSize); - }else{ + handleOneRtp( + trackIdx, _sdp_track[trackIdx]->_type, _sdp_track[trackIdx]->_samplerate, (uint8_t *)data + RtpPacket::kRtpTcpHeaderSize, + len - RtpPacket::kRtpTcpHeaderSize); + } else { trackIdx = getTrackIndexByInterleaved(interleaved - 1); if (trackIdx == -1) { return; } - onRtcpPacket(trackIdx, _sdp_track[trackIdx], (uint8_t *) data + RtpPacket::kRtpTcpHeaderSize, len - RtpPacket::kRtpTcpHeaderSize); + onRtcpPacket(trackIdx, _sdp_track[trackIdx], (uint8_t *)data + RtpPacket::kRtpTcpHeaderSize, len - RtpPacket::kRtpTcpHeaderSize); } } -//此处预留rtcp处理函数 -void RtspPlayer::onRtcpPacket(int track_idx, SdpTrack::Ptr &track, uint8_t *data, size_t len){ - auto rtcp_arr = RtcpHeader::loadFromBytes((char *) data, len); +// 此处预留rtcp处理函数 +void RtspPlayer::onRtcpPacket(int track_idx, SdpTrack::Ptr &track, uint8_t *data, size_t len) { + auto rtcp_arr = RtcpHeader::loadFromBytes((char *)data, len); for (auto &rtcp : rtcp_arr) { _rtcp_context[track_idx]->onRtcp(rtcp); - if ((RtcpType) rtcp->pt == RtcpType::RTCP_SR) { - auto sr = (RtcpSR *) (rtcp); - //设置rtp时间戳与ntp时间戳的对应关系 + if ((RtcpType)rtcp->pt == RtcpType::RTCP_SR) { + auto sr = (RtcpSR *)(rtcp); + // 设置rtp时间戳与ntp时间戳的对应关系 setNtpStamp(track_idx, sr->rtpts, sr->getNtpUnixStampMS()); } } } -void RtspPlayer::onRtpSorted(RtpPacket::Ptr rtppt, int trackidx){ +void RtspPlayer::onRtpSorted(RtpPacket::Ptr rtppt, int trackidx) { _stamp[trackidx] = rtppt->getStampMS(); _rtp_recv_ticker.resetTime(); onRecvRTP(std::move(rtppt), _sdp_track[trackidx]); } -float RtspPlayer::getPacketLossRate(TrackType type) const{ +float RtspPlayer::getPacketLossRate(TrackType type) const { size_t lost = 0, expected = 0; try { auto track_idx = getTrackIndexByTrackType(type); + if (_rtcp_context.empty()) { + return 0; + } auto ctx = _rtcp_context[track_idx]; lost = ctx->getLost(); expected = ctx->getExpectedPackets(); @@ -558,32 +559,33 @@ float RtspPlayer::getPacketLossRate(TrackType type) const{ if (!expected) { return 0; } - return (float) (double(lost) / double(expected)); + return (float)(double(lost) / double(expected)); } -uint32_t RtspPlayer::getProgressMilliSecond() const{ - return MAX(_stamp[0],_stamp[1]); +uint32_t RtspPlayer::getProgressMilliSecond() const { + return MAX(_stamp[0], _stamp[1]); } void RtspPlayer::seekToMilliSecond(uint32_t ms) { - sendPause(type_seek,ms); + sendPause(type_seek, ms); } void RtspPlayer::sendRtspRequest(const string &cmd, const string &url, const std::initializer_list &header) { string key; StrCaseMap header_map; int i = 0; - for(auto &val : header){ - if(++i % 2 == 0){ - header_map.emplace(key,val); - }else{ + for (auto &val : header) { + if (++i % 2 == 0) { + header_map.emplace(key, val); + } else { key = val; } } - sendRtspRequest(cmd,url,header_map); + + sendRtspRequest(cmd, url, header_map); } -void RtspPlayer::sendRtspRequest(const string &cmd, const string &url,const StrCaseMap &header_const) { +void RtspPlayer::sendRtspRequest(const string &cmd, const string &url, const StrCaseMap &header_const) { auto header = header_const; header.emplace("CSeq", StrPrinter << _cseq_send++); header.emplace("User-Agent", kServerName); @@ -625,43 +627,45 @@ void RtspPlayer::sendRtspRequest(const string &cmd, const string &url,const StrC _StrPrinter printer; printer << cmd << " " << url << " RTSP/1.0\r\n"; - for (auto &pr : header){ + + TraceL << cmd << " "<< url; + for (auto &pr : header) { printer << pr.first << ": " << pr.second << "\r\n"; } printer << "\r\n"; SockSender::send(std::move(printer)); } -void RtspPlayer::onBeforeRtpSorted(const RtpPacket::Ptr &rtp, int track_idx){ +void RtspPlayer::onBeforeRtpSorted(const RtpPacket::Ptr &rtp, int track_idx) { auto &rtcp_ctx = _rtcp_context[track_idx]; rtcp_ctx->onRtp(rtp->getSeq(), rtp->getStamp(), rtp->ntp_stamp, rtp->sample_rate, rtp->size() - RtpPacket::kRtpTcpHeaderSize); auto &ticker = _rtcp_send_ticker[track_idx]; if (ticker.elapsedTime() < 3 * 1000) { - //时间未到 + // 时间未到 return; } auto &rtcp_flag = _send_rtcp[track_idx]; - //每3秒发送一次心跳,rtcp与rtsp信令轮流心跳,该特性用于兼容issue:642 - //有些rtsp服务器需要rtcp保活,有些需要发送信令保活 + // 每3秒发送一次心跳,rtcp与rtsp信令轮流心跳,该特性用于兼容issue:642 + // 有些rtsp服务器需要rtcp保活,有些需要发送信令保活 - //发送信令保活 + // 发送信令保活 if (!rtcp_flag) { if (track_idx == 0) { sendKeepAlive(); } ticker.resetTime(); - //下次发送rtcp保活 + // 下次发送rtcp保活 rtcp_flag = true; return; } - //发送rtcp + // 发送rtcp static auto send_rtcp = [](RtspPlayer *thiz, int index, Buffer::Ptr ptr) { if (thiz->_rtp_type == Rtsp::RTP_TCP) { auto &track = thiz->_sdp_track[index]; - thiz->send(makeRtpOverTcpPrefix((uint16_t) (ptr->size()), track->_interleaved + 1)); + thiz->send(makeRtpOverTcpPrefix((uint16_t)(ptr->size()), track->_interleaved + 1)); thiz->send(std::move(ptr)); } else { thiz->_rtcp_sock[index]->send(std::move(ptr)); @@ -670,39 +674,39 @@ void RtspPlayer::onBeforeRtpSorted(const RtpPacket::Ptr &rtp, int track_idx){ auto ssrc = rtp->getSSRC(); auto rtcp = rtcp_ctx->createRtcpRR(ssrc + 1, ssrc); - auto rtcp_sdes = RtcpSdes::create({kServerName}); - rtcp_sdes->chunks.type = (uint8_t) SdesType::RTCP_SDES_CNAME; + auto rtcp_sdes = RtcpSdes::create({ kServerName }); + rtcp_sdes->chunks.type = (uint8_t)SdesType::RTCP_SDES_CNAME; rtcp_sdes->chunks.ssrc = htonl(ssrc); send_rtcp(this, track_idx, std::move(rtcp)); send_rtcp(this, track_idx, RtcpHeader::toBuffer(rtcp_sdes)); ticker.resetTime(); - //下次发送信令保活 + // 下次发送信令保活 rtcp_flag = false; } -void RtspPlayer::onPlayResult_l(const SockException &ex , bool handshake_done) { +void RtspPlayer::onPlayResult_l(const SockException &ex, bool handshake_done) { if (ex.getErrCode() == Err_shutdown) { - //主动shutdown的,不触发回调 + // 主动shutdown的,不触发回调 return; } WarnL << ex.getErrCode() << " " << ex.what(); if (!handshake_done) { - //开始播放阶段 + // 开始播放阶段 _play_check_timer.reset(); onPlayResult(ex); - //是否为性能测试模式 + // 是否为性能测试模式 _benchmark_mode = (*this)[Client::kBenchmarkMode].as(); } else if (ex) { - //播放成功后异常断开回调 + // 播放成功后异常断开回调 onShutdown(ex); } else { - //恢复播放 + // 恢复播放 onResume(); } if (!ex) { - //播放成功,恢复rtp接收超时定时器 + // 播放成功,恢复rtp接收超时定时器 _rtp_recv_ticker.resetTime(); auto timeoutMS = (*this)[Client::kMediaTimeoutMS].as(); weak_ptr weakSelf = static_pointer_cast(shared_from_this()); @@ -712,13 +716,13 @@ void RtspPlayer::onPlayResult_l(const SockException &ex , bool handshake_done) { return false; } if (strongSelf->_rtp_recv_ticker.elapsedTime() > timeoutMS) { - //接收rtp媒体数据包超时 + // 接收rtp媒体数据包超时 strongSelf->onPlayResult_l(SockException(Err_timeout, "receive rtp timeout"), true); return false; } return true; }; - //创建rtp数据接收超时检测定时器 + // 创建rtp数据接收超时检测定时器 _rtp_check_timer = std::make_shared(timeoutMS / 2000.0f, lam, getPoller()); } else { sendTeardown(); @@ -747,13 +751,12 @@ int RtspPlayer::getTrackIndexByTrackType(TrackType track_type) const { if (_sdp_track.size() == 1) { return 0; } - throw SockException(Err_shutdown, StrPrinter << "no such track with type:" << getTrackString(track_type)); + throw SockException(Err_other, StrPrinter << "no such track with type:" << getTrackString(track_type)); } /////////////////////////////////////////////////// // RtspPlayerImp -float RtspPlayerImp::getDuration() const -{ +float RtspPlayerImp::getDuration() const { return _demuxer ? _demuxer->getDuration() : 0; } @@ -770,13 +773,11 @@ void RtspPlayerImp::addTrackCompleted() { } } -std::vector RtspPlayerImp::getTracks(bool ready /*= true*/) const -{ +std::vector RtspPlayerImp::getTracks(bool ready /*= true*/) const { return _demuxer ? _demuxer->getTracks(ready) : Super::getTracks(ready); } -bool RtspPlayerImp::onCheckSDP(const std::string &sdp) -{ +bool RtspPlayerImp::onCheckSDP(const std::string &sdp) { _rtsp_media_src = std::dynamic_pointer_cast(_media_src); if (_rtsp_media_src) { _rtsp_media_src->setSdp(sdp); @@ -788,7 +789,7 @@ bool RtspPlayerImp::onCheckSDP(const std::string &sdp) } void RtspPlayerImp::onRecvRTP(RtpPacket::Ptr rtp, const SdpTrack::Ptr &track) { - //rtp解复用时可以判断是否为关键帧起始位置 + // rtp解复用时可以判断是否为关键帧起始位置 auto key_pos = _demuxer->inputRtp(rtp); if (_rtsp_media_src) { _rtsp_media_src->onWrite(std::move(rtp), key_pos); diff --git a/src/Rtsp/RtspPlayer.h b/src/Rtsp/RtspPlayer.h index 5a9bd1ee..2b4f34c5 100644 --- a/src/Rtsp/RtspPlayer.h +++ b/src/Rtsp/RtspPlayer.h @@ -129,6 +129,7 @@ private: std::string _session_id; uint32_t _cseq_send = 1; std::string _content_base; + std::string _control_url; Rtsp::eRtpType _rtp_type = Rtsp::RTP_TCP; //当前rtp时间戳 diff --git a/src/Rtsp/RtspPlayerImp.h b/src/Rtsp/RtspPlayerImp.h index 9243696a..ad0eaa12 100644 --- a/src/Rtsp/RtspPlayerImp.h +++ b/src/Rtsp/RtspPlayerImp.h @@ -28,7 +28,7 @@ public: RtspPlayerImp(const toolkit::EventPoller::Ptr &poller) : Super(poller) {} ~RtspPlayerImp() override { - DebugL << std::endl; + DebugL; } float getProgress() const override { diff --git a/src/Rtsp/RtspPusher.cpp b/src/Rtsp/RtspPusher.cpp index 01727adc..4748fb15 100644 --- a/src/Rtsp/RtspPusher.cpp +++ b/src/Rtsp/RtspPusher.cpp @@ -26,7 +26,7 @@ RtspPusher::RtspPusher(const EventPoller::Ptr &poller, const RtspMediaSource::Pt RtspPusher::~RtspPusher() { teardown(); - DebugL << endl; + DebugL; } void RtspPusher::sendTeardown(){ @@ -147,7 +147,7 @@ void RtspPusher::onWholeRtspPacket(Parser &parser) { if (func) { func(parser); } - parser.Clear(); + parser.clear(); } void RtspPusher::onRtpPacket(const char *data, size_t len) { @@ -188,11 +188,11 @@ void RtspPusher::sendAnnounce() { void RtspPusher::handleResAnnounce(const Parser &parser) { string authInfo = parser["WWW-Authenticate"]; //发送DESCRIBE命令后的回复 - if ((parser.Url() == "401") && handleAuthenticationFailure(authInfo)) { + if ((parser.status() == "401") && handleAuthenticationFailure(authInfo)) { sendAnnounce(); return; } - if (parser.Url() == "302") { + if (parser.status() == "302") { auto newUrl = parser["Location"]; if (newUrl.empty()) { throw std::runtime_error("未找到Location字段(跳转url)"); @@ -200,8 +200,8 @@ void RtspPusher::handleResAnnounce(const Parser &parser) { publish(newUrl); return; } - if (parser.Url() != "200") { - throw std::runtime_error(StrPrinter << "ANNOUNCE:" << parser.Url() << " " << parser.Tail()); + if (parser.status() != "200") { + throw std::runtime_error(StrPrinter << "ANNOUNCE:" << parser.status() << " " << parser.statusStr()); } _content_base = parser["Content-Base"]; @@ -285,19 +285,19 @@ void RtspPusher::sendSetup(unsigned int track_idx) { } void RtspPusher::handleResSetup(const Parser &parser, unsigned int track_idx) { - if (parser.Url() != "200") { - throw std::runtime_error(StrPrinter << "SETUP:" << parser.Url() << " " << parser.Tail() << endl); + if (parser.status() != "200") { + throw std::runtime_error(StrPrinter << "SETUP:" << parser.status() << " " << parser.statusStr() << endl); } if (track_idx == 0) { _session_id = parser["Session"]; _session_id.append(";"); - _session_id = FindField(_session_id.data(), nullptr, ";"); + _session_id = findSubString(_session_id.data(), nullptr, ";"); } auto transport = parser["Transport"]; if (transport.find("TCP") != string::npos || transport.find("interleaved") != string::npos) { _rtp_type = Rtsp::RTP_TCP; - string interleaved = FindField(FindField((transport + ";").data(), "interleaved=", ";").data(), NULL, "-"); + string interleaved = findSubString(findSubString((transport + ";").data(), "interleaved=", ";").data(), NULL, "-"); _track_vec[track_idx]->_interleaved = atoi(interleaved.data()); } else if (transport.find("multicast") != string::npos) { throw std::runtime_error("SETUP rtsp pusher can not support multicast!"); @@ -305,9 +305,9 @@ void RtspPusher::handleResSetup(const Parser &parser, unsigned int track_idx) { _rtp_type = Rtsp::RTP_UDP; createUdpSockIfNecessary(track_idx); const char *strPos = "server_port="; - auto port_str = FindField((transport + ";").data(), strPos, ";"); - uint16_t rtp_port = atoi(FindField(port_str.data(), NULL, "-").data()); - uint16_t rtcp_port = atoi(FindField(port_str.data(), "-", NULL).data()); + auto port_str = findSubString((transport + ";").data(), strPos, ";"); + uint16_t rtp_port = atoi(findSubString(port_str.data(), NULL, "-").data()); + uint16_t rtcp_port = atoi(findSubString(port_str.data(), "-", NULL).data()); auto &rtp_sock = _rtp_sock[track_idx]; auto &rtcp_sock = _rtcp_sock[track_idx]; diff --git a/src/Rtsp/RtspSession.cpp b/src/Rtsp/RtspSession.cpp index 13f57d54..d780a66d 100644 --- a/src/Rtsp/RtspSession.cpp +++ b/src/Rtsp/RtspSession.cpp @@ -80,7 +80,7 @@ void RtspSession::onError(const SockException &err) { //流量统计事件广播 GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_bytes_usage >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _bytes_usage, duration, is_player, static_cast(*this)); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _bytes_usage, duration, is_player, *this); } //如果是主动关闭的,那么不延迟注销 @@ -128,11 +128,11 @@ void RtspSession::onRecv(const Buffer::Ptr &buf) { } void RtspSession::onWholeRtspPacket(Parser &parser) { - string method = parser.Method(); //提取出请求命令字 + string method = parser.method(); //提取出请求命令字 _cseq = atoi(parser["CSeq"].data()); if (_content_base.empty() && method != "GET") { - _content_base = parser.Url(); - _media_info.parse(parser.FullUrl()); + _content_base = parser.url(); + _media_info.parse(parser.fullUrl()); _media_info.schema = RTSP_SCHEMA; } @@ -160,7 +160,7 @@ void RtspSession::onWholeRtspPacket(Parser &parser) { } (this->*(it->second))(parser); - parser.Clear(); + parser.clear(); } void RtspSession::onRtpPacket(const char *data, size_t len) { @@ -187,7 +187,7 @@ void RtspSession::onRtcpPacket(int track_idx, SdpTrack::Ptr &track, const char * } ssize_t RtspSession::getContentLength(Parser &parser) { - if(parser.Method() == "POST"){ + if(parser.method() == "POST"){ //http post请求的content数据部分是base64编码后的rtsp请求信令包 return remainDataSize(); } @@ -200,7 +200,7 @@ void RtspSession::handleReq_Options(const Parser &parser) { } void RtspSession::handleReq_ANNOUNCE(const Parser &parser) { - auto full_url = parser.FullUrl(); + auto full_url = parser.fullUrl(); _content_base = full_url; if (end_with(full_url, ".sdp")) { //去除.sdp后缀,防止EasyDarwin推流器强制添加.sdp后缀 @@ -250,7 +250,7 @@ void RtspSession::handleReq_ANNOUNCE(const Parser &parser) { throw SockException(Err_shutdown, err); } - SdpParser sdpParser(parser.Content()); + SdpParser sdpParser(parser.content()); _sessionid = makeRandStr(12); _sdp_track = sdpParser.getAvailableTrack(); if (_sdp_track.empty()) { @@ -270,7 +270,7 @@ void RtspSession::handleReq_ANNOUNCE(const Parser &parser) { //获取所有权 _push_src_ownership = _push_src->getOwnership(); _push_src->setProtocolOption(option); - _push_src->setSdp(parser.Content()); + _push_src->setSdp(parser.content()); } _push_src->setListener(static_pointer_cast(shared_from_this())); @@ -294,7 +294,7 @@ void RtspSession::handleReq_ANNOUNCE(const Parser &parser) { }; //rtsp推流需要鉴权 - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtsp_push, _media_info, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtsp_push, _media_info, invoker, *this); if (!flag) { //该事件无人监听,默认不鉴权 onRes("", ProtocolOption()); @@ -352,7 +352,7 @@ void RtspSession::emitOnPlay(){ }; //广播通用播放url鉴权事件 - auto flag = _emit_on_play ? false : NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _media_info, invoker, static_cast(*this)); + auto flag = _emit_on_play ? false : NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _media_info, invoker, *this); if (!flag) { //该事件无人监听,默认不鉴权 onRes(""); @@ -392,7 +392,7 @@ void RtspSession::handleReq_Describe(const Parser &parser) { if(_rtsp_realm.empty()){ //广播是否需要rtsp专属认证事件 - if (!NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastOnGetRtspRealm, _media_info, invoker, static_cast(*this))) { + if (!NOTICE_EMIT(BroadcastOnGetRtspRealmArgs, Broadcast::kBroadcastOnGetRtspRealm, _media_info, invoker, *this)) { //无人监听此事件,说明无需认证 invoker(""); } @@ -497,7 +497,7 @@ void RtspSession::onAuthBasic(const string &realm, const string &auth_base64) { }; //此时必须提供明文密码 - if (!NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastOnRtspAuth, _media_info, realm, user, true, invoker, static_cast(*this))) { + if (!NOTICE_EMIT(BroadcastOnRtspAuthArgs, Broadcast::kBroadcastOnRtspAuth, _media_info, realm, user, true, invoker, *this)) { //表明该流需要认证却没监听请求密码事件,这一般是大意的程序所为,警告之 WarnP(this) << "请监听kBroadcastOnRtspAuth事件!"; //但是我们还是忽略认证以便完成播放 @@ -581,7 +581,7 @@ void RtspSession::onAuthDigest(const string &realm,const string &auth_md5){ }; //此时可以提供明文或md5加密的密码 - if(!NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastOnRtspAuth, _media_info, realm, username, false, invoker, static_cast(*this))){ + if(!NOTICE_EMIT(BroadcastOnRtspAuthArgs, Broadcast::kBroadcastOnRtspAuth, _media_info, realm, username, false, invoker, *this)){ //表明该流需要认证却没监听请求密码事件,这一般是大意的程序所为,警告之 WarnP(this) << "请监听kBroadcastOnRtspAuth事件!"; //但是我们还是忽略认证以便完成播放 @@ -595,8 +595,8 @@ void RtspSession::onAuthUser(const string &realm,const string &authorization){ return; } //请求中包含认证信息 - auto authType = FindField(authorization.data(),NULL," "); - auto authStr = FindField(authorization.data()," ",NULL); + auto authType = findSubString(authorization.data(), NULL, " "); + auto authStr = findSubString(authorization.data(), " ", NULL); if(authType.empty() || authStr.empty()){ //认证信息格式不合法,回复401 Unauthorized onAuthFailed(realm,"can not find auth type or auth string"); @@ -628,25 +628,50 @@ void RtspSession::send_SessionNotFound() { void RtspSession::handleReq_Setup(const Parser &parser) { //处理setup命令,该函数可能进入多次 - int trackIdx = getTrackIndexByControlUrl(parser.FullUrl()); + int trackIdx = getTrackIndexByControlUrl(parser.fullUrl()); SdpTrack::Ptr &trackRef = _sdp_track[trackIdx]; if (trackRef->_inited) { //已经初始化过该Track throw SockException(Err_shutdown, "can not setup one track twice"); } - trackRef->_inited = true; //现在初始化 - if(_rtp_type == Rtsp::RTP_Invalid){ - auto &strTransport = parser["Transport"]; - if(strTransport.find("TCP") != string::npos){ - _rtp_type = Rtsp::RTP_TCP; - }else if(strTransport.find("multicast") != string::npos){ - _rtp_type = Rtsp::RTP_MULTICAST; - }else{ - _rtp_type = Rtsp::RTP_UDP; + static auto getRtpTypeStr = [](const int type) { + switch (type) + { + case Rtsp::RTP_TCP: + return "TCP"; + case Rtsp::RTP_UDP: + return "UDP"; + case Rtsp::RTP_MULTICAST: + return "MULTICAST"; + default: + return "Invalid"; } + }; + + if (_rtp_type == Rtsp::RTP_Invalid) { + auto &strTransport = parser["Transport"]; + auto rtpType = Rtsp::RTP_Invalid; + if (strTransport.find("TCP") != string::npos) { + rtpType = Rtsp::RTP_TCP; + } else if (strTransport.find("multicast") != string::npos) { + rtpType = Rtsp::RTP_MULTICAST; + } else { + rtpType = Rtsp::RTP_UDP; + } + //检查RTP传输类型限制 + GET_CONFIG(int, transport, Rtsp::kRtpTransportType); + if (transport != Rtsp::RTP_Invalid && transport != rtpType) { + WarnL << "rtsp client setup transport " << getRtpTypeStr(rtpType) << " but config force transport " << getRtpTypeStr(transport); + //配置限定RTSP传输方式,但是客户端握手方式不一致,返回461 + sendRtspResponse("461 Unsupported transport"); + return; + } + _rtp_type = rtpType; } + trackRef->_inited = true; //现在初始化 + //允许接收rtp、rtcp包 RtspSplitter::enableRecvRtp(_rtp_type == Rtsp::RTP_TCP); @@ -690,17 +715,17 @@ void RtspSession::handleReq_Setup(const Parser &parser) { _rtcp_socks[trackIdx] = pr.second; //设置客户端内网端口信息 - string strClientPort = FindField(parser["Transport"].data(), "client_port=", NULL); - uint16_t ui16RtpPort = atoi(FindField(strClientPort.data(), NULL, "-").data()); - uint16_t ui16RtcpPort = atoi(FindField(strClientPort.data(), "-", NULL).data()); + string strClientPort = findSubString(parser["Transport"].data(), "client_port=", NULL); + uint16_t ui16RtpPort = atoi(findSubString(strClientPort.data(), NULL, "-").data()); + uint16_t ui16RtcpPort = atoi(findSubString(strClientPort.data(), "-", NULL).data()); auto peerAddr = SockUtil::make_sockaddr(get_peer_ip().data(), ui16RtpPort); //设置rtp发送目标地址 - pr.first->bindPeerAddr((struct sockaddr *) (&peerAddr)); + pr.first->bindPeerAddr((struct sockaddr *) (&peerAddr), 0, true); //设置rtcp发送目标地址 peerAddr = SockUtil::make_sockaddr(get_peer_ip().data(), ui16RtcpPort); - pr.second->bindPeerAddr((struct sockaddr *) (&peerAddr)); + pr.second->bindPeerAddr((struct sockaddr *) (&peerAddr), 0, true); //尝试获取客户端nat映射地址 startListenPeerUdpData(trackIdx); @@ -785,7 +810,7 @@ void RtspSession::handleReq_Play(const Parser &parser) { if (!strRange.empty()) { //这是seek操作 res_header.emplace("Range", strRange); - auto strStart = FindField(strRange.data(), "npt=", "-"); + auto strStart = findSubString(strRange.data(), "npt=", "-"); if (strStart == "now") { strStart = "0"; } @@ -832,7 +857,11 @@ void RtspSession::handleReq_Play(const Parser &parser) { if (!_play_reader && _rtp_type != Rtsp::RTP_MULTICAST) { weak_ptr weak_self = static_pointer_cast(shared_from_this()); _play_reader = play_src->getRing()->attach(getPoller(), use_gop); - _play_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); }); + _play_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(static_pointer_cast(weak_self.lock())); + return ret; + }); _play_reader->setDetachCB([weak_self]() { auto strong_self = weak_self.lock(); if (!strong_self) { @@ -915,9 +944,9 @@ void RtspSession::handleReq_Post(const Parser &parser) { }); }; - if(!parser.Content().empty()){ + if(!parser.content().empty()){ //http poster后面的粘包 - _on_recv(std::make_shared(parser.Content())); + _on_recv(std::make_shared(parser.content())); } sendRtspResponse("200 OK", diff --git a/src/Rtsp/RtspSplitter.cpp b/src/Rtsp/RtspSplitter.cpp index a29e1249..f5d1e792 100644 --- a/src/Rtsp/RtspSplitter.cpp +++ b/src/Rtsp/RtspSplitter.cpp @@ -61,11 +61,11 @@ ssize_t RtspSplitter::onRecvHeader(const char *data, size_t len) { onRtpPacket(data,len); return 0; } - _parser.Parse(data); + _parser.parse(data, len); auto ret = getContentLength(_parser); if(ret == 0){ onWholeRtspPacket(_parser); - _parser.Clear(); + _parser.clear(); } return ret; } @@ -73,7 +73,7 @@ ssize_t RtspSplitter::onRecvHeader(const char *data, size_t len) { void RtspSplitter::onRecvContent(const char *data, size_t len) { _parser.setContent(string(data,len)); onWholeRtspPacket(_parser); - _parser.Clear(); + _parser.clear(); } void RtspSplitter::enableRecvRtp(bool enable) { diff --git a/src/Rtsp/UDPServer.cpp b/src/Rtsp/UDPServer.cpp index 4f648ddf..bb9e4642 100644 --- a/src/Rtsp/UDPServer.cpp +++ b/src/Rtsp/UDPServer.cpp @@ -82,10 +82,12 @@ void UDPServer::onRecv(int interleaved, const Buffer::Ptr &buf, struct sockaddr* return; } auto &ref = it0->second; - for (auto it1 = ref.begin(); it1 != ref.end(); ++it1) { + for (auto it1 = ref.begin(); it1 != ref.end();) { auto &func = it1->second; if (!func(interleaved, buf, peer_addr)) { it1 = ref.erase(it1); + } else { + ++it1; } } if (ref.size() == 0) { diff --git a/src/Shell/ShellSession.cpp b/src/Shell/ShellSession.cpp index fe0c23fd..85c682ee 100644 --- a/src/Shell/ShellSession.cpp +++ b/src/Shell/ShellSession.cpp @@ -135,9 +135,9 @@ inline void ShellSession::pleaseInputPasswd() { }); }; - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastShellLogin,_strUserName,passwd,invoker,static_cast(*this)); - if(!flag){ - //如果无人监听shell登录事件,那么默认shell无法登录 + auto flag = NOTICE_EMIT(BroadcastShellLoginArgs, Broadcast::kBroadcastShellLogin, _strUserName, passwd, invoker, *this); + if (!flag) { + // 如果无人监听shell登录事件,那么默认shell无法登录 onAuth("please listen kBroadcastShellLogin event"); } return true; diff --git a/src/TS/TSMediaSource.h b/src/TS/TSMediaSource.h index 71797c1b..b691d15f 100644 --- a/src/TS/TSMediaSource.h +++ b/src/TS/TSMediaSource.h @@ -50,8 +50,8 @@ public: return _ring; } - void getPlayerList(const std::function> &info_list)> &cb, - const std::function(std::shared_ptr &&info)> &on_change) override { + void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) override { _ring->getInfoList(cb, on_change); } diff --git a/srt/SrtTransportImp.cpp b/srt/SrtTransportImp.cpp index 91ae7488..56d6bd8a 100644 --- a/srt/SrtTransportImp.cpp +++ b/srt/SrtTransportImp.cpp @@ -16,9 +16,11 @@ SrtTransportImp::~SrtTransportImp() { // 流量统计事件广播 GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_total_bytes >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent( - Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, !_is_pusher, - static_cast(*this)); + try { + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, !_is_pusher, *this); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } } } @@ -170,9 +172,7 @@ void SrtTransportImp::emitOnPublish() { }; // 触发推流鉴权事件 - auto flag = NoticeCenter::Instance().emitEvent( - Broadcast::kBroadcastMediaPublish, MediaOriginType::srt_push, _media_info, invoker, - static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::srt_push, _media_info, invoker, *this); if (!flag) { // 该事件无人监听,默认不鉴权 invoker("", ProtocolOption()); @@ -195,8 +195,7 @@ void SrtTransportImp::emitOnPlay() { }); }; - auto flag = NoticeCenter::Instance().emitEvent( - Broadcast::kBroadcastMediaPlayed, _media_info, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _media_info, invoker, *this); if (!flag) { doPlay(); } @@ -225,7 +224,11 @@ void SrtTransportImp::doPlay() { ts_src->pause(false); strong_self->_ts_reader = ts_src->getRing()->attach(strong_self->getPoller()); weak_ptr weak_session = strong_self->getSession(); - strong_self->_ts_reader->setGetInfoCB([weak_session]() { return weak_session.lock(); }); + strong_self->_ts_reader->setGetInfoCB([weak_session]() { + Any ret; + ret.set(static_pointer_cast(weak_session.lock())); + return ret; + }); strong_self->_ts_reader->setDetachCB([weak_self]() { auto strong_self = weak_self.lock(); if (!strong_self) { diff --git a/tests/README.md b/tests/README.md index 448ae9ec..81516128 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,6 +1,6 @@ 此目录下的所有.cpp文件将被编译成可执行程序(不包含此目录下的子目录). 子目录DeviceHK为海康IPC的适配程序,需要先下载海康的SDK才能编译, -由于操作麻烦,所以仅把源码放在这仅供参考. +由于操作麻烦,所以仅把源码放在这里仅供参考. - test_benchmark.cpp diff --git a/tests/test_bench_forward.cpp b/tests/test_bench_forward.cpp new file mode 100644 index 00000000..634d7b8e --- /dev/null +++ b/tests/test_bench_forward.cpp @@ -0,0 +1,267 @@ +/* + * 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 +#include +#include +#include +#include "Util/logger.h" +#include "Util/onceToken.h" +#include "Util/CMD.h" +#include "Util/File.h" +#include "Common/config.h" +#include "Common/Parser.h" +#include "Rtsp/Rtsp.h" +#include "Thread/WorkThreadPool.h" +#include "Pusher/MediaPusher.h" +#include "Player/PlayerProxy.h" + +using namespace std; +using namespace toolkit; +using namespace mediakit; + +class CMD_main : public CMD { +public: + CMD_main() { + _parser.reset(new OptionParser(nullptr)); + + (*_parser) << Option('l',/*该选项简称,如果是\x00则说明无简称*/ + "level",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + to_string(LTrace).data(),/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "日志等级,LTrace~LError(0~4)",/*该选项说明文字*/ + nullptr); + + + (*_parser) << Option('t',/*该选项简称,如果是\x00则说明无简称*/ + "threads",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + to_string(thread::hardware_concurrency()).data(),/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "启动事件触发线程数",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('i',/*该选项简称,如果是\x00则说明无简称*/ + "inputs",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "/tmp/inputs.txt",/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "拉流地址配置文件,支持rtmp、rtsp, hls,多个地址以 \"换行符\" 分割",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('o',/*该选项简称,如果是\x00则说明无简称*/ + "outputs",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "/tmp/outputs.txt",/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "推流地址配置文件,支持rtmp、rtsp,多个地址以 \"换行符\" 分割",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('d',/*该选项简称,如果是\x00则说明无简称*/ + "delay",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "50",/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "启动拉流代理间隔,单位毫秒",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('m',/*该选项简称,如果是\x00则说明无简称*/ + "merge",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "300",/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "合并写毫秒,合并写能提高性能",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('T',/*该选项简称,如果是\x00则说明无简称*/ + "rtp",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + to_string((int) (Rtsp::RTP_TCP)).data(),/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "rtsp拉流方式,支持tcp/udp/multicast:0/1/2",/*该选项说明文字*/ + nullptr); + + } + + ~CMD_main() override {} + + const char *description() const override { + return "主程序命令参数"; + } +}; + + +//此程序为zlm的转推性能测试工具,用于测试拉流代理转推性能 +int main(int argc, char *argv[]) { + CMD_main cmd_main; + try { + cmd_main.operator()(argc, argv); + } catch (ExitException &) { + return 0; + } catch (std::exception &ex) { + cout << ex.what() << endl; + return -1; + } + + int threads = cmd_main["threads"]; + LogLevel logLevel = (LogLevel) cmd_main["level"].as(); + logLevel = MIN(MAX(logLevel, LTrace), LError); + auto in_urls = cmd_main["inputs"]; + auto out_urls = cmd_main["outputs"]; + auto rtp_type = cmd_main["rtp"].as(); + auto delay_ms = cmd_main["delay"].as(); + auto merge_ms = cmd_main["merge"].as(); + + //设置日志 + Logger::Instance().add(std::make_shared("ConsoleChannel", logLevel)); + //启动异步日志线程 + Logger::Instance().setWriter(std::make_shared()); + + //设置线程数 + EventPollerPool::setPoolSize(threads); + WorkThreadPool::setPoolSize(threads); + + //设置合并写 + mINI::Instance()[General::kMergeWriteMS] = merge_ms; + + + std::vector input_urls; + std::vector output_urls; + + auto parse_urls = [&]() { + // 获取输入源列表 + auto inputs = ::split(toolkit::File::loadFile(in_urls.c_str()), "\n"); + for(auto &url : inputs){ + if(url.empty() || url.find("://") == std::string::npos) { + continue; + } + auto input_url = ::trim(url); + input_urls.emplace_back(input_url); + } + // 获取输出源列表 + auto outputs = ::split(toolkit::File::loadFile(out_urls.c_str()), "\n"); + for(auto &url : outputs){ + if(url.empty() || url.find("://") == std::string::npos){ + continue; + } + auto output_url = ::trim(url); + output_urls.emplace_back(output_url); + } + + if(input_urls.empty() || input_urls.size() != output_urls.size()){ + return -1; + } + + for(size_t i = 0; i < input_urls.size(); i++){ + InfoL << "拉流地址: " << input_urls[i] << ",推流地址:" << output_urls[i]; + } + return 0; + }; + + if (0 != parse_urls()){ + cout << "请检查inputs和outputs文件是否正确!" << endl; + return -1; + } + + //推流器map + recursive_mutex mtx; + unordered_map proxy_map; + unordered_map pusher_map; + + auto add_pusher = [&](const MediaSource::Ptr &src, const string &url, int index) { + auto pusher = std::make_shared(src); + pusher->setOnCreateSocket([](const EventPoller::Ptr &poller) { + //socket关闭互斥锁,提高性能 + return std::make_shared(poller, false); + }); + //设置推流失败监听 + pusher->setOnPublished([&mtx, &pusher_map, index](const SockException &ex) { + if (ex) { + //推流失败,移除之 + lock_guard lck(mtx); + pusher_map.erase(index); + } + }); + //设置推流中途断开监听 + pusher->setOnShutdown([&mtx, &pusher_map, index](const SockException &ex) { + //推流中途失败,移除之 + lock_guard lck(mtx); + pusher_map.erase(index); + }); + //设置rtsp推流方式(在rtsp推流时有效) + (*pusher)[Client::kRtpType] = rtp_type; + pusher->publish(url); + //保持对象不销毁 + lock_guard lck(mtx); + pusher_map.emplace(index, std::move(pusher)); + //休眠后再启动下一个推流,防止短时间海量链接 + if (delay_ms > 0) { + usleep(1000 * delay_ms); + } + }; + + // 添加转推任务 + for(size_t i = 0; i < input_urls.size(); i++) { + //休眠一秒打印 + sleep(1); + auto schema = findSubString(output_urls[i].data(), nullptr, "://"); + if (schema != RTSP_SCHEMA && schema != RTMP_SCHEMA) { + cout << "推流协议只支持rtsp或rtmp!" << endl; + return -1; + } + ProtocolOption option; + option.enable_ts = false; + option.enable_fmp4 = false; + option.enable_hls = false; + option.enable_mp4 = false; + option.modify_stamp = (int)ProtocolOption::kModifyStampRelative; + //添加拉流代理 + auto proxy = std::make_shared(DEFAULT_VHOST, "app", std::to_string(i), option, -1, nullptr, 1); + //开始拉流代理 + proxy->play(input_urls[i]); + proxy_map.emplace(i, std::move(proxy)); + } + + // 设置退出信号 + static bool exit_flag = false; + signal(SIGINT, [](int) { exit_flag = true; }); + while (!exit_flag) { + //休眠一秒打印 + sleep(1); + + size_t alive_pusher = 0; + { + lock_guard lck(mtx); + alive_pusher = pusher_map.size(); + } + InfoL << "在线转推器个数:" << alive_pusher; + + auto find_pusher = [&](int index){ + lock_guard lck(mtx); + auto it = pusher_map.find(index); + if (it == pusher_map.end()){ + return false; + } + return true; + }; + for(size_t i = 0; i < input_urls.size(); i++) { + if (!find_pusher(i)){ + auto input_url = input_urls[i]; + auto src = MediaSource::find(RTMP_SCHEMA, DEFAULT_VHOST, "app", std::to_string(i), false); + if (src != nullptr){ + add_pusher(src,output_urls[i],i); + } + } + } + } + + return 0; +} \ No newline at end of file diff --git a/tests/test_bench_push.cpp b/tests/test_bench_push.cpp index d0929a8e..b34f8b99 100644 --- a/tests/test_bench_push.cpp +++ b/tests/test_bench_push.cpp @@ -124,7 +124,7 @@ int main(int argc, char *argv[]) { auto delay_ms = cmd_main["delay"].as(); auto pusher_count = cmd_main["count"].as(); auto merge_ms = cmd_main["merge"].as(); - auto schema = FindField(out_url.data(), nullptr, "://"); + auto schema = findSubString(out_url.data(), nullptr, "://"); if (schema != RTSP_SCHEMA && schema != RTMP_SCHEMA) { cout << "推流协议只支持rtsp或rtmp!" << endl; return -1; diff --git a/tests/test_flv.cpp b/tests/test_flv.cpp new file mode 100644 index 00000000..ffed816f --- /dev/null +++ b/tests/test_flv.cpp @@ -0,0 +1,112 @@ +/* + * 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 +#include +#include "Util/logger.h" +#include "Util/util.h" +#include "Network/TcpServer.h" +#include "Common/config.h" +#include "Rtsp/RtspSession.h" +#include "Rtmp/RtmpSession.h" +#include "Http/HttpSession.h" +#include "Rtmp/FlvSplitter.h" +#include "Rtmp/RtmpMediaSourceImp.h" + +using namespace std; +using namespace toolkit; +using namespace mediakit; + +class FlvSplitterImp : public FlvSplitter { +public: + FlvSplitterImp() { + _src = std::make_shared(MediaTuple{DEFAULT_VHOST, "live", "test"}); + } + ~FlvSplitterImp() override = default; + + void inputData(const char *data, size_t len, uint32_t &stamp) { + FlvSplitter::input(data, len); + stamp = _stamp; + } + +protected: + void onRecvFlvHeader(const FLVHeader &header) override { + } + + bool onRecvMetadata(const AMFValue &metadata) override { + _src->setMetaData(metadata); + _src->setProtocolOption(ProtocolOption()); + return true; + } + + void onRecvRtmpPacket(RtmpPacket::Ptr packet) override { + _stamp = packet->time_stamp; + _src->onWrite(std::move(packet)); + } + +private: + uint32_t _stamp; + RtmpMediaSourceImp::Ptr _src; +}; + +static bool loadFile(const char *path){ + FILE *fp = fopen(path, "rb"); + if (!fp) { + WarnL << "open file failed:" << path; + return false; + } + + char buffer[64 * 1024]; + uint32_t timeStamp_last = 0; + size_t len; + size_t total_size = 0; + FlvSplitterImp flv; + while (true) { + len = fread(buffer, 1, sizeof(buffer), fp); + if (len <= 0) { + break; + } + total_size += len; + uint32_t timeStamp; + flv.inputData(buffer, len, timeStamp); + auto diff = timeStamp - timeStamp_last; + if (diff > 0) { + usleep(diff * 1000); + } else { + usleep(1 * 1000); + } + timeStamp_last = timeStamp; + } + WarnL << total_size / 1024 << "KB"; + fclose(fp); + return true; +} + +int main(int argc,char *argv[]) { + //设置日志 + Logger::Instance().add(std::make_shared("ConsoleChannel")); + //启动异步日志线程 + Logger::Instance().setWriter(std::make_shared()); + loadIniConfig((exeDir() + "config.ini").data()); + TcpServer::Ptr rtspSrv(new TcpServer()); + TcpServer::Ptr rtmpSrv(new TcpServer()); + TcpServer::Ptr httpSrv(new TcpServer()); + rtspSrv->start(554);//默认554 + rtmpSrv->start(1935);//默认1935 + httpSrv->start(80);//默认80 + + if (argc == 2) + loadFile(argv[1]); + else + ErrorL << "parameter error."; + return 0; +} + + diff --git a/tests/test_httpApi.cpp b/tests/test_httpApi.cpp index c03531c6..081311db 100644 --- a/tests/test_httpApi.cpp +++ b/tests/test_httpApi.cpp @@ -43,7 +43,7 @@ void initEventListener(){ static onceToken s_token([](){ NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpRequest,[](BroadcastHttpRequestArgs){ //const Parser &parser,HttpSession::HttpResponseInvoker &invoker,bool &consumed - if(strstr(parser.Url().data(),"/api/") != parser.Url().data()){ + if(strstr(parser.url().data(),"/api/") != parser.url().data()){ return; } //url以"/api/起始,说明是http api" @@ -51,11 +51,11 @@ void initEventListener(){ _StrPrinter printer; ////////////////method//////////////////// - printer << "\r\nmethod:\r\n\t" << parser.Method(); + printer << "\r\nmethod:\r\n\t" << parser.method(); ////////////////url///////////////// - printer << "\r\nurl:\r\n\t" << parser.Url(); + printer << "\r\nurl:\r\n\t" << parser.url(); ////////////////protocol///////////////// - printer << "\r\nprotocol:\r\n\t" << parser.Tail(); + printer << "\r\nprotocol:\r\n\t" << parser.protocol(); ///////////////args////////////////// printer << "\r\nargs:\r\n"; for(auto &pr : parser.getUrlArgs()){ @@ -67,7 +67,7 @@ void initEventListener(){ printer << "\t" << pr.first << " : " << pr.second << "\r\n"; } ////////////////content///////////////// - printer << "\r\ncontent:\r\n" << parser.Content(); + printer << "\r\ncontent:\r\n" << parser.content(); auto contentOut = printer << endl; ////////////////我们测算异步回复,当然你也可以同步回复///////////////// @@ -113,7 +113,7 @@ int main(int argc,char *argv[]){ TcpServer::Ptr httpsSrv(new TcpServer()); httpsSrv->start(mINI::Instance()[Http::kSSLPort]);//默认443 - InfoL << "你可以在浏览器输入:http://127.0.0.1/api/my_api?key0=val0&key1=参数1" << endl; + InfoL << "你可以在浏览器输入:http://127.0.0.1/api/my_api?key0=val0&key1=参数1"; sem.wait(); return 0; diff --git a/tests/test_httpClient.cpp b/tests/test_httpClient.cpp index f218318b..9c581a1c 100644 --- a/tests/test_httpClient.cpp +++ b/tests/test_httpClient.cpp @@ -86,9 +86,9 @@ int main(int argc, char *argv[]) { for (auto &pr: parser.getHeader()) { printer << pr.first << ":" << pr.second << "\r\n"; } - InfoL << "status:" << parser.Url() << "\r\n" + InfoL << "status:" << parser.status() << "\r\n" << "header:\r\n" << (printer << endl) - << "\r\nbody:" << parser.Content(); + << "\r\nbody:" << parser.content(); } }); @@ -122,9 +122,9 @@ int main(int argc, char *argv[]) { for (auto &pr: parser.getHeader()) { printer << pr.first << ":" << pr.second << "\r\n"; } - InfoL << "status:" << parser.Url() << "\r\n" + InfoL << "status:" << parser.status() << "\r\n" << "header:\r\n" << (printer << endl) - << "\r\nbody:" << parser.Content(); + << "\r\nbody:" << parser.content(); } }); @@ -159,9 +159,9 @@ int main(int argc, char *argv[]) { for (auto &pr: parser.getHeader()) { printer << pr.first << ":" << pr.second << "\r\n"; } - InfoL << "status:" << parser.Url() << "\r\n" + InfoL << "status:" << parser.status() << "\r\n" << "header:\r\n" << (printer << endl) - << "\r\nbody:" << parser.Content(); + << "\r\nbody:" << parser.content(); } }); diff --git a/tests/test_pusher.cpp b/tests/test_pusher.cpp index adf85199..8e5ca1f7 100644 --- a/tests/test_pusher.cpp +++ b/tests/test_pusher.cpp @@ -88,8 +88,9 @@ int domain(const string &playUrl, const string &pushUrl) { NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaChanged, [pushUrl,poller](BroadcastMediaChangedArgs) { //媒体源"app/stream"已经注册,这时方可新建一个RtmpPusher对象并绑定该媒体源 - if(bRegist && pushUrl.find(sender.getSchema()) == 0){ - createPusher(poller,sender.getSchema(),sender.getVhost(),sender.getApp(), sender.getId(), pushUrl); + if (bRegist && pushUrl.find(sender.getSchema()) == 0) { + auto tuple = sender.getMediaTuple(); + createPusher(poller, sender.getSchema(), tuple.vhost, tuple.app, tuple.stream, pushUrl); } }); diff --git a/tests/test_pusherMp4.cpp b/tests/test_pusherMp4.cpp index ef338344..c28845d4 100644 --- a/tests/test_pusherMp4.cpp +++ b/tests/test_pusherMp4.cpp @@ -114,7 +114,7 @@ int domain(const string &filePath, const string &pushUrl) { auto poller = EventPollerPool::Instance().getPoller(); //vhost/app/stream可以随便自己填,现在不限制app应用名了 - createPusher(poller, FindField(pushUrl.data(), nullptr, "://").substr(0, 4), DEFAULT_VHOST, "live", "stream", filePath, pushUrl); + createPusher(poller, findSubString(pushUrl.data(), nullptr, "://").substr(0, 4), DEFAULT_VHOST, "live", "stream", filePath, pushUrl); //设置退出信号处理函数 static semaphore sem; signal(SIGINT, [](int) { sem.post(); });// 设置退出信号 diff --git a/tests/test_server.cpp b/tests/test_server.cpp index 6a15c532..dc123ba2 100644 --- a/tests/test_server.cpp +++ b/tests/test_server.cpp @@ -155,9 +155,10 @@ void initEventListener() { //监听rtsp、rtmp源注册或注销事件;此处用于测试rtmp保存为flv录像,保存在http根目录下 NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaChanged, [](BroadcastMediaChangedArgs) { - if (sender.getSchema() == RTMP_SCHEMA && sender.getApp() == "live") { + auto tuple = sender.getMediaTuple(); + if (sender.getSchema() == RTMP_SCHEMA && tuple.app == "live") { lock_guard lck(s_mtxFlvRecorder); - auto key = sender.shortUrl(); + auto key = tuple.shortUrl(); if (bRegist) { DebugL << "开始录制RTMP:" << sender.getUrl(); GET_CONFIG(string, http_root, Http::kRootPath); diff --git a/tests/test_wsServer.cpp b/tests/test_wsServer.cpp index 90dd645e..bd46787a 100644 --- a/tests/test_wsServer.cpp +++ b/tests/test_wsServer.cpp @@ -83,7 +83,7 @@ struct EchoSessionCreator { //返回的Session必须派生于SendInterceptor,可以返回null(拒绝连接) Session::Ptr operator()(const Parser &header, const HttpSession &parent, const Socket::Ptr &pSock) { // return nullptr; - if (header.Url() == "/") { + if (header.url() == "/") { return std::make_shared >(header, parent, pSock); } return std::make_shared >(header, parent, pSock); diff --git a/webrtc/RtpExt.cpp b/webrtc/RtpExt.cpp index e1424ea7..b7d97b5c 100644 --- a/webrtc/RtpExt.cpp +++ b/webrtc/RtpExt.cpp @@ -11,9 +11,7 @@ #include "RtpExt.h" #include "Sdp.h" -#if defined(_WIN32) #pragma pack(push, 1) -#endif // defined(_WIN32) using namespace std; using namespace toolkit; @@ -51,7 +49,7 @@ private: uint8_t id: 4; #endif uint8_t data[1]; -} PACKED; +}; //0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 @@ -77,11 +75,9 @@ private: uint8_t id; uint8_t len; uint8_t data[1]; -} PACKED; +}; -#if defined(_WIN32) #pragma pack(pop) -#endif // defined(_WIN32) ////////////////////////////////////////////////////////////////// @@ -94,6 +90,7 @@ uint8_t RtpExtOneByte::getId() const { } void RtpExtOneByte::setId(uint8_t in) { + CHECK(in < (int)RtpExtType::reserved); id = in & 0x0F; } @@ -143,8 +140,6 @@ void appendExt(map &ret, uint8_t *ptr, const uint8_t *end) { ++ptr; continue; } - //15类型的rtp ext为保留 - CHECK(ext->getId() < (uint8_t) RtpExtType::reserved); CHECK(reinterpret_cast(ext) + Type::kMinSize <= end); CHECK(ext->getData() + ext->getSize() <= end); ret.emplace(ext->getId(), RtpExt(ext, isOneByteExt(), reinterpret_cast(ext->getData()), ext->getSize())); @@ -522,8 +517,13 @@ uint8_t RtpExt::getFramemarkingTID() const { } void RtpExt::setExtId(uint8_t ext_id) { - assert(ext_id > (int) RtpExtType::padding && ext_id <= (int) RtpExtType::reserved && _ext); + assert(ext_id > (int) RtpExtType::padding && _ext); if (_one_byte_ext) { + if (ext_id >= (int)RtpExtType::reserved) { + WarnL << "One byte rtp ext can not store id " << (int)ext_id << "(" << getExtName((RtpExtType)ext_id) << ") big than 14"; + clearExt(); + return; + } auto ptr = reinterpret_cast(_ext); ptr->setId(ext_id); } else { diff --git a/webrtc/RtpExt.h b/webrtc/RtpExt.h index 6345b394..524fa3bb 100644 --- a/webrtc/RtpExt.h +++ b/webrtc/RtpExt.h @@ -34,6 +34,7 @@ namespace mediakit { XX(playout_delay, "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay") \ XX(video_orientation, "urn:3gpp:video-orientation") \ XX(toffset, "urn:ietf:params:rtp-hdrext:toffset") \ + XX(av1, "https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension") \ XX(encrypt, "urn:ietf:params:rtp-hdrext:encrypt") enum class RtpExtType : uint8_t { @@ -41,7 +42,7 @@ enum class RtpExtType : uint8_t { #define XX(type, uri) type, RTP_EXT_MAP(XX) #undef XX - reserved = encrypt, + reserved = 15, }; class RtcMedia; diff --git a/webrtc/Sdp.cpp b/webrtc/Sdp.cpp index 1b08ca8d..7051e42f 100644 --- a/webrtc/Sdp.cpp +++ b/webrtc/Sdp.cpp @@ -1555,7 +1555,10 @@ shared_ptr RtcConfigure::createAnswer(const RtcSession &offer) const //设置音视频端口复用 if (!offer.group.mids.empty()) { for (auto &m : ret->media) { - ret->group.mids.emplace_back(m.mid); + //The remote end has rejected (port 0) the m-section, so it should not be putting its mid in the group attribute. + if (m.port) { + ret->group.mids.emplace_back(m.mid); + } } } return ret; @@ -1613,15 +1616,15 @@ RETRY: if (offer_media.type == TrackApplication) { RtcMedia answer_media = offer_media; answer_media.role = mathDtlsRole(offer_media.role); -#ifdef ENABLE_SCTP - answer_media.direction = matchDirection(offer_media.direction, configure.direction); - answer_media.candidate = configure.candidate; answer_media.ice_ufrag = configure.ice_ufrag; answer_media.ice_pwd = configure.ice_pwd; answer_media.fingerprint = configure.fingerprint; answer_media.ice_lite = configure.ice_lite; +#ifdef ENABLE_SCTP + answer_media.candidate = configure.candidate; #else - answer_media.direction = RtpDirection::inactive; + answer_media.port = 0; + WarnL << "answer sdp忽略application mline, 请安装usrsctp后再测试datachannel功能"; #endif ret->media.emplace_back(answer_media); return; @@ -1816,11 +1819,17 @@ bool RtcConfigure::onCheckCodecProfile(const RtcCodecPlan &plan, CodecId codec) return true; } +/** + Single NAI Unit Mode = 0. // Single NAI mode (Only nals from 1-23 are allowed) + Non Interleaved Mode = 1,// Non-interleaved Mode: 1-23,24 (STAP-A),28 (FU-A) are allowed + Interleaved Mode = 2, // 25 (STAP-B),26 (MTAP16),27 (MTAP24),28 (EU-A),and 29 (EU-B) are allowed. + **/ void RtcConfigure::onSelectPlan(RtcCodecPlan &plan, CodecId codec) const { if (_rtsp_video_plan && codec == CodecH264 && getCodecId(_rtsp_video_plan->codec) == CodecH264) { - //h264时,设置packetization-mod为一致 + // h264时,设置packetization-mod为一致 auto mode = _rtsp_video_plan->fmtp[kMode]; - plan.fmtp[kMode] = mode.empty() ? "0" : mode; + GET_CONFIG(bool, h264_stap_a, Rtp::kH264StapA); + plan.fmtp[kMode] = mode.empty() ? std::to_string(h264_stap_a) : mode; } } diff --git a/webrtc/WebRtcPlayer.cpp b/webrtc/WebRtcPlayer.cpp index a766607f..63f174eb 100644 --- a/webrtc/WebRtcPlayer.cpp +++ b/webrtc/WebRtcPlayer.cpp @@ -48,7 +48,11 @@ void WebRtcPlayer::onStartWebRTC() { _reader = playSrc->getRing()->attach(getPoller(), true); weak_ptr weak_self = static_pointer_cast(shared_from_this()); weak_ptr weak_session = static_pointer_cast(getSession()); - _reader->setGetInfoCB([weak_session]() { return weak_session.lock(); }); + _reader->setGetInfoCB([weak_session]() { + Any ret; + ret.set(static_pointer_cast(weak_session.lock())); + return ret; + }); _reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pkt) { auto strong_self = weak_self.lock(); if (!strong_self) { @@ -67,6 +71,21 @@ void WebRtcPlayer::onStartWebRTC() { } strong_self->onShutdown(SockException(Err_shutdown, "rtsp ring buffer detached")); }); + + _reader->setMessageCB([weak_self] (const toolkit::Any &data) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + if (data.is()) { + auto &buffer = data.get(); + // PPID 51: 文本string + // PPID 53: 二进制 + strong_self->sendDatachannel(0, 51, buffer.data(), buffer.size()); + } else { + WarnL << "Send unknown message type to webrtc player: " << data.type_name(); + } + }); } } void WebRtcPlayer::onDestory() { @@ -77,7 +96,7 @@ void WebRtcPlayer::onDestory() { if (_reader && getSession()) { WarnL << "RTC播放器(" << _media_info.shortUrl() << ")结束播放,耗时(s):" << duration; if (bytes_usage >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, true, static_cast(*getSession())); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, true, *getSession()); } } WebRtcTransportImp::onDestory(); diff --git a/webrtc/WebRtcPusher.cpp b/webrtc/WebRtcPusher.cpp index 334236bf..92b4ad04 100644 --- a/webrtc/WebRtcPusher.cpp +++ b/webrtc/WebRtcPusher.cpp @@ -59,11 +59,14 @@ bool WebRtcPusher::close(MediaSource &sender) { } int WebRtcPusher::totalReaderCount(MediaSource &sender) { - auto total_count = 0; - for (auto &src : _push_src_sim) { - total_count += src.second->totalReaderCount(); + auto total_count = _push_src ? _push_src->totalReaderCount() : 0; + if (_simulcast) { + std::lock_guard lock(_mtx); + for (auto &src : _push_src_sim) { + total_count += src.second->totalReaderCount(); + } } - return total_count + _push_src->totalReaderCount(); + return total_count; } MediaOriginType WebRtcPusher::getOriginType(MediaSource &sender) const { @@ -96,10 +99,11 @@ void WebRtcPusher::onRecvRtp(MediaTrack &track, const string &rid, RtpPacket::Pt } } else { //视频 + std::lock_guard lock(_mtx); auto &src = _push_src_sim[rid]; if (!src) { - auto stream_id = rid.empty() ? _push_src->getId() : _push_src->getId() + "_" + rid; - auto src_imp = _push_src->clone(stream_id); + const auto& stream = _push_src->getMediaTuple().stream; + auto src_imp = _push_src->clone(rid.empty() ? stream : stream + '_' + rid); _push_src_sim_ownership[rid] = src_imp->getOwnership(); src_imp->setListener(static_pointer_cast(shared_from_this())); src = src_imp; @@ -125,7 +129,7 @@ void WebRtcPusher::onDestory() { if (getSession()) { WarnL << "RTC推流器(" << _media_info.shortUrl() << ")结束推流,耗时(s):" << duration; if (bytes_usage >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, false, static_cast(*getSession())); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, bytes_usage, duration, false, *getSession()); } } @@ -145,7 +149,7 @@ void WebRtcPusher::onRtcConfigure(RtcConfigure &configure) const { configure.audio.direction = configure.video.direction = RtpDirection::recvonly; } -float WebRtcPusher::getLossRate(MediaSource &sender,TrackType type){ +float WebRtcPusher::getLossRate(MediaSource &sender,TrackType type) { return WebRtcTransportImp::getLossRate(type); } @@ -155,8 +159,13 @@ void WebRtcPusher::OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport WebRtcTransportImp::OnDtlsTransportClosed(dtlsTransport); } -void WebRtcPusher::onRtcpBye(){ +void WebRtcPusher::onRtcpBye() { WebRtcTransportImp::onRtcpBye(); } +void WebRtcPusher::onShutdown(const SockException &ex) { + _push_src = nullptr; + WebRtcTransportImp::onShutdown(ex); +} + }// namespace mediakit \ No newline at end of file diff --git a/webrtc/WebRtcPusher.h b/webrtc/WebRtcPusher.h index 32c47055..8d309395 100644 --- a/webrtc/WebRtcPusher.h +++ b/webrtc/WebRtcPusher.h @@ -23,15 +23,17 @@ public: static Ptr create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const std::shared_ptr &ownership, const MediaInfo &info, const ProtocolOption &option, bool preferred_tcp = false); + protected: ///////WebRtcTransportImp override/////// void onStartWebRTC() override; void onDestory() override; void onRtcConfigure(RtcConfigure &configure) const override; void onRecvRtp(MediaTrack &track, const std::string &rid, RtpPacket::Ptr rtp) override; + void onShutdown(const SockException &ex) override; void onRtcpBye() override; //// dtls相关的回调 //// - void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override; + void OnDtlsTransportClosed(const RTC::DtlsTransport *dtlsTransport) override; protected: ///////MediaSourceEvent override/////// @@ -65,6 +67,7 @@ private: //推流所有权 std::shared_ptr _push_src_ownership; //推流的rtsp源,支持simulcast + std::recursive_mutex _mtx; std::unordered_map _push_src_sim; std::unordered_map > _push_src_sim_ownership; }; diff --git a/webrtc/WebRtcTransport.cpp b/webrtc/WebRtcTransport.cpp index f7b60cf1..4ec13e6f 100644 --- a/webrtc/WebRtcTransport.cpp +++ b/webrtc/WebRtcTransport.cpp @@ -229,6 +229,19 @@ void WebRtcTransport::OnSctpAssociationMessageReceived( _sctp->SendSctpMessage(params, ppid, msg, len); } #endif + +void WebRtcTransport::sendDatachannel(uint16_t streamId, uint32_t ppid, const char *msg, size_t len) { +#ifdef ENABLE_SCTP + if (_sctp) { + RTC::SctpStreamParameters params; + params.streamId = streamId; + _sctp->SendSctpMessage(params, ppid, (uint8_t *)msg, len); + } +#else + WarnL << "WebRTC datachannel disabled!"; +#endif +} + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void WebRtcTransport::sendSockData(const char *buf, size_t len, RTC::TransportTuple *tuple) { @@ -323,7 +336,7 @@ void WebRtcTransport::inputSockData(char *buf, int len, RTC::TransportTuple *tup if (RTC::StunPacket::IsStun((const uint8_t *)buf, len)) { std::unique_ptr packet(RTC::StunPacket::Parse((const uint8_t *)buf, len)); if (!packet) { - WarnL << "parse stun error" << std::endl; + WarnL << "parse stun error"; return; } _ice_server->ProcessStunPacket(packet.get(), tuple); @@ -515,7 +528,7 @@ void WebRtcTransportImp::onStartWebRTC() { _pt_to_track.emplace(track->plan_rtx->pt, std::unique_ptr(new WrappedRtxTrack(track))); } // 记录rtp ext类型与id的关系,方便接收或发送rtp时修改rtp ext id - track->rtp_ext_ctx = std::make_shared(*m_offer); + track->rtp_ext_ctx = std::make_shared(m_answer); weak_ptr weak_track = track; track->rtp_ext_ctx->setOnGetRtp([this, weak_track](uint8_t pt, uint32_t ssrc, const string &rid) { // ssrc --> MediaTrack @@ -564,8 +577,10 @@ void WebRtcTransportImp::onCheckAnswer(RtcSession &sdp) { GET_CONFIG(uint16_t, udp_port, Rtc::kPort); GET_CONFIG(uint16_t, tcp_port, Rtc::kTcpPort); - m.rtcp_addr.port = udp_port ? udp_port : tcp_port; - m.port = m.rtcp_addr.port; + m.port = m.port ? (udp_port ? udp_port : tcp_port) : 0; + if (m.type != TrackApplication) { + m.rtcp_addr.port = m.port; + } sdp.origin.address = m.addr.address; } @@ -1216,7 +1231,7 @@ void push_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginMana }; // rtsp推流需要鉴权 - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtc_push, info, invoker, static_cast(sender)); + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtc_push, info, invoker, sender); if (!flag) { // 该事件无人监听,默认不鉴权 invoker("", ProtocolOption()); @@ -1250,7 +1265,7 @@ void play_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginMana }; // 广播通用播放url鉴权事件 - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, info, invoker, static_cast(sender)); + auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, info, invoker, sender); if (!flag) { // 该事件无人监听,默认不鉴权 invoker(""); diff --git a/webrtc/WebRtcTransport.h b/webrtc/WebRtcTransport.h index 2534ca3b..2d1e6bbf 100644 --- a/webrtc/WebRtcTransport.h +++ b/webrtc/WebRtcTransport.h @@ -112,6 +112,7 @@ public: */ void sendRtpPacket(const char *buf, int len, bool flush, void *ctx = nullptr); void sendRtcpPacket(const char *buf, int len, bool flush, void *ctx = nullptr); + void sendDatachannel(uint16_t streamId, uint32_t ppid, const char *msg, size_t len); const EventPoller::Ptr& getPoller() const; Session::Ptr getSession() const; diff --git a/www/webrtc/index.html b/www/webrtc/index.html index 0ff074f4..8f9b60e6 100644 --- a/www/webrtc/index.html +++ b/www/webrtc/index.html @@ -89,28 +89,35 @@