
在本文中,我将讨论如何在C中为VLC媒体播放器编写插件。 我写了我的插件来简化用英语观看电视节目和电影的过程。 在创建
想法和
查找解决方案部分中介绍了创建此插件的想法。 插件实现的技术细节在
Hello World插件和
实现部分中提供。 最后的结果以及如何使用它可以在最后一部分
Result中找到 。
该项目的源代码可在
GitHub上
找到 。
主意
在观看我最喜欢的电视剧时学习外语的想法并不是什么新鲜事,但是我个人一直在实现它方面遇到问题。 如果您听不懂他们说的一半,就很难看连续剧或电影。 当然,您可以打开字幕,但是如果语音中遇到陌生的单词或表达,将很难被文字复制。 而且我根本不喜欢看带有俄文字幕的电视剧-大脑切换到母语,并且不再能听外国语言。 我读过某个地方,首先您需要看俄语的系列节目,然后才看原始版本。 但是这种方法也不适合我。 首先,在哪里花那么多时间去看几次相同的东西,其次,再去看第二遍不再那么有趣了-失去了动力。
尽管在观看外国电视节目时遇到了种种困难,但我可以很好地阅读英文技术文档,文章和书籍。 我喜欢在Kindle电子阅读器上阅读书籍,因为词典功能很酷,您只需触摸一下屏幕,就可以找到不熟悉的单词的翻译。 通过在浏览器中安装用于翻译的特殊扩展名,可以方便地阅读英语文章和网站-我使用
Yandex.Translation扩展名。 这种方法使您可以阅读和理解英语文本,而不会因为搜索陌生单词而分心。
我想,为什么不采用相同的方法观看电视节目-一旦发现一个难以理解的词组,我们便以英语打开该系列节目,切换到俄语音轨并向后退一点。 接下来,我们继续观看英语系列赛。
寻找解决方案
实际上,我所需的所有功能已经在许多流行的媒体播放器中提供。 我想在几秒钟前单击一下按钮来切换音轨和倒带视频。 如果在翻译了一个令人费解的片段之后,媒体播放器本身将音轨切换回英语,那也很好。 好吧,能够在英语轨道上重复以前翻译的片段会很好。
也就是说,我需要一个可以为其编写插件的媒体播放器。 还希望它是跨平台的,因为我在Windows下使用PC,在Linux下使用笔记本电脑。 我的选择立即落在了VLC上。 在habr上,我什至发现
了一篇文章 ,其中
@Idunno讲述了如何在LUA上编写VLC扩展。 顺便说一下,他还写了这个扩展程序来学习英语。不幸的是,这个扩展程序在最新版本的VLC(低于2.0.5)中不起作用。 由于操作不稳定,因此从LUA API中删除了添加回调函数的功能,通过该函数可以处理LUA扩展中的键盘事件。 在
README中 ,指向讨论此问题的VLC开发人员的邮件列表的链接导致其在GitHub
@Idunno上的
扩展 。
因此,要实现我的想法,对LUA的扩展不起作用,您需要用C编写一个插件。尽管大约7年前我上大学时最后一次用C编写了东西,但我还是决定尝试一下。
你好世界插件
值得注意的是,VLC媒体播放器具有非常好的
文档 。 我从中了解到,媒体播放器的开发使用模块化方法。 VLC包含几个实现某些功能的独立模块,以及管理这些模块的内核(
libVLCCore )。 模块有两种类型:内部(
树内 )和外部(
树外 )。 内部模块的源代码与内核代码一起存储在一个存储库中。 外部模块的开发和组装独立于VLC媒体播放器。 实际上,后者就是所谓的插件。
该文档还包含有关如何使用C语言编写插件(模块)的文章。本文提供了一个简单插件的源代码,该插件在VLC启动时向控制台显示欢迎消息“
Hello,<name> ”(采用值<name>从插件设置)。 向前走一点,我会说在上面的示例中,在
set_category(CAT_INTERFACE)
之后添加以下行:
set_subcategory( SUBCAT_INTERFACE_CONTROL )
好吧,剩下的就是组装插件并测试其运行了。 还有一个
有关构建外部插件的说明。 这里值得关注
国际化部分,该部分描述了本地化在VLC中的工作方式。 简而言之,对于外部插件,您需要定义宏
N_()
_()
,
_()
:
#define DOMAIN "vlc-myplugin" #define _(str) dgettext(DOMAIN, str) #define N_(str) (str)
对于组装,建议使用良好的旧Makefile或Autotools。 我决定采用简单的方法,并选择了Makefile。 在Makefile中,您需要记住定义
MODULE_STRING
变量-这是我们插件的标识符。 我还对目录进行了一些调整-现在它们是通过
pkg-config定义的。 结果是以下文件:
你好 #ifdef HAVE_CONFIG_H # include "config.h" #endif #define DOMAIN "vlc-myplugin" #define _(str) dgettext(DOMAIN, str) #define N_(str) (str) #include <stdlib.h> /* VLC core API headers */ #include <vlc_common.h> #include <vlc_plugin.h> #include <vlc_interface.h> /* Forward declarations */ static int Open(vlc_object_t *); static void Close(vlc_object_t *); /* Module descriptor */ vlc_module_begin() set_shortname(N_("Hello")) set_description(N_("Hello interface")) set_capability("interface", 0) set_callbacks(Open, Close) set_category(CAT_INTERFACE) set_subcategory( SUBCAT_INTERFACE_CONTROL ) add_string("hello-who", "world", "Target", "Whom to say hello to.", false) vlc_module_end () /* Internal state for an instance of the module */ struct intf_sys_t { char *who; }; /** * Starts our example interface. */ static int Open(vlc_object_t *obj) { intf_thread_t *intf = (intf_thread_t *)obj; /* Allocate internal state */ intf_sys_t *sys = malloc(sizeof (*sys)); if (unlikely(sys == NULL)) return VLC_ENOMEM; intf->p_sys = sys; /* Read settings */ char *who = var_InheritString(intf, "hello-who"); if (who == NULL) { msg_Err(intf, "Nobody to say hello to!"); goto error; } sys->who = who; msg_Info(intf, "Hello %s!", who); return VLC_SUCCESS; error: free(sys); return VLC_EGENERIC; } /** * Stops the interface. */ static void Close(vlc_object_t *obj) { intf_thread_t *intf = (intf_thread_t *)obj; intf_sys_t *sys = intf->p_sys; msg_Info(intf, "Good bye %s!", sys->who); /* Free internal state */ free(sys->who); free(sys); }
生成文件 LD = ld CC = cc PKG_CONFIG = pkg-config INSTALL = install CFLAGS = -g -O2 -Wall -Wextra LDFLAGS = LIBS = VLC_PLUGIN_CFLAGS := $(shell $(PKG_CONFIG) --cflags vlc-plugin) VLC_PLUGIN_LIBS := $(shell $(PKG_CONFIG) --libs vlc-plugin) VLC_PLUGIN_DIR := $(shell $(PKG_CONFIG) --variable=pluginsdir vlc-plugin) plugindir = $(VLC_PLUGIN_DIR)/misc override CC += -std=gnu99 override CPPFLAGS += -DPIC -I. -Isrc override CFLAGS += -fPIC override LDFLAGS += -Wl,-no-undefined,-z,defs override CPPFLAGS += -DMODULE_STRING=\ override CFLAGS += $(VLC_PLUGIN_CFLAGS) override LIBS += $(VLC_PLUGIN_LIBS) all: libhello_plugin.so install: all mkdir -p -- $(DESTDIR)$(plugindir) $(INSTALL) --mode 0755 libhello_plugin.so $(DESTDIR)$(plugindir) install-strip: $(MAKE) install INSTALL= uninstall: rm -f $(plugindir)/libhello_plugin.so clean: rm -f -- libhello_plugin.so src/*.o mostlyclean: clean SOURCES = hello.c $(SOURCES:%.c=src/%.o): $(SOURCES:%.c=src/%.c) libhello_plugin.so: $(SOURCES:%.c=src/%.o) $(CC) $(LDFLAGS) -shared -o $@ $^ $(LIBS) .PHONY: all install install-strip uninstall clean mostlyclean
为Linux构建插件的最简单方法。 为此,实际上,您需要安装VLC媒体播放器本身以及构建插件所需的文件和工具。 在Debian / Ubuntu上,可以使用以下命令完成此操作:
sudo apt-get install vlc libvlc-dev libvlccore-dev gcc make pkg-config
实际上,一切就绪,我们使用以下命令收集并安装插件:
sudo make install
要测试插件,请同时从控制台运行VLC:
vlc
不幸的是,我们没有看到任何“
Hello world ”。 问题是必须首先启用该插件。 为此,请打开设置(“
工具” >
“首选项” ),切换到高级视图(在“
显示设置”组中选择“
全部 ”),然后在左面板的树中找到“
接口” >“
控制接口” -选中“
Hello接口插件”旁边的复选框。

我们保存设置并重新启动VLC。

为Windows构建插件
使用Windows,事情要复杂一些。 要构建插件,您需要下载sdk,其中包含VLC的库,头文件和配置文件。 以前,SDK是常规VLC程序集的一部分,可以在程序安装文件夹中找到。 现在,它作为一个单独的媒体播放器组件出现。 例如,对于VLC版本3.0.8,可以从
ftp://ftp.videolan.org/pub/videolan/vlc/3.0.8/win64/vlc-3.0.8-win64.7z下载此程序集(重要的是下载7z -存档)。
将存档的内容复制到一个文件夹,例如,复制到
C:\ Projects 。 除sdk外,归档文件还包含媒体播放器本身,可用于测试和调试插件。
为了使我们的Makefile可以用于构建和安装插件,您需要修复文件
C:\ Projects \ vlc-3.0.8 \ sdk \ lib \ pkgconfig \ vlc-plugin.pc ,在
前缀和
pluginsdir变量中指示
sdk文件夹的正确路径。和
插件 :
prefix=/c/Projects/vlc-3.0.8/sdk pluginsdir=/c/Projects/vlc-3.0.8/plugins
要在Windows下构建,我们还需要安装编译器和其他实用程序。 通过安装
MSYS2环境可以获得所有必需的软件。 项目网站上有详细的安装说明。 简而言之,安装后,您需要立即打开控制台(
C:\ msys64 \ msys2.exe )并使用以下命令更新MSYS2软件包:
pacman -Syu
接下来,关闭MSYS2终端窗口,然后再次打开并运行命令
pacman -Su
更新所有软件包后,您需要安装工具链:
pacman -S base-devel mingw-w64-x86_64-toolchain
现在已经安装了所有必需的软件包,您可以开始构建插件。 我对Makefile做了一些修改,以便可以在Linux和Windows下构建该插件。 另外,我必须删除一些不受支持的MinGW构建参数,因此Makefile开始看起来像这样:
Windows的Makefile LD = ld CC = cc PKG_CONFIG = pkg-config INSTALL = install CFLAGS = -g -O2 -Wall -Wextra LDFLAGS = LIBS = VLC_PLUGIN_CFLAGS := $(shell $(PKG_CONFIG) --cflags vlc-plugin) VLC_PLUGIN_LIBS := $(shell $(PKG_CONFIG) --libs vlc-plugin) VLC_PLUGIN_DIR := $(shell $(PKG_CONFIG) --variable=pluginsdir vlc-plugin) plugindir = $(VLC_PLUGIN_DIR)/misc override CC += -std=gnu99 override CPPFLAGS += -DPIC -I. -Isrc override CFLAGS += -fPIC override LDFLAGS += -Wl,-no-undefined override CPPFLAGS += -DMODULE_STRING=\ override CFLAGS += $(VLC_PLUGIN_CFLAGS) override LIBS += $(VLC_PLUGIN_LIBS) SUFFIX := so ifeq ($(OS),Windows_NT) SUFFIX := dll endif all: libhello_plugin.$(SUFFIX) install: all mkdir -p -- $(DESTDIR)$(plugindir) $(INSTALL) --mode 0755 libhello_plugin.$(SUFFIX) $(DESTDIR)$(plugindir) install-strip: $(MAKE) install INSTALL= uninstall: rm -f $(plugindir)/libhello_plugin.$(SUFFIX) clean: rm -f -- libhello_plugin.$(SUFFIX) src/*.o mostlyclean: clean SOURCES = hello.c $(SOURCES:%.c=src/%.o): $(SOURCES:%.c=src/%.c) libhello_plugin.$(SUFFIX): $(SOURCES:%.c=src/%.o) $(CC) $(LDFLAGS) -shared -o $@ $^ $(LIBS) .PHONY: all install install-strip uninstall clean mostlyclean
由于MSYS2对VLC的sdk一无所知,因此在构建之前,有必要将该sdk中
pkgconfig文件夹的路径添加到
PKG_CONFIG_PATH环境
变量中 。 打开MinGW控制台(
C:\ msys64 \ mingw64.exec )并执行以下命令:
export PKG_CONFIG_PATH=/c/projects/vlc-3.0.8/sdk/lib/pkgconfig:$PKG_CONFIG_PATH make install
要测试插件,请同时从控制台运行VLC:
/c/projects/vlc-3.0.8/vlc
与Linux一样,转到设置并打开我们的插件。 我们保存设置并重新启动VLC。
插件实现
要实现我的插件,我需要了解如何控制媒体播放器(更改音轨,倒带)以及如何处理键盘按键事件。 为了理解所有这些,我转向了
文档 。 同样在Internet上,我发现了一些有趣的文章,它们阐明了媒体播放器
的体系结构:VLC媒体框架的体系结构和
VLC媒体播放器API文档 。
VLC包含大量独立模块(400+)。 每个模块都应提供有关其实现的功能类型以及初始化/完成功能的信息。 使用
set_capability()和
set_callbacks()宏在
vlc_module_begin() -
vlc_module_end()块中描述了此信息。 模块的初始化/完成函数(通常称为
Open和
Close )具有以下签名:
static int Open(vlc_object_t *) static void Close(vlc_object_t *)
vlc_object_t是在VLC中表示数据的基本类型,所有其他数据都将继承自该类型(请参阅文章
Object_Management )。 必须根据模块实现的功能将指向
vlc_object_t的指针转换为特定的数据类型。 为了控制媒体播放器,我在
set_capability()宏中
设置了 接口值。 因此,在
打开和
关闭函数中,我需要将
vlc_object_t强制转换为
intf_thread_t 。
模块之间的交互基于
观察者设计模式。 VLC提供了一种
“对象变量”机制(请参见
Variables ),您可以使用该机制将变量添加到
vlc_object_t类型及其实例的实例中。 模块可以通过这些变量交换数据。 您还可以将回调函数附加到该变量,该变量的值更改时将调用该函数。
例如,考虑负责处理热键事件的
热键模块(
modules / control / hotkeys.c )。 在Open函数中,
ActionEvent回调函数挂在
key-action 变量上 :
var_AddCallback( p_intf->obj.libvlc, "key-action", ActionEvent, p_intf );
指向
vlc_object_t的指针,变量名,回调函数和指向void的指针传递给
var_AddCallback函数以传递任意数据,然后将其转发给指定的回调函数。 回调函数的签名如下所示。
static int ActionEvent(vlc_object_t *, char const *, vlc_value_t, vlc_value_t, void *)
指向
vlc_object_t的指针,变量名称,该变量的旧值和新值(在这种情况下,是该操作对应的按下的热键组合的标识符)以及添加回调函数时指定的任何其他数据的指针,将传递给回调函数。
热键事件的直接处理在
PutAction函数中执行,该函数在
ActionEvent回调函数中调用。
PutAction函数接受按下热键组合(
i_action )的事件的标识符,并在切换操作符的帮助下执行相应的动作。
例如,倒带事件对应于
ACTIONID_JUMP_BACKWARD_SHORT
。 要执行相应的操作,请从VLC设置(从变量
short-jump-size )获取倒带间隔:
mtime_t it = var_InheritInteger( p_input, varname );
要倒带正在播放的文件,只需将
time-offset变量设置为与您要转换播放的时间对应的值(以微秒为单位):
var_SetInteger( p_input, "time-offset", it * sign * CLOCK_FREQ );
对于快进,您需要指定一个正值,对于快退,则需要指定-负值。
CLOCK_FREQ常数用于将秒转换为微秒。
同样,音轨也会发生变化(
ACTIONID_AUDIO_TRACK
事件)。 只有负责音轨的
audio-es变量才能接受有限的一组值(根据正在播放的文件中可用的音轨)。 您可以使用
var_Change()函数获取变量的可能值的列表:
vlc_value_t list, list2; var_Change( p_input, "audio-es", VLC_VAR_GETCHOICES, &list, &list2 );
除了值列表之外,此功能还允许您获取这些值的描述列表(在本例中为音轨的名称)。 现在我们可以使用
var_Set()函数更改音轨:
var_Set( p_input, "audio-es", list.p_list->p_values[i] );
弄清楚如何管理媒体播放器,还有待学习如何处理键盘事件。 不幸的是,我无法添加新的热键。 所有热键都硬编码在VLC内核代码(
src / misc / actions.c )中。 因此,我为低级键盘按键事件添加了一个处理程序,挂起了我的回调函数来更改
按键变量:
var_AddCallback( p_intf->obj.libvlc, "key-pressed", KeyboardEvent, p_intf );
按下按键的变量存储与最后按下的按键相对应的字符代码(Unicode)。 例如,当您按下带有数字
“ 1”的
按键时,按键变量将被分配值为
49 (在第16个数字系统中为0x00000031)。 您可以在
unicode-table.com上查看其他字符代码。 此外,
按键变量的值考虑了修改键的按下,为它们分配了第四个有效字节。 因此,例如,当按下“
Ctrl + 1 ”组合
键时,按键变量将被赋予值0x
04 000031(00000
1 00 00000000 00000000 00110001
2 )。 为了清楚起见,下表显示了各种组合键的含义:
按下“
Shift + 1 ”组合
键时请注意该值。 由于在这种情况下,“
! “,那么第一个字节的值将对应于Unicode(0x00000021)中此字符的代码。
结果
我将我的
TIP插件称为“请翻译”的缩写,也可以将提示翻译为“提示”。 该插件的源代码发布在
GitHub上 ,您还可以在其中下载适用于Windows和Linux的现成插件程序集。
要安装插件,您需要将文件
libtip_plugin.dll (对于Linux为libtip_plugin.so)复制到
<path-to-vlc> / plugins文件夹。 在Windows上,VLC通常安装在
C:\ Program Files \ VideoLAN \ VLC文件夹中。 在Linux上,可以使用以下命令找到安装文件夹:
whereis vlc
例如,在Ubuntu中,VLC安装在
/ usr / lib / x86_64-linux-gnu / vlc中 。
接下来,您将需要重新启动VLC,然后在主菜单中打开``
工具'' >
``首选项'' ,切换到高级视图(在
``显示设置''组中选择
` `
全部'' ),转到左侧面板的
`` 界面'' >
` ` 控制''部分,然后选中
``提示''旁边的框
(请翻译) 。 然后再次需要重新启动VLC。

在插件设置中,您可以指定主要和辅助(用于翻译)音轨的编号,以及插件倒带与辅助音轨重复播放的时间(以秒为单位)。

为了控制插件,我添加了以下键盘快捷键:
- “ / ”用于翻译
- “ Shift + / ”重复以前翻译的视频片段和主要音轨
在执行翻译和重试命令期间,该插件分别在左上角显示消息
“提示:翻译”和
“提示:重复” 。

根据我使用插件的经验,我可以说总体上我对结果感到满意。 最主要的是选择正确的内容,如果至少能理解那里使用的外国语音的一半,则该插件将有助于翻译其余内容。 否则,该插件将可能无用。