DirectSound
von Sebastian Klose
Übersicht
Die Soundkarte
Wie man es von DirectX gewohnt ist, bietet es zu jedem Bereich die Möglichkeit die verfügbaren Geräte auflisten zu lassen, so auch bei DirectSound:
Bei DirectSound wird dabei zwischen Aufnahme- (Capture) und Soundgeräten unterschieden. Als erstes wollen wir ein kleines Programm schreiben, welches die verfügbaren Geräte auflistet.
Erstellen Sie dazu bitte ein neues Projekt und binden im Menü Projekt à Verweise die DirectX 8 Type Library ein. Anschließend fügen Sie dem Formular zwei Comboboxen hinzu, die Sie „cboSound“ und „cboCapture“ nennen und schreiben folgenden Code in das Formular:
Option Explicit '//Das Ausgangsobjekt, aus dem '//alle anderen DirectX-Objekte '//hervorgehen Dim objDX As New DirectX8 '//Soll die Auflistung aller '//DirectSound Geräte enthalten Dim objDSs As DirectSoundEnum8 'Soundgeräte Dim objDSc As DirectSoundEnum8 'Aufnahmegeräte Private Sub Form_Load() '//Auflistungen von DirectX holen Set objDSs = objDX.GetDSEnum() Set objDSc = objDX.GetDSCaptureEnum() '//Zurückgelieferte Geräte in Comboboxen anzeigen Dim i As Integer 'Soundgeräte For i = 1 To objDSs.GetCount() cboSound.AddItem objDSs.GetDescription(i) Next i 'Aufnahmegeräte For i = 1 To objDSc.GetCount() cboCapture.AddItem objDSc.GetDescription(i) Next i 'ersten Einträge aus Combobox selektieren cboSound.ListIndex = 0 cboCapture.ListIndex = 0 End Sub
Einfaches Abspielen von Sounds
Das nächste Beispiel soll zeigen, wie man einfache Sounds abspielen kann. Erstellen Sie dazu bitte wieder ein neues Projekt, fügen den Verweis auf die DirectX 8 Type Library hinzu und erstellen zwei Buttons auf einem Formular (cmdStart und cmdStop). Der Quelltext der Andwendung sieht folgendermaßen aus. Alle mit einem „<--“ versehenen Stellen werden hinterher erklärt:
Option Explicit '//Unser Grundobjekt Dim objDX As New DirectX8 '//Die Verbindung zu Soundkarte Dim objDS As DirectSound8 '//Spiegelt unseren Sound (eine '//Wave-Datei) wieder Dim objDSBuff As DirectSoundSecondaryBuffer8 Private Sub Form_Load() 'Struktur die unseren SoundBuffer beschreibt Dim descSoundBuff As DSBUFFERDESC 'Der Dateiname mit Pfad der Wav-Datei Dim strFilename As String '//DirectSound instanzieren Set objDS = objDX.DirectSoundCreate(vbNullString) '<-- '//DirectSound mit unserer Anwendung verbinden objDS.SetCooperativeLevel Me.hWnd, DSSCL_NORMAL '<-- '//Einen Soundbuffer erstellen, der aus einer '//Wav-Datei geladen wird strFilename = App.Path & "\yippee.wav" Set objDSBuff = objDS.CreateSoundBufferFromFile(strFilename, descSoundBuff) '<-- End Sub Private Sub cmdPlay_Click() '//Soundbuffer abspielen objDSBuff.Play DSBPLAY_DEFAULT '<-- End Sub Private Sub cmdStop_Click() '//Soundbuffer anhalten und auf Anfang setzen objDSBuff.Stop 'Stop = Pause!! objDSBuff.SetCurrentPosition 0 'Auf Anfang zurück End Sub
Erklärungen
Set objDS = objDX.DirectSoundCreate(vbNullString)
Hier wird eine Verbindung zur Soundkarte hergestellt. Über das DirectSound-Objekt (objDS) können dann später alle Sounds abgespielt werden. Das vbNullString gibt hierbei das Gerät (die Soundkarte) an, die wir verwenden wollen. vbNullString entspricht hierbei dem Standardgerät. Wenn Sie andere Geräte verwenden wollen, müssen Sie den GUID dieses Gerätes übergeben. Sie erhalten ihn indem Sie die Geräte über die Enumeration (siehe Kapitel 1) auflisten lassen und anstatt der GetDescription() die GetGuid() Methode aufrufen (siehe Quelltext Kapitel 1). Statt vbNullString könnten Sie demnach auch „{00000000-0000-0000-0000-000000000000}“ schreiben.
objDS.SetCooperativeLevel Me.hWnd, DSSCL_NORMAL
Hier verbinden wir DirectSound mit unserem Fenster, indem wir ihm das Handle des Fensters übergeben. Der zweite Parameter gibt an, wie DirectSound mit der Anwendung verbunden ist. Es gibt dazu insgesamt 3 Möglichkeiten:
DSSCL_NORMAL Anwendung kann die SetFormat() Methode des DirectSound8PrimaryBuffer8 – Objekts nicht aufrufen! Das Format der Sounddaten ist auf das 8Bit Format festgelegt. Dafür ist Speicherverwaltung in diesem Modus die beste. DSSCL_PRIORITY Die SetFormat() Methode kann aufgerufen werden um das Format des Sounddaten zu ändern. DSSCL_WRITEPRIMARY Der primäre Soundbuffer kann selbst beschrieben werden. - objDSBuff.Play DSBPLAY_DEFAULT
Startet die Wiedergabe des Soundbuffers (es können auch mehrere Soundbuffer gleichzeitig abgespielt werden). Der Parameter gibt an, wie die Bufferdaten abgespielt werden sollen, es gibt hierbei (unter anderem) folgende Möglichkeiten:
DSBPLAY_DEFAULT Startet die Wiedergabe ab Cursorposition (kann mit .SetCurrentPosition() gesetzt werden) und spielt bis zum Ende ab. DSBPLAY_LOOPING Spielt ab Cursorposition bis zum Ende, und wiederholt die Wiedergabe (von Anfang des Buffers) DSBPLAY_TERMINATEBY_TIME ?????? -
objDSBuff.Stop objDSBuff.SetCurrentPosition 0
In der ersten Zeile wird die Wiedergabe angehalten. Hierbei ist zu beachten, dass Stop im Sinne von Pause gemeint ist, der Buffer wird nicht automatisch auf den Anfang zurückgesetzt! In der zweiten Zeile wird der Cursor dann manuell auf den Anfang gesetzt.
Sonstige Dinge zum Abspielen
Lautstärke einstellen
Die Lautstärke wird in DirectSound in 100stel Dezibel angegeben, 0 (oder DSBVOLUME_MAX) entspricht der original Lautstärke des Sounds, -10 000 (oder DSBVOLUME_MIN (entspricht –100db)) ist das Leiseste. Gesetzt wird die Lautstärke mit der objDSBuff.SetVolume (Dezibel) Methode, gelesen mit objDSBuff.GetVolume().
Beispiel:'//Lautstärke um 1 db erhöhen
objDSBuff.SetVolume (objDSBuff.GetVolume() + 100)
Balance einstellen
Es ist auch möglich die Lautstärke der einzelnen Lautsprecher zu ändern. Dabei bleibt ein Kanal immer auf normal Lautstärke und der andere wird um die angegebene Dezibel Zahl leiser gemacht (Angabe in 100stel db). Das Vorzeichen (+ oder -) der Angabe entscheidet, welcher Kanal leiser wird. 0 bedeutet folglich, dass beide Kanäle gleich laut sind. Die Methode dazu ist die objDSBuff.SetPan (Dezibel). Die Konstanten DSBPAN_LEFT, DSBPAN_RIGHT und DSBPAN_CENTER können auch verwendet werden.
Beispiel:'//Linken Lautsprecher um 10db leiser machen
objDSBuff.SetPan (objDSBuff.GetPan() - 1000)
Frequenz ändern
Das Ändern der Frequenz wirkt sich auf die Abspielgeschwindigkeit aus. Erhöht man die Frequenz, werden pro Sekunde mehr Samples abgespielt und somit die Wiedergabe schneller. Die Einheit für die Frequenz sind Hertz. Das Minimum sind 100hz (DSBFREQUENCY_MIN) und das Maximum sind 100000hz (DSBFREQUENCY_MAX). Um die original Frequenz wiederherzustellen, kann die DSBFREQUNCY_ORIGINAL Konstante verwendet werden. Gesetzt wird die Frequenz mit der objDSB.SetFrequency (Hertz) – Methode.
Beispiel:'//Frequenz um 100hz erhöhen
objDSBuff.SetFrequency (objDSBuff.GetFrequency() + 100)
Effektfilter
DirectSound bietet standardmäßig einige Effektfilter, die sich auf einfache Weise auf einen SoundBuffer anwenden lassen. Hier ein Beispiel dazu:
(1 Form, 2 Listboxen (lstEffekte, lstGewaehlt), 3 CommandButtons (cmdAdd, cmdRemove, cmdPlay))
Option Explicit '//Die üblichen Objekte Dim objDX As New DirectX8 Dim objDS As DirectSound8 Dim objDSBuff As DirectSoundSecondaryBuffer8 Private Sub cmdAdd_Click() '//Effekt der "Gewählt-Liste" hinzufügen lstGewaehlt.AddItem lstEffekte.List(lstEffekte.ListIndex) lstEffekte.RemoveItem lstEffekte.ListIndex End Sub Private Sub cmdPlay_Click() '//Für jeden Effekt ein Deskriptor Dim descEffect() As DSEFFECTDESC Dim lngResult() As Long 'Speichert Ergebnis der SetFX-Methode Dim i As Integer 'Schleifenzähler '//Falls Wiedergabe noch läuft objDSBuff.Stop objDSBuff.SetCurrentPosition 0 'auf Anfang zurück '//Wenn keine Effekte gewählt sind, Effekte ausschalten If lstGewaehlt.ListCount = 0 Then objDSBuff.SetFX 0, descEffect, lngResult 'FX aus Else '//Für jeden Eintrag eine Effektfilter erstellen ReDim descEffect(lstGewaehlt.ListCount - 1) ReDim lngResult(lstGewaehlt.ListCount - 1) '//Alle selektierten Filter aktivieren For i = 0 To lstGewaehlt.ListCount - 1 Select Case lstGewaehlt.List(i) Case "Echo": descEffect(i).guidDSFXClass = _ DSFX_STANDARD_ECHO Case "Chor": descEffect(i).guidDSFXClass = _ DSFX_STANDARD_CHORUS Case "Gargle": descEffect(i).guidDSFXClass = _ DSFX_STANDARD_GARGLE Case "Flanger": descEffect(i).guidDSFXClass = _ DSFX_STANDARD_FLANGER Case "Distortion": descEffect(i).guidDSFXClass = _ DSFX_STANDARD_DISTORTION Case "Compressor": descEffect(i).guidDSFXClass = _ DSFX_STANDARD_COMPRESSOR Case "ParamEQ": descEffect(i).guidDSFXClass = _ DSFX_STANDARD_PARAMEQ Case "WavesReverb": descEffect(i).guidDSFXClass = _ DSFX_STANDARD_WAVES_REVERB End Select Next i '//Effekte aktivieren objDSBuff.SetFX lstGewaehlt.ListCount, descEffect, lngResult '//Resourcen anfordern objDSBuff.AcquireResources 0, lngResult End If '//Abspielen objDSBuff.Play DSBPLAY_DEFAULT End Sub Private Sub cmdRemove_Click() '//Effekt wieder aus Liste entfernen lstEffekte.AddItem lstGewaehlt.List(lstGewaehlt.ListIndex) lstGewaehlt.RemoveItem lstGewaehlt.ListIndex End Sub Private Sub Form_Load() Dim descSoundBuff As DSBUFFERDESC '//DirectSound initialisieren und Wavedatei laden '//(Erklärungen siehe Kapitel 2) Set objDS = objDX.DirectSoundCreate(vbNullString) '//Deskriptor für SoundBuffer konfigurieren '//wir geben an, was wir kontrollieren wollen descSoundBuff.lFlags = DSBCAPS_CTRLFX Or DSBCAPS_LOCDEFER objDS.SetCooperativeLevel Me.hWnd, DSSCL_NORMAL Set objDSBuff = objDS.CreateSoundBufferFromFile(App.Path & "\sound.wav", descSoundBuff) End Sub
Erläuterungen
Zunächst erstellen wir wie üblich die DirectSound Objekte und laden die Wavedatei in einen Soundbuffer. Diesmal müssen wir aber das lFlags Attribut des Soundbuffer-Deskriptors festlegen, da wir auf die SetFX() und die AcquireResources() Methoden zugreifen wollen. Vergessen wir den Flag zu setzen, so wird ein Automatisierungsfehler ausgelöst, sobald wir die genannten Methoden versuchen aufzurufen.
Um die Effektfilter jetzt zu aktivieren, müssen wir für jeden Effekt eine eigene Struktur definieren (bei mehreren Effektfiltern als Array!). Dem guidDSFXClass – Attribut weisen wir eine Konstante zu, die den Effekt definiert. Folgende sind dabei erlaubt:
DSFX_STANDARD_CHORUS | Hall Effekt |
DSFX_STANDARD_COMPRESSOR | |
DSFX_STANDARD_DISTORTION | |
DSFX_STANDARD_ECHO | Echo Effekt |
DSFX_STANDARD_FLANGER | |
DSFX_STANDARD_GARGLE | |
DSFX_STANDARD_I3DL2REVERB | |
DSFX_STANDARD_PARAMEQ | |
DSFX_STANDARD_WAVES_REVERB |
Um die Effekte jetzt endgültig auf den Soundanzuwenden müssen wir noch die SetFX() – Methode aufrufen. Als erstes Argument muss die Anzahl der Effekte, die sich im Array befinden und aktiviert werden sollen, angegeben werden. Das zweite Argument enthält das Array mit den Filtern und als letztes Argument wird ein Array gleicher Dimension erwarten in dem für jeden Effekt ein Rückgabewert gespeichert wird. Folgende Rückgabewerte sind möglich:
DSFXR_LOCHARDWARE | Der Effektfilter wurde erfolgreich initialisiert und wird von der Hardware berechnet |
DSFXR_LOCSOFTWARE | Der Effektfilter wurde erfolgreich initialisiert, kann aber nur von der Software emuliert werden |
DSFXR_UNALLOCATED | Der Effekt wurde weder für Hard- noch für Software erstellt |
DSFXR_FAILED | Es konnte kein Effekt erstellt werden, da keine Resourcen zur Verfügung stehen |
DSFXR_PRESENT | Aus irgendeinem Grund konnte der Effekt nicht erstellt werden |
DSFXR_UNKNOWN | Der angegebene Effekt ist auf dem System nicht registriert. |
Zuletzt müssen wir noch angeben, wie die Resourcen zum Abspielen alloziiert werden müssen. Dies geschieht mit dem AcquireResources 0, lngResults Aufruf.
Aufnahme
Zuletzt wollen wir eine Möglichkeit kennen lernen, Sounds über ein Mikrofon (oder andere Eingabegeräte) aufzunehmen und diesen dann als WaveDatei abzuspeichern. Für das nachfolgende Beispiel wird allerdings eine Soundkarte benötigt, gleichzeitiges Abspielen und Aufnehmen erlaubt, da wir gleichzeitig eine Instanz eines Wiedergabe- und eines Aufnahmeobjekts erstellen. Sollte die Soundkarte dieses Feature nicht unterstützen, wird sich das Programm mit einem Automatisierungsfehler beenden. Für das Beispiel benötigen wir ein Formular mit 3 CommandButtons (cmdRecord, cmdStop, cmdSave):
Option Explicit '//Die üblichen Objekte Dim objDX As New DirectX8 Dim objDS As DirectSound8 Dim objDSBuff As DirectSoundSecondaryBuffer8 '//Ein Aufnahmeobjekt und ein Buffer '//auf den aufgenommen wird Dim objDSCap As DirectSoundCapture8 Dim objDSCapBuff As DirectSoundCaptureBuffer8 Private Sub cmdSave_Click() Dim descBuff As DSBUFFERDESC 'beschreibt den Sec.Buffer Dim waveFormat As WAVEFORMATEX 'beschreibt die Aufnahme Dim curInfo As DSCURSORS '/////////////////////////////////////////////////// '//zuerst müssen wir den Capture- in eine SecondaryBuffer '//umwandeln, damit wir ihn speichern können 'Informationen über den CaptureBuffer erhalten objDSCapBuff.GetCurrentPosition curInfo 'Deskriptor für Sec.Buffer erstellen With descBuff 'Die benötigte Größe bestimmen .lBufferBytes = curInfo.lWrite + 1 'das Format muss übernommen werden objDSCapBuff.GetFormat waveFormat .fxFormat = waveFormat End With 'anhand dieser Daten den Sec.Buffer erstellen Set objDSBuff = objDS.CreateSoundBuffer(descBuff) 'jetzt müssen noch die Daten rüberkopiert werden Dim bytes() As Integer ReDim bytes(curInfo.lWrite) objDSCapBuff.ReadBuffer 0, UBound(bytes), bytes(0), DSCBLOCK_DEFAULT objDSBuff.WriteBuffer 0, UBound(bytes), _ bytes(0), DSBLOCK_DEFAULT '/////////////////////////////////////////////////// 'Abspeichern objDSBuff.SaveToFile App.Path & "\wave.wav" End Sub Private Sub cmdRecord_Click() '//wir starten die Aufnahme objDSCapBuff.Start DSCBSTART_DEFAULT '//Buttons an/ausschalten cmdRecord.Enabled = False cmdSave.Enabled = False cmdStop.Enabled = True End Sub Private Sub cmdStop_Click() '//Aufnahme beenden objDSCapBuff.Stop '//Buttons an/ausschalten cmdRecord.Enabled = True cmdSave.Enabled = True cmdStop.Enabled = False End Sub Private Sub Form_Load() Dim descCapBuff As DSCBUFFERDESC 'Wie soll unser Buffer aussehen? Dim capFormat As WAVEFORMATEX 'Was für ein Format soll die Wavedatei haben? '//Das Aufnahmeobjekt erstellen '//wir verwenden das Standardgerät (vbNullString) Set objDSCap = objDX.DirectSoundCaptureCreate(vbNullString) '//Das Wiedergabeobjekt erstellen Set objDS = objDX.DirectSoundCreate(vbNullString) '//Format festlegen, in dem wir aufnehmen wollen With capFormat .nFormatTag = WAVE_FORMAT_PCM .nChannels = 1 'wir nehmen in mono auf .lSamplesPerSec = 44100 .nBitsPerSample = 16 .nBlockAlign = .nChannels * .nBitsPerSample / 8 .lAvgBytesPerSec = .lSamplesPerSec * .nBlockAlign .nSize = 0 End With '//Unseren Aufnahmebuffer für DX "beschreiben" With descCapBuff .fxFormat = capFormat .lBufferBytes = capFormat.lAvgBytesPerSec * 20 .lFlags = DSCBCAPS_WAVEMAPPED End With '//Den Buffer erstellen, auf den wir aufnehmen können Set objDSCapBuff = objDSCap.CreateCaptureBuffer(descCapBuff) End Sub
Wie auch bei Abspielen, so brauchen wir auch zum Aufnehmen ein Aufnahmeobjekt (welches die Soundkarte repräsentiert) und einen Buffer auf den wir aufnehmen können. Mit dem Aufruf von Set objDSCap = objDX.DirectSoundCaptureCreate(vbNullString) stellen wir die Verbindung zur Soundkarte her, vbNullString bedeutet wie immer das Standardgerät, soll ein anderes Gerät verwendet werden, muss dessen GUID andgegeben werden (siehe Kapitel 2). Den Aufnahmebuffer erstellen wir mit der CreateCaptureBuffer() – Methode des zuvor erstellten Capture-Objekts. Die Methode erwartet ein Argument vom Typ DSCBUFFERDEC (Direct Sound Capture Buffer Descriptor). Sie enthält Informationen über das Aufnahmeformat (z.B die Anzahl der Kanäle (Stereo/Mono), die Bitrate, Samples / s, etc). Nachdem wir diesen Buffer erfolgreich instanziert haben, können wir die Aufnahme mit der Start() – Methode des Bufferobjektes beginnen. Mit der Stop() – Methode beenden wir sie. Da man einen Aufnahmebuffer weder abspielen, noch etwas anderes mit ihm als aufnehmen kann, müssen wir ihn in einen SecondaryBuffer (einen Wiedergabebuffer, bekannt aus den vorrangegangenen Kapiteln) umwandeln. Dazu erstellen wir zunächst einen Deskriptor für den SecondaryBuffer. Er muss natürlich das gleiche Format und die gleiche Größe wie der AufnahmeBuffer haben. Anschließend wird mit der CreateSoundBuffer() – Methode des DirectSound-Objekts der SecondaryBuffer erstellt. Als Parameter übergeben wir der Methode des zuvor erstellten Deskriptor. Jetzt haben wir zwei Buffer (einen Aufnahme- und einen Wiedergabebuffer) gleicher Größe und gleichen Formats. Es müssen also nur noch die Daten vom Aufnahme- in den Wiedergabebuffer kopiert werden. Dazu wird ein Integer-Array der Größe des Aufnahmebuffers erstellt. Mit der ReadBuffer() – Methode des Aufnahmebuffers lesen wir die Binärdaten in das Array ein und schreiben sie anschließend mit der WriteBuffer() – Methode des Wiedergabebuffers in den Wiedergabebuffer. Zuletzt speichern wir den Wiedergabebuffer als Wave-Datei ab. Dies geschieht mit der SaveToFile() – Methode, als Parameter wird einfach der Dateiname angegeben.
Beispielprojekt zum Tutorial [98100 Bytes]
Ihre Meinung
Falls Sie Fragen zu diesem Tutorial haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.