How to Read Video Packets with FFmpeg libavformat

This article provides a step-by-step guide on how to use the FFmpeg libavformat C API to open a video file, retrieve stream information, and read individual packet headers. You will learn the essential functions required to initialize the format context, locate the video stream, and loop through packets to extract metadata such as presentation timestamps (PTS), decoding timestamps (DTS), packet size, and stream indices.

Step 1: Initialize Context and Open the Video File

To read a media file, you must first allocate an AVFormatContext and open the file using avformat_open_input. This function reads the file header and populates the context with information about the container format.

#include <libavformat/avformat.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage: %s <input_file>\n", argv[0]);
        return -1;
    }

    const char *filename = argv[1];
    AVFormatContext *format_ctx = NULL;

    // Open the input file and read the header
    if (avformat_open_input(&format_ctx, filename, NULL, NULL) < 0) {
        fprintf(stderr, "Could not open source file %s\n", filename);
        return -1;
    }

Step 2: Retrieve Stream Information

Opening the file only reads the header. To access detailed stream information (such as codecs, frame rates, and durations), you need to call avformat_find_stream_info.

    // Retrieve stream information
    if (avformat_find_stream_info(format_ctx, NULL) < 0) {
        fprintf(stderr, "Could not find stream information\n");
        avformat_close_input(&format_ctx);
        return -1;
    }

    // Dump information about the file onto standard error
    av_dump_format(format_ctx, 0, filename, 0);

Step 3: Find the Video Stream Index

A media file can contain multiple streams (video, audio, subtitles). You must iterate through the streams to find the one containing video data.

    int video_stream_index = -1;
    for (unsigned int i = 0; i < format_ctx->nb_streams; i++) {
        if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_index = i;
            break;
        }
    }

    if (video_stream_index == -1) {
        fprintf(stderr, "Could not find a video stream\n");
        avformat_close_input(&format_ctx);
        return -1;
    }

Step 4: Allocate the Packet and Read Packet Headers

FFmpeg uses the AVPacket structure to store compressed data. You allocate a packet using av_packet_alloc() and read data from the stream using a loop with av_read_frame(). Despite its name, av_read_frame reads a packet (compressed data), not a decoded raw frame.

    AVPacket *packet = av_packet_alloc();
    if (!packet) {
        fprintf(stderr, "Could not allocate packet\n");
        avformat_close_input(&format_ctx);
        return -1;
    }

    printf("Reading packets:\n");
    // Read packets from the context
    while (av_read_frame(format_ctx, packet) >= 0) {
        // Check if the packet belongs to the video stream
        if (packet->stream_index == video_stream_index) {
            printf("Video Packet: PTS=%lld, DTS=%lld, Size=%d, Keyframe=%s\n",
                   packet->pts,
                   packet->dts,
                   packet->size,
                   (packet->flags & AV_PKT_FLAG_KEY) ? "Yes" : "No");
        }

        // Wipe the packet buffer to prepare it for the next iteration
        av_packet_unref(packet);
    }

Step 5: Clean Up Resources

Once processing is complete, free the allocated packet memory and close the input format context to prevent memory leaks.

    av_packet_free(&packet);
    avformat_close_input(&format_ctx);

    return 0;
}