Commit 5d1df021 by baiyfcu Committed by GitHub

Merge pull request #9 from xiongziliang/master

update
parents 612a9dc5 0bff2ad1
release/ filter=lfs diff=lfs merge=lfs -text
*.a filter=lfs diff=lfs merge=lfs -text
*.h linguist-language=cpp
*.c linguist-language=cpp
# These are supported funding model platforms
custom: ['https://www.paypal.me/xiachu']
ko_fi: xiachu
issuehunt: xiongziliang
liberapay: xiachu
Subproject commit e256dabd370220a5b19e3c5b54cf29dd5cccd48e
Subproject commit 8d1681b5bb247e7f47ae0f8c414f6eeb376b742b
......@@ -4,7 +4,7 @@ android {
compileSdkVersion 28
defaultConfig {
applicationId "com.zlmediakit.demo"
minSdkVersion 19
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
......
......@@ -174,6 +174,7 @@ JNI_API(jboolean,startDemo,jstring ini_dir){
mINI::Instance()["http.sslport"] = 8443;
mINI::Instance()["rtsp.port"] = 8554;
mINI::Instance()["rtsp.sslport"] = 8332;
mINI::Instance()["general.enableVhost"] = 0;
for(auto &pr : mINI::Instance()){
//替换hook默认地址
replace(pr.second,"https://127.0.0.1/","http://127.0.0.1:8080/");
......
......@@ -38,10 +38,10 @@ public class MainActivity extends AppCompatActivity {
if(permissionSuccess){
Toast.makeText(this,"你可以修改配置文件再启动:" + sd_dir + "/zlmediakit.ini" ,Toast.LENGTH_LONG).show();
Toast.makeText(this,"SSL证书请放置在:" + sd_dir + "/zlmediakit.pem" ,Toast.LENGTH_LONG).show();
ZLMediaKit.startDemo(sd_dir);
}else{
Toast.makeText(this,"请给予我权限,否则无法启动测试!" ,Toast.LENGTH_LONG).show();
}
ZLMediaKit.startDemo(sd_dir);
}
private ZLMediaKit.MediaPlayer _player;
......
......@@ -153,6 +153,15 @@ if(ENABLE_MP4RECORD)
endif(WIN32)
endif()
if(${CMAKE_BUILD_TYPE} MATCHES "Release")
#查找jemalloc是否安装
find_package(JEMALLOC QUIET)
if(JEMALLOC_FOUND)
message(STATUS "found library:\"${JEMALLOC_LIBRARIES}\"")
include_directories(${JEMALLOC_INCLUDE_DIR})
list(APPEND LINK_LIB_LIST ${JEMALLOC_LIBRARIES})
endif()
endif()
if (WIN32)
list(APPEND LINK_LIB_LIST WS2_32 Iphlpapi shlwapi)
......
......@@ -8,12 +8,11 @@
## Why ZLMediaKit?
- Developed based on C++ 11, the code is stable and reliable, avoiding the use of raw pointers, cross-platform porting is simple and convenient, and the code is clear and concise.
- Support rich streaming media protocols(`RTSP/RTMP/HLS/HTTP-FLV`),and support Inter-protocol conversion.
- Support rich streaming media protocols(`RTSP/RTMP/HLS/HTTP-FLV/Websocket-flv`),and support Inter-protocol conversion.
- Multiplexing asynchronous network IO based on epoll and multi thread,extreme performance.
- Well performance and stable test,can be used commercially.
- Support linux, macos, ios, android, Windows Platforms.
- Very low latency(lower then one second), video opened immediately.
- **Now Support websocket-flv!**
## Features
......
......@@ -4,14 +4,13 @@
## 项目特点
- 基于C++11开发,避免使用裸指针,代码稳定可靠;同时跨平台移植简单方便,代码清晰简洁。
- 打包多种流媒体协议(RTSP/RTMP/HLS),支持协议间的互相转换,提供一站式的服务。
- 打包多种流媒体协议(RTSP/RTMP/HLS/HTTP-FLV/Websocket-FLV),支持协议间的互相转换,提供一站式的服务。
- 使用epoll+线程池+异步网络IO模式开发,并发性能优越。
- 已实现主流的的H264/H265+AAC流媒体方案,代码精简,脉络清晰,适合学习。
- 编码格式与框架代码解耦,方便自由简洁的添加支持其他编码格式
- 代码经过大量的稳定性、性能测试,可满足商用服务器项目。
- 支持linux、macos、ios、android、windows平台
- 支持画面秒开(GOP缓存)、极低延时([500毫秒内,最低可达100毫秒](https://github.com/zlmediakit/ZLMediaKit/wiki/%E5%BB%B6%E6%97%B6%E6%B5%8B%E8%AF%95))
- **支持websocket-flv直播**
- [ZLMediaKit高并发实现原理](https://github.com/xiongziliang/ZLMediaKit/wiki/ZLMediaKit%E9%AB%98%E5%B9%B6%E5%8F%91%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86)
## 项目定位
......
......@@ -6,12 +6,13 @@ apiDebug=1
secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
[ffmpeg]
#FFmpeg可执行程序路径
#FFmpeg可执行程序绝对路径
bin=/usr/local/bin/ffmpeg
#FFmpeg拉流再推流的命令模板,通过该模板可以设置再编码的一些参数
cmd=%s -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s
#FFmpeg日志的路径,如果置空则不生成FFmpeg日志
log=/Users/xzl/git/ZLMediaKit/release/mac/Release/ffmpeg/ffmpeg.log
#可以为相对(相对于本可执行程序目录)或绝对路径
log=./ffmpeg/ffmpeg.log
[general]
#是否启用虚拟主机
......@@ -30,12 +31,18 @@ maxStreamWaitMS=5000
streamNoneReaderDelayMS=5000
#是否开启低延时模式,该模式下禁用MSG_MORE,启用TCP_NODEALY,延时将降低,但数据发送性能将降低
ultraLowDelay=1
#拉流代理是否添加静音音频(直接拉流模式本协议无效)
addMuteAudio=1
#拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
#如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
resetWhenRePlay=1
[hls]
#hls写文件的buf大小,调整参数可以提高文件io性能
fileBufSize=65536
#hls保存文件路径
filePath=/Users/xzl/git/ZLMediaKit/release/mac/Release/httpRoot
#可以为相对(相对于本可执行程序目录)或绝对路径
filePath=./httpRoot
#hls最大切片时间
segDur=3
#m3u8索引中,hls保留切片个数(实际保留切片个数大2~3个)
......@@ -88,7 +95,8 @@ notFound=<html><head><title>404 Not Found</title></head><body bgcolor="white"><c
#http服务器监听端口
port=80
#http文件服务器根目录
rootPath=/Users/xzl/git/ZLMediaKit/release/mac/Release/httpRoot
#可以为相对(相对于本可执行程序目录)或绝对路径
rootPath=./httpRoot
#http文件服务器读文件缓存大小,单位BYTE,调整该参数可以优化文件io性能
sendBufSize=65536
#https服务器监听端口
......@@ -109,12 +117,17 @@ appName=record
#mp4录制写文件缓存,单位BYTE,调整参数可以提高文件io性能
fileBufSize=65536
#mp4录制保存、mp4点播根路径
filePath=/Users/xzl/git/ZLMediaKit/release/mac/Release/httpRoot
#可以为相对(相对于本可执行程序目录)或绝对路径
filePath=./httpRoot
#mp4录制切片时间,单位秒
fileSecond=3600
#mp4点播每次流化数据量,单位毫秒,
#减少该值可以让点播数据发送量更平滑,增大该值则更节省cpu资源
sampleMS=100
sampleMS=500
#mp4录制完成后是否进行二次关键帧索引写入头部
fastStart=0
#MP4点播(rtsp/rtmp/http-flv/ws-flv)是否循环播放文件
fileRepeat=0
[rtmp]
#rtmp必须在此时间内完成握手,否则服务器会断开链接,单位秒
......@@ -158,6 +171,8 @@ keepAliveSecond=15
port=554
#rtsps服务器监听地址
sslport=322
#在接收rtsp推流时,是否重新生成时间戳(很多推流器的时间戳着实很烂)
modifyStamp=1
[shell]
#调试telnet服务器接受最大bufffer大小
......
......@@ -39,7 +39,7 @@ const char kLog[] = FFmpeg_FIELD"log";
onceToken token([]() {
mINI::Instance()[kBin] = trim(System::execute("which ffmpeg"));
mINI::Instance()[kCmd] = "%s -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s";
mINI::Instance()[kLog] = exeDir() + "ffmpeg/ffmpeg.log";
mINI::Instance()[kLog] = "./ffmpeg/ffmpeg.log";
});
}
......@@ -64,7 +64,7 @@ void FFmpegSource::play(const string &src_url,const string &dst_url,int timeout_
char cmd[1024] = {0};
snprintf(cmd, sizeof(cmd),ffmpeg_cmd.data(),ffmpeg_bin.data(),src_url.data(),dst_url.data());
_process.run(cmd,ffmpeg_log);
_process.run(cmd,File::absolutePath("",ffmpeg_log));
InfoL << cmd;
if(_media_info._host == "127.0.0.1"){
......
......@@ -34,9 +34,8 @@
#include "Util/File.h"
#include "Util/logger.h"
#include "Util/uv_errno.h"
#include "Util/TimeTicker.h"
#include "Thread/WorkThreadPool.h"
#include "Process.h"
#include "Poller/Timer.h"
using namespace toolkit;
void Process::run(const string &cmd, const string &log_file_tmp) {
......@@ -46,12 +45,11 @@ void Process::run(const string &cmd, const string &log_file_tmp) {
throw std::runtime_error(StrPrinter << "fork child process falied,err:" << get_uv_errmsg());
}
if (_pid == 0) {
//子进程
//子进程关闭core文件生成
struct rlimit rlim = {0,0};
setrlimit(RLIMIT_CORE, &rlim);
//在启动子进程时,暂时禁用SIGINT、SIGTERM信号
// ignore the SIGINT and SIGTERM
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
......@@ -109,24 +107,73 @@ void Process::run(const string &cmd, const string &log_file_tmp) {
InfoL << "start child proces " << _pid;
}
void Process::kill(int max_delay) {
if (_pid <= 0) {
/**
* 获取进程是否存活状态
* @param pid 进程号
* @param exit_code_ptr 进程返回代码
* @param block 是否阻塞等待
* @return 进程是否还在运行
*/
static bool s_wait(pid_t pid,int *exit_code_ptr,bool block) {
if (pid <= 0) {
return false;
}
int status = 0;
pid_t p = waitpid(pid, &status, block ? 0 : WNOHANG);
int exit_code = (status & 0xFF00) >> 8;
if(exit_code_ptr){
*exit_code_ptr = (status & 0xFF00) >> 8;
}
if (p < 0) {
WarnL << "waitpid failed, pid=" << pid << ", err=" << get_uv_errmsg();
return false;
}
if (p > 0) {
InfoL << "process terminated, pid=" << pid << ", exit code=" << exit_code;
return false;
}
//WarnL << "process is running, pid=" << _pid;
return true;
}
static void s_kill(pid_t pid,int max_delay,bool force){
if (pid <= 0) {
//pid无效
return;
}
if (::kill(_pid, SIGTERM) == -1) {
WarnL << "kill process " << _pid << " falied,err:" << get_uv_errmsg();
} else {
//等待子进程退出
auto pid = _pid;
EventPollerPool::Instance().getPoller()->doDelayTask(max_delay,[pid](){
//最多等待2秒,2秒后强制杀掉程序
if (waitpid(pid, NULL, WNOHANG) == 0) {
::kill(pid, SIGKILL);
WarnL << "force kill process " << pid;
}
if (::kill(pid, force ? SIGKILL : SIGTERM) == -1) {
//进程可能已经退出了
WarnL << "kill process " << pid << " failed:" << get_uv_errmsg();
return;
}
if(force){
//发送SIGKILL信号后,阻塞等待退出
s_wait(pid, NULL, true);
DebugL << "force kill " << pid << " success!";
return;
}
//发送SIGTERM信号后,2秒后检查子进程是否已经退出
WorkThreadPool::Instance().getPoller()->doDelayTask(max_delay,[pid](){
if (!s_wait(pid, nullptr, false)) {
//进程已经退出了
return 0;
});
}
//进程还在运行
WarnL << "process still working,force kill it:" << pid;
s_kill(pid,0, true);
return 0;
});
}
void Process::kill(int max_delay,bool force) {
if (_pid <= 0) {
return;
}
s_kill(_pid,max_delay,force);
_pid = -1;
}
......@@ -134,28 +181,10 @@ Process::~Process() {
kill(2000);
}
Process::Process() {
}
Process::Process() {}
bool Process::wait(bool block) {
if (_pid <= 0) {
return false;
}
int status = 0;
pid_t p = waitpid(_pid, &status, block ? 0 : WNOHANG);
_exit_code = (status & 0xFF00) >> 8;
if (p < 0) {
WarnL << "waitpid failed, pid=" << _pid << ", err=" << get_uv_errmsg();
return false;
}
if (p > 0) {
InfoL << "process terminated, pid=" << _pid << ", exit code=" << _exit_code;
return false;
}
//WarnL << "process is running, pid=" << _pid;
return true;
return s_wait(_pid,&_exit_code,block);
}
int Process::exit_code() {
......
......@@ -36,7 +36,7 @@ public:
Process();
~Process();
void run(const string &cmd,const string &log_file);
void kill(int max_delay);
void kill(int max_delay,bool force = false);
bool wait(bool block = true);
int exit_code();
private:
......
......@@ -45,6 +45,7 @@
#include "Util/MD5.h"
#include "WebApi.h"
#include "WebHook.h"
#include "Thread/WorkThreadPool.h"
#if !defined(_WIN32)
#include "FFmpegSource.h"
......@@ -180,19 +181,35 @@ static inline void addHttpListener(){
if(api_debug){
auto newInvoker = [invoker,parser,allArgs](const string &codeOut,
const HttpSession::KeyValue &headerOut,
const string &contentOut){
const HttpBody::Ptr &body){
stringstream ss;
for(auto &pr : allArgs ){
ss << pr.first << " : " << pr.second << "\r\n";
}
DebugL << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n"
<< "# content:\r\n" << parser.Content() << "\r\n"
<< "# args:\r\n" << ss.str()
<< "# response:\r\n"
<< contentOut << "\r\n";
//body默认为空
int64_t size = 0;
if (body && body->remainSize()) {
//有body,获取body大小
size = body->remainSize();
}
invoker(codeOut,headerOut,contentOut);
if(size < 4 * 1024){
string contentOut = body->readData(size)->toString();
DebugL << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n"
<< "# content:\r\n" << parser.Content() << "\r\n"
<< "# args:\r\n" << ss.str()
<< "# response:\r\n"
<< contentOut << "\r\n";
invoker(codeOut,headerOut,contentOut);
} else{
DebugL << "\r\n# request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n"
<< "# content:\r\n" << parser.Content() << "\r\n"
<< "# args:\r\n" << ss.str()
<< "# response size:"
<< size <<"\r\n";
invoker(codeOut,headerOut,body);
}
};
((HttpSession::HttpResponseInvoker &)invoker) = newInvoker;
}
......@@ -281,6 +298,23 @@ void installWebApi() {
});
});
//获取后台工作线程负载
//测试url http://127.0.0.1/index/api/getWorkThreadsLoad
API_REGIST_INVOKER(api, getWorkThreadsLoad, {
WorkThreadPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
Value val;
auto vec = WorkThreadPool::Instance().getExecutorLoad();
int i = 0;
for (auto load : vec) {
Value obj(objectValue);
obj["load"] = load;
obj["delay"] = vecDelay[i++];
val["data"].append(obj);
}
invoker("200 OK", headerOut, val.toStyledString());
});
});
//获取服务器配置
//测试url http://127.0.0.1/index/api/getServerConfig
API_REGIST(api, getServerConfig, {
......@@ -578,6 +612,13 @@ void installWebApi() {
});
#endif
//新增http api下载可执行程序文件接口
//测试url http://127.0.0.1/index/api/downloadBin
API_REGIST_INVOKER(api,downloadBin,{
CHECK_SECRET();
invoker.responseFile(headerIn,StrCaseMap(),exePath());
});
////////////以下是注册的Hook API////////////
API_REGIST(hook,on_publish,{
//开始推流事件
......
......@@ -32,19 +32,9 @@ namespace mediakit{
void MediaSink::addTrack(const Track::Ptr &track_in) {
lock_guard<recursive_mutex> lck(_mtx);
//克隆Track,只拷贝其数据,不拷贝其数据转发关系
//克隆Track,只拷贝其数据,不拷贝其数据转发关系
auto track = track_in->clone();
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);
}
}));
auto codec_id = track->getCodecId();
_track_map[codec_id] = track;
auto lam = [this,track](){
......@@ -58,6 +48,26 @@ void MediaSink::addTrack(const Track::Ptr &track_in) {
_trackReadyCallback[codec_id] = lam;
_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);
}
}));
}
void MediaSink::resetTracks() {
lock_guard<recursive_mutex> lck(_mtx);
_anyTrackUnReady = false;
_allTrackReady = false;
_track_map.clear();
_trackReadyCallback.clear();
_ticker.resetTime();
}
void MediaSink::inputFrame(const Frame::Ptr &frame) {
......
......@@ -52,7 +52,7 @@ public:
* 输入frame
* @param frame
*/
void inputFrame(const Frame::Ptr &frame) override ;
void inputFrame(const Frame::Ptr &frame) override;
/**
* 添加track,内部会调用Track的clone方法
......@@ -61,21 +61,24 @@ public:
*/
virtual void addTrack(const Track::Ptr & track);
/**
* 重置track
*/
virtual void resetTracks();
/**
* 全部Track是否都准备好了
* @return
*/
bool isAllTrackReady() const ;
bool isAllTrackReady() const;
/**
* 获取特定类型的Track
* @param type track类型
* @param trackReady 是否获取已经准备好的Track
* @param trackReady 是否获取已经准备好的Track
* @return
*/
Track::Ptr getTrack(TrackType type,bool trackReady = true) const ;
Track::Ptr getTrack(TrackType type,bool trackReady = true) const;
protected:
/**
* 某track已经准备好,其ready()状态返回true,
......
......@@ -70,6 +70,19 @@ public:
}
/**
* 重置音视频媒体
*/
void resetTracks() {
if(_rtmp){
_rtmp->resetTracks();
}
if(_rtsp){
_rtsp->resetTracks();
}
_record->resetTracks();
}
/**
* 写入帧数据然后打包rtmp
* @param frame 帧数据
*/
......
......@@ -163,7 +163,7 @@ class Parser {
for (string &key_val : arg_vec) {
auto key = FindField(key_val.data(), NULL, key_delim);
auto val = FindField(key_val.data(), key_delim, NULL);
ret.emplace_force(key,val);
ret.emplace_force(trim(key),trim(val));
}
return ret;
}
......
......@@ -77,12 +77,17 @@ const string kStreamNoneReaderDelayMS = GENERAL_FIELD"streamNoneReaderDelayMS";
const string kMaxStreamWaitTimeMS = GENERAL_FIELD"maxStreamWaitMS";
const string kEnableVhost = GENERAL_FIELD"enableVhost";
const string kUltraLowDelay = GENERAL_FIELD"ultraLowDelay";
const string kAddMuteAudio = GENERAL_FIELD"addMuteAudio";
const string kResetWhenRePlay = GENERAL_FIELD"resetWhenRePlay";
onceToken token([](){
mINI::Instance()[kFlowThreshold] = 1024;
mINI::Instance()[kStreamNoneReaderDelayMS] = 5 * 1000;
mINI::Instance()[kMaxStreamWaitTimeMS] = 5 * 1000;
mINI::Instance()[kEnableVhost] = 1;
mINI::Instance()[kUltraLowDelay] = 1;
mINI::Instance()[kAddMuteAudio] = 1;
mINI::Instance()[kResetWhenRePlay] = 1;
},nullptr);
}//namespace General
......@@ -117,7 +122,7 @@ const string kMaxReqCount = HTTP_FIELD"maxReqCount";
const string kCharSet = HTTP_FIELD"charSet";
//http 服务器根目录
#define HTTP_ROOT_PATH (exeDir() + "httpRoot")
#define HTTP_ROOT_PATH "./httpRoot"
const string kRootPath = HTTP_FIELD"rootPath";
//http 404错误提示内容
......@@ -172,7 +177,7 @@ onceToken token([](){
mINI::Instance()[kHandshakeSecond] = 15;
mINI::Instance()[kKeepAliveSecond] = 15;
mINI::Instance()[kDirectProxy] = 1;
mINI::Instance()[kModifyStamp] = true;
mINI::Instance()[kModifyStamp] = false;
},nullptr);
} //namespace Rtsp
......@@ -254,7 +259,7 @@ namespace Record {
const string kAppName = RECORD_FIELD"appName";
//每次流化MP4文件的时长,单位毫秒
#define RECORD_SAMPLE_MS 100
#define RECORD_SAMPLE_MS 500
const string kSampleMS = RECORD_FIELD"sampleMS";
//MP4文件录制大小,默认一个小时
......@@ -268,6 +273,9 @@ const string kFilePath = RECORD_FIELD"filePath";
//mp4文件写缓存大小
const string kFileBufSize = RECORD_FIELD"fileBufSize";
//mp4录制完成后是否进行二次关键帧索引写入头部
const string kFastStart = RECORD_FIELD"fastStart";
//mp4文件是否重头循环读取
const string kFileRepeat = RECORD_FIELD"fileRepeat";
......@@ -277,6 +285,7 @@ onceToken token([](){
mINI::Instance()[kFileSecond] = RECORD_FILE_SECOND;
mINI::Instance()[kFilePath] = RECORD_FILE_PATH;
mINI::Instance()[kFileBufSize] = 64 * 1024;
mINI::Instance()[kFastStart] = false;
mINI::Instance()[kFileRepeat] = false;
},nullptr);
......
......@@ -177,6 +177,11 @@ extern const string kMaxStreamWaitTimeMS;
extern const string kEnableVhost;
//超低延时模式,默认打开,打开后会降低延时但是转发性能会稍差
extern const string kUltraLowDelay;
//拉流代理时是否添加静音音频
extern const string kAddMuteAudio;
//拉流代理时如果断流再重连成功是否删除前一次的媒体流数据,如果删除将重新开始,
//如果不删除将会接着上一次的数据继续写(录制hls/mp4时会继续在前一个文件后面写)
extern const string kResetWhenRePlay;
}//namespace General
......@@ -268,6 +273,8 @@ extern const string kFileSecond;
extern const string kFilePath;
//mp4文件写缓存大小
extern const string kFileBufSize;
//mp4录制完成后是否进行二次关键帧索引写入头部
extern const string kFastStart;
//mp4文件是否重头循环读取
extern const string kFileRepeat;
} //namespace Record
......
......@@ -41,8 +41,8 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) {
aac_cfg_str = FindField(track->_fmtp.data(), "config=", ";");
}
if (aac_cfg_str.empty()) {
//延后获取adts头
return std::make_shared<AACTrack>();
//如果sdp中获取不到aac config信息,那么在rtp也无法获取,那么忽略该Track
return nullptr;
}
string aac_cfg;
......@@ -60,10 +60,8 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) {
}
if (strcasecmp(track->_codec.data(), "h264") == 0) {
auto map = Parser::parseArgs(track->_fmtp," ","=");
for(auto &pr : map){
trim(pr.second," ;");
}
//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>();
......@@ -77,10 +75,7 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) {
if (strcasecmp(track->_codec.data(), "h265") == 0) {
//a=fmtp:96 sprop-sps=QgEBAWAAAAMAsAAAAwAAAwBdoAKAgC0WNrkky/AIAAADAAgAAAMBlQg=; sprop-pps=RAHA8vA8kAA=
auto map = Parser::parseArgs(track->_fmtp," ","=");
for(auto &pr : map){
trim(pr.second," ;");
}
auto map = Parser::parseArgs(FindField(track->_fmtp.data()," ", nullptr),";","=");
auto vps = decodeBase64(map["sprop-vps"]);
auto sps = decodeBase64(map["sprop-sps"]);
auto pps = decodeBase64(map["sprop-pps"]);
......
......@@ -390,7 +390,7 @@ public:
_printer << "m=video 0 RTP/AVP " << playload_type << "\r\n";
_printer << "b=AS:" << bitrate << "\r\n";
_printer << "a=rtpmap:" << playload_type << " H264/" << 90000 << "\r\n";
_printer << "a=fmtp:" << playload_type << " packetization-mode=1;profile-level-id=";
_printer << "a=fmtp:" << playload_type << " packetization-mode=1; profile-level-id=";
char strTemp[100];
uint32_t profile_level_id = 0;
......@@ -402,7 +402,7 @@ public:
memset(strTemp, 0, 100);
sprintf(strTemp, "%06X", profile_level_id);
_printer << strTemp;
_printer << ";sprop-parameter-sets=";
_printer << "; sprop-parameter-sets=";
memset(strTemp, 0, 100);
av_base64_encode(strTemp, 100, (uint8_t *) strSPS.data(), strSPS.size());
_printer << strTemp << ",";
......
/*
* 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 "HttpBody.h"
#include "Util/util.h"
#include "Util/uv_errno.h"
#include "Util/logger.h"
#include "HttpClient.h"
#ifndef _WIN32
#include <sys/mman.h>
#endif
#ifndef _WIN32
#define ENABLE_MMAP
#endif
namespace mediakit {
HttpStringBody::HttpStringBody(const string &str){
_str = str;
}
uint64_t HttpStringBody::remainSize() {
return _str.size() - _offset;
}
Buffer::Ptr HttpStringBody::readData(uint32_t size) {
size = MIN(remainSize(),size);
if(!size){
//没有剩余字节了
return nullptr;
}
auto ret = std::make_shared<BufferString>(_str,_offset,size);
_offset += size;
return ret;
}
//////////////////////////////////////////////////////////////////
HttpFileBody::HttpFileBody(const string &filePath){
std::shared_ptr<FILE> fp(fopen(filePath.data(), "rb"), [](FILE *fp) {
if(fp){
fclose(fp);
}
});
if(!fp){
init(fp,0,0);
}else{
init(fp,0,HttpMultiFormBody::fileSize(fp.get()));
}
}
HttpFileBody::HttpFileBody(const std::shared_ptr<FILE> &fp, uint64_t offset, uint64_t max_size) {
init(fp,offset,max_size);
}
void HttpFileBody::init(const std::shared_ptr<FILE> &fp,uint64_t offset,uint64_t max_size){
_fp = fp;
_max_size = max_size;
#ifdef ENABLE_MMAP
do {
if(!_fp){
//文件不存在
break;
}
int fd = fileno(fp.get());
if (fd < 0) {
WarnL << "fileno failed:" << get_uv_errmsg(false);
break;
}
auto ptr = (char *) mmap(NULL, max_size, PROT_READ, MAP_SHARED, fd, offset);
if (ptr == MAP_FAILED) {
WarnL << "mmap failed:" << get_uv_errmsg(false);
break;
}
_map_addr.reset(ptr,[max_size,fp](char *ptr){
munmap(ptr,max_size);
});
} while (false);
#endif
if(!_map_addr && offset && fp.get()){
//未映射,那么fseek设置偏移量
fseek(fp.get(), offset, SEEK_SET);
}
}
class BufferMmap : public Buffer{
public:
typedef std::shared_ptr<BufferMmap> Ptr;
BufferMmap(const std::shared_ptr<char> &map_addr,uint64_t offset,int size){
_map_addr = map_addr;
_data = map_addr.get() + offset;
_size = size;
};
virtual ~BufferMmap(){};
//返回数据长度
char *data() const override {
return _data;
}
uint32_t size() const override{
return _size;
}
private:
std::shared_ptr<char> _map_addr;
char *_data;
uint32_t _size;
};
uint64_t HttpFileBody::remainSize() {
return _max_size - _offset;
}
Buffer::Ptr HttpFileBody::readData(uint32_t size) {
size = MIN(remainSize(),size);
if(!size){
//没有剩余字节了
return nullptr;
}
if(!_map_addr){
//fread模式
int iRead;
auto ret = _pool.obtain();
ret->setCapacity(size + 1);
do{
iRead = fread(ret->data(), 1, size, _fp.get());
}while(-1 == iRead && UV_EINTR == get_uv_error(false));
if(iRead > 0){
//读到数据了
ret->setSize(iRead);
_offset += iRead;
return std::move(ret);
}
//读取文件异常,文件真实长度小于声明长度
_offset = _max_size;
WarnL << "read file err:" << get_uv_errmsg();
return nullptr;
}
//mmap模式
auto ret = std::make_shared<BufferMmap>(_map_addr,_offset,size);
_offset += size;
return std::move(ret);
}
//////////////////////////////////////////////////////////////////
HttpMultiFormBody::HttpMultiFormBody(const HttpArgs &args,const string &filePath,const string &boundary){
std::shared_ptr<FILE> fp(fopen(filePath.data(), "rb"), [](FILE *fp) {
if(fp){
fclose(fp);
}
});
if(!fp){
throw std::invalid_argument(StrPrinter << "open file failed:" << filePath << " " << get_uv_errmsg());
}
_fileBody = std::make_shared<HttpFileBody>(fp, 0, fileSize(fp.get()));
auto fileName = filePath;
auto pos = filePath.rfind('/');
if(pos != string::npos){
fileName = filePath.substr(pos + 1);
}
_bodyPrefix = multiFormBodyPrefix(args,boundary,fileName);
_bodySuffix = multiFormBodySuffix(boundary);
_totalSize = _bodyPrefix.size() + _bodySuffix.size() + _fileBody->remainSize();
}
uint64_t HttpMultiFormBody::remainSize() {
return _totalSize - _offset;
}
Buffer::Ptr HttpMultiFormBody::readData(uint32_t size){
if(_bodyPrefix.size()){
auto ret = std::make_shared<BufferString>(_bodyPrefix);
_offset += _bodyPrefix.size();
_bodyPrefix.clear();
return ret;
}
if(_fileBody->remainSize()){
auto ret = _fileBody->readData(size);
if(!ret){
//读取文件出现异常,提前中断
_offset = _totalSize;
}else{
_offset += ret->size();
}
return ret;
}
if(_bodySuffix.size()){
auto ret = std::make_shared<BufferString>(_bodySuffix);
_offset = _totalSize;
_bodySuffix.clear();
return ret;
}
return nullptr;
}
string HttpMultiFormBody::multiFormBodySuffix(const string &boundary){
string MPboundary = string("--") + boundary;
string endMPboundary = MPboundary + "--";
_StrPrinter body;
body << "\r\n" << endMPboundary;
return body;
}
uint64_t HttpMultiFormBody::fileSize(FILE *fp) {
auto current = ftell(fp);
fseek(fp,0L,SEEK_END); /* 定位到文件末尾 */
auto end = ftell(fp); /* 得到文件大小 */
fseek(fp,current,SEEK_SET);
return end - current;
}
string HttpMultiFormBody::multiFormContentType(const string &boundary){
return StrPrinter << "multipart/form-data; boundary=" << boundary;
}
string HttpMultiFormBody::multiFormBodyPrefix(const HttpArgs &args,const string &boundary,const string &fileName){
string MPboundary = string("--") + boundary;
_StrPrinter body;
for(auto &pr : args){
body << MPboundary << "\r\n";
body << "Content-Disposition: form-data; name=\"" << pr.first << "\"\r\n\r\n";
body << pr.second << "\r\n";
}
body << MPboundary << "\r\n";
body << "Content-Disposition: form-data; name=\"" << "file" << "\";filename=\"" << fileName << "\"\r\n";
body << "Content-Type: application/octet-stream\r\n\r\n" ;
return body;
}
}//namespace mediakit
/*
* 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_FILEREADER_H
#define ZLMEDIAKIT_FILEREADER_H
#include <stdlib.h>
#include <memory>
#include "Network/Buffer.h"
#include "Util/ResourcePool.h"
#include "Util/logger.h"
using namespace std;
using namespace toolkit;
#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b) )
#endif //MIN
namespace mediakit {
/**
* http content部分基类定义
*/
class HttpBody{
public:
typedef std::shared_ptr<HttpBody> Ptr;
HttpBody(){}
virtual ~HttpBody(){}
/**
* 剩余数据大小
*/
virtual uint64_t remainSize() { return 0;};
/**
* 读取一定字节数,返回大小可能小于size
* @param size 请求大小
* @return 字节对象
*/
virtual Buffer::Ptr readData(uint32_t size) { return nullptr;};
};
/**
* string类型的content
*/
class HttpStringBody : public HttpBody{
public:
typedef std::shared_ptr<HttpStringBody> Ptr;
HttpStringBody(const string &str);
virtual ~HttpStringBody(){}
uint64_t remainSize() override ;
Buffer::Ptr readData(uint32_t size) override ;
private:
mutable string _str;
uint64_t _offset = 0;
};
/**
* 文件类型的content
*/
class HttpFileBody : public HttpBody{
public:
typedef std::shared_ptr<HttpFileBody> Ptr;
/**
* 构造函数
* @param fp 文件句柄,文件的偏移量必须为0
* @param offset 相对文件头的偏移量
* @param max_size 最大读取字节数,未判断是否大于文件真实大小
*/
HttpFileBody(const std::shared_ptr<FILE> &fp,uint64_t offset,uint64_t max_size);
HttpFileBody(const string &file_path);
~HttpFileBody(){};
uint64_t remainSize() override ;
Buffer::Ptr readData(uint32_t size) override;
private:
void init(const std::shared_ptr<FILE> &fp,uint64_t offset,uint64_t max_size);
private:
std::shared_ptr<FILE> _fp;
uint64_t _max_size;
uint64_t _offset = 0;
std::shared_ptr<char> _map_addr;
ResourcePool<BufferRaw> _pool;
};
class HttpArgs;
/**
* http MultiForm 方式提交的http content
*/
class HttpMultiFormBody : public HttpBody {
public:
typedef std::shared_ptr<HttpMultiFormBody> Ptr;
/**
* 构造函数
* @param args http提交参数列表
* @param filePath 文件路径
* @param boundary boundary字符串
*/
HttpMultiFormBody(const HttpArgs &args,const string &filePath,const string &boundary = "0xKhTmLbOuNdArY");
virtual ~HttpMultiFormBody(){}
uint64_t remainSize() override ;
Buffer::Ptr readData(uint32_t size) override;
public:
static string multiFormBodyPrefix(const HttpArgs &args,const string &boundary,const string &fileName);
static string multiFormBodySuffix(const string &boundary);
static uint64_t fileSize(FILE *fp);
static string multiFormContentType(const string &boundary);
private:
string _bodyPrefix;
string _bodySuffix;
uint64_t _offset = 0;
uint64_t _totalSize;
HttpFileBody::Ptr _fileBody;
};
}//namespace mediakit
#endif //ZLMEDIAKIT_FILEREADER_H
......@@ -242,8 +242,9 @@ void HttpClient::onRecvContent(const char *data, uint64_t len) {
void HttpClient::onFlush() {
_aliveTicker.resetTime();
GET_CONFIG(uint32_t,sendBufSize,Http::kSendBufSize);
while (_body && _body->remainSize() && !isSocketBusy()) {
auto buffer = _body->readData();
auto buffer = _body->readData(sendBufSize);
if (!buffer) {
//数据发送结束或读取数据异常
break;
......
......@@ -39,7 +39,7 @@
#include "HttpCookie.h"
#include "HttpChunkedSplitter.h"
#include "strCoding.h"
#include "HttpBody.h"
using namespace std;
using namespace toolkit;
......@@ -64,145 +64,6 @@ public:
}
};
class HttpBody{
public:
typedef std::shared_ptr<HttpBody> Ptr;
HttpBody(){}
virtual ~HttpBody(){}
//剩余数据大小
virtual uint64_t remainSize() = 0;
virtual Buffer::Ptr readData() = 0;
};
class HttpStringBody : public HttpBody{
public:
typedef std::shared_ptr<HttpStringBody> Ptr;
HttpStringBody(const string &str){
_str = str;
}
virtual ~HttpStringBody(){}
uint64_t remainSize() override {
return _str.size();
}
Buffer::Ptr readData() override {
auto ret = std::make_shared<BufferString>(_str);
_str.clear();
return ret;
}
private:
mutable string _str;
};
class HttpMultiFormBody : public HttpBody {
public:
typedef std::shared_ptr<HttpMultiFormBody> Ptr;
template<typename MapType>
HttpMultiFormBody(const MapType &args,const string &filePath,const string &boundary,uint32_t sliceSize = 4 * 1024){
_fp = fopen(filePath.data(),"rb");
if(!_fp){
throw std::invalid_argument(StrPrinter << "打开文件失败:" << filePath << " " << get_uv_errmsg());
}
auto fileName = filePath;
auto pos = filePath.rfind('/');
if(pos != string::npos){
fileName = filePath.substr(pos + 1);
}
_bodyPrefix = multiFormBodyPrefix(args,boundary,fileName);
_bodySuffix = multiFormBodySuffix(boundary);
_totalSize = _bodyPrefix.size() + _bodySuffix.size() + fileSize(_fp);
_sliceSize = sliceSize;
}
virtual ~HttpMultiFormBody(){
fclose(_fp);
}
uint64_t remainSize() override {
return _totalSize - _offset;
}
Buffer::Ptr readData() override{
if(_bodyPrefix.size()){
auto ret = std::make_shared<BufferString>(_bodyPrefix);
_offset += _bodyPrefix.size();
_bodyPrefix.clear();
return ret;
}
if(0 == feof(_fp)){
auto ret = std::make_shared<BufferRaw>(_sliceSize);
//读文件
int size;
do{
size = fread(ret->data(),1,_sliceSize,_fp);
}while(-1 == size && UV_EINTR == get_uv_error(false));
if(size == -1){
_offset = _totalSize;
WarnL << "fread failed:" << get_uv_errmsg();
return nullptr;
}
_offset += size;
ret->setSize(size);
return ret;
}
if(_bodySuffix.size()){
auto ret = std::make_shared<BufferString>(_bodySuffix);
_offset = _totalSize;
_bodySuffix.clear();
return ret;
}
return nullptr;
}
public:
template<typename MapType>
static string multiFormBodyPrefix(const MapType &args,const string &boundary,const string &fileName){
string MPboundary = string("--") + boundary;
_StrPrinter body;
for(auto &pr : args){
body << MPboundary << "\r\n";
body << "Content-Disposition: form-data; name=\"" << pr.first << "\"\r\n\r\n";
body << pr.second << "\r\n";
}
body << MPboundary << "\r\n";
body << "Content-Disposition: form-data; name=\"" << "file" << "\";filename=\"" << fileName << "\"\r\n";
body << "Content-Type: application/octet-stream\r\n\r\n" ;
return body;
}
static string multiFormBodySuffix(const string &boundary){
string MPboundary = string("--") + boundary;
string endMPboundary = MPboundary + "--";
_StrPrinter body;
body << "\r\n" << endMPboundary;
return body;
}
static uint64_t fileSize(FILE *fp) {
auto current = ftell(fp);
fseek(fp,0L,SEEK_END); /* 定位到文件末尾 */
auto end = ftell(fp); /* 得到文件大小 */
fseek(fp,current,SEEK_SET);
return end - current;
}
static string multiFormContentType(const string &boundary){
return StrPrinter << "multipart/form-data; boundary=" << boundary;
}
private:
FILE *_fp;
string _bodyPrefix;
string _bodySuffix;
uint64_t _offset = 0;
uint64_t _totalSize;
uint32_t _sliceSize;
};
class HttpClient : public TcpClient , public HttpRequestSplitter
{
public:
......
......@@ -36,21 +36,44 @@
#include "HttpRequestSplitter.h"
#include "WebSocketSplitter.h"
#include "HttpCookieManager.h"
#include "HttpBody.h"
#include "Util/function_traits.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,
public WebSocketSplitter {
public:
typedef StrCaseMap KeyValue;
typedef std::function<void(const string &codeOut,
const KeyValue &headerOut,
const string &contentOut)> HttpResponseInvoker;
typedef HttpResponseInvokerImp HttpResponseInvoker;
/**
* @param errMsg 如果为空,则代表鉴权通过,否则为错误提示
......@@ -67,6 +90,7 @@ public:
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 ;
......@@ -118,13 +142,9 @@ private:
bool emitHttpEvent(bool doInvoke);
void urlDecode(Parser &parser);
void sendNotFound(bool bClose);
void sendResponse(const char *pcStatus,const KeyValue &header,const string &strContent);
KeyValue makeHttpHeader(bool bClose=false,int64_t iContentSize=-1,const char *pcContentType="text/html");
void responseDelay(bool bClose,
const string &codeOut,
const KeyValue &headerOut,
const string &contentOut);
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客户端是否有权限访问文件的逻辑步骤
*
......@@ -160,6 +180,7 @@ private:
//处理content数据的callback
function<bool (const char *data,uint64_t len) > _contentCallBack;
bool _flv_over_websocket = false;
bool _is_flv_stream = false;
};
......
......@@ -42,6 +42,7 @@ HlsMaker::~HlsMaker() {
void HlsMaker::makeIndexFile(bool eof) {
char file_content[1024];
int maxSegmentDuration = 0;
for (auto &tp : _seg_dur_list) {
int dur = std::get<0>(tp);
if (dur > maxSegmentDuration) {
......@@ -57,7 +58,7 @@ void HlsMaker::makeIndexFile(bool eof) {
"#EXT-X-TARGETDURATION:%u\n"
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
(maxSegmentDuration + 999) / 1000,
_file_index);
_seg_number ? _file_index : 0);
m3u8.assign(file_content);
......@@ -75,11 +76,18 @@ void HlsMaker::makeIndexFile(bool eof) {
void HlsMaker::inputData(void *data, uint32_t len, uint32_t timestamp) {
addNewFile(timestamp);
onWriteFile((char *) data, len);
//分片数据中断结束
if (data && len) {
addNewSegment(timestamp);
onWriteSegment((char *) data, len);
//记录上次写入数据时间
_ticker_last_data.resetTime();
} else {
flushLastSegment(true);
}
}
void HlsMaker::delOldFile() {
void HlsMaker::delOldSegment() {
if(_seg_number == 0){
//如果设置为保留0个切片,则认为是保存为点播
return;
......@@ -91,22 +99,38 @@ void HlsMaker::delOldFile() {
//但是实际保存的切片个数比m3u8所述多两个,这样做的目的是防止播放器在切片删除前能下载完毕
if (_file_index >= _seg_number + 4) {
onDelFile(_file_index - _seg_number - 4);
onDelSegment(_file_index - _seg_number - 4);
}
}
void HlsMaker::addNewFile(uint32_t) {
int stampInc = _ticker.elapsedTime();
if (stampInc >= _seg_duration * 1000) {
_ticker.resetTime();
auto file_name = onOpenFile(_file_index);
if (_file_index++ > 0) {
_seg_dur_list.push_back(std::make_tuple(stampInc, _last_file_name));
delOldFile();
makeIndexFile();
}
_last_file_name = file_name;
void HlsMaker::addNewSegment(uint32_t) {
if(!_last_file_name.empty() && _ticker.elapsedTime() < _seg_duration * 1000){
//存在上个切片,并且未到分片时间
return;
}
//关闭并保存上一个切片
flushLastSegment();
//新增切片
_last_file_name = onOpenSegment(_file_index++);
//重置切片计时器
_ticker.resetTime();
}
void HlsMaker::flushLastSegment(bool eof){
if(_last_file_name.empty()){
//不存在上个切片
return;
}
//文件创建到最后一次数据写入的时间即为切片长度
auto seg_dur = _ticker.elapsedTime() - _ticker_last_data.elapsedTime();
if(seg_dur <= 0){
seg_dur = 100;
}
_seg_dur_list.push_back(std::make_tuple(seg_dur, _last_file_name));
delOldSegment();
makeIndexFile(eof);
_last_file_name.clear();
}
}//namespace mediakit
\ No newline at end of file
......@@ -60,20 +60,20 @@ protected:
* @param index
* @return
*/
virtual string onOpenFile(int index) = 0;
virtual string onOpenSegment(int index) = 0;
/**
* 删除ts切片文件回调
* @param index
*/
virtual void onDelFile(int index) = 0;
virtual void onDelSegment(int index) = 0;
/**
* 写ts切片文件回调
* @param data
* @param len
*/
virtual void onWriteFile(const char *data, int len) = 0;
virtual void onWriteSegment(const char *data, int len) = 0;
/**
* 写m3u8文件回调
......@@ -83,18 +83,33 @@ protected:
virtual void onWriteHls(const char *data, int len) = 0;
/**
* 关闭上个ts切片并且写入m3u8索引
* @param eof
*/
void flushLastSegment(bool eof = false);
private:
/**
* 生成m3u8文件
* @param eof true代表点播
*/
void makeIndexFile(bool eof = false);
void delOldFile();
void addNewFile(uint32_t timestamp);
protected:
uint32_t _seg_number = 0;
/**
* 删除旧的ts切片
*/
void delOldSegment();
/**
* 添加新的ts切片
* @param timestamp
*/
void addNewSegment(uint32_t timestamp);
private:
uint32_t _seg_number = 0;
float _seg_duration = 0;
uint64_t _file_index = 0;
Ticker _ticker;
Ticker _ticker_last_data;
string _last_file_name;
std::deque<tuple<int,string> > _seg_dur_list;
};
......
......@@ -40,6 +40,7 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file,
_path_hls = m3u8_file;
_params = params;
_buf_size = bufSize;
_is_vod = seg_number == 0;
_file_buf.reset(new char[bufSize],[](char *ptr){
delete[] ptr;
});
......@@ -47,14 +48,14 @@ HlsMakerImp::HlsMakerImp(const string &m3u8_file,
HlsMakerImp::~HlsMakerImp() {
//录制完了
makeIndexFile(true);
if(_seg_number){
flushLastSegment(true);
if(!_is_vod){
//hls直播才删除文件
File::delete_file(_path_prefix.data());
}
}
string HlsMakerImp::onOpenFile(int index) {
string HlsMakerImp::onOpenSegment(int index) {
auto full_path = fullPath(index);
_file = makeFile(full_path, true);
if(!_file){
......@@ -67,12 +68,12 @@ string HlsMakerImp::onOpenFile(int index) {
return StrPrinter << index << ".ts" << "?" << _params;
}
void HlsMakerImp::onDelFile(int index) {
void HlsMakerImp::onDelSegment(int index) {
//WarnL << index;
File::delete_file(fullPath(index).data());
}
void HlsMakerImp::onWriteFile(const char *data, int len) {
void HlsMakerImp::onWriteSegment(const char *data, int len) {
if (_file) {
fwrite(data, len, 1, _file.get());
}
......
......@@ -44,9 +44,9 @@ public:
uint32_t seg_number = 3);
virtual ~HlsMakerImp();
protected:
string onOpenFile(int index) override ;
void onDelFile(int index) override;
void onWriteFile(const char *data, int len) override;
string onOpenSegment(int index) override ;
void onDelSegment(int index) override;
void onWriteSegment(const char *data, int len) override;
void onWriteHls(const char *data, int len) override;
private:
string fullPath(int index);
......@@ -58,6 +58,8 @@ private:
string _path_hls;
string _params;
int _buf_size;
//是否为点播
bool _is_vod;
};
}//namespace mediakit
......
......@@ -32,7 +32,7 @@
namespace mediakit {
class HlsRecorder : public HlsMakerImp , public TsMuxer {
class HlsRecorder : public HlsMakerImp, public TsMuxer {
public:
template<typename ...ArgsType>
HlsRecorder(ArgsType &&...args):HlsMakerImp(std::forward<ArgsType>(args)...){}
......
......@@ -33,11 +33,11 @@
namespace mediakit{
#if defined(_WIN32) || defined(_WIN64)
#define fseek64 _fseeki64
#define ftell64 _ftelli64
#define fseek64 _fseeki64
#define ftell64 _ftelli64
#else
#define fseek64 fseek
#define ftell64 ftell
#define fseek64 fseek
#define ftell64 ftell
#endif
void MP4MuxerBase::init(int flags) {
......@@ -236,7 +236,9 @@ MP4MuxerFile::MP4MuxerFile(const char *file) {
fclose(fp);
});
init(MOV_FLAG_FASTSTART);
GET_CONFIG(bool, mp4FastStart, Record::kFastStart);
init(mp4FastStart ? MOV_FLAG_FASTSTART : 0);
}
MP4MuxerFile::~MP4MuxerFile() {
......@@ -254,15 +256,6 @@ int MP4MuxerFile::onWrite(const void *data, uint64_t bytes) {
return bytes == fwrite(data, 1, bytes, _file.get()) ? 0 : ferror(_file.get());
}
#if defined(_WIN32) || defined(_WIN64)
#define fseek64 _fseeki64
#define ftell64 _ftelli64
#else
#define fseek64 fseek
#define ftell64 ftell
#endif
int MP4MuxerFile::onSeek(uint64_t offset) {
return fseek64(_file.get(), offset, SEEK_SET);
}
......
......@@ -153,6 +153,14 @@ void MP4Recorder::onTrackReady(const Track::Ptr & track){
}
}
void MP4Recorder::resetTracks() {
closeFile();
_tracks.clear();
_haveVideo = false;
_createFileTicker.resetTime();
MediaSink::resetTracks();
}
} /* namespace mediakit */
......
......@@ -63,6 +63,11 @@ public:
const string &strApp,
const string &strStreamId);
virtual ~MP4Recorder();
/**
* 重置所有Track
*/
void resetTracks() override;
private:
/**
* 某Track输出frame,在onAllTrackReady触发后才会调用此方法
......
......@@ -44,10 +44,11 @@ MediaReader::MediaReader(const string &strVhost,const string &strApp, const stri
GET_CONFIG(string,recordPath,Record::kFilePath);
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
if(enableVhost){
strFileName = recordPath + "/" + strVhost + "/" + strApp + "/" + strId;
strFileName = strVhost + "/" + strApp + "/" + strId;
}else{
strFileName = recordPath + "/" + strApp + "/" + strId;
strFileName = strApp + "/" + strId;
}
strFileName = File::absolutePath(strFileName,recordPath);
}
_hMP4File = MP4Read(strFileName.data());
......
......@@ -56,13 +56,15 @@ MediaRecorder::MediaRecorder(const string &strVhost_tmp,
#if defined(ENABLE_HLS)
if(enableHls) {
string m3u8FilePath;
string params;
if(enableVhost){
m3u8FilePath = hlsPath + "/" + strVhost + "/" + strApp + "/" + strId + "/hls.m3u8";
_hlsRecorder.reset(new HlsRecorder(m3u8FilePath,string(VHOST_KEY) + "=" + strVhost ,hlsBufSize, hlsDuration, hlsNum));
m3u8FilePath = strVhost + "/" + strApp + "/" + strId + "/hls.m3u8";
params = string(VHOST_KEY) + "=" + strVhost;
}else{
m3u8FilePath = hlsPath + "/" + strApp + "/" + strId + "/hls.m3u8";
_hlsRecorder.reset(new HlsRecorder(m3u8FilePath,"",hlsBufSize, hlsDuration, hlsNum));
m3u8FilePath = strApp + "/" + strId + "/hls.m3u8";
}
m3u8FilePath = File::absolutePath(m3u8FilePath,hlsPath);
_hlsRecorder.reset(new HlsRecorder(m3u8FilePath,params,hlsBufSize, hlsDuration, hlsNum));
}
#endif //defined(ENABLE_HLS)
......@@ -73,10 +75,11 @@ MediaRecorder::MediaRecorder(const string &strVhost_tmp,
if(enableMp4){
string mp4FilePath;
if(enableVhost){
mp4FilePath = recordPath + "/" + strVhost + "/" + recordAppName + "/" + strApp + "/" + strId + "/";
mp4FilePath = strVhost + "/" + recordAppName + "/" + strApp + "/" + strId + "/";
} else {
mp4FilePath = recordPath + "/" + recordAppName + "/" + strApp + "/" + strId + "/";
mp4FilePath = recordAppName + "/" + strApp + "/" + strId + "/";
}
mp4FilePath = File::absolutePath(mp4FilePath,recordPath);
_mp4Recorder.reset(new MP4Recorder(mp4FilePath,strVhost,strApp,strId));
}
#endif //defined(ENABLE_MP4RECORD)
......@@ -113,4 +116,18 @@ void MediaRecorder::addTrack(const Track::Ptr &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 */
......@@ -51,14 +51,19 @@ public:
* 输入frame
* @param frame
*/
void inputFrame(const Frame::Ptr &frame) override ;
void inputFrame(const Frame::Ptr &frame) override;
/**
* 添加track,内部会调用Track的clone方法
* 只会克隆sps pps这些信息 ,而不会克隆Delegate相关关系
* @param track
*/
void addTrack(const Track::Ptr & track) override;
void addTrack(const Track::Ptr &track) override;
/**
* 重置track
*/
void resetTracks() override;
private:
#if defined(ENABLE_HLS)
std::shared_ptr<HlsRecorder> _hlsRecorder;
......
......@@ -28,48 +28,48 @@
namespace mediakit {
void Stamp::revise(uint32_t dts, uint32_t pts, int64_t &dts_out, int64_t &pts_out) {
int64_t DeltaStamp::deltaStamp(int64_t stamp) {
if(!_last_stamp){
//第一次计算时间戳增量,时间戳增量为0
_last_stamp = stamp;
return 0;
}
int64_t ret = stamp - _last_stamp;
if(ret >= 0){
//时间戳增量为正,返回之
_last_stamp = stamp;
return ret;
}
//时间戳增量为负,说明时间戳回环了或回退了
_last_stamp = stamp;
return _playback ? ret : 0;
}
void DeltaStamp::setPlayBack(bool playback) {
_playback = playback;
}
void Stamp::revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp) {
if(!pts){
//没有播放时间戳,使其赋值为解码时间戳
pts = dts;
}
//pts和dts的差值
int pts_dts_diff = pts - dts;
if(_first){
//记录第一次时间戳,后面好计算时间戳增量
_start_dts = dts;
_first = false;
_ticker.resetTime();
}
if (!dts) {
//没有解码时间戳,我们生成解码时间戳
dts = _ticker.elapsedTime();
}
//相对时间戳
dts_out = dts - _start_dts;
if(dts_out < _dts_inc && !_playback){
//本次相对时间戳竟然小于上次?
if(dts_out < 0 || _dts_inc - dts_out > 0xFFFF){
//时间戳回环,保证下次相对时间戳与本次相对合理增长
_start_dts = dts - _dts_inc;
//本次时间戳强制等于上次时间戳
dts_out = _dts_inc;
}else{
//时间戳变小了?,那么取上次时间戳
dts_out = _dts_inc;
}
}
//保留这次相对时间戳,以便下次对比是否回环或乱序
_dts_inc = dts_out;
_relativeStamp += deltaStamp(modifyStamp ? _ticker.elapsedTime() : dts);
dts_out = _relativeStamp;
//////////////以下是播放时间戳的计算//////////////////
if(pts_dts_diff > 200 || pts_dts_diff < -200){
//如果差值大于200毫秒,则认为由于回环导致时间戳错乱了
pts_dts_diff = 0;
}
pts_out = dts_out + pts_dts_diff;
if(pts_out < 0){
//时间戳不能小于0
......@@ -77,8 +77,13 @@ void Stamp::revise(uint32_t dts, uint32_t pts, int64_t &dts_out, int64_t &pts_ou
}
}
void Stamp::setPlayBack(bool playback) {
_playback = playback;
void Stamp::setRelativeStamp(int64_t relativeStamp) {
_relativeStamp = relativeStamp;
}
int64_t Stamp::getRelativeStamp() const {
return _relativeStamp;
}
}//namespace mediakit
\ No newline at end of file
......@@ -33,18 +33,33 @@ using namespace toolkit;
namespace mediakit {
//该类解决时间戳回环、回退问题
//计算相对时间戳或者产生平滑时间戳
class Stamp {
class DeltaStamp{
public:
Stamp() = default;
~Stamp() = default;
DeltaStamp() = default;
~DeltaStamp() = default;
/**
* 设置回放模式,回放模式时间戳可以回退
* 计算时间戳增量
* @param stamp 绝对时间戳
* @return 时间戳增量
*/
int64_t deltaStamp(int64_t stamp);
/**
* 设置是否为回放模式,回放模式运行时间戳回退
* @param playback 是否为回放模式
*/
void setPlayBack(bool playback = true);
private:
int64_t _last_stamp = 0;
bool _playback = false;
};
//该类解决时间戳回环、回退问题
//计算相对时间戳或者产生平滑时间戳
class Stamp : public DeltaStamp{
public:
Stamp() = default;
~Stamp() = default;
/**
* 修正时间戳
......@@ -52,13 +67,23 @@ public:
* @param pts 输入pts,如果为0则等于dts
* @param dts_out 输出dts
* @param pts_out 输出pts
* @param modifyStamp 是否用系统时间戳覆盖
*/
void revise(uint32_t dts, uint32_t pts, int64_t &dts_out, int64_t &pts_out);
void revise(int64_t dts, int64_t pts, int64_t &dts_out, int64_t &pts_out,bool modifyStamp = false);
/**
* 再设置相对时间戳,用于seek用
* @param relativeStamp 相对时间戳
*/
void setRelativeStamp(int64_t relativeStamp);
/**
* 获取当前相对时间戳
* @return
*/
int64_t getRelativeStamp() const ;
private:
bool _playback = false;
int64_t _start_dts = 0;
int64_t _dts_inc = 0;
bool _first = true;
int64_t _relativeStamp = 0;
SmoothTicker _ticker;
};
......
......@@ -101,6 +101,8 @@ void TsMuxer::inputFrame(const Frame::Ptr &frame) {
}
void TsMuxer::resetTracks() {
//通知片段中断
onTs(nullptr, 0, 0, 0);
uninit();
init();
}
......
......@@ -43,10 +43,10 @@ public:
TsMuxer();
virtual ~TsMuxer();
void addTrack(const Track::Ptr &track) override;
void inputFrame(const Frame::Ptr &frame) override;
void resetTracks() override;
void inputFrame(const Frame::Ptr &frame) override;
protected:
virtual void onTs(const void *packet, int bytes,uint32_t timestamp,int flags) = 0;
void resetTracks();
private:
void init();
void uninit();
......
......@@ -122,7 +122,13 @@ void PlayerProxy::play(const string &strUrlTmp) {
for (auto & track : tracks){
track->delDelegate(strongSelf->_mediaMuxer.get());
}
strongSelf->_mediaMuxer.reset();
GET_CONFIG(bool,resetWhenRePlay,General::kResetWhenRePlay);
if (resetWhenRePlay) {
strongSelf->_mediaMuxer.reset();
} else {
strongSelf->_mediaMuxer->resetTracks();
}
}
//播放异常中断,延时重试播放
if(*piFailedCnt < strongSelf->_iRetryCount || strongSelf->_iRetryCount < 0) {
......@@ -138,7 +144,7 @@ void PlayerProxy::play(const string &strUrlTmp) {
if(directProxy && _bEnableRtsp){
mediaSource = std::make_shared<RtspMediaSource>(_strVhost,_strApp,_strSrc);
}
}else if(dynamic_pointer_cast<RtmpPlayer>(_parser)){
} else if(dynamic_pointer_cast<RtmpPlayer>(_parser)){
//rtmp拉流
if(_bEnableRtmp){
mediaSource = std::make_shared<RtmpMediaSource>(_strVhost,_strApp,_strSrc);
......@@ -154,7 +160,7 @@ PlayerProxy::~PlayerProxy() {
_timer.reset();
}
void PlayerProxy::rePlay(const string &strUrl,int iFailedCnt){
auto iDelay = MAX(2 * 1000, MIN(iFailedCnt * 3000,60*1000));
auto iDelay = MAX(2 * 1000, MIN(iFailedCnt * 3000, 60*1000));
weak_ptr<PlayerProxy> weakSelf = shared_from_this();
_timer = std::make_shared<Timer>(iDelay / 1000.0f,[weakSelf,strUrl,iFailedCnt]() {
//播放失败次数越多,则延时越长
......@@ -213,7 +219,7 @@ public:
if(_iAudioIndex != iAudioIndex){
_iAudioIndex = iAudioIndex;
auto aacFrame = std::make_shared<AACFrameNoCacheAble>((char *)MUTE_ADTS_DATA,
MUTE_ADTS_DATA_LEN,
MUTE_ADTS_DATA_LEN,
_iAudioIndex * MUTE_ADTS_DATA_MS);
FrameRingInterfaceDelegate::inputFrame(aacFrame);
}
......@@ -224,15 +230,22 @@ private:
};
void PlayerProxy::onPlaySuccess() {
GET_CONFIG(bool,resetWhenRePlay,General::kResetWhenRePlay);
if (dynamic_pointer_cast<RtspMediaSource>(_pMediaSrc)) {
//rtsp拉流代理
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), false, _bEnableRtmp, _bEnableHls, _bEnableMp4));
if (resetWhenRePlay || !_mediaMuxer) {
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), false, _bEnableRtmp, _bEnableHls, _bEnableMp4));
}
} else if (dynamic_pointer_cast<RtmpMediaSource>(_pMediaSrc)) {
//rtmp拉流代理
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), _bEnableRtsp, false, _bEnableHls, _bEnableMp4));
if (resetWhenRePlay || !_mediaMuxer) {
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), _bEnableRtsp, false, _bEnableHls, _bEnableMp4));
}
} else {
//其他拉流代理
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), _bEnableRtsp, _bEnableRtmp, _bEnableHls, _bEnableMp4));
if (resetWhenRePlay || !_mediaMuxer) {
_mediaMuxer.reset(new MultiMediaSourceMuxer(_strVhost, _strApp, _strSrc, getDuration(), _bEnableRtsp, _bEnableRtmp, _bEnableHls, _bEnableMp4));
}
}
_mediaMuxer->setListener(shared_from_this());
......@@ -244,13 +257,16 @@ void PlayerProxy::onPlaySuccess() {
videoTrack->addDelegate(_mediaMuxer);
}
//是否添加静音音频
GET_CONFIG(bool,addMuteAudio,General::kAddMuteAudio);
auto audioTrack = getTrack(TrackAudio, false);
if(audioTrack){
//添加音频
_mediaMuxer->addTrack(audioTrack);
//音频数据写入_mediaMuxer
audioTrack->addDelegate(_mediaMuxer);
}else if(videoTrack){
}else if(addMuteAudio && videoTrack){
//没有音频信息,产生一个静音音频
MuteAudioMaker::Ptr audioMaker = std::make_shared<MuteAudioMaker>();
//videoTrack把数据写入MuteAudioMaker
......
......@@ -44,13 +44,17 @@ RtmpSession::~RtmpSession() {
}
void RtmpSession::onError(const SockException& err) {
WarnP(this) << err.what();
bool isPlayer = !_pPublisherSrc;
WarnP(this) << (isPlayer ? "播放器(" : "推流器(")
<< _mediaInfo._vhost << "/"
<< _mediaInfo._app << "/"
<< _mediaInfo._streamid
<< ")断开:" << err.what();
//流量统计事件广播
GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold);
if(_ui64TotalBytes > iFlowThreshold * 1024){
bool isPlayer = !_pPublisherSrc;
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport,
_mediaInfo,
_ui64TotalBytes,
......@@ -486,7 +490,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);
_stamp[chunkData.typeId % 2].revise(0, 0, dts_out, dts_out, true);
chunkData.timeStamp = dts_out;
}
if(!_metadata_got && !chunkData.isCfgFrame()){
......
......@@ -68,35 +68,43 @@ void RtspPlayer::teardown(){
CLEAR_ARR(_aui64RtpRecv)
CLEAR_ARR(_aui64RtpRecv)
CLEAR_ARR(_aui16NowSeq)
CLEAR_ARR(_aiFistStamp);
CLEAR_ARR(_aiNowStamp);
_pPlayTimer.reset();
_pPlayTimer.reset();
_pRtpTimer.reset();
_iSeekTo = 0;
_uiCseq = 1;
_onHandshake = nullptr;
}
void RtspPlayer::play(const string &strUrl){
auto userAndPwd = FindField(strUrl.data(),"://","@");
Rtsp::eRtpType eType = (Rtsp::eRtpType)(int)(*this)[kRtpType];
if(userAndPwd.empty()){
play(strUrl,"","",eType);
return;
auto schema = FindField(strUrl.data(), nullptr,"://");
bool isSSL = strcasecmp(schema.data(),"rtsps") == 0;
//查找"://"与"/"之间的字符串,用于提取用户名密码
auto middle_url = FindField(strUrl.data(),"://","/");
if(middle_url.empty()){
middle_url = FindField(strUrl.data(),"://", nullptr);
}
auto pos = middle_url.rfind('@');
if(pos == string::npos){
//并没有用户名密码
play(isSSL,strUrl,"","",eType);
return;
}
auto suffix = FindField(strUrl.data(),"@",nullptr);
//包含用户名密码
auto user_pwd = middle_url.substr(0,pos);
auto suffix = strUrl.substr(schema.size() + 3 + pos + 1);
auto url = StrPrinter << "rtsp://" << suffix << endl;
if(userAndPwd.find(":") == string::npos){
play(url,userAndPwd,"",eType);
if(user_pwd.find(":") == string::npos){
play(isSSL,url,user_pwd,"",eType);
return;
}
auto user = FindField(userAndPwd.data(),nullptr,":");
auto pwd = FindField(userAndPwd.data(),":",nullptr);
play(url,user,pwd,eType);
auto user = FindField(user_pwd.data(),nullptr,":");
auto pwd = FindField(user_pwd.data(),":",nullptr);
play(isSSL,url,user,pwd,eType);
}
//播放,指定是否走rtp over tcp
void RtspPlayer::play(const string &strUrl, const string &strUser, const string &strPwd, Rtsp::eRtpType eType ) {
void RtspPlayer::play(bool isSSL,const string &strUrl, const string &strUser, const string &strPwd, Rtsp::eRtpType eType ) {
DebugL << strUrl << " "
<< (strUser.size() ? strUser : "null") << " "
<< (strPwd.size() ? strPwd:"null") << " "
......@@ -115,12 +123,12 @@ void RtspPlayer::play(const string &strUrl, const string &strUser, const string
auto ip = FindField(strUrl.data(), "://", "/");
if (!ip.size()) {
ip = FindField(strUrl.data(), "://", NULL);
ip = split(FindField(strUrl.data(), "://", NULL),"?")[0];
}
auto port = atoi(FindField(ip.data(), ":", NULL).data());
if (port <= 0) {
//rtsp 默认端口554
port = 554;
port = isSSL ? 322 : 554;
} else {
//服务器域名
ip = FindField(ip.data(), NULL, ":");
......@@ -222,6 +230,16 @@ void RtspPlayer::handleResDESCRIBE(const Parser& parser) {
SdpParser sdpParser(parser.Content());
//解析sdp
_aTrackInfo = sdpParser.getAvailableTrack();
auto title = sdpParser.getTrack(TrackTitle);
bool isPlayback = false;
if(title && title->_duration ){
isPlayback = true;
}
for(auto &stamp : _stamp){
stamp.setPlayBack(isPlayback);
stamp.setRelativeStamp(0);
}
if (_aTrackInfo.empty()) {
throw std::runtime_error("无有效的Sdp Track");
......@@ -386,7 +404,7 @@ void RtspPlayer::handleResSETUP(const Parser &parser, unsigned int uiTrackIndex)
}
//所有setup命令发送完毕
//发送play命令
pause(false);
sendPause(false, 0,false);
}
void RtspPlayer::sendOptions() {
......@@ -403,25 +421,19 @@ void RtspPlayer::sendDescribe() {
}
void RtspPlayer::sendPause(bool bPause,uint32_t seekMS){
if(!bPause){
//修改时间轴
int iTimeInc = seekMS - getProgressMilliSecond();
for(unsigned int i = 0 ;i < _aTrackInfo.size() ;i++){
_aiFistStamp[i] = _aiNowStamp[i] + iTimeInc;
_aiNowStamp[i] = _aiFistStamp[i];
}
_iSeekTo = seekMS;
}
void RtspPlayer::sendPause(bool bPause,uint32_t seekMS,bool range){
//开启或暂停rtsp
_onHandshake = std::bind(&RtspPlayer::handleResPAUSE,this, placeholders::_1,bPause);
sendRtspRequest(bPause ? "PAUSE" : "PLAY",
_strContentBase,
{"Range",StrPrinter << "npt=" << setiosflags(ios::fixed) << setprecision(2) << seekMS / 1000.0 << "-"});
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);
}
}
void RtspPlayer::pause(bool bPause) {
sendPause(bPause, getProgressMilliSecond());
sendPause(bPause, getProgressMilliSecond(),false);
}
void RtspPlayer::handleResPAUSE(const Parser& parser, bool bPause) {
......@@ -430,6 +442,7 @@ void RtspPlayer::handleResPAUSE(const Parser& parser, bool bPause) {
return;
}
if (!bPause) {
uint32_t iSeekTo = 0;
//修正时间轴
auto strRange = parser["Range"];
if (strRange.size()) {
......@@ -437,25 +450,12 @@ void RtspPlayer::handleResPAUSE(const Parser& parser, bool bPause) {
if (strStart == "now") {
strStart = "0";
}
_iSeekTo = 1000 * atof(strStart.data());
DebugL << "seekTo(ms):" << _iSeekTo ;
}
auto strRtpInfo = parser["RTP-Info"];
if (strRtpInfo.size()) {
strRtpInfo.append(",");
vector<string> vec = split(strRtpInfo, ",");
for(auto &strTrack : vec){
strTrack.append(";");
auto strControlSuffix = strTrack.substr(1 + strTrack.rfind('/'),strTrack.find(';') - strTrack.rfind('/') - 1);
auto strRtpTime = FindField(strTrack.data(), "rtptime=", ";");
auto idx = getTrackIndexByControlSuffix(strControlSuffix);
if(idx != -1){
_aiFistStamp[idx] = _aTrackInfo[idx]->_samplerate>0?atoll(strRtpTime.data()) * 1000 / _aTrackInfo[idx]->_samplerate :1;
_aiNowStamp[idx] = _aiFistStamp[idx];
DebugL << "rtptime(ms):" << strControlSuffix <<" " << strRtpTime;
}
}
iSeekTo = 1000 * atof(strStart.data());
DebugL << "seekTo(ms):" << iSeekTo ;
}
//设置相对时间戳
_stamp[0].setRelativeStamp(iSeekTo);
_stamp[1].setRelativeStamp(iSeekTo);
onPlayResult_l(SockException(Err_success, "rtsp play success"));
} else {
_pRtpTimer.reset();
......@@ -630,12 +630,11 @@ void RtspPlayer::onRtpSorted(const RtpPacket::Ptr &rtppt, int trackidx){
}
_aui64RtpRecv[trackidx] ++;
_aui16NowSeq[trackidx] = rtppt->sequence;
_aiNowStamp[trackidx] = rtppt->timeStamp;
if( _aiFistStamp[trackidx] == 0){
_aiFistStamp[trackidx] = _aiNowStamp[trackidx];
}
rtppt->timeStamp -= _aiFistStamp[trackidx];
//计算相对时间戳
int64_t dts_out;
_stamp[trackidx].revise(rtppt->timeStamp,rtppt->timeStamp,dts_out,dts_out);
rtppt->timeStamp = dts_out;
onRecvRTP_l(rtppt,_aTrackInfo[trackidx]);
}
float RtspPlayer::getPacketLossRate(TrackType type) const{
......@@ -653,7 +652,6 @@ float RtspPlayer::getPacketLossRate(TrackType type) const{
return 1.0 - (double)totalRecv / totalSend;
}
if(_aui16NowSeq[iTrackIdx] - _aui16FirstSeq[iTrackIdx] + 1 == 0){
return 0;
}
......@@ -661,14 +659,10 @@ float RtspPlayer::getPacketLossRate(TrackType type) const{
}
uint32_t RtspPlayer::getProgressMilliSecond() const{
uint32_t iTime[2] = {0,0};
for(unsigned int i = 0 ;i < _aTrackInfo.size() ;i++){
iTime[i] = _aiNowStamp[i] - _aiFistStamp[i];
}
return _iSeekTo + MAX(iTime[0],iTime[1]);
return MAX(_stamp[0].getRelativeStamp(),_stamp[1].getRelativeStamp());
}
void RtspPlayer::seekToMilliSecond(uint32_t ms) {
sendPause(false,ms);
sendPause(false,ms, true);
}
void RtspPlayer::sendRtspRequest(const string &cmd, const string &url, const std::initializer_list<string> &header) {
......
......@@ -40,6 +40,7 @@
#include "Network/TcpClient.h"
#include "RtspSplitter.h"
#include "RtpReceiver.h"
#include "MediaFile/Stamp.h"
using namespace std;
using namespace toolkit;
......@@ -106,7 +107,7 @@ private:
int getTrackIndexByInterleaved(int interleaved) const;
int getTrackIndexByTrackType(TrackType trackType) const;
void play(const string &strUrl, const string &strUser, const string &strPwd, Rtsp::eRtpType eType);
void play(bool isSSL,const string &strUrl, const string &strUser, const string &strPwd, Rtsp::eRtpType eType);
void handleResSETUP(const Parser &parser, unsigned int uiTrackIndex);
void handleResDESCRIBE(const Parser &parser);
bool handleAuthenticationFailure(const string &wwwAuthenticateParamsStr);
......@@ -114,7 +115,7 @@ private:
//发送SETUP命令
void sendSetup(unsigned int uiTrackIndex);
void sendPause(bool bPause,uint32_t ms);
void sendPause(bool bPause,uint32_t ms, bool range);
void sendOptions();
void sendDescribe();
......@@ -148,12 +149,8 @@ private:
std::shared_ptr<Timer> _pPlayTimer;
std::shared_ptr<Timer> _pRtpTimer;
//播放进度控制,单位毫秒
uint32_t _iSeekTo = 0;
//单位毫秒
uint32_t _aiFistStamp[2] = {0,0};
uint32_t _aiNowStamp[2] = {0,0};
//时间戳
Stamp _stamp[2];
//rtcp相关
RtcpCounter _aRtcpCnt[2]; //rtcp统计,trackid idx 为数组下标
......
......@@ -85,7 +85,13 @@ RtspSession::~RtspSession() {
}
void RtspSession::onError(const SockException& err) {
WarnP(this) << err.what();
bool isPlayer = !_pushSrc;
WarnP(this) << (isPlayer ? "播放器(" : "推流器(")
<< _mediaInfo._vhost << "/"
<< _mediaInfo._app << "/"
<< _mediaInfo._streamid
<< ")断开:" << err.what();
if (_rtpType == Rtsp::RTP_MULTICAST) {
//取消UDP端口监听
UDPServer::Instance().stopListenPeer(get_peer_ip().data(), this);
......@@ -100,7 +106,6 @@ void RtspSession::onError(const SockException& err) {
//流量统计事件广播
GET_CONFIG(uint32_t,iFlowThreshold,General::kFlowThreshold);
if(_ui64TotalBytes > iFlowThreshold * 1024){
bool isPlayer = !_pushSrc;
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastFlowReport,
_mediaInfo,
_ui64TotalBytes,
......@@ -932,7 +937,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);
_stamp[trackidx].revise(0, 0, dts_out, dts_out, true);
rtppt->timeStamp = dts_out;
}
_pushSrc->onWrite(rtppt, false);
......
......@@ -101,6 +101,7 @@ int main(int argc, char *argv[]) {
#endif
static char *url = argv[1];
//设置退出信号处理函数
signal(SIGINT, [](int) { SDLDisplayerHelper::Instance().shutdown(); });
//设置日志
......@@ -140,7 +141,7 @@ int main(int argc, char *argv[]) {
decoder.set<H264Decoder>();
}
if(!displayer){
displayer.set<YuvDisplayer>();
displayer.set<YuvDisplayer>(nullptr,url);
}
if(!merger){
merger.set<FrameMerger>();
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论