mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2026-07-07 04:08:13 +08:00
整理文件录制
This commit is contained in:
137
src/Record/HlsMaker.cpp
Normal file
137
src/Record/HlsMaker.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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 "HlsMaker.h"
|
||||
namespace mediakit {
|
||||
|
||||
HlsMaker::HlsMaker(float seg_duration, uint32_t seg_number) {
|
||||
//最小允许设置为0,0个切片代表点播
|
||||
seg_number = MAX(0,seg_number);
|
||||
seg_duration = MAX(1,seg_duration);
|
||||
_seg_number = seg_number;
|
||||
_seg_duration = seg_duration;
|
||||
}
|
||||
|
||||
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) {
|
||||
maxSegmentDuration = dur;
|
||||
}
|
||||
}
|
||||
|
||||
string m3u8;
|
||||
snprintf(file_content,sizeof(file_content),
|
||||
"#EXTM3U\n"
|
||||
"#EXT-X-VERSION:3\n"
|
||||
"#EXT-X-ALLOW-CACHE:NO\n"
|
||||
"#EXT-X-TARGETDURATION:%u\n"
|
||||
"#EXT-X-MEDIA-SEQUENCE:%llu\n",
|
||||
(maxSegmentDuration + 999) / 1000,
|
||||
_seg_number ? _file_index : 0);
|
||||
|
||||
m3u8.assign(file_content);
|
||||
|
||||
for (auto &tp : _seg_dur_list) {
|
||||
snprintf(file_content,sizeof(file_content), "#EXTINF:%.3f,\n%s\n", std::get<0>(tp) / 1000.0, std::get<1>(tp).data());
|
||||
m3u8.append(file_content);
|
||||
}
|
||||
|
||||
if (eof) {
|
||||
snprintf(file_content,sizeof(file_content),"#EXT-X-ENDLIST\n");
|
||||
m3u8.append(file_content);
|
||||
}
|
||||
onWriteHls(m3u8.data(), m3u8.size());
|
||||
}
|
||||
|
||||
|
||||
void HlsMaker::inputData(void *data, uint32_t len, uint32_t timestamp) {
|
||||
//分片数据中断结束
|
||||
if (data && len) {
|
||||
addNewSegment(timestamp);
|
||||
onWriteSegment((char *) data, len);
|
||||
//记录上次写入数据时间
|
||||
_ticker_last_data.resetTime();
|
||||
} else {
|
||||
flushLastSegment(true);
|
||||
}
|
||||
}
|
||||
|
||||
void HlsMaker::delOldSegment() {
|
||||
if(_seg_number == 0){
|
||||
//如果设置为保留0个切片,则认为是保存为点播
|
||||
return;
|
||||
}
|
||||
//在hls m3u8索引文件中,我们保存的切片个数跟_seg_number相关设置一致
|
||||
if (_file_index > _seg_number) {
|
||||
_seg_dur_list.pop_front();
|
||||
}
|
||||
|
||||
GET_CONFIG(uint32_t,segRetain,Hls::kSegmentRetain);
|
||||
//但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕
|
||||
if (_file_index > _seg_number + segRetain) {
|
||||
onDelSegment(_file_index - _seg_number - segRetain - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void HlsMaker::addNewSegment(uint32_t) {
|
||||
if(!_last_file_name.empty() && _ticker.elapsedTime() < _seg_duration * 1000){
|
||||
//存在上个切片,并且未到分片时间
|
||||
return;
|
||||
}
|
||||
|
||||
//关闭并保存上一个切片,如果_seg_number==0,那么是点播。
|
||||
flushLastSegment(_seg_number == 0);
|
||||
//新增切片
|
||||
_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
|
||||
118
src/Record/HlsMaker.h
Normal file
118
src/Record/HlsMaker.h
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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 HLSMAKER_H
|
||||
#define HLSMAKER_H
|
||||
|
||||
#include <deque>
|
||||
#include <tuple>
|
||||
#include "Common/config.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "Util/File.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/logger.h"
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class HlsMaker {
|
||||
public:
|
||||
/**
|
||||
* @param seg_duration 切片文件长度
|
||||
* @param seg_number 切片个数
|
||||
*/
|
||||
HlsMaker(float seg_duration = 5, uint32_t seg_number = 3);
|
||||
virtual ~HlsMaker();
|
||||
|
||||
/**
|
||||
* 写入ts数据
|
||||
* @param data 数据
|
||||
* @param len 数据长度
|
||||
* @param timestamp 毫秒时间戳
|
||||
*/
|
||||
void inputData(void *data, uint32_t len, uint32_t timestamp);
|
||||
protected:
|
||||
/**
|
||||
* 创建ts切片文件回调
|
||||
* @param index
|
||||
* @return
|
||||
*/
|
||||
virtual string onOpenSegment(int index) = 0;
|
||||
|
||||
/**
|
||||
* 删除ts切片文件回调
|
||||
* @param index
|
||||
*/
|
||||
virtual void onDelSegment(int index) = 0;
|
||||
|
||||
/**
|
||||
* 写ts切片文件回调
|
||||
* @param data
|
||||
* @param len
|
||||
*/
|
||||
virtual void onWriteSegment(const char *data, int len) = 0;
|
||||
|
||||
/**
|
||||
* 写m3u8文件回调
|
||||
* @param data
|
||||
* @param len
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 删除旧的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;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //HLSMAKER_H
|
||||
109
src/Record/HlsMakerImp.cpp
Normal file
109
src/Record/HlsMakerImp.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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 "HlsMakerImp.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/uv_errno.h"
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
HlsMakerImp::HlsMakerImp(const string &m3u8_file,
|
||||
const string ¶ms,
|
||||
uint32_t bufSize,
|
||||
float seg_duration,
|
||||
uint32_t seg_number) : HlsMaker(seg_duration, seg_number) {
|
||||
_path_prefix = m3u8_file.substr(0, m3u8_file.rfind('/'));
|
||||
_path_hls = m3u8_file;
|
||||
_params = params;
|
||||
_buf_size = bufSize;
|
||||
_is_vod = seg_number == 0;
|
||||
_file_buf.reset(new char[bufSize],[](char *ptr){
|
||||
delete[] ptr;
|
||||
});
|
||||
}
|
||||
|
||||
HlsMakerImp::~HlsMakerImp() {
|
||||
//录制完了
|
||||
flushLastSegment(true);
|
||||
if(!_is_vod){
|
||||
//hls直播才删除文件
|
||||
File::delete_file(_path_prefix.data());
|
||||
}
|
||||
}
|
||||
|
||||
string HlsMakerImp::onOpenSegment(int index) {
|
||||
auto full_path = fullPath(index);
|
||||
_file = makeFile(full_path, true);
|
||||
if(!_file){
|
||||
WarnL << "create file falied," << full_path << " " << get_uv_errmsg();
|
||||
}
|
||||
//DebugL << index << " " << full_path;
|
||||
if(_params.empty()){
|
||||
return StrPrinter << index << ".ts";
|
||||
}
|
||||
return StrPrinter << index << ".ts" << "?" << _params;
|
||||
}
|
||||
|
||||
void HlsMakerImp::onDelSegment(int index) {
|
||||
//WarnL << index;
|
||||
File::delete_file(fullPath(index).data());
|
||||
}
|
||||
|
||||
void HlsMakerImp::onWriteSegment(const char *data, int len) {
|
||||
if (_file) {
|
||||
fwrite(data, len, 1, _file.get());
|
||||
}
|
||||
}
|
||||
|
||||
void HlsMakerImp::onWriteHls(const char *data, int len) {
|
||||
auto hls = makeFile(_path_hls);
|
||||
if(hls){
|
||||
fwrite(data,len,1,hls.get());
|
||||
hls.reset();
|
||||
} else{
|
||||
WarnL << "create hls file falied," << _path_hls << " " << get_uv_errmsg();
|
||||
}
|
||||
//DebugL << "\r\n" << string(data,len);
|
||||
}
|
||||
|
||||
string HlsMakerImp::fullPath(int index) {
|
||||
return StrPrinter << _path_prefix << "/" << index << ".ts";
|
||||
}
|
||||
|
||||
std::shared_ptr<FILE> HlsMakerImp::makeFile(const string &file,bool setbuf) {
|
||||
auto ret= shared_ptr<FILE>(File::createfile_file(file.data(), "wb"), [](FILE *fp) {
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
});
|
||||
if(ret && setbuf){
|
||||
setvbuf(ret.get(), _file_buf.get(), _IOFBF, _buf_size);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
66
src/Record/HlsMakerImp.h
Normal file
66
src/Record/HlsMakerImp.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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 HLSMAKERIMP_H
|
||||
#define HLSMAKERIMP_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <stdlib.h>
|
||||
#include "HlsMaker.h"
|
||||
using namespace std;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class HlsMakerImp : public HlsMaker{
|
||||
public:
|
||||
HlsMakerImp(const string &m3u8_file,
|
||||
const string ¶ms,
|
||||
uint32_t bufSize = 64 * 1024,
|
||||
float seg_duration = 5,
|
||||
uint32_t seg_number = 3);
|
||||
virtual ~HlsMakerImp();
|
||||
protected:
|
||||
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);
|
||||
std::shared_ptr<FILE> makeFile(const string &file,bool setbuf = false);
|
||||
private:
|
||||
std::shared_ptr<FILE> _file;
|
||||
std::shared_ptr<char> _file_buf;
|
||||
string _path_prefix;
|
||||
string _path_hls;
|
||||
string _params;
|
||||
int _buf_size;
|
||||
//是否为点播
|
||||
bool _is_vod;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //HLSMAKERIMP_H
|
||||
56
src/Record/HlsRecorder.h
Normal file
56
src/Record/HlsRecorder.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 HLSRECORDER_H
|
||||
#define HLSRECORDER_H
|
||||
|
||||
#include "HlsMakerImp.h"
|
||||
#include "TsMuxer.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class HlsRecorder : public TsMuxer {
|
||||
public:
|
||||
HlsRecorder(const string &m3u8_file, const string ¶ms){
|
||||
GET_CONFIG(uint32_t,hlsNum,Hls::kSegmentNum);
|
||||
GET_CONFIG(uint32_t,hlsBufSize,Hls::kFileBufSize);
|
||||
GET_CONFIG(uint32_t,hlsDuration,Hls::kSegmentDuration);
|
||||
_hls = new HlsMakerImp(m3u8_file,params,hlsBufSize,hlsDuration,hlsNum);
|
||||
}
|
||||
~HlsRecorder(){
|
||||
delete _hls;
|
||||
}
|
||||
protected:
|
||||
void onTs(const void *packet, int bytes,uint32_t timestamp,int flags) override {
|
||||
_hls->inputData((char *)packet,bytes,timestamp);
|
||||
};
|
||||
private:
|
||||
HlsMakerImp *_hls;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#endif //HLSRECORDER_H
|
||||
283
src/Record/MP4Muxer.cpp
Normal file
283
src/Record/MP4Muxer.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* 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::resetTracks() {
|
||||
_codec_to_trackid.clear();
|
||||
_started = false;
|
||||
}
|
||||
|
||||
void MP4Muxer::inputFrame(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::addTrack(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;
|
||||
}
|
||||
_codec_to_trackid[track->getCodecId()].track_id = track_id;
|
||||
}
|
||||
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;
|
||||
}
|
||||
_codec_to_trackid[track->getCodecId()].track_id = track_id;
|
||||
}
|
||||
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;
|
||||
}
|
||||
_codec_to_trackid[track->getCodecId()].track_id = track_id;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
WarnL << "MP4录制不支持该编码格式:" << track->getCodecId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MP4MuxerFile::MP4MuxerFile(const char *file){
|
||||
_file_name = file;
|
||||
openFile(file);
|
||||
}
|
||||
|
||||
void MP4MuxerFile::openFile(const char *file) {
|
||||
//创建文件
|
||||
auto fp = File::createfile_file(file,"wb+");
|
||||
if(!fp){
|
||||
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);
|
||||
});
|
||||
|
||||
GET_CONFIG(bool, mp4FastStart, Record::kFastStart);
|
||||
init(mp4FastStart ? MOV_FLAG_FASTSTART : 0);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
int MP4MuxerFile::onSeek(uint64_t offset) {
|
||||
return fseek64(_file.get(), offset, SEEK_SET);
|
||||
}
|
||||
|
||||
uint64_t MP4MuxerFile::onTell() {
|
||||
return ftell64(_file.get());
|
||||
}
|
||||
|
||||
|
||||
void MP4MuxerFile::resetTracks(){
|
||||
MP4Muxer::resetTracks();
|
||||
openFile(_file_name.data());
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#endif//#ifdef ENABLE_MP4RECORD
|
||||
109
src/Record/MP4Muxer.h
Normal file
109
src/Record/MP4Muxer.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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 "Common/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 MediaSinkInterface , public MP4MuxerBase{
|
||||
public:
|
||||
MP4Muxer() = default;
|
||||
~MP4Muxer() override = default;
|
||||
|
||||
/**
|
||||
* 添加已经ready状态的track
|
||||
*/
|
||||
void addTrack(const Track::Ptr & track) override;
|
||||
/**
|
||||
* 输入帧
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
/**
|
||||
* 重置所有track
|
||||
*/
|
||||
void resetTracks() override ;
|
||||
private:
|
||||
struct track_info{
|
||||
int track_id = -1;
|
||||
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();
|
||||
void resetTracks() override ;
|
||||
protected:
|
||||
int onRead(void* data, uint64_t bytes) override;
|
||||
int onWrite(const void* data, uint64_t bytes) override;
|
||||
int onSeek( uint64_t offset) override;
|
||||
uint64_t onTell() override ;
|
||||
void openFile(const char *file);
|
||||
private:
|
||||
std::shared_ptr<FILE> _file;
|
||||
string _file_name;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#endif//#ifdef ENABLE_MP4RECORD
|
||||
|
||||
#endif //ZLMEDIAKIT_MP4MUXER_H
|
||||
361
src/Record/MP4Reader.cpp
Normal file
361
src/Record/MP4Reader.cpp
Normal file
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* 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 "MP4Reader.h"
|
||||
#include "Common/config.h"
|
||||
#include "Util/mini.h"
|
||||
#include "Util/File.h"
|
||||
#include "Http/HttpSession.h"
|
||||
#include "Extension/AAC.h"
|
||||
#include "Extension/H264.h"
|
||||
#include "Thread/WorkThreadPool.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
#ifdef ENABLE_MP4V2
|
||||
MP4Reader::MP4Reader(const string &strVhost,const string &strApp, const string &strId,const string &filePath ) {
|
||||
_poller = WorkThreadPool::Instance().getPoller();
|
||||
auto strFileName = filePath;
|
||||
if(strFileName.empty()){
|
||||
GET_CONFIG(string,recordPath,Record::kFilePath);
|
||||
GET_CONFIG(bool,enableVhost,General::kEnableVhost);
|
||||
if(enableVhost){
|
||||
strFileName = strVhost + "/" + strApp + "/" + strId;
|
||||
}else{
|
||||
strFileName = strApp + "/" + strId;
|
||||
}
|
||||
strFileName = File::absolutePath(strFileName,recordPath);
|
||||
}
|
||||
|
||||
_hMP4File = MP4Read(strFileName.data());
|
||||
if(_hMP4File == MP4_INVALID_FILE_HANDLE){
|
||||
throw runtime_error(StrPrinter << "打开MP4文件失败:" << strFileName << endl);
|
||||
}
|
||||
_video_trId = MP4FindTrackId(_hMP4File, 0, MP4_VIDEO_TRACK_TYPE, 0);
|
||||
if(_video_trId != MP4_INVALID_TRACK_ID){
|
||||
if(strcmp(MP4GetTrackMediaDataName(_hMP4File, _video_trId),"avc1") ==0){
|
||||
auto _video_timescale = MP4GetTrackTimeScale(_hMP4File, _video_trId);
|
||||
auto _video_duration = MP4GetTrackDuration(_hMP4File, _video_trId);
|
||||
_video_num_samples = MP4GetTrackNumberOfSamples(_hMP4File, _video_trId);
|
||||
_video_sample_max_size = MP4GetTrackMaxSampleSize(_hMP4File, _video_trId);
|
||||
_video_width = MP4GetTrackVideoWidth(_hMP4File, _video_trId);
|
||||
_video_height = MP4GetTrackVideoHeight(_hMP4File, _video_trId);
|
||||
_video_framerate = MP4GetTrackVideoFrameRate(_hMP4File, _video_trId);
|
||||
_pcVideoSample = std::shared_ptr<uint8_t> (new uint8_t[_video_sample_max_size],[](uint8_t *ptr){
|
||||
delete [] ptr;
|
||||
});
|
||||
uint8_t **seqheader;
|
||||
uint8_t **pictheader;
|
||||
uint32_t *pictheadersize;
|
||||
uint32_t *seqheadersize;
|
||||
uint32_t ix;
|
||||
if(MP4GetTrackH264SeqPictHeaders(_hMP4File, _video_trId, &seqheader, &seqheadersize, &pictheader, &pictheadersize)){
|
||||
for (ix = 0; seqheadersize[ix] != 0; ix++) {
|
||||
_strSps.assign((char *)(seqheader[ix]), seqheadersize[ix]);
|
||||
float framerate;
|
||||
getAVCInfo(_strSps, (int &)_video_width, (int &)_video_height, framerate);
|
||||
_video_framerate = framerate;
|
||||
_strSps = string("\x0\x0\x0\x1",4) + _strSps;
|
||||
MP4Free(seqheader[ix]);
|
||||
}
|
||||
MP4Free(seqheader);
|
||||
MP4Free(seqheadersize);
|
||||
for (ix = 0; pictheadersize[ix] != 0; ix++) {
|
||||
_strPps.assign("\x0\x0\x0\x1",4);
|
||||
_strPps.append((char *)(pictheader[ix]), pictheadersize[ix]);
|
||||
MP4Free(pictheader[ix]);
|
||||
}
|
||||
MP4Free(pictheader);
|
||||
MP4Free(pictheadersize);
|
||||
}
|
||||
_video_ms = 1000.0 * _video_duration / _video_timescale;
|
||||
/*InfoL << "\r\n"
|
||||
<< _video_ms << "\r\n"
|
||||
<< _video_num_samples << "\r\n"
|
||||
<< _video_framerate << "\r\n"
|
||||
<< _video_width << "\r\n"
|
||||
<< _video_height << "\r\n";*/
|
||||
} else {
|
||||
//如果不是h264,则忽略
|
||||
_video_trId = MP4_INVALID_TRACK_ID;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_audio_trId = MP4FindTrackId(_hMP4File, 0, MP4_AUDIO_TRACK_TYPE, 0);
|
||||
if (_audio_trId != MP4_INVALID_TRACK_ID) {
|
||||
if (strcmp(MP4GetTrackMediaDataName(_hMP4File, _audio_trId), "mp4a") == 0) {
|
||||
_audio_sample_rate = MP4GetTrackTimeScale(_hMP4File, _audio_trId);
|
||||
auto _audio_duration = MP4GetTrackDuration(_hMP4File, _audio_trId);
|
||||
_audio_num_samples = MP4GetTrackNumberOfSamples(_hMP4File,_audio_trId);
|
||||
_audio_num_channels = MP4GetTrackAudioChannels(_hMP4File, _audio_trId);
|
||||
_audio_sample_max_size = MP4GetTrackMaxSampleSize(_hMP4File,_audio_trId);
|
||||
uint8_t *ppConfig;
|
||||
uint32_t pConfigSize;
|
||||
if(MP4GetTrackESConfiguration(_hMP4File,_audio_trId,&ppConfig,&pConfigSize)){
|
||||
_strAacCfg.assign((char *)ppConfig, pConfigSize);
|
||||
makeAdtsHeader(_strAacCfg, _adts);
|
||||
writeAdtsHeader(_adts,_adts.buffer);
|
||||
getAACInfo(_adts, (int &)_audio_sample_rate, (int &)_audio_num_channels);
|
||||
MP4Free(ppConfig);
|
||||
}
|
||||
_audio_ms = 1000.0 * _audio_duration / _audio_sample_rate;
|
||||
/*InfoL << "\r\n"
|
||||
<< _audio_ms << "\r\n"
|
||||
<< _audio_num_samples << "\r\n"
|
||||
<< _audio_num_channels << "\r\n"
|
||||
<< _audio_sample_rate << "\r\n";*/
|
||||
}else{
|
||||
_audio_trId = MP4_INVALID_TRACK_ID;
|
||||
}
|
||||
}
|
||||
if(_audio_trId == MP4_INVALID_TRACK_ID && _video_trId == MP4_INVALID_TRACK_ID){
|
||||
MP4Close(_hMP4File);
|
||||
_hMP4File = MP4_INVALID_FILE_HANDLE;
|
||||
throw runtime_error(StrPrinter << "该MP4文件音视频格式不支持:" << strFileName << endl);
|
||||
}
|
||||
|
||||
_iDuration = MAX(_video_ms,_audio_ms);
|
||||
_mediaMuxer.reset(new MultiMediaSourceMuxer(strVhost, strApp, strId, _iDuration / 1000.0, true, true, false, false));
|
||||
if (_audio_trId != MP4_INVALID_TRACK_ID) {
|
||||
AACTrack::Ptr track = std::make_shared<AACTrack>(_strAacCfg);
|
||||
_mediaMuxer->addTrack(track);
|
||||
}
|
||||
|
||||
if (_video_trId != MP4_INVALID_TRACK_ID) {
|
||||
H264Track::Ptr track = std::make_shared<H264Track>(_strSps,_strPps);
|
||||
_mediaMuxer->addTrack(track);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MP4Reader::~MP4Reader() {
|
||||
if (_hMP4File != MP4_INVALID_FILE_HANDLE) {
|
||||
MP4Close(_hMP4File);
|
||||
_hMP4File = MP4_INVALID_FILE_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MP4Reader::startReadMP4() {
|
||||
auto strongSelf = shared_from_this();
|
||||
GET_CONFIG(uint32_t,sampleMS,Record::kSampleMS);
|
||||
|
||||
_timer = std::make_shared<Timer>(sampleMS / 1000.0f,[strongSelf](){
|
||||
return strongSelf->readSample(0,false);
|
||||
}, _poller);
|
||||
|
||||
//先读sampleMS毫秒的数据用于产生MediaSouce
|
||||
readSample(sampleMS, false);
|
||||
_mediaMuxer->setListener(strongSelf);
|
||||
}
|
||||
bool MP4Reader::seekTo(MediaSource &sender,uint32_t ui32Stamp){
|
||||
seek(ui32Stamp);
|
||||
return true;
|
||||
}
|
||||
bool MP4Reader::close(MediaSource &sender,bool force){
|
||||
if(!_mediaMuxer || (!force && _mediaMuxer->readerCount() != 0)){
|
||||
return false;
|
||||
}
|
||||
_timer.reset();
|
||||
WarnL << sender.getSchema() << "/" << sender.getVhost() << "/" << sender.getApp() << "/" << sender.getId() << " " << force;
|
||||
return true;
|
||||
}
|
||||
|
||||
void MP4Reader::onNoneReader(MediaSource &sender) {
|
||||
if(!_mediaMuxer || _mediaMuxer->readerCount() != 0){
|
||||
return;
|
||||
}
|
||||
MediaSourceEvent::onNoneReader(sender);
|
||||
}
|
||||
|
||||
bool MP4Reader::readSample(int iTimeInc,bool justSeekSyncFrame) {
|
||||
TimeTicker();
|
||||
lock_guard<recursive_mutex> lck(_mtx);
|
||||
auto bFlag0 = readVideoSample(iTimeInc,justSeekSyncFrame);//数据没读完
|
||||
auto bFlag1 = readAudioSample(iTimeInc,justSeekSyncFrame);//数据没读完
|
||||
auto bFlag2 = _mediaMuxer->readerCount() > 0;//读取者大于0
|
||||
if((bFlag0 || bFlag1) && bFlag2){
|
||||
_alive.resetTime();
|
||||
}
|
||||
//重头开始循环读取
|
||||
GET_CONFIG(bool,fileRepeat,Record::kFileRepeat);
|
||||
if (fileRepeat && !bFlag0 && !bFlag1) {
|
||||
seek(0);
|
||||
}
|
||||
//DebugL << "alive ...";
|
||||
//3秒延时关闭
|
||||
return _alive.elapsedTime() < 3 * 1000;
|
||||
}
|
||||
inline bool MP4Reader::readVideoSample(int iTimeInc,bool justSeekSyncFrame) {
|
||||
if (_video_trId != MP4_INVALID_TRACK_ID) {
|
||||
auto iNextSample = getVideoSampleId(iTimeInc);
|
||||
MP4SampleId iIdx = _video_current;
|
||||
for (; iIdx < iNextSample; iIdx++) {
|
||||
uint8_t *pBytes = _pcVideoSample.get();
|
||||
uint32_t numBytes = _video_sample_max_size;
|
||||
if(MP4ReadSample(_hMP4File, _video_trId, iIdx + 1, &pBytes, &numBytes,NULL,NULL,NULL,&_bSyncSample)){
|
||||
if (!justSeekSyncFrame) {
|
||||
uint32_t iOffset = 0;
|
||||
while (iOffset < numBytes) {
|
||||
uint32_t iFrameLen;
|
||||
memcpy(&iFrameLen,pBytes + iOffset,4);
|
||||
iFrameLen = ntohl(iFrameLen);
|
||||
if(iFrameLen + iOffset + 4> numBytes){
|
||||
break;
|
||||
}
|
||||
memcpy(pBytes + iOffset, "\x0\x0\x0\x1", 4);
|
||||
writeH264(pBytes + iOffset, iFrameLen + 4, (double) _video_ms * iIdx / _video_num_samples, 0);
|
||||
iOffset += (iFrameLen + 4);
|
||||
}
|
||||
}else if(_bSyncSample){
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
ErrorL << "读取视频失败:" << iIdx + 1;
|
||||
}
|
||||
}
|
||||
_video_current = iIdx;
|
||||
return _video_current < _video_num_samples;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool MP4Reader::readAudioSample(int iTimeInc,bool justSeekSyncFrame) {
|
||||
if (_audio_trId != MP4_INVALID_TRACK_ID) {
|
||||
auto iNextSample = getAudioSampleId(iTimeInc);
|
||||
for (auto i = _audio_current; i < iNextSample; i++) {
|
||||
uint32_t numBytes = _audio_sample_max_size;
|
||||
uint8_t *pBytes = _adts.buffer + 7;
|
||||
if(MP4ReadSample(_hMP4File, _audio_trId, i + 1, &pBytes, &numBytes)){
|
||||
if (!justSeekSyncFrame) {
|
||||
_adts.aac_frame_length = 7 + numBytes;
|
||||
writeAdtsHeader(_adts, _adts.buffer);
|
||||
writeAAC(_adts.buffer, _adts.aac_frame_length, (double) _audio_ms * i / _audio_num_samples);
|
||||
}
|
||||
}else{
|
||||
ErrorL << "读取音频失败:" << i+ 1;
|
||||
}
|
||||
}
|
||||
_audio_current = iNextSample;
|
||||
return _audio_current < _audio_num_samples;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void MP4Reader::writeH264(uint8_t *pucData,int iLen,uint32_t dts,uint32_t pts) {
|
||||
_mediaMuxer->inputFrame(std::make_shared<H264FrameNoCacheAble>((char*)pucData,iLen,dts,pts));
|
||||
}
|
||||
|
||||
inline void MP4Reader::writeAAC(uint8_t *pucData,int iLen,uint32_t uiStamp) {
|
||||
_mediaMuxer->inputFrame(std::make_shared<AACFrameNoCacheAble>((char*)pucData,iLen,uiStamp));
|
||||
}
|
||||
|
||||
inline MP4SampleId MP4Reader::getVideoSampleId(int iTimeInc ) {
|
||||
MP4SampleId video_current = (double)_video_num_samples * (_iSeekTime + _ticker.elapsedTime() + iTimeInc) / _video_ms;
|
||||
video_current = MAX(0,MIN(_video_num_samples, video_current));
|
||||
return video_current;
|
||||
|
||||
}
|
||||
|
||||
inline MP4SampleId MP4Reader::getAudioSampleId(int iTimeInc) {
|
||||
MP4SampleId audio_current = (double)_audio_num_samples * (_iSeekTime + _ticker.elapsedTime() + iTimeInc) / _audio_ms ;
|
||||
audio_current = MAX(0,MIN(_audio_num_samples,audio_current));
|
||||
return audio_current;
|
||||
}
|
||||
inline void MP4Reader::setSeekTime(uint32_t iSeekTime){
|
||||
_iSeekTime = MAX(0, MIN(iSeekTime,_iDuration));
|
||||
_ticker.resetTime();
|
||||
if (_audio_trId != MP4_INVALID_TRACK_ID) {
|
||||
_audio_current = getAudioSampleId();
|
||||
}
|
||||
if (_video_trId != MP4_INVALID_TRACK_ID) {
|
||||
_video_current = getVideoSampleId();
|
||||
}
|
||||
}
|
||||
|
||||
inline uint32_t MP4Reader::getVideoCurrentTime(){
|
||||
return (double)_video_current * _video_ms /_video_num_samples;
|
||||
}
|
||||
void MP4Reader::seek(uint32_t iSeekTime,bool bReStart){
|
||||
lock_guard<recursive_mutex> lck(_mtx);
|
||||
if(iSeekTime == 0 || _video_trId == MP4_INVALID_TRACK_ID){
|
||||
setSeekTime(iSeekTime);
|
||||
}else{
|
||||
setSeekTime(iSeekTime - 5000);
|
||||
//在之后的10秒查找关键帧
|
||||
readVideoSample(10000, true);
|
||||
if (_bSyncSample) {
|
||||
//找到关键帧
|
||||
auto iIdr = _video_current;
|
||||
setSeekTime(getVideoCurrentTime());
|
||||
_video_current = iIdr;
|
||||
}else{
|
||||
//未找到关键帧
|
||||
setSeekTime(iSeekTime);
|
||||
}
|
||||
}
|
||||
_mediaMuxer->setTimeStamp(_iSeekTime);
|
||||
|
||||
if(bReStart){
|
||||
_timer.reset();
|
||||
startReadMP4();
|
||||
}
|
||||
}
|
||||
|
||||
#endif //ENABLE_MP4V2
|
||||
|
||||
|
||||
|
||||
MediaSource::Ptr MP4Reader::onMakeMediaSource(const string &strSchema,
|
||||
const string &strVhost,
|
||||
const string &strApp,
|
||||
const string &strId,
|
||||
const string &filePath,
|
||||
bool checkApp ){
|
||||
#ifdef ENABLE_MP4V2
|
||||
GET_CONFIG(string,appName,Record::kAppName);
|
||||
if (checkApp && strApp != appName) {
|
||||
return nullptr;
|
||||
}
|
||||
try {
|
||||
MP4Reader::Ptr pReader(new MP4Reader(strVhost,strApp, strId,filePath));
|
||||
pReader->startReadMP4();
|
||||
return MediaSource::find(strSchema,strVhost,strApp, strId, false);
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
return nullptr;
|
||||
#endif //ENABLE_MP4V2
|
||||
}
|
||||
|
||||
|
||||
|
||||
} /* namespace mediakit */
|
||||
141
src/Record/MP4Reader.h
Normal file
141
src/Record/MP4Reader.h
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef SRC_MEDIAFILE_MEDIAREADER_H_
|
||||
#define SRC_MEDIAFILE_MEDIAREADER_H_
|
||||
|
||||
#include "Common/MultiMediaSourceMuxer.h"
|
||||
#include "Extension/AAC.h"
|
||||
|
||||
#ifdef ENABLE_MP4V2
|
||||
#include <mp4v2/mp4v2.h>
|
||||
#endif //ENABLE_MP4V2
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class MP4Reader : public std::enable_shared_from_this<MP4Reader> ,public MediaSourceEvent{
|
||||
public:
|
||||
typedef std::shared_ptr<MP4Reader> Ptr;
|
||||
virtual ~MP4Reader();
|
||||
|
||||
/**
|
||||
* 流化一个mp4文件,使之转换成RtspMediaSource和RtmpMediaSource
|
||||
* @param strVhost 虚拟主机
|
||||
* @param strApp 应用名
|
||||
* @param strId 流id
|
||||
* @param filePath 文件路径,如果为空则根据配置文件和上面参数自动生成,否则使用指定的文件
|
||||
*/
|
||||
MP4Reader(const string &strVhost,const string &strApp, const string &strId,const string &filePath = "");
|
||||
/**
|
||||
* 开始流化MP4文件,需要指出的是,MP4Reader对象一经过调用startReadMP4方法,它的强引用会自持有,
|
||||
* 意思是在文件流化结束之前或中断之前,MP4Reader对象是不会被销毁的(不管有没有被外部对象持有)
|
||||
*/
|
||||
void startReadMP4();
|
||||
|
||||
/**
|
||||
* 设置时移偏移量
|
||||
* @param ui32Stamp 偏移量,单位毫秒
|
||||
* @return
|
||||
*/
|
||||
bool seekTo(MediaSource &sender,uint32_t ui32Stamp) override;
|
||||
|
||||
/**
|
||||
* 关闭MP4Reader的流化进程,会触发该对象放弃自持有
|
||||
* @return
|
||||
*/
|
||||
bool close(MediaSource &sender,bool force) override;
|
||||
|
||||
/**
|
||||
* 自动生成MP4Reader对象然后查找相关的MediaSource对象
|
||||
* @param strSchema 协议名
|
||||
* @param strVhost 虚拟主机
|
||||
* @param strApp 应用名
|
||||
* @param strId 流id
|
||||
* @param filePath 文件路径,如果为空则根据配置文件和上面参数自动生成,否则使用指定的文件
|
||||
* @param checkApp 是否检查app,防止服务器上文件被乱访问
|
||||
* @return MediaSource
|
||||
*/
|
||||
static MediaSource::Ptr onMakeMediaSource(const string &strSchema,
|
||||
const string &strVhost,
|
||||
const string &strApp,
|
||||
const string &strId,
|
||||
const string &filePath = "",
|
||||
bool checkApp = true);
|
||||
|
||||
private:
|
||||
void onNoneReader(MediaSource &sender) override;
|
||||
#ifdef ENABLE_MP4V2
|
||||
void seek(uint32_t iSeekTime,bool bReStart = true);
|
||||
inline void setSeekTime(uint32_t iSeekTime);
|
||||
inline uint32_t getVideoCurrentTime();
|
||||
inline MP4SampleId getVideoSampleId(int iTimeInc = 0);
|
||||
inline MP4SampleId getAudioSampleId(int iTimeInc = 0);
|
||||
bool readSample(int iTimeInc, bool justSeekSyncFrame);
|
||||
inline bool readVideoSample(int iTimeInc,bool justSeekSyncFrame);
|
||||
inline bool readAudioSample(int iTimeInc,bool justSeekSyncFrame);
|
||||
inline void writeH264(uint8_t *pucData,int iLen,uint32_t dts,uint32_t pts);
|
||||
inline void writeAAC(uint8_t *pucData,int iLen,uint32_t uiStamp);
|
||||
private:
|
||||
MP4FileHandle _hMP4File = MP4_INVALID_FILE_HANDLE;
|
||||
MP4TrackId _video_trId = MP4_INVALID_TRACK_ID;
|
||||
uint32_t _video_ms = 0;
|
||||
uint32_t _video_num_samples = 0;
|
||||
uint32_t _video_sample_max_size = 0;
|
||||
uint32_t _video_width = 0;
|
||||
uint32_t _video_height = 0;
|
||||
uint32_t _video_framerate = 0;
|
||||
string _strPps;
|
||||
string _strSps;
|
||||
bool _bSyncSample = false;
|
||||
|
||||
MP4TrackId _audio_trId = MP4_INVALID_TRACK_ID;
|
||||
uint32_t _audio_ms = 0;
|
||||
uint32_t _audio_num_samples = 0;
|
||||
uint32_t _audio_sample_max_size = 0;
|
||||
uint32_t _audio_sample_rate = 0;
|
||||
uint32_t _audio_num_channels = 0;
|
||||
string _strAacCfg;
|
||||
AACFrame _adts;
|
||||
|
||||
int _iDuration = 0;
|
||||
MultiMediaSourceMuxer::Ptr _mediaMuxer;
|
||||
MP4SampleId _video_current = 0;
|
||||
MP4SampleId _audio_current = 0;
|
||||
std::shared_ptr<uint8_t> _pcVideoSample;
|
||||
|
||||
int _iSeekTime = 0 ;
|
||||
Ticker _ticker;
|
||||
Ticker _alive;
|
||||
recursive_mutex _mtx;
|
||||
Timer::Ptr _timer;
|
||||
EventPoller::Ptr _poller;
|
||||
#endif //ENABLE_MP4V2
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
#endif /* SRC_MEDIAFILE_MEDIAREADER_H_ */
|
||||
166
src/Record/MP4Recorder.cpp
Normal file
166
src/Record/MP4Recorder.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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 <ctime>
|
||||
#include <sys/stat.h>
|
||||
#include "Common/config.h"
|
||||
#include "MP4Recorder.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/NoticeCenter.h"
|
||||
#include "Thread/WorkThreadPool.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
string timeStr(const char *fmt) {
|
||||
std::tm tm_snapshot;
|
||||
auto time = ::time(NULL);
|
||||
#if defined(_WIN32)
|
||||
localtime_s(&tm_snapshot, &time); // thread-safe
|
||||
#else
|
||||
localtime_r(&time, &tm_snapshot); // POSIX
|
||||
#endif
|
||||
const size_t size = 1024;
|
||||
char buffer[size];
|
||||
auto success = std::strftime(buffer, size, fmt, &tm_snapshot);
|
||||
if (0 == success)
|
||||
return string(fmt);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
MP4Recorder::MP4Recorder(const string& strPath,
|
||||
const string &strVhost,
|
||||
const string &strApp,
|
||||
const string &strStreamId) {
|
||||
_strPath = strPath;
|
||||
/////record 业务逻辑//////
|
||||
_info.strAppName = strApp;
|
||||
_info.strStreamId = strStreamId;
|
||||
_info.strVhost = strVhost;
|
||||
_info.strFolder = strPath;
|
||||
}
|
||||
MP4Recorder::~MP4Recorder() {
|
||||
closeFile();
|
||||
}
|
||||
|
||||
void MP4Recorder::createFile() {
|
||||
closeFile();
|
||||
auto strDate = timeStr("%Y-%m-%d");
|
||||
auto strTime = timeStr("%H-%M-%S");
|
||||
auto strFileTmp = _strPath + strDate + "/." + strTime + ".mp4";
|
||||
auto strFile = _strPath + strDate + "/" + strTime + ".mp4";
|
||||
|
||||
/////record 业务逻辑//////
|
||||
_info.ui64StartedTime = ::time(NULL);
|
||||
_info.strFileName = strTime + ".mp4";
|
||||
_info.strFilePath = strFile;
|
||||
GET_CONFIG(string,appName,Record::kAppName);
|
||||
_info.strUrl = appName + "/"
|
||||
+ _info.strAppName + "/"
|
||||
+ _info.strStreamId + "/"
|
||||
+ strDate + "/"
|
||||
+ strTime + ".mp4";
|
||||
|
||||
try {
|
||||
_muxer = std::make_shared<MP4MuxerFile>(strFileTmp.data());
|
||||
for(auto &track :_tracks){
|
||||
//添加track
|
||||
_muxer->addTrack(track);
|
||||
}
|
||||
_strFileTmp = strFileTmp;
|
||||
_strFile = strFile;
|
||||
_createFileTicker.resetTime();
|
||||
}catch(std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
void MP4Recorder::asyncClose() {
|
||||
auto muxer = _muxer;
|
||||
auto strFileTmp = _strFileTmp;
|
||||
auto strFile = _strFile;
|
||||
auto info = _info;
|
||||
WorkThreadPool::Instance().getExecutor()->async([muxer,strFileTmp,strFile,info]() {
|
||||
//获取文件录制时间,放在关闭mp4之前是为了忽略关闭mp4执行时间
|
||||
const_cast<Mp4Info&>(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime;
|
||||
//关闭mp4非常耗时,所以要放在后台线程执行
|
||||
const_cast<MP4MuxerFile::Ptr &>(muxer).reset();
|
||||
//临时文件名改成正式文件名,防止mp4未完成时被访问
|
||||
rename(strFileTmp.data(),strFile.data());
|
||||
//获取文件大小
|
||||
struct stat fileData;
|
||||
stat(strFile.data(), &fileData);
|
||||
const_cast<Mp4Info&>(info).ui64FileSize = fileData.st_size;
|
||||
/////record 业务逻辑//////
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordMP4,info);
|
||||
});
|
||||
}
|
||||
|
||||
void MP4Recorder::closeFile() {
|
||||
if (_muxer) {
|
||||
asyncClose();
|
||||
_muxer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MP4Recorder::inputFrame(const Frame::Ptr &frame) {
|
||||
GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);
|
||||
if(!_muxer || ((_createFileTicker.elapsedTime() > recordSec * 1000) &&
|
||||
(!_haveVideo || (_haveVideo && frame->keyFrame()))) ){
|
||||
//成立条件
|
||||
//1、_muxer为空
|
||||
//2、到了切片时间,并且只有音频
|
||||
//3、到了切片时间,有视频并且遇到视频的关键帧
|
||||
createFile();
|
||||
}
|
||||
|
||||
if(_muxer){
|
||||
//生成mp4文件
|
||||
_muxer->inputFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
void MP4Recorder::addTrack(const Track::Ptr & track){
|
||||
//保存所有的track,为创建MP4MuxerFile做准备
|
||||
_tracks.emplace_back(track);
|
||||
if(track->getTrackType() == TrackVideo){
|
||||
_haveVideo = true;
|
||||
}
|
||||
}
|
||||
|
||||
void MP4Recorder::resetTracks() {
|
||||
closeFile();
|
||||
_tracks.clear();
|
||||
_haveVideo = false;
|
||||
_createFileTicker.resetTime();
|
||||
}
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
|
||||
#endif //ENABLE_MP4RECORD
|
||||
100
src/Record/MP4Recorder.h
Normal file
100
src/Record/MP4Recorder.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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 MP4MAKER_H_
|
||||
#define MP4MAKER_H_
|
||||
|
||||
#ifdef ENABLE_MP4RECORD
|
||||
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include "Player/PlayerBase.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "Common/MediaSink.h"
|
||||
#include "MP4Muxer.h"
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class Mp4Info {
|
||||
public:
|
||||
time_t ui64StartedTime; //GMT标准时间,单位秒
|
||||
time_t ui64TimeLen;//录像长度,单位秒
|
||||
off_t ui64FileSize;//文件大小,单位BYTE
|
||||
string strFilePath;//文件路径
|
||||
string strFileName;//文件名称
|
||||
string strFolder;//文件夹路径
|
||||
string strUrl;//播放路径
|
||||
string strAppName;//应用名称
|
||||
string strStreamId;//流ID
|
||||
string strVhost;//vhost
|
||||
};
|
||||
class MP4Recorder : public MediaSinkInterface{
|
||||
public:
|
||||
typedef std::shared_ptr<MP4Recorder> Ptr;
|
||||
MP4Recorder(const string &strPath,
|
||||
const string &strVhost ,
|
||||
const string &strApp,
|
||||
const string &strStreamId);
|
||||
virtual ~MP4Recorder();
|
||||
|
||||
/**
|
||||
* 重置所有Track
|
||||
*/
|
||||
void resetTracks() override;
|
||||
|
||||
/**
|
||||
* 输入frame
|
||||
*/
|
||||
void inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
/**
|
||||
* 添加ready状态的track
|
||||
*/
|
||||
void addTrack(const Track::Ptr & track) override;
|
||||
private:
|
||||
void createFile();
|
||||
void closeFile();
|
||||
void asyncClose();
|
||||
private:
|
||||
string _strPath;
|
||||
string _strFile;
|
||||
string _strFileTmp;
|
||||
Ticker _createFileTicker;
|
||||
Mp4Info _info;
|
||||
bool _haveVideo = false;
|
||||
MP4MuxerFile::Ptr _muxer;
|
||||
list<Track::Ptr> _tracks;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
#endif ///ENABLE_MP4RECORD
|
||||
|
||||
#endif /* MP4MAKER_H_ */
|
||||
95
src/Record/Recorder.cpp
Normal file
95
src/Record/Recorder.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "Recorder.h"
|
||||
#include "Common/config.h"
|
||||
#include "Http/HttpSession.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/mini.h"
|
||||
#include "Network/sockutil.h"
|
||||
#include "HlsMakerImp.h"
|
||||
#include "Player/PlayerBase.h"
|
||||
#include "Common/MediaSink.h"
|
||||
#include "MP4Recorder.h"
|
||||
#include "HlsRecorder.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
MediaSinkInterface *Recorder::createHlsRecorder(const string &strVhost_tmp, const string &strApp, const string &strId) {
|
||||
#if defined(ENABLE_HLS)
|
||||
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
|
||||
GET_CONFIG(string, hlsPath, Hls::kFilePath);
|
||||
|
||||
string strVhost = strVhost_tmp;
|
||||
if (trim(strVhost).empty()) {
|
||||
//如果strVhost为空,则强制为默认虚拟主机
|
||||
strVhost = DEFAULT_VHOST;
|
||||
}
|
||||
|
||||
string m3u8FilePath;
|
||||
string params;
|
||||
if (enableVhost) {
|
||||
m3u8FilePath = strVhost + "/" + strApp + "/" + strId + "/hls.m3u8";
|
||||
params = string(VHOST_KEY) + "=" + strVhost;
|
||||
} else {
|
||||
m3u8FilePath = strApp + "/" + strId + "/hls.m3u8";
|
||||
}
|
||||
m3u8FilePath = File::absolutePath(m3u8FilePath, hlsPath);
|
||||
return new HlsRecorder(m3u8FilePath, params);
|
||||
#else
|
||||
return nullptr;
|
||||
#endif //defined(ENABLE_HLS)
|
||||
}
|
||||
|
||||
MediaSinkInterface *Recorder::createMP4Recorder(const string &strVhost_tmp, const string &strApp, const string &strId) {
|
||||
#if defined(ENABLE_MP4RECORD)
|
||||
GET_CONFIG(bool, enableVhost, General::kEnableVhost);
|
||||
GET_CONFIG(string, recordPath, Record::kFilePath);
|
||||
GET_CONFIG(string, recordAppName, Record::kAppName);
|
||||
|
||||
string strVhost = strVhost_tmp;
|
||||
if (trim(strVhost).empty()) {
|
||||
//如果strVhost为空,则强制为默认虚拟主机
|
||||
strVhost = DEFAULT_VHOST;
|
||||
}
|
||||
|
||||
string mp4FilePath;
|
||||
if (enableVhost) {
|
||||
mp4FilePath = strVhost + "/" + recordAppName + "/" + strApp + "/" + strId + "/";
|
||||
} else {
|
||||
mp4FilePath = recordAppName + "/" + strApp + "/" + strId + "/";
|
||||
}
|
||||
mp4FilePath = File::absolutePath(mp4FilePath, recordPath);
|
||||
return new MP4Recorder(mp4FilePath, strVhost, strApp, strId);
|
||||
#else
|
||||
return nullptr;
|
||||
#endif //defined(ENABLE_MP4RECORD)
|
||||
}
|
||||
|
||||
|
||||
} /* namespace mediakit */
|
||||
49
src/Record/Recorder.h
Normal file
49
src/Record/Recorder.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
|
||||
*
|
||||
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef SRC_MEDIAFILE_RECORDER_H_
|
||||
#define SRC_MEDIAFILE_RECORDER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class MediaSinkInterface;
|
||||
|
||||
class Recorder{
|
||||
public:
|
||||
static MediaSinkInterface *createHlsRecorder(const string &strVhost, const string &strApp, const string &strId);
|
||||
static MediaSinkInterface *createMP4Recorder(const string &strVhost, const string &strApp, const string &strId);
|
||||
private:
|
||||
Recorder() = delete;
|
||||
~Recorder() = delete;
|
||||
};
|
||||
|
||||
} /* namespace mediakit */
|
||||
|
||||
#endif /* SRC_MEDIAFILE_RECORDER_H_ */
|
||||
140
src/Record/TsMuxer.cpp
Normal file
140
src/Record/TsMuxer.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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 "TsMuxer.h"
|
||||
#if defined(ENABLE_HLS)
|
||||
#include "mpeg-ts-proto.h"
|
||||
#include "mpeg-ts.h"
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
TsMuxer::TsMuxer() {
|
||||
init();
|
||||
}
|
||||
|
||||
TsMuxer::~TsMuxer() {
|
||||
uninit();
|
||||
}
|
||||
|
||||
void TsMuxer::addTrack(const Track::Ptr &track) {
|
||||
switch (track->getCodecId()){
|
||||
case CodecH264: {
|
||||
_codec_to_trackid[track->getCodecId()].track_id = mpeg_ts_add_stream(_context, PSI_STREAM_H264, nullptr, 0);
|
||||
} break;
|
||||
case CodecH265: {
|
||||
_codec_to_trackid[track->getCodecId()].track_id = mpeg_ts_add_stream(_context, PSI_STREAM_H265, nullptr, 0);
|
||||
}break;
|
||||
case CodecAAC: {
|
||||
_codec_to_trackid[track->getCodecId()].track_id = mpeg_ts_add_stream(_context, PSI_STREAM_AAC, nullptr, 0);
|
||||
}break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TsMuxer::inputFrame(const Frame::Ptr &frame) {
|
||||
auto it = _codec_to_trackid.find(frame->getCodecId());
|
||||
if(it == _codec_to_trackid.end()){
|
||||
return;
|
||||
}
|
||||
//mp4文件时间戳需要从0开始
|
||||
auto &track_info = it->second;
|
||||
int64_t dts_out, pts_out;
|
||||
|
||||
switch (frame->getCodecId()){
|
||||
case CodecH265:
|
||||
case CodecH264: {
|
||||
//这里的代码逻辑是让SPS、PPS、IDR这些时间戳相同的帧打包到一起当做一个帧处理,
|
||||
if (!_frameCached.empty() && _frameCached.back()->dts() != frame->dts()) {
|
||||
Frame::Ptr back = _frameCached.back();
|
||||
Buffer::Ptr merged_frame = back;
|
||||
if(_frameCached.size() != 1){
|
||||
string merged;
|
||||
_frameCached.for_each([&](const Frame::Ptr &frame){
|
||||
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));
|
||||
}
|
||||
track_info.stamp.revise(back->dts(),back->pts(),dts_out,pts_out);
|
||||
_timestamp = dts_out;
|
||||
mpeg_ts_write(_context, track_info.track_id, back->keyFrame() ? 0x0001 : 0, pts_out * 90LL, dts_out * 90LL, merged_frame->data(), merged_frame->size());
|
||||
_frameCached.clear();
|
||||
}
|
||||
_frameCached.emplace_back(Frame::getCacheAbleFrame(frame));
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
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, frame->data(), frame->size());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TsMuxer::resetTracks() {
|
||||
//通知片段中断
|
||||
onTs(nullptr, 0, 0, 0);
|
||||
uninit();
|
||||
init();
|
||||
}
|
||||
|
||||
void TsMuxer::init() {
|
||||
static mpeg_ts_func_t s_func= {
|
||||
[](void* param, size_t bytes){
|
||||
TsMuxer *muxer = (TsMuxer *)param;
|
||||
assert(sizeof(TsMuxer::_tsbuf) >= bytes);
|
||||
return (void *)muxer->_tsbuf;
|
||||
},
|
||||
[](void* param, void* packet){
|
||||
//do nothing
|
||||
},
|
||||
[](void* param, const void* packet, size_t bytes){
|
||||
TsMuxer *muxer = (TsMuxer *)param;
|
||||
muxer->onTs(packet, bytes,muxer->_timestamp,0);
|
||||
}
|
||||
};
|
||||
if(_context == nullptr){
|
||||
_context = mpeg_ts_create(&s_func,this);
|
||||
}
|
||||
}
|
||||
|
||||
void TsMuxer::uninit() {
|
||||
if(_context){
|
||||
mpeg_ts_destroy(_context);
|
||||
_context = nullptr;
|
||||
}
|
||||
_codec_to_trackid.clear();
|
||||
}
|
||||
|
||||
}//namespace mediakit
|
||||
|
||||
#endif// defined(ENABLE_HLS)
|
||||
67
src/Record/TsMuxer.h
Normal file
67
src/Record/TsMuxer.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 TSMUXER_H
|
||||
#define TSMUXER_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include "Extension/Frame.h"
|
||||
#include "Extension/Track.h"
|
||||
#include "Util/File.h"
|
||||
#include "Common/MediaSink.h"
|
||||
#include "Common/Stamp.h"
|
||||
|
||||
using namespace toolkit;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class TsMuxer : public MediaSinkInterface {
|
||||
public:
|
||||
TsMuxer();
|
||||
virtual ~TsMuxer();
|
||||
void addTrack(const Track::Ptr &track) 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;
|
||||
private:
|
||||
void init();
|
||||
void uninit();
|
||||
private:
|
||||
void *_context = nullptr;
|
||||
char *_tsbuf[188];
|
||||
uint32_t _timestamp = 0;
|
||||
|
||||
struct track_info{
|
||||
int track_id = -1;
|
||||
Stamp stamp;
|
||||
};
|
||||
unordered_map<int,track_info> _codec_to_trackid;
|
||||
List<Frame::Ptr> _frameCached;
|
||||
};
|
||||
|
||||
}//namespace mediakit
|
||||
#endif //TSMUXER_H
|
||||
Reference in New Issue
Block a user