文章

FSPlayer 升级 FFmpeg 7 全过程

FSPlayer 升级 FFmpeg 7 全过程

今天刚把 FSPlayer 依赖的 FFmpeg 从 6.1.1 升级到了 7.1.1,记录了升级过程中遇到的一些问题,由于 FSPlayer 大部分代码来自于 ijkplayer,所以也适用于 ijkplayer 升级到 7 代 FFmpeg。

编译问题汇总

1、新的编解码器 ID 必须添加到列表末尾

1
2
libavcodec/version.c:38:19: error: static assertion failed due to requirement 'AV_CODEC_ID_QOA == 86121': 
Don't insert new codec ids in the middle of a list

FFmpeg 采用了严格的编解码器 ID 管理策略,要求新的编解码器 ID 必须添加到列表末尾,禁止在现有 ID 序列中间插入新 ID。这样做是为了保证 ID 的稳定性,避免破坏向后兼容性。

2、新增 dvdvideo 解复用器

之前通过定义 dvd:// 协议的方式支持了 dvd 视频的播放,但支持的不够好,有些视频时长不对,这次想试下 FFmpeg 提供的内置 dvdvideo解复用器。编译时需要打开选项:

1
--enable-libdvdread --enable-libdvdnav --enable-demuxer=dvdvideo --enable-gpl

之前支持 dvd协议时,只编译了 libdvdread库,发现不编译 libdvdnav 会收到下面的警告,导致 dvdvideo 没有开启:

WARNING: Disabled dvdvideo_demuxer because not all dependencies are satisfied: libdvdnav libdvdread

所以又编译了 libdvdnav 库,libdvdnav库依赖 libdvdread库,编译时要配置好依赖。

编译好之后,测试了下之前有问题的 dvd 视频都可以正常播放了,之前会遇到时间不对问题。

3、支持videotoolbox硬解AV1

从FFmpeg7.1.1 开始支持硬解av1,需要M3芯片,编译时打开 ` –enable-decoder=av1` 选项即可,手上目前没有 M3的机器还无法证实。

4、AVInputFormat 结构发生了大的变化,注册dumxer的方法需要改下,具体改动放到下面,跟上层调用放一起说。

调用 API 遇到的问题

1、AVCodecContext 的一些属性被移除了,包括:pts_correction_num_faulty_dts、channels、 channel_layout

2、AVFrame 的 channel_layout属性被移除了,需要通过 frame->ch_layout 做适配,比如判断 AV_CH_LAYOUT_5POINT1_BACK 的逻辑变得复杂了:

1
2
3
4
if (af->frame->ch_layout.order == AV_CHANNEL_ORDER_NATIVE &&
        af->frame->ch_layout.nb_channels == 6 &&
        af->frame->ch_layout.u.mask & AV_CH_LAYOUT_5POINT1_BACK) {
}

3、AVInputFormat 结构变化很大,里面的方法基本都移除了,现在自定义一个 InputFormat 需要声明 FFInputFormat 结构体了,这个结构体里面的 p 属性是一个 AVInputFormat。这是 FFInputFormat 的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
typedef struct FFInputFormat {
    /**
     * The public AVInputFormat. See avformat.h for it.
     */
    AVInputFormat p;

    /**
     * Raw demuxers store their codec ID here.
     */
    enum AVCodecID raw_codec_id;

    /**
     * Size of private data so that it can be allocated in the wrapper.
     */
    int priv_data_size;

    /**
     * Internal flags. See FF_INFMT_FLAG_* above and FF_FMT_FLAG_* in internal.h.
     */
    int flags_internal;

    /**
     * Tell if a given file has a chance of being parsed as this format.
     * The buffer provided is guaranteed to be AVPROBE_PADDING_SIZE bytes
     * big so you do not have to check for that unless you need more.
     */
    int (*read_probe)(const AVProbeData *);

    /**
     * Read the format header and initialize the AVFormatContext
     * structure. Return 0 if OK. 'avformat_new_stream' should be
     * called to create new streams.
     */
    int (*read_header)(struct AVFormatContext *);

    int (*read_header2)(struct AVFormatContext *, AVDictionary **options);

    /**
     * Read one packet and put it in 'pkt'. pts and flags are also
     * set. 'avformat_new_stream' can be called only if the flag
     * AVFMTCTX_NOHEADER is used and only in the calling thread (not in a
     * background thread).
     * @return 0 on success, < 0 on error.
     *         Upon returning an error, pkt must be unreferenced by the caller.
     */
    int (*read_packet)(struct AVFormatContext *, AVPacket *pkt);

    /**
     * Close the stream. The AVFormatContext and AVStreams are not
     * freed by this function
     */
    int (*read_close)(struct AVFormatContext *);

    /**
     * Seek to a given timestamp relative to the frames in
     * stream component stream_index.
     * @param stream_index Must not be -1.
     * @param flags Selects which direction should be preferred if no exact
     *              match is available.
     * @return >= 0 on success (but not necessarily the new offset)
     */
    int (*read_seek)(struct AVFormatContext *,
                     int stream_index, int64_t timestamp, int flags);

    /**
     * Get the next timestamp in stream[stream_index].time_base units.
     * @return the timestamp or AV_NOPTS_VALUE if an error occurred
     */
    int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index,
                              int64_t *pos, int64_t pos_limit);

    /**
     * Start/resume playing - only meaningful if using a network-based format
     * (RTSP).
     */
    int (*read_play)(struct AVFormatContext *);

    /**
     * Pause playing - only meaningful if using a network-based format
     * (RTSP).
     */
    int (*read_pause)(struct AVFormatContext *);

    /**
     * Seek to timestamp ts.
     * Seeking will be done so that the point from which all active streams
     * can be presented successfully will be closest to ts and within min/max_ts.
     * Active streams are all streams that have AVStream.discard < AVDISCARD_ALL.
     */
    int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);

    /**
     * Returns device list with it properties.
     * @see avdevice_list_devices() for more details.
     */
    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);
} FFInputFormat;

下面是 AVInputFormat 的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
typedef struct AVInputFormat {
    /**
     * A comma separated list of short names for the format. New names
     * may be appended with a minor bump.
     */
    const char *name;

    /**
     * Descriptive name for the format, meant to be more human-readable
     * than name. You should use the NULL_IF_CONFIG_SMALL() macro
     * to define it.
     */
    const char *long_name;

    /**
     * Can use flags: AVFMT_NOFILE, AVFMT_NEEDNUMBER, AVFMT_SHOW_IDS,
     * AVFMT_NOTIMESTAMPS, AVFMT_GENERIC_INDEX, AVFMT_TS_DISCONT, AVFMT_NOBINSEARCH,
     * AVFMT_NOGENSEARCH, AVFMT_NO_BYTE_SEEK, AVFMT_SEEK_TO_PTS.
     */
    int flags;

    /**
     * If extensions are defined, then no probe is done. You should
     * usually not use extension format guessing because it is not
     * reliable enough
     */
    const char *extensions;

    const struct AVCodecTag * const *codec_tag;

    const AVClass *priv_class; ///< AVClass for the private context

    /**
     * Comma-separated list of mime types.
     * It is used check for matching mime types while probing.
     * @see av_probe_input_format2
     */
    const char *mime_type;
} AVInputFormat;

在学 C语言的时候,清楚的记得老师讲过:数组的第一个元素的地址和所在数组的地址相同,结构的第一个属性的地址和所在结构体地址相同,当时只是知道这么回事,好像从来没用过,这次在 FFmpeg 的代码里算出开了眼界了:

1
2
3
4
static inline const FFInputFormat *ffifmt(const AVInputFormat *fmt)
{
    return (const FFInputFormat*)fmt;
}

就这一个方法,干了一件很牛逼的事情,不需要访问 FFInputFormat 的时候,外部都用 AVInputFormat即可,在需要 FFInputFormat 的时候,顺手拈来啊!

这是适配 FFmpeg 7 之后的:

1
2
3
4
5
6
7
8
9
10
11
FFInputFormat ijkff_ijklivehook_demuxer = {
    .p.name           = "ijklivehook",
    .p.long_name      = "Live Hook Controller",
    .p.flags          = AVFMT_NOFILE | AVFMT_TS_DISCONT,
    .p.priv_class     = &ijklivehook_class,
    .priv_data_size = sizeof(Context),
    .read_probe     = ijklivehook_probe,
    .read_header2   = ijklivehook_read_header,
    .read_packet    = ijklivehook_read_packet,
    .read_close     = ijklivehook_read_close,
};

这是对 FFmpeg 源码 ijkutil.c 里的配套修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#define IJK_FF_DEMUXER(x)                                                                          \
extern FFInputFormat ff_##x##_demuxer;                                                               \
int ijkav_register_##x##_demuxer(FFInputFormat *demuxer, int demuxer_size);                        \
int ijkav_register_##x##_demuxer(FFInputFormat *demuxer, int demuxer_size)                         \
{                                                                                                   \
    if (demuxer_size != sizeof(FFInputFormat)) {                                                     \
        av_log(NULL, AV_LOG_ERROR, "ijkav_register_##x##_demuxer: ABI mismatch.\n");               \
        return -1;                                                                                  \
    }                                                                                               \
    memcpy(&ff_##x##_demuxer, demuxer, demuxer_size);                                            \
    return 0;                                                                                       \
}

#define IJK_DUMMY_DEMUXER(x)                                        \
IJK_FF_DEMUXER(x);                                                  \
static const AVClass ijk_##x##_demuxer_class = {                    \
    .class_name = #x,                                               \
    .item_name  = av_default_item_name,                             \
    .version    = LIBAVUTIL_VERSION_INT,                            \
    };                                                              \
                                                                    \
FFInputFormat ff_##x##_demuxer = {                                  \
    .p.name         = #x,                                           \
    .p.priv_class   = &ijk_##x##_demuxer_class,                     \
    .priv_data_size = 1,                                            \
    .flags_internal = FF_INFMT_FLAG_INIT_CLEANUP,                   \
};

这是上层调用时的宏,也需要修改成 FFInputFormat:

1
2
3
4
5
6
#define FS_REGISTER_DEMUXER(x)                                         \
    {                                                                   \
        extern FFInputFormat ijkff_##x##_demuxer;                       \
        int ijkav_register_##x##_demuxer(FFInputFormat *demuxer, int demuxer_size); \
        ijkav_register_##x##_demuxer(&ijkff_##x##_demuxer, sizeof(FFInputFormat)); \
    }

经过上面的修改,fsplayer 完成了 FFmpeg 7.1.1的升级,剩下的就是不断的测试,发现新特性,发现升级之后带来的新问题了。

本文由作者按照 CC BY 4.0 进行授权

© debugly. 保留部分权利。

本站采用 Jekyll 主题 Chirpy