一、视频解码流程
使用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);
}
}