Merge pull request #2 from xiongziliang/master

merge
This commit is contained in:
kqbi 2019-12-08 19:26:29 +08:00 committed by GitHub
commit 2f12a66372
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
89 changed files with 3478 additions and 1577 deletions

View File

@ -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

4
.gitmodules vendored
View File

@ -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

View File

@ -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.hWinsock.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()
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" )
#mpegts
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()
#movflvMP4
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()
#rtprtpps/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.hWinsock.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)

View File

@ -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

View File

@ -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.
```

View File

@ -2,6 +2,13 @@
# 一个基于C++11的高性能运营级流媒体服务框架
[![Build Status](https://travis-ci.org/xiongziliang/ZLMediaKit.svg?branch=master)](https://travis-ci.org/xiongziliang/ZLMediaKit)
## 国内用户请使用gitee镜像下载
```
git clone --depth 1 https://gitee.com/xiahcu/ZLMediaKit
cd ZLMediaKit
git submodule update --init
```
## 项目特点
- 基于C++11开发避免使用裸指针代码稳定可靠同时跨平台移植简单方便代码清晰简洁。
- 打包多种流媒体协议(RTSP/RTMP/HLS/HTTP-FLV/Websocket-FLV支持协议间的互相转换提供一站式的服务。
@ -353,8 +360,15 @@ 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)

View File

@ -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

View File

@ -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(){

View File

@ -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事件触发机制
* 1http请求头查找cookie3
* 2http url参数(ip+)cookiecookie则进入步骤5
* 2http url参数查找cookiecookie则进入步骤5
* 3cookie标记是否有权限访问文件
* 4cookie中记录的url参数是否跟本次url参数一致
* 5kBroadcastHttpAccess事件
@ -459,6 +476,9 @@ void installWebHook(){
invoker(obj["err"].asString(),obj["path"].asString(),obj["second"].asInt());
});
});
//汇报服务器重新启动
reportServerStarted();
}
void unInstallWebHook(){

View File

@ -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);

View File

@ -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;
if(trackReady && !pr.second->ready()){
continue;
}
return pr.second->ready() ? pr.second : nullptr;
ret.emplace_back(pr.second);
}
}
return nullptr;
return std::move(ret);
}

View File

@ -38,11 +38,31 @@ using namespace toolkit;
namespace mediakit{
class MediaSinkInterface : public FrameWriterInterface {
public:
typedef std::shared_ptr<MediaSinkInterface> Ptr;
MediaSinkInterface(){};
virtual ~MediaSinkInterface(){};
/**
* trackTrack的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

View File

@ -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,8 +276,7 @@ MediaSource::Ptr MediaSource::find(
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
MediaSource::Ptr ret;
//查找某一媒体源,找到后返回
searchMedia(schema, vhost, app, id,
[&](SchemaVhostAppStreamMap::iterator &it0 ,
searchMedia(g_mapMediaSrc, schema, vhost, app, id, [&](SchemaVhostAppStreamMap::iterator &it0,
VhostAppStreamMap::iterator &it1,
AppStreamMap::iterator &it2,
StreamMap::iterator &it3) {
@ -133,14 +284,14 @@ MediaSource::Ptr MediaSource::find(
if (!ret) {
//该对象已经销毁
it2->second.erase(it3);
eraseIfEmpty(it0,it1,it2);
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,6 +306,12 @@ void MediaSource::regist() {
g_mapMediaSrc[_strSchema][_strVhost][_strApp][_strId] = shared_from_this();
}
InfoL << _strSchema << " " << _strVhost << " " << _strApp << " " << _strId;
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,
@ -162,11 +319,12 @@ void MediaSource::regist() {
_strApp,
_strId,
*this);
},false);
}
bool MediaSource::unregist() {
//反注册该源
lock_guard<recursive_mutex> lock(g_mtxMediaSrc);
return searchMedia(_strSchema, _strVhost, _strApp, _strId, [&](SchemaVhostAppStreamMap::iterator &it0 ,
return searchMedia(g_mapMediaSrc, _strSchema, _strVhost, _strApp, _strId,[&](SchemaVhostAppStreamMap::iterator &it0,
VhostAppStreamMap::iterator &it1,
AppStreamMap::iterator &it2,
StreamMap::iterator &it3) {
@ -176,7 +334,7 @@ bool MediaSource::unregist() {
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();

View File

@ -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) {
// 通知其停止推流
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 */

View File

@ -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;
};

View File

@ -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;
//相对时间戳
if(_last_dts != dts){
//时间戳发生变更
_relativeStamp += deltaStamp(modifyStamp ? _ticker.elapsedTime() : dts);
_last_dts = dts;
}
dts_out = _relativeStamp;
//////////////以下是播放时间戳的计算//////////////////

View File

@ -84,6 +84,7 @@ public:
int64_t getRelativeStamp() const ;
private:
int64_t _relativeStamp = 0;
int64_t _last_dts = -1;
SmoothTicker _ticker;
};

View File

@ -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";

View File

@ -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播放器

View File

@ -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);
}

View File

@ -50,7 +50,7 @@ typedef enum {
TrackVideo = 0,
TrackAudio,
TrackTitle,
TrackMax = 0x7FFF
TrackMax = 3
} TrackType;
/**

View File

@ -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

View File

@ -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客户端是否有权限访问文件的逻辑步骤
* 1http请求头查找cookie3
* 2http url参数查找cookiecookie则进入步骤5
* 3cookie标记是否有权限访问文件
* 4cookie中记录的url参数是否跟本次url参数一致
* 5kBroadcastHttpAccess事件
*/
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

View File

@ -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

View File

@ -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();
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;
}
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;
}
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;

View File

@ -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客户端是否有权限访问文件的逻辑步骤
*
* 1http请求头查找cookie3
* 2http url参数(ip+)cookiecookie则进入步骤5
* 3cookie标记是否有权限访问文件
* 4cookie中记录的url参数是否跟本次url参数一致
* 5kBroadcastHttpAccess事件
* @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

View File

@ -28,6 +28,7 @@
#define ZLMEDIAKIT_WEBSOCKETSESSION_H
#include "HttpSession.h"
#include "Network/TcpServer.h"
/**
* WebSocket协议

View File

@ -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 */

View File

@ -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;
/**
* trackTrack的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_ */

View File

@ -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();
}
}

View File

@ -64,19 +64,25 @@ 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;
}
return true;
}
vector<Track::Ptr> Demuxer::getTracks(bool trackReady) const {
vector<Track::Ptr> ret;
@ -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 {

View File

@ -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;
/**
* TrackisInited()true时调用
* @return
* Track
* @return Track
*/
vector<Track::Ptr> getTracks(bool trackReady = true) const override;
/**
*
* @return
* @return ,
*/
float getDuration() const override;
protected:

View File

@ -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);

View File

@ -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(){

View File

@ -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 urlrtsp/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;
};

View File

@ -110,8 +110,8 @@ void HlsMaker::addNewSegment(uint32_t) {
return;
}
//关闭并保存上一个切片
flushLastSegment();
//关闭并保存上一个切片如果_seg_number==0,那么是点播。
flushLastSegment(_seg_number == 0);
//新增切片
_last_file_name = onOpenSegment(_file_index++);
//重置切片计时器

View File

@ -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 &params){
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

View File

@ -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);
@ -211,6 +215,11 @@ void MP4Muxer::onTrackReady(const Track::Ptr &track) {
}
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

View File

@ -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输出frameonAllTrackReady触发后才会调用此方法
* @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

View File

@ -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) {

View File

@ -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

View File

@ -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 */

View File

@ -42,7 +42,7 @@ using namespace toolkit;
namespace mediakit {
class Mp4Info {
class MP4Info {
public:
time_t ui64StartedTime; //GMT标准时间单位秒
time_t ui64TimeLen;//录像长度,单位秒
@ -55,9 +55,11 @@ 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,
@ -68,19 +70,16 @@ public:
* Track
*/
void resetTracks() override;
private:
/**
* Track输出frameonAllTrackReady触发后才会调用此方法
* @param frame
*/
void onTrackFrame(const Frame::Ptr &frame) override ;
/**
* track已经准备好ready()true
* sps pps等相关信息了
* @param track
* frame
*/
void onTrackReady(const Track::Ptr & track) override;
void inputFrame(const Frame::Ptr &frame) override;
/**
* ready状态的track
*/
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;

363
src/Record/Recorder.cpp Normal file
View File

@ -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 */

98
src/Record/Recorder.h Normal file
View File

@ -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_ */

View File

@ -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();

View File

@ -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 {

View File

@ -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;
};

View File

@ -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 */

View File

@ -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
* ready状态的track
*/
void onTrackReady(const Track::Ptr & track) override ;
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];
};

View File

@ -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()) {
shutdown(SockException(Err_shutdown,"teardown"));
}
_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;
RtmpProtocol::reset();
CLEAR_ARR(_aiFistStamp);
CLEAR_ARR(_aiNowStamp);
reset();
shutdown(SockException(Err_shutdown,"teardown"));
}
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,45 +103,44 @@ 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) {
} else if (ex) {
//播放成功后异常断开回调
onShutdown(ex);
} else {
//恢复播放
onResume();
}
}
if(ex){
teardown();
@ -158,7 +148,7 @@ void RtmpPlayer::onPlayResult_l(const SockException &ex) {
}
void RtmpPlayer::onConnect(const SockException &err){
if(err.getErrCode() != Err_success) {
onPlayResult_l(err);
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{

View File

@ -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;

View File

@ -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;

View File

@ -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()){

View File

@ -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;

View File

@ -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);
}

57
src/Rtp/PSDecoder.cpp Normal file
View File

@ -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)

55
src/Rtp/PSDecoder.h Normal file
View File

@ -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

78
src/Rtp/RtpDecoder.cpp Normal file
View File

@ -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)

50
src/Rtp/RtpDecoder.h Normal file
View File

@ -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

268
src/Rtp/RtpProcess.cpp Normal file
View File

@ -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)

79
src/Rtp/RtpProcess.h Normal file
View File

@ -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

105
src/Rtp/RtpSelector.cpp Normal file
View File

@ -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)

58
src/Rtp/RtpSelector.h Normal file
View File

@ -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

78
src/Rtp/RtpSession.cpp Normal file
View File

@ -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)

57
src/Rtp/RtpSession.h Normal file
View File

@ -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

58
src/Rtp/RtpSplitter.cpp Normal file
View File

@ -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)

53
src/Rtp/RtpSplitter.h Normal file
View File

@ -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

56
src/Rtp/UdpRecver.cpp Normal file
View File

@ -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)

56
src/Rtp/UdpRecver.h Normal file
View File

@ -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

View File

@ -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;
};

View File

@ -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 */

View File

@ -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
* ready状态的track
*/
void onTrackReady(const Track::Ptr & track) override ;
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;
};

View File

@ -42,6 +42,12 @@ 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);
}
@ -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()));
@ -159,7 +164,7 @@ void RtspPlayer::play(bool isSSL,const string &strUrl, const string &strUser, co
}
void RtspPlayer::onConnect(const SockException &err){
if(err.getErrCode() != Err_success) {
onPlayResult_l(err);
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 &paramsStr) {
@ -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,28 +430,52 @@ 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) {
if (type == type_pause) {
//暂停成功!
_pRtpTimer.reset();
return;
}
//play或seek成功
uint32_t iSeekTo = 0;
//修正时间轴
auto strRange = parser["Range"];
@ -468,10 +490,7 @@ void RtspPlayer::handleResPAUSE(const Parser& parser, bool bPause) {
//设置相对时间戳
_stamp[0].setRelativeStamp(iSeekTo);
_stamp[1].setRelativeStamp(iSeekTo);
onPlayResult_l(SockException(Err_success, "rtsp play success"));
} else {
_pRtpTimer.reset();
}
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,55 +791,47 @@ 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>();
//创建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) {
} else if (ex) {
//播放成功后异常断开回调
onShutdown(ex);
} else {
//恢复播放
onResume();
}
}
if(ex){
teardown();
}
}
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;
}

View File

@ -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());

View File

@ -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 */

View File

@ -351,6 +351,9 @@ inline int RtspPusher::getTrackIndexByTrackType(TrackType type) {
return i;
}
}
if(_aTrackInfo.size() == 1){
return 0;
}
return -1;
}

View File

@ -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;
}

View File

@ -41,6 +41,7 @@
#include "RtspSplitter.h"
#include "RtpReceiver.h"
#include "RtspToRtmpMediaSource.h"
#include "Common/Stamp.h"
using namespace std;
using namespace toolkit;

View File

@ -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);

View File

@ -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);

View File

@ -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;

113
tests/test_rtp.cpp Normal file
View File

@ -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;
}

View File

@ -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;