FFmpeg reads the video stream and saves it as BMP

Introduction

basic concept

Before demonstrating how to read a video file, you should understand a few concepts about video streams:

  • Container: The video file itself is called a container, and the type of the container (such as AVI, MP4) determines how the video information is stored.
  • Stream: Each container can contain several streams. For example, a video file usually contains a video stream and an audio stream.
  • Frame: A frame is the smallest unit of data in a stream. Each stream contains several frames.
  • Codec (CODEC): The data in the stream is encoded by the encoder, rather than directly storing the original data. When processing each frame, it needs to be decoded with CODEC to get the original data.
  • Packet: FFmpeg uses packets to describe the data read from the stream. During actual processing, it will continue to read data from the stream to the packet until the packet contains a whole frame of content before processing.

Process flow

The general process for FFmpeg to read a video stream is:

  1. Open a video (audio) file.
  2. Read data from the stream into packets.
  3. If the packet is not a full frame, do 2. If the packet is a whole frame, then:
  4. Process frames.
  5. Continue with 2 until the entire stream has been processed.

The general flow at the code level is:

Created with Raphaël 2.1.0 注册所有的格式和解码器 打开视频文件 读取视频流信息并找到视频流 找到并打开与流对应的编解码器 创建并初始化解码后的帧 从流中读取帧数据到包 包是一个整帧 处理帧 yes no

Example

The following program reads a video stream and dumps the first frame of data as a BITMAP. (The path to the video file is obtained from the program's startup parameters.)

extern "C"
{
#include "libavcodec\avcodec.h"
#include "libavformat\avformat.h"
#include "libswscale\swscale.h"
#include "libavutil\imgutils.h"
}

#include <iostream>
#include <fstream>
#include <memory>
#include <Windows.h>

void SaveBitmap(uint8_t *data, int width, int height, int bpp) 
{
    BITMAPFILEHEADER bmpHeader = { 0 };
    bmpHeader.bfType = ('M' << 8) | 'B';
    bmpHeader.bfReserved1 = 0;
    bmpHeader.bfReserved2 = 0;
    bmpHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
    bmpHeader.bfSize = bmpHeader.bfOffBits + width*height*bpp / 8;

    BITMAPINFO bmpInfo = { 0 };
    bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmpInfo.bmiHeader.biWidth = width;
    bmpInfo.bmiHeader.biHeight = -height;  // 反转图片
    bmpInfo.bmiHeader.biPlanes = 1;
    bmpInfo.bmiHeader.biBitCount = bpp;
    bmpInfo.bmiHeader.biCompression = 0;
    bmpInfo.bmiHeader.biSizeImage = 0;
    bmpInfo.bmiHeader.biXPelsPerMeter = 100;
    bmpInfo.bmiHeader.biYPelsPerMeter = 100;
    bmpInfo.bmiHeader.biClrUsed = 0;
    bmpInfo.bmiHeader.biClrImportant = 0;

    // 打开文件
    std::ofstream fout("output.bmp", std::ofstream::out | std::ofstream::binary);
    if (!fout)
    {
        return;
    }
    // 使用结束后关闭
    std::shared_ptr<std::ofstream> foutCloser(&fout, [](std::ofstream *f){ f->close(); });

    fout.write(reinterpret_cast<const char*>(&bmpHeader), sizeof(BITMAPFILEHEADER));
    fout.write(reinterpret_cast<const char*>(&bmpInfo.bmiHeader), sizeof(BITMAPINFOHEADER));
    fout.write(reinterpret_cast<const char*>(data), width * height * bpp / 8);
}

int main(int argc, char **argv)
{
    // 注册所有的格式和解码器
    av_register_all();

    AVFormatContext *pFmtCtx = NULL;

    // 打开视频文件,读取文件头信息到 AVFormatContext 结构体中
    if (avformat_open_input(&pFmtCtx, argv[1], NULL, NULL) != 0)
    {
        return -1;
    }
    // 程序结束时关闭 AVFormatContext
    std::shared_ptr<AVFormatContext*> fmtCtxCloser(&pFmtCtx, avformat_close_input);

    // 读取流信息到 AVFormatContext->streams 中
    // AVFormatContext->streams 是一个数组,数组大小是 AVFormatContext->nb_streams
    if (avformat_find_stream_info(pFmtCtx, NULL) < 0)
    {
        return -1;
    }

    // 找到第一个视频流
    int videoStream = -1;
    for (decltype(pFmtCtx->nb_streams) i = 0; i < pFmtCtx->nb_streams; ++i)
    {
        if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoStream = i;
            break;
        }
    }
    if (videoStream == -1)
    {
        return -1;
    }

    // 获取解码器上下文
    AVCodecParameters *pCodecParams = pFmtCtx->streams[videoStream]->codecpar;

    // 获取解码器
    AVCodec *pCodec = avcodec_find_decoder(pCodecParams->codec_id);
    if (pCodec == NULL)
    {
        std::cerr << "Unsupported codec!" << std::endl;
        return -1;
    }

    // 解码器上下文
    AVCodecContext *pCodecCtx = avcodec_alloc_context3(NULL);  // allocate
    if (avcodec_parameters_to_context(pCodecCtx, pCodecParams) < 0) // initialize
    {
        return -1;
    }
    // 程序结束时关闭解码器
    std::shared_ptr<AVCodecContext> codecCtxCloser(pCodecCtx, avcodec_close);

    // 打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    {
        return -1;
    }

    // 创建帧
    AVFrame *pFrame = av_frame_alloc();
    std::shared_ptr<AVFrame*> frameDeleter(&pFrame, av_frame_free);

    // 创建转换后的帧
    AVFrame *pFrameBGR = av_frame_alloc();
    std::shared_ptr<AVFrame*> frameBGRDeleter(&pFrameBGR, av_frame_free);

    // 开辟数据存储区
    int numBytes = av_image_get_buffer_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height, 1);
    uint8_t *buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
    // 程序结束时释放
    std::shared_ptr<uint8_t> bufferDeleter(buffer, av_free);

    // 填充 pFrameBGR 中的若干字段(data、linsize等)
    av_image_fill_arrays(pFrameBGR->data, pFrameBGR->linesize, buffer, AV_PIX_FMT_BGR24,
        pCodecCtx->width, pCodecCtx->height, 1);

    // 获取图像处理上下文
    SwsContext *pSwsCtx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
        pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_BGR24,
        SWS_BILINEAR, NULL, NULL, NULL);
    // 程序结束时释放
    std::shared_ptr<SwsContext> swsCtxDeleter(pSwsCtx, sws_freeContext);

    AVPacket packet;
    int frameCount = 0;
    const int saveFrameIndex = 1;
    while (av_read_frame(pFmtCtx, &packet) >= 0)  // 将 frame 读取到 packet
    {
        // 迭代结束后释放 av_read_frame 分配的 packet 内存
        std::shared_ptr<AVPacket> packetDeleter(&packet, av_packet_unref);

        if (packet.stream_index == videoStream)  // 如果读到的是视频流
        {
            // 使用解码器 pCodecCtx 将 packet 解码
            avcodec_send_packet(pCodecCtx, &packet);
            // 返回 pCodecCtx 解码后的数据,注意只有在解码完整个 frame 时该函数才返回 0 
            if (avcodec_receive_frame(pCodecCtx, pFrame) == 0)
            {
                // 图像转换
                sws_scale(pSwsCtx, pFrame->data,
                    pFrame->linesize, 0, pCodecCtx->height,
                    pFrameBGR->data, pFrameBGR->linesize);

                // 将第 saveFrameIndex 帧保存为 bitmap 图片
                if (++frameCount == saveFrameIndex)
                {
                    SaveBitmap(pFrameBGR->data[0], pCodecCtx->width, pCodecCtx->height, 24);
                    break;  // 结束处理
                }
            }
        }
    }

    return 0;
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324370981&siteId=291194637