Const
noError = 0;
ReadError = 1;
HeaderError = 2;
DataError = 3;
FileCorrupt = 4;
IncorectFileFormat = 5;
type
TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
OpenDialog1: TOpenDialog;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TWaveHeaderChank = record //Заголвок (чанк) формата
wFormatTag : Smallint;
wChannels : WORD;
wSamplesPerSec : Cardinal;
wAvgBytesPerSec: Cardinal;
wBlockAlign : WORD;
wBitsPerSample : WORD;
wcbSize : WORD;
end;
TWaveResult = record //Минимальная структура
ERROR : WORD; //Используеися для возвращения результата
wAvgBytesPerSec: Cardinal; //разбора Wav файла
wBitsPerSample : WORD;
wChannels : WORD;
Data : TMemoryStream;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses math;
Function ReadWave(FileName : AnsiString) : TWaveResult;
var
f : TFileStream;
wFileSize : Cardinal;
wChankSize : Cardinal;
ID : array[0..3] of Char;
Header : TWaveHeaderChank;
RealFileSize : Cardinal;
Begin
FillChar(Result, SizeOf(Result), 0);
Try
f := TFileStream.Create(FileName, fmOpenRead);
f.Seek(0, soFromBeginning);
f.ReadBuffer(ID[0], 4); //читаем тип файла
if String(ID) <> 'RIFF' //Определяем тип файла
then
Begin
Result.ERROR := IncorectFileFormat;
f.Free;
exit;
end;
//////////////////////////
Form1.memo1.Lines.Add(String(ID));
f.ReadBuffer(wFileSize, 4); //читаем размер файла
//////////////////////////
Form1.memo1.Lines.Add('FileSize ' + intToStr(wFileSize));
if f.size <> (wFileSize + 8) //Определяем соответствие указанного размера
then //и размера файла(на случай если был поврежден при
Begin //передаче)
Result.ERROR := FileCorrupt;
f.Free;
exit;
end;
f.ReadBuffer(ID[0], 4);
//////////////////////////
Form1.memo1.Lines.Add(String(ID));
if String(ID) <> 'WAVE' //Определяем формат файла
then
Begin
Result.ERROR := IncorectFileFormat;
f.Free;
exit;
end;
wChankSize := 0;
repeat //Ищем чанк формата
f.Seek(wChankSize, soFromCurrent);//Пропускаем все дополнительные чанки
f.ReadBuffer(ID[0], 4); //Читаем идентификатор чанка
//////////////////////////
Form1.memo1.Lines.Add(String(ID));
f.ReadBuffer(wChankSize, 4); //Читаем размер чанка
if wChankSize > High(integer) //Проверяем размер загловка на разумность
then //размера вероятно можно установить чило и
Begin //меньше например 100
Result.ERROR := DataError;
f.Free;
exit;
end;
//////////////////////////
Form1.memo1.Lines.Add('chankSize ' + intToStr(wChankSize));
until (String(ID)='fmt ') or (String(ID)='data');
if String(ID)='data' //Проверяем найден ли заголовок формата
then
Begin
Result.ERROR := HeaderError;
f.Free;
exit;
end;
f.ReadBuffer(Header, Min(wChankSize, SizeOf(TWaveHeaderChank))); //Читаем заголовок
////////////////////////// //меньше нашей структуры
Form1.memo1.Lines.Add('wFormatTag ' + intToStr(Header.wFormatTag));
Form1.memo1.Lines.Add('wChannels ' + intToStr(Header.wChannels));
Form1.memo1.Lines.Add('wSamplesPerSec ' + intToStr(Header.wSamplesPerSec));
Form1.memo1.Lines.Add('wBlockAlign ' + intToStr(Header.wBlockAlign));
Form1.memo1.Lines.Add('wBitsPerSample ' + intToStr(Header.wBitsPerSample));
if wChankSize > SizeOf(TWaveHeaderChank) //Смещаем указатель чтения в конец блока
then //нужно только для больших заголовков
f.Seek(wChankSize - SizeOf(TWaveHeaderChank), soFromCurrent);
if wChankSize >= SizeOf(TWaveHeaderChank) //определяем расширенный ли заголовок или нет
then
//////////////////////////
Form1.memo1.Lines.Add('wcbSize ' + intToStr(Header.wcbSize));
wChankSize := 0;
repeat //Ищем чанк данных
f.Seek(wChankSize, soFromCurrent);//Пропускаем все дополнительные чанки
f.ReadBuffer(ID[0], 4); //Читаем идентификатор чанка
//////////////////////////
Form1.memo1.Lines.Add(String(ID));
f.ReadBuffer(wChankSize, 4); //Читаем размер чанка
//////////////////////////
Form1.memo1.Lines.Add('chankSize ' + intToStr(wChankSize));
until String(ID)='data';
Result.ERROR := noError; //Заполняем структуру результата
Result.wAvgBytesPerSec := Header.wAvgBytesPerSec;
Result.wBitsPerSample := Header.wBitsPerSample;
Result.wChannels := Header.wChannels;
//Копируем данные в память
Result.Data := TMemoryStream.Create;
Result.Data.Seek(0, soFromBeginning);
Result.Data.Size := wChankSize; //Выделяем память под данные
f.ReadBuffer(Result.Data.Memory^, wChankSize); //Копируем данные в память
Except
Result.ERROR := ReadError;
end;
f.Free;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
r : TWaveResult;
begin
if opendialog1.Execute
then
r := ReadWave(opendialog1.FileName);
end;
end.
-------------------------------------------
Строки Form1.memo1.Lines.Add пошагово выводят информацию в memo и могут быть удалены.
Также можно (но очень не желательно) удалить все проверки на ошибки чтения и т.д.
Данный код читает PCM не сжатый формат и возвращает дастаточный минимум информации, однако этот метод позволяет правельно прочитать и любой сжатый формат(однако возвращаемой информации для интерпретации таких данных не достаточно)
Данные возвращаются ввиде потока TMemoryStream откуда их можно извлекать в соответствии с их форматом.
Перед чтением данных из потока необходимо убедится, что отсутствовали ошибки при чтении файла.
Пример записи wav
Наконец дошли руки написал процедуру записи wav файлов.
Отмечаю следующие особенности: Запись TWaveHeaderChank следует обявлять с директивой packed, в связи с тем, что формат wav подразумевает выравнивание на границу 2 байт, а delphi по умалчанию выравнивает на границу 4 байт. Т.о. размер структуры TWaveHeaderChank без диретивы packed будет занимать 20 байт(структура в примере программ) вместо положенных 18.
Следующая программа записывает одноканальный wav файл длительностью 10 сек, 16 бит выборка. Звук будет напоминать свисты коротковолнового радиоприемника.
TWaveHeaderChank = packed record //Заголвок (чанк) формата
wFormatTag : Smallint;
wChannels : WORD;
wSamplesPerSec : Cardinal;
wAvgBytesPerSec: Cardinal;
wBlockAlign : WORD;
wBitsPerSample : WORD;
wcbSize : WORD;
end;
-------------------------------------------------------------------
Const
noError = 0;
ReadError = 1;
HeaderError = 2;
DataError = 3;
FileCorrupt = 4;
IncorectFileFormat = 5;
HeaderWriteError = 6;
StreamError = 7;
-------------------------------------------------------------------
Function WriteWave(FileName : AnsiString; data : TWaveResult) : WORD;
var
f : TFileStream;
wFileSize : Cardinal;
wChankSize : Cardinal;
ID : array[0..3] of Char;
Header : TWaveHeaderChank;
Begin
Result := noError;
Try
f := TFileStream.Create(FileName, fmCreate);
f.Seek(0, soFromBeginning);
Header.wFormatTag := 1;
Header.wChannels := data.wChannels;
Header.wSamplesPerSec := data.wSamplesPerSec;
Header.wBlockAlign := data.wChannels * (data.wBitsPerSample div 8);
Header.wAvgBytesPerSec:= data.wSamplesPerSec * Header.wBlockAlign;
Header.wBitsPerSample := data.wBitsPerSample;
Header.wcbSize := 0; //нет дополнительного блока
ID := 'RIFF';
f.WriteBuffer(ID, 4);
wFileSize := 0; //пока не известен
f.WriteBuffer(wFileSize, 4);
ID := 'WAVE';
f.WriteBuffer(ID, 4); //Запись идентификатора формата
ID := 'fmt ';
f.WriteBuffer(ID, 4); //Запись идентификатора чанка формата
wChankSize := SizeOf(Header);
f.WriteBuffer(wChankSize, 4); //Запись размера чанка
f.WriteBuffer(Header, SizeOf(Header)); //Запись чанка формата
except
Result := HeaderWriteError;
end;
Try
ID := 'data';
f.WriteBuffer(ID, 4); //Запись чанка данных
wChankSize := data.Data.Size; //Запись размера чанка
f.WriteBuffer(wChankSize, 4); //Запись данных
data.Data.Seek(0, soFromBeginning);
f.CopyFrom(data.Data, data.Data.Size);
except
Result := StreamError;
end;
f.Seek(SizeOf(ID), soFromBeginning); //Поиск записи размера файла
wFileSize := f.Size - SizeOf(ID) - SizeOf(wFileSize);
f.Write(wFileSize, 4); //Запись размера файла - заголовок
f.Free;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
r : TWaveResult;
i : Integer;
d : SmallInt;
begin
r.ERROR := noError;
r.wSamplesPerSec := 44100;
r.wBitsPerSample := 16;
r.wChannels := 1;
r.Data := TMemoryStream.Create;
r.Data.Seek(0, soFromBeginning);
For i := 1 to 10 * r.wSamplesPerSec
do
Begin
d := Round(High(SmallInt) * Sin(2 * Pi * (5 * i / r.wSamplesPerSec) * (500 * i / r.wSamplesPerSec)));
r.Data.WriteBuffer(d, 2);
end;
WriteWave('Sample.wav', r);
r.Data.Free;
end;