====== 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.
→ [[lyx_-_programmiersprache:guides|Guides]] · [[lyx_-_programmiersprache:guides:image|Bildverarbeitung]] · [[lyx_-_programmiersprache:guides:dateisystem-und-io|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 |
**Bekannte Defekte in der aktuellen Fassung** (geprüft mit lyxc 1.0.16F):
* ''MP3Parse'' aus ''std.audio'' liefert **immer** ''AUDIO_ERROR'' (−1). Für MP3 stattdessen ''std.audio.mpg123'' verwenden.
* ''ALSASetFormat'' schlägt mit ''ALSA_ERROR'' fehl, auch bei vorhandener Soundkarte — damit funktioniert **die gesamte Wiedergabe** (''AudioPlayWAV'', ''AudioPlayPCM'', ''PlayWAV'', ''PlayMP3'') derzeit nicht. Analyse, Konvertierung und Datei-Ausgabe sind nicht betroffen.
* ''Mp4Meta'' setzt FPS und Frame-Anzahl **fest auf 0** — diese Felder sind nur bei AVI belastbar.
* ''AviExtractMjpegFrame'' findet die Frames korrekt, scheitert aber am JPEG-Dekoder bei üblichen MJPEG-Strömen. Der rohe Weg über ''AviGetFrameOffset'' funktioniert.
----
===== 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'' ([[lyx_-_programmiersprache:sprache:rohspeicher|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.
''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)''.
----
===== 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 [[lyx_-_programmiersprache:guides:image|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 ''alloc'' anlegen.** Für ''VID_SIZE'', PCM-Daten und OUT-Zeiger ''alloc(n)'' aus ''std.alloc'' verwenden, freigeben mit ''free(ptr, size)''. ''malloc'' arbeitet seit 1.0.17C ebenfalls ohne C-Bibliothek (vorher lieferte es im statischen Binary 0, #1179), braucht dann aber ''free_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)'' und ''AviGetFrameOffset(…, pFrameLen)''. Immer ''alloc(8)'' übergeben (oder ''0'', wo erlaubt) und danach mit ''peek64'' auslesen.
* **Externe Bibliotheken müssen zur Laufzeit da sein.** ''std.audio.alsa'' bindet ''libasound.so.2'', ''std.audio.mpg123'' bindet ''libmpg123''. Fehlen sie, scheitert erst der Aufruf, nicht der Übersetzungslauf.
* **Konstanten vor dem Gebrauch deklarieren.** Wird ein ''con'' erst **nach** der Funktion deklariert, die es benutzt, setzt der Compiler kommentarlos **0** ein — keine Warnung, kein Fehler. Genau das ist die Ursache des ''MP3Parse''-Defekts: die Funktion nutzt ''SEEK_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: alle ''con'' an den Anfang.
* **Amplitude begrenzen.** ''AudioGenTone'' rechnet in 16-Bit-signed; Werte über 32767 kippen um und erzeugen Knacken statt Ton. 8000 entspricht etwa einem Viertel Vollaussteuerung.
----
**Weiterführend:** [[lyx_-_programmiersprache:guides:image|Bildverarbeitung]] · [[lyx_-_programmiersprache:guides:dateisystem-und-io|Dateisystem & I/O]] · [[lyx_-_programmiersprache:units|Standard Library]]