How to Write Packets to a Container in Libavformat

This article explains how to mux encoded data into an output media container using FFmpeg's libavformat library. You will learn the correct sequence of API calls required to initialize the output muxer, write stream headers, convert packet timestamps between different time bases, write packets sequentially to the file, and properly finalize the container structure.

1. Initialize the Output Context and Write Headers

Before any packets can be written, you must allocate an AVFormatContext for your output format, create the necessary streams using avformat_new_stream(), copy codec parameters, open the output I/O context using avio_open(), and write the container header:

// Allocate output format context
AVFormatContext *ofmt_ctx = NULL;
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, "output.mp4");

// Open file if format does not have AVFMT_NOFILE flag
if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
    avio_open(&ofmt_ctx->pb, "output.mp4", AVIO_FLAG_WRITE);
}

// Write container header
int ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
    // Handle error
}

2. Rescale Packet Timestamps

Packets received from an encoder operate on the encoder's time base (AVCodecContext.time_base). Output containers operate on the stream's time base (AVStream.time_base). You must rescale the Presentation Timestamp (pts), Decompression Timestamp (dts), and duration of each AVPacket before sending it to the muxer.

Use av_packet_rescale_ts() to perform this conversion:

av_packet_rescale_ts(pkt, codec_ctx->time_base, out_stream->time_base);
pkt->stream_index = out_stream->index;

3. Write Packets to the Container

libavformat provides two primary functions for writing encoded packets:

  • av_interleaved_write_frame(): Recommended for most containers, especially those containing multiple streams (e.g., audio and video). This function automatically buffers and sorts packets by DTS to ensure proper interleaving. Note that this function takes ownership of the packet data, resetting the passed AVPacket.
  • av_write_frame(): Writes the packet directly without interleaving. This requires that you supply packets strictly in presentation or decode order yourself.

Writing with interleaving:

ret = av_interleaved_write_frame(ofmt_ctx, pkt);
if (ret < 0) {
    // Handle write error
}

4. Flush the Muxer and Write the Trailer

After encoding all frames and writing all packets, flush any internal buffers within the muxer and finalize the file format by writing the container trailer with av_write_trailer():

// Flush remaining interleaved packets by passing NULL
av_interleaved_write_frame(ofmt_ctx, NULL);

// Write container metadata/trailer (e.g., MP4 'moov' atom)
av_write_trailer(ofmt_ctx);

5. Clean Up Resources

Finally, close the I/O stream and release the format context:

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