• Slimer软工课设日报-2016年7月2日


    想给游戏添加丰富的背景音乐甚至音效,然而目前只能简单的利用playsound()函数播放一首音乐,虽说要同时播放音效的话可以调用多线程,但未免有些占内存,所以需要别的方法来实现音乐功能。
    通过搜索,基本了解到了openAL、DirectSound、waveout等实现音频输出的方式,本文主要介绍DirectSound方法
    本文记录DirectSound播放音频的技术。DirectSound是Windows下最常见的音频播放技术。目前大部分的音频播放应用都是通过DirectSound来播放的。本文记录一个使用DirectSound播放PCM的例子。

    注:一位仁兄已经提醒我DirectSound已经计划被XAudio2取代了。后来考证了一下发现确有此事。因此在下次更新中考虑加入XAudio2播放PCM的例子。本文仍然记录一下DirectSound这位“元老”。

    DirectSound简介

    DirectSound是微软所开发DirectX的组件之一,可以在Windows 操作系统上录音,并且记录波形音效(waveform sound)。目前DirectSound 是一个成熟的API ,提供许多有用的功能,例如能够在较高的分辨率播放多声道声音。
    DirectSound3D(DS3D)最早是1993年与 DirectX 3 一起发表的。DirectX 8以后的DirectSound和DirectSound3D的(DS3D)被合称DirectX Audio。


    DirectSound有以下几种对象:

    对象

    数量

    作用

    主要接口

    设备

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

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

    IDirectSound8

    辅助缓冲区

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

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

    IDirectSoundBuffer8,

    IDirectSound3DBuffer8,

    IDirectSoundNotify8

    主缓冲区

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

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

    IDirectSoundBuffer,

    IDirectSound3DListener8

     

    DirectSound播放音频的流程

    使用DirectSound播放音频一般情况下需要如下步骤:

    1. 初始化

    1) 创建一个IDirectSound8接口的对象
    2) 设置协作级
    3) 创建一个主缓冲对象
    4) 创建一个副缓冲对象
    5) 创建通知对象
    6) 设置通知位置

    7) 开始播放

    2. 循环播放声音

    1) 数据填充至副缓冲区

    2) 等待播放完成

    下面结合详细分析一下上文的流程。


    1. 初始化
    1) 创建一个IDirectSound8接口的对象

    通过DirectSoundCreate8()方法可以创建一个设备对象。这个对象通常代表缺省的播放设备。DirectSoundCreate8()函数原型如下。

    1 HRESULT DirectSoundCreate8(  
    2      LPCGUID lpcGuidDevice,  
    3      LPDIRECTSOUND8 * ppDS8,  
    4      LPUNKNOWN pUnkOuter  
    5 )  

    参数的含义如下:
    lpcGuidDevice:要创建的设备对象的GUID。可以指定为NULL,代表默认的播放设备。
    ppDS8:返回的IDirectSound8对象的地址。
    pUnkOuter:必须设为NULL。
    例如如下代码即可创建一个IDirectSound8接口的对象

    1 IDirectSound8 *m_pDS=NULL;    
    2 DirectSoundCreate8(NULL,&m_pDS,NULL); 

    2) 设置协作级
    Windows 是一个多任务环境,同一时间有多个应用程序去访问设备。通过使用协作级别,DirectSound可以确保应用程序不会在别的设备使用时去访问,每个 DirectSound应用程序都有一个协作级别,这个级别决定着访问硬件的权限。
    在创建一个设备对象以后,必须通过用IDirectSound8的SetCooperativeLevel()设置协作权限,否则将听不到声音。SetCooperativeLevel()的原型如下

    1 HRESULT SetCooperativeLevel(  
    2  HWND hwnd,  
    3  DWORD dwLevel  
    4 )  

    参数的含义如下:
    hwnd:应用程序窗口句柄。
    dwLevel:支持以下几种级别。
    DSSCL_EXCLUSIVE:与DSSCL_PRIORITY具有相同的作用。
    DSSCL_NORMAL:正常的协调层级标志,其他程序可共享声卡设备进行播放。
    DSSCL_PRIORITY:设置声卡设备为当前程序独占。
    DSSCL_WRITEPRIMAR:可写主缓冲区,此时副缓冲区就不能进行播放处理,即不能将次缓冲区的数据送进混声器,再输出到主缓冲区上。这是最完全控制声音播放的方式。


    3) 创建一个主缓冲对象
    使用IDirectSound8的CreateSoundBuffer()可以创建一个IDirectSoundBuffer接口的主缓冲区对象。CreateSoundBuffer()的原型如下。

    1 HRESULT CreateSoundBuffer(  
    2  LPCDSBUFFERDESC pcDSBufferDesc,  
    3  LPDIRECTSOUNDBUFFER * ppDSBuffer,  
    4  LPUNKNOWN pUnkOuter  
    5 ) 

    参数的含义如下:
    pcDSBufferDesc:描述声音缓冲的DSBUFFERDESC结构体的地址
    ppDSBuffer:返回的IDirectSoundBuffer接口的对象的地址。
    pUnkOuter:必须设置为NULL。
    其中涉及到一个描述声音缓冲的结构体DSBUFFERDESC,该结构体的定义如下:

    1 typedef struct _DSBUFFERDESC  
    2 {  
    3     DWORD           dwSize;  
    4     DWORD           dwFlags;  
    5     DWORD           dwBufferBytes;  
    6     DWORD           dwReserved;  
    7     LPWAVEFORMATEX  lpwfxFormat;  
    8 } DSBUFFERDESC 

    简单解释一下其中的变量的含义:
    dwSize:结构体的大小。必须初始化该值。
    dwFlags:设置声音缓存的属性。有很多选项,可以组合使用,就不一一列出了。详细的参数可以查看文档。
    dwBufferBytes:缓冲的大小。
    dwReserved:保留参数,暂时没有用。
    lpwfxFormat:指向一个WAVE格式文件头的指针。
    设置DSBUFFERDESC完毕后,就可以使用CreateSoundBuffer()创建主缓冲了。示例代码如下:

     1 DSBUFFERDESC dsbd;  
     2 memset(&dsbd,0,sizeof(dsbd));  
     3 dsbd.dwSize=sizeof(dsbd);  
     4 dsbd.dwFlags=DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY |DSBCAPS_GETCURRENTPOSITION2;  
     5 dsbd.dwBufferBytes=MAX_AUDIO_BUF*BUFFERNOTIFYSIZE;   
     6 //WAVE Header  
     7 dsbd.lpwfxFormat=(WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX));  
     8 dsbd.lpwfxFormat->wFormatTag=WAVE_FORMAT_PCM;     
     9 /* format type */  
    10 (dsbd.lpwfxFormat)->nChannels=channels;            
    11 /* number of channels (i.e. mono, stereo...) */  
    12 (dsbd.lpwfxFormat)->nSamplesPerSec=sample_rate;       
    13 /* sample rate */  
    14 (dsbd.lpwfxFormat)->nAvgBytesPerSec=sample_rate*(bits_per_sample/8)*channels;   
    15 /* for buffer estimation */  
    16 (dsbd.lpwfxFormat)->nBlockAlign=(bits_per_sample/8)*channels;          
    17 /* block size of data */  
    18 (dsbd.lpwfxFormat)->wBitsPerSample=bits_per_sample;       
    19 /* number of bits per sample of mono data */  
    20 (dsbd.lpwfxFormat)->cbSize=0;  
    21   
    22   
    23 //Creates a sound buffer object to manage audio samples.   
    24 HRESULT hr1;  
    25 if( FAILED(m_pDS->CreateSoundBuffer(&dsbd,&m_pDSBuffer,NULL))){     
    26     return FALSE;  
    27 } 

    4) 创建一个副缓冲对象
    使用IDirectSoundBuffer的QueryInterface()可以得到一个IDirectSoundBuffer8接口的对象。IDirectSoundBuffer8的GUID为IID_IDirectSoundBuffer8。示例代码如下。

    1 IDirectSoundBuffer *m_pDSBuffer=NULL;  
    2 IDirectSoundBuffer8 *m_pDSBuffer8=NULL;  
    3 ...  
    4 if( FAILED(m_pDSBuffer->QueryInterface(IID_IDirectSoundBuffer8,(LPVOID*)&m_pDSBuffer8))){  
    5     return FALSE ;  
    6 }  

    5) 创建通知对象
    使用IDirectSoundBuffer8的QueryInterface()可以得到一个IDirectSoundNotify8接口的对象。IDirectSoundBuffer8的GUID为IID_IDirectSoundNotify。示例代码如下。

    IDirectSoundBuffer8 *m_pDSBuffer8=NULL;  
    IDirectSoundNotify8 *m_pDSNotify=NULL;    
    …  
    if(FAILED(m_pDSBuffer8->QueryInterface(IID_IDirectSoundNotify,(LPVOID*)&m_pDSNotify))){  
        return FALSE ;  
    } 

    一句话概括一下通知对象的作用:当DirectSound缓冲区中的数据播放完毕后,告知系统应该填充新的数据。


    6) 设置通知位置
    使用IDirectSoundNotify8的SetNotificationPositions()可以设置通知的位置。SetNotificationPositions()的原型如下。

    1 HRESULT SetNotificationPositions(  
    2          DWORD dwPositionNotifies,  
    3          LPCDSBPOSITIONNOTIFY pcPositionNotifies  
    4 )  

    参数含义如下。

    dwPositionNotifies:DSBPOSITIONNOTIFY结构体的数量。既包含几个通知的位置。
    pcPositionNotifies:指向DSBPOSITIONNOTIFY结构体数组的指针。
    再这里涉及到一个结构体DSBPOSITIONNOTIFY,它描述了通知的位置。DSBPOSITIONNOTIFY的定义如下。

    1 typedef struct DSBPOSITIONNOTIFY {  
    2     DWORD dwOffset;  
    3     HANDLE hEventNotify;  
    4 } DSBPOSITIONNOTIFY; 

    它的成员的含义如下。
    dwOffset:通知事件触发的位置(距离缓冲开始位置的偏移量)。
    hEventNotify:触发的事件的句柄。


    7) 开始播放
    使用IDirectSoundBuffer8的SetCurrentPosition ()可以设置播放的位置。SetCurrentPosition ()原型如下

    1 HRESULT SetCurrentPosition(  
    2          DWORD dwNewPosition  
    3 )  

    其中dwNewPosition是播放点与缓冲区首个字节之间的偏移量。
    使用IDirectSoundBuffer8的Play ()可以开始播放音频数据。Play ()原型如下。

    1 HRESULT Play(  
    2          DWORD dwReserved1,  
    3          DWORD dwPriority,  
    4          DWORD dwFlags  
    5 )  

    参数含义:

    dwReserved1:保留参数,必须取0。
    dwPriority:优先级,一般情况下取0即可。
    dwFlags:标志位。目前常见的是DSBPLAY_LOOPING。当播放至缓冲区结尾的时候,重新从缓冲区开始处开始播放。


    2. 循环播放声音
    1) 数据填充至副缓冲区

    数据填充至副缓冲区之前,需要先使用Lock()锁定缓冲区。然后就可以使用fread(),memcpy()等方法将PCM音频采样数据填充至缓冲区。数据填充完毕后,使用Unlock()取消对缓冲区的锁定。
    Lock()函数的原型如下。

    1 HRESULT Lock(  
    2          DWORD dwOffset,  
    3          DWORD dwBytes,  
    4          LPVOID * ppvAudioPtr1,  
    5          LPDWORD  pdwAudioBytes1,  
    6          LPVOID * ppvAudioPtr2,  
    7          LPDWORD pdwAudioBytes2,  
    8          DWORD dwFlags  
    9 )  

    参数的含义如下。
    dwOffset:锁定的内存与缓冲区首地址之间的偏移量。
    dwBytes:锁定的缓存的大小。
    ppvAudioPtr1:获取到的指向缓存数据的指针。
    pdwAudioBytes1:获取到的缓存数据的大小。
    ppvAudioPtr2:没有用到,设置为NULL。
    pdwAudioBytes2:没有用到,设置为0。
    dwFlags:暂时没有研究。


    UnLock()函数的原型如下。

    1 HRESULT Unlock(  
    2          LPVOID pvAudioPtr1,  
    3          DWORD dwAudioBytes1,  
    4          LPVOID pvAudioPtr2,  
    5          DWORD dwAudioBytes2  
    6 ) 

    参数含义如下。
    pvAudioPtr1:通过Lock()获取到的指向缓存数据的指针。
    dwAudioBytes1:写入的数据量。
    pvAudioPtr2:没有用到。

    dwAudioBytes2:没有用到。

    2) 等待播放完成

    根据此前设置的通知机制,使用WaitForMultipleObjects()等待缓冲区中的数据播放完毕,然后进入下一个循环。


    播放音频流程总结

    DirectSound播放PCM音频数据的流程如下图所示。



    其中涉及到的几个结构体之间的关系如下图所示。

    转载自:http://www.cnblogs.com/lidabo/p/4160057.html
  • 相关阅读:
    Redis 设计与实现-内部数据结构
    RuntimeBinderException 异常
    IPv4和IPv6地址库
    -Xms -Xmx -Xmn -Xss -XX:
    倒计时
    列表操作-分片赋值
    20175316毕设准备Day1-2
    20175316毕业设计——基于区块链服务的仓库管理系统
    Python学习笔记:sys.argv入参
    Python学习笔记:bisect模块实现二分搜索
  • 原文地址:https://www.cnblogs.com/hesoyamlyf/p/5635584.html
Copyright © 2020-2023  润新知