Inhaltsverzeichnis

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):

</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 (ALSAAudioOpenALSASetFormatALSAPrepareALSAWrite/ALSAWriteGenericALSAClose) 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


Weiterführend: Bildverarbeitung · Dateisystem & I/O · Standard Library