Samstag, 11. Februar 2012


Artikel

März 2003 | Artikel

Der Ton macht die Musik

(Link zum Artikel: http://www.entwickler.de/dotnet//000319)

DirectX9 - Audiowiedergabe mit DirectSound in Visual Basic .NET

Text: von Jens Konerow
  • Teilen
  • kommentieren
  • empfehlen
  • Bookmark and Share
Mit der neunten Version von Microsofts Multimediaschnittstelle DirectX hält auch die Managed-Variante ihren Einzug, um eine nahtlose Verknüpfung zu .NET herzustellen. DirectSound ist seit langem ein fester Bestandteil von DirectX und kümmert sich um die 2D- sowie um die 3D-Audiowiedergabe. Mit Hilfe des objektorientierten Programmiermodells wird damit jede Soundausgabe fast zum Kinderspiel.

Mittlerweile genießt DirectSound einen festen Platz in der Multimediaprogrammierung und dort vor allem im Spielebereich. Dennoch eignet sich der Einsatz dieser Schnittstelle auch für jede andere Applikation, bei der den Lautsprechern hin und wieder ein Ton entweichen soll, um den Benutzer beispielsweise auf eine eingetroffene Nachricht aufmerksam zu machen. Die Implementierung von DirectSound ist dabei denkbar einfach. Zunächst muss immer ein so genanntes Device erzeugt werden, damit wir Zugriff auf das Ausgabegerät bekommen. Welche Rechte die Anwendung auf die Audiohardware besitzen soll, ist über den CooperativeLevel festzulegen. Anschließend organisieren wir die Audiodaten in so genannten Soundbuffern, bei denen zwischen dem primären und dem sekundären Soundbuffer unterschieden wird. Letzterer lässt sich nochmals in einen Static- sowie einen Streaming-Buffer unterteilen. Der primäre Soundbuffer gehört generell den Streaming-Buffern an, denn dieser mischt alle auszugebenen Sounds und leitete daraufhin die Daten an die Soundkarte weiter. Sie können lediglich einen primären Buffer erstellen, die Anzahl der sekundären Buffer hingegen ist nur durch den physikalischen Speicher des Systems begrenzt. Eine kleine Ausnahme pictureet hierbei der so genannte Capture Buffer. Es handelt sich zwar um einen Streaming-Buffer, dennoch dient dieser nicht etwa der Ausgabe, sondern dem Aufnehmen von Audiodaten, wie es der Name schon vermuten lässt. Auf die einzelnen Buffer können zusätzlich noch diverse Filter angewandt werden, um beispielsweise einen Echo- oder Chorus-Effekt zu erzeugen.

Erzeugen eines sekundären Soundbuffers
Wir wollen uns nicht lange mit trockener Theorie aufhalten, sondern gleich zur Tat schreiten und uns den Sachverhalt an einem praktischen Beispiel ansehen. Aus diesem Grund werden im Laufe dieses Abschnitts die einzelnen Code-Fragmente in den Raum geworfen und anschließend erklärt. Doch nun zu unserem Beispiel. Fügen Sie einen neuen Verweis auf die Bibliothek Microsoft.DirectX.DirectSound.dll und importieren Sie anschließend den gleichnamigen Namensraum, damit sich eine neue Instanz der Klasse Device erstellen lässt:
  1. Dim DSDevice As New Device()
Zusätzlich kann dem Konstruktor der Device-Klasse ein GUID, sprich eine eindeutige Kennnummer eines Geräts, übergeben werden. Andernfalls kommt das Standardgerät zum Einsatz. Nachdem das Objekt initialisiert worden ist, müssen die Rechte auf die Audiohardware festgelegt werden:
  1. DSDevice.SetCooperativeLevel(Me, CooperativeLevel.Priority)
Die Syntax der SetCooperativeLevel()-Methode verlangt nach zwei Argumenten: Zum einen nach einem Objekt vom Typ Control und zum anderen nach einem Wert der CooperativeLevel-Enumeration (siehe Tabelle 1), welcher die Rechte bestimmt.

Name der Konstante Beschreibung
Normal Festes Format des primären Soundbuffers (22 kHz, Stereo, 8-Bit)
Priority Anwendung besitzt die ersten Rechte auf die Audiohardware, Format des primären Soundbuffers ist veränderbar oder auch Hardwaremixing wird möglich.
Write-primary Anwendung kann direkt in den primären Soundbuffer schreiben.
 
  Nun kann die SecondaryBuffer-Klasse instanziiert werden, wobei aufgrund des überladenen Konstruktors mehrere Möglichkeiten bei der Wahl der Argumente bestehen. Für das erste Beispiel genügt die Übergabe eines Dateipfads sowie des Device-Objekts. DirectSound lädt die angegebene Audiodaten automatisch in den Buffer:
  1. Dim DSSoundBuffer As SecondaryBuffer
  2. DSSoundBuffer = New SecondaryBuffer("c:\Beispiel.wav", DSDevice)
Somit ist die Anwendung bereit, mittels der Methode Play() des SecondaryBuffer-Objekts die Wiedergabe zu starten. Folgendermaßen ist die genannte Methode deklariert:
  1. Public Sub Play(ByVal priority As Integer, ByVal flags As BufferPlayFlags)
Der Parameter priority ist für das einfache Abspielen eines Buffers nicht von Relevanz. Hingegen ist letzterer schon interessanter, denn durch Setzen eines entsprechenden Flags lässt sich der Soundbuffer wahlweise einmalig (BufferPlayFlags.Default-Flag) oder in einer Endlosschleife (BufferPlayFlags.Looping-Flag) abspielen. Über den Befehl Stop() können Sie die Wiedergabe wieder dementsprechend anhalten. Zuletzt sei noch kurz die Status-Eigenschaft eines SecondaryBuffer-Objekts erwähnt, über welche sich beispielsweise in Erfahrung bringen lässt, ob im Moment eine Wiedergabe stattfindet oder nicht.
Soundbuffer-Eigenschaften manipulieren
Bisher haben wir uns auf das Wesentliche beschränkt, der Wiedergabe eines Sounds. Meist ist jedoch erwünscht, dass dem Benutzer Optionen wie die Lautstärke- oder Panorama-Regelung zur Verfügung stehen. Um solche Einstellungen vornehmen zu können, müssen diese zunächst freigeschaltet werden. Dies geschieht über eine Buffer-Beschreibung vom Typ BufferDescription. Entweder setzten Sie entsprechende Flags in der Flags-Eigenschaft desselben Objekts oder Sie nutzen stattdessen die booleschen Eigenschaften wie beispielsweise ControlVolume oder ControlFrequency und weisen diesen den Wert True zu. Tabelle 2 gibt eine Übersicht über die wichtigsten Flags.

Flag Beschreibung
ControlVolume Gibt die Volume-Eigenschaft des SecondaryBuffer-Objekts frei.
ControlPan Gibt die Pan-Eigenschaft des Buffers frei (Balance-Regelung).
ControlFrequency Gibt die Frequency-Eigenschaft frei.
ControlEffects Erlaubt das Anwenden von Effekten auf einen Soundbuffer.
StickyFocus Buffer fährt mit der Wiedergabe selbst dann fort, wenn die Anwendung den Focus verliert, außer wenn diese Anwendung DirectSound verwendet.
GlobalFocus Entspricht StickyFocus, gibt jedoch die Sounds selbst dann wieder, wenn die andere Anwendung, welche den Focus besitzt, DirectSound verwendet.
 
  Erstellen Sie einen Soundbuffer, wird dieser automatisch mit der höchsten Lautstärke geladen. Der gültige Wertebereich reicht von -10.000 bis 0. Bei der Panoramaregelung befinden sich alle gültigen Werte zwischen -10.000 und 10.000. Gültige Frequenzen siedeln sich zwischen 100 und 100.000 an. Sehen wir uns zur besseren Anschauung ein kleines Code-Fragment an:
  1. Dim DSSoundBuffer As SecondaryBuffer<br></br>Dim DSBufferDesc As New BufferDescription()
  2. DSBufferDesc.ControlPan = True
  3. DSSoundBuffer = New SecondaryBuffer("c:\Beispiel.wav", DSBufferDesc, DSDevice)
  4. DSSoundBuffer.Pan = 10000
Verfügbare Devices auflisten
Um dem Benutzer ein Auswahl eines Devices aus einer Liste zu ermöglichen, gibt Ihnen DirectSound die Möglichkeit, über ein entsprechendes DevicesCollection- bzw. CaptureDevicesCollection-Objekt alle auf dem System vorhandenen Geräte abzurufen. Alle Elemente der Collection sind vom Typ DeviceInformation, welcher unter anderem eine Beschreibung sowie eine eindeutige Kennnummer des Geräts liefert. Im folgenden Quellcode werden alle Devices zur Ausgabe von Audiodaten in einer Combo-Box aufgelistet:
  1. Dim DSDeviceEnum As DevicesCollection
  2. Dim DeviceInfo As New DeviceInformation()
  3. DSDeviceEnum = New DevicesCollection()
  4. For Each DeviceInfo In DSDeviceEnum
  5. cboDevice.Items.Add(DeviceInfo.Description)
  6. Next
Effektfilter anwenden
DirectSound bietet einige standardmäßige Effekte, die auf einen Soundbuffer angewandt werden können, um zum Beispiel ein Echo zu erzeugen. Folgende Effekte bietet DirectSound Ihnen an:
  • Chorus
  • Compression
  • Distortion
  • Echo
  • Environmental Reverberation
  • Flange
  • Gargle
  • Parametric Equalizer
  • Waves Reverberation
Jeder dieser Effekte besitzt einige Eigenschaften, mit denen sich der Effekt manipulieren lässt. Darum soll es jedoch erst im nächsten Abschnitt gehen. Zunächst wollen wir uns die Standardeffekte zu Nutze machen. Um dieses Feature frei zu schalten, setzten wir das ControlEffects-Flag in der Buffer-Beschreibung. Anschließend instanziieren wir die Klasse EffectDescription, um definieren zu können, welcher Filter gewollt ist. In der DSoundHelper-Klasse stehen einige shared Felder zur Verfügung, die einen GUID zurückgeben, durch den ein Standard-Effekt definiert ist. Weisen Sie diese Kennnummer der GuidEffectClass-Eigenschaft eines EffectDescription-Objekts zu. Zum Abschluss müssen die Effekte noch auf den Soundbuffer angewandt werden. Übergeben Sie dafür der SetEffects()-Methode einer Instanz der Klasse SecondaryBuffer einen Array mit allen EffectDescription-Objekten. Wollen Sie stattdessen wieder alle Filter entfernen, übergeben Sie einfach Nothing.
  1. Dim DSDevice As New Device()
  2. Dim DSSoundbuffer As SecondaryBuffer
  3. Dim DSBufferDesc As New BufferDescription()
  4. Dim DSEffectDesc() As EffectDescription = {New EffectDescription()}
  5. DSDevice.SetCooperativeLevel(Me, CooperativeLevel.Priority)
  6. DSBufferDesc.ControlEffects = True
  7. DSSoundbuffer = New SecondaryBuffer("c:\Beispiel.wav", DSBufferDesc, DSDevice)
  8. DSEffectDesc(0).GuidEffectClass = DSoundHelper.StandardEchoGuid
  9. DSSoundbuffer.SetEffects(DSEffectDesc)
Durch den obigen Quelltext wurde ein Soundbuffer erzeugt, auf dem anschließend ein Echo-Effekt angewandt wurde. Als Ergebnis liefert die SetEffects()-Methode zusätzlich einen Wert aus der EffectsReturnValue-Enumeration, welcher angibt, ob ein Fehler aufgetreten ist oder nicht. Mit einem Aufruf der Play()-Methode ließe sich der Buffer dann wie gewohnt abspielen.
Effekte manipulieren
DirectSound beschränkt sich nicht nur auf die Bereitstellung vordefinierter Effekte, sondern räumt Ihnen auch die Möglichkeit ein, diese zu manipulieren. Jeder Effekt kann dabei unterschiedliche Eigenschaften besitzen. Die Vorgehensweise ist dabei folgende. Zunächst rufen wir einen oder mehrere auf den Buffer angewandte Effekte über die GetEffects()-Methode ab. Wahlweise kann der überladenen Methode ein Integer-Wert übergeben werden, welcher dem Index eines Elements entspricht oder zwei Integer-Werte, welche zum einen als Index und zum anderen als Anzahl der abzurufenden Elemente fungieren. Je nachdem welches Element Sie zurückgeben lassen bzw. welches Element des zurückgegebenen Arrays Sie gerade ansprechen, entspricht der Typ beispielsweise der ChorusEffect- oder der FlangerEffect-Klasse. Betrachten wir ein kleines Beispiel:
  1. Dim FlangerE As FlangerEffect
  2. FlangerE = DSSoundbuffer.GetEffects(0)
Sämtliche relevanten Eigenschaften eines Effekts sind in einem weiteren Objekt gespeichert, das sich beispielsweise über die AllParameter-Eigenschaft der obigen Instanz abrufen lässt. Bei einem Flanger-Effekt hieße die entsprechende Klasse dann EffectsFlanger. Folgendes Code-Fragment ruft ein solches Objekt ab, ändert zwei Eigenschaftswerte und übergibt die Änderungen anschließend wieder:
  1. Dim FlangerParams As EffectsFlanger
  2. FlangerParams = DSSoundbuffer.GetEffects(0).AllParameters
  3. FlangerParams.Delay = 3
  4. FlangerParams.Feedback = 0
  5. DSSoundbuffer.GetEffects(0).AllParameters = FlangerParams
Auf der Heft-CD finden Sie eine zusätzliche HTML-Datei, in der alle Eigenschaften eines jeden Effekts mit dessen zulässigen Minimal-, Maximal- sowie dessen Default-Wert aufgelistet sind. Natürlich lassen sich die zulässigen Werte auch programmtechnisch ermitteln:
  1. Trackbar1.Minimum = FlangerE.DelayMin
  2. Trackbar1.Maximum = FlangerE.DelayMax
  3. Trackbar1.Value = FlangerParams.Delay
3D-Audiowiedergabe
Bisher haben wir uns immer auf eine zweidimensionale Ausgabe beschränkt. DirectSound bietet jedoch noch die dritte Dimension, welche um einiges interessanter ist. Microsoft hat dabei versucht, alle Erscheinungen aus der Wirklichkeit auch in DirectSound zu implementieren. Als Beispiel sei hier der Doppler-Effekt genannt, durch den es zu höheren Tönen kommt, wenn sich uns z.B. ein Polizeiwagen mit Sirene nähert. Nachdem dieser an uns vorbeigefahren ist, werden die Töne wieder tiefer. In DirectSound gibt es grob gesagt zwei Arten von Objekten: zum einen die Soundquelle und zum anderen den Zuhörer (Listener). Wie Audiodaten schließlich auszugeben sind, ergibt sich aus einer Vielzahl von Faktoren. Mitunter sind hier die Position, die Geschwindigkeit oder auch die Ausrichtung der Soundquelle sowie des Listeners zu nennen.
Einen 3D-Buffer erstellen
Zunächst verfahren wir beim Erstellen eines 3D-Buffers genauso wie bei einem Soundbuffer für eine zweidimensionale Ausgabe, außer dass wir zusätzlich das Control3D-Flag in der Buffer-Beschreibung setzen müssen. Anschließend kann der Buffer erstellt und mit Audiodaten gefüllt werden. Doch beachten Sie, dass DirectSound bei einem 3D-Buffer keine Stereo-Sounds unterstützt, denn in der Realität gibt es auch keine solchen Geräusche. Ein Stereo-Klangpicture ergibt sich immer aus einer Vielzahl von Geräuschen, die allesamt mono sind. Ist das SecondaryBuffer-Objekt initialisiert, übergeben wir dieses dem Konstruktor der Buffer3D-Klasse:
  1. DSBufferDesc.Control3D = True
  2. DSSoundbuffer = New SecondaryBuffer("C:\beispiel.wav", DSBufferDesc, DSDevice)
  3. DS3DBuffer = New Buffer3D(DSSoundbuffer)
Nun lässt sich über die Mode-Eigenschaft des eben neu erzeugten Objekts bestimmen, welcher Processing Mode zur Anwendung kommen soll. Setzten Sie dafür einen der drei Werte aus der Mode3D-Enumeration (siehe Tabelle 3).

Name der Konstante Bedeutung
Disabled Die 3D-Audioausgabe wird für diesen Buffer deaktiviert. Die Quelle ist beim Listener zentriert.
HeadRelative Der Buffer ist relativ zum Listener ausgerichtet und wird automatisch neu positioniert, wenn der Listener seine Position, Geschwindigkeit oder Ausrichtung ändert.
Normal Die Position und Ausrichtung der Soundquelle ist absolut.
 
  Mit Hilfe dieser Eigenschaft bestimmen Sie das Verhalten des Buffers zum Listener. Standardmäßig befindet sich der Buffer im normalen Modus, d.h. sämtliche Angaben zur Position oder Ausrichtung sind absolut durch die Vektoren definiert. Um den 3D-Buffer abzuspielen, sprechen Sie nicht etwa die neue Instanz der Buffer3D-Klasse an, sondern das alte SecondaryBuffer-Objekt.
Die Position eines 3D-Buffers
Bei Positionsangaben in DirectSound kommt standardmäßig die Einheit Meter zur Anwendung, wobei durch kleine Modifikationen auch dies an Ihre Bedürfnisse angepasst werden kann. Definiert ist eine Position in einem Raum bekanntlich durch einen X-, einen Y- und einen Z-Wert. Für diesen Zweck wird Ihnen die Struktur Vector3 gestellt. Sämtliche Positionen sind in einem kartesischen Koordinatensystem angegeben, wobei die X-Achse für eine Bewegung von links nach rechts und umgekehrt gedacht ist. Für eine Bewegung von unten nach oben steht uns die Y-Achse zur Verfügung und für die Entfernung verwenden wir die Z-Achse. Nachdem eine Position mittels einer Vector3-Struktur definiert ist, kann diese der Position-Eigenschaft eines Buffer3D-Objekts zugewiesen werden:
  1. Dim Position As Vector3
  2. Position.X = 10
  3. Position.Y = 20
  4. Position.Z = 30
  5. DS3DBuffer.Position = Position
Die Geschwindigkeit eines 3D-Buffers
Neben der Position sowie der Ausrichtung spielt auch die Geschwindigkeit der Soundquelle eine Rolle, denn diese beeinflusst beispielsweise die Auswirkung des Doppler-Effekts. Alle Angaben der Geschwindigkeit verwenden ebenfalls eine Vector3-Struktur. Durch den Vektor (1, 0, 0) ist beispielsweise definiert, dass sich die Quelle um einen Meter pro Sekunde entlang der X-Achse bewegt, denn Geschwindigkeiten werden immer in Einheit pro Sekunde angegeben. Das Abrufen des aktuellen Vektors bzw. das Zuweisen eines neuen Vektors geschieht über die Velocity-Eigenschaft eines Buffer3D-Objekts.
Die Ausrichtung eines 3D-Buffers
Stellen Sie sich einmal genau vor Ihren Lautsprecher und gehen dann langsam nach rechts oder links. Sie werden feststellen, dass die Musik etwas leiser ist, wenn Sie seitlich ausweichen. Dieses Verhalten ist damit zu erklären, dass sich die Schallwellen unterschiedlich ausbreiten. Solch ein Effekt lässt sich selbstverständlich auch mit DirectSound rekonstruieren. Bei einer gerichteten Soundquelle gibt es einen so genannten Inner und einen Outer Cone (siehe Abb. 1). Töne im inneren Schallkegel sind dabei lauter als die im äußeren. Jeder 3D-Buffer hält die Eigenschaften ConeAngles, ConeOrientation und ConeOutsideVolume bereit, um die Ausrichtung sowie den Abstrahlwinkel festzulegen. Mittels der ConeOutsideVolume-Eigenschaft bestimmen Sie einen Wert zur Abschwächung der Lautstärke vom inneren zum äußeren Schallkegel hin. Die Angabe erfolgt in x/100 db, wobei x meist negativ ist, denn eigentlich soll es zur Verminderung der Lautstärke kommen. Wenn Sie der ConeOrientation-Eigenschaft keinen Vektor zuweisen, besitzt der Buffer standardmäßig keine Ausrichtung.
  1. Dim Angle As New Angles()
  2. Dim Orientation As Vector3
  3. Angle.Inside = 90
  4. Angle.Outside = 180
  5. Orientation.X = 10
  6. Orientation.Z = 30
  7. DS3DBuffer.ConeAngles = Angle
  8. DS3DBuffer.ConeOrientation = Orientation
Entfernungen von Schallquellen
Wenn sich Objekte nähern, sind die Geräusche nicht immer gleich zu hören. Ein Auto beispielsweise muss sich in einem bestimmten Bereich finden, damit es vollständig zu hören ist. Ein jeder Soundbuffer hält zwei Eigenschaften, MinDistance und MaxDistance, für Sie parat, um dieses Verhalten an den Tag legen zu können. Letztere Eigenschaft gibt einen maximalen Abstand an, ab dem die Lautstärke des Geräuschs nicht weiter vermindert wird. Falls Sie wollen, dass die Schallquelle gar keinen Ton mehr von sich gibt, muss das Mute3DatMaximumDistance-Flag in der Buffer-Beschreibung gesetzt sein. Durch die MinDistance-Eigenschaft hingegen wird ein Mindestabstand definiert, ab dem die Schallquelle in voller Lautstärke zu hören ist.
Der 3D-Listener
Der Listener in einer DirectSound-Applikation stellt sozusagen die Person des Geschehens dar, die sämtliche Soundquelle wahrnimmt. Ein Listener3D-Objekt kann jeweils nur einmal pro Anwendung existieren und wird direkt vom primären Soundbuffer abgeleitet, d.h. wir erzeugen zunächst ein Objekt, welches unseren primären Soundbuffer repräsentiert und anschließend übergeben wir dieses Objekt dem Konstruktor der Listener3D-Klasse:
  1. Dim DSPrimBuffer As Buffer
  2. Dim DSBufferDesc As New BufferDescription()
  3. Dim DS3DListener As Listener3D
  4. DSBufferDesc.Control3D = True
  5. DSBufferDesc.PrimaryBuffer = True
  6. DSPrimBuffer = New Buffer(DSBufferDesc, DSDevice)
  7. DS3DListener = New Listener3D(DSPrimBuffer)
Im Grunde gibt es keine großen Unterschiede beim Erstellen eines Listeners im Vergleich zum 3D-Buffer. Wichtig hierbei ist aber, dass Sie anstatt eines SecondaryBuffer-Objekts eine Instanz der Buffer-Klasse erstellen sowie das PrimaryBuffer-Flag in der Buffer-Beschreibung setzen. Das Listener3D-Objekt verfügt genauso wie der 3D-Buffer über eine Position- und Velocity-Eigenschaft. Lediglich die Eigenschaft Orientation ist eine Erklärung wert. Hinter dieser Eigenschaft verbirgt sich eine Struktur namens Listener3DOrientation, welche wiederum zwei Vector3-Strukturen beherbergt. Ein Vektor zeigt in die Sichtrichtung des Listeners und der andere Vektor nach oben, wobei beide Vektoren einen rechten Winkel pictureen (siehe Abb. 2). Der Standard-Front-Vektor entspricht (0, 0, 1) und der Top-Vektor ist (0, 1, 0).
Der Doppler- und Rolloff-Effekt
Den Doppler-Effekt haben Sie bereits kennen gelernt. Über die DopplerFactor-Eigenschaft eines Listener3D-Objekts lässt sich dieser Effekt aktivieren, deaktivieren oder übertrieben darstellen. Der Gültige Wertebereich reicht von 0 bis 10, wobei dieser Effekt bei einem Wert von 1 der Realität entspricht.
Als Rolloff-Effekt bezeichnen wir die Verminderung der Lautstärke einer Schallquelle bei deren Entfernung vom Listener. Weisen Sie der RolloffFactor-Eigenschaft einen Wert zwischen 0 und 10 zu, wobei auch hier wieder die 1 dem reellen Wert entspricht.
Ändern der Einheit
Wollten Sie statt der Einheit Meter lieber Zentimeter verwenden, dann muss die DistanceFactor-Eigenschaft des Listener3D-Objekts auf den Wert 0.01 abgeändert werden. Der Standardwert ist 1 und entspricht einem Meter. Anschließend können sämtliche Positions-, Geschwindigkeits- und Ausrichtungsangaben in Zentimeter gemacht werden.

Kommentare