mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2026-06-27 21:02:23 +08:00
AI automatically translates all comments in the code into English (#3917)
This commit is contained in:
@@ -18,7 +18,8 @@ namespace mediakit {
|
||||
|
||||
HlsMaker::HlsMaker(bool is_fmp4, float seg_duration, uint32_t seg_number, bool seg_keep) {
|
||||
_is_fmp4 = is_fmp4;
|
||||
//最小允许设置为0,0个切片代表点播
|
||||
// 最小允许设置为0,0个切片代表点播 [AUTO-TRANSLATED:19235e8e]
|
||||
// Minimum allowed setting is 0, 0 slices represent on-demand
|
||||
_seg_number = seg_number;
|
||||
_seg_duration = seg_duration;
|
||||
_seg_keep = seg_keep;
|
||||
@@ -96,21 +97,25 @@ void HlsMaker::inputInitSegment(const char *data, size_t len) {
|
||||
void HlsMaker::inputData(const char *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet) {
|
||||
if (data && len) {
|
||||
if (timestamp < _last_timestamp) {
|
||||
// 时间戳回退了,切片时长重新计时
|
||||
// 时间戳回退了,切片时长重新计时 [AUTO-TRANSLATED:fe91bd7f]
|
||||
// Timestamp has been rolled back, slice duration is recalculated
|
||||
WarnL << "Timestamp reduce: " << _last_timestamp << " -> " << timestamp;
|
||||
_last_seg_timestamp = _last_timestamp = timestamp;
|
||||
}
|
||||
if (is_idr_fast_packet) {
|
||||
// 尝试切片ts
|
||||
// 尝试切片ts [AUTO-TRANSLATED:62264109]
|
||||
// Attempt to slice ts
|
||||
addNewSegment(timestamp);
|
||||
}
|
||||
if (!_last_file_name.empty()) {
|
||||
// 存在切片才写入ts数据
|
||||
// 存在切片才写入ts数据 [AUTO-TRANSLATED:ddd46115]
|
||||
// Write ts data only if there are slices
|
||||
onWriteSegment(data, len);
|
||||
_last_timestamp = timestamp;
|
||||
}
|
||||
} else {
|
||||
// resetTracks时触发此逻辑
|
||||
// resetTracks时触发此逻辑 [AUTO-TRANSLATED:0ba915ed]
|
||||
// This logic is triggered when resetTracks is called
|
||||
flushLastSegment(false);
|
||||
}
|
||||
}
|
||||
@@ -118,19 +123,23 @@ void HlsMaker::inputData(const char *data, size_t len, uint64_t timestamp, bool
|
||||
void HlsMaker::delOldSegment() {
|
||||
GET_CONFIG(uint32_t, segDelay, Hls::kSegmentDelay);
|
||||
if (_seg_number == 0) {
|
||||
//如果设置为保留0个切片,则认为是保存为点播
|
||||
// 如果设置为保留0个切片,则认为是保存为点播 [AUTO-TRANSLATED:5bf20108]
|
||||
// If set to keep 0 slices, it is considered to be saved as on-demand
|
||||
return;
|
||||
}
|
||||
//在hls m3u8索引文件中,我们保存的切片个数跟_seg_number相关设置一致
|
||||
// 在hls m3u8索引文件中,我们保存的切片个数跟_seg_number相关设置一致 [AUTO-TRANSLATED:b14b5b98]
|
||||
// In the hls m3u8 index file, the number of slices we save is consistent with the _seg_number setting
|
||||
if (_file_index > _seg_number + segDelay) {
|
||||
_seg_dur_list.pop_front();
|
||||
}
|
||||
//如果设置为一直保存,就不删除
|
||||
// 如果设置为一直保存,就不删除 [AUTO-TRANSLATED:7c622e24]
|
||||
// If set to always save, it will not be deleted
|
||||
if (_seg_keep) {
|
||||
return;
|
||||
}
|
||||
GET_CONFIG(uint32_t, segRetain, Hls::kSegmentRetain);
|
||||
//但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕
|
||||
// 但是实际保存的切片个数比m3u8所述多若干个,这样做的目的是防止播放器在切片删除前能下载完毕 [AUTO-TRANSLATED:1688f857]
|
||||
// However, the actual number of slices saved is a few more than what is stated in the m3u8, this is done to prevent the player from downloading the slices before they are deleted
|
||||
if (_file_index > _seg_number + segDelay + segRetain) {
|
||||
onDelSegment(_file_index - _seg_number - segDelay - segRetain - 1);
|
||||
}
|
||||
@@ -139,35 +148,44 @@ void HlsMaker::delOldSegment() {
|
||||
void HlsMaker::addNewSegment(uint64_t stamp) {
|
||||
GET_CONFIG(bool, fastRegister, Hls::kFastRegister);
|
||||
if (_file_index > fastRegister && stamp - _last_seg_timestamp < _seg_duration * 1000) {
|
||||
//确保序号为0的切片立即open,如果开启快速注册功能,序号为1的切片也应该遇到关键帧立即生成;否则需要等切片时长够长
|
||||
// 确保序号为0的切片立即open,如果开启快速注册功能,序号为1的切片也应该遇到关键帧立即生成;否则需要等切片时长够长 [AUTO-TRANSLATED:d81d1a1c]
|
||||
// Ensure that the slice with sequence number 0 is opened immediately, if the fast registration function is enabled, the slice with sequence number 1 should also be generated immediately when it encounters a keyframe; otherwise, it needs to wait until the slice duration is long enough
|
||||
return;
|
||||
}
|
||||
//关闭并保存上一个切片,如果_seg_number==0,那么是点播。
|
||||
// 关闭并保存上一个切片,如果_seg_number==0,那么是点播。 [AUTO-TRANSLATED:14076b61]
|
||||
// Close and save the previous slice, if _seg_number==0, then it is on-demand.
|
||||
flushLastSegment(false);
|
||||
//新增切片
|
||||
// 新增切片 [AUTO-TRANSLATED:b8623419]
|
||||
// Add a new slice
|
||||
_last_file_name = onOpenSegment(_file_index++);
|
||||
//记录本次切片的起始时间戳
|
||||
// 记录本次切片的起始时间戳 [AUTO-TRANSLATED:8eb776e9]
|
||||
// Record the starting timestamp of this slice
|
||||
_last_seg_timestamp = _last_timestamp ? _last_timestamp : stamp;
|
||||
}
|
||||
|
||||
void HlsMaker::flushLastSegment(bool eof){
|
||||
GET_CONFIG(uint32_t, segDelay, Hls::kSegmentDelay);
|
||||
if (_last_file_name.empty()) {
|
||||
//不存在上个切片
|
||||
// 不存在上个切片 [AUTO-TRANSLATED:d81fe08e]
|
||||
// There is no previous slice
|
||||
return;
|
||||
}
|
||||
//文件创建到最后一次数据写入的时间即为切片长度
|
||||
// 文件创建到最后一次数据写入的时间即为切片长度 [AUTO-TRANSLATED:1f85739c]
|
||||
// The time from file creation to the last data write is the slice length
|
||||
auto seg_dur = _last_timestamp - _last_seg_timestamp;
|
||||
if (seg_dur <= 0) {
|
||||
seg_dur = 100;
|
||||
}
|
||||
_seg_dur_list.emplace_back(seg_dur, std::move(_last_file_name));
|
||||
delOldSegment();
|
||||
//先flush ts切片,否则可能存在ts文件未写入完毕就被访问的情况
|
||||
// 先flush ts切片,否则可能存在ts文件未写入完毕就被访问的情况 [AUTO-TRANSLATED:f8d6dc87]
|
||||
// Flush the ts slice first, otherwise there may be a situation where the ts file is not written completely before it is accessed
|
||||
onFlushLastSegment(seg_dur);
|
||||
//然后写m3u8文件
|
||||
// 然后写m3u8文件 [AUTO-TRANSLATED:67200ce1]
|
||||
// Then write the m3u8 file
|
||||
makeIndexFile(false, eof);
|
||||
//写入切片延迟的m3u8文件
|
||||
// 写入切片延迟的m3u8文件 [AUTO-TRANSLATED:b1f12e43]
|
||||
// Write the m3u8 file with slice delay
|
||||
if (segDelay) {
|
||||
makeIndexFile(true, eof);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,12 @@ public:
|
||||
* @param seg_duration 切片文件长度
|
||||
* @param seg_number 切片个数
|
||||
* @param seg_keep 是否保留切片文件
|
||||
* @param is_fmp4 Use fmp4 or mpegts
|
||||
* @param seg_duration Segment file length
|
||||
* @param seg_number Number of segments
|
||||
* @param seg_keep Whether to keep the segment file
|
||||
|
||||
* [AUTO-TRANSLATED:260bbca3]
|
||||
*/
|
||||
HlsMaker(bool is_fmp4 = false, float seg_duration = 5, uint32_t seg_number = 3, bool seg_keep = false);
|
||||
virtual ~HlsMaker() = default;
|
||||
@@ -35,6 +41,13 @@ public:
|
||||
* @param len 数据长度
|
||||
* @param timestamp 毫秒时间戳
|
||||
* @param is_idr_fast_packet 是否为关键帧第一个包
|
||||
* Write ts data
|
||||
* @param data Data
|
||||
* @param len Data length
|
||||
* @param timestamp Millisecond timestamp
|
||||
* @param is_idr_fast_packet Whether it is the first packet of the key frame
|
||||
|
||||
* [AUTO-TRANSLATED:b886bbbf]
|
||||
*/
|
||||
void inputData(const char *data, size_t len, uint64_t timestamp, bool is_idr_fast_packet);
|
||||
|
||||
@@ -42,26 +55,43 @@ public:
|
||||
* 输入fmp4 init segment
|
||||
* @param data 数据
|
||||
* @param len 数据长度
|
||||
* Input fmp4 init segment
|
||||
* @param data Data
|
||||
* @param len Data length
|
||||
|
||||
* [AUTO-TRANSLATED:8d613a42]
|
||||
*/
|
||||
void inputInitSegment(const char *data, size_t len);
|
||||
|
||||
/**
|
||||
* 是否为直播
|
||||
* Whether it is live
|
||||
|
||||
* [AUTO-TRANSLATED:1dae0496]
|
||||
*/
|
||||
bool isLive() const;
|
||||
|
||||
/**
|
||||
* 是否保留切片文件
|
||||
* Whether to keep the segment file
|
||||
|
||||
* [AUTO-TRANSLATED:c2d1bce5]
|
||||
*/
|
||||
bool isKeep() const;
|
||||
|
||||
/**
|
||||
* 是否采用fmp4切片还是mpegts
|
||||
* Whether to use fmp4 segmentation or mpegts
|
||||
|
||||
* [AUTO-TRANSLATED:36763fc8]
|
||||
*/
|
||||
bool isFmp4() const;
|
||||
|
||||
/**
|
||||
* 清空记录
|
||||
* Clear records
|
||||
|
||||
* [AUTO-TRANSLATED:34a4b6cd]
|
||||
*/
|
||||
void clear();
|
||||
|
||||
@@ -70,12 +100,21 @@ protected:
|
||||
* 创建ts切片文件回调
|
||||
* @param index
|
||||
* @return
|
||||
* Create ts segment file callback
|
||||
* @param index
|
||||
* @return
|
||||
|
||||
* [AUTO-TRANSLATED:2a3806fc]
|
||||
*/
|
||||
virtual std::string onOpenSegment(uint64_t index) = 0;
|
||||
|
||||
/**
|
||||
* 删除ts切片文件回调
|
||||
* @param index
|
||||
* Delete ts segment file callback
|
||||
* @param index
|
||||
|
||||
* [AUTO-TRANSLATED:1c0d4397]
|
||||
*/
|
||||
virtual void onDelSegment(uint64_t index) = 0;
|
||||
|
||||
@@ -83,6 +122,11 @@ protected:
|
||||
* 写init.mp4切片文件回调
|
||||
* @param data
|
||||
* @param len
|
||||
* Write init.mp4 segment file callback
|
||||
* @param data
|
||||
* @param len
|
||||
|
||||
* [AUTO-TRANSLATED:e0021ec5]
|
||||
*/
|
||||
virtual void onWriteInitSegment(const char *data, size_t len) = 0;
|
||||
|
||||
@@ -90,23 +134,39 @@ protected:
|
||||
* 写ts切片文件回调
|
||||
* @param data
|
||||
* @param len
|
||||
* Write ts segment file callback
|
||||
* @param data
|
||||
* @param len
|
||||
|
||||
* [AUTO-TRANSLATED:bb81e206]
|
||||
*/
|
||||
virtual void onWriteSegment(const char *data, size_t len) = 0;
|
||||
|
||||
/**
|
||||
* 写m3u8文件回调
|
||||
* Write m3u8 file callback
|
||||
|
||||
* [AUTO-TRANSLATED:5754525f]
|
||||
*/
|
||||
virtual void onWriteHls(const std::string &data, bool include_delay) = 0;
|
||||
|
||||
/**
|
||||
* 上一个 ts 切片写入完成, 可在这里进行通知处理
|
||||
* @param duration_ms 上一个 ts 切片的时长, 单位为毫秒
|
||||
* The previous ts segment is written, you can notify here
|
||||
* @param duration_ms The duration of the previous ts segment, in milliseconds
|
||||
|
||||
* [AUTO-TRANSLATED:36b42bc0]
|
||||
*/
|
||||
virtual void onFlushLastSegment(uint64_t duration_ms) {};
|
||||
|
||||
/**
|
||||
* 关闭上个ts切片并且写入m3u8索引
|
||||
* @param eof HLS直播是否已结束
|
||||
* Close the previous ts segment and write the m3u8 index
|
||||
* @param eof Whether the HLS live broadcast has ended
|
||||
|
||||
* [AUTO-TRANSLATED:614b7e14]
|
||||
*/
|
||||
void flushLastSegment(bool eof);
|
||||
|
||||
@@ -114,17 +174,28 @@ private:
|
||||
/**
|
||||
* 生成m3u8文件
|
||||
* @param eof true代表点播
|
||||
* Generate m3u8 file
|
||||
* @param eof true represents on-demand
|
||||
|
||||
* [AUTO-TRANSLATED:d6c74fb6]
|
||||
*/
|
||||
void makeIndexFile(bool include_delay, bool eof = false);
|
||||
|
||||
/**
|
||||
* 删除旧的ts切片
|
||||
* Delete old ts segments
|
||||
|
||||
* [AUTO-TRANSLATED:5da8bd70]
|
||||
*/
|
||||
void delOldSegment();
|
||||
|
||||
/**
|
||||
* 添加新的ts切片
|
||||
* @param timestamp
|
||||
* Add new ts segments
|
||||
* @param timestamp
|
||||
|
||||
* [AUTO-TRANSLATED:e321e9f0]
|
||||
*/
|
||||
void addNewSegment(uint64_t timestamp);
|
||||
|
||||
|
||||
@@ -43,7 +43,8 @@ HlsMakerImp::HlsMakerImp(bool is_fmp4, const string &m3u8_file, const string &pa
|
||||
|
||||
HlsMakerImp::~HlsMakerImp() {
|
||||
try {
|
||||
// 可能hls注册时导致抛异常
|
||||
// 可能hls注册时导致抛异常 [AUTO-TRANSLATED:82add30d]
|
||||
// Possible exception thrown during hls registration
|
||||
clearCache(false, true);
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
@@ -62,7 +63,8 @@ static void clearHls(const std::list<std::string> &files) {
|
||||
}
|
||||
|
||||
void HlsMakerImp::clearCache(bool immediately, bool eof) {
|
||||
// 录制完了
|
||||
// 录制完了 [AUTO-TRANSLATED:5d3bfbeb]
|
||||
// Recording finished
|
||||
flushLastSegment(eof);
|
||||
if (!isLive() || isKeep()) {
|
||||
return;
|
||||
@@ -79,7 +81,8 @@ void HlsMakerImp::clearCache(bool immediately, bool eof) {
|
||||
lst.emplace_back(std::move(pr.second));
|
||||
}
|
||||
|
||||
// hls直播才删除文件
|
||||
// hls直播才删除文件 [AUTO-TRANSLATED:81d2aaa5]
|
||||
// Delete file only after hls live streaming
|
||||
GET_CONFIG(uint32_t, delay, Hls::kDeleteDelaySec);
|
||||
if (!delay || immediately) {
|
||||
clearHls(lst);
|
||||
@@ -110,7 +113,8 @@ string HlsMakerImp::onOpenSegment(uint64_t index) {
|
||||
}
|
||||
_file = makeFile(segment_path, true);
|
||||
|
||||
// 保存本切片的元数据
|
||||
// 保存本切片的元数据 [AUTO-TRANSLATED:64e6f692]
|
||||
// Save metadata for this slice
|
||||
_info.start_time = ::time(NULL);
|
||||
_info.file_name = segment_name;
|
||||
_info.file_path = segment_path;
|
||||
@@ -171,7 +175,8 @@ void HlsMakerImp::onWriteHls(const std::string &data, bool include_delay) {
|
||||
}
|
||||
|
||||
void HlsMakerImp::onFlushLastSegment(uint64_t duration_ms) {
|
||||
// 关闭并flush文件到磁盘
|
||||
// 关闭并flush文件到磁盘 [AUTO-TRANSLATED:9798ec4d]
|
||||
// Close and flush file to disk
|
||||
_file = nullptr;
|
||||
|
||||
GET_CONFIG(bool, broadcastRecordTs, Hls::kBroadcastRecordTs);
|
||||
|
||||
@@ -27,17 +27,28 @@ public:
|
||||
|
||||
/**
|
||||
* 设置媒体信息
|
||||
* Set media information
|
||||
|
||||
* [AUTO-TRANSLATED:d205db9f]
|
||||
*/
|
||||
void setMediaSource(const MediaTuple& tuple);
|
||||
|
||||
/**
|
||||
* 获取MediaSource
|
||||
* @return
|
||||
* Get MediaSource
|
||||
* @return
|
||||
|
||||
* [AUTO-TRANSLATED:af916433]
|
||||
*/
|
||||
HlsMediaSource::Ptr getMediaSource() const;
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
* Clear cache
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:f872d7e2]
|
||||
*/
|
||||
void clearCache();
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ void HlsCookieData::addReaderCount() {
|
||||
_ring_reader = src->getRing()->attach(EventPollerPool::Instance().getPoller());
|
||||
auto added = _added;
|
||||
_ring_reader->setDetachCB([added]() {
|
||||
// HlsMediaSource已经销毁
|
||||
// HlsMediaSource已经销毁 [AUTO-TRANSLATED:bedb0385]
|
||||
// HlsMediaSource has been destroyed
|
||||
*added = false;
|
||||
});
|
||||
auto info = _sock_info;
|
||||
@@ -90,7 +91,8 @@ void HlsMediaSource::setIndexFile(std::string index_file)
|
||||
regist();
|
||||
}
|
||||
|
||||
//赋值m3u8索引文件内容
|
||||
// 赋值m3u8索引文件内容 [AUTO-TRANSLATED:c11882b5]
|
||||
// Assign m3u8 index file content
|
||||
std::lock_guard<std::mutex> lck(_mtx_index);
|
||||
_index_file = std::move(index_file);
|
||||
|
||||
@@ -107,7 +109,8 @@ void HlsMediaSource::getIndexFile(std::function<void(const std::string& str)> cb
|
||||
cb(_index_file);
|
||||
return;
|
||||
}
|
||||
//等待生成m3u8文件
|
||||
// 等待生成m3u8文件 [AUTO-TRANSLATED:c3ae3286]
|
||||
// Waiting for m3u8 file generation
|
||||
_list_cb.emplace_back(std::move(cb));
|
||||
}
|
||||
|
||||
|
||||
@@ -29,26 +29,42 @@ public:
|
||||
|
||||
/**
|
||||
* 获取媒体源的环形缓冲
|
||||
* Get the circular buffer of the media source
|
||||
|
||||
* [AUTO-TRANSLATED:75ac76b6]
|
||||
*/
|
||||
const RingType::Ptr &getRing() const { return _ring; }
|
||||
|
||||
/**
|
||||
* 获取播放器个数
|
||||
* Get the number of players
|
||||
|
||||
* [AUTO-TRANSLATED:a451c846]
|
||||
*/
|
||||
int readerCount() override { return _ring ? _ring->readerCount() : 0; }
|
||||
|
||||
/**
|
||||
* 设置或清空m3u8索引文件内容
|
||||
* Set or clear the m3u8 index file content
|
||||
|
||||
* [AUTO-TRANSLATED:71db921d]
|
||||
*/
|
||||
void setIndexFile(std::string index_file);
|
||||
|
||||
/**
|
||||
* 异步获取m3u8文件
|
||||
* Asynchronously get the m3u8 file
|
||||
|
||||
* [AUTO-TRANSLATED:e962b3ad]
|
||||
*/
|
||||
void getIndexFile(std::function<void(const std::string &str)> cb);
|
||||
|
||||
/**
|
||||
* 同步获取m3u8文件
|
||||
* Synchronously get the m3u8 file
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:52b228df]
|
||||
*/
|
||||
std::string getIndexFile() const {
|
||||
std::lock_guard<std::mutex> lck(_mtx_index);
|
||||
|
||||
@@ -29,7 +29,8 @@ public:
|
||||
|
||||
_option = option;
|
||||
_hls = std::make_shared<HlsMakerImp>(is_fmp4, m3u8_file, params, hlsBufSize, hlsDuration, hlsNum, hlsKeep);
|
||||
// 清空上次的残余文件
|
||||
// 清空上次的残余文件 [AUTO-TRANSLATED:e16122be]
|
||||
// Clear the residual files from the last time
|
||||
_hls->clearCache();
|
||||
}
|
||||
|
||||
@@ -45,10 +46,12 @@ public:
|
||||
int readerCount() { return _hls->getMediaSource()->readerCount(); }
|
||||
|
||||
void onReaderChanged(MediaSource &sender, int size) override {
|
||||
// hls保留切片个数为0时代表为hls录制(不删除切片),那么不管有无观看者都一直生成hls
|
||||
// hls保留切片个数为0时代表为hls录制(不删除切片),那么不管有无观看者都一直生成hls [AUTO-TRANSLATED:55709255]
|
||||
// When the number of hls slices is 0, it means hls recording (not deleting slices), so hls is generated all the time regardless of whether there are viewers
|
||||
_enabled = _option.hls_demand ? (_hls->isLive() ? size : true) : true;
|
||||
if (!size && _hls->isLive() && _option.hls_demand) {
|
||||
// hls直播时,如果无人观看就删除视频缓存,目的是为了防止视频跳跃
|
||||
// hls直播时,如果无人观看就删除视频缓存,目的是为了防止视频跳跃 [AUTO-TRANSLATED:1d875c6a]
|
||||
// When hls is live, if no one is watching, delete the video cache to prevent video jumping
|
||||
_clear_cache = true;
|
||||
}
|
||||
MediaSourceEventInterceptor::onReaderChanged(sender, size);
|
||||
@@ -57,7 +60,8 @@ public:
|
||||
bool inputFrame(const Frame::Ptr &frame) override {
|
||||
if (_clear_cache && _option.hls_demand) {
|
||||
_clear_cache = false;
|
||||
//清空旧的m3u8索引文件于ts切片
|
||||
// 清空旧的m3u8索引文件于ts切片 [AUTO-TRANSLATED:a4ce0664]
|
||||
// Clear the old m3u8 index file and ts slices
|
||||
_hls->clearCache();
|
||||
_hls->getMediaSource()->setIndexFile("");
|
||||
}
|
||||
@@ -68,7 +72,8 @@ public:
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
//缓存尚未清空时,还允许触发inputFrame函数,以便及时清空缓存
|
||||
// 缓存尚未清空时,还允许触发inputFrame函数,以便及时清空缓存 [AUTO-TRANSLATED:7cfd4d49]
|
||||
// When the cache has not been cleared, it is still allowed to trigger the inputFrame function to clear the cache in time
|
||||
return _option.hls_demand ? (_clear_cache ? true : _enabled) : true;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@ static struct mov_buffer_t s_io = {
|
||||
MP4FileIO::Writer MP4FileIO::createWriter(int flags, bool is_fmp4){
|
||||
Writer writer;
|
||||
Ptr self = shared_from_this();
|
||||
//保存自己的强引用,防止提前释放
|
||||
// 保存自己的强引用,防止提前释放 [AUTO-TRANSLATED:e8e14f60]
|
||||
// Save a strong reference to itself to prevent premature release
|
||||
writer.reset(mp4_writer_create(is_fmp4, &s_io,this, flags),[self](mp4_writer_t *ptr){
|
||||
if(ptr){
|
||||
mp4_writer_destroy(ptr);
|
||||
@@ -57,7 +58,8 @@ MP4FileIO::Writer MP4FileIO::createWriter(int flags, bool is_fmp4){
|
||||
MP4FileIO::Reader MP4FileIO::createReader(){
|
||||
Reader reader;
|
||||
Ptr self = shared_from_this();
|
||||
//保存自己的强引用,防止提前释放
|
||||
// 保存自己的强引用,防止提前释放 [AUTO-TRANSLATED:e8e14f60]
|
||||
// Save a strong reference to itself to prevent premature release
|
||||
reader.reset(mov_reader_create(&s_io,this),[self](mov_reader_t *ptr){
|
||||
if(ptr){
|
||||
mov_reader_destroy(ptr);
|
||||
@@ -80,7 +82,8 @@ MP4FileIO::Reader MP4FileIO::createReader(){
|
||||
#endif
|
||||
|
||||
void MP4FileDisk::openFile(const char *file, const char *mode) {
|
||||
//创建文件
|
||||
// 创建文件 [AUTO-TRANSLATED:bd145ed5]
|
||||
// Create a file
|
||||
auto fp = File::create_file(file, mode);
|
||||
if(!fp){
|
||||
throw std::runtime_error(string("打开文件失败:") + file);
|
||||
@@ -88,7 +91,8 @@ void MP4FileDisk::openFile(const char *file, const char *mode) {
|
||||
|
||||
GET_CONFIG(uint32_t,mp4BufSize,Record::kFileBufSize);
|
||||
|
||||
//新建文件io缓存
|
||||
// 新建文件io缓存 [AUTO-TRANSLATED:fda9ff47]
|
||||
// Create a new file io cache
|
||||
std::shared_ptr<char> file_buf(new char[mp4BufSize],[](char *ptr){
|
||||
if(ptr){
|
||||
delete [] ptr;
|
||||
@@ -96,11 +100,13 @@ void MP4FileDisk::openFile(const char *file, const char *mode) {
|
||||
});
|
||||
|
||||
if(file_buf){
|
||||
//设置文件io缓存
|
||||
// 设置文件io缓存 [AUTO-TRANSLATED:0ed9c8ad]
|
||||
// Set the file io cache
|
||||
setvbuf(fp, file_buf.get(), _IOFBF, mp4BufSize);
|
||||
}
|
||||
|
||||
//创建智能指针
|
||||
// 创建智能指针 [AUTO-TRANSLATED:e7920ab2]
|
||||
// Create a smart pointer
|
||||
_file.reset(fp,[file_buf](FILE *fp) {
|
||||
fflush(fp);
|
||||
fclose(fp);
|
||||
@@ -168,7 +174,8 @@ int MP4FileMemory::onRead(void *data, size_t bytes){
|
||||
|
||||
int MP4FileMemory::onWrite(const void *data, size_t bytes){
|
||||
if (_offset + bytes > _memory.size()) {
|
||||
//需要扩容
|
||||
// 需要扩容 [AUTO-TRANSLATED:211c91e3]
|
||||
// Need to expand
|
||||
_memory.resize(_offset + bytes);
|
||||
}
|
||||
memcpy((uint8_t *) _memory.data() + _offset, data, bytes);
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
//mp4文件IO的抽象接口类
|
||||
// mp4文件IO的抽象接口类 [AUTO-TRANSLATED:dab24105]
|
||||
// Abstract interface class for mp4 file IO
|
||||
class MP4FileIO : public std::enable_shared_from_this<MP4FileIO> {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<MP4FileIO>;
|
||||
@@ -40,17 +41,30 @@ public:
|
||||
* @param flags 支持0、MOV_FLAG_FASTSTART、MOV_FLAG_SEGMENT
|
||||
* @param is_fmp4 是否为fmp4还是普通mp4
|
||||
* @return mp4复用器
|
||||
* Create an mp4 muxer
|
||||
* @param flags Supports 0, MOV_FLAG_FASTSTART, MOV_FLAG_SEGMENT
|
||||
* @param is_fmp4 Whether it is fmp4 or ordinary mp4
|
||||
* @return mp4 muxer
|
||||
|
||||
* [AUTO-TRANSLATED:97fefe95]
|
||||
*/
|
||||
virtual Writer createWriter(int flags, bool is_fmp4 = false);
|
||||
|
||||
/**
|
||||
* 创建mp4解复用器
|
||||
* @return mp4解复用器
|
||||
* Create an mp4 demuxer
|
||||
* @return mp4 demuxer
|
||||
|
||||
* [AUTO-TRANSLATED:4a303019]
|
||||
*/
|
||||
virtual Reader createReader();
|
||||
|
||||
/**
|
||||
* 获取文件读写位置
|
||||
* Get the file read/write position
|
||||
|
||||
* [AUTO-TRANSLATED:f8a5b290]
|
||||
*/
|
||||
virtual uint64_t onTell() = 0;
|
||||
|
||||
@@ -58,6 +72,11 @@ public:
|
||||
* seek至文件某处
|
||||
* @param offset 文件偏移量
|
||||
* @return 是否成功(0成功)
|
||||
* Seek to a certain location in the file
|
||||
* @param offset File offset
|
||||
* @return Whether it is successful (0 successful)
|
||||
|
||||
* [AUTO-TRANSLATED:936089eb]
|
||||
*/
|
||||
virtual int onSeek(uint64_t offset) = 0;
|
||||
|
||||
@@ -66,6 +85,12 @@ public:
|
||||
* @param data 数据存放指针
|
||||
* @param bytes 指针长度
|
||||
* @return 是否成功(0成功)
|
||||
* Read a certain amount of data from the file
|
||||
* @param data Data storage pointer
|
||||
* @param bytes Pointer length
|
||||
* @return Whether it is successful (0 successful)
|
||||
|
||||
* [AUTO-TRANSLATED:926bf3f0]
|
||||
*/
|
||||
virtual int onRead(void *data, size_t bytes) = 0;
|
||||
|
||||
@@ -74,11 +99,18 @@ public:
|
||||
* @param data 数据指针
|
||||
* @param bytes 数据长度
|
||||
* @return 是否成功(0成功)
|
||||
* Write a certain amount of data to the file
|
||||
* @param data Data pointer
|
||||
* @param bytes Data length
|
||||
* @return Whether it is successful (0 successful)
|
||||
|
||||
* [AUTO-TRANSLATED:dc0abb95]
|
||||
*/
|
||||
virtual int onWrite(const void *data, size_t bytes) = 0;
|
||||
};
|
||||
|
||||
//磁盘MP4文件类
|
||||
// 磁盘MP4文件类 [AUTO-TRANSLATED:e3f5ac07]
|
||||
// Disk MP4 file class
|
||||
class MP4FileDisk : public MP4FileIO {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<MP4FileDisk>;
|
||||
@@ -87,11 +119,19 @@ public:
|
||||
* 打开磁盘文件
|
||||
* @param file 文件路径
|
||||
* @param mode fopen的方式
|
||||
* Open the disk file
|
||||
* @param file File path
|
||||
* @param mode fopen mode
|
||||
|
||||
* [AUTO-TRANSLATED:c3144f10]
|
||||
*/
|
||||
void openFile(const char *file, const char *mode);
|
||||
|
||||
/**
|
||||
* 关闭磁盘文件
|
||||
* Close the disk file
|
||||
|
||||
* [AUTO-TRANSLATED:fc6b4f50]
|
||||
*/
|
||||
void closeFile();
|
||||
|
||||
@@ -111,11 +151,18 @@ public:
|
||||
|
||||
/**
|
||||
* 获取文件大小
|
||||
* Get the file size
|
||||
|
||||
* [AUTO-TRANSLATED:3a2b682a]
|
||||
*/
|
||||
size_t fileSize() const;
|
||||
|
||||
/**
|
||||
* 获取并清空文件缓存
|
||||
* Get and clear the file cache
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:620d5cf6]
|
||||
*/
|
||||
std::string getAndClearMemory();
|
||||
|
||||
|
||||
@@ -25,11 +25,18 @@ public:
|
||||
/**
|
||||
* 打开文件
|
||||
* @param file mp4文件路径
|
||||
* Open file
|
||||
* @param file mp4 file path
|
||||
|
||||
* [AUTO-TRANSLATED:a64c5a6b]
|
||||
*/
|
||||
void openMP4(const std::string &file);
|
||||
|
||||
/**
|
||||
* @brief 关闭 mp4 文件
|
||||
* @brief Close mp4 file
|
||||
|
||||
* [AUTO-TRANSLATED:527865d9]
|
||||
*/
|
||||
void closeMP4();
|
||||
|
||||
@@ -37,6 +44,11 @@ public:
|
||||
* 移动时间轴至某处
|
||||
* @param stamp_ms 预期的时间轴位置,单位毫秒
|
||||
* @return 时间轴位置
|
||||
* Move timeline to a specific location
|
||||
* @param stamp_ms Expected timeline position, in milliseconds
|
||||
* @return Timeline position
|
||||
|
||||
* [AUTO-TRANSLATED:51ce0f6d]
|
||||
*/
|
||||
int64_t seekTo(int64_t stamp_ms);
|
||||
|
||||
@@ -45,6 +57,12 @@ public:
|
||||
* @param keyFrame 是否为关键帧
|
||||
* @param eof 是否文件读取完毕
|
||||
* @return 帧数据,可能为空
|
||||
* Read a frame of data
|
||||
* @param keyFrame Whether it is a key frame
|
||||
* @param eof Whether the file has been read completely
|
||||
* @return Frame data, may be empty
|
||||
|
||||
* [AUTO-TRANSLATED:adf550de]
|
||||
*/
|
||||
Frame::Ptr readFrame(bool &keyFrame, bool &eof);
|
||||
|
||||
@@ -52,12 +70,22 @@ public:
|
||||
* 获取所有Track信息
|
||||
* @param trackReady 是否要求track为就绪状态
|
||||
* @return 所有Track
|
||||
* Get all Track information
|
||||
* @param trackReady Whether to require the track to be ready
|
||||
* @return All Tracks
|
||||
|
||||
* [AUTO-TRANSLATED:c07ad51a]
|
||||
*/
|
||||
std::vector<Track::Ptr> getTracks(bool trackReady) const override;
|
||||
|
||||
/**
|
||||
* 获取文件长度
|
||||
* @return 文件长度,单位毫秒
|
||||
* Get file length
|
||||
* @return File length, in milliseconds
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:dcd865d6]
|
||||
*/
|
||||
uint64_t getDurationMS() const;
|
||||
|
||||
|
||||
@@ -85,21 +85,26 @@ void MP4MuxerInterface::flush() {
|
||||
bool MP4MuxerInterface::inputFrame(const Frame::Ptr &frame) {
|
||||
auto it = _tracks.find(frame->getIndex());
|
||||
if (it == _tracks.end()) {
|
||||
// 该Track不存在或初始化失败
|
||||
// 该Track不存在或初始化失败 [AUTO-TRANSLATED:316597dc]
|
||||
// This Track does not exist or initialization failed
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_started) {
|
||||
// 该逻辑确保含有视频时,第一帧为关键帧
|
||||
// 该逻辑确保含有视频时,第一帧为关键帧 [AUTO-TRANSLATED:04f177fb]
|
||||
// This logic ensures that the first frame is a keyframe when there is video
|
||||
if (_have_video && !frame->keyFrame()) {
|
||||
// 含有视频,但是不是关键帧,那么前面的帧丢弃
|
||||
// 含有视频,但是不是关键帧,那么前面的帧丢弃 [AUTO-TRANSLATED:5f0ba99e]
|
||||
// Contains video, but not a keyframe, then the previous frames are discarded
|
||||
return false;
|
||||
}
|
||||
// 开始写文件
|
||||
// 开始写文件 [AUTO-TRANSLATED:bc3f11e2]
|
||||
// Start writing the file
|
||||
_started = true;
|
||||
}
|
||||
|
||||
// fmp4封装超过一定I帧间隔,强制刷新segment,防止内存上涨
|
||||
// fmp4封装超过一定I帧间隔,强制刷新segment,防止内存上涨 [AUTO-TRANSLATED:0be6ef15]
|
||||
// fmp4 encapsulation exceeds a certain I-frame interval, force refresh segment to prevent memory increase
|
||||
if (frame->getTrackType() == TrackVideo && _mov_writter->fmp4) {
|
||||
if (frame->keyFrame()) {
|
||||
_non_iframe_video_count = 0;
|
||||
@@ -113,12 +118,14 @@ bool MP4MuxerInterface::inputFrame(const Frame::Ptr &frame) {
|
||||
}
|
||||
}
|
||||
|
||||
// mp4文件时间戳需要从0开始
|
||||
// mp4文件时间戳需要从0开始 [AUTO-TRANSLATED:c963b841]
|
||||
// The mp4 file timestamp needs to start from 0
|
||||
auto &track = it->second;
|
||||
switch (frame->getCodecId()) {
|
||||
case CodecH264:
|
||||
case CodecH265: {
|
||||
// 这里的代码逻辑是让SPS、PPS、IDR这些时间戳相同的帧打包到一起当做一个帧处理,
|
||||
// 这里的代码逻辑是让SPS、PPS、IDR这些时间戳相同的帧打包到一起当做一个帧处理, [AUTO-TRANSLATED:edf57c32]
|
||||
// The code logic here is to package frames with the same timestamp, such as SPS, PPS, and IDR, as one frame,
|
||||
track.merger.inputFrame(frame, [this, &track](uint64_t dts, uint64_t pts, const Buffer::Ptr &buffer, bool have_idr) {
|
||||
int64_t dts_out, pts_out;
|
||||
track.stamp.revise(dts, pts, dts_out, pts_out);
|
||||
@@ -190,7 +197,8 @@ bool MP4MuxerInterface::addTrack(const Track::Ptr &track) {
|
||||
_tracks[track->getIndex()].track_id = track_id;
|
||||
}
|
||||
|
||||
// 尝试音视频同步
|
||||
// 尝试音视频同步 [AUTO-TRANSLATED:5f8b8040]
|
||||
// Try audio and video synchronization
|
||||
stampSync();
|
||||
return true;
|
||||
}
|
||||
@@ -222,16 +230,19 @@ void MP4MuxerMemory::resetTracks() {
|
||||
|
||||
bool MP4MuxerMemory::inputFrame(const Frame::Ptr &frame) {
|
||||
if (_init_segment.empty()) {
|
||||
// 尚未生成init segment
|
||||
// 尚未生成init segment [AUTO-TRANSLATED:b4baa65f]
|
||||
// Init segment has not been generated yet
|
||||
return false;
|
||||
}
|
||||
|
||||
// flush切片
|
||||
// flush切片 [AUTO-TRANSLATED:c4358dce]
|
||||
// Flush segment
|
||||
saveSegment();
|
||||
|
||||
auto data = _memory_file->getAndClearMemory();
|
||||
if (!data.empty()) {
|
||||
// 输出切片数据
|
||||
// 输出切片数据 [AUTO-TRANSLATED:4bc994c9]
|
||||
// Output segment data
|
||||
onSegmentData(std::move(data), _last_dst, _key_frame);
|
||||
_key_frame = false;
|
||||
}
|
||||
|
||||
@@ -24,41 +24,65 @@ public:
|
||||
|
||||
/**
|
||||
* 添加已经ready状态的track
|
||||
* Add tracks that are in ready state
|
||||
|
||||
* [AUTO-TRANSLATED:ea4983df]
|
||||
*/
|
||||
bool addTrack(const Track::Ptr &track) override;
|
||||
|
||||
/**
|
||||
* 输入帧
|
||||
* Input frame
|
||||
|
||||
* [AUTO-TRANSLATED:c91b5ec6]
|
||||
*/
|
||||
bool inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
/**
|
||||
* 重置所有track
|
||||
* Reset all tracks
|
||||
|
||||
* [AUTO-TRANSLATED:f203fa3e]
|
||||
*/
|
||||
void resetTracks() override;
|
||||
|
||||
/**
|
||||
* 刷新输出所有frame缓存
|
||||
* Refresh all frame cache output
|
||||
|
||||
* [AUTO-TRANSLATED:adaea568]
|
||||
*/
|
||||
void flush() override;
|
||||
|
||||
/**
|
||||
* 是否包含视频
|
||||
* Whether it contains video
|
||||
|
||||
* [AUTO-TRANSLATED:6d9e1039]
|
||||
*/
|
||||
bool haveVideo() const;
|
||||
|
||||
/**
|
||||
* 保存fmp4分片
|
||||
* Save fmp4 fragment
|
||||
|
||||
* [AUTO-TRANSLATED:7b808759]
|
||||
*/
|
||||
void saveSegment();
|
||||
|
||||
/**
|
||||
* 创建新切片
|
||||
* Create new fragment
|
||||
|
||||
* [AUTO-TRANSLATED:b27545cf]
|
||||
*/
|
||||
void initSegment();
|
||||
|
||||
/**
|
||||
* 获取mp4时长,单位毫秒
|
||||
* Get mp4 duration, in milliseconds
|
||||
|
||||
* [AUTO-TRANSLATED:d87afcfb]
|
||||
*/
|
||||
uint64_t getDuration() const;
|
||||
|
||||
@@ -93,17 +117,27 @@ public:
|
||||
~MP4Muxer() override;
|
||||
/**
|
||||
* 重置所有track
|
||||
* Reset all tracks
|
||||
|
||||
* [AUTO-TRANSLATED:f203fa3e]
|
||||
*/
|
||||
void resetTracks() override;
|
||||
|
||||
/**
|
||||
* 打开mp4
|
||||
* @param file 文件完整路径
|
||||
* Open mp4
|
||||
* @param file Full file path
|
||||
|
||||
* [AUTO-TRANSLATED:416892f4]
|
||||
*/
|
||||
void openMP4(const std::string &file);
|
||||
|
||||
/**
|
||||
* 手动关闭文件(对象析构时会自动关闭)
|
||||
* Manually close the file (it will be closed automatically when the object is destructed)
|
||||
|
||||
* [AUTO-TRANSLATED:9ca68ff9]
|
||||
*/
|
||||
void closeMP4();
|
||||
|
||||
@@ -121,16 +155,25 @@ public:
|
||||
|
||||
/**
|
||||
* 重置所有track
|
||||
* Reset all tracks
|
||||
|
||||
* [AUTO-TRANSLATED:f203fa3e]
|
||||
*/
|
||||
void resetTracks() override;
|
||||
|
||||
/**
|
||||
* 输入帧
|
||||
* Input frame
|
||||
|
||||
* [AUTO-TRANSLATED:c91b5ec6]
|
||||
*/
|
||||
bool inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
/**
|
||||
* 获取fmp4 init segment
|
||||
* Get fmp4 init segment
|
||||
|
||||
* [AUTO-TRANSLATED:6c704ec9]
|
||||
*/
|
||||
const std::string &getInitSegment();
|
||||
|
||||
@@ -140,6 +183,12 @@ protected:
|
||||
* @param std::string 切片内容
|
||||
* @param stamp 切片末尾时间戳
|
||||
* @param key_frame 是否有关键帧
|
||||
* Output fmp4 fragment callback function
|
||||
* @param std::string Fragment content
|
||||
* @param stamp Fragment end timestamp
|
||||
* @param key_frame Whether there is a key frame
|
||||
|
||||
* [AUTO-TRANSLATED:dd742da5]
|
||||
*/
|
||||
virtual void onSegmentData(std::string string, uint64_t stamp, bool key_frame) = 0;
|
||||
|
||||
@@ -173,6 +222,13 @@ protected:
|
||||
* @param std::string 切片内容
|
||||
* @param stamp 切片末尾时间戳
|
||||
* @param key_frame 是否有关键帧
|
||||
* Output fmp4 fragment callback function
|
||||
* @param std::string Fragment content
|
||||
* @param stamp Fragment end timestamp
|
||||
* @param key_frame Whether there is a key frame
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:dd742da5]
|
||||
*/
|
||||
virtual void onSegmentData(std::string string, uint64_t stamp, bool key_frame) = 0;
|
||||
};
|
||||
|
||||
@@ -23,11 +23,13 @@ namespace mediakit {
|
||||
MP4Reader::MP4Reader(const MediaTuple &tuple, const string &file_path,
|
||||
toolkit::EventPoller::Ptr poller) {
|
||||
ProtocolOption option;
|
||||
// 读取mp4文件并流化时,不重复生成mp4/hls文件
|
||||
// 读取mp4文件并流化时,不重复生成mp4/hls文件 [AUTO-TRANSLATED:5d414546]
|
||||
// Read mp4 file and stream it, do not regenerate mp4/hls file repeatedly
|
||||
option.enable_mp4 = false;
|
||||
option.enable_hls = false;
|
||||
option.enable_hls_fmp4 = false;
|
||||
// mp4支持多track
|
||||
// mp4支持多track [AUTO-TRANSLATED:b9688762]
|
||||
// mp4 supports multiple tracks
|
||||
option.max_track = 16;
|
||||
setup(tuple, file_path, option, std::move(poller));
|
||||
}
|
||||
@@ -37,7 +39,8 @@ MP4Reader::MP4Reader(const MediaTuple &tuple, const string &file_path, const Pro
|
||||
}
|
||||
|
||||
void MP4Reader::setup(const MediaTuple &tuple, const std::string &file_path, const ProtocolOption &option, toolkit::EventPoller::Ptr poller) {
|
||||
//读写文件建议放在后台线程
|
||||
// 读写文件建议放在后台线程 [AUTO-TRANSLATED:6f09ef53]
|
||||
// It is recommended to read and write files in the background thread
|
||||
_poller = poller ? std::move(poller) : WorkThreadPool::Instance().getPoller();
|
||||
_file_path = file_path;
|
||||
if (_file_path.empty()) {
|
||||
@@ -69,13 +72,15 @@ void MP4Reader::setup(const MediaTuple &tuple, const std::string &file_path, con
|
||||
_have_video = true;
|
||||
}
|
||||
}
|
||||
//添加完毕所有track,防止单track情况下最大等待3秒
|
||||
// 添加完毕所有track,防止单track情况下最大等待3秒 [AUTO-TRANSLATED:445e3403]
|
||||
// After all tracks are added, prevent the maximum waiting time of 3 seconds in the case of a single track
|
||||
_muxer->addTrackCompleted();
|
||||
}
|
||||
|
||||
bool MP4Reader::readSample() {
|
||||
if (_paused) {
|
||||
//确保暂停时,时间轴不走动
|
||||
// 确保暂停时,时间轴不走动 [AUTO-TRANSLATED:3d38dd31]
|
||||
// Ensure that the timeline does not move when paused
|
||||
_seek_ticker.resetTime();
|
||||
return true;
|
||||
}
|
||||
@@ -95,7 +100,8 @@ bool MP4Reader::readSample() {
|
||||
|
||||
GET_CONFIG(bool, file_repeat, Record::kFileRepeat);
|
||||
if (eof && (file_repeat || _file_repeat)) {
|
||||
//需要从头开始看
|
||||
// 需要从头开始看 [AUTO-TRANSLATED:5b563a35]
|
||||
// Need to start from the beginning
|
||||
seekTo(0);
|
||||
return true;
|
||||
}
|
||||
@@ -126,15 +132,18 @@ void MP4Reader::startReadMP4(uint64_t sample_ms, bool ref_self, bool file_repeat
|
||||
setCurrentStamp(0);
|
||||
auto strong_self = shared_from_this();
|
||||
if (_muxer) {
|
||||
//一直读到所有track就绪为止
|
||||
// 一直读到所有track就绪为止 [AUTO-TRANSLATED:410f9ecc]
|
||||
// Keep reading until all tracks are ready
|
||||
while (!_muxer->isAllTrackReady() && readNextSample());
|
||||
//注册后再切换OwnerPoller
|
||||
// 注册后再切换OwnerPoller [AUTO-TRANSLATED:4a483e23]
|
||||
// Register and then switch OwnerPoller
|
||||
_muxer->setMediaListener(strong_self);
|
||||
}
|
||||
|
||||
auto timer_sec = (sample_ms ? sample_ms : sampleMS) / 1000.0f;
|
||||
|
||||
//启动定时器
|
||||
// 启动定时器 [AUTO-TRANSLATED:0b93ed77]
|
||||
// Start the timer
|
||||
if (ref_self) {
|
||||
_timer = std::make_shared<Timer>(timer_sec, [strong_self]() {
|
||||
lock_guard<recursive_mutex> lck(strong_self->_mtx);
|
||||
@@ -169,13 +178,15 @@ void MP4Reader::setCurrentStamp(uint32_t new_stamp) {
|
||||
_last_dts = new_stamp;
|
||||
_seek_ticker.resetTime();
|
||||
if (old_stamp != new_stamp && _muxer) {
|
||||
//时间轴未拖动时不操作
|
||||
// 时间轴未拖动时不操作 [AUTO-TRANSLATED:c5b53103]
|
||||
// Do not operate when the timeline is not dragged
|
||||
_muxer->setTimeStamp(new_stamp);
|
||||
}
|
||||
}
|
||||
|
||||
bool MP4Reader::seekTo(MediaSource &sender, uint32_t stamp) {
|
||||
//拖动进度条后应该恢复播放
|
||||
// 拖动进度条后应该恢复播放 [AUTO-TRANSLATED:8a6d11f7]
|
||||
// Playback should resume after dragging the progress bar
|
||||
pause(sender, false);
|
||||
TraceL << getOriginUrl(sender) << ",stamp:" << stamp;
|
||||
return seekTo(stamp);
|
||||
@@ -185,7 +196,8 @@ bool MP4Reader::pause(MediaSource &sender, bool pause) {
|
||||
if (_paused == pause) {
|
||||
return true;
|
||||
}
|
||||
//_seek_ticker重新计时,不管是暂停还是seek都不影响总的播放进度
|
||||
// _seek_ticker重新计时,不管是暂停还是seek都不影响总的播放进度 [AUTO-TRANSLATED:96051076]
|
||||
// _seek_ticker restarts the timer, whether it is paused or seek does not affect the total playback progress
|
||||
setCurrentStamp(getCurrentStamp());
|
||||
_paused = pause;
|
||||
TraceL << getOriginUrl(sender) << ",pause:" << pause;
|
||||
@@ -197,9 +209,11 @@ bool MP4Reader::speed(MediaSource &sender, float speed) {
|
||||
WarnL << "播放速度取值范围非法:" << speed;
|
||||
return false;
|
||||
}
|
||||
//_seek_ticker重置,赋值_seek_to
|
||||
// _seek_ticker重置,赋值_seek_to [AUTO-TRANSLATED:b30a3f06]
|
||||
// _seek_ticker reset, assign _seek_to
|
||||
setCurrentStamp(getCurrentStamp());
|
||||
// 设置播放速度后应该恢复播放
|
||||
// 设置播放速度后应该恢复播放 [AUTO-TRANSLATED:851fcde9]
|
||||
// Playback should resume after setting the playback speed
|
||||
_paused = false;
|
||||
if (_speed == speed) {
|
||||
return true;
|
||||
@@ -212,35 +226,42 @@ bool MP4Reader::speed(MediaSource &sender, float speed) {
|
||||
bool MP4Reader::seekTo(uint32_t stamp_seek) {
|
||||
lock_guard<recursive_mutex> lck(_mtx);
|
||||
if (stamp_seek > _demuxer->getDurationMS()) {
|
||||
//超过文件长度
|
||||
// 超过文件长度 [AUTO-TRANSLATED:b4361054]
|
||||
// Exceeds the file length
|
||||
return false;
|
||||
}
|
||||
auto stamp = _demuxer->seekTo(stamp_seek);
|
||||
if (stamp == -1) {
|
||||
//seek失败
|
||||
// seek失败 [AUTO-TRANSLATED:88cc8444]
|
||||
// Seek failed
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_have_video) {
|
||||
//没有视频,不需要搜索关键帧;设置当前时间戳
|
||||
// 没有视频,不需要搜索关键帧;设置当前时间戳 [AUTO-TRANSLATED:82f87f21]
|
||||
// There is no video, no need to search for keyframes; set the current timestamp
|
||||
setCurrentStamp((uint32_t) stamp);
|
||||
return true;
|
||||
}
|
||||
//搜索到下一帧关键帧
|
||||
// 搜索到下一帧关键帧 [AUTO-TRANSLATED:aa2ec689]
|
||||
// Search for the next keyframe
|
||||
bool keyFrame = false;
|
||||
bool eof = false;
|
||||
while (!eof) {
|
||||
auto frame = _demuxer->readFrame(keyFrame, eof);
|
||||
if (!frame) {
|
||||
//文件读完了都未找到下一帧关键帧
|
||||
// 文件读完了都未找到下一帧关键帧 [AUTO-TRANSLATED:49a8d3a7]
|
||||
// The file has been read but the next keyframe has not been found
|
||||
continue;
|
||||
}
|
||||
if (keyFrame || frame->keyFrame() || frame->configFrame()) {
|
||||
//定位到key帧
|
||||
// 定位到key帧 [AUTO-TRANSLATED:0300901d]
|
||||
// Locate to the keyframe
|
||||
if (_muxer) {
|
||||
_muxer->inputFrame(frame);
|
||||
}
|
||||
//设置当前时间戳
|
||||
// 设置当前时间戳 [AUTO-TRANSLATED:88949974]
|
||||
// Set the current timestamp
|
||||
setCurrentStamp(frame->dts());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,13 @@ public:
|
||||
* @param app 应用名
|
||||
* @param stream_id 流id,置空时,只解复用mp4,但是不生成MediaSource
|
||||
* @param file_path 文件路径,如果为空则根据配置文件和上面参数自动生成,否则使用指定的文件
|
||||
* Play an mp4 file and convert it to a MediaSource stream
|
||||
* @param vhost Virtual host
|
||||
* @param app Application name
|
||||
* @param stream_id Stream id, if empty, only demultiplex mp4, but not generate MediaSource
|
||||
* @param file_path File path, if empty, it will be automatically generated according to the configuration file and the above parameters, otherwise use the specified file
|
||||
|
||||
* [AUTO-TRANSLATED:2faeb5db]
|
||||
*/
|
||||
MP4Reader(const MediaTuple &tuple, const std::string &file_path = "", toolkit::EventPoller::Ptr poller = nullptr);
|
||||
|
||||
@@ -37,16 +44,29 @@ public:
|
||||
* @param sample_ms 每次读取文件数据量,单位毫秒,置0时采用配置文件配置
|
||||
* @param ref_self 是否让定时器引用此对象本身,如果无其他对象引用本身,在不循环读文件时,读取文件结束后本对象将自动销毁
|
||||
* @param file_repeat 是否循环读取文件,如果配置文件设置为循环读文件,此参数无效
|
||||
* Start demultiplexing the MP4 file
|
||||
* @param sample_ms The amount of file data read each time, in milliseconds, set to 0 to use the configuration file configuration
|
||||
* @param ref_self Whether to let the timer reference this object itself, if there is no other object referencing itself, when not looping to read the file, after reading the file, this object will be automatically destroyed
|
||||
* @param file_repeat Whether to loop to read the file, if the configuration file is set to loop to read the file, this parameter is invalid
|
||||
|
||||
* [AUTO-TRANSLATED:2164a99d]
|
||||
*/
|
||||
void startReadMP4(uint64_t sample_ms = 0, bool ref_self = true, bool file_repeat = false);
|
||||
|
||||
/**
|
||||
* 停止解复用MP4定时器
|
||||
* Stop demultiplexing the MP4 timer
|
||||
|
||||
* [AUTO-TRANSLATED:45fb1ef7]
|
||||
*/
|
||||
void stopReadMP4();
|
||||
|
||||
/**
|
||||
* 获取mp4解复用器
|
||||
* Get the mp4 demultiplexer
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:4f0dfc29]
|
||||
*/
|
||||
const MP4Demuxer::Ptr& getDemuxer() const;
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ namespace mediakit {
|
||||
|
||||
MP4Recorder::MP4Recorder(const MediaTuple &tuple, const string &path, size_t max_second) {
|
||||
_folder_path = path;
|
||||
/////record 业务逻辑//////
|
||||
// ///record 业务逻辑////// [AUTO-TRANSLATED:2e78931a]
|
||||
// ///record Business Logic//////
|
||||
static_cast<MediaTuple &>(_info) = tuple;
|
||||
_info.folder = path;
|
||||
GET_CONFIG(uint32_t, s_max_second, Protocol::kMP4MaxSecond);
|
||||
@@ -47,7 +48,8 @@ void MP4Recorder::createFile() {
|
||||
auto full_path = _folder_path + date + "/" + file_name;
|
||||
auto full_path_tmp = _folder_path + date + "/." + file_name;
|
||||
|
||||
/////record 业务逻辑//////
|
||||
// ///record 业务逻辑////// [AUTO-TRANSLATED:2e78931a]
|
||||
// ///record Business Logic//////
|
||||
_info.start_time = ::time(NULL);
|
||||
_info.file_name = file_name;
|
||||
_info.file_path = full_path;
|
||||
@@ -59,7 +61,8 @@ void MP4Recorder::createFile() {
|
||||
TraceL << "Open tmp mp4 file: " << full_path_tmp;
|
||||
_muxer->openMP4(full_path_tmp);
|
||||
for (auto &track :_tracks) {
|
||||
//添加track
|
||||
// 添加track [AUTO-TRANSLATED:80ae762a]
|
||||
// Add track
|
||||
_muxer->addTrack(track);
|
||||
}
|
||||
_full_path_tmp = full_path_tmp;
|
||||
@@ -77,23 +80,28 @@ void MP4Recorder::asyncClose() {
|
||||
TraceL << "Start close tmp mp4 file: " << full_path_tmp;
|
||||
WorkThreadPool::Instance().getExecutor()->async([muxer, full_path_tmp, full_path, info]() mutable {
|
||||
info.time_len = muxer->getDuration() / 1000.0f;
|
||||
// 关闭mp4可能非常耗时,所以要放在后台线程执行
|
||||
// 关闭mp4可能非常耗时,所以要放在后台线程执行 [AUTO-TRANSLATED:a7378a11]
|
||||
// Closing mp4 can be very time-consuming, so it should be executed in the background thread
|
||||
TraceL << "Closing tmp mp4 file: " << full_path_tmp;
|
||||
muxer->closeMP4();
|
||||
TraceL << "Closed tmp mp4 file: " << full_path_tmp;
|
||||
if (!full_path_tmp.empty()) {
|
||||
// 获取文件大小
|
||||
// 获取文件大小 [AUTO-TRANSLATED:7b90eb41]
|
||||
// Get file size
|
||||
info.file_size = File::fileSize(full_path_tmp);
|
||||
if (info.file_size < 1024) {
|
||||
// 录像文件太小,删除之
|
||||
// 录像文件太小,删除之 [AUTO-TRANSLATED:923d27c3]
|
||||
// The recording file is too small, delete it
|
||||
File::delete_file(full_path_tmp);
|
||||
return;
|
||||
}
|
||||
// 临时文件名改成正式文件名,防止mp4未完成时被访问
|
||||
// 临时文件名改成正式文件名,防止mp4未完成时被访问 [AUTO-TRANSLATED:541a6f00]
|
||||
// Change the temporary file name to the official file name to prevent access to the mp4 before it is completed
|
||||
rename(full_path_tmp.data(), full_path.data());
|
||||
}
|
||||
TraceL << "Emit mp4 record event: " << full_path;
|
||||
//触发mp4录制切片生成事件
|
||||
// 触发mp4录制切片生成事件 [AUTO-TRANSLATED:9959dcd4]
|
||||
// Trigger mp4 recording slice generation event
|
||||
NOTICE_EMIT(BroadcastRecordMP4Args, Broadcast::kBroadcastRecordMP4, info);
|
||||
});
|
||||
}
|
||||
@@ -113,9 +121,11 @@ void MP4Recorder::flush() {
|
||||
|
||||
bool MP4Recorder::inputFrame(const Frame::Ptr &frame) {
|
||||
if (!(_have_video && frame->getTrackType() == TrackAudio)) {
|
||||
//如果有视频且输入的是音频,那么应该忽略切片逻辑
|
||||
// 如果有视频且输入的是音频,那么应该忽略切片逻辑 [AUTO-TRANSLATED:fbb15d93]
|
||||
// If there is video and the input is audio, then the slice logic should be ignored
|
||||
if (_last_dts == 0 || _last_dts > frame->dts()) {
|
||||
//b帧情况下dts时间戳可能回退
|
||||
// b帧情况下dts时间戳可能回退 [AUTO-TRANSLATED:1de38f77]
|
||||
// In the case of b-frames, the dts timestamp may regress
|
||||
_last_dts = MAX(frame->dts(), _last_dts);
|
||||
}
|
||||
auto duration = 5u; // 默认至少一帧5ms
|
||||
@@ -123,24 +133,30 @@ bool MP4Recorder::inputFrame(const Frame::Ptr &frame) {
|
||||
duration = MAX(duration, frame->dts() - _last_dts);
|
||||
}
|
||||
if (!_muxer || ((duration > _max_second * 1000) && (!_have_video || (_have_video && frame->keyFrame())))) {
|
||||
//成立条件
|
||||
// 1、_muxer为空
|
||||
// 2、到了切片时间,并且只有音频
|
||||
// 3、到了切片时间,有视频并且遇到视频的关键帧
|
||||
// 成立条件 [AUTO-TRANSLATED:8c9c6083]
|
||||
// Conditions for establishment
|
||||
// 1、_muxer为空 [AUTO-TRANSLATED:fa236097]
|
||||
// 1. _muxer is empty
|
||||
// 2、到了切片时间,并且只有音频 [AUTO-TRANSLATED:212e9d23]
|
||||
// 2. It's time to slice, and there is only audio
|
||||
// 3、到了切片时间,有视频并且遇到视频的关键帧 [AUTO-TRANSLATED:fa4a71ad]
|
||||
// 3. It's time to slice, there is video and a video keyframe is encountered
|
||||
_last_dts = 0;
|
||||
createFile();
|
||||
}
|
||||
}
|
||||
|
||||
if (_muxer) {
|
||||
//生成mp4文件
|
||||
// 生成mp4文件 [AUTO-TRANSLATED:76a8d77c]
|
||||
// Generate mp4 file
|
||||
return _muxer->inputFrame(frame);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MP4Recorder::addTrack(const Track::Ptr &track) {
|
||||
//保存所有的track,为创建MP4MuxerFile做准备
|
||||
// 保存所有的track,为创建MP4MuxerFile做准备 [AUTO-TRANSLATED:815c2486]
|
||||
// Save all tracks in preparation for creating MP4MuxerFile
|
||||
_tracks.emplace_back(track);
|
||||
if (track->getTrackType() == TrackVideo) {
|
||||
_have_video = true;
|
||||
|
||||
@@ -31,21 +31,34 @@ public:
|
||||
|
||||
/**
|
||||
* 重置所有Track
|
||||
* Reset all Tracks
|
||||
|
||||
* [AUTO-TRANSLATED:8dd80826]
|
||||
*/
|
||||
void resetTracks() override;
|
||||
|
||||
/**
|
||||
* 输入frame
|
||||
* Input frame
|
||||
|
||||
* [AUTO-TRANSLATED:3722ea0e]
|
||||
*/
|
||||
bool inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
/**
|
||||
* 刷新输出所有frame缓存
|
||||
* Refresh output all frame cache
|
||||
|
||||
* [AUTO-TRANSLATED:adaea568]
|
||||
*/
|
||||
void flush() override;
|
||||
|
||||
/**
|
||||
* 添加ready状态的track
|
||||
* Add ready state track
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:2d8138b3]
|
||||
*/
|
||||
bool addTrack(const Track::Ptr & track) override;
|
||||
|
||||
|
||||
@@ -54,10 +54,12 @@ bool MpegMuxer::inputFrame(const Frame::Ptr &frame) {
|
||||
switch (frame->getCodecId()) {
|
||||
case CodecH264:
|
||||
case CodecH265: {
|
||||
// 这里的代码逻辑是让SPS、PPS、IDR这些时间戳相同的帧打包到一起当做一个帧处理,
|
||||
// 这里的代码逻辑是让SPS、PPS、IDR这些时间戳相同的帧打包到一起当做一个帧处理, [AUTO-TRANSLATED:edf57c32]
|
||||
// The code logic here is to package frames with the same timestamp, such as SPS, PPS, and IDR, together as one frame.
|
||||
return track.merger.inputFrame(frame, [this, &track](uint64_t dts, uint64_t pts, const Buffer::Ptr &buffer, bool have_idr) {
|
||||
_key_pos = have_idr;
|
||||
// 取视频时间戳为TS的时间戳
|
||||
// 取视频时间戳为TS的时间戳 [AUTO-TRANSLATED:5ff7796d]
|
||||
// Take the video timestamp as the TS timestamp.
|
||||
_timestamp = dts;
|
||||
_max_cache_size = 512 + 1.2 * buffer->size();
|
||||
mpeg_muxer_input((::mpeg_muxer_t *)_context, track.track_id, have_idr ? 0x0001 : 0, pts * 90LL, dts * 90LL, buffer->data(), buffer->size());
|
||||
@@ -71,7 +73,8 @@ bool MpegMuxer::inputFrame(const Frame::Ptr &frame) {
|
||||
|
||||
default: {
|
||||
if (!_have_video) {
|
||||
// 没有视频时,才以音频时间戳为TS的时间戳
|
||||
// 没有视频时,才以音频时间戳为TS的时间戳 [AUTO-TRANSLATED:17cef4f7]
|
||||
// When there is no video, use the audio timestamp as the TS timestamp.
|
||||
_timestamp = frame->dts();
|
||||
}
|
||||
|
||||
@@ -89,7 +92,8 @@ bool MpegMuxer::inputFrame(const Frame::Ptr &frame) {
|
||||
|
||||
void MpegMuxer::resetTracks() {
|
||||
_have_video = false;
|
||||
//通知片段中断
|
||||
// 通知片段中断 [AUTO-TRANSLATED:ed3d87ba]
|
||||
// Notify fragment interruption.
|
||||
onWrite(nullptr, _timestamp, false);
|
||||
releaseContext();
|
||||
createContext();
|
||||
@@ -113,7 +117,8 @@ void MpegMuxer::createContext() {
|
||||
},
|
||||
/*free*/
|
||||
[](void *param, void *packet) {
|
||||
//什么也不做
|
||||
// 什么也不做 [AUTO-TRANSLATED:e2f8de75]
|
||||
// Do nothing.
|
||||
},
|
||||
/*wtite*/
|
||||
[](void *param, int stream, void *packet, size_t bytes) {
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
#include "Util/ResourcePool.h"
|
||||
namespace mediakit {
|
||||
|
||||
//该类用于产生MPEG-TS/MPEG-PS
|
||||
// 该类用于产生MPEG-TS/MPEG-PS [AUTO-TRANSLATED:267efc85]
|
||||
// This class is used to generate MPEG-TS/MPEG-PS
|
||||
class MpegMuxer : public MediaSinkInterface {
|
||||
public:
|
||||
MpegMuxer(bool is_ps = false);
|
||||
@@ -30,21 +31,33 @@ public:
|
||||
|
||||
/**
|
||||
* 添加音视频轨道
|
||||
* Add audio and video tracks
|
||||
|
||||
* [AUTO-TRANSLATED:7b0c1d64]
|
||||
*/
|
||||
bool addTrack(const Track::Ptr &track) override;
|
||||
|
||||
/**
|
||||
* 重置音视频轨道
|
||||
* Reset audio and video tracks
|
||||
|
||||
* [AUTO-TRANSLATED:6eb1b742]
|
||||
*/
|
||||
void resetTracks() override;
|
||||
|
||||
/**
|
||||
* 输入帧数据
|
||||
* Input frame data
|
||||
|
||||
* [AUTO-TRANSLATED:d13bc7f2]
|
||||
*/
|
||||
bool inputFrame(const Frame::Ptr &frame) override;
|
||||
|
||||
/**
|
||||
* 刷新输出所有frame缓存
|
||||
* Flush all frame buffers in the output
|
||||
|
||||
* [AUTO-TRANSLATED:adaea568]
|
||||
*/
|
||||
void flush() override;
|
||||
|
||||
@@ -54,6 +67,13 @@ protected:
|
||||
* @param buffer ts/ps数据包
|
||||
* @param timestamp 时间戳,单位毫秒
|
||||
* @param key_pos 是否为关键帧的第一个ts/ps包,用于确保ts切片第一帧为关键帧
|
||||
* Callback for outputting ts/ps data
|
||||
* @param buffer ts/ps data packet
|
||||
* @param timestamp Timestamp, in milliseconds
|
||||
* @param key_pos Whether it is the first ts/ps packet of a key frame, used to ensure that the first frame of the ts slice is a key frame
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:dda8ed40]
|
||||
*/
|
||||
virtual void onWrite(std::shared_ptr<toolkit::Buffer> buffer, uint64_t timestamp, bool key_pos) = 0;
|
||||
|
||||
|
||||
@@ -42,15 +42,20 @@ public:
|
||||
class Recorder{
|
||||
public:
|
||||
typedef enum {
|
||||
// 录制hls
|
||||
// 录制hls [AUTO-TRANSLATED:24a50dff]
|
||||
// Record hls
|
||||
type_hls = 0,
|
||||
// 录制MP4
|
||||
// 录制MP4 [AUTO-TRANSLATED:03d73bb7]
|
||||
// Record MP4
|
||||
type_mp4 = 1,
|
||||
// 录制hls.fmp4
|
||||
// 录制hls.fmp4 [AUTO-TRANSLATED:031cf6f1]
|
||||
// Record hls.fmp4
|
||||
type_hls_fmp4 = 2,
|
||||
// fmp4直播
|
||||
// fmp4直播 [AUTO-TRANSLATED:ac37a248]
|
||||
// fmp4 live
|
||||
type_fmp4 = 3,
|
||||
// ts直播
|
||||
// ts直播 [AUTO-TRANSLATED:b062b43a]
|
||||
// ts live
|
||||
type_ts = 4,
|
||||
} type;
|
||||
|
||||
@@ -62,6 +67,15 @@ public:
|
||||
* @param stream_id 流id
|
||||
* @param customized_path 录像文件保存自定义根目录,为空则采用配置文件设置
|
||||
* @return 录制文件绝对路径
|
||||
* Get the absolute path of the recording file
|
||||
* @param type hls or MP4 recording
|
||||
* @param vhost virtual host
|
||||
* @param app application name
|
||||
* @param stream_id stream id
|
||||
* @param customized_path custom root directory for saving recording files, empty means using configuration file settings
|
||||
* @return absolute path of the recording file
|
||||
|
||||
* [AUTO-TRANSLATED:2fd57fcd]
|
||||
*/
|
||||
static std::string getRecordPath(type type, const MediaTuple& tuple, const std::string &customized_path = "");
|
||||
|
||||
@@ -74,6 +88,17 @@ public:
|
||||
* @param customized_path 录像文件保存自定义根目录,为空则采用配置文件设置
|
||||
* @param max_second mp4录制最大切片时间,单位秒,置0则采用配置文件配置
|
||||
* @return 对象指针,可能为nullptr
|
||||
* Create a recorder object
|
||||
* @param type hls or MP4 recording
|
||||
* @param vhost virtual host
|
||||
* @param app application name
|
||||
* @param stream_id stream id
|
||||
* @param customized_path custom root directory for saving recording files, empty means using configuration file settings
|
||||
* @param max_second maximum slice time for mp4 recording, in seconds, 0 means using configuration file settings
|
||||
* @return object pointer, may be nullptr
|
||||
|
||||
|
||||
* [AUTO-TRANSLATED:e0b6e43b]
|
||||
*/
|
||||
static std::shared_ptr<MediaSinkInterface> createRecorder(type type, const MediaTuple& tuple, const ProtocolOption &option);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user