FFmpeg source code analysis: write media file tail av_write_trailer()

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:

  1. If there is AVBSFContext, write the bitstream filter packet finally;
  2. Fill in empty data packets;
  3. AVIOContext, if there is a flag for avio context to be written;
  4. Call AVOutputFormat to write the end of the file;
  5. Release muxer resources;
  6. 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:

  1. Judge whether to rewrite extradata;
  2. If there is a caption track, the end package needs to be written;
  3. Judge whether to write chapter track;
  4. Write mdat tag and size;
  5. Write moov tag;
  6. Write sidx tag;
  7. 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.

Added by daloss on Sun, 13 Feb 2022 10:00:18 +0200