Freigeben über


Time-Stamped-Ereignisse

Das Gesamtbild des Synthesizer-Timings besteht darin, dass jede Notiz zeitstempelt und in einem Puffer platziert wird, anstatt eine Notiz genau dann zu senden, wenn sie wiedergegeben werden muss. Dieser Puffer wird dann verarbeitet und innerhalb von zwei Millisekunden der Zeit wiedergegeben, die durch den Zeitstempel angegeben wird. (Obwohl die Zeitliche Auflösung in Hunderten von Nanosekunden liegt, werden wir in Millisekunden sprechen, die für diese Diskussion bequemer sind.)

Da die Latenz dem System über die Latenzuhr bekannt ist, können Zeitstempelereignisse in einem Puffer darauf warten, dass sie zu ihrer richtigen Zeit wiedergegeben werden, anstatt nur Ereignisse in eine Warteschlange zu verschieben und zu hoffen, dass die Latenz niedrig ist.

Eine master Uhr implementiert eine COM-IReferenceClock-Schnittstelle (beschrieben in der Microsoft Windows SDK-Dokumentation). Alle Geräte im System verwenden diese Referenzzeit.

Die Wellensenke-Implementierung von Microsoft erstellt einen Thread, der alle 20 Millisekunden aktiviert wird. Die Aufgabe des Threads besteht darin, einen weiteren Puffer zu erstellen und an DirectSound zu übergeben. Um diesen Puffer zu erstellen, ruft er den Synthesizer auf und fordert ihn auf, eine bestimmte Menge an Musikdaten zu rendern. Der erforderliche Betrag wird von der tatsächlichen Aktivierungszeit des Threads bestimmt, die wahrscheinlich genau 20 Millisekunden beträgt.

Was tatsächlich an den Synthesizer übergeben wird, ist einfach ein Zeiger auf den Speicherort im Speicher, an dem mit dem Schreiben von Daten in den PCM-Puffer begonnen werden soll, und ein längenparameter, der angibt, wie viele Daten geschrieben werden sollen. Der Synth kann dann PCM-Daten in diesen Puffer schreiben und bis zur angegebenen Menge ausfüllen. Das heißt, es wird von der Startadresse gerendert, bis er die angegebene Länge erreicht. Dieser Speicherblock kann ein DirectSoundBuffer sein (der Standardfall), aber es kann auch ein DirectShow-Diagramm oder ein anderes Ziel sein, das durch die Wellensenke definiert wird.

Der PCM-Puffer ist konzeptionell zyklisch (das heißt, er wird ständig in Schleifen ausgeführt). Der Synthesizer rendert die 16-Bit-Zahlen, die den Sound beschreiben, in aufeinanderfolgende Slices des Puffers. Die Slicegröße unterscheidet sich bei jedem Aufwachen des Threads geringfügig, da die Senke nicht alle 20 Millisekunden genau reaktiviert werden kann. Jedes Mal, wenn der Thread reaktiviert wird, wird also nachgeholt, um zu bestimmen, wie weit er den Puffer durchlaufen soll, bevor er wieder in den Standbymodus wechselt.

Aus Anwendungssicht verfügt der Synth-Porttreiber selbst über eine IDirectMusicSynth::GetLatencyClock-Funktion , die die Uhr aus der Wellensenke abruft. Es gibt also zwei Uhren:

  • Die master Uhr, auf die jeder, einschließlich der Wellensenke, lauscht.

  • Die Latenzuhr, die von der Wellensenke implementiert wird, die von der Anwendung als DirectMusic-Port betrachtet wird, der die Latenzuhr bereitstellt.

Anders ausgedrückt: Die Anwendung fragt nach der Latenzuhr, sieht die Uhr aber als stammt von der DirectMusic-Portstraktion und nicht als von der Wellensenke.

Die von dieser Latenzuhr zurückgegebene Zeit ist die früheste Zeit, in der der Puffer gerendert werden kann, da der Synth bereits bis zu diesem Zeitpunkt im Puffer gerendert wurde. Wenn der Synthesizer beim letzten Schreibvorgang einen kleineren Puffer gerendert hätte, wäre die Latenz ebenfalls kleiner.

Daher ruft die Wellensenke IDirectMusicSynth::Render auf dem Synth auf, präsentiert den Puffer und fordert an, dass er mit gerenderten Daten gefüllt wird. Wie in der folgenden Abbildung gezeigt, akzeptiert der Synth alle Zeitstempelereignisse, die als Ergebnis von IDirectMusicSynth::P layBuffer-Funktionsaufrufen eingehen.

Diagramm zur Veranschaulichung des Warteschlangenprozesses von Nachrichten mit Zeitstempeln in einem Synthesizer.

Jeder Eingabepuffer enthält Zeitstempelmeldungen. Jede dieser Nachrichten wird in einer Warteschlange platziert, die zu dem durch den Zeitstempel angegebenen Zeitpunkt in einen Puffer gerendert werden soll.

Eines der wichtigen Dinge an diesem Modell ist, dass es keine bestimmte Reihenfolge außer dem Zeitstempel gibt. Diese Ereignisse werden in gestreamt, sodass sie vor dem Rendern jederzeit der Warteschlange hinzugefügt werden können. Alles ist ereignisbasiert in Bezug auf die Zeit. Wenn die Referenzzeit z. B. derzeit bei 400 Zeiteinheiten liegt, geschieht jetzt alles, was zum Zeitpunkt 400 geschehen soll. Ereignisse, die mit einem Zeitstempel von 10 Einheiten ab jetzt auftreten, werden zum Zeitpunkt 410 geschehen.