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);
    }
    
}
相关推荐
MediaTea1 小时前
Pr:音频仪表
音视频
桃园码工1 小时前
13_HTML5 Audio(音频) --[HTML5 API 学习之旅]
音视频·html5·audio
cuijiecheng20187 小时前
音视频入门基础:MPEG2-TS专题(21)——FFmpeg源码中,获取TS流的视频信息的实现
ffmpeg·音视频
γ..8 小时前
基于MATLAB的图像增强
开发语言·深度学习·神经网络·学习·机器学习·matlab·音视频
cuijiecheng20188 小时前
音视频入门基础:AAC专题(13)——FFmpeg源码中,获取ADTS格式的AAC裸流音频信息的实现
ffmpeg·音视频·aac
悟纤11 小时前
Suno Api V4模型无水印开发「高清音频WAV下载」 —— 「Suno Api系列」第6篇
音视频·suno·suno v4·suno ai
gomogomono17 小时前
HDR视频技术之八:色域映射
音视频·hdr·yuv
流氓也是种气质 _Cookie20 小时前
uniapp blob格式转换为video .mp4文件使用ffmpeg工具
ffmpeg·uni-app
野蛮的大西瓜21 小时前
BigBlueButton视频会议 vs 华为云会议的详细对比
人工智能·自动化·音视频·实时音视频·信息与通信·视频编解码
网络安全queen1 天前
网络安全-企业环境渗透2-wordpress任意文件读&&FFmpeg任意文件读
安全·web安全·ffmpeg