一个PCM Wav文件类
unit PCMWaves;
interface
uses
Classes, SysUtils, MMSystem;
type
TPCMWaveHeader = packed record
rID : array[0..3] of Char; //'RIFF' 标识
rLen : Longint;
wID : array[0..3] of Char; //'WAVE' 标识
fID : array[0..3] of Char; //'fmt ' 标识
fLen : Longint; //固定值, 16
wFormatTag : Word; //固定值, 1
nChannels : Word; //Mono=1, Stereo=2
nSamplesPerSec : Longint; //采样频率, HZ
nAvgBytesPerSec : Longint;
nBlockAlign : Word;
nBitsPerSample : Word; //精度, e.g. 8 or 16
dId : array[0..3]of Char; //'data' 标识
dLen : Longint; //数据长度
end;
type
EInvalidPCMWaveFormat = class(Exception);
TPCMWave = class(TObject)
private
FStream : TMemoryStream;
FHeader : TPCMWaveHeader;
protected
function isValidFormat(aStream: TStream): Boolean; virtual;
function getPCMWaveHeader(aStream: TStream): TPCMWaveHeader; virtual;
function getData: Pointer; virtual;
public
constructor Create; virtual;
destructor Destroy; override;
procedure LoadFromFile(const aFileName: String); virtual;
procedure LoadFromStream(aStream: TStream); virtual;
procedure SaveToFile(const aFileName: String); virtual;
procedure SaveToStream(aStream: TStream); virtual;
procedure Append(const aFileName: String); overload; virtual;
procedure Append(aStream: TStream); overload; virtual;
procedure Clear;
procedure Play;
procedure Stop;
property Header: TPCMWaveHeader read FHeader;
property Data: Pointer read getData;
end;
implementation
uses
LogFiles;
constructor TPCMWave.Create;
begin
FillChar(FHeader, SizeOf(FHeader), 0);
with FHeader do
begin
rID := 'RIFF'; //'RIFF' 标识
rLen := 36; //dLen + 36
wID := 'WAVE'; //'WAVE' 标识
fID := 'fmt '; //'fmt ' 标识
fLen := 16; //固定值, 16
wFormatTag := 1; //固定值, 1
nChannels := 1; //Mono=1, Stereo=2
nSamplesPerSec := 16000; //采样频率, HZ
nAvgBytesPerSec := 32000; //nSamplesPerSec*nChannels*Trunc(nBitsPerSample div 8);
nBlockAlign := 2; //nChannels*(nBitsPerSample div 8);
nBitsPerSample := 16; //精度, e.g. 8 or 16
dId := 'data'; //'data' 标识
dLen := 0;
end;
FStream := TMemoryStream.Create;
Assert(FStream <> nil);
FStream.Write(FHeader, SizeOf(FHeader));
end;
destructor TPCMWave.Destroy;
begin
if Assigned(FStream) then FStream.Free;
inherited Destroy;
end;
function TPCMWave.isValidFormat(aStream: TStream): Boolean;
var
Header : TPCMWaveHeader;
begin
Header := getPCMWaveHeader(aStream);
with Header do
begin
Result := (rID = 'RIFF') and (wID = 'WAVE') and (wFormatTag = 1);
end;
end;
procedure TPCMWave.SaveToFile(const aFileName: String);
var
Stream: TStream;
begin
Stream := TFileStream.Create(aFileName, fmCreate);
Assert(Stream <> nil);
try
SaveToStream(Stream);
finally
Stream.Free;
end;
end;
procedure TPCMWave.LoadFromFile(const aFileName: String);
var
Stream: TStream;
begin
Stream := TFileStream.Create(aFileName, fmOpenRead or fmShareDenyWrite);
Assert(Stream <> nil);
try
LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
procedure TPCMWave.SaveToStream(aStream: TStream);
begin
aStream.Seek(0, soFromBeginning);
FStream.Seek(0, soFromBeginning);
aStream.CopyFrom(FStream, FStream.Size);
end;
procedure TPCMWave.LoadFromStream(aStream: TStream);
begin
if not isValidFormat(aStream) then
raise EInvalidPCMWaveFormat.Create('Invalid PCM Wave Format');
aStream.Seek(0, soFromBeginning);
FStream.Seek(0, soFromBeginning);
FStream.CopyFrom(aStream, aStream.Size);
FHeader := getPCMWaveHeader(FStream);
end;
function TPCMWave.getPCMWaveHeader(aStream: TStream): TPCMWaveHeader;
var
Header : TPCMWaveHeader;
begin
aStream.Seek(0, soFromBeginning);
aStream.Read(Header, SizeOf(Header));
Result := Header;
end;
function TPCMWave.getData: Pointer;
begin
Result := FStream.Memory;
end;
procedure TPCMWave.Append(const aFileName: String);
var
Stream: TStream;
begin
Stream := TFileStream.Create(aFileName, fmOpenRead or fmShareDenyWrite);
Assert(Stream <> nil);
try
Append(Stream);
finally
Stream.Free;
end;
end;
procedure TPCMWave.Append(aStream: TStream);
var
Header : TPCMWaveHeader;
isSame : Boolean;
begin
if not isValidFormat(aStream) then
raise EInvalidPCMWaveFormat.Create('Invalid PCM Wave Format');
//数据区非空, 追加
if FHeader.dLen > 0 then
begin
//检测是否是相同的数据格式
Header := getPCMWaveHeader(aStream);
isSame := (Header.nChannels = FHeader.nChannels) //
and (Header.nSamplesPerSec = FHeader.nSamplesPerSec) //
and (Header.nBitsPerSample = FHeader.nBitsPerSample); //
//数据格式相同
if isSame then
begin
//处理非标准格式, 按规范定义, rLen不包含RIFF标志和长度共8字节
//因此,aStream.Size = SizeOf(TPCMWaveHeader)+Header.dLen
// aStream.Size = Header.rLen + 8
//但在实际中发现存在rLen值为包含RIFF标志和长度8字节的文件
if Header.dLen > aStream.Size-SizeOf(TPCMWaveHeader) then
begin
Header.dLen := aStream.Size - SizeOf(TPCMWaveHeader);
end;
//计算数据区长度
with FHeader do
begin
rLen := rLen + Header.dLen;
dLen := dLen + Header.dLen;
end;
//更新文件头
FStream.Seek(0, soFromBeginning);
FStream.Write(FHeader, SizeOf(FHeader));
//追加数据
FStream.Seek(0, soFromEnd);
aStream.Seek(SizeOf(TPCMWaveHeader), soFromBeginning); //跳过文件头
FStream.CopyFrom(aStream, Header.dLen);
end;
end
else //数据区为空, 读入新的数据
begin
LoadFromStream(aStream);
end;
end;
procedure TPCMWave.Clear;
begin
FillChar(FHeader, SizeOf(FHeader), 0);
with FHeader do
begin
rID := 'RIFF'; //'RIFF' 标识
rLen := 36; //dLen + 36
wID := 'WAVE'; //'WAVE' 标识
fID := 'fmt '; //'fmt ' 标识
fLen := 16; //固定值, 16
wFormatTag := 1; //固定值, 1
nChannels := 1; //Mono=1, Stereo=2
nSamplesPerSec := 16000; //采样频率, HZ
nAvgBytesPerSec := 32000; //nSamplesPerSec*nChannels*Trunc(nBitsPerSample div 8);
nBlockAlign := 2; //nChannels*(nBitsPerSample div 8);
nBitsPerSample := 16; //精度, e.g. 8 or 16
dId := 'data'; //'data' 标识
dLen := 0;
end;
FStream.Clear;
FStream.Write(FHeader, SizeOf(FHeader));
end;
procedure TPCMWave.Play;
begin
//声卡已安装
if waveOutGetNumDevs > 0 then
begin
PlaySound(Data, hInstance, SND_MEMORY or SND_ASYNC);
end;
end;
procedure TPCMWave.Stop;
begin
//声卡已安装
if waveOutGetNumDevs > 0 then
begin
PlaySound(nil, hInstance, 0);
end;
end;
end.