同编码器类似,解码器也需要传递参数。不过相比编码器,解码器在运行时所需要的大部分信息都包含在输入码流中,因此输入参数一般只需要指定一个待解码的视频码流文件即可
if (ctx.pCodec->capabilities & AV_CODEC_CAP_TRUNCATED)
ctx.pCodecContext->flags |= AV_CODEC_FLAG_TRUNCATED; // we do not send complete frames
//打开AVCodec对象
if (avcodec_open2(ctx.pCodecContext, ctx.pCodec, NULL) < 0)
{
fprintf(stderr, “Could not open codec\n”);
return false;
}
我们应该记得,在FFMpeg视频编码的实现中,AVCodecContext对象分配完成后,下一步实在该对象中设置编码的参数。而在解码器的实现中,基本不需要额外设置参数信息,因此这个对象更多地作为输出参数接收数据。因此对象分配完成后,不需要进一步的初始化操作。
解码器与编码器实现中不同的一点在于,解码器的实现中需要额外的一个AVCodecParserContext结构,用于从码流中截取一个完整的NAL单元。因此我们需要分配一个AVCodecParserContext类型的对象,使用函数av_parser_init,声明为:
AVCodecParserContext *av_parser_init(int codec_id);
调用方式为:
ctx.pCodecParserCtx = av_parser_init(AV_CODEC_ID_H264);
if (!ctx.pCodecParserCtx)
{
printf(“Could not allocate video parser context\n”);
return false;
}
//分配AVFrame对象
ctx.frame = av_frame_alloc();
if (!ctx.frame)
{
fprintf(stderr, “Could not allocate video frame\n”);
return false;
}
完成必须的codec组件的建立和初始化之后,开始进入正式的解码循环过程。解码循环通常按照以下几个步骤实现:
首先按照某个指定的长度读取一段码流保存到缓存区中。
由于H.264中一个包的长度是不定的,我们读取一段固定长度的码流通常不可能刚好读出一个包的长度。所以我们就需要使用AVCodecParserContext结构对我们读出的码流信息进行解析,直到取出一个完整的H.264包。对码流解析的函数为av_parser_parse2,声明方式如:
int av_parser_parse2(AVCodecParserContext *s,
AVCodecContext *avctx,
uint8_t **poutbuf, int *poutbuf_size,
const uint8_t *buf, int buf_size,
int64_t pts, int64_t dts,
int64_t pos);
这个函数的各个参数的意义:
AVCodecParserContext *s:初始化过的AVCodecParserContext对象,决定了码流该以怎样的标准进行解析;
AVCodecContext *avctx:预先定义好的AVCodecContext对象;
uint8_t **poutbuf:AVPacket::data的地址,保存解析完成的包数据;
int *poutbuf_size:AVPacket的实际数据长度;如果没解析出完整的一个包,这个值为0;
const uint8_t *buf, int buf_size:输入参数,缓存的地址和长度;
int64_t pts, int64_t dts:显示和解码的时间戳;
nt64_t pos :码流中的位置;
返回值为解析所使用的比特位的长度;
具体的调用方式为:
len = av_parser_parse2(ctx.pCodecParserCtx, ctx.pCodecContext,
&(ctx.pkt.data), &(ctx.pkt.size),
pDataPtr, uDataSize,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
如果参数poutbuf_size的值为0,那么应继续解析缓存中剩余的码流;如果缓存中的数据全部解析后依然未能找到一个完整的包,那么继续从输入文件中读取数据到缓存,继续解析操作,直到pkt.size不为0为止。
void write_out_yuv_frame(const CodecCtx &ctx, IOParam &in_out)
{
uint8_t ***pBuf = ctx.frame->data;
int** pStride = ctx.frame->linesize;
for (int color_idx = 0; color_idx < 3; color_idx++)
{
int nWidth = color_idx = 0 ? ctx.frame->width : ctx.frame->width / 2;
0 ? ctx.frame->height : ctx.frame->height / 2;
int nHeight = color_idx =
for(int idx=0;idx < nHeight; idx++)
{
fwrite(pBuf[color_idx],1, nWidth, in_out.pFout);
pBuf[color_idx] += pStride[color_idx]; //可能有空白数据填充,要跳过
}
fflush(in_out.pFout);
}
}
最后,同编码器一样,解码过程的最后一帧可能也存在延迟。处理最后这一帧的方法也跟解码器类似:将AVPacket::data设为NULL,AVPacket::size设为0,然后在调用avcodec_encode_video2完成最后的解码过程:
ctx.pkt.data = NULL;
ctx.pkt.size = 0;
while(1)
{
//将编码器中剩余的数据继续输出完
int ret = avcodec_decode_video2(ctx.pCodecContext, ctx.frame, &got_picture, &(ctx.pkt));
if (ret < 0)
{
printf(“Decode Error.\n”);
return ret;
}
if (got\_picture)
{
write\_out\_yuv\_frame(ctx, inputoutput);
printf("Flush Decoder: Succeed to decode 1 frame!\n");
}
else
{
break;
}
} //while(1)
收尾工作主要包括关闭输入输出文件、关闭FFMpeg解码器各个组件。其中关闭解码器组件需要:
avcodec_close(ctx.pCodecContext);
av_free(ctx.pCodecContext);
av_frame_free(&(ctx.frame));