建立 IDirectSound8 對象後, 首先要通過其 SetCoOperativeLevel() 方法設置協作優先級;
因為其它應用程序有可能同時使用該設備(聲卡), 這是必需的步驟.
function SetCoOperativeLevel(
hwnd: HWND; //窗口句柄
dwLevel: DWord //協作優先級
): HResult; stdcall;
//協作優先級選項
DSSCL_NORMAL = 1; //普通; 使用次緩沖區, 不能修改、壓縮、設置主緩沖區; 不影響使用該設備的其它應用程序
DSSCL_PRIORITY = 2; //優先; 可設置但不能直接寫入主緩沖區
DSSCL_EXCLUSIVE = 3; //獨占; 已過時, 現同 DSSCL_PRIORITY
DSSCL_WRITEPRIMARY = 4; //頂級; 必須直接寫入主緩沖區, 不能使用次緩沖區
然後通過 IDirectSound8.CreateSoundBuffer() 方法建立緩沖區, 這個過程主要是填寫 TDSBufferDesc 結構;
填寫 TDSBufferDesc 結構時又同時需要 TWaveFormatEx 結構的指針, 這個 TWaveFormatEx 結構我們會直接從 Wave 文件中讀取.
只播放一個文件可以指定建立主緩沖區(它只能有一個), 當然也可使用次緩沖區(這同時會自動建立主緩沖區), 次緩沖區的聲音最終也是經主緩沖混音輸出.
緩沖區又分靜態緩沖區和流式緩沖區:
流式緩沖區適合播放較大的聲音文件, 其原理是邊寫入變播放(程序寫起來很麻煩);
使用靜態緩沖區可以一次性設置到需要的大小, 這樣只寫入一次就夠了, 下面的例子就先使用了這種簡單方法.
function CreateSoundBuffer(
const pcDSBufferDesc: TDSBufferDesc; //描述緩沖區的結構
out ppDSBuffer: IDirectSoundBuffer; //緩沖區對象
pUnkOuter: IUnknown //未使用, nil
): HResult; stdcall; //錯誤碼
TDSBufferDesc = packed record
dwSize: DWord; //結構大小(字節)
dwFlags: DWord; //功能標識
dwBufferBytes: DWord; //緩沖區大小
dwReserved: DWord; //未使用, 須為 0
lpwfxFormat: PWaveFormatEx; //TWaveFormatEx 結構的指針
guid3DAlgorithm: TGUID; //關於 3D 算法的 GUID 常量; DX7 後的版本可用, 當前結構比之前的 TDSBufferDesc1 就多出了這個字段
end;
//TDSBufferDesc.dwFlags:
DSBCAPS_PRIMARYBUFFER = $00000001; //使用主緩沖區, 默認是使用次緩沖區
DSBCAPS_STATIC = $00000002; //靜態緩沖區, 若有可能會將緩沖區建立在聲卡上; 默認是創建流式緩沖區
DSBCAPS_LOCHARDWARE = $00000004; //強制使用硬緩沖
DSBCAPS_LOCSOFTWARE = $00000008; //強制使用軟緩沖
DSBCAPS_CTRL3D = $00000010; //緩沖區具有 3D 控制能力
DSBCAPS_CTRLFREQUENCY = $00000020; //緩沖區具有頻率控制能力
DSBCAPS_CTRLPAN = $00000040; //緩沖區具有相位控制能力
DSBCAPS_CTRLVOLUME = $00000080; //緩沖區具有音量控制能力
DSBCAPS_CTRLPOSITIONNOTIFY = $00000100; //緩沖區具有位置通知能力
DSBCAPS_CTRLFX = $00000200; //緩沖區支持特效
DSBCAPS_STICKYFOCUS = $00004000; //當程序切換到其它不使用 DirectSound 的程序時, 可繼續播放, 否則會靜音
DSBCAPS_GLOBALFOCUS = $00008000; //當程序即使切換到其它使用 DirectSound 的程序, 該緩沖區仍可用, 除非其它程序有優先設置
DSBCAPS_GETCURRENTPOSITION2 = $00010000; //使 GetCurrentPosition 能獲取更精確的播放位置
DSBCAPS_MUTE3DATMAXDISTANCE = $00020000; //衰減的最大距離, 僅適用於軟緩沖區
DSBCAPS_LOCDEFER = $00040000; //讓 DirectSound 自動延遲決定是使用硬緩沖還是軟緩沖
DSBCAPS_TRUEPLAYPOSITION = $00080000; //強制 GetCurrentPosition 返回真實的播放位置, 僅在 Vista 之後的版本有效
向緩沖區寫入數據前需要先使用 IDirectSoundBuffer.Lock() 方法鎖定內存(先禁止 Windows 自動管理這塊內存).
Lock() 會通過其 var 參數返回寫入指針和要寫入的數據大小(這裡的緩沖區特別是設備提供的緩沖區不會太大, 所以大小不會太隨意).
Lock() 返回兩個寫入指針和兩個數據大小(一對); 當寫到緩沖區尾部還不能寫完時, 就要繞回來從頭寫, 此時就需要第二個指針和大小.
寫這個雙指針的程序時也有點繞, 幸好本例暫時只用到一個指針.
Lock() 還有兩個鎖定標識常量, 本例使用 DSBLOCK_ENTIREBUFFER, 標識鎖定整個緩沖區, 這樣其前兩個參數也暫時不用考慮了.
寫入完成後還要解鎖.
function Lock(
dwOffset: DWord; //鎖定起始處的偏移量
dwBytes: DWord; //要鎖定的字節數
ppvAudioPtr1: PPointer; //輸出第一個內存指針
pdwAudioBytes1: PDWord; //輸出已鎖定的字節數
ppvAudioPtr2: PPointer; //輸出第二個內存指針
pdwAudioBytes2: PDWord; //輸出已鎖定的字節數
dwFlags: DWord //鎖定控制標志
): HResult; stdcall; //錯誤碼
//Lock.dwFlags
DSBLOCK_FROMWRITECURSOR = $00000001; //從寫入位置鎖定, 參數 dwOffset 將被忽略
DSBLOCK_ENTIREBUFFER = $00000002; //鎖定整個緩沖區, 參數 dwBytes 將被忽略
//
function Unlock(
pvAudioPtr1: Pointer; //第一個鎖定的偏移量
dwAudioBytes1: DWord; //需要解鎖的字節數
pvAudioPtr2: Pointer; //第二個鎖定的偏移量
dwAudioBytes2: DWord //需要解鎖的字節數
): HResult; stdcall; //錯誤碼
寫入後, 就可以通過 IDirectSoundBuffer.Play()、Stop() 控制播放了:
function Play(
dwReserved1: DWord; //未使用, 0
dwPriority: DWord; //未使用, 0
dwFlags: DWord //播放控制標志; 如果只播放一次可以直接給個 0
): HResult; stdcall; //
//Play.dwFlags
DSBPLAY_LOOPING = $00000001; //循環播放
DSBPLAY_LOCHARDWARE = $00000002; //僅播放硬緩沖區的聲音
DSBPLAY_LOCSOFTWARE = $00000004; //僅播放軟緩沖區的聲音
DSBPLAY_TERMINATEBY_TIME = $00000008; //暫未學習
DSBPLAY_TERMINATEBY_DISTANCE = $00000010; //暫未學習
DSBPLAY_TERMINATEBY_PRIORITY = $00000020; //暫未學習
//
function Stop: HResult; stdcall; //叫暫停更合適
學寫下面的程序前, 我曾想過是否使用前人寫過的 DSUtil.pas, 但種種原因還是放棄了, 主要還是想了解得透徹些.
程序用到了以前寫過的兩個函數:
http://www.cnblogs.com/del/archive/2009/11/06/1597735.Html
http://www.cnblogs.com/del/archive/2009/11/06/1597735.Html
測試程序只用到了三個 Button, 還有准備一個測試文件(C:\Temp\Test.wav).
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses DirectSound, MMSystem;
const wavPath = 'C:\Temp\Test.wav'; //測試用的 Wave, 須保證文件存在並注意路徑權限, 且只能是 PCM 格式的 Wave 文件
var
myDSound: IDirectSound8;
buf: IDirectSoundBuffer; //緩沖區對象
{從 Wave 文件中獲取 TWaveFormatEx 結構的函數}
function GetWaveFmt(FilePath: string; var fmt: TWaveFormatEx): Boolean;
var
hFile: HMMIO;
ckiRIFF,ckiFmt: TMMCKInfo;
begin
Result := False;
hFile := mmioOpen(PChar(FilePath), nil, MMIO_READ);
if hFile = 0 then Exit;
ZeroMemory(@ckiRIFF, SizeOf(TMMCKInfo));
ZeroMemory(@ckiFmt, SizeOf(TMMCKInfo));
ZeroMemory(@fmt, SizeOf(TWaveFormatEx));
ckiFmt.ckid := mmiOStringToFOURCC('fmt', 0);
mmioDescend(hFile, @ckiRIFF, nil, MMIO_FINDRIFF);
if (ckiRIFF.ckid = FOURCC_RIFF) and
(ckiRIFF.fccType = mmiOStringToFOURCC('WAVE',0)) and
(mmioDescend(hFile, @ckiFmt, @ckiRIFF, MMIO_FINDCHUNK) = MMSYSERR_NOERROR) then
Result := (mmioRead(hFile, @fmt, ckiFmt.cksize) = ckiFmt.cksize);
mmioClose(hFile, 0);
end;
{從 Wave 文件中獲取波形數據的函數}
function GetWaveData(FilePath: string; var stream: TMemoryStream): Boolean;
var
hFile: HMMIO;
ckiRIFF,ckiData: TMMCKInfo;
begin
Result := False;
hFile := mmioOpen(PChar(FilePath), nil, MMIO_READ);
if hFile = 0 then Exit;
ZeroMemory(@ckiRIFF, SizeOf(TMMCKInfo));
ZeroMemory(@ckiData, SizeOf(TMMCKInfo));
ckiData.ckid := mmiOStringToFOURCC('data', 0);
mmioDescend(hFile, @ckiRIFF, nil, MMIO_FINDRIFF);
if (ckiRIFF.ckid = FOURCC_RIFF) and
(ckiRIFF.fccType = mmiOStringToFOURCC('WAVE',0)) and
(mmioDescend(hFile, @ckiData, @ckiRIFF, MMIO_FINDCHUNK) = MMSYSERR_NOERROR) then
begin
stream.Size := ckiData.cksize;
Result := (mmioRead(hFile, stream.Memory, ckiData.cksize) = ckiData.cksize);
end;
mmioClose(hFile, 0);
end;
{程序初始化}
procedure TForm1.FormCreate(Sender: TObject);
begin
Button1.Caption := '建立並播放';
Button2.Caption := '反復播放';
Button3.Caption := '暫停';
Button2.Enabled := False;
Button3.Enabled := False;
system.ReportMemoryLeaksOnShutdown := True; //讓程序自動報告內存洩露
end;
{主要程序}
procedure TForm1.Button1Click(Sender: TObject);
var
bufDesc: TDSBufferDesc; //建立緩沖區需要的結構
wavFormat: TWaveFormatEx; //從 Wave 中提取的結構
wavData: TMemoryStream; //從 Wave 中提取的波形數據
p1: Pointer; //從緩沖區獲取的寫指針
n1: DWord; //要寫入緩沖區的數據大小
begin
{從 Wave 文件中讀取格式與波形數據}
if not GetWaveFmt(wavPath, wavFormat) then Exit;
wavData := TMemoryStream.Create;
if not GetWaveData(wavPath, wavData) then begin wavData.Free; Exit; end;
{建立設備對象, 並設置寫作優先級}
DirectSoundCreate8(nil, myDSound, nil);
myDSound.SetCoOperativeLevel(Self.Handle, DSSCL_NORMAL);
{填充建立緩沖區需要的結構}
ZeroMemory(@bufDesc, SizeOf(TDSBufferDesc));
bufDesc.dwSize := SizeOf(TDSBufferDesc);
bufDesc.dwFlags := DSBCAPS_STATIC; //指定使用靜態緩沖區
bufDesc.dwBufferBytes := wavData.Size; //數據大小
bufDesc.lpwfxFormat := @wavFormat; //數據格式
// bufDesc.guid3DAlgorithm := DS3DALG_DEFAULT; //這個暫不需要
{建立緩沖區}
myDSound.CreateSoundBuffer(bufDesc, buf, nil);
{鎖定緩沖區內存以獲取寫入地址和寫入大小}
buf.Lock(0, 0, @p1, @n1, nil, nil, DSBLOCK_ENTIREBUFFER);
{寫入}
wavData.Position := 0;
CopyMemory(p1, wavData.Memory, n1);
wavData.Free;
{解鎖}
buf.Unlock(p1, n1, nil, 0);
{播放}
buf.Play(0, 0, 0);
Button1.Enabled := False;
Button2.Enabled := True;
Button3.Enabled := True;
end;
{循環播放}
procedure TForm1.Button2Click(Sender: TObject);
begin
buf.Play(0, 0, DSBPLAY_LOOPING);
end;
{暫停播放}
procedure TForm1.Button3Click(Sender: TObject);
begin
buf.Stop;
end;
//釋放接口, 不然會有內存洩露(因為此緩沖區的生命周期可能會長於應用程序); 且釋放順序也很重要
procedure TForm1.FormDestroy(Sender: TObject);
begin
buf := nil;
myDSound := nil;
end;
end.