diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index dc2660e2..55bb0021 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -14,7 +14,7 @@ jobs: run: git submodule update --init - name: apt-get安装依赖库(非必选) - run: sudo apt-get install -y cmake libmysqlclient-dev libssl-dev libx264-dev libfaac-dev libmp4v2-dev libsdl-dev libavcodec-dev libavutil-dev + run: 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 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/media-server b/3rdpart/media-server index 40edf624..4325386b 160000 --- a/3rdpart/media-server +++ b/3rdpart/media-server @@ -1 +1 @@ -Subproject commit 40edf6243d9d99676062062efdec203b24a178aa +Subproject commit 4325386be318889b7815cdd86a2e83f05a059c81 diff --git a/CMakeLists.txt b/CMakeLists.txt index f90e5cd3..6d20b60d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,27 +30,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 +46,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 +96,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 +106,75 @@ 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) + 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) +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 +183,8 @@ elseif(NOT ANDROID OR IOS) list(APPEND LINK_LIB_LIST pthread) endif () - - #测试程序 add_subdirectory(tests) #主服务器 add_subdirectory(server) - - - - - - - - - - - - - - - - - - - - - - - - - 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 d02ad8e3..e3c0c950 100644 --- a/README.md +++ b/README.md @@ -331,6 +331,8 @@ docker build -t zlmediakit . 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 @@ -349,6 +351,7 @@ 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. + ``` diff --git a/README_CN.md b/README_CN.md index a2eba340..037c5c5a 100644 --- a/README_CN.md +++ b/README_CN.md @@ -2,6 +2,13 @@ # 一个基于C++11的高性能运营级流媒体服务框架 [![Build Status](https://travis-ci.org/xiongziliang/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xiongziliang/ZLMediaKit) + +## 国内用户请使用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),支持协议间的互相转换,提供一站式的服务。 @@ -352,9 +359,16 @@ git submodule update --init ## 联系方式 - 邮箱:<771730766@qq.com>(本项目相关或流媒体相关问题请走issue流程,否则恕不邮件答复) - QQ群:542509000 + +## 怎么提问? +如果要对项目有相关疑问,建议您这么做: + - 1、仔细看下readme、wiki,如果有必要可以查看下issue. + - 2、如果您的问题还没解决,可以提issue. + - 3、有些问题,如果不具备参考性的,无需在issue提的,可以在qq群提. + - 4、QQ私聊一般不接受无偿技术咨询和支持(谈谈人生理想还是可以的😂),毕竟精力有限,谢谢理解. ## 捐赠 -如果本项目能切实帮助您减少重复开发的工作量,您可以在自愿的基础上支持下作者,谢谢! +欢迎捐赠以便更好的推动项目的发展,谢谢您的支持! [支付宝](https://raw.githubusercontent.com/xiongziliang/other/master/IMG_3919.JPG) diff --git a/conf/config.ini b/conf/config.ini index 1ef7c145..a7588b80 100644 --- a/conf/config.ini +++ b/conf/config.ini @@ -86,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 @@ -94,8 +96,6 @@ timeoutSec=10 charSet=utf-8 #http链接超时时间 keepAliveSecond=10 -#keep-alive类型的链接最多复用次数 -maxReqCount=100 #http请求体最大字节数,如果post的body太大,则不适合缓存body在内存 maxReqSize=4096 #404网页内容,用户可以自定义404网页 @@ -161,6 +161,18 @@ maxRtpCount=50 #视频mtu大小,该参数限制rtp最大字节数,推荐不要超过1400 videoMtuSize=1400 +[rtp_proxy] +#udp类型的代理服务器是否检查rtp源地址,地址不配备将丢弃数据 +checkSource=1 +#导出调试数据(包括rtp/ps/h264)至该目录,置空则关闭数据导出 +dumpDir= +#udp和tcp代理服务器,支持rtp(必须是ts或ps类型)代理 +port=10000 +#rtp如果是ts/ps类型则选择MP2P,还可以设置为MP4V-ES +rtp_type=MP2P +#rtp超时时间,单位秒 +timeoutSec=15 + [rtsp] #rtsp专有鉴权方式是采用base64还是md5方式 authBasic=0 diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 9a23da09..64402ad8 100644 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -46,6 +46,7 @@ #include "WebApi.h" #include "WebHook.h" #include "Thread/WorkThreadPool.h" +#include "Rtp/RtpSelector.h" #if !defined(_WIN32) #include "FFmpegSource.h" @@ -395,25 +396,21 @@ void installWebApi() { API_REGIST(api,getMediaList,{ CHECK_SECRET(); //获取所有MediaSource列表 - 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(); val["data"].append(item); }); }); @@ -453,21 +450,17 @@ void installWebApi() { int count_hit = 0; int count_closed = 0; list media_list; - 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; } - if(!allArgs["stream"].empty() && allArgs["stream"] != stream){ + if(!allArgs["stream"].empty() && allArgs["stream"] != media->getId()){ return; } ++count_hit; @@ -700,6 +693,56 @@ void installWebApi() { invoker.responseFile(headerIn,StrCaseMap(),exePath()); }); +#if defined(ENABLE_RTPPROXY) + API_REGIST(api,getSsrcInfo,{ + CHECK_SECRET(); + CHECK_ARGS("ssrc"); + auto process = RtpSelector::Instance().getProcess(allArgs["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_REGIST(api,startRecord,{ + 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["wait_for_record"], + allArgs["continue_record"]); + val["result"] = result; + }); + + // 停止录制hls或MP4 + API_REGIST(api,stopRecord,{ + 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_REGIST(api,getRecordStatus,{ + 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; + }); + ////////////以下是注册的Hook API//////////// API_REGIST(hook,on_publish,{ //开始推流事件 @@ -840,6 +883,12 @@ void installWebApi() { }); + API_REGIST(hook,on_server_started,{ + //服务器重启报告 + throw SuccessException(); + }); + + } void unInstallWebApi(){ diff --git a/server/WebHook.cpp b/server/WebHook.cpp index 1391d10a..b6283025 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); @@ -411,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事件 @@ -459,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 82fbd0b9..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 @@ -235,6 +239,7 @@ int start_main(int argc,char *argv[]) { #endif// #if !defined(_WIN32) + pid_t pid = getpid(); if (bDaemon) { //启动守护进程 System::startDaemon(); @@ -268,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); @@ -278,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 接口"; @@ -321,6 +354,7 @@ int start_main(int argc,char *argv[]) { } unInstallWebApi(); unInstallWebHook(); + Recorder::stopAll(); //休眠1秒再退出,防止资源释放顺序错误 InfoL << "程序退出中,请等待..."; sleep(1); diff --git a/src/Common/MediaSink.cpp b/src/Common/MediaSink.cpp index 4d74e2c9..64049dc5 100644 --- a/src/Common/MediaSink.cpp +++ b/src/Common/MediaSink.cpp @@ -49,14 +49,9 @@ void MediaSink::addTrack(const Track::Ptr &track_in) { _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(!_anyTrackUnReady){ + onTrackFrame(frame); } })); } @@ -111,21 +106,16 @@ void MediaSink::inputFrame(const Frame::Ptr &frame) { } } -bool MediaSink::isAllTrackReady() const { - return _allTrackReady; -} - -Track::Ptr MediaSink::getTrack(TrackType type,bool trackReady) const { +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..64317385 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,18 @@ public: * 只会克隆sps pps这些信息 ,而不会克隆Delegate相关关系 * @param track */ - virtual void addTrack(const Track::Ptr & track); + void addTrack(const Track::Ptr & track) override; /** * 重置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, diff --git a/src/Common/MediaSource.cpp b/src/Common/MediaSource.cpp index cb668f37..036f541c 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,8 +38,155 @@ 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, +MediaSource::~MediaSource() { + unregist(); +} + +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; + weak_ptr weakPtr = shared_from_this(); + EventPollerPool::Instance().getPoller()->async([weakPtr,this](){ + auto strongPtr = weakPtr.lock(); + if (!strongPtr) { + return; + } + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaResetTracks, + _strSchema, + _strVhost, + _strApp, + _strId, + *this); + },false); +} + +void MediaSource::setListener(const std::weak_ptr &listener){ + _listener = listener; +} + +const std::weak_ptr& MediaSource::getListener() const{ + return _listener; +} + +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){ @@ -99,12 +246,17 @@ 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); } + +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, @@ -124,23 +276,22 @@ MediaSource::Ptr MediaSource::find( 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; - }); + 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,28 +306,35 @@ 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); + weak_ptr weakPtr = shared_from_this(); + EventPollerPool::Instance().getPoller()->async([weakPtr,this](){ + auto strongPtr = weakPtr.lock(); + if (!strongPtr) { + return; + } + NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaChanged, + true, + _strSchema, + _strVhost, + _strApp, + _strId, + *this); + },false); } 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){ + return 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()){ + if (strongMedia && this != strongMedia.get()) { //不是自己,不允许反注册 return false; } it2->second.erase(it3); - eraseIfEmpty(it0,it1,it2); + eraseIfEmpty(g_mapMediaSrc, it0, it1, it2); unregisted(); return true; }); @@ -192,6 +350,9 @@ void MediaSource::unregisted(){ *this); } + +/////////////////////////////////////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("://"); @@ -241,6 +402,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 5cfe597d..69b77ccc 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,17 +54,18 @@ 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); }; @@ -92,7 +93,10 @@ public: 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,160 +104,59 @@ 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) ; - - static void findAsync(const MediaInfo &info, - const std::shared_ptr &session, - bool retry, - const function &cb); - - 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; - } - - bool seekTo(uint32_t ui32Stamp) { - auto listener = _listener.lock(); - if(!listener){ - return false; - } - return listener->seekTo(*this,ui32Stamp); - } + // 获取协议类型 + const string& getSchema() const; + // 虚拟主机 + const string& getVhost() const; + // 应用名 + const string& getApp() const; + // 流id + const string& getId() const; + // 获取所有Track + vector getTracks(bool trackReady = true) const override; + // 获取监听者 + const std::weak_ptr& getListener() const; + // 设置TrackSource + void setTrackSource(const std::weak_ptr &track_src); + // 设置监听者 + virtual void setListener(const std::weak_ptr &listener); + // 获取观看者个数 + virtual int readerCount() = 0; + // 获取流当前时间戳 virtual uint32_t getTimeStamp(TrackType trackType) = 0; - bool close(bool force) { - auto listener = _listener.lock(); - if(!listener){ - return false; - } - return listener->close(*this,force); - } + // 拖动进度条 + bool seekTo(uint32_t ui32Stamp); + // 关闭该流 + bool close(bool force); + // 该流无人观看 + void onNoneReader(); - void onNoneReader(){ - auto listener = _listener.lock(); - if(!listener){ - return; - } - listener->onNoneReader(*this); - } + // 同步查找流 + 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); - virtual void setListener(const std::weak_ptr &listener){ - _listener = listener; - } - - std::weak_ptr getListener(){ - return _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; - - /** - * 获取track - * @return - */ - virtual vector getTracks(bool trackReady) const{ - return vector(0); - } 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: - 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; //访问静态的媒体源表的互斥锁 + string _strSchema; + string _strVhost; + string _strApp; + string _strId; + std::weak_ptr _listener; + 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..7c39e5f8 100644 --- a/src/Common/MultiMediaSourceMuxer.h +++ b/src/Common/MultiMediaSourceMuxer.h @@ -29,9 +29,9 @@ #include "Rtsp/RtspMediaSourceMuxer.h" #include "Rtmp/RtmpMediaSourceMuxer.h" -#include "MediaFile/MediaRecorder.h" +#include "Record/Recorder.h" -class MultiMediaSourceMuxer : public FrameWriterInterface{ +class MultiMediaSourceMuxer : public MediaSink , public std::enable_shared_from_this{ public: typedef std::shared_ptr Ptr; @@ -42,58 +42,35 @@ public: bool bEanbleRtsp = true, bool bEanbleRtmp = true, bool bEanbleHls = true, - bool bEnableMp4 = false - ){ + bool bEnableMp4 = false){ if (bEanbleRtmp) { _rtmp = std::make_shared(vhost, strApp, strId, std::make_shared(dur_sec)); } if (bEanbleRtsp) { _rtsp = std::make_shared(vhost, strApp, strId, std::make_shared(dur_sec)); } - _record = std::make_shared(vhost,strApp,strId,bEanbleHls,bEnableMp4); + + if(bEanbleHls){ + Recorder::startRecord(Recorder::type_hls,vhost, strApp, strId, true, false); + } + + if(bEnableMp4){ + Recorder::startRecord(Recorder::type_mp4,vhost, strApp, strId, true, false); + } + } 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); } /** @@ -122,10 +99,50 @@ public: _rtsp->setTimeStamp(stamp); } } + +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(); + } + } private: RtmpMediaSourceMuxer::Ptr _rtmp; RtspMediaSourceMuxer::Ptr _rtsp; - MediaRecorder::Ptr _record; }; diff --git a/src/MediaFile/Stamp.cpp b/src/Common/Stamp.cpp similarity index 93% rename from src/MediaFile/Stamp.cpp rename to src/Common/Stamp.cpp index ab77a1bf..4474fe45 100644 --- a/src/MediaFile/Stamp.cpp +++ b/src/Common/Stamp.cpp @@ -60,8 +60,11 @@ 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){ + //时间戳发生变更 + _relativeStamp += deltaStamp(modifyStamp ? _ticker.elapsedTime() : dts); + _last_dts = dts; + } dts_out = _relativeStamp; //////////////以下是播放时间戳的计算////////////////// diff --git a/src/MediaFile/Stamp.h b/src/Common/Stamp.h similarity index 99% rename from src/MediaFile/Stamp.h rename to src/Common/Stamp.h index 82458fda..09e77338 100644 --- a/src/MediaFile/Stamp.h +++ b/src/Common/Stamp.h @@ -84,6 +84,7 @@ public: int64_t getRelativeStamp() const ; private: int64_t _relativeStamp = 0; + int64_t _last_dts = -1; SmoothTicker _ticker; }; diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 39336e69..d4f08373 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"; @@ -107,8 +108,6 @@ const string kSendBufSize = HTTP_FIELD"sendBufSize"; const string kMaxReqSize = HTTP_FIELD"maxReqSize"; //http keep-alive秒数 const string kKeepAliveSecond = HTTP_FIELD"keepAliveSecond"; -//http keep-alive最大请求数 -const string kMaxReqCount = HTTP_FIELD"maxReqCount"; //http 字符编码 const string kCharSet = HTTP_FIELD"charSet"; //http 服务器根目录 @@ -120,8 +119,6 @@ onceToken token([](){ mINI::Instance()[kSendBufSize] = 64 * 1024; mINI::Instance()[kMaxReqSize] = 4*1024; mINI::Instance()[kKeepAliveSecond] = 15; - mINI::Instance()[kMaxReqCount] = 100; - #if defined(_WIN32) mINI::Instance()[kCharSet] = "gb2312"; #else @@ -278,6 +275,28 @@ onceToken token([](){ },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类型,支持MP2P/MP4V-ES +const string kRtpType = RTP_PROXY_FIELD"rtp_type"; +//rtp接收超时时间 +const string kTimeoutSec = RTP_PROXY_FIELD"timeoutSec"; + +onceToken token([](){ + mINI::Instance()[kDumpDir] = ""; + mINI::Instance()[kCheckSource] = 1; + mINI::Instance()[kRtpType] = "MP2P"; + mINI::Instance()[kTimeoutSec] = 15; +},nullptr); +} //namespace RtpProxy + + namespace Client { const string kNetAdapter = "net_adapter"; const string kRtpType = "rtp_type"; diff --git a/src/Common/config.h b/src/Common/config.h index ba171c99..8eb447b3 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -71,9 +71,13 @@ namespace Broadcast { extern const string kBroadcastMediaChanged; #define BroadcastMediaChangedArgs const bool &bRegist, const string &schema,const string &vhost,const string &app,const string &stream,MediaSource &sender +//MediaSource重置Track事件 +extern const string kBroadcastMediaResetTracks; +#define BroadcastMediaResetTracksArgs const string &schema,const string &vhost,const string &app,const string &stream,MediaSource &sender + //录制mp4文件成功后广播 extern const string kBroadcastRecordMP4; -#define BroadcastRecordMP4Args const Mp4Info &info +#define BroadcastRecordMP4Args const MP4Info &info //收到http api请求广播 extern const string kBroadcastHttpRequest; @@ -155,11 +159,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 ////////////通用配置/////////// @@ -199,8 +198,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 服务器根目录 @@ -299,6 +296,17 @@ 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类型,支持MP2P/MP4V-ES +extern const string kRtpType; +//rtp接收超时时间 +extern const string kTimeoutSec; +} //namespace RtpProxy /** * rtsp/rtmp播放器、推流器相关设置名, diff --git a/src/Extension/Factory.cpp b/src/Extension/Factory.cpp index d65dbb56..4aad5e9f 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); } diff --git a/src/Extension/Frame.h b/src/Extension/Frame.h index 79b5d02a..5e4234f7 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; /** diff --git a/src/Extension/Track.h b/src/Extension/Track.h index 5209c74e..229a13ea 100644 --- a/src/Extension/Track.h +++ b/src/Extension/Track.h @@ -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/HttpFileManager.cpp b/src/Http/HttpFileManager.cpp new file mode 100644 index 00000000..d44f736d --- /dev/null +++ b/src/Http/HttpFileManager.cpp @@ -0,0 +1,511 @@ +/* + * 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" + +namespace mediakit { + +// hls的播放cookie缓存时间默认60秒, +// 每次访问一次该cookie,那么将重新刷新cookie有效期 +// 假如播放器在60秒内都未访问该cookie,那么将重新触发hls播放鉴权 +static int kHlsCookieSecond = 60; +static const string kCookieName = "ZL_COOKIE"; +static const string kCookiePathKey = "kCookiePathKey"; +static const string kAccessErrKey = "kAccessErrKey"; +static const string kAccessHls = "kAccessHls"; +static const string kHlsSuffix = "/hls.m3u8"; + +static const string &getContentType(const char *name) { + const char *dot; + dot = strrchr(name, '.'); + static StrCaseMap 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"); + }); + static string defaultType = "text/plain"; + if (!dot) { + return defaultType; + } + auto it = mapType.find(dot); + 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(BroadcastHttpAccessArgs){ + //访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件 + Broadcast::AuthInvoker mediaAuthInvoker = [invoker,path](const string &err){ + //cookie有效期为kHlsCookieSecond + invoker(err,"",kHlsCookieSecond); + }; + + auto args_copy = args; + replace(args_copy._streamid,kHlsSuffix,""); + return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,args_copy,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 accessErr = (*cookie)[kAccessErrKey].get(); + auto cookiePath = (*cookie)[kCookiePathKey].get(); + auto is_hls = (*cookie)[kAccessHls].get(); + if (path.find(cookiePath) == 0) { + //上次cookie是限定本目录 + if (accessErr.empty()) { + //上次鉴权成功 + if(is_hls){ + //如果播放的是hls,那么刷新hls的cookie + 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(accessErr, cookie_from_header ? nullptr : cookie); + return; + } + } + //如果url参数变了或者不是限定本目录,那么旧cookie失效,重新鉴权 + HttpCookieManager::Instance().delCookie(cookie); + } + + bool is_hls = end_of(path,kHlsSuffix); + //该用户从来未获取过cookie,这个时候我们广播是否允许该用户访问该http目录 + HttpSession::HttpAccessPathInvoker accessPathInvoker = [callback, uid, path, is_dir, is_hls]( 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); + //记录访问的是否为hls + (*cookie)[kAccessHls].set(is_hls); + } + callback(errMsg, cookie); + }; + + if (is_hls && emitHlsPlayed(parser, mediaInfo, path, is_dir, accessPathInvoker, sender)) { + //是hls的播放鉴权,拦截之 + return; + } + + //事件未被拦截,则认为是http下载请求 + bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess, parser, mediaInfo, 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) { + if (!File::is_file(strFile.data())) { + sendNotFound(cb); + return; + } + //判断是否有权限访问该文件 + canAccessPath(sender, parser, mediaInfo, false, [cb, strFile, parser](const string &errMsg, const HttpServerCookie::Ptr &cookie) { + if (!errMsg.empty()) { + StrCaseMap headerOut; + if (cookie) { + headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get()); + } + cb("401 Unauthorized", "text/html", headerOut, std::make_shared(errMsg)); + return; + } + + StrCaseMap httpHeader; + if (cookie) { + httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get()); + } + HttpSession::HttpResponseInvoker invoker = [&](const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) { + cb(codeOut.data(), getContentType(strFile.data()), headerOut, body); + }; + invoker.responseFile(parser.getValues(), httpHeader, strFile); + }); +} + +/** + * 访问文件或文件夹 + * @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); + + GET_CONFIG(bool, enableVhost, General::kEnableVhost); + GET_CONFIG(string, rootPath, Http::kRootPath); + auto strFile = File::absolutePath(enableVhost ? mediaInfo._vhost + parser.Url() : parser.Url(), rootPath); + + //访问的是文件夹 + 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)[kCookiePathKey].get()); + } + 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..4421431a --- /dev/null +++ b/src/Http/HttpFileManager.h @@ -0,0 +1,80 @@ +/* + * 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); +private: + HttpFileManager() = delete; + ~HttpFileManager() = delete; +}; + +} + + +#endif //ZLMEDIAKIT_HTTPFILEMANAGER_H diff --git a/src/Http/HttpSession.cpp b/src/Http/HttpSession.cpp index 10d6b08d..88fa99e0 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())); } @@ -324,12 +168,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 +194,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 +207,6 @@ bool HttpSession::checkLiveFlvStream(const function &cb){ if(!rtmp_src){ //未找到该流 sendNotFound(bClose); - if(bClose){ - shutdown(SockException(Err_shutdown,"flv stream not found")); - } return; } //找到流了 @@ -375,7 +214,6 @@ 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 ; } @@ -421,158 +259,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,185 +282,40 @@ 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; } - void HttpSession::sendResponse(const char *pcStatus, bool bClose, const char *pcContentType, const HttpSession::KeyValue &header, const HttpBody::Ptr &body, bool set_content_len ){ - 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; @@ -798,7 +339,7 @@ void HttpSession::sendResponse(const char *pcStatus, headerOut.emplace("Server", SERVER_NAME); headerOut.emplace("Connection", bClose ? "close" : "keep-alive"); if(!bClose){ - headerOut.emplace("Keep-Alive",StrPrinter << "timeout=" << keepAliveSec << ", max=" << reqCnt << endl); + headerOut.emplace("Keep-Alive",StrPrinter << "timeout=" << keepAliveSec << ", max=100" << endl); } if(!_origin.empty()){ @@ -837,7 +378,7 @@ void HttpSession::sendResponse(const char *pcStatus, 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; } @@ -846,7 +387,8 @@ void HttpSession::sendResponse(const char *pcStatus, GET_CONFIG(uint32_t,sendBufSize,Http::kSendBufSize); weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); - auto onFlush = [body,bClose,weakSelf]() { + string status_code = pcStatus; + auto onFlush = [body,bClose,weakSelf,status_code]() { auto strongSelf = weakSelf.lock(); if(!strongSelf){ //本对象已经销毁 @@ -883,7 +425,7 @@ void HttpSession::sendResponse(const char *pcStatus, if(bClose) { //最后一次flush事件,文件也发送完毕了,可以关闭socket了 - strongSelf->shutdown(SockException(Err_shutdown,"close connection after send http body completed")); + strongSelf->shutdown(SockException(Err_shutdown, StrPrinter << "close connection after send http body completed with status code:" << status_code)); } //不再监听onFlush事件 return false; @@ -917,10 +459,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 +473,6 @@ bool HttpSession::emitHttpEvent(bool doInvoke){ //本对象已经销毁 return; } - - if(codeOut.empty()){ - //回调提供的参数异常 - strongSelf->sendNotFound(bClose); - return; - } - strongSelf->sendResponse(codeOut.data(), bClose, nullptr, headerOut, body); }); }; @@ -950,17 +482,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 +527,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..207b061b 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, @@ -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 ; @@ -145,26 +116,6 @@ private: 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(); //设置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/WebSocketSession.h b/src/Http/WebSocketSession.h index fe4d443a..682a2559 100644 --- a/src/Http/WebSocketSession.h +++ b/src/Http/WebSocketSession.h @@ -28,6 +28,7 @@ #define ZLMEDIAKIT_WEBSOCKETSESSION_H #include "HttpSession.h" +#include "Network/TcpServer.h" /** * 通过该模板类可以透明化WebSocket协议, 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/MediaFile/MediaRecorder.h b/src/MediaFile/MediaRecorder.h deleted file mode 100644 index c36f8613..00000000 --- a/src/MediaFile/MediaRecorder.h +++ /dev/null @@ -1,79 +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_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; - -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: -#if defined(ENABLE_HLS) - std::shared_ptr _hlsRecorder; -#endif //defined(ENABLE_HLS) - -#if defined(ENABLE_MP4RECORD) - std::shared_ptr _mp4Recorder; -#endif //defined(ENABLE_MP4RECORD) -}; - -} /* namespace mediakit */ - -#endif /* SRC_MEDIAFILE_MEDIARECORDER_H_ */ 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..7a66a0ee 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,7 +104,7 @@ vector Demuxer::getTracks(bool trackReady) const { ret.emplace_back(_audioTrack); } } - return ret; + return std::move(ret); } float Demuxer::getDuration() const { diff --git a/src/Player/PlayerBase.h b/src/Player/PlayerBase.h index 50330b06..7e8f55ff 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,7 +234,7 @@ protected: function _shutdownCB; function _playResultCB; function _resumeCB; - std::shared_ptr _parser; + std::shared_ptr _delegate; MediaSource::Ptr _pMediaSrc; }; @@ -282,14 +257,14 @@ 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; protected: diff --git a/src/Player/PlayerProxy.cpp b/src/Player/PlayerProxy.cpp index 075394c9..766db316 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); 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 96% rename from src/MediaFile/HlsMaker.cpp rename to src/Record/HlsMaker.cpp index 3788f139..8c5b2994 100644 --- a/src/MediaFile/HlsMaker.cpp +++ b/src/Record/HlsMaker.cpp @@ -110,8 +110,8 @@ void HlsMaker::addNewSegment(uint32_t) { return; } - //关闭并保存上一个切片 - flushLastSegment(); + //关闭并保存上一个切片,如果_seg_number==0,那么是点播。 + flushLastSegment(_seg_number == 0); //新增切片 _last_file_name = onOpenSegment(_file_index++); //重置切片计时器 diff --git a/src/MediaFile/HlsMaker.h b/src/Record/HlsMaker.h similarity index 100% rename from src/MediaFile/HlsMaker.h rename to src/Record/HlsMaker.h diff --git a/src/MediaFile/HlsMakerImp.cpp b/src/Record/HlsMakerImp.cpp similarity index 100% rename from src/MediaFile/HlsMakerImp.cpp rename to src/Record/HlsMakerImp.cpp diff --git a/src/MediaFile/HlsMakerImp.h b/src/Record/HlsMakerImp.h similarity index 100% rename from src/MediaFile/HlsMakerImp.h rename to src/Record/HlsMakerImp.h diff --git a/src/MediaFile/HlsRecorder.h b/src/Record/HlsRecorder.h similarity index 75% rename from src/MediaFile/HlsRecorder.h rename to src/Record/HlsRecorder.h index ca20c5f0..c0def9bb 100644 --- a/src/MediaFile/HlsRecorder.h +++ b/src/Record/HlsRecorder.h @@ -32,15 +32,23 @@ namespace mediakit { -class HlsRecorder : public HlsMakerImp, public TsMuxer { +class HlsRecorder : public TsMuxer { public: - template - HlsRecorder(ArgsType &&...args):HlsMakerImp(std::forward(args)...){} - ~HlsRecorder(){}; + 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; + } protected: void onTs(const void *packet, int bytes,uint32_t timestamp,int flags) override { - inputData((char *)packet,bytes,timestamp); + _hls->inputData((char *)packet,bytes,timestamp); }; +private: + HlsMakerImp *_hls; }; }//namespace mediakit diff --git a/src/MediaFile/MP4Muxer.cpp b/src/Record/MP4Muxer.cpp similarity index 95% rename from src/MediaFile/MP4Muxer.cpp rename to src/Record/MP4Muxer.cpp index c1b5bad3..70d6e38c 100644 --- a/src/MediaFile/MP4Muxer.cpp +++ b/src/Record/MP4Muxer.cpp @@ -67,8 +67,12 @@ void MP4MuxerBase::init(int flags) { } /////////////////////////////////// +void MP4Muxer::resetTracks() { + _codec_to_trackid.clear(); + _started = false; +} -void MP4Muxer::onTrackFrame(const Frame::Ptr &frame) { +void MP4Muxer::inputFrame(const Frame::Ptr &frame) { if(frame->configFrame()){ //忽略配置帧 return; @@ -117,7 +121,7 @@ void MP4Muxer::onTrackFrame(const Frame::Ptr &frame) { with_nalu_size); } -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); @@ -210,7 +214,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 +246,6 @@ MP4MuxerFile::MP4MuxerFile(const char *file) { }); GET_CONFIG(bool, mp4FastStart, Record::kFastStart); - init(mp4FastStart ? MOV_FLAG_FASTSTART : 0); } @@ -264,6 +272,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 86% rename from src/MediaFile/MP4Muxer.h rename to src/Record/MP4Muxer.h index de15d4cc..401431d2 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,23 +57,24 @@ 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; @@ -89,13 +90,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 88% rename from src/MediaFile/MediaReader.cpp rename to src/Record/MP4Reader.cpp index eae430bd..b082234d 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()){ @@ -153,7 +154,7 @@ MediaReader::MediaReader(const string &strVhost,const string &strApp, const stri } -MediaReader::~MediaReader() { +MP4Reader::~MP4Reader() { if (_hMP4File != MP4_INVALID_FILE_HANDLE) { MP4Close(_hMP4File); _hMP4File = MP4_INVALID_FILE_HANDLE; @@ -161,7 +162,7 @@ MediaReader::~MediaReader() { } -void MediaReader::startReadMP4() { +void MP4Reader::startReadMP4() { auto strongSelf = shared_from_this(); GET_CONFIG(uint32_t,sampleMS,Record::kSampleMS); @@ -173,11 +174,11 @@ 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){ +bool MP4Reader::close(MediaSource &sender,bool force){ if(!_mediaMuxer || (!force && _mediaMuxer->readerCount() != 0)){ return false; } @@ -186,14 +187,14 @@ bool MediaReader::close(MediaSource &sender,bool force){ return true; } -void MediaReader::onNoneReader(MediaSource &sender) { +void MP4Reader::onNoneReader(MediaSource &sender) { if(!_mediaMuxer || _mediaMuxer->readerCount() != 0){ return; } MediaSourceEvent::onNoneReader(sender); } -bool MediaReader::readSample(int iTimeInc,bool justSeekSyncFrame) { +bool MP4Reader::readSample(int iTimeInc,bool justSeekSyncFrame) { TimeTicker(); lock_guard lck(_mtx); auto bFlag0 = readVideoSample(iTimeInc,justSeekSyncFrame);//数据没读完 @@ -211,14 +212,15 @@ 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)){ + MP4Duration pRenderingOffset; + if(MP4ReadSample(_hMP4File, _video_trId, iIdx + 1, &pBytes, &numBytes,NULL,NULL,&pRenderingOffset,&_bSyncSample)){ if (!justSeekSyncFrame) { uint32_t iOffset = 0; while (iOffset < numBytes) { @@ -229,7 +231,8 @@ 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); + uint32_t dts = (double) _video_ms * iIdx / _video_num_samples; + writeH264(pBytes + iOffset, iFrameLen + 4, dts, dts + pRenderingOffset / 90); iOffset += (iFrameLen + 4); } }else if(_bSyncSample){ @@ -245,7 +248,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++) { @@ -267,27 +270,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 +301,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 +334,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 +346,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 86% rename from src/MediaFile/MediaReader.h rename to src/Record/MP4Reader.h index a2917367..576d0ed2 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,10 +49,10 @@ 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(); @@ -64,13 +64,13 @@ public: bool seekTo(MediaSource &sender,uint32_t ui32Stamp) override; /** - * 关闭MediaReader的流化进程,会触发该对象放弃自持有 + * 关闭MP4Reader的流化进程,会触发该对象放弃自持有 * @return */ bool close(MediaSource &sender,bool force) override; /** - * 自动生成MediaReader对象然后查找相关的MediaSource对象 + * 自动生成MP4Reader对象然后查找相关的MediaSource对象 * @param strSchema 协议名 * @param strVhost 虚拟主机 * @param strApp 应用名 diff --git a/src/MediaFile/MP4Recorder.cpp b/src/Record/MP4Recorder.cpp similarity index 94% rename from src/MediaFile/MP4Recorder.cpp rename to src/Record/MP4Recorder.cpp index 0f36561c..e0332a56 100644 --- a/src/MediaFile/MP4Recorder.cpp +++ b/src/Record/MP4Recorder.cpp @@ -107,7 +107,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 +115,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 +128,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 +145,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 +158,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..3204fb51 100644 --- a/src/MediaFile/MP4Recorder.h +++ b/src/Record/MP4Recorder.h @@ -42,7 +42,7 @@ using namespace toolkit; namespace mediakit { -class Mp4Info { +class MP4Info { public: time_t ui64StartedTime; //GMT标准时间,单位秒 time_t ui64TimeLen;//录像长度,单位秒 @@ -55,32 +55,31 @@ public: string strStreamId;//流ID string strVhost;//vhost }; -class MP4Recorder : public MediaSink{ + +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,7 +89,7 @@ private: string _strFile; string _strFileTmp; Ticker _createFileTicker; - Mp4Info _info; + MP4Info _info; bool _haveVideo = false; MP4MuxerFile::Ptr _muxer; list _tracks; diff --git a/src/Record/Recorder.cpp b/src/Record/Recorder.cpp new file mode 100644 index 00000000..42d145e3 --- /dev/null +++ b/src/Record/Recorder.cpp @@ -0,0 +1,363 @@ +/* +* 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 { + +MediaSinkInterface *createHlsRecorder(const string &strVhost_tmp, const string &strApp, const string &strId) { +#if defined(ENABLE_HLS) + GET_CONFIG(bool, enableVhost, General::kEnableVhost); + GET_CONFIG(string, hlsPath, Hls::kFilePath); + + string strVhost = strVhost_tmp; + if (trim(strVhost).empty()) { + //如果strVhost为空,则强制为默认虚拟主机 + strVhost = DEFAULT_VHOST; + } + + 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); + return new HlsRecorder(m3u8FilePath, params); +#else + return nullptr; +#endif //defined(ENABLE_HLS) +} + +MediaSinkInterface *createMP4Recorder(const string &strVhost_tmp, const string &strApp, const string &strId) { +#if defined(ENABLE_MP4RECORD) + GET_CONFIG(bool, enableVhost, General::kEnableVhost); + GET_CONFIG(string, recordPath, Record::kFilePath); + GET_CONFIG(string, recordAppName, Record::kAppName); + + string strVhost = strVhost_tmp; + if (trim(strVhost).empty()) { + //如果strVhost为空,则强制为默认虚拟主机 + strVhost = DEFAULT_VHOST; + } + + string mp4FilePath; + if (enableVhost) { + mp4FilePath = strVhost + "/" + recordAppName + "/" + strApp + "/" + strId + "/"; + } else { + mp4FilePath = recordAppName + "/" + strApp + "/" + strId + "/"; + } + mp4FilePath = File::absolutePath(mp4FilePath, recordPath); + return new MP4Recorder(mp4FilePath, strVhost, strApp, strId); +#else + return nullptr; +#endif //defined(ENABLE_MP4RECORD) +} + +//////////////////////////////////////////////////////////////////////////////////////// + +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; + } +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)); + } + + int startRecord(const string &vhost, const string &app, const string &stream_id, 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; + } + + string schema; + auto tracks = findTracks(vhost, app, stream_id,schema); + if (!waitForRecord && tracks.empty()) { + // 暂时无法开启录制 + return -1; + } + + auto recorder = MediaSinkInterface::Ptr(createRecorder(vhost, app, stream_id)); + if (!recorder) { + // 创建录制器失败 + return -2; + } + auto helper = std::make_shared(recorder, continueRecord); + if(tracks.size()){ + helper->attachTracks(std::move(tracks),schema); + } + _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::Instance().addListener(this,Broadcast::kBroadcastMediaChanged,[this](BroadcastMediaChangedArgs){ + if(bRegist){ + onRegist(schema,vhost,app,stream,sender); + }else{ + onUnRegist(schema,vhost,app,stream,sender); + } + }); + NoticeCenter::Instance().addListener(this,Broadcast::kBroadcastMediaResetTracks,[this](BroadcastMediaResetTracksArgs){ + onRegist(schema,vhost,app,stream,sender); + }); + } + + ~MediaSourceWatcher(){ + NoticeCenter::Instance().delListener(this,Broadcast::kBroadcastMediaChanged); + NoticeCenter::Instance().delListener(this,Broadcast::kBroadcastMediaResetTracks); + } + + void onRegist(const string &schema,const string &vhost,const string &app,const string &stream,MediaSource &sender){ + auto key = getRecorderKey(vhost,app,stream); + lock_guard lck(_recorder_mtx); + auto it = _recorder_map.find(key); + if(it == _recorder_map.end()){ + // 录像记录不存在 + return; + } + + if(!it->second->isRecording() || it->second->getSchema() == schema){ + // 绑定的协议一致或者并未正在录制则替换tracks + auto tracks = sender.getTracks(true); + if (!tracks.empty()) { + it->second->attachTracks(std::move(tracks),schema); + } + } + } + + void onUnRegist(const string &schema,const string &vhost,const string &app,const string &stream,MediaSource &sender){ + auto key = getRecorderKey(vhost,app,stream); + lock_guard lck(_recorder_mtx); + auto it = _recorder_map.find(key); + if(it == _recorder_map.end() || it->second->getSchema() != schema){ + // 录像记录不存在或绑定的协议不一致 + 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以便录制 + vector findTracks(const string &vhost, const string &app, const string &stream_id,string &schema) { + auto src = MediaSource::find(RTMP_SCHEMA, vhost, app, stream_id); + if (src) { + auto ret = src->getTracks(true); + if (!ret.empty()) { + schema = RTMP_SCHEMA; + return std::move(ret); + } + } + + src = MediaSource::find(RTSP_SCHEMA, vhost, app, stream_id); + if (src) { + schema = RTSP_SCHEMA; + return src->getTracks(true); + } + return vector(); + } + + string getRecorderKey(const string &vhost, const string &app, const string &stream_id) { + return vhost + "/" + app + "/" + stream_id; + } + + MediaSinkInterface *createRecorder(const string &vhost, const string &app, const string &stream_id) { + MediaSinkInterface *ret = nullptr; + switch (type) { + case Recorder::type_hls: + ret = createHlsRecorder(vhost, app, stream_id); + break; + case Recorder::type_mp4: + ret = createMP4Recorder(vhost, app, stream_id); + break; + default: + break; + } + if(!ret){ + WarnL << "can not create recorder of type: " << type; + } + return ret; + } +private: + recursive_mutex _recorder_mtx; + 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; +} + +int Recorder::startRecord(Recorder::type type, const string &vhost, const string &app, const string &stream_id, bool waitForRecord, bool continueRecord) { + switch (type){ + case type_mp4: + return MediaSourceWatcher::Instance().startRecord(vhost,app,stream_id,waitForRecord,continueRecord); + case type_hls: + return MediaSourceWatcher::Instance().startRecord(vhost,app,stream_id,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..6172cabd --- /dev/null +++ b/src/Record/Recorder.h @@ -0,0 +1,98 @@ +/* + * 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 + * @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 waitForRecord 是否等待流注册后再录制,未注册时,置false将返回失败 + * @param continueRecord 流注销时是否继续等待录制还是立即停止录制 + * @return 0代表成功,负数代表失败 + */ + static int startRecord(type type, const string &vhost, const string &app, const string &stream_id,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(); +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 100% rename from src/MediaFile/TsMuxer.cpp rename to src/Record/TsMuxer.cpp diff --git a/src/MediaFile/TsMuxer.h b/src/Record/TsMuxer.h similarity index 96% rename from src/MediaFile/TsMuxer.h rename to src/Record/TsMuxer.h index a876c8c3..26b262cc 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(); 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/RtmpMediaSourceMuxer.h b/src/Rtmp/RtmpMediaSourceMuxer.h index e3d9561d..55112dea 100644 --- a/src/Rtmp/RtmpMediaSourceMuxer.h +++ b/src/Rtmp/RtmpMediaSourceMuxer.h @@ -48,13 +48,19 @@ public: void setListener(const std::weak_ptr &listener){ _mediaSouce->setListener(listener); } + int readerCount() const{ return _mediaSouce->readerCount(); } -private: - void onAllTrackReady() override { + + void onAllTrackReady(){ _mediaSouce->onGetMetaData(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..e252d410 100644 --- a/src/Rtmp/RtmpMuxer.cpp +++ b/src/Rtmp/RtmpMuxer.cpp @@ -38,13 +38,7 @@ RtmpMuxer::RtmpMuxer(const TitleMeta::Ptr &title) { _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,26 +51,34 @@ 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); + } +} const AMFValue &RtmpMuxer::getMetadata() const { - if(!isAllTrackReady()){ - //尚未就绪 - static AMFValue s_amf; - return s_amf; - } return _metadata; } @@ -84,4 +86,12 @@ RtmpRingInterface::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..3c4743b9 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; @@ -55,16 +55,26 @@ public: * @return */ RtmpRingInterface::RingType::Ptr getRtmpRing() const; -protected: + /** - * 某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: RtmpRingInterface::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..b170a19f 100644 --- a/src/Rtmp/RtmpPlayerImp.h +++ b/src/Rtmp/RtmpPlayerImp.h @@ -67,18 +67,18 @@ private: if(_pRtmpMediaSrc){ _pRtmpMediaSrc->onGetMetaData(val); } - _parser.reset(new RtmpDemuxer(val)); + _delegate.reset(new RtmpDemuxer(val)); return true; } void onMediaData(const RtmpPacket::Ptr &chunkData) override { if(_pRtmpMediaSrc){ _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; diff --git a/src/Rtmp/RtmpSession.cpp b/src/Rtmp/RtmpSession.cpp index e946d275..a9f62916 100644 --- a/src/Rtmp/RtmpSession.cpp +++ b/src/Rtmp/RtmpSession.cpp @@ -319,7 +319,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){ @@ -493,7 +493,7 @@ 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()){ diff --git a/src/Rtmp/RtmpSession.h b/src/Rtmp/RtmpSession.h index a891b49b..9eac1e35 100644 --- a/src/Rtmp/RtmpSession.h +++ b/src/Rtmp/RtmpSession.h @@ -37,7 +37,7 @@ #include "Util/util.h" #include "Util/TimeTicker.h" #include "Network/TcpSession.h" -#include "MediaFile/Stamp.h" +#include "Common/Stamp.h" using namespace toolkit; diff --git a/src/Rtmp/RtmpToRtspMediaSource.h b/src/Rtmp/RtmpToRtspMediaSource.h index 4d7c864b..31df804f 100644 --- a/src/Rtmp/RtmpToRtspMediaSource.h +++ b/src/Rtmp/RtmpToRtspMediaSource.h @@ -52,7 +52,8 @@ public: RtmpToRtspMediaSource(const string &vhost, const string &app, const string &id, - int ringSize = 0) : RtmpMediaSource(vhost, app, id,ringSize){ + int ringSize = 0) : + RtmpMediaSource(vhost, app, id,ringSize){ } virtual ~RtmpToRtspMediaSource(){} @@ -83,7 +84,7 @@ public: _muxer->addTrack(track); track->addDelegate(_muxer); } - _muxer->setListener(_listener); + _muxer->setListener(getListener()); } RtmpMediaSource::onWrite(pkt,key_pos); } diff --git a/src/Rtp/PSDecoder.cpp b/src/Rtp/PSDecoder.cpp new file mode 100644 index 00000000..47abb768 --- /dev/null +++ b/src/Rtp/PSDecoder.cpp @@ -0,0 +1,57 @@ +/* + * 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; + thiz->onPSDecode(stream, codecid, flags, pts, dts, data, bytes); + },this); +} + +PSDecoder::~PSDecoder() { + ps_demuxer_destroy((struct ps_demuxer_t*)_ps_demuxer); +} + +int PSDecoder::decodePS(const uint8_t *data, int bytes) { + return ps_demuxer_input((struct ps_demuxer_t*)_ps_demuxer,data,bytes); +} + +}//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..c8a73872 --- /dev/null +++ b/src/Rtp/PSDecoder.h @@ -0,0 +1,55 @@ +/* + * 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 + +namespace mediakit{ + +class PSDecoder { +public: + PSDecoder(); + virtual ~PSDecoder(); + int decodePS(const uint8_t *data, int bytes); +protected: + virtual void onPSDecode(int stream, + int codecid, + int flags, + int64_t pts, + int64_t dts, + const void *data, + int bytes) = 0; +private: + void *_ps_demuxer = nullptr; +}; + +}//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..5f78cd7c --- /dev/null +++ b/src/Rtp/RtpDecoder.cpp @@ -0,0 +1,78 @@ +/* + * 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 char *type_name) { + 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(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, &s_func, this); + if (!_rtp_decoder) { + WarnL << "unsupported rtp type:" << (int) rtp_type << ",size:" << bytes << ",hexdump" << hexdump(data, bytes > 16 ? 16 : bytes); + } + } + 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..845dbdbd --- /dev/null +++ b/src/Rtp/RtpDecoder.h @@ -0,0 +1,50 @@ +/* + * 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 char *type_name); + virtual void onRtpDecode(const void *packet, int bytes, uint32_t timestamp, int flags) = 0; +private: + void *_rtp_decoder = nullptr; + BufferRaw::Ptr _buffer; +}; + +}//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..b19c43c9 --- /dev/null +++ b/src/Rtp/RtpProcess.cpp @@ -0,0 +1,268 @@ +/* + * 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{ + +/** + * 合并一些时间戳相同的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; + DebugL << printSSRC(_ssrc); + _muxer = std::make_shared(DEFAULT_VHOST,"rtp",printSSRC(_ssrc)); + + 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) { + 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(); + return handleOneRtp(0,_track,(unsigned char *)data,data_len); +} + +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()); + } + + GET_CONFIG(string,rtp_type,::RtpProxy::kRtpType); + decodeRtp(rtp->data() + 4 ,rtp->size() - 4,rtp_type.data()); +} + +void RtpProcess::onRtpDecode(const void *packet, int bytes, uint32_t, int flags) { + if(_save_file_ps){ + fwrite((uint8_t *)packet,bytes, 1, _save_file_ps.get()); + } + + auto ret = decodePS((uint8_t *)packet,bytes); + if(ret != bytes){ + WarnL << ret << " != " << bytes << " " << flags; + } +} + +void RtpProcess::onPSDecode(int stream, + int codecid, + int flags, + int64_t pts, + int64_t dts, + const void *data, + int bytes) { + + 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:" << _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 / 90, pts / 90,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:" << _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 / 90, pts / 90, 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:" << _codecid_audio; + return; + } + _muxer->inputFrame(std::make_shared((char *) data, bytes, dts / 90, 7)); + break; + } + default: + WarnL << "unsupported codec type:" << 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); +} + +}//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..13ddfbeb --- /dev/null +++ b/src/Rtp/RtpProcess.h @@ -0,0 +1,79 @@ +/* + * 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 "PSDecoder.h" +#include "Common/Device.h" +using namespace mediakit; + +namespace mediakit{ + +string printSSRC(uint32_t ui32Ssrc); + +class FrameMerger; +class RtpProcess : public RtpReceiver , public RtpDecoder , public PSDecoder { +public: + typedef std::shared_ptr Ptr; + RtpProcess(uint32_t ssrc); + ~RtpProcess(); + bool inputRtp(const char *data,int data_len, const struct sockaddr *addr); + bool alive(); + string get_peer_ip(); + uint16_t get_peer_port(); +protected: + void onRtpSorted(const RtpPacket::Ptr &rtp, int track_index) override ; + void onRtpDecode(const void *packet, int bytes, uint32_t timestamp, int flags) override; + void onPSDecode(int stream, + int codecid, + int flags, + int64_t pts, + int64_t dts, + const void *data, + int bytes) override ; +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; +}; + +}//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..2ff997a7 --- /dev/null +++ b/src/Rtp/RtpSelector.cpp @@ -0,0 +1,105 @@ +/* + * 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) { + if(_last_rtp_time.elapsedTime() > 3000){ + _last_rtp_time.resetTime(); + onManager(); + } + auto ssrc = getSSRC(data,data_len); + if(!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); + } + return false; +} + +uint32_t RtpSelector::getSSRC(const char *data, int data_len) { + if(data_len < 12){ + return 0; + } + uint32_t *ssrc_ptr = (uint32_t *)(data + 8); + return ntohl(*ssrc_ptr); +} + +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; + } + RtpProcess::Ptr &ref = _map_rtp_process[ssrc]; + if(!ref){ + ref = std::make_shared(ssrc); + } + return ref; +} + +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.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->alive()) { + ++it; + continue; + } + WarnL << "RtpProcess timeout:" << printSSRC(it->first); + it = _map_rtp_process.erase(it); + } +} + +RtpSelector::RtpSelector() { +} + +RtpSelector::~RtpSelector() { +} + +}//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..872d3ada --- /dev/null +++ b/src/Rtp/RtpSelector.h @@ -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. + */ + +#ifndef ZLMEDIAKIT_RTPSELECTOR_H +#define ZLMEDIAKIT_RTPSELECTOR_H + +#if defined(ENABLE_RTPPROXY) +#include +#include +#include +#include "RtpProcess.h" + +namespace mediakit{ + +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); + static uint32_t getSSRC(const char *data,int data_len); + RtpProcess::Ptr getProcess(uint32_t ssrc,bool makeNew); + void delProcess(uint32_t ssrc,const RtpProcess *ptr); +private: + void onManager(); +private: + unordered_map _map_rtp_process; + recursive_mutex _mtx_map; + Ticker _last_rtp_time; +}; + +}//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..c765997f --- /dev/null +++ b/src/Rtp/RtpSession.cpp @@ -0,0 +1,78 @@ +/* + * 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(_ssrc){ + 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(!_ssrc){ + _ssrc = RtpSelector::getSSRC(data + 2,len - 2); + _process = RtpSelector::Instance().getProcess(_ssrc, true); + } + _process->inputRtp(data + 2,len - 2,&addr); + _ticker.resetTime(); +} + +}//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..ff8771a1 --- /dev/null +++ b/src/Rtp/RtpSession.h @@ -0,0 +1,57 @@ +/* + * 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: + RtpSession(const Socket::Ptr &sock); + ~RtpSession() override; + void onRecv(const Buffer::Ptr &) override; + void onError(const SockException &err) override; + void onManager() override; +private: + 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/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/RtspMediaSourceMuxer.h b/src/Rtsp/RtspMediaSourceMuxer.h index f4637942..050e83c2 100644 --- a/src/Rtsp/RtspMediaSourceMuxer.h +++ b/src/Rtsp/RtspMediaSourceMuxer.h @@ -48,16 +48,23 @@ 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 { + + void onAllTrackReady(){ _mediaSouce->onGetSDP(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..1e482add 100644 --- a/src/Rtsp/RtspMuxer.cpp +++ b/src/Rtsp/RtspMuxer.cpp @@ -38,29 +38,34 @@ RtspMuxer::RtspMuxer(const TitleSdp::Ptr &title){ _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; } @@ -68,5 +73,12 @@ RtpRingInterface::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..2d9a8736 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; @@ -57,16 +57,26 @@ public: * @return */ RtpRingInterface::RingType::Ptr getRtpRing() const; -protected: + /** - * 某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]; + RtpRingInterface::RingType::Ptr _rtpRing; }; diff --git a/src/Rtsp/RtspPlayer.cpp b/src/Rtsp/RtspPlayer.cpp index aa7dfce3..10a1f7e3 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<onPlayResult_l(SockException(Err_timeout,"play rtsp timeout")); + strongSelf->onPlayResult_l(SockException(Err_timeout,"play rtsp timeout"),false); return false; },getPoller())); @@ -158,8 +163,8 @@ void RtspPlayer::play(bool isSSL,const string &strUrl, const string &strUser, co startConnect(ip, 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; } @@ -170,7 +175,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) { @@ -403,8 +409,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()); }); } } @@ -416,14 +421,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() { @@ -432,46 +430,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) { @@ -483,8 +502,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); } } @@ -674,7 +693,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) { @@ -764,7 +783,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){ @@ -772,33 +791,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){ @@ -806,21 +823,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; } @@ -830,6 +841,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..8247dc9d 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,9 +101,8 @@ 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; @@ -111,12 +110,11 @@ private: 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..8992bded 100644 --- a/src/Rtsp/RtspPlayerImp.h +++ b/src/Rtsp/RtspPlayerImp.h @@ -66,28 +66,38 @@ private: if(_pRtspMediaSrc){ _pRtspMediaSrc->onGetSDP(sdp); } - _parser.reset(new RtspDemuxer(sdp)); + _delegate.reset(new RtspDemuxer(sdp)); return true; } void onRecvRTP(const RtpPacket::Ptr &rtp, const SdpTrack::Ptr &track) override { if(_pRtspMediaSrc){ _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..e40a185b 100644 --- a/src/Rtsp/RtspPusher.cpp +++ b/src/Rtsp/RtspPusher.cpp @@ -351,6 +351,9 @@ inline int RtspPusher::getTrackIndexByTrackType(TrackType type) { return i; } } + if(_aTrackInfo.size() == 1){ + return 0; + } return -1; } diff --git a/src/Rtsp/RtspSession.cpp b/src/Rtsp/RtspSession.cpp index 14e2b89a..1d679e23 100644 --- a/src/Rtsp/RtspSession.cpp +++ b/src/Rtsp/RtspSession.cpp @@ -371,7 +371,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; @@ -940,7 +940,7 @@ 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); + _stamp[trackidx].revise(rtppt->timeStamp, rtppt->timeStamp, dts_out, dts_out, true); rtppt->timeStamp = dts_out; } _pushSrc->onWrite(rtppt, false); @@ -1105,6 +1105,9 @@ inline int RtspSession::getTrackIndexByTrackType(TrackType type) { return i; } } + if(_aTrackInfo.size() == 1){ + return 0; + } return -1; } inline int RtspSession::getTrackIndexByControlSuffix(const string &controlSuffix) { @@ -1125,6 +1128,9 @@ inline int RtspSession::getTrackIndexByInterleaved(int interleaved){ return i; } } + if(_aTrackInfo.size() == 1){ + return 0; + } return -1; } diff --git a/src/Rtsp/RtspSession.h b/src/Rtsp/RtspSession.h index 33637666..6ffa550a 100644 --- a/src/Rtsp/RtspSession.h +++ b/src/Rtsp/RtspSession.h @@ -41,6 +41,7 @@ #include "RtspSplitter.h" #include "RtpReceiver.h" #include "RtspToRtmpMediaSource.h" +#include "Common/Stamp.h" using namespace std; using namespace toolkit; diff --git a/src/Rtsp/RtspToRtmpMediaSource.h b/src/Rtsp/RtspToRtmpMediaSource.h index a05cb393..c4cdaf18 100644 --- a/src/Rtsp/RtspToRtmpMediaSource.h +++ b/src/Rtsp/RtspToRtmpMediaSource.h @@ -43,7 +43,8 @@ public: RtspToRtmpMediaSource(const string &vhost, const string &app, const string &id, - int ringSize = 0) : RtspMediaSource(vhost, app, id,ringSize) { + int ringSize = 0) + : RtspMediaSource(vhost, app, id,ringSize) { } virtual ~RtspToRtmpMediaSource() {} @@ -69,7 +70,7 @@ public: _muxer->addTrack(track); track->addDelegate(_muxer); } - _muxer->setListener(_listener); + _muxer->setListener(getListener()); } } RtspMediaSource::onWrite(rtp, bKeyPos); 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_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..1be10ceb --- /dev/null +++ b/tests/test_rtp.cpp @@ -0,0 +1,113 @@ +/* + * 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]; + 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; + memcpy(&timeStamp, rtp + 4, 4); + timeStamp = ntohl(timeStamp); + timeStamp /= 90; + + if(timeStamp_last){ + auto diff = timeStamp - timeStamp_last; + if(diff > 0){ + usleep(diff * 1000); + } + } + + timeStamp_last = timeStamp; + + RtpSelector::Instance().inputRtp(rtp,len, nullptr); + } + 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 + 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..27c9c86c 100644 --- a/tests/test_server.cpp +++ b/tests/test_server.cpp @@ -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;