ffmpeg视频解码

一、视频解码流程

使用ffmpeg解码视频帧主要可分为两大步骤:初始化解码器解码视频帧,以下代码以mjpeg为例

1. 初始化解码器

初始化解码器主要有以下步骤:

(1)查找解码器

cpp 复制代码
// 查找MJPEG解码器
    pCodec = avcodec_find_decoder_by_name(videoCodecName);
    if (pCodec == nullptr) {
        release();
        return false;
    }
    // 分配解码器上下文
    pCodecCtx = avcodec_alloc_context3(pCodec);
    if (!pCodecCtx) {
        release();
        return false;
    }

(2)设置解码器参数

cpp 复制代码
    pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
    pCodecCtx->width = mVideoSrcWidth; // 视频宽度
    pCodecCtx->height = mVideoSrcHeight; // 视频高度
    pCodecCtx->pix_fmt = AV_PIX_FMT_YUVJ422P; // 或者其他适合的格式
    pCodecCtx->time_base = { 1, mVideoSrcFps }; // 帧率
    pCodecCtx->thread_count = 2;

(3)打开解码器

cpp 复制代码
 // 打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0) {
       
        release();
        return false;
    }

2. 解码视频帧数据

解码视频帧数据主要有以下步骤:

(1)将编码数据送往解码器

cpp 复制代码
   AVPacket* packet = av_packet_alloc();
   if (!packet) {
       release();
       return false;
   }
   
   packet->data = data; // 待解码数据地址
   packet->size = size; // 待解码数据大小
   
   // 发送数据到解码器
   int ret = avcodec_send_packet(pCodecCtx, packet);
   if (ret < 0) {
       release();
       return false;
   }

(2)接收解码数据

cpp 复制代码
ret = avcodec_receive_frame(pCodecCtx, pFrame);

二、使用ffmpeg实现对内存中的视频帧数据解码

以下代码中InitDecoder为初始化解码器接口,DecodeVideoFrame为解码视频帧接口

需注意

(1)解码的色彩空间pCodecCtx->pix_fmt不可随意指定,调用avcodec_send_packet后可能会变化,这与视频帧的编码方式有关

(2)每次接收完解码数据要调用av_frame_unref进行释放,否则会有内存泄漏问题

cpp 复制代码
extern "C" { // ffmpeg为使用C语言库,因此要声明为C语言的方式链接
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/error.h>
}

char videoCodecName[] = "mjpeg";
FILE* out_file = nullptr;

AVFormatContext* pFormatCtx = nullptr;

AVCodecContext* pCodecCtx = nullptr;
const AVCodec* pCodec = nullptr;
AVFrame* pFrame = nullptr;
AVFrame* pFrameYUYV = nullptr;
int yuyv_size = 0;
uint8_t* buffer = nullptr;
SwsContext* sws_ctx = nullptr;

int      mVideoSrcWidth = 1920;
int      mVideoSrcHeight = 1080;
int      mVideoSrcFps = 30;

bool InitDecoder() {
    fopen_s(&out_file, "test_yuv.yuv", "wb");
    
    // 查找MJPEG解码器
    pCodec = avcodec_find_decoder_by_name(videoCodecName);
    if (pCodec == nullptr) {
        release();
        return false;
    }
    // 分配解码器上下文
    pCodecCtx = avcodec_alloc_context3(pCodec);
    if (!pCodecCtx) {
        release();
        return false;
    }
    pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
    pCodecCtx->width = mVideoSrcWidth; // 视频宽度
    pCodecCtx->height = mVideoSrcHeight; // 视频高度
    pCodecCtx->pix_fmt = AV_PIX_FMT_YUVJ422P; // 或者其他适合的格式
    pCodecCtx->time_base = { 1, mVideoSrcFps }; // 帧率
    pCodecCtx->thread_count = 2;
    
    // 打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0) {
       
        release();
        return false;
    }

    // 分配帧
    pFrame = av_frame_alloc();
    pFrameYUYV = av_frame_alloc();
    if (!pFrame || !pFrameYUYV) {
        release();
        return false;
    }

    // 分配YUYV帧的缓冲区
    yuyv_size = av_image_get_buffer_size(AV_PIX_FMT_YUYV422, pCodecCtx->width, pCodecCtx->height, 1);
    buffer = (uint8_t*)av_malloc(yuyv_size * sizeof(uint8_t));
    if (!buffer) {
        release();
        return false;
    }

    av_image_fill_arrays(pFrameYUYV->data, pFrameYUYV->linesize, buffer, AV_PIX_FMT_YUYV422, pCodecCtx->width, pCodecCtx->height, 1);

    // 创建图像转换上下文
    sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
        pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUYV422,
        SWS_BILINEAR, nullptr, nullptr, nullptr);
    if (!sws_ctx) {
        release();
        return false;
    }
}

bool DecodeVideoFrame(uint8_t* data, int size)
{
    AVPacket* packet = av_packet_alloc();
    if (!packet) {
        release();
        return false;
    }
    
    packet->data = data;
    packet->size = size;
    
    // 发送数据到解码器
    int ret = avcodec_send_packet(pCodecCtx, packet);
    if (ret < 0) {
        release();
        return false;
    }

    // 循环接收解码后的帧
    while (ret >= 0) {
        ret = avcodec_receive_frame(pCodecCtx, pFrame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        }
        else if (ret < 0) {
            release();
            return false;
        }
        /******** 将解码后数据进行处理 ********/
        // 转换为YUYV格式
        if (sws_scale(sws_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUYV->data, pFrameYUYV->linesize) < 0) {
            return false;
        }
        fwrite(pFrameYUYV->data[0], 1, yuyv_size, out_file);
        /******** 将解码后数据进行处理 ********/
       
        av_frame_unref(pFrame); // 将每次接收的解码帧释放掉,否则会内存泄露
        
    }
    av_packet_unref(packet);
    av_packet_free(&packet);
   
    return true;
}

void release()
{
    if (buffer) {
        av_free(buffer);
        buffer = nullptr;
    }

    if (pCodecCtx) {
        avcodec_free_context(&pCodecCtx);
        pCodecCtx = nullptr;
    }
    if (pFormatCtx) {
        avformat_close_input(&pFormatCtx);
        pFormatCtx = nullptr;
    }
    if (pFrame) {
        av_frame_free(&pFrame);
        pFrame = nullptr;
    }
    if (pFrameYUYV) {
        av_frame_free(&pFrameYUYV);
        pFrameYUYV = nullptr;
    }

    if (out_file) {
        fclose(out_file);
    }
    
}
相关推荐
纽格立科技5 小时前
把警报链从纸面接到机房——读德国DAB+自动安全警报实施指南
车载系统·音视频·信息与通信·传媒
hz567896 小时前
视频会议私有化部署指南:架构、成本与实施流程详解
安全·音视频·实时音视频·信息与通信
纽格立科技6 小时前
9月10日,德国的收音机会自己醒来——DAB+自动安全警报进入常态运行
车载系统·音视频·信息与通信·传媒
AI码农小姐姐1 天前
AI漫剧推文短视频动态镜头实现:FFmpeg zoompan 与 Ken Burns 效果实践
人工智能·音视频·ai工具·ai漫剧
技灵AI1 天前
ChatGPT Images 2.5 深度解读:延迟减半、Sketch 草图与 Flare/Sunburst 双模型 API
人工智能·gpt·aigc·音视频·images2.5
林澈在路上1 天前
游戏场景背景音乐定制AI软件哪款好 2026对比推荐
大数据·人工智能·aigc·音视频
AI码农小姐姐1 天前
AI漫剧推文短视频自动化生产:从文本分镜到视频合成的工程实现
人工智能·音视频·ai工具·ai漫剧·知漫剧
benbenAItalk1 天前
真人克隆数字人的素材与成品视频生成:还原度和批量稳定性怎么控?
人工智能·科技·音视频
纽格立科技1 天前
纽格立参展IBC 2026——DRM端到端全链路方案,展位Hall 8 F71
车载系统·音视频·信息与通信·传媒
linux_cfan1 天前
videojs v10 源代码系列解读:10 · `DestroyMixin`:双重 rAF 延迟销毁
前端·javascript·音视频