DirectSound 3D 特效相关接口: IDirectSound3DBuffer8、IDirectSound3DListener8.
IDirectSound3DBuffer8 是声源, 它可以有一个或多个; IDirectSound3DListener8 是听者, 它只能有一个.
IDirectSound3DBuffer8 通过 QueryInterface() 从次缓冲获取, 要求该次缓冲建立时必须指定 DSBCAPS_CTRL3D;
IDirectSound3DListener8 通过 QueryInterface() 从主缓冲区获取, 该主缓冲区建立时也必须指定 DSBCAPS_CTRL3D.
"声源" 和 "听者" 可以交互变换, 也可独立变换; 只变换 "听者" 时, 提供声音的缓冲区建立时也应该指定 DSBCAPS_CTRL3D.
IDirectSound3DBuffer8 其实只负责 3D 效果变换, 真正提供声音和播放控制的还是它之前的 IDirectSound3DBuffer 或 IDirectSound3DBuffer8.
IDirectSound3DBuffer8 相关方法:
{3D 音效处理模式} GetMode()、SetMode() DS3DMODE_NORMAL = $00000000; //普通 DS3DMODE_HEADRELATIVE = $00000001; //声源的参数是相对于听者的 DS3DMODE_DISABLE = $00000002; //禁用 3D 特效 {最大、最小距离} GetMaxDistance()、SetMaxDistance() GetMinDistance()、SetMinDistance() {位置、速度、方向} GetPosition()、SetPosition() GetVelocity()、SetVelocity() GetConeOrientation()、SetConeOrientation() {声源椎参数} GetConeAngles()、SetConeAngles() GetConeOutsideVolume()、SetConeOutsideVolume() {一起获取或设置参数} GetAllParameters()、SetAllParameters()
IDirectSound3DListener8 相关方法:
{位置、速度、方向} GetPosition()、SetPosition() GetVelocity()、SetVelocity() GetOrientation()、SetOrientation() {距离因子、多普勒因子、滚降因子} GetDistanceFactor()、SetDistanceFactor() GetDopplerFactor()、SetDopplerFactor() GetRolloffFactor()、SetRolloffFactor() {一起获取或设置参数} GetAllParameters()、SetAllParameters() {延迟设置} CommitDeferredSettings() //在使用系列 Set... 方法时, 如果指定了 DS3D_DEFERRED 参数, 则需用该方法提交参数设置; //和 DS3D_DEFERRED = 1 对应的是 DS3D_IMMEDIATE = 0, 表示立即生效; 使用延迟设置更节省资源.
测试代码(测试文件须是单声道的 Wave):
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls; type TForm1 = class(TForm) Button1: TButton; Button2: TButton; RadioGroup1: TRadioGroup; RadioGroup2: TRadioGroup; GroupBox1: TGroupBox; LabeledEdit1: TLabeledEdit; LabeledEdit2: TLabeledEdit; LabeledEdit3: TLabeledEdit; LabeledEdit4: TLabeledEdit; LabeledEdit5: TLabeledEdit; LabeledEdit6: TLabeledEdit; LabeledEdit7: TLabeledEdit; LabeledEdit8: TLabeledEdit; LabeledEdit9: TLabeledEdit; LabeledEdit10: TLabeledEdit; LabeledEdit11: TLabeledEdit; LabeledEdit12: TLabeledEdit; LabeledEdit13: TLabeledEdit; LabeledEdit14: TLabeledEdit; GroupBox2: TGroupBox; LabeledEdit15: TLabeledEdit; LabeledEdit16: TLabeledEdit; LabeledEdit17: TLabeledEdit; LabeledEdit18: TLabeledEdit; LabeledEdit19: TLabeledEdit; LabeledEdit20: TLabeledEdit; LabeledEdit21: TLabeledEdit; LabeledEdit22: TLabeledEdit; LabeledEdit23: TLabeledEdit; LabeledEdit24: TLabeledEdit; LabeledEdit25: TLabeledEdit; LabeledEdit26: TLabeledEdit; LabeledEdit27: TLabeledEdit; LabeledEdit28: TLabeledEdit; LabeledEdit29: TLabeledEdit; procedure FormCreate(Sender: TObject); procedure FormShow(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure LabeledEdit1KeyPress(Sender: TObject; var Key: Char); procedure LabeledEdit1Change(Sender: TObject); procedure LabeledEdit15Change(Sender: TObject); procedure RadioGroup1Click(Sender: TObject); procedure RadioGroup2Click(Sender: TObject); end; var Form1: TForm1; implementation {$R *.dfm} uses DirectSound, ReadWave2; //ReadWave2 是前面(9)自定义的单元 var myDSound: IDirectSound8; buf: IDirectSoundBuffer; buf3D: IDirectSound3DBuffer8; //3D声源 Listener: IDirectSound3DListener8; dwApply: DWORD = DS3D_IMMEDIATE; //0 {建立 IDirectSound8、IDirectSound3DListener8} procedure TForm1.FormCreate(Sender: TObject); var bufDesc: TDSBufferDesc; bufPrimary: IDirectSoundBuffer; //这里的主缓冲区只是个临时的桥 begin System.ReportMemoryLeaksOnShutdown := true; DirectSoundCreate8(nil, myDSound, nil); myDSound.SetCooperativeLevel(Handle, DSSCL_PRIORITY); //要用到主缓冲区, 所以使用 DSSCL_PRIORITY 级别 ZeroMemory(@bufDesc, SizeOf(TDSBufferDesc)); bufDesc.dwSize := SizeOf(TDSBufferDesc); bufDesc.dwFlags := DSBCAPS_PRIMARYBUFFER or DSBCAPS_CTRL3D; //标识是主缓冲区、同时准备使用 3D 特效 bufDesc.dwBufferBytes := 0; bufDesc.lpwfxFormat := nil; bufDesc.guid3DAlgorithm := DS3DALG_DEFAULT; //主缓冲区在 DSBCAPS_CTRL3D 模式下可以启用这个选项 myDSound.CreateSoundBuffer(bufDesc, bufPrimary, nil); bufPrimary.QueryInterface(IID_IDirectSound3DListener8, Listener); //bufPrimary := nil; end; {建立 IDirectSoundBuffer、IDirectSound3DBuffer8, 并载入播放资源} procedure TForm1.Button1Click(Sender: TObject); var wav: TReadWave; bufDesc: TDSBufferDesc; p1: Pointer; n1: DWORD; begin Self.OnShow(nil); wav := TReadWave.Create; {如果打开失败或不是单声道则退出} if not wav.OpenDialog or (wav.Format.nChannels <> 1) then begin wav.Free; Exit; end; ZeroMemory(@bufDesc, SizeOf(bufDesc)); bufDesc.dwSize := SizeOf(bufDesc); bufDesc.dwFlags := DSBCAPS_STATIC or DSBCAPS_CTRL3D; bufDesc.dwBufferBytes := wav.Size; bufdesc.lpwfxFormat := @wav.Format; myDSound.CreateSoundBuffer(bufDesc, buf, nil); buf.QueryInterface(IID_IDirectSound3DBuffer8, buf3D); buf.Lock(0, 0, @p1, @n1, nil, nil, DSBLOCK_ENTIREBUFFER); wav.Read(p1, wav.Size); wav.Free; buf.Unlock(p1, n1, nil, 0); buf.Play(0, 0, DSBPLAY_LOOPING); end; procedure TForm1.Button2Click(Sender: TObject); begin if buf <> nil then buf.Stop; end; {初始化控件及默认值} procedure TForm1.FormShow(Sender: TObject); var i: Integer; begin Button1.Caption := '打开并播放'; Button2.Caption := '停止播放'; RadioGroup1.Caption := 'Mode:'; RadioGroup1.Items.CommaText := 'DS3DMODE_NORMAL, DS3DMODE_HEADRELATIVE, DS3DMODE_DISABLE'; RadioGroup1.ItemIndex := 0; RadioGroup2.Caption := 'Apply:'; RadioGroup2.Items.CommaText := 'DS3D_IMMEDIATE, DS3D_DEFERRED'; RadioGroup2.ItemIndex := dwApply; GroupBox1.Caption := '声源控制:'; LabeledEdit1.EditLabel.Caption := 'Position-X:'; LabeledEdit2.EditLabel.Caption := 'Position-Y:'; LabeledEdit3.EditLabel.Caption := 'Position-Z:'; LabeledEdit1.Text := '0.0'; LabeledEdit2.Text := '0.0'; LabeledEdit3.Text := '0.0'; LabeledEdit4.EditLabel.Caption := 'Velocity-X:'; LabeledEdit5.EditLabel.Caption := 'Velocity-Y:'; LabeledEdit6.EditLabel.Caption := 'Velocity-Z:'; LabeledEdit4.Text := '0.0'; LabeledEdit5.Text := '0.0'; LabeledEdit6.Text := '0.0'; LabeledEdit7.EditLabel.Caption := 'Orientation-X:'; LabeledEdit8.EditLabel.Caption := 'Orientation-Y:'; LabeledEdit9.EditLabel.Caption := 'Orientation-Z:'; LabeledEdit7.Text := '0.0'; LabeledEdit8.Text := '0.0'; LabeledEdit9.Text := '1.0'; LabeledEdit10.EditLabel.Caption := 'Min:'; LabeledEdit11.EditLabel.Caption := 'Max:'; LabeledEdit10.Text := '0.0'; LabeledEdit11.Text := '1000000000.0'; LabeledEdit12.EditLabel.Caption := 'Volume:'; LabeledEdit12.Text := '0'; LabeledEdit13.EditLabel.Caption := 'InsideConeAngle:'; LabeledEdit14.EditLabel.Caption := 'OutsideConeAngle:'; LabeledEdit13.Text := '360'; LabeledEdit14.Text := '360'; // GroupBox2.Caption := '听者控制:'; LabeledEdit15.EditLabel.Caption := 'Position-X:'; LabeledEdit16.EditLabel.Caption := 'Position-Y:'; LabeledEdit17.EditLabel.Caption := 'Position-Z:'; LabeledEdit15.Text := '0.0'; LabeledEdit16.Text := '0.0'; LabeledEdit17.Text := '0.0'; LabeledEdit18.EditLabel.Caption := 'Velocity-X:'; LabeledEdit19.EditLabel.Caption := 'Velocity-Y:'; LabeledEdit20.EditLabel.Caption := 'Velocity-Z:'; LabeledEdit18.Text := '0.0'; LabeledEdit19.Text := '0.0'; LabeledEdit20.Text := '0.0'; LabeledEdit21.EditLabel.Caption := 'Orientation-xFront:'; LabeledEdit22.EditLabel.Caption := 'Orientation-yFront:'; LabeledEdit23.EditLabel.Caption := 'Orientation-zFront:'; LabeledEdit21.Text := '0.0'; LabeledEdit22.Text := '0.0'; LabeledEdit23.Text := '1.0'; LabeledEdit24.EditLabel.Caption := 'Orientation-xTop:'; LabeledEdit25.EditLabel.Caption := 'Orientation-yTop:'; LabeledEdit26.EditLabel.Caption := 'Orientation-zTop:'; LabeledEdit24.Text := '0.0'; LabeledEdit25.Text := '1.0'; LabeledEdit26.Text := '0.0'; LabeledEdit27.EditLabel.Caption := 'DistanceFactor:'; LabeledEdit28.EditLabel.Caption := 'DopplerFactor:'; LabeledEdit29.EditLabel.Caption := 'RolloffFactor:'; LabeledEdit27.Text := '1.0'; LabeledEdit28.Text := '1.0'; LabeledEdit29.Text := '1.0'; for i := 0 to self.ComponentCount - 1 do begin if Components[i].ClassNameIs('TLabeledEdit') then begin TLabeledEdit(Components[i]).OnKeyPress := LabeledEdit1.OnKeyPress; if TLabeledEdit(Components[i]).Parent = GroupBox1 then TLabeledEdit(Components[i]).OnChange := LabeledEdit1.OnChange; if TLabeledEdit(Components[i]).Parent = GroupBox2 then TLabeledEdit(Components[i]).OnChange := LabeledEdit15.OnChange; end; end; end; {限制测试数据} procedure TForm1.LabeledEdit1KeyPress(Sender: TObject; var Key: Char); begin Self.Caption := TLabeledEdit(Sender).Name; if not CharInSet(Key, ['-', '.', '0'..'9', #8]) then Key := #0; end; {声源变换时} procedure TForm1.LabeledEdit1Change(Sender: TObject); var str: string; begin if buf3D = nil then Exit; str := TLabeledEdit(Sender).Text; if (str = '-') or (str = '.') or (str = '') then Exit; buf3D.SetPosition(StrToFloat(LabeledEdit1.Text), StrToFloat(LabeledEdit2.Text), StrToFloat(LabeledEdit3.Text), dwApply); buf3D.SetVelocity(StrToFloat(LabeledEdit4.Text), StrToFloat(LabeledEdit5.Text), StrToFloat(LabeledEdit6.Text), dwApply); buf3D.SetConeOrientation(StrToFloat(LabeledEdit7.Text), StrToFloat(LabeledEdit8.Text), StrToFloat(LabeledEdit9.Text), dwApply); buf3D.SetMinDistance(StrToFloat(LabeledEdit10.Text), dwApply); buf3D.SetMaxDistance(StrToFloat(LabeledEdit11.Text), dwApply); buf3D.SetConeOutsideVolume(StrToInt(LabeledEdit12.Text), dwApply); buf3D.SetConeAngles(StrToInt(LabeledEdit13.Text), StrToInt(LabeledEdit14.Text), dwApply); if (dwApply = DS3D_DEFERRED) and Assigned(Listener) then Listener.CommitDeferredSettings; end; {听者变换时} procedure TForm1.LabeledEdit15Change(Sender: TObject); var str: string; rVector: TD3DVector; begin if (buf = nil) or (Listener = nil) then Exit; str := TLabeledEdit(Sender).Text; if (str = '-') or (str = '.') or (str = '') then Exit; Listener.SetPosition(StrToFloat(LabeledEdit15.Text), StrToFloat(LabeledEdit16.Text), StrToFloat(LabeledEdit17.Text), dwApply); Listener.SetVelocity(StrToFloat(LabeledEdit18.Text), StrToFloat(LabeledEdit19.Text), StrToFloat(LabeledEdit20.Text), dwApply); Listener.SetOrientation(StrToFloat(LabeledEdit21.Text), StrToFloat(LabeledEdit22.Text), StrToFloat(LabeledEdit23.Text), StrToFloat(LabeledEdit24.Text), StrToFloat(LabeledEdit25.Text), StrToFloat(LabeledEdit26.Text), dwApply); Listener.SetDistanceFactor(StrToFloat(LabeledEdit27.Text), dwApply); Listener.SetDopplerFactor(StrToFloat(LabeledEdit28.Text), dwApply); Listener.SetRolloffFactor(StrToFloat(LabeledEdit29.Text), dwApply); if dwApply = DS3D_DEFERRED then Listener.CommitDeferredSettings; end; {3D 音效模式} procedure TForm1.RadioGroup1Click(Sender: TObject); begin if Assigned(buf3D) then buf3D.SetMode(RadioGroup1.ItemIndex, dwApply); if (dwApply = DS3D_DEFERRED) and Assigned(Listener) then Listener.CommitDeferredSettings; end; {是否延迟设置; 此变化内在的, 应该不会被觉察} procedure TForm1.RadioGroup2Click(Sender: TObject); var i: Integer; begin if buf3D = nil then Exit; dwApply := TRadioGroup(Sender).ItemIndex; for i := 0 to self.ComponentCount - 1 do if Components[i].ClassNameIs('TLabeledEdit') then TLabeledEdit(Components[i]).OnChange(TLabeledEdit(Components[i])); end; procedure TForm1.FormDestroy(Sender: TObject); begin buf := nil; buf3D := nil; Listener := nil; myDSound := nil; end; end.
窗体设计:
object Form1: TForm1 Left = 0 Top = 0 Caption = 'Form1' ClientHeight = 459 ClientWidth = 759 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'Tahoma' Font.Style = [] OldCreateOrder = False OnCreate = FormCreate OnDestroy = FormDestroy OnShow = FormShow PixelsPerInch = 96 TextHeight = 13 object Button1: TButton Left = 23 Top = 40 Width = 75 Height = 49 Caption = 'Button1' TabOrder = 0 OnClick = Button1Click end object Button2: TButton Left = 104 Top = 40 Width = 75 Height = 49 Caption = 'Button2' TabOrder = 1 OnClick = Button2Click end object GroupBox1: TGroupBox Left = 8 Top = 134 Width = 353 Height = 317 Caption = 'GroupBox1' TabOrder = 2 object LabeledEdit1: TLabeledEdit Left = 16 Top = 42 Width = 97 Height = 21 EditLabel.Width = 61 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit1' TabOrder = 0 OnChange = LabeledEdit1Change OnKeyPress = LabeledEdit1KeyPress end object LabeledEdit2: TLabeledEdit Left = 128 Top = 42 Width = 97 Height = 21 EditLabel.Width = 61 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit2' TabOrder = 1 end object LabeledEdit3: TLabeledEdit Left = 240 Top = 42 Width = 97 Height = 21 EditLabel.Width = 61 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit3' TabOrder = 2 end object LabeledEdit4: TLabeledEdit Left = 16 Top = 90 Width = 97 Height = 21 EditLabel.Width = 61 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit4' TabOrder = 3 end object LabeledEdit5: TLabeledEdit Left = 128 Top = 90 Width = 97 Height = 21 EditLabel.Width = 61 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit5' TabOrder = 4 end object LabeledEdit6: TLabeledEdit Left = 240 Top = 90 Width = 97 Height = 21 EditLabel.Width = 61 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit6' TabOrder = 5 end object LabeledEdit7: TLabeledEdit Left = 16 Top = 138 Width = 97 Height = 21 EditLabel.Width = 61 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit7' TabOrder = 6 end object LabeledEdit8: TLabeledEdit Left = 128 Top = 138 Width = 97 Height = 21 EditLabel.Width = 61 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit8' TabOrder = 7 end object LabeledEdit9: TLabeledEdit Left = 240 Top = 138 Width = 97 Height = 21 EditLabel.Width = 61 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit9' TabOrder = 8 end object LabeledEdit10: TLabeledEdit Left = 16 Top = 186 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit10' TabOrder = 9 end object LabeledEdit11: TLabeledEdit Left = 128 Top = 186 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit11' TabOrder = 10 end object LabeledEdit12: TLabeledEdit Left = 16 Top = 234 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit12' TabOrder = 11 end object LabeledEdit13: TLabeledEdit Left = 16 Top = 282 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit13' TabOrder = 12 end object LabeledEdit14: TLabeledEdit Left = 128 Top = 285 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit14' TabOrder = 13 end end object GroupBox2: TGroupBox Left = 390 Top = 136 Width = 355 Height = 315 Caption = 'GroupBox2' TabOrder = 3 object LabeledEdit15: TLabeledEdit Left = 16 Top = 42 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit15' TabOrder = 0 OnChange = LabeledEdit15Change end object LabeledEdit16: TLabeledEdit Left = 127 Top = 42 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit16' TabOrder = 1 end object LabeledEdit17: TLabeledEdit Left = 240 Top = 42 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit17' TabOrder = 2 end object LabeledEdit18: TLabeledEdit Left = 16 Top = 106 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit18' TabOrder = 3 end object LabeledEdit19: TLabeledEdit Left = 128 Top = 106 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit19' TabOrder = 4 end object LabeledEdit20: TLabeledEdit Left = 240 Top = 106 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit20' TabOrder = 5 end object LabeledEdit21: TLabeledEdit Left = 16 Top = 170 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit21' TabOrder = 6 end object LabeledEdit22: TLabeledEdit Left = 128 Top = 170 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit22' TabOrder = 7 end object LabeledEdit23: TLabeledEdit Left = 240 Top = 170 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit23' TabOrder = 8 end object LabeledEdit24: TLabeledEdit Left = 16 Top = 218 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit24' TabOrder = 9 end object LabeledEdit25: TLabeledEdit Left = 128 Top = 218 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit25' TabOrder = 10 end object LabeledEdit26: TLabeledEdit Left = 240 Top = 218 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit26' TabOrder = 11 end object LabeledEdit27: TLabeledEdit Left = 16 Top = 282 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit27' TabOrder = 12 end object LabeledEdit28: TLabeledEdit Left = 128 Top = 282 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit28' TabOrder = 13 end object LabeledEdit29: TLabeledEdit Left = 240 Top = 282 Width = 97 Height = 21 EditLabel.Width = 67 EditLabel.Height = 13 EditLabel.Caption = 'LabeledEdit29' TabOrder = 14 end end object RadioGroup1: TRadioGroup Left = 200 Top = 17 Width = 281 Height = 98 Caption = 'RadioGroup1' TabOrder = 4 OnClick = RadioGroup1Click end object RadioGroup2: TRadioGroup Left = 504 Top = 17 Width = 241 Height = 96 Caption = 'RadioGroup2' TabOrder = 5 OnClick = RadioGroup2Click end end
运行效果图: