Hack para dar suporte aos botões do fone de ouvido do Windows Android

Quase todos os dias ouço música no meu smartphone e uso os botões de controle no fone de ouvido. Mas eu sempre não gostei de uma coisa. Volto para casa, continuando a ouvir, o fone de ouvido se conecta ao meu PC em casa - e de repente os botões param de funcionar.

Obviamente, pesquisei a solução para este problema. Infelizmente, no Windows, esse recurso maravilhoso não é muito suportado. Alguns minutos de pesquisa deram apenas menções turvas no Stack Overflow sobre placas de som e mensagens de algumas pessoas de que tudo funcionava bem em seus laptops.

Isso não me assustou - e decidi aceitar o problema como um desafio interessante: é possível criar algum tipo de programa para ativar os botões de controle, se não houver suporte de hardware para eles? A resposta é sim, você pode. E eis como fazê-lo em meia hora.

Como funcionam os botões do fone de ouvido Android


A primeira coisa a entender é como os botões do fone de ouvido funcionam. Uma pesquisa rápida na Internet encontrou essa especificação na documentação do Android. Há um gráfico lá.



Como você pode entender, quando você pressiona um botão no fone de ouvido, o circuito em um dos resistores se fecha. Destaca-se o botão A (Reproduzir / Pausar / Gancho) com uma resistência de 0 ohms, ou seja, um curto-circuito do microfone. Se conseguirmos detectar um curto-circuito no microfone, podemos determinar a pressão do botão Reproduzir / Pausar.

Teste de hipótese


Antes de iniciar a programação, gostaria de verificar a razoabilidade do nosso raciocínio em princípio. Ou seja, o fato de que o sinal do microfone pode ser determinado pressionando o botão Reproduzir / Pausar. Felizmente, para isso, basta gravar o som no computador e observar o resultado. Lancei o Audacity, pressionei o botão Reproduzir / Pausar durante a gravação - e recebi esse sinal.


Bingo

Como você pode ver, pressionar o botão é obviamente refletido na forma de onda: uma queda repentina para -1 seguida de uma transição repentina para 1 e uma diminuição gradual para 0. Intuitivamente pela especificação, eu assumiria que o sinal salta para 1 e permanece lá até o botão ser liberado, mas na realidade, parece diferente. No entanto, essa imagem ainda é fácil de detectar se você capturar o fluxo de áudio do microfone.

Captura de som com Python


Conhecendo a maneira de detectar o pressionamento de botão no fone de ouvido, você pode pensar no objetivo principal: como controlar o player na área de trabalho usando os botões do fone de ouvido.

O primeiro passo é detectar um clique no botão. Para fazer isso, você precisa pegar o fluxo de áudio do microfone e descobrir a assinatura distinta que vimos anteriormente. Para simplificar, implementamos a solução em Python. Após outra pequena pesquisa na Internet, encontrei um pacote chamado sounddevice, que permite abstrair da parte mais difícil - captura de áudio real de um microfone.

Um pouco de codificação nos dá o seguinte:

import sounddevice as sd SAMPLE_RATE = 1000 # Sample rate for our input stream BLOCK_SIZE = 100 # Number of samples before we trigger a processing callback class HeadsetButtonController: def process_frames(self, indata, frames, time, status): mean = sum([y for x in indata[:] for y in x])/len(indata[:]) print(mean) def __init__(self): self.stream = sd.InputStream( samplerate=SAMPLE_RATE, blocksize=BLOCK_SIZE, channels=1, callback=self.process_frames ) self.stream.start() if __name__ == '__main__': controller = HeadsetButtonController() while True: pass 

Esse código produz continuamente o valor médio de cada lote de amostras. Definimos a taxa de amostragem para 1000, o que é terrivelmente pequeno para o processamento de som (geralmente é usado 44100), mas na verdade não precisamos de muita precisão. O tamanho do bloco determina quantas amostras no buffer acionam um retorno de chamada. Novamente, definimos valores muito baixos. Um tamanho de bloco de 100 e uma taxa de amostragem de 1000 na verdade significa acionar 10 vezes por segundo, onde apenas 100 amostras são processadas com cada chamada.

Detecção de clique no botão: provavelmente muito fácil


Agora capturamos o fluxo de áudio e podemos implementar um mecanismo real para detectar o pressionamento de botão. Lembre-se de que o sinal salta para 1 cada vez que você pressiona. Isso sugere a maneira mais fácil de detectar: ​​se N blocos consecutivos tiverem um valor de sinal acima de 0,9, ou seja, um clique.

Implementamos o algoritmo em nossa função:

 import sounddevice as sd SAMPLE_RATE = 1000 # Sample rate for our input stream BLOCK_SIZE = 100 # Number of samples before we trigger a processing callback PRESS_SECONDS = 0.2 # Number of seconds button should be held to register press PRESS_SAMPLE_THRESHOLD = 0.9 # Signal amplitude to register as a button press BLOCKS_TO_PRESS = (SAMPLE_RATE/BLOCK_SIZE) * PRESS_SECONDS ... def process_frames(self, indata, frames, time, status): mean = sum([y for x in indata[:] for y in x])/len(indata[:]) if mean < PRESS_SAMPLE_THRESHOLD: self.times_pressed += 1 if self.times_pressed > BLOCKS_TO_PRESS and not self.is_held: # The button was pressed! self.is_held = True else: self.is_held = False self.times_pressed = 0 ... 

De fato, lançamos um contador interno, quantos blocos processados ​​atendem ao requisito de limite, que é simplesmente definido como 0,9, proporcionando o inevitável ruído da amostra. Se o bloco não atender ao requisito, o contador será redefinido - e começaremos novamente. A variável is_held monitora os acionadores para não registrá-los repetidamente se o botão não for liberado.

Controle de reprodução do Windows


Agora resta apenas substituir o comentário "O botão foi pressionado!" No código real para controlar a reprodução de áudio no Windows. Google novamente para descobrir como fazer isso: você pode controlar a reprodução simulando pressionamentos de teclas com os códigos de teclas virtuais correspondentes.

Aconteceu que simular pressionamentos de tecla é muito fácil com o pacote pywin32 , que é apenas um shell Python para a API do Windows. Juntando tudo, podemos criar a seguinte função:

 import win32api import win32con VK_MEDIA_PLAY_PAUSE = 0xB3 def toggle_play(): win32api.keybd_event(VK_MEDIA_PLAY_PAUSE, 0, 0, 0) 

E nós conseguimos! toggle_play função toggle_play no lugar do código em que o comentário "O botão foi pressionado!" , permite controlar qualquer media player no Windows usando os botões do fone de ouvido Android.

Testes mostraram que o código funciona surpreendentemente bem. A única diferença entre a funcionalidade no Android e no Windows é um pequeno atraso ao pressionar o botão, mas você pode conviver com ele.


E então o que aconteceu

O script Python consiste em 51 linhas que ativam os botões no fone de ouvido Android no Windows. O código fonte final deste projeto está no Github .

Espere, isso não é tudo!


Depois de usar o programa alegremente por várias horas, notei um problema sério:



O programa usa quase 30% da CPU! Obviamente, isso é inaceitável durante um longo trabalho, algo precisa ser feito. Observando o código, percebi que o thread principal está no estado ocioso no loop principal, embora nada aconteça lá. A solução mais lógica é simplesmente sacrificar o encadeamento para sempre: como o retorno de chamada é chamado automaticamente, ainda não precisamos de um loop.

 from time import sleep if __name__ == '__main__': controller = HeadsetButtonController() while True: sleep(10) 



Eu também não queria executar o script Python manualmente após cada inicialização do computador. Felizmente, o Python para Windows vem com um utilitário útil chamado pythonw.exe que inicia o processo daemon sem um terminal conectado. Colocamos um atalho para esse processo no diretório Microsoft \ Windows \ Menu \ Programas \ Startup , especificando nosso script como o primeiro argumento - então o aplicativo é iniciado automaticamente e é executado silenciosamente em segundo plano.

Source: https://habr.com/ru/post/pt417303/


All Articles