Radio-Aufnahmen mit VDR-2.8.1

  • Man könnte natürlich damit die Schwäche im Ausgabeplugin beheben, dass es in Drain() true liefert, obwohl noch nicht alles abgespielt ist. Die richtige Stelle wäre dann aber hier:

    ...

    Im Prinzip spricht also nichts dagegen, beim schnellen Rücklauf werden dann zwar die ersten Sekunden immer noch nicht abgespielt, dafür startet die Wiedergabe dann aber ganz von vorne.

    Sag Bescheid, ob das für dich passt, dann mache ich es so.

    Satz 1: Die richtige Stelle weißt du natürlich am besten, so tief bin ich nicht drin im Code. Was meinst du mit "Schwäche beheben"?

    Satz 2: Verstehe ich noch nicht ganz, aber wenn ich schnellen Rücklauf habe und bin schon bei Sekunde 2, würde ich vermutlich nicht unbedingt alles sehen müssen.

    Satz 3: Ich teste das, aber erst später - aktuell ist der VDR produktiv ;)

    Meine VDRs

    (SatIP Server) --- Kathrein Exip 418 ---
    (Server) --- HW: RPI5 --- SW: RPiOs, VDR 2.7.9 mit vtuner-ng, live, epgsearch, markad ---
    (Client 1) --- HW: RPI4 --- SW: VDR*ELEC mit softhddevice-drm-gles ---
    (Client 2) --- HW: Radxa Rock 4B+ - RK3399 --- SW: VDR*ELEC mit softhddevice-drm-gles ---
    (WIP-Clients) --- RPi5, Radxa Rock 4C+ (RK3399T), Tanix TX6, Odroid N2+ --- SW: VDR*ELEC mit softhddevice-drm-gles --

    Edited once, last by rell (August 10, 2026 at 10:23 PM).

  • Was meinst du mit "Schwäche beheben"?

    Anscheinend liefert bei deinem Device Drain() true, obwohl noch nicht alles bin zum ersten Frame wiedergegeben wurde. Daher startet die Wiedergabe nicht ganz am Anfang. Indem readIndex auf -1 gesetzt wird, wird dafür gesorgt, dass danach zumindest die Wiedergabe in Normalgeschwindigkeit ganz am Anfang beginnt.

  • Anscheinend liefert bei deinem Device Drain() true, obwohl noch nicht alles bin zum ersten Frame wiedergegeben wurde. Daher startet die Wiedergabe nicht gaz am Anfang. Indem readIndex auf -1 gesetzt wird, wird dafür gesorgt, dass danach zumindest die Wiedergabe in Normalgeschwindigkeit ganz am Anfang beginnt.

    Hm, ich versuche mal, hier alle Pakete zu verfolgen und schaue, ob und wo da was "verloren" geht.

  • Kleine Korrektur:

    Ansonsten gibt es am Ende der normalen Wiedergabe eine kurze "komische" Anzeige der Restlaufzeit in der Fortschrittsanzeige.

  • Also wenn du das so mit einbauen willst, gerne. Funktioniert. Ich habe auch schon eine Vermutung, was da im Ausgabedevice nicht stimmen könnte. Beim schnellen Rücklauf wird nach jedem Frame mit einem Dekoder-Flush das dekodieren erzwungen. Ein Drain() triggert jetzt auch nochmal einen Dekoder-Flush (was bei normalem Playback Sinn macht). Evtl. kriegt sich da was in die Haare. Auf die Schnelle würde ich sagen, dass man sich bei pdBackward diesen Flush() sparen kann, sondern einfach auf leere Buffer wartet... Teste ich morgen.

  • Ich habe mal meine Pakete verfolgt. Das letzte Paket, das beim Decoder ankommt (was gleichzeitig auch das letzte sein müsste, was bei Trisckspeed von VDR gekommen sein sollte) hat PTS 16:15:07.622. Das wird dann auch angezeigt. Das ist dann auch der PTS, den GetSTC() zurückliefert. Die Aufnahme startet aber mit 16:15:06.422, was der erste PTS sein wird. Meine Vermutung ist, dass Trickspeed einfach nicht zum ersten Frame kommt...

    Hier der "Beweis" als kommentiertes Log von softhddevice-drm-gles (von unten nach oben):

    https://pastebin.com/raw/4trb6iKp

    EDIT: Es war noch ein kleiner Fehler im Ausgabeplugin: Alle Daten, die mit PlayVideo() kommen, werden zuerst in einen reassembly-Puffer gepackt, der eventuelle Teilstücke zu einem Paket zusammenbaut. Das fertige Paket wird dann aber erst an den Stream geschickt, wenn wieder neue Daten mit neuer PTS kommen. Beim Log von oben ist dieses letzte Paket bei einem Drain praktisch im reassembly-buffer verloren gegangen.

    Ich habe es jetzt so geändert, dass die Daten im reassembly-buffer bei einem Drain auch an den Stream geschickt werden. In Ergänzung zum Log von oben habe ich dann noch ein Paket mit PTS 16:15:07.022, das Dekoder und Renderer durchläuft. Das ist aber jetzt wirklich das letzte Paket von VDR ;)

    Das erste Paket bei Streamstart ist 16:15:06.422

    Wenn ich den readIndex = -1 entferne, startet der Stream bei 16:15:07.622 .

    Bei beiden Varianten stimmt es also nicht ganz. Theorie:

    Es sieht für mich so aus, dass der neue Stream immer mit dem vorletzten STC startet. Erklärt sich daurch, dass bei mir verschiedene Threads laufen: Der Renderer nimmt erst das letzte Frame aus der Queue, in der Zwischenzeit wird die Abfrage nach leeren Buffern true und Drain() liefert true zurück. Danach kommt gleich das Clear(). Erst jetzt wird tatsächlich der Pageflip vom renderer durchgeführt und anschließend die neue PTS gesetzt. -> cDvbPlayer frägt diesen PTS nicht mehr ab und verwendet den vorletzten.

    Jetzt stellt sich die Frage, ob VDR den neuen PTS nach dem Drain nochmal neu abfragen sollte oder das DeviceDrain() erst true zurückliefern sollte, wenn der letzte PTS gesetzt ist?

    Ich weiß, etwas kompliziert :)

    Meine VDRs

    (SatIP Server) --- Kathrein Exip 418 ---
    (Server) --- HW: RPI5 --- SW: RPiOs, VDR 2.7.9 mit vtuner-ng, live, epgsearch, markad ---
    (Client 1) --- HW: RPI4 --- SW: VDR*ELEC mit softhddevice-drm-gles ---
    (Client 2) --- HW: Radxa Rock 4B+ - RK3399 --- SW: VDR*ELEC mit softhddevice-drm-gles ---
    (WIP-Clients) --- RPi5, Radxa Rock 4C+ (RK3399T), Tanix TX6, Odroid N2+ --- SW: VDR*ELEC mit softhddevice-drm-gles --

    Edited 2 times, last by rell (August 11, 2026 at 10:41 AM).

  • VDR fragt erst STC ab und dann Drain():

    Code
                 uint32_t Stc = DeviceGetSTC();
                 if (DeviceDrain(100) || StuckAtEof && time(NULL) - StuckAtEof > MAXSTUCKATEOF) {

    Wenn DeviceGetSTC() noch nicht den letzten (bzw. beim schnellen Rücklauf den ersten) STC-Wert geliefert hat, dann sollte DeviceDrain() auch nicht true liefern.

  • Ich habe den Fehler in softhddevice-drm-gles gefunden. Rücklauf zum Anfang und Wiedergabe ganz von vorne funktioniert jetzt. Auch ohne readIndex = -1.

    Mir fällt aber auf, dass am Ende der Aufzeichnung nach DeviceDrain() noch Pakete (ca. 0,6s lang) vom VDR kommen. Können das nicht indizierte Daten sein, bzw. sollte Drain() nicht wirklich erst nach dem allerletzten Paket kommen? Es funktioniert zwar, aber logischer wäre, wenn VDR nach einem Drain() auch wirklich nichts mehr schickt!?

  • Das klappt. Jetzt kommt nach dem Drain() kein neues Frame mehr.

  • Klappt auch. Bei mir bleiben zwar 2 gesendete Pakete am Ende im Niemandsland, aber ich glaube, aus denen kann der Dekoder einfach nichts machen.

  • Ja, ich glaube das passt jetzt. Alle Puffer sind leergeräumt. Mehr geht nicht.

  • Der Patch entspricht dem Stand, den ich gestern am Schluß getestet habe. Läuft also hier.

    Einzig hier:

    Code
    +       ///< The first call puts the device into "draining" state; any call to
    +       ///< Clear(), SetPlayMode() or one of the Play...() functions cancels it.

    Bin ich mir nicht sicher. Macht ein PlayAudio() oder PlayVideo() Sinn, wenn noch nicht alle Buffer geleert sind? M.E. reicht es aus, wenn Clear() das draining cancelt.

    Wenn VDR einen Drain anstösst, will er ja leere Buffer. Wenn es das Device nicht hinkriegt und nicht selbst nach "Erfolg" seinen draining Status zurücksetzt, muss es VDR mit einem Clear machen. Ein Play*() oder SetPlayMode() auf einen unvollständigen Drain() ohne vorheriges Clear() abzusetzen halte ich für falsch.

  • Ich habe den Patch reviewed mit dem vaapivideo. Dort hatte ich den bisherigen Weg implementiert. Den Codeteil kann ich jetzt wieder rausnehmen., der genau die Wiederholung wieder wegfiltert, damit die Audio-Clock bei Radio-Aufnahmen nicht künstlich weiterläuft.

    Ein paar Punkte sind mir beim Durchgehen aufgefallen:

    1. Drain nur bei echtem Stream-Ende

    DeviceDrain() wird aktuell auch gerufen, wenn nur SwitchToPlayFrame gesetzt ist — also beim Übergang Trickmodus → Wiedergabe im Timeshift. Da kommen aber noch Daten. Das Gerät bekommt also "es kommt nichts mehr" gesagt, obwohl es weitergeht, und die 100 ms Wartezeit pro Schleifendurchlauf drosseln zusätzlich den Nachschub. Meldet das Gerät dann "fertig", bricht break die Wiedergabe mitten in der Aufnahme ab. Ich würde das auf echtes EOF eingrenzen:

    Code
    else if (eof && DeviceDrain(10) || StuckAtEof && time(NULL) - StuckAtEof > MAXSTUCKATEOF) {

    2. Pause-Abfrage vor die Stopp-Entscheidung

    In 2.8.2 steht playMode == pmPause bewusst vor allem anderen. Wenn PauseAtLastMark am Aufnahmeende zuschlägt, friert der Code eine Zeile vorher auf pmPause ein und läuft dann trotzdem noch in den eof-Block. Steht die Drain-Abfrage davor, wird bei einem fertig gedrainten Gerät die Wiedergabe beendet statt eingefroren.

    3. Timeout: 10 statt 100 ms

    DeviceDrain() läuft unter LOCK_THREAD von cDvbPlayer::Action(). Pause, Sprung usw. hängen am selben Lock — bei 100 ms pro Durchlauf und einem Drain über mehrere Sekunden merkt man das interaktiv an der Fernbedienung. Über MAXSTUCKATEOF ist ja ohnehin abgesichert, und der Aufruf wiederholt sich sowieso.

    4. Default-Implementierung: Drain() false, Flush() unverändert true

    Das ist der Punkt, den ich am kompliziertesten finde. Aktuell liefert cDevice::Flush() false und Drain() ruft Flush() auf. Damit hängen aber alte Player-Plugins, die noch in einer Schleife auf DeviceFlush() pollen: früher kam bei einem Device ohne eigenes Flush() sofort true, jetzt nie mehr.

    Die beiden Semantiken widersprechen sich auch: Flush()==true heißt "Puffer leer", Drain()==true heißt "alles ausgespielt". Ein altes Device, dessen Puffer leer sind, während im Decoder noch reorderte Frames stecken, würde das Drain() die letzten Bilder abschneiden.

    Mein Vorschlag: Flush() unverändert lassen (true) und Drain() schlicht false zurückgeben, ohne Kompatiblitätslayer. Alte Player + altes Device verhalten sich dann exakt wie bisher, und nicht portierte Devices laufen sauber in den 3-Sekunden-Fallback — also genau das, was hier im Thread beim Test ohne den dropFrame-Mechanismus rauskam.

    5. Zum MUTE_DEPRECATED_FLUSH — das lässt sich ganz vermeiden

    Ich habe mir angeschaut, warum das Makro überhaupt nötig ist, und das ist nicht gut gelöst: cPlayer::DeviceFlush() ist inline im Header definiert und ruft device->Flush(). Wenn Flush() das Attribut trägt, feuert die Warnung in jedem Compileraufruf, der player.h einbindet — nicht nur an echten Aufrufstellen. Das trifft also auch jeden Plugin-Build, selbst wenn das Plugin DeviceFlush() nie anfasst. Deshalb braucht es das Makro (oder ein #pragma).

    • Im Core gibt es kein einziges #pragma GCC diagnostic, und alle vorhandenen [[deprecated]] (recording.h, timers.h, …) funktionieren ohne #pragma— weil der Core seine eigenen deprecated-Funktionen einfach nie aufruft.
    • [[deprecated]] an einer virtuellen Funktion warnt nicht beim Überschreiben, nur beim Aufrufen (habe ich mit GCC gegengeprüft). Ein Device-Plugin, das Flush() implementiert, bekommt vom Attribut also sowieso nichts mit — da hilft nur der Kommentar im Header.

    Deshalb würde ich das Attribut nur an cPlayer::DeviceFlush() hängen und cDevice::Flush() im Header-Kommentar als deprecated markieren (wie z.B. auch in osd.h, libsi/si.h, remux.c). Ergebnis bei mir:

    • player.h einbinden, Device ableiten, Flush() überschreiben, Drain() implementieren → keine Warnung
    • altes Player-Plugin ruft DeviceFlush()genau eine Warnung an der richtigen Stelle
    • Core baut komplett warnungsfrei

    Damit fällt das Makro ersatzlos weg.

    Plugin-seitig habe ich Drain() inzwischen implementiert (hinter #if APIVERSNUM >= 30014) und den Repeat-Filter entsprechend abgeklemmt. Wichtig war für mich der Hinweis von kls, dass Drain() erst true liefern darf, wenn GetSTC() den letzten Wert geliefert hat — bei mir hängt das am noch nicht abgespielten ALSA-Rest, damit stimmt beides automatisch überein.

  • Nicht getestet, aber zork 's Patch bzw. Erläuterungen klingen vernünftig.

    Anmerkungen:

    Mein Vorschlag: Flush() unverändert lassen (true) und Drain() schlicht false zurückgeben, ohne Kompatiblitätslayer.

    Müsste es dann nicht so im Patch sein?

    Code
    +  bool DeviceDrain(int TimeoutMs = 0) { return device ? device->Drain(TimeoutMs) : false; }

    Ob ich für softhddevice-drm-gles das CancelDrain() ausser im DeviceClear() auch noch woanders brauche bin ich mir nachwievor nicht sicher. Wie kann sich denn SetPlayMode() in den Drain() schieben? Das CancelDrain() beim ClearForMediaPlayer() und FlushForSeek() betrifft bei dir ja ausschließlich den mediaplayer, wenn ich es richtig verstehe.

    Damit hängen aber alte Player-Plugins, die noch in einer Schleife auf DeviceFlush() pollen:

    Gibt es denn überhaupt Plugins, die DeviceFlush() aufrufen? VDR selbst nutzt das ja nicht.

    Meine VDRs

    (SatIP Server) --- Kathrein Exip 418 ---
    (Server) --- HW: RPI5 --- SW: RPiOs, VDR 2.7.9 mit vtuner-ng, live, epgsearch, markad ---
    (Client 1) --- HW: RPI4 --- SW: VDR*ELEC mit softhddevice-drm-gles ---
    (Client 2) --- HW: Radxa Rock 4B+ - RK3399 --- SW: VDR*ELEC mit softhddevice-drm-gles ---
    (WIP-Clients) --- RPi5, Radxa Rock 4C+ (RK3399T), Tanix TX6, Odroid N2+ --- SW: VDR*ELEC mit softhddevice-drm-gles --

    Edited once, last by rell (August 13, 2026 at 10:08 AM).

  • Müsste es dann nicht so im Patch sein?

    Code
    +  bool DeviceDrain(int TimeoutMs = 0) { return device ? device->Drain(TimeoutMs) : false; }

    DeviceDrain() greift nur, wenn kein Device am Player hängt, deswegen ein festes true.

    Ob ich für softhddevice-drm-gles das CancelDrain() ausser im DeviceClear() auch noch woanders brauche bin ich mir nachwievor nicht sicher. Wie kann sich denn SetPlayMode() in den Drain() schieben? Das CancelDrain() beim ClearForMediaPlayer() und FlushForSeek() betrifft bei dir ja ausschließlich den mediaplayer, wenn ich es richtig verstehe.

    Richtig: SetPlayMode() schiebt sich gar nicht in einen laufenden Drain() — beim Start läuft der Player-Thread noch nicht, beim Ende ist er schon gestoppt.

    Es geht um das Cleanup zwischen zwei Wiedergaben. Mein Drain() flusht den Codec-Reorder-Puffer genau einmal pro Durchgang, und dieses "einmal" ist ein Flag, was ich mir merke (drainEosRequested = true). Bleibt es nach Wiedergabe 1 gesetzt, überspringt Wiedergabe 2 den Flush und verliert das Ende. Ein Clear() gibt es beim Start einer Wiedergabe aber nicht garantiert — deshalb SetPlayMode() als verlässlicher Reset.

    Empfehlung also: zurücksetzen überall dort, wo du gepufferte Daten wegwirfst oder dir eine einmalige Aktion merkst. Ist dein m_draining nur ein Status-Flag und wirfst du Puffer nur in Clear() weg, reicht bei dir Clear() + SetPlayMode().

    ClearForMediaPlayer() und FlushForSeek() sind meine eigenen Mediaplayer-Pfade, die Puffer an Clear() vorbei wegwerfen.

    Gibt es denn überhaupt Plugins, die DeviceFlush() aufrufen? VDR selbst nutzt das ja nicht.

    Mal geprüft: softhddevice, softhddevice-ua0lnj, softhdcuvid, vaapidevice und vnsiserver rufen DeviceFlush() nicht auf. Die ersten vier implementieren aber Flush():

    Code
    int Flush(int timeout) {
        if (atomic_read(&MyVideoStream->PacketsFilled)) {
            if (timeout) usleep(timeout * 1000);
            return !atomic_read(&MyVideoStream->PacketsFilled);
        }
        return 1;
    }

    Damit würde das mit allen Plugins funktionieren, jeweils mit dem 3-Sekunden-Fallback. jojo61 müsste dann auch Drain() implementieren.

    Aufgrund der neuen Erkenntnisse ist hier eine neue, vereinfachte Version. Gebaut und kurz getestet.

    Files

    System 1: Hardware : MSI PRO B760M-B, Intel Core i3-13100, DVBSky S952 V3, 1x 2TB NVMe, Gehäuse Antec Remote Fusion Black, 16 GB DDR4 RAM.
    Software : Fedora 44, vdr 2.8.2, vaapivideo 1.8.3, Kernel 7.2.4
    System 2: Hardware : Intel NUC10i5FNK, Intel Core i5-10210U (Comet Lake), DVB TechnoTrend TT-connect S2-4600 USB, 1x 1TB NVMe, 32 GB DDR4 RAM.
    Software : Fedora 44, vdr 2.8.2, vaapivideo 1.8.3, Kernel 7.2.4

Participate now!

Don’t have an account yet? Register yourself now and be a part of our community!