概述
本文主要记录和分析ffmpeg项目中写文件的流程和代码。(比如录制存储为mp4,ts之类的)
流程
avformat_write_header()
av_write_frame()
av_write_trailer()
代码分析
1.avformat_write_header()
2.av_write_frame()
3.av_write_trailer()
本函数位于libavformat/mux.c。
流程(补一个图)
具体代码如下
int av_write_trailer(AVFormatContext *s)
{
int ret, i;
for (;; ) { //将剩下未写完的包写入文件
AVPacket pkt;
ret = interleave_packet(s, &pkt, NULL, 1);
if (ret < 0)
goto fail;
if (!ret)
break;
ret = write_packet(s, &pkt);
if (ret >= 0)
s->streams[pkt.stream_index]->nb_frames++;
av_free_packet(&pkt);
if (ret < 0)
goto fail;
if(s->pb && s->pb->error)
goto fail;
}
fail:
if (s->oformat->write_trailer) //写文件尾
if (ret >= 0) {
ret = s->oformat->write_trailer(s);
} else {
s->oformat->write_trailer(s);
}
if (s->pb)
avio_flush(s->pb);
if (ret == 0)
ret = s->pb ? s->pb->error : 0;
for (i = 0; i < s->nb_streams; i++) {
av_freep(&s->streams[i]->priv_data);
av_freep(&s->streams[i]->index_entries);
}
if (s->oformat->priv_class)
av_opt_free(s->priv_data);
av_freep(&s->priv_data);
return ret;
}
(1)循环调用interleave_packet()以及write_packet(),将还未输出的AVPacket输出出来。
(2)调用AVOutputFormat的write_trailer(),输出文件尾。
来看一下AVOutputFormat->write_trailer(),结构体定义如下:
AVOutputFormat ff_mp4_muxer = {
.name = "mp4",
.long_name = NULL_IF_CONFIG_SMALL("MP4 (MPEG-4 Part 14)"),
.mime_type = "video/mp4",
.extensions = "mp4",
.priv_data_size = sizeof(MOVMuxContext),
.audio_codec = AV_CODEC_ID_AAC,
.video_codec = CONFIG_LIBX264_ENCODER ?
AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4,
.write_header = mov_write_header,
.write_packet = mov_write_packet,
.write_trailer = mov_write_trailer,
.flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE,
.codec_tag = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 },
.priv_class = &mp4_muxer_class,
};
所以我们的关注点到了mov_write_trailer()这个函数,位于libavformat/movenc.c文件。来看下代码中都做了啥:
static int mov_write_trailer(AVFormatContext *s)
{
MOVMuxContext *mov = s->priv_data;
AVIOContext *pb = s->pb;
int res = 0;
int i;
int64_t moov_pos;
/*
* Before actually writing the trailer, make sure that there are no
* dangling subtitles, that need a terminating sample.
*/
for (i = 0; i < mov->nb_streams; i++) {
MOVTrack *trk = &mov->tracks[i];
if (trk->enc->codec_id == AV_CODEC_ID_MOV_TEXT &&
!trk->last_sample_is_subtitle_end) {
mov_write_subtitle_end_packet(s, i, trk->track_duration);
trk->last_sample_is_subtitle_end = 1;
}
}
// If there were no chapters when the header was written, but there
// are chapters now, write them in the trailer. This only works
// when we are not doing fragments.
if (!mov->chapter_track && !(mov->flags & FF_MOV_FLAG_FRAGMENT)) {
if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) {
mov->chapter_track = mov->nb_streams++;
if ((res = mov_create_chapter_track(s, mov->chapter_track)) < 0)
goto error;
}
}
if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) { //存为视频文件
moov_pos = avio_tell(pb);
/* Write size of mdat tag */
if (mov->mdat_size + 8 <= UINT32_MAX) {
avio_seek(pb, mov->mdat_pos, SEEK_SET);
avio_wb32(pb, mov->mdat_size + 8);
} else {
/* overwrite 'wide' placeholder atom */
avio_seek(pb, mov->mdat_pos - 8, SEEK_SET);
/* special value: real atom size will be 64 bit value after
* tag field */
avio_wb32(pb, 1);
ffio_wfourcc(pb, "mdat");
avio_wb64(pb, mov->mdat_size + 16);
}
avio_seek(pb, mov->reserved_moov_size > 0 ? mov->reserved_header_pos : moov_pos, SEEK_SET);
if (mov->flags & FF_MOV_FLAG_FASTSTART) { //如果faststart,将moov box移到文件前部分
av_log(s, AV_LOG_INFO, "Starting second pass: moving the moov atom to the beginning of the file\n");
res = shift_data(s);
if (res == 0) {
avio_seek(pb, mov->reserved_header_pos, SEEK_SET);
if ((res = mov_write_moov_tag(pb, mov, s)) < 0)
goto error; //mov_write_moov_tag写moovbox内容
}
} else if (mov->reserved_moov_size > 0) {
int64_t size;
if ((res = mov_write_moov_tag(pb, mov, s)) < 0)
goto error; //mov_write_moov_tag写moovbox内容
size = mov->reserved_moov_size - (avio_tell(pb) - mov->reserved_header_pos);
if (size < 8){
av_log(s, AV_LOG_ERROR, "reserved_moov_size is too small, needed %"PRId64" additional\n", 8-size);
res = AVERROR(EINVAL);
goto error;
}
avio_wb32(pb, size);
ffio_wfourcc(pb, "free");
ffio_fill(pb, 0, size - 8);
avio_seek(pb, moov_pos, SEEK_SET);
} else {
if ((res = mov_write_moov_tag(pb, mov, s)) < 0)
goto error; //mov_write_moov_tag写moovbox内容
}
res = 0;
} else { //存为fmp4流式文件
mov_auto_flush_fragment(s);
for (i = 0; i < mov->nb_streams; i++)
mov->tracks[i].data_offset = 0;
if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX) {
av_log(s, AV_LOG_INFO, "Starting second pass: inserting sidx atoms\n");
res = shift_data(s);
if (res == 0) {
int64_t end = avio_tell(pb);
avio_seek(pb, mov->reserved_header_pos, SEEK_SET);
mov_write_sidx_tags(pb, mov, -1, 0);
avio_seek(pb, end, SEEK_SET);
mov_write_mfra_tag(pb, mov);
}
} else {
mov_write_mfra_tag(pb, mov);
}
}
error:
mov_free(s);
return res;
}
这里不深入介绍moov box的格式和书写,具体可以参考mp4格式,fmp4格式及其与mp4/ts的对比 这两篇。
mov_write_trailer()主要完成了moov box的写入,对于点播的mp4文件,如果指定了要将moov移到前面,则在文件中进行内容交换。
参考
- 1.FFmpeg源代码简单分析:av_write_trailer() 文章主要分析了flv文件尾写入