Audio & Video mit Lyx
Lyx bringt Parser und Metadaten-Leser für Audio und Video mit, dazu Anbindungen an libmpg123 (MP3-Dekodierung) und libasound (ALSA-Ausgabe). Es gibt keine Encoder außer WAV: Video kann gelesen, aber nicht geschrieben werden, Audio kann als WAV geschrieben, aber nicht als MP3 kodiert werden.
→ Guides · Bildverarbeitung · Dateisystem & I/O
import std.audio; // WAV/MP3-Header, Ton erzeugen, WAV schreiben, Konvertierung
import std.audio.mpg123; // MP3 → PCM (libmpg123)
import std.audio.alsa; // ALSA-Direktzugriff (libasound)
import std.audio.playback; // Fassade: AudioOpen / AudioPlayWAV / AudioPlayMP3
import std.video.core; // VidMeta-Layout, Format- und Codec-Konstanten
import std.video.mp4; // MP4/ISOBMFF-Leser
import std.video.avi; // AVI-Leser + MJPEG-Frame-Zugriff
Überblick
| Unit | Kann | Braucht externe Bibliothek |
|---|---|---|
std.audio | WAV parsen/schreiben, PCM erzeugen, 8→16 Bit, Stereo→Mono | nein |
std.audio.mpg123 | MP3 dekodieren, Formatabfrage | libmpg123 |
std.audio.alsa | PCM-Gerät öffnen, Format setzen, Puffer ausgeben | libasound.so.2 |
std.audio.playback | PlayWAV / PlayMP3 in einer Zeile | libasound.so.2 (+ libmpg123 für MP3) |
std.video.core | Container erkennen, gemeinsames Metadaten-Layout | nein |
std.video.mp4 | Breite, Höhe, Dauer, Codec aus ISOBMFF | nein |
std.video.avi | Metadaten, Frame-Anzahl, MJPEG-Frames herauslösen | nein |
<WRAP center round important 60%> Bekannte Defekte in der aktuellen Fassung (geprüft mit lyxc 1.0.16F):
MP3Parseausstd.audioliefert immerAUDIO_ERROR(−1). Für MP3 stattdessenstd.audio.mpg123verwenden.ALSASetFormatschlägt mitALSA_ERRORfehl, auch bei vorhandener Soundkarte — damit funktioniert die gesamte Wiedergabe (AudioPlayWAV,AudioPlayPCM,PlayWAV,PlayMP3) derzeit nicht. Analyse, Konvertierung und Datei-Ausgabe sind nicht betroffen.Mp4Metasetzt FPS und Frame-Anzahl fest auf 0 — diese Felder sind nur bei AVI belastbar.AviExtractMjpegFramefindet die Frames korrekt, scheitert aber am JPEG-Dekoder bei üblichen MJPEG-Strömen. Der rohe Weg überAviGetFrameOffsetfunktioniert.
</WRAP>
Audio-Metadaten lesen
Alle Audio-Informationen laufen über ein Struct:
pub type AudioInfo = struct {
format: int64; // AF_PCM8 | AF_PCM16 | AF_MP3
sampleRate: int64;
channels: int64;
bitsPerSample: int64;
dataOffset: int64; // Byte-Offset der Rohdaten in der Datei
dataSize: int64;
durationMs: int64;
};
unit main;
import std.audio;
import std.io;
import std.string;
fn main(): int64 {
var wav: AudioInfo;
if (WAVParse("demo.wav"c, wav) != AUDIO_OK) {
PrintLn("WAV-Parse fehlgeschlagen");
return 1;
}
PrintLn(StrConcat("Samplerate: ", IntToStr(wav.sampleRate)));
PrintLn(StrConcat("Kanaele: ", IntToStr(wav.channels)));
PrintLn(StrConcat("Bits/Sample: ", IntToStr(wav.bitsPerSample)));
PrintLn(StrConcat("Dauer (ms): ", IntToStr(wav.durationMs)));
PrintLn(StrConcat("Datenbytes: ", IntToStr(wav.dataSize)));
return 0;
}
Samplerate: 44100 Kanaele: 2 Bits/Sample: 16 Dauer (ms): 1000 Datenbytes: 176400
Rückgabewerte: AUDIO_OK (0), AUDIO_ERROR (−1), AUDIO_NOT_SUPPORTED (−3). Mit AudioErrorString(code) gibt es einen Klartext.
Ton erzeugen und als WAV speichern
Der einzige vollständige Schreibpfad in Lyx — PCM erzeugen, Datei ablegen, wieder einlesen:
unit main;
import std.audio;
import std.alloc;
import std.io;
import std.string;
fn main(): int64 {
var sizeOut: int64 := alloc(8); // OUT-Parameter für die Bytezahl
// 440 Hz, 500 ms, 44100 Hz, mono, Amplitude 8000 (max. 32767)
var pcm: int64 := AudioGenTone(440, 500, 44100, 1, 8000, sizeOut);
if (pcm == 0) { PrintLn("Ton-Erzeugung fehlgeschlagen"); return 1; }
var bytes: int64 := peek64(sizeOut);
PrintLn(StrConcat("PCM-Bytes: ", IntToStr(bytes)));
if (WAVSaveFile("ton.wav"c, pcm, bytes, 44100, 1, 16) != AUDIO_OK) {
PrintLn("Speichern fehlgeschlagen");
return 1;
}
var info: AudioInfo;
WAVParse("ton.wav"c, info);
PrintLn(StrConcat("Dauer (ms): ", IntToStr(info.durationMs)));
free(sizeOut, 8);
return 0;
}
PCM-Bytes: 44100 Dauer (ms): 500
Ergebnis laut file: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 44100 Hz.
Wer den Header selbst schreiben will (etwa beim Streaming, wenn die Datenlänge erst am Ende feststeht), nimmt WAVWriteHeader(fd, dataSize, sampleRate, channels, bitsPerSample) auf einen offenen Deskriptor.
PCM-Daten aufbereiten
| Funktion | Zweck |
|---|---|
WAVGetBuffer(path, info) | Datei parsen und Rohdaten in einen frisch allozierten Puffer laden |
WAVReadData(path, info) | nur die Daten (info muss bereits gefüllt sein) |
WAVConvert8To16(src, dst, count) | 8-Bit-unsigned → 16-Bit-signed |
WAVStereoToMono(src, dst, frames) | Kanäle mitteln |
var info: AudioInfo;
WAVParse("demo.wav"c, info);
var pcm: int64 := WAVGetBuffer("demo.wav"c, info);
if (pcm == 0) { PrintLn("Laden fehlgeschlagen"); return 1; }
var frames: int64 := info.dataSize / (info.channels * (info.bitsPerSample / 8));
var mono: int64 := alloc(frames * 2); // 16 Bit pro Frame
WAVStereoToMono(pcm, mono, frames);
PrintLn(StrConcat("Frames: ", IntToStr(frames))); // → 44100
Der Zielpuffer wird vom Aufrufer gestellt und ist bei Stereo→Mono halb so groß wie die Quelle.
MP3 dekodieren (std.audio.mpg123)
MP3Parse aus std.audio ist defekt (siehe Kasten oben) — der funktionierende Weg führt über libmpg123:
unit main;
import std.audio.mpg123;
import std.alloc;
import std.io;
import std.string;
fn main(): int64 {
var h: int64 := MP3Init();
if (h == 0) { PrintLn("libmpg123 nicht verfuegbar"); return 1; }
if (MP3OpenFile(h, "tone.mp3"c) != MPG123_OK) { PrintLn("Datei-Fehler"); return 1; }
var rate: int64 := alloc(8);
var ch: int64 := alloc(8);
var enc: int64 := alloc(8);
MP3GetFormatFull(h, rate, ch, enc);
PrintLn(StrConcat(StrConcat("rate=", IntToStr(peek64(rate))),
StrConcat(" ch=", IntToStr(peek64(ch)))));
var buf: int64 := alloc(8192);
var done: int64 := alloc(8); // OUT: tatsächlich dekodierte Bytes
var r: int64 := MP3Read(h, buf, 8192, done); // MPG123_OK | MPG123_DONE | MPG123_NEW_FORMAT
PrintLn(StrConcat("dekodiert: ", IntToStr(peek64(done))));
MP3Close(h);
return 0;
}
rate=44100 ch=1 dekodiert: 8192
Typische Dekodierschleife: MP3Read so lange aufrufen, bis MPG123_DONE (−12) zurückkommt; MPG123_NEW_FORMAT (−3) bedeutet, dass Samplerate oder Kanalzahl mitten im Strom wechseln — dann Format neu abfragen. Die Ausgabe ist MPG123_ENC_SIGNED_16, also 16-Bit-PCM, interleaved, direkt für WAV oder ALSA geeignet.
Wiedergabe (derzeit defekt)
So ist die Wiedergabe gedacht — der Code übersetzt und läuft, ALSASetFormat scheitert aber:
import std.audio.playback;
var h: int64 := AudioOpen("default"c); // 0 = Fehler (kein negativer Wert!)
if (h == 0) { PrintLn("Kein Audiogeraet"); return 1; }
AudioPlayWAV(h, "ton.wav"c); // liefert derzeit AUDIO_ERROR
AudioClose(h);
Kurzformen ohne eigenes Handle: PlayWAV(path) und PlayMP3(path).
Direkter ALSA-Weg (ALSAAudioOpen → ALSASetFormat → ALSAPrepare → ALSAWrite/ALSAWriteGeneric → ALSAClose) hat denselben Defekt: die Format-Stufe meldet ALSA_ERROR (−1), obwohl das Gerät geöffnet werden konnte. Ursache ist die FFI-Signatur von snd_pcm_hw_params_set_rate_near — ALSA erwartet dort einen Zeiger auf die Rate, übergeben wird der Wert.
Zwischenlösung, bis das behoben ist: PCM erzeugen bzw. dekodieren, per WAVSaveFile ablegen und die Datei an einen externen Player übergeben.
Video-Metadaten lesen
Alle Video-Leser schreiben in dasselbe Layout — einen Puffer von VID_SIZE (64 Byte), ausgelesen mit peek64 (Rohspeicher):
| Offset | Feld |
|---|---|
VID_OFF_WIDTH (0) | Breite in Pixeln |
VID_OFF_HEIGHT (8) | Höhe |
VID_OFF_DURATION_MS (16) | Dauer in Millisekunden |
VID_OFF_FPS_NUM (24) / VID_OFF_FPS_DEN (32) | Bildrate als Bruch |
VID_OFF_FRAME_COUNT (40) | Bilder gesamt |
VID_OFF_FORMAT (48) | VID_FMT_AVI / VID_FMT_MP4 / VID_FMT_UNKNOWN |
VID_OFF_CODEC (56) | VID_CODEC_H264, _H265, _MJPEG, _AV1, _MP4V, _VP8, _VP9, _UNKNOWN |
unit main;
import std.video.core;
import std.video.mp4;
import std.video.avi;
import std.alloc;
import std.io;
import std.string;
fn codecName(c: int64): pchar {
if (c == VID_CODEC_H264) { return "H.264"; }
if (c == VID_CODEC_H265) { return "H.265"; }
if (c == VID_CODEC_MJPEG) { return "MJPEG"; }
if (c == VID_CODEC_AV1) { return "AV1"; }
return "unbekannt";
}
fn zeigen(path: pchar): void {
var vid: int64 := alloc(VID_SIZE);
var ok: int64 := 0;
// zweites Argument ist ein OUT-Zeiger für die Dateigröße — 0 = nicht gewünscht
if (Mp4MetaFile(path as int64, 0, vid) != 0) { ok := 1; }
else { if (AviMetaFile(path as int64, 0, vid) != 0) { ok := 1; } }
if (ok == 0) { PrintLn("nicht lesbar"); free(vid, VID_SIZE); return; }
PrintLn(StrConcat(StrConcat(StrConcat(path, ": "),
StrConcat(IntToStr(peek64(vid + VID_OFF_WIDTH)), "x")),
IntToStr(peek64(vid + VID_OFF_HEIGHT))));
PrintLn(StrConcat(" Dauer (ms): ", IntToStr(peek64(vid + VID_OFF_DURATION_MS))));
PrintLn(StrConcat(" Codec: ", codecName(peek64(vid + VID_OFF_CODEC))));
var num: int64 := peek64(vid + VID_OFF_FPS_NUM);
var den: int64 := peek64(vid + VID_OFF_FPS_DEN);
if (den > 0) { PrintLn(StrConcat(" FPS: ", IntToStr(num / den))); }
free(vid, VID_SIZE);
}
fn main(): int64 {
zeigen("demo.mp4");
zeigen("demo.avi");
return 0;
}
demo.mp4: 640x480 Dauer (ms): 2000 Codec: H.264 FPS: 0 ← MP4-Leser liefert keine Bildrate demo.avi: 320x240 Dauer (ms): 2000 Codec: MJPEG FPS: 10
Wer nur wissen will, was eine Datei ist, nimmt VidDetectFormat(buf, len) auf die ersten 12 Bytes: RIFF…AVI → AVI, ftyp bei Offset 4 → MP4.
Zwei Varianten pro Leser: Mp4Meta(buf, len, vid) / AviMeta(buf, len, vid) arbeiten auf einem bereits geladenen Puffer, Mp4MetaFile(path, plenOut, vid) / AviMetaFile(…) laden selbst. Rückgabe 1 = erkannt, 0 = nicht erkannt.
<WRAP center round tip 60%>
plenOut ist ein Ausgabe-Zeiger für die Dateigröße, kein Längen-Eingabewert. Wer dort versehentlich StrLen(path) übergibt, schreibt der Bibliothek einen poke64 auf Adresse 9 — sofortiger Speicherzugriffsfehler. Entweder 0 übergeben oder alloc(8).
</WRAP>
Einzelbilder aus AVI holen
AviGetFrameCount zählt die 00dc-Chunks, AviGetFrameOffset liefert Zeiger und Länge des rohen JPEG-Datenstroms:
unit main;
import std.video.avi;
import std.alloc;
import std.io;
import std.string;
fn main(): int64 {
var size: int64 := FileSize("demo.avi"c as int64);
var buf: int64 := alloc(size);
ReadFile("demo.avi"c as int64, buf, size);
PrintLn(StrConcat("Frames: ", IntToStr(AviGetFrameCount(buf, size))));
var lenOut: int64 := alloc(8);
var frame: int64 := AviGetFrameOffset(buf, size, 3, lenOut); // 4. Frame
if (frame == 0) { PrintLn("Frame nicht gefunden"); return 1; }
var flen: int64 := peek64(lenOut);
PrintLn(StrConcat("Frame 3, Bytes: ", IntToStr(flen)));
FileWriteAll("frame3.jpg"c as int64, frame, flen); // roh herausschreiben
free(lenOut, 8);
free(buf, size);
return 0;
}
Frames: 20 Frame 3, Bytes: 11929
Die erzeugte Datei ist ein gültiges Baseline-JPEG (hier 320×240, 3 Komponenten).
AviExtractMjpegFrame(buf, len, idx, img) würde denselben Frame direkt in ein GrfImage dekodieren; der JPEG-Dekoder aus std.image lehnt gängige MJPEG-Frames aktuell aber ab (Rückgabe 0). Bis das behoben ist: Frame roh herausschreiben wie oben, oder — wenn das Bild im Programm gebraucht wird — den JPEG-Puffer selbst an JpegDecode übergeben und den Rückgabewert prüfen, statt ihn anzunehmen. Siehe Bildverarbeitung.
Entscheidungshilfe
| Aufgabe | Weg |
|---|---|
| „Was ist das für eine Datei?„ | VidDetectFormat (Video), WAVParse-Rückgabe (Audio) |
| Auflösung/Dauer eines Videos | Mp4MetaFile / AviMetaFile |
| Bildrate, Frame-Anzahl | nur AVI belastbar; bei MP4 aus Dauer und eigener Zählung schätzen |
| Vorschaubild erzeugen | AVI: AviGetFrameOffset → JPEG herausschreiben; MP4: derzeit nicht möglich |
| WAV analysieren | WAVParse |
| WAV erzeugen | AudioGenTone bzw. eigenes PCM → WAVSaveFile |
| MP3 lesen | std.audio.mpg123 (nicht MP3Parse) |
| Abspielen | derzeit defekt — Datei ablegen und extern abspielen |
Fallstricke
- Puffer mit
allocanlegen. FürVID_SIZE, PCM-Daten und OUT-Zeigeralloc(n)ausstd.allocverwenden, freigeben mitfree(ptr, size).mallocarbeitet seit 1.0.17C ebenfalls ohne C-Bibliothek (vorher lieferte es im statischen Binary 0, #1179), braucht dann aberfree_mem(ptr)— die beiden Familien nicht mischen. - OUT-Parameter sind Adressen, keine Werte. Betrifft
AudioGenTone(…, sizeOut),MP3Read(…, donePtr),MP3GetFormatFull(h, rateOut, chOut, encOut),Mp4MetaFile(path, plenOut, vid)undAviGetFrameOffset(…, pFrameLen). Immeralloc(8)übergeben (oder0, wo erlaubt) und danach mitpeek64auslesen. - Externe Bibliotheken müssen zur Laufzeit da sein.
std.audio.alsabindetlibasound.so.2,std.audio.mpg123bindetlibmpg123. Fehlen sie, scheitert erst der Aufruf, nicht der Übersetzungslauf. - Konstanten vor dem Gebrauch deklarieren. Wird ein
conerst nach der Funktion deklariert, die es benutzt, setzt der Compiler kommentarlos 0 ein — keine Warnung, kein Fehler. Genau das ist die Ursache desMP3Parse-Defekts: die Funktion nutztSEEK_END, das erst weiter unten in der Unit steht, sucht also ab Dateianfang statt ab Dateiende und ermittelt die Größe 0. Bei eigenen Units: alleconan den Anfang. - Amplitude begrenzen.
AudioGenTonerechnet in 16-Bit-signed; Werte über 32767 kippen um und erzeugen Knacken statt Ton. 8000 entspricht etwa einem Viertel Vollaussteuerung.
Weiterführend: Bildverarbeitung · Dateisystem & I/O · Standard Library
