一、解码流程总览
二、解码流程分解
第一步:注册
使用FFmpeg对应的库,都需要进行注册,注册了这个才能正常使用编码器和解码器;
///第一步
av_register_all();
第二步:打开文件
打开文件,根据文件名信息获取对应的FFmpeg全局上下文
///第二步
AVFormatContext *pFormatCtx; //文件上下文,描述了一个媒体文件或媒体流的构成和基本信息
pFormatCtx = avformat_alloc_context(); //分配指针
if (avformat_open_input(&pFormatCtx, file_path, NULL, NULL) != 0) { //打开文件,信息存储到文件上下文中,后续对针对文件上下文即可
printf("无法打开文件");
return -1;
}
第三步:探测流信息
一定要探测流信息,拿到流编码的编码格式,不探测流信息则器流编码器拿到的编码类型可能为空,后续进行数据转换的时候就无法知晓原始格式,导致错误;
///第三步
//探寻文件中是否存在信息流
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
printf("文件中没有发现信息流");
return -1;
}
//探寻文件中是否存储视频流
int videoStream = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
}
}
//如果videoStream为-1 说明没有找到视频流
if (videoStream == -1) {
printf("文件中未发现视频流");
return -1;
}
//探寻文件中是否存在音频流
int audioStream = -1
for (i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
audioStream = i;
}
}
//如果audioStream 为-1 说明没有找到音频流
if (audioStream == -1) {
printf("文件中未发现音频流");
return -1;
}
第四步:查找对应的解码器
依据流的格式查找解码器,软解码还是硬解码是在此处决定的,但是特别注意是否支持硬件,需要自己查找本地的硬件解码器对应的标识,并查询其是否支持。普遍操作是,枚举支持文件后缀解码的所有解码器进行查找,查找到了就是可以硬解了;
注意:解码时查找解码器,编码时查找编码器,两者函数不同,不要弄错了,否则后续能打开但是数据是错的;
///第四步
AVCodecContext *pCodecCtx; //描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息
AVCodec *pCodec; //存储编解码器信息的结构体
//查找解码器
pCodecCtx = pFormatCtx->streams[videoStream]->codec; //获取视频流中编码器上下文
pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //获取视频流的编码器信息
if (pCodec == NULL) {
printf("未发现编码器");
return -1;
}
第五步:打开解码器
打开获取到的解码器
///第五步
//打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("无法打开编码器");
return -1;
}
第六步:申请缩放数据格式转换结构体
基本上解码的数据都是yuv系列格式,但是我们显示的数据是rgb等相关颜色空间的数据,所以此处转换结构体就是进行转换前导转换后的描述,给后续转换函数提供转码依据,是很关键并且非常常用的结构体;
///第六步
static struct SwsContext *img_convert_ctx; //用于视频图像的转换
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
AV_PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL);
第七步:计算缩放颜色空间转换后缓存大小
///第七步
int numBytes; //字节数
numBytes = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width,pCodecCtx->height);
第八步:申请缓存区,将AVFrama的data映射到单独的outBuffer上
申请一个缓存区outBuffer,fill到我们目标帧数据的data上,比如rgb数据,QAVFrame的data上存的是有指定格式的数据且存储有规则,而fill到outBuffer(自己申请的目标格式一帧缓存区),则是我们需要的数据格式存储顺序;
例如:解码转换后的数据为rgb888,实际直接使用data数据是错误的,但是用outBuffer就是对的,所以此处应该是FFmpeg的fill函数做了一些转换;
///第七步
AVFrame *pFrame, *pFrameRGB; //存储音视频原始数据(即未被编码的数据)的结构体
pFrame = av_frame_alloc();
pFrameRGB = av_frame_alloc();
uint8_t *out_buffer; //缓存
out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_BGR24,
pCodecCtx->width, pCodecCtx->height);
第九步:循环解码
1、获取一帧packet
int y_size = pCodecCtx->width * pCodecCtx->height;
packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
av_new_packet(packet, y_size); //分配packet的数据
int ret, got_picture;
while(1) {
if (av_read_frame(pFormatCtx, packet) < 0) { //读取一帧packet数据包
break; //这里认为视频读取完了
}
......
}
2、解码获取原始数据
int y_size = pCodecCtx->width * pCodecCtx->height;
packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
av_new_packet(packet, y_size); //分配packet的数据
int ret, got_picture;
while(1) {
if (av_read_frame(pFormatCt