MP4录制支持多track

This commit is contained in:
xia-chu 2023-12-09 17:13:04 +08:00
parent db86e4a76c
commit 5b95cd1f5a
2 changed files with 46 additions and 70 deletions

View File

@ -60,7 +60,7 @@ bool MP4MuxerInterface::haveVideo() const {
uint64_t MP4MuxerInterface::getDuration() const { uint64_t MP4MuxerInterface::getDuration() const {
uint64_t ret = 0; uint64_t ret = 0;
for (auto &pr : _codec_to_trackid) { for (auto &pr : _tracks) {
if (pr.second.stamp.getRelativeStamp() > (int64_t)ret) { if (pr.second.stamp.getRelativeStamp() > (int64_t)ret) {
ret = pr.second.stamp.getRelativeStamp(); ret = pr.second.stamp.getRelativeStamp();
} }
@ -72,61 +72,50 @@ void MP4MuxerInterface::resetTracks() {
_started = false; _started = false;
_have_video = false; _have_video = false;
_mov_writter = nullptr; _mov_writter = nullptr;
_frame_merger.clear(); _tracks.clear();
_codec_to_trackid.clear();
} }
void MP4MuxerInterface::flush() { void MP4MuxerInterface::flush() {
_frame_merger.flush(); for (auto &pr : _tracks) {
pr.second.merger.flush();
}
} }
bool MP4MuxerInterface::inputFrame(const Frame::Ptr &frame) { bool MP4MuxerInterface::inputFrame(const Frame::Ptr &frame) {
auto it = _codec_to_trackid.find(frame->getCodecId()); auto it = _tracks.find(frame->getIndex());
if (it == _codec_to_trackid.end()) { if (it == _tracks.end()) {
//该Track不存在或初始化失败 // 该Track不存在或初始化失败
return false; return false;
} }
if (!_started) { if (!_started) {
//该逻辑确保含有视频时,第一帧为关键帧 // 该逻辑确保含有视频时,第一帧为关键帧
if (_have_video && !frame->keyFrame()) { if (_have_video && !frame->keyFrame()) {
//含有视频,但是不是关键帧,那么前面的帧丢弃 // 含有视频,但是不是关键帧,那么前面的帧丢弃
return false; return false;
} }
//开始写文件 // 开始写文件
_started = true; _started = true;
} }
//mp4文件时间戳需要从0开始 // mp4文件时间戳需要从0开始
auto &track_info = it->second; auto &track = it->second;
switch (frame->getCodecId()) { switch (frame->getCodecId()) {
case CodecH264: case CodecH264:
case CodecH265: { case CodecH265: {
//这里的代码逻辑是让SPS、PPS、IDR这些时间戳相同的帧打包到一起当做一个帧处理 // 这里的代码逻辑是让SPS、PPS、IDR这些时间戳相同的帧打包到一起当做一个帧处理
_frame_merger.inputFrame(frame, [this, &track_info](uint64_t dts, uint64_t pts, const Buffer::Ptr &buffer, bool have_idr) { track.merger.inputFrame(frame, [&](uint64_t dts, uint64_t pts, const Buffer::Ptr &buffer, bool have_idr) {
int64_t dts_out, pts_out; int64_t dts_out, pts_out;
track_info.stamp.revise(dts, pts, dts_out, pts_out); track.stamp.revise(dts, pts, dts_out, pts_out);
mp4_writer_write(_mov_writter.get(), mp4_writer_write(_mov_writter.get(), track.track_id, buffer->data(), buffer->size(), pts_out, dts_out, have_idr ? MOV_AV_FLAG_KEYFREAME : 0);
track_info.track_id,
buffer->data(),
buffer->size(),
pts_out,
dts_out,
have_idr ? MOV_AV_FLAG_KEYFREAME : 0);
}); });
break; break;
} }
default: { default: {
int64_t dts_out, pts_out; int64_t dts_out, pts_out;
track_info.stamp.revise(frame->dts(), frame->pts(), dts_out, pts_out); track.stamp.revise(frame->dts(), frame->pts(), dts_out, pts_out);
mp4_writer_write(_mov_writter.get(), mp4_writer_write(_mov_writter.get(), track.track_id, frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(), pts_out, dts_out, frame->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0);
track_info.track_id,
frame->data() + frame->prefixSize(),
frame->size() - frame->prefixSize(),
pts_out,
dts_out,
frame->keyFrame() ? MOV_AV_FLAG_KEYFREAME : 0);
break; break;
} }
} }
@ -134,23 +123,14 @@ bool MP4MuxerInterface::inputFrame(const Frame::Ptr &frame) {
} }
void MP4MuxerInterface::stampSync() { void MP4MuxerInterface::stampSync() {
if (_codec_to_trackid.size() < 2) { Stamp *first = nullptr;
return; for (auto &pr : _tracks) {
} if (!first) {
first = &pr.second.stamp;
Stamp *audio = nullptr, *video = nullptr; } else {
for(auto &pr : _codec_to_trackid){ pr.second.stamp.syncTo(*first);
switch (getTrackType((CodecId) pr.first)){
case TrackAudio : audio = &pr.second.stamp; break;
case TrackVideo : video = &pr.second.stamp; break;
default : break;
} }
} }
if (audio && video) {
//音频时间戳同步于视频,因为音频时间戳被修改后不影响播放
audio->syncTo(*video);
}
} }
bool MP4MuxerInterface::addTrack(const Track::Ptr &track) { bool MP4MuxerInterface::addTrack(const Track::Ptr &track) {
@ -164,7 +144,7 @@ bool MP4MuxerInterface::addTrack(const Track::Ptr &track) {
} }
if (!track->ready()) { if (!track->ready()) {
WarnL << "Track[" << track->getCodecName() << "] unready"; WarnL << "Track " << track->getCodecName() << " unready";
return false; return false;
} }
@ -175,36 +155,26 @@ bool MP4MuxerInterface::addTrack(const Track::Ptr &track) {
auto extra_size = extra ? extra->size() : 0; auto extra_size = extra ? extra->size() : 0;
if (track->getTrackType() == TrackVideo) { if (track->getTrackType() == TrackVideo) {
auto video_track = dynamic_pointer_cast<VideoTrack>(track); auto video_track = dynamic_pointer_cast<VideoTrack>(track);
if (!video_track) { CHECK(video_track);
WarnL << "不是VideoTrack";
return false;
}
auto track_id = mp4_writer_add_video(_mov_writter.get(), mp4_object, video_track->getVideoWidth(), video_track->getVideoHeight(), extra_data, extra_size); auto track_id = mp4_writer_add_video(_mov_writter.get(), mp4_object, video_track->getVideoWidth(), video_track->getVideoHeight(), extra_data, extra_size);
if (track_id < 0) { if (track_id < 0) {
WarnL << "添加Video Track失败:" << video_track->getCodecName(); WarnL << "mp4_writer_add_video failed: " << video_track->getCodecName();
return false; return false;
} }
_codec_to_trackid[track->getCodecId()].track_id = track_id; _tracks[track->getIndex()].track_id = track_id;
_have_video = true; _have_video = true;
} else if (track->getTrackType() == TrackAudio) { } else if (track->getTrackType() == TrackAudio) {
auto audio_track = dynamic_pointer_cast<AudioTrack>(track); auto audio_track = dynamic_pointer_cast<AudioTrack>(track);
if (!audio_track) { CHECK(audio_track);
WarnL << "不是音频Track:" << track->getCodecName(); auto track_id = mp4_writer_add_audio(_mov_writter.get(), mp4_object, audio_track->getAudioChannel(), audio_track->getAudioSampleBit() * audio_track->getAudioChannel(), audio_track->getAudioSampleRate(), extra_data, extra_size);
return false;
}
auto track_id = mp4_writer_add_audio(_mov_writter.get(), mp4_object, audio_track->getAudioChannel(),
audio_track->getAudioSampleBit() * audio_track->getAudioChannel(),
audio_track->getAudioSampleRate(), extra_data, extra_size);
if (track_id < 0) { if (track_id < 0) {
WarnL << "添加Track[" << track->getCodecName() << "]失败:" << track_id; WarnL << "mp4_writer_add_audio failed: " << audio_track->getCodecName();
return false; return false;
} }
_codec_to_trackid[track->getCodecId()].track_id = track_id; _tracks[track->getIndex()].track_id = track_id;
} }
//尝试音视频同步 // 尝试音视频同步
stampSync(); stampSync();
return true; return true;
} }
@ -236,7 +206,7 @@ void MP4MuxerMemory::resetTracks() {
bool MP4MuxerMemory::inputFrame(const Frame::Ptr &frame) { bool MP4MuxerMemory::inputFrame(const Frame::Ptr &frame) {
if (_init_segment.empty()) { if (_init_segment.empty()) {
//尚未生成init segment // 尚未生成init segment
return false; return false;
} }
@ -259,5 +229,5 @@ bool MP4MuxerMemory::inputFrame(const Frame::Ptr &frame) {
return MP4MuxerInterface::inputFrame(frame); return MP4MuxerInterface::inputFrame(frame);
} }
}//namespace mediakit } // namespace mediakit
#endif //defined(ENABLE_MP4) #endif // defined(ENABLE_MP4)

View File

@ -72,12 +72,18 @@ private:
bool _started = false; bool _started = false;
bool _have_video = false; bool _have_video = false;
MP4FileIO::Writer _mov_writter; MP4FileIO::Writer _mov_writter;
struct track_info {
class FrameMergerImp : public FrameMerger {
public:
FrameMergerImp() : FrameMerger(FrameMerger::mp4_nal_size) {}
};
struct MP4Track {
int track_id = -1; int track_id = -1;
Stamp stamp; Stamp stamp;
FrameMergerImp merger;
}; };
std::unordered_map<int, track_info> _codec_to_trackid; std::unordered_map<int, MP4Track> _tracks;
FrameMerger _frame_merger { FrameMerger::mp4_nal_size };
}; };
class MP4Muxer : public MP4MuxerInterface{ class MP4Muxer : public MP4MuxerInterface{