FFmpeg provides muxer encapsulation and demuxer unpacking of audio and video in libavformat module. The muxer package file includes avformat_write_header(),av_write_frame() and av_write_trailer(). This paper mainly discusses AV_ write_ How to write the trail function to the end of the file and finally complete the encapsulation of multimedia files.
About avformat_write_header() and av_write_frame() to view the previous two articles:
FFmpeg source code analysis: write file header avformat_write_header()
FFmpeg source code analysis: write audio and video frames av_write_frame()
1,av_write_trailer
av_ write_ The header file declaration of the tracker () is located in libavformat / avformat h. The description is as follows:
/** * Write the stream trailer to an output media file and free the * file private data. * * May only be called after a successful call to avformat_write_header. * * @param s media file handle * @return 0 if OK, AVERROR_xxx on error */ int av_write_trailer(AVFormatContext *s);
The Chinese translation is roughly as follows:
Write the tail of the audio and video stream to the output media file, and release the private data of the file. Only in avformat_ write_ This function cannot be called until the header() call is successful.
Next, let's look at AV_ write_ The implementation of the tracker () function is located in libavformat / mux c:
int av_write_trailer(AVFormatContext *s) { int i, ret1, ret = 0; AVPacket *pkt = s->internal->pkt; av_packet_unref(pkt); // If there is AVBSFContext, write the bitstream filter packet finally for (i = 0; i < s->nb_streams; i++) { if (s->streams[i]->internal->bsfc) { ret1 = write_packets_from_bsfs(s, s->streams[i], pkt, 1/*interleaved*/); if (ret1 < 0) av_packet_unref(pkt); if (ret >= 0) ret = ret1; } } // Fill empty packets ret1 = interleaved_write_packet(s, NULL, 1); if (ret >= 0) ret = ret1; if (s->oformat->write_trailer) { // Write marker flag of avio if (!(s->oformat->flags & AVFMT_NOFILE) && s->pb) avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER); // Call AVOutputFormat to write the end of the file if (ret >= 0) { ret = s->oformat->write_trailer(s); } else { s->oformat->write_trailer(s); } } // Free muxer resources deinit_muxer(s); if (s->pb) avio_flush(s->pb); if (ret == 0) ret = s->pb ? s->pb->error : 0; // Release priv_data and index_entries 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; }
According to the source code, there are six steps to write the end of the media file:
- If there is AVBSFContext, write the bitstream filter packet finally;
- Fill in empty data packets;
- AVIOContext, if there is a flag for avio context to be written;
- Call AVOutputFormat to write the end of the file;
- Release muxer resources;
- Release priv_data and index_entries;
Write in step 1_ packets_ from_ Bsfs and interleaved in step 2_ write_ Packet in av_write_frame() The article has an introduction. Let's mainly analyze the processing of steps 3, 4 and 5.
2,avio_write_marker
avio_ write_ The marker is located in aviobuf C file, the function is implemented as follows:
void avio_write_marker(AVIOContext *s, int64_t time, enum AVIODataMarkerType type) { if (type == AVIO_DATA_MARKER_FLUSH_POINT) { if (s->buf_ptr - s->buffer >= s->min_packet_size) avio_flush(s); return; } if (!s->write_data_type) return; if (type == AVIO_DATA_MARKER_BOUNDARY_POINT && s->ignore_boundary_point) type = AVIO_DATA_MARKER_UNKNOWN; if (type == AVIO_DATA_MARKER_UNKNOWN && (s->current_type != AVIO_DATA_MARKER_HEADER && s->current_type != AVIO_DATA_MARKER_TRAILER)) return; // Judge whether the marker of header and tracker are the same switch (type) { case AVIO_DATA_MARKER_HEADER: case AVIO_DATA_MARKER_TRAILER: if (type == s->current_type) return; break; } // If we've reached here, we have a new, noteworthy marker. // Flush the previous data and mark the start of the new data. avio_flush(s); s->current_type = type; s->last_time = time; }
3,s->oformat->write_trailer
Take the packaging format of mp4 as an example, which is located in libavformat / movenc C file, the corresponding AVOutputFormat is as follows:
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, .init = mov_init, .write_header = mov_write_header, .write_packet = mov_write_packet, .write_trailer = mov_write_trailer, .deinit = mov_free, .flags = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH | AVFMT_TS_NEGATIVE, .codec_tag = mp4_codec_tags_list, .check_bitstream = mov_check_bitstream, .priv_class = &mp4_muxer_class, };
At this point, write_ The tracer function pointer points to mov_write_trailer(), let's take a look at the function implementation:
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; // Determine whether to overwrite extradata if (mov->need_rewrite_extradata) { for (i = 0; i < s->nb_streams; i++) { MOVTrack *track = &mov->tracks[i]; AVCodecParameters *par = track->par; track->vos_len = par->extradata_size; av_freep(&track->vos_data); track->vos_data = av_malloc(track->vos_len + AV_INPUT_BUFFER_PADDING_SIZE); if (!track->vos_data) return AVERROR(ENOMEM); memcpy(track->vos_data, par->extradata, track->vos_len); memset(track->vos_data + track->vos_len, 0, AV_INPUT_BUFFER_PADDING_SIZE); } mov->need_rewrite_extradata = 0; } // If there is a caption track, you need to write the end package for (i = 0; i < mov->nb_streams; i++) { MOVTrack *trk = &mov->tracks[i]; if (trk->par->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; } } // Judge whether to write chapter track 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) return res; } } if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) { moov_pos = avio_tell(pb); // Write mdat tag and size 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); // Write moov tag if (mov->flags & FF_MOV_FLAG_FASTSTART) { res = shift_data(s); if (res < 0) return res; avio_seek(pb, mov->reserved_header_pos, SEEK_SET); if ((res = mov_write_moov_tag(pb, mov, s)) < 0) return res; } else if (mov->reserved_moov_size > 0) { int64_t size; if ((res = mov_write_moov_tag(pb, mov, s)) < 0) return res; size = mov->reserved_moov_size - (avio_tell(pb) - mov->reserved_header_pos); if (size < 8){ return AVERROR(EINVAL); } 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) return res; } res = 0; } else { mov_auto_flush_fragment(s, 1); for (i = 0; i < mov->nb_streams; i++) mov->tracks[i].data_offset = 0; // Write sidx tag if (mov->flags & FF_MOV_FLAG_GLOBAL_SIDX) { int64_t end; res = shift_data(s); if (res < 0) return res; 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); } // Write mfra tag if (!(mov->flags & FF_MOV_FLAG_SKIP_TRAILER)) { avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER); res = mov_write_mfra_tag(pb, mov); if (res < 0) return res; } } return res; }
According to the source code, there are seven steps to write the end of the file in mp4 format:
- Judge whether to rewrite extradata;
- If there is a caption track, the end package needs to be written;
- Judge whether to write chapter track;
- Write mdat tag and size;
- Write moov tag;
- Write sidx tag;
- Write mfra tag;
4,deinit_muxer
deinit_ The muxer() function is responsible for whether to Muxer memory resources, as follows:
static void deinit_muxer(AVFormatContext *s) { if (s->oformat && s->oformat->deinit && s->internal->initialized) s->oformat->deinit(s); s->internal->initialized = s->internal->streams_initialized = 0; }
Similarly, take the MP4 package format as an example. By the front FF_ mp4_ The Muxer structure shows that the deinit function pointer points to mov_free(). Therefore, s - > oformat - > deinit() is finally called to mov_free() to free up resources. The function is implemented as follows:
static void mov_free(AVFormatContext *s) { MOVMuxContext *mov = s->priv_data; int i; av_packet_free(&mov->pkt); if (!mov->tracks) return; if (mov->chapter_track) { avcodec_parameters_free(&mov->tracks[mov->chapter_track].par); } // Traverse all nb_streams, release relevant resources for (i = 0; i < mov->nb_streams; i++) { if (mov->tracks[i].tag == MKTAG('r','t','p',' ')) ff_mov_close_hinting(&mov->tracks[i]); else if (mov->tracks[i].tag == MKTAG('t','m','c','d') && mov->nb_meta_tmcd) av_freep(&mov->tracks[i].par); av_freep(&mov->tracks[i].cluster); av_freep(&mov->tracks[i].frag_info); av_packet_free(&mov->tracks[i].cover_image); if (mov->tracks[i].eac3_priv) { struct eac3_info *info = mov->tracks[i].eac3_priv; av_packet_free(&info->pkt); av_freep(&mov->tracks[i].eac3_priv); } if (mov->tracks[i].vos_len) av_freep(&mov->tracks[i].vos_data); ff_mov_cenc_free(&mov->tracks[i].cenc); ffio_free_dyn_buf(&mov->tracks[i].mdat_buf); } av_freep(&mov->tracks); ffio_free_dyn_buf(&mov->mdat_buf); }
So far, AV_ write_ The tail function of the trail() write media file has been analyzed. Combined with the previous article: avformat_write_header() write media file header and av_write_frame() writes media packets, which can completely implement muxer wrapper.