在 Delphi 開發多媒體播放器時,若需實現「A-B 區段循環」這類進階功能,單靠基礎播放控制已無法滿足。本文將深入解析如何利用 Bass.dll 的同步回調機制(BASS_ChannelSetSync),精準控制播放流在指定位置跳轉,從而實現自定義區段的無縫循環播放。
Bass.dll 提供的同步回調允許開發者在音頻流到達特定位置(以字節為單位)時觸發自定義函數。我們利用此特性:
MySyncLoop 被觸發。procedure TForm1.FormCreate(Sender: TObject);
begin
// 初始化 Bass 庫,檢查版本相容性
if HiWord(BASS_GetVersion) <> BASSVERSION then
MessageBox(0, 'Bass.dll 版本不符!', nil, MB_ICONERROR);
if not BASS_Init(-1, 44100, 0, 0, nil) then
ShowMessage('初始化失敗');
// ... 介面元件初始化 ...
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Mp3Path: AnsiString;
hs2: HSTREAM;
time: Double;
i: Cardinal;
begin
BASS_StreamFree(hs);
// 開啟對話框選擇檔案
OpenDialog1.Filter := 'Mp3 文件(*.mp3)|*.mp3|Wav 文件(*.wav)|*wav';
if OpenDialog1.Execute then
Mp3Path := AnsiString(OpenDialog1.FileName);
// 建立播放流
hs := BASS_StreamCreateFile(False, PAnsiChar(Mp3Path), 0, 0, 0);
if hs < BASS_ERROR_ENDED then
Caption := '打開失敗'
else begin
Caption := string(Mp3Path);
// 建立解碼流以提取波形峰值
hs2 := BASS_StreamCreateFile(False, PAnsiChar(Mp3Path), 0, 0, BASS_STREAM_DECODE);
time := BASS_ChannelBytes2Seconds(hs2, BASS_ChannelGetLength(hs, BASS_POS_BYTE));
SetLength(Data, Trunc(time * 50 + 1));
for i := 0 to Length(Data) - 1 do
Data[i] := BASS_ChannelGetLevel(hs2);
BASS_StreamFree(hs2);
Mp3Len := BASS_ChannelGetLength(hs, BASS_POS_BYTE); // 記錄總字節長度
Draw; // 繪製波形
end;
end;
procedure TForm1.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
PosLoop: Cardinal;
begin
case Button of
mbLeft: begin // 左鍵:設定起始點(黃線)
Shape2.Left := X;
Shape2.Visible := True;
end;
mbRight: begin // 右鍵:設定結束點(紅線)並建立回調
Shape3.Left := X;
Shape3.Visible := True;
// 移除舊的同步回調
BASS_ChannelRemoveSync(hs, hSyncLoop);
// 計算結束點對應的字節位置
PosLoop := Trunc(Shape3.Left / PaintBox1.Width * Mp3Len);
// 註冊 BASS_SYNC_POS 同步回調
hSyncLoop := BASS_ChannelSetSync(hs, BASS_SYNC_POS, PosLoop, @MySyncLoop, nil);
end;
end;
end;
procedure MySyncLoop(handle: HSYNC; channel, data, user: DWORD); stdcall;
var
PosStart: Cardinal;
begin
// 若未設定起始點,則從 0 開始
if not Form1.Shape2.Visible then
PosStart := 0
else
PosStart := Trunc(Form1.Shape2.Left / Form1.PaintBox1.Width * Mp3Len);
// 設定播放位置並繼續播放
BASS_ChannelSetPosition(hs, PosStart, BASS_POS_BYTE);
BASS_ChannelPlay(hs, False);
end;
下圖展示了程式執行時的介面:波形圖上方可拖曳黃、紅兩線標記循環區段,播放指針(白線)在區段內往返移動。

unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, ComCtrls, Bass;
type
TForm1 = class(TForm)
OpenDialog1: TOpenDialog;
Timer1: TTimer;
PaintBox1: TPaintBox;
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
Button5: TButton;
Shape1: TShape;
Shape2: TShape;
Shape3: TShape;
Label1: TLabel;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure PaintBox1Paint(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
private
procedure Draw;
end;
procedure MySyncLoop(handle: HSYNC; channel, data, user: DWORD); stdcall;
var
Form1: TForm1;
implementation
{$R *.dfm}
var
hs: HSTREAM;
Data: array of Cardinal;
bit: TBitmap;
hSyncLoop: Cardinal;
Mp3Len: Cardinal;
{ ... 此處省略前文已解析的各事件處理代碼,完整代碼請參考原始附件 ... }
end.
透過 BASS_ChannelSetSync 註冊位置同步回調,配合 BASS_ChannelSetPosition 即時修改播放指針,即可輕鬆實現音樂的任意區段循環播放。此技術同樣適用於有聲書重聽、語言學習跟讀等場景,極大豐富了播放器的互動體驗。