news 2026/9/20 11:52:39

LibVLC 3 到 4 迁移指南:事件模型、微秒时间单位与新解析器 API 全面解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LibVLC 3 到 4 迁移指南:事件模型、微秒时间单位与新解析器 API 全面解析
  • 音视频

【免费下载链接】vlc

VLC media player - plays everything, runs anywhere. Code here: https://code.videolan.org/videolan/vlc

项目地址:https://gitcode.com/gh_mirrors/vl/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,几乎所有非平凡的应用都会被触及:

  1. libvlc_event_manager_t/libvlc_event_attach()事件系统被彻底移除。每一个 LibVLC 对象(媒体播放器、媒体发现器、渲染器发现器、媒体列表播放器、解析器、媒体)在创建时都必须显式传入一个带版本号的回调结构体(*_cbs)。
  2. libvlc_time_t在全 API 范围内统一使用微秒。LibVLC 3 在公共 API 中混用毫秒与微秒;4.0 中,libvlc_time_t(即int64_t)成为全 API 唯一的时间单位类型。
  3. 新增独立的libvlc_parser_tAPI。解析(parsing)与缩略图(thumbnail)生成从libvlc_media_t中剥离出来,归入独立 API。一个解析器对象拥有自己的线程池,可以同时服务大量并发的解析与缩略图请求,详见第 6 节。

这三项改动决定了后续所有迁移工作的基本方向:改写事件处理、修正时间单位、迁移解析逻辑。


2. 被移除的 API 与替代方案

2.1 全局事件系统整体消失

<vlc/libvlc_events.h>中的一切都被移除,该头文件已被删除。移除的符号包括:

  • libvlc_event_manager_t
  • libvlc_event_t/libvlc_event_type_t
  • libvlc_callback_t
  • libvlc_event_attach()/libvlc_event_detach()
  • libvlc_media_event_manager()libvlc_media_list_event_manager()libvlc_media_player_event_manager()(以及其他所有对象上的等价函数)
  • 全部事件枚举值:libvlc_MediaPlayerPlayinglibvlc_MediaParsedChangedlibvlc_MediaListItemAddedlibvlc_MediaListEndReachedlibvlc_MediaPlayerMediaChanged

迁移方案:改用对象专属的回调结构体(*_cbs),在对象创建时传入。各对象的映射关系见第 4 节。

2.2libvlc_media_t上的解析/缩略图功能被移除

<vlc/libvlc_media.h>中移除:

  • libvlc_media_parse_request()
  • libvlc_media_parse_stop()
  • libvlc_media_thumbnail_request_t
  • libvlc_media_thumbnail_request_by_time()
  • libvlc_media_thumbnail_request_by_pos()
  • libvlc_media_thumbnail_request_destroy()

旧枚举libvlc_media_parse_flag_t中的libvlc_media_parse_locallibvlc_media_parse_networklibvlc_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枚举改用返回boollibvlc_media_is_parsed();解析请求的详细结果现在只能通过libvlc_parser_cbs.on_parsedlibvlc_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 3LibVLC 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()

两条命名约定值得注意:

  • releasedestroy:标记该对象不是引用计数的,它只有单一所有者,一次调用即可释放。例如libvlc_media_discoverer_destroy()直接销毁对象。
  • holdretain:与库中其余 API 保持一致(如libvlc_media_retainlibvlc_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_tlibvlc_media_track.haudiovideosubtitle
libvlc_video_setup_device_info_tlibvlc_media_player.hd3d11d3d9
libvlc_video_output_cfg_tlibvlc_media_player.hdxgi_formatd3d9_formatopengl_formatp_surfaceanw

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_contextout->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 中的cbstime_cbs都是这种声明方式。

4.1 媒体播放器 —libvlc_media_player_cbs

定义于 include/vlc/libvlc_media_player.h,取代所有libvlc_MediaPlayer*事件。完整映射如下:

回调取代(v3 事件)
on_media_changedlibvlc_MediaPlayerMediaChanged
on_media_stoppinglibvlc_MediaPlayerStopping(现在接收libvlc_stopping_reason_t
on_state_changedlibvlc_MediaPlayer{Opening,Playing,Paused,Stopped,…}
on_buffering_changedlibvlc_MediaPlayerBuffering
on_capabilities_changedlibvlc_MediaPlayerSeekableChangedPausableChanged
on_position_changedlibvlc_MediaPlayerPositionChanged/TimeChanged(同时接收微秒时间与位置)
on_length_changedlibvlc_MediaPlayerLengthChanged
on_track_list_changedon_track_selection_changed轨道(track)相关事件
on_program_list_changedon_program_selection_changed节目(program)相关事件
on_titles_changedon_title_selection_changed标题(title)事件
on_chapter_selection_changed现在直接传递libvlc_title_description_t *libvlc_chapter_description_t *
on_recording_changedlibvlc_MediaPlayerRecordChanged
on_screenshot_takenlibvlc_MediaPlayerSnapshotTaken
on_media_parsedlibvlc_MediaParsedChanged(经由播放器传递)
on_media_meta_changedlibvlc_MediaMetaChanged
on_media_subitems_changedlibvlc_MediaSubItemAdded/SubItemTreeAdded
on_media_attachments_added新增— 传递libvlc_picture_list_t *附件
on_vout_changedlibvlc_MediaPlayerVout
on_cork_changedlibvlc_MediaPlayerCorked/Uncorked
on_audio_volume_changedon_audio_mute_changedon_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— 列表动作:addedremovedupdated
  • libvlc_stopping_reason_t— 停止原因:erroreosuser,见 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_pausedon_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,包含positionratets_uslength_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_pointlibvlc_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_MediaListPlayerNextItemSeton_media_changed取代,libvlc_MediaListPlayerPlayed/libvlc_MediaListPlayerStoppedon_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 = 1timeout = 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):结果尺寸有三种模式——
    • 同时提供宽高:硬编码尺寸,图像被拉伸到该宽高比,若croptrue则裁剪;
    • 只提供一个、另一个为 0:按媒体宽高比推导;
    • 两者均为 0:与媒体原始尺寸相同。
  • crop(默认false):仅在宽高均为非零(硬编码尺寸)时有意义,否则忽略。
  • type(默认libvlc_picture_Argb):图片类型枚举libvlc_picture_type_t
  • seek:定位参数,可整体零初始化(成员取默认值)——
    • typelibvlc_thumbnailer_seek_none(默认,不 seek)/libvlc_thumbnailer_seek_time(按时间)/libvlc_thumbnailer_seek_pos(按位置);
    • value:联合体,按type选择活动成员,time为微秒,pos[0,1]位置;
    • speedlibvlc_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_Pngseek.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. 迁移清单:按顺序执行的十步

按顺序执行以下步骤,每一步都会让你的代码树多编译通过一部分:

  1. 替换事件系统。

    • 删除每一个libvlc_event_attach()/detach()调用及关联的分发函数。
    • 为每个对象构建*_cbs结构体,version设为最新版本号(当前0),用第 4.1 节的表格翻译你的处理器。
    • 删除所有libvlc_*_event_manager()查找调用。
  2. 更新对象构造。

    • 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)
  3. 重命名析构/保留函数。

    • libvlc_media_discoverer_release_destroy
    • libvlc_renderer_discoverer_release_destroy
    • libvlc_media_track_hold_retain
    • libvlc_renderer_item_hold_retain
  4. 全面切换到微秒。审计每一个涉及时间单位的调用点。将(ms)字面量换算为(ms * 1000),重命名局部变量,必要时更新数据库 schema。

  5. 迁移解析/缩略图逻辑。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()

  6. 更新对话框结构体。添加.version = 0(当前最新版本)。把错误处理器迁移到libvlc_dialog_set_error_callback()

  7. 审查异步控制流。任何假设libvlc_media_player_stop()/set_pause()/set_media()在状态变化完成后才返回的代码,都要改为等待on_state_changed/on_media_changed/on_media_stopping

  8. 删除已移除的枚举值。

    • libvlc_Buffering
    • libvlc_media_parsed_status_t枚举(用libvlc_media_is_parsed()替代libvlc_media_get_parsed_status())。
    • libvlc_media_parse_flag_t标志(parse_localparse_networkparse_forced)。
  9. 为命名联合体添加u.前缀。对第 3.1 节列出的每一处匿名联合体访问插入u.track->videotrack->u.videoout->dxgi_formatout->u.dxgi_format等。编译器会逐一标出这些位置。

  10. -Werror重新编译。剩余的大部分破坏点都是琐碎的适配工作。


8. 参考:仓库中的移植示例代码

doc/libvlc/目录下提供了已移植的示例,全面演练了新的基于回调的 API,可直接作为迁移模板阅读:

  • doc/libvlc/player.c — 媒体播放器 + 时间监视器:展示了libvlc_media_player_cbslibvlc_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.cd3d11_player.cppappkit_player.mgtk_player.csdl_opengl_player.cppQtPlayer/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

项目地址:https://gitcode.com/gh_mirrors/vl/vlc
点击查看免费下载

相关推荐

上一篇:解密 Input Overlay:直播输入可视化工具的终极配置指南
下一篇:终极解决方案:eslint-config-prettier常见问题快速解决指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 11:51:49

C# OPC UA客户端开发实战:适配西门子与KepServer

简介&#xff1a;这是一套基于C#的OPC UA客户端源码&#xff0c;目标是解决西门子机床等设备在非标准认证、加密策略与私有数据模型下的连接难题&#xff0c;同时兼容KepServer等常见OPC UA服务器&#xff0c;适合工业通信开发者和自动化集成人员参考。压缩包共2000个文件、约3…

作者头像 李华
网站建设 2026/9/20 11:51:05

Java Web应用环境迁移常见问题与解决方案

1. 项目背景与问题概述最近在负责一个名为"苍穹外卖"的线上订餐系统从测试环境迁移到生产环境的过程中&#xff0c;遇到了四个典型的报错问题&#xff1a;JDK版本不兼容、数据源配置异常、端口占用冲突以及JWT令牌验证失败。这些问题看似独立&#xff0c;实际上环环相…

作者头像 李华
网站建设 2026/9/20 11:49:58

传感器AI化:嵌入式人工智能如何重构设备智能

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 11:48:56

redux-saga 入门教程:从 Hello Saga 到异步副作用与可测试性实战

前端 【免费下载链接】redux-saga An alternative side effect model for Redux apps 项目地址&#xff1a; https://gitcode.com/gh_mirrors/re/redux-saga 点击查看 免费下载 导读 本文是 redux-saga 的入门实战教程&#xff0c;基于官方 Beginner Tutorial 展开&#xff0…

作者头像 李华
网站建设 2026/9/20 11:48:34

Qt 5.15.19 与 Qt for MCUs 2.11 LTS 发布解析:ESP32-S3 与 RA8D1 实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 11:46:09

X5R与X7R陶瓷电容选型本质:温度特性、材料差异与场景适配

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华