diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml new file mode 100644 index 00000000..59766f69 --- /dev/null +++ b/.github/workflows/ccpp.yml @@ -0,0 +1,25 @@ +name: C/C++ CI + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + + - name: 下载submodule源码 + run: git submodule update --init + + - name: apt-get安装依赖库(非必选) + run: sudo apt-get update && sudo apt-get install -y cmake libssl-dev libmp4v2-dev libsdl-dev libavcodec-dev libavutil-dev + + - name: 编译 + run: mkdir -p linux_build && cd linux_build && cmake .. && make -j4 + + - name: 运行MediaServer + run: pwd && cd release/linux/Debug && sudo ./MediaServer -d & + + diff --git a/.gitignore b/.gitignore index 853ad8f7..f5ec7af1 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,15 @@ *.DS_Store /cmake-build-debug/ +/cmake-build-release/ +/linux/ +/.vs/ /.idea/ /c_wrapper/.idea/ -/release/mac/Debug/ \ No newline at end of file +/release/ +/Android/.idea/ +/Android/app/src/main/cpp/libs_export/ +/3rdpart/media-server/.idea/ +/3rdpart/media-server/.idea/ +/build/ +/3rdpart/media-server/.idea/ diff --git a/.gitmodules b/.gitmodules index e9de36ce..54226a7f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "ZLToolKit"] path = 3rdpart/ZLToolKit - url = https://github.com/xiongziliang/ZLToolKit.git + url = https://gitee.com/xiahcu/ZLToolKit [submodule "3rdpart/media-server"] path = 3rdpart/media-server - url = https://github.com/xiongziliang/media-server + url = https://gitee.com/xiahcu/media-server diff --git a/3rdpart/ZLToolKit b/3rdpart/ZLToolKit index 8d1681b5..34b42499 160000 --- a/3rdpart/ZLToolKit +++ b/3rdpart/ZLToolKit @@ -1 +1 @@ -Subproject commit 8d1681b5bb247e7f47ae0f8c414f6eeb376b742b +Subproject commit 34b42499ce9ee055b5c7cac9a270239984d0e839 diff --git a/3rdpart/media-server b/3rdpart/media-server index 40edf624..8d40dad3 160000 --- a/3rdpart/media-server +++ b/3rdpart/media-server @@ -1 +1 @@ -Subproject commit 40edf6243d9d99676062062efdec203b24a178aa +Subproject commit 8d40dad3dbdce171756691d4511aca49fcf2a231 diff --git a/CMakeLists.txt b/CMakeLists.txt index f90e5cd3..ee853b01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -project(ZLMediaKit) +project(ZLMediaKit) cmake_minimum_required(VERSION 3.1.3) #使能c++11 set(CMAKE_CXX_STANDARD 11) @@ -20,6 +20,7 @@ set(RELEASE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/release) if (CMAKE_SYSTEM_NAME MATCHES "Linux") SET(LIBRARY_OUTPUT_PATH ${RELEASE_DIR}/linux/${BuildType}) SET(EXECUTABLE_OUTPUT_PATH ${RELEASE_DIR}/linux/${BuildType}) + add_compile_options(-fPIC) elseif (CMAKE_SYSTEM_NAME MATCHES "Windows") SET(LIBRARY_OUTPUT_PATH ${RELEASE_DIR}/windows/${BuildType}) SET(EXECUTABLE_OUTPUT_PATH ${RELEASE_DIR}/windows/${BuildType}) @@ -30,27 +31,15 @@ endif () LINK_DIRECTORIES(${LIBRARY_OUTPUT_PATH}) - #设置工程源码根目录 set(ToolKit_Root ${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/ZLToolKit/src) set(MediaKit_Root ${CMAKE_CURRENT_SOURCE_DIR}/src) +set(MediaServer_Root ${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/media-server) #设置头文件目录 INCLUDE_DIRECTORIES(${ToolKit_Root}) INCLUDE_DIRECTORIES(${MediaKit_Root}) -#收集源代码 -file(GLOB ToolKit_src_list ${ToolKit_Root}/*/*.cpp ${ToolKit_Root}/*/*.h ${ToolKit_Root}/*/*.c) -file(GLOB MediaKit_src_list ${MediaKit_Root}/*/*.cpp ${MediaKit_Root}/*/*.h ${MediaKit_Root}/*/*.c) - -#去除win32的适配代码 -if (NOT WIN32) - list(REMOVE_ITEM ToolKit_src_list ${ToolKit_Root}/win32/getopt.c) -else() - #防止Windows.h包含Winsock.h - add_definitions(-DWIN32_LEAN_AND_MEAN -DMP4V2_NO_STDINT_DEFS) -endif () - set(ENABLE_HLS true) set(ENABLE_OPENSSL true) set(ENABLE_MYSQL false) @@ -58,23 +47,10 @@ set(ENABLE_MP4V2 true) set(ENABLE_FAAC false) set(ENABLE_X264 false) set(ENABLE_MP4RECORD true) +set(ENABLE_RTPPROXY true) -#添加两个静态库 -if(ENABLE_HLS) - message(STATUS "ENABLE_HLS defined") - add_definitions(-DENABLE_HLS) - set(MediaServer_Root ${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/media-server) - set(LINK_LIB_LIST zlmediakit zltoolkit mpeg) -else() - set(LINK_LIB_LIST zlmediakit zltoolkit) -endif() +set(LINK_LIB_LIST zlmediakit zltoolkit) -if(ENABLE_MP4RECORD) - message(STATUS "ENABLE_MP4RECORD defined") - add_definitions(-DENABLE_MP4RECORD) - set(MediaServer_Root ${CMAKE_CURRENT_SOURCE_DIR}/3rdpart/media-server) - list(APPEND LINK_LIB_LIST mov flv) -endif() #查找openssl是否安装 find_package(OpenSSL QUIET) if (OPENSSL_FOUND AND ENABLE_OPENSSL) @@ -121,38 +97,6 @@ if (FAAC_FOUND AND ENABLE_FAAC) list(APPEND LINK_LIB_LIST ${FAAC_LIBRARIES}) endif () - -#添加库 -add_library(zltoolkit STATIC ${ToolKit_src_list}) -add_library(zlmediakit STATIC ${MediaKit_src_list}) - - -set(VS_FALGS "/wd4819 /wd4996 /wd4018 /wd4267 /wd4244 /wd4101 /wd4828 /wd4309 /wd4573" ) -#libmpeg -if(ENABLE_HLS) - aux_source_directory(${MediaServer_Root}/libmpeg/include src_mpeg) - aux_source_directory(${MediaServer_Root}/libmpeg/source src_mpeg) - include_directories(${MediaServer_Root}/libmpeg/include) - add_library(mpeg STATIC ${src_mpeg}) - if(WIN32) - set_target_properties(mpeg PROPERTIES COMPILE_FLAGS ${VS_FALGS} ) - endif(WIN32) -endif() - -if(ENABLE_MP4RECORD) - aux_source_directory(${MediaServer_Root}/libmov/include src_mov) - aux_source_directory(${MediaServer_Root}/libmov/source src_mov) - include_directories(${MediaServer_Root}/libmov/include) - aux_source_directory(${MediaServer_Root}/libflv/include src_flv) - aux_source_directory(${MediaServer_Root}/libflv/source src_flv) - include_directories(${MediaServer_Root}/libflv/include) - add_library(mov STATIC ${src_mov}) - add_library(flv STATIC ${src_flv}) - if(WIN32) - set_target_properties(mov flv PROPERTIES COMPILE_FLAGS ${VS_FALGS} ) - endif(WIN32) -endif() - if(${CMAKE_BUILD_TYPE} MATCHES "Release") #查找jemalloc是否安装 find_package(JEMALLOC QUIET) @@ -163,6 +107,79 @@ if(${CMAKE_BUILD_TYPE} MATCHES "Release") endif() endif() +set(VS_FALGS "/wd4819 /wd4996 /wd4018 /wd4267 /wd4244 /wd4101 /wd4828 /wd4309 /wd4573" ) + +#添加mpeg用于支持ts生成 +if(ENABLE_HLS) + message(STATUS "ENABLE_HLS defined") + add_definitions(-DENABLE_HLS) + + aux_source_directory(${MediaServer_Root}/libmpeg/include src_mpeg) + aux_source_directory(${MediaServer_Root}/libmpeg/source src_mpeg) + include_directories(${MediaServer_Root}/libmpeg/include) + + add_library(mpeg STATIC ${src_mpeg}) + list(APPEND LINK_LIB_LIST mpeg) + + if(WIN32) + set_target_properties(mpeg PROPERTIES COMPILE_FLAGS ${VS_FALGS} ) + endif(WIN32) +endif() + +#添加mov、flv库用于MP4录制 +if(ENABLE_MP4RECORD) + message(STATUS "ENABLE_MP4RECORD defined") + add_definitions(-DENABLE_MP4RECORD) + + aux_source_directory(${MediaServer_Root}/libmov/include src_mov) + aux_source_directory(${MediaServer_Root}/libmov/source src_mov) + include_directories(${MediaServer_Root}/libmov/include) + + aux_source_directory(${MediaServer_Root}/libflv/include src_flv) + aux_source_directory(${MediaServer_Root}/libflv/source src_flv) + include_directories(${MediaServer_Root}/libflv/include) + + add_library(mov STATIC ${src_mov}) + add_library(flv STATIC ${src_flv}) + list(APPEND LINK_LIB_LIST mov flv) + + if(WIN32) + set_target_properties(mov flv PROPERTIES COMPILE_FLAGS ${VS_FALGS} ) + endif(WIN32) +endif() + +#添加rtp库用于rtp转ps/ts +if(ENABLE_RTPPROXY AND ENABLE_HLS) + message(STATUS "ENABLE_RTPPROXY defined") + aux_source_directory(${MediaServer_Root}/librtp/include src_rtp) + aux_source_directory(${MediaServer_Root}/librtp/source src_rtp) + aux_source_directory(${MediaServer_Root}/librtp/payload src_rtp) + include_directories(${MediaServer_Root}/librtp/include) + add_library(rtp STATIC ${src_rtp}) + add_definitions(-DENABLE_RTPPROXY) + list(APPEND LINK_LIB_LIST rtp) +endif() + +#收集源代码 +file(GLOB ToolKit_src_list ${ToolKit_Root}/*/*.cpp ${ToolKit_Root}/*/*.h ${ToolKit_Root}/*/*.c) +if(IOS) + list(APPEND ToolKit_src_list ${ToolKit_Root}/Network/Socket_ios.mm) +endif() + +file(GLOB MediaKit_src_list ${MediaKit_Root}/*/*.cpp ${MediaKit_Root}/*/*.h ${MediaKit_Root}/*/*.c) + +#去除win32的适配代码 +if (NOT WIN32) + list(REMOVE_ITEM ToolKit_src_list ${ToolKit_Root}/win32/getopt.c) +else() + #防止Windows.h包含Winsock.h + add_definitions(-DWIN32_LEAN_AND_MEAN -DMP4V2_NO_STDINT_DEFS -DOS_WINDOWS) +endif () + +#添加库 +add_library(zltoolkit STATIC ${ToolKit_src_list}) +add_library(zlmediakit STATIC ${MediaKit_src_list}) + if (WIN32) list(APPEND LINK_LIB_LIST WS2_32 Iphlpapi shlwapi) set_target_properties(zltoolkit PROPERTIES COMPILE_FLAGS ${VS_FALGS} ) @@ -171,35 +188,16 @@ elseif(NOT ANDROID OR IOS) list(APPEND LINK_LIB_LIST pthread) endif () +#复制文件过来 +execute_process(COMMAND cp -r ${CMAKE_CURRENT_SOURCE_DIR}/www ${EXECUTABLE_OUTPUT_PATH}/) +execute_process(COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/conf/config.ini ${EXECUTABLE_OUTPUT_PATH}/) +#添加c库 +add_subdirectory(api) -#测试程序 -add_subdirectory(tests) - -#主服务器 -add_subdirectory(server) - - - - - - - - - - - - - - - - - - - - - - - - - +if (NOT IOS) + #测试程序 + add_subdirectory(tests) + #主服务器 + add_subdirectory(server) +endif () diff --git a/LICENSE b/LICENSE index d4008608..367501b6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,8 @@ MIT License Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> +Copyright (c) 2019 Gemfield +Copyright (c) 2018 huohuo <913481084@qq.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 5dcbcb68..1377e922 100644 --- a/README.md +++ b/README.md @@ -1,242 +1,257 @@ ![logo](https://raw.githubusercontent.com/zlmediakit/ZLMediaKit/master/logo.png) -# A lightweight ,high performance and stable stream server and client framework based on C++11. +[english readme](https://github.com/xiongziliang/ZLMediaKit/blob/master/README_en.md) +# 一个基于C++11的高性能运营级流媒体服务框架 [![Build Status](https://travis-ci.org/xiongziliang/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xiongziliang/ZLMediaKit) -[中文](https://github.com/xiongziliang/ZLMediaKit/blob/master/README_CN.md) -## 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`),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. +## 国内用户请使用gitee镜像下载 +``` +git clone --depth 1 https://gitee.com/xiahcu/ZLMediaKit +cd ZLMediaKit +git submodule update --init +``` +## 项目特点 +- 基于C++11开发,避免使用裸指针,代码稳定可靠;同时跨平台移植简单方便,代码清晰简洁。 +- 打包多种流媒体协议(RTSP/RTMP/HLS/HTTP-FLV/Websocket-FLV),支持协议间的互相转换,提供一站式的服务。 +- 使用epoll+线程池+异步网络IO模式开发,并发性能优越。 +- 已实现主流的的H264/H265+AAC流媒体方案,代码精简,脉络清晰,适合学习。 +- 编码格式与框架代码解耦,方便自由简洁的添加支持其他编码格式。 +- 代码经过大量的稳定性、性能测试,可满足商用服务器项目。 +- 支持linux、macos、ios、android、windows平台。 +- 支持画面秒开(GOP缓存)、极低延时([500毫秒内,最低可达100毫秒](https://github.com/zlmediakit/ZLMediaKit/wiki/%E5%BB%B6%E6%97%B6%E6%B5%8B%E8%AF%95))。 +- [ZLMediaKit高并发实现原理](https://github.com/xiongziliang/ZLMediaKit/wiki/ZLMediaKit%E9%AB%98%E5%B9%B6%E5%8F%91%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86)。 +- 提供完善的标准[C API](https://github.com/xiongziliang/ZLMediaKit/tree/master/api/include),可以作SDK用,或供其他语言调用。 +- 提供完整的[MediaServer](https://github.com/xiongziliang/ZLMediaKit/tree/master/server)服务器,可以免开发直接部署为商用服务器。 -## Features +## 项目定位 +- 移动嵌入式跨平台流媒体解决方案。 +- 商用级流媒体服务器。 +- 网络编程二次开发SDK。 + +## 功能清单 - RTSP - - RTSP[S] server,support rtsp push. - - RTSP player and pusher. - - RTP Transport : `rtp over udp` `rtp over tcp` `rtp over http` `rtp udp multicast` . - - Basic/Digest/Url Authentication. - - H265/H264/AAC codec. - - Recorded as mp4. - - Vod of mp4. - + - RTSP 服务器,支持RTMP/MP4转RTSP + - RTSPS 服务器,支持亚马逊echo show这样的设备 + - RTSP 播放器,支持RTSP代理,支持生成静音音频 + - RTSP 推流客户端与服务器 + - 支持 `rtp over udp` `rtp over tcp` `rtp over http` `rtp组播` 四种RTP传输方式 + - 服务器/客户端完整支持Basic/Digest方式的登录鉴权,全异步可配置化的鉴权接口 + - 支持H265编码 + - 服务器支持RTSP推流(包括`rtp over udp` `rtp over tcp`方式) + - 支持任意编码格式的rtsp推流,只是除H264/H265+AAC外无法转协议 + - RTMP - - RTMP server,support player and pusher. - - RTMP player and pusher. - - Support HTTP-FLV player. - - H264/AAC codec. - - Recorded as flv or mp4. - - Vod of mp4. - + - RTMP 播放服务器,支持RTSP/MP4转RTMP + - RTMP 发布服务器,支持录制发布流 + - RTMP 播放器,支持RTMP代理,支持生成静音音频 + - RTMP 推流客户端 + - 支持http[s]-flv直播 + - 支持websocket-flv直播 + - 支持任意编码格式的rtmp推流,只是除H264/H265+AAC外无法转协议 + - HLS - - RTSP RTMP can be converted into HLS,built-in HTTP server. - - Play authentication based on cookie. + - 支持HLS文件生成,自带HTTP文件服务器 + - 通过cookie追踪技术,可以模拟HLS播放为长连接,实现丰富的业务逻辑 + - 支持完备的HLS用户追踪、播放统计等业务功能,可以实现HLS按需拉流等业务 - 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. - -- 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. + - 服务器支持`目录索引生成`,`文件下载`,`表单提交请求` + - 客户端提供`文件下载器(支持断点续传)`,`接口请求器`,`文件上传器` + - 完整HTTP API服务器,可以作为web后台开发框架 + - 支持跨域访问 + - 支持http客户端、服务器cookie + - 支持WebSocket服务器和客户端 + - 支持http文件访问鉴权 + +- GB28181 + - 支持UDP/TCP国标RTP推流,可以转换成RTSP/RTMP/HLS等协议 + +- 点播 + - 支持录制为FLV/HLS/MP4 + - RTSP/RTMP/HTTP-FLV/WS-FLV支持MP4文件点播,支持seek + +- 其他 + - 支持丰富的restful api以及web hook事件 + - 支持简单的telnet调试 + - 支持配置文件热加载 + - 支持流量统计、推拉流鉴权等事件 + - 支持虚拟主机,可以隔离不同域名 + - 支持按需拉流,无人观看自动关断拉流 + - 支持先拉流后推流,提高及时推流画面打开率 + - 提供c api sdk + +## 其他功能细节表 -- Protocol conversion: +- 转协议: -| protocol/codec | H264 | H265 | AAC | other | -| :------------------------------: | :--: | :--: | :--: | :---: | -| RTSP[S] --> RTMP/HTTP[S]-FLV/FLV | Y | N | Y | N | -| RTMP --> RTSP[S] | Y | N | Y | N | -| RTSP[S] --> HLS | Y | Y | Y | N | -| RTMP --> HLS | Y | N | Y | N | -| RTSP[S] --> MP4 | Y | Y | Y | N | -| RTMP --> MP4 | Y | N | Y | N | -| MP4 --> RTSP[S] | Y | N | Y | N | -| MP4 --> RTMP | Y | N | Y | N | + | 功能/编码格式 | H264 | H265 | AAC | other | + | :------------------------------: | :--: | :--: | :--: | :---: | + | RTSP[S] --> RTMP/HTTP[S]-FLV/FLV | Y | N | Y | N | + | RTMP --> RTSP[S] | Y | N | Y | N | + | RTSP[S] --> HLS | Y | Y | Y | N | + | RTMP --> HLS | Y | N | Y | N | + | RTSP[S] --> MP4 | Y | Y | Y | N | + | RTMP --> MP4 | Y | N | Y | N | + | MP4 --> RTSP[S] | Y | N | Y | N | + | MP4 --> RTMP | Y | N | Y | N | -- Stream generation: +- 流生成: -| feature/codec | H264 | H265 | AAC | other | -| :-----------: | :--: | :--: | :--: | :---: | -| RTSP[S] push | Y | Y | Y | Y | -| RTSP proxy | Y | Y | Y | Y | -| RTMP push | Y | Y | Y | Y | -| RTMP proxy | Y | Y | Y | Y | + | 功能/编码格式 | H264 | H265 | AAC | other | + | :------------------------------: | :--: | :--: | :--: | :---: | + | RTSP[S]推流 | Y | Y | Y | Y | + | RTSP拉流代理 | Y | Y | Y | Y | + | RTMP推流 | Y | Y | Y | Y | + | RTMP拉流代理 | Y | Y | Y | Y | -- RTP transport: +- RTP传输方式: -| feature/transport | tcp | udp | http | udp_multicast | -| :-----------------: | :--: | :--: | :--: | :-----------: | -| RTSP[S] Play Server | Y | Y | Y | Y | -| RTSP[S] Push Server | Y | Y | N | N | -| RTSP Player | Y | Y | N | Y | -| RTSP Pusher | Y | Y | N | N | + | 功能/RTP传输方式 | tcp | udp | http | udp_multicast | + | :-----------------: | :--: | :--: | :--: | :-----------: | + | RTSP[S] Play Server | Y | Y | Y | Y | + | RTSP[S] Push Server | Y | Y | N | N | + | RTSP Player | Y | Y | N | Y | + | RTSP Pusher | Y | Y | N | N | -- Server supported: +- 支持的服务器类型列表 -| Server | Y/N | -| :-----------------: | :--: | -| RTSP[S] Play Server | Y | -| RTSP[S] Push Server | Y | -| RTMP | Y | -| HTTP[S]/WebSocket[S] | Y | + | 服务类型 | Y/N | + | :-----------------: | :--: | + | RTSP[S] Play Server | Y | + | RTSP[S] Push Server | Y | + | RTMP | Y | + | HTTP[S]/WebSocket[S] | Y | -- Client supported: +- 支持的客户端类型 -| Client | Y/N | -| :---------: | :--: | -| RTSP Player | Y | -| RTSP Pusher | Y | -| RTMP Player | Y | -| RTMP Pusher | Y | -| HTTP[S] | Y | -| WebSocket[S] | Y | + | 客户端类型 | Y/N | + | :---------: | :--: | + | RTSP Player | Y | + | RTSP Pusher | Y | + | RTMP Player | Y | + | RTMP Pusher | Y | + | HTTP[S] | Y | + | WebSocket[S] | Y | +## 后续任务 +- 完善支持H265 +## 编译要求 +- 编译器支持C++11,GCC4.8/Clang3.3/VC2015或以上 +- cmake3.2或以上 -## 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. -- Windows. -- **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:** +- **必须使用git下载完整的代码,不要使用下载zip包的方式下载源码,否则子模块代码默认不下载!你可以像以下这样操作:** ``` git clone https://github.com/zlmediakit/ZLMediaKit.git cd ZLMediaKit git submodule update --init ``` - - -## 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. - -### Build on linux - -- My environment - - Ubuntu16.04 64 bit and gcc5.4 +## 编译(Linux) +- 我的编译环境 + - Ubuntu16.04 64 bit + gcc5.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, - # step 4 can be manipulated directly. - - # 1、Install GCC5.2 (this step can be skipped if the GCC version is higher than 4.7) + //如果是centos6.x,需要先安装较新版本的gcc以及cmake,然后打开脚本build_for_linux.sh手动编译 + //如果是ubuntu这样的比较新的系统版本可以直接操作第4步 + + 1、安装GCC5.2(如果gcc版本高于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、安装cmake + #需要安装新版本cmake,当然你也可以通过yum或者apt-get方式安装(前提是版本够新) + tar -xvf cmake-3.10.0-rc4.tar.gz cd cmake-3.10.0-rc4 ./configure make -j4 sudo make install - - # 3、Switch to high version GCC + + 3、切换高版本gcc scl enable devtoolset-4 bash - - # 4、build + + 4、编译 cd ZLMediaKit ./build_for_linux.sh ``` -### Build on macOS - -- My environment +## 编译(macOS) +- 我的编译环境 - macOS Sierra(10.12.1) + xcode8.3.1 - Homebrew 1.1.3 - cmake 3.8.0 -- Guidance +- 编译 ``` cd ZLMediaKit ./build_for_mac.sh ``` -### Build on iOS - - This build method is no longer recommended.It is recommended that make Xcode project by yourself. - -- My environment - - Same with Build on macOS - -- Guidance - - ``` - cd ZLMediaKit - ./build_for_ios.sh - ``` - -- You can also generate Xcode projects and recompile them: +## 编译(iOS) +- 编译环境:`请参考macOS的编译指导。` +- 生成Xcode工程再编译,[了解更多](https://github.com/leetal/ios-cmake): ``` cd ZLMediaKit mkdir -p build cd build - # Generate Xcode project, project file is in build directory - cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/iOS.cmake -DIOS_PLATFORM=SIMULATOR64 -G "Xcode" + # 生成Xcode工程,工程文件在build目录下 + cmake .. -G Xcode -DCMAKE_TOOLCHAIN_FILE=../cmake/ios.toolchain.cmake -DPLATFORM=OS64COMBINED ``` - - - -### Build on Android - - Now you can open android sudio project in `Android` folder,this is a `aar library` and damo project. - -- My environment +## 编译(Android) +- 我的编译环境 - macOS Sierra(10.12.1) + xcode8.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) -- Guidance +- 编译 ``` cd ZLMediaKit export ANDROID_NDK_ROOT=/path/to/ndk ./build_for_android.sh ``` -### Build on Windows - -- My environment +## 编译(Windows) +- 我的编译环境 - 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 进入ZLMediaKit目录执行 git submodule update --init 以下载ZLToolKit的代码 + 2 使用cmake-gui打开工程并生成vs工程文件. +   3 找到工程文件(ZLMediaKit.sln),双击用vs2017打开. +   4 选择编译Release 版本. + 5 找到目标文件并运行测试用例. ``` -## Usage -- As server: +## Docker Image +You can pull a pre-built docker image from Docker Hub and run with +```bash +docker run -id -p 1935:1935 -p 8080:80 gemfield/zlmediakit +``` + +Dockerfile is also supplied to build images on Ubuntu 16.04 +```bash +cd docker +docker build -t zlmediakit . +``` + +## 使用方法 +- 作为服务器: ```cpp TcpServer::Ptr rtspSrv(new TcpServer()); TcpServer::Ptr rtmpSrv(new TcpServer()); @@ -249,7 +264,7 @@ It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumber httpsSrv->start(mINI::Instance()[Config::Http::kSSLPort]); ``` -- As player: +- 作为播放器: ```cpp MediaPlayer::Ptr player(new MediaPlayer()); weak_ptr weakPlayer = player; @@ -262,11 +277,11 @@ It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumber auto viedoTrack = strongPlayer->getTrack(TrackVideo); if (!viedoTrack) { - WarnL << "none video Track!"; + WarnL << "没有视频Track!"; return; } viedoTrack->addDelegate(std::make_shared([](const Frame::Ptr &frame) { - //please decode video here + //此处解码并播放 })); }); @@ -274,11 +289,11 @@ It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumber ErrorL << "OnShutdown:" << ex.what(); }); - //rtp transport over tcp + //支持rtmp、rtsp (*player)[Client::kRtpType] = Rtsp::RTP_TCP; player->play("rtsp://admin:jzan123456@192.168.0.122/"); ``` -- As proxy server: +- 作为代理服务器: ```cpp //support rtmp and rtsp url //just support H264+AAC @@ -287,63 +302,111 @@ It is recommended to compile on Ubuntu or MacOS,compiling on windows is cumber map proxyMap; int i=0; for(auto url : urlList){ + //PlayerProxy构造函数前两个参数分别为应用名(app),流id(streamId) + //比如说应用为live,流id为0,那么直播地址为: + //http://127.0.0.1/live/0/hls.m3u8 + //rtsp://127.0.0.1/live/0 + //rtmp://127.0.0.1/live/0 + //录像地址为: + //http://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4 + //rtsp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4 + //rtmp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4 PlayerProxy::Ptr player(new PlayerProxy("live",to_string(i++).data())); player->play(url); proxyMap.emplace(string(url),player); } ``` -- As puser: +- 作为推流客户端器: ```cpp PlayerProxy::Ptr player(new PlayerProxy("app","stream")); + //拉一个流,生成一个RtmpMediaSource,源的名称是"app/stream" + //你也可以以其他方式生成RtmpMediaSource,比如说MP4文件(请研读MediaReader代码) player->play("rtmp://live.hkstv.hk.lxdns.com/live/hks"); - + RtmpPusher::Ptr pusher; + //监听RtmpMediaSource注册事件,在PlayerProxy播放成功后触发。 NoticeCenter::Instance().addListener(nullptr,Config::Broadcast::kBroadcastRtmpSrcRegisted, [&pusher](BroadcastRtmpSrcRegistedArgs){ + //媒体源"app/stream"已经注册,这时方可新建一个RtmpPusher对象并绑定该媒体源 const_cast(pusher).reset(new RtmpPusher(app,stream)); + + //推流地址,请改成你自己的服务器。 + //这个范例地址(也是基于mediakit)是可用的,但是带宽只有1mb,访问可能很卡顿。 pusher->publish("rtmp://jizan.iok.la/live/test"); }); - + ``` +## QA +- 怎么测试服务器性能? -## Mirrors + ZLMediaKit提供了测试性能的示例,代码在tests/test_benchmark.cpp。 -[ZLToolKit](http://git.oschina.net/xiahcu/ZLToolKit) + 这里是测试报告:[benchmark.md](https://github.com/xiongziliang/ZLMediaKit/blob/master/benchmark.md) -[ZLMediaKit](http://git.oschina.net/xiahcu/ZLMediaKit) +- github下载太慢了,有其他下载方式吗? + + 你可以在通过开源中国获取最新的代码,地址为: + + [ZLToolKit](http://git.oschina.net/xiahcu/ZLToolKit) + + [ZLMediaKit](http://git.oschina.net/xiahcu/ZLMediaKit) -## Licence +- 在windows下编译很多错误? -``` -MIT License + 由于本项目主体代码在macOS/linux下开发,部分源码采用的是无bom头的UTF-8编码;由于windows对于utf-8支持不甚友好,所以如果发现编译错误请先尝试添 加bom头再编译。 + 也可以通过参考这篇博客解决: + [vs2015:/utf-8选项解决UTF-8 without BOM 源码中文输出乱码问题](https://blog.csdn.net/10km/article/details/80203286) -Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> +## 参考案例 + - [IOS摄像头实时录制,生成rtsp/rtmp/hls/http-flv](https://gitee.com/xiahcu/IOSMedia) + - [IOS rtmp/rtsp播放器,视频推流器](https://gitee.com/xiahcu/IOSPlayer) + - [支持linux、windows、mac的rtmp/rtsp播放器](https://github.com/xiongziliang/ZLMediaPlayer) + - [配套的管理WEB网站](https://github.com/chenxiaolei/ZLMediaKit_NVR_UI) + +## 授权协议 -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +本项目自有代码使用宽松的MIT协议,在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。 +但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除; +由于使用本项目而产生的商业纠纷或侵权行为一概与本项项目及开发者无关,请自行承担法律风险。 -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +## 联系方式 + - 邮箱:<771730766@qq.com>(本项目相关或流媒体相关问题请走issue流程,否则恕不邮件答复) + - QQ群:542509000 + +## 怎么提问? +如果要对项目有相关疑问,建议您这么做: + - 1、仔细看下readme、wiki,如果有必要可以查看下issue. + - 2、如果您的问题还没解决,可以提issue. + - 3、有些问题,如果不具备参考性的,无需在issue提的,可以在qq群提. + - 4、QQ私聊一般不接受无偿技术咨询和支持(谈谈人生理想还是可以的😂),毕竟精力有限,谢谢理解. + +## 致谢 +感谢以下各位对本项目包括但不限于代码贡献、问题反馈、资金捐赠等各种方式的支持!以下排名不分先后: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -``` +[老陈](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) +[好心情](<409257224@qq.com>) + +## 捐赠 +欢迎捐赠以便更好的推动项目的发展,谢谢您的支持! + +[支付宝](https://raw.githubusercontent.com/xiongziliang/other/master/IMG_3919.JPG) + +[微信](https://raw.githubusercontent.com/xiongziliang/other/master/IMG_3920.JPG) -## Contact - - Email:<771730766@qq.com> - - QQ chat group:542509000 - - diff --git a/README_CN.md b/README_CN.md deleted file mode 100644 index 54199461..00000000 --- a/README_CN.md +++ /dev/null @@ -1,361 +0,0 @@ -![logo](https://raw.githubusercontent.com/zlmediakit/ZLMediaKit/master/logo.png) -# 一个基于C++11的高性能运营级流媒体服务框架 - [![Build Status](https://travis-ci.org/xiongziliang/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xiongziliang/ZLMediaKit) - -## 项目特点 -- 基于C++11开发,避免使用裸指针,代码稳定可靠;同时跨平台移植简单方便,代码清晰简洁。 -- 打包多种流媒体协议(RTSP/RTMP/HLS/HTTP-FLV/Websocket-FLV),支持协议间的互相转换,提供一站式的服务。 -- 使用epoll+线程池+异步网络IO模式开发,并发性能优越。 -- 已实现主流的的H264/H265+AAC流媒体方案,代码精简,脉络清晰,适合学习。 -- 编码格式与框架代码解耦,方便自由简洁的添加支持其他编码格式 -- 代码经过大量的稳定性、性能测试,可满足商用服务器项目。 -- 支持linux、macos、ios、android、windows平台 -- 支持画面秒开(GOP缓存)、极低延时([500毫秒内,最低可达100毫秒](https://github.com/zlmediakit/ZLMediaKit/wiki/%E5%BB%B6%E6%97%B6%E6%B5%8B%E8%AF%95)) -- [ZLMediaKit高并发实现原理](https://github.com/xiongziliang/ZLMediaKit/wiki/ZLMediaKit%E9%AB%98%E5%B9%B6%E5%8F%91%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86) - -## 项目定位 -- 移动嵌入式跨平台流媒体解决方案。 -- 商用级流媒体服务器。 -- 网络编程二次开发SDK。 - - -## 功能清单 -- RTSP - - RTSP 服务器,支持RTMP/MP4转RTSP。 - - RTSPS 服务器,支持亚马逊echo show这样的设备 - - RTSP 播放器,支持RTSP代理,支持生成静音音频 - - RTSP 推流客户端与服务器 - - 支持 `rtp over udp` `rtp over tcp` `rtp over http` `rtp组播` 四种RTP传输方式 。 - - 服务器/客户端完整支持Basic/Digest方式的登录鉴权,全异步可配置化的鉴权接口。 - - 支持H265编码 - - 服务器支持RTSP推流(包括`rtp over udp` `rtp over tcp`方式) - - 支持任意编码格式的rtsp推流,只是除H264/H265+AAC外无法转协议 - -- RTMP - - RTMP 播放服务器,支持RTSP/MP4转RTMP。 - - RTMP 发布服务器,支持录制发布流。 - - RTMP 播放器,支持RTMP代理,支持生成静音音频 - - RTMP 推流客户端。 - - 支持http-flv直播。 - - 支持https-flv直播。 - - 支持任意编码格式的rtmp推流,只是除H264/H265+AAC外无法转协议 - -- HLS - - 支持HLS文件生成,自带HTTP文件服务器。 - - 支持播放鉴权,鉴权结果可以缓存为cookie - -- HTTP[S] - - 服务器支持`目录索引生成`,`文件下载`,`表单提交请求`。 - - 客户端提供`文件下载器(支持断点续传)`,`接口请求器`,`文件上传器`。 - - 完整HTTP API服务器,可以作为web后台开发框架。 - - 支持跨域访问。 - - 支持http客户端、服务器cookie - - 支持WebSocket服务器和客户端 - - 支持http文件访问鉴权 - -- 其他 - - 支持输入YUV+PCM自动生成RTSP/RTMP/HLS/MP4. - - 支持简单的telnet调试。 - - 支持H264的解析,支持B帧的POC计算排序。 - - 支持配置文件热加载 - - 支持流量统计、推流播放鉴权等事件 - - 支持rtsp/rtmp/http虚拟主机 - - 支持flv、mp4文件录制 - - 支持rtps/rtmp协议的mp4点播,支持seek - - 支持按需拉流,无人观看自动关断拉流 - - 支持先拉流后推流,提高及时推流画面打开率 - - 支持rtsp/rtmp/http-flv/hls播放鉴权(url参数方式) - - - -## 其他功能细节表 - -- 转协议: - - | 功能/编码格式 | H264 | H265 | AAC | other | - | :------------------------------: | :--: | :--: | :--: | :---: | - | RTSP[S] --> RTMP/HTTP[S]-FLV/FLV | Y | N | Y | N | - | RTMP --> RTSP[S] | Y | N | Y | N | - | RTSP[S] --> HLS | Y | Y | Y | N | - | RTMP --> HLS | Y | N | Y | N | - | RTSP[S] --> MP4 | Y | Y | Y | N | - | RTMP --> MP4 | Y | N | Y | N | - | MP4 --> RTSP[S] | Y | N | Y | N | - | MP4 --> RTMP | Y | N | Y | N | - -- 流生成: - - | 功能/编码格式 | H264 | H265 | AAC | other | - | :------------------------------: | :--: | :--: | :--: | :---: | - | RTSP[S]推流 | Y | Y | Y | Y | - | RTSP拉流代理 | Y | Y | Y | Y | - | RTMP推流 | Y | Y | Y | Y | - | RTMP拉流代理 | Y | Y | Y | Y | - -- RTP传输方式: - - | 功能/RTP传输方式 | tcp | udp | http | udp_multicast | - | :-----------------: | :--: | :--: | :--: | :-----------: | - | RTSP[S] Play Server | Y | Y | Y | Y | - | RTSP[S] Push Server | Y | Y | N | N | - | RTSP Player | Y | Y | N | Y | - | RTSP Pusher | Y | Y | N | N | - - -- 支持的服务器类型列表 - - | 服务类型 | Y/N | - | :-----------------: | :--: | - | RTSP[S] Play Server | Y | - | RTSP[S] Push Server | Y | - | RTMP | Y | - | HTTP[S]/WebSocket[S] | Y | - -- 支持的客户端类型 - - | 客户端类型 | Y/N | - | :---------: | :--: | - | RTSP Player | Y | - | RTSP Pusher | Y | - | RTMP Player | Y | - | RTMP Pusher | Y | - | HTTP[S] | Y | - | WebSocket[S] | Y | - -## 后续任务 -- 完善支持H265 - -## 编译要求 -- 编译器支持C++11,GCC4.8/Clang3.3/VC2015或以上 -- cmake3.2或以上 -- **必须使用git下载完整的代码,不要使用下载zip包的方式下载源码,否则子模块代码默认不下载!你可以像以下这样操作:** -``` -git clone https://github.com/zlmediakit/ZLMediaKit.git -cd ZLMediaKit -git submodule update --init -``` - -## 编译(Linux) -- 我的编译环境 - - Ubuntu16.04 64 bit + gcc5.4 - - cmake 3.5.1 -- 编译 - - ``` - //如果是centos6.x,需要先安装较新版本的gcc以及cmake,然后打开脚本build_for_linux.sh手动编译 - //如果是ubuntu这样的比较新的系统版本可以直接操作第4步 - - 1、安装GCC5.2(如果gcc版本高于4.7可以跳过此步骤) - sudo yum install centos-release-scl -y - sudo yum install devtoolset-4-toolchain -y - scl enable devtoolset-4 bash - - 2、安装cmake - #需要安装新版本cmake,当然你也可以通过yum或者apt-get方式安装(前提是版本够新) - tar -xvf cmake-3.10.0-rc4.tar.gz - cd cmake-3.10.0-rc4 - ./configure - make -j4 - sudo make install - - 3、切换高版本gcc - scl enable devtoolset-4 bash - - 4、编译 - cd ZLMediaKit - ./build_for_linux.sh - ``` - -## 编译(macOS) -- 我的编译环境 - - macOS Sierra(10.12.1) + xcode8.3.1 - - Homebrew 1.1.3 - - cmake 3.8.0 -- 编译 - - ``` - cd ZLMediaKit - ./build_for_mac.sh - ``` - -## 编译(iOS) -- 编译环境:`请参考macOS的编译指导。` -- 编译 - - ``` - cd ZLMediaKit - ./build_for_ios.sh - ``` -- 你也可以生成Xcode工程再编译: - - ``` - cd ZLMediaKit - mkdir -p build - cd build - # 生成Xcode工程,工程文件在build目录下 - cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/iOS.cmake -DIOS_PLATFORM=SIMULATOR64 -G "Xcode" - ``` - -## 编译(Android) -- 我的编译环境 - - macOS Sierra(10.12.1) + xcode8.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) -- 编译 - - ``` - cd ZLMediaKit - export ANDROID_NDK_ROOT=/path/to/ndk - ./build_for_android.sh - ``` -## 编译(Windows) -- 我的编译环境 - - windows 10 - - visual studio 2017 - - [cmake-gui](https://cmake.org/files/v3.10/cmake-3.10.0-rc1-win32-x86.msi) - -- 编译 -``` - 1 进入ZLMediaKit目录执行 git submodule update --init 以下载ZLToolKit的代码 - 2 使用cmake-gui打开工程并生成vs工程文件. -   3 找到工程文件(ZLMediaKit.sln),双击用vs2017打开. -   4 选择编译Release 版本. - 5 找到目标文件并运行测试用例. -``` -## 使用方法 -- 作为服务器: - ```cpp - TcpServer::Ptr rtspSrv(new TcpServer()); - TcpServer::Ptr rtmpSrv(new TcpServer()); - TcpServer::Ptr httpSrv(new TcpServer()); - TcpServer::Ptr httpsSrv(new TcpServer()); - - rtspSrv->start(mINI::Instance()[Config::Rtsp::kPort]); - rtmpSrv->start(mINI::Instance()[Config::Rtmp::kPort]); - httpSrv->start(mINI::Instance()[Config::Http::kPort]); - httpsSrv->start(mINI::Instance()[Config::Http::kSSLPort]); - ``` - -- 作为播放器: - ```cpp - MediaPlayer::Ptr player(new MediaPlayer()); - weak_ptr weakPlayer = player; - player->setOnPlayResult([weakPlayer](const SockException &ex) { - InfoL << "OnPlayResult:" << ex.what(); - auto strongPlayer = weakPlayer.lock(); - if (ex || !strongPlayer) { - return; - } - - auto viedoTrack = strongPlayer->getTrack(TrackVideo); - if (!viedoTrack) { - WarnL << "没有视频Track!"; - return; - } - viedoTrack->addDelegate(std::make_shared([](const Frame::Ptr &frame) { - //此处解码并播放 - })); - }); - - player->setOnShutdown([](const SockException &ex) { - ErrorL << "OnShutdown:" << ex.what(); - }); - - //支持rtmp、rtsp - (*player)[Client::kRtpType] = Rtsp::RTP_TCP; - player->play("rtsp://admin:jzan123456@192.168.0.122/"); - ``` -- 作为代理服务器: - ```cpp - //support rtmp and rtsp url - //just support H264+AAC - auto urlList = {"rtmp://live.hkstv.hk.lxdns.com/live/hks", - "rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov"}; - map proxyMap; - int i=0; - for(auto url : urlList){ - //PlayerProxy构造函数前两个参数分别为应用名(app),流id(streamId) - //比如说应用为live,流id为0,那么直播地址为: - //http://127.0.0.1/live/0/hls.m3u8 - //rtsp://127.0.0.1/live/0 - //rtmp://127.0.0.1/live/0 - //录像地址为: - //http://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4 - //rtsp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4 - //rtmp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4 - PlayerProxy::Ptr player(new PlayerProxy("live",to_string(i++).data())); - player->play(url); - proxyMap.emplace(string(url),player); - } - ``` - -- 作为推流客户端器: - ```cpp - PlayerProxy::Ptr player(new PlayerProxy("app","stream")); - //拉一个流,生成一个RtmpMediaSource,源的名称是"app/stream" - //你也可以以其他方式生成RtmpMediaSource,比如说MP4文件(请研读MediaReader代码) - player->play("rtmp://live.hkstv.hk.lxdns.com/live/hks"); - - RtmpPusher::Ptr pusher; - //监听RtmpMediaSource注册事件,在PlayerProxy播放成功后触发。 - NoticeCenter::Instance().addListener(nullptr,Config::Broadcast::kBroadcastRtmpSrcRegisted, - [&pusher](BroadcastRtmpSrcRegistedArgs){ - //媒体源"app/stream"已经注册,这时方可新建一个RtmpPusher对象并绑定该媒体源 - const_cast(pusher).reset(new RtmpPusher(app,stream)); - - //推流地址,请改成你自己的服务器。 - //这个范例地址(也是基于mediakit)是可用的,但是带宽只有1mb,访问可能很卡顿。 - pusher->publish("rtmp://jizan.iok.la/live/test"); - }); - - ``` -## QA -- 怎么测试服务器性能? - - ZLMediaKit提供了测试性能的示例,代码在tests/test_benchmark.cpp。 - - 这里是测试报告:[benchmark.md](https://github.com/xiongziliang/ZLMediaKit/blob/master/benchmark.md) - -- github下载太慢了,有其他下载方式吗? - - 你可以在通过开源中国获取最新的代码,地址为: - - [ZLToolKit](http://git.oschina.net/xiahcu/ZLToolKit) - - [ZLMediaKit](http://git.oschina.net/xiahcu/ZLMediaKit) - - -- 在windows下编译很多错误? - - 由于本项目主体代码在macOS/linux下开发,部分源码采用的是无bom头的UTF-8编码;由于windows对于utf-8支持不甚友好,所以如果发现编译错误请先尝试添 加bom头再编译。 - 也可以通过参考这篇博客解决: - [vs2015:/utf-8选项解决UTF-8 without BOM 源码中文输出乱码问题](https://blog.csdn.net/10km/article/details/80203286) - -## 参考案例 - - [IOS摄像头实时录制,生成rtsp/rtmp/hls/http-flv](https://gitee.com/xiahcu/IOSMedia) - - [IOS rtmp/rtsp播放器,视频推流器](https://gitee.com/xiahcu/IOSPlayer) - - [支持linux、windows、mac的rtmp/rtsp播放器](https://github.com/xiongziliang/ZLMediaPlayer) - - 上述工程可能在最新的代码的情况下编译不过,请手动修改 - - -## 授权协议 - -本项目自有代码使用宽松的MIT协议,在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。 -但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除; -由于使用本项目而产生的商业纠纷或侵权行为一概与本项项目及开发者无关,请自行承担法律风险。 - -## 联系方式 - - 邮箱:<771730766@qq.com>(本项目相关或流媒体相关问题请走issue流程,否则恕不邮件答复) - - QQ群:542509000 - -## 捐赠 -如果本项目能切实帮助您减少重复开发的工作量,您可以在自愿的基础上支持下作者,谢谢! - -[支付宝](https://raw.githubusercontent.com/xiongziliang/other/master/IMG_3919.JPG) - -[微信](https://raw.githubusercontent.com/xiongziliang/other/master/IMG_3920.JPG) - - - diff --git a/README_en.md b/README_en.md new file mode 100644 index 00000000..d9947c01 --- /dev/null +++ b/README_en.md @@ -0,0 +1,348 @@ +![logo](https://raw.githubusercontent.com/zlmediakit/ZLMediaKit/master/logo.png) + +# A lightweight ,high performance and stable stream server and client framework based on C++11. + + [![Build Status](https://travis-ci.org/xiongziliang/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xiongziliang/ZLMediaKit) + + +## 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`),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. + +## Features + +- RTSP + - RTSP[S] server,support rtsp push. + - RTSP player and pusher. + - RTP Transport : `rtp over udp` `rtp over tcp` `rtp over http` `rtp udp multicast` . + - Basic/Digest/Url Authentication. + - H265/H264/AAC codec. + - Recorded as mp4. + - Vod of mp4. + +- RTMP + - RTMP server,support player and pusher. + - RTMP player and pusher. + - Support HTTP-FLV player. + - H264/AAC codec. + - Recorded as flv or mp4. + - Vod of mp4. + +- HLS + - RTSP RTMP can be converted into HLS,built-in HTTP server. + - Play authentication based on cookie. + +- 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. + +- 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. + + + +- Protocol conversion: + +| protocol/codec | H264 | H265 | AAC | other | +| :------------------------------: | :--: | :--: | :--: | :---: | +| RTSP[S] --> RTMP/HTTP[S]-FLV/FLV | Y | N | Y | N | +| RTMP --> RTSP[S] | Y | N | Y | N | +| RTSP[S] --> HLS | Y | Y | Y | N | +| RTMP --> HLS | Y | N | Y | N | +| RTSP[S] --> MP4 | Y | Y | Y | N | +| RTMP --> MP4 | Y | N | Y | N | +| MP4 --> RTSP[S] | Y | N | Y | N | +| MP4 --> RTMP | Y | N | Y | N | + +- Stream generation: + +| feature/codec | H264 | H265 | AAC | other | +| :-----------: | :--: | :--: | :--: | :---: | +| RTSP[S] push | Y | Y | Y | Y | +| RTSP proxy | Y | Y | Y | Y | +| RTMP push | Y | Y | Y | Y | +| RTMP proxy | Y | Y | Y | Y | + +- RTP transport: + +| feature/transport | tcp | udp | http | udp_multicast | +| :-----------------: | :--: | :--: | :--: | :-----------: | +| RTSP[S] Play Server | Y | Y | Y | Y | +| RTSP[S] Push Server | Y | Y | N | N | +| RTSP Player | Y | Y | N | Y | +| RTSP Pusher | Y | Y | N | N | + + +- Server supported: + +| Server | Y/N | +| :-----------------: | :--: | +| RTSP[S] Play Server | Y | +| RTSP[S] Push Server | Y | +| RTMP | Y | +| HTTP[S]/WebSocket[S] | Y | + +- Client supported: + +| Client | Y/N | +| :---------: | :--: | +| RTSP Player | Y | +| RTSP Pusher | Y | +| RTMP Player | Y | +| RTMP Pusher | Y | +| HTTP[S] | Y | +| WebSocket[S] | Y | + + + +## 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. +- 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. + +### 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:** +``` +git clone https://github.com/zlmediakit/ZLMediaKit.git +cd ZLMediaKit +git submodule update --init +``` + +### Build on linux + +- My environment + - Ubuntu16.04 64 bit and gcc5.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, + # step 4 can be manipulated directly. + + # 1、Install GCC5.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 + cd cmake-3.10.0-rc4 + ./configure + make -j4 + sudo make install + + # 3、Switch to high version GCC + scl enable devtoolset-4 bash + + # 4、build + cd ZLMediaKit + ./build_for_linux.sh + ``` + +### Build on macOS + +- My environment + - macOS Sierra(10.12.1) + xcode8.3.1 + - Homebrew 1.1.3 + - cmake 3.8.0 +- Guidance + + ``` + cd ZLMediaKit + ./build_for_mac.sh + ``` + +### Build on iOS +- You can generate Xcode projects and recompile them , [learn more](https://github.com/leetal/ios-cmake): + + ``` + cd ZLMediaKit + mkdir -p build + cd build + # Generate Xcode project, project file is in build directory + cmake .. -G Xcode -DCMAKE_TOOLCHAIN_FILE=../cmake/ios.toolchain.cmake -DPLATFORM=OS64COMBINED + ``` + + + + +### Build on Android + + Now you can open android sudio project in `Android` folder,this is a `aar library` and damo project. + +- My environment + - macOS Sierra(10.12.1) + xcode8.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) +- Guidance + + ``` + cd ZLMediaKit + export ANDROID_NDK_ROOT=/path/to/ndk + ./build_for_android.sh + ``` +### Build 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) + +- 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. +``` +## Usage + +- As server: + ```cpp + TcpServer::Ptr rtspSrv(new TcpServer()); + TcpServer::Ptr rtmpSrv(new TcpServer()); + TcpServer::Ptr httpSrv(new TcpServer()); + TcpServer::Ptr httpsSrv(new TcpServer()); + + rtspSrv->start(mINI::Instance()[Config::Rtsp::kPort]); + rtmpSrv->start(mINI::Instance()[Config::Rtmp::kPort]); + httpSrv->start(mINI::Instance()[Config::Http::kPort]); + httpsSrv->start(mINI::Instance()[Config::Http::kSSLPort]); + ``` + +- As player: + ```cpp + MediaPlayer::Ptr player(new MediaPlayer()); + weak_ptr weakPlayer = player; + player->setOnPlayResult([weakPlayer](const SockException &ex) { + InfoL << "OnPlayResult:" << ex.what(); + auto strongPlayer = weakPlayer.lock(); + if (ex || !strongPlayer) { + return; + } + + auto viedoTrack = strongPlayer->getTrack(TrackVideo); + if (!viedoTrack) { + WarnL << "none video Track!"; + return; + } + viedoTrack->addDelegate(std::make_shared([](const Frame::Ptr &frame) { + //please decode video here + })); + }); + + player->setOnShutdown([](const SockException &ex) { + ErrorL << "OnShutdown:" << ex.what(); + }); + + //rtp transport over tcp + (*player)[Client::kRtpType] = Rtsp::RTP_TCP; + player->play("rtsp://admin:jzan123456@192.168.0.122/"); + ``` +- As proxy server: + ```cpp + //support rtmp and rtsp url + //just support H264+AAC + auto urlList = {"rtmp://live.hkstv.hk.lxdns.com/live/hks", + "rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov"}; + map proxyMap; + int i=0; + for(auto url : urlList){ + PlayerProxy::Ptr player(new PlayerProxy("live",to_string(i++).data())); + player->play(url); + proxyMap.emplace(string(url),player); + } + ``` + +- As puser: + ```cpp + PlayerProxy::Ptr player(new PlayerProxy("app","stream")); + player->play("rtmp://live.hkstv.hk.lxdns.com/live/hks"); + + RtmpPusher::Ptr pusher; + NoticeCenter::Instance().addListener(nullptr,Config::Broadcast::kBroadcastRtmpSrcRegisted, + [&pusher](BroadcastRtmpSrcRegistedArgs){ + const_cast(pusher).reset(new RtmpPusher(app,stream)); + pusher->publish("rtmp://jizan.iok.la/live/test"); + }); + + ``` +## Docker Image +You can pull a pre-built docker image from Docker Hub and run with +```bash +docker run -id -p 1935:1935 -p 8080:80 gemfield/zlmediakit +``` + +Dockerfile is also supplied to build images on Ubuntu 16.04 +```bash +cd docker +docker build -t zlmediakit . +``` + +## Mirrors + +[ZLToolKit](http://git.oschina.net/xiahcu/ZLToolKit) + +[ZLMediaKit](http://git.oschina.net/xiahcu/ZLMediaKit) + + +## Licence + +``` +MIT License + +Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> +Copyright (c) 2019 Gemfield +Copyright (c) 2018 huohuo <913481084@qq.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +``` + + + +## Contact + - Email:<771730766@qq.com> + - QQ chat group:542509000 + + diff --git a/api/CMakeLists.txt b/api/CMakeLists.txt new file mode 100644 index 00000000..9cdb0d83 --- /dev/null +++ b/api/CMakeLists.txt @@ -0,0 +1,28 @@ +include_directories(include source) + +file(GLOB api_src_list include/*.h source/*.cpp source/*.h source/*.c) +if (IOS) + add_library(mk_api STATIC ${api_src_list}) + target_link_libraries(mk_api ${LINK_LIB_LIST}) +else () + add_library(mk_api SHARED ${api_src_list}) + if (WIN32) + add_definitions(-DMediaKitApi_EXPORTS) + endif () + + target_link_libraries(mk_api ${LINK_LIB_LIST}) + add_subdirectory(tests) + + #安装目录 + if (WIN32) + set(INSTALL_PATH_LIB $ENV{HOME}/${CMAKE_PROJECT_NAME}/lib) + set(INSTALL_PATH_INCLUDE $ENV{HOME}/${CMAKE_PROJECT_NAME}/include) + else () + set(INSTALL_PATH_LIB lib) + set(INSTALL_PATH_INCLUDE include) + endif () + + file(GLOB api_header_list include/*.h) + install(FILES ${api_header_list} DESTINATION ${INSTALL_PATH_INCLUDE}) + install(TARGETS mk_api ARCHIVE DESTINATION ${INSTALL_PATH_LIB} LIBRARY DESTINATION ${INSTALL_PATH_LIB}) +endif () \ No newline at end of file diff --git a/api/include/mk_common.h b/api/include/mk_common.h new file mode 100755 index 00000000..08100f69 --- /dev/null +++ b/api/include/mk_common.h @@ -0,0 +1,147 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef MK_COMMON_H +#define MK_COMMON_H + +#include + +#if defined(_WIN32) +#if defined(MediaKitApi_EXPORTS) + #define API_EXPORT __declspec(dllexport) + #else + #define API_EXPORT __declspec(dllimport) + #endif + + #define API_CALL __cdecl +#else +#define API_EXPORT +#define API_CALL +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + // 线程数 + int thread_num; + // 日志级别,支持0~4 + int log_level; + + // 配置文件是内容还是路径 + int ini_is_path; + // 配置文件内容或路径,可以为NULL,如果该文件不存在,那么将导出默认配置至该文件 + const char *ini; + + // ssl证书是内容还是路径 + int ssl_is_path; + // ssl证书内容或路径,可以为NULL + const char *ssl; + // 证书密码,可以为NULL + const char *ssl_pwd; +} mk_config; + +/** + * 初始化环境,调用该库前需要先调用此函数 + * @param cfg 库运行相关参数 + */ +API_EXPORT void API_CALL mk_env_init(const mk_config *cfg); + +/** + * 关闭所有服务器,请在main函数退出时调用 + */ +API_EXPORT void API_CALL mk_stop_all_server(); + +/** + * 基础类型参数版本的mk_env_init,为了方便其他语言调用 + * @param thread_num 线程数 + * @param log_level 日志级别,支持0~4 + * @param ini_is_path 配置文件是内容还是路径 + * @param ini 配置文件内容或路径,可以为NULL,如果该文件不存在,那么将导出默认配置至该文件 + * @param ssl_is_path ssl证书是内容还是路径 + * @param ssl ssl证书内容或路径,可以为NULL + * @param ssl_pwd 证书密码,可以为NULL + */ +API_EXPORT void API_CALL mk_env_init1( int thread_num, + int log_level, + int ini_is_path, + const char *ini, + int ssl_is_path, + const char *ssl, + const char *ssl_pwd); + +/** + * 设置配置项 + * @param key 配置项名 + * @param val 配置项值 + */ +API_EXPORT void API_CALL mk_set_option(const char *key, const char *val); + +/** + * 创建http[s]服务器 + * @param port htt监听端口,推荐80,传入0则随机分配 + * @param ssl 是否为ssl类型服务器 + * @return 0:失败,非0:端口号 + */ +API_EXPORT uint16_t API_CALL mk_http_server_start(uint16_t port, int ssl); + +/** + * 创建rtsp[s]服务器 + * @param port rtsp监听端口,推荐554,传入0则随机分配 + * @param ssl 是否为ssl类型服务器 + * @return 0:失败,非0:端口号 + */ +API_EXPORT uint16_t API_CALL mk_rtsp_server_start(uint16_t port, int ssl); + +/** + * 创建rtmp[s]服务器 + * @param port rtmp监听端口,推荐1935,传入0则随机分配 + * @param ssl 是否为ssl类型服务器 + * @return 0:失败,非0:端口号 + */ +API_EXPORT uint16_t API_CALL mk_rtmp_server_start(uint16_t port, int ssl); + +/** + * 创建rtp服务器 + * @param port rtp监听端口(包括udp/tcp) + * @return 0:失败,非0:端口号 + */ +API_EXPORT uint16_t API_CALL mk_rtp_server_start(uint16_t port); + +/** + * 创建shell服务器 + * @param port shell监听端口 + * @return 0:失败,非0:端口号 + */ +API_EXPORT uint16_t API_CALL mk_shell_server_start(uint16_t port); + +#ifdef __cplusplus +} +#endif + + +#endif /* MK_COMMON_H */ diff --git a/api/include/mk_events.h b/api/include/mk_events.h new file mode 100644 index 00000000..3c7bd4f7 --- /dev/null +++ b/api/include/mk_events.h @@ -0,0 +1,188 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef MK_EVENTS_H +#define MK_EVENTS_H + +#include "mk_common.h" +#include "mk_events_objects.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + /** + * 注册或反注册MediaSource事件广播 + * @param regist 注册为1,注销为0 + * @param sender 该MediaSource对象 + */ + void (API_CALL *on_mk_media_changed)(int regist, + const mk_media_source sender); + + /** + * 收到rtsp/rtmp推流事件广播,通过该事件控制推流鉴权 + * @see mk_publish_auth_invoker_do + * @param url_info 推流url相关信息 + * @param invoker 执行invoker返回鉴权结果 + * @param sender 该tcp客户端相关信息 + */ + void (API_CALL *on_mk_media_publish)(const mk_media_info url_info, + const mk_publish_auth_invoker invoker, + const mk_tcp_session sender); + + /** + * 播放rtsp/rtmp/http-flv/hls事件广播,通过该事件控制播放鉴权 + * @see mk_auth_invoker_do + * @param url_info 播放url相关信息 + * @param invoker 执行invoker返回鉴权结果 + * @param sender 播放客户端相关信息 + */ + void (API_CALL *on_mk_media_play)(const mk_media_info url_info, + const mk_auth_invoker invoker, + const mk_tcp_session sender); + + /** + * 未找到流后会广播该事件,请在监听该事件后去拉流或其他方式产生流,这样就能按需拉流了 + * @param url_info 播放url相关信息 + * @param sender 播放客户端相关信息 + */ + void (API_CALL *on_mk_media_not_found)(const mk_media_info url_info, + const mk_tcp_session sender); + + /** + * 某个流无人消费时触发,目的为了实现无人观看时主动断开拉流等业务逻辑 + * @param sender 该MediaSource对象 + */ + void (API_CALL *on_mk_media_no_reader)(const mk_media_source sender); + + /** + * 收到http api请求广播(包括GET/POST) + * @param parser http请求内容对象 + * @param invoker 执行该invoker返回http回复 + * @param consumed 置1则说明我们要处理该事件 + * @param sender http客户端相关信息 + */ + void (API_CALL *on_mk_http_request)(const mk_parser parser, + const mk_http_response_invoker invoker, + int *consumed, + const mk_tcp_session sender); + + /** + * 在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限 + * @param parser http请求内容对象 + * @param path 文件绝对路径 + * @param is_dir path是否为文件夹 + * @param invoker 执行invoker返回本次访问文件的结果 + * @param sender http客户端相关信息 + */ + void (API_CALL *on_mk_http_access)(const mk_parser parser, + const char *path, + int is_dir, + const mk_http_access_path_invoker invoker, + mk_tcp_session sender); + + /** + * 在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射 + * 在该事件中通过自行覆盖path参数,可以做到譬如根据虚拟主机或者app选择不同http根目录的目的 + * @param parser http请求内容对象 + * @param path 文件绝对路径,覆盖之可以重定向到其他文件 + * @param sender http客户端相关信息 + */ + void (API_CALL *on_mk_http_before_access)(const mk_parser parser, + char *path, + const mk_tcp_session sender); + + /** + * 该rtsp流是否需要认证?是的话调用invoker并传入realm,否则传入空的realm + * @param url_info 请求rtsp url相关信息 + * @param invoker 执行invoker返回是否需要rtsp专属认证 + * @param sender rtsp客户端相关信息 + */ + void (API_CALL *on_mk_rtsp_get_realm)(const mk_media_info url_info, + const mk_rtsp_get_realm_invoker invoker, + const mk_tcp_session sender); + + /** + * 请求认证用户密码事件,user_name为用户名,must_no_encrypt如果为true,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败 + * 获取到密码后请调用invoker并输入对应类型的密码和密码类型,invoker执行时会匹配密码 + * @param url_info 请求rtsp url相关信息 + * @param realm rtsp认证realm + * @param user_name rtsp认证用户名 + * @param must_no_encrypt 如果为true,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败 + * @param invoker 执行invoker返回rtsp专属认证的密码 + * @param sender rtsp客户端信息 + */ + void (API_CALL *on_mk_rtsp_auth)(const mk_media_info url_info, + const char *realm, + const char *user_name, + int must_no_encrypt, + const mk_rtsp_auth_invoker invoker, + const mk_tcp_session sender); + + /** + * 录制mp4分片文件成功后广播 + */ + void (API_CALL *on_mk_record_mp4)(const mk_mp4_info mp4); + + /** + * shell登录鉴权 + */ + void (API_CALL *on_mk_shell_login)(const char *user_name, + const char *passwd, + const mk_auth_invoker invoker, + const mk_tcp_session sender); + + /** + * 停止rtsp/rtmp/http-flv会话后流量汇报事件广播 + * @param url_info 播放url相关信息 + * @param total_bytes 耗费上下行总流量,单位字节数 + * @param total_seconds 本次tcp会话时长,单位秒 + * @param is_player 客户端是否为播放器 + * @param peer_ip 客户端ip + * @param peer_port 客户端端口号 + */ + void (API_CALL *on_mk_flow_report)(const mk_media_info url_info, + uint64_t total_bytes, + uint64_t total_seconds, + int is_player, + const char *peer_ip, + uint16_t peer_port); +} mk_events; + + +/** + * 监听ZLMediaKit里面的事件 + * @param events 各个事件的结构体,这个对象在内部会再拷贝一次,可以设置为null以便取消监听 + */ +API_EXPORT void API_CALL mk_events_listen(const mk_events *events); + + +#ifdef __cplusplus +} +#endif +#endif //MK_EVENTS_H + diff --git a/api/include/mk_events_objects.h b/api/include/mk_events_objects.h new file mode 100644 index 00000000..a385fdfb --- /dev/null +++ b/api/include/mk_events_objects.h @@ -0,0 +1,323 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef MK_EVENT_OBJECTS_H +#define MK_EVENT_OBJECTS_H +#include "mk_common.h" +#include "mk_tcp.h" +#ifdef __cplusplus +extern "C" { +#endif + +///////////////////////////////////////////MP4Info///////////////////////////////////////////// +//MP4Info对象的C映射 +typedef void* mk_mp4_info; +//MP4Info::ui64StartedTime +API_EXPORT uint64_t API_CALL mk_mp4_info_get_start_time(const mk_mp4_info ctx); +//MP4Info::ui64TimeLen +API_EXPORT uint64_t API_CALL mk_mp4_info_get_time_len(const mk_mp4_info ctx); +//MP4Info::ui64FileSize +API_EXPORT uint64_t API_CALL mk_mp4_info_get_file_size(const mk_mp4_info ctx); +//MP4Info::strFilePath +API_EXPORT const char* API_CALL mk_mp4_info_get_file_path(const mk_mp4_info ctx); +//MP4Info::strFileName +API_EXPORT const char* API_CALL mk_mp4_info_get_file_name(const mk_mp4_info ctx); +//MP4Info::strFolder +API_EXPORT const char* API_CALL mk_mp4_info_get_folder(const mk_mp4_info ctx); +//MP4Info::strUrl +API_EXPORT const char* API_CALL mk_mp4_info_get_url(const mk_mp4_info ctx); +//MP4Info::strVhost +API_EXPORT const char* API_CALL mk_mp4_info_get_vhost(const mk_mp4_info ctx); +//MP4Info::strAppName +API_EXPORT const char* API_CALL mk_mp4_info_get_app(const mk_mp4_info ctx); +//MP4Info::strStreamId +API_EXPORT const char* API_CALL mk_mp4_info_get_stream(const mk_mp4_info ctx); + +///////////////////////////////////////////Parser///////////////////////////////////////////// +//Parser对象的C映射 +typedef void* mk_parser; +//Parser::Method(),获取命令字,譬如GET/POST +API_EXPORT const char* API_CALL mk_parser_get_method(const mk_parser ctx); +//Parser::Url(),获取HTTP的访问url(不包括?后面的参数) +API_EXPORT const char* API_CALL mk_parser_get_url(const mk_parser ctx); +//Parser::FullUrl(),包括?后面的参数 +API_EXPORT const char* API_CALL mk_parser_get_full_url(const mk_parser ctx); +//Parser::Params(),?后面的参数字符串 +API_EXPORT const char* API_CALL mk_parser_get_url_params(const mk_parser ctx); +//Parser::getUrlArgs()["key"],获取?后面的参数中的特定参数 +API_EXPORT const char* API_CALL mk_parser_get_url_param(const mk_parser ctx,const char *key); +//Parser::Tail(),获取协议相关信息,譬如 HTTP/1.1 +API_EXPORT const char* API_CALL mk_parser_get_tail(const mk_parser ctx); +//Parser::getValues()["key"],获取HTTP头中特定字段 +API_EXPORT const char* API_CALL mk_parser_get_header(const mk_parser ctx,const char *key); +//Parser::Content(),获取HTTP body +API_EXPORT const char* API_CALL mk_parser_get_content(const mk_parser ctx, int *length); + +///////////////////////////////////////////MediaInfo///////////////////////////////////////////// +//MediaInfo对象的C映射 +typedef void* mk_media_info; +//MediaInfo::_param_strs +API_EXPORT const char* API_CALL mk_media_info_get_params(const mk_media_info ctx); +//MediaInfo::_schema +API_EXPORT const char* API_CALL mk_media_info_get_schema(const mk_media_info ctx); +//MediaInfo::_vhost +API_EXPORT const char* API_CALL mk_media_info_get_vhost(const mk_media_info ctx); +//MediaInfo::_app +API_EXPORT const char* API_CALL mk_media_info_get_app(const mk_media_info ctx); +//MediaInfo::_streamid +API_EXPORT const char* API_CALL mk_media_info_get_stream(const mk_media_info ctx); + + +///////////////////////////////////////////MediaSource///////////////////////////////////////////// +//MediaSource对象的C映射 +typedef void* mk_media_source; +//查找MediaSource的回调函数 +typedef void(API_CALL *on_mk_media_source_find_cb)(void *user_data, const mk_media_source ctx); + +//MediaSource::getSchema() +API_EXPORT const char* API_CALL mk_media_source_get_schema(const mk_media_source ctx); +//MediaSource::getVhost() +API_EXPORT const char* API_CALL mk_media_source_get_vhost(const mk_media_source ctx); +//MediaSource::getApp() +API_EXPORT const char* API_CALL mk_media_source_get_app(const mk_media_source ctx); +//MediaSource::getId() +API_EXPORT const char* API_CALL mk_media_source_get_stream(const mk_media_source ctx); +//MediaSource::readerCount() +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); +//MediaSource::close() +API_EXPORT int API_CALL mk_media_source_close(const mk_media_source ctx,int force); +//MediaSource::seekTo() +API_EXPORT int API_CALL mk_media_source_seek_to(const mk_media_source ctx,uint32_t stamp); + +//MediaSource::find() +API_EXPORT void API_CALL mk_media_source_find(const char *schema, + const char *vhost, + const char *app, + const char *stream, + void *user_data, + on_mk_media_source_find_cb cb); +//MediaSource::for_each_media() +API_EXPORT void API_CALL mk_media_source_for_each(void *user_data, on_mk_media_source_find_cb cb); + +///////////////////////////////////////////HttpBody///////////////////////////////////////////// +//HttpBody对象的C映射 +typedef void* mk_http_body; +/** + * 生成HttpStringBody + * @param str 字符串指针 + * @param len 字符串长度,为0则用strlen获取 + */ +API_EXPORT mk_http_body API_CALL mk_http_body_from_string(const char *str,int len); + +/** + * 生成HttpFileBody + * @param file_path 文件完整路径 + */ +API_EXPORT mk_http_body API_CALL mk_http_body_from_file(const char *file_path); + +/** + * 生成HttpMultiFormBody + * @param key_val 参数key-value + * @param file_path 文件完整路径 + */ +API_EXPORT mk_http_body API_CALL mk_http_body_from_multi_form(const char *key_val[],const char *file_path); + +/** + * 销毁HttpBody + */ +API_EXPORT void API_CALL mk_http_body_release(mk_http_body ctx); + +///////////////////////////////////////////HttpResponseInvoker///////////////////////////////////////////// +//HttpSession::HttpResponseInvoker对象的C映射 +typedef void* mk_http_response_invoker; + +/** + * HttpSession::HttpResponseInvoker(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body); + * @param response_code 譬如200 OK + * @param response_header 返回的http头,譬如 {"Content-Type","text/html",NULL} 必须以NULL结尾 + * @param response_body body对象 + */ +API_EXPORT void API_CALL mk_http_response_invoker_do(const mk_http_response_invoker ctx, + const char *response_code, + const char **response_header, + const mk_http_body response_body); + +/** + * HttpSession::HttpResponseInvoker(const string &codeOut, const StrCaseMap &headerOut, const string &body); + * @param response_code 譬如200 OK + * @param response_header 返回的http头,譬如 {"Content-Type","text/html",NULL} 必须以NULL结尾 + * @param response_content 返回的content部分,譬如一个网页内容 + */ +API_EXPORT void API_CALL mk_http_response_invoker_do_string(const mk_http_response_invoker ctx, + const char *response_code, + const char **response_header, + const char *response_content); +/** + * HttpSession::HttpResponseInvoker(const StrCaseMap &requestHeader,const StrCaseMap &responseHeader,const string &filePath); + * @param request_parser 请求事件中的mk_parser对象,用于提取其中http头中的Range字段,通过该字段先fseek然后再发送文件部分片段 + * @param response_header 返回的http头,譬如 {"Content-Type","text/html",NULL} 必须以NULL结尾 + * @param response_file_path 返回的content部分,譬如/path/to/html/file + */ +API_EXPORT void API_CALL mk_http_response_invoker_do_file(const mk_http_response_invoker ctx, + const mk_parser request_parser, + const char *response_header[], + const char *response_file_path); +/** +* 克隆mk_http_response_invoker对象,通过克隆对象为堆对象,可以实现跨线程异步执行mk_http_response_invoker_do +* 如果是同步执行mk_http_response_invoker_do,那么没必要克隆对象 +*/ +API_EXPORT mk_http_response_invoker API_CALL mk_http_response_invoker_clone(const mk_http_response_invoker ctx); + +/** + * 销毁堆上的克隆对象 + */ +API_EXPORT void API_CALL mk_http_response_invoker_clone_release(const mk_http_response_invoker ctx); + +///////////////////////////////////////////HttpAccessPathInvoker///////////////////////////////////////////// +//HttpSession::HttpAccessPathInvoker对象的C映射 +typedef void* mk_http_access_path_invoker; +/** + * HttpSession::HttpAccessPathInvoker(const string &errMsg,const string &accessPath, int cookieLifeSecond); + * @param err_msg 如果为空,则代表鉴权通过,否则为错误提示,可以为null + * @param access_path 运行或禁止访问的根目录,可以为null + * @param cookie_life_second 鉴权cookie有效期 + **/ +API_EXPORT void API_CALL mk_http_access_path_invoker_do(const mk_http_access_path_invoker ctx, + const char *err_msg, + const char *access_path, + int cookie_life_second); + +/** +* 克隆mk_http_access_path_invoker对象,通过克隆对象为堆对象,可以实现跨线程异步执行mk_http_access_path_invoker_do +* 如果是同步执行mk_http_access_path_invoker_do,那么没必要克隆对象 +*/ +API_EXPORT mk_http_access_path_invoker API_CALL mk_http_access_path_invoker_clone(const mk_http_access_path_invoker ctx); + +/** + * 销毁堆上的克隆对象 + */ +API_EXPORT void API_CALL mk_http_access_path_invoker_clone_release(const mk_http_access_path_invoker ctx); + +///////////////////////////////////////////RtspSession::onGetRealm///////////////////////////////////////////// +//RtspSession::onGetRealm对象的C映射 +typedef void* mk_rtsp_get_realm_invoker; +/** + * 执行RtspSession::onGetRealm + * @param realm 该rtsp流是否需要开启rtsp专属鉴权,至null或空字符串则不鉴权 + */ +API_EXPORT void API_CALL mk_rtsp_get_realm_invoker_do(const mk_rtsp_get_realm_invoker ctx, + const char *realm); + +/** +* 克隆mk_rtsp_get_realm_invoker对象,通过克隆对象为堆对象,可以实现跨线程异步执行mk_rtsp_get_realm_invoker_do +* 如果是同步执行mk_rtsp_get_realm_invoker_do,那么没必要克隆对象 +*/ +API_EXPORT mk_rtsp_get_realm_invoker API_CALL mk_rtsp_get_realm_invoker_clone(const mk_rtsp_get_realm_invoker ctx); + +/** + * 销毁堆上的克隆对象 + */ +API_EXPORT void API_CALL mk_rtsp_get_realm_invoker_clone_release(const mk_rtsp_get_realm_invoker ctx); + +///////////////////////////////////////////RtspSession::onAuth///////////////////////////////////////////// +//RtspSession::onAuth对象的C映射 +typedef void* mk_rtsp_auth_invoker; + +/** + * 执行RtspSession::onAuth + * @param encrypted 为true是则表明是md5加密的密码,否则是明文密码, 在请求明文密码时如果提供md5密码者则会导致认证失败 + * @param pwd_or_md5 明文密码或者md5加密的密码 + */ +API_EXPORT void API_CALL mk_rtsp_auth_invoker_do(const mk_rtsp_auth_invoker ctx, + int encrypted, + const char *pwd_or_md5); + +/** + * 克隆mk_rtsp_auth_invoker对象,通过克隆对象为堆对象,可以实现跨线程异步执行mk_rtsp_auth_invoker_do + * 如果是同步执行mk_rtsp_auth_invoker_do,那么没必要克隆对象 + */ +API_EXPORT mk_rtsp_auth_invoker API_CALL mk_rtsp_auth_invoker_clone(const mk_rtsp_auth_invoker ctx); + +/** + * 销毁堆上的克隆对象 + */ +API_EXPORT void API_CALL mk_rtsp_auth_invoker_clone_release(const mk_rtsp_auth_invoker ctx); + +///////////////////////////////////////////Broadcast::PublishAuthInvoker///////////////////////////////////////////// +//Broadcast::PublishAuthInvoker对象的C映射 +typedef void* mk_publish_auth_invoker; + +/** + * 执行Broadcast::PublishAuthInvoker + * @param err_msg 为空或null则代表鉴权成功 + * @param enable_rtxp rtmp推流时是否运行转rtsp;rtsp推流时,是否允许转rtmp + * @param enable_hls 是否允许转换hls + * @param enable_mp4 是否运行MP4录制 + */ +API_EXPORT void API_CALL mk_publish_auth_invoker_do(const mk_publish_auth_invoker ctx, + const char *err_msg, + int enable_rtxp, + int enable_hls, + int enable_mp4); + +/** + * 克隆mk_publish_auth_invoker对象,通过克隆对象为堆对象,可以实现跨线程异步执行mk_publish_auth_invoker_do + * 如果是同步执行mk_publish_auth_invoker_do,那么没必要克隆对象 + */ +API_EXPORT mk_publish_auth_invoker API_CALL mk_publish_auth_invoker_clone(const mk_publish_auth_invoker ctx); + +/** + * 销毁堆上的克隆对象 + */ +API_EXPORT void API_CALL mk_publish_auth_invoker_clone_release(const mk_publish_auth_invoker ctx); + +///////////////////////////////////////////Broadcast::AuthInvoker///////////////////////////////////////////// +//Broadcast::AuthInvoker对象的C映射 +typedef void* mk_auth_invoker; + +/** + * 执行Broadcast::AuthInvoker + * @param err_msg 为空或null则代表鉴权成功 + */ +API_EXPORT void API_CALL mk_auth_invoker_do(const mk_auth_invoker ctx, const char *err_msg); + +/** + * 克隆mk_auth_invoker对象,通过克隆对象为堆对象,可以实现跨线程异步执行mk_auth_invoker_do + * 如果是同步执行mk_auth_invoker_do,那么没必要克隆对象 + */ +API_EXPORT mk_auth_invoker API_CALL mk_auth_invoker_clone(const mk_auth_invoker ctx); + +/** + * 销毁堆上的克隆对象 + */ +API_EXPORT void API_CALL mk_auth_invoker_clone_release(const mk_auth_invoker ctx); + +#ifdef __cplusplus +} +#endif +#endif //MK_EVENT_OBJECTS_H diff --git a/api/include/mk_httpclient.h b/api/include/mk_httpclient.h new file mode 100755 index 00000000..7acb91dd --- /dev/null +++ b/api/include/mk_httpclient.h @@ -0,0 +1,174 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef MK_HTTPCLIENT_H_ +#define MK_HTTPCLIENT_H_ + +#include "mk_common.h" +#include "mk_events_objects.h" + +#ifdef __cplusplus +extern "C" { +#endif + +///////////////////////////////////////////HttpDownloader///////////////////////////////////////////// + +typedef void *mk_http_downloader; + +/** + * @param user_data 用户数据指针 + * @param code 错误代码,0代表成功 + * @param err_msg 错误提示 + * @param file_path 文件保存路径 + */ +typedef void(API_CALL *on_mk_download_complete)(void *user_data, int code, const char *err_msg, const char *file_path); + +/** + * 创建http[s]下载器 + * @return 下载器指针 + */ +API_EXPORT mk_http_downloader API_CALL mk_http_downloader_create(); + +/** + * 销毁http[s]下载器 + * @param ctx 下载器指针 + */ +API_EXPORT void API_CALL mk_http_downloader_release(mk_http_downloader ctx); + +/** + * 开始http[s]下载 + * @param ctx 下载器指针 + * @param url http[s]下载url + * @param file 文件保存路径 + * @param cb 回调函数 + * @param user_data 用户数据指针 + */ +API_EXPORT void API_CALL mk_http_downloader_start(mk_http_downloader ctx, const char *url, const char *file, on_mk_download_complete cb, void *user_data); + + +///////////////////////////////////////////HttpRequester///////////////////////////////////////////// +typedef void *mk_http_requester; + +/** + * http请求结果回调 + * 在code == 0时代表本次http会话是完整的(收到了http回复) + * 用户应该通过user_data获取到mk_http_requester对象 + * 然后通过mk_http_requester_get_response等函数获取相关回复数据 + * 在回调结束时,应该通过mk_http_requester_release函数销毁该对象 + * 或者调用mk_http_requester_clear函数后再复用该对象 + * @param user_data 用户数据指针 + * @param code 错误代码,0代表成功 + * @param err_msg 错误提示 + */ +typedef void(API_CALL *on_mk_http_requester_complete)(void *user_data, int code, const char *err_msg); + +/** + * 创建HttpRequester + */ +API_EXPORT mk_http_requester API_CALL mk_http_requester_create(); + +/** + * 在复用mk_http_requester对象时才需要用到此方法 + */ +API_EXPORT void API_CALL mk_http_requester_clear(mk_http_requester ctx); + +/** + * 销毁HttpRequester + * 如果调用了mk_http_requester_start函数且正在等待http回复, + * 也可以调用mk_http_requester_release方法取消本次http请求 + */ +API_EXPORT void API_CALL mk_http_requester_release(mk_http_requester ctx); + +/** + * 设置HTTP方法,譬如GET/POST + */ +API_EXPORT void API_CALL mk_http_requester_set_method(mk_http_requester ctx,const char *method); + +/** + * 批量设置设置HTTP头 + * @param header 譬如 {"Content-Type","text/html",NULL} 必须以NULL结尾 + */ +API_EXPORT void API_CALL mk_http_requester_set_header(mk_http_requester ctx, const char *header[]); + +/** + * 添加HTTP头 + * @param key 譬如Content-Type + * @param value 譬如 text/html + * @param force 如果已经存在该key,是否强制替换 + */ +API_EXPORT void API_CALL mk_http_requester_add_header(mk_http_requester ctx,const char *key,const char *value,int force); + +/** + * 设置消息体, + * @param body mk_http_body对象,通过mk_http_body_from_string等函数生成,使用完毕后请调用mk_http_body_release释放之 + */ +API_EXPORT void API_CALL mk_http_requester_set_body(mk_http_requester ctx, mk_http_body body); + +/** + * 在收到HTTP回复后可调用该方法获取状态码 + * @return 譬如 200 OK + */ +API_EXPORT const char* API_CALL mk_http_requester_get_response_status(mk_http_requester ctx); + +/** + * 在收到HTTP回复后可调用该方法获取响应HTTP头 + * @param key HTTP头键名 + * @return HTTP头键值 + */ +API_EXPORT const char* API_CALL mk_http_requester_get_response_header(mk_http_requester ctx,const char *key); + +/** + * 在收到HTTP回复后可调用该方法获取响应HTTP body + * @param length 返回body长度,可以为null + * @return body指针 + */ +API_EXPORT const char* API_CALL mk_http_requester_get_response_body(mk_http_requester ctx, int *length); + +/** + * 在收到HTTP回复后可调用该方法获取响应 + * @return 响应对象 + */ +API_EXPORT mk_parser API_CALL mk_http_requester_get_response(mk_http_requester ctx); + +/** + * 设置回调函数 + * @param cb 回调函数,不能为空 + * @param user_data 用户数据指针 + */ +API_EXPORT void API_CALL mk_http_requester_set_cb(mk_http_requester ctx,on_mk_http_requester_complete cb, void *user_data); + +/** + * 开始url请求 + * @param url 请求url,支持http/https + * @param timeout_second 最大超时时间 + */ +API_EXPORT void API_CALL mk_http_requester_start(mk_http_requester ctx,const char *url, float timeout_second); + +#ifdef __cplusplus +} +#endif + +#endif /* MK_HTTPCLIENT_H_ */ diff --git a/api/include/mk_media.h b/api/include/mk_media.h new file mode 100755 index 00000000..4f56a4f8 --- /dev/null +++ b/api/include/mk_media.h @@ -0,0 +1,161 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef MK_MEDIA_H_ +#define MK_MEDIA_H_ + +#include "mk_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void *mk_media; + +/** + * 创建一个媒体源 + * @param vhost 虚拟主机名,一般为__defaultVhost__ + * @param app 应用名,推荐为live + * @param stream 流id,例如camera + * @param duration 时长(单位秒),直播则为0 + * @param hls_enabled 是否生成hls + * @param mp4_enabled 是否生成mp4 + * @return 对象指针 + */ +API_EXPORT mk_media API_CALL mk_media_create(const char *vhost, const char *app, const char *stream, float duration, int hls_enabled, int mp4_enabled); + +/** + * 销毁媒体源 + * @param ctx 对象指针 + */ +API_EXPORT void API_CALL mk_media_release(mk_media ctx); + +/** + * 添加h264视频轨道 + * @param ctx 对象指针 + * @param width 视频宽度 + * @param height 视频高度 + * @param fps 视频fps + */ +API_EXPORT void API_CALL mk_media_init_h264(mk_media ctx, int width, int height, int fps); + +/** + * 添加h265视频轨道 + * @param ctx 对象指针 + * @param width 视频宽度 + * @param height 视频高度 + * @param fps 视频fps + */ +API_EXPORT void API_CALL mk_media_init_h265(mk_media ctx, int width, int height, int fps); + +/** + * 添加aac音频轨道 + * @param ctx 对象指针 + * @param channel 通道数 + * @param sample_bit 采样位数,只支持16 + * @param sample_rate 采样率 + * @param profile aac编码profile,在不输入adts头时用于生产adts头 + */ +API_EXPORT void API_CALL mk_media_init_aac(mk_media ctx, int channel, int sample_bit, int sample_rate, int profile); + +/** + * 初始化h264/h265/aac完毕后调用此函数, + * 在单track(只有音频或视频)时,因为ZLMediaKit不知道后续是否还要添加track,所以会多等待3秒钟 + * 如果产生的流是单Track类型,请调用此函数以便加快流生成速度,当然不调用该函数,影响也不大(会多等待3秒) + * @param ctx 对象指针 + */ +API_EXPORT void API_CALL mk_media_init_complete(mk_media ctx); + +/** + * 输入单帧H264视频,帧起始字节00 00 01,00 00 00 01均可 + * @param ctx 对象指针 + * @param data 单帧H264数据 + * @param len 单帧H264数据字节数 + * @param dts 解码时间戳,单位毫秒 + * @param pts 播放时间戳,单位毫秒 + */ +API_EXPORT void API_CALL mk_media_input_h264(mk_media ctx, void *data, int len, uint32_t dts, uint32_t pts); + +/** + * 输入单帧H265视频,帧起始字节00 00 01,00 00 00 01均可 + * @param ctx 对象指针 + * @param data 单帧H265数据 + * @param len 单帧H265数据字节数 + * @param dts 解码时间戳,单位毫秒 + * @param pts 播放时间戳,单位毫秒 + */ +API_EXPORT void API_CALL mk_media_input_h265(mk_media ctx, void *data, int len, uint32_t dts, uint32_t pts); + +/** + * 输入单帧AAC音频 + * @param ctx 对象指针 + * @param data 单帧AAC数据 + * @param len 单帧AAC数据字节数 + * @param dts 时间戳,毫秒 + * @param with_adts_header data中是否包含7个字节的adts头 + */ +API_EXPORT void API_CALL mk_media_input_aac(mk_media ctx, void *data, int len, uint32_t dts, int with_adts_header); + +/** + * 输入单帧AAC音频(单独指定adts头) + * @param ctx 对象指针 + * @param data 不包含adts头的单帧AAC数据 + * @param len 单帧AAC数据字节数 + * @param dts 时间戳,毫秒 + * @param adts adts头 + */ +API_EXPORT void API_CALL mk_media_input_aac1(mk_media ctx, void *data, int len, uint32_t dts, void *adts); + +/** + * 在调用对应的MediaSource.close()时会触发该回调 + * 你可以在该事件中做清理工作(比如说关闭摄像头,同时调用mk_media_release函数销毁该对象) + * @param user_data 用户数据指针,通过mk_media_set_on_close函数设置 + * @return 返回0告知事件触发者关闭媒体失败,非0代表成功 + */ +typedef int(API_CALL *on_mk_media_close)(void *user_data); + +/** + * 监听MediaSource.close()事件 + * 在选择关闭一个MediaSource时,将会最终触发到该回调 + * 你可以通过该事件选择删除对象,当然你在该事件中也可以什么都不做 + * @param ctx 对象指针 + * @param cb 回调指针 + * @param user_data 用户数据指针 + */ +API_EXPORT void API_CALL mk_media_set_on_close(mk_media ctx, on_mk_media_close cb, void *user_data); + +/** + * 获取总的观看人数 + * @param ctx 对象指针 + * @return 观看人数 + */ +API_EXPORT int API_CALL mk_media_total_reader_count(mk_media ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* MK_MEDIA_H_ */ diff --git a/api/include/mk_mediakit.h b/api/include/mk_mediakit.h new file mode 100755 index 00000000..c8911a1b --- /dev/null +++ b/api/include/mk_mediakit.h @@ -0,0 +1,42 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef MK_API_H_ +#define MK_API_H_ + +#include "mk_common.h" +#include "mk_httpclient.h" +#include "mk_media.h" +#include "mk_proxyplayer.h" +#include "mk_recorder.h" +#include "mk_player.h" +#include "mk_pusher.h" +#include "mk_events.h" +#include "mk_tcp.h" +#include "mk_util.h" +#include "mk_thread.h" + +#endif /* MK_API_H_ */ diff --git a/api/include/mk_player.h b/api/include/mk_player.h new file mode 100755 index 00000000..13ac2bbc --- /dev/null +++ b/api/include/mk_player.h @@ -0,0 +1,176 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef MK_PLAYER_H_ +#define MK_PLAYER_H_ + +#include "mk_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* mk_player; + +/** + * 播放结果或播放中断事件的回调 + * @param user_data 用户数据指针 + * @param err_code 错误代码,0为成功 + * @param err_msg 错误提示 + */ +typedef void(API_CALL *on_mk_play_event)(void *user_data,int err_code,const char *err_msg); + +/** + * 收到音视频数据回调 + * @param user_data 用户数据指针 + * @param track_type 0:视频,1:音频 + * @param codec_id 0:H264,1:H265,2:AAC + * @param data 数据指针 + * @param len 数据长度 + * @param dts 解码时间戳,单位毫秒 + * @param pts 显示时间戳,单位毫秒 + */ +typedef void(API_CALL *on_mk_play_data)(void *user_data,int track_type,int codec_id,void *data,int len,uint32_t dts,uint32_t pts); + +/** + * 创建一个播放器,支持rtmp[s]/rtsp[s] + * @return 播放器指针 + */ +API_EXPORT mk_player API_CALL mk_player_create(); + +/** + * 销毁播放器 + * @param ctx 播放器指针 + */ +API_EXPORT void API_CALL mk_player_release(mk_player ctx); + +/** + * 设置播放器配置选项 + * @param ctx 播放器指针 + * @param key 配置项键,支持 net_adapter/rtp_type/rtsp_user/rtsp_pwd/protocol_timeout_ms/media_timeout_ms/beat_interval_ms/max_analysis_ms + * @param val 配置项值,如果是整形,需要转换成统一转换成string + */ +API_EXPORT void API_CALL mk_player_set_option(mk_player ctx, const char *key, const char *val); + +/** + * 开始播放url + * @param ctx 播放器指针 + * @param url rtsp[s]/rtmp[s] url + */ +API_EXPORT void API_CALL mk_player_play(mk_player ctx, const char *url); + +/** + * 暂停或恢复播放,仅对点播有用 + * @param ctx 播放器指针 + * @param pause 1:暂停播放,0:恢复播放 + */ +API_EXPORT void API_CALL mk_player_pause(mk_player ctx, int pause); + +/** + * 设置点播进度条 + * @param ctx 对象指针 + * @param progress 取值范围未 0.0~1.0 + */ +API_EXPORT void API_CALL mk_player_seekto(mk_player ctx, float progress); + +/** + * 设置播放器开启播放结果回调函数 + * @param ctx 播放器指针 + * @param cb 回调函数指针,不得为null + * @param user_data 用户数据指针 + */ +API_EXPORT void API_CALL mk_player_set_on_result(mk_player ctx, on_mk_play_event cb, void *user_data); + +/** + * 设置播放被异常中断的回调 + * @param ctx 播放器指针 + * @param cb 回调函数指针,不得为null + * @param user_data 用户数据指针 + */ +API_EXPORT void API_CALL mk_player_set_on_shutdown(mk_player ctx, on_mk_play_event cb, void *user_data); + +/** + * 设置音视频数据回调函数 + * 该接口只能在播放成功事件触发后才能调用 + * @param ctx 播放器指针 + * @param cb 回调函数指针,不得为null + * @param user_data 用户数据指针 + */ +API_EXPORT void API_CALL mk_player_set_on_data(mk_player ctx, on_mk_play_data cb, void *user_data); + +/** + * 获取视频宽度 + */ +API_EXPORT int API_CALL mk_player_video_width(mk_player ctx); + +/** + * 获取视频高度 + */ +API_EXPORT int API_CALL mk_player_video_height(mk_player ctx); + +/** + * 获取视频帧率 + */ +API_EXPORT int API_CALL mk_player_video_fps(mk_player ctx); + +/** + * 获取音频采样率 + */ +API_EXPORT int API_CALL mk_player_audio_samplerate(mk_player ctx); + +/** + * 获取音频采样位数,一般为16 + */ +API_EXPORT int API_CALL mk_player_audio_bit(mk_player ctx); + +/** + * 获取音频通道数 + */ +API_EXPORT int API_CALL mk_player_audio_channel(mk_player ctx); + +/** + * 获取点播节目时长,如果是直播返回0,否则返回秒数 + */ +API_EXPORT float API_CALL mk_player_duration(mk_player ctx); + +/** + * 获取点播播放进度,取值范围未 0.0~1.0 + */ +API_EXPORT float API_CALL mk_player_progress(mk_player ctx); + +/** + * 获取丢包率,rtsp时有效 + * @param ctx 对象指针 + * @param track_type 0:视频,1:音频 + */ +API_EXPORT float API_CALL mk_player_loss_rate(mk_player ctx, int track_type); + + +#ifdef __cplusplus +} +#endif + +#endif /* MK_PLAYER_H_ */ diff --git a/api/include/mk_proxyplayer.h b/api/include/mk_proxyplayer.h new file mode 100644 index 00000000..a8495eda --- /dev/null +++ b/api/include/mk_proxyplayer.h @@ -0,0 +1,75 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef MK_PROXY_PLAYER_H_ +#define MK_PROXY_PLAYER_H_ + +#include "mk_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void *mk_proxy_player; + +/** + * 创建一个代理播放器 + * @param vhost 虚拟主机名,一般为__defaultVhost__ + * @param app 应用名 + * @param stream 流名 + * @param rtp_type rtsp播放方式:RTP_TCP = 0, RTP_UDP = 1, RTP_MULTICAST = 2 + * @param hls_enabled 是否生成hls + * @param mp4_enabled 是否生成mp4 + * @return 对象指针 + */ +API_EXPORT mk_proxy_player API_CALL mk_proxy_player_create(const char *vhost, const char *app, const char *stream, int hls_enabled, int mp4_enabled); + +/** + * 销毁代理播放器 + * @param ctx 对象指针 + */ +API_EXPORT void API_CALL mk_proxy_player_release(mk_proxy_player ctx); + +/** + * 设置代理播放器配置选项 + * @param ctx 代理播放器指针 + * @param key 配置项键,支持 net_adapter/rtp_type/rtsp_user/rtsp_pwd/protocol_timeout_ms/media_timeout_ms/beat_interval_ms/max_analysis_ms + * @param val 配置项值,如果是整形,需要转换成统一转换成string + */ +API_EXPORT void API_CALL mk_proxy_player_set_option(mk_proxy_player ctx, const char *key, const char *val); + +/** + * 开始播放 + * @param ctx 对象指针 + * @param url 播放url,支持rtsp/rtmp + */ +API_EXPORT void API_CALL mk_proxy_player_play(mk_proxy_player ctx, const char *url); + +#ifdef __cplusplus +} +#endif + +#endif /* MK_PROXY_PLAYER_H_ */ diff --git a/api/include/mk_pusher.h b/api/include/mk_pusher.h new file mode 100644 index 00000000..cc4f2645 --- /dev/null +++ b/api/include/mk_pusher.h @@ -0,0 +1,100 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#ifndef MK_PUSHER_H +#define MK_PUSHER_H + +#include "mk_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* mk_pusher; + +/** + * 推流结果或推流中断事件的回调 + * @param user_data 用户数据指针 + * @param err_code 错误代码,0为成功 + * @param err_msg 错误提示 + */ +typedef void(API_CALL *on_mk_push_event)(void *user_data,int err_code,const char *err_msg); + +/** + * 绑定的MediaSource对象并创建rtmp[s]/rtsp[s]推流器 + * MediaSource通过mk_media_create或mk_proxy_player_create生成 + * 该MediaSource对象必须已注册 + * + * @param schema 绑定的MediaSource对象所属协议,支持rtsp/rtmp + * @param vhost 绑定的MediaSource对象的虚拟主机,一般为__defaultVhost__ + * @param app 绑定的MediaSource对象的应用名,一般为live + * @param stream 绑定的MediaSource对象的流id + * @return 对象指针 + */ +API_EXPORT mk_pusher API_CALL mk_pusher_create(const char *schema,const char *vhost,const char *app, const char *stream); + +/** + * 释放推流器 + * @param ctx 推流器指针 + */ +API_EXPORT void API_CALL mk_pusher_release(mk_pusher ctx); + +/** + * 设置推流器配置选项 + * @param ctx 推流器指针 + * @param key 配置项键,支持 net_adapter/rtp_type/rtsp_user/rtsp_pwd/protocol_timeout_ms/media_timeout_ms/beat_interval_ms/max_analysis_ms + * @param val 配置项值,如果是整形,需要转换成统一转换成string + */ +API_EXPORT void API_CALL mk_pusher_set_option(mk_pusher ctx, const char *key, const char *val); + +/** + * 开始推流 + * @param ctx 推流器指针 + * @param url 推流地址,支持rtsp[s]/rtmp[s] + */ +API_EXPORT void API_CALL mk_pusher_publish(mk_pusher ctx,const char *url); + +/** + * 设置推流器推流结果回调函数 + * @param ctx 推流器指针 + * @param cb 回调函数指针,不得为null + * @param user_data 用户数据指针 + */ +API_EXPORT void API_CALL mk_pusher_set_on_result(mk_pusher ctx, on_mk_push_event cb, void *user_data); + +/** + * 设置推流被异常中断的回调 + * @param ctx 推流器指针 + * @param cb 回调函数指针,不得为null + * @param user_data 用户数据指针 + */ +API_EXPORT void API_CALL mk_pusher_set_on_shutdown(mk_pusher ctx, on_mk_push_event cb, void *user_data); + +#ifdef __cplusplus +} +#endif +#endif //MK_PUSHER_H diff --git a/api/include/mk_recorder.h b/api/include/mk_recorder.h new file mode 100644 index 00000000..c60e0e54 --- /dev/null +++ b/api/include/mk_recorder.h @@ -0,0 +1,108 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef MK_RECORDER_API_H_ +#define MK_RECORDER_API_H_ + +#include "mk_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +///////////////////////////////////////////flv录制///////////////////////////////////////////// + +typedef void* mk_flv_recorder; + +/** + * 创建flv录制器 + * @return + */ +API_EXPORT mk_flv_recorder API_CALL mk_flv_recorder_create(); + +/** + * 释放flv录制器 + * @param ctx + */ +API_EXPORT void API_CALL mk_flv_recorder_release(mk_flv_recorder ctx); + +/** + * 开始录制flv + * @param ctx flv录制器 + * @param vhost 虚拟主机 + * @param app 绑定的RtmpMediaSource的 app名 + * @param stream 绑定的RtmpMediaSource的 stream名 + * @param file_path 文件存放地址 + * @return 0:开始超过,-1:失败,打开文件失败或该RtmpMediaSource不存在 + */ +API_EXPORT int API_CALL mk_flv_recorder_start(mk_flv_recorder ctx, const char *vhost, const char *app, const char *stream, const char *file_path); + + +///////////////////////////////////////////hls/mp4录制///////////////////////////////////////////// + +/** + * 获取录制状态 + * @param type 0:hls,1:MP4 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream 流id + * @return 录制状态,0:未录制,1:等待MediaSource注册,注册成功后立即开始录制,2:MediaSource已注册,并且正在录制 + */ +API_EXPORT int API_CALL mk_recorder_status(int type, const char *vhost, const char *app, const char *stream); + +/** + * 开始录制 + * @param type 0:hls,1:MP4 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream 流id + * @param customized_path 录像文件保存自定义目录,默认为空或null则自动生成 + * @param wait_for_record 是否等待流注册后再录制,未注册时,置false将返回失败 + * @param continue_record 流注销时是否继续等待录制还是立即停止录制 + * @return 0代表成功,负数代表失败 + */ +API_EXPORT int API_CALL mk_recorder_start(int type, const char *vhost, const char *app, const char *stream, const char *customized_path, int wait_for_record, int continue_record); + +/** + * 停止录制 + * @param type 0:hls,1:MP4 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream 流id + * @return 1:成功,0:失败 + */ +API_EXPORT int API_CALL mk_recorder_stop(int type, const char *vhost, const char *app, const char *stream); + +/** + * 停止所有录制,一般程序退出时调用 + */ +API_EXPORT void API_CALL mk_recorder_stop_all(); + +#ifdef __cplusplus +} +#endif + +#endif /* MK_RECORDER_API_H_ */ diff --git a/api/include/mk_tcp.h b/api/include/mk_tcp.h new file mode 100644 index 00000000..34ffb342 --- /dev/null +++ b/api/include/mk_tcp.h @@ -0,0 +1,226 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef MK_TCP_H +#define MK_TCP_H + +#include "mk_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +///////////////////////////////////////////TcpSession///////////////////////////////////////////// +//TcpSession对象的C映射 +typedef void* mk_tcp_session; +//TcpSession::safeShutdown() +API_EXPORT void API_CALL mk_tcp_session_shutdown(const mk_tcp_session ctx,int err,const char *err_msg); +//TcpSession::get_peer_ip() +API_EXPORT const char* API_CALL mk_tcp_session_peer_ip(const mk_tcp_session ctx); +//TcpSession::get_local_ip() +API_EXPORT const char* API_CALL mk_tcp_session_local_ip(const mk_tcp_session ctx); +//TcpSession::get_peer_port() +API_EXPORT uint16_t API_CALL mk_tcp_session_peer_port(const mk_tcp_session ctx); +//TcpSession::get_local_port() +API_EXPORT uint16_t API_CALL mk_tcp_session_local_port(const mk_tcp_session ctx); +//TcpSession::send() +API_EXPORT void API_CALL mk_tcp_session_send(const mk_tcp_session ctx,const char *data,int len); +//切换到该对象所在线程后再TcpSession::send() +API_EXPORT void API_CALL mk_tcp_session_send_safe(const mk_tcp_session ctx,const char *data,int len); + +///////////////////////////////////////////自定义tcp服务///////////////////////////////////////////// + +typedef struct { + /** + * 收到mk_tcp_session创建对象 + * @param server_port 服务器端口号 + * @param session 会话处理对象 + */ + void (API_CALL *on_mk_tcp_session_create)(uint16_t server_port,mk_tcp_session session); + + /** + * 收到客户端发过来的数据 + * @param server_port 服务器端口号 + * @param session 会话处理对象 + * @param data 数据指针 + * @param len 数据长度 + */ + void (API_CALL *on_mk_tcp_session_data)(uint16_t server_port,mk_tcp_session session,const char *data,int len); + + /** + * 每隔2秒的定时器,用于管理超时等任务 + * @param server_port 服务器端口号 + * @param session 会话处理对象 + */ + void (API_CALL *on_mk_tcp_session_manager)(uint16_t server_port,mk_tcp_session session); + + /** + * 一般由于客户端断开tcp触发 + * @param server_port 服务器端口号 + * @param session 会话处理对象 + * @param code 错误代码 + * @param msg 错误提示 + */ + void (API_CALL *on_mk_tcp_session_disconnect)(uint16_t server_port,mk_tcp_session session,int code,const char *msg); +} mk_tcp_session_events; + + +typedef enum { + //普通的tcp + mk_type_tcp = 0, + //ssl类型的tcp + mk_type_ssl = 1, + //基于websocket的连接 + mk_type_ws = 2, + //基于ssl websocket的连接 + mk_type_wss = 3 +}mk_tcp_type; + +/** + * tcp会话对象附着用户数据 + * 该函数只对mk_tcp_server_server_start启动的服务类型有效 + * @param session 会话对象 + * @param user_data 用户数据指针 + */ +API_EXPORT void API_CALL mk_tcp_session_set_user_data(mk_tcp_session session,void *user_data); + +/** + * 获取tcp会话对象上附着的用户数据 + * 该函数只对mk_tcp_server_server_start启动的服务类型有效 + * @param session tcp会话对象 + * @return 用户数据指针 + */ +API_EXPORT void* API_CALL mk_tcp_session_get_user_data(mk_tcp_session session); + +/** + * 开启tcp服务器 + * @param port 监听端口号,0则为随机 + * @param type 服务器类型 + */ +API_EXPORT uint16_t API_CALL mk_tcp_server_start(uint16_t port, mk_tcp_type type); + +/** + * 监听tcp服务器事件 + */ +API_EXPORT void API_CALL mk_tcp_server_events_listen(const mk_tcp_session_events *events); + + +///////////////////////////////////////////自定义tcp客户端///////////////////////////////////////////// + +typedef void* mk_tcp_client; + +typedef struct { + /** + * tcp客户端连接服务器成功或失败回调 + * @param client tcp客户端 + * @param code 0为连接成功,否则为失败原因 + * @param msg 连接失败错误提示 + */ + void (API_CALL *on_mk_tcp_client_connect)(mk_tcp_client client,int code,const char *msg); + + /** + * tcp客户端与tcp服务器之间断开回调 + * 一般是eof事件导致 + * @param client tcp客户端 + * @param code 错误代码 + * @param msg 错误提示 + */ + void (API_CALL *on_mk_tcp_client_disconnect)(mk_tcp_client client,int code,const char *msg); + + /** + * 收到tcp服务器发来的数据 + * @param client tcp客户端 + * @param data 数据指针 + * @param len 数据长度 + */ + void (API_CALL *on_mk_tcp_client_data)(mk_tcp_client client,const char *data,int len); + + /** + * 每隔2秒的定时器,用于管理超时等任务 + * @param client tcp客户端 + */ + void (API_CALL *on_mk_tcp_client_manager)(mk_tcp_client client); +} mk_tcp_client_events; + +/** + * 创建tcp客户端 + * @param events 回调函数结构体 + * @param user_data 用户数据指针 + * @param type 客户端类型 + * @return 客户端对象 + */ +API_EXPORT mk_tcp_client API_CALL mk_tcp_client_create(mk_tcp_client_events *events, mk_tcp_type type); + +/** + * 释放tcp客户端 + * @param ctx 客户端对象 + */ +API_EXPORT void API_CALL mk_tcp_client_release(mk_tcp_client ctx); + +/** + * 发起连接 + * @param ctx 客户端对象 + * @param host 服务器ip或域名 + * @param port 服务器端口号 + * @param time_out_sec 超时时间 + */ +API_EXPORT void API_CALL mk_tcp_client_connect(mk_tcp_client ctx, const char *host, uint16_t port, float time_out_sec); + +/** + * 非线程安全的发送数据 + * 开发者如果能确保在本对象网络线程内,可以调用此此函数 + * @param ctx 客户端对象 + * @param data 数据指针 + * @param len 数据长度,等于0时,内部通过strlen获取 + */ +API_EXPORT void API_CALL mk_tcp_client_send(mk_tcp_client ctx, const char *data, int len); + +/** + * 切换到本对象的网络线程后再发送数据 + * @param ctx 客户端对象 + * @param data 数据指针 + * @param len 数据长度,等于0时,内部通过strlen获取 + */ +API_EXPORT void API_CALL mk_tcp_client_send_safe(mk_tcp_client ctx, const char *data, int len); + +/** + * 客户端附着用户数据 + * @param ctx 客户端对象 + * @param user_data 用户数据指针 + */ +API_EXPORT void API_CALL mk_tcp_client_set_user_data(mk_tcp_client ctx,void *user_data); + +/** + * 获取客户端对象上附着的用户数据 + * @param ctx 客户端对象 + * @return 用户数据指针 + */ +API_EXPORT void* API_CALL mk_tcp_client_get_user_data(mk_tcp_client ctx); + +#ifdef __cplusplus +} +#endif +#endif //MK_TCP_H diff --git a/api/include/mk_thread.h b/api/include/mk_thread.h new file mode 100644 index 00000000..2f9b0f37 --- /dev/null +++ b/api/include/mk_thread.h @@ -0,0 +1,102 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef MK_THREAD_H +#define MK_THREAD_H + +#include +#include "mk_common.h" +#include "mk_tcp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +///////////////////////////////////////////事件线程///////////////////////////////////////////// +typedef void* mk_thread; + +/** + * 获取tcp会话对象所在事件线程 + * @param ctx tcp会话对象 + * @return 对象所在事件线程 + */ +API_EXPORT mk_thread API_CALL mk_thread_from_tcp_session(mk_tcp_session ctx); + +/** + * 获取tcp客户端对象所在事件线程 + * @param ctx tcp客户端 + * @return 对象所在事件线程 + */ +API_EXPORT mk_thread API_CALL mk_thread_from_tcp_client(mk_tcp_client ctx); + +///////////////////////////////////////////线程切换///////////////////////////////////////////// +typedef void (API_CALL *on_mk_async)(void *user_data); + +/** + * 切换到事件线程并异步执行 + * @param ctx 事件线程 + * @param cb 回调函数 + * @param user_data 用户数据指针 + */ +API_EXPORT void API_CALL mk_async_do(mk_thread ctx,on_mk_async cb, void *user_data); + +/** + * 切换到事件线程并同步执行 + * @param ctx 事件线程 + * @param cb 回调函数 + * @param user_data 用户数据指针 + */ +API_EXPORT void API_CALL mk_sync_do(mk_thread ctx,on_mk_async cb, void *user_data); + +///////////////////////////////////////////定时器///////////////////////////////////////////// +typedef void* mk_timer; + +/** + * 定时器触发事件 + * @return 下一次触发延时(单位毫秒),返回0则不再重复 + */ +typedef uint64_t (API_CALL *on_mk_timer)(void *user_data); + +/** + * 创建定时器 + * @param ctx 线程对象 + * @param delay_ms 执行延时,单位毫秒 + * @param cb 回调函数 + * @param user_data 用户数据指针 + * @return 定时器对象 + */ +API_EXPORT mk_timer API_CALL mk_timer_create(mk_thread ctx,uint64_t delay_ms, on_mk_timer cb, void *user_data); + +/** + * 销毁和取消定时器 + * @param ctx 定时器对象 + */ +API_EXPORT void API_CALL mk_timer_release(mk_timer ctx); + +#ifdef __cplusplus +} +#endif +#endif //MK_THREAD_H diff --git a/api/include/mk_util.h b/api/include/mk_util.h new file mode 100644 index 00000000..e87599f8 --- /dev/null +++ b/api/include/mk_util.h @@ -0,0 +1,94 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef MK_UTIL_H +#define MK_UTIL_H + +#include +#include "mk_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * 获取本程序可执行文件路径 + * @return 文件路径,使用完后需要自己free + */ +API_EXPORT char* API_CALL mk_util_get_exe_path(); + +/** + * 获取本程序可执行文件相同目录下文件的绝对路径 + * @param relative_path 同目录下文件的路径相对,可以为null + * @return 文件路径,使用完后需要自己free + */ +API_EXPORT char* API_CALL mk_util_get_exe_dir(const char *relative_path); + +/** + * 获取unix标准的系统时间戳 + * @return 当前系统时间戳 + */ +API_EXPORT uint64_t API_CALL mk_util_get_current_millisecond(); + +/** + * 获取时间字符串 + * @param fmt 时间格式,譬如%Y-%m-%d %H:%M:%S + * @return 时间字符串,使用完后需要自己free + */ +API_EXPORT char* API_CALL mk_util_get_current_time_string(const char *fmt); + +/** + * 打印二进制为字符串 + * @param buf 二进制数据 + * @param len 数据长度 + * @return 可打印的调试信息,使用完后需要自己free + */ +API_EXPORT char* API_CALL mk_util_hex_dump(const void *buf, int len); +///////////////////////////////////////////日志///////////////////////////////////////////// + +/** + * 打印日志 + * @param level 日志级别,支持0~4 + * @param file __FILE__ + * @param function __FUNCTION__ + * @param line __LINE__ + * @param fmt printf类型的格式控制字符串 + * @param ... 不定长参数 + */ +API_EXPORT void API_CALL mk_log_printf(int level, const char *file, const char *function, int line, const char *fmt, ...); + +// 以下宏可以替换printf使用 +#define log_trace(fmt,...) mk_log_printf(0,__FILE__,__FUNCTION__,__LINE__,fmt,##__VA_ARGS__) +#define log_debug(fmt,...) mk_log_printf(1,__FILE__,__FUNCTION__,__LINE__,fmt,##__VA_ARGS__) +#define log_info(fmt,...) mk_log_printf(2,__FILE__,__FUNCTION__,__LINE__,fmt,##__VA_ARGS__) +#define log_warn(fmt,...) mk_log_printf(3,__FILE__,__FUNCTION__,__LINE__,fmt,##__VA_ARGS__) +#define log_error(fmt,...) mk_log_printf(4,__FILE__,__FUNCTION__,__LINE__,fmt,##__VA_ARGS__) +#define log_printf(lev,fmt,...) mk_log_printf(lev,__FILE__,__FUNCTION__,__LINE__,fmt,##__VA_ARGS__) + +#ifdef __cplusplus +} +#endif +#endif //MK_UTIL_H diff --git a/api/source/mk_common.cpp b/api/source/mk_common.cpp new file mode 100755 index 00000000..15eff659 --- /dev/null +++ b/api/source/mk_common.cpp @@ -0,0 +1,210 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "mk_common.h" +#include +#include +#include "Util/logger.h" +#include "Util/SSLBox.h" +#include "Network/TcpServer.h" +#include "Thread/WorkThreadPool.h" + +#include "Rtsp/RtspSession.h" +#include "Rtmp/RtmpSession.h" +#include "Http/HttpSession.h" +#include "Shell/ShellSession.h" +using namespace std; +using namespace toolkit; +using namespace mediakit; + +static TcpServer::Ptr rtsp_server[2]; +static TcpServer::Ptr rtmp_server[2]; +static TcpServer::Ptr http_server[2]; +static TcpServer::Ptr shell_server; + +#ifdef ENABLE_RTPPROXY +#include "Rtp/UdpRecver.h" +#include "Rtp/RtpSession.h" +static std::shared_ptr udpRtpServer; +static TcpServer::Ptr tcpRtpServer; +#endif + +//////////////////////////environment init/////////////////////////// +API_EXPORT void API_CALL mk_env_init(const mk_config *cfg) { + assert(cfg); + mk_env_init1(cfg->thread_num, + cfg->log_level, + cfg->ini_is_path, + cfg->ini, + cfg->ssl_is_path, + cfg->ssl, + cfg->ssl_pwd); +} + +extern void stopAllTcpServer(); + +API_EXPORT void API_CALL mk_stop_all_server(){ + CLEAR_ARR(rtsp_server); + CLEAR_ARR(rtmp_server); + CLEAR_ARR(http_server); +#ifdef ENABLE_RTPPROXY + udpRtpServer = nullptr; + tcpRtpServer = nullptr; +#endif + stopAllTcpServer(); +} + +API_EXPORT void API_CALL mk_env_init1( int thread_num, + int log_level, + int ini_is_path, + const char *ini, + int ssl_is_path, + const char *ssl, + const char *ssl_pwd) { + + static onceToken token([&]() { + Logger::Instance().add(std::make_shared("console", (LogLevel) log_level)); + Logger::Instance().setWriter(std::make_shared()); + + EventPollerPool::setPoolSize(thread_num); + WorkThreadPool::setPoolSize(thread_num); + + if (ini && ini[0]) { + //设置配置文件 + if (ini_is_path) { + try{ + mINI::Instance().parseFile(ini); + }catch (std::exception &ex) { + InfoL << "dump ini file to:" << ini; + mINI::Instance().dumpFile(ini); + } + } else { + mINI::Instance().parse(ini); + } + } + + if (ssl && ssl[0]) { + //设置ssl证书 + SSL_Initor::Instance().loadCertificate(ssl, true, ssl_pwd ? ssl_pwd : "", ssl_is_path); + } + }); +} + +API_EXPORT void API_CALL mk_set_option(const char *key, const char *val) { + assert(key && val); + if (mINI::Instance().find(key) == mINI::Instance().end()) { + WarnL << "key:" << key << " not existed!"; + return; + } + mINI::Instance()[key] = val; +} + +API_EXPORT uint16_t API_CALL mk_http_server_start(uint16_t port, int ssl) { + ssl = MAX(0,MIN(ssl,1)); + try { + http_server[ssl] = std::make_shared(); + if(ssl){ + http_server[ssl]->start >(port); + } else{ + http_server[ssl]->start(port); + } + return http_server[ssl]->getPort(); + } catch (std::exception &ex) { + http_server[ssl].reset(); + WarnL << ex.what(); + return 0; + } +} + +API_EXPORT uint16_t API_CALL mk_rtsp_server_start(uint16_t port, int ssl) { + ssl = MAX(0,MIN(ssl,1)); + try { + rtsp_server[ssl] = std::make_shared(); + if(ssl){ + rtsp_server[ssl]->start >(port); + }else{ + rtsp_server[ssl]->start(port); + } + return rtsp_server[ssl]->getPort(); + } catch (std::exception &ex) { + rtsp_server[ssl].reset(); + WarnL << ex.what(); + return 0; + } +} + +API_EXPORT uint16_t API_CALL mk_rtmp_server_start(uint16_t port, int ssl) { + ssl = MAX(0,MIN(ssl,1)); + try { + rtmp_server[ssl] = std::make_shared(); + if(ssl){ + rtmp_server[ssl]->start >(port); + }else{ + rtmp_server[ssl]->start(port); + } + return rtmp_server[ssl]->getPort(); + } catch (std::exception &ex) { + rtmp_server[ssl].reset(); + WarnL << ex.what(); + return 0; + } +} + +API_EXPORT uint16_t API_CALL mk_rtp_server_start(uint16_t port){ +#ifdef ENABLE_RTPPROXY + try { + //创建rtp tcp服务器 + tcpRtpServer = std::make_shared(); + tcpRtpServer->start(port); + + //创建rtp udp服务器 + auto ret = tcpRtpServer->getPort(); + udpRtpServer = std::make_shared(); + udpRtpServer->initSock(port); + return ret; + } catch (std::exception &ex) { + tcpRtpServer.reset(); + udpRtpServer.reset(); + WarnL << ex.what(); + return 0; + } +#else + WarnL << "未启用该功能!"; + return 0; +#endif +} + +API_EXPORT uint16_t API_CALL mk_shell_server_start(uint16_t port){ + try { + shell_server = std::make_shared(); + shell_server->start(port); + return shell_server->getPort(); + } catch (std::exception &ex) { + shell_server.reset(); + WarnL << ex.what(); + return 0; + } +} diff --git a/api/source/mk_events.cpp b/api/source/mk_events.cpp new file mode 100644 index 00000000..90499f86 --- /dev/null +++ b/api/source/mk_events.cpp @@ -0,0 +1,174 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "mk_events.h" +#include "Common/config.h" +#include "Common/MediaSource.h" +#include "Http/HttpSession.h" +#include "Rtsp/RtspSession.h" +#include "Record/MP4Recorder.h" +using namespace mediakit; + +static void* s_tag; +static mk_events s_events = {0}; + +API_EXPORT void API_CALL mk_events_listen(const mk_events *events){ + if(events){ + memcpy(&s_events,events, sizeof(s_events)); + }else{ + memset(&s_events,0,sizeof(s_events)); + } + + static onceToken tokne([]{ + NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastMediaChanged,[](BroadcastMediaChangedArgs){ + if(s_events.on_mk_media_changed){ + s_events.on_mk_media_changed(bRegist, + (mk_media_source)&sender); + } + }); + + NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastRecordMP4,[](BroadcastRecordMP4Args){ + if(s_events.on_mk_record_mp4){ + s_events.on_mk_record_mp4((mk_mp4_info)&info); + } + }); + + NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastHttpRequest,[](BroadcastHttpRequestArgs){ + if(s_events.on_mk_http_request){ + int consumed_int = consumed; + s_events.on_mk_http_request((mk_parser)&parser, + (mk_http_response_invoker)&invoker, + &consumed_int, + (mk_tcp_session)&sender); + consumed = consumed_int; + } + }); + + NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){ + if(s_events.on_mk_http_access){ + s_events.on_mk_http_access((mk_parser)&parser, + path.c_str(), + is_dir, + (mk_http_access_path_invoker)&invoker, + (mk_tcp_session)&sender); + } else{ + invoker("","",0); + } + }); + + NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastHttpBeforeAccess,[](BroadcastHttpBeforeAccessArgs){ + if(s_events.on_mk_http_before_access){ + char path_c[4 * 1024] = {0}; + strcpy(path_c,path.c_str()); + s_events.on_mk_http_before_access((mk_parser) &parser, + path_c, + (mk_tcp_session) &sender); + path = path_c; + } + }); + + + NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastOnGetRtspRealm,[](BroadcastOnGetRtspRealmArgs){ + if (s_events.on_mk_rtsp_get_realm) { + s_events.on_mk_rtsp_get_realm((mk_media_info) &args, + (mk_rtsp_get_realm_invoker) &invoker, + (mk_tcp_session) &sender); + }else{ + invoker(""); + } + }); + + NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastOnRtspAuth,[](BroadcastOnRtspAuthArgs){ + if (s_events.on_mk_rtsp_auth) { + s_events.on_mk_rtsp_auth((mk_media_info) &args, + realm.c_str(), + user_name.c_str(), + must_no_encrypt, + (mk_rtsp_auth_invoker) &invoker, + (mk_tcp_session) &sender); + } + }); + + NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastMediaPublish,[](BroadcastMediaPublishArgs){ + if (s_events.on_mk_media_publish) { + s_events.on_mk_media_publish((mk_media_info) &args, + (mk_publish_auth_invoker) &invoker, + (mk_tcp_session) &sender); + }else{ + GET_CONFIG(bool,toRtxp,General::kPublishToRtxp); + GET_CONFIG(bool,toHls,General::kPublishToHls); + GET_CONFIG(bool,toMP4,General::kPublishToMP4); + invoker("",toRtxp,toHls,toMP4); + } + }); + + NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastMediaPlayed,[](BroadcastMediaPlayedArgs){ + if (s_events.on_mk_media_play) { + s_events.on_mk_media_play((mk_media_info) &args, + (mk_auth_invoker) &invoker, + (mk_tcp_session) &sender); + }else{ + invoker(""); + } + }); + + NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastShellLogin,[](BroadcastShellLoginArgs){ + if (s_events.on_mk_shell_login) { + s_events.on_mk_shell_login(user_name.c_str(), + passwd.c_str(), + (mk_auth_invoker) &invoker, + (mk_tcp_session) &sender); + }else{ + invoker(""); + } + }); + + NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastFlowReport,[](BroadcastFlowReportArgs){ + if (s_events.on_mk_flow_report) { + s_events.on_mk_flow_report((mk_media_info) &args, + totalBytes, + totalDuration, + isPlayer, + peerIP.c_str(), + peerPort); + } + }); + + NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastNotFoundStream,[](BroadcastNotFoundStreamArgs){ + if (s_events.on_mk_media_not_found) { + s_events.on_mk_media_not_found((mk_media_info) &args, + (mk_tcp_session) &sender); + } + }); + + NoticeCenter::Instance().addListener(&s_tag,Broadcast::kBroadcastStreamNoneReader,[](BroadcastStreamNoneReaderArgs){ + if (s_events.on_mk_media_no_reader) { + s_events.on_mk_media_no_reader((mk_media_source) &sender); + } + }); + }); + +} diff --git a/api/source/mk_events_objects.cpp b/api/source/mk_events_objects.cpp new file mode 100644 index 00000000..d7ea5534 --- /dev/null +++ b/api/source/mk_events_objects.cpp @@ -0,0 +1,424 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include "mk_events_objects.h" +#include "Common/config.h" +#include "Record/MP4Recorder.h" +#include "Network/TcpSession.h" +#include "Http/HttpSession.h" +#include "Http/HttpBody.h" +#include "Http/HttpClient.h" +#include "Rtsp/RtspSession.h" +using namespace mediakit; + +///////////////////////////////////////////MP4Info///////////////////////////////////////////// +API_EXPORT uint64_t API_CALL mk_mp4_info_get_start_time(const mk_mp4_info ctx){ + assert(ctx); + MP4Info *info = (MP4Info *)ctx; + return info->ui64StartedTime; +} + +API_EXPORT uint64_t API_CALL mk_mp4_info_get_time_len(const mk_mp4_info ctx){ + assert(ctx); + MP4Info *info = (MP4Info *)ctx; + return info->ui64TimeLen; +} + +API_EXPORT uint64_t API_CALL mk_mp4_info_get_file_size(const mk_mp4_info ctx){ + assert(ctx); + MP4Info *info = (MP4Info *)ctx; + return info->ui64FileSize; +} + +API_EXPORT const char* API_CALL mk_mp4_info_get_file_path(const mk_mp4_info ctx){ + assert(ctx); + MP4Info *info = (MP4Info *)ctx; + return info->strFilePath.c_str(); +} + +API_EXPORT const char* API_CALL mk_mp4_info_get_file_name(const mk_mp4_info ctx){ + assert(ctx); + MP4Info *info = (MP4Info *)ctx; + return info->strFileName.c_str(); +} + +API_EXPORT const char* API_CALL mk_mp4_info_get_folder(const mk_mp4_info ctx){ + assert(ctx); + MP4Info *info = (MP4Info *)ctx; + return info->strFolder.c_str(); +} + +API_EXPORT const char* API_CALL mk_mp4_info_get_url(const mk_mp4_info ctx){ + assert(ctx); + MP4Info *info = (MP4Info *)ctx; + return info->strUrl.c_str(); +} + +API_EXPORT const char* API_CALL mk_mp4_info_get_vhost(const mk_mp4_info ctx){ + assert(ctx); + MP4Info *info = (MP4Info *)ctx; + return info->strVhost.c_str(); +} + +API_EXPORT const char* API_CALL mk_mp4_info_get_app(const mk_mp4_info ctx){ + assert(ctx); + MP4Info *info = (MP4Info *)ctx; + return info->strAppName.c_str(); +} + +API_EXPORT const char* API_CALL mk_mp4_info_get_stream(const mk_mp4_info ctx){ + assert(ctx); + MP4Info *info = (MP4Info *)ctx; + return info->strStreamId.c_str(); +} + +///////////////////////////////////////////Parser///////////////////////////////////////////// +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(); +} +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(); +} +API_EXPORT const char* API_CALL mk_parser_get_full_url(const mk_parser ctx){ + assert(ctx); + Parser *parser = (Parser *)ctx; + return parser->FullUrl().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(); +} +API_EXPORT const char* API_CALL mk_parser_get_url_param(const mk_parser ctx,const char *key){ + assert(ctx && key); + Parser *parser = (Parser *)ctx; + return parser->getUrlArgs()[key].c_str(); +} +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(); +} +API_EXPORT const char* API_CALL mk_parser_get_header(const mk_parser ctx,const char *key){ + assert(ctx && key); + Parser *parser = (Parser *)ctx; + return parser->getValues()[key].c_str(); +} +API_EXPORT const char* API_CALL mk_parser_get_content(const mk_parser ctx, int *length){ + assert(ctx); + Parser *parser = (Parser *)ctx; + if(length){ + *length = parser->Content().size(); + } + 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(); +} + +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(); +} +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(); +} +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(); +} +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(); +} + +///////////////////////////////////////////MediaSource///////////////////////////////////////////// +API_EXPORT const char* API_CALL mk_media_source_get_schema(const mk_media_source ctx){ + assert(ctx); + MediaSource *src = (MediaSource *)ctx; + return src->getSchema().c_str(); +} +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(); +} +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(); +} +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(); +} +API_EXPORT int API_CALL mk_media_source_get_reader_count(const mk_media_source ctx){ + assert(ctx); + MediaSource *src = (MediaSource *)ctx; + return src->readerCount(); +} + +API_EXPORT int API_CALL mk_media_source_get_total_reader_count(const mk_media_source ctx){ + assert(ctx); + MediaSource *src = (MediaSource *)ctx; + return src->totalReaderCount(); +} + +API_EXPORT int API_CALL mk_media_source_close(const mk_media_source ctx,int force){ + assert(ctx); + MediaSource *src = (MediaSource *)ctx; + return src->close(force); +} +API_EXPORT int API_CALL mk_media_source_seek_to(const mk_media_source ctx,uint32_t stamp){ + assert(ctx); + MediaSource *src = (MediaSource *)ctx; + return src->seekTo(stamp); +} + +API_EXPORT void API_CALL mk_media_source_find(const char *schema, + const char *vhost, + const char *app, + const char *stream, + void *user_data, + on_mk_media_source_find_cb cb) { + assert(schema && vhost && app && stream && cb); + auto src = MediaSource::find(schema, vhost, app, stream); + cb(user_data, src.get()); +} + +API_EXPORT void API_CALL mk_media_source_for_each(void *user_data, on_mk_media_source_find_cb cb){ + assert(cb); + MediaSource::for_each_media([&](const MediaSource::Ptr &src){ + cb(user_data,src.get()); + }); +} + +///////////////////////////////////////////HttpBody///////////////////////////////////////////// +API_EXPORT mk_http_body API_CALL mk_http_body_from_string(const char *str,int len){ + assert(str); + if(!len){ + len = strlen(str); + } + return new HttpBody::Ptr(new HttpStringBody(string(str,len))); +} + +API_EXPORT mk_http_body API_CALL mk_http_body_from_file(const char *file_path){ + assert(file_path); + return new HttpBody::Ptr(new HttpFileBody(file_path)); +} + +template +static C get_http_header( const char *response_header[]){ + C header; + for (int i = 0; response_header[i] != NULL;) { + auto key = response_header[i]; + auto value = response_header[i + 1]; + if (key && value) { + i += 2; + header.emplace(key,value); + continue; + } + break; + } + return std::move(header); +} + +API_EXPORT mk_http_body API_CALL mk_http_body_from_multi_form(const char *key_val[],const char *file_path){ + assert(key_val && file_path); + return new HttpBody::Ptr(new HttpMultiFormBody(get_http_header(key_val),file_path)); +} + +API_EXPORT void API_CALL mk_http_body_release(mk_http_body ctx){ + assert(ctx); + HttpBody::Ptr *ptr = (HttpBody::Ptr *)ctx; + delete ptr; +} + +///////////////////////////////////////////HttpResponseInvoker///////////////////////////////////////////// +API_EXPORT void API_CALL mk_http_response_invoker_do_string(const mk_http_response_invoker ctx, + const char *response_code, + const char **response_header, + const char *response_content){ + assert(ctx && response_code && response_header && response_content); + auto header = get_http_header(response_header); + HttpSession::HttpResponseInvoker *invoker = (HttpSession::HttpResponseInvoker *)ctx; + (*invoker)(response_code,header,response_content); +} + +API_EXPORT void API_CALL mk_http_response_invoker_do_file(const mk_http_response_invoker ctx, + const mk_parser request_parser, + const char *response_header[], + const char *response_file_path){ + assert(ctx && request_parser && response_header && response_file_path); + auto header = get_http_header(response_header); + HttpSession::HttpResponseInvoker *invoker = (HttpSession::HttpResponseInvoker *)ctx; + (*invoker).responseFile(((Parser*)(request_parser))->getValues(),header,response_file_path); +} + +API_EXPORT void API_CALL mk_http_response_invoker_do(const mk_http_response_invoker ctx, + const char *response_code, + const char **response_header, + const mk_http_body response_body){ + assert(ctx && response_code && response_header && response_body); + auto header = get_http_header(response_header); + HttpSession::HttpResponseInvoker *invoker = (HttpSession::HttpResponseInvoker *)ctx; + HttpBody::Ptr *body = (HttpBody::Ptr*) response_body; + (*invoker)(response_code,header,*body); +} + +API_EXPORT mk_http_response_invoker API_CALL mk_http_response_invoker_clone(const mk_http_response_invoker ctx){ + assert(ctx); + HttpSession::HttpResponseInvoker *invoker = (HttpSession::HttpResponseInvoker *)ctx; + return new HttpSession::HttpResponseInvoker (*invoker); +} + +API_EXPORT void API_CALL mk_http_response_invoker_clone_release(const mk_http_response_invoker ctx){ + assert(ctx); + HttpSession::HttpResponseInvoker *invoker = (HttpSession::HttpResponseInvoker *)ctx; + delete invoker; +} + +///////////////////////////////////////////HttpAccessPathInvoker///////////////////////////////////////////// +API_EXPORT void API_CALL mk_http_access_path_invoker_do(const mk_http_access_path_invoker ctx, + const char *err_msg, + const char *access_path, + int cookie_life_second){ + assert(ctx); + HttpSession::HttpAccessPathInvoker *invoker = (HttpSession::HttpAccessPathInvoker *)ctx; + (*invoker)(err_msg ? err_msg : "", + access_path? access_path : "", + cookie_life_second); +} + +API_EXPORT mk_http_access_path_invoker API_CALL mk_http_access_path_invoker_clone(const mk_http_access_path_invoker ctx){ + assert(ctx); + HttpSession::HttpAccessPathInvoker *invoker = (HttpSession::HttpAccessPathInvoker *)ctx; + return new HttpSession::HttpAccessPathInvoker(*invoker); +} + +API_EXPORT void API_CALL mk_http_access_path_invoker_clone_release(const mk_http_access_path_invoker ctx){ + assert(ctx); + HttpSession::HttpAccessPathInvoker *invoker = (HttpSession::HttpAccessPathInvoker *)ctx; + delete invoker; +} + +///////////////////////////////////////////RtspSession::onGetRealm///////////////////////////////////////////// +API_EXPORT void API_CALL mk_rtsp_get_realm_invoker_do(const mk_rtsp_get_realm_invoker ctx, + const char *realm){ + assert(ctx); + RtspSession::onGetRealm *invoker = (RtspSession::onGetRealm *)ctx; + (*invoker)(realm ? realm : ""); +} + +API_EXPORT mk_rtsp_get_realm_invoker API_CALL mk_rtsp_get_realm_invoker_clone(const mk_rtsp_get_realm_invoker ctx){ + assert(ctx); + RtspSession::onGetRealm *invoker = (RtspSession::onGetRealm *)ctx; + return new RtspSession::onGetRealm (*invoker); +} + +API_EXPORT void API_CALL mk_rtsp_get_realm_invoker_clone_release(const mk_rtsp_get_realm_invoker ctx){ + assert(ctx); + RtspSession::onGetRealm *invoker = (RtspSession::onGetRealm *)ctx; + delete invoker; +} + +///////////////////////////////////////////RtspSession::onAuth///////////////////////////////////////////// +API_EXPORT void API_CALL mk_rtsp_auth_invoker_do(const mk_rtsp_auth_invoker ctx, + int encrypted, + const char *pwd_or_md5){ + assert(ctx); + RtspSession::onAuth *invoker = (RtspSession::onAuth *)ctx; + (*invoker)(encrypted, pwd_or_md5 ? pwd_or_md5 : ""); +} + +API_EXPORT mk_rtsp_auth_invoker API_CALL mk_rtsp_auth_invoker_clone(const mk_rtsp_auth_invoker ctx){ + assert(ctx); + RtspSession::onAuth *invoker = (RtspSession::onAuth *)ctx; + return new RtspSession::onAuth(*invoker); +} + +API_EXPORT void API_CALL mk_rtsp_auth_invoker_clone_release(const mk_rtsp_auth_invoker ctx){ + assert(ctx); + RtspSession::onAuth *invoker = (RtspSession::onAuth *)ctx; + delete invoker; +} + +///////////////////////////////////////////Broadcast::PublishAuthInvoker///////////////////////////////////////////// +API_EXPORT void API_CALL mk_publish_auth_invoker_do(const mk_publish_auth_invoker ctx, + const char *err_msg, + int enable_rtxp, + int enable_hls, + int enable_mp4){ + assert(ctx); + Broadcast::PublishAuthInvoker *invoker = (Broadcast::PublishAuthInvoker *)ctx; + (*invoker)(err_msg ? err_msg : "", enable_rtxp, enable_hls, enable_mp4); +} + +API_EXPORT mk_publish_auth_invoker API_CALL mk_publish_auth_invoker_clone(const mk_publish_auth_invoker ctx){ + assert(ctx); + Broadcast::PublishAuthInvoker *invoker = (Broadcast::PublishAuthInvoker *)ctx; + return new Broadcast::PublishAuthInvoker(*invoker); +} + +API_EXPORT void API_CALL mk_publish_auth_invoker_clone_release(const mk_publish_auth_invoker ctx){ + assert(ctx); + Broadcast::PublishAuthInvoker *invoker = (Broadcast::PublishAuthInvoker *)ctx; + delete invoker; +} + +///////////////////////////////////////////Broadcast::AuthInvoker///////////////////////////////////////////// +API_EXPORT void API_CALL mk_auth_invoker_do(const mk_auth_invoker ctx, const char *err_msg){ + assert(ctx); + Broadcast::AuthInvoker *invoker = (Broadcast::AuthInvoker *)ctx; + (*invoker)(err_msg ? err_msg : ""); +} + +API_EXPORT mk_auth_invoker API_CALL mk_auth_invoker_clone(const mk_auth_invoker ctx){ + assert(ctx); + Broadcast::AuthInvoker *invoker = (Broadcast::AuthInvoker *)ctx; + return new Broadcast::AuthInvoker(*invoker); +} + +API_EXPORT void API_CALL mk_auth_invoker_clone_release(const mk_auth_invoker ctx){ + assert(ctx); + Broadcast::AuthInvoker *invoker = (Broadcast::AuthInvoker *)ctx; + delete invoker; +} \ No newline at end of file diff --git a/api/source/mk_httpclient.cpp b/api/source/mk_httpclient.cpp new file mode 100755 index 00000000..e1262011 --- /dev/null +++ b/api/source/mk_httpclient.cpp @@ -0,0 +1,159 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "mk_httpclient.h" + +#include "Util/logger.h" +#include "Http/HttpDownloader.h" +#include "Http/HttpRequester.h" + +using namespace std; +using namespace toolkit; +using namespace mediakit; + +API_EXPORT mk_http_downloader API_CALL mk_http_downloader_create() { + HttpDownloader::Ptr *obj(new HttpDownloader::Ptr(new HttpDownloader())); + return (mk_http_downloader) obj; +} + +API_EXPORT void API_CALL mk_http_downloader_release(mk_http_downloader ctx) { + assert(ctx); + HttpDownloader::Ptr *obj = (HttpDownloader::Ptr *) ctx; + delete obj; +} + +API_EXPORT void API_CALL mk_http_downloader_start(mk_http_downloader ctx, const char *url, const char *file, on_mk_download_complete cb, void *user_data) { + assert(ctx && url && file); + HttpDownloader::Ptr *obj = (HttpDownloader::Ptr *) ctx; + (*obj)->setOnResult([cb, user_data](ErrCode code, const string &errMsg, const string &filePath) { + if (cb) { + cb(user_data, code, errMsg.data(), filePath.data()); + } + }); + (*obj)->startDownload(url, file, false); +} + + +///////////////////////////////////////////HttpRequester///////////////////////////////////////////// +API_EXPORT mk_http_requester API_CALL mk_http_requester_create(){ + HttpRequester::Ptr *ret = new HttpRequester::Ptr(new HttpRequester); + return ret; +} + +API_EXPORT void API_CALL mk_http_requester_clear(mk_http_requester ctx){ + assert(ctx); + HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx; + (*obj)->clear(); +} + +API_EXPORT void API_CALL mk_http_requester_release(mk_http_requester ctx){ + assert(ctx); + HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx; + delete obj; +} + +API_EXPORT void API_CALL mk_http_requester_set_method(mk_http_requester ctx,const char *method){ + assert(ctx); + HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx; + (*obj)->setMethod(method); +} + +template +static C get_http_header( const char *response_header[]){ + C header; + for (int i = 0; response_header[i] != NULL;) { + auto key = response_header[i]; + auto value = response_header[i + 1]; + if (key && value) { + i += 2; + header.emplace(key,value); + continue; + } + break; + } + return std::move(header); +} + +API_EXPORT void API_CALL mk_http_requester_set_body(mk_http_requester ctx, mk_http_body body){ + assert(ctx && body); + HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx; + HttpBody::Ptr *body_obj = (HttpBody::Ptr *)body; + (*obj)->setBody(*body_obj); +} + +API_EXPORT void API_CALL mk_http_requester_set_header(mk_http_requester ctx, const char *header[]){ + assert(ctx && header); + auto header_obj = get_http_header(header); + HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx; + (*obj)->setHeader(header_obj); +} + +API_EXPORT void API_CALL mk_http_requester_add_header(mk_http_requester ctx,const char *key,const char *value,int force){ + assert(ctx && key && value); + HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx; + (*obj)->addHeader(key,value,force); +} + +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)->responseStatus().c_str(); +} + +API_EXPORT const char* API_CALL mk_http_requester_get_response_header(mk_http_requester ctx,const char *key){ + assert(ctx); + HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx; + return (*obj)->response()[key].c_str(); +} + +API_EXPORT const char* API_CALL mk_http_requester_get_response_body(mk_http_requester ctx, int *length){ + assert(ctx); + HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx; + if(length){ + *length = (*obj)->response().Content().size(); + } + return (*obj)->response().Content().c_str(); +} + +API_EXPORT mk_parser API_CALL mk_http_requester_get_response(mk_http_requester ctx){ + assert(ctx); + HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx; + return (mk_parser)&((*obj)->response()); +} + +API_EXPORT void API_CALL mk_http_requester_set_cb(mk_http_requester ctx,on_mk_http_requester_complete cb, void *user_data){ + assert(ctx && cb); + HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx; + (*obj)->setOnResult([cb,user_data](const SockException &ex,const string &status,const StrCaseMap &header,const string &strRecvBody){ + cb(user_data, ex.getErrCode(),ex.what()); + }); +} + +API_EXPORT void API_CALL mk_http_requester_start(mk_http_requester ctx,const char *url, float timeout_second){ + assert(ctx && url && url[0] && timeout_second > 0); + HttpRequester::Ptr *obj = (HttpRequester::Ptr *)ctx; + (*obj)->sendRequest(url,timeout_second); +} + diff --git a/api/source/mk_media.cpp b/api/source/mk_media.cpp new file mode 100755 index 00000000..d513af54 --- /dev/null +++ b/api/source/mk_media.cpp @@ -0,0 +1,184 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "mk_media.h" +#include "Util/logger.h" +#include "Common/Device.h" + +using namespace std; +using namespace toolkit; +using namespace mediakit; + +class MediaHelper : public MediaSourceEvent , public std::enable_shared_from_this { +public: + typedef std::shared_ptr Ptr; + template + MediaHelper(ArgsType &&...args){ + _channel = std::make_shared(std::forward(args)...); + } + ~MediaHelper(){} + + void attachEvent(){ + _channel->setListener(shared_from_this()); + } + + DevChannel::Ptr &getChannel(){ + return _channel; + } + + void setCallBack(on_mk_media_close cb, void *user_data){ + _cb = cb; + _user_data = user_data; + } +protected: + // 通知其停止推流 + bool close(MediaSource &sender,bool force) override{ + if(!force && _channel->totalReaderCount()){ + //非强制关闭且正有人在观看该视频 + return false; + } + if(!_cb){ + //未设置回调,没法关闭 + return false; + } + if(!_cb(_user_data)){ + //回调选择返回不关闭该视频 + return false; + } + + //回调中已经关闭该视频 + WarnL << "close media:" << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force; + return true; + } + + // 通知无人观看 + void onNoneReader(MediaSource &sender) override{ + if(_channel->totalReaderCount()){ + //统计有误,还有人在看 + return; + } + MediaSourceEvent::onNoneReader(sender); + } + + // 观看总人数 + int totalReaderCount(MediaSource &sender) override{ + return _channel->totalReaderCount(); + } +private: + DevChannel::Ptr _channel; + on_mk_media_close _cb; + void *_user_data; +}; + +API_EXPORT void API_CALL mk_media_set_on_close(mk_media ctx, on_mk_media_close cb, void *user_data){ + assert(ctx); + MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx; + (*obj)->setCallBack(cb,user_data); +} + +API_EXPORT int API_CALL mk_media_total_reader_count(mk_media ctx){ + assert(ctx); + MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx; + return (*obj)->getChannel()->totalReaderCount(); +} + +API_EXPORT mk_media API_CALL mk_media_create(const char *vhost, const char *app, const char *stream, float duration, int hls_enabled, int mp4_enabled) { + assert(vhost && app && stream); + MediaHelper::Ptr *obj(new MediaHelper::Ptr(new MediaHelper(vhost, app, stream, duration, true, true, hls_enabled, mp4_enabled))); + (*obj)->attachEvent(); + return (mk_media) obj; +} + +API_EXPORT void API_CALL mk_media_release(mk_media ctx) { + assert(ctx); + MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx; + delete obj; +} + +API_EXPORT void API_CALL mk_media_init_h264(mk_media ctx, int width, int height, int frameRate) { + assert(ctx); + MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx; + VideoInfo info; + info.iFrameRate = frameRate; + info.iWidth = width; + info.iHeight = height; + (*obj)->getChannel()->initVideo(info); +} + +API_EXPORT void API_CALL mk_media_init_h265(mk_media ctx, int width, int height, int frameRate) { + assert(ctx); + MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx; + VideoInfo info; + info.iFrameRate = frameRate; + info.iWidth = width; + info.iHeight = height; + (*obj)->getChannel()->initH265Video(info); +} + +API_EXPORT void API_CALL mk_media_init_aac(mk_media ctx, int channel, int sample_bit, int sample_rate, int profile) { + assert(ctx); + MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx; + AudioInfo info; + info.iSampleRate = sample_rate; + info.iChannel = channel; + info.iSampleBit = sample_bit; + info.iProfile = profile; + (*obj)->getChannel()->initAudio(info); +} + +API_EXPORT void API_CALL mk_media_init_complete(mk_media ctx){ + assert(ctx); + MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx; + (*obj)->getChannel()->addTrackCompleted(); +} + +API_EXPORT void API_CALL mk_media_input_h264(mk_media ctx, void *data, int len, uint32_t dts, uint32_t pts) { + assert(ctx && data && len > 0); + MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx; + (*obj)->getChannel()->inputH264((char *) data, len, dts, pts); +} + +API_EXPORT void API_CALL mk_media_input_h265(mk_media ctx, void *data, int len, uint32_t dts, uint32_t pts) { + assert(ctx && data && len > 0); + MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx; + (*obj)->getChannel()->inputH265((char *) data, len, dts, pts); +} + +API_EXPORT void API_CALL mk_media_input_aac(mk_media ctx, void *data, int len, uint32_t dts, int with_adts_header) { + assert(ctx && data && len > 0); + MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx; + (*obj)->getChannel()->inputAAC((char *) data, len, dts, with_adts_header); +} + +API_EXPORT void API_CALL mk_media_input_aac1(mk_media ctx, void *data, int len, uint32_t dts, void *adts) { + assert(ctx && data && len > 0 && adts); + MediaHelper::Ptr *obj = (MediaHelper::Ptr *) ctx; + (*obj)->getChannel()->inputAAC((char *) data, len, dts, (char *) adts); +} + + + + diff --git a/api/source/mk_player.cpp b/api/source/mk_player.cpp new file mode 100755 index 00000000..75c556ac --- /dev/null +++ b/api/source/mk_player.cpp @@ -0,0 +1,178 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "mk_player.h" +#include "Util/logger.h" +#include "Player/MediaPlayer.h" +using namespace std; +using namespace toolkit; +using namespace mediakit; + +API_EXPORT mk_player API_CALL mk_player_create() { + MediaPlayer::Ptr *obj = new MediaPlayer::Ptr(new MediaPlayer()); + return obj; +} +API_EXPORT void API_CALL mk_player_release(mk_player ctx) { + assert(ctx); + MediaPlayer::Ptr *obj = (MediaPlayer::Ptr *)ctx; + delete obj; +} + +API_EXPORT void API_CALL mk_player_set_option(mk_player ctx,const char* key,const char *val){ + assert(ctx && key && val); + MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx); + string key_str(key), val_str(val); + player->getPoller()->async([key_str,val_str,player](){ + //切换线程后再操作 + (*player)[key_str] = val_str; + }); +} +API_EXPORT void API_CALL mk_player_play(mk_player ctx, const char *url) { + assert(ctx && url); + MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx); + string url_str(url); + player->getPoller()->async([url_str,player](){ + //切换线程后再操作 + player->play(url_str); + }); +} + +API_EXPORT void API_CALL mk_player_pause(mk_player ctx, int pause) { + assert(ctx); + MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx); + player->getPoller()->async([pause,player](){ + //切换线程后再操作 + player->pause(pause); + }); +} + +API_EXPORT void API_CALL mk_player_seekto(mk_player ctx, float progress) { + assert(ctx); + MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx); + player->getPoller()->async([progress,player](){ + //切换线程后再操作 + player->seekTo(progress); + }); +} + +static void mk_player_set_on_event(mk_player ctx, on_mk_play_event cb, void *user_data, int type) { + assert(ctx && cb); + MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx); + player->getPoller()->async([cb,user_data,type,player](){ + //切换线程后再操作 + if(type == 0){ + player->setOnPlayResult([cb,user_data](const SockException &ex){ + cb(user_data,ex.getErrCode(),ex.what()); + }); + }else{ + player->setOnShutdown([cb,user_data](const SockException &ex){ + cb(user_data,ex.getErrCode(),ex.what()); + }); + } + }); +} + +API_EXPORT void API_CALL mk_player_set_on_result(mk_player ctx, on_mk_play_event cb, void *user_data) { + mk_player_set_on_event(ctx,cb,user_data,0); +} + +API_EXPORT void API_CALL mk_player_set_on_shutdown(mk_player ctx, on_mk_play_event cb, void *user_data) { + mk_player_set_on_event(ctx,cb,user_data,1); +} + +API_EXPORT void API_CALL mk_player_set_on_data(mk_player ctx, on_mk_play_data cb, void *user_data) { + assert(ctx && cb); + MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx); + player->getPoller()->async([player,cb,user_data](){ + //切换线程后再操作 + auto delegate = std::make_shared([cb,user_data](const Frame::Ptr &frame){ + cb(user_data,frame->getTrackType(),frame->getCodecId(),frame->data(),frame->size(),frame->dts(),frame->pts()); + }); + for(auto &track : player->getTracks()){ + track->addDelegate(delegate); + } + }); +} + +API_EXPORT int API_CALL mk_player_video_width(mk_player ctx) { + assert(ctx); + MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx); + auto track = dynamic_pointer_cast(player->getTrack(TrackVideo)); + return track ? track->getVideoWidth() : 0; +} + +API_EXPORT int API_CALL mk_player_video_height(mk_player ctx) { + assert(ctx); + MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx); + auto track = dynamic_pointer_cast(player->getTrack(TrackVideo)); + return track ? track->getVideoHeight() : 0; +} + +API_EXPORT int API_CALL mk_player_video_fps(mk_player ctx) { + assert(ctx); + MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx); + auto track = dynamic_pointer_cast(player->getTrack(TrackVideo)); + return track ? track->getVideoFps() : 0; +} + +API_EXPORT int API_CALL mk_player_audio_samplerate(mk_player ctx) { + assert(ctx); + MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx); + auto track = dynamic_pointer_cast(player->getTrack(TrackAudio)); + return track ? track->getAudioSampleRate() : 0; +} + +API_EXPORT int API_CALL mk_player_audio_bit(mk_player ctx) { + assert(ctx); + MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx); + auto track = dynamic_pointer_cast(player->getTrack(TrackAudio)); + return track ? track->getAudioSampleBit() : 0; +} + +API_EXPORT int API_CALL mk_player_audio_channel(mk_player ctx) { + assert(ctx); + MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx); + auto track = dynamic_pointer_cast(player->getTrack(TrackAudio)); + return track ? track->getAudioChannel() : 0; +} + +API_EXPORT float API_CALL mk_player_duration(mk_player ctx) { + assert(ctx); + MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx); + return player->getDuration(); +} + +API_EXPORT float API_CALL mk_player_progress(mk_player ctx) { + assert(ctx); + MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx); + return player->getProgress(); +} + +API_EXPORT float API_CALL mk_player_loss_rate(mk_player ctx, int track_type) { + assert(ctx); + MediaPlayer::Ptr &player = *((MediaPlayer::Ptr *)ctx); + return player->getPacketLossRate((TrackType)track_type); +} diff --git a/api/source/mk_proxyplayer.cpp b/api/source/mk_proxyplayer.cpp new file mode 100644 index 00000000..23702a9d --- /dev/null +++ b/api/source/mk_proxyplayer.cpp @@ -0,0 +1,63 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "mk_proxyplayer.h" +#include "Player/PlayerProxy.h" + +using namespace toolkit; +using namespace mediakit; + +API_EXPORT mk_proxy_player API_CALL mk_proxy_player_create(const char *vhost, const char *app, const char *stream, int hls_enabled, int mp4_enabled) { + assert(vhost && app && stream); + PlayerProxy::Ptr *obj(new PlayerProxy::Ptr(new PlayerProxy(vhost, app, stream, true, true, hls_enabled, mp4_enabled))); + return (mk_proxy_player) obj; +} + +API_EXPORT void API_CALL mk_proxy_player_release(mk_proxy_player ctx) { + assert(ctx); + PlayerProxy::Ptr *obj = (PlayerProxy::Ptr *) ctx; + delete obj; +} + +API_EXPORT void API_CALL mk_proxy_player_set_option(mk_proxy_player ctx, const char *key, const char *val){ + assert(ctx && key && val); + PlayerProxy::Ptr &obj = *((PlayerProxy::Ptr *) ctx); + string key_str(key),val_str(val); + obj->getPoller()->async([obj,key_str,val_str](){ + //切换线程再操作 + (*obj)[key_str] = val_str; + }); +} + +API_EXPORT void API_CALL mk_proxy_player_play(mk_proxy_player ctx, const char *url) { + assert(ctx && url); + PlayerProxy::Ptr &obj = *((PlayerProxy::Ptr *) ctx); + string url_str(url); + obj->getPoller()->async([obj,url_str](){ + //切换线程再操作 + obj->play(url_str); + }); +} diff --git a/api/source/mk_pusher.cpp b/api/source/mk_pusher.cpp new file mode 100644 index 00000000..cdab8d26 --- /dev/null +++ b/api/source/mk_pusher.cpp @@ -0,0 +1,84 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include "mk_pusher.h" +#include "Pusher/MediaPusher.h" +using namespace mediakit; + +API_EXPORT mk_pusher API_CALL mk_pusher_create(const char *schema,const char *vhost,const char *app, const char *stream){ + assert(schema && vhost && app && schema); + MediaPusher::Ptr *obj = new MediaPusher::Ptr(new MediaPusher(schema,vhost,app,stream)); + return obj; +} + +API_EXPORT void API_CALL mk_pusher_release(mk_pusher ctx){ + assert(ctx); + MediaPusher::Ptr *obj = (MediaPusher::Ptr *)ctx; + delete obj; +} + +API_EXPORT void API_CALL mk_pusher_set_option(mk_pusher ctx, const char *key, const char *val){ + assert(ctx && key && val); + MediaPusher::Ptr &obj = *((MediaPusher::Ptr *)ctx); + string key_str(key),val_str(val); + obj->getPoller()->async([obj,key_str,val_str](){ + //切换线程再操作 + (*obj)[key_str] = val_str; + }); +} + +API_EXPORT void API_CALL mk_pusher_publish(mk_pusher ctx,const char *url){ + assert(ctx && url); + MediaPusher::Ptr &obj = *((MediaPusher::Ptr *)ctx); + string url_str(url); + obj->getPoller()->async([obj,url_str](){ + //切换线程再操作 + obj->publish(url_str); + }); +} + +API_EXPORT void API_CALL mk_pusher_set_on_result(mk_pusher ctx, on_mk_push_event cb, void *user_data){ + assert(ctx && cb); + MediaPusher::Ptr &obj = *((MediaPusher::Ptr *)ctx); + obj->getPoller()->async([obj,cb,user_data](){ + //切换线程再操作 + obj->setOnPublished([cb,user_data](const SockException &ex){ + cb(user_data,ex.getErrCode(),ex.what()); + }); + }); +} + +API_EXPORT void API_CALL mk_pusher_set_on_shutdown(mk_pusher ctx, on_mk_push_event cb, void *user_data){ + assert(ctx && cb); + MediaPusher::Ptr &obj = *((MediaPusher::Ptr *)ctx); + obj->getPoller()->async([obj,cb,user_data](){ + //切换线程再操作 + obj->setOnShutdown([cb,user_data](const SockException &ex){ + cb(user_data,ex.getErrCode(),ex.what()); + }); + }); +} diff --git a/api/source/mk_recorder.cpp b/api/source/mk_recorder.cpp new file mode 100644 index 00000000..efd29c42 --- /dev/null +++ b/api/source/mk_recorder.cpp @@ -0,0 +1,72 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "mk_recorder.h" +#include "Rtmp/FlvMuxer.h" +#include "Record/Recorder.h" +using namespace toolkit; +using namespace mediakit; + +API_EXPORT mk_flv_recorder API_CALL mk_flv_recorder_create(){ + FlvRecorder::Ptr *ret = new FlvRecorder::Ptr(new FlvRecorder); + return ret; +} +API_EXPORT void API_CALL mk_flv_recorder_release(mk_flv_recorder ctx){ + assert(ctx); + FlvRecorder::Ptr *record = (FlvRecorder::Ptr *)(ctx); + delete record; +} +API_EXPORT int API_CALL mk_flv_recorder_start(mk_flv_recorder ctx, const char *vhost, const char *app, const char *stream, const char *file_path){ + assert(ctx && vhost && app && stream && file_path); + try { + FlvRecorder::Ptr *record = (FlvRecorder::Ptr *)(ctx); + (*record)->startRecord(EventPollerPool::Instance().getPoller(),vhost,app,stream,file_path); + return 0; + }catch (std::exception &ex){ + WarnL << ex.what(); + return -1; + } +} + +///////////////////////////////////////////hls/mp4录制///////////////////////////////////////////// +API_EXPORT int API_CALL mk_recorder_status(int type, const char *vhost, const char *app, const char *stream){ + assert(vhost && app && stream); + return Recorder::getRecordStatus((Recorder::type)type,vhost,app,stream); +} + +API_EXPORT int API_CALL mk_recorder_start(int type, const char *vhost, const char *app, const char *stream,const char *customized_path,int wait_for_record, int continue_record){ + assert(vhost && app && stream); + return Recorder::startRecord((Recorder::type)type,vhost,app,stream,customized_path ? customized_path : "",wait_for_record,continue_record); +} + +API_EXPORT int API_CALL mk_recorder_stop(int type, const char *vhost, const char *app, const char *stream){ + assert(vhost && app && stream); + return Recorder::stopRecord((Recorder::type)type,vhost,app,stream); +} + +API_EXPORT void API_CALL mk_recorder_stop_all(){ + Recorder::stopAll(); +} \ No newline at end of file diff --git a/api/source/mk_tcp.cpp b/api/source/mk_tcp.cpp new file mode 100644 index 00000000..4517ea00 --- /dev/null +++ b/api/source/mk_tcp.cpp @@ -0,0 +1,271 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "mk_tcp.h" +#include "mk_tcp_private.h" +#include "Http/WebSocketClient.h" +#include "Http/WebSocketSession.h" +using namespace mediakit; + +//////////////////////////////////////////////////////////////////////////////////////// +API_EXPORT void API_CALL mk_tcp_session_shutdown(const mk_tcp_session ctx,int err,const char *err_msg){ + assert(ctx); + TcpSession *session = (TcpSession *)ctx; + session->safeShutdown(SockException((ErrCode)err,err_msg)); +} +API_EXPORT const char* API_CALL mk_tcp_session_peer_ip(const mk_tcp_session ctx){ + assert(ctx); + TcpSession *session = (TcpSession *)ctx; + return session->get_peer_ip().c_str(); +} +API_EXPORT const char* API_CALL mk_tcp_session_local_ip(const mk_tcp_session ctx){ + assert(ctx); + TcpSession *session = (TcpSession *)ctx; + return session->get_local_ip().c_str(); +} +API_EXPORT uint16_t API_CALL mk_tcp_session_peer_port(const mk_tcp_session ctx){ + assert(ctx); + TcpSession *session = (TcpSession *)ctx; + return session->get_peer_port(); +} +API_EXPORT uint16_t API_CALL mk_tcp_session_local_port(const mk_tcp_session ctx){ + assert(ctx); + TcpSession *session = (TcpSession *)ctx; + return session->get_local_port(); +} +API_EXPORT void API_CALL mk_tcp_session_send(const mk_tcp_session ctx,const char *data,int len){ + assert(ctx && data); + if(!len){ + len = strlen(data); + } + TcpSession *session = (TcpSession *)ctx; + session->send(data,len); +} + +API_EXPORT void API_CALL mk_tcp_session_send_safe(const mk_tcp_session ctx,const char *data,int len){ + assert(ctx && data); + if(!len){ + len = strlen(data); + } + try { + weak_ptr weak_session = ((TcpSession *)ctx)->shared_from_this(); + string str = string(data,len); + ((TcpSession *)ctx)->async([weak_session,str](){ + auto session_session = weak_session.lock(); + if(session_session){ + session_session->send(str); + } + }); + }catch (std::exception &ex){ + WarnL << "can not got the strong pionter of this mk_tcp_session:" << ex.what(); + } +} + +////////////////////////////////////////TcpSessionForC//////////////////////////////////////////////// +static TcpServer::Ptr s_tcp_server[4]; +static mk_tcp_session_events s_events_server = {0}; + +TcpSessionForC::TcpSessionForC(const Socket::Ptr &pSock) : TcpSession(pSock) { + _local_port = get_local_port(); + if (s_events_server.on_mk_tcp_session_create) { + s_events_server.on_mk_tcp_session_create(_local_port,this); + } +} + +void TcpSessionForC::onRecv(const Buffer::Ptr &buffer) { + if (s_events_server.on_mk_tcp_session_data) { + s_events_server.on_mk_tcp_session_data(_local_port,this, buffer->data(), buffer->size()); + } +} + +void TcpSessionForC::onError(const SockException &err) { + if (s_events_server.on_mk_tcp_session_disconnect) { + s_events_server.on_mk_tcp_session_disconnect(_local_port,this, err.getErrCode(), err.what()); + } +} + +void TcpSessionForC::onManager() { + if (s_events_server.on_mk_tcp_session_manager) { + s_events_server.on_mk_tcp_session_manager(_local_port,this); + } +} + +void stopAllTcpServer(){ + CLEAR_ARR(s_tcp_server); +} + +API_EXPORT void API_CALL mk_tcp_session_set_user_data(mk_tcp_session session,void *user_data){ + assert(session); + TcpSessionForC *obj = (TcpSessionForC *)session; + obj->_user_data = user_data; +} + +API_EXPORT void* API_CALL mk_tcp_session_get_user_data(mk_tcp_session session){ + assert(session); + TcpSessionForC *obj = (TcpSessionForC *)session; + return obj->_user_data; +} + +API_EXPORT void API_CALL mk_tcp_server_events_listen(const mk_tcp_session_events *events){ + if (events) { + memcpy(&s_events_server, events, sizeof(s_events_server)); + } else { + memset(&s_events_server, 0, sizeof(s_events_server)); + } +} + +API_EXPORT uint16_t API_CALL mk_tcp_server_start(uint16_t port, mk_tcp_type type){ + type = MAX(mk_type_tcp, MIN(type, mk_type_wss)); + try { + s_tcp_server[type] = std::make_shared(); + switch (type) { + case mk_type_tcp: + s_tcp_server[type]->start(port); + break; + case mk_type_ssl: + s_tcp_server[type]->start >(port); + break; + case mk_type_ws: + s_tcp_server[type]->start>(port); + break; + case mk_type_wss: + s_tcp_server[type]->start>(port); + break; + default: + return 0; + } + return s_tcp_server[type]->getPort(); + } catch (std::exception &ex) { + s_tcp_server[type].reset(); + WarnL << ex.what(); + return 0; + } +} + +///////////////////////////////////////////////////TcpClientForC///////////////////////////////////////////////////////// +TcpClientForC::TcpClientForC(mk_tcp_client_events *events){ + _events = *events; +} + + +void TcpClientForC::onRecv(const Buffer::Ptr &pBuf) { + if(_events.on_mk_tcp_client_data){ + _events.on_mk_tcp_client_data(_client,pBuf->data(),pBuf->size()); + } +} + +void TcpClientForC::onErr(const SockException &ex) { + if(_events.on_mk_tcp_client_disconnect){ + _events.on_mk_tcp_client_disconnect(_client,ex.getErrCode(),ex.what()); + } +} + +void TcpClientForC::onManager() { + if(_events.on_mk_tcp_client_manager){ + _events.on_mk_tcp_client_manager(_client); + } +} + +void TcpClientForC::onConnect(const SockException &ex) { + if(_events.on_mk_tcp_client_connect){ + _events.on_mk_tcp_client_connect(_client,ex.getErrCode(),ex.what()); + } +} + +TcpClientForC::~TcpClientForC() { + TraceL << "mk_tcp_client_release:" << _client; +} + +void TcpClientForC::setClient(mk_tcp_client client) { + _client = client; + TraceL << "mk_tcp_client_create:" << _client; +} + +TcpClientForC::Ptr *mk_tcp_client_create_l(mk_tcp_client_events *events, mk_tcp_type type){ + assert(events); + type = MAX(mk_type_tcp, MIN(type, mk_type_wss)); + switch (type) { + case mk_type_tcp: + return new TcpClientForC::Ptr(new TcpClientForC(events)); + case mk_type_ssl: + return (TcpClientForC::Ptr *)new shared_ptr >(new TcpSessionWithSSL(events)); + case mk_type_ws: + return (TcpClientForC::Ptr *)new shared_ptr >(new WebSocketClient(events)); + case mk_type_wss: + return (TcpClientForC::Ptr *)new shared_ptr >(new WebSocketClient(events)); + default: + return nullptr; + } +} + +API_EXPORT mk_tcp_client API_CALL mk_tcp_client_create(mk_tcp_client_events *events, mk_tcp_type type){ + auto ret = mk_tcp_client_create_l(events,type); + (*ret)->setClient(ret); + return ret; +} + +API_EXPORT void API_CALL mk_tcp_client_release(mk_tcp_client ctx){ + assert(ctx); + TcpClient::Ptr *client = (TcpClient::Ptr *)ctx; + delete client; +} + +API_EXPORT void API_CALL mk_tcp_client_connect(mk_tcp_client ctx, const char *host, uint16_t port, float time_out_sec){ + assert(ctx); + TcpClient::Ptr *client = (TcpClient::Ptr *)ctx; + (*client)->startConnect(host,port); +} + +API_EXPORT void API_CALL mk_tcp_client_send(mk_tcp_client ctx, const char *data, int len){ + assert(ctx && data); + TcpClient::Ptr *client = (TcpClient::Ptr *)ctx; + (*client)->send(data,len); +} + +API_EXPORT void API_CALL mk_tcp_client_send_safe(mk_tcp_client ctx, const char *data, int len){ + assert(ctx && data); + TcpClient::Ptr *client = (TcpClient::Ptr *)ctx; + weak_ptr weakClient = *client; + Buffer::Ptr buf = (*client)->obtainBuffer(data,len); + (*client)->async([weakClient,buf](){ + auto strongClient = weakClient.lock(); + if(strongClient){ + strongClient->send(buf); + } + }); +} + +API_EXPORT void API_CALL mk_tcp_client_set_user_data(mk_tcp_client ctx,void *user_data){ + assert(ctx); + TcpClientForC::Ptr *client = (TcpClientForC::Ptr *)ctx; + (*client)->_user_data = user_data; +} + +API_EXPORT void* API_CALL mk_tcp_client_get_user_data(mk_tcp_client ctx){ + assert(ctx); + TcpClientForC::Ptr *client = (TcpClientForC::Ptr *)ctx; + return (*client)->_user_data; +} diff --git a/api/source/mk_tcp_private.h b/api/source/mk_tcp_private.h new file mode 100644 index 00000000..379068f4 --- /dev/null +++ b/api/source/mk_tcp_private.h @@ -0,0 +1,62 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef MK_TCP_PRIVATE_H +#define MK_TCP_PRIVATE_H + +#include "mk_tcp.h" +#include "Network/TcpClient.h" +#include "Network/TcpSession.h" +using namespace toolkit; + +class TcpClientForC : public TcpClient { + public: + typedef std::shared_ptr Ptr; + TcpClientForC(mk_tcp_client_events *events) ; + ~TcpClientForC() override ; + void onRecv(const Buffer::Ptr &pBuf) override; + void onErr(const SockException &ex) override; + void onManager() override; + void onConnect(const SockException &ex) override; + void setClient(mk_tcp_client client); + void *_user_data; + private: + mk_tcp_client_events _events; + mk_tcp_client _client; +}; + +class TcpSessionForC : public TcpSession { + public: + TcpSessionForC(const Socket::Ptr &pSock) ; + ~TcpSessionForC() override = default; + void onRecv(const Buffer::Ptr &buffer) override ; + void onError(const SockException &err) override; + void onManager() override; + void *_user_data; + uint16_t _local_port; +}; + +#endif //MK_TCP_PRIVATE_H diff --git a/api/source/mk_thread.cpp b/api/source/mk_thread.cpp new file mode 100644 index 00000000..185e54f1 --- /dev/null +++ b/api/source/mk_thread.cpp @@ -0,0 +1,76 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "mk_thread.h" +#include "mk_tcp_private.h" +#include "Util/logger.h" +#include "Poller/EventPoller.h" +using namespace std; +using namespace toolkit; + +API_EXPORT mk_thread API_CALL mk_thread_from_tcp_session(mk_tcp_session ctx){ + assert(ctx); + TcpSession *obj = (TcpSession *)ctx; + return obj->getPoller().get(); +} + +API_EXPORT mk_thread API_CALL mk_thread_from_tcp_client(mk_tcp_client ctx){ + assert(ctx); + TcpClient::Ptr *client = (TcpClient::Ptr *)ctx; + return (*client)->getPoller().get(); +} + +API_EXPORT void API_CALL mk_async_do(mk_thread ctx,on_mk_async cb, void *user_data){ + assert(ctx && cb); + EventPoller *poller = (EventPoller *)ctx; + poller->async([cb,user_data](){ + cb(user_data); + }); +} + +API_EXPORT void API_CALL mk_sync_do(mk_thread ctx,on_mk_async cb, void *user_data){ + assert(ctx && cb); + EventPoller *poller = (EventPoller *)ctx; + poller->sync([cb,user_data](){ + cb(user_data); + }); +} + +API_EXPORT mk_timer API_CALL mk_timer_create(mk_thread ctx,uint64_t delay_ms,on_mk_timer cb, void *user_data){ + assert(ctx && cb); + EventPoller *poller = (EventPoller *)ctx; + auto ret = poller->doDelayTask(delay_ms,[cb,user_data](){ + return cb(user_data); + }); + return new DelayTask::Ptr(ret); +} + +API_EXPORT void API_CALL mk_timer_release(mk_timer ctx){ + assert(ctx); + DelayTask::Ptr *obj = (DelayTask::Ptr *)ctx; + (*obj)->cancel(); + delete obj; +} \ No newline at end of file diff --git a/api/source/mk_util.cpp b/api/source/mk_util.cpp new file mode 100644 index 00000000..97bd9158 --- /dev/null +++ b/api/source/mk_util.cpp @@ -0,0 +1,70 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "mk_util.h" +#include +#include +#include "Util/logger.h" +using namespace std; +using namespace toolkit; + +API_EXPORT char* API_CALL mk_util_get_exe_path(){ + return strdup(exePath().data()); +} + +API_EXPORT char* API_CALL mk_util_get_exe_dir(const char *relative_path){ + if(relative_path){ + return strdup((exeDir() + relative_path).data()); + } + return strdup(exeDir().data()); +} + +API_EXPORT uint64_t API_CALL mk_util_get_current_millisecond(){ + return getCurrentMillisecond(); +} + +API_EXPORT char* API_CALL mk_util_get_current_time_string(const char *fmt){ + assert(fmt); + return strdup(getTimeStr(fmt).data()); +} + +API_EXPORT char* API_CALL mk_util_hex_dump(const void *buf, int len){ + assert(buf && len > 0); + return strdup(hexdump(buf,len).data()); +} + +API_EXPORT void API_CALL mk_log_printf(int level, const char *file, const char *function, int line, const char *fmt, ...) { + assert(file && function && fmt); + LogContextCapturer info(Logger::Instance(), (LogLevel) level, file, function, line); + va_list pArg; + va_start(pArg, fmt); + char buf[4096]; + int n = vsprintf(buf, fmt, pArg); + buf[n] = '\0'; + va_end(pArg); + info << buf; +} + diff --git a/api/tests/CMakeLists.txt b/api/tests/CMakeLists.txt new file mode 100644 index 00000000..24f5cdb5 --- /dev/null +++ b/api/tests/CMakeLists.txt @@ -0,0 +1,25 @@ +aux_source_directory(. TEST_SRC_LIST) +foreach(TEST_SRC ${TEST_SRC_LIST}) + STRING(REGEX REPLACE "^\\./|\\.c[a-zA-Z0-9_]*$" "" TEST_EXE_NAME ${TEST_SRC}) + message(STATUS "add c api tester:${TEST_EXE_NAME}") + set(exe_name api_tester_${TEST_EXE_NAME}) + add_executable(${exe_name} ${TEST_SRC}) + + if(WIN32) + set_target_properties(${exe_name} PROPERTIES COMPILE_FLAGS ${VS_FALGS} ) + endif(WIN32) + + target_link_libraries(${exe_name} mk_api) +endforeach() + + + + + + + + + + + + diff --git a/api/tests/server.c b/api/tests/server.c new file mode 100644 index 00000000..575313bb --- /dev/null +++ b/api/tests/server.c @@ -0,0 +1,449 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "mk_mediakit.h" + +#ifdef _WIN32 +#include "windows.h" +#else + +#include "unistd.h" + +#endif + +#define LOG_LEV 4 + +/** + * 注册或反注册MediaSource事件广播 + * @param regist 注册为1,注销为0 + * @param sender 该MediaSource对象 + */ +void API_CALL on_mk_media_changed(int regist, + const mk_media_source sender) { + log_printf(LOG_LEV,"%d %s/%s/%s/%s",(int)regist, + mk_media_source_get_schema(sender), + mk_media_source_get_vhost(sender), + mk_media_source_get_app(sender), + mk_media_source_get_stream(sender)); + +} + +/** + * 收到rtsp/rtmp推流事件广播,通过该事件控制推流鉴权 + * @see mk_publish_auth_invoker_do + * @param url_info 推流url相关信息 + * @param invoker 执行invoker返回鉴权结果 + * @param sender 该tcp客户端相关信息 + */ +void API_CALL on_mk_media_publish(const mk_media_info url_info, + const mk_publish_auth_invoker invoker, + const mk_tcp_session sender) { + log_printf(LOG_LEV, + "client info, local: %s:%d, peer: %s:%d\n" + "%s/%s/%s/%s, url params: %s", + mk_tcp_session_local_ip(sender), + mk_tcp_session_local_port(sender), + mk_tcp_session_peer_ip(sender), + mk_tcp_session_peer_port(sender), + mk_media_info_get_schema(url_info), + mk_media_info_get_vhost(url_info), + mk_media_info_get_app(url_info), + mk_media_info_get_stream(url_info), + mk_media_info_get_params(url_info)); + + //允许推流,并且允许转rtxp/hls/mp4 + mk_publish_auth_invoker_do(invoker, NULL, 1, 1, 1); +} + +/** + * 播放rtsp/rtmp/http-flv/hls事件广播,通过该事件控制播放鉴权 + * @see mk_auth_invoker_do + * @param url_info 播放url相关信息 + * @param invoker 执行invoker返回鉴权结果 + * @param sender 播放客户端相关信息 + */ +void API_CALL on_mk_media_play(const mk_media_info url_info, + const mk_auth_invoker invoker, + const mk_tcp_session sender) { + + log_printf(LOG_LEV, + "client info, local: %s:%d, peer: %s:%d\n" + "%s/%s/%s/%s, url params: %s", + mk_tcp_session_local_ip(sender), + mk_tcp_session_local_port(sender), + mk_tcp_session_peer_ip(sender), + mk_tcp_session_peer_port(sender), + mk_media_info_get_schema(url_info), + mk_media_info_get_vhost(url_info), + mk_media_info_get_app(url_info), + mk_media_info_get_stream(url_info), + mk_media_info_get_params(url_info)); + + //允许播放 + mk_auth_invoker_do(invoker, NULL); +} + +/** + * 未找到流后会广播该事件,请在监听该事件后去拉流或其他方式产生流,这样就能按需拉流了 + * @param url_info 播放url相关信息 + * @param sender 播放客户端相关信息 + */ +void API_CALL on_mk_media_not_found(const mk_media_info url_info, + const mk_tcp_session sender) { + log_printf(LOG_LEV, + "client info, local: %s:%d, peer: %s:%d\n" + "%s/%s/%s/%s, url params: %s", + mk_tcp_session_local_ip(sender), + mk_tcp_session_local_port(sender), + mk_tcp_session_peer_ip(sender), + mk_tcp_session_peer_port(sender), + mk_media_info_get_schema(url_info), + mk_media_info_get_vhost(url_info), + mk_media_info_get_app(url_info), + mk_media_info_get_stream(url_info), + mk_media_info_get_params(url_info)); +} + +/** + * 某个流无人消费时触发,目的为了实现无人观看时主动断开拉流等业务逻辑 + * @param sender 该MediaSource对象 + */ +void API_CALL on_mk_media_no_reader(const mk_media_source sender) { + log_printf(LOG_LEV, + "%s/%s/%s/%s", + mk_media_source_get_schema(sender), + mk_media_source_get_vhost(sender), + mk_media_source_get_app(sender), + mk_media_source_get_stream(sender)); +} + +/** + * 收到http api请求广播(包括GET/POST) + * @param parser http请求内容对象 + * @param invoker 执行该invoker返回http回复 + * @param consumed 置1则说明我们要处理该事件 + * @param sender http客户端相关信息 + */ +//测试url : http://127.0.0.1/api/test +void API_CALL on_mk_http_request(const mk_parser parser, + const mk_http_response_invoker invoker, + int *consumed, + const mk_tcp_session sender) { + + log_printf(LOG_LEV, + "client info, local: %s:%d, peer: %s:%d\n" + "%s %s?%s %s\n" + "User-Agent: %s\n" + "%s", + mk_tcp_session_local_ip(sender), + mk_tcp_session_local_port(sender), + mk_tcp_session_peer_ip(sender), + mk_tcp_session_peer_port(sender), + mk_parser_get_method(parser), + mk_parser_get_url(parser), + mk_parser_get_url_params(parser), + mk_parser_get_tail(parser), + mk_parser_get_header(parser, "User-Agent"), + mk_parser_get_content(parser,NULL)); + + const char *url = mk_parser_get_url(parser); + if(strcmp(url,"/api/test") != 0){ + *consumed = 0; + return; + } + + //只拦截api: /api/test + *consumed = 1; + const char *response_header[] = {"Content-Type","text/html",NULL}; + const char *content = + "" + "" + "hello world" + "" + "" + "

hello world


" + "
""ZLMediaKit-4.0
" + "" + ""; + mk_http_body body = mk_http_body_from_string(content,0); + mk_http_response_invoker_do(invoker, "200 OK", response_header, body); + mk_http_body_release(body); +} + +/** + * 在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限 + * @param parser http请求内容对象 + * @param path 文件绝对路径 + * @param is_dir path是否为文件夹 + * @param invoker 执行invoker返回本次访问文件的结果 + * @param sender http客户端相关信息 + */ +void API_CALL on_mk_http_access(const mk_parser parser, + const char *path, + int is_dir, + const mk_http_access_path_invoker invoker, + mk_tcp_session sender) { + + log_printf(LOG_LEV, + "client info, local: %s:%d, peer: %s:%d, path: %s ,is_dir: %d\n" + "%s %s?%s %s\n" + "User-Agent: %s\n" + "%s", + mk_tcp_session_local_ip(sender), + mk_tcp_session_local_port(sender), + mk_tcp_session_peer_ip(sender), + mk_tcp_session_peer_port(sender), + path,(int)is_dir, + mk_parser_get_method(parser), + mk_parser_get_url(parser), + mk_parser_get_url_params(parser), + mk_parser_get_tail(parser), + mk_parser_get_header(parser,"User-Agent"), + mk_parser_get_content(parser,NULL)); + + //有访问权限,每次访问文件都需要鉴权 + mk_http_access_path_invoker_do(invoker, NULL, NULL, 0); +} + +/** + * 在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射 + * 在该事件中通过自行覆盖path参数,可以做到譬如根据虚拟主机或者app选择不同http根目录的目的 + * @param parser http请求内容对象 + * @param path 文件绝对路径,覆盖之可以重定向到其他文件 + * @param sender http客户端相关信息 + */ +void API_CALL on_mk_http_before_access(const mk_parser parser, + char *path, + const mk_tcp_session sender) { + log_printf(LOG_LEV, + "client info, local: %s:%d, peer: %s:%d, path: %s\n" + "%s %s?%s %s\n" + "User-Agent: %s\n" + "%s", + mk_tcp_session_local_ip(sender), + mk_tcp_session_local_port(sender), + mk_tcp_session_peer_ip(sender), + mk_tcp_session_peer_port(sender), + path, + mk_parser_get_method(parser), + mk_parser_get_url(parser), + mk_parser_get_url_params(parser), + mk_parser_get_tail(parser), + mk_parser_get_header(parser, "User-Agent"), + mk_parser_get_content(parser,NULL)); + //覆盖path的值可以重定向文件 +} + +/** + * 该rtsp流是否需要认证?是的话调用invoker并传入realm,否则传入空的realm + * @param url_info 请求rtsp url相关信息 + * @param invoker 执行invoker返回是否需要rtsp专属认证 + * @param sender rtsp客户端相关信息 + */ +void API_CALL on_mk_rtsp_get_realm(const mk_media_info url_info, + const mk_rtsp_get_realm_invoker invoker, + const mk_tcp_session sender) { + log_printf(LOG_LEV, + "client info, local: %s:%d, peer: %s:%d\n" + "%s/%s/%s/%s, url params: %s", + mk_tcp_session_local_ip(sender), + mk_tcp_session_local_port(sender), + mk_tcp_session_peer_ip(sender), + mk_tcp_session_peer_port(sender), + mk_media_info_get_schema(url_info), + mk_media_info_get_vhost(url_info), + mk_media_info_get_app(url_info), + mk_media_info_get_stream(url_info), + mk_media_info_get_params(url_info)); + + //rtsp播放默认鉴权 + mk_rtsp_get_realm_invoker_do(invoker, "zlmediakit"); +} + +/** + * 请求认证用户密码事件,user_name为用户名,must_no_encrypt如果为1,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败 + * 获取到密码后请调用invoker并输入对应类型的密码和密码类型,invoker执行时会匹配密码 + * @param url_info 请求rtsp url相关信息 + * @param realm rtsp认证realm + * @param user_name rtsp认证用户名 + * @param must_no_encrypt 如果为1,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败 + * @param invoker 执行invoker返回rtsp专属认证的密码 + * @param sender rtsp客户端信息 + */ +void API_CALL on_mk_rtsp_auth(const mk_media_info url_info, + const char *realm, + const char *user_name, + int must_no_encrypt, + const mk_rtsp_auth_invoker invoker, + const mk_tcp_session sender) { + + log_printf(LOG_LEV, + "client info, local: %s:%d, peer: %s:%d\n" + "%s/%s/%s/%s, url params: %s\n" + "realm: %s, user_name: %s, must_no_encrypt: %d", + mk_tcp_session_local_ip(sender), + mk_tcp_session_local_port(sender), + mk_tcp_session_peer_ip(sender), + mk_tcp_session_peer_port(sender), + mk_media_info_get_schema(url_info), + mk_media_info_get_vhost(url_info), + mk_media_info_get_app(url_info), + mk_media_info_get_stream(url_info), + mk_media_info_get_params(url_info), + realm,user_name,(int)must_no_encrypt); + + //rtsp播放用户名跟密码一致 + mk_rtsp_auth_invoker_do(invoker,0,user_name); +} + +/** + * 录制mp4分片文件成功后广播 + */ +void API_CALL on_mk_record_mp4(const mk_mp4_info mp4) { + log_printf(LOG_LEV, + "\nstart_time: %d\n" + "time_len: %d\n" + "file_size: %d\n" + "file_path: %s\n" + "file_name: %s\n" + "folder: %s\n" + "url: %s\n" + "vhost: %s\n" + "app: %s\n" + "stream: %s\n", + mk_mp4_info_get_start_time(mp4), + mk_mp4_info_get_time_len(mp4), + mk_mp4_info_get_file_size(mp4), + mk_mp4_info_get_file_path(mp4), + mk_mp4_info_get_file_name(mp4), + mk_mp4_info_get_folder(mp4), + mk_mp4_info_get_url(mp4), + mk_mp4_info_get_vhost(mp4), + mk_mp4_info_get_app(mp4), + mk_mp4_info_get_stream(mp4)); +} + +/** + * shell登录鉴权 + */ +void API_CALL on_mk_shell_login(const char *user_name, + const char *passwd, + const mk_auth_invoker invoker, + const mk_tcp_session sender) { + log_printf(LOG_LEV,"client info, local: %s:%d, peer: %s:%d\n" + "user_name: %s, passwd: %s", + mk_tcp_session_local_ip(sender), + mk_tcp_session_local_port(sender), + mk_tcp_session_peer_ip(sender), + mk_tcp_session_peer_port(sender), + user_name, passwd); + //允许登录shell + mk_auth_invoker_do(invoker, NULL); +} + +/** + * 停止rtsp/rtmp/http-flv会话后流量汇报事件广播 + * @param url_info 播放url相关信息 + * @param total_bytes 耗费上下行总流量,单位字节数 + * @param total_seconds 本次tcp会话时长,单位秒 + * @param is_player 客户端是否为播放器 + * @param peer_ip 客户端ip + * @param peer_port 客户端端口号 + */ +void API_CALL on_mk_flow_report(const mk_media_info url_info, + uint64_t total_bytes, + uint64_t total_seconds, + int is_player, + const char *peer_ip, + uint16_t peer_port) { + log_printf(LOG_LEV,"%s/%s/%s/%s, url params: %s," + "total_bytes: %d, total_seconds: %d, is_player: %d, peer_ip:%s, peer_port:%d", + mk_media_info_get_schema(url_info), + mk_media_info_get_vhost(url_info), + mk_media_info_get_app(url_info), + mk_media_info_get_stream(url_info), + mk_media_info_get_params(url_info), + (int)total_bytes, (int)total_seconds, (int)is_player,peer_ip, (int)peer_port); +} + +static int flag = 1; +static void s_on_exit(int sig){ + flag = 0; +} +int main(int argc, char *argv[]) { + char *ini_path = mk_util_get_exe_dir("c_api.ini"); + char *ssl_path = mk_util_get_exe_dir("ssl.p12"); + + mk_config config = { + .ini = ini_path, + .ini_is_path = 1, + .log_level = 0, + .ssl = ssl_path, + .ssl_is_path = 1, + .ssl_pwd = NULL, + .thread_num = 0 + }; + mk_env_init(&config); + free(ini_path); + free(ssl_path); + + mk_http_server_start(80, 0); + mk_http_server_start(443, 1); + mk_rtsp_server_start(554, 0); + mk_rtmp_server_start(1935, 0); + mk_shell_server_start(9000); + mk_rtp_server_start(10000); + + mk_events events = { + .on_mk_media_changed = on_mk_media_changed, + .on_mk_media_publish = on_mk_media_publish, + .on_mk_media_play = on_mk_media_play, + .on_mk_media_not_found = on_mk_media_not_found, + .on_mk_media_no_reader = on_mk_media_no_reader, + .on_mk_http_request = on_mk_http_request, + .on_mk_http_access = on_mk_http_access, + .on_mk_http_before_access = on_mk_http_before_access, + .on_mk_rtsp_get_realm = on_mk_rtsp_get_realm, + .on_mk_rtsp_auth = on_mk_rtsp_auth, + .on_mk_record_mp4 = on_mk_record_mp4, + .on_mk_shell_login = on_mk_shell_login, + .on_mk_flow_report = on_mk_flow_report + }; + mk_events_listen(&events); + + signal(SIGINT, s_on_exit );// 设置退出信号 + while (flag) { +#ifdef _WIN32 + Sleep(1000); +#else + sleep(1); +#endif + } + mk_stop_all_server(); + return 0; +} \ No newline at end of file diff --git a/api/tests/websocket.c b/api/tests/websocket.c new file mode 100644 index 00000000..82e2a878 --- /dev/null +++ b/api/tests/websocket.c @@ -0,0 +1,227 @@ +/* + * MIT License + * + * Copyright (c) 2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include "mk_mediakit.h" +#ifdef _WIN32 +#include "windows.h" +#else +#include "unistd.h" +#endif + +#define LOG_LEV 4 +//修改此宏,可以选择协议类型 +#define TCP_TYPE mk_type_ws + +static int flag = 1; +static void s_on_exit(int sig){ + flag = 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +typedef struct { + mk_tcp_session _session; + //下面你可以夹杂你的私货数据 + char your_some_useful_data[1024]; +} tcp_session_user_data; +/** + * 当tcp客户端连接服务器时触发 + * @param session 会话处理对象 + */ +void API_CALL on_mk_tcp_session_create(uint16_t server_port,mk_tcp_session session){ + log_printf(LOG_LEV,"%s %d",mk_tcp_session_peer_ip(session),(int)mk_tcp_session_peer_port(session)); + tcp_session_user_data *user_data = malloc(sizeof(tcp_session_user_data)); + user_data->_session = session; + mk_tcp_session_set_user_data(session,user_data); +} + +/** + * 收到tcp客户端发过来的数据 + * @param session 会话处理对象 + * @param data 数据指针 + * @param len 数据长度 + */ +void API_CALL on_mk_tcp_session_data(uint16_t server_port,mk_tcp_session session,const char *data,int len){ + log_printf(LOG_LEV,"from %s %d, data[%d]: %s",mk_tcp_session_peer_ip(session),(int)mk_tcp_session_peer_port(session), len,data); + mk_tcp_session_send(session,"echo:",0); + mk_tcp_session_send(session,data,len); +} + +/** + * 每隔2秒的定时器,用于管理超时等任务 + * @param session 会话处理对象 + */ +void API_CALL on_mk_tcp_session_manager(uint16_t server_port,mk_tcp_session session){ + log_printf(LOG_LEV,"%s %d",mk_tcp_session_peer_ip(session),(int)mk_tcp_session_peer_port(session)); +} + +/** + * 一般由于客户端断开tcp触发 + * 本事件中可以调用mk_tcp_session_send_safe函数 + * @param session 会话处理对象 + * @param code 错误代码 + * @param msg 错误提示 + */ +void API_CALL on_mk_tcp_session_disconnect(uint16_t server_port,mk_tcp_session session,int code,const char *msg){ + log_printf(LOG_LEV,"%s %d: %d %s",mk_tcp_session_peer_ip(session),(int)mk_tcp_session_peer_port(session),code,msg); + tcp_session_user_data *user_data = (tcp_session_user_data *)mk_tcp_session_get_user_data(session); + free(user_data); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +typedef struct { + mk_tcp_client client; + //下面你可以夹杂你的私货数据 + char your_some_useful_data[1024]; + int count; +} tcp_client_user_data; + +/** + * tcp客户端连接服务器成功或失败回调 + * @param client tcp客户端 + * @param code 0为连接成功,否则为失败原因 + * @param msg 连接失败错误提示 + */ +void API_CALL on_mk_tcp_client_connect(mk_tcp_client client,int code,const char *msg){ + log_printf(LOG_LEV,"connect result:%d %s",code,msg); + if(code == 0){ + //连接上后我们发送一个hello world测试数据 + mk_tcp_client_send(client,"hello world",0); + }else{ + tcp_client_user_data *user_data = mk_tcp_client_get_user_data(client); + mk_tcp_client_release(client); + free(user_data); + } +} + +/** + * tcp客户端与tcp服务器之间断开回调 + * 一般是eof事件导致 + * @param client tcp客户端 + * @param code 错误代码 + * @param msg 错误提示 + */ +void API_CALL on_mk_tcp_client_disconnect(mk_tcp_client client,int code,const char *msg){ + log_printf(LOG_LEV,"disconnect:%d %s",code,msg); + //服务器主动断开了,我们销毁对象 + tcp_client_user_data *user_data = mk_tcp_client_get_user_data(client); + mk_tcp_client_release(client); + free(user_data); +} + +/** + * 收到tcp服务器发来的数据 + * @param client tcp客户端 + * @param data 数据指针 + * @param len 数据长度 + */ +void API_CALL on_mk_tcp_client_data(mk_tcp_client client,const char *data,int len){ + log_printf(LOG_LEV,"data[%d]:%s",len,data); +} + +/** + * 每隔2秒的定时器,用于管理超时等任务 + * @param client tcp客户端 + */ +void API_CALL on_mk_tcp_client_manager(mk_tcp_client client){ + tcp_client_user_data *user_data = mk_tcp_client_get_user_data(client); + char buf[1024]; + sprintf(buf,"on_mk_tcp_client_manager:%d",user_data->count); + mk_tcp_client_send(client,buf,0); + + if(++user_data->count == 5){ + //发送5遍后主动释放对象 + mk_tcp_client_release(client); + free(user_data); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +void test_server(){ + mk_tcp_session_events events_server = { + .on_mk_tcp_session_create = on_mk_tcp_session_create, + .on_mk_tcp_session_data = on_mk_tcp_session_data, + .on_mk_tcp_session_manager = on_mk_tcp_session_manager, + .on_mk_tcp_session_disconnect = on_mk_tcp_session_disconnect + }; + + mk_tcp_server_events_listen(&events_server); + mk_tcp_server_start(80, TCP_TYPE); +} + +void test_client(){ + mk_tcp_client_events events_clent = { + .on_mk_tcp_client_connect = on_mk_tcp_client_connect, + .on_mk_tcp_client_data = on_mk_tcp_client_data, + .on_mk_tcp_client_disconnect = on_mk_tcp_client_disconnect, + .on_mk_tcp_client_manager = on_mk_tcp_client_manager, + }; + mk_tcp_client client = mk_tcp_client_create(&events_clent, TCP_TYPE); + + tcp_client_user_data *user_data = (tcp_client_user_data *)malloc(sizeof(tcp_client_user_data)); + user_data->client = client; + user_data->count = 0; + mk_tcp_client_set_user_data(client,user_data); + + mk_tcp_client_connect(client, "121.40.165.18", 8800, 3); + //你可以连接127.0.0.1 80测试 +// mk_tcp_client_connect(client, "127.0.0.1", 80, 3); +} + +int main(int argc, char *argv[]) { + char *ini_path = mk_util_get_exe_dir("c_api.ini"); + char *ssl_path = mk_util_get_exe_dir("ssl.p12"); + + mk_config config = { + .ini = ini_path, + .ini_is_path = 1, + .log_level = 0, + .ssl = ssl_path, + .ssl_is_path = 1, + .ssl_pwd = NULL, + .thread_num = 0 + }; + mk_env_init(&config); + free(ini_path); + free(ssl_path); + + test_server(); + test_client(); + + signal(SIGINT, s_on_exit );// 设置退出信号 + while (flag) { +#ifdef _WIN32 + Sleep(1000); +#else + sleep(1); +#endif + } + mk_stop_all_server(); + return 0; +} \ No newline at end of file diff --git a/build_docker_images.sh b/build_docker_images.sh new file mode 100644 index 00000000..a642584f --- /dev/null +++ b/build_docker_images.sh @@ -0,0 +1,3 @@ +#!/bin/bash +docker build -t gemfield/zlmediakit:20.01-runtime-ubuntu18.04 -f docker/ubuntu18.04/Dockerfile.runtime . +#docker build -t gemfield/zlmediakit:20.01-devel-ubuntu18.04 -f docker/ubuntu18.04/Dockerfile.devel . diff --git a/build_for_ios.sh b/build_for_ios.sh deleted file mode 100755 index c7bf929b..00000000 --- a/build_for_ios.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -cd .. -git clone --depth=1 https://github.com/xiongziliang/ZLMediaKit.git -cd ZLMediaKit -git submodule init -git submodule update - -mkdir -p ios_build -rm -rf ./build -ln -s ./ios_build build -cd ios_build -cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/iOS.cmake -DIOS_PLATFORM=OS -make -j4 diff --git a/cmake/FindZLTOOLKIT.cmake b/cmake/FindZLTOOLKIT.cmake deleted file mode 100644 index 3839ca3f..00000000 --- a/cmake/FindZLTOOLKIT.cmake +++ /dev/null @@ -1,18 +0,0 @@ -find_path(ZLTOOLKIT_INCLUDE_DIR - NAMES Network/Socket.h - PATHS - ${PROJECT_SOURCE_DIR}/../ZLToolKit/src - $ENV{HOME}/ZLToolKit/include) - -find_library(ZLTOOLKIT_LIBRARY - NAMES ZLToolKit - PATHS - ${PROJECT_SOURCE_DIR}/../ZLToolKit/build/lib - $ENV{HOME}/ZLToolKit/lib) - -set(ZLTOOLKIT_LIBRARIES ${ZLTOOLKIT_LIBRARY}) -set(ZLTOOLKIT_INCLUDE_DIRS ${ZLTOOLKIT_INCLUDE_DIR}) - -include(FindPackageHandleStandardArgs) - -find_package_handle_standard_args(ZLTOOLKIT DEFAULT_MSG ZLTOOLKIT_LIBRARY ZLTOOLKIT_INCLUDE_DIR) diff --git a/cmake/iOS.cmake b/cmake/iOS.cmake deleted file mode 100644 index f4367787..00000000 --- a/cmake/iOS.cmake +++ /dev/null @@ -1,209 +0,0 @@ -# This file is based off of the Platform/Darwin.cmake and Platform/UnixPaths.cmake -# files which are included with CMake 2.8.4 -# It has been altered for iOS development - -# Options: -# -# IOS_PLATFORM = OS (default) or SIMULATOR or SIMULATOR64 -# This decides if SDKS will be selected from the iPhoneOS.platform or iPhoneSimulator.platform folders -# OS - the default, used to build for iPhone and iPad physical devices, which have an arm arch. -# SIMULATOR - used to build for the Simulator platforms, which have an x86 arch. -# -# CMAKE_IOS_DEVELOPER_ROOT = automatic(default) or /path/to/platform/Developer folder -# By default this location is automatcially chosen based on the IOS_PLATFORM value above. -# If set manually, it will override the default location and force the user of a particular Developer Platform -# -# CMAKE_IOS_SDK_ROOT = automatic(default) or /path/to/platform/Developer/SDKs/SDK folder -# By default this location is automatcially chosen based on the CMAKE_IOS_DEVELOPER_ROOT value. -# In this case it will always be the most up-to-date SDK found in the CMAKE_IOS_DEVELOPER_ROOT path. -# If set manually, this will force the use of a specific SDK version - -# Macros: -# -# set_xcode_property (TARGET XCODE_PROPERTY XCODE_VALUE) -# A convenience macro for setting xcode specific properties on targets -# example: set_xcode_property (myioslib IPHONEOS_DEPLOYMENT_TARGET "3.1") -# -# find_host_package (PROGRAM ARGS) -# A macro used to find executable programs on the host system, not within the iOS environment. -# Thanks to the android-cmake project for providing the command - -# Standard settings -set (CMAKE_SYSTEM_NAME Darwin) -set (CMAKE_SYSTEM_VERSION 1) -set (UNIX True) -set (APPLE True) -set (IOS True) - -# Required as of cmake 2.8.10 -set (CMAKE_OSX_DEPLOYMENT_TARGET "" CACHE STRING "Force unset of the deployment target for iOS" FORCE) - -# Determine the cmake host system version so we know where to find the iOS SDKs -find_program (CMAKE_UNAME uname /bin /usr/bin /usr/local/bin) -if (CMAKE_UNAME) -exec_program(uname ARGS -r OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION) -string (REGEX REPLACE "^([0-9]+)\\.([0-9]+).*$" "\\1" DARWIN_MAJOR_VERSION "${CMAKE_HOST_SYSTEM_VERSION}") -endif (CMAKE_UNAME) - -# Force the compilers to gcc for iOS -include (CMakeForceCompiler) -#CMAKE_FORCE_C_COMPILER (/usr/bin/gcc Apple) -set(CMAKE_C_COMPILER /usr/bin/clang) -#CMAKE_FORCE_CXX_COMPILER (/usr/bin/g++ Apple) -set(CMAKE_CXX_COMPILER /usr/bin/clang++) -set(CMAKE_AR ar CACHE FILEPATH "" FORCE) - -# Skip the platform compiler checks for cross compiling -set (CMAKE_CXX_COMPILER_WORKS TRUE) -set (CMAKE_C_COMPILER_WORKS TRUE) - -# All iOS/Darwin specific settings - some may be redundant -set (CMAKE_SHARED_LIBRARY_PREFIX "lib") -set (CMAKE_SHARED_LIBRARY_SUFFIX ".dylib") -set (CMAKE_SHARED_MODULE_PREFIX "lib") -set (CMAKE_SHARED_MODULE_SUFFIX ".so") -set (CMAKE_MODULE_EXISTS 1) -set (CMAKE_DL_LIBS "") - -set (CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ") -set (CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ") -set (CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}") -set (CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}") - -# Hidden visibilty is required for cxx on iOS -set (CMAKE_C_FLAGS_INIT "") -set (CMAKE_CXX_FLAGS_INIT "-fvisibility=hidden -fvisibility-inlines-hidden") - -set (CMAKE_C_LINK_FLAGS "-Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}") -set (CMAKE_CXX_LINK_FLAGS "-Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}") - -set (CMAKE_PLATFORM_HAS_INSTALLNAME 1) -set (CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib -headerpad_max_install_names") -set (CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle -headerpad_max_install_names") -set (CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,") -set (CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,") -set (CMAKE_FIND_LIBRARY_SUFFIXES ".dylib" ".so" ".a") - -# hack: if a new cmake (which uses CMAKE_INSTALL_NAME_TOOL) runs on an old build tree -# (where install_name_tool was hardcoded) and where CMAKE_INSTALL_NAME_TOOL isn't in the cache -# and still cmake didn't fail in CMakeFindBinUtils.cmake (because it isn't rerun) -# hardcode CMAKE_INSTALL_NAME_TOOL here to install_name_tool, so it behaves as it did before, Alex -if (NOT DEFINED CMAKE_INSTALL_NAME_TOOL) -find_program(CMAKE_INSTALL_NAME_TOOL install_name_tool) -endif (NOT DEFINED CMAKE_INSTALL_NAME_TOOL) - -# Setup iOS platform unless specified manually with IOS_PLATFORM -if (NOT DEFINED IOS_PLATFORM) -set (IOS_PLATFORM "OS") -endif (NOT DEFINED IOS_PLATFORM) -set (IOS_PLATFORM ${IOS_PLATFORM} CACHE STRING "Type of iOS Platform") - -# Setup building for arm64 or not -if (NOT DEFINED BUILD_ARM64) -set (BUILD_ARM64 true) -endif (NOT DEFINED BUILD_ARM64) -set (BUILD_ARM64 ${BUILD_ARM64} CACHE STRING "Build arm64 arch or not") - -# Check the platform selection and setup for developer root -if (${IOS_PLATFORM} STREQUAL "OS") -set (IOS_PLATFORM_LOCATION "iPhoneOS.platform") - -# This causes the installers to properly locate the output libraries -set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphoneos") -elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR") -set (SIMULATOR true) -set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform") - -# This causes the installers to properly locate the output libraries -set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator") -elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR64") -set (SIMULATOR true) -set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform") - -# This causes the installers to properly locate the output libraries -set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator") -else (${IOS_PLATFORM} STREQUAL "OS") -message (FATAL_ERROR "Unsupported IOS_PLATFORM value selected. Please choose OS or SIMULATOR") -endif (${IOS_PLATFORM} STREQUAL "OS") - -# Setup iOS developer location unless specified manually with CMAKE_IOS_DEVELOPER_ROOT -# Note Xcode 4.3 changed the installation location, choose the most recent one available -exec_program(/usr/bin/xcode-select ARGS -print-path OUTPUT_VARIABLE CMAKE_XCODE_DEVELOPER_DIR) -set (XCODE_POST_43_ROOT "${CMAKE_XCODE_DEVELOPER_DIR}/Platforms/${IOS_PLATFORM_LOCATION}/Developer") -set (XCODE_PRE_43_ROOT "/Developer/Platforms/${IOS_PLATFORM_LOCATION}/Developer") -if (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT) -if (EXISTS ${XCODE_POST_43_ROOT}) -set (CMAKE_IOS_DEVELOPER_ROOT ${XCODE_POST_43_ROOT}) -elseif(EXISTS ${XCODE_PRE_43_ROOT}) -set (CMAKE_IOS_DEVELOPER_ROOT ${XCODE_PRE_43_ROOT}) -endif (EXISTS ${XCODE_POST_43_ROOT}) -endif (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT) -set (CMAKE_IOS_DEVELOPER_ROOT ${CMAKE_IOS_DEVELOPER_ROOT} CACHE PATH "Location of iOS Platform") - -# Find and use the most recent iOS sdk unless specified manually with CMAKE_IOS_SDK_ROOT -if (NOT DEFINED CMAKE_IOS_SDK_ROOT) -file (GLOB _CMAKE_IOS_SDKS "${CMAKE_IOS_DEVELOPER_ROOT}/SDKs/*") -if (_CMAKE_IOS_SDKS) -list (SORT _CMAKE_IOS_SDKS) -list (REVERSE _CMAKE_IOS_SDKS) -list (GET _CMAKE_IOS_SDKS 0 CMAKE_IOS_SDK_ROOT) -else (_CMAKE_IOS_SDKS) -message (FATAL_ERROR "No iOS SDK's found in default search path ${CMAKE_IOS_DEVELOPER_ROOT}. Manually set CMAKE_IOS_SDK_ROOT or install the iOS SDK.") -endif (_CMAKE_IOS_SDKS) -message (STATUS "Toolchain using default iOS SDK: ${CMAKE_IOS_SDK_ROOT}") -endif (NOT DEFINED CMAKE_IOS_SDK_ROOT) -set (CMAKE_IOS_SDK_ROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Location of the selected iOS SDK") - -# Set the sysroot default to the most recent SDK -set (CMAKE_OSX_SYSROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Sysroot used for iOS support") - -# set the architecture for iOS -if (${IOS_PLATFORM} STREQUAL "OS") -set (IOS_ARCH armv7 armv7s arm64) -elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR") -set (IOS_ARCH i386) -elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR64") -set (IOS_ARCH x86_64) -endif (${IOS_PLATFORM} STREQUAL "OS") - -set (CMAKE_OSX_ARCHITECTURES ${IOS_ARCH} CACHE string "Build architecture for iOS") - -# Set the find root to the iOS developer roots and to user defined paths -set (CMAKE_FIND_ROOT_PATH ${CMAKE_IOS_DEVELOPER_ROOT} ${CMAKE_IOS_SDK_ROOT} ${CMAKE_PREFIX_PATH} CACHE string "iOS find search path root") - -# default to searching for frameworks first -set (CMAKE_FIND_FRAMEWORK FIRST) - -# set up the default search directories for frameworks -set (CMAKE_SYSTEM_FRAMEWORK_PATH -${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks -${CMAKE_IOS_SDK_ROOT}/System/Library/PrivateFrameworks -${CMAKE_IOS_SDK_ROOT}/Developer/Library/Frameworks -) - -# only search the iOS sdks, not the remainder of the host filesystem -set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) -set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) - - -# This little macro lets you set any XCode specific property -macro (set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE) -set_property (TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY} ${XCODE_VALUE}) -endmacro (set_xcode_property) - - -# This macro lets you find executable programs on the host system -macro (find_host_package) -set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) -set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) -set (IOS FALSE) - -find_package(${ARGN}) - -set (IOS TRUE) -set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) -set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -endmacro (find_host_package) diff --git a/cmake/ios.toolchain.cmake b/cmake/ios.toolchain.cmake new file mode 100644 index 00000000..146fb661 --- /dev/null +++ b/cmake/ios.toolchain.cmake @@ -0,0 +1,674 @@ +# This file is part of the ios-cmake project. It was retrieved from +# https://github.com/cristeab/ios-cmake.git, which is a fork of +# https://code.google.com/p/ios-cmake/. Which in turn is based off of +# the Platform/Darwin.cmake and Platform/UnixPaths.cmake files which +# are included with CMake 2.8.4 +# +# The ios-cmake project is licensed under the new BSD license. +# +# Copyright (c) 2014, Bogdan Cristea and LTE Engineering Software, +# Kitware, Inc., Insight Software Consortium. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# This file is based off of the Platform/Darwin.cmake and +# Platform/UnixPaths.cmake files which are included with CMake 2.8.4 +# It has been altered for iOS development. +# +# Updated by Alex Stewart (alexs.mac@gmail.com) +# +# ***************************************************************************** +# Now maintained by Alexander Widerberg (widerbergaren [at] gmail.com) +# under the BSD-3-Clause license +# https://github.com/leetal/ios-cmake +# ***************************************************************************** +# +# INFORMATION / HELP +# +# The following arguments control the behaviour of this toolchain: +# +# PLATFORM: (default "OS") +# OS = Build for iPhoneOS. +# OS64 = Build for arm64 iphoneOS. +# OS64COMBINED = Build for arm64 x86_64 iphoneOS. Combined into FAT STATIC lib (supported on 3.14+ of CMakewith "-G Xcode" argument ONLY) +# SIMULATOR = Build for x86 i386 iphoneOS Simulator. +# SIMULATOR64 = Build for x86_64 iphoneOS Simulator. +# TVOS = Build for arm64 tvOS. +# TVOSCOMBINED = Build for arm64 x86_64 tvOS. Combined into FAT STATIC lib (supported on 3.14+ of CMake with "-G Xcode" argument ONLY) +# SIMULATOR_TVOS = Build for x86_64 tvOS Simulator. +# WATCHOS = Build for armv7k arm64_32 for watchOS. +# WATCHOSCOMBINED = Build for armv7k arm64_32 x86_64 watchOS. Combined into FAT STATIC lib (supported on 3.14+ of CMake with "-G Xcode" argument ONLY) +# SIMULATOR_WATCHOS = Build for x86_64 for watchOS Simulator. +# +# CMAKE_OSX_SYSROOT: Path to the SDK to use. By default this is +# automatically determined from PLATFORM and xcodebuild, but +# can also be manually specified (although this should not be required). +# +# CMAKE_DEVELOPER_ROOT: Path to the Developer directory for the platform +# being compiled for. By default this is automatically determined from +# CMAKE_OSX_SYSROOT, but can also be manually specified (although this should +# not be required). +# +# DEPLOYMENT_TARGET: Minimum SDK version to target. Default 2.0 on watchOS and 9.0 on tvOS+iOS +# +# ENABLE_BITCODE: (1|0) Enables or disables bitcode support. Default 1 (true) +# +# ENABLE_ARC: (1|0) Enables or disables ARC support. Default 1 (true, ARC enabled by default) +# +# ENABLE_VISIBILITY: (1|0) Enables or disables symbol visibility support. Default 0 (false, visibility hidden by default) +# +# ENABLE_STRICT_TRY_COMPILE: (1|0) Enables or disables strict try_compile() on all Check* directives (will run linker +# to actually check if linking is possible). Default 0 (false, will set CMAKE_TRY_COMPILE_TARGET_TYPE to STATIC_LIBRARY) +# +# ARCHS: (armv7 armv7s armv7k arm64 arm64_32 i386 x86_64) If specified, will override the default architectures for the given PLATFORM +# OS = armv7 armv7s arm64 (if applicable) +# OS64 = arm64 (if applicable) +# SIMULATOR = i386 +# SIMULATOR64 = x86_64 +# TVOS = arm64 +# SIMULATOR_TVOS = x86_64 (i386 has since long been deprecated) +# WATCHOS = armv7k arm64_32 (if applicable) +# SIMULATOR_WATCHOS = x86_64 (i386 has since long been deprecated) +# +# This toolchain defines the following variables for use externally: +# +# XCODE_VERSION: Version number (not including Build version) of Xcode detected. +# SDK_VERSION: Version of SDK being used. +# CMAKE_OSX_ARCHITECTURES: Architectures being compiled for (generated from PLATFORM). +# +# This toolchain defines the following macros for use externally: +# +# set_xcode_property (TARGET XCODE_PROPERTY XCODE_VALUE XCODE_VARIANT) +# A convenience macro for setting xcode specific properties on targets. +# Available variants are: All, Release, RelWithDebInfo, Debug, MinSizeRel +# example: set_xcode_property (myioslib IPHONEOS_DEPLOYMENT_TARGET "3.1" "all"). +# +# find_host_package (PROGRAM ARGS) +# A macro used to find executable programs on the host system, not within the +# environment. Thanks to the android-cmake project for providing the +# command. +# +# ******************************** DEPRECATIONS ******************************* +# +# IOS_DEPLOYMENT_TARGET: (Deprecated) Alias to DEPLOYMENT_TARGET +# CMAKE_IOS_DEVELOPER_ROOT: (Deprecated) Alias to CMAKE_DEVELOPER_ROOT +# IOS_PLATFORM: (Deprecated) Alias to PLATFORM +# IOS_ARCH: (Deprecated) Alias to ARCHS +# +# ***************************************************************************** +# + +# Fix for PThread library not in path +set(CMAKE_THREAD_LIBS_INIT "-lpthread") +set(CMAKE_HAVE_THREADS_LIBRARY 1) +set(CMAKE_USE_WIN32_THREADS_INIT 0) +set(CMAKE_USE_PTHREADS_INIT 1) + +# Cache what generator is used +set(USED_CMAKE_GENERATOR "${CMAKE_GENERATOR}" CACHE STRING "Expose CMAKE_GENERATOR" FORCE) + +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.14") + set(MODERN_CMAKE YES) + message(STATUS "Merging integrated CMake 3.14+ iOS,tvOS,watchOS,macOS toolchain(s) with this toolchain!") +endif() + +# Get the Xcode version being used. +execute_process(COMMAND xcodebuild -version + OUTPUT_VARIABLE XCODE_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +string(REGEX MATCH "Xcode [0-9\\.]+" XCODE_VERSION "${XCODE_VERSION}") +string(REGEX REPLACE "Xcode ([0-9\\.]+)" "\\1" XCODE_VERSION "${XCODE_VERSION}") +message(STATUS "Building with Xcode version: ${XCODE_VERSION}") + +######## ALIASES (DEPRECATION WARNINGS) + +if(DEFINED IOS_PLATFORM) + set(PLATFORM ${IOS_PLATFORM}) + message(DEPRECATION "IOS_PLATFORM argument is DEPRECATED. Consider using the new PLATFORM argument instead.") +endif() + +if(DEFINED IOS_DEPLOYMENT_TARGET) + set(DEPLOYMENT_TARGET ${IOS_DEPLOYMENT_TARGET}) + message(DEPRECATION "IOS_DEPLOYMENT_TARGET argument is DEPRECATED. Consider using the new DEPLOYMENT_TARGET argument instead.") +endif() + +if(DEFINED CMAKE_IOS_DEVELOPER_ROOT) + set(CMAKE_DEVELOPER_ROOT ${CMAKE_IOS_DEVELOPER_ROOT}) + message(DEPRECATION "CMAKE_IOS_DEVELOPER_ROOT argument is DEPRECATED. Consider using the new CMAKE_DEVELOPER_ROOT argument instead.") +endif() + +if(DEFINED IOS_ARCH) + set(ARCHS ${IOS_ARCH}) + message(DEPRECATION "IOS_ARCH argument is DEPRECATED. Consider using the new ARCHS argument instead.") +endif() + +######## END ALIASES + +# Unset the FORCE on cache variables if in try_compile() +set(FORCE_CACHE FORCE) +get_property(_CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE) +if(_CMAKE_IN_TRY_COMPILE) + unset(FORCE_CACHE) +endif() + +# Default to building for iPhoneOS if not specified otherwise, and we cannot +# determine the platform from the CMAKE_OSX_ARCHITECTURES variable. The use +# of CMAKE_OSX_ARCHITECTURES is such that try_compile() projects can correctly +# determine the value of PLATFORM from the root project, as +# CMAKE_OSX_ARCHITECTURES is propagated to them by CMake. +if(NOT DEFINED PLATFORM) + if (CMAKE_OSX_ARCHITECTURES) + if(CMAKE_OSX_ARCHITECTURES MATCHES ".*arm.*" AND CMAKE_OSX_SYSROOT MATCHES ".*iphoneos.*") + set(PLATFORM "OS") + elseif(CMAKE_OSX_ARCHITECTURES MATCHES "i386" AND CMAKE_OSX_SYSROOT MATCHES ".*iphonesimulator.*") + set(PLATFORM "SIMULATOR") + elseif(CMAKE_OSX_ARCHITECTURES MATCHES "x86_64" AND CMAKE_OSX_SYSROOT MATCHES ".*iphonesimulator.*") + set(PLATFORM "SIMULATOR64") + elseif(CMAKE_OSX_ARCHITECTURES MATCHES "arm64" AND CMAKE_OSX_SYSROOT MATCHES ".*appletvos.*") + set(PLATFORM "TVOS") + elseif(CMAKE_OSX_ARCHITECTURES MATCHES "x86_64" AND CMAKE_OSX_SYSROOT MATCHES ".*appletvsimulator.*") + set(PLATFORM "SIMULATOR_TVOS") + elseif(CMAKE_OSX_ARCHITECTURES MATCHES ".*armv7k.*" AND CMAKE_OSX_SYSROOT MATCHES ".*watchos.*") + set(PLATFORM "WATCHOS") + elseif(CMAKE_OSX_ARCHITECTURES MATCHES "i386" AND CMAKE_OSX_SYSROOT MATCHES ".*watchsimulator.*") + set(PLATFORM "SIMULATOR_WATCHOS") + endif() + endif() + if (NOT PLATFORM) + set(PLATFORM "OS") + endif() +endif() + +set(PLATFORM_INT "${PLATFORM}" CACHE STRING "Type of platform for which the build targets.") + +# Handle the case where we are targeting iOS and a version above 10.3.4 (32-bit support dropped officially) +if(PLATFORM_INT STREQUAL "OS" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4) + set(PLATFORM_INT "OS64") + message(STATUS "Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.") +elseif(PLATFORM_INT STREQUAL "SIMULATOR" AND DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 10.3.4) + set(PLATFORM_INT "SIMULATOR64") + message(STATUS "Targeting minimum SDK version ${DEPLOYMENT_TARGET}. Dropping 32-bit support.") +endif() + +# Determine the platform name and architectures for use in xcodebuild commands +# from the specified PLATFORM name. +if(PLATFORM_INT STREQUAL "OS") + set(SDK_NAME iphoneos) + if(NOT ARCHS) + set(ARCHS armv7 armv7s arm64) + endif() +elseif(PLATFORM_INT STREQUAL "OS64") + set(SDK_NAME iphoneos) + if(NOT ARCHS) + if (XCODE_VERSION VERSION_GREATER 10.0) + set(ARCHS arm64) # Add arm64e when Apple have fixed the integration issues with it, libarclite_iphoneos.a is currently missung bitcode markers for example + else() + set(ARCHS arm64) + endif() + endif() +elseif(PLATFORM_INT STREQUAL "OS64COMBINED") + set(SDK_NAME iphoneos) + if(MODERN_CMAKE) + if(NOT ARCHS) + if (XCODE_VERSION VERSION_GREATER 10.0) + set(ARCHS arm64 x86_64) # Add arm64e when Apple have fixed the integration issues with it, libarclite_iphoneos.a is currently missung bitcode markers for example + else() + set(ARCHS arm64 x86_64) + endif() + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the OS64COMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR") + set(SDK_NAME iphonesimulator) + if(NOT ARCHS) + set(ARCHS i386) + endif() + message(DEPRECATION "SIMULATOR IS DEPRECATED. Consider using SIMULATOR64 instead.") +elseif(PLATFORM_INT STREQUAL "SIMULATOR64") + set(SDK_NAME iphonesimulator) + if(NOT ARCHS) + set(ARCHS x86_64) + endif() +elseif(PLATFORM_INT STREQUAL "TVOS") + set(SDK_NAME appletvos) + if(NOT ARCHS) + set(ARCHS arm64) + endif() +elseif (PLATFORM_INT STREQUAL "TVOSCOMBINED") + set(SDK_NAME appletvos) + if(MODERN_CMAKE) + if(NOT ARCHS) + set(ARCHS arm64 x86_64) + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the TVOSCOMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR_TVOS") + set(SDK_NAME appletvsimulator) + if(NOT ARCHS) + set(ARCHS x86_64) + endif() +elseif(PLATFORM_INT STREQUAL "WATCHOS") + set(SDK_NAME watchos) + if(NOT ARCHS) + if (XCODE_VERSION VERSION_GREATER 10.0) + set(ARCHS armv7k arm64_32) + else() + set(ARCHS armv7k) + endif() + endif() +elseif(PLATFORM_INT STREQUAL "WATCHOSCOMBINED") + set(SDK_NAME watchos) + if(MODERN_CMAKE) + if(NOT ARCHS) + if (XCODE_VERSION VERSION_GREATER 10.0) + set(ARCHS armv7k arm64_32 i386) + else() + set(ARCHS armv7k i386) + endif() + endif() + else() + message(FATAL_ERROR "Please make sure that you are running CMake 3.14+ to make the WATCHOSCOMBINED setting work") + endif() +elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS") + set(SDK_NAME watchsimulator) + if(NOT ARCHS) + set(ARCHS i386) + endif() +else() + message(FATAL_ERROR "Invalid PLATFORM: ${PLATFORM_INT}") +endif() +message(STATUS "Configuring ${SDK_NAME} build for platform: ${PLATFORM_INT}, architecture(s): ${ARCHS}") + +if(MODERN_CMAKE AND PLATFORM_INT MATCHES ".*COMBINED" AND NOT USED_CMAKE_GENERATOR MATCHES "Xcode") + message(FATAL_ERROR "The COMBINED options only work with Xcode generator, -G Xcode") +endif() + +# If user did not specify the SDK root to use, then query xcodebuild for it. +execute_process(COMMAND xcodebuild -version -sdk ${SDK_NAME} Path + OUTPUT_VARIABLE CMAKE_OSX_SYSROOT_INT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +if (NOT DEFINED CMAKE_OSX_SYSROOT_INT AND NOT DEFINED CMAKE_OSX_SYSROOT) + message(SEND_ERROR "Please make sure that Xcode is installed and that the toolchain" + "is pointing to the correct path. Please run:" + "sudo xcode-select -s /Applications/Xcode.app/Contents/Developer" + "and see if that fixes the problem for you.") + message(FATAL_ERROR "Invalid CMAKE_OSX_SYSROOT: ${CMAKE_OSX_SYSROOT} " + "does not exist.") +elseif(DEFINED CMAKE_OSX_SYSROOT) + message(STATUS "Using SDK: ${CMAKE_OSX_SYSROOT} for platform: ${PLATFORM_INT} when checking compatibility") +elseif(DEFINED CMAKE_OSX_SYSROOT_INT) + message(STATUS "Using SDK: ${CMAKE_OSX_SYSROOT_INT} for platform: ${PLATFORM_INT}") + set(CMAKE_OSX_SYSROOT "${CMAKE_OSX_SYSROOT_INT}" CACHE INTERNAL "") +endif() + +# Set Xcode property for SDKROOT as well if Xcode generator is used +if(USED_CMAKE_GENERATOR MATCHES "Xcode") + set(CMAKE_OSX_SYSROOT "${SDK_NAME}" CACHE INTERNAL "") + if(NOT DEFINED CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM) + set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM 123456789A CACHE INTERNAL "") + endif() +endif() + +# Specify minimum version of deployment target. +if(NOT DEFINED DEPLOYMENT_TARGET) + if (PLATFORM_INT STREQUAL "WATCHOS" OR PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS") + # Unless specified, SDK version 2.0 is used by default as minimum target version (watchOS). + set(DEPLOYMENT_TARGET "2.0" + CACHE STRING "Minimum SDK version to build for." ) + else() + # Unless specified, SDK version 9.0 is used by default as minimum target version (iOS, tvOS). + set(DEPLOYMENT_TARGET "9.0" + CACHE STRING "Minimum SDK version to build for." ) + endif() + message(STATUS "Using the default min-version since DEPLOYMENT_TARGET not provided!") +endif() +# Use bitcode or not +if(NOT DEFINED ENABLE_BITCODE AND NOT ARCHS MATCHES "((^|;|, )(i386|x86_64))+") + # Unless specified, enable bitcode support by default + message(STATUS "Enabling bitcode support by default. ENABLE_BITCODE not provided!") + set(ENABLE_BITCODE TRUE) +elseif(NOT DEFINED ENABLE_BITCODE) + message(STATUS "Disabling bitcode support by default on simulators. ENABLE_BITCODE not provided for override!") + set(ENABLE_BITCODE FALSE) +endif() +set(ENABLE_BITCODE_INT ${ENABLE_BITCODE} CACHE BOOL "Whether or not to enable bitcode" ${FORCE_CACHE}) +# Use ARC or not +if(NOT DEFINED ENABLE_ARC) + # Unless specified, enable ARC support by default + set(ENABLE_ARC TRUE) + message(STATUS "Enabling ARC support by default. ENABLE_ARC not provided!") +endif() +set(ENABLE_ARC_INT ${ENABLE_ARC} CACHE BOOL "Whether or not to enable ARC" ${FORCE_CACHE}) +# Use hidden visibility or not +if(NOT DEFINED ENABLE_VISIBILITY) + # Unless specified, disable symbols visibility by default + set(ENABLE_VISIBILITY FALSE) + message(STATUS "Hiding symbols visibility by default. ENABLE_VISIBILITY not provided!") +endif() +set(ENABLE_VISIBILITY_INT ${ENABLE_VISIBILITY} CACHE BOOL "Whether or not to hide symbols (-fvisibility=hidden)" ${FORCE_CACHE}) +# Set strict compiler checks or not +if(NOT DEFINED ENABLE_STRICT_TRY_COMPILE) + # Unless specified, disable strict try_compile() + set(ENABLE_STRICT_TRY_COMPILE FALSE) + message(STATUS "Using NON-strict compiler checks by default. ENABLE_STRICT_TRY_COMPILE not provided!") +endif() +set(ENABLE_STRICT_TRY_COMPILE_INT ${ENABLE_STRICT_TRY_COMPILE} CACHE BOOL "Whether or not to use strict compiler checks" ${FORCE_CACHE}) +# Get the SDK version information. +execute_process(COMMAND xcodebuild -sdk ${CMAKE_OSX_SYSROOT} -version SDKVersion + OUTPUT_VARIABLE SDK_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + +# Find the Developer root for the specific iOS platform being compiled for +# from CMAKE_OSX_SYSROOT. Should be ../../ from SDK specified in +# CMAKE_OSX_SYSROOT. There does not appear to be a direct way to obtain +# this information from xcrun or xcodebuild. +if (NOT DEFINED CMAKE_DEVELOPER_ROOT AND NOT USED_CMAKE_GENERATOR MATCHES "Xcode") + get_filename_component(PLATFORM_SDK_DIR ${CMAKE_OSX_SYSROOT} PATH) + get_filename_component(CMAKE_DEVELOPER_ROOT ${PLATFORM_SDK_DIR} PATH) + + if (NOT DEFINED CMAKE_DEVELOPER_ROOT) + message(FATAL_ERROR "Invalid CMAKE_DEVELOPER_ROOT: " + "${CMAKE_DEVELOPER_ROOT} does not exist.") + endif() +endif() +# Find the C & C++ compilers for the specified SDK. +if(NOT CMAKE_C_COMPILER) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find clang + OUTPUT_VARIABLE CMAKE_C_COMPILER + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + message(STATUS "Using C compiler: ${CMAKE_C_COMPILER}") +endif() +if(NOT CMAKE_CXX_COMPILER) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find clang++ + OUTPUT_VARIABLE CMAKE_CXX_COMPILER + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + message(STATUS "Using CXX compiler: ${CMAKE_CXX_COMPILER}") +endif() +# Find (Apple's) libtool. +execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find libtool + OUTPUT_VARIABLE BUILD_LIBTOOL + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +message(STATUS "Using libtool: ${BUILD_LIBTOOL}") +# Configure libtool to be used instead of ar + ranlib to build static libraries. +# This is required on Xcode 7+, but should also work on previous versions of +# Xcode. +set(CMAKE_C_CREATE_STATIC_LIBRARY + "${BUILD_LIBTOOL} -static -o ") +set(CMAKE_CXX_CREATE_STATIC_LIBRARY + "${BUILD_LIBTOOL} -static -o ") +# Find the toolchain's provided install_name_tool if none is found on the host +if(NOT CMAKE_INSTALL_NAME_TOOL) + execute_process(COMMAND xcrun -sdk ${CMAKE_OSX_SYSROOT} -find install_name_tool + OUTPUT_VARIABLE CMAKE_INSTALL_NAME_TOOL_INT + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + set(CMAKE_INSTALL_NAME_TOOL ${CMAKE_INSTALL_NAME_TOOL_INT} CACHE STRING "" ${FORCE_CACHE}) + message(STATUS "Using install_name_tool: ${CMAKE_INSTALL_NAME_TOOL}") +endif() +# Get the version of Darwin (OS X) of the host. +execute_process(COMMAND uname -r + OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) +# CMake 3.14+ support building for iOS, watchOS and tvOS out of the box. +if(MODERN_CMAKE) + if(SDK_NAME MATCHES "iphone") + set(CMAKE_SYSTEM_NAME iOS CACHE INTERNAL "" ${FORCE_CACHE}) + elseif(SDK_NAME MATCHES "appletv") + set(CMAKE_SYSTEM_NAME tvOS CACHE INTERNAL "" ${FORCE_CACHE}) + elseif(SDK_NAME MATCHES "watch") + set(CMAKE_SYSTEM_NAME watchOS CACHE INTERNAL "" ${FORCE_CACHE}) + endif() + + # Provide flags for a combined FAT library build on newer CMake versions + if(PLATFORM_INT MATCHES ".*COMBINED") + set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO CACHE INTERNAL "" ${FORCE_CACHE}) + set(CMAKE_IOS_INSTALL_COMBINED YES CACHE INTERNAL "" ${FORCE_CACHE}) + message(STATUS "Will combine built (static) artifacts into FAT lib...") + endif() +else() + # Legacy code path prior to CMake 3.14 + set(CMAKE_SYSTEM_NAME Darwin CACHE INTERNAL "" ${FORCE_CACHE}) +endif() +# Standard settings. +set(CMAKE_SYSTEM_VERSION ${SDK_VERSION} CACHE INTERNAL "") +set(UNIX TRUE CACHE BOOL "") +set(APPLE TRUE CACHE BOOL "") +set(IOS TRUE CACHE BOOL "") +set(CMAKE_AR ar CACHE FILEPATH "" FORCE) +set(CMAKE_RANLIB ranlib CACHE FILEPATH "" FORCE) +set(CMAKE_STRIP strip CACHE FILEPATH "" FORCE) +# Set the architectures for which to build. +set(CMAKE_OSX_ARCHITECTURES ${ARCHS} CACHE STRING "Build architecture for iOS") +# Change the type of target generated for try_compile() so it'll work when cross-compiling, weak compiler checks +if(ENABLE_STRICT_TRY_COMPILE_INT) + message(STATUS "Using strict compiler checks (default in CMake).") +else() + set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +endif() +# All iOS/Darwin specific settings - some may be redundant. +set(CMAKE_MACOSX_BUNDLE YES) +set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO") +set(CMAKE_SHARED_LIBRARY_PREFIX "lib") +set(CMAKE_SHARED_LIBRARY_SUFFIX ".dylib") +set(CMAKE_SHARED_MODULE_PREFIX "lib") +set(CMAKE_SHARED_MODULE_SUFFIX ".so") +set(CMAKE_C_COMPILER_ABI ELF) +set(CMAKE_CXX_COMPILER_ABI ELF) +set(CMAKE_C_HAS_ISYSROOT 1) +set(CMAKE_CXX_HAS_ISYSROOT 1) +set(CMAKE_MODULE_EXISTS 1) +set(CMAKE_DL_LIBS "") +set(CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ") +set(CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ") +set(CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}") +set(CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}") + +if(ARCHS MATCHES "((^|;|, )(arm64|arm64e|x86_64))+") + set(CMAKE_C_SIZEOF_DATA_PTR 8) + set(CMAKE_CXX_SIZEOF_DATA_PTR 8) + if(ARCHS MATCHES "((^|;|, )(arm64|arm64e))+") + set(CMAKE_SYSTEM_PROCESSOR "aarch64") + else() + set(CMAKE_SYSTEM_PROCESSOR "x86_64") + endif() + message(STATUS "Using a data_ptr size of 8") +else() + set(CMAKE_C_SIZEOF_DATA_PTR 4) + set(CMAKE_CXX_SIZEOF_DATA_PTR 4) + set(CMAKE_SYSTEM_PROCESSOR "arm") + message(STATUS "Using a data_ptr size of 4") +endif() + +message(STATUS "Building for minimum ${SDK_NAME} version: ${DEPLOYMENT_TARGET}" + " (SDK version: ${SDK_VERSION})") +# Note that only Xcode 7+ supports the newer more specific: +# -m${SDK_NAME}-version-min flags, older versions of Xcode use: +# -m(ios/ios-simulator)-version-min instead. +if(PLATFORM_INT STREQUAL "OS" OR PLATFORM_INT STREQUAL "OS64") + if(XCODE_VERSION VERSION_LESS 7.0) + set(SDK_NAME_VERSION_FLAGS + "-mios-version-min=${DEPLOYMENT_TARGET}") + else() + # Xcode 7.0+ uses flags we can build directly from SDK_NAME. + set(SDK_NAME_VERSION_FLAGS + "-m${SDK_NAME}-version-min=${DEPLOYMENT_TARGET}") + endif() +elseif(PLATFORM_INT STREQUAL "TVOS") + set(SDK_NAME_VERSION_FLAGS + "-mtvos-version-min=${DEPLOYMENT_TARGET}") +elseif(PLATFORM_INT STREQUAL "SIMULATOR_TVOS") + set(SDK_NAME_VERSION_FLAGS + "-mtvos-simulator-version-min=${DEPLOYMENT_TARGET}") +elseif(PLATFORM_INT STREQUAL "WATCHOS") + set(SDK_NAME_VERSION_FLAGS + "-mwatchos-version-min=${DEPLOYMENT_TARGET}") +elseif(PLATFORM_INT STREQUAL "SIMULATOR_WATCHOS") + set(SDK_NAME_VERSION_FLAGS + "-mwatchos-simulator-version-min=${DEPLOYMENT_TARGET}") +else() + # SIMULATOR or SIMULATOR64 both use -mios-simulator-version-min. + set(SDK_NAME_VERSION_FLAGS + "-mios-simulator-version-min=${DEPLOYMENT_TARGET}") +endif() +message(STATUS "Version flags set to: ${SDK_NAME_VERSION_FLAGS}") +set(CMAKE_OSX_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET} CACHE STRING + "Set CMake deployment target" ${FORCE_CACHE}) + +if(ENABLE_BITCODE_INT) + set(BITCODE "-fembed-bitcode") + set(CMAKE_XCODE_ATTRIBUTE_BITCODE_GENERATION_MODE bitcode CACHE INTERNAL "") + message(STATUS "Enabling bitcode support.") +else() + set(BITCODE "") + set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE NO CACHE INTERNAL "") + message(STATUS "Disabling bitcode support.") +endif() + +if(ENABLE_ARC_INT) + set(FOBJC_ARC "-fobjc-arc") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES CACHE INTERNAL "") + message(STATUS "Enabling ARC support.") +else() + set(FOBJC_ARC "-fno-objc-arc") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC NO CACHE INTERNAL "") + message(STATUS "Disabling ARC support.") +endif() + +if(NOT ENABLE_VISIBILITY_INT) + set(VISIBILITY "-fvisibility=hidden") + set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN YES CACHE INTERNAL "") + message(STATUS "Hiding symbols (-fvisibility=hidden).") +else() + set(VISIBILITY "") + set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN NO CACHE INTERNAL "") +endif() + +#Check if Xcode generator is used, since that will handle these flags automagically +if(USED_CMAKE_GENERATOR MATCHES "Xcode") + message(STATUS "Not setting any manual command-line buildflags, since Xcode is selected as generator.") +else() + set(CMAKE_C_FLAGS + "${SDK_NAME_VERSION_FLAGS} ${BITCODE} -fobjc-abi-version=2 ${FOBJC_ARC} ${CMAKE_C_FLAGS}") + # Hidden visibilty is required for C++ on iOS. + set(CMAKE_CXX_FLAGS + "${SDK_NAME_VERSION_FLAGS} ${BITCODE} ${VISIBILITY} -fvisibility-inlines-hidden -fobjc-abi-version=2 ${FOBJC_ARC} ${CMAKE_CXX_FLAGS}") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -O0 -g ${CMAKE_CXX_FLAGS_DEBUG}") + set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS} -DNDEBUG -Os -ffast-math ${CMAKE_CXX_FLAGS_MINSIZEREL}") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS} -DNDEBUG -O2 -g -ffast-math ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -DNDEBUG -O3 -ffast-math ${CMAKE_CXX_FLAGS_RELEASE}") + set(CMAKE_C_LINK_FLAGS "${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}") + set(CMAKE_CXX_LINK_FLAGS "${SDK_NAME_VERSION_FLAGS} -Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}") + + # In order to ensure that the updated compiler flags are used in try_compile() + # tests, we have to forcibly set them in the CMake cache, not merely set them + # in the local scope. + list(APPEND VARS_TO_FORCE_IN_CACHE + CMAKE_C_FLAGS + CMAKE_CXX_FLAGS + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELWITHDEBINFO + CMAKE_CXX_FLAGS_MINSIZEREL + CMAKE_CXX_FLAGS_RELEASE + CMAKE_C_LINK_FLAGS + CMAKE_CXX_LINK_FLAGS) + foreach(VAR_TO_FORCE ${VARS_TO_FORCE_IN_CACHE}) + set(${VAR_TO_FORCE} "${${VAR_TO_FORCE}}" CACHE STRING "") + endforeach() +endif() + +set(CMAKE_PLATFORM_HAS_INSTALLNAME 1) +set(CMAKE_SHARED_LINKER_FLAGS "-rpath @executable_path/Frameworks -rpath @loader_path/Frameworks") +set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib -Wl,-headerpad_max_install_names") +set(CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle -Wl,-headerpad_max_install_names") +set(CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,") +set(CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,") +set(CMAKE_FIND_LIBRARY_SUFFIXES ".tbd" ".dylib" ".so" ".a") +set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG "-install_name") + +# Set the find root to the iOS developer roots and to user defined paths. +set(CMAKE_FIND_ROOT_PATH ${CMAKE_OSX_SYSROOT_INT} ${CMAKE_PREFIX_PATH} CACHE STRING "Root path that will be prepended + to all search paths") +# Default to searching for frameworks first. +set(CMAKE_FIND_FRAMEWORK FIRST) +# Set up the default search directories for frameworks. +set(CMAKE_FRAMEWORK_PATH + ${CMAKE_DEVELOPER_ROOT}/Library/PrivateFrameworks + ${CMAKE_OSX_SYSROOT_INT}/System/Library/Frameworks + ${CMAKE_FRAMEWORK_PATH} CACHE STRING "Frameworks search paths" ${FORCE_CACHE}) + +# By default, search both the specified iOS SDK and the remainder of the host filesystem. +if(NOT CMAKE_FIND_ROOT_PATH_MODE_PROGRAM) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH CACHE STRING "" ${FORCE_CACHE}) +endif() +if(NOT CMAKE_FIND_ROOT_PATH_MODE_LIBRARY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY CACHE STRING "" ${FORCE_CACHE}) +endif() +if(NOT CMAKE_FIND_ROOT_PATH_MODE_INCLUDE) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY CACHE STRING "" ${FORCE_CACHE}) +endif() +if(NOT CMAKE_FIND_ROOT_PATH_MODE_PACKAGE) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY CACHE STRING "" ${FORCE_CACHE}) +endif() + +# +# Some helper-macros below to simplify and beautify the CMakeFile +# + +# This little macro lets you set any Xcode specific property. +macro(set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE XCODE_RELVERSION) + set(XCODE_RELVERSION_I "${XCODE_RELVERSION}") + if(XCODE_RELVERSION_I STREQUAL "All") + set_property(TARGET ${TARGET} PROPERTY + XCODE_ATTRIBUTE_${XCODE_PROPERTY} "${XCODE_VALUE}") + else() + set_property(TARGET ${TARGET} PROPERTY + XCODE_ATTRIBUTE_${XCODE_PROPERTY}[variant=${XCODE_RELVERSION_I}] "${XCODE_VALUE}") + endif() +endmacro(set_xcode_property) +# This macro lets you find executable programs on the host system. +macro(find_host_package) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER) + set(IOS FALSE) + find_package(${ARGN}) + set(IOS TRUE) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) +endmacro(find_host_package) diff --git a/conf/config.ini b/conf/config.ini index cbbcb96d..ed4727af 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -9,14 +9,14 @@ secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc #FFmpeg可执行程序绝对路径 bin=/usr/local/bin/ffmpeg #FFmpeg拉流再推流的命令模板,通过该模板可以设置再编码的一些参数 -cmd=%s -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s +cmd=%s -re -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s #FFmpeg日志的路径,如果置空则不生成FFmpeg日志 #可以为相对(相对于本可执行程序目录)或绝对路径 log=./ffmpeg/ffmpeg.log [general] #是否启用虚拟主机 -enableVhost=1 +enableVhost=0 #播放器或推流器在断开后会触发hook.on_flow_report事件(使用多少流量事件), #flowThreshold参数控制触发hook.on_flow_report事件阈值,使用流量超过该阈值后才触发,单位KB flowThreshold=1024 @@ -25,10 +25,10 @@ flowThreshold=1024 #ZLMediaKit会最多让播放器等待maxStreamWaitMS毫秒 #如果在这个时间内,该流注册成功,那么会立即返回播放器播放成功 #否则返回播放器未找到该流,该机制的目的是可以先播放再推流 -maxStreamWaitMS=5000 +maxStreamWaitMS=15000 #某个流无人观看时,触发hook.on_stream_none_reader事件的最大等待时间,单位毫秒 #在配合hook.on_stream_none_reader事件时,可以做到无人观看自动停止拉流或停止接收推流 -streamNoneReaderDelayMS=5000 +streamNoneReaderDelayMS=20000 #是否开启低延时模式,该模式下禁用MSG_MORE,启用TCP_NODEALY,延时将降低,但数据发送性能将降低 ultraLowDelay=1 #拉流代理是否添加静音音频(直接拉流模式本协议无效) @@ -36,18 +36,26 @@ addMuteAudio=1 #拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始, #如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写) resetWhenRePlay=1 +#是否默认推流时转换成rtsp或rtmp,hook接口(on_publish)中可以覆盖该设置 +publishToRtxp=1 +#是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置 +publishToHls=1 +#是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置 +publishToMP4=0 [hls] #hls写文件的buf大小,调整参数可以提高文件io性能 fileBufSize=65536 #hls保存文件路径 #可以为相对(相对于本可执行程序目录)或绝对路径 -filePath=./httpRoot +filePath=./www #hls最大切片时间 -segDur=3 +segDur=2 #m3u8索引中,hls保留切片个数(实际保留切片个数大2~3个) #如果设置为0,则不删除切片,而是保存为点播 segNum=3 +#HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数 +segRetain=5 [hook] #在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然 @@ -78,6 +86,8 @@ on_stream_changed=https://127.0.0.1/index/hook/on_stream_changed 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 #hook api最大等待回复时间,单位秒 timeoutSec=10 @@ -85,9 +95,7 @@ timeoutSec=10 #http服务器字符编码,windows上默认gb2312 charSet=utf-8 #http链接超时时间 -keepAliveSecond=10 -#keep-alive类型的链接最多复用次数 -maxReqCount=100 +keepAliveSecond=30 #http请求体最大字节数,如果post的body太大,则不适合缓存body在内存 maxReqSize=4096 #404网页内容,用户可以自定义404网页 @@ -96,7 +104,7 @@ notFound=404 Not Found" + +RUN apt-get update && \ + DEBIAN_FRONTEND="noninteractive" \ + apt-get install -y --no-install-recommends \ + vim \ + ca-certificates \ + tzdata \ + libssl-dev \ + libx264-dev \ + libfaac-dev \ + libmp4v2-dev && \ + apt autoremove -y && \ + apt clean -y && \ + rm -rf /var/lib/apt/lists/* + +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 \ No newline at end of file diff --git a/docker/ubuntu18.04/Dockerfile.devel b/docker/ubuntu18.04/Dockerfile.devel new file mode 100644 index 00000000..335c9a3f --- /dev/null +++ b/docker/ubuntu18.04/Dockerfile.devel @@ -0,0 +1,44 @@ +FROM ubuntu:18.04 +LABEL maintainer "Gemfield " +#shell,rtmp,rtsp,rtsps,http,https,rtp +EXPOSE 9000/tcp +EXPOSE 1935/tcp +EXPOSE 554/tcp +EXPOSE 322/tcp +EXPOSE 80/tcp +EXPOSE 443/tcp +EXPOSE 10000/udp +EXPOSE 10000/tcp + +RUN apt-get update && \ + DEBIAN_FRONTEND="noninteractive" \ + apt-get install -y --no-install-recommends \ + build-essential \ + cmake \ + git \ + curl \ + vim \ + ca-certificates \ + tzdata \ + libssl-dev \ + libmysqlclient-dev \ + libx264-dev \ + libfaac-dev \ + libmp4v2-dev && \ + apt autoremove -y && \ + apt clean -y && \ + rm -rf /var/lib/apt/lists/* + +RUN mkdir -p /opt/media + +WORKDIR /opt/media +RUN git clone --depth=1 https://github.com/xiongziliang/ZLMediaKit && \ + cd ZLMediaKit && git submodule update --init --recursive && \ + mkdir -p build release/linux/Release/ + +WORKDIR /opt/media/ZLMediaKit/build +RUN cmake -DCMAKE_BUILD_TYPE=Release .. && \ + make -j4 + +ENV PATH /opt/media/ZLMediaKit/release/linux/Release:$PATH +CMD MediaServer \ No newline at end of file diff --git a/docker/ubuntu18.04/Dockerfile.runtime b/docker/ubuntu18.04/Dockerfile.runtime new file mode 100644 index 00000000..f10c2658 --- /dev/null +++ b/docker/ubuntu18.04/Dockerfile.runtime @@ -0,0 +1,62 @@ +FROM ubuntu:18.04 AS build +#shell,rtmp,rtsp,rtsps,http,https,rtp +EXPOSE 9000/tcp +EXPOSE 1935/tcp +EXPOSE 554/tcp +EXPOSE 322/tcp +EXPOSE 80/tcp +EXPOSE 443/tcp +EXPOSE 10000/udp +EXPOSE 10000/tcp + +RUN apt-get update && \ + DEBIAN_FRONTEND="noninteractive" \ + apt-get install -y --no-install-recommends \ + build-essential \ + cmake \ + git \ + curl \ + vim \ + ca-certificates \ + tzdata \ + libssl-dev \ + libmysqlclient-dev \ + libx264-dev \ + libfaac-dev \ + libmp4v2-dev && \ + apt autoremove -y && \ + apt clean -y && \ + rm -rf /var/lib/apt/lists/* + +RUN mkdir -p /opt/media + +WORKDIR /opt/media +RUN git clone --depth=1 https://github.com/xiongziliang/ZLMediaKit && \ + cd ZLMediaKit && git submodule update --init --recursive && \ + mkdir -p build release/linux/Release/ + +WORKDIR /opt/media/ZLMediaKit/build +RUN cmake -DCMAKE_BUILD_TYPE=Release .. && \ + make -j4 + +FROM ubuntu:18.04 +LABEL maintainer "Gemfield " + +RUN apt-get update && \ + DEBIAN_FRONTEND="noninteractive" \ + apt-get install -y --no-install-recommends \ + vim \ + ca-certificates \ + tzdata \ + libssl-dev \ + libx264-dev \ + libfaac-dev \ + libmp4v2-dev && \ + apt autoremove -y && \ + apt clean -y && \ + rm -rf /var/lib/apt/lists/* + +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 \ No newline at end of file diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 5bc42a8d..092a4221 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -3,12 +3,7 @@ file(GLOB jsoncpp_src_list ../3rdpart/jsoncpp/*.cpp ../3rdpart/jsoncpp/*.h ) add_library(jsoncpp STATIC ${jsoncpp_src_list}) -if (CMAKE_SYSTEM_NAME MATCHES "Windows") - set(MediaServer_src_list ./WebApi.cpp ./WebHook.cpp main.cpp) -else() - file(GLOB MediaServer_src_list ./*.cpp ./*.h) -endif() - +file(GLOB MediaServer_src_list ./*.cpp ./*.h) #message(STATUS ${MediaServer_src_list}) add_executable(MediaServer ${MediaServer_src_list}) diff --git a/server/FFmpegSource.cpp b/server/FFmpegSource.cpp index e55e6dbb..d8a821c6 100644 --- a/server/FFmpegSource.cpp +++ b/server/FFmpegSource.cpp @@ -32,13 +32,22 @@ namespace FFmpeg { #define FFmpeg_FIELD "ffmpeg." -const char kBin[] = FFmpeg_FIELD"bin"; -const char kCmd[] = FFmpeg_FIELD"cmd"; -const char kLog[] = FFmpeg_FIELD"log"; +const string kBin = FFmpeg_FIELD"bin"; +const string kCmd = FFmpeg_FIELD"cmd"; +const string kLog = FFmpeg_FIELD"log"; onceToken token([]() { - mINI::Instance()[kBin] = trim(System::execute("which ffmpeg")); - mINI::Instance()[kCmd] = "%s -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s"; +#ifdef _WIN32 + string ffmpeg_bin = System::execute("where ffmpeg"); + //windows下先关闭FFmpeg日志(目前不支持日志重定向) + mINI::Instance()[kCmd] = "%s -re -i \"%s\" -loglevel quiet -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s "; +#else + string ffmpeg_bin = System::execute("which ffmpeg"); + mINI::Instance()[kCmd] = "%s -re -i \"%s\" -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s "; +#endif + //默认ffmpeg命令路径为环境变量中路径 + mINI::Instance()[kBin] = ffmpeg_bin.empty() ? "ffmpeg" : ffmpeg_bin; + //ffmpeg日志保存路径 mINI::Instance()[kLog] = "./ffmpeg/ffmpeg.log"; }); } @@ -48,7 +57,6 @@ FFmpegSource::FFmpegSource() { } FFmpegSource::~FFmpegSource() { - NoticeCenter::Instance().delListener(this, Broadcast::kBroadcastStreamNoneReader); DebugL; } @@ -64,7 +72,7 @@ void FFmpegSource::play(const string &src_url,const string &dst_url,int timeout_ char cmd[1024] = {0}; snprintf(cmd, sizeof(cmd),ffmpeg_cmd.data(),ffmpeg_bin.data(),src_url.data(),dst_url.data()); - _process.run(cmd,File::absolutePath("",ffmpeg_log)); + _process.run(cmd,ffmpeg_log.empty() ? "" : File::absolutePath("",ffmpeg_log)); InfoL << cmd; if(_media_info._host == "127.0.0.1"){ @@ -83,6 +91,7 @@ void FFmpegSource::play(const string &src_url,const string &dst_url,int timeout_ if(src){ //推流给自己成功 cb(SockException()); + strongSelf->onGetMediaSource(src); strongSelf->startTimer(timeout_ms); return; } @@ -147,11 +156,11 @@ void FFmpegSource::findAsync(int maxWaitMS, const function_media_info._schema || - vhost != strongSelf->_media_info._vhost || - app != strongSelf->_media_info._app || - stream != strongSelf->_media_info._streamid){ + 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) { //不是自己感兴趣的事件,忽略之 return; } @@ -192,8 +201,11 @@ void FFmpegSource::startTimer(int timeout_ms) { //同步查找流 if (!src) { //流不在线,重新拉流 - strongSelf->play(strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, - [](const SockException &) {}); + if(strongSelf->_replay_ticker.elapsedTime() > 10 * 1000){ + //上次重试时间超过10秒,那么再重试FFmpeg拉流 + strongSelf->_replay_ticker.resetTime(); + strongSelf->play(strongSelf->_src_url, strongSelf->_dst_url, timeout_ms, [](const SockException &) {}); + } } }); } else { @@ -205,29 +217,43 @@ void FFmpegSource::startTimer(int timeout_ms) { } return true; }, _poller); - - NoticeCenter::Instance().delListener(this, Broadcast::kBroadcastStreamNoneReader); - NoticeCenter::Instance().addListener(this, Broadcast::kBroadcastStreamNoneReader,[weakSelf](BroadcastStreamNoneReaderArgs) { - auto strongSelf = weakSelf.lock(); - if (!strongSelf) { - //自身已经销毁 - return; - } - - if(sender.getVhost() != strongSelf->_media_info._vhost || - sender.getApp() != strongSelf->_media_info._app || - sender.getId() != strongSelf->_media_info._streamid){ - //不是自己感兴趣的事件,忽略之 - return; - } - - //该流无人观看,我们停止吧 - if(strongSelf->_onClose){ - strongSelf->_onClose(); - } - }); } void FFmpegSource::setOnClose(const function &cb){ _onClose = cb; -} \ No newline at end of file +} + +bool FFmpegSource::close(MediaSource &sender, bool force) { + auto listener = _listener.lock(); + if(listener && !listener->close(sender,force)){ + //关闭失败 + return false; + } + //该流无人观看,我们停止吧 + if(_onClose){ + _onClose(); + } + return true; +} + +void FFmpegSource::onNoneReader(MediaSource &sender) { + auto listener = _listener.lock(); + if(listener){ + listener->onNoneReader(sender); + }else{ + MediaSourceEvent::onNoneReader(sender); + } +} + +int FFmpegSource::totalReaderCount(MediaSource &sender) { + auto listener = _listener.lock(); + if(listener){ + return listener->totalReaderCount(sender); + } + return 0; +} + +void FFmpegSource::onGetMediaSource(const MediaSource::Ptr &src) { + _listener = src->getListener(); + src->setListener(shared_from_this()); +} diff --git a/server/FFmpegSource.h b/server/FFmpegSource.h index 6be92fc3..a11144d7 100644 --- a/server/FFmpegSource.h +++ b/server/FFmpegSource.h @@ -39,7 +39,7 @@ using namespace std; using namespace toolkit; using namespace mediakit; -class FFmpegSource : public std::enable_shared_from_this{ +class FFmpegSource : public std::enable_shared_from_this , public MediaSourceEvent{ public: typedef shared_ptr Ptr; typedef function onPlay; @@ -55,6 +55,12 @@ public: private: void findAsync(int maxWaitMS ,const function &cb); void startTimer(int timeout_ms); + void onGetMediaSource(const MediaSource::Ptr &src); + + //MediaSourceEvent override + bool close(MediaSource &sender,bool force) override; + void onNoneReader(MediaSource &sender) override ; + int totalReaderCount(MediaSource &sender) override; private: Process _process; Timer::Ptr _timer; @@ -63,6 +69,8 @@ private: string _src_url; string _dst_url; function _onClose; + std::weak_ptr _listener; + Ticker _replay_ticker; }; diff --git a/server/Process.cpp b/server/Process.cpp index e215dc81..eb8eac4d 100644 --- a/server/Process.cpp +++ b/server/Process.cpp @@ -26,8 +26,15 @@ #include #include + +#ifndef _WIN32 #include #include +#else +//#include +#include +#endif + #include #include #include "Util/util.h" @@ -39,75 +46,95 @@ using namespace toolkit; void Process::run(const string &cmd, const string &log_file_tmp) { - kill(2000); - _pid = fork(); - if (_pid < 0) { - throw std::runtime_error(StrPrinter << "fork child process falied,err:" << get_uv_errmsg()); - } - if (_pid == 0) { - //子进程关闭core文件生成 - struct rlimit rlim = {0,0}; - setrlimit(RLIMIT_CORE, &rlim); + kill(2000); +#ifdef _WIN32 + STARTUPINFO si; + PROCESS_INFORMATION pi; + ZeroMemory(&si, sizeof(si)); //结构体初始化; + ZeroMemory(&pi, sizeof(pi)); - //在启动子进程时,暂时禁用SIGINT、SIGTERM信号 - // ignore the SIGINT and SIGTERM - signal(SIGINT, SIG_IGN); - signal(SIGTERM, SIG_IGN); + LPTSTR lpDir = const_cast(cmd.data()); - string log_file ; - if(log_file_tmp.empty()){ - log_file = "/dev/null"; - }else{ - log_file = StrPrinter << log_file_tmp << "." << getpid(); - } + if (CreateProcess(NULL, lpDir, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)){ + //下面两行关闭句柄,解除本进程和新进程的关系,不然有可能 不小心调用TerminateProcess函数关掉子进程 + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); - int log_fd = -1; - int flags = O_CREAT | O_WRONLY | O_APPEND; - mode_t mode = S_IRWXO | S_IRWXG | S_IRWXU;// S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH; - File::createfile_path(log_file.data(), mode); - if ((log_fd = ::open(log_file.c_str(), flags, mode)) < 0) { - fprintf(stderr, "open log file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno)); - } else { - // dup to stdout and stderr. - if (dup2(log_fd, STDOUT_FILENO) < 0) { - fprintf(stderr, "dup2 stdout file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno)); - } - if (dup2(log_fd, STDERR_FILENO) < 0) { - fprintf(stderr, "dup2 stderr file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno)); - } - // close log fd - ::close(log_fd); - } - fprintf(stderr, "\r\n\r\n#### pid=%d,cmd=%s #####\r\n\r\n", getpid(), cmd.data()); + _pid = pi.dwProcessId; + InfoL << "start child proces " << _pid; + } else { + WarnL << "start child proces fail: " << GetLastError(); + } +#else + _pid = fork(); + if (_pid < 0) { + throw std::runtime_error(StrPrinter << "fork child process falied,err:" << get_uv_errmsg()); + } + if (_pid == 0) { + //子进程关闭core文件生成 + struct rlimit rlim = { 0,0 }; + setrlimit(RLIMIT_CORE, &rlim); - // close other fds - // TODO: do in right way. - for (int i = 3; i < 1024; i++) { - ::close(i); - } + //在启动子进程时,暂时禁用SIGINT、SIGTERM信号 + // ignore the SIGINT and SIGTERM + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); - auto params = split(cmd, " "); - // memory leak in child process, it's ok. - char **charpv_params = new char *[params.size() + 1]; - for (int i = 0; i < (int) params.size(); i++) { - std::string &p = params[i]; - charpv_params[i] = (char *) p.data(); - } - // EOF: NULL - charpv_params[params.size()] = NULL; + string log_file; + if (log_file_tmp.empty()) { + log_file = "/dev/null"; + } + else { + log_file = StrPrinter << log_file_tmp << "." << getpid(); + } - // TODO: execv or execvp - auto ret = execv(params[0].c_str(), charpv_params); - if (ret < 0) { - fprintf(stderr, "fork process failed, errno=%d(%s)\r\n", errno, strerror(errno)); - } - exit(ret); - } + int log_fd = -1; + int flags = O_CREAT | O_WRONLY | O_APPEND; + mode_t mode = S_IRWXO | S_IRWXG | S_IRWXU;// S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH; + File::createfile_path(log_file.data(), mode); + if ((log_fd = ::open(log_file.c_str(), flags, mode)) < 0) { + fprintf(stderr, "open log file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno)); + } + else { + // dup to stdout and stderr. + if (dup2(log_fd, STDOUT_FILENO) < 0) { + fprintf(stderr, "dup2 stdout file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno)); + } + if (dup2(log_fd, STDERR_FILENO) < 0) { + fprintf(stderr, "dup2 stderr file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno)); + } + // close log fd + ::close(log_fd); + } + fprintf(stderr, "\r\n\r\n#### pid=%d,cmd=%s #####\r\n\r\n", getpid(), cmd.data()); - InfoL << "start child proces " << _pid; + // close other fds + // TODO: do in right way. + for (int i = 3; i < 1024; i++) { + ::close(i); + } + + auto params = split(cmd, " "); + // memory leak in child process, it's ok. + char **charpv_params = new char *[params.size() + 1]; + for (int i = 0; i < (int)params.size(); i++) { + std::string &p = params[i]; + charpv_params[i] = (char *)p.data(); + } + // EOF: NULL + charpv_params[params.size()] = NULL; + + // TODO: execv or execvp + auto ret = execv(params[0].c_str(), charpv_params); + if (ret < 0) { + fprintf(stderr, "fork process failed, errno=%d(%s)\r\n", errno, strerror(errno)); + } + exit(ret); + } + InfoL << "start child proces " << _pid; +#endif // _WIN32 } - /** * 获取进程是否存活状态 * @param pid 进程号 @@ -120,20 +147,30 @@ static bool s_wait(pid_t pid,int *exit_code_ptr,bool block) { return false; } int status = 0; - pid_t p = waitpid(pid, &status, block ? 0 : WNOHANG); - int exit_code = (status & 0xFF00) >> 8; - if(exit_code_ptr){ - *exit_code_ptr = (status & 0xFF00) >> 8; - } - if (p < 0) { - WarnL << "waitpid failed, pid=" << pid << ", err=" << get_uv_errmsg(); - return false; - } - if (p > 0) { - InfoL << "process terminated, pid=" << pid << ", exit code=" << exit_code; - return false; - } - //WarnL << "process is running, pid=" << _pid; +#ifdef _WIN32 + HANDLE hProcess = NULL; + hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); //打开目标进程 + if (hProcess == NULL) { + return false; + } + + CloseHandle(hProcess); +#else + pid_t p = waitpid(pid, &status, block ? 0 : WNOHANG); + int exit_code = (status & 0xFF00) >> 8; + if (exit_code_ptr) { + *exit_code_ptr = (status & 0xFF00) >> 8; + } + if (p < 0) { + WarnL << "waitpid failed, pid=" << pid << ", err=" << get_uv_errmsg(); + return false; + } + if (p > 0) { + InfoL << "process terminated, pid=" << pid << ", exit code=" << exit_code; + return false; + } +#endif // _WIN32 + return true; } @@ -142,12 +179,25 @@ static void s_kill(pid_t pid,int max_delay,bool force){ //pid无效 return; } +#ifdef _WIN32 + HANDLE hProcess = NULL; + hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); //打开目标进程 + if (hProcess == NULL) { + WarnL << "\nOpen Process fAiled: " << GetLastError(); + return; + } + DWORD ret = TerminateProcess(hProcess, 0); //结束目标进程 + if (ret == 0) { + WarnL << GetLastError; + } +#else + if (::kill(pid, force ? SIGKILL : SIGTERM) == -1) { + //进程可能已经退出了 + WarnL << "kill process " << pid << " failed:" << get_uv_errmsg(); + return; + } +#endif // _WIN32 - if (::kill(pid, force ? SIGKILL : SIGTERM) == -1) { - //进程可能已经退出了 - WarnL << "kill process " << pid << " failed:" << get_uv_errmsg(); - return; - } if(force){ //发送SIGKILL信号后,阻塞等待退出 diff --git a/server/Process.h b/server/Process.h index cce8470a..392ccbbf 100644 --- a/server/Process.h +++ b/server/Process.h @@ -23,10 +23,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#ifndef IPTV_PROCESS_H -#define IPTV_PROCESS_H +#ifndef ZLMEDIAKIT_PROCESS_H +#define ZLMEDIAKIT_PROCESS_H +#ifdef _WIN32 +typedef int pid_t; +#else #include +#endif // _WIN32 + #include #include using namespace std; @@ -45,4 +50,4 @@ private: }; -#endif //IPTV_PROCESS_H +#endif //ZLMEDIAKIT_PROCESS_H diff --git a/server/System.cpp b/server/System.cpp index dc9fdb15..df666120 100644 --- a/server/System.cpp +++ b/server/System.cpp @@ -24,239 +24,36 @@ * SOFTWARE. */ -#include "System.h" -#include -#include -#include +#if !defined(_WIN32) #include #include -#include #include -#ifndef ANDROID +#if !defined(ANDROID) #include -#endif -#include -#include -#include -#include "Util/mini.h" -#include "Util/util.h" -#include "Util/logger.h" -#include "Util/onceToken.h" -#include "Util/NoticeCenter.h" +#endif//!defined(ANDROID) +#endif//!defined(_WIN32) + #include "System.h" +#include +#include +#include +#include "Util/logger.h" +#include "Util/NoticeCenter.h" #include "Util/uv_errno.h" -#include "Util/CMD.h" -#include "Util/MD5.h" using namespace toolkit; const int MAX_STACK_FRAMES = 128; #define BroadcastOnCrashDumpArgs int &sig,const vector > &stack const char kBroadcastOnCrashDump[] = "kBroadcastOnCrashDump"; -//#if defined(__MACH__) || defined(__APPLE__) -//#define TEST_LINUX -//#endif - -vector splitWithEmptyLine(const string &s, const char *delim) { - vector ret; - int last = 0; - int index = s.find(delim, last); - while (index != string::npos) { - ret.push_back(s.substr(last, index - last)); - last = index + strlen(delim); - index = s.find(delim, last); - } - if (s.size() - last > 0) { - ret.push_back(s.substr(last)); - } - return ret; -} - -map splitTopStr(const string &cmd_str) { - map ret; - auto lines = splitWithEmptyLine(cmd_str, "\n"); - int i = 0; - for (auto &line : lines) { - if(i++ < 1 && line.empty()){ - continue; - } - if (line.empty()) { - break; - } - auto line_vec = split(line, ":"); - - if (line_vec.size() < 2) { - continue; - } - trim(line_vec[0], " \r\n\t"); - auto args_vec = split(line_vec[1], ","); - for (auto &arg : args_vec) { - auto arg_vec = split(trim(arg, " \r\n\t."), " "); - if (arg_vec.size() < 2) { - continue; - } - ret[line_vec[0]].emplace(arg_vec[1], arg_vec[0]); - } - } - return ret; -} - -bool System::getSystemUsage(SystemUsage &usage) { - try { -#if defined(__linux) || defined(__linux__) || defined(TEST_LINUX) - string cmd_str; -#if !defined(TEST_LINUX) - cmd_str = System::execute("top -b -n 1"); -#else - cmd_str = "top - 07:21:31 up 5:48, 2 users, load average: 0.03, 0.62, 0.54\n" - "Tasks: 80 total, 1 running, 78 sleeping, 0 stopped, 1 zombie\n" - "%Cpu(s): 0.8 us, 0.4 sy, 0.0 ni, 98.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st\n" - "KiB Mem: 2058500 total, 249100 used, 1809400 free, 19816 buffers\n" - "KiB Swap: 1046524 total, 0 used, 1046524 free. 153012 cached Mem\n" - "\n"; +#ifdef _WIN32 +#define popen _popen +#define pclose _pclose #endif - if (cmd_str.empty()) { - WarnL << "System::execute(\"top -b -n 1\") return empty"; - return false; - } - auto topMap = splitTopStr(cmd_str); - - usage.task_total = topMap["Tasks"]["total"]; - usage.task_running = topMap["Tasks"]["running"]; - usage.task_sleeping = topMap["Tasks"]["sleeping"]; - usage.task_stopped = topMap["Tasks"]["stopped"]; - - usage.cpu_user = topMap["%Cpu(s)"]["us"]; - usage.cpu_sys = topMap["%Cpu(s)"]["sy"]; - usage.cpu_idle = topMap["%Cpu(s)"]["id"]; - - usage.mem_total = topMap["KiB Mem"]["total"]; - usage.mem_free = topMap["KiB Mem"]["free"]; - usage.mem_used = topMap["KiB Mem"]["used"]; - return true; - -#elif defined(__MACH__) || defined(__APPLE__) - /* - "Processes: 275 total, 2 running, 1 stuck, 272 sleeping, 1258 threads \n" - "2018/09/12 10:41:32\n" - "Load Avg: 2.06, 2.88, 2.86 \n" - "CPU usage: 14.54% user, 25.45% sys, 60.0% idle \n" - "SharedLibs: 117M resident, 37M data, 15M linkedit.\n" - "MemRegions: 46648 total, 3654M resident, 62M private, 714M shared.\n" - "PhysMem: 7809M used (1906M wired), 381M unused.\n" - "VM: 751G vsize, 614M framework vsize, 0(0) swapins, 0(0) swapouts.\n" - "Networks: packets: 502366/248M in, 408957/87M out.\n" - "Disks: 349435/6037M read, 78622/2577M written."; - */ - - string cmd_str = System::execute("top -l 1"); - if(cmd_str.empty()){ - WarnL << "System::execute(\"top -n 1\") return empty"; - return false; - } - auto topMap = splitTopStr(cmd_str); - usage.task_total = topMap["Processes"]["total"]; - usage.task_running = topMap["Processes"]["running"]; - usage.task_sleeping = topMap["Processes"]["sleeping"]; - usage.task_stopped = topMap["Processes"]["stuck"]; - - usage.cpu_user = topMap["CPU usage"]["user"]; - usage.cpu_sys = topMap["CPU usage"]["sys"]; - usage.cpu_idle = topMap["CPU usage"]["idle"]; - - usage.mem_free = topMap["PhysMem"]["unused"].as() * 1024 * 1024; - usage.mem_used = topMap["PhysMem"]["used"].as() * 1024 * 1024; - usage.mem_total = usage.mem_free + usage.mem_used; - return true; -#else - WarnL << "System not supported"; - return false; -#endif - } catch (std::exception &ex) { - WarnL << ex.what(); - return false; - } -} - -bool System::getNetworkUsage(vector &usage) { - try { -#if defined(__linux) || defined(__linux__) || defined(TEST_LINUX) - string cmd_str; -#if !defined(TEST_LINUX) - cmd_str = System::execute("cat /proc/net/dev"); -#else - cmd_str = - "Inter-| Receive | Transmit\n" - " face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed\n" - " lo: 475978 7546 0 0 0 0 0 0 475978 7546 0 0 0 0 0 0\n" - "enp3s0: 151747818 315136 0 0 0 0 0 145 1590783447 1124616 0 0 0 0 0 0"; -#endif - if (cmd_str.empty()) { - return false; - } - auto lines = split(cmd_str, "\n"); - int i = 0; - vector column_name_vec; - vector category_prefix_vec; - for (auto &line : lines) { - switch (i++) { - case 0: { - category_prefix_vec = split(line, "|"); - } - break; - case 1: { - auto category_suffix_vec = split(line, "|"); - int j = 0; - for (auto &category_suffix : category_suffix_vec) { - auto column_suffix_vec = split(category_suffix, " "); - for (auto &column_suffix : column_suffix_vec) { - column_name_vec.emplace_back(trim(category_prefix_vec[j]) + "-" + trim(column_suffix)); - } - j++; - } - } - break; - default: { - mINI valMap; - auto vals = split(line, " "); - int j = 0; - for (auto &val : vals) { - valMap[column_name_vec[j++]] = trim(val, " \r\n\t:"); - } - usage.emplace_back(NetworkUsage()); - auto &ifrUsage = usage.back(); - ifrUsage.interface = valMap["Inter--face"]; - ifrUsage.recv_bytes = valMap["Receive-bytes"]; - ifrUsage.recv_packets = valMap["Receive-packets"]; - ifrUsage.snd_bytes = valMap["Transmit-bytes"]; - ifrUsage.snd_packets = valMap["Transmit-packets"]; - } - break; - } - } - return true; -#else - WarnL << "System not supported"; - return false; -#endif - } catch (std::exception &ex) { - WarnL << ex.what(); - return false; - } -} - - -bool System::getTcpUsage(System::TcpUsage &usage) { - usage.established = atoi(trim(System::execute("netstat -na|grep ESTABLISHED|wc -l")).data()); - usage.syn_recv = atoi(trim(System::execute("netstat -na|grep SYN_RECV|wc -l")).data()); - usage.time_wait = atoi(trim(System::execute("netstat -na|grep TIME_WAIT|wc -l")).data()); - usage.close_wait = atoi(trim(System::execute("netstat -na|grep CLOSE_WAIT|wc -l")).data()); - return true; -} string System::execute(const string &cmd) { -// DebugL << cmd; - FILE *fPipe = popen(cmd.data(), "r"); + FILE *fPipe = NULL; + fPipe = popen(cmd.data(), "r"); if(!fPipe){ return ""; } @@ -269,12 +66,12 @@ string System::execute(const string &cmd) { return ret; } +#if !defined(ANDROID) && !defined(_WIN32) static string addr2line(const string &address) { string cmd = StrPrinter << "addr2line -e " << exePath() << " " << address; return System::execute(cmd); } -#ifndef ANDROID static void sig_crash(int sig) { signal(sig, SIG_DFL); void *array[MAX_STACK_FRAMES]; @@ -294,14 +91,13 @@ static void sig_crash(int sig) { #endif//__linux } free(strings); - NoticeCenter::Instance().emitEvent(kBroadcastOnCrashDump,sig,stack); } - -#endif//#ifndef ANDROID +#endif // !defined(ANDROID) && !defined(_WIN32) void System::startDaemon() { +#ifndef _WIN32 static pid_t pid; do{ pid = fork(); @@ -336,21 +132,11 @@ void System::startDaemon() { DebugL << "waitpid被中断:" << get_uv_errmsg(); }while (true); }while (true); -} - - - -static string currentDateTime(){ - time_t ts = time(NULL); - std::tm tm_snapshot; - localtime_r(&ts, &tm_snapshot); - - char buffer[1024] = {0}; - std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm_snapshot); - return buffer; +#endif // _WIN32 } void System::systemSetup(){ +#if !defined(_WIN32) struct rlimit rlim,rlim_new; if (getrlimit(RLIMIT_CORE, &rlim)==0) { rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY; @@ -373,11 +159,9 @@ void System::systemSetup(){ #ifndef ANDROID signal(SIGSEGV, sig_crash); signal(SIGABRT, sig_crash); -#endif//#ifndef ANDROID - NoticeCenter::Instance().addListener(nullptr,kBroadcastOnCrashDump,[](BroadcastOnCrashDumpArgs){ stringstream ss; - ss << "## crash date:" << currentDateTime() << endl; + ss << "## crash date:" << getTimeStr("%Y-%m-%d %H:%M:%S") << endl; ss << "## exe: " << exeName() << endl; ss << "## signal: " << sig << endl; ss << "## stack: " << endl; @@ -391,8 +175,9 @@ void System::systemSetup(){ ofstream out(StrPrinter << exeDir() << "/crash." << getpid(), ios::out | ios::binary | ios::trunc); out << stack_info; out.flush(); - cerr << stack_info << endl; }); +#endif// ANDROID +#endif//!defined(_WIN32) } diff --git a/server/System.h b/server/System.h index c8857a73..565b99b3 100644 --- a/server/System.h +++ b/server/System.h @@ -24,67 +24,17 @@ * SOFTWARE. */ -#ifndef IPTV_BASH_H -#define IPTV_BASH_H +#ifndef ZLMEDIAKIT_SYSTEM_H +#define ZLMEDIAKIT_SYSTEM_H -#include #include -#include -#include -#include "Util/NoticeCenter.h" - using namespace std; -using namespace toolkit; class System { public: - typedef struct { - uint32_t task_total; - uint32_t task_running; - uint32_t task_sleeping; - uint32_t task_stopped; - - uint64_t mem_total; - uint64_t mem_free; - uint64_t mem_used; - - float cpu_user; - float cpu_sys; - float cpu_idle; - } SystemUsage; - - typedef struct { - uint64_t recv_bytes; - uint64_t recv_packets; - uint64_t snd_bytes; - uint64_t snd_packets; - string interface; - } NetworkUsage; - - typedef struct { - uint64_t available; - uint64_t used; - float used_per; - string mounted_on; - string filesystem; - bool mounted; - } DiskUsage; - - typedef struct { - uint32_t established; - uint32_t syn_recv; - uint32_t time_wait; - uint32_t close_wait; - } TcpUsage; - - static bool getSystemUsage(SystemUsage &usage); - static bool getNetworkUsage(vector &usage); - static bool getTcpUsage(TcpUsage &usage); static string execute(const string &cmd); static void startDaemon(); static void systemSetup(); - }; - -#endif //IPTV_BASH_H +#endif //ZLMEDIAKIT_SYSTEM_H diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 9a4fcdb7..e72d36ec 100644 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -46,40 +46,12 @@ #include "WebApi.h" #include "WebHook.h" #include "Thread/WorkThreadPool.h" - -#if !defined(_WIN32) +#include "Rtp/RtpSelector.h" #include "FFmpegSource.h" -#endif//!defined(_WIN32) - using namespace Json; using namespace toolkit; using namespace mediakit; - -typedef map ApiArgsType; - - -#define API_ARGS TcpSession &sender, \ - HttpSession::KeyValue &headerIn, \ - HttpSession::KeyValue &headerOut, \ - ApiArgsType &allArgs, \ - Json::Value &val - -#define API_REGIST(field, name, ...) \ - s_map_api.emplace("/index/"#field"/"#name,[](API_ARGS,const HttpSession::HttpResponseInvoker &invoker){ \ - static auto lam = [&](API_ARGS) __VA_ARGS__ ; \ - lam(sender,headerIn, headerOut, allArgs, val); \ - invoker("200 OK", headerOut, val.toStyledString()); \ - }); - -#define API_REGIST_INVOKER(field, name, ...) \ - s_map_api.emplace("/index/"#field"/"#name,[](API_ARGS,const HttpSession::HttpResponseInvoker &invoker) __VA_ARGS__); - -//异步http api lambad定义 -typedef std::function AsyncHttpApi; -//api列表 -static map s_map_api; - namespace API { typedef enum { InvalidArgs = -300, @@ -99,6 +71,7 @@ static onceToken token([]() { }); }//namespace API + class ApiRetException: public std::runtime_error { public: ApiRetException(const char *str = "success" ,int code = API::Success):runtime_error(str){ @@ -128,34 +101,54 @@ public: ~SuccessException() = default; }; +#define API_ARGS1 TcpSession &sender,HttpSession::KeyValue &headerIn, HttpSession::KeyValue &headerOut, ApiArgsType &allArgs, Json::Value &val +#define API_ARGS2 API_ARGS1, const HttpSession::HttpResponseInvoker &invoker +#define API_ARGS_VALUE1 sender,headerIn,headerOut,allArgs,val +#define API_ARGS_VALUE2 API_ARGS_VALUE1, invoker + +typedef map ApiArgsType; +//http api列表 +static map > s_map_api; + +template +static void api_regist1(const string &api_path, FUNC &&func) { + s_map_api.emplace(api_path, [func](API_ARGS2) { + func(API_ARGS_VALUE1); + invoker("200 OK", headerOut, val.toStyledString()); + }); +} + +template +static void api_regist2(const string &api_path, FUNC &&func) { + s_map_api.emplace(api_path, std::forward(func)); +} //获取HTTP请求中url参数、content参数 static ApiArgsType getAllArgs(const Parser &parser) { ApiArgsType allArgs; - if(parser["Content-Type"].find("application/x-www-form-urlencoded") == 0){ + if (parser["Content-Type"].find("application/x-www-form-urlencoded") == 0) { auto contentArgs = parser.parseArgs(parser.Content()); for (auto &pr : contentArgs) { allArgs[pr.first] = HttpSession::urlDecode(pr.second); } - }else if(parser["Content-Type"].find("application/json") == 0){ + } else if (parser["Content-Type"].find("application/json") == 0) { try { stringstream ss(parser.Content()); Value jsonArgs; ss >> jsonArgs; auto keys = jsonArgs.getMemberNames(); - for (auto key = keys.begin(); key != keys.end(); ++key){ + for (auto key = keys.begin(); key != keys.end(); ++key) { allArgs[*key] = jsonArgs[*key].asString(); } - }catch (std::exception &ex){ + } catch (std::exception &ex) { WarnL << ex.what(); } - }else if(!parser["Content-Type"].empty()){ + } else if (!parser["Content-Type"].empty()) { WarnL << "invalid Content-Type:" << parser["Content-Type"]; } - auto &urlArgs = parser.getUrlArgs(); - for (auto &pr : urlArgs) { - allArgs[pr.first] = HttpSession::urlDecode(pr.second); + for (auto &pr : parser.getUrlArgs()) { + allArgs[pr.first] = pr.second; } return std::move(allArgs); } @@ -266,10 +259,8 @@ static inline string getProxyKey(const string &vhost,const string &app,const str return vhost + "/" + app + "/" + stream; } -#if !defined(_WIN32) static unordered_map s_ffmpegMap; static recursive_mutex s_ffmpegMapMtx; -#endif//#if !defined(_WIN32) /** * 安装api接口 @@ -278,12 +269,11 @@ static recursive_mutex s_ffmpegMapMtx; */ void installWebApi() { addHttpListener(); - GET_CONFIG(string,api_secret,API::kSecret); //获取线程负载 //测试url http://127.0.0.1/index/api/getThreadsLoad - API_REGIST_INVOKER(api, getThreadsLoad, { + api_regist2("/index/api/getThreadsLoad",[](API_ARGS2){ EventPollerPool::Instance().getExecutorDelay([invoker, headerOut](const vector &vecDelay) { Value val; auto vec = EventPollerPool::Instance().getExecutorLoad(); @@ -294,13 +284,14 @@ void installWebApi() { obj["delay"] = vecDelay[i++]; val["data"].append(obj); } + val["code"] = API::Success; invoker("200 OK", headerOut, val.toStyledString()); }); }); //获取后台工作线程负载 //测试url http://127.0.0.1/index/api/getWorkThreadsLoad - API_REGIST_INVOKER(api, getWorkThreadsLoad, { + api_regist2("/index/api/getWorkThreadsLoad", [](API_ARGS2){ WorkThreadPool::Instance().getExecutorDelay([invoker, headerOut](const vector &vecDelay) { Value val; auto vec = WorkThreadPool::Instance().getExecutorLoad(); @@ -311,13 +302,14 @@ void installWebApi() { obj["delay"] = vecDelay[i++]; val["data"].append(obj); } + val["code"] = API::Success; invoker("200 OK", headerOut, val.toStyledString()); }); }); //获取服务器配置 //测试url http://127.0.0.1/index/api/getServerConfig - API_REGIST(api, getServerConfig, { + api_regist1("/index/api/getServerConfig",[](API_ARGS1){ CHECK_SECRET(); Value obj; for (auto &pr : mINI::Instance()) { @@ -329,7 +321,7 @@ void installWebApi() { //设置服务器配置 //测试url(比如关闭http api调试) http://127.0.0.1/index/api/setServerConfig?api.apiDebug=0 //你也可以通过http post方式传参,可以通过application/x-www-form-urlencoded或application/json方式传参 - API_REGIST(api, setServerConfig, { + api_regist1("/index/api/setServerConfig",[](API_ARGS1){ CHECK_SECRET(); auto &ini = mINI::Instance(); int changed = API::Success; @@ -347,25 +339,35 @@ void installWebApi() { } if (changed > 0) { NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig); - ini.dumpFile(); + ini.dumpFile(g_ini_file); } val["changed"] = changed; }); - //获取服务器api列表 - //测试url http://127.0.0.1/index/api/getApiList - API_REGIST(api,getApiList,{ + static auto s_get_api_list = [](API_ARGS1){ CHECK_SECRET(); for(auto &pr : s_map_api){ val["data"].append(pr.first); } + }; + + //获取服务器api列表 + //测试url http://127.0.0.1/index/api/getApiList + api_regist1("/index/api/getApiList",[](API_ARGS1){ + s_get_api_list(API_ARGS_VALUE1); + }); + + //获取服务器api列表 + //测试url http://127.0.0.1/index/ + api_regist1("/index/",[](API_ARGS1){ + s_get_api_list(API_ARGS_VALUE1); }); #if !defined(_WIN32) //重启服务器,只有Daemon方式才能重启,否则是直接关闭! //测试url http://127.0.0.1/index/api/restartServer - API_REGIST(api,restartServer,{ + api_regist1("/index/api/restartServer",[](API_ARGS1){ CHECK_SECRET(); EventPollerPool::Instance().getPoller()->doDelayTask(1000,[](){ //尝试正常退出 @@ -388,37 +390,68 @@ void installWebApi() { //测试url0(获取所有流) http://127.0.0.1/index/api/getMediaList //测试url1(获取虚拟主机为"__defaultVost__"的流) http://127.0.0.1/index/api/getMediaList?vhost=__defaultVost__ //测试url2(获取rtsp类型的流) http://127.0.0.1/index/api/getMediaList?schema=rtsp - API_REGIST(api,getMediaList,{ + api_regist1("/index/api/getMediaList",[](API_ARGS1){ CHECK_SECRET(); //获取所有MediaSource列表 - val["code"] = API::Success; - val["msg"] = "success"; - MediaSource::for_each_media([&](const string &schema, - const string &vhost, - const string &app, - const string &stream, - const MediaSource::Ptr &media){ - if(!allArgs["schema"].empty() && allArgs["schema"] != schema){ + MediaSource::for_each_media([&](const MediaSource::Ptr &media){ + if(!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()){ return; } - if(!allArgs["vhost"].empty() && allArgs["vhost"] != vhost){ + if(!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()){ return; } - if(!allArgs["app"].empty() && allArgs["app"] != app){ + if(!allArgs["app"].empty() && allArgs["app"] != media->getApp()){ return; } Value item; - item["schema"] = schema; - item["vhost"] = vhost; - item["app"] = app; - item["stream"] = stream; + item["schema"] = media->getSchema(); + item["vhost"] = media->getVhost(); + item["app"] = media->getApp(); + item["stream"] = media->getId(); + item["readerCount"] = media->readerCount(); + item["totalReaderCount"] = media->totalReaderCount(); + for(auto &track : media->getTracks()){ + Value obj; + obj["codec_id"] = track->getCodecId(); + obj["codec_type"] = track->getTrackType(); + obj["ready"] = track->ready(); + item["tracks"].append(obj); + } val["data"].append(item); }); }); + //测试url http://127.0.0.1/index/api/isMediaOnline?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs + api_regist1("/index/api/isMediaOnline",[](API_ARGS1){ + CHECK_SECRET(); + CHECK_ARGS("schema","vhost","app","stream"); + val["online"] = (bool) (MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"],false)); + }); + + //测试url http://127.0.0.1/index/api/getMediaInfo?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs + api_regist1("/index/api/getMediaInfo",[](API_ARGS1){ + CHECK_SECRET(); + CHECK_ARGS("schema","vhost","app","stream"); + auto src = MediaSource::find(allArgs["schema"],allArgs["vhost"],allArgs["app"],allArgs["stream"],false); + if(!src){ + val["online"] = false; + return; + } + val["online"] = true; + val["readerCount"] = src->readerCount(); + val["totalReaderCount"] = src->totalReaderCount(); + for(auto &track : src->getTracks()){ + Value obj; + obj["codec_id"] = track->getCodecId(); + obj["codec_type"] = track->getTrackType(); + obj["ready"] = track->ready(); + val["tracks"].append(obj); + } + }); + //主动关断流,包括关断拉流、推流 //测试url http://127.0.0.1/index/api/close_stream?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1 - API_REGIST(api,close_stream,{ + api_regist1("/index/api/close_stream",[](API_ARGS1){ CHECK_SECRET(); CHECK_ARGS("schema","vhost","app","stream"); //踢掉推流器 @@ -428,25 +461,60 @@ void installWebApi() { allArgs["stream"]); if(src){ bool flag = src->close(allArgs["force"].as()); - val["code"] = flag ? 0 : -1; + val["result"] = flag ? 0 : -1; val["msg"] = flag ? "success" : "close failed"; }else{ - val["code"] = -2; + val["result"] = -2; val["msg"] = "can not find the stream"; } }); + //批量主动关断流,包括关断拉流、推流 + //测试url http://127.0.0.1/index/api/close_streams?schema=rtsp&vhost=__defaultVhost__&app=live&stream=obs&force=1 + api_regist1("/index/api/close_streams",[](API_ARGS1){ + CHECK_SECRET(); + //筛选命中个数 + int count_hit = 0; + int count_closed = 0; + list media_list; + MediaSource::for_each_media([&](const MediaSource::Ptr &media){ + if(!allArgs["schema"].empty() && allArgs["schema"] != media->getSchema()){ + return; + } + if(!allArgs["vhost"].empty() && allArgs["vhost"] != media->getVhost()){ + return; + } + if(!allArgs["app"].empty() && allArgs["app"] != media->getApp()){ + return; + } + if(!allArgs["stream"].empty() && allArgs["stream"] != media->getId()){ + return; + } + ++count_hit; + media_list.emplace_back(media); + }); + + bool force = allArgs["force"].as(); + for(auto &media : media_list){ + if(media->close(force)){ + ++count_closed; + } + } + val["count_hit"] = count_hit; + val["count_closed"] = count_closed; + }); + //获取所有TcpSession列表信息 //可以根据本地端口和远端ip来筛选 //测试url(筛选某端口下的tcp会话) http://127.0.0.1/index/api/getAllSession?local_port=1935 - API_REGIST(api,getAllSession,{ + api_regist1("/index/api/getAllSession",[](API_ARGS1){ CHECK_SECRET(); Value jsession; uint16_t local_port = allArgs["local_port"].as(); string &peer_ip = allArgs["peer_ip"]; SessionMap::Instance().for_each_session([&](const string &id,const TcpSession::Ptr &session){ - if(local_port != API::Success && local_port != session->get_local_port()){ + if(local_port != 0 && local_port != session->get_local_port()){ return; } if(!peer_ip.empty() && peer_ip != session->get_peer_ip()){ @@ -464,19 +532,42 @@ void installWebApi() { //断开tcp连接,比如说可以断开rtsp、rtmp播放器等 //测试url http://127.0.0.1/index/api/kick_session?id=123456 - API_REGIST(api,kick_session,{ + api_regist1("/index/api/kick_session",[](API_ARGS1){ CHECK_SECRET(); CHECK_ARGS("id"); //踢掉tcp会话 auto session = SessionMap::Instance().get(allArgs["id"]); if(!session){ - val["code"] = API::OtherFailed; - val["msg"] = "can not find the target"; - return; + throw ApiRetException("can not find the target",API::OtherFailed); } session->safeShutdown(); - val["code"] = API::Success; - val["msg"] = "success"; + }); + + + //批量断开tcp连接,比如说可以断开rtsp、rtmp播放器等 + //测试url http://127.0.0.1/index/api/kick_sessions?local_port=1935 + api_regist1("/index/api/kick_sessions",[](API_ARGS1){ + CHECK_SECRET(); + uint16_t local_port = allArgs["local_port"].as(); + string &peer_ip = allArgs["peer_ip"]; + uint64_t count_hit = 0; + + list session_list; + SessionMap::Instance().for_each_session([&](const string &id,const TcpSession::Ptr &session){ + if(local_port != 0 && local_port != session->get_local_port()){ + return; + } + if(!peer_ip.empty() && peer_ip != session->get_peer_ip()){ + return; + } + session_list.emplace_back(session); + ++count_hit; + }); + + for(auto &session : session_list){ + session->safeShutdown(); + } + val["count_hit"] = (Json::UInt64)count_hit; }); static auto addStreamProxy = [](const string &vhost, @@ -521,7 +612,7 @@ void installWebApi() { //动态添加rtsp/rtmp拉流代理 //测试url http://127.0.0.1/index/api/addStreamProxy?vhost=__defaultVhost__&app=proxy&enable_rtsp=1&enable_rtmp=1&stream=0&url=rtmp://127.0.0.1/live/obs - API_REGIST_INVOKER(api,addStreamProxy,{ + api_regist2("/index/api/addStreamProxy",[](API_ARGS2){ CHECK_SECRET(); CHECK_ARGS("vhost","app","stream","url","enable_rtsp","enable_rtmp"); addStreamProxy(allArgs["vhost"], @@ -546,15 +637,14 @@ void installWebApi() { //关闭拉流代理 //测试url http://127.0.0.1/index/api/delStreamProxy?key=__defaultVhost__/proxy/0 - API_REGIST(api,delStreamProxy,{ + api_regist1("/index/api/delStreamProxy",[](API_ARGS1){ CHECK_SECRET(); CHECK_ARGS("key"); lock_guard lck(s_proxyMapMtx); val["data"]["flag"] = s_proxyMap.erase(allArgs["key"]) == 1; }); -#if !defined(_WIN32) - static auto addFFmepgSource = [](const string &src_url, + static auto addFFmpegSource = [](const string &src_url, const string &dst_url, int timeout_ms, const function &cb){ @@ -584,14 +674,14 @@ void installWebApi() { //动态添加rtsp/rtmp拉流代理 //测试url http://127.0.0.1/index/api/addFFmpegSource?src_url=http://live.hkstv.hk.lxdns.com/live/hks2/playlist.m3u8&dst_url=rtmp://127.0.0.1/live/hks2&timeout_ms=10000 - API_REGIST_INVOKER(api,addFFmpegSource,{ + api_regist2("/index/api/addFFmpegSource",[](API_ARGS2){ CHECK_SECRET(); CHECK_ARGS("src_url","dst_url","timeout_ms"); auto src_url = allArgs["src_url"]; auto dst_url = allArgs["dst_url"]; int timeout_ms = allArgs["timeout_ms"]; - addFFmepgSource(src_url,dst_url,timeout_ms,[invoker,val,headerOut](const SockException &ex,const string &key){ + addFFmpegSource(src_url,dst_url,timeout_ms,[invoker,val,headerOut](const SockException &ex,const string &key){ if(ex){ const_cast(val)["code"] = API::OtherFailed; const_cast(val)["msg"] = ex.what(); @@ -602,25 +692,127 @@ void installWebApi() { }); }); - //关闭拉流代理 - //测试url http://127.0.0.1/index/api/delFFmepgSource?key=key - API_REGIST(api,delFFmepgSource,{ + + static auto api_delFFmpegSource = [](API_ARGS1){ CHECK_SECRET(); CHECK_ARGS("key"); lock_guard lck(s_ffmpegMapMtx); val["data"]["flag"] = s_ffmpegMap.erase(allArgs["key"]) == 1; + }; + + //关闭拉流代理 + //测试url http://127.0.0.1/index/api/delFFmepgSource?key=key + api_regist1("/index/api/delFFmpegSource",[](API_ARGS1){ + api_delFFmpegSource(API_ARGS_VALUE1); + }); + + //此处为了兼容之前的拼写错误 + api_regist1("/index/api/delFFmepgSource",[](API_ARGS1){ + api_delFFmpegSource(API_ARGS_VALUE1); }); -#endif //新增http api下载可执行程序文件接口 //测试url http://127.0.0.1/index/api/downloadBin - API_REGIST_INVOKER(api,downloadBin,{ + api_regist2("/index/api/downloadBin",[](API_ARGS2){ CHECK_SECRET(); invoker.responseFile(headerIn,StrCaseMap(),exePath()); }); +#if defined(ENABLE_RTPPROXY) + api_regist1("/index/api/getSsrcInfo",[](API_ARGS1){ + CHECK_SECRET(); + CHECK_ARGS("ssrc"); + uint32_t ssrc = 0; + stringstream ss(allArgs["ssrc"]); + ss >> std::hex >> ssrc; + + auto process = RtpSelector::Instance().getProcess(ssrc,false); + if(!process){ + val["exist"] = false; + return; + } + val["exist"] = true; + val["peer_ip"] = process->get_peer_ip(); + val["peer_port"] = process->get_peer_port(); + }); +#endif//ENABLE_RTPPROXY + + // 开始录制hls或MP4 + api_regist1("/index/api/startRecord",[](API_ARGS1){ + CHECK_SECRET(); + CHECK_ARGS("type","vhost","app","stream","wait_for_record","continue_record"); + + int result = Recorder::startRecord((Recorder::type)allArgs["type"].as(), + allArgs["vhost"], + allArgs["app"], + allArgs["stream"], + allArgs["customized_path"], + allArgs["wait_for_record"], + allArgs["continue_record"]); + val["result"] = result; + }); + + // 停止录制hls或MP4 + api_regist1("/index/api/stopRecord",[](API_ARGS1){ + CHECK_SECRET(); + CHECK_ARGS("type","vhost","app","stream"); + int result = Recorder::stopRecord((Recorder::type)allArgs["type"].as(), + allArgs["vhost"], + allArgs["app"], + allArgs["stream"]); + val["result"] = result; + }); + + // 获取hls或MP4录制状态 + api_regist1("/index/api/getRecordStatus",[](API_ARGS1){ + CHECK_SECRET(); + CHECK_ARGS("type","vhost","app","stream"); + auto status = Recorder::getRecordStatus((Recorder::type)allArgs["type"].as(), + allArgs["vhost"], + allArgs["app"], + allArgs["stream"]); + val["status"] = (int)status; + }); + + //获取录像文件夹列表或mp4文件列表 + //http://127.0.0.1/index/api/getMp4RecordFile?vhost=__defaultVhost__&app=live&stream=ss&period=2020-01 + api_regist1("/index/api/getMp4RecordFile", [](API_ARGS1){ + CHECK_SECRET(); + CHECK_ARGS("vhost", "app", "stream"); + auto record_path = Recorder::getRecordPath(Recorder::type_mp4, allArgs["vhost"], allArgs["app"],allArgs["stream"]); + auto period = allArgs["period"]; + + //判断是获取mp4文件列表还是获取文件夹列表 + bool search_mp4 = period.size() == sizeof("2020-02-01") - 1; + if (search_mp4) { + record_path = record_path + period + "/"; + } + + Json::Value paths(arrayValue); + //这是筛选日期,获取文件夹列表 + File::scanDir(record_path, [&](const string &path, bool isDir) { + int pos = path.rfind('/'); + if (pos != string::npos) { + string relative_path = path.substr(pos + 1); + if (search_mp4) { + if (!isDir) { + //我们只收集mp4文件,对文件夹不感兴趣 + paths.append(relative_path); + } + } else if (isDir && relative_path.find(period) == 0) { + //匹配到对应日期的文件夹 + paths.append(relative_path); + } + } + return true; + }, false); + + val["data"]["rootPath"] = record_path; + val["data"]["paths"] = paths; + }); + ////////////以下是注册的Hook API//////////// - API_REGIST(hook,on_publish,{ + api_regist1("/index/hook/on_publish",[](API_ARGS1){ //开始推流事件 //转换成rtsp或rtmp val["enableRtxp"] = true; @@ -630,23 +822,23 @@ void installWebApi() { val["enableMP4"] = false; }); - API_REGIST(hook,on_play,{ + api_regist1("/index/hook/on_play",[](API_ARGS1){ //开始播放事件 throw SuccessException(); }); - API_REGIST(hook,on_flow_report,{ + api_regist1("/index/hook/on_flow_report",[](API_ARGS1){ //流量统计hook api throw SuccessException(); }); - API_REGIST(hook,on_rtsp_realm,{ + api_regist1("/index/hook/on_rtsp_realm",[](API_ARGS1){ //rtsp是否需要鉴权,默认需要鉴权 val["code"] = API::Success; val["realm"] = "zlmediakit_reaml"; }); - API_REGIST(hook,on_rtsp_auth,{ + api_regist1("/index/hook/on_rtsp_auth",[](API_ARGS1){ //rtsp鉴权密码,密码等于用户名 //rtsp可以有双重鉴权!后面还会触发on_play事件 CHECK_ARGS("user_name"); @@ -655,14 +847,14 @@ void installWebApi() { val["passwd"] = allArgs["user_name"].data(); }); - API_REGIST(hook,on_stream_changed,{ + api_regist1("/index/hook/on_stream_changed",[](API_ARGS1){ //媒体注册或反注册事件 throw SuccessException(); }); #if !defined(_WIN32) - API_REGIST_INVOKER(hook,on_stream_not_found_ffmpeg,{ + api_regist2("/index/hook/on_stream_not_found_ffmpeg",[](API_ARGS2){ //媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流 CHECK_SECRET(); CHECK_ARGS("vhost","app","stream"); @@ -677,7 +869,7 @@ void installWebApi() { << allArgs["stream"] << "?vhost=" << allArgs["vhost"]; - addFFmepgSource("http://live.hkstv.hk.lxdns.com/live/hks2/playlist.m3u8",/** ffmpeg拉流支持任意编码格式任意协议 **/ + addFFmpegSource("http://hls-ott-zhibo.wasu.tv/live/272/index.m3u8",/** ffmpeg拉流支持任意编码格式任意协议 **/ dst_url, (1000 * timeout_sec) - 500, [invoker,val,headerOut](const SockException &ex,const string &key){ @@ -692,7 +884,7 @@ void installWebApi() { }); #endif//!defined(_WIN32) - API_REGIST_INVOKER(hook,on_stream_not_found,{ + api_regist2("/index/hook/on_stream_not_found",[](API_ARGS2){ //媒体未找到事件,我们都及时拉流hks作为替代品,目的是为了测试按需拉流 CHECK_SECRET(); CHECK_ARGS("vhost","app","stream"); @@ -718,17 +910,17 @@ void installWebApi() { }); }); - API_REGIST(hook,on_record_mp4,{ + api_regist1("/index/hook/on_record_mp4",[](API_ARGS1){ //录制mp4分片完毕事件 throw SuccessException(); }); - API_REGIST(hook,on_shell_login,{ + api_regist1("/index/hook/on_shell_login",[](API_ARGS1){ //shell登录调试事件 throw SuccessException(); }); - API_REGIST(hook,on_stream_none_reader,{ + api_regist1("/index/hook/on_stream_none_reader",[](API_ARGS1){ //无人观看流默认关闭 val["close"] = true; }); @@ -738,7 +930,7 @@ void installWebApi() { return true; }; - API_REGIST(hook,on_http_access,{ + api_regist1("/index/hook/on_http_access",[](API_ARGS1){ //在这里根据allArgs["params"](url参数)来判断该http客户端是否有权限访问该文件 if(!checkAccess(allArgs["params"])){ //无访问权限 @@ -759,6 +951,12 @@ void installWebApi() { }); + api_regist1("/index/hook/on_server_started",[](API_ARGS1){ + //服务器重启报告 + throw SuccessException(); + }); + + } void unInstallWebApi(){ @@ -767,10 +965,8 @@ void unInstallWebApi(){ s_proxyMap.clear(); } -#if !defined(_WIN32) { lock_guard lck(s_ffmpegMapMtx); s_ffmpegMap.clear(); } -#endif } \ No newline at end of file diff --git a/server/WebApi.h b/server/WebApi.h index c3ffecf1..2a917db3 100644 --- a/server/WebApi.h +++ b/server/WebApi.h @@ -47,5 +47,7 @@ extern const string kPort; void installWebApi(); void unInstallWebApi(); +//配置文件路径 +extern string g_ini_file; #endif //ZLMEDIAKIT_WEBAPI_H diff --git a/server/WebHook.cpp b/server/WebHook.cpp index e4c8945e..65933573 100644 --- a/server/WebHook.cpp +++ b/server/WebHook.cpp @@ -39,6 +39,7 @@ #include "Rtsp/RtspSession.h" #include "Http/HttpSession.h" #include "WebHook.h" +#include "Record/MP4Recorder.h" using namespace Json; using namespace toolkit; @@ -71,6 +72,7 @@ const string kOnRecordMp4 = HOOK_FIELD"on_record_mp4"; 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 kAdminParams = HOOK_FIELD"admin_params"; onceToken token([](){ @@ -87,6 +89,7 @@ onceToken token([](){ mINI::Instance()[kOnShellLogin] = "https://127.0.0.1/index/hook/on_shell_login"; mINI::Instance()[kOnStreamNoneReader] = "https://127.0.0.1/index/hook/on_stream_none_reader"; mINI::Instance()[kOnHttpAccess] = "https://127.0.0.1/index/hook/on_http_access"; + mINI::Instance()[kOnServerStarted] = "https://127.0.0.1/index/hook/on_server_started"; mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc"; },nullptr); }//namespace Hook @@ -177,6 +180,20 @@ static ArgsType make_json(const MediaInfo &args){ return std::move(body); } +static void reportServerStarted(){ + GET_CONFIG(bool,hook_enable,Hook::kEnable); + GET_CONFIG(string,hook_server_started,Hook::kOnServerStarted); + if(!hook_enable || hook_server_started.empty()){ + return; + } + + ArgsType body; + for (auto &pr : mINI::Instance()) { + body[pr.first] = (string &) pr.second; + } + //执行hook + do_http_hook(hook_server_started,body, nullptr); +} void installWebHook(){ GET_CONFIG(bool,hook_enable,Hook::kEnable); @@ -194,8 +211,11 @@ void installWebHook(){ GET_CONFIG(string,hook_http_access,Hook::kOnHttpAccess); NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPublish,[](BroadcastMediaPublishArgs){ + GET_CONFIG(bool,toRtxp,General::kPublishToRtxp); + GET_CONFIG(bool,toHls,General::kPublishToHls); + GET_CONFIG(bool,toMP4,General::kPublishToMP4); if(!hook_enable || args._param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1"){ - invoker("",true, true,false); + invoker("",toRtxp,toHls,toMP4); return; } //异步执行该hook api,防止阻塞NoticeCenter @@ -207,9 +227,9 @@ void installWebHook(){ do_http_hook(hook_publish,body,[invoker](const Value &obj,const string &err){ if(err.empty()){ //推流鉴权成功 - bool enableRtxp = true; - bool enableHls = true; - bool enableMP4 = false; + bool enableRtxp = toRtxp; + bool enableHls = toHls; + bool enableMP4 = toMP4; //兼容用户不传递enableRtxp、enableHls、enableMP4参数 if(obj.isMember("enableRtxp")){ @@ -249,16 +269,16 @@ void installWebHook(){ }); NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastFlowReport,[](BroadcastFlowReportArgs){ - if(!hook_enable || args._param_strs == hook_adminparams || hook_flowreport.empty() || sender.get_peer_ip() == "127.0.0.1"){ + if(!hook_enable || args._param_strs == hook_adminparams || hook_flowreport.empty() || peerIP == "127.0.0.1"){ return; } auto body = make_json(args); - body["ip"] = sender.get_peer_ip(); - body["port"] = sender.get_peer_port(); - body["id"] = sender.getIdentifier(); body["totalBytes"] = (Json::UInt64)totalBytes; body["duration"] = (Json::UInt64)totalDuration; body["player"] = isPlayer; + body["ip"] = peerIP; + body["port"] = peerPort; + body["id"] = sessionIdentifier; //执行hook do_http_hook(hook_flowreport,body, nullptr); }); @@ -321,10 +341,10 @@ void installWebHook(){ } ArgsType body; body["regist"] = bRegist; - body["schema"] = schema; - body["vhost"] = vhost; - body["app"] = app; - body["stream"] = stream; + body["schema"] = sender.getSchema(); + body["vhost"] = sender.getVhost(); + body["app"] = sender.getApp(); + body["stream"] = sender.getId(); //执行hook do_http_hook(hook_stream_chaned,body, nullptr); }); @@ -408,7 +428,7 @@ void installWebHook(){ /** * kBroadcastHttpAccess事件触发机制 * 1、根据http请求头查找cookie,找到进入步骤3 - * 2、根据http url参数(如果没有根据ip+端口号)查找cookie,如果还是未找到cookie则进入步骤5 + * 2、根据http url参数查找cookie,如果还是未找到cookie则进入步骤5 * 3、cookie标记是否有权限访问文件,如果有权限,直接返回文件 * 4、cookie中记录的url参数是否跟本次url参数一致,如果一致直接返回客户端错误码 * 5、触发kBroadcastHttpAccess事件 @@ -421,7 +441,7 @@ void installWebHook(){ //如果没有url参数,客户端又不支持cookie,那么会根据ip和端口追踪用户 //追踪用户的目的是为了缓存上次鉴权结果,减少鉴权次数,提高性能 NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){ - if(sender.get_peer_ip() == "127.0.0.1" && args._param_strs == hook_adminparams){ + if(sender.get_peer_ip() == "127.0.0.1" || parser.Params() == hook_adminparams){ //如果是本机或超级管理员访问,那么不做访问鉴权;权限有效期1个小时 invoker("","",60 * 60); return; @@ -456,6 +476,9 @@ void installWebHook(){ invoker(obj["err"].asString(),obj["path"].asString(),obj["second"].asInt()); }); }); + + //汇报服务器重新启动 + reportServerStarted(); } void unInstallWebHook(){ diff --git a/server/main.cpp b/server/main.cpp index fe583513..6d26bbe9 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -38,11 +38,11 @@ #include "Common/config.h" #include "Rtsp/UDPServer.h" #include "Rtsp/RtspSession.h" +#include "Rtp/RtpSession.h" #include "Rtmp/RtmpSession.h" #include "Shell/ShellSession.h" -#include "Rtmp/FlvMuxer.h" -#include "Player/PlayerProxy.h" #include "Http/WebSocketSession.h" +#include "Rtp/UdpRecver.h" #include "WebApi.h" #include "WebHook.h" @@ -58,36 +58,31 @@ namespace mediakit { ////////////HTTP配置/////////// namespace Http { #define HTTP_FIELD "http." -#define HTTP_PORT 80 const string kPort = HTTP_FIELD"port"; -#define HTTPS_PORT 443 const string kSSLPort = HTTP_FIELD"sslport"; onceToken token1([](){ - mINI::Instance()[kPort] = HTTP_PORT; - mINI::Instance()[kSSLPort] = HTTPS_PORT; + mINI::Instance()[kPort] = 80; + mINI::Instance()[kSSLPort] = 443; },nullptr); }//namespace Http ////////////SHELL配置/////////// namespace Shell { #define SHELL_FIELD "shell." -#define SHELL_PORT 9000 const string kPort = SHELL_FIELD"port"; onceToken token1([](){ - mINI::Instance()[kPort] = SHELL_PORT; + mINI::Instance()[kPort] = 9000; },nullptr); } //namespace Shell ////////////RTSP服务器配置/////////// namespace Rtsp { #define RTSP_FIELD "rtsp." -#define RTSP_PORT 554 -#define RTSPS_PORT 322 const string kPort = RTSP_FIELD"port"; const string kSSLPort = RTSP_FIELD"sslport"; onceToken token1([](){ - mINI::Instance()[kPort] = RTSP_PORT; - mINI::Instance()[kSSLPort] = RTSPS_PORT; + mINI::Instance()[kPort] = 554; + mINI::Instance()[kSSLPort] = 332; },nullptr); } //namespace Rtsp @@ -95,12 +90,21 @@ onceToken token1([](){ ////////////RTMP服务器配置/////////// namespace Rtmp { #define RTMP_FIELD "rtmp." -#define RTMP_PORT 1935 const string kPort = RTMP_FIELD"port"; onceToken token1([](){ - mINI::Instance()[kPort] = RTMP_PORT; + mINI::Instance()[kPort] = 1935; },nullptr); } //namespace RTMP + +////////////Rtp代理相关配置/////////// +namespace RtpProxy { +#define RTP_PROXY_FIELD "rtp_proxy." +const string kPort = RTP_PROXY_FIELD"port"; +onceToken token1([](){ + mINI::Instance()[kPort] = 10000; +},nullptr); +} //namespace RtpProxy + } // namespace mediakit @@ -148,7 +152,7 @@ public: Option::ArgRequired,/*该选项后面必须跟值*/ (exeDir() + "ssl.p12").data(),/*该选项默认值*/ false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ - "ssl证书路径,支持p12/pem类型",/*该选项说明文字*/ + "ssl证书文件或文件夹,支持p12/pem类型",/*该选项说明文字*/ nullptr); (*_parser) << Option('t',/*该选项简称,如果是\x00则说明无简称*/ @@ -205,6 +209,8 @@ static void inline listen_shell_input(){ } #endif//!defined(_WIN32) +//全局变量,在WebApi中用于保存配置文件用 +string g_ini_file; int start_main(int argc,char *argv[]) { { @@ -219,7 +225,7 @@ int start_main(int argc,char *argv[]) { bool bDaemon = cmd_main.hasKey("daemon"); LogLevel logLevel = (LogLevel) cmd_main["level"].as(); logLevel = MIN(MAX(logLevel, LTrace), LError); - static string ini_file = cmd_main["config"]; + g_ini_file = cmd_main["config"]; string ssl_file = cmd_main["ssl"]; int threads = cmd_main["threads"]; @@ -233,6 +239,7 @@ int start_main(int argc,char *argv[]) { #endif// #if !defined(_WIN32) + pid_t pid = getpid(); if (bDaemon) { //启动守护进程 System::startDaemon(); @@ -244,14 +251,21 @@ int start_main(int argc,char *argv[]) { //启动异步日志线程 Logger::Instance().setWriter(std::make_shared()); //加载配置文件,如果配置文件不存在就创建一个 - loadIniConfig(ini_file.data()); + loadIniConfig(g_ini_file.data()); - //加载证书,证书包含公钥和私钥 - SSL_Initor::Instance().loadCertificate(ssl_file.data()); - //信任某个自签名证书 - SSL_Initor::Instance().trustCertificate(ssl_file.data()); - //不忽略无效证书证书(例如自签名或过期证书) - SSL_Initor::Instance().ignoreInvalidCertificate(true); + if(!File::is_dir(ssl_file.data())){ + //不是文件夹,加载证书,证书包含公钥和私钥 + SSL_Initor::Instance().loadCertificate(ssl_file.data()); + }else{ + //加载文件夹下的所有证书 + File::scanDir(ssl_file,[](const string &path, bool isDir){ + if(!isDir){ + //最后的一个证书会当做默认证书(客户端ssl握手时未指定主机) + SSL_Initor::Instance().loadCertificate(path.data()); + } + return true; + }); + } uint16_t shellPort = mINI::Instance()[Shell::kPort]; uint16_t rtspPort = mINI::Instance()[Rtsp::kPort]; @@ -259,6 +273,7 @@ int start_main(int argc,char *argv[]) { uint16_t rtmpPort = mINI::Instance()[Rtmp::kPort]; uint16_t httpPort = mINI::Instance()[Http::kPort]; uint16_t httpsPort = mINI::Instance()[Http::kSSLPort]; + uint16_t rtp_proxy = mINI::Instance()[RtpProxy::kPort]; //设置poller线程数,该函数必须在使用ZLToolKit网络相关对象之前调用才能生效 EventPollerPool::setPoolSize(threads); @@ -269,21 +284,48 @@ int start_main(int argc,char *argv[]) { TcpServer::Ptr rtspSrv(new TcpServer()); TcpServer::Ptr rtmpSrv(new TcpServer()); TcpServer::Ptr httpSrv(new TcpServer()); - - shellSrv->start(shellPort); - rtspSrv->start(rtspPort);//默认554 - rtmpSrv->start(rtmpPort);//默认1935 - //http服务器 - httpSrv->start(httpPort);//默认80 - //如果支持ssl,还可以开启https服务器 TcpServer::Ptr httpsSrv(new TcpServer()); - //https服务器,支持websocket - httpsSrv->start(httpsPort);//默认443 - //支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问 TcpServer::Ptr rtspSSLSrv(new TcpServer()); - rtspSSLSrv->start(rtspsPort);//默认322 + +#if defined(ENABLE_RTPPROXY) + UdpRecver recver; + TcpServer::Ptr tcpRtpServer(new TcpServer()); +#endif//defined(ENABLE_RTPPROXY) + + try { + //rtsp服务器,端口默认554 + rtspSrv->start(rtspPort);//默认554 + //rtsps服务器,端口默认322 + rtspSSLSrv->start(rtspsPort); + //rtmp服务器,端口默认1935 + rtmpSrv->start(rtmpPort); + //http服务器,端口默认80 + httpSrv->start(httpPort); + //https服务器,端口默认443 + httpsSrv->start(httpsPort); + //telnet远程调试服务器 + shellSrv->start(shellPort); + +#if defined(ENABLE_RTPPROXY) + //创建rtp udp服务器 + recver.initSock(rtp_proxy); + //创建rtp tcp服务器 + tcpRtpServer->start(rtp_proxy); +#endif//defined(ENABLE_RTPPROXY) + + }catch (std::exception &ex){ + WarnL << "端口占用或无权限:" << ex.what() << endl; + ErrorL << "程序启动失败,请修改配置文件中端口号后重试!" << endl; + sleep(1); +#if !defined(_WIN32) + if(pid != getpid()){ + kill(pid,SIGINT); + } +#endif + return -1; + } installWebApi(); InfoL << "已启动http api 接口"; @@ -306,12 +348,13 @@ int start_main(int argc,char *argv[]) { });// 设置退出信号 #if !defined(_WIN32) - signal(SIGHUP, [](int) { mediakit::loadIniConfig(ini_file.data()); }); + signal(SIGHUP, [](int) { mediakit::loadIniConfig(g_ini_file.data()); }); #endif sem.wait(); } unInstallWebApi(); unInstallWebHook(); + Recorder::stopAll(); //休眠1秒再退出,防止资源释放顺序错误 InfoL << "程序退出中,请等待..."; sleep(1); diff --git a/src/Common/Device.cpp b/src/Common/Device.cpp index d98683e1..58fd53cb 100644 --- a/src/Common/Device.cpp +++ b/src/Common/Device.cpp @@ -32,6 +32,7 @@ #include "Util/TimeTicker.h" #include "Extension/AAC.h" #include "Extension/H264.h" +#include "Extension/H265.h" using namespace toolkit; @@ -103,7 +104,39 @@ void DevChannel::inputH264(const char* pcData, int iDataLen, uint32_t dts,uint32 } else { prefixeSize = 0; } - inputFrame(std::make_shared((char *)pcData,iDataLen,dts,pts,prefixeSize)); + + H264Frame::Ptr frame = std::make_shared(); + frame->_dts = dts; + frame->_pts = pts; + frame->_buffer.assign("\x00\x00\x00\x01",4); + frame->_buffer.append(pcData + prefixeSize, iDataLen - prefixeSize); + frame->_prefix_size = 4; + inputFrame(frame); +} + +void DevChannel::inputH265(const char* pcData, int iDataLen, uint32_t dts,uint32_t pts) { + if(dts == 0){ + dts = (uint32_t)_aTicker[0].elapsedTime(); + } + if(pts == 0){ + pts = dts; + } + int prefixeSize; + if (memcmp("\x00\x00\x00\x01", pcData, 4) == 0) { + prefixeSize = 4; + } else if (memcmp("\x00\x00\x01", pcData, 3) == 0) { + prefixeSize = 3; + } else { + prefixeSize = 0; + } + + H265Frame::Ptr frame = std::make_shared(); + frame->_dts = dts; + frame->_pts = pts; + frame->_buffer.assign("\x00\x00\x00\x01",4); + frame->_buffer.append(pcData + prefixeSize, iDataLen - prefixeSize); + frame->_prefix_size = 4; + inputFrame(frame); } void DevChannel::inputAAC(const char* pcData, int iDataLen, uint32_t uiStamp,bool withAdtsHeader) { @@ -135,6 +168,11 @@ void DevChannel::initVideo(const VideoInfo& info) { addTrack(std::make_shared()); } +void DevChannel::initH265Video(const VideoInfo &info){ + _video = std::make_shared(info); + addTrack(std::make_shared()); +} + void DevChannel::initAudio(const AudioInfo& info) { _audio = std::make_shared(info); addTrack(std::make_shared()); diff --git a/src/Common/Device.h b/src/Common/Device.h index 7e5e7684..a8c63daf 100644 --- a/src/Common/Device.h +++ b/src/Common/Device.h @@ -88,6 +88,12 @@ public: */ void initVideo(const VideoInfo &info); + /** + * 初始化h265视频Track + * @param info + */ + void initH265Video(const VideoInfo &info); + /** * 初始化aac音频Track * 相当于MultiMediaSourceMuxer::addTrack(AACTrack::Ptr ); @@ -104,6 +110,15 @@ public: */ void inputH264(const char *pcData, int iDataLen, uint32_t dts,uint32_t pts = 0); + /** + * 输入265帧 + * @param pcData 265单帧数据指针 + * @param iDataLen 数据指针长度 + * @param dts 解码时间戳,单位毫秒;等于0时内部会自动生成时间戳 + * @param pts 播放时间戳,单位毫秒;等于0时内部会赋值为dts + */ + void inputH265(const char *pcData, int iDataLen, uint32_t dts,uint32_t pts = 0); + /** * 输入可能带adts头的aac帧 * @param pcDataWithAdts 可能带adts头的aac帧 diff --git a/src/Common/MediaSink.cpp b/src/Common/MediaSink.cpp index 4d74e2c9..9d224ac2 100644 --- a/src/Common/MediaSink.cpp +++ b/src/Common/MediaSink.cpp @@ -26,7 +26,11 @@ #include "MediaSink.h" //最多等待未初始化的Track 10秒,超时之后会忽略未初始化的Track -#define MAX_WAIT_MS 10000 +#define MAX_WAIT_MS_READY 10000 + +//如果添加Track,最多等待3秒 +#define MAX_WAIT_MS_ADD_TRACK 3000 + namespace mediakit{ @@ -34,98 +38,129 @@ void MediaSink::addTrack(const Track::Ptr &track_in) { lock_guard lck(_mtx); //克隆Track,只拷贝其数据,不拷贝其数据转发关系 auto track = track_in->clone(); - auto codec_id = track->getCodecId(); _track_map[codec_id] = track; - auto lam = [this,track](){ + _allTrackReady = false; + _trackReadyCallback[codec_id] = [this, track]() { onTrackReady(track); }; - if(track->ready()){ - lam(); - }else{ - _anyTrackUnReady = true; - _allTrackReady = false; - _trackReadyCallback[codec_id] = lam; - _ticker.resetTime(); - } + _ticker.resetTime(); - weak_ptr weakSelf = shared_from_this(); - track->addDelegate(std::make_shared([weakSelf](const Frame::Ptr &frame){ - auto strongSelf = weakSelf.lock(); - if(!strongSelf){ - return; - } - if(!strongSelf->_anyTrackUnReady){ - strongSelf->onTrackFrame(frame); + track->addDelegate(std::make_shared([this](const Frame::Ptr &frame) { + if (_allTrackReady) { + onTrackFrame(frame); } })); } void MediaSink::resetTracks() { lock_guard lck(_mtx); - _anyTrackUnReady = false; _allTrackReady = false; _track_map.clear(); _trackReadyCallback.clear(); _ticker.resetTime(); + _max_track_size = 2; } void MediaSink::inputFrame(const Frame::Ptr &frame) { lock_guard lck(_mtx); - auto codec_id = frame->getCodecId(); - auto it = _track_map.find(codec_id); + auto it = _track_map.find(frame->getCodecId()); if (it == _track_map.end()) { return; } it->second->inputFrame(frame); + checkTrackIfReady(it->second); +} - if(!_allTrackReady && !_trackReadyCallback.empty() && it->second->ready()){ - //Track由未就绪状态转换成就绪状态,我们就触发onTrackReady回调 - auto it_callback = _trackReadyCallback.find(codec_id); - if(it_callback != _trackReadyCallback.end()){ - it_callback->second(); - _trackReadyCallback.erase(it_callback); - } +void MediaSink::checkTrackIfReady_l(const Track::Ptr &track){ + //Track由未就绪状态转换成就绪状态,我们就触发onTrackReady回调 + auto it_callback = _trackReadyCallback.find(track->getCodecId()); + if (it_callback != _trackReadyCallback.end() && track->ready()) { + it_callback->second(); + _trackReadyCallback.erase(it_callback); } +} - if(!_allTrackReady && (_trackReadyCallback.empty() || _ticker.elapsedTime() > MAX_WAIT_MS)){ - _allTrackReady = true; - _anyTrackUnReady = false; - if(!_trackReadyCallback.empty()){ - //这是超时强制忽略未准备好的Track - _trackReadyCallback.clear(); - //移除未准备好的Track - for(auto it = _track_map.begin() ; it != _track_map.end() ; ){ - if(!it->second->ready()){ - it = _track_map.erase(it); - continue; - } - ++it; +void MediaSink::checkTrackIfReady(const Track::Ptr &track){ + if (!_allTrackReady && !_trackReadyCallback.empty()) { + if (track) { + checkTrackIfReady_l(track); + } else { + for (auto &pr : _track_map) { + checkTrackIfReady_l(pr.second); } } + } - if(!_track_map.empty()){ - //最少有一个有效的Track - onAllTrackReady(); + if(!_allTrackReady){ + if(_ticker.elapsedTime() > MAX_WAIT_MS_READY){ + //如果超过规定时间,那么不再等待并忽略未准备好的Track + emitAllTrackReady(); + return; + } + + if(!_trackReadyCallback.empty()){ + //在超时时间内,如果存在未准备好的Track,那么继续等待 + return; + } + + if(_track_map.size() == _max_track_size){ + //如果已经添加了音视频Track,并且不存在未准备好的Track,那么说明所有Track都准备好了 + emitAllTrackReady(); + return; + } + + if(_track_map.size() == 1 && _ticker.elapsedTime() > MAX_WAIT_MS_ADD_TRACK){ + //如果只有一个Track,那么在该Track添加后,我们最多还等待若干时间(可能后面还会添加Track) + emitAllTrackReady(); + return; } } } -bool MediaSink::isAllTrackReady() const { - return _allTrackReady; +void MediaSink::addTrackCompleted(){ + { + lock_guard lck(_mtx); + _max_track_size = _track_map.size(); + } + checkTrackIfReady(nullptr); } -Track::Ptr MediaSink::getTrack(TrackType type,bool trackReady) const { +void MediaSink::emitAllTrackReady() { + if (_allTrackReady) { + return; + } + + if (!_trackReadyCallback.empty()) { + //这是超时强制忽略未准备好的Track + _trackReadyCallback.clear(); + //移除未准备好的Track + for (auto it = _track_map.begin(); it != _track_map.end();) { + if (!it->second->ready()) { + it = _track_map.erase(it); + continue; + } + ++it; + } + } + + if (!_track_map.empty()) { + //最少有一个有效的Track + _allTrackReady = true; + onAllTrackReady(); + } +} + +vector MediaSink::getTracks(bool trackReady) const{ + vector ret; lock_guard lck(_mtx); for (auto &pr : _track_map){ - if(pr.second->getTrackType() == type){ - if(!trackReady){ - return pr.second; - } - return pr.second->ready() ? pr.second : nullptr; + if(trackReady && !pr.second->ready()){ + continue; } + ret.emplace_back(pr.second); } - return nullptr; + return std::move(ret); } diff --git a/src/Common/MediaSink.h b/src/Common/MediaSink.h index 8798590d..c948aaa1 100644 --- a/src/Common/MediaSink.h +++ b/src/Common/MediaSink.h @@ -38,11 +38,31 @@ using namespace toolkit; namespace mediakit{ +class MediaSinkInterface : public FrameWriterInterface { +public: + typedef std::shared_ptr Ptr; + + MediaSinkInterface(){}; + virtual ~MediaSinkInterface(){}; + + /** + * 添加track,内部会调用Track的clone方法 + * 只会克隆sps pps这些信息 ,而不会克隆Delegate相关关系 + * @param track + */ + virtual void addTrack(const Track::Ptr & track) = 0; + + /** + * 重置track + */ + virtual void resetTracks() = 0; +}; + /** * 该类的作用是等待Track ready()返回true也就是就绪后再通知派生类进行下一步的操作 * 目的是输入Frame前由Track截取处理下,以便获取有效的信息(譬如sps pps aa_cfg) */ -class MediaSink : public FrameWriterInterface , public std::enable_shared_from_this{ +class MediaSink : public MediaSinkInterface , public TrackSource{ public: typedef std::shared_ptr Ptr; MediaSink(){} @@ -59,26 +79,25 @@ public: * 只会克隆sps pps这些信息 ,而不会克隆Delegate相关关系 * @param track */ - virtual void addTrack(const Track::Ptr & track); + void addTrack(const Track::Ptr & track) override; + + /** + * 添加Track完毕,如果是单Track,会最多等待3秒才会触发onAllTrackReady + * 这样会增加生成流的延时,如果添加了音视频双Track,那么可以不调用此方法 + * 否则为了降低流注册延时,请手动调用此方法 + */ + void addTrackCompleted(); /** * 重置track */ - virtual void resetTracks(); + void resetTracks() override; /** - * 全部Track是否都准备好了 - * @return - */ - bool isAllTrackReady() const; - - /** - * 获取特定类型的Track - * @param type track类型 + * 获取所有Track * @param trackReady 是否获取已经准备好的Track - * @return */ - Track::Ptr getTrack(TrackType type,bool trackReady = true) const; + vector getTracks(bool trackReady = true) const override ; protected: /** * 某track已经准备好,其ready()状态返回true, @@ -97,13 +116,24 @@ protected: * @param frame */ virtual void onTrackFrame(const Frame::Ptr &frame) {}; +private: + /** + * 触发onAllTrackReady事件 + */ + void emitAllTrackReady(); + + /** + * 检查track是否准备完毕 + */ + void checkTrackIfReady(const Track::Ptr &track); + void checkTrackIfReady_l(const Track::Ptr &track); private: mutable recursive_mutex _mtx; map _track_map; map > _trackReadyCallback; bool _allTrackReady = false; - bool _anyTrackUnReady = false; Ticker _ticker; + int _max_track_size = 2; }; diff --git a/src/Common/MediaSource.cpp b/src/Common/MediaSource.cpp index cb668f37..75d6c269 100644 --- a/src/Common/MediaSource.cpp +++ b/src/Common/MediaSource.cpp @@ -26,7 +26,7 @@ #include "MediaSource.h" -#include "MediaFile/MediaReader.h" +#include "Record/MP4Reader.h" #include "Util/util.h" #include "Network/sockutil.h" #include "Network/TcpSession.h" @@ -38,17 +38,145 @@ namespace mediakit { recursive_mutex MediaSource::g_mtxMediaSrc; MediaSource::SchemaVhostAppStreamMap MediaSource::g_mapMediaSrc; +MediaSource::MediaSource(const string &strSchema, const string &strVhost, const string &strApp, const string &strId) : + _strSchema(strSchema), _strApp(strApp), _strId(strId) { + if (strVhost.empty()) { + _strVhost = DEFAULT_VHOST; + } else { + _strVhost = strVhost; + } +} -void MediaSource::findAsync(const MediaInfo &info, - const std::shared_ptr &session, - bool retry, - const function &cb){ +MediaSource::~MediaSource() { + unregist(); +} - auto src = MediaSource::find(info._schema, - info._vhost, - info._app, - info._streamid, - true); +const string& MediaSource::getSchema() const { + return _strSchema; +} + +const string& MediaSource::getVhost() const { + return _strVhost; +} + +const string& MediaSource::getApp() const { + //获取该源的id + return _strApp; +} + +const string& MediaSource::getId() const { + return _strId; +} + +vector MediaSource::getTracks(bool trackReady) const { + auto strongPtr = _track_source.lock(); + if(strongPtr){ + return strongPtr->getTracks(trackReady); + } + return vector(); +} + +void MediaSource::setTrackSource(const std::weak_ptr &track_src) { + _track_source = track_src; + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaResetTracks, *this); +} + +void MediaSource::setListener(const std::weak_ptr &listener){ + _listener = listener; +} + +const std::weak_ptr& MediaSource::getListener() const{ + return _listener; +} + +int MediaSource::totalReaderCount(){ + auto listener = _listener.lock(); + if(!listener){ + return readerCount(); + } + return listener->totalReaderCount(*this); +} +bool MediaSource::seekTo(uint32_t ui32Stamp) { + auto listener = _listener.lock(); + if(!listener){ + return false; + } + return listener->seekTo(*this,ui32Stamp); +} + +bool MediaSource::close(bool force) { + auto listener = _listener.lock(); + if(!listener){ + return false; + } + return listener->close(*this,force); +} + +void MediaSource::onNoneReader(){ + auto listener = _listener.lock(); + if(!listener){ + return; + } + listener->onNoneReader(*this); +} + +void MediaSource::for_each_media(const function &cb) { + lock_guard lock(g_mtxMediaSrc); + for (auto &pr0 : g_mapMediaSrc) { + for (auto &pr1 : pr0.second) { + for (auto &pr2 : pr1.second) { + for (auto &pr3 : pr2.second) { + auto src = pr3.second.lock(); + if(src){ + cb(src); + } + } + } + } + } +} + +template +static bool searchMedia(MAP &map, const string &schema, const string &vhost, const string &app, const string &id, FUNC &&func) { + auto it0 = map.find(schema); + if (it0 == map.end()) { + //未找到协议 + return false; + } + auto it1 = it0->second.find(vhost); + if (it1 == it0->second.end()) { + //未找到vhost + return false; + } + auto it2 = it1->second.find(app); + if (it2 == it1->second.end()) { + //未找到app + return false; + } + auto it3 = it2->second.find(id); + if (it3 == it2->second.end()) { + //未找到streamId + return false; + } + return func(it0, it1, it2, it3); +} + +template +static void eraseIfEmpty(MAP &map, IT0 it0, IT1 it1, IT2 it2) { + if (it2->second.empty()) { + it1->second.erase(it2); + if (it1->second.empty()) { + it0->second.erase(it1); + if (it0->second.empty()) { + map.erase(it0); + } + } + } +}; + +void findAsync_l(const MediaInfo &info, const std::shared_ptr &session, bool retry, + const function &cb){ + auto src = MediaSource::find(info._schema, info._vhost, info._app, info._streamid, true); if(src || !retry){ cb(src); return; @@ -81,7 +209,11 @@ void MediaSource::findAsync(const MediaInfo &info, return; } - if(!bRegist || schema != info._schema || vhost != info._vhost || app != info._app ||stream != info._streamid){ + if (!bRegist || + sender.getSchema() != info._schema || + sender.getVhost() != info._vhost || + sender.getApp() != info._app || + sender.getId() != info._streamid) { //不是自己感兴趣的事件,忽略之 return; } @@ -99,18 +231,18 @@ void MediaSource::findAsync(const MediaInfo &info, } DebugL << "收到媒体注册事件,回复播放器:" << info._schema << "/" << info._vhost << "/" << info._app << "/" << info._streamid; //再找一遍媒体源,一般能找到 - findAsync(info,strongSession,false,cb); + findAsync_l(info,strongSession,false,cb); }, false); }; //监听媒体注册事件 NoticeCenter::Instance().addListener(listener_tag, Broadcast::kBroadcastMediaChanged, onRegist); } -MediaSource::Ptr MediaSource::find( - const string &schema, - const string &vhost_tmp, - const string &app, - const string &id, - bool bMake) { + +void MediaSource::findAsync(const MediaInfo &info, const std::shared_ptr &session,const function &cb){ + return findAsync_l(info, session, true, cb); +} + +MediaSource::Ptr MediaSource::find(const string &schema, const string &vhost_tmp, const string &app, const string &id, bool bMake) { string vhost = vhost_tmp; if(vhost.empty()){ vhost = DEFAULT_VHOST; @@ -121,26 +253,28 @@ MediaSource::Ptr MediaSource::find( vhost = DEFAULT_VHOST; } - lock_guard lock(g_mtxMediaSrc); MediaSource::Ptr ret; - //查找某一媒体源,找到后返回 - searchMedia(schema, vhost, app, id, - [&](SchemaVhostAppStreamMap::iterator &it0 , - VhostAppStreamMap::iterator &it1, - AppStreamMap::iterator &it2, - StreamMap::iterator &it3){ - ret = it3->second.lock(); - if(!ret){ - //该对象已经销毁 - it2->second.erase(it3); - eraseIfEmpty(it0,it1,it2); - return false; - } - return true; - }); + { + lock_guard lock(g_mtxMediaSrc); + //查找某一媒体源,找到后返回 + searchMedia(g_mapMediaSrc, schema, vhost, app, id, [&](SchemaVhostAppStreamMap::iterator &it0, + VhostAppStreamMap::iterator &it1, + AppStreamMap::iterator &it2, + StreamMap::iterator &it3) { + ret = it3->second.lock(); + if (!ret) { + //该对象已经销毁 + it2->second.erase(it3); + eraseIfEmpty(g_mapMediaSrc, it0, it1, it2); + return false; + } + return true; + }); + } + if(!ret && bMake){ //未查找媒体源,则创建一个 - ret = MediaReader::onMakeMediaSource(schema, vhost,app,id); + ret = MP4Reader::onMakeMediaSource(schema, vhost,app,id); } return ret; } @@ -155,43 +289,39 @@ void MediaSource::regist() { g_mapMediaSrc[_strSchema][_strVhost][_strApp][_strId] = shared_from_this(); } InfoL << _strSchema << " " << _strVhost << " " << _strApp << " " << _strId; - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, - true, - _strSchema, - _strVhost, - _strApp, - _strId, - *this); + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, true, *this); } + +//反注册该源 bool MediaSource::unregist() { - //反注册该源 - lock_guard lock(g_mtxMediaSrc); - return searchMedia(_strSchema, _strVhost, _strApp, _strId, [&](SchemaVhostAppStreamMap::iterator &it0 , - VhostAppStreamMap::iterator &it1, - AppStreamMap::iterator &it2, - StreamMap::iterator &it3){ - auto strongMedia = it3->second.lock(); - if(strongMedia && this != strongMedia.get()){ - //不是自己,不允许反注册 - return false; - } - it2->second.erase(it3); - eraseIfEmpty(it0,it1,it2); - unregisted(); - return true; - }); -} -void MediaSource::unregisted(){ - InfoL << "" << _strSchema << " " << _strVhost << " " << _strApp << " " << _strId; - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, - false, - _strSchema, - _strVhost, - _strApp, - _strId, - *this); + bool ret; + { + lock_guard lock(g_mtxMediaSrc); + ret = searchMedia(g_mapMediaSrc, _strSchema, _strVhost, _strApp, _strId, + [&](SchemaVhostAppStreamMap::iterator &it0, + VhostAppStreamMap::iterator &it1, + AppStreamMap::iterator &it2, + StreamMap::iterator &it3) { + auto strongMedia = it3->second.lock(); + if (strongMedia && this != strongMedia.get()) { + //不是自己,不允许反注册 + return false; + } + it2->second.erase(it3); + eraseIfEmpty(g_mapMediaSrc, it0, it1, it2); + return true; + }); + } + + if(ret){ + InfoL << "" << _strSchema << " " << _strVhost << " " << _strApp << " " << _strId; + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, false, *this); + } + return ret; } +/////////////////////////////////////MediaInfo////////////////////////////////////// + void MediaInfo::parse(const string &url){ //string url = "rtsp://127.0.0.1:8554/live/id?key=val&a=1&&b=2&vhost=vhost.com"; auto schema_pos = url.find("://"); @@ -226,9 +356,9 @@ void MediaInfo::parse(const string &url){ if(pos != string::npos){ _streamid = steamid.substr(0,pos); _param_strs = steamid.substr(pos + 1); - _params = Parser::parseArgs(_param_strs); - if(_params.find(VHOST_KEY) != _params.end()){ - _vhost = _params[VHOST_KEY]; + auto params = Parser::parseArgs(_param_strs); + if(params.find(VHOST_KEY) != params.end()){ + _vhost = params[VHOST_KEY]; } } else{ _streamid = steamid; @@ -241,6 +371,8 @@ void MediaInfo::parse(const string &url){ } } +/////////////////////////////////////MediaSourceEvent////////////////////////////////////// + void MediaSourceEvent::onNoneReader(MediaSource &sender){ //没有任何读取器消费该源,表明该源可以关闭了 WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId(); diff --git a/src/Common/MediaSource.h b/src/Common/MediaSource.h index a54bc8d8..e6920050 100644 --- a/src/Common/MediaSource.h +++ b/src/Common/MediaSource.h @@ -45,7 +45,7 @@ using namespace toolkit; namespace toolkit{ class TcpSession; -}//namespace toolkit +}// namespace toolkit namespace mediakit { @@ -54,33 +54,33 @@ class MediaSourceEvent{ public: MediaSourceEvent(){}; virtual ~MediaSourceEvent(){}; -public: + + // 通知拖动进度条 virtual bool seekTo(MediaSource &sender,uint32_t ui32Stamp){ - //拖动进度条 return false; } + // 通知其停止推流 virtual bool close(MediaSource &sender,bool force) { - //通知其停止推流 return false; } + // 通知无人观看 virtual void onNoneReader(MediaSource &sender); + + // 观看总人数 + virtual int totalReaderCount(MediaSource &sender) = 0; }; +/** + * 解析url获取媒体相关信息 + */ class MediaInfo{ public: MediaInfo(){} - MediaInfo(const string &url){ - parse(url); - } ~MediaInfo(){} - + MediaInfo(const string &url){ parse(url); } void parse(const string &url); - - string &operator[](const string &key){ - return _params[key]; - } public: string _schema; string _host; @@ -88,11 +88,13 @@ public: string _vhost; string _app; string _streamid; - StrCaseMap _params; string _param_strs; }; -class MediaSource: public enable_shared_from_this { +/** + * 媒体源,任何rtsp/rtmp的直播流都源自该对象 + */ +class MediaSource: public TrackSource, public enable_shared_from_this { public: typedef std::shared_ptr Ptr; typedef unordered_map > StreamMap; @@ -100,155 +102,64 @@ public: typedef unordered_map VhostAppStreamMap; typedef unordered_map SchemaVhostAppStreamMap; - MediaSource(const string &strSchema, - const string &strVhost, - const string &strApp, - const string &strId) : - _strSchema(strSchema), - _strApp(strApp), - _strId(strId) { - if(strVhost.empty()){ - _strVhost = DEFAULT_VHOST; - }else{ - _strVhost = strVhost; - } - } - virtual ~MediaSource() { - unregist(); - } + MediaSource(const string &strSchema, const string &strVhost, const string &strApp, const string &strId) ; + virtual ~MediaSource() ; - static Ptr find(const string &schema, - const string &vhost, - const string &app, - const string &id, - bool bMake = true) ; + // 获取协议类型 + const string& getSchema() const; + // 虚拟主机 + const string& getVhost() const; + // 应用名 + const string& getApp() const; + // 流id + const string& getId() const; - static void findAsync(const MediaInfo &info, - const std::shared_ptr &session, - bool retry, - const function &cb); + // 设置TrackSource + void setTrackSource(const std::weak_ptr &track_src); + // 获取所有Track + vector getTracks(bool trackReady = true) const override; - const string& getSchema() const { - return _strSchema; - } - const string& getVhost() const { - return _strVhost; - } - const string& getApp() const { - //获取该源的id - return _strApp; - } - const string& getId() const { - return _strId; - } + // 设置监听者 + virtual void setListener(const std::weak_ptr &listener); + // 获取监听者 + const std::weak_ptr& getListener() const; - bool seekTo(uint32_t ui32Stamp) { - auto listener = _listener.lock(); - if(!listener){ - return false; - } - return listener->seekTo(*this,ui32Stamp); - } - - virtual uint32_t getTimeStamp(TrackType trackType) = 0; - - bool close(bool force) { - auto listener = _listener.lock(); - if(!listener){ - return false; - } - return listener->close(*this,force); - } - - void onNoneReader(){ - auto listener = _listener.lock(); - if(!listener){ - return; - } - listener->onNoneReader(*this); - } - virtual void setListener(const std::weak_ptr &listener){ - _listener = listener; - } - - template - static void for_each_media(FUN && fun){ - lock_guard lock(g_mtxMediaSrc); - for (auto &pr0 : g_mapMediaSrc){ - for(auto &pr1 : pr0.second){ - for(auto &pr2 : pr1.second){ - for(auto &pr3 : pr2.second){ - fun(pr0.first,pr1.first,pr2.first,pr3.first,pr3.second.lock()); - } - } - } - } - } + // 本协议获取观看者个数,可能返回本协议的观看人数,也可能返回总人数 virtual int readerCount() = 0; + // 观看者个数,包括(hls/rtsp/rtmp) + virtual int totalReaderCount(); - /** - * 获取track - * @return - */ - virtual vector getTracks(bool trackReady) const{ - return vector(0); - } + // 获取流当前时间戳 + virtual uint32_t getTimeStamp(TrackType trackType) { return 0; }; + // 设置时间戳 + virtual void setTimeStamp(uint32_t uiStamp) {}; + + // 拖动进度条 + bool seekTo(uint32_t ui32Stamp); + // 关闭该流 + bool close(bool force); + // 该流无人观看 + void onNoneReader(); + + // 同步查找流 + static Ptr find(const string &schema, const string &vhost, const string &app, const string &id, bool bMake = true) ; + // 异步查找流 + static void findAsync(const MediaInfo &info, const std::shared_ptr &session, const function &cb); + // 遍历所有流 + static void for_each_media(const function &cb); protected: void regist() ; bool unregist() ; private: - template - static bool searchMedia(const string &schema, - const string &vhost, - const string &app, - const string &id, - FUN &&fun){ - auto it0 = g_mapMediaSrc.find(schema); - if (it0 == g_mapMediaSrc.end()) { - //未找到协议 - return false; - } - auto it1 = it0->second.find(vhost); - if(it1 == it0->second.end()){ - //未找到vhost - return false; - } - auto it2 = it1->second.find(app); - if(it2 == it1->second.end()){ - //未找到app - return false; - } - auto it3 = it2->second.find(id); - if(it3 == it2->second.end()){ - //未找到streamId - return false; - } - return fun(it0,it1,it2,it3); - } - template - static void eraseIfEmpty(IT0 it0,IT1 it1,IT2 it2){ - if(it2->second.empty()){ - it1->second.erase(it2); - if(it1->second.empty()){ - it0->second.erase(it1); - if(it0->second.empty()){ - g_mapMediaSrc.erase(it0); - } - } - } - }; - - void unregisted(); -protected: + string _strSchema; + string _strVhost; + string _strApp; + string _strId; std::weak_ptr _listener; -private: - string _strSchema;//协议类型 - string _strVhost; //vhost - string _strApp; //媒体app - string _strId; //媒体id - static SchemaVhostAppStreamMap g_mapMediaSrc; //静态的媒体源表 - static recursive_mutex g_mtxMediaSrc; //访问静态的媒体源表的互斥锁 + weak_ptr _track_source; + static SchemaVhostAppStreamMap g_mapMediaSrc; + static recursive_mutex g_mtxMediaSrc; }; } /* namespace mediakit */ diff --git a/src/Common/MultiMediaSourceMuxer.h b/src/Common/MultiMediaSourceMuxer.h index b466ffec..0ec1f2ed 100644 --- a/src/Common/MultiMediaSourceMuxer.h +++ b/src/Common/MultiMediaSourceMuxer.h @@ -29,71 +29,57 @@ #include "Rtsp/RtspMediaSourceMuxer.h" #include "Rtmp/RtmpMediaSourceMuxer.h" -#include "MediaFile/MediaRecorder.h" +#include "Record/Recorder.h" +#include "Record/HlsMediaSource.h" +#include "Record/HlsRecorder.h" -class MultiMediaSourceMuxer : public FrameWriterInterface{ +class MultiMediaSourceMuxer : public MediaSink , public std::enable_shared_from_this{ public: - typedef std::shared_ptr Ptr; + class Listener{ + public: + Listener() = default; + virtual ~Listener() = default; + virtual void onAllTrackReady() = 0; + }; - MultiMediaSourceMuxer(const string &vhost, - const string &strApp, - const string &strId, - float dur_sec = 0.0, - bool bEanbleRtsp = true, - bool bEanbleRtmp = true, - bool bEanbleHls = true, - bool bEnableMp4 = false - ){ - if (bEanbleRtmp) { - _rtmp = std::make_shared(vhost, strApp, strId, std::make_shared(dur_sec)); + typedef std::shared_ptr Ptr; + MultiMediaSourceMuxer(const string &vhost, const string &app, const string &stream, float dur_sec = 0.0, + bool enable_rtsp = true, bool enable_rtmp = true, bool enable_hls = true, bool enable_mp4 = false){ + if (enable_rtmp) { + _rtmp = std::make_shared(vhost, app, stream, std::make_shared(dur_sec)); } - if (bEanbleRtsp) { - _rtsp = std::make_shared(vhost, strApp, strId, std::make_shared(dur_sec)); + if (enable_rtsp) { + _rtsp = std::make_shared(vhost, app, stream, std::make_shared(dur_sec)); } - _record = std::make_shared(vhost,strApp,strId,bEanbleHls,bEnableMp4); + + if(enable_hls){ + Recorder::startRecord(Recorder::type_hls,vhost, app, stream, "", true, false); + } + + if(enable_mp4){ + Recorder::startRecord(Recorder::type_mp4,vhost, app, stream, "", true, false); + } + + _get_hls_media_source = [vhost,app,stream](){ + auto recorder = dynamic_pointer_cast(Recorder::getRecorder(Recorder::type_hls,vhost,app,stream)); + if(recorder){ + return recorder->getMediaSource(); + } + return MediaSource::Ptr(); + }; } virtual ~MultiMediaSourceMuxer(){} - - /** - * 添加音视频媒体 - * @param track 媒体描述 - */ - void addTrack(const Track::Ptr & track) { - if(_rtmp){ - _rtmp->addTrack(track); - } - if(_rtsp){ - _rtsp->addTrack(track); - } - _record->addTrack(track); - } - /** * 重置音视频媒体 */ - void resetTracks() { + void resetTracks() override{ if(_rtmp){ _rtmp->resetTracks(); } if(_rtsp){ _rtsp->resetTracks(); } - _record->resetTracks(); - } - - /** - * 写入帧数据然后打包rtmp - * @param frame 帧数据 - */ - void inputFrame(const Frame::Ptr &frame) override { - if(_rtmp) { - _rtmp->inputFrame(frame); - } - if(_rtsp) { - _rtsp->inputFrame(frame); - } - _record->inputFrame(frame); } /** @@ -104,28 +90,93 @@ public: if(_rtmp) { _rtmp->setListener(listener); } + if(_rtsp) { _rtsp->setListener(listener); } + + auto hls_src = _get_hls_media_source(); + if(hls_src){ + hls_src->setListener(listener); + } } /** * 返回总的消费者个数 * @return */ - int readerCount() const{ - return (_rtsp ? _rtsp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0); + int totalReaderCount() const{ + auto hls_src = _get_hls_media_source(); + return (_rtsp ? _rtsp->readerCount() : 0) + (_rtmp ? _rtmp->readerCount() : 0) + (hls_src ? hls_src->readerCount() : 0); } void setTimeStamp(uint32_t stamp){ + if(_rtmp){ + _rtmp->setTimeStamp(stamp); + } + if(_rtsp){ _rtsp->setTimeStamp(stamp); } } + + void setTrackListener(Listener *listener){ + _listener = listener; + } +protected: + /** + * 添加音视频媒体 + * @param track 媒体描述 + */ + void onTrackReady(const Track::Ptr & track) override { + if(_rtmp){ + _rtmp->addTrack(track); + } + if(_rtsp){ + _rtsp->addTrack(track); + } + } + + /** + * 写入帧数据然后打包rtmp + * @param frame 帧数据 + */ + void onTrackFrame(const Frame::Ptr &frame) override { + if(_rtmp) { + _rtmp->inputFrame(frame); + } + if(_rtsp) { + _rtsp->inputFrame(frame); + } + } + + /** + * 所有Track都准备就绪,触发媒体注册事件 + */ + void onAllTrackReady() override{ + if(_rtmp) { + _rtmp->setTrackSource(shared_from_this()); + _rtmp->onAllTrackReady(); + } + if(_rtsp) { + _rtsp->setTrackSource(shared_from_this()); + _rtsp->onAllTrackReady(); + } + + auto hls_src = _get_hls_media_source(); + if(hls_src){ + hls_src->setTrackSource(shared_from_this()); + } + + if(_listener){ + _listener->onAllTrackReady(); + } + } private: RtmpMediaSourceMuxer::Ptr _rtmp; RtspMediaSourceMuxer::Ptr _rtsp; - MediaRecorder::Ptr _record; + Listener *_listener = nullptr; + function _get_hls_media_source; }; diff --git a/src/Common/Parser.h b/src/Common/Parser.h index eaa1874d..bd15d0b8 100644 --- a/src/Common/Parser.h +++ b/src/Common/Parser.h @@ -28,27 +28,26 @@ class StrCaseMap : public multimap{ StrCaseMap() = default; ~StrCaseMap() = default; - template - string &operator[](K &&k){ - auto it = find(std::forward(k)); + string &operator[](const string &k){ + auto it = find(k); if(it == end()){ - it = Super::emplace(std::forward(k),""); + it = Super::emplace(k,""); } return it->second; } - template - void emplace(K &&k , V &&v) { - auto it = find(std::forward(k)); + template + void emplace(const string &k, V &&v) { + auto it = find(k); if(it != end()){ return; } - Super::emplace(std::forward(k),std::forward(v)); + Super::emplace(k,std::forward(v)); } - template - void emplace_force(K &&k , V &&v) { - Super::emplace(std::forward(k),std::forward(v)); + template + void emplace_force(const string k , V &&v) { + Super::emplace(k,std::forward(v)); } }; diff --git a/src/MediaFile/Stamp.cpp b/src/Common/Stamp.cpp similarity index 55% rename from src/MediaFile/Stamp.cpp rename to src/Common/Stamp.cpp index ab77a1bf..a0017752 100644 --- a/src/MediaFile/Stamp.cpp +++ b/src/Common/Stamp.cpp @@ -26,12 +26,18 @@ #include "Stamp.h" +#define MAX_DELTA_STAMP 1000 +#define MAX_CTS 500 +#define ABS(x) ((x) > 0 ? (x) : (-x)) + namespace mediakit { int64_t DeltaStamp::deltaStamp(int64_t stamp) { if(!_last_stamp){ //第一次计算时间戳增量,时间戳增量为0 - _last_stamp = stamp; + if(stamp){ + _last_stamp = stamp; + } return 0; } @@ -39,7 +45,8 @@ int64_t DeltaStamp::deltaStamp(int64_t stamp) { if(ret >= 0){ //时间戳增量为正,返回之 _last_stamp = stamp; - return ret; + //在直播情况下,时间戳增量不得大于MAX_DELTA_STAMP + return ret < MAX_DELTA_STAMP ? ret : (_playback ? ret : 0); } //时间戳增量为负,说明时间戳回环了或回退了 @@ -60,13 +67,20 @@ void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out, //pts和dts的差值 int pts_dts_diff = pts - dts; - //相对时间戳 - _relativeStamp += deltaStamp(modifyStamp ? _ticker.elapsedTime() : dts); + if(_last_dts != dts){ + //时间戳发生变更 + if(modifyStamp){ + _relativeStamp = _ticker.elapsedTime(); + }else{ + _relativeStamp += deltaStamp(dts); + } + _last_dts = dts; + } dts_out = _relativeStamp; //////////////以下是播放时间戳的计算////////////////// - if(pts_dts_diff > 200 || pts_dts_diff < -200){ - //如果差值大于200毫秒,则认为由于回环导致时间戳错乱了 + if(ABS(pts_dts_diff) > MAX_CTS){ + //如果差值太大,则认为由于回环导致时间戳错乱了 pts_dts_diff = 0; } @@ -86,4 +100,58 @@ int64_t Stamp::getRelativeStamp() const { } +bool DtsGenerator::getDts(uint32_t pts, uint32_t &dts){ + bool ret = false; + if(pts == _last_pts){ + //pts未变,返回上次结果 + if(_last_dts){ + dts = _last_dts; + ret = true; + } + return ret; + } + + ret = getDts_l(pts,dts); + if(ret){ + //保存本次结果 + _last_dts = dts; + } + //记录上次pts + _last_pts = pts; + return ret; +} + +bool DtsGenerator::getDts_l(uint32_t pts, uint32_t &dts){ + if(_sorter_max_size == 1){ + //没有B帧 + dts = pts; + return true; + } + + if(!_sorter_max_size){ + if(pts > _last_max_pts){ + if(_frames_since_last_max_pts && _count_sorter_max_size++ > 0){ + _sorter_max_size = _frames_since_last_max_pts; + _dts_pts_offset = (pts - _last_max_pts) / 2; + } + _frames_since_last_max_pts = 0; + _last_max_pts = pts; + } + ++_frames_since_last_max_pts; + } + + _pts_sorter.emplace(pts); + if(_sorter_max_size && _pts_sorter.size() > _sorter_max_size){ + auto it = _pts_sorter.begin(); + dts = *it + _dts_pts_offset; + if(dts > pts){ + //dts不能大于pts(基本不可能到达这个逻辑) + dts = pts; + } + _pts_sorter.erase(it); + return true; + } + return false; +} + }//namespace mediakit \ No newline at end of file diff --git a/src/MediaFile/Stamp.h b/src/Common/Stamp.h similarity index 84% rename from src/MediaFile/Stamp.h rename to src/Common/Stamp.h index 82458fda..43d11159 100644 --- a/src/MediaFile/Stamp.h +++ b/src/Common/Stamp.h @@ -27,8 +27,9 @@ #ifndef ZLMEDIAKIT_STAMP_H #define ZLMEDIAKIT_STAMP_H -#include "Util/TimeTicker.h" +#include #include +#include "Util/TimeTicker.h" using namespace toolkit; namespace mediakit { @@ -84,9 +85,31 @@ public: int64_t getRelativeStamp() const ; private: int64_t _relativeStamp = 0; + int64_t _last_dts = -1; SmoothTicker _ticker; }; + +class DtsGenerator{ +public: + DtsGenerator() = default; + ~DtsGenerator() = default; + bool getDts(uint32_t pts, uint32_t &dts); +private: + bool getDts_l(uint32_t pts, uint32_t &dts); +private: + uint32_t _dts_pts_offset = 0; + uint32_t _last_dts = 0; + uint32_t _last_pts = 0; + uint32_t _last_max_pts = 0; + int _frames_since_last_max_pts = 0; + int _sorter_max_size = 0; + int _count_sorter_max_size = 0; + set _pts_sorter; + + +}; + }//namespace mediakit #endif //ZLMEDIAKIT_STAMP_H diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 15540bcd..ded712dc 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -55,6 +55,7 @@ bool loadIniConfig(const char *ini_path){ ////////////广播名称/////////// namespace Broadcast { const string kBroadcastMediaChanged = "kBroadcastMediaChanged"; +const string kBroadcastMediaResetTracks = "kBroadcastMediaResetTracks"; const string kBroadcastRecordMP4 = "kBroadcastRecordMP4"; const string kBroadcastHttpRequest = "kBroadcastHttpRequest"; const string kBroadcastHttpAccess = "kBroadcastHttpAccess"; @@ -67,6 +68,7 @@ const string kBroadcastReloadConfig = "kBroadcastReloadConfig"; const string kBroadcastShellLogin = "kBroadcastShellLogin"; const string kBroadcastNotFoundStream = "kBroadcastNotFoundStream"; const string kBroadcastStreamNoneReader = "kBroadcastStreamNoneReader"; +const string kBroadcastHttpBeforeAccess = "kBroadcastHttpBeforeAccess"; } //namespace Broadcast //通用配置项目 @@ -79,15 +81,21 @@ const string kEnableVhost = GENERAL_FIELD"enableVhost"; const string kUltraLowDelay = GENERAL_FIELD"ultraLowDelay"; const string kAddMuteAudio = GENERAL_FIELD"addMuteAudio"; const string kResetWhenRePlay = GENERAL_FIELD"resetWhenRePlay"; +const string kPublishToRtxp = GENERAL_FIELD"publishToRtxp"; +const string kPublishToHls = GENERAL_FIELD"publishToHls"; +const string kPublishToMP4 = GENERAL_FIELD"publishToMP4"; onceToken token([](){ mINI::Instance()[kFlowThreshold] = 1024; - mINI::Instance()[kStreamNoneReaderDelayMS] = 5 * 1000; - mINI::Instance()[kMaxStreamWaitTimeMS] = 5 * 1000; - mINI::Instance()[kEnableVhost] = 1; + mINI::Instance()[kStreamNoneReaderDelayMS] = 20 * 1000; + mINI::Instance()[kMaxStreamWaitTimeMS] = 15 * 1000; + mINI::Instance()[kEnableVhost] = 0; mINI::Instance()[kUltraLowDelay] = 1; mINI::Instance()[kAddMuteAudio] = 1; mINI::Instance()[kResetWhenRePlay] = 1; + mINI::Instance()[kPublishToRtxp] = 1; + mINI::Instance()[kPublishToHls] = 1; + mINI::Instance()[kPublishToMP4] = 0; },nullptr); }//namespace General @@ -95,57 +103,40 @@ onceToken token([](){ ////////////HTTP配置/////////// namespace Http { #define HTTP_FIELD "http." - //http 文件发送缓存大小 -#define HTTP_SEND_BUF_SIZE (64 * 1024) const string kSendBufSize = HTTP_FIELD"sendBufSize"; - //http 最大请求字节数 -#define HTTP_MAX_REQ_SIZE (4*1024) const string kMaxReqSize = HTTP_FIELD"maxReqSize"; - //http keep-alive秒数 -#define HTTP_KEEP_ALIVE_SECOND 10 const string kKeepAliveSecond = HTTP_FIELD"keepAliveSecond"; - -//http keep-alive最大请求数 -#define HTTP_MAX_REQ_CNT 100 -const string kMaxReqCount = HTTP_FIELD"maxReqCount"; - - //http 字符编码 -#if defined(_WIN32) -#define HTTP_CHAR_SET "gb2312" -#else -#define HTTP_CHAR_SET "utf-8" -#endif const string kCharSet = HTTP_FIELD"charSet"; - //http 服务器根目录 -#define HTTP_ROOT_PATH "./httpRoot" const string kRootPath = HTTP_FIELD"rootPath"; - //http 404错误提示内容 -#define HTTP_NOT_FOUND ""\ - "404 Not Found"\ - ""\ - "

您访问的资源不存在!

"\ - "
"\ - SERVER_NAME\ - "
"\ - ""\ - "" const string kNotFound = HTTP_FIELD"notFound"; - onceToken token([](){ - mINI::Instance()[kSendBufSize] = HTTP_SEND_BUF_SIZE; - mINI::Instance()[kMaxReqSize] = HTTP_MAX_REQ_SIZE; - mINI::Instance()[kKeepAliveSecond] = HTTP_KEEP_ALIVE_SECOND; - mINI::Instance()[kMaxReqCount] = HTTP_MAX_REQ_CNT; - mINI::Instance()[kCharSet] = HTTP_CHAR_SET; - mINI::Instance()[kRootPath] = HTTP_ROOT_PATH; - mINI::Instance()[kNotFound] = HTTP_NOT_FOUND; + mINI::Instance()[kSendBufSize] = 64 * 1024; + mINI::Instance()[kMaxReqSize] = 4*1024; + mINI::Instance()[kKeepAliveSecond] = 15; +#if defined(_WIN32) + mINI::Instance()[kCharSet] = "gb2312"; +#else + mINI::Instance()[kCharSet] ="utf-8"; +#endif + + mINI::Instance()[kRootPath] = "./www"; + mINI::Instance()[kNotFound] = + "" + "404 Not Found" + "" + "

您访问的资源不存在!

" + "
" + SERVER_NAME + "
" + "" + ""; },nullptr); }//namespace Http @@ -153,12 +144,10 @@ onceToken token([](){ ////////////SHELL配置/////////// namespace Shell { #define SHELL_FIELD "shell." - -#define SHELL_MAX_REQ_SIZE 1024 const string kMaxReqSize = SHELL_FIELD"maxReqSize"; onceToken token([](){ - mINI::Instance()[kMaxReqSize] = SHELL_MAX_REQ_SIZE; + mINI::Instance()[kMaxReqSize] = 1024; },nullptr); } //namespace Shell @@ -169,7 +158,6 @@ const string kAuthBasic = RTSP_FIELD"authBasic"; const string kHandshakeSecond = RTSP_FIELD"handshakeSecond"; const string kKeepAliveSecond = RTSP_FIELD"keepAliveSecond"; const string kDirectProxy = RTSP_FIELD"directProxy"; -const string kModifyStamp = RTSP_FIELD"modifyStamp"; onceToken token([](){ //默认Md5方式认证 @@ -177,9 +165,7 @@ onceToken token([](){ mINI::Instance()[kHandshakeSecond] = 15; mINI::Instance()[kKeepAliveSecond] = 15; mINI::Instance()[kDirectProxy] = 1; - mINI::Instance()[kModifyStamp] = false; },nullptr); - } //namespace Rtsp ////////////RTMP服务器配置/////////// @@ -190,44 +176,32 @@ const string kHandshakeSecond = RTMP_FIELD"handshakeSecond"; const string kKeepAliveSecond = RTMP_FIELD"keepAliveSecond"; onceToken token([](){ - mINI::Instance()[kModifyStamp] = true; + mINI::Instance()[kModifyStamp] = false; mINI::Instance()[kHandshakeSecond] = 15; mINI::Instance()[kKeepAliveSecond] = 15; },nullptr); - } //namespace RTMP ////////////RTP配置/////////// namespace Rtp { #define RTP_FIELD "rtp." - //RTP打包最大MTU,公网情况下更小 -#define RTP_VIDOE_MTU_SIZE 1400 const string kVideoMtuSize = RTP_FIELD"videoMtuSize"; - -#define RTP_Audio_MTU_SIZE 600 const string kAudioMtuSize = RTP_FIELD"audioMtuSize"; - //RTP排序缓存最大个数 -#define RTP_MAX_RTP_COUNT 50 const string kMaxRtpCount = RTP_FIELD"maxRtpCount"; - //如果RTP序列正确次数累计达到该数字就启动清空排序缓存 -#define RTP_CLEAR_COUNT 10 const string kClearCount = RTP_FIELD"clearCount"; - //最大RTP时间为13个小时,每13小时回环一次 -#define RTP_CYCLE_MS (13*60*60*1000) const string kCycleMS = RTP_FIELD"cycleMS"; - onceToken token([](){ - mINI::Instance()[kVideoMtuSize] = RTP_VIDOE_MTU_SIZE; - mINI::Instance()[kAudioMtuSize] = RTP_Audio_MTU_SIZE; - mINI::Instance()[kMaxRtpCount] = RTP_MAX_RTP_COUNT; - mINI::Instance()[kClearCount] = RTP_CLEAR_COUNT; - mINI::Instance()[kCycleMS] = RTP_CYCLE_MS; + mINI::Instance()[kVideoMtuSize] = 1400; + mINI::Instance()[kAudioMtuSize] = 600; + mINI::Instance()[kMaxRtpCount] = 50; + mINI::Instance()[kClearCount] = 10; + mINI::Instance()[kCycleMS] = 13*60*60*1000; },nullptr); } //namespace Rtsp @@ -239,88 +213,86 @@ const string kAddrMin = MULTI_FIELD"addrMin"; //组播分配截止地址 const string kAddrMax = MULTI_FIELD"addrMax"; //组播TTL -#define MULTI_UDP_TTL 64 const string kUdpTTL = MULTI_FIELD"udpTTL"; onceToken token([](){ mINI::Instance()[kAddrMin] = "239.0.0.0"; mINI::Instance()[kAddrMax] = "239.255.255.255"; - mINI::Instance()[kUdpTTL] = MULTI_UDP_TTL; + mINI::Instance()[kUdpTTL] = 64; },nullptr); - } //namespace MultiCast ////////////录像配置/////////// namespace Record { #define RECORD_FIELD "record." - //查看录像的应用名称 -#define RECORD_APP_NAME "record" const string kAppName = RECORD_FIELD"appName"; - //每次流化MP4文件的时长,单位毫秒 -#define RECORD_SAMPLE_MS 500 const string kSampleMS = RECORD_FIELD"sampleMS"; - //MP4文件录制大小,默认一个小时 -#define RECORD_FILE_SECOND (60*60) const string kFileSecond = RECORD_FIELD"fileSecond"; - //录制文件路径 -#define RECORD_FILE_PATH HTTP_ROOT_PATH const string kFilePath = RECORD_FIELD"filePath"; - //mp4文件写缓存大小 const string kFileBufSize = RECORD_FIELD"fileBufSize"; - //mp4录制完成后是否进行二次关键帧索引写入头部 const string kFastStart = RECORD_FIELD"fastStart"; - //mp4文件是否重头循环读取 const string kFileRepeat = RECORD_FIELD"fileRepeat"; onceToken token([](){ - mINI::Instance()[kAppName] = RECORD_APP_NAME; - mINI::Instance()[kSampleMS] = RECORD_SAMPLE_MS; - mINI::Instance()[kFileSecond] = RECORD_FILE_SECOND; - mINI::Instance()[kFilePath] = RECORD_FILE_PATH; + mINI::Instance()[kAppName] = "record"; + mINI::Instance()[kSampleMS] = 500; + mINI::Instance()[kFileSecond] = 60*60; + mINI::Instance()[kFilePath] = "./www"; mINI::Instance()[kFileBufSize] = 64 * 1024; mINI::Instance()[kFastStart] = false; mINI::Instance()[kFileRepeat] = false; },nullptr); - } //namespace Record ////////////HLS相关配置/////////// namespace Hls { #define HLS_FIELD "hls." - //HLS切片时长,单位秒 -#define HLS_SEGMENT_DURATION 3 const string kSegmentDuration = HLS_FIELD"segDur"; - //HLS切片个数 -#define HLS_SEGMENT_NUM 3 const string kSegmentNum = HLS_FIELD"segNum"; - +//HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数 +const string kSegmentRetain = HLS_FIELD"segRetain"; //HLS文件写缓存大小 -#define HLS_FILE_BUF_SIZE (64 * 1024) const string kFileBufSize = HLS_FIELD"fileBufSize"; - //录制文件路径 -#define HLS_FILE_PATH (HTTP_ROOT_PATH) const string kFilePath = HLS_FIELD"filePath"; onceToken token([](){ - mINI::Instance()[kSegmentDuration] = HLS_SEGMENT_DURATION; - mINI::Instance()[kSegmentNum] = HLS_SEGMENT_NUM; - mINI::Instance()[kFileBufSize] = HLS_FILE_BUF_SIZE; - mINI::Instance()[kFilePath] = HLS_FILE_PATH; + mINI::Instance()[kSegmentDuration] = 2; + mINI::Instance()[kSegmentNum] = 3; + mINI::Instance()[kSegmentRetain] = 5; + mINI::Instance()[kFileBufSize] = 64 * 1024; + mINI::Instance()[kFilePath] = "./www"; },nullptr); - } //namespace Hls +////////////Rtp代理相关配置/////////// +namespace RtpProxy { +#define RTP_PROXY_FIELD "rtp_proxy." +//rtp调试数据保存目录 +const string kDumpDir = RTP_PROXY_FIELD"dumpDir"; +//是否限制udp数据来源ip和端口 +const string kCheckSource = RTP_PROXY_FIELD"checkSource"; +//rtp接收超时时间 +const string kTimeoutSec = RTP_PROXY_FIELD"timeoutSec"; + +onceToken token([](){ + mINI::Instance()[kDumpDir] = ""; + mINI::Instance()[kCheckSource] = 1; + mINI::Instance()[kTimeoutSec] = 15; +},nullptr); +} //namespace RtpProxy + + namespace Client { const string kNetAdapter = "net_adapter"; const string kRtpType = "rtp_type"; @@ -331,7 +303,6 @@ const string kTimeoutMS = "protocol_timeout_ms"; const string kMediaTimeoutMS = "media_timeout_ms"; const string kBeatIntervalMS = "beat_interval_ms"; const string kMaxAnalysisMS = "max_analysis_ms"; - } } // namespace mediakit diff --git a/src/Common/config.h b/src/Common/config.h index a4557ea1..ba81611c 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -62,6 +62,7 @@ bool loadIniConfig(const char *ini_path = nullptr); #define HTTP_SCHEMA "http" #define RTSP_SCHEMA "rtsp" #define RTMP_SCHEMA "rtmp" +#define HLS_SCHEMA "hls" #define DEFAULT_VHOST "__defaultVhost__" ////////////广播名称/////////// @@ -69,19 +70,28 @@ namespace Broadcast { //注册或反注册MediaSource事件广播 extern const string kBroadcastMediaChanged; -#define BroadcastMediaChangedArgs const bool &bRegist, const string &schema,const string &vhost,const string &app,const string &stream,MediaSource &sender +#define BroadcastMediaChangedArgs const bool &bRegist, MediaSource &sender + +//MediaSource重置Track事件 +extern const string kBroadcastMediaResetTracks; +#define BroadcastMediaResetTracksArgs MediaSource &sender //录制mp4文件成功后广播 extern const string kBroadcastRecordMP4; -#define BroadcastRecordMP4Args const Mp4Info &info +#define BroadcastRecordMP4Args const MP4Info &info //收到http api请求广播 extern const string kBroadcastHttpRequest; #define BroadcastHttpRequestArgs const Parser &parser,const HttpSession::HttpResponseInvoker &invoker,bool &consumed,TcpSession &sender -//收到http 访问文件或目录的广播,通过该事件控制访问http目录的权限 +//在http文件服务器中,收到http访问文件或目录的广播,通过该事件控制访问http目录的权限 extern const string kBroadcastHttpAccess; -#define BroadcastHttpAccessArgs const Parser &parser,const MediaInfo &args,const string &path,const bool &is_dir,const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender +#define BroadcastHttpAccessArgs const Parser &parser,const string &path,const bool &is_dir,const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender + +//在http文件服务器中,收到http访问文件或目录前的广播,通过该事件可以控制http url到文件路径的映射 +//在该事件中通过自行覆盖path参数,可以做到譬如根据虚拟主机或者app选择不同http根目录的目的 +extern const string kBroadcastHttpBeforeAccess; +#define BroadcastHttpBeforeAccessArgs const Parser &parser,string &path,TcpSession &sender //该流是否需要认证?是的话调用invoker并传入realm,否则传入空的realm.如果该事件不监听则不认证 extern const string kBroadcastOnGetRtspRealm; @@ -117,7 +127,7 @@ extern const string kBroadcastShellLogin; //停止rtsp/rtmp/http-flv会话后流量汇报事件广播 extern const string kBroadcastFlowReport; -#define BroadcastFlowReportArgs const MediaInfo &args,const uint64_t &totalBytes,const uint64_t &totalDuration,const bool &isPlayer,TcpSession &sender +#define BroadcastFlowReportArgs const MediaInfo &args,const uint64_t &totalBytes,const uint64_t &totalDuration,const bool &isPlayer, const string &sessionIdentifier, const string &peerIP,const uint16_t &peerPort //未找到流后会广播该事件,请在监听该事件后去拉流或其他方式产生流,这样就能按需拉流了 extern const string kBroadcastNotFoundStream; @@ -155,11 +165,6 @@ extern const string kBroadcastReloadConfig; static type arg = mINI::Instance()[key]; \ LISTEN_RELOAD_KEY(arg,key); - -//兼容老代码 -#define GET_CONFIG_AND_REGISTER GET_CONFIG -#define BroadcastRtmpPublishArgs BroadcastMediaPublishArgs -#define kBroadcastRtmpPublish kBroadcastMediaPublish } //namespace Broadcast ////////////通用配置/////////// @@ -182,6 +187,12 @@ extern const string kAddMuteAudio; //拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始, //如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写) extern const string kResetWhenRePlay; +//是否默认推流时转换成rtsp或rtmp,hook接口(on_publish)中可以覆盖该设置 +extern const string kPublishToRtxp ; +//是否默认推流时转换成hls,hook接口(on_publish)中可以覆盖该设置 +extern const string kPublishToHls ; +//是否默认推流时mp4录像,hook接口(on_publish)中可以覆盖该设置 +extern const string kPublishToMP4 ; }//namespace General @@ -193,8 +204,6 @@ extern const string kSendBufSize; extern const string kMaxReqSize; //http keep-alive秒数 extern const string kKeepAliveSecond; -//http keep-alive最大请求数 -extern const string kMaxReqCount; //http 字符编码 extern const string kCharSet; //http 服务器根目录 @@ -223,8 +232,6 @@ extern const string kKeepAliveSecond; //假定您的拉流源地址不是264或265或AAC,那么你可以使用直接代理的方式来支持rtsp代理 //默认开启rtsp直接代理,rtmp由于没有这些问题,是强制开启直接代理的 extern const string kDirectProxy; -//rtsp推流是否修改时间戳 -extern const string kModifyStamp; } //namespace Rtsp ////////////RTMP服务器配置/////////// @@ -283,14 +290,25 @@ extern const string kFileRepeat; namespace Hls { //HLS切片时长,单位秒 extern const string kSegmentDuration; -//HLS切片个数,如果设置为0,则不删除切片,而是保存为点播 +//m3u8文件中HLS切片个数,如果设置为0,则不删除切片,而是保存为点播 extern const string kSegmentNum; +//HLS切片从m3u8文件中移除后,继续保留在磁盘上的个数 +extern const string kSegmentRetain; //HLS文件写缓存大小 extern const string kFileBufSize; //录制文件路径 extern const string kFilePath; } //namespace Hls +////////////Rtp代理相关配置/////////// +namespace RtpProxy { +//rtp调试数据保存目录,置空则不生成 +extern const string kDumpDir; +//是否限制udp数据来源ip和端口 +extern const string kCheckSource; +//rtp接收超时时间 +extern const string kTimeoutSec; +} //namespace RtpProxy /** * rtsp/rtmp播放器、推流器相关设置名, diff --git a/src/Extension/AAC.h b/src/Extension/AAC.h index 156c4122..063756f6 100644 --- a/src/Extension/AAC.h +++ b/src/Extension/AAC.h @@ -104,7 +104,6 @@ public: //表示说ADTS帧中有一个AAC数据块并不是说没有。(一个AAC原始帧包含一段时间内1024个采样及相关数据) unsigned int no_raw_data_blocks_in_frame; //2 uimsfb unsigned char buffer[2 * 1024 + 7]; - uint16_t sequence; uint32_t timeStamp; uint32_t iPrefixSize = 7; } ; @@ -257,6 +256,9 @@ private: * 解析2个字节的aac配置 */ void onReady(){ + if(_cfg.size() < 2){ + return; + } AACFrame aacFrame; makeAdtsHeader(_cfg,aacFrame); getAACInfo(aacFrame,_sampleRate,_channel); diff --git a/src/Extension/AACRtmp.cpp b/src/Extension/AACRtmp.cpp index fd1d3733..6a657aab 100644 --- a/src/Extension/AACRtmp.cpp +++ b/src/Extension/AACRtmp.cpp @@ -40,8 +40,6 @@ AACFrame::Ptr AACRtmpDecoder::obtainFrame() { } bool AACRtmpDecoder::inputRtmp(const RtmpPacket::Ptr &pkt, bool key_pos) { - RtmpCodec::inputRtmp(pkt, false); - if (pkt->isCfgFrame()) { _aac_cfg = pkt->getAacCfg(); return false; @@ -78,19 +76,24 @@ AACRtmpEncoder::AACRtmpEncoder(const Track::Ptr &track) { _track = dynamic_pointer_cast(track); } -void AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) { - RtmpCodec::inputFrame(frame); +void AACRtmpEncoder::makeConfigPacket() { + if (_track && _track->ready()) { + //从track中和获取aac配置信息 + _aac_cfg = _track->getAacCfg(); + } - if(_aac_cfg.empty()){ - if(frame->prefixSize() >= 7){ + if (!_aac_cfg.empty()) { + makeAudioConfigPkt(); + } +} + +void AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) { + if (_aac_cfg.empty()) { + if (frame->prefixSize() >= 7) { //包含adts头,从adts头获取aac配置信息 _aac_cfg = makeAdtsConfig(reinterpret_cast(frame->data())); - makeAudioConfigPkt(); - } else if(_track && _track->ready()){ - //从track中和获取aac配置信息 - _aac_cfg = _track->getAacCfg(); - makeAudioConfigPkt(); } + makeConfigPacket(); } if(!_aac_cfg.empty()){ @@ -106,7 +109,7 @@ void AACRtmpEncoder::inputFrame(const Frame::Ptr &frame) { rtmpPkt->bodySize = rtmpPkt->strBuf.size(); rtmpPkt->chunkId = CHUNK_AUDIO; rtmpPkt->streamId = STREAM_MEDIA; - rtmpPkt->timeStamp = frame->stamp(); + rtmpPkt->timeStamp = frame->dts(); rtmpPkt->typeId = MSG_AUDIO; RtmpCodec::inputRtmp(rtmpPkt, false); } diff --git a/src/Extension/AACRtmp.h b/src/Extension/AACRtmp.h index 661a37a1..8181ebfd 100644 --- a/src/Extension/AACRtmp.h +++ b/src/Extension/AACRtmp.h @@ -88,6 +88,10 @@ public: */ void inputFrame(const Frame::Ptr &frame) override; + /** + * 生成config包 + */ + void makeConfigPacket() override; private: void makeAudioConfigPkt(); private: diff --git a/src/Extension/AACRtp.cpp b/src/Extension/AACRtp.cpp index 69291f59..a84c22be 100644 --- a/src/Extension/AACRtp.cpp +++ b/src/Extension/AACRtp.cpp @@ -40,10 +40,8 @@ AACRtpEncoder::AACRtpEncoder(uint32_t ui32Ssrc, } void AACRtpEncoder::inputFrame(const Frame::Ptr &frame) { - RtpCodec::inputFrame(frame); - GET_CONFIG(uint32_t, cycleMS, Rtp::kCycleMS); - auto uiStamp = frame->stamp(); + auto uiStamp = frame->dts(); auto pcData = frame->data() + frame->prefixSize(); auto iLen = frame->size() - frame->prefixSize(); @@ -102,8 +100,6 @@ AACFrame::Ptr AACRtpDecoder::obtainFrame() { } bool AACRtpDecoder::inputRtp(const RtpPacket::Ptr &rtppack, bool key_pos) { - RtpCodec::inputRtp(rtppack, false); - // 获取rtp数据长度 int length = rtppack->size() - rtppack->offset; @@ -153,7 +149,6 @@ bool AACRtpDecoder::inputRtp(const RtpPacket::Ptr &rtppack, bool key_pos) { memcpy(_adts->buffer + _adts->aac_frame_length, rtp_packet_payload + next_aac_payload_offset, cur_aac_payload_len); _adts->aac_frame_length += (cur_aac_payload_len); if (rtppack->mark == true) { - _adts->sequence = rtppack->sequence; _adts->timeStamp = rtppack->timeStamp; writeAdtsHeader(*_adts, _adts->buffer); onGetAAC(_adts); diff --git a/src/Extension/Factory.cpp b/src/Extension/Factory.cpp index d65dbb56..cff9a4c0 100644 --- a/src/Extension/Factory.cpp +++ b/src/Extension/Factory.cpp @@ -63,13 +63,15 @@ 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(FindField(track->_fmtp.data()," ", nullptr),";","="); auto sps_pps = map["sprop-parameter-sets"]; - if(sps_pps.empty()){ - return std::make_shared(); - } string base64_SPS = FindField(sps_pps.data(), NULL, ","); string base64_PPS = FindField(sps_pps.data(), ",", NULL); auto sps = decodeBase64(base64_SPS); auto pps = decodeBase64(base64_PPS); + if(sps.empty() || pps.empty()){ + //如果sdp里面没有sps/pps,那么可能在后续的rtp里面恢复出sps/pps + return std::make_shared(); + } + return std::make_shared(sps,pps,0,0); } @@ -79,6 +81,10 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) { auto vps = decodeBase64(map["sprop-vps"]); auto sps = decodeBase64(map["sprop-sps"]); auto pps = decodeBase64(map["sprop-pps"]); + if(sps.empty() || pps.empty()){ + //如果sdp里面没有sps/pps,那么可能在后续的rtp里面恢复出sps/pps + return std::make_shared(); + } return std::make_shared(vps,sps,pps,0,0,0); } @@ -174,6 +180,9 @@ CodecId Factory::getCodecIdByAmf(const AMFValue &val){ if(str == "mp4a"){ return CodecAAC; } + if(str == "hev1" || str == "hvc1"){ + return CodecH265; + } WarnL << "暂不支持该Amf:" << str; return CodecInvalid; } @@ -181,12 +190,9 @@ CodecId Factory::getCodecIdByAmf(const AMFValue &val){ if (val.type() != AMF_NULL){ auto type_id = val.as_integer(); switch (type_id){ - case 7:{ - return CodecH264; - } - case 10:{ - return CodecAAC; - } + case 7: return CodecH264; + case 10: return CodecAAC; + case 12: return CodecH265; default: WarnL << "暂不支持该Amf:" << type_id; return CodecInvalid; @@ -213,14 +219,10 @@ RtmpCodec::Ptr Factory::getRtmpCodecByTrack(const Track::Ptr &track) { AMFValue Factory::getAmfByCodecId(CodecId codecId) { switch (codecId){ - case CodecAAC:{ - return AMFValue("mp4a"); - } - case CodecH264:{ - return AMFValue("avc1"); - } - default: - return AMFValue(AMF_NULL); + case CodecAAC: return AMFValue("mp4a"); + case CodecH264: return AMFValue("avc1"); + case CodecH265: return AMFValue(12); + default: return AMFValue(AMF_NULL); } } diff --git a/src/Extension/Frame.h b/src/Extension/Frame.h index 79b5d02a..d4cbca6b 100644 --- a/src/Extension/Frame.h +++ b/src/Extension/Frame.h @@ -50,7 +50,7 @@ typedef enum { TrackVideo = 0, TrackAudio, TrackTitle, - TrackMax = 0x7FFF + TrackMax = 3 } TrackType; /** @@ -81,13 +81,6 @@ class Frame : public Buffer, public CodecInfo { public: typedef std::shared_ptr Ptr; virtual ~Frame(){} - /** - * 时间戳,已经废弃,请使用dts() 、pts()接口 - */ - inline uint32_t stamp() const { - return dts(); - }; - /** * 返回解码时间戳,单位毫秒 @@ -198,79 +191,15 @@ private: }; -/** - * 帧环形缓存接口类 - */ -class FrameRingInterface : public FrameWriterInterface{ -public: - typedef RingBuffer RingType; - typedef std::shared_ptr Ptr; - - FrameRingInterface(){} - virtual ~FrameRingInterface(){} - - /** - * 获取帧环形缓存 - * @return - */ - virtual RingType::Ptr getFrameRing() const = 0; - - /** - * 设置帧环形缓存 - * @param ring - */ - virtual void setFrameRing(const RingType::Ptr &ring) = 0; -}; - -/** - * 帧环形缓存 - */ -class FrameRing : public FrameRingInterface{ -public: - typedef std::shared_ptr Ptr; - - FrameRing(){ - } - virtual ~FrameRing(){} - - /** - * 获取帧环形缓存 - * @return - */ - RingType::Ptr getFrameRing() const override { - return _frameRing; - } - - /** - * 设置帧环形缓存 - * @param ring - */ - void setFrameRing(const RingType::Ptr &ring) override { - _frameRing = ring; - } - - /** - * 输入数据帧 - * @param frame - */ - void inputFrame(const Frame::Ptr &frame) override{ - if(_frameRing){ - _frameRing->write(frame,frame->keyFrame()); - } - } -protected: - RingType::Ptr _frameRing; -}; - /** * 支持代理转发的帧环形缓存 */ -class FrameRingInterfaceDelegate : public FrameRing { +class FrameDispatcher : public FrameWriterInterface { public: - typedef std::shared_ptr Ptr; + typedef std::shared_ptr Ptr; - FrameRingInterfaceDelegate(){} - virtual ~FrameRingInterfaceDelegate(){} + FrameDispatcher(){} + virtual ~FrameDispatcher(){} void addDelegate(const FrameWriterInterface::Ptr &delegate){ lock_guard lck(_mtx); @@ -287,7 +216,6 @@ public: * @param frame 帧 */ void inputFrame(const Frame::Ptr &frame) override{ - FrameRing::inputFrame(frame); lock_guard lck(_mtx); for(auto &pr : _delegateMap){ pr.second->inputFrame(frame); diff --git a/src/Extension/H264.h b/src/Extension/H264.h index 394752ec..b4c71a6b 100644 --- a/src/Extension/H264.h +++ b/src/Extension/H264.h @@ -49,25 +49,25 @@ public: NAL_SPS = 7, NAL_PPS = 8, NAL_IDR = 5, - NAL_B_P = 1 + NAL_SEI = 6, } NalType; char *data() const override{ - return (char *)buffer.data(); + return (char *)_buffer.data(); } uint32_t size() const override { - return buffer.size(); + return _buffer.size(); } uint32_t dts() const override { - return timeStamp; + return _dts; } uint32_t pts() const override { - return ptsStamp ? ptsStamp : timeStamp; + return _pts ? _pts : _dts; } uint32_t prefixSize() const override{ - return iPrefixSize; + return _prefix_size; } TrackType getTrackType() const override{ @@ -79,11 +79,11 @@ public: } bool keyFrame() const override { - return H264_TYPE(buffer[iPrefixSize]) == H264Frame::NAL_IDR; + return H264_TYPE(_buffer[_prefix_size]) == H264Frame::NAL_IDR; } bool configFrame() const override{ - switch(H264_TYPE(buffer[iPrefixSize]) ){ + switch(H264_TYPE(_buffer[_prefix_size]) ){ case H264Frame::NAL_SPS: case H264Frame::NAL_PPS: return true; @@ -92,10 +92,10 @@ public: } } public: - uint32_t timeStamp; - uint32_t ptsStamp = 0; - string buffer; - uint32_t iPrefixSize = 4; + uint32_t _dts = 0; + uint32_t _pts = 0; + uint32_t _prefix_size = 4; + string _buffer; }; @@ -315,18 +315,15 @@ private: //I insertConfigFrame(frame); VideoTrack::inputFrame(frame); - _last_frame_is_idr = true; } break; - case H264Frame::NAL_B_P:{ - //B or P + default: VideoTrack::inputFrame(frame); - _last_frame_is_idr = false; - } break; } + _last_frame_is_idr = type == H264Frame::NAL_IDR; if(_width == 0 && ready()){ onReady(); } @@ -343,19 +340,19 @@ private: if(!_sps.empty()){ auto spsFrame = std::make_shared(); - spsFrame->iPrefixSize = 4; - spsFrame->buffer.assign("\x0\x0\x0\x1",4); - spsFrame->buffer.append(_sps); - spsFrame->timeStamp = frame->stamp(); + spsFrame->_prefix_size = 4; + spsFrame->_buffer.assign("\x0\x0\x0\x1",4); + spsFrame->_buffer.append(_sps); + spsFrame->_dts = frame->dts(); VideoTrack::inputFrame(spsFrame); } if(!_pps.empty()){ auto ppsFrame = std::make_shared(); - ppsFrame->iPrefixSize = 4; - ppsFrame->buffer.assign("\x0\x0\x0\x1",4); - ppsFrame->buffer.append(_pps); - ppsFrame->timeStamp = frame->stamp(); + ppsFrame->_prefix_size = 4; + ppsFrame->_buffer.assign("\x0\x0\x0\x1",4); + ppsFrame->_buffer.append(_pps); + ppsFrame->_dts = frame->dts(); VideoTrack::inputFrame(ppsFrame); } } diff --git a/src/Extension/H264Rtmp.cpp b/src/Extension/H264Rtmp.cpp index 2f56b9e6..2aa4a7cb 100644 --- a/src/Extension/H264Rtmp.cpp +++ b/src/Extension/H264Rtmp.cpp @@ -35,15 +35,13 @@ H264RtmpDecoder::H264RtmpDecoder() { H264Frame::Ptr H264RtmpDecoder::obtainFrame() { //从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象 auto frame = obtainObj(); - frame->buffer.clear(); - frame->iPrefixSize = 4; + frame->_buffer.clear(); + frame->_prefix_size = 4; return frame; } bool H264RtmpDecoder::inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos) { - key_pos = decodeRtmp(rtmp); - RtmpCodec::inputRtmp(rtmp, key_pos); - return key_pos; + return decodeRtmp(rtmp); } bool H264RtmpDecoder::decodeRtmp(const RtmpPacket::Ptr &pkt) { @@ -80,10 +78,10 @@ bool H264RtmpDecoder::decodeRtmp(const RtmpPacket::Ptr &pkt) { inline void H264RtmpDecoder::onGetH264(const char* pcData, int iLen, uint32_t dts,uint32_t pts) { #if 1 - _h264frame->timeStamp = dts; - _h264frame->ptsStamp = pts; - _h264frame->buffer.assign("\x0\x0\x0\x1", 4); //添加264头 - _h264frame->buffer.append(pcData, iLen); + _h264frame->_dts = dts; + _h264frame->_pts = pts; + _h264frame->_buffer.assign("\x0\x0\x0\x1", 4); //添加264头 + _h264frame->_buffer.append(pcData, iLen); //写入环形缓存 RtmpCodec::inputFrame(_h264frame); @@ -101,87 +99,80 @@ inline void H264RtmpDecoder::onGetH264(const char* pcData, int iLen, uint32_t dt H264RtmpEncoder::H264RtmpEncoder(const Track::Ptr &track) { _track = dynamic_pointer_cast(track); +} +void H264RtmpEncoder::makeConfigPacket(){ + if (_track && _track->ready()) { + //尝试从track中获取sps pps信息 + _sps = _track->getSps(); + _pps = _track->getPps(); + } + + if (!_sps.empty() && !_pps.empty()) { + //获取到sps/pps + makeVideoConfigPkt(); + _gotSpsPps = true; + } } void H264RtmpEncoder::inputFrame(const Frame::Ptr &frame) { - RtmpCodec::inputFrame(frame); - auto pcData = frame->data() + frame->prefixSize(); auto iLen = frame->size() - frame->prefixSize(); auto type = H264_TYPE(((uint8_t*)pcData)[0]); - if(!_gotSpsPps){ + if (!_gotSpsPps) { //尝试从frame中获取sps pps - switch (type){ - case H264Frame::NAL_SPS:{ + switch (type) { + case H264Frame::NAL_SPS: { //sps - if(_sps.empty()){ - _sps = string(pcData,iLen); - } - } + _sps = string(pcData, iLen); + makeConfigPacket(); break; - case H264Frame::NAL_PPS:{ + } + case H264Frame::NAL_PPS: { //pps - if(_pps.empty()){ - _pps = string(pcData,iLen); - } - } + _pps = string(pcData, iLen); + makeConfigPacket(); break; + } default: break; } - - if(_track && _track->ready()){ - //尝试从track中获取sps pps信息 - _sps = _track->getSps(); - _pps = _track->getPps(); - } - - if(!_sps.empty() && !_pps.empty()){ - _gotSpsPps = true; - makeVideoConfigPkt(); - } } - switch (type){ - case H264Frame::NAL_IDR: - case H264Frame::NAL_B_P:{ - if(_lastPacket && _lastPacket->timeStamp != frame->stamp()) { - RtmpCodec::inputRtmp(_lastPacket, _lastPacket->isVideoKeyFrame()); - _lastPacket = nullptr; - } - - if(!_lastPacket) { - //I or P or B frame - int8_t flags = 7; //h.264 - bool is_config = false; - flags |= ((frame->keyFrame() ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4); - - _lastPacket = ResourcePoolHelper::obtainObj(); - _lastPacket->strBuf.clear(); - _lastPacket->strBuf.push_back(flags); - _lastPacket->strBuf.push_back(!is_config); - auto cts = frame->pts() - frame->dts(); - cts = htonl(cts); - _lastPacket->strBuf.append((char *)&cts + 1, 3); - - _lastPacket->chunkId = CHUNK_VIDEO; - _lastPacket->streamId = STREAM_MEDIA; - _lastPacket->timeStamp = frame->stamp(); - _lastPacket->typeId = MSG_VIDEO; - - } - auto size = htonl(iLen); - _lastPacket->strBuf.append((char *) &size, 4); - _lastPacket->strBuf.append(pcData, iLen); - _lastPacket->bodySize = _lastPacket->strBuf.size(); - } - break; - - default: - break; + if(type == H264Frame::NAL_SEI){ + return; } + + if(_lastPacket && _lastPacket->timeStamp != frame->dts()) { + RtmpCodec::inputRtmp(_lastPacket, _lastPacket->isVideoKeyFrame()); + _lastPacket = nullptr; + } + + if(!_lastPacket) { + //I or P or B frame + int8_t flags = 7; //h.264 + bool is_config = false; + flags |= (((frame->configFrame() || frame->keyFrame()) ? FLV_KEY_FRAME : FLV_INTER_FRAME) << 4); + + _lastPacket = ResourcePoolHelper::obtainObj(); + _lastPacket->strBuf.clear(); + _lastPacket->strBuf.push_back(flags); + _lastPacket->strBuf.push_back(!is_config); + auto cts = frame->pts() - frame->dts(); + cts = htonl(cts); + _lastPacket->strBuf.append((char *)&cts + 1, 3); + + _lastPacket->chunkId = CHUNK_VIDEO; + _lastPacket->streamId = STREAM_MEDIA; + _lastPacket->timeStamp = frame->dts(); + _lastPacket->typeId = MSG_VIDEO; + + } + auto size = htonl(iLen); + _lastPacket->strBuf.append((char *) &size, 4); + _lastPacket->strBuf.append(pcData, iLen); + _lastPacket->bodySize = _lastPacket->strBuf.size(); } diff --git a/src/Extension/H264Rtmp.h b/src/Extension/H264Rtmp.h index 68802d68..a1cae242 100644 --- a/src/Extension/H264Rtmp.h +++ b/src/Extension/H264Rtmp.h @@ -90,6 +90,11 @@ public: * @param frame 帧数据 */ void inputFrame(const Frame::Ptr &frame) override; + + /** + * 生成config包 + */ + void makeConfigPacket() override; private: void makeVideoConfigPkt(); private: diff --git a/src/Extension/H264Rtp.cpp b/src/Extension/H264Rtp.cpp index 367b3fce..5df37767 100644 --- a/src/Extension/H264Rtp.cpp +++ b/src/Extension/H264Rtp.cpp @@ -70,15 +70,13 @@ H264RtpDecoder::H264RtpDecoder() { H264Frame::Ptr H264RtpDecoder::obtainFrame() { //从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象 auto frame = ResourcePoolHelper::obtainObj(); - frame->buffer.clear(); - frame->iPrefixSize = 4; + frame->_buffer.clear(); + frame->_prefix_size = 4; return frame; } bool H264RtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool key_pos) { - key_pos = decodeRtp(rtp); - RtpCodec::inputRtp(rtp, key_pos); - return key_pos; + return decodeRtp(rtp); } bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { @@ -115,9 +113,9 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { if (nal.type >= 0 && nal.type < 24) { //a full frame - _h264frame->buffer.assign("\x0\x0\x0\x1", 4); - _h264frame->buffer.append((char *)frame, length); - _h264frame->timeStamp = rtppack->timeStamp; + _h264frame->_buffer.assign("\x0\x0\x0\x1", 4); + _h264frame->_buffer.append((char *)frame, length); + _h264frame->_pts = rtppack->timeStamp; auto key = _h264frame->keyFrame(); onGetH264(_h264frame); return (key); //i frame @@ -144,9 +142,9 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { //过小的帧丢弃 NALU nal; MakeNalu(ptr[0], nal); - _h264frame->buffer.assign("\x0\x0\x0\x1", 4); - _h264frame->buffer.append((char *)ptr, len); - _h264frame->timeStamp = rtppack->timeStamp; + _h264frame->_buffer.assign("\x0\x0\x0\x1", 4); + _h264frame->_buffer.append((char *)ptr, len); + _h264frame->_pts = rtppack->timeStamp; if(nal.type == H264Frame::NAL_IDR){ haveIDR = true; } @@ -164,10 +162,10 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { if (fu.S) { //该帧的第一个rtp包 FU-A start char tmp = (nal.forbidden_zero_bit << 7 | nal.nal_ref_idc << 5 | fu.type); - _h264frame->buffer.assign("\x0\x0\x0\x1", 4); - _h264frame->buffer.push_back(tmp); - _h264frame->buffer.append((char *)frame + 2, length - 2); - _h264frame->timeStamp = rtppack->timeStamp; + _h264frame->_buffer.assign("\x0\x0\x0\x1", 4); + _h264frame->_buffer.push_back(tmp); + _h264frame->_buffer.append((char *)frame + 2, length - 2); + _h264frame->_pts = rtppack->timeStamp; //该函数return时,保存下当前sequence,以便下次对比seq是否连续 _lastSeq = rtppack->sequence; return _h264frame->keyFrame(); @@ -175,25 +173,24 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { if (rtppack->sequence != _lastSeq + 1 && rtppack->sequence != 0) { //中间的或末尾的rtp包,其seq必须连续(如果回环了则判定为连续),否则说明rtp丢包,那么该帧不完整,必须得丢弃 - _h264frame->buffer.clear(); + _h264frame->_buffer.clear(); WarnL << "rtp sequence不连续: " << rtppack->sequence << " != " << _lastSeq << " + 1,该帧被废弃"; return false; } if (!fu.E) { //该帧的中间rtp包 FU-A mid - _h264frame->buffer.append((char *)frame + 2, length - 2); + _h264frame->_buffer.append((char *)frame + 2, length - 2); //该函数return时,保存下当前sequence,以便下次对比seq是否连续 _lastSeq = rtppack->sequence; return false; } //该帧最后一个rtp包 FU-A end - _h264frame->buffer.append((char *)frame + 2, length - 2); - _h264frame->timeStamp = rtppack->timeStamp; - auto key = _h264frame->keyFrame(); + _h264frame->_buffer.append((char *)frame + 2, length - 2); + _h264frame->_pts = rtppack->timeStamp; onGetH264(_h264frame); - return key; + return false; } default:{ @@ -211,8 +208,19 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { } void H264RtpDecoder::onGetH264(const H264Frame::Ptr &frame) { - //写入环形缓存 - RtpCodec::inputFrame(frame); + auto flag = _dts_generator.getDts(frame->_pts,frame->_dts); + if(!flag){ + if(frame->configFrame() || frame->keyFrame()){ + flag = true; + frame->_dts = frame->_pts; + } + } + + //根据pts计算dts + if(flag){ + //写入环形缓存 + RtpCodec::inputFrame(frame); + } _h264frame = obtainFrame(); } @@ -232,11 +240,9 @@ H264RtpEncoder::H264RtpEncoder(uint32_t ui32Ssrc, } void H264RtpEncoder::inputFrame(const Frame::Ptr &frame) { - RtpCodec::inputFrame(frame); - GET_CONFIG(uint32_t,cycleMS,Rtp::kCycleMS); auto pcData = frame->data() + frame->prefixSize(); - auto uiStamp = frame->stamp(); + auto uiStamp = frame->pts(); auto iLen = frame->size() - frame->prefixSize(); //获取NALU的5bit 帧类型 unsigned char naluType = H264_TYPE(pcData[0]); diff --git a/src/Extension/H264Rtp.h b/src/Extension/H264Rtp.h index 5ccac81d..da4aa2d6 100644 --- a/src/Extension/H264Rtp.h +++ b/src/Extension/H264Rtp.h @@ -30,6 +30,7 @@ #include "Rtsp/RtpCodec.h" #include "Util/ResourcePool.h" #include "Extension/H264.h" +#include "Common/Stamp.h" using namespace toolkit; namespace mediakit{ @@ -66,6 +67,7 @@ private: H264Frame::Ptr obtainFrame(); private: H264Frame::Ptr _h264frame; + DtsGenerator _dts_generator; int _lastSeq = 0; }; diff --git a/src/Extension/H265.h b/src/Extension/H265.h index 021ef3d1..80591e32 100644 --- a/src/Extension/H265.h +++ b/src/Extension/H265.h @@ -74,23 +74,23 @@ public: } NaleType; char *data() const override { - return (char *) buffer.data(); + return (char *) _buffer.data(); } uint32_t size() const override { - return buffer.size(); + return _buffer.size(); } uint32_t dts() const override { - return timeStamp; + return _dts; } uint32_t pts() const override { - return ptsStamp ? ptsStamp : timeStamp; + return _pts ? _pts : _dts; } uint32_t prefixSize() const override { - return iPrefixSize; + return _prefix_size; } TrackType getTrackType() const override { @@ -102,11 +102,11 @@ public: } bool keyFrame() const override { - return isKeyFrame(H265_TYPE(buffer[iPrefixSize])); + return isKeyFrame(H265_TYPE(_buffer[_prefix_size])); } bool configFrame() const override{ - switch(H265_TYPE(buffer[iPrefixSize])){ + switch(H265_TYPE(_buffer[_prefix_size])){ case H265Frame::NAL_VPS: case H265Frame::NAL_SPS: case H265Frame::NAL_PPS: @@ -131,10 +131,10 @@ public: } public: - uint32_t timeStamp; - uint32_t ptsStamp = 0; - string buffer; - uint32_t iPrefixSize = 4; + uint32_t _dts = 0; + uint32_t _pts = 0; + uint32_t _prefix_size = 4; + string _buffer; }; @@ -356,27 +356,27 @@ private: } if(!_vps.empty()){ auto vpsFrame = std::make_shared(); - vpsFrame->iPrefixSize = 4; - vpsFrame->buffer.assign("\x0\x0\x0\x1", 4); - vpsFrame->buffer.append(_vps); - vpsFrame->timeStamp = frame->stamp(); + vpsFrame->_prefix_size = 4; + vpsFrame->_buffer.assign("\x0\x0\x0\x1", 4); + vpsFrame->_buffer.append(_vps); + vpsFrame->_dts = frame->dts(); VideoTrack::inputFrame(vpsFrame); } if (!_sps.empty()) { auto spsFrame = std::make_shared(); - spsFrame->iPrefixSize = 4; - spsFrame->buffer.assign("\x0\x0\x0\x1", 4); - spsFrame->buffer.append(_sps); - spsFrame->timeStamp = frame->stamp(); + spsFrame->_prefix_size = 4; + spsFrame->_buffer.assign("\x0\x0\x0\x1", 4); + spsFrame->_buffer.append(_sps); + spsFrame->_dts = frame->dts(); VideoTrack::inputFrame(spsFrame); } if (!_pps.empty()) { auto ppsFrame = std::make_shared(); - ppsFrame->iPrefixSize = 4; - ppsFrame->buffer.assign("\x0\x0\x0\x1", 4); - ppsFrame->buffer.append(_pps); - ppsFrame->timeStamp = frame->stamp(); + ppsFrame->_prefix_size = 4; + ppsFrame->_buffer.assign("\x0\x0\x0\x1", 4); + ppsFrame->_buffer.append(_pps); + ppsFrame->_dts = frame->dts(); VideoTrack::inputFrame(ppsFrame); } } diff --git a/src/Extension/H265Rtp.cpp b/src/Extension/H265Rtp.cpp index 6ac9e7e0..8f41ec76 100644 --- a/src/Extension/H265Rtp.cpp +++ b/src/Extension/H265Rtp.cpp @@ -70,15 +70,13 @@ H265RtpDecoder::H265RtpDecoder() { H265Frame::Ptr H265RtpDecoder::obtainFrame() { //从缓存池重新申请对象,防止覆盖已经写入环形缓存的对象 auto frame = ResourcePoolHelper::obtainObj(); - frame->buffer.clear(); - frame->iPrefixSize = 4; + frame->_buffer.clear(); + frame->_prefix_size = 4; return frame; } bool H265RtpDecoder::inputRtp(const RtpPacket::Ptr &rtp, bool key_pos) { - key_pos = decodeRtp(rtp); - RtpCodec::inputRtp(rtp, key_pos); - return key_pos; + return decodeRtp(rtp); } bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { @@ -101,11 +99,11 @@ bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { MakeFU(frame[2], fu); if (fu.S) { //该帧的第一个rtp包 - _h265frame->buffer.assign("\x0\x0\x0\x1", 4); - _h265frame->buffer.push_back(fu.type << 1); - _h265frame->buffer.push_back(0x01); - _h265frame->buffer.append((char *) frame + 3, length - 3); - _h265frame->timeStamp = rtppack->timeStamp; + _h265frame->_buffer.assign("\x0\x0\x0\x1", 4); + _h265frame->_buffer.push_back(fu.type << 1); + _h265frame->_buffer.push_back(0x01); + _h265frame->_buffer.append((char *) frame + 3, length - 3); + _h265frame->_pts = rtppack->timeStamp; //该函数return时,保存下当前sequence,以便下次对比seq是否连续 _lastSeq = rtppack->sequence; return (_h265frame->keyFrame()); //i frame @@ -113,32 +111,31 @@ bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { if (rtppack->sequence != _lastSeq + 1 && rtppack->sequence != 0) { //中间的或末尾的rtp包,其seq必须连续(如果回环了则判定为连续),否则说明rtp丢包,那么该帧不完整,必须得丢弃 - _h265frame->buffer.clear(); + _h265frame->_buffer.clear(); WarnL << "rtp sequence不连续: " << rtppack->sequence << " != " << _lastSeq << " + 1,该帧被废弃"; return false; } if (!fu.E) { //该帧的中间rtp包 - _h265frame->buffer.append((char *) frame + 3, length - 3); + _h265frame->_buffer.append((char *) frame + 3, length - 3); //该函数return时,保存下当前sequence,以便下次对比seq是否连续 _lastSeq = rtppack->sequence; return false; } //该帧最后一个rtp包 - _h265frame->buffer.append((char *) frame + 3, length - 3); - _h265frame->timeStamp = rtppack->timeStamp; - auto key = _h265frame->keyFrame(); + _h265frame->_buffer.append((char *) frame + 3, length - 3); + _h265frame->_pts = rtppack->timeStamp; onGetH265(_h265frame); - return key; + return false; } default: // 4.4.1. Single NAL Unit Packets (p24) //a full frame - _h265frame->buffer.assign("\x0\x0\x0\x1", 4); - _h265frame->buffer.append((char *)frame, length); - _h265frame->timeStamp = rtppack->timeStamp; + _h265frame->_buffer.assign("\x0\x0\x0\x1", 4); + _h265frame->_buffer.append((char *)frame, length); + _h265frame->_pts = rtppack->timeStamp; auto key = _h265frame->keyFrame(); onGetH265(_h265frame); return key; @@ -146,8 +143,18 @@ bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) { } void H265RtpDecoder::onGetH265(const H265Frame::Ptr &frame) { - //写入环形缓存 - RtpCodec::inputFrame(frame); + //计算dts + auto flag = _dts_generator.getDts(frame->_pts,frame->_dts); + if(!flag){ + if(frame->configFrame() || frame->keyFrame()){ + flag = true; + frame->_dts = frame->_pts; + } + } + if(flag){ + //写入环形缓存 + RtpCodec::inputFrame(frame); + } _h265frame = obtainFrame(); } @@ -167,11 +174,9 @@ H265RtpEncoder::H265RtpEncoder(uint32_t ui32Ssrc, } void H265RtpEncoder::inputFrame(const Frame::Ptr &frame) { - RtpCodec::inputFrame(frame); - GET_CONFIG(uint32_t,cycleMS,Rtp::kCycleMS); uint8_t *pcData = (uint8_t*)frame->data() + frame->prefixSize(); - auto uiStamp = frame->stamp(); + auto uiStamp = frame->pts(); auto iLen = frame->size() - frame->prefixSize(); unsigned char naluType = H265_TYPE(pcData[0]); //获取NALU的5bit 帧类型 uiStamp %= cycleMS; diff --git a/src/Extension/H265Rtp.h b/src/Extension/H265Rtp.h index 8b51f880..a2844092 100644 --- a/src/Extension/H265Rtp.h +++ b/src/Extension/H265Rtp.h @@ -30,6 +30,7 @@ #include "Rtsp/RtpCodec.h" #include "Util/ResourcePool.h" #include "Extension/H265.h" +#include "Common/Stamp.h" using namespace toolkit; @@ -67,6 +68,7 @@ private: H265Frame::Ptr obtainFrame(); private: H265Frame::Ptr _h265frame; + DtsGenerator _dts_generator; int _lastSeq = 0; }; diff --git a/src/Extension/SPSParser.c b/src/Extension/SPSParser.c index b073b9cb..54c19592 100644 --- a/src/Extension/SPSParser.c +++ b/src/Extension/SPSParser.c @@ -1330,7 +1330,7 @@ int hevcDecodeShortTermRps(T_GetBitContext *pvBuf, int iDeltaRps; unsigned int uiAbsDeltaRps; uint8_t u8UseDeltaFlag = 0; - uint8_t u8DeltaRpsSign; + uint8_t u8DeltaRpsSign = 0; if (is_slice_header) { unsigned int uiDeltaIdx = parseUe(pvBuf) + 1; diff --git a/src/Extension/Track.h b/src/Extension/Track.h index 5209c74e..ccd675e9 100644 --- a/src/Extension/Track.h +++ b/src/Extension/Track.h @@ -39,7 +39,7 @@ namespace mediakit{ /** * 媒体通道描述类,也支持帧输入输出 */ -class Track : public FrameRingInterfaceDelegate , public CodecInfo{ +class Track : public FrameDispatcher , public CodecInfo{ public: typedef std::shared_ptr Ptr; Track(){} @@ -131,6 +131,35 @@ public: }; +class TrackSource{ +public: + TrackSource(){} + virtual ~TrackSource(){} + + /** + * 获取全部的Track + * @param trackReady 是否获取全部已经准备好的Track + * @return + */ + virtual vector getTracks(bool trackReady = true) const = 0; + + /** + * 获取特定Track + * @param type track类型 + * @param trackReady 是否获取全部已经准备好的Track + * @return + */ + Track::Ptr getTrack(TrackType type , bool trackReady = true) const { + auto tracks = getTracks(trackReady); + for(auto &track : tracks){ + if(track->getTrackType() == type){ + return track; + } + } + return nullptr; + } +}; + }//namespace mediakit #endif //ZLMEDIAKIT_TRACK_H diff --git a/src/Http/HttpBody.h b/src/Http/HttpBody.h index d119cbc5..c82163a4 100644 --- a/src/Http/HttpBody.h +++ b/src/Http/HttpBody.h @@ -32,6 +32,7 @@ #include "Network/Buffer.h" #include "Util/ResourcePool.h" #include "Util/logger.h" +#include "Thread/WorkThreadPool.h" using namespace std; using namespace toolkit; @@ -45,23 +46,55 @@ namespace mediakit { /** * http content部分基类定义 */ -class HttpBody{ +class HttpBody : public std::enable_shared_from_this{ public: typedef std::shared_ptr Ptr; - HttpBody(){} + HttpBody(){ +// _async_read_thread = WorkThreadPool::Instance().getPoller(); + } virtual ~HttpBody(){} /** - * 剩余数据大小 + * 剩余数据大小,如果返回>=INT64_MAX, 那么就不设置content-length */ virtual uint64_t remainSize() { return 0;}; /** * 读取一定字节数,返回大小可能小于size * @param size 请求大小 - * @return 字节对象 + * @return 字节对象,如果读完了,那么请返回nullptr */ virtual Buffer::Ptr readData(uint32_t size) { return nullptr;}; + + /** + * 异步请求读取一定字节数,返回大小可能小于size + * @param size 请求大小 + * @param cb 回调函数 + */ + virtual void readDataAsync(uint32_t size,const function &cb){ +#if 0 + if(size >= remainSize()){ + //假如剩余数据很小,那么同步获取(为了优化性能) + cb(readData(size)); + return; + } + //如果是大文件,那么后台读取 + weak_ptr weakSelf = shared_from_this(); + _async_read_thread->async([cb,size,weakSelf](){ + auto strongSelf = weakSelf.lock(); + if(strongSelf){ + cb(strongSelf->readData(size)); + } + }); +#else + //由于unix和linux是通过mmap的方式读取文件,所以把读文件操作放在后台线程并不能提高性能 + //反而会由于频繁的线程切换导致性能降低以及延时增加,所以我们默认同步获取文件内容 + //(其实并没有读,拷贝文件数据时在内核态完成文件读) + cb(readData(size)); +#endif + } +private: +// EventPoller::Ptr _async_read_thread; }; /** diff --git a/src/Http/HttpCookieManager.cpp b/src/Http/HttpCookieManager.cpp index 8bd5da1b..0c210dab 100644 --- a/src/Http/HttpCookieManager.cpp +++ b/src/Http/HttpCookieManager.cpp @@ -76,8 +76,8 @@ bool HttpServerCookie::isExpired() { return _ticker.elapsedTime() > _max_elapsed * 1000; } -std::shared_ptr > HttpServerCookie::getLock(){ - return std::make_shared >(_mtx); +std::shared_ptr > HttpServerCookie::getLock(){ + return std::make_shared >(_mtx); } string HttpServerCookie::cookieExpireTime() const{ diff --git a/src/Http/HttpCookieManager.h b/src/Http/HttpCookieManager.h index 4d67e386..73f7afae 100644 --- a/src/Http/HttpCookieManager.h +++ b/src/Http/HttpCookieManager.h @@ -108,9 +108,7 @@ public: * 获取区域锁 * @return */ - std::shared_ptr > getLock(); - - + std::shared_ptr > getLock(); private: string cookieExpireTime() const ; private: @@ -119,7 +117,7 @@ private: string _cookie_uuid; uint64_t _max_elapsed; Ticker _ticker; - mutex _mtx; + recursive_mutex _mtx; std::weak_ptr _manager; }; diff --git a/src/Http/HttpFileManager.cpp b/src/Http/HttpFileManager.cpp new file mode 100644 index 00000000..81cf9a8a --- /dev/null +++ b/src/Http/HttpFileManager.cpp @@ -0,0 +1,673 @@ +/* + * MIT License + * + * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#if !defined(_WIN32) +#include +#endif //!defined(_WIN32) +#include +#include "HttpFileManager.h" +#include "Util/File.h" +#include "HttpSession.h" +#include "Record/HlsMediaSource.h" + +namespace mediakit { + +// hls的播放cookie缓存时间默认60秒, +// 每次访问一次该cookie,那么将重新刷新cookie有效期 +// 假如播放器在60秒内都未访问该cookie,那么将重新触发hls播放鉴权 +static int kHlsCookieSecond = 60; +static const string kCookieName = "ZL_COOKIE"; +static const string kHlsSuffix = "/hls.m3u8"; + +class HttpCookieAttachment{ +public: + HttpCookieAttachment() {}; + ~HttpCookieAttachment() {}; +public: + //cookie生效作用域,本cookie只对该目录下的文件生效 + string _path; + //上次鉴权失败信息,为空则上次鉴权成功 + string _err_msg; + //本cookie是否为hls直播的 + bool _is_hls = false; + //hls直播时的其他一些信息,主要用于播放器个数计数以及流量计数 + HlsCookieData::Ptr _hls_data; + //如果是hls直播,那么判断该cookie是否使用过MediaSource::findAsync查找过 + //如果程序未正常退出,会残余上次的hls文件,所以判断hls直播是否存在的关键不是文件存在与否 + //而是应该判断HlsMediaSource是否已注册,但是这样会每次获取m3u8文件时都会用MediaSource::findAsync判断一次 + //会导致程序性能低下,所以我们应该在cookie声明周期的第一次判断HlsMediaSource是否已经注册,后续通过文件存在与否判断 + bool _have_find_media_source = false; +}; + +static const char *s_mime_src[][2] = { + {"html", "text/html"}, + {"htm", "text/html"}, + {"shtml", "text/html"}, + {"css", "text/css"}, + {"xml", "text/xml"}, + {"gif", "image/gif"}, + {"jpeg", "image/jpeg"}, + {"jpg", "image/jpeg"}, + {"js", "application/javascript"}, + {"map", "application/javascript" }, + {"atom", "application/atom+xml"}, + {"rss", "application/rss+xml"}, + {"mml", "text/mathml"}, + {"txt", "text/plain"}, + {"jad", "text/vnd.sun.j2me.app-descriptor"}, + {"wml", "text/vnd.wap.wml"}, + {"htc", "text/x-component"}, + {"png", "image/png"}, + {"tif", "image/tiff"}, + {"tiff", "image/tiff"}, + {"wbmp", "image/vnd.wap.wbmp"}, + {"ico", "image/x-icon"}, + {"jng", "image/x-jng"}, + {"bmp", "image/x-ms-bmp"}, + {"svg", "image/svg+xml"}, + {"svgz", "image/svg+xml"}, + {"webp", "image/webp"}, + {"woff", "application/font-woff"}, + {"woff2","application/font-woff" }, + {"jar", "application/java-archive"}, + {"war", "application/java-archive"}, + {"ear", "application/java-archive"}, + {"json", "application/json"}, + {"hqx", "application/mac-binhex40"}, + {"doc", "application/msword"}, + {"pdf", "application/pdf"}, + {"ps", "application/postscript"}, + {"eps", "application/postscript"}, + {"ai", "application/postscript"}, + {"rtf", "application/rtf"}, + {"m3u8", "application/vnd.apple.mpegurl"}, + {"xls", "application/vnd.ms-excel"}, + {"eot", "application/vnd.ms-fontobject"}, + {"ppt", "application/vnd.ms-powerpoint"}, + {"wmlc", "application/vnd.wap.wmlc"}, + {"kml", "application/vnd.google-earth.kml+xml"}, + {"kmz", "application/vnd.google-earth.kmz"}, + {"7z", "application/x-7z-compressed"}, + {"cco", "application/x-cocoa"}, + {"jardiff", "application/x-java-archive-diff"}, + {"jnlp", "application/x-java-jnlp-file"}, + {"run", "application/x-makeself"}, + {"pl", "application/x-perl"}, + {"pm", "application/x-perl"}, + {"prc", "application/x-pilot"}, + {"pdb", "application/x-pilot"}, + {"rar", "application/x-rar-compressed"}, + {"rpm", "application/x-redhat-package-manager"}, + {"sea", "application/x-sea"}, + {"swf", "application/x-shockwave-flash"}, + {"sit", "application/x-stuffit"}, + {"tcl", "application/x-tcl"}, + {"tk", "application/x-tcl"}, + {"der", "application/x-x509-ca-cert"}, + {"pem", "application/x-x509-ca-cert"}, + {"crt", "application/x-x509-ca-cert"}, + {"xpi", "application/x-xpinstall"}, + {"xhtml", "application/xhtml+xml"}, + {"xspf", "application/xspf+xml"}, + {"zip", "application/zip"}, + {"bin", "application/octet-stream"}, + {"exe", "application/octet-stream"}, + {"dll", "application/octet-stream"}, + {"deb", "application/octet-stream"}, + {"dmg", "application/octet-stream"}, + {"iso", "application/octet-stream"}, + {"img", "application/octet-stream"}, + {"msi", "application/octet-stream"}, + {"msp", "application/octet-stream"}, + {"msm", "application/octet-stream"}, + {"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + {"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, + {"pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, + {"mid", "audio/midi"}, + {"midi", "audio/midi"}, + {"kar", "audio/midi"}, + {"mp3", "audio/mpeg"}, + {"ogg", "audio/ogg"}, + {"m4a", "audio/x-m4a"}, + {"ra", "audio/x-realaudio"}, + {"3gpp", "video/3gpp"}, + {"3gp", "video/3gpp"}, + {"ts", "video/mp2t"}, + {"mp4", "video/mp4"}, + {"mpeg", "video/mpeg"}, + {"mpg", "video/mpeg"}, + {"mov", "video/quicktime"}, + {"webm", "video/webm"}, + {"flv", "video/x-flv"}, + {"m4v", "video/x-m4v"}, + {"mng", "video/x-mng"}, + {"asx", "video/x-ms-asf"}, + {"asf", "video/x-ms-asf"}, + {"wmv", "video/x-ms-wmv"}, + {"avi", "video/x-msvideo"}, +}; + +const string &HttpFileManager::getContentType(const char *name) { + const char *dot; + dot = strrchr(name, '.'); + static StrCaseMap mapType; + static onceToken token([&]() { + for (unsigned int i = 0; i < sizeof (s_mime_src) / sizeof (s_mime_src[0]); ++i) { + mapType.emplace(s_mime_src[i][0], s_mime_src[i][1]); + } + }); + static string defaultType = "text/plain"; + if (!dot) { + return defaultType; + } + auto it = mapType.find(dot + 1); + if (it == mapType.end()) { + return defaultType; + } + return it->second; +} + +static string searchIndexFile(const string &dir){ + DIR *pDir; + dirent *pDirent; + if ((pDir = opendir(dir.data())) == NULL) { + return ""; + } + set setFile; + while ((pDirent = readdir(pDir)) != NULL) { + static set indexSet = {"index.html","index.htm","index"}; + if(indexSet.find(pDirent->d_name) != indexSet.end()){ + string ret = pDirent->d_name; + closedir(pDir); + return ret; + } + } + closedir(pDir); + return ""; +} + +static bool makeFolderMenu(const string &httpPath, const string &strFullPath, string &strRet) { + string strPathPrefix(strFullPath); + string last_dir_name; + if(strPathPrefix.back() == '/'){ + strPathPrefix.pop_back(); + }else{ + last_dir_name = split(strPathPrefix,"/").back(); + } + + if (!File::is_dir(strPathPrefix.data())) { + return false; + } + stringstream ss; + ss << "\r\n" + "\r\n" + "文件索引\r\n" + "\r\n" + "\r\n" + "

文件索引:"; + + ss << httpPath; + ss << "

\r\n"; + if (httpPath != "/") { + ss << "
  • "; + ss << "根目录"; + ss << "
  • \r\n"; + + ss << "
  • "; + ss << "上级目录"; + ss << "
  • \r\n"; + } + + DIR *pDir; + dirent *pDirent; + if ((pDir = opendir(strPathPrefix.data())) == NULL) { + return false; + } + set setFile; + while ((pDirent = readdir(pDir)) != NULL) { + if (File::is_special_dir(pDirent->d_name)) { + continue; + } + if(pDirent->d_name[0] == '.'){ + continue; + } + setFile.emplace(pDirent->d_name); + } + int i = 0; + for(auto &strFile :setFile ){ + string strAbsolutePath = strPathPrefix + "/" + strFile; + bool isDir = File::is_dir(strAbsolutePath.data()); + ss << "
  • " << i++ << "\t"; + ss << ""; + ss << strFile; + if (isDir) { + ss << "/
  • \r\n"; + continue; + } + //是文件 + struct stat fileData; + if (0 == stat(strAbsolutePath.data(), &fileData)) { + auto &fileSize = fileData.st_size; + if (fileSize < 1024) { + ss << " (" << fileData.st_size << "B)" << endl; + } else if (fileSize < 1024 * 1024) { + ss << fixed << setprecision(2) << " (" << fileData.st_size / 1024.0 << "KB)"; + } else if (fileSize < 1024 * 1024 * 1024) { + ss << fixed << setprecision(2) << " (" << fileData.st_size / 1024 / 1024.0 << "MB)"; + } else { + ss << fixed << setprecision(2) << " (" << fileData.st_size / 1024 / 1024 / 1024.0 << "GB)"; + } + } + ss << "\r\n"; + } + closedir(pDir); + ss << "
      \r\n"; + ss << "
    \r\n"; + ss.str().swap(strRet); + return true; +} + +//字符串是否以xx结尾 +static bool end_of(const string &str, const string &substr){ + auto pos = str.rfind(substr); + return pos != string::npos && pos == str.size() - substr.size(); +}; + +//拦截hls的播放请求 +static bool emitHlsPlayed(const Parser &parser, const MediaInfo &mediaInfo, const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender){ + //访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件 + Broadcast::AuthInvoker mediaAuthInvoker = [invoker](const string &err){ + //cookie有效期为kHlsCookieSecond + invoker(err,"",kHlsCookieSecond); + }; + return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,mediaInfo,mediaAuthInvoker,sender); +} + + +/** + * 判断http客户端是否有权限访问文件的逻辑步骤 + * 1、根据http请求头查找cookie,找到进入步骤3 + * 2、根据http url参数查找cookie,如果还是未找到cookie则进入步骤5 + * 3、cookie标记是否有权限访问文件,如果有权限,直接返回文件 + * 4、cookie中记录的url参数是否跟本次url参数一致,如果一致直接返回客户端错误码 + * 5、触发kBroadcastHttpAccess事件 + */ +static void canAccessPath(TcpSession &sender, const Parser &parser, const MediaInfo &mediaInfo, bool is_dir, + const function &callback) { + //获取用户唯一id + auto uid = parser.Params(); + auto path = parser.Url(); + + //先根据http头中的cookie字段获取cookie + HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, parser.getValues()); + //如果不是从http头中找到的cookie,我们让http客户端设置下cookie + bool cookie_from_header = true; + if (!cookie && !uid.empty()) { + //客户端请求中无cookie,再根据该用户的用户id获取cookie + cookie = HttpCookieManager::Instance().getCookieByUid(kCookieName, uid); + cookie_from_header = false; + } + + if (cookie) { + //找到了cookie,对cookie上锁先 + auto lck = cookie->getLock(); + auto attachment = (*cookie)[kCookieName].get(); + if (path.find(attachment._path) == 0) { + //上次cookie是限定本目录 + if (attachment._err_msg.empty()) { + //上次鉴权成功 + if(attachment._is_hls){ + //如果播放的是hls,那么刷新hls的cookie(获取ts文件也会刷新) + cookie->updateTime(); + cookie_from_header = false; + } + callback("", cookie_from_header ? nullptr : cookie); + return; + } + //上次鉴权失败,但是如果url参数发生变更,那么也重新鉴权下 + if (parser.Params().empty() || parser.Params() == cookie->getUid()) { + //url参数未变,或者本来就没有url参数,那么判断本次请求为重复请求,无访问权限 + callback(attachment._err_msg, cookie_from_header ? nullptr : cookie); + return; + } + } + //如果url参数变了或者不是限定本目录,那么旧cookie失效,重新鉴权 + HttpCookieManager::Instance().delCookie(cookie); + } + + bool is_hls = mediaInfo._schema == HLS_SCHEMA; + string identifier = sender.getIdentifier(); + string peer_ip = sender.get_peer_ip(); + uint16_t peer_port = sender.get_peer_port(); + + //该用户从来未获取过cookie,这个时候我们广播是否允许该用户访问该http目录 + HttpSession::HttpAccessPathInvoker accessPathInvoker = [callback, uid, path, is_dir, is_hls, mediaInfo, identifier, peer_ip, peer_port] + (const string &errMsg, const string &cookie_path_in, int cookieLifeSecond) { + HttpServerCookie::Ptr cookie; + if (cookieLifeSecond) { + //本次鉴权设置了有效期,我们把鉴权结果缓存在cookie中 + string cookie_path = cookie_path_in; + if (cookie_path.empty()) { + //如果未设置鉴权目录,那么我们采用当前目录 + cookie_path = is_dir ? path : path.substr(0, path.rfind("/") + 1); + } + + cookie = HttpCookieManager::Instance().addCookie(kCookieName, uid, cookieLifeSecond); + //对cookie上锁 + auto lck = cookie->getLock(); + HttpCookieAttachment attachment; + //记录用户能访问的路径 + attachment._path = cookie_path; + //记录能否访问 + attachment._err_msg = errMsg; + //记录访问的是否为hls + attachment._is_hls = is_hls; + if(is_hls){ + //hls相关信息 + attachment._hls_data = std::make_shared(mediaInfo, identifier, peer_ip, peer_port); + //hls未查找MediaSource + attachment._have_find_media_source = false; + } + (*cookie)[kCookieName].set(std::move(attachment)); + callback(errMsg, cookie); + }else{ + callback(errMsg, nullptr); + } + }; + + if (is_hls && emitHlsPlayed(parser, mediaInfo, accessPathInvoker, sender)) { + //是hls的播放鉴权,拦截之 + return; + } + + //事件未被拦截,则认为是http下载请求 + bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, path, is_dir, accessPathInvoker, sender); + if (!flag) { + //此事件无人监听,我们默认都有权限访问 + callback("", nullptr); + } +} + +/** + * 发送404 Not Found + */ +static void sendNotFound(const HttpFileManager::invoker &cb) { + GET_CONFIG(string,notFound,Http::kNotFound); + cb("404 Not Found","text/html",StrCaseMap(),std::make_shared(notFound)); +} + +/** + * 拼接文件路径 + */ +static string pathCat(const string &a, const string &b){ + if(a.back() == '/'){ + return a + b; + } + return a + '/' + b; +} + +/** + * 访问文件 + * @param sender 事件触发者 + * @param parser http请求 + * @param mediaInfo http url信息 + * @param strFile 文件绝对路径 + * @param cb 回调对象 + */ +static void accessFile(TcpSession &sender, const Parser &parser, const MediaInfo &mediaInfo, const string &strFile, const HttpFileManager::invoker &cb) { + bool is_hls = end_of(strFile, kHlsSuffix); + bool file_exist = File::is_file(strFile.data()); + if (!is_hls && !file_exist) { + //文件不存在且不是hls,那么直接返回404 + sendNotFound(cb); + return; + } + + if(is_hls){ + //hls,那么移除掉后缀获取真实的stream_id并且修改协议为HLS + const_cast(mediaInfo._schema) = HLS_SCHEMA; + replace(const_cast(mediaInfo._streamid), kHlsSuffix, ""); + } + + weak_ptr weakSession = sender.shared_from_this(); + //判断是否有权限访问该文件 + canAccessPath(sender, parser, mediaInfo, false, [cb, strFile, parser, is_hls, mediaInfo, weakSession , file_exist](const string &errMsg, const HttpServerCookie::Ptr &cookie) { + auto strongSession = weakSession.lock(); + if(!strongSession){ + //http客户端已经断开,不需要回复 + return; + } + if (!errMsg.empty()) { + //文件鉴权失败 + StrCaseMap headerOut; + if (cookie) { + headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get()._path); + } + cb("401 Unauthorized", "text/html", headerOut, std::make_shared(errMsg)); + return; + } + + auto response_file = [file_exist](const HttpServerCookie::Ptr &cookie, const HttpFileManager::invoker &cb, const string &strFile, const Parser &parser) { + StrCaseMap httpHeader; + if (cookie) { + httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get()._path); + } + HttpSession::HttpResponseInvoker invoker = [&](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) { + if (cookie && file_exist) { + cookie->getLock(); + auto is_hls = (*cookie)[kCookieName].get()._is_hls; + if (is_hls) { + (*cookie)[kCookieName].get()._hls_data->addByteUsage(body->remainSize()); + } + } + cb(codeOut.data(), HttpFileManager::getContentType(strFile.data()), headerOut, body); + }; + invoker.responseFile(parser.getValues(), httpHeader, strFile); + }; + + if (!is_hls) { + //不是hls,直接回复文件或404 + response_file(cookie, cb, strFile, parser); + } else { + //是hls直播,判断是否存在 + bool have_find_media_src = false; + if(cookie){ + have_find_media_src = (*cookie)[kCookieName].get()._have_find_media_source; + if(!have_find_media_src){ + (*cookie)[kCookieName].get()._have_find_media_source = true; + } + } + if(have_find_media_src){ + //之前该cookie已经通过MediaSource::findAsync查找过了,所以现在只以文件系统查找结果为准 + response_file(cookie, cb, strFile, parser); + return; + } + //hls文件不存在,我们等待其生成并延后回复 + MediaSource::findAsync(mediaInfo, strongSession, [response_file, cookie, cb, strFile, parser](const MediaSource::Ptr &src) { + //hls已经生成或者超时后仍未生成,那么不管怎么样都返回客户端 + response_file(cookie, cb, strFile, parser); + }); + } + }); +} + +static string getFilePath(const Parser &parser,const MediaInfo &mediaInfo, TcpSession &sender){ + GET_CONFIG(bool, enableVhost, General::kEnableVhost); + GET_CONFIG(string, rootPath, Http::kRootPath); + auto ret = File::absolutePath(enableVhost ? mediaInfo._vhost + parser.Url() : parser.Url(), rootPath); + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpBeforeAccess, parser, ret, sender); + return std::move(ret); +} + +/** + * 访问文件或文件夹 + * @param sender 事件触发者 + * @param parser http请求 + * @param cb 回调对象 + */ +void HttpFileManager::onAccessPath(TcpSession &sender, Parser &parser, const HttpFileManager::invoker &cb) { + auto fullUrl = string(HTTP_SCHEMA) + "://" + parser["Host"] + parser.FullUrl(); + MediaInfo mediaInfo(fullUrl); + auto strFile = getFilePath(parser, mediaInfo, sender); + //访问的是文件夹 + if (File::is_dir(strFile.data())) { + auto indexFile = searchIndexFile(strFile); + if (!indexFile.empty()) { + //发现该文件夹下有index文件 + strFile = pathCat(strFile, indexFile); + parser.setUrl(pathCat(parser.Url(), indexFile)); + accessFile(sender, parser, mediaInfo, strFile, cb); + return; + } + string strMenu; + //生成文件夹菜单索引 + if (!makeFolderMenu(parser.Url(), strFile, strMenu)) { + //文件夹不存在 + sendNotFound(cb); + return; + } + //判断是否有权限访问该目录 + canAccessPath(sender, parser, mediaInfo, true, [strMenu, cb](const string &errMsg, const HttpServerCookie::Ptr &cookie) { + if (!errMsg.empty()) { + const_cast(strMenu) = errMsg; + } + StrCaseMap headerOut; + if (cookie) { + headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookieName].get()._path); + } + cb(errMsg.empty() ? "200 OK" : "401 Unauthorized", "text/html", headerOut, std::make_shared(strMenu)); + }); + return; + } + + //访问的是文件 + accessFile(sender, parser, mediaInfo, strFile, cb); +}; + + +////////////////////////////////////HttpResponseInvokerImp////////////////////////////////////// + +void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const{ + if(_lambad){ + _lambad(codeOut,headerOut,body); + } +} + +void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const string &body) const{ + this->operator()(codeOut,headerOut,std::make_shared(body)); +} + +HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda0 &lambda){ + _lambad = lambda; +} + +HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda1 &lambda){ + if(!lambda){ + _lambad = nullptr; + return; + } + _lambad = [lambda](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body){ + string str; + if(body && body->remainSize()){ + str = body->readData(body->remainSize())->toString(); + } + lambda(codeOut,headerOut,str); + }; +} + +void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader, + const StrCaseMap &responseHeader, + const string &filePath) const { + StrCaseMap &httpHeader = const_cast(responseHeader); + std::shared_ptr fp(fopen(filePath.data(), "rb"), [](FILE *fp) { + if (fp) { + fclose(fp); + } + }); + + if (!fp) { + //打开文件失败 + GET_CONFIG(string,notFound,Http::kNotFound); + GET_CONFIG(string,charSet,Http::kCharSet); + + auto strContentType = StrPrinter << "text/html; charset=" << charSet << endl; + httpHeader["Content-Type"] = strContentType; + (*this)("404 Not Found", httpHeader, notFound); + return; + } + + auto &strRange = const_cast(requestHeader)["Range"]; + int64_t iRangeStart = 0; + int64_t iRangeEnd = 0 ; + int64_t fileSize = HttpMultiFormBody::fileSize(fp.get()); + + const char *pcHttpResult = NULL; + if (strRange.size() == 0) { + //全部下载 + pcHttpResult = "200 OK"; + iRangeEnd = fileSize - 1; + } else { + //分节下载 + pcHttpResult = "206 Partial Content"; + iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data()); + iRangeEnd = atoll(FindField(strRange.data(), "-", "\r\n").data()); + if (iRangeEnd == 0) { + iRangeEnd = fileSize - 1; + } + //分节下载返回Content-Range头 + httpHeader.emplace("Content-Range", StrPrinter << "bytes " << iRangeStart << "-" << iRangeEnd << "/" << fileSize << endl); + } + + //回复文件 + HttpBody::Ptr fileBody = std::make_shared(fp, iRangeStart, iRangeEnd - iRangeStart + 1); + (*this)(pcHttpResult, httpHeader, fileBody); +} + +HttpResponseInvokerImp::operator bool(){ + return _lambad.operator bool(); +} + + +}//namespace mediakit \ No newline at end of file diff --git a/src/Http/HttpFileManager.h b/src/Http/HttpFileManager.h new file mode 100644 index 00000000..dc3a613e --- /dev/null +++ b/src/Http/HttpFileManager.h @@ -0,0 +1,87 @@ +/* + * MIT License + * + * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZLMEDIAKIT_HTTPFILEMANAGER_H +#define ZLMEDIAKIT_HTTPFILEMANAGER_H + +#include "HttpBody.h" +#include "HttpCookie.h" +#include "Common/Parser.h" +#include "Network/TcpSession.h" +#include "Util/function_traits.h" + +namespace mediakit { + +class HttpResponseInvokerImp{ +public: + typedef std::function HttpResponseInvokerLambda0; + typedef std::function HttpResponseInvokerLambda1; + + HttpResponseInvokerImp(){} + ~HttpResponseInvokerImp(){} + template + HttpResponseInvokerImp(const C &c):HttpResponseInvokerImp(typename function_traits::stl_function_type(c)) {} + HttpResponseInvokerImp(const HttpResponseInvokerLambda0 &lambda); + HttpResponseInvokerImp(const HttpResponseInvokerLambda1 &lambda); + + void operator()(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const; + void operator()(const string &codeOut, const StrCaseMap &headerOut, const string &body) const; + void responseFile(const StrCaseMap &requestHeader,const StrCaseMap &responseHeader,const string &filePath) const; + operator bool(); +private: + HttpResponseInvokerLambda0 _lambad; +}; + +/** + * 该对象用于控制http静态文件夹服务器的访问权限 + */ +class HttpFileManager { +public: + typedef function invoker; + + /** + * 访问文件或文件夹 + * @param sender 事件触发者 + * @param parser http请求 + * @param cb 回调对象 + */ + static void onAccessPath(TcpSession &sender, Parser &parser, const invoker &cb); + + /** + * 获取mime值 + * @param name 文件后缀 + * @return mime值 + */ + static const string &getContentType(const char *name); +private: + HttpFileManager() = delete; + ~HttpFileManager() = delete; +}; + +} + + +#endif //ZLMEDIAKIT_HTTPFILEMANAGER_H diff --git a/src/Http/HttpRequester.cpp b/src/Http/HttpRequester.cpp index b96c7d45..aa9c962e 100644 --- a/src/Http/HttpRequester.cpp +++ b/src/Http/HttpRequester.cpp @@ -53,6 +53,7 @@ void HttpRequester::onResponseCompleted() { void HttpRequester::onDisconnect(const SockException &ex){ if(_onResult){ + const_cast(response()).setContent(_strRecvBody); _onResult(ex,responseStatus(),responseHeader(),_strRecvBody); _onResult = nullptr; } @@ -69,5 +70,9 @@ void HttpRequester::clear() { _onResult = nullptr; } +void HttpRequester::setOnResult(const HttpRequesterResult &onResult) { + _onResult = onResult; +} + }//namespace mediakit diff --git a/src/Http/HttpRequester.h b/src/Http/HttpRequester.h index dc6ecfb3..9de7523a 100644 --- a/src/Http/HttpRequester.h +++ b/src/Http/HttpRequester.h @@ -38,6 +38,7 @@ public: typedef std::function HttpRequesterResult; HttpRequester(); virtual ~HttpRequester(); + void setOnResult(const HttpRequesterResult &onResult); void startRequester(const string &url,const HttpRequesterResult &onResult,float timeOutSecond = 10); void clear() override ; private: diff --git a/src/Http/HttpSession.cpp b/src/Http/HttpSession.cpp index 10d6b08d..d9144d11 100644 --- a/src/Http/HttpSession.cpp +++ b/src/Http/HttpSession.cpp @@ -35,163 +35,12 @@ #include "Common/config.h" #include "strCoding.h" #include "HttpSession.h" -#include "Util/File.h" -#include "Util/util.h" -#include "Util/TimeTicker.h" -#include "Util/onceToken.h" -#include "Util/mini.h" -#include "Util/NoticeCenter.h" #include "Util/base64.h" #include "Util/SHA1.h" -#include "Rtmp/utils.h" using namespace toolkit; namespace mediakit { -static int kHlsCookieSecond = 10 * 60; -static const string kCookieName = "ZL_COOKIE"; -static const string kCookiePathKey = "kCookiePathKey"; -static const string kAccessErrKey = "kAccessErrKey"; - -string dateStr() { - char buf[64]; - time_t tt = time(NULL); - strftime(buf, sizeof buf, "%a, %b %d %Y %H:%M:%S GMT", gmtime(&tt)); - return buf; -} - -const char *HttpSession::get_mime_type(const char *name) { - const char *dot; - dot = strrchr(name, '.'); - static HttpSession::KeyValue mapType; - static onceToken token([&]() { - mapType.emplace(".html", "text/html"); - mapType.emplace(".htm", "text/html"); - mapType.emplace(".mp4", "video/mp4"); - mapType.emplace(".mkv", "video/x-matroska"); - mapType.emplace(".rmvb", "application/vnd.rn-realmedia"); - mapType.emplace(".rm", "application/vnd.rn-realmedia"); - mapType.emplace(".m3u8", "application/vnd.apple.mpegurl"); - mapType.emplace(".jpg", "image/jpeg"); - mapType.emplace(".jpeg", "image/jpeg"); - mapType.emplace(".gif", "image/gif"); - mapType.emplace(".png", "image/png"); - mapType.emplace(".ico", "image/x-icon"); - mapType.emplace(".css", "text/css"); - mapType.emplace(".js", "application/javascript"); - mapType.emplace(".au", "audio/basic"); - mapType.emplace(".wav", "audio/wav"); - mapType.emplace(".avi", "video/x-msvideo"); - mapType.emplace(".mov", "video/quicktime"); - mapType.emplace(".qt", "video/quicktime"); - mapType.emplace(".mpeg", "video/mpeg"); - mapType.emplace(".mpe", "video/mpeg"); - mapType.emplace(".vrml", "model/vrml"); - mapType.emplace(".wrl", "model/vrml"); - mapType.emplace(".midi", "audio/midi"); - mapType.emplace(".mid", "audio/midi"); - mapType.emplace(".mp3", "audio/mpeg"); - mapType.emplace(".ogg", "application/ogg"); - mapType.emplace(".pac", "application/x-ns-proxy-autoconfig"); - mapType.emplace(".flv", "video/x-flv"); - }, nullptr); - if (!dot) { - return "text/plain"; - } - auto it = mapType.find(dot); - if (it == mapType.end()) { - return "text/plain"; - } - return it->second.data(); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const{ - if(_lambad){ - _lambad(codeOut,headerOut,body); - } -} - -void HttpResponseInvokerImp::operator()(const string &codeOut, const StrCaseMap &headerOut, const string &body) const{ - this->operator()(codeOut,headerOut,std::make_shared(body)); -} - -HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda0 &lambda){ - _lambad = lambda; -} - -HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::HttpResponseInvokerLambda1 &lambda){ - if(!lambda){ - _lambad = nullptr; - return; - } - _lambad = [lambda](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body){ - string str; - if(body && body->remainSize()){ - str = body->readData(body->remainSize())->toString(); - } - lambda(codeOut,headerOut,str); - }; -} - -void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader, - const StrCaseMap &responseHeader, - const string &filePath) const { - StrCaseMap &httpHeader = const_cast(responseHeader); - do { - std::shared_ptr fp(fopen(filePath.data(), "rb"), [](FILE *fp) { - if (fp) { - fclose(fp); - } - }); - if (!fp) { - //打开文件失败 - break; - } - - auto &strRange = const_cast(requestHeader)["Range"]; - int64_t iRangeStart = 0; - int64_t iRangeEnd = 0 ; - int64_t fileSize = HttpMultiFormBody::fileSize(fp.get()); - - const char *pcHttpResult = NULL; - if (strRange.size() == 0) { - //全部下载 - pcHttpResult = "200 OK"; - iRangeEnd = fileSize - 1; - } else { - //分节下载 - pcHttpResult = "206 Partial Content"; - iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data()); - iRangeEnd = atoll(FindField(strRange.data(), "-", "\r\n").data()); - if (iRangeEnd == 0) { - iRangeEnd = fileSize - 1; - } - //分节下载返回Content-Range头 - httpHeader.emplace("Content-Range", StrPrinter << "bytes " << iRangeStart << "-" << iRangeEnd << "/" << fileSize << endl); - } - - //回复文件 - HttpBody::Ptr fileBody = std::make_shared(fp, iRangeStart, iRangeEnd - iRangeStart + 1); - (*this)(pcHttpResult, httpHeader, fileBody); - return; - }while(false); - - GET_CONFIG(string,notFound,Http::kNotFound); - GET_CONFIG(string,charSet,Http::kCharSet); - - auto strContentType = StrPrinter << "text/html; charset=" << charSet << endl; - httpHeader["Content-Type"] = strContentType; - (*this)("404 Not Found", httpHeader, notFound); -} - -HttpResponseInvokerImp::operator bool(){ - return _lambad.operator bool(); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - HttpSession::HttpSession(const Socket::Ptr &pSock) : TcpSession(pSock) { TraceP(this); GET_CONFIG(uint32_t,keep_alive_sec,Http::kKeepAliveSecond); @@ -206,19 +55,18 @@ HttpSession::~HttpSession() { int64_t HttpSession::onRecvHeader(const char *header,uint64_t len) { typedef void (HttpSession::*HttpCMDHandle)(int64_t &); - static unordered_map g_mapCmdIndex; + static unordered_map s_func_map; static onceToken token([]() { - g_mapCmdIndex.emplace("GET",&HttpSession::Handle_Req_GET); - g_mapCmdIndex.emplace("POST",&HttpSession::Handle_Req_POST); + s_func_map.emplace("GET",&HttpSession::Handle_Req_GET); + s_func_map.emplace("POST",&HttpSession::Handle_Req_POST); }, nullptr); _parser.Parse(header); urlDecode(_parser); string cmd = _parser.Method(); - auto it = g_mapCmdIndex.find(cmd); - if (it == g_mapCmdIndex.end()) { + auto it = s_func_map.find(cmd); + if (it == s_func_map.end()) { sendResponse("403 Forbidden", true); - shutdown(SockException(Err_shutdown,StrPrinter << "403 Forbidden:" << cmd)); return 0; } @@ -230,10 +78,6 @@ int64_t HttpSession::onRecvHeader(const char *header,uint64_t len) { auto &fun = it->second; try { (this->*fun)(content_len); - }catch (SockException &ex){ - if(ex){ - shutdown(ex); - } }catch (exception &ex){ shutdown(SockException(Err_shutdown,ex.what())); } @@ -259,21 +103,18 @@ void HttpSession::onRecv(const Buffer::Ptr &pBuf) { void HttpSession::onError(const SockException& err) { if(_is_flv_stream){ + uint64_t duration = _ticker.createdTime()/1000; //flv播放器 - WarnP(this) << "播放器(" + WarnP(this) << "FLV播放器(" << _mediaInfo._vhost << "/" << _mediaInfo._app << "/" << _mediaInfo._streamid - << ")断开:" << err.what(); + << ")断开:" << err.what() + << ",耗时(s):" << duration; GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold); if(_ui64TotalBytes > iFlowThreshold * 1024){ - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, - _mediaInfo, - _ui64TotalBytes, - _ticker.createdTime()/1000, - true, - *this); + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _ui64TotalBytes, duration , true, getIdentifier(), get_peer_ip(), get_peer_port()); } return; } @@ -312,7 +153,7 @@ bool HttpSession::checkWebSocket(){ auto res_cb = [this,headerOut](){ _flv_over_websocket = true; - sendResponse("101 Switching Protocols",false,nullptr,headerOut,nullptr,false); + sendResponse("101 Switching Protocols",false,nullptr,headerOut,nullptr, true); }; //判断是否为websocket-flv @@ -324,12 +165,12 @@ bool HttpSession::checkWebSocket(){ //如果checkLiveFlvStream返回false,则代表不是websocket-flv,而是普通的websocket连接 if(!onWebSocketConnect(_parser)){ sendResponse("501 Not Implemented",true, nullptr, headerOut); - shutdown(SockException(Err_shutdown,"WebSocket server not implemented")); return true; } sendResponse("101 Switching Protocols",false, nullptr,headerOut); return true; } + //http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2 //如果url(除去?以及后面的参数)后缀是.flv,那么表明该url是一个http-flv直播。 bool HttpSession::checkLiveFlvStream(const function &cb){ @@ -350,12 +191,10 @@ bool HttpSession::checkLiveFlvStream(const function &cb){ return false; } _mediaInfo._streamid.erase(_mediaInfo._streamid.size() - 4);//去除.flv后缀 - - GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount); - bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt); + bool bClose = !strcasecmp(_parser["Connection"].data(),"close"); weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); - MediaSource::findAsync(_mediaInfo,weakSelf.lock(), true,[weakSelf,bClose,this,cb](const MediaSource::Ptr &src){ + MediaSource::findAsync(_mediaInfo,weakSelf.lock(),[weakSelf,bClose,this,cb](const MediaSource::Ptr &src){ auto strongSelf = weakSelf.lock(); if(!strongSelf){ //本对象已经销毁 @@ -365,9 +204,6 @@ bool HttpSession::checkLiveFlvStream(const function &cb){ if(!rtmp_src){ //未找到该流 sendNotFound(bClose); - if(bClose){ - shutdown(SockException(Err_shutdown,"flv stream not found")); - } return; } //找到流了 @@ -375,13 +211,12 @@ bool HttpSession::checkLiveFlvStream(const function &cb){ bool authSuccess = err.empty(); if(!authSuccess){ sendResponse("401 Unauthorized", true, nullptr, KeyValue(), std::make_shared(err)); - shutdown(SockException(Err_shutdown,StrPrinter << "401 Unauthorized:" << err)); return ; } if(!cb) { //找到rtmp源,发送http头,负载后续发送 - sendResponse("200 OK", false, "video/x-flv",KeyValue(),nullptr,false); + sendResponse("200 OK", false, "video/x-flv",KeyValue(),nullptr,true); }else{ cb(); } @@ -421,158 +256,6 @@ bool HttpSession::checkLiveFlvStream(const function &cb){ return true; } -bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) ; - -static string findIndexFile(const string &dir){ - DIR *pDir; - dirent *pDirent; - if ((pDir = opendir(dir.data())) == NULL) { - return ""; - } - set setFile; - while ((pDirent = readdir(pDir)) != NULL) { - static set indexSet = {"index.html","index.htm","index"}; - if(indexSet.find(pDirent->d_name) != indexSet.end()){ - closedir(pDir); - return pDirent->d_name; - } - } - closedir(pDir); - return ""; -} - -string HttpSession::getClientUid(){ - //如果http客户端不支持cookie,那么我们可以通过url参数来追踪用户 - //如果url参数也没有,那么只能通过ip+端口号来追踪用户 - //追踪用户的目的是为了减少http短链接情况的重复鉴权验证,通过缓存记录鉴权结果,提高性能 - string uid = _parser.Params(); - if(uid.empty()){ - uid = StrPrinter << get_peer_ip() << ":" << get_peer_port(); - } - return uid; -} - - -//字符串是否以xx结尾 -static bool end_of(const string &str, const string &substr){ - auto pos = str.rfind(substr); - return pos != string::npos && pos == str.size() - substr.size(); -}; - -//拦截hls的播放请求 -static bool checkHls(BroadcastHttpAccessArgs){ - if(!end_of(args._streamid,("/hls.m3u8"))) { - //不是hls - return false; - } - //访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件 - Broadcast::AuthInvoker mediaAuthInvoker = [invoker,path](const string &err){ - //cookie有效期为kHlsCookieSecond - invoker(err,"",kHlsCookieSecond); - }; - - auto args_copy = args; - replace(args_copy._streamid,"/hls.m3u8",""); - return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,args_copy,mediaAuthInvoker,sender); -} - -void HttpSession::canAccessPath(const string &path,bool is_dir,const function &callback_in){ - auto callback = [callback_in,this](const string &errMsg,const HttpServerCookie::Ptr &cookie){ - try { - callback_in(errMsg,cookie); - }catch (SockException &ex){ - if(ex){ - shutdown(ex); - } - }catch (exception &ex){ - shutdown(SockException(Err_shutdown,ex.what())); - } - }; - - //获取用户唯一id - auto uid = getClientUid(); - //先根据http头中的cookie字段获取cookie - HttpServerCookie::Ptr cookie = HttpCookieManager::Instance().getCookie(kCookieName, _parser.getValues()); - //如果不是从http头中找到的cookie,我们让http客户端设置下cookie - bool cookie_from_header = true; - if(!cookie){ - //客户端请求中无cookie,再根据该用户的用户id获取cookie - cookie = HttpCookieManager::Instance().getCookieByUid(kCookieName, uid); - cookie_from_header = false; - } - - if(cookie){ - //找到了cookie,对cookie上锁先 - auto lck = cookie->getLock(); - auto accessErr = (*cookie)[kAccessErrKey].get(); - auto cookiePath = (*cookie)[kCookiePathKey].get(); - if(path.find(cookiePath) == 0){ - //上次cookie是限定本目录 - if(accessErr.empty()){ - //上次鉴权成功 - callback("", cookie_from_header ? nullptr : cookie); - return; - } - //上次鉴权失败,但是如果url参数发生变更,那么也重新鉴权下 - if (_parser.Params().empty() || _parser.Params() == cookie->getUid()) { - //url参数未变,或者本来就没有url参数,那么判断本次请求为重复请求,无访问权限 - callback(accessErr, cookie_from_header ? nullptr : cookie); - return; - } - } - //如果url参数变了或者不是限定本目录,那么旧cookie失效,重新鉴权 - HttpCookieManager::Instance().delCookie(cookie); - } - - //该用户从来未获取过cookie,这个时候我们广播是否允许该用户访问该http目录 - weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); - HttpAccessPathInvoker accessPathInvoker = [weakSelf,callback,uid,path,is_dir] (const string &errMsg,const string &cookie_path_in, int cookieLifeSecond) { - HttpServerCookie::Ptr cookie ; - if(cookieLifeSecond) { - //本次鉴权设置了有效期,我们把鉴权结果缓存在cookie中 - string cookie_path = cookie_path_in; - if(cookie_path.empty()){ - //如果未设置鉴权目录,那么我们采用当前目录 - cookie_path = is_dir ? path : path.substr(0,path.rfind("/") + 1); - } - - cookie = HttpCookieManager::Instance().addCookie(kCookieName, uid, cookieLifeSecond); - //对cookie上锁 - auto lck = cookie->getLock(); - //记录用户能访问的路径 - (*cookie)[kCookiePathKey].set(cookie_path); - //记录能否访问 - (*cookie)[kAccessErrKey].set(errMsg); - } - - auto strongSelf = weakSelf.lock(); - if (!strongSelf) { - //自己已经销毁 - return; - } - strongSelf->async([weakSelf,callback,cookie,errMsg]() { - //切换到自己线程 - auto strongSelf = weakSelf.lock(); - if (!strongSelf) { - //自己已经销毁 - return; - } - callback(errMsg, cookie); - }); - }; - - if(checkHls(_parser,_mediaInfo,path,is_dir,accessPathInvoker,*this)){ - //是hls的播放鉴权,拦截之 - return; - } - - bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess,_parser,_mediaInfo,path,is_dir,accessPathInvoker,*this); - if(!flag){ - //此事件无人监听,我们默认都有权限访问 - callback("", nullptr); - } - -} void HttpSession::Handle_Req_GET(int64_t &content_len) { //先看看是否为WebSocket请求 @@ -596,220 +279,159 @@ void HttpSession::Handle_Req_GET(int64_t &content_len) { return; } - //事件未被拦截,则认为是http下载请求 - auto fullUrl = string(HTTP_SCHEMA) + "://" + _parser["Host"] + _parser.FullUrl(); - _mediaInfo.parse(fullUrl); + bool bClose = !strcasecmp(_parser["Connection"].data(),"close"); - /////////////HTTP连接是否需要被关闭//////////////// - GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount); - GET_CONFIG(bool,enableVhost,General::kEnableVhost); - GET_CONFIG(string,rootPath,Http::kRootPath); - auto strFile = File::absolutePath(enableVhost ? _mediaInfo._vhost + _parser.Url() : _parser.Url(),rootPath); - bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt); - - do{ - //访问的是文件夹 - if (strFile.back() == '/' || File::is_dir(strFile.data())) { - auto indexFile = findIndexFile(strFile); - if(!indexFile.empty()){ - //发现该文件夹下有index文件 - strFile = strFile + "/" + indexFile; - _parser.setUrl(_parser.Url() + "/" + indexFile); - break; - } - string strMeun; - //生成文件夹菜单索引 - if (!makeMeun(_parser.Url(),strFile,strMeun)) { - //文件夹不存在 - sendNotFound(bClose); - throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on folder"); - } - - //判断是否有权限访问该目录 - canAccessPath(_parser.Url(),true,[this,bClose,strFile,strMeun](const string &errMsg,const HttpServerCookie::Ptr &cookie){ - if(!errMsg.empty()){ - const_cast(strMeun) = errMsg; - } - KeyValue headerOut; - if(cookie){ - headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get()); - } - sendResponse(errMsg.empty() ? "200 OK" : "401 Unauthorized" ,bClose, "text/html", headerOut, std::make_shared(strMeun)); - throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access folder"); - }); + weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + HttpFileManager::onAccessPath(*this, _parser, [weakSelf, bClose](const string &status_code, const string &content_type, + const StrCaseMap &responseHeader, const HttpBody::Ptr &body) { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { return; } - }while(0); - - auto parser = _parser; - //判断是否有权限访问该文件 - canAccessPath(_parser.Url(),false,[this,parser,bClose,strFile](const string &errMsg,const HttpServerCookie::Ptr &cookie){ - if(!errMsg.empty()){ - KeyValue headerOut; - if(cookie){ - headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get()); + strongSelf->async([weakSelf, bClose, status_code, content_type, responseHeader, body]() { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return; } - sendResponse("401 Unauthorized" ,bClose, nullptr, headerOut, std::make_shared(errMsg)); - throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file failed"); - } - - KeyValue httpHeader; - if(cookie){ - httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get()); - } - - HttpResponseInvoker invoker = [this,bClose,&strFile](const string &codeOut, const KeyValue &headerOut, const HttpBody::Ptr &body){ - sendResponse(codeOut.data(), bClose, get_mime_type(strFile.data()), headerOut, body); - }; - invoker.responseFile(parser.getValues(),httpHeader,strFile); + strongSelf->sendResponse(status_code.data(), bClose, content_type.data(), responseHeader, body); + }); }); } -bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) { - string strPathPrefix(strFullPath); - string last_dir_name; - if(strPathPrefix.back() == '/'){ - strPathPrefix.pop_back(); - }else{ - last_dir_name = split(strPathPrefix,"/").back(); - } - - if (!File::is_dir(strPathPrefix.data())) { - return false; - } - stringstream ss; - ss << "\r\n" - "\r\n" - "文件索引\r\n" - "\r\n" - "\r\n" - "

    文件索引:"; - - ss << httpPath; - ss << "

    \r\n"; - if (httpPath != "/") { - ss << "
  • "; - ss << "根目录"; - ss << "
  • \r\n"; - - ss << "
  • "; - ss << "上级目录"; - ss << "
  • \r\n"; - } - - DIR *pDir; - dirent *pDirent; - if ((pDir = opendir(strPathPrefix.data())) == NULL) { - return false; - } - set setFile; - while ((pDirent = readdir(pDir)) != NULL) { - if (File::is_special_dir(pDirent->d_name)) { - continue; - } - if(pDirent->d_name[0] == '.'){ - continue; - } - setFile.emplace(pDirent->d_name); - } - int i = 0; - for(auto &strFile :setFile ){ - string strAbsolutePath = strPathPrefix + "/" + strFile; - bool isDir = File::is_dir(strAbsolutePath.data()); - ss << "
  • " << i++ << "\t"; - ss << ""; - ss << strFile; - if (isDir) { - ss << "/
  • \r\n"; - continue; - } - //是文件 - struct stat fileData; - if (0 == stat(strAbsolutePath.data(), &fileData)) { - auto &fileSize = fileData.st_size; - if (fileSize < 1024) { - ss << " (" << fileData.st_size << "B)" << endl; - } else if (fileSize < 1024 * 1024) { - ss << fixed << setprecision(2) << " (" << fileData.st_size / 1024.0 << "KB)"; - } else if (fileSize < 1024 * 1024 * 1024) { - ss << fixed << setprecision(2) << " (" << fileData.st_size / 1024 / 1024.0 << "MB)"; - } else { - ss << fixed << setprecision(2) << " (" << fileData.st_size / 1024 / 1024 / 1024.0 << "GB)"; - } - } - ss << "\r\n"; - } - closedir(pDir); - ss << "
      \r\n"; - ss << "
    \r\n"; - ss.str().swap(strRet); - return true; +static string dateStr() { + char buf[64]; + time_t tt = time(NULL); + strftime(buf, sizeof buf, "%a, %b %d %Y %H:%M:%S GMT", gmtime(&tt)); + return buf; } +class AsyncSenderData { +public: + friend class AsyncSender; + typedef std::shared_ptr Ptr; + AsyncSenderData(const TcpSession::Ptr &session, const HttpBody::Ptr &body, bool close_when_complete) { + _session = dynamic_pointer_cast(session); + _body = body; + _close_when_complete = close_when_complete; + } + ~AsyncSenderData() = default; +private: + std::weak_ptr _session; + HttpBody::Ptr _body; + bool _close_when_complete; + bool _read_complete = false; +}; + +class AsyncSender { +public: + typedef std::shared_ptr Ptr; + static bool onSocketFlushed(const AsyncSenderData::Ptr &data) { + if (data->_read_complete) { + if (data->_close_when_complete) { + //发送完毕需要关闭socket + shutdown(data->_session.lock()); + } + return false; + } + + GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize); + 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); + }, false); + }); + return true; + } + +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还可写,继续请求数据 + onSocketFlushed(data); + } + return; + } + //文件写完了 + data->_read_complete = true; + if (!session->isSocketBusy() && data->_close_when_complete) { + shutdown(session); + } + } + + static void shutdown(const std::shared_ptr &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"; +static const string kServerName = SERVER_NAME; void HttpSession::sendResponse(const char *pcStatus, bool bClose, const char *pcContentType, const HttpSession::KeyValue &header, const HttpBody::Ptr &body, - bool set_content_len ){ - + bool is_http_flv ){ GET_CONFIG(string,charSet,Http::kCharSet); GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond); - GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount); //body默认为空 int64_t size = 0; if (body && body->remainSize()) { //有body,获取body大小 size = body->remainSize(); - if (size >= INT64_MAX) { - //不固定长度的body,那么不设置content-length字段 - size = -1; - } } - if(!set_content_len || size == -1){ - //如果是不定长度body,或者不设置conten-length, - //那么一定是Keep-Alive类型 + if(is_http_flv){ + //http-flv直播是Keep-Alive类型 bClose = false; + }else if(size >= INT64_MAX){ + //不固定长度的body,那么发送完body后应该关闭socket,以便浏览器做下载完毕的判断 + bClose = true; } HttpSession::KeyValue &headerOut = const_cast(header); - headerOut.emplace("Date", dateStr()); - headerOut.emplace("Server", SERVER_NAME); - headerOut.emplace("Connection", bClose ? "close" : "keep-alive"); + headerOut.emplace(kDate, dateStr()); + headerOut.emplace(kServer, kServerName); + headerOut.emplace(kConnection, bClose ? "close" : "keep-alive"); if(!bClose){ - headerOut.emplace("Keep-Alive",StrPrinter << "timeout=" << keepAliveSec << ", max=" << reqCnt << endl); + string keepAliveString = "timeout="; + keepAliveString += to_string(keepAliveSec); + keepAliveString += ", max=100"; + headerOut.emplace(kKeepAlive,std::move(keepAliveString)); } if(!_origin.empty()){ //设置跨域 - headerOut.emplace("Access-Control-Allow-Origin",_origin); - headerOut.emplace("Access-Control-Allow-Credentials", "true"); + headerOut.emplace(kAccessControlAllowOrigin,_origin); + headerOut.emplace(kAccessControlAllowCredentials, "true"); } - if(set_content_len && size >= 0){ - //文件长度为定值或者,且不是http-flv强制设置Content-Length - headerOut["Content-Length"] = StrPrinter << size << endl; + if(!is_http_flv && size >= 0 && size < INT64_MAX){ + //文件长度为固定值,且不是http-flv强制设置Content-Length + headerOut[kContentLength] = to_string(size); } if(size && !pcContentType){ @@ -819,82 +441,48 @@ void HttpSession::sendResponse(const char *pcStatus, if(size && pcContentType){ //有body时,设置文件类型 - auto strContentType = StrPrinter << pcContentType << "; charset=" << charSet << endl; - headerOut.emplace("Content-Type",strContentType); + string strContentType = pcContentType; + strContentType += "; charset="; + strContentType += charSet; + headerOut.emplace(kContentType,std::move(strContentType)); } //发送http头 - _StrPrinter printer; - printer << "HTTP/1.1 " << pcStatus << "\r\n"; + string str; + str.reserve(256); + str += "HTTP/1.1 " ; + str += pcStatus ; + str += "\r\n"; for (auto &pr : header) { - printer << pr.first << ": " << pr.second << "\r\n"; + str += pr.first ; + str += ": "; + str += pr.second; + str += "\r\n"; } - - printer << "\r\n"; - send(printer << endl); + str += "\r\n"; + send(std::move(str)); _ticker.resetTime(); if(!size){ //没有body if(bClose){ - shutdown(SockException(Err_shutdown,"close connection after send http header completed")); + shutdown(SockException(Err_shutdown,StrPrinter << "close connection after send http header completed with status code:" << pcStatus)); } return; } - //发送http body - GET_CONFIG(uint32_t,sendBufSize,Http::kSendBufSize); - weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); - - auto onFlush = [body,bClose,weakSelf]() { - auto strongSelf = weakSelf.lock(); - if(!strongSelf){ - //本对象已经销毁 - return false; - } - while(true){ - //更新超时计时器 - strongSelf->_ticker.resetTime(); - //读取文件 - auto sendBuf = body->readData(sendBufSize); - if (!sendBuf) { - //文件读完 - if(strongSelf->isSocketBusy() && bClose){ - //套接字忙,我们等待触发下一次onFlush事件 - //待所有数据flush到socket fd再移除onFlush事件监听 - //标记文件读写完毕 - return true; - } - //文件全部flush到socket fd,可以直接关闭socket了 - break; - } - - //文件还未读完 - if(strongSelf->send(sendBuf) == -1) { - //socket已经销毁,不再监听onFlush事件 - return false; - } - if(strongSelf->isSocketBusy()){ - //socket忙,那么停止继续写,等待下一次onFlush事件,然后再读文件写socket - return true; - } - //socket还可写,继续写socket - } - - if(bClose) { - //最后一次flush事件,文件也发送完毕了,可以关闭socket了 - strongSelf->shutdown(SockException(Err_shutdown,"close connection after send http body completed")); - } - //不再监听onFlush事件 - return false; - }; - + GET_CONFIG(uint32_t, sendBufSize, Http::kSendBufSize); if(body->remainSize() > sendBufSize){ //文件下载提升发送性能 setSocketFlags(); } - onFlush(); - _sock->setOnFlush(onFlush); + + //发送http body + AsyncSenderData::Ptr data = std::make_shared(shared_from_this(),body,bClose); + _sock->setOnFlush([data](){ + return AsyncSender::onSocketFlushed(data); + }); + AsyncSender::onSocketFlushed(data); } string HttpSession::urlDecode(const string &str){ @@ -917,10 +505,7 @@ void HttpSession::urlDecode(Parser &parser){ } bool HttpSession::emitHttpEvent(bool doInvoke){ - ///////////////////是否断开本链接/////////////////////// - GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount); - - bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt); + bool bClose = !strcasecmp(_parser["Connection"].data(),"close"); /////////////////////异步回复Invoker/////////////////////////////// weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); HttpResponseInvoker invoker = [weakSelf,bClose](const string &codeOut, const KeyValue &headerOut, const HttpBody::Ptr &body){ @@ -934,13 +519,6 @@ bool HttpSession::emitHttpEvent(bool doInvoke){ //本对象已经销毁 return; } - - if(codeOut.empty()){ - //回调提供的参数异常 - strongSelf->sendNotFound(bClose); - return; - } - strongSelf->sendResponse(codeOut.data(), bClose, nullptr, headerOut, body); }); }; @@ -950,17 +528,12 @@ bool HttpSession::emitHttpEvent(bool doInvoke){ if(!consumed && doInvoke){ //该事件无人消费,所以返回404 invoker("404 Not Found",KeyValue(), HttpBody::Ptr()); - if(bClose){ - //close类型,回复完毕,关闭连接 - shutdown(SockException(Err_shutdown,"404 Not Found")); - } } return consumed; } void HttpSession::Handle_Req_POST(int64_t &content_len) { GET_CONFIG(uint64_t,maxReqSize,Http::kMaxReqSize); - GET_CONFIG(int,maxReqCnt,Http::kMaxReqCount); int64_t totalContentLen = _parser["Content-Length"].empty() ? -1 : atoll(_parser["Content-Length"].data()); @@ -1000,7 +573,7 @@ void HttpSession::Handle_Req_POST(int64_t &content_len) { content_len = -1; auto parserCopy = _parser; std::shared_ptr recvedContentLen = std::make_shared(0); - bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > maxReqCnt); + bool bClose = !strcasecmp(_parser["Connection"].data(),"close"); _contentCallBack = [this,parserCopy,totalContentLen,recvedContentLen,bClose](const char *data,uint64_t len){ *(recvedContentLen) += len; diff --git a/src/Http/HttpSession.h b/src/Http/HttpSession.h index 98f234c3..0d9ce18e 100644 --- a/src/Http/HttpSession.h +++ b/src/Http/HttpSession.h @@ -27,46 +27,19 @@ #define SRC_HTTP_HTTPSESSION_H_ #include -#include "Common/config.h" -#include "Common/Parser.h" #include "Network/TcpSession.h" -#include "Network/TcpServer.h" #include "Rtmp/RtmpMediaSource.h" #include "Rtmp/FlvMuxer.h" #include "HttpRequestSplitter.h" #include "WebSocketSplitter.h" #include "HttpCookieManager.h" -#include "HttpBody.h" -#include "Util/function_traits.h" +#include "HttpFileManager.h" using namespace std; using namespace toolkit; namespace mediakit { -/** - * 该类实现与老代码的兼容适配 - */ -class HttpResponseInvokerImp{ -public: - typedef std::function HttpResponseInvokerLambda0; - typedef std::function HttpResponseInvokerLambda1; - - HttpResponseInvokerImp(){} - ~HttpResponseInvokerImp(){} - template - HttpResponseInvokerImp(const C &c):HttpResponseInvokerImp(typename function_traits::stl_function_type(c)) {} - HttpResponseInvokerImp(const HttpResponseInvokerLambda0 &lambda); - HttpResponseInvokerImp(const HttpResponseInvokerLambda1 &lambda); - - void operator()(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const; - void operator()(const string &codeOut, const StrCaseMap &headerOut, const string &body) const; - void responseFile(const StrCaseMap &requestHeader,const StrCaseMap &responseHeader,const string &filePath) const; - operator bool(); -private: - HttpResponseInvokerLambda0 _lambad; -}; - class HttpSession: public TcpSession, public FlvMuxer, public HttpRequestSplitter, @@ -74,7 +47,7 @@ class HttpSession: public TcpSession, public: typedef StrCaseMap KeyValue; typedef HttpResponseInvokerImp HttpResponseInvoker; - + friend class AsyncSender; /** * @param errMsg 如果为空,则代表鉴权通过,否则为错误提示 * @param accessPath 运行或禁止访问的根目录 @@ -88,9 +61,7 @@ public: virtual void onRecv(const Buffer::Ptr &) override; virtual void onError(const SockException &err) override; virtual void onManager() override; - static string urlDecode(const string &str); - static const char* get_mime_type(const char* name); protected: //FlvMuxer override void onWrite(const Buffer::Ptr &data) override ; @@ -144,27 +115,7 @@ private: void sendNotFound(bool bClose); void sendResponse(const char *pcStatus, bool bClose, const char *pcContentType = nullptr, const HttpSession::KeyValue &header = HttpSession::KeyValue(), - const HttpBody::Ptr &body = nullptr,bool set_content_len = true); - /** - * 判断http客户端是否有权限访问文件的逻辑步骤 - * - * 1、根据http请求头查找cookie,找到进入步骤3 - * 2、根据http url参数(如果没有根据ip+端口号)查找cookie,如果还是未找到cookie则进入步骤5 - * 3、cookie标记是否有权限访问文件,如果有权限,直接返回文件 - * 4、cookie中记录的url参数是否跟本次url参数一致,如果一致直接返回客户端错误码 - * 5、触发kBroadcastHttpAccess事件 - * @param path 文件或目录 - * @param is_dir path是否为目录 - * @param callback 有权限或无权限的回调 - */ - void canAccessPath(const string &path,bool is_dir,const function &callback); - - /** - * 获取用户唯一识别id - * 有url参数返回参数,无参数返回ip+端口号 - * @return - */ - string getClientUid(); + const HttpBody::Ptr &body = nullptr,bool is_http_flv = false); //设置socket标志 void setSocketFlags(); @@ -172,7 +123,6 @@ private: string _origin; Parser _parser; Ticker _ticker; - uint32_t _iReqCnt = 0; //消耗的总流量 uint64_t _ui64TotalBytes = 0; //flv over http diff --git a/src/Http/WebSocketClient.h b/src/Http/WebSocketClient.h index 6eeae0ac..e3d215a7 100644 --- a/src/Http/WebSocketClient.h +++ b/src/Http/WebSocketClient.h @@ -89,6 +89,7 @@ public: HttpWsClient(ClientTypeImp &delegate) : _delegate(delegate){ _Sec_WebSocket_Key = encodeBase64(SHA1::encode_bin(makeRandStr(16, false))); + setPoller(delegate.getPoller()); } ~HttpWsClient(){} @@ -302,7 +303,7 @@ private: //拦截websocket数据接收 _onRecv = [this](const Buffer::Ptr &pBuf){ //解析websocket数据包 - WebSocketSplitter::decode((uint8_t*)pBuf->data(),pBuf->size()); + this->WebSocketSplitter::decode((uint8_t*)pBuf->data(),pBuf->size()); }; return; } @@ -348,21 +349,25 @@ public: /** * 重载startConnect方法, * 目的是替换TcpClient的连接服务器行为,使之先完成WebSocket握手 - * @param strUrl websocket服务器ip或域名 + * @param host websocket服务器ip或域名 * @param iPort websocket服务器端口 * @param fTimeOutSec 超时时间 */ - void startConnect(const string &strUrl, uint16_t iPort, float fTimeOutSec = 3) override { + void startConnect(const string &host, uint16_t iPort, float fTimeOutSec = 3) override { string ws_url; if(useWSS){ //加密的ws - ws_url = StrPrinter << "wss://" + strUrl << ":" << iPort << "/" ; + ws_url = StrPrinter << "wss://" + host << ":" << iPort << "/" ; }else{ //明文ws - ws_url = StrPrinter << "ws://" + strUrl << ":" << iPort << "/" ; + ws_url = StrPrinter << "ws://" + host << ":" << iPort << "/" ; } _wsClient->startWsClient(ws_url,fTimeOutSec); } + + void startWebSocket(const string &ws_url,float fTimeOutSec = 3){ + _wsClient->startWsClient(ws_url,fTimeOutSec); + } private: typename HttpWsClient::Ptr _wsClient; }; diff --git a/src/Http/WebSocketSession.h b/src/Http/WebSocketSession.h index fe4d443a..af302b8a 100644 --- a/src/Http/WebSocketSession.h +++ b/src/Http/WebSocketSession.h @@ -28,17 +28,82 @@ #define ZLMEDIAKIT_WEBSOCKETSESSION_H #include "HttpSession.h" +#include "Network/TcpServer.h" + +/** + * 数据发送拦截器 + */ +class SendInterceptor{ +public: + typedef function onBeforeSendCB; + SendInterceptor() = default; + virtual ~SendInterceptor() = default; + virtual void setOnBeforeSendCB(const onBeforeSendCB &cb) = 0; +}; + +/** + * 该类实现了TcpSession派生类发送数据的截取 + * 目的是发送业务数据前进行websocket协议的打包 + */ +template +class TcpSessionTypeImp : public TcpSessionType, public SendInterceptor{ +public: + typedef std::shared_ptr Ptr; + + TcpSessionTypeImp(const Parser &header, const HttpSession &parent, const Socket::Ptr &pSock) : + _identifier(parent.getIdentifier()), TcpSessionType(pSock) {} + + ~TcpSessionTypeImp() {} + + /** + * 设置发送数据截取回调函数 + * @param cb 截取回调函数 + */ + void setOnBeforeSendCB(const onBeforeSendCB &cb) override { + _beforeSendCB = cb; + } + +protected: + /** + * 重载send函数截取数据 + * @param buf 需要截取的数据 + * @return 数据字节数 + */ + int send(const Buffer::Ptr &buf) override { + if (_beforeSendCB) { + return _beforeSendCB(buf); + } + return TcpSessionType::send(buf); + } + + string getIdentifier() const override { + return _identifier; + } + +private: + onBeforeSendCB _beforeSendCB; + string _identifier; +}; + +template +class TcpSessionCreator { +public: + //返回的TcpSession必须派生于SendInterceptor,可以返回null + TcpSession::Ptr operator()(const Parser &header, const HttpSession &parent, const Socket::Ptr &pSock){ + return std::make_shared >(header,parent,pSock); + } +}; + /** * 通过该模板类可以透明化WebSocket协议, * 用户只要实现WebSock协议下的具体业务协议,譬如基于WebSocket协议的Rtmp协议等 -* @tparam SessionType 业务协议的TcpSession类 */ -template -class WebSocketSession : public HttpSessionType { +template +class WebSocketSessionBase : public HttpSessionType { public: - WebSocketSession(const Socket::Ptr &pSock) : HttpSessionType(pSock){} - virtual ~WebSocketSession(){} + WebSocketSessionBase(const Socket::Ptr &pSock) : HttpSessionType(pSock){} + virtual ~WebSocketSessionBase(){} //收到eof或其他导致脱离TcpServer事件的回调 void onError(const SockException &err) override{ @@ -68,23 +133,27 @@ protected: */ bool onWebSocketConnect(const Parser &header) override{ //创建websocket session类 - _session = std::make_shared(HttpSessionType::getIdentifier(),HttpSessionType::_sock); + _session = _creator(header, *this,HttpSessionType::_sock); + if(!_session){ + //此url不允许创建websocket连接 + return false; + } auto strongServer = _weakServer.lock(); if(strongServer){ _session->attachServer(*strongServer); } //此处截取数据并进行websocket协议打包 - weak_ptr weakSelf = dynamic_pointer_cast(HttpSessionType::shared_from_this()); - _session->setOnBeforeSendCB([weakSelf](const Buffer::Ptr &buf){ + weak_ptr weakSelf = dynamic_pointer_cast(HttpSessionType::shared_from_this()); + dynamic_pointer_cast(_session)->setOnBeforeSendCB([weakSelf](const Buffer::Ptr &buf) { auto strongSelf = weakSelf.lock(); - if(strongSelf){ + if (strongSelf) { WebSocketHeader header; header._fin = true; header._reserved = 0; header._opcode = DataType; header._mask_flag = false; - strongSelf->WebSocketSplitter::encode(header,buf); + strongSelf->WebSocketSplitter::encode(header, buf); } return buf->size(); }); @@ -154,50 +223,19 @@ protected: void onWebSocketEncodeData(const Buffer::Ptr &buffer) override{ SocketHelper::send(buffer); } -private: - typedef function onBeforeSendCB; - /** - * 该类实现了TcpSession派生类发送数据的截取 - * 目的是发送业务数据前进行websocket协议的打包 - */ - class SessionImp : public SessionType{ - public: - SessionImp(const string &identifier,const Socket::Ptr &pSock) : - _identifier(identifier),SessionType(pSock){} - - ~SessionImp(){} - - /** - * 设置发送数据截取回调函数 - * @param cb 截取回调函数 - */ - void setOnBeforeSendCB(const onBeforeSendCB &cb){ - _beforeSendCB = cb; - } - protected: - /** - * 重载send函数截取数据 - * @param buf 需要截取的数据 - * @return 数据字节数 - */ - int send(const Buffer::Ptr &buf) override { - if(_beforeSendCB){ - return _beforeSendCB(buf); - } - return SessionType::send(buf); - } - string getIdentifier() const override{ - return _identifier; - } - private: - onBeforeSendCB _beforeSendCB; - string _identifier; - }; private: string _remian_data; weak_ptr _weakServer; - std::shared_ptr _session; + TcpSession::Ptr _session; + Creator _creator; }; +template +class WebSocketSession : public WebSocketSessionBase,HttpSessionType,DataType>{ +public: + WebSocketSession(const Socket::Ptr &pSock) : WebSocketSessionBase,HttpSessionType,DataType>(pSock){} + virtual ~WebSocketSession(){} +}; + #endif //ZLMEDIAKIT_WEBSOCKETSESSION_H diff --git a/src/MediaFile/MediaRecorder.cpp b/src/MediaFile/MediaRecorder.cpp deleted file mode 100644 index e422b6e5..00000000 --- a/src/MediaFile/MediaRecorder.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> - * - * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "MediaRecorder.h" -#include "Common/config.h" -#include "Http/HttpSession.h" -#include "Util/util.h" -#include "Util/mini.h" -#include "Network/sockutil.h" -#include "HlsMakerImp.h" -using namespace toolkit; - -namespace mediakit { - -MediaRecorder::MediaRecorder(const string &strVhost_tmp, - const string &strApp, - const string &strId, - bool enableHls, - bool enableMp4) { - - GET_CONFIG(string,hlsPath,Hls::kFilePath); - GET_CONFIG(uint32_t,hlsBufSize,Hls::kFileBufSize); - GET_CONFIG(uint32_t,hlsDuration,Hls::kSegmentDuration); - GET_CONFIG(uint32_t,hlsNum,Hls::kSegmentNum); - GET_CONFIG(bool,enableVhost,General::kEnableVhost); - - string strVhost = strVhost_tmp; - if(trim(strVhost).empty()){ - //如果strVhost为空,则强制为默认虚拟主机 - strVhost = DEFAULT_VHOST; - } - -#if defined(ENABLE_HLS) - if(enableHls) { - string m3u8FilePath; - string params; - if(enableVhost){ - m3u8FilePath = strVhost + "/" + strApp + "/" + strId + "/hls.m3u8"; - params = string(VHOST_KEY) + "=" + strVhost; - }else{ - m3u8FilePath = strApp + "/" + strId + "/hls.m3u8"; - } - m3u8FilePath = File::absolutePath(m3u8FilePath,hlsPath); - _hlsRecorder.reset(new HlsRecorder(m3u8FilePath,params,hlsBufSize, hlsDuration, hlsNum)); - } -#endif //defined(ENABLE_HLS) - -#if defined(ENABLE_MP4RECORD) - GET_CONFIG(string,recordPath,Record::kFilePath); - GET_CONFIG(string,recordAppName,Record::kAppName); - - if(enableMp4){ - string mp4FilePath; - if(enableVhost){ - mp4FilePath = strVhost + "/" + recordAppName + "/" + strApp + "/" + strId + "/"; - } else { - mp4FilePath = recordAppName + "/" + strApp + "/" + strId + "/"; - } - mp4FilePath = File::absolutePath(mp4FilePath,recordPath); - _mp4Recorder.reset(new MP4Recorder(mp4FilePath,strVhost,strApp,strId)); - } -#endif //defined(ENABLE_MP4RECORD) -} - -MediaRecorder::~MediaRecorder() { -} - -void MediaRecorder::inputFrame(const Frame::Ptr &frame) { -#if defined(ENABLE_HLS) - if (_hlsRecorder) { - _hlsRecorder->inputFrame(frame); - } -#endif //defined(ENABLE_HLS) - -#if defined(ENABLE_MP4RECORD) - if (_mp4Recorder) { - _mp4Recorder->inputFrame(frame); - } -#endif //defined(ENABLE_MP4RECORD) -} - -void MediaRecorder::addTrack(const Track::Ptr &track) { -#if defined(ENABLE_HLS) - if (_hlsRecorder) { - _hlsRecorder->addTrack(track); - } -#endif //defined(ENABLE_HLS) - -#if defined(ENABLE_MP4RECORD) - if (_mp4Recorder) { - _mp4Recorder->addTrack(track); - } -#endif //defined(ENABLE_MP4RECORD) -} - -void MediaRecorder::resetTracks() { -#if defined(ENABLE_HLS) - if (_hlsRecorder) { - _hlsRecorder->resetTracks(); - } -#endif //defined(ENABLE_HLS) - -#if defined(ENABLE_MP4RECORD) - if (_mp4Recorder) { - _mp4Recorder->resetTracks(); - } -#endif //defined(ENABLE_MP4RECORD) -} - -} /* namespace mediakit */ diff --git a/src/Player/MediaPlayer.cpp b/src/Player/MediaPlayer.cpp index 73c04d54..53365d1a 100644 --- a/src/Player/MediaPlayer.cpp +++ b/src/Player/MediaPlayer.cpp @@ -42,13 +42,13 @@ MediaPlayer::MediaPlayer(const EventPoller::Ptr &poller) { MediaPlayer::~MediaPlayer() { } void MediaPlayer::play(const string &strUrl) { - _parser = PlayerBase::createPlayer(_poller,strUrl); - _parser->setOnShutdown(_shutdownCB); - _parser->setOnPlayResult(_playResultCB); - _parser->setOnResume(_resumeCB); - _parser->setMediaSouce(_pMediaSrc); - _parser->mINI::operator=(*this); - _parser->play(strUrl); + _delegate = PlayerBase::createPlayer(_poller,strUrl); + _delegate->setOnShutdown(_shutdownCB); + _delegate->setOnPlayResult(_playResultCB); + _delegate->setOnResume(_resumeCB); + _delegate->setMediaSouce(_pMediaSrc); + _delegate->mINI::operator=(*this); + _delegate->play(strUrl); } EventPoller::Ptr MediaPlayer::getPoller(){ @@ -56,14 +56,14 @@ EventPoller::Ptr MediaPlayer::getPoller(){ } void MediaPlayer::pause(bool bPause) { - if (_parser) { - _parser->pause(bPause); + if (_delegate) { + _delegate->pause(bPause); } } void MediaPlayer::teardown() { - if (_parser) { - _parser->teardown(); + if (_delegate) { + _delegate->teardown(); } } diff --git a/src/Player/PlayerBase.cpp b/src/Player/PlayerBase.cpp index 4de82c30..5b94a976 100644 --- a/src/Player/PlayerBase.cpp +++ b/src/Player/PlayerBase.cpp @@ -64,17 +64,23 @@ PlayerBase::PlayerBase() { this->mINI::operator[](kTimeoutMS) = 10000; this->mINI::operator[](kMediaTimeoutMS) = 5000; this->mINI::operator[](kBeatIntervalMS) = 5000; - this->mINI::operator[](kMaxAnalysisMS) = 2000; + this->mINI::operator[](kMaxAnalysisMS) = 5000; } ///////////////////////////Demuxer////////////////////////////// bool Demuxer::isInited(int analysisMs) { - if(_ticker.createdTime() < analysisMs){ - //analysisMs毫秒内判断条件 - //如果音视频都准备好了 ,说明Track全部就绪 - return (_videoTrack && _videoTrack->ready() && _audioTrack && _audioTrack->ready()); + if(analysisMs && _ticker.createdTime() > analysisMs){ + //analysisMs毫秒后强制初始化完毕 + return true; + } + if (_videoTrack && !_videoTrack->ready()) { + //视频未准备好 + return false; + } + if (_audioTrack && !_audioTrack->ready()) { + //音频未准备好 + return false; } - //analysisMs毫秒后强制初始化完毕 return true; } @@ -98,10 +104,21 @@ vector Demuxer::getTracks(bool trackReady) const { ret.emplace_back(_audioTrack); } } - return ret; + return std::move(ret); } float Demuxer::getDuration() const { return _fDuration; } + +void Demuxer::onAddTrack(const Track::Ptr &track){ + if(_listener){ + _listener->onAddTrack(track); + } +} + +void Demuxer::setTrackListener(Demuxer::Listener *listener) { + _listener = listener; +} + } /* namespace mediakit */ diff --git a/src/Player/PlayerBase.h b/src/Player/PlayerBase.h index 50330b06..8fb397b3 100644 --- a/src/Player/PlayerBase.h +++ b/src/Player/PlayerBase.h @@ -41,7 +41,7 @@ using namespace toolkit; namespace mediakit { -class DemuxerBase { +class DemuxerBase : public TrackSource{ public: typedef std::shared_ptr Ptr; @@ -57,29 +57,6 @@ public: * @return */ virtual bool isInited(int analysisMs) { return true; } - - /** - * 获取全部的Track - * @param trackReady 是否获取全部已经准备好的Track - * @return - */ - virtual vector getTracks(bool trackReady = true) const { return vector();} - - /** - * 获取特定Track - * @param type track类型 - * @param trackReady 是否获取全部已经准备好的Track - * @return - */ - virtual Track::Ptr getTrack(TrackType type , bool trackReady = true) const { - auto tracks = getTracks(trackReady); - for(auto &track : tracks){ - if(track->getTrackType() == type){ - return track; - } - } - return nullptr; - } }; @@ -150,6 +127,13 @@ public: * @return */ virtual float getPacketLossRate(TrackType trackType) const {return 0; } + + /** + * 获取所有track + */ + vector getTracks(bool trackReady = true) const override{ + return vector(); + } protected: virtual void onShutdown(const SockException &ex) {} virtual void onPlayResult(const SockException &ex) {} @@ -159,9 +143,8 @@ protected: virtual void onResume(){}; }; -template -class PlayerImp : public Parent -{ +template +class PlayerImp : public Parent { public: typedef std::shared_ptr Ptr; @@ -170,62 +153,62 @@ public: virtual ~PlayerImp(){} void setOnShutdown(const function &cb) override { - if (_parser) { - _parser->setOnShutdown(cb); + if (_delegate) { + _delegate->setOnShutdown(cb); } _shutdownCB = cb; } void setOnPlayResult(const function &cb) override { - if (_parser) { - _parser->setOnPlayResult(cb); + if (_delegate) { + _delegate->setOnPlayResult(cb); } _playResultCB = cb; } void setOnResume(const function &cb) override { - if (_parser) { - _parser->setOnResume(cb); + if (_delegate) { + _delegate->setOnResume(cb); } _resumeCB = cb; } bool isInited(int analysisMs) override{ - if (_parser) { - return _parser->isInited(analysisMs); + if (_delegate) { + return _delegate->isInited(analysisMs); } - return PlayerBase::isInited(analysisMs); + return Parent::isInited(analysisMs); } float getDuration() const override { - if (_parser) { - return _parser->getDuration(); + if (_delegate) { + return _delegate->getDuration(); } - return PlayerBase::getDuration(); + return Parent::getDuration(); } float getProgress() const override{ - if (_parser) { - return _parser->getProgress(); + if (_delegate) { + return _delegate->getProgress(); } - return PlayerBase::getProgress(); + return Parent::getProgress(); } void seekTo(float fProgress) override{ - if (_parser) { - return _parser->seekTo(fProgress); + if (_delegate) { + return _delegate->seekTo(fProgress); } - return PlayerBase::seekTo(fProgress); + return Parent::seekTo(fProgress); } void setMediaSouce(const MediaSource::Ptr & src) override { - if (_parser) { - _parser->setMediaSouce(src); + if (_delegate) { + _delegate->setMediaSouce(src); } _pMediaSrc = src; } vector getTracks(bool trackReady = true) const override{ - if (_parser) { - return _parser->getTracks(trackReady); + if (_delegate) { + return _delegate->getTracks(trackReady); } - return PlayerBase::getTracks(trackReady); + return Parent::getTracks(trackReady); } protected: void onShutdown(const SockException &ex) override { @@ -236,18 +219,10 @@ protected: } void onPlayResult(const SockException &ex) override { - if(!_playResultCB){ - return; - } - if(ex){ - //播放失败,则立即回调 + if(_playResultCB) { _playResultCB(ex); _playResultCB = nullptr; - return; } - //播放成功 - _playResultCB(ex); - _playResultCB = nullptr; } void onResume() override{ @@ -259,13 +234,20 @@ protected: function _shutdownCB; function _playResultCB; function _resumeCB; - std::shared_ptr _parser; + std::shared_ptr _delegate; MediaSource::Ptr _pMediaSrc; }; class Demuxer : public PlayerBase{ public: + class Listener{ + public: + Listener() = default; + virtual ~Listener() = default; + virtual void onAddTrack(const Track::Ptr &track) = 0; + }; + Demuxer(){}; virtual ~Demuxer(){}; @@ -282,17 +264,25 @@ public: bool isInited(int analysisMs) override; /** - * 获取所有可用Track,请在isInited()返回true时调用 - * @return + * 获取所有Track + * @return 所有Track */ vector getTracks(bool trackReady = true) const override; /** * 获取节目总时长 - * @return + * @return 节目总时长,单位秒 */ float getDuration() const override; + + /** + * 设置track监听器 + */ + void setTrackListener(Listener *listener); protected: + void onAddTrack(const Track::Ptr &track); +protected: + Listener *_listener = nullptr; AudioTrack::Ptr _audioTrack; VideoTrack::Ptr _videoTrack; Ticker _ticker; diff --git a/src/Player/PlayerProxy.cpp b/src/Player/PlayerProxy.cpp index 075394c9..1ba1d1b7 100644 --- a/src/Player/PlayerProxy.cpp +++ b/src/Player/PlayerProxy.cpp @@ -138,13 +138,13 @@ void PlayerProxy::play(const string &strUrlTmp) { MediaPlayer::play(strUrlTmp); MediaSource::Ptr mediaSource; - if(dynamic_pointer_cast(_parser)){ + if(dynamic_pointer_cast(_delegate)){ //rtsp拉流 GET_CONFIG(bool,directProxy,Rtsp::kDirectProxy); if(directProxy && _bEnableRtsp){ mediaSource = std::make_shared(_strVhost,_strApp,_strSrc); } - } else if(dynamic_pointer_cast(_parser)){ + } else if(dynamic_pointer_cast(_delegate)){ //rtmp拉流 if(_bEnableRtmp){ mediaSource = std::make_shared(_strVhost,_strApp,_strSrc); @@ -174,12 +174,8 @@ void PlayerProxy::rePlay(const string &strUrl,int iFailedCnt){ }, getPoller()); } -int PlayerProxy::readerCount(){ - return (_mediaMuxer ? _mediaMuxer->readerCount() : 0) + (_pMediaSrc ? _pMediaSrc->readerCount() : 0); -} - bool PlayerProxy::close(MediaSource &sender,bool force) { - if(!force && readerCount() != 0){ + if(!force && totalReaderCount()){ return false; } @@ -201,13 +197,21 @@ bool PlayerProxy::close(MediaSource &sender,bool force) { } void PlayerProxy::onNoneReader(MediaSource &sender) { - if(!_mediaMuxer || _mediaMuxer->readerCount() != 0){ + if(!_mediaMuxer || totalReaderCount()){ return; } MediaSourceEvent::onNoneReader(sender); } -class MuteAudioMaker : public FrameRingInterfaceDelegate{ +int PlayerProxy::totalReaderCount(){ + return (_mediaMuxer ? _mediaMuxer->totalReaderCount() : 0) + (_pMediaSrc ? _pMediaSrc->readerCount() : 0); +} + +int PlayerProxy::totalReaderCount(MediaSource &sender) { + return totalReaderCount(); +} + +class MuteAudioMaker : public FrameDispatcher{ public: typedef std::shared_ptr Ptr; @@ -215,16 +219,26 @@ public: virtual ~MuteAudioMaker(){} void inputFrame(const Frame::Ptr &frame) override { if(frame->getTrackType() == TrackVideo){ - auto iAudioIndex = frame->stamp() / MUTE_ADTS_DATA_MS; + auto iAudioIndex = frame->dts() / MUTE_ADTS_DATA_MS; if(_iAudioIndex != iAudioIndex){ _iAudioIndex = iAudioIndex; - auto aacFrame = std::make_shared((char *)MUTE_ADTS_DATA, - MUTE_ADTS_DATA_LEN, - _iAudioIndex * MUTE_ADTS_DATA_MS); - FrameRingInterfaceDelegate::inputFrame(aacFrame); + auto aacFrame = std::make_shared((char *)MUTE_ADTS_DATA, MUTE_ADTS_DATA_LEN, _iAudioIndex * MUTE_ADTS_DATA_MS); + FrameDispatcher::inputFrame(aacFrame); } } } + +private: + class AACFrameCacheAble : public AACFrameNoCacheAble{ + public: + template + AACFrameCacheAble(ARGS && ...args) : AACFrameNoCacheAble(std::forward(args)...){}; + virtual ~AACFrameCacheAble() = default; + + bool cacheAble() const override { + return true; + } + }; private: int _iAudioIndex = 0; }; @@ -276,6 +290,13 @@ void PlayerProxy::onPlaySuccess() { //MuteAudioMaker生成静音音频然后写入_mediaMuxer; audioMaker->addDelegate(_mediaMuxer); } + + //添加完毕所有track,防止单track情况下最大等待3秒 + _mediaMuxer->addTrackCompleted(); + + if(_pMediaSrc){ + _pMediaSrc->setTrackSource(_mediaMuxer); + } } diff --git a/src/Player/PlayerProxy.h b/src/Player/PlayerProxy.h index aff0e3d4..e4ed9807 100644 --- a/src/Player/PlayerProxy.h +++ b/src/Player/PlayerProxy.h @@ -75,18 +75,15 @@ public: * @param strUrl */ void play(const string &strUrl) override; - - - /** - * 被主动关闭 - * @return - */ - bool close(MediaSource &sender,bool force) override; private: + //MediaSourceEvent override + bool close(MediaSource &sender,bool force) override; void onNoneReader(MediaSource &sender) override; + int totalReaderCount(MediaSource &sender) override; + int totalReaderCount() ; + void rePlay(const string &strUrl,int iFailedCnt); void onPlaySuccess(); - int readerCount() ; private: bool _bEnableRtsp; bool _bEnableRtmp; diff --git a/src/Pusher/MediaPusher.cpp b/src/Pusher/MediaPusher.cpp index 78475fb1..26f8de8b 100644 --- a/src/Pusher/MediaPusher.cpp +++ b/src/Pusher/MediaPusher.cpp @@ -52,11 +52,11 @@ MediaPusher::MediaPusher(const string &schema, MediaPusher::~MediaPusher() { } void MediaPusher::publish(const string &strUrl) { - _parser = PusherBase::createPusher(_poller,_src.lock(),strUrl); - _parser->setOnShutdown(_shutdownCB); - _parser->setOnPublished(_publishCB); - _parser->mINI::operator=(*this); - _parser->publish(strUrl); + _delegate = PusherBase::createPusher(_poller,_src.lock(),strUrl); + _delegate->setOnShutdown(_shutdownCB); + _delegate->setOnPublished(_publishCB); + _delegate->mINI::operator=(*this); + _delegate->publish(strUrl); } EventPoller::Ptr MediaPusher::getPoller(){ diff --git a/src/Pusher/PusherBase.h b/src/Pusher/PusherBase.h index 6fc86e5b..752f3358 100644 --- a/src/Pusher/PusherBase.h +++ b/src/Pusher/PusherBase.h @@ -75,7 +75,7 @@ public: virtual void setOnShutdown(const Event &cb) = 0; }; -template +template class PusherImp : public Parent { public: typedef std::shared_ptr Ptr; @@ -90,8 +90,8 @@ public: * @param strUrl 推流url,支持rtsp/rtmp */ void publish(const string &strUrl) override{ - if (_parser) { - _parser->publish(strUrl); + if (_delegate) { + _delegate->publish(strUrl); } } @@ -99,8 +99,8 @@ public: * 中断推流 */ void teardown() override{ - if (_parser) { - _parser->teardown(); + if (_delegate) { + _delegate->teardown(); } } @@ -109,8 +109,8 @@ public: * @param onPublished */ void setOnPublished(const PusherBase::Event &cb) override{ - if (_parser) { - _parser->setOnPublished(cb); + if (_delegate) { + _delegate->setOnPublished(cb); } _publishCB = cb; } @@ -120,15 +120,15 @@ public: * @param onShutdown */ void setOnShutdown(const PusherBase::Event &cb) override{ - if (_parser) { - _parser->setOnShutdown(cb); + if (_delegate) { + _delegate->setOnShutdown(cb); } _shutdownCB = cb; } protected: PusherBase::Event _shutdownCB; PusherBase::Event _publishCB; - std::shared_ptr _parser; + std::shared_ptr _delegate; }; diff --git a/src/MediaFile/HlsMaker.cpp b/src/Record/HlsMaker.cpp similarity index 85% rename from src/MediaFile/HlsMaker.cpp rename to src/Record/HlsMaker.cpp index 959fcc36..704e5f61 100644 --- a/src/MediaFile/HlsMaker.cpp +++ b/src/Record/HlsMaker.cpp @@ -75,14 +75,16 @@ void HlsMaker::makeIndexFile(bool eof) { } -void HlsMaker::inputData(void *data, uint32_t len, uint32_t timestamp) { - //分片数据中断结束 +void HlsMaker::inputData(void *data, uint32_t len, uint32_t timestamp, bool is_idr_fast_packet) { if (data && len) { - addNewSegment(timestamp); + if(is_idr_fast_packet){ + addNewSegment(timestamp); + } onWriteSegment((char *) data, len); //记录上次写入数据时间 _ticker_last_data.resetTime(); } else { + //resetTracks时触发此逻辑 flushLastSegment(true); } } @@ -93,13 +95,14 @@ void HlsMaker::delOldSegment() { return; } //在hls m3u8索引文件中,我们保存的切片个数跟_seg_number相关设置一致 - if (_file_index >= _seg_number + 2) { + if (_file_index > _seg_number) { _seg_dur_list.pop_front(); } - //但是实际保存的切片个数比m3u8所述多两个,这样做的目的是防止播放器在切片删除前能下载完毕 - if (_file_index >= _seg_number + 4) { - onDelSegment(_file_index - _seg_number - 4); + GET_CONFIG(uint32_t,segRetain,Hls::kSegmentRetain); + //但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕 + if (_file_index > _seg_number + segRetain) { + onDelSegment(_file_index - _seg_number - segRetain - 1); } } @@ -109,8 +112,8 @@ void HlsMaker::addNewSegment(uint32_t) { return; } - //关闭并保存上一个切片 - flushLastSegment(); + //关闭并保存上一个切片,如果_seg_number==0,那么是点播。 + flushLastSegment(_seg_number == 0); //新增切片 _last_file_name = onOpenSegment(_file_index++); //重置切片计时器 @@ -133,4 +136,8 @@ void HlsMaker::flushLastSegment(bool eof){ _last_file_name.clear(); } +bool HlsMaker::isLive() { + return _seg_number != 0; +} + }//namespace mediakit \ No newline at end of file diff --git a/src/MediaFile/HlsMaker.h b/src/Record/HlsMaker.h similarity index 93% rename from src/MediaFile/HlsMaker.h rename to src/Record/HlsMaker.h index 49635a60..12e5dab4 100644 --- a/src/MediaFile/HlsMaker.h +++ b/src/Record/HlsMaker.h @@ -52,8 +52,9 @@ public: * @param data 数据 * @param len 数据长度 * @param timestamp 毫秒时间戳 + * @param is_idr_fast_packet 是否为关键帧第一个包 */ - void inputData(void *data, uint32_t len, uint32_t timestamp); + void inputData(void *data, uint32_t len, uint32_t timestamp, bool is_idr_fast_packet); protected: /** * 创建ts切片文件回调 @@ -87,6 +88,11 @@ protected: * @param eof */ void flushLastSegment(bool eof = false); + + /** + * 是否为直播 + */ + bool isLive(); private: /** * 生成m3u8文件 diff --git a/src/MediaFile/HlsMakerImp.cpp b/src/Record/HlsMakerImp.cpp similarity index 70% rename from src/MediaFile/HlsMakerImp.cpp rename to src/Record/HlsMakerImp.cpp index 8fa9d4fd..cdbe8e01 100644 --- a/src/MediaFile/HlsMakerImp.cpp +++ b/src/Record/HlsMakerImp.cpp @@ -40,7 +40,6 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file, _path_hls = m3u8_file; _params = params; _buf_size = bufSize; - _is_vod = seg_number == 0; _file_buf.reset(new char[bufSize],[](char *ptr){ delete[] ptr; }); @@ -49,28 +48,41 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file, HlsMakerImp::~HlsMakerImp() { //录制完了 flushLastSegment(true); - if(!_is_vod){ + if(isLive()){ //hls直播才删除文件 File::delete_file(_path_prefix.data()); } } string HlsMakerImp::onOpenSegment(int index) { - auto full_path = fullPath(index); - _file = makeFile(full_path, true); + string segment_name , segment_path; + { + auto strDate = getTimeStr("%Y-%m-%d"); + auto strHour = getTimeStr("%H"); + auto strTime = getTimeStr("%M-%S"); + segment_name = StrPrinter << strDate + "/" + strHour + "/" + strTime << "_" << index << ".ts"; + segment_path = _path_prefix + "/" + segment_name; + if(isLive()){ + _segment_file_paths.emplace(index,segment_path); + } + } + _file = makeFile(segment_path, true); if(!_file){ - WarnL << "create file falied," << full_path << " " << get_uv_errmsg(); + WarnL << "create file falied," << segment_path << " " << get_uv_errmsg(); } - //DebugL << index << " " << full_path; if(_params.empty()){ - return StrPrinter << index << ".ts"; + return std::move(segment_name); } - return StrPrinter << index << ".ts" << "?" << _params; + return std::move(segment_name + "?" + _params); } void HlsMakerImp::onDelSegment(int index) { - //WarnL << index; - File::delete_file(fullPath(index).data()); + auto it = _segment_file_paths.find(index); + if(it == _segment_file_paths.end()){ + return; + } + File::delete_file(it->second.data()); + _segment_file_paths.erase(it); } void HlsMakerImp::onWriteSegment(const char *data, int len) { @@ -84,18 +96,19 @@ void HlsMakerImp::onWriteHls(const char *data, int len) { if(hls){ fwrite(data,len,1,hls.get()); hls.reset(); + if(_media_src){ + _media_src->registHls(); + } } else{ WarnL << "create hls file falied," << _path_hls << " " << get_uv_errmsg(); } //DebugL << "\r\n" << string(data,len); } -string HlsMakerImp::fullPath(int index) { - return StrPrinter << _path_prefix << "/" << index << ".ts"; -} std::shared_ptr HlsMakerImp::makeFile(const string &file,bool setbuf) { - auto ret= shared_ptr(File::createfile_file(file.data(), "wb"), [](FILE *fp) { + auto file_buf = _file_buf; + auto ret= shared_ptr(File::createfile_file(file.data(), "wb"), [file_buf](FILE *fp) { if (fp) { fclose(fp); } @@ -106,4 +119,12 @@ std::shared_ptr HlsMakerImp::makeFile(const string &file,bool setbuf) { return ret; } +void HlsMakerImp::setMediaSource(const string &vhost, const string &app, const string &stream_id) { + _media_src = std::make_shared(vhost, app, stream_id); +} + +MediaSource::Ptr HlsMakerImp::getMediaSource() const{ + return _media_src; +} + }//namespace mediakit \ No newline at end of file diff --git a/src/MediaFile/HlsMakerImp.h b/src/Record/HlsMakerImp.h similarity index 82% rename from src/MediaFile/HlsMakerImp.h rename to src/Record/HlsMakerImp.h index 55637fd8..d12aef5d 100644 --- a/src/MediaFile/HlsMakerImp.h +++ b/src/Record/HlsMakerImp.h @@ -31,6 +31,7 @@ #include #include #include "HlsMaker.h" +#include "HlsMediaSource.h" using namespace std; namespace mediakit { @@ -43,23 +44,36 @@ public: float seg_duration = 5, uint32_t seg_number = 3); virtual ~HlsMakerImp(); + + /** + * 设置媒体信息 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream_id 流id + */ + void setMediaSource(const string &vhost, const string &app, const string &stream_id); + + /** + * 获取MediaSource + * @return + */ + MediaSource::Ptr getMediaSource() const; protected: string onOpenSegment(int index) override ; void onDelSegment(int index) override; void onWriteSegment(const char *data, int len) override; void onWriteHls(const char *data, int len) override; private: - string fullPath(int index); std::shared_ptr makeFile(const string &file,bool setbuf = false); private: + HlsMediaSource::Ptr _media_src; + map _segment_file_paths; std::shared_ptr _file; std::shared_ptr _file_buf; string _path_prefix; string _path_hls; string _params; int _buf_size; - //是否为点播 - bool _is_vod; }; }//namespace mediakit diff --git a/src/Record/HlsMediaSource.cpp b/src/Record/HlsMediaSource.cpp new file mode 100644 index 00000000..88bc0c71 --- /dev/null +++ b/src/Record/HlsMediaSource.cpp @@ -0,0 +1,83 @@ +/* + * MIT License + * + * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "HlsMediaSource.h" + +namespace mediakit{ + +HlsCookieData::HlsCookieData(const MediaInfo &info, const string &sessionIdentifier, const string &peer_ip, uint16_t peer_port) { + _info = info; + _sessionIdentifier = sessionIdentifier; + _peer_ip = peer_ip; + _peer_port = peer_port; + _added = std::make_shared(false); + addReaderCount(); +} + +void HlsCookieData::addReaderCount(){ + if(!*_added){ + auto src = dynamic_pointer_cast(MediaSource::find(HLS_SCHEMA,_info._vhost,_info._app,_info._streamid)); + if(src){ + src->modifyReaderCount(true); + *_added = true; + _src = src; + _ring_reader = src->getRing()->attach(EventPollerPool::Instance().getPoller()); + auto added = _added; + _ring_reader->setDetachCB([added](){ + //HlsMediaSource已经销毁 + *added = false; + }); + } + } +} + +HlsCookieData::~HlsCookieData() { + if (*_added) { + auto src = _src.lock(); + if (src) { + src->modifyReaderCount(false); + } + uint64_t duration = (_ticker.createdTime() - _ticker.elapsedTime()) / 1000; + WarnL << _sessionIdentifier << "(" << _peer_ip << ":" << _peer_port << ") " + << "HLS播放器(" << _info._vhost << "/" << _info._app << "/" << _info._streamid + << ")断开,耗时(s):" << duration; + + GET_CONFIG(uint32_t, iFlowThreshold, General::kFlowThreshold); + if (_bytes > iFlowThreshold * 1024) { + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _info, _bytes, duration, true, _sessionIdentifier, _peer_ip, _peer_port); + } + } +} + +void HlsCookieData::addByteUsage(uint64_t bytes) { + addReaderCount(); + _bytes += bytes; + _ticker.resetTime(); +} + + +}//namespace mediakit + diff --git a/src/Record/HlsMediaSource.h b/src/Record/HlsMediaSource.h new file mode 100644 index 00000000..38a192b7 --- /dev/null +++ b/src/Record/HlsMediaSource.h @@ -0,0 +1,114 @@ +/* + * MIT License + * + * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef ZLMEDIAKIT_HLSMEDIASOURCE_H +#define ZLMEDIAKIT_HLSMEDIASOURCE_H + +#include +#include "Util/TimeTicker.h" +#include "Common/MediaSource.h" +namespace mediakit{ + +class HlsMediaSource : public MediaSource { +public: + friend class HlsCookieData; + typedef RingBuffer RingType; + typedef std::shared_ptr Ptr; + HlsMediaSource(const string &vhost, const string &app, const string &stream_id) : MediaSource(HLS_SCHEMA, vhost, app, stream_id){ + _readerCount = 0; + _ring = std::make_shared(); + } + + virtual ~HlsMediaSource() = default; + + /** + * 获取媒体源的环形缓冲 + */ + const RingType::Ptr &getRing() const { + return _ring; + } + + /** + * 获取播放器个数 + * @return + */ + int readerCount() override { + return _readerCount.load(); + } + + /** + * 注册hls + */ + void registHls(){ + if(!_registed){ + regist(); + _registed = true; + } + } + +private: + /** + * 修改观看者个数 + * @param add 添加海思删除 + */ + void modifyReaderCount(bool add) { + if (add) { + ++_readerCount; + return; + } + + if (--_readerCount == 0 && totalReaderCount() == 0) { + onNoneReader(); + } + } +private: + atomic_int _readerCount; + bool _registed = false; + RingType::Ptr _ring; +}; + +class HlsCookieData{ +public: + typedef std::shared_ptr Ptr; + HlsCookieData(const MediaInfo &info, const string &sessionIdentifier, const string &peer_ip, uint16_t peer_port); + ~HlsCookieData(); + void addByteUsage(uint64_t bytes); +private: + void addReaderCount(); +private: + uint64_t _bytes = 0; + MediaInfo _info; + string _sessionIdentifier; + string _peer_ip; + uint16_t _peer_port; + std::shared_ptr _added; + weak_ptr _src; + Ticker _ticker; + HlsMediaSource::RingType::RingReader::Ptr _ring_reader; +}; + + +}//namespace mediakit +#endif //ZLMEDIAKIT_HLSMEDIASOURCE_H diff --git a/src/MediaFile/MediaRecorder.h b/src/Record/HlsRecorder.h similarity index 52% rename from src/MediaFile/MediaRecorder.h rename to src/Record/HlsRecorder.h index c36f8613..7bebd20b 100644 --- a/src/MediaFile/MediaRecorder.h +++ b/src/Record/HlsRecorder.h @@ -24,56 +24,44 @@ * SOFTWARE. */ -#ifndef SRC_MEDIAFILE_MEDIARECORDER_H_ -#define SRC_MEDIAFILE_MEDIARECORDER_H_ - -#include -#include "Player/PlayerBase.h" -#include "Common/MediaSink.h" -#include "MP4Recorder.h" -#include "HlsRecorder.h" - -using namespace toolkit; +#ifndef HLSRECORDER_H +#define HLSRECORDER_H +#include "HlsMakerImp.h" +#include "TsMuxer.h" namespace mediakit { -class MediaRecorder : public MediaSink{ -public: - typedef std::shared_ptr Ptr; - MediaRecorder(const string &strVhost, - const string &strApp, - const string &strId, - bool enableHls = true, - bool enableMp4 = false); - virtual ~MediaRecorder(); - - /** - * 输入frame - * @param frame - */ - void inputFrame(const Frame::Ptr &frame) override; - - /** - * 添加track,内部会调用Track的clone方法 - * 只会克隆sps pps这些信息 ,而不会克隆Delegate相关关系 - * @param track - */ - void addTrack(const Track::Ptr &track) override; - - /** - * 重置track - */ - void resetTracks() override; -private: +class HlsRecorder #if defined(ENABLE_HLS) - std::shared_ptr _hlsRecorder; -#endif //defined(ENABLE_HLS) +: public TsMuxer +#endif + { +public: + typedef std::shared_ptr Ptr; + HlsRecorder(const string &m3u8_file, const string ¶ms){ + GET_CONFIG(uint32_t,hlsNum,Hls::kSegmentNum); + GET_CONFIG(uint32_t,hlsBufSize,Hls::kFileBufSize); + GET_CONFIG(uint32_t,hlsDuration,Hls::kSegmentDuration); + _hls = new HlsMakerImp(m3u8_file,params,hlsBufSize,hlsDuration,hlsNum); + } + ~HlsRecorder(){ + delete _hls; + } + void setMediaSource(const string &vhost, const string &app, const string &stream_id){ + _hls->setMediaSource(vhost, app, stream_id); + } -#if defined(ENABLE_MP4RECORD) - std::shared_ptr _mp4Recorder; -#endif //defined(ENABLE_MP4RECORD) + MediaSource::Ptr getMediaSource() const{ + return _hls->getMediaSource(); + } +#if defined(ENABLE_HLS) +protected: + void onTs(const void *packet, int bytes,uint32_t timestamp,bool is_idr_fast_packet) override { + _hls->inputData((char *)packet,bytes,timestamp, is_idr_fast_packet); + }; +#endif +private: + HlsMakerImp *_hls; }; - -} /* namespace mediakit */ - -#endif /* SRC_MEDIAFILE_MEDIARECORDER_H_ */ +}//namespace mediakit +#endif //HLSRECORDER_H diff --git a/src/MediaFile/MP4Muxer.cpp b/src/Record/MP4Muxer.cpp similarity index 65% rename from src/MediaFile/MP4Muxer.cpp rename to src/Record/MP4Muxer.cpp index c1b5bad3..785fedbd 100644 --- a/src/MediaFile/MP4Muxer.cpp +++ b/src/Record/MP4Muxer.cpp @@ -67,57 +67,96 @@ void MP4MuxerBase::init(int flags) { } /////////////////////////////////// +void MP4Muxer::resetTracks() { + _codec_to_trackid.clear(); + _started = false; + _have_video = false; +} -void MP4Muxer::onTrackFrame(const Frame::Ptr &frame) { - if(frame->configFrame()){ - //忽略配置帧 - return; - } +void MP4Muxer::inputFrame(const Frame::Ptr &frame) { auto it = _codec_to_trackid.find(frame->getCodecId()); if(it == _codec_to_trackid.end()){ //该Track不存在或初始化失败 return; } - if(!_started){ + if (!_started) { //还没开始 - if(frame->getTrackType() != TrackVideo || !frame->keyFrame()){ - //如果首帧是音频或者是视频但是不是i帧,那么不能开始写文件 - return; + if (!_have_video) { + _started = true; + } else { + if (frame->getTrackType() != TrackVideo || !frame->keyFrame()) { + //如果首帧是音频或者是视频但是不是i帧,那么不能开始写文件 + return; + } + //开始写文件 + _started = true; } - //开始写文件 - _started = true; - } - - int with_nalu_size ; - switch (frame->getCodecId()){ - case CodecH264: - case CodecH265: - //我们输入264、265是没有头四个字节表明数据长度的 - with_nalu_size = 0; - break; - default: - //aac或其他类型frame不用添加4个nalu_size的字节 - with_nalu_size = 1; - break; } //mp4文件时间戳需要从0开始 auto &track_info = it->second; int64_t dts_out, pts_out; - track_info.stamp.revise(frame->dts(),frame->pts(),dts_out,pts_out); - mov_writer_write_l(_mov_writter.get(), - track_info.track_id, - frame->data() + frame->prefixSize(), - frame->size() - frame->prefixSize(), - pts_out, - dts_out, - frame->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0, - with_nalu_size); + switch (frame->getCodecId()) { + case CodecH264: + case CodecH265: { + //这里的代码逻辑是让SPS、PPS、IDR这些时间戳相同的帧打包到一起当做一个帧处理, + if (!_frameCached.empty() && _frameCached.back()->dts() != frame->dts()) { + Frame::Ptr back = _frameCached.back(); + //求相对时间戳 + track_info.stamp.revise(back->dts(), back->pts(), dts_out, pts_out); + + if (_frameCached.size() != 1) { + //缓存中有多帧,需要按照mp4格式合并一起 + string merged; + _frameCached.for_each([&](const Frame::Ptr &frame) { + uint32_t nalu_size = frame->size() - frame->prefixSize(); + nalu_size = htonl(nalu_size); + merged.append((char *) &nalu_size, 4); + merged.append(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize()); + }); + mov_writer_write_l(_mov_writter.get(), + track_info.track_id, + merged.data(), + merged.size(), + pts_out, + dts_out, + back->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0, + 1/*我们合并时已经生成了4个字节的MP4格式start code*/); + } else { + //缓存中只有一帧视频 + mov_writer_write_l(_mov_writter.get(), + track_info.track_id, + back->data() + back->prefixSize(), + back->size() - back->prefixSize(), + pts_out, + dts_out, + back->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0, + 0/*需要生成头4个字节的MP4格式start code*/); + } + _frameCached.clear(); + } + //缓存帧,时间戳相同的帧合并一起写入mp4 + _frameCached.emplace_back(Frame::getCacheAbleFrame(frame)); + } + break; + default: { + track_info.stamp.revise(frame->dts(), frame->pts(), dts_out, pts_out); + mov_writer_write_l(_mov_writter.get(), + track_info.track_id, + frame->data() + frame->prefixSize(), + frame->size() - frame->prefixSize(), + pts_out, + dts_out, + frame->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0, + 1/*aac或其他类型frame不用添加4个nalu_size的字节*/); + } + break; + } } -void MP4Muxer::onTrackReady(const Track::Ptr &track) { +void MP4Muxer::addTrack(const Track::Ptr &track) { switch (track->getCodecId()) { case CodecAAC: { auto aac_track = dynamic_pointer_cast(track); @@ -125,6 +164,10 @@ void MP4Muxer::onTrackReady(const Track::Ptr &track) { WarnL << "不是AAC Track"; return; } + if(!aac_track->ready()){ + WarnL << "AAC Track未就绪"; + return; + } auto track_id = mov_writer_add_audio(_mov_writter.get(), MOV_OBJECT_AAC, aac_track->getAudioChannel(), @@ -144,11 +187,15 @@ void MP4Muxer::onTrackReady(const Track::Ptr &track) { WarnL << "不是H264 Track"; return; } + if(!h264_track->ready()){ + WarnL << "H264 Track未就绪"; + return; + } - struct mpeg4_avc_t avc; + struct mpeg4_avc_t avc = {0}; string sps_pps = string("\x00\x00\x00\x01", 4) + h264_track->getSps() + string("\x00\x00\x00\x01", 4) + h264_track->getPps(); - h264_annexbtomp4(&avc, sps_pps.data(), sps_pps.size(), NULL, 0, NULL); + h264_annexbtomp4(&avc, sps_pps.data(), sps_pps.size(), NULL, 0, NULL, NULL); uint8_t extra_data[1024]; int extra_data_size = mpeg4_avc_decoder_configuration_record_save(&avc, extra_data, sizeof(extra_data)); @@ -169,6 +216,7 @@ void MP4Muxer::onTrackReady(const Track::Ptr &track) { return; } _codec_to_trackid[track->getCodecId()].track_id = track_id; + _have_video = true; } break; case CodecH265: { @@ -177,12 +225,16 @@ void MP4Muxer::onTrackReady(const Track::Ptr &track) { WarnL << "不是H265 Track"; return; } + if(!h265_track->ready()){ + WarnL << "H265 Track未就绪"; + return; + } - struct mpeg4_hevc_t hevc; + struct mpeg4_hevc_t hevc = {0}; string vps_sps_pps = string("\x00\x00\x00\x01", 4) + h265_track->getVps() + string("\x00\x00\x00\x01", 4) + h265_track->getSps() + string("\x00\x00\x00\x01", 4) + h265_track->getPps(); - h265_annexbtomp4(&hevc, vps_sps_pps.data(), vps_sps_pps.size(), NULL, 0, NULL); + h265_annexbtomp4(&hevc, vps_sps_pps.data(), 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)); @@ -202,6 +254,7 @@ void MP4Muxer::onTrackReady(const Track::Ptr &track) { return; } _codec_to_trackid[track->getCodecId()].track_id = track_id; + _have_video = true; } break; default: @@ -210,7 +263,12 @@ void MP4Muxer::onTrackReady(const Track::Ptr &track) { } } -MP4MuxerFile::MP4MuxerFile(const char *file) { +MP4MuxerFile::MP4MuxerFile(const char *file){ + _file_name = file; + openFile(file); +} + +void MP4MuxerFile::openFile(const char *file) { //创建文件 auto fp = File::createfile_file(file,"wb+"); if(!fp){ @@ -237,7 +295,6 @@ MP4MuxerFile::MP4MuxerFile(const char *file) { }); GET_CONFIG(bool, mp4FastStart, Record::kFastStart); - init(mp4FastStart ? MOV_FLAG_FASTSTART : 0); } @@ -264,6 +321,12 @@ uint64_t MP4MuxerFile::onTell() { return ftell64(_file.get()); } + +void MP4MuxerFile::resetTracks(){ + MP4Muxer::resetTracks(); + openFile(_file_name.data()); +} + }//namespace mediakit #endif//#ifdef ENABLE_MP4RECORD diff --git a/src/MediaFile/MP4Muxer.h b/src/Record/MP4Muxer.h similarity index 84% rename from src/MediaFile/MP4Muxer.h rename to src/Record/MP4Muxer.h index de15d4cc..3285107e 100644 --- a/src/MediaFile/MP4Muxer.h +++ b/src/Record/MP4Muxer.h @@ -39,7 +39,7 @@ #include "Extension/AAC.h" #include "Extension/H264.h" #include "Extension/H265.h" -#include "Stamp.h" +#include "Common/Stamp.h" namespace mediakit{ @@ -57,30 +57,33 @@ protected: std::shared_ptr _mov_writter; }; -class MP4Muxer : public MediaSink , public MP4MuxerBase{ +class MP4Muxer : public MediaSinkInterface , public MP4MuxerBase{ public: MP4Muxer() = default; ~MP4Muxer() override = default; -protected: - /** - * 某track已经准备好,其ready()状态返回true, - * 此时代表可以获取其例如sps pps等相关信息了 - * @param track - */ - void onTrackReady(const Track::Ptr & track) override; /** - * 某Track输出frame,在onAllTrackReady触发后才会调用此方法 - * @param frame + * 添加已经ready状态的track */ - void onTrackFrame(const Frame::Ptr &frame) override; + void addTrack(const Track::Ptr & track) override; + /** + * 输入帧 + */ + void inputFrame(const Frame::Ptr &frame) override; + + /** + * 重置所有track + */ + void resetTracks() override ; private: struct track_info{ int track_id = -1; Stamp stamp; }; unordered_map _codec_to_trackid; + List _frameCached; bool _started = false; + bool _have_video = false; }; @@ -89,13 +92,16 @@ public: typedef std::shared_ptr Ptr; MP4MuxerFile(const char *file); ~MP4MuxerFile(); + void resetTracks() override ; protected: int onRead(void* data, uint64_t bytes) override; int onWrite(const void* data, uint64_t bytes) override; int onSeek( uint64_t offset) override; uint64_t onTell() override ; + void openFile(const char *file); private: std::shared_ptr _file; + string _file_name; }; }//namespace mediakit diff --git a/src/MediaFile/MediaReader.cpp b/src/Record/MP4Reader.cpp similarity index 83% rename from src/MediaFile/MediaReader.cpp rename to src/Record/MP4Reader.cpp index eae430bd..3fd552b4 100644 --- a/src/MediaFile/MediaReader.cpp +++ b/src/Record/MP4Reader.cpp @@ -24,9 +24,10 @@ * SOFTWARE. */ -#include "MediaReader.h" +#include "MP4Reader.h" #include "Common/config.h" #include "Util/mini.h" +#include "Util/File.h" #include "Http/HttpSession.h" #include "Extension/AAC.h" #include "Extension/H264.h" @@ -37,7 +38,7 @@ using namespace toolkit; namespace mediakit { #ifdef ENABLE_MP4V2 -MediaReader::MediaReader(const string &strVhost,const string &strApp, const string &strId,const string &filePath ) { +MP4Reader::MP4Reader(const string &strVhost,const string &strApp, const string &strId,const string &filePath ) { _poller = WorkThreadPool::Instance().getPoller(); auto strFileName = filePath; if(strFileName.empty()){ @@ -150,10 +151,13 @@ MediaReader::MediaReader(const string &strVhost,const string &strApp, const stri H264Track::Ptr track = std::make_shared(_strSps,_strPps); _mediaMuxer->addTrack(track); } + + //添加完毕所有track,防止单track情况下最大等待3秒 + _mediaMuxer->addTrackCompleted(); } -MediaReader::~MediaReader() { +MP4Reader::~MP4Reader() { if (_hMP4File != MP4_INVALID_FILE_HANDLE) { MP4Close(_hMP4File); _hMP4File = MP4_INVALID_FILE_HANDLE; @@ -161,7 +165,7 @@ MediaReader::~MediaReader() { } -void MediaReader::startReadMP4() { +void MP4Reader::startReadMP4() { auto strongSelf = shared_from_this(); GET_CONFIG(uint32_t,sampleMS,Record::kSampleMS); @@ -173,12 +177,12 @@ void MediaReader::startReadMP4() { readSample(sampleMS, false); _mediaMuxer->setListener(strongSelf); } - bool MediaReader::seekTo(MediaSource &sender,uint32_t ui32Stamp){ + bool MP4Reader::seekTo(MediaSource &sender,uint32_t ui32Stamp){ seek(ui32Stamp); return true; } -bool MediaReader::close(MediaSource &sender,bool force){ - if(!_mediaMuxer || (!force && _mediaMuxer->readerCount() != 0)){ +bool MP4Reader::close(MediaSource &sender,bool force){ + if(!_mediaMuxer || (!force && _mediaMuxer->totalReaderCount())){ return false; } _timer.reset(); @@ -186,19 +190,23 @@ bool MediaReader::close(MediaSource &sender,bool force){ return true; } -void MediaReader::onNoneReader(MediaSource &sender) { - if(!_mediaMuxer || _mediaMuxer->readerCount() != 0){ +void MP4Reader::onNoneReader(MediaSource &sender) { + if(!_mediaMuxer || _mediaMuxer->totalReaderCount()){ return; } MediaSourceEvent::onNoneReader(sender); } -bool MediaReader::readSample(int iTimeInc,bool justSeekSyncFrame) { +int MP4Reader::totalReaderCount(MediaSource &sender) { + return _mediaMuxer ? _mediaMuxer->totalReaderCount() : sender.readerCount(); +} + +bool MP4Reader::readSample(int iTimeInc,bool justSeekSyncFrame) { TimeTicker(); lock_guard lck(_mtx); auto bFlag0 = readVideoSample(iTimeInc,justSeekSyncFrame);//数据没读完 auto bFlag1 = readAudioSample(iTimeInc,justSeekSyncFrame);//数据没读完 - auto bFlag2 = _mediaMuxer->readerCount() > 0;//读取者大于0 + auto bFlag2 = _mediaMuxer->totalReaderCount() > 0;//读取者大于0 if((bFlag0 || bFlag1) && bFlag2){ _alive.resetTime(); } @@ -211,16 +219,19 @@ bool MediaReader::readSample(int iTimeInc,bool justSeekSyncFrame) { //3秒延时关闭 return _alive.elapsedTime() < 3 * 1000; } -inline bool MediaReader::readVideoSample(int iTimeInc,bool justSeekSyncFrame) { +inline bool MP4Reader::readVideoSample(int iTimeInc,bool justSeekSyncFrame) { if (_video_trId != MP4_INVALID_TRACK_ID) { auto iNextSample = getVideoSampleId(iTimeInc); MP4SampleId iIdx = _video_current; for (; iIdx < iNextSample; iIdx++) { uint8_t *pBytes = _pcVideoSample.get(); uint32_t numBytes = _video_sample_max_size; - if(MP4ReadSample(_hMP4File, _video_trId, iIdx + 1, &pBytes, &numBytes,NULL,NULL,NULL,&_bSyncSample)){ - if (!justSeekSyncFrame) { - uint32_t iOffset = 0; + MP4Duration pRenderingOffset; + if(MP4ReadSample(_hMP4File, _video_trId, iIdx + 1, &pBytes, &numBytes,NULL,NULL,&pRenderingOffset,&_bSyncSample)){ + if (!justSeekSyncFrame) { + uint32_t dts = (double) _video_ms * iIdx / _video_num_samples; + uint32_t pts = dts + pRenderingOffset / 90; + uint32_t iOffset = 0; while (iOffset < numBytes) { uint32_t iFrameLen; memcpy(&iFrameLen,pBytes + iOffset,4); @@ -229,7 +240,7 @@ inline bool MediaReader::readVideoSample(int iTimeInc,bool justSeekSyncFrame) { break; } memcpy(pBytes + iOffset, "\x0\x0\x0\x1", 4); - writeH264(pBytes + iOffset, iFrameLen + 4, (double) _video_ms * iIdx / _video_num_samples, 0); + writeH264(pBytes + iOffset, iFrameLen + 4, dts, pts); iOffset += (iFrameLen + 4); } }else if(_bSyncSample){ @@ -245,7 +256,7 @@ inline bool MediaReader::readVideoSample(int iTimeInc,bool justSeekSyncFrame) { return false; } -inline bool MediaReader::readAudioSample(int iTimeInc,bool justSeekSyncFrame) { +inline bool MP4Reader::readAudioSample(int iTimeInc,bool justSeekSyncFrame) { if (_audio_trId != MP4_INVALID_TRACK_ID) { auto iNextSample = getAudioSampleId(iTimeInc); for (auto i = _audio_current; i < iNextSample; i++) { @@ -253,9 +264,10 @@ inline bool MediaReader::readAudioSample(int iTimeInc,bool justSeekSyncFrame) { uint8_t *pBytes = _adts.buffer + 7; if(MP4ReadSample(_hMP4File, _audio_trId, i + 1, &pBytes, &numBytes)){ if (!justSeekSyncFrame) { + uint32_t dts = (double) _audio_ms * i / _audio_num_samples; _adts.aac_frame_length = 7 + numBytes; writeAdtsHeader(_adts, _adts.buffer); - writeAAC(_adts.buffer, _adts.aac_frame_length, (double) _audio_ms * i / _audio_num_samples); + writeAAC(_adts.buffer, _adts.aac_frame_length, dts); } }else{ ErrorL << "读取音频失败:" << i+ 1; @@ -267,27 +279,27 @@ inline bool MediaReader::readAudioSample(int iTimeInc,bool justSeekSyncFrame) { return false; } -inline void MediaReader::writeH264(uint8_t *pucData,int iLen,uint32_t dts,uint32_t pts) { +inline void MP4Reader::writeH264(uint8_t *pucData,int iLen,uint32_t dts,uint32_t pts) { _mediaMuxer->inputFrame(std::make_shared((char*)pucData,iLen,dts,pts)); } -inline void MediaReader::writeAAC(uint8_t *pucData,int iLen,uint32_t uiStamp) { +inline void MP4Reader::writeAAC(uint8_t *pucData,int iLen,uint32_t uiStamp) { _mediaMuxer->inputFrame(std::make_shared((char*)pucData,iLen,uiStamp)); } -inline MP4SampleId MediaReader::getVideoSampleId(int iTimeInc ) { +inline MP4SampleId MP4Reader::getVideoSampleId(int iTimeInc ) { MP4SampleId video_current = (double)_video_num_samples * (_iSeekTime + _ticker.elapsedTime() + iTimeInc) / _video_ms; video_current = MAX(0,MIN(_video_num_samples, video_current)); return video_current; } -inline MP4SampleId MediaReader::getAudioSampleId(int iTimeInc) { +inline MP4SampleId MP4Reader::getAudioSampleId(int iTimeInc) { MP4SampleId audio_current = (double)_audio_num_samples * (_iSeekTime + _ticker.elapsedTime() + iTimeInc) / _audio_ms ; audio_current = MAX(0,MIN(_audio_num_samples,audio_current)); return audio_current; } -inline void MediaReader::setSeekTime(uint32_t iSeekTime){ +inline void MP4Reader::setSeekTime(uint32_t iSeekTime){ _iSeekTime = MAX(0, MIN(iSeekTime,_iDuration)); _ticker.resetTime(); if (_audio_trId != MP4_INVALID_TRACK_ID) { @@ -298,10 +310,10 @@ inline void MediaReader::setSeekTime(uint32_t iSeekTime){ } } -inline uint32_t MediaReader::getVideoCurrentTime(){ +inline uint32_t MP4Reader::getVideoCurrentTime(){ return (double)_video_current * _video_ms /_video_num_samples; } -void MediaReader::seek(uint32_t iSeekTime,bool bReStart){ +void MP4Reader::seek(uint32_t iSeekTime,bool bReStart){ lock_guard lck(_mtx); if(iSeekTime == 0 || _video_trId == MP4_INVALID_TRACK_ID){ setSeekTime(iSeekTime); @@ -331,7 +343,7 @@ void MediaReader::seek(uint32_t iSeekTime,bool bReStart){ -MediaSource::Ptr MediaReader::onMakeMediaSource(const string &strSchema, +MediaSource::Ptr MP4Reader::onMakeMediaSource(const string &strSchema, const string &strVhost, const string &strApp, const string &strId, @@ -343,7 +355,7 @@ MediaSource::Ptr MediaReader::onMakeMediaSource(const string &strSchema, return nullptr; } try { - MediaReader::Ptr pReader(new MediaReader(strVhost,strApp, strId,filePath)); + MP4Reader::Ptr pReader(new MP4Reader(strVhost,strApp, strId,filePath)); pReader->startReadMP4(); return MediaSource::find(strSchema,strVhost,strApp, strId, false); } catch (std::exception &ex) { diff --git a/src/MediaFile/MediaReader.h b/src/Record/MP4Reader.h similarity index 84% rename from src/MediaFile/MediaReader.h rename to src/Record/MP4Reader.h index a2917367..30487c41 100644 --- a/src/MediaFile/MediaReader.h +++ b/src/Record/MP4Reader.h @@ -37,10 +37,10 @@ using namespace toolkit; namespace mediakit { -class MediaReader : public std::enable_shared_from_this ,public MediaSourceEvent{ +class MP4Reader : public std::enable_shared_from_this ,public MediaSourceEvent{ public: - typedef std::shared_ptr Ptr; - virtual ~MediaReader(); + typedef std::shared_ptr Ptr; + virtual ~MP4Reader(); /** * 流化一个mp4文件,使之转换成RtspMediaSource和RtmpMediaSource @@ -49,28 +49,15 @@ public: * @param strId 流id * @param filePath 文件路径,如果为空则根据配置文件和上面参数自动生成,否则使用指定的文件 */ - MediaReader(const string &strVhost,const string &strApp, const string &strId,const string &filePath = ""); + MP4Reader(const string &strVhost,const string &strApp, const string &strId,const string &filePath = ""); /** - * 开始流化MP4文件,需要指出的是,MediaReader对象一经过调用startReadMP4方法,它的强引用会自持有, - * 意思是在文件流化结束之前或中断之前,MediaReader对象是不会被销毁的(不管有没有被外部对象持有) + * 开始流化MP4文件,需要指出的是,MP4Reader对象一经过调用startReadMP4方法,它的强引用会自持有, + * 意思是在文件流化结束之前或中断之前,MP4Reader对象是不会被销毁的(不管有没有被外部对象持有) */ void startReadMP4(); /** - * 设置时移偏移量 - * @param ui32Stamp 偏移量,单位毫秒 - * @return - */ - bool seekTo(MediaSource &sender,uint32_t ui32Stamp) override; - - /** - * 关闭MediaReader的流化进程,会触发该对象放弃自持有 - * @return - */ - bool close(MediaSource &sender,bool force) override; - - /** - * 自动生成MediaReader对象然后查找相关的MediaSource对象 + * 自动生成MP4Reader对象然后查找相关的MediaSource对象 * @param strSchema 协议名 * @param strVhost 虚拟主机 * @param strApp 应用名 @@ -87,7 +74,11 @@ public: bool checkApp = true); private: + //MediaSourceEvent override + bool seekTo(MediaSource &sender,uint32_t ui32Stamp) override; + bool close(MediaSource &sender,bool force) override; void onNoneReader(MediaSource &sender) override; + int totalReaderCount(MediaSource &sender) override; #ifdef ENABLE_MP4V2 void seek(uint32_t iSeekTime,bool bReStart = true); inline void setSeekTime(uint32_t iSeekTime); diff --git a/src/MediaFile/MP4Recorder.cpp b/src/Record/MP4Recorder.cpp similarity index 84% rename from src/MediaFile/MP4Recorder.cpp rename to src/Record/MP4Recorder.cpp index 0f36561c..a92f3fde 100644 --- a/src/MediaFile/MP4Recorder.cpp +++ b/src/Record/MP4Recorder.cpp @@ -29,30 +29,12 @@ #include #include "Common/config.h" #include "MP4Recorder.h" -#include "Util/util.h" -#include "Util/NoticeCenter.h" #include "Thread/WorkThreadPool.h" using namespace toolkit; namespace mediakit { -string timeStr(const char *fmt) { - std::tm tm_snapshot; - auto time = ::time(NULL); -#if defined(_WIN32) - localtime_s(&tm_snapshot, &time); // thread-safe -#else - localtime_r(&time, &tm_snapshot); // POSIX -#endif - const size_t size = 1024; - char buffer[size]; - auto success = std::strftime(buffer, size, fmt, &tm_snapshot); - if (0 == success) - return string(fmt); - return buffer; -} - MP4Recorder::MP4Recorder(const string& strPath, const string &strVhost, const string &strApp, @@ -70,8 +52,8 @@ MP4Recorder::~MP4Recorder() { void MP4Recorder::createFile() { closeFile(); - auto strDate = timeStr("%Y-%m-%d"); - auto strTime = timeStr("%H-%M-%S"); + auto strDate = getTimeStr("%Y-%m-%d"); + auto strTime = getTimeStr("%H-%M-%S"); auto strFileTmp = _strPath + strDate + "/." + strTime + ".mp4"; auto strFile = _strPath + strDate + "/" + strTime + ".mp4"; @@ -107,7 +89,7 @@ void MP4Recorder::asyncClose() { auto info = _info; WorkThreadPool::Instance().getExecutor()->async([muxer,strFileTmp,strFile,info]() { //获取文件录制时间,放在关闭mp4之前是为了忽略关闭mp4执行时间 - const_cast(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime; + const_cast(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime; //关闭mp4非常耗时,所以要放在后台线程执行 const_cast(muxer).reset(); //临时文件名改成正式文件名,防止mp4未完成时被访问 @@ -115,7 +97,7 @@ void MP4Recorder::asyncClose() { //获取文件大小 struct stat fileData; stat(strFile.data(), &fileData); - const_cast(info).ui64FileSize = fileData.st_size; + const_cast(info).ui64FileSize = fileData.st_size; /////record 业务逻辑////// NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordMP4,info); }); @@ -128,7 +110,7 @@ void MP4Recorder::closeFile() { } } -void MP4Recorder::onTrackFrame(const Frame::Ptr &frame) { +void MP4Recorder::inputFrame(const Frame::Ptr &frame) { GET_CONFIG(uint32_t,recordSec,Record::kFileSecond); if(!_muxer || ((_createFileTicker.elapsedTime() > recordSec * 1000) && (!_haveVideo || (_haveVideo && frame->keyFrame()))) ){ @@ -145,7 +127,7 @@ void MP4Recorder::onTrackFrame(const Frame::Ptr &frame) { } } -void MP4Recorder::onTrackReady(const Track::Ptr & track){ +void MP4Recorder::addTrack(const Track::Ptr & track){ //保存所有的track,为创建MP4MuxerFile做准备 _tracks.emplace_back(track); if(track->getTrackType() == TrackVideo){ @@ -158,7 +140,6 @@ void MP4Recorder::resetTracks() { _tracks.clear(); _haveVideo = false; _createFileTicker.resetTime(); - MediaSink::resetTracks(); } } /* namespace mediakit */ diff --git a/src/MediaFile/MP4Recorder.h b/src/Record/MP4Recorder.h similarity index 82% rename from src/MediaFile/MP4Recorder.h rename to src/Record/MP4Recorder.h index 0c619461..838f2507 100644 --- a/src/MediaFile/MP4Recorder.h +++ b/src/Record/MP4Recorder.h @@ -27,8 +27,6 @@ #ifndef MP4MAKER_H_ #define MP4MAKER_H_ -#ifdef ENABLE_MP4RECORD - #include #include #include "Player/PlayerBase.h" @@ -42,7 +40,7 @@ using namespace toolkit; namespace mediakit { -class Mp4Info { +class MP4Info { public: time_t ui64StartedTime; //GMT标准时间,单位秒 time_t ui64TimeLen;//录像长度,单位秒 @@ -55,32 +53,32 @@ public: string strStreamId;//流ID string strVhost;//vhost }; -class MP4Recorder : public MediaSink{ + +#ifdef ENABLE_MP4RECORD +class MP4Recorder : public MediaSinkInterface{ public: typedef std::shared_ptr Ptr; + MP4Recorder(const string &strPath, - const string &strVhost , - const string &strApp, - const string &strStreamId); + const string &strVhost, + const string &strApp, + const string &strStreamId); virtual ~MP4Recorder(); /** * 重置所有Track */ void resetTracks() override; -private: + /** - * 某Track输出frame,在onAllTrackReady触发后才会调用此方法 - * @param frame + * 输入frame */ - void onTrackFrame(const Frame::Ptr &frame) override ; + void inputFrame(const Frame::Ptr &frame) override; /** - * 某track已经准备好,其ready()状态返回true, - * 此时代表可以获取其例如sps pps等相关信息了 - * @param track + * 添加ready状态的track */ - void onTrackReady(const Track::Ptr & track) override; + void addTrack(const Track::Ptr & track) override; private: void createFile(); void closeFile(); @@ -90,14 +88,14 @@ private: string _strFile; string _strFileTmp; Ticker _createFileTicker; - Mp4Info _info; + MP4Info _info; bool _haveVideo = false; MP4MuxerFile::Ptr _muxer; list _tracks; }; -} /* namespace mediakit */ - #endif ///ENABLE_MP4RECORD +} /* namespace mediakit */ + #endif /* MP4MAKER_H_ */ diff --git a/src/Record/Recorder.cpp b/src/Record/Recorder.cpp new file mode 100644 index 00000000..d5710b9b --- /dev/null +++ b/src/Record/Recorder.cpp @@ -0,0 +1,404 @@ +/* +* MIT License +* +* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> +* +* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#include "Recorder.h" +#include "Common/config.h" +#include "Common/MediaSource.h" +#include "MP4Recorder.h" +#include "HlsRecorder.h" + +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) { + GET_CONFIG(bool, enableVhost, General::kEnableVhost); + switch (type) { + case Recorder::type_hls: { + GET_CONFIG(string, hlsPath, Hls::kFilePath); + string m3u8FilePath; + if (enableVhost) { + m3u8FilePath = vhost + "/" + app + "/" + stream_id + "/hls.m3u8"; + } else { + m3u8FilePath = app + "/" + stream_id + "/hls.m3u8"; + } + //Here we use the customized file path. + if (!customized_path.empty()) { + m3u8FilePath = customized_path + "/hls.m3u8"; + } + return File::absolutePath(m3u8FilePath, hlsPath); + } + case Recorder::type_mp4: { + GET_CONFIG(string, recordPath, Record::kFilePath); + GET_CONFIG(string, recordAppName, Record::kAppName); + string mp4FilePath; + if (enableVhost) { + mp4FilePath = vhost + "/" + recordAppName + "/" + app + "/" + stream_id + "/"; + } else { + mp4FilePath = recordAppName + "/" + app + "/" + stream_id + "/"; + } + //Here we use the customized file path. + if (!customized_path.empty()) { + mp4FilePath = customized_path + "/"; + } + return File::absolutePath(mp4FilePath, recordPath); + } + default: + return ""; + } +} +//////////////////////////////////////////////////////////////////////////////////////// + +class RecorderHelper { +public: + typedef std::shared_ptr Ptr; + + /** + * 构建函数 + * @param bContinueRecord false表明hls录制从头开始录制(意味着hls临时文件在媒体反注册时会被删除) + */ + RecorderHelper(const MediaSinkInterface::Ptr &recorder, bool bContinueRecord) { + _recorder = recorder; + _continueRecord = bContinueRecord; + } + + ~RecorderHelper() { + resetTracks(); + } + + // 附则于track上 + void attachTracks(vector &&tracks, const string &schema){ + if(isTracksSame(tracks)){ + return; + } + resetTracks(); + _tracks = std::move(tracks); + _schema = schema; + for (auto &track : _tracks) { + _recorder->addTrack(track); + track->addDelegate(_recorder); + } + } + + + // 判断新的tracks是否与之前的一致 + bool isTracksSame(const vector &tracks){ + if(tracks.size() != _tracks.size()) { + return false; + } + int i = 0; + for(auto &track : tracks){ + if(track != _tracks[i++]){ + return false; + } + } + return true; + } + + // 重置所有track + void resetTracks(){ + if(_tracks.empty()){ + return; + } + for (auto &track : _tracks) { + track->delDelegate(_recorder.get()); + } + _tracks.clear(); + _recorder->resetTracks(); + } + + // 返回false表明hls录制从头开始录制(意味着hls临时文件在媒体反注册时会被删除) + bool continueRecord(){ + return _continueRecord; + } + + bool isRecording() { + return !_tracks.empty(); + } + + const string &getSchema() const{ + return _schema; + } + + const MediaSinkInterface::Ptr& getRecorder() const{ + return _recorder; + } +private: + MediaSinkInterface::Ptr _recorder; + vector _tracks; + bool _continueRecord; + string _schema; +}; + + +template +class MediaSourceWatcher { +public: + static MediaSourceWatcher& Instance(){ + static MediaSourceWatcher instance; + return instance; + } + + Recorder::status getRecordStatus(const string &vhost, const string &app, const string &stream_id) { + return getRecordStatus_l(getRecorderKey(vhost, app, stream_id)); + } + + MediaSinkInterface::Ptr getRecorder(const string &vhost, const string &app, const string &stream_id) const{ + auto key = getRecorderKey(vhost, app, stream_id); + lock_guard lck(_recorder_mtx); + auto it = _recorder_map.find(key); + if (it == _recorder_map.end()) { + return nullptr; + } + return it->second->getRecorder(); + } + + int startRecord(const string &vhost, const string &app, const string &stream_id, const string &customized_path, bool waitForRecord, bool continueRecord) { + auto key = getRecorderKey(vhost, app, stream_id); + lock_guard lck(_recorder_mtx); + if (getRecordStatus_l(key) != Recorder::status_not_record) { + // 已经在录制了 + return 0; + } + + auto src = findMediaSource(vhost, app, stream_id); + if (!waitForRecord && !src) { + // 暂时无法开启录制 + return -1; + } + + auto recorder = Recorder::createRecorder(type, vhost, app, stream_id, customized_path); + if (!recorder) { + // 创建录制器失败 + WarnL << "不支持该录制类型:" << type; + return -2; + } + auto helper = std::make_shared(recorder, continueRecord); + if(src){ + auto tracks = src->getTracks(needTrackReady()); + if(tracks.size()){ + helper->attachTracks(std::move(tracks),src->getSchema()); + } + auto hls_recorder = dynamic_pointer_cast(recorder); + if(hls_recorder){ + hls_recorder->getMediaSource()->setListener(src->getListener()); + } + } + + _recorder_map[key] = std::move(helper); + return 0; + } + + bool stopRecord(const string &vhost, const string &app, const string &stream_id) { + lock_guard lck(_recorder_mtx); + return _recorder_map.erase(getRecorderKey(vhost, app, stream_id)); + } + + void stopAll(){ + lock_guard lck(_recorder_mtx); + _recorder_map.clear(); + } + +private: + MediaSourceWatcher(){ + //保存NoticeCenter的强引用,防止在MediaSourceWatcher单例释放前释放NoticeCenter单例 + _notice_center = NoticeCenter::Instance().shared_from_this(); + _notice_center->addListener(this,Broadcast::kBroadcastMediaChanged,[this](BroadcastMediaChangedArgs){ + if(!bRegist){ + removeRecorder(sender); + } + }); + _notice_center->addListener(this,Broadcast::kBroadcastMediaResetTracks,[this](BroadcastMediaResetTracksArgs){ + addRecorder(sender); + }); + } + + ~MediaSourceWatcher(){ + _notice_center->delListener(this,Broadcast::kBroadcastMediaChanged); + _notice_center->delListener(this,Broadcast::kBroadcastMediaResetTracks); + } + + void addRecorder(MediaSource &sender){ + auto tracks = sender.getTracks(needTrackReady()); + auto key = getRecorderKey(sender.getVhost(),sender.getApp(),sender.getId()); + lock_guard lck(_recorder_mtx); + auto it = _recorder_map.find(key); + if(it == _recorder_map.end()){ + // 录像记录不存在 + return; + } + + if(!it->second->isRecording() || it->second->getSchema() == sender.getSchema()){ + // 绑定的协议一致或者并未正在录制则替换tracks + if (!tracks.empty()) { + it->second->attachTracks(std::move(tracks),sender.getSchema()); + } + } + } + + void removeRecorder(MediaSource &sender){ + auto key = getRecorderKey(sender.getVhost(),sender.getApp(),sender.getId()); + lock_guard lck(_recorder_mtx); + auto it = _recorder_map.find(key); + if(it == _recorder_map.end() || it->second->getSchema() != sender.getSchema()){ + // 录像记录不存在或绑定的协议不一致 + return; + } + + if(it->second->continueRecord()){ + // 如果可以继续录制,那么只重置tracks,不删除对象 + it->second->resetTracks(); + }else{ + // 删除对象(意味着可能删除hls临时文件) + _recorder_map.erase(it); + } + } + + Recorder::status getRecordStatus_l(const string &key) { + auto it = _recorder_map.find(key); + if (it == _recorder_map.end()) { + return Recorder::status_not_record; + } + return it->second->isRecording() ? Recorder::status_recording : Recorder::status_wait_record; + } + + // 查找MediaSource以便录制 + MediaSource::Ptr findMediaSource(const string &vhost, const string &app, const string &stream_id) { + bool need_ready = needTrackReady(); + auto src = MediaSource::find(RTMP_SCHEMA, vhost, app, stream_id); + if (src) { + auto ret = src->getTracks(need_ready); + if (!ret.empty()) { + return std::move(src); + } + } + + src = MediaSource::find(RTSP_SCHEMA, vhost, app, stream_id); + if (src) { + auto ret = src->getTracks(need_ready); + if (!ret.empty()) { + return std::move(src); + } + } + return nullptr; + } + + string getRecorderKey(const string &vhost, const string &app, const string &stream_id) const{ + return vhost + "/" + app + "/" + stream_id; + } + + + /** + * 有些录制类型不需要track就绪即可录制 + */ + bool needTrackReady(){ + switch (type){ + case Recorder::type_hls: + return false; + case Recorder::type_mp4: + return true; + default: + return true; + } + } +private: + mutable recursive_mutex _recorder_mtx; + NoticeCenter::Ptr _notice_center; + unordered_map _recorder_map; +}; + + +Recorder::status Recorder::getRecordStatus(Recorder::type type, const string &vhost, const string &app, const string &stream_id) { + switch (type){ + case type_mp4: + return MediaSourceWatcher::Instance().getRecordStatus(vhost,app,stream_id); + case type_hls: + return MediaSourceWatcher::Instance().getRecordStatus(vhost,app,stream_id); + } + return status_not_record; +} + +std::shared_ptr Recorder::getRecorder(type type, const string &vhost, const string &app, const string &stream_id){ + switch (type){ + case type_mp4: + return MediaSourceWatcher::Instance().getRecorder(vhost,app,stream_id); + case type_hls: + return MediaSourceWatcher::Instance().getRecorder(vhost,app,stream_id); + } + return nullptr; +} + +std::shared_ptr Recorder::createRecorder(type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path){ + auto path = Recorder::getRecordPath(type, vhost, app, stream_id); + switch (type) { + case Recorder::type_hls: { +#if defined(ENABLE_HLS) + auto ret = std::make_shared(path, string(VHOST_KEY) + "=" + vhost); + ret->setMediaSource(vhost, app, stream_id); + return ret; +#endif + return nullptr; + } + + case Recorder::type_mp4: { +#if defined(ENABLE_MP4RECORD) + return std::make_shared(path, vhost, app, stream_id); +#endif + return nullptr; + } + + default: + return nullptr; + } +} + +int Recorder::startRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path, bool waitForRecord, bool continueRecord) { + switch (type){ + case type_mp4: + return MediaSourceWatcher::Instance().startRecord(vhost,app,stream_id,customized_path,waitForRecord,continueRecord); + case type_hls: + return MediaSourceWatcher::Instance().startRecord(vhost,app,stream_id,customized_path,waitForRecord,continueRecord); + } + WarnL << "unknown record type: " << type; + return -3; +} + +bool Recorder::stopRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id) { + switch (type){ + case type_mp4: + return MediaSourceWatcher::Instance().stopRecord(vhost,app,stream_id); + case type_hls: + return MediaSourceWatcher::Instance().stopRecord(vhost,app,stream_id); + } + return false; +} + +void Recorder::stopAll() { + MediaSourceWatcher::Instance().stopAll(); + MediaSourceWatcher::Instance().stopAll(); +} + +} /* namespace mediakit */ diff --git a/src/Record/Recorder.h b/src/Record/Recorder.h new file mode 100644 index 00000000..cb3d9cc6 --- /dev/null +++ b/src/Record/Recorder.h @@ -0,0 +1,130 @@ +/* + * MIT License + * + * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SRC_MEDIAFILE_RECORDER_H_ +#define SRC_MEDIAFILE_RECORDER_H_ + +#include +#include +using namespace std; + +namespace mediakit { + +class MediaSinkInterface; + +class Recorder{ +public: + typedef enum { + // 未录制 + status_not_record = 0, + // 等待MediaSource注册,注册成功后立即开始录制 + status_wait_record = 1, + // MediaSource已注册,并且正在录制 + status_recording = 2, + } status; + + typedef enum { + // 录制hls + type_hls = 0, + // 录制MP4 + type_mp4 = 1 + } type; + + /** + * 获取录制文件绝对路径 + * @param type hls还是MP4录制 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream_id 流id + * @param customized_path 录像文件保存自定义目录,默认为空则自动生成 + * @return 录制文件绝对路径 + */ + static string getRecordPath(type type, const string &vhost, const string &app, const string &stream_id,const string &customized_path = ""); + + /** + * 获取录制状态 + * @param type hls还是MP4录制 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream_id 流id + * @return 录制状态 + */ + static status getRecordStatus(type type, const string &vhost, const string &app, const string &stream_id); + + /** + * 开始录制 + * @param type hls还是MP4录制 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream_id 流id + * @param customized_path 录像文件保存自定义目录,默认为空则自动生成 + * @param waitForRecord 是否等待流注册后再录制,未注册时,置false将返回失败 + * @param continueRecord 流注销时是否继续等待录制还是立即停止录制 + * @return 0代表成功,负数代表失败 + */ + static int startRecord(type type, const string &vhost, const string &app, const string &stream_id,const string &customized_path,bool waitForRecord, bool continueRecord); + + /** + * 停止录制 + * @param type hls还是MP4录制 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream_id 流id + */ + static bool stopRecord(type type, const string &vhost, const string &app, const string &stream_id); + + /** + * 停止所有录制,一般程序退出时调用 + */ + static void stopAll(); + + /** + * 获取录制对象 + * @param type hls还是MP4录制 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream_id 流id + */ + static std::shared_ptr getRecorder(type type, const string &vhost, const string &app, const string &stream_id); + + /** + * 创建录制器对象 + * @param type hls还是MP4录制 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param stream_id 流id + * @param customized_path 录像文件保存自定义目录,默认为空则自动生成 + * @return 对象指针,可能为nullptr + */ + static std::shared_ptr createRecorder(type type, const string &vhost, const string &app, const string &stream_id, const string &customized_path); +private: + Recorder() = delete; + ~Recorder() = delete; +}; + +} /* namespace mediakit */ + +#endif /* SRC_MEDIAFILE_RECORDER_H_ */ diff --git a/src/MediaFile/TsMuxer.cpp b/src/Record/TsMuxer.cpp similarity index 90% rename from src/MediaFile/TsMuxer.cpp rename to src/Record/TsMuxer.cpp index fe63624b..e63c4f8f 100644 --- a/src/MediaFile/TsMuxer.cpp +++ b/src/Record/TsMuxer.cpp @@ -40,16 +40,21 @@ TsMuxer::~TsMuxer() { } void TsMuxer::addTrack(const Track::Ptr &track) { - switch (track->getCodecId()){ + switch (track->getCodecId()) { case CodecH264: { + _have_video = true; _codec_to_trackid[track->getCodecId()].track_id = mpeg_ts_add_stream(_context, PSI_STREAM_H264, nullptr, 0); - } break; + } + break; case CodecH265: { + _have_video = true; _codec_to_trackid[track->getCodecId()].track_id = mpeg_ts_add_stream(_context, PSI_STREAM_H265, nullptr, 0); - }break; + } + break; case CodecAAC: { _codec_to_trackid[track->getCodecId()].track_id = mpeg_ts_add_stream(_context, PSI_STREAM_AAC, nullptr, 0); - }break; + } + break; default: break; } @@ -63,7 +68,7 @@ void TsMuxer::inputFrame(const Frame::Ptr &frame) { //mp4文件时间戳需要从0开始 auto &track_info = it->second; int64_t dts_out, pts_out; - + _is_idr_fast_packet = !_have_video; switch (frame->getCodecId()){ case CodecH265: case CodecH264: { @@ -80,6 +85,9 @@ void TsMuxer::inputFrame(const Frame::Ptr &frame) { merged.append("\x00\x00\x00\x01",4); merged.append(frame->data(),frame->size()); } + if(frame->keyFrame()){ + _is_idr_fast_packet = true; + } }); merged_frame = std::make_shared(std::move(merged)); } @@ -101,6 +109,7 @@ void TsMuxer::inputFrame(const Frame::Ptr &frame) { } void TsMuxer::resetTracks() { + _have_video = false; //通知片段中断 onTs(nullptr, 0, 0, 0); uninit(); @@ -119,7 +128,8 @@ void TsMuxer::init() { }, [](void* param, const void* packet, size_t bytes){ TsMuxer *muxer = (TsMuxer *)param; - muxer->onTs(packet, bytes,muxer->_timestamp,0); + muxer->onTs(packet, bytes,muxer->_timestamp,muxer->_is_idr_fast_packet); + muxer->_is_idr_fast_packet = false; } }; if(_context == nullptr){ diff --git a/src/MediaFile/TsMuxer.h b/src/Record/TsMuxer.h similarity index 92% rename from src/MediaFile/TsMuxer.h rename to src/Record/TsMuxer.h index a876c8c3..b484a864 100644 --- a/src/MediaFile/TsMuxer.h +++ b/src/Record/TsMuxer.h @@ -32,13 +32,13 @@ #include "Extension/Track.h" #include "Util/File.h" #include "Common/MediaSink.h" -#include "Stamp.h" +#include "Common/Stamp.h" using namespace toolkit; namespace mediakit { -class TsMuxer : public MediaSink { +class TsMuxer : public MediaSinkInterface { public: TsMuxer(); virtual ~TsMuxer(); @@ -46,7 +46,7 @@ public: void resetTracks() override; void inputFrame(const Frame::Ptr &frame) override; protected: - virtual void onTs(const void *packet, int bytes,uint32_t timestamp,int flags) = 0; + virtual void onTs(const void *packet, int bytes,uint32_t timestamp,bool is_idr_fast_packet) = 0; private: void init(); void uninit(); @@ -61,6 +61,8 @@ private: }; unordered_map _codec_to_trackid; List _frameCached; + bool _is_idr_fast_packet = false; + bool _have_video = false; }; }//namespace mediakit diff --git a/src/Rtmp/FlvMuxer.h b/src/Rtmp/FlvMuxer.h index 7f48d3fd..a01d6250 100644 --- a/src/Rtmp/FlvMuxer.h +++ b/src/Rtmp/FlvMuxer.h @@ -30,7 +30,7 @@ #include "Rtmp/Rtmp.h" #include "Rtmp/RtmpMediaSource.h" #include "Network/Socket.h" -#include "MediaFile/Stamp.h" +#include "Common/Stamp.h" using namespace toolkit; namespace mediakit { diff --git a/src/Rtmp/RtmpCodec.h b/src/Rtmp/RtmpCodec.h index 525a32da..25f2fc5a 100644 --- a/src/Rtmp/RtmpCodec.h +++ b/src/Rtmp/RtmpCodec.h @@ -34,25 +34,29 @@ using namespace toolkit; namespace mediakit{ -class RtmpRingInterface { +class RtmpRing{ public: + typedef std::shared_ptr Ptr; typedef RingBuffer RingType; - typedef std::shared_ptr Ptr; - RtmpRingInterface(){} - virtual ~RtmpRingInterface(){} + RtmpRing(){} + virtual ~RtmpRing(){} /** * 获取rtmp环形缓存 * @return */ - virtual RingType::Ptr getRtmpRing() const = 0; + virtual RingType::Ptr getRtmpRing() const{ + return _rtmpRing; + } /** * 设置rtmp环形缓存 * @param ring */ - virtual void setRtmpRing(const RingType::Ptr &ring) = 0; + virtual void setRtmpRing(const RingType::Ptr &ring){ + _rtmpRing = ring; + } /** * 输入rtmp包 @@ -60,26 +64,7 @@ public: * @param key_pos 是否为关键帧 * @return 是否为关键帧 */ - virtual bool inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos) = 0; -}; - -class RtmpRing : public RtmpRingInterface { -public: - typedef std::shared_ptr Ptr; - - RtmpRing(){ - } - virtual ~RtmpRing(){} - - RingType::Ptr getRtmpRing() const override { - return _rtmpRing; - } - - void setRtmpRing(const RingType::Ptr &ring) override { - _rtmpRing = ring; - } - - bool inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos) override{ + virtual bool inputRtmp(const RtmpPacket::Ptr &rtmp, bool key_pos){ if(_rtmpRing){ _rtmpRing->write(rtmp,key_pos); } @@ -90,11 +75,12 @@ protected: }; -class RtmpCodec : public RtmpRing, public FrameRingInterfaceDelegate , public CodecInfo{ +class RtmpCodec : public RtmpRing, public FrameDispatcher , public CodecInfo{ public: typedef std::shared_ptr Ptr; RtmpCodec(){} virtual ~RtmpCodec(){} + virtual void makeConfigPacket() {}; }; diff --git a/src/Rtmp/RtmpDemuxer.cpp b/src/Rtmp/RtmpDemuxer.cpp index 818b0af5..65f8fb91 100644 --- a/src/Rtmp/RtmpDemuxer.cpp +++ b/src/Rtmp/RtmpDemuxer.cpp @@ -29,8 +29,7 @@ namespace mediakit { - -RtmpDemuxer::RtmpDemuxer(const AMFValue &val) { +void RtmpDemuxer::loadMetaData(const AMFValue &val){ try { makeVideoTrack(val["videocodecid"]); makeAudioTrack(val["audiocodecid"]); @@ -45,7 +44,6 @@ RtmpDemuxer::RtmpDemuxer(const AMFValue &val) { } } - bool RtmpDemuxer::inputRtmp(const RtmpPacket::Ptr &pkt) { switch (pkt->typeId) { case MSG_VIDEO: { @@ -86,6 +84,7 @@ void RtmpDemuxer::makeVideoTrack(const AMFValue &videoCodec) { if (_videoRtmpDecoder) { //设置rtmp解码器代理,生成的frame写入该Track _videoRtmpDecoder->addDelegate(_videoTrack); + onAddTrack(_videoTrack); } else { //找不到相应的rtmp解码器,该track无效 _videoTrack.reset(); @@ -102,6 +101,7 @@ void RtmpDemuxer::makeAudioTrack(const AMFValue &audioCodec) { if (_audioRtmpDecoder) { //设置rtmp解码器代理,生成的frame写入该Track _audioRtmpDecoder->addDelegate(_audioTrack); + onAddTrack(_audioTrack); } else { //找不到相应的rtmp解码器,该track无效 _audioTrack.reset(); diff --git a/src/Rtmp/RtmpDemuxer.h b/src/Rtmp/RtmpDemuxer.h index 64ef1cf5..a2c900a8 100644 --- a/src/Rtmp/RtmpDemuxer.h +++ b/src/Rtmp/RtmpDemuxer.h @@ -43,17 +43,10 @@ class RtmpDemuxer : public Demuxer{ public: typedef std::shared_ptr Ptr; - /** - * 等效于RtmpDemuxer(AMFValue(AMF_NULL)) - */ - RtmpDemuxer(){} - /** - * 构造rtmp解复用器 - * @param val rtmp的metadata,可以传入null类型, - * 这样就会在inputRtmp时异步探测媒体编码格式 - */ - RtmpDemuxer(const AMFValue &val); - virtual ~RtmpDemuxer(){}; + RtmpDemuxer() = default; + virtual ~RtmpDemuxer() = default; + + void loadMetaData(const AMFValue &metadata); /** * 开始解复用 diff --git a/src/Rtmp/RtmpMediaSource.h b/src/Rtmp/RtmpMediaSource.h index 5e13faae..db13960e 100644 --- a/src/Rtmp/RtmpMediaSource.h +++ b/src/Rtmp/RtmpMediaSource.h @@ -46,130 +46,175 @@ #include "Thread/ThreadPool.h" using namespace toolkit; +#define RTMP_GOP_SIZE 512 namespace mediakit { -class RtmpMediaSource: public MediaSource ,public RingDelegate { +/** + * rtmp媒体源的数据抽象 + * rtmp有关键的三要素,分别是metadata、config帧,普通帧 + * 其中metadata是非必须的,有些编码格式也没有config帧(比如MP3) + * 只要生成了这三要素,那么要实现rtmp推流、rtmp服务器就很简单了 + * rtmp推拉流协议中,先传递metadata,然后传递config帧,然后一直传递普通帧 + */ +class RtmpMediaSource : public MediaSource, public RingDelegate { public: typedef std::shared_ptr Ptr; typedef RingBuffer RingType; + /** + * 构造函数 + * @param vhost 虚拟主机名 + * @param app 应用名 + * @param stream_id 流id + * @param ring_size 可以设置固定的环形缓冲大小,0则自适应 + */ RtmpMediaSource(const string &vhost, - const string &strApp, - const string &strId, - int ringSize = 0) : - MediaSource(RTMP_SCHEMA,vhost,strApp,strId), - _ringSize(ringSize) {} + const string &app, + const string &stream_id, + int ring_size = RTMP_GOP_SIZE) : + MediaSource(RTMP_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) { + } virtual ~RtmpMediaSource() {} + /** + * 获取媒体源的环形缓冲 + */ const RingType::Ptr &getRing() const { - //获取媒体源的rtp环形缓冲 - return _pRing; + return _ring; } + /** + * 获取播放器个数 + * @return + */ int readerCount() override { - return _pRing ? _pRing->readerCount() : 0; + return _ring ? _ring->readerCount() : 0; } + /** + * 获取metadata + */ const AMFValue &getMetaData() const { - lock_guard lock(_mtxMap); + lock_guard lock(_mtx); return _metadata; } - template - void getConfigFrame(const FUN &f) { - lock_guard lock(_mtxMap); - for (auto &pr : _mapCfgFrame) { + + /** + * 获取所有的config帧 + */ + template + void getConfigFrame(const FUNC &f) { + lock_guard lock(_mtx); + for (auto &pr : _config_frame_map) { f(pr.second); } } - virtual void onGetMetaData(const AMFValue &metadata) { - lock_guard lock(_mtxMap); + /** + * 设置metadata + */ + virtual void setMetaData(const AMFValue &metadata) { + lock_guard lock(_mtx); _metadata = metadata; - } - - void onWrite(const RtmpPacket::Ptr &pkt,bool isKey = true) override { - lock_guard lock(_mtxMap); - if (pkt->isCfgFrame()) { - _mapCfgFrame[pkt->typeId] = pkt; - return; - } - - _mapStamp[pkt->typeId] = pkt->timeStamp; - if(!_pRing){ - weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); - _pRing = std::make_shared(_ringSize,[weakSelf](const EventPoller::Ptr &,int size,bool){ - auto strongSelf = weakSelf.lock(); - if(!strongSelf){ - return; - } - strongSelf->onReaderChanged(size); - }); - onReaderChanged(0); - } - - if(!_registed && _metadata && _mapCfgFrame.size() >= getTrackSize()){ - //在未注册的情况下,需要获取到metadata并且获取到全部的config帧才可以注册 - _registed = true; + if(_ring){ regist(); } + } - _pRing->write(pkt,pkt->isVideoKeyFrame()); + /** + * 输入rtmp包 + * @param pkt rtmp包 + * @param key 是否为关键帧 + */ + void onWrite(const RtmpPacket::Ptr &pkt, bool key = true) override { + lock_guard lock(_mtx); + if(pkt->typeId == MSG_VIDEO){ + //有视频,那么启用GOP缓存 + _have_video = true; + } + if (pkt->isCfgFrame()) { + _config_frame_map[pkt->typeId] = pkt; + return; + } + + if (!_ring) { + weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + auto lam = [weakSelf](const EventPoller::Ptr &, int size, bool) { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return; + } + strongSelf->onReaderChanged(size); + }; + + //rtmp包缓存最大允许512个,如果是纯视频(25fps)大概为20秒数据 + //但是这个是GOP缓存的上限值,真实的GOP缓存大小等于两个I帧之间的包数的两倍 + //而且每次遇到I帧,则会清空GOP缓存,所以真实的GOP缓存远小于此值 + _ring = std::make_shared(_ring_size,std::move(lam)); + onReaderChanged(0); + + if(_metadata){ + regist(); + } + } + _track_stamps_map[pkt->typeId] = pkt->timeStamp; + //不存在视频,为了减少缓存延时,那么关闭GOP缓存 + _ring->write(pkt, _have_video ? pkt->isVideoKeyFrame() : true); checkNoneReader(); - } + } + /** + * 获取当前时间戳 + */ uint32_t getTimeStamp(TrackType trackType) override { - lock_guard lock(_mtxMap); - switch (trackType){ + lock_guard lock(_mtx); + switch (trackType) { case TrackVideo: - return _mapStamp[MSG_VIDEO]; + return _track_stamps_map[MSG_VIDEO]; case TrackAudio: - return _mapStamp[MSG_AUDIO]; + return _track_stamps_map[MSG_AUDIO]; default: - return MAX(_mapStamp[MSG_VIDEO],_mapStamp[MSG_AUDIO]); + return MAX(_track_stamps_map[MSG_VIDEO], _track_stamps_map[MSG_AUDIO]); } } private: - void onReaderChanged(int size){ - //我们记录最后一次活动时间 - _readerTicker.resetTime(); - if(size != 0 || readerCount() != 0){ - //还有消费者正在观看该流 - _asyncEmitNoneReader = false; - return; - } - _asyncEmitNoneReader = true; - } - - void checkNoneReader(){ - GET_CONFIG(int,stream_none_reader_delay,General::kStreamNoneReaderDelayMS); - if(_asyncEmitNoneReader && _readerTicker.elapsedTime() > stream_none_reader_delay){ - _asyncEmitNoneReader = false; - onNoneReader(); - } - } - - int getTrackSize(){ - int ret = 0; - if(_metadata["videocodecid"]){ - ++ret; + /** + * 每次增减消费者都会触发该函数 + */ + void onReaderChanged(int size) { + //我们记录最后一次活动时间 + _reader_changed_ticker.resetTime(); + if (size != 0 || totalReaderCount() != 0) { + //还有消费者正在观看该流 + _async_emit_none_reader = false; + return; } - if(_metadata["audiocodecid"]){ - ++ret; + _async_emit_none_reader = true; + } + + /** + * 检查是否无人消费该流, + * 如果无人消费且超过一定时间会触发onNoneReader事件 + */ + void checkNoneReader() { + GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS); + if (_async_emit_none_reader && _reader_changed_ticker.elapsedTime() > stream_none_reader_delay) { + _async_emit_none_reader = false; + onNoneReader(); } - return ret; } protected: + int _ring_size; + bool _async_emit_none_reader = false; + bool _have_video = false; + mutable recursive_mutex _mtx; + Ticker _reader_changed_ticker; AMFValue _metadata; - unordered_map _mapCfgFrame; - unordered_map _mapStamp; - mutable recursive_mutex _mtxMap; - RingBuffer::Ptr _pRing; //rtp环形缓冲 - int _ringSize; - Ticker _readerTicker; - bool _asyncEmitNoneReader = false; - bool _registed = false; + RingBuffer::Ptr _ring; + unordered_map _track_stamps_map; + unordered_map _config_frame_map; }; } /* namespace mediakit */ diff --git a/src/Rtmp/RtmpToRtspMediaSource.h b/src/Rtmp/RtmpMediaSourceImp.h similarity index 56% rename from src/Rtmp/RtmpToRtspMediaSource.h rename to src/Rtmp/RtmpMediaSourceImp.h index 4d7c864b..fca06936 100644 --- a/src/Rtmp/RtmpToRtspMediaSource.h +++ b/src/Rtmp/RtmpMediaSourceImp.h @@ -39,55 +39,48 @@ #include "RtmpMediaSource.h" #include "RtmpDemuxer.h" #include "Common/MultiMediaSourceMuxer.h" - using namespace std; using namespace toolkit; namespace mediakit { - -class RtmpToRtspMediaSource: public RtmpMediaSource { +class RtmpMediaSourceImp: public RtmpMediaSource, public Demuxer::Listener , public MultiMediaSourceMuxer::Listener { public: - typedef std::shared_ptr Ptr; + typedef std::shared_ptr Ptr; - RtmpToRtspMediaSource(const string &vhost, - const string &app, - const string &id, - int ringSize = 0) : RtmpMediaSource(vhost, app, id,ringSize){ - } - virtual ~RtmpToRtspMediaSource(){} - - void onGetMetaData(const AMFValue &metadata) override { - if(!_demuxer){ - //在未调用onWrite前,设置Metadata能触发生成RtmpDemuxer - _demuxer = std::make_shared(metadata); - } - RtmpMediaSource::onGetMetaData(metadata); + /** + * 构造函数 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param id 流id + * @param ringSize 环形缓存大小 + */ + RtmpMediaSourceImp(const string &vhost, const string &app, const string &id, int ringSize = RTMP_GOP_SIZE) : RtmpMediaSource(vhost, app, id, ringSize) { + _demuxer = std::make_shared(); + _demuxer->setTrackListener(this); } + ~RtmpMediaSourceImp() = default; + + /** + * 设置metadata + */ + void setMetaData(const AMFValue &metadata) override{ + _demuxer->loadMetaData(metadata); + RtmpMediaSource::setMetaData(metadata); + } + + /** + * 输入rtmp并解析 + */ void onWrite(const RtmpPacket::Ptr &pkt,bool key_pos = true) override { - if(!_demuxer){ - //尚未获取Metadata,那么不管有没有Metadata,都生成RtmpDemuxer - _demuxer = std::make_shared(); - } - _demuxer->inputRtmp(pkt); - if(!_muxer && _demuxer->isInited(2000)){ - _muxer = std::make_shared(getVhost(), - getApp(), - getId(), - _demuxer->getDuration(), - _enableRtsp, - false,//不重复生成rtmp - _enableHls, - _enableMP4); - for (auto &track : _demuxer->getTracks(false)){ - _muxer->addTrack(track); - track->addDelegate(_muxer); - } - _muxer->setListener(_listener); - } + key_pos = _demuxer->inputRtmp(pkt); RtmpMediaSource::onWrite(pkt,key_pos); } + /** + * 设置监听器 + * @param listener + */ void setListener(const std::weak_ptr &listener) override { RtmpMediaSource::setListener(listener); if(_muxer){ @@ -95,20 +88,12 @@ public: } } - int readerCount() override { - return RtmpMediaSource::readerCount() + (_muxer ? _muxer->readerCount() : 0); - } - - /** - * 获取track - * @return + /** + * 获取观看总人数,包括(hls/rtsp/rtmp) */ - vector getTracks(bool trackReady) const override { - if(!_demuxer){ - return this->RtmpMediaSource::getTracks(trackReady); - } - return _demuxer->getTracks(trackReady); - } + int totalReaderCount() override{ + return readerCount() + (_muxer ? _muxer->totalReaderCount() : 0); + } /** * 设置协议转换 @@ -116,20 +101,37 @@ public: * @param enableHls 是否转换成hls * @param enableMP4 是否mp4录制 */ - void setProtocolTranslation(bool enableRtsp,bool enableHls,bool enableMP4){ -// DebugL << enableRtsp << " " << enableHls << " " << enableMP4; - _enableRtsp = enableRtsp; - _enableHls = enableHls; - _enableMP4 = enableMP4; + void setProtocolTranslation(bool enableRtsp, bool enableHls, bool enableMP4) { + //不重复生成rtmp + _muxer = std::make_shared(getVhost(), getApp(), getId(), _demuxer->getDuration(), enableRtsp, false, enableHls, enableMP4); + _muxer->setListener(getListener()); + _muxer->setTrackListener(this); + for(auto &track : _demuxer->getTracks(false)){ + _muxer->addTrack(track); + track->addDelegate(_muxer); + } + } + + /** + * _demuxer触发的添加Track事件 + */ + void onAddTrack(const Track::Ptr &track) override { + if(_muxer){ + _muxer->addTrack(track); + track->addDelegate(_muxer); + } + } + + /** + * _muxer触发的所有Track就绪的事件 + */ + void onAllTrackReady() override{ + setTrackSource(_muxer); } private: RtmpDemuxer::Ptr _demuxer; MultiMediaSourceMuxer::Ptr _muxer; - bool _enableHls = true; - bool _enableMP4 = false; - bool _enableRtsp = true; }; - } /* namespace mediakit */ #endif /* SRC_RTMP_RTMPTORTSPMEDIASOURCE_H_ */ diff --git a/src/Rtmp/RtmpMediaSourceMuxer.h b/src/Rtmp/RtmpMediaSourceMuxer.h index e3d9561d..dce7e70e 100644 --- a/src/Rtmp/RtmpMediaSourceMuxer.h +++ b/src/Rtmp/RtmpMediaSourceMuxer.h @@ -48,12 +48,23 @@ public: void setListener(const std::weak_ptr &listener){ _mediaSouce->setListener(listener); } + + void setTimeStamp(uint32_t stamp){ + _mediaSouce->setTimeStamp(stamp); + } + int readerCount() const{ return _mediaSouce->readerCount(); } -private: - void onAllTrackReady() override { - _mediaSouce->onGetMetaData(getMetadata()); + + void onAllTrackReady(){ + makeConfigPacket(); + _mediaSouce->setMetaData(getMetadata()); + } + + // 设置TrackSource + void setTrackSource(const std::weak_ptr &track_src){ + _mediaSouce->setTrackSource(track_src); } private: RtmpMediaSource::Ptr _mediaSouce; diff --git a/src/Rtmp/RtmpMuxer.cpp b/src/Rtmp/RtmpMuxer.cpp index 6ed7e324..89d345ca 100644 --- a/src/Rtmp/RtmpMuxer.cpp +++ b/src/Rtmp/RtmpMuxer.cpp @@ -35,16 +35,10 @@ RtmpMuxer::RtmpMuxer(const TitleMeta::Ptr &title) { }else{ _metadata = title->getMetadata(); } - _rtmpRing = std::make_shared(); + _rtmpRing = std::make_shared(); } -void RtmpMuxer::onTrackReady(const Track::Ptr &track) { - //生成rtmp编码器 - //克隆该Track,防止循环引用 - auto encoder = Factory::getRtmpCodecByTrack(track->clone()); - if (!encoder) { - return; - } +void RtmpMuxer::addTrack(const Track::Ptr &track) { //根据track生产metadata Metadata::Ptr metadata; switch (track->getTrackType()){ @@ -57,31 +51,55 @@ void RtmpMuxer::onTrackReady(const Track::Ptr &track) { } break; default: - return;; + return; } + + auto &encoder = _encoder[track->getTrackType()]; + //生成rtmp编码器,克隆该Track,防止循环引用 + encoder = Factory::getRtmpCodecByTrack(track->clone()); + if (!encoder) { + return; + } + + //设置rtmp输出环形缓存 + encoder->setRtmpRing(_rtmpRing); + //添加其metadata metadata->getMetadata().object_for_each([&](const std::string &key, const AMFValue &value){ _metadata.set(key,value); }); - //设置Track的代理,这样输入frame至Track时,最终数据将输出到RtmpEncoder中 - track->addDelegate(encoder); - //Rtmp编码器共用同一个环形缓存 - encoder->setRtmpRing(_rtmpRing); } +void RtmpMuxer::inputFrame(const Frame::Ptr &frame) { + auto &encoder = _encoder[frame->getTrackType()]; + if(encoder){ + encoder->inputFrame(frame); + } +} + +void RtmpMuxer::makeConfigPacket(){ + for(auto &encoder : _encoder){ + if(encoder){ + encoder->makeConfigPacket(); + } + } +} const AMFValue &RtmpMuxer::getMetadata() const { - if(!isAllTrackReady()){ - //尚未就绪 - static AMFValue s_amf; - return s_amf; - } return _metadata; } -RtmpRingInterface::RingType::Ptr RtmpMuxer::getRtmpRing() const { +RtmpRing::RingType::Ptr RtmpMuxer::getRtmpRing() const { return _rtmpRing; } +void RtmpMuxer::resetTracks() { + _metadata.clear(); + for(auto &encoder : _encoder){ + encoder = nullptr; + } +} + + }/* namespace mediakit */ \ No newline at end of file diff --git a/src/Rtmp/RtmpMuxer.h b/src/Rtmp/RtmpMuxer.h index 87552eb5..cc09178e 100644 --- a/src/Rtmp/RtmpMuxer.h +++ b/src/Rtmp/RtmpMuxer.h @@ -34,7 +34,7 @@ namespace mediakit{ -class RtmpMuxer : public MediaSink{ +class RtmpMuxer : public MediaSinkInterface{ public: typedef std::shared_ptr Ptr; @@ -54,17 +54,32 @@ public: * 获取rtmp环形缓存 * @return */ - RtmpRingInterface::RingType::Ptr getRtmpRing() const; -protected: + RtmpRing::RingType::Ptr getRtmpRing() const; + /** - * 某track已经准备好,其ready()状态返回true, - * 此时代表可以获取其例如sps pps等相关信息了 - * @param track - */ - void onTrackReady(const Track::Ptr & track) override ; + * 添加ready状态的track + */ + void addTrack(const Track::Ptr & track) override; + + /** + * 写入帧数据 + * @param frame 帧 + */ + void inputFrame(const Frame::Ptr &frame) override; + + /** + * 重置所有track + */ + void resetTracks() override ; + + /** + * 生成config包 + */ + void makeConfigPacket(); private: - RtmpRingInterface::RingType::Ptr _rtmpRing; + RtmpRing::RingType::Ptr _rtmpRing; AMFValue _metadata; + RtmpCodec::Ptr _encoder[TrackMax]; }; diff --git a/src/Rtmp/RtmpPlayer.cpp b/src/Rtmp/RtmpPlayer.cpp index cb5c4900..5251ca42 100644 --- a/src/Rtmp/RtmpPlayer.cpp +++ b/src/Rtmp/RtmpPlayer.cpp @@ -34,44 +34,35 @@ using namespace mediakit::Client; namespace mediakit { -unordered_map RtmpPlayer::g_mapCmd; RtmpPlayer::RtmpPlayer(const EventPoller::Ptr &poller) : TcpClient(poller) { - static onceToken token([]() { - g_mapCmd.emplace("_error",&RtmpPlayer::onCmd_result); - g_mapCmd.emplace("_result",&RtmpPlayer::onCmd_result); - g_mapCmd.emplace("onStatus",&RtmpPlayer::onCmd_onStatus); - g_mapCmd.emplace("onMetaData",&RtmpPlayer::onCmd_onMetaData); - }, []() {}); - } RtmpPlayer::~RtmpPlayer() { DebugL << endl; } + void RtmpPlayer::teardown() { if (alive()) { - _strApp.clear(); - _strStream.clear(); - _strTcUrl.clear(); - - { - lock_guard lck(_mtxOnResultCB); - _mapOnResultCB.clear(); - } - { - lock_guard lck(_mtxOnStatusCB); - _dqOnStatusCB.clear(); - } - _pBeatTimer.reset(); - _pPlayTimer.reset(); - _pMediaTimer.reset(); - _iSeekTo = 0; - CLEAR_ARR(_aiFistStamp); - CLEAR_ARR(_aiNowStamp); - reset(); - shutdown(SockException(Err_shutdown,"teardown")); + shutdown(SockException(Err_shutdown,"teardown")); } + _strApp.clear(); + _strStream.clear(); + _strTcUrl.clear(); + _pBeatTimer.reset(); + _pPlayTimer.reset(); + _pMediaTimer.reset(); + _iSeekTo = 0; + RtmpProtocol::reset(); + + CLEAR_ARR(_aiFistStamp); + CLEAR_ARR(_aiNowStamp); + + lock_guard lck(_mtxOnResultCB); + _mapOnResultCB.clear(); + lock_guard lck2(_mtxOnStatusCB); + _dqOnStatusCB.clear(); } + void RtmpPlayer::play(const string &strUrl) { teardown(); string strHost = FindField(strUrl.data(), "://", "/"); @@ -80,7 +71,7 @@ void RtmpPlayer::play(const string &strUrl) { _strTcUrl = string("rtmp://") + strHost + "/" + _strApp; if (!_strApp.size() || !_strStream.size()) { - onPlayResult_l(SockException(Err_other,"rtmp url非法")); + onPlayResult_l(SockException(Err_other,"rtmp url非法"),false); return; } DebugL << strHost << " " << _strApp << " " << _strStream; @@ -104,7 +95,7 @@ void RtmpPlayer::play(const string &strUrl) { if(!strongSelf) { return false; } - strongSelf->onPlayResult_l(SockException(Err_timeout,"play rtmp timeout")); + strongSelf->onPlayResult_l(SockException(Err_timeout,"play rtmp timeout"),false); return false; },getPoller())); @@ -112,53 +103,52 @@ void RtmpPlayer::play(const string &strUrl) { startConnect(strHost, iPort , playTimeOutSec); } void RtmpPlayer::onErr(const SockException &ex){ - onPlayResult_l(ex); + //定时器_pPlayTimer为空后表明握手结束了 + onPlayResult_l(ex, !_pPlayTimer); } -void RtmpPlayer::onPlayResult_l(const SockException &ex) { +void RtmpPlayer::onPlayResult_l(const SockException &ex , bool handshakeCompleted) { WarnL << ex.getErrCode() << " " << ex.what(); if(!ex){ - //恢复rtmp接收超时定时器 + //播放成功,恢复rtmp接收超时定时器 _mediaTicker.resetTime(); weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); int timeoutMS = (*this)[kMediaTimeoutMS].as(); + //创建rtmp数据接收超时检测定时器 _pMediaTimer.reset( new Timer(timeoutMS / 2000.0, [weakSelf,timeoutMS]() { auto strongSelf=weakSelf.lock(); if(!strongSelf) { return false; } if(strongSelf->_mediaTicker.elapsedTime()> timeoutMS) { - //recv media timeout! - strongSelf->onPlayResult_l(SockException(Err_timeout,"recv rtmp timeout")); + //接收rtmp媒体数据超时 + strongSelf->onPlayResult_l(SockException(Err_timeout,"receive rtmp timeout"),true); return false; } return true; },getPoller())); } - if (_pPlayTimer) { + if (!handshakeCompleted) { //开始播放阶段 _pPlayTimer.reset(); onPlayResult(ex); - }else { - //播放中途阶段 - if (ex) { - //播放成功后异常断开回调 - onShutdown(ex); - }else{ - //恢复播放 - onResume(); - } - } + } else if (ex) { + //播放成功后异常断开回调 + onShutdown(ex); + } else { + //恢复播放 + onResume(); + } if(ex){ teardown(); } } void RtmpPlayer::onConnect(const SockException &err){ - if(err.getErrCode()!=Err_success) { - onPlayResult_l(err); + if(err.getErrCode() != Err_success) { + onPlayResult_l(err, false); return; } weak_ptr weakSelf= dynamic_pointer_cast(shared_from_this()); @@ -175,7 +165,8 @@ void RtmpPlayer::onRecv(const Buffer::Ptr &pBuf){ onParseRtmp(pBuf->data(), pBuf->size()); } catch (exception &e) { SockException ex(Err_other, e.what()); - onPlayResult_l(ex); + //定时器_pPlayTimer为空后表明握手结束了 + onPlayResult_l(ex, !_pPlayTimer); } } @@ -253,7 +244,7 @@ inline void RtmpPlayer::send_pause(bool bPause) { }else{ _bPaused = bPause; if(!bPause){ - onPlayResult_l(SockException(Err_success, "rtmp resum success")); + onPlayResult_l(SockException(Err_success, "resum rtmp success"), true); }else{ //暂停播放 _pMediaTimer.reset(); @@ -327,7 +318,7 @@ void RtmpPlayer::onCmd_onMetaData(AMFDecoder &dec) { void RtmpPlayer::onStreamDry(uint32_t ui32StreamId) { //TraceL << ui32StreamId; - onPlayResult_l(SockException(Err_other,"rtmp stream dry")); + onPlayResult_l(SockException(Err_other,"rtmp stream dry"), true); } void RtmpPlayer::onMediaData_l(const RtmpPacket::Ptr &packet) { @@ -343,7 +334,7 @@ void RtmpPlayer::onMediaData_l(const RtmpPacket::Ptr &packet) { onMediaData(packet); }else{ //先触发onPlayResult事件,这个时候解码器才能初始化完毕 - onPlayResult_l(SockException(Err_success,"play rtmp success")); + onPlayResult_l(SockException(Err_success,"play rtmp success"), false); //触发onPlayResult事件后,再把帧数据输入到解码器 onMediaData(packet); } @@ -351,6 +342,15 @@ void RtmpPlayer::onMediaData_l(const RtmpPacket::Ptr &packet) { void RtmpPlayer::onRtmpChunk(RtmpPacket &chunkData) { + typedef void (RtmpPlayer::*rtmp_func_ptr)(AMFDecoder &dec); + static unordered_map s_func_map; + static onceToken token([]() { + s_func_map.emplace("_error",&RtmpPlayer::onCmd_result); + s_func_map.emplace("_result",&RtmpPlayer::onCmd_result); + s_func_map.emplace("onStatus",&RtmpPlayer::onCmd_onStatus); + s_func_map.emplace("onMetaData",&RtmpPlayer::onCmd_onMetaData); + }, []() {}); + switch (chunkData.typeId) { case MSG_CMD: case MSG_CMD3: @@ -358,8 +358,8 @@ void RtmpPlayer::onRtmpChunk(RtmpPacket &chunkData) { case MSG_DATA3: { AMFDecoder dec(chunkData.strBuf, 0); std::string type = dec.load(); - auto it = g_mapCmd.find(type); - if(it != g_mapCmd.end()){ + auto it = s_func_map.find(type); + if(it != s_func_map.end()){ auto fun = it->second; (this->*fun)(dec); }else{ diff --git a/src/Rtmp/RtmpPlayer.h b/src/Rtmp/RtmpPlayer.h index e63ee668..10bc8ff1 100644 --- a/src/Rtmp/RtmpPlayer.h +++ b/src/Rtmp/RtmpPlayer.h @@ -61,7 +61,8 @@ protected: void seekToMilliSecond(uint32_t ms); protected: void onMediaData_l(const RtmpPacket::Ptr &chunkData); - void onPlayResult_l(const SockException &ex); + //在获取config帧后才触发onPlayResult_l(而不是收到play命令回复),所以此时所有track都初始化完毕了 + void onPlayResult_l(const SockException &ex, bool handshakeCompleted); //form Tcpclient void onRecv(const Buffer::Ptr &pBuf) override; @@ -104,9 +105,6 @@ private: deque > _dqOnStatusCB; recursive_mutex _mtxOnStatusCB; - typedef void (RtmpPlayer::*rtmpCMDHandle)(AMFDecoder &dec); - static unordered_map g_mapCmd; - //超时功能实现 Ticker _mediaTicker; std::shared_ptr _pMediaTimer; diff --git a/src/Rtmp/RtmpPlayerImp.h b/src/Rtmp/RtmpPlayerImp.h index 49e8da6a..e90acc43 100644 --- a/src/Rtmp/RtmpPlayerImp.h +++ b/src/Rtmp/RtmpPlayerImp.h @@ -65,23 +65,30 @@ private: bool onCheckMeta(const AMFValue &val) override { _pRtmpMediaSrc = dynamic_pointer_cast(_pMediaSrc); if(_pRtmpMediaSrc){ - _pRtmpMediaSrc->onGetMetaData(val); + _pRtmpMediaSrc->setMetaData(val); + _set_meta_data = true; } - _parser.reset(new RtmpDemuxer(val)); + _delegate.reset(new RtmpDemuxer); + _delegate->loadMetaData(val); return true; } void onMediaData(const RtmpPacket::Ptr &chunkData) override { if(_pRtmpMediaSrc){ + if(!_set_meta_data && !chunkData->isCfgFrame()){ + _set_meta_data = true; + _pRtmpMediaSrc->setMetaData(TitleMeta().getMetadata()); + } _pRtmpMediaSrc->onWrite(chunkData); } - if(!_parser){ + if(!_delegate){ //这个流没有metadata - _parser.reset(new RtmpDemuxer()); + _delegate.reset(new RtmpDemuxer()); } - _parser->inputRtmp(chunkData); + _delegate->inputRtmp(chunkData); } private: RtmpMediaSource::Ptr _pRtmpMediaSrc; + bool _set_meta_data = false; }; diff --git a/src/Rtmp/RtmpProtocol.cpp b/src/Rtmp/RtmpProtocol.cpp index ad21351f..4de21a47 100644 --- a/src/Rtmp/RtmpProtocol.cpp +++ b/src/Rtmp/RtmpProtocol.cpp @@ -332,7 +332,7 @@ void RtmpProtocol::handle_C0C1() { //complex handsharke handle_C1_complex(); #else - WarnL << "未打开ENABLE_OPENSSL宏,复杂握手采用简单方式处理!"; + WarnL << "未打开ENABLE_OPENSSL宏,复杂握手采用简单方式处理,flash播放器可能无法播放!"; handle_C1_simple(); #endif//ENABLE_OPENSSL } @@ -372,10 +372,10 @@ void RtmpProtocol::handle_C1_complex(){ check_C1_Digest(digest,c1_joined); send_complex_S0S1S2(0,digest); - InfoL << "schema0"; +// InfoL << "schema0"; }catch(std::exception &ex){ //貌似flash从来都不用schema1 - WarnL << "try rtmp complex schema0 failed:" << ex.what(); +// WarnL << "try rtmp complex schema0 failed:" << ex.what(); try{ /* c1s1 schema1 time: 4bytes @@ -389,9 +389,9 @@ void RtmpProtocol::handle_C1_complex(){ check_C1_Digest(digest,c1_joined); send_complex_S0S1S2(1,digest); - InfoL << "schema1"; +// InfoL << "schema1"; }catch(std::exception &ex){ - WarnL << "try rtmp complex schema1 failed:" << ex.what(); +// WarnL << "try rtmp complex schema1 failed:" << ex.what(); handle_C1_simple(); } } diff --git a/src/Rtmp/RtmpPusher.cpp b/src/Rtmp/RtmpPusher.cpp index 834276a7..ea2ebca8 100644 --- a/src/Rtmp/RtmpPusher.cpp +++ b/src/Rtmp/RtmpPusher.cpp @@ -60,8 +60,8 @@ void RtmpPusher::teardown() { } } -void RtmpPusher::onPublishResult(const SockException &ex) { - if(_pPublishTimer){ +void RtmpPusher::onPublishResult(const SockException &ex,bool handshakeCompleted) { + if(!handshakeCompleted){ //播放结果回调 _pPublishTimer.reset(); if(_onPublished){ @@ -87,7 +87,7 @@ void RtmpPusher::publish(const string &strUrl) { _strTcUrl = string("rtmp://") + strHost + "/" + _strApp; if (!_strApp.size() || !_strStream.size()) { - onPublishResult(SockException(Err_other,"rtmp url非法")); + onPublishResult(SockException(Err_other,"rtmp url非法"),false); return; } DebugL << strHost << " " << _strApp << " " << _strStream; @@ -102,13 +102,13 @@ void RtmpPusher::publish(const string &strUrl) { } weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); - float playTimeOutSec = (*this)[kTimeoutMS].as() / 1000.0; - _pPublishTimer.reset( new Timer(playTimeOutSec, [weakSelf]() { + float publishTimeOutSec = (*this)[kTimeoutMS].as() / 1000.0; + _pPublishTimer.reset( new Timer(publishTimeOutSec, [weakSelf]() { auto strongSelf=weakSelf.lock(); if(!strongSelf) { return false; } - strongSelf->onPublishResult(SockException(Err_timeout,"publish rtmp timeout")); + strongSelf->onPublishResult(SockException(Err_timeout,"publish rtmp timeout"), false); return false; },getPoller())); @@ -120,11 +120,12 @@ void RtmpPusher::publish(const string &strUrl) { } void RtmpPusher::onErr(const SockException &ex){ - onPublishResult(ex); + //定时器_pPublishTimer为空后表明握手结束了 + onPublishResult(ex,!_pPublishTimer); } void RtmpPusher::onConnect(const SockException &err){ if(err) { - onPublishResult(err); + onPublishResult(err,false); return; } //推流器不需要多大的接收缓存,节省内存占用 @@ -146,7 +147,8 @@ void RtmpPusher::onRecv(const Buffer::Ptr &pBuf){ onParseRtmp(pBuf->data(), pBuf->size()); } catch (exception &e) { SockException ex(Err_other, e.what()); - onPublishResult(ex); + //定时器_pPublishTimer为空后表明握手结束了 + onPublishResult(ex,!_pPublishTimer); } } @@ -223,10 +225,10 @@ inline void RtmpPusher::send_metaData(){ _pRtmpReader->setDetachCB([weakSelf](){ auto strongSelf = weakSelf.lock(); if(strongSelf){ - strongSelf->onPublishResult(SockException(Err_other,"媒体源被释放")); + strongSelf->onPublishResult(SockException(Err_other,"媒体源被释放"), !strongSelf->_pPublishTimer); } }); - onPublishResult(SockException(Err_success,"success")); + onPublishResult(SockException(Err_success,"success"), false); //提升发送性能 setSocketFlags(); } diff --git a/src/Rtmp/RtmpPusher.h b/src/Rtmp/RtmpPusher.h index bc696c2e..9be06f37 100644 --- a/src/Rtmp/RtmpPusher.h +++ b/src/Rtmp/RtmpPusher.h @@ -63,7 +63,7 @@ protected: send(buffer); } private: - void onPublishResult(const SockException &ex); + void onPublishResult(const SockException &ex,bool handshakeCompleted); template inline void addOnResultCB(const FUN &fun) { diff --git a/src/Rtmp/RtmpSession.cpp b/src/Rtmp/RtmpSession.cpp index 62787b1d..15e4342f 100644 --- a/src/Rtmp/RtmpSession.cpp +++ b/src/Rtmp/RtmpSession.cpp @@ -45,22 +45,19 @@ RtmpSession::~RtmpSession() { void RtmpSession::onError(const SockException& err) { bool isPlayer = !_pPublisherSrc; - WarnP(this) << (isPlayer ? "播放器(" : "推流器(") + uint64_t duration = _ticker.createdTime()/1000; + WarnP(this) << (isPlayer ? "RTMP播放器(" : "RTMP推流器(") << _mediaInfo._vhost << "/" << _mediaInfo._app << "/" << _mediaInfo._streamid - << ")断开:" << err.what(); + << ")断开:" << err.what() + << ",耗时(s):" << duration; //流量统计事件广播 GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold); if(_ui64TotalBytes > iFlowThreshold * 1024){ - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, - _mediaInfo, - _ui64TotalBytes, - _ticker.createdTime()/1000, - isPlayer, - *this); + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _ui64TotalBytes, duration, isPlayer, getIdentifier(), get_peer_ip(), get_peer_port()); } } @@ -169,7 +166,7 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) { shutdown(SockException(Err_shutdown,errMsg)); return; } - _pPublisherSrc.reset(new RtmpToRtspMediaSource(_mediaInfo._vhost,_mediaInfo._app,_mediaInfo._streamid)); + _pPublisherSrc.reset(new RtmpMediaSourceImp(_mediaInfo._vhost,_mediaInfo._app,_mediaInfo._streamid)); _pPublisherSrc->setListener(dynamic_pointer_cast(shared_from_this())); //设置转协议 _pPublisherSrc->setProtocolTranslation(enableRtxp,enableHls,enableMP4); @@ -198,7 +195,10 @@ void RtmpSession::onCmd_publish(AMFDecoder &dec) { *this); if(!flag){ //该事件无人监听,默认鉴权成功 - onRes("",true,true,false); + GET_CONFIG(bool,toRtxp,General::kPublishToRtxp); + GET_CONFIG(bool,toHls,General::kPublishToHls); + GET_CONFIG(bool,toMP4,General::kPublishToMP4); + onRes("",toRtxp,toHls,toMP4); } } @@ -208,7 +208,7 @@ void RtmpSession::onCmd_deleteStream(AMFDecoder &dec) { status.set("code", "NetStream.Unpublish.Success"); status.set("description", "Stop publishing."); sendReply("onStatus", nullptr, status); - throw std::runtime_error(StrPrinter << "Stop publishing." << endl); + throw std::runtime_error(StrPrinter << "Stop publishing" << endl); } @@ -299,7 +299,7 @@ void RtmpSession::sendPlayResponse(const string &err,const RtmpMediaSource::Ptr strongSelf->shutdown(SockException(Err_shutdown,"rtmp ring buffer detached")); }); _pPlayerSrc = src; - if (src->readerCount() == 1) { + if (src->totalReaderCount() == 1) { src->seekTo(0); } //提高服务器发送性能 @@ -316,7 +316,7 @@ void RtmpSession::doPlayResponse(const string &err,const std::function weakSelf = dynamic_pointer_cast(shared_from_this()); - MediaSource::findAsync(_mediaInfo,weakSelf.lock(), true,[weakSelf,cb](const MediaSource::Ptr &src){ + MediaSource::findAsync(_mediaInfo,weakSelf.lock(),[weakSelf,cb](const MediaSource::Ptr &src){ auto rtmp_src = dynamic_pointer_cast(src); auto strongSelf = weakSelf.lock(); if(strongSelf){ @@ -434,9 +434,9 @@ void RtmpSession::setMetaData(AMFDecoder &dec) { throw std::runtime_error("can only set metadata"); } auto metadata = dec.load(); - //dumpMetadata(metadata); - _metadata_got = true; - _pPublisherSrc->onGetMetaData(metadata); +// dumpMetadata(metadata); + _pPublisherSrc->setMetaData(metadata); + _set_meta_data = true; } void RtmpSession::onProcessCmd(AMFDecoder &dec) { @@ -455,7 +455,7 @@ void RtmpSession::onProcessCmd(AMFDecoder &dec) { std::string method = dec.load(); auto it = s_cmd_functions.find(method); if (it == s_cmd_functions.end()) { - TraceP(this) << "can not support cmd:" << method; +// TraceP(this) << "can not support cmd:" << method; return; } _dNowReqID = dec.load(); @@ -476,10 +476,11 @@ void RtmpSession::onRtmpChunk(RtmpPacket &chunkData) { case MSG_DATA3: { AMFDecoder dec(chunkData.strBuf, chunkData.typeId == MSG_CMD3 ? 1 : 0); std::string type = dec.load(); - TraceP(this) << "notify:" << type; if (type == "@setDataFrame") { setMetaData(dec); - } + }else{ + TraceP(this) << "unknown notify:" << type; + } } break; case MSG_AUDIO: @@ -490,13 +491,13 @@ void RtmpSession::onRtmpChunk(RtmpPacket &chunkData) { GET_CONFIG(bool,rtmp_modify_stamp,Rtmp::kModifyStamp); if(rtmp_modify_stamp){ int64_t dts_out; - _stamp[chunkData.typeId % 2].revise(0, 0, dts_out, dts_out, true); + _stamp[chunkData.typeId % 2].revise(chunkData.timeStamp, chunkData.timeStamp, dts_out, dts_out, true); chunkData.timeStamp = dts_out; } - if(!_metadata_got && !chunkData.isCfgFrame()){ - //有些rtmp推流客户端不产生metadata,我们产生一个默认的metadata,目的是为了触发注册操作 - _metadata_got = true; - _pPublisherSrc->onGetMetaData(TitleMeta().getMetadata()); + + if(!_set_meta_data && !chunkData.isCfgFrame()){ + _set_meta_data = true; + _pPublisherSrc->setMetaData(TitleMeta().getMetadata()); } _pPublisherSrc->onWrite(std::make_shared(std::move(chunkData))); } @@ -535,7 +536,7 @@ void RtmpSession::onSendMedia(const RtmpPacket::Ptr &pkt) { bool RtmpSession::close(MediaSource &sender,bool force) { //此回调在其他线程触发 - if(!_pPublisherSrc || (!force && _pPublisherSrc->readerCount() != 0)){ + if(!_pPublisherSrc || (!force && _pPublisherSrc->totalReaderCount())){ return false; } string err = StrPrinter << "close media:" << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force; @@ -545,12 +546,16 @@ bool RtmpSession::close(MediaSource &sender,bool force) { void RtmpSession::onNoneReader(MediaSource &sender) { //此回调在其他线程触发 - if(!_pPublisherSrc || _pPublisherSrc->readerCount() != 0){ + if(!_pPublisherSrc || _pPublisherSrc->totalReaderCount()){ return; } MediaSourceEvent::onNoneReader(sender); } +int RtmpSession::totalReaderCount(MediaSource &sender) { + return _pPublisherSrc ? _pPublisherSrc->totalReaderCount() : sender.readerCount(); +} + void RtmpSession::setSocketFlags(){ GET_CONFIG(bool,ultraLowDelay,General::kUltraLowDelay); if(!ultraLowDelay) { diff --git a/src/Rtmp/RtmpSession.h b/src/Rtmp/RtmpSession.h index a891b49b..f5b6df79 100644 --- a/src/Rtmp/RtmpSession.h +++ b/src/Rtmp/RtmpSession.h @@ -33,11 +33,11 @@ #include "utils.h" #include "Common/config.h" #include "RtmpProtocol.h" -#include "RtmpToRtspMediaSource.h" +#include "RtmpMediaSourceImp.h" #include "Util/util.h" #include "Util/TimeTicker.h" #include "Network/TcpSession.h" -#include "MediaFile/Stamp.h" +#include "Common/Stamp.h" using namespace toolkit; @@ -83,19 +83,22 @@ private: sendResponse(MSG_CMD, invoke.data()); } - bool close(MediaSource &sender,bool force) override ; + //MediaSourceEvent override + bool close(MediaSource &sender,bool force) override ; void onNoneReader(MediaSource &sender) override; + int totalReaderCount(MediaSource &sender) override; + void setSocketFlags(); string getStreamId(const string &str); void dumpMetadata(const AMFValue &metadata); private: - bool _metadata_got = false; std::string _strTcUrl; MediaInfo _mediaInfo; double _dNowReqID = 0; + bool _set_meta_data = false; Ticker _ticker;//数据接收时间 RingBuffer::RingReader::Ptr _pRingReader; - std::shared_ptr _pPublisherSrc; + std::shared_ptr _pPublisherSrc; std::weak_ptr _pPlayerSrc; //时间戳修整器 Stamp _stamp[2]; diff --git a/src/MediaFile/HlsRecorder.h b/src/Rtp/Decoder.cpp similarity index 69% rename from src/MediaFile/HlsRecorder.h rename to src/Rtp/Decoder.cpp index ca20c5f0..ca6e8a6b 100644 --- a/src/MediaFile/HlsRecorder.h +++ b/src/Rtp/Decoder.cpp @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> + * Copyright (c) 2020 xiongziliang <771730766@qq.com> * * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). * @@ -24,25 +24,18 @@ * SOFTWARE. */ -#ifndef HLSRECORDER_H -#define HLSRECORDER_H - -#include "HlsMakerImp.h" -#include "TsMuxer.h" - +#if defined(ENABLE_RTPPROXY) +#include "Decoder.h" +#include "PSDecoder.h" +#include "TSDecoder.h" namespace mediakit { - -class HlsRecorder : public HlsMakerImp, public TsMuxer { -public: - template - HlsRecorder(ArgsType &&...args):HlsMakerImp(std::forward(args)...){} - ~HlsRecorder(){}; -protected: - void onTs(const void *packet, int bytes,uint32_t timestamp,int flags) override { - inputData((char *)packet,bytes,timestamp); - }; -}; +Decoder::Ptr Decoder::createDecoder(Decoder::Type type) { + switch (type){ + case decoder_ps : return std::make_shared(); + case decoder_ts : return std::make_shared(); + default : return nullptr; + } +} }//namespace mediakit - -#endif //HLSRECORDER_H +#endif//defined(ENABLE_RTPPROXY) diff --git a/src/Rtp/Decoder.h b/src/Rtp/Decoder.h new file mode 100644 index 00000000..406508d2 --- /dev/null +++ b/src/Rtp/Decoder.h @@ -0,0 +1,57 @@ +/* + * MIT License + * + * Copyright (c) 2020 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZLMEDIAKIT_DECODER_H +#define ZLMEDIAKIT_DECODER_H + +#if defined(ENABLE_RTPPROXY) +#include +#include +#include +#include "Decoder.h" +using namespace std; +namespace mediakit { + +class Decoder { +public: + typedef std::shared_ptr Ptr; + typedef enum { + decoder_ts = 0, + decoder_ps + }Type; + + typedef std::function onDecode; + virtual int input(const uint8_t *data, int bytes) = 0; + virtual void setOnDecode(const onDecode &decode) = 0; + static Ptr createDecoder(Type type); +protected: + Decoder() = default; + virtual ~Decoder() = default; +}; + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_DECODER_H diff --git a/src/Rtp/PSDecoder.cpp b/src/Rtp/PSDecoder.cpp new file mode 100644 index 00000000..77733d2b --- /dev/null +++ b/src/Rtp/PSDecoder.cpp @@ -0,0 +1,61 @@ +/* + * MIT License + * + * Copyright (c) 2019 Gemfield + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#if defined(ENABLE_RTPPROXY) +#include "PSDecoder.h" +#include "mpeg-ps.h" +namespace mediakit{ + +PSDecoder::PSDecoder() { + _ps_demuxer = ps_demuxer_create([](void* param, + int stream, + int codecid, + int flags, + int64_t pts, + int64_t dts, + const void* data, + size_t bytes){ + PSDecoder *thiz = (PSDecoder *)param; + if(thiz->_on_decode){ + thiz->_on_decode(stream, codecid, flags, pts, dts, data, bytes); + } + },this); +} + +PSDecoder::~PSDecoder() { + ps_demuxer_destroy((struct ps_demuxer_t*)_ps_demuxer); +} + +int PSDecoder::input(const uint8_t *data, int bytes) { + return ps_demuxer_input((struct ps_demuxer_t*)_ps_demuxer,data,bytes); +} + +void PSDecoder::setOnDecode(const Decoder::onDecode &decode) { + _on_decode = decode; +} + +}//namespace mediakit +#endif//#if defined(ENABLE_RTPPROXY) \ No newline at end of file diff --git a/src/Rtp/PSDecoder.h b/src/Rtp/PSDecoder.h new file mode 100644 index 00000000..967a4ed4 --- /dev/null +++ b/src/Rtp/PSDecoder.h @@ -0,0 +1,49 @@ +/* + * MIT License + * + * Copyright (c) 2019 Gemfield + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZLMEDIAKIT_PSDECODER_H +#define ZLMEDIAKIT_PSDECODER_H + +#if defined(ENABLE_RTPPROXY) +#include +#include "Decoder.h" +namespace mediakit{ + +//ps解析器 +class PSDecoder : public Decoder { +public: + PSDecoder(); + ~PSDecoder(); + int input(const uint8_t* data, int bytes) override; + void setOnDecode(const onDecode &decode) override; +private: + void *_ps_demuxer = nullptr; + onDecode _on_decode; +}; + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_PSDECODER_H diff --git a/src/Rtp/RtpDecoder.cpp b/src/Rtp/RtpDecoder.cpp new file mode 100644 index 00000000..af64f0d5 --- /dev/null +++ b/src/Rtp/RtpDecoder.cpp @@ -0,0 +1,86 @@ +/* + * MIT License + * + * Copyright (c) 2019 Gemfield + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#if defined(ENABLE_RTPPROXY) +#include +#include "Util/logger.h" +#include "RtpDecoder.h" +#include "rtp-payload.h" +using namespace toolkit; + +namespace mediakit{ + +RtpDecoder::RtpDecoder() { + _buffer = std::make_shared(); +} + +RtpDecoder::~RtpDecoder() { + if(_rtp_decoder){ + rtp_payload_decode_destroy(_rtp_decoder); + _rtp_decoder = nullptr; + } +} + +void RtpDecoder::decodeRtp(const void *data, int bytes,const string &type_name) { + if(_rtp_type != type_name && _rtp_decoder){ + //rtp类型发生变化,切换之 + rtp_payload_decode_destroy(_rtp_decoder); + _rtp_decoder = nullptr; + } + + if(!_rtp_decoder){ + static rtp_payload_t s_func= { + [](void* param, int bytes){ + RtpDecoder *obj = (RtpDecoder *)param; + obj->_buffer->setCapacity(bytes); + return (void *)obj->_buffer->data(); + }, + [](void* param, void* packet){ + //do nothing + }, + [](void* param, const void *packet, int bytes, uint32_t timestamp, int flags){ + RtpDecoder *obj = (RtpDecoder *)param; + obj->onRtpDecode((uint8_t *)packet, bytes, timestamp, flags); + } + }; + + uint8_t rtp_type = 0x7F & ((uint8_t *) data)[1]; + InfoL << "rtp type:" << (int) rtp_type; + _rtp_decoder = rtp_payload_decode_create(rtp_type, type_name.data(), &s_func, this); + if (!_rtp_decoder) { + WarnL << "unsupported rtp type:" << (int) rtp_type << ",size:" << bytes << ",hexdump" << hexdump(data, bytes > 16 ? 16 : bytes); + }else{ + _rtp_type = type_name; + } + } + + if(_rtp_decoder){ + rtp_payload_decode_input(_rtp_decoder,data,bytes); + } +} + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) \ No newline at end of file diff --git a/src/Rtp/RtpDecoder.h b/src/Rtp/RtpDecoder.h new file mode 100644 index 00000000..a6d78a65 --- /dev/null +++ b/src/Rtp/RtpDecoder.h @@ -0,0 +1,51 @@ +/* + * MIT License + * + * Copyright (c) 2019 Gemfield + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZLMEDIAKIT_RTPDECODER_H +#define ZLMEDIAKIT_RTPDECODER_H + +#if defined(ENABLE_RTPPROXY) +#include "Network/Buffer.h" +using namespace toolkit; + +namespace mediakit{ + +class RtpDecoder { +public: + RtpDecoder(); + virtual ~RtpDecoder(); +protected: + void decodeRtp(const void *data, int bytes,const string &type_name); + virtual void onRtpDecode(const uint8_t *packet, int bytes, uint32_t timestamp, int flags) = 0; +private: + void *_rtp_decoder = nullptr; + BufferRaw::Ptr _buffer; + string _rtp_type; +}; + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_RTPDECODER_H diff --git a/src/Rtp/RtpProcess.cpp b/src/Rtp/RtpProcess.cpp new file mode 100644 index 00000000..32986f98 --- /dev/null +++ b/src/Rtp/RtpProcess.cpp @@ -0,0 +1,335 @@ +/* + * MIT License + * + * Copyright (c) 2019 Gemfield + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#if defined(ENABLE_RTPPROXY) +#include "mpeg-ps.h" +#include "RtpProcess.h" +#include "Util/File.h" +#include "Extension/H265.h" +#include "Extension/AAC.h" + +namespace mediakit{ + +static const vector kRtpTypes = {"MP2P","MP4V-ES"}; + +/** +* 合并一些时间戳相同的frame +*/ +class FrameMerger { +public: + FrameMerger() = default; + virtual ~FrameMerger() = default; + + void inputFrame(const Frame::Ptr &frame,const function &cb){ + if (!_frameCached.empty() && _frameCached.back()->dts() != frame->dts()) { + Frame::Ptr back = _frameCached.back(); + Buffer::Ptr merged_frame = back; + if(_frameCached.size() != 1){ + string merged; + _frameCached.for_each([&](const Frame::Ptr &frame){ + merged.append(frame->data(),frame->size()); + }); + merged_frame = std::make_shared(std::move(merged)); + } + cb(back->dts(),back->pts(),merged_frame); + _frameCached.clear(); + } + _frameCached.emplace_back(Frame::getCacheAbleFrame(frame)); + } +private: + List _frameCached; +}; + +string printSSRC(uint32_t ui32Ssrc) { + char tmp[9] = { 0 }; + ui32Ssrc = htonl(ui32Ssrc); + uint8_t *pSsrc = (uint8_t *) &ui32Ssrc; + for (int i = 0; i < 4; i++) { + sprintf(tmp + 2 * i, "%02X", pSsrc[i]); + } + return tmp; +} + +static string printAddress(const struct sockaddr *addr){ + return StrPrinter << inet_ntoa(((struct sockaddr_in *) addr)->sin_addr) << ":" << ntohs(((struct sockaddr_in *) addr)->sin_port); +} + +RtpProcess::RtpProcess(uint32_t ssrc) { + _ssrc = ssrc; + _track = std::make_shared(); + _track = std::make_shared(); + _track->_interleaved = 0; + _track->_samplerate = 90000; + _track->_type = TrackVideo; + _track->_ssrc = _ssrc; + getNextRtpType(); + DebugL << printSSRC(_ssrc); + + GET_CONFIG(bool,toRtxp,General::kPublishToRtxp); + GET_CONFIG(bool,toHls,General::kPublishToHls); + GET_CONFIG(bool,toMP4,General::kPublishToMP4); + + _muxer = std::make_shared(DEFAULT_VHOST,"rtp",printSSRC(_ssrc),0,toRtxp,toRtxp,toHls,toMP4); + + GET_CONFIG(string,dump_dir,RtpProxy::kDumpDir); + { + FILE *fp = !dump_dir.empty() ? File::createfile_file(File::absolutePath(printSSRC(_ssrc) + ".rtp",dump_dir).data(),"wb") : nullptr; + if(fp){ + _save_file_rtp.reset(fp,[](FILE *fp){ + fclose(fp); + }); + } + } + + { + FILE *fp = !dump_dir.empty() ? File::createfile_file(File::absolutePath(printSSRC(_ssrc) + ".mp2",dump_dir).data(),"wb") : nullptr; + if(fp){ + _save_file_ps.reset(fp,[](FILE *fp){ + fclose(fp); + }); + } + } + + { + FILE *fp = !dump_dir.empty() ? File::createfile_file(File::absolutePath(printSSRC(_ssrc) + ".video",dump_dir).data(),"wb") : nullptr; + if(fp){ + _save_file_video.reset(fp,[](FILE *fp){ + fclose(fp); + }); + } + } + _merger = std::make_shared(); +} + +RtpProcess::~RtpProcess() { + if(_addr){ + DebugL << printSSRC(_ssrc) << " " << printAddress(_addr); + delete _addr; + }else{ + DebugL << printSSRC(_ssrc); + } +} + +bool RtpProcess::inputRtp(const char *data, int data_len,const struct sockaddr *addr,uint32_t *dts_out) { + GET_CONFIG(bool,check_source,RtpProxy::kCheckSource); + //检查源是否合法 + if(!_addr){ + _addr = new struct sockaddr; + memcpy(_addr,addr, sizeof(struct sockaddr)); + DebugL << "RtpProcess(" << printSSRC(_ssrc) << ") bind to address:" << printAddress(_addr); + } + + if(check_source && memcmp(_addr,addr,sizeof(struct sockaddr)) != 0){ + DebugL << "RtpProcess(" << printSSRC(_ssrc) << ") address dismatch:" << printAddress(addr) << " != " << printAddress(_addr); + return false; + } + + _last_rtp_time.resetTime(); + bool ret = handleOneRtp(0,_track,(unsigned char *)data,data_len); + if(dts_out){ + *dts_out = _dts; + } + return ret; +} + +void RtpProcess::getNextRtpType(){ + _rtp_type = kRtpTypes[_rtp_type_idx++]; + _rtp_dec_failed_cnt = 0; + if(_rtp_type_idx == kRtpTypes.size()){ + _rtp_type_idx = 0; + } +} + +void RtpProcess::onRtpSorted(const RtpPacket::Ptr &rtp, int) { + if(rtp->sequence != _sequence + 1){ + WarnL << rtp->sequence << " != " << _sequence << "+1"; + } + _sequence = rtp->sequence; + + if(_save_file_rtp){ + uint16_t size = rtp->size() - 4; + size = htons(size); + fwrite((uint8_t *) &size, 2, 1, _save_file_rtp.get()); + fwrite((uint8_t *) rtp->data() + 4, rtp->size() - 4, 1, _save_file_rtp.get()); + } + + decodeRtp(rtp->data() + 4 ,rtp->size() - 4,_rtp_type); +} + +void RtpProcess::onRtpDecode(const uint8_t *packet, int bytes, uint32_t, int flags) { + if(_save_file_ps){ + fwrite((uint8_t *)packet,bytes, 1, _save_file_ps.get()); + } + + if(!_decoder){ + //创建解码器 + if(bytes % 188 == 0 || packet[0] == 0x47){ + //猜测是ts负载 + _decoder = Decoder::createDecoder(Decoder::decoder_ts); + }else{ + //猜测是ps负载 + _decoder = Decoder::createDecoder(Decoder::decoder_ps); + } + _decoder->setOnDecode([this](int stream,int codecid,int flags,int64_t pts,int64_t dts,const void *data,int bytes){ + onDecode(stream,codecid,flags,pts,dts,data,bytes); + }); + } + + auto ret = _decoder->input((uint8_t *)packet,bytes); + if(ret != bytes){ + WarnL << ret << " != " << bytes << " " << flags; + if(++_rtp_dec_failed_cnt == 10){ + getNextRtpType(); + InfoL << "rtp of ssrc " << printSSRC(_ssrc) << " change to type: " << _rtp_type ; + } + } else{ + _rtp_dec_failed_cnt = 0; + } +} + +#define SWITCH_CASE(codec_id) case codec_id : return #codec_id +static const char *getCodecName(int codec_id) { + switch (codec_id) { + SWITCH_CASE(STREAM_VIDEO_MPEG4); + SWITCH_CASE(STREAM_VIDEO_H264); + SWITCH_CASE(STREAM_VIDEO_H265); + SWITCH_CASE(STREAM_VIDEO_SVAC); + SWITCH_CASE(STREAM_AUDIO_MP3); + SWITCH_CASE(STREAM_AUDIO_AAC); + SWITCH_CASE(STREAM_AUDIO_G711); + SWITCH_CASE(STREAM_AUDIO_G722); + SWITCH_CASE(STREAM_AUDIO_G723); + SWITCH_CASE(STREAM_AUDIO_G729); + SWITCH_CASE(STREAM_AUDIO_SVAC); + default: + return "unknown codec"; + } +} + +void RtpProcess::onDecode(int stream,int codecid,int flags,int64_t pts,int64_t dts,const void *data,int bytes) { + pts /= 90; + dts /= 90; + _stamps[codecid].revise(dts,pts,dts,pts,false); + _dts = dts; + + switch (codecid) { + case STREAM_VIDEO_H264: { + if (!_codecid_video) { + //获取到视频 + _codecid_video = codecid; + InfoL << "got video track: H264"; + auto track = std::make_shared(); + _muxer->addTrack(track); + } + + if (codecid != _codecid_video) { + WarnL << "video track change to H264 from codecid:" << getCodecName(_codecid_video); + return; + } + + if(_save_file_video){ + fwrite((uint8_t *)data,bytes, 1, _save_file_video.get()); + } + auto frame = std::make_shared((char *) data, bytes, dts, pts,0); + _merger->inputFrame(frame,[this](uint32_t dts, uint32_t pts, const Buffer::Ptr &buffer) { + _muxer->inputFrame(std::make_shared(buffer->data(), buffer->size(), dts, pts,4)); + }); + break; + } + + case STREAM_VIDEO_H265: { + if (!_codecid_video) { + //获取到视频 + _codecid_video = codecid; + InfoL << "got video track: H265"; + auto track = std::make_shared(); + _muxer->addTrack(track); + } + if (codecid != _codecid_video) { + WarnL << "video track change to H265 from codecid:" << getCodecName(_codecid_video); + return; + } + if(_save_file_video){ + fwrite((uint8_t *)data,bytes, 1, _save_file_video.get()); + } + auto frame = std::make_shared((char *) data, bytes, dts, pts, 0); + _merger->inputFrame(frame,[this](uint32_t dts, uint32_t pts, const Buffer::Ptr &buffer) { + _muxer->inputFrame(std::make_shared(buffer->data(), buffer->size(), dts, pts, 4)); + }); + break; + } + + case STREAM_AUDIO_AAC: { + if (!_codecid_audio) { + //获取到音频 + _codecid_audio = codecid; + InfoL << "got audio track: AAC"; + auto track = std::make_shared(); + _muxer->addTrack(track); + } + + if (codecid != _codecid_audio) { + WarnL << "audio track change to AAC from codecid:" << getCodecName(_codecid_audio); + return; + } + _muxer->inputFrame(std::make_shared((char *) data, bytes, dts, 7)); + break; + } + default: + if(codecid != 0){ + WarnL << "unsupported codec type:" << getCodecName(codecid); + } + return; + } +} + +bool RtpProcess::alive() { + GET_CONFIG(int,timeoutSec,RtpProxy::kTimeoutSec) + if(_last_rtp_time.elapsedTime() / 1000 < timeoutSec){ + return true; + } + return false; +} + +string RtpProcess::get_peer_ip() { + return inet_ntoa(((struct sockaddr_in *) _addr)->sin_addr); +} + +uint16_t RtpProcess::get_peer_port() { + return ntohs(((struct sockaddr_in *) _addr)->sin_port); +} + +int RtpProcess::totalReaderCount(){ + return _muxer->totalReaderCount(); +} + +void RtpProcess::setListener(const std::weak_ptr &listener){ + _muxer->setListener(listener); +} + + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) \ No newline at end of file diff --git a/src/Rtp/RtpProcess.h b/src/Rtp/RtpProcess.h new file mode 100644 index 00000000..e8295969 --- /dev/null +++ b/src/Rtp/RtpProcess.h @@ -0,0 +1,84 @@ +/* + * MIT License + * + * Copyright (c) 2019 Gemfield + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZLMEDIAKIT_RTPPROCESS_H +#define ZLMEDIAKIT_RTPPROCESS_H + +#if defined(ENABLE_RTPPROXY) + +#include "Rtsp/RtpReceiver.h" +#include "RtpDecoder.h" +#include "Decoder.h" +#include "Common/Device.h" +#include "Common/Stamp.h" +using namespace mediakit; + +namespace mediakit{ + +string printSSRC(uint32_t ui32Ssrc); +class FrameMerger; +class RtpProcess : public RtpReceiver , public RtpDecoder{ +public: + typedef std::shared_ptr Ptr; + RtpProcess(uint32_t ssrc); + ~RtpProcess(); + bool inputRtp(const char *data,int data_len, const struct sockaddr *addr , uint32_t *dts_out = nullptr); + bool alive(); + string get_peer_ip(); + uint16_t get_peer_port(); + + int totalReaderCount(); + void setListener(const std::weak_ptr &listener); +protected: + void onRtpSorted(const RtpPacket::Ptr &rtp, int track_index) override ; + void onRtpDecode(const uint8_t *packet, int bytes, uint32_t timestamp, int flags) override; + void onDecode(int stream,int codecid,int flags,int64_t pts,int64_t dts, const void *data,int bytes); +private: + void getNextRtpType(); +private: + std::shared_ptr _save_file_rtp; + std::shared_ptr _save_file_ps; + std::shared_ptr _save_file_video; + uint32_t _ssrc; + SdpTrack::Ptr _track; + struct sockaddr *_addr = nullptr; + uint16_t _sequence = 0; + int _codecid_video = 0; + int _codecid_audio = 0; + MultiMediaSourceMuxer::Ptr _muxer; + std::shared_ptr _merger; + Ticker _last_rtp_time; + map _stamps; + uint32_t _dts = 0; + int _rtp_type_idx = 0; + string _rtp_type; + int _rtp_dec_failed_cnt = 0; + Decoder::Ptr _decoder; +}; + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_RTPPROCESS_H diff --git a/src/Rtp/RtpSelector.cpp b/src/Rtp/RtpSelector.cpp new file mode 100644 index 00000000..95c6e473 --- /dev/null +++ b/src/Rtp/RtpSelector.cpp @@ -0,0 +1,161 @@ +/* + * MIT License + * + * Copyright (c) 2019 Gemfield + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#if defined(ENABLE_RTPPROXY) +#include "RtpSelector.h" + +namespace mediakit{ + +INSTANCE_IMP(RtpSelector); + +bool RtpSelector::inputRtp(const char *data, int data_len,const struct sockaddr *addr,uint32_t *dts_out) { + uint32_t ssrc = 0; + if(!getSSRC(data,data_len,ssrc)){ + WarnL << "get ssrc from rtp failed:" << data_len; + return false; + } + auto process = getProcess(ssrc, true); + if(process){ + return process->inputRtp(data,data_len, addr,dts_out); + } + return false; +} + +bool RtpSelector::getSSRC(const char *data,int data_len, uint32_t &ssrc){ + if(data_len < 12){ + return false; + } + uint32_t *ssrc_ptr = (uint32_t *)(data + 8); + ssrc = ntohl(*ssrc_ptr); + return true; +} + +RtpProcess::Ptr RtpSelector::getProcess(uint32_t ssrc,bool makeNew) { + lock_guard lck(_mtx_map); + auto it = _map_rtp_process.find(ssrc); + if(it == _map_rtp_process.end() && !makeNew){ + return nullptr; + } + RtpProcessHelper::Ptr &ref = _map_rtp_process[ssrc]; + if(!ref){ + ref = std::make_shared(ssrc,shared_from_this()); + ref->attachEvent(); + createTimer(); + } + return ref->getProcess(); +} + +void RtpSelector::createTimer() { + if (!_timer) { + //创建超时管理定时器 + weak_ptr weakSelf = shared_from_this(); + _timer = std::make_shared(3.0, [weakSelf] { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return false; + } + strongSelf->onManager(); + return true; + }, EventPollerPool::Instance().getPoller()); + } +} + +void RtpSelector::delProcess(uint32_t ssrc,const RtpProcess *ptr) { + lock_guard lck(_mtx_map); + auto it = _map_rtp_process.find(ssrc); + if(it == _map_rtp_process.end()){ + return; + } + + if(it->second->getProcess().get() != ptr){ + return; + } + + _map_rtp_process.erase(it); +} + +void RtpSelector::onManager() { + lock_guard lck(_mtx_map); + for (auto it = _map_rtp_process.begin(); it != _map_rtp_process.end();) { + if (it->second->getProcess()->alive()) { + ++it; + continue; + } + WarnL << "RtpProcess timeout:" << printSSRC(it->first); + it = _map_rtp_process.erase(it); + } +} + +RtpSelector::RtpSelector() { +} + +RtpSelector::~RtpSelector() { +} + +RtpProcessHelper::RtpProcessHelper(uint32_t ssrc, const weak_ptr &parent) { + _ssrc = ssrc; + _parent = parent; + _process = std::make_shared(_ssrc); +} + +RtpProcessHelper::~RtpProcessHelper() { +} + +void RtpProcessHelper::attachEvent() { + _process->setListener(shared_from_this()); +} + +bool RtpProcessHelper::close(MediaSource &sender, bool force) { + //此回调在其他线程触发 + if(!_process || (!force && _process->totalReaderCount())){ + return false; + } + auto parent = _parent.lock(); + if(!parent){ + return false; + } + parent->delProcess(_ssrc,_process.get()); + WarnL << "close media:" << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force; + return true; +} + +void RtpProcessHelper::onNoneReader(MediaSource &sender) { + if(!_process || _process->totalReaderCount()){ + return; + } + MediaSourceEvent::onNoneReader(sender); +} + +int RtpProcessHelper::totalReaderCount(MediaSource &sender) { + return _process ? _process->totalReaderCount() : sender.totalReaderCount(); +} + +RtpProcess::Ptr &RtpProcessHelper::getProcess() { + return _process; +} + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) \ No newline at end of file diff --git a/src/Rtp/RtpSelector.h b/src/Rtp/RtpSelector.h new file mode 100644 index 00000000..9edaca54 --- /dev/null +++ b/src/Rtp/RtpSelector.h @@ -0,0 +1,81 @@ +/* + * MIT License + * + * Copyright (c) 2019 Gemfield + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZLMEDIAKIT_RTPSELECTOR_H +#define ZLMEDIAKIT_RTPSELECTOR_H + +#if defined(ENABLE_RTPPROXY) +#include +#include +#include +#include "RtpProcess.h" +#include "Common/MediaSource.h" + +namespace mediakit{ + +class RtpSelector; +class RtpProcessHelper : public MediaSourceEvent , public std::enable_shared_from_this { +public: + typedef std::shared_ptr Ptr; + RtpProcessHelper(uint32_t ssrc,const weak_ptr &parent); + ~RtpProcessHelper(); + void attachEvent(); + RtpProcess::Ptr & getProcess(); +protected: + // 通知其停止推流 + bool close(MediaSource &sender,bool force) override; + // 通知无人观看 + void onNoneReader(MediaSource &sender) override; + // 观看总人数 + int totalReaderCount(MediaSource &sender) override; +private: + weak_ptr _parent; + RtpProcess::Ptr _process; + uint32_t _ssrc = 0; +}; + +class RtpSelector : public std::enable_shared_from_this{ +public: + RtpSelector(); + ~RtpSelector(); + + static RtpSelector &Instance(); + bool inputRtp(const char *data,int data_len,const struct sockaddr *addr ,uint32_t *dts_out = nullptr ); + static bool getSSRC(const char *data,int data_len, uint32_t &ssrc); + RtpProcess::Ptr getProcess(uint32_t ssrc,bool makeNew); + void delProcess(uint32_t ssrc,const RtpProcess *ptr); +private: + void onManager(); + void createTimer(); +private: + unordered_map _map_rtp_process; + recursive_mutex _mtx_map; + Timer::Ptr _timer; +}; + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_RTPSELECTOR_H diff --git a/src/Rtp/RtpSession.cpp b/src/Rtp/RtpSession.cpp new file mode 100644 index 00000000..e638b88e --- /dev/null +++ b/src/Rtp/RtpSession.cpp @@ -0,0 +1,104 @@ +/* + * MIT License + * + * Copyright (c) 2019 Gemfield + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#if defined(ENABLE_RTPPROXY) +#include "RtpSession.h" +#include "RtpSelector.h" +namespace mediakit{ + +RtpSession::RtpSession(const Socket::Ptr &sock) : TcpSession(sock) { + DebugP(this); + socklen_t addr_len = sizeof(addr); + getpeername(sock->rawFD(), &addr, &addr_len); +} +RtpSession::~RtpSession() { + DebugP(this); + if(_process){ + RtpSelector::Instance().delProcess(_ssrc,_process.get()); + } +} + +void RtpSession::onRecv(const Buffer::Ptr &data) { + try { + RtpSplitter::input(data->data(), data->size()); + } catch (SockException &ex) { + shutdown(ex); + } catch (std::exception &ex) { + shutdown(SockException(Err_other, ex.what())); + } +} + +void RtpSession::onError(const SockException &err) { + WarnL << _ssrc << " " << err.what(); +} + +void RtpSession::onManager() { + if(_process && !_process->alive()){ + shutdown(SockException(Err_timeout, "receive rtp timeout")); + } + + if(!_process && _ticker.createdTime() > 10 * 1000){ + shutdown(SockException(Err_timeout, "illegal connection")); + } +} + +void RtpSession::onRtpPacket(const char *data, uint64_t len) { + if (!_process) { + if (!RtpSelector::getSSRC(data + 2, len - 2, _ssrc)) { + return; + } + _process = RtpSelector::Instance().getProcess(_ssrc, true); + _process->setListener(dynamic_pointer_cast(shared_from_this())); + } + _process->inputRtp(data + 2, len - 2, &addr); + _ticker.resetTime(); +} + +bool RtpSession::close(MediaSource &sender, bool force) { + //此回调在其他线程触发 + if(!_process || (!force && _process->totalReaderCount())){ + return false; + } + string err = StrPrinter << "close media:" << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force; + safeShutdown(SockException(Err_shutdown,err)); + return true; +} + +void RtpSession::onNoneReader(MediaSource &sender) { + //此回调在其他线程触发 + if(!_process || _process->totalReaderCount()){ + return; + } + MediaSourceEvent::onNoneReader(sender); +} + +int RtpSession::totalReaderCount(MediaSource &sender) { + //此回调在其他线程触发 + return _process ? _process->totalReaderCount() : sender.totalReaderCount(); +} + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) \ No newline at end of file diff --git a/src/Rtp/RtpSession.h b/src/Rtp/RtpSession.h new file mode 100644 index 00000000..e8d2afed --- /dev/null +++ b/src/Rtp/RtpSession.h @@ -0,0 +1,63 @@ +/* + * MIT License + * + * Copyright (c) 2019 Gemfield + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZLMEDIAKIT_RTPSESSION_H +#define ZLMEDIAKIT_RTPSESSION_H + +#if defined(ENABLE_RTPPROXY) +#include "Network/TcpSession.h" +#include "RtpSplitter.h" +#include "RtpProcess.h" +#include "Util/TimeTicker.h" +using namespace toolkit; + +namespace mediakit{ + +class RtpSession : public TcpSession , public RtpSplitter , public MediaSourceEvent{ +public: + RtpSession(const Socket::Ptr &sock); + ~RtpSession() override; + void onRecv(const Buffer::Ptr &) override; + void onError(const SockException &err) override; + void onManager() override; +protected: + // 通知其停止推流 + bool close(MediaSource &sender,bool force) override; + // 通知无人观看 + void onNoneReader(MediaSource &sender) override; + // 观看总人数 + int totalReaderCount(MediaSource &sender) override; + void onRtpPacket(const char *data,uint64_t len) override; +private: + uint32_t _ssrc = 0; + RtpProcess::Ptr _process; + Ticker _ticker; + struct sockaddr addr; +}; + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_RTPSESSION_H diff --git a/src/Rtp/RtpSplitter.cpp b/src/Rtp/RtpSplitter.cpp new file mode 100644 index 00000000..2317f68b --- /dev/null +++ b/src/Rtp/RtpSplitter.cpp @@ -0,0 +1,58 @@ +/* + * MIT License + * + * Copyright (c) 2019 Gemfield + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#if defined(ENABLE_RTPPROXY) +#include "RtpSplitter.h" +namespace mediakit{ + +RtpSplitter::RtpSplitter() { +} + +RtpSplitter::~RtpSplitter() { +} + +const char *RtpSplitter::onSearchPacketTail(const char *data, int len) { + //这是rtp包 + if(len < 2){ + //数据不够 + return nullptr; + } + uint16_t length = (((uint8_t *)data)[0] << 8) | ((uint8_t *)data)[1]; + if(len < length + 2){ + //数据不够 + return nullptr; + } + //返回rtp包末尾 + return data + 2 + length; +} + +int64_t RtpSplitter::onRecvHeader(const char *data, uint64_t len) { + onRtpPacket(data,len); + return 0; +} + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) \ No newline at end of file diff --git a/src/Rtp/RtpSplitter.h b/src/Rtp/RtpSplitter.h new file mode 100644 index 00000000..34a026a4 --- /dev/null +++ b/src/Rtp/RtpSplitter.h @@ -0,0 +1,53 @@ +/* + * MIT License + * + * Copyright (c) 2019 Gemfield + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZLMEDIAKIT_RTPSPLITTER_H +#define ZLMEDIAKIT_RTPSPLITTER_H + +#if defined(ENABLE_RTPPROXY) +#include "Http/HttpRequestSplitter.h" + +namespace mediakit{ + +class RtpSplitter : public HttpRequestSplitter{ +public: + RtpSplitter(); + virtual ~RtpSplitter(); +protected: + /** + * 收到rtp包回调 + * @param data + * @param len + */ + virtual void onRtpPacket(const char *data,uint64_t len) = 0; +protected: + const char *onSearchPacketTail(const char *data,int len) override ; + int64_t onRecvHeader(const char *data,uint64_t len) override; +}; + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_RTPSPLITTER_H diff --git a/src/Rtp/TSDecoder.cpp b/src/Rtp/TSDecoder.cpp new file mode 100644 index 00000000..9f1bee8c --- /dev/null +++ b/src/Rtp/TSDecoder.cpp @@ -0,0 +1,96 @@ +/* + * MIT License + * + * Copyright (c) 2020 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#if defined(ENABLE_RTPPROXY) +#include "mpeg-ts.h" +#include "TSDecoder.h" +#define TS_PACKET_SIZE 188 +namespace mediakit { + +void TSSegment::setOnSegment(const TSSegment::onSegment &cb) { + _onSegment = cb; +} + +int64_t TSSegment::onRecvHeader(const char *data, uint64_t len) { + _onSegment(data, len); + return 0; +} + +const char *TSSegment::onSearchPacketTail(const char *data, int len) { + if (len < _size + 1) { + if (len == _size && ((uint8_t *) data)[0] == 0x47) { + return data + _size; + } + return nullptr; + } + //下一个包头 + if (((uint8_t *) data)[_size] == 0x47) { + return data + _size; + } + + auto pos = memchr(data + _size, 0x47, len - _size); + if (pos) { + return (char *) pos; + } + return nullptr; +} + +//////////////////////////////////////////////////////////////// + +TSDecoder::TSDecoder() : _ts_segment(TS_PACKET_SIZE) { + _ts_segment.setOnSegment([this](const char *data,uint64_t len){ + if(((uint8_t*)data)[0] != 0x47 || len != TS_PACKET_SIZE ){ + WarnL << "不是ts包:" << (int)(data[0]) << " " << len; + return; + } + ts_demuxer_input(_demuxer_ctx,(uint8_t*)data,len); + }); + _demuxer_ctx = ts_demuxer_create([](void* param, int program, int stream, int codecid, int flags, int64_t pts, int64_t dts, const void* data, size_t bytes){ + TSDecoder *thiz = (TSDecoder*)param; + if(thiz->_on_decode){ + thiz->_on_decode(stream,codecid,flags,pts,dts,data,bytes); + } + return 0; + },this); +} + +TSDecoder::~TSDecoder() { + ts_demuxer_destroy(_demuxer_ctx); +} + +int TSDecoder::input(const uint8_t *data, int bytes) { + if(bytes == TS_PACKET_SIZE && ((uint8_t*)data)[0] == 0x47){ + return ts_demuxer_input(_demuxer_ctx,(uint8_t*)data,bytes); + } + _ts_segment.input((char*)data,bytes); + return bytes; +} + +void TSDecoder::setOnDecode(const Decoder::onDecode &decode) { + _on_decode = decode; +} + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) \ No newline at end of file diff --git a/src/Rtp/TSDecoder.h b/src/Rtp/TSDecoder.h new file mode 100644 index 00000000..dc442726 --- /dev/null +++ b/src/Rtp/TSDecoder.h @@ -0,0 +1,68 @@ +/* + * MIT License + * + * Copyright (c) 2020 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZLMEDIAKIT_TSDECODER_H +#define ZLMEDIAKIT_TSDECODER_H + +#if defined(ENABLE_RTPPROXY) +#include "Util/logger.h" +#include "Http/HttpRequestSplitter.h" +#include "Decoder.h" + +using namespace toolkit; +namespace mediakit { + +//ts包拆分器 +class TSSegment : public HttpRequestSplitter { +public: + typedef std::function onSegment; + TSSegment(int size = 188) : _size(size){} + ~TSSegment(){} + void setOnSegment(const onSegment &cb); +protected: + int64_t onRecvHeader(const char *data, uint64_t len) override ; + const char *onSearchPacketTail(const char *data, int len) override ; +private: + int _size; + onSegment _onSegment; +}; + +//ts解析器 +class TSDecoder : public Decoder { +public: + TSDecoder(); + ~TSDecoder(); + int input(const uint8_t* data, int bytes) override ; + void setOnDecode(const onDecode &decode) override; +private: + TSSegment _ts_segment; + struct ts_demuxer_t* _demuxer_ctx = nullptr; + onDecode _on_decode; +}; + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_TSDECODER_H diff --git a/src/Rtp/UdpRecver.cpp b/src/Rtp/UdpRecver.cpp new file mode 100644 index 00000000..79995a85 --- /dev/null +++ b/src/Rtp/UdpRecver.cpp @@ -0,0 +1,56 @@ +/* + * MIT License + * + * Copyright (c) 2019 Gemfield + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#if defined(ENABLE_RTPPROXY) +#include "UdpRecver.h" +#include "RtpSelector.h" +namespace mediakit{ + +UdpRecver::UdpRecver() { +} + +UdpRecver::~UdpRecver() { +} + +bool UdpRecver::initSock(uint16_t local_port,const char *local_ip) { + _sock.reset(new Socket(nullptr, false)); + onceToken token(nullptr,[&](){ + SockUtil::setRecvBuf(_sock->rawFD(),4 * 1024 * 1024); + }); + + auto &ref = RtpSelector::Instance(); + _sock->setOnRead([&ref](const Buffer::Ptr &buf, struct sockaddr *addr, int ){ + ref.inputRtp(buf->data(),buf->size(),addr); + }); + return _sock->bindUdpSock(local_port,local_ip); +} + +EventPoller::Ptr UdpRecver::getPoller() { + return _sock->getPoller(); +} + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) \ No newline at end of file diff --git a/src/Rtp/UdpRecver.h b/src/Rtp/UdpRecver.h new file mode 100644 index 00000000..f2a19a36 --- /dev/null +++ b/src/Rtp/UdpRecver.h @@ -0,0 +1,56 @@ +/* + * MIT License + * + * Copyright (c) 2019 Gemfield + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ZLMEDIAKIT_UDPRECVER_H +#define ZLMEDIAKIT_UDPRECVER_H + +#if defined(ENABLE_RTPPROXY) +#include +#include "Network/Socket.h" +using namespace std; +using namespace toolkit; + +namespace mediakit{ + +/** + * 组播接收器 + */ +class UdpRecver { +public: + typedef std::shared_ptr Ptr; + typedef function onRecv; + + UdpRecver(); + virtual ~UdpRecver(); + bool initSock(uint16_t local_port,const char *local_ip = "0.0.0.0"); + EventPoller::Ptr getPoller(); +protected: + Socket::Ptr _sock; +}; + +}//namespace mediakit +#endif//defined(ENABLE_RTPPROXY) +#endif //ZLMEDIAKIT_UDPRECVER_H diff --git a/src/Rtsp/RtpCodec.h b/src/Rtsp/RtpCodec.h index 7ce212c7..338de751 100644 --- a/src/Rtsp/RtpCodec.h +++ b/src/Rtsp/RtpCodec.h @@ -34,25 +34,29 @@ using namespace toolkit; namespace mediakit{ -class RtpRingInterface { +class RtpRing{ public: + typedef std::shared_ptr Ptr; typedef RingBuffer RingType; - typedef std::shared_ptr Ptr; - RtpRingInterface(){} - virtual ~RtpRingInterface(){} + RtpRing(){} + virtual ~RtpRing(){} /** * 获取rtp环形缓存 * @return */ - virtual RingType::Ptr getRtpRing() const = 0; + virtual RingType::Ptr getRtpRing() const { + return _rtpRing; + } /** * 设置rtp环形缓存 * @param ring */ - virtual void setRtpRing(const RingType::Ptr &ring) = 0; + virtual void setRtpRing(const RingType::Ptr &ring){ + _rtpRing = ring; + } /** * 输入rtp包 @@ -60,26 +64,7 @@ public: * @param key_pos 是否为关键帧第一个rtp包 * @return 是否为关键帧第一个rtp包 */ - virtual bool inputRtp(const RtpPacket::Ptr &rtp, bool key_pos) = 0; -}; - -class RtpRing : public RtpRingInterface { -public: - typedef std::shared_ptr Ptr; - - RtpRing(){ - } - virtual ~RtpRing(){} - - RingType::Ptr getRtpRing() const override { - return _rtpRing; - } - - void setRtpRing(const RingType::Ptr &ring) override { - _rtpRing = ring; - } - - bool inputRtp(const RtpPacket::Ptr &rtp, bool key_pos) override{ + virtual bool inputRtp(const RtpPacket::Ptr &rtp, bool key_pos){ if(_rtpRing){ _rtpRing->write(rtp,key_pos); } @@ -147,7 +132,7 @@ protected: uint32_t _ui32TimeStamp = 0; }; -class RtpCodec : public RtpRing, public FrameRingInterfaceDelegate , public CodecInfo{ +class RtpCodec : public RtpRing, public FrameDispatcher , public CodecInfo{ public: typedef std::shared_ptr Ptr; RtpCodec(){} diff --git a/src/Rtsp/Rtsp.cpp b/src/Rtsp/Rtsp.cpp index 8cf3dae6..d6d95258 100644 --- a/src/Rtsp/Rtsp.cpp +++ b/src/Rtsp/Rtsp.cpp @@ -85,74 +85,84 @@ string SdpTrack::toString() const { } return _printer; } -void SdpParser::load(const string &sdp) { - _track_map.clear(); - string key; - SdpTrack::Ptr track = std::make_shared(); - auto lines = split(sdp,"\n"); - for (auto &line : lines){ - trim(line); - if(line.size() < 2 || line[1] != '='){ - continue; - } - char opt = line[0]; - string opt_val = line.substr(2); - switch (opt){ - case 'o': - track->_o = opt_val; - break; - case 's': - track->_s = opt_val; - break; - case 'i': - track->_i = opt_val; - break; - case 'c': - track->_c = opt_val; - break; - case 't': - track->_t = opt_val; - break; - case 'b': - track->_b = opt_val; - break; - case 'm':{ - _track_map[key] = track; - track = std::make_shared(); - key = FindField(opt_val.data(), nullptr," "); - track->_m = opt_val; - } - break; - case 'a':{ - string attr = FindField(opt_val.data(), nullptr,":"); - if(attr.empty()){ - track->_attr[opt_val] = ""; - }else{ - track->_attr[attr] = FindField(opt_val.data(),":", nullptr); - } - } - break; - default: - track->_other[opt] = opt_val; - break; - } +static TrackType toTrackType(const string &str) { + if (str == "") { + return TrackTitle; } - _track_map[key] = track; + if (str == "video") { + return TrackVideo; + } - for (auto &pr : _track_map) { - auto &track = *pr.second; - if (pr.first == "") { - track._type = TrackTitle; - } else if (pr.first == "video") { - track._type = TrackVideo; - } else if (pr.first == "audio") { - track._type = TrackAudio; - } else { - track._type = TrackInvalid; + if (str == "audio") { + return TrackAudio; + } + + return TrackInvalid; +} + +void SdpParser::load(const string &sdp) { + { + _track_vec.clear(); + string key; + SdpTrack::Ptr track = std::make_shared(); + + auto lines = split(sdp, "\n"); + for (auto &line : lines) { + trim(line); + if (line.size() < 2 || line[1] != '=') { + continue; + } + char opt = line[0]; + string opt_val = line.substr(2); + switch (opt) { + case 'o': + track->_o = opt_val; + break; + case 's': + track->_s = opt_val; + break; + case 'i': + track->_i = opt_val; + break; + case 'c': + track->_c = opt_val; + break; + case 't': + track->_t = opt_val; + break; + case 'b': + track->_b = opt_val; + break; + case 'm': { + track->_type = toTrackType(key); + _track_vec.emplace_back(track); + track = std::make_shared(); + key = FindField(opt_val.data(), nullptr, " "); + track->_m = opt_val; + } + break; + case 'a': { + string attr = FindField(opt_val.data(), nullptr, ":"); + if (attr.empty()) { + track->_attr[opt_val] = ""; + } else { + track->_attr[attr] = FindField(opt_val.data(), ":", nullptr); + } + } + break; + default: + track->_other[opt] = opt_val; + break; + } } + track->_type = toTrackType(key); + _track_vec.emplace_back(track); + } + for (auto &track_ptr : _track_vec) { + 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}; @@ -198,9 +208,9 @@ bool SdpParser::available() const { } SdpTrack::Ptr SdpParser::getTrack(TrackType type) const { - for (auto &pr : _track_map){ - if(pr.second->_type == type){ - return pr.second; + for (auto &track : _track_vec){ + if(track->_type == type){ + return track; } } return nullptr; @@ -208,31 +218,42 @@ SdpTrack::Ptr SdpParser::getTrack(TrackType type) const { vector SdpParser::getAvailableTrack() const { vector ret; - auto video = getTrack(TrackVideo); - if(video){ - ret.emplace_back(video); + bool audio_added = false; + bool video_added = false; + for (auto &track : _track_vec){ + if(track->_type == TrackAudio ){ + if(!audio_added){ + ret.emplace_back(track); + audio_added = true; + } + continue; + } + + if(track->_type == TrackVideo ){ + if(!video_added){ + ret.emplace_back(track); + video_added = true; + } + continue; + } } - auto audio = getTrack(TrackAudio); - if(audio){ - ret.emplace_back(audio); - } - return ret; + return std::move(ret); } string SdpParser::toString() const { string title,audio,video; - for(auto &pr : _track_map){ - switch (pr.second->_type){ + for(auto &track : _track_vec){ + switch (track->_type){ case TrackTitle:{ - title = pr.second->toString(); + title = track->toString(); } break; case TrackVideo:{ - video = pr.second->toString(); + video = track->toString(); } break; case TrackAudio:{ - audio = pr.second->toString(); + audio = track->toString(); } break; default: @@ -242,5 +263,58 @@ string SdpParser::toString() const { return title + video + audio; } +bool RtspUrl::parse(const string &strUrl) { + auto schema = FindField(strUrl.data(), nullptr, "://"); + bool isSSL = strcasecmp(schema.data(), "rtsps") == 0; + //查找"://"与"/"之间的字符串,用于提取用户名密码 + auto middle_url = FindField(strUrl.data(), "://", "/"); + if (middle_url.empty()) { + middle_url = FindField(strUrl.data(), "://", nullptr); + } + auto pos = middle_url.rfind('@'); + if (pos == string::npos) { + //并没有用户名密码 + return setup(isSSL, 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(isSSL, url, user_pwd, ""); + } + auto user = FindField(user_pwd.data(), nullptr, ":"); + auto pwd = FindField(user_pwd.data(), ":", nullptr); + return setup(isSSL, url, user, pwd); +} + +bool RtspUrl::setup(bool isSSL, const string &strUrl, const string &strUser, const string &strPwd) { + auto ip = FindField(strUrl.data(), "://", "/"); + if (ip.empty()) { + ip = split(FindField(strUrl.data(), "://", NULL), "?")[0]; + } + auto port = atoi(FindField(ip.data(), ":", NULL).data()); + if (port <= 0 || port > UINT16_MAX) { + //rtsp 默认端口554 + port = isSSL ? 322 : 554; + } else { + //服务器域名 + ip = FindField(ip.data(), NULL, ":"); + } + + if (ip.empty()) { + return false; + } + + _url = std::move(strUrl); + _user = std::move(strUser); + _passwd = std::move(strPwd); + _host = std::move(ip); + _port = port; + _is_ssl = isSSL; + return true; +} + }//namespace mediakit diff --git a/src/Rtsp/Rtsp.h b/src/Rtsp/Rtsp.h index e005c971..26e2a979 100644 --- a/src/Rtsp/Rtsp.h +++ b/src/Rtsp/Rtsp.h @@ -122,9 +122,27 @@ public: vector getAvailableTrack() const; string toString() const ; private: - map _track_map; + vector _track_vec; }; +/** + * 解析rtsp url的工具类 + */ +class RtspUrl{ +public: + string _url; + string _user; + string _passwd; + string _host; + uint16_t _port; + bool _is_ssl; +public: + RtspUrl() = default; + ~RtspUrl() = default; + bool parse(const string &url); +private: + bool setup(bool,const string &, const string &, const string &); +}; /** * rtsp sdp基类 diff --git a/src/Rtsp/RtspDemuxer.cpp b/src/Rtsp/RtspDemuxer.cpp index 06d75e83..950d2e12 100644 --- a/src/Rtsp/RtspDemuxer.cpp +++ b/src/Rtsp/RtspDemuxer.cpp @@ -34,7 +34,7 @@ using namespace std; namespace mediakit { -RtspDemuxer::RtspDemuxer(const string& sdp) { +void RtspDemuxer::loadSdp(const string &sdp){ loadSdp(SdpParser(sdp)); } @@ -89,6 +89,7 @@ void RtspDemuxer::makeAudioTrack(const SdpTrack::Ptr &audio) { if(_audioRtpDecoder){ //设置rtp解码器代理,生成的frame写入该Track _audioRtpDecoder->addDelegate(_audioTrack); + onAddTrack(_audioTrack); } else{ //找不到相应的rtp解码器,该track无效 _audioTrack.reset(); @@ -105,6 +106,7 @@ void RtspDemuxer::makeVideoTrack(const SdpTrack::Ptr &video) { if(_videoRtpDecoder){ //设置rtp解码器代理,生成的frame写入该Track _videoRtpDecoder->addDelegate(_videoTrack); + onAddTrack(_videoTrack); }else{ //找不到相应的rtp解码器,该track无效 _videoTrack.reset(); diff --git a/src/Rtsp/RtspDemuxer.h b/src/Rtsp/RtspDemuxer.h index 8a4d407e..c864c19a 100644 --- a/src/Rtsp/RtspDemuxer.h +++ b/src/Rtsp/RtspDemuxer.h @@ -40,8 +40,13 @@ namespace mediakit { class RtspDemuxer : public Demuxer{ public: typedef std::shared_ptr Ptr; - RtspDemuxer(const string &sdp); - virtual ~RtspDemuxer(){}; + RtspDemuxer() = default; + virtual ~RtspDemuxer() = default; + + /** + * 加载sdp + */ + void loadSdp(const string &sdp); /** * 开始解复用 diff --git a/src/Rtsp/RtspMediaSource.h b/src/Rtsp/RtspMediaSource.h index ef888ff7..48edce20 100644 --- a/src/Rtsp/RtspMediaSource.h +++ b/src/Rtsp/RtspMediaSource.h @@ -35,142 +35,198 @@ #include "Common/config.h" #include "Common/MediaSource.h" #include "RtpCodec.h" - #include "Util/logger.h" #include "Util/RingBuffer.h" #include "Util/TimeTicker.h" #include "Util/ResourcePool.h" #include "Util/NoticeCenter.h" #include "Thread/ThreadPool.h" - using namespace std; using namespace toolkit; +#define RTP_GOP_SIZE 2048 + namespace mediakit { -class RtspMediaSource: public MediaSource , public RingDelegate { +/** + * rtsp媒体源的数据抽象 + * rtsp有关键的两要素,分别是sdp、rtp包 + * 只要生成了这两要素,那么要实现rtsp推流、rtsp服务器就很简单了 + * rtsp推拉流协议中,先传递sdp,然后再协商传输方式(tcp/udp/组播),最后一直传递rtp + */ +class RtspMediaSource : public MediaSource, public RingDelegate { public: typedef ResourcePool PoolType; typedef std::shared_ptr Ptr; typedef RingBuffer RingType; - RtspMediaSource(const string &strVhost, - const string &strApp, - const string &strId, - int ringSize = 0) : - MediaSource(RTSP_SCHEMA,strVhost,strApp,strId), - _ringSize(ringSize){} + /** + * 构造函数 + * @param vhost 虚拟主机名 + * @param app 应用名 + * @param stream_id 流id + * @param ring_size 可以设置固定的环形缓冲大小,0则自适应 + */ + RtspMediaSource(const string &vhost, + const string &app, + const string &stream_id, + int ring_size = RTP_GOP_SIZE) : + MediaSource(RTSP_SCHEMA, vhost, app, stream_id), _ring_size(ring_size) {} virtual ~RtspMediaSource() {} + /** + * 获取媒体源的环形缓冲 + */ const RingType::Ptr &getRing() const { - //获取媒体源的rtp环形缓冲 - return _pRing; + return _ring; } - int readerCount() override { - return _pRing ? _pRing->readerCount() : 0; + /** + * 获取播放器个数 + */ + int readerCount() override { + return _ring ? _ring->readerCount() : 0; } - const string& getSdp() const { - //获取该源的媒体描述信息 - return _strSdp; + /** + * 获取该源的sdp + */ + const string &getSdp() const { + return _sdp; } + /** + * 获取相应轨道的ssrc + */ virtual uint32_t getSsrc(TrackType trackType) { - auto track = _sdpParser.getTrack(trackType); - if(!track){ + auto track = _sdp_parser.getTrack(trackType); + if (!track) { return 0; } return track->_ssrc; } + + /** + * 获取相应轨道的seqence + */ virtual uint16_t getSeqence(TrackType trackType) { - auto track = _sdpParser.getTrack(trackType); - if(!track){ + auto track = _sdp_parser.getTrack(trackType); + if (!track) { return 0; } return track->_seq; } + /** + * 获取相应轨道的时间戳,单位毫秒 + */ uint32_t getTimeStamp(TrackType trackType) override { - auto track = _sdpParser.getTrack(trackType); - if(track) { + auto track = _sdp_parser.getTrack(trackType); + if (track) { return track->_time_stamp; } - auto tracks = _sdpParser.getAvailableTrack(); - switch (tracks.size()){ - case 0: return 0; - case 1: return tracks[0]->_time_stamp; - default:return MAX(tracks[0]->_time_stamp,tracks[1]->_time_stamp); + auto tracks = _sdp_parser.getAvailableTrack(); + switch (tracks.size()) { + case 0: + return 0; + case 1: + return tracks[0]->_time_stamp; + default: + return MAX(tracks[0]->_time_stamp, tracks[1]->_time_stamp); } } - virtual void setTimeStamp(uint32_t uiStamp) { - auto tracks = _sdpParser.getAvailableTrack(); + /** + * 更新时间戳 + */ + void setTimeStamp(uint32_t uiStamp) override { + auto tracks = _sdp_parser.getAvailableTrack(); for (auto &track : tracks) { - track->_time_stamp = uiStamp; + track->_time_stamp = uiStamp; } } - virtual void onGetSDP(const string& sdp) { - //派生类设置该媒体源媒体描述信息 - _strSdp = sdp; - _sdpParser.load(sdp); - if(_pRing){ - regist(); + /** + * 设置sdp + */ + virtual void setSdp(const string &sdp) { + _sdp = sdp; + _sdp_parser.load(sdp); + _have_video = (bool)_sdp_parser.getTrack(TrackVideo); + if (_ring) { + regist(); } } - void onWrite(const RtpPacket::Ptr &rtppt, bool keyPos) override { - auto track = _sdpParser.getTrack(rtppt->type); - if(track){ - track->_seq = rtppt->sequence; - track->_time_stamp = rtppt->timeStamp; - track->_ssrc = rtppt->ssrc; + /** + * 输入rtp + * @param rtp rtp包 + * @param keyPos 该包是否为关键帧的第一个包 + */ + void onWrite(const RtpPacket::Ptr &rtp, bool keyPos) override { + auto track = _sdp_parser.getTrack(rtp->type); + if (track) { + track->_seq = rtp->sequence; + track->_time_stamp = rtp->timeStamp; + track->_ssrc = rtp->ssrc; } - if(!_pRing){ - weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); - _pRing = std::make_shared(_ringSize,[weakSelf](const EventPoller::Ptr &,int size,bool){ - auto strongSelf = weakSelf.lock(); - if(!strongSelf){ - return; - } - strongSelf->onReaderChanged(size); - }); - onReaderChanged(0); - if(!_strSdp.empty()){ - regist(); - } + if (!_ring) { + weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + auto lam = [weakSelf](const EventPoller::Ptr &, int size, bool) { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + return; + } + strongSelf->onReaderChanged(size); + }; + //rtp包缓存最大允许2048个,大概最多3MB数据 + //但是这个是GOP缓存的上限值,真实的GOP缓存大小等于两个I帧之间的包数的两倍 + //而且每次遇到I帧,则会清空GOP缓存,所以真实的GOP缓存远小于此值 + _ring = std::make_shared(_ring_size, std::move(lam)); + onReaderChanged(0); + if (!_sdp.empty()) { + regist(); + } } - _pRing->write(rtppt,keyPos); - checkNoneReader(); + //不存在视频,为了减少缓存延时,那么关闭GOP缓存 + _ring->write(rtp, _have_video ? keyPos : true); + checkNoneReader(); } private: - void onReaderChanged(int size){ - //我们记录最后一次活动时间 - _readerTicker.resetTime(); - if(size != 0 || readerCount() != 0){ - //还有消费者正在观看该流 - _asyncEmitNoneReader = false; - return; - } - _asyncEmitNoneReader = true; - } + /** + * 每次增减消费者都会触发该函数 + */ + void onReaderChanged(int size) { + //我们记录最后一次活动时间 + _reader_changed_ticker.resetTime(); + if (size != 0 || totalReaderCount() != 0) { + //还有消费者正在观看该流 + _async_emit_none_reader = false; + return; + } + _async_emit_none_reader = true; + } - void checkNoneReader(){ - GET_CONFIG(int,stream_none_reader_delay,General::kStreamNoneReaderDelayMS); - if(_asyncEmitNoneReader && _readerTicker.elapsedTime() > stream_none_reader_delay){ - _asyncEmitNoneReader = false; - onNoneReader(); - } + /** + * 检查是否无人消费该流, + * 如果无人消费且超过一定时间会触发onNoneReader事件 + */ + void checkNoneReader() { + GET_CONFIG(int, stream_none_reader_delay, General::kStreamNoneReaderDelayMS); + if (_async_emit_none_reader && _reader_changed_ticker.elapsedTime() > stream_none_reader_delay) { + _async_emit_none_reader = false; + onNoneReader(); + } } protected: - SdpParser _sdpParser; - string _strSdp; //媒体描述信息 - RingType::Ptr _pRing; //rtp环形缓冲 - int _ringSize; - Ticker _readerTicker; - bool _asyncEmitNoneReader = false; + int _ring_size; + bool _async_emit_none_reader = false; + bool _have_video = false; + Ticker _reader_changed_ticker; + SdpParser _sdp_parser; + string _sdp; + RingType::Ptr _ring; }; } /* namespace mediakit */ diff --git a/src/Rtsp/RtspMediaSourceImp.h b/src/Rtsp/RtspMediaSourceImp.h new file mode 100644 index 00000000..4586df2b --- /dev/null +++ b/src/Rtsp/RtspMediaSourceImp.h @@ -0,0 +1,128 @@ +/* +* MIT License +* +* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> +* +* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#ifndef SRC_RTSP_RTSPTORTMPMEDIASOURCE_H_ +#define SRC_RTSP_RTSPTORTMPMEDIASOURCE_H_ + +#include "Rtmp/amf.h" +#include "RtspMediaSource.h" +#include "RtspDemuxer.h" +#include "Common/MultiMediaSourceMuxer.h" +using namespace toolkit; + +namespace mediakit { +class RtspMediaSourceImp : public RtspMediaSource, public Demuxer::Listener , public MultiMediaSourceMuxer::Listener { +public: + typedef std::shared_ptr Ptr; + + /** + * 构造函数 + * @param vhost 虚拟主机 + * @param app 应用名 + * @param id 流id + * @param ringSize 环形缓存大小 + */ + RtspMediaSourceImp(const string &vhost, const string &app, const string &id, int ringSize = RTP_GOP_SIZE) : RtspMediaSource(vhost, app, id,ringSize) { + _demuxer = std::make_shared(); + _demuxer->setTrackListener(this); + } + + ~RtspMediaSourceImp() = default; + + /** + * 设置sdp + */ + void setSdp(const string &strSdp) override { + _demuxer->loadSdp(strSdp); + RtspMediaSource::setSdp(strSdp); + } + + /** + * 输入rtp并解析 + */ + void onWrite(const RtpPacket::Ptr &rtp, bool key_pos) override { + key_pos = _demuxer->inputRtp(rtp); + RtspMediaSource::onWrite(rtp, key_pos); + } + + /** + * 设置监听器 + * @param listener + */ + void setListener(const std::weak_ptr &listener) override { + RtspMediaSource::setListener(listener); + if(_muxer){ + _muxer->setListener(listener); + } + } + + /** + * 获取观看总人数,包括(hls/rtsp/rtmp) + */ + int totalReaderCount() override{ + return readerCount() + (_muxer ? _muxer->totalReaderCount() : 0); + } + + /** + * 设置协议转换 + * @param enableRtmp 是否转换成rtmp + * @param enableHls 是否转换成hls + * @param enableMP4 是否mp4录制 + */ + void setProtocolTranslation(bool enableRtmp,bool enableHls,bool enableMP4){ + //不重复生成rtsp + _muxer = std::make_shared(getVhost(), getApp(), getId(), _demuxer->getDuration(), false, enableRtmp, enableHls, enableMP4); + _muxer->setListener(getListener()); + _muxer->setTrackListener(this); + for(auto &track : _demuxer->getTracks(false)){ + _muxer->addTrack(track); + track->addDelegate(_muxer); + } + } + + /** + * _demuxer触发的添加Track事件 + */ + void onAddTrack(const Track::Ptr &track) override { + if(_muxer){ + _muxer->addTrack(track); + track->addDelegate(_muxer); + } + } + + /** + * _muxer触发的所有Track就绪的事件 + */ + void onAllTrackReady() override{ + setTrackSource(_muxer); + } +private: + RtspDemuxer::Ptr _demuxer; + MultiMediaSourceMuxer::Ptr _muxer; +}; +} /* namespace mediakit */ + +#endif /* SRC_RTSP_RTSPTORTMPMEDIASOURCE_H_ */ diff --git a/src/Rtsp/RtspMediaSourceMuxer.h b/src/Rtsp/RtspMediaSourceMuxer.h index f4637942..834374e6 100644 --- a/src/Rtsp/RtspMediaSourceMuxer.h +++ b/src/Rtsp/RtspMediaSourceMuxer.h @@ -48,15 +48,22 @@ public: void setListener(const std::weak_ptr &listener){ _mediaSouce->setListener(listener); } + int readerCount() const{ return _mediaSouce->readerCount(); } + void setTimeStamp(uint32_t stamp){ _mediaSouce->setTimeStamp(stamp); } -private: - void onAllTrackReady() override { - _mediaSouce->onGetSDP(getSdp()); + + void onAllTrackReady(){ + _mediaSouce->setSdp(getSdp()); + } + + // 设置TrackSource + void setTrackSource(const std::weak_ptr &track_src){ + _mediaSouce->setTrackSource(track_src); } private: RtspMediaSource::Ptr _mediaSouce; diff --git a/src/Rtsp/RtspMuxer.cpp b/src/Rtsp/RtspMuxer.cpp index 9f4da62a..04ac57dd 100644 --- a/src/Rtsp/RtspMuxer.cpp +++ b/src/Rtsp/RtspMuxer.cpp @@ -35,38 +35,50 @@ RtspMuxer::RtspMuxer(const TitleSdp::Ptr &title){ } else{ _sdp = title->getSdp(); } - _rtpRing = std::make_shared(); + _rtpRing = std::make_shared(); } -void RtspMuxer::onTrackReady(const Track::Ptr &track) { +void RtspMuxer::addTrack(const Track::Ptr &track) { //根据track生成sdp Sdp::Ptr sdp = track->getSdp(); if (!sdp) { return; } - auto encoder = Factory::getRtpEncoderBySdp(sdp); + + auto &encoder = _encoder[track->getTrackType()]; + encoder = Factory::getRtpEncoderBySdp(sdp); if (!encoder) { return; } + + //设置rtp输出环形缓存 + encoder->setRtpRing(_rtpRing); + //添加其sdp _sdp.append(sdp->getSdp()); - //设置Track的代理,这样输入frame至Track时,最终数据将输出到RtpEncoder中 - track->addDelegate(encoder); - //rtp编码器共用同一个环形缓存 - encoder->setRtpRing(_rtpRing); +} + +void RtspMuxer::inputFrame(const Frame::Ptr &frame) { + auto &encoder = _encoder[frame->getTrackType()]; + if(encoder){ + encoder->inputFrame(frame); + } } string RtspMuxer::getSdp() { - if(!isAllTrackReady()){ - //尚未就绪 - return ""; - } return _sdp; } -RtpRingInterface::RingType::Ptr RtspMuxer::getRtpRing() const { +RtpRing::RingType::Ptr RtspMuxer::getRtpRing() const { return _rtpRing; } +void RtspMuxer::resetTracks() { + _sdp.clear(); + for(auto &encoder : _encoder){ + encoder = nullptr; + } +} + } /* namespace mediakit */ \ No newline at end of file diff --git a/src/Rtsp/RtspMuxer.h b/src/Rtsp/RtspMuxer.h index 3d99537b..18a74515 100644 --- a/src/Rtsp/RtspMuxer.h +++ b/src/Rtsp/RtspMuxer.h @@ -35,7 +35,7 @@ namespace mediakit{ /** * rtsp生成器 */ -class RtspMuxer : public MediaSink{ +class RtspMuxer : public MediaSinkInterface{ public: typedef std::shared_ptr Ptr; @@ -56,17 +56,27 @@ public: * 获取rtp环形缓存 * @return */ - RtpRingInterface::RingType::Ptr getRtpRing() const; -protected: + RtpRing::RingType::Ptr getRtpRing() const; + /** - * 某track已经准备好,其ready()状态返回true, - * 此时代表可以获取其例如sps pps等相关信息了 - * @param track - */ - void onTrackReady(const Track::Ptr & track) override ; + * 添加ready状态的track + */ + void addTrack(const Track::Ptr & track) override; + + /** + * 写入帧数据 + * @param frame 帧 + */ + void inputFrame(const Frame::Ptr &frame) override; + + /** + * 重置所有track + */ + void resetTracks() override ; private: - RtpRingInterface::RingType::Ptr _rtpRing; string _sdp; + RtpCodec::Ptr _encoder[TrackMax]; + RtpRing::RingType::Ptr _rtpRing; }; diff --git a/src/Rtsp/RtspPlayer.cpp b/src/Rtsp/RtspPlayer.cpp index bd3b83b9..682cbe19 100644 --- a/src/Rtsp/RtspPlayer.cpp +++ b/src/Rtsp/RtspPlayer.cpp @@ -42,11 +42,17 @@ using namespace mediakit::Client; namespace mediakit { +enum PlayType { + type_play = 0, + type_pause, + type_seek +}; + RtspPlayer::RtspPlayer(const EventPoller::Ptr &poller) : TcpClient(poller){ RtpReceiver::setPoolSize(64); } RtspPlayer::~RtspPlayer(void) { - DebugL< weakSelf = dynamic_pointer_cast(shared_from_this()); float playTimeOutSec = (*this)[kTimeoutMS].as() / 1000.0; @@ -143,18 +108,19 @@ void RtspPlayer::play(bool isSSL,const string &strUrl, const string &strUser, co if(!strongSelf) { return false; } - strongSelf->onPlayResult_l(SockException(Err_timeout,"play rtsp timeout")); + strongSelf->onPlayResult_l(SockException(Err_timeout,"play rtsp timeout"),false); return false; },getPoller())); if(!(*this)[kNetAdapter].empty()){ setNetAdapter((*this)[kNetAdapter]); } - startConnect(ip, port , playTimeOutSec); + startConnect(url._host, url._port, playTimeOutSec); } + void RtspPlayer::onConnect(const SockException &err){ - if(err.getErrCode()!=Err_success) { - onPlayResult_l(err); + if(err.getErrCode() != Err_success) { + onPlayResult_l(err,false); return; } @@ -165,7 +131,8 @@ void RtspPlayer::onRecv(const Buffer::Ptr& pBuf) { input(pBuf->data(),pBuf->size()); } void RtspPlayer::onErr(const SockException &ex) { - onPlayResult_l(ex); + //定时器_pPlayTimer为空后表明握手结束了 + onPlayResult_l(ex,!_pPlayTimer); } // from live555 bool RtspPlayer::handleAuthenticationFailure(const string ¶msStr) { @@ -272,6 +239,13 @@ void RtspPlayer::createUdpSockIfNecessary(int track_idx){ throw std::runtime_error("open rtcp sock failed"); } } + + if(rtpSockRef->get_local_port() % 2 != 0){ + //如果rtp端口不是偶数,那么与rtcp端口互换,目的是兼容一些要求严格的服务器 + Socket::Ptr tmp = rtpSockRef; + rtpSockRef = rtcpSockRef; + rtcpSockRef = tmp; + } } @@ -391,8 +365,7 @@ void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int uiTrackIndex) WarnL << "收到其他地址的rtcp数据:" << inet_ntoa(((struct sockaddr_in *) addr)->sin_addr); return; } - strongSelf->onRtcpPacket(uiTrackIndex, strongSelf->_aTrackInfo[uiTrackIndex], - (unsigned char *) buf->data(), buf->size()); + strongSelf->onRtcpPacket(uiTrackIndex, strongSelf->_aTrackInfo[uiTrackIndex], (unsigned char *) buf->data(), buf->size()); }); } } @@ -404,14 +377,7 @@ void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int uiTrackIndex) } //所有setup命令发送完毕 //发送play命令 - sendPause(false, 0,false); -} - -void RtspPlayer::sendOptions() { - _onHandshake = [](const Parser& parser){ -// DebugL << "options response"; - }; - sendRtspRequest("OPTIONS",_strContentBase); + sendPause(type_play, 0); } void RtspPlayer::sendDescribe() { @@ -420,46 +386,67 @@ void RtspPlayer::sendDescribe() { sendRtspRequest("DESCRIBE",_strUrl,{"Accept","application/sdp"}); } - -void RtspPlayer::sendPause(bool bPause,uint32_t seekMS,bool range){ +void RtspPlayer::sendPause(int type , uint32_t seekMS){ + _onHandshake = std::bind(&RtspPlayer::handleResPAUSE,this, placeholders::_1,type); //开启或暂停rtsp - _onHandshake = std::bind(&RtspPlayer::handleResPAUSE,this, placeholders::_1,bPause); - if(!bPause && range){ - sendRtspRequest(bPause ? "PAUSE" : "PLAY", _strContentBase, - {"Range",StrPrinter << "npt=" << setiosflags(ios::fixed) << setprecision(2) << seekMS / 1000.0 << "-"}); - } else{ - sendRtspRequest(bPause ? "PAUSE" : "PLAY", _strContentBase); - } - + switch (type){ + case type_pause: + sendRtspRequest("PAUSE", _strContentBase); + break; + case type_play: + sendRtspRequest("PLAY", _strContentBase); + break; + case type_seek: + sendRtspRequest("PLAY", _strContentBase, {"Range",StrPrinter << "npt=" << setiosflags(ios::fixed) << setprecision(2) << seekMS / 1000.0 << "-"}); + break; + default: + WarnL << "unknown type : " << type; + _onHandshake = nullptr; + break; + } } void RtspPlayer::pause(bool bPause) { - sendPause(bPause, getProgressMilliSecond(),false); + sendPause(bPause ? type_pause : type_seek, getProgressMilliSecond()); } -void RtspPlayer::handleResPAUSE(const Parser& parser, bool bPause) { +void RtspPlayer::handleResPAUSE(const Parser& parser,int type) { if (parser.Url() != "200") { - WarnL <<(bPause ? "Pause" : "Play") << " failed:" << parser.Url() << " " << parser.Tail() << endl; + switch (type) { + case type_pause: + WarnL << "Pause failed:" << parser.Url() << " " << parser.Tail() << endl; + break; + case type_play: + WarnL << "Play failed:" << parser.Url() << " " << parser.Tail() << endl; + break; + case type_seek: + WarnL << "Seek failed:" << parser.Url() << " " << parser.Tail() << endl; + break; + } return; } - if (!bPause) { - uint32_t iSeekTo = 0; - //修正时间轴 - auto strRange = parser["Range"]; - if (strRange.size()) { - auto strStart = FindField(strRange.data(), "npt=", "-"); - if (strStart == "now") { - strStart = "0"; - } - iSeekTo = 1000 * atof(strStart.data()); - DebugL << "seekTo(ms):" << iSeekTo ; - } - //设置相对时间戳 - _stamp[0].setRelativeStamp(iSeekTo); - _stamp[1].setRelativeStamp(iSeekTo); - onPlayResult_l(SockException(Err_success, "rtsp play success")); - } else { + + if (type == type_pause) { + //暂停成功! _pRtpTimer.reset(); + return; } + + //play或seek成功 + uint32_t iSeekTo = 0; + //修正时间轴 + auto strRange = parser["Range"]; + if (strRange.size()) { + auto strStart = FindField(strRange.data(), "npt=", "-"); + if (strStart == "now") { + strStart = "0"; + } + iSeekTo = 1000 * atof(strStart.data()); + DebugL << "seekTo(ms):" << iSeekTo; + } + //设置相对时间戳 + _stamp[0].setRelativeStamp(iSeekTo); + _stamp[1].setRelativeStamp(iSeekTo); + onPlayResult_l(SockException(Err_success, type == type_seek ? "resum rtsp success" : "rtsp play success"), type == type_seek); } void RtspPlayer::onWholeRtspPacket(Parser &parser) { @@ -471,8 +458,8 @@ void RtspPlayer::onWholeRtspPacket(Parser &parser) { } parser.Clear(); } catch (std::exception &err) { - SockException ex(Err_other, err.what()); - onPlayResult_l(ex); + //定时器_pPlayTimer为空后表明握手结束了 + onPlayResult_l(SockException(Err_other, err.what()),!_pPlayTimer); } } @@ -662,7 +649,7 @@ uint32_t RtspPlayer::getProgressMilliSecond() const{ return MAX(_stamp[0].getRelativeStamp(),_stamp[1].getRelativeStamp()); } void RtspPlayer::seekToMilliSecond(uint32_t ms) { - sendPause(false,ms, true); + sendPause(type_seek,ms); } void RtspPlayer::sendRtspRequest(const string &cmd, const string &url, const std::initializer_list &header) { @@ -752,7 +739,7 @@ void RtspPlayer::onRecvRTP_l(const RtpPacket::Ptr &pkt, const SdpTrack::Ptr &tra } -void RtspPlayer::onPlayResult_l(const SockException &ex) { +void RtspPlayer::onPlayResult_l(const SockException &ex , bool handshakeCompleted) { WarnL << ex.getErrCode() << " " << ex.what(); if(!ex){ @@ -760,33 +747,31 @@ void RtspPlayer::onPlayResult_l(const SockException &ex) { _rtpTicker.resetTime(); weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); int timeoutMS = (*this)[kMediaTimeoutMS].as(); - _pRtpTimer.reset( new Timer(timeoutMS / 2000.0, [weakSelf,timeoutMS]() { + //创建rtp数据接收超时检测定时器 + _pRtpTimer.reset( new Timer(timeoutMS / 2000.0, [weakSelf,timeoutMS]() { auto strongSelf=weakSelf.lock(); if(!strongSelf) { return false; } if(strongSelf->_rtpTicker.elapsedTime()> timeoutMS) { - //recv rtp timeout! - strongSelf->onPlayResult_l(SockException(Err_timeout,"recv rtp timeout")); + //接收rtp媒体数据包超时 + strongSelf->onPlayResult_l(SockException(Err_timeout,"receive rtp timeout"), true); return false; } return true; },getPoller())); } - if (_pPlayTimer) { + if (!handshakeCompleted) { //开始播放阶段 _pPlayTimer.reset(); onPlayResult(ex); - }else { - //播放中途阶段 - if (ex) { - //播放成功后异常断开回调 - onShutdown(ex); - }else{ - //恢复播放 - onResume(); - } + } else if (ex) { + //播放成功后异常断开回调 + onShutdown(ex); + } else { + //恢复播放 + onResume(); } if(ex){ @@ -794,21 +779,15 @@ void RtspPlayer::onPlayResult_l(const SockException &ex) { } } -int RtspPlayer::getTrackIndexByControlSuffix(const string &controlSuffix) const{ - for (unsigned int i = 0; i < _aTrackInfo.size(); i++) { - auto pos = _aTrackInfo[i]->_control_surffix.find(controlSuffix); - if (pos == 0) { - return i; - } - } - return -1; -} int RtspPlayer::getTrackIndexByInterleaved(int interleaved) const{ for (unsigned int i = 0; i < _aTrackInfo.size(); i++) { if (_aTrackInfo[i]->_interleaved == interleaved) { return i; } } + if(_aTrackInfo.size() == 1){ + return 0; + } return -1; } @@ -818,6 +797,9 @@ int RtspPlayer::getTrackIndexByTrackType(TrackType trackType) const { return i; } } + if(_aTrackInfo.size() == 1){ + return 0; + } return -1; } diff --git a/src/Rtsp/RtspPlayer.h b/src/Rtsp/RtspPlayer.h index be170953..5f4f7395 100644 --- a/src/Rtsp/RtspPlayer.h +++ b/src/Rtsp/RtspPlayer.h @@ -40,7 +40,7 @@ #include "Network/TcpClient.h" #include "RtspSplitter.h" #include "RtpReceiver.h" -#include "MediaFile/Stamp.h" +#include "Common/Stamp.h" using namespace std; using namespace toolkit; @@ -101,22 +101,19 @@ protected: void onErr(const SockException &ex) override; private: void onRecvRTP_l(const RtpPacket::Ptr &pRtppt, const SdpTrack::Ptr &track); - void onPlayResult_l(const SockException &ex); + void onPlayResult_l(const SockException &ex , bool handshakeCompleted); - int getTrackIndexByControlSuffix(const string &controlSuffix) const; int getTrackIndexByInterleaved(int interleaved) const; int getTrackIndexByTrackType(TrackType trackType) const; - void play(bool isSSL,const string &strUrl, const string &strUser, const string &strPwd, Rtsp::eRtpType eType); void handleResSETUP(const Parser &parser, unsigned int uiTrackIndex); void handleResDESCRIBE(const Parser &parser); bool handleAuthenticationFailure(const string &wwwAuthenticateParamsStr); - void handleResPAUSE(const Parser &parser, bool bPause); + void handleResPAUSE(const Parser &parser, int type); //发送SETUP命令 void sendSetup(unsigned int uiTrackIndex); - void sendPause(bool bPause,uint32_t ms, bool range); - void sendOptions(); + void sendPause(int type , uint32_t ms); void sendDescribe(); void sendRtspRequest(const string &cmd, const string &url ,const StrCaseMap &header = StrCaseMap()); diff --git a/src/Rtsp/RtspPlayerImp.h b/src/Rtsp/RtspPlayerImp.h index effa7328..f61ac7c6 100644 --- a/src/Rtsp/RtspPlayerImp.h +++ b/src/Rtsp/RtspPlayerImp.h @@ -64,30 +64,43 @@ private: bool onCheckSDP(const string &sdp) override { _pRtspMediaSrc = dynamic_pointer_cast(_pMediaSrc); if(_pRtspMediaSrc){ - _pRtspMediaSrc->onGetSDP(sdp); + _pRtspMediaSrc->setSdp(sdp); } - _parser.reset(new RtspDemuxer(sdp)); + _delegate.reset(new RtspDemuxer); + _delegate->loadSdp(sdp); return true; } void onRecvRTP(const RtpPacket::Ptr &rtp, const SdpTrack::Ptr &track) override { if(_pRtspMediaSrc){ + // rtsp直接代理是无法判断该rtp是否是I帧,所以GOP缓存基本是无效的 + // 为了减少内存使用,那么我们设置为一直关键帧以便清空GOP缓存 _pRtspMediaSrc->onWrite(rtp,true); } - _parser->inputRtp(rtp); + _delegate->inputRtp(rtp); - //由于我们重载isInited方法强制认为一旦获取sdp那么就初始化Track成功, - //所以我们不需要在后续检验是否初始化成功 - //checkInited(0); + if(_maxAnalysisMS && _delegate->isInited(_maxAnalysisMS)){ + PlayerImp::onPlayResult(SockException(Err_success,"play rtsp success")); + _maxAnalysisMS = 0; + } } - bool isInited(int analysisMs) override{ - //rtsp是通过sdp来完成track的初始化的,所以我们强制返回true, - //认为已经初始化完毕,这样可以提高rtsp打开速度 - return true; + //在RtspPlayer中触发onPlayResult事件只是代表收到play回复了, + //并不代表所有track初始化成功了(这跟rtmp播放器不一样) + //如果sdp里面信息不完整,只能尝试延后从rtp中恢复关键信息并初始化track + //如果超过这个时间还未获取成功,那么会强制触发onPlayResult事件(虽然此时有些track还未初始化成功) + void onPlayResult(const SockException &ex) override { + //isInited判断条件:无超时 + if(ex || _delegate->isInited(0)){ + //已经初始化成功,说明sdp里面有完善的信息 + PlayerImp::onPlayResult(ex); + }else{ + //还没初始化成功,说明sdp里面信息不完善,还有一些track未初始化成功 + _maxAnalysisMS = (*this)[Client::kMaxAnalysisMS]; + } } private: RtspMediaSource::Ptr _pRtspMediaSrc; - + int _maxAnalysisMS = 0; }; } /* namespace mediakit */ diff --git a/src/Rtsp/RtspPusher.cpp b/src/Rtsp/RtspPusher.cpp index 7e2ca8c2..7dd6a61f 100644 --- a/src/Rtsp/RtspPusher.cpp +++ b/src/Rtsp/RtspPusher.cpp @@ -43,25 +43,46 @@ void RtspPusher::teardown() { } void RtspPusher::publish(const string &strUrl) { - auto userAndPwd = FindField(strUrl.data(),"://","@"); - Rtsp::eRtpType eType = (Rtsp::eRtpType)(int)(*this)[ kRtpType]; - if(userAndPwd.empty()){ - publish(strUrl,"","",eType); + RtspUrl url; + if(!url.parse(strUrl)){ + onPublishResult(SockException(Err_other,StrPrinter << "illegal rtsp url:" << strUrl),false); return; } - auto suffix = FindField(strUrl.data(),"@",nullptr); - auto url = StrPrinter << "rtsp://" << suffix << endl; - if(userAndPwd.find(":") == string::npos){ - publish(url,userAndPwd,"",eType); - return; + + teardown(); + + if (url._user.size()) { + (*this)[kRtspUser] = url._user; } - auto user = FindField(userAndPwd.data(),nullptr,":"); - auto pwd = FindField(userAndPwd.data(),":",nullptr); - publish(url,user,pwd,eType); + if (url._passwd.size()) { + (*this)[kRtspPwd] = url._passwd; + (*this)[kRtspPwdIsMD5] = false; + } + + _strUrl = strUrl; + _eType = (Rtsp::eRtpType)(int)(*this)[kRtpType]; + DebugL << url._url << " " << (url._user.size() ? url._user : "null") << " " << (url._passwd.size() ? url._passwd : "null") << " " << _eType; + + weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + float publishTimeOutSec = (*this)[kTimeoutMS].as() / 1000.0; + _pPublishTimer.reset( new Timer(publishTimeOutSec, [weakSelf]() { + auto strongSelf=weakSelf.lock(); + if(!strongSelf) { + return false; + } + strongSelf->onPublishResult(SockException(Err_timeout,"publish rtsp timeout"),false); + return false; + },getPoller())); + + if(!(*this)[kNetAdapter].empty()){ + setNetAdapter((*this)[kNetAdapter]); + } + + startConnect(url._host, url._port, publishTimeOutSec); } -void RtspPusher::onPublishResult(const SockException &ex) { - if(_pPublishTimer){ +void RtspPusher::onPublishResult(const SockException &ex, bool handshakeCompleted) { + if(!handshakeCompleted){ //播放结果回调 _pPublishTimer.reset(); if(_onPublished){ @@ -79,62 +100,14 @@ void RtspPusher::onPublishResult(const SockException &ex) { } } -void RtspPusher::publish(const string & strUrl, const string &strUser, const string &strPwd, Rtsp::eRtpType eType ) { - DebugL << strUrl << " " - << (strUser.size() ? strUser : "null") << " " - << (strPwd.size() ? strPwd:"null") << " " - << eType; - teardown(); - - if(strUser.size()){ - (*this)[kRtspUser] = strUser; - } - if(strPwd.size()){ - (*this)[kRtspPwd] = strPwd; - (*this)[kRtspPwdIsMD5] = false; - } - - _eType = eType; - - auto ip = FindField(strUrl.data(), "://", "/"); - if (!ip.size()) { - ip = FindField(strUrl.data(), "://", NULL); - } - auto port = atoi(FindField(ip.data(), ":", NULL).data()); - if (port <= 0) { - //rtsp 默认端口554 - port = 554; - } else { - //服务器域名 - ip = FindField(ip.data(), NULL, ":"); - } - - _strUrl = strUrl; - - weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); - float playTimeOutSec = (*this)[kTimeoutMS].as() / 1000.0; - _pPublishTimer.reset( new Timer(playTimeOutSec, [weakSelf]() { - auto strongSelf=weakSelf.lock(); - if(!strongSelf) { - return false; - } - strongSelf->onPublishResult(SockException(Err_timeout,"publish rtsp timeout")); - return false; - },getPoller())); - - if(!(*this)[kNetAdapter].empty()){ - setNetAdapter((*this)[kNetAdapter]); - } - startConnect(ip, port , playTimeOutSec); -} - void RtspPusher::onErr(const SockException &ex) { - onPublishResult(ex); + //定时器_pPublishTimer为空后表明握手结束了 + onPublishResult(ex,!_pPublishTimer); } void RtspPusher::onConnect(const SockException &err) { if(err) { - onPublishResult(err); + onPublishResult(err,false); return; } //推流器不需要多大的接收缓存,节省内存占用 @@ -147,7 +120,8 @@ void RtspPusher::onRecv(const Buffer::Ptr &pBuf){ input(pBuf->data(), pBuf->size()); } catch (exception &e) { SockException ex(Err_other, e.what()); - onPublishResult(ex); + //定时器_pPublishTimer为空后表明握手结束了 + onPublishResult(ex,!_pPublishTimer); } } @@ -351,6 +325,9 @@ inline int RtspPusher::getTrackIndexByTrackType(TrackType type) { return i; } } + if(_aTrackInfo.size() == 1){ + return 0; + } return -1; } @@ -374,7 +351,7 @@ void RtspPusher::sendRecord() { _pRtspReader->setDetachCB([weakSelf](){ auto strongSelf = weakSelf.lock(); if(strongSelf){ - strongSelf->onPublishResult(SockException(Err_other,"媒体源被释放")); + strongSelf->onPublishResult(SockException(Err_other,"媒体源被释放"), !strongSelf->_pPublishTimer); } }); if(_eType != Rtsp::RTP_TCP){ @@ -389,7 +366,7 @@ void RtspPusher::sendRecord() { return true; },getPoller())); } - onPublishResult(SockException(Err_success,"success")); + onPublishResult(SockException(Err_success,"success"), false); //提升发送性能 setSocketFlags(); }; diff --git a/src/Rtsp/RtspPusher.h b/src/Rtsp/RtspPusher.h index 958c1c87..99431f4d 100644 --- a/src/Rtsp/RtspPusher.h +++ b/src/Rtsp/RtspPusher.h @@ -48,8 +48,7 @@ protected: void onWholeRtspPacket(Parser &parser) override ; void onRtpPacket(const char *data,uint64_t len) override {}; private: - void publish(const string &strUrl, const string &strUser, const string &strPwd, Rtsp::eRtpType eType ); - void onPublishResult(const SockException &ex); + void onPublishResult(const SockException &ex, bool handshakeCompleted); void sendAnnounce(); void sendSetup(unsigned int uiTrackIndex); diff --git a/src/Rtsp/RtspSession.cpp b/src/Rtsp/RtspSession.cpp index 3985ebec..29a62779 100644 --- a/src/Rtsp/RtspSession.cpp +++ b/src/Rtsp/RtspSession.cpp @@ -86,11 +86,13 @@ RtspSession::~RtspSession() { void RtspSession::onError(const SockException& err) { bool isPlayer = !_pushSrc; - WarnP(this) << (isPlayer ? "播放器(" : "推流器(") + uint64_t duration = _ticker.createdTime()/1000; + WarnP(this) << (isPlayer ? "RTSP播放器(" : "RTSP推流器(") << _mediaInfo._vhost << "/" << _mediaInfo._app << "/" << _mediaInfo._streamid - << ")断开:" << err.what(); + << ")断开:" << err.what() + << ",耗时(s):" << duration; if (_rtpType == Rtsp::RTP_MULTICAST) { //取消UDP端口监听 @@ -106,12 +108,7 @@ void RtspSession::onError(const SockException& err) { //流量统计事件广播 GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold); if(_ui64TotalBytes > iFlowThreshold * 1024){ - NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, - _mediaInfo, - _ui64TotalBytes, - _ticker.createdTime()/1000, - isPlayer, - *this); + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport, _mediaInfo, _ui64TotalBytes, duration, isPlayer, getIdentifier(), get_peer_ip(), get_peer_port()); } } @@ -263,9 +260,9 @@ void RtspSession::handleReq_ANNOUNCE(const Parser &parser) { _strSession = makeRandStr(12); _aTrackInfo = sdpParser.getAvailableTrack(); - _pushSrc = std::make_shared(_mediaInfo._vhost,_mediaInfo._app,_mediaInfo._streamid); + _pushSrc = std::make_shared(_mediaInfo._vhost,_mediaInfo._app,_mediaInfo._streamid); _pushSrc->setListener(dynamic_pointer_cast(shared_from_this())); - _pushSrc->onGetSDP(sdpParser.toString()); + _pushSrc->setSdp(sdpParser.toString()); sendRtspResponse("200 OK",{"Content-Base",_strContentBase + "/"}); } @@ -324,8 +321,11 @@ void RtspSession::handleReq_RECORD(const Parser &parser){ auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish,_mediaInfo,invoker,*this); if(!flag){ //该事件无人监听,默认不鉴权 - onRes("",true,true,false); - } + GET_CONFIG(bool,toRtxp,General::kPublishToRtxp); + GET_CONFIG(bool,toHls,General::kPublishToHls); + GET_CONFIG(bool,toMP4,General::kPublishToMP4); + onRes("",toRtxp,toHls,toMP4); + } } void RtspSession::handleReq_Describe(const Parser &parser) { @@ -368,7 +368,7 @@ void RtspSession::handleReq_Describe(const Parser &parser) { void RtspSession::onAuthSuccess() { TraceP(this); weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); - MediaSource::findAsync(_mediaInfo,weakSelf.lock(), true,[weakSelf](const MediaSource::Ptr &src){ + MediaSource::findAsync(_mediaInfo,weakSelf.lock(),[weakSelf](const MediaSource::Ptr &src){ auto strongSelf = weakSelf.lock(); if(!strongSelf){ return; @@ -770,7 +770,7 @@ void RtspSession::handleReq_Play(const Parser &parser) { auto iStartTime = 1000 * atof(strStart.data()); InfoP(this) << "rtsp seekTo(ms):" << iStartTime; useBuf = !pMediaSrc->seekTo(iStartTime); - }else if(pMediaSrc->readerCount() == 0){ + }else if(pMediaSrc->totalReaderCount() == 0){ //第一个消费者 pMediaSrc->seekTo(0); } @@ -934,12 +934,6 @@ inline void RtspSession::send_NotAcceptable() { void RtspSession::onRtpSorted(const RtpPacket::Ptr &rtppt, int trackidx) { - GET_CONFIG(bool,modify_stamp,Rtsp::kModifyStamp); - if(modify_stamp){ - int64_t dts_out; - _stamp[trackidx].revise(0, 0, dts_out, dts_out, true); - rtppt->timeStamp = dts_out; - } _pushSrc->onWrite(rtppt, false); } inline void RtspSession::onRcvPeerUdpData(int intervaled, const Buffer::Ptr &pBuf, const struct sockaddr& addr) { @@ -1102,6 +1096,9 @@ inline int RtspSession::getTrackIndexByTrackType(TrackType type) { return i; } } + if(_aTrackInfo.size() == 1){ + return 0; + } return -1; } inline int RtspSession::getTrackIndexByControlSuffix(const string &controlSuffix) { @@ -1122,12 +1119,15 @@ inline int RtspSession::getTrackIndexByInterleaved(int interleaved){ return i; } } + if(_aTrackInfo.size() == 1){ + return 0; + } return -1; } bool RtspSession::close(MediaSource &sender,bool force) { //此回调在其他线程触发 - if(!_pushSrc || (!force && _pushSrc->readerCount() != 0)){ + if(!_pushSrc || (!force && _pushSrc->totalReaderCount())){ return false; } string err = StrPrinter << "close media:" << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force; @@ -1138,12 +1138,15 @@ bool RtspSession::close(MediaSource &sender,bool force) { void RtspSession::onNoneReader(MediaSource &sender){ //此回调在其他线程触发 - if(!_pushSrc || _pushSrc->readerCount() != 0){ + if(!_pushSrc || _pushSrc->totalReaderCount()){ return; } MediaSourceEvent::onNoneReader(sender); } +int RtspSession::totalReaderCount(MediaSource &sender) { + return _pushSrc ? _pushSrc->totalReaderCount() : sender.readerCount(); +} void RtspSession::sendRtpPacket(const RtpPacket::Ptr & pkt) { //InfoP(this) <<(int)pkt.Interleaved; diff --git a/src/Rtsp/RtspSession.h b/src/Rtsp/RtspSession.h index 33637666..6a77dfc5 100644 --- a/src/Rtsp/RtspSession.h +++ b/src/Rtsp/RtspSession.h @@ -40,7 +40,8 @@ #include "RtspMediaSource.h" #include "RtspSplitter.h" #include "RtpReceiver.h" -#include "RtspToRtmpMediaSource.h" +#include "RtspMediaSourceImp.h" +#include "Common/Stamp.h" using namespace std; using namespace toolkit; @@ -107,8 +108,9 @@ protected: //MediaSourceEvent override bool close(MediaSource &sender,bool force) override ; void onNoneReader(MediaSource &sender) override; + int totalReaderCount(MediaSource &sender) override; - //TcpSession override + //TcpSession override int send(const Buffer::Ptr &pkt) override; /** @@ -230,7 +232,7 @@ private: //是否开始发送rtp bool _enableSendRtp; //rtsp推流相关 - RtspToRtmpMediaSource::Ptr _pushSrc; + RtspMediaSourceImp::Ptr _pushSrc; //rtcp统计,trackid idx 为数组下标 RtcpCounter _aRtcpCnt[2]; //rtcp发送时间,trackid idx 为数组下标 diff --git a/src/Rtsp/RtspToRtmpMediaSource.h b/src/Rtsp/RtspToRtmpMediaSource.h deleted file mode 100644 index a05cb393..00000000 --- a/src/Rtsp/RtspToRtmpMediaSource.h +++ /dev/null @@ -1,121 +0,0 @@ -/* -* MIT License -* -* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> -* -* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. -*/ - -#ifndef SRC_RTSP_RTSPTORTMPMEDIASOURCE_H_ -#define SRC_RTSP_RTSPTORTMPMEDIASOURCE_H_ - -#include "Rtmp/amf.h" -#include "RtspMediaSource.h" -#include "RtspDemuxer.h" -#include "Common/MultiMediaSourceMuxer.h" - -using namespace toolkit; - -namespace mediakit { - -class RtspToRtmpMediaSource : public RtspMediaSource { -public: - typedef std::shared_ptr Ptr; - - RtspToRtmpMediaSource(const string &vhost, - const string &app, - const string &id, - int ringSize = 0) : RtspMediaSource(vhost, app, id,ringSize) { - } - - virtual ~RtspToRtmpMediaSource() {} - - virtual void onGetSDP(const string &strSdp) override { - _demuxer = std::make_shared(strSdp); - RtspMediaSource::onGetSDP(strSdp); - } - - virtual void onWrite(const RtpPacket::Ptr &rtp, bool bKeyPos) override { - if (_demuxer) { - bKeyPos = _demuxer->inputRtp(rtp); - if (!_muxer && _demuxer->isInited(2000)) { - _muxer = std::make_shared(getVhost(), - getApp(), - getId(), - _demuxer->getDuration(), - false,//不重复生成rtsp - _enableRtmp, - _enableHls, - _enableMP4); - for (auto &track : _demuxer->getTracks(false)) { - _muxer->addTrack(track); - track->addDelegate(_muxer); - } - _muxer->setListener(_listener); - } - } - RtspMediaSource::onWrite(rtp, bKeyPos); - } - - void setListener(const std::weak_ptr &listener) override { - RtspMediaSource::setListener(listener); - if(_muxer){ - _muxer->setListener(listener); - } - } - int readerCount() override { - return RtspMediaSource::readerCount() + (_muxer ? _muxer->readerCount() : 0); - } - - /** - * 获取track - * @return - */ - vector getTracks(bool trackReady) const override { - if(!_demuxer){ - return this->RtspMediaSource::getTracks(trackReady); - } - return _demuxer->getTracks(trackReady); - } - - /** - * 设置协议转换 - * @param enableRtmp 是否转换成rtmp - * @param enableHls 是否转换成hls - * @param enableMP4 是否mp4录制 - */ - void setProtocolTranslation(bool enableRtmp,bool enableHls,bool enableMP4){ -// DebugL << enableRtmp << " " << enableHls << " " << enableMP4; - _enableRtmp = enableRtmp; - _enableHls = enableHls; - _enableMP4 = enableMP4; - } -private: - RtspDemuxer::Ptr _demuxer; - MultiMediaSourceMuxer::Ptr _muxer; - bool _enableHls = true; - bool _enableMP4 = false; - bool _enableRtmp = true; -}; - -} /* namespace mediakit */ - -#endif /* SRC_RTSP_RTSPTORTMPMEDIASOURCE_H_ */ diff --git a/src/Shell/ShellCMD.h b/src/Shell/ShellCMD.h index eef6b653..e3a44901 100644 --- a/src/Shell/ShellCMD.h +++ b/src/Shell/ShellCMD.h @@ -16,39 +16,35 @@ class CMD_media: public CMD { public: CMD_media(){ _parser.reset(new OptionParser([](const std::shared_ptr &stream,mINI &ini){ - MediaSource::for_each_media([&](const string &schema, - const string &vhost, - const string &app, - const string &streamid, - const MediaSource::Ptr &media){ - if(!ini["schema"].empty() && ini["schema"] != schema){ + MediaSource::for_each_media([&](const MediaSource::Ptr &media){ + if(!ini["schema"].empty() && ini["schema"] != media->getSchema()){ //筛选协议不匹配 return; } - if(!ini["vhost"].empty() && ini["vhost"] != vhost){ + if(!ini["vhost"].empty() && ini["vhost"] != media->getVhost()){ //筛选虚拟主机不匹配 return; } - if(!ini["app"].empty() && ini["app"] != app){ + if(!ini["app"].empty() && ini["app"] != media->getApp()){ //筛选应用名不匹配 return; } - if(!ini["stream"].empty() && ini["stream"] != streamid){ + if(!ini["stream"].empty() && ini["stream"] != media->getId()){ //流id不匹配 return; } if(ini.find("list") != ini.end()){ //列出源 (*stream) << "\t" - << schema << "/" - << vhost << "/" - << app << "/" - << streamid + << media->getSchema() << "/" + << media->getVhost() << "/" + << media->getApp() << "/" + << media->getId() << "\r\n"; return; } - EventPollerPool::Instance().getPoller()->async([ini,media,stream,schema,vhost,app,streamid](){ + EventPollerPool::Instance().getPoller()->async([ini,media,stream](){ if(ini.find("kick") != ini.end()){ //踢出源 do{ @@ -59,18 +55,18 @@ public: break; } (*stream) << "\t踢出成功:" - << schema << "/" - << vhost << "/" - << app << "/" - << streamid + << media->getSchema() << "/" + << media->getVhost() << "/" + << media->getApp() << "/" + << media->getId() << "\r\n"; return; }while(0); (*stream) << "\t踢出失败:" - << schema << "/" - << vhost << "/" - << app << "/" - << streamid + << media->getSchema() << "/" + << media->getVhost() << "/" + << media->getApp() << "/" + << media->getId() << "\r\n"; } },false); diff --git a/tests/test_pusher.cpp b/tests/test_pusher.cpp index c7a56aa0..d51aeadb 100644 --- a/tests/test_pusher.cpp +++ b/tests/test_pusher.cpp @@ -99,8 +99,8 @@ int domain(const string &playUrl, const string &pushUrl) { NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaChanged, [pushUrl,poller](BroadcastMediaChangedArgs) { //媒体源"app/stream"已经注册,这时方可新建一个RtmpPusher对象并绑定该媒体源 - if(bRegist && pushUrl.find(schema) == 0){ - createPusher(poller,schema,vhost,app, stream, pushUrl); + if(bRegist && pushUrl.find(sender.getSchema()) == 0){ + createPusher(poller,sender.getSchema(),sender.getVhost(),sender.getApp(), sender.getId(), pushUrl); } }); diff --git a/tests/test_pusherMp4.cpp b/tests/test_pusherMp4.cpp index fc502d29..9b94631b 100644 --- a/tests/test_pusherMp4.cpp +++ b/tests/test_pusherMp4.cpp @@ -33,7 +33,7 @@ #include "Rtmp/RtmpPusher.h" #include "Common/config.h" #include "Pusher/MediaPusher.h" -#include "MediaFile/MediaReader.h" +#include "Record/MP4Reader.h" using namespace std; using namespace toolkit; @@ -63,7 +63,7 @@ void createPusher(const EventPoller::Ptr &poller, const string &filePath, const string &url) { //不限制APP名,并且指定文件绝对路径 - auto src = MediaReader::onMakeMediaSource(schema,vhost,app,stream,filePath, false); + auto src = MP4Reader::onMakeMediaSource(schema,vhost,app,stream,filePath, false); if(!src){ //文件不存在 WarnL << "MP4文件不存在:" << filePath; diff --git a/tests/test_rtp.cpp b/tests/test_rtp.cpp new file mode 100644 index 00000000..8e36a131 --- /dev/null +++ b/tests/test_rtp.cpp @@ -0,0 +1,111 @@ +/* + * MIT License + * + * Copyright (c) 2019 Gemfield + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "Util/MD5.h" +#include "Util/File.h" +#include "Util/logger.h" +#include "Util/SSLBox.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 "Rtp/RtpSelector.h" + +using namespace std; +using namespace toolkit; +using namespace mediakit; + +#if defined(ENABLE_RTPPROXY) +static bool loadFile(const char *path){ + FILE *fp = fopen(path, "rb"); + if (!fp) { + WarnL << "open file failed:" << path; + return false; + } + + uint32_t timeStamp_last = 0; + uint16_t len; + char rtp[2 * 1024]; + struct sockaddr addr = {0}; + while (true) { + if (2 != fread(&len, 1, 2, fp)) { + WarnL; + break; + } + len = ntohs(len); + if (len < 12 || len > sizeof(rtp)) { + WarnL << len; + break; + } + + if (len != fread(rtp, 1, len, fp)) { + WarnL; + break; + } + + uint32_t timeStamp; + RtpSelector::Instance().inputRtp(rtp,len, &addr,&timeStamp); + if(timeStamp_last){ + auto diff = timeStamp - timeStamp_last; + if(diff > 0){ + usleep(diff * 1000); + } + } + timeStamp_last = timeStamp; + } + fclose(fp); + return true; +} +#endif//#if defined(ENABLE_RTPPROXY) + +int main(int argc,char *argv[]) { + //设置日志 + Logger::Instance().add(std::make_shared("ConsoleChannel")); +#if defined(ENABLE_RTPPROXY) + //启动异步日志线程 + 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 + //此处选择是否导出调试文件 +// mINI::Instance()[RtpProxy::kDumpDir] = "/Users/xzl/Desktop/"; + + loadFile(argv[1]); +#else + ErrorL << "please ENABLE_RTPPROXY and then test"; +#endif//#if defined(ENABLE_RTPPROXY) + return 0; +} + + diff --git a/tests/test_server.cpp b/tests/test_server.cpp index 6c9d6bf7..02d67a1c 100644 --- a/tests/test_server.cpp +++ b/tests/test_server.cpp @@ -178,23 +178,23 @@ void initEventListener() { //监听rtsp、rtmp源注册或注销事件;此处用于测试rtmp保存为flv录像,保存在http根目录下 NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaChanged, [](BroadcastMediaChangedArgs) { - if (schema == RTMP_SCHEMA && app == "live") { + if (sender.getSchema() == RTMP_SCHEMA && sender.getApp() == "live") { lock_guard lck(s_mtxFlvRecorder); if (bRegist) { - DebugL << "开始录制RTMP:" << schema << " " << vhost << " " << app << " " << stream; + DebugL << "开始录制RTMP:" << sender.getSchema() << " " << sender.getVhost() << " " << sender.getApp() << " " << sender.getId(); GET_CONFIG(string, http_root, Http::kRootPath); auto path = - http_root + "/" + vhost + "/" + app + "/" + stream + "_" + to_string(time(NULL)) + ".flv"; + http_root + "/" + sender.getVhost() + "/" + sender.getApp() + "/" + sender.getId() + "_" + to_string(time(NULL)) + ".flv"; FlvRecorder::Ptr recorder(new FlvRecorder); try { recorder->startRecord(EventPollerPool::Instance().getPoller(), dynamic_pointer_cast(sender.shared_from_this()), path); - s_mapFlvRecorder[vhost + "/" + app + "/" + stream] = recorder; + s_mapFlvRecorder[sender.getVhost() + "/" + sender.getApp() + "/" + sender.getId()] = recorder; } catch (std::exception &ex) { WarnL << ex.what(); } } else { - s_mapFlvRecorder.erase(vhost + "/" + app + "/" + stream); + s_mapFlvRecorder.erase(sender.getVhost() + "/" + sender.getApp() + "/" + sender.getId()); } } }); @@ -239,7 +239,7 @@ int main(int argc,char *argv[]) { //这里是拉流地址,支持rtmp/rtsp协议,负载必须是H264+AAC //如果是其他不识别的音视频将会被忽略(譬如说h264+adpcm转发后会去除音频) - auto urlList = {"rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov" + auto urlList = {"rtsp://admin:admin123@192.168.1.64:554/cam/realmonitor?channel=1&subtype=1" //rtsp链接支持输入用户名密码 /*"rtsp://admin:jzan123456@192.168.0.122/"*/}; map proxyMap; @@ -258,7 +258,7 @@ int main(int argc,char *argv[]) { //rtsp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4 //rtmp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4 - PlayerProxy::Ptr player(new PlayerProxy(DEFAULT_VHOST, "live", to_string(i).data())); + PlayerProxy::Ptr player(new PlayerProxy(DEFAULT_VHOST, "live", std::string("chn") + to_string(i).data())); //指定RTP over TCP(播放rtsp时有效) (*player)[kRtpType] = Rtsp::RTP_TCP; //开始播放,如果播放失败或者播放中止,将会自动重试若干次,重试次数在配置文件中配置,默认一直重试 @@ -354,6 +354,7 @@ int main(int argc,char *argv[]) { signal(SIGHUP, [](int) { loadIniConfig(); }); sem.wait(); + Recorder::stopAll(); lock_guard lck(s_mtxFlvRecorder); s_mapFlvRecorder.clear(); return 0; diff --git a/tests/test_wsServer.cpp b/tests/test_wsServer.cpp index 12891889..1821467b 100644 --- a/tests/test_wsServer.cpp +++ b/tests/test_wsServer.cpp @@ -51,6 +51,7 @@ public: } void onRecv(const Buffer::Ptr &buffer) override { //回显数据 + send("from EchoSession:"); send(buffer); } void onError(const SockException &err) override{ @@ -62,6 +63,48 @@ public: } }; + +class EchoSessionWithUrl : public TcpSession { +public: + EchoSessionWithUrl(const Socket::Ptr &pSock) : TcpSession(pSock){ + DebugL; + } + virtual ~EchoSessionWithUrl(){ + DebugL; + } + + void attachServer(const TcpServer &server) override{ + DebugL << getIdentifier() << " " << TcpSession::getIdentifier(); + } + void onRecv(const Buffer::Ptr &buffer) override { + //回显数据 + send("from EchoSessionWithUrl:"); + send(buffer); + } + void onError(const SockException &err) override{ + WarnL << err.what(); + } + //每隔一段时间触发,用来做超时管理 + void onManager() override{ + DebugL; + } +}; + + +/** + * 此对象可以根据websocket 客户端访问的url选择创建不同的对象 + */ +struct EchoSessionCreator { + //返回的TcpSession必须派生于SendInterceptor,可以返回null(拒绝连接) + TcpSession::Ptr operator()(const Parser &header, const HttpSession &parent, const Socket::Ptr &pSock) { +// return nullptr; + if (header.Url() == "/") { + return std::make_shared >(header, parent, pSock); + } + return std::make_shared >(header, parent, pSock); + } +}; + int main(int argc, char *argv[]) { //设置日志 Logger::Instance().add(std::make_shared()); @@ -71,13 +114,19 @@ int main(int argc, char *argv[]) { TcpServer::Ptr httpSrv(new TcpServer()); //http服务器,支持websocket - httpSrv->start>(80);//默认80 + httpSrv->start >(80);//默认80 TcpServer::Ptr httpsSrv(new TcpServer()); //https服务器,支持websocket - httpsSrv->start>(443);//默认443 + httpsSrv->start >(443);//默认443 + + TcpServer::Ptr httpSrvOld(new TcpServer()); + //兼容之前的代码(但是不支持根据url选择生成TcpSession类型) + httpSrvOld->start >(8080); + + DebugL << "请打开网页:http://www.websocket-test.com/,进行测试"; + DebugL << "连接 ws://127.0.0.1/xxxx,ws://127.0.0.1/ 测试的效果将不同,支持根据url选择不同的处理逻辑"; - DebugL << "请打开网页:http://www.websocket-test.com/,连接 ws://127.0.0.1/测试"; //设置退出信号处理函数 static semaphore sem; diff --git a/www/readme/index.html b/www/readme/index.html new file mode 100644 index 00000000..964500e1 --- /dev/null +++ b/www/readme/index.html @@ -0,0 +1,8 @@ + +欢迎使用ZLMediaKit + +

    欢迎使用ZLMediaKit,请阅读wiki 获取更多帮助!

    +
    +
    ZLMediaKit
    + + \ No newline at end of file