002 ffmpeg_videodecoding

功能是把h264文件变成yuv文件,测试命令行:

./video_decoding ds_480x272.h264 res.ds_480x272.yuv

我用PotPlayer来播放h264文件,yuv文件通过ffplay或者YUVplayer来播放。

这次不从main函数开始了,从一个函数,属于ffmpeg函数初始化和结束的部分。

typedef struct
{
    AVCodec         *pCodec;                //编解码器实例指针
    AVCodecContext  *pCodecContext;         //编解码器上下文,指定了编解码的参数
    AVCodecParserContext *pCodecParserCtx;  //编解码解析器,从码流中截取完整的一个NAL Unit数据

    AVFrame         *frame;                 //封装图像对象指针
    AVPacket        pkt;                    //封装码流对象实例
} CodecCtx;

这是自定义的Codec上下文,从头到尾为了方便使用。看它如何初始化的,Open_deocder,一步一步的来。

bool Open_deocder(CodecCtx &ctx)
{
    avcodec_register_all();

1 注册编解码器对象

    av_init_packet(&(ctx.pkt));

2 初始化AVPacket对象,保存编码前的数据,也就是yuv数据。记住pkt不是一个指针,对于它的free比较纳闷。

    ctx.pCodec = avcodec_find_decoder(AV_CODEC_ID_H264);

3 根据CODEC_ID查找AVCodec对象

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

4 根据AVCodec对象分配AVCodecContext

    ctx.pCodecParserCtx = av_parser_init(AV_CODEC_ID_H264);

3 根据CODEC_ID初始化AVCodecParserContext对象

    if (avcodec_open2(ctx.pCodecContext, ctx.pCodec, NULL) < 0)

6 打开AVCodec对象

    ctx.frame = av_frame_alloc();
    return true;
}

7 分配AVFrame对象,保存编码后的数据。

void Close_decoder(CodecCtx &ctx)
{
    avcodec_close(ctx.pCodecContext);
    av_free(ctx.pCodecContext);
    av_frame_free(&(ctx.frame));
}

Close_decoder属于结尾部分,通过AVCodecContext来关闭codec。之后,free掉AVFrame。

对于codec初始化,涉及到三个结构:AVCodec,AVCodecContext和AVCodecParserContext。都需要进行初始化。之后开始进行循环部分,读取数据后编码。在main函数里面。

int main(int argc, char **argv)
{
    ...
    while(1)
    {
        uDataSize = fread(inbuf,1,INBUF_SIZE,  inputoutput.pFin);

将码流文件按某长度读入输入缓存区,uDataSize为读取的数据长度。

        pDataPtr = inbuf;
        while(uDataSize > 0)
        {
            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);
            pDataPtr += len;
            uDataSize -= len;

解析缓存区中的数据为AVPacket对象。最后pDataPtr指针偏移,说明已经解析了。

            int ret = avcodec_decode_video2(ctx.pCodecContext, ctx.frame, &got_picture, &(ctx.pkt));
            if (got_picture) 
            {                
                write_out_yuv_frame(ctx, inputoutput);
            }
        } 
    }
    ...
}

根据AVCodecContext的设置,解析AVPacket中的码流,输出到AVFrame。如果got_picture为真,则已经获得一帧完整的图像,写出到输出文件。通过 write_out_yuv_frame 输出的文件中。看一看:

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);
    }
}

这个函数过一眼就ok。

最后上几个ffmpeg的重要函数:

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 :码流中的位置;
返回值为解析所使用的比特位的长度;
*/
int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
                     int *got_picture_ptr,
                     const AVPacket *avpkt);

//这个函数与前篇所遇到的编码函数avcodec_encode_video2有些类似,只是参数的顺序略有不同,解码函数的输入输出参数与编码函数相比交换了位置。该函数各个参数的意义:
//AVCodecContext *avctx:编解码器上下文对象,在打开编解码器时生成;
//AVFrame *picture: 保存解码完成后的像素数据;我们只需要分配对象的空间,像素的空间codec会为我们分配好;
//int *got_picture_ptr: 标识位,如果为1,那么说明已经有一帧完整的像素帧可以输出了
//const AVPacket *avpkt: 前面解析好的码流包;

最后解决一个问题,CodecCtx.pkt是保存yuv数据的,从文件中读取得来。CodecCtx.pkt.data没有new内存,使用的还是读数据时放数据的内存地址。所以不用delete掉。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值