Rückgabewert von cDevice::Poll wird ignoriert

  • Dies ist die Fortsetzung von
    Sinn und Unsinn von "emergency exit"


    Mich stört schon länger, daß DevicePoll(Poller, 10); in dvbplayer.c den Rückgabewert ignoriert.
    Ein Fehler ist es nicht, da ich schon einen Workaround verwende.


    Ich habe extrem große Ausgabepuffer von bis zu 8s.


    cDevice::Poll liefert false (busy/voll) zurück sobald meine Devicepuffer genügend gefüllt wird.
    Da aber der Rückgabewert nie überprüft wird, wird weiter cDevice::PlayVideo, ... aufgerufen.
    Und die Puffer komplett gefüllt.


    Ich habe mir dadurch geholfen, daß ich in meinem cDevice::PlayVideo cDevice::PlayAudio
    noch einmal prüfe ob für beides genug Daten vorhanden sind und dann weiter Pakete
    ablehne.


    Wenn ich mich richtig errinnere war das Problem, daß die Daten in den Puffern bei
    Sprüngen und Richtungswechseln zu Fehlern führte.


    Johns

    Sag mir, wo die Developer sind. Wo sind sie geblieben? . . . . . . . . . . . . . . . . . . . . SoftHdDevice - A software and GPU emulated HD output device plugin.
    Sag mir, wo die Developer sind. Was ist geschehn?


    Client0: Crown CW02 MSI_C847MS-E33 Zotac_GT640_passiv Cine-S2 iMon-MCE / streamdev softhddevice
    Client1: Lian_Li_PC-Q09FB ASRock_H67M-ITX/HT I3-2100 ASUS_ENGT520_passiv / streamdev softhddevice
    Test: Lian_Li_PC-Q09R Asus C60M1-I / streamdev
    Server0: Dockstar TT-S2-3600-USB / streamdev
    Server2: Lian_Li_PC-Q07R Intel_DH61DL G620 WD20EARX 90W PicoPSU Cine-S2+DuoFlex-S2+DuoFlex-CT / streamdev / 22 Watt Verbrauch

  • Dies ist die Fortsetzung von
    Sinn und Unsinn von "emergency exit"


    Mich stört schon länger, daß DevicePoll(Poller, 10); in dvbplayer.c den Rückgabewert ignoriert.
    Ein Fehler ist es nicht, da ich schon einen Workaround verwende.


    Ich würde das nicht als "Workaround" bezeichnen, sondern als die richtige Implementierung (siehe unten).
    Ein Ausgabedevice darf nicht zwingend voraussetzen, daß der Player jemals Poll() aufruft. Ein Player könnte ohne weiteres ständig PlayTs() aufrufen, und das Ausgabedevice liefert halt 0 zurück, wenn sein Puffer voll ist. Wenn der Player nicht zu "busy" werden will, dann kann er Poll aufrufen um Zeitscheiben an andere Threads abzugeben. Er ist aber in keinster Weise verpflichtet, den Rückgabewert des Poll-Aufrufes auszuwerten.


    Quote


    Ich habe extrem große Ausgabepuffer von bis zu 8s.


    Wieso eigentlich? Hat das einen konkreten Grund, daß die Puffer dermaßen groß sind?


    Quote


    cDevice::Poll liefert false (busy/voll) zurück sobald meine Devicepuffer genügend gefüllt wird.
    Da aber der Rückgabewert nie überprüft wird, wird weiter cDevice::PlayVideo, ... aufgerufen.
    Und die Puffer komplett gefüllt.


    Was ist denn daran falsch, die Puffer komplett gefüllt zu halten?


    Quote


    Ich habe mir dadurch geholfen, daß ich in meinem cDevice::PlayVideo cDevice::PlayAudio
    noch einmal prüfe ob für beides genug Daten vorhanden sind und dann weiter Pakete
    ablehne.


    Das ist ja auch dein gutes Recht ;)


    Der Aufruf von DevicePoll(Poller, 10) in cDvbPlayer::Action() dient ja nur dazu, daß die Schleife nicht zu "busy" wird, und jederzeit weitermachen kann, sobald der Ausgabepuffer Daten annehmen kann. Aus der Sicht des Players müsste es sogar erlaubt sein, ständig PlayTs() aufzurufen ohne jemals einen Poll zu machen, denn PlayTs() darf nicht "blockieren".


    Quote


    Wenn ich mich richtig errinnere war das Problem, daß die Daten in den Puffern bei
    Sprüngen und Richtungswechseln zu Fehlern führte.


    Das verstehe ich nicht ganz. Bei Sprüngen und Richtungswechseln werden doch über DeviceClear() die Puffer gelöscht!?


    Klaus


  • Wieso eigentlich? Hat das einen konkreten Grund, daß die Puffer dermaßen groß sind?


    Faulheit.


    Zwischen dem Audio und Video Stream gibt es Teilweise sehr große Zeitabstände.
    Die Zeitstempel für den Ton liegen teilweise bis 1.4s vor dem Bild.
    Habe mich gewundert, aber dvbsnoop zeigt ein ähnlches Bild.
    Dann brauche ich einen Tonpuffer von min 0.3s um Tonaussetzer zu vermeiden.


    Da die Puffer für den Worst Case 8 Kanal bzw. HDTV ausgelegt sind, kommen
    dann bei Stereo und SDTV noch größere Zeiten heraus.


    Quote


    Was ist denn daran falsch, die Puffer komplett gefüllt zu halten?


    Es entstehen Belastungspeaks beim ersten Befüllen der Puffer.
    Was beim Start oder Sprüngen passiert, hier ist das System durch die
    Dekoder auch besonders belastet.


    Quote


    Das verstehe ich nicht ganz. Bei Sprüngen und Richtungswechseln werden doch über DeviceClear() die Puffer gelöscht!?


    Es ist jetzt über ein Jahr her, daß ich diesen Teil geschrieben habe. Ich muß nochmal genau gucken was passiert.
    Ich glaube der Player meint er hätte die ~8s bereits gespielt und überspringt so die Daten die bereits im Puffer sind.
    Durch den Clear werden die ja verworfen. Aber dies ist jetzt nur rumraten, ich werde die Puffer noch etwas erhöhen
    und meine Softlimits entfernen.


    Johns

    Sag mir, wo die Developer sind. Wo sind sie geblieben? . . . . . . . . . . . . . . . . . . . . SoftHdDevice - A software and GPU emulated HD output device plugin.
    Sag mir, wo die Developer sind. Was ist geschehn?


    Client0: Crown CW02 MSI_C847MS-E33 Zotac_GT640_passiv Cine-S2 iMon-MCE / streamdev softhddevice
    Client1: Lian_Li_PC-Q09FB ASRock_H67M-ITX/HT I3-2100 ASUS_ENGT520_passiv / streamdev softhddevice
    Test: Lian_Li_PC-Q09R Asus C60M1-I / streamdev
    Server0: Dockstar TT-S2-3600-USB / streamdev
    Server2: Lian_Li_PC-Q07R Intel_DH61DL G620 WD20EARX 90W PicoPSU Cine-S2+DuoFlex-S2+DuoFlex-CT / streamdev / 22 Watt Verbrauch

  • So ich habe mir das Problem noch einmal angeschaut.


    So wie es aussieht ist nur die Fortschrittsanzeige falsch.
    Wenn man vom normalen Abspielen auf schnellen Rücklauf umschaltet,
    es geht auch Zeitlupe rückwärts. Springt die Anzeige ein paar Sekunden
    zurück, dann läuft die Anzeige ein paar Schritte nach rechts um dann wieder
    zurückzuspringen und dann endlich richtig nach links zulaufen.


    Meine Puffer werden richtig gelöscht und das Ausgabedevice mit den richtigen
    Paketen gefüttert (Zeitstempel). Bis auf 1-2 noch alten Frames werden dann
    die neuen Frames mit den richtigen Zeitstempel angezeigt.


    Anbei ein Patch der das Softlimit im SoftHdDevice Plugin ausschaltet, bitte testen ob es Probleme
    beim Spulen usw. macht.


    Johns

    Files

    Sag mir, wo die Developer sind. Wo sind sie geblieben? . . . . . . . . . . . . . . . . . . . . SoftHdDevice - A software and GPU emulated HD output device plugin.
    Sag mir, wo die Developer sind. Was ist geschehn?


    Client0: Crown CW02 MSI_C847MS-E33 Zotac_GT640_passiv Cine-S2 iMon-MCE / streamdev softhddevice
    Client1: Lian_Li_PC-Q09FB ASRock_H67M-ITX/HT I3-2100 ASUS_ENGT520_passiv / streamdev softhddevice
    Test: Lian_Li_PC-Q09R Asus C60M1-I / streamdev
    Server0: Dockstar TT-S2-3600-USB / streamdev
    Server2: Lian_Li_PC-Q07R Intel_DH61DL G620 WD20EARX 90W PicoPSU Cine-S2+DuoFlex-S2+DuoFlex-CT / streamdev / 22 Watt Verbrauch

  • Es grenzt an Leichenfledderei, diesen Thread nach über 11 Jahren nochmal aufzuwärmen, aber so brauche ich zumindest die Thematik nicht nochmal neu erläutern.

    Ich hätte dazu einige Fragen und hoffe hier insbesondere auf Erleuchtung durch kls :)

    Ist das heute unverändert so, dass der dvbplayer im vdr den Rückgabewert von


    Code
      virtual bool Poll(cPoller &Poller, int TimeoutMs = 0);
           ///< Returns true if the device itself or any of the file handles in
           ///< Poller is ready for further action.
           ///< If TimeoutMs is not zero, the device will wait up to the given number
           ///< of milliseconds before returning in case it can't accept any data.

    ignoriert?


    Was ist, wenn ein Ausgabedevice während der Wiedergabe einer Aufzeichnung in den Funktionen PlayVideo oder Play*Audio 0 returniert, weil seine buffer einen definierten Maximalfüllstand erreicht haben und es signalisieren möchte, dass es keine weiteren Daten annehmen kann? Ist dafür 0 überhaupt richtig, oder sollte -1 returniert werden? Die Beschreibung in device.h ist da nicht ganz eindeutig

    Code
    ...shall process the packet either as a whole (returning
           ///< Length) or not at all (returning 0 or -1 and setting 'errno' accordingly).
           ///< Returns the number of bytes actually taken from Data, or -1
           ///< in case of an error.

    Welchen Unterschied macht es aus Sicht von vdr, welchen Wert die Funktion des Ausgabedevice zurückgibt? Ändert das etwas am weiteren Schicken von Paketen? So wie ich die Aussage

    Quote

    . Aus der Sicht des Players müsste es sogar erlaubt sein, ständig PlayTs() aufzurufen ohne jemals einen Poll zu machen, denn PlayTs() darf nicht "blockieren".

    verstehe, ist das nicht der Fall. Haben denn die Rückgabewerte 0 und/oder -1 irgendwelche Folgen (z.B. Aufruf von Clear oder emergency exit)?


    Ich versuche derzeit ein Problem in softhdodroid zu analysieren. Während einer laufenden Zeitlupe versiegen nach einigen Sekunden die Videodaten - vdr ruft PlayVideo nicht mehr auf. Auf der Videoseite finde ich dafür keine Ursache. An Poll kann es nicht liegen, das scheint tatsächlich ignoriert zu werden, denn es wird noch eine ganze Zeitlang, nachdem Poll 0 (false) zurückliefert, immer noch PlayVideo aufgerufen. Die Buffer laufen weder über noch fordert vdr ein Clear an. Kann es sein, dass vdr die Lieferung von Videodaten stoppt, weil das Ausgabeplugin in Play*Audio 0 zurückgibt? (weil es während der Trickfunktionen so über Mute() die Tonausgabe unterdrückt)

    VDR1: ACT-620, Asus P8B75-M LX, Intel Core i3-3240, 4 GB DDR3 RAM 1600 MHz, passive Geforce GT1030 von MSI, Sandisk 2TB SSD, 2xWinTV DualHD, Atric-IR-Einschalter. SW: Xubuntu 20.04 auf 64GB Sandisk SSD.

    VDR2: Odroid N2+ mit CoreELEC und Ubuntu in chroot, WinTV DualHD

    VDR3: Tanix TX3 mit CoreELEC und Ubuntu in chroot, WinTV DualHD

  • Ist das heute unverändert so, dass der dvbplayer im vdr den Rückgabewert von Poll() ignoriert?

    Offensichtlich:

    Code
            else if (Sleep) {
               cPoller Poller;
               DevicePoll(Poller, 10);
               Sleep = false;
               if (playMode == pmStill || playMode == pmPause)
                  cCondWait::SleepMs(3);
               }

    Was ist, wenn ein Ausgabedevice während der Wiedergabe einer Aufzeichnung in den Funktionen PlayVideo oder Play*Audio 0 returniert, weil seine buffer einen definierten Maximalfüllstand erreicht haben und es signalisieren möchte, dass es keine weiteren Daten annehmen kann?

    Wenn es momentan keine weiteren Daten annehmen kann, soll es 0 zurückliefern. Wenn ein Fehler aufgetreten ist, der den weiteren Ablauf verhindert, dann -1.

    Welchen Unterschied macht es aus Sicht von vdr, welchen Wert die Funktion des Ausgabedevice zurückgibt?

    Du meinst die Poll-Funktion, oder? Keinen (da ja der Returnwert ignoriert wird). Das Polling dient dazu, weitermachen zu können, sobald das Device dazu in der Lage ist - und nicht unnötig zu warten.

    Haben denn die Rückgabewerte 0 und/oder -1 irgendwelche Folgen

    Hier meinst du die Play-Funktionen, oder? Das ist anscheinend nicht wirklich konsequent durchgezogen für negative Werte. So wie's aussieht funktionieren nur Werte >=0 richtig. Bei der Wiedergabe wird hier anscheinend kein Fehler erwartet ;-).

    Kann es sein, dass vdr die Lieferung von Videodaten stoppt, weil das Ausgabeplugin in Play*Audio 0 zurückgibt? (weil es während der Trickfunktionen so über Mute() die Tonausgabe unterdrückt)

    Halte ich für wahrscheinlich. Play*Audio() MUSS die Daten annehmen. Es kann sie im Mute-Fall gerne verwerfen, aber zunächst MUSS sie sie annehmen, sonst passiert genau das, was du beobachtest. Wobei, soweit ich das sehe, bei Zeitlupe gar keine Audiodaten an das Device geschickt werden:

    Code
                        if (!VideoOnly || HasIBPTrickSpeed()) {
                           int w = PlayTsAudio(Data, TS_SIZE);
                           if (w < 0)
                              return Played ? Played : w;
                           if (w == 0)
                              break;
                           Audios.PlayTsAudio(Data, TS_SIZE);
                           }
                        }

    Hast du evtl. cDevice::PlayTs() reimplementiert?

  • Wobei, soweit ich das sehe, bei Zeitlupe gar keine Audiodaten an das Device geschickt werden:

    Code
                        if (!VideoOnly || HasIBPTrickSpeed()) {
                           int w = PlayTsAudio(Data, TS_SIZE);
                           if (w < 0)
                              return Played ? Played : w;
                           if (w == 0)
                              break;
                           Audios.PlayTsAudio(Data, TS_SIZE);
                           }
                        }

    Bin ich an der falschen Stelle, oder steh ich auf dem Schlauch? https://git.tvdr.de/?p=vdr.git…96b9eba1a401;hb=HEAD#l655

    Code
    bool VideoOnly = (dropFrame || playMode != pmPlay && !(playMode == pmSlow && playDir == pdForward)) && DeviceIsPlayingVideo();

    Damit wird doch VideoOnly bei pmSlow forward nicht true, oder? Und damit werden bei Zeitlupe vorwärts schon Daten geschickt?

  • Code
    bool VideoOnly = (dropFrame || playMode != pmPlay && !(playMode == pmSlow && playDir == pdForward)) && DeviceIsPlayingVideo();


    ChatGPT sagt das dazu


    Code
    To summarize, VideoOnly is true if:
    
        dropFrame is true, OR
        playMode is not pmPlay, AND the combination of (playMode == pmSlow && playDir == pdForward) is false.
        And DeviceIsPlayingVideo() must return true.
    
    If DeviceIsPlayingVideo() is false, VideoOnly will be false regardless of the other conditions.


    Gruß,

    Roland

    https://www.minidvblinux.de/

    1x OctopusNet mit 8x DVB-C
    1x Raspberry 4 MLD 6.5 SATIP (softhddevice-drm )

    1x RockPi 4 MLD 6.5 SATIP (softhddevice-drm )

    1x Raspberry 3 mit SATIP MLD 5.4

    1x Raspberry 2 mit STAIPMLD 6.5

    1x Raspberry 1 (staubt gerade so vor sich hin) ;)
    1x ODROID N2+ mit SATIP MLD 6.5

    1x ODROID N2 L mit SATIP MLD 6.5

    1x Zotac CI327 MLD 6.5 SATIP (softhddevice)

  • Ich bekomm da immer einen Knoten im Kopf, aber dann lieg ich wohl richtig.

  • Vielleicht ist das beabsichtigt, damit der Decoder Bild und Ton intern weiter synchron halten kann. So wahrscheinlich der Fall bei av7110 (FF-SD).


    Wenn man in softhd* in void Mute() nicht SkipAudio setzt sondern SetAudioVolume(0) nimmt, könnte es vielleicht gehen - wenn außerdem StreamFreezed beim Wechsel von Pause zu pmSlow auf 0 gesetzt wird. Auf AudioFlushBuffers muss in Mute dann wohl auch verzichtet werden.

    Das probiere ich morgen oder Freitag mal in softhdodroid.

    VDR1: ACT-620, Asus P8B75-M LX, Intel Core i3-3240, 4 GB DDR3 RAM 1600 MHz, passive Geforce GT1030 von MSI, Sandisk 2TB SSD, 2xWinTV DualHD, Atric-IR-Einschalter. SW: Xubuntu 20.04 auf 64GB Sandisk SSD.

    VDR2: Odroid N2+ mit CoreELEC und Ubuntu in chroot, WinTV DualHD

    VDR3: Tanix TX3 mit CoreELEC und Ubuntu in chroot, WinTV DualHD

  • Klingt plausibel...

    Falls es keine Lösung im softhd* gibt, könnte man das Verhalten im VDR auch vom "Können" des decoders abhängig machen und das so ähnlich wie HasIBPTrickspeed beim device abfragen!?

  • So wie es aussieht ist nur die Fortschrittsanzeige falsch.

    Wenn man vom normalen Abspielen auf schnellen Rücklauf umschaltet,
    es geht auch Zeitlupe rückwärts. Springt die Anzeige ein paar Sekunden
    zurück, dann läuft die Anzeige ein paar Schritte nach rechts um dann wieder
    zurückzuspringen und dann endlich richtig nach links zulaufen.

    Das Problem habe ich hier auch. Ist das bei den anderen softhd* auch so?

  • Wenn man in softhd* in void Mute() nicht SkipAudio setzt sondern SetAudioVolume(0) nimmt, könnte es vielleicht gehen - wenn außerdem StreamFreezed beim Wechsel von Pause zu pmSlow auf 0 gesetzt wird. Auf AudioFlushBuffers muss in Mute dann wohl auch verzichtet werden.

    Das probiere ich morgen oder Freitag mal in softhdodroid.

    Habe mir das nochmal angesehen - ist leider noch komplizierter.

    StreamFreezed wird beim Aufruf von Trickspeed bereits wieder auf 0 gesetzt - das passiert also beim Wechsel von Pause zu pmSlow.

    Dass vdr während einer laufenden Zeitlupe irgendwann keine Videopakete mehr schickt, wenn SkipAudio nicht gesetzt ist, liegt also nicht an einem durch StreamFreezed ausgelösten return 0 in PlayAudio. Ich vermute, der Grund liegt hier in PlayAudio:

    Code
        // hard limit buffer full: don't overrun audio buffers on replay
        if (AudioFreeBytes() < AUDIO_MIN_BUFFER_FREE) {
            return 0;
        }
    
        // dont fill audio buffers too much
        if (AudioGetBufferUsedbytes() > AUDIO_MAX_BUFFERS) {
            usleep(10);
            return 0;
        }

    Es gibt noch einen weiteren Aspekt:

    Beim Drücken der Pausentaste wird über Freeze() die Funktion AudioPause() in audio.c aufgerufen. Sie bewirkt AudioPaused, was zu return 1 im AlsaThread führt. Da AudioPaused erst über AudioPlay() auf 0 gesetzt wird (was in Play() geschieht), müsste also während der laufenden Zeitlupe die ganze Zeit AudioPaused = 1 gelten. Warum es dann zu einem Überlaufen der Audio/Alsa-Buffer kommen kann, ist mir allerdings unklar.

    VDR1: ACT-620, Asus P8B75-M LX, Intel Core i3-3240, 4 GB DDR3 RAM 1600 MHz, passive Geforce GT1030 von MSI, Sandisk 2TB SSD, 2xWinTV DualHD, Atric-IR-Einschalter. SW: Xubuntu 20.04 auf 64GB Sandisk SSD.

    VDR2: Odroid N2+ mit CoreELEC und Ubuntu in chroot, WinTV DualHD

    VDR3: Tanix TX3 mit CoreELEC und Ubuntu in chroot, WinTV DualHD

  • Ich vermute, der Grund liegt hier in PlayAudio:

    Code
        // hard limit buffer full: don't overrun audio buffers on replay
        if (AudioFreeBytes() < AUDIO_MIN_BUFFER_FREE) {
            return 0;
        }
    
        // dont fill audio buffers too much
        if (AudioGetBufferUsedbytes() > AUDIO_MAX_BUFFERS) {
            usleep(10);
            return 0;
        }

    Deswegen muss bei jedem neuen Video das Audio vor dem Video weggeschmissen wird (wie in AudioVideoReady()).


    Warum es dann zu einem Überlaufen der Audio/Alsa-Buffer kommen kann, ist mir allerdings unklar

    Bei einer Aufnahme sind alle Audio und Video Daten schon da, und deswegen die Puffer kurz nach Play() voll.

    Wenn man den AudioFlushBuffers() raus nimmt, bleibt der Audiopuffer voll.

    Wenn Zeitlupenstandbilder kommen, muss man das Audio davor wegschmeissen, dann ist wieder etwas Platz im Audiopuffer und er kann wieder etwas Audio annehmen.


    In anderen Worten, so wie die Standbilder rein fliessen, muss auch das Audio verbraucht werden.

    Das ist genauso wie beim normalen Play, nur langsamer.


    Das alte Verhalten macht den Audiopuffer leer, verwirft das eingehende Audio, und dann fehlt Audio und beginnt erst wieder mit den neuen Audiodaten, die aber 3 Sekunden weiter vorne sind.

    Der Fehler liegt also vermutlich NICHT am VDR sondern daran, dass die verworfenen Audiodaten fehlen.

    Ich habe mich in #78 wohl geirrt.

  • Ich bin mittlerweils auch deiner Meinung, jrie


    Meine Gedanken - wobei ich nicht weiß, ob ich es richtig zusammenfasse:

    VDR schickt bei slow forward mit PlayAudio und PlayVideo Daten, wie wenn es ein normales Play wäre.


    PlayAudio() kann dem VDR jetzt mit return size signalisieren, dass die Daten angenommen wurden. VDR schickt dann weitere Daten, die vor dem Video liegen. Video hat aber einen älteren Timestamp, da es ja noch nicht soweit ist und dann dürfte es zu Framedrops bzw. zu diesem 3s Sprung führen.

    PlayAudio() könnte aber auch mit return 0 signalisieren, dass die Daten nicht angenommen wurden. Dann ist der video timestamp irgendwann relativ weit vor audio. Ein Play() füllt jetzt die AudioBuffer mit alten Audio-Daten, die der VDR ja vorher schon los werden wollte. Es laufen aber die Buffer voll, weil PTS in AudioEnque nicht mehr so gesetzt werden kann, dass sich das video synchronisieren kann. Und dann geht gar nichts mehr.


    Ich hoffe, ich habe das einigermaßen richtig verstanden.


    Der Lösungsansatz im Plugin wäre m.E. tatsächlich, bei slow forward wie bei einem Play() alle Audiodaten anzunehmen und bei jedem PlayVideo() die nicht gespielten AudioDaten aus dem AudioBuffer zu verwerfen. Dann wäre man bei einem Play() nach einem slow forward immer synchron. Werden die alten AudioDaten nicht verworfen, läuft der Buffer voll. Wird er dann irgendwann gewaltsam geleert, kommt es zum Sprung.


    Wenn VDR als Player voraussetzt, dass das Device ja wissen muss, welche Audiodaten nicht gebraucht werden, wäre das wohl der Ansatz.

    Auch wenn es in VDR "einfach" lösbar wäre, kann man VDR wohl nicht dafür verantwortlich machen, wenn das Device mit seinen Buffern nicht richtig umgeht. Schließlich buffert ja das Device und nicht VDR. Es ist im Prinzip nur ein langsames Play() - allerdings mit anderer Synchronisation.

Participate now!

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