====== 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]]