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 07d5d388..c90129d3 100644 --- a/3rdpart/CMakeLists.txt +++ b/3rdpart/CMakeLists.txt @@ -47,44 +47,44 @@ set(MediaServer_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/media-server") # TODO: 补一个函数处理各种库 # 添加 mov、flv 库用于 MP4 录制 -if(ENABLE_MP4) - message(STATUS "ENABLE_MP4 defined") - +if (ENABLE_MP4 OR ENABLE_HLS_FMP4) # MOV set(MediaServer_MOV_ROOT ${MediaServer_ROOT}/libmov) aux_source_directory(${MediaServer_MOV_ROOT}/include MOV_SRC_LIST) - aux_source_directory(${MediaServer_MOV_ROOT}/source MOV_SRC_LIST) + aux_source_directory(${MediaServer_MOV_ROOT}/source MOV_SRC_LIST) add_library(mov STATIC ${MOV_SRC_LIST}) add_library(MediaServer::mov ALIAS mov) - target_compile_definitions(mov - PUBLIC -DENABLE_MP4) - target_compile_options(mov - PRIVATE ${COMPILE_OPTIONS_DEFAULT}) + target_compile_options(mov PRIVATE ${COMPILE_OPTIONS_DEFAULT}) target_include_directories(mov - PRIVATE - "$" - 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) @@ -108,9 +108,11 @@ if(ENABLE_RTPPROXY OR ENABLE_HLS) update_cached_list(MK_LINK_LIBRARIES MediaServer::mpeg) if(ENABLE_RTPPROXY) + message(STATUS "ENABLE_RTPPROXY defined") update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_RTPPROXY) endif() if(ENABLE_HLS) + message(STATUS "ENABLE_HLS defined") update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS) endif() endif() diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index e4744a0a..5d74e09b 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit e4744a0a523817356f2ec995ee5a732264c31629 +Subproject commit 5d74e09b8c84cccc46036ed2ef1a62f670c423d4 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 66689d9c..cb85679b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ option(ENABLE_HLS "Enable HLS" ON) option(ENABLE_JEMALLOC_STATIC "Enable static linking to the jemalloc library" OFF) option(ENABLE_MEM_DEBUG "Enable Memory Debug" OFF) option(ENABLE_MP4 "Enable MP4" ON) +option(ENABLE_HLS_FMP4 "Enable HLS-FMP4" ON) option(ENABLE_MSVC_MT "Enable MSVC Mt/Mtd lib" ON) option(ENABLE_MYSQL "Enable MySQL" OFF) option(ENABLE_OPENSSL "Enable OpenSSL" ON) @@ -200,8 +201,8 @@ if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") endif() # mediakit 以及各个 runtime 依赖 -update_cached_list(MK_LINK_LIBRARIES "") -update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_VERSION) +update_cached(MK_LINK_LIBRARIES "") +update_cached(MK_COMPILE_DEFINITIONS ENABLE_VERSION) if (DISABLE_REPORT) update_cached_list(MK_COMPILE_DEFINITIONS DISABLE_REPORT) @@ -461,7 +462,7 @@ if(ENABLE_SERVER) endif() # Android 会 add_subdirectory 并依赖该变量 -if(ENABLE_SERVER_LIB) +if(ENABLE_SERVER_LIB AND NOT CMAKE_PARENT_LIST_FILE STREQUAL CMAKE_CURRENT_LIST_FILE) set(MK_LINK_LIBRARIES ${MK_LINK_LIBRARIES} PARENT_SCOPE) endif() diff --git a/README.md b/README.md index ddee13cd..97fb5751 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ ## 功能清单 ### 功能一览 -功能一览 +功能一览 - RTSP[S] - RTSP[S] 服务器,支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备 @@ -69,9 +69,10 @@ - 支持H264/H265/AAC/G711/OPUS编码,其他编码能转发但不能转协议 - 支持[RTMP-H265](https://github.com/ksvc/FFmpeg/wiki) - 支持[RTMP-OPUS](https://github.com/ZLMediaKit/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81) + - 支持[enhanced-rtmp(H265)](https://github.com/veovera/enhanced-rtmp) - HLS - - 支持HLS文件生成,自带HTTP文件服务器 + - 支持HLS文件(mpegts/fmp4)生成,自带HTTP文件服务器 - 通过cookie追踪技术,可以模拟HLS播放为长连接,可以实现HLS按需拉流、播放统计等业务 - 支持HLS播发器,支持拉流HLS转rtsp/rtmp/mp4 - 支持H264/H265/AAC/G711/OPUS编码 @@ -168,30 +169,36 @@ bash build_docker_images.sh ``` ## 合作项目 + + - 视频管理平台 + - [wvp-GB28181-pro](https://github.com/648540858/wvp-GB28181-pro) java实现的开箱即用的GB28181协议视频平台 + - [AKStream](https://github.com/chatop2020/AKStream) c#实现的全功能的软NVR接口/GB28181平台 + - [BXC_SipServer](https://github.com/any12345com/BXC_SipServer) c++实现的国标GB28181流媒体信令服务器 + - [gosip](https://github.com/panjjo/gosip) golang实现的GB28181服务器 + - [FreeEhome](https://github.com/tsingeye/FreeEhome) golang实现的海康ehome服务器 + + - 播放器 + - [h265web.js](https://github.com/numberwolf/h265web.js) 基于wasm支持H265的播放器,支持本项目多种专属协议 + - [jessibuca](https://github.com/langhuihui/jessibuca) 基于wasm支持H265的播放器 + - [wsPlayer](https://github.com/v354412101/wsPlayer) 基于MSE的websocket-fmp4播放器 + - [BXC_gb28181Player](https://github.com/any12345com/BXC_gb28181Player) C++开发的支持国标GB28181协议的视频流播放器 - - 可视化管理网站 - - [最新的前后端分离web项目,支持webrtc播放](https://github.com/langmansh/AKStreamNVR) - - [基于ZLMediaKit主线的管理WEB网站](https://gitee.com/kkkkk5G/MediaServerUI) - - [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI) - - [一个非常漂亮的可视化后台管理系统](https://github.com/MingZhuLiu/ZLMediaServerManagent) - - - 流媒体管理平台 - - [GB28181完整解决方案,自带web管理网站,支持webrtc、h265播放](https://github.com/648540858/wvp-GB28181-pro) - - [功能强大的流媒体控制管理接口平台,支持GB28181](https://github.com/chatop2020/AKStream) - - [Go实现的GB28181服务器](https://github.com/panjjo/gosip) - - [node-js版本的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http) - - [Go实现的海康ehome服务器](https://github.com/tsingeye/FreeEhome) - - - 客户端 - - [c sdk完整c#包装库](https://github.com/malegend/ZLMediaKit.Autogen) +- WEB管理网站 + - [AKStreamNVR](https://github.com/langmansh/AKStreamNVR) 前后端分离web项目,支持webrtc播放 + + - SDK + - [c# sdk](https://github.com/malegend/ZLMediaKit.Autogen) 本项目c sdk完整c#包装库 + - [metaRTC](https://github.com/metartc/metaRTC) 全国产纯c webrtc sdk + + - 其他项目(已停止更新) + - [NodeJS实现的GB28181平台](https://gitee.com/hfwudao/GB28181_Node_Http) + - [基于ZLMediaKit主线的管理WEB网站 ](https://gitee.com/kkkkk5G/MediaServerUI) + - [基于ZLMediaKit分支的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI) + - [一个非常漂亮的可视化后台管理系统](https://github.com/MingZhuLiu/ZLMediaServerManagent) - [基于C SDK实现的推流客户端](https://github.com/hctym1995/ZLM_ApiDemo) - [C#版本的Http API与Hook](https://github.com/chengxiaosheng/ZLMediaKit.HttpApi) - [DotNetCore的RESTful客户端](https://github.com/MingZhuLiu/ZLMediaKit.DotNetCore.Sdk) - - 播放器 - - [基于wasm支持H265的播放器](https://github.com/numberwolf/h265web.js) - - [基于MSE的websocket-fmp4播放器](https://github.com/v354412101/wsPlayer) - - [全国产webrtc sdk(metaRTC)](https://github.com/metartc/metaRTC) ## 授权协议 @@ -203,9 +210,12 @@ bash build_docker_images.sh ## 联系方式 - 邮箱:<1213642868@qq.com>(本项目相关或流媒体相关问题请走issue流程,否则恕不邮件答复) - - QQ群:两个qq群已满员(共4000人),后续将不再新建qq群,用户可加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364)提问以支持本项目。 - - 关注微信公众号: + - 请关注微信公众号获取最新消息推送: + + - 也可以自愿有偿加入知识星球咨询和获取资料: + + ## 怎么提问? @@ -213,9 +223,7 @@ bash build_docker_images.sh - 1、仔细看下readme、wiki,如果有必要可以查看下issue. - 2、如果您的问题还没解决,可以提issue. - - 3、有些问题,如果不具备参考性的,无需在issue提的,可以在qq群提. - - 4、QQ私聊一般不接受无偿技术咨询和支持([为什么不提倡QQ私聊](https://github.com/ZLMediaKit/ZLMediaKit/wiki/%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E5%BB%BA%E8%AE%AEQQ%E7%A7%81%E8%81%8A%E5%92%A8%E8%AF%A2%E9%97%AE%E9%A2%98%EF%BC%9F)). - - 5、如果需要获取更及时贴心的技术支持,可以有偿加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364). + - 3、如果需要获取更及时贴心的技术支持,可以有偿加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364). ## 特别感谢 @@ -310,6 +318,21 @@ bash build_docker_images.sh [朱如洪 ](https://github.com/zhu410289616) [lijin](https://github.com/1461521844lijin) [PioLing](https://github.com/PioLing) +[BackT0TheFuture](https://github.com/BackT0TheFuture) +[perara](https://github.com/perara) +[codeRATny](https://github.com/codeRATny) +[dengjfzh](https://github.com/dengjfzh) +[百鸣](https://github.com/ixingqiao) +[fruit Juice](https://github.com/xuandu) +[tbago](https://github.com/tbago) +[Luosh](https://github.com/Luosh) +[linxiaoyan87](https://github.com/linxiaoyan) +[waken](https://github.com/mc373906408) +[Deepslient](https://github.com/Deepslient) + +同时感谢JetBrains对开源项目的支持,本项目使用CLion开发与调试: + +[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/CLion.svg)](https://jb.gg/OpenSourceSupport) ## 使用案例 diff --git a/README_en.md b/README_en.md index 154f51c7..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 @@ -68,9 +68,10 @@ - Supports H264/H265/AAC/G711/OPUS encoding. Other encodings can be forwarded but cannot be converted to protocol - Supports [RTMP-H265](https://github.com/ksvc/FFmpeg/wiki) - Supports [RTMP-OPUS](https://github.com/ZLMediaKit/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81) + - Supports [enhanced-rtmp(H265)](https://github.com/veovera/enhanced-rtmp) - HLS - - Supports HLS file generation and comes with an HTTP file server + - Supports HLS file(mpegts/fmp4) generation and comes with an HTTP file server - Through cookie tracking technology, it can simulate HLS playback as a long connection, which can achieve HLS on-demand pulling, playback statistics, and other businesses - Supports HLS player and can pull HLS to rtsp/rtmp/mp4 - Supports H264/H265/AAC/G711/OPUS encoding @@ -349,6 +350,7 @@ bash build_docker_images.sh - Media management platform - [GB28181 complete solution with web management website, supporting webrtc and h265 playback](https://github.com/648540858/wvp-GB28181-pro) - [Powerful media control and management interface platform, supporting GB28181](https://github.com/chatop2020/AKStream) + - [GB28181 server implemented in C++](https://github.com/any12345com/BXC_SipServer) - [GB28181 server implemented in Go](https://github.com/panjjo/gosip) - [Node-js version of GB28181 platform](https://gitee.com/hfwudao/GB28181_Node_Http) - [Hikvision ehome server implemented in Go](https://github.com/tsingeye/FreeEhome) @@ -363,6 +365,7 @@ bash build_docker_images.sh - [Player supporting H265 based on wasm](https://github.com/numberwolf/h265web.js) - [WebSocket-fmp4 player based on MSE](https://github.com/v354412101/wsPlayer) - [Domestic webrtc sdk(metaRTC)](https://github.com/metartc/metaRTC) + - [GB28181 player implemented in C++](https://github.com/any12345com/BXC_gb28181Player) ## License @@ -479,6 +482,21 @@ Thanks to all those who have supported this project in various ways, including b [朱如洪 ](https://github.com/zhu410289616) [lijin](https://github.com/1461521844lijin) [PioLing](https://github.com/PioLing) +[BackT0TheFuture](https://github.com/BackT0TheFuture) +[perara](https://github.com/perara) +[codeRATny](https://github.com/codeRATny) +[dengjfzh](https://github.com/dengjfzh) +[百鸣](https://github.com/ixingqiao) +[fruit Juice](https://github.com/xuandu) +[tbago](https://github.com/tbago) +[Luosh](https://github.com/Luosh) +[linxiaoyan87](https://github.com/linxiaoyan) +[waken](https://github.com/mc373906408) +[Deepslient](https://github.com/Deepslient) + +Also thank to JetBrains for their support for open source project, we developed and debugged zlmediakit with CLion: + +[![JetBrains](https://resources.jetbrains.com/storage/products/company/brand/logos/CLion.svg)](https://jb.gg/OpenSourceSupport) ## Use Cases 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 018988f4..3dc9a2c3 100644 --- a/api/include/mk_events_objects.h +++ b/api/include/mk_events_objects.h @@ -100,6 +100,9 @@ API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_so API_EXPORT int API_CALL mk_media_source_get_track_count(const mk_media_source ctx); // copy track reference by index from MediaSource, please use mk_track_unref to release it API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx, int index); +// MediaSource::broadcastMessage +API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len); + /** * 直播源在ZLMediaKit中被称作为MediaSource, * 目前支持3种,分别是RtmpMediaSource、RtspMediaSource、HlsMediaSource 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 fd7f696c..3833017f 100644 --- a/api/source/mk_events_objects.cpp +++ b/api/source/mk_events_objects.cpp @@ -214,6 +214,16 @@ API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx return (mk_track) new Track::Ptr(std::move(tracks[index])); } +API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len) { + assert(ctx && msg && len); + MediaSource *src = (MediaSource *)ctx; + + Any any; + Buffer::Ptr buffer = std::make_shared(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_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/conf/config.ini b/conf/config.ini index bd72cac6..0bd86b0b 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -40,13 +40,20 @@ modify_stamp=2 enable_audio=1 #添加acc静音音频,在关闭音频时,此开关无效 add_mute_audio=1 +#无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close) +#此配置置1时,此流如果无人观看,将不触发on_none_reader hook回调, +#而是将直接关闭流 +auto_close=0 + #推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。 #置0关闭此特性(推流断开会导致立即断开播放器) #此参数不应大于播放器超时时间;单位毫秒 continue_push_ms=15000 -#是否开启转换为hls +#是否开启转换为hls(mpegts) enable_hls=1 +#是否开启转换为hls(fmp4) +enable_hls_fmp4=0 #是否开启MP4录制 enable_mp4=0 #是否开启转换为rtsp/webrtc @@ -124,7 +131,7 @@ segDur=2 segNum=3 #HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数 segRetain=5 -#是否广播 ts 切片完成通知 +#是否广播 hls切片(ts/fmp4)完成通知(on_record_ts) broadcastRecordTs=0 #直播hls文件删除延时,单位秒,issue: #913 deleteDelaySec=10 @@ -135,9 +142,6 @@ deleteDelaySec=10 segKeep=0 [hook] -#在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然 -#该配置项的目的是为了开发者自己调试测试,该参数暴露后会有泄露隐私的安全隐患 -admin_params=secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc #是否启用hook事件,启用后,推拉流都将进行鉴权 enable=0 #播放器或推流器使用流量事件,置空则关闭 @@ -150,7 +154,7 @@ on_play=https://127.0.0.1/index/hook/on_play on_publish=https://127.0.0.1/index/hook/on_publish #录制mp4切片完成事件 on_record_mp4=https://127.0.0.1/index/hook/on_record_mp4 -# 录制 hls ts 切片完成事件 +# 录制 hls ts(或fmp4) 切片完成事件 on_record_ts=https://127.0.0.1/index/hook/on_record_ts #rtsp播放鉴权事件,此事件中比对rtsp的用户名密码 on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth @@ -162,6 +166,8 @@ on_rtsp_realm=https://127.0.0.1/index/hook/on_rtsp_realm on_shell_login=https://127.0.0.1/index/hook/on_shell_login #直播流注册或注销事件 on_stream_changed=https://127.0.0.1/index/hook/on_stream_changed +#过滤on_stream_changed hook的协议类型,可以选择只监听某些感兴趣的协议;置空则不过滤协议 +stream_changed_schemas=rtsp/rtmp/fmp4/ts/hls/hls.fmp4 #无人观看流事件,通过该事件,可以选择是否关闭无人观看的流。配合general.streamNoneReaderDelayMS选项一起使用 on_stream_none_reader=https://127.0.0.1/index/hook/on_stream_none_reader #播放时,未找到流事件,通过配合hook.on_stream_none_reader事件可以完成按需拉流 @@ -237,6 +243,8 @@ forbidCacheSuffix= forwarded_ip_header= #默认允许所有跨域请求 allow_cross_domains=1 +#允许访问http api和http文件索引的ip地址范围白名单,置空情况下不做限制 +allow_ip_range=127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255 [multicast] #rtp组播截止组播ip地址 @@ -266,8 +274,6 @@ handshakeSecond=15 #rtmp超时时间,如果该时间内未收到客户端的数据, #或者tcp发送缓存超过这个时间,则会断开连接,单位秒 keepAliveSecond=15 -#在接收rtmp推流时,是否重新生成时间戳(很多推流器的时间戳着实很烂) -modifyStamp=0 #rtmp服务器监听端口 port=1935 #rtmps服务器监听地址 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/postman/ZLMediaKit.postman_collection.json b/postman/ZLMediaKit.postman_collection.json index ad1929bd..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 }, { @@ -584,6 +590,12 @@ "value": null, "description": "是否修改原始时间戳,默认值2;取值范围:0.采用源视频流绝对时间戳,不做任何改变;1.采用zlmediakit接收数据时的系统时间戳(有平滑处理);2.采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正", "disabled": true + }, + { + "key": "auto_close", + "value": null, + "description": "无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)", + "disabled": true } ] } @@ -609,7 +621,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "key", @@ -640,7 +652,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -709,7 +721,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "key", @@ -740,7 +752,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "src_url", @@ -797,7 +809,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "key", @@ -827,7 +839,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -873,7 +885,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -900,6 +912,56 @@ }, "response": [] }, + { + "name": "广播webrtc datachannel消息(broadcastMessage)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{ZLMediaKit_URL}}/index/api/broadcastMessage?secret={{ZLMediaKit_secret}}&schema=rtsp&vhost={{defaultVhost}}&app=live&stream=test&msg=Hello zlmediakit123", + "host": [ + "{{ZLMediaKit_URL}}" + ], + "path": [ + "index", + "api", + "broadcastMessage" + ], + "query": [ + { + "key": "secret", + "value": "{{ZLMediaKit_secret}}", + "description": "api操作密钥(配置文件配置)" + }, + { + "key": "schema", + "value": "rtsp", + "description": "协议,例如 rtsp或rtmp,目前仅支持rtsp协议" + }, + { + "key": "vhost", + "value": "{{defaultVhost}}", + "description": "虚拟主机,例如__defaultVhost__" + }, + { + "key": "app", + "value": "live", + "description": "应用名,例如 live" + }, + { + "key": "stream", + "value": "test", + "description": "流id,例如 test" + }, + { + "key": "msg", + "value": "Hello ZLMediakit" + } + ] + } + }, + "response": [] + }, { "name": "获取流信息(getMediaInfo)", "request": { @@ -919,7 +981,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -965,7 +1027,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1016,7 +1078,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1062,7 +1124,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "type", @@ -1120,7 +1182,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1166,7 +1228,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1212,7 +1274,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "type", @@ -1258,7 +1320,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "type", @@ -1304,7 +1366,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "url", @@ -1345,7 +1407,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "stream_id", @@ -1376,7 +1438,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "port", @@ -1435,7 +1497,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "dst_url", @@ -1476,7 +1538,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "stream_id", @@ -1507,7 +1569,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "stream_id", @@ -1543,7 +1605,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "stream_id", @@ -1574,7 +1636,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "stream_id", @@ -1605,7 +1667,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -1631,7 +1693,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1734,7 +1796,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -1822,7 +1884,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 748cb48a..63abc05d 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -34,7 +34,7 @@ if(ENABLE_SERVER_LIB) PRIVATE ${COMPILE_OPTIONS_DEFAULT}) target_link_libraries(MediaServer PRIVATE ${MK_LINK_LIBRARIES}) - update_cached(MK_LINK_LIBRARIES MediaServer) + update_cached_list(MK_LINK_LIBRARIES MediaServer) return() endif() diff --git a/server/FFmpegSource.cpp b/server/FFmpegSource.cpp index fa9044ed..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" @@ -70,10 +71,10 @@ void FFmpegSource::setupRecordFlag(bool enable_hls, bool enable_mp4){ _enable_mp4 = enable_mp4; } -void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,const string &dst_url,int timeout_ms,const onPlay &cb) { - GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin); - GET_CONFIG(string,ffmpeg_cmd_default,FFmpeg::kCmd); - GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog); +void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url, const string &dst_url, int timeout_ms, const onPlay &cb) { + GET_CONFIG(string, ffmpeg_bin, FFmpeg::kBin); + GET_CONFIG(string, ffmpeg_cmd_default, FFmpeg::kCmd); + GET_CONFIG(string, ffmpeg_log, FFmpeg::kLog); _src_url = src_url; _dst_url = dst_url; @@ -91,120 +92,114 @@ void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,cons auto cmd_it = mINI::Instance().find(ffmpeg_cmd_key); if (cmd_it != mINI::Instance().end()) { ffmpeg_cmd = cmd_it->second; - } else{ + } else { WarnL << "配置文件中,ffmpeg命令模板(" << ffmpeg_cmd_key << ")不存在,已采用默认模板(" << ffmpeg_cmd_default << ")"; } } - char cmd[2048] = {0}; + char cmd[2048] = { 0 }; snprintf(cmd, sizeof(cmd), ffmpeg_cmd.data(), File::absolutePath("", ffmpeg_bin).data(), src_url.data(), dst_url.data()); auto log_file = ffmpeg_log.empty() ? "" : File::absolutePath("", ffmpeg_log); _process.run(cmd, log_file); InfoL << cmd; if (is_local_ip(_media_info.host)) { - //推流给自己的,通过判断流是否注册上来判断是否正常 + // 推流给自己的,通过判断流是否注册上来判断是否正常 if (_media_info.schema != RTSP_SCHEMA && _media_info.schema != RTMP_SCHEMA) { - cb(SockException(Err_other,"本服务只支持rtmp/rtsp推流")); + cb(SockException(Err_other, "本服务只支持rtmp/rtsp推流")); return; } weak_ptr 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 || + if (!bRegist || sender.getSchema() != strongSelf->_media_info.schema || !equalMediaTuple(sender.getMediaTuple(), strongSelf->_media_info)) { - //不是自己感兴趣的事件,忽略之 + // 不是自己感兴趣的事件,忽略之 return; } - //查找的流终于注册上了;取消延时任务,防止多次回调 + // 查找的流终于注册上了;取消延时任务,防止多次回调 onRegistTimeout->cancel(); - //取消事件监听 - NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged); + // 取消事件监听 + NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged); - //切换到自己的线程再回复 - strongSelf->_poller->async([weakSelf,cb](){ - auto strongSelf = weakSelf.lock(); - if(!strongSelf) { - return; + // 切换到自己的线程再回复 + strongSelf->_poller->async([weakSelf, cb]() { + if (auto strongSelf = weakSelf.lock()) { + // 再找一遍媒体源,一般能找到 + strongSelf->findAsync(0, cb); } - //再找一遍媒体源,一般能找到 - strongSelf->findAsync(0,cb); }, false); }; - //监听媒体注册事件 + // 监听媒体注册事件 NoticeCenter::Instance().addListener(listener_tag, Broadcast::kBroadcastMediaChanged, onRegist); } @@ -222,49 +217,49 @@ void FFmpegSource::startTimer(int timeout_ms) { } bool needRestart = ffmpeg_restart_sec > 0 && strongSelf->_replay_ticker.elapsedTime() > ffmpeg_restart_sec * 1000; if (is_local_ip(strongSelf->_media_info.host)) { - //推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常 + // 推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常 strongSelf->findAsync(0, [&](const MediaSource::Ptr &src) { - //同步查找流 + // 同步查找流 if (!src || needRestart) { - if(needRestart){ + if (needRestart) { strongSelf->_replay_ticker.resetTime(); - if(strongSelf->_process.wait(false)){ - //FFmpeg进程还在运行,超时就关闭它 + if (strongSelf->_process.wait(false)) { + // FFmpeg进程还在运行,超时就关闭它 strongSelf->_process.kill(2000); } InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url; } - //流不在线,重新拉流, 这里原先是10秒超时,实际发现10秒不够,改成20秒了 - if(strongSelf->_replay_ticker.elapsedTime() > 20 * 1000){ - //上次重试时间超过10秒,那么再重试FFmpeg拉流 + // 流不在线,重新拉流, 这里原先是10秒超时,实际发现10秒不够,改成20秒了 + if (strongSelf->_replay_ticker.elapsedTime() > 20 * 1000) { + // 上次重试时间超过10秒,那么再重试FFmpeg拉流 strongSelf->_replay_ticker.resetTime(); strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [](const SockException &) {}); } } }); } else { - //推流给其他服务器的,我们通过判断FFmpeg进程是否在线,如果FFmpeg推流中断,那么它应该会自动退出 + // 推流给其他服务器的,我们通过判断FFmpeg进程是否在线,如果FFmpeg推流中断,那么它应该会自动退出 if (!strongSelf->_process.wait(false) || needRestart) { - if(needRestart){ + if (needRestart) { strongSelf->_replay_ticker.resetTime(); - if(strongSelf->_process.wait(false)){ - //FFmpeg进程还在运行,超时就关闭它 + if (strongSelf->_process.wait(false)) { + // FFmpeg进程还在运行,超时就关闭它 strongSelf->_process.kill(2000); } InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url; } - //ffmpeg不在线,重新拉流 + // ffmpeg不在线,重新拉流 strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [weakSelf](const SockException &ex) { - if(!ex){ - //没有错误 + if (!ex) { + // 没有错误 return; } auto strongSelf = weakSelf.lock(); if (!strongSelf) { - //自身已经销毁 + // 自身已经销毁 return; } - //上次重试时间超过10秒,那么再重试FFmpeg拉流 + // 上次重试时间超过10秒,那么再重试FFmpeg拉流 strongSelf->startTimer(10 * 1000); }); } @@ -294,20 +289,17 @@ MediaOriginType FFmpegSource::getOriginType(MediaSource &sender) const{ return MediaOriginType::ffmpeg_pull; } -string FFmpegSource::getOriginUrl(MediaSource &sender) const{ +string FFmpegSource::getOriginUrl(MediaSource &sender) const { return _src_url; } -std::shared_ptr FFmpegSource::getOriginSock(MediaSource &sender) const { - return nullptr; -} - void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) { - auto listener = src->getListener(true); - if (listener.lock().get() != this) { + auto muxer = src->getMuxer(); + auto listener = muxer ? muxer->getDelegate() : nullptr; + if (listener && listener.get() != this) { //防止多次进入onGetMediaSource函数导致无限递归调用的bug setDelegate(listener); - src->setListener(shared_from_this()); + muxer->setDelegate(shared_from_this()); if (_enable_hls) { src->setupRecord(Recorder::type_hls, true, "", 0); } @@ -318,14 +310,14 @@ void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) { } void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float timeout_sec, const onSnap &cb) { - GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin); - GET_CONFIG(string,ffmpeg_snap,FFmpeg::kSnap); - GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog); + GET_CONFIG(string, ffmpeg_bin, FFmpeg::kBin); + GET_CONFIG(string, ffmpeg_snap, FFmpeg::kSnap); + GET_CONFIG(string, ffmpeg_log, FFmpeg::kLog); Ticker ticker; - WorkThreadPool::Instance().getPoller()->async([timeout_sec, play_url,save_path,cb, ticker](){ + WorkThreadPool::Instance().getPoller()->async([timeout_sec, play_url, save_path, cb, ticker]() { auto elapsed_ms = ticker.elapsedTime(); if (elapsed_ms > timeout_sec * 1000) { - //超时,后台线程负载太高,当代太久才启动该任务 + // 超时,后台线程负载太高,当代太久才启动该任务 cb(false, "wait work poller schedule snap task timeout"); return; } @@ -346,13 +338,12 @@ void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float return 0; }); - //等待FFmpeg进程退出 + // 等待FFmpeg进程退出 process->wait(true); // FFmpeg进程退出了可以取消定时器了 delayTask->cancel(); - //执行回调函数 + // 执行回调函数 bool success = process->exit_code() == 0 && File::fileSize(save_path.data()); cb(success, (!success && !log_file.empty()) ? File::loadFile(log_file.data()) : ""); }); } - 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/System.cpp b/server/System.cpp index aae4c792..bc93ed3c 100644 --- a/server/System.cpp +++ b/server/System.cpp @@ -126,6 +126,12 @@ void System::startDaemon(bool &kill_parent_if_failed) { exit(0); }); + signal(SIGTERM,[](int) { + WarnL << "收到主动退出信号,关闭父进程与子进程"; + kill(pid, SIGINT); + exit(0); + }); + do { int status = 0; if (waitpid(pid, &status, 0) >= 0) { diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 2fe75f73..e8081756 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -247,7 +247,7 @@ static inline void addHttpListener(){ size = body->remainSize(); } - LogContextCapture log(getLogger(), toolkit::LTrace, __FILE__, "http api debug", __LINE__); + LogContextCapture log(getLogger(), toolkit::LDebug, __FILE__, "http api debug", __LINE__); log << "\r\n# request:\r\n" << parser.method() << " " << parser.fullUrl() << "\r\n"; log << "# header:\r\n"; @@ -537,7 +537,7 @@ void addStreamProxy(const string &vhost, const string &app, const string &stream lock_guard lck(s_proxyMapMtx); if (s_proxyMap.find(key) != s_proxyMap.end()) { //已经在拉流了 - cb(SockException(Err_success), key); + cb(SockException(Err_other, "This stream already exists"), key); return; } //添加拉流代理 @@ -588,7 +588,8 @@ void installWebApi() { //获取线程负载 //测试url http://127.0.0.1/index/api/getThreadsLoad - api_regist("/index/api/getThreadsLoad",[](API_ARGS_MAP_ASYNC){ + api_regist("/index/api/getThreadsLoad", [](API_ARGS_MAP_ASYNC) { + CHECK_SECRET(); EventPollerPool::Instance().getExecutorDelay([invoker, headerOut](const vector &vecDelay) { Value val; auto vec = EventPollerPool::Instance().getExecutorLoad(); @@ -606,7 +607,8 @@ void installWebApi() { //获取后台工作线程负载 //测试url http://127.0.0.1/index/api/getWorkThreadsLoad - api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC){ + api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC) { + CHECK_SECRET(); WorkThreadPool::Instance().getExecutorDelay([invoker, headerOut](const vector &vecDelay) { Value val; auto vec = WorkThreadPool::Instance().getExecutorLoad(); @@ -652,6 +654,10 @@ void installWebApi() { continue; #endif } + if (pr.first == FFmpeg::kBin) { + WarnL << "Configuration named " << FFmpeg::kBin << " is not allowed to be set by setServerConfig api."; + continue; + } if (ini[pr.first] == pr.second) { continue; } @@ -660,7 +666,7 @@ void installWebApi() { ++changed; } if (changed > 0) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig); + NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig); ini.dumpFile(g_ini_file); } val["changed"] = changed; @@ -789,25 +795,40 @@ void installWebApi() { throw ApiRetException("can not find the stream", API::NotFound); } src->getPlayerList( - [=](const std::list> &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(); 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 424b8d1a..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"; @@ -48,7 +49,6 @@ const string kOnServerExited = HOOK_FIELD "on_server_exited"; const string kOnServerKeepalive = HOOK_FIELD "on_server_keepalive"; const string kOnSendRtpStopped = HOOK_FIELD "on_send_rtp_stopped"; const string kOnRtpServerTimeout = HOOK_FIELD "on_rtp_server_timeout"; -const string kAdminParams = HOOK_FIELD "admin_params"; const string kAliveInterval = HOOK_FIELD "alive_interval"; const string kRetry = HOOK_FIELD "retry"; const string kRetryDelay = HOOK_FIELD "retry_delay"; @@ -74,10 +74,10 @@ static onceToken token([]() { mINI::Instance()[kOnServerKeepalive] = ""; mINI::Instance()[kOnSendRtpStopped] = ""; mINI::Instance()[kOnRtpServerTimeout] = ""; - mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc"; mINI::Instance()[kAliveInterval] = 30.0; mINI::Instance()[kRetry] = 1; mINI::Instance()[kRetryDelay] = 3.0; + mINI::Instance()[kStreamChangedSchemas] = "rtsp/rtmp/fmp4/ts/hls/hls.fmp4"; }); } // namespace Hook @@ -166,12 +166,16 @@ string getVhost(const HttpArgs &value) { return val != value.end() ? val->second : ""; } +static atomic 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); @@ -331,11 +335,10 @@ static mINI jsonToMini(const Value &obj) { void installWebHook() { GET_CONFIG(bool, hook_enable, Hook::kEnable); - GET_CONFIG(string, hook_adminparams, Hook::kAdminParams); NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) { GET_CONFIG(string, hook_publish, Hook::kOnPublish); - if (!hook_enable || args.param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_publish.empty()) { invoker("", ProtocolOption()); return; } @@ -360,7 +363,7 @@ void installWebHook() { NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) { GET_CONFIG(string, hook_play, Hook::kOnPlay); - if (!hook_enable || args.param_strs == hook_adminparams || hook_play.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_play.empty()) { invoker(""); return; } @@ -374,7 +377,7 @@ void installWebHook() { NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) { GET_CONFIG(string, hook_flowreport, Hook::kOnFlowReport); - if (!hook_enable || args.param_strs == hook_adminparams || hook_flowreport.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_flowreport.empty()) { return; } auto body = make_json(args); @@ -393,7 +396,7 @@ void installWebHook() { // 监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问 NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastOnGetRtspRealm, [](BroadcastOnGetRtspRealmArgs) { GET_CONFIG(string, hook_rtsp_realm, Hook::kOnRtspRealm); - if (!hook_enable || args.param_strs == hook_adminparams || hook_rtsp_realm.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_rtsp_realm.empty()) { // 无需认证 invoker(""); return; @@ -441,10 +444,26 @@ void installWebHook() { // 监听rtsp、rtmp源注册或注销事件 NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaChanged, [](BroadcastMediaChangedArgs) { - GET_CONFIG(string, hook_stream_chaned, Hook::kOnStreamChanged); - if (!hook_enable || hook_stream_chaned.empty()) { + GET_CONFIG(string, hook_stream_changed, Hook::kOnStreamChanged); + if (!hook_enable || hook_stream_changed.empty()) { return; } + GET_CONFIG_FUNC(std::set, 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); @@ -455,7 +474,7 @@ void installWebHook() { body["regist"] = bRegist; } // 执行hook - do_http_hook(hook_stream_chaned, body, nullptr); + do_http_hook(hook_stream_changed, body, nullptr); }); GET_CONFIG_FUNC(vector, origin_urls, Cluster::kOriginUrl, [](const string &str) { @@ -542,7 +561,7 @@ void installWebHook() { NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastShellLogin, [](BroadcastShellLoginArgs) { GET_CONFIG(string, hook_shell_login, Hook::kOnShellLogin); - if (!hook_enable || hook_shell_login.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_shell_login.empty()) { invoker(""); return; } @@ -585,7 +604,7 @@ void installWebHook() { }); }); - NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastSendRtpStopped, [](BroadcastSendRtpStopped) { + NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastSendRtpStopped, [](BroadcastSendRtpStoppedArgs) { GET_CONFIG(string, hook_send_rtp_stopped, Hook::kOnSendRtpStopped); if (!hook_enable || hook_send_rtp_stopped.empty()) { return; @@ -620,15 +639,14 @@ void installWebHook() { // 追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能 NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastHttpAccess, [](BroadcastHttpAccessArgs) { GET_CONFIG(string, hook_http_access, Hook::kOnHttpAccess); - if (sender.get_peer_ip() == "127.0.0.1" || parser.params() == hook_adminparams) { - // 如果是本机或超级管理员访问,那么不做访问鉴权;权限有效期1个小时 - invoker("", "", 60 * 60); - return; - } if (!hook_enable || hook_http_access.empty()) { // 未开启http文件访问鉴权,那么允许访问,但是每次访问都要鉴权; // 因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权) - invoker("", "", 0); + if (!HttpFileManager::isIPAllowed(sender.get_peer_ip())) { + invoker("Your ip is not allowed to access the service.", "", 0); + } else { + invoker("", "", 0); + } return; } @@ -656,7 +674,7 @@ void installWebHook() { }); }); - NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeout) { + NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeoutArgs) { GET_CONFIG(string, rtp_server_timeout, Hook::kOnRtpServerTimeout); if (!hook_enable || rtp_server_timeout.empty()) { return; diff --git a/server/main.cpp b/server/main.cpp index d09bf5d5..70b6348f 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -351,6 +351,14 @@ int start_main(int argc,char *argv[]) { #endif //defined(ENABLE_SRT) try { + auto &secret = mINI::Instance()[API::kSecret]; + if (secret == "035c73f7-bb6b-4889-a715-d9eb2d1925cc" || secret.empty()) { + // 使用默认secret被禁止启动 + secret = makeRandStr(32, true); + mINI::Instance().dumpFile(g_ini_file); + WarnL << "The " << API::kSecret << " is invalid, modified it to: " << secret + << ", saved config file: " << g_ini_file; + } //rtsp服务器,端口默认554 if (rtspPort) { rtspSrv->start(rtspPort); } //rtsps服务器,端口默认322 @@ -388,8 +396,7 @@ int start_main(int argc,char *argv[]) { #endif//defined(ENABLE_SRT) } catch (std::exception &ex) { - WarnL << "端口占用或无权限:" << ex.what(); - ErrorL << "程序启动失败,请修改配置文件中端口号后重试!"; + ErrorL << "Start server failed: " << ex.what(); sleep(1); #if !defined(_WIN32) if (pid != getpid() && kill_parent_if_failed) { @@ -413,6 +420,12 @@ int start_main(int argc,char *argv[]) { sem.post(); }); // 设置退出信号 + signal(SIGTERM,[](int) { + WarnL << "SIGTERM:exit"; + signal(SIGTERM, SIG_IGN); + sem.post(); + }); + #if !defined(_WIN32) signal(SIGHUP, [](int) { mediakit::loadIniConfig(g_ini_file.data()); }); #endif 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/MediaSource.cpp b/src/Common/MediaSource.cpp index 7c48c3e1..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; @@ -56,9 +58,11 @@ ProtocolOption::ProtocolOption() { GET_CONFIG(int, s_modify_stamp, Protocol::kModifyStamp); GET_CONFIG(bool, s_enabel_audio, Protocol::kEnableAudio); GET_CONFIG(bool, s_add_mute_audio, Protocol::kAddMuteAudio); + GET_CONFIG(bool, s_auto_close, Protocol::kAutoClose); GET_CONFIG(uint32_t, s_continue_push_ms, Protocol::kContinuePushMS); GET_CONFIG(bool, s_enable_hls, Protocol::kEnableHls); + GET_CONFIG(bool, s_enable_hls_fmp4, Protocol::kEnableHlsFmp4); GET_CONFIG(bool, s_enable_mp4, Protocol::kEnableMP4); GET_CONFIG(bool, s_enable_rtsp, Protocol::kEnableRtsp); GET_CONFIG(bool, s_enable_rtmp, Protocol::kEnableRtmp); @@ -80,9 +84,11 @@ ProtocolOption::ProtocolOption() { modify_stamp = s_modify_stamp; enable_audio = s_enabel_audio; add_mute_audio = s_add_mute_audio; + auto_close = s_auto_close; continue_push_ms = s_continue_push_ms; enable_hls = s_enable_hls; + enable_hls_fmp4 = s_enable_hls_fmp4; enable_mp4 = s_enable_mp4; enable_rtsp = s_enable_rtsp; enable_rtmp = s_enable_rtmp; @@ -170,20 +176,8 @@ void MediaSource::setListener(const std::weak_ptr &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(){ @@ -275,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(); @@ -470,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) { @@ -500,7 +499,7 @@ void MediaSource::emitEvent(bool regist){ listener->onRegist(*this, regist); } //触发广播 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, regist, *this); + NOTICE_EMIT(BroadcastMediaChangedArgs, Broadcast::kBroadcastMediaChanged, regist, *this); InfoL << (regist ? "媒体注册:" : "媒体注销:") << getUrl(); } @@ -663,8 +662,15 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){ } if (!is_mp4_vod) { - //直播时触发无人观看事件,让开发者自行选择是否关闭 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastStreamNoneReader, *strong_sender); + auto muxer = strong_sender->getMuxer(); + if (muxer && muxer->getOption().auto_close) { + // 此流被标记为无人观看自动关闭流 + WarnL << "Auto cloe stream when none reader: " << strong_sender->getUrl(); + strong_sender->close(false); + } else { + // 直播时触发无人观看事件,让开发者自行选择是否关闭 + NOTICE_EMIT(BroadcastStreamNoneReaderArgs, Broadcast::kBroadcastStreamNoneReader, *strong_sender); + } } else { //这个是mp4点播,我们自动关闭 WarnL << "MP4点播无人观看,自动关闭:" << strong_sender->getUrl(); @@ -778,6 +784,11 @@ toolkit::EventPoller::Ptr MediaSourceEventInterceptor::getOwnerPoller(MediaSourc throw std::runtime_error(toolkit::demangle(typeid(*this).name()) + "::getOwnerPoller failed"); } +std::shared_ptr 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 9391e6fb..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: @@ -148,11 +151,18 @@ public: bool enable_audio; //添加静音音频,在关闭音频时,此开关无效 bool add_mute_audio; + // 无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close) + // 此配置置1时,此流如果无人观看,将不触发on_none_reader hook回调, + // 而是将直接关闭流 + bool auto_close; + //断连续推延时,单位毫秒,默认采用配置文件 uint32_t continue_push_ms; - //是否开启转换为hls + //是否开启转换为hls(mpegts) bool enable_hls; + //是否开启转换为hls(fmp4) + bool enable_hls_fmp4; //是否开启MP4录制 bool enable_mp4; //是否开启转换为rtsp/webrtc @@ -185,15 +195,20 @@ public: //hls录制保存路径 std::string hls_save_path; + // 支持通过on_publish返回值替换stream_id + std::string stream_replace; + template ProtocolOption(const MAP &allArgs) : ProtocolOption() { #define GET_OPT_VALUE(key) getArgsValue(allArgs, #key, key) GET_OPT_VALUE(modify_stamp); GET_OPT_VALUE(enable_audio); GET_OPT_VALUE(add_mute_audio); + GET_OPT_VALUE(auto_close); GET_OPT_VALUE(continue_push_ms); GET_OPT_VALUE(enable_hls); + GET_OPT_VALUE(enable_hls_fmp4); GET_OPT_VALUE(enable_mp4); GET_OPT_VALUE(enable_rtsp); GET_OPT_VALUE(enable_rtmp); @@ -211,6 +226,7 @@ public: GET_OPT_VALUE(mp4_save_path); GET_OPT_VALUE(hls_save_path); + GET_OPT_VALUE(stream_replace); } private: @@ -250,6 +266,7 @@ public: bool stopSendRtp(MediaSource &sender, const std::string &ssrc) override; float getLossRate(MediaSource &sender, TrackType type) override; toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; + std::shared_ptr getMuxer(MediaSource &sender) override; private: std::weak_ptr _listener; @@ -323,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或者文件路径 @@ -365,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 3b16f8b6..19dd41f7 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,6 +71,14 @@ static string getTrackInfoStr(const TrackSource *track_src){ return std::move(codec_info); } +const ProtocolOption &MultiMediaSourceMuxer::getOption() const { + return _option; +} + +const MediaTuple &MultiMediaSourceMuxer::getMediaTuple() const { + return _tuple; +} + std::string MultiMediaSourceMuxer::shortUrl() const { auto ret = getOriginUrl(MediaSource::NullMediaSource()); if (!ret.empty()) { @@ -79,6 +88,10 @@ std::string MultiMediaSourceMuxer::shortUrl() const { } MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_sec, const ProtocolOption &option): _tuple(tuple) { + if (!option.stream_replace.empty()) { + // 支持在on_publish hook中替换stream_id + _tuple.stream = option.stream_replace; + } _poller = EventPollerPool::Instance().getPoller(); _create_in_poller = _poller->isCurrentThread(); _option = option; @@ -97,17 +110,18 @@ MultiMediaSourceMuxer::MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_ if (option.enable_hls) { _hls = dynamic_pointer_cast(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); @@ -128,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); } } @@ -148,15 +162,13 @@ std::weak_ptr MultiMediaSourceMuxer::getTrackLi } int MultiMediaSourceMuxer::totalReaderCount() const { - auto hls = _hls; return (_rtsp ? _rtsp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) + (_ts ? _ts->readerCount() : 0) + - #if defined(ENABLE_MP4) (_fmp4 ? _fmp4->readerCount() : 0) + - #endif (_mp4 ? _option.mp4_as_player : 0) + - (hls ? hls->readerCount() : 0) + + (_hls ? _hls->readerCount() : 0) + + (_hls_fmp4 ? _hls_fmp4->readerCount() : 0) + (_ring ? _ring->readerCount() : 0); } @@ -184,6 +196,7 @@ int MultiMediaSourceMuxer::totalReaderCount(MediaSource &sender) { //此函数可能跨线程调用 bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type, bool start, const string &custom_path, size_t max_second) { + CHECK(getOwnerPoller(MediaSource::NullMediaSource())->isCurrentThread(), "Can only call setupRecord in it's owner poller"); onceToken token(nullptr, [&]() { if (_option.mp4_as_player && type == Recorder::type_mp4) { //开启关闭mp4录制,触发观看人数变化相关事件 @@ -219,19 +232,59 @@ bool MultiMediaSourceMuxer::setupRecord(MediaSource &sender, Recorder::type type } return true; } + case Recorder::type_hls_fmp4: { + if (start && !_hls_fmp4) { + //开始录制 + _option.hls_save_path = custom_path; + auto hls = dynamic_pointer_cast(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; } } @@ -263,7 +316,7 @@ void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const MediaSourceE strong_self->getOwnerPoller(MediaSource::NullMediaSource())->async([=]() { WarnL << "stream:" << strong_self->shortUrl() << " stop send rtp:" << ssrc << ", reason:" << ex; strong_self->_rtp_sender.erase(ssrc); - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastSendRtpStopped, *strong_self, ssrc, ex); + NOTICE_EMIT(BroadcastSendRtpStoppedArgs, Broadcast::kBroadcastSendRtpStopped, *strong_self, ssrc, ex); }); } }); @@ -320,6 +373,10 @@ EventPoller::Ptr MultiMediaSourceMuxer::getOwnerPoller(MediaSource &sender) { } } +std::shared_ptr MultiMediaSourceMuxer::getMuxer(MediaSource &sender) { + return shared_from_this(); +} + bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) { bool ret = false; if (_rtmp) { @@ -331,20 +388,17 @@ bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) { if (_ts) { ret = _ts->addTrack(track) ? true : ret; } -#if defined(ENABLE_MP4) if (_fmp4) { ret = _fmp4->addTrack(track) ? true : ret; } -#endif - - //拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 - auto hls = _hls; - if (hls) { - ret = hls->addTrack(track) ? true : ret; + if (_hls) { + ret = _hls->addTrack(track) ? true : ret; } - auto mp4 = _mp4; - if (mp4) { - ret = mp4->addTrack(track) ? true : ret; + if (_hls_fmp4) { + ret = _hls_fmp4->addTrack(track) ? true : ret; + } + if (_mp4) { + ret = _mp4->addTrack(track) ? true : ret; } return ret; } @@ -354,16 +408,27 @@ void MultiMediaSourceMuxer::onAllTrackReady() { setMediaListener(getDelegate()); if (_rtmp) { - _rtmp->onAllTrackReady(); + _rtmp->addTrackCompleted(); } if (_rtsp) { - _rtsp->onAllTrackReady(); + _rtsp->addTrackCompleted(); + } + if (_ts) { + _ts->addTrackCompleted(); + } + if (_mp4) { + _mp4->addTrackCompleted(); } -#if defined(ENABLE_MP4) if (_fmp4) { - _fmp4->onAllTrackReady(); + _fmp4->addTrackCompleted(); } -#endif + if (_hls) { + _hls->addTrackCompleted(); + } + if (_hls_fmp4) { + _hls_fmp4->addTrackCompleted(); + } + auto listener = _track_listener.lock(); if (listener) { listener->onAllTrackReady(); @@ -411,21 +476,17 @@ void MultiMediaSourceMuxer::resetTracks() { if (_ts) { _ts->resetTracks(); } -#if defined(ENABLE_MP4) if (_fmp4) { _fmp4->resetTracks(); } -#endif - - //拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 - auto hls = _hls; - if (hls) { - hls->resetTracks(); + if (_hls_fmp4) { + _hls_fmp4->resetTracks(); } - - auto mp4 = _mp4; - if (mp4) { - mp4->resetTracks(); + if (_hls) { + _hls->resetTracks(); + } + if (_mp4) { + _mp4->resetTracks(); } } @@ -447,23 +508,20 @@ bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) { ret = _ts->inputFrame(frame) ? true : ret; } - //拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 - //此处使用智能指针拷贝来确保线程安全,比互斥锁性能更优 - auto hls = _hls; - if (hls) { - ret = hls->inputFrame(frame) ? true : ret; - } - auto mp4 = _mp4; - if (mp4) { - ret = mp4->inputFrame(frame) ? true : ret; + if (_hls) { + ret = _hls->inputFrame(frame) ? true : ret; } -#if defined(ENABLE_MP4) + if (_hls_fmp4) { + ret = _hls_fmp4->inputFrame(frame) ? true : ret; + } + + if (_mp4) { + ret = _mp4->inputFrame(frame) ? true : ret; + } if (_fmp4) { ret = _fmp4->inputFrame(frame) ? true : ret; } -#endif - if (_ring) { if (frame->getTrackType() == TrackVideo) { // 视频时,遇到第一帧配置帧或关键帧则标记为gop开始处 @@ -485,15 +543,14 @@ bool MultiMediaSourceMuxer::isEnabled(){ if (!_is_enable || _last_check.elapsedTime() > stream_none_reader_delay_ms) { //无人观看时,每次检查是否真的无人观看 //有人观看时,则延迟一定时间检查一遍是否无人观看了(节省性能) - auto hls = _hls; _is_enable = (_rtmp ? _rtmp->isEnabled() : false) || (_rtsp ? _rtsp->isEnabled() : false) || (_ts ? _ts->isEnabled() : false) || - #if defined(ENABLE_MP4) (_fmp4 ? _fmp4->isEnabled() : false) || - #endif (_ring ? (bool)_ring->readerCount() : false) || - (hls ? hls->isEnabled() : false) || _mp4; + (_hls ? _hls->isEnabled() : false) || + (_hls_fmp4 ? _hls_fmp4->isEnabled() : false) || + _mp4; if (_is_enable) { //无人观看时,不刷新计时器,因为无人观看时每次都会检查一遍,所以刷新计数器无意义且浪费cpu diff --git a/src/Common/MultiMediaSourceMuxer.h b/src/Common/MultiMediaSourceMuxer.h index 8ee0ef7a..3e6d7e7f 100644 --- a/src/Common/MultiMediaSourceMuxer.h +++ b/src/Common/MultiMediaSourceMuxer.h @@ -131,9 +131,13 @@ public: */ toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; - 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: @@ -169,18 +173,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 3e793039..9c073f2c 100644 --- a/src/Common/Parser.cpp +++ b/src/Common/Parser.cpp @@ -48,12 +48,17 @@ void Parser::parse(const char *buf, size_t size) { clear(); auto ptr = buf; while (true) { - auto next_line = strstr(ptr, "\r\n"); - CHECK(next_line); + auto next_line = strchr(ptr, '\n'); + auto offset = 1; + CHECK(next_line && next_line > ptr); + if (*(next_line - 1) == '\r') { + next_line -= 1; + offset = 2; + } if (ptr == buf) { auto blank = strchr(ptr, ' '); CHECK(blank > ptr && blank < next_line); - _method = std::string(ptr, blank); + _method = std::string(ptr, blank - ptr); auto next_blank = strchr(blank + 1, ' '); CHECK(next_blank && next_blank < next_line); _url.assign(blank + 1, next_blank); @@ -67,7 +72,7 @@ void Parser::parse(const char *buf, size_t size) { } else { auto pos = strchr(ptr, ':'); CHECK(pos > ptr && pos < next_line); - std::string key { ptr, pos }; + std::string key { ptr, static_cast(pos - ptr) }; std::string value; if (pos[1] == ' ') { value.assign(pos + 2, next_line); @@ -76,7 +81,7 @@ void Parser::parse(const char *buf, size_t size) { } _headers.emplace_force(trim(std::move(key)), trim(std::move(value))); } - ptr = next_line + 2; + ptr = next_line + offset; if (strncmp(ptr, "\r\n", 2) == 0) { // 协议解析完毕 _content.assign(ptr + 2, buf + size); break; @@ -320,7 +325,6 @@ void splitUrl(const std::string &url, std::string &host, uint16_t &port) { host = url.substr(0, pos); checkHost(host); } - #if 0 //测试代码 static onceToken token([](){ diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 3d79d894..b113a968 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -31,7 +31,7 @@ bool loadIniConfig(const char *ini_path) { } try { mINI::Instance().parseFile(ini); - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig); + NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig); return true; } catch (std::exception &) { InfoL << "dump ini file to:" << ini; @@ -99,9 +99,11 @@ namespace Protocol { const string kModifyStamp = PROTOCOL_FIELD "modify_stamp"; const string kEnableAudio = PROTOCOL_FIELD "enable_audio"; const string kAddMuteAudio = PROTOCOL_FIELD "add_mute_audio"; +const string kAutoClose = PROTOCOL_FIELD "auto_close"; const string kContinuePushMS = PROTOCOL_FIELD "continue_push_ms"; const string kEnableHls = PROTOCOL_FIELD "enable_hls"; +const string kEnableHlsFmp4 = PROTOCOL_FIELD "enable_hls_fmp4"; const string kEnableMP4 = PROTOCOL_FIELD "enable_mp4"; const string kEnableRtsp = PROTOCOL_FIELD "enable_rtsp"; const string kEnableRtmp = PROTOCOL_FIELD "enable_rtmp"; @@ -125,8 +127,10 @@ static onceToken token([]() { mINI::Instance()[kEnableAudio] = 1; mINI::Instance()[kAddMuteAudio] = 1; mINI::Instance()[kContinuePushMS] = 15000; + mINI::Instance()[kAutoClose] = 0; mINI::Instance()[kEnableHls] = 1; + mINI::Instance()[kEnableHlsFmp4] = 0; mINI::Instance()[kEnableMP4] = 0; mINI::Instance()[kEnableRtsp] = 1; mINI::Instance()[kEnableRtmp] = 1; @@ -161,6 +165,7 @@ const string kDirMenu = HTTP_FIELD "dirMenu"; const string kForbidCacheSuffix = HTTP_FIELD "forbidCacheSuffix"; const string kForwardedIpHeader = HTTP_FIELD "forwarded_ip_header"; const string kAllowCrossDomains = HTTP_FIELD "allow_cross_domains"; +const string kAllowIPRange = HTTP_FIELD "allow_ip_range"; static onceToken token([]() { mINI::Instance()[kSendBufSize] = 64 * 1024; @@ -189,6 +194,7 @@ static onceToken token([]() { mINI::Instance()[kForbidCacheSuffix] = ""; mINI::Instance()[kForwardedIpHeader] = ""; mINI::Instance()[kAllowCrossDomains] = 1; + mINI::Instance()[kAllowIPRange] = "127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255"; }); } // namespace Http @@ -225,12 +231,10 @@ static onceToken token([]() { ////////////RTMP服务器配置/////////// namespace Rtmp { #define RTMP_FIELD "rtmp." -const string kModifyStamp = RTMP_FIELD "modifyStamp"; const string kHandshakeSecond = RTMP_FIELD "handshakeSecond"; const string kKeepAliveSecond = RTMP_FIELD "keepAliveSecond"; static onceToken token([]() { - mINI::Instance()[kModifyStamp] = false; mINI::Instance()[kHandshakeSecond] = 15; mINI::Instance()[kKeepAliveSecond] = 15; }); diff --git a/src/Common/config.h b/src/Common/config.h index fb998cd5..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配置/////////// 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/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/Factory.cpp b/src/Extension/Factory.cpp index 4e0b795c..63a2bbd5 100644 --- a/src/Extension/Factory.cpp +++ b/src/Extension/Factory.cpp @@ -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/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 eae8edd1..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(); 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/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/HttpFileManager.cpp b/src/Http/HttpFileManager.cpp index 7f69297b..5577ffbb 100644 --- a/src/Http/HttpFileManager.cpp +++ b/src/Http/HttpFileManager.cpp @@ -33,6 +33,7 @@ namespace mediakit { static int kHlsCookieSecond = 60; static const string kCookieName = "ZL_COOKIE"; static const string kHlsSuffix = "/hls.m3u8"; +static const string kHlsFMP4Suffix = "/hls.fmp4.m3u8"; struct HttpCookieAttachment { //是否已经查找到过MediaSource @@ -49,6 +50,67 @@ const string &HttpFileManager::getContentType(const char *name) { return HttpConst::getHttpContentType(name); } +#ifndef ntohll +static uint64_t ntohll(uint64_t val) { + return (((uint64_t)ntohl(val)) << 32) + ntohl(val >> 32); +} +#endif + +static uint64_t get_ip_uint64(const std::string &ip) { + try { + auto storage = SockUtil::make_sockaddr(ip.data(), 0); + if (storage.ss_family == AF_INET) { + return ntohl(reinterpret_cast(reinterpret_cast(storage).sin_addr)); + } + if (storage.ss_family == AF_INET6) { + return ntohll(reinterpret_cast(reinterpret_cast(storage).sin6_addr)); + } + } catch (std::exception &ex) { + WarnL << ex.what(); + } + return 0; +} + +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) { + ret.emplace_back(ip_min, ip_max); + } + } else if (range.size() == 1) { + auto ip = get_ip_uint64(trim(range[0])); + if (ip) { + ret.emplace_back(ip, ip); + } + } else { + WarnL << "Invalid ip range: " << item; + } + } + return ret; + }); + + if (allow_ip_range.empty()) { + return true; + } + auto ip_int = get_ip_uint64(ip); + for (auto &range : allow_ip_range) { + if (ip_int >= range.first && ip_int <= range.second) { + return true; + } + } + return false; +} + static string searchIndexFile(const string &dir){ DIR *pDir; dirent *pDirent; @@ -57,7 +119,7 @@ static string searchIndexFile(const string &dir){ } set 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 +250,7 @@ static bool emitHlsPlayed(const Parser &parser, const MediaInfo &media_info, con //cookie有效期为kHlsCookieSecond invoker(err, "", kHlsCookieSecond); }; - bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, static_cast(sender)); + bool flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, sender); if (!flag) { //未开启鉴权,那么允许播放 auth_invoker(""); @@ -278,7 +340,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo HttpCookieManager::Instance().delCookie(cookie); } - bool is_hls = media_info.schema == HLS_SCHEMA; + bool is_hls = media_info.schema == HLS_SCHEMA || media_info.schema == HLS_FMP4_SCHEMA; SockInfoImp::Ptr info = std::make_shared(); info->_identifier = sender.getIdentifier(); @@ -320,10 +382,10 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo return; } - //事件未被拦截,则认为是http下载请求 - bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, static_cast(sender)); + // 事件未被拦截,则认为是http下载请求 + bool flag = NOTICE_EMIT(BroadcastHttpAccessArgs, Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, sender); if (!flag) { - //此事件无人监听,我们默认都有权限访问 + // 此事件无人监听,我们默认都有权限访问 callback("", nullptr); } } @@ -355,7 +417,7 @@ static string pathCat(const string &a, const string &b){ * @param cb 回调对象 */ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &media_info, const string &file_path, const HttpFileManager::invoker &cb) { - bool is_hls = end_with(file_path, kHlsSuffix); + bool is_hls = end_with(file_path, kHlsSuffix) || end_with(file_path, kHlsFMP4Suffix); if (!is_hls && !File::fileExist(file_path.data())) { //文件不存在且不是hls,那么直接返回404 sendNotFound(cb); @@ -363,8 +425,13 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m } if (is_hls) { // hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS - const_cast(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()); @@ -457,7 +524,7 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m }); } -static string getFilePath(const Parser &parser,const MediaInfo &media_info, Session &sender){ +static string getFilePath(const Parser &parser,const MediaInfo &media_info, Session &sender) { GET_CONFIG(bool, enableVhost, General::kEnableVhost); GET_CONFIG(string, rootPath, Http::kRootPath); GET_CONFIG_FUNC(StrCaseMap, virtualPathMap, Http::kVirtualPath, [](const string &str) { @@ -482,7 +549,14 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess } } auto ret = File::absolutePath(enableVhost ? media_info.vhost + url : url, path); - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, static_cast(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; } @@ -504,11 +578,14 @@ void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFi if (File::is_dir(file_path.data())) { auto indexFile = searchIndexFile(file_path); if (!indexFile.empty()) { - //发现该文件夹下有index文件 + // 发现该文件夹下有index文件 file_path = pathCat(file_path, indexFile); - parser.setUrl(pathCat(parser.url(), indexFile)); - accessFile(sender, parser, media_info, file_path, cb); - return; + if (!File::is_dir(file_path.data())) { + // 不是文件夹 + parser.setUrl(pathCat(parser.url(), indexFile)); + accessFile(sender, parser, media_info, file_path, cb); + return; + } } string strMenu; //生成文件夹菜单索引 @@ -621,4 +698,4 @@ HttpResponseInvokerImp::operator bool(){ } -}//namespace mediakit \ 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 145a08a2..69407928 100644 --- a/src/Http/HttpSession.cpp +++ b/src/Http/HttpSession.cpp @@ -170,7 +170,7 @@ void HttpSession::onError(const SockException &err) { GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_total_bytes_usage >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration, true, static_cast(*this)); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration, true, *this); } return; } @@ -311,7 +311,7 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix } }; - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _mediaInfo, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _mediaInfo, invoker, *this); if (!flag) { // 该事件无人监听,默认不鉴权 onRes(""); @@ -338,7 +338,11 @@ bool HttpSession::checkLiveStreamFMP4(const function &cb) { 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) { @@ -378,7 +382,11 @@ bool HttpSession::checkLiveStreamTS(const function &cb) { 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->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) { @@ -711,7 +719,7 @@ bool HttpSession::emitHttpEvent(bool doInvoke) { }; ///////////////////广播HTTP事件/////////////////////////// bool consumed = false; // 该事件是否被消费 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, static_cast(*this)); + NOTICE_EMIT(BroadcastHttpRequestArgs, Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, *this); if (!consumed && doInvoke) { // 该事件无人消费,所以返回404 invoker(404, KeyValue(), HttpBody::Ptr()); diff --git a/src/Record/HlsMaker.cpp b/src/Record/HlsMaker.cpp index bc6cf53d..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,83 +16,76 @@ using namespace std; namespace mediakit { -HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number, bool seg_keep) { +HlsMaker::HlsMaker(bool is_fmp4, float seg_duration, uint32_t seg_number, bool seg_keep) { + _is_fmp4 = is_fmp4; //最小允许设置为0,0个切片代表点播 _seg_number = seg_number; _seg_duration = seg_duration; _seg_keep = seg_keep; } -HlsMaker::~HlsMaker() = default; - void HlsMaker::makeIndexFile(bool eof) { - char file_content[1024]; int maxSegmentDuration = 0; - for (auto &tp : _seg_dur_list) { int dur = std::get<0>(tp); if (dur > maxSegmentDuration) { maxSegmentDuration = dur; } } + auto index_seq = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL; - auto sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL; - - string m3u8; - if (_seg_number == 0) { - // 录像点播支持时移 - snprintf(file_content, sizeof(file_content), - "#EXTM3U\n" - "#EXT-X-PLAYLIST-TYPE:EVENT\n" - "#EXT-X-VERSION:4\n" - "#EXT-X-TARGETDURATION:%u\n" - "#EXT-X-MEDIA-SEQUENCE:%llu\n", - (maxSegmentDuration + 999) / 1000, - sequence); + string index_str; + index_str.reserve(2048); + index_str += "#EXTM3U\n"; + index_str += (_is_fmp4 ? "#EXT-X-VERSION:7\n" : "#EXT-X-VERSION:4\n"); + if (_seg_number == 0) { + index_str += "#EXT-X-PLAYLIST-TYPE:EVENT\n"; } else { - snprintf(file_content, sizeof(file_content), - "#EXTM3U\n" - "#EXT-X-VERSION:3\n" - "#EXT-X-ALLOW-CACHE:NO\n" - "#EXT-X-TARGETDURATION:%u\n" - "#EXT-X-MEDIA-SEQUENCE:%llu\n", - (maxSegmentDuration + 999) / 1000, - sequence); + index_str += "#EXT-X-ALLOW-CACHE:NO\n"; + } + index_str += "#EXT-X-TARGETDURATION:" + std::to_string((maxSegmentDuration + 999) / 1000) + "\n"; + index_str += "#EXT-X-MEDIA-SEQUENCE:" + std::to_string(index_seq) + "\n"; + if (_is_fmp4) { + index_str += "#EXT-X-MAP:URI=\"init.mp4\"\n"; } - - m3u8.assign(file_content); + stringstream ss; for (auto &tp : _seg_dur_list) { - snprintf(file_content, sizeof(file_content), "#EXTINF:%.3f,\n%s\n", std::get<0>(tp) / 1000.0, std::get<1>(tp).data()); - m3u8.append(file_content); + ss << "#EXTINF:" << std::setprecision(3) << std::get<0>(tp) / 1000.0 << ",\n" << std::get<1>(tp) << "\n"; } + index_str += ss.str(); if (eof) { - snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n"); - m3u8.append(file_content); + index_str += "#EXT-X-ENDLIST\n"; } - onWriteHls(m3u8); + onWriteHls(index_str); } +void HlsMaker::inputInitSegment(const char *data, size_t len) { + if (!_is_fmp4) { + throw std::invalid_argument("Only fmp4-hls can input init segment"); + } + onWriteInitSegment(data, len); +} -void HlsMaker::inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) { +void HlsMaker::inputData(const char *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) { if (data && len) { if (timestamp < _last_timestamp) { - //时间戳回退了,切片时长重新计时 - WarnL << "stamp reduce: " << _last_timestamp << " -> " << timestamp; + // 时间戳回退了,切片时长重新计时 + WarnL << "Timestamp reduce: " << _last_timestamp << " -> " << timestamp; _last_seg_timestamp = _last_timestamp = timestamp; } if (is_idr_fast_packet) { - //尝试切片ts + // 尝试切片ts addNewSegment(timestamp); } if (!_last_file_name.empty()) { - //存在切片才写入ts数据 - onWriteSegment((char *) data, len); + // 存在切片才写入ts数据 + onWriteSegment(data, len); _last_timestamp = timestamp; } } else { - //resetTracks时触发此逻辑 + // resetTracks时触发此逻辑 flushLastSegment(false); } } @@ -148,14 +142,18 @@ void HlsMaker::flushLastSegment(bool eof){ makeIndexFile(eof); } -bool HlsMaker::isLive() { +bool HlsMaker::isLive() const { return _seg_number != 0; } -bool HlsMaker::isKeep() { +bool HlsMaker::isKeep() const { return _seg_keep; } +bool HlsMaker::isFmp4() const { + return _is_fmp4; +} + void HlsMaker::clear() { _file_index = 0; _last_timestamp = 0; 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 f3fe516b..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; + }); } } } @@ -47,7 +53,7 @@ HlsCookieData::~HlsCookieData() { uint64_t bytes = _bytes.load(); if (bytes >= iFlowThreshold * 1024) { try { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, static_cast(*_sock_info)); + 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 a1bbd34c..02c66976 100644 --- a/src/Record/MP4Demuxer.cpp +++ b/src/Record/MP4Demuxer.cpp @@ -102,7 +102,7 @@ void MP4Demuxer::onVideoTrack(uint32_t track, uint8_t object, int width, int hei uint8_t config[1024 * 10] = {0}; int size = mpeg4_avc_to_nalu(&avc, config, sizeof(config)); if (size > 0) { - video->inputFrame(std::make_shared((char *)config, size, 0, 4)); + video->inputFrame(std::make_shared((char *)config, size, 0, 0,4)); } } } @@ -117,7 +117,7 @@ void MP4Demuxer::onVideoTrack(uint32_t track, uint8_t object, int width, int hei uint8_t config[1024 * 10] = {0}; int size = mpeg4_hevc_to_nalu(&hevc, config, sizeof(config)); if (size > 0) { - video->inputFrame(std::make_shared((char *) config, size, 0, 4)); + video->inputFrame(std::make_shared((char *) config, size, 0, 0,4)); } } } @@ -247,7 +247,7 @@ Frame::Ptr MP4Demuxer::makeFrame(uint32_t track_id, const Buffer::Ptr &buf, int6 AACTrack::Ptr track = dynamic_pointer_cast(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; } 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 5c144d2e..2def3781 100644 --- a/src/Record/MP4Reader.cpp +++ b/src/Record/MP4Reader.cpp @@ -184,8 +184,10 @@ bool MP4Reader::speed(MediaSource &sender, float speed) { WarnL << "播放速度取值范围非法:" << speed; return false; } - //设置播放速度后应该恢复播放 - pause(sender, false); + //_seek_ticker重置,赋值_seek_to + setCurrentStamp(getCurrentStamp()); + // 设置播放速度后应该恢复播放 + _paused = false; if (_speed == speed) { return true; } diff --git a/src/Record/MP4Recorder.cpp b/src/Record/MP4Recorder.cpp index 4784fcda..5373b8a1 100644 --- a/src/Record/MP4Recorder.cpp +++ b/src/Record/MP4Recorder.cpp @@ -54,6 +54,7 @@ void MP4Recorder::createFile() { 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/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 a68df5bc..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) { @@ -107,14 +111,12 @@ void FlvMuxer::onWriteFlvHeader(const RtmpMediaSource::Ptr &src) { //flv header onWrite(buffer, false); - auto &metadata = src->getMetaData(); - if (metadata) { - //在有metadata的情况下才发送metadata - //其实metadata没什么用,有些推流器不产生metadata + // metadata + src->getMetaData([&](const AMFValue &metadata) { AMFEncoder invoke; invoke << "onMetaData" << metadata; onWriteFlvTag(MSG_DATA, std::make_shared(invoke.data()), 0, false); - } + }); //config frame src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) { diff --git a/src/Rtmp/FlvPlayer.cpp b/src/Rtmp/FlvPlayer.cpp index c0dc1dfa..fbefd23e 100644 --- a/src/Rtmp/FlvPlayer.cpp +++ b/src/Rtmp/FlvPlayer.cpp @@ -62,7 +62,7 @@ bool FlvPlayer::onRecvMetadata(const AMFValue &metadata) { } void FlvPlayer::onRecvRtmpPacket(RtmpPacket::Ptr packet) { - if (!_play_result && !packet->isCfgFrame()) { + if (!_play_result && !packet->isConfigFrame()) { _play_result = true; onPlayResult(SockException(Err_success, "play http-flv success")); } diff --git a/src/Rtmp/FlvSplitter.cpp b/src/Rtmp/FlvSplitter.cpp index c2e21e6a..3e99d9ef 100644 --- a/src/Rtmp/FlvSplitter.cpp +++ b/src/Rtmp/FlvSplitter.cpp @@ -91,21 +91,26 @@ void FlvSplitter::onRecvContent(const char *data, size_t len) { case MSG_DATA3: { BufferLikeString buffer(string(data, len)); AMFDecoder dec(buffer, _type == MSG_DATA3 ? 3 : 0); - std::string type = dec.load(); + auto first = dec.load(); bool flag = true; - if (type == "@setDataFrame") { - std::string type = dec.load(); - if (type == "onMetaData") { + 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 type:" << type; + WarnL << "unknown notify:" << type; } - } else if (type == "onMetaData") { - flag = onRecvMetadata(dec.load()); } else { - WarnL << "unknown notify:" << type; + WarnL << "Parse flv script data failed, invalid amf value: " << first.to_string(); } - if(!flag){ + if (!flag) { throw std::invalid_argument("check rtmp metadata failed"); } return; 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 739d4cf5..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,7 +88,7 @@ public: uint8_t body_size[3]; uint8_t type_id; uint8_t stream_index[4]; /* Note, this is little-endian while others are BE */ -}PACKED; +}; class FLVHeader { public: @@ -142,7 +121,7 @@ public: uint32_t length; //固定为0 uint32_t previous_tag_size0; -} PACKED; +}; class RtmpTagHeader { public: @@ -151,11 +130,9 @@ public: uint8_t timestamp[3] = {0}; uint8_t timestamp_ex = 0; uint8_t streamid[3] = {0}; /* Always 0. */ -} PACKED; +}; -#if defined(_WIN32) #pragma pack(pop) -#endif // defined(_WIN32) class RtmpPacket : public toolkit::Buffer{ public: @@ -182,11 +159,15 @@ public: void clear(); + // video config frame和key frame都返回true + // 用于gop缓存定位 bool isVideoKeyFrame() const; - bool isCfgFrame() const; - int getMediaType() const; + // aac config或h264/h265 config返回true,支持增强型rtmp + // 用于缓存解码配置信息 + bool isConfigFrame() const; + int getRtmpCodecId() const; int getAudioSampleRate() const; int getAudioSampleBit() const; int getAudioChannel() const; @@ -269,5 +250,121 @@ private: //根据音频track获取flags uint8_t getAudioRtmpFlags(const Track::Ptr &track); +////////////////// rtmp video ////////////////////////// +//https://rtmp.veriskope.com/pdf/video_file_format_spec_v10_1.pdf + +// UB [4]; Type of video frame. +enum class RtmpFrameType : uint8_t { + reserved = 0, + key_frame = 1, // key frame (for AVC, a seekable frame) + inter_frame = 2, // inter frame (for AVC, a non-seekable frame) + disposable_inter_frame = 3, // disposable inter frame (H.263 only) + generated_key_frame = 4, // generated key frame (reserved for server use only) + video_info_frame = 5, // video info/command frame +}; + +#define MKBETAG(a, b, c, d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24)) + +// UB [4]; Codec Identifier. +enum class RtmpVideoCodec : uint32_t { + h263 = 2, // Sorenson H.263 + screen_video = 3, // Screen video + vp6 = 4, // On2 VP6 + vp6_alpha = 5, // On2 VP6 with alpha channel + screen_video2 = 6, // Screen video version 2 + h264 = 7, // avc + h265 = 12, // 国内扩展 + + // 增强型rtmp FourCC + fourcc_vp9 = MKBETAG('v', 'p', '0', '9'), + fourcc_av1 = MKBETAG('a', 'v', '0', '1'), + fourcc_hevc = MKBETAG('h', 'v', 'c', '1') +}; + +// UI8; +enum class RtmpH264PacketType : uint8_t { + h264_config_header = 0, // AVC or HEVC sequence header(sps/pps) + h264_nalu = 1, // AVC or HEVC NALU + h264_end_seq = 2, // AVC or HEVC end of sequence (lower level NALU sequence ender is not REQUIRED or supported) +}; + +// https://github.com/veovera/enhanced-rtmp/blob/main/enhanced-rtmp.pdf +// UB[4] +enum class RtmpPacketType : uint8_t { + PacketTypeSequenceStart = 0, + PacketTypeCodedFrames = 1, + PacketTypeSequenceEnd = 2, + + // CompositionTime Offset is implied to equal zero. This is + // an optimization to save putting SI24 composition time value of zero on + // the wire. See pseudo code below in the VideoTagBody section + PacketTypeCodedFramesX = 3, + + // VideoTagBody does not contain video data. VideoTagBody + // instead contains an AMF encoded metadata. See Metadata Frame + // section for an illustration of its usage. As an example, the metadata + // can be HDR information. This is a good way to signal HDR + // information. This also opens up future ways to express additional + // metadata that is meant for the next video sequence. + // + // note: presence of PacketTypeMetadata means that FrameType + // flags at the top of this table should be ignored + PacketTypeMetadata = 4, + + // Carriage of bitstream in MPEG-2 TS format + // note: PacketTypeSequenceStart and PacketTypeMPEG2TSSequenceStart + // are mutually exclusive + PacketTypeMPEG2TSSequenceStart = 5, +}; + +////////////////// rtmp audio ////////////////////////// +//https://rtmp.veriskope.com/pdf/video_file_format_spec_v10_1.pdf + +// UB [4]; Format of SoundData +enum class RtmpAudioCodec : uint8_t { + /** + 0 = Linear PCM, platform endian + 1 = ADPCM + 2 = MP3 + 3 = Linear PCM, little endian + 4 = Nellymoser 16 kHz mono + 5 = Nellymoser 8 kHz mono + 6 = Nellymoser + 7 = G.711 A-law logarithmic PCM + 8 = G.711 mu-law logarithmic PCM + 9 = reserved + 10 = AAC + 11 = Speex + 14 = MP3 8 kHz + 15 = Device-specific sound + */ + g711a = 7, + g711u = 8, + aac = 10, + opus = 13 // 国内扩展 +}; + +// UI8; +enum class RtmpAACPacketType : uint8_t { + aac_config_header = 0, // AAC sequence header + aac_raw = 1, // AAC raw +}; + +//////////////////////////////////////////// + +struct RtmpPacketInfo { + CodecId codec = CodecInvalid; + bool is_enhanced; + union { + struct { + RtmpFrameType frame_type; + RtmpPacketType pkt_type; // enhanced = true + RtmpH264PacketType h264_pkt_type; // enhanced = false + } video; + }; +}; +// https://github.com/veovera/enhanced-rtmp +CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info = nullptr); + }//namespace mediakit #endif//__rtmp_h 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 9546d889..42914cda 100644 --- a/src/Rtmp/RtmpPlayer.cpp +++ b/src/Rtmp/RtmpPlayer.cpp @@ -191,6 +191,13 @@ void RtmpPlayer::send_connect() { obj.set("audioCodecs", (double) (0x0400)); //只支持H264 obj.set("videoCodecs", (double) (0x0080)); + + AMFValue fourCcList(AMF_STRICT_ARRAY); + fourCcList.add("av01"); + fourCcList.add("vp09"); + fourCcList.add("hvc1"); + obj.set("fourCcList", fourCcList); + sendInvoke("connect", obj); addOnResultCB([this](AMFDecoder &dec) { //TraceL << "connect result"; @@ -332,7 +339,7 @@ void RtmpPlayer::onMediaData_l(RtmpPacket::Ptr chunk_data) { return; } - if (chunk_data->isCfgFrame()) { + if (chunk_data->isConfigFrame()) { //输入配置帧以便初始化完成各个track onRtmpPacket(chunk_data); } else { diff --git a/src/Rtmp/RtmpPusher.cpp b/src/Rtmp/RtmpPusher.cpp index e8c55f70..f401cf34 100644 --- a/src/Rtmp/RtmpPusher.cpp +++ b/src/Rtmp/RtmpPusher.cpp @@ -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 c5e90d5c..fd050ae5 100644 --- a/src/Rtmp/RtmpSession.cpp +++ b/src/Rtmp/RtmpSession.cpp @@ -37,7 +37,7 @@ void RtmpSession::onError(const SockException& err) { GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_total_bytes >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, is_player, static_cast(*this)); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, is_player, *this); } //如果是主动关闭的,那么不延迟注销 @@ -240,7 +240,7 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) { on_res(err, option); }); }; - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtmp_push, _media_info, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtmp_push, _media_info, invoker, *this); if(!flag){ //该事件无人监听,默认鉴权成功 on_res("", ProtocolOption()); @@ -316,17 +316,14 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr "description", "Now published." , "details", _media_info.stream, "clientid", "0"}); - - auto &metadata = src->getMetaData(); - if(metadata){ - //在有metadata的情况下才发送metadata - //其实metadata没什么用,有些推流器不产生metadata - // onMetaData + // metadata + src->getMetaData([&](const AMFValue &metadata) { invoke.clear(); invoke << "onMetaData" << metadata; sendResponse(MSG_DATA, invoke.data()); - } + }); + // config frame src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) { onSendMedia(pkt); }); @@ -334,7 +331,11 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr src->pause(false); _ring_reader = src->getRing()->attach(getPoller()); weak_ptr 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) { @@ -355,6 +356,7 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr if (!strong_self) { return; } + strong_self->sendUserControl(CONTROL_STREAM_EOF/*or CONTROL_STREAM_DRY ?*/, STREAM_MEDIA); strong_self->shutdown(SockException(Err_shutdown,"rtmp ring buffer detached")); }); src->pause(false); @@ -408,7 +410,7 @@ void RtmpSession::doPlay(AMFDecoder &dec){ }); }; - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _media_info, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _media_info, invoker, *this); if (!flag) { // 该事件无人监听,默认不鉴权 doPlayResponse("", [token](bool) {}); @@ -506,6 +508,7 @@ void RtmpSession::setMetaData(AMFDecoder &dec) { throw std::runtime_error("can only set metadata"); } _push_metadata = dec.load(); + _set_meta_data = false; } void RtmpSession::onProcessCmd(AMFDecoder &dec) { @@ -553,6 +556,7 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) { } else if (type == "onMetaData") { //兼容某些不规范的推流器 _push_metadata = dec.load(); + _set_meta_data = false; } else { TraceP(this) << "unknown notify:" << type; } @@ -562,7 +566,14 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) { case MSG_AUDIO: case MSG_VIDEO: { if (!_push_src) { - WarnL << "Not a rtmp push!"; + if (_ring_reader) { + throw std::runtime_error("Rtmp player send media packets"); + } + if (packet->isConfigFrame()) { + auto id = packet->type_id; + _push_config_packets.emplace(id, std::move(packet)); + } + WarnL << "Rtmp pusher send media packet before handshake completed!"; return; } @@ -570,6 +581,12 @@ void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) { _set_meta_data = true; _push_src->setMetaData(_push_metadata ? _push_metadata : TitleMeta().getMetadata()); } + if (!_push_config_packets.empty()) { + for (auto &pr : _push_config_packets) { + _push_src->onWrite(std::move(pr.second)); + } + _push_config_packets.clear(); + } _push_src->onWrite(std::move(packet)); break; } 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 741cc4b1..5e61ccf4 100644 --- a/src/Rtp/RtpProcess.cpp +++ b/src/Rtp/RtpProcess.cpp @@ -67,7 +67,7 @@ RtpProcess::~RtpProcess() { GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_total_bytes >= iFlowThreshold * 1024) { try { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, false, static_cast(*this)); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, false, *this); } catch (std::exception &ex) { WarnL << "Exception occurred: " << ex.what(); } @@ -210,31 +210,27 @@ void RtpProcess::setOnDetach(function cb) { } string RtpProcess::get_peer_ip() { - if (!_addr) { + try { + return _addr ? SockUtil::inet_ntoa((sockaddr *)_addr.get()) : "::"; + } catch (std::exception &ex) { return "::"; } - return SockUtil::inet_ntoa((sockaddr *)_addr.get()); } uint16_t RtpProcess::get_peer_port() { - if (!_addr) { + try { + return _addr ? SockUtil::inet_port((sockaddr *)_addr.get()) : 0; + } catch (std::exception &ex) { return 0; } - return SockUtil::inet_port((sockaddr *)_addr.get()); } string RtpProcess::get_local_ip() { - if (_sock) { - return _sock->get_local_ip(); - } - return "::"; + return _sock ? _sock->get_local_ip() : "::"; } uint16_t RtpProcess::get_local_port() { - if (_sock) { - return _sock->get_local_port(); - } - return 0; + return _sock ? _sock->get_local_port() : 0; } string RtpProcess::getIdentifier() const { @@ -270,9 +266,9 @@ void RtpProcess::emitOnPublish() { }; //触发推流鉴权事件 - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, MediaOriginType::rtp_push, _media_info, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPublishArgs, Broadcast::kBroadcastMediaPublish, MediaOriginType::rtp_push, _media_info, invoker, *this); if (!flag) { - //该事件无人监听,默认不鉴权 + // 该事件无人监听,默认不鉴权 invoker("", ProtocolOption()); } } @@ -305,4 +301,4 @@ float RtpProcess::getLossRate(MediaSource &sender, TrackType type) { } }//namespace mediakit -#endif//defined(ENABLE_RTPPROXY) \ No newline at end of file +#endif//defined(ENABLE_RTPPROXY) diff --git a/src/Rtp/RtpServer.cpp b/src/Rtp/RtpServer.cpp index 59e207ea..78aee8e1 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 2e3618da..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; @@ -197,14 +204,14 @@ void SdpParser::load(const string &sdp) { auto &track = *track_ptr; auto it = track._attr.find("range"); if (it != track._attr.end()) { - char name[16] = {0}, start[16] = {0}, end[16] = {0}; + char name[16] = { 0 }, start[16] = { 0 }, end[16] = { 0 }; int ret = sscanf(it->second.data(), "%15[^=]=%15[^-]-%15s", name, start, end); if (3 == ret || 2 == ret) { if (strcmp(start, "now") == 0) { strcpy(start, "0"); } - track._start = (float) atof(start); - track._end = (float) atof(end); + track._start = (float)atof(start); + track._end = (float)atof(end); track._duration = track._end - track._start; } } @@ -212,11 +219,11 @@ void SdpParser::load(const string &sdp) { for (it = track._attr.find("rtpmap"); it != track._attr.end() && it->first == "rtpmap";) { auto &rtpmap = it->second; int pt, samplerate, channel; - char codec[16] = {0}; + char codec[16] = { 0 }; sscanf(rtpmap.data(), "%d", &pt); if (track._pt != pt && track._pt != 0xff) { - //pt不匹配 + // pt不匹配 it = track._attr.erase(it); continue; } @@ -230,18 +237,18 @@ void SdpParser::load(const string &sdp) { track._samplerate = samplerate; } if (!track._samplerate && track._type == TrackVideo) { - //未设置视频采样率时,赋值为90000 + // 未设置视频采样率时,赋值为90000 track._samplerate = 90000; } ++it; } - for (it = track._attr.find("fmtp"); it != track._attr.end() && it->first == "fmtp"; ) { + for (it = track._attr.find("fmtp"); it != track._attr.end() && it->first == "fmtp";) { auto &fmtp = it->second; int pt; sscanf(fmtp.data(), "%d", &pt); if (track._pt != pt && track._pt != 0xff) { - //pt不匹配 + // pt不匹配 it = track._attr.erase(it); continue; } @@ -315,8 +322,17 @@ string SdpParser::toString() const { return title + video + audio; } -template -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 0dafa03a..f6a13fda 100644 --- a/src/Rtsp/RtspPlayer.cpp +++ b/src/Rtsp/RtspPlayer.cpp @@ -38,8 +38,8 @@ RtspPlayer::~RtspPlayer(void) { 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")); } @@ -203,6 +203,8 @@ void RtspPlayer::handleResDESCRIBE(const Parser &parser) { // 解析sdp SdpParser sdpParser(parser.content()); + _control_url = sdpParser.getControlUrl(_content_base); + string sdp; auto play_track = (TrackType)((int)(*this)[Client::kPlayTrack] - 1); if (play_track != TrackInvalid) { @@ -412,7 +414,7 @@ void RtspPlayer::sendKeepAlive() { _on_response = [](const Parser &parser) {}; if (_supported_cmd.find("GET_PARAMETER") != _supported_cmd.end()) { // 支持GET_PARAMETER,用此命令保活 - sendRtspRequest("GET_PARAMETER", _content_base); + sendRtspRequest("GET_PARAMETER", _control_url); } else { // 不支持GET_PARAMETER,用OPTIONS命令保活 sendRtspRequest("OPTIONS", _play_url); @@ -423,12 +425,12 @@ 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; + case type_pause: sendRtspRequest("PAUSE", _control_url, {}); break; case type_play: // 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; @@ -442,7 +444,7 @@ 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) { @@ -451,7 +453,7 @@ void RtspPlayer::handleResPAUSE(const Parser &parser, int type) { case type_pause: WarnL << "Pause failed:" << parser.status() << " " << parser.statusStr(); break; case type_play: WarnL << "Play failed:" << parser.status() << " " << parser.statusStr(); - onPlayResult_l(SockException(Err_shutdown, StrPrinter << "rtsp play failed:" << parser.status() << " " << parser.statusStr()), !_play_check_timer); + 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; } @@ -481,6 +483,11 @@ 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); @@ -537,6 +544,9 @@ 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(); @@ -571,6 +581,7 @@ void RtspPlayer::sendRtspRequest(const string &cmd, const string &url, const std key = val; } } + sendRtspRequest(cmd, url, header_map); } @@ -615,12 +626,9 @@ void RtspPlayer::sendRtspRequest(const string &cmd, const string &url, const Str } _StrPrinter printer; - if (cmd == "PLAY") { - printer << cmd << " " << _play_url << " RTSP/1.0\r\n"; - } else { - printer << cmd << " " << url << " RTSP/1.0\r\n"; - } + printer << cmd << " " << url << " RTSP/1.0\r\n"; + TraceL << cmd << " "<< url; for (auto &pr : header) { printer << pr.first << ": " << pr.second << "\r\n"; } @@ -743,7 +751,7 @@ 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)); } /////////////////////////////////////////////////// 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/RtspSession.cpp b/src/Rtsp/RtspSession.cpp index 25ff3a72..c9438829 100644 --- a/src/Rtsp/RtspSession.cpp +++ b/src/Rtsp/RtspSession.cpp @@ -81,7 +81,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); } //如果是主动关闭的,那么不延迟注销 @@ -343,7 +343,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()); @@ -401,7 +401,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(""); @@ -441,7 +441,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(""); } @@ -546,7 +546,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事件!"; //但是我们还是忽略认证以便完成播放 @@ -630,7 +630,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事件!"; //但是我们还是忽略认证以便完成播放 @@ -770,11 +770,11 @@ void RtspSession::handleReq_Setup(const Parser &parser) { 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); @@ -906,7 +906,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) { 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 c3c18c5d..56d6bd8a 100644 --- a/srt/SrtTransportImp.cpp +++ b/srt/SrtTransportImp.cpp @@ -17,7 +17,7 @@ SrtTransportImp::~SrtTransportImp() { GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_total_bytes >= iFlowThreshold * 1024) { try { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, !_is_pusher, static_cast(*this)); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, !_is_pusher, *this); } catch (std::exception &ex) { WarnL << "Exception occurred: " << ex.what(); } @@ -172,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()); @@ -197,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(); } @@ -227,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/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/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 e8e46a40..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,6 +99,7 @@ void WebRtcPusher::onRecvRtp(MediaTrack &track, const string &rid, RtpPacket::Pt } } else { //视频 + std::lock_guard lock(_mtx); auto &src = _push_src_sim[rid]; if (!src) { const auto& stream = _push_src->getMediaTuple().stream; @@ -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 031710e8..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) { @@ -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..a29629bd 100644 --- a/www/webrtc/index.html +++ b/www/webrtc/index.html @@ -89,28 +89,35 @@