(1) Warum ist das korrekte Timing bei der Audiowiedergabe so wichtig ? | |
Windows ist event
driven (=Ereignis gesteuert). Ereignisse im System werden einer Anwendung
in Form einer „Message“ (Nachricht) mitgeteilt. Jede Win32-Anwendung hat einen Event-Handler, der die eingehende Ereignisse
verarbeitet. Dieser Event-Handler ist in der Regel in einer Endlosschleife in
der WinMain-Hauptroutine enthalten, die alleine für das Message Handling
verantwortlich ist.
Die Event-Handler-Funktion
muß dabei allerdings nicht für alle Nachrichten eigene Routinen enthalten. Es
genügt auf die für das Programm relevanten Messages zu reagieren. Das zentrale
Windows Messaging System ist das Herz des Kooperationsmodells in Windows.
Die Nachrichten werden nicht direkt an eine Anwendung
geschickt, sondern in Message Queues in einer FIFO-Struktur
zwischengespeichert. Die Anwendung kann sich bei Bedarf dort die Nachrichten
abholen. Andernfalls werden sie von Windows selbst bearbeitet. Es gibt keine
Garantie, in welcher Reihenfolge die Nachrichten ankommen.
Die folgende Grafik gibt einen Überblick über die
Nachrichtenverarbeitung in Standard-Windows-Applikationen.
Quelle: Andre LaMothe, DirectX-tasy (http://www.gamedev.net/reference/articles/article589.asp)
DirectMusic arbeitet ebenfalls mit einem umfangreichen
Message-System. Es kennt dabei zwei verschiedene Arten von Messages:
Standard MIDI Messages |
können nur von MIDI Geräten gelesen werden, enthalten MIDI-Wiedergabedaten |
Performance Messages |
Jede andere Form von Nachrichten über oder mit
Sounddaten |
Die DirectMusic Messages landen nicht im Windows Message
Handling System, sondern verkehren nur intern in DirectX. In der Regel werden
diese Nachrichten automatisch zur Kommunikation zwischen den einzelnen
DirectX-Schichten erzeugt. Eine Windows-Anwendung kann allerdings auch selbst
Messages in die Warteschlange einreihen (z.B. Tempoänderungen). Sie muß sich
dann allerdings auch selbst um die korrekte Adressierung an den zuständigen
Toolgraph des Audiopfades kümmern.
Da die Nachrichten nur intern verwendet werden, benötigt
die Anwendung auch nicht zwingend ein Message-Handling-System.
Je zu spielende Note, können mehrere Nachrichten mit
unterschiedlichem Informationsgehalt versendet werden (z.B. Tonhöhe,
Tempowechsel, Dynamikänderungen, Instrumentenänderungen). Dabei ist allerdings
die Deadline zu beachten. Das ist der späteste Punkt, an welchem die Nachricht
ankommen muß, damit die Note zur richtigen Zeit mit den richtigen Werten
gespielt werden kann (siehe im einzelnen weiter unten). Nachrichten können auch an mehrere Empfänger
geschickt werden. Solche Broadcast- bzw. Multicastnachrichten sind bei globalen
Änderungen wie z.B. einem Tempowechsel notwendig.
Beispiele für Nachrichten:
Grundstruktur aller Messages:
|
|
|
Größe der Struktur |
|
Referenzzeit, zu der die Message abgespielt werden muß |
|
(Relative) Musikzeit, zu der die Message abgespielt werden muß |
|
|
|
Der Empfänger der Nachricht (ein PChannel bzw. spezielle Konstanten für Broadcast- oder Multicastnachrichten) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Spezielle Struktur Tempoänderung:
|
|
|
|
|
|
|
|
Spezielle Struktur Wiedergabe einer Note
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Die Verwaltung und Steuerung des Nachrichtenversandes
übernimmt die Komponente Performance im
DirectX-System. Sie sorgt dafür, dass die Message entsprechend ihrer Deadline
rechtzeitig zum Empfänger abgeschickt wird. So werden z.B. Tempoänderungen
sofort weitergeleitet, während Notenangaben in eine Warteschlange der zu
spielenden Noten eingereiht werden. Es gibt auch Messages, die nur informieren
und sich auf die Soundwiedergabe nicht auswirken (z.B. die Mitteilung einer
Taktartänderung: von 3/4 –Takt auf 4/4-Takt).
Die Messages werden
zunächst an Tools im
Segment-Toolgraph, dann an den Audiopath-Toolgraph oder den
Performance-Toolgraph geschickt. Ein Toolgraph
bezeichnet dabei die Zusammenfassung von mehreren Tools. Das erste Tool in
einem Toolgraph empfängt die Nachricht. Muß die Nachricht weitergeleitet
werden, versieht das Tool sie mit einem Zeiger auf das nächste Tool. Außerdem
bekommt die Nachricht ein Flag, welches beschreibt, wann die Nachricht
weitergeleitet werden soll. Dieses Flag kann von jedem Tool geändert werden.
Folgende Flags gibt es:
Flag-Name |
Inhalt |
IMMEDIATE |
Sofort |
QUEUE |
kurz bevor die Note gespielt werden soll (unter
Brücksichtigung der Latenzzeit) |
ATTIME |
genau zur Deadline-Zeit (so z.B. bei
Notifications) |
Tools verarbeiten Messages in einem Thread mit hoher
Priorität. Dies soll sicherstellen, dass die Nachrichten schnellstmöglich
verarbeitet werden und die Töne nach möglichst kleiner Latenzzeit gespielt
werden können (siehe dazu auch weiter unten). Zeitaufwändige Funktionen wie
z.B. Grafik- oder Datei-I/O sollten daher nicht in einem Tool integriert sein.
Sollen solche Aufgaben dennoch von einem Tool aus gestartet werden, sollte
dieses die Aufgabe per Message an einen anderen Thread (z.B. den Haupt-Thread)
delegieren.
Die Tools werden jeweils durch einen PChannel
(„Performance Channel“) eindeutig identifiziert. Während es im MIDI-Standard nur 16 Kanäle gab, gibt es unter
DirectMusic 65536 Kanalgruppen à jeweils 16 PChannels. Für
Abwärtskompatibilität wird für ein MIDI-Gerät eine Kanalgruppe verwendet.
Jeder Kanal hat eine begrenzte Anzahl von Stimmen
gleichzeitig erklingender Musik. Sollen mehr Noten gespielt werden als Stimmen
vorhanden sind, können Prioritäten für einzelne Noten vergeben werden („Channel
Priority“). Eine Überlastung
des Systems wird damit vermieden.
Jede Nachricht kommt, wenn sie nicht
zwischendurch verworfen wurde, zum Schluß beim Ausgabe-Tool (Output-Tool) an,
welches die Daten ins Midi-Format konvertiert bevor sie an den DirectMusic
Synthesizer geschickt werden. Der Synthesizer erzeugt daraus einen Wavesound
und schickt ihn an eine Senke, welche wiederum die Daten im PCM-Format an die
DirectSound Puffer weiterleitet. Der DirectMusic Synthesizer dient der
Klangerzeugung und kann einen Submix vornehmen. Das heißt, er kann mehrere von
ihm erzeugte Töne schon einmal vorab abmischen, um sie z.B. danach gemeinsam an
einen sekundären Soundpuffer weiterzuleiten, der sie dort mit einem
Spezialeffekt belegt.
Die folgende Grafik zeigt den Fluß der Audiodaten:
Quelle: MS DirectX 8.1 SDK
Von der Message abzugrenzen ist die Notification (Benachrichtigung).
Notifications sind spezielle Messages, welche eine Instanz darüber informieren
sollen, dass ein bestimmtes Ereignis eingetreten ist. Notifications können im Gegensatz zu Messages auch von Anwendungen empfangen
werden. Benachrichtigungen im Bereich Audio sind z.B. wichtig, wenn ein
Musiksegment sich dem Ende zuneigt und ein anderes nachfolgen soll oder wenn
ein Sound auf ein grafisches Ereignis synchronisiert werden muß.
DirectMusic kann damit beauftragt werden, in diesen
Fällen spezielle Benachrichtigungen abzuschicken. Diese werden vor dem Empfang
wie Nachrichten in einer FIFO-Struktur zwischengespeichert. Benachrichtigungen
mit gleichem Zeitstempel werden in nicht definierter Reihenfolge empfangen.
Wird DirectSound mit einem Ringpuffer verwendet (siehe
auch oben), in den die Musikdaten zyklisch hereingeschrieben werden müssen, ist
auch Polling möglich, d.h. das regelmäßige Abfragen, ob z.B. der Abspielcursor bereits einen bestimmten Punkt erreicht
hat. Nachteil des Pollings ist die Ressourcenverschwendung durch das ständige
Abfragen. Benachrichtigungen sind daher in der Regel vorzuziehen.
Die Ereignisüberprüfung und Verwaltung sollte wieder in
einem eigenen Thread stattfinden.
Beispiel für einen solchen Thread mit einer
Endlosschleife:
void WaitForEvent( LPVOID lpv) {
DWORD
dwResult;
DMUS_NOTIFICATION_PMSG* pPmsg;
char
szCount[4];
while
(TRUE) {
dwResult
= WaitForSingleObject(g_hNotify, 100);
while
(S_OK == g_pPerf->GetNotificationPMsg(&pPmsg)) //S_OK signalisiert,
{ //dass noch weitere Nachrichten in Queue sind
// Checke Notification-Typ und reagiere darauf entsprechend
...
g_pPerf->FreePMsg((DMUS_PMSG*)pPmsg);
}
}
}
Wenn mehrere
Signale zeitgleich gespielt werden sollen, dürfen sie nur wenige Millisekunden
auseinander liegen, um noch als gleichzeitig vom menschlichen Ohr wahrgenommen
zu werden. Soundeffekte in einem Spiel sind in der Regel die Reaktion auf
irgendein Ereignis. Um den Sound als Ereignis auf eine Eingabe und gleichzeitig
synchron mit einer Grafik zu hören, darf ein Zeitunterschied von 40ms nicht
überschritten werden. Noch weniger, nämlich nur etwa 10-20 ms darf die Zeit
sein, wenn zwei Soundeffekte gleichzeitig erklingen sollen.
Um ein Geräusch als akustische Reaktion auf ein Ereignis
korrekt wiederzugeben, benötigt man für die Wiedergabe daher ein
Echtzeit-Betriebssystem, während die sonstige Programmabarbeitung weiter in
Nicht-Echtzeit laufen kann. Echtzeit setzt eine möglichst schnelle, fest
definierte Abarbeitungszeit, ein hohes Maß an Einplanbarkeit (Schedulability)
und Systemstabilität auch bei Überlast voraus.[1]
Die folgende Grafik zeigt diese Aufteilung in Echtzeit und
Nicht-Echtzeitbetrieb:
Quelle: Steinmetz, Kapitel Echtzeit
Windows ist kein Echtzeit-Betriebssystem. Es müssen also
genug Vorkehrungen getroffen werden, damit der Klang durch alle Schichten
möglichst zur richtigen Zeit aus dem Lautsprecher kommt.
Um die Einhaltung der Zeit kümmern sich die im folgenden
beschriebenen Komponenten in DirectX.
Der Zeitgebung
liegt zunächst eine Master Clock im Kernel Mode des Systems zugrunde. Hier
handelt es sich um eine Hardware-Zeitgeber, der als Default-Einstellung die
Systemuhr verwendet. Möglich sind aber auch andere Hardwarezeitgeber. Die
Master Clock liefert etwa alle 100 ns ein Referenzsignal in Form eines 64-Bit-Wertes.
Außerdem gibt es die Music Time, welche relativ zum Tempo
mitläuft. Sie startet mit der Performance und erhöht sich defaultmäßig 768 mal
pro Viertelnote.
Die Latenzzeit ist die Zeitverzögerung zwischen Beginn
der Verarbeitung und der Soundausgabe aus den Lautsprecherboxen. Um durch die
vielen Schichten des Windows-Systems durchzugelangen, benötigte Audio bei der
Einführung von DirectX Mitte der 90er Jahre vom Beginn des Abspielen bis zur
Ausgabe über die Lautsprecher 100-200 ms. Bei einer Bildrate von 30 fps würde
der Ton somit etwa 5 Bilder und damit deutlich wahrnehmbar zu spät kommen.
Heutzutage können Latenzzeiten von im günstigsten Fall
wenigen Millisekunden bis zu 100 Millisekunden im Worst Case auftreten.
Aufgrund der Nicht-Echtzeitfähigkeit unter Windows sind die genauen Zeiten
nicht vorhersehbar.
Die Bumper Length ist die Zeitverzögerung zwischen dem
Empfang des Ereignisses und dem Beginn der Verarbeitung des Ereignisses
(Default: 50ms).
Das Performance-Objekt fragt durchgehend die Spuren der
Segmente ab, ob diese Musik bis zu einem angegebenen spätesten Zeitpunkt zu
spielen haben. Ist dies der Fall, senden diese entsprechende Messages, welche
dann in eine Warteschlange gereiht werden.
Ist z.B. die Current Time (aktuelle Zeit) 10.000 ms und
die Prepare Time (Vorbereitungszeit=Latenzzeit + Bumper Length+Queue Time) 1000
ms (Default-Wert), beträgt die End Time 11.000 ms. D.h. alle neuen Messages,
die bis spätestens 11.000 ms gespielt werden müssen, werden an die Warteschlage
gehängt. Im Zeitpunkt 10.850 ms (100ms Latenzzeit + 50ms Bumper Length) muß die
Performance die Message dann in den Soundpuffer stecken.
Die Prepare Time kann verstellt werden. Es empfiehlt sich
allerdings, wenn möglich so weit im voraus zu planen. Dieses ist natürlich bei
Sounds als direkte Reaktion auf Ereignisse nicht möglich. Diese sollen
möglichst schnell gespielt werden und haben daher nicht das Flag
DMUS_SEGF_AFTERPREPARETIME gesetzt.
Die folgende Grafik zeigt den zeitlichen Ablauf bzw. die
Bezeichner der einzelnen Zeiten.
Quelle: MS DirectX 8.0 SDK
Das Abspielen eines Sounds kann je nach gesetztem Flag zu einer bestimmten
Zeit, in einem bestimmen Takt in der Musik, an einer bestimmten Stelle in einem
Takt oder zum frühst möglichen Zeitpunkt passieren. Es ist auch möglich, den
Sound zu einem Zeitpunkt in der Vergangenheit spielen zu lassen. Dies führt
dazu, dass entweder nur noch ein abgeschnittenes Ende des Sounds oder gar
nichts mehr gespielt wird.
Eine Latenzzeit unter 50ms
Wie
eben gezeigt sind Multimedia-Anwendungen sehr zeitkritisch. Viele Prozesse
haben Deadlines, die eingehalten werden müssen. Über welche
Scheduling-Algorithmen kann dies nun am besten erreicht werden ?
Prozesse
in Multimediaanwendungen sollten zunächst die Möglichkeit haben, preemptiv zu
sein. Das heißt, ein Prozess, der Gefahr läuft nicht rechtzeitig dranzukommen,
muß den aktuell laufenden Prozess unterbrechen bzw. vom Scheduler rechtzeitig
Rechenzeit zugeteilt bekommen können.[2]
Um solche Deadlines von Prozessen zu erkennen und zu verwalten ist in einem
Multimedia-Betriebssystem Echtzeit-Scheduling erforderlich. Zwei häufig
verwendete Scheduler-Algorithmen dafür sind:
Hierbei
gibt jeder Prozess an, zu welchem Zeitpunkt er fertig sein muß. Derjenige, der
als erster durch sein muß, kommt auch als erstes dran. Es handelt sich also um
dynamisches Scheduling, da bei jedem hinzukommenden Prozess die
Deadline-Prüfung und die Vergabe von Prioritäten bzw. die Einordnung in die
Abarbeitungswarteschlange vorgenommen werden muß.
Dieser
Vorgang ist recht aufwändig, aber sehr effizient und führt zu einer
vollständigen CPU-Auslastung.
Das Rate
Monotonic Scheduling setzt als Randbedingung voraus, dass die zeitkritischen
Prozesse periodisch sein müssen und die nicht-periodischen Prozesse nicht
zeitkritisch sein dürfen. Dies ist z.B. in der Videobearbeitung / -wiedergabe
/-aufnahme die Regel. Dann können die Planungsentscheidungen bzgl. der
Prioritätenvergabe statisch vor Beginn des Vorganges festgelegt werden. Zur
Laufzeit werden keine Prioritätenänderungen vorgenommen.
Dieses Verfahren
ist zwar recht einfach zu implementieren, setzt aber neben der Randbedingung
große Vorkenntnisse über die zu erwartenden Prozesse und eine genaue Planung
beim Implementieren der Prozesse voraus.
Alle
Windows-Systeme ab Windows 95 arbeiten mit preemptiven Multitasking. Es
arbeiten generell zwei Scheduler in Windows:
-
der primäre Scheduler vergibt ca. alle 20 ms erneut Prioritäten zwischen 0
und 31 für jeden wartenden Thread. Threads, welche keine hohen Prioritäten
haben, werden suspendiert.
-
der Timeslice-Scheduler weist den Threads prozentual Rechenzeit zu.
Die Threads werden nach der Round-Robin-Strategie
bearbeitet.
Welche
speziellen Algorithmen in DirectMusic für das rechtzeitige Bearbeiten der
Musikdaten verwendet werden, gibt Microsoft nicht Preis. Das Performance-Objekt,
welches die Nachrichten verwaltet, richtet sich aber nach der Deadline und
kommt insofern dem Earliest Deadline First Scheduling nahe.
[1] siehe Steinmetz, http://www.et-online.fernuni-hagen.de/lehre/k02415.ws/kap10/10013.htm
[2] siehe auch Tanenbaum, Modern
Operating Systems, S.471