DirectSound入门指南(0)播放声音
2017-06-04 09:40
218 查看
过去PC机上播放声音和音乐比登天还难!然而,随着DirectSound和DirectMusic的出现,这一切变得相当容易了。本文根据《Windows游戏编程大师技巧》一书学习了DirectSound基本原理,从《Doubango开源项目》中习得了开发技巧。(后文中的源码,为博主从开源库中剥离出来的)。呼,本文讲了很多原理,下一篇文章《DirectSound录制声音入门指南(1)》就很简单粗暴了。
目录:
初始化DirectSound
理解协作等级
主声音缓冲区与辅助声音缓冲区
创建声音辅助缓冲区
把数据写入辅助声音缓冲区
声音渲染
控制音量
调整频率
完整DEMO
DirectSound内部格式是22kHz、8位立体声。但自然界中的大多数声音都是单声道,除非你在不同位置用两个麦克风进行录音,或者你有正真的立体数据,否则对DirectSound传入立体声数据只是种浪费。
建议设置:16位、单声道、22kHz;
有两个组件是我们所关心的:
使用DirectSound时加载的动态链接库DLL;
编译时的库文件dsound.lib和头文件dsound.h;
在DirectX8.0中有一个新的DirectSound接口,IDirectSound8。微软公司跳过了一系列版本,直接从版本1过渡到版本8。但是,IDirectSound8没有为我们带来任何新东西。我们正在用的标准DirectSound接口实际从DirectX3.0就存在了。本段摘自《Windows游戏编程大师技巧》一书。
DirectSound主要接口:
IUnknown ——所有COM对象的基对象。
IDirectSound——DirectSound的主COM对象。它代表音频本身。如果你的计算机中有多块声卡,那么每块声卡都需要一个DirectSound对象。
IDirectSoundBuffer——代表混音硬件和实际的声音。有两种类型的DirectSound缓冲,主缓冲和辅助缓冲。
主缓冲——只有一个主缓冲提供正在播放的声音,通过硬件或软件把声音混合起来。
辅助缓冲——提供存储,用来回放的声音。
IDirectSoundCapture——实时捕获声音。
IDirectSoundNotify——这个接口被用于反馈声音给DirectSound。在一个具有复杂的声音系统中药使用它。
DirectSound被设定为许多等级。主要有两类:
可控制主缓冲;
不可控制主缓冲。
记住,主缓冲代表实际的混音硬件或软件,它始终在混合声音并将其发送给扬声器。如果你直接操作主缓冲,微软希望你知道你在做什么,因为这可能带来灾难性的…
几大协作等级:
普通等级(DSSCL_NORMAL)——这是协作性最好的。当程序获得焦点即可发出声音,同时其他应用程序也可发出声音,不独占。不可控制主缓冲。
优先等级(DSSCL_PRIORITY)——可以访问所有硬件,可以改变主混音器的设定,可以要求声卡完成更高等级的内存操作(如压缩)。这个设定只有在你必须改变主缓冲数据格式时才是必要得——比如想播放16位采样值时,可以这么做。
排他等级(DSSCL_EXCLUSIVE)——同优先等级一样,但只有你的应用程序在前台才能发出声音。
写优先等级(DSSCL_WRITEPRIMARY)——这是最高优先级别,你可以完全控制。并且想听到声音就必须得自己控制主缓冲。如果你在写自己的混音程序或者声音引擎,就只能使用该模式——我想只有John Miles才会使用这个等级。
小结:
辅助缓冲区代表你想播放的声音,它可以任意大小,根据你的计算机内存,请量力而行。然而,声卡的SRAM却只能存储一定的数据。
有两种类型的辅助缓冲:静态、流态。
流式声音缓冲稍有不同。DirectSound使用了一个环形缓冲区,以环形数据形式存储声音,
通过播放游标不断的读取数据;
通过写游标不断的写入数据。
为了提高性能,声音缓冲区访问函数可能返回一个被分成两部分的内存地址。比如,
环形缓冲区大小为10,
目前存放至了位置8,
又来了一个大小为5的语音包,
那么,将会跨越,造成两个返回地址。
dwFlags 包含着声音缓冲区创建的标志,
注意,
你赋予一个声音的能力越多,那么在听到声音前处理的道数就越多,处理时间越长。
如果把dwFlags设定为DSBLOCK_FROMWRITECURSOR ,那么缓冲将从当前写入游标被锁住;如果设定为DSBLOCK_ENTIREBUFFER,缓冲区将被完全锁住。
DirectSound工作方式有些注意点,
锁定它,但不是返回一个指针,而是两个!因此,你必须先把一部分数据写入到第一个指针指向的内存,其余的写入第二个指针指向的内存区域。
再将上文讲到的知识重复一遍,
假如你有一个1000字节的缓冲区(逻辑上环形),锁定缓冲区返回的两个指针可能为ptr1= 100、ptr2 = 0。假设你要写入950字节数据,DirectSound从ptr1 = 100开始写入900字节,(由于逻辑上环形缓冲区),这时候又从ptr2开始写入剩下的数据。
DSBPLAY_LOOPING标志可以循环播放声音,如果打算只播放一次,把dwFlags设置为0即可。
再美妙的声音也终将有停止的时刻,
最好封装一个宏,这样就将-10000~0映射为以dB为声音单位(0-100dB),
一切尽在 IDirectSoundBuffer_SetFrequency中。
Config.h
DsoundPlayer.h
全部项目工程尽在《DirectSound 播放声音入门指南(0)》,DEMO
目录:
初始化DirectSound
理解协作等级
主声音缓冲区与辅助声音缓冲区
创建声音辅助缓冲区
把数据写入辅助声音缓冲区
声音渲染
控制音量
调整频率
完整DEMO
DirectSound内部格式是22kHz、8位立体声。但自然界中的大多数声音都是单声道,除非你在不同位置用两个麦克风进行录音,或者你有正真的立体数据,否则对DirectSound传入立体声数据只是种浪费。
建议设置:16位、单声道、22kHz;
有两个组件是我们所关心的:
使用DirectSound时加载的动态链接库DLL;
编译时的库文件dsound.lib和头文件dsound.h;
在DirectX8.0中有一个新的DirectSound接口,IDirectSound8。微软公司跳过了一系列版本,直接从版本1过渡到版本8。但是,IDirectSound8没有为我们带来任何新东西。我们正在用的标准DirectSound接口实际从DirectX3.0就存在了。本段摘自《Windows游戏编程大师技巧》一书。
DirectSound主要接口:
IUnknown ——所有COM对象的基对象。
IDirectSound——DirectSound的主COM对象。它代表音频本身。如果你的计算机中有多块声卡,那么每块声卡都需要一个DirectSound对象。
IDirectSoundBuffer——代表混音硬件和实际的声音。有两种类型的DirectSound缓冲,主缓冲和辅助缓冲。
主缓冲——只有一个主缓冲提供正在播放的声音,通过硬件或软件把声音混合起来。
辅助缓冲——提供存储,用来回放的声音。
IDirectSoundCapture——实时捕获声音。
IDirectSoundNotify——这个接口被用于反馈声音给DirectSound。在一个具有复杂的声音系统中药使用它。
初始化DirectSound
主DirectSound对象代表一块声卡。如果你有多块声卡,就必须枚举、检测并未它们分配GUID(唯一标识)。一般直接使用默认声卡,简单创建一个DirectSound主对象就够了。/* 创建播放设备 */ if ((hr = DirectSoundCreate(NULL, &ds->device, NULL) != DS_OK)){ return -3; }
理解协作等级
这步是必须的,否则无法发出声音。你可以较为直接的方法进行控制,但微软建议你不要硬来。DirectSound被设定为许多等级。主要有两类:
可控制主缓冲;
不可控制主缓冲。
记住,主缓冲代表实际的混音硬件或软件,它始终在混合声音并将其发送给扬声器。如果你直接操作主缓冲,微软希望你知道你在做什么,因为这可能带来灾难性的…
#define DSSCL_NORMAL 0x00000001 #define DSSCL_PRIORITY 0x00000002 #define DSSCL_EXCLUSIVE 0x00000003 #define DSSCL_WRITEPRIMARY 0x00000004
几大协作等级:
普通等级(DSSCL_NORMAL)——这是协作性最好的。当程序获得焦点即可发出声音,同时其他应用程序也可发出声音,不独占。不可控制主缓冲。
优先等级(DSSCL_PRIORITY)——可以访问所有硬件,可以改变主混音器的设定,可以要求声卡完成更高等级的内存操作(如压缩)。这个设定只有在你必须改变主缓冲数据格式时才是必要得——比如想播放16位采样值时,可以这么做。
排他等级(DSSCL_EXCLUSIVE)——同优先等级一样,但只有你的应用程序在前台才能发出声音。
写优先等级(DSSCL_WRITEPRIMARY)——这是最高优先级别,你可以完全控制。并且想听到声音就必须得自己控制主缓冲。如果你在写自己的混音程序或者声音引擎,就只能使用该模式——我想只有John Miles才会使用这个等级。
小结:
值 | 描述 |
---|---|
DSSCL_NORMAL | 设定普通等级 |
DSSCL_PRIORITY | 设定优先等级,允许设置主缓冲数据格式 |
DSSCL_EXCLUSIVE | 同上,但是它是独占的 |
DSSCL_WRITEPRIMARY | 神一样的级别,完全控制主缓冲 |
/* 设置协调级别 */ if ((hWnd = GetForegroundWindow()) || (hWnd = GetDesktopWindow()) || (hWnd = GetConsoleWindow())){ if ((hr = IDirectSound_SetCooperativeLevel(ds->device, hWnd, DSSCL_PRIORITY)) != DS_OK){ return -4; } }
主声音缓冲区与辅助声音缓冲区
主缓冲区就是来源于声卡本身的DirectSound对象。只要你没有将协作等级设置为DSSCL_WRITEPRIMARY,DirectSound就会为你管好主缓冲区。另外,如果你把协作等级设置为最低DSSCL_NORMAL,DirectSound就会为你创建一个主缓冲区而不需要你自己创建。辅助缓冲区代表你想播放的声音,它可以任意大小,根据你的计算机内存,请量力而行。然而,声卡的SRAM却只能存储一定的数据。
有两种类型的辅助缓冲:静态、流态。
流式声音缓冲稍有不同。DirectSound使用了一个环形缓冲区,以环形数据形式存储声音,
通过播放游标不断的读取数据;
通过写游标不断的写入数据。
为了提高性能,声音缓冲区访问函数可能返回一个被分成两部分的内存地址。比如,
环形缓冲区大小为10,
目前存放至了位置8,
又来了一个大小为5的语音包,
那么,将会跨越,造成两个返回地址。
创建声音辅助缓冲区
dsbd.dwSize = sizeof(DSBUFFERDESC); dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER; dsbd.dwBufferBytes = 0; dsbd.lpwfxFormat = NULL; //... if ((hr = IDirectSound_CreateSoundBuffer(ds->device, &dsbd, &ds->primaryBuffer, NULL)) != DS_OK){ return -6; }
dwFlags 包含着声音缓冲区创建的标志,
#define DSBCAPS_PRIMARYBUFFER 0x00000001 //表明缓冲的是主缓冲的声音。只当想创建主缓冲的时候,才设定该标志 #define DSBCAPS_STATIC 0x00000002 //表明缓冲用于静态声音数据,大多数情况下,你将在硬件内存中创建这些缓冲 #define DSBCAPS_LOCHARDWARE 0x00000004 //如果内存够的话,使用硬件混频和内存建立声音缓冲 #define DSBCAPS_LOCSOFTWARE 0x00000008 //强制缓冲存储在软件内存中,并且使用软件混音 #define DSBCAPS_CTRL3D 0x00000010 #define DSBCAPS_CTRLFREQUENCY 0x00000020 //缓冲用于频率控制功能 #define DSBCAPS_CTRLPAN 0x00000040 //缓冲拥有省道平衡控制功能 #define DSBCAPS_CTRLVOLUME 0x00000080 //缓冲拥有音量控制功能 #define DSBCAPS_CTRLPOSITIONNOTIFY 0x00000100 #define DSBCAPS_CTRLFX 0x00000200 #define DSBCAPS_STICKYFOCUS 0x00004000 #define DSBCAPS_GLOBALFOCUS 0x00008000 #define DSBCAPS_GETCURRENTPOSITION2 0x00010000 #define DSBCAPS_MUTE3DATMAXDISTANCE 0x00020000 #define DSBCAPS_LOCDEFER 0x00040000 #define DSBCAPS_TRUEPLAYPOSITION 0x00080000
注意,
你赋予一个声音的能力越多,那么在听到声音前处理的道数就越多,处理时间越长。
把数据写入辅助声音缓冲区
辅助缓冲区实际上是环形的,这比直接写线性缓冲区困难一些。DirectSound给我们做了很多工作,你只要锁定表面内存,接着就可以写入数据了…如果把dwFlags设定为DSBLOCK_FROMWRITECURSOR ,那么缓冲将从当前写入游标被锁住;如果设定为DSBLOCK_ENTIREBUFFER,缓冲区将被完全锁住。
// lock if (hr = IDirectSoundBuffer_Lock(ds->secondaryBuffer, dwWriteCursor/* Ignored because of DSBLOCK_FROMWRITECURSOR */, (DWORD)ds->bytes_per_notif_size, &lpvAudio1, &dwBytesAudio1, &lpvAudio2, &dwBytesAudio2, DSBLOCK_FROMWRITECURSOR) != DS_OK){ printf("IDirectSoundBuffer_Lock error\n"); continue; } #if OPEN_READ_PCM_FROM_FILE if ((out_size = fread(ds->bytes_per_notif_ptr, 1, ds->bytes_per_notif_size, ds->fp)) != ds->bytes_per_notif_size){ //ds->started = false;//停止播放 printf("player finish.\n"); stopPlayer(ds); } #endif if (out_size < ds->bytes_per_notif_size) { // fill with silence memset(&ds->bytes_per_notif_ptr[out_size], 0, (ds->bytes_per_notif_size - out_size)); } if ((dwBytesAudio1 + dwBytesAudio2) == ds->bytes_per_notif_size) { memcpy(lpvAudio1, ds->bytes_per_notif_ptr, dwBytesAudio1); if (lpvAudio2 && dwBytesAudio2) { memcpy(lpvAudio2, &ds->bytes_per_notif_ptr[dwBytesAudio1], dwBytesAudio2); } } else { //DEBUG_ERROR("Not expected: %d+%d#%d", dwBytesAudio1, dwBytesAudio2, dsound->bytes_per_notif_size); } // unlock if ((hr = IDirectSoundBuffer_Unlock(ds->secondaryBuffer, lpvAudio1, dwBytesAudio1, lpvAudio2, dwBytesAudio2)) != DS_OK) { }
DirectSound工作方式有些注意点,
锁定它,但不是返回一个指针,而是两个!因此,你必须先把一部分数据写入到第一个指针指向的内存,其余的写入第二个指针指向的内存区域。
再将上文讲到的知识重复一遍,
假如你有一个1000字节的缓冲区(逻辑上环形),锁定缓冲区返回的两个指针可能为ptr1= 100、ptr2 = 0。假设你要写入950字节数据,DirectSound从ptr1 = 100开始写入900字节,(由于逻辑上环形缓冲区),这时候又从ptr2开始写入剩下的数据。
声音渲染
一切的准备好了,播放声音…DSBPLAY_LOOPING标志可以循环播放声音,如果打算只播放一次,把dwFlags设置为0即可。
/* 开始播放缓冲区 将次缓冲区的声音数据送到混声器中,与其他声音进行混合,最后输到主缓冲区自动播放.*/ if ((hr = IDirectSoundBuffer_Play(ds->secondaryBuffer, 0, 0, DSBPLAY_LOOPING)) != DS_OK){ return -6; }
再美妙的声音也终将有停止的时刻,
if ((hr = IDirectSoundBuffer_Stop(ds->secondaryBuffer)) != DS_OK){ }
控制音量
SetVolume()与你期待的工作方式可能不一样,如果传入0,折相当于DSBVOLUME_MAX,声音将被无衰减播放——即音量最大;如果设定为DSBVOLUME_MIN或-10000,那么衰减将达到最大:-100dB,这时听不到任何声音,连蚂蚁都听不到。/* 设置音量 [-10000,0]*/ if (IDirectSoundBuffer_SetVolume(_secondaryBuffer, 0/*_convert_volume(0)*/) != DS_OK){ printf("setVolume error\n"); }
最好封装一个宏,这样就将-10000~0映射为以dB为声音单位(0-100dB),
#define DSVOLUME_TO_DB(volume) ((DWORD)-30*100-volume)
调整频率
这是最酷的地方,可以让声音变得慢且邪恶,或变得欢快且萝莉。可以听起来忽然像考拉,忽而像树懒…一切尽在 IDirectSoundBuffer_SetFrequency中。
完整DEMO
摘录了一部分,跃跃欲试的请下拉,找到git传送门。Config.h
#ifndef _WIN32_CONFIG_H #define _WIN32_CONFIG_H #define MEDIA_BITS_PER_SAMPLE_DEFAULT 16 #define MEDIA_CHANNELS_DEFAULT 1 #define MEDIA_RATE_DEFAULT 8000 #define MEDIA_PTIME_DEFAULT 60 #endif
DsoundPlayer.h
#ifndef WIN32_AUDIO_CONTROL_DSPLAYER_H #define WIN32_AUDIO_CONTROL_DSPLAYER_H #include <dsound.h> #include <stdint.h> #include <stdio.h> #include "Config.h" #pragma comment (lib,"dsound.lib") #pragma comment (lib,"dxguid.lib") #if !defined(PLAYER_NOTIF_POS_COUNT) # define PLAYER_NOTIF_POS_COUNT 20 #endif /* PLAYER_NOTIF_POS_COUNT */ /*开关,是否从文件读取数据*/ #define OPEN_READ_PCM_FROM_FILE 1 typedef struct PLAYER{ PLAYER(){ device = NULL; primaryBuffer = NULL; secondaryBuffer = NULL; started = false; bytes_per_notif_ptr = NULL; #if OPEN_READ_PCM_FROM_FILE fp = NULL; #endif } LPDIRECTSOUND device; LPDIRECTSOUNDBUFFER primaryBuffer; LPDIRECTSOUNDBUFFER secondaryBuffer; HANDLE notifEvents[PLAYER_NOTIF_POS_COUNT]; bool started; size_t bytes_per_notif_size; uint8_t* bytes_per_notif_ptr; HANDLE tid[2]; #if OPEN_READ_PCM_FROM_FILE FILE* fp; #endif } Player; /*播放准备*/ int prepare(Player* ds); /*开始播放*/ int startPlayer(Player* ds); /*挂起播放*/ int suspendPlayer(Player* ds); /*唤醒播放*/ int resumePlayer(Player* ds); /*停止播放*/ int stopPlayer(Player* ds); /*释放内存资源*/ int unprepare(Player* ds); DWORD WINAPI playerThreadImpl(LPVOID params); #if OPEN_READ_PCM_FROM_FILE int openFile(Player* ds); int closeFile(Player* ds); #endif #endif
全部项目工程尽在《DirectSound 播放声音入门指南(0)》,DEMO
相关文章推荐
- DirectSound入门指南(1)录制声音
- 首战用DirectSound改造waveout,贴出成功播放声音的代码。
- DirectSound播放音频应用程序开发快速入门
- 建立第一个directX程序——在C#下利用DirectSound实现声音播放
- DirectX.DirectSound声音播放资料
- DirectSound采集播放声音技术文档
- 首战用DirectSound改造waveout,贴出成功播放声音的代码
- 用DirectSound在窗口中播放声音,可当窗口失去焦点后却不播放的解决办法
- 建立第一个directX程序——在C#下利用DirectSound实现声音播放
- DirectX编程:[初级]C#中利用DirectSound播放WAV格式声音[最少只要4句话]
- DirectSound 播放声音杂音问题.
- DirectX编程:[初级]C#中利用DirectSound播放WAV格式声音[最少只要4句话]
- DirectSound播放音频应用程序开发快速入门
- VC++设计的DirectSound播放声音程序学习小记
- 用DirectSound在窗口中播放声音,可当窗口失去焦点后却不播放的解决办法
- DirectSound 播放声音杂音问题.
- Cocos2d入门 <六> 播放声音
- DirectSound播放音频应用程序开发快速入门
- DirectSound入门指南(1)录音实战
- 建立第一个directX程序——在C#下利用DirectSound实现声音播放