使用ffmpeg接口将h.264解码为YUV

参数传递和解析

同编码器类似,解码器也需要传递参数。不过相比编码器,解码器在运行时所需要的大部分信息都包含在输入码流中,因此输入参数一般只需要指定一个待解码的视频码流文件即可

按照要求初始化需要的FFMpeg结构

第一步: 获取解码器指针。根据编解码器ID,获取编解码器指针

第二步: 获取解码器上下文。AVCodecContext实例。通过编解码器AVCodec指针,获取编解码器上下文

第三步: 设置 AVCodecContext可以截断方式读取数据

if (ctx.pCodec->capabilities & AV_CODEC_CAP_TRUNCATED)
ctx.pCodecContext->flags |= AV_CODEC_FLAG_TRUNCATED; // we do not send complete frames

第四步: 打开解码器,不需要像编码一样设置 AVCodecContext参数。

//打开AVCodec对象
if (avcodec_open2(ctx.pCodecContext, ctx.pCodec, NULL) < 0)
{
fprintf(stderr, “Could not open codec\n”);
return false;
}

第五步: 获取 AVCodecParserContext结构,解析h.264 NAL,生成可供解码器解码的AVPacket

我们应该记得,在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对象

//分配AVFrame对象
ctx.frame = av_frame_alloc();
if (!ctx.frame)
{
fprintf(stderr, “Could not allocate video frame\n”);
return false;
}

解码循环体

第一步: 根据码流内存数据,解析出一个完整的H.264包(AVPacket)

完成必须的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为止。

第二步: 解码

第三步: 将解码之后的 AVFrame写入文件

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;
int nHeight = color_idx =
0 ? ctx.frame->height : ctx.frame->height / 2;
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));

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值