<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://raphaelpaiva.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://raphaelpaiva.github.io/" rel="alternate" type="text/html" /><updated>2026-01-06T19:52:21+00:00</updated><id>https://raphaelpaiva.github.io/feed.xml</id><title type="html">Raphael Paiva</title><subtitle>Blog</subtitle><entry><title type="html">Preparando para o campo | O Gravador sem Cabeça #5</title><link href="https://raphaelpaiva.github.io/2026/01/05/hr5.html" rel="alternate" type="text/html" title="Preparando para o campo | O Gravador sem Cabeça #5" /><published>2026-01-05T00:00:00+00:00</published><updated>2026-01-05T00:00:00+00:00</updated><id>https://raphaelpaiva.github.io/2026/01/05/hr5</id><content type="html" xml:base="https://raphaelpaiva.github.io/2026/01/05/hr5.html"><![CDATA[<p><img src="/files/hr5/header.jpg" alt="header" /></p>

<p>Vamos continuar o nosso gravador sem cabeça da <a href="/2024/01/05/hr4.html">postagem anterior</a>. Esse post não requer a leitura dos anteriores, mas vou dar um pouco de contexto.</p>

<h2 id="contexto">Contexto</h2>

<p>Eu quero criar um gravador em que você conecte interfaces de áudio USB e ele as grave, controlando por um tablet ou telefone.</p>

<p>Pra isso eu preciso saber se com um Raspberry Pi 3B sem tela, eu:</p>

<ol>
  <li><del>Consigo gravar de uma interface de áudio usando arecord?</del> ✅</li>
  <li><del>Consigo gravar de duas interfaces de áudio simultaneamente usando arecord?</del> ✅</li>
  <li><del>Consigo usar um serviço web pra disparar a gravação?</del> ✅</li>
  <li><del>Consigo conectar e enviar os comandos acima diretamente no pi como um hotspot?</del> ✅</li>
</ol>

<p>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.</p>

<h2 id="voltando-aos-trabalhos">Voltando aos trabalhos</h2>

<p>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.</p>

<p><img src="/files/hr3/irbox.png" alt="MVave IRBox" />
<em>Um pequeno notável</em></p>

<p>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.</p>

<h2 id="onde-paramos">Onde paramos?</h2>

<p>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).</p>

<p>Só aí já temos alguns problemas:</p>

<ol>
  <li>As gravações vão precisar de mais de 10 segundos de duração 😅.</li>
  <li>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).</li>
</ol>

<p>Então surgem alguns requisitos para que eu possa levar meu Raspberry Pi pra passear:</p>

<ol>
  <li>Precisamos detectar e escolher uma interface de áudio para uma gravação</li>
  <li>Precisamos iniciar uma gravação sem limite de tempo</li>
  <li>Precisamos parar uma gravação</li>
  <li>Precisamos exportar uma gravação</li>
</ol>

<p>Essencialmente, vamos transformar essa prova de conceito em um produto, ou na primeira versão de um.</p>

<h2 id="planejamento">Planejamento</h2>

<p>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.</p>

<p>Vamos criar um endpoint <code class="language-plaintext highlighter-rouge">/devices</code> para listar os dispositivos de áudio conectados e modificar o <code class="language-plaintext highlighter-rouge">/record</code> para que use o dispositivo escolhido.</p>

<h2 id="listando-dispositivos-de-áudio-de-entrada">Listando dispositivos de áudio de entrada</h2>

<p>No <a href="/2024/01/04/hr1.html">primeiro post</a> 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 <code class="language-plaintext highlighter-rouge">arecord -L</code> para gerar uma lista de dispositivos.</p>

<p>A saída do comando <code class="language-plaintext highlighter-rouge">arecord -L</code> é algo assim:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>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
</code></pre></div></div>

<p>Vamos criar uma função para interpretar essa saída e gerar uma lista de dispositivos de áudio.</p>

<p>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.</p>

<p>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.</p>

<p>Basta saber que criamos uma função chamada <code class="language-plaintext highlighter-rouge">parse_arecord_L</code> que recebe a saída do comando <code class="language-plaintext highlighter-rouge">arecord -L</code> como string e retorna uma lista de objetos <code class="language-plaintext highlighter-rouge">SoundDevice</code>, cada um representando um dispositivo de áudio.</p>

<p><a href="https://github.com/raphaelpaiva/hr/blob/d822948bf379ea19ce6a40c0eb23b9cb68b4698f/sound_device.py">Dá um confere aqui</a>, se quiser.</p>

<p>E para criar o endpoint que lista os dispositivos, chamamos o comando <code class="language-plaintext highlighter-rouge">arecord -L</code> e passamos a saída para a função que acabamos de criar.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">subprocess</span> <span class="kn">import</span> <span class="n">run</span>

<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"/devices"</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">devices</span><span class="p">():</span>
  <span class="n">devices</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">SoundDevice</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
  
  <span class="n">output_str</span> <span class="o">=</span> <span class="n">run</span><span class="p">([</span><span class="s">'arecord'</span><span class="p">,</span> <span class="s">'-L'</span><span class="p">],</span> <span class="n">capture_output</span><span class="o">=</span><span class="bp">True</span><span class="p">).</span><span class="n">stdout</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="s">'utf-8'</span><span class="p">)</span>
  <span class="n">devices</span> <span class="o">=</span> <span class="n">parse_arecord_L</span><span class="p">(</span><span class="n">output_str</span><span class="p">)</span>

  <span class="k">return</span> <span class="p">{</span><span class="s">"devices"</span><span class="p">:</span> <span class="n">devices</span><span class="p">}</span>
</code></pre></div></div>

<p>O endpoint <code class="language-plaintext highlighter-rouge">/record</code> 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 <code class="language-plaintext highlighter-rouge">device</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="s">"/record"</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">record</span><span class="p">(</span><span class="n">payload</span><span class="p">:</span> <span class="nb">dict</span><span class="p">):</span> <span class="c1"># Recebe o nome do dispositivo no payload
</span>  <span class="n">device</span> <span class="o">=</span> <span class="n">payload</span><span class="p">[</span><span class="s">'device'</span><span class="p">]</span> <span class="c1"># Exemplo: 'hw:CARD=CODEC,DEV=0'
</span>
  <span class="n">cmd</span> <span class="o">=</span> <span class="p">[</span><span class="s">'ffmpeg'</span><span class="p">,</span> <span class="s">'-y'</span><span class="p">,</span> <span class="s">'-f'</span><span class="p">,</span> <span class="s">'alsa'</span><span class="p">,</span> <span class="s">'-i'</span><span class="p">,</span> <span class="n">device</span><span class="p">,</span> <span class="s">'-ac'</span><span class="p">,</span> <span class="s">'2'</span><span class="p">,</span> <span class="s">'bg.wav'</span><span class="p">]</span> <span class="c1"># Monta o comando para gravação
</span>  
  <span class="n">process</span> <span class="o">=</span> <span class="n">Popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">stdin</span><span class="o">=</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">PIPE</span><span class="p">)</span>

  <span class="n">sleep</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>

  <span class="k">try</span><span class="p">:</span>
    <span class="n">process</span><span class="p">.</span><span class="n">communicate</span><span class="p">(</span><span class="sa">b</span><span class="s">'q'</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
  <span class="k">except</span> <span class="n">TimeoutExpired</span><span class="p">:</span>
    <span class="n">process</span><span class="p">.</span><span class="n">kill</span><span class="p">()</span>

  <span class="k">return</span> <span class="p">{</span>  
    <span class="s">"pid"</span><span class="p">:</span> <span class="n">process</span><span class="p">.</span><span class="n">pid</span><span class="p">,</span>
    <span class="s">"rc"</span><span class="p">:</span> <span class="n">process</span><span class="p">.</span><span class="n">returncode</span>
  <span class="p">}</span>
</code></pre></div></div>

<p>Este código essencialmente recebe o nome do dispositivo e o usa para iniciar uma gravação com <code class="language-plaintext highlighter-rouge">ffmpeg</code>. Espera 10 segundos e então para a gravação. O nome do arquivo será sempre <code class="language-plaintext highlighter-rouge">bg.wav</code> por enquanto.</p>

<p>Está praticamente igual ao protótipo, com duas modificações importantes:
Recebemos como parâmetro o dispositivo de áudio a ser usado, dentro de <code class="language-plaintext highlighter-rouge">payload['device']</code>.</p>

<p>Estamos usando <code class="language-plaintext highlighter-rouge">ffmpeg</code> ao invés de <code class="language-plaintext highlighter-rouge">arecord</code>. O <code class="language-plaintext highlighter-rouge">ffmpeg</code> é 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.</p>

<p>Depois de disparar o comando na linha <code class="language-plaintext highlighter-rouge">process = Popen(cmd, stdin=PIPE, stdout=PIPE)</code>, esperamos 10 segundos e então paramos a gravação com <code class="language-plaintext highlighter-rouge">process.communicate(b'q', timeout=10)</code>. O <code class="language-plaintext highlighter-rouge">ffmpeg</code> espera que a gente aperte <code class="language-plaintext highlighter-rouge">q</code> para parar a gravação quando não especificamos uma duração. Cheira a gambiarra e é, mas funciona por enquanto.</p>

<h2 id="interface-web">Interface Web</h2>

<p>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.</p>

<p>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:</p>

<p><img src="/files/hr5/interface.jpeg" alt="" /></p>

<p>Voce pode conferir esse maravilhoso html completo <a href="https://github.com/raphaelpaiva/hr/blob/d822948bf379ea19ce6a40c0eb23b9cb68b4698f/static/index.html">aqui no github</a>.</p>

<h2 id="resultado">Resultado</h2>]]></content><author><name></name></author><category term="linux" /><category term="hotspot" /><category term="raspberrypi" /><category term="pt_BR" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Pcm</title><link href="https://raphaelpaiva.github.io/2024/01/30/pcm.html" rel="alternate" type="text/html" title="Pcm" /><published>2024-01-30T00:00:00+00:00</published><updated>2024-01-30T00:00:00+00:00</updated><id>https://raphaelpaiva.github.io/2024/01/30/pcm</id><content type="html" xml:base="https://raphaelpaiva.github.io/2024/01/30/pcm.html"><![CDATA[<h1 id="pcm">PCM</h1>

<p>Já parou pra pensar como um computador “ouve”? Como ele é capaz de capturar, armazenar e tranmitir áudio? É parecido com o jeito que ouvimos?</p>

<p>Esse tópico é muito interessante e abrange muitos conhecimentos sobre computação, física e matemática.</p>

<h2 id="som-é-onda">Som é onda?</h2>

<p>Não sou ninguém pra afirmar isso, mas pessoalmente não gosto muito de dizer que som é uma onda. Ele pode ser modelado precisamente como uma onda, sem dúvidas, e acaba que dizemos que um é o outro, mas uma onda é algo abstrato e bem mais abrangente</p>

<p>Qualquer variação repetitiva de uma grandeza pode ser modelada como uma onda. Seja o movimento de um pêndulo, a variação da altura da água quando você joga uma pedra, ou movimento das cordas de um instrumento (incluindo a sua voz), todos podem ser modelados como ondas.</p>

<p>O som não é muito diferente. Ele também é uma variação e, embora possamos ser tentados a dizer que é uma variação de pressão de ar, na verdade é uma variação de energia. Claro, o meio mais comum de transferência de energia que nós, humanos, interpretamos como som é o ar, mas tecnicamente qualquer coisa que excite o nosso tímpano (ou outras partes do sistema auditivo) vai causar a mesma experiência sensorial.</p>

<p>E aí chegamos num termo que eu gosto mais: <em>experiência sensorial</em>. O som, no fim das contas, é uma das formas pelas quais interpretamos variações de energia mecânica ao nosso redor. Ele existe em nossas mentes. À nossa volta existe ar com pressão variando, alguma coisa vibrando, ou até mesmo emabixo da água: não é o ar que está chegando nos nossos ouvidos, é água.</p>

<p>Mas e aí, o som é onda? Não. Som pode ser modelado como uma onda, mas para fins práticos e analíticos, usamos ambos os termos como sinônimos.</p>

<h2 id="e-como-é-que-o-ser-humano-ouve">E como é que o ser humano ouve?</h2>

<p>Sem entrar em muitos detalhes sobre a fisiologia, o sistema auditivo é basicamente uma máquina que transforma energia mecânica em energia elétrica.</p>

<p>A energia mecânica faz uma pequena membrana na orelha média, o tímpano, se mover. Esse movimento é transmitido através de um sistema de três ossos (martelo, bigorna e estribo) até a cóclea. Aqui as coisas começam a ficar bem complexas, essa energia mecânica passa por um fluido e através de um complexo de células, a diferença de pressão hidrostática desse fluido é transmitida para o nervo auditivo, na forma de pulsos elétricos. Depois disso, o cérebro faz sua mágica e nós ouvimos uma bela melodia, ou o motoca cortando o giro da moto. <em>Maravilhoso!</em></p>

<p>Leia <a href="https://www.infoescola.com/anatomia-humana/audicao/">este artigo</a> para mais detalhes.</p>

<p>Duas coisas chamam minha atenção nesse sistema todo: o tímpano e o fluido na cóclea. O tímpano por ser o receptor da energia mecânica e o fluido por ser o transmissor final para o nervo auditivo. Uma característica fundamental do fluido é ele facilitar o entendimento, pelo menos pra mim, que o que é passado para o nervo auditivo é uma grandeza <em>contínua</em>. Quando perturbamos um fluido, seu movimento é suave, sem interrupções até voltar ao repouso.</p>

<p>Com isso, chegamos à uma diferença fundamental entre grandezas físicas e um computador: essa calculadora metida a besta que usamos todos os dias pra ver fotos de gatinhos é uma máquina <em>discreta</em>. Isso dificulta bastante a tarefa do computador perceber o ambiente ai seu redor.</p>

<h2 id="grandezas-contínuas-vs-discretas">Grandezas contínuas vs discretas</h2>]]></content><author><name></name></author><summary type="html"><![CDATA[PCM]]></summary></entry><entry><title type="html">Gravando com duas interfaces ao mesmo tempo | O Gravador sem Cabeça #2</title><link href="https://raphaelpaiva.github.io/2024/01/05/hr2.html" rel="alternate" type="text/html" title="Gravando com duas interfaces ao mesmo tempo | O Gravador sem Cabeça #2" /><published>2024-01-05T00:00:00+00:00</published><updated>2024-01-05T00:00:00+00:00</updated><id>https://raphaelpaiva.github.io/2024/01/05/hr2</id><content type="html" xml:base="https://raphaelpaiva.github.io/2024/01/05/hr2.html"><![CDATA[<p><img src="/files/hr2/header.jpg" alt="header" /></p>

<p>Bora continuar o que começamos na <a href="/2024/01/04/hr1.html">postagem anterior</a>. Eu recomendo <strong>muito</strong> você ler antes a parte #1, mas vou te dar uma colher de chá.</p>

<h2 id="contexto">Contexto</h2>

<p>Eu quero saber se com um Raspberry Pi 3B sem tela, eu:</p>
<ol>
  <li><del>Consigo gravar de uma interface de áudio usando arecord?</del></li>
  <li>Consigo gravar de duas interfaces de áudio simultaneamente usando arecord?</li>
  <li>Consigo usar um serviço web pra disparar a gravação?</li>
  <li>Consigo conectar e enviar os comandos acima diretamente no pi como um hot spot?</li>
</ol>

<p>Hoje vamos atacar o item #2</p>

<h2 id="mão-na-massa">Mão na massa</h2>

<p>Eu tenho uma <a href="https://www.boss.info/br/products/gt-1/">Boss GT1</a>. Uma delícia. Pequena, leve e <em>boa o suficiente®</em>. O mais importante é: ela é uma interface de áudio USB.</p>

<p><img src="/files/hr2/gt1.jpg" alt="A GT1 de guerra" />
<em>Tu é braba!</em></p>

<p>Bora plugar no pi e ver o que acontece.</p>

<p>No <a href="/2024/01/04/hr1.html">primeiro artigo dessa série</a> vimos como listar as interfaces de captura com o <code class="language-plaintext highlighter-rouge">arecord</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>raphaelpaiva@hr:~ $ arecord -l
**** List of CAPTURE Hardware Devices ****
card 2: CODEC [USB Audio CODEC], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 3: GT1 [GT-1], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
</code></pre></div></div>

<p>Que coisa linda! Surgiu a placa 3 e agora com um nome que faz sentido. Me faz pensar por que diabos a Behringer tem o nome CODEC 🤔.</p>

<p>Eu sou preguiçoso. Vamos tentar o mesmo comando que funcionou antes, mas trocando só a interface:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>raphaelpaiva@hr:~ $ arecord -Dhw:CARD=GT1,DEV=0 -d 5 -f S16_LE -r 44100 -c 2 opa.wav
Recording WAVE 'opa.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
arecord: set_params:1343: Sample format non available
Available formats:
- S32_LE
</code></pre></div></div>

<p>Rapaz. Seria S32 32bit? No manual diz 24, mas quem sou eu pra reclamar 😅?</p>

<p>Beleza, então. S32_LE será!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>raphaelpaiva@hr:~ $ arecord -Dhw:CARD=GT1,DEV=0 -d 5 -f S32_LE -r 44100 -c 2 opa.wav
Recording WAVE 'opa.wav' : Signed 32 bit Little Endian, Rate 44100 Hz, Stereo
</code></pre></div></div>

<p>Uhuuu 🎉!</p>

<p>De tão empolgado, esqueci até de plugar a guitarra, mas tava num efeito barulhento.</p>

<audio controls="controls">
  <source type="audio/wav" src="/files/hr2/gt1.wav" />&lt;/source&gt;
  <p>Your browser does not support the audio element.</p>
</audio>
<p><a href="/files/hr2/gt1.wav" target="_blank">Que bonitinho esse ruído psicodélico!</a></p>

<p>Show! Agora preciso ver se eu consigo gravar com as duas interfaces ao mesmo tempo.</p>

<p>Pra isso, eu vou precisar executar os dois comandos ao mesmo tempo na linha de comando.</p>

<p>No Linux, se você colocar um <code class="language-plaintext highlighter-rouge">&amp;</code> logo após um comando, ele vai ser invocado e jogado pro fundo. Em outras palavras, o programa vai estar executando, mas o terminal estará liberado pra rodar outro comando.</p>

<p>Vamos tomar vantagem disso fazendo o seguinte: <code class="language-plaintext highlighter-rouge">COMANDO_PRA_GRAVAR_DA_GT1 &amp; COMANDO_PRA_GRAVAR_DA_BEHRINGER</code>.</p>

<p>Repare o <code class="language-plaintext highlighter-rouge">&amp;</code> no meio de um comando e outro. Isto vai fazer com que o primeiro comando seja invocado, jogado pro fundo e logo em seguida o segundo comando seja invocado.</p>

<p>Isso é rodar ao mesmo tempo? Não, mas pros nossos propósitos aqui, é <em>bom o suficiente</em>.</p>

<p>Lembrando que isso é um protótipo. Eu só quero ver se funciona. Eu não faria assim numa situação de desenvolvimento de produto. Vamos ver isso no futuro.</p>

<p>Vamos modificar um pouco os comandos também pra cada um ir para o seu arquivo separado. A gravação da GT1 vai para o arquivo <code class="language-plaintext highlighter-rouge">gt1.wav</code> e a da Behringer pro <code class="language-plaintext highlighter-rouge">beh.wav</code>.</p>

<p>Vai ficar assim:</p>

<p><code class="language-plaintext highlighter-rouge">COMANDO_PRA_GRAVAR_DA_GT1: arecord -Dhw:CARD=GT1,DEV=0 -d 5 -f S32_LE -r 44100 -c 2 gt1.wav</code></p>

<p><code class="language-plaintext highlighter-rouge">COMANDO_PRA_GRAVAR_DA_BEHRINGER: arecord -Dhw:CARD=CODEC,DEV=0 -d 5 -f S16_LE -r 44100 -c 2 beh.wav</code></p>

<p>Como eu não tenho 3 instrumentos disponíveis, eu vou fazer o seguinte:</p>

<p>Na Behringer:</p>
<ol>
  <li>Vou falar no microfone.</li>
  <li>Vou tocar na ponta do cabo com o pé. Vai fazer um ruído de “mau contato”.</li>
</ol>

<p>Na GT1:</p>
<ol>
  <li>Vou tocar um acorde na guitarra.</li>
</ol>

<p>Pois bem:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>raphaelpaiva@hr:~ $ arecord -Dhw:CARD=GT1,DEV=0 -d 5 -f S32_LE -r 44100 -c 2 gt1.wav &amp; arecord -Dhw:CARD=CODEC,DEV=0 -d 5 -f S16_LE -r 44100 -c 2 beh.wav
[2] 19853
Recording WAVE 'gt1.wav' : Signed 32 bit Little Endian, Rate 44100 Hz, Recording WAVE 'beh.wav' : Signed 16 bit Little Endian, StereoRate 44100 Hz, Stereo

[1]   Done                    arecord -Dhw:CARD=GT1,DEV=0 -d 5 -f S32_LE -r 44100 -c 2 gt1.wav
[2]+  Done                    arecord -Dhw:CARD=GT1,DEV=0 -d 5 -f S32_LE -r 44100 -c 2 gt1.wav
</code></pre></div></div>

<p>A saída ficou um pouco confusa dessa vez, mas o importante é vermos que na terceira linha temos duas informações:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">Recording WAVE 'gt1.wav' : Signed 32 bit Little Endian, Rate 44100 Hz</code></li>
  <li><code class="language-plaintext highlighter-rouge">Recording WAVE 'beh.wav' : Signed 16 bit Little Endian, StereoRate 44100 Hz, Stereo</code></li>
</ol>

<p>Ou seja: estamos gravando o arquivo gt1.wav com 32bit e o beh.wav com 16bit, que são indicativos de que cada arquivo está com os parâmetros certos.</p>

<p>Vamos ver como ficou cada arquivo:</p>

<h3 id="behwav">beh.wav</h3>
<audio controls="controls">
  <source type="audio/wav" src="/files/hr2/beh_conjunto.wav" />&lt;/source&gt;
  <p>Your browser does not support the audio element.</p>
</audio>
<p><a href="/files/hr2/beh_conjunto.wav" target="_blank">Caso seu browser não carregue, clique aqui</a>.</p>

<h3 id="gt1wav">gt1.wav</h3>
<audio controls="controls">
  <source type="audio/wav" src="/files/hr2/gt1_conjunto.wav" />&lt;/source&gt;
  <p>Your browser does not support the audio element.</p>
</audio>
<p><a href="/files/hr2/gt1_conjunto.wav" target="_blank">Caso seu browser não carregue, clique aqui</a>.</p>

<h2 id="conclusão">Conclusão</h2>

<p>Parece que funcionou!</p>

<p>Então o <code class="language-plaintext highlighter-rouge">arecord</code> me permite gravar, ao mesmo tempo, de várias interfaces ao mesmo tempo, é só chamar ele várias vezes.</p>

<p><em>Pode ser</em>, com ênfase no pode, que tenhamos problemas de sincronização com vários áudios, mas cruzaremos essa ponte quando chegarmos nela.</p>

<p>Mas na hora de gravar com a banda eu não vou ter uma linha de comando disponível e nem seria prático.</p>

<p>Na próxima publicação, vamos montar uma aplicação web bem simples pra fazer exatamente o que fizemos hoje, sem precisar de uma linha de comando.</p>]]></content><author><name></name></author><category term="linux" /><category term="audio" /><category term="raspberrypi" /><category term="pt_BR" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Controle Remoto via Web com Raspberry Pi, Python e FastAPI | O Gravador sem Cabeça #3</title><link href="https://raphaelpaiva.github.io/2024/01/05/hr3.html" rel="alternate" type="text/html" title="Controle Remoto via Web com Raspberry Pi, Python e FastAPI | O Gravador sem Cabeça #3" /><published>2024-01-05T00:00:00+00:00</published><updated>2024-01-05T00:00:00+00:00</updated><id>https://raphaelpaiva.github.io/2024/01/05/hr3</id><content type="html" xml:base="https://raphaelpaiva.github.io/2024/01/05/hr3.html"><![CDATA[<p><img src="/files/hr3/header.jpg" alt="header" /></p>

<p>Vamos continuar o nosso gravador sem cabeça <a href="/2024/01/05/hr2.html">postagem anterior</a>. Vimos que conseguimos gravar com mais de uma interface de áudio ao mesmo tempo. Eu recomendo <strong>muito</strong> você ler antes <a href="/2024/01/04/hr1.html">a parte #1</a> e <a href="/2024/01/05/hr2.html">a parte #2</a>, mas vou te dar uma colher de chá.</p>

<h2 id="contexto">Contexto</h2>

<p>Eu quero criar um gravador em que você conecte interfaces de áudio USB e ele as grave, controlando por um tablet.</p>

<p>Pra isso eu preciso saber se com um Raspberry Pi 3B sem tela, eu:</p>
<ol>
  <li><del>Consigo gravar de uma interface de áudio usando arecord?</del> ✅</li>
  <li><del>Consigo gravar de duas interfaces de áudio simultaneamente usando arecord?</del> ✅</li>
  <li>Consigo usar um serviço web pra disparar a gravação?</li>
  <li>Consigo conectar e enviar os comandos acima diretamente no pi como um hot spot?</li>
</ol>

<p>Hoje vamos atacar o item #3: <strong>Consigo usar um serviço web pra disparar a gravação?</strong></p>

<p>Como não vamos ter tela ou botões no nosso gravador, vamos precisar controlá-lo remotamente. O jeito mais fácil que eu consigo pensar de fazer isso, da perspectiva de usuário, é por WiFi, através de um serviço web. Ou seja: <em>num computador, celular ou tablet, vamos abrir uma página no navegador capaz de controlar o gravador</em>.</p>

<p>Pra isso vamos precisar programar um pouco. Finalmente! O foco dessa publicação será mais em software web do que Linux ou áudio, como os anteriores.</p>

<p>Bora lá!</p>

<h2 id="mão-na-massa">Mão na massa</h2>

<p>Primeiro eu preciso pensar em que comandos eu vou enviar. Pra manter as coisas simples, vou continuar o que estamos fazendo: gravar um áudio de 5 segundos, pra cada interface que temos. Lembre-se: esse é apenas um protótipo.</p>

<p>Então o mínimo que eu posso fazer é isso: uma página html com um link: Gravar.</p>

<p>Se bem me lembro de html, vamos testar uma coisinha na minha máquina mesmo:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- hr.html --&gt;</span>

<span class="nt">&lt;html&gt;</span>
  <span class="nt">&lt;head&gt;</span>
    <span class="nt">&lt;title&gt;</span>O Gravador sem Cabeça<span class="nt">&lt;/title&gt;</span>
  <span class="nt">&lt;/head&gt;</span>

  <span class="nt">&lt;body&gt;</span>
    <span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"/gravar"</span><span class="nt">&gt;</span>Gravar<span class="nt">&lt;/a&gt;</span>
  <span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>

<p><img src="/files/hr3/mestredohtml.png" alt="Mestre do html" /></p>

<p><em>Dá pra ver que eu levo jeito pra designer, diz aí!</em></p>

<p>Mas beleza, se clicar nesse link vai pra uma página de erro. Claro, não implementamos endereço do link <code class="language-plaintext highlighter-rouge">/gravar</code>.</p>

<p>O que eu quero que aconteça quando clicar no link? Que grave, oras. Mas como é que eu vou fazer uma página mandar um comando pro Raspberry Pi?</p>

<p>Aí vai entrar uma palavra da moda: vamos criar uma <a href="https://aws.amazon.com/pt/what-is/api/">API</a>, uma forma de um programa (a página que criamos, ou o navegador) se comunicar com outro programa, o arecord.</p>

<h3 id="o-que-diabos-é-uma-api">O que diabos é uma API?</h3>

<p>Assim como sua TV tem <em>entradas</em>, como HDMI, USB e o controle remoto; e <em>saídas</em> como a óptica, fone de ouvido / áudio e a própria tela, programas podem definir suas entradas e saídas para se comunicarem com outros programas.</p>

<p>Sua TV tem um conjunto <em>finito</em> de entradas e saídas, vamos chamar isso de <em>interface</em>. Quando uma interface serve para a comunicação entre dois programas, nós chamamos de <em>Application Programming Interface</em>, ou simplesmente de API.</p>

<h2 id="protótipo-com-python-e-fastapi">Protótipo com Python e FastAPI</h2>

<p>Tá, e como a gente cria uma API? Já existe pronta?</p>

<p>Vamos ter que programar um pouquinho. Pra não me alongar, eu preciso que você segure minha mão e tome algumas coisas como garantidas. Você pode chamar de mágica, mas em breve entraremos em detalhes. Lembre-se: estamos fazendo apenas um protótipo.</p>

<p>Pra ser bem rápido, eu vou usar <a href="https://www.python.org/">Python</a> e uma biblioteca chamada <a href="https://fastapi.tiangolo.com/">FastAPI</a>. Ela vai nos permitir, de uma maneira muito simples, subir um servidor com uma API que vamos definir.</p>

<p>O primeiro passo é ver se o Raspberry Pi já vem com alguma instalação do Python.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>raphaelpaiva@hr:~/hr $ python --version
Python 3.9.2
</code></pre></div></div>

<p>Show, temos um Python 3.9 já instalado. Dá pro gasto, mas pra usar o FastApi vamos precisar de um outro programa que não veio instalado, o pip, um gerenciador de pacotes e bibliotecas pro python.</p>

<p>Pra instalar, é moleza <code class="language-plaintext highlighter-rouge">sudo apt install -y python3-pip</code>. Agora sim podemos usar o <code class="language-plaintext highlighter-rouge">pip</code> pra instalar o FastApi: <code class="language-plaintext highlighter-rouge">pip install fastapi[all]</code></p>

<p>Agora vamos pra mão na massa de fato.</p>

<h2 id="implementando-o-ponto-de-entrada">Implementando o ponto de entrada</h2>

<p>Vamos implementar o <code class="language-plaintext highlighter-rouge">/gravar</code> que chamamos na nossa página de uma forma bem simples, só pra ver se a chamada funciona.</p>

<p>No pi, eu criei uma pasta chamada <code class="language-plaintext highlighter-rouge">hr</code> pra colocar os nossos arquivos python. Nele, criei um arquivo <code class="language-plaintext highlighter-rouge">main.py</code> com o seguinte código:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># main.py
</span><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span> <span class="c1"># importando a biblioteca FastAPI
</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">()</span> <span class="c1"># Inicializando o nosso aplicativo
</span>
<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'/gravar'</span><span class="p">)</span> <span class="c1"># definindo a operação que vai ser executada quando chamarmos '/gravar'
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">gravar</span><span class="p">():</span>
  <span class="k">return</span> <span class="s">"opa!"</span>    <span class="c1"># Nesse caso, espero que retorne apenas um texto escrito "opa!"
</span></code></pre></div></div>

<p>Segundo as instruções na <a href="https://fastapi.tiangolo.com/#run-it">documentação do FastAPI</a> pra rodar o programa, precisamos do <code class="language-plaintext highlighter-rouge">uvicorn</code>. O FastApi já o instala pra nós, então basta vamos rodar:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>raphaelpaiva@hr:~/hr $ python -m uvicorn main:app --reload --host 0.0.0.0
INFO:     Will watch for changes in these directories: ['/home/raphaelpaiva/hr']
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [54511] using WatchFiles
INFO:     Started server process [54513]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
</code></pre></div></div>

<p>Traduzindo:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">python -m uvicorn</code>: Uvicorn é um módulo python que vai carregar a nossa API. Para <em>rodar</em> um módulo python, utilizamos o comando <code class="language-plaintext highlighter-rouge">python -m &lt;NOME DO MODULO&gt;</code>. Daí pra frente, passamos parâmetros pro módulo.</li>
  <li><code class="language-plaintext highlighter-rouge">main:app</code>: especifica qual aplicação o uvicorn vai carregar. No nosso caso <code class="language-plaintext highlighter-rouge">main:app</code> significa a aplicação definida na linha 4 do arquivo <code class="language-plaintext highlighter-rouge">main.py</code>. O padrão é <code class="language-plaintext highlighter-rouge">&lt;NOME DO ARQUIVO&gt;:&lt;NOME DA VARIAVEL QUE RECEBEU O FastAPI()&gt;</code></li>
  <li><code class="language-plaintext highlighter-rouge">--reload</code>: instrui o uvicorn pra recarregar a aplicação toda vez que algum arquivo for modificado.</li>
  <li><code class="language-plaintext highlighter-rouge">--host 0.0.0.0</code> faz com que o uvicorn escute requisições em todas as interfaces de rede. Isso vai garantir que vamos conseguir chamar a aplicação do meu computador e não apenas de dentro do Raspberry Pi.</li>
</ul>

<p>Um detalhe importante da saída do programa é</p>
<pre><code class="language-log">INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
</code></pre>

<p>Alí ele nos indica a porta em que o uvicorn está escutando: <code class="language-plaintext highlighter-rouge">8000</code>.</p>

<p>Vamos ver se o bichão funciona.</p>

<p>No navegador, vou tentar acessar o Raspberry Pi na porta 8000. Pode ser pelo endereço IP do Pi ou pelo nome que configurei nele. Vou pelo nome, é mais bonitinho: <code class="language-plaintext highlighter-rouge">http://hr.local:8000/gravar</code>.</p>

<p><img src="/files/hr3/opaws.png" alt="opa" />
<em>Opa!</em></p>

<p>Estamos chegando lá. Duas coisas que precisamos fazer:</p>
<ol>
  <li>Fazer aquela página html ser servida pelo uvicorn.</li>
  <li>fazer o link ‘Gravar’ realmente gravar alguma coisa.</li>
</ol>

<p>Vamos fazer a nossa página ser exibida quando entrarmos em <code class="language-plaintext highlighter-rouge">http://hr.local:8000/</code>. Para isso, vamos adicionar uma outra operação no nosso arquivo <code class="language-plaintext highlighter-rouge">main.py</code>. Ele vai ficar dessa forma:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># main.py
</span><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>
<span class="kn">from</span> <span class="nn">fastapi.responses</span> <span class="kn">import</span> <span class="n">HTMLResponse</span>

<span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">()</span>

<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'/'</span><span class="p">)</span> <span class="c1"># quando chamarmos a raiz (representado pelo '/') da nossa aplicação
</span><span class="k">async</span> <span class="k">def</span> <span class="nf">index</span><span class="p">():</span>
  <span class="n">index_html</span> <span class="o">=</span> <span class="s">"""
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;O Gravador sem Cabeça&lt;/title&gt;
  &lt;/head&gt;

  &lt;body&gt;
    &lt;a href="/gravar"&gt;Gravar&lt;/a&gt;
  &lt;/body&gt;
&lt;/html&gt;
"""</span>
  <span class="k">return</span> <span class="n">HTMLResponse</span><span class="p">(</span><span class="n">content</span><span class="o">=</span><span class="n">index_html</span><span class="p">,</span> <span class="n">status_code</span><span class="o">=</span><span class="mi">200</span><span class="p">)</span> <span class="c1"># Vamos devolver uma resposta html, com o código da página que criamos anteriormente.
</span>
<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'/gravar'</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">gravar</span><span class="p">():</span>
  <span class="k">return</span> <span class="s">"opa!"</span>

</code></pre></div></div>

<p>Se acessarmos a raiz da nossa aplicação…</p>

<p><img src="/files/hr3/index.png" alt="index" />
<em>Agora a página mais bela da terra está sendo servida do Raspberry Pi e não mais no meu computador.</em></p>

<p>E se clicar no gravar, vamos ver o nosso ‘Opa!’ que implementamos anteriormente!</p>

<h2 id="chamando-um-programa-através-do-python">Chamando um programa através do python</h2>

<p>O Python possui uma biblioteca já inclusa chamada <a href="https://docs.python.org/3.9/library/subprocess.html">subprocess</a>. Ela nos permite chamar processos (ou programas) através de código python.</p>

<p>Dentro dela, temos uma função chamada run. É o método mais simples de chamar um programa.</p>

<p>Vamos modificar a nossa função <code class="language-plaintext highlighter-rouge">gravar</code> para chamar o <code class="language-plaintext highlighter-rouge">arecord</code> com os comandos que vimos anteriormente.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'/gravar'</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">gravar</span><span class="p">():</span>
  <span class="c1"># O comando que usamos pra gravar da GT1 no post anterior
</span>  <span class="n">command</span> <span class="o">=</span> <span class="p">[</span><span class="s">"arecord"</span><span class="p">,</span> <span class="s">"-Dhw:CARD=GT1,DEV=0"</span><span class="p">,</span> <span class="s">"-d"</span><span class="p">,</span> <span class="s">"5"</span><span class="p">,</span> <span class="s">"-f"</span><span class="p">,</span> <span class="s">"S32_LE"</span><span class="p">,</span> <span class="s">"-r"</span><span class="p">,</span> <span class="s">"44100"</span><span class="p">,</span> <span class="s">"-c"</span><span class="p">,</span> <span class="s">"2"</span><span class="p">,</span> <span class="s">"opa.wav"</span><span class="p">]</span>
  <span class="n">response</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">command</span><span class="p">,</span> <span class="n">capture_output</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="c1"># Chamamos o processo
</span>  <span class="k">return</span> <span class="n">response</span><span class="p">.</span><span class="n">stdout</span> <span class="o">+</span> <span class="sa">b</span><span class="s">' / '</span> <span class="o">+</span> <span class="n">response</span><span class="p">.</span><span class="n">stderr</span> <span class="c1"># E devolvemos qualquer coisa que ele colocaria no terminal.
</span></code></pre></div></div>

<p>A <code class="language-plaintext highlighter-rouge">subprocess.run()</code> recebe o comando a rodar como uma lista. Em python, uma lista pode ser definida como acima: cada elemento é separado por uma virgula e todos ficam entre colchetes. Todos os elementos estão entre aspas pois todos são do tipo texto (ou <code class="language-plaintext highlighter-rouge">string</code>).</p>

<p>Passamos um outro parâmetro pra ela, chamado <code class="language-plaintext highlighter-rouge">capture_output</code>, quando ele é <code class="language-plaintext highlighter-rouge">True</code>, podemos inspecionar a saída do programa através das propriedades <code class="language-plaintext highlighter-rouge">stdout</code> e <code class="language-plaintext highlighter-rouge">stderr</code> da resposta da chamada da <code class="language-plaintext highlighter-rouge">subprocess.run()</code>, que é o que retornamos na última linha, separado por ` / `.</p>

<p>Então, esperamos que tenhamos uma resposta parecida com a de rodar o programa na linha de comando quando clicarmos em gravar.</p>

<p><img src="/files/hr3/gravar.png" alt="" />
<em>Ihá!</em></p>

<p>Claro, que eu estava com a guitarra desplugada, mas aprecie o ruído girando na sua orelha.</p>

<audio controls="controls">
  <source type="audio/wav" src="/files/hr3/opa.wav" />&lt;/source&gt;
  <p>Your browser does not support the audio element.</p>
</audio>
<p><a href="/files/hr3/opa.wav" target="_blank">Caso seu browser não carregue, clique aqui</a>.</p>

<p>Beleza, gravamos de uma. Mas queremos gravar de várias ao mesmo tempo.</p>

<h2 id="chamadas-bloqueantes-e-não-bloqueantes">Chamadas bloqueantes e não bloqueantes</h2>

<p><img src="/files/hr3/reqtime.png" alt="" /></p>

<p>Quando clicamos em gravar, demora um pouco pra recebermos a resposta. Mais precisamente, 5.08s. <em>Nós pedimos pra gravar 5 segundos</em> (com o parâmetro <code class="language-plaintext highlighter-rouge">-d 5</code> pro <code class="language-plaintext highlighter-rouge">arecord</code>), 0.08s pra requisição ir até o Pi, processar e voltar, parece justo.</p>

<p>E se quisermos gravar de duas interfaces em paralelo?</p>

<p>Vamos modificar a nossa função pra rodar os dois comandos.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'/gravar'</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">gravar</span><span class="p">():</span>
  <span class="n">gt1_command</span> <span class="o">=</span> <span class="p">[</span><span class="s">"arecord"</span><span class="p">,</span> <span class="s">"-Dhw:CARD=GT1,DEV=0"</span><span class="p">,</span> <span class="s">"-d"</span><span class="p">,</span> <span class="s">"5"</span><span class="p">,</span> <span class="s">"-f"</span><span class="p">,</span> <span class="s">"S32_LE"</span><span class="p">,</span> <span class="s">"-r"</span><span class="p">,</span> <span class="s">"44100"</span><span class="p">,</span> <span class="s">"-c"</span><span class="p">,</span> <span class="s">"2"</span><span class="p">,</span> <span class="s">"opa.wav"</span><span class="p">]</span>
  <span class="n">beh_command</span> <span class="o">=</span> <span class="p">[</span><span class="s">"arecord"</span><span class="p">,</span> <span class="s">"-Dhw:CARD=CODEC,DEV=0"</span><span class="p">,</span> <span class="s">"-d"</span><span class="p">,</span> <span class="s">"5"</span><span class="p">,</span> <span class="s">"-f"</span><span class="p">,</span> <span class="s">"S16_LE"</span><span class="p">,</span> <span class="s">"-r"</span><span class="p">,</span> <span class="s">"44100"</span><span class="p">,</span> <span class="s">"-c"</span><span class="p">,</span> <span class="s">"2"</span><span class="p">,</span> <span class="s">"beh.wav"</span><span class="p">]</span>
  <span class="n">gt1_response</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">gt1_command</span><span class="p">,</span> <span class="n">capture_output</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
  <span class="n">beh_response</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">beh_command</span><span class="p">,</span> <span class="n">capture_output</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
  
  <span class="k">return</span> <span class="p">{</span>
    <span class="s">'gt1'</span><span class="p">:</span> <span class="n">gt1_response</span><span class="p">.</span><span class="n">stdout</span> <span class="o">+</span> <span class="sa">b</span><span class="s">' / '</span> <span class="o">+</span> <span class="n">gt1_response</span><span class="p">.</span><span class="n">stderr</span><span class="p">,</span>
    <span class="s">'beh'</span><span class="p">:</span> <span class="n">beh_response</span><span class="p">.</span><span class="n">stdout</span> <span class="o">+</span> <span class="sa">b</span><span class="s">' / '</span> <span class="o">+</span> <span class="n">beh_response</span><span class="p">.</span><span class="n">stderr</span>
  <span class="p">}</span>
</code></pre></div></div>
<p><em>Especificamos os comandos para cada interface, rodamos ambos e vamos retornar agora um valor um pouco melhor estruturado, especificando a saída de cada uma.</em></p>

<p><img src="/files/hr3/respblock.png" alt="" /></p>

<p><em>A resposta ficou mais bonitinha desa vez.</em></p>

<p>Mas se formos olhar o tempo:</p>

<p><img src="/files/hr3/reqtimeblock.png" alt="" /></p>

<p><em>Demorou 10 segundos.</em></p>

<p>Isso foi porque um comando rodou e só depois que ele terminou, o outro começou. Lembra, na <a href="/2024/01/05/hr2.html">postagem anterior</a> que utilizamos aquele <code class="language-plaintext highlighter-rouge">&amp;</code> para que pudéssemos rodar ambos os comandos ao mesmo tempo? Precisamos de algo parecido aqui.</p>

<p>Por padrão, qualquer função que chamamos em um programa é <em>bloqueante</em>, ou seja, seu programa vai ficar parado até a função terminar o seu trabalho. É o caso da <code class="language-plaintext highlighter-rouge">subprocess.run()</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">gt1_response</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">gt1_command</span><span class="p">,</span> <span class="n">capture_output</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">beh_response</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">beh_command</span><span class="p">,</span> <span class="n">capture_output</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
</code></pre></div></div>
<p>Neste caso, a primeira chamada, para a gt1, vai durar até o comando de gravação da gt1 acabar. Como especificamos 5 segundos de gravação serão 5s nele e mais 5 segundos abaixo. Na mesma biblioteca, temos um modo de chamar um programa externo de forma <em>não bloqueante</em>, servindo o mesmo propósito do <code class="language-plaintext highlighter-rouge">&amp;</code> da linha de comando: o <code class="language-plaintext highlighter-rouge">subprocess.Popen()</code></p>

<p>Modificando um pouco mais o nosso método:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'/gravar'</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">gravar</span><span class="p">():</span>
  <span class="n">gt1_command</span> <span class="o">=</span> <span class="p">[</span><span class="s">"arecord"</span><span class="p">,</span> <span class="s">"-Dhw:CARD=GT1,DEV=0"</span><span class="p">,</span> <span class="s">"-d"</span><span class="p">,</span> <span class="s">"5"</span><span class="p">,</span> <span class="s">"-f"</span><span class="p">,</span> <span class="s">"S32_LE"</span><span class="p">,</span> <span class="s">"-r"</span><span class="p">,</span> <span class="s">"44100"</span><span class="p">,</span> <span class="s">"-c"</span><span class="p">,</span> <span class="s">"2"</span><span class="p">,</span> <span class="s">"opa.wav"</span><span class="p">]</span>
  <span class="n">beh_command</span> <span class="o">=</span> <span class="p">[</span><span class="s">"arecord"</span><span class="p">,</span> <span class="s">"-Dhw:CARD=CODEC,DEV=0"</span><span class="p">,</span> <span class="s">"-d"</span><span class="p">,</span> <span class="s">"5"</span><span class="p">,</span> <span class="s">"-f"</span><span class="p">,</span> <span class="s">"S16_LE"</span><span class="p">,</span> <span class="s">"-r"</span><span class="p">,</span> <span class="s">"44100"</span><span class="p">,</span> <span class="s">"-c"</span><span class="p">,</span> <span class="s">"2"</span><span class="p">,</span> <span class="s">"beh.wav"</span><span class="p">]</span>

  <span class="n">gt1_process</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">Popen</span><span class="p">(</span><span class="n">gt1_command</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="p">.</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">subprocess</span><span class="p">.</span><span class="n">STDOUT</span><span class="p">)</span>
  <span class="n">beh_process</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">Popen</span><span class="p">(</span><span class="n">beh_command</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="p">.</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">subprocess</span><span class="p">.</span><span class="n">STDOUT</span><span class="p">)</span>

  <span class="n">gt1_stdout</span><span class="p">,</span> <span class="n">gt1_stderr</span> <span class="o">=</span> <span class="n">gt1_process</span><span class="p">.</span><span class="n">communicate</span><span class="p">()</span>
  <span class="n">beh_stdout</span><span class="p">,</span> <span class="n">beh_stderr</span> <span class="o">=</span> <span class="n">beh_process</span><span class="p">.</span><span class="n">communicate</span><span class="p">()</span>
  
  <span class="k">return</span> <span class="p">{</span>
    <span class="s">'gt1'</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">gt1_stdout</span><span class="p">)</span> <span class="o">+</span> <span class="s">' / '</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">gt1_stderr</span><span class="p">),</span>
    <span class="s">'beh'</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">beh_stdout</span><span class="p">)</span> <span class="o">+</span> <span class="s">' / '</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">beh_stderr</span><span class="p">)</span>
  <span class="p">}</span>
</code></pre></div></div>

<p>Tivemos algumas novidades:</p>

<ul>
  <li>Especificamos os parâmetros <code class="language-plaintext highlighter-rouge">stdout</code> e <code class="language-plaintext highlighter-rouge">stderr</code> na chamada do <code class="language-plaintext highlighter-rouge">Popen()</code>,
    <ul>
      <li>Isso serve para que possamos utilizar a saída do programa.</li>
    </ul>
  </li>
  <li>Chamamos, para os dois processos o método <code class="language-plaintext highlighter-rouge">communicate()</code>.
    <ul>
      <li>Com ele, nós conseguimos capturar a saída e esperar que cada um deles termine sua execução antes de continuar nosso programa.</li>
    </ul>
  </li>
  <li>No retorno, transformamos a saída em <code class="language-plaintext highlighter-rouge">string</code> utilizando a função <code class="language-plaintext highlighter-rouge">str()</code>, só pra facilitar a geração da nossa resposta bonitinha.</li>
</ul>

<p>Vamos ver agora o tempo.</p>

<p><img src="/files/hr3/reqtimepara.png" alt="" /></p>

<p><em>Ah muleke!</em></p>

<p>Agora sim! Geramos nossos dois arquivos, paralelamente.</p>

<p>No fim das contas, nosso programa ficou assim:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># main.py
</span>
<span class="kn">import</span> <span class="nn">subprocess</span>
<span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>
<span class="kn">from</span> <span class="nn">fastapi.responses</span> <span class="kn">import</span> <span class="n">HTMLResponse</span>

<span class="n">app</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">()</span>

<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'/'</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">gravar</span><span class="p">():</span>
  <span class="n">index_html</span> <span class="o">=</span> <span class="s">"""
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;O Gravador sem Cabeça&lt;/title&gt;
  &lt;/head&gt;

  &lt;body&gt;
    &lt;a href="/gravar"&gt;Gravar&lt;/a&gt;
  &lt;/body&gt;
&lt;/html&gt;
"""</span>
  <span class="k">return</span> <span class="n">HTMLResponse</span><span class="p">(</span><span class="n">content</span><span class="o">=</span><span class="n">index_html</span><span class="p">,</span> <span class="n">status_code</span><span class="o">=</span><span class="mi">200</span><span class="p">)</span>

<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'/gravar'</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">gravar</span><span class="p">():</span>
  <span class="n">gt1_command</span> <span class="o">=</span> <span class="p">[</span><span class="s">"arecord"</span><span class="p">,</span> <span class="s">"-Dhw:CARD=GT1,DEV=0"</span><span class="p">,</span> <span class="s">"-d"</span><span class="p">,</span> <span class="s">"5"</span><span class="p">,</span> <span class="s">"-f"</span><span class="p">,</span> <span class="s">"S32_LE"</span><span class="p">,</span> <span class="s">"-r"</span><span class="p">,</span> <span class="s">"44100"</span><span class="p">,</span> <span class="s">"-c"</span><span class="p">,</span> <span class="s">"2"</span><span class="p">,</span> <span class="s">"opa.wav"</span><span class="p">]</span>
  <span class="n">beh_command</span> <span class="o">=</span> <span class="p">[</span><span class="s">"arecord"</span><span class="p">,</span> <span class="s">"-Dhw:CARD=CODEC,DEV=0"</span><span class="p">,</span> <span class="s">"-d"</span><span class="p">,</span> <span class="s">"5"</span><span class="p">,</span> <span class="s">"-f"</span><span class="p">,</span> <span class="s">"S16_LE"</span><span class="p">,</span> <span class="s">"-r"</span><span class="p">,</span> <span class="s">"44100"</span><span class="p">,</span> <span class="s">"-c"</span><span class="p">,</span> <span class="s">"2"</span><span class="p">,</span> <span class="s">"beh.wav"</span><span class="p">]</span>

  <span class="n">gt1_process</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">Popen</span><span class="p">(</span><span class="n">gt1_command</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="p">.</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">subprocess</span><span class="p">.</span><span class="n">STDOUT</span><span class="p">)</span> <span class="c1">#subprocess.run(gt1_command, capture_output=True)
</span>  <span class="n">beh_process</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">Popen</span><span class="p">(</span><span class="n">beh_command</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="p">.</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">subprocess</span><span class="p">.</span><span class="n">STDOUT</span><span class="p">)</span>

  <span class="n">gt1_stdout</span><span class="p">,</span> <span class="n">gt1_stderr</span> <span class="o">=</span> <span class="n">gt1_process</span><span class="p">.</span><span class="n">communicate</span><span class="p">()</span>
  <span class="n">beh_stdout</span><span class="p">,</span> <span class="n">beh_stderr</span> <span class="o">=</span> <span class="n">beh_process</span><span class="p">.</span><span class="n">communicate</span><span class="p">()</span>
  
  <span class="k">return</span> <span class="p">{</span>
    <span class="s">'gt1'</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">gt1_stdout</span><span class="p">)</span> <span class="o">+</span> <span class="s">' / '</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">gt1_stderr</span><span class="p">),</span>
    <span class="s">'beh'</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">beh_stdout</span><span class="p">)</span> <span class="o">+</span> <span class="s">' / '</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">beh_stderr</span><span class="p">)</span>
  <span class="p">}</span>

</code></pre></div></div>

<h2 id="conclusão">Conclusão</h2>

<p>Fizemos bastante coisa nesse aqui.</p>
<ol>
  <li>Criamos uma página com um link</li>
  <li>Implementamos um programa em <code class="language-plaintext highlighter-rouge">python</code> que roda lá no Raspberry Pi.</li>
  <li>Tornamos esse programa disponível pela rede com o <code class="language-plaintext highlighter-rouge">uvicorn</code>.</li>
  <li>O link da página que criamos chama o nosso programa e ele grava os nossos áudios, da mesma forma que nos post anteriores.</li>
</ol>

<p>Já temos muita coisa encaminhada! Falta apenas um item da nossa lista: conectar diretamente ao Raspberry Pi com wifi, sem depender de um roteador.</p>

<p>Nos vemos no próximo!</p>]]></content><author><name></name></author><category term="python" /><category term="programação" /><category term="web" /><category term="linux" /><category term="audio" /><category term="raspberrypi" /><category term="pt_BR" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Como fazer um hotspot no Raspberry Pi | O Gravador sem Cabeça #4</title><link href="https://raphaelpaiva.github.io/2024/01/05/hr4.html" rel="alternate" type="text/html" title="Como fazer um hotspot no Raspberry Pi | O Gravador sem Cabeça #4" /><published>2024-01-05T00:00:00+00:00</published><updated>2024-01-05T00:00:00+00:00</updated><id>https://raphaelpaiva.github.io/2024/01/05/hr4</id><content type="html" xml:base="https://raphaelpaiva.github.io/2024/01/05/hr4.html"><![CDATA[<p><img src="/files/hr4/header.jpg" alt="header" /></p>

<p>Vamos continuar o nosso gravador sem cabeça da <a href="/2024/01/05/hr3.html">postagem anterior</a>. Esse post não requer a leitura dos anteriores, mas vou dar um pouco de contexto.</p>

<h2 id="contexto">Contexto</h2>

<p>Eu quero criar um gravador em que você conecte interfaces de áudio USB e ele as grave, controlando por um tablet.</p>

<p>Pra isso eu preciso saber se com um Raspberry Pi 3B sem tela, eu:</p>
<ol>
  <li><del>Consigo gravar de uma interface de áudio usando arecord?</del> ✅</li>
  <li><del>Consigo gravar de duas interfaces de áudio simultaneamente usando arecord?</del> ✅</li>
  <li><del>Consigo usar um serviço web pra disparar a gravação?</del> ✅</li>
  <li>Consigo conectar e enviar os comandos acima diretamente no pi como um hotspot?</li>
</ol>

<p>Hoje vamos atacar o item #4: <strong>Consigo conectar e enviar os comandos acima diretamente no pi como um hotspot?</strong></p>

<p>Bora lá!</p>

<h2 id="mão-na-massa">Mão na massa</h2>

<p>Nós vamos mexer com a rede Wi-Fi do Raspberry Pi, então se você está conectado nele por Wi-Fi, use a rede cabeada, ou use-o diretamente com teclado e monitor para poder resolver qualquer problema que venha a ocorrer com o Wi-Fi.</p>

<p>Isso dito, vamos nos certificar que o Pi está usando o NetworkManager, que vai facilitar muito a nossa vida:</p>

<h2 id="ativando-o-networkmanager">Ativando o NetworkManager</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>raphaelpaiva@hr:~ <span class="nv">$ </span><span class="nb">sudo </span>raspi-config
</code></pre></div></div>

<p>Isso deve abrir uma tela com um menu, parecida com esta aqui:</p>

<p><img src="/files/hr4/menu.png" alt="menu" /></p>

<p>Selecione a opção <code class="language-plaintext highlighter-rouge">6 Advanced Options</code> e depois <code class="language-plaintext highlighter-rouge">AA Network Config</code></p>

<p><img src="/files/hr4/netconf.png" alt="networkconfig" /></p>

<p>Depois disso, certifique-se de que a opção <code class="language-plaintext highlighter-rouge">2 NetworkManager</code> está habilitada.</p>

<p><img src="/files/hr4/nm.png" alt="nm" /></p>

<p>Pronto! Se você perdeu a conexão, tente conectar novamente.</p>

<h2 id="configurando-o-hotspot">Configurando o HotSpot</h2>

<p>Pra configurar rede usando o NetworkManager, temos algumas opções muito boas como o <code class="language-plaintext highlighter-rouge">nmcli</code> (via linha de comando) e o <code class="language-plaintext highlighter-rouge">nmtui</code> (interface semelhante ao <code class="language-plaintext highlighter-rouge">raspi-config</code>). Vamos de <code class="language-plaintext highlighter-rouge">nmcli</code> por ser mais direto.</p>

<p>Pra configurar o hotspot, temos uns comandos pra rodar. Basicamente <code class="language-plaintext highlighter-rouge">nmcli con add</code> pra adicionar uma conexão e <code class="language-plaintext highlighter-rouge">sudo nmcli con modify</code> pra modificar configurações de uma conexão.</p>

<p>Vou explicar passo a passo.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Cria uma conexão Wi-Fi chamada hr-hotspot com o mesmo ssid</span>
raphaelpaiva@hr:~ <span class="nv">$ </span><span class="nb">sudo </span>nmcli con add <span class="nb">type </span>Wi-Fi ifname wlan0 con-name hr-hotspot autoconnect <span class="nb">yes </span>ssid hr-hotspot

<span class="c"># Essa conexão vai estar no modo ap (access point) usando o banda bg e vai compartilhar qualquer outra conexão existente</span>
<span class="c"># o compartilhamento não é necessário, mas é conveniente</span>
raphaelpaiva@hr:~ <span class="nv">$ </span><span class="nb">sudo </span>nmcli con modify hr-hotspot 802-11-wireless.mode ap 802-11-wireless.band <span class="nb">bg </span>ipv4.method shared

<span class="c"># Essa conexão vai usar segurança wpa-psk (passkey). Não é o mais seguro, mas esse projeto não requer muita segurança.</span>
raphaelpaiva@hr:~ <span class="nv">$ </span><span class="nb">sudo </span>nmcli con modify hr-hotspot Wi-Fi-sec.key-mgmt wpa-psk

<span class="c"># Vamos colocar uma senha bem segura...</span>
raphaelpaiva@hr:~ <span class="nv">$ </span><span class="nb">sudo </span>nmcli con modify hr-hotspot Wi-Fi-sec.psk <span class="s2">"12345678"</span>

<span class="c"># E vamos, finalmente, habilitar essa conexão.</span>
raphaelpaiva@hr:~ <span class="nv">$ </span><span class="nb">sudo </span>nmcli con up hr-hotspot
</code></pre></div></div>

<p>Vou rodar o servicinho que criamos na <a href="/2024/01/05/hr3.html">postagem anterior</a> e tentar acessá-lo.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>raphaelpaiva@hr:~ <span class="nv">$ </span><span class="nb">cd </span>hr
raphaelpaiva@hr:~/hr <span class="nv">$ </span>python <span class="nt">-m</span> uvicorn main:app <span class="nt">--reload</span> <span class="nt">--host</span> 0.0.0.0
INFO:     Will watch <span class="k">for </span>changes <span class="k">in </span>these directories: <span class="o">[</span><span class="s1">'/home/raphaelpaiva/hr'</span><span class="o">]</span>
INFO:     Uvicorn running on http://0.0.0.0:8000 <span class="o">(</span>Press CTRL+C to quit<span class="o">)</span>
INFO:     Started reloader process <span class="o">[</span>1589] using WatchFiles
INFO:     Started server process <span class="o">[</span>1592]
INFO:     Waiting <span class="k">for </span>application startup.
INFO:     Application startup complete.
</code></pre></div></div>

<p>Importante também dar uma olhada no ip do pi nessa rede:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>raphaelpaiva@hr:~ <span class="nv">$ </span>ip a
<span class="o">[</span>...]
3: wlan0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    <span class="nb">link</span>/ether b8:27:eb:67:e6:84 brd ff:ff:ff:ff:ff:ff
    inet 10.42.0.1/24 brd 10.42.0.255 scope global noprefixroute wlan0
       valid_lft forever preferred_lft forever
    inet6 fe80::26aa:e855:d327:9941/64 scope <span class="nb">link </span>noprefixroute 
       valid_lft forever preferred_lft forever
</code></pre></div></div>
<p>Beleza! <em>10.42.0.1</em>.</p>

<p>Nesse ponto eu desconectei o cabo de rede pra me certificar de que não havia nenhuma outra rede disponível.</p>

<p>Vamos lá pro meu tablet véio de guerra.
<img src="/files/hr4/conect.jpg" alt="conectando" /></p>

<p>Colocando a senha mega segura…</p>

<p><img src="/files/hr4/conectado.jpg" alt="conectado" /></p>

<p>Vamos tentar agora acessar o serviço. Lembrando que ele roda na porta <code class="language-plaintext highlighter-rouge">8000</code>, então vamos acessar <code class="language-plaintext highlighter-rouge">http://10.42.0.1:8000</code> e cruzar os dedos.</p>

<p><img src="/files/hr4/sucesso.jpg" alt="sucesso" /></p>

<p>Solta os fogos! 🎉</p>

<p>Dando uma olhada nos logs da aplicação, dá pra ver que o acesso veio da mesma rede do Pi:</p>

<pre><code class="language-log">INFO:     10.42.0.177:37478 - "GET / HTTP/1.1" 200 OK
INFO:     10.42.0.177:37478 - "GET /favicon.ico HTTP/1.1" 404 Not Found
INFO:     10.42.0.177:41574 - "GET / HTTP/1.1" 200 OK
</code></pre>

<h2 id="conclusão">Conclusão</h2>

<p>Conseguimos acessar o Raspberry Pi diretamente via Wi-Fi e entrar no nosso serviço, ou seja, todas as etapas pra validar a ideia do Gravador foram validadas!</p>

<p>Agora é hora de pensar nele como um produto e estruturar um pouco mais ele como um todo. Vamos dar prosseguimento no próximo post!</p>

<h2 id="referências">Referências</h2>

<p>Claro que eu colei a configuração de algum lugar, então segue o link da alma caridosa que compilou pra gente: <a href="https://gist.github.com/narate/d3f001c97e1c981a59f94cd76f041140">https://gist.github.com/narate/d3f001c97e1c981a59f94cd76f041140</a>. Obrigado, estranho!</p>]]></content><author><name></name></author><category term="linux" /><category term="hotspot" /><category term="raspberrypi" /><category term="pt_BR" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Dá pra gravar áudio da linha de comando? | O Gravador sem Cabeça #1</title><link href="https://raphaelpaiva.github.io/2024/01/04/hr1.html" rel="alternate" type="text/html" title="Dá pra gravar áudio da linha de comando? | O Gravador sem Cabeça #1" /><published>2024-01-04T00:00:00+00:00</published><updated>2024-01-04T00:00:00+00:00</updated><id>https://raphaelpaiva.github.io/2024/01/04/hr1</id><content type="html" xml:base="https://raphaelpaiva.github.io/2024/01/04/hr1.html"><![CDATA[<p><img src="/files/hr/header-1.jpeg" alt="" /></p>

<h2 id="o-problema">O Problema</h2>

<ul>
  <li>Eu tenho uma banda (🍪).</li>
  <li>Nós gostamos de nos gravar 🌚, áudio e vídeo.</li>
  <li>Nós temos uma mesa de som velha, com uma interface USB 🎚️.</li>
  <li>Nós temos um netbook velho de um núcleo, 32bits 💻.</li>
  <li>Nós queremos eventualmente gravar multipista com uma interface adicional, ou juntando várias pra fazer mixagens melhores 🎛️.</li>
  <li>Nós temos celulares pra gravar vídeos 📹.</li>
  <li>Eu tenho um <a href="https://github.com/raphaelpaiva/cameracontrol">aplicativo</a> que comanda câmeras de celulares (android, apenas) 📱.</li>
  <li>Não sabemos se vai funcionar pois o netbook é bem antigo e lento. Sim, já quero inventar antes de testar.</li>
</ul>

<p>Pois bem. Eu tenho um raspberry pi 3 sobrando 🍓.</p>

<p>Qual é o problema? Nenhum, eu quero inventar alguma coisa 😄.</p>

<h2 id="as-idéia">As idéia</h2>

<p>A ideia principal é: Um gravador portátil, <strong>simples de usar</strong> (pluga a(s) interface(s) e aperta gravar). E por gravar, eu entendo:</p>
<ol>
  <li>Gravar áudio das interfaces</li>
  <li>Enviar o comando de gravação para as câmeras</li>
</ol>

<h2 id="limitações">Limitações</h2>

<p>Se não me engano, temos <a href="https://www.alsa-project.org/">ALSA</a> (um sistema de áudio do Linux) por padrão no <a href="https://www.raspberrypi.com/software/">Raspberry Pi OS</a>. Dá até pra agregar interfaces (pegar várias e transformar em uma interface só), mas também dá pra disparar vários processos do <a href="https://linux.die.net/man/1/arecord">arecord</a> (o programa gravador padrão do ALSA) pra gravar áudio de interfaces diferentes ao mesmo tempo, que parece mais simples.</p>

<p>Bom, o raspberry pi não tem tela, nem pretendo colocar. Botões talvez sejam uma boa ideia, mas, pelo menos inicialmente, vou precisar de mais controle, até porque vou estar explorando, né?.</p>

<p>Se não tem tela, não tem interface gráfica. Daria até pra rodar um <a href="https://reaper.fm">Reaper</a> e tentar fazer alguma mágica, mas acho que temos soluções mais simples, pelo menos por enquanto. Provavelmente vou pelo caminho de interface web pra comandar por um celular ou tablet.</p>

<p>E daí vem o nome desse projeto. Normalmente chamamos algo que não tem uma interface direta (nesse caso, uma tela) de <em>headless</em>, ou <em>sem cabeça</em>.</p>

<p>Pra acessar a interface web, eu vou precisar conectar no pi num ambiente potencialmente sem rede, então vou ter que sacrificar o WiFi do Pi pra criar um <em>hot spot</em> onde o controlador vai conectar. Não é muito diferente de como as mesas de som digitais funcionam.</p>

<p>Para as câmeras tem o meu projeto citado acima, mas o mais importante é o áudio. Mais importante ainda é:</p>

<blockquote>
  <p>Dado um Raspberry Pi 3B com uma interface de áudio conectada, eu consigo conectar diretamente nele via WiFi com um tablet e enviar comandos pra gravar áudio?</p>
</blockquote>

<h2 id="protótipo">Protótipo</h2>

<p>Vamos lá.</p>

<p>Tenho algumas coisas pra testar.</p>

<ol>
  <li>Consigo gravar de uma interface de áudio usando arecord?</li>
  <li>Consigo gravar de duas interfaces de áudio simultaneamente usando arecord?</li>
  <li>Consigo usar um serviço web pra disparar a gravação?</li>
  <li>Consigo conectar e enviar os comandos acima diretamente no pi como um hot spot?</li>
</ol>

<h2 id="mão-na-massa">Mão na massa</h2>

<p>Vamos tentar riscar o primeiro item.</p>

<p>Reinstalei o Pi e pluguei minha interface numa das portas USB. Vamos ver se o ALSA detectou.</p>

<p>Eu sei que existe um comando pra gravar áudio direto da linha de comando, que faz parte da “caixa de ferramentas” do ALSA, chamado <code class="language-plaintext highlighter-rouge">arecord</code>.</p>

<p>Bora tentar:</p>

<pre><code class="language-shellscript">raphaelpaiva@hr:~ $ arecord
Usage: arecord [OPTION]... [FILE]...

-h, --help              help
    --version           print current version
-l, --list-devices      list all soundcards and digital audio devices
-L, --list-pcms         list device names
-D, --device=NAME       select PCM by name
-q, --quiet             quiet mode
-t, --file-type TYPE    file type (voc, wav, raw or au)
-c, --channels=#        channels
-f, --format=FORMAT     sample format (case insensitive)
-r, --rate=#            sample rate
-d, --duration=#        interrupt after # seconds
-s, --samples=#         interrupt after # samples per channel
-M, --mmap              mmap stream
-N, --nonblock          nonblocking mode
-F, --period-time=#     distance between interrupts is # microseconds
-B, --buffer-time=#     buffer duration is # microseconds
    --period-size=#     distance between interrupts is # frames
    --buffer-size=#     buffer duration is # frames
-A, --avail-min=#       min available space for wakeup is # microseconds
-R, --start-delay=#     delay for automatic PCM start is # microseconds
                        (relative to buffer size if &lt;= 0)
-T, --stop-delay=#      delay for automatic PCM stop is # microseconds from xrun
-v, --verbose           show PCM structure and setup (accumulative)
-V, --vumeter=TYPE      enable VU meter (TYPE: mono or stereo)
-I, --separate-channels one file for each channel
-i, --interactive       allow interactive operation from stdin
-m, --chmap=ch1,ch2,..  Give the channel map to override or follow
    --disable-resample  disable automatic rate resample
    --disable-channels  disable automatic channel conversions
    --disable-format    disable automatic format conversions
    --disable-softvol   disable software volume control (softvol)
    --test-position     test ring buffer position
    --test-coef=#       test coefficient for ring buffer position (default 8)
                        expression for validation is: coef * (buffer_size / 2)
    --test-nowait       do not wait for ring buffer - eats whole CPU
    --max-file-time=#   start another output file when the old file has recorded
                        for this many seconds
    --process-id-file   write the process ID here
    --use-strftime      apply the strftime facility to the output file name
    --dump-hw-params    dump hw_params of the device
    --fatal-errors      treat all errors as fatal
Recognized sample formats are: S8 U8 S16_LE S16_BE U16_LE U16_BE S24_LE S24_BE U24_LE U24_BE S32_LE S32_BE U32_LE U32_BE FLOAT_LE FLOAT_BE FLOAT64_LE FLOAT64_BE IEC958_SUBFRAME_LE IEC958_SUBFRAME_BE MU_LAW A_LAW IMA_ADPCM MPEG GSM S20_LE S20_BE U20_LE U20_BE SPECIAL S24_3LE S24_3BE U24_3LE U24_3BE S20_3LE S20_3BE U20_3LE U20_3BE S18_3LE S18_3BE U18_3LE U18_3BE G723_24 G723_24_1B G723_40 G723_40_1B DSD_U8 DSD_U16_LE DSD_U32_LE DSD_U16_BE DSD_U32_BE
Some of these may not be available on selected hardware
The available format shortcuts are:
-f cd (16 bit little endian, 44100, stereo)
-f cdr (16 bit big endian, 44100, stereo)
-f dat (16 bit little endian, 48000, stereo)
</code></pre>

<p>Ele me chamou de burro de forma educada.</p>

<p><code class="language-plaintext highlighter-rouge">Usage: arecord [OPTION]... [FILE]...</code> me diz como usar esse comando. Eu tenho que passar pra ele opções (pelo menos uma) ou arquivos (pelo menos um).</p>

<p>Vamos tentar passar um arquivo qualquer, deve ser o arquivo de destino da gravação.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>raphaelpaiva@hr:~ $ arecord opa.wav
arecord: main:830: audio open error: No such file or directory
</code></pre></div></div>

<p>Ok, erro ao abrir o áudio. Soa até meio estranho. Como no Linux tudo é representado por um arquivo, inclusive a minha interface de áudio, eu suspeito que ele não tenha conseguido abrir o dispositivo de áudio. Talvez tenhamos que informar o <code class="language-plaintext highlighter-rouge">arecord</code> de <em>qual</em> interface eu quero gravar. Vai que tem mais de uma.</p>

<p>Da saída ali em cima, onde as opções foram listadas, algumas chamaram minha atenção. A primeira delas foi</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-l, --list-devices      list all soundcards and digital audio devices
</code></pre></div></div>

<p>Bora lá tentar passar essa opção então!</p>

<pre><code class="language-shellscript">raphaelpaiva@hr:~ $ arecord -l
**** List of CAPTURE Hardware Devices ****
card 2: CODEC [USB Audio CODEC], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
</code></pre>

<p>Opa! Essa saída significa que ele detectou um dispositivo de saída.</p>

<p>Traduzindo:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">card 2: CODEC [USB Audio CODEC], device 0: USB Audio [USB Audio]</code>
    <ul>
      <li>Esse dispositivo está na placa número 2 (<code class="language-plaintext highlighter-rouge">card 2</code>).</li>
      <li>O nome dessa placa é <code class="language-plaintext highlighter-rouge">CODEC [USB Audio CODEC]</code>.</li>
      <li>O dispositivo de saída é o primeiro dispositivo desta placa (<code class="language-plaintext highlighter-rouge">device 0</code>).</li>
      <li>O nome dele é <code class="language-plaintext highlighter-rouge">USB Audio [USB Audio]</code>.</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">Subdevices: 1/1</code>
    <ul>
      <li>Ele tem um sub-dispositivo</li>
    </ul>
  </li>
  <li><code class="language-plaintext highlighter-rouge">Subdevice #0: subdevice #0</code>
    <ul>
      <li>Este sub-dispositivo é o número 0</li>
    </ul>
  </li>
</ul>

<p>Entendeu? Eu também não, mas já deu pra sacar que o ALSA faz referência dos dispositivos por números ou por nomes.</p>

<p>Do manual do ALSA, tem uma outra opção interessante:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-L, --list-pcms
        List all PCMs defined
</code></pre></div></div>

<p>Que diabos é um PCM? É uma sigla pra <a href="https://en.wikipedia.org/wiki/Pulse-code_modulation">Pulse-code modulation</a>, ou uma das formas que um computador pode “ouvir”. No contexto do ALSA, PCM é o que vem do seu dispositivo de captura, ou seja, essa opção lista as fontes de áudio disponíveis.</p>

<p>Vou escrever sobre PCM depois, é muito interessante, mas não vale alongar aqui.</p>

<pre><code class="language-shellscript">raphaelpaiva@hr:~ $ arecord -L
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
</code></pre>

<p>Temos um pouco mais de informação dessa opção do <code class="language-plaintext highlighter-rouge">arecord</code>. Duas seções saltam aos olhos:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>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
</code></pre></div></div>

<p>Parece que temos acesso ao dado direto do hardware com o <code class="language-plaintext highlighter-rouge">hw:</code> e acesso ao dado tratado com o <code class="language-plaintext highlighter-rouge">plughw:</code>. Que diferença isso faz? Sei lá. Vou chutar que podem rolar umas conversões de formato e taxa de amostragem, mas tem uma outra coisa mais importante:</p>

<p><code class="language-plaintext highlighter-rouge">hw:CARD=CODEC,DEV=0</code> tem <strong>muita</strong> cara de ser um identificador de dispositivo. <code class="language-plaintext highlighter-rouge">CODEC</code> é o nome da placa que vimos na saída do <code class="language-plaintext highlighter-rouge">arecord -l</code> e 0 é o número do dispositivo, então <code class="language-plaintext highlighter-rouge">hw:CARD=CODEC,DEV=0</code> deve significar a fonte de áudio direto do hardware que vem da placa CODEC, dispositivo 0. Da mesma forma, <code class="language-plaintext highlighter-rouge">plughw:CARD=CODEC,DEV=0</code> deve significar a mesma fonte de áudio, mas com conversões de software, seja lá o que isso signifique.</p>

<p>A tradução do resto da saída do <code class="language-plaintext highlighter-rouge">arecord -L</code> fica como exercício para o leitor 😄.</p>

<p>Uma terceira opção que chamou atenção foi a seguinte:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-D, --device=NAME       select PCM by name
</code></pre></div></div>

<p>Tá com toda cara que é com essa opção que eu digo qual é a interface que eu quero utilizar. Resta saber se vamos de <code class="language-plaintext highlighter-rouge">hw:</code> ou <code class="language-plaintext highlighter-rouge">plughw:</code>. Como o <code class="language-plaintext highlighter-rouge">plughw:</code> faz conversões, ele parece ser a aposta mais segura.</p>

<p>Vamo lá</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>raphaelpaiva@hr:~ $ arecord -Dplughw:CARD=CODEC,DEV=0 opa.wav
Recording WAVE 'opa.wav' : Unsigned 8 bit, Rate 8000 Hz, Mono
</code></pre></div></div>

<p>Parece que tá gravando!</p>

<p>Como eu não sabia parar, meti um ctrl+C mesmo (padrão para abortar programas na linha de comando) e vamos ver no que deu.</p>

<audio controls="controls">
  <source type="audio/wav" src="/files/hr/opa.wav" />&lt;/source&gt;
  <p>Your browser does not support the audio element.</p>
</audio>

<p><a href="/files/hr/opa.wav" target="_blank">Se não apareceu o áudio, ouve aí a alegra do nerdola</a></p>

<p>Parece até o áudio do Yuri Gagarin com essa qualidade tosca. Tudo bem, com 8 bits de resolução e uma taxa de amostragem de 8kHz não dá pra esperar muita coisa.</p>

<p>Vamos ver se dá pra melhorar isso, mas eu estou curioso pra saber o que vem se eu usar o <code class="language-plaintext highlighter-rouge">hw:</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>raphaelpaiva@hr:~ $ arecord -Dhw:CARD=CODEC,DEV=0 opa.wav
Recording WAVE 'opa.wav' : Unsigned 8 bit, Rate 8000 Hz, Mono
arecord: set_params:1343: Sample format non available
Available formats:
- S8
- S16_LE
</code></pre></div></div>

<p>Opa! Eu gosto de erro assim. Que diz o que eu fiz de errado e ainda diz como consertar:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>arecord: set_params:1343: Sample format non available
Available formats:
- S8
- S16_LE
</code></pre></div></div>

<p>Então eu tenho que especificar o formato da amostra. Lá da primeira saída do programa, dá pra ver como e ainda tem algumas opções interessantes pra controlar a qualidade e duração.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-c, --channels=#        channels
-f, --format=FORMAT     sample format (case insensitive)
-r, --rate=#            sample rate
-d, --duration=#        interrupt after # seconds
</code></pre></div></div>

<p>Vamos mexer só no formato por enquanto, que foi o que ele reclamou.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>raphaelpaiva@hr:~ $ arecord -Dhw:CARD=CODEC,DEV=0 -f S16_LE opa.wav
Recording WAVE 'opa.wav' : Signed 16 bit Little Endian, Rate 8000 Hz, Mono
Warning: rate is not accurate (requested = 8000Hz, got = 11025Hz)
         please, try the plug plugin
</code></pre></div></div>

<p>Deu um warning, mas gravou. Vou chorar 😭.</p>
<audio controls="controls">
  <source type="audio/wav" src="/files/hr/opahw.wav" />&lt;/source&gt;
  <p>Your browser does not support the audio element.</p>
</audio>
<p><a href="/files/hr/opahw.wav" target="_blank">Ouve aí o choro do nerdola</a>.</p>

<p>Reparou que a qualidade parece bem melhor? Esse foi o salto de 8bits/8000Hz pra 16bits/11025Hz. Brabo.</p>

<p>Vou escrever sobre o que esses número significam, mas calma.</p>

<p>Vamos brincar um pouco com os outros parâmetros.</p>

<p>Eu quero gravar um áudio de 5 segundos, 16bits, 44100Hz e usando todos os 2 canais da interface. Vou plugar minha guitarra no segundo canal e bora ver se vai sair fumaça.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>arecord -Dhw:CARD=CODEC,DEV=0 -d 5 -f S16_LE -r 44100 -c 2 opa.wav
Recording WAVE 'opa.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
</code></pre></div></div>

<audio controls="controls">
  <source type="audio/wav" src="/files/hr/opa2canais.wav" />&lt;/source&gt;
  <p>Your browser does not support the audio element.</p>
</audio>
<p><a href="/files/hr/opa2canais.wav" target="_blank">Ai papai! Emocionado. Ouve aí!</a>.</p>

<h2 id="conclusão">Conclusão</h2>

<p>Dá, hein!</p>

<p>Vimos como gravar áudio de uma interface de áudio via linha de comando usando o ALSA, sistema de som padrão no Raspberry Pi OS.</p>

<p><a href="/2024/01/05/hr2.html">Na próxima etapa</a> vamos ver se conseguimos gravar de duas interfaces simultaneamente.</p>

<p>Segue o pai!</p>

<blockquote>
  <p>Dedico esse posto ao nut da minha guitarra que quebrou durante essa gravação. Não coloque si no lugar da mizinha, viu?</p>
</blockquote>]]></content><author><name></name></author><category term="linux" /><category term="audio" /><category term="raspberrypi" /><category term="pt_BR" /><summary type="html"><![CDATA[]]></summary></entry></feed>