Banner alexa tocando musica do pc

No começo desse ano eu percebi o quão distante as Alexas estão ficando em relação ao avanço tecnologico das IAs. Se você não paga subscription os auto-falantes que um dia chamamos de “inteligentes” estão ficando literalmente “burros” a cada dia que passa.

há uns 4 anos atrás eu ganhei uma Echo Show 5 de um amigo e desde então eu tenho usado ela diariamente para controlar algumas coisas aqui em casa, tocar músicas e também para tirar duvidas aleatórias do dia a dia.

Decidi que ja está na hora de aposentar a Echo e dar espaço para uma próxima assistente virtual.

Uma foto da minha alexa ainda no ritmo natalino:

Alexa noel

Ao invés de simplesmente vender a echo show eu decidi fazer algo útil com ela.

se a Echo não é tão boa sendo uma uma assistente virtual, então o que ela é boa fazendo?

Eu sei que ela é boa tocando música, os speakers dela são muito bons (talvez até melhor do que o meu PC).

Fora que um speaker para computador que tenha tela touch embutida e que sejam relativamente baratas é raro de se ver.

E dai que veio a ideia de transformar ela em um speaker inteligente pro meu computador. Um projeto que me diverti fazendo, portanto vou compartilhar com vocês os desafios que tive e como resolvi.




Procurando formas de usar a Echo como speaker

Por onde começar nesse projeto de conectar a alexa no meu PC para reproduzir o som?

A resposta mais obvia é simplesmente usar o Spotify que ja vem nativamente. Mas tem um problema nisso: um speaker de verdade não é seletivo, o som vem do PC como um todo e não apenas de um aplicativo.

Será que existe alguma skill no app da alexa que dê para adicionar para isso? bom, eu não encontrei. Depois de muitas pesquisas não achei nenhuma forma oficial de usar como um speaker como eu gostaria.

O único caminho que me restou foi procurar alternativas na conunidade que desse para customizar a alexa a ponto de transformar ela em um speaker…

Tive sorte em encontrar que 1 mês antes no Forum XDA postaram uma forma de “desbloquear” alguns modelos da Echo Show dando possibilidade de instalar seu próprio sistema operacional.

Alexa noel

Esse post abriu meus olhos no que a Echo é capaz, ela conseguir rodar Android é incrivel e significa que esse plano (de usar ela como um speaker) tecnicamente é possível.

Apesar de possível, desbloquear o bootloader da Echo Show não é uma tarefa fácil e sem riscos. Se algo der errado você pode simplemente quebrar sua alexa num ponto irrecuperavel.




Instalando Android na Echo Show

Como minha Echo Show 5 é da segunda geração eu precisei seguir esse passo a passo aqui, la tem todos os links necessários. Caso tenha curiosidade o repositório com o exploit chain é R0rt1z2/amonet, juntaram eles em 1 pacote só para que o processo seja facilitado.

Antes de começar a seguir o passo a passo eu li todas as mensagens do post original, vi muitos tutoriais diferentes para entender a fundo o que acontece e o que deve ser feito, se for fazer isso sugiro fazer o mesmo.

Amonet rodando na echo show 5

Tinha uma pequena chance desse script quebrar minha alexa, então os 5 minutos dele rodando pareceram 30 minutos. Fiquei literalmente assim:

rodando script tenso meme

Deu bom!

Confesso que ver a tela de configuração do TWRP deu um alívio.

Com a Echo desbloqueada eu poderia substituir o SO pelo o que eu quisesse, mas seria loucura fazer isso sem backup dos arquivos importantes da sua alexa, melhor prevenir fazendo backup do SO antigo:

TWRP fazendo backup

Agora que eu tenho backup posso ficar tranquilo caso faça alguma coisa de errado.

Conforme o post eu instalei o LineageOS 18 diretamente pelo TWRP mesmo.

lineage OS instalado

Sucesso !




Em busca de uma solução p/ redirecionar áudio

Apesar da Echo estar rodando um SO Android eu ainda tinha um caminho pela frente até ela virar um Speaker de verdade.

project almost ready meme

Eu ainda precisava alguma forma de “espelhar” ou redirecionar o audio do meu PC, e para isso tive que pesquisar bastante sobre o assunto e nesse processo descobri várias formas diferentes de fazer isso:

Nome É software pago? Artefatos p/ baixar Método
VLC é gratis! Linux/Mac/Windows -> iOS/Android WIFI
AndroidUsbAudioDevice é Grátis! Windows -> Android USB
audiorelay Grátis com anúncios! Linux/Mac/Windows -> Android WIFI/USB
SoundWire Server Grátis com anúncios! Linux/Windows -> Android WIFI
Airfoil Pago! Mac -> iOS/Android WIFI


No meu caso eu precisava de um programa que suportase Mac -> Android e de preferência algo 100% via USB, grátis e opensource.

A solução do audiorelay é o que chega mais próximos ao que eu preciso out-of-the-box, o VLC me atenderia caso eu rodasse comandos no terminal para redirecionar o uso do WIFI para o USB.

Eu percebi que todas as opções exigiam instalar algo no computador (obviamente) mas também algo no meu Android.

Eu até tolero baixar um app no android da echo, mas eu quero evitar com todas as forças a parte burocrática:

  • configurar, abrir o app no celular e no computador, rodar comandos toda vez, tudo isso só pra fazer o áudio ir pro celular.

descartei audiorelay por ter anúncios e VLC eu descartei por ter vários passos a serem feitos pra usar, não é plug-and-play como eu gostaria.

E todas as outras que encontrei não atendiam algum dos requisitos, sempre tinham pelo menos um ponto negativo.

bad choices meme

Veja, as soluções em si não chegam a ser ruins, eu estava buscando algo bem específico e nesse contexto não eram opções boas.




Desistindo e criando minha própria solução

A experiência ideal é rodar apenas 1 comando no computador e tudo se resolver sozinho.

Esse é um dos principais motivos de eu ser fã do scrcpy e gnirehtet, ambos tem uma experiência de uso muito simples mesmo fazendo várias coisas complexas por baixo dos panos.

O scrcpy redireciona áudio do celular para o PC, portanto de curioso resolvi olhar se nele tinha como fazer o caminho oposto. Recebi um banho de agua fria: o principal mantenedor disse que isso é fora do escopo do scrcpy e faz sentido.

Pelo comentário dele, deu a entender que não é viável tecnicamente redirecionar áudio do PC para celular: "no technical solution anyway"

meme are you challenging me

E foi nesse exato momento que decidi criar eu mesmo alguma solução.




Plano de implementação

Objetivo: ter uma CLI que rodo 1 vez e já faz tudo acontecer, sem config e tudo automágico.

Pré-requisitos:

  • simples de usar
  • ser grátis e opensource
  • USB como meio de transporte de dados

Desisti da ideia de WIFI pois tive a impressão que poderia aumentar muito a latência do áudio.

Então uma visão geral do projeto e caminho dos dados seria tipo assim:

                   (1)   Interface de audio do PC       
                                    │               
                                    ▼               
                (2)   programa rodando no computador
                                    │               
                                    ▼               
                                   usb              
                                    │               
                                    ▼               
                        (3)   meu aplicativo        
                                    │               
                                    ▼               
                        (4)  audio do celular       

Quebrei o problema maior em 4 problemas menores para facilitar.

Esses mesmos “4 problemas menores” iriam tomar entre 1 a 2 semanas (nos horarios livres) de desenvolvimento para finalizar, tudo pra não gastar 1 minuto configurando algo toda vez que for usar kkkkk Mas quem nunca fez isso atire a primeira pedra.

automation meme

Passo 1: coleta do áudio do PC

essa foi relativamente simples… Como meu objetivo é suportar mac e linux eu foquei em usar alguma ferramenta que funcionasse bem para os 2. Testei algumas mas a que mais saiu melhor foi um binário chamado SoX (Sound eXchange), eu pude escolher uma interface e trazer o dado bruto via STDOUT.

Fazendo vários testes eu consegui chegar a esse comando aqui, ele me traz exatamente o que eu preciso num sample rate e buffer definido.


sox --buffer 3840 \                 # valor calculado
    -t coreaudio "BlackHole 2ch" \  # interface virtual
    -r 48000 \                      # 48 kHz
    -c 2 \                          # 2 canais de áudio
    -b 16 \                         # 16-bit PCM
    -e signed-integer -L \          # little-endian Signed Int
    -t raw - 2>/dev/null            # output sem inferir formato de áudio

  • coleta do audio de interface de áudio do pc ✅


Passo 2: escrita de dados em um device USB

Com poucas pesquisas já percebi que a melhor biblioteca para isso é a libusb, ela funciona com qualquer versão do USB e funciona cross-platform.

O único problema dessa biblioteca é a documentação, particularmente não achei muito fácil de entender, falta exemplos para as funcionalidades.

Mas de qualquer forma achei vários exemplos online usando essa lib portanto acabou que foi uma boa escolha. No final para escrever dados na interface USB eu precisei seguir um fluxo semelhante a esse:

  1. Abro uma conexão com um device (libusb_open_device_with_vid_pid)
  2. Reivindico a interface USB escolhendo o device (libusb_claim_interface)
  3. Inicio uma transferência passando um callback (libusb_fill_bulk_transfer)
    • 3.1. Callback vai inflando o buffer com os bytes conforme vão chegando
  4. Envio os dados para aquele device (libusb_submit_transfer)
  • envio de dados para o dispositivo via USB


Passo 3: Um app conseguir ler os dados que vem pelo USB

Usar o libusb pra escrever no USB funcionou mas foi sofrido até conseguir ficar certinho como eu queria, por isso para o passo 3 eu fui atrás de soluções fáceis de usar.

Durante minhas pesquisas eu esbarrei com o Android Open Accessory (AOA), um protocolo que permite um aplicativo interagir com o USB do celular de uma forma relativamente simples.

Achei bem promissor, mas fiquei na dúvida se ainda funciona visto que o protocolo é de 15 anos atrás (2011).

Aparentemente da pra saber se seu device suporta o “modo acessory” do AOA usando o comando:

adb shell ls /system/etc/permissions | grep usb.accessory

E pra minha surpresa, TODOS meus devices tinham o arquivo usb.accessory.xml ai na pasta (confirmando que suportam esse modo acessório), isso é muito bom!

Olhando mais a fundo nesse protocolo eu encontrei 2 versões:

  • v1.0: básica e tem só o essencial
  • v2.0: adiciona mais capacidades ao protocolo

Como eu precisava só do essencial segui com a versão v1.0.

Foi relativamente simples implementar, precisei declarar no AndroidManifest um Service que tivesse intent filter do USB_ACCESSORY_ATTACHED, passando metadados do acessório:

<usb-accessory
      manufacturer="Victor"
      model="DroidSink"
      version="1.0" />

Essa configuração é essencial para que o programa do pc consiga abrir uma conexão com meu app do celular.

No momento que o usuário aceita o popup de pareamento, o UsbManager nos permite conectar no device e ao fazer isso e recebemos um objeto do tipo ParcelFileDescriptor, combinando ele a um FileInputStream nos permite iniciar a leitura de forma muito simples.

  • leitura de dados no app via USB


Passo 4: Reproduzir o áudio no aparelho

Com uma stream de dados vindo, reproduzir em aúdio é a ultima etapa do processo. Para fazer isso com qualidade precisamos garantir que o áudio de saída tem as mesmas especificações que o de entrada la no computador.

Usei a API AudioTrack para fazer essa configuração.

Nela posso configurar uma série de coisas, por exemplo a prioridade desse som, o tipo dele, modo de transfêrencia que será usado e por ai vai. Para o tamanho de buffer eu fui com o mesmo valor que defini na coleta de audio do PC:

val minBufferSize = AudioTrack.getMinBufferSize(
      48000, // 48 kHz
      AudioFormat.CHANNEL_OUT_STEREO,
      AudioFormat.ENCODING_PCM_16BIT
)

val chunkSize = 3840 // valor calculado, mesmo do comando do sox

val bufferSizeInBytes = maxOf(
      minBufferSize, 
      chunkSize * 3 // buffer de 3 vezes o tamanho do chunk de leitura
)

Enquanto eu estava implementando esse passo eu tive muitos problemas de áudio picado, depois de bastante troubleshooting eu entendi que caso eu tivesse alguma configuração diferente entre a coleta e a reprodução, eu teria algum tipo de problema. A causa do problema é bem obvia olhando agora, mas na hora eu jurava que buffers maiores resolveriam, mas eu estava errado.

O tempo de latência é calculado se baseando nessas especificações do projeto. Por esse e outros motivos a configuração inicial (sox) e final (no android) são essenciais para uma qualidade decente de áudio.

  • reprodução de áudio no aparelho ✅


Calculando a latência geral do projeto

⚠️ Não manjo tanto assim de cálculo de latência de áudio, posso ter deixado alguma coisa pra trás, se ver algo errado me avise

Como eu estabeleci que 1 chunk equivale a 3840 bytes a escrita iria demorar cerca de 20ms.

Por garantia eu coloquei o buffer sendo 3 vezes o tamanho de escrita, sacrifico 60ms de latência pela promeça de qualidade de áudio. Estou assumindo que a leitura da stream pelo app iria demorar uns 10ms.

calculating meme

Somando tudo até agora a latência total é de aproximadamente 90ms, mas eu preferi arredondar para cima para caso eu tenha esquecido algo.

Portanto do momento de coleta do som até o momento de reproduzir, a latência final é no minimo ~100ms.

Pelos meus testes esse me pareceu ser um tempo aceitável na maioria dos casos, não é excelente mas também não é incomodo, com excessão de jogos que esse delay faz diferença.



Conclusão

Esse projeto eu chamei de DroidSink, ele está disponivel no meu github caso queira ver como está o código.

Desde a publicação da v1.0.0 (dia Feb 7) eu já usei MUITO, conforme o tempo foi passando fui fazendo algumas melhorias e correções.

Pra mim ele tem funcionado de forma incrível, eu rodo 1 comando e ele ja faz TUDO pra mim:

  • instala o que precisa instar no celular
  • configura o que precisa no device
  • captura audio do pc e reproduz no celular
droidsink run

Veja esse exemplo tocando We didn't start the fire no meu computador e o som saindo na minha Echo rodando android.


Usando todo esse tempo identifiquei uma série de melhorias que poderiam ser feitas, como por exemplo:

  • Diminuir latência para no minimo 50ms.
  • Disponibilizar p/ download em mais lugares.
  • Garantir compatibilidade “out-of-the-box” com linux-arm64 e linux-x86_64.
  • Ter alguma UI para ser apresentada no device.
  • Melhorar o que é apresentado no terminal enquanto roda.
  • … e várias outras ideias …

Sigo trabalhando nas melhorias conforme aparece um tempinho.