
Neste artigo, falarei sobre como escrever um plug-in em C para o VLC media player. Eu escrevi meu plugin para simplificar assistir programas de TV e filmes em inglês. A ideia de criar este plugin é descrita nas seções
Idéia e
Localização de uma solução . Detalhes técnicos da implementação do plug-in são fornecidos nas seções
Plug -
in do Hello World e
Implementação . O que aconteceu no final e como usá-lo pode ser encontrado na última seção,
Resultado .
O código fonte do projeto está disponível no
GitHub .
Idéia
A idéia de aprender uma língua estrangeira enquanto assisto minha série favorita não é nova, mas sempre tive problemas com ela pessoalmente. É muito difícil assistir a uma série ou filme quando você não entende metade do que eles dizem. Obviamente, você pode ativar legendas, mas se uma palavra ou expressão desconhecida for encontrada em um discurso, não ficará mais claro que será duplicado pelo texto. E eu não gostei de assistir a série com legendas em russo - o cérebro muda para o idioma nativo e deixa de perceber fala estrangeira. Li em algum lugar que primeiro você precisa assistir a uma série em russo e depois no original. Mas essa abordagem também não me agradou. Em primeiro lugar, onde levar tanto tempo para assistir à mesma coisa várias vezes e, em segundo lugar, assistir à segunda vez não é mais tão interessante - a motivação está perdida.
Apesar de todas as dificuldades em assistir programas de TV estrangeiros, eu posso muito bem ler documentação técnica, artigos e livros em inglês. Gosto de ler livros no leitor eletrônico do Kindle, pois a função de dicionário é legal por lá - você pode encontrar uma tradução de uma palavra desconhecida com um toque na tela. É conveniente ler artigos e sites em inglês instalando uma extensão especial para tradução no navegador - eu uso a extensão
Yandex.Translation . Essa abordagem permite que você leia e compreenda textos em inglês, sem muita distração para a busca de palavras desconhecidas.
Pensei, por que não aplicar a mesma abordagem para assistir programas de TV - ligamos a série em inglês assim que uma frase incompreensível é encontrada, mudamos para a faixa de áudio russa e retrocedemos um pouco. Em seguida, continuamos a assistir a série em inglês.
Procure uma solução
De fato, toda a funcionalidade que eu preciso já está disponível em muitos players de mídia populares. A única coisa que gostaria de mudar a faixa de áudio e retroceder o vídeo alguns segundos atrás com o clique de um botão. Também seria ótimo se, depois de traduzir um fragmento incompreensível, o próprio media player retornasse a faixa de áudio para o inglês. Bem, seria bom poder repetir o fragmento traduzido anteriormente com a faixa em inglês.
Ou seja, preciso de um media player para o qual você possa escrever plugins. Também é desejável que seja multiplataforma, pois eu uso um PC no Windows e um laptop no Linux. Minha escolha caiu imediatamente no VLC. No habr, eu até encontrei
um artigo no qual
@Idunno diz como escrever uma extensão VLC no LUA. A propósito, ele também escreveu esta extensão para aprender inglês. Infelizmente, essa extensão não funciona nas versões mais recentes do VLC (anteriores à 2.0.5). Devido à operação instável, a capacidade de adicionar funções de retorno de chamada através das quais foi possível processar eventos de teclado na extensão LUA foi removida da API LUA. No
README , um link para a lista de discussão dos desenvolvedores do VLC que discutem esse problema leva à sua extensão no GitHub
@Idunno .
Portanto, para implementar minha ideia, uma extensão para LUA não funciona, você precisa escrever um plug-in em C. E, embora eu tenha escrito algo em C pela última vez há cerca de 7 anos, na universidade, decidi tentar.
Hello world plugin
Vale a pena notar que o VLC media player possui uma
documentação muito boa. Aprendi com ele que o desenvolvimento de um media player usa uma abordagem modular. O VLC consiste em vários módulos independentes que implementam determinadas funcionalidades e no kernel (
libVLCCore ), que gerencia esses módulos. Existem dois tipos de módulos: interno (
dentro da árvore ) e externo (
fora da árvore ). O código fonte dos módulos internos é armazenado em um repositório com o código do kernel. Os módulos externos são desenvolvidos e montados independentemente do media player VLC. Na verdade, estes são os chamados plugins.
A documentação também possui
um artigo sobre como gravar seu plug-in (módulo) em C. Este artigo fornece o código-fonte de um plug-in simples que, quando o VLC é iniciado, exibe uma mensagem de boas-vindas "
Hello, <name> " no console (o valor <name> é usado nas configurações do plug-in). Correndo um pouco à frente, direi que no exemplo acima, adicione a seguinte linha após
set_category(CAT_INTERFACE)
:
set_subcategory( SUBCAT_INTERFACE_CONTROL )
Bem, tudo o que resta é montar o plug-in e testar seu funcionamento. Há também uma
instrução para criar um plugin externo. Aqui, vale a pena prestar atenção na seção
Internacionalização , que descreve como a localização funciona no VLC. Em resumo, para plug-ins externos, você precisa definir as macros
N_()
,
_()
:
#define DOMAIN "vlc-myplugin" #define _(str) dgettext(DOMAIN, str) #define N_(str) (str)
Para montagem, propõe-se usar os bons e antigos Makefile ou Autotools. Decidi seguir o caminho mais simples e escolhi o Makefile. No Makefile, você precisa se lembrar de definir a variável
MODULE_STRING
- este é o identificador do nosso plugin. Também ajustei um pouco o trabalho com os diretórios - agora eles são definidos através do
pkg-config . O resultado são os seguintes arquivos:
hello.c #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); }
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,-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
A maneira mais fácil de criar um plugin para Linux. Para fazer isso, você precisa instalar, de fato, o próprio VLC media player, bem como os arquivos e as ferramentas necessárias para criar o plug-in. No Debian / Ubuntu, isso pode ser feito com o seguinte comando:
sudo apt-get install vlc libvlc-dev libvlccore-dev gcc make pkg-config
Na verdade, está tudo pronto, coletamos e instalamos nosso plugin usando o comando:
sudo make install
Para testar o plug-in, execute o VLC também no console:
vlc
Infelizmente, não vimos nenhum "
Olá, mundo ". A questão é que o plug-in deve primeiro ser ativado. Para fazer isso, abra as configurações (
Ferramentas >
Preferências ), alterne para a visualização avançada (selecione
Tudo no grupo
Mostrar configurações ) e encontre na árvore no painel esquerdo
Interface >
Interfaces de controle - marque a caixa ao lado do nosso plug-in de
interface Hello .

Salvaremos as configurações e reiniciaremos o VLC.

Crie um plug-in para Windows
Com o Windows, as coisas são um pouco mais complicadas. Para criar o plug-in, você precisa fazer o download do sdk, que contém bibliotecas, cabeçalho e arquivos de configuração do VLC. Anteriormente, o sdk fazia parte do conjunto regular do VLC e podia ser encontrado na pasta de instalação do programa. Agora, ele vem como uma montagem separada do media player. Por exemplo, para o VLC versão 3.0.8, é possível fazer o download deste assembly em
ftp://ftp.videolan.org/pub/videolan/vlc/3.0.8/win64/vlc-3.0.8-win64.7z (é importante fazer o download do 7z -arquivo).
Copie o conteúdo do arquivo morto para uma pasta, por exemplo, em
C: \ Projetos . Além do sdk, o arquivo morto também contém o próprio media player, que pode ser usado para testar e depurar o plug-in.
Para que nosso Makefile possa ser usado para criar e instalar o plug-in, é necessário corrigir o arquivo
C: \ Projects \ vlc-3.0.8 \ sdk \ lib \ pkgconfig \ vlc-plugin.pc , indicando o caminho correto para a pasta
sdk nas variáveis
prefix e
pluginsdir e
plugins, respectivamente:
prefix=/c/Projects/vlc-3.0.8/sdk pluginsdir=/c/Projects/vlc-3.0.8/plugins
Para criar no Windows, também precisamos instalar um compilador e outros utilitários. Todo o software necessário pode ser obtido instalando o ambiente
MSYS2 . O site do projeto possui instruções detalhadas de instalação. Em resumo, imediatamente após a instalação, você precisa abrir o console (
C: \ msys64 \ msys2.exe ) e atualizar os pacotes MSYS2 usando o comando:
pacman -Syu
Em seguida, feche a janela do terminal MSYS2, abra-a novamente e execute o comando
pacman -Su
Após atualizar todos os pacotes, você precisa instalar o conjunto de ferramentas:
pacman -S base-devel mingw-w64-x86_64-toolchain
Agora que todos os pacotes necessários estão instalados, você pode começar a criar o plugin. Modifiquei um pouco o Makefile para que ele pudesse criar o plug-in no Linux e no Windows. Além disso, eu tive que remover alguns parâmetros de compilação MinGW não suportados, como resultado, o Makefile começou a ficar assim:
Makefile para Windows 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
Como o MSYS2 não sabe nada sobre o sdk for VLC, você precisa adicionar o caminho para a pasta
pkgconfig desse sdk na
variável de ambiente
PKG_CONFIG_PATH . Abra o console do MinGW (
C: \ msys64 \ mingw64.exec ) e execute os comandos:
export PKG_CONFIG_PATH=/c/projects/vlc-3.0.8/sdk/lib/pkgconfig:$PKG_CONFIG_PATH make install
Para testar o plug-in, execute o VLC também no console:
/c/projects/vlc-3.0.8/vlc
Como no caso do Linux, vá para as configurações e ative nosso plugin. Salvaremos as configurações e reiniciaremos o VLC.
Implementação de plugins
Para implementar meu plug-in, eu precisava entender como controlar o media player (alterar a faixa de áudio, rebobinar) e como lidar com os eventos de pressionamento de tecla do teclado. Para entender tudo isso, virei-me para a
documentação . Também na Internet, encontrei alguns artigos interessantes que esclarecem a arquitetura do media player:
A arquitetura da estrutura de mídia VLC e a
documentação da API do VLC media player .
O VLC consiste em um grande número de módulos independentes (mais de 400). Cada módulo deve fornecer informações sobre o tipo de funcionalidade que implementa, bem como as funções de inicialização / finalização. Essas informações são descritas no
bloco vlc_module_begin () -
vlc_module_end () usando as
macros set_capability () e
set_callbacks () . As funções de inicialização / finalização do módulo (geralmente chamadas de
Abrir e
Fechar ) têm a seguinte assinatura:
static int Open(vlc_object_t *) static void Close(vlc_object_t *)
vlc_object_t é o tipo de base para representar dados no VLC, do qual todos os outros são herdados (consulte o artigo
Gerenciamento de objetos ). Um ponteiro para
vlc_object_t deve ser
convertido em um tipo de dados específico, de acordo com a funcionalidade que o módulo implementa. Para controlar o media player,
defino o valor
da interface na macro
set_capability () . Assim, nas funções
Abrir e
Fechar , preciso converter
vlc_object_t em
intf_thread_t .
A interação entre os módulos é baseada no padrão de design do
observador . O VLC fornece um mecanismo de
"variáveis de objeto" (consulte
Variáveis ), com o qual você pode adicionar variáveis a instâncias do tipo
vlc_object_t (e suas derivadas). Os módulos podem trocar dados através dessas variáveis. Você também pode anexar uma função de retorno de chamada à variável, que será chamada quando o valor dessa variável for alterado.
Como exemplo, considere o módulo de teclas de
atalho (
modules / control / hotkeys.c ), responsável por manipular eventos de teclas de atalho. Na função Abrir, a função de retorno de chamada
ActionEvent está pendurada na
variável de ação-chave :
var_AddCallback( p_intf->obj.libvlc, "key-action", ActionEvent, p_intf );
Um ponteiro para
vlc_object_t , um nome de variável, uma função de retorno de chamada e um ponteiro para anular são passados para a função
var_AddCallback para transmitir dados arbitrários, que são encaminhados para a função de retorno de chamada especificada. A assinatura da função de retorno de chamada é mostrada abaixo.
static int ActionEvent(vlc_object_t *, char const *, vlc_value_t, vlc_value_t, void *)
Um ponteiro para
vlc_object_t , o nome da variável, os valores antigos e novos dessa variável (nesse caso, o identificador da combinação de teclas de atalho correspondente correspondente da ação), bem como o ponteiro para quaisquer dados adicionais especificados ao adicionar a função de retorno de chamada, são passados para a função de retorno de chamada .
O processamento direto de eventos de teclas de atalho é realizado na função
PutAction , chamada dentro da função de retorno de chamada
ActionEvent . A função
PutAction aceita um identificador de um evento ao pressionar uma combinação de teclas de atalho (
i_action ) e, com a ajuda do operador do comutador, executa as ações correspondentes.
Por exemplo, um evento de retrocesso corresponde a
ACTIONID_JUMP_BACKWARD_SHORT
. Para executar a ação correspondente, o intervalo de retrocesso é obtido das configurações do VLC (da variável
tamanho do salto curto ):
mtime_t it = var_InheritInteger( p_input, varname );
Para retroceder o arquivo em reprodução, basta definir a variável de
deslocamento de tempo no valor correspondente ao tempo (em microssegundos) pelo qual você deseja alterar a reprodução:
var_SetInteger( p_input, "time-offset", it * sign * CLOCK_FREQ );
Para avanço rápido, você precisa especificar um valor positivo, para retrocesso rápido - negativo. A
constante CLOCK_FREQ é usada para converter segundos em microssegundos.
Da mesma forma, a faixa de áudio é alterada (evento
ACTIONID_AUDIO_TRACK
). Somente a variável
audio-es responsável pela faixa de áudio pode aceitar um conjunto limitado de valores (de acordo com as faixas de áudio disponíveis no arquivo sendo reproduzido). Você pode obter uma lista dos possíveis valores de uma variável usando a função
var_Change () :
vlc_value_t list, list2; var_Change( p_input, "audio-es", VLC_VAR_GETCHOICES, &list, &list2 );
Além da lista de valores, essa função também permite obter uma lista de descrições desses valores (nesse caso, o nome das faixas de áudio). Agora podemos mudar a faixa de áudio usando a função
var_Set () :
var_Set( p_input, "audio-es", list.p_list->p_values[i] );
Como gerenciar o media player descoberto, resta aprender a lidar com os eventos do teclado. Infelizmente, não foi possível adicionar uma nova tecla de atalho. Todas as teclas de atalho são codificadas no código do kernel do VLC (
src / misc / actions.c ). Portanto, adicionei um manipulador para eventos de pressionamento de tecla de teclado de nível inferior, suspendendo minha função de retorno de chamada para alterar a variável
pressionada por tecla :
var_AddCallback( p_intf->obj.libvlc, "key-pressed", KeyboardEvent, p_intf );
A variável
pressionada por tecla armazena o código de caractere (em Unicode) correspondente à última tecla pressionada. Por exemplo, quando você pressiona a tecla com o número
"1" , a variável
pressionada por tecla recebe o valor
49 (0x00000031 no 16º sistema numérico). Você pode ver outros códigos de caracteres em
unicode-table.com . Além disso, o valor da variável
pressionada por tecla leva em consideração o pressionamento das teclas modificadoras, o quarto byte significativo é alocado para elas. Assim, por exemplo, ao pressionar a combinação de
teclas "
Ctrl + 1 ", a variável
pressionada por tecla receberá o valor 0x
04 000031 (00000
1 00 00000000 00000000 00110001
2 ). Para maior clareza, a tabela abaixo mostra os significados de várias combinações de teclas:
Preste atenção ao valor ao pressionar a combinação “
Shift + 1 ”. Como neste caso o “
! ", O valor do primeiro byte corresponderá ao código desse caractere em Unicode (0x00000021).
Resultado
Eu chamei meu plug-in de
DICA de acrônimo para a frase "traduza, por favor", também a dica pode ser traduzida como "dica". O código fonte do plug-in é publicado no
GitHub , onde você também pode fazer o download de assemblies de plug-in prontos para Windows e Linux.
Para instalar o plug-in, você precisa copiar o arquivo
libtip_plugin.dll (libtip_plugin.so para Linux) para a pasta
<path-to-vlc> / plugins . No Windows, o VLC geralmente é instalado na pasta
C: \ Arquivos de Programas \ VideoLAN \ VLC . No Linux, você pode encontrar a pasta de instalação usando o comando:
whereis vlc
No Ubuntu, por exemplo, o VLC é instalado em
/ usr / lib / x86_64-linux-gnu / vlc .
Em seguida, você precisará reiniciar o VLC e, no menu principal, abra
Ferramentas >
Preferências , alterne para a visualização avançada (selecione
Tudo no grupo
Mostrar configurações ), vá para a seção
Interface >
Controle no painel esquerdo e marque a caixa ao lado de
DICA (traduza-a, por favor) . Então, novamente, você precisará reiniciar o VLC.

Nas configurações do plug-in, você pode especificar os números das faixas de áudio principal e auxiliar (para tradução), bem como o tempo (em segundos) pelo qual o plug-in será rebobinado para repetir com a faixa de áudio auxiliar.

Para controlar o plug-in, adicionei os seguintes atalhos de teclado:
- " / " Para tradução
- " Shift + / " para repetir o fragmento de vídeo traduzido anteriormente com a faixa de áudio principal
Durante a execução dos comandos de conversão e nova tentativa, o plug-in exibe as mensagens
"DICA: traduzir" e
"DICA: repetir" no canto superior esquerdo
, respectivamente.

Pela minha experiência no uso do plug-in, posso dizer que, em geral, estou satisfeito com o resultado. O principal é escolher o conteúdo certo, se pelo menos metade da fala estrangeira usada lá for entendida, o plug-in ajudará a traduzir o restante. Caso contrário, o plug-in provavelmente será inútil.