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的升级,剩下的就是不断的测试,发现新特性,发现升级之后带来的新问题了。