1.简介
解码如下图所示,将H.264数据解码为YUV。
2.流程
2.1 在使用FFmpeg API之前,需要先注册API,然后才能使用API。当然,新版本的库不需要再调用下面的方法。
av_register_all()
2.2 构建输入AVFormatContext
声明输入的封装结构体,通过输入文件或者流地址作为封装结构的句柄。
AVFormatContext* ifmt_ctx = NULL;
const char* inputUrl = "test.mp4";
///打开输入的流
int ret = avformat_open_input(&ifmt_ctx, inputUrl, NULL, NULL);
if (ret != 0)
{
printf("Couldn't open input stream.\n");
return -1;
}
2.3查找音视频流信息,通过下面的接口与AVFormatContext中建立输入文件对应的流信息。
//查找;
if (avformat_find_stream_info(inputFmtCtx, NULL) < 0)
{
printf("Couldn't find stream information.\n");
return -1;
}
2.4查找解码器
先找到视频流索引,找到视频流,根据视频流的codec_id找到解码器。
//找到视频流索引
int video_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
AVStream* st = ifmt_ctx->streams[video_index];
AVCodec* codec = nullptr;
//找到解码器
codec = avcodec_find_decoder(st->codecpar->codec_id);
if (!codec)
{
fprintf(stderr, "Codec not found\n");
exit(1);
}
2.5申请AVCodecContenxt
//申请AVCodecContext
AVCodecContext* codec_ctx = nullptr;
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx)
{
exit(1);
}
2.6同步AVCodecParameters
FFmpeg在解码时获取的相关编码信息,首先存储到AVCodecParameters中,然后对AVCodecParameters中存储的信息进行解析与处理,为了兼容,将AVCodecParameters的参数同步到AVCodecContext中。
avcodec_parameters_to_context(codec_ctx, ifmt_ctx->streams[video_index]->codecpar);
2.7打开解码器
解码器参数设置完成后,就需要打开解码器
//打开解码器
if ((ret = avcodec_open2(codec_ctx, codec, NULL) < 0))
{
return -1;
}
2.8然后通过while循环,不停的读取数据
av_read_frame(ifmt_ctx, pkt)
2.9帧解码
通过下面两个接口解码,获取到解码后的帧数据。
avcodec_send_packet(codec_ctx, pkt);
avcodec_receive_frame(codec_ctx, frame);
3.源码
此源码演示解码后的数据保存到本地,以yuv为结尾的文件。
#include "pch.h"
#include <iostream>
extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/dict.h"
#include "libavutil/opt.h"
#include "libavutil/timestamp.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavutil/imgutils.h"
};
int main()
{
//av_register_all();
avformat_network_init();
AVFormatContext* ifmt_ctx = NULL;
const char* inputUrl = "test.mp4";
///打开输入的流
int ret = avformat_open_input(&ifmt_ctx, inputUrl, NULL, NULL);
if (ret != 0)
{
printf("Couldn't open input stream.\n");
return -1;
}
//查找流信息
if (avformat_find_stream_info(ifmt_ctx, NULL) < 0)
{
printf("Couldn't find stream information.\n");
return -1;
}
//找到视频流索引
int video_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
AVStream* st = ifmt_ctx->streams[video_index];
AVCodec* codec = nullptr;
//找到解码器
codec = avcodec_find_decoder(st->codecpar->codec_id);
if (!codec)
{
fprintf(stderr, "Codec not found\n");
exit(1);
}
//申请AVCodecContext
AVCodecContext* codec_ctx = nullptr;
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx)
{
exit(1);
}
avcodec_parameters_to_context(codec_ctx, ifmt_ctx->streams[video_index]->codecpar);
//打开解码器
if ((ret = avcodec_open2(codec_ctx, codec, NULL) < 0))
{
return -1;
}
AVPacket* pkt = av_packet_alloc();
//av_init_packet(pkt);
AVFrame *frame = av_frame_alloc();
while (av_read_frame(ifmt_ctx, pkt) >= 0)
{
if (pkt->stream_index == video_index)
{
int ret = avcodec_send_packet(codec_ctx, pkt);
if (ret >= 0)
{
ret = avcodec_receive_frame(codec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
break;
}
else if (ret < 0)
{
break;
}
switch (codec_ctx->pix_fmt)
{
case AV_PIX_FMT_YUV420P:
{
int size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, frame->width, frame->height, 1);
uint8_t* yuvbuf = (uint8_t*)av_malloc(size);
int i, j, k = 0;
for (i = 0; i < frame->height; i++)
{
memcpy(yuvbuf + frame->width * i, frame->data[0] + frame->linesize[0] * i, frame->width);
}
for (j = 0; j < frame->height / 2; j++)
{
memcpy(yuvbuf + frame->width * i + frame->width / 2 * j,frame->data[1] + frame->linesize[1] * j,frame->width / 2);
}
for (k = 0; k < frame->height / 2; k++)
{
memcpy(yuvbuf + frame->width * i + frame->width / 2 * j + frame->width / 2 * k,frame->data[2] + frame->linesize[2] * k,frame->width / 2);
}
char fileName[20] = { 0 };
sprintf(fileName, "img/%d.yuv", codec_ctx->frame_number);
FILE* f;
f = fopen(fileName, "wb");
fwrite(yuvbuf,1, size,f);
fclose(f);
av_free(yuvbuf);
}
break;
default:
return -1;
}
}
}
}
avcodec_close(codec_ctx);
avcodec_free_context(&codec_ctx);
avformat_close_input(&ifmt_ctx);
av_frame_free(&frame);
av_packet_free(&pkt);
return 0;
}