Codificação de áudio sem esforço no navegador com WebAssembly
Transforme um arquivo de áudio curto em MP3 sem fazer upload dele. Este tutorial cria uma pequena aplicação de navegador com FFmpeg.wasm, um servidor estático local, um botão de cancelar e um link de download.
Introdução à codificação de áudio no navegador
A codificação no navegador mantém o áudio selecionado no dispositivo do usuário. O navegador baixa os arquivos do codificador, lê o arquivo selecionado para a memória e executa o FFmpeg em um Web Worker. A codificação ainda consome CPU e memória, então este exemplo limita as entradas a 25 MiB e processa um arquivo por vez.
O papel do WebAssembly no processamento de áudio
O FFmpeg.wasm empacota o FFmpeg como WebAssembly. Usamos o @ffmpeg/core@0.12.10 de thread
única com @ffmpeg/ffmpeg@0.12.15. O wrapper e o core têm números de versão separados;
instalar o mesmo número para os dois não identifica um conjunto de pacotes válido.
Benefícios de usar WebAssembly para codificação de áudio
- Processamento local: a aplicação não tem nenhum endpoint de upload de áudio.
- Responsividade: um worker executa o codificador fora da thread principal da interface.
- Suporte a formatos: o FFmpeg fornece os decodificadores e o codificador MP3 usados aqui.
O WebAssembly não garante velocidade de codificação nativa. O FAQ do FFmpeg.wasm explica as limitações de desempenho e de memória; teste arquivos representativos nos dispositivos que você suporta.
Configurando uma aplicação web simples para codificação de áudio
Pré-requisitos
Use Node.js 24, Yarn 4, um shell POSIX e um navegador atual com suporte a WebAssembly e module
workers. Comece com um arquivo WAV curto. Outros contêineres de áudio só funcionam quando o
decodificador correspondente está incluído no core do FFmpeg com versão fixada. O atributo
accept do seletor de arquivos é uma conveniência, não uma validação.
Configuração do projeto
Crie um diretório novo e instale versões exatas. O linker node-modules disponibiliza os caminhos de pacote usados pelos comandos de cópia dos arquivos:
mkdir webassembly-audio-encoder
cd webassembly-audio-encoder
corepack yarn init
corepack yarn config set nodeLinker node-modules
corepack yarn add --exact @ffmpeg/ffmpeg@0.12.15 @ffmpeg/core@0.12.10 express@5.1.0
mkdir -p public/vendor
cp -R node_modules/@ffmpeg/ffmpeg/dist/esm public/vendor/ffmpeg
cp -R node_modules/@ffmpeg/core/dist/esm public/vendor/core
Mantenha o diretório ffmpeg inteiro: os módulos JavaScript dele incluem o
worker do wrapper e seus imports relativos. Os dois diretórios vendor precisam vir deste conjunto de
pacotes instalado. Não há dependência de CDN em tempo de execução e nenhum bundler é necessário para
esses imports relativos no navegador.
Configuração do servidor de desenvolvimento
Salve isto como server.ts. Ele serve apenas public/, então
os arquivos do projeto não ficam expostos:
import { fileURLToPath } from 'node:url'
import express from 'express'
const app = express()
app.use(express.static(fileURLToPath(new URL('./public/', import.meta.url))))
app.listen(3000, '127.0.0.1', () => {
console.log('Open http://127.0.0.1:3000')
})
Depois de criar os arquivos abaixo, inicie o servidor a partir do diretório do projeto:
node server.ts
Abra http://127.0.0.1:3000. Pare o servidor com Ctrl+C quando terminar.
Configuração e uso do FFmpeg.wasm
Salve isto como public/index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Browser audio encoder</title>
</head>
<body>
<h1>Encode audio to MP3</h1>
<label for="uploader">Audio file, up to 25 MiB</label>
<input type="file" id="uploader" accept="audio/*" />
<button id="encodeButton" type="button">Encode audio</button>
<button id="cancelButton" type="button" disabled>Cancel</button>
<p id="status" role="status">Choose an audio file.</p>
<a id="download" download="output.mp3" hidden>Download MP3</a>
<script type="module" src="./index.js"></script>
</body>
</html>
Salve isto como public/index.js. Cada tentativa recebe seu próprio worker e sistema de
arquivos virtual. Encerrar esse worker libera a memória da entrada, da saída e do codificador,
inclusive depois de uma conversão com falha.
import { FFmpeg } from './vendor/ffmpeg/index.js'
const uploader = document.getElementById('uploader')
const encodeButton = document.getElementById('encodeButton')
const cancelButton = document.getElementById('cancelButton')
const status = document.getElementById('status')
const download = document.getElementById('download')
let active = null
let downloadURL = null
function clearDownload() {
download.hidden = true
download.removeAttribute('href')
if (downloadURL !== null) URL.revokeObjectURL(downloadURL)
downloadURL = null
}
function cancelEncoding() {
if (active === null) return
active.canceled = true
active.ffmpeg.terminate()
}
async function encodeFile() {
if (active !== null) return
const file = uploader.files?.[0]
if (!file || file.size === 0 || file.size > 25 * 1024 * 1024) {
status.textContent = 'Choose a nonempty audio file of at most 25 MiB.'
return
}
clearDownload()
const job = { ffmpeg: new FFmpeg(), canceled: false }
active = job
uploader.disabled = true
encodeButton.disabled = true
cancelButton.disabled = false
status.textContent = 'Loading the encoder…'
// Also bound loading and worker failures that may never reply to the wrapper.
const deadline = setTimeout(() => {
job.ffmpeg.terminate()
}, 120_000)
try {
await job.ffmpeg.load({
coreURL: new URL('./vendor/core/ffmpeg-core.js', location.href).href,
wasmURL: new URL('./vendor/core/ffmpeg-core.wasm', location.href).href,
})
const input = new Uint8Array(await file.arrayBuffer())
if (job.canceled) return
await job.ffmpeg.writeFile('input.audio', input)
status.textContent = 'Encoding…'
const exitCode = await job.ffmpeg.exec(
['-i', 'input.audio', '-map', '0:a:0', '-vn', '-c:a', 'libmp3lame', '-b:a', '192k', 'output.mp3'],
60_000,
)
if (exitCode !== 0) throw new Error('Encoder failed or timed out')
const output = await job.ffmpeg.readFile('output.mp3')
if (!(output instanceof Uint8Array) || output.byteLength === 0) {
throw new Error('Encoder returned no audio')
}
downloadURL = URL.createObjectURL(new Blob([output], { type: 'audio/mpeg' }))
download.href = downloadURL
download.hidden = false
status.textContent = 'Done. Your MP3 is ready to download.'
} catch {
status.textContent = job.canceled
? 'Encoding canceled.'
: 'Encoding failed. Try a shorter supported audio file and check the encoder assets.'
} finally {
clearTimeout(deadline)
job.ffmpeg.terminate()
active = null
uploader.disabled = false
encodeButton.disabled = false
cancelButton.disabled = true
if (job.canceled) status.textContent = 'Encoding canceled.'
}
}
encodeButton.addEventListener('click', encodeFile)
cancelButton.addEventListener('click', cancelEncoding)
window.addEventListener('pagehide', () => {
cancelEncoding()
clearDownload()
})
window.addEventListener('pageshow', (event) => {
if (event.persisted && active === null) {
status.textContent = 'Choose an audio file to encode again.'
}
})
Os nomes de arquivo virtuais fixos evitam que um nome de arquivo selecionado seja tratado como opção ou caminho do FFmpeg. Um mapeamento de áudio explícito seleciona o primeiro stream de áudio, e um código de saída diferente de zero impede que uma saída parcial seja oferecida como download bem-sucedido. O link do MP3 continua válido até a próxima tentativa de codificação ou até a página ficar oculta.
Carregando o core WebAssembly
O wrapper do FFmpeg inicia um module worker a partir do
vendor/ffmpeg/worker.js copiado. Esse worker importa o core ESM da mesma origem e carrega o
arquivo Wasm dele. Este core de thread única não precisa de um ffmpeg-core.worker.js separado
nem de SharedArrayBuffer. Não substitua pelo @ffmpeg/core-mt sem também implementar os
requisitos adicionais de worker e de isolamento cross-origin dele.
Integrando WebAssembly com JavaScript para recursos de áudio aprimorados
A camada JavaScript gerencia a seleção de arquivos e as URLs de download; o FFmpeg cuida da decodificação e da codificação. Um AudioContext ou AudioWorklet é desnecessário para conversão de arquivos. Se você adicionar efeitos em tempo real ou gravação depois, mantenha esse pipeline de reprodução separado deste fluxo assíncrono de codificação de arquivos.
A referência da API do FFmpeg documenta as operações de arquivo
baseadas em promises, o tempo limite de execução e o terminate(). Um worker novo por
conversão custa tempo de inicialização, mas torna simples o cancelamento e a limpeza dos arquivos
virtuais.
Testando e otimizando o desempenho da codificação de áudio
Codifique um WAV curto, siga o link de download e reproduza o resultado. Depois, experimente um segundo arquivo, cancele durante o carregamento e durante a codificação e selecione um arquivo vazio ou não suportado. Cada tentativa deve devolver os botões ao estado de pronto. Saia da página e volte repetidamente: uma página restaurada deve permitir outra conversão, e um link de download antigo deve continuar oculto.
O limite de entrada de 25 MiB é uma política desta demonstração, não uma garantia de pico de memória. O áudio decodificado e o codificador podem ocupar muito mais memória do que a entrada compactada. O comando tem um tempo limite de codificação de 60 segundos, e o prazo externo de 120 segundos encerra um carregamento ou uma chamada ao worker que tenha travado. Para arquivos maiores, considere o processamento no lado do servidor em vez de aumentar os limites sem medição.
Implantando sua aplicação de áudio web e preparando-a para o futuro
Implante o conteúdo de public/ junto, na raiz do site, via HTTPS. O servidor
Express local serve apenas para desenvolvimento. Garanta que seu host sirva módulos JavaScript e
arquivos .wasm com os tipos MIME corretos e permita module workers da mesma
origem. Mantenha os arquivos de vendor e o código da aplicação versionados juntos e execute
novamente as verificações de conversão ao atualizar uma dependência.
Conclusão
Agora você tem um fluxo local completo de áudio para MP3: um conjunto de pacotes FFmpeg compatível, carregamento via worker, conversão com limites, cancelamento e um link de download persistente. Para fluxos de trabalho que precisam de uploads e processamento no lado do servidor, conheça o serviço de codificação de áudio da Transloadit.
