Write Encoded Packets to Container with libavformat

This article provides a practical guide on how to write encoded audio or video packets into a media container file using FFmpeg’s libavformat API. We will cover the complete lifecycle of container writing, including allocating the output context, adding streams, writing the container header, interleaving and writing packets, and properly finalizing the file structure to prevent data corruption.

Step 1: Allocate the Output Format Context

Before writing any data, you must initialize an AVFormatContext for the output container. Use avformat_alloc_output_context2 to automatically detect the container format based on the file extension or format name.

AVFormatContext *out_ctx = NULL;
int ret = avformat_alloc_output_context2(&out_ctx, NULL, NULL, "output.mp4");
if (ret < 0) {
    // Handle allocation error
}

Step 2: Create Output Streams and Copy Parameters

You need to create a new AVStream inside the format context for every track (video, audio, subtitles) you want to write. You must also copy the codec configuration parameters from your encoder context (AVCodecContext) to the stream’s parameters (AVCodecParameters).

AVStream *out_stream = avformat_new_stream(out_ctx, NULL);
if (!out_stream) {
    // Handle stream creation error
}

// Copy codec parameters from your encoder context
ret = avcodec_parameters_from_context(out_stream->codecpar, encoder_ctx);
if (ret < 0) {
    // Handle copy parameters error
}

// Ensure the stream has a defined timebase
out_stream->time_base = encoder_ctx->time_base;

Step 3: Open the Output File

If the chosen output container format writes to a physical file (which is determined by checking the AVFMT_NOFILE flag), you must open the target destination using FFmpeg’s I/O layer (avio_open).

if (!(out_ctx->oformat->flags & AVFMT_NOFILE)) {
    ret = avio_open(&out_ctx->pb, "output.mp4", AVIO_FLAG_WRITE);
    if (ret < 0) {
        // Handle file open error
    }
}

Step 4: Write the Container Header

Write the file header and initial structural metadata to the output context using avformat_write_header. This function processes any format options and configures the stream variables.

ret = avformat_write_header(out_ctx, NULL);
if (ret < 0) {
    // Handle write header error
}

Step 5: Rescale Timestamps and Write Packets

When writing packets (AVPacket), you must translate their Presentation Timestamp (PTS), Decompression Timestamp (DTS), and duration from the encoder’s time base to the output stream’s time base.

Use av_interleaved_write_frame to write the packets. This function automatically manages interleaving multiple streams (like mixing audio and video packets) chronologically, which is required by most container formats.

AVPacket *pkt = av_packet_alloc();

// ... Retrieve your encoded packet from the encoder ...

// Rescale timestamps from encoder time_base to stream time_base
av_packet_rescale_ts(pkt, encoder_ctx->time_base, out_stream->time_base);
pkt->stream_index = out_stream->index;

// Write the packet to the output file
ret = av_interleaved_write_frame(out_ctx, pkt);
if (ret < 0) {
    // Handle write error
}

av_packet_unref(pkt);

Step 6: Flush and Close the File

Once all media packets have been written, write the trailer to finalize the container structure (this writes index tables, metadata, and duration markers to the file). Finally, close the file I/O context and free the format context structures.

// Write the trailer
av_write_trailer(out_ctx);

// Close the file output stream if opened
if (!(out_ctx->oformat->flags & AVFMT_NOFILE)) {
    avio_closep(&out_ctx->pb);
}

// Free the format context
avformat_free_context(out_ctx);