程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> 在 Delphi 下使用 DirectSound (3): 播放第一個 Wave 文件

在 Delphi 下使用 DirectSound (3): 播放第一個 Wave 文件

編輯:Delphi

建立 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. 




  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved