以文本方式查看主题 - 计算机科学论坛 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- VC and 声音处理技术汇总 (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=38156) |
-- 作者:卷积内核 -- 发布时间:9/21/2006 11:06:00 AM -- VC and 声音处理技术汇总 一、DirectX Audio的强大功能 DirectX Audio做的不仅仅只是简单地对声音的回放。它提供了一个完整的系统,能够利用硬件加速的功能动态地操纵控制音轨和声道(soundtrack)。 如果你想在你的程序中使用DirectMusic和DirectSound,你就能够获得以下功能: l 读入且播放例如MIDI, WAVE, 以及DirectMusic Producer run time等格式的声音文件。 l 同时对多个声音资源进行混音播放。 l 对播放的声轨进行高精度时间控制。 l 可以动态地变更播放节拍与节奏,甚至即时地动态控制一些MIDI事件。 l 使用DLS合成器(DAudio自带的波表合成器),你的程序就可以确保在不同的计算机上听到相同的MIDI声音。你的 程序能够播放不受类别限制,各试各样乐器的声音,使用DAudio,你甚至还能够创造出独一无二的乐器声音类别并将它发布。 l 把声音做3D定位,创造出3D的环境声响。 l 可以非常容易地实现例如变调,回响,以及别的声音特效。 l 能使用数量超过16个的MIDI音轨。DirectAudio突破了从前只能使用16个MIDI音轨的限制,它把同时回放大数量的音轨变成为了可能,而这个数量仅仅只是受到了你的声音硬件合成器能力的限制。 l 播放位于不同的Audiopaths(可以通过各种方式管理声音数据)上播放声音段(segments),效果就可以独各实现在每一段声音里。 l 实现在不同的端口(port)之间捕捉MIDI数据及流("thru")。 l 从话筒或者别的输入设备中捕捉声音数据。 如果使用DirectMusic Producer或类似程序中的源文件,你还能够做到: l 在声音的回放期间能够做更多的控制,例如动态选择不同的音效变化器(variations)或切换不同的和旋级数。 l 精确地定时重放音乐。 l 利用变化器(variations)播放声音(wave) l Audiopaths利用Map performance channels可以使相同的声音段(segment)在channel的不同部分产生完全不同的播放效果。 l 动态地创造出全新的音乐片段,这不需要专门算法库而仅仅需要你的作曲能力。 l 动态地将存在的音乐片段进行组合过渡。 l 在声音回放时端,程序可以获得存在于特殊节拍点的各类声音特效的信息。 这些功能适用于常常只使用主声音流(mainstream)的程序。如果程序只要实现一些基本简单的功能,开发者将DirectX Audio设计得非常方便使用,它对于一些想入门的程序员显得非常友好。DirectX Audio也是可扩展的。好的程式可以利用虚函数(virtual)继承设计出新的类,例如: l 支持新的声音格式文件。 l 音轨可以包含各类数据。 l 处理信息的工具 - 比方说,可以截取记录实现变换,或者将要显示的歌词置入声音片段(segment)文件。 l 自定义的音序器(sequencer)。 l 自定义的合成器(synthesizer)。 l 效果过滤器。(Effects filters)。 DirectX Audio的每一个部件都是DirectMusic Producer,我们可以采集DLS,和旋图(chordmaps),风格(styles),以及片段(segments) -- 让你能够充分地利用DirectMusic强大的功能与优势。DirectMusic Producer 甚至也能够实现创建可播放的包含多节拍声音的片段(segments)。这里所指的声音是可以在播放时段(run time)以压缩格式存在与播放流中或者内存中。 DirectX Audio 被 Microsoft Windows95, Microsoft Windows98, Microsoft Windows2000所支持。只是对硬件波表合成器的支持只有 Windows 2000 和 Windows 98 Second Edition。 |
-- 作者:卷积内核 -- 发布时间:9/21/2006 11:08:00 AM -- 二、VC++下MIDI、WAV及CD的播放 内容简介: 加入音乐是增强应用程序功能的所有方法中最简单的一个。几乎每个计算机游戏或多媒体程序都以某种MIDI或CD音乐为背景。音乐可以使用户心情愉快;在合适的场合播放恰当的音乐能够使程序员和他的VC++程序焕发光彩。 目录: 文档内容: 第一部分 MIDI的播放 一、 原理 虽然MicroSoft支持MIDI文件,然而Visual C++或MFC并没有创建任何组件来实现这种支持,但是MicroSoft API提供了三种不同的方法来实现MIDI的播放: MCI(The Media Control Interface)。这是最基本的方法,本文将详细讨论这种方法。 流缓冲器。这种格式允许应用程序为MIDI数据分配缓冲器。在需要精确控制MIDI播放的时候,流缓冲器将很有用处。 低级MIDI设备。需要完全控制MIDI数据的应用程序可以使用这种方法。 原型:DWORD mciSendCommand(UINT wDeviceID,UINT wMessage,DWORD dwParam1,DWORD dwParam2); 参数: wDeviceID:接受消息的设备ID 返值:调用成功,返回零;否则,返回双字中的低字存放有错误信息。 二、MIDI的播放控制 1. 打开设备 MCI设备ID指明打开了哪个设备,当发送了MCI_OPEN命令时,这个值在参数块中返回——应被保存备用。 2. 关闭设备 3. 播放 4. 暂停 5. 停止 6. 跳跃 7. 查询当前信息 MCI_STATUS标志 8. 设置时间格式及播放速度 MCI_SEQ_SET_TEMPO 设置播放速度, PlaySound()函数。它可以通过单行编码来播放Wave格式的声音。此函数有两个限制:必须将声音数据完整地载入物理内存;数据格式必须被所配置的某一音频驱动器支持。根据经验,PlaySound()适用于100K以 MCI(The Media Control Interface),与上一章播放MIDI文件相似,可以播放100K 以上的文件。 低级Wave音频设备。用这些设备可以运行完全控制Wave数据的应用文件。 二、 WAV文件播放控制 因为同样使用MCI,与上一章相同,只列出不同的部分。 1. 打开设备 2. 录音 3. 保存录音 1. 开光驱门 2. 关光驱门 3. 打开设备 4. 播放 5. 查询当前信息 注意: MCI_STATUS_POSITION参数返回值调用MCI_TMSF_TRACK(), MCI_TMSF_MINUTE(), MCI_TMSF_SECOND(),MCI_TMSF_FRAME才能得到当前的位置的道、分、秒、帧。 6. 跳跃 MCI调用简单,功能强大,可以满足日常多媒体编程的基本需要。但是,MCI一次只能播放一个文件,使用DirectSound技术可以实现八个以上WAV文件的同时播放。 |
-- 作者:卷积内核 -- 发布时间:9/21/2006 11:12:00 AM -- 三、利用MCI播放MIDI、WAVE MIDI播放: 首先在工程头文件中加入: 然后在程序初始化部分中加入: mciOpenParams.lpstrDeviceType = "sequencer"; if(dwError=mciSendCommand(wMIDIDeviceID,MCI_PLAY,MCI_NOTIFY,(DWORD)(LPVOID)&mciPlayParams)) WAVE播放: 首先在工程头文件中加入: 然后在需要播放的地方加入: SND_ASYNC: 在背景播放 SND_LOOP: 连续播放 |
-- 作者:卷积内核 -- 发布时间:9/21/2006 11:14:00 AM -- 四、SEAL声音函数手册 /*------------------------------------------------------------------------------------*/ /*声音设备函数 */ /*------------------------------------------------------------------------------------*/ //本程序库带32声道混音 /*---------------------------------------------------------------------*/ UINT AIAPI AInitialize(void) //初始化,DOS下必须 /*---------------------------------------------------------------------*/ UINT AIAPI AGetAudioDevCaps(UINT nDeviceId, LPAUDIOCAPS lpCaps) //得到声音设备的信息(成功返回0) nDeviceId 声音设备识别号 lpCaps 一个AUDIOCAPS结构的地址指针 /////////////////////////////////////////// VOID PrintAudioDevs(VOID) { AUDIOCAPS caps; UINT nDeviceId; /* show all the installed device drivers */ /*---------------------------------------------------------------------*/ Value Description AUDIO_PRODUCT_ESS Ensoniq Soundscape (Elite) device AUDIO_PRODUCT_ARIA Sierra Semiconductors Aria Sound device Value Description AUDIO_FORMAT_4M16 16-bit mono 44100 Hz VOID AIAPI TimerHandler(VOID) VOID InitTimerHandler(VOID) VOID DoneTimerHandler(VOID) /* allocate an audio voice or channel */ /* play the waveform through the voice */ /* wait until the wave finishes or a key is pressed */ /* stop the voice (if already playing) */ /* release the allocated voice */ /* first prepare the voices to play the waveform */ /* now start playing all them simultaneously */ /* set voice volumes (the 揺cho?voice has a lower volume) */ /* set the delay (measured in samples) for the 揺cho?voice */ /* start both voices as simultenously as possible */ /* slightly change the pitch of one of the voices */ /* set the pan position of the voices to left and right */ /* start both voices simultaneously */ if (ALoadWaveFile(lpszFileName, &lpWave, 0L)?= AUDIO_ERROR_NONE) AUDIO_ERROR_NOMEMORY Not enough system memory |
-- 作者:卷积内核 -- 发布时间:9/21/2006 11:15:00 AM -- 五、WAVE文件格式剖析 WAVE文件作为多媒体中使用的声波文件格式之一,它是以RIFF格式为标准的。RIFF是英文Resource Interchange File Format的缩写,每个WAVE文件的头四个字节便是“RIFF”。WAVE文件由文件头和数据体两大部分组成。其中文件头又分为RIFF/WAV文件标识段和声音数据格式说明段两部分。WAVE文件各部分内容及格式见附表。 常见的声音文件主要有两种,分别对应于单声道(11.025KHz采样率、8Bit的采样值)和双声道(44.1KHz采样率、16Bit的采样值)。采样率是指:声音信号在“模→数”转换过程中单位时间内采样的次数。采样值是指每一次采样周期内声音模拟信号的积分值。 对于单声道声音文件,采样数据为八位的短整数(short int 00H-FFH);而对于双声道立体声声音文件,每次采样数据为一个16位的整数(int),高八位和低八位分别代表左右两个声道。 WAVE文件数据块包含以脉冲编码调制(PCM)格式表示的样本。WAVE文件是由样本组织而成的。在单声道WAVE文件中,声道0代表左声道,声道1代表右声道。在多声道WAVE文件中,样本是交替出现的。 偏移地址 字节数 数据类型 内 容 PCM数据的存放方式: 样本1 样本2 WAVE文件的每个样本值包含在一个整数i中,i的长度为容纳指定样本长度所需的最小字节数。首先存储低有效字节,表示样本幅度的位放在i的高有效位上,剩下的位置为0,这样8位和16位的PCM波形样本的数据格式如下所示。 样本大小 数据格式 最大值 最小值 |
-- 作者:卷积内核 -- 发布时间:9/21/2006 11:16:00 AM -- 六、游戏音乐与音效的播放 目录: 游戏的配乐 从MIDI开始 播放MIDI的程式作法 文档内容: 在Win32环境下,播放音乐音效的方法太多了,而且有一个共同点就是:你不需要花很大的心力就可以得到你需要的东西。延续主题式的探讨,这一期我们着重在音乐与音效的播放。 □ 游戏的配乐 我相信很多人一定同意音乐在游戏里面所占的地位,回想一下国内RPG的经典「仙剑奇侠传」,剥掉音乐这一个层面,整个游戏将会逊色不少,尤其适当的场景搭配适当的音乐,更能让玩家融入剧情当中。该哭的时候哭,该笑的时候笑,大概就很切中要领了。RPG剩下的音效部份,并不特别突出,大抵上知道砍人的时候有挥剑的声音就可以了,所以在音效的表现方面,通常比较不那麽注重。而即时战斗的游戏着重在厮杀的音效表现上,一大片人马,一片混杂的声音,这其中牵涉到混音的部份,我们底下也会探讨到。读完这篇文章,你会学习到什麽时候该用什麽样的程式作法来表现游戏的另一个生命:音乐与音效。 □ 从MIDI开始 早期DOS下的音乐部份,大多数采用声霸卡的规格,副档名为CMF者便是这种格式,当然游戏通常不会让你看到真正的作法,但是内部采用这种格式居多是无庸置疑的。而WINDOW下的游戏以光碟发行者居多,为了充分达到空间利用的阶段,游戏中会大量使用WAV格式的档案,或是直接将音乐烧成音轨的格式。尤其很多游戏喜欢采用第一片资料片,第二片音乐片的作法,平常不玩游戏还可以当成音乐CD来听,算是满有质感的一件事。当然,我的意思是这些音乐必须要声声入耳,如果音乐本身庸庸碌碌的,即使烧成音轨,一样是庸庸碌碌,改变不了这个事实。 在WINDOW下,考量到空间的大小,MIDI格式的音乐档绝对是最佳的选择,一首五分钟的MIDI了不起十万字元的大小,这跟WAV格式一分钟占用量以MB计,简直是小巫见大巫,所以网站上的音乐,游戏的音乐,都很适合用MIDI来表现,而音乐部份我个人注重旋律,至於一首音乐本身使用到的乐器数量,我倒是很少去注意,人的耳朵听东西有一定的极限,只要不产生杂音,配合优美的旋律,大致上都可以接受。 □ 播放MIDI的程式作法 游戏中播放音乐的要点就是循环播放,也就是播放完毕以後,要让他从头开始播放,直到场景更换,或是游戏结束为止。所以当MIDI档案播放完毕以後,必须要能通知程式,让程式做出适当的处理。播放MIDI的作法只要藉由WINDOW的多媒体的支援,马上就搞定了,甚至直接从HELP的作法剪过来,稍微修改一下,也能符合需要,因为这种东西相当公式化,A君和B君写出来的程式码也大致上会长得差不多,废话不多说,看看程式多麽简单便是: class CMidi { public: DWORD Play(HWND,char* FileName); void Replay(); void Stop(); private: UINT wDeviceID;//MCI装置代号 DWORD dwReturn; MCI_OPEN_PARMS mciOpenParms; MCI_PLAY_PARMS mciPlayParms; MCI_STATUS_PARMS mciStatusParms; MCI_SEQ_SET_PARMS mciSeqSetParms; }; 将他包装成一个类别来使用也可以,而介面的部份需要单纯化,从直觉上来说,第一个动作就是播放(Play),接着是重播(Replay),最後当然是善後的工作了(Stop),不多不少,刚好三个,当然你会想到,是不是需要一个暂停的介面,没问题,这不是什麽难事,花额外的三分钟应该可以胜任愉快。 了解类别大致上的长相以後,让我们来看看实作的部份是怎麽一回事,先从CMidi::Play()开始: DWORD CMidi::Play(HWND hwnd,char* MidiFile) { // 开启Midi的硬体装置,我们使用一般内定值 mciOpenParms.lpstrDeviceType = "sequencer"; //这个叁数就是要播放的MIDI档案名称 mciOpenParms.lpstrElementName = MidiFile; // 使用Message的方式来播放MIDI而不是STRING的方式 if (dwReturn = mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_ELEMENT, (DWORD)(LPVOID) &mciOpenParms) return (dwReturn); // The device opened successfully; get the device ID. wDeviceID = mciOpenParms.wDeviceID; // Check if the output port is the MIDI mapper. mciStatusParms.dwItem = MCI_SEQ_STATUS_PORT; if (dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD)(LPVOID) &mciStatusParms)) { mciSendCommand(wDeviceID, MCI_CLOSE, 0, NULL); return (dwReturn); } // 为了达成重复播放的目的,必须让我们的程式能够接收到 // MM_MCINOTIFY的讯息,这个函示呼叫的方式,就是传递 // WM_PLAY讯息给装置,叫他开始播放。 mciPlayParms.dwCallback = (DWORD) hwnd; if (dwReturn = mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)(LPVOID) &mciPlayParms)) { mciSendCommand(wDeviceID, MCI_CLOSE, 0, NULL); return (dwReturn); } return (0L); }; 播放MIDI的方式有两种,第一种是利用字串命令硬体动作,第二种是传递讯息的方式,我们采用第二种,原因很清楚了,必须透过讯息的传递,我们才能得知音乐是否播放完毕了。 接下来我们看看Cmidi::Replay是怎麽一回事: void CMidi::Replay() { mciSendCommand(wDeviceID, MCI_SEEK,MCI_SEEK_TO_START, NULL); mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)(LPVOID) &mciPlayParms); } 真是不可思议地简单呀,函示里面只包含两条呼叫,第一条呼叫送讯息给装置,叫他把MIDI的播放指标移到最开头的部份,也就是MCI_SEEK_TO_START, 作法就像移动档案指标一样。接着第二条指令光看也明白,就是叫他继续播放就是了,而且别忘了MCI_NOTIFY,当下次播放完毕,还是得用讯息通知我们的程式。 最後看一下Cmidi::Stop()的作法: void CMidi::Stop() { mciSendCommand(wDeviceID, MCI_CLOSE, 0, NULL); } 越来越单纯了,里面只有包含一个函示呼叫,其中的讯息叁数MCI_CLOSE,就是结束整个音乐的播放。当你结束播放以後,要播放另一首音乐,很简单,再次呼叫Cmidi::Play()即可。 整个类别的使用方法大致上是这样的:首先配置一个实际的CMidi物件给程式,只要在全域的地方下条指令 CMidi midi;即可,尔後midi就是真实的物件了。在场景初始化的部份呼叫midi.Play(hwnd,"ff3celes.mid");,输入正确的MIDI档名即可。此处我播放的是太空战士三代的音乐,只是示范一下,当然这首音乐确实很棒就是了。而在讯息回圈里面,我们必须定义一个讯息: case MM_MCINOTIFY: midi.Replay(); break; 在音乐播放完毕以後,我们的讯息回圈会收到MM_MCINOTIFY这个讯息,这时候如同我们前面所言,呼叫Cmidi::Replay()即可。而当场景更换,要重新一首新的音乐,或是程式结束的时候,就是呼叫Cmidi::Stop()的时机。因为一个场景同时间只会存在一首音乐,所以我们的类别表现良好,不用担心。 |
-- 作者:卷积内核 -- 发布时间:9/21/2006 11:17:00 AM -- 七、使用DIRECTX 优化声音特性 内容摘要: 微软所提供开发工具包括一系列专为游戏和交互DircetX DirectSound 以及 Direct3Dsound音频媒体的程序设计人员开发的强有力的工具。DirectX充分利用了声音加速硬件以尽可能的提高其运行速度,减少CPU的占用时间。但是,音频信号仍然对整个系统的运行产生着重大影响。本文所描述的技术将帮助用户使用DirectSound 以及Direct3DSound以减少音频重放对系统运行的影响。 前言: 技巧和技术: 我们将再次提到 DirectSound 合成器。这种 DirectSound 元件负责从缓冲区中将声音中按位隔行合成。然后执行诸如:音量大小、均衡调节(左右声道平衡)、频率转换,以及三维操纵等操作。当合成器不能识别你通过API存放的组件时(不同于上面所述的任一控制操作),这正是 DirectSound 的 CPU 核心所要做的。一些运行问题将在“ DirectSound 合成器发生了什么情况”的项目中加以讨论。下面的关于合成器和主、从缓冲区之间关系的示意图,将使读者清楚地了解他们之间的关系。 如果 DirectSound 开发小组成员见到上面的示意图,他们将对此不屑一顾。因为,合成器远比上图所展示的优秀。上图中,我并没有任何与合成器相关的三维组件,以及其他类型的进程。 既然,我们获得的背景知识远比上面所述的多,我们将进一步接触其他一系列对我们有用的东西。以下是帮助你最大限度的使用DirectSound功能的一个技术列表。 巧妙使用声音的技巧; 下面我们将对上面所述的各项技术作详细的说明。 一.巧妙使用声音的技巧 你应该同你的声音设计者坐下来一起探讨一下全方位地利用声音演奏带来的强烈震撼(如果你自己本身就是程序员及声音设计者,那么你就自己一个人静下心来仔细领略一下),思考一下究竟是哪种声音在将你的渴望以久的经历传递给用户的过程中起着最为重要的作用。当可能减少从缓冲区的使用时,请采用声音预混技术。例如:如果你正在一个声道中模仿蟋蟀的低吟,而在另一个声道中记录了田蛙的欢唱以烘托出夏夜的氛围时,请将它们合成到一个声道中。 如果在你的脑海中有应用程序的初步方案,并且在以后将对其进行折衷处理的时候,你应该对该进程进行相当程度的简化。但要记住,想要设计出相当精练的实用声音程序,你需要长时间地字斟句酌。Beatles的Sgt.Pepper's LonelyHearts Club Band 就是一个有创意的伟大杰作。它被记录在一盘四声道盒式录音带中。与此形成对照,现代的音频记录设备提供了至少四十八个声道,并能提供真正的可用来进行实用合成的无限多声道的盒式录音带以及可利用的MIDI序列发生器。 二.在主缓冲区中使用相同的数据格式 到目前为止,大多数声卡都是ISA总线卡, 它通过DMA方式将声音数据从系统内存移动到当地缓冲区中,处理器在进行内存读写之前将被迫等待DMA的数据传输结束,这势必会影响到CPU的运行速度。对于ISA总线声卡,上述的数据传送方式无疑将对系统的运行产生不可回避的影响,但并不会对新型的32位PCI卡产生任何影响。 对于DirectSound,DMA数据传输的影响直接关系到数据的输出速率以及主缓冲区的访问速率。我曾听说过这样的趣事:在一台主频为90Mhz的奔腾机上运行基本格式为44.1Khz,16位的立体声音乐程序,DMA将占用多达30%的CPU指令周期!DMA数据传输正是影响DirectSound运行的最大因素。值得庆幸的是,上述问题在你无法顺利执行时非常容易处理。实验表明,减少数据存取速率的最好办法是改变主缓冲区中的数据格式。这里的转换十分明显,运行改变了声音的品质,要改变主缓冲区中的数据格式,只需调用方法IDirectSoundBuffer::Setformat,但不要忘记:你的协作层设置成DSSCL.PRIORITY或DSSCL_EXCLUSIVE,以避免主缓冲区的耗费。 三.在无声时间间隔中连续使用主缓冲区 四.使用硬件进行声音合成 在你进行硬件声音合成时使用静态缓冲区。DirectSound将试图在静态缓冲中 进行声音合成。 为你用得最多的声音文件建立声音缓冲区(可用来进行声音硬件合成的合成器是有一定的限度的。 在声音文件运行时,使用方法IDirectSound::GetCaps决定声音加速硬件支持何种格式,并尽可能的采用这些格式(一些声卡只能合成特定格式的声音文件。例如:SoundBlaster AWE32声卡只能合成单16位格式的声音文件)。 当你调用CreatSoundBuffer建立从缓冲区时,你得建立静态缓冲区,在结构DSBUFFERDESC的dwPlag区域设定DSBCAPS_LOCHARDWARE标志。你也可以通过设定 DSBCAPS_LOCHARDWARE标志将缓冲区的数据进行强制性的硬件合成。但是,硬件合成所要使用的资源不可用时,CreatSoundBuffer就将出错。 方法IDirectSound::GetCaps为我们提供了关于声音加速能力的详尽描述,这对于我们进行存取操作有很大的邦助。我们在其工作时间内可以调用GetCaps,调整音频系统以最佳方式使用硬件资源。在 DirectX 文档中查看结构 DSAPS和标志DSCAPS.dwFlags可使我们准确了解到一些系统的有用信息。 五.最低限度的进行声音控制变换 注意: 三维控制变换 (方向、位置、速度、多普勒因子等等) 也将引起DirectSound合成器在其先前进行合成的缓冲区中重新合成。但是,你也可以将一系列三维控制变换集合在一组中,这将只使DirectSound合成器只进行一次重新合成。请仔细阅读以下关于延时控制变换详细说明的章节。 六.使用延时三维进程命令 改变三维声音缓冲区以及听众进行的诸如对方位、速度、多普勒因子的操作,都将引起DirectSound合成器重新合成先前合成缓冲区中的信息,当然,这将浪费一定的CPU指令周期。为了使三维设置的改变对系统运行的影响降低到最小程度,你就应该使用延时三维进程命令。这是DirectSound三维声音组件所特有的特征为了使用三维延时进程命令,请设定每个三维设置变换(SetPosition,SetVelocity等等)中的方法IDirect3DListener或 Direct3DSoundBuffer的dwApply 参数的DS3DMODE_DISABLE 标志,并且将所有的这些变化制成一帧,随后,再调用IDirect3DListener::CommitDeferredSettings去执行所有的延时命令,在先前的合成缓冲区中进行一次重新合成。 我已经为读者罗列了一系列利用DirectX优化音频媒体的特殊工具。我所能给予你的最好的建议是:设计你的支持运行、监视和音量调节的音频子系统。毫无疑问,你必须保证你有充足的时间等待你的程序的运行!如果你从一开始就将程序运行的协调性考虑进去,这个任务将变得更加容易。 |
-- 作者:卷积内核 -- 发布时间:9/21/2006 11:18:00 AM -- 八、DirectSound 无题 目录: 音效的表现 Play Sound using PlaySound() 使用DirectSound 相关事宜 末语 文档内容: □ 音效的表现 WINDOW下音效档的使用,大抵上就是使用WAV档,通常音效只是一小段,不会超过数秒的声音档,当然你可以疯狂一点,将整个WAV档当成音乐来播放,如同前面提到的,与其要这样做,倒不如将WAV档弄成CD音轨,在WINDOW下播放一片CD片,也是如同想像中的简单,这边我们不谈这部份,专心探讨WAV档的使用时机与程式作法的差异。我会讨论不同的作法,以及解释为什麽有些人宁愿舍简就繁,抱头猛钻DirectSound(当然这不一定是正确的)。 □ Play Sound using PlaySound() 是的,在WINDOW API的支援下,播放小段WAV档的最佳方案就是使用PlaySound()函示,他简单到什麽地步呢?比方我要播放sound.wav档,我只要这麽做就好: PlaySound("sound.wav",NULL,SND_FILENAME|SND_ASYNC); 最後的叁数SND_ASYNC 代表音效开始拨放以後,不等待,直接从函示返回,当然其他的叁数诸如SND_LOOP可以让音效重复播放,直到下次呼叫PlaySound()为止。 这边我们必须探讨PlaySound()的使用时机,以及其限制,否则我们很难想像,为什麽还需要DirectSound的存在。PlaySound()的第一个限制就是WAV档案的大小,事实上是有限制的,当WAV档案太大的话,PlaySound()无法处理。第二个限制就是混音的功能,PlaySound()没办法帮上任何一点忙,比方说,在一个场景里面,同时有一只小鸡,一只鸭,他们同时在叫,这时候我需要同时表现这两种声音,我这麽做: PlaySound("chicken.wav",NULL,SND_FILENAME|SND_ASYNC); PlaySound("duck.wav",NULL,SND_FILENAME|SND_ASYNC); 不行,在第一个PlaySound()呼叫的时候,他确实正要播放鸡叫的声音,但是接着马上又呼叫第二个函示,结果鸡叫的声音停止了,转而播放鸭子叫的声音,所以你永远只会听到鸭子的叫声。你会说,如果我不使用非同步作法呢?你会得到的结果是这样的:你先听完鸡叫的声音,然後鸡叫完以後,才听见鸭叫的声音,总之事情就是这个样子,PlaySound()好用,但是他的功能在这边完全派不上用场了。 在这边我们归纳PlaySound()使用的时机:凡是不使用到混音的功能,仅单纯播放片段WAV档者,使用之。换句话说,如果你的游戏同时间只会存在一种音效,PlaySound()将会是最好的选择,不必使用到DirectSound的功能。事实上我们看到国内的游戏大致上都可以用PlaySound()搞定,开门的声音,开宝箱的声音,战斗砍人的声音,实际上同时间只有一种音效播放中,用PlaySound()才是明智的选择。但是我真的见过某些程式,明明只播放单音的WAV档,却还煞有其事地使用复杂的DirectSound,我宁可相信他是为了练习,而不是因为不知道有PlaySound()可用而舍简就繁。 另外在VC的整合环境下,有些人使用PlaySound()遇到的问题都是Link Error,找不到PlaySound()的连结点,事实上这个函示包含在Winmm.lib里面,整合环境的专案内定值不包含这个函示库,要自行加入。 □ 使用DirectSound 在程式需要混音的时候,DirectSound就显得相当有价值了,因为他会替你处理掉许多内部的动作,你所需要做的,就是用,用就对了,也不必管他内部在搞什麽鬼。关於DirectSound的内容,说真的也相当繁杂,我仅就应用的层面,提醒一下哪些时候该使用什麽方式,并举一些例子供叁考。 在了解DirectSound的时候,最重要的就是区分音效Buffer的不同,其中分成两种 primary buffer (主要音效缓冲区)与 secondary buffer(次要音效缓冲区)。Primary buffer通常我们不会去管他,直接交给 播放WAV的方式,也分成两种,第一种是小片段的音效,使用 static buffer,播放资料量极大的WAV档案,你需要用到的是 streaming buffer,这两种的差别有必要搞清楚,所谓static buffer就是静态,小量的音效,通常我们会让他一直常驻在记忆体里面,而不会感到心虚,毕竟所占的记忆体极小。Streaming buffer则是播放大的WAV档,采用分批播放的一种方式,所以程式中必须不停地写入各段的资料。他们使用的方式大概是这样的,如同前面所提到的阿鸡阿鸭WAV音效,档案小,又必须经常使用者,我们使用static buffer,而准备将WAV当音乐播放者(很疯狂),因为一首三分钟的WAV音乐,通常占用数十MB的空间,所以没有别的选择,必须使用streaming buffer。 好了,疯狂的事情留给别人去做,我这边示范一下混音的方式,就如同星海争霸那种整个场景都是动物惨叫声的情况,我们也模拟一下,同时播放五种动物的叫声,当然他们是很和平相处的。在这边的程式范例,有些撷取自DirectX内附的SAMPLE,比方处理WAV格式的部份,我们可以在 SAMPLE里边找到WAVE.C这个档案直接套用。 BOOL InitDSound( HWND hwnd) // Create DirectSound. // Set cooperative level. return TRUE; BOOL InitDSound( HWND hwnd) 仅简单地初始化DirectSound,事实上跟DirectDraw的初始化部份相当类似,所以感觉上都是基本的调调,没什麽好特别讨论的。接着介绍第二个函示: LPDIRECTSOUNDBUFFER LoadStatic (LPDIRECTSOUND lpds,LPSTR lpzFileName) if ( WaveOpenFile( lpzFileName, &hmmio, &pwfx, &mmckinfoParent ) != 0 ) if ( WaveStartDataRead( &hmmio, &mmckinfo, &mmckinfoParent ) != 0 ) if ( lpdsbStatic == NULL ) lpdsbStatic->Lock( 0, 0, &lpvAudio1, &dwBytes1, NULL, NULL, DSBLOCK_ENTIREBUFFER ); return lpdsbStatic; LoadStatic() 主要功能载入WAV档,在这个函示里面,我们也使用到WAVE.C里面的函示,专门处理WAV档。 void PlayStatic(LPDIRECTSOUNDBUFFER lpdsbStatic) lpdsbStatic->SetCurrentPosition(0); lpdsbStatic->Play(0,0,0); PlayStatic()仅简单地使用Play(),就完成播放的动作了,其他的事情不用我们操心,在这边比较复杂的倒是搞定WAVE的内容与格式,还好有WAVE.C的帮助,我们没有遭遇太大的难关。至於这三个函示怎麽使用呢? 首先定义一个全域的变数lpds,供InitDSound()使用,这样的指标整个程式只有一个,所以定义成全域变数是很合理的作法。紧接着数数看你需要几个static buffer,以我们的例子来说,我需要五个,所以我定义的变数如下: LPDIRECTSOUNDBUFFER lpdsb[5]; //配置五个音效指标 要替这五个指标做初始化的的工作,只要笨笨地呼叫LoadStatic()即可,作法大致相同: // 替这五个指标作初始化的工作 lpdsb[0]=LoadStatic(lpds,"chicken.wav"); lpdsb[1]=LoadStatic(lpds,"duck.wav"); lpdsb[2]=LoadStatic(lpds,"horse.wav"); lpdsb[3]=LoadStatic(lpds,"pig.wav"); lpdsb[4]=LoadStatic(lpds,"dragon.wav"); 到了这里,已经完成所有的任务了,这时候我们可以随时使用PlayStatic()来播放音效,因为我们要测试一下混音的效果,所以我故意连续Play五次: PlayStatic(lpdsb[0]); PlayStatic(lpdsb[1]); PlayStatic(lpdsb[2]); PlayStatic(lpdsb[3]); PlayStatic(lpdsb[4]); 这下子没问题了,你要怎麽混音都随便你,执行完毕会有五种动物的声音同时呈现,真是壮观的「音面」呀,虽然「场面」上倒是不知道这些动物躲在哪里啦。 □ 相关事宜 关於3D音效的作法,要配合3D的游戏比较具有真正的意义,不过我们也可以使用简单的方式让音效更具真实感,在上面说到的 static buffer,每一个都可以当成独立的物件看待,其中包含控制音效的音量,喇叭的左声道右声道,更重要的是,你设定其中一个音效的音量,不会影响到另外一个音效的音量,换句话说,他们独立作业就是了。一个简单的应用方式,如果你判断某物体距离你越来越远,则可以让那个物体的音量渐渐减低,制造远近距离的感觉。又有如战斗方式的程式,砍杀敌人的声音可以分成左右声道来表现,比方从左边杀过来,则让右边的音效消音,只让左边的喇叭发声,都是可行的办法。而程式要控制这些部份只要增加一个旗标即可,在dsbdesc.dwFlags 加上DSBCAPS_CTRLDEFAULT即可。如果忘了加入这个旗标,则所有设定音量,左右声道,音频的呼叫都会失败。 □ 末语 看完这篇文章,音乐音效的处理大致上已经没有问题了,有问题的倒是要如何做出动人的音乐,这部份当然不是我们程式者的任务。你可以到我的网站取得此次介绍的类别与函示库,直接套用即可,另外我会写一个混音的小范例,让你实际听一下效果如何。 呃...事实上呢,有些东西是需要自己写的,有些公式化的部份则有现成的东西套用是最好不过。什麽东西你不需要自己写呢?凡是那些属於固定格式,不具创意性的东西,别人写好,拿来用就是了。比方说,最近自己常常幻想如果有一整组的现成函示库支援图形格式的转换该有多好。当然这东西自己写不是问题,只是将时间用在这种没什麽技巧可言的地方,会感觉有点可惜就是了。每个人处事的原则不一样,看待事物的想法也不一样,我没说我是对的,这只是我个人的原则罢了。大家应该有自己的主见。 |
-- 作者:卷积内核 -- 发布时间:9/21/2006 11:19:00 AM -- 九、DirectSound 目录: 关于DirectSound DirectSound设备 枚举可以使用的声音设备 创造DirectSound对象 设置合作级 检索硬件信息 扬声器的设置 压缩 DirectSound缓冲 静态缓冲和流缓冲 创建辅助缓冲 缓冲控制选项 主缓冲的存取 播放声音 重放(PLAYBACK)的控制 播放进度和可以被写的位置(Current Play and Write Positions) 播放缓冲时的通知(PLAY BUFFER NOTIFITACION) 混音(MIXING SOUND) 自己的混音 文档内容: 使用DirectSound可以做到什么呢? 1、很方便的了解硬件能力,并且根据当前计算机硬件配置硬件来决定最好的解决问题的方法。 2、弥补驱动程序的不足——通过属性设置以便硬件能力可以完全发挥,即便是驱动程序没有很好的支持该功能。 3、短传输延迟时间的混音为了快速的响应流。 4、3-D声音 5、声音的捕获
DirectSound核心
这部分描述了将怎样枚举可以使用的声音设备、为一设备创造DirectSound对象、使用对象的方法来设置合作级别、检验设备的能力、创造声音缓冲、配置系统扬声器和压缩数据。 枚举可以使用的声音设备 一般的情况下,可能并不需要枚举可以使用的声音设备,使用缺省设备不会带来什么麻烦(相信用户-上帝:P的设置),但是如果你希望将程序做得更“面对对象”一些,给用户更多更好的选择(很多3D GAME都可以让用户来选择显卡,道理大同小异),那你就需要使用到枚举可供使用的声音设备了。 代码如下: //回调函数 if ( lpGUID != NULL ) memcpy( lpTemp, lpGUID, sizeof(GUID)); ComboBox_AddString( hCombo, lpszDesc ); 创造DirectSound对象 创造DirectSound对象最简单的方法是调用DirectSoundCreate函数。 LPDIRECTSOUND lpds; 该函数的第一个参数是硬件设备,NULL表示使用默认的设备,第二个参数是远程指针LPDIRECTSOUND的地址,也就是创造的DirectSound对象放置的地址,第三个参数必须为NULL,暂时没有用。 设置合作级 因为WINDOWS是一多任务环境,可以允许多个应用程序同时工作,当然也会产生多个程序在同时里使用同一设备工作的情况,通过合作级别,DirectX可以保证所有的程序在使用同一设备时不会发生冲突(大家和平共处岂不是一件乐事),所以每个使用DirectSound的程序都应该有一合作级别用来决定允许访问的设备。 //C的例子,会换成C++的吗?不会说NO吧! 标准级(DSSCL_NORMAL):该级别只能使用22KHZ、立体声(STEREO)、8位的音乐,并且不能直接的写主缓冲,也不能使用压缩过的声音。 优先级(DSSCL_PAIORITY):可以实现硬件混合(hardware mixing),可以设置主缓冲的声音格式(可以根据需要来使用不同质量的音乐)和压缩过的音乐。 独占级(DSSCL_EXCLUSIVE):当应用程序在前台工作时,其它程序是不可使用声音的。 写主缓冲级(DSSCL_WRITEPRIMARY):最高的合作级,程序可以直接的操纵主缓冲,而且程序必须直接的写主缓冲区(最基层的操作)。在这种级别,第二缓冲将不可用。 检索硬件信息 DirectSound允许应用程序检索硬件信息,当然,在一般情况下,这样做是不必要的,因为DirectSound可以自动有效的使用硬件加速,我们完全可以不用去管硬件是否具有某些能力,不过为了提高程序的效率,这样做还是是有用处的。 DSCAPS dscaps; dscaps.dwSize = sizeof(DSCAPS); 扬声器的设置 DirectSound的扬声器设置可以用来调节输出音量的大小和优化3D效果。 压缩 应用程序可以通过IDirectSound::Compact来获得最多的可用内存,当然从前面对合作级别的讨论我们可以发现使用压缩的前提是程序的合作级别至少应该是优先级。
DirectSound缓冲 你可以在同一段物理内存上创建两个或更多的辅助缓冲(使用IDirectSound::DuplicateSoundBuffer方法),但是如果最初的缓冲(原本)所在的硬件内存资源不支持多缓冲,那么这个调用将以失败告终。 静态缓冲和流缓冲 在应用程序里,辅助缓冲可以有两种——静态缓冲(一段内存空间一段完整的声音;好处在于可以一次将全部的声音存入缓冲)和流缓冲(并不将全部的数据一次读入缓冲,而是在播放声音时动态的读入;其好处在于占用空间较小),它们可以分别适应不同的程序需求。 创建辅助缓冲 //创建辅助缓冲的例子 //设置PCMWAVEFORMAT结构 //设置DSBUFFERDESC结构 很简单是吧,只要填两个STRUCT就OK了。 因为DirectSound对先创建的缓冲优先分配硬件资源,所以你应该先创建重要的缓冲。如果你事先声明要创建一硬件缓冲(和放在显存里的表面差不多),就应该在DSBUFFERDESC结构里设置DSBCAPS_LOCHARDWARE标志,但是如果你得不到足够的硬件资源(硬件内存或混音容量hardware memory or mixing capacity),将无法创建缓冲。 缓冲控制选项 你创建一辅助缓冲时,还应该声明该缓冲需要用到的控制选项。这项工作需要你为DSBUFFERDESC结构设置以DSBCAPS_CTRL为首的标志(这些标志可以是单独的来使用,也可以同时设置几个)。 主缓冲的存取 如果你不满意DirectSound的工作,可以直接的操纵主声音缓冲,也可以说是直接的操纵硬件了,但是这将意味着DirectSound的部分特性不可用,包括辅助缓冲的混音和混音的硬件加速。 //写主缓冲时的初始化工作 //初始化WAVEFORMATEX 结构 //初始化DSBUFFERDESC结构 //设置合作级别 //如果失败 播放声音 播放声音要通过以下步骤: 因为流缓冲存储通常是循环的(就像循环队列),所以当你锁定缓冲时DirectSound会返回2个指针。譬如你从一个只有4,000字节的缓冲中点开始锁定3,000字节长的数据,那么DirectSound返回的第一个指针是从中点开始的那2,000字节,而第二个指针则是缓冲最前面的那1,000字节。当然如果没有发生这种情况第二个指针是NULL。 下面就是一个C语言的例子: //获得将要写的块的地址 //如果返回DSERR_BUFFERLOST,还原并重新锁定 if SUCCEEDED(hr) //失败 重放(PLAYBACK)的控制 你可以通过IDirectSoundBuffer::GetVolume和IDirectSoundBuffer::SetVolume来获得或设置该缓冲的音量,设置主缓冲的音量将改变声卡的设置。 诚如前面所说的,这些缓冲控制都必须在设置了相应的标志才可以使用。 播放进度和可以被写的位置(Current Play and Write Positions) DirectSound通常都保证缓冲里有两个指针,一个是当前的播放位置——即当前的播放进度,一个是当前的可以写数据的位置。这两个指针都只是相对缓冲而言的偏移而已。 IDirectSoundBuffer::Play方法通常都从当前的播放进度开始播放音乐。在缓冲刚建立时,播放进度是指向0,而当一段音乐播放完毕以后,播放进度指向那段音乐数据最末端的下一字节,同样的,当音乐被停止时,播放进度也指向停止位置的下一字节。 我们可以将缓冲想象成一个时钟的钟面,而这两个指针则可以作为是钟面上的两个指针。如果数据是顺时针的写上去的,那么可以被写数据的位置始终在当前的播放进度的前面——如果当前的播放进度是1,那么从2开始才是可以写数据的位置;而当播放进度到2这个位置时,从3开始才是现在可以写数据的位置了。 需要注意的是如果你使用的是流缓冲,那么你应该自己来维护现在可以写数据的位置,而且这个指针和IDirectSoundBuffer::Lock里的那个参数dwWriteCursor不是一回事,那个参数只是你想从什么位置开始写你的数据(记住是你想而不是你只能)。当然你也可以在dwFlags参数里加上DSBLOCK_FROMWRITECURSOR标志来使函数忽略dwWriteCursor参数而从当前可以写数据的那个位置开始写数据。 你可以通过IDirectSoundBuffer::GetCurrentPosition和IDirectSoundBuffer::SetCurrentPosition来检索或设置这两个指针,不过当前可以写数据的位置是不可以由你自己决定的,而应该在创建缓冲时加入DSBCAPS_GETCURRENTPOSITION2标志来保证当前可以写数据的位置是正确的。 播放缓冲时的通知(PLAY BUFFER NOTIFITACION) 在你使用流缓冲时,很可能需要知道播放进度已经到什么位置了,或者重放被停止没有。你可以通过IDirectSoundNotify::SetNotificationPositions方法来在缓冲里设置若干个通知点,当相应的事件在这些点发生时DirectSound会给予通知。但是如果音乐已经在播放了,是不允许做这些事的。 首先你应该获得IDirectSoundNotify接口的指针,就像下面一样: // LPDIRECTSOUNDBUFFER lpDsbSecondary; LPDIRECTSOUNDNOTIFY lpDsNotify; //接口指针 HRESULT hr = lpDsbSecondary->QueryInterface(IID_IDirectSoundNotify, (LPVOID *)&lpDsNotify); 注意:IDirectSoundNotify接口和创建它的辅助缓冲是相关联的。 现在你可以通过WIN32 API的CreateEvent()来创建一事件对象。然后你需要为DSBPOSITIONNOTIFY结构的hEventNotify设置一句柄(CreateEvent()返回的),并且设置你想设置的通知位置的偏移值给dwOffset,就可以来设置通知位置了。 设置通知位置的例子如下: DSBPOSITIONNOTIFY PositionNotify; PositionNotify.Offset = DSBPN_OFFSETSTOP; lpDsNotify->SetNotificationPositions(1, &PositionNotify); 如果你需要设置更多的通知位置,你可以通过结构数组来实现。 混音(MIXING SOUND) 对DirectSound来说混音是很容易的,它允许你同时播放多个辅助缓冲,它可以自己来完成这些任务。 只要你的程序正确的指定DSBCAPS_STATIC标志,DirectSound就可以最大限度的使用硬件加速,这些标志需要在静态缓冲重新使用时再指定一次。 如果你所有的缓冲都使用同一种声音格式而且硬件输出也是使用这种格式,那么DirectSound的混音将不需要在格式转换上花任何的工夫,从而大到最优的效果(什么都是最优!:P)。 我们可以通过创建一主缓冲或是调用IDirectSoundBuffer::SetFormat方法来改变硬件输出格式,记住这主缓冲仅仅是为控制目的,和写主缓冲是不一样的,而且这种调用必须要DSSCL_PRIORITY(优先级)或更高的级别。 自己的混音 只有在DSSCL_WRITEPRIMARY级别才可以使用自己写的混音部分。在设置了合作级别后,创建主缓冲,然后锁定它,并写数据,再就可以像其它的缓冲一样的来播放了,不过需要设置DSBPLAY_LOOPING标志才可以。 下面就是一个例子: BOOL AppMixIntoPrimaryBuffer( LPAPPSTREAMINFO lpAppStreamInfo, LPDIRECTSOUNDBUFFER lpDsbPrimary, DWORD dwDataBytes, DWORD dwOldPos, LPDWORD lpdwNewPos) //锁定缓冲 //如果返回DSERR_BUFFERLOST,还原DS并从新锁定 if SUCCEEDED(hr) //解锁 if SUCCEEDED(hr) //锁定或解锁失败 |
-- 作者:卷积内核 -- 发布时间:9/21/2006 11:21:00 AM -- 十、游戏背景音乐 不能出声的哑巴游戏总是不太爽。今天下午花了一点时间大致把游戏背景音乐封装了一下。这个封装基本是参照了DMUtil.h的几个类。原来的打算是自己做一个MySound的类,封装到自己的MySound.h和MySound.cpp里。但是我的Demo用到了SDK的FrameWork,不知道为什么,我封装好的简易音乐类无法和原来的Demo正常编译连接。于是只好老老实实按照FrameWork的封装好的DMUtil.h在进一步封装。 其实还是可以做得更好一些的。但是现在没那么多时间,能用就成吧。 #include "DMUtil.h" 构造初始 封装的四个函数
void CMyD3DApplication::InitMusic(HWND hWnd) void CMyD3DApplication::PlayMusic( HWND hDlg, BOOL bLooped ) g_pMusicSegment->Play( DMUS_SEGF_BEAT ); void CMyD3DApplication::StopMusic() 就这么简单。四个函数就搞定游戏背景音乐了。 |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
3,796.875ms |