FFmpeg如何从视频中截取一帧图像

129 阅读4分钟
  1. 初始化 FFmpeg: 使用 av_register_all() 初始化 FFmpeg 库。
  2. 打开视频文件: 使用 avformat_open_input() 打开视频文件,并获取 AVFormatContext。
  3. 查找视频流: 使用 avformat_find_stream_info() 获取流信息,然后遍历 pFormatCtx->streams 找到视频流,并记录其索引。
  4. 获取编解码器上下文: 使用 avcodec_alloc_context3() 分配编解码器上下文,并使用 avcodec_parameters_to_context() 将流参数复制到编解码器上下文。
  5. 查找并打开解码器: 使用 avcodec_find_decoder() 查找适合的视频解码器,并使用 avcodec_open2() 打开解码器。
  6. 分配原始帧和 RGB 帧: 使用 av_frame_alloc() 分配两个 AVFrame 对象,一个用于存储解码后的原始帧,另一个用于存储转换后的 RGB 帧。
  7. 分配 RGB 帧缓冲区: 使用 av_image_get_buffer_size() 计算 RGB 帧所需的缓冲区大小,并使用 av_malloc() 分配缓冲区内存。然后使用 av_image_fill_arrays() 将缓冲区分配给 RGB 帧的图像平面。
  8. 初始化 SWS 上下文: 使用 sws_getContext() 初始化图像转换上下文。
  9. 读取并处理视频帧: 使用 av_read_frame() 从视频文件中读取数据包,检查是否属于视频流,并使用 avcodec_decode_video2() 解码数据包。如果帧解码成功,就使用 sws_scale() 将解码后的原始帧转换为 RGB 格式。
  10. 保存帧为 BMP 文件: 使用标准文件 I/O 函数(如 fopen() 和 fwrite())将 RGB 帧保存为 BMP 图像文件。需注意 BMP 文件的文件头和信息头的格式。
  11. 释放资源: 处理完成后,释放分配的缓冲区和 AVFrame,关闭编解码器上下文,并关闭视频文件。
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    const char *filename = "your_video_file.mp4";
    const char *output_filename = "frame.bmp";
    AVFormatContext *pFormatCtx = NULL;
    AVCodecContext *pCodecCtx = NULL;
    AVCodec *pCodec = NULL;
    SwsContext *sws_ctx = NULL;
    int videoStreamIndex;

    // 初始化FFmpeg
    av_register_all();

    // 打开视频文件
    if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) {
        fprintf(stderr, "Could not open file\n");
        return -1;
    }

    // 查找视频流
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        fprintf(stderr, "Could not find stream information\n");
        return -1;
    }

    // 遍历找到视频流,并记录其索引
    videoStreamIndex = -1;
    for (int i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStreamIndex = i;
            break;
        }
    }
    if (videoStreamIndex == -1) {
        fprintf(stderr, "Could not find video stream\n");
        return -1;
    }

    // 获取编解码器上下文
    pCodecCtx = avcodec_alloc_context3(NULL);
    if (!pCodecCtx) {
        fprintf(stderr, "Could not allocate video codec context\n");
        return -1;
    }
    // 将流参数复制到编解码器上下文
    avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStreamIndex]->codecpar);

    // 查找解码器
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (pCodec == NULL) {
        fprintf(stderr, "Unsupported codec\n");
        return -1;
    }

    // 打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        return -1;
    }

    // 分配两个帧,一个用来存储解码后的原始帧,一个用来存储转换后的RGB帧
    AVFrame *pFrame = av_frame_alloc();
    AVFrame *pFrameRGB = av_frame_alloc();
    if (pFrame == NULL || pFrameRGB == NULL) {
        fprintf(stderr, "Could not allocate video frame\n");
        return -1;
    }

    //定义目标图像的宽度和高度
    int target_width = 1280;
    int target_height = 720;

    // 计算RGB帧所需的缓冲区大小,分配内存
    int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, target_width, target_height, 1);
    uint8_t *buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));

    // 将缓冲区分配给RGB帧的图像平面
    av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer, AV_PIX_FMT_RGB24, target_width, target_height, 1);

    // 初始化图像转换上下文
    sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
                             target_width, target_height, AV_PIX_FMT_RGB24,
                             SWS_BILINEAR, NULL, NULL, NULL);

    // 读取并处理视频帧
    int frameFinished;
    AVPacket packet;
    // 从视频文件中读取数据包
    while (av_read_frame(pFormatCtx, &packet) >= 0) {
        // 检查是否属于视频流
        if (packet.stream_index == videoStreamIndex) {
            // 解码数据包
            avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

            if (frameFinished) {
                // 解码成功后,将解码后的原始帧转换为RGB格式
                sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
                          pFrame->linesize, 0, pCodecCtx->height,
                          pFrameRGB->data, pFrameRGB->linesize);

                // 保存帧
                FILE *pFile = fopen(output_filename, "wb");
                if (pFile == NULL) {
                    fprintf(stderr, "Could not open output file\n");
                    return -1;
                }

                // Write BMP header
                int width = target_width;
                int height = target_height;
                int bytesPerPixel = 3;
                int fileHeaderSize = 14;
                int infoHeaderSize = 40;
                int paddingSize = (4 - (width * bytesPerPixel) % 4) % 4;
                int fileSize = fileHeaderSize + infoHeaderSize + (bytesPerPixel * width + paddingSize) * height;

                unsigned char fileHeader[14] = {
                    'B', 'M',         // Signature
                    fileSize, 0, 0, 0, // Image file size in bytes
                    0, 0, 0, 0,       // Reserved
                    fileHeaderSize + infoHeaderSize, 0, 0, 0 // Start of pixel array
                };

                unsigned char infoHeader[40] = {
                    infoHeaderSize, 0, 0, 0, // Info header size
                    width, 0, 0, 0,           // Width
                    height, 0, 0, 0,          // Height
                    1, 0,                    // Number of color planes
                    24, 0,                   // Bits per pixel
                    0, 0, 0, 0,              // Compression
                    0, 0, 0, 0,              // Image size (no compression)
                    0, 0, 0, 0,              // X pixels per meter
                    0, 0, 0, 0,              // Y pixels per meter
                    0, 0, 0, 0,              // Total colors
                    0, 0, 0, 0               // Important colors
                };

                fwrite(fileHeader, 1, fileHeaderSize, pFile);
                fwrite(infoHeader, 1, infoHeaderSize, pFile);

                // Write pixel data
                for (int y = height - 1; y >= 0; y--) {
                    fwrite(pFrameRGB->data[0] + y * pFrameRGB->linesize[0], bytesPerPixel, width, pFile);
                    fwrite("\0\0\0", 1, paddingSize, pFile);
                }

                fclose(pFile);
                break; // We only need the first frame
            }
        }

        // 释放资源
        av_packet_unref(&packet);
    }

    // Free the RGB image
    av_free(buffer);
    av_frame_free(&pFrameRGB);

    // Free the YUV frame
    av_frame_free(&pFrame);

    // Close the codecs
    avcodec_close(pCodecCtx);

    // Close the video file
    avformat_close_input(&pFormatCtx);

    return 0;
}