【FFMpeg视频开发与应用基础】三、调用FFmpeg SDK对H.264格式的视频压缩码流进行解码

《FFMpeg视频开发与应用基础——使用FFMpeg工具与SDK》视频教程已经在“CSDN学院”上线,视频中包含了从0开始逐行代码实现FFMpeg视频开发的过程,欢迎观看!链接地址:FFMpeg视频开发与应用基础——使用FFMpeg工具与SDK

工程代码地址:FFmpeg_Tutorial



经过了上篇调用FFMpeg SDK对视频进行编码的过程之后,我们可以比较容易地理解本篇的内容,即上一篇的逆过程——将H.264格式的裸码流解码为像素格式的图像信息。


1、FFMpeg视频解码器所包含的结构

同FFMpeg编码器类似,FFMpeg解码器也需要编码时的各种结构,除此之外,解码器还需要另一个结构——编解码解析器——用于从码流中截取出一帧完整的码流数据单元。因此我们定义一个编解码上下文结构为:

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

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

2、FFMpeg进行解码操作的主要步骤

(1). 参数传递和解析

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

(2). 按照要求初始化需要的FFMpeg结构

首先,所有涉及到编解码的的功能,都必须要注册音视频编解码器之后才能使用。注册编解码调用下面的函数:

avcodec_register_all();

编解码器注册完成之后,根据指定的CODEC_ID查找指定的codec实例。CODEC_ID通常指定了编解码器的格式,在这里我们使用当前应用最为广泛的H.264格式为例。查找codec调用的函数为avcodec_find_encoder,其声明格式为:

AVCodec *avcodec_find_encoder(enum AVCodecID id);

该函数的输入参数为一个AVCodecID的枚举类型,返回值为一个指向AVCodec结构的指针,用于接收找到的编解码器实例。如果没有找到,那么该函数会返回一个空指针。调用方法如下:

/* find the mpeg1 video encoder */
ctx.codec = avcodec_find_encoder(AV_CODEC_ID_H264); //根据CODEC_ID查找编解码器对象实例的指针
if (!ctx.codec)
{
    fprintf(stderr, "Codec not found\n");
    return false;
}

AVCodec查找成功后,下一步是分配AVCodecContext实例。分配AVCodecContext实例需要我们前面查找到的AVCodec作为参数,调用的是avcodec_alloc_context3函数。其声明方式为:

AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);

其特点同avcodec_find_encoder类似,返回一个指向AVCodecContext实例的指针。如果分配失败,会返回一个空指针。调用方式为:

ctx.c = avcodec_alloc_context3(ctx.codec);          //分配AVCodecContext实例
if (!ctx.c)
{
    fprintf(stderr, "Could not allocate video codec context\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;
}

随后,打开AVCodec对象,然后分配AVFrame对象:

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

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

(3)、解码循环体

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

在最终解析出一个完整的包之后,我们就可以调用解码API进行实际的解码过程了。解码过程调用的函数为avcodec_decode_video2,该函数的声明为:

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: 前面解析好的码流包;

实际调用的方法为:

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("Succeed to decode 1 frame!\n");
}

最后,同编码器一样,解码过程的最后一帧可能也存在延迟。处理最后这一帧的方法也跟解码器类似:将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)

(4). 收尾工作

收尾工作主要包括关闭输入输出文件、关闭FFMpeg解码器各个组件。其中关闭解码器组件需要:

avcodec_close(ctx.pCodecContext);
av_free(ctx.pCodecContext);
av_frame_free(&(ctx.frame));

3、总结

解码器的流程与编码器类似,只是中间需要加入一个解析的过程。整个流程大致为:

1.读取码流数据 -> 2.解析数据,是否尚未解析出一个包就已经用完?是返回1,否继续 -> 3.解析出一个包?是则继续,否则返回上一步继续解析 -> 4.调用avcodec_decode_video2进行解码 -> 5.是否解码出一帧完整的图像?是则继续,否则返回上一步继续解码 -> 6.写出图像数据 -> 返回步骤2继续解析。

时间: 2024-10-28 20:21:15

【FFMpeg视频开发与应用基础】三、调用FFmpeg SDK对H.264格式的视频压缩码流进行解码的相关文章

【FFMpeg视频开发与应用基础】二、调用FFmpeg SDK对YUV视频序列进行编码

<FFMpeg视频开发与应用基础--使用FFMpeg工具与SDK>视频教程已经在"CSDN学院"上线,视频中包含了从0开始逐行代码实现FFMpeg视频开发的过程,欢迎观看!链接地址:FFMpeg视频开发与应用基础--使用FFMpeg工具与SDK 工程代码地址:FFmpeg_Tutorial 视频由像素格式编码为码流格式是FFMpeg的一项基本功能.通常,视频编码器的输入视频通常为原始的图像像素值,输出格式为符合某种格式规定的二进制码流. 1.FFMpeg进行视频编码所需要的

【FFMpeg视频开发与应用基础】七、 调用FFMpeg SDK实现视频水印

<FFMpeg视频开发与应用基础--使用FFMpeg工具与SDK>视频教程已经在"CSDN学院"上线,视频中包含了从0开始逐行代码实现FFMpeg视频开发的过程,欢迎观看!链接地址:FFMpeg视频开发与应用基础--使用FFMpeg工具与SDK Github工程代码地址:FFmpeg_Tutorial 视频的水印通常指附加在原始视频上的可见或者不可见的,与原始视频无直接关联的标识.通常在有线电视画面上电视台的台标以及视频网站上的logo就是典型的视频水印的应用场景.通常实现

【FFMpeg视频开发与应用基础】八、 调用FFMpeg SDK实现视频缩放

<FFMpeg视频开发与应用基础--使用FFMpeg工具与SDK>视频教程已经在"CSDN学院"上线,视频中包含了从0开始逐行代码实现FFMpeg视频开发的过程,欢迎观看!链接地址:FFMpeg视频开发与应用基础--使用FFMpeg工具与SDK Github工程代码地址:FFmpeg_Tutorial 视频缩放是视频开发中一项最基本的功能.通过对视频的像素数据进行采样或插值,可以将低分辨率的视频转换到更高的分辨率,或者将高分辨率的视频转换为更低的分辨率.通过FFMpeg提供

【FFMpeg视频开发与应用基础】五、调用FFMpeg SDK封装音频和视频为视频文件

<FFMpeg视频开发与应用基础--使用FFMpeg工具与SDK>视频教程已经在"CSDN学院"上线,视频中包含了从0开始逐行代码实现FFMpeg视频开发的过程,欢迎观看!链接地址:FFMpeg视频开发与应用基础--使用FFMpeg工具与SDK 工程代码地址:FFmpeg_Tutorial 音频和视频的封装过程为解封装的逆过程,即将独立的音频数据和视频数据按照容器文件所规定的格式封装为一个完整的视频文件的过程.对于大多数消费者来说,视频封装的容器是大家最为熟悉的,因为它直接

【FFMpeg视频开发与应用基础】六、调用FFMpeg SDK实现视频文件的转封装

<FFMpeg视频开发与应用基础--使用FFMpeg工具与SDK>视频教程已经在"CSDN学院"上线,视频中包含了从0开始逐行代码实现FFMpeg视频开发的过程,欢迎观看!链接地址:FFMpeg视频开发与应用基础--使用FFMpeg工具与SDK 工程代码地址:FFmpeg_Tutorial 有时候我们可能会面对这样的一种需求,即我们不需要对视频内的音频或视频信号进行什么实际的操作,只是希望能把文件的封装格式进行转换,例如从avi转换为mp4格式或者flv格式等.实际上,转封

【FFMpeg视频开发与应用基础】一、使用FFmpeg命令行工具和批处理脚本进行简单的音视频文件编辑

<FFMpeg视频开发与应用基础--使用FFMpeg工具与SDK>视频教程已经在"CSDN学院"上线,视频中包含了从0开始逐行代码实现FFMpeg视频开发的过程,欢迎观看!链接地址:FFMpeg视频开发与应用基础--使用FFMpeg工具与SDK 工程代码地址:FFmpeg_Tutorial 1.基本介绍 对于每一个从事音视频技术开发的工程师,想必没有一个人对FFmpeg这个名称感到陌生.FFmpeg是一套非常知名的音视频处理的开源工具,它包含了开发完成的工具软件.封装好的函

VLC 获取播放器H.264格式视频

问题描述 C#调用vlcdll获取本地视频文件的持续播放时间(播放总时间)格式是:h.264 解决方案

ONVIF、RTSP/RTP、FFMPEG的开发实录

前言 本文从零基础一步步实现ONVIF协议.RTSP/RTP协议获取IPC实时视频流.FFMPEG解码.开发环境为WIN7 32位 + VS2010. 最终成功获取浩云.海康.大华的IPC实时视频流. 如果要了解本文更多细节,或者用本文作设计指导,那最好把文中提到的连接都打开,与本文对照着看. 前期准备 1.准备一个ONVIF服务器 既然开发的是客户端,那必需要有服务端了.我这里大把的IPC,好几个品牌的,就随便拿了一个. 如果没有IPC,倒是可以用 VLC media player 搭建一下.

【H.264/AVC视频编解码技术详解】三. H.264简介

<H.264/AVC视频编解码技术详解>视频教程已经在"CSDN学院"上线,视频中详述了H.264的背景.标准协议和实现,并通过一个实战工程的形式对H.264的标准进行解析和实现,欢迎观看! "纸上得来终觉浅,绝知此事要躬行",只有自己按照标准文档以代码的形式操作一遍,才能对视频压缩编码标准的思想和方法有足够深刻的理解和体会! 链接地址:H.264/AVC视频编解码技术详解 本节视频免费 一. H.264视频编码标准 H.264视频编码标准是ITU-T与