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);
    }
    
}
相关推荐
做萤石二次开发的哈哈1 小时前
萤石开放平台如何拉取 RTC 房间成员列表?
音视频·萤石开放平台
离离原上草772 小时前
Pavo AI Agent 源码深度解析:从自然语言故事到视频分镜的 Multi-Agent 工程实践
人工智能·音视频
YWamy4 小时前
播放器类音视频 SDK 选型指南:主流产品对比与适配建议
音视频
weixin_446260855 小时前
HumanForge:基于多智能体伪造推理的以人为中心深度伪造视频基准
人工智能·音视频
美狐美颜SDK开放平台5 小时前
从RTC到AI美颜:直播APP开发中的直播美颜SDK技术架构解析
人工智能·音视频·实时音视频·美颜sdk·视频美颜sdk·美颜api
潜创微科技5 小时前
一根网线传 4K60:基于 IT9205 的网线延长方案,把 HDMI 送到 100 米外
音视频
蓝田~6 小时前
AI 图片与视频处理 — 从原理到实践
网络·人工智能·音视频
乐橙开放平台8 小时前
巡店系统笔记:乐橙 listDeviceDetailsByPage 台账 + bindDeviceLive 辅码流最小闭环
网络·笔记·物联网·自动化·音视频·智能家居
鲲穹AI全能助理8 小时前
实用音频合成工具分享:灵境配音 使用场景与功能说明
ffmpeg·音视频
AI2中文网9 小时前
App Inventor 2 音频播放器 vs 音效 vs 视频播放器:三个媒体组件,选错了App直接卡死
android·音视频·媒体