Olá a todos.
Há algum tempo, nós sobre como conseguimos rodar um telefone SIP no STM32F4-Discovery com 1 MB de ROM e 192 kB de RAM) baseado em . É importante mencionar que aquela versão era mínima e conectava dois telefones diretamente sem servidor e com a transmissão de voz apenas em uma direção. Por isso, decidimos lançar um telefone mais completo, com chamadas através de um servidor, transmissão de voz em ambas as direções, mas mantendo o tamanho da memória o menor possível.

Para o telefone, decidimos escolher o aplicativo simple_pjsua parte da biblioteca PJSIP. Este é um aplicativo mínimo que pode se registrar no servidor, receber e realizar chamadas. Abaixo, já irei apresentar uma descrição de como executá-lo no STM32F7-Discovery.
Como executar
- Configurando o Embox
make confload-platform/pjsip/stm32f7cube - No arquivo conf/mods.config, definimos a conta SIP necessária.
include platform.pjsip.cmd.simple_pjsua_imported( sip_domain="server", sip_user="username", sip_passwd="password")onde servidor — este é o servidor SIP (por exemplo, sip.linphone.org), nome de usuário e senha — o nome de usuário e a senha da conta.
- Compilamos o Embox com o comando make. Temos informações sobre a programação da placa em e no .
- Executamos no console do Embox o comando “simple_pjsua_imported”
00:00:12.870 pjsua_acc.c ....O status de saída SIP para acc 0 não está ativo 00:00:12.884 pjsua_acc.c ....sip:alexk2222@sip.linphone.org: registro bem-sucedido, status=200 (Registro bem-sucedido) 00:00:12.911 pjsua_acc.c ....Temporizador de keep-alive iniciado para acc 0, destino:91.121.209.194:5060, intervalo:15s - Por fim, basta conectar alto-falantes ou fones de ouvido à saída de áudio e falar em dois pequenos microfones MEMS próximos ao display. Chamamos do Linux através do aplicativo simple_pjsua, pjsua. Ou você pode usar qualquer outro, como linphone.
Tudo isso está descrito em nosso .
Como chegamos a isso
Então, inicialmente surgiu a questão da escolha da plataforma de hardware. Como ficou claro que o STM32F4-Discovery não seria suficiente em termos de memória, escolhemos o STM32F7-Discovery. Ele tem 1 MB de flash e 256 kB de RAM (+ 64 kB de memória rápida especial, que também iremos usar). Também não é muito para chamadas através de servidor, mas decidimos tentar.
Condicionalmente, dividimos a tarefa em várias etapas:
- Execução do PJSIP no QEMU. Isso foi conveniente para depuração, além de já termos lá suporte para o codec AC97.
- Gravação de voz e reprodução no QEMU e no STM32.
- Portabilidade do aplicativo simple_pjsua da biblioteca PJSIP. Ele permite registrar-se no servidor SIP e fazer chamadas.
- Implantar seu próprio servidor baseado em Asterisk e testá-lo, após o que tentaremos externos, como sip.linphone.org.
O som no Embox funciona através do Portaudio, que também é utilizado no PISIP. No QEMU, surgiram os primeiros problemas — os arquivos WAV de 44100 Hz eram reproduzidos corretamente, mas em 8000 algo definitivamente não estava certo. Descobriu-se que o problema estava na configuração da frequência — por padrão, no hardware, era 44100, e isso não alteramos programaticamente.
Aqui, talvez seja importante esclarecer como a reprodução de som ocorre. Um ponteiro de memória pode ser definido para a placa de som, a partir do qual deve ser reproduzido ou gravado em uma frequência previamente definida. Após o preenchimento do buffer, ocorre uma interrupção e a execução continua com o próximo buffer. O desafio é que esses buffers precisam ser preenchidos antecipadamente enquanto o anterior ainda está sendo reproduzido. Enfrentaremos esse problema mais adiante no STM32F7.
Em seguida, alugamos um servidor e instalamos o Asterisk nele. Como era necessário bastante depuração e não queríamos falar muito no microfone, precisávamos implementar a reprodução e gravação automáticas. Para isso, modificamos o simple_pjsua para permitir que arquivos fossem utilizados em vez de dispositivos de áudio. No PJSIP, isso é bastante simples, pois há o conceito de porta, que pode ser tanto um dispositivo quanto um arquivo. E essas portas podem ser conectadas de forma flexível a outras portas. O código pode ser consultado em nosso pjsip. Assim, o esquema era o seguinte. No servidor Asterisk, criei duas contas — uma para Linux e outra para Embox. Em seguida, no Embox, a seguinte comando é executado simple_pjsua_imported, o Embox se registra no servidor, após o qual fazemos uma chamada do Linux para o Embox. No momento da conexão, verificamos no servidor Asterisk se toda a conexão foi estabelecida, e após algum tempo devemos ouvir o som vindo do Linux no Embox, enquanto no Linux salvamos o arquivo que está sendo reproduzido a partir do Embox.
Após fazer isso funcionar no QEMU, passamos para a portabilidade no STM32F7-Discovery. O primeiro problema foi que não cabíamos em 1 MB de ROM sem habilitar a otimização do compilador '-Os' para o tamanho da imagem. Portanto, habilitamos '-Os'. Em seguida, desativamos o suporte a C++ através de patch, pois isso é necessário apenas para pjsua, e estamos usando o simple_pjsua.
Depois que conseguimos ajustar simple_pjsua, decidimos que agora havia chances de que isso funcionasse. Mas primeiro precisávamos resolver a gravação e reprodução da voz. A pergunta era — para onde gravar? Escolhemos a memória externa — SDRAM (128 MB). Você pode tentar isso você mesmo:
Criará um WAV estéreo a 16000 Hz e com duração de 10 segundos:
record -r 16000 -c 2 -d 10000 -m C0000000
Reproduzindo:
play -m C0000000
Aqui surgiram dois problemas. O primeiro era com o codec — usa-se WM8994, que possui um conceito chamado slot, e existem 4 desses slots. Por padrão, se não for configurado, ao reproduzir áudio, a reprodução ocorre em todos os quatro slots. Portanto, à frequência de 16000 Hz, estávamos obtendo 8000 Hz, e para 8000 Hz a reprodução simplesmente não funcionava. Quando escolhemos apenas os slots 0 e 2, funcionou como deveria. Outro problema foi a interface de áudio no STM32Cube, onde a saída de áudio funciona através de SAI (Serial Audio Interface) de forma síncrona com a entrada de áudio (não me aprofundei nos detalhes, mas parece que eles compartilham um clock comum, e durante a inicialização da saída de áudio, de alguma forma está atrelada à entrada de áudio). Ou seja, não é possível iniciá-los separadamente, portanto fizemos o seguinte — a entrada e a saída de áudio sempre estão funcionando (incluindo a geração de interrupções). Mas quando nada está sendo reproduzido no sistema, simplesmente alimentamos a saída de áudio com um buffer vazio, e quando a reprodução é iniciada, começamos a preenchê-lo adequadamente.
Em seguida, encontramos o problema de que o som ao gravar a voz estava muito baixo. Isso ocorre porque os microfones MEMS no STM32F7-Discovery não funcionam bem em frequências abaixo de 16000 Hz. Portanto, configuramos para 16000 Hz, mesmo que a entrada seja 8000 Hz. Para isso, foi necessário adicionar uma conversão de frequência no software.
Depois, foi necessário aumentar o tamanho da pilha, que está localizada na RAM. Segundo nossas contas, o pjsip exigia cerca de 190 KB, enquanto nós tínhamos apenas cerca de 100 KB restantes. Então, foi preciso utilizar um pouco de memória externa — SDRAM (cerca de 128 KB).
Após todas essas alterações, vi os primeiros pacotes entre Linux e Embox e ouvi o som! Mas o som estava horrível, nada parecido com o do QEMU, nada podia ser compreendido. Então, questionamos qual poderia ser o problema. A depuração mostrou que o Embox simplesmente não estava conseguindo preencher/descarregar os buffers de áudio a tempo. Enquanto o pjsip processava um quadro, acabavam ocorrendo 2 interrupções ao finalizar o processamento dos buffers, o que era excessivo. O primeiro pensamento para acelerar foi a otimização do compilador, mas essa já estava ativada no PJSIP. O segundo — ponto flutuante de hardware, sobre o qual falamos em . Mas como a prática mostrou, o FPU não proporcionou um ganho significativo de velocidade. O próximo passo foi a definição de prioridades de threads. No Embox, existem diferentes estratégias de escalonamento, e eu ativei a que suporta prioridades, dando ao fluxo de áudio a maior prioridade. Isso também não ajudou.
A próxima ideia foi que estávamos lidando com memória externa e seria bom mover para lá as estruturas que são acessadas com bastante frequência. Fiz uma análise preliminar de quando e para que simple_pjsua a memória é alocada. Verificou-se que dos 190 Kb, os primeiros 90 Kb são alocados para necessidades internas do PJSIP e são acessados com pouca frequência. Durante uma chamada recebida, a função pjsua_call_answer é chamada, na qual os buffers para trabalhar com os quadros de entrada e saída são alocados. Isso consumiu mais cerca de 100 Kb. E aqui fizemos o seguinte. Até o momento da chamada, as informações são armazenadas na memória externa. Assim que a chamada ocorre, mudamos imediatamente a heap para outra — na RAM. Dessa forma, todos os dados 'quentes' foram movidos para uma memória mais rápida e previsível.
Como resultado, tudo isso permitiu iniciar simple_pjsua e fazer chamadas através do nosso servidor. E depois, também através de outros servidores, como sip.linphone.org.
Como resultado, obtemos um serviço de "monitoramento como serviço", automatizado na linha de produção de integração contínua. O desenvolvedor ou testador precisa apenas definir a lista de métricas em um arquivo JSON, e tudo o mais acontece automaticamente. Obtemos um controle de qualidade transparente dos lançamentos: todas as notificações sobre desempenho, consumo de recursos ou regressões arquitetônicas.
No final, conseguimos iniciar simple_pjsua com a transmissão de voz em ambas as direções através do servidor. O problema com os 128 Kb SDRAM gastos a mais pode ser resolvido utilizando um Cortex-M7 um pouco mais potente (por exemplo, STM32F769NI com 512 Kb de RAM), mas ainda assim não desistimos da ideia de conseguir ajustar tudo dentro de 256 Kb 🙂 Ficaremos felizes se alguém se interessar, e ainda melhor — testar. Todos os fontes, como de costume, estão no nosso .
Fonte: habr.com
