Finalize Output Files with av_write_trailer in FFmpeg

This article provides a step-by-step guide on how to properly finalize and close an output multimedia file using the FFmpeg C library. It covers flushing delayed frames from the encoder, invoking av_write_trailer to write file headers and indexes, safely closing the I/O stream, and deallocating associated resources to avoid memory leaks or corrupted output files.

1. Flush the Encoder

Before writing the container trailer, you must ensure that all cached frames inside the encoder are converted into packets and written to the output file. Modern video encoders often delay frames for B-frame calculations.

Send a NULL frame to the encoder to enter flushing mode, then read and write all remaining packets:

// Signal end of stream by sending NULL
avcodec_send_frame(codec_ctx, NULL);

AVPacket *pkt = av_packet_alloc();
while (avcodec_receive_packet(codec_ctx, pkt) == 0) {
    // Rescale packet timestamps to muxer stream timebase
    av_packet_rescale_ts(pkt, codec_ctx->time_base, out_stream->time_base);
    pkt->stream_index = out_stream->index;

    // Write the flushed packet
    av_interleaved_write_frame(format_ctx, pkt);
    av_packet_unref(pkt);
}
av_packet_free(&pkt);

2. Write the Stream Trailer with av_write_trailer

The av_write_trailer() function finalizes the container format. For formats like MP4 or MKV, this step writes essential metadata, such as the moov atom or index tables, without which the output file may become unplayable or non-seekable.

int ret = av_write_trailer(format_ctx);
if (ret < 0) {
    fprintf(stderr, "Error writing trailer: %s\n", av_err2str(ret));
}

av_write_trailer flushes any remaining buffered packets within the muxer and handles format-specific finalizing routines.

3. Close the I/O Context

If the format does not use the AVFMT_NOFILE flag, the underlying AVIOContext must be closed to ensure data is committed to disk and the file handle is released.

if (!(format_ctx->oformat->flags & AVFMT_NOFILE)) {
    avio_closep(&format_ctx->pb);
}

Using avio_closep() is safer than avio_close() because it closes the file and sets the pointer to NULL, preventing dangling pointers.

4. Free Allocated Resources

Once the file is written and the I/O stream is closed, free the remaining data structures to prevent memory leaks:

// Free codec contexts
avcodec_free_context(&codec_ctx);

// Free format context
avformat_free_context(format_ctx);

Complete Sequence

The complete teardown sequence in standard order:

// 1. Flush encoder
avcodec_send_frame(codec_ctx, NULL);
while (avcodec_receive_packet(codec_ctx, pkt) == 0) {
    av_packet_rescale_ts(pkt, codec_ctx->time_base, out_stream->time_base);
    pkt->stream_index = out_stream->index;
    av_interleaved_write_frame(format_ctx, pkt);
    av_packet_unref(pkt);
}

// 2. Finalize muxer/container
av_write_trailer(format_ctx);

// 3. Close output file
if (!(format_ctx->oformat->flags & AVFMT_NOFILE)) {
    avio_closep(&format_ctx->pb);
}

// 4. Free memory
av_packet_free(&pkt);
avcodec_free_context(&codec_ctx);
avformat_free_context(format_ctx);