Audio-Wellenformen mit Go erstellen: Schritt für Schritt
Erstellen Sie eine PNG-Wellenform aus einer lokalen Audiodatei, indem Sie FFmpeg mit Go ausführen. Go übernimmt die Validierung, den Abbruch und die Bereitstellung der Ausgabe; FFmpeg decodiert die Audiodaten und zeichnet die Wellenform. Dieser Ansatz benötigt keine Go-Pakete von Drittanbietern.
Voraussetzungen
Verwenden Sie Go 1.22 oder neuer und eine FFmpeg-Installation mit PNG-Encoder und showwavespic-Filter. Das Beispiel wurde mit FFmpeg 9.0.1 getestet. MP3, PCM WAV, Vorbis in Ogg und FLAC funktionieren, wenn die entsprechenden Decoder vorhanden sind. Dies ist ein Befehlszeilenwerkzeug für vertrauenswürdige lokale Audiodateien.
Go-Umgebung einrichten
Installieren Sie FFmpeg gemäß der Anleitung für Ihre Plattform auf der FFmpeg-Downloadseite und erstellen Sie anschließend ein Modul:
mkdir waveform-generator
cd waveform-generator
go mod init waveform-generator
go mod edit -go=1.22
ffmpeg -version
Einfache Wellenformen erstellen
Speichern Sie dieses vollständige Programm als main.go.
Es rendert die erste Audiospur als transparentes PNG und mischt dabei ihre Kanäle zu Mono.
Ein bereits vorhandenes Ziel wird nicht überschrieben.
package main
import (
"context"
"flag"
"fmt"
"image/png"
"io"
"os"
"os/exec"
"os/signal"
"path/filepath"
"time"
)
func generateWaveform(ctx context.Context, input, output string, width, height int) error {
if width < 16 || width > 4096 || height < 16 || height > 1024 {
return fmt.Errorf("width must be 16–4096 and height 16–1024")
}
input, err := filepath.Abs(input)
if err != nil {
return err
}
info, err := os.Stat(input)
if err != nil {
return err
}
if !info.Mode().IsRegular() || info.Size() == 0 || info.Size() > 100*1024*1024 {
return fmt.Errorf("input must be a nonempty regular file of at most 100 MiB")
}
temporary, err := os.CreateTemp(filepath.Dir(output), ".waveform-*.png")
if err != nil {
return err
}
defer os.Remove(temporary.Name())
defer temporary.Close()
filter := fmt.Sprintf("[0:a:0]aformat=channel_layouts=mono,showwavespic=s=%dx%d:colors=0x167d9a:scale=sqrt:filter=peak[v]", width, height)
command := exec.CommandContext(ctx, "ffmpeg", "-hide_banner", "-loglevel", "error",
"-nostdin", "-protocol_whitelist", "file,pipe", "-i", input,
"-filter_complex", filter, "-map", "[v]", "-frames:v", "1",
"-c:v", "png", "-f", "image2pipe", "pipe:1")
command.Stdout = temporary
command.Stderr = os.Stderr
command.WaitDelay = 5 * time.Second
if err := command.Run(); err != nil {
return fmt.Errorf("render waveform: %w", err)
}
if err := ctx.Err(); err != nil {
return err
}
if _, err := temporary.Seek(0, io.SeekStart); err != nil {
return err
}
image, err := png.Decode(temporary)
if err != nil {
return fmt.Errorf("invalid waveform PNG: %w", err)
}
if image.Bounds().Dx() != width || image.Bounds().Dy() != height {
return fmt.Errorf("unexpected waveform dimensions")
}
if err := temporary.Close(); err != nil {
return err
}
// Linking publishes the complete file without replacing a concurrent writer's output.
if err := os.Link(temporary.Name(), output); err != nil {
return fmt.Errorf("publish waveform (destination must be new): %w", err)
}
return nil
}
func run() error {
width := flag.Int("width", 1024, "image width in pixels")
height := flag.Int("height", 256, "image height in pixels")
flag.Parse()
if flag.NArg() != 2 {
return fmt.Errorf("usage: waveform-generator [-width 1024] [-height 256] input.wav output.png")
}
interrupted, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
ctx, cancel := context.WithTimeout(interrupted, 2*time.Minute)
defer cancel()
return generateWaveform(ctx, flag.Arg(0), flag.Arg(1), *width, *height)
}
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("Waveform saved")
}
Führen Sie es mit einer eigenen Audiodatei aus:
go run . input.wav waveform.png
Öffnen Sie waveform.png in einem Bildbetrachter. Die Zeit verläuft von links
nach rechts; höhere Spitzen zeigen größere Sample-Amplituden an. Die Quadratwurzelskala macht
leise Abschnitte besser sichtbar.
Erweiterte Funktionen
Große Dateien streamen
Obwohl Go die Ausgabe von FFmpeg direkt auf die Festplatte schreibt, puffert
showwavespic decodierte Audiodaten, um die gesamte Aufnahme abzubilden.
Dies ist kein Streaming-Algorithmus mit konstantem Speicherbedarf. Das Eingabelimit von 100 MiB
und das Zeitlimit von zwei Minuten begrenzen den Arbeitsumfang dieses Tutorials, doch die Größe
der komprimierten Datei begrenzt nicht den Speicherbedarf der decodierten Daten. Rendern Sie bei
langen Aufnahmen kürzere Abschnitte in separaten Aufträgen oder verwenden Sie einen
Wellenformalgorithmus, der Samples schrittweise aggregiert.
Eigene Farben und Gestaltung
Ändern Sie im Filter den festen Wert für colors=0x167d9a, um eine andere
Wellenformfarbe auszuwählen. Verwenden Sie die validierten Optionen für Breite und Höhe,
um die Bildgröße zu ändern:
go run . -width 800 -height 200 input.wav compact-waveform.png
Die Ausgabe hat einen transparenten Hintergrund. Das Mischen zu Mono ergibt eine kompakte Vorschau,
doch gegenphasige Stereosignale können sich auslöschen. Um Kanäle getrennt zu betrachten,
entfernen Sie aformat=channel_layouts=mono, und ergänzen Sie split_channels=1
in den Optionen von showwavespic.
Fehlerbehandlung und Validierung
Das Programm prüft Dateigröße und Dateityp vor dem Decodieren. FFmpeg untersucht die tatsächlichen Medieninhalte; eine Dateiendung allein belegt nicht, dass eine Datei gültige Audiodaten enthält. Fehlende Audiospuren, Fehler beim Decodieren, ein Abbruch und eine ungültige PNG-Ausgabe verhindern jeweils die Bereitstellung. Temporäre Dateien werden bei der Rückkehr aus der Funktion entfernt, auch im Fehlerfall.
Das Zielverzeichnis muss vorhanden sein und Hardlinks unterstützen. Eine vorhandene Ausgabedatei bleibt erhalten, auch wenn sie erst während des Encodings erstellt wurde. Verwenden Sie für jeden Durchlauf einen neuen Dateinamen.
Tests
Erzeugen Sie einen kurzen synthetischen Ton und rendern Sie ihn anschließend:
ffmpeg -nostdin -n -f lavfi -i "sine=frequency=440:duration=2" -c:a pcm_s16le tone.wav
go run . tone.wav tone.png
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height -of json tone.png
Die Prüfung sollte ein PNG mit einer Breite von 1024 und einer Höhe von 256 erkennen. Testen Sie auch mit einer leeren Datei, einer Datei ohne Audiodaten und einem bereits vorhandenen Ziel. Jeder dieser Versuche muss fehlschlagen, ohne vorhandene Dateien zu ersetzen.
Leistung optimieren
Jeder Auftrag startet einen FFmpeg-Prozess. Halten Sie die Parallelität gering, da jeder Prozess seine eigenen decodierten Audiodaten im Speicher hält. Speichern Sie fertige Wellenformen anhand des Audioinhalts und der Rendering-Einstellungen zwischen, wenn wiederholte Anfragen andernfalls dieselbe Aufnahme erneut verarbeiten würden.
In einen Webdienst integrieren
Die Funktion generateWaveform akzeptiert einen Kontext, sodass ein Server das
Zeitlimit einer Anfrage oder eines Auftrags an FFmpeg weitergeben kann. Ein öffentlicher
Upload-Dienst benötigt außerdem ein privates Auftragsverzeichnis, ein Limit für die Upload-Größe,
eine begrenzte Worker-Queue und Speicherlimits für Prozesse, bevor er nicht vertrauenswürdige
Medien decodiert. Geben Sie temporäre Pfade und Decoder-Diagnosen nicht in Antworten an Clients aus.
Ihre Audioanwendungen verbessern
Sie haben jetzt einen Go-Befehl, der eine echte Wellenform rendert, das entstandene Bild validiert und es bereitstellt, ohne frühere Ergebnisse zu überschreiben. Für verwaltete Audioverarbeitung entdecken Sie die Audiodienste von Transloadit.
