以文本方式查看主题

-  计算机科学论坛  (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的播放
  WAV文件的播放
  CD的播放

文档内容:

第一部分 MIDI的播放
  乐器数字化接口(MIDI)是由音乐界的一些大公司(包括生产电子音乐合成器的公司)制订的一项协议,后来被计算机产业所采用并成为多媒体音乐文件的标准格式。MIDI文件一般较小,对硬件设备的要求高。

一、 原理

  虽然MicroSoft支持MIDI文件,然而Visual C++或MFC并没有创建任何组件来实现这种支持,但是MicroSoft API提供了三种不同的方法来实现MIDI的播放:

MCI(The Media Control Interface)。这是最基本的方法,本文将详细讨论这种方法。

流缓冲器。这种格式允许应用程序为MIDI数据分配缓冲器。在需要精确控制MIDI播放的时候,流缓冲器将很有用处。

低级MIDI设备。需要完全控制MIDI数据的应用程序可以使用这种方法。
MCI可以通过mciSendCommand()和mciSendString()来完成,本文仅使用mciSendCommand()函数。

原型:DWORD mciSendCommand(UINT wDeviceID,UINT wMessage,DWORD dwParam1,DWORD dwParam2);

参数: wDeviceID:接受消息的设备ID
wMessage:MCI命令消息
dwParam1:命令的标志位
dwParam2:所使用参数块的指针

返值:调用成功,返回零;否则,返回双字中的低字存放有错误信息。

二、MIDI的播放控制

1. 打开设备
MCI_OPEN_PARMS OpenParms;
OpenParms.lpstrDeviceType = (LPCSTR) MCI_DEVTYPE_SEQUENCER; //MIDI类型
OpenParms.lpstrElementName = (LPCSTR) Filename;
OpenParms.wDeviceID = 0;
mciSendCommand (NULL, MCI_OPEN, MCI_WAIT | MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID | MCI_OPEN_ELEMENT, (DWORD)(LPVOID) &OpenParms)

MCI设备ID指明打开了哪个设备,当发送了MCI_OPEN命令时,这个值在参数块中返回——应被保存备用。

2. 关闭设备
mciSendCommand (m_wDeviceID, MCI_CLOSE, NULL, NULL);

3. 播放
MCI_PLAY_PARMS PlayParms;
PlayParms.dwFrom = 0;
// 指定从什么地方(时间)播放
mciSendCommand (m_wDeviceID, MCI_PLAY, MCI_FROM, (DWORD)(LPVOID)&PlayParms));

4. 暂停
MCI_PLAY_PARMS PlayParms;
mciSendCommand (m_wDeviceID, MCI_PAUSE, 0, (DWORD)(LPVOID) &PlayParms);

5. 停止
mciSendCommand (m_wDeviceID, MCI_STOP, NULL, NULL);

6. 跳跃
* 跳转到任意地方
MCI_SEEK_PARMS SeekParms;
SeekParms.dwTo = (nMinute * 60 + nSecond) * 1000;
//跳转的目标时间,时间单位为毫秒
mciSendCommand (m_wDeviceID, MCI_SEEK, MCI_TO | MCI_WAIT,(DWORD)(LPVOID)&SeekParms);
* 跳到文件头
mciSendCommand (m_wDeviceID, MCI_SEEK, MCI_SEEK_TO_START, NULL);
* 跳到文件尾
mciSendCommand (m_wDeviceID, MCI_SEEK, MCI_SEEK_TO_END, NULL);

7. 查询当前信息
MCI_STATUS_PARMS StatusParms;
StatusParms.dwItem = MCI_SEQ_STATUS_DIVTYPE;
mciSendCommand (m_wDeviceID, MCI_STATUS, MCI_WAIT | MCI_STATUS_ITEM, (DWORD)(LPVOID)&StatusParms);
返回信息存放于StatusParms.dwReturn中。

MCI_STATUS标志
MCI_STATUS_LENGTH 获得文件长度
MCI_STATUS_MODE 获得文件播放的当前状态
MCI_STATUS_POSITION 获得文件播放的当前位置
MCI_STATUS_TIME_FORMAT 获得当前的时间格式
MCI_SEQ_STATUS_DIVTYPE 判断文件是PPQN类型还是SMPTE类型
MCI_SEQ_STATUS_TEMPO 获得当前播放速度,PQRN类型,
此值为节拍/分,SMPTE类型,此值为祯/秒

8. 设置时间格式及播放速度
MCI_SET_PARMS SetParms;
SetParms.dwTimeFormat = MCI_FORMAT_MILLISECONDS;
//设置时间单位为毫秒
mciSendCommand (m_wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD)(LPVOID) &SetParms);

MCI_SEQ_SET_TEMPO 设置播放速度,
PQRN类型,此值为节拍/分,
SMPTE类型,此值为祯/秒


第二部分 WAV文件的播放
一、原理
MicroSoft API提供了三种不同的方法来实现WAV的播放:

PlaySound()函数。它可以通过单行编码来播放Wave格式的声音。此函数有两个限制:必须将声音数据完整地载入物理内存;数据格式必须被所配置的某一音频驱动器支持。根据经验,PlaySound()适用于100K以
下的文件。

MCI(The Media Control Interface),与上一章播放MIDI文件相似,可以播放100K 以上的文件。

低级Wave音频设备。用这些设备可以运行完全控制Wave数据的应用文件。

二、 WAV文件播放控制

因为同样使用MCI,与上一章相同,只列出不同的部分。

1. 打开设备
将MIDI的MCI_DEVTYPE_SEQUENCER 改为"waveaudio"

2. 录音
MCI_RECORD_PARMS RecordParms;
mciSendCommand (m_wDeviceID, MCI_RECORD, NULL, (DWORD)(LPVOID)&RecordParms);

3. 保存录音
MCI_SAVE_PARMS SaveParms;
SaveParms.lpfilename = (LPCSTR) Filename;
mciSendCommand (m_wDeviceID, MCI_SAVE, MCI_SAVE_FILE | MCI_WAIT, (DWORD)(LPVOID) &SaveParms);


第三部分 CD的播放
  CD的独特优势在于,它由作曲家设计,并由音乐厂家生产。不同的计算机播放MIDI 文件时,声音效果也不一样,但是CD的声音效果总是相同的。高品质的音频对计算机用户产生的效果会使你感到吃惊。我们依然采用MCI播放CD,大部分的播放控制与前两部分相同,只列出不同的部分;

1. 开光驱门
mciSendCommand (m_wDeviceID, MCI_SET, MCI_SET_DOOR_OPEN, NULL);

2. 关光驱门
mciSendCommand (m_wDeviceID, MCI_SET, MCI_SET_DOOR_CLOSED, NULL);

3. 打开设备
将MIDI的MCI_DEVTYPE_SEQUENCER 改为MCI_DEVTYPE_CD_AUDIO

4. 播放
指定播放起点必须经过MCI_MAKE_TMSF(Track,Minute,Second,Frame)转化

5. 查询当前信息
MCI_STATUS_CURRENT_TRACK 得到当前曲目
MCI_STATUS_LENGTH 得到CD或指定曲目长度
MCI_STATUS_MODE 得到驱动器的当前状态
MCI_STATUS_NUMBER_OF_TRACKS 得到CD曲目的数目
MCI_STATUS_POSITION 得到当前格式下的位置
MCI_STATUS_READY 检查设备是否就绪
MCI_STATUS_TIME_FORMAT 得到当前时间格式
MCI_STATUS_MEDIA_PRESENT 检查以确认CD是否在驱动器内
MCI_CDA_STATUS_TYPE_TRACK 检查已确认某曲目是否为音频曲目

注意:
使用MCI_STATUS_LENGTH参数查询CD 及曲目长度,返回值通过调用MCI_MSF_MINUTE(),MCI_MSF_SECOND()转换为分、秒。

MCI_STATUS_POSITION参数返回值调用MCI_TMSF_TRACK(), MCI_TMSF_MINUTE(), MCI_TMSF_SECOND(),MCI_TMSF_FRAME才能得到当前的位置的道、分、秒、帧。

6. 跳跃
跳转的目标必须经过MCI_MAKE_TMSF(Track,Minute,Second,Frame)转化最好将上述三种格式分开建类,或做成动态连接库。在 Project-- >Setting-- >Link-- >Object/library modules中加入winmm.lib,
源程序中包含< mmsystem.h >。

MCI调用简单,功能强大,可以满足日常多媒体编程的基本需要。但是,MCI一次只能播放一个文件,使用DirectSound技术可以实现八个以上WAV文件的同时播放。


--  作者:卷积内核
--  发布时间:9/21/2006 11:12:00 AM

--  
三、利用MCI播放MIDI、WAVE

 
MIDI播放:

首先在工程头文件中加入:
#include "mmsystem.h"
#pragma comment (lib,"WINMM.LIB")

然后在程序初始化部分中加入:
UINT wMIDIDeviceID;
MCI_OPEN_PARMS mciOpenParams;
MCI_PLAY_PARMS mciPlayParams;
DWORD dwError;

mciOpenParams.lpstrDeviceType = "sequencer";
mciOpenParams.lpstrElementName = "main.mid"; //要播放的MIDI
if(dwError=mciSendCommand(0,MCI_OPEN,MCI_OPEN_ELEMENT|MCI_OPEN_TYPE,(DWORD)(LPVOID)&mciOpenParams))
    ; // 播放
else
    wMIDIDeviceID=mciOpenParams.wDeviceID;

if(dwError=mciSendCommand(wMIDIDeviceID,MCI_PLAY,MCI_NOTIFY,(DWORD)(LPVOID)&mciPlayParams))
{
    mciSendCommand(wMIDIDeviceID,MCI_CLOSE,0,NULL); //关闭
}

WAVE播放:

首先在工程头文件中加入:
#include "mmsystem.h"
#pragma comment (lib,"WINMM.LIB")

然后在需要播放的地方加入:
sndPlaySound("sound_file.name",SND_ASYNC|SND_LOOP);

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 */
    for (nDeviceId = 0; nDeviceId < AGetAudioNumDevs(); nDeviceId++)
    {
        AGetAudioDevCaps(nDeviceId, &caps);
        printf(nDeviceId=%d wProductId=%d szProductName=%s\n?
        nDeviceId, caps.wProductId, caps.szProductName);
    }
}

/*---------------------------------------------------------------------*/
AUDIOCAPS结构 //说明声音设备的能力
typedef struct
{
    WORD wProductId;
    CHAR szProductName[30];
    DWORD dwFormats;
} AUDIOCAPS, *LPAUDIOCAPS;
///////////////////////////////////////////
wProductId //硬件标识,具有以下值

Value Description
AUDIO_PRODUCT_NONE Virtual silence device
AUDIO_PRODUCT_SB Sound Blaster 1.0 device
AUDIO_PRODUCT_SB15 Sound Blaster 1.5 device
AUDIO_PRODUCT_SB20 Sound Blaster 2.0 device
AUDIO_PRODUCT_SBPRO Sound Blaster Pro I or II device
AUDIO_PRODUCT_SB16 Sound Blaster 16/ASP device
AUDIO_PRODUCT_AWE32 Sound Blaster AWE32 device
AUDIO_PRODUCT_WSS Windows Sound System device

AUDIO_PRODUCT_ESS Ensoniq Soundscape (Elite) device
AUDIO_PRODUCT_GUS Gravis Ultrasound (GF1) device
AUDIO_PRODUCT_GUSDB Gravis Ultrasound Daughterboard device
AUDIO_PRODUCT_GUSMAX Gravis Ultrasound Max (CS4231) device
AUDIO_PRODUCT_IWAVE AMD Interwave-based device
AUDIO_PRODUCT_PAS Pro Audio Spectrum (Plus) device
AUDIO_PRODUCT_PAS16 Pro Audio Spectrum 16 device

AUDIO_PRODUCT_ARIA Sierra Semiconductors Aria Sound device
AUDIO_PRODUCT_WINDOWS Microsoft Windows WAVE device
AUDIO_PRODUCT_DSOUND Microsoft Windows DirectSound device
AUDIO_PRODUCT_LINUX UNIX Sound System for Linux device
AUDIO_PRODUCT_SPARC SPARC workstation 8-bit ulaw or dbri device
AUDIO_PRODUCT_SGI Silicon Graphics Indigo Audio Ports device
///////////////////////////////////////////
szProductName
硬件产品名称字符串,NULL结束
///////////////////////////////////////////
dwFormats
产品回放能力标示

Value Description
AUDIO_FORMAT_1M08 8-bit mono 11025 Hz
AUDIO_FORMAT_1S08 8-bit stereo 11025 Hz
AUDIO_FORMAT_1M16 16-bit mono 11025 Hz
AUDIO_FORMAT_1S16 16-bit stereo 11025 Hz
AUDIO_FORMAT_2M08 8-bit mono 22050 Hz
AUDIO_FORMAT_2S08 8-bit stereo 22050 Hz
AUDIO_FORMAT_2M16 16-bit mono 22050 Hz
AUDIO_FORMAT_2S16 16-bit stereo 22050 Hz
AUDIO_FORMAT_4M08 8-bit mono 44100 Hz
AUDIO_FORMAT_4S08 8-bit stereo 44100 Hz

AUDIO_FORMAT_4M16 16-bit mono 44100 Hz
AUDIO_FORMAT_4S16 16-bit stereo 44100 Hz
/*---------------------------------------------------------------------*/
UINT AIAPI AGetAudioNumDevs(VOID)
//返回安装的声音设备个数
/*---------------------------------------------------------------------*/
UINT AIAPI APingAudio(LPUINT lpnDeviceId)
//自动发现声音设备
lpnDeviceId 无符号整数指针,指向ID
///////////////////////////////////////////
VOID DetectAudioDevice(VOID)
{
    AUDIOCAPS caps;
    UINT nDeviceId;
    if (APingAudio(&nDeviceId)?= AUDIO_ERROR_NONE)
    {
        printf(搉o audio device found.\n?;
    }
    else
    {
        AGetAudioDevCaps(nDeviceId, &caps);
        printf(?s device found.\n? caps.szProductName);
    }
}
/*---------------------------------------------------------------------*/
UINT AIAPI AOpenAudio(LPAUDIOINFO lpInfo)
//打开声音设备,成功返回0
lpInfo AUDIOINFO结构指针,设定回放参数
///////////////////////////////////////////
VOID InitializeAudio(VOID)
{
    AUDIOINFO info;
    info.nDeviceId = AUDIO_DEVICE_MAPPER;
    info.wFormat = AUDIO_FORMAT_16BITS | AUDIO_FORMAT_STEREO;
    info.nSampleRate = 44100;
    if (AOpenAudio(&info)?= AUDIO_ERROR_NONE)
    {
        printf("Audio initialization failed.\n");
        exit(1);
    }
    else
    {
        printf("Audio device initialized at %d bits %s %u Hz\n",
        info.wFormat & AUDIO_FORMAT_16BITS?"16:8",
        info.wFormat & AUDIO_FORMAT_STEREO?"stereo:mono",info.nSampleRate);
    }
}
/*---------------------------------------------------------------------*/
typedef struct //声音回放设定
{
    UINT nDeviceId;
    WORD wFormat;
    WORD nSampleRate;
} AUDIOINFO, *LPAUDIOINFO;
///////////////////////////////////////////
nDeviceId
//指定我们希望回放的设备
Value Description
AUDIO_DEVICE_NONE Silence device driver
AUDIO_DEVICE_MAPPER Audio mapper driver
///////////////////////////////////////////
wFormat
//回放格式
Value Description
AUDIO_FORMAT_8BITS 8 bits per sample
AUDIO_FORMAT_16BITS 16 bits per sample
AUDIO_FORMAT_MONO Monophonic output
AUDIO_FORMAT_STEREO Stereo output
AUDIO_FORMAT_FILTER Enable filtering
///////////////////////////////////////////
nSampleRate
//回放频率,推荐使用11025,22050,44100Hz
/*---------------------------------------------------------------------*/
UINT AIAPI AUpdateAudio(VOID)
//向声音设备送声音值(返0成功),DOS下必须
//必须周期性的调用(至少每秒钟调用一次)
/*---------------------------------------------------------------------*/
UINT AIAPI ACloseAudio(VOID)
//关闭声音设备(返0成功)
/*---------------------------------------------------------------------*/
//////////////////////////时钟中断服务///////////////////////////////////
/*---------------------------------------------------------------------*/
UINT AIAPI ASetAudioTimerProc(LPFNAUDIOTIMER lpfnAudioTimer)
//安装时钟服务程序(返0成功),后台回放,用户程序不可再用
lpfnAudioTimer 时钟处理程序地址
///////////////////////////////////////////
volatile UINT nTickCounter = 0;

VOID AIAPI TimerHandler(VOID)
{
    nTickCounter++;
}

VOID InitTimerHandler(VOID)
{
    /* the handler will be called at 125 BPM (50 times per second) */
    ASetAudioTimerProc(TimerHandler);
    ASetAudioTimerRate(125);
}

VOID DoneTimerHandler(VOID)
{
ASetAudioTimerProc(NULL);
}
/*---------------------------------------------------------------------*/
UINT AIAPI ASetAudioTimerRate(UINT nTimerRate)
//改变时钟频率(返0成功)
nTimerRate 每分钟拍节(32-255)
/*---------------------------------------------------------------------*/
/*------------------------------------------------------------------------------------*/
/*Voice函数 */
/*------------------------------------------------------------------------------------*/
UINT AIAPI AOpenVoices(UINT nVoices)
//打开声音设备(0成功)
nVoices 最大声道数(1-32)(可能是混音声道数)
/*---------------------------------------------------------------------*/
UINT AIAPI ACreateAudioVoice(LPHAC lphVoice)
//分配1条声道(0成功)
lphVoice 声道头
///////////////////////////////////////////
VOID PlayWaveform(LPAUDIOWAVE lpWave)
{
    HAC hVoice;
    BOOL stopped;

    /* allocate an audio voice or channel */
    if (ACreateAudioVoice(&hVoice)?= AUDIO_ERROR_NONE)
    {

        /* play the waveform through the voice */
        APlayVoice(hVoice, lpWave);
        ASetVoiceVolume(hVoice, 64);
        ASetVoicePanning(hVoice, 128);

        /* wait until the wave finishes or a key is pressed */
        while (!kbhit())
        {
            AUpdateAudio();
            AGetVoiceStatus(hVoice, &stopped);
            if (stopped) break;
        }

        /* stop the voice (if already playing) */
        AStopVoice(hVoice);

        /* release the allocated voice */
        ADestroyAudioVoice(hVoice);
    }
}
/*---------------------------------------------------------------------*/
UINT AIAPI ADestroyAudioVoice(HAC hVoice)
//释放一个声道(每条声音播完后必须释放)
hVoice 声道头
/*---------------------------------------------------------------------*/
UINT AIAPI ACloseVoices(VOID)
//关闭所有声音
/*---------------------------------------------------------------------*/
UINT AIAPI APlayVoice(HAC hVoice, LPAUDIOWAVE lpWave)
//开始播放一条波形
hVoice 声道头
lpWave wave结构指针
/*---------------------------------------------------------------------*/
UINT AIAPI APrimeVoice(HAC hVoice, LPAUDIOWAVE lpWave)
//准备一条wave到声道上
hVoice 声道头
lpWave wave结构指针
///////////////////////////////////////////
VOID PlayChord(HAC aVoices[3], LPAUDIOWAVE lpWave, LONG aFreqs[3])
{
    UINT n;

    /* first prepare the voices to play the waveform */
    for (n = 0? n < 3? n++)
    {
        APrimeVoice(aVoices[n], lpWave);
        ASetVoiceFrequency(aVoices[n], aFreqs[n]);
        ASetVoiceVolume(aVoices[n], 64);
    }

    /* now start playing all them simultaneously */
    for (n = 0? n < 3? n++)
    {
        AStartVoice(aVoices[n]);
    }
}
/*---------------------------------------------------------------------*/
UINT AIAPI AStartVoice(HAC hVoice)
//播放一个声道,可以指定连续循环播放
hVoice 声道头
/*---------------------------------------------------------------------*/
UINT AIAPI AStopVoice(HAC hVoice)
//停止播放一个声道
hVoice 声道头
/*---------------------------------------------------------------------*/
UINT AIAPI AGetVoicePosition(HAC hVoice, LPLONG lpdwPosition)
//返回正在播放的声音位置(播放点,做拉条用)
hVoice //询问的声道头
lpdwPosition //长指针返回播放点
/*---------------------------------------------------------------------*/
UINT AIAPI ASetVoicePosition(HAC hVoice, LONG dwPosition)
//设定正在播放的声音位置(播放点,做拉条用)
hVoice //设定的声道头
lpdwPosition //长指针设定播放点
///////////////////////////////////////////
VOID PlayEchoVoices(HAC aVoices[2], LPAUDIOWAVE lpWave, LONG dwDelay)
{
    /* prepare two voices with the same waveform */
    APrimeVoice(aVoices[0], lpWave);
    APrimeVoice(aVoices[1], lpWave);
    ASetVoiceFrequency(aVoices[0], lpWave->nSampleRate);
    ASetVoiceFrequency(aVoices[1], lpWave->nSampleRate);

    /* set voice volumes (the 揺cho?voice has a lower volume) */
    ASetVoiceVolume(aVoices[0], 64);
    ASetVoiceVolume(aVoices[1], 32);

    /* set the delay (measured in samples) for the 揺cho?voice */
    ASetVoicePosition(aVoices[1], dwDelay);

    /* start both voices as simultenously as possible */
    AStartVoice(aVoices[0]);
    AStartVoice(aVoices[1]);
}
/*---------------------------------------------------------------------*/
UINT AIAPI AGetVoiceVolume(HAC hVoice, LPUINT lpnVolume)
//得到声道的音量
hVoice //询问的声道头
lpdwPosition //无符号整形指针返回音量
/*---------------------------------------------------------------------*/
UINT AIAPI ASetVoiceVolume(HAC hVoice, UINT nVolume)
//设定声道的音量
hVoice //设定的声道头
lpdwPosition //无符号整形指针设定音量
/*---------------------------------------------------------------------*/
UINT AIAPI AGetVoiceFrequency(HAC hVoice, LPLONG lpdwFrequency)
//返回声道取样频率
lpdwFrequency 每秒Hz数
/*---------------------------------------------------------------------*/
UINT AIAPI ASetVoiceFrequency(HAC hVoice, LONG dwFrequency)
//设定声道取样频率
dwFrequency 每秒Hz数
///////////////////////////////////////////
VOID PlayVoiceStereo(HAC aVoices[2], LPAUDIOWAVE lpWave, LONG dwPitchShift)
{
    /* prepare two voices to play a waveform */
    APrimeVoice(aVoices[0], lpWave);
    APrimeVoice(aVoices[1], lpWave);
    ASetVoiceVolume(aVoices[0], 64);
    ASetVoiceVolume(aVoices[1], 64);

    /* slightly change the pitch of one of the voices */
    ASetVoiceFrequency(aVoices[0], lpWave->nSampleRate);
    ASetVoiceFrequency(aVoices[1], lpWave->nSampleRate + dwPitchShift);

    /* set the pan position of the voices to left and right */
    ASetVoicePanning(aVoices[0], 0);
    ASetVoicePanning(aVoices[1], 255);

    /* start both voices simultaneously */
    AStartVoice(aVoices[0]);
    AStartVoice(aVoices[1]);
}
/*---------------------------------------------------------------------*/
UINT AIAPI AGetVoicePanning(HAC hVoice, LPUINT lpnPanning)
//返回立体声面板(?)位置
lpnPanning unsigned integer指针
/*---------------------------------------------------------------------*/
UINT AIAPI ASetVoicePanning(HAC hVoice, UINT nPanning)
//改变立体声面板(?)位置
nPanning 立体声面板值(0-255)
/*---------------------------------------------------------------------*/
UINT AIAPI AGetVoiceStatus(HAC hVoice, LPBOOL lpbStatus)
//返回当前声音状态
///////////////////////////////////////////
lpbStatus boolean变量指针
VOID CheckVoice(HAC hVoice)
{
    BOOL stopped;
    AGetVoiceStatus(hVoice, &stopped);
    if (stopped) printf("voice is stopped.\n");
}
/*------------------------------------------------------------------------------------*/
/*Wave函数 */
/*------------------------------------------------------------------------------------*/
UINT AIAPI ALoadWaveFile(LPSTR lpszFileName, LPAUDIOWAVE* lplpWave,DWORD dwOffset)
//LOAD一个RIFF/WAVE文件
lpszFileName Filename of the .WAV waveform file on disk.
lplpWave Address of a pointer to a waveform structure.
dwOffset Starting file offset of the waveform file.
///////////////////////////////////////////
VOID PlayWaveFile(HAC hVoice, LPSTR lpszFileName)
{
    LPAUDIOWAVE lpWave;
    BOOL stopped;

    if (ALoadWaveFile(lpszFileName, &lpWave, 0L)?= AUDIO_ERROR_NONE)
    {
        APlayVoice(hVoice, lpWave);
        ASetVoiceVolume(hVoice, 64);
        while (!kbhit())
        {
            AUpdateAudio();
            AGetVoiceStatus(hVoice, &stopped);
            if (stopped) break;
        }
        AFreeWaveFile(lpWave);
    }
}
/*---------------------------------------------------------------------*/
UINT AIAPI AFreeWaveFile(LPAUDIOWAVE lpWave)
//释放一个WAV文件
/*---------------------------------------------------------------------*/
UINT AIAPI ACreateAudioData(LPAUDIOWAVE lpWave)
//建立一个WAV对象
lpWave 指向WAVE结构的指针
///////////////////////////////////////////
LPAUDIOWAVE CreateAudio8BitMono(WORD nSampleRate,LPBYTE lpData, DWORD dwLength)
{
    LPAUDIOWAVE lpWave;
    /* first allocate structure to hold the waveform object */
    if ((lpWave = (LPAUDIOWAVE) malloc(sizeof(AUDIOWAVE)))?= NULL)
    {
        /* create a 8-bit mono one-shot waveform object */
        lpWave->wFormat = AUDIO_FORMAT_8BITS | AUDIO_FORMAT_MONO;
        lpWave->nSampleRate = nSampleRate;
        lpWave->dwLength = dwLength;
        lpWave->dwLoopStart = lpWave->dwLoopEnd = 0;
        if (ACreateAudioData(lpWave)?= AUDIO_ERROR_NONE)
        {
            free(lpWave);
            return NULL;
        }
        /* copy the data into the waveform object */
        memcpy(lpWave->lpData, lpData, dwLength);
        /* upload the data to the audio DRAM local memory */
        AWriteAudioData(lpWave, 0L, dwLength);
    }
    return lpWave;
}
/*---------------------------------------------------------------------*/
UINT AIAPI ADestroyAudioData(LPAUDIOWAVE lpWave)
//释放一个WAVE对象
/*---------------------------------------------------------------------*/
typedef struct WAVE结构
{
LPBYTE lpData;
DWORD dwHandle;
DWORD dwLength;
DWORD dwLoopStart;
DWORD dwLoopEnd;
WORD nSampleRate;
WORD wFormat;
} AUDIOWAVE, *LPAUDIOWAVE;
///////////////////////////////////////////
lpData 只读,数据地址指针
///////////////////////////////////////////
dwHandle 只读,声音设备使用的句柄
///////////////////////////////////////////
dwLength 指定字节为单位的波形数据长度
///////////////////////////////////////////
dwLoopStart 循环模式下的循环起始点
///////////////////////////////////////////
dwLoopEnd 循环模式下的循环结束点
///////////////////////////////////////////
nSampleEate 波形数据约定的取样频率
///////////////////////////////////////////
wFormat 波形数据格式
Value Description
AUDIO_FORMAT_8BITS 8 bits per sample
AUDIO_FORMAT_16BITS 16 bits per sample
AUDIO_FORMAT_MONO Monophonic output
AUDIO_FORMAT_STEREO Stereo output
AUDIO_FORMAT_LOOP Forward looping 向前循环
AUDIO_FORMAT_BIDILOOP Bidirectional looping 双向循环
AUDIO_FORMAT_REVERSE Reverse playing 反向播放
/*------------------------------------------------------------------------------------*/
/*Music函数 */
/*------------------------------------------------------------------------------------*/
UINT AIAPI ALoadModuleFile(LPSTR lpszFileName, LPAUDIOMODULE* lplpModule,DWORD dwOffset)
//调入一个MOD/MTM/S3M/XM文件
/*---------------------------------------------------------------------*/
UINT AIAPI AFreeModuleFile(LPAUDIOMODULE lpModule)
//释放一个MOD/MTM/S3M/XM文件
/*---------------------------------------------------------------------*/
UINT AIAPI APlayModule(LPAUDIOMODULE lpModule)
//播放一个音乐文件
/*---------------------------------------------------------------------*/
UINT AIAPI AStopModule(VOID)
//停止播放音乐文件
/*---------------------------------------------------------------------*/
UINT AIAPI APauseModule(VOID)
//暂停播放音乐文件
/*---------------------------------------------------------------------*/
UINT AIAPI AResumeModule(VOID)
//重新开始继续播放音乐文件
/*---------------------------------------------------------------------*/
UINT AIAPI AGetModulePosition(LPUINT lpnOrder, LPUINT lpnRow)
//返回声音文件播放位置
/*---------------------------------------------------------------------*/
UINT AIAPI ASetModulePosition(UINT nOrder, UINT nRow)
//改变声音文件播放位置
/*---------------------------------------------------------------------*/
UINT AIAPI AGetModuleVolume(LPUINT lpnVolume)
//返回声音文件播放音量
/*---------------------------------------------------------------------*/
UINT AIAPI ASetModuleVolume(LPUINT lpnVolume)
//改变声音文件播放音量(0-64)
/*---------------------------------------------------------------------*/
typedef struct //音乐结构
{
    CHAR szModuleName[32];
    WORD wFlags;
    WORD nOrders;
    WORD nRestart;
    WORD nTracks;
    WORD nPatterns;
    WORD nPatches;
    WORD nTempo;
    WORD nBPM;
    BYTE aOrderTable[AUDIO_MAX_ORDERS];
    BYTE aPanningTable[AUDIO_MAX_VOICES];
    LPAUDIOPATTERN aPatternTable;
    LPAUDIOPATCH aPatchTable;
} AUDIOMODULE, *LPAUDIOMODULE;
///////////////////////////////////////////
szModuleName 音乐模块名,字符串(32字节)
///////////////////////////////////////////
wFlags 音乐格式
Value Description
AUDIO_MODULE_LINEAR The module uses linear frequencies 线性格式
AUDIO_MODULE_AMIGA Use Amiga logarithmic frequencies 对数格式
AUDIO_MODULE_PANNING Use pan position from instruments 方位格式
///////////////////////////////////////////
nOrders 次序表个数
///////////////////////////////////////////
nRestart 重复时的起点
///////////////////////////////////////////
nTracks 音乐声道数
///////////////////////////////////////////
nPatterns 音乐模型数
///////////////////////////////////////////
nPatches 修补工具数
///////////////////////////////////////////
nTempo 节拍速度
///////////////////////////////////////////
nBPM 每分钟节拍数
///////////////////////////////////////////
aOrderTable 模型播放次序(因为本程序包采用波形模拟方式,模型指波形)
///////////////////////////////////////////
aPanningTable 立体声位置
///////////////////////////////////////////
aPatternTable 模型结构
///////////////////////////////////////////
aPatchTable 修补片结构
/*---------------------------------------------------------------------*/
返回码
Value Description
AUDIO_ERROR_NONE No error ==0
AUDIO_ERROR_INVALHANDLE Invalid voice or waveform handle
AUDIO_ERROR_INVALPARAM Invalid parameter passed
AUDIO_ERROR_NOTSUPPORTED Invalid audio system call
AUDIO_ERROR_BADDEVICEID Invalid device identifier
AUDIO_ERROR_NODEVICE No audio device found
AUDIO_ERROR_DEVICEBUSY Audio device is busy
AUDIO_ERROR_BADFORMAT Bad or unsupported audio playback format

AUDIO_ERROR_NOMEMORY Not enough system memory
AUDIO_ERROR_NODRAMMEMORY Not enough onboard DRAM memory
AUDIO_ERROR_FILENOTFOUND Module or waveform file not found
AUDIO_ERROR_BADFILEFORMAT Invalid module or waveform file format
/*---------------------------------------------------------------------*/
/*------------------------------------------------------------------------------------*/
系统分析(肖舸):
1 系统音乐由MOD,S3M格式播放,声音播放WAV文件
2 音乐可以一次装入多个,按道管理,像CD一样,声音可以一次装入多个,通过声道播放,32个
声道可以混音,同时,声道还标示立体声播放时的方位
3 系统内建后台播放,采用时钟中断,但可能与我们的时钟冲突,不建议使用,而应该在我们
的时钟中使用AUpdateAudio函数播放
4 系统内建声音/音乐文件管理,可以使用
/*------------------------------------------------------------------------------------*/


--  作者:卷积内核
--  发布时间: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文件中,样本是交替出现的。
  WAVE文件格式说明表  

  偏移地址 字节数 数据类型 内   容
 
文件头
00H 4 char "RIFF"标志
04H 4 long int 文件长度
08H 4 char "WAVE"标志
0CH 4 char "fmt"标志
10H 4   过渡字节(不定)
14H 2 int 格式类别(10H为PCM形式的声音数据)
16H 2 int 通道数,单声道为1,双声道为2
18H 2 int 采样率(每秒样本数),表示每个通道的播放速度,
1CH 4 long int 波形音频数据传送速率,其值为通道数×每秒数据位数×每样本的数据位数/8。播放软件利用此值可以估计缓冲区的大小。
20H 2 int 数据块的调整数(按字节算的),其值为通道数×每样本的数据位值/8。播放软件需要一次处理多个该值大小的字节数据,以便将其值用于缓冲区的调整。
22H 2   每样本的数据位数,表示每个声道中各个样本的数据位数。如果有多个声道,对每个声道而言,样本大小都一样。
24H 4 char 数据标记符"data"
28H 4 long int 语音数据的长度

  PCM数据的存放方式:

  样本1 样本2
8位单声道 0声道 0声道
8位立体声 0声道(左) 1声道(右) 0声道(左) 1声道(右)
16位单声道 0声道低字节 0声道高字节 0声道低字节 0声道高字节
16位立体声 0声道(左)低字节 0声道(左)高字节 1声道(右)低字节 1声道(右)高字节

 WAVE文件的每个样本值包含在一个整数i中,i的长度为容纳指定样本长度所需的最小字节数。首先存储低有效字节,表示样本幅度的位放在i的高有效位上,剩下的位置为0,这样8位和16位的PCM波形样本的数据格式如下所示。  

样本大小 数据格式 最大值 最小值
8位PCM unsigned int 225 0
16位PCM int 32767 -32767

 


--  作者:卷积内核
--  发布时间: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以减少音频重放对系统运行的影响。

前言:
  微软所提供的开发工具中DirectX 波形声音重放设备是为支持在Windows 95 和Windows NT 中开发游戏和交互媒体应用程序而设计的。 DirectSound 和 Direct3DSound允许你在同一个三维空间中同时运行多个声音文件和移动声音源。只要有可能DirectX ,将充分利用声音加速设备来改善运行状况和减少CPU的使用。但这并不是说你可在三维程序空间运行、编译充斥着大量声音代码的程序,并且随心所欲地将其移来移去。如果你并没有注意如何使用计算机的声音资源你将很快发现,你的计算机的CPU周期绝大部分被你自己添加的一个户外历险游戏的44.1khz,16位立体声的优美声音所占去。

技巧和技术:
首先,我们来谈谈一些有关的定义。你所熟悉的DirectSound包括以下一些术语:
 从缓冲区: 是指用来执行波形数据的应用程序缓冲区。每个执行的波形文件都有一个从缓冲区,每个这样的缓冲区都有自己特定的格式。
主缓冲区: 是DirectSound的输出缓冲区。一般说来,应用程序并非将波形数据直接写入主缓冲区。相反,DirectSound首先在从缓冲区中将波形数据加以合成,然后输入主缓冲区中。注意:仅有一个主缓冲区,并且其格式决定了其输出格式。
静态缓冲区: 包含了内存中的一个完整的声音。因为,通过一次简单操作,你能在缓冲区中写入一个完整的声音。所以,他们对于用户十分方便。静态缓冲区通过声卡的合成加速执行。
流缓冲区: 仅仅为声音的一部分,利用它,我们并不需要大量的内存就能运行较长的声音文件。在利用流缓冲区时,用户必须周期性的在声音缓冲区中写入数据。但是,流缓冲区不能在硬件中进行声音合成。

  我们将再次提到 DirectSound 合成器。这种 DirectSound 元件负责从缓冲区中将声音中按位隔行合成。然后执行诸如:音量大小、均衡调节(左右声道平衡)、频率转换,以及三维操纵等操作。当合成器不能识别你通过API存放的组件时(不同于上面所述的任一控制操作),这正是 DirectSound 的 CPU 核心所要做的。一些运行问题将在“ DirectSound 合成器发生了什么情况”的项目中加以讨论。下面的关于合成器和主、从缓冲区之间关系的示意图,将使读者清楚地了解他们之间的关系。

  如果 DirectSound 开发小组成员见到上面的示意图,他们将对此不屑一顾。因为,合成器远比上图所展示的优秀。上图中,我并没有任何与合成器相关的三维组件,以及其他类型的进程。

  既然,我们获得的背景知识远比上面所述的多,我们将进一步接触其他一系列对我们有用的东西。以下是帮助你最大限度的使用DirectSound功能的一个技术列表。

巧妙使用声音的技巧;
在主缓冲区中使用相同的格式;
将主缓冲区设置为最低存储速率的格式;
在短促频繁的无声时间间隔内连续地使用主缓冲区;
尽可能地使用硬件进行声音合成;
最大限度的保证控制变换;
使用延时的三维进程命令;

下面我们将对上面所述的各项技术作详细的说明。

一.巧妙使用声音的技巧
  DirectSound的最为优秀的特征之一就是独立演奏、控制多声道音频信号的能力。一旦声音的设计者真正地掌握了它们,那么,真可以称得上是一本万利。唯一的花费只是CPU指令周期。每个你所使用的从缓冲区都将消耗CPU指令周期。每一次诸如频率量化等的进程操作都将带来CPU指令周期的额外消耗。三维声音将比常规声音消耗更多的CPU指令周期。这些读者能够想象得到吗?

  你应该同你的声音设计者坐下来一起探讨一下全方位地利用声音演奏带来的强烈震撼(如果你自己本身就是程序员及声音设计者,那么你就自己一个人静下心来仔细领略一下),思考一下究竟是哪种声音在将你的渴望以久的经历传递给用户的过程中起着最为重要的作用。当可能减少从缓冲区的使用时,请采用声音预混技术。例如:如果你正在一个声道中模仿蟋蟀的低吟,而在另一个声道中记录了田蛙的欢唱以烘托出夏夜的氛围时,请将它们合成到一个声道中。

  如果在你的脑海中有应用程序的初步方案,并且在以后将对其进行折衷处理的时候,你应该对该进程进行相当程度的简化。但要记住,想要设计出相当精练的实用声音程序,你需要长时间地字斟句酌。Beatles的Sgt.Pepper's LonelyHearts Club Band 就是一个有创意的伟大杰作。它被记录在一盘四声道盒式录音带中。与此形成对照,现代的音频记录设备提供了至少四十八个声道,并能提供真正的可用来进行实用合成的无限多声道的盒式录音带以及可利用的MIDI序列发生器。

二.在主缓冲区中使用相同的数据格式
  DirectSound合成器将每个从缓冲区中的数据转换为主缓冲区的数据格式,这种数据的转换是在数据进入主缓冲区的合成过程中实现的,它也将占用CPU周期。你可以在保证从缓冲区(例如波形文件)和主缓冲区具有相同的数据格式的前提下消除这笔开销。事实上,正是由于DirecSound的这种格式转换方式,你所要做的仅仅是对比样例速率和声道数,即使样例速率(8位或16位)存在一些差别也没有关系,因为它唯一的后果只是降低主缓冲区的数据存取速率。

  到目前为止,大多数声卡都是ISA总线卡, 它通过DMA方式将声音数据从系统内存移动到当地缓冲区中,处理器在进行内存读写之前将被迫等待DMA的数据传输结束,这势必会影响到CPU的运行速度。对于ISA总线声卡,上述的数据传送方式无疑将对系统的运行产生不可回避的影响,但并不会对新型的32位PCI卡产生任何影响。

  对于DirectSound,DMA数据传输的影响直接关系到数据的输出速率以及主缓冲区的访问速率。我曾听说过这样的趣事:在一台主频为90Mhz的奔腾机上运行基本格式为44.1Khz,16位的立体声音乐程序,DMA将占用多达30%的CPU指令周期!DMA数据传输正是影响DirectSound运行的最大因素。值得庆幸的是,上述问题在你无法顺利执行时非常容易处理。实验表明,减少数据存取速率的最好办法是改变主缓冲区中的数据格式。这里的转换十分明显,运行改变了声音的品质,要改变主缓冲区中的数据格式,只需调用方法IDirectSoundBuffer::Setformat,但不要忘记:你的协作层设置成DSSCL.PRIORITY或DSSCL_EXCLUSIVE,以避免主缓冲区的耗费。

三.在无声时间间隔中连续使用主缓冲区
  DMA同时从另一个方面影响着系统的运行。当没有声音播放时,DirectSound停止了合成器的工作和DMA的活动。如果你的程序中存在着短促频繁的无声时间间隔,在每次声音播放时使合成随声音播放时的间断而起起停停,将比你让合成器一直处于连续的工作状态的情况更糟。在这种情况下,你可以在主缓冲区中强制性的调用方法PLAY使合成器处于激活状态。这样,即使在没有声音播放时,合成器也将连续工作。此时,为了恢复停止合成器的缺省方式,我们可以在主缓冲区中调用方法STOP。

四.使用硬件进行声音合成
  如果系统中装配了支持声卡的DirectSound驱动器,大多数声卡都支持一定水平的硬件合成。下面的一段小窍门将允许你尽量使用硬件合成。

  在你进行硬件声音合成时使用静态缓冲区。DirectSound将试图在静态缓冲中 进行声音合成。

  为你用得最多的声音文件建立声音缓冲区(可用来进行声音硬件合成的合成器是有一定的限度的。

  在声音文件运行时,使用方法IDirectSound::GetCaps决定声音加速硬件支持何种格式,并尽可能的采用这些格式(一些声卡只能合成特定格式的声音文件。例如:SoundBlaster AWE32声卡只能合成单16位格式的声音文件)。

  当你调用CreatSoundBuffer建立从缓冲区时,你得建立静态缓冲区,在结构DSBUFFERDESC的dwPlag区域设定DSBCAPS_LOCHARDWARE标志。你也可以通过设定 DSBCAPS_LOCHARDWARE标志将缓冲区的数据进行强制性的硬件合成。但是,硬件合成所要使用的资源不可用时,CreatSoundBuffer就将出错。

  方法IDirectSound::GetCaps为我们提供了关于声音加速能力的详尽描述,这对于我们进行存取操作有很大的邦助。我们在其工作时间内可以调用GetCaps,调整音频系统以最佳方式使用硬件资源。在 DirectX 文档中查看结构 DSAPS和标志DSCAPS.dwFlags可使我们准确了解到一些系统的有用信息。

五.最低限度的进行声音控制变换
  在从合成器中改变均衡、音量或频率也将影响到应用程序的运行。为了防止声音输出时的中断的产生,DirectSound合成器必须提前20到100毫秒,甚至更多的时间进行声音合成。当你进行声音控制变换时,合成器不得不刷新正在进行的声音合成的缓冲区的信息,重新合成以适应适应的变化。比较好的方法是尽量减少送入系统的控制改变次数。这在按流或群输入时显得尤为重要。同时,我们应尽量减少日常的调用SetVolume、SetPn、SetFrequency的不连续操作。例如:如果你进行帧同步的定期检测,需要将适应从左声道扬声器移动到右声道扬声器时,你就应该每帧调用SetPan一次,而不是每帧两次。

注意: 三维控制变换 (方向、位置、速度、多普勒因子等等) 也将引起DirectSound合成器在其先前进行合成的缓冲区中重新合成。但是,你也可以将一系列三维控制变换集合在一组中,这将只使DirectSound合成器只进行一次重新合成。请仔细阅读以下关于延时控制变换详细说明的章节。

六.使用延时三维进程命令
  正如我在前面所说的那样,三维声音将比常规声音花费更多的CPU指令周期。这是因为在每一个合成周期内,为计算出三维立体声音效将占用更多的CPU指令周期。你应该尽可能地减少使用三维立体声,最好不要使用那些并不对你真正有用的三维声音。这是通过实践得出的在你运行程序时影响整个系统工作的另一因素。在设计你的应用程序时,你应该尽早进行尝试,使其更加容易使声音具有或没有三维效果。你也可以调用具有 DS3DMODE_DISABLE标志的方法IDirectSound::SetMode,使得三维进程能够在三维缓冲区中运行。

  改变三维声音缓冲区以及听众进行的诸如对方位、速度、多普勒因子的操作,都将引起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通常我们不会去管他,直接交给
DirectSound操作即可,只有在你需要完全控制混音的情况下,你才会需要动到primary buffer的内容,根据我的经验,这部份是不需要操心的。需要我们操心的部份是在 secondary buffer,这种buffer理论上可以产生不限制的个数,而primary buffer仅有一个,当然实际能产生多少 secondary buffer要看记忆体的多寡来决定。当我们同时播放secondary buffer的时候,其资料会流向 primary buffer,并且DirectSound会自动完成许多内部的动作,比方音效格式的转换,混音的支援等等。

  播放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这个档案直接套用。
许多函示呼叫,旗标用法,因为太庞杂了,所以请各位叁考DirectSound的文件,我不拟赘述,介绍三个函示如下:

BOOL InitDSound( HWND hwnd)
{
    HRESULT hr;

    // Create DirectSound.
    if ( FAILED( hr = DirectSoundCreate(NULL, &lpds, NULL ) ) )
        return FALSE;

    // Set cooperative level.
    if ( FAILED( hr = lpds->SetCooperativeLevel( hwnd, DSSCL_PRIORITY ) ) )
        return FALSE;

    return TRUE;
} // 初始化DirectSound

BOOL InitDSound( HWND hwnd) 仅简单地初始化DirectSound,事实上跟DirectDraw的初始化部份相当类似,所以感觉上都是基本的调调,没什麽好特别讨论的。接着介绍第二个函示:

LPDIRECTSOUNDBUFFER LoadStatic (LPDIRECTSOUND lpds,LPSTR lpzFileName)
{
    WAVEFORMATEX *pwfx;
    HMMIO hmmio;
    MMCKINFO mmckinfo;
    MMCKINFO mmckinfoParent;
    LPDIRECTSOUNDBUFFER lpdsbStatic=NULL;
    LPVOID lpvAudio1;
    DWORD dwBytes1;
    DSBUFFERDESC dsbdesc;
    UINT cbBytesRead;

    if ( WaveOpenFile( lpzFileName, &hmmio, &pwfx, &mmckinfoParent ) != 0 )
        return FALSE;

    if ( WaveStartDataRead( &hmmio, &mmckinfo, &mmckinfoParent ) != 0 )
        return FALSE;

    if ( lpdsbStatic == NULL )
    {
        memset( &dsbdesc, 0, sizeof( DSBUFFERDESC ) );
        dsbdesc.dwSize = sizeof( DSBUFFERDESC );
        dsbdesc.dwFlags = DSBCAPS_STATIC;
        dsbdesc.dwBufferBytes = mmckinfo.cksize;
        dsbdesc.lpwfxFormat = pwfx;
        if ( FAILED( lpds->CreateSoundBuffer(&dsbdesc, &lpdsbStatic, NULL ) ) )
        {
            WaveCloseReadFile( &hmmio, &pwfx );
            return FALSE;
        }
    }

    lpdsbStatic->Lock( 0, 0, &lpvAudio1, &dwBytes1, NULL, NULL, DSBLOCK_ENTIREBUFFER );
    WaveReadFile( hmmio, dwBytes1, ( BYTE * )lpvAudio1,&mmckinfo, &cbBytesRead );
    lpdsbStatic->Unlock( lpvAudio1, dwBytes1, NULL, 0 );
    WaveCloseReadFile( &hmmio, &pwfx );

    return lpdsbStatic;
}

LoadStatic() 主要功能载入WAV档,在这个函示里面,我们也使用到WAVE.C里面的函示,专门处理WAV档。
当我们传入WAV的档名以後,呼叫成功以後,会传回一个static secondary buffer的指标。这代表我们可以开始对音效做处理了,不用说,当然就是播放罗。底下的函示便是:

void PlayStatic(LPDIRECTSOUNDBUFFER lpdsbStatic)
{
    if ( lpdsbStatic == NULL ) return;

    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


  DirectSound是DirectX API的音频(waveaudio)组件之一,它可以提供快速的混音、硬件加速功能,并且可以直接访问相关设备,当然,最主要的是它提供的功能与现有的(?将来的呢?)设备驱动程序保持兼容性。
DirectSound允许进行波型声音的捕获、重放,也可以通过控制硬件和相应的驱动来获得更多的服务。
DirectSound的优势当然和DirectX的其它组件一样——速度,它允许你最大效率的使用硬件,并拥有良好的兼容性(其实别的都好说,就要这两样就够了:P)。

使用DirectSound可以做到什么呢?

1、很方便的了解硬件能力,并且根据当前计算机硬件配置硬件来决定最好的解决问题的方法。

2、弥补驱动程序的不足——通过属性设置以便硬件能力可以完全发挥,即便是驱动程序没有很好的支持该功能。

3、短传输延迟时间的混音为了快速的响应流。

4、3-D声音

5、声音的捕获

DirectSound核心
DirectSound设备

  这部分描述了将怎样枚举可以使用的声音设备、为一设备创造DirectSound对象、使用对象的方法来设置合作级别、检验设备的能力、创造声音缓冲、配置系统扬声器和压缩数据。

枚举可以使用的声音设备

  一般的情况下,可能并不需要枚举可以使用的声音设备,使用缺省设备不会带来什么麻烦(相信用户-上帝:P的设置),但是如果你希望将程序做得更“面对对象”一些,给用户更多更好的选择(很多3D GAME都可以让用户来选择显卡,道理大同小异),那你就需要使用到枚举可供使用的声音设备了。
  枚举可供使用的声音设备首先要写一回调函数,在程序每找到一可供使用的声音设备时调用,在那个函数里你可以做任何事,并且它可以拥有任何的合法的名字,但是你必须作为DSEnumCallback声明它,回调函数必须返回一BOOL,TRUE则继续列表,FALSE就退出枚举过程了。
  和显示设备的枚举差不多,下面就是载自Dsenum.c的几段代码。由于本人认为使用得不多就没怎么研究(其实也是很简单的:P),所以就不再累赘,后面还有更令人兴奋的东西呢!

代码如下:

//回调函数
BOOL CALLBACK DSEnumProc(LPGUID lpGUID,LPCTSTR lpszDesc,LPCTSTR lpszDrvName,LPVOID lpContext )
{
    HWND hCombo = *(HWND *)lpContext;
    LPGUID lpTemp = NULL;

    if ( lpGUID != NULL )
    {
        if (( lpTemp = malloc( sizeof(GUID))) == NULL )
            return( TRUE );

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

    ComboBox_AddString( hCombo, lpszDesc );
    ComboBox_SetItemData( hCombo,
    ComboBox_FindString( hCombo, 0, lpszDesc ), lpTemp );  //编辑者注:此句有误
    return( TRUE );
}

创造DirectSound对象

创造DirectSound对象最简单的方法是调用DirectSoundCreate函数。

LPDIRECTSOUND lpds;
HRESULT hr = DirectSoundCreate(NULL, &lpds, NULL));

该函数的第一个参数是硬件设备,NULL表示使用默认的设备,第二个参数是远程指针LPDIRECTSOUND的地址,也就是创造的DirectSound对象放置的地址,第三个参数必须为NULL,暂时没有用。
当没有相应的设备或设备在别的程序的控制下不能响应你的呼叫时,函数返回出错,这时,如果你的程序继续工作,所有和DirectSound对象相关的操作都将不可进行!

设置合作级

因为WINDOWS是一多任务环境,可以允许多个应用程序同时工作,当然也会产生多个程序在同时里使用同一设备工作的情况,通过合作级别,DirectX可以保证所有的程序在使用同一设备时不会发生冲突(大家和平共处岂不是一件乐事),所以每个使用DirectSound的程序都应该有一合作级别用来决定允许访问的设备。
DirectSound有四种合作级别:标准级、优先级、独占级和写主缓冲级(write-primary,写是主要的动作),其中游戏普遍使用优先级这种级别可以使程序在同一采样条件下作出最柔韧的输出(MS的文档也很有文学味的嘛!)。

//C的例子,会换成C++的吗?不会说NO吧!
HRESULT hr = lpDirectSound->lpVtbl->SetCooperativeLevel( lpDirectSound, hwnd, DSSCL_PRIORITY );

标准级(DSSCL_NORMAL):该级别只能使用22KHZ、立体声(STEREO)、8位的音乐,并且不能直接的写主缓冲,也不能使用压缩过的声音。

优先级(DSSCL_PAIORITY):可以实现硬件混合(hardware mixing),可以设置主缓冲的声音格式(可以根据需要来使用不同质量的音乐)和压缩过的音乐。

独占级(DSSCL_EXCLUSIVE):当应用程序在前台工作时,其它程序是不可使用声音的。

写主缓冲级(DSSCL_WRITEPRIMARY):最高的合作级,程序可以直接的操纵主缓冲,而且程序必须直接的写主缓冲区(最基层的操作)。在这种级别,第二缓冲将不可用。
除了该级别外,所有试图LOCK主缓冲的操作都将失败,也就是说只有该级别可以对主缓冲进行写操作!
当使用写主缓冲级的程序处于前台时,后台所有程序的第二缓冲都将停止且丢失,而如果这时使用写主缓冲级的程序转到后台工作,它的主缓冲也将丢失并且在又一次转到前台工作时应该还原(restore)。更多的信息将在缓冲区管理里阐述(现在还没有翻译:P不过用过了DDraw以后这些东西应该很熟悉了才是)。
如果要设置写主缓冲级,先应该确定现在是否可以使用该级别——使用IDirectSound::GetCaps函数,检查DSCAPS结构里是否有DSCAPS_EMULDRIVER标志。

检索硬件信息

DirectSound允许应用程序检索硬件信息,当然,在一般情况下,这样做是不必要的,因为DirectSound可以自动有效的使用硬件加速,我们完全可以不用去管硬件是否具有某些能力,不过为了提高程序的效率,这样做还是是有用处的。
检索硬件信息使用IDirectSound::GetCaps方法,例如:

DSCAPS dscaps;

dscaps.dwSize = sizeof(DSCAPS);
HRESULT hr = lpDirectSound->lpVtbl->GetCaps(lpDirectSound,&dscaps);

扬声器的设置

DirectSound的扬声器设置可以用来调节输出音量的大小和优化3D效果。
在WIN98和WIN2000,可以通过IDirectSound::GetSpeakerConfig来获得当前扬声器的设置,并通过IDirectSound::SetSpeakerConfig来改变扬声器设置;而在WIN95里IDirectSound::GetSpeakerConfig只是简单的返回一默认值或是返回使用IDirectSound::SetSpeakerConfig设置后的值。

压缩

应用程序可以通过IDirectSound::Compact来获得最多的可用内存,当然从前面对合作级别的讨论我们可以发现使用压缩的前提是程序的合作级别至少应该是优先级。

DirectSound缓冲
基础(又是费话连篇)


  在初始化DirectSound时,它会自动地为你的程序创建一主缓冲,这个主缓冲的作用就是混音并送到输出设备。
除了主缓冲外,程序至少还应该创建一个辅助缓冲,辅助缓冲的作用是储存将要使用的声音,它可以在不使用的时候释放掉(费话,不过这也就是暗示我们主缓冲是不可释放的)。

你可以在同一段物理内存上创建两个或更多的辅助缓冲(使用IDirectSound::DuplicateSoundBuffer方法),但是如果最初的缓冲(原本)所在的硬件内存资源不支持多缓冲,那么这个调用将以失败告终。
DirectSound还可以同时播放多个声音,当然其大前提是硬件允许。
DirectSound播放一声音只需要很短的时间延迟,如果在播放声音的同时来播放动画,你将感觉不到延迟,但是如果DirectSound需要通过软件仿真来完成这一动作,那么延迟时间将延长5-8倍。
通常情况下,你并不需要和主缓冲打交道,DirectSound会自己来管理它的,除非你要使用自己写的混音部分,这时,DirectSound就会让你自行管理主缓冲,更详细的讲解见主缓冲的存取部分。

静态缓冲和流缓冲

在应用程序里,辅助缓冲可以有两种——静态缓冲(一段内存空间一段完整的声音;好处在于可以一次将全部的声音存入缓冲)和流缓冲(并不将全部的数据一次读入缓冲,而是在播放声音时动态的读入;其好处在于占用空间较小),它们可以分别适应不同的程序需求。
一般的说,如果声音是需要再三播放的,而且容量有限(好比游戏音效),那么使用静态缓冲就更有助于提高程序的效率,相反,如果是很冗长的音乐,还是使用流缓冲的好。

创建辅助缓冲

//创建辅助缓冲的例子
BOOL AppCreateBasicBuffer( LPDIRECTSOUND lpDirectSound, LPDIRECTSOUNDBUFFER *lplpDsb )
{
    PCMWAVEFORMAT pcmwf;
    DSBUFFERDESC dsbdesc;
    HRESULT hr;

    //设置PCMWAVEFORMAT结构
    memset(&pcmwf, 0, sizeof(PCMWAVEFORMAT));
    pcmwf.wf.wFormatTag = WAVE_FORMAT_PCM;
    pcmwf.wf.nChannels = 2;
    pcmwf.wf.nSamplesPerSec = 22050;
    pcmwf.wf.nBlockAlign = 4;
    pcmwf.wf.nAvgBytesPerSec =
    pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign;
    pcmwf.wBitsPerSample = 16;

    //设置DSBUFFERDESC结构
    memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); //将结构置0.
    dsbdesc.dwSize = sizeof(DSBUFFERDESC);
    //使用默认的设置(音量之类)
    dsbdesc.dwFlags = DSBCAPS_CTRLDEFAULT;
    //3秒钟长度的缓冲(3-second buffer)
    dsbdesc.dwBufferBytes = 3 * pcmwf.wf.nAvgBytesPerSec;
    dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf;
    //创建缓冲
    hr = lpDirectSound->lpVtbl->CreateSoundBuffer(lpDirectSound,
    &dsbdesc, lplpDsb, NULL);
    if SUCCEEDED(hr)
    {
        //成功
        return TRUE;
    }
    else
    {
        //失败
        *lplpDsb = NULL;
        return FALSE;
    }
}

很简单是吧,只要填两个STRUCT就OK了。

因为DirectSound对先创建的缓冲优先分配硬件资源,所以你应该先创建重要的缓冲。如果你事先声明要创建一硬件缓冲(和放在显存里的表面差不多),就应该在DSBUFFERDESC结构里设置DSBCAPS_LOCHARDWARE标志,但是如果你得不到足够的硬件资源(硬件内存或混音容量hardware memory or mixing capacity),将无法创建缓冲。
创建缓冲时,也可以声明是静态缓冲(设置DSBCAPS_STATIC标志)还是流缓冲;默认值是流缓冲(上面就是使用的默认值)。
缓冲是和DirectSound对象相关联的,如果释放了DirectSound对象,则它所有的缓冲也都将被释放。

缓冲控制选项

你创建一辅助缓冲时,还应该声明该缓冲需要用到的控制选项。这项工作需要你为DSBUFFERDESC结构设置以DSBCAPS_CTRL为首的标志(这些标志可以是单独的来使用,也可以同时设置几个)。
可用的控制有3-D属性、频率、Pan(左右正道的差值)、音量、Position notification(可能是指播放时的进度)。
为了能在所有的声卡上都可以获得做好的效果,最好只设置需要的控制选项。如果一块声卡支持硬件缓冲但不支持底盘控制(pan control),那么DiractSound只会在DSBCAPS_CTRLPAN标志没有被声明时使用硬件加速。这也就是说,DirectSound通过控制选项来决定如何为缓冲来分配硬件资源。
如果你使用一个缓冲不支持的控制,譬如为一个并没有声明DSBCAPS_CTRLVOLUME标志的缓冲调用IDirectSoundBuffer::SetVolume方法,是不可能成功的。

主缓冲的存取

如果你不满意DirectSound的工作,可以直接的操纵主声音缓冲,也可以说是直接的操纵硬件了,但是这将意味着DirectSound的部分特性不可用,包括辅助缓冲的混音和混音的硬件加速。
主缓冲其实是硬件缓冲,它的大小是由硬件来决定的,而这个值通常是很小的,因此你应该使用数据流的方式来访问该缓冲。而且如果硬件不提供主缓冲,你就不能直接的访问它了(其实是访问DX软件仿真的主缓冲);你应该调用IDirectSoundBuffer::GetCaps方法来检查DSBCAPS结构里是否有DSBCAPS_LOCHARDWARE标志,有才可以设置DSSCL_WRITEPRIMARY合作级别来访问主缓冲。

//写主缓冲时的初始化工作
BOOL AppCreateWritePrimaryBuffer( LPDIRECTSOUND lpDirectSound, LPDIRECTSOUNDBUFFER *lplpDsb, LPDWORD lpdwBufferSize, HWND hwnd )
{
    DSBUFFERDESC dsbdesc;
    DSBCAPS dsbcaps;
    HRESULT hr;
    WAVEFORMATEX wf;

    //初始化WAVEFORMATEX 结构
    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;

    //初始化DSBUFFERDESC结构
    memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
    dsbdesc.dwSize = sizeof(DSBUFFERDESC);
    dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
    //缓冲的大小是由硬件决定的
    dsbdesc.dwBufferBytes = 0;
    dsbdesc.lpwfxFormat = NULL; //该字段必须置NULL

    //设置合作级别
    hr = lpDirectSound->lpVtbl->SetCooperativeLevel(lpDirectSound,
    hwnd, DSSCL_WRITEPRIMARY);
    if SUCCEEDED(hr)
    {
        //创建缓冲
        hr = lpDirectSound->lpVtbl->CreateSoundBuffer(lpDirectSound, &dsbdesc, lplpDsb, NULL);
        if SUCCEEDED(hr)
        {
            //设置主缓冲需要的格式
            hr = (*lplpDsb)->lpVtbl->SetFormat(*lplpDsb, &wf);
            if SUCCEEDED(hr)
            {
                //获得主缓冲的大小
                dsbcaps.dwSize = sizeof(DSBCAPS);
                (*lplpDsb)->lpVtbl->GetCaps(*lplpDsb, &dsbcaps);
                *lpdwBufferSize = dsbcaps.dwBufferBytes;
                return TRUE;
            }
        }
    }

    //如果失败
    *lplpDsb = NULL;
    *lpdwBufferSize = 0;
    return FALSE;
}

播放声音

播放声音要通过以下步骤:
1、锁定辅助缓冲的一部分以获得你所需要的那部分缓冲的基址。
2、向缓冲写数据。
3、解锁。
4、使用IDirectSoundBuffer::Play方法来播放声音。
如果是使用的流缓冲,还需要反复的执行1-3步骤。

因为流缓冲存储通常是循环的(就像循环队列),所以当你锁定缓冲时DirectSound会返回2个指针。譬如你从一个只有4,000字节的缓冲中点开始锁定3,000字节长的数据,那么DirectSound返回的第一个指针是从中点开始的那2,000字节,而第二个指针则是缓冲最前面的那1,000字节。当然如果没有发生这种情况第二个指针是NULL。
如果你设置了DSBPLAY_LOOPING标志,那么音乐将不停的播放下去,除非你使用IDirectSoundBuffer::Stop来停止它。
有关流缓冲的部分在后继章节里还将详细的讨论到。

下面就是一个C语言的例子:
//写辅助缓冲
BOOL AppWriteDataToBuffer(
LPDIRECTSOUNDBUFFER lpDsb, //缓冲
DWORD dwOffset,            //要写入数据的缓冲徧移地址
LPBYTE lpbSoundData,       //要写入的数据
DWORD dwSoundBytes)        //一次写入的块的大小
{
    LPVOID lpvPtr1;
    DWORD dwBytes1;
    LPVOID lpvPtr2;
    DWORD dwBytes2;
    HRESULT hr;

    //获得将要写的块的地址
    hr = lpDsb->lpVtbl->Lock(lpDsb, dwOffset, dwSoundBytes, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);

    //如果返回DSERR_BUFFERLOST,还原并重新锁定
    if (DSERR_BUFFERLOST == hr)
    {
        lpDsb->lpVtbl->Restore(lpDsb);
        hr = lpDsb->lpVtbl->Lock(lpDsb, dwOffset, dwSoundBytes, &lpvPtr1, &dwAudio1, &lpvPtr2, &dwAudio2, 0);
    }

    if SUCCEEDED(hr)
    {
        //拷贝数据
        CopyMemory(lpvPtr1, lpbSoundData, dwBytes1);
        if (NULL != lpvPtr2)
        {
            CopyMemory(lpvPtr2, lpbSoundData+dwBytes1, dwBytes2);
        }
        //解锁
        hr = lpDsb->lpVtbl->Unlock(lpDsb, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);
        if SUCCEEDED(hr)
        {
            //成功
            return TRUE;
        }
    }

    //失败
    return FALSE;
}

重放(PLAYBACK)的控制

你可以通过IDirectSoundBuffer::GetVolume和IDirectSoundBuffer::SetVolume来获得或设置该缓冲的音量,设置主缓冲的音量将改变声卡的设置。
同样的,你也可以通过IDirectSoundBuffer::GetFrequency和IDirectSoundBuffer::SetFrequency来获得或设置声音的频率,通过IDirectSoundBuffer::GetPan和IDirectSoundBuffer::SetPan来检索或改变左右声道的相对差,但是你不可以改变主缓冲的相应设置。

诚如前面所说的,这些缓冲控制都必须在设置了相应的标志才可以使用。

播放进度和可以被写的位置(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);
if SUCCEEDED(hr)
{
    //成功后就可以使用lpDsNotify->SetNotificationPositions了。
}

注意:IDirectSoundNotify接口和创建它的辅助缓冲是相关联的。

现在你可以通过WIN32 API的CreateEvent()来创建一事件对象。然后你需要为DSBPOSITIONNOTIFY结构的hEventNotify设置一句柄(CreateEvent()返回的),并且设置你想设置的通知位置的偏移值给dwOffset,就可以来设置通知位置了。

设置通知位置的例子如下:

DSBPOSITIONNOTIFY PositionNotify;

PositionNotify.Offset = DSBPN_OFFSETSTOP;
PositionNotify.hEventNotify = hMyEvent;
// hMyEvent是一个由CreateEvent()返回的句柄

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)
{
    LPVOID lpvPtr1;
    DWORD dwBytes1;
    LPVOID lpvPtr2;
    DWORD dwBytes2;
    HRESULT hr;

    //锁定缓冲
    hr = lpDsbPrimary->lpVtbl->Lock(lpDsbPrimary, dwOldPos, dwDataBytes, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);

    //如果返回DSERR_BUFFERLOST,还原DS并从新锁定
    if (DSERR_BUFFERLOST == hr)
    {
        lpDsbPrimary->lpVtbl->Restore(lpDsbPrimary);
        hr = lpDsbPrimary->lpVtbl->Lock(lpDsbPrimary, dwOldPos, dwDataBytes, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);
    }

    if SUCCEEDED(hr)
    {
        //将混音的数据送到缓冲区内
        CustomMixer(lpAppStreamInfo, lpvPtr1, dwBytes1);
        //该函数负责混合若干数据流。下同
        *lpdwNewPos = dwOldPos + dwBytes1;
        if (NULL != lpvPtr2)
        {
            CustomMixer(lpAppStreamInfo, lpvPtr2, dwBytes2);
            *lpdwNewPos = dwBytes2;
        }

        //解锁
        hr = lpDsbPrimary->lpVtbl->Unlock(lpDsbPrimary, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);

        if SUCCEEDED(hr)
        {
            return TRUE;
        }
    }

    //锁定或解锁失败
    return FALSE;
}


--  作者:卷积内核
--  发布时间:9/21/2006 11:21:00 AM

--  
十、游戏背景音乐

 
  不能出声的哑巴游戏总是不太爽。今天下午花了一点时间大致把游戏背景音乐封装了一下。这个封装基本是参照了DMUtil.h的几个类。原来的打算是自己做一个MySound的类,封装到自己的MySound.h和MySound.cpp里。但是我的Demo用到了SDK的FrameWork,不知道为什么,我封装好的简易音乐类无法和原来的Demo正常编译连接。于是只好老老实实按照FrameWork的封装好的DMUtil.h在进一步封装。

  其实还是可以做得更好一些的。但是现在没那么多时间,能用就成吧。

#include "DMUtil.h"
先在类里面申明
CMusicManager* g_pMusicManager;
CMusicSegment* g_pMusicSegment;

构造初始
g_pMusicManager = NULL;
g_pMusicSegment = NULL;

封装的四个函数
void InitMusic(HWND hWnd);                        // 初始化
HRESULT LoadSegmentFile( HWND hDlg, TCHAR* strFileName); // 载入音乐文件
void PlayMusic(HWND hDlg, BOOL bLooped);                // 播放,可控制是否循环播放
void StopMusic();                              // 停止播放


HRESULT CMyD3DApplication::LoadSegmentFile( HWND hDlg, TCHAR* strFileName )
{
    HRESULT hr;
    SAFE_DELETE( g_pMusicSegment );
    // Have the loader collect any garbage now that the old
    // segment has been released
    g_pMusicManager->CollectGarbage();
    // Set the media path based on the file name (something like C:\MEDIA)
    // to be used as the search directory for finding DirectMusic content
    // related to this file.
    TCHAR strMediaPath[MAX_PATH];
    GetCurrentDirectory(MAX_PATH, strMediaPath);
    g_pMusicManager->SetSearchDirectory(strMediaPath);
    BOOL bMidiFile = FALSE;
    // Load the file into a DirectMusic segment
    if( FAILED( g_pMusicManager->CreateSegmentFromFile( &g_pMusicSegment, strFileName,
TRUE, bMidiFile ) ) )
    {
        MessageBox( NULL, "No find Music sound.", "", MB_OK | MB_ICONERROR );
        return S_FALSE;
    }
    return S_OK;
}

void CMyD3DApplication::InitMusic(HWND hWnd)
{
    g_pMusicManager = new CMusicManager();
    g_pMusicManager->Initialize(m_hWnd );
    TCHAR strFileName[MAX_PATH];
    strcpy( strFileName, "sample.sgt" );
    LoadSegmentFile(m_hWnd, strFileName ) ;
}

void CMyD3DApplication::PlayMusic( HWND hDlg, BOOL bLooped )
{
    if( bLooped )
    {
        // Set the segment to repeat many times
        g_pMusicSegment->SetRepeats( DMUS_SEG_REPEAT_INFINITE );
    }
    else
    {
        // Set the segment to not repeat
        g_pMusicSegment->SetRepeats( 0 );
    }

    g_pMusicSegment->Play( DMUS_SEGF_BEAT );
}

void CMyD3DApplication::StopMusic()
{
    g_pMusicSegment->Stop( DMUS_SEGF_BEAT );
}

就这么简单。四个函数就搞定游戏背景音乐了。


W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
3,796.875ms