From bc1d31e148aed0b67d2ee377e785fec37291fbd4 Mon Sep 17 00:00:00 2001 From: xiongguangjie Date: Thu, 12 Oct 2023 17:09:20 +0800 Subject: [PATCH] Push force replace (#2899) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alex Co-authored-by: xia-chu <771730766@qq.com> Co-authored-by: Johnny Co-authored-by: BackT0TheFuture <10088733+BackT0TheFuture@users.noreply.github.com> Co-authored-by: ljx0305 Co-authored-by: Per-Arne Andersen Co-authored-by: codeRATny <60806889+codeRATny@users.noreply.github.com> Co-authored-by: 老衲不出家 Co-authored-by: Kiki Co-authored-by: PioLing <964472638@qq.com> Co-authored-by: dengjfzh <76604422+dengjfzh@users.noreply.github.com> Co-authored-by: a-ucontrol <55526028+a-ucontrol@users.noreply.github.com> Co-authored-by: 百鸣 <94030128+ixingqiao@users.noreply.github.com> Co-authored-by: fruit Juice <2317232721@qq.com> Co-authored-by: Luosh Co-authored-by: tbago Co-authored-by: Talus Co-authored-by: 朱如洪 Co-authored-by: pedoc Co-authored-by: XiaoYan Lin Co-authored-by: xiangshengjye <46069012+xiangshengjye@users.noreply.github.com> Co-authored-by: tjpgt <602950305@qq.com> Co-authored-by: Nick Co-authored-by: yogo-zhangyingzhe <100331270+yogo-zhangyingzhe@users.noreply.github.com> Co-authored-by: Xiaofeng Wang Co-authored-by: Dw9 Co-authored-by: waken <33921191+mc373906408@users.noreply.github.com> Co-authored-by: Deepslient <1154547394@qq.com> --- .github/workflows/docker.yml | 13 +- 3rdpart/CMakeLists.txt | 56 ++- 3rdpart/ZLToolKit | 2 +- AUTHORS | 16 +- CMakeLists.txt | 34 +- README.md | 85 +++- README_en.md | 460 +++++++++++++----- api/CMakeLists.txt | 20 +- api/include/mk_events.h | 11 + api/include/mk_events_objects.h | 28 +- api/include/mk_frame.h | 102 ++++ api/include/mk_recorder.h | 4 +- api/include/mk_tcp.h | 6 +- api/source/mk_common.cpp | 2 +- api/source/mk_events.cpp | 7 + api/source/mk_events_objects.cpp | 68 ++- api/source/mk_frame.cpp | 91 ++++ api/source/mk_httpclient.cpp | 6 +- api/source/mk_media.cpp | 5 +- api/source/mk_recorder.cpp | 14 +- api/source/mk_tcp.cpp | 8 +- api/source/mk_tcp_private.h | 2 +- api/source/mk_thread.cpp | 2 +- api/source/mk_util.cpp | 2 +- api/tests/CMakeLists.txt | 12 - api/tests/pusher.c | 1 + api/tests/server.c | 1 + cmake/Jemalloc.cmake | 23 +- conf/config.ini | 40 +- default.pem | 146 +++--- docker/centos7/Dockerfile.runtime | 2 +- docker/ubuntu16.04/Dockerfile.devel | 2 +- docker/ubuntu16.04/Dockerfile.runtime | 2 +- docker/ubuntu18.04/Dockerfile.devel | 2 +- docker/ubuntu18.04/Dockerfile.runtime | 2 +- dockerfile | 2 +- player/SDLAudioDevice.cpp | 6 +- player/test_player.cpp | 3 +- postman/ZLMediaKit.postman_collection.json | 220 +++++++-- server/CMakeLists.txt | 10 +- server/FFmpegSource.cpp | 175 ++++--- server/FFmpegSource.h | 3 +- server/Process.cpp | 2 +- server/Process.h | 2 +- server/System.cpp | 27 +- server/WebApi.cpp | 148 ++++-- server/WebApi.h | 20 +- server/WebHook.cpp | 118 +++-- server/WebHook.h | 1 + server/main.cpp | 50 +- src/Codec/Transcode.cpp | 2 + src/Common/Device.h | 6 +- src/Common/JemallocUtil.cpp | 74 +++ src/Common/JemallocUtil.h | 30 ++ src/Common/MediaSource.cpp | 127 +++-- src/Common/MediaSource.h | 85 ++-- src/Common/MultiMediaSourceMuxer.cpp | 236 +++++---- src/Common/MultiMediaSourceMuxer.h | 22 +- src/Common/Parser.cpp | 185 ++++--- src/Common/Parser.h | 99 ++-- src/Common/Stamp.cpp | 218 +++++---- src/Common/Stamp.h | 6 +- src/Common/config.cpp | 21 +- src/Common/config.h | 23 +- src/Common/macros.h | 9 +- src/{Http => Common}/strCoding.cpp | 0 src/{Http => Common}/strCoding.h | 2 +- src/Extension/AAC.cpp | 4 +- src/Extension/AAC.h | 2 +- src/Extension/AACRtmp.cpp | 73 ++- src/Extension/AACRtp.cpp | 2 +- src/Extension/CommonRtp.cpp | 7 +- src/Extension/CommonRtp.h | 1 + src/Extension/Factory.cpp | 47 +- src/Extension/Factory.h | 10 + src/Extension/Frame.cpp | 7 +- src/Extension/Frame.h | 4 +- src/Extension/H264.cpp | 12 +- src/Extension/H264Rtmp.cpp | 101 ++-- src/Extension/H264Rtp.cpp | 30 +- src/Extension/H264Rtp.h | 9 +- src/Extension/H265Rtmp.cpp | 236 +++++---- src/Extension/H265Rtmp.h | 21 +- src/Extension/H265Rtp.cpp | 2 +- src/FMP4/FMP4MediaSource.h | 12 +- src/FMP4/FMP4MediaSourceMuxer.h | 13 +- src/Http/HlsParser.cpp | 2 +- src/Http/HlsPlayer.cpp | 32 +- src/Http/HttpClient.cpp | 32 +- src/Http/HttpClient.h | 4 +- src/Http/HttpConst.cpp | 4 +- src/Http/HttpConst.h | 30 +- src/Http/HttpCookieManager.cpp | 9 +- src/Http/HttpCookieManager.h | 8 +- src/Http/HttpFileManager.cpp | 206 ++++++-- src/Http/HttpFileManager.h | 7 + src/Http/HttpRequestSplitter.cpp | 2 +- src/Http/HttpRequester.cpp | 4 +- src/Http/HttpSession.cpp | 541 ++++++++++----------- src/Http/HttpSession.h | 23 +- src/Http/TsPlayer.cpp | 9 +- src/Http/WebSocketClient.h | 6 +- src/Http/WebSocketSession.h | 2 +- src/Player/PlayerBase.cpp | 20 +- src/Player/PlayerProxy.cpp | 201 ++++++-- src/Player/PlayerProxy.h | 102 +++- src/Pusher/MediaPusher.cpp | 3 +- src/Pusher/PusherBase.cpp | 2 +- src/Pusher/PusherProxy.cpp | 59 ++- src/Pusher/PusherProxy.h | 17 +- src/Record/HlsMaker.cpp | 84 ++-- src/Record/HlsMaker.h | 32 +- src/Record/HlsMakerImp.cpp | 48 +- src/Record/HlsMakerImp.h | 12 +- src/Record/HlsMediaSource.cpp | 14 +- src/Record/HlsMediaSource.h | 8 +- src/Record/HlsRecorder.h | 76 ++- src/Record/MP4.cpp | 5 +- src/Record/MP4.h | 4 +- src/Record/MP4Demuxer.cpp | 8 +- src/Record/MP4Muxer.cpp | 22 +- src/Record/MP4Muxer.h | 36 +- src/Record/MP4Reader.cpp | 19 +- src/Record/MP4Recorder.cpp | 17 +- src/Record/MP4Recorder.h | 3 +- src/Record/MPEG.cpp | 26 +- src/Record/MPEG.h | 4 +- src/Record/Recorder.cpp | 67 ++- src/Record/Recorder.h | 26 +- src/Rtcp/Rtcp.h | 26 +- src/Rtcp/RtcpContext.cpp | 6 +- src/Rtcp/RtcpContext.h | 4 +- src/Rtcp/RtcpFCI.cpp | 4 +- src/Rtcp/RtcpFCI.h | 22 +- src/Rtmp/FlvMuxer.cpp | 24 +- src/Rtmp/FlvPlayer.cpp | 76 +++ src/Rtmp/FlvPlayer.h | 49 ++ src/Rtmp/FlvSplitter.cpp | 130 +++++ src/Rtmp/FlvSplitter.h | 42 ++ src/Rtmp/Rtmp.cpp | 245 ++++++---- src/Rtmp/Rtmp.h | 159 ++++-- src/Rtmp/RtmpDemuxer.cpp | 48 +- src/Rtmp/RtmpDemuxer.h | 1 + src/Rtmp/RtmpMediaSource.h | 42 +- src/Rtmp/RtmpMediaSourceImp.cpp | 124 ++--- src/Rtmp/RtmpMediaSourceImp.h | 2 +- src/Rtmp/RtmpMediaSourceMuxer.h | 9 +- src/Rtmp/RtmpPlayer.cpp | 84 +--- src/Rtmp/RtmpPlayer.h | 6 +- src/Rtmp/RtmpPlayerImp.h | 120 +++-- src/Rtmp/RtmpPusher.cpp | 44 +- src/Rtmp/RtmpPusher.h | 2 +- src/Rtmp/RtmpSession.cpp | 109 +++-- src/Rtmp/RtmpSession.h | 2 +- src/Rtmp/amf.h | 1 + src/Rtp/GB28181Process.cpp | 6 +- src/Rtp/RtpProcess.cpp | 54 +- src/Rtp/RtpSender.cpp | 6 +- src/Rtp/RtpServer.cpp | 4 +- src/Rtp/RtpSession.cpp | 2 +- src/Rtp/RtpSplitter.cpp | 35 +- src/Rtp/RtpSplitter.h | 3 +- src/Rtsp/RtpCodec.cpp | 2 +- src/Rtsp/RtpReceiver.cpp | 2 +- src/Rtsp/Rtsp.cpp | 146 +++--- src/Rtsp/Rtsp.h | 244 +++++----- src/Rtsp/RtspMediaSource.h | 21 +- src/Rtsp/RtspMediaSourceImp.cpp | 15 +- src/Rtsp/RtspMediaSourceImp.h | 3 +- src/Rtsp/RtspMediaSourceMuxer.h | 9 +- src/Rtsp/RtspMuxer.cpp | 9 + src/Rtsp/RtspPlayer.cpp | 393 +++++++-------- src/Rtsp/RtspPlayer.h | 3 +- src/Rtsp/RtspPlayerImp.h | 2 +- src/Rtsp/RtspPusher.cpp | 34 +- src/Rtsp/RtspPusher.h | 2 +- src/Rtsp/RtspSession.cpp | 135 +++-- src/Rtsp/RtspSplitter.cpp | 6 +- src/Rtsp/UDPServer.cpp | 4 +- src/Shell/ShellSession.cpp | 8 +- src/TS/TSMediaSource.h | 11 +- src/TS/TSMediaSourceMuxer.h | 7 +- srt/SrtSession.cpp | 2 +- srt/SrtTransportImp.cpp | 53 +- tests/README.md | 2 +- tests/test_bench_forward.cpp | 267 ++++++++++ tests/test_bench_push.cpp | 2 +- tests/test_flv.cpp | 112 +++++ tests/test_httpApi.cpp | 12 +- tests/test_httpClient.cpp | 12 +- tests/test_pusher.cpp | 5 +- tests/test_pusherMp4.cpp | 2 +- tests/test_server.cpp | 20 +- tests/test_wsClient.cpp | 2 +- tests/test_wsServer.cpp | 2 +- webrtc/IceServer.cpp | 2 +- webrtc/RtpExt.cpp | 18 +- webrtc/RtpExt.h | 3 +- webrtc/Sdp.cpp | 23 +- webrtc/WebRtcPlayer.cpp | 25 +- webrtc/WebRtcPlayer.h | 1 + webrtc/WebRtcPusher.cpp | 36 +- webrtc/WebRtcPusher.h | 13 +- webrtc/WebRtcSession.cpp | 4 +- webrtc/WebRtcSession.h | 4 +- webrtc/WebRtcTransport.cpp | 61 ++- webrtc/WebRtcTransport.h | 7 +- www/webrtc/index.html | 89 ++-- 208 files changed, 5858 insertions(+), 3106 deletions(-) create mode 100644 src/Common/JemallocUtil.cpp create mode 100644 src/Common/JemallocUtil.h rename src/{Http => Common}/strCoding.cpp (100%) rename src/{Http => Common}/strCoding.h (98%) create mode 100644 src/Rtmp/FlvPlayer.cpp create mode 100644 src/Rtmp/FlvPlayer.h create mode 100644 src/Rtmp/FlvSplitter.cpp create mode 100644 src/Rtmp/FlvSplitter.h create mode 100644 tests/test_bench_forward.cpp create mode 100644 tests/test_flv.cpp diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index db67603d..c18493ac 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,6 +1,17 @@ name: Docker -on: [push, pull_request] +on: + push: + branches: + - "master" + - "feature/*" + - "release/*" + + pull_request: + branches: + - "master" + - "feature/*" + - "release/*" env: # Use docker.io for Docker Hub if empty diff --git a/3rdpart/CMakeLists.txt b/3rdpart/CMakeLists.txt index 44e3a551..c90129d3 100644 --- a/3rdpart/CMakeLists.txt +++ b/3rdpart/CMakeLists.txt @@ -24,7 +24,11 @@ ############################################################################## # jsoncpp -aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/src/lib_json JSONCPP_SRC_LIST) +file(GLOB JSONCPP_SRC_LIST + ${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/include/json/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/src/lib_json/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/src/lib_json/*.h) + add_library(jsoncpp STATIC ${JSONCPP_SRC_LIST}) target_compile_options(jsoncpp PRIVATE ${COMPILE_OPTIONS_DEFAULT}) @@ -43,44 +47,44 @@ set(MediaServer_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/media-server") # TODO: 补一个函数处理各种库 # 添加 mov、flv 库用于 MP4 录制 -if(ENABLE_MP4) - message(STATUS "ENABLE_MP4 defined") - +if (ENABLE_MP4 OR ENABLE_HLS_FMP4) # MOV set(MediaServer_MOV_ROOT ${MediaServer_ROOT}/libmov) aux_source_directory(${MediaServer_MOV_ROOT}/include MOV_SRC_LIST) - aux_source_directory(${MediaServer_MOV_ROOT}/source MOV_SRC_LIST) + aux_source_directory(${MediaServer_MOV_ROOT}/source MOV_SRC_LIST) add_library(mov STATIC ${MOV_SRC_LIST}) add_library(MediaServer::mov ALIAS mov) - target_compile_definitions(mov - PUBLIC -DENABLE_MP4) - target_compile_options(mov - PRIVATE ${COMPILE_OPTIONS_DEFAULT}) + target_compile_options(mov PRIVATE ${COMPILE_OPTIONS_DEFAULT}) target_include_directories(mov - PRIVATE - "$" - PUBLIC - "$") + PRIVATE + "$" + PUBLIC + "$") # FLV set(MediaServer_FLV_ROOT ${MediaServer_ROOT}/libflv) aux_source_directory(${MediaServer_FLV_ROOT}/include FLV_SRC_LIST) - aux_source_directory(${MediaServer_FLV_ROOT}/source FLV_SRC_LIST) + aux_source_directory(${MediaServer_FLV_ROOT}/source FLV_SRC_LIST) add_library(flv STATIC ${FLV_SRC_LIST}) add_library(MediaServer::flv ALIAS flv) - target_compile_options(flv - PRIVATE ${COMPILE_OPTIONS_DEFAULT}) + target_compile_options(flv PRIVATE ${COMPILE_OPTIONS_DEFAULT}) target_include_directories(flv - PRIVATE - "$" - PUBLIC - "$") + PRIVATE + "$" + PUBLIC + "$") - update_cached_list(MK_LINK_LIBRARIES - MediaServer::flv MediaServer::mov) - update_cached_list(MK_COMPILE_DEFINITIONS - ENABLE_MP4) -endif() + update_cached_list(MK_LINK_LIBRARIES MediaServer::flv MediaServer::mov) + + if (ENABLE_MP4) + message(STATUS "ENABLE_MP4 defined") + update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_MP4) + endif () + if (ENABLE_HLS_FMP4) + message(STATUS "ENABLE_HLS_FMP4 defined") + update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS_FMP4) + endif () +endif () # 添加 mpeg 用于支持 ts 生成 if(ENABLE_RTPPROXY OR ENABLE_HLS) @@ -104,9 +108,11 @@ if(ENABLE_RTPPROXY OR ENABLE_HLS) update_cached_list(MK_LINK_LIBRARIES MediaServer::mpeg) if(ENABLE_RTPPROXY) + message(STATUS "ENABLE_RTPPROXY defined") update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_RTPPROXY) endif() if(ENABLE_HLS) + message(STATUS "ENABLE_HLS defined") update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS) endif() endif() diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index 7e40c751..b11582c3 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit 7e40c751659d5c1ec623699732284c12e0a4feb8 +Subproject commit b11582c38e8dbbb8d93ca9ce33c9a0b0cd58f59a diff --git a/AUTHORS b/AUTHORS index 77f7ea9f..57928af2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -70,4 +70,18 @@ WuPeng [ahaooahaz](https://github.com/AHAOAHA) [TempoTian](https://github.com/TempoTian) [Derek Liu](https://github.com/yjkhtddx) -[ljx0305](https://github.com/ljx0305) \ No newline at end of file +[ljx0305](https://github.com/ljx0305) +[朱如洪 ](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) \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b110377..39c6c517 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,8 +39,10 @@ option(ENABLE_FAAC "Enable FAAC" OFF) option(ENABLE_FFMPEG "Enable FFmpeg" OFF) option(ENABLE_HLS "Enable HLS" ON) option(ENABLE_JEMALLOC_STATIC "Enable static linking to the jemalloc library" OFF) +option(ENABLE_JEMALLOC_DUMP "Enable jemalloc to dump malloc statistics" OFF) option(ENABLE_MEM_DEBUG "Enable Memory Debug" OFF) option(ENABLE_MP4 "Enable MP4" ON) +option(ENABLE_HLS_FMP4 "Enable HLS-FMP4" ON) option(ENABLE_MSVC_MT "Enable MSVC Mt/Mtd lib" ON) option(ENABLE_MYSQL "Enable MySQL" OFF) option(ENABLE_OPENSSL "Enable OpenSSL" ON) @@ -200,8 +202,8 @@ if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") endif() # mediakit 以及各个 runtime 依赖 -update_cached_list(MK_LINK_LIBRARIES "") -update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_VERSION) +update_cached(MK_LINK_LIBRARIES "") +update_cached(MK_COMPILE_DEFINITIONS ENABLE_VERSION) if (DISABLE_REPORT) update_cached_list(MK_COMPILE_DEFINITIONS DISABLE_REPORT) @@ -334,7 +336,11 @@ if(ENABLE_JEMALLOC_STATIC) if(NOT EXISTS ${DEP_ROOT_DIR}) file(MAKE_DIRECTORY ${DEP_ROOT_DIR}) endif() - + if (ENABLE_JEMALLOC_DUMP) + set(ENABLE_JEMALLOC_STAT ON) + else () + set(ENABLE_JEMALLOC_STAT OFF) + endif () include(Jemalloc) include_directories(SYSTEM ${DEP_ROOT_DIR}/${JEMALLOC_NAME}/include/jemalloc) link_directories(${DEP_ROOT_DIR}/${JEMALLOC_NAME}/lib) @@ -348,6 +354,12 @@ if(JEMALLOC_FOUND) message(STATUS "found library: ${JEMALLOC_LIBRARIES}") include_directories(${JEMALLOC_INCLUDE_DIR}) update_cached_list(MK_LINK_LIBRARIES ${JEMALLOC_LIBRARIES}) + add_definitions(-DUSE_JEMALLOC) + message(STATUS "jemalloc will be used to avoid memory fragmentation") + if (ENABLE_JEMALLOC_DUMP) + add_definitions(-DENABLE_JEMALLOC_DUMP) + message(STATUS "jemalloc will save memory usage statistics when the program exits") + endif () endif() # 查找 openssl 是否安装 @@ -449,11 +461,6 @@ if(ENABLE_API) add_subdirectory(api) endif() -# IOS 不编译可执行程序 -if(IOS) - return() -endif() - ############################################################################## if(ENABLE_PLAYER AND ENABLE_FFMPEG) @@ -461,13 +468,20 @@ if(ENABLE_PLAYER AND ENABLE_FFMPEG) endif() #MediaServer主程序 -add_subdirectory(server) +if(ENABLE_SERVER) + add_subdirectory(server) +endif() # Android 会 add_subdirectory 并依赖该变量 -if(ENABLE_SERVER_LIB) +if(ENABLE_SERVER_LIB AND NOT CMAKE_PARENT_LIST_FILE STREQUAL CMAKE_CURRENT_LIST_FILE) set(MK_LINK_LIBRARIES ${MK_LINK_LIBRARIES} PARENT_SCOPE) endif() +# IOS 不编译可执行程序 +if(IOS) + return() +endif() + #cpp测试demo程序 if (ENABLE_TESTS) add_subdirectory(tests) diff --git a/README.md b/README.md index a7a13837..97fb5751 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ ![logo](https://raw.githubusercontent.com/ZLMediaKit/ZLMediaKit/master/www/logo.png) +简体中文 | [English](./README_en.md) + # 一个基于C++11的高性能运营级流媒体服务框架 [![](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/ZLMediaKit/ZLMediaKit/blob/master/LICENSE) @@ -44,7 +46,7 @@ ## 功能清单 ### 功能一览 -功能一览 +功能一览 - RTSP[S] - RTSP[S] 服务器,支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备 @@ -61,14 +63,16 @@ - RTMP[S] 发布服务器,支持录制发布流 - RTMP[S] 播放器,支持RTMP代理,支持生成静音音频 - RTMP[S] 推流客户端 - - 支持http[s]-flv直播 + - 支持http[s]-flv直播服务器 + - 支持http[s]-flv直播播放器 - 支持websocket-flv直播 - 支持H264/H265/AAC/G711/OPUS编码,其他编码能转发但不能转协议 - 支持[RTMP-H265](https://github.com/ksvc/FFmpeg/wiki) - 支持[RTMP-OPUS](https://github.com/ZLMediaKit/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81) + - 支持[enhanced-rtmp(H265)](https://github.com/veovera/enhanced-rtmp) - HLS - - 支持HLS文件生成,自带HTTP文件服务器 + - 支持HLS文件(mpegts/fmp4)生成,自带HTTP文件服务器 - 通过cookie追踪技术,可以模拟HLS播放为长连接,可以实现HLS按需拉流、播放统计等业务 - 支持HLS播发器,支持拉流HLS转rtsp/rtmp/mp4 - 支持H264/H265/AAC/G711/OPUS编码 @@ -99,6 +103,7 @@ - 支持es/ps/ts/ehome rtp推流 - 支持es/ps rtp转推 - 支持GB28181主动拉流模式 + - 支持双向语音对讲 - MP4点播与录制 - 支持录制为FLV/HLS/MP4 @@ -119,6 +124,7 @@ - 支持datachannel - 支持webrtc over tcp模式 - 优秀的nack、jitter buffer算法, 抗丢包能力卓越 + - 支持whip/whep协议 - [SRT支持](./srt/srt.md) - 其他 - 支持丰富的restful api以及web hook事件 @@ -163,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) ## 授权协议 @@ -198,9 +210,12 @@ bash build_docker_images.sh ## 联系方式 - 邮箱:<1213642868@qq.com>(本项目相关或流媒体相关问题请走issue流程,否则恕不邮件答复) - - QQ群:两个qq群已满员(共4000人),后续将不再新建qq群,用户可加入[知识星球](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364)提问以支持本项目。 - - 关注微信公众号: + - 请关注微信公众号获取最新消息推送: + + - 也可以自愿有偿加入知识星球咨询和获取资料: + + ## 怎么提问? @@ -208,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). ## 特别感谢 @@ -302,6 +315,24 @@ bash build_docker_images.sh [TempoTian](https://github.com/TempoTian) [Derek Liu](https://github.com/yjkhtddx) [ljx0305](https://github.com/ljx0305) +[朱如洪 ](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 dd031c10..5eee72d1 100644 --- a/README_en.md +++ b/README_en.md @@ -1,138 +1,206 @@ ![logo](https://raw.githubusercontent.com/zlmediakit/ZLMediaKit/master/www/logo.png) -# A lightweight ,high performance and stable stream server and client framework based on C++11. +[简体中文](./README.md) | English + +# An high-performance, enterprise-level streaming media service framework based on C++11. -[![license](http://img.shields.io/badge/license-MIT-green.svg)](https://github.com/xia-chu/ZLMediaKit/blob/master/LICENSE) -[![C++](https://img.shields.io/badge/language-c++-red.svg)](https://en.cppreference.com/) -[![platform](https://img.shields.io/badge/platform-linux%20|%20macos%20|%20windows-blue.svg)](https://github.com/xia-chu/ZLMediaKit) -[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-yellow.svg)](https://github.com/xia-chu/ZLMediaKit/pulls) -[![Build Status](https://travis-ci.org/xia-chu/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xia-chu/ZLMediaKit) +[![](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/ZLMediaKit/ZLMediaKit/blob/master/LICENSE) +[![](https://img.shields.io/badge/language-c++-red.svg)](https://en.cppreference.com/) +[![](https://img.shields.io/badge/platform-linux%20|%20macos%20|%20windows-blue.svg)](https://github.com/ZLMediaKit/ZLMediaKit) +[![](https://img.shields.io/badge/PRs-welcome-yellow.svg)](https://github.com/ZLMediaKit/ZLMediaKit/pulls) -## Why ZLMediaKit? -- Developed based on C++ 11, the code is stable and reliable, avoiding the use of raw pointers, cross-platform porting is simple and convenient, and the code is clear and concise. -- Support rich streaming media protocols(`RTSP/RTMP/HLS/HTTP-FLV/WebSocket-flv/HTTP-TS/WebSocket-TS/HTTP-fMP4/Websocket-fMP4/MP4/WebRTC`),and support Inter-protocol conversion. -- Multiplexing asynchronous network IO based on epoll and multi thread,extreme performance. -- Well performance and stable test,can be used commercially. -- Support linux, macos, ios, android, Windows Platforms. -- Very low latency(lower then one second), video opened immediately. +[![](https://github.com/ZLMediaKit/ZLMediaKit/actions/workflows/android.yml/badge.svg)](https://github.com/ZLMediaKit/ZLMediaKit) +[![](https://github.com/ZLMediaKit/ZLMediaKit/actions/workflows/linux.yml/badge.svg)](https://github.com/ZLMediaKit/ZLMediaKit) +[![](https://github.com/ZLMediaKit/ZLMediaKit/actions/workflows/macos.yml/badge.svg)](https://github.com/ZLMediaKit/ZLMediaKit) +[![](https://github.com/ZLMediaKit/ZLMediaKit/actions/workflows/windows.yml/badge.svg)](https://github.com/ZLMediaKit/ZLMediaKit) -## Features +[![](https://github.com/ZLMediaKit/ZLMediaKit/actions/workflows/docker.yml/badge.svg)](https://hub.docker.com/r/zlmediakit/zlmediakit/tags) +[![](https://img.shields.io/docker/pulls/zlmediakit/zlmediakit)](https://hub.docker.com/r/zlmediakit/zlmediakit/tags) + +## Project Features +- Developed with C++11, avoiding the use of raw pointers, providing stable and reliable code with superior performance. +- Supports multiple protocols (RTSP/RTMP/HLS/HTTP-FLV/WebSocket-FLV/GB28181/HTTP-TS/WebSocket-TS/HTTP-fMP4/WebSocket-fMP4/MP4/WebRTC), and protocol conversion. +- Developed with multiplexing/multithreading/asynchronous network IO models, providing excellent concurrency performance and supporting massive client connections. +- The code has undergone extensive stability and performance testing, and has been extensively used in production environments. +- Supports all major platforms, including linux, macos, ios, android, and windows. +- Supports multiple instruction set platforms, such as x86, arm, risc-v, mips, Loongson, and Shenwei. +- Provides ultra-fast startup, extremely low latency (within 500 milliseconds, and can be as low as 100 milliseconds), and excellent user experience. +- Provides a comprehensive standard [C API](https://github.com/ZLMediaKit/ZLMediaKit/tree/master/api/include) that can be used as an SDK or called by other languages. +- Provides a complete [MediaServer](https://github.com/ZLMediaKit/ZLMediaKit/tree/master/server) server, which can be deployed directly as a commercial server without additional development. +- Provides a complete [restful api](https://github.com/ZLMediaKit/ZLMediaKit/wiki/MediaServer%E6%94%AF%E6%8C%81%E7%9A%84HTTP-API) and [web hook](https://github.com/ZLMediaKit/ZLMediaKit/wiki/MediaServer%E6%94%AF%E6%8C%81%E7%9A%84HTTP-HOOK-API), supporting rich business logic. +- Bridges the video surveillance protocol stack and the live streaming protocol stack, and provides comprehensive support for RTSP/RTMP. +- Fully supports H265/H264/AAC/G711/OPUS. +- Provides complete functions, including clustering, on-demand protocol conversion, on-demand push/pull streams, playback before publishing, and continuous publishing after disconnection. +- Provides ultimate performance, supporting 10W-level players on a single machine and 100Gb/s-level IO bandwidth capability. +- Provides ultimate user experience with [exclusive features](https://github.com/ZLMediaKit/ZLMediaKit/wiki/ZLMediakit%E7%8B%AC%E5%AE%B6%E7%89%B9%E6%80%A7%E4%BB%8B%E7%BB%8D). +- [Who is using zlmediakit?](https://github.com/ZLMediaKit/ZLMediaKit/issues/511) +- Fully supports IPv6 networks. + +## Project Positioning + +- Cross-platform streaming media solution for mobile and embedded systems. +- Commercial-grade streaming media server. +- Network programming secondary development SDK. + +## Feature List +### Overview of Features +Overview of Features - RTSP[S] - - RTSP[S] server,support rtsp push. - - RTSP[S] player and pusher. - - RTP Transport : `rtp over udp` `rtp over tcp` `rtp over http` `rtp udp multicast` . - - Basic/Digest/Url Authentication. - - H265/H264/AAC/G711/OPUS codec. - - Recorded as mp4. - - Vod of mp4. - + - RTSP[S] server, supports RTMP/MP4/HLS to RTSP[S] conversion, supports devices such as Amazon Echo Show + - RTSP[S] player, supports RTSP proxy, supports generating silent audio + - RTSP[S] push client and server + - Supports four RTP transmission modes: `rtp over udp` `rtp over tcp` `rtp over http` `rtp multicast` + - Server/client fully supports Basic/Digest authentication, asynchronous configurable authentication interface + - Supports H265 encoding + - The server supports RTSP pushing (including `rtp over udp` and `rtp over tcp`) + - Supports H264/H265/AAC/G711/OPUS/MJPEG encoding. Other encodings can be forwarded but cannot be converted to protocol + - RTMP[S] - - RTMP[S] server,support player and pusher. - - RTMP[S] player and pusher. - - Support HTTP-FLV/WebSocket-FLV sever. - - H265/H264/AAC/G711/OPUS codec. - - Recorded as flv or mp4. - - Vod of mp4. - - support [RTMP-H265](https://github.com/ksvc/FFmpeg/wiki) - + - RTMP[S] playback server, supports RTSP/MP4/HLS to RTMP conversion + - RTMP[S] publishing server, supports recording and publishing streams + - RTMP[S] player, supports RTMP proxy, supports generating silent audio + - RTMP[S] push client + - Supports http[s]-flv live streaming server + - Supports http[s]-flv live streaming player + - Supports websocket-flv live streaming + - Supports H264/H265/AAC/G711/OPUS encoding. Other encodings can be forwarded but cannot be converted to protocol + - Supports [RTMP-H265](https://github.com/ksvc/FFmpeg/wiki) + - Supports [RTMP-OPUS](https://github.com/ZLMediaKit/ZLMediaKit/wiki/RTMP%E5%AF%B9H265%E5%92%8COPUS%E7%9A%84%E6%94%AF%E6%8C%81) + - Supports [enhanced-rtmp(H265)](https://github.com/veovera/enhanced-rtmp) + - HLS - - RTSP RTMP can be converted into HLS,built-in HTTP server. - - Play authentication based on cookie. - - Support HLS player, support streaming HLS proxy to RTSP / RTMP / MP4. - + - 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 + - TS - - Support HTTP-TS/WebSocket-TS sever. - + - Supports http[s]-ts live streaming + - Supports ws[s]-ts live streaming + - Supports H264/H265/AAC/G711/OPUS encoding + - fMP4 - - Support HTTP-fMP4/WebSocket-fMP4 sever. + - Supports http[s]-fmp4 live streaming + - Supports ws[s]-fmp4 live streaming + - Supports H264/H265/AAC/G711/OPUS/MJPEG encoding -- HTTP[S] - - HTTP server,suppor directory meun、RESTful http api. - - HTTP client,downloader,uploader,and http api requester. - - Cookie supported. - - WebSocket Server and Client. - - File access authentication. - -- WebRTC(experiential) - - Support webrtc push stream and transfer to other protocols - - Support webrtc play, support other protocol to webrtc - - Support simulcast - - Support rtx/nack - - Support transport-cc rtcp/rtp ext -- [SRT support](./srt/srt_en.md) +- HTTP[S] and WebSocket + - The server supports `directory index generation`, `file download`, `form submission requests` + - The client provides `file downloader (supports resume breakpoint)`, `interface requestor`, `file uploader` + - Complete HTTP API server, which can be used as a web backend development framework + - Supports cross-domain access + - Supports http client/server cookie + - Supports WebSocket server and client + - Supports http file access authentication + +- GB28181 and RTP Streaming + - Supports UDP/TCP RTP (PS/TS/ES) streaming server, which can be converted to RTSP/RTMP/HLS and other protocols + - Supports RTSP/RTMP/HLS and other protocol conversion to RTP streaming client, supports TCP/UDP mode, provides corresponding RESTful API, supports active and passive modes + - Supports H264/H265/AAC/G711/OPUS encoding + - Supports ES/PS/TS/EHOME RTP streaming + - Supports ES/PS RTP forwarding + - Supports GB28181 active pull mode + - Supports two-way voice intercom + +- MP4 VOD and Recording + - Supports recording as FLV/HLS/MP4 + - Supports MP4 file playback for RTSP/RTMP/HTTP-FLV/WS-FLV, supports seek + - Supports H264/H265/AAC/G711/OPUS encoding + +- WebRTC + - Supports WebRTC streaming and conversion to other protocols + - Supports WebRTC playback and conversion from other protocols to WebRTC + - Supports two-way echo testing + - Supports simulcast streaming + - Supports uplink and downlink RTX/NACK packet loss retransmission + - **Supports single-port, multi-threaded, and client network connection migration (unique in the open source community)**. + - Supports TWCC RTCP dynamic rate control + - Supports REMB/PLI/SR/RR RTCP + - Supports RTP extension parsing + - Supports GOP buffer and instant WebRTC playback + - Supports data channels + - Supports WebRTC over TCP mode + - Excellent NACK and jitter buffer algorithms with outstanding packet loss resistance + - Supports WHIP/WHEP protocols +- [SRT support](./srt/srt.md) - Others - - Support stream proxy by ffmpeg. - - RESTful http api and http hook event api. - - Config file hot loading. - - Vhost supported. - - Auto close stream when nobody played. - - Play and push authentication. - - Pull stream on Demand. - - Support TS / PS streaming push through RTP,and it can be converted to RTSP / RTMP / HLS / FLV. - - Support real-time online screenshot http api. - + - Supports rich RESTful APIs and webhook events + - Supports simple Telnet debugging + - Supports hot reloading of configuration files + - Supports traffic statistics, stream authentication, and other events + - Supports virtual hosts for isolating different domain names + - Supports on-demand streaming and automatic shutdown of streams with no viewers + - Supports pre-play before streaming to increase the rate of timely stream openings + - Provides a complete and powerful C API SDK + - Supports FFmpeg stream proxy for any format + - Supports HTTP API for real-time screenshot generation and return + - Supports on-demand demultiplexing and protocol conversion, reducing CPU usage by only enabling it when someone is watching + - Supports cluster deployment in traceable mode, with RTSP/RTMP/HLS/HTTP-TS support for traceable mode and HLS support for edge stations and multiple sources for source stations (using round-robin tracing) + - Can reconnect to streaming after abnormal disconnection in RTSP/RTMP/WebRTC pushing within a timeout period, with no impact on the player. + ## System Requirements -- Compiler support c++11,GCC4.8/Clang3.3/VC2015 or above. -- cmake3.1 or above. -- All Linux , both 32 and 64 bits -- Apple OSX(Darwin), both 32 and 64bits. -- All hardware with x86/x86_64/arm/mips cpu. +- Compiler with c++11 support, such as GCC 4.8+, Clang 3.3+, or VC2015+. +- CMake 3.1+. +- Linux (32-bit and 64-bit). +- Apple macOS (32-bit and 64-bit). +- Any hardware with x86, x86_64, ARM, or MIPS CPU. - Windows. ## How to build -It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumbersome, and some features are not compiled by default. +It is recommended to compile on Ubuntu or macOS. Compiling on Windows is cumbersome, and some features are not compiled by default. -### Before build -- **You must use git to clone the complete code. Do not download the source code by downloading zip package. Otherwise, the sub-module code will not be downloaded by default.You can do it like this:** +### Before Building + +- **You must use Git to clone the complete code. Do not download the source code by downloading the ZIP package. Otherwise, the submodule code will not be downloaded by default. You can do it like this:** ``` git clone https://github.com/xia-chu/ZLMediaKit.git cd ZLMediaKit git submodule update --init ``` -### Build on linux +### Building on Linux -- My environment - - Ubuntu16.04 64 bit and gcc5.4 - - cmake 3.5.1 +- My Environment + - Ubuntu 16.04 (64-bit) with GCC 5.4. + - CMake 3.5.1. - Guidance ``` - # If it is on centos6.x, you need to install the newer version of GCC and cmake first, - # and then compile manually according to the script "build_for_linux.sh". - # If it is on a newer version of a system such as Ubuntu or Debain, + # If it is on CentOS 6.x, you need to install a newer version of GCC and CMake first, + # and then compile manually according to the "build_for_linux.sh" script. + # If it is on a newer version of a system such as Ubuntu or Debian, # step 4 can be manipulated directly. - # 1、Install GCC5.2 (this step can be skipped if the GCC version is higher than 4.7) + # 1. Install GCC 5.2 (this step can be skipped if the GCC version is higher than 4.7). sudo yum install centos-release-scl -y sudo yum install devtoolset-4-toolchain -y scl enable devtoolset-4 bash - # 2、Install cmake (this step can be skipped if the cmake version is higher than 3.1) - tar -xvf cmake-3.10.0-rc4.tar.gz #you need download cmake source file manually + # 2. Install CMake (this step can be skipped if the CMake version is higher than 3.1). + tar -xvf cmake-3.10.0-rc4.tar.gz #you need to download the CMake source file manually cd cmake-3.10.0-rc4 ./configure make -j4 sudo make install - # 3、Switch to high version GCC + # 3. Switch to a higher version of GCC. scl enable devtoolset-4 bash - # 4、build + # 4. Build. cd ZLMediaKit ./build_for_linux.sh ``` -### Build on macOS +### Building on macOS -- My environment - - macOS Sierra(10.12.1) + xcode8.3.1 - - Homebrew 1.1.3 - - cmake 3.8.0 +- My Environment + - macOS Sierra (10.12.1) with Xcode 8.3.1. + - Homebrew 1.1.3. + - CMake 3.8.0. - Guidance ``` @@ -140,7 +208,7 @@ git submodule update --init ./build_for_mac.sh ``` -### Build on iOS +### Building on iOS - You can generate Xcode projects and recompile them , [learn more](https://github.com/leetal/ios-cmake): ``` @@ -152,15 +220,16 @@ git submodule update --init ``` -### Build on Android +### Building on Android - Now you can open android sudio project in `Android` folder,this is a `aar library` and damo project. + Now you can open the Android Studio project in the `Android` folder. This is an `AAR` library and demo project. - My environment - - macOS Sierra(10.12.1) + xcode8.3.1 + - macOS Sierra (10.12.1) + Xcode 8.3.1 - Homebrew 1.1.3 - - cmake 3.8.0 - - [android-ndk-r14b](https://dl.google.com/android/repository/android-ndk-r14b-darwin-x86_64.zip) + - CMake 3.8.0 + - [Android NDK r14b](https://dl.google.com/android/repository/android-ndk-r14b-darwin-x86_64.zip) + - Guidance ``` @@ -168,23 +237,25 @@ git submodule update --init export ANDROID_NDK_ROOT=/path/to/ndk ./build_for_android.sh ``` -### Build on Windows + +### Building on Windows - My environment - - windows 10 - - visual studio 2017 - - [cmake-gui](https://cmake.org/files/v3.10/cmake-3.10.0-rc1-win32-x86.msi) + - Windows 10 + - Visual Studio 2017 + - [CMake GUI](https://cmake.org/files/v3.10/cmake-3.10.0-rc1-win32-x86.msi) - Guidance ``` -1 Enter the ZLMediaKit directory and execute git submodule update -- init downloads the code for ZLToolKit -2 Open the project with cmake-gui and generate the vs project file. -3 Find the project file (ZLMediaKit.sln), double-click to open it with vs2017. -4 Choose to compile Release version. Find the target file and run the test case. +1. Enter the ZLMediaKit directory and execute `git submodule update --init` to download the code for ZLToolKit. +2. Open the project with CMake GUI and generate the Visual Studio project file. +3. Find the project file (ZLMediaKit.sln), double-click to open it with VS2017. +4. Choose to compile the Release version. Find the target file and run the test cases. ``` + ## Usage -- As server: +- As a server: ```cpp TcpServer::Ptr rtspSrv(new TcpServer()); TcpServer::Ptr rtmpSrv(new TcpServer()); @@ -197,7 +268,7 @@ git submodule update --init httpsSrv->start(mINI::Instance()[Config::Http::kSSLPort]); ``` -- As player: +- As a player: ```cpp MediaPlayer::Ptr player(new MediaPlayer()); weak_ptr weakPlayer = player; @@ -210,7 +281,7 @@ git submodule update --init auto viedoTrack = strongPlayer->getTrack(TrackVideo); if (!viedoTrack) { - WarnL << "none video Track!"; + WarnL << "No video Track!"; return; } viedoTrack->addDelegate([](const Frame::Ptr &frame) { @@ -222,14 +293,13 @@ git submodule update --init ErrorL << "OnShutdown:" << ex.what(); }); - //rtp transport over tcp + //RTP transport over TCP (*player)[Client::kRtpType] = Rtsp::RTP_TCP; player->play("rtsp://admin:jzan123456@192.168.0.122/"); ``` -- As proxy server: +- As a proxy server: ```cpp - //support rtmp and rtsp url - //just support H264+AAC + //Support RTMP and RTSP URLs, but only H264 + AAC codec is supported auto urlList = {"rtmp://live.hkstv.hk.lxdns.com/live/hks", "rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov"}; map proxyMap; @@ -241,7 +311,7 @@ git submodule update --init } ``` -- As puser: +- As a pusher: ```cpp PlayerProxy::Ptr player(new PlayerProxy("app","stream")); player->play("rtmp://live.hkstv.hk.lxdns.com/live/hks"); @@ -255,19 +325,179 @@ git submodule update --init ``` ## Docker Image -You can pull a pre-built docker image from Docker Hub and run with + +You can download the pre-compiled image from Docker Hub and start it: + ```bash +#This image is pushed by the GitHub continuous integration automatic compilation to keep up with the latest code (master branch) docker run -id -p 1935:1935 -p 8080:80 -p 8443:443 -p 8554:554 -p 10000:10000 -p 10000:10000/udp -p 8000:8000/udp -p 9000:9000/udp zlmediakit/zlmediakit:master ``` -Dockerfile is also supplied to build images on Ubuntu 16.04 +You can also compile the image based on the Dockerfile: + ```bash -cd docker -docker build -t zlmediakit . +bash build_docker_images.sh ``` -## Contact - - Email:<1213642868@qq.com> - - QQ chat group:542509000 +## Collaborative Projects +- Visual management website + - [The latest web project with front-end and back-end separation, supporting webrtc playback](https://github.com/langmansh/AKStreamNVR) + - [Management web site based on ZLMediaKit master branch](https://gitee.com/kkkkk5G/MediaServerUI) + - [Management web site based on ZLMediaKit branch](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI) + - [A very beautiful visual background management system](https://github.com/MingZhuLiu/ZLMediaServerManagent) + +- 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) +- Client + - [Complete C# wrapper library for c sdk](https://github.com/malegend/ZLMediaKit.Autogen) + - [Push client implemented based on C SDK](https://github.com/hctym1995/ZLM_ApiDemo) + - [Http API and Hook in C#](https://github.com/chengxiaosheng/ZLMediaKit.HttpApi) + - [RESTful client in DotNetCore](https://github.com/MingZhuLiu/ZLMediaKit.DotNetCore.Sdk) + +- Player + - [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 + +The self-owned code of this project is licensed under the permissive MIT License and can be freely applied to commercial and non-commercial projects while retaining copyright information. +However, this project also uses some scattered [open source code](https://github.com/ZLMediaKit/ZLMediaKit/wiki/%E4%BB%A3%E7%A0%81%E4%BE%9D%E8%B5%96%E4%B8%8E%E7%89%88%E6%9D%83%E5%A3%B0%E6%98%8E) , please replace or remove it for commercial use. +Any commercial disputes or infringement caused by using this project have nothing to do with the project and developers and shall be at your own legal risk. +When using the code of this project, the license agreement should also indicate the license of the third-party libraries that this project depends on. + +## Contact Information + +- Email: <1213642868@qq.com> (For project-related or streaming media-related questions, please follow the issue process. Otherwise, we will not reply to emails.) +- QQ groups: Both QQ groups with a total of 4000 members are full. We will not create new QQ groups in the future. Users can join the [Knowledge Planet](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364) to ask questions and support this project. +- Follow WeChat Official Account: + + +## How to Ask Questions? + +If you have any questions about the project, we recommend that you: + +- 1. Carefully read the readme and wiki. If necessary, you can also check the issues. +- 2. If your question has not been resolved, you can raise an issue. +- 3. Some questions may not be suitable for issues, but can be raised in QQ groups. +- 4. We generally do not accept free technical consulting and support via QQ private chat. ([Why we don't encourage QQ private chat](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. If you need more timely and thoughtful technical support, you can join the [Knowledge Planet](https://github.com/ZLMediaKit/ZLMediaKit/issues/2364) for a fee. + +## Special Thanks + +This project uses the [media-server](https://github.com/ireader/media-server) library developed by [Lao Chen](https://github.com/ireader). The reuse and de-multiplexing of ts/fmp4/mp4/ps container formats in this project depend on the media-server library. Lao Chen has provided invaluable help and support multiple times in implementing many functions of this project, and we would like to express our sincere gratitude to him! + +## Acknowledgments + +Thanks to all those who have supported this project in various ways, including but not limited to code contributions, problem feedback, and donations. The following list is not in any particular order: + +[老陈](https://github.com/ireader) +[Gemfield](https://github.com/gemfield) +[南冠彤](https://github.com/nanguantong2) +[凹凸慢](https://github.com/tsingeye) +[chenxiaolei](https://github.com/chenxiaolei) +[史前小虫](https://github.com/zqsong) +[清涩绿茶](https://github.com/baiyfcu) +[3503207480](https://github.com/3503207480) +[DroidChow](https://github.com/DroidChow) +[阿塞](https://github.com/HuoQiShuai) +[火宣](https://github.com/ChinaCCF) +[γ瑞γミ](https://github.com/JerryLinGd) +[linkingvision](https://www.linkingvision.com/) +[茄子](https://github.com/taotaobujue2008) +[好心情](mailto:409257224@qq.com) +[浮沉](https://github.com/MingZhuLiu) +[Xiaofeng Wang](https://github.com/wasphin) +[doodoocoder](https://github.com/doodoocoder) +[qingci](https://github.com/Colibrow) +[swwheihei](https://github.com/swwheihei) +[KKKKK5G](https://gitee.com/kkkkk5G) +[Zhou Weimin](mailto:zhouweimin@supremind.com) +[Jim Jin](https://github.com/jim-king-2000) +[西瓜丶](mailto:392293307@qq.com) +[MingZhuLiu](https://github.com/MingZhuLiu) +[chengxiaosheng](https://github.com/chengxiaosheng) +[big panda](mailto:2381267071@qq.com) +[tanningzhong](https://github.com/tanningzhong) +[hctym1995](https://github.com/hctym1995) +[hewenyuan](https://gitee.com/kingyuanyuan) +[sunhui](mailto:sunhui200475@163.com) +[mirs](mailto:fangpengcheng@bilibili.com) +[Kevin Cheng](mailto:kevin__cheng@outlook.com) +[Liu Jiang](mailto:root@oopy.org) +[along](https://github.com/alongl) +[qingci](mailto:xpy66swsry@gmail.com) +[lyg1949](mailto:zh.ghlong@qq.com) +[zhlong](mailto:zh.ghlong@qq.com) +[大裤衩](mailto:3503207480@qq.com) +[droid.chow](mailto:droid.chow@gmail.com) +[陈晓林](https://github.com/musicwood) +[CharleyWangHZ](https://github.com/CharleyWangHZ) +[Johnny](https://github.com/johzzy) +[DoubleX69](https://github.com/DoubleX69) +[lawrencehj](https://github.com/lawrencehj) +[yangkun](mailto:xyyangkun@163.com) +[Xinghua Zhao](mailto:holychaossword@hotmail.com) +[hejilin](https://github.com/brokensword2018) +[rqb500](https://github.com/rqb500) +[Alex](https://github.com/alexliyu7352) +[Dw9](https://github.com/Dw9) +[明月惊鹊](mailto:mingyuejingque@gmail.com) +[cgm](mailto:2958580318@qq.com) +[hejilin](mailto:1724010622@qq.com) +[alexliyu7352](mailto:liyu7352@gmail.com) +[cgm](mailto:2958580318@qq.com) +[haorui wang](https://github.com/HaoruiWang) +[joshuafc](mailto:joshuafc@foxmail.com) +[JayChen0519](https://github.com/JayChen0519) +[zx](mailto:zuoxue@qq.com) +[wangcker](mailto:wangcker@163.com) +[WuPeng](mailto:wp@zafu.edu.cn) +[starry](https://github.com/starry) +[mtdxc](https://github.com/mtdxc) +[胡刚风](https://github.com/hugangfeng333) +[zhao85](https://github.com/zhao85) +[dreamisdream](https://github.com/dreamisdream) +[dingcan](https://github.com/dcan123) +[Haibo Chen](https://github.com/duiniuluantanqin) +[Leon](https://gitee.com/leon14631) +[custompal](https://github.com/custompal) +[PioLing](https://github.com/PioLing) +[KevinZang](https://github.com/ZSC714725) +[gongluck](https://github.com/gongluck) +[a-ucontrol](https://github.com/a-ucontrol) +[TalusL](https://github.com/TalusL) +[ahaooahaz](https://github.com/AHAOAHA) +[TempoTian](https://github.com/TempoTian) +[Derek Liu](https://github.com/yjkhtddx) +[ljx0305](https://github.com/ljx0305) +[朱如洪 ](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 + +This project has gained recognition from many companies and individual developers. According to the author's incomplete statistics, companies using this project include well-known Internet giants, leading cloud service companies in China, several well-known AI unicorn companies, as well as a series of small and medium-sized companies. Users can endorse this project by pasting their company name and relevant project information on the [issue page](https://github.com/ZLMediaKit/ZLMediaKit/issues/511). Thank you for your support! diff --git a/api/CMakeLists.txt b/api/CMakeLists.txt index c7962a0f..102a03a6 100644 --- a/api/CMakeLists.txt +++ b/api/CMakeLists.txt @@ -30,13 +30,6 @@ file(GLOB API_SRC_LIST set(LINK_LIBRARIES ${MK_LINK_LIBRARIES}) -if(IOS) - add_library(mk_api STATIC ${API_SRC_LIST}) - target_link_libraries(mk_api - PRIVATE ${LINK_LIBRARIES}) - return() -endif () - set(COMPILE_DEFINITIONS ${MK_COMPILE_DEFINITIONS}) if (MSVC) @@ -46,6 +39,8 @@ endif () if(ENABLE_API_STATIC_LIB) add_library(mk_api STATIC ${API_SRC_LIST}) list(APPEND COMPILE_DEFINITIONS MediaKitApi_STATIC) +elseif(IOS) + add_library(mk_api STATIC ${API_SRC_LIST}) else() add_library(mk_api SHARED ${API_SRC_LIST}) endif() @@ -74,8 +69,6 @@ generate_export_header(mk_api STATIC_DEFINE MediaKitApi_STATIC EXPORT_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/mk_export.h") -add_subdirectory(tests) - file(GLOB API_HEADER_LIST include/*.h ${CMAKE_CURRENT_BINARY_DIR}/*.h) install(FILES ${API_HEADER_LIST} DESTINATION ${INSTALL_PATH_INCLUDE}) @@ -83,3 +76,12 @@ install(TARGETS mk_api ARCHIVE DESTINATION ${INSTALL_PATH_LIB} LIBRARY DESTINATION ${INSTALL_PATH_LIB} RUNTIME DESTINATION ${INSTALL_PATH_RUNTIME}) + +# IOS 跳过测试代码 +if(IOS) + return() +endif() + +if (ENABLE_TESTS) + add_subdirectory(tests) +endif() diff --git a/api/include/mk_events.h b/api/include/mk_events.h index c628a0ec..0db68a5f 100644 --- a/api/include/mk_events.h +++ b/api/include/mk_events.h @@ -166,6 +166,17 @@ typedef struct { */ void (API_CALL *on_mk_log)(int level, const char *file, int line, const char *function, const char *message); + /** + * 发送rtp流失败回调,适用于mk_media_source_start_send_rtp/mk_media_start_send_rtp接口触发的rtp发送 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream 流id + * @param ssrc ssrc的10进制打印,通过atoi转换为整型 + * @param err 错误代码 + * @param msg 错误提示 + */ + void(API_CALL *on_mk_media_send_rtp_stop)(const char *vhost, const char *app, const char *stream, const char *ssrc, int err, const char *msg); + } mk_events; diff --git a/api/include/mk_events_objects.h b/api/include/mk_events_objects.h index f8d3c571..3dc9a2c3 100644 --- a/api/include/mk_events_objects.h +++ b/api/include/mk_events_objects.h @@ -12,6 +12,7 @@ #define MK_EVENT_OBJECTS_H #include "mk_common.h" #include "mk_tcp.h" +#include "mk_track.h" #ifdef __cplusplus extern "C" { #endif @@ -61,19 +62,19 @@ API_EXPORT const char* API_CALL mk_parser_get_content(const mk_parser ctx, size_ ///////////////////////////////////////////MediaInfo///////////////////////////////////////////// //MediaInfo对象的C映射 typedef struct mk_media_info_t *mk_media_info; -//MediaInfo::_param_strs +//MediaInfo::param_strs API_EXPORT const char* API_CALL mk_media_info_get_params(const mk_media_info ctx); -//MediaInfo::_schema +//MediaInfo::schema API_EXPORT const char* API_CALL mk_media_info_get_schema(const mk_media_info ctx); -//MediaInfo::_vhost +//MediaInfo::vhost API_EXPORT const char* API_CALL mk_media_info_get_vhost(const mk_media_info ctx); -//MediaInfo::_app +//MediaInfo::app API_EXPORT const char* API_CALL mk_media_info_get_app(const mk_media_info ctx); -//MediaInfo::_streamid +//MediaInfo::stream API_EXPORT const char* API_CALL mk_media_info_get_stream(const mk_media_info ctx); -//MediaInfo::_host +//MediaInfo::host API_EXPORT const char* API_CALL mk_media_info_get_host(const mk_media_info ctx); -//MediaInfo::_port +//MediaInfo::port API_EXPORT uint16_t API_CALL mk_media_info_get_port(const mk_media_info ctx); @@ -95,6 +96,13 @@ API_EXPORT const char* API_CALL mk_media_source_get_stream(const mk_media_source API_EXPORT int API_CALL mk_media_source_get_reader_count(const mk_media_source ctx); //MediaSource::totalReaderCount() API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_source ctx); +// get track count from MediaSource +API_EXPORT int API_CALL mk_media_source_get_track_count(const mk_media_source ctx); +// copy track reference by index from MediaSource, please use mk_track_unref to release it +API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx, int index); +// MediaSource::broadcastMessage +API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len); + /** * 直播源在ZLMediaKit中被称作为MediaSource, * 目前支持3种,分别是RtmpMediaSource、RtspMediaSource、HlsMediaSource @@ -132,6 +140,12 @@ API_EXPORT void API_CALL mk_media_source_find(const char *schema, int from_mp4, void *user_data, on_mk_media_source_find_cb cb); + +API_EXPORT const mk_media_source API_CALL mk_media_source_find2(const char *schema, + const char *vhost, + const char *app, + const char *stream, + int from_mp4); //MediaSource::for_each_media() API_EXPORT void API_CALL mk_media_source_for_each(void *user_data, on_mk_media_source_find_cb cb, const char *schema, const char *vhost, const char *app, const char *stream); 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/include/mk_tcp.h b/api/include/mk_tcp.h index 84deed8e..4ab19da1 100644 --- a/api/include/mk_tcp.h +++ b/api/include/mk_tcp.h @@ -82,11 +82,11 @@ API_EXPORT void API_CALL mk_tcp_session_send_buffer(const mk_tcp_session ctx, mk API_EXPORT void API_CALL mk_tcp_session_send_safe(const mk_tcp_session ctx, const char *data, size_t len); API_EXPORT void API_CALL mk_tcp_session_send_buffer_safe(const mk_tcp_session ctx, mk_buffer buffer); -//创建mk_tcp_session的弱引用 +//创建mk_tcp_session的强引用 API_EXPORT mk_tcp_session_ref API_CALL mk_tcp_session_ref_from(const mk_tcp_session ctx); -//删除mk_tcp_session的弱引用 +//删除mk_tcp_session的强引用 API_EXPORT void mk_tcp_session_ref_release(const mk_tcp_session_ref ref); -//根据弱引用获取mk_tcp_session,如果mk_tcp_session已经销毁,那么返回NULL +//根据强引用获取mk_tcp_session API_EXPORT mk_tcp_session mk_tcp_session_from_ref(const mk_tcp_session_ref ref); ///////////////////////////////////////////自定义tcp服务///////////////////////////////////////////// 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 4b641a2b..3833017f 100644 --- a/api/source/mk_events_objects.cpp +++ b/api/source/mk_events_objects.cpp @@ -86,17 +86,17 @@ API_EXPORT const char* API_CALL mk_mp4_info_get_stream(const mk_mp4_info ctx){ API_EXPORT const char* API_CALL mk_parser_get_method(const mk_parser ctx){ assert(ctx); Parser *parser = (Parser *)ctx; - return parser->Method().c_str(); + return parser->method().c_str(); } API_EXPORT const char* API_CALL mk_parser_get_url(const mk_parser ctx){ assert(ctx); Parser *parser = (Parser *)ctx; - return parser->Url().c_str(); + return parser->url().c_str(); } API_EXPORT const char* API_CALL mk_parser_get_url_params(const mk_parser ctx){ assert(ctx); Parser *parser = (Parser *)ctx; - return parser->Params().c_str(); + return parser->params().c_str(); } API_EXPORT const char* API_CALL mk_parser_get_url_param(const mk_parser ctx,const char *key){ assert(ctx && key); @@ -106,7 +106,7 @@ API_EXPORT const char* API_CALL mk_parser_get_url_param(const mk_parser ctx,cons API_EXPORT const char* API_CALL mk_parser_get_tail(const mk_parser ctx){ assert(ctx); Parser *parser = (Parser *)ctx; - return parser->Tail().c_str(); + return parser->protocol().c_str(); } API_EXPORT const char* API_CALL mk_parser_get_header(const mk_parser ctx,const char *key){ assert(ctx && key); @@ -117,52 +117,52 @@ API_EXPORT const char* API_CALL mk_parser_get_content(const mk_parser ctx, size_ assert(ctx); Parser *parser = (Parser *)ctx; if(length){ - *length = parser->Content().size(); + *length = parser->content().size(); } - return parser->Content().c_str(); + return parser->content().c_str(); } ///////////////////////////////////////////MediaInfo///////////////////////////////////////////// API_EXPORT const char* API_CALL mk_media_info_get_params(const mk_media_info ctx){ assert(ctx); MediaInfo *info = (MediaInfo *)ctx; - return info->_param_strs.c_str(); + return info->param_strs.c_str(); } API_EXPORT const char* API_CALL mk_media_info_get_schema(const mk_media_info ctx){ assert(ctx); MediaInfo *info = (MediaInfo *)ctx; - return info->_schema.c_str(); + return info->schema.c_str(); } API_EXPORT const char* API_CALL mk_media_info_get_vhost(const mk_media_info ctx){ assert(ctx); MediaInfo *info = (MediaInfo *)ctx; - return info->_vhost.c_str(); + return info->vhost.c_str(); } API_EXPORT const char* API_CALL mk_media_info_get_host(const mk_media_info ctx){ assert(ctx); MediaInfo *info = (MediaInfo *)ctx; - return info->_host.c_str(); + return info->host.c_str(); } API_EXPORT uint16_t API_CALL mk_media_info_get_port(const mk_media_info ctx){ assert(ctx); MediaInfo *info = (MediaInfo *)ctx; - return info->_port; + return info->port; } API_EXPORT const char* API_CALL mk_media_info_get_app(const mk_media_info ctx){ assert(ctx); MediaInfo *info = (MediaInfo *)ctx; - return info->_app.c_str(); + return info->app.c_str(); } API_EXPORT const char* API_CALL mk_media_info_get_stream(const mk_media_info ctx){ assert(ctx); MediaInfo *info = (MediaInfo *)ctx; - return info->_streamid.c_str(); + return info->stream.c_str(); } ///////////////////////////////////////////MediaSource///////////////////////////////////////////// @@ -174,17 +174,17 @@ API_EXPORT const char* API_CALL mk_media_source_get_schema(const mk_media_source API_EXPORT const char* API_CALL mk_media_source_get_vhost(const mk_media_source ctx){ assert(ctx); MediaSource *src = (MediaSource *)ctx; - return src->getVhost().c_str(); + return src->getMediaTuple().vhost.c_str(); } API_EXPORT const char* API_CALL mk_media_source_get_app(const mk_media_source ctx){ assert(ctx); MediaSource *src = (MediaSource *)ctx; - return src->getApp().c_str(); + return src->getMediaTuple().app.c_str(); } API_EXPORT const char* API_CALL mk_media_source_get_stream(const mk_media_source ctx){ assert(ctx); MediaSource *src = (MediaSource *)ctx; - return src->getId().c_str(); + return src->getMediaTuple().stream.c_str(); } API_EXPORT int API_CALL mk_media_source_get_reader_count(const mk_media_source ctx){ assert(ctx); @@ -198,6 +198,32 @@ API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_so return src->totalReaderCount(); } +API_EXPORT int API_CALL mk_media_source_get_track_count(const mk_media_source ctx) { + assert(ctx); + MediaSource *src = (MediaSource *)ctx; + return src->getTracks(false).size(); +} + +API_EXPORT mk_track API_CALL mk_media_source_get_track(const mk_media_source ctx, int index) { + assert(ctx); + MediaSource *src = (MediaSource *)ctx; + auto tracks = src->getTracks(false); + if (index < 0 && index >= tracks.size()) { + return nullptr; + } + return (mk_track) new Track::Ptr(std::move(tracks[index])); +} + +API_EXPORT int API_CALL mk_media_source_broadcast_msg(const mk_media_source ctx, const char *msg, size_t len) { + assert(ctx && msg && len); + MediaSource *src = (MediaSource *)ctx; + + Any any; + Buffer::Ptr buffer = std::make_shared(std::string(msg, len)); + any.set(std::move(buffer)); + return src->broadcastMessage(any); +} + API_EXPORT int API_CALL mk_media_source_close(const mk_media_source ctx,int force){ assert(ctx); MediaSource *src = (MediaSource *)ctx; @@ -248,6 +274,16 @@ API_EXPORT void API_CALL mk_media_source_find(const char *schema, cb(user_data, (mk_media_source)src.get()); } +API_EXPORT const mk_media_source API_CALL mk_media_source_find2(const char *schema, + const char *vhost, + const char *app, + const char *stream, + int from_mp4) { + assert(schema && vhost && app && stream); + auto src = MediaSource::find(schema, vhost, app, stream, from_mp4); + return (mk_media_source)src.get(); +} + API_EXPORT void API_CALL mk_media_source_for_each(void *user_data, on_mk_media_source_find_cb cb, const char *schema, const char *vhost, const char *app, const char *stream) { assert(cb); diff --git a/api/source/mk_frame.cpp b/api/source/mk_frame.cpp index 8c2e4ff6..7a7cdb85 100644 --- a/api/source/mk_frame.cpp +++ b/api/source/mk_frame.cpp @@ -9,10 +9,12 @@ */ #include "mk_frame.h" +#include "mk_track.h" #include "Extension/Frame.h" #include "Extension/H264.h" #include "Extension/H265.h" #include "Extension/AAC.h" +#include "Record/MPEG.h" using namespace mediakit; @@ -178,3 +180,92 @@ API_EXPORT uint32_t API_CALL mk_frame_get_flags(mk_frame frame) { } return ret; } + +API_EXPORT mk_frame_merger API_CALL mk_frame_merger_create(int type) { + return reinterpret_cast(new FrameMerger(type)); +} + +API_EXPORT void API_CALL mk_frame_merger_release(mk_frame_merger ctx) { + assert(ctx); + delete reinterpret_cast(ctx); +} + +API_EXPORT void API_CALL mk_frame_merger_clear(mk_frame_merger ctx) { + assert(ctx); + reinterpret_cast(ctx)->clear(); +} + +API_EXPORT void API_CALL mk_frame_merger_flush(mk_frame_merger ctx) { + assert(ctx); + reinterpret_cast(ctx)->flush(); +} + +API_EXPORT void API_CALL mk_frame_merger_input(mk_frame_merger ctx, mk_frame frame, on_mk_frame_merger cb, void *user_data) { + assert(ctx && frame && cb); + reinterpret_cast(ctx)->inputFrame(*((Frame::Ptr *) frame), [cb, user_data](uint64_t dts, uint64_t pts, const toolkit::Buffer::Ptr &buffer, bool have_key_frame) { + cb(user_data, dts, pts, (mk_buffer)(&buffer), have_key_frame); + }); +} + +////////////////////////////////////////////////////////////////////// + +class MpegMuxerForC : public MpegMuxer { +public: + using onMuxer = std::function; + MpegMuxerForC(bool is_ps) : MpegMuxer(is_ps) { + _cb = nullptr; + } + ~MpegMuxerForC() { MpegMuxer::flush(); }; + + void setOnMuxer(onMuxer cb) { + _cb = std::move(cb); + } + +private: + void onWrite(std::shared_ptr buffer, uint64_t timestamp, bool key_pos) override { + if (_cb) { + if (!buffer) { + _cb(nullptr, 0, timestamp, key_pos); + } else { + _cb(buffer->data(), buffer->size(), timestamp, key_pos); + } + } + } + +private: + onMuxer _cb; +}; + +API_EXPORT mk_mpeg_muxer API_CALL mk_mpeg_muxer_create(on_mk_mpeg_muxer_frame cb, void *user_data, int is_ps){ + assert(cb); + auto ret = new MpegMuxerForC(is_ps); + std::shared_ptr ptr(user_data, [](void *) {}); + ret->setOnMuxer([cb, ptr, ret](const char *frame, size_t size, uint64_t timestamp, int key_pos) { + cb(ptr.get(), reinterpret_cast(ret), frame, size, timestamp, key_pos); + }); + return reinterpret_cast(ret); +} + +API_EXPORT void API_CALL mk_mpeg_muxer_release(mk_mpeg_muxer ctx){ + assert(ctx); + auto ptr = reinterpret_cast(ctx); + delete ptr; +} + +API_EXPORT void API_CALL mk_mpeg_muxer_init_track(mk_mpeg_muxer ctx, void* track) { + assert(ctx && track); + auto ptr = reinterpret_cast(ctx); + ptr->addTrack(*((Track::Ptr *) track)); +} + +API_EXPORT void API_CALL mk_mpeg_muxer_init_complete(mk_mpeg_muxer ctx) { + assert(ctx); + auto ptr = reinterpret_cast(ctx); + ptr->addTrackCompleted(); +} + +API_EXPORT int API_CALL mk_mpeg_muxer_input_frame(mk_mpeg_muxer ctx, mk_frame frame){ + assert(ctx && frame); + auto ptr = reinterpret_cast(ctx); + return ptr->inputFrame(*((Frame::Ptr *) frame)); +} \ No newline at end of file diff --git a/api/source/mk_httpclient.cpp b/api/source/mk_httpclient.cpp index 9c52cd33..43de7a33 100755 --- a/api/source/mk_httpclient.cpp +++ b/api/source/mk_httpclient.cpp @@ -108,7 +108,7 @@ API_EXPORT void API_CALL mk_http_requester_add_header(mk_http_requester ctx,cons API_EXPORT const char* API_CALL mk_http_requester_get_response_status(mk_http_requester ctx){ assert(ctx); HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx; - return (*obj)->response().Url().c_str(); + return (*obj)->response().status().c_str(); } API_EXPORT const char* API_CALL mk_http_requester_get_response_header(mk_http_requester ctx,const char *key){ @@ -121,9 +121,9 @@ API_EXPORT const char* API_CALL mk_http_requester_get_response_body(mk_http_requ assert(ctx); HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx; if(length){ - *length = (*obj)->response().Content().size(); + *length = (*obj)->response().content().size(); } - return (*obj)->response().Content().c_str(); + return (*obj)->response().content().c_str(); } API_EXPORT mk_parser API_CALL mk_http_requester_get_response(mk_http_requester ctx){ diff --git a/api/source/mk_media.cpp b/api/source/mk_media.cpp index 070c12ed..3e1d33e0 100755 --- a/api/source/mk_media.cpp +++ b/api/source/mk_media.cpp @@ -22,7 +22,8 @@ public: MediaHelper(const char *vhost, const char *app, const char *stream, float duration, const ProtocolOption &option) { _poller = EventPollerPool::Instance().getPoller(); // 在poller线程中创建DevChannel(MultiMediaSourceMuxer)对象,确保严格的线程安全限制 - _poller->sync([&]() { _channel = std::make_shared(vhost, app, stream, duration, option); }); + auto tuple = MediaTuple{vhost, app, stream}; + _poller->sync([&]() { _channel = std::make_shared(tuple, duration, option); }); } ~MediaHelper() = default; @@ -263,7 +264,7 @@ API_EXPORT void API_CALL mk_media_input_yuv(mk_media ctx, const char *yuv[3], in } API_EXPORT int API_CALL mk_media_input_aac(mk_media ctx, const void *data, int len, uint64_t dts, void *adts) { - assert(ctx && data && len > 0 && adts); + assert(ctx && data && len > 0); MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx; return (*obj)->getChannel()->inputAAC((const char *) data, len, dts, (char *) adts); } 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_tcp.cpp b/api/source/mk_tcp.cpp index 2b79d08a..d155cac8 100644 --- a/api/source/mk_tcp.cpp +++ b/api/source/mk_tcp.cpp @@ -139,7 +139,7 @@ API_EXPORT void API_CALL mk_tcp_session_send(const mk_tcp_session ctx, const cha API_EXPORT void API_CALL mk_tcp_session_send_buffer_safe(const mk_tcp_session ctx, mk_buffer buffer) { assert(ctx && buffer); try { - std::weak_ptr weak_session = ((SessionForC *) ctx)->shared_from_this(); + std::weak_ptr weak_session = ((SessionForC *) ctx)->shared_from_this(); auto ref = mk_buffer_ref(buffer); ((SessionForC *) ctx)->async([weak_session, ref]() { auto session_session = weak_session.lock(); @@ -149,13 +149,13 @@ API_EXPORT void API_CALL mk_tcp_session_send_buffer_safe(const mk_tcp_session ct mk_buffer_unref(ref); }); } catch (std::exception &ex) { - WarnL << "can not got the strong pionter of this mk_tcp_session:" << ex.what(); + WarnL << "can not got the strong pointer of this mk_tcp_session:" << ex.what(); } } API_EXPORT mk_tcp_session_ref API_CALL mk_tcp_session_ref_from(const mk_tcp_session ctx) { auto ref = ((SessionForC *) ctx)->shared_from_this(); - return (mk_tcp_session_ref)new std::shared_ptr(std::dynamic_pointer_cast(ref)); + return (mk_tcp_session_ref)new std::shared_ptr(std::static_pointer_cast(ref)); } API_EXPORT void mk_tcp_session_ref_release(const mk_tcp_session_ref ref) { @@ -271,7 +271,7 @@ void TcpClientForC::onRecv(const Buffer::Ptr &pBuf) { } } -void TcpClientForC::onErr(const SockException &ex) { +void TcpClientForC::onError(const SockException &ex) { if(_events.on_mk_tcp_client_disconnect){ _events.on_mk_tcp_client_disconnect(_client,ex.getErrCode(),ex.what()); } diff --git a/api/source/mk_tcp_private.h b/api/source/mk_tcp_private.h index 241e88b6..e569ca16 100644 --- a/api/source/mk_tcp_private.h +++ b/api/source/mk_tcp_private.h @@ -21,7 +21,7 @@ public: TcpClientForC(mk_tcp_client_events *events) ; ~TcpClientForC() override ; void onRecv(const toolkit::Buffer::Ptr &pBuf) override; - void onErr(const toolkit::SockException &ex) override; + void onError(const toolkit::SockException &ex) override; void onManager() override; void onConnect(const toolkit::SockException &ex) override; void setClient(mk_tcp_client client); diff --git a/api/source/mk_thread.cpp b/api/source/mk_thread.cpp index 56b5be2a..29775b43 100644 --- a/api/source/mk_thread.cpp +++ b/api/source/mk_thread.cpp @@ -143,7 +143,7 @@ public: } EventPoller::Ptr getPoller() { - return dynamic_pointer_cast(getExecutor()); + return static_pointer_cast(getExecutor()); } }; diff --git a/api/source/mk_util.cpp b/api/source/mk_util.cpp index 6c775529..5c5d3197 100644 --- a/api/source/mk_util.cpp +++ b/api/source/mk_util.cpp @@ -65,7 +65,7 @@ API_EXPORT mk_ini API_CALL mk_ini_default() { static void emit_ini_file_reload(mk_ini ini) { if (ini == mk_ini_default()) { // 广播配置文件热加载 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig); + NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig); } } diff --git a/api/tests/CMakeLists.txt b/api/tests/CMakeLists.txt index f2875ff6..8108b71b 100644 --- a/api/tests/CMakeLists.txt +++ b/api/tests/CMakeLists.txt @@ -43,15 +43,3 @@ foreach(TEST_SRC ${TEST_SRC_LIST}) target_link_libraries(${exe_name} mk_api) target_compile_options(${exe_name} PRIVATE ${COMPILE_OPTIONS_DEFAULT}) endforeach() - - - - - - - - - - - - diff --git a/api/tests/pusher.c b/api/tests/pusher.c index a6f3cd2d..2014fd09 100644 --- a/api/tests/pusher.c +++ b/api/tests/pusher.c @@ -9,6 +9,7 @@ */ #include +#include #include "mk_mediakit.h" typedef struct { diff --git a/api/tests/server.c b/api/tests/server.c index 8b283b6b..d331519c 100644 --- a/api/tests/server.c +++ b/api/tests/server.c @@ -9,6 +9,7 @@ */ #include +#include #include "mk_mediakit.h" #define LOG_LEV 4 diff --git a/cmake/Jemalloc.cmake b/cmake/Jemalloc.cmake index 94d46b64..38847e58 100644 --- a/cmake/Jemalloc.cmake +++ b/cmake/Jemalloc.cmake @@ -1,21 +1,34 @@ # Download and build Jemalloc -set(JEMALLOC_VERSION 5.2.1) +set(JEMALLOC_VERSION 5.3.0) set(JEMALLOC_NAME jemalloc-${JEMALLOC_VERSION}) set(JEMALLOC_TAR_PATH ${DEP_ROOT_DIR}/${JEMALLOC_NAME}.tar.bz2) list(APPEND jemalloc_CONFIG_ARGS --disable-initial-exec-tls) -list(APPEND jemalloc_CONFIG_ARGS --without-export) +#list(APPEND jemalloc_CONFIG_ARGS --without-export) +if (ENABLE_JEMALLOC_STAT) + list(APPEND jemalloc_CONFIG_ARGS --enable-stats) + message(STATUS "Jemalloc stats enabled") +else () list(APPEND jemalloc_CONFIG_ARGS --disable-stats) + message(STATUS "Jemalloc stats disabled") +endif () list(APPEND jemalloc_CONFIG_ARGS --disable-libdl) #list(APPEND jemalloc_CONFIG_ARGS --disable-cxx) #list(APPEND jemalloc_CONFIG_ARGS --with-jemalloc-prefix=je_) #list(APPEND jemalloc_CONFIG_ARGS --enable-debug) if(NOT EXISTS ${JEMALLOC_TAR_PATH}) - message(STATUS "Downloading ${JEMALLOC_NAME}...") - file(DOWNLOAD https://github.com/jemalloc/jemalloc/releases/download/${JEMALLOC_VERSION}/${JEMALLOC_NAME}.tar.bz2 - ${JEMALLOC_TAR_PATH}) + set(JEMALLOC_URL https://github.com/jemalloc/jemalloc/releases/download/${JEMALLOC_VERSION}/${JEMALLOC_NAME}.tar.bz2) + message(STATUS "Downloading ${JEMALLOC_NAME} from ${JEMALLOC_URL}") + file(DOWNLOAD ${JEMALLOC_URL} ${JEMALLOC_TAR_PATH} SHOW_PROGRESS STATUS JEMALLOC_DOWNLOAD_STATUS LOG JEMALLOC_DOWNLOAD_LOG) + list(GET JEMALLOC_DOWNLOAD_STATUS 0 JEMALLOC_DOWNLOAD_STATUS_CODE) + if(NOT JEMALLOC_DOWNLOAD_STATUS_CODE EQUAL 0) + file(REMOVE ${JEMALLOC_TAR_PATH}) + message(STATUS "${JEMALLOC_DOWNLOAD_LOG}") + message(FATAL_ERROR "${JEMALLOC_NAME} download failed! error is ${JEMALLOC_DOWNLOAD_STATUS}") + return() + endif () endif() SET( DIR_CONTAINING_JEMALLOC ${DEP_ROOT_DIR}/${JEMALLOC_NAME} ) diff --git a/conf/config.ini b/conf/config.ini index 8950b299..dc343b32 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -22,7 +22,7 @@ bin=/usr/bin/ffmpeg #FFmpeg拉流再推流的命令模板,通过该模板可以设置再编码的一些参数 cmd=%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s #FFmpeg生成截图的命令,可以通过修改该配置改变截图分辨率或质量 -snap=%s -i %s -y -f mjpeg -t 0.001 %s +snap=%s -i %s -y -f mjpeg -frames:v 1 %s #FFmpeg日志的路径,如果置空则不生成FFmpeg日志 #可以为相对(相对于本可执行程序目录)或绝对路径 log=./ffmpeg/ffmpeg.log @@ -32,18 +32,28 @@ restart_sec=0 #转协议相关开关;如果addStreamProxy api和on_publish hook回复未指定转协议参数,则采用这些配置项 [protocol] #转协议时,是否开启帧级时间戳覆盖 -modify_stamp=0 +# 0:采用源视频流绝对时间戳,不做任何改变 +# 1:采用zlmediakit接收数据时的系统时间戳(有平滑处理) +# 2:采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正 +modify_stamp=2 #转协议是否开启音频 enable_audio=1 #添加acc静音音频,在关闭音频时,此开关无效 add_mute_audio=1 +#无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close) +#此配置置1时,此流如果无人观看,将不触发on_none_reader hook回调, +#而是将直接关闭流 +auto_close=0 + #推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。 #置0关闭此特性(推流断开会导致立即断开播放器) #此参数不应大于播放器超时时间;单位毫秒 continue_push_ms=15000 -#是否开启转换为hls +#是否开启转换为hls(mpegts) enable_hls=1 +#是否开启转换为hls(fmp4) +enable_hls_fmp4=0 #是否开启MP4录制 enable_mp4=0 #是否开启转换为rtsp/webrtc @@ -121,7 +131,7 @@ segDur=2 segNum=3 #HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数 segRetain=5 -#是否广播 ts 切片完成通知 +#是否广播 hls切片(ts/fmp4)完成通知(on_record_ts) broadcastRecordTs=0 #直播hls文件删除延时,单位秒,issue: #913 deleteDelaySec=10 @@ -132,9 +142,6 @@ deleteDelaySec=10 segKeep=0 [hook] -#在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然 -#该配置项的目的是为了开发者自己调试测试,该参数暴露后会有泄露隐私的安全隐患 -admin_params=secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc #是否启用hook事件,启用后,推拉流都将进行鉴权 enable=0 #播放器或推流器使用流量事件,置空则关闭 @@ -147,7 +154,7 @@ on_play=https://127.0.0.1/index/hook/on_play on_publish=https://127.0.0.1/index/hook/on_publish #录制mp4切片完成事件 on_record_mp4=https://127.0.0.1/index/hook/on_record_mp4 -# 录制 hls ts 切片完成事件 +# 录制 hls ts(或fmp4) 切片完成事件 on_record_ts=https://127.0.0.1/index/hook/on_record_ts #rtsp播放鉴权事件,此事件中比对rtsp的用户名密码 on_rtsp_auth=https://127.0.0.1/index/hook/on_rtsp_auth @@ -159,12 +166,16 @@ on_rtsp_realm=https://127.0.0.1/index/hook/on_rtsp_realm on_shell_login=https://127.0.0.1/index/hook/on_shell_login #直播流注册或注销事件 on_stream_changed=https://127.0.0.1/index/hook/on_stream_changed +#过滤on_stream_changed hook的协议类型,可以选择只监听某些感兴趣的协议;置空则不过滤协议 +stream_changed_schemas=rtsp/rtmp/fmp4/ts/hls/hls.fmp4 #无人观看流事件,通过该事件,可以选择是否关闭无人观看的流。配合general.streamNoneReaderDelayMS选项一起使用 on_stream_none_reader=https://127.0.0.1/index/hook/on_stream_none_reader #播放时,未找到流事件,通过配合hook.on_stream_none_reader事件可以完成按需拉流 on_stream_not_found=https://127.0.0.1/index/hook/on_stream_not_found #服务器启动报告,可以用于服务器的崩溃重启事件监听 on_server_started=https://127.0.0.1/index/hook/on_server_started +#服务器退出报告,当服务器正常退出时触发 +on_server_exited=https://127.0.0.1/index/hook/on_server_exited #server保活上报 on_server_keepalive=https://127.0.0.1/index/hook/on_server_keepalive #发送rtp(startSendRtp)被动关闭时回调 @@ -230,6 +241,10 @@ forbidCacheSuffix= #可以把http代理前真实客户端ip放在http头中:https://github.com/ZLMediaKit/ZLMediaKit/issues/1388 #切勿暴露此key,否则可能导致伪造客户端ip forwarded_ip_header= +#默认允许所有跨域请求 +allow_cross_domains=1 +#允许访问http api和http文件索引的ip地址范围白名单,置空情况下不做限制 +allow_ip_range=::1,127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255 [multicast] #rtp组播截止组播ip地址 @@ -259,8 +274,6 @@ handshakeSecond=15 #rtmp超时时间,如果该时间内未收到客户端的数据, #或者tcp发送缓存超过这个时间,则会断开连接,单位秒 keepAliveSecond=15 -#在接收rtmp推流时,是否重新生成时间戳(很多推流器的时间戳着实很烂) -modifyStamp=0 #rtmp服务器监听端口 port=1935 #rtmps服务器监听地址 @@ -276,6 +289,9 @@ videoMtuSize=1400 rtpMaxSize=10 # rtp 打包时,低延迟开关,默认关闭(为0),h264存在一帧多个slice(NAL)的情况,在这种情况下,如果开启可能会导致画面花屏 lowLatency=0 +# H264 rtp打包模式是否采用stap-a模式(为了在老版本浏览器上兼容webrtc)还是采用Single NAL unit packet per H.264 模式 +# 有些老的rtsp设备不支持stap-a rtp,设置此配置为0可提高兼容性 +h264_stap_a=1 [rtp_proxy] #导出调试数据(包括rtp/ps/h264)至该目录,置空则关闭数据导出 @@ -357,6 +373,10 @@ port=554 sslport=0 #rtsp 转发是否使用低延迟模式,当开启时,不会缓存rtp包,来提高并发,可以降低一帧的延迟 lowLatency=0 +#强制协商rtp传输方式 (0:TCP,1:UDP,2:MULTICAST,-1:不限制) +#当客户端发起RTSP SETUP的时候如果传输类型和此配置不一致则返回461 Unsupported transport +#迫使客户端重新SETUP并切换到对应协议。目前支持FFMPEG和VLC +rtpTransportType=-1 [shell] #调试telnet服务器接受最大bufffer大小 maxReqSize=1024 diff --git a/default.pem b/default.pem index 9bfab500..e0ec1132 100644 --- a/default.pem +++ b/default.pem @@ -1,89 +1,89 @@ -----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAjTFnNz2btUHR/K8TegU2n06lXsQuW4AfsMGthBLvnz2zdgL2 -uVxfRCuleoRpECEV9I80ibTXAJlouRLbnhLHto8gzyLeQue9/DtxTEGcS7y2aMha -0wAoUcOBv7tAITSnFepGHXoYgyU7HOCvn96U1bzqLTOhxOCH/xy003dwoEC7+Pjl -dsWs/16cpvYiZV/dZvzDDKIpHSIvQy1whi6N0SDpzz/ncThn1z8xcJDx0I1jVR76 -juP7TttbyhqJkO+fOlLn4yP9K5wZ/dPFJn2+bQRCrzGc4SM7J5YOq8ckTTTbZtSp -9yPpm7O5QyfdzePhOpRrjWroLsqaDOPV2UQlzwIDAQABAoIBADCWTh8P19vdnR3X -v5uPXLcgkL7WQt+g7Qbd91CKVaRWTsHvDilGVNA4Ntc85oyy3gPNHfa/YPdnU0bQ -6vtwGgLEKTWumY6rgdDhQcFMmLTlaV4QiFSw6q8MWMN6c/yZSmA7wMoXAIVs0/VB -ip44sb4Fpw5MBMCjxZjwL3fP09WJPlUqx09vVo7eH8rFwLBikmn982IzRigAx1I8 -TX0wkdqvv33MSxBXPMQIrwPqjf2arxWFzb6vp6yolYbMZtgORF9gznWABRy3oY50 -9jFkTkbxZFlSMVuF7nlM0WJj5Q9/IelBqpozODWUVvB+6inCqkxNLkbh0ISbpXWC -16gUZfUCgYEAxWo3FRNBrNXhVD5h2N4ApyUXkZ5UYIY5zbsHEJCrPjooh9uHu9kh -xXh5v11J/7TV9BfwLZ4qRbDBH4fq0DKEOXOZRLY5Lo4KbrYmlEDCabuJdmwwHeGh -S5K37F5z/+zPz9KWkKN+9Rg32xdLxh0969O77GnvuBrhzASpVsF6ZFMCgYEAtxf1 -eVg4Kxzuy0AWs+CisSVQc+5CbZ9teKA5fli2EVSmL5dsrKatVTIDghudJgQTU6cr -zP9I20K11jeqIoK5saQXH3CzogN6aDuKssq4rDbvVSZ09Zry6N1WMz9GPe31zEYw -sdU1w7vUw+l3unFfWOP4oZm0MH+na61V1YohCRUCgYANlp0J/1RS8DndUZnskoNa -/eucY1iNeE+8QHZhBoQy+U/W4h56qJxxejRvHp28UxczAP7QNQXV3C++2t0nzYJa -bgGLwDs5YB+JtVH8fGSlYHo6w4GgXOp8SDIOvAWiBQvc0zL367kOZ8dYdkcJ8PNV -KzLROA1/D6KhJ2T8ir7A7wKBgQCjVVxGw8xXqZfc+W9HSD3aic8bnJDl+jNOSKEB -dWH2U+1sx0jLPGWketlmV/v4zenv1lHcrl/wObK9RysfXj8JmbiG86NMBI5OLc+t -b+sOtnMLIyNzdqb71Xfwf6HJ3V5IvNTzz6AG3KkRnFSSnlDQm45RmyyDl11jUV4h -APg3gQKBgBzFeuKWnaTZz1FQBr5Ytl9gtxBRMl+49jtkqyzErJYFHe0MTWeD/1xj -mEC/7UERYWhIQF1L4ah6c0QkecR3F1s9/IYK/QHsnSJFwRyFuMas6StCERsDq5oQ -GWpXAmw7JTa8OYwxVjORdXY25Iwv6rEr6iUYBWZrkhoWYBySWpSZ +MIIEowIBAAKCAQEAtgg0vsIc8bIMb9hAO1Lwq2TVNYMiD928WCVwq7Bj3B9LM8ci +fN+mBxHz35SeC9JnQysbKAf8VVfVi7GVzwFq4VaRreoIl8DoMbBORb2iKxfLXKEH +S283bbl17csAYHTTBqFsKnO3kKvYoDHRKFX0T0rzZibI0ACQiylW+ALE3fVS0hFI +8fmoZnIAOyQ15RLp7KPO4bMRVdZaOqRUZeW0YzbvOBeGZKbx3bXiscL9RwfGSxlF +MCLahaJsBrVDo4/IGDRCmSydK8k/41MHd86pGPzCixBx7cqtKO/6+cVeEuGxfJPf +JVuxNq6vkRSt2HxCc6i2iagA2uL/WYdg3Pa/4wIDAQABAoIBAADFrCObAzBrRu46 +hps50NeJR/ZAJibXE/NzxTSVPPc0EseXcqgA8t1Y0CYEpV77d4CrcCQNVJ6wDrHX +AQGtydxG17tbIMo0AUgkrVBSa5uvMCembzd8s0l93egyUkAWfsaqbKEJeJ/eer7D +N1Xqd2zWro2iYHuxZOuSM1I+AMPIQsmYJ71w6/h9YpQh436Vd+zNQ5k/nWpLHihT +VB2ECrJ36IbuiYo3UbSr9gQjyBSMkk/oUqO4jonkb6L7r0mqHXNeblycg99/m6i7 +O5c5DQKMhzqibwvNNf6uvWCcLKfF5Kqzzf9DKR3/pYOBQrVTA24l4UFsfTdEKUNS +a8W3P8ECgYEA6CQOG15V9upc2nPzfFwgftGyomSMYH54PkSFdr2R4djyXkyil6Ik +efK3E+lKr9YnzwcLw3csPmVt3lqSgixQUMcyXXrhCttfk/qzSJkI+UZPQE+SrNeW +0c+blQOzVcfbNRu248iGFaRx+5qA6PMH4UZTgn7e6nXoPUgRp4ryI/MCgYEAyL24 +R7uMSuPQBRJFU84Lu+Rv4lkKdCYSLuQtMZly74m11iG6e+EHJQx0C3eexrC8LhOV +Sm4xTlwVrYQ+IdW51bhAwwHcnzGUzpbESJSDK5ZTd/P5daz8yt8ZaGbUFxNEsxTr +ElKPRcjJH5CRuyYr24DYg+CpMGdlF0N6Pcx5IFECgYAedlzDiqWNOUPmBsE02IIL +IklmtfsVzoLI6QT6h/XUxTtI1JWhgE15EzijDEIYwOmIaUxJ4iGULos0Wn5PRrFj +aEBbs/xECHWKXaOZKzvaOje8ILUGqWPJNI0eCNZHs2o4leJyEaZGwMWUVroD16B5 +F1luDmgCLGbFY+etLLaJsQKBgB40VbcNZDWcg59PuXi7pw5Vd/RB243QcKn3kUlG +QoICYYbfulSLbmzHq+pRzGUvEJGKRstVOzwEJQrfvA2RQA4FVFFDRXP6nN5c1xno +prf3PYXuAtoO9lZ8LTGFT2JNdufPPPOb0oz4gjKqqRLU0oKLp4hoVGzBEffnIkyM +KKmRAoGBAIGXh4gvxzEQMgGzfKfNuxKCT9SEhsg7NU++Iey3qn4G4t+jIWOt2Gi7 +5+y49JWoGq6DL+2ZVVw6Cn6wd9tfzDKD5GhvIztK0z1+wqpFOL4M8bwqJDOKgsZ3 +PCPASbxPgMyNCjRhvxBuscCr+dRFYDUrirOK9EUPyO9EoNTPPN9a -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- -MIIGAjCCBOqgAwIBAgIQAiXv68Xco/vd9YeB4g3HLjANBgkqhkiG9w0BAQsFADBu +MIIGBTCCBO2gAwIBAgIQDNIYeWoFoT3jxF2+HmEbTDANBgkqhkiG9w0BAQsFADBu MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg -RFYgVExTIENBIC0gRzEwHhcNMjIwOTE4MDAwMDAwWhcNMjMwOTE4MjM1OTU5WjAh +RFYgVExTIENBIC0gRzIwHhcNMjMwOTI4MDAwMDAwWhcNMjQwOTI3MjM1OTU5WjAh MR8wHQYDVQQDExZkZWZhdWx0LnpsbWVkaWFraXQuY29tMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEAjTFnNz2btUHR/K8TegU2n06lXsQuW4AfsMGthBLv -nz2zdgL2uVxfRCuleoRpECEV9I80ibTXAJlouRLbnhLHto8gzyLeQue9/DtxTEGc -S7y2aMha0wAoUcOBv7tAITSnFepGHXoYgyU7HOCvn96U1bzqLTOhxOCH/xy003dw -oEC7+PjldsWs/16cpvYiZV/dZvzDDKIpHSIvQy1whi6N0SDpzz/ncThn1z8xcJDx -0I1jVR76juP7TttbyhqJkO+fOlLn4yP9K5wZ/dPFJn2+bQRCrzGc4SM7J5YOq8ck -TTTbZtSp9yPpm7O5QyfdzePhOpRrjWroLsqaDOPV2UQlzwIDAQABo4IC5zCCAuMw -HwYDVR0jBBgwFoAUVXRPsnJP9WC6UNHX5lFcmgGHGtcwHQYDVR0OBBYEFPnRZrfz -q/QAf5u4Xp4eGWvhMdvfMCEGA1UdEQQaMBiCFmRlZmF1bHQuemxtZWRpYWtpdC5j -b20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD -AjA+BgNVHSAENzA1MDMGBmeBDAECATApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3 -LmRpZ2ljZXJ0LmNvbS9DUFMwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY +AQEFAAOCAQ8AMIIBCgKCAQEAtgg0vsIc8bIMb9hAO1Lwq2TVNYMiD928WCVwq7Bj +3B9LM8cifN+mBxHz35SeC9JnQysbKAf8VVfVi7GVzwFq4VaRreoIl8DoMbBORb2i +KxfLXKEHS283bbl17csAYHTTBqFsKnO3kKvYoDHRKFX0T0rzZibI0ACQiylW+ALE +3fVS0hFI8fmoZnIAOyQ15RLp7KPO4bMRVdZaOqRUZeW0YzbvOBeGZKbx3bXiscL9 +RwfGSxlFMCLahaJsBrVDo4/IGDRCmSydK8k/41MHd86pGPzCixBx7cqtKO/6+cVe +EuGxfJPfJVuxNq6vkRSt2HxCc6i2iagA2uL/WYdg3Pa/4wIDAQABo4IC6jCCAuYw +HwYDVR0jBBgwFoAUeN+RkF/u3qz2xXXr1UxVU+8kSrYwHQYDVR0OBBYEFHmEMVp9 +9EHIPWA2U1iLKogCosGFMCEGA1UdEQQaMBiCFmRlZmF1bHQuemxtZWRpYWtpdC5j +b20wPgYDVR0gBDcwNTAzBgZngQwBAgEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3 +dy5kaWdpY2VydC5jb20vQ1BTMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggr +BgEFBQcDAQYIKwYBBQUHAwIwgYAGCCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYY aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEoGCCsGAQUFBzAChj5odHRwOi8vY2Fj -ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcx -LmNydDAJBgNVHRMEAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgDoPtDa -PvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAAAYNQt3JvAAAEAwBHMEUCIEaO -G4ffzzaE6OMqiu6PUr+Y+wO2tsXCkGt1jt04Ix1qAiEAhNZwqFACieds1ZbY3r/p -wlF3iFbhqp+kNfPzon7kwc8AdgA1zxkbv7FsV78PrUxtQsu7ticgJlHqP+Eq76gD -wzvWTAAAAYNQt3JVAAAEAwBHMEUCIBOErqyKvihAEKItLWG/Plgtxh/hCTMsE+t5 -+MfsAQLCAiEA76d50S4iy1wxya+8IUASVlKStaHNqBkJAS+Oadxs2sMAdwCzc3cH -4YRQ+GOG1gWp3BEJSnktsWcMC4fc8AMOeTalmgAAAYNQt3LIAAAEAwBIMEYCIQC/ -kfFCpwF76sw/Qx3sxR8b3srW+Ds0k/6VrIIDZcYV5gIhAKkLmuyeDvzulp0y4f0t -GDgIN/OoURq6CuHA67UJlsWzMA0GCSqGSIb3DQEBCwUAA4IBAQB0BwVxPRihSdPJ -FUPLQ+ClHy9O/UisnRD7NadQQtbcMXn6L9Lwd0f2la0ytLQAKHADOZDA08KfQ5qW -B19OeQOlTwp2nhY2ZvoLEG+paeh0gYxIgD76APnd/m3g2H7GeW144ymjPcZRoldj -ZKYSdzStJJIFYXzL3FR9wjkMc4xOEes/IY5PFtj8OT8CFf7zl0R7L2Vcw9RGYi9u -vLjGwwJW9kXTX8UlKXFyjJN0ZyrmxBQHq5uNtigx8xy6HtMnPsc58tp1IqitIELp -HIur2XrRPBJA5XtpDg3AE8bXhRTM8oFMPL0UoSFWyWRYGgBo1Msc10dpXPtmbgIc -pPW8w+2c +ZXJ0cy5kaWdpY2VydC5jb20vRW5jcnlwdGlvbkV2ZXJ5d2hlcmVEVlRMU0NBLUcy +LmNydDAMBgNVHRMBAf8EAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgDu +zdBk1dsazsVct520zROiModGfLzs3sNRSFlGcR+1mwAAAYravqonAAAEAwBHMEUC +IQDX+gqsd7I0yzjkhgp2YrccUlTx4wkFptFvmQxeChImRgIgJdgJa2Uamd790BCI +/CZwSqmRlor5eU8exAixdcopYpcAdwBIsONr2qZHNA/lagL6nTDrHFIBy1bdLIHZ +u7+rOdiEcwAAAYravqqCAAAEAwBIMEYCIQCP6rkKg2FlF92CyMbVMk3ESh/9gVaM +tRsv5I//i5IVigIhAINHERhy7812wR47fwmvqWDjxyOB1ZodU7WA9D5L/1bVAHYA +2ra/az+1tiKfm8K7XGvocJFxbLtRhIU0vaQ9MEjX+6sAAAGK2r6qQQAABAMARzBF +AiAiz3bp/j4SlnVxKg1HZY+YdUboi+kaKf5G8X6aFLIqUgIhAPPCm5UN05p7Oqrc +sP/wdHDB7O/2AbUksYSLhidmwfmhMA0GCSqGSIb3DQEBCwUAA4IBAQBmaG51jU1E +MsgT1VzutQUXglEvJGVf54cA+0TSfjfnP1n9ALdKjGxHL3KBh4UkPx5zdE5//FUX +dacua6BQEWSCmMtYL0CFieFnLGXh0mgkfvRaP6+3xe6TkJ4kuyJkMS9YMDpVl80F +2GLlE09EsZ3Xk9+SCpmWOPLOCDFURbwpc5ht+acROfzYJQyCY0L8EGbyL5/q9oMn +ugRGh4oyGvXgKvFIPzpZkaOmb0b63/uBc5JkiyQhuFdYaS2cLOwupXmCtIHL4Od6 +OU8/8smT8NEkD7d3lUijtc84q2TihW7ebT7RtOco49PDvFP/7w28QjxM8Ohv9/Gz +Xyta8ICQVwmK -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIQAnmsRYvBskWr+YBTzSybsTANBgkqhkiG9w0BAQsFADBh +MIIEqjCCA5KgAwIBAgIQDeD/te5iy2EQn2CMnO1e0zANBgkqhkiG9w0BAQsFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0xNzExMjcxMjQ2MTBaFw0yNzExMjcxMjQ2MTBaMG4xCzAJBgNVBAYTAlVT +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xNzExMjcxMjQ2NDBaFw0yNzExMjcxMjQ2NDBaMG4xCzAJBgNVBAYTAlVT MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH -MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPeP6wkab41dyQh6mKc -oHqt3jRIxW5MDvf9QyiOR7VfFwK656es0UFiIb74N9pRntzF1UgYzDGu3ppZVMdo -lbxhm6dWS9OK/lFehKNT0OYI9aqk6F+U7cA6jxSC+iDBPXwdF4rs3KRyp3aQn6pj -pp1yr7IB6Y4zv72Ee/PlZ/6rK6InC6WpK0nPVOYR7n9iDuPe1E4IxUMBH/T33+3h -yuH3dvfgiWUOUkjdpMbyxX+XNle5uEIiyBsi4IvbcTCh8ruifCIi5mDXkZrnMT8n -wfYCV6v6kDdXkbgGRLKsR4pucbJtbKqIkUGxuZI2t7pfewKRc5nWecvDBZf3+p1M -pA8CAwEAAaOCAU8wggFLMB0GA1UdDgQWBBRVdE+yck/1YLpQ0dfmUVyaAYca1zAf -BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTAOBgNVHQ8BAf8EBAMCAYYw +MjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO8Uf46i/nr7pkgTDqnE +eSIfCFqvPnUq3aF1tMJ5hh9MnO6Lmt5UdHfBGwC9Si+XjK12cjZgxObsL6Rg1njv +NhAMJ4JunN0JGGRJGSevbJsA3sc68nbPQzuKp5Jc8vpryp2mts38pSCXorPR+sch +QisKA7OSQ1MjcFN0d7tbrceWFNbzgL2csJVQeogOBGSe/KZEIZw6gXLKeFe7mupn +NYJROi2iC11+HuF79iAttMc32Cv6UOxixY/3ZV+LzpLnklFq98XORgwkIJL1HuvP +ha8yvb+W6JislZJL+HLFtidoxmI7Qm3ZyIV66W533DsGFimFJkz3y0GeHWuSVMbI +lfsCAwEAAaOCAU8wggFLMB0GA1UdDgQWBBR435GQX+7erPbFdevVTFVT7yRKtjAf +BgNVHSMEGDAWgBROIlQgGJXm427mD/r6uRLtBhePOTAOBgNVHQ8BAf8EBAMCAYYw HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu -Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG +Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG /WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT -MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAK3Gp6/aGq7aBZsxf/oQ+TD/B -SwW3AU4ETK+GQf2kFzYZkby5SFrHdPomunx2HBzViUchGoofGgg7gHW0W3MlQAXW -M0r5LUvStcr82QDWYNPaUy4taCQmyaJ+VB+6wxHstSigOlSNF2a6vg4rgexixeiV -4YSB03Yqp2t3TeZHM9ESfkus74nQyW7pRGezj+TC44xCagCQQOzzNmzEAP2SnCrJ -sNE2DpRVMnL8J6xBRdjmOsC3N6cQuKuRXbzByVBjCqAA8t1L0I+9wXJerLPyErjy -rMKWaBFLmfK/AHNF4ZihwPGOc7w6UHczBZXH5RFzJNnww+WnKuTPI0HfnVH8lg== ------END CERTIFICATE----- +MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAoBs1eCLKakLtVRPFRjBIJ9LJ +L0s8ZWum8U8/1TMVkQMBn+CPb5xnCD0GSA6L/V0ZFrMNqBirrr5B241OesECvxIi +98bZ90h9+q/X5eMyOD35f8YTaEMpdnQCnawIwiHx06/0BfiTj+b/XQih+mqt3ZXe +xNCJqKexdiB2IWGSKcgahPacWkk/BAQFisKIFYEqHzV974S3FAz/8LIfD58xnsEN +GfzyIDkH3JrwYZ8caPTf6ZX9M1GrISN8HnWTtdNCH2xEajRa/h9ZBXjUyFKQrGk2 +n2hcLrfZSbynEC/pSw/ET7H5nWwckjmAJ1l9fcnbqkU/pf6uMQmnfl0JQjJNSg== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/docker/centos7/Dockerfile.runtime b/docker/centos7/Dockerfile.runtime index 8f588de5..c7d9f990 100644 --- a/docker/centos7/Dockerfile.runtime +++ b/docker/centos7/Dockerfile.runtime @@ -128,4 +128,4 @@ WORKDIR /opt/zlm VOLUME [ "/opt/zlm/conf/","/opt/zlm/log/","opt/zlm/ffmpeg/"] COPY --from=build /opt/build / ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH TZ=Asia/Shanghai -CMD ./MediaServer -c ./conf/config.ini \ No newline at end of file +CMD ["./MediaServer", "-c" , "./conf/config.ini"] \ No newline at end of file diff --git a/docker/ubuntu16.04/Dockerfile.devel b/docker/ubuntu16.04/Dockerfile.devel index 476cca53..65ff0958 100644 --- a/docker/ubuntu16.04/Dockerfile.devel +++ b/docker/ubuntu16.04/Dockerfile.devel @@ -41,4 +41,4 @@ RUN cmake -DCMAKE_BUILD_TYPE=Release .. && \ make ENV PATH /opt/media/ZLMediaKit/release/linux/Release/:$PATH -CMD MediaServer +CMD ["MediaServer"] diff --git a/docker/ubuntu16.04/Dockerfile.runtime b/docker/ubuntu16.04/Dockerfile.runtime index 4b84e017..bc869e92 100644 --- a/docker/ubuntu16.04/Dockerfile.runtime +++ b/docker/ubuntu16.04/Dockerfile.runtime @@ -60,4 +60,4 @@ RUN apt-get update && \ WORKDIR /opt/media/bin/ COPY --from=build /opt/media/ZLMediaKit/release/linux/Release/MediaServer /opt/media/bin/MediaServer ENV PATH /opt/media/bin:$PATH -CMD MediaServer +CMD ["MediaServer"] diff --git a/docker/ubuntu18.04/Dockerfile.devel b/docker/ubuntu18.04/Dockerfile.devel index 05109cee..0a61d86c 100644 --- a/docker/ubuntu18.04/Dockerfile.devel +++ b/docker/ubuntu18.04/Dockerfile.devel @@ -42,4 +42,4 @@ RUN cmake -DCMAKE_BUILD_TYPE=Release .. && \ make ENV PATH /opt/media/ZLMediaKit/release/linux/Release:$PATH -CMD MediaServer +CMD ["MediaServer"] diff --git a/docker/ubuntu18.04/Dockerfile.runtime b/docker/ubuntu18.04/Dockerfile.runtime index ce3b6496..0a1bae65 100644 --- a/docker/ubuntu18.04/Dockerfile.runtime +++ b/docker/ubuntu18.04/Dockerfile.runtime @@ -60,4 +60,4 @@ RUN apt-get update && \ WORKDIR /opt/media/bin/ COPY --from=build /opt/media/ZLMediaKit/release/linux/Release/MediaServer /opt/media/bin/MediaServer ENV PATH /opt/media/bin:$PATH -CMD MediaServer +CMD ["MediaServer"] diff --git a/dockerfile b/dockerfile index 3297b86e..84851901 100644 --- a/dockerfile +++ b/dockerfile @@ -83,4 +83,4 @@ COPY --from=build /opt/media/ZLMediaKit/release/linux/${MODEL}/MediaServer /opt/ COPY --from=build /opt/media/ZLMediaKit/release/linux/${MODEL}/config.ini /opt/media/conf/ COPY --from=build /opt/media/ZLMediaKit/www/ /opt/media/bin/www/ ENV PATH /opt/media/bin:$PATH -CMD ["sh","-c","./MediaServer -s default.pem -c ../conf/config.ini -l 0"] +CMD ["./MediaServer","-s", "default.pem", "-c", "../conf/config.ini", "-l","0"] diff --git a/player/SDLAudioDevice.cpp b/player/SDLAudioDevice.cpp index 41835fb7..76093c89 100644 --- a/player/SDLAudioDevice.cpp +++ b/player/SDLAudioDevice.cpp @@ -33,9 +33,9 @@ SDLAudioDevice::SDLAudioDevice() { SDLAudioDevice *_this = (SDLAudioDevice *) userdata; _this->onReqPCM((char *) stream, len); }; - if (SDL_OpenAudio(&wanted_spec, &_audio_config) < 0) { - throw std::runtime_error("SDL_OpenAudio failed"); - } + if (SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &_audio_config, SDL_AUDIO_ALLOW_ANY_CHANGE) < 0) { + throw std::runtime_error("SDL_OpenAudioDevice failed"); + } InfoL << "actual audioSpec, " << "freq:" << _audio_config.freq << ", format:" << hex << _audio_config.format << dec diff --git a/player/test_player.cpp b/player/test_player.cpp index 126280a0..faf29af7 100644 --- a/player/test_player.cpp +++ b/player/test_player.cpp @@ -56,8 +56,7 @@ int main(int argc, char *argv[]) { if (argc < 3) { ErrorL << "\r\n测试方法:./test_player rtxp_url rtp_type\r\n" - << "例如:./test_player rtsp://admin:123456@127.0.0.1/live/0 0\r\n" - << endl; + << "例如:./test_player rtsp://admin:123456@127.0.0.1/live/0 0\r\n"; return 0; } diff --git a/postman/ZLMediaKit.postman_collection.json b/postman/ZLMediaKit.postman_collection.json index ff4305ce..fdda9d2a 100644 --- a/postman/ZLMediaKit.postman_collection.json +++ b/postman/ZLMediaKit.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "4626d766-16b5-4255-89ba-f7614de2398c", + "_postman_id": "39e8a1df-cc8e-4e3f-bf5e-197c86e7bf0f", "name": "ZLMediaKit", "description": "媒体服务器", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" @@ -25,7 +25,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -51,7 +51,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -77,7 +77,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -103,7 +103,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -129,7 +129,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -155,7 +155,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "api.apiDebug", @@ -186,7 +186,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" } ] } @@ -212,7 +212,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -262,7 +262,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -314,7 +314,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -366,7 +366,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "local_port", @@ -404,7 +404,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "id", @@ -435,7 +435,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "local_port", @@ -473,7 +473,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "vhost", @@ -516,7 +516,13 @@ { "key": "enable_hls", "value": null, - "description": "是否转hls", + "description": "是否转hls-ts", + "disabled": true + }, + { + "key": "enable_hls_fmp4", + "value": null, + "description": "是否转hls-fmp4", "disabled": true }, { @@ -582,7 +588,13 @@ { "key": "modify_stamp", "value": null, - "description": "是否重新计算时间戳", + "description": "是否修改原始时间戳,默认值2;取值范围:0.采用源视频流绝对时间戳,不做任何改变;1.采用zlmediakit接收数据时的系统时间戳(有平滑处理);2.采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正", + "disabled": true + }, + { + "key": "auto_close", + "value": null, + "description": "无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close)", "disabled": true } ] @@ -609,7 +621,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "key", @@ -627,7 +639,7 @@ "method": "GET", "header": [], "url": { - "raw": "{{ZLMediaKit_URL}}/index/api/addStreamPusherProxy?secret={{ZLMediaKit_secret}}&schema=rtmp&vhost={{defaultVhost}}&app=live&stream=test&dst_url=rtmp://127.0.0.1/live/push", + "raw": "{{ZLMediaKit_URL}}/index/api/addStreamPusherProxy?secret={{ZLMediaKit_secret}}&schema=rtmp&vhost={{defaultVhost}}&app=live&stream=test&dst_url=rtmp://192.168.1.104/live/push", "host": [ "{{ZLMediaKit_URL}}" ], @@ -640,7 +652,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "schema", @@ -664,7 +676,7 @@ }, { "key": "dst_url", - "value": "rtmp://127.0.0.1/live/push", + "value": "rtmp://192.168.1.104/live/push", "description": "推流地址,需要与schema字段协议一致" }, { @@ -696,7 +708,7 @@ "method": "GET", "header": [], "url": { - "raw": "{{ZLMediaKit_URL}}/index/api/delStreamPusherProxy?secret={{ZLMediaKit_secret}}&key=__defaultVhost__/live/test", + "raw": "{{ZLMediaKit_URL}}/index/api/delStreamPusherProxy?secret={{ZLMediaKit_secret}}&key=rtmp/__defaultVhost__/live/test/f40a8ab006cac16ecc0858409e890491", "host": [ "{{ZLMediaKit_URL}}" ], @@ -709,11 +721,11 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "key", - "value": "__defaultVhost__/live/test", + "value": "rtmp/__defaultVhost__/live/test/f40a8ab006cac16ecc0858409e890491", "description": "addStreamPusherProxy接口返回的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", @@ -1363,7 +1425,7 @@ "method": "GET", "header": [], "url": { - "raw": "{{ZLMediaKit_URL}}/index/api/openRtpServer?secret={{ZLMediaKit_secret}}&port=0&enable_tcp=1&stream_id=test", + "raw": "{{ZLMediaKit_URL}}/index/api/openRtpServer?secret={{ZLMediaKit_secret}}&port=0&tcp_mode=1&stream_id=test", "host": [ "{{ZLMediaKit_URL}}" ], @@ -1376,7 +1438,7 @@ { "key": "secret", "value": "{{ZLMediaKit_secret}}", - "description": "api操作密钥(配置文件配置),如果操作ip是127.0.0.1,则不需要此参数" + "description": "api操作密钥(配置文件配置)" }, { "key": "port", @@ -1422,7 +1484,7 @@ "method": "GET", "header": [], "url": { - "raw": "{{ZLMediaKit_URL}}/index/api/connectRtpServer?secret={{ZLMediaKit_secret}}&dst_url=127.0.0.1&dst_port=10000&stream_id=test", + "raw": "{{ZLMediaKit_URL}}/index/api/connectRtpServer?secret={{ZLMediaKit_secret}}&dst_url=0&dst_port=1&stream_id=test", "host": [ "{{ZLMediaKit_URL}}" ], @@ -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", @@ -1874,6 +1936,64 @@ } }, "response": [] + }, + { + "name": "获取拉流代理信息(getProxyInfo)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{ZLMediaKit_URL}}/index/api/getProxyInfo?secret={{ZLMediaKit_secret}}&key=__defaultVhost__/live/test", + "host": [ + "{{ZLMediaKit_URL}}" + ], + "path": [ + "index", + "api", + "getProxyInfo" + ], + "query": [ + { + "key": "secret", + "value": "{{ZLMediaKit_secret}}" + }, + { + "key": "key", + "value": "__defaultVhost__/live/test" + } + ] + } + }, + "response": [] + }, + { + "name": "获取推流代理信息(getProxyPusherInfo)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{ZLMediaKit_URL}}/index/api/getProxyPusherInfo?secret={{ZLMediaKit_secret}}&key=rtmp/__defaultVhost__/live/test/f40a8ab006cac16ecc0858409e890491", + "host": [ + "{{ZLMediaKit_URL}}" + ], + "path": [ + "index", + "api", + "getProxyPusherInfo" + ], + "query": [ + { + "key": "secret", + "value": "{{ZLMediaKit_secret}}" + }, + { + "key": "key", + "value": "rtmp/__defaultVhost__/live/test/f40a8ab006cac16ecc0858409e890491" + } + ] + } + }, + "response": [] } ], "event": [ diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index f0dea82b..63abc05d 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -34,11 +34,17 @@ if(ENABLE_SERVER_LIB) PRIVATE ${COMPILE_OPTIONS_DEFAULT}) target_link_libraries(MediaServer PRIVATE ${MK_LINK_LIBRARIES}) - update_cached(MK_LINK_LIBRARIES MediaServer) + update_cached_list(MK_LINK_LIBRARIES MediaServer) return() endif() -add_executable(MediaServer ${MediaServer_SRC_LIST}) +# IOS 不编译可执行程序,只做依赖库 +if(IOS) + add_library(MediaServer STATIC ${MediaServer_SRC_LIST}) +else() + add_executable(MediaServer ${MediaServer_SRC_LIST}) +endif() + target_compile_definitions(MediaServer PRIVATE ${COMPILE_DEFINITIONS}) target_compile_options(MediaServer diff --git a/server/FFmpegSource.cpp b/server/FFmpegSource.cpp index 48fb2cd5..8363950c 100644 --- a/server/FFmpegSource.cpp +++ b/server/FFmpegSource.cpp @@ -11,6 +11,7 @@ #include "FFmpegSource.h" #include "Common/config.h" #include "Common/MediaSource.h" +#include "Common/MultiMediaSourceMuxer.h" #include "Util/File.h" #include "System.h" #include "Thread/WorkThreadPool.h" @@ -39,7 +40,7 @@ onceToken token([]() { //ffmpeg日志保存路径 mINI::Instance()[kLog] = "./ffmpeg/ffmpeg.log"; mINI::Instance()[kCmd] = "%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s"; - mINI::Instance()[kSnap] = "%s -i %s -y -f mjpeg -t 0.001 %s"; + mINI::Instance()[kSnap] = "%s -i %s -y -f mjpeg -frames:v 1 %s"; mINI::Instance()[kRestartSec] = 0; }); } @@ -70,10 +71,10 @@ void FFmpegSource::setupRecordFlag(bool enable_hls, bool enable_mp4){ _enable_mp4 = enable_mp4; } -void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,const string &dst_url,int timeout_ms,const onPlay &cb) { - GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin); - GET_CONFIG(string,ffmpeg_cmd_default,FFmpeg::kCmd); - GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog); +void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url, const string &dst_url, int timeout_ms, const onPlay &cb) { + GET_CONFIG(string, ffmpeg_bin, FFmpeg::kBin); + GET_CONFIG(string, ffmpeg_cmd_default, FFmpeg::kCmd); + GET_CONFIG(string, ffmpeg_log, FFmpeg::kLog); _src_url = src_url; _dst_url = dst_url; @@ -91,122 +92,114 @@ void FFmpegSource::play(const string &ffmpeg_cmd_key, const string &src_url,cons auto cmd_it = mINI::Instance().find(ffmpeg_cmd_key); if (cmd_it != mINI::Instance().end()) { ffmpeg_cmd = cmd_it->second; - } else{ + } else { WarnL << "配置文件中,ffmpeg命令模板(" << ffmpeg_cmd_key << ")不存在,已采用默认模板(" << ffmpeg_cmd_default << ")"; } } - char cmd[2048] = {0}; + char cmd[2048] = { 0 }; snprintf(cmd, sizeof(cmd), ffmpeg_cmd.data(), File::absolutePath("", ffmpeg_bin).data(), src_url.data(), dst_url.data()); auto log_file = ffmpeg_log.empty() ? "" : File::absolutePath("", ffmpeg_log); _process.run(cmd, log_file); InfoL << cmd; - if (is_local_ip(_media_info._host)) { - //推流给自己的,通过判断流是否注册上来判断是否正常 - if(_media_info._schema != RTSP_SCHEMA && _media_info._schema != RTMP_SCHEMA){ - cb(SockException(Err_other,"本服务只支持rtmp/rtsp推流")); + if (is_local_ip(_media_info.host)) { + // 推流给自己的,通过判断流是否注册上来判断是否正常 + if (_media_info.schema != RTSP_SCHEMA && _media_info.schema != RTMP_SCHEMA) { + 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._streamid); - if(src || !maxWaitMS){ + auto src = MediaSource::find(_media_info.schema, _media_info.vhost, _media_info.app, _media_info.stream); + if (src || !maxWaitMS) { cb(src); return; } void *listener_tag = this; - //若干秒后执行等待媒体注册超时回调 - auto onRegistTimeout = _poller->doDelayTask(maxWaitMS,[cb,listener_tag](){ - //取消监听该事件 - NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged); + // 若干秒后执行等待媒体注册超时回调 + auto onRegistTimeout = _poller->doDelayTask(maxWaitMS, [cb, listener_tag]() { + // 取消监听该事件 + NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged); cb(nullptr); return 0; }); weak_ptr weakSelf = shared_from_this(); - auto onRegist = [listener_tag,weakSelf,cb,onRegistTimeout](BroadcastMediaChangedArgs) { + auto onRegist = [listener_tag, weakSelf, cb, onRegistTimeout](BroadcastMediaChangedArgs) { auto strongSelf = weakSelf.lock(); - if(!strongSelf) { - //本身已经销毁,取消延时任务 + if (!strongSelf) { + // 本身已经销毁,取消延时任务 onRegistTimeout->cancel(); - NoticeCenter::Instance().delListener(listener_tag,Broadcast::kBroadcastMediaChanged); + NoticeCenter::Instance().delListener(listener_tag, Broadcast::kBroadcastMediaChanged); return; } - if (!bRegist || - sender.getSchema() != strongSelf->_media_info._schema || - sender.getVhost() != strongSelf->_media_info._vhost || - sender.getApp() != strongSelf->_media_info._app || - sender.getId() != strongSelf->_media_info._streamid) { - //不是自己感兴趣的事件,忽略之 + 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); } @@ -223,50 +216,50 @@ void FFmpegSource::startTimer(int timeout_ms) { return false; } bool needRestart = ffmpeg_restart_sec > 0 && strongSelf->_replay_ticker.elapsedTime() > ffmpeg_restart_sec * 1000; - if (is_local_ip(strongSelf->_media_info._host)) { - //推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常 + if (is_local_ip(strongSelf->_media_info.host)) { + // 推流给自己的,我们通过检查是否已经注册来判断FFmpeg是否工作正常 strongSelf->findAsync(0, [&](const MediaSource::Ptr &src) { - //同步查找流 + // 同步查找流 if (!src || needRestart) { - if(needRestart){ + if (needRestart) { strongSelf->_replay_ticker.resetTime(); - if(strongSelf->_process.wait(false)){ - //FFmpeg进程还在运行,超时就关闭它 + if (strongSelf->_process.wait(false)) { + // FFmpeg进程还在运行,超时就关闭它 strongSelf->_process.kill(2000); } InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url; } - //流不在线,重新拉流, 这里原先是10秒超时,实际发现10秒不够,改成20秒了 - if(strongSelf->_replay_ticker.elapsedTime() > 20 * 1000){ - //上次重试时间超过10秒,那么再重试FFmpeg拉流 + // 流不在线,重新拉流, 这里原先是10秒超时,实际发现10秒不够,改成20秒了 + if (strongSelf->_replay_ticker.elapsedTime() > 20 * 1000) { + // 上次重试时间超过10秒,那么再重试FFmpeg拉流 strongSelf->_replay_ticker.resetTime(); strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [](const SockException &) {}); } } }); } else { - //推流给其他服务器的,我们通过判断FFmpeg进程是否在线,如果FFmpeg推流中断,那么它应该会自动退出 + // 推流给其他服务器的,我们通过判断FFmpeg进程是否在线,如果FFmpeg推流中断,那么它应该会自动退出 if (!strongSelf->_process.wait(false) || needRestart) { - if(needRestart){ + if (needRestart) { strongSelf->_replay_ticker.resetTime(); - if(strongSelf->_process.wait(false)){ - //FFmpeg进程还在运行,超时就关闭它 + if (strongSelf->_process.wait(false)) { + // FFmpeg进程还在运行,超时就关闭它 strongSelf->_process.kill(2000); } InfoL << "FFmpeg即将重启, 将会继续拉流 " << strongSelf->_src_url; } - //ffmpeg不在线,重新拉流 + // ffmpeg不在线,重新拉流 strongSelf->play(strongSelf->_ffmpeg_cmd_key, strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [weakSelf](const SockException &ex) { - if(!ex){ - //没有错误 + if (!ex) { + // 没有错误 return; } auto strongSelf = weakSelf.lock(); if (!strongSelf) { - //自身已经销毁 + // 自身已经销毁 return; } - //上次重试时间超过10秒,那么再重试FFmpeg拉流 + // 上次重试时间超过10秒,那么再重试FFmpeg拉流 strongSelf->startTimer(10 * 1000); }); } @@ -296,20 +289,17 @@ MediaOriginType FFmpegSource::getOriginType(MediaSource &sender) const{ return MediaOriginType::ffmpeg_pull; } -string FFmpegSource::getOriginUrl(MediaSource &sender) const{ +string FFmpegSource::getOriginUrl(MediaSource &sender) const { return _src_url; } -std::shared_ptr FFmpegSource::getOriginSock(MediaSource &sender) const { - return nullptr; -} - void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) { - auto listener = src->getListener(true); - if (listener.lock().get() != this) { + auto muxer = src->getMuxer(); + auto listener = muxer ? muxer->getDelegate() : nullptr; + if (listener && listener.get() != this) { //防止多次进入onGetMediaSource函数导致无限递归调用的bug setDelegate(listener); - src->setListener(shared_from_this()); + muxer->setDelegate(shared_from_this()); if (_enable_hls) { src->setupRecord(Recorder::type_hls, true, "", 0); } @@ -320,14 +310,14 @@ void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) { } void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float timeout_sec, const onSnap &cb) { - GET_CONFIG(string,ffmpeg_bin,FFmpeg::kBin); - GET_CONFIG(string,ffmpeg_snap,FFmpeg::kSnap); - GET_CONFIG(string,ffmpeg_log,FFmpeg::kLog); + GET_CONFIG(string, ffmpeg_bin, FFmpeg::kBin); + GET_CONFIG(string, ffmpeg_snap, FFmpeg::kSnap); + GET_CONFIG(string, ffmpeg_log, FFmpeg::kLog); Ticker ticker; - WorkThreadPool::Instance().getPoller()->async([timeout_sec, play_url,save_path,cb, ticker](){ + WorkThreadPool::Instance().getPoller()->async([timeout_sec, play_url, save_path, cb, ticker]() { auto elapsed_ms = ticker.elapsedTime(); if (elapsed_ms > timeout_sec * 1000) { - //超时,后台线程负载太高,当代太久才启动该任务 + // 超时,后台线程负载太高,当代太久才启动该任务 cb(false, "wait work poller schedule snap task timeout"); return; } @@ -348,13 +338,12 @@ void FFmpegSnap::makeSnap(const string &play_url, const string &save_path, float return 0; }); - //等待FFmpeg进程退出 + // 等待FFmpeg进程退出 process->wait(true); // FFmpeg进程退出了可以取消定时器了 delayTask->cancel(); - //执行回调函数 + // 执行回调函数 bool success = process->exit_code() == 0 && File::fileSize(save_path.data()); cb(success, (!success && !log_file.empty()) ? File::loadFile(log_file.data()) : ""); }); } - diff --git a/server/FFmpegSource.h b/server/FFmpegSource.h index e85df3ec..8efb7ced 100644 --- a/server/FFmpegSource.h +++ b/server/FFmpegSource.h @@ -20,6 +20,7 @@ namespace FFmpeg { extern const std::string kSnap; + extern const std::string kBin; } class FFmpegSnap { @@ -79,8 +80,6 @@ private: mediakit::MediaOriginType getOriginType(mediakit::MediaSource &sender) const override; //获取媒体源url或者文件路径 std::string getOriginUrl(mediakit::MediaSource &sender) const override; - // 获取媒体源客户端相关信息 - std::shared_ptr getOriginSock(mediakit::MediaSource &sender) const override; private: bool _enable_hls = false; diff --git a/server/Process.cpp b/server/Process.cpp index 7ddebb08..140282d9 100644 --- a/server/Process.cpp +++ b/server/Process.cpp @@ -108,7 +108,7 @@ static int cloneFunc(void *ptr) { #endif -void Process::run(const string &cmd, string &log_file) { +void Process::run(const string &cmd, string log_file) { kill(2000); #ifdef _WIN32 STARTUPINFO si = { 0 }; diff --git a/server/Process.h b/server/Process.h index d514edb5..06f55345 100644 --- a/server/Process.h +++ b/server/Process.h @@ -26,7 +26,7 @@ class Process { public: Process(); ~Process(); - void run(const std::string &cmd, std::string &log_file); + void run(const std::string &cmd, std::string log_file); void kill(int max_delay,bool force = false); bool wait(bool block = true); int exit_code(); diff --git a/server/System.cpp b/server/System.cpp index aae4c792..c06d34e1 100644 --- a/server/System.cpp +++ b/server/System.cpp @@ -22,10 +22,11 @@ #include #include +#include "Common/JemallocUtil.h" +#include "Common/macros.h" +#include "System.h" #include "Util/logger.h" #include "Util/uv_errno.h" -#include "System.h" -#include "Common/macros.h" using namespace std; using namespace toolkit; @@ -55,6 +56,16 @@ string System::execute(const string &cmd) { static constexpr int MAX_STACK_FRAMES = 128; +static void save_jemalloc_stats() { + string jemalloc_status = JemallocUtil::get_malloc_stats(); + if (jemalloc_status.empty()) { + return; + } + ofstream out(StrPrinter << exeDir() << "/jemalloc.json", ios::out | ios::binary | ios::trunc); + out << jemalloc_status; + out.flush(); +} + static void sig_crash(int sig) { signal(sig, SIG_DFL); void *array[MAX_STACK_FRAMES]; @@ -126,6 +137,12 @@ void System::startDaemon(bool &kill_parent_if_failed) { exit(0); }); + signal(SIGTERM,[](int) { + WarnL << "收到主动退出信号,关闭父进程与子进程"; + kill(pid, SIGINT); + exit(0); + }); + do { int status = 0; if (waitpid(pid, &status, 0) >= 0) { @@ -143,6 +160,12 @@ void System::startDaemon(bool &kill_parent_if_failed) { } void System::systemSetup(){ + +#ifdef ENABLE_JEMALLOC_DUMP + //Save memory report when program exits + atexit(save_jemalloc_stats); +#endif //ENABLE_JEMALLOC_DUMP + #if !defined(_WIN32) struct rlimit rlim,rlim_new; if (getrlimit(RLIMIT_CORE, &rlim)==0) { diff --git a/server/WebApi.cpp b/server/WebApi.cpp index aea85a6c..e8081756 100755 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -130,7 +130,7 @@ static HttpApi toApi(const function &cb) { //参数解析成json对象然后处理 Json::Value args; Json::Reader reader; - reader.parse(parser.Content(), args); + reader.parse(parser.content(), args); cb(sender, headerOut, HttpAllArgs(parser, args), val, invoker); }; @@ -152,7 +152,7 @@ static HttpApi toApi(const function &cb) { Json::Value val; val["code"] = API::Success; - cb(sender, headerOut, HttpAllArgs(parser, (string &)parser.Content()), val, invoker); + cb(sender, headerOut, HttpAllArgs(parser, (string &)parser.content()), val, invoker); }; } @@ -191,13 +191,13 @@ void api_regist(const string &api_path, const function> jsonArgs; auto keys = jsonArgs.getMemberNames(); @@ -231,7 +231,7 @@ static inline void addHttpListener(){ GET_CONFIG(bool, api_debug, API::kApiDebug); //注册监听kBroadcastHttpRequest事件 NoticeCenter::Instance().addListener(&web_api_tag, Broadcast::kBroadcastHttpRequest, [](BroadcastHttpRequestArgs) { - auto it = s_map_api.find(parser.Url()); + auto it = s_map_api.find(parser.url()); if (it == s_map_api.end()) { return; } @@ -247,15 +247,15 @@ static inline void addHttpListener(){ size = body->remainSize(); } - LogContextCapture log(getLogger(), toolkit::LTrace, __FILE__, "http api debug", __LINE__); - log << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n"; + LogContextCapture log(getLogger(), toolkit::LDebug, __FILE__, "http api debug", __LINE__); + log << "\r\n# request:\r\n" << parser.method() << " " << parser.fullUrl() << "\r\n"; log << "# header:\r\n"; for (auto &pr : parser.getHeader()) { log << pr.first << " : " << pr.second << "\r\n"; } - auto &content = parser.Content(); + auto &content = parser.content(); log << "# content:\r\n" << (content.size() > 4 * 1024 ? content.substr(0, 4 * 1024) : content) << "\r\n"; if (size > 0 && size < 4 * 1024) { @@ -321,12 +321,16 @@ static void fillSockInfo(Value& val, SockInfo* info) { val["identifier"] = info->getIdentifier(); } +void dumpMediaTuple(const MediaTuple &tuple, Json::Value& item) { + item[VHOST_KEY] = tuple.vhost; + item["app"] = tuple.app; + item["stream"] = tuple.stream; +} + Value makeMediaSourceJson(MediaSource &media){ Value item; item["schema"] = media.getSchema(); - item[VHOST_KEY] = media.getVhost(); - item["app"] = media.getApp(); - item["stream"] = media.getId(); + dumpMediaTuple(media.getMediaTuple(), item); item["createStamp"] = (Json::UInt64) media.getCreateStamp(); item["aliveSecond"] = (Json::UInt64) media.getAliveSecond(); item["bytesSpeed"] = media.getBytesSpeed(); @@ -533,7 +537,7 @@ void addStreamProxy(const string &vhost, const string &app, const string &stream lock_guard lck(s_proxyMapMtx); if (s_proxyMap.find(key) != s_proxyMap.end()) { //已经在拉流了 - cb(SockException(Err_success), key); + cb(SockException(Err_other, "This stream already exists"), key); return; } //添加拉流代理 @@ -584,7 +588,8 @@ void installWebApi() { //获取线程负载 //测试url http://127.0.0.1/index/api/getThreadsLoad - api_regist("/index/api/getThreadsLoad",[](API_ARGS_MAP_ASYNC){ + api_regist("/index/api/getThreadsLoad", [](API_ARGS_MAP_ASYNC) { + CHECK_SECRET(); EventPollerPool::Instance().getExecutorDelay([invoker, headerOut](const vector &vecDelay) { Value val; auto vec = EventPollerPool::Instance().getExecutorLoad(); @@ -602,7 +607,8 @@ void installWebApi() { //获取后台工作线程负载 //测试url http://127.0.0.1/index/api/getWorkThreadsLoad - api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC){ + api_regist("/index/api/getWorkThreadsLoad", [](API_ARGS_MAP_ASYNC) { + CHECK_SECRET(); WorkThreadPool::Instance().getExecutorDelay([invoker, headerOut](const vector &vecDelay) { Value val; auto vec = WorkThreadPool::Instance().getExecutorLoad(); @@ -648,6 +654,10 @@ void installWebApi() { continue; #endif } + if (pr.first == FFmpeg::kBin) { + WarnL << "Configuration named " << FFmpeg::kBin << " is not allowed to be set by setServerConfig api."; + continue; + } if (ini[pr.first] == pr.second) { continue; } @@ -656,7 +666,7 @@ void installWebApi() { ++changed; } if (changed > 0) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig); + NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig); ini.dumpFile(g_ini_file); } val["changed"] = changed; @@ -785,25 +795,40 @@ void installWebApi() { throw ApiRetException("can not find the stream", API::NotFound); } src->getPlayerList( - [=](const std::list> &info_list) mutable { + [=](const std::list &info_list) mutable { val["code"] = API::Success; auto &data = val["data"]; data = Value(arrayValue); for (auto &info : info_list) { - auto obj = static_pointer_cast(info); - data.append(std::move(*obj)); + auto &obj = info.get(); + data.append(std::move(obj)); } invoker(200, headerOut, val.toStyledString()); }, - [](std::shared_ptr &&info) -> std::shared_ptr { + [](toolkit::Any &&info) -> toolkit::Any { auto obj = std::make_shared(); - auto session = static_pointer_cast(info); - fillSockInfo(*obj, session.get()); - (*obj)["typeid"] = toolkit::demangle(typeid(*session).name()); - return obj; + auto &sock = info.get(); + fillSockInfo(*obj, &sock); + (*obj)["typeid"] = toolkit::demangle(typeid(sock).name()); + toolkit::Any ret; + ret.set(obj); + return ret; }); }); + api_regist("/index/api/broadcastMessage", [](API_ARGS_MAP) { + CHECK_SECRET(); + CHECK_ARGS("schema", "vhost", "app", "stream", "msg"); + auto src = MediaSource::find(allArgs["schema"], allArgs["vhost"], allArgs["app"], allArgs["stream"]); + if (!src) { + throw ApiRetException("can not find the stream", API::NotFound); + } + Any any; + Buffer::Ptr buffer = std::make_shared(allArgs["msg"]); + any.set(std::move(buffer)); + src->broadcastMessage(any); + }); + //测试url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs api_regist("/index/api/getMediaInfo",[](API_ARGS_MAP_ASYNC){ CHECK_SECRET(); @@ -1420,12 +1445,55 @@ void installWebApi() { }); }); + api_regist("/index/api/getProxyPusherInfo", [](API_ARGS_MAP_ASYNC) { + CHECK_SECRET(); + CHECK_ARGS("key"); + decltype(s_proxyPusherMap.end()) it; + { + lock_guard lck(s_proxyPusherMapMtx); + it = s_proxyPusherMap.find(allArgs["key"]); + } + + if (it == s_proxyPusherMap.end()) { + throw ApiRetException("can not find pusher", API::NotFound); + } + + auto pusher = it->second; + + val["data"]["status"] = pusher->getStatus(); + val["data"]["liveSecs"] = pusher->getLiveSecs(); + val["data"]["rePublishCount"] = pusher->getRePublishCount(); + invoker(200, headerOut, val.toStyledString()); + }); + + api_regist("/index/api/getProxyInfo", [](API_ARGS_MAP_ASYNC) { + CHECK_SECRET(); + CHECK_ARGS("key"); + decltype(s_proxyMap.end()) it; + { + lock_guard lck(s_proxyMapMtx); + it = s_proxyMap.find(allArgs["key"]); + } + + if (it == s_proxyMap.end()) { + throw ApiRetException("can not find the proxy", API::NotFound); + } + + auto proxy = it->second; + + val["data"]["status"] = proxy->getStatus(); + val["data"]["liveSecs"] = proxy->getLiveSecs(); + val["data"]["rePullCount"] = proxy->getRePullCount(); + invoker(200, headerOut, val.toStyledString()); + }); + // 删除录像文件夹 // http://127.0.0.1/index/api/deleteRecordDirectroy?vhost=__defaultVhost__&app=live&stream=ss&period=2020-01-01 api_regist("/index/api/deleteRecordDirectory", [](API_ARGS_MAP) { CHECK_SECRET(); CHECK_ARGS("vhost", "app", "stream"); - auto record_path = Recorder::getRecordPath(Recorder::type_mp4, allArgs["vhost"], allArgs["app"], allArgs["stream"], allArgs["customized_path"]); + auto tuple = MediaTuple{allArgs["vhost"], allArgs["app"], allArgs["stream"]}; + auto record_path = Recorder::getRecordPath(Recorder::type_mp4, tuple, allArgs["customized_path"]); auto period = allArgs["period"]; record_path = record_path + period + "/"; int result = File::delete_file(record_path.data()); @@ -1442,7 +1510,8 @@ void installWebApi() { api_regist("/index/api/getMp4RecordFile", [](API_ARGS_MAP){ CHECK_SECRET(); CHECK_ARGS("vhost", "app", "stream"); - auto record_path = Recorder::getRecordPath(Recorder::type_mp4, allArgs["vhost"], allArgs["app"], allArgs["stream"], allArgs["customized_path"]); + auto tuple = MediaTuple{allArgs["vhost"], allArgs["app"], allArgs["stream"]}; + auto record_path = Recorder::getRecordPath(Recorder::type_mp4, tuple, allArgs["customized_path"]); auto period = allArgs["period"]; //判断是获取mp4文件列表还是获取文件夹列表 @@ -1519,7 +1588,7 @@ void installWebApi() { } //找到截图 - auto tm = FindField(path.data() + scan_path.size(), nullptr, ".jpeg"); + auto tm = findSubString(path.data() + scan_path.size(), nullptr, ".jpeg"); if (atoll(tm.data()) + expire_sec < time(NULL)) { //截图已经过期,改名,以便再次请求时,可以返回老截图 rename(path.data(), new_snap.data()); @@ -1593,7 +1662,7 @@ void installWebApi() { CHECK_ARGS("app", "stream"); return StrPrinter << RTC_SCHEMA << "://" << _args["Host"] << "/" << _args["app"] << "/" - << _args["stream"] << "?" << _args.getParser().Params() + "&session=" + _session_id; + << _args["stream"] << "?" << _args.getParser().params() + "&session=" + _session_id; } private: @@ -1628,18 +1697,21 @@ void installWebApi() { }); }); + static constexpr char delete_webrtc_url [] = "/index/api/delete_webrtc"; static auto whip_whep_func = [](const char *type, API_ARGS_STRING_ASYNC) { auto offer = allArgs.getArgs(); CHECK(!offer.empty(), "http body(webrtc offer sdp) is empty"); - WebRtcPluginManager::Instance().getAnswerSdp(static_cast(sender), type, - WebRtcArgsImp(allArgs, sender.getIdentifier()), - [invoker, offer, headerOut](const WebRtcInterface &exchanger) mutable { + auto &session = static_cast(sender); + auto location = std::string("http") + (session.overSsl() ? "s" : "") + "://" + allArgs["host"] + delete_webrtc_url; + WebRtcPluginManager::Instance().getAnswerSdp(session, type, WebRtcArgsImp(allArgs, sender.getIdentifier()), + [invoker, offer, headerOut, location](const WebRtcInterface &exchanger) mutable { // 设置跨域 headerOut["Access-Control-Allow-Origin"] = "*"; try { // 设置返回类型 headerOut["Content-Type"] = "application/sdp"; + headerOut["Location"] = location + "?id=" + exchanger.getIdentifier() + "&token=" + exchanger.deleteRandStr(); invoker(201, headerOut, exchangeSdp(exchanger, offer)); } catch (std::exception &ex) { headerOut["Content-Type"] = "text/plain"; @@ -1650,6 +1722,22 @@ void installWebApi() { api_regist("/index/api/whip", [](API_ARGS_STRING_ASYNC) { whip_whep_func("push", API_ARGS_VALUE, invoker); }); api_regist("/index/api/whep", [](API_ARGS_STRING_ASYNC) { whip_whep_func("play", API_ARGS_VALUE, invoker); }); + + api_regist(delete_webrtc_url, [](API_ARGS_MAP_ASYNC) { + CHECK_ARGS("id", "token"); + CHECK(allArgs.getParser().method() == "DELETE", "http method is not DELETE: " + allArgs.getParser().method()); + auto obj = WebRtcTransportManager::Instance().getItem(allArgs["id"]); + if (!obj) { + invoker(404, headerOut, "id not found"); + return; + } + if (obj->deleteRandStr() != allArgs["token"]) { + invoker(401, headerOut, "token incorrect"); + return; + } + obj->safeShutdown(SockException(Err_shutdown, "deleted by http api")); + invoker(200, headerOut, ""); + }); #endif #if defined(ENABLE_VERSION) diff --git a/server/WebApi.h b/server/WebApi.h index aaf0b19e..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,21 +221,29 @@ 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(); -uint16_t openRtpServer(uint16_t local_port, const std::string &stream_id, int tcp_mode, const std::string &local_ip, bool re_use_port, uint32_t ssrc); +#if defined(ENABLE_RTPPROXY) +uint16_t openRtpServer(uint16_t local_port, const std::string &stream_id, int tcp_mode, const std::string &local_ip, bool re_use_port, uint32_t ssrc, bool only_audio); void connectRtpServer(const std::string &stream_id, const std::string &dst_url, uint16_t dst_port, const std::function &cb); bool closeRtpServer(const std::string &stream_id); +#endif + Json::Value makeMediaSourceJson(mediakit::MediaSource &media); void getStatisticJson(const std::function &cb); void addStreamProxy(const std::string &vhost, const std::string &app, const std::string &stream, const std::string &url, int retry_count, diff --git a/server/WebHook.cpp b/server/WebHook.cpp index 35c78957..d05c4f60 100755 --- a/server/WebHook.cpp +++ b/server/WebHook.cpp @@ -37,6 +37,7 @@ const string kOnFlowReport = HOOK_FIELD "on_flow_report"; const string kOnRtspRealm = HOOK_FIELD "on_rtsp_realm"; const string kOnRtspAuth = HOOK_FIELD "on_rtsp_auth"; const string kOnStreamChanged = HOOK_FIELD "on_stream_changed"; +const string kStreamChangedSchemas = HOOK_FIELD "stream_changed_schemas"; const string kOnStreamNotFound = HOOK_FIELD "on_stream_not_found"; const string kOnRecordMp4 = HOOK_FIELD "on_record_mp4"; const string kOnRecordTs = HOOK_FIELD "on_record_ts"; @@ -44,10 +45,10 @@ const string kOnShellLogin = HOOK_FIELD "on_shell_login"; const string kOnStreamNoneReader = HOOK_FIELD "on_stream_none_reader"; const string kOnHttpAccess = HOOK_FIELD "on_http_access"; const string kOnServerStarted = HOOK_FIELD "on_server_started"; +const string kOnServerExited = HOOK_FIELD "on_server_exited"; const string kOnServerKeepalive = HOOK_FIELD "on_server_keepalive"; const string kOnSendRtpStopped = HOOK_FIELD "on_send_rtp_stopped"; const string kOnRtpServerTimeout = HOOK_FIELD "on_rtp_server_timeout"; -const string kAdminParams = HOOK_FIELD "admin_params"; const string kAliveInterval = HOOK_FIELD "alive_interval"; const string kRetry = HOOK_FIELD "retry"; const string kRetryDelay = HOOK_FIELD "retry_delay"; @@ -69,13 +70,14 @@ static onceToken token([]() { mINI::Instance()[kOnStreamNoneReader] = ""; mINI::Instance()[kOnHttpAccess] = ""; mINI::Instance()[kOnServerStarted] = ""; + mINI::Instance()[kOnServerExited] = ""; mINI::Instance()[kOnServerKeepalive] = ""; mINI::Instance()[kOnSendRtpStopped] = ""; mINI::Instance()[kOnRtpServerTimeout] = ""; - mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc"; mINI::Instance()[kAliveInterval] = 30.0; mINI::Instance()[kRetry] = 1; mINI::Instance()[kRetryDelay] = 3.0; + mINI::Instance()[kStreamChangedSchemas] = "rtsp/rtmp/fmp4/ts/hls/hls.fmp4"; }); } // namespace Hook @@ -100,14 +102,14 @@ static void parse_http_response(const SockException &ex, const Parser &res, cons fun(Json::nullValue, errStr, should_retry); return; } - if (res.Url() != "200") { - auto errStr = StrPrinter << "[bad http status code]:" << res.Url() << endl; + if (res.status() != "200") { + auto errStr = StrPrinter << "[bad http status code]:" << res.status() << endl; fun(Json::nullValue, errStr, should_retry); return; } Value result; try { - stringstream ss(res.Content()); + stringstream ss(res.content()); ss >> result; } catch (std::exception &ex) { auto errStr = StrPrinter << "[parse json failed]:" << ex.what() << endl; @@ -164,12 +166,16 @@ string getVhost(const HttpArgs &value) { return val != value.end() ? val->second : ""; } +static atomic s_hook_index { 0 }; + void do_http_hook(const string &url, const ArgsType &body, const function &func, uint32_t retry) { GET_CONFIG(string, mediaServerId, General::kMediaServerId); GET_CONFIG(float, hook_timeoutSec, Hook::kTimeoutSec); GET_CONFIG(float, retry_delay, Hook::kRetryDelay); const_cast(body)["mediaServerId"] = mediaServerId; + const_cast(body)["hook_index"] = s_hook_index++; + auto requester = std::make_shared(); requester->setMethod("POST"); auto bodyStr = to_string(body); @@ -213,13 +219,13 @@ void do_http_hook(const string &url, const ArgsType &body, const function snprintf(url, sizeof(url), origin_fmt.data(), info._app.data(), info._streamid.data())) { + if ((ssize_t)origin_fmt.size() > snprintf(url, sizeof(url), origin_fmt.data(), info.app.data(), info.stream.data())) { WarnL << "get origin url failed, origin_fmt:" << origin_fmt; return ""; } // 告知源站这是来自边沿站的拉流请求,如果未找到流请立即返回拉流失败 - return string(url) + '?' + kEdgeServerParam + '&' + VHOST_KEY + '=' + info._vhost + '&' + info._param_strs; + return string(url) + '?' + kEdgeServerParam + '&' + VHOST_KEY + '=' + info.vhost + '&' + info.param_strs; } static void pullStreamFromOrigin(const vector &urls, size_t index, size_t failed_cnt, const MediaInfo &args, const function &closePlayer) { @@ -280,10 +298,10 @@ static void pullStreamFromOrigin(const vector &urls, size_t index, size_ InfoL << "pull stream from origin, failed_cnt: " << failed_cnt << ", timeout_sec: " << timeout_sec << ", url: " << url; ProtocolOption option; - option.enable_hls = option.enable_hls || (args._schema == HLS_SCHEMA); + option.enable_hls = option.enable_hls || (args.schema == HLS_SCHEMA); option.enable_mp4 = false; - addStreamProxy(args._vhost, args._app, args._streamid, url, retry_count, option, Rtsp::RTP_TCP, timeout_sec, [=](const SockException &ex, const string &key) mutable { + addStreamProxy(args.vhost, args.app, args.stream, url, retry_count, option, Rtsp::RTP_TCP, timeout_sec, [=](const SockException &ex, const string &key) mutable { if (!ex) { return; } @@ -317,11 +335,10 @@ static mINI jsonToMini(const Value &obj) { void installWebHook() { GET_CONFIG(bool, hook_enable, Hook::kEnable); - GET_CONFIG(string, hook_adminparams, Hook::kAdminParams); NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) { GET_CONFIG(string, hook_publish, Hook::kOnPublish); - if (!hook_enable || args._param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_publish.empty()) { invoker("", ProtocolOption()); return; } @@ -346,7 +363,7 @@ void installWebHook() { NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) { GET_CONFIG(string, hook_play, Hook::kOnPlay); - if (!hook_enable || args._param_strs == hook_adminparams || hook_play.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_play.empty()) { invoker(""); return; } @@ -360,7 +377,7 @@ void installWebHook() { NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) { GET_CONFIG(string, hook_flowreport, Hook::kOnFlowReport); - if (!hook_enable || args._param_strs == hook_adminparams || hook_flowreport.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_flowreport.empty()) { return; } auto body = make_json(args); @@ -379,7 +396,7 @@ void installWebHook() { // 监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问 NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastOnGetRtspRealm, [](BroadcastOnGetRtspRealmArgs) { GET_CONFIG(string, hook_rtsp_realm, Hook::kOnRtspRealm); - if (!hook_enable || args._param_strs == hook_adminparams || hook_rtsp_realm.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_rtsp_realm.empty()) { // 无需认证 invoker(""); return; @@ -427,23 +444,37 @@ void installWebHook() { // 监听rtsp、rtmp源注册或注销事件 NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaChanged, [](BroadcastMediaChangedArgs) { - GET_CONFIG(string, hook_stream_chaned, Hook::kOnStreamChanged); - if (!hook_enable || hook_stream_chaned.empty()) { + GET_CONFIG(string, hook_stream_changed, Hook::kOnStreamChanged); + if (!hook_enable || hook_stream_changed.empty()) { return; } + GET_CONFIG_FUNC(std::set, stream_changed_set, Hook::kStreamChangedSchemas, [](const std::string &str) { + std::set ret; + auto vec = split(str, "/"); + for (auto &schema : vec) { + trim(schema); + if (!schema.empty()) { + ret.emplace(schema); + } + } + return ret; + }); + if (!stream_changed_set.empty() && stream_changed_set.find(sender.getSchema()) == stream_changed_set.end()) { + // 该协议注册注销事件被忽略 + return; + } + ArgsType body; if (bRegist) { body = makeMediaSourceJson(sender); body["regist"] = bRegist; } else { body["schema"] = sender.getSchema(); - body[VHOST_KEY] = sender.getVhost(); - body["app"] = sender.getApp(); - body["stream"] = sender.getId(); + dumpMediaTuple(sender.getMediaTuple(), body); body["regist"] = bRegist; } // 执行hook - do_http_hook(hook_stream_chaned, body, nullptr); + do_http_hook(hook_stream_changed, body, nullptr); }); GET_CONFIG_FUNC(vector, origin_urls, Cluster::kOriginUrl, [](const string &str) { @@ -467,7 +498,7 @@ void installWebHook() { return; } - if (start_with(args._param_strs, kEdgeServerParam)) { + if (start_with(args.param_strs, kEdgeServerParam)) { // 源站收到来自边沿站的溯源请求,流不存在时立即返回拉流失败 closePlayer(); return; @@ -503,9 +534,7 @@ void installWebHook() { body["file_name"] = info.file_name; body["folder"] = info.folder; body["url"] = info.url; - body["app"] = info.app; - body["stream"] = info.stream; - body[VHOST_KEY] = info.vhost; + dumpMediaTuple(info, body); return body; }; @@ -532,7 +561,7 @@ void installWebHook() { NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastShellLogin, [](BroadcastShellLoginArgs) { GET_CONFIG(string, hook_shell_login, Hook::kOnShellLogin); - if (!hook_enable || hook_shell_login.empty() || sender.get_peer_ip() == "127.0.0.1") { + if (!hook_enable || hook_shell_login.empty()) { invoker(""); return; } @@ -561,9 +590,7 @@ void installWebHook() { ArgsType body; body["schema"] = sender.getSchema(); - body[VHOST_KEY] = sender.getVhost(); - body["app"] = sender.getApp(); - body["stream"] = sender.getId(); + dumpMediaTuple(sender.getMediaTuple(), body); weak_ptr weakSrc = sender.shared_from_this(); // 执行hook do_http_hook(hook_stream_none_reader, body, [weakSrc](const Value &obj, const string &err) { @@ -577,16 +604,14 @@ void installWebHook() { }); }); - NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastSendRtpStopped, [](BroadcastSendRtpStopped) { + NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastSendRtpStopped, [](BroadcastSendRtpStoppedArgs) { GET_CONFIG(string, hook_send_rtp_stopped, Hook::kOnSendRtpStopped); if (!hook_enable || hook_send_rtp_stopped.empty()) { return; } ArgsType body; - body[VHOST_KEY] = sender.getVhost(); - body["app"] = sender.getApp(); - body["stream"] = sender.getStreamId(); + dumpMediaTuple(sender.getMediaTuple(), body); body["ssrc"] = ssrc; body["originType"] = (int)sender.getOriginType(MediaSource::NullMediaSource()); body["originTypeStr"] = getOriginTypeString(sender.getOriginType(MediaSource::NullMediaSource())); @@ -614,15 +639,14 @@ void installWebHook() { // 追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能 NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastHttpAccess, [](BroadcastHttpAccessArgs) { GET_CONFIG(string, hook_http_access, Hook::kOnHttpAccess); - if (sender.get_peer_ip() == "127.0.0.1" || parser.Params() == hook_adminparams) { - // 如果是本机或超级管理员访问,那么不做访问鉴权;权限有效期1个小时 - invoker("", "", 60 * 60); - return; - } if (!hook_enable || hook_http_access.empty()) { // 未开启http文件访问鉴权,那么允许访问,但是每次访问都要鉴权; // 因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权) - invoker("", "", 0); + if (!HttpFileManager::isIPAllowed(sender.get_peer_ip())) { + invoker("Your ip is not allowed to access the service.", "", 0); + } else { + invoker("", "", 0); + } return; } @@ -632,7 +656,7 @@ void installWebHook() { body["id"] = sender.getIdentifier(); body["path"] = path; body["is_dir"] = is_dir; - body["params"] = parser.Params(); + body["params"] = parser.params(); for (auto &pr : parser.getHeader()) { body[string("header.") + pr.first] = pr.second; } @@ -650,7 +674,7 @@ void installWebHook() { }); }); - NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeout) { + NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::KBroadcastRtpServerTimeout, [](BroadcastRtpServerTimeoutArgs) { GET_CONFIG(string, rtp_server_timeout, Hook::kOnRtpServerTimeout); if (!hook_enable || rtp_server_timeout.empty()) { return; @@ -676,3 +700,7 @@ void unInstallWebHook() { g_keepalive_timer.reset(); NoticeCenter::Instance().delListener(&web_hook_tag); } + +void onProcessExited() { + reportServerExited(); +} \ No newline at end of file diff --git a/server/WebHook.h b/server/WebHook.h index ea99f736..c75d05c3 100755 --- a/server/WebHook.h +++ b/server/WebHook.h @@ -31,6 +31,7 @@ extern const std::string kTimeoutSec; void installWebHook(); void unInstallWebHook(); +void onProcessExited(); /** * 触发http hook请求 * @param url 请求地址 diff --git a/server/main.cpp b/server/main.cpp index 9a190935..70b6348f 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -179,6 +179,29 @@ public: throw ExitException(); }); #endif + (*_parser) << Option(0,/*该选项简称,如果是\x00则说明无简称*/ + "log-slice",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "100",/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "最大保存日志切片个数",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option(0,/*该选项简称,如果是\x00则说明无简称*/ + "log-size",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "256",/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "单个日志切片最大容量,单位MB",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option(0,/*该选项简称,如果是\x00则说明无简称*/ + "log-dir",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + (exeDir() + "log/").data(),/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "日志保存文件夹路径",/*该选项说明文字*/ + nullptr); } ~CMD_main() override{} @@ -213,9 +236,11 @@ int start_main(int argc,char *argv[]) { //设置日志 Logger::Instance().add(std::make_shared("ConsoleChannel", logLevel)); #if !defined(ANDROID) - auto fileChannel = std::make_shared("FileChannel", exeDir() + "log/", logLevel); + auto fileChannel = std::make_shared("FileChannel", cmd_main["log-dir"], logLevel); // 日志最多保存天数 fileChannel->setMaxDay(cmd_main["max_day"]); + fileChannel->setFileMaxCount(cmd_main["log-slice"]); + fileChannel->setFileMaxSize(cmd_main["log-size"]); Logger::Instance().add(fileChannel); #endif // !defined(ANDROID) @@ -326,6 +351,14 @@ int start_main(int argc,char *argv[]) { #endif //defined(ENABLE_SRT) try { + auto &secret = mINI::Instance()[API::kSecret]; + if (secret == "035c73f7-bb6b-4889-a715-d9eb2d1925cc" || secret.empty()) { + // 使用默认secret被禁止启动 + secret = makeRandStr(32, true); + mINI::Instance().dumpFile(g_ini_file); + WarnL << "The " << API::kSecret << " is invalid, modified it to: " << secret + << ", saved config file: " << g_ini_file; + } //rtsp服务器,端口默认554 if (rtspPort) { rtspSrv->start(rtspPort); } //rtsps服务器,端口默认322 @@ -363,8 +396,7 @@ int start_main(int argc,char *argv[]) { #endif//defined(ENABLE_SRT) } catch (std::exception &ex) { - WarnL << "端口占用或无权限:" << ex.what() << endl; - ErrorL << "程序启动失败,请修改配置文件中端口号后重试!" << endl; + ErrorL << "Start server failed: " << ex.what(); sleep(1); #if !defined(_WIN32) if (pid != getpid() && kill_parent_if_failed) { @@ -384,9 +416,15 @@ int start_main(int argc,char *argv[]) { static semaphore sem; signal(SIGINT, [](int) { InfoL << "SIGINT:exit"; - signal(SIGINT, SIG_IGN);// 设置退出信号 + signal(SIGINT, SIG_IGN); // 设置退出信号 sem.post(); - });// 设置退出信号 + }); // 设置退出信号 + + signal(SIGTERM,[](int) { + WarnL << "SIGTERM:exit"; + signal(SIGTERM, SIG_IGN); + sem.post(); + }); #if !defined(_WIN32) signal(SIGHUP, [](int) { mediakit::loadIniConfig(g_ini_file.data()); }); @@ -395,6 +433,8 @@ int start_main(int argc,char *argv[]) { } unInstallWebApi(); unInstallWebHook(); + onProcessExited(); + //休眠1秒再退出,防止资源释放顺序错误 InfoL << "程序退出中,请等待..."; sleep(1); diff --git a/src/Codec/Transcode.cpp b/src/Codec/Transcode.cpp index bab515cc..25f9e77c 100644 --- a/src/Codec/Transcode.cpp +++ b/src/Codec/Transcode.cpp @@ -436,6 +436,7 @@ FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std: av_dict_set(&dict, "zerolatency", "1", 0); av_dict_set(&dict, "strict", "-2", 0); +#ifdef AV_CODEC_CAP_TRUNCATED if (codec->capabilities & AV_CODEC_CAP_TRUNCATED) { /* we do not send complete frames */ _context->flags |= AV_CODEC_FLAG_TRUNCATED; @@ -443,6 +444,7 @@ FFmpegDecoder::FFmpegDecoder(const Track::Ptr &track, int thread_num, const std: // 此时业务层应该需要合帧 _do_merger = true; } +#endif int ret = avcodec_open2(_context.get(), codec, &dict); av_dict_free(&dict); diff --git a/src/Common/Device.h b/src/Common/Device.h index 963fb0dc..cb99657d 100644 --- a/src/Common/Device.h +++ b/src/Common/Device.h @@ -47,10 +47,8 @@ public: using Ptr = std::shared_ptr; //fDuration<=0为直播,否则为点播 - DevChannel( - const std::string &vhost, const std::string &app, const std::string &stream_id, float duration = 0, - const ProtocolOption &option = ProtocolOption()) - : MultiMediaSourceMuxer(vhost, app, stream_id, duration, option) {} + DevChannel(const MediaTuple& tuple, float duration = 0, const ProtocolOption &option = ProtocolOption()) + : MultiMediaSourceMuxer(tuple, duration, option) {} ~DevChannel() override = default; /** diff --git a/src/Common/JemallocUtil.cpp b/src/Common/JemallocUtil.cpp new file mode 100644 index 00000000..fa2de64a --- /dev/null +++ b/src/Common/JemallocUtil.cpp @@ -0,0 +1,74 @@ +/* +* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. +* +* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). +* +* Use of this source code is governed by MIT license that can be found in the +* LICENSE file in the root of the source tree. All contributing project authors +* may be found in the AUTHORS file in the root of the source tree. +*/ + + +#include "JemallocUtil.h" +#include "Util/logger.h" +#ifdef USE_JEMALLOC +#include +#include +#endif + +namespace mediakit { + +void set_profile_active(bool active) { +#ifdef USE_JEMALLOC + int err = mallctl("prof.active", nullptr, nullptr, (void *)&active, sizeof(active)); + if (err != 0) { + WarnL << "mallctl failed with: " << err; + } +#endif +} + +void JemallocUtil::enable_profiling() { + set_profile_active(true); +} +void JemallocUtil::disable_profiling() { + set_profile_active(false); +} +void JemallocUtil::dump(const std::string &file_name) { +#ifdef USE_JEMALLOC + auto *c_str = file_name.c_str(); + int err = mallctl("prof.dump", nullptr, nullptr, &c_str, sizeof(const char *)); + if (err != 0) { + std::cerr << "mallctl failed with: " << err << std::endl; + } +#endif +} +std::string JemallocUtil::get_malloc_stats() { +#ifdef USE_JEMALLOC + std::string res; + malloc_stats_print([](void *opaque, const char *s) { ((std::string *)opaque)->append(s); }, &res, "J"); + return res; +#else + return ""; +#endif +} + +void JemallocUtil::some_malloc_stats(const std::function &fn) { +#ifdef USE_JEMALLOC + constexpr std::array STATS = { + "stats.allocated", "stats.active", "stats.metadata", "stats.metadata_thp", + "stats.resident", "stats.mapped", "stats.retained", "stats.zero_reallocs", + }; + + for (const char *stat : STATS) { + size_t value; + size_t len = sizeof(value); + auto err = mallctl(stat, &value, &len, nullptr, 0); + if (err != 0) { + ErrorL << "Failed reading " << stat << ": " << err; + continue; + } + fn(stat, value); + } +#endif +} +} // namespace mediakit \ No newline at end of file diff --git a/src/Common/JemallocUtil.h b/src/Common/JemallocUtil.h new file mode 100644 index 00000000..796a58bb --- /dev/null +++ b/src/Common/JemallocUtil.h @@ -0,0 +1,30 @@ +/* +* Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. +* +* This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). +* +* Use of this source code is governed by MIT license that can be found in the +* LICENSE file in the root of the source tree. All contributing project authors +* may be found in the AUTHORS file in the root of the source tree. +*/ + +#ifndef ZLMEDIAKIT_JEMALLOCUTIL_H +#define ZLMEDIAKIT_JEMALLOCUTIL_H +#include +#include +namespace mediakit { +class JemallocUtil { +public: + JemallocUtil() = default; + ~JemallocUtil() = default; + + static void enable_profiling(); + + static void disable_profiling(); + + static void dump(const std::string &file_name); + static std::string get_malloc_stats(); + static void some_malloc_stats(const std::function &fn); +}; +} // namespace mediakit +#endif // ZLMEDIAKIT_JEMALLOCUTIL_H diff --git a/src/Common/MediaSource.cpp b/src/Common/MediaSource.cpp index 5e378124..1977b616 100644 --- a/src/Common/MediaSource.cpp +++ b/src/Common/MediaSource.cpp @@ -15,8 +15,10 @@ #include "MediaSource.h" #include "Common/config.h" #include "Common/Parser.h" +#include "Common/MultiMediaSourceMuxer.h" #include "Record/MP4Reader.h" #include "PacketCache.h" + using namespace std; using namespace toolkit; @@ -53,12 +55,14 @@ string getOriginTypeString(MediaOriginType type){ ////////////////////////////////////////////////////////////////////////////////////////////////////////////// ProtocolOption::ProtocolOption() { - GET_CONFIG(bool, s_modify_stamp, Protocol::kModifyStamp); + GET_CONFIG(int, s_modify_stamp, Protocol::kModifyStamp); GET_CONFIG(bool, s_enabel_audio, Protocol::kEnableAudio); GET_CONFIG(bool, s_add_mute_audio, Protocol::kAddMuteAudio); + GET_CONFIG(bool, s_auto_close, Protocol::kAutoClose); GET_CONFIG(uint32_t, s_continue_push_ms, Protocol::kContinuePushMS); GET_CONFIG(bool, s_enable_hls, Protocol::kEnableHls); + GET_CONFIG(bool, s_enable_hls_fmp4, Protocol::kEnableHlsFmp4); GET_CONFIG(bool, s_enable_mp4, Protocol::kEnableMP4); GET_CONFIG(bool, s_enable_rtsp, Protocol::kEnableRtsp); GET_CONFIG(bool, s_enable_rtmp, Protocol::kEnableRtmp); @@ -80,9 +84,11 @@ ProtocolOption::ProtocolOption() { modify_stamp = s_modify_stamp; enable_audio = s_enabel_audio; add_mute_audio = s_add_mute_audio; + auto_close = s_auto_close; continue_push_ms = s_continue_push_ms; enable_hls = s_enable_hls; + enable_hls_fmp4 = s_enable_hls_fmp4; enable_mp4 = s_enable_mp4; enable_rtsp = s_enable_rtsp; enable_rtmp = s_enable_rtmp; @@ -105,7 +111,7 @@ ProtocolOption::ProtocolOption() { ////////////////////////////////////////////////////////////////////////////////////////////////////////////// struct MediaSourceNull : public MediaSource { - MediaSourceNull() : MediaSource("schema", "vhost", "app", "stream") {}; + MediaSourceNull() : MediaSource("schema", MediaTuple{"vhost", "app", "stream"}) {}; int readerCount() override { return 0; } }; @@ -114,38 +120,21 @@ MediaSource &MediaSource::NullMediaSource() { return *s_null; } -MediaSource::MediaSource(const string &schema, const string &vhost, const string &app, const string &stream_id){ +MediaSource::MediaSource(const string &schema, const MediaTuple& tuple): _tuple(tuple) { GET_CONFIG(bool, enableVhost, General::kEnableVhost); - if (!enableVhost) { - _vhost = DEFAULT_VHOST; - } else { - _vhost = vhost.empty() ? DEFAULT_VHOST : vhost; + if (!enableVhost || _tuple.vhost.empty()) { + _tuple.vhost = DEFAULT_VHOST; } _schema = schema; - _app = app; - _stream_id = stream_id; _create_stamp = time(NULL); } MediaSource::~MediaSource() { - unregist(); -} - -const string& MediaSource::getSchema() const { - return _schema; -} - -const string& MediaSource::getVhost() const { - return _vhost; -} - -const string& MediaSource::getApp() const { - //获取该源的id - return _app; -} - -const string& MediaSource::getId() const { - return _stream_id; + try { + unregist(); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } } std::shared_ptr MediaSource::getOwnership() { @@ -187,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(){ @@ -292,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(); @@ -424,7 +406,7 @@ static MediaSource::Ptr find_l(const string &schema, const string &vhost_in, con static void findAsync_l(const MediaInfo &info, const std::shared_ptr &session, bool retry, const function &cb){ - auto src = find_l(info._schema, info._vhost, info._app, info._streamid, true); + auto src = find_l(info.schema, info.vhost, info.app, info.stream, true); if (src || !retry) { cb(src); return; @@ -459,10 +441,8 @@ static void findAsync_l(const MediaInfo &info, const std::shared_ptr &s weak_ptr weak_session = session; auto on_register = [weak_session, info, cb_once, cancel_all, poller](BroadcastMediaChangedArgs) { if (!bRegist || - sender.getSchema() != info._schema || - sender.getVhost() != info._vhost || - sender.getApp() != info._app || - sender.getId() != info._streamid) { + sender.getSchema() != info.schema || + !equalMediaTuple(sender.getMediaTuple(), info)) { //不是自己感兴趣的事件,忽略之 return; } @@ -489,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) { @@ -519,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(); } @@ -527,7 +507,7 @@ void MediaSource::regist() { { //减小互斥锁临界区 lock_guard lock(s_media_source_mtx); - auto &ref = s_media_source_map[_schema][_vhost][_app][_stream_id]; + auto &ref = s_media_source_map[_schema][_tuple.vhost][_tuple.app][_tuple.stream]; auto src = ref.lock(); if (src) { if (src.get() == this) { @@ -570,7 +550,7 @@ bool MediaSource::unregist() { { //减小互斥锁临界区 lock_guard lock(s_media_source_mtx); - erase_media_source(ret, this, s_media_source_map, _schema, _vhost, _app, _stream_id); + erase_media_source(ret, this, s_media_source_map, _schema, _tuple.vhost, _tuple.app, _tuple.stream); } if (ret) { @@ -579,34 +559,37 @@ bool MediaSource::unregist() { return ret; } +bool equalMediaTuple(const MediaTuple& a, const MediaTuple& b) { + return a.vhost == b.vhost && a.app == b.app && a.stream == b.stream; +} /////////////////////////////////////MediaInfo////////////////////////////////////// void MediaInfo::parse(const std::string &url_in){ - _full_url = url_in; + full_url = url_in; auto url = url_in; auto pos = url.find("?"); if (pos != string::npos) { - _param_strs = url.substr(pos + 1); + param_strs = url.substr(pos + 1); url.erase(pos); } auto schema_pos = url.find("://"); if (schema_pos != string::npos) { - _schema = url.substr(0, schema_pos); + schema = url.substr(0, schema_pos); } else { schema_pos = -3; } auto split_vec = split(url.substr(schema_pos + 3), "/"); if (split_vec.size() > 0) { - splitUrl(split_vec[0], _host, _port); - _vhost = _host; - if (_vhost == "localhost" || isIP(_vhost.data())) { + splitUrl(split_vec[0], host, port); + vhost = host; + if (vhost == "localhost" || isIP(vhost.data())) { //如果访问的是localhost或ip,那么则为默认虚拟主机 - _vhost = DEFAULT_VHOST; + vhost = DEFAULT_VHOST; } } if (split_vec.size() > 1) { - _app = split_vec[1]; + app = split_vec[1]; } if (split_vec.size() > 2) { string stream_id; @@ -616,18 +599,18 @@ void MediaInfo::parse(const std::string &url_in){ if (stream_id.back() == '/') { stream_id.pop_back(); } - _streamid = stream_id; + stream = stream_id; } - auto params = Parser::parseArgs(_param_strs); + auto params = Parser::parseArgs(param_strs); if (params.find(VHOST_KEY) != params.end()) { - _vhost = params[VHOST_KEY]; + vhost = params[VHOST_KEY]; } GET_CONFIG(bool, enableVhost, General::kEnableVhost); - if (!enableVhost || _vhost.empty()) { + if (!enableVhost || vhost.empty()) { //如果关闭虚拟主机或者虚拟主机为空,则设置虚拟主机为默认 - _vhost = DEFAULT_VHOST; + vhost = DEFAULT_VHOST; } } @@ -663,7 +646,7 @@ void MediaSourceEvent::onReaderChanged(MediaSource &sender, int size){ GET_CONFIG(string, record_app, Record::kAppName); GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS); //如果mp4点播, 无人观看时我们强制关闭点播 - bool is_mp4_vod = sender.getApp() == record_app; + bool is_mp4_vod = sender.getMediaTuple().app == record_app; weak_ptr weak_sender = sender.shared_from_this(); _async_close_timer = std::make_shared(stream_none_reader_delay / 1000.0f, [weak_sender, is_mp4_vod]() { @@ -679,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(); @@ -794,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 c06a7c83..bcf75a09 100644 --- a/src/Common/MediaSource.h +++ b/src/Common/MediaSource.h @@ -41,6 +41,7 @@ enum class MediaOriginType : uint8_t { std::string getOriginTypeString(MediaOriginType type); class MediaSource; +class MultiMediaSourceMuxer; class MediaSourceEvent { public: friend class MediaSource; @@ -88,6 +89,8 @@ public: virtual bool isRecording(MediaSource &sender, Recorder::type type) { return false; } // 获取所有track相关信息 virtual std::vector getMediaTracks(MediaSource &sender, bool trackReady = true) const { return std::vector(); }; + // 获取MultiMediaSourceMuxer对象 + virtual std::shared_ptr getMuxer(MediaSource &sender) { return nullptr; } class SendRtpArgs { public: @@ -136,17 +139,30 @@ class ProtocolOption { public: ProtocolOption(); - //时间戳修复这一路流标志位 - bool modify_stamp; + enum { + kModifyStampOff = 0, // 采用源视频流绝对时间戳,不做任何改变 + kModifyStampSystem = 1, // 采用zlmediakit接收数据时的系统时间戳(有平滑处理) + kModifyStampRelative = 2 // 采用源视频流时间戳相对时间戳(增长量),有做时间戳跳跃和回退矫正 + }; + // 时间戳类型 + int modify_stamp; + //转协议是否开启音频 bool enable_audio; //添加静音音频,在关闭音频时,此开关无效 bool add_mute_audio; + // 无人观看时,是否直接关闭(而不是通过on_none_reader hook返回close) + // 此配置置1时,此流如果无人观看,将不触发on_none_reader hook回调, + // 而是将直接关闭流 + bool auto_close; + //断连续推延时,单位毫秒,默认采用配置文件 uint32_t continue_push_ms; - //是否开启转换为hls + //是否开启转换为hls(mpegts) bool enable_hls; + //是否开启转换为hls(fmp4) + bool enable_hls_fmp4; //是否开启MP4录制 bool enable_mp4; //是否开启转换为rtsp/webrtc @@ -179,15 +195,20 @@ public: //hls录制保存路径 std::string hls_save_path; + // 支持通过on_publish返回值替换stream_id + std::string stream_replace; + template ProtocolOption(const MAP &allArgs) : ProtocolOption() { #define GET_OPT_VALUE(key) getArgsValue(allArgs, #key, key) GET_OPT_VALUE(modify_stamp); GET_OPT_VALUE(enable_audio); GET_OPT_VALUE(add_mute_audio); + GET_OPT_VALUE(auto_close); GET_OPT_VALUE(continue_push_ms); GET_OPT_VALUE(enable_hls); + GET_OPT_VALUE(enable_hls_fmp4); GET_OPT_VALUE(enable_mp4); GET_OPT_VALUE(enable_rtsp); GET_OPT_VALUE(enable_rtmp); @@ -205,6 +226,7 @@ public: GET_OPT_VALUE(mp4_save_path); GET_OPT_VALUE(hls_save_path); + GET_OPT_VALUE(stream_replace); } private: @@ -244,6 +266,7 @@ public: bool stopSendRtp(MediaSource &sender, const std::string &ssrc) override; float getLossRate(MediaSource &sender, TrackType type) override; toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; + std::shared_ptr getMuxer(MediaSource &sender) override; private: std::weak_ptr _listener; @@ -252,26 +275,24 @@ private: /** * 解析url获取媒体相关信息 */ -class MediaInfo { +class MediaInfo: public MediaTuple { public: ~MediaInfo() = default; MediaInfo() = default; MediaInfo(const std::string &url) { parse(url); } void parse(const std::string &url); - std::string shortUrl() const { return _vhost + "/" + _app + "/" + _streamid; } - std::string getUrl() const { return _schema + "://" + shortUrl(); } + std::string getUrl() const { return schema + "://" + shortUrl(); } public: - uint16_t _port = 0; - std::string _full_url; - std::string _schema; - std::string _host; - std::string _vhost; - std::string _app; - std::string _streamid; - std::string _param_strs; + uint16_t port = 0; + std::string full_url; + std::string schema; + std::string host; + std::string param_strs; }; +bool equalMediaTuple(const MediaTuple& a, const MediaTuple& b); + /** * 媒体源,任何rtsp/rtmp的直播流都源自该对象 */ @@ -280,23 +301,21 @@ public: static MediaSource& NullMediaSource(); using Ptr = std::shared_ptr; - MediaSource(const std::string &schema, const std::string &vhost, const std::string &app, const std::string &stream_id); + MediaSource(const std::string &schema, const MediaTuple& tuple); virtual ~MediaSource(); ////////////////获取MediaSource相关信息//////////////// // 获取协议类型 - const std::string& getSchema() const; - // 虚拟主机 - const std::string& getVhost() const; - // 应用名 - const std::string& getApp() const; - // 流id - const std::string& getId() const; + const std::string& getSchema() const { + return _schema; + } - std::string shortUrl() const { return _vhost + "/" + _app + "/" + _stream_id; } + const MediaTuple& getMediaTuple() const { + return _tuple; + } - std::string getUrl() const { return _schema + "://" + shortUrl(); } + std::string getUrl() const { return _schema + "://" + _tuple.shortUrl(); } //获取对象所有权 std::shared_ptr getOwnership(); @@ -321,19 +340,21 @@ public: // 设置监听者 virtual void setListener(const std::weak_ptr &listener); // 获取监听者 - std::weak_ptr getListener(bool next = false) const; + std::weak_ptr getListener() const; // 本协议获取观看者个数,可能返回本协议的观看人数,也可能返回总人数 virtual int readerCount() = 0; // 观看者个数,包括(hls/rtsp/rtmp) virtual int totalReaderCount(); // 获取播放器列表 - virtual void getPlayerList(const std::function> &info_list)> &cb, - const std::function(std::shared_ptr &&info)> &on_change) { + virtual void getPlayerList(const std::function &info_list)> &cb, + const std::function &on_change) { assert(cb); - cb(std::list>()); + cb(std::list()); } + virtual bool broadcastMessage(const toolkit::Any &data) { return false; } + // 获取媒体源类型 MediaOriginType getOriginType() const; // 获取媒体源url或者文件路径 @@ -363,13 +384,15 @@ public: float getLossRate(mediakit::TrackType type); // 获取所在线程 toolkit::EventPoller::Ptr getOwnerPoller(); + // 获取MultiMediaSourceMuxer对象 + std::shared_ptr getMuxer(); ////////////////static方法,查找或生成MediaSource//////////////// // 同步查找流 static Ptr find(const std::string &schema, const std::string &vhost, const std::string &app, const std::string &id, bool from_mp4 = false); static Ptr find(const MediaInfo &info, bool from_mp4 = false) { - return find(info._schema, info._vhost, info._app, info._streamid, from_mp4); + return find(info.schema, info.vhost, info.app, info.stream, from_mp4); } // 忽略schema,同步查找流,可能返回rtmp/rtsp/hls类型 @@ -394,15 +417,13 @@ private: protected: toolkit::BytesSpeed _speed[TrackMax]; + MediaTuple _tuple; private: std::atomic_flag _owned { false }; time_t _create_stamp; toolkit::Ticker _ticker; std::string _schema; - std::string _vhost; - std::string _app; - std::string _stream_id; std::weak_ptr _listener; // 对象个数统计 toolkit::ObjectStatistic _statistic; diff --git a/src/Common/MultiMediaSourceMuxer.cpp b/src/Common/MultiMediaSourceMuxer.cpp index 75b6ff48..19dd41f7 100644 --- a/src/Common/MultiMediaSourceMuxer.cpp +++ b/src/Common/MultiMediaSourceMuxer.cpp @@ -25,7 +25,7 @@ namespace { class MediaSourceForMuxer : public MediaSource { public: MediaSourceForMuxer(const MultiMediaSourceMuxer::Ptr &muxer) - : MediaSource("muxer", muxer->getVhost(), muxer->getApp(), muxer->getStreamId()) { + : MediaSource("muxer", muxer->getMediaTuple()) { MediaSource::setListener(muxer); } int readerCount() override { return 0; } @@ -33,10 +33,11 @@ public: } // namespace static std::shared_ptr makeRecorder(MediaSource &sender, const vector &tracks, Recorder::type type, const ProtocolOption &option){ - auto recorder = Recorder::createRecorder(type, sender.getVhost(), sender.getApp(), sender.getId(), option); + auto recorder = Recorder::createRecorder(type, sender.getMediaTuple(), option); for (auto &track : tracks) { recorder->addTrack(track); } + recorder->addTrackCompleted(); return recorder; } @@ -70,16 +71,12 @@ static string getTrackInfoStr(const TrackSource *track_src){ return std::move(codec_info); } -const std::string &MultiMediaSourceMuxer::getVhost() const { - return _vhost; +const ProtocolOption &MultiMediaSourceMuxer::getOption() const { + return _option; } -const std::string &MultiMediaSourceMuxer::getApp() const { - return _app; -} - -const std::string &MultiMediaSourceMuxer::getStreamId() const { - return _stream_id; +const MediaTuple &MultiMediaSourceMuxer::getMediaTuple() const { + return _tuple; } std::string MultiMediaSourceMuxer::shortUrl() const { @@ -87,37 +84,44 @@ std::string MultiMediaSourceMuxer::shortUrl() const { if (!ret.empty()) { return ret; } - return _vhost + "/" + _app + "/" + _stream_id; + return _tuple.shortUrl(); } -MultiMediaSourceMuxer::MultiMediaSourceMuxer(const string &vhost, const string &app, const string &stream, float dur_sec, const ProtocolOption &option) { +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(); - _vhost = vhost; - _app = app; - _stream_id = stream; _option = option; + if (dur_sec > 0.01) { + // 点播 + _stamp[TrackVideo].setPlayBack(); + _stamp[TrackAudio].setPlayBack(); + } if (option.enable_rtmp) { - _rtmp = std::make_shared(vhost, app, stream, option, std::make_shared(dur_sec)); + _rtmp = std::make_shared(_tuple, option, std::make_shared(dur_sec)); } if (option.enable_rtsp) { - _rtsp = std::make_shared(vhost, app, stream, option, std::make_shared(dur_sec)); + _rtsp = std::make_shared(_tuple, option, std::make_shared(dur_sec)); } if (option.enable_hls) { - _hls = dynamic_pointer_cast(Recorder::createRecorder(Recorder::type_hls, vhost, app, stream, option)); + _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, vhost, app, stream, option); + _mp4 = Recorder::createRecorder(Recorder::type_mp4, _tuple, option); } if (option.enable_ts) { - _ts = std::make_shared(vhost, app, stream, option); + _ts = dynamic_pointer_cast(Recorder::createRecorder(Recorder::type_ts, _tuple, option)); } -#if defined(ENABLE_MP4) if (option.enable_fmp4) { - _fmp4 = std::make_shared(vhost, app, stream, option); + _fmp4 = dynamic_pointer_cast(Recorder::createRecorder(Recorder::type_fmp4, _tuple, option)); } -#endif //音频相关设置 enableAudio(option.enable_audio); @@ -138,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); } } @@ -158,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); } @@ -194,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录制,触发观看人数变化相关事件 @@ -229,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; } } @@ -252,10 +295,11 @@ void MultiMediaSourceMuxer::startSendRtp(MediaSource &sender, const MediaSourceE auto ring = _ring; auto ssrc = args.ssrc; auto tracks = getTracks(false); - auto rtp_sender = std::make_shared(getOwnerPoller(sender)); + auto poller = getOwnerPoller(sender); + auto rtp_sender = std::make_shared(poller); weak_ptr weak_self = shared_from_this(); - rtp_sender->startSend(args, [ssrc, weak_self, rtp_sender, cb, tracks, ring](uint16_t local_port, const SockException &ex) mutable { + rtp_sender->startSend(args, [ssrc, weak_self, rtp_sender, cb, tracks, ring, poller](uint16_t local_port, const SockException &ex) mutable { cb(local_port, ex); auto strong_self = weak_self.lock(); if (!strong_self || ex) { @@ -272,12 +316,12 @@ 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); }); } }); - auto reader = ring->attach(EventPoller::getCurrentPoller()); + auto reader = ring->attach(poller); reader->setReadCB([rtp_sender](const Frame::Ptr &frame) { rtp_sender->inputFrame(frame); }); @@ -319,7 +363,7 @@ EventPoller::Ptr MultiMediaSourceMuxer::getOwnerPoller(MediaSource &sender) { try { auto ret = listener->getOwnerPoller(sender); if (ret != _poller) { - WarnL << "OwnerPoller changed:" << shortUrl(); + WarnL << "OwnerPoller changed " << _poller->getThreadName() << " -> " << ret->getThreadName() << " : " << shortUrl(); _poller = ret; } return ret; @@ -329,8 +373,11 @@ EventPoller::Ptr MultiMediaSourceMuxer::getOwnerPoller(MediaSource &sender) { } } -bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) { +std::shared_ptr MultiMediaSourceMuxer::getMuxer(MediaSource &sender) { + return shared_from_this(); +} +bool MultiMediaSourceMuxer::onTrackReady(const Track::Ptr &track) { bool ret = false; if (_rtmp) { ret = _rtmp->addTrack(track) ? true : ret; @@ -341,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; } @@ -364,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(); @@ -385,6 +440,11 @@ void MultiMediaSourceMuxer::onAllTrackReady() { createGopCacheIfNeed(); } #endif + auto tracks = getTracks(false); + if (tracks.size() >= 2) { + // 音频时间戳同步于视频,因为音频时间戳被修改后不影响播放 + _stamp[TrackAudio].syncTo(_stamp[TrackVideo]); + } InfoL << "stream: " << shortUrl() << " , codec info: " << getTrackInfoStr(this); } @@ -416,29 +476,25 @@ void MultiMediaSourceMuxer::resetTracks() { if (_ts) { _ts->resetTracks(); } -#if defined(ENABLE_MP4) if (_fmp4) { _fmp4->resetTracks(); } -#endif - - //拷贝智能指针,目的是为了防止跨线程调用设置录像相关api导致的线程竞争问题 - auto hls = _hls; - if (hls) { - hls->resetTracks(); + if (_hls_fmp4) { + _hls_fmp4->resetTracks(); } - - auto mp4 = _mp4; - if (mp4) { - mp4->resetTracks(); + if (_hls) { + _hls->resetTracks(); + } + if (_mp4) { + _mp4->resetTracks(); } } bool MultiMediaSourceMuxer::onTrackFrame(const Frame::Ptr &frame_in) { auto frame = frame_in; - if (_option.modify_stamp) { - //开启了时间戳覆盖 - frame = std::make_shared(frame, _stamp[frame->getTrackType()],true); + if (_option.modify_stamp != ProtocolOption::kModifyStampOff) { + // 时间戳不采用原始的绝对时间戳 + frame = std::make_shared(frame, _stamp[frame->getTrackType()], _option.modify_stamp); } bool ret = false; @@ -452,29 +508,28 @@ 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开始处 auto video_key_pos = frame->keyFrame() || frame->configFrame(); _ring->write(frame, video_key_pos && !_video_key_pos); - _video_key_pos = video_key_pos; + if (!frame->dropAble()) { + _video_key_pos = video_key_pos; + } } else { // 没有视频时,设置is_key为true,目的是关闭gop缓存 _ring->write(frame, !haveVideo()); @@ -488,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 043081e3..3e6d7e7f 100644 --- a/src/Common/MultiMediaSourceMuxer.h +++ b/src/Common/MultiMediaSourceMuxer.h @@ -37,7 +37,7 @@ public: virtual void onAllTrackReady() = 0; }; - MultiMediaSourceMuxer(const std::string &vhost, const std::string &app, const std::string &stream, float dur_sec = 0.0,const ProtocolOption &option = ProtocolOption()); + MultiMediaSourceMuxer(const MediaTuple& tuple, float dur_sec = 0.0,const ProtocolOption &option = ProtocolOption()); ~MultiMediaSourceMuxer() override = default; /** @@ -131,9 +131,13 @@ public: */ toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override; - const std::string& getVhost() const; - const std::string& getApp() const; - const std::string& getStreamId() const; + /** + * 获取本对象 + */ + std::shared_ptr getMuxer(MediaSource &sender) override; + + const ProtocolOption &getOption() const; + const MediaTuple &getMediaTuple() const; std::string shortUrl() const; protected: @@ -164,25 +168,19 @@ private: bool _is_enable = false; bool _create_in_poller = false; bool _video_key_pos = false; - std::string _vhost; - std::string _app; - std::string _stream_id; + MediaTuple _tuple; ProtocolOption _option; toolkit::Ticker _last_check; Stamp _stamp[2]; std::weak_ptr _track_listener; -#if defined(ENABLE_RTPPROXY) std::unordered_map _rtp_sender; -#endif //ENABLE_RTPPROXY - -#if defined(ENABLE_MP4) FMP4MediaSourceMuxer::Ptr _fmp4; -#endif RtmpMediaSourceMuxer::Ptr _rtmp; RtspMediaSourceMuxer::Ptr _rtsp; TSMediaSourceMuxer::Ptr _ts; MediaSinkInterface::Ptr _mp4; HlsRecorder::Ptr _hls; + HlsFMP4Recorder::Ptr _hls_fmp4; toolkit::EventPoller::Ptr _poller; RingType::Ptr _ring; diff --git a/src/Common/Parser.cpp b/src/Common/Parser.cpp index 2620ba31..9c073f2c 100644 --- a/src/Common/Parser.cpp +++ b/src/Common/Parser.cpp @@ -10,19 +10,22 @@ #include #include "Parser.h" +#include "strCoding.h" #include "macros.h" #include "Network/sockutil.h" +#include "Common/macros.h" using namespace std; using namespace toolkit; -namespace mediakit{ +namespace mediakit { -string FindField(const char* buf, const char* start, const char *end ,size_t bufSize) { - if(bufSize <=0 ){ - bufSize = strlen(buf); +string findSubString(const char *buf, const char *start, const char *end, size_t buf_size) { + if (buf_size <= 0) { + buf_size = strlen(buf); } - const char *msg_start = buf, *msg_end = buf + bufSize; + auto msg_start = buf; + auto msg_end = buf + buf_size; size_t len = 0; if (start != NULL) { len = strlen(start); @@ -41,126 +44,147 @@ string FindField(const char* buf, const char* start, const char *end ,size_t buf return string(msg_start, msg_end); } -void Parser::Parse(const char *buf) { - //解析 - const char *start = buf; - Clear(); +void Parser::parse(const char *buf, size_t size) { + clear(); + auto ptr = buf; while (true) { - auto line = FindField(start, NULL, "\r\n"); - if (line.size() == 0) { - break; + auto next_line = strchr(ptr, '\n'); + auto offset = 1; + CHECK(next_line && next_line > ptr); + if (*(next_line - 1) == '\r') { + next_line -= 1; + offset = 2; } - if (start == buf) { - _strMethod = FindField(line.data(), NULL, " "); - auto strFullUrl = FindField(line.data(), " ", " "); - auto args_pos = strFullUrl.find('?'); - if (args_pos != string::npos) { - _strUrl = strFullUrl.substr(0, args_pos); - _params = strFullUrl.substr(args_pos + 1); - _mapUrlArgs = parseArgs(_params); - } else { - _strUrl = strFullUrl; + if (ptr == buf) { + auto blank = strchr(ptr, ' '); + CHECK(blank > ptr && blank < next_line); + _method = std::string(ptr, blank - ptr); + auto next_blank = strchr(blank + 1, ' '); + CHECK(next_blank && next_blank < next_line); + _url.assign(blank + 1, next_blank); + auto pos = _url.find('?'); + if (pos != string::npos) { + _params = _url.substr(pos + 1); + _url_args = parseArgs(_params); + _url = _url.substr(0, pos); } - _strTail = FindField(line.data(), (strFullUrl + " ").data(), NULL); + _protocol = std::string(next_blank + 1, next_line); } else { - auto field = FindField(line.data(), NULL, ": "); - auto value = FindField(line.data(), ": ", NULL); - if (field.size() != 0) { - _mapHeaders.emplace_force(field, value); + auto pos = strchr(ptr, ':'); + CHECK(pos > ptr && pos < next_line); + std::string key { ptr, static_cast(pos - ptr) }; + std::string value; + if (pos[1] == ' ') { + value.assign(pos + 2, next_line); + } else { + value.assign(pos + 1, next_line); } + _headers.emplace_force(trim(std::move(key)), trim(std::move(value))); } - start = start + line.size() + 2; - if (strncmp(start, "\r\n", 2) == 0) { //协议解析完毕 - _strContent = FindField(start, "\r\n", NULL); + ptr = next_line + offset; + if (strncmp(ptr, "\r\n", 2) == 0) { // 协议解析完毕 + _content.assign(ptr + 2, buf + size); break; } } } -const string &Parser::Method() const { - return _strMethod; +const string &Parser::method() const { + return _method; } -const string &Parser::Url() const { - return _strUrl; +const string &Parser::url() const { + return _url; } -string Parser::FullUrl() const { +const std::string &Parser::status() const { + return url(); +} + +string Parser::fullUrl() const { if (_params.empty()) { - return _strUrl; + return _url; } - return _strUrl + "?" + _params; + return _url + "?" + _params; } -const string &Parser::Tail() const { - return _strTail; +const string &Parser::protocol() const { + return _protocol; } +const std::string &Parser::statusStr() const { + return protocol(); +} + +static std::string kNull; + const string &Parser::operator[](const char *name) const { - auto it = _mapHeaders.find(name); - if (it == _mapHeaders.end()) { - return _strNull; + auto it = _headers.find(name); + if (it == _headers.end()) { + return kNull; } return it->second; } -const string &Parser::Content() const { - return _strContent; +const string &Parser::content() const { + return _content; } -void Parser::Clear() { - _strMethod.clear(); - _strUrl.clear(); +void Parser::clear() { + _method.clear(); + _url.clear(); _params.clear(); - _strTail.clear(); - _strContent.clear(); - _mapHeaders.clear(); - _mapUrlArgs.clear(); + _protocol.clear(); + _content.clear(); + _headers.clear(); + _url_args.clear(); } -const string &Parser::Params() const { +const string &Parser::params() const { return _params; } void Parser::setUrl(string url) { - this->_strUrl = std::move(url); + _url = std::move(url); } void Parser::setContent(string content) { - this->_strContent = std::move(content); + _content = std::move(content); } StrCaseMap &Parser::getHeader() const { - return _mapHeaders; + return _headers; } StrCaseMap &Parser::getUrlArgs() const { - return _mapUrlArgs; + return _url_args; } StrCaseMap Parser::parseArgs(const string &str, const char *pair_delim, const char *key_delim) { StrCaseMap ret; auto arg_vec = split(str, pair_delim); - for (string &key_val : arg_vec) { + for (auto &key_val : arg_vec) { if (key_val.empty()) { - //忽略 + // 忽略 continue; } - auto key = trim(FindField(key_val.data(), NULL, key_delim)); - if (!key.empty()) { - auto val = trim(FindField(key_val.data(), key_delim, NULL)); - ret.emplace_force(key, val); + auto pos = key_val.find(key_delim); + if (pos != string::npos) { + auto key = trim(std::string(key_val, 0, pos)); + auto val = trim(key_val.substr(pos + strlen(key_delim))); + ret.emplace_force(std::move(key), std::move(val)); } else { trim(key_val); if (!key_val.empty()) { - ret.emplace_force(key_val, ""); + ret.emplace_force(std::move(key_val), ""); } } } return ret; } -std::string Parser::merge_url(const string &base_url, const string &path) { - //以base_url为基础, 合并path路径生成新的url, path支持相对路径和绝对路径 + +std::string Parser::mergeUrl(const string &base_url, const string &path) { + // 以base_url为基础, 合并path路径生成新的url, path支持相对路径和绝对路径 if (base_url.empty()) { return path; } @@ -234,43 +258,45 @@ std::string Parser::merge_url(const string &base_url, const string &path) { } return final_url.str(); } + void RtspUrl::parse(const string &strUrl) { - auto schema = FindField(strUrl.data(), nullptr, "://"); + auto schema = findSubString(strUrl.data(), nullptr, "://"); bool is_ssl = strcasecmp(schema.data(), "rtsps") == 0; - //查找"://"与"/"之间的字符串,用于提取用户名密码 - auto middle_url = FindField(strUrl.data(), "://", "/"); + // 查找"://"与"/"之间的字符串,用于提取用户名密码 + auto middle_url = findSubString(strUrl.data(), "://", "/"); if (middle_url.empty()) { - middle_url = FindField(strUrl.data(), "://", nullptr); + middle_url = findSubString(strUrl.data(), "://", nullptr); } auto pos = middle_url.rfind('@'); if (pos == string::npos) { - //并没有用户名密码 + // 并没有用户名密码 return setup(is_ssl, strUrl, "", ""); } - //包含用户名密码 + // 包含用户名密码 auto user_pwd = middle_url.substr(0, pos); auto suffix = strUrl.substr(schema.size() + 3 + pos + 1); auto url = StrPrinter << "rtsp://" << suffix << endl; if (user_pwd.find(":") == string::npos) { return setup(is_ssl, url, user_pwd, ""); } - auto user = FindField(user_pwd.data(), nullptr, ":"); - auto pwd = FindField(user_pwd.data(), ":", nullptr); + auto user = findSubString(user_pwd.data(), nullptr, ":"); + auto pwd = findSubString(user_pwd.data(), ":", nullptr); return setup(is_ssl, url, user, pwd); } void RtspUrl::setup(bool is_ssl, const string &url, const string &user, const string &passwd) { - auto ip = FindField(url.data(), "://", "/"); + auto ip = findSubString(url.data(), "://", "/"); if (ip.empty()) { - ip = split(FindField(url.data(), "://", NULL), "?")[0]; + ip = split(findSubString(url.data(), "://", NULL), "?")[0]; } uint16_t port = is_ssl ? 322 : 554; splitUrl(ip, ip, port); + _url = std::move(url); - _user = std::move(user); - _passwd = std::move(passwd); + _user = strCoding::UrlDecode(std::move(user)); + _passwd = strCoding::UrlDecode(std::move(passwd)); _host = std::move(ip); _port = port; _is_ssl = is_ssl; @@ -289,7 +315,7 @@ void splitUrl(const std::string &url, std::string &host, uint16_t &port) { CHECK(!url.empty(), "empty url"); auto pos = url.rfind(':'); if (pos == string::npos || url.back() == ']') { - //没有冒号,未指定端口;或者是纯粹的ipv6地址 + // 没有冒号,未指定端口;或者是纯粹的ipv6地址 host = url; checkHost(host); return; @@ -299,7 +325,6 @@ void splitUrl(const std::string &url, std::string &host, uint16_t &port) { host = url.substr(0, pos); checkHost(host); } - #if 0 //测试代码 static onceToken token([](){ @@ -312,4 +337,4 @@ static onceToken token([](){ }); #endif -}//namespace mediakit +} // namespace mediakit diff --git a/src/Common/Parser.h b/src/Common/Parser.h index 6f312664..3741f4bc 100644 --- a/src/Common/Parser.h +++ b/src/Common/Parser.h @@ -17,15 +17,13 @@ namespace mediakit { -//从字符串中提取子字符串 -std::string FindField(const char *buf, const char *start, const char *end, size_t bufSize = 0); -//把url解析为主机地址和端口号,兼容ipv4/ipv6/dns -void splitUrl(const std::string &url, std::string &host, uint16_t& port); +// 从字符串中提取子字符串 +std::string findSubString(const char *buf, const char *start, const char *end, size_t buf_size = 0); +// 把url解析为主机地址和端口号,兼容ipv4/ipv6/dns +void splitUrl(const std::string &url, std::string &host, uint16_t &port); struct StrCaseCompare { - bool operator()(const std::string &__x, const std::string &__y) const { - return strcasecmp(__x.data(), __y.data()) < 0; - } + bool operator()(const std::string &__x, const std::string &__y) const { return strcasecmp(__x.data(), __y.data()) < 0; } }; class StrCaseMap : public std::multimap { @@ -42,84 +40,87 @@ public: return it->second; } - template - void emplace(const std::string &k, V &&v) { + template + void emplace(K &&k, V &&v) { auto it = find(k); if (it != end()) { return; } - Super::emplace(k, std::forward(v)); + Super::emplace(std::forward(k), std::forward(v)); } - template - void emplace_force(const std::string k, V &&v) { - Super::emplace(k, std::forward(v)); + template + void emplace_force(K &&k, V &&v) { + Super::emplace(std::forward(k), std::forward(v)); } }; -//rtsp/http/sip解析类 +// rtsp/http/sip解析类 class Parser { public: Parser() = default; ~Parser() = default; - //解析信令 - void Parse(const char *buf); + // 解析http/rtsp/sip请求,需要确保buf以\0结尾 + void parse(const char *buf, size_t size); - //获取命令字 - const std::string &Method() const; + // 获取命令字,如GET/POST + const std::string &method() const; - //获取中间url,不包含?后面的参数 - const std::string &Url() const; + // 请求时,获取中间url,不包含?后面的参数 + const std::string &url() const; + // 回复时,获取状态码,如200/404 + const std::string &status() const; - //获取中间url,包含?后面的参数 - std::string FullUrl() const; + // 获取中间url,包含?后面的参数 + std::string fullUrl() const; - //获取命令协议名 - const std::string &Tail() const; + // 请求时,获取协议名,如HTTP/1.1 + const std::string &protocol() const; + // 回复时,获取状态字符串,如 OK/Not Found + const std::string &statusStr() const; - //根据header key名,获取请求header value值 + // 根据header key名,获取请求header value值 const std::string &operator[](const char *name) const; - //获取http body或sdp - const std::string &Content() const; + // 获取http body或sdp + const std::string &content() const; - //清空,为了重用 - void Clear(); + // 清空,为了重用 + void clear(); - //获取?后面的参数 - const std::string &Params() const; + // 获取?后面的参数 + const std::string ¶ms() const; - //重新设置url + // 重新设置url void setUrl(std::string url); - //重新设置content + // 重新设置content void setContent(std::string content); - //获取header列表 + // 获取header列表 StrCaseMap &getHeader() const; - //获取url参数列表 + // 获取url参数列表 StrCaseMap &getUrlArgs() const; - //解析?后面的参数 + // 解析?后面的参数 static StrCaseMap parseArgs(const std::string &str, const char *pair_delim = "&", const char *key_delim = "="); - static std::string merge_url(const std::string &base_url, const std::string &path); + static std::string mergeUrl(const std::string &base_url, const std::string &path); private: - std::string _strMethod; - std::string _strUrl; - std::string _strTail; - std::string _strContent; - std::string _strNull; + std::string _method; + std::string _url; + std::string _protocol; + std::string _content; std::string _params; - mutable StrCaseMap _mapHeaders; - mutable StrCaseMap _mapUrlArgs; + mutable StrCaseMap _headers; + mutable StrCaseMap _url_args; }; -//解析rtsp url的工具类 -class RtspUrl{ +// 解析rtsp url的工具类 +class RtspUrl { public: bool _is_ssl; uint16_t _port; @@ -134,9 +135,9 @@ public: void parse(const std::string &url); private: - void setup(bool,const std::string &, const std::string &, const std::string &); + void setup(bool, const std::string &, const std::string &, const std::string &); }; -}//namespace mediakit +} // namespace mediakit -#endif //ZLMEDIAKIT_PARSER_H +#endif // ZLMEDIAKIT_PARSER_H diff --git a/src/Common/Stamp.cpp b/src/Common/Stamp.cpp index df7f7368..2656a176 100644 --- a/src/Common/Stamp.cpp +++ b/src/Common/Stamp.cpp @@ -10,7 +10,7 @@ #include "Stamp.h" -//时间戳最大允许跳变3秒,主要是防止网络抖动导致的跳变 +// 时间戳最大允许跳变3秒,主要是防止网络抖动导致的跳变 #define MAX_DELTA_STAMP (3 * 1000) #define STAMP_LOOP_DELTA (60 * 1000) #define MAX_CTS 500 @@ -25,51 +25,52 @@ int64_t DeltaStamp::relativeStamp(int64_t stamp) { return _relative_stamp; } -int64_t DeltaStamp::relativeStamp(){ +int64_t DeltaStamp::relativeStamp() { return _relative_stamp; } int64_t DeltaStamp::deltaStamp(int64_t stamp) { - if(!_last_stamp){ - //第一次计算时间戳增量,时间戳增量为0 - if(stamp){ + if (!_last_stamp) { + // 第一次计算时间戳增量,时间戳增量为0 + if (stamp) { _last_stamp = stamp; } return 0; } int64_t ret = stamp - _last_stamp; - if(ret >= 0){ - //时间戳增量为正,返回之 + if (ret >= 0) { + // 时间戳增量为正,返回之 _last_stamp = stamp; - //在直播情况下,时间戳增量不得大于MAX_DELTA_STAMP - return ret < MAX_DELTA_STAMP ? ret : 0; + // 在直播情况下,时间戳增量不得大于MAX_DELTA_STAMP,否则强制相对时间戳加1 + return ret < MAX_DELTA_STAMP ? ret : 1; } - //时间戳增量为负,说明时间戳回环了或回退了 + // 时间戳增量为负,说明时间戳回环了或回退了 _last_stamp = stamp; - //如果时间戳回退不多,那么返回负值 - return -ret < MAX_CTS ? ret : 0; + + // 如果时间戳回退不多,那么返回负值,否则返回加1 + return -ret < MAX_DELTA_STAMP ? ret : 1; } void Stamp::setPlayBack(bool playback) { _playback = playback; } -void Stamp::syncTo(Stamp &other){ +void Stamp::syncTo(Stamp &other) { _sync_master = &other; } -//限制dts回退 -void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) { +// 限制dts回退 +void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) { revise_l(dts, pts, dts_out, pts_out, modifyStamp); if (_playback) { - //回放允许时间戳回退 + // 回放允许时间戳回退 return; } if (dts_out < _last_dts_out) { -// WarnL << "dts回退:" << dts_out << " < " << _last_dts_out; + // WarnL << "dts回退:" << dts_out << " < " << _last_dts_out; dts_out = _last_dts_out; pts_out = _last_pts_out; return; @@ -78,35 +79,35 @@ void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, _last_pts_out = pts_out; } -//音视频时间戳同步 -void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) { +// 音视频时间戳同步 +void Stamp::revise_l(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) { revise_l2(dts, pts, dts_out, pts_out, modifyStamp); if (!_sync_master || modifyStamp || _playback) { - //自动生成时间戳或回放或同步完毕 + // 自动生成时间戳或回放或同步完毕 return; } if (_sync_master && _sync_master->_last_dts_in) { - //音视频dts当前时间差 + // 音视频dts当前时间差 int64_t dts_diff = _last_dts_in - _sync_master->_last_dts_in; if (ABS(dts_diff) < 5000) { - //如果绝对时间戳小于5秒,那么说明他们的起始时间戳是一致的,那么强制同步 + // 如果绝对时间戳小于5秒,那么说明他们的起始时间戳是一致的,那么强制同步 _relative_stamp = _sync_master->_relative_stamp + dts_diff; } - //下次不用再强制同步 + // 下次不用再强制同步 _sync_master = nullptr; } } -//求取相对时间戳 -void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) { +// 求取相对时间戳 +void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, bool modifyStamp) { if (!pts) { - //没有播放时间戳,使其赋值为解码时间戳 + // 没有播放时间戳,使其赋值为解码时间戳 pts = dts; } if (_playback) { - //这是点播 + // 这是点播 dts_out = dts; pts_out = pts; _relative_stamp = dts_out; @@ -114,13 +115,13 @@ void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_o return; } - //pts和dts的差值 + // pts和dts的差值 int64_t pts_dts_diff = pts - dts; if (_last_dts_in != dts) { - //时间戳发生变更 + // 时间戳发生变更 if (modifyStamp) { - //内部自己生产时间戳 + // 内部自己生产时间戳 _relative_stamp = _ticker.elapsedTime(); } else { _relative_stamp += deltaStamp(dts); @@ -131,7 +132,7 @@ void Stamp::revise_l2(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_o //////////////以下是播放时间戳的计算////////////////// if (ABS(pts_dts_diff) > MAX_CTS) { - //如果差值太大,则认为由于回环导致时间戳错乱了 + // 如果差值太大,则认为由于回环导致时间戳错乱了 pts_dts_diff = 0; } @@ -146,156 +147,157 @@ int64_t Stamp::getRelativeStamp() const { return _relative_stamp; } -bool DtsGenerator::getDts(uint64_t pts, uint64_t &dts){ +bool DtsGenerator::getDts(uint64_t pts, uint64_t &dts) { bool ret = false; if (pts == _last_pts) { - //pts未变,说明dts也不会变,返回上次dts + // pts未变,说明dts也不会变,返回上次dts if (_last_dts) { dts = _last_dts; ret = true; } } else { - //pts变了,尝试计算dts + // pts变了,尝试计算dts ret = getDts_l(pts, dts); if (ret) { - //获取到了dts,保存本次结果 + // 获取到了dts,保存本次结果 _last_dts = dts; } } if (!ret) { - //pts排序列队长度还不知道,也就是不知道有没有B帧, - //那么先强制dts == pts,这样可能导致有B帧的情况下,起始画面有几帧回退 + // pts排序列队长度还不知道,也就是不知道有没有B帧, + // 那么先强制dts == pts,这样可能导致有B帧的情况下,起始画面有几帧回退 dts = pts; } - //记录上次pts + // 记录上次pts _last_pts = pts; return ret; } -//该算法核心思想是对pts进行排序,排序好的pts就是dts。 -//排序有一定的滞后性,那么需要加上排序导致的时间戳偏移量 -bool DtsGenerator::getDts_l(uint64_t pts, uint64_t &dts){ - if(_sorter_max_size == 1){ - //没有B帧,dts就等于pts +// 该算法核心思想是对pts进行排序,排序好的pts就是dts。 +// 排序有一定的滞后性,那么需要加上排序导致的时间戳偏移量 +bool DtsGenerator::getDts_l(uint64_t pts, uint64_t &dts) { + if (_sorter_max_size == 1) { + // 没有B帧,dts就等于pts dts = pts; return true; } - if(!_sorter_max_size){ - //尚未计算出pts排序列队长度(也就是P帧间B帧个数) - if(pts > _last_max_pts){ - //pts时间戳增加了,那么说明这帧画面不是B帧(说明是P帧或关键帧) - if(_frames_since_last_max_pts && _count_sorter_max_size++ > 0){ - //已经出现多次非B帧的情况,那么我们就能知道P帧间B帧的个数 + if (!_sorter_max_size) { + // 尚未计算出pts排序列队长度(也就是P帧间B帧个数) + if (pts > _last_max_pts) { + // pts时间戳增加了,那么说明这帧画面不是B帧(说明是P帧或关键帧) + if (_frames_since_last_max_pts && _count_sorter_max_size++ > 0) { + // 已经出现多次非B帧的情况,那么我们就能知道P帧间B帧的个数 _sorter_max_size = _frames_since_last_max_pts; - //我们记录P帧间时间间隔(也就是多个B帧时间戳增量累计) + // 我们记录P帧间时间间隔(也就是多个B帧时间戳增量累计) _dts_pts_offset = (pts - _last_max_pts); - //除以2,防止dts大于pts + // 除以2,防止dts大于pts _dts_pts_offset /= 2; } - //遇到P帧或关键帧,连续B帧计数清零 + // 遇到P帧或关键帧,连续B帧计数清零 _frames_since_last_max_pts = 0; - //记录上次非B帧的pts时间戳(同时也是dts),用于统计连续B帧时间戳增量 + // 记录上次非B帧的pts时间戳(同时也是dts),用于统计连续B帧时间戳增量 _last_max_pts = pts; } - //如果pts时间戳小于上一个P帧,那么断定这个是B帧,我们记录B帧连续个数 + // 如果pts时间戳小于上一个P帧,那么断定这个是B帧,我们记录B帧连续个数 ++_frames_since_last_max_pts; } - //pts放入排序缓存列队,缓存列队最大等于连续B帧个数 + // pts放入排序缓存列队,缓存列队最大等于连续B帧个数 _pts_sorter.emplace(pts); - if(_sorter_max_size && _pts_sorter.size() > _sorter_max_size){ - //如果启用了pts排序(意味着存在B帧),并且pts排序缓存列队长度大于连续B帧个数, - //意味着后续的pts都会比最早的pts大,那么说明可以取出最早的pts了,这个pts将当做该帧的dts基准 + if (_sorter_max_size && _pts_sorter.size() > _sorter_max_size) { + // 如果启用了pts排序(意味着存在B帧),并且pts排序缓存列队长度大于连续B帧个数, + // 意味着后续的pts都会比最早的pts大,那么说明可以取出最早的pts了,这个pts将当做该帧的dts基准 auto it = _pts_sorter.begin(); - //由于该pts是前面偏移了个_sorter_max_size帧的pts(也就是那帧画面的dts), - //那么我们加上时间戳偏移量,基本等于该帧的dts + // 由于该pts是前面偏移了个_sorter_max_size帧的pts(也就是那帧画面的dts), + // 那么我们加上时间戳偏移量,基本等于该帧的dts dts = *it + _dts_pts_offset; - if(dts > pts){ - //dts不能大于pts(基本不可能到达这个逻辑) + if (dts > pts) { + // dts不能大于pts(基本不可能到达这个逻辑) dts = pts; } - //pts排序缓存出列 + // pts排序缓存出列 _pts_sorter.erase(it); return true; } - //排序缓存尚未满 + // 排序缓存尚未满 return false; } void NtpStamp::setNtpStamp(uint32_t rtp_stamp, uint64_t ntp_stamp_ms) { - update(rtp_stamp, ntp_stamp_ms); -} - -void NtpStamp::update(uint32_t rtp_stamp, uint64_t ntp_stamp_ms) { - if (ntp_stamp_ms == 0) { - //实测发现有些rtsp服务器发送的rtp时间戳和ntp时间戳一直为0 + if (!ntp_stamp_ms || !rtp_stamp) { + // 实测发现有些rtsp服务器发送的rtp时间戳和ntp时间戳一直为0 + WarnL << "Invalid sender report rtcp, ntp_stamp_ms = " << ntp_stamp_ms << ", rtp_stamp = " << rtp_stamp; return; } + update(rtp_stamp, ntp_stamp_ms * 1000); +} + +void NtpStamp::update(uint32_t rtp_stamp, uint64_t ntp_stamp_us) { _last_rtp_stamp = rtp_stamp; - _last_ntp_stamp_ms = ntp_stamp_ms; + _last_ntp_stamp_us = ntp_stamp_us; } uint64_t NtpStamp::getNtpStamp(uint32_t rtp_stamp, uint32_t sample_rate) { if (rtp_stamp == _last_rtp_stamp) { - return _last_ntp_stamp_ms; + return _last_ntp_stamp_us / 1000; } - return getNtpStamp_l(rtp_stamp, sample_rate); + return getNtpStampUS(rtp_stamp, sample_rate) / 1000; } -uint64_t NtpStamp::getNtpStamp_l(uint32_t rtp_stamp, uint32_t sample_rate) { - if (!_last_ntp_stamp_ms) { - //尚未收到sender report rtcp包,那么赋值为本地系统时间戳吧 - update(rtp_stamp, getCurrentMillisecond(true)); +uint64_t NtpStamp::getNtpStampUS(uint32_t rtp_stamp, uint32_t sample_rate) { + if (!_last_ntp_stamp_us) { + // 尚未收到sender report rtcp包,那么赋值为本地系统时间戳吧 + update(rtp_stamp, getCurrentMicrosecond(true)); } - //rtp时间戳正增长 + // rtp时间戳正增长 if (rtp_stamp >= _last_rtp_stamp) { - auto diff = static_cast((rtp_stamp - _last_rtp_stamp) / (sample_rate / 1000.0f)); - if (diff < MAX_DELTA_STAMP) { - //时间戳正常增长 - update(rtp_stamp, _last_ntp_stamp_ms + diff); - return _last_ntp_stamp_ms; + auto diff_us = static_cast((rtp_stamp - _last_rtp_stamp) / (sample_rate / 1000000.0f)); + if (diff_us < MAX_DELTA_STAMP * 1000) { + // 时间戳正常增长 + update(rtp_stamp, _last_ntp_stamp_us + diff_us); + return _last_ntp_stamp_us; } - //时间戳大幅跳跃 - uint64_t loop_delta = STAMP_LOOP_DELTA * sample_rate / 1000; - if (_last_rtp_stamp < loop_delta && rtp_stamp > UINT32_MAX - loop_delta) { - //应该是rtp时间戳溢出+乱序 - uint64_t max_rtp_ms = uint64_t(UINT32_MAX) * 1000 / sample_rate; - return _last_ntp_stamp_ms + diff - max_rtp_ms; + // 时间戳大幅跳跃 + uint64_t loop_delta_hz = STAMP_LOOP_DELTA * sample_rate / 1000; + if (_last_rtp_stamp < loop_delta_hz && rtp_stamp > UINT32_MAX - loop_delta_hz) { + // 应该是rtp时间戳溢出+乱序 + uint64_t max_rtp_us = uint64_t(UINT32_MAX) * 1000000 / sample_rate; + return _last_ntp_stamp_us + diff_us - max_rtp_us; } - //不明原因的时间戳大幅跳跃,直接返回上次值 + // 不明原因的时间戳大幅跳跃,直接返回上次值 WarnL << "rtp stamp abnormal increased:" << _last_rtp_stamp << " -> " << rtp_stamp; - update(rtp_stamp, _last_ntp_stamp_ms); - return _last_ntp_stamp_ms; + update(rtp_stamp, _last_ntp_stamp_us); + return _last_ntp_stamp_us; } - //rtp时间戳负增长 - auto diff = static_cast((_last_rtp_stamp - rtp_stamp) / (sample_rate / 1000.0f)); - if (diff < MAX_DELTA_STAMP) { - //正常范围的时间戳回退,说明收到rtp乱序了 - return _last_ntp_stamp_ms - diff; + // rtp时间戳负增长 + auto diff_us = static_cast((_last_rtp_stamp - rtp_stamp) / (sample_rate / 1000000.0f)); + if (diff_us < MAX_DELTA_STAMP * 1000) { + // 正常范围的时间戳回退,说明收到rtp乱序了 + return _last_ntp_stamp_us - diff_us; } - //时间戳大幅度回退 - uint64_t loop_delta = STAMP_LOOP_DELTA * sample_rate / 1000; - if (rtp_stamp < loop_delta && _last_rtp_stamp > UINT32_MAX - loop_delta) { - //确定是时间戳溢出 - uint64_t max_rtp_ms = uint64_t(UINT32_MAX) * 1000 / sample_rate; - update(rtp_stamp, _last_ntp_stamp_ms + (max_rtp_ms - diff)); - return _last_ntp_stamp_ms; + // 时间戳大幅度回退 + uint64_t loop_delta_hz = STAMP_LOOP_DELTA * sample_rate / 1000; + if (rtp_stamp < loop_delta_hz && _last_rtp_stamp > UINT32_MAX - loop_delta_hz) { + // 确定是时间戳溢出 + uint64_t max_rtp_us = uint64_t(UINT32_MAX) * 1000000 / sample_rate; + update(rtp_stamp, _last_ntp_stamp_us + (max_rtp_us - diff_us)); + return _last_ntp_stamp_us; } - //不明原因的时间戳回退,直接返回上次值 + // 不明原因的时间戳回退,直接返回上次值 WarnL << "rtp stamp abnormal reduced:" << _last_rtp_stamp << " -> " << rtp_stamp; - update(rtp_stamp, _last_ntp_stamp_ms); - return _last_ntp_stamp_ms; + update(rtp_stamp, _last_ntp_stamp_us); + return _last_ntp_stamp_us; } -}//namespace mediakit +} // namespace mediakit diff --git a/src/Common/Stamp.h b/src/Common/Stamp.h index cc2dcae0..03cb7bb7 100644 --- a/src/Common/Stamp.h +++ b/src/Common/Stamp.h @@ -125,12 +125,12 @@ public: uint64_t getNtpStamp(uint32_t rtp_stamp, uint32_t sample_rate); private: - void update(uint32_t rtp_stamp, uint64_t ntp_stamp_ms); - uint64_t getNtpStamp_l(uint32_t rtp_stamp, uint32_t sample_rate); + void update(uint32_t rtp_stamp, uint64_t ntp_stamp_us); + uint64_t getNtpStampUS(uint32_t rtp_stamp, uint32_t sample_rate); private: uint32_t _last_rtp_stamp = 0; - uint64_t _last_ntp_stamp_ms = 0; + uint64_t _last_ntp_stamp_us = 0; }; }//namespace mediakit diff --git a/src/Common/config.cpp b/src/Common/config.cpp index cae98d55..6e75e27d 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -9,6 +9,7 @@ */ #include "Common/config.h" +#include "MediaSource.h" #include "Util/NoticeCenter.h" #include "Util/logger.h" #include "Util/onceToken.h" @@ -30,7 +31,7 @@ bool loadIniConfig(const char *ini_path) { } try { mINI::Instance().parseFile(ini); - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig); + NOTICE_EMIT(BroadcastReloadConfigArgs, Broadcast::kBroadcastReloadConfig); return true; } catch (std::exception &) { InfoL << "dump ini file to:" << ini; @@ -98,9 +99,11 @@ namespace Protocol { const string kModifyStamp = PROTOCOL_FIELD "modify_stamp"; const string kEnableAudio = PROTOCOL_FIELD "enable_audio"; const string kAddMuteAudio = PROTOCOL_FIELD "add_mute_audio"; +const string kAutoClose = PROTOCOL_FIELD "auto_close"; const string kContinuePushMS = PROTOCOL_FIELD "continue_push_ms"; const string kEnableHls = PROTOCOL_FIELD "enable_hls"; +const string kEnableHlsFmp4 = PROTOCOL_FIELD "enable_hls_fmp4"; const string kEnableMP4 = PROTOCOL_FIELD "enable_mp4"; const string kEnableRtsp = PROTOCOL_FIELD "enable_rtsp"; const string kEnableRtmp = PROTOCOL_FIELD "enable_rtmp"; @@ -120,12 +123,14 @@ const string kTSDemand = PROTOCOL_FIELD "ts_demand"; const string kFMP4Demand = PROTOCOL_FIELD "fmp4_demand"; static onceToken token([]() { - mINI::Instance()[kModifyStamp] = 0; + mINI::Instance()[kModifyStamp] = (int)ProtocolOption::kModifyStampRelative; mINI::Instance()[kEnableAudio] = 1; mINI::Instance()[kAddMuteAudio] = 1; mINI::Instance()[kContinuePushMS] = 15000; + mINI::Instance()[kAutoClose] = 0; mINI::Instance()[kEnableHls] = 1; + mINI::Instance()[kEnableHlsFmp4] = 0; mINI::Instance()[kEnableMP4] = 0; mINI::Instance()[kEnableRtsp] = 1; mINI::Instance()[kEnableRtmp] = 1; @@ -159,6 +164,8 @@ const string kNotFound = HTTP_FIELD "notFound"; 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; @@ -186,6 +193,8 @@ static onceToken token([]() { << endl; mINI::Instance()[kForbidCacheSuffix] = ""; mINI::Instance()[kForwardedIpHeader] = ""; + mINI::Instance()[kAllowCrossDomains] = 1; + mINI::Instance()[kAllowIPRange] = "::1,127.0.0.1,172.16.0.0-172.31.255.255,192.168.0.0-192.168.255.255,10.0.0.0-10.255.255.255"; }); } // namespace Http @@ -206,6 +215,7 @@ const string kHandshakeSecond = RTSP_FIELD "handshakeSecond"; const string kKeepAliveSecond = RTSP_FIELD "keepAliveSecond"; const string kDirectProxy = RTSP_FIELD "directProxy"; const string kLowLatency = RTSP_FIELD"lowLatency"; +const string kRtpTransportType = RTSP_FIELD"rtpTransportType"; static onceToken token([]() { // 默认Md5方式认证 @@ -214,18 +224,17 @@ static onceToken token([]() { mINI::Instance()[kKeepAliveSecond] = 15; mINI::Instance()[kDirectProxy] = 1; mINI::Instance()[kLowLatency] = 0; + mINI::Instance()[kRtpTransportType] = -1; }); } // namespace Rtsp ////////////RTMP服务器配置/////////// namespace Rtmp { #define RTMP_FIELD "rtmp." -const string kModifyStamp = RTMP_FIELD "modifyStamp"; const string kHandshakeSecond = RTMP_FIELD "handshakeSecond"; const string kKeepAliveSecond = RTMP_FIELD "keepAliveSecond"; static onceToken token([]() { - mINI::Instance()[kModifyStamp] = false; mINI::Instance()[kHandshakeSecond] = 15; mINI::Instance()[kKeepAliveSecond] = 15; }); @@ -239,15 +248,15 @@ const string kVideoMtuSize = RTP_FIELD "videoMtuSize"; const string kAudioMtuSize = RTP_FIELD "audioMtuSize"; // rtp包最大长度限制,单位是KB const string kRtpMaxSize = RTP_FIELD "rtpMaxSize"; - const string kLowLatency = RTP_FIELD "lowLatency"; +const string kH264StapA = RTP_FIELD "h264_stap_a"; static onceToken token([]() { mINI::Instance()[kVideoMtuSize] = 1400; mINI::Instance()[kAudioMtuSize] = 600; mINI::Instance()[kRtpMaxSize] = 10; mINI::Instance()[kLowLatency] = 0; - + mINI::Instance()[kH264StapA] = 1; }); } // namespace Rtp diff --git a/src/Common/config.h b/src/Common/config.h index 146c6067..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 @@ -246,6 +252,10 @@ extern const std::string kDirMenu; extern const std::string kForbidCacheSuffix; // 可以把http代理前真实客户端ip放在http头中:https://github.com/ZLMediaKit/ZLMediaKit/issues/1388 extern const std::string kForwardedIpHeader; +// 是否允许所有跨域请求 +extern const std::string kAllowCrossDomains; +// 允许访问http api和http文件索引的ip地址范围白名单,置空情况下不做限制 +extern const std::string kAllowIPRange; } // namespace Http ////////////SHELL配置/////////// @@ -271,6 +281,11 @@ extern const std::string kDirectProxy; // rtsp 转发是否使用低延迟模式,当开启时,不会缓存rtp包,来提高并发,可以降低一帧的延迟 extern const std::string kLowLatency; + +//强制协商rtp传输方式 (0:TCP,1:UDP,2:MULTICAST,-1:不限制) +//当客户端发起RTSP SETUP的时候如果传输类型和此配置不一致则返回461 Unsupport Transport +//迫使客户端重新SETUP并切换到对应协议。目前支持FFMPEG和VLC +extern const std::string kRtpTransportType; } // namespace Rtsp ////////////RTMP服务器配置/////////// @@ -291,6 +306,8 @@ extern const std::string kAudioMtuSize; extern const std::string kRtpMaxSize; // rtp 打包时,低延迟开关,默认关闭(为0),h264存在一帧多个slice(NAL)的情况,在这种情况下,如果开启可能会导致画面花屏 extern const std::string kLowLatency; +//H264 rtp打包模式是否采用stap-a模式(为了在老版本浏览器上兼容webrtc)还是采用Single NAL unit packet per H.264 模式 +extern const std::string kH264StapA; } // namespace Rtp ////////////组播配置/////////// diff --git a/src/Common/macros.h b/src/Common/macros.h index dd9ad64d..8c86fc10 100644 --- a/src/Common/macros.h +++ b/src/Common/macros.h @@ -32,14 +32,6 @@ #define __LITTLE_ENDIAN LITTLE_ENDIAN #endif -#ifndef PACKED -#if !defined(_WIN32) -#define PACKED __attribute__((packed)) -#else -#define PACKED -#endif //! defined(_WIN32) -#endif - #ifndef CHECK #define CHECK(exp, ...) ::mediakit::Assert_ThrowCpp(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__) #endif // CHECK @@ -67,6 +59,7 @@ #define HLS_SCHEMA "hls" #define TS_SCHEMA "ts" #define FMP4_SCHEMA "fmp4" +#define HLS_FMP4_SCHEMA "hls.fmp4" #define SRT_SCHEMA "srt" #define DEFAULT_VHOST "__defaultVhost__" diff --git a/src/Http/strCoding.cpp b/src/Common/strCoding.cpp similarity index 100% rename from src/Http/strCoding.cpp rename to src/Common/strCoding.cpp diff --git a/src/Http/strCoding.h b/src/Common/strCoding.h similarity index 98% rename from src/Http/strCoding.h rename to src/Common/strCoding.h index 4be8328e..00177f74 100644 --- a/src/Http/strCoding.h +++ b/src/Common/strCoding.h @@ -13,7 +13,7 @@ #include #include - +#include namespace mediakit { class strCoding { diff --git a/src/Extension/AAC.cpp b/src/Extension/AAC.cpp index ea57d643..1047a8b3 100644 --- a/src/Extension/AAC.cpp +++ b/src/Extension/AAC.cpp @@ -246,7 +246,7 @@ AACTrack::AACTrack(const string &aac_cfg) { onReady(); } -const string &AACTrack::getAacCfg() const { +const string &AACTrack::getConfig() const { return _cfg; } @@ -342,7 +342,7 @@ Sdp::Ptr AACTrack::getSdp() { WarnL << getCodecName() << " Track未准备好"; return nullptr; } - return std::make_shared(getAacCfg(), getAudioSampleRate(), getAudioChannel(), getBitRate() / 1024); + return std::make_shared(getConfig(), getAudioSampleRate(), getAudioChannel(), getBitRate() / 1024); } }//namespace mediakit \ No newline at end of file diff --git a/src/Extension/AAC.h b/src/Extension/AAC.h index ad7479f6..b95fc6f2 100644 --- a/src/Extension/AAC.h +++ b/src/Extension/AAC.h @@ -44,7 +44,7 @@ public: /** * 获取aac 配置信息 */ - const std::string &getAacCfg() const; + const std::string &getConfig() const; bool ready() override; CodecId getCodecId() const override; diff --git a/src/Extension/AACRtmp.cpp b/src/Extension/AACRtmp.cpp index 1454a727..f1fdbab9 100644 --- a/src/Extension/AACRtmp.cpp +++ b/src/Extension/AACRtmp.cpp @@ -16,12 +16,9 @@ using namespace toolkit; namespace mediakit { -static string getAacCfg(const RtmpPacket &thiz) { +static string getConfig(const RtmpPacket &thiz) { string ret; - if (thiz.getMediaType() != FLV_CODEC_AAC) { - return ret; - } - if (!thiz.isCfgFrame()) { + if ((RtmpAudioCodec)thiz.getRtmpCodecId() != RtmpAudioCodec::aac) { return ret; } if (thiz.buffer.size() < 4) { @@ -33,8 +30,8 @@ static string getAacCfg(const RtmpPacket &thiz) { } void AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { - if (pkt->isCfgFrame()) { - _aac_cfg = getAacCfg(*pkt); + if (pkt->isConfigFrame()) { + _aac_cfg = getConfig(*pkt); if (!_aac_cfg.empty()) { onGetAAC(nullptr, 0, 0); } @@ -82,7 +79,7 @@ AACRtmpEncoder::AACRtmpEncoder(const Track::Ptr &track) { void AACRtmpEncoder::makeConfigPacket() { if (_track && _track->ready()) { //从track中和获取aac配置信息 - _aac_cfg = _track->getAacCfg(); + _aac_cfg = _track->getConfig(); } if (!_aac_cfg.empty()) { @@ -93,51 +90,45 @@ void AACRtmpEncoder::makeConfigPacket() { bool AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) { if (_aac_cfg.empty()) { if (frame->prefixSize()) { - //包含adts头,从adts头获取aac配置信息 - _aac_cfg = makeAacConfig((uint8_t *) (frame->data()), frame->prefixSize()); + // 包含adts头,从adts头获取aac配置信息 + _aac_cfg = makeAacConfig((uint8_t *)(frame->data()), frame->prefixSize()); } makeConfigPacket(); } - if(_aac_cfg.empty()){ + if (_aac_cfg.empty()) { return false; } - auto rtmpPkt = RtmpPacket::create(); - //header - uint8_t is_config = false; - rtmpPkt->buffer.push_back(_audio_flv_flags); - rtmpPkt->buffer.push_back(!is_config); - - //aac data - rtmpPkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); - - rtmpPkt->body_size = rtmpPkt->buffer.size(); - rtmpPkt->chunk_id = CHUNK_AUDIO; - rtmpPkt->stream_index = STREAM_MEDIA; - rtmpPkt->time_stamp = frame->dts(); - rtmpPkt->type_id = MSG_AUDIO; - RtmpCodec::inputRtmp(rtmpPkt); + auto pkt = RtmpPacket::create(); + // header + pkt->buffer.push_back(_audio_flv_flags); + pkt->buffer.push_back((uint8_t)RtmpAACPacketType::aac_raw); + // aac data + pkt->buffer.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_AUDIO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = frame->dts(); + pkt->type_id = MSG_AUDIO; + RtmpCodec::inputRtmp(pkt); return true; } void AACRtmpEncoder::makeAudioConfigPkt() { _audio_flv_flags = getAudioRtmpFlags(std::make_shared(_aac_cfg)); - auto rtmpPkt = RtmpPacket::create(); - - //header - uint8_t is_config = true; - rtmpPkt->buffer.push_back(_audio_flv_flags); - rtmpPkt->buffer.push_back(!is_config); - //aac config - rtmpPkt->buffer.append(_aac_cfg); - - rtmpPkt->body_size = rtmpPkt->buffer.size(); - rtmpPkt->chunk_id = CHUNK_AUDIO; - rtmpPkt->stream_index = STREAM_MEDIA; - rtmpPkt->time_stamp = 0; - rtmpPkt->type_id = MSG_AUDIO; - RtmpCodec::inputRtmp(rtmpPkt); + auto pkt = RtmpPacket::create(); + // header + pkt->buffer.push_back(_audio_flv_flags); + pkt->buffer.push_back((uint8_t)RtmpAACPacketType::aac_config_header); + // aac config + pkt->buffer.append(_aac_cfg); + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_AUDIO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = 0; + pkt->type_id = MSG_AUDIO; + RtmpCodec::inputRtmp(pkt); } }//namespace mediakit \ No newline at end of file diff --git a/src/Extension/AACRtp.cpp b/src/Extension/AACRtp.cpp index 97c84ff6..b5c5ab39 100644 --- a/src/Extension/AACRtp.cpp +++ b/src/Extension/AACRtp.cpp @@ -64,7 +64,7 @@ AACRtpDecoder::AACRtpDecoder(const Track::Ptr &track) { if (!aacTrack || !aacTrack->ready()) { WarnL << "该aac track无效!"; } else { - _aac_cfg = aacTrack->getAacCfg(); + _aac_cfg = aacTrack->getConfig(); } obtainFrame(); } diff --git a/src/Extension/CommonRtp.cpp b/src/Extension/CommonRtp.cpp index 9ba9b682..68767a6f 100644 --- a/src/Extension/CommonRtp.cpp +++ b/src/Extension/CommonRtp.cpp @@ -34,10 +34,10 @@ bool CommonRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool){ return false; } auto payload = rtp->getPayload(); - auto stamp = rtp->getStampMS(); + auto stamp = rtp->getStamp(); auto seq = rtp->getSeq(); - if (_frame->_dts != stamp || _frame->_buffer.size() > _max_frame_size) { + if (_last_stamp != stamp || _frame->_buffer.size() > _max_frame_size) { //时间戳发生变化或者缓存超过MAX_FRAME_SIZE,则清空上帧数据 if (!_frame->_buffer.empty()) { //有有效帧,则输出 @@ -46,7 +46,8 @@ bool CommonRtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool){ //新的一帧数据 obtainFrame(); - _frame->_dts = stamp; + _frame->_dts = rtp->getStampMS(); + _last_stamp = stamp; _drop_flag = false; } else if (_last_seq != 0 && (uint16_t)(_last_seq + 1) != seq) { //时间戳未发生变化,但是seq却不连续,说明中间rtp丢包了,那么整帧应该废弃 diff --git a/src/Extension/CommonRtp.h b/src/Extension/CommonRtp.h index 7df29af8..6553207e 100644 --- a/src/Extension/CommonRtp.h +++ b/src/Extension/CommonRtp.h @@ -50,6 +50,7 @@ private: private: bool _drop_flag = false; uint16_t _last_seq = 0; + uint64_t _last_stamp = 0; size_t _max_frame_size; CodecId _codec; FrameImp::Ptr _frame; diff --git a/src/Extension/Factory.cpp b/src/Extension/Factory.cpp index c85f920c..63a2bbd5 100644 --- a/src/Extension/Factory.cpp +++ b/src/Extension/Factory.cpp @@ -45,9 +45,9 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) { case CodecOpus : return std::make_shared(); case CodecAAC : { - string aac_cfg_str = FindField(track->_fmtp.data(), "config=", ";"); + string aac_cfg_str = findSubString(track->_fmtp.data(), "config=", ";"); if (aac_cfg_str.empty()) { - aac_cfg_str = FindField(track->_fmtp.data(), "config=", nullptr); + aac_cfg_str = findSubString(track->_fmtp.data(), "config=", nullptr); } if (aac_cfg_str.empty()) { //如果sdp中获取不到aac config信息,那么在rtp也无法获取,那么忽略该Track @@ -67,8 +67,8 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) { //a=fmtp:96 packetization-mode=1;profile-level-id=42C01F;sprop-parameter-sets=Z0LAH9oBQBboQAAAAwBAAAAPI8YMqA==,aM48gA== auto map = Parser::parseArgs(track->_fmtp, ";", "="); auto sps_pps = map["sprop-parameter-sets"]; - string base64_SPS = FindField(sps_pps.data(), NULL, ","); - string base64_PPS = FindField(sps_pps.data(), ",", NULL); + string base64_SPS = findSubString(sps_pps.data(), NULL, ","); + string base64_PPS = findSubString(sps_pps.data(), ",", NULL); auto sps = decodeBase64(base64_SPS); auto pps = decodeBase64(base64_PPS); if (sps.empty() || pps.empty()) { @@ -201,17 +201,20 @@ static CodecId getVideoCodecIdByAmf(const AMFValue &val){ } if (val.type() != AMF_NULL) { - auto type_id = val.as_integer(); + auto type_id = (RtmpVideoCodec)val.as_integer(); switch (type_id) { - case FLV_CODEC_H264 : return CodecH264; - case FLV_CODEC_H265 : return CodecH265; - default : WarnL << "暂不支持该视频Amf:" << type_id; return CodecInvalid; + case RtmpVideoCodec::h264: return CodecH264; + case RtmpVideoCodec::fourcc_hevc: + case RtmpVideoCodec::h265: return CodecH265; + case RtmpVideoCodec::fourcc_av1: return CodecAV1; + case RtmpVideoCodec::fourcc_vp9: return CodecVP9; + default: WarnL << "暂不支持该视频Amf:" << (int)type_id; return CodecInvalid; } } return CodecInvalid; } -Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0) { +Track::Ptr Factory::getTrackByCodecId(CodecId codecId, int sample_rate, int channels, int sample_bit) { switch (codecId){ case CodecH264 : return std::make_shared(); case CodecH265 : return std::make_shared(); @@ -243,13 +246,13 @@ static CodecId getAudioCodecIdByAmf(const AMFValue &val) { } if (val.type() != AMF_NULL) { - auto type_id = val.as_integer(); + auto type_id = (RtmpAudioCodec)val.as_integer(); switch (type_id) { - case FLV_CODEC_AAC : return CodecAAC; - case FLV_CODEC_G711A : return CodecG711A; - case FLV_CODEC_G711U : return CodecG711U; - case FLV_CODEC_OPUS : return CodecOpus; - default : WarnL << "暂不支持该音频Amf:" << type_id; return CodecInvalid; + case RtmpAudioCodec::aac : return CodecAAC; + case RtmpAudioCodec::g711a : return CodecG711A; + case RtmpAudioCodec::g711u : return CodecG711U; + case RtmpAudioCodec::opus : return CodecOpus; + default : WarnL << "暂不支持该音频Amf:" << (int)type_id; return CodecInvalid; } } @@ -291,13 +294,13 @@ RtmpCodec::Ptr Factory::getRtmpCodecByTrack(const Track::Ptr &track, bool is_enc } AMFValue Factory::getAmfByCodecId(CodecId codecId) { - switch (codecId){ - case CodecAAC: return AMFValue(FLV_CODEC_AAC); - case CodecH264: return AMFValue(FLV_CODEC_H264); - case CodecH265: return AMFValue(FLV_CODEC_H265); - case CodecG711A: return AMFValue(FLV_CODEC_G711A); - case CodecG711U: return AMFValue(FLV_CODEC_G711U); - case CodecOpus: return AMFValue(FLV_CODEC_OPUS); + switch (codecId) { + case CodecAAC: return AMFValue((int)RtmpAudioCodec::aac); + case CodecH264: return AMFValue((int)RtmpVideoCodec::h264); + case CodecH265: return AMFValue((int)RtmpVideoCodec::h265); + case CodecG711A: return AMFValue((int)RtmpAudioCodec::g711a); + case CodecG711U: return AMFValue((int)RtmpAudioCodec::g711u); + case CodecOpus: return AMFValue((int)RtmpAudioCodec::opus); default: return AMFValue(AMF_NULL); } } diff --git a/src/Extension/Factory.h b/src/Extension/Factory.h index 6b0c3c0a..d0045865 100644 --- a/src/Extension/Factory.h +++ b/src/Extension/Factory.h @@ -21,6 +21,16 @@ namespace mediakit{ class Factory { public: + + /** + * 根据codec_id 获取track + * @param codecId 编码id + * @param sample_rate 采样率,视频固定为90000 + * @param channels 音频通道数 + * @param sample_bit 音频采样位数 + */ + static Track::Ptr getTrackByCodecId(CodecId codecId, int sample_rate = 0, int channels = 0, int sample_bit = 0); + ////////////////////////////////rtsp相关////////////////////////////////// /** * 根据sdp生成Track对象 diff --git a/src/Extension/Frame.cpp b/src/Extension/Frame.cpp index 7cc3cb41..152eea1f 100644 --- a/src/Extension/Frame.cpp +++ b/src/Extension/Frame.cpp @@ -13,6 +13,7 @@ #include "H265.h" #include "Common/Parser.h" #include "Common/Stamp.h" +#include "Common/MediaSource.h" using namespace std; using namespace toolkit; @@ -31,11 +32,11 @@ Frame::Ptr Frame::getCacheAbleFrame(const Frame::Ptr &frame){ return std::make_shared(frame); } -FrameStamp::FrameStamp(Frame::Ptr frame, Stamp &stamp, bool modify_stamp) +FrameStamp::FrameStamp(Frame::Ptr frame, Stamp &stamp, int modify_stamp) { _frame = std::move(frame); - //覆盖时间戳 - stamp.revise(_frame->dts(), _frame->pts(), _dts, _pts, modify_stamp); + // kModifyStampSystem时采用系统时间戳,kModifyStampRelative采用相对时间戳 + stamp.revise(_frame->dts(), _frame->pts(), _dts, _pts, modify_stamp == ProtocolOption::kModifyStampSystem); } TrackType getTrackType(CodecId codecId) { diff --git a/src/Extension/Frame.h b/src/Extension/Frame.h index 4328e544..3609ff41 100644 --- a/src/Extension/Frame.h +++ b/src/Extension/Frame.h @@ -40,7 +40,7 @@ typedef enum { XX(CodecVP8, TrackVideo, 7, "VP8", PSI_STREAM_VP8) \ XX(CodecVP9, TrackVideo, 8, "VP9", PSI_STREAM_VP9) \ XX(CodecAV1, TrackVideo, 9, "AV1", PSI_STREAM_AV1) \ - XX(CodecJPEG, TrackVideo, 10, "JPEG", PSI_STREAM_JPEG_2000) + XX(CodecJPEG, TrackVideo, 10, "JPEG", PSI_STREAM_RESERVED) typedef enum { CodecInvalid = -1, @@ -492,7 +492,7 @@ private: class FrameStamp : public Frame { public: using Ptr = std::shared_ptr; - FrameStamp(Frame::Ptr frame, Stamp &stamp, bool modify_stamp); + FrameStamp(Frame::Ptr frame, Stamp &stamp, int modify_stamp); ~FrameStamp() override {} uint64_t dts() const override { return (uint64_t)_dts; } diff --git a/src/Extension/H264.cpp b/src/Extension/H264.cpp index 2395159b..eeccd600 100644 --- a/src/Extension/H264.cpp +++ b/src/Extension/H264.cpp @@ -12,9 +12,10 @@ #include "SPSParser.h" #include "Util/logger.h" #include "Util/base64.h" +#include "Common/config.h" -using namespace toolkit; using namespace std; +using namespace toolkit; namespace mediakit { @@ -248,7 +249,14 @@ public: _printer << "b=AS:" << bitrate << "\r\n"; } _printer << "a=rtpmap:" << payload_type << " " << getCodecName() << "/" << 90000 << "\r\n"; - _printer << "a=fmtp:" << payload_type << " packetization-mode=1; profile-level-id="; + + /** + Single NAI Unit Mode = 0. // Single NAI mode (Only nals from 1-23 are allowed) + Non Interleaved Mode = 1,// Non-interleaved Mode: 1-23,24 (STAP-A),28 (FU-A) are allowed + Interleaved Mode = 2, // 25 (STAP-B),26 (MTAP16),27 (MTAP24),28 (EU-A),and 29 (EU-B) are allowed. + **/ + GET_CONFIG(bool, h264_stap_a, Rtp::kH264StapA); + _printer << "a=fmtp:" << payload_type << " packetization-mode=" << h264_stap_a << "; profile-level-id="; uint32_t profile_level_id = 0; if (strSPS.length() >= 4) { // sanity check diff --git a/src/Extension/H264Rtmp.cpp b/src/Extension/H264Rtmp.cpp index f85ae425..9e62896d 100644 --- a/src/Extension/H264Rtmp.cpp +++ b/src/Extension/H264Rtmp.cpp @@ -30,10 +30,7 @@ H264Frame::Ptr H264RtmpDecoder::obtainFrame() { * 返回不带0x00 00 00 01头的sps pps */ static bool getH264Config(const RtmpPacket &thiz, string &sps, string &pps) { - if (thiz.getMediaType() != FLV_CODEC_H264) { - return false; - } - if (!thiz.isCfgFrame()) { + if ((RtmpVideoCodec)thiz.getRtmpCodecId() != RtmpVideoCodec::h264) { return false; } if (thiz.buffer.size() < 13) { @@ -59,7 +56,7 @@ static bool getH264Config(const RtmpPacket &thiz, string &sps, string &pps) { } void H264RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { - if (pkt->isCfgFrame()) { + if (pkt->isConfigFrame()) { //缓存sps pps,后续插入到I帧之前 if (!getH264Config(*pkt, _sps, _pps)) { WarnL << "get h264 sps/pps failed, rtmp packet is: " << hexdump(pkt->data(), pkt->size()); @@ -159,26 +156,21 @@ bool H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) { } return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) { - //flags - _rtmp_packet->buffer[0] = FLV_CODEC_H264 | ((have_key_frame ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4); - //not config - _rtmp_packet->buffer[1] = true; - int32_t cts = pts - dts; - if (cts < 0) { - cts = 0; - } - //cts - set_be24(&_rtmp_packet->buffer[2], cts); - - _rtmp_packet->time_stamp = dts; - _rtmp_packet->body_size = _rtmp_packet->buffer.size(); - _rtmp_packet->chunk_id = CHUNK_VIDEO; - _rtmp_packet->stream_index = STREAM_MEDIA; - _rtmp_packet->type_id = MSG_VIDEO; - //输出rtmp packet - RtmpCodec::inputRtmp(_rtmp_packet); - _rtmp_packet = nullptr; - }, &_rtmp_packet->buffer); + // flags + _rtmp_packet->buffer[0] = (uint8_t)RtmpVideoCodec::h264 | ((uint8_t)(have_key_frame ? RtmpFrameType::key_frame : RtmpFrameType::inter_frame) << 4); + _rtmp_packet->buffer[1] = (uint8_t)RtmpH264PacketType::h264_nalu; + int32_t cts = pts - dts; + // cts + set_be24(&_rtmp_packet->buffer[2], cts); + _rtmp_packet->time_stamp = dts; + _rtmp_packet->body_size = _rtmp_packet->buffer.size(); + _rtmp_packet->chunk_id = CHUNK_VIDEO; + _rtmp_packet->stream_index = STREAM_MEDIA; + _rtmp_packet->type_id = MSG_VIDEO; + // 输出rtmp packet + RtmpCodec::inputRtmp(_rtmp_packet); + _rtmp_packet = nullptr; + }, &_rtmp_packet->buffer); } void H264RtmpEncoder::makeVideoConfigPkt() { @@ -186,42 +178,39 @@ void H264RtmpEncoder::makeVideoConfigPkt() { WarnL << "sps长度不足4字节"; return; } - int8_t flags = FLV_CODEC_H264; - flags |= (FLV_KEY_FRAME << 4); - bool is_config = true; - - auto rtmpPkt = RtmpPacket::create(); - //header - rtmpPkt->buffer.push_back(flags); - rtmpPkt->buffer.push_back(!is_config); - //cts - rtmpPkt->buffer.append("\x0\x0\x0", 3); - - //AVCDecoderConfigurationRecord start - rtmpPkt->buffer.push_back(1); // version - rtmpPkt->buffer.push_back(_sps[1]); // profile - rtmpPkt->buffer.push_back(_sps[2]); // compat - rtmpPkt->buffer.push_back(_sps[3]); // level - rtmpPkt->buffer.push_back((char)0xff); // 6 bits reserved + 2 bits nal size length - 1 (11) - rtmpPkt->buffer.push_back((char)0xe1); // 3 bits reserved + 5 bits number of sps (00001) - //sps + auto flags = (uint8_t)RtmpVideoCodec::h264; + flags |= ((uint8_t)RtmpFrameType::key_frame << 4); + auto pkt = RtmpPacket::create(); + // header + pkt->buffer.push_back(flags); + pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header); + // cts + pkt->buffer.append("\x0\x0\x0", 3); + // AVCDecoderConfigurationRecord start + pkt->buffer.push_back(1); // version + pkt->buffer.push_back(_sps[1]); // profile + pkt->buffer.push_back(_sps[2]); // compat + pkt->buffer.push_back(_sps[3]); // level + pkt->buffer.push_back((char)0xff); // 6 bits reserved + 2 bits nal size length - 1 (11) + pkt->buffer.push_back((char)0xe1); // 3 bits reserved + 5 bits number of sps (00001) + // sps uint16_t size = (uint16_t)_sps.size(); size = htons(size); - rtmpPkt->buffer.append((char *) &size, 2); - rtmpPkt->buffer.append(_sps); - //pps - rtmpPkt->buffer.push_back(1); // version + pkt->buffer.append((char *)&size, 2); + pkt->buffer.append(_sps); + // pps + pkt->buffer.push_back(1); // version size = (uint16_t)_pps.size(); size = htons(size); - rtmpPkt->buffer.append((char *) &size, 2); - rtmpPkt->buffer.append(_pps); + pkt->buffer.append((char *)&size, 2); + pkt->buffer.append(_pps); - rtmpPkt->body_size = rtmpPkt->buffer.size(); - rtmpPkt->chunk_id = CHUNK_VIDEO; - rtmpPkt->stream_index = STREAM_MEDIA; - rtmpPkt->time_stamp = 0; - rtmpPkt->type_id = MSG_VIDEO; - RtmpCodec::inputRtmp(rtmpPkt); + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_VIDEO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = 0; + pkt->type_id = MSG_VIDEO; + RtmpCodec::inputRtmp(pkt); } }//namespace mediakit diff --git a/src/Extension/H264Rtp.cpp b/src/Extension/H264Rtp.cpp index f12218a4..28c775c6 100644 --- a/src/Extension/H264Rtp.cpp +++ b/src/Extension/H264Rtp.cpp @@ -13,9 +13,7 @@ namespace mediakit{ -#if defined(_WIN32) #pragma pack(push, 1) -#endif // defined(_WIN32) class FuFlags { public: @@ -30,11 +28,9 @@ public: unsigned end_bit: 1; unsigned start_bit: 1; #endif -} PACKED; +}; -#if defined(_WIN32) #pragma pack(pop) -#endif // defined(_WIN32) H264RtpDecoder::H264RtpDecoder() { _frame = obtainFrame(); @@ -209,8 +205,8 @@ void H264RtpEncoder::insertConfigFrame(uint64_t pts){ void H264RtpEncoder::packRtp(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){ if (len + 3 <= getMaxSize()) { - //STAP-A模式打包小于MTU - packRtpStapA(ptr, len, pts, is_mark, gop_pos); + // 采用STAP-A/Single NAL unit packet per H.264 模式 + packRtpSmallFrame(ptr, len, pts, is_mark, gop_pos); } else { //STAP-A模式打包会大于MTU,所以采用FU-A模式 packRtpFu(ptr, len, pts, is_mark, gop_pos); @@ -220,8 +216,8 @@ void H264RtpEncoder::packRtp(const char *ptr, size_t len, uint64_t pts, bool is_ void H264RtpEncoder::packRtpFu(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){ auto packet_size = getMaxSize() - 2; if (len <= packet_size + 1) { - //小于FU-A打包最小字节长度要求,采用STAP-A模式 - packRtpStapA(ptr, len, pts, is_mark, gop_pos); + // 小于FU-A打包最小字节长度要求,采用STAP-A/Single NAL unit packet per H.264 模式 + packRtpSmallFrame(ptr, len, pts, is_mark, gop_pos); return; } @@ -257,8 +253,17 @@ void H264RtpEncoder::packRtpFu(const char *ptr, size_t len, uint64_t pts, bool i } } +void H264RtpEncoder::packRtpSmallFrame(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos) { + GET_CONFIG(bool, h264_stap_a, Rtp::kH264StapA); + if (h264_stap_a) { + packRtpStapA(data, len, pts, is_mark, gop_pos); + } else { + packRtpSingleNalu(data, len, pts, is_mark, gop_pos); + } +} + void H264RtpEncoder::packRtpStapA(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){ - //如果帧长度不超过mtu,为了兼容性 webrtc,采用STAP-A模式打包 + // 如果帧长度不超过mtu,为了兼容性 webrtc,采用STAP-A模式打包 auto rtp = makeRtp(getTrackType(), nullptr, len + 3, is_mark, pts); uint8_t *payload = rtp->getPayload(); //STAP-A @@ -270,6 +275,11 @@ void H264RtpEncoder::packRtpStapA(const char *ptr, size_t len, uint64_t pts, boo RtpCodec::inputRtp(rtp, gop_pos); } +void H264RtpEncoder::packRtpSingleNalu(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos) { + // Single NAL unit packet per H.264 模式 + RtpCodec::inputRtp(makeRtp(getTrackType(), data, len, is_mark, pts), gop_pos); +} + bool H264RtpEncoder::inputFrame(const Frame::Ptr &frame) { auto ptr = frame->data() + frame->prefixSize(); switch (H264_TYPE(ptr[0])) { diff --git a/src/Extension/H264Rtp.h b/src/Extension/H264Rtp.h index 77bca5d0..31200cb8 100644 --- a/src/Extension/H264Rtp.h +++ b/src/Extension/H264Rtp.h @@ -28,7 +28,7 @@ public: using Ptr = std::shared_ptr; H264RtpDecoder(); - ~H264RtpDecoder() {} + ~H264RtpDecoder() override = default; /** * 输入264 rtp包 @@ -77,9 +77,10 @@ public: uint32_t sample_rate = 90000, uint8_t pt = 96, uint8_t interleaved = TrackVideo * 2); - ~H264RtpEncoder() {} - /** + ~H264RtpEncoder() override = default; + + /** * 输入264帧 * @param frame 帧数据,必须 */ @@ -96,6 +97,8 @@ private: void packRtp(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos); void packRtpFu(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos); void packRtpStapA(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos); + void packRtpSingleNalu(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos); + void packRtpSmallFrame(const char *data, size_t len, uint64_t pts, bool is_mark, bool gop_pos); private: Frame::Ptr _sps; diff --git a/src/Extension/H265Rtmp.cpp b/src/Extension/H265Rtmp.cpp index 493edac8..af685d36 100644 --- a/src/Extension/H265Rtmp.cpp +++ b/src/Extension/H265Rtmp.cpp @@ -12,12 +12,12 @@ #include "H265Rtmp.h" #ifdef ENABLE_MP4 #include "mpeg4-hevc.h" -#endif//ENABLE_MP4 +#endif // ENABLE_MP4 using namespace std; using namespace toolkit; -namespace mediakit{ +namespace mediakit { H265RtmpDecoder::H265RtmpDecoder() { _h265frame = obtainFrame(); @@ -30,46 +30,105 @@ H265Frame::Ptr H265RtmpDecoder::obtainFrame() { } #ifdef ENABLE_MP4 + +static bool decode_HEVCDecoderConfigurationRecord(uint8_t *extra, size_t bytes, string &frame) { + struct mpeg4_hevc_t hevc; + memset(&hevc, 0, sizeof(hevc)); + if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *)extra, bytes, &hevc) > 0) { + uint8_t *config = new uint8_t[bytes * 2]; + int size = mpeg4_hevc_to_nalu(&hevc, config, bytes * 2); + if (size > 4) { + frame.assign((char *)config + 4, size - 4); + } + delete[] config; + return size > 4; + } + return false; +} + /** * 返回不带0x00 00 00 01头的sps - * @return */ -static bool getH265ConfigFrame(const RtmpPacket &thiz,string &frame) { - if (thiz.getMediaType() != FLV_CODEC_H265) { - return false; - } - if (!thiz.isCfgFrame()) { +static bool getH265ConfigFrame(const RtmpPacket &thiz, string &frame) { + if ((RtmpVideoCodec)thiz.getRtmpCodecId() != RtmpVideoCodec::h265) { return false; } if (thiz.buffer.size() < 6) { WarnL << "bad H265 cfg!"; return false; } - - auto extra = thiz.buffer.data() + 5; - auto bytes = thiz.buffer.size() - 5; - - struct mpeg4_hevc_t hevc; - memset(&hevc, 0, sizeof(hevc)); - if (mpeg4_hevc_decoder_configuration_record_load((uint8_t *) extra, bytes, &hevc) > 0) { - uint8_t *config = new uint8_t[bytes * 2]; - int size = mpeg4_hevc_to_nalu(&hevc, config, bytes * 2); - if (size > 4) { - frame.assign((char *) config + 4, size - 4); - } - delete [] config; - return size > 4; - } - return false; + return decode_HEVCDecoderConfigurationRecord((uint8_t *)thiz.buffer.data() + 5, thiz.buffer.size() - 5, frame); } #endif void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { - if (pkt->isCfgFrame()) { + if (_info.codec == CodecInvalid) { + // 先判断是否为增强型rtmp + parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &_info); + } + + if (_info.is_enhanced) { + // 增强型rtmp + parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size(), &_info); + if (!_info.is_enhanced || _info.codec != CodecH265) { + throw std::invalid_argument("Invalid enhanced-rtmp hevc packet!"); + } + auto data = (uint8_t *)pkt->data() + 5; + auto size = pkt->size() - 5; + switch (_info.video.pkt_type) { + case RtmpPacketType::PacketTypeSequenceStart: { +#ifdef ENABLE_MP4 + string config; + if (decode_HEVCDecoderConfigurationRecord(data, size, config)) { + onGetH265(config.data(), config.size(), pkt->time_stamp, pkt->time_stamp); + } +#else + WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善"; +#endif + break; + } + + case RtmpPacketType::PacketTypeCodedFramesX: + case RtmpPacketType::PacketTypeCodedFrames: { + auto pts = pkt->time_stamp; + if (RtmpPacketType::PacketTypeCodedFrames == _info.video.pkt_type) { + // SI24 = [CompositionTime Offset] + CHECK(size > 7); + int32_t cts = (((data[0] << 16) | (data[1] << 8) | (data[2])) + 0xff800000) ^ 0xff800000; + pts += cts; + data += 3; + size -= 3; + } + splitFrame(data, size, pkt->time_stamp, pts); + break; + } + + case RtmpPacketType::PacketTypeMetadata: { + // The body does not contain video data. The body is an AMF encoded metadata. + // The metadata will be represented by a series of [name, value] pairs. + // For now the only defined [name, value] pair is [“colorInfo”, Object] + // See Metadata Frame section for more details of this object. + // + // For a deeper understanding of the encoding please see description + // of SCRIPTDATA and SSCRIPTDATAVALUE in the FLV file spec. + // DATA = [“colorInfo”, Object] + break; + } + case RtmpPacketType::PacketTypeSequenceEnd: { + // signals end of sequence + break; + } + default: break; + } + return; + } + + // 国内扩展(12) H265 rtmp + if (pkt->isConfigFrame()) { #ifdef ENABLE_MP4 string config; - if(getH265ConfigFrame(*pkt,config)){ - onGetH265(config.data(), config.size(), pkt->time_stamp , pkt->time_stamp); + if (getH265ConfigFrame(*pkt, config)) { + onGetH265(config.data(), config.size(), pkt->time_stamp, pkt->time_stamp); } #else WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善"; @@ -78,41 +137,42 @@ void H265RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt) { } if (pkt->buffer.size() > 9) { - auto total_len = pkt->buffer.size(); - size_t offset = 5; - uint8_t *cts_ptr = (uint8_t *) (pkt->buffer.data() + 2); + uint8_t *cts_ptr = (uint8_t *)(pkt->buffer.data() + 2); int32_t cts = (((cts_ptr[0] << 16) | (cts_ptr[1] << 8) | (cts_ptr[2])) + 0xff800000) ^ 0xff800000; auto pts = pkt->time_stamp + cts; - while (offset + 4 < total_len) { - uint32_t frame_len; - memcpy(&frame_len, pkt->buffer.data() + offset, 4); - frame_len = ntohl(frame_len); - offset += 4; - if (frame_len + offset > total_len) { - break; - } - onGetH265(pkt->buffer.data() + offset, frame_len, pkt->time_stamp, pts); - offset += frame_len; - } + splitFrame((uint8_t *)pkt->data() + 5, pkt->size() - 5, pkt->time_stamp, pts); } } -inline void H265RtmpDecoder::onGetH265(const char* pcData, size_t iLen, uint32_t dts,uint32_t pts) { - if(iLen == 0){ +void H265RtmpDecoder::splitFrame(const uint8_t *data, size_t size, uint32_t dts, uint32_t pts) { + auto end = data + size; + while (data + 4 < end) { + uint32_t frame_len = load_be32(data); + data += 4; + if (data + frame_len > end) { + break; + } + onGetH265((const char *)data, frame_len, dts, pts); + data += frame_len; + } +} + +inline void H265RtmpDecoder::onGetH265(const char *data, size_t size, uint32_t dts, uint32_t pts) { + if (size == 0) { return; } #if 1 _h265frame->_dts = dts; _h265frame->_pts = pts; - _h265frame->_buffer.assign("\x00\x00\x00\x01", 4); //添加265头 - _h265frame->_buffer.append(pcData, iLen); + _h265frame->_buffer.assign("\x00\x00\x00\x01", 4); // 添加265头 + _h265frame->_buffer.append(data, size); - //写入环形缓存 + // 写入环形缓存 RtmpCodec::inputFrame(_h265frame); _h265frame = obtainFrame(); #else - //防止内存拷贝,这样产生的265帧不会有0x00 00 01头 - auto frame = std::make_shared((char *)pcData,iLen,dts,pts,0); + // 防止内存拷贝,这样产生的265帧不会有0x00 00 01头 + auto frame = std::make_shared((char *)data, size, dts, pts, 0); RtmpCodec::inputFrame(frame); #endif } @@ -123,16 +183,16 @@ H265RtmpEncoder::H265RtmpEncoder(const Track::Ptr &track) { _track = dynamic_pointer_cast(track); } -void H265RtmpEncoder::makeConfigPacket(){ +void H265RtmpEncoder::makeConfigPacket() { if (_track && _track->ready()) { - //尝试从track中获取sps pps信息 + // 尝试从track中获取sps pps信息 _sps = _track->getSps(); _pps = _track->getPps(); _vps = _track->getVps(); } if (!_sps.empty() && !_pps.empty() && !_vps.empty()) { - //获取到sps/pps + // 获取到sps/pps makeVideoConfigPkt(); _got_config_frame = true; } @@ -175,50 +235,42 @@ bool H265RtmpEncoder::inputFrame(const Frame::Ptr &frame) { if (!_rtmp_packet) { _rtmp_packet = RtmpPacket::create(); - //flags/not_config/cts预占位 + // flags/not_config/cts预占位 _rtmp_packet->buffer.resize(5); } return _merger.inputFrame(frame, [this](uint64_t dts, uint64_t pts, const Buffer::Ptr &, bool have_key_frame) { - //flags - _rtmp_packet->buffer[0] = FLV_CODEC_H265 | ((have_key_frame ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4); - //not config - _rtmp_packet->buffer[1] = true; - int32_t cts = pts - dts; - if (cts < 0) { - cts = 0; - } - //cts - set_be24(&_rtmp_packet->buffer[2], cts); - - _rtmp_packet->time_stamp = dts; - _rtmp_packet->body_size = _rtmp_packet->buffer.size(); - _rtmp_packet->chunk_id = CHUNK_VIDEO; - _rtmp_packet->stream_index = STREAM_MEDIA; - _rtmp_packet->type_id = MSG_VIDEO; - //输出rtmp packet - RtmpCodec::inputRtmp(_rtmp_packet); - _rtmp_packet = nullptr; + // flags + _rtmp_packet->buffer[0] = (uint8_t)RtmpVideoCodec::h265 | ((uint8_t)(have_key_frame ? RtmpFrameType::key_frame : RtmpFrameType::inter_frame) << 4); + _rtmp_packet->buffer[1] = (uint8_t)RtmpH264PacketType::h264_nalu; + int32_t cts = pts - dts; + // cts + set_be24(&_rtmp_packet->buffer[2], cts); + _rtmp_packet->time_stamp = dts; + _rtmp_packet->body_size = _rtmp_packet->buffer.size(); + _rtmp_packet->chunk_id = CHUNK_VIDEO; + _rtmp_packet->stream_index = STREAM_MEDIA; + _rtmp_packet->type_id = MSG_VIDEO; + // 输出rtmp packet + RtmpCodec::inputRtmp(_rtmp_packet); + _rtmp_packet = nullptr; }, &_rtmp_packet->buffer); } void H265RtmpEncoder::makeVideoConfigPkt() { #ifdef ENABLE_MP4 - int8_t flags = FLV_CODEC_H265; - flags |= (FLV_KEY_FRAME << 4); - bool is_config = true; - auto rtmpPkt = RtmpPacket::create(); - //header - rtmpPkt->buffer.push_back(flags); - rtmpPkt->buffer.push_back(!is_config); - //cts - rtmpPkt->buffer.append("\x0\x0\x0", 3); + auto flags = (uint8_t)RtmpVideoCodec::h265; + flags |= ((uint8_t)RtmpFrameType::key_frame << 4); + auto pkt = RtmpPacket::create(); + // header + pkt->buffer.push_back(flags); + pkt->buffer.push_back((uint8_t)RtmpH264PacketType::h264_config_header); + // cts + pkt->buffer.append("\x0\x0\x0", 3); struct mpeg4_hevc_t hevc; memset(&hevc, 0, sizeof(hevc)); - string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps + - string("\x00\x00\x00\x01", 4) + _sps + - string("\x00\x00\x00\x01", 4) + _pps; + string vps_sps_pps = string("\x00\x00\x00\x01", 4) + _vps + string("\x00\x00\x00\x01", 4) + _sps + string("\x00\x00\x00\x01", 4) + _pps; h265_annexbtomp4(&hevc, vps_sps_pps.data(), (int)vps_sps_pps.size(), NULL, 0, NULL, NULL); uint8_t extra_data[1024]; int extra_data_size = mpeg4_hevc_decoder_configuration_record_save(&hevc, extra_data, sizeof(extra_data)); @@ -226,17 +278,17 @@ void H265RtmpEncoder::makeVideoConfigPkt() { WarnL << "生成H265 extra_data 失败"; return; } - //HEVCDecoderConfigurationRecord - rtmpPkt->buffer.append((char *)extra_data, extra_data_size); - rtmpPkt->body_size = rtmpPkt->buffer.size(); - rtmpPkt->chunk_id = CHUNK_VIDEO; - rtmpPkt->stream_index = STREAM_MEDIA; - rtmpPkt->time_stamp = 0; - rtmpPkt->type_id = MSG_VIDEO; - RtmpCodec::inputRtmp(rtmpPkt); + // HEVCDecoderConfigurationRecord + pkt->buffer.append((char *)extra_data, extra_data_size); + pkt->body_size = pkt->buffer.size(); + pkt->chunk_id = CHUNK_VIDEO; + pkt->stream_index = STREAM_MEDIA; + pkt->time_stamp = 0; + pkt->type_id = MSG_VIDEO; + RtmpCodec::inputRtmp(pkt); #else WarnL << "请开启MP4相关功能并使能\"ENABLE_MP4\",否则对H265-RTMP支持不完善"; #endif } -}//namespace mediakit +} // namespace mediakit diff --git a/src/Extension/H265Rtmp.h b/src/Extension/H265Rtmp.h index 593bb753..527728f6 100644 --- a/src/Extension/H265Rtmp.h +++ b/src/Extension/H265Rtmp.h @@ -15,7 +15,7 @@ #include "Extension/Track.h" #include "Extension/H265.h" -namespace mediakit{ +namespace mediakit { /** * h265 Rtmp解码类 * 将 h265 over rtmp 解复用出 h265-Frame @@ -25,7 +25,7 @@ public: using Ptr = std::shared_ptr; H265RtmpDecoder(); - ~H265RtmpDecoder() {} + ~H265RtmpDecoder() = default; /** * 输入265 Rtmp包 @@ -33,22 +33,23 @@ public: */ void inputRtmp(const RtmpPacket::Ptr &rtmp) override; - CodecId getCodecId() const override{ - return CodecH265; - } + CodecId getCodecId() const override { return CodecH265; } protected: - void onGetH265(const char *pcData, size_t iLen, uint32_t dts,uint32_t pts); H265Frame::Ptr obtainFrame(); + void onGetH265(const char *data, size_t size, uint32_t dts, uint32_t pts); + void splitFrame(const uint8_t *data, size_t size, uint32_t dts, uint32_t pts); + protected: + RtmpPacketInfo _info; H265Frame::Ptr _h265frame; }; /** * 265 Rtmp打包类 */ -class H265RtmpEncoder : public H265RtmpDecoder{ +class H265RtmpEncoder : public H265RtmpDecoder { public: using Ptr = std::shared_ptr; @@ -87,9 +88,9 @@ private: std::string _pps; H265Track::Ptr _track; RtmpPacket::Ptr _rtmp_packet; - FrameMerger _merger{FrameMerger::mp4_nal_size}; + FrameMerger _merger { FrameMerger::mp4_nal_size }; }; -}//namespace mediakit +} // namespace mediakit -#endif //ZLMEDIAKIT_H265RTMPCODEC_H +#endif // ZLMEDIAKIT_H265RTMPCODEC_H diff --git a/src/Extension/H265Rtp.cpp b/src/Extension/H265Rtp.cpp index 246b3ea3..23bbb109 100644 --- a/src/Extension/H265Rtp.cpp +++ b/src/Extension/H265Rtp.cpp @@ -302,7 +302,7 @@ void H265RtpEncoder::packRtpFu(const char *ptr, size_t len, uint64_t pts, bool i } void H265RtpEncoder::packRtp(const char *ptr, size_t len, uint64_t pts, bool is_mark, bool gop_pos){ - if (len + 3 <= getMaxSize()) { + if (len <= getMaxSize()) { //signal-nalu RtpCodec::inputRtp(makeRtp(getTrackType(), ptr, len, is_mark, pts), gop_pos); } else { diff --git a/src/FMP4/FMP4MediaSource.h b/src/FMP4/FMP4MediaSource.h index e004dc5e..13674ebe 100644 --- a/src/FMP4/FMP4MediaSource.h +++ b/src/FMP4/FMP4MediaSource.h @@ -39,10 +39,8 @@ public: using RingDataType = std::shared_ptr >; using RingType = toolkit::RingBuffer; - FMP4MediaSource(const std::string &vhost, - const std::string &app, - const std::string &stream_id, - int ring_size = FMP4_GOP_SIZE) : MediaSource(FMP4_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {} + FMP4MediaSource(const MediaTuple& tuple, + int ring_size = FMP4_GOP_SIZE) : MediaSource(FMP4_SCHEMA, tuple), _ring_size(ring_size) {} ~FMP4MediaSource() override { flush(); } @@ -53,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); } @@ -108,7 +106,7 @@ public: private: void createRing(){ - std::weak_ptr weak_self = std::dynamic_pointer_cast(shared_from_this()); + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); _ring = std::make_shared(_ring_size, [weak_self](int size) { auto strong_self = weak_self.lock(); if (!strong_self) { diff --git a/src/FMP4/FMP4MediaSourceMuxer.h b/src/FMP4/FMP4MediaSourceMuxer.h index ad16f352..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" @@ -23,12 +21,9 @@ class FMP4MediaSourceMuxer final : public MP4MuxerMemory, public MediaSourceEven public: using Ptr = std::shared_ptr; - FMP4MediaSourceMuxer(const std::string &vhost, - const std::string &app, - const std::string &stream_id, - const ProtocolOption &option) { + FMP4MediaSourceMuxer(const MediaTuple& tuple, const ProtocolOption &option) { _option = option; - _media_src = std::make_shared(vhost, app, stream_id); + _media_src = std::make_shared(tuple); } ~FMP4MediaSourceMuxer() override { MP4MuxerMemory::flush(); }; @@ -66,7 +61,8 @@ public: return _option.fmp4_demand ? (_clear_cache ? true : _enabled) : true; } - void onAllTrackReady() { + void addTrackCompleted() override { + MP4MuxerMemory::addTrackCompleted(); _media_src->setInitSegment(getInitSegment()); } @@ -89,5 +85,4 @@ private: }//namespace mediakit -#endif// defined(ENABLE_MP4) #endif //ZLMEDIAKIT_FMP4MEDIASOURCEMUXER_H diff --git a/src/Http/HlsParser.cpp b/src/Http/HlsParser.cpp index c7b45e3e..5cbd9195 100644 --- a/src/Http/HlsParser.cpp +++ b/src/Http/HlsParser.cpp @@ -37,7 +37,7 @@ bool HlsParser::parse(const string &http_url, const string &m3u8) { if ((_is_m3u8_inner || extinf_dur != 0) && line[0] != '#') { segment.duration = extinf_dur; - segment.url = Parser::merge_url(http_url, line); + segment.url = Parser::mergeUrl(http_url, line); if (!_is_m3u8_inner) { //ts按照先后顺序排序 ts_map.emplace(index++, segment); diff --git a/src/Http/HlsPlayer.cpp b/src/Http/HlsPlayer.cpp index e8ca1471..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(); @@ -80,7 +85,7 @@ void HlsPlayer::fetchSegment() { //播放器目前还存活,正在下载中 return; } - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); if (!_http_ts_player) { _http_ts_player = std::make_shared(getPoller()); _http_ts_player->setOnCreateSocket([weak_self](const EventPoller::Ptr &poller) { @@ -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(); @@ -186,7 +194,7 @@ bool HlsPlayer::onParsed(bool is_m3u8_inner, int64_t sequence, const map weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); auto url = ts_map.rbegin()->second.url; getPoller()->async([weak_self, url]() { auto strong_self = weak_self.lock(); @@ -259,7 +267,7 @@ bool HlsPlayer::onRedirectUrl(const string &url, bool temporary) { } void HlsPlayer::playDelay() { - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); _timer.reset(new Timer(delaySecond(), [weak_self]() { auto strong_self = weak_self.lock(); if (strong_self) { diff --git a/src/Http/HttpClient.cpp b/src/Http/HttpClient.cpp index 0e50fe0c..328872e6 100644 --- a/src/Http/HttpClient.cpp +++ b/src/Http/HttpClient.cpp @@ -21,7 +21,7 @@ namespace mediakit { void HttpClient::sendRequest(const string &url) { clearResponse(); _url = url; - auto protocol = FindField(url.data(), NULL, "://"); + auto protocol = findSubString(url.data(), NULL, "://"); uint16_t port; bool is_https; if (strcasecmp(protocol.data(), "http") == 0) { @@ -35,11 +35,11 @@ void HttpClient::sendRequest(const string &url) { throw std::invalid_argument(strErr); } - auto host = FindField(url.data(), "://", "/"); + auto host = findSubString(url.data(), "://", "/"); if (host.empty()) { - host = FindField(url.data(), "://", NULL); + host = findSubString(url.data(), "://", NULL); } - _path = FindField(url.data(), host.data(), NULL); + _path = findSubString(url.data(), host.data(), NULL); if (_path.empty()) { _path = "/"; } @@ -100,7 +100,7 @@ void HttpClient::clearResponse() { _header_recved = false; _recved_body_size = 0; _total_body_size = 0; - _parser.Clear(); + _parser.clear(); _chunked_splitter = nullptr; _wait_header.resetTime(); _wait_body.resetTime(); @@ -176,25 +176,25 @@ void HttpClient::onRecv(const Buffer::Ptr &pBuf) { HttpRequestSplitter::input(pBuf->data(), pBuf->size()); } -void HttpClient::onErr(const SockException &ex) { +void HttpClient::onError(const SockException &ex) { onResponseCompleted_l(ex); } ssize_t HttpClient::onRecvHeader(const char *data, size_t len) { - _parser.Parse(data); - if (_parser.Url() == "302" || _parser.Url() == "301" || _parser.Url() == "303") { - auto new_url = Parser::merge_url(_url, _parser["Location"]); + _parser.parse(data, len); + if (_parser.status() == "302" || _parser.status() == "301" || _parser.status() == "303") { + auto new_url = Parser::mergeUrl(_url, _parser["Location"]); if (new_url.empty()) { throw invalid_argument("未找到Location字段(跳转url)"); } - if (onRedirectUrl(new_url, _parser.Url() == "302")) { + if (onRedirectUrl(new_url, _parser.status() == "302")) { HttpClient::sendRequest(new_url); return 0; } } checkCookie(_parser.getHeader()); - onResponseHeader(_parser.Url(), _parser.getHeader()); + onResponseHeader(_parser.status(), _parser.getHeader()); _header_recved = true; if (_parser["Transfer-Encoding"] == "chunked") { @@ -226,7 +226,7 @@ ssize_t HttpClient::onRecvHeader(const char *data, size_t len) { if (_total_body_size == 0) { //后续没content,本次http请求结束 - onResponseCompleted_l(SockException(Err_success, "success")); + onResponseCompleted_l(SockException(Err_success, "The request is successful but has no body")); return 0; } @@ -260,7 +260,7 @@ void HttpClient::onRecvContent(const char *data, size_t len) { if (_recved_body_size == (size_t)_total_body_size) { //content接收完毕 onResponseBody(data, len); - onResponseCompleted_l(SockException(Err_success, "success")); + onResponseCompleted_l(SockException(Err_success, "completed")); return; } @@ -329,7 +329,7 @@ void HttpClient::onResponseCompleted_l(const SockException &ex) { if (_total_body_size > 0 && _recved_body_size >= (size_t)_total_body_size) { //回复header中有content-length信息,那么收到的body大于等于声明值则认为成功 - onResponseCompleted(SockException(Err_success, "success")); + onResponseCompleted(SockException(Err_success, "read body completed")); return; } @@ -361,8 +361,8 @@ void HttpClient::checkCookie(HttpClient::HttpHeader &headers) { int index = 0; auto arg_vec = split(it_set_cookie->second, ";"); for (string &key_val : arg_vec) { - auto key = FindField(key_val.data(), NULL, "="); - auto val = FindField(key_val.data(), "=", NULL); + auto key = findSubString(key_val.data(), NULL, "="); + auto val = findSubString(key_val.data(), "=", NULL); if (index++ == 0) { cookie->setKeyVal(key, val); diff --git a/src/Http/HttpClient.h b/src/Http/HttpClient.h index 82fc2f9d..497f4a1d 100644 --- a/src/Http/HttpClient.h +++ b/src/Http/HttpClient.h @@ -22,7 +22,7 @@ #include "HttpRequestSplitter.h" #include "HttpCookie.h" #include "HttpChunkedSplitter.h" -#include "strCoding.h" +#include "Common/strCoding.h" #include "HttpBody.h" namespace mediakit { @@ -177,7 +177,7 @@ protected: //// TcpClient override //// void onConnect(const toolkit::SockException &ex) override; void onRecv(const toolkit::Buffer::Ptr &pBuf) override; - void onErr(const toolkit::SockException &ex) override; + void onError(const toolkit::SockException &ex) override; void onFlush() override; void onManager() override; diff --git a/src/Http/HttpConst.cpp b/src/Http/HttpConst.cpp index 3e4116a7..0f7409ac 100644 --- a/src/Http/HttpConst.cpp +++ b/src/Http/HttpConst.cpp @@ -18,7 +18,7 @@ using namespace toolkit; namespace mediakit{ -const char *getHttpStatusMessage(int status) { +const char *HttpConst::getHttpStatusMessage(int status) { switch (status) { case 100: return "Continue"; case 101: return "Switching Protocol"; @@ -196,7 +196,7 @@ static const char *s_mime_src[][2] = { {"avi", "video/x-msvideo"}, }; -const string &getHttpContentType(const char *name) { +const string& HttpConst::getHttpContentType(const char *name) { const char *dot; dot = strrchr(name, '.'); static StrCaseMap mapType; diff --git a/src/Http/HttpConst.h b/src/Http/HttpConst.h index b68f8be5..bced1a7e 100644 --- a/src/Http/HttpConst.h +++ b/src/Http/HttpConst.h @@ -15,19 +15,25 @@ namespace mediakit{ -/** - * 根据http错误代码获取字符说明 - * @param status 譬如404 - * @return 错误代码字符说明,譬如Not Found - */ -const char *getHttpStatusMessage(int status); +class HttpConst { +public: + HttpConst() = delete; + ~HttpConst() = delete; -/** - * 根据文件后缀返回http mime - * @param name 文件后缀,譬如html - * @return mime值,譬如text/html - */ -const std::string &getHttpContentType(const char *name); + /** + * 根据http错误代码获取字符说明 + * @param status 譬如404 + * @return 错误代码字符说明,譬如Not Found + */ + static const char *getHttpStatusMessage(int status); + + /** + * 根据文件后缀返回http mime + * @param name 文件后缀,譬如html + * @return mime值,譬如text/html + */ + static const std::string &getHttpContentType(const char *name); +}; }//mediakit diff --git a/src/Http/HttpCookieManager.cpp b/src/Http/HttpCookieManager.cpp index 2ce0f188..e829423d 100644 --- a/src/Http/HttpCookieManager.cpp +++ b/src/Http/HttpCookieManager.cpp @@ -61,7 +61,7 @@ bool HttpServerCookie::isExpired() { return _ticker.elapsedTime() > _max_elapsed * 1000; } -void HttpServerCookie::setAttach(std::shared_ptr attach) { +void HttpServerCookie::setAttach(toolkit::Any attach) { _attach = std::move(attach); } @@ -114,8 +114,7 @@ void HttpCookieManager::onManager() { } } -HttpServerCookie::Ptr HttpCookieManager::addCookie(const string &cookie_name, const string &uid_in, - uint64_t max_elapsed, std::shared_ptr attach, int max_client) { +HttpServerCookie::Ptr HttpCookieManager::addCookie(const string &cookie_name, const string &uid_in, uint64_t max_elapsed, toolkit::Any attach, int max_client) { lock_guard lck(_mtx_cookie); auto cookie = _generator.obtain(); auto uid = uid_in.empty() ? cookie : uid_in; @@ -158,9 +157,9 @@ HttpServerCookie::Ptr HttpCookieManager::getCookie(const string &cookie_name, co if (it == http_header.end()) { return nullptr; } - auto cookie = FindField(it->second.data(), (cookie_name + "=").data(), ";"); + auto cookie = findSubString(it->second.data(), (cookie_name + "=").data(), ";"); if (cookie.empty()) { - cookie = FindField(it->second.data(), (cookie_name + "=").data(), nullptr); + cookie = findSubString(it->second.data(), (cookie_name + "=").data(), nullptr); } if (cookie.empty()) { return nullptr; diff --git a/src/Http/HttpCookieManager.h b/src/Http/HttpCookieManager.h index e55c2086..53775b79 100644 --- a/src/Http/HttpCookieManager.h +++ b/src/Http/HttpCookieManager.h @@ -85,14 +85,14 @@ public: /** * 设置附加数据 */ - void setAttach(std::shared_ptr attach); + void setAttach(toolkit::Any attach); /* * 获取附加数据 */ template T& getAttach() { - return *static_cast(_attach.get()); + return _attach.get(); } private: @@ -104,7 +104,7 @@ private: std::string _cookie_uuid; uint64_t _max_elapsed; toolkit::Ticker _ticker; - std::shared_ptr _attach; + toolkit::Any _attach; std::weak_ptr _manager; }; @@ -163,7 +163,7 @@ public: */ HttpServerCookie::Ptr addCookie( const std::string &cookie_name, const std::string &uid, uint64_t max_elapsed = COOKIE_DEFAULT_LIFE, - std::shared_ptr attach = nullptr, + toolkit::Any = toolkit::Any{}, int max_client = 1); /** diff --git a/src/Http/HttpFileManager.cpp b/src/Http/HttpFileManager.cpp index b690801a..1eaa9ec1 100644 --- a/src/Http/HttpFileManager.cpp +++ b/src/Http/HttpFileManager.cpp @@ -20,7 +20,7 @@ #include "Record/HlsMediaSource.h" #include "Common/Parser.h" #include "Common/config.h" -#include "strCoding.h" +#include "Common/strCoding.h" using namespace std; using namespace toolkit; @@ -31,12 +31,16 @@ namespace mediakit { // 每次访问一次该cookie,那么将重新刷新cookie有效期 // 假如播放器在60秒内都未访问该cookie,那么将重新触发hls播放鉴权 static int kHlsCookieSecond = 60; +static int kFindSrcIntervalSecond = 3; static const string kCookieName = "ZL_COOKIE"; static const string kHlsSuffix = "/hls.m3u8"; +static const string kHlsFMP4Suffix = "/hls.fmp4.m3u8"; struct HttpCookieAttachment { - //是否已经查找到过MediaSource + // 是否已经查找到过MediaSource bool _find_src = false; + // 查找MediaSource计时 + Ticker _find_src_ticker; //cookie生效作用域,本cookie只对该目录下的文件生效 string _path; //上次鉴权失败信息,为空则上次鉴权成功 @@ -46,7 +50,112 @@ struct HttpCookieAttachment { }; const string &HttpFileManager::getContentType(const char *name) { - return getHttpContentType(name); + return HttpConst::getHttpContentType(name); +} + +namespace { +class UInt128 { +public: + UInt128() = default; + + UInt128(const struct sockaddr_storage &storage) { + _family = storage.ss_family; + memset(_bytes, 0, 16); + switch (storage.ss_family) { + case AF_INET: { + memcpy(_bytes, &(reinterpret_cast(storage).sin_addr), 4); + break; + } + case AF_INET6: { + memcpy(_bytes, &(reinterpret_cast(storage).sin6_addr), 16); + break; + } + default: CHECK(false, "Invalid socket family"); break; + } + } + + bool operator==(const UInt128 &that) const { return _family == that._family && !memcmp(_bytes, that._bytes, 16); } + + bool operator<=(const UInt128 &that) const { return *this < that || *this == that; } + + bool operator>=(const UInt128 &that) const { return *this > that || *this == that; } + + bool operator>(const UInt128 &that) const { return that < *this; } + + bool operator<(const UInt128 &that) const { + auto sz = _family == AF_INET ? 4 : 16; + for (int i = 0; i < sz; ++i) { + if (_bytes[i] < that._bytes[i]) { + return true; + } else if (_bytes[i] > that._bytes[i]) { + return false; + } + } + return false; + } + + operator bool() const { return _family != -1; } + + bool same_type(const UInt128 &that) const { return _family == that._family; } + +private: + int _family = -1; + uint8_t _bytes[16]; +}; + +} + +static UInt128 get_ip_uint64(const std::string &ip) { + try { + return UInt128(SockUtil::make_sockaddr(ip.data(), 0)); + } catch (std::exception &ex) { + WarnL << ex.what(); + } + return UInt128(); +} + +bool HttpFileManager::isIPAllowed(const std::string &ip) { + using IPRangs = std::vector>; + GET_CONFIG_FUNC(IPRangs, allow_ip_range, Http::kAllowIPRange, [](const string &str) -> IPRangs { + IPRangs ret; + auto vec = split(str, ","); + for (auto &item : vec) { + if (trim(item).empty()) { + continue; + } + auto range = split(item, "-"); + if (range.size() == 2) { + auto ip_min = get_ip_uint64(trim(range[0])); + auto ip_max = get_ip_uint64(trim(range[1])); + if (ip_min && ip_max && ip_min.same_type(ip_max)) { + ret.emplace_back(ip_min, ip_max); + } else { + WarnL << "Invalid ip range or family: " << item; + } + } else if (range.size() == 1) { + auto ip = get_ip_uint64(trim(range[0])); + if (ip) { + ret.emplace_back(ip, ip); + } else { + WarnL << "Invalid ip: " << item; + } + } else { + WarnL << "Invalid ip range: " << item; + } + } + return ret; + }); + + if (allow_ip_range.empty()) { + return true; + } + auto ip_int = get_ip_uint64(ip); + for (auto &range : allow_ip_range) { + if (ip_int.same_type(range.first) && ip_int >= range.first && ip_int <= range.second) { + return true; + } + } + return false; } static string searchIndexFile(const string &dir){ @@ -57,7 +166,7 @@ static string searchIndexFile(const string &dir){ } set setFile; while ((pDirent = readdir(pDir)) != NULL) { - static set indexSet = {"index.html", "index.htm", "index"}; + static set indexSet = {"index.html", "index.htm"}; if (indexSet.find(pDirent->d_name) != indexSet.end()) { string ret = pDirent->d_name; closedir(pDir); @@ -188,7 +297,7 @@ static bool emitHlsPlayed(const Parser &parser, const MediaInfo &media_info, con //cookie有效期为kHlsCookieSecond invoker(err, "", kHlsCookieSecond); }; - bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, static_cast(sender)); + bool flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, media_info, auth_invoker, sender); if (!flag) { //未开启鉴权,那么允许播放 auth_invoker(""); @@ -240,8 +349,8 @@ public: static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo &media_info, bool is_dir, const function &callback) { //获取用户唯一id - auto uid = parser.Params(); - auto path = parser.Url(); + auto uid = parser.params(); + auto path = parser.url(); //先根据http头中的cookie字段获取cookie HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, parser.getHeader()); @@ -268,7 +377,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo return; } //上次鉴权失败,但是如果url参数发生变更,那么也重新鉴权下 - if (parser.Params().empty() || parser.Params() == cookie->getUid()) { + if (parser.params().empty() || parser.params() == cookie->getUid()) { //url参数未变,或者本来就没有url参数,那么判断本次请求为重复请求,无访问权限 callback(attach._err_msg, update_cookie ? cookie : nullptr); return; @@ -278,7 +387,7 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo HttpCookieManager::Instance().delCookie(cookie); } - bool is_hls = media_info._schema == HLS_SCHEMA; + bool is_hls = media_info.schema == HLS_SCHEMA || media_info.schema == HLS_FMP4_SCHEMA; SockInfoImp::Ptr info = std::make_shared(); info->_identifier = sender.getIdentifier(); @@ -308,7 +417,9 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo // hls相关信息 attach->_hls_data = std::make_shared(media_info, info); } - callback(err_msg, HttpCookieManager::Instance().addCookie(kCookieName, uid, life_second, attach)); + toolkit::Any any; + any.set(std::move(attach)); + callback(err_msg, HttpCookieManager::Instance().addCookie(kCookieName, uid, life_second, std::move(any))); } else { callback(err_msg, nullptr); } @@ -320,10 +431,10 @@ static void canAccessPath(Session &sender, const Parser &parser, const MediaInfo return; } - //事件未被拦截,则认为是http下载请求 - bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, static_cast(sender)); + // 事件未被拦截,则认为是http下载请求 + bool flag = NOTICE_EMIT(BroadcastHttpAccessArgs, Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, sender); if (!flag) { - //此事件无人监听,我们默认都有权限访问 + // 此事件无人监听,我们默认都有权限访问 callback("", nullptr); } } @@ -355,7 +466,7 @@ static string pathCat(const string &a, const string &b){ * @param cb 回调对象 */ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &media_info, const string &file_path, const HttpFileManager::invoker &cb) { - bool is_hls = end_with(file_path, kHlsSuffix); + bool is_hls = end_with(file_path, kHlsSuffix) || end_with(file_path, kHlsFMP4Suffix); if (!is_hls && !File::fileExist(file_path.data())) { //文件不存在且不是hls,那么直接返回404 sendNotFound(cb); @@ -363,11 +474,16 @@ 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._streamid), 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 = sender.shared_from_this(); + weak_ptr weakSession = static_pointer_cast(sender.shared_from_this()); //判断是否有权限访问该文件 canAccessPath(sender, parser, media_info, false, [cb, file_path, parser, is_hls, media_info, weakSession](const string &err_msg, const HttpServerCookie::Ptr &cookie) { auto strongSession = weakSession.lock(); @@ -421,14 +537,15 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m return; } - auto src = cookie->getAttach()._hls_data->getMediaSource(); + auto &attach = cookie->getAttach(); + auto src = attach._hls_data->getMediaSource(); if (src) { - //直接从内存获取m3u8索引文件(而不是从文件系统) + // 直接从内存获取m3u8索引文件(而不是从文件系统) response_file(cookie, cb, file_path, parser, src->getIndexFile()); return; } - if (cookie->getAttach()._find_src) { - //查找过MediaSource,但是流已经注销了,不用再查找 + if (attach._find_src && attach._find_src_ticker.elapsedTime() < kFindSrcIntervalSecond * 1000) { + // 最近已经查找过MediaSource了,为了防止频繁查找导致占用全局互斥锁的问题,我们尝试直接从磁盘返回hls索引文件 response_file(cookie, cb, file_path, parser); return; } @@ -444,11 +561,14 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m auto &attach = cookie->getAttach(); attach._hls_data->setMediaSource(hls); - //添加HlsMediaSource的观看人数(HLS是按需生成的,这样可以触发HLS文件的生成) + // 添加HlsMediaSource的观看人数(HLS是按需生成的,这样可以触发HLS文件的生成) attach._hls_data->addByteUsage(0); - //标记找到MediaSource + // 标记找到MediaSource attach._find_src = true; + // 重置查找MediaSource计时 + attach._find_src_ticker.resetTime(); + // m3u8文件可能不存在, 等待m3u8索引文件按需生成 hls->getIndexFile([response_file, file_path, cookie, cb, parser](const string &file) { response_file(cookie, cb, file_path, parser, file); @@ -457,7 +577,7 @@ static void accessFile(Session &sender, const Parser &parser, const MediaInfo &m }); } -static string getFilePath(const Parser &parser,const MediaInfo &media_info, Session &sender){ +static string getFilePath(const Parser &parser,const MediaInfo &media_info, Session &sender) { GET_CONFIG(bool, enableVhost, General::kEnableVhost); GET_CONFIG(string, rootPath, Http::kRootPath); GET_CONFIG_FUNC(StrCaseMap, virtualPathMap, Http::kVirtualPath, [](const string &str) { @@ -465,15 +585,15 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess }); string url, path; - auto it = virtualPathMap.find(media_info._app); + auto it = virtualPathMap.find(media_info.app); if (it != virtualPathMap.end()) { //访问的是virtualPath path = it->second; - url = parser.Url().substr(1 + media_info._app.size()); + url = parser.url().substr(1 + media_info.app.size()); } else { //访问的是rootPath path = rootPath; - url = parser.Url(); + url = parser.url(); } for (auto &ch : url) { if (ch == '\\') { @@ -481,8 +601,15 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess ch = '/'; } } - auto ret = File::absolutePath(enableVhost ? media_info._vhost + url : url, path); - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, static_cast(sender)); + auto ret = File::absolutePath(enableVhost ? media_info.vhost + url : url, path); + auto http_root = File::absolutePath(enableVhost ? media_info.vhost + "/" : "/", path); + if (!start_with(ret, http_root)) { + // 访问的http文件不得在http根目录之外 + throw std::runtime_error("Attempting to access files outside of the http root directory"); + } + // 替换url,防止返回的目录索引网页被注入非法内容 + const_cast(parser).setUrl("/" + ret.substr(http_root.size())); + NOTICE_EMIT(BroadcastHttpBeforeAccessArgs, Broadcast::kBroadcastHttpBeforeAccess, parser, ret, sender); return ret; } @@ -493,7 +620,7 @@ static string getFilePath(const Parser &parser,const MediaInfo &media_info, Sess * @param cb 回调对象 */ void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFileManager::invoker &cb) { - auto fullUrl = string(HTTP_SCHEMA) + "://" + parser["Host"] + parser.FullUrl(); + auto fullUrl = string(HTTP_SCHEMA) + "://" + parser["Host"] + parser.fullUrl(); MediaInfo media_info(fullUrl); auto file_path = getFilePath(parser, media_info, sender); if (file_path.size() == 0) { @@ -504,15 +631,18 @@ void HttpFileManager::onAccessPath(Session &sender, Parser &parser, const HttpFi if (File::is_dir(file_path.data())) { auto indexFile = searchIndexFile(file_path); if (!indexFile.empty()) { - //发现该文件夹下有index文件 + // 发现该文件夹下有index文件 file_path = pathCat(file_path, indexFile); - parser.setUrl(pathCat(parser.Url(), indexFile)); - accessFile(sender, parser, media_info, file_path, cb); - return; + if (!File::is_dir(file_path.data())) { + // 不是文件夹 + parser.setUrl(pathCat(parser.url(), indexFile)); + accessFile(sender, parser, media_info, file_path, cb); + return; + } } string strMenu; //生成文件夹菜单索引 - if (!makeFolderMenu(parser.Url(), file_path, strMenu)) { + if (!makeFolderMenu(parser.url(), file_path, strMenu)) { //文件夹不存在 sendNotFound(cb); return; @@ -600,8 +730,8 @@ void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader, if (!strRange.empty()) { //分节下载 code = 206; - auto iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data()); - auto iRangeEnd = atoll(FindField(strRange.data(), "-", nullptr).data()); + auto iRangeStart = atoll(findSubString(strRange.data(), "bytes=", "-").data()); + auto iRangeEnd = atoll(findSubString(strRange.data(), "-", nullptr).data()); auto fileSize = fileBody->remainSize(); if (iRangeEnd == 0) { iRangeEnd = fileSize - 1; @@ -621,4 +751,4 @@ HttpResponseInvokerImp::operator bool(){ } -}//namespace mediakit \ No newline at end of file +}//namespace mediakit diff --git a/src/Http/HttpFileManager.h b/src/Http/HttpFileManager.h index 330b9fa1..a3c0c1f3 100644 --- a/src/Http/HttpFileManager.h +++ b/src/Http/HttpFileManager.h @@ -62,6 +62,13 @@ public: * @return mime值 */ static const std::string &getContentType(const char *name); + + /** + * 该ip是否再白名单中 + * @param ip 支持ipv4和ipv6 + */ + static bool isIPAllowed(const std::string &ip); + private: HttpFileManager() = delete; ~HttpFileManager() = delete; diff --git a/src/Http/HttpRequestSplitter.cpp b/src/Http/HttpRequestSplitter.cpp index 4d859686..a7883f2f 100644 --- a/src/Http/HttpRequestSplitter.cpp +++ b/src/Http/HttpRequestSplitter.cpp @@ -91,7 +91,7 @@ void HttpRequestSplitter::input(const char *data,size_t len) { _remain_data.assign(ptr, _remain_data_size); return; } - //收到content数据,并且接受content完毕 + //收到content数据,并且接收content完毕 onRecvContent(ptr,_content_len); _remain_data_size -= _content_len; diff --git a/src/Http/HttpRequester.cpp b/src/Http/HttpRequester.cpp index ddc73631..8bd09950 100644 --- a/src/Http/HttpRequester.cpp +++ b/src/Http/HttpRequester.cpp @@ -27,7 +27,7 @@ void HttpRequester::onResponseBody(const char *buf, size_t size) { void HttpRequester::onResponseCompleted(const SockException &ex) { if (ex && _retry++ < _max_retry) { - std::weak_ptr weak_self = std::dynamic_pointer_cast(shared_from_this()); + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); getPoller()->doDelayTask(_retry_delay, [weak_self](){ if (auto self = weak_self.lock()) { InfoL << "resend request " << self->getUrl() << " with retry " << self->getRetry(); @@ -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 79924933..69407928 100644 --- a/src/Http/HttpSession.cpp +++ b/src/Http/HttpSession.cpp @@ -12,7 +12,7 @@ #include #include #include "Common/config.h" -#include "strCoding.h" +#include "Common/strCoding.h" #include "HttpSession.h" #include "HttpConst.h" #include "Util/base64.h" @@ -24,105 +24,168 @@ using namespace toolkit; namespace mediakit { HttpSession::HttpSession(const Socket::Ptr &pSock) : Session(pSock) { - GET_CONFIG(uint32_t,keep_alive_sec,Http::kKeepAliveSecond); + GET_CONFIG(uint32_t, keep_alive_sec, Http::kKeepAliveSecond); pSock->setSendTimeOutSecond(keep_alive_sec); } HttpSession::~HttpSession() = default; -void HttpSession::Handle_Req_HEAD(ssize_t &content_len){ - //暂时全部返回200 OK,因为HTTP GET存在按需生成流的操作,所以不能按照HTTP GET的流程返回 - //如果直接返回404,那么又会导致按需生成流的逻辑失效,所以HTTP HEAD在静态文件或者已存在资源时才有效 - //对于按需生成流的直播场景并不适用 +void HttpSession::onHttpRequest_HEAD() { + // 暂时全部返回200 OK,因为HTTP GET存在按需生成流的操作,所以不能按照HTTP GET的流程返回 + // 如果直接返回404,那么又会导致按需生成流的逻辑失效,所以HTTP HEAD在静态文件或者已存在资源时才有效 + // 对于按需生成流的直播场景并不适用 sendResponse(200, false); } -void HttpSession::Handle_Req_OPTIONS(ssize_t &content_len) { +void HttpSession::onHttpRequest_OPTIONS() { KeyValue header; - header.emplace("Allow", "GET, POST, OPTIONS"); - header.emplace("Access-Control-Allow-Origin", "*"); + header.emplace("Allow", "GET, POST, HEAD, OPTIONS"); + GET_CONFIG(bool, allow_cross_domains, Http::kAllowCrossDomains); + if (allow_cross_domains) { + header.emplace("Access-Control-Allow-Origin", "*"); + header.emplace("Access-Control-Allow-Headers", "*"); + header.emplace("Access-Control-Allow-Methods", "GET, POST, HEAD, OPTIONS"); + } header.emplace("Access-Control-Allow-Credentials", "true"); header.emplace("Access-Control-Request-Methods", "GET, POST, OPTIONS"); header.emplace("Access-Control-Request-Headers", "Accept,Accept-Language,Content-Language,Content-Type"); sendResponse(200, true, nullptr, header); } -ssize_t HttpSession::onRecvHeader(const char *header,size_t len) { - typedef void (HttpSession::*HttpCMDHandle)(ssize_t &); - static unordered_map s_func_map; +ssize_t HttpSession::onRecvHeader(const char *header, size_t len) { + using func_type = void (HttpSession::*)(); + static unordered_map s_func_map; static onceToken token([]() { - s_func_map.emplace("GET",&HttpSession::Handle_Req_GET); - s_func_map.emplace("POST",&HttpSession::Handle_Req_POST); - s_func_map.emplace("HEAD",&HttpSession::Handle_Req_HEAD); - s_func_map.emplace("OPTIONS",&HttpSession::Handle_Req_OPTIONS); - }, nullptr); + s_func_map.emplace("GET", &HttpSession::onHttpRequest_GET); + s_func_map.emplace("POST", &HttpSession::onHttpRequest_POST); + // DELETE命令用于whip/whep用,只用于触发http api + s_func_map.emplace("DELETE", &HttpSession::onHttpRequest_POST); + s_func_map.emplace("HEAD", &HttpSession::onHttpRequest_HEAD); + s_func_map.emplace("OPTIONS", &HttpSession::onHttpRequest_OPTIONS); + }); - _parser.Parse(header); - CHECK(_parser.Url()[0] == '/'); + _parser.parse(header, len); + CHECK(_parser.url()[0] == '/'); urlDecode(_parser); - string cmd = _parser.Method(); + auto &cmd = _parser.method(); auto it = s_func_map.find(cmd); if (it == s_func_map.end()) { - WarnP(this) << "不支持该命令:" << cmd; + WarnP(this) << "Http method not supported: " << cmd; sendResponse(405, true); return 0; } - //跨域 - _origin = _parser["Origin"]; + size_t content_len; + auto &content_len_str = _parser["Content-Length"]; + if (content_len_str.empty()) { + if (it->first == "POST") { + // Http post未指定长度,我们认为是不定长的body + WarnL << "Received http post request without content-length, consider it to be unlimited length"; + content_len = SIZE_MAX; + } else { + content_len = 0; + } + } else { + // 已经指定长度 + content_len = atoll(content_len_str.data()); + } - //默认后面数据不是content而是header - ssize_t content_len = 0; - (this->*(it->second))(content_len); + if (content_len == 0) { + //// 没有body的情况,直接触发回调 //// + (this->*(it->second))(); + _parser.clear(); + // 如果设置了_on_recv_body, 那么说明后续要处理body + return _on_recv_body ? -1 : 0; + } - //清空解析器节省内存 - _parser.Clear(); - //返回content长度 - return content_len; + GET_CONFIG(size_t, maxReqSize, Http::kMaxReqSize); + if (content_len > maxReqSize) { + //// 不定长body或超大body //// + if (content_len != SIZE_MAX) { + WarnL << "Http body size is too huge: " << content_len << " > " << maxReqSize + << ", please set " << Http::kMaxReqSize << " in config.ini file."; + } + + size_t received = 0; + auto parser = std::move(_parser); + _on_recv_body = [this, parser, received, content_len](const char *data, size_t len) mutable { + received += len; + onRecvUnlimitedContent(parser, data, len, content_len, received); + if (received < content_len) { + // 还没收满 + return true; + } + + // 收满了 + setContentLen(0); + return false; + }; + // 声明后续都是body;Http body在本对象缓冲,不通过HttpRequestSplitter保存 + return -1; + } + + //// body size明确指定且小于最大值的情况 //// + auto body = std::make_shared(); + // 预留一定的内存buffer,防止频繁的内存拷贝 + body->reserve(content_len); + + _on_recv_body = [this, body, content_len, it](const char *data, size_t len) mutable { + body->append(data, len); + if (body->size() < content_len) { + // 未收满数据 + return true; + } + + // 收集body完毕 + _parser.setContent(std::move(*body)); + (this->*(it->second))(); + _parser.clear(); + + // 后续是header + setContentLen(0); + return false; + }; + + // 声明后续都是body;Http body在本对象缓冲,不通过HttpRequestSplitter保存 + return -1; } -void HttpSession::onRecvContent(const char *data,size_t len) { - if(_contentCallBack){ - if(!_contentCallBack(data,len)){ - _contentCallBack = nullptr; - } +void HttpSession::onRecvContent(const char *data, size_t len) { + if (_on_recv_body && !_on_recv_body(data, len)) { + _on_recv_body = nullptr; } } void HttpSession::onRecv(const Buffer::Ptr &pBuf) { _ticker.resetTime(); - input(pBuf->data(),pBuf->size()); + input(pBuf->data(), pBuf->size()); } -void HttpSession::onError(const SockException& err) { +void HttpSession::onError(const SockException &err) { if (_is_live_stream) { - //flv/ts播放器 + // flv/ts播放器 uint64_t duration = _ticker.createdTime() / 1000; - WarnP(this) << "FLV/TS/FMP4播放器(" - << _mediaInfo.shortUrl() - << ")断开:" << err - << ",耗时(s):" << duration; + WarnP(this) << "FLV/TS/FMP4播放器(" << _mediaInfo.shortUrl() << ")断开:" << err << ",耗时(s):" << duration; GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_total_bytes_usage >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, - duration, true, static_cast(*this)); + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _mediaInfo, _total_bytes_usage, duration, true, *this); } return; } } void HttpSession::onManager() { - GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond); + GET_CONFIG(uint32_t, keepAliveSec, Http::kKeepAliveSecond); - if(_ticker.elapsedTime() > keepAliveSec * 1000){ - //1分钟超时 - shutdown(SockException(Err_timeout,"session timeout")); + if (_ticker.elapsedTime() > keepAliveSec * 1000) { + // 1分钟超时 + shutdown(SockException(Err_timeout, "session timeout")); } } -bool HttpSession::checkWebSocket(){ +bool HttpSession::checkWebSocket() { auto Sec_WebSocket_Key = _parser["Sec-WebSocket-Key"]; if (Sec_WebSocket_Key.empty()) { return false; @@ -142,25 +205,31 @@ bool HttpSession::checkWebSocket(){ sendResponse(101, false, nullptr, headerOut, nullptr, true); }; - //判断是否为websocket-flv - if (checkLiveStreamFlv(res_cb)) { - //这里是websocket-flv直播请求 + auto res_cb_flv = [this, headerOut]() mutable { + _live_over_websocket = true; + headerOut.emplace("Cache-Control", "no-store"); + sendResponse(101, false, nullptr, headerOut, nullptr, true); + }; + + // 判断是否为websocket-flv + if (checkLiveStreamFlv(res_cb_flv)) { + // 这里是websocket-flv直播请求 return true; } - //判断是否为websocket-ts + // 判断是否为websocket-ts if (checkLiveStreamTS(res_cb)) { - //这里是websocket-ts直播请求 + // 这里是websocket-ts直播请求 return true; } - //判断是否为websocket-fmp4 + // 判断是否为websocket-fmp4 if (checkLiveStreamFMP4(res_cb)) { - //这里是websocket-fmp4直播请求 + // 这里是websocket-fmp4直播请求 return true; } - //这是普通的websocket连接 + // 这是普通的websocket连接 if (!onWebSocketConnect(_parser)) { sendResponse(501, true, nullptr, headerOut); return true; @@ -169,8 +238,8 @@ bool HttpSession::checkWebSocket(){ return true; } -bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix, const function &cb){ - std::string url = _parser.Url(); +bool HttpSession::checkLiveStream(const string &schema, const string &url_suffix, const function &cb) { + std::string url = _parser.url(); auto it = _parser.getUrlArgs().find("schema"); if (it != _parser.getUrlArgs().end()) { if (strcasecmp(it->second.c_str(), schema.c_str())) { @@ -180,57 +249,57 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffi } else { auto prefix_size = url_suffix.size(); if (url.size() < prefix_size || strcasecmp(url.data() + (url.size() - prefix_size), url_suffix.data())) { - //未找到后缀 + // 未找到后缀 return false; } // url去除特殊后缀 url.resize(url.size() - prefix_size); } - //带参数的url - if (!_parser.Params().empty()) { + // 带参数的url + if (!_parser.params().empty()) { url += "?"; - url += _parser.Params(); + url += _parser.params(); } - //解析带上协议+参数完整的url + // 解析带上协议+参数完整的url _mediaInfo.parse(schema + "://" + _parser["Host"] + url); - if (_mediaInfo._app.empty() || _mediaInfo._streamid.empty()) { - //url不合法 + if (_mediaInfo.app.empty() || _mediaInfo.stream.empty()) { + // url不合法 return false; } bool close_flag = !strcasecmp(_parser["Connection"].data(), "close"); - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); - //鉴权结果回调 + // 鉴权结果回调 auto onRes = [cb, weak_self, close_flag](const string &err) { auto strong_self = weak_self.lock(); if (!strong_self) { - //本对象已经销毁 + // 本对象已经销毁 return; } if (!err.empty()) { - //播放鉴权失败 + // 播放鉴权失败 strong_self->sendResponse(401, close_flag, nullptr, KeyValue(), std::make_shared(err)); return; } - //异步查找直播流 + // 异步查找直播流 MediaSource::findAsync(strong_self->_mediaInfo, strong_self, [weak_self, close_flag, cb](const MediaSource::Ptr &src) { auto strong_self = weak_self.lock(); if (!strong_self) { - //本对象已经销毁 + // 本对象已经销毁 return; } if (!src) { - //未找到该流 + // 未找到该流 strong_self->sendNotFound(close_flag); } else { strong_self->_is_live_stream = true; - //触发回调 + // 触发回调 cb(src); } }); @@ -242,38 +311,42 @@ bool HttpSession::checkLiveStream(const string &schema, const string &url_suffi } }; - auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed, _mediaInfo, invoker, static_cast(*this)); + auto flag = NOTICE_EMIT(BroadcastMediaPlayedArgs, Broadcast::kBroadcastMediaPlayed, _mediaInfo, invoker, *this); if (!flag) { - //该事件无人监听,默认不鉴权 + // 该事件无人监听,默认不鉴权 onRes(""); } return true; } -//http-fmp4 链接格式:http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2 -bool HttpSession::checkLiveStreamFMP4(const function &cb){ +// http-fmp4 链接格式:http://vhost-url:port/app/streamid.live.mp4?key1=value1&key2=value2 +bool HttpSession::checkLiveStreamFMP4(const function &cb) { return checkLiveStream(FMP4_SCHEMA, ".live.mp4", [this, cb](const MediaSource::Ptr &src) { auto fmp4_src = dynamic_pointer_cast(src); assert(fmp4_src); if (!cb) { - //找到源,发送http头,负载后续发送 + // 找到源,发送http头,负载后续发送 sendResponse(200, false, HttpFileManager::getContentType(".mp4").data(), KeyValue(), nullptr, true); } else { - //自定义发送http头 + // 自定义发送http头 cb(); } - //直播牺牲延时提升发送性能 + // 直播牺牲延时提升发送性能 setSocketFlags(); onWrite(std::make_shared(fmp4_src->getInitSegment()), true); - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); fmp4_src->pause(false); _fmp4_reader = fmp4_src->getRing()->attach(getPoller()); - _fmp4_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); }); + _fmp4_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(static_pointer_cast(weak_self.lock())); + return ret; + }); _fmp4_reader->setDetachCB([weak_self]() { auto strong_self = weak_self.lock(); if (!strong_self) { - //本对象已经销毁 + // 本对象已经销毁 return; } strong_self->shutdown(SockException(Err_shutdown, "fmp4 ring buffer detached")); @@ -281,83 +354,84 @@ bool HttpSession::checkLiveStreamFMP4(const function &cb){ _fmp4_reader->setReadCB([weak_self](const FMP4MediaSource::RingDataType &fmp4_list) { auto strong_self = weak_self.lock(); if (!strong_self) { - //本对象已经销毁 + // 本对象已经销毁 return; } size_t i = 0; auto size = fmp4_list->size(); - fmp4_list->for_each([&](const FMP4Packet::Ptr &ts) { - strong_self->onWrite(ts, ++i == size); - }); + fmp4_list->for_each([&](const FMP4Packet::Ptr &ts) { strong_self->onWrite(ts, ++i == size); }); }); }); } -//http-ts 链接格式:http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2 -bool HttpSession::checkLiveStreamTS(const function &cb){ +// http-ts 链接格式:http://vhost-url:port/app/streamid.live.ts?key1=value1&key2=value2 +bool HttpSession::checkLiveStreamTS(const function &cb) { return checkLiveStream(TS_SCHEMA, ".live.ts", [this, cb](const MediaSource::Ptr &src) { auto ts_src = dynamic_pointer_cast(src); assert(ts_src); if (!cb) { - //找到源,发送http头,负载后续发送 + // 找到源,发送http头,负载后续发送 sendResponse(200, false, HttpFileManager::getContentType(".ts").data(), KeyValue(), nullptr, true); } else { - //自定义发送http头 + // 自定义发送http头 cb(); } - //直播牺牲延时提升发送性能 + // 直播牺牲延时提升发送性能 setSocketFlags(); - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); ts_src->pause(false); _ts_reader = ts_src->getRing()->attach(getPoller()); - _ts_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); }); - _ts_reader->setDetachCB([weak_self](){ + _ts_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(static_pointer_cast(weak_self.lock())); + return ret; + }); + _ts_reader->setDetachCB([weak_self]() { auto strong_self = weak_self.lock(); if (!strong_self) { - //本对象已经销毁 + // 本对象已经销毁 return; } - strong_self->shutdown(SockException(Err_shutdown,"ts ring buffer detached")); + strong_self->shutdown(SockException(Err_shutdown, "ts ring buffer detached")); }); _ts_reader->setReadCB([weak_self](const TSMediaSource::RingDataType &ts_list) { auto strong_self = weak_self.lock(); if (!strong_self) { - //本对象已经销毁 + // 本对象已经销毁 return; } size_t i = 0; auto size = ts_list->size(); - ts_list->for_each([&](const TSPacket::Ptr &ts) { - strong_self->onWrite(ts, ++i == size); - }); + ts_list->for_each([&](const TSPacket::Ptr &ts) { strong_self->onWrite(ts, ++i == size); }); }); }); } -//http-flv 链接格式:http://vhost-url:port/app/streamid.live.flv?key1=value1&key2=value2 -bool HttpSession::checkLiveStreamFlv(const function &cb){ +// http-flv 链接格式:http://vhost-url:port/app/streamid.live.flv?key1=value1&key2=value2 +bool HttpSession::checkLiveStreamFlv(const function &cb) { auto start_pts = atoll(_parser.getUrlArgs()["starPts"].data()); return checkLiveStream(RTMP_SCHEMA, ".live.flv", [this, cb, start_pts](const MediaSource::Ptr &src) { auto rtmp_src = dynamic_pointer_cast(src); assert(rtmp_src); if (!cb) { - //找到源,发送http头,负载后续发送 - sendResponse(200, false, HttpFileManager::getContentType(".flv").data(), KeyValue(), nullptr, true); + // 找到源,发送http头,负载后续发送 + KeyValue headerOut; + headerOut["Cache-Control"] = "no-store"; + sendResponse(200, false, HttpFileManager::getContentType(".flv").data(), headerOut, nullptr, true); } else { - //自定义发送http头 + // 自定义发送http头 cb(); } - //直播牺牲延时提升发送性能 + // 直播牺牲延时提升发送性能 setSocketFlags(); - //非H264/AAC时打印警告日志,防止用户提无效问题 + // 非H264/AAC时打印警告日志,防止用户提无效问题 auto tracks = src->getTracks(false); for (auto &track : tracks) { switch (track->getCodecId()) { case CodecH264: - case CodecAAC: - break; + case CodecAAC: break; default: { WarnP(this) << "flv播放器一般只支持H264和AAC编码,该编码格式可能不被播放器支持:" << track->getCodecName(); break; @@ -369,46 +443,42 @@ bool HttpSession::checkLiveStreamFlv(const function &cb){ }); } -void HttpSession::Handle_Req_GET(ssize_t &content_len) { - Handle_Req_GET_l(content_len, true); -} - -void HttpSession::Handle_Req_GET_l(ssize_t &content_len, bool sendBody) { - //先看看是否为WebSocket请求 +void HttpSession::onHttpRequest_GET() { + // 先看看是否为WebSocket请求 if (checkWebSocket()) { - content_len = -1; - _contentCallBack = [this](const char *data, size_t len) { - WebSocketSplitter::decode((uint8_t *) data, len); - //_contentCallBack是可持续的,后面还要处理后续数据 + // 后续都是websocket body数据 + _on_recv_body = [this](const char *data, size_t len) { + WebSocketSplitter::decode((uint8_t *)data, len); + // _contentCallBack是可持续的,后面还要处理后续数据 return true; }; return; } if (emitHttpEvent(false)) { - //拦截http api事件 + // 拦截http api事件 return; } if (checkLiveStreamFlv()) { - //拦截http-flv播放器 + // 拦截http-flv播放器 return; } if (checkLiveStreamTS()) { - //拦截http-ts播放器 + // 拦截http-ts播放器 return; } if (checkLiveStreamFMP4()) { - //拦截http-fmp4播放器 + // 拦截http-fmp4播放器 return; } - bool bClose = !strcasecmp(_parser["Connection"].data(),"close"); - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + bool bClose = !strcasecmp(_parser["Connection"].data(), "close"); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); HttpFileManager::onAccessPath(*this, _parser, [weak_self, bClose](int code, const string &content_type, - const StrCaseMap &responseHeader, const HttpBody::Ptr &body) { + const StrCaseMap &responseHeader, const HttpBody::Ptr &body) { auto strong_self = weak_self.lock(); if (!strong_self) { return; @@ -434,12 +504,13 @@ class AsyncSenderData { public: friend class AsyncSender; using Ptr = std::shared_ptr; - AsyncSenderData(const Session::Ptr &session, const HttpBody::Ptr &body, bool close_when_complete) { - _session = dynamic_pointer_cast(session); + AsyncSenderData(HttpSession::Ptr session, const HttpBody::Ptr &body, bool close_when_complete) { + _session = std::move(session); _body = body; _close_when_complete = close_when_complete; } ~AsyncSenderData() = default; + private: std::weak_ptr _session; HttpBody::Ptr _body; @@ -453,7 +524,7 @@ public: static bool onSocketFlushed(const AsyncSenderData::Ptr &data) { if (data->_read_complete) { if (data->_close_when_complete) { - //发送完毕需要关闭socket + // 发送完毕需要关闭socket shutdown(data->_session.lock()); } return false; @@ -463,13 +534,13 @@ public: data->_body->readDataAsync(sendBufSize, [data](const Buffer::Ptr &sendBuf) { auto session = data->_session.lock(); if (!session) { - //本对象已经销毁 + // 本对象已经销毁 return; } session->async([data, sendBuf]() { auto session = data->_session.lock(); if (!session) { - //本对象已经销毁 + // 本对象已经销毁 return; } onRequestData(data, session, sendBuf); @@ -482,14 +553,14 @@ private: static void onRequestData(const AsyncSenderData::Ptr &data, const std::shared_ptr &session, const Buffer::Ptr &sendBuf) { session->_ticker.resetTime(); if (sendBuf && session->send(sendBuf) != -1) { - //文件还未读完,还需要继续发送 + // 文件还未读完,还需要继续发送 if (!session->isSocketBusy()) { - //socket还可写,继续请求数据 + // socket还可写,继续请求数据 onSocketFlushed(data); } return; } - //文件写完了 + // 文件写完了 data->_read_complete = true; if (!session->isSocketBusy() && data->_close_when_complete) { shutdown(session); @@ -497,34 +568,25 @@ private: } static void shutdown(const std::shared_ptr &session) { - if(session){ + if (session) { session->shutdown(SockException(Err_shutdown, StrPrinter << "close connection after send http body completed.")); } } }; -static const string kDate = "Date"; -static const string kServer = "Server"; -static const string kConnection = "Connection"; -static const string kKeepAlive = "Keep-Alive"; -static const string kContentType = "Content-Type"; -static const string kContentLength = "Content-Length"; -static const string kAccessControlAllowOrigin = "Access-Control-Allow-Origin"; -static const string kAccessControlAllowCredentials = "Access-Control-Allow-Credentials"; - void HttpSession::sendResponse(int code, bool bClose, const char *pcContentType, const HttpSession::KeyValue &header, const HttpBody::Ptr &body, - bool no_content_length ){ - GET_CONFIG(string,charSet,Http::kCharSet); - GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond); + bool no_content_length) { + GET_CONFIG(string, charSet, Http::kCharSet); + GET_CONFIG(uint32_t, keepAliveSec, Http::kKeepAliveSecond); - //body默认为空 + // body默认为空 int64_t size = 0; if (body && body->remainSize()) { - //有body,获取body大小 + // 有body,获取body大小 size = body->remainSize(); } @@ -532,52 +594,53 @@ void HttpSession::sendResponse(int code, // http-flv直播是Keep-Alive类型 bClose = false; } else if ((size_t)size >= SIZE_MAX || size < 0) { - //不固定长度的body,那么发送完body后应该关闭socket,以便浏览器做下载完毕的判断 + // 不固定长度的body,那么发送完body后应该关闭socket,以便浏览器做下载完毕的判断 bClose = true; } HttpSession::KeyValue &headerOut = const_cast(header); - headerOut.emplace(kDate, dateStr()); - headerOut.emplace(kServer, kServerName); - headerOut.emplace(kConnection, bClose ? "close" : "keep-alive"); + headerOut.emplace("Date", dateStr()); + headerOut.emplace("Server", kServerName); + headerOut.emplace("Connection", bClose ? "close" : "keep-alive"); + + GET_CONFIG(bool, allow_cross_domains, Http::kAllowCrossDomains); + if (allow_cross_domains) { + headerOut.emplace("Access-Control-Allow-Origin", "*"); + headerOut.emplace("Access-Control-Allow-Credentials", "true"); + } + if (!bClose) { string keepAliveString = "timeout="; keepAliveString += to_string(keepAliveSec); keepAliveString += ", max=100"; - headerOut.emplace(kKeepAlive, std::move(keepAliveString)); - } - - if (!_origin.empty()) { - //设置跨域 - headerOut.emplace(kAccessControlAllowOrigin, _origin); - headerOut.emplace(kAccessControlAllowCredentials, "true"); + headerOut.emplace("Keep-Alive", std::move(keepAliveString)); } if (!no_content_length && size >= 0 && (size_t)size < SIZE_MAX) { - //文件长度为固定值,且不是http-flv强制设置Content-Length - headerOut[kContentLength] = to_string(size); + // 文件长度为固定值,且不是http-flv强制设置Content-Length + headerOut["Content-Length"] = to_string(size); } if (size && !pcContentType) { - //有body时,设置缺省类型 + // 有body时,设置缺省类型 pcContentType = "text/plain"; } if ((size || no_content_length) && pcContentType) { - //有body时,设置文件类型 + // 有body时,设置文件类型 string strContentType = pcContentType; strContentType += "; charset="; strContentType += charSet; - headerOut.emplace(kContentType, std::move(strContentType)); + headerOut.emplace("Content-Type", std::move(strContentType)); } - //发送http头 + // 发送http头 string str; str.reserve(256); str += "HTTP/1.1 "; str += to_string(code); str += ' '; - str += getHttpStatusMessage(code); + str += HttpConst::getHttpStatusMessage(code); str += "\r\n"; for (auto &pr : header) { str += pr.first; @@ -590,9 +653,9 @@ void HttpSession::sendResponse(int code, _ticker.resetTime(); if (!size) { - //没有body + // 没有body if (bClose) { - shutdown(SockException(Err_shutdown,StrPrinter << "close connection after send http header completed with status code:" << code)); + shutdown(SockException(Err_shutdown, StrPrinter << "close connection after send http header completed with status code:" << code)); } return; } @@ -607,20 +670,20 @@ void HttpSession::sendResponse(int code, GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize); if (body->remainSize() > sendBufSize) { - //文件下载提升发送性能 + // 文件下载提升发送性能 setSocketFlags(); } - //发送http body - AsyncSenderData::Ptr data = std::make_shared(shared_from_this(), body, bClose); + // 发送http body + AsyncSenderData::Ptr data = std::make_shared(static_pointer_cast(shared_from_this()), body, bClose); getSock()->setOnFlush([data]() { return AsyncSender::onSocketFlushed(data); }); AsyncSender::onSocketFlushed(data); } -string HttpSession::urlDecode(const string &str){ +string HttpSession::urlDecode(const string &str) { auto ret = strCoding::UrlDecode(str); #ifdef _WIN32 - GET_CONFIG(string,charSet,Http::kCharSet); + GET_CONFIG(string, charSet, Http::kCharSet); bool isGb2312 = !strcasecmp(charSet.data(), "gb2312"); if (isGb2312) { ret = strCoding::UTF8ToGB2312(ret); @@ -629,117 +692,51 @@ string HttpSession::urlDecode(const string &str){ return ret; } -void HttpSession::urlDecode(Parser &parser){ - parser.setUrl(urlDecode(parser.Url())); - for(auto &pr : _parser.getUrlArgs()){ +void HttpSession::urlDecode(Parser &parser) { + parser.setUrl(urlDecode(parser.url())); + for (auto &pr : _parser.getUrlArgs()) { const_cast(pr.second) = urlDecode(pr.second); } } -bool HttpSession::emitHttpEvent(bool doInvoke){ - bool bClose = !strcasecmp(_parser["Connection"].data(),"close"); +bool HttpSession::emitHttpEvent(bool doInvoke) { + bool bClose = !strcasecmp(_parser["Connection"].data(), "close"); /////////////////////异步回复Invoker/////////////////////////////// - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); - HttpResponseInvoker invoker = [weak_self,bClose](int code, const KeyValue &headerOut, const HttpBody::Ptr &body){ + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + HttpResponseInvoker invoker = [weak_self, bClose](int code, const KeyValue &headerOut, const HttpBody::Ptr &body) { auto strong_self = weak_self.lock(); - if(!strong_self) { + if (!strong_self) { return; } strong_self->async([weak_self, bClose, code, headerOut, body]() { auto strong_self = weak_self.lock(); if (!strong_self) { - //本对象已经销毁 + // 本对象已经销毁 return; } strong_self->sendResponse(code, bClose, nullptr, headerOut, body); }); }; ///////////////////广播HTTP事件/////////////////////////// - bool consumed = false;//该事件是否被消费 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpRequest,_parser,invoker,consumed,static_cast(*this)); - if(!consumed && doInvoke){ - //该事件无人消费,所以返回404 - invoker(404,KeyValue(), HttpBody::Ptr()); + bool consumed = false; // 该事件是否被消费 + NOTICE_EMIT(BroadcastHttpRequestArgs, Broadcast::kBroadcastHttpRequest, _parser, invoker, consumed, *this); + if (!consumed && doInvoke) { + // 该事件无人消费,所以返回404 + invoker(404, KeyValue(), HttpBody::Ptr()); } return consumed; } std::string HttpSession::get_peer_ip() { GET_CONFIG(string, forwarded_ip_header, Http::kForwardedIpHeader); - if(!forwarded_ip_header.empty() && !_parser.getHeader()[forwarded_ip_header].empty()){ + if (!forwarded_ip_header.empty() && !_parser.getHeader()[forwarded_ip_header].empty()) { return _parser.getHeader()[forwarded_ip_header]; } return Session::get_peer_ip(); } -void HttpSession::Handle_Req_POST(ssize_t &content_len) { - GET_CONFIG(size_t,maxReqSize,Http::kMaxReqSize); - - ssize_t totalContentLen = _parser["Content-Length"].empty() ? -1 : atoll(_parser["Content-Length"].data()); - - if(totalContentLen == 0){ - //content为空 - //emitHttpEvent内部会选择是否关闭连接 - emitHttpEvent(true); - return; - } - - if(totalContentLen > 0 && (size_t)totalContentLen < maxReqSize ){ - //返回固定长度的content - content_len = totalContentLen; - auto parserCopy = _parser; - _contentCallBack = [this,parserCopy](const char *data,size_t len){ - //恢复http头 - _parser = parserCopy; - //设置content - _parser.setContent(string(data,len)); - //触发http事件,emitHttpEvent内部会选择是否关闭连接 - emitHttpEvent(true); - //清空数据,节省内存 - _parser.Clear(); - //content已经接收完毕 - return false; - }; - }else{ - //返回不固定长度的content或者超过长度限制的content - content_len = -1; - auto parserCopy = _parser; - std::shared_ptr recvedContentLen = std::make_shared(0); - bool bClose = !strcasecmp(_parser["Connection"].data(),"close"); - - _contentCallBack = [this,parserCopy,totalContentLen,recvedContentLen,bClose](const char *data,size_t len){ - *(recvedContentLen) += len; - if (totalContentLen < 0) { - //不固定长度的content,源源不断接收数据 - onRecvUnlimitedContent(parserCopy, data, len, SIZE_MAX, *(recvedContentLen)); - return true; - } - - //长度超过限制的content - onRecvUnlimitedContent(parserCopy,data,len,totalContentLen,*(recvedContentLen)); - - if(*(recvedContentLen) < (size_t)totalContentLen){ - //数据还没接收完毕 - //_contentCallBack是可持续的,后面还要处理后续content数据 - return true; - } - - //数据接收完毕 - if(!bClose){ - //keep-alive类型连接 - //content接收完毕,后续都是http header - setContentLen(0); - //content已经接收完毕 - return false; - } - - //连接类型是close类型,收完content就关闭连接 - shutdown(SockException(Err_shutdown,"recv http content completed")); - //content已经接收完毕 - return false ; - }; - } - //有后续content数据要处理,暂时不关闭连接 +void HttpSession::onHttpRequest_POST() { + emitHttpEvent(true); } void HttpSession::sendNotFound(bool bClose) { @@ -747,19 +744,19 @@ void HttpSession::sendNotFound(bool bClose) { sendResponse(404, bClose, "text/html", KeyValue(), std::make_shared(notFound)); } -void HttpSession::setSocketFlags(){ +void HttpSession::setSocketFlags() { GET_CONFIG(int, mergeWriteMS, General::kMergeWriteMS); - if(mergeWriteMS > 0) { - //推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高 + if (mergeWriteMS > 0) { + // 推流模式下,关闭TCP_NODELAY会增加推流端的延时,但是服务器性能将提高 SockUtil::setNoDelay(getSock()->rawFD(), false); - //播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能 + // 播放模式下,开启MSG_MORE会增加延时,但是能提高发送性能 setSendFlags(SOCKET_DEFAULE_FLAGS | FLAG_MORE); } } void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) { - if(flush){ - //需要flush那么一次刷新缓存 + if (flush) { + // 需要flush那么一次刷新缓存 HttpSession::setSendFlushFlag(true); } @@ -777,18 +774,18 @@ void HttpSession::onWrite(const Buffer::Ptr &buffer, bool flush) { } if (flush) { - //本次刷新缓存后,下次不用刷新缓存 + // 本次刷新缓存后,下次不用刷新缓存 HttpSession::setSendFlushFlag(false); } } -void HttpSession::onWebSocketEncodeData(Buffer::Ptr buffer){ +void HttpSession::onWebSocketEncodeData(Buffer::Ptr buffer) { _total_bytes_usage += buffer->size(); send(std::move(buffer)); } -void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in){ - WebSocketHeader& header = const_cast(header_in); +void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in) { + WebSocketHeader &header = const_cast(header_in); header._mask_flag = false; switch (header._opcode) { @@ -798,15 +795,15 @@ void HttpSession::onWebSocketDecodeComplete(const WebSocketHeader &header_in){ break; } - default : break; + default: break; } } void HttpSession::onDetach() { - shutdown(SockException(Err_shutdown,"rtmp ring buffer detached")); + shutdown(SockException(Err_shutdown, "rtmp ring buffer detached")); } -std::shared_ptr HttpSession::getSharedPtr(){ +std::shared_ptr HttpSession::getSharedPtr() { return dynamic_pointer_cast(shared_from_this()); } diff --git a/src/Http/HttpSession.h b/src/Http/HttpSession.h index 42eaad44..513f8b63 100644 --- a/src/Http/HttpSession.h +++ b/src/Http/HttpSession.h @@ -28,15 +28,16 @@ class HttpSession: public toolkit::Session, public HttpRequestSplitter, public WebSocketSplitter { public: - typedef StrCaseMap KeyValue; - typedef HttpResponseInvokerImp HttpResponseInvoker; + using Ptr = std::shared_ptr; + using KeyValue = StrCaseMap; + using HttpResponseInvoker = HttpResponseInvokerImp ; friend class AsyncSender; /** * @param errMsg 如果为空,则代表鉴权通过,否则为错误提示 * @param accessPath 运行或禁止访问的根目录 * @param cookieLifeSecond 鉴权cookie有效期 **/ - typedef std::function HttpAccessPathInvoker; + using HttpAccessPathInvoker = std::function; HttpSession(const toolkit::Socket::Ptr &pSock); ~HttpSession() override; @@ -100,11 +101,10 @@ protected: std::string get_peer_ip() override; private: - void Handle_Req_GET(ssize_t &content_len); - void Handle_Req_GET_l(ssize_t &content_len, bool sendBody); - void Handle_Req_POST(ssize_t &content_len); - void Handle_Req_HEAD(ssize_t &content_len); - void Handle_Req_OPTIONS(ssize_t &content_len); + void onHttpRequest_GET(); + void onHttpRequest_POST(); + void onHttpRequest_HEAD(); + void onHttpRequest_OPTIONS(); bool checkLiveStream(const std::string &schema, const std::string &url_suffix, const std::function &cb); @@ -123,19 +123,20 @@ private: //设置socket标志 void setSocketFlags(); +protected: + MediaInfo _mediaInfo; + private: bool _is_live_stream = false; bool _live_over_websocket = false; //消耗的总流量 uint64_t _total_bytes_usage = 0; - std::string _origin; Parser _parser; toolkit::Ticker _ticker; - MediaInfo _mediaInfo; TSMediaSource::RingType::RingReader::Ptr _ts_reader; FMP4MediaSource::RingType::RingReader::Ptr _fmp4_reader; //处理content数据的callback - std::function _contentCallBack; + std::function _on_recv_body; }; using HttpsSession = toolkit::SessionWithSSL; diff --git a/src/Http/TsPlayer.cpp b/src/Http/TsPlayer.cpp index 70383899..20737bc5 100644 --- a/src/Http/TsPlayer.cpp +++ b/src/Http/TsPlayer.cpp @@ -34,7 +34,12 @@ void TsPlayer::teardown() { void TsPlayer::onResponseCompleted(const SockException &ex) { if (!_play_result) { _play_result = true; - onPlayResult(ex); + if (!ex && responseBodyTotalSize() == 0 && responseBodySize() == 0) { + //if the server does not return any data, it is considered a failure + onShutdown(ex); + } else { + onPlayResult(ex); + } } else { onShutdown(ex); } @@ -44,7 +49,7 @@ void TsPlayer::onResponseCompleted(const SockException &ex) { void TsPlayer::onResponseBody(const char *buf, size_t size) { if (!_play_result) { _play_result = true; - onPlayResult(SockException(Err_success, "play http-ts success")); + onPlayResult(SockException(Err_success, "read http-ts stream successfully")); } if (!_benchmark_mode) { HttpTSPlayer::onResponseBody(buf, size); diff --git a/src/Http/WebSocketClient.h b/src/Http/WebSocketClient.h index 737ebcda..5850b027 100644 --- a/src/Http/WebSocketClient.h +++ b/src/Http/WebSocketClient.h @@ -218,7 +218,7 @@ protected: /** * tcp连接断开 */ - void onErr(const toolkit::SockException &ex) override { + void onError(const toolkit::SockException &ex) override { // tcp断开或者shutdown导致的断开 onWebSocketException(ex); } @@ -316,7 +316,7 @@ private: if (!ex) { // websocket握手成功 // 此处截取TcpClient派生类发送的数据并进行websocket协议打包 - std::weak_ptr weakSelf = std::dynamic_pointer_cast(shared_from_this()); + std::weak_ptr weakSelf = std::static_pointer_cast(shared_from_this()); if (auto strong_ref = _weak_delegate.lock()) { strong_ref->setOnBeforeSendCB([weakSelf](const toolkit::Buffer::Ptr &buf) { auto strong_self = weakSelf.lock(); @@ -350,7 +350,7 @@ private: // 握手成功之后的中途断开 _onRecv = nullptr; if (auto strong_ref = _weak_delegate.lock()) { - strong_ref->onErr(ex); + strong_ref->onError(ex); } return; } diff --git a/src/Http/WebSocketSession.h b/src/Http/WebSocketSession.h index c8d8b432..96021c4d 100644 --- a/src/Http/WebSocketSession.h +++ b/src/Http/WebSocketSession.h @@ -139,7 +139,7 @@ protected: } //此处截取数据并进行websocket协议打包 - std::weak_ptr weakSelf = std::dynamic_pointer_cast(HttpSessionType::shared_from_this()); + std::weak_ptr weakSelf = std::static_pointer_cast(HttpSessionType::shared_from_this()); std::dynamic_pointer_cast(_session)->setOnBeforeSendCB([weakSelf](const toolkit::Buffer::Ptr &buf) { auto strongSelf = weakSelf.lock(); if (strongSelf) { diff --git a/src/Player/PlayerBase.cpp b/src/Player/PlayerBase.cpp index 31d8e051..3f465bd7 100644 --- a/src/Player/PlayerBase.cpp +++ b/src/Player/PlayerBase.cpp @@ -12,6 +12,7 @@ #include "PlayerBase.h" #include "Rtsp/RtspPlayerImp.h" #include "Rtmp/RtmpPlayerImp.h" +#include "Rtmp/FlvPlayer.h" #include "Http/HlsPlayer.h" #include "Http/TsPlayerImp.h" @@ -20,15 +21,16 @@ using namespace toolkit; namespace mediakit { -PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller, const string &url_in) { - static auto releasePlayer = [](PlayerBase *ptr) { - onceToken token(nullptr, [&]() { - delete ptr; +PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &in_poller, const string &url_in) { + auto poller = in_poller ? in_poller : EventPollerPool::Instance().getPoller(); + static auto releasePlayer = [poller](PlayerBase *ptr) { + poller->async([ptr]() { + onceToken token(nullptr, [&]() { delete ptr; }); + ptr->teardown(); }); - ptr->teardown(); }; string url = url_in; - string prefix = FindField(url.data(), NULL, "://"); + string prefix = findSubString(url.data(), NULL, "://"); auto pos = url.find('?'); if (pos != string::npos) { //去除?后面的字符串 @@ -53,9 +55,13 @@ PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &poller, const s if ((strcasecmp("http", prefix.data()) == 0 || strcasecmp("https", prefix.data()) == 0)) { if (end_with(url, ".m3u8") || end_with(url_in, ".m3u8")) { return PlayerBase::Ptr(new HlsPlayerImp(poller), releasePlayer); - } else if (end_with(url, ".ts") || end_with(url_in, ".ts")) { + } + if (end_with(url, ".ts") || end_with(url_in, ".ts")) { return PlayerBase::Ptr(new TsPlayerImp(poller), releasePlayer); } + if (end_with(url, ".flv") || end_with(url_in, ".flv")) { + return PlayerBase::Ptr(new FlvPlayerImp(poller), releasePlayer); + } } throw std::invalid_argument("not supported play schema:" + url_in); diff --git a/src/Player/PlayerProxy.cpp b/src/Player/PlayerProxy.cpp index 521c38e8..2a319ff8 100644 --- a/src/Player/PlayerProxy.cpp +++ b/src/Player/PlayerProxy.cpp @@ -8,43 +8,97 @@ * may be found in the AUTHORS file in the root of the source tree. */ -#include "Common/config.h" #include "PlayerProxy.h" -#include "Util/mini.h" -#include "Util/MD5.h" -#include "Util/logger.h" +#include "Common/config.h" #include "Extension/AAC.h" #include "Rtmp/RtmpMediaSource.h" -#include "Rtsp/RtspMediaSource.h" #include "Rtmp/RtmpPlayer.h" +#include "Rtsp/RtspMediaSource.h" #include "Rtsp/RtspPlayer.h" +#include "Util/MD5.h" +#include "Util/logger.h" +#include "Util/mini.h" using namespace toolkit; using namespace std; namespace mediakit { -PlayerProxy::PlayerProxy(const string &vhost, const string &app, const string &stream_id, const ProtocolOption &option, - int retry_count, const EventPoller::Ptr &poller) : MediaPlayer(poller) , _option(option) { - _vhost = vhost; - _app = app; - _stream_id = stream_id; +PlayerProxy::PlayerProxy( + const string &vhost, const string &app, const string &stream_id, const ProtocolOption &option, int retry_count, + const EventPoller::Ptr &poller, int reconnect_delay_min, int reconnect_delay_max, int reconnect_delay_step) + : MediaPlayer(poller) + , _option(option) { + _tuple.vhost = vhost; + _tuple.app = app; + _tuple.stream = stream_id; _retry_count = retry_count; - _on_close = [](const SockException &) {}; + + setOnClose(nullptr); + setOnConnect(nullptr); + setOnDisconnect(nullptr); + + _reconnect_delay_min = reconnect_delay_min > 0 ? reconnect_delay_min : 2; + _reconnect_delay_max = reconnect_delay_max > 0 ? reconnect_delay_max : 60; + _reconnect_delay_step = reconnect_delay_step > 0 ? reconnect_delay_step : 3; + _live_secs = 0; + _live_status = 1; + _repull_count = 0; (*this)[Client::kWaitTrackReady] = false; } -void PlayerProxy::setPlayCallbackOnce(const function &cb) { - _on_play = cb; +void PlayerProxy::setPlayCallbackOnce(function cb) { + _on_play = std::move(cb); } -void PlayerProxy::setOnClose(const function &cb) { - _on_close = cb ? cb : [](const SockException &) {}; +void PlayerProxy::setOnClose(function cb) { + _on_close = cb ? std::move(cb) : [](const SockException &) {}; +} + +void PlayerProxy::setOnDisconnect(std::function cb) { + _on_disconnect = cb ? std::move(cb) : [] () {}; +} + +void PlayerProxy::setOnConnect(std::function cb) { + _on_connect = cb ? std::move(cb) : [](const TranslationInfo&) {}; +} + +void PlayerProxy::setTranslationInfo() +{ + _transtalion_info.byte_speed = _media_src ? _media_src->getBytesSpeed() : -1; + _transtalion_info.start_time_stamp = _media_src ? _media_src->getCreateStamp() : 0; + _transtalion_info.stream_info.clear(); + auto tracks = _muxer->getTracks(); + for (auto &track : tracks) { + _transtalion_info.stream_info.emplace_back(); + auto &back = _transtalion_info.stream_info.back(); + back.bitrate = track->getBitRate(); + back.codec_type = track->getTrackType(); + back.codec_name = track->getCodecName(); + switch (back.codec_type) { + case TrackAudio : { + auto audio_track = dynamic_pointer_cast(track); + back.audio_sample_rate = audio_track->getAudioSampleRate(); + back.audio_channel = audio_track->getAudioChannel(); + back.audio_sample_bit = audio_track->getAudioSampleBit(); + break; + } + case TrackVideo : { + auto video_track = dynamic_pointer_cast(track); + back.video_width = video_track->getVideoWidth(); + back.video_height = video_track->getVideoHeight(); + back.video_fps = video_track->getVideoFps(); + break; + } + default: + break; + } + } } void PlayerProxy::play(const string &strUrlTmp) { weak_ptr weakSelf = shared_from_this(); - std::shared_ptr piFailedCnt(new int(0)); //连续播放失败次数 + std::shared_ptr piFailedCnt(new int(0)); // 连续播放失败次数 setOnPlayResult([weakSelf, strUrlTmp, piFailedCnt](const SockException &err) { auto strongSelf = weakSelf.lock(); if (!strongSelf) { @@ -58,15 +112,22 @@ void PlayerProxy::play(const string &strUrlTmp) { if (!err) { // 取消定时器,避免hls拉流索引文件因为网络波动失败重连成功后出现循环重试的情况 - strongSelf->_timer.reset(); + strongSelf->_timer.reset(); + strongSelf->_live_ticker.resetTime(); + strongSelf->_live_status = 0; // 播放成功 - *piFailedCnt = 0;//连续播放失败次数清0 + *piFailedCnt = 0; // 连续播放失败次数清0 strongSelf->onPlaySuccess(); + strongSelf->setTranslationInfo(); + strongSelf->_on_connect(strongSelf->_transtalion_info); + + InfoL << "play " << strUrlTmp << " success"; } else if (*piFailedCnt < strongSelf->_retry_count || strongSelf->_retry_count < 0) { // 播放失败,延时重试播放 + strongSelf->_on_disconnect(); strongSelf->rePlay(strUrlTmp, (*piFailedCnt)++); } else { - //达到了最大重试次数,回调关闭 + // 达到了最大重试次数,回调关闭 strongSelf->_on_close(err); } }); @@ -76,7 +137,7 @@ void PlayerProxy::play(const string &strUrlTmp) { return; } - //注销直接拉流代理产生的流:#532 + // 注销直接拉流代理产生的流:#532 strongSelf->setMediaSource(nullptr); if (strongSelf->_muxer) { @@ -92,11 +153,20 @@ void PlayerProxy::play(const string &strUrlTmp) { strongSelf->_muxer->resetTracks(); } } - //播放异常中断,延时重试播放 + + if (*piFailedCnt == 0) { + // 第一次重拉更新时长 + strongSelf->_live_secs += strongSelf->_live_ticker.elapsedTime() / 1000; + strongSelf->_live_ticker.resetTime(); + TraceL << " live secs " << strongSelf->_live_secs; + } + + // 播放异常中断,延时重试播放 if (*piFailedCnt < strongSelf->_retry_count || strongSelf->_retry_count < 0) { + strongSelf->_repull_count++; strongSelf->rePlay(strUrlTmp, (*piFailedCnt)++); } else { - //达到了最大重试次数,回调关闭 + // 达到了最大重试次数,回调关闭 strongSelf->_on_close(err); } }); @@ -104,7 +174,7 @@ void PlayerProxy::play(const string &strUrlTmp) { MediaPlayer::play(strUrlTmp); } catch (std::exception &ex) { ErrorL << ex.what(); - _on_play_result(SockException(Err_other, ex.what())); + onPlayResult(SockException(Err_other, ex.what())); return; } _pull_url = strUrlTmp; @@ -114,14 +184,14 @@ void PlayerProxy::play(const string &strUrlTmp) { void PlayerProxy::setDirectProxy() { MediaSource::Ptr mediaSource; if (dynamic_pointer_cast(_delegate)) { - //rtsp拉流 + // rtsp拉流 GET_CONFIG(bool, directProxy, Rtsp::kDirectProxy); if (directProxy) { - mediaSource = std::make_shared(_vhost, _app, _stream_id); + mediaSource = std::make_shared(_tuple); } } else if (dynamic_pointer_cast(_delegate)) { - //rtmp拉流,rtmp强制直接代理 - mediaSource = std::make_shared(_vhost, _app, _stream_id); + // rtmp拉流,rtmp强制直接代理 + mediaSource = std::make_shared(_tuple); } if (mediaSource) { setMediaSource(mediaSource); @@ -131,30 +201,37 @@ void PlayerProxy::setDirectProxy() { PlayerProxy::~PlayerProxy() { _timer.reset(); // 避免析构时, 忘记回调api请求 - if(_on_play) { - _on_play(SockException(Err_shutdown, "player proxy close")); + if (_on_play) { + try { + _on_play(SockException(Err_shutdown, "player proxy close")); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } _on_play = nullptr; } } void PlayerProxy::rePlay(const string &strUrl, int iFailedCnt) { - auto iDelay = MAX(2 * 1000, MIN(iFailedCnt * 3000, 60 * 1000)); + auto iDelay = MAX(_reconnect_delay_min * 1000, MIN(iFailedCnt * _reconnect_delay_step * 1000, _reconnect_delay_max * 1000)); weak_ptr weakSelf = shared_from_this(); - _timer = std::make_shared(iDelay / 1000.0f, [weakSelf, strUrl, iFailedCnt]() { - //播放失败次数越多,则延时越长 - auto strongPlayer = weakSelf.lock(); - if (!strongPlayer) { + _timer = std::make_shared( + iDelay / 1000.0f, + [weakSelf, strUrl, iFailedCnt]() { + // 播放失败次数越多,则延时越长 + auto strongPlayer = weakSelf.lock(); + if (!strongPlayer) { + return false; + } + WarnL << "重试播放[" << iFailedCnt << "]:" << strUrl; + strongPlayer->MediaPlayer::play(strUrl); + strongPlayer->setDirectProxy(); return false; - } - WarnL << "重试播放[" << iFailedCnt << "]:" << strUrl; - strongPlayer->MediaPlayer::play(strUrl); - strongPlayer->setDirectProxy(); - return false; - }, getPoller()); + }, + getPoller()); } bool PlayerProxy::close(MediaSource &sender) { - //通知其停止推流 + // 通知其停止推流 weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); getPoller()->async_first([weakSelf]() { auto strongSelf = weakSelf.lock(); @@ -194,51 +271,69 @@ float PlayerProxy::getLossRate(MediaSource &sender, TrackType type) { return getPacketLossRate(type); } +TranslationInfo PlayerProxy::getTranslationInfo() { + return _transtalion_info; +} + void PlayerProxy::onPlaySuccess() { GET_CONFIG(bool, reset_when_replay, General::kResetWhenRePlay); if (dynamic_pointer_cast(_media_src)) { - //rtsp拉流代理 + // rtsp拉流代理 if (reset_when_replay || !_muxer) { _option.enable_rtsp = false; - _muxer = std::make_shared(_vhost, _app, _stream_id, getDuration(), _option); + _muxer = std::make_shared(_tuple, getDuration(), _option); } } else if (dynamic_pointer_cast(_media_src)) { - //rtmp拉流代理 + // rtmp拉流代理 if (reset_when_replay || !_muxer) { _option.enable_rtmp = false; - _muxer = std::make_shared(_vhost, _app, _stream_id, getDuration(), _option); + _muxer = std::make_shared(_tuple, getDuration(), _option); } } else { - //其他拉流代理 + // 其他拉流代理 if (reset_when_replay || !_muxer) { - _muxer = std::make_shared(_vhost, _app, _stream_id, getDuration(), _option); + _muxer = std::make_shared(_tuple, getDuration(), _option); } } _muxer->setMediaListener(shared_from_this()); auto videoTrack = getTrack(TrackVideo, false); if (videoTrack) { - //添加视频 + // 添加视频 _muxer->addTrack(videoTrack); - //视频数据写入_mediaMuxer + // 视频数据写入_mediaMuxer videoTrack->addDelegate(_muxer); } auto audioTrack = getTrack(TrackAudio, false); if (audioTrack) { - //添加音频 + // 添加音频 _muxer->addTrack(audioTrack); - //音频数据写入_mediaMuxer + // 音频数据写入_mediaMuxer audioTrack->addDelegate(_muxer); } - //添加完毕所有track,防止单track情况下最大等待3秒 + // 添加完毕所有track,防止单track情况下最大等待3秒 _muxer->addTrackCompleted(); if (_media_src) { - //让_muxer对象拦截一部分事件(比如说录像相关事件) + // 让_muxer对象拦截一部分事件(比如说录像相关事件) _media_src->setListener(_muxer); } } +int PlayerProxy::getStatus() { + return _live_status.load(); +} +uint64_t PlayerProxy::getLiveSecs() { + if (_live_status == 0) { + return _live_secs + _live_ticker.elapsedTime() / 1000; + } + return _live_secs; +} + +uint64_t PlayerProxy::getRePullCount() { + return _repull_count; +} + } /* namespace mediakit */ diff --git a/src/Player/PlayerProxy.h b/src/Player/PlayerProxy.h index 02cba77b..a0d1cf32 100644 --- a/src/Player/PlayerProxy.h +++ b/src/Player/PlayerProxy.h @@ -11,21 +11,64 @@ #ifndef SRC_DEVICE_PLAYERPROXY_H_ #define SRC_DEVICE_PLAYERPROXY_H_ -#include #include "Common/MultiMediaSourceMuxer.h" #include "Player/MediaPlayer.h" #include "Util/TimeTicker.h" +#include namespace mediakit { -class PlayerProxy : public MediaPlayer, public MediaSourceEvent, public std::enable_shared_from_this { +struct StreamInfo +{ + TrackType codec_type; + std::string codec_name; + int bitrate; + int audio_sample_rate; + int audio_sample_bit; + int audio_channel; + int video_width; + int video_height; + float video_fps; + + StreamInfo() + { + codec_type = TrackInvalid; + codec_name = "none"; + bitrate = -1; + audio_sample_rate = -1; + audio_channel = -1; + audio_sample_bit = -1; + video_height = -1; + video_width = -1; + video_fps = -1.0; + } +}; + +struct TranslationInfo +{ + std::vector stream_info; + int byte_speed; + uint64_t start_time_stamp; + + TranslationInfo() + { + byte_speed = -1; + start_time_stamp = 0; + } +}; + +class PlayerProxy + : public MediaPlayer + , public MediaSourceEvent + , public std::enable_shared_from_this { public: using Ptr = std::shared_ptr; - //如果retry_count<0,则一直重试播放;否则重试retry_count次数 - //默认一直重试 - PlayerProxy(const std::string &vhost, const std::string &app, const std::string &stream_id, - const ProtocolOption &option, int retry_count = -1, const toolkit::EventPoller::Ptr &poller = nullptr); + // 如果retry_count<0,则一直重试播放;否则重试retry_count次数 + // 默认一直重试 + PlayerProxy( + const std::string &vhost, const std::string &app, const std::string &stream_id, const ProtocolOption &option, int retry_count = -1, + const toolkit::EventPoller::Ptr &poller = nullptr, int reconnect_delay_min = 2, int reconnect_delay_max = 60, int reconnect_delay_step = 3); ~PlayerProxy() override; @@ -33,13 +76,25 @@ public: * 设置play结果回调,只触发一次;在play执行之前有效 * @param cb 回调对象 */ - void setPlayCallbackOnce(const std::function &cb); + void setPlayCallbackOnce(std::function cb); /** * 设置主动关闭回调 * @param cb 回调对象 */ - void setOnClose(const std::function &cb); + void setOnClose(std::function cb); + + /** + * Set a callback for failed server connection + * @param cb 回调对象 + */ + void setOnDisconnect(std::function cb); + + /** + * Set a callback for a successful connection to the server + * @param cb 回调对象 + */ + void setOnConnect(std::function cb); /** * 开始拉流播放 @@ -50,10 +105,17 @@ public: /** * 获取观看总人数 */ - int totalReaderCount() ; + int totalReaderCount(); + + int getStatus(); + uint64_t getLiveSecs(); + uint64_t getRePullCount(); + + // Using this only makes sense after a successful connection to the server + TranslationInfo getTranslationInfo(); private: - //MediaSourceEvent override + // MediaSourceEvent override bool close(MediaSource &sender) override; int totalReaderCount(MediaSource &sender) override; MediaOriginType getOriginType(MediaSource &sender) const override; @@ -61,21 +123,33 @@ private: std::shared_ptr getOriginSock(MediaSource &sender) const override; float getLossRate(MediaSource &sender, TrackType type) override; - void rePlay(const std::string &strUrl,int iFailedCnt); + void rePlay(const std::string &strUrl, int iFailedCnt); void onPlaySuccess(); void setDirectProxy(); + void setTranslationInfo(); private: ProtocolOption _option; int _retry_count; - std::string _vhost; - std::string _app; - std::string _stream_id; + int _reconnect_delay_min; + int _reconnect_delay_max; + int _reconnect_delay_step; + MediaTuple _tuple; std::string _pull_url; toolkit::Timer::Ptr _timer; + std::function _on_disconnect; + std::function _on_connect; std::function _on_close; std::function _on_play; + TranslationInfo _transtalion_info; MultiMediaSourceMuxer::Ptr _muxer; + + toolkit::Ticker _live_ticker; + // 0 表示正常 1 表示正在尝试拉流 + std::atomic _live_status; + std::atomic _live_secs; + + std::atomic _repull_count; }; } /* namespace mediakit */ diff --git a/src/Pusher/MediaPusher.cpp b/src/Pusher/MediaPusher.cpp index 865b4d2a..f0ea401b 100644 --- a/src/Pusher/MediaPusher.cpp +++ b/src/Pusher/MediaPusher.cpp @@ -31,8 +31,7 @@ MediaPusher::MediaPusher(const string &schema, MediaPusher(MediaSource::find(schema, vhost, app, stream), poller){ } -MediaPusher::~MediaPusher() { -} +MediaPusher::~MediaPusher() = default; static void setOnCreateSocket_l(const std::shared_ptr &delegate, const Socket::onCreateSocket &cb){ auto helper = dynamic_pointer_cast(delegate); diff --git a/src/Pusher/PusherBase.cpp b/src/Pusher/PusherBase.cpp index fa0d68e8..eddbf7b8 100644 --- a/src/Pusher/PusherBase.cpp +++ b/src/Pusher/PusherBase.cpp @@ -26,7 +26,7 @@ PusherBase::Ptr PusherBase::createPusher(const EventPoller::Ptr &poller, }); ptr->teardown(); }; - std::string prefix = FindField(url.data(), NULL, "://"); + std::string prefix = findSubString(url.data(), NULL, "://"); if (strcasecmp("rtsps",prefix.data()) == 0) { return PusherBase::Ptr(new TcpClientWithSSL(poller, std::dynamic_pointer_cast(src)), releasePusher); diff --git a/src/Pusher/PusherProxy.cpp b/src/Pusher/PusherProxy.cpp index 22a658e7..ffe1a500 100644 --- a/src/Pusher/PusherProxy.cpp +++ b/src/Pusher/PusherProxy.cpp @@ -16,10 +16,13 @@ using namespace std; namespace mediakit { PusherProxy::PusherProxy(const MediaSource::Ptr &src, int retry_count, const EventPoller::Ptr &poller) - : MediaPusher(src, poller){ + : MediaPusher(src, poller) { _retry_count = retry_count; _on_close = [](const SockException &) {}; _weak_src = src; + _live_secs = 0; + _live_status = 1; + _republish_count = 0; } PusherProxy::~PusherProxy() { @@ -52,13 +55,17 @@ void PusherProxy::publish(const string &dst_url) { auto src = strong_self->_weak_src.lock(); if (!err) { // 推流成功 + strong_self->_live_ticker.resetTime(); + strong_self->_live_status = 0; *failed_cnt = 0; InfoL << "Publish " << dst_url << " success"; } else if (src && (*failed_cnt < strong_self->_retry_count || strong_self->_retry_count < 0)) { // 推流失败,延时重试推送 + strong_self->_republish_count++; + strong_self->_live_status = 1; strong_self->rePublish(dst_url, (*failed_cnt)++); } else { - //如果媒体源已经注销, 或达到了最大重试次数,回调关闭 + // 如果媒体源已经注销, 或达到了最大重试次数,回调关闭 strong_self->_on_close(err); } }); @@ -69,12 +76,20 @@ void PusherProxy::publish(const string &dst_url) { return; } + if (*failed_cnt == 0) { + // 第一次重推更新时长 + strong_self->_live_secs += strong_self->_live_ticker.elapsedTime() / 1000; + strong_self->_live_ticker.resetTime(); + TraceL << " live secs " << strong_self->_live_secs; + } + auto src = strong_self->_weak_src.lock(); - //推流异常中断,延时重试播放 + // 推流异常中断,延时重试播放 if (src && (*failed_cnt < strong_self->_retry_count || strong_self->_retry_count < 0)) { + strong_self->_republish_count++; strong_self->rePublish(dst_url, (*failed_cnt)++); } else { - //如果媒体源已经注销, 或达到了最大重试次数,回调关闭 + // 如果媒体源已经注销, 或达到了最大重试次数,回调关闭 strong_self->_on_close(err); } }); @@ -85,16 +100,34 @@ void PusherProxy::publish(const string &dst_url) { void PusherProxy::rePublish(const string &dst_url, int failed_cnt) { auto delay = MAX(2 * 1000, MIN(failed_cnt * 3000, 60 * 1000)); weak_ptr weak_self = shared_from_this(); - _timer = std::make_shared(delay / 1000.0f, [weak_self, dst_url, failed_cnt]() { - //推流失败次数越多,则延时越长 - auto strong_self = weak_self.lock(); - if (!strong_self) { + _timer = std::make_shared( + delay / 1000.0f, + [weak_self, dst_url, failed_cnt]() { + // 推流失败次数越多,则延时越长 + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + WarnL << "推流重试[" << failed_cnt << "]:" << dst_url; + strong_self->MediaPusher::publish(dst_url); return false; - } - WarnL << "推流重试[" << failed_cnt << "]:" << dst_url; - strong_self->MediaPusher::publish(dst_url); - return false; - }, getPoller()); + }, + getPoller()); +} + +int PusherProxy::getStatus() { + return _live_status.load(); +} +uint64_t PusherProxy::getLiveSecs() { + if (_live_status == 0) { + return _live_secs + _live_ticker.elapsedTime() / 1000; + } else { + return _live_secs; + } +} + +uint64_t PusherProxy::getRePublishCount() { + return _republish_count; } } /* namespace mediakit */ diff --git a/src/Pusher/PusherProxy.h b/src/Pusher/PusherProxy.h index bee32d63..e0146514 100644 --- a/src/Pusher/PusherProxy.h +++ b/src/Pusher/PusherProxy.h @@ -16,7 +16,9 @@ namespace mediakit { -class PusherProxy : public MediaPusher, public std::enable_shared_from_this { +class PusherProxy + : public MediaPusher + , public std::enable_shared_from_this { public: using Ptr = std::shared_ptr; @@ -41,7 +43,11 @@ public: * 开始拉流播放 * @param dstUrl 目标推流地址 */ - void publish(const std::string& dstUrl) override; + void publish(const std::string &dstUrl) override; + + int getStatus(); + uint64_t getLiveSecs(); + uint64_t getRePublishCount(); private: // 重推逻辑函数 @@ -50,6 +56,11 @@ private: private: int _retry_count; toolkit::Timer::Ptr _timer; + toolkit::Ticker _live_ticker; + // 0 表示正常 1 表示正在尝试推流 + std::atomic _live_status; + std::atomic _live_secs; + std::atomic _republish_count; std::weak_ptr _weak_src; std::function _on_close; std::function _on_publish; @@ -57,4 +68,4 @@ private: } /* namespace mediakit */ -#endif //SRC_DEVICE_PUSHERPROXY_H +#endif // SRC_DEVICE_PUSHERPROXY_H diff --git a/src/Record/HlsMaker.cpp b/src/Record/HlsMaker.cpp index df4cb8de..31f705b8 100644 --- a/src/Record/HlsMaker.cpp +++ b/src/Record/HlsMaker.cpp @@ -8,6 +8,7 @@ * may be found in the AUTHORS file in the root of the source tree. */ +#include #include "HlsMaker.h" #include "Common/config.h" @@ -15,85 +16,76 @@ using namespace std; namespace mediakit { -HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number, bool seg_keep) { +HlsMaker::HlsMaker(bool is_fmp4, float seg_duration, uint32_t seg_number, bool seg_keep) { + _is_fmp4 = is_fmp4; //最小允许设置为0,0个切片代表点播 _seg_number = seg_number; _seg_duration = seg_duration; _seg_keep = seg_keep; } -HlsMaker::~HlsMaker() { -} - - void HlsMaker::makeIndexFile(bool eof) { - char file_content[1024]; int maxSegmentDuration = 0; - for (auto &tp : _seg_dur_list) { int dur = std::get<0>(tp); if (dur > maxSegmentDuration) { maxSegmentDuration = dur; } } + auto index_seq = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL; - auto sequence = _seg_number ? (_file_index > _seg_number ? _file_index - _seg_number : 0LL) : 0LL; - - string m3u8; - if (_seg_number == 0) { - // 录像点播支持时移 - snprintf(file_content, sizeof(file_content), - "#EXTM3U\n" - "#EXT-X-PLAYLIST-TYPE:EVENT\n" - "#EXT-X-VERSION:4\n" - "#EXT-X-TARGETDURATION:%u\n" - "#EXT-X-MEDIA-SEQUENCE:%llu\n", - (maxSegmentDuration + 999) / 1000, - sequence); + string index_str; + index_str.reserve(2048); + index_str += "#EXTM3U\n"; + index_str += (_is_fmp4 ? "#EXT-X-VERSION:7\n" : "#EXT-X-VERSION:4\n"); + if (_seg_number == 0) { + index_str += "#EXT-X-PLAYLIST-TYPE:EVENT\n"; } else { - snprintf(file_content, sizeof(file_content), - "#EXTM3U\n" - "#EXT-X-VERSION:3\n" - "#EXT-X-ALLOW-CACHE:NO\n" - "#EXT-X-TARGETDURATION:%u\n" - "#EXT-X-MEDIA-SEQUENCE:%llu\n", - (maxSegmentDuration + 999) / 1000, - sequence); + index_str += "#EXT-X-ALLOW-CACHE:NO\n"; + } + index_str += "#EXT-X-TARGETDURATION:" + std::to_string((maxSegmentDuration + 999) / 1000) + "\n"; + index_str += "#EXT-X-MEDIA-SEQUENCE:" + std::to_string(index_seq) + "\n"; + if (_is_fmp4) { + index_str += "#EXT-X-MAP:URI=\"init.mp4\"\n"; } - - m3u8.assign(file_content); + stringstream ss; for (auto &tp : _seg_dur_list) { - snprintf(file_content, sizeof(file_content), "#EXTINF:%.3f,\n%s\n", std::get<0>(tp) / 1000.0, std::get<1>(tp).data()); - m3u8.append(file_content); + ss << "#EXTINF:" << std::setprecision(3) << std::get<0>(tp) / 1000.0 << ",\n" << std::get<1>(tp) << "\n"; } + index_str += ss.str(); if (eof) { - snprintf(file_content, sizeof(file_content), "#EXT-X-ENDLIST\n"); - m3u8.append(file_content); + index_str += "#EXT-X-ENDLIST\n"; } - onWriteHls(m3u8); + onWriteHls(index_str); } +void HlsMaker::inputInitSegment(const char *data, size_t len) { + if (!_is_fmp4) { + throw std::invalid_argument("Only fmp4-hls can input init segment"); + } + onWriteInitSegment(data, len); +} -void HlsMaker::inputData(void *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) { +void HlsMaker::inputData(const char *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) { if (data && len) { if (timestamp < _last_timestamp) { - //时间戳回退了,切片时长重新计时 - WarnL << "stamp reduce: " << _last_timestamp << " -> " << timestamp; + // 时间戳回退了,切片时长重新计时 + WarnL << "Timestamp reduce: " << _last_timestamp << " -> " << timestamp; _last_seg_timestamp = _last_timestamp = timestamp; } if (is_idr_fast_packet) { - //尝试切片ts + // 尝试切片ts addNewSegment(timestamp); } if (!_last_file_name.empty()) { - //存在切片才写入ts数据 - onWriteSegment((char *) data, len); + // 存在切片才写入ts数据 + onWriteSegment(data, len); _last_timestamp = timestamp; } } else { - //resetTracks时触发此逻辑 + // resetTracks时触发此逻辑 flushLastSegment(false); } } @@ -150,14 +142,18 @@ void HlsMaker::flushLastSegment(bool eof){ makeIndexFile(eof); } -bool HlsMaker::isLive() { +bool HlsMaker::isLive() const { return _seg_number != 0; } -bool HlsMaker::isKeep() { +bool HlsMaker::isKeep() const { return _seg_keep; } +bool HlsMaker::isFmp4() const { + return _is_fmp4; +} + void HlsMaker::clear() { _file_index = 0; _last_timestamp = 0; diff --git a/src/Record/HlsMaker.h b/src/Record/HlsMaker.h index 57a5308a..185a19d1 100644 --- a/src/Record/HlsMaker.h +++ b/src/Record/HlsMaker.h @@ -14,18 +14,20 @@ #include #include #include +#include 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数据 @@ -34,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; /** * 清空记录 @@ -65,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 @@ -108,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 865561b7..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); } } @@ -163,14 +167,14 @@ std::shared_ptr HlsMakerImp::makeFile(const string &file, bool setbuf) { } void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const string &stream_id) { - _media_src = std::make_shared(vhost, app, stream_id); _info.app = app; _info.stream = stream_id; _info.vhost = vhost; + _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 406a6c6a..f3adb775 100644 --- a/src/Record/HlsMediaSource.cpp +++ b/src/Record/HlsMediaSource.cpp @@ -33,6 +33,12 @@ void HlsCookieData::addReaderCount() { // HlsMediaSource已经销毁 *added = false; }); + auto info = _sock_info; + _ring_reader->setGetInfoCB([info]() { + Any ret; + ret.set(info); + return ret; + }); } } } @@ -46,7 +52,11 @@ HlsCookieData::~HlsCookieData() { GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); uint64_t bytes = _bytes.load(); if (bytes >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, static_cast(*_sock_info)); + try { + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _info, bytes, duration, true, *_sock_info); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } } } } @@ -68,7 +78,7 @@ HlsMediaSource::Ptr HlsCookieData::getMediaSource() const { void HlsMediaSource::setIndexFile(std::string index_file) { if (!_ring) { - std::weak_ptr weakSelf = std::dynamic_pointer_cast(shared_from_this()); + std::weak_ptr weakSelf = std::static_pointer_cast(shared_from_this()); auto lam = [weakSelf](int size) { auto strongSelf = weakSelf.lock(); if (!strongSelf) { diff --git a/src/Record/HlsMediaSource.h b/src/Record/HlsMediaSource.h index 4211eae4..1e0ecd14 100644 --- a/src/Record/HlsMediaSource.h +++ b/src/Record/HlsMediaSource.h @@ -25,8 +25,7 @@ public: using RingType = toolkit::RingBuffer; using Ptr = std::shared_ptr; - HlsMediaSource(const std::string &vhost, const std::string &app, const std::string &stream_id) - : MediaSource(HLS_SCHEMA, vhost, app, stream_id) {} + HlsMediaSource(const std::string &schema, const MediaTuple &tuple) : MediaSource(schema, tuple) {} ~HlsMediaSource() override = default; /** @@ -59,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 12ff668e..aa1a4960 100644 --- a/src/Record/HlsRecorder.h +++ b/src/Record/HlsRecorder.h @@ -13,35 +13,35 @@ #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 std::string &vhost, const std::string &app, const std::string &stream_id) { - _hls->setMediaSource(vhost, app, stream_id); + void setMediaSource(const MediaTuple& tuple) { + _hls->setMediaSource(tuple.vhost, tuple.app, tuple.stream); } void setListener(const std::weak_ptr &listener) { setDelegate(listener); - _hls->getMediaSource()->setListener(shared_from_this()); + _hls->getMediaSource()->setListener(this->shared_from_this()); } int readerCount() { return _hls->getMediaSource()->readerCount(); } @@ -64,7 +64,7 @@ public: _hls->getMediaSource()->setIndexFile(""); } if (_enabled || !_option.hls_demand) { - return MpegMuxer::inputFrame(frame); + return Muxer::inputFrame(frame); } return false; } @@ -74,20 +74,54 @@ public: return _option.hls_demand ? (_clear_cache ? true : _enabled) : true; } -private: - void onWrite(std::shared_ptr buffer, uint64_t timestamp, bool key_pos) override { - if (!buffer) { - _hls->inputData(nullptr, 0, timestamp, key_pos); - } else { - _hls->inputData(buffer->data(), buffer->size(), timestamp, key_pos); - } - } - -private: +protected: bool _enabled = true; bool _clear_cache = false; ProtocolOption _option; std::shared_ptr _hls; }; + +class HlsRecorder final : public HlsRecorderBase { +public: + using Ptr = std::shared_ptr; + template + HlsRecorder(ARGS && ...args) : HlsRecorderBase(false, std::forward(args)...) {} + ~HlsRecorder() override { this->flush(); } + +private: + void onWrite(std::shared_ptr buffer, uint64_t timestamp, bool key_pos) override { + if (!buffer) { + // reset tracks + _hls->inputData(nullptr, 0, timestamp, key_pos); + } else { + _hls->inputData(buffer->data(), buffer->size(), timestamp, key_pos); + } + } +}; + +class HlsFMP4Recorder final : public HlsRecorderBase { +public: + using Ptr = std::shared_ptr; + template + HlsFMP4Recorder(ARGS && ...args) : HlsRecorderBase(true, std::forward(args)...) {} + ~HlsFMP4Recorder() override { this->flush(); } + + void addTrackCompleted() override { + HlsRecorderBase::addTrackCompleted(); + auto data = getInitSegment(); + _hls->inputInitSegment(data.data(), data.size()); + } + +private: + void onSegmentData(std::string buffer, uint64_t timestamp, bool key_pos) override { + if (buffer.empty()) { + // reset tracks + _hls->inputData(nullptr, 0, timestamp, key_pos); + } else { + _hls->inputData((char *)buffer.data(), buffer.size(), timestamp, key_pos); + } + } +}; + }//namespace mediakit #endif //HLSRECORDER_H diff --git a/src/Record/MP4.cpp b/src/Record/MP4.cpp index 8d92c256..10f1b0ee 100644 --- a/src/Record/MP4.cpp +++ b/src/Record/MP4.cpp @@ -8,7 +8,8 @@ * may be found in the AUTHORS file in the root of the source tree. */ -#ifdef ENABLE_MP4 +#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) + #include "MP4.h" #include "Util/File.h" #include "Util/logger.h" @@ -176,4 +177,4 @@ int MP4FileMemory::onWrite(const void *data, size_t bytes){ } }//namespace mediakit -#endif //NABLE_MP4RECORD +#endif // defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) diff --git a/src/Record/MP4.h b/src/Record/MP4.h index 14bc1ca7..63e7af9c 100644 --- a/src/Record/MP4.h +++ b/src/Record/MP4.h @@ -11,7 +11,7 @@ #ifndef ZLMEDIAKIT_MP4_H #define ZLMEDIAKIT_MP4_H -#ifdef ENABLE_MP4 +#if defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) #include #include @@ -136,5 +136,5 @@ private: }; }//namespace mediakit -#endif //NABLE_MP4RECORD +#endif //defined(ENABLE_MP4) || defined(ENABLE_HLS_FMP4) #endif //ZLMEDIAKIT_MP4_H diff --git a/src/Record/MP4Demuxer.cpp b/src/Record/MP4Demuxer.cpp index a1c657fd..02c66976 100644 --- a/src/Record/MP4Demuxer.cpp +++ b/src/Record/MP4Demuxer.cpp @@ -21,7 +21,7 @@ using namespace std; namespace mediakit { -MP4Demuxer::MP4Demuxer() {} +MP4Demuxer::MP4Demuxer() = default; MP4Demuxer::~MP4Demuxer() { closeMP4(); @@ -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 35e28ac8..2def3781 100644 --- a/src/Record/MP4Reader.cpp +++ b/src/Record/MP4Reader.cpp @@ -20,17 +20,18 @@ using namespace toolkit; namespace mediakit { -MP4Reader::MP4Reader(const string &vhost, const string &app, const string &stream_id, const string &file_path) { +MP4Reader::MP4Reader(const std::string &vhost, const std::string &app, const std::string &stream_id, const string &file_path) { //读写文件建议放在后台线程 + auto tuple = MediaTuple{vhost, app, stream_id}; _poller = WorkThreadPool::Instance().getPoller(); _file_path = file_path; if (_file_path.empty()) { GET_CONFIG(string, recordPath, Protocol::kMP4SavePath); GET_CONFIG(bool, enableVhost, General::kEnableVhost); if (enableVhost) { - _file_path = vhost + "/" + app + "/" + stream_id; + _file_path = tuple.shortUrl(); } else { - _file_path = app + "/" + stream_id; + _file_path = tuple.app + "/" + tuple.stream; } _file_path = File::absolutePath(_file_path, recordPath); } @@ -38,14 +39,14 @@ MP4Reader::MP4Reader(const string &vhost, const string &app, const string &strea _demuxer = std::make_shared(); _demuxer->openMP4(_file_path); - if (stream_id.empty()) { + if (tuple.stream.empty()) { return; } ProtocolOption option; //读取mp4文件并流化时,不重复生成mp4/hls文件 option.enable_mp4 = false; option.enable_hls = false; - _muxer = std::make_shared(vhost, app, stream_id, _demuxer->getDurationMS() / 1000.0f, option); + _muxer = std::make_shared(tuple, _demuxer->getDurationMS() / 1000.0f, option); auto tracks = _demuxer->getTracks(false); if (tracks.empty()) { throw std::runtime_error(StrPrinter << "该mp4文件没有有效的track:" << _file_path); @@ -179,12 +180,14 @@ bool MP4Reader::pause(MediaSource &sender, bool pause) { } bool MP4Reader::speed(MediaSource &sender, float speed) { - if (speed < 0.1 && speed > 20) { + if (speed < 0.1 || speed > 20) { WarnL << "播放速度取值范围非法:" << speed; return false; } - //设置播放速度后应该恢复播放 - pause(sender, false); + //_seek_ticker重置,赋值_seek_to + setCurrentStamp(getCurrentStamp()); + // 设置播放速度后应该恢复播放 + _paused = false; if (_speed == speed) { return true; } diff --git a/src/Record/MP4Recorder.cpp b/src/Record/MP4Recorder.cpp index fd7c8955..5373b8a1 100644 --- a/src/Record/MP4Recorder.cpp +++ b/src/Record/MP4Recorder.cpp @@ -41,19 +41,20 @@ MP4Recorder::~MP4Recorder() { void MP4Recorder::createFile() { closeFile(); auto date = getTimeStr("%Y-%m-%d"); - auto time = getTimeStr("%H-%M-%S"); - auto full_path_tmp = _folder_path + date + "/." + time + ".mp4"; - auto full_path = _folder_path + date + "/" + time + ".mp4"; + auto file_name = getTimeStr("%H-%M-%S") + "-" + std::to_string(_file_index++) + ".mp4"; + auto full_path = _folder_path + date + "/" + file_name; + auto full_path_tmp = _folder_path + date + "/." + file_name; /////record 业务逻辑////// _info.start_time = ::time(NULL); - _info.file_name = time + ".mp4"; + _info.file_name = file_name; _info.file_path = full_path; GET_CONFIG(string, appName, Record::kAppName); - _info.url = appName + "/" + _info.app + "/" + _info.stream + "/" + date + "/" + time + ".mp4"; + _info.url = appName + "/" + _info.app + "/" + _info.stream + "/" + date + "/" + file_name; try { _muxer = std::make_shared(); + TraceL << "Open tmp mp4 file: " << full_path_tmp; _muxer->openMP4(full_path_tmp); for (auto &track :_tracks) { //添加track @@ -71,10 +72,13 @@ void MP4Recorder::asyncClose() { auto full_path_tmp = _full_path_tmp; auto full_path = _full_path; auto info = _info; + TraceL << "Start close tmp mp4 file: " << full_path_tmp; WorkThreadPool::Instance().getExecutor()->async([muxer, full_path_tmp, full_path, info]() mutable { info.time_len = muxer->getDuration() / 1000.0f; // 关闭mp4可能非常耗时,所以要放在后台线程执行 + TraceL << "Closing tmp mp4 file: " << full_path_tmp; muxer->closeMP4(); + TraceL << "Closed tmp mp4 file: " << full_path_tmp; if (!full_path_tmp.empty()) { // 获取文件大小 info.file_size = File::fileSize(full_path_tmp.data()); @@ -86,8 +90,9 @@ void MP4Recorder::asyncClose() { // 临时文件名改成正式文件名,防止mp4未完成时被访问 rename(full_path_tmp.data(), full_path.data()); } + TraceL << "Emit mp4 record event: " << full_path; //触发mp4录制切片生成事件 - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordMP4, info); + NOTICE_EMIT(BroadcastRecordMP4Args, Broadcast::kBroadcastRecordMP4, info); }); } diff --git a/src/Record/MP4Recorder.h b/src/Record/MP4Recorder.h index 69175238..3e0c8013 100644 --- a/src/Record/MP4Recorder.h +++ b/src/Record/MP4Recorder.h @@ -57,13 +57,14 @@ private: private: bool _have_video = false; size_t _max_second; + uint64_t _last_dts = 0; + uint64_t _file_index = 0; std::string _folder_path; std::string _full_path; std::string _full_path_tmp; RecordInfo _info; MP4Muxer::Ptr _muxer; std::list _tracks; - uint64_t _last_dts = 0; }; #endif ///ENABLE_MP4 diff --git a/src/Record/MPEG.cpp b/src/Record/MPEG.cpp index 985cf98f..153fd796 100644 --- a/src/Record/MPEG.cpp +++ b/src/Record/MPEG.cpp @@ -30,19 +30,18 @@ MpegMuxer::~MpegMuxer() { releaseContext(); } -#define XX(name, type, value, str, mpeg_id) \ - case name : { \ - if (mpeg_id == PSI_STREAM_RESERVED) { \ - break; \ - } \ - _codec_to_trackid[track->getCodecId()] = mpeg_muxer_add_stream((::mpeg_muxer_t *)_context, mpeg_id, nullptr, 0); \ - return true; \ +#define XX(name, type, value, str, mpeg_id) \ + case name: { \ + if (mpeg_id == PSI_STREAM_RESERVED) { \ + break; \ + } \ + if (track->getTrackType() == TrackVideo) { \ + _have_video = true; \ + } \ + _codec_to_trackid[track->getCodecId()] = mpeg_muxer_add_stream((::mpeg_muxer_t *)_context, mpeg_id, nullptr, 0); \ + return true; \ } - bool MpegMuxer::addTrack(const Track::Ptr &track) { - if (track->getTrackType() == TrackVideo) { - _have_video = true; - } switch (track->getCodecId()) { CODEC_MAP(XX) default: break; @@ -85,6 +84,11 @@ bool MpegMuxer::inputFrame(const Frame::Ptr &frame) { //没有视频时,才以音频时间戳为TS的时间戳 _timestamp = frame->dts(); } + + if(frame->getTrackType() == TrackType::TrackVideo){ + _key_pos = frame->keyFrame(); + _timestamp = frame->dts(); + } _max_cache_size = 512 + 1.2 * frame->size(); mpeg_muxer_input((::mpeg_muxer_t *)_context, track_id, frame->keyFrame() ? 0x0001 : 0, frame->pts() * 90LL, frame->dts() * 90LL, frame->data(), frame->size()); flushCache(); diff --git a/src/Record/MPEG.h b/src/Record/MPEG.h index 4cb456db..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) = default; + 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 f3e91db5..6183ff1c 100644 --- a/src/Record/Recorder.cpp +++ b/src/Record/Recorder.cpp @@ -14,23 +14,24 @@ #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; namespace mediakit { -string Recorder::getRecordPath(Recorder::type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path) { +string Recorder::getRecordPath(Recorder::type type, const MediaTuple& tuple, const string &customized_path) { GET_CONFIG(bool, enableVhost, General::kEnableVhost); switch (type) { case Recorder::type_hls: { GET_CONFIG(string, hlsPath, Protocol::kHlsSavePath); string m3u8FilePath; if (enableVhost) { - m3u8FilePath = vhost + "/" + app + "/" + stream_id + "/hls.m3u8"; + m3u8FilePath = tuple.shortUrl() + "/hls.m3u8"; } else { - m3u8FilePath = app + "/" + stream_id + "/hls.m3u8"; + m3u8FilePath = tuple.app + "/" + tuple.stream + "/hls.m3u8"; } //Here we use the customized file path. if (!customized_path.empty()) { @@ -43,9 +44,9 @@ string Recorder::getRecordPath(Recorder::type type, const string &vhost, const s GET_CONFIG(string, recordAppName, Record::kAppName); string mp4FilePath; if (enableVhost) { - mp4FilePath = vhost + "/" + recordAppName + "/" + app + "/" + stream_id + "/"; + mp4FilePath = tuple.vhost + "/" + recordAppName + "/" + tuple.app + "/" + tuple.stream + "/"; } else { - mp4FilePath = recordAppName + "/" + app + "/" + stream_id + "/"; + mp4FilePath = recordAppName + "/" + tuple.app + "/" + tuple.stream + "/"; } //Here we use the customized file path. if (!customized_path.empty()) { @@ -53,19 +54,33 @@ string Recorder::getRecordPath(Recorder::type type, const string &vhost, const s } 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 ""; } } -std::shared_ptr Recorder::createRecorder(type type, const string &vhost, const string &app, const string &stream_id, const ProtocolOption &option){ +std::shared_ptr Recorder::createRecorder(type type, const MediaTuple& tuple, const ProtocolOption &option){ switch (type) { case Recorder::type_hls: { #if defined(ENABLE_HLS) - auto path = Recorder::getRecordPath(type, vhost, app, stream_id, option.hls_save_path); + 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) + "=" + vhost : "", option); - ret->setMediaSource(vhost, app, stream_id); + 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相关功能未打开,请开启ENABLE_HLS宏后编译再测试"); @@ -75,13 +90,41 @@ std::shared_ptr Recorder::createRecorder(type type, const st case Recorder::type_mp4: { #if defined(ENABLE_MP4) - auto path = Recorder::getRecordPath(type, vhost, app, stream_id, option.mp4_save_path); - return std::make_shared(path, vhost, app, stream_id, option.mp4_max_second); + auto path = Recorder::getRecordPath(type, tuple, option.mp4_save_path); + return std::make_shared(path, tuple.vhost, tuple.app, tuple.stream, option.mp4_max_second); #else throw std::invalid_argument("mp4相关功能未打开,请开启ENABLE_MP4宏后编译再测试"); #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 491088ff..42763b32 100644 --- a/src/Record/Recorder.h +++ b/src/Record/Recorder.h @@ -18,7 +18,16 @@ namespace mediakit { class MediaSinkInterface; class ProtocolOption; -class RecordInfo { +struct MediaTuple { + std::string vhost; + std::string app; + std::string stream; + std::string shortUrl() const { + return vhost + '/' + app + '/' + stream; + } +}; + +class RecordInfo: public MediaTuple { public: time_t start_time; // GMT 标准时间,单位秒 float time_len; // 录像长度,单位秒 @@ -27,9 +36,6 @@ public: std::string file_name; // 文件名称 std::string folder; // 文件夹路径 std::string url; // 播放路径 - std::string app; // 应用名称 - std::string stream; // 流 ID - std::string vhost; // 虚拟主机 }; class Recorder{ @@ -38,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; /** @@ -50,7 +62,7 @@ public: * @param customized_path 录像文件保存自定义根目录,为空则采用配置文件设置 * @return 录制文件绝对路径 */ - static std::string getRecordPath(type type, const std::string &vhost, const std::string &app, const std::string &stream_id,const std::string &customized_path = ""); + static std::string getRecordPath(type type, const MediaTuple& tuple, const std::string &customized_path = ""); /** * 创建录制器对象 @@ -62,7 +74,7 @@ public: * @param max_second mp4录制最大切片时间,单位秒,置0则采用配置文件配置 * @return 对象指针,可能为nullptr */ - static std::shared_ptr createRecorder(type type, const std::string &vhost, const std::string &app, const std::string &stream_id, const ProtocolOption &option); + static std::shared_ptr createRecorder(type type, const MediaTuple& tuple, const ProtocolOption &option); private: Recorder() = delete; 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/RtcpContext.cpp b/src/Rtcp/RtcpContext.cpp index 796d3303..6d4469b4 100644 --- a/src/Rtcp/RtcpContext.cpp +++ b/src/Rtcp/RtcpContext.cpp @@ -34,7 +34,7 @@ size_t RtcpContext::getLost() { throw std::runtime_error("没有实现, rtp发送者无法统计丢包率"); } -size_t RtcpContext::geLostInterval() { +size_t RtcpContext::getLostInterval() { throw std::runtime_error("没有实现, rtp发送者无法统计丢包率"); } @@ -231,7 +231,7 @@ size_t RtcpContextForRecv::getLost() { return getExpectedPackets() - _packets; } -size_t RtcpContextForRecv::geLostInterval() { +size_t RtcpContextForRecv::getLostInterval() { auto lost = getLost(); auto ret = lost - _last_lost; _last_lost = lost; @@ -248,7 +248,7 @@ Buffer::Ptr RtcpContextForRecv::createRtcpRR(uint32_t rtcp_ssrc, uint32_t rtp_ss uint8_t fraction = 0; auto expected_interval = getExpectedPacketsInterval(); if (expected_interval) { - fraction = uint8_t(geLostInterval() << 8 / expected_interval); + fraction = uint8_t(getLostInterval() << 8 / expected_interval); } item->fraction = fraction; diff --git a/src/Rtcp/RtcpContext.h b/src/Rtcp/RtcpContext.h index 31b6167d..79920311 100644 --- a/src/Rtcp/RtcpContext.h +++ b/src/Rtcp/RtcpContext.h @@ -78,7 +78,7 @@ public: /** * 上次结果与本次结果间丢包个数 */ - virtual size_t geLostInterval(); + virtual size_t getLostInterval(); protected: // 收到或发送的rtp的字节数 @@ -120,7 +120,7 @@ public: size_t getExpectedPackets() const override; size_t getExpectedPacketsInterval() override; size_t getLost() override; - size_t geLostInterval() override; + size_t getLostInterval() override; void onRtcp(RtcpHeader *rtcp) override; private: diff --git a/src/Rtcp/RtcpFCI.cpp b/src/Rtcp/RtcpFCI.cpp index c6508217..8ed27670 100644 --- a/src/Rtcp/RtcpFCI.cpp +++ b/src/Rtcp/RtcpFCI.cpp @@ -240,7 +240,7 @@ public: RunLengthChunk(SymbolStatus status, uint16_t run_length); // 打印本对象 string dumpString() const; -} PACKED; +}; RunLengthChunk::RunLengthChunk(SymbolStatus status, uint16_t run_length) { type = 0; @@ -291,7 +291,7 @@ public: StatusVecChunk(bool symbol_bit, const vector &status); // 打印本对象 string dumpString() const; -} PACKED; +}; StatusVecChunk::StatusVecChunk(bool symbol_bit, const vector &status) { CHECK(status.size() << symbol_bit <= 14); diff --git a/src/Rtcp/RtcpFCI.h b/src/Rtcp/RtcpFCI.h index 745dc16a..5ce36aa2 100644 --- a/src/Rtcp/RtcpFCI.h +++ b/src/Rtcp/RtcpFCI.h @@ -55,7 +55,7 @@ public: private: uint32_t data; -} PACKED; +}; #if 0 //PSFB fmt = 3 @@ -97,7 +97,7 @@ public: uint8_t padding; static size_t constexpr kSize = 8; -} PACKED; +}; #endif // PSFB fmt = 4 @@ -125,7 +125,7 @@ private: uint32_t ssrc; uint8_t seq_number; uint8_t reserved[3]; -} PACKED; +}; #if 0 //PSFB fmt = 5 @@ -147,7 +147,7 @@ public: private: uint8_t data[kSize]; -} PACKED; +}; //PSFB fmt = 6 //https://tools.ietf.org/html/rfc5104#section-4.3.2.1 @@ -160,7 +160,7 @@ private: // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ class FCI_TSTN : public FCI_TSTR{ -} PACKED; +}; //PSFB fmt = 7 //https://tools.ietf.org/html/rfc5104#section-4.3.4.1 @@ -183,7 +183,7 @@ public: private: uint8_t data[kSize]; -} PACKED; +}; #endif @@ -233,7 +233,7 @@ private: // SSRC feedback (32 bits) Consists of one or more SSRC entries which // this feedback message applies to. uint32_t ssrc_feedback[1]; -} PACKED; +}; /////////////////////////////////////////// RTPFB //////////////////////////////////////////////////// @@ -265,7 +265,7 @@ private: uint16_t pid; // bitmask of following lost packets (BLP): 16 bits uint16_t blp; -} PACKED; +}; #if 0 //RTPFB fmt = 3 @@ -300,7 +300,7 @@ private: // to the description in section 4.2.1.2. The value is an // unsigned integer [0..511]. uint32_t max_tbr; -} PACKED; +}; //RTPFB fmt = 4 // https://tools.ietf.org/html/rfc5104#section-4.2.2.1 @@ -314,7 +314,7 @@ private: class FCI_TMMBN : public FCI_TMMBR{ public: -} PACKED; +}; #endif enum class SymbolStatus : uint8_t { @@ -374,7 +374,7 @@ private: uint8_t ref_time[3]; // feedback packet count,反馈包号,本包是第几个transport-cc包,每次加1 | uint8_t fb_pkt_count; -} PACKED; +}; } // namespace mediakit #endif // ZLMEDIAKIT_RTCPFCI_H diff --git a/src/Rtmp/FlvMuxer.cpp b/src/Rtmp/FlvMuxer.cpp index bd64acf9..b2d1a6ad 100644 --- a/src/Rtmp/FlvMuxer.cpp +++ b/src/Rtmp/FlvMuxer.cpp @@ -46,7 +46,11 @@ void FlvMuxer::start(const EventPoller::Ptr &poller, const RtmpMediaSource::Ptr std::weak_ptr weak_self = getSharedPtr(); media->pause(false); _ring_reader = media->getRing()->attach(poller); - _ring_reader->setGetInfoCB([weak_self]() { return dynamic_pointer_cast(weak_self.lock()); }); + _ring_reader->setGetInfoCB([weak_self]() { + Any ret; + ret.set(dynamic_pointer_cast(weak_self.lock())); + return ret; + }); _ring_reader->setDetachCB([weak_self]() { auto strong_self = weak_self.lock(); if (!strong_self) { @@ -97,26 +101,22 @@ void FlvMuxer::onWriteFlvHeader(const RtmpMediaSource::Ptr &src) { header->flv[0] = 'F'; header->flv[1] = 'L'; header->flv[2] = 'V'; - header->version = 1; - header->length = htonl(9); + header->version = FLVHeader::kFlvVersion; + header->length = htonl(FLVHeader::kFlvHeaderLength); header->have_video = src->haveVideo(); header->have_audio = src->haveAudio(); + //memset时已经赋值为0 + //header->previous_tag_size0 = 0; //flv header onWrite(buffer, false); - //PreviousTagSize0 Always 0 - auto size = htonl(0); - onWrite(obtainBuffer((char *) &size, 4), false); - - auto &metadata = src->getMetaData(); - if (metadata) { - //在有metadata的情况下才发送metadata - //其实metadata没什么用,有些推流器不产生metadata + // metadata + src->getMetaData([&](const AMFValue &metadata) { AMFEncoder invoke; invoke << "onMetaData" << metadata; onWriteFlvTag(MSG_DATA, std::make_shared(invoke.data()), 0, false); - } + }); //config frame src->getConfigFrame([&](const RtmpPacket::Ptr &pkt) { diff --git a/src/Rtmp/FlvPlayer.cpp b/src/Rtmp/FlvPlayer.cpp new file mode 100644 index 00000000..0095eb04 --- /dev/null +++ b/src/Rtmp/FlvPlayer.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "FlvPlayer.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +FlvPlayer::FlvPlayer(const EventPoller::Ptr &poller) { + setPoller(poller); +} + +void FlvPlayer::play(const string &url) { + TraceL << "play http-flv: " << url; + _play_result = false; + setHeaderTimeout((*this)[Client::kTimeoutMS].as()); + setBodyTimeout((*this)[Client::kMediaTimeoutMS].as()); + setMethod("GET"); + sendRequest(url); +} + +void FlvPlayer::onResponseHeader(const string &status, const HttpClient::HttpHeader &header) { + if (status != "200" && status != "206") { + // http状态码不符合预期 + throw invalid_argument("bad http status code:" + status); + } + + auto content_type = const_cast(header)["Content-Type"]; + if (content_type.find("video/x-flv") != 0) { + throw invalid_argument("content type not http-flv: " + content_type); + } +} + +void FlvPlayer::teardown() { + HttpClientImp::shutdown(); +} + +void FlvPlayer::onResponseCompleted(const SockException &ex) { + if (!_play_result) { + _play_result = true; + onPlayResult(ex); + } else { + onShutdown(ex); + } +} + +void FlvPlayer::onResponseBody(const char *buf, size_t size) { + if (!_benchmark_mode) { + // 性能测试模式不做数据解析,节省cpu + FlvSplitter::input(buf, size); + } +} + +bool FlvPlayer::onRecvMetadata(const AMFValue &metadata) { + return onMetadata(metadata); +} + +void FlvPlayer::onRecvRtmpPacket(RtmpPacket::Ptr packet) { + if (!_play_result && !packet->isConfigFrame()) { + _play_result = true; + _benchmark_mode = (*this)[Client::kBenchmarkMode].as(); + onPlayResult(SockException(Err_success, "play http-flv success")); + } + onRtmpPacket(std::move(packet)); +} + +}//mediakit \ No newline at end of file diff --git a/src/Rtmp/FlvPlayer.h b/src/Rtmp/FlvPlayer.h new file mode 100644 index 00000000..2e79dac2 --- /dev/null +++ b/src/Rtmp/FlvPlayer.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_FLVPLAYER_H +#define ZLMEDIAKIT_FLVPLAYER_H + +#include "FlvSplitter.h" +#include "Http/HttpClientImp.h" +#include "Player/PlayerBase.h" + +namespace mediakit { + +class FlvPlayer : public PlayerBase, public HttpClientImp, private FlvSplitter { +public: + FlvPlayer(const toolkit::EventPoller::Ptr &poller); + ~FlvPlayer() override = default; + + void play(const std::string &url) override; + void teardown() override; + +protected: + void onResponseHeader(const std::string &status, const HttpHeader &header) override; + void onResponseCompleted(const toolkit::SockException &ex) override; + void onResponseBody(const char *buf, size_t size) override; + +protected: + virtual void onRtmpPacket(RtmpPacket::Ptr packet) = 0; + virtual bool onMetadata(const AMFValue &metadata) = 0; + +private: + bool onRecvMetadata(const AMFValue &metadata) override; + void onRecvRtmpPacket(RtmpPacket::Ptr packet) override; + +private: + bool _play_result = false; + bool _benchmark_mode = false; +}; + +using FlvPlayerImp = FlvPlayerBase; + +}//namespace mediakit +#endif //ZLMEDIAKIT_FLVPLAYER_H diff --git a/src/Rtmp/FlvSplitter.cpp b/src/Rtmp/FlvSplitter.cpp new file mode 100644 index 00000000..3e99d9ef --- /dev/null +++ b/src/Rtmp/FlvSplitter.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ +#include "FlvSplitter.h" +#include "utils.h" + +using namespace std; +using namespace toolkit; + +namespace mediakit { + +const char *FlvSplitter::onSearchPacketTail(const char *data, size_t len) { + if (!_flv_started) { + //还没获取到flv头 + if (len < sizeof(FLVHeader)) { + //数据不够 + return nullptr; + } + return data + sizeof(FLVHeader); + } + + //获取到flv头,处理tag数据 + if (len < sizeof(RtmpTagHeader)) { + //数据不够 + return nullptr; + } + return data + sizeof(RtmpTagHeader); +} + +ssize_t FlvSplitter::onRecvHeader(const char *data, size_t len) { + if (!_flv_started) { + //获取到flv头了 + auto header = reinterpret_cast(data); + if (memcmp(header->flv, "FLV", 3)) { + throw std::invalid_argument("不是flv容器格式!"); + } + if (header->version != FLVHeader::kFlvVersion) { + throw std::invalid_argument("flv头中version字段不正确"); + } + if (!header->have_video && !header->have_audio) { + throw std::invalid_argument("flv头中声明音频和视频都不存在"); + } + if (FLVHeader::kFlvHeaderLength != ntohl(header->length)) { + throw std::invalid_argument("flv头中length字段非法"); + } + if (0 != ntohl(header->previous_tag_size0)) { + throw std::invalid_argument("flv头中previous tag size字段非法"); + } + onRecvFlvHeader(*header); + _flv_started = true; + return 0; + } + + //获取到flv头,处理tag数据 + auto tag = reinterpret_cast(data); + auto data_size = load_be24(tag->data_size); + _type = tag->type; + _time_stamp = load_be24(tag->timestamp); + _time_stamp |= (tag->timestamp_ex << 24); + return data_size + 4/*PreviousTagSize*/; +} + +void FlvSplitter::onRecvContent(const char *data, size_t len) { + len -= 4; + auto previous_tag_size = load_be32(data + len); + if (len != previous_tag_size - sizeof(RtmpTagHeader)) { + WarnL << "flv previous tag size 字段非法:" << len << " != " << previous_tag_size - sizeof(RtmpTagHeader); + } + RtmpPacket::Ptr packet; + switch (_type) { + case MSG_AUDIO : { + packet = RtmpPacket::create(); + packet->chunk_id = CHUNK_AUDIO; + packet->stream_index = STREAM_MEDIA; + break; + } + case MSG_VIDEO: { + packet = RtmpPacket::create(); + packet->chunk_id = CHUNK_VIDEO; + packet->stream_index = STREAM_MEDIA; + break; + } + + case MSG_DATA: + case MSG_DATA3: { + BufferLikeString buffer(string(data, len)); + AMFDecoder dec(buffer, _type == MSG_DATA3 ? 3 : 0); + auto first = dec.load(); + bool flag = true; + if (first.type() == AMFType::AMF_STRING) { + auto type = first.as_string(); + if (type == "@setDataFrame") { + type = dec.load(); + if (type == "onMetaData") { + flag = onRecvMetadata(dec.load()); + } else { + WarnL << "unknown type:" << type; + } + } else if (type == "onMetaData") { + flag = onRecvMetadata(dec.load()); + } else { + WarnL << "unknown notify:" << type; + } + } else { + WarnL << "Parse flv script data failed, invalid amf value: " << first.to_string(); + } + if (!flag) { + throw std::invalid_argument("check rtmp metadata failed"); + } + return; + } + + default: WarnL << "不识别的flv msg type:" << (int) _type; return; + } + + packet->time_stamp = _time_stamp; + packet->type_id = _type; + packet->body_size = len; + packet->buffer.assign(data, len); + onRecvRtmpPacket(std::move(packet)); +} + + +}//namespace mediakit \ No newline at end of file diff --git a/src/Rtmp/FlvSplitter.h b/src/Rtmp/FlvSplitter.h new file mode 100644 index 00000000..78f2d1d5 --- /dev/null +++ b/src/Rtmp/FlvSplitter.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLMEDIAKIT_FLVSPLITTER_H +#define ZLMEDIAKIT_FLVSPLITTER_H + +#include "Rtmp.h" +#include "Http/HttpRequestSplitter.h" +#include "RtmpPlayerImp.h" + +namespace mediakit { + +class FlvSplitter : public HttpRequestSplitter { +public: + FlvSplitter() = default; + ~FlvSplitter() = default; + +protected: + void onRecvContent(const char *data,size_t len) override; + ssize_t onRecvHeader(const char *data,size_t len) override; + const char *onSearchPacketTail(const char *data, size_t len) override; + +protected: + virtual void onRecvFlvHeader(const FLVHeader &header) {}; + virtual bool onRecvMetadata(const AMFValue &metadata) = 0; + virtual void onRecvRtmpPacket(RtmpPacket::Ptr packet) = 0; + +private: + bool _flv_started = false; + uint8_t _type; + uint32_t _time_stamp; +}; + +}//namespace mediakit +#endif //ZLMEDIAKIT_FLVSPLITTER_H diff --git a/src/Rtmp/Rtmp.cpp b/src/Rtmp/Rtmp.cpp index 6ef1698a..140afeed 100644 --- a/src/Rtmp/Rtmp.cpp +++ b/src/Rtmp/Rtmp.cpp @@ -10,10 +10,10 @@ #include "Rtmp.h" #include "Extension/Factory.h" -namespace mediakit{ -TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::map &header) -{ +namespace mediakit { + +TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::map &header) { _metadata.set("duration", dur_sec); _metadata.set("fileSize", (int)fileSize); _metadata.set("title", std::string("Streamed by ") + kServerName); @@ -22,14 +22,14 @@ TitleMeta::TitleMeta(float dur_sec, size_t fileSize, const std::mapgetVideoWidth() > 0 ){ +VideoMeta::VideoMeta(const VideoTrack::Ptr &video) { + if (video->getVideoWidth() > 0) { _metadata.set("width", video->getVideoWidth()); } - if(video->getVideoHeight() > 0 ){ + if (video->getVideoHeight() > 0) { _metadata.set("height", video->getVideoHeight()); } - if(video->getVideoFps() > 0 ){ + if (video->getVideoFps() > 0) { _metadata.set("framerate", video->getVideoFps()); } if (video->getBitRate()) { @@ -39,26 +39,26 @@ VideoMeta::VideoMeta(const VideoTrack::Ptr &video){ _metadata.set("videocodecid", Factory::getAmfByCodecId(_codecId)); } -AudioMeta::AudioMeta(const AudioTrack::Ptr &audio){ +AudioMeta::AudioMeta(const AudioTrack::Ptr &audio) { if (audio->getBitRate()) { _metadata.set("audiodatarate", audio->getBitRate() / 1024); } - if(audio->getAudioSampleRate() > 0){ + if (audio->getAudioSampleRate() > 0) { _metadata.set("audiosamplerate", audio->getAudioSampleRate()); } - if(audio->getAudioSampleBit() > 0){ + if (audio->getAudioSampleBit() > 0) { _metadata.set("audiosamplesize", audio->getAudioSampleBit()); } - if(audio->getAudioChannel() > 0){ + if (audio->getAudioChannel() > 0) { _metadata.set("stereo", audio->getAudioChannel() > 1); } _codecId = audio->getCodecId(); _metadata.set("audiocodecid", Factory::getAmfByCodecId(_codecId)); } -uint8_t getAudioRtmpFlags(const Track::Ptr &track){ - switch (track->getTrackType()){ - case TrackAudio : { +uint8_t getAudioRtmpFlags(const Track::Ptr &track) { + switch (track->getTrackType()) { + case TrackAudio: { auto audioTrack = std::dynamic_pointer_cast(track); if (!audioTrack) { WarnL << "获取AudioTrack失败"; @@ -68,21 +68,21 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){ auto iChannel = audioTrack->getAudioChannel(); auto iSampleBit = audioTrack->getAudioSampleBit(); - uint8_t flvAudioType ; - switch (track->getCodecId()){ - case CodecG711A : flvAudioType = FLV_CODEC_G711A; break; - case CodecG711U : flvAudioType = FLV_CODEC_G711U; break; - case CodecOpus : { - flvAudioType = FLV_CODEC_OPUS; - //opus不通过flags获取音频相关信息 + uint8_t flvAudioType; + switch (track->getCodecId()) { + case CodecG711A: flvAudioType = (uint8_t)RtmpAudioCodec::g711a; break; + case CodecG711U: flvAudioType = (uint8_t)RtmpAudioCodec::g711u; break; + case CodecOpus: { + flvAudioType = (uint8_t)RtmpAudioCodec::opus; + // opus不通过flags获取音频相关信息 iSampleRate = 44100; iSampleBit = 16; iChannel = 2; break; } - case CodecAAC : { - flvAudioType = FLV_CODEC_AAC; - //aac不通过flags获取音频相关信息 + case CodecAAC: { + flvAudioType = (uint8_t)RtmpAudioCodec::aac; + // aac不通过flags获取音频相关信息 iSampleRate = 44100; iSampleBit = 16; iChannel = 2; @@ -93,23 +93,15 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){ uint8_t flvSampleRate; switch (iSampleRate) { - case 44100: - flvSampleRate = 3; - break; - case 22050: - flvSampleRate = 2; - break; - case 11025: - flvSampleRate = 1; - break; + case 44100: flvSampleRate = 3; break; + case 22050: flvSampleRate = 2; break; + case 11025: flvSampleRate = 1; break; case 16000: // nellymoser only case 8000: // nellymoser only case 5512: // not MP3 flvSampleRate = 0; break; - default: - WarnL << "FLV does not support sample rate " << iSampleRate << " ,choose from (44100, 22050, 11025)"; - return 0; + default: WarnL << "FLV does not support sample rate " << iSampleRate << " ,choose from (44100, 22050, 11025)"; return 0; } uint8_t flvStereoOrMono = (iChannel > 1); @@ -117,32 +109,28 @@ uint8_t getAudioRtmpFlags(const Track::Ptr &track){ return (flvAudioType << 4) | (flvSampleRate << 2) | (flvSampleBit << 1) | flvStereoOrMono; } - default : return 0; + default: return 0; } } - void Metadata::addTrack(AMFValue &metadata, const Track::Ptr &track) { Metadata::Ptr new_metadata; switch (track->getTrackType()) { case TrackVideo: { new_metadata = std::make_shared(std::dynamic_pointer_cast(track)); - } break; + } case TrackAudio: { new_metadata = std::make_shared(std::dynamic_pointer_cast(track)); - } break; - default: - return; + } + default: return; } - new_metadata->getMetadata().object_for_each([&](const std::string &key, const AMFValue &value) { - metadata.set(key, value); - }); + new_metadata->getMetadata().object_for_each([&](const std::string &key, const AMFValue &value) { metadata.set(key, value); }); } -RtmpPacket::Ptr RtmpPacket::create(){ +RtmpPacket::Ptr RtmpPacket::create() { #if 0 static ResourcePool packet_pool; static onceToken token([]() { @@ -156,8 +144,7 @@ RtmpPacket::Ptr RtmpPacket::create(){ #endif } -void RtmpPacket::clear() -{ +void RtmpPacket::clear() { is_abs_stamp = false; time_stamp = 0; ts_field = 0; @@ -165,36 +152,56 @@ void RtmpPacket::clear() buffer.clear(); } -bool RtmpPacket::isVideoKeyFrame() const -{ - return type_id == MSG_VIDEO && (uint8_t)buffer[0] >> 4 == FLV_KEY_FRAME && (uint8_t)buffer[1] == 1; +bool RtmpPacket::isVideoKeyFrame() const { + if (type_id != MSG_VIDEO) { + return false; + } + RtmpFrameType frame_type; + if (buffer[0] >> 7) { + // IsExHeader == 1 + frame_type = (RtmpFrameType)((buffer[0] >> 4) & 0x07); + } else { + // IsExHeader == 0 + frame_type = (RtmpFrameType)(buffer[0] >> 4); + } + return frame_type == RtmpFrameType::key_frame; } -bool RtmpPacket::isCfgFrame() const -{ +bool RtmpPacket::isConfigFrame() const { switch (type_id) { - case MSG_VIDEO: return buffer[1] == 0; - case MSG_AUDIO: { - switch (getMediaType()) { - case FLV_CODEC_AAC: return buffer[1] == 0; - default: return false; + case MSG_AUDIO: { + return (RtmpAudioCodec)getRtmpCodecId() == RtmpAudioCodec::aac && (RtmpAACPacketType)buffer[1] == RtmpAACPacketType::aac_config_header; } - } - default: return false; + case MSG_VIDEO: { + if (!isVideoKeyFrame()) { + return false; + } + if (buffer[0] >> 7) { + // IsExHeader == 1 + return (RtmpPacketType)(buffer[0] & 0x0f) == RtmpPacketType::PacketTypeSequenceStart; + } + // IsExHeader == 0 + switch ((RtmpVideoCodec)getRtmpCodecId()) { + case RtmpVideoCodec::h265: + case RtmpVideoCodec::h264: { + return (RtmpH264PacketType)buffer[1] == RtmpH264PacketType::h264_config_header; + } + default: return false; + } + } + default: return false; } } -int RtmpPacket::getMediaType() const -{ +int RtmpPacket::getRtmpCodecId() const { switch (type_id) { - case MSG_VIDEO: return (uint8_t)buffer[0] & 0x0F; - case MSG_AUDIO: return (uint8_t)buffer[0] >> 4; - default: return 0; + case MSG_VIDEO: return (uint8_t)buffer[0] & 0x0F; + case MSG_AUDIO: return (uint8_t)buffer[0] >> 4; + default: return 0; } } -int RtmpPacket::getAudioSampleRate() const -{ +int RtmpPacket::getAudioSampleRate() const { if (type_id != MSG_AUDIO) { return 0; } @@ -203,8 +210,7 @@ int RtmpPacket::getAudioSampleRate() const return sampleRate[flvSampleRate]; } -int RtmpPacket::getAudioSampleBit() const -{ +int RtmpPacket::getAudioSampleBit() const { if (type_id != MSG_AUDIO) { return 0; } @@ -213,8 +219,7 @@ int RtmpPacket::getAudioSampleBit() const return sampleBit[flvSampleBit]; } -int RtmpPacket::getAudioChannel() const -{ +int RtmpPacket::getAudioChannel() const { if (type_id != MSG_AUDIO) { return 0; } @@ -223,8 +228,7 @@ int RtmpPacket::getAudioChannel() const return channel[flvStereoOrMono]; } -RtmpPacket & RtmpPacket::operator=(const RtmpPacket &that) -{ +RtmpPacket &RtmpPacket::operator=(const RtmpPacket &that) { is_abs_stamp = that.is_abs_stamp; stream_index = that.stream_index; body_size = that.body_size; @@ -234,32 +238,101 @@ RtmpPacket & RtmpPacket::operator=(const RtmpPacket &that) return *this; } -RtmpHandshake::RtmpHandshake(uint32_t _time, uint8_t *_random /*= nullptr*/) -{ +RtmpHandshake::RtmpHandshake(uint32_t _time, uint8_t *_random /*= nullptr*/) { _time = htonl(_time); memcpy(time_stamp, &_time, 4); if (!_random) { random_generate((char *)random, sizeof(random)); - } - else { + } else { memcpy(random, _random, sizeof(random)); } } -void RtmpHandshake::random_generate(char *bytes, int size) -{ - static char cdata[] = { 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72, - 0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x2d, 0x77, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x2d, 0x77, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x40, 0x31, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x6d }; +void RtmpHandshake::random_generate(char *bytes, int size) { + static char cdata[] = { 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72, 0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x6e, + 0x6c, 0x69, 0x6e, 0x2d, 0x77, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x40, 0x31, 0x32, 0x36, 0x2e, 0x63, + 0x6f, 0x6d }; for (int i = 0; i < size; i++) { bytes[i] = cdata[rand() % (sizeof(cdata) - 1)]; } } -}//namespace mediakit +#pragma pack(push, 1) + +struct RtmpVideoHeaderEnhanced { +#if __BYTE_ORDER == __BIG_ENDIAN + uint8_t enhanced : 1; + uint8_t frame_type : 3; + uint8_t pkt_type : 4; + uint32_t fourcc; +#else + uint8_t pkt_type : 4; + uint8_t frame_type : 3; + uint8_t enhanced : 1; + uint32_t fourcc; +#endif +}; + +struct RtmpVideoHeaderClassic { +#if __BYTE_ORDER == __BIG_ENDIAN + uint8_t frame_type : 4; + uint8_t codec_id : 4; + uint8_t h264_pkt_type; +#else + uint8_t codec_id : 4; + uint8_t frame_type : 4; + uint8_t h264_pkt_type; +#endif +}; + +#pragma pack(pop) + +CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info) { + RtmpPacketInfo save; + info = info ? info : &save; + info->codec = CodecInvalid; + + CHECK(size > 0); + RtmpVideoHeaderEnhanced *enhanced_header = (RtmpVideoHeaderEnhanced *)data; + if (enhanced_header->enhanced) { + // IsExHeader == 1 + CHECK(size >= 5, "Invalid rtmp buffer size: ", size); + info->is_enhanced = true; + info->video.frame_type = (RtmpFrameType)(enhanced_header->frame_type); + info->video.pkt_type = (RtmpPacketType)(enhanced_header->pkt_type); + + switch ((RtmpVideoCodec)ntohl(enhanced_header->fourcc)) { + case RtmpVideoCodec::fourcc_av1: info->codec = CodecAV1; break; + case RtmpVideoCodec::fourcc_vp9: info->codec = CodecVP9; break; + case RtmpVideoCodec::fourcc_hevc: info->codec = CodecH265; break; + default: WarnL << "Rtmp video codec not supported: " << std::string((char *)data + 1, 4); + } + } else { + // IsExHeader == 0 + RtmpVideoHeaderClassic *classic_header = (RtmpVideoHeaderClassic *)data; + info->is_enhanced = false; + info->video.frame_type = (RtmpFrameType)(classic_header->frame_type); + switch ((RtmpVideoCodec)(classic_header->codec_id)) { + case RtmpVideoCodec::h264: { + CHECK(size >= 1, "Invalid rtmp buffer size: ", size); + info->codec = CodecH264; + info->video.h264_pkt_type = (RtmpH264PacketType)classic_header->h264_pkt_type; + break; + } + case RtmpVideoCodec::h265: { + CHECK(size >= 1, "Invalid rtmp buffer size: ", size); + info->codec = CodecH265; + info->video.h264_pkt_type = (RtmpH264PacketType)classic_header->h264_pkt_type; + break; + } + default: WarnL << "Rtmp video codec not supported: " << (int)classic_header->codec_id; break; + } + } + return info->codec; +} + +} // namespace mediakit namespace toolkit { - StatisticImp(mediakit::RtmpPacket); +StatisticImp(mediakit::RtmpPacket); } \ No newline at end of file diff --git a/src/Rtmp/Rtmp.h b/src/Rtmp/Rtmp.h index 55802549..66908af2 100644 --- a/src/Rtmp/Rtmp.h +++ b/src/Rtmp/Rtmp.h @@ -18,13 +18,6 @@ #include "Network/Buffer.h" #include "Extension/Track.h" -#if !defined(_WIN32) -#define PACKED __attribute__((packed)) -#else -#define PACKED -#endif //!defined(_WIN32) - - #define DEFAULT_CHUNK_LEN 128 #define HANDSHAKE_PLAINTEXT 0x03 #define RANDOM_LEN (1536 - 8) @@ -63,23 +56,9 @@ #define CHUNK_AUDIO 6 /*音频chunkID*/ #define CHUNK_VIDEO 7 /*视频chunkID*/ -#define FLV_KEY_FRAME 1 -#define FLV_INTER_FRAME 2 - -#define FLV_CODEC_AAC 10 -#define FLV_CODEC_H264 7 -//金山扩展: https://github.com/ksvc/FFmpeg/wiki -#define FLV_CODEC_H265 12 -#define FLV_CODEC_G711A 7 -#define FLV_CODEC_G711U 8 -//参考学而思网校: https://github.com/notedit/rtmp/commit/6e314ac5b29611431f8fb5468596b05815743c10 -#define FLV_CODEC_OPUS 13 - namespace mediakit { -#if defined(_WIN32) #pragma pack(push, 1) -#endif // defined(_WIN32) class RtmpHandshake { public: @@ -93,7 +72,7 @@ public: void create_complex_c0c1(); -}PACKED; +}; class RtmpHeader { public: @@ -109,10 +88,12 @@ public: uint8_t body_size[3]; uint8_t type_id; uint8_t stream_index[4]; /* Note, this is little-endian while others are BE */ -}PACKED; +}; class FLVHeader { public: + static constexpr uint8_t kFlvVersion = 1; + static constexpr uint8_t kFlvHeaderLength = 9; //FLV char flv[3]; //File version (for example, 0x01 for FLV version 1) @@ -138,7 +119,9 @@ public: #endif //The length of this header in bytes,固定为9 uint32_t length; -} PACKED; + //固定为0 + uint32_t previous_tag_size0; +}; class RtmpTagHeader { public: @@ -147,11 +130,9 @@ public: uint8_t timestamp[3] = {0}; uint8_t timestamp_ex = 0; uint8_t streamid[3] = {0}; /* Always 0. */ -} PACKED; +}; -#if defined(_WIN32) #pragma pack(pop) -#endif // defined(_WIN32) class RtmpPacket : public toolkit::Buffer{ public: @@ -178,11 +159,15 @@ public: void clear(); + // video config frame和key frame都返回true + // 用于gop缓存定位 bool isVideoKeyFrame() const; - bool isCfgFrame() const; - int getMediaType() const; + // aac config或h264/h265 config返回true,支持增强型rtmp + // 用于缓存解码配置信息 + bool isConfigFrame() const; + int getRtmpCodecId() const; int getAudioSampleRate() const; int getAudioSampleBit() const; int getAudioChannel() const; @@ -265,5 +250,121 @@ private: //根据音频track获取flags uint8_t getAudioRtmpFlags(const Track::Ptr &track); +////////////////// rtmp video ////////////////////////// +//https://rtmp.veriskope.com/pdf/video_file_format_spec_v10_1.pdf + +// UB [4]; Type of video frame. +enum class RtmpFrameType : uint8_t { + reserved = 0, + key_frame = 1, // key frame (for AVC, a seekable frame) + inter_frame = 2, // inter frame (for AVC, a non-seekable frame) + disposable_inter_frame = 3, // disposable inter frame (H.263 only) + generated_key_frame = 4, // generated key frame (reserved for server use only) + video_info_frame = 5, // video info/command frame +}; + +#define MKBETAG(a, b, c, d) ((d) | ((c) << 8) | ((b) << 16) | ((unsigned)(a) << 24)) + +// UB [4]; Codec Identifier. +enum class RtmpVideoCodec : uint32_t { + h263 = 2, // Sorenson H.263 + screen_video = 3, // Screen video + vp6 = 4, // On2 VP6 + vp6_alpha = 5, // On2 VP6 with alpha channel + screen_video2 = 6, // Screen video version 2 + h264 = 7, // avc + h265 = 12, // 国内扩展 + + // 增强型rtmp FourCC + fourcc_vp9 = MKBETAG('v', 'p', '0', '9'), + fourcc_av1 = MKBETAG('a', 'v', '0', '1'), + fourcc_hevc = MKBETAG('h', 'v', 'c', '1') +}; + +// UI8; +enum class RtmpH264PacketType : uint8_t { + h264_config_header = 0, // AVC or HEVC sequence header(sps/pps) + h264_nalu = 1, // AVC or HEVC NALU + h264_end_seq = 2, // AVC or HEVC end of sequence (lower level NALU sequence ender is not REQUIRED or supported) +}; + +// https://github.com/veovera/enhanced-rtmp/blob/main/enhanced-rtmp.pdf +// UB[4] +enum class RtmpPacketType : uint8_t { + PacketTypeSequenceStart = 0, + PacketTypeCodedFrames = 1, + PacketTypeSequenceEnd = 2, + + // CompositionTime Offset is implied to equal zero. This is + // an optimization to save putting SI24 composition time value of zero on + // the wire. See pseudo code below in the VideoTagBody section + PacketTypeCodedFramesX = 3, + + // VideoTagBody does not contain video data. VideoTagBody + // instead contains an AMF encoded metadata. See Metadata Frame + // section for an illustration of its usage. As an example, the metadata + // can be HDR information. This is a good way to signal HDR + // information. This also opens up future ways to express additional + // metadata that is meant for the next video sequence. + // + // note: presence of PacketTypeMetadata means that FrameType + // flags at the top of this table should be ignored + PacketTypeMetadata = 4, + + // Carriage of bitstream in MPEG-2 TS format + // note: PacketTypeSequenceStart and PacketTypeMPEG2TSSequenceStart + // are mutually exclusive + PacketTypeMPEG2TSSequenceStart = 5, +}; + +////////////////// rtmp audio ////////////////////////// +//https://rtmp.veriskope.com/pdf/video_file_format_spec_v10_1.pdf + +// UB [4]; Format of SoundData +enum class RtmpAudioCodec : uint8_t { + /** + 0 = Linear PCM, platform endian + 1 = ADPCM + 2 = MP3 + 3 = Linear PCM, little endian + 4 = Nellymoser 16 kHz mono + 5 = Nellymoser 8 kHz mono + 6 = Nellymoser + 7 = G.711 A-law logarithmic PCM + 8 = G.711 mu-law logarithmic PCM + 9 = reserved + 10 = AAC + 11 = Speex + 14 = MP3 8 kHz + 15 = Device-specific sound + */ + g711a = 7, + g711u = 8, + aac = 10, + opus = 13 // 国内扩展 +}; + +// UI8; +enum class RtmpAACPacketType : uint8_t { + aac_config_header = 0, // AAC sequence header + aac_raw = 1, // AAC raw +}; + +//////////////////////////////////////////// + +struct RtmpPacketInfo { + CodecId codec = CodecInvalid; + bool is_enhanced; + union { + struct { + RtmpFrameType frame_type; + RtmpPacketType pkt_type; // enhanced = true + RtmpH264PacketType h264_pkt_type; // enhanced = false + } video; + }; +}; +// https://github.com/veovera/enhanced-rtmp +CodecId parseVideoRtmpPacket(const uint8_t *data, size_t size, RtmpPacketInfo *info = nullptr); + }//namespace mediakit #endif//__rtmp_h diff --git a/src/Rtmp/RtmpDemuxer.cpp b/src/Rtmp/RtmpDemuxer.cpp index b91dd503..20d8f052 100644 --- a/src/Rtmp/RtmpDemuxer.cpp +++ b/src/Rtmp/RtmpDemuxer.cpp @@ -19,12 +19,12 @@ size_t RtmpDemuxer::trackCount(const AMFValue &metadata) { size_t ret = 0; metadata.object_for_each([&](const string &key, const AMFValue &val) { if (key == "videocodecid") { - //找到视频 + // 找到视频 ++ret; return; } if (key == "audiocodecid") { - //找到音频 + // 找到音频 ++ret; return; } @@ -32,7 +32,7 @@ size_t RtmpDemuxer::trackCount(const AMFValue &metadata) { return ret; } -bool RtmpDemuxer::loadMetaData(const AMFValue &val){ +bool RtmpDemuxer::loadMetaData(const AMFValue &val) { bool ret = false; try { int audiosamplerate = 0; @@ -60,12 +60,12 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){ return; } if (key == "videocodecid") { - //找到视频 + // 找到视频 videocodecid = &val; return; } if (key == "audiocodecid") { - //找到音频 + // 找到音频 audiocodecid = &val; return; } @@ -79,12 +79,12 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){ } }); if (videocodecid) { - //有视频 + // 有视频 ret = true; makeVideoTrack(*videocodecid, videodatarate * 1024); } if (audiocodecid) { - //有音频 + // 有音频 ret = true; makeAudioTrack(*audiocodecid, audiosamplerate, audiochannels, audiosamplesize, audiodatarate * 1024); } @@ -93,7 +93,7 @@ bool RtmpDemuxer::loadMetaData(const AMFValue &val){ } if (ret) { - //metadata中存在track相关的描述,那么我们根据metadata判断有多少个track + // metadata中存在track相关的描述,那么我们根据metadata判断有多少个track addTrackCompleted(); } return ret; @@ -108,8 +108,8 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) { case MSG_VIDEO: { if (!_try_get_video_track) { _try_get_video_track = true; - auto codec = AMFValue(pkt->getMediaType()); - makeVideoTrack(codec, 0); + auto codec_id = parseVideoRtmpPacket((uint8_t *)pkt->data(), pkt->size()); + makeVideoTrack(Factory::getTrackByCodecId(codec_id), 0); } if (_video_rtmp_decoder) { _video_rtmp_decoder->inputRtmp(pkt); @@ -120,7 +120,7 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) { case MSG_AUDIO: { if (!_try_get_audio_track) { _try_get_audio_track = true; - auto codec = AMFValue(pkt->getMediaType()); + auto codec = AMFValue(pkt->getRtmpCodecId()); makeAudioTrack(codec, pkt->getAudioSampleRate(), pkt->getAudioChannel(), pkt->getAudioSampleBit(), 0); } if (_audio_rtmp_decoder) { @@ -128,51 +128,55 @@ void RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) { } break; } - default : break; + default: break; } } void RtmpDemuxer::makeVideoTrack(const AMFValue &videoCodec, int bit_rate) { + makeVideoTrack(Factory::getVideoTrackByAmf(videoCodec), bit_rate); +} + +void RtmpDemuxer::makeVideoTrack(const Track::Ptr &track, int bit_rate) { if (_video_rtmp_decoder) { return; } - //生成Track对象 - _video_track = dynamic_pointer_cast(Factory::getVideoTrackByAmf(videoCodec)); + // 生成Track对象 + _video_track = dynamic_pointer_cast(track); if (!_video_track) { return; } - //生成rtmpCodec对象以便解码rtmp + // 生成rtmpCodec对象以便解码rtmp _video_rtmp_decoder = Factory::getRtmpCodecByTrack(_video_track, false); if (!_video_rtmp_decoder) { - //找不到相应的rtmp解码器,该track无效 + // 找不到相应的rtmp解码器,该track无效 _video_track.reset(); return; } _video_track->setBitRate(bit_rate); - //设置rtmp解码器代理,生成的frame写入该Track + // 设置rtmp解码器代理,生成的frame写入该Track _video_rtmp_decoder->addDelegate(_video_track); addTrack(_video_track); _try_get_video_track = true; } -void RtmpDemuxer::makeAudioTrack(const AMFValue &audioCodec,int sample_rate, int channels, int sample_bit, int bit_rate) { +void RtmpDemuxer::makeAudioTrack(const AMFValue &audioCodec, int sample_rate, int channels, int sample_bit, int bit_rate) { if (_audio_rtmp_decoder) { return; } - //生成Track对象 + // 生成Track对象 _audio_track = dynamic_pointer_cast(Factory::getAudioTrackByAmf(audioCodec, sample_rate, channels, sample_bit)); if (!_audio_track) { return; } - //生成rtmpCodec对象以便解码rtmp + // 生成rtmpCodec对象以便解码rtmp _audio_rtmp_decoder = Factory::getRtmpCodecByTrack(_audio_track, false); if (!_audio_rtmp_decoder) { - //找不到相应的rtmp解码器,该track无效 + // 找不到相应的rtmp解码器,该track无效 _audio_track.reset(); return; } _audio_track->setBitRate(bit_rate); - //设置rtmp解码器代理,生成的frame写入该Track + // 设置rtmp解码器代理,生成的frame写入该Track _audio_rtmp_decoder->addDelegate(_audio_track); addTrack(_audio_track); _try_get_audio_track = true; diff --git a/src/Rtmp/RtmpDemuxer.h b/src/Rtmp/RtmpDemuxer.h index 31b0ed2d..4cf0412b 100644 --- a/src/Rtmp/RtmpDemuxer.h +++ b/src/Rtmp/RtmpDemuxer.h @@ -45,6 +45,7 @@ public: private: void makeVideoTrack(const AMFValue &val, int bit_rate); + void makeVideoTrack(const Track::Ptr &val, int bit_rate); void makeAudioTrack(const AMFValue &val, int sample_rate, int channels, int sample_bit, int bit_rate); private: diff --git a/src/Rtmp/RtmpMediaSource.h b/src/Rtmp/RtmpMediaSource.h index d91393b0..732cbb85 100644 --- a/src/Rtmp/RtmpMediaSource.h +++ b/src/Rtmp/RtmpMediaSource.h @@ -46,12 +46,7 @@ public: * @param stream_id 流id * @param ring_size 可以设置固定的环形缓冲大小,0则自适应 */ - RtmpMediaSource(const std::string &vhost, - const std::string &app, - const std::string &stream_id, - int ring_size = RTMP_GOP_SIZE) : - MediaSource(RTMP_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) { - } + RtmpMediaSource(const MediaTuple& tuple, int ring_size = RTMP_GOP_SIZE): MediaSource(RTMP_SCHEMA, tuple), _ring_size(ring_size) {} ~RtmpMediaSource() override { flush(); } @@ -62,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); } @@ -78,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 8e49e534..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::dynamic_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 std::string &vhost, const std::string &app, const std::string &id, int ringSize) : RtmpMediaSource(vhost, app, id, 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(getVhost(), getApp(), getId(), _demuxer->getDuration(), _option); + _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/RtmpMediaSourceImp.h b/src/Rtmp/RtmpMediaSourceImp.h index c6382c8d..31fa24fe 100644 --- a/src/Rtmp/RtmpMediaSourceImp.h +++ b/src/Rtmp/RtmpMediaSourceImp.h @@ -35,7 +35,7 @@ public: * @param id 流id * @param ringSize 环形缓存大小 */ - RtmpMediaSourceImp(const std::string &vhost, const std::string &app, const std::string &id, int ringSize = RTMP_GOP_SIZE); + RtmpMediaSourceImp(const MediaTuple& tuple, int ringSize = RTMP_GOP_SIZE); ~RtmpMediaSourceImp() override = default; diff --git a/src/Rtmp/RtmpMediaSourceMuxer.h b/src/Rtmp/RtmpMediaSourceMuxer.h index 70f3fffe..3f6b8bd4 100644 --- a/src/Rtmp/RtmpMediaSourceMuxer.h +++ b/src/Rtmp/RtmpMediaSourceMuxer.h @@ -21,13 +21,11 @@ class RtmpMediaSourceMuxer final : public RtmpMuxer, public MediaSourceEventInte public: using Ptr = std::shared_ptr; - RtmpMediaSourceMuxer(const std::string &vhost, - const std::string &strApp, - const std::string &strId, + RtmpMediaSourceMuxer(const MediaTuple& tuple, const ProtocolOption &option, const TitleMeta::Ptr &title = nullptr) : RtmpMuxer(title) { _option = option; - _media_src = std::make_shared(vhost, strApp, strId); + _media_src = std::make_shared(tuple); getRtmpRing()->setDelegate(_media_src); } @@ -46,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 780ce980..42914cda 100644 --- a/src/Rtmp/RtmpPlayer.cpp +++ b/src/Rtmp/RtmpPlayer.cpp @@ -27,7 +27,7 @@ namespace mediakit { RtmpPlayer::RtmpPlayer(const EventPoller::Ptr &poller) : TcpClient(poller) {} RtmpPlayer::~RtmpPlayer() { - DebugL << endl; + DebugL; } void RtmpPlayer::teardown() { @@ -52,14 +52,14 @@ void RtmpPlayer::teardown() { void RtmpPlayer::play(const string &url) { teardown(); - string host_url = FindField(url.data(), "://", "/"); + string host_url = findSubString(url.data(), "://", "/"); { auto pos = url.find_last_of('/'); if (pos != string::npos) { _stream_id = url.substr(pos + 1); } } - _app = FindField(url.data(), (host_url + "/").data(), ("/" + _stream_id).data()); + _app = findSubString(url.data(), (host_url + "/").data(), ("/" + _stream_id).data()); _tc_url = string("rtmp://") + host_url + "/" + _app; if (!_app.size() || !_stream_id.size()) { @@ -75,7 +75,7 @@ void RtmpPlayer::play(const string &url) { setNetAdapter((*this)[Client::kNetAdapter]); } - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); float play_timeout_sec = (*this)[Client::kTimeoutMS].as() / 1000.0f; _play_timer.reset(new Timer(play_timeout_sec, [weak_self]() { auto strong_self = weak_self.lock(); @@ -90,7 +90,7 @@ void RtmpPlayer::play(const string &url) { startConnect(host_url, port, play_timeout_sec); } -void RtmpPlayer::onErr(const SockException &ex){ +void RtmpPlayer::onError(const SockException &ex){ //定时器_pPlayTimer为空后表明握手结束了 onPlayResult_l(ex, !_play_timer); } @@ -120,7 +120,7 @@ void RtmpPlayer::onPlayResult_l(const SockException &ex, bool handshake_done) { //播放成功,恢复rtmp接收超时定时器 _rtmp_recv_ticker.resetTime(); auto timeout_ms = (*this)[Client::kMediaTimeoutMS].as(); - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); auto lam = [weak_self, timeout_ms]() { auto strong_self = weak_self.lock(); if (!strong_self) { @@ -146,7 +146,7 @@ void RtmpPlayer::onConnect(const SockException &err) { onPlayResult_l(err, false); return; } - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); startClientSession([weak_self]() { if (auto strong_self = weak_self.lock()) { strong_self->send_connect(); @@ -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"; @@ -258,7 +265,7 @@ void RtmpPlayer::send_pause(bool pause) { _beat_timer.reset(); if (pause) { - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); _beat_timer.reset(new Timer((*this)[Client::kBeatIntervalMS].as() / 1000.0f, [weak_self]() { auto strong_self = weak_self.lock(); if (!strong_self) { @@ -313,8 +320,8 @@ void RtmpPlayer::onCmd_onStatus(AMFDecoder &dec) { void RtmpPlayer::onCmd_onMetaData(AMFDecoder &dec) { //TraceL; auto val = dec.load(); - if (!onCheckMeta(val)) { - throw std::runtime_error("onCheckMeta failed"); + if (!onMetadata(val)) { + throw std::runtime_error("onMetadata failed"); } _metadata_got = true; } @@ -328,18 +335,18 @@ void RtmpPlayer::onMediaData_l(RtmpPacket::Ptr chunk_data) { _rtmp_recv_ticker.resetTime(); if (!_play_timer) { //已经触发了onPlayResult事件,直接触发onMediaData事件 - onMediaData(chunk_data); + onRtmpPacket(chunk_data); return; } - if (chunk_data->isCfgFrame()) { + if (chunk_data->isConfigFrame()) { //输入配置帧以便初始化完成各个track - onMediaData(chunk_data); + onRtmpPacket(chunk_data); } else { //先触发onPlayResult事件,这个时候解码器才能初始化完毕 onPlayResult_l(SockException(Err_success, "play rtmp success"), false); //触发onPlayResult事件后,再把帧数据输入到解码器 - onMediaData(chunk_data); + onRtmpPacket(chunk_data); } } @@ -379,8 +386,8 @@ void RtmpPlayer::onRtmpChunk(RtmpPacket::Ptr packet) { _now_stamp[idx] = chunk_data.time_stamp; } if (!_metadata_got) { - if (!onCheckMeta(TitleMeta().getMetadata())) { - throw std::runtime_error("onCheckMeta failed"); + if (!onMetadata(TitleMeta().getMetadata())) { + throw std::runtime_error("onMetadata failed"); } _metadata_got = true; } @@ -420,49 +427,4 @@ void RtmpPlayer::seekToMilliSecond(uint32_t seekMS){ }); } -//////////////////////////////////////////// -float RtmpPlayerImp::getDuration() const -{ - return _demuxer ? _demuxer->getDuration() : 0; -} - -std::vector RtmpPlayerImp::getTracks(bool ready /*= true*/) const -{ - return _demuxer ? _demuxer->getTracks(ready) : Super::getTracks(ready); -} - -bool RtmpPlayerImp::onCheckMeta(const AMFValue &val) -{ - //无metadata或metadata中无track信息时,需要从数据包中获取track - _wait_track_ready = (*this)[Client::kWaitTrackReady].as() || RtmpDemuxer::trackCount(val) == 0; - onCheckMeta_l(val); - return true; -} - -void RtmpPlayerImp::onMediaData(RtmpPacket::Ptr chunkData) -{ - if (!_demuxer) { - //有些rtmp流没metadata - onCheckMeta_l(TitleMeta().getMetadata()); - } - _demuxer->inputRtmp(chunkData); - if (_rtmp_src) { - _rtmp_src->onWrite(std::move(chunkData)); - } -} - -void RtmpPlayerImp::onCheckMeta_l(const AMFValue &val) -{ - _rtmp_src = std::dynamic_pointer_cast(_media_src); - if (_rtmp_src) { - _rtmp_src->setMetaData(val); - } - if (_demuxer) { - return; - } - _demuxer = std::make_shared(); - //TraceL<<" _wait_track_ready "<<_wait_track_ready; - _demuxer->setTrackListener(this, _wait_track_ready); - _demuxer->loadMetaData(val); -} } /* namespace mediakit */ diff --git a/src/Rtmp/RtmpPlayer.h b/src/Rtmp/RtmpPlayer.h index 55e4c9c4..75abf7f9 100644 --- a/src/Rtmp/RtmpPlayer.h +++ b/src/Rtmp/RtmpPlayer.h @@ -37,8 +37,8 @@ public: void teardown() override; protected: - virtual bool onCheckMeta(const AMFValue &val) = 0; - virtual void onMediaData(RtmpPacket::Ptr chunk_data) = 0; + virtual bool onMetadata(const AMFValue &val) = 0; + virtual void onRtmpPacket(RtmpPacket::Ptr chunk_data) = 0; uint32_t getProgressMilliSecond() const; void seekToMilliSecond(uint32_t ms); @@ -50,7 +50,7 @@ protected: //form Tcpclient void onRecv(const toolkit::Buffer::Ptr &buf) override; void onConnect(const toolkit::SockException &err) override; - void onErr(const toolkit::SockException &ex) override; + void onError(const toolkit::SockException &ex) override; //from RtmpProtocol void onRtmpChunk(RtmpPacket::Ptr chunk_data) override; void onStreamDry(uint32_t stream_index) override; diff --git a/src/Rtmp/RtmpPlayerImp.h b/src/Rtmp/RtmpPlayerImp.h index c09d460b..ecf9a34f 100644 --- a/src/Rtmp/RtmpPlayerImp.h +++ b/src/Rtmp/RtmpPlayerImp.h @@ -13,21 +13,100 @@ #include #include +#include "Common/config.h" #include "RtmpPlayer.h" -#include "RtmpDemuxer.h" #include "RtmpMediaSource.h" +#include "RtmpDemuxer.h" +#include "Poller/Timer.h" +#include "Util/TimeTicker.h" namespace mediakit { -class RtmpPlayerImp: public PlayerImp, private TrackListener { +template +class FlvPlayerBase: public PlayerImp, private TrackListener { +public: + using Ptr = std::shared_ptr; + using Super = PlayerImp; + + FlvPlayerBase(const toolkit::EventPoller::Ptr &poller) : Super(poller) {}; + + ~FlvPlayerBase() override { + DebugL << std::endl; + } + + float getDuration() const override { + return _demuxer ? _demuxer->getDuration() : 0; + } + + std::vector getTracks(bool ready = true) const override { + return _demuxer ? _demuxer->getTracks(ready) : Super::getTracks(ready); + } + +private: + //派生类回调函数 + bool onMetadata(const AMFValue &val) override { + //无metadata或metadata中无track信息时,需要从数据包中获取track + _wait_track_ready = this->Super::operator[](Client::kWaitTrackReady).template as() || RtmpDemuxer::trackCount(val) == 0; + onCheckMeta_l(val); + return true; + } + + void onRtmpPacket(RtmpPacket::Ptr chunkData) override { + if (!_demuxer) { + //有些rtmp流没metadata + onCheckMeta_l(TitleMeta().getMetadata()); + } + _demuxer->inputRtmp(chunkData); + if (_rtmp_src) { + _rtmp_src->onWrite(std::move(chunkData)); + } + } + + void onPlayResult(const toolkit::SockException &ex) override { + if (!_wait_track_ready || ex) { + Super::onPlayResult(ex); + return; + } + } + + bool addTrack(const Track::Ptr &track) override { return true; } + + void addTrackCompleted() override { + if (_wait_track_ready) { + Super::onPlayResult(toolkit::SockException(toolkit::Err_success, "play success")); + } + } + +private: + void onCheckMeta_l(const AMFValue &val) { + _rtmp_src = std::dynamic_pointer_cast(this->Super::_media_src); + if (_rtmp_src) { + _rtmp_src->setMetaData(val); + } + if(_demuxer){ + return; + } + _demuxer = std::make_shared(); + //TraceL<<" _wait_track_ready "<<_wait_track_ready; + _demuxer->setTrackListener(this, _wait_track_ready); + _demuxer->loadMetaData(val); + } + +private: + bool _wait_track_ready = true; + RtmpDemuxer::Ptr _demuxer; + RtmpMediaSource::Ptr _rtmp_src; +}; + +class RtmpPlayerImp: public FlvPlayerBase { public: using Ptr = std::shared_ptr; - using Super = PlayerImp; + using Super = FlvPlayerBase; RtmpPlayerImp(const toolkit::EventPoller::Ptr &poller) : Super(poller) {}; ~RtmpPlayerImp() override { - DebugL << std::endl; + DebugL; } float getProgress() const override { @@ -46,39 +125,6 @@ public: uint32_t pos = MAX(float(0), MIN(seekPos, getDuration())) * 1000; seekToMilliSecond(pos); } - - float getDuration() const override; - - std::vector getTracks(bool ready = true) const override; - -private: - //派生类回调函数 - bool onCheckMeta(const AMFValue &val) override; - - void onMediaData(RtmpPacket::Ptr chunkData) override; - - void onPlayResult(const toolkit::SockException &ex) override { - if (!_wait_track_ready || ex) { - Super::onPlayResult(ex); - return; - } - } - - bool addTrack(const Track::Ptr &track) override { return true; } - - void addTrackCompleted() override { - if (_wait_track_ready) { - Super::onPlayResult(toolkit::SockException(toolkit::Err_success, "play success")); - } - } - -private: - void onCheckMeta_l(const AMFValue &val); - -private: - bool _wait_track_ready = true; - RtmpDemuxer::Ptr _demuxer; - RtmpMediaSource::Ptr _rtmp_src; }; diff --git a/src/Rtmp/RtmpPusher.cpp b/src/Rtmp/RtmpPusher.cpp index 109696c5..f401cf34 100644 --- a/src/Rtmp/RtmpPusher.cpp +++ b/src/Rtmp/RtmpPusher.cpp @@ -27,7 +27,7 @@ RtmpPusher::RtmpPusher(const EventPoller::Ptr &poller, const RtmpMediaSource::Pt RtmpPusher::~RtmpPusher() { teardown(); - DebugL << endl; + DebugL; } void RtmpPusher::teardown() { @@ -65,9 +65,9 @@ void RtmpPusher::onPublishResult_l(const SockException &ex, bool handshake_done) void RtmpPusher::publish(const string &url) { teardown(); - string host_url = FindField(url.data(), "://", "/"); - _app = FindField(url.data(), (host_url + "/").data(), "/"); - _stream_id = FindField(url.data(), (host_url + "/" + _app + "/").data(), NULL); + string host_url = findSubString(url.data(), "://", "/"); + _app = findSubString(url.data(), (host_url + "/").data(), "/"); + _stream_id = findSubString(url.data(), (host_url + "/" + _app + "/").data(), NULL); _tc_url = string("rtmp://") + host_url + "/" + _app; if (!_app.size() || !_stream_id.size()) { @@ -79,7 +79,7 @@ void RtmpPusher::publish(const string &url) { uint16_t port = 1935; splitUrl(host_url, host_url, port); - weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + weak_ptr weakSelf = static_pointer_cast(shared_from_this()); float publishTimeOutSec = (*this)[Client::kTimeoutMS].as() / 1000.0f; _publish_timer.reset(new Timer(publishTimeOutSec, [weakSelf]() { auto strongSelf = weakSelf.lock(); @@ -97,7 +97,7 @@ void RtmpPusher::publish(const string &url) { startConnect(host_url, port); } -void RtmpPusher::onErr(const SockException &ex){ +void RtmpPusher::onError(const SockException &ex){ //定时器_pPublishTimer为空后表明握手结束了 onPublishResult_l(ex, !_publish_timer); } @@ -107,7 +107,7 @@ void RtmpPusher::onConnect(const SockException &err){ onPublishResult_l(err, false); return; } - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); startClientSession([weak_self]() { auto strong_self = weak_self.lock(); if (!strong_self) { @@ -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,17 +190,21 @@ 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); }); src->pause(false); _rtmp_reader = src->getRing()->attach(getPoller()); - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); _rtmp_reader->setReadCB([weak_self](const RtmpMediaSource::RingDataType &pkt) { auto strong_self = weak_self.lock(); if (!strong_self) { @@ -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/RtmpPusher.h b/src/Rtmp/RtmpPusher.h index bd41fd75..0b9cdb89 100644 --- a/src/Rtmp/RtmpPusher.h +++ b/src/Rtmp/RtmpPusher.h @@ -31,7 +31,7 @@ protected: //for Tcpclient override void onRecv(const toolkit::Buffer::Ptr &buf) override; void onConnect(const toolkit::SockException &err) override; - void onErr(const toolkit::SockException &ex) override; + void onError(const toolkit::SockException &ex) override; //for RtmpProtocol override void onRtmpChunk(RtmpPacket::Ptr chunk_data) override; diff --git a/src/Rtmp/RtmpSession.cpp b/src/Rtmp/RtmpSession.cpp index 7dfbb532..fd09141f 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); } //如果是主动关闭的,那么不延迟注销 @@ -82,27 +82,23 @@ void RtmpSession::onCmd_connect(AMFDecoder &dec) { ///////////set peerBandwidth//////////////// sendPeerBandwidth(5000000); - _media_info._app = params["app"].as_string(); - _tc_url = params["tcUrl"].as_string(); - if(_tc_url.empty()){ - //defaultVhost:默认vhost - _tc_url = string(RTMP_SCHEMA) + "://" + DEFAULT_VHOST + "/" + _media_info._app; + auto tc_url = params["tcUrl"].as_string(); + if (tc_url.empty()) { + // defaultVhost:默认vhost + tc_url = string(RTMP_SCHEMA) + "://" + DEFAULT_VHOST + "/" + _media_info.app; } else { - auto pos = _tc_url.rfind('?'); + auto pos = tc_url.rfind('?'); if (pos != string::npos) { - //tc_url 中可能包含?以及参数,参见issue: #692 - _tc_url = _tc_url.substr(0, pos); - } - auto stream_start = _tc_url.rfind('/'); - if (stream_start != string::npos && stream_start > 1) { - auto protocol_end = _tc_url.find("://") + 2; - auto app_start = _tc_url.rfind('/', stream_start - 1); - if (app_start != protocol_end) { - // contain stream name part - _tc_url = _tc_url.substr(0, stream_start); - } + // tc_url 中可能包含?以及参数,参见issue: #692 + tc_url = tc_url.substr(0, pos); } } + // 初步解析,只用于获取vhost信息 + _media_info.parse(tc_url); + _media_info.schema = RTMP_SCHEMA; + // 赋值rtmp app + _media_info.app = params["app"].as_string(); + bool ok = true; //(app == APP_NAME); AMFValue version(AMF_OBJECT); version.set("fmsVer", "FMS/3,0,1,123"); @@ -114,7 +110,7 @@ void RtmpSession::onCmd_connect(AMFDecoder &dec) { status.set("objectEncoding", params["objectEncoding"]); sendReply(ok ? "_result" : "_error", version, status); if (!ok) { - throw std::runtime_error("Unsupported application: " + _media_info._app); + throw std::runtime_error("Unsupported application: " + _media_info.app); } AMFEncoder invoke; @@ -128,7 +124,7 @@ void RtmpSession::onCmd_createStream(AMFDecoder &dec) { void RtmpSession::onCmd_publish(AMFDecoder &dec) { std::shared_ptr ticker(new Ticker); - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); std::shared_ptr token(new onceToken(nullptr, [ticker, weak_self]() { auto strong_self = weak_self.lock(); if (strong_self) { @@ -136,13 +132,15 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) { } })); dec.load();/* NULL */ - _media_info.parse(_tc_url + "/" + getStreamId(dec.load())); - _media_info._schema = RTMP_SCHEMA; + // 赋值为rtmp stream id 信息 + _media_info.stream = getStreamId(dec.load()); + // 再解析url,切割url为app/stream_id (不一定符合rtmp url切割规范) + _media_info.parse(_media_info.getUrl()); auto now_stream_index = _now_stream_index; auto on_res_push = [this](ProtocolOption option){ if (!_push_src) { - _push_src = std::make_shared(_media_info._vhost, _media_info._app, _media_info._streamid); + _push_src = std::make_shared(_media_info); //获取所有权 _push_src_ownership = _push_src->getOwnership(); _push_src->setProtocolOption(option); @@ -169,7 +167,7 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) { } assert(!_push_src); - auto src = MediaSource::find(RTMP_SCHEMA, _media_info._vhost, _media_info._app, _media_info._streamid); + auto src = MediaSource::find(RTMP_SCHEMA, _media_info.vhost, _media_info.app, _media_info.stream); auto push_failed = (bool)src; RtmpMediaSourceImp::Ptr rtmp_src; while (src) { @@ -223,7 +221,7 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) { on_res_push(option); }; - if(_media_info._app.empty() || _media_info._streamid.empty()){ + if(_media_info.app.empty() || _media_info.stream.empty()){ //不允许莫名其妙的推流url on_res("rtmp推流url非法", ProtocolOption()); return; @@ -242,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()); @@ -283,7 +281,7 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr sendStatus({ "level", (ok ? "status" : "error"), "code", (ok ? "NetStream.Play.Reset" : (auth_success ? "NetStream.Play.StreamNotFound" : "NetStream.Play.BadAuth")), "description", (ok ? "Resetting and playing." : (auth_success ? "No such stream." : err.data())), - "details", _media_info._streamid, + "details", _media_info.stream, "clientid", "0" }); if (!ok) { @@ -297,7 +295,7 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr sendStatus({ "level", "status", "code", "NetStream.Play.Start", "description", "Started playing." , - "details", _media_info._streamid, + "details", _media_info.stream, "clientid", "0"}); // |RtmpSampleAccess(true, true) @@ -316,27 +314,28 @@ void RtmpSession::sendPlayResponse(const string &err, const RtmpMediaSource::Ptr sendStatus({ "level", "status", "code", "NetStream.Play.PublishNotify", "description", "Now published." , - "details", _media_info._streamid, + "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); }); src->pause(false); _ring_reader = src->getRing()->attach(getPoller()); - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); - _ring_reader->setGetInfoCB([weak_self]() { return weak_self.lock(); }); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); + _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) { @@ -357,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); @@ -374,7 +374,7 @@ void RtmpSession::doPlayResponse(const string &err,const std::function weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); MediaSource::findAsync(_media_info, weak_self.lock(), [weak_self,cb](const MediaSource::Ptr &src){ auto rtmp_src = dynamic_pointer_cast(src); auto strong_self = weak_self.lock(); @@ -387,7 +387,7 @@ void RtmpSession::doPlayResponse(const string &err,const std::function ticker(new Ticker); - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); std::shared_ptr token(new onceToken(nullptr, [ticker,weak_self](){ auto strong_self = weak_self.lock(); if (strong_self) { @@ -410,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) {}); @@ -458,9 +458,11 @@ string RtmpSession::getStreamId(const string &str){ } void RtmpSession::onCmd_play(AMFDecoder &dec) { - dec.load();/* NULL */ - _media_info.parse(_tc_url + "/" + getStreamId(dec.load())); - _media_info._schema = RTMP_SCHEMA; + dec.load(); /* NULL */ + // 赋值为rtmp stream id 信息 + _media_info.stream = getStreamId(dec.load()); + // 再解析url,切割url为app/stream_id (不一定符合rtmp url切割规范) + _media_info.parse(_media_info.getUrl()); doPlay(dec); } @@ -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; } @@ -614,7 +631,7 @@ MediaOriginType RtmpSession::getOriginType(MediaSource &sender) const{ } string RtmpSession::getOriginUrl(MediaSource &sender) const { - return _media_info._full_url; + return _media_info.full_url; } std::shared_ptr RtmpSession::getOriginSock(MediaSource &sender) const { diff --git a/src/Rtmp/RtmpSession.h b/src/Rtmp/RtmpSession.h index 243ac749..f51b567e 100644 --- a/src/Rtmp/RtmpSession.h +++ b/src/Rtmp/RtmpSession.h @@ -92,12 +92,12 @@ private: uint32_t _continue_push_ms = 0; //消耗的总流量 uint64_t _total_bytes = 0; - std::string _tc_url; //数据接收超时计时器 toolkit::Ticker _ticker; 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/Rtmp/amf.h b/src/Rtmp/amf.h index 283fbcc7..276315cf 100644 --- a/src/Rtmp/amf.h +++ b/src/Rtmp/amf.h @@ -16,6 +16,7 @@ #include #include #include +#include namespace toolkit { class BufferLikeString; } diff --git a/src/Rtp/GB28181Process.cpp b/src/Rtp/GB28181Process.cpp index b4f93f75..204d98f8 100644 --- a/src/Rtp/GB28181Process.cpp +++ b/src/Rtp/GB28181Process.cpp @@ -134,7 +134,7 @@ bool GB28181Process::inputRtp(bool, const char *data, size_t data_len) { // 设置dump目录 GET_CONFIG(string, dump_dir, RtpProxy::kDumpDir); if (!dump_dir.empty()) { - auto save_path = File::absolutePath(_media_info._streamid + ".mpeg", dump_dir); + auto save_path = File::absolutePath(_media_info.stream + ".mpeg", dump_dir); _save_file_ps.reset(File::create_file(save_path.data(), "wb"), [](FILE *fp) { if (fp) { fclose(fp); @@ -171,11 +171,11 @@ void GB28181Process::onRtpDecode(const Frame::Ptr &frame) { // 创建解码器 if (checkTS((uint8_t *)frame->data(), frame->size())) { // 猜测是ts负载 - InfoL << _media_info._streamid << " judged to be TS"; + InfoL << _media_info.stream << " judged to be TS"; _decoder = DecoderImp::createDecoder(DecoderImp::decoder_ts, _interface); } else { // 猜测是ps负载 - InfoL << _media_info._streamid << " judged to be PS"; + InfoL << _media_info.stream << " judged to be PS"; _decoder = DecoderImp::createDecoder(DecoderImp::decoder_ps, _interface); } } diff --git a/src/Rtp/RtpProcess.cpp b/src/Rtp/RtpProcess.cpp index fac1bd2b..5e61ccf4 100644 --- a/src/Rtp/RtpProcess.cpp +++ b/src/Rtp/RtpProcess.cpp @@ -26,14 +26,14 @@ static constexpr size_t kMaxCachedFrame = 200; namespace mediakit { RtpProcess::RtpProcess(const string &stream_id) { - _media_info._schema = kRtpAppName; - _media_info._vhost = DEFAULT_VHOST; - _media_info._app = kRtpAppName; - _media_info._streamid = stream_id; + _media_info.schema = kRtpAppName; + _media_info.vhost = DEFAULT_VHOST; + _media_info.app = kRtpAppName; + _media_info.stream = stream_id; GET_CONFIG(string, dump_dir, RtpProxy::kDumpDir); { - FILE *fp = !dump_dir.empty() ? File::create_file(File::absolutePath(_media_info._streamid + ".rtp", dump_dir).data(), "wb") : nullptr; + FILE *fp = !dump_dir.empty() ? File::create_file(File::absolutePath(_media_info.stream + ".rtp", dump_dir).data(), "wb") : nullptr; if (fp) { _save_file_rtp.reset(fp, [](FILE *fp) { fclose(fp); @@ -42,7 +42,7 @@ RtpProcess::RtpProcess(const string &stream_id) { } { - FILE *fp = !dump_dir.empty() ? File::create_file(File::absolutePath(_media_info._streamid + ".video", dump_dir).data(), "wb") : nullptr; + FILE *fp = !dump_dir.empty() ? File::create_file(File::absolutePath(_media_info.stream + ".video", dump_dir).data(), "wb") : nullptr; if (fp) { _save_file_video.reset(fp, [](FILE *fp) { fclose(fp); @@ -66,7 +66,11 @@ RtpProcess::~RtpProcess() { //流量统计事件广播 GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_total_bytes >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, false, static_cast(*this)); + try { + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, false, *this); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } } } @@ -206,35 +210,31 @@ 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 { - return _media_info._streamid; + return _media_info.stream; } void RtpProcess::emitOnPublish() { @@ -251,9 +251,7 @@ void RtpProcess::emitOnPublish() { return; } if (err.empty()) { - strong_self->_muxer = std::make_shared(strong_self->_media_info._vhost, - strong_self->_media_info._app, - strong_self->_media_info._streamid,0.0f, + strong_self->_muxer = std::make_shared(strong_self->_media_info, 0.0f, option); if (strong_self->_only_audio) { strong_self->_muxer->setOnlyAudio(); @@ -268,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()); } } @@ -291,7 +289,7 @@ toolkit::EventPoller::Ptr RtpProcess::getOwnerPoller(MediaSource &sender) { if (_sock) { return _sock->getPoller(); } - throw std::runtime_error("RtpProcess::getOwnerPoller failed:" + _media_info._streamid); + throw std::runtime_error("RtpProcess::getOwnerPoller failed:" + _media_info.stream); } float RtpProcess::getLossRate(MediaSource &sender, TrackType type) { @@ -299,8 +297,8 @@ float RtpProcess::getLossRate(MediaSource &sender, TrackType type) { if (!expected) { return -1; } - return geLostInterval() * 100 / expected; + return getLostInterval() * 100 / expected; } }//namespace mediakit -#endif//defined(ENABLE_RTPPROXY) \ No newline at end of file +#endif//defined(ENABLE_RTPPROXY) diff --git a/src/Rtp/RtpSender.cpp b/src/Rtp/RtpSender.cpp index a15884f0..a102d5e2 100644 --- a/src/Rtp/RtpSender.cpp +++ b/src/Rtp/RtpSender.cpp @@ -28,7 +28,11 @@ RtpSender::RtpSender(EventPoller::Ptr poller) { } RtpSender::~RtpSender() { - flush(); + try { + flush(); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } } void RtpSender::startSend(const MediaSourceEvent::SendRtpArgs &args, const function &cb){ diff --git a/src/Rtp/RtpServer.cpp b/src/Rtp/RtpServer.cpp index 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/Rtp/RtpSession.cpp b/src/Rtp/RtpSession.cpp index 26faadfb..38b6d4cd 100644 --- a/src/Rtp/RtpSession.cpp +++ b/src/Rtp/RtpSession.cpp @@ -122,7 +122,7 @@ void RtpSession::onRtpPacket(const char *data, size_t len) { return; } _process->setOnlyAudio(_only_audio); - _process->setDelegate(dynamic_pointer_cast(shared_from_this())); + _process->setDelegate(static_pointer_cast(shared_from_this())); } try { uint32_t rtp_ssrc = 0; diff --git a/src/Rtp/RtpSplitter.cpp b/src/Rtp/RtpSplitter.cpp index f7175b30..0f4b6dc4 100644 --- a/src/Rtp/RtpSplitter.cpp +++ b/src/Rtp/RtpSplitter.cpp @@ -46,24 +46,31 @@ const char *RtpSplitter::onSearchPacketTail(const char *data, size_t len) { return nullptr; } - if (isEhome(data, len)) { - //是ehome协议 - if (len < kEHOME_OFFSET + 4) { - //数据不够 - return nullptr; + if ( _is_ehome ) { + if (isEhome(data, len)) { + //是ehome协议 + if (len < kEHOME_OFFSET + 4) { + //数据不够 + return nullptr; + } + //忽略ehome私有头后是rtsp样式的rtp,多4个字节, + _offset = kEHOME_OFFSET + 4; + _is_ehome = true; + //忽略ehome私有头 + return onSearchPacketTail_l(data + kEHOME_OFFSET + 2, len - kEHOME_OFFSET - 2); } - //忽略ehome私有头后是rtsp样式的rtp,多4个字节, - _offset = kEHOME_OFFSET + 4; - _is_ehome = true; - //忽略ehome私有头 - return onSearchPacketTail_l(data + kEHOME_OFFSET + 2, len - kEHOME_OFFSET - 2); + _is_ehome = false; } - if (data[0] == '$') { - //可能是4个字节的rtp头 - _offset = 4; - return onSearchPacketTail_l(data + 2, len - 2); + if ( _is_rtsp_interleaved ) { + if (data[0] == '$') { + //可能是4个字节的rtp头 + _offset = 4; + return onSearchPacketTail_l(data + 2, len - 2); + } + _is_rtsp_interleaved = false; } + //两个字节的rtp头 _offset = 2; return onSearchPacketTail_l(data, len); diff --git a/src/Rtp/RtpSplitter.h b/src/Rtp/RtpSplitter.h index 9401691e..a2990a7f 100644 --- a/src/Rtp/RtpSplitter.h +++ b/src/Rtp/RtpSplitter.h @@ -35,7 +35,8 @@ protected: const char *onSearchPacketTail_l(const char *data, size_t len); private: - bool _is_ehome = false; + bool _is_ehome = true; + bool _is_rtsp_interleaved = true; size_t _offset = 0; }; diff --git a/src/Rtsp/RtpCodec.cpp b/src/Rtsp/RtpCodec.cpp index 21257bce..6d1d4a9c 100644 --- a/src/Rtsp/RtpCodec.cpp +++ b/src/Rtsp/RtpCodec.cpp @@ -39,7 +39,7 @@ RtpPacket::Ptr RtpInfo::makeRtp(TrackType type, const void* data, size_t len, bo ++_seq; header->stamp = htonl(uint64_t(stamp) * _sample_rate / 1000); header->ssrc = htonl(_ssrc); - + rtp->ntp_stamp = stamp; //有效负载 if (data) { memcpy(&ptr[RtpPacket::kRtpHeaderSize + RtpPacket::kRtpTcpHeaderSize], data, len); diff --git a/src/Rtsp/RtpReceiver.cpp b/src/Rtsp/RtpReceiver.cpp index 363a3175..7e669138 100644 --- a/src/Rtsp/RtpReceiver.cpp +++ b/src/Rtsp/RtpReceiver.cpp @@ -71,7 +71,7 @@ RtpPacket::Ptr RtpTrack::inputRtp(TrackType type, int sample_rate, uint8_t *ptr, } else { //ssrc错误 if (_ssrc_alive.elapsedTime() < 3 * 1000) { - //接受正确ssrc的rtp在10秒内,那么我们认为存在多路rtp,忽略掉ssrc不匹配的rtp + //接收正确ssrc的rtp在10秒内,那么我们认为存在多路rtp,忽略掉ssrc不匹配的rtp WarnL << "ssrc mismatch, rtp dropped:" << ssrc << " != " << _ssrc; return nullptr; } diff --git a/src/Rtsp/Rtsp.cpp b/src/Rtsp/Rtsp.cpp index b80a88f7..daf09680 100644 --- a/src/Rtsp/Rtsp.cpp +++ b/src/Rtsp/Rtsp.cpp @@ -10,6 +10,7 @@ #include #include +#include #include "Rtsp.h" #include "Common/Parser.h" #include "Common/config.h" @@ -22,7 +23,8 @@ namespace mediakit { int RtpPayload::getClockRate(int pt) { switch (pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return clock_rate; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return clock_rate; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return 90000; @@ -31,7 +33,8 @@ int RtpPayload::getClockRate(int pt) { TrackType RtpPayload::getTrackType(int pt) { switch (pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return type; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return type; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return TrackInvalid; @@ -40,7 +43,8 @@ TrackType RtpPayload::getTrackType(int pt) { int RtpPayload::getAudioChannel(int pt) { switch (pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return channel; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return channel; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return 1; @@ -49,7 +53,8 @@ int RtpPayload::getAudioChannel(int pt) { const char *RtpPayload::getName(int pt) { switch (pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return #name; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return #name; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return "unknown payload type"; @@ -58,7 +63,8 @@ const char *RtpPayload::getName(int pt) { CodecId RtpPayload::getCodecId(int pt) { switch (pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return codec_id; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return codec_id; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return CodecInvalid; @@ -85,7 +91,8 @@ static void getAttrSdp(const multimap &attr, _StrPrinter &printe string SdpTrack::getName() const { switch (_pt) { -#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) case value : return #name; +#define SWITCH_CASE(name, type, value, clock_rate, channel, codec_id) \ + case value: return #name; RTP_PT_MAP(SWITCH_CASE) #undef SWITCH_CASE default: return _codec; @@ -94,7 +101,7 @@ string SdpTrack::getName() const { string SdpTrack::getControlUrl(const string &base_url) const { if (_control.find("://") != string::npos) { - //以rtsp://开头 + // 以rtsp://开头 return _control; } return base_url + "/" + _control; @@ -180,11 +187,11 @@ void SdpParser::load(const string &sdp) { break; } case 'a': { - string attr = FindField(opt_val.data(), nullptr, ":"); + string attr = findSubString(opt_val.data(), nullptr, ":"); if (attr.empty()) { track->_attr.emplace(opt_val, ""); } else { - track->_attr.emplace(attr, FindField(opt_val.data(), ":", nullptr)); + track->_attr.emplace(attr, findSubString(opt_val.data(), ":", nullptr)); } break; } @@ -197,14 +204,14 @@ void SdpParser::load(const string &sdp) { auto &track = *track_ptr; auto it = track._attr.find("range"); if (it != track._attr.end()) { - char name[16] = {0}, start[16] = {0}, end[16] = {0}; + char name[16] = { 0 }, start[16] = { 0 }, end[16] = { 0 }; int ret = sscanf(it->second.data(), "%15[^=]=%15[^-]-%15s", name, start, end); if (3 == ret || 2 == ret) { if (strcmp(start, "now") == 0) { strcpy(start, "0"); } - track._start = (float) atof(start); - track._end = (float) atof(end); + track._start = (float)atof(start); + track._end = (float)atof(end); track._duration = track._end - track._start; } } @@ -212,11 +219,11 @@ void SdpParser::load(const string &sdp) { for (it = track._attr.find("rtpmap"); it != track._attr.end() && it->first == "rtpmap";) { auto &rtpmap = it->second; int pt, samplerate, channel; - char codec[16] = {0}; + char codec[16] = { 0 }; sscanf(rtpmap.data(), "%d", &pt); if (track._pt != pt && track._pt != 0xff) { - //pt不匹配 + // pt不匹配 it = track._attr.erase(it); continue; } @@ -230,22 +237,22 @@ void SdpParser::load(const string &sdp) { track._samplerate = samplerate; } if (!track._samplerate && track._type == TrackVideo) { - //未设置视频采样率时,赋值为90000 + // 未设置视频采样率时,赋值为90000 track._samplerate = 90000; } ++it; } - for (it = track._attr.find("fmtp"); it != track._attr.end() && it->first == "fmtp"; ) { + for (it = track._attr.find("fmtp"); it != track._attr.end() && it->first == "fmtp";) { auto &fmtp = it->second; int pt; sscanf(fmtp.data(), "%d", &pt); if (track._pt != pt && track._pt != 0xff) { - //pt不匹配 + // pt不匹配 it = track._attr.erase(it); continue; } - track._fmtp = FindField(fmtp.data(), " ", nullptr); + track._fmtp = findSubString(fmtp.data(), " ", nullptr); ++it; } @@ -315,8 +322,17 @@ string SdpParser::toString() const { return title + video + audio; } -template -class PortManager : public std::enable_shared_from_this > { +std::string SdpParser::getControlUrl(const std::string &url) const { + auto title_track = getTrack(TrackTitle); + if (title_track && title_track->_control.find("://") != string::npos) { + // 以rtsp://开头 + return title_track->_control; + } + return url; +} + +template +class PortManager : public std::enable_shared_from_this> { public: PortManager() { static auto func = [](const string &str, int index) { @@ -326,11 +342,11 @@ public: }; GET_CONFIG_FUNC(uint16_t, s_min_port, RtpProxy::kPortRange, [](const string &str) { return func(str, 0); }); GET_CONFIG_FUNC(uint16_t, s_max_port, RtpProxy::kPortRange, [](const string &str) { return func(str, 1); }); - assert(s_max_port >= s_min_port + 36 -1); + assert(s_max_port >= s_min_port + 36 - 1); setRange((s_min_port + 1) / 2, s_max_port / 2); } - static PortManager& Instance() { + static PortManager &Instance() { static auto instance = std::make_shared(); return *instance; } @@ -344,12 +360,12 @@ public: } if (is_udp) { if (!sock0->bindUdpSock(2 * *sock_pair, local_ip.data(), re_use_port)) { - //分配端口失败 + // 分配端口失败 throw runtime_error("open udp socket[0] failed"); } if (!sock1->bindUdpSock(2 * *sock_pair + 1, local_ip.data(), re_use_port)) { - //分配端口失败 + // 分配端口失败 throw runtime_error("open udp socket[1] failed"); } @@ -359,12 +375,12 @@ public: sock1->setOnAccept(on_cycle); } else { if (!sock0->listen(2 * *sock_pair, local_ip.data())) { - //分配端口失败 + // 分配端口失败 throw runtime_error("listen tcp socket[0] failed"); } if (!sock1->listen(2 * *sock_pair + 1, local_ip.data())) { - //分配端口失败 + // 分配端口失败 throw runtime_error("listen tcp socket[1] failed"); } @@ -377,9 +393,13 @@ public: private: void setRange(uint16_t start_pos, uint16_t end_pos) { + std::mt19937 rng(std::random_device {}()); lock_guard lck(_pool_mtx); + auto it = _port_pair_pool.begin(); while (start_pos < end_pos) { - _port_pair_pool.emplace_back(start_pos++); + // 随机端口排序,防止重启后导致分配的端口重复 + _port_pair_pool.insert(it, start_pos++); + it = _port_pair_pool.begin() + (rng() % (1 + _port_pair_pool.size())); } } @@ -400,7 +420,7 @@ private: return; } InfoL << "return port to pool:" << 2 * pos << "-" << 2 * pos + 1; - //回收端口号 + // 回收端口号 lock_guard lck(strong_self->_pool_mtx); strong_self->_port_pair_pool.emplace_back(pos); }); @@ -416,7 +436,7 @@ void makeSockPair(std::pair &pair, const string &local int try_count = 0; while (true) { try { - //udp和tcp端口池使用相同算法和范围分配,但是互不相干 + // udp和tcp端口池使用相同算法和范围分配,但是互不相干 if (is_udp) { PortManager<0>::Instance().makeSockPair(pair, local_ip, re_use_port, is_udp); } else { @@ -433,9 +453,9 @@ void makeSockPair(std::pair &pair, const string &local } string printSSRC(uint32_t ui32Ssrc) { - char tmp[9] = {0}; + char tmp[9] = { 0 }; ui32Ssrc = htonl(ui32Ssrc); - uint8_t *pSsrc = (uint8_t *) &ui32Ssrc; + uint8_t *pSsrc = (uint8_t *)&ui32Ssrc; for (int i = 0; i < 4; i++) { sprintf(tmp + 2 * i, "%02X", pSsrc[i]); } @@ -447,7 +467,7 @@ bool isRtp(const char *buf, size_t size) { return false; } RtpHeader *header = (RtpHeader *)buf; - return ((header->pt < 64) || (header->pt >= 96)); + return ((header->pt < 64) || (header->pt >= 96)) && header->version == RtpPacket::kRtpVersion; } bool isRtcp(const char *buf, size_t size) { @@ -470,12 +490,10 @@ Buffer::Ptr makeRtpOverTcpPrefix(uint16_t size, uint8_t interleaved) { return rtp_tcp; } -#define AV_RB16(x) \ - ((((const uint8_t*)(x))[0] << 8) | \ - ((const uint8_t*)(x))[1]) +#define AV_RB16(x) ((((const uint8_t *)(x))[0] << 8) | ((const uint8_t *)(x))[1]) size_t RtpHeader::getCsrcSize() const { - //每个csrc占用4字节 + // 每个csrc占用4字节 return csrc << 2; } @@ -487,18 +505,18 @@ uint8_t *RtpHeader::getCsrcData() { } size_t RtpHeader::getExtSize() const { - //rtp有ext + // rtp有ext if (!ext) { return 0; } auto ext_ptr = &payload + getCsrcSize(); - //uint16_t reserved = AV_RB16(ext_ptr); - //每个ext占用4字节 + // uint16_t reserved = AV_RB16(ext_ptr); + // 每个ext占用4字节 return AV_RB16(ext_ptr + 2) << 2; } uint16_t RtpHeader::getExtReserved() const { - //rtp有ext + // rtp有ext if (!ext) { return 0; } @@ -511,12 +529,12 @@ uint8_t *RtpHeader::getExtData() { return nullptr; } auto ext_ptr = &payload + getCsrcSize(); - //多出的4个字节分别为reserved、ext_len + // 多出的4个字节分别为reserved、ext_len return ext_ptr + 4; } size_t RtpHeader::getPayloadOffset() const { - //有ext时,还需要忽略reserved、ext_len 4个字节 + // 有ext时,还需要忽略reserved、ext_len 4个字节 return getCsrcSize() + (ext ? (4 + getExtSize()) : 0); } @@ -528,7 +546,7 @@ size_t RtpHeader::getPaddingSize(size_t rtp_size) const { if (!padding) { return 0; } - auto end = (uint8_t *) this + rtp_size - 1; + auto end = (uint8_t *)this + rtp_size - 1; return *end; } @@ -539,12 +557,12 @@ ssize_t RtpHeader::getPayloadSize(size_t rtp_size) const { string RtpHeader::dumpString(size_t rtp_size) const { _StrPrinter printer; - printer << "version:" << (int) version << "\r\n"; + printer << "version:" << (int)version << "\r\n"; printer << "padding:" << getPaddingSize(rtp_size) << "\r\n"; printer << "ext:" << getExtSize() << "\r\n"; printer << "csrc:" << getCsrcSize() << "\r\n"; - printer << "mark:" << (int) mark << "\r\n"; - printer << "pt:" << (int) pt << "\r\n"; + printer << "mark:" << (int)mark << "\r\n"; + printer << "pt:" << (int)pt << "\r\n"; printer << "seq:" << ntohs(seq) << "\r\n"; printer << "stamp:" << ntohl(stamp) << "\r\n"; printer << "ssrc:" << ntohl(ssrc) << "\r\n"; @@ -557,16 +575,16 @@ string RtpHeader::dumpString(size_t rtp_size) const { /////////////////////////////////////////////////////////////////////// RtpHeader *RtpPacket::getHeader() { - //需除去rtcp over tcp 4个字节长度 - return (RtpHeader *) (data() + RtpPacket::kRtpTcpHeaderSize); + // 需除去rtcp over tcp 4个字节长度 + return (RtpHeader *)(data() + RtpPacket::kRtpTcpHeaderSize); } const RtpHeader *RtpPacket::getHeader() const { - return (RtpHeader *) (data() + RtpPacket::kRtpTcpHeaderSize); + return (RtpHeader *)(data() + RtpPacket::kRtpTcpHeaderSize); } string RtpPacket::dumpString() const { - return ((RtpPacket *) this)->getHeader()->dumpString(size() - RtpPacket::kRtpTcpHeaderSize); + return ((RtpPacket *)this)->getHeader()->dumpString(size() - RtpPacket::kRtpTcpHeaderSize); } uint16_t RtpPacket::getSeq() const { @@ -590,7 +608,7 @@ uint8_t *RtpPacket::getPayload() { } size_t RtpPacket::getPayloadSize() const { - //需除去rtcp over tcp 4个字节长度 + // 需除去rtcp over tcp 4个字节长度 return getHeader()->getPayloadSize(size() - kRtpTcpHeaderSize); } @@ -608,23 +626,22 @@ RtpPacket::Ptr RtpPacket::create() { #endif } - /** -* 构造title类型sdp -* @param dur_sec rtsp点播时长,0代表直播,单位秒 -* @param header 自定义sdp描述 -* @param version sdp版本 -*/ + * 构造title类型sdp + * @param dur_sec rtsp点播时长,0代表直播,单位秒 + * @param header 自定义sdp描述 + * @param version sdp版本 + */ -TitleSdp::TitleSdp(float dur_sec, const std::map& header, int version) : Sdp(0, 0) { +TitleSdp::TitleSdp(float dur_sec, const std::map &header, int version) + : Sdp(0, 0) { _printer << "v=" << version << "\r\n"; if (!header.empty()) { for (auto &pr : header) { _printer << pr.first << "=" << pr.second << "\r\n"; } - } - else { + } else { _printer << "o=- 0 0 IN IP4 0.0.0.0\r\n"; _printer << "s=Streamed by " << kServerName << "\r\n"; _printer << "c=IN IP4 0.0.0.0\r\n"; @@ -632,18 +649,17 @@ TitleSdp::TitleSdp(float dur_sec, const std::map& head } if (dur_sec <= 0) { - //直播 + // 直播 _printer << "a=range:npt=now-\r\n"; - } - else { - //点播 + } else { + // 点播 _dur_sec = dur_sec; _printer << "a=range:npt=0-" << dur_sec << "\r\n"; } _printer << "a=control:*\r\n"; } -}//namespace mediakit +} // namespace mediakit namespace toolkit { StatisticImp(mediakit::RtpPacket); diff --git a/src/Rtsp/Rtsp.h b/src/Rtsp/Rtsp.h index 6bb19d8d..dc34f918 100644 --- a/src/Rtsp/Rtsp.h +++ b/src/Rtsp/Rtsp.h @@ -11,13 +11,13 @@ #ifndef RTSP_RTSP_H_ #define RTSP_RTSP_H_ -#include -#include -#include -#include #include "Common/macros.h" #include "Extension/Frame.h" #include "Network/Socket.h" +#include +#include +#include +#include namespace mediakit { @@ -29,148 +29,141 @@ typedef enum { RTP_MULTICAST = 2, } eRtpType; -#define RTP_PT_MAP(XX) \ - XX(PCMU, TrackAudio, 0, 8000, 1, CodecG711U) \ - XX(GSM, TrackAudio , 3, 8000, 1, CodecInvalid) \ - XX(G723, TrackAudio, 4, 8000, 1, CodecInvalid) \ - XX(DVI4_8000, TrackAudio, 5, 8000, 1, CodecInvalid) \ - XX(DVI4_16000, TrackAudio, 6, 16000, 1, CodecInvalid) \ - XX(LPC, TrackAudio, 7, 8000, 1, CodecInvalid) \ - XX(PCMA, TrackAudio, 8, 8000, 1, CodecG711A) \ - XX(G722, TrackAudio, 9, 8000, 1, CodecInvalid) \ - XX(L16_Stereo, TrackAudio, 10, 44100, 2, CodecInvalid) \ - XX(L16_Mono, TrackAudio, 11, 44100, 1, CodecInvalid) \ - XX(QCELP, TrackAudio, 12, 8000, 1, CodecInvalid) \ - XX(CN, TrackAudio, 13, 8000, 1, CodecInvalid) \ - XX(MPA, TrackAudio, 14, 90000, 1, CodecInvalid) \ - XX(G728, TrackAudio, 15, 8000, 1, CodecInvalid) \ - XX(DVI4_11025, TrackAudio, 16, 11025, 1, CodecInvalid) \ - XX(DVI4_22050, TrackAudio, 17, 22050, 1, CodecInvalid) \ - XX(G729, TrackAudio, 18, 8000, 1, CodecInvalid) \ - XX(CelB, TrackVideo, 25, 90000, 1, CodecInvalid) \ - XX(JPEG, TrackVideo, 26, 90000, 1, CodecJPEG) \ - XX(nv, TrackVideo, 28, 90000, 1, CodecInvalid) \ - XX(H261, TrackVideo, 31, 90000, 1, CodecInvalid) \ - XX(MPV, TrackVideo, 32, 90000, 1, CodecInvalid) \ - XX(MP2T, TrackVideo, 33, 90000, 1, CodecInvalid) \ - XX(H263, TrackVideo, 34, 90000, 1, CodecInvalid) \ +#define RTP_PT_MAP(XX) \ + XX(PCMU, TrackAudio, 0, 8000, 1, CodecG711U) \ + XX(GSM, TrackAudio, 3, 8000, 1, CodecInvalid) \ + XX(G723, TrackAudio, 4, 8000, 1, CodecInvalid) \ + XX(DVI4_8000, TrackAudio, 5, 8000, 1, CodecInvalid) \ + XX(DVI4_16000, TrackAudio, 6, 16000, 1, CodecInvalid) \ + XX(LPC, TrackAudio, 7, 8000, 1, CodecInvalid) \ + XX(PCMA, TrackAudio, 8, 8000, 1, CodecG711A) \ + XX(G722, TrackAudio, 9, 8000, 1, CodecInvalid) \ + XX(L16_Stereo, TrackAudio, 10, 44100, 2, CodecInvalid) \ + XX(L16_Mono, TrackAudio, 11, 44100, 1, CodecInvalid) \ + XX(QCELP, TrackAudio, 12, 8000, 1, CodecInvalid) \ + XX(CN, TrackAudio, 13, 8000, 1, CodecInvalid) \ + XX(MPA, TrackAudio, 14, 90000, 1, CodecInvalid) \ + XX(G728, TrackAudio, 15, 8000, 1, CodecInvalid) \ + XX(DVI4_11025, TrackAudio, 16, 11025, 1, CodecInvalid) \ + XX(DVI4_22050, TrackAudio, 17, 22050, 1, CodecInvalid) \ + XX(G729, TrackAudio, 18, 8000, 1, CodecInvalid) \ + XX(CelB, TrackVideo, 25, 90000, 1, CodecInvalid) \ + XX(JPEG, TrackVideo, 26, 90000, 1, CodecJPEG) \ + XX(nv, TrackVideo, 28, 90000, 1, CodecInvalid) \ + XX(H261, TrackVideo, 31, 90000, 1, CodecInvalid) \ + XX(MPV, TrackVideo, 32, 90000, 1, CodecInvalid) \ + XX(MP2T, TrackVideo, 33, 90000, 1, CodecInvalid) \ + XX(H263, TrackVideo, 34, 90000, 1, CodecInvalid) typedef enum { -#define ENUM_DEF(name, type, value, clock_rate, channel, codec_id) PT_ ## name = value, +#define ENUM_DEF(name, type, value, clock_rate, channel, codec_id) PT_##name = value, RTP_PT_MAP(ENUM_DEF) #undef ENUM_DEF - PT_MAX = 128 + PT_MAX + = 128 } PayloadType; -}; +}; // namespace Rtsp -#if defined(_WIN32) #pragma pack(push, 1) -#endif // defined(_WIN32) class RtpHeader { public: #if __BYTE_ORDER == __BIG_ENDIAN - //版本号,固定为2 - uint32_t version: 2; - //padding - uint32_t padding: 1; - //扩展 - uint32_t ext: 1; - //csrc - uint32_t csrc: 4; - //mark - uint32_t mark: 1; - //负载类型 - uint32_t pt: 7; + // 版本号,固定为2 + uint32_t version : 2; + // padding + uint32_t padding : 1; + // 扩展 + uint32_t ext : 1; + // csrc + uint32_t csrc : 4; + // mark + uint32_t mark : 1; + // 负载类型 + uint32_t pt : 7; #else - //csrc - uint32_t csrc: 4; - //扩展 - uint32_t ext: 1; - //padding - uint32_t padding: 1; - //版本号,固定为2 - uint32_t version: 2; - //负载类型 - uint32_t pt: 7; - //mark - uint32_t mark: 1; + // csrc + uint32_t csrc : 4; + // 扩展 + uint32_t ext : 1; + // padding + uint32_t padding : 1; + // 版本号,固定为2 + uint32_t version : 2; + // 负载类型 + uint32_t pt : 7; + // mark + uint32_t mark : 1; #endif - //序列号 - uint32_t seq: 16; - //时间戳 + // 序列号 + uint32_t seq : 16; + // 时间戳 uint32_t stamp; - //ssrc + // ssrc uint32_t ssrc; - //负载,如果有csrc和ext,前面为 4 * csrc + (4 + 4 * ext_len) + // 负载,如果有csrc和ext,前面为 4 * csrc + (4 + 4 * ext_len) uint8_t payload; public: - //返回csrc字段字节长度 + // 返回csrc字段字节长度 size_t getCsrcSize() const; - //返回csrc字段首地址,不存在时返回nullptr + // 返回csrc字段首地址,不存在时返回nullptr uint8_t *getCsrcData(); - //返回ext字段字节长度 + // 返回ext字段字节长度 size_t getExtSize() const; - //返回ext reserved值 + // 返回ext reserved值 uint16_t getExtReserved() const; - //返回ext段首地址,不存在时返回nullptr + // 返回ext段首地址,不存在时返回nullptr uint8_t *getExtData(); - //返回有效负载指针,跳过csrc、ext - uint8_t* getPayloadData(); - //返回有效负载总长度,不包括csrc、ext、padding + // 返回有效负载指针,跳过csrc、ext + uint8_t *getPayloadData(); + // 返回有效负载总长度,不包括csrc、ext、padding ssize_t getPayloadSize(size_t rtp_size) const; - //打印调试信息 + // 打印调试信息 std::string dumpString(size_t rtp_size) const; private: - //返回有效负载偏移量 + // 返回有效负载偏移量 size_t getPayloadOffset() const; - //返回padding长度 + // 返回padding长度 size_t getPaddingSize(size_t rtp_size) const; -} PACKED; +}; -#if defined(_WIN32) #pragma pack(pop) -#endif // defined(_WIN32) -//此rtp为rtp over tcp形式,需要忽略前4个字节 -class RtpPacket : public toolkit::BufferRaw{ +// 此rtp为rtp over tcp形式,需要忽略前4个字节 +class RtpPacket : public toolkit::BufferRaw { public: using Ptr = std::shared_ptr; - enum { - kRtpVersion = 2, - kRtpHeaderSize = 12, - kRtpTcpHeaderSize = 4 - }; + enum { kRtpVersion = 2, kRtpHeaderSize = 12, kRtpTcpHeaderSize = 4 }; - //获取rtp头 - RtpHeader* getHeader(); - const RtpHeader* getHeader() const; + // 获取rtp头 + RtpHeader *getHeader(); + const RtpHeader *getHeader() const; - //打印调试信息 + // 打印调试信息 std::string dumpString() const; - //主机字节序的seq + // 主机字节序的seq uint16_t getSeq() const; uint32_t getStamp() const; - //主机字节序的时间戳,已经转换为毫秒 + // 主机字节序的时间戳,已经转换为毫秒 uint64_t getStampMS(bool ntp = true) const; - //主机字节序的ssrc + // 主机字节序的ssrc uint32_t getSSRC() const; - //有效负载,跳过csrc、ext - uint8_t* getPayload(); - //有效负载长度,不包括csrc、ext、padding + // 有效负载,跳过csrc、ext + uint8_t *getPayload(); + // 有效负载长度,不包括csrc、ext、padding size_t getPayloadSize() const; - //音视频类型 + // 音视频类型 TrackType type; - //音频为采样率,视频一般为90000 + // 音频为采样率,视频一般为90000 uint32_t sample_rate; - //ntp时间戳 + // ntp时间戳 uint64_t ntp_stamp; static Ptr create(); @@ -180,7 +173,7 @@ private: RtpPacket() = default; private: - //对象个数统计 + // 对象个数统计 toolkit::ObjectStatistic _statistic; }; @@ -229,7 +222,7 @@ public: uint8_t _interleaved = 0; uint16_t _seq = 0; uint32_t _ssrc = 0; - //时间戳,单位毫秒 + // 时间戳,单位毫秒 uint32_t _time_stamp = 0; }; @@ -246,15 +239,16 @@ public: SdpTrack::Ptr getTrack(TrackType type) const; std::vector getAvailableTrack() const; std::string toString() const; + std::string getControlUrl(const std::string &url) const; private: std::vector _track_vec; }; /** -* rtsp sdp基类 -*/ -class Sdp : public CodecInfo{ + * rtsp sdp基类 + */ +class Sdp : public CodecInfo { public: using Ptr = std::shared_ptr; @@ -263,7 +257,7 @@ public: * @param sample_rate 采样率 * @param payload_type pt类型 */ - Sdp(uint32_t sample_rate, uint8_t payload_type){ + Sdp(uint32_t sample_rate, uint8_t payload_type) { _sample_rate = sample_rate; _payload_type = payload_type; } @@ -274,23 +268,19 @@ public: * 获取sdp字符串 * @return */ - virtual std::string getSdp() const = 0; + virtual std::string getSdp() const = 0; /** * 获取pt * @return */ - uint8_t getPayloadType() const{ - return _payload_type; - } + uint8_t getPayloadType() const { return _payload_type; } /** * 获取采样率 * @return */ - uint32_t getSampleRate() const{ - return _sample_rate; - } + uint32_t getSampleRate() const { return _sample_rate; } private: uint8_t _payload_type; @@ -298,9 +288,9 @@ private: }; /** -* sdp中除音视频外的其他描述部分 -*/ -class TitleSdp : public Sdp{ + * sdp中除音视频外的其他描述部分 + */ +class TitleSdp : public Sdp { public: using Ptr = std::shared_ptr; /** @@ -309,36 +299,28 @@ public: * @param header 自定义sdp描述 * @param version sdp版本 */ - TitleSdp(float dur_sec = 0, - const std::map &header = std::map(), - int version = 0); + TitleSdp(float dur_sec = 0, const std::map &header = std::map(), int version = 0); - std::string getSdp() const override { - return _printer; - } + std::string getSdp() const override { return _printer; } - CodecId getCodecId() const override { - return CodecInvalid; - } + CodecId getCodecId() const override { return CodecInvalid; } - float getDuration() const { - return _dur_sec; - } + float getDuration() const { return _dur_sec; } private: float _dur_sec = 0; toolkit::_StrPrinter _printer; }; -//创建rtp over tcp4个字节的头 +// 创建rtp over tcp4个字节的头 toolkit::Buffer::Ptr makeRtpOverTcpPrefix(uint16_t size, uint8_t interleaved); -//创建rtp-rtcp端口对 +// 创建rtp-rtcp端口对 void makeSockPair(std::pair &pair, const std::string &local_ip, bool re_use_port = false, bool is_udp = true); -//十六进制方式打印ssrc +// 十六进制方式打印ssrc std::string printSSRC(uint32_t ui32Ssrc); bool isRtp(const char *buf, size_t size); -bool isRtcp(const char *buf, size_t size); +bool isRtcp(const char *buf, size_t size); -} //namespace mediakit -#endif //RTSP_RTSP_H_ +} // namespace mediakit +#endif // RTSP_RTSP_H_ diff --git a/src/Rtsp/RtspMediaSource.h b/src/Rtsp/RtspMediaSource.h index 4296f131..bac7fcf9 100644 --- a/src/Rtsp/RtspMediaSource.h +++ b/src/Rtsp/RtspMediaSource.h @@ -42,11 +42,7 @@ public: * @param stream_id 流id * @param ring_size 可以设置固定的环形缓冲大小,0则自适应 */ - RtspMediaSource(const std::string &vhost, - const std::string &app, - const std::string &stream_id, - int ring_size = RTP_GOP_SIZE) : - MediaSource(RTSP_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {} + RtspMediaSource(const MediaTuple& tuple, int ring_size = RTP_GOP_SIZE): MediaSource(RTSP_SCHEMA, tuple), _ring_size(ring_size) {} ~RtspMediaSource() override { flush(); } @@ -57,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; + } + /** * 获取播放器个数 */ @@ -76,6 +79,10 @@ public: return _sdp; } + virtual RtspMediaSource::Ptr clone(const std::string& stream) { + return nullptr; + } + /** * 获取相应轨道的ssrc */ diff --git a/src/Rtsp/RtspMediaSourceImp.cpp b/src/Rtsp/RtspMediaSourceImp.cpp index a262fad5..ea630415 100644 --- a/src/Rtsp/RtspMediaSourceImp.cpp +++ b/src/Rtsp/RtspMediaSourceImp.cpp @@ -55,7 +55,7 @@ void RtspMediaSource::onWrite(RtpPacket::Ptr rtp, bool keyPos) { track->_ssrc = rtp->getSSRC(); } if (!_ring) { - std::weak_ptr weakSelf = std::dynamic_pointer_cast(shared_from_this()); + std::weak_ptr weakSelf = std::static_pointer_cast(shared_from_this()); auto lam = [weakSelf](int size) { auto strongSelf = weakSelf.lock(); if (!strongSelf) { @@ -74,8 +74,7 @@ void RtspMediaSource::onWrite(RtpPacket::Ptr rtp, bool keyPos) { PacketCache::inputPacket(stamp, is_video, std::move(rtp), keyPos); } -RtspMediaSourceImp::RtspMediaSourceImp(const std::string &vhost, const std::string &app, const std::string &id, int ringSize) - : RtspMediaSource(vhost, app, id, ringSize) +RtspMediaSourceImp::RtspMediaSourceImp(const MediaTuple& tuple, int ringSize): RtspMediaSource(tuple, ringSize) { _demuxer = std::make_shared(); _demuxer->setTrackListener(this); @@ -114,7 +113,7 @@ void RtspMediaSourceImp::setProtocolOption(const ProtocolOption &option) //导致rtc无法播放,所以在rtsp推流rtc播放时,建议关闭直接代理模式 _option = option; _option.enable_rtsp = !direct_proxy; - _muxer = std::make_shared(getVhost(), getApp(), getId(), _demuxer->getDuration(), _option); + _muxer = std::make_shared(_tuple, _demuxer->getDuration(), _option); _muxer->setMediaListener(getListener()); _muxer->setTrackListener(std::static_pointer_cast(shared_from_this())); //让_muxer对象拦截一部分事件(比如说录像相关事件) @@ -126,6 +125,14 @@ void RtspMediaSourceImp::setProtocolOption(const ProtocolOption &option) } } +RtspMediaSource::Ptr RtspMediaSourceImp::clone(const std::string &stream) { + auto tuple = _tuple; + tuple.stream = stream; + auto src_imp = std::make_shared(tuple); + src_imp->setSdp(getSdp()); + src_imp->setProtocolOption(getProtocolOption()); + return src_imp; +} } diff --git a/src/Rtsp/RtspMediaSourceImp.h b/src/Rtsp/RtspMediaSourceImp.h index 385fc80a..483d7610 100644 --- a/src/Rtsp/RtspMediaSourceImp.h +++ b/src/Rtsp/RtspMediaSourceImp.h @@ -28,7 +28,7 @@ public: * @param id 流id * @param ringSize 环形缓存大小 */ - RtspMediaSourceImp(const std::string &vhost, const std::string &app, const std::string &id, int ringSize = RTP_GOP_SIZE); + RtspMediaSourceImp(const MediaTuple& tuple, int ringSize = RTP_GOP_SIZE); ~RtspMediaSourceImp() override = default; @@ -107,6 +107,7 @@ public: } } + RtspMediaSource::Ptr clone(const std::string& stream) override; private: bool _all_track_ready = false; ProtocolOption _option; diff --git a/src/Rtsp/RtspMediaSourceMuxer.h b/src/Rtsp/RtspMediaSourceMuxer.h index d8f40101..955c6341 100644 --- a/src/Rtsp/RtspMediaSourceMuxer.h +++ b/src/Rtsp/RtspMediaSourceMuxer.h @@ -21,13 +21,11 @@ class RtspMediaSourceMuxer final : public RtspMuxer, public MediaSourceEventInte public: using Ptr = std::shared_ptr; - RtspMediaSourceMuxer(const std::string &vhost, - const std::string &strApp, - const std::string &strId, + RtspMediaSourceMuxer(const MediaTuple& tuple, const ProtocolOption &option, const TitleSdp::Ptr &title = nullptr) : RtspMuxer(title) { _option = option; - _media_src = std::make_shared(vhost,strApp,strId); + _media_src = std::make_shared(tuple); getRtpRing()->setDelegate(_media_src); } @@ -46,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 2d7ab925..f6a13fda 100644 --- a/src/Rtsp/RtspPlayer.cpp +++ b/src/Rtsp/RtspPlayer.cpp @@ -8,49 +8,44 @@ * may be found in the AUTHORS file in the root of the source tree. */ -#include -#include -#include -#include -#include "Common/config.h" #include "RtspPlayer.h" -#include "Util/MD5.h" -#include "Util/base64.h" +#include "Common/config.h" #include "Rtcp/Rtcp.h" #include "Rtcp/RtcpContext.h" -#include "RtspMediaSource.h" #include "RtspDemuxer.h" +#include "RtspMediaSource.h" #include "RtspPlayerImp.h" +#include "Util/MD5.h" +#include "Util/base64.h" +#include +#include +#include +#include using namespace toolkit; using namespace std; namespace mediakit { -enum PlayType { - type_play = 0, - type_pause, - type_seek, - type_speed -}; +enum PlayType { type_play = 0, type_pause, type_seek, type_speed }; -RtspPlayer::RtspPlayer(const EventPoller::Ptr &poller) : TcpClient(poller){ -} +RtspPlayer::RtspPlayer(const EventPoller::Ptr &poller) + : TcpClient(poller) {} RtspPlayer::~RtspPlayer(void) { - DebugL << endl; + DebugL; } -void RtspPlayer::sendTeardown(){ +void RtspPlayer::sendTeardown() { if (alive()) { - if (!_content_base.empty()) { - sendRtspRequest("TEARDOWN", _content_base); + if (!_control_url.empty()) { + sendRtspRequest("TEARDOWN", _control_url); } shutdown(SockException(Err_shutdown, "teardown")); } } -void RtspPlayer::teardown(){ +void RtspPlayer::teardown() { sendTeardown(); _md5_nonce.clear(); _realm.clear(); @@ -69,7 +64,7 @@ void RtspPlayer::teardown(){ _on_response = nullptr; } -void RtspPlayer::play(const string &strUrl){ +void RtspPlayer::play(const string &strUrl) { RtspUrl url; try { url.parse(strUrl); @@ -92,34 +87,37 @@ void RtspPlayer::play(const string &strUrl){ _rtp_type = (Rtsp::eRtpType)(int)(*this)[Client::kRtpType]; DebugL << url._url << " " << (url._user.size() ? url._user : "null") << " " << (url._passwd.size() ? url._passwd : "null") << " " << _rtp_type; - weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + weak_ptr weakSelf = static_pointer_cast(shared_from_this()); float playTimeOutSec = (*this)[Client::kTimeoutMS].as() / 1000.0f; - _play_check_timer.reset(new Timer(playTimeOutSec, [weakSelf]() { - auto strongSelf=weakSelf.lock(); - if(!strongSelf) { + _play_check_timer.reset(new Timer( + playTimeOutSec, + [weakSelf]() { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return false; + } + strongSelf->onPlayResult_l(SockException(Err_timeout, "play rtsp timeout"), false); return false; - } - strongSelf->onPlayResult_l(SockException(Err_timeout,"play rtsp timeout"),false); - return false; - }, getPoller())); + }, + getPoller())); - if(!(*this)[Client::kNetAdapter].empty()){ + if (!(*this)[Client::kNetAdapter].empty()) { setNetAdapter((*this)[Client::kNetAdapter]); } startConnect(url._host, url._port, playTimeOutSec); } -void RtspPlayer::onConnect(const SockException &err){ - if(err.getErrCode() != Err_success) { - onPlayResult_l(err,false); +void RtspPlayer::onConnect(const SockException &err) { + if (err.getErrCode() != Err_success) { + onPlayResult_l(err, false); return; } sendOptions(); } -void RtspPlayer::onRecv(const Buffer::Ptr& buf) { - if(_benchmark_mode && !_play_check_timer){ - //在性能测试模式下,如果rtsp握手完毕后,不再解析rtp包 +void RtspPlayer::onRecv(const Buffer::Ptr &buf) { + if (_benchmark_mode && !_play_check_timer) { + // 在性能测试模式下,如果rtsp握手完毕后,不再解析rtp包 _rtp_recv_ticker.resetTime(); return; } @@ -131,22 +129,22 @@ void RtspPlayer::onRecv(const Buffer::Ptr& buf) { } } -void RtspPlayer::onErr(const SockException &ex) { - //定时器_pPlayTimer为空后表明握手结束了 - onPlayResult_l(ex,!_play_check_timer); +void RtspPlayer::onError(const SockException &ex) { + // 定时器_pPlayTimer为空后表明握手结束了 + onPlayResult_l(ex, !_play_check_timer); } // from live555 bool RtspPlayer::handleAuthenticationFailure(const string ¶msStr) { - if(!_realm.empty()){ - //已经认证过了 + if (!_realm.empty()) { + // 已经认证过了 return false; } char *realm = new char[paramsStr.size()]; char *nonce = new char[paramsStr.size()]; char *stale = new char[paramsStr.size()]; - onceToken token(nullptr,[&](){ + onceToken token(nullptr, [&]() { delete[] realm; delete[] nonce; delete[] stale; @@ -169,41 +167,43 @@ bool RtspPlayer::handleAuthenticationFailure(const string ¶msStr) { return false; } -bool RtspPlayer::handleResponse(const string &cmd, const Parser &parser){ +bool RtspPlayer::handleResponse(const string &cmd, const Parser &parser) { string authInfo = parser["WWW-Authenticate"]; - //发送DESCRIBE命令后的回复 - if ((parser.Url() == "401") && handleAuthenticationFailure(authInfo)) { + // 发送DESCRIBE命令后的回复 + if ((parser.status() == "401") && handleAuthenticationFailure(authInfo)) { sendOptions(); return false; } - if(parser.Url() == "302" || parser.Url() == "301"){ + if (parser.status() == "302" || parser.status() == "301") { auto newUrl = parser["Location"]; - if(newUrl.empty()){ + if (newUrl.empty()) { throw std::runtime_error("未找到Location字段(跳转url)"); } play(newUrl); return false; } - if (parser.Url() != "200") { - throw std::runtime_error(StrPrinter << cmd << ":" << parser.Url() << " " << parser.Tail() << endl); + if (parser.status() != "200") { + throw std::runtime_error(StrPrinter << cmd << ":" << parser.status() << " " << parser.statusStr() << endl); } return true; } -void RtspPlayer::handleResDESCRIBE(const Parser& parser) { +void RtspPlayer::handleResDESCRIBE(const Parser &parser) { if (!handleResponse("DESCRIBE", parser)) { return; } _content_base = parser["Content-Base"]; - if(_content_base.empty()){ + if (_content_base.empty()) { _content_base = _play_url; } if (_content_base.back() == '/') { _content_base.pop_back(); } - //解析sdp - SdpParser sdpParser(parser.Content()); + // 解析sdp + SdpParser sdpParser(parser.content()); + + _control_url = sdpParser.getControlUrl(_content_base); string sdp; auto play_track = (TrackType)((int)(*this)[Client::kPlayTrack] - 1); @@ -224,16 +224,16 @@ void RtspPlayer::handleResDESCRIBE(const Parser& parser) { } _rtcp_context.clear(); for (auto &track : _sdp_track) { - if(track->_pt != 0xff){ - setPayloadType(_rtcp_context.size(),track->_pt); + if (track->_pt != 0xff) { + setPayloadType(_rtcp_context.size(), track->_pt); } _rtcp_context.emplace_back(std::make_shared()); } sendSetup(0); } -//有必要的情况下创建udp端口 -void RtspPlayer::createUdpSockIfNecessary(int track_idx){ +// 有必要的情况下创建udp端口 +void RtspPlayer::createUdpSockIfNecessary(int track_idx) { auto &rtpSockRef = _rtp_sock[track_idx]; auto &rtcpSockRef = _rtcp_sock[track_idx]; if (!rtpSockRef || !rtcpSockRef) { @@ -244,41 +244,38 @@ void RtspPlayer::createUdpSockIfNecessary(int track_idx){ } } -//发送SETUP命令 +// 发送SETUP命令 void RtspPlayer::sendSetup(unsigned int track_idx) { _on_response = std::bind(&RtspPlayer::handleResSETUP, this, placeholders::_1, track_idx); auto &track = _sdp_track[track_idx]; auto control_url = track->getControlUrl(_content_base); switch (_rtp_type) { case Rtsp::RTP_TCP: { - sendRtspRequest("SETUP", control_url, {"Transport", StrPrinter << "RTP/AVP/TCP;unicast;interleaved=" << track->_type * 2 << "-" << track->_type * 2 + 1}); - } - break; + sendRtspRequest( + "SETUP", control_url, { "Transport", StrPrinter << "RTP/AVP/TCP;unicast;interleaved=" << track->_type * 2 << "-" << track->_type * 2 + 1 }); + } break; case Rtsp::RTP_MULTICAST: { - sendRtspRequest("SETUP", control_url, {"Transport", "RTP/AVP;multicast"}); - } - break; + sendRtspRequest("SETUP", control_url, { "Transport", "RTP/AVP;multicast" }); + } break; case Rtsp::RTP_UDP: { createUdpSockIfNecessary(track_idx); - sendRtspRequest("SETUP", control_url, {"Transport", - StrPrinter << "RTP/AVP;unicast;client_port=" - << _rtp_sock[track_idx]->get_local_port() << "-" - << _rtcp_sock[track_idx]->get_local_port()}); - } - break; - default: - break; + sendRtspRequest( + "SETUP", control_url, + { "Transport", + StrPrinter << "RTP/AVP;unicast;client_port=" << _rtp_sock[track_idx]->get_local_port() << "-" << _rtcp_sock[track_idx]->get_local_port() }); + } break; + default: break; } } void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int track_idx) { - if (parser.Url() != "200") { - throw std::runtime_error(StrPrinter << "SETUP:" << parser.Url() << " " << parser.Tail() << endl); + if (parser.status() != "200") { + throw std::runtime_error(StrPrinter << "SETUP:" << parser.status() << " " << parser.statusStr() << endl); } if (track_idx == 0) { _session_id = parser["Session"]; _session_id.append(";"); - _session_id = FindField(_session_id.data(), nullptr, ";"); + _session_id = findSubString(_session_id.data(), nullptr, ";"); } auto strTransport = parser["Transport"]; @@ -292,9 +289,9 @@ void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int track_idx) { auto transport_map = Parser::parseArgs(strTransport, ";", "="); RtspSplitter::enableRecvRtp(_rtp_type == Rtsp::RTP_TCP); string ssrc = transport_map["ssrc"]; - if(!ssrc.empty()){ + if (!ssrc.empty()) { sscanf(ssrc.data(), "%x", &_sdp_track[track_idx]->_ssrc); - } else{ + } else { _sdp_track[track_idx]->_ssrc = 0; } @@ -310,47 +307,47 @@ void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int track_idx) { auto &pRtcpSockRef = _rtcp_sock[track_idx]; if (_rtp_type == Rtsp::RTP_MULTICAST) { - //udp组播 - auto multiAddr = transport_map["destination"]; + // udp组播 + auto multiAddr = transport_map["destination"]; pRtpSockRef = createSocket(); - //目前组播仅支持ipv4 + // 目前组播仅支持ipv4 if (!pRtpSockRef->bindUdpSock(rtp_port, "0.0.0.0")) { pRtpSockRef.reset(); throw std::runtime_error("open udp sock err"); } auto fd = pRtpSockRef->rawFD(); - if (-1 == SockUtil::joinMultiAddrFilter(fd, multiAddr.data(), get_peer_ip().data(),get_local_ip().data())) { - SockUtil::joinMultiAddr(fd, multiAddr.data(),get_local_ip().data()); + if (-1 == SockUtil::joinMultiAddrFilter(fd, multiAddr.data(), get_peer_ip().data(), get_local_ip().data())) { + SockUtil::joinMultiAddr(fd, multiAddr.data(), get_local_ip().data()); } - //设置rtcp发送端口 + // 设置rtcp发送端口 pRtcpSockRef = createSocket(); - //目前组播仅支持ipv4 + // 目前组播仅支持ipv4 if (!pRtcpSockRef->bindUdpSock(0, "0.0.0.0")) { - //分配端口失败 + // 分配端口失败 throw runtime_error("open udp socket failed"); } - //设置发送地址和发送端口 + // 设置发送地址和发送端口 auto dst = SockUtil::make_sockaddr(get_peer_ip().data(), rtcp_port); pRtcpSockRef->bindPeerAddr((struct sockaddr *)&(dst)); } else { createUdpSockIfNecessary(track_idx); - //udp单播 + // udp单播 auto dst = SockUtil::make_sockaddr(get_peer_ip().data(), rtp_port); pRtpSockRef->bindPeerAddr((struct sockaddr *)&(dst)); - //发送rtp打洞包 + // 发送rtp打洞包 pRtpSockRef->send("\xce\xfa\xed\xfe", 4); dst = SockUtil::make_sockaddr(get_peer_ip().data(), rtcp_port); - //设置rtcp发送目标,为后续发送rtcp做准备 + // 设置rtcp发送目标,为后续发送rtcp做准备 pRtcpSockRef->bindPeerAddr((struct sockaddr *)&(dst)); } auto peer_ip = get_peer_ip(); - weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); - //设置rtp over udp接收回调处理函数 - pRtpSockRef->setOnRead([peer_ip, track_idx, weakSelf](const Buffer::Ptr &buf, struct sockaddr *addr , int addr_len) { + weak_ptr weakSelf = static_pointer_cast(shared_from_this()); + // 设置rtp over udp接收回调处理函数 + pRtpSockRef->setOnRead([peer_ip, track_idx, weakSelf](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { auto strongSelf = weakSelf.lock(); if (!strongSelf) { return; @@ -359,13 +356,13 @@ void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int track_idx) { WarnL << "收到其他地址的rtp数据:" << SockUtil::inet_ntoa(addr); return; } - strongSelf->handleOneRtp(track_idx, strongSelf->_sdp_track[track_idx]->_type, - strongSelf->_sdp_track[track_idx]->_samplerate, (uint8_t *) buf->data(), buf->size()); + strongSelf->handleOneRtp( + track_idx, strongSelf->_sdp_track[track_idx]->_type, strongSelf->_sdp_track[track_idx]->_samplerate, (uint8_t *)buf->data(), buf->size()); }); - if(pRtcpSockRef) { - //设置rtcp over udp接收回调处理函数 - pRtcpSockRef->setOnRead([peer_ip, track_idx, weakSelf](const Buffer::Ptr &buf, struct sockaddr *addr , int addr_len) { + if (pRtcpSockRef) { + // 设置rtcp over udp接收回调处理函数 + pRtcpSockRef->setOnRead([peer_ip, track_idx, weakSelf](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { auto strongSelf = weakSelf.lock(); if (!strongSelf) { return; @@ -374,68 +371,66 @@ void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int track_idx) { WarnL << "收到其他地址的rtcp数据:" << SockUtil::inet_ntoa(addr); return; } - strongSelf->onRtcpPacket(track_idx, strongSelf->_sdp_track[track_idx], (uint8_t *) buf->data(), buf->size()); + strongSelf->onRtcpPacket(track_idx, strongSelf->_sdp_track[track_idx], (uint8_t *)buf->data(), buf->size()); }); } } if (track_idx < _sdp_track.size() - 1) { - //需要继续发送SETUP命令 + // 需要继续发送SETUP命令 sendSetup(track_idx + 1); return; } - //所有setup命令发送完毕 - //发送play命令 + // 所有setup命令发送完毕 + // 发送play命令 sendPause(type_play, 0); } void RtspPlayer::sendDescribe() { - //发送DESCRIBE命令后处理函数:handleResDESCRIBE + // 发送DESCRIBE命令后处理函数:handleResDESCRIBE _on_response = std::bind(&RtspPlayer::handleResDESCRIBE, this, placeholders::_1); - sendRtspRequest("DESCRIBE", _play_url, {"Accept", "application/sdp"}); + sendRtspRequest("DESCRIBE", _play_url, { "Accept", "application/sdp" }); } -void RtspPlayer::sendOptions(){ - _on_response = [this](const Parser& parser){ +void RtspPlayer::sendOptions() { + _on_response = [this](const Parser &parser) { if (!handleResponse("OPTIONS", parser)) { return; } - //获取服务器支持的命令 + // 获取服务器支持的命令 _supported_cmd.clear(); - auto public_val = split(parser["Public"],","); - for(auto &cmd : public_val){ + auto public_val = split(parser["Public"], ","); + for (auto &cmd : public_val) { trim(cmd); _supported_cmd.emplace(cmd); } - //发送Describe请求,获取sdp + // 发送Describe请求,获取sdp sendDescribe(); }; sendRtspRequest("OPTIONS", _play_url); } -void RtspPlayer::sendKeepAlive(){ +void RtspPlayer::sendKeepAlive() { _on_response = [](const Parser &parser) {}; - if(_supported_cmd.find("GET_PARAMETER") != _supported_cmd.end()){ - //支持GET_PARAMETER,用此命令保活 - sendRtspRequest("GET_PARAMETER", _content_base); - }else{ - //不支持GET_PARAMETER,用OPTIONS命令保活 + if (_supported_cmd.find("GET_PARAMETER") != _supported_cmd.end()) { + // 支持GET_PARAMETER,用此命令保活 + sendRtspRequest("GET_PARAMETER", _control_url); + } else { + // 不支持GET_PARAMETER,用OPTIONS命令保活 sendRtspRequest("OPTIONS", _play_url); } } -void RtspPlayer::sendPause(int type , uint32_t seekMS){ +void RtspPlayer::sendPause(int type, uint32_t seekMS) { _on_response = std::bind(&RtspPlayer::handleResPAUSE, this, placeholders::_1, type); - //开启或暂停rtsp - switch (type){ - case type_pause: - sendRtspRequest("PAUSE", _content_base); - break; + // 开启或暂停rtsp + switch (type) { + case type_pause: sendRtspRequest("PAUSE", _control_url, {}); break; case type_play: - sendRtspRequest("PLAY", _content_base); - break; + // sendRtspRequest("PLAY", _content_base); + // break; case type_seek: - sendRtspRequest("PLAY", _content_base, {"Range",StrPrinter << "npt=" << setiosflags(ios::fixed) << setprecision(2) << seekMS / 1000.0 << "-"}); + sendRtspRequest("PLAY", _control_url, { "Range", StrPrinter << "npt=" << setiosflags(ios::fixed) << setprecision(2) << seekMS / 1000.0 << "-" }); break; default: WarnL << "unknown type : " << type; @@ -449,38 +444,34 @@ void RtspPlayer::pause(bool bPause) { } void RtspPlayer::speed(float speed) { - sendRtspRequest("PLAY", _content_base, {"Scale", StrPrinter << speed}); + sendRtspRequest("PLAY", _control_url, { "Scale", StrPrinter << speed }); } -void RtspPlayer::handleResPAUSE(const Parser& parser,int type) { - if (parser.Url() != "200") { +void RtspPlayer::handleResPAUSE(const Parser &parser, int type) { + if (parser.status() != "200") { switch (type) { - case type_pause: - WarnL << "Pause failed:" << parser.Url() << " " << parser.Tail() << endl; - break; + case type_pause: WarnL << "Pause failed:" << parser.status() << " " << parser.statusStr(); break; case type_play: - WarnL << "Play failed:" << parser.Url() << " " << parser.Tail() << endl; - onPlayResult_l(SockException(Err_shutdown, StrPrinter << "rtsp play failed:" << parser.Url() << " " << parser.Tail() ), !_play_check_timer); - break; - case type_seek: - WarnL << "Seek failed:" << parser.Url() << " " << parser.Tail() << endl; + WarnL << "Play failed:" << parser.status() << " " << parser.statusStr(); + onPlayResult_l(SockException(Err_other, StrPrinter << "rtsp play failed:" << parser.status() << " " << parser.statusStr()), !_play_check_timer); break; + case type_seek: WarnL << "Seek failed:" << parser.status() << " " << parser.statusStr(); break; } return; } if (type == type_pause) { - //暂停成功! + // 暂停成功! _rtp_check_timer.reset(); return; } - //play或seek成功 + // play或seek成功 uint32_t iSeekTo = 0; - //修正时间轴 + // 修正时间轴 auto strRange = parser["Range"]; if (strRange.size()) { - auto strStart = FindField(strRange.data(), "npt=", "-"); + auto strStart = findSubString(strRange.data(), "npt=", "-"); if (strStart == "now") { strStart = "0"; } @@ -492,60 +483,70 @@ void RtspPlayer::handleResPAUSE(const Parser& parser,int type) { } void RtspPlayer::onWholeRtspPacket(Parser &parser) { + if (!start_with(parser.method(), "RTSP")) { + // 不是rtsp回复,忽略 + WarnL << "Not rtsp response: " << parser.method(); + return; + } try { decltype(_on_response) func; _on_response.swap(func); - if(func){ + if (func) { func(parser); } - parser.Clear(); + parser.clear(); } catch (std::exception &err) { - //定时器_pPlayTimer为空后表明握手结束了 - onPlayResult_l(SockException(Err_other, err.what()),!_play_check_timer); + // 定时器_pPlayTimer为空后表明握手结束了 + onPlayResult_l(SockException(Err_other, err.what()), !_play_check_timer); } } void RtspPlayer::onRtpPacket(const char *data, size_t len) { int trackIdx = -1; uint8_t interleaved = data[1]; - if(interleaved %2 == 0){ + if (interleaved % 2 == 0) { trackIdx = getTrackIndexByInterleaved(interleaved); if (trackIdx == -1) { return; } - handleOneRtp(trackIdx, _sdp_track[trackIdx]->_type, _sdp_track[trackIdx]->_samplerate, (uint8_t *)data + RtpPacket::kRtpTcpHeaderSize, len - RtpPacket::kRtpTcpHeaderSize); - }else{ + handleOneRtp( + trackIdx, _sdp_track[trackIdx]->_type, _sdp_track[trackIdx]->_samplerate, (uint8_t *)data + RtpPacket::kRtpTcpHeaderSize, + len - RtpPacket::kRtpTcpHeaderSize); + } else { trackIdx = getTrackIndexByInterleaved(interleaved - 1); if (trackIdx == -1) { return; } - onRtcpPacket(trackIdx, _sdp_track[trackIdx], (uint8_t *) data + RtpPacket::kRtpTcpHeaderSize, len - RtpPacket::kRtpTcpHeaderSize); + onRtcpPacket(trackIdx, _sdp_track[trackIdx], (uint8_t *)data + RtpPacket::kRtpTcpHeaderSize, len - RtpPacket::kRtpTcpHeaderSize); } } -//此处预留rtcp处理函数 -void RtspPlayer::onRtcpPacket(int track_idx, SdpTrack::Ptr &track, uint8_t *data, size_t len){ - auto rtcp_arr = RtcpHeader::loadFromBytes((char *) data, len); +// 此处预留rtcp处理函数 +void RtspPlayer::onRtcpPacket(int track_idx, SdpTrack::Ptr &track, uint8_t *data, size_t len) { + auto rtcp_arr = RtcpHeader::loadFromBytes((char *)data, len); for (auto &rtcp : rtcp_arr) { _rtcp_context[track_idx]->onRtcp(rtcp); - if ((RtcpType) rtcp->pt == RtcpType::RTCP_SR) { - auto sr = (RtcpSR *) (rtcp); - //设置rtp时间戳与ntp时间戳的对应关系 + if ((RtcpType)rtcp->pt == RtcpType::RTCP_SR) { + auto sr = (RtcpSR *)(rtcp); + // 设置rtp时间戳与ntp时间戳的对应关系 setNtpStamp(track_idx, sr->rtpts, sr->getNtpUnixStampMS()); } } } -void RtspPlayer::onRtpSorted(RtpPacket::Ptr rtppt, int trackidx){ +void RtspPlayer::onRtpSorted(RtpPacket::Ptr rtppt, int trackidx) { _stamp[trackidx] = rtppt->getStampMS(); _rtp_recv_ticker.resetTime(); onRecvRTP(std::move(rtppt), _sdp_track[trackidx]); } -float RtspPlayer::getPacketLossRate(TrackType type) const{ +float RtspPlayer::getPacketLossRate(TrackType type) const { size_t lost = 0, expected = 0; try { auto track_idx = getTrackIndexByTrackType(type); + if (_rtcp_context.empty()) { + return 0; + } auto ctx = _rtcp_context[track_idx]; lost = ctx->getLost(); expected = ctx->getExpectedPackets(); @@ -558,32 +559,33 @@ float RtspPlayer::getPacketLossRate(TrackType type) const{ if (!expected) { return 0; } - return (float) (double(lost) / double(expected)); + return (float)(double(lost) / double(expected)); } -uint32_t RtspPlayer::getProgressMilliSecond() const{ - return MAX(_stamp[0],_stamp[1]); +uint32_t RtspPlayer::getProgressMilliSecond() const { + return MAX(_stamp[0], _stamp[1]); } void RtspPlayer::seekToMilliSecond(uint32_t ms) { - sendPause(type_seek,ms); + sendPause(type_seek, ms); } void RtspPlayer::sendRtspRequest(const string &cmd, const string &url, const std::initializer_list &header) { string key; StrCaseMap header_map; int i = 0; - for(auto &val : header){ - if(++i % 2 == 0){ - header_map.emplace(key,val); - }else{ + for (auto &val : header) { + if (++i % 2 == 0) { + header_map.emplace(key, val); + } else { key = val; } } - sendRtspRequest(cmd,url,header_map); + + sendRtspRequest(cmd, url, header_map); } -void RtspPlayer::sendRtspRequest(const string &cmd, const string &url,const StrCaseMap &header_const) { +void RtspPlayer::sendRtspRequest(const string &cmd, const string &url, const StrCaseMap &header_const) { auto header = header_const; header.emplace("CSeq", StrPrinter << _cseq_send++); header.emplace("User-Agent", kServerName); @@ -625,43 +627,45 @@ void RtspPlayer::sendRtspRequest(const string &cmd, const string &url,const StrC _StrPrinter printer; printer << cmd << " " << url << " RTSP/1.0\r\n"; - for (auto &pr : header){ + + TraceL << cmd << " "<< url; + for (auto &pr : header) { printer << pr.first << ": " << pr.second << "\r\n"; } printer << "\r\n"; SockSender::send(std::move(printer)); } -void RtspPlayer::onBeforeRtpSorted(const RtpPacket::Ptr &rtp, int track_idx){ +void RtspPlayer::onBeforeRtpSorted(const RtpPacket::Ptr &rtp, int track_idx) { auto &rtcp_ctx = _rtcp_context[track_idx]; rtcp_ctx->onRtp(rtp->getSeq(), rtp->getStamp(), rtp->ntp_stamp, rtp->sample_rate, rtp->size() - RtpPacket::kRtpTcpHeaderSize); auto &ticker = _rtcp_send_ticker[track_idx]; if (ticker.elapsedTime() < 3 * 1000) { - //时间未到 + // 时间未到 return; } auto &rtcp_flag = _send_rtcp[track_idx]; - //每3秒发送一次心跳,rtcp与rtsp信令轮流心跳,该特性用于兼容issue:642 - //有些rtsp服务器需要rtcp保活,有些需要发送信令保活 + // 每3秒发送一次心跳,rtcp与rtsp信令轮流心跳,该特性用于兼容issue:642 + // 有些rtsp服务器需要rtcp保活,有些需要发送信令保活 - //发送信令保活 + // 发送信令保活 if (!rtcp_flag) { if (track_idx == 0) { sendKeepAlive(); } ticker.resetTime(); - //下次发送rtcp保活 + // 下次发送rtcp保活 rtcp_flag = true; return; } - //发送rtcp + // 发送rtcp static auto send_rtcp = [](RtspPlayer *thiz, int index, Buffer::Ptr ptr) { if (thiz->_rtp_type == Rtsp::RTP_TCP) { auto &track = thiz->_sdp_track[index]; - thiz->send(makeRtpOverTcpPrefix((uint16_t) (ptr->size()), track->_interleaved + 1)); + thiz->send(makeRtpOverTcpPrefix((uint16_t)(ptr->size()), track->_interleaved + 1)); thiz->send(std::move(ptr)); } else { thiz->_rtcp_sock[index]->send(std::move(ptr)); @@ -670,55 +674,55 @@ void RtspPlayer::onBeforeRtpSorted(const RtpPacket::Ptr &rtp, int track_idx){ auto ssrc = rtp->getSSRC(); auto rtcp = rtcp_ctx->createRtcpRR(ssrc + 1, ssrc); - auto rtcp_sdes = RtcpSdes::create({kServerName}); - rtcp_sdes->chunks.type = (uint8_t) SdesType::RTCP_SDES_CNAME; + auto rtcp_sdes = RtcpSdes::create({ kServerName }); + rtcp_sdes->chunks.type = (uint8_t)SdesType::RTCP_SDES_CNAME; rtcp_sdes->chunks.ssrc = htonl(ssrc); send_rtcp(this, track_idx, std::move(rtcp)); send_rtcp(this, track_idx, RtcpHeader::toBuffer(rtcp_sdes)); ticker.resetTime(); - //下次发送信令保活 + // 下次发送信令保活 rtcp_flag = false; } -void RtspPlayer::onPlayResult_l(const SockException &ex , bool handshake_done) { +void RtspPlayer::onPlayResult_l(const SockException &ex, bool handshake_done) { if (ex.getErrCode() == Err_shutdown) { - //主动shutdown的,不触发回调 + // 主动shutdown的,不触发回调 return; } WarnL << ex.getErrCode() << " " << ex.what(); if (!handshake_done) { - //开始播放阶段 + // 开始播放阶段 _play_check_timer.reset(); onPlayResult(ex); - //是否为性能测试模式 + // 是否为性能测试模式 _benchmark_mode = (*this)[Client::kBenchmarkMode].as(); } else if (ex) { - //播放成功后异常断开回调 + // 播放成功后异常断开回调 onShutdown(ex); } else { - //恢复播放 + // 恢复播放 onResume(); } if (!ex) { - //播放成功,恢复rtp接收超时定时器 + // 播放成功,恢复rtp接收超时定时器 _rtp_recv_ticker.resetTime(); auto timeoutMS = (*this)[Client::kMediaTimeoutMS].as(); - weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + weak_ptr weakSelf = static_pointer_cast(shared_from_this()); auto lam = [weakSelf, timeoutMS]() { auto strongSelf = weakSelf.lock(); if (!strongSelf) { return false; } if (strongSelf->_rtp_recv_ticker.elapsedTime() > timeoutMS) { - //接收rtp媒体数据包超时 + // 接收rtp媒体数据包超时 strongSelf->onPlayResult_l(SockException(Err_timeout, "receive rtp timeout"), true); return false; } return true; }; - //创建rtp数据接收超时检测定时器 + // 创建rtp数据接收超时检测定时器 _rtp_check_timer = std::make_shared(timeoutMS / 2000.0f, lam, getPoller()); } else { sendTeardown(); @@ -747,13 +751,12 @@ int RtspPlayer::getTrackIndexByTrackType(TrackType track_type) const { if (_sdp_track.size() == 1) { return 0; } - throw SockException(Err_shutdown, StrPrinter << "no such track with type:" << getTrackString(track_type)); + throw SockException(Err_other, StrPrinter << "no such track with type:" << getTrackString(track_type)); } /////////////////////////////////////////////////// // RtspPlayerImp -float RtspPlayerImp::getDuration() const -{ +float RtspPlayerImp::getDuration() const { return _demuxer ? _demuxer->getDuration() : 0; } @@ -770,13 +773,11 @@ void RtspPlayerImp::addTrackCompleted() { } } -std::vector RtspPlayerImp::getTracks(bool ready /*= true*/) const -{ +std::vector RtspPlayerImp::getTracks(bool ready /*= true*/) const { return _demuxer ? _demuxer->getTracks(ready) : Super::getTracks(ready); } -bool RtspPlayerImp::onCheckSDP(const std::string &sdp) -{ +bool RtspPlayerImp::onCheckSDP(const std::string &sdp) { _rtsp_media_src = std::dynamic_pointer_cast(_media_src); if (_rtsp_media_src) { _rtsp_media_src->setSdp(sdp); @@ -788,7 +789,7 @@ bool RtspPlayerImp::onCheckSDP(const std::string &sdp) } void RtspPlayerImp::onRecvRTP(RtpPacket::Ptr rtp, const SdpTrack::Ptr &track) { - //rtp解复用时可以判断是否为关键帧起始位置 + // rtp解复用时可以判断是否为关键帧起始位置 auto key_pos = _demuxer->inputRtp(rtp); if (_rtsp_media_src) { _rtsp_media_src->onWrite(std::move(rtp), key_pos); diff --git a/src/Rtsp/RtspPlayer.h b/src/Rtsp/RtspPlayer.h index 0bd101cd..2b4f34c5 100644 --- a/src/Rtsp/RtspPlayer.h +++ b/src/Rtsp/RtspPlayer.h @@ -84,7 +84,7 @@ protected: /////////////TcpClient override///////////// void onConnect(const toolkit::SockException &err) override; void onRecv(const toolkit::Buffer::Ptr &buf) override; - void onErr(const toolkit::SockException &ex) override; + void onError(const toolkit::SockException &ex) override; private: void onPlayResult_l(const toolkit::SockException &ex , bool handshake_done); @@ -129,6 +129,7 @@ private: std::string _session_id; uint32_t _cseq_send = 1; std::string _content_base; + std::string _control_url; Rtsp::eRtpType _rtp_type = Rtsp::RTP_TCP; //当前rtp时间戳 diff --git a/src/Rtsp/RtspPlayerImp.h b/src/Rtsp/RtspPlayerImp.h index 9243696a..ad0eaa12 100644 --- a/src/Rtsp/RtspPlayerImp.h +++ b/src/Rtsp/RtspPlayerImp.h @@ -28,7 +28,7 @@ public: RtspPlayerImp(const toolkit::EventPoller::Ptr &poller) : Super(poller) {} ~RtspPlayerImp() override { - DebugL << std::endl; + DebugL; } float getProgress() const override { diff --git a/src/Rtsp/RtspPusher.cpp b/src/Rtsp/RtspPusher.cpp index 04d0d1a1..4748fb15 100644 --- a/src/Rtsp/RtspPusher.cpp +++ b/src/Rtsp/RtspPusher.cpp @@ -26,7 +26,7 @@ RtspPusher::RtspPusher(const EventPoller::Ptr &poller, const RtspMediaSource::Pt RtspPusher::~RtspPusher() { teardown(); - DebugL << endl; + DebugL; } void RtspPusher::sendTeardown(){ @@ -80,7 +80,7 @@ void RtspPusher::publish(const string &url_str) { DebugL << url._url << " " << (url._user.size() ? url._user : "null") << " " << (url._passwd.size() ? url._passwd : "null") << " " << _rtp_type; - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); float publish_timeout_sec = (*this)[Client::kTimeoutMS].as() / 1000.0f; _publish_timer.reset(new Timer(publish_timeout_sec, [weak_self]() { auto strong_self = weak_self.lock(); @@ -118,7 +118,7 @@ void RtspPusher::onPublishResult_l(const SockException &ex, bool handshake_done) } } -void RtspPusher::onErr(const SockException &ex) { +void RtspPusher::onError(const SockException &ex) { //定时器_pPublishTimer为空后表明握手结束了 onPublishResult_l(ex, !_publish_timer); } @@ -147,7 +147,7 @@ void RtspPusher::onWholeRtspPacket(Parser &parser) { if (func) { func(parser); } - parser.Clear(); + parser.clear(); } void RtspPusher::onRtpPacket(const char *data, size_t len) { @@ -188,11 +188,11 @@ void RtspPusher::sendAnnounce() { void RtspPusher::handleResAnnounce(const Parser &parser) { string authInfo = parser["WWW-Authenticate"]; //发送DESCRIBE命令后的回复 - if ((parser.Url() == "401") && handleAuthenticationFailure(authInfo)) { + if ((parser.status() == "401") && handleAuthenticationFailure(authInfo)) { sendAnnounce(); return; } - if (parser.Url() == "302") { + if (parser.status() == "302") { auto newUrl = parser["Location"]; if (newUrl.empty()) { throw std::runtime_error("未找到Location字段(跳转url)"); @@ -200,8 +200,8 @@ void RtspPusher::handleResAnnounce(const Parser &parser) { publish(newUrl); return; } - if (parser.Url() != "200") { - throw std::runtime_error(StrPrinter << "ANNOUNCE:" << parser.Url() << " " << parser.Tail()); + if (parser.status() != "200") { + throw std::runtime_error(StrPrinter << "ANNOUNCE:" << parser.status() << " " << parser.statusStr()); } _content_base = parser["Content-Base"]; @@ -285,19 +285,19 @@ void RtspPusher::sendSetup(unsigned int track_idx) { } void RtspPusher::handleResSetup(const Parser &parser, unsigned int track_idx) { - if (parser.Url() != "200") { - throw std::runtime_error(StrPrinter << "SETUP:" << parser.Url() << " " << parser.Tail() << endl); + if (parser.status() != "200") { + throw std::runtime_error(StrPrinter << "SETUP:" << parser.status() << " " << parser.statusStr() << endl); } if (track_idx == 0) { _session_id = parser["Session"]; _session_id.append(";"); - _session_id = FindField(_session_id.data(), nullptr, ";"); + _session_id = findSubString(_session_id.data(), nullptr, ";"); } auto transport = parser["Transport"]; if (transport.find("TCP") != string::npos || transport.find("interleaved") != string::npos) { _rtp_type = Rtsp::RTP_TCP; - string interleaved = FindField(FindField((transport + ";").data(), "interleaved=", ";").data(), NULL, "-"); + string interleaved = findSubString(findSubString((transport + ";").data(), "interleaved=", ";").data(), NULL, "-"); _track_vec[track_idx]->_interleaved = atoi(interleaved.data()); } else if (transport.find("multicast") != string::npos) { throw std::runtime_error("SETUP rtsp pusher can not support multicast!"); @@ -305,9 +305,9 @@ void RtspPusher::handleResSetup(const Parser &parser, unsigned int track_idx) { _rtp_type = Rtsp::RTP_UDP; createUdpSockIfNecessary(track_idx); const char *strPos = "server_port="; - auto port_str = FindField((transport + ";").data(), strPos, ";"); - uint16_t rtp_port = atoi(FindField(port_str.data(), NULL, "-").data()); - uint16_t rtcp_port = atoi(FindField(port_str.data(), "-", NULL).data()); + auto port_str = findSubString((transport + ";").data(), strPos, ";"); + uint16_t rtp_port = atoi(findSubString(port_str.data(), NULL, "-").data()); + uint16_t rtcp_port = atoi(findSubString(port_str.data(), "-", NULL).data()); auto &rtp_sock = _rtp_sock[track_idx]; auto &rtcp_sock = _rtcp_sock[track_idx]; @@ -320,7 +320,7 @@ void RtspPusher::handleResSetup(const Parser &parser, unsigned int track_idx) { rtcp_sock->bindPeerAddr((struct sockaddr *)&(rtcpto)); auto peer_ip = get_peer_ip(); - weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + weak_ptr weakSelf = static_pointer_cast(shared_from_this()); if(rtcp_sock) { //设置rtcp over udp接收回调处理函数 rtcp_sock->setOnRead([peer_ip, track_idx, weakSelf](const Buffer::Ptr &buf, struct sockaddr *addr , int addr_len) { @@ -451,7 +451,7 @@ void RtspPusher::sendRecord() { src->pause(false); _rtsp_reader = src->getRing()->attach(getPoller()); - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); _rtsp_reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pkt) { auto strong_self = weak_self.lock(); if (!strong_self) { diff --git a/src/Rtsp/RtspPusher.h b/src/Rtsp/RtspPusher.h index 2cddd61f..63353368 100644 --- a/src/Rtsp/RtspPusher.h +++ b/src/Rtsp/RtspPusher.h @@ -35,7 +35,7 @@ protected: //for Tcpclient override void onRecv(const toolkit::Buffer::Ptr &buf) override; void onConnect(const toolkit::SockException &err) override; - void onErr(const toolkit::SockException &ex) override; + void onError(const toolkit::SockException &ex) override; //RtspSplitter override void onWholeRtspPacket(Parser &parser) override ; diff --git a/src/Rtsp/RtspSession.cpp b/src/Rtsp/RtspSession.cpp index e2d07372..01677526 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); } //如果是主动关闭的,那么不延迟注销 @@ -129,12 +129,12 @@ void RtspSession::onRecv(const Buffer::Ptr &buf) { } void RtspSession::onWholeRtspPacket(Parser &parser) { - string method = parser.Method(); //提取出请求命令字 + string method = parser.method(); //提取出请求命令字 _cseq = atoi(parser["CSeq"].data()); if (_content_base.empty() && method != "GET") { - _content_base = parser.Url(); - _media_info.parse(parser.FullUrl()); - _media_info._schema = RTSP_SCHEMA; + _content_base = parser.url(); + _media_info.parse(parser.fullUrl()); + _media_info.schema = RTSP_SCHEMA; } using rtsp_request_handler = void (RtspSession::*)(const Parser &parser); @@ -161,7 +161,7 @@ void RtspSession::onWholeRtspPacket(Parser &parser) { } (this->*(it->second))(parser); - parser.Clear(); + parser.clear(); } void RtspSession::onRtpPacket(const char *data, size_t len) { @@ -188,7 +188,7 @@ void RtspSession::onRtcpPacket(int track_idx, SdpTrack::Ptr &track, const char * } ssize_t RtspSession::getContentLength(Parser &parser) { - if(parser.Method() == "POST"){ + if(parser.method() == "POST"){ //http post请求的content数据部分是base64编码后的rtsp请求信令包 return remainDataSize(); } @@ -201,7 +201,7 @@ void RtspSession::handleReq_Options(const Parser &parser) { } void RtspSession::handleReq_ANNOUNCE(const Parser &parser) { - auto full_url = parser.FullUrl(); + auto full_url = parser.fullUrl(); _content_base = full_url; if (end_with(full_url, ".sdp")) { //去除.sdp后缀,防止EasyDarwin推流器强制添加.sdp后缀 @@ -209,15 +209,16 @@ void RtspSession::handleReq_ANNOUNCE(const Parser &parser) { _media_info.parse(full_url); } - if (_media_info._app.empty() || _media_info._streamid.empty()) { + if (_media_info.app.empty() || _media_info.stream.empty()) { //推流rtsp url必须最少两级(rtsp://host/app/stream_id),不允许莫名其妙的推流url static constexpr auto err = "rtsp推流url非法,最少确保两级rtsp url"; sendRtspResponse("403 Forbidden", {"Content-Type", "text/plain"}, err); throw SockException(Err_shutdown, StrPrinter << err << ":" << full_url); } + auto onResPushSrc = [this, parser, full_url](ProtocolOption option) { - SdpParser sdpParser(parser.Content()); + SdpParser sdpParser(parser.content()); _sessionid = makeRandStr(12); _sdp_track = sdpParser.getAvailableTrack(); if (_sdp_track.empty()) { @@ -233,18 +234,17 @@ void RtspSession::handleReq_ANNOUNCE(const Parser &parser) { } if (!_push_src) { - _push_src = std::make_shared(_media_info._vhost, _media_info._app, _media_info._streamid); + _push_src = std::make_shared(_media_info); // 获取所有权 _push_src_ownership = _push_src->getOwnership(); _push_src->setProtocolOption(option); - _push_src->setSdp(parser.Content()); + _push_src->setSdp(parser.content()); } - _push_src->setListener(dynamic_pointer_cast(shared_from_this())); + _push_src->setListener(static_pointer_cast(shared_from_this())); _continue_push_ms = option.continue_push_ms; sendRtspResponse("200 OK"); }; - auto onRes = [this, parser, full_url,onResPushSrc](const string &err, const ProtocolOption &option) { if (!err.empty()) { sendRtspResponse("401 Unauthorized", { "Content-Type", "text/plain" }, err); @@ -253,7 +253,7 @@ void RtspSession::handleReq_ANNOUNCE(const Parser &parser) { } assert(!_push_src); - auto src = MediaSource::find(RTSP_SCHEMA, _media_info._vhost, _media_info._app, _media_info._streamid); + auto src = MediaSource::find(_media_info); auto push_failed = (bool)src; RtspMediaSource::Ptr rtsp_src; RtspMediaSourceImp::Ptr rtsp_src_imp; @@ -299,7 +299,7 @@ void RtspSession::handleReq_ANNOUNCE(const Parser &parser) { return; } }else{ - auto listener = rtsp_src->getListener(true); + auto listener = rtsp_src->getListener(); //TraceL<(listener.lock()); if(muxer){ @@ -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()); @@ -374,7 +374,7 @@ void RtspSession::handleReq_RECORD(const Parser &parser){ } void RtspSession::emitOnPlay(){ - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); //url鉴权回调 auto onRes = [weak_self](const string &err) { auto strong_self = weak_self.lock(); @@ -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(""); @@ -413,7 +413,7 @@ void RtspSession::emitOnPlay(){ void RtspSession::handleReq_Describe(const Parser &parser) { //该请求中的认证信息 auto authorization = parser["Authorization"]; - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); //rtsp专属鉴权是否开启事件回调 onGetRealm invoker = [weak_self, authorization](const string &realm) { auto strong_self = weak_self.lock(); @@ -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(""); } @@ -451,7 +451,7 @@ void RtspSession::handleReq_Describe(const Parser &parser) { } void RtspSession::onAuthSuccess() { - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); MediaSource::findAsync(_media_info, weak_self.lock(), [weak_self](const MediaSource::Ptr &src){ auto strong_self = weak_self.lock(); if(!strong_self){ @@ -520,7 +520,7 @@ void RtspSession::onAuthBasic(const string &realm, const string &auth_base64) { } auto user = user_pwd_vec[0]; auto pwd = user_pwd_vec[1]; - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); onAuth invoker = [pwd, realm, weak_self](bool encrypted, const string &good_pwd) { auto strong_self = weak_self.lock(); if (!strong_self) { @@ -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事件!"; //但是我们还是忽略认证以便完成播放 @@ -613,7 +613,7 @@ void RtspSession::onAuthDigest(const string &realm,const string &auth_md5){ } }; - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); onAuth invoker = [realInvoker,weak_self](bool encrypted,const string &good_pwd){ auto strong_self = weak_self.lock(); if(!strong_self){ @@ -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事件!"; //但是我们还是忽略认证以便完成播放 @@ -644,8 +644,8 @@ void RtspSession::onAuthUser(const string &realm,const string &authorization){ return; } //请求中包含认证信息 - auto authType = FindField(authorization.data(),NULL," "); - auto authStr = FindField(authorization.data()," ",NULL); + auto authType = findSubString(authorization.data(), NULL, " "); + auto authStr = findSubString(authorization.data(), " ", NULL); if(authType.empty() || authStr.empty()){ //认证信息格式不合法,回复401 Unauthorized onAuthFailed(realm,"can not find auth type or auth string"); @@ -677,25 +677,50 @@ void RtspSession::send_SessionNotFound() { void RtspSession::handleReq_Setup(const Parser &parser) { //处理setup命令,该函数可能进入多次 - int trackIdx = getTrackIndexByControlUrl(parser.FullUrl()); + int trackIdx = getTrackIndexByControlUrl(parser.fullUrl()); SdpTrack::Ptr &trackRef = _sdp_track[trackIdx]; if (trackRef->_inited) { //已经初始化过该Track throw SockException(Err_shutdown, "can not setup one track twice"); } - trackRef->_inited = true; //现在初始化 - if(_rtp_type == Rtsp::RTP_Invalid){ - auto &strTransport = parser["Transport"]; - if(strTransport.find("TCP") != string::npos){ - _rtp_type = Rtsp::RTP_TCP; - }else if(strTransport.find("multicast") != string::npos){ - _rtp_type = Rtsp::RTP_MULTICAST; - }else{ - _rtp_type = Rtsp::RTP_UDP; + static auto getRtpTypeStr = [](const int type) { + switch (type) + { + case Rtsp::RTP_TCP: + return "TCP"; + case Rtsp::RTP_UDP: + return "UDP"; + case Rtsp::RTP_MULTICAST: + return "MULTICAST"; + default: + return "Invalid"; } + }; + + if (_rtp_type == Rtsp::RTP_Invalid) { + auto &strTransport = parser["Transport"]; + auto rtpType = Rtsp::RTP_Invalid; + if (strTransport.find("TCP") != string::npos) { + rtpType = Rtsp::RTP_TCP; + } else if (strTransport.find("multicast") != string::npos) { + rtpType = Rtsp::RTP_MULTICAST; + } else { + rtpType = Rtsp::RTP_UDP; + } + //检查RTP传输类型限制 + GET_CONFIG(int, transport, Rtsp::kRtpTransportType); + if (transport != Rtsp::RTP_Invalid && transport != rtpType) { + WarnL << "rtsp client setup transport " << getRtpTypeStr(rtpType) << " but config force transport " << getRtpTypeStr(transport); + //配置限定RTSP传输方式,但是客户端握手方式不一致,返回461 + sendRtspResponse("461 Unsupported transport"); + return; + } + _rtp_type = rtpType; } + trackRef->_inited = true; //现在初始化 + //允许接收rtp、rtcp包 RtspSplitter::enableRecvRtp(_rtp_type == Rtsp::RTP_TCP); @@ -739,17 +764,17 @@ void RtspSession::handleReq_Setup(const Parser &parser) { _rtcp_socks[trackIdx] = pr.second; //设置客户端内网端口信息 - string strClientPort = FindField(parser["Transport"].data(), "client_port=", NULL); - uint16_t ui16RtpPort = atoi(FindField(strClientPort.data(), NULL, "-").data()); - uint16_t ui16RtcpPort = atoi(FindField(strClientPort.data(), "-", NULL).data()); + string strClientPort = findSubString(parser["Transport"].data(), "client_port=", NULL); + uint16_t ui16RtpPort = atoi(findSubString(strClientPort.data(), NULL, "-").data()); + uint16_t ui16RtcpPort = atoi(findSubString(strClientPort.data(), "-", NULL).data()); auto peerAddr = SockUtil::make_sockaddr(get_peer_ip().data(), ui16RtpPort); //设置rtp发送目标地址 - pr.first->bindPeerAddr((struct sockaddr *) (&peerAddr)); + pr.first->bindPeerAddr((struct sockaddr *) (&peerAddr), 0, true); //设置rtcp发送目标地址 peerAddr = SockUtil::make_sockaddr(get_peer_ip().data(), ui16RtcpPort); - pr.second->bindPeerAddr((struct sockaddr *) (&peerAddr)); + pr.second->bindPeerAddr((struct sockaddr *) (&peerAddr), 0, true); //尝试获取客户端nat映射地址 startListenPeerUdpData(trackIdx); @@ -766,12 +791,12 @@ void RtspSession::handleReq_Setup(const Parser &parser) { break; case Rtsp::RTP_MULTICAST: { if(!_multicaster){ - _multicaster = RtpMultiCaster::get(*this, get_local_ip(), _media_info._vhost, _media_info._app, _media_info._streamid, _multicast_ip, _multicast_video_port, _multicast_audio_port); + _multicaster = RtpMultiCaster::get(*this, get_local_ip(), _media_info.vhost, _media_info.app, _media_info.stream, _multicast_ip, _multicast_video_port, _multicast_audio_port); if (!_multicaster) { send_NotAcceptable(); throw SockException(Err_shutdown, "can not get a available udp multicast socket"); } - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); _multicaster->setDetachCB(this, [weak_self]() { auto strong_self = weak_self.lock(); if(!strong_self) { @@ -834,7 +859,7 @@ void RtspSession::handleReq_Play(const Parser &parser) { if (!strRange.empty()) { //这是seek操作 res_header.emplace("Range", strRange); - auto strStart = FindField(strRange.data(), "npt=", "-"); + auto strStart = findSubString(strRange.data(), "npt=", "-"); if (strStart == "now") { strStart = "0"; } @@ -879,9 +904,13 @@ void RtspSession::handleReq_Play(const Parser &parser) { setSocketFlags(); if (!_play_reader && _rtp_type != Rtsp::RTP_MULTICAST) { - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + 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) { @@ -929,7 +958,7 @@ void RtspSession::handleReq_Get(const Parser &parser) { //注册http getter,以便http poster绑定 lock_guard lock(g_mtxGetter); - g_mapGetter[_http_x_sessioncookie] = dynamic_pointer_cast(shared_from_this()); + g_mapGetter[_http_x_sessioncookie] = static_pointer_cast(shared_from_this()); } void RtspSession::handleReq_Post(const Parser &parser) { @@ -964,9 +993,9 @@ void RtspSession::handleReq_Post(const Parser &parser) { }); }; - if(!parser.Content().empty()){ + if(!parser.content().empty()){ //http poster后面的粘包 - _on_recv(std::make_shared(parser.Content())); + _on_recv(std::make_shared(parser.content())); } sendRtspResponse("200 OK", @@ -1022,7 +1051,7 @@ void RtspSession::onRcvPeerUdpData(int interleaved, const Buffer::Ptr &buf, cons } void RtspSession::startListenPeerUdpData(int track_idx) { - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); auto peer_ip = get_peer_ip(); auto onUdpData = [weak_self,peer_ip](const Buffer::Ptr &buf, struct sockaddr *peer_addr, int interleaved){ auto strong_self = weak_self.lock(); @@ -1194,7 +1223,7 @@ MediaOriginType RtspSession::getOriginType(MediaSource &sender) const{ } string RtspSession::getOriginUrl(MediaSource &sender) const { - return _media_info._full_url; + return _media_info.full_url; } std::shared_ptr RtspSession::getOriginSock(MediaSource &sender) const { diff --git a/src/Rtsp/RtspSplitter.cpp b/src/Rtsp/RtspSplitter.cpp index a29e1249..f5d1e792 100644 --- a/src/Rtsp/RtspSplitter.cpp +++ b/src/Rtsp/RtspSplitter.cpp @@ -61,11 +61,11 @@ ssize_t RtspSplitter::onRecvHeader(const char *data, size_t len) { onRtpPacket(data,len); return 0; } - _parser.Parse(data); + _parser.parse(data, len); auto ret = getContentLength(_parser); if(ret == 0){ onWholeRtspPacket(_parser); - _parser.Clear(); + _parser.clear(); } return ret; } @@ -73,7 +73,7 @@ ssize_t RtspSplitter::onRecvHeader(const char *data, size_t len) { void RtspSplitter::onRecvContent(const char *data, size_t len) { _parser.setContent(string(data,len)); onWholeRtspPacket(_parser); - _parser.Clear(); + _parser.clear(); } void RtspSplitter::enableRecvRtp(bool enable) { diff --git a/src/Rtsp/UDPServer.cpp b/src/Rtsp/UDPServer.cpp index 4f648ddf..bb9e4642 100644 --- a/src/Rtsp/UDPServer.cpp +++ b/src/Rtsp/UDPServer.cpp @@ -82,10 +82,12 @@ void UDPServer::onRecv(int interleaved, const Buffer::Ptr &buf, struct sockaddr* return; } auto &ref = it0->second; - for (auto it1 = ref.begin(); it1 != ref.end(); ++it1) { + for (auto it1 = ref.begin(); it1 != ref.end();) { auto &func = it1->second; if (!func(interleaved, buf, peer_addr)) { it1 = ref.erase(it1); + } else { + ++it1; } } if (ref.size() == 0) { diff --git a/src/Shell/ShellSession.cpp b/src/Shell/ShellSession.cpp index 81e18d7b..85c682ee 100644 --- a/src/Shell/ShellSession.cpp +++ b/src/Shell/ShellSession.cpp @@ -120,7 +120,7 @@ inline void ShellSession::pleaseInputPasswd() { _loginInterceptor=nullptr; }; - weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + weak_ptr weakSelf = static_pointer_cast(shared_from_this()); Broadcast::AuthInvoker invoker = [weakSelf,onAuth](const string &errMessage){ auto strongSelf = weakSelf.lock(); if(!strongSelf){ @@ -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 1f4503a4..b691d15f 100644 --- a/src/TS/TSMediaSource.h +++ b/src/TS/TSMediaSource.h @@ -39,10 +39,7 @@ public: using RingDataType = std::shared_ptr >; using RingType = toolkit::RingBuffer; - TSMediaSource(const std::string &vhost, - const std::string &app, - const std::string &stream_id, - int ring_size = TS_GOP_SIZE) : MediaSource(TS_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {} + TSMediaSource(const MediaTuple& tuple, int ring_size = TS_GOP_SIZE): MediaSource(TS_SCHEMA, tuple), _ring_size(ring_size) {} ~TSMediaSource() override { flush(); } @@ -53,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); } @@ -92,7 +89,7 @@ public: private: void createRing(){ - std::weak_ptr weak_self = std::dynamic_pointer_cast(shared_from_this()); + std::weak_ptr weak_self = std::static_pointer_cast(shared_from_this()); _ring = std::make_shared(_ring_size, [weak_self](int size) { auto strong_self = weak_self.lock(); if (!strong_self) { diff --git a/src/TS/TSMediaSourceMuxer.h b/src/TS/TSMediaSourceMuxer.h index 392b3236..389ea901 100644 --- a/src/TS/TSMediaSourceMuxer.h +++ b/src/TS/TSMediaSourceMuxer.h @@ -21,12 +21,9 @@ class TSMediaSourceMuxer final : public MpegMuxer, public MediaSourceEventInterc public: using Ptr = std::shared_ptr; - TSMediaSourceMuxer(const std::string &vhost, - const std::string &app, - const std::string &stream_id, - const ProtocolOption &option) : MpegMuxer(false) { + TSMediaSourceMuxer(const MediaTuple& tuple, const ProtocolOption &option) : MpegMuxer(false) { _option = option; - _media_src = std::make_shared(vhost, app, stream_id); + _media_src = std::make_shared(tuple); } ~TSMediaSourceMuxer() override { MpegMuxer::flush(); }; diff --git a/srt/SrtSession.cpp b/srt/SrtSession.cpp index 5f73d92f..9f83ddde 100644 --- a/srt/SrtSession.cpp +++ b/srt/SrtSession.cpp @@ -100,7 +100,7 @@ void SrtSession::onRecv(const Buffer::Ptr &buffer) { } if (_transport) { - _transport->setSession(shared_from_this()); + _transport->setSession(static_pointer_cast(shared_from_this())); } InfoP(this); } diff --git a/srt/SrtTransportImp.cpp b/srt/SrtTransportImp.cpp index b91505f1..56d6bd8a 100644 --- a/srt/SrtTransportImp.cpp +++ b/srt/SrtTransportImp.cpp @@ -16,9 +16,11 @@ SrtTransportImp::~SrtTransportImp() { // 流量统计事件广播 GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); if (_total_bytes >= iFlowThreshold * 1024) { - NoticeCenter::Instance().emitEvent( - Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, !_is_pusher, - static_cast(*this)); + try { + NOTICE_EMIT(BroadcastFlowReportArgs, Broadcast::kBroadcastFlowReport, _media_info, _total_bytes, duration, !_is_pusher, *this); + } catch (std::exception &ex) { + WarnL << "Exception occurred: " << ex.what(); + } } } @@ -35,7 +37,7 @@ void SrtTransportImp::onHandShakeFinished(std::string &streamid, struct sockaddr return; } - auto params = Parser::parseArgs(_media_info._param_strs); + auto params = Parser::parseArgs(_media_info.param_strs); if (params["m"] == "publish") { _is_pusher = true; _decoder = DecoderImp::createDecoder(DecoderImp::decoder_ts, this); @@ -52,7 +54,7 @@ bool SrtTransportImp::parseStreamid(std::string &streamid) { if (!toolkit::start_with(streamid, "#!::")) { return false; } - _media_info._schema = SRT_SCHEMA; + _media_info.schema = SRT_SCHEMA; std::string real_streamid = streamid.substr(4); std::string vhost, app, stream_name; @@ -70,10 +72,10 @@ bool SrtTransportImp::parseStreamid(std::string &streamid) { app = tmps[0]; stream_name = tmps[1]; } else { - if (_media_info._param_strs.empty()) { - _media_info._param_strs = it.first + "=" + it.second; + if (_media_info.param_strs.empty()) { + _media_info.param_strs = it.first + "=" + it.second; } else { - _media_info._param_strs += "&" + it.first + "=" + it.second; + _media_info.param_strs += "&" + it.first + "=" + it.second; } } } @@ -82,15 +84,15 @@ bool SrtTransportImp::parseStreamid(std::string &streamid) { } if (vhost != "") { - _media_info._vhost = vhost; + _media_info.vhost = vhost; } else { - _media_info._vhost = DEFAULT_VHOST; + _media_info.vhost = DEFAULT_VHOST; } - _media_info._app = app; - _media_info._streamid = stream_name; + _media_info.app = app; + _media_info.stream = stream_name; - TraceL << " mediainfo=" << _media_info.shortUrl() << " params=" << _media_info._param_strs; + TraceL << " mediainfo=" << _media_info.shortUrl() << " params=" << _media_info.param_strs; return true; } @@ -136,7 +138,7 @@ mediakit::MediaOriginType SrtTransportImp::getOriginType(mediakit::MediaSource & // 获取媒体源url或者文件路径 std::string SrtTransportImp::getOriginUrl(mediakit::MediaSource &sender) const { - return _media_info._full_url; + return _media_info.full_url; } // 获取媒体源客户端相关信息 @@ -157,9 +159,7 @@ void SrtTransportImp::emitOnPublish() { return; } if (err.empty()) { - strong_self->_muxer = std::make_shared(strong_self->_media_info._vhost, - strong_self->_media_info._app, - strong_self->_media_info._streamid,0.0f, + strong_self->_muxer = std::make_shared(strong_self->_media_info,0.0f, option); strong_self->_muxer->setMediaListener(strong_self); strong_self->doCachedFunc(); @@ -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(); } @@ -207,7 +204,7 @@ void SrtTransportImp::emitOnPlay() { void SrtTransportImp::doPlay() { // 异步查找直播流 MediaInfo info = _media_info; - info._schema = TS_SCHEMA; + info.schema = TS_SCHEMA; std::weak_ptr weak_self = static_pointer_cast(shared_from_this()); MediaSource::findAsync(info, getSession(), [weak_self](const MediaSource::Ptr &src) { auto strong_self = weak_self.lock(); @@ -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) { @@ -281,7 +282,7 @@ uint16_t SrtTransportImp::get_local_port() { } std::string SrtTransportImp::getIdentifier() const { - return _media_info._streamid; + return _media_info.stream; } bool SrtTransportImp::inputFrame(const Frame::Ptr &frame) { @@ -293,7 +294,7 @@ bool SrtTransportImp::inputFrame(const Frame::Ptr &frame) { auto diff = _type_to_stamp[TrackType::TrackVideo].getRelativeStamp() - _type_to_stamp[TrackType::TrackAudio].getRelativeStamp(); if(std::abs(diff) > 5000){ // 超过5s,应该同步 TODO - WarnL << _media_info._full_url<<" video or audio not sync : "<getCodecName()<<" dts "<dts()<<" pts "<pts(); diff --git a/tests/README.md b/tests/README.md index 448ae9ec..81516128 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,6 +1,6 @@ 此目录下的所有.cpp文件将被编译成可执行程序(不包含此目录下的子目录). 子目录DeviceHK为海康IPC的适配程序,需要先下载海康的SDK才能编译, -由于操作麻烦,所以仅把源码放在这仅供参考. +由于操作麻烦,所以仅把源码放在这里仅供参考. - test_benchmark.cpp diff --git a/tests/test_bench_forward.cpp b/tests/test_bench_forward.cpp new file mode 100644 index 00000000..634d7b8e --- /dev/null +++ b/tests/test_bench_forward.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include +#include "Util/logger.h" +#include "Util/onceToken.h" +#include "Util/CMD.h" +#include "Util/File.h" +#include "Common/config.h" +#include "Common/Parser.h" +#include "Rtsp/Rtsp.h" +#include "Thread/WorkThreadPool.h" +#include "Pusher/MediaPusher.h" +#include "Player/PlayerProxy.h" + +using namespace std; +using namespace toolkit; +using namespace mediakit; + +class CMD_main : public CMD { +public: + CMD_main() { + _parser.reset(new OptionParser(nullptr)); + + (*_parser) << Option('l',/*该选项简称,如果是\x00则说明无简称*/ + "level",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + to_string(LTrace).data(),/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "日志等级,LTrace~LError(0~4)",/*该选项说明文字*/ + nullptr); + + + (*_parser) << Option('t',/*该选项简称,如果是\x00则说明无简称*/ + "threads",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + to_string(thread::hardware_concurrency()).data(),/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "启动事件触发线程数",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('i',/*该选项简称,如果是\x00则说明无简称*/ + "inputs",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "/tmp/inputs.txt",/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "拉流地址配置文件,支持rtmp、rtsp, hls,多个地址以 \"换行符\" 分割",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('o',/*该选项简称,如果是\x00则说明无简称*/ + "outputs",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "/tmp/outputs.txt",/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "推流地址配置文件,支持rtmp、rtsp,多个地址以 \"换行符\" 分割",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('d',/*该选项简称,如果是\x00则说明无简称*/ + "delay",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "50",/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "启动拉流代理间隔,单位毫秒",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('m',/*该选项简称,如果是\x00则说明无简称*/ + "merge",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "300",/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "合并写毫秒,合并写能提高性能",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('T',/*该选项简称,如果是\x00则说明无简称*/ + "rtp",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + to_string((int) (Rtsp::RTP_TCP)).data(),/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "rtsp拉流方式,支持tcp/udp/multicast:0/1/2",/*该选项说明文字*/ + nullptr); + + } + + ~CMD_main() override {} + + const char *description() const override { + return "主程序命令参数"; + } +}; + + +//此程序为zlm的转推性能测试工具,用于测试拉流代理转推性能 +int main(int argc, char *argv[]) { + CMD_main cmd_main; + try { + cmd_main.operator()(argc, argv); + } catch (ExitException &) { + return 0; + } catch (std::exception &ex) { + cout << ex.what() << endl; + return -1; + } + + int threads = cmd_main["threads"]; + LogLevel logLevel = (LogLevel) cmd_main["level"].as(); + logLevel = MIN(MAX(logLevel, LTrace), LError); + auto in_urls = cmd_main["inputs"]; + auto out_urls = cmd_main["outputs"]; + auto rtp_type = cmd_main["rtp"].as(); + auto delay_ms = cmd_main["delay"].as(); + auto merge_ms = cmd_main["merge"].as(); + + //设置日志 + Logger::Instance().add(std::make_shared("ConsoleChannel", logLevel)); + //启动异步日志线程 + Logger::Instance().setWriter(std::make_shared()); + + //设置线程数 + EventPollerPool::setPoolSize(threads); + WorkThreadPool::setPoolSize(threads); + + //设置合并写 + mINI::Instance()[General::kMergeWriteMS] = merge_ms; + + + std::vector input_urls; + std::vector output_urls; + + auto parse_urls = [&]() { + // 获取输入源列表 + auto inputs = ::split(toolkit::File::loadFile(in_urls.c_str()), "\n"); + for(auto &url : inputs){ + if(url.empty() || url.find("://") == std::string::npos) { + continue; + } + auto input_url = ::trim(url); + input_urls.emplace_back(input_url); + } + // 获取输出源列表 + auto outputs = ::split(toolkit::File::loadFile(out_urls.c_str()), "\n"); + for(auto &url : outputs){ + if(url.empty() || url.find("://") == std::string::npos){ + continue; + } + auto output_url = ::trim(url); + output_urls.emplace_back(output_url); + } + + if(input_urls.empty() || input_urls.size() != output_urls.size()){ + return -1; + } + + for(size_t i = 0; i < input_urls.size(); i++){ + InfoL << "拉流地址: " << input_urls[i] << ",推流地址:" << output_urls[i]; + } + return 0; + }; + + if (0 != parse_urls()){ + cout << "请检查inputs和outputs文件是否正确!" << endl; + return -1; + } + + //推流器map + recursive_mutex mtx; + unordered_map proxy_map; + unordered_map pusher_map; + + auto add_pusher = [&](const MediaSource::Ptr &src, const string &url, int index) { + auto pusher = std::make_shared(src); + pusher->setOnCreateSocket([](const EventPoller::Ptr &poller) { + //socket关闭互斥锁,提高性能 + return std::make_shared(poller, false); + }); + //设置推流失败监听 + pusher->setOnPublished([&mtx, &pusher_map, index](const SockException &ex) { + if (ex) { + //推流失败,移除之 + lock_guard lck(mtx); + pusher_map.erase(index); + } + }); + //设置推流中途断开监听 + pusher->setOnShutdown([&mtx, &pusher_map, index](const SockException &ex) { + //推流中途失败,移除之 + lock_guard lck(mtx); + pusher_map.erase(index); + }); + //设置rtsp推流方式(在rtsp推流时有效) + (*pusher)[Client::kRtpType] = rtp_type; + pusher->publish(url); + //保持对象不销毁 + lock_guard lck(mtx); + pusher_map.emplace(index, std::move(pusher)); + //休眠后再启动下一个推流,防止短时间海量链接 + if (delay_ms > 0) { + usleep(1000 * delay_ms); + } + }; + + // 添加转推任务 + for(size_t i = 0; i < input_urls.size(); i++) { + //休眠一秒打印 + sleep(1); + auto schema = findSubString(output_urls[i].data(), nullptr, "://"); + if (schema != RTSP_SCHEMA && schema != RTMP_SCHEMA) { + cout << "推流协议只支持rtsp或rtmp!" << endl; + return -1; + } + ProtocolOption option; + option.enable_ts = false; + option.enable_fmp4 = false; + option.enable_hls = false; + option.enable_mp4 = false; + option.modify_stamp = (int)ProtocolOption::kModifyStampRelative; + //添加拉流代理 + auto proxy = std::make_shared(DEFAULT_VHOST, "app", std::to_string(i), option, -1, nullptr, 1); + //开始拉流代理 + proxy->play(input_urls[i]); + proxy_map.emplace(i, std::move(proxy)); + } + + // 设置退出信号 + static bool exit_flag = false; + signal(SIGINT, [](int) { exit_flag = true; }); + while (!exit_flag) { + //休眠一秒打印 + sleep(1); + + size_t alive_pusher = 0; + { + lock_guard lck(mtx); + alive_pusher = pusher_map.size(); + } + InfoL << "在线转推器个数:" << alive_pusher; + + auto find_pusher = [&](int index){ + lock_guard lck(mtx); + auto it = pusher_map.find(index); + if (it == pusher_map.end()){ + return false; + } + return true; + }; + for(size_t i = 0; i < input_urls.size(); i++) { + if (!find_pusher(i)){ + auto input_url = input_urls[i]; + auto src = MediaSource::find(RTMP_SCHEMA, DEFAULT_VHOST, "app", std::to_string(i), false); + if (src != nullptr){ + add_pusher(src,output_urls[i],i); + } + } + } + } + + return 0; +} \ No newline at end of file diff --git a/tests/test_bench_push.cpp b/tests/test_bench_push.cpp index d0929a8e..b34f8b99 100644 --- a/tests/test_bench_push.cpp +++ b/tests/test_bench_push.cpp @@ -124,7 +124,7 @@ int main(int argc, char *argv[]) { auto delay_ms = cmd_main["delay"].as(); auto pusher_count = cmd_main["count"].as(); auto merge_ms = cmd_main["merge"].as(); - auto schema = FindField(out_url.data(), nullptr, "://"); + auto schema = findSubString(out_url.data(), nullptr, "://"); if (schema != RTSP_SCHEMA && schema != RTMP_SCHEMA) { cout << "推流协议只支持rtsp或rtmp!" << endl; return -1; diff --git a/tests/test_flv.cpp b/tests/test_flv.cpp new file mode 100644 index 00000000..ffed816f --- /dev/null +++ b/tests/test_flv.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2016 The ZLMediaKit project authors. All Rights Reserved. + * + * This file is part of ZLMediaKit(https://github.com/xia-chu/ZLMediaKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include "Util/logger.h" +#include "Util/util.h" +#include "Network/TcpServer.h" +#include "Common/config.h" +#include "Rtsp/RtspSession.h" +#include "Rtmp/RtmpSession.h" +#include "Http/HttpSession.h" +#include "Rtmp/FlvSplitter.h" +#include "Rtmp/RtmpMediaSourceImp.h" + +using namespace std; +using namespace toolkit; +using namespace mediakit; + +class FlvSplitterImp : public FlvSplitter { +public: + FlvSplitterImp() { + _src = std::make_shared(MediaTuple{DEFAULT_VHOST, "live", "test"}); + } + ~FlvSplitterImp() override = default; + + void inputData(const char *data, size_t len, uint32_t &stamp) { + FlvSplitter::input(data, len); + stamp = _stamp; + } + +protected: + void onRecvFlvHeader(const FLVHeader &header) override { + } + + bool onRecvMetadata(const AMFValue &metadata) override { + _src->setMetaData(metadata); + _src->setProtocolOption(ProtocolOption()); + return true; + } + + void onRecvRtmpPacket(RtmpPacket::Ptr packet) override { + _stamp = packet->time_stamp; + _src->onWrite(std::move(packet)); + } + +private: + uint32_t _stamp; + RtmpMediaSourceImp::Ptr _src; +}; + +static bool loadFile(const char *path){ + FILE *fp = fopen(path, "rb"); + if (!fp) { + WarnL << "open file failed:" << path; + return false; + } + + char buffer[64 * 1024]; + uint32_t timeStamp_last = 0; + size_t len; + size_t total_size = 0; + FlvSplitterImp flv; + while (true) { + len = fread(buffer, 1, sizeof(buffer), fp); + if (len <= 0) { + break; + } + total_size += len; + uint32_t timeStamp; + flv.inputData(buffer, len, timeStamp); + auto diff = timeStamp - timeStamp_last; + if (diff > 0) { + usleep(diff * 1000); + } else { + usleep(1 * 1000); + } + timeStamp_last = timeStamp; + } + WarnL << total_size / 1024 << "KB"; + fclose(fp); + return true; +} + +int main(int argc,char *argv[]) { + //设置日志 + Logger::Instance().add(std::make_shared("ConsoleChannel")); + //启动异步日志线程 + Logger::Instance().setWriter(std::make_shared()); + loadIniConfig((exeDir() + "config.ini").data()); + TcpServer::Ptr rtspSrv(new TcpServer()); + TcpServer::Ptr rtmpSrv(new TcpServer()); + TcpServer::Ptr httpSrv(new TcpServer()); + rtspSrv->start(554);//默认554 + rtmpSrv->start(1935);//默认1935 + httpSrv->start(80);//默认80 + + if (argc == 2) + loadFile(argv[1]); + else + ErrorL << "parameter error."; + return 0; +} + + diff --git a/tests/test_httpApi.cpp b/tests/test_httpApi.cpp index c03531c6..081311db 100644 --- a/tests/test_httpApi.cpp +++ b/tests/test_httpApi.cpp @@ -43,7 +43,7 @@ void initEventListener(){ static onceToken s_token([](){ NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpRequest,[](BroadcastHttpRequestArgs){ //const Parser &parser,HttpSession::HttpResponseInvoker &invoker,bool &consumed - if(strstr(parser.Url().data(),"/api/") != parser.Url().data()){ + if(strstr(parser.url().data(),"/api/") != parser.url().data()){ return; } //url以"/api/起始,说明是http api" @@ -51,11 +51,11 @@ void initEventListener(){ _StrPrinter printer; ////////////////method//////////////////// - printer << "\r\nmethod:\r\n\t" << parser.Method(); + printer << "\r\nmethod:\r\n\t" << parser.method(); ////////////////url///////////////// - printer << "\r\nurl:\r\n\t" << parser.Url(); + printer << "\r\nurl:\r\n\t" << parser.url(); ////////////////protocol///////////////// - printer << "\r\nprotocol:\r\n\t" << parser.Tail(); + printer << "\r\nprotocol:\r\n\t" << parser.protocol(); ///////////////args////////////////// printer << "\r\nargs:\r\n"; for(auto &pr : parser.getUrlArgs()){ @@ -67,7 +67,7 @@ void initEventListener(){ printer << "\t" << pr.first << " : " << pr.second << "\r\n"; } ////////////////content///////////////// - printer << "\r\ncontent:\r\n" << parser.Content(); + printer << "\r\ncontent:\r\n" << parser.content(); auto contentOut = printer << endl; ////////////////我们测算异步回复,当然你也可以同步回复///////////////// @@ -113,7 +113,7 @@ int main(int argc,char *argv[]){ TcpServer::Ptr httpsSrv(new TcpServer()); httpsSrv->start(mINI::Instance()[Http::kSSLPort]);//默认443 - InfoL << "你可以在浏览器输入:http://127.0.0.1/api/my_api?key0=val0&key1=参数1" << endl; + InfoL << "你可以在浏览器输入:http://127.0.0.1/api/my_api?key0=val0&key1=参数1"; sem.wait(); return 0; diff --git a/tests/test_httpClient.cpp b/tests/test_httpClient.cpp index f218318b..9c581a1c 100644 --- a/tests/test_httpClient.cpp +++ b/tests/test_httpClient.cpp @@ -86,9 +86,9 @@ int main(int argc, char *argv[]) { for (auto &pr: parser.getHeader()) { printer << pr.first << ":" << pr.second << "\r\n"; } - InfoL << "status:" << parser.Url() << "\r\n" + InfoL << "status:" << parser.status() << "\r\n" << "header:\r\n" << (printer << endl) - << "\r\nbody:" << parser.Content(); + << "\r\nbody:" << parser.content(); } }); @@ -122,9 +122,9 @@ int main(int argc, char *argv[]) { for (auto &pr: parser.getHeader()) { printer << pr.first << ":" << pr.second << "\r\n"; } - InfoL << "status:" << parser.Url() << "\r\n" + InfoL << "status:" << parser.status() << "\r\n" << "header:\r\n" << (printer << endl) - << "\r\nbody:" << parser.Content(); + << "\r\nbody:" << parser.content(); } }); @@ -159,9 +159,9 @@ int main(int argc, char *argv[]) { for (auto &pr: parser.getHeader()) { printer << pr.first << ":" << pr.second << "\r\n"; } - InfoL << "status:" << parser.Url() << "\r\n" + InfoL << "status:" << parser.status() << "\r\n" << "header:\r\n" << (printer << endl) - << "\r\nbody:" << parser.Content(); + << "\r\nbody:" << parser.content(); } }); diff --git a/tests/test_pusher.cpp b/tests/test_pusher.cpp index adf85199..8e5ca1f7 100644 --- a/tests/test_pusher.cpp +++ b/tests/test_pusher.cpp @@ -88,8 +88,9 @@ int domain(const string &playUrl, const string &pushUrl) { NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaChanged, [pushUrl,poller](BroadcastMediaChangedArgs) { //媒体源"app/stream"已经注册,这时方可新建一个RtmpPusher对象并绑定该媒体源 - if(bRegist && pushUrl.find(sender.getSchema()) == 0){ - createPusher(poller,sender.getSchema(),sender.getVhost(),sender.getApp(), sender.getId(), pushUrl); + if (bRegist && pushUrl.find(sender.getSchema()) == 0) { + auto tuple = sender.getMediaTuple(); + createPusher(poller, sender.getSchema(), tuple.vhost, tuple.app, tuple.stream, pushUrl); } }); diff --git a/tests/test_pusherMp4.cpp b/tests/test_pusherMp4.cpp index ef338344..c28845d4 100644 --- a/tests/test_pusherMp4.cpp +++ b/tests/test_pusherMp4.cpp @@ -114,7 +114,7 @@ int domain(const string &filePath, const string &pushUrl) { auto poller = EventPollerPool::Instance().getPoller(); //vhost/app/stream可以随便自己填,现在不限制app应用名了 - createPusher(poller, FindField(pushUrl.data(), nullptr, "://").substr(0, 4), DEFAULT_VHOST, "live", "stream", filePath, pushUrl); + createPusher(poller, findSubString(pushUrl.data(), nullptr, "://").substr(0, 4), DEFAULT_VHOST, "live", "stream", filePath, pushUrl); //设置退出信号处理函数 static semaphore sem; signal(SIGINT, [](int) { sem.post(); });// 设置退出信号 diff --git a/tests/test_server.cpp b/tests/test_server.cpp index c13b8595..dc123ba2 100644 --- a/tests/test_server.cpp +++ b/tests/test_server.cpp @@ -90,8 +90,8 @@ void initEventListener() { static onceToken s_token([]() { //监听kBroadcastOnGetRtspRealm事件决定rtsp链接是否需要鉴权(传统的rtsp鉴权方案)才能访问 NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastOnGetRtspRealm, [](BroadcastOnGetRtspRealmArgs) { - DebugL << "RTSP是否需要鉴权事件:" << args.getUrl() << " " << args._param_strs; - if (string("1") == args._streamid) { + DebugL << "RTSP是否需要鉴权事件:" << args.getUrl() << " " << args.param_strs; + if (string("1") == args.stream) { // live/1需要认证 //该流需要认证,并且设置realm invoker(REALM); @@ -104,7 +104,7 @@ void initEventListener() { //监听kBroadcastOnRtspAuth事件返回正确的rtsp鉴权用户密码 NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastOnRtspAuth, [](BroadcastOnRtspAuthArgs) { - DebugL << "RTSP播放鉴权:" << args.getUrl() << " " << args._param_strs; + DebugL << "RTSP播放鉴权:" << args.getUrl() << " " << args.param_strs; DebugL << "RTSP用户:" << user_name << (must_no_encrypt ? " Base64" : " MD5") << " 方式登录"; string user = user_name; //假设我们异步读取数据库 @@ -134,14 +134,14 @@ void initEventListener() { //监听rtsp/rtmp推流事件,返回结果告知是否有推流权限 NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) { - DebugL << "推流鉴权:" << args.getUrl() << " " << args._param_strs; + DebugL << "推流鉴权:" << args.getUrl() << " " << args.param_strs; invoker("", ProtocolOption());//鉴权成功 //invoker("this is auth failed message");//鉴权失败 }); //监听rtsp/rtsps/rtmp/http-flv播放事件,返回结果告知是否有播放权限(rtsp通过kBroadcastOnRtspAuth或此事件都可以实现鉴权) NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaPlayed, [](BroadcastMediaPlayedArgs) { - DebugL << "播放鉴权:" << args.getUrl() << " " << args._param_strs; + DebugL << "播放鉴权:" << args.getUrl() << " " << args.param_strs; invoker("");//鉴权成功 //invoker("this is auth failed message");//鉴权失败 }); @@ -155,9 +155,10 @@ void initEventListener() { //监听rtsp、rtmp源注册或注销事件;此处用于测试rtmp保存为flv录像,保存在http根目录下 NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaChanged, [](BroadcastMediaChangedArgs) { - if (sender.getSchema() == RTMP_SCHEMA && sender.getApp() == "live") { + auto tuple = sender.getMediaTuple(); + if (sender.getSchema() == RTMP_SCHEMA && tuple.app == "live") { lock_guard lck(s_mtxFlvRecorder); - auto key = sender.shortUrl(); + auto key = tuple.shortUrl(); if (bRegist) { DebugL << "开始录制RTMP:" << sender.getUrl(); GET_CONFIG(string, http_root, Http::kRootPath); @@ -182,14 +183,13 @@ void initEventListener() { * 你可以在这个事件触发时再去拉流,这样就可以实现按需拉流 * 拉流成功后,ZLMediaKit会把其立即转发给播放器(最大等待时间约为5秒,如果5秒都未拉流成功,播放器会播放失败) */ - DebugL << "未找到流事件:" << args.getUrl() << " " << args._param_strs; + DebugL << "未找到流事件:" << args.getUrl() << " " << args.param_strs; }); //监听播放或推流结束时消耗流量事件 NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastFlowReport, [](BroadcastFlowReportArgs) { - DebugL << "播放器(推流器)断开连接事件:" << args.getUrl() << " " << args._param_strs - << "\r\n使用流量:" << totalBytes << " bytes,连接时长:" << totalDuration << "秒"; + DebugL << "播放器(推流器)断开连接事件:" << args.getUrl() << " " << args.param_strs << "\r\n使用流量:" << totalBytes << " bytes,连接时长:" << totalDuration << "秒"; }); diff --git a/tests/test_wsClient.cpp b/tests/test_wsClient.cpp index bd3ba0b5..a3819799 100644 --- a/tests/test_wsClient.cpp +++ b/tests/test_wsClient.cpp @@ -32,7 +32,7 @@ protected: DebugL << pBuf->toString(); } //被动断开连接回调 - void onErr(const SockException &ex) override { + void onError(const SockException &ex) override { WarnL << ex; } //tcp连接成功后每2秒触发一次该事件 diff --git a/tests/test_wsServer.cpp b/tests/test_wsServer.cpp index 90dd645e..bd46787a 100644 --- a/tests/test_wsServer.cpp +++ b/tests/test_wsServer.cpp @@ -83,7 +83,7 @@ struct EchoSessionCreator { //返回的Session必须派生于SendInterceptor,可以返回null(拒绝连接) Session::Ptr operator()(const Parser &header, const HttpSession &parent, const Socket::Ptr &pSock) { // return nullptr; - if (header.Url() == "/") { + if (header.url() == "/") { return std::make_shared >(header, parent, pSock); } return std::make_shared >(header, parent, pSock); diff --git a/webrtc/IceServer.cpp b/webrtc/IceServer.cpp index f0f79358..d4530d10 100644 --- a/webrtc/IceServer.cpp +++ b/webrtc/IceServer.cpp @@ -520,7 +520,7 @@ namespace RTC return; this->selectedTuple = storedTuple; - this->lastSelectedTuple = storedTuple->shared_from_this(); + this->lastSelectedTuple = std::static_pointer_cast(storedTuple->shared_from_this()); // Notify the listener. this->listener->OnIceServerSelectedTuple(this, this->selectedTuple); 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 bbdb6274..63f174eb 100644 --- a/webrtc/WebRtcPlayer.cpp +++ b/webrtc/WebRtcPlayer.cpp @@ -47,8 +47,12 @@ void WebRtcPlayer::onStartWebRTC() { playSrc->pause(false); _reader = playSrc->getRing()->attach(getPoller(), true); weak_ptr weak_self = static_pointer_cast(shared_from_this()); - weak_ptr weak_session = getSession(); - _reader->setGetInfoCB([weak_session]() { return weak_session.lock(); }); + weak_ptr weak_session = static_pointer_cast(getSession()); + _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/WebRtcPlayer.h b/webrtc/WebRtcPlayer.h index fbe168b2..02208f6d 100644 --- a/webrtc/WebRtcPlayer.h +++ b/webrtc/WebRtcPlayer.h @@ -21,6 +21,7 @@ public: using Ptr = std::shared_ptr; ~WebRtcPlayer() override = default; static Ptr create(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const MediaInfo &info, bool preferred_tcp = false); + MediaInfo getMediaInfo() { return _media_info; } protected: ///////WebRtcTransportImp override/////// diff --git a/webrtc/WebRtcPusher.cpp b/webrtc/WebRtcPusher.cpp index 8fe6549c..92b4ad04 100644 --- a/webrtc/WebRtcPusher.cpp +++ b/webrtc/WebRtcPusher.cpp @@ -10,13 +10,14 @@ #include "WebRtcPusher.h" #include "Common/config.h" +#include "Rtsp/RtspMediaSourceImp.h" using namespace std; namespace mediakit { WebRtcPusher::Ptr WebRtcPusher::create(const EventPoller::Ptr &poller, - const RtspMediaSourceImp::Ptr &src, + const RtspMediaSource::Ptr &src, const std::shared_ptr &ownership, const MediaInfo &info, const ProtocolOption &option, @@ -30,7 +31,7 @@ WebRtcPusher::Ptr WebRtcPusher::create(const EventPoller::Ptr &poller, } WebRtcPusher::WebRtcPusher(const EventPoller::Ptr &poller, - const RtspMediaSourceImp::Ptr &src, + const RtspMediaSource::Ptr &src, const std::shared_ptr &ownership, const MediaInfo &info, const ProtocolOption &option, @@ -58,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 { @@ -70,7 +74,7 @@ MediaOriginType WebRtcPusher::getOriginType(MediaSource &sender) const { } string WebRtcPusher::getOriginUrl(MediaSource &sender) const { - return _media_info._full_url; + return _media_info.full_url; } std::shared_ptr WebRtcPusher::getOriginSock(MediaSource &sender) const { @@ -95,13 +99,12 @@ void WebRtcPusher::onRecvRtp(MediaTrack &track, const string &rid, RtpPacket::Pt } } else { //视频 + std::lock_guard lock(_mtx); auto &src = _push_src_sim[rid]; if (!src) { - auto stream_id = rid.empty() ? _push_src->getId() : _push_src->getId() + "_" + rid; - auto src_imp = std::make_shared(_push_src->getVhost(), _push_src->getApp(), stream_id); + const auto& stream = _push_src->getMediaTuple().stream; + auto src_imp = _push_src->clone(rid.empty() ? stream : stream + '_' + rid); _push_src_sim_ownership[rid] = src_imp->getOwnership(); - src_imp->setSdp(_push_src->getSdp()); - src_imp->setProtocolOption(_push_src->getProtocolOption()); src_imp->setListener(static_pointer_cast(shared_from_this())); src = src_imp; } @@ -126,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()); } } @@ -146,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); } @@ -156,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 f00e0483..8d309395 100644 --- a/webrtc/WebRtcPusher.h +++ b/webrtc/WebRtcPusher.h @@ -12,7 +12,7 @@ #define ZLMEDIAKIT_WEBRTCPUSHER_H #include "WebRtcTransport.h" -#include "Rtsp/RtspMediaSourceImp.h" +#include "Rtsp/RtspMediaSource.h" namespace mediakit { @@ -20,18 +20,20 @@ class WebRtcPusher : public WebRtcTransportImp, public MediaSourceEvent { public: using Ptr = std::shared_ptr; ~WebRtcPusher() override = default; - static Ptr create(const EventPoller::Ptr &poller, const RtspMediaSourceImp::Ptr &src, + 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/////// @@ -51,7 +53,7 @@ protected: float getLossRate(MediaSource &sender,TrackType type) override; private: - WebRtcPusher(const EventPoller::Ptr &poller, const RtspMediaSourceImp::Ptr &src, + WebRtcPusher(const EventPoller::Ptr &poller, const RtspMediaSource::Ptr &src, const std::shared_ptr &ownership, const MediaInfo &info, const ProtocolOption &option, bool preferred_tcp); private: @@ -61,10 +63,11 @@ private: //媒体相关元数据 MediaInfo _media_info; //推流的rtsp源 - RtspMediaSourceImp::Ptr _push_src; + RtspMediaSource::Ptr _push_src; //推流所有权 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/WebRtcSession.cpp b/webrtc/WebRtcSession.cpp index 6216802c..ee3051c0 100644 --- a/webrtc/WebRtcSession.cpp +++ b/webrtc/WebRtcSession.cpp @@ -54,7 +54,7 @@ WebRtcSession::WebRtcSession(const Socket::Ptr &sock) : Session(sock) { WebRtcSession::~WebRtcSession() = default; void WebRtcSession::attachServer(const Server &server) { - _server = std::dynamic_pointer_cast(const_cast(server).shared_from_this()); + _server = std::static_pointer_cast(const_cast(server).shared_from_this()); } void WebRtcSession::onRecv_l(const char *data, size_t len) { @@ -108,7 +108,7 @@ void WebRtcSession::onError(const SockException &err) { if (!_transport) { return; } - auto self = shared_from_this(); + auto self = static_pointer_cast(shared_from_this()); auto transport = std::move(_transport); getPoller()->async([transport, self]() mutable { //延时减引用,防止使用transport对象时,销毁对象 diff --git a/webrtc/WebRtcSession.h b/webrtc/WebRtcSession.h index f70d5e74..9cf7343c 100644 --- a/webrtc/WebRtcSession.h +++ b/webrtc/WebRtcSession.h @@ -35,6 +35,9 @@ public: void onManager() override; static EventPoller::Ptr queryPoller(const Buffer::Ptr &buffer); +protected: + WebRtcTransportImp::Ptr _transport; + private: //// HttpRequestSplitter override //// ssize_t onRecvHeader(const char *data, size_t len) override; @@ -47,7 +50,6 @@ private: bool _find_transport = true; Ticker _ticker; std::weak_ptr _server; - WebRtcTransportImp::Ptr _transport; }; }// namespace mediakit diff --git a/webrtc/WebRtcTransport.cpp b/webrtc/WebRtcTransport.cpp index 1d1431c9..4ec13e6f 100644 --- a/webrtc/WebRtcTransport.cpp +++ b/webrtc/WebRtcTransport.cpp @@ -23,6 +23,8 @@ #include "WebRtcPlayer.h" #include "WebRtcPusher.h" +#include "Rtsp/RtspMediaSourceImp.h" + #define RTP_SSRC_OFFSET 1 #define RTX_SSRC_OFFSET 2 #define RTP_CNAME "zlmediakit-rtp" @@ -113,6 +115,13 @@ const string &WebRtcTransport::getIdentifier() const { return _identifier; } +const std::string& WebRtcTransport::deleteRandStr() const { + if (_delete_rand_str.empty()) { + _delete_rand_str = makeRandStr(32); + } + return _delete_rand_str; +} + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void WebRtcTransport::OnIceServerSendStunPacket( @@ -220,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) { @@ -230,7 +252,7 @@ void WebRtcTransport::sendSockData(const char *buf, size_t len, RTC::TransportTu Session::Ptr WebRtcTransport::getSession() const { auto tuple = _ice_server->GetSelectedTuple(true); - return tuple ? tuple->shared_from_this() : nullptr; + return tuple ? static_pointer_cast(tuple->shared_from_this()) : nullptr; } void WebRtcTransport::sendRtcpRemb(uint32_t ssrc, size_t bit_rate) { @@ -314,7 +336,7 @@ void WebRtcTransport::inputSockData(char *buf, int len, RTC::TransportTuple *tup if (RTC::StunPacket::IsStun((const uint8_t *)buf, len)) { std::unique_ptr packet(RTC::StunPacket::Parse((const uint8_t *)buf, len)); if (!packet) { - WarnL << "parse stun error" << std::endl; + WarnL << "parse stun error"; return; } _ice_server->ProcessStunPacket(packet.get(), tuple); @@ -506,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 @@ -555,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; } @@ -689,7 +713,7 @@ public: if (!expected) { return -1; } - return _rtcp_context.geLostInterval() * 100 / expected; + return _rtcp_context.getLostInterval() * 100 / expected; } private: @@ -860,7 +884,7 @@ void WebRtcTransportImp::onRtcp(const char *buf, size_t len) { void WebRtcTransportImp::createRtpChannel(const string &rid, uint32_t ssrc, MediaTrack &track) { // rid --> RtpReceiverImp auto &ref = track.rtp_channel[rid]; - weak_ptr weak_self = dynamic_pointer_cast(shared_from_this()); + weak_ptr weak_self = static_pointer_cast(shared_from_this()); ref = std::make_shared( getPoller(), [&track, this, rid](RtpPacket::Ptr rtp) mutable { onSortedRtp(track, rid, std::move(rtp)); }, [&track, weak_self, ssrc](const FCI_NACK &nack) mutable { @@ -1053,6 +1077,15 @@ void WebRtcTransportImp::onBeforeEncryptRtp(const char *buf, int &len, void *ctx } } +void WebRtcTransportImp::safeShutdown(const SockException &ex) { + std::weak_ptr weak_self = static_pointer_cast(shared_from_this()); + getPoller()->async([ex, weak_self]() { + if (auto strong_self = weak_self.lock()) { + strong_self->onShutdown(ex); + } + }); +} + void WebRtcTransportImp::onShutdown(const SockException &ex) { WarnL << ex; unrefSelf(); @@ -1161,7 +1194,7 @@ void push_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginMana RtspMediaSourceImp::Ptr push_src; std::shared_ptr push_src_ownership; - auto src = MediaSource::find(RTSP_SCHEMA, info._vhost, info._app, info._streamid); + auto src = MediaSource::find(RTSP_SCHEMA, info.vhost, info.app, info.stream); auto push_failed = (bool)src; while (src) { @@ -1188,7 +1221,7 @@ void push_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginMana } if (!push_src) { - push_src = std::make_shared(info._vhost, info._app, info._streamid); + push_src = std::make_shared(info); push_src_ownership = push_src->getOwnership(); push_src->setProtocolOption(option); } @@ -1198,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()); @@ -1209,7 +1242,7 @@ void play_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginMana MediaInfo info(args["url"]); bool preferred_tcp = args["preferred_tcp"]; - auto session_ptr = sender.shared_from_this(); + auto session_ptr = static_pointer_cast(sender.shared_from_this()); Broadcast::AuthInvoker invoker = [cb, info, session_ptr, preferred_tcp](const string &err) mutable { if (!err.empty()) { cb(WebRtcException(SockException(Err_other, err))); @@ -1217,7 +1250,7 @@ void play_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginMana } // webrtc播放的是rtsp的源 - info._schema = RTSP_SCHEMA; + info.schema = RTSP_SCHEMA; MediaSource::findAsync(info, session_ptr, [=](const MediaSource::Ptr &src_in) mutable { auto src = dynamic_pointer_cast(src_in); if (!src) { @@ -1225,14 +1258,14 @@ void play_plugin(Session &sender, const WebRtcArgs &args, const WebRtcPluginMana return; } // 还原成rtc,目的是为了hook时识别哪种播放协议 - info._schema = RTC_SCHEMA; + info.schema = RTC_SCHEMA; auto rtc = WebRtcPlayer::create(EventPollerPool::Instance().getPoller(), src, info, preferred_tcp); cb(*rtc); }); }; // 广播通用播放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 eb33b090..2d1e6bbf 100644 --- a/webrtc/WebRtcTransport.h +++ b/webrtc/WebRtcTransport.h @@ -40,7 +40,8 @@ public: WebRtcInterface() = default; virtual ~WebRtcInterface() = default; virtual std::string getAnswerSdp(const std::string &offer) = 0; - virtual const std::string &getIdentifier() const = 0; + virtual const std::string& getIdentifier() const = 0; + virtual const std::string& deleteRandStr() const { static std::string s_null; return s_null; } }; std::string exchangeSdp(const WebRtcInterface &exchanger, const std::string& offer); @@ -92,6 +93,7 @@ public: * 获取对象唯一id */ const std::string& getIdentifier() const override; + const std::string& deleteRandStr() const override; /** * socket收到udp数据 @@ -110,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; @@ -174,6 +177,7 @@ protected: std::shared_ptr _ice_server; private: + mutable std::string _delete_rand_str; std::string _identifier; EventPoller::Ptr _poller; std::shared_ptr _dtls_transport; @@ -248,6 +252,7 @@ public: void createRtpChannel(const std::string &rid, uint32_t ssrc, MediaTrack &track); void removeTuple(RTC::TransportTuple* tuple); + void safeShutdown(const SockException &ex); protected: void OnIceServerSelectedTuple(const RTC::IceServer *iceServer, RTC::TransportTuple *tuple) override; diff --git a/www/webrtc/index.html b/www/webrtc/index.html index 0ff074f4..8f9b60e6 100644 --- a/www/webrtc/index.html +++ b/www/webrtc/index.html @@ -89,28 +89,35 @@