只有使用 IDirectSoundBuffer8 的次缓冲区才能设置"特效", 主缓冲区主要负责的是混音和处理 3D 效果.
IDirectSoundBuffer8(非 IDirectSoundBuffer) 支持以下效果器:
IDirectSoundFXChorus8 //合唱; 微调原生与回声的延迟 IDirectSoundFXCompressor8 //压缩; 压缩某些振幅 IDirectSoundFXDistortion8 //失真; 将波形顶部修改为方形或锯齿形 IDirectSoundFXEcho8 //回声; 重复并衰减回声 IDirectSoundFXFlanger8 //镶边; 延迟回声 IDirectSoundFXGargle8 //漱口; 有人叫它咕嘟效果 IDirectSoundFXI3DL2Reverb8 //环境混响; 房间、大厅等 IDirectSoundFXParamEq8 //均衡; 缩放不同频率的信号 IDirectSoundFXWavesReverb8 //混响; DirectX Media Objects(DMOs), 是微软从 Waves 购买的声音处理技术
使用步骤:
1、通过 IDirectSoundBuffer8 的 SetFX() 方法关联特效, 这个过程主要是给 SetFX() 方法的参数准备 TDSEffectDesc 结构数组;
2、通过 IDirectSoundBuffer8 的 GetObjectInPath() 方法获取特效对象;
3、通过特效对象的 SetAllParameters() 方法设置特效参数.
9 个特效对象都只有两个方法: GetAllParameters()、SetAllParameters(), 两方法的参数都是结构体(各不相同).
{给 IDirectSoundBuffer8 关联特效; 不能在缓冲区锁定或播放时使用:} function SetFX( dwEffectsCount: DWORD; //特效数目, 即第二个参数 pDSFXDesc 的大小 pDSFXDesc: PDSEffectDesc; //TDSEffectDesc 结构的数组 pdwResultCodes: PDWORD //接收设置结果, 不需要则给 nil ): HResult; stdcall; //可以使用 SefFX(0, nil, nil) 删除缓冲区的所有特效. {SetFX() 方法用到的结构体:} TDSEffectDesc = packed record dwSize : DWORD; //结构大小 dwFlags : DWORD; //处理标志; 一般给 0 guidDSFXClass : TGUID; //指定要使用的效果 dwReserved1 : DWORD_PTR; //未使用 dwReserved2 : DWORD_PTR; //未使用 end; //DSEffectDesc.dwFlags DSFX_LOCHARDWARE = $00000001; //使用硬件处理效果; 这其实在 Direct9.0 还不支持 DSFX_LOCSOFTWARE = $00000002; //使用软件处理效果; 同默认 0 //默认 //DSEffectDesc.guidDSFXClass, 这分别是上面九个接口对应的 GUID 查询标识: GUID_DSFX_STANDARD_CHORUS GUID_DSFX_STANDARD_COMPRESSOR GUID_DSFX_STANDARD_DISTORTION GUID_DSFX_STANDARD_ECHO GUID_DSFX_STANDARD_FLANGER GUID_DSFX_STANDARD_GARGLE GUID_DSFX_STANDARD_I3DL2REVERB GUID_DSFX_STANDARD_PARAMEQ GUID_DSFX_WAVES_REVERB {从 IDirectSoundBuffer8 获取特效对象的方法} function GetObjectInPath( const rguidObject: TGUID; //对象查询标识, GUID_DSFX_ ... dwIndex: DWORD; //该特效在 SetFX() 安排特效数组时的索引 const rguidInterface: TGUID; //对象的唯一标识, IID_IDirectSoundFX ... 或 IDirectSoundFX ... out ppObject //返回要获取的特效接口 ): HResult; stdcall; {IDirectSoundFXGargle8.SetAllParameters() 需要结构体} TDSFXGargle = packed record dwRateHz: DWORD; //频率; 取值范围 1..1000, 默认 20 dwWaveShape: DWORD; //波形; 三角波(0)、方波(1) end; //TDSFXGargle.dwRateHz 取值范围, 默认是 20 DSFXGARGLE_RATEHZ_MIN = 1; // DSFXGARGLE_RATEHZ_MAX = 1000; // DSFXGARGLE_WAVE_TRIANGLE = 0; //三角波 DSFXGARGLE_WAVE_SQUARE = 1; //方波
为让代码更简洁, 又把前面自定义的 ReadWave 该为了 ReadWave2 (增加了一个 OpenDialog 方法):
unit ReadWave2; interface uses Windows, Classes, SysUtils, MMSystem, Dialogs; type TReadWave = class private FFileHandle: HMMIO; FFormat: TWaveFormatEx; FSize: DWORD; function GetFormatAndSize(hFile: HMMIO): Boolean; public destructor Destroy; override; function Open(FileName: string): Boolean; //从文件打开 function OpenResource(ResName: string): Boolean; //从资源打开, 资源的指定格式必须是 WAVE function OpenDialog: Boolean; //从对话框打开 function Read(pDest: Pointer; Size: DWORD): Boolean; //读出波形数据 property Format: TWaveFormatEx read FFormat; //读出格式数据 property Size: DWORD read FSize; //读出波形数据的大小 end; implementation { TReadWave } destructor TReadWave.Destroy; begin if FFileHandle > 0 then mmioClose(FFileHandle, 0); inherited; end; function TReadWave.GetFormatAndSize(hFile: HMMIO): Boolean; var ckiRIFF,ckiFmt,ckiData: TMMCKInfo; begin Result := False; if hFile = 0 then Exit; ZeroMemory(@ckiRIFF, SizeOf(TMMCKInfo)); mmioDescend(hFile, @ckiRIFF, nil, MMIO_FINDRIFF); if (ckiRIFF.ckid <> FOURCC_RIFF) or (ckiRIFF.fccType <> mmioStringToFOURCC('WAVE',0)) then Exit; ZeroMemory(@FFormat, SizeOf(TWaveFormatEx)); ZeroMemory(@ckiFmt, SizeOf(TMMCKInfo)); ckiFmt.ckid := mmioStringToFOURCC('fmt', 0); ZeroMemory(@ckiData, SizeOf(TMMCKInfo)); ckiData.ckid := mmioStringToFOURCC('data', 0); if (mmioDescend(hFile, @ckiFmt, @ckiRIFF, MMIO_FINDCHUNK) = MMSYSERR_NOERROR) then mmioRead(hFile, @FFormat, SizeOf(TWaveFormatEx)); mmioAscend(hFile, @ckiFmt, 0); if (mmioDescend(hFile, @ckiData, @ckiRIFF, MMIO_FINDCHUNK) = MMSYSERR_NOERROR) then FSize := ckiData.cksize; Result := FFormat.wFormatTag = WAVE_FORMAT_PCM; end; function TReadWave.Open(FileName: string): Boolean; begin Result := False; if not FileExists(FileName) then Exit; if FFileHandle > 0 then mmioClose(FFileHandle, 0); FFileHandle := mmioOpen(PChar(FileName), nil, MMIO_READ); Result := GetFormatAndSize(FFileHandle); end; function TReadWave.OpenDialog: Boolean; begin with TOpenDialog.Create(nil) do begin Filter := 'Wave File(*.wav)|*.wav'; if Execute then Result := Open(FileName); Free; end; end; function TReadWave.OpenResource(ResName: string): Boolean; var res: TResourceStream; mmioInfo: TMMIOInfo; begin Result := False; res := TResourceStream.Create(HInstance, ResName, 'WAVE'); ZeroMemory(@mmioInfo, SizeOf(TMMIOInfo)); mmioInfo.fccIOProc := FOURCC_MEM; mmioInfo.cchBuffer := res.Size; mmioInfo.pchBuffer := res.Memory; if FFileHandle > 0 then mmioClose(FFileHandle, 0); FFileHandle := mmioOpen(nil, @mmioInfo, MMIO_ALLOCBUF or MMIO_READ); Result := GetFormatAndSize(FFileHandle); res.Free; end; function TReadWave.Read(pDest: Pointer; Size: DWORD): Boolean; begin Result := mmioRead(FFileHandle, pDest, Size) = Size; end; end.
测试代码:
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ComCtrls; type TForm1 = class(TForm) Button1: TButton; //打开并播放 Button2: TButton; //停止 TrackBar1: TTrackBar; //用于调整 TDSFXGargle.dwRateHz RadioGroup1: TRadioGroup; //用于调整 TDSFXGargle.dwWaveShape procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure TrackBar1Change(Sender: TObject); //RadioGroup1.OnClick 也关联它 end; var Form1: TForm1; implementation {$R *.dfm} uses DirectSound, ReadWave2; //ReadWave2 是刚刚修改过的自定义单元 var myDSound: IDirectSound8; buf8: IDirectSoundBuffer8; fxGargle: IDirectSoundFXGargle8; //IDirectSoundFXGargle8 效果器 {建立设备对象并初始化界面} procedure TForm1.FormCreate(Sender: TObject); begin DirectSoundCreate8(nil, myDSound, nil); myDSound.SetCooperativeLevel(Handle, DSSCL_NORMAL); TrackBar1.Min := DSFXGARGLE_RATEHZ_MIN; //1 TrackBar1.Max := DSFXGARGLE_RATEHZ_MAX; //1000 TrackBar1.Position := 20; //默认值 TrackBar1.ShowSelRange := False; TrackBar1.TickStyle := tsNone; RadioGroup1.Items.CommaText := 'TRIANGLE, SQUARE'; RadioGroup1.Columns := 2; RadioGroup1.ItemIndex := 0; RadioGroup1.OnClick := TrackBar1.OnChange; //两个事件的代码相同 System.ReportMemoryLeaksOnShutdown := true; end; {建立缓冲区、关联特效、获取特效对象并播放} procedure TForm1.Button1Click(Sender: TObject); var buf: IDirectSoundBuffer; //最终需要的是 IDirectSoundBuffer8, 这里的 IDirectSoundBuffer 只是做个桥 bufDesc: TDSBufferDesc; rEffect: TDSEffectDesc; //SetFX() 方法需要的结构 wav: TReadWave; p1: Pointer; n1: DWORD; begin {经过对自定义单元的修改, 现在调入一个 Wave 很方便} wav := TReadWave.Create; if not wav.OpenDialog then begin wav.Free; Exit; end; {获取 IDirectSoundBuffer8 接口对象} ZeroMemory(@bufDesc, SizeOf(TDSBufferDesc)); bufDesc.dwSize := SizeOf(TDSBufferDesc); bufDesc.dwFlags := DSBCAPS_CTRLFX; //! bufDesc.dwBufferBytes := wav.Size; bufDesc.lpwfxFormat := @wav.Format; myDSound.CreateSoundBuffer(bufDesc, buf, nil); buf.QueryInterface(IID_IDirectSoundBuffer8, buf8); {载入波形} buf8.Lock(0, 0, @p1, @n1, nil, nil, DSBLOCK_ENTIREBUFFER); wav.Read(p1, n1); wav.Free; buf8.Unlock(p1, n1, nil, 0); {准备 SetFX() 需要的结构} ZeroMemory(@rEffect, SizeOf(TDSEffectDesc)); rEffect.dwSize := SizeOf(TDSEffectDesc); rEffect.dwFlags := 0; rEffect.guidDSFXClass := GUID_DSFX_STANDARD_GARGLE; //指定是 IDirectSoundFXGargle8 效果器 {关联效果器} buf8.SetFX(1, @rEffect, nil); //参数应该是个数组, 既然只有一个元素, 就先省了 {获取效果器对象} buf8.GetObjectInPath(GUID_DSFX_STANDARD_GARGLE, 0, IID_IDirectSoundFXGargle8, fxGargle); {播放} buf8.Play(0, 0, DSBPLAY_LOOPING); // buf := nil; //局部接口会被自动释放 end; {停止播放} procedure TForm1.Button2Click(Sender: TObject); begin if Assigned(buf8) then buf8.Stop; end; {特效变换} procedure TForm1.TrackBar1Change(Sender: TObject); var rGargle: TDSFXGargle; begin if buf8 = nil then Exit; rGargle.dwRateHz := TrackBar1.Position; rGargle.dwWaveShape := RadioGroup1.ItemIndex; fxGargle.SetAllParameters(rGargle); end; procedure TForm1.FormDestroy(Sender: TObject); begin buf8 := nil; myDSound := nil; end; end.
运行效果图: