Adicionar um botão de microfone a um site parece simples: captar a voz do utilizador, reconhecer as palavras e inseri-las num campo de texto. Na prática, conseguir que isto funcione de forma fiável em navegadores de computadores e dispositivos móveis é bastante mais difícil, sobretudo quando todo o reconhecimento de voz tem de ocorrer localmente, sem enviar o áudio do microfone para um servidor.
O desafio não é apenas escolher um modelo de reconhecimento de voz. Uma implementação funcional tem de gerir corretamente as permissões do microfone, a captação e a reamostragem de áudio, os workers do navegador, o WebAssembly, a inicialização de modelos ONNX, a tokenização, a cache e a integração com o campo de entrada de texto da aplicação.
Este artigo explica como implementar uma funcionalidade prática de conversão de voz em texto que respeite a privacidade, utilizando OpenAI Whisper, Transformers.js, ONNX Runtime Web, AudioWorklet e um Web Worker dedicado. Também aborda várias falhas reais de integração que podem deixar o botão do microfone preso num estado de carregamento, mesmo depois de os ficheiros do modelo terem sido transferidos com sucesso.
O objetivo é simples: uma pessoa clica no botão do microfone, fala naturalmente e vê o texto reconhecido aparecer num campo de entrada já existente. Pode rever ou editar o resultado antes de o enviar. A gravação do microfone não precisa de sair do navegador.
O que significa executar o reconhecimento de voz inteiramente no navegador
Existem duas abordagens fundamentalmente diferentes para adicionar entrada de voz a um site.
A primeira utiliza um serviço de reconhecimento de voz. O navegador capta o áudio, envia-o para um serviço remoto e recebe o texto reconhecido. Isto pode simplificar a implementação, mas cria dependência da rede, questões de privacidade, dependência da disponibilidade do fornecedor e possíveis custos baseados na utilização.
A segunda abordagem realiza a inferência diretamente no navegador. O site transfere um modelo de reconhecimento de voz e o ambiente de execução necessário. O navegador capta o áudio do microfone, converte-o em amostras numéricas, executa o modelo localmente e produz a transcrição sem um servidor de reconhecimento de voz.
Este artigo centra-se exclusivamente na segunda abordagem.
É importante não confundir o reconhecimento de voz comum do navegador com o reconhecimento garantidamente local. A Web Speech API pode utilizar um motor de reconhecimento apoiado por um servidor. Algumas implementações mais recentes disponibilizam a opção processLocally, mas o suporte varia consoante o navegador e o sistema operativo. Se o processamento estritamente local for obrigatório, o SpeechRecognition genérico não deve ser utilizado como alternativa automática.
Em vez disso, a aplicação controla toda a cadeia de inferência.
Microphone
|
v
AudioWorklet
|
v
Float32 PCM audio
|
v
Dedicated Web Worker
|
v
16 kHz resampling
|
v
Whisper + ONNX Runtime Web
|
v
Tokenizer and text decoding
|
v
React input stateO site continua a disponibilizar JavaScript, WebAssembly, ficheiros de configuração e pesos do modelo. São transferências estáticas normais. A diferença é que o áudio do microfone é processado localmente, em vez de ser enviado para reconhecimento. Se, mais tarde, o utilizador enviar o texto resultante através de um formulário comum, isso constitui uma ação separada da aplicação.
Como escolher um modelo prático: Whisper Tiny ou Base
Whisper é uma família de modelos multilingues de reconhecimento de voz. A capacidade de reconhecer vários idiomas com um único modelo torna-a particularmente útil em sites multilingues.
Para mensagens de voz curtas, dois modelos oferecem um equilíbrio útil entre qualidade do reconhecimento e consumo de recursos do navegador: Whisper Tiny e Whisper Base.
| Modelo | Tamanho aproximado dos pesos ONNX Q8 selecionados | Utilização recomendada |
|---|---|---|
| Whisper Tiny | 41 MB | Opção prudente para dispositivos móveis e com poucos recursos |
| Whisper Base | 77 MB | Opção de maior qualidade para dispositivos capazes e previamente testados |
Estes tamanhos referem-se aos ficheiros ONNX selecionados do codificador e do descodificador nas exportações examinadas. Não incluem todos os ficheiros de apoio e não devem ser confundidos com o consumo máximo de memória do navegador.
Whisper Tiny é atraente porque é transferido mais rapidamente, normalmente exige menos recursos de processamento e é mais adequado a ambientes móveis de capacidade desconhecida. Whisper Base oferece, em geral, melhor qualidade de reconhecimento, especialmente em alguns idiomas nos quais os modelos mais pequenos cometem mais erros.
O modelo não deve ser escolhido apenas com base na RAM física. O navegador pode ter disponível muito menos memória do que as especificações do dispositivo sugerem. Também importam a memória já utilizada pela página, as alocações do WebAssembly, os tensores temporários e a pressão sobre a memória do sistema operativo.
Uma abordagem prudente consiste em começar com Tiny em dispositivos móveis cujas capacidades são desconhecidas, reservar Base para classes de dispositivos que satisfaçam os requisitos e recorrer a Tiny caso a inicialização de Base falhe.
Uma instância de Base que falhou tem de ser libertada antes de carregar Tiny. Manter ambos os modelos em memória anula a finalidade dessa alternativa.
Porque é que WebAssembly é uma opção padrão sensata para produção
ONNX Runtime Web permite executar redes neuronais dentro dos navegadores. Para esta carga de trabalho, WebAssembly oferece uma via prática de inferência por CPU nos principais navegadores para computadores e dispositivos móveis.
WebGPU pode acelerar modelos compatíveis em hardware adequado, mas a sua disponibilidade não equivale a uma cadeia funcional de reconhecimento de voz. Um navegador que exponha navigator.gpu não garante que ONNX Runtime suporte a plataforma, que o grafo específico de Whisper seja inicializado corretamente ou que a inferência repetida evite problemas de recursos.
Por isso, uma implementação prudente pode utilizar WASM como mecanismo de execução em produção e avaliar WebGPU separadamente, depois de realizar testes de compatibilidade, precisão, memória e sessões repetidas.
Na implementação inicial, WASM com uma única thread também evita tornar todo o site dependente do isolamento entre origens e de SharedArrayBuffer.
env.backends.onnx.wasm.numThreads = 1;
env.backends.onnx.wasm.proxy = false;Executar a inferência num worker dedicado mantém o processamento exigente fora da thread principal de JavaScript. O campo de entrada, o teclado virtual, a deslocação da página e a interface da aplicação continuam a responder enquanto o modelo processa o áudio.
Não carregue o reconhecimento de voz durante o carregamento inicial da página
A funcionalidade de microfone não deve impor custos significativos de rede e memória a visitantes que nunca a utilizam.
Não importe Transformers.js antecipadamente no pacote inicial da aplicação. Não inicialize ONNX Runtime quando a página é aberta nem transfira automaticamente os pesos do modelo durante os períodos de inatividade.
Em vez disso, carregue os componentes de reconhecimento apenas depois de o utilizador clicar explicitamente no botão do microfone.
Uma sequência útil para a primeira utilização é:
- Receber o clique no botão do microfone.
- Pedir permissão para utilizar o microfone através de
getUserMedia(). - Se a permissão for recusada, apresentar um erro sem transferir o modelo.
- Se for concedida, libertar o fluxo temporário do microfone enquanto o modelo é preparado.
- Inicializar dinamicamente o worker dedicado e carregar o modelo local selecionado.
- Aguardar até o worker confirmar que a cadeia de reconhecimento está pronta.
- Iniciar automaticamente a gravação se o utilizador não tiver cancelado a operação.
- Apresentar um estado de escuta compreensível.
O pedido de permissão tem de estar associado à interação do utilizador. A aplicação também deve tratar operações canceladas e restrições de captação específicas dos navegadores, em vez de assumir que a gravação pode sempre ser reiniciada sem nova interação.
A preparação do modelo deve ter um estado visível, como A preparar a entrada de voz. Um indicador de carregamento sem uma transição clara pode fazer uma transferência bem-sucedida do modelo parecer um microfone avariado.
Depois de inicializado, o worker pode continuar disponível brevemente para outro ditado. Um tempo limite de inatividade de cerca de um minuto é um ponto de partida razoável. Depois disso, termine o worker e liberte o modelo da memória. Os ficheiros do modelo guardados na cache do navegador podem continuar disponíveis em visitas posteriores.
Capte áudio não comprimido com AudioWorklet
Whisper necessita de amostras numéricas de áudio. Embora MediaRecorder seja excelente para gravar ficheiros de áudio, utilizá-lo para reconhecimento local pode introduzir um ciclo desnecessário de codificação e descodificação.
O percurso de processamento mais simples é:
getUserMedia()
|
v
AudioContext
|
v
AudioWorklet
|
v
Float32 PCM buffers
|
v
Dedicated WorkerAudioWorklet executa código de processamento de áudio fora da thread principal de JavaScript da aplicação. Pode captar amostras PCM, converter entrada multicanal em mono, calcular medições simples de volume e transferir os buffers acumulados para o worker.
Uma inicialização simplificada do microfone tem o seguinte aspeto:
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
channelCount: { ideal: 1 },
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
},
});
const context = new AudioContext();
await context.audioWorklet.addModule(
'/assets/speech/pcm-worklet.js'
);
const source = context.createMediaStreamSource(stream);
const processor = new AudioWorkletNode(
context,
'pcm-capture'
);
source.connect(processor);
// Connect the worklet through an inaudible output path
// if required to keep the audio processing graph active.As restrições do microfone são preferências, não garantias. Os navegadores podem aplicar diferentes algoritmos de cancelamento de eco, supressão de ruído e controlo automático de ganho.
Um detalhe importante é que a frequência de amostragem do microfone não é necessariamente 16 kHz. Muitos contextos de áudio utilizam 44.1 kHz ou 48 kHz. Whisper espera uma entrada de 16 kHz, pelo que as amostras captadas precisam de uma reamostragem adequada.
Descartar simplesmente uma em cada duas ou três amostras não é, em geral, uma solução correta. Um reamostrador adequado com filtro passa-baixo, como uma implementação sinc com janela, reduz o aliasing e preserva frequências úteis da voz ao diminuir a frequência de amostragem.
Mantenha a reamostragem dentro do worker dedicado, e não na função de AudioWorklet executada em tempo real.
Transfira áudio de forma eficiente entre threads do navegador
AudioWorklet pode produzir muitos pequenos segmentos de áudio. Enviar cada bloco de processamento de áudio numa mensagem separada pode criar sobrecarga desnecessária.
Uma estratégia prática consiste em acumular pequenos lotes de PCM e transferir cada lote como um ArrayBuffer.
worker.postMessage(
{
type: 'audio-chunk',
buffer,
},
[buffer]
);A lista de transferência é importante: transfere a propriedade do ArrayBuffer em vez de efetuar uma cópia completa desnecessária.
O worker pode adicionar estes segmentos a um acumulador de áudio e combiná-los quando a gravação terminar.
Também é útil acompanhar a frequência de amostragem real da captação, o número de amostras recolhidas e a duração da gravação. Estes metadados ajudam a diagnosticar gravações silenciosas sem registar dados de áudio sensíveis.
Detete pausas sem adicionar outra rede neuronal
O ditado de mensagens de texto curtas não exige necessariamente um detetor neuronal de atividade de voz.
Um detetor RMS adaptativo pode oferecer uma aproximação leve à atividade de voz. O RMS mede a amplitude do sinal, permitindo que a aplicação estime quando uma pessoa que estava a falar ficou em silêncio.
A interface deve permitir sempre parar manualmente a gravação com um segundo clique no microfone. A deteção automática de silêncio é uma conveniência, não uma condição necessária para um reconhecimento bem-sucedido.
Entre os valores iniciais razoáveis para ditados curtos encontram-se:
| Parâmetro | Valor inicial |
|---|---|
| Intervalo de análise | 20 ms |
| Calibração do ruído | 300 ms |
| Confirmação de voz | 140 ms |
| Silêncio antes da paragem automática | 1.4 segundos |
| Tempo limite sem voz | 8 segundos |
| Duração máxima da gravação | 30 segundos |
Estes são valores de partida para configuração, não limites universalmente ideais. A voz baixa, a música de fundo e o processamento de ruído do microfone podem afetar as medições RMS.
É preferível deixar que o utilizador pare manualmente a gravação a cortar automaticamente as últimas palavras de uma frase.
Para a introdução habitual de textos curtos, uma transcrição única costuma ser mais simples do que executar Whisper repetidamente para obter resultados provisórios. Grave a fala, pare, execute o modelo uma vez e insira a transcrição final.
A cadeia completa de Whisper exige mais do que um modelo ONNX
Este é um dos detalhes de implementação mais importantes.
A inferência de Whisper não consiste simplesmente numa chamada a um codificador ONNX seguida de outra a um descodificador ONNX. Uma cadeia completa de reconhecimento automático de voz também exige extração de características do áudio, tokenização, configuração de geração e descodificação dos tokens previstos em texto legível.
A cadeia completa de processamento é:
Raw microphone PCM
|
v
Resampling to 16 kHz
|
v
Whisper feature extractor
|
v
ONNX encoder
|
v
ONNX decoder and token generation
|
v
Whisper tokenizer
|
v
Recognized textA inicialização bem-sucedida de uma sessão ONNX apenas demonstra que é possível carregar o grafo do modelo. Não demonstra que toda a cadeia de reconhecimento funcione.
Numa integração testada de Transformers.js, as sessões do modelo conseguiam inicializar-se, mas a transcrição continuava a falhar porque a cadeia construída localmente não tinha o processador e o tokenizador necessários devidamente configurados.
A implementação compatível inicializava explicitamente estes componentes utilizando os metadados locais do modelo.
O exemplo seguinte é uma ilustração reduzida dessa abordagem específica de uma versão, e não um worker autónomo completo:
import {
WhisperTokenizer,
WhisperFeatureExtractor,
WhisperProcessor,
} from '@huggingface/transformers';
// These values come from local model configuration files.
const tokenizer = new WhisperTokenizer(
tokenizerJson,
tokenizerConfig
);
const featureExtractor = new WhisperFeatureExtractor(
preprocessorConfig
);
const processor = new WhisperProcessor(
{},
{
tokenizer,
feature_extractor: featureExtractor,
},
''
);
// In the affected version-specific integration:
recognizer.tokenizer = tokenizer;
recognizer.processor = processor;As assinaturas exatas dos construtores e o método de integração devem ser verificados face à versão instalada de Transformers.js. Algumas versões e exportações de modelos conseguem construir estes componentes automaticamente; a criação manual não deve ser tratada como um requisito universal.
Com a cadeia completa disponível, é possível executar a transcrição de textos curtos indicando explicitamente o idioma e a tarefa:
const result = await recognizer(audio16k, {
language: 'en',
task: 'transcribe',
return_timestamps: false,
});Quando a aplicação já conhece o idioma do utilizador, indicá-lo explicitamente evita uma deteção desnecessária e pode melhorar a previsibilidade em gravações curtas.
Fixe versões compatíveis de Transformers.js e ONNX Runtime
Um segundo tipo importante de falha envolve a relação entre Transformers.js e ONNX Runtime Web.
Transformers.js fornece a cadeia de reconhecimento de nível superior, enquanto ONNX Runtime Web executa os grafos da rede neuronal. Estes pacotes têm de ser compatíveis entre si e com as exportações exatas dos modelos ONNX utilizados.
Na implementação investigada, uma versão de desenvolvimento de ONNX Runtime Web inicializava o codificador de Whisper, mas falhava ao criar uma sessão do descodificador quantizado. O erro envolvia a ausência de uma escala de quantização numa operação DequantizeLinear.
Não era um problema de permissão do microfone, de transferência ou de formato do áudio. O navegador conseguia receber os ficheiros do modelo, mas o ambiente de execução não conseguia inicializar um dos grafos necessários.
Testar diretamente o codificador e o descodificador com uma versão estável e fixada de ONNX Runtime Web resolveu essa incompatibilidade específica.
A verificação de compatibilidade foi especialmente valiosa porque permitiu distinguir duas afirmações diferentes:
- O ficheiro do modelo foi transferido com sucesso.
- O ambiente de execução criou com sucesso uma sessão de inferência utilizável a partir do ficheiro do modelo.
Estas afirmações não são equivalentes.
Para as exportações de modelos testadas, uma combinação compatível utilizava Transformers.js 4.2.0 com ONNX Runtime Web 1.24.3. Um ambiente de execução de desenvolvimento mais recente falhava com o grafo do descodificador quantizado. Trata-se de uma observação específica dessas versões, não de uma prova de que ambientes de execução mais antigos sejam sempre melhores.
Ao substituir uma dependência transitiva do ambiente de execução, fixe a versão exata no ficheiro de bloqueio de dependências e documente a razão da substituição. A atualização do ambiente deve exigir um teste de regressão completo da inferência no navegador, e não apenas uma instalação bem-sucedida do pacote.
Verifique o formato do pacote do worker antes de depurar Whisper
Um Web Worker de JavaScript pode ser criado com diferentes formatos de módulo.
Uma falha de integração ocorreu porque a aplicação instanciava um worker como módulo ES, enquanto o pacote gerado para esse worker utilizava o mecanismo importScripts() dos workers clássicos.
Estes modelos de execução não são intercambiáveis. Um worker de tipo módulo não pode simplesmente executar código exclusivo dos workers clássicos.
A lição prática é ajustar o construtor Worker ao código efetivamente gerado, em vez de copiar uma opção do construtor de um tutorial genérico.
Por exemplo, as opções seguintes não são automaticamente equivalentes:
// Module worker: requires a compatible module bundle.
new Worker(workerUrl, { type: 'module' });
// Classic worker: requires a compatible classic bundle.
new Worker(workerUrl);A escolha correta depende da ferramenta de empacotamento e do JavaScript gerado.
Ao diagnosticar um erro de inicialização do worker, examine a consola do navegador, o ficheiro gerado para o worker e o painel de rede. Determine se a falha ocorre antes mesmo de ONNX Runtime começar a inicializar.
Algumas bibliotecas de navegador empacotadas também calculam URLs relativamente à localização do seu próprio módulo. Ao incluí-las em workers, teste os caminhos resultantes dos recursos do ambiente de execução em vez de assumir que os URLs relativos originais continuarão a funcionar corretamente.
Disponibilize módulos WASM e JavaScript com os tipos MIME corretos
Mesmo ficheiros JavaScript e ONNX perfeitamente compatíveis podem falhar se o servidor web devolver cabeçalhos de resposta incorretos.
Numa falha concreta, um módulo JavaScript com a extensão .mjs era servido como application/octet-stream. O navegador rejeitava o módulo porque o tipo da resposta não era adequado à execução de módulos JavaScript.
Os tipos de resposta esperados são normalmente:
| Extensão do ficheiro | Content-Type |
|---|---|
| .mjs | application/javascript |
| .wasm | application/wasm |
Verifique a resposta efetiva em produção, e não apenas o ficheiro de configuração do servidor.
curl -I https://example.com/runtime/speech/v2/runtime.mjs
curl -I https://example.com/runtime/speech/v2/runtime.wasmOs URLs acima são ilustrativos. Utilize os nomes reais dos ficheiros versionados da compilação escolhida do ambiente de execução.
Um código de estado HTTP correto não é suficiente. O navegador pode ainda rejeitar um ficheiro devido ao tipo MIME, à política de segurança de conteúdos (Content Security Policy), ao modo de execução do módulo ou a um URL incorreto do recurso.
Tenha em conta que o mesmo pedido pode funcionar ao ser aberto diretamente num separador do navegador e falhar quando carregado como módulo executável ou dependência WebAssembly.
Use URLs imutáveis para os recursos sem prender os utilizadores a ficheiros defeituosos
Os ficheiros dos modelos Whisper são suficientemente grandes para que a cache do navegador seja importante.
Os caminhos de recursos imutáveis e versionados permitem ao navegador reutilizar ficheiros inalterados do modelo e do ambiente de execução sem os transferir sempre que alguém fala.
Uma política típica de cache é:
Cache-Control: public, max-age=31536000, immutableNo entanto, isto cria um requisito de implantação: depois de um URL ser declarado imutável, o seu conteúdo não deve ser substituído silenciosamente.
Se o binário do ambiente de execução tiver de mudar, publique-o num novo URL versionado.
/models/speech/v1/
/runtime/speech/v1/
/runtime/speech/v2/A alteração do caminho do ambiente de execução não é apenas estética. O navegador pode continuar a utilizar um binário WebAssembly incompatível, anteriormente guardado em cache, se a aplicação reutilizar um URL imutável depois de alterar o seu conteúdo.
Também é útil manter os ficheiros de integração JavaScript e WebAssembly da mesma distribuição do ambiente de execução. Misturar ficheiros de versões diferentes pode provocar falhas de inicialização difíceis de diagnosticar.
Um manifesto gerado durante a compilação, com os ficheiros esperados, os respetivos tamanhos e somas de verificação criptográficas, pode detetar recursos ausentes ou divergentes antes da implantação.
Configure a biblioteca de reconhecimento para utilizar recursos alojados no próprio site e desative o carregamento remoto de modelos. Assim, a ausência de um modelo local provoca um erro explícito em vez de desencadear uma transferência não prevista de um serviço externo de alojamento de modelos.
Porque é que o microfone pode continuar a carregar sem funcionar
Um sintoma comum durante o desenvolvimento é um botão de microfone que pede permissão, apresenta uma animação de carregamento e acaba por passar para um estado de erro sem inserir qualquer texto.
O navegador pode até mostrar que o microfone está a ser utilizado.
Isto não prova que o reconhecedor de voz funcione. Prova apenas que a captação pelo microfone foi permitida ou esteve ativa em algum momento.
Várias falhas independentes podem produzir sintomas quase idênticos:
- Tipo MIME incorreto do módulo JavaScript. O navegador transfere um módulo do ambiente de execução, mas recusa executá-lo.
- Incompatibilidade do formato de execução do worker. O worker compilado usa APIs de workers clássicos, mas é iniciado como worker de tipo módulo, ou vice-versa.
- Incompatibilidade do descodificador ONNX. O ambiente de execução cria a sessão do codificador, mas falha com o grafo de um descodificador quantizado.
- Cadeia de reconhecimento incompleta. As sessões ONNX existem, mas o tokenizador ou o processador de áudio não estão corretamente inicializados.
Estes problemas têm de ser diagnosticados de forma independente. Corrigir um deles pode revelar o seguinte.
O princípio de depuração mais importante é que HTTP 200 não significa que o modelo tenha sido inicializado com sucesso, e uma inicialização bem-sucedida do modelo não significa que a transcrição funcione.
Mesmo uma transcrição bem-sucedida não prova que o resultado chegue ao campo de texto real da aplicação.
Cada etapa da cadeia precisa de um sinal de diagnóstico significativo.
Adicione observabilidade sem recolher fala privada
Os registos de diagnóstico úteis não exigem gravar o que a pessoa diz.
A aplicação pode acompanhar estados técnicos como:
permission-granted
worker-created
runtime-loading
runtime-ready
encoder-ready
decoder-ready
recording-started
pcm-received
recording-stopped
inference-started
inference-completed
result-insertedEm caso de falha, inclua a etapa, um código de erro técnico estável e metadados do erro sem informação sensível.
Uma falha de inicialização do modelo deve ser distinguível de uma recusa de permissão do microfone ou de uma gravação vazia. Durante o desenvolvimento, uma falha do worker deve ser visível, em vez de ficar escondida por um gestor de erros genérico.
Não registe amostras do microfone, frases reconhecidas, sequências de tokens ou outros conteúdos de voz. A telemetria de desempenho pode utilizar intervalos de duração, categoria do modelo escolhido, tempo de inicialização, tempo de inferência e categorias de erros.
Isto facilita muito o diagnóstico de um microfone que falha sem comprometer os requisitos de privacidade.
Insira a transcrição através do estado do React
O reconhecimento de voz só é útil quando a transcrição final chega ao campo existente do formulário.
Numa aplicação React, esse campo é frequentemente controlado pelo estado do componente. Atribuir diretamente um valor a input.value pode entrar em conflito com o comportamento de renderização do framework.
Em vez disso, o texto reconhecido deve seguir o mesmo mecanismo de atualização de estado utilizado na escrita normal.
A integração também precisa de considerar o comportamento assíncrono. O reconhecimento pode terminar depois de o utilizador já ter escrito mais caracteres ou movido o cursor.
Utilizar um valor desatualizado do campo, captado no início da gravação, pode substituir acidentalmente alterações mais recentes.
Um padrão simplificado de integração é:
const insertion = insertTranscript({
currentValue: latestValue,
transcript: recognizedText,
selection: currentSelection,
maxLength: inputLimit,
});
setMessage(insertion.value);
restoreCaret(insertion.caret);A função auxiliar de inserção deve preservar o texto atual, substituir opcionalmente o intervalo selecionado, aplicar o espaçamento adequado e respeitar o comprimento máximo do campo.
O Unicode merece atenção porque o JavaScript conta o comprimento das strings em unidades de código UTF-16. Cortar uma string numa posição arbitrária pode dividir um par substituto.
Acima de tudo, o reconhecimento de voz nunca deve enviar automaticamente o formulário. O utilizador deve poder rever e corrigir o texto antes do envio.
Trate as falhas sem prejudicar a entrada normal de texto
O subsistema de voz deve ser opcional.
Se a permissão do microfone for recusada, o navegador não conseguir alocar o modelo, um worker falhar ou uma gravação não contiver fala, o campo de texto normal tem de continuar a funcionar.
Represente o ciclo de vida do microfone com estados explícitos como:
idle
preparing
listening
recognizing
errorUtilize etiquetas acessíveis e informação de estado visível. Um microfone que apenas muda para vermelho ou rosa depois de carregar quase nada comunica ao utilizador.
O cancelamento deve libertar as pistas de áudio, o contexto de áudio, os buffers pendentes e a sessão de gravação ativa. A interface também deve impedir que resultados desatualizados de uma gravação anteriormente cancelada substituam um estado de entrada mais recente.
Se um modelo grande falhar devido a limitações de memória ou inicialização, liberte-o completamente antes de tentar um modelo mais pequeno. Limite as novas tentativas para evitar que o navegador transfira ou inicialize repetidamente modelos que não consegue executar.
Não mude silenciosamente para um serviço de reconhecimento de voz na nuvem quando o reconhecimento local falhar. Se o processamento exclusivamente local for um requisito estrito, a falha de um reconhecedor local tem de permanecer local.
Teste toda a cadeia de processamento no navegador
Uma verificação TypeScript ou uma compilação de produção bem-sucedida não prova, por si só, que a entrada de voz funciona.
Da mesma forma, confirmar que o navegador consegue transferir os ficheiros ONNX não prova que ambas as sessões do modelo sejam inicializadas corretamente.
O teste de regressão mais útil deve executar o mesmo worker, os mesmos ficheiros do ambiente de execução, o modelo, o processador e o tokenizador utilizados em produção.
Forneça uma gravação PCM curta e conhecida à cadeia real de inferência no navegador e confirme que é devolvida uma transcrição não vazia.
Depois, teste toda a interação com o microfone num navegador real.
- Carregamento inicial da página: verifique que não são transferidos modelos de voz nem recursos do ambiente de execução antes de utilizar o microfone.
- Inicialização a frio: verifique a permissão do microfone, os pedidos dos modelos, os tipos MIME de JavaScript e WASM e uma confirmação real de que o worker está pronto.
- Inicialização de ONNX: verifique que é possível criar as sessões do codificador e do descodificador.
- Captação de áudio: confirme que AudioWorklet produz buffers PCM não vazios.
- Reamostragem: confirme que o worker recebe a frequência de amostragem real e produz áudio válido a 16 kHz.
- Paragem manual: verifique que um segundo clique no microfone para a gravação de forma fiável.
- Paragem automática: verifique que as pausas naturais terminam a gravação sem cortar a voz baixa.
- Transcrição: verifique que o processador, o modelo e o tokenizador completos devolvem texto reconhecível.
- Integração com o campo de entrada: verifique que o texto reconhecido aparece no campo controlado real sem substituir alterações do utilizador.
- Recuperação de falhas: teste permissões recusadas, ficheiros ausentes, erros do worker, falhas de inicialização do modelo, cancelamento e recurso a um modelo mais pequeno.
- Privacidade: examine os pedidos de rede e confirme que o áudio do microfone não é enviado para um servidor nem para um serviço de análise.
- Compatibilidade móvel: repita os testes em dispositivos móveis reais, incluindo inicializações a frio e a quente e várias sessões de ditado consecutivas.
Os testes com PCM previamente gravado são excelentes para validar a cadeia de inferência, mas não provam que a captação pelo microfone funcione. O teste final de aceitação tem de incluir o microfone real e a interface efetiva.
Uma arquitetura prática para ditado fiável no navegador
Para um site que precisa de entrada de voz curta, multilingue e respeitadora da privacidade, a arquitetura mais prática é deliberadamente simples:
React microphone button
|
v
Permission request
|
v
Lazy model initialization
|
v
AudioWorklet PCM capture
|
v
Adaptive RMS / manual stop
|
v
Dedicated Worker
|
v
16 kHz audio resampling
|
v
Whisper Tiny or Base Q8
|
v
Compatible ONNX Runtime WASM
|
v
Whisper processor + tokenizer
|
v
Final transcript
|
v
Controlled React inputA aplicação deve alojar os seus próprios ficheiros versionados do modelo e do ambiente de execução, manter a inferência fora da thread principal, evitar transferências desnecessárias de modelos, libertar recursos após a utilização e nunca enviar o áudio do microfone para reconhecimento de voz.
Opções mais sofisticadas, como aceleração por WebGPU, deteção neuronal de atividade de voz, transcrição parcial contínua ou categorias adicionais de modelos, podem ser consideradas mais tarde, quando as medições justificarem a sua complexidade.
A lição mais valiosa não é uma versão específica de uma biblioteca nem um tamanho de modelo em particular. É a importância de validar cada etapa da cadeia de reconhecimento de voz no navegador.
Um pedido de permissão do microfone que funciona não significa que o áudio esteja a ser transcrito. Um modelo transferido não significa que o seu descodificador tenha sido inicializado. Uma sessão ONNX bem-sucedida não significa que o tokenizador esteja disponível. E uma transcrição bem-sucedida não significa que a aplicação tenha inserido o texto corretamente.
A funcionalidade só está completa quando uma pessoa consegue clicar no microfone, falar naturalmente, parar a gravação e ver as palavras reconhecidas no campo editável, sem enviar a gravação para um servidor de reconhecimento de voz.