Müheloses Audio-Encoding im Browser mit WebAssembly
Wandeln Sie eine kurze Audiodatei in eine MP3-Datei um, ohne sie hochzuladen. In diesem Tutorial entwickeln Sie eine kleine Browseranwendung mit FFmpeg.wasm, einem lokalen Server für statische Dateien, einer Abbruchschaltfläche und einem Download-Link.
Einführung in Audio-Encoding im Browser
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 die Eingabe auf 25 MiB und verarbeitet jeweils eine Datei.
Die Rolle von WebAssembly bei der Audioverarbeitung
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 separate
Versionsnummern. Dieselbe Nummer für beide zu installieren ergibt keine gültige Paketkombination.
Vorteile von WebAssembly für Audio-Encoding
- 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 Leistungs- und Speichergrenzen. Testen Sie repräsentative Dateien auf den Geräten, die Sie unterstützen.
Eine einfache Webanwendung für Audio-Encoding einrichten
Voraussetzungen
Verwenden Sie Node.js 24, Yarn 4, eine POSIX-Shell und einen aktuellen Browser mit WebAssembly und
Modul-Workern. Beginnen Sie mit einer kurzen WAV-Datei. Andere Audiocontainer funktionieren nur,
wenn ihr Decoder im FFmpeg-Core der festgelegten Version enthalten ist. Das Attribut
accept der Dateiauswahl erleichtert die Auswahl, validiert sie aber nicht.
Projekt einrichten
Erstellen Sie ein neues Verzeichnis und installieren Sie exakt festgelegte Versionen. Der node-modules-Linker stellt die Paketpfade bereit, die die Befehle zum Kopieren der Dateien verwenden:
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
Behalten Sie das gesamte Verzeichnis ffmpeg bei: 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 erforderlich.
Entwicklungsserver einrichten
Speichern Sie dies als server.ts. Der Server liefert ausschließlich
public/ aus, sodass die Projektdateien nicht zugänglich sind:
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')
})
Nachdem Sie die folgenden Dateien erstellt haben, starten Sie den Server aus dem Projektverzeichnis:
node server.ts
Öffnen Sie http://127.0.0.1:3000. Beenden Sie den Server nach Abschluss mit Strg+C.
FFmpeg.wasm einrichten und verwenden
Speichern Sie dies als 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 public/index.js. Jeder Versuch erhält einen eigenen Worker
und ein eigenes virtuelles Dateisystem. Beim Beenden dieses Workers wird der Speicher für Eingabe,
Ausgabe und Encoder freigegeben, auch nach einer fehlgeschlagenen Konvertierung.
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.'
}
})
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.
Den WebAssembly-Core 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 Datei
ffmpeg-core.worker.js noch SharedArrayBuffer. Ersetzen Sie ihn nicht durch
@ffmpeg/core-mt, ohne auch dessen Anforderungen an zusätzliche Worker und die
Isolation zwischen Ursprüngen zu erfüllen.
WebAssembly mit JavaScript für erweiterte Audiofunktionen integrieren
Die JavaScript-Schicht verwaltet die Dateiauswahl und die Download-URLs; FFmpeg übernimmt das Decodieren und Codieren. Für die Dateikonvertierung ist weder ein AudioContext noch ein AudioWorklet erforderlich. Wenn Sie später Echtzeiteffekte oder eine Aufnahmefunktion ergänzen, halten Sie diese Wiedergabepipeline vom asynchronen Arbeitsablauf zum Datei-Encoding getrennt.
Die FFmpeg-API-Referenz dokumentiert die Dateivorgänge auf Promise-Basis,
das Zeitlimit für die Ausführung und terminate(). Ein neuer Worker pro Job benötigt
Zeit zur Initialisierung, vereinfacht aber den Abbruch und das Bereinigen virtueller Dateien.
Die Leistung beim Audio-Encoding testen und optimieren
Codieren Sie eine kurze WAV-Datei, folgen Sie dem Download-Link und spielen Sie das Ergebnis ab. Probieren Sie anschließend eine zweite Datei aus, brechen Sie während des Ladens und während des Encodings ab und wählen Sie eine leere oder nicht unterstützte Datei aus. Nach jedem Versuch sollten die Schaltflächen wieder einsatzbereit sein. Verlassen Sie die Seite wiederholt und kehren Sie zurück: Eine wiederhergestellte Seite sollte eine weitere Konvertierung ermöglichen, und ein alter Download-Link sollte ausgeblendet bleiben.
Die Eingabegrenze von 25 MiB ist eine Vorgabe für diese Demo und garantiert keinen maximalen Speicherverbrauch. Decodierte Audiodaten und der Encoder können deutlich mehr Arbeitsspeicher beanspruchen als die komprimierte Eingabe. Der Befehl hat ein Encoding-Zeitlimit von 60 Sekunden. Das übergeordnete Zeitlimit von 120 Sekunden beendet einen blockierten Ladevorgang oder Worker-Aufruf. Ziehen Sie für größere Dateien eine serverseitige Verarbeitung in Betracht, statt die Grenzen ohne Messungen zu erhöhen.
Ihre Web-Audioanwendung bereitstellen und zukunftssicher machen
Stellen Sie den Inhalt von public/ gemeinsam im Stammverzeichnis der Website
über HTTPS bereit. Der lokale Express-Server dient nur der Entwicklung. Stellen Sie sicher, dass Ihr
Host JavaScript-Module und Dateien mit der Erweiterung .wasm mit den korrekten
MIME-Typen ausliefert und Modul-Worker desselben Ursprungs zulässt. Versionieren Sie die
vendor-Dateien gemeinsam mit dem Anwendungscode und wiederholen Sie die Konvertierungsprüfungen,
wenn Sie eine Abhängigkeit aktualisieren.
Fazit
Sie haben jetzt einen vollständigen lokalen Ablauf zur Konvertierung von Audio in MP3: eine passende FFmpeg-Paketkombination, das Laden von Workern, eine Konvertierung mit festen Grenzen, eine Abbruchfunktion und einen beständigen Download-Link. Für Arbeitsabläufe, die Uploads und serverseitige Verarbeitung benötigen, entdecken Sie den Audio-Encoding-Service von Transloadit.
