- PVSM.RU - https://www.pvsm.ru -

В данной статье я расскажу о том, как написать плагин на языке C для медиаплеера VLC. Я написал свой плагин для упрощения просмотра сериалов и фильмов на английском языке. Идея создания этого плагина описывается в разделах Идея и Поиск решения. Технические детали реализации плагина приведены в разделах Hello World плагин и Реализация. О том, что получилось в итоге и как этим пользоваться можно прочитать в последнем разделе, Результат.
Исходный код проекта доступен на GitHub [1].
Идея изучать иностранный язык за просмотром любимого сериала не нова, но вот с ее воплощением в жизнь лично у меня всегда возникали проблемы. Очень сложно смотреть сериал или фильм, когда не понимаешь половину того, что там говорят. Конечно, можно включить субтитры, но если в речи встретится незнакомое слово или выражение, то от того, что оно будет продублировано текстом, яснее не станет. А смотреть сериал с русскими субтитрами мне совсем не понравилось — переключается на родной язык и перестает воспринимать иностранную речь. Я где-то читал, что сначала нужно смотреть серию на русском языке, а потом уже в оригинале. Но меня такой подход тоже не устроил. Во-первых, где взять столько времени, чтобы по нескольку раз смотреть одно и то же, а во-вторых, смотреть второй раз уже не так интересно — теряется мотивация.
Несмотря на все сложности с просмотром иностранных сериалов, я довольно неплохо могу читать техническую документацию, статьи и книги на английском. Книги мне нравится читать на электронной читалке Kindle, так как там классно реализована функция работы со словарями — можно одним касанием экрана найти перевод незнакомого слова. Англоязычные статьи и сайты удобно читать, установив в браузере специальное расширение для перевода, — я пользуюсь расширением Яндекс.Перевод [3]. Такой подход позволяет читать и понимать английские тексты, не сильно отвлекаясь на поиск незнакомых слов.
Я подумал, почему бы не применить тот же подход для просмотра сериалов — включаем серию на английском, как только встречается непонятная фраза, переключаемся на русскую аудиодорожку и отматываем немного назад. Далее продолжаем смотреть серию на английском.
На самом деле, вся необходимая мне функциональность уже доступна во многих популярных медиаплеерах. Единственное, мне бы хотелось переключать аудиодорожку и отматывать видео на несколько секунд назад нажатием одной кнопки. Также было бы здорово, если бы после перевода непонятного фрагмента медиаплеер сам переключал аудиодорожку обратно на английскую. Ну и еще неплохо было бы иметь возможность повторять переведенный ранее фрагмент с английской дорожкой.
То есть, мне нужен медиаплеер, под который можно писать плагины. Желательно еще, чтобы он был кроссплатформенным, так как я пользуюсь ПК под Windows и ноутбуком под Linux. Мой выбор сразу же пал на VLC. На хабре я даже нашел статью [4], в которой @Idunno [5] рассказывает, как написать расширение VLC на LUA. Кстати, это расширение он тоже написал для изучения английского) К сожалению, данное расширение не работает в последних версиях VLC (старше 2.0.5). Из-за нестабильной работы из LUA API убрали возможность добавления callback-функций, через которые в LUA-расширении можно было обрабатывать события клавиатуры. В README [6] к своему расширению на GitHub @Idunno [5] приводит ссылку на mailing list разработчиков VLC с обсуждением данной проблемы.
Таким образом, для реализации моей идеи расширение на LUA не подойдет, нужно писать плагин на C. И хоть я и писал что-либо на C последний раз лет 7 назад, еще в университете, решил попробовать.
Стоит отметить, что у медиаплеера VLC достаточно хорошая документация [7]. Из нее я узнал, что при разработке медиаплеера используется модульный подход. VLC состоит из нескольких независимых модулей, реализующих определенную функциональность, и ядра (libVLCCore), которое управляет этими модулями. При этом модули бывают двух типов: внутренние (in-tree) и внешние (out-of-tree). Исходный код внутренних модулей хранится в одном репозитории с кодом ядра. Внешние модули разрабатываются и собираются независимо от медиаплеера VLC. Собственно, последние и есть то, что называют плагинами.
В документации также есть статья [8] о том, как написать свой плагин (модуль) на языке C. В этой статье приводится исходный код простого плагина, который при запуске VLC выводит на консоль приветственное сообщение «Hello, <name>» (значение <name> берется из настроек плагина). Забегая немного вперед, скажу, что в приведенном примере нужно добавить следующую строчку после set_category(CAT_INTERFACE):
set_subcategory( SUBCAT_INTERFACE_CONTROL )
Отлично, осталось только собрать плагин и протестировать его работу. По сборке внешнего плагина тоже есть инструкция [9]. Тут стоит обратить внимание на раздел Internationalization, в котором описывается, как работает локализация в VLC. Если коротко, то для внешних плагинов требуется определять макросы N_(), _():
#define DOMAIN "vlc-myplugin"
#define _(str) dgettext(DOMAIN, str)
#define N_(str) (str)
Для сборки предлагается использовать старый добрый Makefile либо Autotools. Я решил пойти простым путем и выбрал Makefile. В Makefile нужно не забыть определить переменную MODULE_STRING — это идентификатор нашего плагина. Также я немного подправил работу с директориями — теперь они определяются через pkg-config. В итоге получились следующие файлы:
/**
* @file hello.c
* @brief Hello world interface VLC module example
*/
#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="hello"
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="$(INSTALL) -s"
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» мы не увидели. Все дело в том, что плагин нужно сначала включить. Для этого открываем настройки (Tools > Preferences), переключаемся на расширенный вид (выбираем All в группе Show settings) и находим в дереве на панели слева Interface > Control interfaces — ставим галочку напротив нашего плагина Hello interface.
Сохраняем настройки и перезапускаем VLC.
С 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 [12] (важно скачивать именно 7z-архив).
Копируем содержимое архива в какую-нибудь папку, например, в С:Projects. Кроме sdk архив содержит и сам медиаплеер, который можно использовать для тестирования и отладки плагина.
Чтобы наш Makefile можно было использовать для сборки и установки плагина, нужно поправить файл C:Projectsvlc-3.0.8sdklibpkgconfigvlc-plugin.pc, указав в переменных prefix и pluginsdir корректный путь до папки с sdk и plugins соответственно:
prefix=/c/Projects/vlc-3.0.8/sdk
pluginsdir=/c/Projects/vlc-3.0.8/plugins
Для сборки под Windows нам также потребуется установить компилятор и другие утилиты. Все необходимое ПО можно получить, установив среду MSYS2 [13]. На сайте проекта есть подробная инструкция по установке. Если коротко, то сразу после установки нужно открыть консоль (C:msys64msys2.exe) и обновить пакеты MSYS2 с помощью команды:
pacman -Syu
Далее нужно закрыть окно терминала MSYS2, затем открыть его еще раз и выполнить команду
pacman -Su
После обновления всех пакетов нужно установить тулчейн:
pacman -S base-devel mingw-w64-x86_64-toolchain
Теперь, когда все необходимые пакеты установлены, можно приступать к сборке плагина. Я немного доработал Makefile, чтобы он мог собирать плагин как под Linux так и под Windows. Кроме того, пришлось убрать некоторые неподдерживаемые MinGW параметры сборки, в итоге 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="hello"
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="$(INSTALL) -s"
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 ничего не знает о нашем sdk для VLC, то перед сборкой нужно добавить в переменную окружения PKG_CONFIG_PATH путь до папки pkgconfig из этого sdk. Открываем консоль MinGW (C:msys64mingw64.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.
Для реализации моего плагина мне необходимо было понять, как управлять медиаплеером (менять аудиодорожку, перематывать назад) и как обрабатывать события нажатия клавиш клавиатуры. Чтобы разобраться во всем этом, я обратился к документации [14]. Также в Интернете я нашел пару интересных статей, проливающих свет на архитектуру медиаплеера: The architecture of VLC media framework [15] и VLC media player API Documentation [16].
VLC состоит из большого количества независимых модулей (400+). Каждый модуль должен предоставлять информацию о типе реализуемой им функциональности, а также функции инициализации/финализации. Данная информация описывается в блоке vlc_module_begin() — vlc_module_end() с помощью макросов set_capability() и set_callbacks(). Функции инициализации/финализации модуля (обычно они называются Open и Close) имеют следующую сигнатуру:
static int Open(vlc_object_t *)
static void Close(vlc_object_t *)
vlc_object_t — это базовый тип для представления данных в VLC, от которого наследуются все остальные (см. статью Object_Management [17]). Указатель на vlc_object_t нужно приводить к конкретному типу данных в соответствии с той функциональностью, которую реализует модуль. Для управления медиаплеером я указал в макросе set_capability() значение interface. Соответственно, в функциях Open и Close мне нужно привести vlc_object_t к intf_thread_t.
Взаимодействие между модулями построено на основе шаблона проектирования observer [18]. VLC предоставляет механизм «object variables» (см. статью Variables [19]), с помощью которого в экземпляры типа vlc_object_t (и его производные) можно добавлять переменные. Через эти переменные модули могут обмениваться данными. Также на переменную можно навесить callback-функцию, которая будет вызываться при изменении значения этой переменной.
В качестве примера можно рассмотреть модуль Hotkeys (modules/control/hotkeys.c [20]), который отвечает за обработку событий нажатия горячих клавиш. В функции Open на переменную key-action навешивается callback-функция ActionEvent:
var_AddCallback( p_intf->obj.libvlc, "key-action", ActionEvent, p_intf );
В функцию var_AddCallback передается указатель на vlc_object_t, имя переменной, callback-функция и указатель на void для передачи произвольных данных, которые потом пробрасываются в указанную callback-функцию. Сигнатура callback-функции приведена ниже.
static int ActionEvent(vlc_object_t *, char const *, vlc_value_t, vlc_value_t, void *)
В качестве параметров в callback-функцию передается указатель на vlc_object_t, имя переменной, старое и новое значение этой переменной (в данном случае, идентификатор соответствующего нажатой комбинации горячих клавиш действия), а также заданный при добавлении callback-функции указатель на какие-либо дополнительные данные.
Непосредственно обработка событий горячих клавиш выполняется в функции PutAction, которая вызывается внутри callback-функции ActionEvent. Функция PutAction принимает на вход идентификатор события нажатия комбинации горячих клавиш (i_action) и с помощью оператора switch выполняет соответствующие действия.
Например, событию перемотки назад соответствует значение 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 [21]). Поэтому я добавил обработчик более низкоуровневых событий нажатия клавиш клавиатуры, повесив свою callback-функцию на изменение переменной key-pressed:
var_AddCallback( p_intf->obj.libvlc, "key-pressed", KeyboardEvent, p_intf );
В переменной key-pressed хранится код символа (в Unicode), соответствующего последней нажатой клавише. Например, при нажатии клавиши с цифрой «1» переменной key-pressed будет присвоено значение 49 (0x00000031 в 16ой системе счисления). Посмотреть коды других символов можно на сайте unicode-table.com [22]. Кроме того, в значении переменной key-pressed учитывается нажатие клавиш-модификаторов, для них отведен четвертый значащий байт. Так, например, при нажатии комбинации клавиш «Ctrl + 1» переменной key-pressed будет присвоено значение 0x04000031 (00000100 00000000 00000000 001100012). Для наглядности в таблице ниже приведены значения различных комбинаций клавиш:
| Комбинация клавиш | Значение |
|---|---|
| Ctrl + 1 | 00000100 00000000 00000000 001100012 |
| Alt + 1 | 00000001 00000000 00000000 001100012 |
| Ctrl + Alt + 1 | 00000101 00000000 00000000 001100012 |
| Shift + 1 | 00000010 00000000 00000000 001000012 |
Обратите внимание на значение при нажатии комбинации «Shift + 1». Так как в этом случае будет выведен символ «!», то и значение первого байта будет соответствовать коду этого символа в Unicode (0x00000021).
Я назвал свой плагин TIP — акроним от фразы «translate it, please», также tip можно перевести как «подсказка». Исходный код плагина опубликован на GitHub [1], там же можно скачать готовые сборки плагина под Windows и Linux.
Для установки плагина нужно скопировать файл libtip_plugin.dll (libtip_plugin.so для Linux) в папку <path-to-vlc>/plugins. В Windows VLC обычно устанавливается в папку C:Program FilesVideoLANVLC. В Linux найти папку установки можно с помощью команды:
whereis vlc
В Ubuntu, например, VLC ставится в /usr/lib/x86_64-linux-gnu/vlc.
Далее потребуется перезапустить VLC, затем в главном меню открыть Tools > Preferences, переключиться на расширенный вид (выбрать All в группе Show settings), на панели слева перейти в раздел Interface > Control и поставить галочку напротив пункта TIP (translate it, please). После чего снова потребуется перезапуск VLC.
В настройках плагина можно указать номера основной и вспомогательной (для перевода) аудиодорожек, а также время (в секундах), на которое плагин будет отматывать назад для повтора со вспомогательной аудиодорожкой.
Для управления плагином я добавил следующие горячие клавиши:
Во время выполнения команд перевода и повтора плагин отображает в верхнем левом углу сообщения «TIP: translate» и «TIP: repeat» соответственно.

По своему опыту использования плагина могу сказать, что, в целом, я доволен результатом. Главное, правильно подобрать контент, если будет понятна хотя бы половина используемой там иностранной речи, плагин поможет перевести остальное. В противном случае плагин, наверное, будет бесполезен.
Автор: aklexel
Источник [25]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/inostrannyie-yazyiki/336760
Ссылки в тексте:
[1] GitHub: https://github.com/aklexel/vlc-tip-plugin
[2] мозг: http://www.braintools.ru
[3] Яндекс.Перевод: https://yandex.ru/soft/element/translate/
[4] статью: https://habr.com/ru/post/169351/
[5] @Idunno: https://habr.com/ru/users/zm33y/
[6] README: https://github.com/zm33y/vlc-lua-sia
[7] документация: https://wiki.videolan.org/Documentation:Documentation/
[8] статья: https://wiki.videolan.org/Hacker_Guide/How_To_Write_a_Module/
[9] инструкция: https://wiki.videolan.org/OutOfTreeCompile/
[10] Image: https://habrastorage.org/webt/fi/wr/ey/fiwreyqdhfd4ncbgzc2g8z1z3y8.png
[11] Image: https://habrastorage.org/webt/g3/-n/h3/g3-nh3qc7k37kzwpbfywtow8msa.png
[12] ftp://ftp.videolan.org/pub/videolan/vlc/3.0.8/win64/vlc-3.0.8-win64.7z: https://www.pvsm.ruftp://ftp.videolan.org/pub/videolan/vlc/3.0.8/win64/vlc-3.0.8-win64.7z
[13] MSYS2: http://www.msys2.org/
[14] документации: https://wiki.videolan.org/Hacker_Guide
[15] The architecture of VLC media framework: https://web.archive.org/web/20141204234622/http://www.enjoythearchitecture.com/vlc-architecture.html
[16] VLC media player API Documentation: https://scholar.google.at/scholar?cluster=5791386869307511242
[17] Object_Management: https://wiki.videolan.org/Hacker_Guide/Object_Management/
[18] observer: https://en.wikipedia.org/wiki/Observer_pattern
[19] Variables: https://wiki.videolan.org/Hacker_Guide/Variables/
[20] modules/control/hotkeys.c: https://git.videolan.org/?p=vlc.git;a=blob;f=modules/control/hotkeys.c;hb=bb21396e96093b30145dfe1ed328853359b1b70c
[21] src/misc/actions.c: https://git.videolan.org/?p=vlc.git;a=blob;f=src/misc/actions.c;hb=8349b1a82af397d27d0fd820f76e5b80b83707d0
[22] unicode-table.com: https://unicode-table.com/
[23] Image: https://habrastorage.org/webt/df/06/qk/df06qkcl2vbiatwq7j_8uw4m_ey.png
[24] Image: https://habrastorage.org/webt/mv/9-/vu/mv9-vuo9mfzicqyiqpsopnevrpu.png
[25] Источник: https://habr.com/ru/post/475992/?utm_campaign=475992&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.