commit
2f12a66372
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 40edf6243d9d99676062062efdec203b24a178aa
|
||||
Subproject commit 4325386be318889b7815cdd86a2e83f05a059c81
|
||||
159
CMakeLists.txt
159
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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,8 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
||||
Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
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
|
||||
|
|
|
|||
|
|
@ -331,6 +331,8 @@ docker build -t zlmediakit .
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
||||
Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
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.
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
|
|
|||
16
README_CN.md
16
README_CN.md
|
|
@ -2,6 +2,13 @@
|
|||
# 一个基于C++11的高性能运营级流媒体服务框架
|
||||
[](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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<MediaSource::Ptr> 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<int>(),
|
||||
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<int>(),
|
||||
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<int>(),
|
||||
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(){
|
||||
|
|
|
|||
|
|
@ -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(){
|
||||
|
|
|
|||
|
|
@ -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<ShellSession>(shellPort);
|
||||
rtspSrv->start<RtspSession>(rtspPort);//默认554
|
||||
rtmpSrv->start<RtmpSession>(rtmpPort);//默认1935
|
||||
//http服务器
|
||||
httpSrv->start<HttpSession>(httpPort);//默认80
|
||||
|
||||
//如果支持ssl,还可以开启https服务器
|
||||
TcpServer::Ptr httpsSrv(new TcpServer());
|
||||
//https服务器,支持websocket
|
||||
httpsSrv->start<HttpsSession>(httpsPort);//默认443
|
||||
|
||||
//支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问
|
||||
TcpServer::Ptr rtspSSLSrv(new TcpServer());
|
||||
rtspSSLSrv->start<RtspSessionWithSSL>(rtspsPort);//默认322
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
UdpRecver recver;
|
||||
TcpServer::Ptr tcpRtpServer(new TcpServer());
|
||||
#endif//defined(ENABLE_RTPPROXY)
|
||||
|
||||
try {
|
||||
//rtsp服务器,端口默认554
|
||||
rtspSrv->start<RtspSession>(rtspPort);//默认554
|
||||
//rtsps服务器,端口默认322
|
||||
rtspSSLSrv->start<RtspSessionWithSSL>(rtspsPort);
|
||||
//rtmp服务器,端口默认1935
|
||||
rtmpSrv->start<RtmpSession>(rtmpPort);
|
||||
//http服务器,端口默认80
|
||||
httpSrv->start<HttpSession>(httpPort);
|
||||
//https服务器,端口默认443
|
||||
httpsSrv->start<HttpsSession>(httpsPort);
|
||||
//telnet远程调试服务器
|
||||
shellSrv->start<ShellSession>(shellPort);
|
||||
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
//创建rtp udp服务器
|
||||
recver.initSock(rtp_proxy);
|
||||
//创建rtp tcp服务器
|
||||
tcpRtpServer->start<RtpSession>(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);
|
||||
|
|
|
|||
|
|
@ -49,14 +49,9 @@ void MediaSink::addTrack(const Track::Ptr &track_in) {
|
|||
_ticker.resetTime();
|
||||
}
|
||||
|
||||
weak_ptr<MediaSink> weakSelf = shared_from_this();
|
||||
track->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([weakSelf](const Frame::Ptr &frame){
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if(!strongSelf){
|
||||
return;
|
||||
}
|
||||
if(!strongSelf->_anyTrackUnReady){
|
||||
strongSelf->onTrackFrame(frame);
|
||||
track->addDelegate(std::make_shared<FrameWriterInterfaceHelper>([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<Track::Ptr> MediaSink::getTracks(bool trackReady) const{
|
||||
vector<Track::Ptr> ret;
|
||||
lock_guard<recursive_mutex> 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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -38,11 +38,31 @@ using namespace toolkit;
|
|||
|
||||
namespace mediakit{
|
||||
|
||||
class MediaSinkInterface : public FrameWriterInterface {
|
||||
public:
|
||||
typedef std::shared_ptr<MediaSinkInterface> 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<MediaSink>{
|
||||
class MediaSink : public MediaSinkInterface , public TrackSource{
|
||||
public:
|
||||
typedef std::shared_ptr<MediaSink> 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<Track::Ptr> getTracks(bool trackReady = true) const override ;
|
||||
protected:
|
||||
/**
|
||||
* 某track已经准备好,其ready()状态返回true,
|
||||
|
|
|
|||
|
|
@ -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<Track::Ptr> MediaSource::getTracks(bool trackReady) const {
|
||||
auto strongPtr = _track_source.lock();
|
||||
if(strongPtr){
|
||||
return strongPtr->getTracks(trackReady);
|
||||
}
|
||||
return vector<Track::Ptr>();
|
||||
}
|
||||
|
||||
void MediaSource::setTrackSource(const std::weak_ptr<TrackSource> &track_src) {
|
||||
_track_source = track_src;
|
||||
weak_ptr<MediaSource> 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<MediaSourceEvent> &listener){
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
const std::weak_ptr<MediaSourceEvent>& 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<void(const MediaSource::Ptr &src)> &cb) {
|
||||
lock_guard<recursive_mutex> 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<typename MAP, typename FUNC>
|
||||
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<typename MAP, typename IT0, typename IT1, typename IT2>
|
||||
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<TcpSession> &session,
|
||||
bool retry,
|
||||
const function<void(const MediaSource::Ptr &src)> &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<TcpSession> &session,const function<void(const Ptr &src)> &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<recursive_mutex> 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<MediaSource> 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<recursive_mutex> 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();
|
||||
|
|
|
|||
|
|
@ -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<MediaSource> {
|
||||
/**
|
||||
* 媒体源,任何rtsp/rtmp的直播流都源自该对象
|
||||
*/
|
||||
class MediaSource: public TrackSource, public enable_shared_from_this<MediaSource> {
|
||||
public:
|
||||
typedef std::shared_ptr<MediaSource> Ptr;
|
||||
typedef unordered_map<string, weak_ptr<MediaSource> > StreamMap;
|
||||
|
|
@ -100,160 +104,59 @@ public:
|
|||
typedef unordered_map<string, AppStreamMap > VhostAppStreamMap;
|
||||
typedef unordered_map<string, VhostAppStreamMap > 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<TcpSession> &session,
|
||||
bool retry,
|
||||
const function<void(const MediaSource::Ptr &src)> &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<Track::Ptr> getTracks(bool trackReady = true) const override;
|
||||
// 获取监听者
|
||||
const std::weak_ptr<MediaSourceEvent>& getListener() const;
|
||||
|
||||
// 设置TrackSource
|
||||
void setTrackSource(const std::weak_ptr<TrackSource> &track_src);
|
||||
// 设置监听者
|
||||
virtual void setListener(const std::weak_ptr<MediaSourceEvent> &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<TcpSession> &session, const function<void(const Ptr &src)> &cb);
|
||||
// 遍历所有流
|
||||
static void for_each_media(const function<void(const Ptr &src)> &cb);
|
||||
|
||||
virtual void setListener(const std::weak_ptr<MediaSourceEvent> &listener){
|
||||
_listener = listener;
|
||||
}
|
||||
|
||||
std::weak_ptr<MediaSourceEvent> getListener(){
|
||||
return _listener;
|
||||
}
|
||||
|
||||
template <typename FUN>
|
||||
static void for_each_media(FUN && fun){
|
||||
lock_guard<recursive_mutex> 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<Track::Ptr> getTracks(bool trackReady) const{
|
||||
return vector<Track::Ptr>(0);
|
||||
}
|
||||
protected:
|
||||
void regist() ;
|
||||
bool unregist() ;
|
||||
private:
|
||||
template <typename FUN>
|
||||
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 <typename IT0,typename IT1,typename IT2>
|
||||
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<MediaSourceEvent> _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<MediaSourceEvent> _listener;
|
||||
weak_ptr<TrackSource> _track_source;
|
||||
static SchemaVhostAppStreamMap g_mapMediaSrc;
|
||||
static recursive_mutex g_mtxMediaSrc;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
|
|
|||
|
|
@ -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<MultiMediaSourceMuxer>{
|
||||
public:
|
||||
typedef std::shared_ptr<MultiMediaSourceMuxer> 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<RtmpMediaSourceMuxer>(vhost, strApp, strId, std::make_shared<TitleMeta>(dur_sec));
|
||||
}
|
||||
if (bEanbleRtsp) {
|
||||
_rtsp = std::make_shared<RtspMediaSourceMuxer>(vhost, strApp, strId, std::make_shared<TitleSdp>(dur_sec));
|
||||
}
|
||||
_record = std::make_shared<MediaRecorder>(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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
//////////////以下是播放时间戳的计算//////////////////
|
||||
|
|
@ -84,6 +84,7 @@ public:
|
|||
int64_t getRelativeStamp() const ;
|
||||
private:
|
||||
int64_t _relativeStamp = 0;
|
||||
int64_t _last_dts = -1;
|
||||
SmoothTicker _ticker;
|
||||
};
|
||||
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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播放器、推流器相关设置名,
|
||||
|
|
|
|||
|
|
@ -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<H264Track>();
|
||||
}
|
||||
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<H264Track>();
|
||||
}
|
||||
|
||||
return std::make_shared<H264Track>(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<H265Track>();
|
||||
}
|
||||
return std::make_shared<H265Track>(vps,sps,pps,0,0,0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ typedef enum {
|
|||
TrackVideo = 0,
|
||||
TrackAudio,
|
||||
TrackTitle,
|
||||
TrackMax = 0x7FFF
|
||||
TrackMax = 3
|
||||
} TrackType;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -131,6 +131,35 @@ public:
|
|||
};
|
||||
|
||||
|
||||
class TrackSource{
|
||||
public:
|
||||
TrackSource(){}
|
||||
virtual ~TrackSource(){}
|
||||
|
||||
/**
|
||||
* 获取全部的Track
|
||||
* @param trackReady 是否获取全部已经准备好的Track
|
||||
* @return
|
||||
*/
|
||||
virtual vector<Track::Ptr> 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
|
||||
|
|
|
|||
|
|
@ -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 <sys/stat.h>
|
||||
#if !defined(_WIN32)
|
||||
#include <dirent.h>
|
||||
#endif //!defined(_WIN32)
|
||||
#include <iomanip>
|
||||
#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<string> setFile;
|
||||
while ((pDirent = readdir(pDir)) != NULL) {
|
||||
static set<const char *,StrCaseCompare> 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 << "<html>\r\n"
|
||||
"<head>\r\n"
|
||||
"<title>文件索引</title>\r\n"
|
||||
"</head>\r\n"
|
||||
"<body>\r\n"
|
||||
"<h1>文件索引:";
|
||||
|
||||
ss << httpPath;
|
||||
ss << "</h1>\r\n";
|
||||
if (httpPath != "/") {
|
||||
ss << "<li><a href=\"";
|
||||
ss << "/";
|
||||
ss << "\">";
|
||||
ss << "根目录";
|
||||
ss << "</a></li>\r\n";
|
||||
|
||||
ss << "<li><a href=\"";
|
||||
if(!last_dir_name.empty()){
|
||||
ss << "./";
|
||||
}else{
|
||||
ss << "../";
|
||||
}
|
||||
ss << "\">";
|
||||
ss << "上级目录";
|
||||
ss << "</a></li>\r\n";
|
||||
}
|
||||
|
||||
DIR *pDir;
|
||||
dirent *pDirent;
|
||||
if ((pDir = opendir(strPathPrefix.data())) == NULL) {
|
||||
return false;
|
||||
}
|
||||
set<string> 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 << "<li><span>" << i++ << "</span>\t";
|
||||
ss << "<a href=\"";
|
||||
if(!last_dir_name.empty()){
|
||||
ss << last_dir_name << "/" << strFile;
|
||||
}else{
|
||||
ss << strFile;
|
||||
}
|
||||
|
||||
if(isDir){
|
||||
ss << "/";
|
||||
}
|
||||
ss << "\">";
|
||||
ss << strFile;
|
||||
if (isDir) {
|
||||
ss << "/</a></li>\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 << "</a></li>\r\n";
|
||||
}
|
||||
closedir(pDir);
|
||||
ss << "<ul>\r\n";
|
||||
ss << "</ul>\r\n</body></html>";
|
||||
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<void(const string &errMsg, const HttpServerCookie::Ptr &cookie)> &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<string>();
|
||||
auto cookiePath = (*cookie)[kCookiePathKey].get<string>();
|
||||
auto is_hls = (*cookie)[kAccessHls].get<bool>();
|
||||
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<string>(cookie_path);
|
||||
//记录能否访问
|
||||
(*cookie)[kAccessErrKey].set<string>(errMsg);
|
||||
//记录访问的是否为hls
|
||||
(*cookie)[kAccessHls].set<bool>(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<HttpStringBody>(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<string>());
|
||||
}
|
||||
cb("401 Unauthorized", "text/html", headerOut, std::make_shared<HttpStringBody>(errMsg));
|
||||
return;
|
||||
}
|
||||
|
||||
StrCaseMap httpHeader;
|
||||
if (cookie) {
|
||||
httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get<string>());
|
||||
}
|
||||
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<string &>(strMenu) = errMsg;
|
||||
}
|
||||
StrCaseMap headerOut;
|
||||
if (cookie) {
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get<string>());
|
||||
}
|
||||
cb(errMsg.empty() ? "200 OK" : "401 Unauthorized", "text/html", headerOut, std::make_shared<HttpStringBody>(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<HttpStringBody>(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<StrCaseMap&>(responseHeader);
|
||||
std::shared_ptr<FILE> 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<StrCaseMap &>(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<HttpFileBody>(fp, iRangeStart, iRangeEnd - iRangeStart + 1);
|
||||
(*this)(pcHttpResult, httpHeader, fileBody);
|
||||
}
|
||||
|
||||
HttpResponseInvokerImp::operator bool(){
|
||||
return _lambad.operator bool();
|
||||
}
|
||||
|
||||
|
||||
}//namespace mediakit
|
||||
|
|
@ -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<void(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body)> HttpResponseInvokerLambda0;
|
||||
typedef std::function<void(const string &codeOut, const StrCaseMap &headerOut, const string &body)> HttpResponseInvokerLambda1;
|
||||
|
||||
HttpResponseInvokerImp(){}
|
||||
~HttpResponseInvokerImp(){}
|
||||
template<typename C>
|
||||
HttpResponseInvokerImp(const C &c):HttpResponseInvokerImp(typename function_traits<C>::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<void(const string &status_code, const string &content_type, const StrCaseMap &responseHeader, const HttpBody::Ptr &body)> 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
|
||||
|
|
@ -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<HttpStringBody>(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<StrCaseMap&>(responseHeader);
|
||||
do {
|
||||
std::shared_ptr<FILE> fp(fopen(filePath.data(), "rb"), [](FILE *fp) {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
});
|
||||
if (!fp) {
|
||||
//打开文件失败
|
||||
break;
|
||||
}
|
||||
|
||||
auto &strRange = const_cast<StrCaseMap &>(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<HttpFileBody>(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<string, HttpCMDHandle> g_mapCmdIndex;
|
||||
static unordered_map<string, HttpCMDHandle> 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<void()> &cb){
|
||||
|
|
@ -350,12 +194,10 @@ bool HttpSession::checkLiveFlvStream(const function<void()> &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<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(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<void()> &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<void()> &cb){
|
|||
bool authSuccess = err.empty();
|
||||
if(!authSuccess){
|
||||
sendResponse("401 Unauthorized", true, nullptr, KeyValue(), std::make_shared<HttpStringBody>(err));
|
||||
shutdown(SockException(Err_shutdown,StrPrinter << "401 Unauthorized:" << err));
|
||||
return ;
|
||||
}
|
||||
|
||||
|
|
@ -421,158 +259,6 @@ bool HttpSession::checkLiveFlvStream(const function<void()> &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<string> setFile;
|
||||
while ((pDirent = readdir(pDir)) != NULL) {
|
||||
static set<const char *,StrCaseCompare> 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<void(const string &errMsg,const HttpServerCookie::Ptr &cookie)> &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<string>();
|
||||
auto cookiePath = (*cookie)[kCookiePathKey].get<string>();
|
||||
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<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(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<string>(cookie_path);
|
||||
//记录能否访问
|
||||
(*cookie)[kAccessErrKey].set<string>(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<string &>(strMeun) = errMsg;
|
||||
}
|
||||
KeyValue headerOut;
|
||||
if(cookie){
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get<string>());
|
||||
}
|
||||
sendResponse(errMsg.empty() ? "200 OK" : "401 Unauthorized" ,bClose, "text/html", headerOut, std::make_shared<HttpStringBody>(strMeun));
|
||||
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access folder");
|
||||
});
|
||||
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(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<string>());
|
||||
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<HttpStringBody>(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<string>());
|
||||
}
|
||||
|
||||
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 << "<html>\r\n"
|
||||
"<head>\r\n"
|
||||
"<title>文件索引</title>\r\n"
|
||||
"</head>\r\n"
|
||||
"<body>\r\n"
|
||||
"<h1>文件索引:";
|
||||
|
||||
ss << httpPath;
|
||||
ss << "</h1>\r\n";
|
||||
if (httpPath != "/") {
|
||||
ss << "<li><a href=\"";
|
||||
ss << "/";
|
||||
ss << "\">";
|
||||
ss << "根目录";
|
||||
ss << "</a></li>\r\n";
|
||||
|
||||
ss << "<li><a href=\"";
|
||||
if(!last_dir_name.empty()){
|
||||
ss << "./";
|
||||
}else{
|
||||
ss << "../";
|
||||
}
|
||||
ss << "\">";
|
||||
ss << "上级目录";
|
||||
ss << "</a></li>\r\n";
|
||||
}
|
||||
|
||||
DIR *pDir;
|
||||
dirent *pDirent;
|
||||
if ((pDir = opendir(strPathPrefix.data())) == NULL) {
|
||||
return false;
|
||||
}
|
||||
set<string> 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 << "<li><span>" << i++ << "</span>\t";
|
||||
ss << "<a href=\"";
|
||||
if(!last_dir_name.empty()){
|
||||
ss << last_dir_name << "/" << strFile;
|
||||
}else{
|
||||
ss << strFile;
|
||||
}
|
||||
|
||||
if(isDir){
|
||||
ss << "/";
|
||||
}
|
||||
ss << "\">";
|
||||
ss << strFile;
|
||||
if (isDir) {
|
||||
ss << "/</a></li>\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 << "</a></li>\r\n";
|
||||
}
|
||||
closedir(pDir);
|
||||
ss << "<ul>\r\n";
|
||||
ss << "</ul>\r\n</body></html>";
|
||||
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<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(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<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(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<uint64_t> recvedContentLen = std::make_shared<uint64_t>(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;
|
||||
|
|
|
|||
|
|
@ -27,46 +27,19 @@
|
|||
#define SRC_HTTP_HTTPSESSION_H_
|
||||
|
||||
#include <functional>
|
||||
#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<void(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body)> HttpResponseInvokerLambda0;
|
||||
typedef std::function<void(const string &codeOut, const StrCaseMap &headerOut, const string &body)> HttpResponseInvokerLambda1;
|
||||
|
||||
HttpResponseInvokerImp(){}
|
||||
~HttpResponseInvokerImp(){}
|
||||
template<typename C>
|
||||
HttpResponseInvokerImp(const C &c):HttpResponseInvokerImp(typename function_traits<C>::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<void(const string &errMsg,const HttpServerCookie::Ptr &cookie)> &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
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
#define ZLMEDIAKIT_WEBSOCKETSESSION_H
|
||||
|
||||
#include "HttpSession.h"
|
||||
#include "Network/TcpServer.h"
|
||||
|
||||
/**
|
||||
* 通过该模板类可以透明化WebSocket协议,
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
@ -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 <memory>
|
||||
#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<MediaRecorder> 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> _hlsRecorder;
|
||||
#endif //defined(ENABLE_HLS)
|
||||
|
||||
#if defined(ENABLE_MP4RECORD)
|
||||
std::shared_ptr<MP4Recorder> _mp4Recorder;
|
||||
#endif //defined(ENABLE_MP4RECORD)
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
#endif /* SRC_MEDIAFILE_MEDIARECORDER_H_ */
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Track::Ptr> Demuxer::getTracks(bool trackReady) const {
|
|||
ret.emplace_back(_audioTrack);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
return std::move(ret);
|
||||
}
|
||||
|
||||
float Demuxer::getDuration() const {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ using namespace toolkit;
|
|||
|
||||
namespace mediakit {
|
||||
|
||||
class DemuxerBase {
|
||||
class DemuxerBase : public TrackSource{
|
||||
public:
|
||||
typedef std::shared_ptr<DemuxerBase> Ptr;
|
||||
|
||||
|
|
@ -57,29 +57,6 @@ public:
|
|||
* @return
|
||||
*/
|
||||
virtual bool isInited(int analysisMs) { return true; }
|
||||
|
||||
/**
|
||||
* 获取全部的Track
|
||||
* @param trackReady 是否获取全部已经准备好的Track
|
||||
* @return
|
||||
*/
|
||||
virtual vector<Track::Ptr> getTracks(bool trackReady = true) const { return vector<Track::Ptr>();}
|
||||
|
||||
/**
|
||||
* 获取特定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<Track::Ptr> getTracks(bool trackReady = true) const override{
|
||||
return vector<Track::Ptr>();
|
||||
}
|
||||
protected:
|
||||
virtual void onShutdown(const SockException &ex) {}
|
||||
virtual void onPlayResult(const SockException &ex) {}
|
||||
|
|
@ -159,9 +143,8 @@ protected:
|
|||
virtual void onResume(){};
|
||||
};
|
||||
|
||||
template<typename Parent,typename Parser>
|
||||
class PlayerImp : public Parent
|
||||
{
|
||||
template<typename Parent,typename Delegate>
|
||||
class PlayerImp : public Parent {
|
||||
public:
|
||||
typedef std::shared_ptr<PlayerImp> Ptr;
|
||||
|
||||
|
|
@ -170,62 +153,62 @@ public:
|
|||
|
||||
virtual ~PlayerImp(){}
|
||||
void setOnShutdown(const function<void(const SockException &)> &cb) override {
|
||||
if (_parser) {
|
||||
_parser->setOnShutdown(cb);
|
||||
if (_delegate) {
|
||||
_delegate->setOnShutdown(cb);
|
||||
}
|
||||
_shutdownCB = cb;
|
||||
}
|
||||
void setOnPlayResult(const function<void(const SockException &ex)> &cb) override {
|
||||
if (_parser) {
|
||||
_parser->setOnPlayResult(cb);
|
||||
if (_delegate) {
|
||||
_delegate->setOnPlayResult(cb);
|
||||
}
|
||||
_playResultCB = cb;
|
||||
}
|
||||
|
||||
void setOnResume(const function<void()> &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<Track::Ptr> 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<void(const SockException &ex)> _shutdownCB;
|
||||
function<void(const SockException &ex)> _playResultCB;
|
||||
function<void()> _resumeCB;
|
||||
std::shared_ptr<Parser> _parser;
|
||||
std::shared_ptr<Delegate> _delegate;
|
||||
MediaSource::Ptr _pMediaSrc;
|
||||
};
|
||||
|
||||
|
|
@ -282,14 +257,14 @@ public:
|
|||
bool isInited(int analysisMs) override;
|
||||
|
||||
/**
|
||||
* 获取所有可用Track,请在isInited()返回true时调用
|
||||
* @return
|
||||
* 获取所有Track
|
||||
* @return 所有Track
|
||||
*/
|
||||
vector<Track::Ptr> getTracks(bool trackReady = true) const override;
|
||||
|
||||
/**
|
||||
* 获取节目总时长
|
||||
* @return
|
||||
* @return 节目总时长,单位秒
|
||||
*/
|
||||
float getDuration() const override;
|
||||
protected:
|
||||
|
|
|
|||
|
|
@ -138,13 +138,13 @@ void PlayerProxy::play(const string &strUrlTmp) {
|
|||
MediaPlayer::play(strUrlTmp);
|
||||
|
||||
MediaSource::Ptr mediaSource;
|
||||
if(dynamic_pointer_cast<RtspPlayer>(_parser)){
|
||||
if(dynamic_pointer_cast<RtspPlayer>(_delegate)){
|
||||
//rtsp拉流
|
||||
GET_CONFIG(bool,directProxy,Rtsp::kDirectProxy);
|
||||
if(directProxy && _bEnableRtsp){
|
||||
mediaSource = std::make_shared<RtspMediaSource>(_strVhost,_strApp,_strSrc);
|
||||
}
|
||||
} else if(dynamic_pointer_cast<RtmpPlayer>(_parser)){
|
||||
} else if(dynamic_pointer_cast<RtmpPlayer>(_delegate)){
|
||||
//rtmp拉流
|
||||
if(_bEnableRtmp){
|
||||
mediaSource = std::make_shared<RtmpMediaSource>(_strVhost,_strApp,_strSrc);
|
||||
|
|
|
|||
|
|
@ -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(){
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ public:
|
|||
virtual void setOnShutdown(const Event &cb) = 0;
|
||||
};
|
||||
|
||||
template<typename Parent,typename Parser>
|
||||
template<typename Parent,typename Delegate>
|
||||
class PusherImp : public Parent {
|
||||
public:
|
||||
typedef std::shared_ptr<PusherImp> 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> _parser;
|
||||
std::shared_ptr<Delegate> _delegate;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -110,8 +110,8 @@ void HlsMaker::addNewSegment(uint32_t) {
|
|||
return;
|
||||
}
|
||||
|
||||
//关闭并保存上一个切片
|
||||
flushLastSegment();
|
||||
//关闭并保存上一个切片,如果_seg_number==0,那么是点播。
|
||||
flushLastSegment(_seg_number == 0);
|
||||
//新增切片
|
||||
_last_file_name = onOpenSegment(_file_index++);
|
||||
//重置切片计时器
|
||||
|
|
@ -32,15 +32,23 @@
|
|||
|
||||
namespace mediakit {
|
||||
|
||||
class HlsRecorder : public HlsMakerImp, public TsMuxer {
|
||||
class HlsRecorder : public TsMuxer {
|
||||
public:
|
||||
template<typename ...ArgsType>
|
||||
HlsRecorder(ArgsType &&...args):HlsMakerImp(std::forward<ArgsType>(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
|
||||
|
|
@ -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<AACTrack>(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
|
||||
|
|
@ -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_writer_t> _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<MP4MuxerFile> 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> _file;
|
||||
string _file_name;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
|
@ -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<recursive_mutex> 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<H264FrameNoCacheAble>((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<AACFrameNoCacheAble>((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<recursive_mutex> 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) {
|
||||
|
|
@ -37,10 +37,10 @@ using namespace toolkit;
|
|||
|
||||
namespace mediakit {
|
||||
|
||||
class MediaReader : public std::enable_shared_from_this<MediaReader> ,public MediaSourceEvent{
|
||||
class MP4Reader : public std::enable_shared_from_this<MP4Reader> ,public MediaSourceEvent{
|
||||
public:
|
||||
typedef std::shared_ptr<MediaReader> Ptr;
|
||||
virtual ~MediaReader();
|
||||
typedef std::shared_ptr<MP4Reader> 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 应用名
|
||||
|
|
@ -107,7 +107,7 @@ void MP4Recorder::asyncClose() {
|
|||
auto info = _info;
|
||||
WorkThreadPool::Instance().getExecutor()->async([muxer,strFileTmp,strFile,info]() {
|
||||
//获取文件录制时间,放在关闭mp4之前是为了忽略关闭mp4执行时间
|
||||
const_cast<Mp4Info&>(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime;
|
||||
const_cast<MP4Info&>(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime;
|
||||
//关闭mp4非常耗时,所以要放在后台线程执行
|
||||
const_cast<MP4MuxerFile::Ptr &>(muxer).reset();
|
||||
//临时文件名改成正式文件名,防止mp4未完成时被访问
|
||||
|
|
@ -115,7 +115,7 @@ void MP4Recorder::asyncClose() {
|
|||
//获取文件大小
|
||||
struct stat fileData;
|
||||
stat(strFile.data(), &fileData);
|
||||
const_cast<Mp4Info&>(info).ui64FileSize = fileData.st_size;
|
||||
const_cast<MP4Info&>(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 */
|
||||
|
|
@ -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<MP4Recorder> 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<Track::Ptr> _tracks;
|
||||
|
|
@ -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<RecorderHelper> Ptr;
|
||||
|
||||
/**
|
||||
* 构建函数
|
||||
* @param bContinueRecord false表明hls录制从头开始录制(意味着hls临时文件在媒体反注册时会被删除)
|
||||
*/
|
||||
RecorderHelper(const MediaSinkInterface::Ptr &recorder, bool bContinueRecord) {
|
||||
_recorder = recorder;
|
||||
_continueRecord = bContinueRecord;
|
||||
}
|
||||
|
||||
~RecorderHelper() {
|
||||
resetTracks();
|
||||
}
|
||||
|
||||
// 附则于track上
|
||||
void attachTracks(vector<Track::Ptr> &&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<Track::Ptr> &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<Track::Ptr> _tracks;
|
||||
bool _continueRecord;
|
||||
string _schema;
|
||||
};
|
||||
|
||||
|
||||
template<Recorder::type type>
|
||||
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<decltype(_recorder_mtx)> 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<RecorderHelper>(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<decltype(_recorder_mtx)> lck(_recorder_mtx);
|
||||
return _recorder_map.erase(getRecorderKey(vhost, app, stream_id));
|
||||
}
|
||||
|
||||
void stopAll(){
|
||||
lock_guard<decltype(_recorder_mtx)> 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<decltype(_recorder_mtx)> 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<decltype(_recorder_mtx)> 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<Track::Ptr> 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<Track::Ptr>();
|
||||
}
|
||||
|
||||
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<string, RecorderHelper::Ptr> _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<type_mp4>::Instance().getRecordStatus(vhost,app,stream_id);
|
||||
case type_hls:
|
||||
return MediaSourceWatcher<type_hls>::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<type_mp4>::Instance().startRecord(vhost,app,stream_id,waitForRecord,continueRecord);
|
||||
case type_hls:
|
||||
return MediaSourceWatcher<type_hls>::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<type_mp4>::Instance().stopRecord(vhost,app,stream_id);
|
||||
case type_hls:
|
||||
return MediaSourceWatcher<type_hls>::Instance().stopRecord(vhost,app,stream_id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Recorder::stopAll() {
|
||||
MediaSourceWatcher<type_hls>::Instance().stopAll();
|
||||
MediaSourceWatcher<type_mp4>::Instance().stopAll();
|
||||
}
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
|
@ -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 <memory>
|
||||
#include <string>
|
||||
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_ */
|
||||
|
|
@ -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();
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -48,13 +48,19 @@ public:
|
|||
void setListener(const std::weak_ptr<MediaSourceEvent> &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<TrackSource> &track_src){
|
||||
_mediaSouce->setTrackSource(track_src);
|
||||
}
|
||||
private:
|
||||
RtmpMediaSource::Ptr _mediaSouce;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,13 +38,7 @@ RtmpMuxer::RtmpMuxer(const TitleMeta::Ptr &title) {
|
|||
_rtmpRing = std::make_shared<RtmpRingInterface::RingType>();
|
||||
}
|
||||
|
||||
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 */
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
namespace mediakit{
|
||||
|
||||
class RtmpMuxer : public MediaSink{
|
||||
class RtmpMuxer : public MediaSinkInterface{
|
||||
public:
|
||||
typedef std::shared_ptr<RtmpMuxer> 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];
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -34,44 +34,35 @@ using namespace mediakit::Client;
|
|||
|
||||
namespace mediakit {
|
||||
|
||||
unordered_map<string, RtmpPlayer::rtmpCMDHandle> 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<recursive_mutex> lck(_mtxOnResultCB);
|
||||
_mapOnResultCB.clear();
|
||||
}
|
||||
{
|
||||
lock_guard<recursive_mutex> 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<recursive_mutex> lck(_mtxOnResultCB);
|
||||
_mapOnResultCB.clear();
|
||||
lock_guard<recursive_mutex> 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<RtmpPlayer> weakSelf = dynamic_pointer_cast<RtmpPlayer>(shared_from_this());
|
||||
int timeoutMS = (*this)[kMediaTimeoutMS].as<int>();
|
||||
//创建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<RtmpPlayer> weakSelf= dynamic_pointer_cast<RtmpPlayer>(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<string, rtmp_func_ptr> 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<std::string>();
|
||||
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{
|
||||
|
|
|
|||
|
|
@ -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<function<void(AMFValue &dec)> > _dqOnStatusCB;
|
||||
recursive_mutex _mtxOnStatusCB;
|
||||
|
||||
typedef void (RtmpPlayer::*rtmpCMDHandle)(AMFDecoder &dec);
|
||||
static unordered_map<string, rtmpCMDHandle> g_mapCmd;
|
||||
|
||||
//超时功能实现
|
||||
Ticker _mediaTicker;
|
||||
std::shared_ptr<Timer> _pMediaTimer;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ void RtmpSession::doPlayResponse(const string &err,const std::function<void(bool
|
|||
|
||||
//鉴权成功,查找媒体源并回复
|
||||
weak_ptr<RtmpSession> weakSelf = dynamic_pointer_cast<RtmpSession>(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<RtmpMediaSource>(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()){
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
*
|
||||
* 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)
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
*
|
||||
* 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 <stdint.h>
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
*
|
||||
* 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 <assert.h>
|
||||
#include "Util/logger.h"
|
||||
#include "RtpDecoder.h"
|
||||
#include "rtp-payload.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
RtpDecoder::RtpDecoder() {
|
||||
_buffer = std::make_shared<BufferRaw>();
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
*
|
||||
* 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
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
*
|
||||
* 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<void(uint32_t dts,uint32_t pts,const Buffer::Ptr &buffer)> &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<BufferString>(std::move(merged));
|
||||
}
|
||||
cb(back->dts(),back->pts(),merged_frame);
|
||||
_frameCached.clear();
|
||||
}
|
||||
_frameCached.emplace_back(Frame::getCacheAbleFrame(frame));
|
||||
}
|
||||
private:
|
||||
List<Frame::Ptr> _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<SdpTrack>();
|
||||
_track = std::make_shared<SdpTrack>();
|
||||
_track->_interleaved = 0;
|
||||
_track->_samplerate = 90000;
|
||||
_track->_type = TrackVideo;
|
||||
_track->_ssrc = _ssrc;
|
||||
DebugL << printSSRC(_ssrc);
|
||||
_muxer = std::make_shared<MultiMediaSourceMuxer>(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<FrameMerger>();
|
||||
}
|
||||
|
||||
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<H264Track>();
|
||||
_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<H264FrameNoCacheAble>((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<H264FrameNoCacheAble>(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<H265Track>();
|
||||
_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<H265FrameNoCacheAble>((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<H265FrameNoCacheAble>(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<AACTrack>();
|
||||
_muxer->addTrack(track);
|
||||
}
|
||||
|
||||
if (codecid != _codecid_audio) {
|
||||
WarnL << "audio track change to AAC from codecid:" << _codecid_audio;
|
||||
return;
|
||||
}
|
||||
_muxer->inputFrame(std::make_shared<AACFrameNoCacheAble>((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)
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
*
|
||||
* 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<RtpProcess> 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<FILE> _save_file_rtp;
|
||||
std::shared_ptr<FILE> _save_file_ps;
|
||||
std::shared_ptr<FILE> _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<FrameMerger> _merger;
|
||||
Ticker _last_rtp_time;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif//defined(ENABLE_RTPPROXY)
|
||||
#endif //ZLMEDIAKIT_RTPPROCESS_H
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
*
|
||||
* 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<decltype(_mtx_map)> 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<RtpProcess>(ssrc);
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
void RtpSelector::delProcess(uint32_t ssrc,const RtpProcess *ptr) {
|
||||
lock_guard<decltype(_mtx_map)> 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<decltype(_mtx_map)> 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)
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
*
|
||||
* 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 <stdint.h>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include "RtpProcess.h"
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
class RtpSelector : public std::enable_shared_from_this<RtpSelector>{
|
||||
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<uint32_t,RtpProcess::Ptr> _map_rtp_process;
|
||||
recursive_mutex _mtx_map;
|
||||
Ticker _last_rtp_time;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif//defined(ENABLE_RTPPROXY)
|
||||
#endif //ZLMEDIAKIT_RTPSELECTOR_H
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
*
|
||||
* 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)
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
*
|
||||
* 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
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
*
|
||||
* 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)
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
*
|
||||
* 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
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
*
|
||||
* 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)
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
*
|
||||
* 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 <memory>
|
||||
#include "Network/Socket.h"
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit{
|
||||
|
||||
/**
|
||||
* 组播接收器
|
||||
*/
|
||||
class UdpRecver {
|
||||
public:
|
||||
typedef std::shared_ptr<UdpRecver> Ptr;
|
||||
typedef function<void(const Buffer::Ptr &buf)> 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
|
||||
|
|
@ -48,16 +48,23 @@ public:
|
|||
void setListener(const std::weak_ptr<MediaSourceEvent> &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<TrackSource> &track_src){
|
||||
_mediaSouce->setTrackSource(track_src);
|
||||
}
|
||||
private:
|
||||
RtspMediaSource::Ptr _mediaSouce;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,29 +38,34 @@ RtspMuxer::RtspMuxer(const TitleSdp::Ptr &title){
|
|||
_rtpRing = std::make_shared<RtpRingInterface::RingType>();
|
||||
}
|
||||
|
||||
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 */
|
||||
|
|
@ -35,7 +35,7 @@ namespace mediakit{
|
|||
/**
|
||||
* rtsp生成器
|
||||
*/
|
||||
class RtspMuxer : public MediaSink{
|
||||
class RtspMuxer : public MediaSinkInterface{
|
||||
public:
|
||||
typedef std::shared_ptr<RtspMuxer> 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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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<<endl;
|
||||
DebugL << endl;
|
||||
}
|
||||
void RtspPlayer::teardown(){
|
||||
if (alive()) {
|
||||
|
|
@ -56,7 +62,6 @@ void RtspPlayer::teardown(){
|
|||
|
||||
_rtspMd5Nonce.clear();
|
||||
_rtspRealm.clear();
|
||||
|
||||
_aTrackInfo.clear();
|
||||
_strSession.clear();
|
||||
_strContentBase.clear();
|
||||
|
|
@ -135,7 +140,7 @@ void RtspPlayer::play(bool isSSL,const string &strUrl, const string &strUser, co
|
|||
}
|
||||
|
||||
if(ip.empty()){
|
||||
onPlayResult_l(SockException(Err_other,StrPrinter << "illegal rtsp url:" << strUrl));
|
||||
onPlayResult_l(SockException(Err_other,StrPrinter << "illegal rtsp url:" << strUrl),false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +153,7 @@ void RtspPlayer::play(bool isSSL,const string &strUrl, const string &strUser, co
|
|||
if(!strongSelf) {
|
||||
return false;
|
||||
}
|
||||
strongSelf->onPlayResult_l(SockException(Err_timeout,"play rtsp timeout"));
|
||||
strongSelf->onPlayResult_l(SockException(Err_timeout,"play rtsp timeout"),false);
|
||||
return false;
|
||||
},getPoller()));
|
||||
|
||||
|
|
@ -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<string> &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<RtspPlayer> weakSelf = dynamic_pointer_cast<RtspPlayer>(shared_from_this());
|
||||
int timeoutMS = (*this)[kMediaTimeoutMS].as<int>();
|
||||
_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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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<RtspPlayer,RtspDemuxer>::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<RtspPlayer,RtspDemuxer>::onPlayResult(ex);
|
||||
}else{
|
||||
//还没初始化成功,说明sdp里面信息不完善,还有一些track未初始化成功
|
||||
_maxAnalysisMS = (*this)[Client::kMaxAnalysisMS];
|
||||
}
|
||||
}
|
||||
private:
|
||||
RtspMediaSource::Ptr _pRtspMediaSrc;
|
||||
|
||||
int _maxAnalysisMS = 0;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
|
|
|||
|
|
@ -351,6 +351,9 @@ inline int RtspPusher::getTrackIndexByTrackType(TrackType type) {
|
|||
return i;
|
||||
}
|
||||
}
|
||||
if(_aTrackInfo.size() == 1){
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ void RtspSession::handleReq_Describe(const Parser &parser) {
|
|||
void RtspSession::onAuthSuccess() {
|
||||
TraceP(this);
|
||||
weak_ptr<RtspSession> weakSelf = dynamic_pointer_cast<RtspSession>(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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
#include "RtspSplitter.h"
|
||||
#include "RtpReceiver.h"
|
||||
#include "RtspToRtmpMediaSource.h"
|
||||
#include "Common/Stamp.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -16,39 +16,35 @@ class CMD_media: public CMD {
|
|||
public:
|
||||
CMD_media(){
|
||||
_parser.reset(new OptionParser([](const std::shared_ptr<ostream> &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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2019 Gemfield <gemfield@civilnet.cn>
|
||||
*
|
||||
* 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 <map>
|
||||
#include <iostream>
|
||||
#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>("ConsoleChannel"));
|
||||
#if defined(ENABLE_RTPPROXY)
|
||||
//启动异步日志线程
|
||||
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
|
||||
loadIniConfig((exeDir() + "config.ini").data());
|
||||
TcpServer::Ptr rtspSrv(new TcpServer());
|
||||
TcpServer::Ptr rtmpSrv(new TcpServer());
|
||||
TcpServer::Ptr httpSrv(new TcpServer());
|
||||
rtspSrv->start<RtspSession>(554);//默认554
|
||||
rtmpSrv->start<RtmpSession>(1935);//默认1935
|
||||
httpSrv->start<HttpSession>(80);//默认80
|
||||
loadFile(argv[1]);
|
||||
#else
|
||||
ErrorL << "please ENABLE_RTPPROXY and then test";
|
||||
#endif//#if defined(ENABLE_RTPPROXY)
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -354,6 +354,7 @@ int main(int argc,char *argv[]) {
|
|||
signal(SIGHUP, [](int) { loadIniConfig(); });
|
||||
sem.wait();
|
||||
|
||||
Recorder::stopAll();
|
||||
lock_guard<mutex> lck(s_mtxFlvRecorder);
|
||||
s_mapFlvRecorder.clear();
|
||||
return 0;
|
||||
|
|
|
|||
Loading…
Reference in New Issue