- 音视频
【免费下载链接】vlc
VLC media player - plays everything, runs anywhere. Code here: https://code.videolan.org/videolan/vlc
LibVLC 4.0 对公共 API 进行了大规模破坏性重构:全局事件系统被回调结构体取代、时间单位统一为微秒、解析与缩略图生成被抽离为独立的libvlc_parser_t对象。本文以 VLC 仓库中的官方迁移文档 doc/libvlc/CHANGES-v3-to-v4.md 为主体,结合 include/vlc/libvlc_parser.h、include/vlc/libvlc_media_player.h 等头文件与doc/libvlc/下的移植示例代码,逐条梳理被移除、重命名、重构的 API,讲解新事件模型的版本化回调机制、微秒时间单位的迁移要点、libvlc_parser_t的完整用法,并给出可直接照做的分步迁移清单。读完本文,你将能够把基于 LibVLC 3 编写的播放器、媒体库与缩略图工具平滑迁移到 LibVLC 4。
1. 三大结构性变化:迁移前必须理解的全局改动
有三项改动贯穿整个公共 API,几乎所有非平凡的应用都会被触及:
libvlc_event_manager_t/libvlc_event_attach()事件系统被彻底移除。每一个 LibVLC 对象(媒体播放器、媒体发现器、渲染器发现器、媒体列表播放器、解析器、媒体)在创建时都必须显式传入一个带版本号的回调结构体(*_cbs)。libvlc_time_t在全 API 范围内统一使用微秒。LibVLC 3 在公共 API 中混用毫秒与微秒;4.0 中,libvlc_time_t(即int64_t)成为全 API 唯一的时间单位类型。- 新增独立的
libvlc_parser_tAPI。解析(parsing)与缩略图(thumbnail)生成从libvlc_media_t中剥离出来,归入独立 API。一个解析器对象拥有自己的线程池,可以同时服务大量并发的解析与缩略图请求,详见第 6 节。
这三项改动决定了后续所有迁移工作的基本方向:改写事件处理、修正时间单位、迁移解析逻辑。
2. 被移除的 API 与替代方案
2.1 全局事件系统整体消失
<vlc/libvlc_events.h>中的一切都被移除,该头文件已被删除。移除的符号包括:
libvlc_event_manager_tlibvlc_event_t/libvlc_event_type_tlibvlc_callback_tlibvlc_event_attach()/libvlc_event_detach()libvlc_media_event_manager()、libvlc_media_list_event_manager()、libvlc_media_player_event_manager()(以及其他所有对象上的等价函数)- 全部事件枚举值:
libvlc_MediaPlayerPlaying、libvlc_MediaParsedChanged、libvlc_MediaListItemAdded、libvlc_MediaListEndReached、libvlc_MediaPlayerMediaChanged等
迁移方案:改用对象专属的回调结构体(*_cbs),在对象创建时传入。各对象的映射关系见第 4 节。
2.2libvlc_media_t上的解析/缩略图功能被移除
从<vlc/libvlc_media.h>中移除:
libvlc_media_parse_request()libvlc_media_parse_stop()libvlc_media_thumbnail_request_tlibvlc_media_thumbnail_request_by_time()libvlc_media_thumbnail_request_by_pos()libvlc_media_thumbnail_request_destroy()
旧枚举libvlc_media_parse_flag_t中的libvlc_media_parse_local、libvlc_media_parse_network、libvlc_media_parse_forced三个值也已消失,新的标志集合在 include/vlc/libvlc_parser.h 中重新定义(见下文的标志表)。
迁移方案:改用 include/vlc/libvlc_parser.h 中的libvlc_parser_t对象(第 6 节)。它同时处理解析与缩略图生成,可管理多个并发请求,并提供统一的取消/超时接口。
2.3 其他移除项
| 被移除内容 | 替代方案 |
|---|---|
libvlc_Buffering状态值(libvlc_state_t中) | 使用媒体播放器的on_buffering_changed回调(缓冲进度范围为[0,1],见 include/vlc/libvlc_media_player.h) |
libvlc_media_get_parsed_status()与libvlc_media_parsed_status_t枚举 | 改用返回bool的libvlc_media_is_parsed();解析请求的详细结果现在只能通过libvlc_parser_cbs.on_parsed以libvlc_parser_status_t形式获知(第 6 节) |
libvlc_media_discoverer_media_list() | 改用libvlc_media_discoverer_cbs回调(第 4.3 节) |
libvlc_media_list_player_set_media_player() | 播放器必须是构造时传入的那个,不能再被替换 |
3. 重命名的 API 与命名约定变化
| LibVLC 3 | LibVLC 4 |
|---|---|
libvlc_media_player_stop() | libvlc_media_player_stop_async() |
libvlc_media_discoverer_release() | libvlc_media_discoverer_destroy() |
libvlc_renderer_discoverer_release() | libvlc_renderer_discoverer_destroy() |
libvlc_media_track_hold() | libvlc_media_track_retain() |
libvlc_renderer_item_hold() | libvlc_renderer_item_retain() |
两条命名约定值得注意:
release→destroy:标记该对象不是引用计数的,它只有单一所有者,一次调用即可释放。例如libvlc_media_discoverer_destroy()直接销毁对象。hold→retain:与库中其余 API 保持一致(如libvlc_media_retain、libvlc_picture_retain等)。
此外,libvlc_media_player_stop()改名为libvlc_media_player_stop_async()本身就暗示了语义变化:它不再同步等待状态切换完成,异步控制流需要依赖回调来确认(见第 7 节清单第 7 步)。
3.1 匿名联合体统一命名为u
所有曾经内嵌匿名联合体的公共结构体,现在都把联合体包装成名为u的成员。成员本身的名字不变,只是访问路径上多了一层u.。这一改动消除了对(C++ 中非标准、依赖编译器扩展的)匿名联合体行为的依赖,使判别联合体(discriminated union)显式化。
主要动机是自动生成的绑定:为其他语言从 C 头文件生成绑定的工具,无法可靠地命名或寻址匿名联合体的字段——因为这些字段没有可限定(qualify)它们的外层成员。给联合体一个显式名字u后,每个字段都有了稳定、可寻址的路径,绑定即可机械地自动生成。
受影响的结构体与字段:
| 结构体(所在头文件) | 现在位于.u下的字段 |
|---|---|
libvlc_media_track_t(libvlc_media_track.h) | audio、video、subtitle |
libvlc_video_setup_device_info_t(libvlc_media_player.h) | d3d11、d3d9 |
libvlc_video_output_cfg_t(libvlc_media_player.h) | dxgi_format、d3d9_format、opengl_format、p_surface、anw |
LibVLC 3 的写法(匿名联合体):
switch (track->i_type) { case libvlc_track_video: w = track->video->i_width; /* anonymous union */ break; case libvlc_track_audio: rate = track->audio->i_rate; break; }LibVLC 4 的写法(命名联合体成员u):
switch (track->i_type) { case libvlc_track_video: w = track->u.video->i_width; /* named union member 'u' */ break; case libvlc_track_audio: rate = track->u.audio->i_rate; break; }同样的.u.前缀也出现在 D3D11/D3D9/OpenGL 视频回调设置中,例如out->d3d11.device_context变成out->u.d3d11.device_context,out->dxgi_format变成out->u.dxgi_format。编译器会对每一处漏改的匿名联合体访问报错,这实际上降低了迁移难度——逐一修复编译错误即可。
4. 回调结构体:LibVLC 4 的新事件模型
所有曾经依赖事件管理器的对象,现在都在构造时接收一个带版本号的*_cbs结构体和一个不透明指针(opaque pointer)。每个结构体都以uint32_t version字段开头,应将其设置为编译所用头文件中可用的最新版本号——当前所有结构体的最新版本都是0。每个字段都在注释中标注了它可用的版本(例如 "available since version 0")。
版本号的作用:你设置的版本告诉 LibVLC 你填充了哪些字段,LibVLC 据此安全地调用你设置的每一个回调。如果设置的版本低于你实际填充的字段数,LibVLC 会忽略多出的字段,静默丢弃你期望触发的回调。当你基于更新的头文件重新编译、想使用新增字段时,就把版本号提升到新的最新版本。
前向 ABI 兼容性:未来 LibVLC 版本可能会向*_cbs结构体追加新字段并提升最新版本号。由于结构体总是通过指针传递、字段严格只追加(append-only),所有既有字段的字节偏移保持不变。旧应用固化的版本号告诉 LibVLC 可以安全读取结构体多深——边界之外一律视为 NULL。因此,新的libvlc.so可以正确运行旧而未修改的二进制。
基本用法示例:
static const struct libvlc_media_player_cbs cbs = { .version = 0, .on_state_changed = my_on_state, .on_position_changed = my_on_position, /* remaining fields may be NULL if the callback is marked Optional */ }; libvlc_media_player_t *mp = libvlc_media_player_new(inst, &cbs, my_opaque);未使用的回调,如果文档标注为Optional(可选),可以留为 NULL 或零初始化;标注为Mandatory(必选)的回调则必须设置。
生命周期与 const 正确性:LibVLC 存储的是你传入的指针,不会复制结构体。因此回调对象必须:
- 比注册它的 LibVLC 对象(播放器、发现器等)活得更久;
- 提交之后不得再被修改。
当所有字段都是编译期常量时,请将每个*_cbs结构体声明为static const——示例代码 doc/libvlc/player.c 中的cbs与time_cbs都是这种声明方式。
4.1 媒体播放器 —libvlc_media_player_cbs
定义于 include/vlc/libvlc_media_player.h,取代所有libvlc_MediaPlayer*事件。完整映射如下:
| 回调 | 取代(v3 事件) |
|---|---|
on_media_changed | libvlc_MediaPlayerMediaChanged |
on_media_stopping | libvlc_MediaPlayerStopping(现在接收libvlc_stopping_reason_t) |
on_state_changed | libvlc_MediaPlayer{Opening,Playing,Paused,Stopped,…} |
on_buffering_changed | libvlc_MediaPlayerBuffering |
on_capabilities_changed | libvlc_MediaPlayerSeekableChanged、PausableChanged |
on_position_changed | libvlc_MediaPlayerPositionChanged/TimeChanged(同时接收微秒时间与位置) |
on_length_changed | libvlc_MediaPlayerLengthChanged |
on_track_list_changed、on_track_selection_changed | 轨道(track)相关事件 |
on_program_list_changed、on_program_selection_changed | 节目(program)相关事件 |
on_titles_changed、on_title_selection_changed | 标题(title)事件 |
on_chapter_selection_changed | 现在直接传递libvlc_title_description_t *和libvlc_chapter_description_t * |
on_recording_changed | libvlc_MediaPlayerRecordChanged |
on_screenshot_taken | libvlc_MediaPlayerSnapshotTaken |
on_media_parsed | libvlc_MediaParsedChanged(经由播放器传递) |
on_media_meta_changed | libvlc_MediaMetaChanged |
on_media_subitems_changed | libvlc_MediaSubItemAdded/SubItemTreeAdded |
on_media_attachments_added | 新增— 传递libvlc_picture_list_t *附件 |
on_vout_changed | libvlc_MediaPlayerVout |
on_cork_changed | libvlc_MediaPlayerCorked/Uncorked |
on_audio_volume_changed、on_audio_mute_changed、on_audio_device_changed | 对应的音频事件 |
回调中出现的新枚举:
libvlc_capability_t— 播放器能力位域:seek(0x01)、pause(0x02)、change_rate(0x04)、rewind(0x08),见 include/vlc/libvlc_media_player.h。libvlc_list_action_t— 列表动作:added、removed、updated。libvlc_stopping_reason_t— 停止原因:error、eos、user,见 include/vlc/libvlc_media_player.h。
4.2 播放器时间监视器 —libvlc_media_player_watch_time_cbs
如果只是要"时间/位置变了"这种简单通知,第 4.1 节的on_position_changed就够了。但当你需要精确、与输出同步的播放时间(用来驱动进度条和时钟标签)时,应该改用libvlc_media_player_watch_time()。它接收一个回调结构体:
int libvlc_media_player_watch_time(libvlc_media_player_t *mp, libvlc_time_t min_period_us, const struct libvlc_media_player_watch_time_cbs *cbs, void *cbs_opaque);成员包括on_update(必选)、on_paused、on_seek。调用libvlc_media_player_unwatch_time()停止监视。同一时刻只能注册一个监视器——在libvlc_media_player_unwatch_time()之前再次调用会失败并返回 -1(见 include/vlc/libvlc_media_player.h)。
为什么存在这个 API:LibVLC 3 中时间显示由libvlc_MediaPlayerTimeChanged/PositionChanged事件驱动,它们报告的是input(demux)时间。该时间大约每 250ms 才刷新一次,并且落后于实际音视频输出约一个输出缓冲大小(300ms 到 2s),因此时钟可能明显错误、与听到或看到的画面不同步。而时间监视器报告的是来自output本身的点——每一帧显示的视频帧或每写出的音频样本。
如何使用:每次on_update传递一个libvlc_media_player_time_point_t,包含position、rate、ts_us、length_us以及基于libvlc_clock()的system_date_us(见 include/vlc/libvlc_media_player.h)。由于每个点都内嵌了系统日期和速率,它是一个自包含的值:on_update只需把它复制一次到 UI 线程——这次复制就是唯一的同步点。此后 UI 主循环可以在每次重绘时对同一个点重新插值。插值只读取复制的点,不取锁,只是少量算术运算,因此想调用多少次都几乎零成本。源更新可能稀疏且不规律(5ms、1s、10s……),插值正好填补这些空隙:
把时间/位置插值到"现在",使用
libvlc_media_player_time_point_interpolate(),可从 UI 主循环以任意频率驱动平滑的进度条。min_period_us限制on_update的触发频率——但不限制你插值的频率:libvlc_time_t now = libvlc_clock(), ts_us; double pos; if (libvlc_media_player_time_point_interpolate(&point, now, &ts_us, &pos) == 0) ui_set_time(ts_us, pos);计算下一个时间间隔的系统日期(例如下一个整秒),使用
libvlc_media_player_time_point_get_next_date(),从而精确地在整秒刷新时间标签,而不是轮询等待。
on_paused通知监视器停止其插值定时器;on_seek在 seek 进行中报告目标点(settle 后传NULL),UI 可以据此直接跳到目标,而不是跨跳变插值。
完整的实现示例见 doc/libvlc/player.c:它在context_init中以min_period_us = 500000ULL(即 500ms)注册监视器(player.c),在mainloop_display_ui中基于复制的time_point用libvlc_media_player_time_point_interpolate()计算当前时间与位置(player.c)。注意示例中还处理了暂停与 seek 状态:暂停时用paused_date作为插值基准,seek 进行中直接使用点的原始ts_us/position而不再插值。该 libvlc API 包装了核心层的vlc_player_AddTimer()(配合vlc_player_timer_point_Interpolate()与vlc_player_timer_point_GetNextIntervalDate()),Qt 界面在modules/gui/qt/player/player_controller.cpp中以同样方式使用。
4.3 媒体发现器 —libvlc_media_discoverer_cbs
libvlc_media_discoverer_t * libvlc_media_discoverer_new(libvlc_instance_t *inst, const char *name, const struct libvlc_media_discoverer_cbs *cbs, void *cbs_opaque);回调:on_media_added(opaque, parent, media)、on_media_removed(opaque, media)。parent参数取代了被移除的libvlc_media_discoverer_media_list()函数,用于父项跟踪。
4.4 渲染器发现器 —libvlc_renderer_discoverer_cbs
libvlc_renderer_discoverer_t * libvlc_renderer_discoverer_new(libvlc_instance_t *inst, const char *name, const struct libvlc_renderer_discoverer_cbs *cbs, void *cbs_opaque);回调:on_item_added(必选)、on_item_removed(可选)。
4.5 媒体 —libvlc_media_open_cbs
libvlc_media_new_callbacks()不再接收四个函数指针,而是接收一个结构体:
LibVLC 3:
libvlc_media_t *m = libvlc_media_new_callbacks(open_cb, read_cb, seek_cb, close_cb, opaque);LibVLC 4:
static const struct libvlc_media_open_cbs cbs = { .version = 0, .open = my_open, /* optional */ .read = my_read, /* mandatory */ .seek = my_seek, /* optional */ .close = my_close, /* optional */ }; libvlc_media_t *m = libvlc_media_new_callbacks(&cbs, opaque);4.6 对话框 —libvlc_dialog_cbs
libvlc_dialog_cbs增加了uint32_t version字段(用最新版本号初始化,当前为0)。错误显示回调不再属于该结构体,需单独用libvlc_dialog_set_error_callback()注册。
4.7 媒体列表播放器 — 复用libvlc_media_player_cbs
libvlc_media_list_player_new()现在接收媒体播放器的回调结构体,从而无需任何列表播放器事件即可观察状态/媒体变化:
libvlc_media_list_player_t * libvlc_media_list_player_new(libvlc_instance_t *inst, const struct libvlc_media_player_cbs *cbs, void *cbs_opaque);libvlc_MediaListPlayerNextItemSet由on_media_changed取代,libvlc_MediaListPlayerPlayed/libvlc_MediaListPlayerStopped由on_state_changed取代。
4.8 解析器 —libvlc_parser_cbs/libvlc_thumbnailer_cbs
见第 6 节。
5. 微秒时间单位:全 API 统一
LibVLC 3 中原本使用毫秒的函数,现在通过libvlc_time_t接收/返回微秒。整个 API 使用了单一类型化的时间单位。如果你在其他组件(数据库、调度器等)之间序列化或交换时间,必须审查每一个边界。
典型迁移动作(第 7 节清单第 4 步):把(ms)字面量换算为(ms * 1000),重命名局部变量,必要时更新数据库 schema。示例代码中处处体现这一约定,例如 doc/libvlc/player.c 的跳转量libvlc_time_t seek_jump = 1000000; /* 1 second jump (in us) */,以及format_time_us()用us / 1000000换算秒。
6. 全新libvlc_parser_tAPI:解析与缩略图统一入口
头文件:include/vlc/libvlc_parser.h
一个解析器对象拥有自己的线程池,可服务大量并发的解析与缩略图请求。每个请求用一个不透明的任务句柄(task handle)标识。
6.1 生命周期与配置
struct libvlc_parser_cfg cfg = { .version = 0, .max_parser_threads = 0, /* 0 = default (1) */ .max_thumbnailer_threads = 0, .timeout = 0, /* in us, 0 = no timeout */ }; libvlc_parser_t *p = libvlc_parser_new(inst, &cfg); /* ... */ libvlc_parser_destroy(p);配置字段(均标注于 include/vlc/libvlc_parser.h):
version:结构体版本,当前为0;max_parser_threads:解析线程上限,0表示默认(1 线程);max_thumbnailer_threads:缩略图线程上限,0表示默认(1 线程);timeout:解析超时(微秒),0表示无限制,-1表示继承preparse-timeout配置项的值。
注意:libvlc_parser_destroy()会取消所有挂起与运行中的任务,通过对应的on_parsed/on_ended回调上报,并阻塞直到所有工作线程 join 完毕。头文件明确说明"在任务仍在运行或挂起时调用本 API 是安全的"(见 include/vlc/libvlc_parser.h)。示例 doc/libvlc/parser.c 中配置了max_parser_threads = 1与timeout = 5000000(5 秒)。
6.2 解析媒体
/* cbs must outlive the returned task handle (i.e., until on_parsed is called on that particular task handle) */ static const struct libvlc_parser_cbs cbs = { .version = 0, .on_parsed = my_on_parsed, /* mandatory */ .on_attachments_added = my_on_attachments_added, /* optional */ }; libvlc_parser_request_t req = { .version = 0, .media = media, .parse_flags = libvlc_media_parse | libvlc_media_fetch_local, }; libvlc_parser_task *task = libvlc_parser_task_new_parse(p, &req, &cbs, opaque); libvlc_parser_submit(p, task); /* later, when done: */ libvlc_parser_task_release(task);新解析标志集合(定义于 include/vlc/libvlc_parser.h):
| 标志 | 含义 |
|---|---|
libvlc_media_parse(0x01) | 解析媒体 |
libvlc_media_fetch_local(0x02) | 使用本地资源获取元数据与封面图 |
libvlc_media_fetch_network(0x04) | 使用网络资源获取元数据与封面图 |
libvlc_media_do_interact(0x08) | 解析该项(不含子项)时通过libvlc_dialog_cbs与用户交互,例如输入需要凭据时接收回调 |
parse_flags若为0则默认视为libvlc_media_parse。
解析结果状态libvlc_parser_status_t(include/vlc/libvlc_parser.h):failed(解析失败)、timeout(超时)、cancelled(被取消)、done(成功完成)。示例 doc/libvlc/parser.c 中的status_to_string()展示了如何在on_parsed中解读这些状态。
关键行为变化:libvlc_parser_t现在允许对已经解析过的libvlc_media_t重新排队。LibVLC 3 中,已解析过的媒体会被拒绝;现在会再次解析,因为其元数据可能已经改变。头文件中进一步说明:同一libvlc_media_t可以多次解析(例如刷新网络元数据),之前成功、失败、超时或取消的解析都不会阻止提交新任务;但重复提交一个已经成功提交过的任务句柄是未定义行为(见 include/vlc/libvlc_parser.h)。
注意所有权细节:libvlc_media_t由请求持有,创建任务后可以释放自己的引用——示例 doc/libvlc/parser.c 在libvlc_parser_task_new_parse()之后立即libvlc_media_release(media),并注释/* media held by the request */。另外示例中解析器持有 libvlc 实例,create_parser()里libvlc_parser_new()之后立即libvlc_release(libvlc),注释为/* instance held by the parser */。
6.3 生成缩略图
/* tcbs must outlive the returned task handle (i.e., until on_ended is called on that particular task handle) */ static const struct libvlc_thumbnailer_cbs tcbs = { .version = 0, .on_ended = my_on_thumbnail_ended, /* mandatory */ }; libvlc_thumbnailer_request_t treq = { .version = 0, .media = media, .width = 320, .height = 0, /* derived from aspect ratio */ .crop = false, .type = libvlc_picture_Argb, .seek = { .type = libvlc_thumbnailer_seek_time, .value = { .time = 10 * 1000 * 1000, /* 10s, in us */ }, .speed = libvlc_media_thumbnail_seek_fast, }, .hw_dec = false, }; libvlc_parser_task *task = libvlc_parser_task_new_thumbnail(p, &treq, &tcbs, opaque); libvlc_parser_submit(p, task); /* later, when done: */ libvlc_parser_task_release(task);缩略图请求字段详解(include/vlc/libvlc_parser.h):
width/height(默认 0):结果尺寸有三种模式——- 同时提供宽高:硬编码尺寸,图像被拉伸到该宽高比,若
crop为true则裁剪; - 只提供一个、另一个为 0:按媒体宽高比推导;
- 两者均为 0:与媒体原始尺寸相同。
- 同时提供宽高:硬编码尺寸,图像被拉伸到该宽高比,若
crop(默认false):仅在宽高均为非零(硬编码尺寸)时有意义,否则忽略。type(默认libvlc_picture_Argb):图片类型枚举libvlc_picture_type_t。seek:定位参数,可整体零初始化(成员取默认值)——type:libvlc_thumbnailer_seek_none(默认,不 seek)/libvlc_thumbnailer_seek_time(按时间)/libvlc_thumbnailer_seek_pos(按位置);value:联合体,按type选择活动成员,time为微秒,pos为[0,1]位置;speed:libvlc_media_thumbnail_seek_precise(精确但可能较慢,默认)/libvlc_media_thumbnail_seek_fast(快速但可能不精确)。
hw_dec(默认false):是否启用硬件解码器。
重要语义:on_ended回调中picture仅在该回调内有效,如需保留必须用libvlc_picture_retain()持有;出错、超时或被取消时picture为 NULL(include/vlc/libvlc_parser.h)。
完整可运行示例见 doc/libvlc/thumbnailer.c:它是一个兼容 Nautilus 的缩略图生成器(vlc-thumb),按位置 30% 处取帧(VLC_THUMBNAIL_POSITION = 30./100.),输出 PNG,用信号量等待异步完成,再通过libvlc_picture_save()保存并libvlc_picture_release()释放。它示范了type = libvlc_picture_Png与seek.type = libvlc_thumbnailer_seek_pos的用法,以及libvlc_parser_destroy()在等待完成之后调用的正确顺序。
6.4 取消与内省
libvlc_parser_cancel_request(p, task)— 取消指定请求,传NULL则取消全部。返回被取消的请求数量。被取消的解析任务以libvlc_parser_status_cancelled触发on_parsed;被取消的缩略图任务以picture == NULL触发on_ended。如果请求已处于终止状态(完成、取消、出错、超时)或从未提交,则该调用为 no-op,不会触发任何回调(见 include/vlc/libvlc_parser.h)。libvlc_parser_task_get_media(task)— 返回任务关联的媒体;是借用指针,不要释放(由任务持有)。libvlc_parser_task_release(task)—必须调用,释放任务句柄(在on_parsed/on_ended内部调用是安全的)。它不会取消进行中的请求;无论任务是否提交都必须释放,否则内存泄漏;释放后不得再使用该句柄。libvlc_parser_destroy(p)— 取消所有挂起与运行中的任务,通过对应回调上报,并阻塞至所有工作线程 join。
任务提交流程要点(include/vlc/libvlc_parser.h):libvlc_parser_submit()成功后,完成回调保证恰好被调用一次(包括被取消的情况),且该回调甚至可能在本函数返回前就执行;提交失败时不触发回调,调用者保留引用、可再次提交;一个任务最多提交一次,只有上次提交失败才允许重提。
7. 迁移清单:按顺序执行的十步
按顺序执行以下步骤,每一步都会让你的代码树多编译通过一部分:
替换事件系统。
- 删除每一个
libvlc_event_attach()/detach()调用及关联的分发函数。 - 为每个对象构建
*_cbs结构体,version设为最新版本号(当前0),用第 4.1 节的表格翻译你的处理器。 - 删除所有
libvlc_*_event_manager()查找调用。
- 删除每一个
更新对象构造。
libvlc_media_player_new(inst)→libvlc_media_player_new(inst, &cbs, opaque)。- 对
libvlc_media_player_new_from_media()、libvlc_media_list_player_new()、libvlc_media_discoverer_new()、libvlc_renderer_discoverer_new()做同样处理。 libvlc_media_new_callbacks(open, read, seek, close, op)→libvlc_media_new_callbacks(&cbs_struct, op)。
重命名析构/保留函数。
libvlc_media_discoverer_release→_destroy。libvlc_renderer_discoverer_release→_destroy。libvlc_media_track_hold→_retain。libvlc_renderer_item_hold→_retain。
全面切换到微秒。审计每一个涉及时间单位的调用点。将
(ms)字面量换算为(ms * 1000),重命名局部变量,必要时更新数据库 schema。迁移解析/缩略图逻辑。把
libvlc_media_parse_request()/libvlc_media_thumbnail_request_by_*()调用替换为每个 libvlc 实例共享一个libvlc_parser_t;用libvlc_parser_task_new_parse()/libvlc_parser_task_new_thumbnail()创建任务,用libvlc_parser_submit()启动,并删除libvlc_media_parse_stop()。更新对话框结构体。添加
.version = 0(当前最新版本)。把错误处理器迁移到libvlc_dialog_set_error_callback()。审查异步控制流。任何假设
libvlc_media_player_stop()/set_pause()/set_media()在状态变化完成后才返回的代码,都要改为等待on_state_changed/on_media_changed/on_media_stopping。删除已移除的枚举值。
libvlc_Bufferinglibvlc_media_parsed_status_t枚举(用libvlc_media_is_parsed()替代libvlc_media_get_parsed_status())。- 旧
libvlc_media_parse_flag_t标志(parse_local、parse_network、parse_forced)。
为命名联合体添加
u.前缀。对第 3.1 节列出的每一处匿名联合体访问插入u.:track->video→track->u.video、out->dxgi_format→out->u.dxgi_format等。编译器会逐一标出这些位置。用
-Werror重新编译。剩余的大部分破坏点都是琐碎的适配工作。
8. 参考:仓库中的移植示例代码
doc/libvlc/目录下提供了已移植的示例,全面演练了新的基于回调的 API,可直接作为迁移模板阅读:
- doc/libvlc/player.c — 媒体播放器 + 时间监视器:展示了
libvlc_media_player_cbs与libvlc_media_player_watch_time_cbs的组合使用、基于 select() 的事件循环、暂停/seek 状态下的插值处理。 - doc/libvlc/parser.c — 解析队列:展示了共享解析器 + 多请求提交、
on_parsed状态解析、任务句柄释放。 - doc/libvlc/thumbnailer.c — 通过解析器生成缩略图:位置定位、PNG 输出、图片保存与释放。
- doc/libvlc/media_discoverer.c — 媒体发现器回调。
- doc/libvlc/renderer_discoverer.c — 渲染器发现器回调。
这些示例的构建方式可参考 doc/libvlc/CMakeLists.txt。此外,doc/libvlc/下还提供d3d9_player.c、d3d11_player.cpp、appkit_player.m、gtk_player.c、sdl_opengl_player.cpp、QtPlayer/、QtGL/等平台示例,其中也大量涉及.u.联合体与回调结构体的用法。
后续阅读建议:每个头文件内的 doxygen 注释都详细记录了每个回调字段及其可用版本(如available since version 0),这是判断回调是否必选、字段语义的最权威参考。迁移中遇到任何不确定的回调,回到 include/vlc/libvlc_media_player.h、include/vlc/libvlc_parser.h 等头文件查阅对应注释即可。
- 音视频
【免费下载链接】vlc
VLC media player - plays everything, runs anywhere. Code here: https://code.videolan.org/videolan/vlc
相关推荐
毫秒级微虚拟机迁移:Firecracker冷迁移与实时迁移全解析
毫秒级微虚拟机迁移:Firecracker冷迁移与实时迁移全解析 引言:从业务痛点到技术解决方案 你是否遇到过这些问题?服务器维护时服务中断长达数分钟,弹性扩容
虚拟化云原生Axure RP中文界面终极指南:3分钟快速汉化完整教程
Axure RP中文界面终极指南:3分钟快速汉化完整教程 你是否曾经面对Axure RP的全英文界面感到困惑?是否因为专业术语的理解障碍而影响了原型设计效率?今
前端构建构建工具开发工具CLI从4.x到5.0:Pearcleaner迁移指南与新功能全解析
从4.x到5.0:Pearcleaner迁移指南与新功能全解析 随着macOS应用生态的不断发展,保持清理工具的兼容性和高效性变得尤为重要。Pearcleane
桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考