FFmpeg 读取视频流并保存为BMP

简介

基本概念

在演示如何读取视频文件之前,应先了解几个关于视频流的概念:

  • 容器(Container): 视频文件本身就叫容器,容器的类型(比如AVI、MP4)决定了视频信息如何存储。
  • 流(Stream):每个容器可以包含若干个流。比如一个视频文件通常包含了一个视频流和一个音频流。
  • 帧(Frame):帧是流中数据的最小单位。每个流里面包含若干帧。
  • 编解码器(CODEC):流中的数据都是以编码器编码而成的,而不是直接存储原始数据。在处理每一帧时,需要用CODEC来解码才能得到原始数据。
  • 包(Packet):FFmpeg用包来描述从流中读到的数据,在实际处理时,将从流中不断读取数据到包,直到包中包含了一个整帧的内容再进行处理。

处理流程

FFmpeg读取视频流的一般流程为:

  1. 打开视频(音频)文件。
  2. 从流中读取数据到包。
  3. 如果包不是一个整帧,则执行2。如果包是一个整帧,则:
  4. 处理帧。
  5. 继续执行2,直到整个流处理完毕。

代码级别的一般流程为:

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

示例

下面的程序读取一个视频流,将第一帧数据转储为BITMAP。(视频文件的路径由程序的启动参数获取。)

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;
}
阅读更多
版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/u011304970/article/details/72995308
想对作者说点什么? 我来说一句

AVI视频导出为bmp图片

2014年03月24日 43KB 下载

没有更多推荐了,返回首页

不良信息举报

FFmpeg 读取视频流并保存为BMP

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭