




已阅读5页,还剩4页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
利用Directsound编程实现实时混音 摘要:将多个音频文件或多路音频数据同时输出到音频输出设备上,就可同时听到多个不同的声音,这就是混音。在游戏开发,网络视频会议开发中都会用到混音技术,本文详细介绍如何利用Directsound实现几路不同的音频进行实时的混音。 关键词:Directsound 混音在游戏开发中比较常用的音效素材都是比较短的,所以一般常用的API是playsound()函数,比如我们要在游戏背景中播放一个test.wav音效素材,只要简单的调用下面的函数即可PlaySound(test.wav,NULL,SND_FILENAME|SND_ASYNC);如此简单,事实上我们看到,国内的游戏大致上都可以用PlaySound()搞定。但是既然是简单,从功能上就要受限了,如果遇到复杂的场景就没法用playsound实现了,比如在场景中既有开门的声音,又有砍人的声音,你如果用playsound就没法同时听到两种声音,只能是一个声音完了再听到另外一个声音,这时,就需要混音了。在网络视频会议开发中,如果不同的客户端同时发言,如何将多个不同端点的话音数据经网络传输到达某一个端点,经该端点的Wave设备输出,能同时听到多个人的话音,从而实现局域网络中多方的话音交谈,这也需要用到混音技术。在网络上实现话音交谈,特别强调实时性,要尽量保证话音的平滑、连续,因此为了保证话音数据连续,减少话音数据存储带来的延时,在具体实现中,话音的录制和播放都不采用文件的形式,录制和播放的话音数据都存在缓冲区中。在Windows系统中,一般情况下,高层Wave接口函数无法直接播放缓冲区中的话音数据,而必须用底层函数来实现,常用的是Windows API中的Wave函数。将Wave数据在Wave设备上输出使用的是WaveOutWrite函数,但是该函数不支持多路Wave数据的同时播放,为了能达到多路Wave数据同时播放的效果,对缓冲区中多路Wave数据进行必要的预处理后,再提交给Wave输出设备播放,实现原理如图1所示。图1 多路Wave混音的实现原理这种混音的方式效果跟你采用的算法有很大关系,但是如果我们采用Directsound进行混音,就简单多了,我们只需要将我们要混音的内容传给它,Directsound会在内部自动进行混音的。下面我们就进入Directsound混音编程。在了解Directsound如何混音前我们先来看看DirectSound是如何播放一段wave音频的。这里只是简单的介绍一下播放声音的步骤。第一步,创建一个设备对象。 在你的代码中你可以通过调用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,那么你就可以同时来播放这些数据,这些声音会自动进行混音的。看到了吧,Directsound混音很简单,我们只要在一个设备上创建几个辅助的缓冲区,然后将数据读取到缓冲区中,同时的播放,Directsound就会自动在主缓冲区自动混音的。至于同时可以播放几个辅助缓冲区则有硬件设备的性能决定。 在WDM驱动模式下,混音的工作由核心混音器来完成,不同的辅助缓冲区可能具有不同的WAV格式(例如,不同的采样频率),在必要的时候,辅助缓冲区的格式要转换成主缓冲区,或者核心混音器的格式。在VXD驱动模式下,如果你的辅助缓冲区都采用相同的音频格式,并且硬件的音频格式也和你的音频格式匹配,此时,混音器不用作任何的转换。你的应用程序可以创建一个主缓冲区,然后通过IDirectSoundBuffer8:SetFormat来设置硬件的输出格式。要注意,只有你的协作度一定要是Priority Cooperative Level.,并且,一定要创建辅助缓冲区前设置主缓冲区,DirectSound会将你的设置保存下来。在WDM模式下,对主缓冲区的的设置没有作用,因为主缓冲区的格式是由内核混音器来决定的。下面开始吧,让我们看看如何进行混音吧,假设我们的背景需要混音的素材是下面的三个wave文件,test1 .wav test2.wav test3.wav。首先定义一下我们需要的几个变量:LPDIRECTSOUND8 g_pDS = NULL; LPDIRECTSOUNDBUFFER g_pDsbuffer3 = NULL;CWaveFile* g_pWaveFile;/WAVEFORMATEX g_wfxInput; /输入的音频格式这里简单介绍一下CWaveFile类,Directsound里封装了一个CWaveFile类用来操作wav文件,可以通过open来写入文件的头信息,write来写入文件的数据,Getsize函数获取文件的长度,close关闭文件。你可以在DirectSound的路径下找到这个类的定义(SDK root)samplesC+CommonSrcDsutil.cpp。首先初始化DirectsoundBOOL InitDirectSound()if ( FAILED( hr = DirectSoundCreate(NULL, & g_pDS, NULL ) ) )return FALSE;/ Set cooperative level.if ( FAILED( hr = g_pDS -SetCooperativeLevel( hwnd, DSSCL_PRIORITY ) ) )return FALSE;return TRUE;在初始化Directsound的时候,创建设备对象最简单的方法就是通过DirectSoundCreate8函数,这个函数的第一个参数指定了和这个对象邦定的设备的GUID,你可以通过枚举设备来获取这个设备的GUID, 如果这个参数也可以为NULL,缺省的系统的声音输出设备就是DSDEVID_DefaultPlayback 。当你创建完设备对象后,一定要调用IDirectSound8:SetCooperativeLevel来设置协作度,否则,你不会听到声音的。DirectSound定义了三种水平,DSSCL_NORMAL, DSSCL_PRIORITY, and DSSCL_WRITEPRIMARY在Priority层次的协作度下,应用程序可以有优先权使用硬件资源,比如使用硬件进行混音,当然也可以设置主缓冲区的媒体格式,游戏程序应该采用这个层次的协作度,这个层次的协作度在允许应用程序控制采用频率和位深度的同时,也给应用程序很大的权力,这个层次的协作度允许其他应用程序的声音和游戏的音频同时被听到,不影响。下面的函数加载wave文件,然后将音频数据读取到缓冲区中,然后通过Directsound创建了的静态辅助缓冲区,将音频数据copy到Directsound的静态辅助缓冲区,然后就可以play了。LPDIRECTSOUNDBUFFER LoadWaveFile(LPSTR lpzFileName) DSBUFFERDESC dsbdesc;HRESULT hr;BYTE *pBuffer;DWORD dwSizeRead; LPDIRECTSOUNDBUFFER lpdsbStatic=NULL;if( FAILED( hr = g_pWaveFile-Open( lpzFileName, &g_wfxInput, WAVEFILE_WRITE ) ) ) return NULL;DWORD dwSize = g_pWaveFile-GetSize();pBuffer = new BYTEdwSize;g_pWaveFile-Read(pBuffer,dwSize,&dwSizeRead);if(dwSizeRead 0)memset(dsbdesc,0,sizeof(DSBUFFERDESC);dsbdesc.dwSize = sizeof(DSBUFFERDESC);dsbdesc.dwFlags =DSBCAPS_STATIC; dsbdesc.dwBufferBytes =dwSizeRead; dsbdesc.lpwfxFormat = g_wfxInput;if ( FAILED( g_pDS-CreateSoundBuffer(&dsbdesc, & lpdsbStatic, NULL ) ) )g_pWaveFile-Close();delete pBuffer;return NULLLPVOID 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, pBuffer, dwLength);lpdsbStatic -Unlock(lpvWrite, / Address of lock start.dwLength, / Size of lock.NULL, / No wraparound portion.0); / No wraparound size.delete pBuffer;return lpdsbStatic; 这里我想简单的讲一下Directsound的辅助缓冲区,在Directsound中,辅助缓冲区分两类,一种是Static Buffer,这种buffer主要用于播放那些比较短的音频,可以将文件中的音频数据全部copy到Static buffer中,如果音频文件比较大,未了限制内存的开销,就要用到Streaming buffer,一般来说,Streaming buffer只能包含几秒钟的数据量,然后在播放的过程中不断的更新streaming buffer中的数据。静态缓冲区的创建和管理和流缓冲区很相似,唯一的区别就是它们使用的方式不一样,静态缓冲区只填充一次数据,然后就可以play,然而,流缓冲区是一边play,一边填充数据。上面创建的就是得静态的buffer,如果你要播放比较长的音频文件,你就要使用streaming buffer了。流缓冲区用来播放那些比较长的声音,因为数据比较长,没法一次填充到缓冲区中,一边播放,一边将新的数据填充到buffer中。可以通过IDirectSoundBuffer8:Play函授来播放缓冲区中的内容,注意在该函数的参数中一定要设置DSBPLAY_LOOPING标志。通过IDirectSoundBuffer8:Stop方法中断播放,该方法会立即停止缓冲区播放,因此你要确保所有的数据都被播放,你可以通过拖动播放位置或者设置通知位置来实现。将音频流倒入缓冲区需要下面三个步骤 1、确保你的缓冲区已经做好接收新数据的准备。你可以拖放播放的光标位置或者等待通知2、调用IDirectSoundBuffer8:Lock.函数锁住缓冲区的位置,这个函数返回一个或者两个可以写入数据的地址3、使用标准的copy数据的方法将音频数据写入缓冲区中4、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光标间的内容。 下面的这个函数演示了如果向streaming buffer中填充音频数据,在调用这个函数之前,你一定要确保你的streaming buffer是空的,但如何知道buffer是空闲没有数据呢?一个更有效的方法采用通知机制,通过IDirectSoundNotify8:SetNotificationPositions方法,你可以设置任何一个小于buffer的位置来触发一个事件,然后响应处理函数中调用下面的函数将音频数据copy到Directsound的Streaming buffer中。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; 将音频数据复制到Directsound 的辅助缓冲区中,剩下的工作就是play buffer
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 电子束加速器创新创业项目商业计划书
- 教师招聘之《小学教师招聘》通关训练试卷详解(达标题)附答案详解
- 2025年教师招聘之《幼儿教师招聘》真题带答案详解(综合题)
- 教师招聘之《小学教师招聘》题型+答案(考点题)附参考答案详解【模拟题】
- 教师招聘之《小学教师招聘》考前冲刺练习(综合题)附答案详解
- 2025年教师招聘之《小学教师招聘》考试题库带答案详解(巩固)
- 内蒙古呼伦贝尔农垦集团有限公司招聘笔试题库及1套参考答案详解
- 2025年思政综合测试题及答案
- 工行历年考试题库及答案
- 节能知识培训单位职责课件
- (高清版)DG∕TJ 08-7-2021 建筑工程交通设计及停车库(场)设置标准
- 《优化教学策略:打造卓越课件的秘诀》课件
- 教师专业发展-课件
- 2025年数学新课标《义务教育数学课程标准(2025年版)》解读
- 网络分析仪企业ESG实践与创新战略研究报告
- 保险行业组织发展
- 管制部运行管理手册(机场)第六章 运行程序
- 药品临床综合评价解读
- 2025年中国人寿:养老险北京分公司招聘笔试参考题库含答案解析
- 2025年度建筑地基基础清包工劳务合同规范范本
- 逻辑思维在写作中的运用与提升
评论
0/150
提交评论