Preparando para o campo | O Gravador sem Cabeça #5

Vamos continuar o nosso gravador sem cabeça da postagem anterior. Esse post não requer a leitura dos anteriores, mas vou dar um pouco de contexto.
Contexto
Eu quero criar um gravador em que você conecte interfaces de áudio USB e ele as grave, controlando por um tablet ou telefone.
Pra isso eu preciso saber se com um Raspberry Pi 3B sem tela, eu:
Consigo gravar de uma interface de áudio usando arecord?✅Consigo gravar de duas interfaces de áudio simultaneamente usando arecord?✅Consigo usar um serviço web pra disparar a gravação?✅Consigo conectar e enviar os comandos acima diretamente no pi como um hotspot?✅
Conseguimos validar todas as necessidades básicas para o protótipo. Já está na hora desse projeto ter mais cara de produto e encarar o campo.
Voltando aos trabalhos
Dois anos se passaram desde o último post. A vida mudou bastante e o projeto acabou perdendo prioridade. Muito porque eu troquei de equipamento. Vendi minha boa e velha GT-1 (saudades) e troquei por uma mais moderna, a HX Stomp e esta, infelizmente, não é compatível com Linux. Um tempo depois, acabei adquirindo um MVave IR BOX, um pequeno notável, que serve como uma interface USB e funciona no Linux :D.
Um pequeno notável
Foram dois anos cheios de música, com shows, composições e gravações. Na sanha de registrar os eventos da banda com mais fidelidade pra poder gerar material, estava na hora de voltar à esse projeto.
Onde paramos?
Desde o último post, o projeto era apenas uma prova de conceito. A interface era apenas um link que gravava 10 segundos de áudio de interfaces usb pré configuradas (minha antiga pedaleira GT-1 e uma interface Behringer emprestada).
Só aí já temos alguns problemas:
- As gravações vão precisar de mais de 10 segundos de duração 😅.
- As interfaces de áudio USB não serão as mesmas. Eu mesmo não tenho mais a pedaleira que usava e o pessoal da banda têm outras (que espero que sejam compatíveis).
Então surgem alguns requisitos para que eu possa levar meu Raspberry Pi pra passear:
- Precisamos detectar e escolher uma interface de áudio para uma gravação
- Precisamos iniciar uma gravação sem limite de tempo
- Precisamos parar uma gravação
- Precisamos exportar uma gravação
Essencialmente, vamos transformar essa prova de conceito em um produto, ou na primeira versão de um.
Planejamento
Pra sair do zero, estabeleci um objetivo simples: criar uma API enxuta para controlar as gravações. Vamos manter os 5 segundos por enquanto, mas vamos permitir escolher a interface de áudio.
Vamos criar um endpoint /devices para listar os dispositivos de áudio conectados e modificar o /record para que use o dispositivo escolhido.
Listando dispositivos de áudio de entrada
No primeiro post exploramos como listar dispositivos de áudio usando o Alsa, sistema de som padrão do sistema operacional do Raspberry Pi. Vamos usar a mesma abordagem aqui, mas agora precisamos interpretar a saída do comando arecord -L para gerar uma lista de dispositivos.
A saída do comando arecord -L é algo assim:
null
Discard all samples (playback) or generate zero samples (capture)
hw:CARD=CODEC,DEV=0
USB Audio CODEC, USB Audio
Direct hardware device without any conversions
plughw:CARD=CODEC,DEV=0
USB Audio CODEC, USB Audio
Hardware device with all software conversions
default:CARD=CODEC
USB Audio CODEC, USB Audio
Default Audio Device
sysdefault:CARD=CODEC
USB Audio CODEC, USB Audio
Default Audio Device
front:CARD=CODEC,DEV=0
USB Audio CODEC, USB Audio
Front output / input
dsnoop:CARD=CODEC,DEV=0
USB Audio CODEC, USB Audio
Direct sample snooping device
Vamos criar uma função para interpretar essa saída e gerar uma lista de dispositivos de áudio.
A ideia aqui é simples: cada dispositivo começa com uma linha que não tem indentação, seguida por uma ou mais linhas indentadas que descrevem o dispositivo. Vamos capturar o nome do dispositivo e sua descrição.
Não precisa entender o código linha a linha. Não é a forma mais elegante de implementar e isso já mudou, mas foi a primeira versão que escrevi.
Basta saber que criamos uma função chamada parse_arecord_L que recebe a saída do comando arecord -L como string e retorna uma lista de objetos SoundDevice, cada um representando um dispositivo de áudio.
Dá um confere aqui, se quiser.
E para criar o endpoint que lista os dispositivos, chamamos o comando arecord -L e passamos a saída para a função que acabamos de criar.
from subprocess import run
@app.get("/devices")
async def devices():
devices: List[SoundDevice] = []
output_str = run(['arecord', '-L'], capture_output=True).stdout.decode('utf-8')
devices = parse_arecord_L(output_str)
return {"devices": devices}
O endpoint /record precisa ser modificado pra receber o dispositivo que o usuário escolheu. Vamos fazer isso esperando um payload JSON com o nome do dispositivo no campo device.
@app.post("/record")
async def record(payload: dict): # Recebe o nome do dispositivo no payload
device = payload['device'] # Exemplo: 'hw:CARD=CODEC,DEV=0'
cmd = ['ffmpeg', '-y', '-f', 'alsa', '-i', device, '-ac', '2', 'bg.wav'] # Monta o comando para gravação
process = Popen(cmd, stdin=PIPE, stdout=PIPE)
sleep(10)
try:
process.communicate(b'q', timeout=10)
except TimeoutExpired:
process.kill()
return {
"pid": process.pid,
"rc": process.returncode
}
Este código essencialmente recebe o nome do dispositivo e o usa para iniciar uma gravação com ffmpeg. Espera 10 segundos e então para a gravação. O nome do arquivo será sempre bg.wav por enquanto.
Está praticamente igual ao protótipo, com duas modificações importantes:
Recebemos como parâmetro o dispositivo de áudio a ser usado, dentro de payload['device'].
Estamos usando ffmpeg ao invés de arecord. O ffmpeg é mais flexível e nos permitirá fazer coisas mais legais no futuro. Com ele, por exemplo, não precisamos nos preocupar em passar a taxa de amostragem e o formato da amostragem. Ele se vira.
Depois de disparar o comando na linha process = Popen(cmd, stdin=PIPE, stdout=PIPE), esperamos 10 segundos e então paramos a gravação com process.communicate(b'q', timeout=10). O ffmpeg espera que a gente aperte q para parar a gravação quando não especificamos uma duração. Cheira a gambiarra e é, mas funciona por enquanto.
Interface Web
Eu não sou designer. Eu não sou bom criando interfaces e não gosto muito de trabalhar com isso. Isso não quer dizer que eu seja ruim implementando, o problema mesmo é ter uma ideia de uma interface legal.
Mas estamos em 2026 e temos aí o Gemini pra nos ajudar. Expliquei os endpoints e pedi a página mais simples possível, com html e javascript puros e ele me deu isso aqui:

Voce pode conferir esse maravilhoso html completo aqui no github.