Merge pull request #4 from zlmediakit/master

update
This commit is contained in:
baiyfcu 2019-08-12 18:25:04 +08:00 committed by GitHub
commit d3a4fc1f4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 2516 additions and 356 deletions

@ -1 +1 @@
Subproject commit 936d3c05b183cba279bb348f8eac9eca0cc810c2 Subproject commit 2bb234006c852b1d1a61a0e9a7f39dde7105fe34

@ -1 +1 @@
Subproject commit e399b93802610dcf574ff64bcb7677572cd028c1 Subproject commit 6df71e01c174cdfe69e597cc4acb766a20b28620

View File

@ -1,4 +1,4 @@
#include <jni.h> #include <jni.h>
#include <string> #include <string>
#include "test_server.cpp" #include "test_server.cpp"

View File

@ -36,6 +36,7 @@ set(ENABLE_MYSQL true)
set(ENABLE_MP4V2 true) set(ENABLE_MP4V2 true)
set(ENABLE_FAAC true) set(ENABLE_FAAC true)
set(ENABLE_X264 true) set(ENABLE_X264 true)
set(ENABLE_MP4RECORD true)
# #
if(ENABLE_HLS) if(ENABLE_HLS)
@ -47,6 +48,12 @@ else()
set(LINK_LIB_LIST zlmediakit zltoolkit) set(LINK_LIB_LIST zlmediakit zltoolkit)
endif() endif()
if(ENABLE_MP4RECORD)
message(STATUS "ENABLE_MP4RECORD defined")
add_definitions(-DENABLE_MP4RECORD)
set(MediaServer_Root ${CMAKE_SOURCE_DIR}/3rdpart/media-server)
list(APPEND LINK_LIB_LIST mov flv)
endif()
#openssl #openssl
find_package(OpenSSL QUIET) find_package(OpenSSL QUIET)
if (OPENSSL_FOUND AND ENABLE_OPENSSL) if (OPENSSL_FOUND AND ENABLE_OPENSSL)
@ -111,6 +118,21 @@ if(ENABLE_HLS)
endif(WIN32) endif(WIN32)
endif() 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 (WIN32) if (WIN32)
list(APPEND LINK_LIB_LIST WS2_32 Iphlpapi shlwapi) list(APPEND LINK_LIB_LIST WS2_32 Iphlpapi shlwapi)
set_target_properties(zltoolkit PROPERTIES COMPILE_FLAGS ${VS_FALGS} ) set_target_properties(zltoolkit PROPERTIES COMPILE_FLAGS ${VS_FALGS} )

View File

@ -63,7 +63,7 @@
| RTMP --> RTSP[S] | Y | N | Y | N | | RTMP --> RTSP[S] | Y | N | Y | N |
| RTSP[S] --> HLS | Y | Y | Y | N | | RTSP[S] --> HLS | Y | Y | Y | N |
| RTMP --> HLS | Y | N | Y | N | | RTMP --> HLS | Y | N | Y | N |
| RTSP[S] --> MP4 | Y | N | Y | N | | RTSP[S] --> MP4 | Y | Y | Y | N |
| RTMP --> MP4 | Y | N | Y | N | | RTMP --> MP4 | Y | N | Y | N |
| MP4 --> RTSP[S] | Y | N | Y | N | | MP4 --> RTSP[S] | Y | N | Y | N |
| MP4 --> RTMP | Y | N | Y | N | | MP4 --> RTMP | Y | N | Y | N |
@ -73,9 +73,9 @@
| feature/codec | H264 | H265 | AAC | other | | feature/codec | H264 | H265 | AAC | other |
| :-----------: | :--: | :--: | :--: | :---: | | :-----------: | :--: | :--: | :--: | :---: |
| RTSP[S] push | Y | Y | Y | Y | | RTSP[S] push | Y | Y | Y | Y |
| RTSP proxy | Y | Y | Y | N | | RTSP proxy | Y | Y | Y | Y |
| RTMP push | Y | Y | Y | Y | | RTMP push | Y | Y | Y | Y |
| RTMP proxy | Y | N | Y | N | | RTMP proxy | Y | Y | Y | Y |
- RTP transport: - RTP transport:

View File

@ -78,7 +78,7 @@
| RTMP --> RTSP[S] | Y | N | Y | N | | RTMP --> RTSP[S] | Y | N | Y | N |
| RTSP[S] --> HLS | Y | Y | Y | N | | RTSP[S] --> HLS | Y | Y | Y | N |
| RTMP --> HLS | Y | N | Y | N | | RTMP --> HLS | Y | N | Y | N |
| RTSP[S] --> MP4 | Y | N | Y | N | | RTSP[S] --> MP4 | Y | Y | Y | N |
| RTMP --> MP4 | Y | N | Y | N | | RTMP --> MP4 | Y | N | Y | N |
| MP4 --> RTSP[S] | Y | N | Y | N | | MP4 --> RTSP[S] | Y | N | Y | N |
| MP4 --> RTMP | Y | N | Y | N | | MP4 --> RTMP | Y | N | Y | N |
@ -88,9 +88,9 @@
| 功能/编码格式 | H264 | H265 | AAC | other | | 功能/编码格式 | H264 | H265 | AAC | other |
| :------------------------------: | :--: | :--: | :--: | :---: | | :------------------------------: | :--: | :--: | :--: | :---: |
| RTSP[S]推流 | Y | Y | Y | Y | | RTSP[S]推流 | Y | Y | Y | Y |
| RTSP拉流代理 | Y | Y | Y | N | | RTSP拉流代理 | Y | Y | Y | Y |
| RTMP推流 | Y | Y | Y | Y | | RTMP推流 | Y | Y | Y | Y |
| RTMP拉流代理 | Y | N | Y | N | | RTMP拉流代理 | Y | Y | Y | Y |
- RTP传输方式: - RTP传输方式:

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -127,6 +127,14 @@ public:
"日志等级,LTrace~LError(0~4)",/*该选项说明文字*/ "日志等级,LTrace~LError(0~4)",/*该选项说明文字*/
nullptr); nullptr);
(*_parser) << Option('m',/*该选项简称,如果是\x00则说明无简称*/
"max_day",/*该选项全称,每个选项必须有全称不得为null或空字符串*/
Option::ArgRequired,/*该选项后面必须跟值*/
"7",/*该选项默认值*/
false,/*该选项是否必须赋值如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
"日志最多保存天数",/*该选项说明文字*/
nullptr);
(*_parser) << Option('c',/*该选项简称,如果是\x00则说明无简称*/ (*_parser) << Option('c',/*该选项简称,如果是\x00则说明无简称*/
"config",/*该选项全称,每个选项必须有全称不得为null或空字符串*/ "config",/*该选项全称,每个选项必须有全称不得为null或空字符串*/
Option::ArgRequired,/*该选项后面必须跟值*/ Option::ArgRequired,/*该选项后面必须跟值*/
@ -216,11 +224,10 @@ int main(int argc,char *argv[]) {
//设置日志 //设置日志
Logger::Instance().add(std::make_shared<ConsoleChannel>("ConsoleChannel", logLevel)); Logger::Instance().add(std::make_shared<ConsoleChannel>("ConsoleChannel", logLevel));
#if defined(__linux__) || defined(__linux) auto fileChannel = std::make_shared<FileChannel>("FileChannel", exeDir() + "log/", logLevel);
Logger::Instance().add(std::make_shared<SysLogChannel>("SysLogChannel",logLevel)); //日志最多保存天数
#else fileChannel->setMaxDay(cmd_main["max_day"]);
Logger::Instance().add(std::make_shared<FileChannel>("FileChannel", exePath() + ".log", logLevel)); Logger::Instance().add(fileChannel);
#endif
#if !defined(_WIN32) #if !defined(_WIN32)
if (bDaemon) { if (bDaemon) {

View File

@ -41,7 +41,7 @@ void MediaSink::addTrack(const Track::Ptr &track_in) {
if(!strongSelf){ if(!strongSelf){
return; return;
} }
if(strongSelf->_allTrackReady){ if(!strongSelf->_anyTrackUnReady){
strongSelf->onTrackFrame(frame); strongSelf->onTrackFrame(frame);
} }
})); }));
@ -53,6 +53,7 @@ void MediaSink::addTrack(const Track::Ptr &track_in) {
if(track->ready()){ if(track->ready()){
lam(); lam();
}else{ }else{
_anyTrackUnReady = true;
_allTrackReady = false; _allTrackReady = false;
_trackReadyCallback[codec_id] = lam; _trackReadyCallback[codec_id] = lam;
_ticker.resetTime(); _ticker.resetTime();
@ -79,7 +80,7 @@ void MediaSink::inputFrame(const Frame::Ptr &frame) {
if(!_allTrackReady && (_trackReadyCallback.empty() || _ticker.elapsedTime() > MAX_WAIT_MS)){ if(!_allTrackReady && (_trackReadyCallback.empty() || _ticker.elapsedTime() > MAX_WAIT_MS)){
_allTrackReady = true; _allTrackReady = true;
_anyTrackUnReady = false;
if(!_trackReadyCallback.empty()){ if(!_trackReadyCallback.empty()){
//这是超时强制忽略未准备好的Track //这是超时强制忽略未准备好的Track
_trackReadyCallback.clear(); _trackReadyCallback.clear();

View File

@ -99,6 +99,7 @@ private:
map<int,Track::Ptr> _track_map; map<int,Track::Ptr> _track_map;
map<int,function<void()> > _trackReadyCallback; map<int,function<void()> > _trackReadyCallback;
bool _allTrackReady = false; bool _allTrackReady = false;
bool _anyTrackUnReady = false;
Ticker _ticker; Ticker _ticker;
}; };

View File

@ -1,4 +1,4 @@
// //
// Created by xzl on 2019/6/28. // Created by xzl on 2019/6/28.
// //

View File

@ -1,4 +1,4 @@
// //
// Created by xzl on 2019/6/28. // Created by xzl on 2019/6/28.
// //

View File

@ -260,11 +260,15 @@ const string kFileSecond = RECORD_FIELD"fileSecond";
#define RECORD_FILE_PATH HTTP_ROOT_PATH #define RECORD_FILE_PATH HTTP_ROOT_PATH
const string kFilePath = RECORD_FIELD"filePath"; const string kFilePath = RECORD_FIELD"filePath";
//mp4文件写缓存大小
const string kFileBufSize = RECORD_FIELD"fileBufSize";
onceToken token([](){ onceToken token([](){
mINI::Instance()[kAppName] = RECORD_APP_NAME; mINI::Instance()[kAppName] = RECORD_APP_NAME;
mINI::Instance()[kSampleMS] = RECORD_SAMPLE_MS; mINI::Instance()[kSampleMS] = RECORD_SAMPLE_MS;
mINI::Instance()[kFileSecond] = RECORD_FILE_SECOND; mINI::Instance()[kFileSecond] = RECORD_FILE_SECOND;
mINI::Instance()[kFilePath] = RECORD_FILE_PATH; mINI::Instance()[kFilePath] = RECORD_FILE_PATH;
mINI::Instance()[kFileBufSize] = 64 * 1024;
},nullptr); },nullptr);
} //namespace Record } //namespace Record

View File

@ -255,6 +255,8 @@ extern const string kSampleMS;
extern const string kFileSecond; extern const string kFileSecond;
//录制文件路径 //录制文件路径
extern const string kFilePath; extern const string kFilePath;
//mp4文件写缓存大小
extern const string kFileBufSize;
} //namespace Record } //namespace Record
////////////HLS相关配置/////////// ////////////HLS相关配置///////////

View File

@ -79,6 +79,10 @@ public:
bool keyFrame() const override { bool keyFrame() const override {
return false; return false;
} }
bool configFrame() const override{
return false;
}
public: public:
unsigned int syncword = 0; //12 bslbf 同步字The bit string 1111 1111 1111说明一个ADTS帧的开始 unsigned int syncword = 0; //12 bslbf 同步字The bit string 1111 1111 1111说明一个ADTS帧的开始
unsigned int id; //1 bslbf MPEG 标示符, 设置为1 unsigned int id; //1 bslbf MPEG 标示符, 设置为1
@ -127,6 +131,10 @@ public:
bool keyFrame() const override { bool keyFrame() const override {
return false; return false;
} }
bool configFrame() const override{
return false;
}
} ; } ;

View File

@ -36,10 +36,10 @@ namespace mediakit{
Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) { Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) {
if (strcasecmp(track->_codec.data(), "mpeg4-generic") == 0) { if (strcasecmp(track->_codec.data(), "mpeg4-generic") == 0) {
string aac_cfg_str = FindField(track->_fmtp.data(), "config=", nullptr); string aac_cfg_str = FindField(track->_fmtp.data(), "config=", nullptr);
if (aac_cfg_str.size() != 4) { if (aac_cfg_str.empty()) {
aac_cfg_str = FindField(track->_fmtp.data(), "config=", ";"); aac_cfg_str = FindField(track->_fmtp.data(), "config=", ";");
} }
if (aac_cfg_str.size() != 4) { if (aac_cfg_str.empty()) {
//延后获取adts头 //延后获取adts头
return std::make_shared<AACTrack>(); return std::make_shared<AACTrack>();
} }
@ -76,8 +76,14 @@ Track::Ptr Factory::getTrackBySdp(const SdpTrack::Ptr &track) {
if (strcasecmp(track->_codec.data(), "h265") == 0) { if (strcasecmp(track->_codec.data(), "h265") == 0) {
//a=fmtp:96 sprop-sps=QgEBAWAAAAMAsAAAAwAAAwBdoAKAgC0WNrkky/AIAAADAAgAAAMBlQg=; sprop-pps=RAHA8vA8kAA= //a=fmtp:96 sprop-sps=QgEBAWAAAAMAsAAAAwAAAwBdoAKAgC0WNrkky/AIAAADAAgAAAMBlQg=; sprop-pps=RAHA8vA8kAA=
int pt; int pt, id;
char sprop_vps[128] = {0},sprop_sps[128] = {0},sprop_pps[128] = {0}; char sprop_vps[128] = {0},sprop_sps[128] = {0},sprop_pps[128] = {0};
if (5 == sscanf(track->_fmtp.data(), "%d profile-id=%d; sprop-sps=%127[^;]; sprop-pps=%127[^;]; sprop-vps=%127[^;]", &pt, &id, sprop_sps,sprop_pps, sprop_vps)) {
auto vps = decodeBase64(sprop_vps);
auto sps = decodeBase64(sprop_sps);
auto pps = decodeBase64(sprop_pps);
return std::make_shared<H265Track>(vps,sps,pps,0,0,0);
}
if (4 == sscanf(track->_fmtp.data(), "%d sprop-vps=%127[^;]; sprop-sps=%127[^;]; sprop-pps=%127[^;]", &pt, sprop_vps,sprop_sps, sprop_pps)) { if (4 == sscanf(track->_fmtp.data(), "%d sprop-vps=%127[^;]; sprop-sps=%127[^;]; sprop-pps=%127[^;]", &pt, sprop_vps,sprop_sps, sprop_pps)) {
auto vps = decodeBase64(sprop_vps); auto vps = decodeBase64(sprop_vps);
auto sps = decodeBase64(sprop_sps); auto sps = decodeBase64(sprop_sps);

View File

@ -117,6 +117,12 @@ public:
*/ */
virtual bool keyFrame() const = 0; virtual bool keyFrame() const = 0;
/**
* sps pps vps
* @return
*/
virtual bool configFrame() const = 0;
/** /**
* *
*/ */
@ -371,6 +377,7 @@ public:
_trackType = frame->getTrackType(); _trackType = frame->getTrackType();
_codec = frame->getCodecId(); _codec = frame->getCodecId();
_key = frame->keyFrame(); _key = frame->keyFrame();
_config = frame->configFrame();
} }
virtual ~FrameCacheAble() = default; virtual ~FrameCacheAble() = default;
@ -394,12 +401,17 @@ public:
bool keyFrame() const override{ bool keyFrame() const override{
return _key; return _key;
} }
bool configFrame() const override{
return _config;
}
private: private:
Frame::Ptr _frame; Frame::Ptr _frame;
BufferRaw::Ptr _buffer; BufferRaw::Ptr _buffer;
TrackType _trackType; TrackType _trackType;
CodecId _codec; CodecId _codec;
bool _key; bool _key;
bool _config;
}; };

View File

@ -32,9 +32,6 @@ using namespace toolkit;
namespace mediakit{ namespace mediakit{
bool getAVCInfo(const string& strSps,int &iVideoWidth, int &iVideoHeight, float &iVideoFps) {
return getAVCInfo(strSps.data(),strSps.size(),iVideoWidth,iVideoHeight,iVideoFps);
}
bool getAVCInfo(const char * sps,int sps_len,int &iVideoWidth, int &iVideoHeight, float &iVideoFps){ bool getAVCInfo(const char * sps,int sps_len,int &iVideoWidth, int &iVideoHeight, float &iVideoFps){
T_GetBitContext tGetBitBuf; T_GetBitContext tGetBitBuf;
T_SPS tH264SpsInfo; T_SPS tH264SpsInfo;
@ -51,6 +48,9 @@ bool getAVCInfo(const char * sps,int sps_len,int &iVideoWidth, int &iVideoHeight
return true; return true;
} }
bool getAVCInfo(const string& strSps,int &iVideoWidth, int &iVideoHeight, float &iVideoFps) {
return getAVCInfo(strSps.data(),strSps.size(),iVideoWidth,iVideoHeight,iVideoFps);
}
const char *memfind(const char *buf, int len, const char *subbuf, int sublen) { const char *memfind(const char *buf, int len, const char *subbuf, int sublen) {
for (auto i = 0; i < len - sublen; ++i) { for (auto i = 0; i < len - sublen; ++i) {

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
@ -36,7 +36,6 @@ using namespace toolkit;
namespace mediakit{ namespace mediakit{
bool getAVCInfo(const string &strSps,int &iVideoWidth, int &iVideoHeight, float &iVideoFps); bool getAVCInfo(const string &strSps,int &iVideoWidth, int &iVideoHeight, float &iVideoFps);
bool getAVCInfo(const char * sps,int sps_len,int &iVideoWidth, int &iVideoHeight, float &iVideoFps);
void splitH264(const char *ptr, int len, const std::function<void(const char *, int)> &cb); void splitH264(const char *ptr, int len, const std::function<void(const char *, int)> &cb);
/** /**
@ -80,13 +79,21 @@ public:
} }
bool keyFrame() const override { bool keyFrame() const override {
return type == NAL_IDR; return H264_TYPE(buffer[iPrefixSize]) == H264Frame::NAL_IDR;
}
bool configFrame() const override{
switch(H264_TYPE(buffer[iPrefixSize]) ){
case H264Frame::NAL_SPS:
case H264Frame::NAL_PPS:
return true;
default:
return false;
}
} }
public: public:
uint16_t sequence;
uint32_t timeStamp; uint32_t timeStamp;
uint32_t ptsStamp = 0; uint32_t ptsStamp = 0;
unsigned char type;
string buffer; string buffer;
uint32_t iPrefixSize = 4; uint32_t iPrefixSize = 4;
}; };
@ -120,6 +127,16 @@ public:
bool keyFrame() const override { bool keyFrame() const override {
return H264_TYPE(_ptr[_prefixSize]) == H264Frame::NAL_IDR; return H264_TYPE(_ptr[_prefixSize]) == H264Frame::NAL_IDR;
} }
bool configFrame() const override{
switch(H264_TYPE(_ptr[_prefixSize])){
case H264Frame::NAL_SPS:
case H264Frame::NAL_PPS:
return true;
default:
return false;
}
}
}; };
/** /**
@ -326,7 +343,6 @@ private:
if(!_sps.empty()){ if(!_sps.empty()){
auto spsFrame = std::make_shared<H264Frame>(); auto spsFrame = std::make_shared<H264Frame>();
spsFrame->type = H264Frame::NAL_SPS;
spsFrame->iPrefixSize = 4; spsFrame->iPrefixSize = 4;
spsFrame->buffer.assign("\x0\x0\x0\x1",4); spsFrame->buffer.assign("\x0\x0\x0\x1",4);
spsFrame->buffer.append(_sps); spsFrame->buffer.append(_sps);
@ -336,7 +352,6 @@ private:
if(!_pps.empty()){ if(!_pps.empty()){
auto ppsFrame = std::make_shared<H264Frame>(); auto ppsFrame = std::make_shared<H264Frame>();
ppsFrame->type = H264Frame::NAL_PPS;
ppsFrame->iPrefixSize = 4; ppsFrame->iPrefixSize = 4;
ppsFrame->buffer.assign("\x0\x0\x0\x1",4); ppsFrame->buffer.assign("\x0\x0\x0\x1",4);
ppsFrame->buffer.append(_pps); ppsFrame->buffer.append(_pps);

View File

@ -80,7 +80,6 @@ bool H264RtmpDecoder::decodeRtmp(const RtmpPacket::Ptr &pkt) {
inline void H264RtmpDecoder::onGetH264(const char* pcData, int iLen, uint32_t dts,uint32_t pts) { inline void H264RtmpDecoder::onGetH264(const char* pcData, int iLen, uint32_t dts,uint32_t pts) {
#if 1 #if 1
_h264frame->type = H264_TYPE(pcData[0]);
_h264frame->timeStamp = dts; _h264frame->timeStamp = dts;
_h264frame->ptsStamp = pts; _h264frame->ptsStamp = pts;
_h264frame->buffer.assign("\x0\x0\x0\x1", 4); //添加264头 _h264frame->buffer.assign("\x0\x0\x0\x1", 4); //添加264头

View File

@ -100,12 +100,10 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
//a full frame //a full frame
_h264frame->buffer.assign("\x0\x0\x0\x1", 4); _h264frame->buffer.assign("\x0\x0\x0\x1", 4);
_h264frame->buffer.append((char *)frame, length); _h264frame->buffer.append((char *)frame, length);
_h264frame->type = nal.type;
_h264frame->timeStamp = rtppack->timeStamp; _h264frame->timeStamp = rtppack->timeStamp;
_h264frame->sequence = rtppack->sequence; auto key = _h264frame->keyFrame();
auto isIDR = _h264frame->type == H264Frame::NAL_IDR;
onGetH264(_h264frame); onGetH264(_h264frame);
return (isIDR); //i frame return (key); //i frame
} }
switch (nal.type){ switch (nal.type){
@ -131,9 +129,7 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
MakeNalu(ptr[0], nal); MakeNalu(ptr[0], nal);
_h264frame->buffer.assign("\x0\x0\x0\x1", 4); _h264frame->buffer.assign("\x0\x0\x0\x1", 4);
_h264frame->buffer.append((char *)ptr, len); _h264frame->buffer.append((char *)ptr, len);
_h264frame->type = nal.type;
_h264frame->timeStamp = rtppack->timeStamp; _h264frame->timeStamp = rtppack->timeStamp;
_h264frame->sequence = rtppack->sequence;
if(nal.type == H264Frame::NAL_IDR){ if(nal.type == H264Frame::NAL_IDR){
haveIDR = true; haveIDR = true;
} }
@ -148,35 +144,39 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
//FU-A //FU-A
FU fu; FU fu;
MakeFU(frame[1], fu); MakeFU(frame[1], fu);
if (fu.S == 1) { if (fu.S) {
//FU-A start //该帧的第一个rtp包
char tmp = (nal.forbidden_zero_bit << 7 | nal.nal_ref_idc << 5 | fu.type); char tmp = (nal.forbidden_zero_bit << 7 | nal.nal_ref_idc << 5 | fu.type);
_h264frame->buffer.assign("\x0\x0\x0\x1", 4); _h264frame->buffer.assign("\x0\x0\x0\x1", 4);
_h264frame->buffer.push_back(tmp); _h264frame->buffer.push_back(tmp);
_h264frame->buffer.append((char *)frame + 2, length - 2); _h264frame->buffer.append((char *)frame + 2, length - 2);
_h264frame->type = fu.type;
_h264frame->timeStamp = rtppack->timeStamp; _h264frame->timeStamp = rtppack->timeStamp;
_h264frame->sequence = rtppack->sequence; //该函数return时保存下当前sequence,以便下次对比seq是否连续
return (_h264frame->type == H264Frame::NAL_IDR); //i frame _lastSeq = rtppack->sequence;
return _h264frame->keyFrame();
} }
if (rtppack->sequence != (uint16_t)(_h264frame->sequence + 1)) { if (rtppack->sequence != _lastSeq + 1 && rtppack->sequence != 0) {
//中间的或末尾的rtp包其seq必须连续(如果回环了则判定为连续)否则说明rtp丢包那么该帧不完整必须得丢弃
_h264frame->buffer.clear(); _h264frame->buffer.clear();
WarnL << "丢包,帧废弃:" << rtppack->sequence << "," << _h264frame->sequence; WarnL << "rtp sequence不连续: " << rtppack->sequence << " != " << _lastSeq << " + 1,该帧被废弃";
return false; return false;
} }
_h264frame->sequence = rtppack->sequence;
if (fu.E == 1) { if (!fu.E) {
//FU-A end //该帧的中间rtp包
_h264frame->buffer.append((char *)frame + 2, length - 2); _h264frame->buffer.append((char *)frame + 2, length - 2);
_h264frame->timeStamp = rtppack->timeStamp; //该函数return时保存下当前sequence,以便下次对比seq是否连续
auto isIDR = _h264frame->type == H264Frame::NAL_IDR; _lastSeq = rtppack->sequence;
onGetH264(_h264frame); return false;
return isIDR;
} }
//FU-A mid
//该帧最后一个rtp包
_h264frame->buffer.append((char *)frame + 2, length - 2); _h264frame->buffer.append((char *)frame + 2, length - 2);
return false; _h264frame->timeStamp = rtppack->timeStamp;
auto key = _h264frame->keyFrame();
onGetH264(_h264frame);
return key;
} }
default:{ default:{
@ -195,10 +195,8 @@ bool H264RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
void H264RtpDecoder::onGetH264(const H264Frame::Ptr &frame) { void H264RtpDecoder::onGetH264(const H264Frame::Ptr &frame) {
//写入环形缓存 //写入环形缓存
auto lastSeq = _h264frame->sequence;
RtpCodec::inputFrame(frame); RtpCodec::inputFrame(frame);
_h264frame = obtainFrame(); _h264frame = obtainFrame();
_h264frame->sequence = lastSeq;
} }

View File

@ -64,6 +64,7 @@ private:
H264Frame::Ptr obtainFrame(); H264Frame::Ptr obtainFrame();
private: private:
H264Frame::Ptr _h264frame; H264Frame::Ptr _h264frame;
int _lastSeq = 0;
}; };
/** /**

View File

@ -25,9 +25,47 @@
*/ */
#include "H265.h" #include "H265.h"
#include "SPSParser.h"
#include "Util/logger.h"
namespace mediakit{ namespace mediakit{
bool getHEVCInfo(const char * vps, int vps_len,const char * sps,int sps_len,int &iVideoWidth, int &iVideoHeight, float &iVideoFps){
T_GetBitContext tGetBitBuf;
T_HEVCSPS tH265SpsInfo;
T_HEVCVPS tH265VpsInfo;
if ( vps_len > 2 ){
memset(&tGetBitBuf,0,sizeof(tGetBitBuf));
memset(&tH265VpsInfo,0,sizeof(tH265VpsInfo));
tGetBitBuf.pu8Buf = (uint8_t*)vps+2;
tGetBitBuf.iBufSize = vps_len-2;
if(0 != h265DecVideoParameterSet((void *) &tGetBitBuf, &tH265VpsInfo)){
return false;
}
}
if ( sps_len > 2 ){
memset(&tGetBitBuf,0,sizeof(tGetBitBuf));
memset(&tH265SpsInfo,0,sizeof(tH265SpsInfo));
tGetBitBuf.pu8Buf = (uint8_t*)sps+2;
tGetBitBuf.iBufSize = sps_len-2;
if(0 != h265DecSeqParameterSet((void *) &tGetBitBuf, &tH265SpsInfo)){
return false;
}
}
else
return false;
h265GetWidthHeight(&tH265SpsInfo, &iVideoWidth, &iVideoHeight);
iVideoFps = 0;
h265GeFramerate(&tH265VpsInfo, &tH265SpsInfo, &iVideoFps);
// ErrorL << iVideoWidth << " " << iVideoHeight << " " << iVideoFps;
return true;
}
bool getHEVCInfo(const string &strVps, const string &strSps, int &iVideoWidth, int &iVideoHeight, float &iVideoFps) {
return getHEVCInfo(strVps.data(),strVps.size(),strSps.data(),strSps.size(),iVideoWidth,iVideoHeight,iVideoFps);
}
Sdp::Ptr H265Track::getSdp() { Sdp::Ptr H265Track::getSdp() {
if(!ready()){ if(!ready()){
WarnL << "H265 Track未准备好"; WarnL << "H265 Track未准备好";

View File

@ -36,6 +36,8 @@ using namespace toolkit;
namespace mediakit { namespace mediakit {
bool getHEVCInfo(const string &strVps, const string &strSps, int &iVideoWidth, int &iVideoHeight, float &iVideoFps);
/** /**
* 265 * 265
*/ */
@ -83,6 +85,10 @@ public:
return timeStamp; return timeStamp;
} }
uint32_t pts() const override {
return ptsStamp ? ptsStamp : timeStamp;
}
uint32_t prefixSize() const override { uint32_t prefixSize() const override {
return iPrefixSize; return iPrefixSize;
} }
@ -96,7 +102,18 @@ public:
} }
bool keyFrame() const override { bool keyFrame() const override {
return isKeyFrame(type); return isKeyFrame(H265_TYPE(buffer[iPrefixSize]));
}
bool configFrame() const override{
switch(H265_TYPE(buffer[iPrefixSize])){
case H265Frame::NAL_VPS:
case H265Frame::NAL_SPS:
case H265Frame::NAL_PPS:
return true;
default:
return false;
}
} }
static bool isKeyFrame(int type) { static bool isKeyFrame(int type) {
@ -114,9 +131,8 @@ public:
} }
public: public:
uint16_t sequence;
uint32_t timeStamp; uint32_t timeStamp;
unsigned char type; uint32_t ptsStamp = 0;
string buffer; string buffer;
uint32_t iPrefixSize = 4; uint32_t iPrefixSize = 4;
}; };
@ -143,8 +159,18 @@ public:
} }
bool keyFrame() const override { bool keyFrame() const override {
int type = H265_TYPE(((uint8_t *) _ptr)[_prefixSize]); return H265Frame::isKeyFrame(H265_TYPE(((uint8_t *) _ptr)[_prefixSize]));
return H265Frame::isKeyFrame(type); }
bool configFrame() const override{
switch(H265_TYPE(((uint8_t *) _ptr)[_prefixSize])){
case H265Frame::NAL_VPS:
case H265Frame::NAL_SPS:
case H265Frame::NAL_PPS:
return true;
default:
return false;
}
} }
}; };
@ -176,6 +202,7 @@ public:
_vps = vps.substr(vps_prefix_len); _vps = vps.substr(vps_prefix_len);
_sps = sps.substr(sps_prefix_len); _sps = sps.substr(sps_prefix_len);
_pps = pps.substr(pps_prefix_len); _pps = pps.substr(pps_prefix_len);
onReady();
} }
/** /**
@ -206,6 +233,30 @@ public:
return CodecH265; return CodecH265;
} }
/**
*
* @return
*/
int getVideoHeight() const override{
return _height ;
}
/**
*
* @return
*/
int getVideoWidth() const override{
return _width;
}
/**
* fps
* @return
*/
float getVideoFps() const override{
return _fps;
}
bool ready() override { bool ready() override {
return !_vps.empty() && !_sps.empty() && !_pps.empty(); return !_vps.empty() && !_sps.empty() && !_pps.empty();
} }
@ -280,6 +331,12 @@ private:
} }
} }
/**
* sps获取宽高fps
*/
void onReady(){
getHEVCInfo(_vps, _sps, _width, _height, _fps);
}
Track::Ptr clone() override { Track::Ptr clone() override {
return std::make_shared<std::remove_reference<decltype(*this)>::type>(*this); return std::make_shared<std::remove_reference<decltype(*this)>::type>(*this);
} }
@ -294,7 +351,6 @@ private:
} }
if(!_vps.empty()){ if(!_vps.empty()){
auto vpsFrame = std::make_shared<H265Frame>(); auto vpsFrame = std::make_shared<H265Frame>();
vpsFrame->type = H265Frame::NAL_VPS;
vpsFrame->iPrefixSize = 4; vpsFrame->iPrefixSize = 4;
vpsFrame->buffer.assign("\x0\x0\x0\x1", 4); vpsFrame->buffer.assign("\x0\x0\x0\x1", 4);
vpsFrame->buffer.append(_vps); vpsFrame->buffer.append(_vps);
@ -303,7 +359,6 @@ private:
} }
if (!_sps.empty()) { if (!_sps.empty()) {
auto spsFrame = std::make_shared<H265Frame>(); auto spsFrame = std::make_shared<H265Frame>();
spsFrame->type = H265Frame::NAL_SPS;
spsFrame->iPrefixSize = 4; spsFrame->iPrefixSize = 4;
spsFrame->buffer.assign("\x0\x0\x0\x1", 4); spsFrame->buffer.assign("\x0\x0\x0\x1", 4);
spsFrame->buffer.append(_sps); spsFrame->buffer.append(_sps);
@ -313,7 +368,6 @@ private:
if (!_pps.empty()) { if (!_pps.empty()) {
auto ppsFrame = std::make_shared<H265Frame>(); auto ppsFrame = std::make_shared<H265Frame>();
ppsFrame->type = H265Frame::NAL_PPS;
ppsFrame->iPrefixSize = 4; ppsFrame->iPrefixSize = 4;
ppsFrame->buffer.assign("\x0\x0\x0\x1", 4); ppsFrame->buffer.assign("\x0\x0\x0\x1", 4);
ppsFrame->buffer.append(_pps); ppsFrame->buffer.append(_pps);
@ -325,6 +379,9 @@ private:
string _vps; string _vps;
string _sps; string _sps;
string _pps; string _pps;
int _width = 0;
int _height = 0;
float _fps = 0;
bool _last_frame_is_idr = false; bool _last_frame_is_idr = false;
}; };

View File

@ -99,56 +99,56 @@ bool H265RtpDecoder::decodeRtp(const RtpPacket::Ptr &rtppack) {
// fragmentation unit (FU) // fragmentation unit (FU)
FU fu; FU fu;
MakeFU(frame[2], fu); MakeFU(frame[2], fu);
if (fu.S == 1) { if (fu.S) {
//FU-A start //该帧的第一个rtp包
_h265frame->buffer.assign("\x0\x0\x0\x1", 4); _h265frame->buffer.assign("\x0\x0\x0\x1", 4);
_h265frame->buffer.push_back(fu.type << 1); _h265frame->buffer.push_back(fu.type << 1);
_h265frame->buffer.push_back(0x01); _h265frame->buffer.push_back(0x01);
_h265frame->buffer.append((char *) frame + 3, length - 3); _h265frame->buffer.append((char *) frame + 3, length - 3);
_h265frame->type = fu.type;
_h265frame->timeStamp = rtppack->timeStamp; _h265frame->timeStamp = rtppack->timeStamp;
_h265frame->sequence = rtppack->sequence; //该函数return时保存下当前sequence,以便下次对比seq是否连续
_lastSeq = rtppack->sequence;
return (_h265frame->keyFrame()); //i frame return (_h265frame->keyFrame()); //i frame
} }
if (rtppack->sequence != (uint16_t) (_h265frame->sequence + 1)) { if (rtppack->sequence != _lastSeq + 1 && rtppack->sequence != 0) {
//中间的或末尾的rtp包其seq必须连续(如果回环了则判定为连续)否则说明rtp丢包那么该帧不完整必须得丢弃
_h265frame->buffer.clear(); _h265frame->buffer.clear();
WarnL << "丢包,帧废弃:" << rtppack->sequence << "," << _h265frame->sequence; WarnL << "rtp sequence不连续: " << rtppack->sequence << " != " << _lastSeq << " + 1,该帧被废弃";
return false; return false;
} }
_h265frame->sequence = rtppack->sequence;
if (fu.E == 1) { if (!fu.E) {
//FU-A end //该帧的中间rtp包
_h265frame->buffer.append((char *) frame + 3, length - 3); _h265frame->buffer.append((char *) frame + 3, length - 3);
_h265frame->timeStamp = rtppack->timeStamp; //该函数return时保存下当前sequence,以便下次对比seq是否连续
auto isIDR = _h265frame->keyFrame(); _lastSeq = rtppack->sequence;
onGetH265(_h265frame); return false;
return isIDR;
} }
//FU-A mid
//该帧最后一个rtp包
_h265frame->buffer.append((char *) frame + 3, length - 3); _h265frame->buffer.append((char *) frame + 3, length - 3);
return false; _h265frame->timeStamp = rtppack->timeStamp;
auto key = _h265frame->keyFrame();
onGetH265(_h265frame);
return key;
} }
default: // 4.4.1. Single NAL Unit Packets (p24) default: // 4.4.1. Single NAL Unit Packets (p24)
//a full frame //a full frame
_h265frame->buffer.assign("\x0\x0\x0\x1", 4); _h265frame->buffer.assign("\x0\x0\x0\x1", 4);
_h265frame->buffer.append((char *)frame, length); _h265frame->buffer.append((char *)frame, length);
_h265frame->type = nal;
_h265frame->timeStamp = rtppack->timeStamp; _h265frame->timeStamp = rtppack->timeStamp;
_h265frame->sequence = rtppack->sequence; auto key = _h265frame->keyFrame();
auto isIDR = _h265frame->keyFrame();
onGetH265(_h265frame); onGetH265(_h265frame);
return (isIDR); //i frame return key;
} }
} }
void H265RtpDecoder::onGetH265(const H265Frame::Ptr &frame) { void H265RtpDecoder::onGetH265(const H265Frame::Ptr &frame) {
//写入环形缓存 //写入环形缓存
auto lastSeq = _h265frame->sequence;
RtpCodec::inputFrame(frame); RtpCodec::inputFrame(frame);
_h265frame = obtainFrame(); _h265frame = obtainFrame();
_h265frame->sequence = lastSeq;
} }

View File

@ -65,6 +65,7 @@ private:
H265Frame::Ptr obtainFrame(); H265Frame::Ptr obtainFrame();
private: private:
H265Frame::Ptr _h265frame; H265Frame::Ptr _h265frame;
int _lastSeq = 0;
}; };
/** /**

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,15 @@
#define QP_MAX_NUM (51 + 6*6) // The maximum supported qp #define QP_MAX_NUM (51 + 6*6) // The maximum supported qp
#define HEVC_MAX_SHORT_TERM_RPS_COUNT 64
#define T_PROFILE_HEVC_MAIN 1
#define T_PROFILE_HEVC_MAIN_10 2
#define T_PROFILE_HEVC_MAIN_STILL_PICTURE 3
#define T_PROFILE_HEVC_REXT 4
/** /**
* Chromaticity coordinates of the source primaries. * Chromaticity coordinates of the source primaries.
*/ */
@ -67,6 +76,62 @@ enum T_AVColorSpace {
}; };
enum {
// 7.4.3.1: vps_max_layers_minus1 is in [0, 62].
HEVC_MAX_LAYERS = 63,
// 7.4.3.1: vps_max_sub_layers_minus1 is in [0, 6].
HEVC_MAX_SUB_LAYERS = 7,
// 7.4.3.1: vps_num_layer_sets_minus1 is in [0, 1023].
HEVC_MAX_LAYER_SETS = 1024,
// 7.4.2.1: vps_video_parameter_set_id is u(4).
HEVC_MAX_VPS_COUNT = 16,
// 7.4.3.2.1: sps_seq_parameter_set_id is in [0, 15].
HEVC_MAX_SPS_COUNT = 16,
// 7.4.3.3.1: pps_pic_parameter_set_id is in [0, 63].
HEVC_MAX_PPS_COUNT = 64,
// A.4.2: MaxDpbSize is bounded above by 16.
HEVC_MAX_DPB_SIZE = 16,
// 7.4.3.1: vps_max_dec_pic_buffering_minus1[i] is in [0, MaxDpbSize - 1].
HEVC_MAX_REFS = HEVC_MAX_DPB_SIZE,
// 7.4.3.2.1: num_short_term_ref_pic_sets is in [0, 64].
HEVC_MAX_SHORT_TERM_REF_PIC_SETS = 64,
// 7.4.3.2.1: num_long_term_ref_pics_sps is in [0, 32].
HEVC_MAX_LONG_TERM_REF_PICS = 32,
// A.3: all profiles require that CtbLog2SizeY is in [4, 6].
HEVC_MIN_LOG2_CTB_SIZE = 4,
HEVC_MAX_LOG2_CTB_SIZE = 6,
// E.3.2: cpb_cnt_minus1[i] is in [0, 31].
HEVC_MAX_CPB_CNT = 32,
// A.4.1: in table A.6 the highest level allows a MaxLumaPs of 35 651 584.
HEVC_MAX_LUMA_PS = 35651584,
// A.4.1: pic_width_in_luma_samples and pic_height_in_luma_samples are
// constrained to be not greater than sqrt(MaxLumaPs * 8). Hence height/
// width are bounded above by sqrt(8 * 35651584) = 16888.2 samples.
HEVC_MAX_WIDTH = 16888,
HEVC_MAX_HEIGHT = 16888,
// A.4.1: table A.6 allows at most 22 tile rows for any level.
HEVC_MAX_TILE_ROWS = 22,
// A.4.1: table A.6 allows at most 20 tile columns for any level.
HEVC_MAX_TILE_COLUMNS = 20,
// 7.4.7.1: in the worst case (tiles_enabled_flag and
// entropy_coding_sync_enabled_flag are both set), entry points can be
// placed at the beginning of every Ctb row in every tile, giving an
// upper bound of (num_tile_columns_minus1 + 1) * PicHeightInCtbsY - 1.
// Only a stream with very high resolution and perverse parameters could
// get near that, though, so set a lower limit here with the maximum
// possible value for 4K video (at most 135 16x16 Ctb rows).
HEVC_MAX_ENTRY_POINT_OFFSETS = HEVC_MAX_TILE_COLUMNS * 135,
};
/** /**
* rational number numerator/denominator * rational number numerator/denominator
*/ */
@ -170,6 +235,209 @@ typedef struct T_PPS {
int iChromaQpDiff; int iChromaQpDiff;
} T_PPS; } T_PPS;
typedef struct T_HEVCWindow {
unsigned int uiLeftOffset;
unsigned int uiRightOffset;
unsigned int uiTopOffset;
unsigned int uiBottomOffset;
} T_HEVCWindow;
typedef struct T_VUI {
T_AVRational tSar;
int iOverscanInfoPresentFlag;
int iOverscanAppropriateFlag;
int iVideoSignalTypePresentFlag;
int iVideoFormat;
int iVideoFullRangeFlag;
int iColourDescriptionPresentFlag;
uint8_t u8ColourPrimaries;
uint8_t u8TransferCharacteristic;
uint8_t u8MatrixCoeffs;
int iChromaLocInfoPresentFlag;
int iChromaSampleLocTypeTopField;
int iChromaSampleLocTypeBottomField;
int iNeutraChromaIndicationFlag;
int iFieldSeqFlag;
int iFrameFieldInfoPresentFlag;
int iDefaultDisplayWindowFlag;
T_HEVCWindow tDefDispWin;
int iVuiTimingInfoPresentFlag;
uint32_t u32VuiNumUnitsInTick;
uint32_t u32VuiTimeScale;
int iVuiPocProportionalToTimingFlag;
int iVuiNumTicksPocDiffOneMinus1;
int iVuiHrdParametersPresentFlag;
int iBitstreamRestrictionFlag;
int iTilesFixedStructureFlag;
int iMotionVectorsOverPicBoundariesFlag;
int iRestrictedRefPicListsFlag;
int iMinSpatialSegmentationIdc;
int iMaxBytesPerPicDenom;
int iMaxBitsPerMinCuDenom;
int iLog2MaxMvLengthHorizontal;
int iLog2MaxMvLengthVertical;
} T_VUI;
typedef struct T_PTLCommon {
uint8_t u8ProfileSpace;
uint8_t u8TierFlag;
uint8_t u8ProfileIdc;
uint8_t au8ProfileCompatibilityFlag[32];
uint8_t u8LevelIdc;
uint8_t u8ProgressiveSourceFlag;
uint8_t u8InterlacedSourceFlag;
uint8_t u8NonPackedConstraintFlag;
uint8_t u8FrameOnlyConstraintFlag;
} T_PTLCommon;
typedef struct T_PTL {
T_PTLCommon tGeneralPtl;
T_PTLCommon atSubLayerPtl[HEVC_MAX_SUB_LAYERS];
uint8_t au8SubLayerProfilePresentFlag[HEVC_MAX_SUB_LAYERS];
uint8_t au8SubLayerLevelPresentFlag[HEVC_MAX_SUB_LAYERS];
} T_PTL;
typedef struct T_ScalingList {
/* This is a little wasteful, since sizeID 0 only needs 8 coeffs,
* and size ID 3 only has 2 arrays, not 6. */
uint8_t aaau8Sl[4][6][64];
uint8_t aau8SlDc[2][6];
} T_ScalingList;
typedef struct T_ShortTermRPS {
unsigned int uiNumNegativePics;
int iNumDeltaPocs;
int iRpsIdxNumDeltaPocs;
int32_t au32DeltaPoc[32];
uint8_t au8Used[32];
} T_ShortTermRPS;
typedef struct T_HEVCVPS {
uint8_t u8VpsTemporalIdNestingFlag;
int iVpsMaxLayers;
int iVpsMaxSubLayers; ///< vps_max_temporal_layers_minus1 + 1
T_PTL tPtl;
int iVpsSubLayerOrderingInfoPresentFlag;
unsigned int uiVpsMaxDecPicBuffering[HEVC_MAX_SUB_LAYERS];
unsigned int auiVpsNumReorderPics[HEVC_MAX_SUB_LAYERS];
unsigned int auiVpsMaxLatencyIncrease[HEVC_MAX_SUB_LAYERS];
int iVpsMaxLayerId;
int iVpsNumLayerSets; ///< vps_num_layer_sets_minus1 + 1
uint8_t u8VpsTimingInfoPresentFlag;
uint32_t u32VpsNumUnitsInTick;
uint32_t u32VpsTimeScale;
uint8_t u8VpsPocProportionalToTimingFlag;
int iVpsNumTicksPocDiffOne; ///< vps_num_ticks_poc_diff_one_minus1 + 1
int iVpsNumHrdParameters;
} T_HEVCVPS;
typedef struct T_HEVCSPS {
unsigned int uiVpsId;
int iChromaFormatIdc;
uint8_t u8SeparateColourPlaneFlag;
///< output (i.e. cropped) values
int iIutputWidth, iOutputHeight;
T_HEVCWindow tOutputWindow;
T_HEVCWindow tPicConfWin;
int iBitDepth;
int iBitDepthChroma;
int iPixelShift;
unsigned int uiLog2MaxPocLsb;
int iPcmEnabledFlag;
int iMaxSubLayers;
struct {
int iMaxDecPicBuffering;
int iNumReorderPics;
int iMaxLatencyIncrease;
} stTemporalLayer[HEVC_MAX_SUB_LAYERS];
uint8_t u8temporalIdNestingFlag;
T_VUI tVui;
T_PTL tPtl;
uint8_t u8ScalingListEnableFlag;
T_ScalingList tScalingList;
unsigned int uiNbStRps;
T_ShortTermRPS atStRps[HEVC_MAX_SHORT_TERM_RPS_COUNT];
uint8_t u8AmpEnabledFlag;
uint8_t u8SaoEnabled;
uint8_t u8LongTermRefPicsPresentFlag;
uint16_t au16LtRefPicPocLsbSps[32];
uint8_t au8UsedByCurrPicLtSpsFlag[32];
uint8_t u8NumLongTermRefPicsSps;
struct {
uint8_t u8BitDepth;
uint8_t u8BitDepthChroma;
unsigned int uiLog2MinPcmCbSize;
unsigned int uiLog2MaxPcmCbSize;
uint8_t u8LoopFilterDisableFlag;
} pcm;
uint8_t u8SpsTemporalMvpEnabledFlag;
uint8_t u8SpsStrongIntraMmoothingEnableFlag;
unsigned int uiLog2MinCbSize;
unsigned int uiLog2DiffMaxMinCodingBlockSize;
unsigned int uiLog2MinTbSize;
unsigned int uiLog2MaxTrafoSize;
unsigned int uiLog2CtbSize;
unsigned int uiLog2MinPuSize;
int iMaxTransformHierarchyDepthInter;
int iMaxTransformHierarchyDepthIntra;
int iTransformSkipRotationEnabledFlag;
int iTransformSkipContextEnabledFlag;
int iImplicitRdpcmEnabledFlag;
int iExplicitRdpcmEnabledFlag;
int iIntraSmoothingDisabledFlag;
int iHighPrecisionOffsetsEnabledFlag;
int iPersistentRiceAdaptationEnabledFlag;
///< coded frame dimension in various units
int iWidth;
int iHeight;
int iCtbWidth;
int iCtbHeight;
int iCtbSize;
int iMinCbWidth;
int iMinCbHeight;
int iMinTbWidth;
int iMinTbHeight;
int iMinPuWidth;
int iMinPuHeight;
int iTbMask;
int aiHshift[3];
int aiVshift[3];
int iQpBdOffset;
int iVuiPresent;
}T_HEVCSPS;
typedef struct T_GetBitContext{ typedef struct T_GetBitContext{
uint8_t *pu8Buf; /*Ö¸ÏòSPS start*/ uint8_t *pu8Buf; /*Ö¸ÏòSPS start*/
int iBufSize; /*SPS ³¤¶È*/ int iBufSize; /*SPS ³¤¶È*/
@ -180,8 +448,15 @@ typedef struct T_GetBitContext{
int h264DecSeqParameterSet(void *pvBuf, T_SPS *ptSps); int h264DecSeqParameterSet(void *pvBuf, T_SPS *ptSps);
int h265DecSeqParameterSet( void *pvBufSrc, T_HEVCSPS *ptSps );
int h265DecVideoParameterSet( void *pvBufSrc, T_HEVCVPS *ptVps );
void h264GetWidthHeight(T_SPS *ptSps, int *piWidth, int *piHeight); void h264GetWidthHeight(T_SPS *ptSps, int *piWidth, int *piHeight);
void h265GetWidthHeight(T_HEVCSPS *ptSps, int *piWidth, int *piHeight);
void h264GeFramerate(T_SPS *ptSps, float *pfFramerate); void h264GeFramerate(T_SPS *ptSps, float *pfFramerate);
void h265GeFramerate(T_HEVCVPS *ptVps, T_HEVCSPS *ptSps,float *pfFramerate);
#if defined (__cplusplus) #if defined (__cplusplus)
} }

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -212,11 +212,12 @@ inline bool HttpSession::checkWebSocket(){
headerOut["Sec-WebSocket-Protocol"] = _parser["Sec-WebSocket-Protocol"]; headerOut["Sec-WebSocket-Protocol"] = _parser["Sec-WebSocket-Protocol"];
} }
sendResponse("101 Switching Protocols",headerOut,""); sendResponse("101 Switching Protocols",headerOut,"");
checkLiveFlvStream(true);
return true; return true;
} }
//http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2 //http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2
//如果url(除去?以及后面的参数)后缀是.flv,那么表明该url是一个http-flv直播。 //如果url(除去?以及后面的参数)后缀是.flv,那么表明该url是一个http-flv直播。
inline bool HttpSession::checkLiveFlvStream(){ inline bool HttpSession::checkLiveFlvStream(bool over_websocket){
auto pos = strrchr(_parser.Url().data(),'.'); auto pos = strrchr(_parser.Url().data(),'.');
if(!pos){ if(!pos){
//未找到".flv"后缀 //未找到".flv"后缀
@ -239,7 +240,7 @@ inline bool HttpSession::checkLiveFlvStream(){
bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt); bool bClose = (strcasecmp(_parser["Connection"].data(),"close") == 0) || ( ++_iReqCnt > reqCnt);
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this()); weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
MediaSource::findAsync(_mediaInfo,weakSelf.lock(), true,[weakSelf,bClose,this](const MediaSource::Ptr &src){ MediaSource::findAsync(_mediaInfo,weakSelf.lock(), true,[weakSelf,bClose,this,over_websocket](const MediaSource::Ptr &src){
auto strongSelf = weakSelf.lock(); auto strongSelf = weakSelf.lock();
if(!strongSelf){ if(!strongSelf){
//本对象已经销毁 //本对象已经销毁
@ -248,29 +249,35 @@ inline bool HttpSession::checkLiveFlvStream(){
auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src); auto rtmp_src = dynamic_pointer_cast<RtmpMediaSource>(src);
if(!rtmp_src){ if(!rtmp_src){
//未找到该流 //未找到该流
sendNotFound(bClose); if(!over_websocket){
sendNotFound(bClose);
}
if(bClose){ if(bClose){
shutdown(SockException(Err_shutdown,"flv stream not found")); shutdown(SockException(Err_shutdown,"flv stream not found"));
} }
return; return;
} }
//找到流了 //找到流了
auto onRes = [this,rtmp_src](const string &err){ auto onRes = [this,rtmp_src,over_websocket](const string &err){
bool authSuccess = err.empty(); bool authSuccess = err.empty();
if(!authSuccess){ if(!authSuccess){
sendResponse("401 Unauthorized", makeHttpHeader(true,err.size()),err); if(!over_websocket){
sendResponse("401 Unauthorized", makeHttpHeader(true,err.size()),err);
}
shutdown(SockException(Err_shutdown,StrPrinter << "401 Unauthorized:" << err)); shutdown(SockException(Err_shutdown,StrPrinter << "401 Unauthorized:" << err));
return ; return ;
} }
//找到rtmp源发送http头负载后续发送 if(!over_websocket) {
sendResponse("200 OK", makeHttpHeader(false,0,get_mime_type(".flv")), ""); //找到rtmp源发送http头负载后续发送
sendResponse("200 OK", makeHttpHeader(false, 0, get_mime_type(".flv")), "");
}
//开始发送rtmp负载 //开始发送rtmp负载
//关闭tcp_nodelay ,优化性能 //关闭tcp_nodelay ,优化性能
SockUtil::setNoDelay(_sock->rawFD(),false); SockUtil::setNoDelay(_sock->rawFD(),false);
(*this) << SocketFlags(kSockFlags); (*this) << SocketFlags(kSockFlags);
_flv_over_websocket = over_websocket;
try{ try{
start(getPoller(),rtmp_src); start(getPoller(),rtmp_src);
}catch (std::exception &ex){ }catch (std::exception &ex){
@ -473,7 +480,7 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
} }
//再看看是否为http-flv直播请求 //再看看是否为http-flv直播请求
if(checkLiveFlvStream()){ if(checkLiveFlvStream(false)){
return; return;
} }
@ -936,8 +943,23 @@ inline void HttpSession::sendNotFound(bool bClose) {
void HttpSession::onWrite(const Buffer::Ptr &buffer) { void HttpSession::onWrite(const Buffer::Ptr &buffer) {
_ticker.resetTime(); _ticker.resetTime();
_ui64TotalBytes += buffer->size(); if(!_flv_over_websocket){
send(buffer); _ui64TotalBytes += buffer->size();
send(buffer);
return;
}
WebSocketHeader header;
header._fin = true;
header._reserved = 0;
header._opcode = WebSocketHeader::BINARY;
header._mask_flag = false;
WebSocketSplitter::encode(header,(uint8_t *)buffer->data(),buffer->size());
}
void HttpSession::onWebSocketEncodeData(const uint8_t *ptr,uint64_t len){
_ui64TotalBytes += len;
SocketHelper::send((char *)ptr,len);
} }
void HttpSession::onDetach() { void HttpSession::onDetach() {

View File

@ -102,10 +102,16 @@ protected:
WebSocketSplitter::decode((uint8_t *)data,len); WebSocketSplitter::decode((uint8_t *)data,len);
} }
/**
* websocket协议打包后回调
* @param ptr
* @param len
*/
void onWebSocketEncodeData(const uint8_t *ptr,uint64_t len) override;
private: private:
inline void Handle_Req_GET(int64_t &content_len); inline void Handle_Req_GET(int64_t &content_len);
inline void Handle_Req_POST(int64_t &content_len); inline void Handle_Req_POST(int64_t &content_len);
inline bool checkLiveFlvStream(); inline bool checkLiveFlvStream(bool over_websocket = false);
inline bool checkWebSocket(); inline bool checkWebSocket();
inline bool emitHttpEvent(bool doInvoke); inline bool emitHttpEvent(bool doInvoke);
inline void urlDecode(Parser &parser); inline void urlDecode(Parser &parser);
@ -148,6 +154,7 @@ private:
MediaInfo _mediaInfo; MediaInfo _mediaInfo;
//处理content数据的callback //处理content数据的callback
function<bool (const char *data,uint64_t len) > _contentCallBack; function<bool (const char *data,uint64_t len) > _contentCallBack;
bool _flv_over_websocket = false;
}; };

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

282
src/MediaFile/MP4Muxer.cpp Normal file
View File

@ -0,0 +1,282 @@
/*
* 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.
*/
#ifdef ENABLE_MP4RECORD
#include "MP4Muxer.h"
#include "Util/File.h"
#include "Common/config.h"
namespace mediakit{
#if defined(_WIN32) || defined(_WIN64)
#define fseek64 _fseeki64
#define ftell64 _ftelli64
#else
#define fseek64 fseek
#define ftell64 ftell
#endif
void MP4MuxerBase::init(int flags) {
static struct mov_buffer_t s_io = {
[](void* ctx, void* data, uint64_t bytes) {
MP4MuxerBase *thiz = (MP4MuxerBase *)ctx;
return thiz->onRead(data,bytes);
},
[](void* ctx, const void* data, uint64_t bytes){
MP4MuxerBase *thiz = (MP4MuxerBase *)ctx;
return thiz->onWrite(data,bytes);
},
[](void* ctx, uint64_t offset) {
MP4MuxerBase *thiz = (MP4MuxerBase *)ctx;
return thiz->onSeek(offset);
},
[](void* ctx){
MP4MuxerBase *thiz = (MP4MuxerBase *)ctx;
return thiz->onTell();
}
};
_mov_writter.reset(mov_writer_create(&s_io,this,flags),[](mov_writer_t *ptr){
if(ptr){
mov_writer_destroy(ptr);
}
});
}
///////////////////////////////////
void MP4Muxer::onTrackFrame(const Frame::Ptr &frame) {
if(frame->configFrame()){
//忽略配置帧
return;
}
auto it = _codec_to_trackid.find(frame->getCodecId());
if(it == _codec_to_trackid.end()){
//该Track不存在或初始化失败
return;
}
if(!_started){
//还没开始
if(frame->getTrackType() != TrackVideo || !frame->keyFrame()){
//如果首帧是音频或者是视频但是不是i帧那么不能开始写文件
return;
}
//开始写文件
_started = true;
}
int with_nalu_size ;
switch (frame->getCodecId()){
case CodecH264:
case CodecH265:
//我们输入264、265是没有头四个字节表明数据长度的
with_nalu_size = 0;
break;
default:
//aac或其他类型frame不用添加4个nalu_size的字节
with_nalu_size = 1;
break;
}
//mp4文件时间戳需要从0开始
auto &track_info = it->second;
int64_t dts_out, pts_out;
track_info.stamp.revise(frame->dts(),frame->pts(),dts_out,pts_out);
mov_writer_write_l(_mov_writter.get(),
track_info.track_id,
frame->data() + frame->prefixSize(),
frame->size() - frame->prefixSize(),
pts_out,
dts_out,
frame->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0,
with_nalu_size);
}
void MP4Muxer::onTrackReady(const Track::Ptr &track) {
switch (track->getCodecId()) {
case CodecAAC: {
auto aac_track = dynamic_pointer_cast<AACTrack>(track);
if (!aac_track) {
WarnL << "不是AAC Track";
return;
}
auto track_id = mov_writer_add_audio(_mov_writter.get(),
MOV_OBJECT_AAC,
aac_track->getAudioChannel(),
aac_track->getAudioSampleBit() * aac_track->getAudioChannel(),
aac_track->getAudioSampleRate(),
aac_track->getAacCfg().data(), 2);
if(track_id < 0){
WarnL << "添加AAC Track失败:" << track_id;
return;
}
track_info info;
info.track_id = track_id;
_codec_to_trackid[track->getCodecId()] = info;
}
break;
case CodecH264: {
auto h264_track = dynamic_pointer_cast<H264Track>(track);
if (!h264_track) {
WarnL << "不是H264 Track";
return;
}
struct mpeg4_avc_t avc;
string sps_pps = string("\x00\x00\x00\x01", 4) + h264_track->getSps() +
string("\x00\x00\x00\x01", 4) + h264_track->getPps();
h264_annexbtomp4(&avc, sps_pps.data(), sps_pps.size(), NULL, 0, NULL);
uint8_t extra_data[1024];
int extra_data_size = mpeg4_avc_decoder_configuration_record_save(&avc, extra_data, sizeof(extra_data));
if (extra_data_size == -1) {
WarnL << "生成H264 extra_data 失败";
return;
}
auto track_id = mov_writer_add_video(_mov_writter.get(),
MOV_OBJECT_H264,
h264_track->getVideoWidth(),
h264_track->getVideoHeight(),
extra_data,
extra_data_size);
if(track_id < 0){
WarnL << "添加H264 Track失败:" << track_id;
return;
}
track_info info;
info.track_id = track_id;
_codec_to_trackid[track->getCodecId()] = info;
}
break;
case CodecH265: {
auto h265_track = dynamic_pointer_cast<H265Track>(track);
if (!h265_track) {
WarnL << "不是H265 Track";
return;
}
struct mpeg4_hevc_t hevc;
string vps_sps_pps = string("\x00\x00\x00\x01", 4) + h265_track->getVps() +
string("\x00\x00\x00\x01", 4) + h265_track->getSps() +
string("\x00\x00\x00\x01", 4) + h265_track->getPps();
h265_annexbtomp4(&hevc, vps_sps_pps.data(), vps_sps_pps.size(), NULL, 0, NULL);
uint8_t extra_data[1024];
int extra_data_size = mpeg4_hevc_decoder_configuration_record_save(&hevc, extra_data, sizeof(extra_data));
if (extra_data_size == -1) {
WarnL << "生成H265 extra_data 失败";
return;
}
auto track_id = mov_writer_add_video(_mov_writter.get(),
MOV_OBJECT_HEVC,
h265_track->getVideoWidth(),
h265_track->getVideoHeight(),
extra_data,
extra_data_size);
if(track_id < 0){
WarnL << "添加H265 Track失败:" << track_id;
return;
}
track_info info;
info.track_id = track_id;
_codec_to_trackid[track->getCodecId()] = info;
}
break;
default:
WarnL << "MP4录制不支持该编码格式:" << track->getCodecId();
break;
}
}
MP4MuxerFile::MP4MuxerFile(const char *file) {
//创建文件
auto fp = File::createfile_file(file,"wb+");
if(!fp){
throw std::runtime_error(string("打开文件失败:") + file);
}
GET_CONFIG(uint32_t,mp4BufSize,Record::kFileBufSize);
//新建文件io缓存
std::shared_ptr<char> file_buf(new char[mp4BufSize],[](char *ptr){
if(ptr){
delete [] ptr;
}
});
if(file_buf){
//设置文件io缓存
setvbuf(fp, file_buf.get(), _IOFBF, mp4BufSize);
}
//创建智能指针
_file.reset(fp,[file_buf](FILE *fp) {
fclose(fp);
});
init(MOV_FLAG_FASTSTART);
}
MP4MuxerFile::~MP4MuxerFile() {
_mov_writter = nullptr;
}
int MP4MuxerFile::onRead(void *data, uint64_t bytes) {
if (bytes == fread(data, 1, bytes, _file.get())){
return 0;
}
return 0 != ferror(_file.get()) ? ferror(_file.get()) : -1 /*EOF*/;
}
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);
}
uint64_t MP4MuxerFile::onTell() {
return ftell64(_file.get());
}
}//namespace mediakit
#endif//#ifdef ENABLE_MP4RECORD

105
src/MediaFile/MP4Muxer.h Normal file
View File

@ -0,0 +1,105 @@
/*
* 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_MP4MUXER_H
#define ZLMEDIAKIT_MP4MUXER_H
#ifdef ENABLE_MP4RECORD
#include "Common/MediaSink.h"
#include "mov-writer.h"
#include "mpeg4-hevc.h"
#include "mpeg4-avc.h"
#include "mpeg4-aac.h"
#include "mov-buffer.h"
#include "mov-format.h"
#include "Extension/AAC.h"
#include "Extension/H264.h"
#include "Extension/H265.h"
#include "Stamp.h"
namespace mediakit{
class MP4MuxerBase{
public:
MP4MuxerBase() = default;
virtual ~MP4MuxerBase() = default;
protected:
virtual int onRead(void* data, uint64_t bytes) = 0;
virtual int onWrite(const void* data, uint64_t bytes) = 0;
virtual int onSeek( uint64_t offset) = 0;
virtual uint64_t onTell() = 0;
void init(int flags);
protected:
std::shared_ptr<mov_writer_t> _mov_writter;
};
class MP4Muxer : public MediaSink , 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
*/
void onTrackFrame(const Frame::Ptr &frame) override;
private:
struct track_info{
int track_id = -1;
Stamp stamp;
};
unordered_map<int,track_info> _codec_to_trackid;
bool _started = false;
};
class MP4MuxerFile : public MP4Muxer {
public:
typedef std::shared_ptr<MP4MuxerFile> Ptr;
MP4MuxerFile(const char *file);
~MP4MuxerFile();
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 ;
private:
std::shared_ptr<FILE> _file;
};
}//namespace mediakit
#endif//#ifdef ENABLE_MP4RECORD
#endif //ZLMEDIAKIT_MP4MUXER_H

View File

@ -106,11 +106,11 @@ void MediaRecorder::addTrack(const Track::Ptr &track) {
} }
#endif //defined(ENABLE_HLS) #endif //defined(ENABLE_HLS)
#if defined(ENABLE_MP4V2) #if defined(ENABLE_MP4RECORD)
if (_mp4Maker) { if (_mp4Maker) {
_mp4Maker->addTrack(track); _mp4Maker->addTrack(track);
} }
#endif //defined(ENABLE_MP4V2) #endif //defined(ENABLE_MP4RECORD)
} }
} /* namespace mediakit */ } /* namespace mediakit */

View File

@ -64,9 +64,9 @@ private:
std::shared_ptr<HlsRecorder> _hlsMaker; std::shared_ptr<HlsRecorder> _hlsMaker;
#endif //defined(ENABLE_HLS) #endif //defined(ENABLE_HLS)
#if defined(ENABLE_MP4V2) #if defined(ENABLE_MP4RECORD)
std::shared_ptr<Mp4Maker> _mp4Maker; std::shared_ptr<Mp4Maker> _mp4Maker;
#endif //defined(ENABLE_MP4V2) #endif //defined(ENABLE_MP4RECORD)
}; };
} /* namespace mediakit */ } /* namespace mediakit */

View File

@ -24,18 +24,13 @@
* SOFTWARE. * SOFTWARE.
*/ */
#ifdef ENABLE_MP4V2 #ifdef ENABLE_MP4RECORD
#include <ctime> #include <ctime>
#include <sys/stat.h> #include <sys/stat.h>
#include "Common/config.h" #include "Common/config.h"
#include "Mp4Maker.h" #include "Mp4Maker.h"
#include "MediaRecorder.h"
#include "Util/File.h"
#include "Util/mini.h"
#include "Util/util.h" #include "Util/util.h"
#include "Util/NoticeCenter.h" #include "Util/NoticeCenter.h"
#include "Extension/H264.h"
#include "Extension/AAC.h"
#include "Thread/WorkThreadPool.h" #include "Thread/WorkThreadPool.h"
using namespace toolkit; using namespace toolkit;
@ -62,88 +57,19 @@ Mp4Maker::Mp4Maker(const string& strPath,
const string &strVhost, const string &strVhost,
const string &strApp, const string &strApp,
const string &strStreamId) { const string &strStreamId) {
DebugL << strPath;
_strPath = strPath; _strPath = strPath;
/////record 业务逻辑////// /////record 业务逻辑//////
_info.strAppName = strApp; _info.strAppName = strApp;
_info.strStreamId = strStreamId; _info.strStreamId = strStreamId;
_info.strVhost = strVhost; _info.strVhost = strVhost;
_info.strFolder = strPath; _info.strFolder = strPath;
//----record 业务逻辑----//
} }
Mp4Maker::~Mp4Maker() { Mp4Maker::~Mp4Maker() {
closeFile(); closeFile();
} }
void Mp4Maker::inputH264(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp){
auto iType = H264_TYPE(((uint8_t*)pData)[0]);
switch (iType) {
case H264Frame::NAL_B_P: //P
case H264Frame::NAL_IDR: { //IDR
if (_strLastVideo.size()) {
int64_t iTimeInc = (int64_t)ui32TimeStamp - (int64_t)_ui32LastVideoTime;
iTimeInc = MAX(0,MIN(iTimeInc,500));
if(iTimeInc == 0 || iTimeInc == 500){
WarnL << "abnormal time stamp increment:" << ui32TimeStamp << " " << _ui32LastVideoTime;
}
inputH264_l((char *) _strLastVideo.data(), _strLastVideo.size(), iTimeInc);
}
uint32_t prefixe = htonl(ui32Length);
_strLastVideo.assign((char *) &prefixe, 4);
_strLastVideo.append((char *)pData,ui32Length);
_ui32LastVideoTime = ui32TimeStamp;
}
break;
default:
break;
}
}
void Mp4Maker::inputAAC(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp){
if (_strLastAudio.size()) {
int64_t iTimeInc = (int64_t)ui32TimeStamp - (int64_t)_ui32LastAudioTime;
iTimeInc = MAX(0,MIN(iTimeInc,500));
if(iTimeInc == 0 || iTimeInc == 500){
WarnL << "abnormal time stamp increment:" << ui32TimeStamp << " " << _ui32LastAudioTime;
}
inputAAC_l((char *) _strLastAudio.data(), _strLastAudio.size(), iTimeInc);
}
_strLastAudio.assign((char *)pData, ui32Length);
_ui32LastAudioTime = ui32TimeStamp;
}
void Mp4Maker::inputH264_l(void *pData, uint32_t ui32Length, uint32_t ui32Duration) {
GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);
auto iType = H264_TYPE(((uint8_t*)pData)[4]);
if(iType == H264Frame::NAL_IDR && (_hMp4 == MP4_INVALID_FILE_HANDLE || _ticker.elapsedTime() > recordSec * 1000)){
//在I帧率处新建MP4文件
//如果文件未创建或者文件超过10分钟则创建新文件
createFile();
}
if (_hVideo != MP4_INVALID_TRACK_ID) {
MP4WriteSample(_hMp4, _hVideo, (uint8_t *) pData, ui32Length,ui32Duration * 90,0,iType == 5);
}
}
void Mp4Maker::inputAAC_l(void *pData, uint32_t ui32Length, uint32_t ui32Duration) {
GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);
if (!_haveVideo && (_hMp4 == MP4_INVALID_FILE_HANDLE || _ticker.elapsedTime() > recordSec * 1000)) {
//在I帧率处新建MP4文件
//如果文件未创建或者文件超过10分钟则创建新文件
createFile();
}
if (_hAudio != MP4_INVALID_TRACK_ID) {
auto duration = ui32Duration * _audioSampleRate /1000.0;
MP4WriteSample(_hMp4, _hAudio, (uint8_t*)pData, ui32Length,duration,0,false);
}
}
void Mp4Maker::createFile() { void Mp4Maker::createFile() {
closeFile(); closeFile();
auto strDate = timeStr("%Y-%m-%d"); auto strDate = timeStr("%Y-%m-%d");
auto strTime = timeStr("%H-%M-%S"); auto strTime = timeStr("%H-%M-%S");
auto strFileTmp = _strPath + strDate + "/." + strTime + ".mp4"; auto strFileTmp = _strPath + strDate + "/." + strTime + ".mp4";
@ -153,76 +79,37 @@ void Mp4Maker::createFile() {
_info.ui64StartedTime = ::time(NULL); _info.ui64StartedTime = ::time(NULL);
_info.strFileName = strTime + ".mp4"; _info.strFileName = strTime + ".mp4";
_info.strFilePath = strFile; _info.strFilePath = strFile;
GET_CONFIG(string,appName,Record::kAppName); GET_CONFIG(string,appName,Record::kAppName);
_info.strUrl = appName + "/" _info.strUrl = appName + "/"
+ _info.strAppName + "/" + _info.strAppName + "/"
+ _info.strStreamId + "/" + _info.strStreamId + "/"
+ strDate + "/" + strDate + "/"
+ strTime + ".mp4"; + strTime + ".mp4";
//----record 业务逻辑----// try {
_muxer = std::make_shared<MP4MuxerFile>(strFileTmp.data());
#if !defined(_WIN32) for(auto &track :_tracks){
File::createfile_path(strFileTmp.data(), S_IRWXO | S_IRWXG | S_IRWXU); //添加track
#else _muxer->addTrack(track);
File::createfile_path(strFileTmp.data(), 0);
#endif
_hMp4 = MP4Create(strFileTmp.data());
if (_hMp4 == MP4_INVALID_FILE_HANDLE) {
WarnL << "创建MP4文件失败:" << strFileTmp;
return;
}
//MP4SetTimeScale(_hMp4, 90000);
_strFileTmp = strFileTmp;
_strFile = strFile;
_ticker.resetTime();
auto videoTrack = dynamic_pointer_cast<H264Track>(getTrack(TrackVideo));
if(videoTrack){
auto &sps = videoTrack->getSps();
auto &pps = videoTrack->getPps();
_hVideo = MP4AddH264VideoTrack(_hMp4,
90000,
MP4_INVALID_DURATION,
videoTrack->getVideoWidth(),
videoTrack->getVideoHeight(),
sps[1],
sps[2],
sps[3],
3);
if(_hVideo != MP4_INVALID_TRACK_ID){
MP4AddH264SequenceParameterSet(_hMp4, _hVideo, (uint8_t *)sps.data(), sps.size());
MP4AddH264PictureParameterSet(_hMp4, _hVideo, (uint8_t *)pps.data(), pps.size());
}else{
WarnL << "添加视频通道失败:" << strFileTmp;
}
}
auto audioTrack = dynamic_pointer_cast<AACTrack>(getTrack(TrackAudio));
if(audioTrack){
_audioSampleRate = audioTrack->getAudioSampleRate();
_hAudio = MP4AddAudioTrack(_hMp4, _audioSampleRate, MP4_INVALID_DURATION, MP4_MPEG4_AUDIO_TYPE);
if (_hAudio != MP4_INVALID_TRACK_ID) {
auto &cfg = audioTrack->getAacCfg();
MP4SetTrackESConfiguration(_hMp4, _hAudio,(uint8_t *)cfg.data(), cfg.size());
}else{
WarnL << "添加音频通道失败:" << strFileTmp;
} }
_strFileTmp = strFileTmp;
_strFile = strFile;
_createFileTicker.resetTime();
}catch(std::exception &ex) {
WarnL << ex.what();
} }
} }
void Mp4Maker::asyncClose() { void Mp4Maker::asyncClose() {
auto hMp4 = _hMp4; auto muxer = _muxer;
auto strFileTmp = _strFileTmp; auto strFileTmp = _strFileTmp;
auto strFile = _strFile; auto strFile = _strFile;
auto info = _info; auto info = _info;
WorkThreadPool::Instance().getExecutor()->async([hMp4,strFileTmp,strFile,info]() { WorkThreadPool::Instance().getExecutor()->async([muxer,strFileTmp,strFile,info]() {
//获取文件录制时间,放在MP4Close之前是为了忽略MP4Close执行时间 //获取文件录制时间放在关闭mp4之前是为了忽略关闭mp4执行时间
const_cast<Mp4Info&>(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime; const_cast<Mp4Info&>(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime;
//MP4Close非常耗时,所以要放在后台线程执行 //关闭mp4非常耗时所以要放在后台线程执行
MP4Close(hMp4,MP4_CLOSE_DO_NOT_COMPUTE_BITRATE); const_cast<MP4MuxerFile::Ptr &>(muxer).reset();
//临时文件名改成正式文件名防止mp4未完成时被访问 //临时文件名改成正式文件名防止mp4未完成时被访问
rename(strFileTmp.data(),strFile.data()); rename(strFileTmp.data(),strFile.data());
//获取文件大小 //获取文件大小
@ -235,35 +122,38 @@ void Mp4Maker::asyncClose() {
} }
void Mp4Maker::closeFile() { void Mp4Maker::closeFile() {
if (_hMp4 != MP4_INVALID_FILE_HANDLE) { if (_muxer) {
asyncClose(); asyncClose();
_hMp4 = MP4_INVALID_FILE_HANDLE; _muxer = nullptr;
_hVideo = MP4_INVALID_TRACK_ID;
_hAudio = MP4_INVALID_TRACK_ID;
} }
} }
void Mp4Maker::onTrackFrame(const Frame::Ptr &frame) { void Mp4Maker::onTrackFrame(const Frame::Ptr &frame) {
switch (frame->getCodecId()){ GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);
case CodecH264:{ if(!_muxer || ((_createFileTicker.elapsedTime() > recordSec * 1000) &&
inputH264(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(),frame->stamp()); (!_haveVideo || (_haveVideo && frame->keyFrame()))) ){
} //成立条件
break; //1、_muxer为空
case CodecAAC:{ //2、到了切片时间并且只有音频
inputAAC(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(),frame->stamp()); //3、到了切片时间有视频并且遇到视频的关键帧
} createFile();
break; }
default: if(_muxer){
break; //生成mp4文件
_muxer->inputFrame(frame);
} }
} }
void Mp4Maker::onAllTrackReady() { void Mp4Maker::onTrackReady(const Track::Ptr & track){
_haveVideo = getTrack(TrackVideo).operator bool(); //保存所有的track为创建MP4MuxerFile做准备
_tracks.emplace_back(track);
if(track->getTrackType() == TrackVideo){
_haveVideo = true;
}
} }
} /* namespace mediakit */ } /* namespace mediakit */
#endif //ENABLE_MP4V2 #endif //ENABLE_MP4RECORD

View File

@ -1,4 +1,4 @@
/*  /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
@ -27,19 +27,17 @@
#ifndef MP4MAKER_H_ #ifndef MP4MAKER_H_
#define MP4MAKER_H_ #define MP4MAKER_H_
#ifdef ENABLE_MP4V2 #ifdef ENABLE_MP4RECORD
#include <mutex> #include <mutex>
#include <memory> #include <memory>
#include <mp4v2/mp4v2.h>
#include "Player/PlayerBase.h" #include "Player/PlayerBase.h"
#include "Util/util.h" #include "Util/util.h"
#include "Util/logger.h" #include "Util/logger.h"
#include "Util/TimeTicker.h" #include "Util/TimeTicker.h"
#include "Util/TimeTicker.h" #include "Util/TimeTicker.h"
#include "Common/MediaSink.h" #include "Common/MediaSink.h"
#include "Extension/Track.h" #include "MP4Muxer.h"
using namespace toolkit; using namespace toolkit;
namespace mediakit { namespace mediakit {
@ -72,44 +70,29 @@ private:
*/ */
void onTrackFrame(const Frame::Ptr &frame) override ; void onTrackFrame(const Frame::Ptr &frame) override ;
/** /**
* Track准备好了 * track已经准备好ready()true
*/ * sps pps等相关信息了
void onAllTrackReady() override; * @param track
*/
void onTrackReady(const Track::Ptr & track) override;
private: private:
void createFile(); void createFile();
void closeFile(); void closeFile();
void asyncClose(); void asyncClose();
//时间戳参考频率1000
void inputH264(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp);
//时间戳参考频率1000
void inputAAC(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp);
void inputH264_l(void *pData, uint32_t ui32Length, uint32_t ui64Duration);
void inputAAC_l(void *pData, uint32_t ui32Length, uint32_t ui64Duration);
private: private:
MP4FileHandle _hMp4 = MP4_INVALID_FILE_HANDLE;
MP4TrackId _hVideo = MP4_INVALID_TRACK_ID;
MP4TrackId _hAudio = MP4_INVALID_TRACK_ID;
string _strPath; string _strPath;
string _strFile; string _strFile;
string _strFileTmp; string _strFileTmp;
Ticker _ticker; Ticker _createFileTicker;
string _strLastVideo;
string _strLastAudio;
uint32_t _ui32LastVideoTime = 0;
uint32_t _ui32LastAudioTime = 0;
Mp4Info _info; Mp4Info _info;
bool _haveVideo = false; bool _haveVideo = false;
int _audioSampleRate; MP4MuxerFile::Ptr _muxer;
list<Track::Ptr> _tracks;
}; };
} /* namespace mediakit */ } /* namespace mediakit */
#endif ///ENABLE_MP4V2 #endif ///ENABLE_MP4RECORD
#endif /* MP4MAKER_H_ */ #endif /* MP4MAKER_H_ */

79
src/MediaFile/Stamp.cpp Normal file
View File

@ -0,0 +1,79 @@
/*
* 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 "Stamp.h"
namespace mediakit {
void Stamp::revise(uint32_t dts, uint32_t pts, int64_t &dts_out, int64_t &pts_out) {
if(_first){
//记录第一次时间戳,后面好计算时间戳增量
_start_dts = dts;
_first = false;
_ticker = std::make_shared<SmoothTicker>();
}
//pts和dts的差值
int pts_dts_diff = pts - dts;
if(_modifyStamp){
dts = _ticker->elapsedTime();
}
//相对时间戳
dts_out = dts - _start_dts;
if(dts_out < _dts_inc){
//本次相对时间戳竟然小于上次?
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;
//////////////以下是播放时间戳的计算//////////////////
if(!pts){
//没有播放时间戳
pts = dts;
}
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
pts_out = 0;
}
}
}//namespace mediakit

51
src/MediaFile/Stamp.h Normal file
View File

@ -0,0 +1,51 @@
/*
* 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_STAMP_H
#define ZLMEDIAKIT_STAMP_H
#include "Util/TimeTicker.h"
#include <cstdint>
using namespace toolkit;
namespace mediakit {
class Stamp {
public:
Stamp(bool modifyStamp = false) {_modifyStamp = modifyStamp;};
~Stamp() = default;
void revise(uint32_t dts, uint32_t pts, int64_t &dts_out, int64_t &pts_out);
private:
int64_t _start_dts = 0;
int64_t _dts_inc = 0;
bool _first = true;
bool _modifyStamp;
std::shared_ptr<SmoothTicker> _ticker;
};
}//namespace mediakit
#endif //ZLMEDIAKIT_STAMP_H

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
@ -41,54 +41,76 @@ TsMuxer::~TsMuxer() {
void TsMuxer::addTrack(const Track::Ptr &track) { void TsMuxer::addTrack(const Track::Ptr &track) {
switch (track->getCodecId()){ switch (track->getCodecId()){
case CodecH264: case CodecH264: {
_codecid_to_stream_id[CodecH264] = mpeg_ts_add_stream(_context,PSI_STREAM_H264, nullptr,0); track_info info;
break; info.track_id = mpeg_ts_add_stream(_context, PSI_STREAM_H264, nullptr, 0);
case CodecH265: _codec_to_trackid[track->getCodecId()] = info;
_codecid_to_stream_id[CodecH265] = mpeg_ts_add_stream(_context,PSI_STREAM_H265, nullptr,0); } break;
break; case CodecH265: {
case CodecAAC: track_info info;
_codecid_to_stream_id[CodecAAC] = mpeg_ts_add_stream(_context,PSI_STREAM_AAC, nullptr,0); info.track_id = mpeg_ts_add_stream(_context, PSI_STREAM_H265, nullptr, 0);
break; _codec_to_trackid[track->getCodecId()] = info;
}break;
case CodecAAC: {
track_info info;
info.track_id = mpeg_ts_add_stream(_context, PSI_STREAM_AAC, nullptr, 0);
_codec_to_trackid[track->getCodecId()] = info;
}break;
default: default:
break; break;
} }
} }
void TsMuxer::inputFrame(const Frame::Ptr &frame) { void TsMuxer::inputFrame(const Frame::Ptr &frame) {
auto it = _codecid_to_stream_id.find(frame->getCodecId()); auto it = _codec_to_trackid.find(frame->getCodecId());
if(it == _codecid_to_stream_id.end()){ if(it == _codec_to_trackid.end()){
return; return;
} }
//mp4文件时间戳需要从0开始
auto &track_info = it->second;
int64_t dts_out, pts_out;
switch (frame->getCodecId()){ switch (frame->getCodecId()){
case CodecH265: case CodecH265:
case CodecH264: { case CodecH264: {
//这里的代码逻辑是让SPS、PPS、IDR这些时间戳相同的帧打包到一起当做一个帧处理
if (!_frameCached.empty() && _frameCached.back()->dts() != frame->dts()) { Buffer::Ptr merged_frame ;
Frame::Ptr back = _frameCached.back(); if(frame->configFrame()){
Buffer::Ptr merged_frame = back; //配置帧,缓存后直接返回,以便下次输入关键帧时使用
if(_frameCached.size() != 1){ _config_frame_cache.append("\x00\x00\x00\x01",4);
string merged; _config_frame_cache.append(frame->data() + frame->prefixSize(),frame->size() - frame->prefixSize());
_frameCached.for_each([&](const Frame::Ptr &frame){ break;
if(frame->prefixSize()){
merged.append(frame->data(),frame->size());
} else{
merged.append("\x00\x00\x00\x01",4);
merged.append(frame->data(),frame->size());
}
});
merged_frame = std::make_shared<BufferString>(std::move(merged));
}
_timestamp = back->dts();
mpeg_ts_write(_context, it->second, back->keyFrame() ? 0x0001 : 0, back->pts() * 90LL, back->dts() * 90LL, merged_frame->data(), merged_frame->size());
_frameCached.clear();
} }
_frameCached.emplace_back(Frame::getCacheAbleFrame(frame));
if(frame->keyFrame()){
//关键帧
if(!_config_frame_cache.empty()){
//有配置帧,那么配置帧合并关键帧后输入ts打包
_config_frame_cache.append("\x00\x00\x00\x01",4);
_config_frame_cache.append(frame->data() + frame->prefixSize(),frame->size() - frame->prefixSize());
merged_frame = std::make_shared<BufferString>(std::move(_config_frame_cache));
_config_frame_cache.clear();
}else{
//这是非第一个的关键帧(h265有多种关键帧)
merged_frame = frame;
}
}else{
//这里是普通帧例如B/P
merged_frame = frame;
//sps、pps这些配置帧清空掉
_config_frame_cache.clear();
}
//输入到ts文件
track_info.stamp.revise(frame->dts(),frame->pts(),dts_out,pts_out);
_timestamp = dts_out;
mpeg_ts_write(_context, track_info.track_id, frame->keyFrame() ? 0x0001 : 0, pts_out * 90LL, dts_out * 90LL, merged_frame->data(), merged_frame->size());
} }
break; break;
default: { default: {
_timestamp = frame->dts(); track_info.stamp.revise(frame->dts(),frame->pts(),dts_out,pts_out);
mpeg_ts_write(_context, it->second, frame->keyFrame() ? 0x0001 : 0, frame->pts() * 90LL, frame->dts() * 90LL, frame->data(), frame->size()); _timestamp = dts_out;
mpeg_ts_write(_context, track_info.track_id, frame->keyFrame() ? 0x0001 : 0, pts_out * 90LL, dts_out * 90LL, frame->data(), frame->size());
} }
break; break;
} }
@ -124,7 +146,7 @@ void TsMuxer::uninit() {
mpeg_ts_destroy(_context); mpeg_ts_destroy(_context);
_context = nullptr; _context = nullptr;
} }
_codecid_to_stream_id.clear(); _codec_to_trackid.clear();
} }
}//namespace mediakit }//namespace mediakit

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
@ -31,6 +31,9 @@
#include "Extension/Frame.h" #include "Extension/Frame.h"
#include "Extension/Track.h" #include "Extension/Track.h"
#include "Util/File.h" #include "Util/File.h"
#include "Common/MediaSink.h"
#include "Stamp.h"
using namespace toolkit; using namespace toolkit;
namespace mediakit { namespace mediakit {
@ -39,8 +42,8 @@ class TsMuxer {
public: public:
TsMuxer(); TsMuxer();
virtual ~TsMuxer(); virtual ~TsMuxer();
void addTrack(const Track::Ptr &track); void addTrack(const Track::Ptr &track) ;
void inputFrame(const Frame::Ptr &frame); void inputFrame(const Frame::Ptr &frame) ;
protected: protected:
virtual void onTs(const void *packet, int bytes,uint32_t timestamp,int flags) = 0; virtual void onTs(const void *packet, int bytes,uint32_t timestamp,int flags) = 0;
void resetTracks(); void resetTracks();
@ -51,8 +54,13 @@ private:
void *_context = nullptr; void *_context = nullptr;
char *_tsbuf[188]; char *_tsbuf[188];
uint32_t _timestamp = 0; uint32_t _timestamp = 0;
unordered_map<int,int > _codecid_to_stream_id;
List<Frame::Ptr> _frameCached; struct track_info{
int track_id = -1;
Stamp stamp;
};
unordered_map<int,track_info> _codec_to_trackid;
string _config_frame_cache;
}; };
}//namespace mediakit }//namespace mediakit

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
// //
// Created by xzl on 2019/3/27. // Created by xzl on 2019/3/27.
// //

View File

@ -1,4 +1,4 @@
// //
// Created by xzl on 2019/3/27. // Created by xzl on 2019/3/27.
// //

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -1,4 +1,4 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>

View File

@ -239,8 +239,7 @@ int main(int argc,char *argv[]) {
//这里是拉流地址支持rtmp/rtsp协议负载必须是H264+AAC //这里是拉流地址支持rtmp/rtsp协议负载必须是H264+AAC
//如果是其他不识别的音视频将会被忽略(譬如说h264+adpcm转发后会去除音频) //如果是其他不识别的音视频将会被忽略(譬如说h264+adpcm转发后会去除音频)
auto urlList = {"rtmp://live.hkstv.hk.lxdns.com/live/hks1", auto urlList = {"rtsp://184.72.239.149/vod/mp4:BigBuckBunny_115k.mov"
"rtmp://live.hkstv.hk.lxdns.com/live/hks2"
//rtsp链接支持输入用户名密码 //rtsp链接支持输入用户名密码
/*"rtsp://admin:jzan123456@192.168.0.122/"*/}; /*"rtsp://admin:jzan123456@192.168.0.122/"*/};
map<string, PlayerProxy::Ptr> proxyMap; map<string, PlayerProxy::Ptr> proxyMap;