Audio im Browser mit WebAssembly zu MP3 codieren
Wandeln Sie eine kurze Audiodatei in MP3 um, ohne sie hochzuladen. In dieser Anleitung erstellen Sie eine kleine Browseranwendung mit FFmpeg.wasm, einem lokalen statischen Server, einer Abbruchschaltfläche und einem Download-Link.
Beim Encoding im Browser bleiben die ausgewählten Audiodaten auf dem Gerät des Nutzers. Der Browser lädt die Encoder-Dateien herunter, liest die ausgewählte Datei in den Arbeitsspeicher und führt FFmpeg in einem Web Worker aus. Encoding beansprucht dennoch CPU und Arbeitsspeicher. Deshalb begrenzt dieses Beispiel Eingaben auf 25 MiB und verarbeitet jeweils nur eine Datei.
FFmpeg-Pakete auswählen
FFmpeg.wasm stellt FFmpeg als WebAssembly bereit. Wir verwenden die Single-Thread-Variante
@ffmpeg/core@0.12.10 mit @ffmpeg/ffmpeg@0.12.15. Wrapper und Core haben eigene
Versionsnummern. Dieselbe Nummer für beide zu installieren ergibt keine gültige Paketkombination.
- Lokale Verarbeitung: Die Anwendung hat keinen Endpunkt für Audio-Uploads.
- Reaktionsfähigkeit: Ein Worker führt den Encoder außerhalb des Hauptthreads der Oberfläche aus.
- Formatunterstützung: FFmpeg liefert die Decoder und den hier verwendeten MP3-Encoder.
WebAssembly garantiert keine native Encoding-Geschwindigkeit. Die FAQ zu FFmpeg.wasm erläutern die Grenzen bei Leistung und Arbeitsspeicher. Testen Sie repräsentative Dateien auf den Geräten, die Sie unterstützen.
Lokales Projekt einrichten
Voraussetzungen
Verwenden Sie Node.js 24.15 oder neuer innerhalb von 24.x oder 26.5 oder neuer innerhalb von 26.x,
Bash sowie einen Browser mit WebAssembly und Modul-Workern. Die Anleitung wurde unter Linux mit
Node.js 24.15.0, 26.5.0 und 26.8.1, Yarn 4.12.0 sowie Chromium 145 getestet. Dies sind die getesteten
Serverlaufzeitumgebungen, keine Anforderungen des Browser-Encoders. Node führt
server.ts dank
nativer TypeScript-Unterstützung direkt aus;
ein Compiler oder Bundler ist nicht nötig.
Prüfen Sie vor dem Erstellen der Dateien, ob node und
corepack in Ihrem PATH verfügbar sind. Installieren Sie
Corepack separat, falls Ihre Node-Distribution es nicht mitliefert.
Die folgende Einrichtung fordert bei Corepack ausdrücklich Yarn 4.12.0 an.
Beginnen Sie mit einer kurzen, intakten Mono- oder Stereo-WAV-Datei mit 44,1 kHz oder 48 kHz.
Andere Audiocontainer funktionieren nur, wenn ihr Decoder im FFmpeg-Core der festgelegten Version
enthalten ist. Nicht unterstützte Abtastraten oder Kanalanordnungen werden für MP3 möglicherweise
neu abgetastet oder heruntergemischt. Das Attribut accept der Dateiauswahl
dient dem Komfort, nicht der Validierung. Andere Browser und Mobilgeräte müssen separat getestet
werden.
Projekteinrichtung
Fügen Sie diesen Block in Bash ein, und zwar im Verzeichnis, in dem Sie das Projekt erstellen
möchten. Er verweigert die Ausführung, wenn das Verzeichnis webassembly-audio-encoder bereits
existiert, und belässt Ihre Shell im ursprünglichen Verzeichnis, auch wenn die Installation
fehlschlägt. Die untergeordnete Lockdatei erstellt ein separates Yarn-Projekt. Der eigene Name
der Konfigurationsdatei verhindert, dass die üblichen Einstellungen aus
.yarnrc.yml eines übergeordneten Projekts übernommen werden.
(
command -v node >/dev/null &&
command -v corepack >/dev/null &&
mkdir webassembly-audio-encoder &&
cd webassembly-audio-encoder &&
printf '%s\n' \
'{"private":true,"type":"module","packageManager":"yarn@4.12.0",' \
'"dependencies":{"@ffmpeg/ffmpeg":"0.12.15","@ffmpeg/core":"0.12.10","express":"5.1.0"}}' \
> package.json &&
printf '\n' > yarn.lock &&
printf '%s\n' 'nodeLinker: node-modules' 'enableGlobalCache: false' \
> .audio-yarnrc.yml &&
YARN_RC_FILENAME=.audio-yarnrc.yml corepack yarn@4.12.0 install &&
mkdir -p public/vendor/ffmpeg public/vendor/core &&
cp -R node_modules/@ffmpeg/ffmpeg/dist/esm/. public/vendor/ffmpeg/ &&
cp -R node_modules/@ffmpeg/core/dist/esm/. public/vendor/core/
)
Behalten Sie das gesamte Verzeichnis ffmpeg: Seine JavaScript-Module
enthalten den Worker des Wrappers und dessen relative Importe. Beide vendor-Verzeichnisse müssen
aus dieser installierten Paketkombination stammen. Zur Laufzeit besteht keine CDN-Abhängigkeit,
und für diese relativen Browserimporte ist kein Bundler nötig.
Falls die Installation oder das Kopieren nach dem Schreiben der Konfigurationsdateien fehlschlägt, behalten Sie das Verzeichnis, beheben Sie den gemeldeten Fehler und wiederholen Sie die restlichen Schritte aus demselben übergeordneten Verzeichnis:
(
cd webassembly-audio-encoder &&
YARN_RC_FILENAME=.audio-yarnrc.yml corepack yarn@4.12.0 install &&
mkdir -p public/vendor/ffmpeg public/vendor/core &&
cp -R node_modules/@ffmpeg/ffmpeg/dist/esm/. public/vendor/ffmpeg/ &&
cp -R node_modules/@ffmpeg/core/dist/esm/. public/vendor/core/
)
Der erneute Versuch ersetzt die lokal eingebundenen Paketdateien und erhält Ihre Anwendungsdateien.
Verwenden Sie für spätere Yarn-Befehle in diesem Projekt denselben Selektor
YARN_RC_FILENAME.
Entwicklungsserver einrichten
Speichern Sie dies als webassembly-audio-encoder/server.ts. Der Server liefert nur
public/ aus, sodass die Projektdateien nicht zugänglich sind. Port 3000 muss
frei sein. Express 5
übergibt Bindungsfehler an den listen-Callback;
der Code gibt einen Fehler aus und beendet sich mit einem Status ungleich null:
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', (error) => {
if (error) {
console.error(`Cannot start the audio server: ${error.message}`)
process.exitCode = 1
return
}
console.log('Open http://127.0.0.1:3000')
})
Nachdem Sie die folgenden Dateien erstellt haben, starten Sie den Server aus dem übergeordneten Verzeichnis:
(cd webassembly-audio-encoder && node server.ts)
Öffnen Sie http://127.0.0.1:3000. Beenden Sie den Server anschließend mit Strg+C;
Ihre Shell bleibt im übergeordneten Verzeichnis. Dieser Loopback-Server ist für die lokale
Anleitung vorgesehen.
Bedienelemente für den Encoder hinzufügen
Speichern Sie dies als webassembly-audio-encoder/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>
Speichern Sie dies als webassembly-audio-encoder/public/index.js. Jeder Versuch erhält einen eigenen Worker
und ein eigenes virtuelles Dateisystem. Beim Beenden dieses Workers werden seine virtuellen
Dateien und sein Encoder-Zustand verworfen, auch nach einer fehlgeschlagenen Konvertierung.
Der Blob des fertigen Downloads bleibt verfügbar, bis seine URL widerrufen wird.
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
clearDownload()
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
}
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.'
}
})
Die festen virtuellen Dateinamen verhindern, dass ein ausgewählter Dateiname als FFmpeg-Option
oder Pfad behandelt wird. Eine explizite Audiozuordnung wählt den ersten Audiostream aus.
Ein Exit-Code ungleich null verhindert, dass eine unvollständige Ausgabe als erfolgreicher
Download angeboten wird. Der MP3-Link bleibt bis zum nächsten Encoding-Versuch oder bis zum
Ausblenden der Seite gültig. In diesem Befehl wählt -b:a 192k eine konstante
MP3-Bitrate. Die Optionen von libmp3lame
unterscheiden zwischen Bitrate und Qualitätseinstellungen für variable Bitraten.
Passende lokale Dateien laden
Der FFmpeg-Wrapper startet einen Modul-Worker aus der kopierten
Datei vendor/ffmpeg/worker.js. Dieser Worker importiert den ESM-Core vom selben Ursprung
und lädt dessen Wasm-Datei. Dieser Single-Thread-Core benötigt weder eine separate
ffmpeg-core.worker.js noch SharedArrayBuffer. Ersetzen Sie ihn nicht durch
@ffmpeg/core-mt, ohne auch dessen zusätzlichen Worker und die Anforderungen an
Cross-Origin-Isolation umzusetzen.
Nach jeder Konvertierung aufräumen
Die JavaScript-Schicht verwaltet die Dateiauswahl und Download-URLs; FFmpeg übernimmt das Decodieren und Encoding. Für diese Dateikonvertierung ist kein AudioContext oder AudioWorklet nötig.
Die FFmpeg-API-Referenz dokumentiert die Promise-basierten
Dateioperationen, das Zeitlimit für die Ausführung und terminate().
Ein neuer Worker pro Job kostet Initialisierungszeit, vereinfacht aber den Abbruch und das
Aufräumen der virtuellen Dateien.
Eine Aufnahme konvertieren und prüfen
Wählen Sie eine kurze WAV-Datei mit erkennbarem Ton am Anfang und am Ende aus und drücken Sie
Encode audio. Nach Abschluss des Encodings lautet der Status
Done. Your MP3 is ready to download.
Folgen Sie Download MP3.
Öffnen Sie die gespeicherte Datei output.mp3 in einem Audioplayer und hören
Sie sie bis zum Ende an. Dauer, Kanäle und Inhalt sollten der ausgewählten Aufnahme entsprechen.
Probieren Sie als Nächstes eine andere Aufnahme aus, danach eine leere oder nicht unterstützte Datei nach einer erfolgreichen Konvertierung. Der vorherige Download-Link sollte verschwinden, wenn Sie Encode audio drücken. Drücken Sie Cancel sowohl während des Ladens als auch während des Encodings; anschließend sollte Encode audio wieder verfügbar sein. Verlassen Sie die Seite und kehren Sie zurück: Die Seite sollte eine weitere Konvertierung ermöglichen, während der alte Download-Link ausgeblendet bleibt.
Ein erfolgreicher Abschluss des Encoders beweist nicht, dass eine beschädigte Eingabe intakt war. FFmpeg kann Audio aus manchen abgeschnittenen Dateien wiederherstellen und dennoch null zurückgeben. Verwenden Sie intakte Eingaben und prüfen Sie die gesamte heruntergeladene Aufnahme, bevor Sie sich darauf verlassen.
Die Eingabegrenze von 25 MiB ist eine Vorgabe dieser Demo, keine Garantie für den maximalen Speicherbedarf. Decodierte Audiodaten und der Encoder können viel mehr Arbeitsspeicher belegen als die komprimierte Eingabe. Der Befehl hat ein Encoding-Zeitlimit von 60 Sekunden; das äußere Zeitlimit von 120 Sekunden beendet einen festhängenden Ladevorgang oder Worker-Aufruf. Ziehen Sie bei größeren Dateien eine serverseitige Verarbeitung in Betracht, statt die Grenzen ohne Messung anzuheben.
Halten Sie die vendor-Dateien und den Anwendungscode zusammen und wiederholen Sie die Konvertierungsprüfungen, wenn Sie die festgelegten Paketversionen aktualisieren. Dieses Beispiel bietet keinen Endpunkt zum Hochladen oder Speichern; Downloads werden von Ihrem Browser gespeichert.
Für Workflows, die Uploads und serverseitige Verarbeitung benötigen, entdecken Sie den Audio-Encoding-Service von Transloadit.
