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);
    }
    
}
相关推荐
潜创微科技16 小时前
HDMI1.3 无线传输芯片方案 空旷 150 米量产级音视频方案
音视频
VidDown16 小时前
VidDown 工具站:免费、本地优先的开发者工具箱
javascript·编辑器·音视频·视频编解码·视频
换个昵称都难16 小时前
音频格式之WAV
音视频
AI创界者17 小时前
PilotTTS 一键整合包(Win/Mac):8G 显存畅跑,实测解锁情绪与副语言的精准控制
人工智能·macos·aigc·音视频
u1521096484918 小时前
S.S.Audio PRO A2音频隔离器
嵌入式硬件·音视频·实时音视频·视频编解码·视频
VidDown20 小时前
显卡处理视频技术详解:从硬解码到 NVENC,GPU 如何让视频处理起飞?
javascript·编辑器·音视频·视频编解码·视频
EasyDSS20 小时前
全能音视频平台/私有化音视频系统EasyDSS!直播/点播/会议/集群对讲一站式落地
音视频
Damon_X21 小时前
车载音频复习
音视频
源之缘-OFD先行者21 小时前
破界渲染:WinForm下的FFmpeg+Vortice极速推流引擎
ffmpeg·winform·推流·h264
3DVisionary21 小时前
告别数据中断:XTDIC-VG视频引伸计在金属疲劳测试中3个真实案例
人工智能·音视频·应用案例·xtdic-vg·视频引伸计·疲劳测试·实战复盘