DirectSound开发指南

1DirectSound简介(Introduction to DirectSound)

    曾经学习过Directshow的开发,对于Dsound一直没有仔细的莱学习,以前只是知道Dsound是做音频开发的,我一直以为它和Dshow的结构体系差不多,经过仔细学习后,发现,其实他们完全两码事。

   闲话少说,下面我们看看DirectSound到底能帮我们做些什么。

 1播放WAVE格式的音频文件或者资源。

 2可以同时播放多个音频。

 3Assign high-priority sounds to hardware-controlled buffers

 4播放3D立体声音

 5在声音中添加特技效果,比如回声,动态的改变特技的参数等

 6将麦克风或者其他音频输入设备的声音录制成wave格式的文件

 

 

 

 

呵呵,DirectSound就能做这么多事情,读到这里,我都有点怀疑DirectSound是不是就是封装了mmio系列和wav系列的函数。因为这些底层的API也能够完成这些事情。

2DirectSound初体验(Getting Started with DirectSound)

在开始本节内容前,我会首先提醒一下,如果你想用Directsound开发,那么你首先要包含Dsound.h头文件,其实我可以实话告诉你,你仅仅包含dsound.h你的工程肯定调补通,其实下面的一些头文件也要包含,我第一次就搞了半天才搞好,

#include <windows.h>

#include <mmsystem.h>

#include <mmreg.h>

#include <dsound.h>

 

 

 

 

如果你还想使用Dsound的API的话,那么你就要在你的vc开发环境中添加Dsound..lib库,

如果你的程序还提示有很多的外部链接找不到,那么我建议你可以将下面的库都添加到你的工程中comctl32.lib dxerr9.lib winmm.lib dsound.lib dxguid.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib,这些是我从Dsound提供的例子中得到的,肯定够你用的,ok,开发环境配置好了。

下面我们简单的来学习一下如果通过Directsound的API播放声音,既然是breif overview,那么详细的内容你可以参考下面的一节内容,这里只是简单的介绍一下播放声音的步骤。

第一步,创建一个设备对象。                                                                                                                                                                                                                                                                                      

  在你的代码中你可以通过调用DirectSoundCreat8函数来创建一个支持IDirectSound8接口的对象,这个对象通常代表缺省的播放设备。当然你可以枚举可用的设备,然后将设备的GUID传递给DirectSoundCreat8函数。

 注意,Directsound虽然基于COM,但是你并不需要初始化com库,这些Directsound都帮你做好了,当然,如果你使用DMOs特技,你就要自己初始化com库了,切记。

第二步,创建一个辅助Buffer,也叫后备缓冲区

你可以通过IDirectSound8::CreateSoundBuffer来创建buffer对象,这个对象主要用来获取处理数据,这种buffer称作辅助缓冲区,以和主缓冲区区别开来,Direcsound通过把几个后备缓冲区的声音混合到主缓冲区中,然后输出到声音输出设备上,达到混音的效果。

第三步,获取PCM类型的数据

将WAV文件或者其他资源的数据读取到缓冲区中。

第四步,将数据读取到缓冲区

你可以通过 IDirectSoundBuffer8::Lock.方法来准备一个辅助缓冲区来进行写操作,通常这个方法返回一个内存地址,见数据从你的私人buffer中复制到这个地址中,然后调用IDirectSoundBuffer8::Unlock.

第五步,播放缓冲区中的数据

你可以通过IDirectSoundBuffer8::Play方法来播放缓冲区中的音频数据,你可以通过IDirectSoundBuffer8::Stop来暂停播放数据,你可以反复的莱停止,播放,音频数据,如果你同时创建了几个buffer,那么你就可以同时来播放这些数据,这些声音会自动进行混音的。

 

 

 

 

  呵呵,简单介绍到这里的,如果想深入了解,请继续参考下一部分。

3DirectSound实用开发技巧Using DirectSound

在进行这部分之前,我们首先学习一下Directsound中常用的几个对象,简单学习一下哦

对象

数量

作用

主要接口

设备

每个应用程序只有一个设备对象

用来管理设备,创建辅助缓冲区

IDirectSound8

辅助缓冲区

每一个声音对应一个辅助缓冲区

用来管理一个静态的或者动态的声音流,然后在主缓冲区中混音

IDirectSoundBuffer8,IDirectSound3DBuffer8,IDirectSoundNotify8

主缓冲区

一个应用程序只有一个主缓冲区

将辅助缓冲区的数据进行混音,并且控制3D参数.

IDirectSoundBuffer,IDirectSound3DListener8

特技

没有,或者

来辅助缓冲的声音数据进行处理

Interface for the particular effect, such asIDirectSoundFXChorus8

 

 

 

 

首先,要创建一个设备对象,然后通过设备对象创建buffer对象。辅助缓冲区由应用程序创建和管理,DirectSound会自动地创建和管理主缓冲区,一般来说,应用程序即使没有获取这个对象的接口也可以播放音频数据,但是,如果应用程序要想得到IDirectSound3DListener8接口,就必须要自己创建一个主缓冲区。

  我们可以将短小的声音文件全部读取到辅助缓冲区中,然后通过一个简单的命令来播放。如果声音文件很长,就必须采用数据流了。

3.1Dsound设备对象(DirectSound Devices)

本节主要讲述下面的几个内容

1如何枚举系统输出声音的设备

2创建设备对象

3设置声音设备的协作度

下面首先看看如何枚举系统中的声音输出设备

1如何枚举系统输出声音的设备

如果你的应用程序使用用户首选的输出设备来输出声音,那么你就没有必要来枚举所有的输出设备,如果你通过DirectSoundCreat8函数来创建一个设备对象的同时,就给这个对象指定了一个缺省的设备,当然如果遇到下面的一些情形,你就要来枚举设备对象

  例如,你的应用程序并不支持所有的输出设备,或者你的应用程需要两个或者多个设备,或者你希望用户自己来选择输出设备。

 枚举设备,你首先要定义一个回调函数,这个回调函数可以被系统中的每个设备来调用,你可以在各函数做任何事情,这个函数的命名也没有任何的限制,但是函数应该以DSEnumCallback为原型,如果枚举没有结束,这个回调函数就返回TRUE,如果枚举结束,例如你找到合适的设备,这个函数就要返回FALSE。

  下面是回调函数的一个例子,这个函数将枚举的每一个设备都添加到一个combox中,将设备的GUID保存到一个item 中,这个函数的前三个参数由设备的驱动程序提供,第四个参数有DirectSoundEnumerate函数提供,这个参数可以是任意的32位值,这个例子里是combox的句柄,

BOOL CALLBACK DSEnumProc(LPGUID lpGUID,

         LPCTSTR lpszDesc,

         LPCTSTR lpszDrvName,

         LPVOID lpContext )

{

  HWND hCombo = (HWND)lpContext;

  LPGUID lpTemp = NULL;

 

  if (lpGUID != NULL)  //  NULL only for "Primary Sound Driver".

  {

    if ((lpTemp = (LPGUID)malloc(sizeof(GUID))) == NULL)

    {

        return(TRUE);

    }

    memcpy(lpTemp, lpGUID, sizeof(GUID));

  }

 

  ComboBox_AddString(hCombo, lpszDesc);

  ComboBox_SetItemData(hCombo,

      ComboBox_FindString(hCombo, 0, lpszDesc),

      lpTemp );

  free(lpTemp);

  return(TRUE);

}

枚举设备通常都是在对话框初始化的时候才进行的,我们假设hCombo就是combox句柄,hDlg就对话框的句柄,看看我们怎么来枚举设备的吧

if (FAILED(DirectSoundEnumerate((LPDSENUMCALLBACK)DSEnumProc,

    (VOID*)&hCombo)))

{

  EndDialog(hDlg, TRUE);

  return(TRUE);

}

在这个例子中,combox的句柄作为参数传递到DirectSoundEnumerate函数中,然后又被传递到回调函数中,这个参数你可以是你想传递的任意的32位值。

注:第一个被枚举的设备通常称为Primary sound driver,并且回调函数的lpGUIDNULL,这个设备就是用户通过控制面板设置的缺省的输出声音设备,

2创建设备对象

  创建设备对象最简单的方法就是通过DirectSoundCreate8函数,这个函数的第一个参数指定了和这个对象邦定的设备的GUID,你可以通过枚举设备来获取这个设备的GUID,你可以传递一个下面的参数来指定一个缺省的设备

DSDEVID_DefaultPlayback 缺省的系统的声音输出设备,这个参数也可以为NULL

SDEVID_DefaultVoicePlayback,缺省的声音输出设备,通常指第二缺省设备,例如USB耳机麦克风

  如果没有声音输出设备,这个函数就返回error,或者,在VXD驱动程序下,如果声音输出设备正被某个应用程序通过waveform格式的api函数所控制,该函数也返回error,

下面是创建对象的代码,及其简单

LPDIRECTSOUND8 lpds;

HRESULT hr = DirectSoundCreate8(NULL, &lpds, NULL));

如果你想通过表准的COM调用来创建设备对象,下面我就给出代码,你可以比较一下

HRESULT hr = CoInitializeEx(NULL, 0);

if (FAILED(hr))

{

  ErrorHandler(hr);  // Add error-handling here.

}

LPDIRECTSOUND8 lpds;

hr = CoCreateInstance(&CLSID_DirectSound8,

          NULL,

          CLSCTX_INPROC_SERVER,

          IID_IDirectSound8,

          (LPVOID*) &lpds);

if (FAILED(hr))

{

  ErrorHandler(hr);  // Add error-handling here.

}

hr = lpds->Initialize(NULL);

if (FAILED(hr))

{

  ErrorHandler(hr);  // Add error-handling here.

}

 

 

 

 

CoUninitialize();

 

 

 

 

3设置声音设备的协作度

因为Windows是一个多任务操作环境,在同一个时刻有可能多个应用程序共用同一个设备,通过协作水平,DirectX就可以保证这些应用程序在访问设备的时候不会冲突,每个Directsound应用程序都有一个协作度,用来确定来接近设备的程度,

当你创建完设备对象后,一定要调用IDirectSound8::SetCooperativeLevel来设置协作度,否则,你不会听到声音的,

HRESULT hr = lpDirectSound->SetCooperativeLevel(hwnd, DSSCL_PRIORITY);

if (FAILED(hr))

{

  ErrorHandler(hr);  // Add error-handling here.

}

Hwnd参数代表了应用程序的窗口。

DirectSound定义了三种水平,DSSCL_NORMAL, DSSCL_PRIORITY, and DSSCL_WRITEPRIMARY

在NORMAL水平的协作度下,应用程序不能设置主缓冲区的格式,也不能对主缓冲区进行写操作,所有在这个层次的应用程序使用的媒体格式都是22kHZ,立体声,采样精度8位,所以,设备可以在各个应用程序中任意的切换。

在Priority层次的协作度下,应用程序可以有优先权使用硬件资源,比如使用硬件进行混音,当然也可以设置主缓冲区的媒体格式,游戏程序应该采用这个层次的协作度,这个层次的协作度在允许应用程序控制采用频率和位深度的同时,也给应用程序很大的权力,这个层次的协作度允许其他应用程序的声音和游戏的音频同时被听到,不影响。

最高层次的协作度就是Write_primary,当采用这个层次的协作度来使用一个dsound设备时,在非WDM模式驱动下,你的应用程序可以直接操作主缓冲区,在这个模式下,应有程序必须直接的操作主缓冲区。如果你想直接把音频数据直接写入到主缓冲区,你就要将你的协作度模式设置为writeprimary,如果你的应用程序没有设置到这个层次,那么所有的对主缓冲区的IDirectSoundBuffer::Lock都会失败。

当你的应用程序的协作度设置为wirte_primary水平时,并且你的应用程序gains the foreground,那么其他应用程序的辅助缓冲区就会停止,并且标示为lost,当你的应用程序转到background,那么它的主缓冲区就会标示为lost,当它再次回到foreground的时候会恢复到原来的状态,如果你的设备没有出现在你的系统中,那么你就没法设置wirte primary协作度了,所以,在设置协作度之前,可以通过IDirectSound8::GetCaps方法来查询一下设备是否可用。

4设置声音设备(扬声器)

  windowos98或者2000系统中,用户可以通过控制面板来设置扬声器的属性,应用程序可以通过IDirectSound8::GetSpeakerConfig.函数来获取这些属性,我们不建议应用程序通过IDirectSound8::SetSpeakerConfig函授来设置扬声器的属性,因为这些设置的改动会影响到其他用户或者应用程序。

5查询输出设备的性能

 你的应用程序可以通过Directsound来检查声音设备的性能,然而许多应用程序并不需要这么做,因为Directsound会自动地利用硬件提供的较好的性能,并不需要你手动地来选择。

但是,However, high-performance applications can use the information to scale their sound requirements to the available hardware. For example, an application might choose to play more sounds if hardware mixing is available than if it is not.

当你调用DirectSoundCreate8创建一个设备对象后,你的应用程序就可以通过IDirectSound8::GetCaps函数来获取设备的性能属性,下面的代码演示了这个过程

DSCAPS dscaps;

 

dscaps.dwSize = sizeof(DSCAPS);

HRESULT hr = lpDirectSound->GetCaps(&dscaps);

if (FAILED(hr))

{

  ErrorHandler(hr);  // Add error-handling here.

}

  通过 DSCAPS结构,该函数就给我返回硬件设备的一些性能,但是在调用这个函数之前一定要初始化这个结构的dwSize成员。

  如果你的应用程序能够操作硬件设备,比如你的协作度是Write_primory级别的,那么你在硬件设备上分配内存的时候,就要调用IDirectSoudn8::GetCaps来查看一下硬件的资源是否满足你的要求。

3.2Dsound的buffer对象(DirectSound Buffers)

  在存储和播放几个音频流的时候,你的应用程序要给每一个音频流都要创建一个辅助缓冲区(buffer)对象。

  辅助缓冲区可以和应用程的生命期一样的长,也可以在不需要的时候销毁。辅助缓冲区可以是一个包含了整个声音数据的静态缓冲区,也是可以只包含声音数据的一部份,然后再播放时不断更新数据的流缓冲区。为了限制内存开销,在播放比较长的声音文件时要采用流缓冲区,这些缓冲区只包含几秒钟的数据量。

  你可以通过同时播放几个辅助缓冲区中的声音来对他们进行混音,至于同时可以播放几个辅助缓冲区则有硬件设备的性能决定。

辅助缓冲区的格式并不完全一样,一般用来描述缓冲格式的参数如下

 Format,缓冲区的format必须要所播放音频的waveformat一致。

 Controls,不同的缓冲区的控制参数的值可以不一样,比如音量,频率,以及在不同方向的移动,当创建buffer时,你就要指定你需要的控制参数的值,例如,不要为一个不支持3D的音频创建一个3D缓冲区

 Location,你创建的缓冲区可以在硬件管理的内存中,也可以在软件管理的内存中,当然,硬件缓冲区比软件缓冲区速度要快。

 

 你可以调用IDirectSound8::CreateSoundBuffer函数来创建一个缓冲区(buffer)对象。这个函数返回一个指向IDrectSoundBuffer接口的指针,通过这个接口,应用程序可以获取

IDirectSoundBuffer8 interface.

 下面的一段代码演示了如何创建一个辅助缓冲区,并且返回一个IDirectSoundBuffer8接口

HRESULT CreateBasicBuffer(LPDIRECTSOUND8 lpDirectSound, LPDIRECTSOUNDBUFFER8* ppDsb8)

{

  WAVEFORMATEX wfx;

  DSBUFFERDESC dsbdesc;

  LPDIRECTSOUNDBUFFER pDsb = NULL;

  HRESULT hr;

 

  // Set up WAV format structure.

 

 

 

  memset(&wfx, 0, sizeof(WAVEFORMATEX));

  wfx.wFormatTag = WAVE_FORMAT_PCM;

  wfx.nChannels = 2;

  wfx.nSamplesPerSec = 22050;

  wfx.nBlockAlign = 4;

  wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;

  wfx.wBitsPerSample = 16;

 

  // Set up DSBUFFERDESC structure.

 

  memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));

  dsbdesc.dwSize = sizeof(DSBUFFERDESC);

  dsbdesc.dwFlags =

    DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY; //  buffer

  dsbdesc.dwBufferBytes = 3 * wfx.nAvgBytesPerSec;

  dsbdesc.lpwfxFormat = &wfx;

 

  // Create buffer.

 

  hr = lpDirectSound->CreateSoundBuffer(&dsbdesc, &pDsb, NULL);

  if (SUCCEEDED(hr))

  {

     hr = pDsb->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*) ppDsb8);

     pDsb->Release();

  }

  return hr;

}

  这个例子中创建了一个可以持续播放3秒钟的流缓冲区,如果你要创建静态的缓冲区,你就要创建的buffersize正好能够容下你的音频数据即可。

   如果缓冲区的位置没有指定,DirectSound在条件允许的情况下将你的缓冲区设置为硬件控制,因为硬件缓冲区的混音是通过声卡的加速器来进行的,受应用程序的影响较小。

  如果你想自己控制你创建的缓冲区(buffer)的位置,那么你一定要将DSBUFFERDESC中的 dsbdesc.dwFlags成员变量设置为DSBCAPS_LOCHARDWARE或者设置为DSBCAPS_LOCSOFTWARE,如果设置为DSBCAPS_LOCHARDWARE,此时硬件设备的资源不足时,缓冲区创建失败。

 如果你想使用DirectSound的管理声音的特性,那么你创建缓冲区的时候一定要设置DSBCAPS_LOCDEFER标志,这个标志表示只有在播放的时候才分配内存,更多的细节,你可以参考动态的声音管理一节。

   你可以通过IDirectSoundBuffer8::GetCaps方法来探明已经存在的缓冲区,并且可以检查该buffer的dwFlags设置情况。

   缓冲区对象属于创建它的设备对象。当设备对象销毁时,它所创建的buffer对象也全部被销毁,没法被引用了。

   你可以通过IDirectSound8::DuplicateSoundBuffer同时创建两个或者多个包含相同数据的辅助缓冲区,当然不允许复制主缓冲区。因为复制的缓冲区和原始的缓冲区共享内存,改变复制的缓冲区的数据的同时,也改变了原始缓冲区的内容。

 

 

 

  下面我们看看buffer的控制属性

  当你创建了缓冲区的时候,你的应用程序设置该缓冲区的控制属性,这是由DSBUFFERDESC结构的dwFlags成员来控制的。

下面我们来看看dwFlags的取值

 DSBCAPS_CTRL3D  表示声音可以在3个方向上进行移动

 DSBCAPS_CTRLFX ,表示可以在缓冲区中添加特技

 DSBCAPS_CTRLFREQUENCY ,表示声音的频率可以被改动

 DSBCAPS_CTRLPAN,表示声音可以从左声道被移动到右声道

 DSBCAPS_CTRLPOSITIONNOTIFY,可以在buffer中设置通知的位置。

 DSBCAPS_CTRLVOLUME,声音的大小可以被改变。

注意,有些标志位的组合是不允许的,具体的信息可以参见DSBUFFERDESC结构。

 

 

 

 为了使得你的声卡更好的工作,你最好改变你应用程序用到的几个控制属性,其他的属性最好采用DirectSound来默认的设置。

  DirectSound通过控制属性来判断是否可以在硬件设备上分配缓冲区。例如,一个设备可能支持硬件缓冲区,但是不支持控制pan,此时,当DSBCAPS_CTRLVOLUME标志没有设置的时候,DirectSound就可以使用硬件加速。

 如果你的应用程序想调用buffer不支持的控制项,那么调用就会失败,例如,如果你想通过IDirectSoundBuffer8::SetVolume方法来设置音量,只有在创建buffer时设置了DSBCAPS_CTRLVOLUME标志这个函数调用才会成功。

 

 

 

   当你创建的辅助缓冲区的控制标志位被设置为DSBCAPS_CTRL3D时,如果该buffer创建的位置由软件控制,那么你就可以给你创建的缓冲区指定一个3D你的声音的算法,缺省的情况下,采用的是HRTF(no head-related transfer function)算法来处理3D化声音。

 

 

 

 下面我们看看如何两种buffer如何播放声音的,先看看静态的缓冲区吧。

  包含全部音频数据的缓冲区我们称为静态的缓冲区,尽管,不同的声音可能会反复使用同一个内存buffer,但严格来说,静态缓冲区的数据只写入一次。

 静态缓冲区的创建和管理和流缓冲区很相似,唯一的区别就是它们使用的方式不一样,静态缓冲区只填充一次数据,然后就可以play,然而,流缓冲区是一边play,一边填充数据。

 

 

 

 给静态缓冲区加载数据分下面几个步骤

1,  调用IDirectSoundBuffer8::Lock函数来锁定所有的内存,你要指定你锁定内存中你开始写入数据的偏移位置,并且取回该偏移位置的地址。

2,  采用标准的数据copy方法,将音频数据复制到返回的地址。

3,  调用IDirectSoundBuffer8::Unlock.,解锁该地址。

 下面的例子演示了上面提到的几个步骤,lpdsbStatic是指向静态buffer的指针

LPVOID lpvWrite;

DWORD  dwLength;

 

 

 

if (DS_OK == lpdsbStatic->Lock(

      0,          // Offset at which to start lock.

      0,          // Size of lock; ignored because of flag.

      &lpvWrite,  // Gets address of first part of lock.

      &dwLength,  // Gets size of first part of lock.

      NULL,       // Address of wraparound not needed.

      NULL,       // Size of wraparound not needed.

      DSBLOCK_ENTIREBUFFER))  // Flag.

{

  memcpy(lpvWrite, pbData, dwLength);

  lpdsbStatic->Unlock(

      lpvWrite,   // Address of lock start.

      dwLength,   // Size of lock.

      NULL,       // No wraparound portion.

      0);         // No wraparound size.

}

else

(

  ErrorHandler();  // Add error-handling here.

}

将数据加载到缓冲区中就可以播放,调用IDirectSoundBuffer8::Play方法。如下:

lpdsbStatic->SetCurrentPosition(0);

HRESULT hr = lpdsbStatic->Play(

    0,  // Unused.

    0,  // Priority for voice management.

    0); // Flags.

if (FAILED(hr))

(

  ErrorHandler();  // Add error-handling here.

}

因为这个例子中没有设置DSBPLAY_LOOPING标志,当buffer到达最后时就会自动停止,你也可以调用IDirectSoundBuffer8::Stop方法来停止播放。如果你提前停止播放,播放光标的位置就会被保存下来,因此,例子中IDirectSoundBuffer8::SetCurrentPosition方法就是为了保证从头开始播放。

 

 

 

  下面我们看看流缓冲区的用法

   流缓冲区用来播放那些比较长的声音,因为数据比较长,没法一次填充到缓冲区中,一边播放,一边将新的数据填充到buffer中。

   可以通过IDirectSoundBuffer8::Play函授来播放缓冲区中的内容,注意在该函数的参数中一定要设置DSBPLAY_LOOPING标志。

   通过IDirectSoundBuffer8::Stop方法中断播放,该方法会立即停止缓冲区播放,因此你要确保所有的数据都被播放,你可以通过拖动播放位置或者设置通知位置来实现。

  将音频流倒入缓冲区需要下面三个步骤

1确保你的缓冲区已经做好接收新数据的准备。你可以拖放播放的光标位置或者等待通知

2调用IDirectSoundBuffer8::Lock.函数锁住缓冲区的位置,这个函数返回一个或者两个可以写入数据的地址

3使用标准的copy数据的方法将音频数据写入缓冲区中

IDirectSoundBuffer8::Unlock.,解锁

 IDirectSoundBuffer8::Lock可能返回两个地址的原因在于你锁定内存的数量是随机的,有时你锁定的区域正好包含buffer的起始点,这时,就会给你返回两个地址,举个例子吧

假设你锁定了30,000字节,偏移位置为20,000字节,也就是开始位置,如果你的缓冲区的大小为40,000字节,此时就会给你返回四个数据

1内存地址的偏移位置20,000,

2从偏移位置到buffer的最末端的字节数,也是20,000,你要在第一个地址写入20,000个字节的内容

3偏移量为0的地址

4从起始点开始的字节数,也就是10,000字节,你要将这个字节数的内容写入第二个地址。

如果不包含零点,最后两个数值为NULL和0,

当然,你也有可能锁定buffer的全部内存,建议你在播放的时候不要这么做,通过你只是更新所有buffer中的一部份,例如,你可能在播放广标到达1/2位置前要将第一个1/4内存更新成新的数据,你一定不要更新play光标和Write光标间的内容。

 

 

 

BOOL AppWriteDataToBuffer(

    LPDIRECTSOUNDBUFFER8 lpDsb,  // The buffer.

    DWORD dwOffset,              // Our own write cursor.

    LPBYTE lpbSoundData,         // Start of our data.

    DWORD dwSoundBytes)          // Size of block to copy.

{

  LPVOID  lpvPtr1;

  DWORD dwBytes1;

  LPVOID  lpvPtr2;

  DWORD dwBytes2;

  HRESULT hr;

 

  // Obtain memory address of write block. This will be in two parts

  // if the block wraps around.

 

  hr = lpDsb->Lock(dwOffset, dwSoundBytes, &lpvPtr1,

      &dwBytes1, &lpvPtr2, &dwBytes2, 0);

 

  // If the buffer was lost, restore and retry lock.

 

  if (DSERR_BUFFERLOST == hr)

  {

    lpDsb->Restore();

    hr = lpDsb->Lock(dwOffset, dwSoundBytes,

        &lpvPtr1, &dwBytes1,

        &lpvPtr2, &dwBytes2, 0);

  }

  if (SUCCEEDED(hr))

  {

    // Write to pointers.

 

    CopyMemory(lpvPtr1, lpbSoundData, dwBytes1);

    if (NULL != lpvPtr2)

    {

      CopyMemory(lpvPtr2, lpbSoundData+dwBytes1, dwBytes2);

    }

 

    // Release the data back to DirectSound.

 

    hr = lpDsb->Unlock(lpvPtr1, dwBytes1, lpvPtr2,

    dwBytes2);

    if (SUCCEEDED(hr))

    {

      // Success.

      return TRUE;

    }

  }

 

  // Lock, Unlock, or Restore failed.

 

  return FALSE;

}

 

 

 

下面我们看看如何控制播放的属性

你可以通过IDirectSoundBuffer8::GetVolume and IDirectSoundBuffer8::SetVolume函数来获取或者设置正在播放的音频的音量的大小。

 如果设置主缓冲区的音量就会改变声卡的音频的声量大小。音量的大小,用分贝来表示,一般没法来增强缺省的音量,这里要提示一下,分贝的增减不是线形的,减少3分贝相当于减少1/2的能量。最大值衰减100分贝几乎听不到了。

 通过IDirectSoundBuffer8::GetFrequency and IDirectSoundBuffer8::SetFrequency方法可以获取设置音频播放的频率,主缓冲区的频率不允许改动,

通过  IDirectSoundBuffer8::GetPan and IDirectSoundBuffer8::SetPan函数可以设置音频在左右声道播放的位置,具有3D特性的缓冲区没法调整声道。

 

 

 

  下面要看看混音。

   如果辅助缓冲区中的音频同时播放就会主缓冲区自动的混音,在WDM驱动模式下,混音的工作由核心混音器来完成,不同的辅助缓冲区可能具有不同的WAV格式(例如,不同的采样频率),在必要的时候,辅助缓冲区的格式要转换成主缓冲区,或者核心混音器的格式。

  在VXD驱动模式下,如果你的辅助缓冲区都采用相同的音频格式,并且硬件的音频格式也和你的音频格式匹配,此时,混音器不用作任何的转换。你的应用程序可以创建一个主缓冲区,然后通过IDirectSoundBuffer8::SetFormat来设置硬件的输出格式。要注意,只有你的协作度一定要是Priority Cooperative Level.,并且,一定要创建辅助缓冲区前设置主缓冲区,DirectSound会将你的设置保存下来。

  在WDM模式下,对主缓冲区的的设置没有作用,因为主缓冲区的格式是由内核混音器来决定的。

 

 

 

循环播放;

Directsound并不支持在buffer内部的循环或者声音的一部份循环,DSBPLAY_LOOPING标志是整个buffer到end处然后重新从头的播放,如果你在静态的缓冲区的播放到末端后然后将play光标设置到起始位置重新播放,会导致audio glitches,因为DirectSound必须忽略所有的预处理的数据。所以,如果要循环播放,一定要在stream buffer中进行。

 

 

 

最后,我们来谈一下缓冲区管理。

  通过IDirectSoundBuffer8::GetCaps方法我们可以获取DirectSoundBuffer对象的一些属性。应用程序可以通过IDirectSoundBuffer8::GetStatus方法还获取buffer是播放还是停止状态。

   通过IDirectSoundBuffer8::GetFormat方法可以获取buffer中音频数据的格式,你也可以调用IDirectSoundBuffer8::SetFormat方法来主缓冲区的数据格式。

  Sound Buffer中的一些数据在某些条件下有可能丢失,例如,如果buffer位于声卡的内存中,此时其他的应用程序获取硬件的控制权并请求资源。当具有Write_primary权限的应用程序moves to forground,此时,Directsound就会使其他的buffer内容丢失才能够让foreground的应用程序直接向主缓冲区中写数据。

   当IDirectSoundBuffer8::Lock or IDirectSoundBuffer8::Play方法向一个lost buffer进行操作时,就会返回一个错误码。当造成buffer丢失的应用程序降低协作度,低于write_primary,或者moves to background,其他的应用程序可以调用IDirectSoundBuffer8::Restore来重新分配内存,如果成功,这个方法就会恢复内存中的内容以及对该内存的设置,但是,恢复的缓冲区中不包含合法的数据,因此,应用程序要重新向该buffer中填写数据

3.3Using WAV Data

  在WDM驱动模式下,DirectSound缓冲区支持如下WAV格式:多声道,多个扬声器配置,例如5.1,在前左,前中,前右,后左,后右都有扬声器,超重低音。也支持多于16的采样精度。

  这种格式可以用WAVEFORMATEXTENSIBLE结构来描述,这个结构是WAVEFORMATEX的扩展,

  对于多声道,DirectSound并不支持3D。

 

 

 

  下面我讲一下如何计算wave的播放时间

  Wave格式的播放长度由数据大小和格式决定,可以通过CWaveFile::GetSize和CWaveFile::GetFormat来获取数据的大小和格式

下面的代码告诉你如何计算wave的总播放时间以毫秒为时间单位

DWORD GetSoundLength(LPSTR strFileName)

{

  CWaveFile* pWav;

  DWORD dwLen = 0;

  DWORD dwSize;

  WAVEFORMATEX* wfx;

 

  pWav = new CWaveFile();

  if (SUCCEEDED(pWav->Open(strFileName, NULL, WAVEFILE_READ)))

  {

    wfx = pWav->GetFormat();

    dwSize = pWav->GetSize();

    dwLen = (DWORD) (1000 * dwSize / wfx->nAvgBytesPerSec);

    pWav->Close();

  }

  if (pWav) delete pWav;

  return dwLen;

}

 

 

 

3.43-D Sound

 

 

 

3.5增加声音特技Using Effects

   DirectX通过DirectX  Media Objects(DMOs)来支持在声音中添加特技。

为了使用特技,DirectSound 应用程序必须首先CoInitialize来初始化Com库,当然也不排除通过DirectSoundCreate8来创建设备对象。

  对于在DSBUFFERDESC结构中设置DSBCAPS_CTRLFX标志的辅助缓冲区,你可以在该缓冲区添加任意的特技,但是,buffer一定要处于停止状态并且没有被锁定,在DiectX 9.0中,特技不依靠硬件来加速。当然也可以在硬件缓冲区中设置特技,但这样做没有任何的好处。

  对于很小的缓冲区,特技也许并不能好好工作,DirectSound不提倡在小于150毫秒的数据缓冲区中使用特技,这个值被定义为DSBSIZ_FX_MIN。

   下面的代码在缓冲设置了回声特技。

HRESULT SetEcho(LPDIRECTSOUNDBUFFER8 pDSBuffer)

{

  HRESULT hr;

  DWORD dwResults[1];  // One element for each effect.

 

  // Describe the effect.

  DSEFFECTDESC dsEffect;

  memset(&dsEffect, 0, sizeof(DSEFFECTDESC));

  dsEffect.dwSize = sizeof(DSEFFECTDESC);

  dsEffect.dwFlags = 0;

  dsEffect.guidDSFXClass = GUID_DSFX_STANDARD_ECHO; //设置特技

 

  // Set the effect

  if (SUCCEEDED(hr = pDSBuffer->SetFX(1, &dsEffect, dwResults)))

  {

    switch (dwResults[0])

    {

      case DSFXR_LOCHARDWARE:

        OutputDebugString("Effect was placed in hardware.");

        break;

      case DSFXR_LOCSOFTWARE:

        OutputDebugString("Effect was placed in software.");

        break;

      case DSFXR_UNALLOCATED:

        OutputDebugString("Effect is not yet allocated to hardware or software.");

        break;

    }

  }

  return hr;

}

 

 

 

 为了获取或者设置特技的参数,首先你要从包含特技的缓冲区对象中获取相应的特技接口指针。下面列出DirectSound支持的特技接口。

·         IDirectSoundFXChorus8

·         IDirectSoundFXCompressor8

·         IDirectSoundFXDistortion8

·         IDirectSoundFXEcho8

·         IDirectSoundFXFlanger8

·         IDirectSoundFXGargle8

·         IDirectSoundFXI3DL2Reverb8

·         IDirectSoundFXParamEq8

·         IDirectSoundFXWavesReverb8

下面我简单介绍一下标准的特技

Chorus,

Compression

Distortion

Echo

Environmental Reverberation

Flange

Gargle

Parametric Equalizer

Waves Reverberation

3.6录制Capturing Waveforms

1枚举录音的设备

如果你的程序只是想从用户缺省的设备上进行声音的录制,那么就没有必要来枚举出系统中的所有录音的设备,当你调用DirectSoundCaptureCreate8 或者另外一个函数

 DirectSoundFullDuplexCreate8的时候,其实就默认指定了一个缺省的录音设备。

当然,在下面的情况下,你就必须要枚举系统中所有的设备,

DWORD pv;  // Can be any 32-bit type.

 

HRESULT hr = DirectSoundCaptureEnumerate(

    (LPDSENUMCALLBACK)DSEnumProc, (VOID*)&pv);

2创建设备对象

你可以通过DirectSoundCaptureCreate8或者DirectSoundFullDuplexCreate8函数直接创建设备对象,该函数返回一个指向IDirectSoundCapture8接口的指针

 

 

 

3录音设备的性能

你可以通过IDirectSoundCapture8::GetCaps方法来获取录音的性能,这个函数的参数是一个DSCCAPS类型的结构,在传递这个参数之前,一定要初始化该结构的dwSize成员变量。同时,你可以通过这个结构返回设备支持的声道数,以及类似WAVEINCAPS结构的其他设备属性

4创建录音的buffer

我们可以通过IDirectSoundCapture8::CreateCaptureBuffer来创建一个录音的buffer对象,这个函数的一个参数采用DSCBUFFERDESC类型的结构来说明buffer的一些特性,这个结构的最后一个成员变量是一个WAVEFORMATEX结构,这个结构一定要初始化成泥需要的wav格式。

说明一下,如果你的应用程序一边播放的同时进行录制,如果你录制的buffer格式和你的主缓冲buffer不一样,那么你创建录制buffer对象就会失败,原因在于,有些声卡只支持一种时钟,不能同时支持录音和播放同时以两种不同的格式进行。

  下面的函数,演示了如何创建一个录音的buffer对象,这个buffer对象能够处理1秒的数据,注意,这里传递的录音设备对象参数一定要通过DirectSoundCaptureCreate8来创建,而不是早期的DirectSoundCaptureCreate接口,否则,buffer对象不支持IDirectSoundCaptureBuffer8接口,

HRESULT CreateCaptureBuffer(LPDIRECTSOUNDCAPTURE8 pDSC,

                            LPDIRECTSOUNDCAPTUREBUFFER8* ppDSCB8)

{

  HRESULT hr;

  DSCBUFFERDESC               dscbd;

  LPDIRECTSOUNDCAPTUREBUFFER  pDSCB;

  WAVEFORMATEX                wfx =

    {WAVE_FORMAT_PCM, 2, 44100, 176400, 4, 16, 0};

    // wFormatTag, nChannels, nSamplesPerSec, mAvgBytesPerSec,

    // nBlockAlign, wBitsPerSample, cbSize

 

  if ((NULL == pDSC) || (NULL == ppDSCB8)) return E_INVALIDARG;

  dscbd.dwSize = sizeof(DSCBUFFERDESC);

  dscbd.dwFlags = 0;

  dscbd.dwBufferBytes = wfx.nAvgBytesPerSec;

  dscbd.dwReserved = 0;

  dscbd.lpwfxFormat = &wfx;

  dscbd.dwFXCount = 0;

  dscbd.lpDSCFXDesc = NULL;

 

  if (SUCCEEDED(hr = pDSC->CreateCaptureBuffer(&dscbd, &pDSCB, NULL)))

  {

    hr = pDSCB->QueryInterface(IID_IDirectSoundCaptureBuffer8, (LPVOID*)ppDSCB8);

    pDSCB->Release(); 

  }

  return hr;

}

5通过录音buffer对象获取信息

 你可以通过IDirectSoundCaptureBuffer8::GetCaps方法来获取录音buffer的大小,但一定要记得初始化DSCBCAPS结构类型参数的dwSize成员变量。

为了获取buffer中数据的格式,你可以通过IDirectSoundCaptureBuffer8::GetFormat.方法来获取buffer中的数据格式,这个函数通过WAVEFORMATEX结构返回音频数据的信息,

 如果我们想知道一个录音buffer目前的状态如何,可以通过IDirectSoundCaptureBuffer8::GetStatus来获取,这个函数通过一个DWORD类型的参数来表示该buffer是否正在录音,

IDirectSoundCaptureBuffer8::GetCurrentPosition方法可以获取buffer中read指针和录制指针的偏差。Read指针指向填充到该buffer中的数据的最末端,capture指针则指向复制到硬件的数据的末端,你read指针指向的前段数据都是安全数据,你都可以安全的复制。

6录音buffer对象通知机制

 为了安全的定期的从录音buffer中copy数据,你的应用程序就要知道,什么时候read指针指向了特定的位置,一个方法是通过IDirectSoundCaptureBuffer8::GetCurrentPosition.方法来获取read指针的位置,另外一个更有效的方法采用通知机制,通过IDirectSoundNotify8::SetNotificationPositions方法,你可以设置任何一个小于buffer的位置

来触发一个事件,切记,当buffer正在running的时候,不要设置。

如何来设置一个触发事件呢,首先要得到IDirectSoundNotify8接口指针,你可以通过buffer对象的QuerInterface来获取这个指针接口,对于你指定的任何一个position,你都要通过CreateEvent方法,创建一个win32内核对象, 然后将内核对象的句柄赋给DSBPOSITIONNOTIFY结构的hEventNotify成员,通过该结构的dwOffset来设置需要通知的位置在buffer中的偏移量。

最后将这个结构或者结构数组,传递给SetNotificationPositions函数,下面的例子设置了三个通知,当position达到buffer的一半时会触发一个通知,当达到buffer的末端时,触发第二个通知,当录音停止时触发第三个通知消息。

HRESULT SetCaptureNotifications(LPDIRECTSOUNDCAPTUREBUFFER8 pDSCB)

{

  #define cEvents  3

 

 

 

  LPDIRECTSOUNDNOTIFY8 pDSNotify;

  WAVEFORMATEX         wfx; 

  HANDLE     rghEvent[cEvents] = {0};

  DSBPOSITIONNOTIFY  rgdsbpn[cEvents];

  HRESULT    hr;

 

 

 

  if (NULL == pDSCB) return E_INVALIDARG;

  if (FAILED(hr = pDSCB->QueryInterface(IID_IDirectSoundNotify, (LPVOID*)&pDSNotify)))

  {

    return hr;

  }

  if (FAILED(hr = pDSCB->GetFormat(&wfx, sizeof(WAVEFORMATEX), NULL)))

  {

    return hr;

  }

 

 

 

  // Create events.

  for (int i = 0; i < cEvents; ++i)

  {

    rghEvent[i] = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (NULL == rghEvent[i])

    {

      hr = GetLastError();

      return hr;

    }

  }

 

  // Describe notifications.

 

  rgdsbpn[0].dwOffset = (wfx.nAvgBytesPerSec/2) -1;

  rgdsbpn[0].hEventNotify = rghEvent[0];

 

  rgdsbpn[1].dwOffset = wfx.nAvgBytesPerSec - 1;

  rgdsbpn[1].hEventNotify = rghEvent[1];

 

  rgdsbpn[2].dwOffset = DSBPN_OFFSETSTOP;

  rgdsbpn[2].hEventNotify = rghEvent[2];

 

  // Create notifications.

 

  hr = pDSNotify->SetNotificationPositions(cEvents, rgdsbpn);

  pDSNotify->Release();

  return hr;

}

7录音的步骤

录音主要包括下面几个步骤

1调用IDirectSoundCaptureBuffer8::Start使buffer对象开始工作,通过你要给这个函数dwFlags传递一个DSCBSTART_LOOPING参数,注意,buffer就会不停工作,而不是当buffer填充满了就停止工作,当缓冲区满了后,就会从头重新填充

2等待你期望的事件通知,当buffer被填充到某个你期望的位置时,会触发通知。

3当你收到通知的时,你就要调用IDirectSoundCaptureBuffer8::Lock来锁住bufer的一部份,切记,不要将capture指针指向的内存锁住,你可以调用IDirectSoundCaptureBuffer8::GetCurrentPosition方法来获取read指针的位置。在传递给Lock函数的参数中,你一定要指定内存的大小和偏移量,这个函数会返回你锁住的内存的起始地址,以及block的大小,

4从你锁住的内存中复制data,

5复制完成后要记得IDirectSoundCaptureBuffer8::Unlock方法来解锁内存

6在你停止录音之前,你可以反复的重复2~5步骤,如果你想停止录音了,你可以调用

IDirectSoundCaptureBuffer8::Stop方法。

 

 

 

9将录音写入wav文件

WAV文件是采用RIFF格式的文件,在文件中包含一系列的chunks,来描述头信息和数据信息,win32API提供一套mmio系列函数用来操作RIFF格式的文件,但是Directsound并没有提供读写wav格式文件的函数,但是,Directsound里封装了一个CWaveFile类用来操作wav文件,可以通过open来写入文件的头信息,write来写入文件的数据,close函数写入文件的长度,关闭文件。

下面是代码,如何创建一个wav格式的文件

CWaveFile   g_pWaveFile;

WAVEFORMATEX  wfxInput;

 

ZeroMemory( &wfxInput, sizeof(wfxInput));

wfxInput.wFormatTag = WAVE_FORMAT_PCM;

wfxInput.nSamplesPerSec = 22050

wfxInput.wBitsPerSample =  8;

wfxInput.nChannels = 1;

wfxInput.nBlockAlign =

    wfxInput.nChannels * (wfxInput.wBitsPerSample / 8);

wfxInput.nAvgBytesPerSec =

    wfxInput.nBlockAlign * wfxInput.nSamplesPerSec;

 

g_pWaveFile = new CWaveFile;

if (FAILED(g_pWaveFile->Open("mywave.wav", &wfxInput,

    WAVEFILE_WRITE)))

{

  g_pWaveFile->Close();

}

下面的代码就演示了如何从buffer对象中将数据写入文件中

HRESULT RecordCapturedData()

{

  HRESULT hr;

  VOID* pbCaptureData  = NULL;

  DWORD dwCaptureLength;

  VOID* pbCaptureData2 = NULL;

  DWORD dwCaptureLength2;

  VOID* pbPlayData   = NULL;

  UINT  dwDataWrote;

  DWORD dwReadPos;

  LONG lLockSize;

 

  if (NULL == g_pDSBCapture)

      return S_FALSE;

  if (NULL == g_pWaveFile)

      return S_FALSE;

 

  if (FAILED (hr = g_pDSBCapture->GetCurrentPosition(

    NULL, &dwReadPos)))

      return hr;

 

  // Lock everything between the private cursor

  // and the read cursor, allowing for wraparound.

 

  lLockSize = dwReadPos - g_dwNextCaptureOffset;

  if( lLockSize < 0 ) lLockSize += g_dwCaptureBufferSize;

 

  if( lLockSize == 0 ) return S_FALSE;

 

  if (FAILED(hr = g_pDSBCapture->Lock(

        g_dwNextCaptureOffset, lLockSize,

        &pbCaptureData, &dwCaptureLength,

        &pbCaptureData2, &dwCaptureLength2, 0L)))

    return hr;

 

  // Write the data. This is done in two steps

  // to account for wraparound.

 

 

 

  if (FAILED( hr = g_pWaveFile->Write( dwCaptureLength,

      (BYTE*)pbCaptureData, &dwDataWrote)))

    return hr;

 

  if (pbCaptureData2 != NULL)

  {

    if (FAILED(hr = g_pWaveFile->Write(

        dwCaptureLength2, (BYTE*)pbCaptureData2,

        &dwDataWrote)))

      return hr;

  }

 

  // Unlock the capture buffer.

 

 g_pDSBCapture->Unlock( pbCaptureData, dwCaptureLength,

    pbCaptureData2, dwCaptureLength2  );

 

  // Move the capture offset forward.

 

  g_dwNextCaptureOffset += dwCaptureLength;

  g_dwNextCaptureOffset %= g_dwCaptureBufferSize;

  g_dwNextCaptureOffset += dwCaptureLength2;

  g_dwNextCaptureOffset %= g_dwCaptureBufferSize;

 

  return S_OK;

}

 4DirectSound开发高级技巧

 

 

 

4.1Dsound驱动模型(DirectSound Driver Models)

在VXD驱动模型下,所有的DirectSound的混音工作都是由Dsound.vxd来完成的,一个虚拟的设备驱动程序。Dsound.vxd也提供操作声卡从Cpu接收数据的缓冲区的方法,这其实和DirectSound的主缓冲区是类似的。DirectSound应用程序可以给主缓冲区设置特定的属性,例如,采样频率,或者采样精度,也就改变了硬件设备。

在WDM驱动模式下,DirectSound并不直接操作硬件,当然,操作硬件加速缓冲区除外。相应的,DirectSound将数据送往内核混音器,内核混音器的工作就是将多个格式的音频流调整为一个统一的格式,将它们进行混音,然后将结果送到硬件上进行播放。其实,它的工作和Dsound.vxd的工作类似。一个重要的区别在于Dsound.vxd仅仅对DirectSound的内存缓冲区进行混音,内核混音器会对所有的windows音频数据进行混音,包括那些使用waveOut win32函数输出数据的应用,DirectSound和Wave格式的音频输出设备不能同时打开在WDM驱动模式下是不成立的。

 最重要的是内核混音器和音频硬件的关系,内核混音器就是一个系统中的软件,可以用来指定硬件的DMA缓冲区。它根据它要进行混音的音频数据格式,来选择硬件的格式,它会将它混音的音频数据以一种高质量的形式进行输出,或者根据硬件的情况选择近似的音频质量。

这里有一个很重要的暗示,就是DirectSound不能设置硬件的DMA缓冲区格式。对于你的应用程序而言,硬件格式其实就是根据你实际播放的音频的格式来定的。如果你play的是44kHZ,内核混音器就会将所有的数据都混音成44kHZ,同时也保证硬件以44Khz进行输出。

作为应用程序的开发者,你没法来选择系统驱动模式,因为驱动模式的选择有声卡类型,windows版本,以及安装的驱动程序来控制。由于这个原因,你在测试你的程序时要注意所有的情况,DirectSound或许用的是Dsound.vxd或者用的是内核混音器,你要确保你的应用程序对两种方式都支持。

 

 

 

4.2设置硬件的扩展属性(System Property Sets)

 

 

 

4.3Property Sets for DirectSound Buffers

   DirectSound设备的属性可以通过下面的类对象来获取,该类的ID为CLSID_DirectSoundPrivate (11AB3EC0-25EC-11d1-A4D8-00C04FC28ACA). 该类支持IKsPropertySet接口,CLSID和属性的定义在Dsconf.h文件中可以找到。

1要想创建该类的对象,首先要通过LoadLibrary加载Dsound.dll

2调用GetProcAddress函数来获取DllGetClassObject函数接口。

3通过DllGetClassObject函数来获取IClassFactory接口,CLSID_DirectSoudnPrivate类厂对象接口。

4调用IClassFactory::CreateInstance来创建CLSID_DirectSoundPrivate对象,并获取IKsPropertySet接口(IID_IKsPropertySet)。

CLSID_DirectSoundPrivate对象只支持一个属性设置接口DSPROPSETID_DirectSoundDevice(84624f82-25ec-11d1-a4d8-00c04fc28aca)这个属性设置接口暴露了下面三个只读的属性

DSPROPERTY_DIRECTSOUNDDEVICE_WAVEDEVICEMAPPING

DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION

DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE

DSPROPERTY_DIRECTSOUNDDEVICE_WAVEDEVICEMAPPING这个属性根据指定的设备的名称来获取该设备的GUID。

获取的属性数据包含在

DSPROPERTY_DIRECTSOUNDDEVICE_WAVEDEVICEMAPPING_DATA结构里,这个结构包含下面的成员

Member

Type

Description

DeviceName

String

[in] Name of device

DataFlow

DIRECTSOUNDDEVICE_DATAFLOW

[in] Direction of data flow, either DIRECTSOUNDDEVICE_DATAFLOW_RENDER or DIRECTSOUNDDEVICE_DATAFLOW_CAPTURE

DeviceID

GUID

[out] DirectSound device ID

 

 

 

DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION属性

  该属性可以返回指定GUID设备的全部的属性描述,可以通过下面的结构返回数据

DSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATA这个结构的成员如下

Member

Type

Description

Type

DIRECTSOUNDDEVICE_TYPE

[out] Device type: DIRECTSOUNDDEVICE_TYPE_EMULATED, DIRECTSOUNDDEVICE_TYPE_VXD or DIRECTSOUNDDEVICE_TYPE_WDM

DataFlow

DIRECTSOUNDDEVICE_DATAFLOW

[in, out] Direction of data flow, either DIRECTSOUNDDEVICE_DATAFLOW_RENDER or DIRECTSOUNDDEVICE_DATAFLOW_CAPTURE

DeviceID

GUID

[in] DirectSound device GUID, or NULL for the default device of the type specified by DataFlow

Description

String

[out] Description of DirectSound device

Module

String

[out] Module name of the DirectSound driver

Interface

String

[out] PnP device interface name

WaveDeviceID

ULONG

[out] Identifier of the corresponding Windows Multimedia device

 

 

 

 DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE属性

这个属性用来枚举所有的DirectSound的播放或者录音设备。可以通过

DSPROPERTY_DIRECTSOUNDDEVICE_ENUMERATE_DATA结构将属性值返回,这个结构的成员如下

Member

Type

Description

Callback

LPFNDIRECTSOUNDDEVICEENUMERATECALLBACK

[in] Application-defined callback function. WhenIKsPropertySet::Get is called, this function is called once for each device enumerated. It takes as parameters aDSPROPERTY_DIRECTSOUNDDEVICE_DESCRIPTION_DATAstructure describing the enumerated device, and the value in Context.

Context

LPVOID

[in] User-defined value to be passed to the callback function for each device enumerated.

 

 

 

4.4如何优化Directsound(Optimizing DirectSound Performance)

主要讲三个方面的内容,如何用硬件进行混音,动态声音管理,有效地使用缓冲区

 

 

 

   许多声卡都拥有自己的辅助缓冲区,可以处理3-D特技和混音特技。这些缓冲区,就像它们的名字一样,其实是存在于系统的内存中,而不是在声卡上,但是这些缓冲区是由声卡来直接操作的,所以比需要处理器来控制软件缓冲区,速度要快许多。所以,在硬件条件允许的条件下要多申请硬件的缓冲区,特别是3-D缓冲区。

   缺省的情况下,DirectSound会优先申请硬件缓冲区,但是能够申请多少硬件的缓冲区是由硬件设备的性能决定的。硬件在同一时刻能播放的声音数量越多,可申请的硬件缓冲区的数量就越多,当创建一个缓冲区时,DirectSound就分配一个hardware voice,缓冲区销毁时就释放hardware voice。如果一个应用程序创建了很多的缓冲区,但是,很多缓冲区是由软件来销毁的,也就是说,这些缓冲区是由CPU而不是声卡来控制和混音的。

   注意:DirectSound 声音管理器分配的是硬件混音资源,并不是缓冲区,在一个PCI板卡上,缓冲区在分配前后都占用相同大小的内存,不管是将该缓冲区分配给硬件还是软件混音器。

 

 

 

   动态的声音管理,通过在缓冲区播放才进行voice  allocation,获取提前结束那么权限比较低的音频数据播放,释放他们的资源,从而可以减轻硬件设备的压力。

   当缓冲区正在play的时候,为了延迟硬件资源用来分配给混音,3-D特技,可以在创建缓冲区对象时,将DSBUFFERDESC结构的dwFlages设置为DSBCAPS_LOCDEFER标志,传递给IDirectSound8::CreateSoundBuffer.函数,当你对这样的缓冲区进行IDirectSoundBuffer8::Play orIDirectSoundBuffer8::AcquireResources操作时,DrectSound会尽可能的在硬件上play。

   当调用Play的时候,你可以试图通过传递下面表格中的参数将其他正在使用的硬件 voice资源释放掉。从而供你的buffer使用

  

DSBPLAY_TERMINATEBY_TIME

Select the buffer that has been playing longer than any other candidate buffers.

DSBPLAY_TERMINATEBY_DISTANCE

Select the 3-D candidate buffer farthest from the listener.

DSBPLAY_TERMINATEBY_PRIORITY

Select the buffer that has the lowest priority of candidate buffers, as set in the call to Play. If this is combined with one of the other two flags, the other flag is used only to resolve ties.

DirectSound会根据你设置的标志,对所有正在play的buffer进行搜索,如果找到符合条件的缓冲区,DirectSound就会停掉该资源,然后将该资源分配给你的心的缓冲区使用。如果没有找到符合条件的缓冲区,那么你的新创建的缓冲区只好在软件缓冲区播放了,当然,如果你设置了DSBPLAY_LOCHARDWARE标志,此时,play调用就会失败。

 

 

 

下面我们看看如何更有效地使用缓冲区。

   当使用流缓冲区的时候,要限制通知的次数和数据读写的次数。不要创建有很多通知positions的缓冲区,或者太小的缓冲区。流缓冲区在小于3个通知位置时工作的效率最高。当你改变一个辅助缓冲区的控制项时,此时效率就会受影响,所以,要尽可能少的调用IDirectSoundBuffer8::SetVolume,IDirectSoundBuffer8::SetFrequency.

IDirectSoundBuffer8::SetPan, 函数,例如,你有个习惯总是喜欢在控制面板上来回的拖动左右声道的位置。

  一定要记住,3D缓冲区是需要占用的更多的CPU的。所以,要注意下面的事情

 1将经常播放的sounds放到硬件缓冲区中,

 2 Don't create 3-D buffers for sounds that won't benefit from the effect.

 3通过IDirectSound3DBuffer8::SetMode函数,设置DS3DMODE_DISABLE标志来停止对3d缓冲区的3d处理,

 4最好批量的参数进行调整。

 

 

 

4.5向主缓冲区写数据(Writing to the Primary Buffer)

  当应用程序需要一些特殊的混音或者特技,而辅助缓冲区不支持这些功能,那么DirectSOund允许直接曹操主缓冲区,

  当你获得操作主缓冲区的权限时,其他的DirectSound特性就变得不可用了,辅助缓冲区没法混音,硬件加速混音器也无法工作。

   大多数的应用程序应该使用辅助缓冲区避免直接操作主缓冲区,因为可以申请大块的辅助缓冲区,可以提高足够长的写入时间,从而避免了音频数据产生缝隙的危险。

  只有当主缓冲区硬件时你才能操作它,所以你可以通过IDirectSoundBuffer8::GetCaps函数来查询,该函数的参数结构dwFlages成员设置为DSBCAPS_LOCHARDWARE,如果你想锁定一个正在被软件枚举的主缓冲区,会失败的。

  你通过IDirectSound8::CreateSoundBuffer函数来创建主缓冲区,只要设置DSBCAPS_PRIMARYBUFFER标志即可。同时要保证你的协作度为DSSCL_WRITEPRIMARY。

下面的代码演示了如何获取向主缓冲区写数据的权限

BOOL AppCreateWritePrimaryBuffer(

  LPDIRECTSOUND8 lpDirectSound,

  LPDIRECTSOUNDBUFFER *lplpDsb,

  LPDWORD lpdwBufferSize,

  HWND hwnd)

{

  DSBUFFERDESC dsbdesc;

  DSBCAPS dsbcaps;

  HRESULT hr;

  WAVEFORMATEX wf;

 

  // Set up wave format structure.

  memset(&wf, 0, sizeof(WAVEFORMATEX));

  wf.wFormatTag = WAVE_FORMAT_PCM;

  wf.nChannels = 2;

  wf.nSamplesPerSec = 22050;

  wf.nBlockAlign = 4;

  wf.nAvgBytesPerSec =

      wf.nSamplesPerSec * wf.nBlockAlign;

  wf.wBitsPerSample = 16;

 

  // Set up DSBUFFERDESC structure.

  memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));

  dsbdesc.dwSize = sizeof(DSBUFFERDESC);

  dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;

  // Buffer size is determined by sound hardware.

  dsbdesc.dwBufferBytes = 0;

  dsbdesc.lpwfxFormat = NULL; // Must be NULL for primary buffers.

 

  // Obtain write-primary cooperative level.

  hr = lpDirectSound->SetCooperativeLevel(hwnd, DSSCL_WRITEPRIMARY);

  if SUCCEEDED(hr)

  {

    // Try to create buffer.

    hr = lpDirectSound->CreateSoundBuffer(&dsbdesc,

      lplpDsb, NULL);

    if SUCCEEDED(hr)

    {

      // Set primary buffer to desired format.

      hr = (*lplpDsb)->SetFormat(&wf);

      if SUCCEEDED(hr)

      {

        // If you want to know the buffer size, call GetCaps.

        dsbcaps.dwSize = sizeof(DSBCAPS);

        (*lplpDsb)->GetCaps(&dsbcaps);

        *lpdwBufferSize = dsbcaps.dwBufferBytes;

        return TRUE;

      }

    }

  }

  // Failure.

  *lplpDsb = NULL;

  *lpdwBufferSize = 0;

  return FALSE;

}

 

 

 

下面的代码演示了应用程序如何混音的,其中CustomMixer函数是用来将几个音频流混音的函数AppMixIntoPrimaryBuffer 应该定时的被调用。

BOOL AppMixIntoPrimaryBuffer(

    APPSTREAMINFO* lpAppStreamInfo,

    LPDIRECTSOUNDBUFFER lpDsbPrimary,

    DWORD dwDataBytes,

    DWORD dwOldPos,

    LPDWORD lpdwNewPos)

{

  LPVOID lpvPtr1;

  DWORD dwBytes1;

  LPVOID lpvPtr2;

  DWORD dwBytes2;

  HRESULT hr;

  // Obtain write pointer.

  hr = lpDsbPrimary->Lock(dwOldPos, dwDataBytes,

          &lpvPtr1, &dwBytes1,

          &lpvPtr2, &dwBytes2, 0);

 

  // If DSERR_BUFFERLOST is returned, restore and retry lock.

 

  if (DSERR_BUFFERLOST == hr)

  {

    lpDsbPrimary->Restore();

    hr = lpDsbPrimary->Lock(dwOldPos, dwDataBytes,

            &lpvPtr1, &dwBytes1,

            &lpvPtr2, &dwBytes2, 0);

  }

  if SUCCEEDED(hr)

  {

    // Mix data into the returned pointers.

    CustomMixer(lpAppStreamInfo, lpvPtr1, dwBytes1);

    *lpdwNewPos = dwOldPos + dwBytes1;

    if (NULL != lpvPtr2)

    {

      CustomMixer(lpAppStreamInfo, lpvPtr2, dwBytes2);

      *lpdwNewPos = dwBytes2; // Because it wrapped around.

    }

    // Release the data back to DirectSound.

    hr = lpDsbPrimary->Unlock(lpvPtr1, dwBytes1,

            lpvPtr2, dwBytes2);

    if SUCCEEDED(hr)

    {

      return TRUE;

    }

  }

  // Lock or Unlock failed.

  return FALSE;

}

 

你可能感兴趣的:(DirectSound开发指南)