代码改变世界

FFMpeg笔记(十三)升级FFmpeg6.1

2024-03-24 11:21  jiayayao  阅读(153)  评论(0编辑  收藏  举报

    FFmpeg最新版已更新6.1,许多之前标记为deprecated的api被彻底删除了,同时也增加了很多新的特性和功能。FFmpeg团队持续优化代码,新版本往往也具有更高的编码和解码效率,更多的格式支持,因此及时更新FFmpeg版本,有利于提升应用的处理速度和资源利用率。本文记录项目中FFmpeg更新至6.1版本过程中遇到的问题。

一、FFmpeg6.1已废弃的api

#av_dup_packet
#av_frame_get_channels
#av_free_packet
#av_get_codec_tag_string
#av_lockmgr_register
#av_packet_split_side_data
#avcodec_copy_context
#avfilter_register_all
#avpicture_fill
#avcodec_encode_video2
#avcodec_encode_audio2
#avpicture_get_size
#avcodec_register
#avcodec_decode_video2

二、AVStream使用codecpar来替换codec

    AVStream和AVCodec,一个保存流信息,一个保存解码器实例和参数。在之前的版本中AVStream的codec指针保存了解码器实例,这意味着如果要创建一个新的AVStream对象,就要创建一个AVCodec对象。流信息和解码器实例本身不具备强耦合关系,因此只使用一个codecpar变量只保存解码器参数更为合适。

    在之前的版本中,两种方式均可,在6.1版本AVStream结构体中保留的codec对象被彻底“删除”。这里的“删除”是指放到了AVStream的内部,不允许外部获取。因为新版本增加了一种FFStream结构体,用来保护AVStream结构体的“私有”变量,不允许外部获取。

三、FFStream来获取AVStream的“私有”变量

    这是FFmpeg新的设计思路。类似AVFormatContext、AVStream、AVIOContext这种对外的结构体,设计者不愿意用户获取和更改部分变量,因此为重要的结构体设计了一个以FF开头的内部结构体,用于保护FFmpeg的内部变量。以FFStream举例:

typedef struct FFStream {
    /**
     * The public context.
     */
    AVStream pub;

    ......
}

    AVStream成员始终是FFStream的首个变量,持有AVStream指针时,可以通过调用ffstream函数获取FFStream结构体,当然也就可以获取AVStream内部的成员。

static av_always_inline FFStream *ffstream(AVStream *st)
{
    return (FFStream*)st;
}

    需要注意的是,正常编译的FFmpeg是不可以使用ffstream函数的,如果要使用,需要在Makefile中将internal.h文件输出。

四、通过memcpy获取到的AVIOContext,调用avio_seek时崩溃

     项目中有使用memcpy AVIOContext的代码,继续使用时,调用avio_seek发生崩溃。分析后,确认是上述第三点的设计思路引起的崩溃。在之前的版本没有FF内部结构体,通过memcpy可能没问题。但是新版本通过memcpy只是拷贝了AVIOContext,没有拷贝其内部FF结构体。执行avio_seek时,获取到的FFIOContext是一个野指针,从而崩溃。

int64_t avio_seek(AVIOContext *s, int64_t offset, int whence)
{
    FFIOContext *const ctx = ffiocontext(s); #通过memcpy获取到的FFIOContext是一个野指针
    ......
    short_seek = ctx->short_seek_threshold;
    if (ctx->short_seek_get) { 
        int tmp = ctx->short_seek_get(s->opaque);#调用野指针发生崩溃
        short_seek = FFMAX(tmp, short_seek);
    }
......
return offset; }

    因此,开发者应该严格遵循FFmpeg提供的pubblic api使用方法,通过memcpy结构体的调用是危险的。

五、side data

    side data在FFmpeg被用在加密和解密等作用。其中av_stream_get_side_data虽已标记为废弃,但仍在兼容。但坑爹的是,这个兼容是放到了avformat_find_stream_info结束时兼容,而项目是在流解析时就已经用到此函数,导致播放异常。分析后,采用av_packet_side_data_get替换。

六、音频编码失败

    新版本音频的channel_layout定义由变化,笔者猜测变化的原因是channel_layout和channels都可以看出来声道个数,一个概念用两个变量来描述容易引起开发者的疑惑。

    新版本对channel_layout和channels两个成员均标记为废弃,其内部封装了channel_layout,采用如下方法来简洁定义:

AVChannelLayout ch_layout = AV_CHANNEL_LAYOUT_MONO;
av_channel_layout_copy(&audio_codec_ctx->ch_layout, &ch_layout);

七、变量类型不匹配导致栈损坏

    av_packet_get_side_data最后一个参数size类型是size_t,但在老版本上是int,代码沿用时,发生崩溃。分析av_packet_get_side_data函数内部对参数size进行了写入操作,引起了栈损坏。但在编译时,FFmpeg没有报错!!

    因此,增加编译参数-Werror=incompatible-pointer-types,解决了所有类型不匹配问题。