FFmpeg 读取视频流并保存为BMP

简介: FFmpeg 读取视频流并保存为BMP 简介 基本概念 在演示如何读取视频文件之前,应先了解几个关于视频流的概念: 容器(Container): 视频文件本身就叫容器,容器的类型(比如AVI、MP4)决定了视频信息如何存储。

FFmpeg 读取视频流并保存为BMP

简介

基本概念

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

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

处理流程

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

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

代码级别的一般流程为:

注册所有的格式和解码器打开视频文件读取视频流信息并找到视频流找到并打开与流对应的编解码器创建并初始化解码后的帧从流中读取帧数据到包包是一个整帧处理帧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;
}
原文地址http://www.bieryun.com/2932.html
相关文章
|
6月前
|
网络协议 API 网络安全
探讨TCP传输视频流并利用FFmpeg进行播放的过程
探讨TCP传输视频流并利用FFmpeg进行播放的过程
636 0
|
6月前
|
编解码 Linux API
【FFmpeg 视频流处理】FFmpeg API深度解析:视频流画面合并、拼接与裁剪技巧
【FFmpeg 视频流处理】FFmpeg API深度解析:视频流画面合并、拼接与裁剪技巧
575 0
|
6月前
|
编解码 API 数据处理
【摄像头数据处理】摄像头数据处理:使用FFmpeg合并、编码和封装视频流
【摄像头数据处理】摄像头数据处理:使用FFmpeg合并、编码和封装视频流
352 0
|
6月前
|
存储 编解码 缓存
【ffmpeg 移动视频流位置】深入理解FFmpeg:精细探讨seek操作和编解码上下文
【ffmpeg 移动视频流位置】深入理解FFmpeg:精细探讨seek操作和编解码上下文
332 0
|
存储 应用服务中间件 nginx
FFmpeg+Nginx将视频转为hls点播视频流
FFmpeg+Nginx将视频转为hls点播视频流
623 0
FFmpeg+Nginx将视频转为hls点播视频流
【FFmpeg】ffplay 播放视频命令 ( 播放 | 暂停 | 停止 | 音量控制 | 进度控制 | 音频流 / 视频流 / 字幕流 / 节目切换 )
【FFmpeg】ffplay 播放视频命令 ( 播放 | 暂停 | 停止 | 音量控制 | 进度控制 | 音频流 / 视频流 / 字幕流 / 节目切换 )
587 0
【FFmpeg】ffplay 播放视频命令 ( 播放 | 暂停 | 停止 | 音量控制 | 进度控制 | 音频流 / 视频流 / 字幕流 / 节目切换 )
【FFmpeg】ffplay 播放视频命令 ( 播放 | 暂停 | 停止 | 音量控制 | 进度控制 | 音频流 / 视频流 / 字幕流 / 节目切换 )(二)
【FFmpeg】ffplay 播放视频命令 ( 播放 | 暂停 | 停止 | 音量控制 | 进度控制 | 音频流 / 视频流 / 字幕流 / 节目切换 )(二)
879 0
【FFmpeg】ffplay 播放视频命令 ( 播放 | 暂停 | 停止 | 音量控制 | 进度控制 | 音频流 / 视频流 / 字幕流 / 节目切换 )(二)
|
编解码 Windows 内存技术
QT应用编程: 基于FFMPEG设计的流媒体播放器(播放rtmp视频流)
QT应用编程: 基于FFMPEG设计的流媒体播放器(播放rtmp视频流)
737 0
QT应用编程: 基于FFMPEG设计的流媒体播放器(播放rtmp视频流)