Модераторы: Snowy, Alexeis, MetalFan

Поиск:

Закрытая темаСоздание новой темы Создание опроса
> Формат WAVE файлов, статья 
:(
    Опции темы
Alexeis
Дата 31.3.2006, 10:11 (ссылка) |    (голосов:3) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 55
Всего: 459



В связи с недостаточной информацией на русском языке, представляю вам свой перевод фрагмента одной из наиболее удачных статей(на мой взгляд). В эту статью не вошло описание дополнительных чанков, а также оскорбительные высказывания в сторону авторов WAVE формата. Все структуры переписаны в формат record Object pascal.
HTML вариант
здесь
----------------------------------------------------------------------------------------

Формат WAVE файлов

WAVE – это формат хранения оцифрованных аудио данных. Данный формат поддерживает данные различной битности, с различной частотой выборки и числом каналов. Данный формат весьма популярен на платформах стандарта IBM PC (и совместимых с ним).
Этот формат подразумевает хранение аудио данных в особых блоках, которые называют чанками (chunks).
Форматы данных
ID = array[1..4] of Char; - идентификатор чанка.
Организация данных
Все данные хранятся в виде 8-ми битовых чисел, расположенных таким образом, что младшая часть слова данных записывается первой.
Byte : | байт 0 |.
Word : | байт 0 | байт 1 |
Integer : | байт 0 | байт 1 | байт 2 | | байт 3 |
Структура файла
Wav файл представляет собой набор из многочисленных чанков разного типа. Важнейшим чанком является чанк формата (Format chunk – «fmt»), который содержит важные параметры звуковых данных, например частоту выборки. Другим важным чанком является чанк данных (Data chunk), который и определяет форму аудио сигнала. Присутствие других дополнительных чанков (optional chunks) не обязательно.
Все приложения использующие wav файлы должны уметь читать обязательные чанки, и уметь выборочно игнорировать дополнительные чанки. Программы, предназначенные для копирования wav файлов должны копировать все чанки, даже те которые он не может интерпретировать. Порядок следования чанков разного типа может быть произвольным. Исключение составляет чанк формата, который должен предшествовать чанку данных. Некоторые грубо написанные программы всегда интерпретируют первый чанк (после RIFF заголовка) как чанк формата, хотя в спецификации формата подразумевается, что первый чанк может быть не только чанком формата, но и дополнительным чанком.
Рассмотрим схему простейшего wav файла

Цитата
________________________
| RIFF WAVE Chunk        |
|  groupID  = 'RIFF'    |
|  FileSize : Cardinal  |
|  riffType = 'WAVE'    |
|  ___________________  |
|  | Format Chunk    | |
|  | ckID = 'fmt '    | |
|  |__________________| |
|  ___________________  |
|  | Sound Data Chunk | |
|  | ckID = 'data'    | |
|  |__________________| |
|_______________________|

Формат wav файла создавался и модифицировался без всякой координации множеством авторов (каждый из которых вносил свои специфические чанки). В результате он получился отчасти противоречивым, например 8-ми битные данные являются беззнаковыми, а 16-битные – числа со знаком.
Элементы выборок и фреймы
Важнейшими понятиями при интерпретация wave файлов, являются понятия элементы выборок и фреймы. Элемент выборки представляет собой мгновенное значение амплитуды сигнала. Данные длинной более 8-ми бит хранятся в виде блока размером от 9 до 32 бит состоящего из двух смежных слов (определяется значением поля wBitsPerSample, в чанке формата, для несжатого подстандарта PCM). Например для 16 битного формата (2х8 бит) диапазон значений амплитуд от -32768 ($8000) до 32767 ($7FFF). Для формата 8бит и меньше (1 беззнаковый байт) диапазон значений амплитуд от 0 до 255.
В связи с архитектурой современных процессоров было решено, что при сохранении данных размер выборки будет округлятся до величины кратной 8-битам. Для АЦП с разрядностью не более 8-бит данные сохраняются в 8-ми битном формате. Для АЦП с разрядностью от 9 до 16 бит элемент выборки будет 16 битным числом со знаком.
Для АЦП с разрядностью от 17 до 24 бит выборка будет 3-х байтной. Для АЦП с разрядностью от 25 до32 бит выборка будет двойным знаковым 32 битным словом. И т.д.
Кроме того, биты данных (не кратных 8) должны быть выровнены по левому краю. Например 12 битные данные (записываются 16-ти битным словом) выравниваются следующим образом – данные выборки записываются с 4-го по 15 бит включительно, биты от 0 до 3 устанавливаются в нуль. Т.о. число 101000010111 записывается как

Цитата

___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___ ___
|      |    |      |      |      |      |      |    |      |      |      |      |      |      |      |    |
| 1      0    1    0    0    0    0    1    0    1    1    1      0  0      0    0  |
|___|__|___|___|___|___|___|__|___|___|___|___|___|___|___|___|
<--------------------------------------------->                          <------------->
    12 битная выборка выровненная по левому краю  крайне правые 
                                                                    Байты          установленные                                                                          в нуль.
Но в файл данные будут записаны так
___ ___ ___ ___ ___ ___ ___ ___    ___ ___ ___ ___ ___ ___ ___ __
|  |      |      |      |  |        |    |      |  |    |      |      |      |      |      |    |  |
| 0  1      1      1    0    0    0      0 |  | 1      0    1      0    0      0    0  1  |
|__|___|___|___|__|___|___|___|  |___|___|___|___|___|___|__|___|
<------------->          <------------->      <----------------------------->
  биты 0 до 3    4 нулевых                биты от 4 до 11


При многоканальной записи элементы выборок записываются поочередно. Так для стерео записи в начале пишется одна выборка левого канала, затем правого, затем следующая выборка левого и т.д. Такое расположение выборок упрощает пересылку данных в ЦАП. Блок из нескольких выборок воспроизводимых одновременно называется фреймом. В нашем примере фрейм это блок, состоящий из двух выборок (левого и правого каналов)

Цитата

  фрейм 0      фрейм 1            фрейм N
_____ _____ _____ _____          _____ _____
| ch1    | ch2    | ch1  | ch2  | . . . | ch1    | ch2  |
|_____|_____|_____|_____|      |_____|_____|
_____
|          | = одна выборка
|_____|


Для одноканальной записи, фрейм состоит просто из одной выборки. Для многоканальной записи правила составления фрейма следующие:

Цитата

channels      1        2
            _________ __
            | left    | right  |
stereo  |          |          |
            |_____|______|

                    1        2          3
                _________ _________
                | left    | right  | center  |
3 channel |        |            |            |
                |_____|__ __________|

                1        2        3        4
            _________ _________ ______
            | front  | front  | rear    | rear    |
quad    | left    | right    | left    | right  |
            |_____|______|______|______|

                    1        2          3            4
                _________ _________ ________
                | left    | center  | right  | surround|
4 channel |        |            |            |                |
                |_____|______|______|________|

                    1          2        3          4          5          6
                _______ _________ _________ _________ ______
                | left      | left    | center  | right    | right  |surround |
6 channel | center  |          |            | center  |          |                |
                |_______|_____|______|_______|_____|________|


Выборки в пределах одного фрейма записываются непрерывно один за другим без неиспользованных байтов между ними. Следует отметить, что все вышеупомянутое относится к формату записи без сжатия (PCM).
Добавлено @ 10:12

Чанк формат (Format Chunk)
Чанк Формат («fmt») описывает фундаментальные параметры данных, частоту выборки, разрядность данных и число каналов цифровой звукозаписи.
Код

ID = array[1..4] of Char;
ChankFormat = Record
    chunkID          : ID;
  chunkSize        : Integer;

  wFormatTag       : SmallInt;
  wChannels        : Word;
  dwSamplesPerSec  : Cardinal;
  dwAvgBytesPerSec : Cardinal;
  wBlockAlign      : Word;
  wBitsPerSample   : Word;
end;

ChankFormat – может иметь и дополнительные поля, поэтому нельзя игнорировать поле
chunkSize – определяющее размер записи минус 8 бит (поля chunkID и chunkSize),.
Значение chunkedID всегда равно “fmt “. Звуковые данные могут хранится без сжатия, тогда выборки хранятся как было описано в разделе Элементы выборок и фреймы. В случае формата со сжатием размеры выборок могут отличаться от разрядности звуковых данных. На наличие сжатия указывает поле wFormatTag. В этом случае его значение wFormatTag отличается от единицы. Кроме того в ChankFormat появятся несколько полей идентифицирующие метод сжатия.
- Первое дополнительное поле (типа Word) определяет длину добавочного блока(после этого поля).
-Далее следует специальный чанк (Fact chunk) содержащий переменную типа Cardinal, определяющую размер всех аудио данных (в несжатом ввиде). Размер задается в выборках. Существует большое количество методов сжатия. Детальное описание каждого из них можно посмотреть на сайте Microsoft.
Если никакое сжатие не используется (то есть, wFormatTag = 1), то после ChankFormat нет дополнительных служебных полей.
Поле wChannels содержит число звуковых каналов. Значение 1 указывает на моно звучание, значение 2 стерео ,4 четыре канала звука и т.д. Напомню, что выборки многоканальных записей чередуются, а элементарный блок из таких данных образуют Фрейм. Реальные данные хранятся в чанке данных (Data Chunk), который будет описан позже.
-Поле dwSamplesPerSec хранит частоту выборок в секунду (то есть, Герц). Существуют три стандарта MPC - 11025, 22050, и 44100 ГЦ, хотя допускается использование других частот.
- Поле dwAvgBytesPerSec указывает, сколько байтов проигрываются каждую секунду. dwAvgBytesPerSec может использоваться приложением, чтобы оценить, буфер какого размера необходим, для того чтобы должным образом воспроизводить звук без проблем связанных с прерыванием воспроизведения звука. Его значение должно быть равно результату произведения dwSamplesPerSec * wBlockAlign округленного в большую сторону.
- Поле wBlockAlign должно быть равно значению выражения
wChannels * (wBitsPerSample div 8) округленному в большую сторону. По существу, wBlockAlign - размер фреймов в байтов. (т.о. фрейм для 16-битовой выборки равен 2 байтом, фрейм для 16-битовой стерео выборки 4 байта. И т.д).
В каждом звуковом файле может быть не более одного чанка формата.

Чанк данных (Data Chunk)
Чанк данных содержит фреймы звуковых данных (все каналы звуковых данных)
Рассмотрим структуру
Код

DataChunk = Record
  chunkID             : ID;
  chunkSize           : Integer;

  waveformData        : array of byte; 
end;

-Значение ID всегда равно ‘data’.
- chunkSize - число байтов в чанке минус 8 байт (поля ID и chunkSize) кроме того, не учитываются байты выравнивания структуры. Помните, что описание данных хранится в чанке формата. Все вышеперечисленное относится только к несжатому формату.
-Массив waveformData содержит собственно выборки, порядок их следования и выравнивание описано в разделе выборки и фреймы. Число фреймов можно вычислить, разделив значение chunkSize на wBlockAlign чанка формата.
Чанк данных является необходимой частью любого звукового файла. В одном файле может присутствовать только один чанк данных.

В заключение хочется напомнить, что существуют множество других чанков таких как например Cue Chunk(вроде описания данного аудио файла), Playlist chunk, Associated Data List, Label Chunk, Note Chunk, Labeled Text Chunk, Sampler Chunk, Instrument Chunk Format. Для воспроизведения аудио их надо просто игнорировать.

Это сообщение отредактировал(а) alexeis1 - 12.4.2006, 08:59


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
Alexeis
Дата 3.4.2006, 09:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 55
Всего: 459



В данной статье автор упустил важный на мой взгляд момент связанный с разбором и поиском чанков в Wav файле. А дело вот в чем. Все чанки имеют следующую общую структуру
Код

AnyChunk = Record    
  chunkID             : ID;    
  chunkSize           : Integer;    
  //Специфические для чанка данные
end;


Теперь, зная это, можно однозначно читать файл, примерно так:
1) Читаем заголовок wav (единственный чанк не соответствующий указанной структуре, но он всегда первый, поэтому проблем не будет)
2) Читаем 4 байта - идентификатор чанка chunkID и
4 - байта - длина остаточной части чанка (до конца чанка) chunkSize
3) Сравниваем идентификатор чанка chunkID с тем идентификатором того чанка который ищем. Если совпало то читаем чило байт chunkSize, иначе пропускаем это же число байт chunkSize и переходим к шагу №2.

Таким образом программа будет всегда устойчиво работать, даже
если появятся новые типы чанков или в конце любого чанка добавятся новые поля.
------------------------------------------
далее попробую написать код реализующий этот алгоритм....


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
Alexeis
Дата 5.4.2006, 09:28 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 55
Всего: 459



 В перевод внесены следующие исправления
1) После заголовка 'RIFF' - следует размер оставшегося файла
2)тип данных поля wFormatTag   заголовка формата  правельно SmallInt (был ShortInt). В некоторых источниках описан как word, однако в нашем случае не критично (если значение не превышает приблизительно 32000)

А теперь попробуем правильно прочитать прочитать wav файл как этого требует формат.

Код

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;      //Используется для возвращения результата
    wSamplesPerSec: 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.wSamplesPerSec := Header.wSamplesPerSec;
    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 откуда их можно извлекать в соответствии с их форматом.
Перед чтением данных из потока необходимо убедится, что отсутствовали ошибки при чтении файла.

тему не закрываю далее будет запись хотя по правде говоря с записью проблем быть не должно(ведь можно использовать минимум чанков)  

Это сообщение отредактировал(а) Alexeis - 20.10.2009, 12:31


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
Black_Joker
Дата 5.4.2006, 11:55 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 212
Регистрация: 9.11.2002
Где: Таганрог

Репутация: нет
Всего: нет



Как получить значения амплитуд и частот (тон) звука?

Это сообщение отредактировал(а) Black_Joker - 5.4.2006, 11:58
PM   Вверх
Alexeis
Дата 5.4.2006, 12:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 55
Всего: 459



Black_Joker, В результате работы функции
Function ReadWave(FileName : AnsiString) : TWaveResult;возвращается результат в виде записи TWaveResult одно из полей которой Data - т.е. звуковые данные(амплитуда выборки) в формате потока TMemoryStream
Чтение данных из потока производится в соответствии с их форматом (описанно в статье), при помощи метода readBuffer({переменная куда запишутся данные}, {Размер блока})
Размер блока - завит от того сколько данных читаем, если одну выборку, то размер выборки в байтах, если фрейм целиком, то размер фрейма, если данные например за секунду времени - то размер фрейма * частоту выборки и т.д.
Добавлено @ 12:32
Значение частот в wav файле не хранится его можно получить выполнив дискретное преобразование фурье над блоком данных некоторой длины. Алгоритмы дискретного преобразования фурье очень хорошо описаны во многих статьях и учебниках, достаточно запустить поиск, а затем разгребать smile . Есть пример и на сайте delphiWorld.narod.ru(но не очень удачный - слишком краткое описание)


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
Black_Joker
Дата 5.4.2006, 15:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 212
Регистрация: 9.11.2002
Где: Таганрог

Репутация: нет
Всего: нет



Я имею в виду не кол-во точек в сеукунду (частоту дискретизации) а высоту звака (ультрозвук - инфрозвук).
PM   Вверх
londinium
Дата 6.4.2006, 09:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 39
Регистрация: 21.1.2006

Репутация: нет
Всего: нет



Здравствуйте!

Насколько я помню ультразвук и инфразвук как раз и определяются частотой сигнала. Если не ошибаюсь, частота ультразвука >20 кГц, а инфразвука < 20 Гц.

Londinium
PM MAIL   Вверх
Alexeis
Дата 6.4.2006, 09:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 55
Всего: 459



Black_Joker, В любом случае wav файл хранит только амплитуды выборок и ни в коем случае ни тон, ни частоты, ни спектр. Посмотрите РИСУНОК и все станет ясно


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
Alexeis
Дата 12.4.2006, 09:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 55
Всего: 459



Наконец дошли руки написал процедуру записи wav файлов.
Отмечаю следующие особенности: Запись TWaveHeaderChank следует обявлять с директивой packed, в связи с тем, что формат wav подразумевает выравнивание на границу 2 байт, а delphi по умалчанию выравнивает на границу 4 байт. Т.о. размер структуры TWaveHeaderChank без диретивы packed будет занимать 20 байт(структура в примере программ) вместо положенных 18.
Следующая программа записывает одноканальный wav файл длительностью 10 сек, 16 бит выборка. Звук будет напоминать свисты коротковолнового радиоприемника.
Код

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

Const
  noError            = 0;
  ReadError          = 1;
  HeaderError        = 2;
  DataError          = 3;
  FileCorrupt        = 4;
  IncorectFileFormat = 5;
  HeaderWriteError   = 6;
  StreamError        = 7;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    OpenDialog1: TOpenDialog;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  TWaveHeaderChank = packed record     //Заголвок (чанк) формата
    wFormatTag     : Smallint;
    wChannels      : WORD;
    wSamplesPerSec : Cardinal;
    wAvgBytesPerSec: Cardinal;
    wBlockAlign    : WORD;
    wBitsPerSample : WORD;
    wcbSize        : WORD;
  end;

  TWaveResult = record          //Минимальная структура
    ERROR          : WORD;      //Используеися для возвращения результата
    wSamplesPerSec : 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.wSamplesPerSec := Header.wSamplesPerSec;
    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;

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.Button1Click(Sender: TObject);
var
  r : TWaveResult;
begin
  if opendialog1.Execute
  then
    r := ReadWave(opendialog1.FileName);
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;

end.




--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
Alexeis
Дата 12.4.2006, 09:33 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 55
Всего: 459



HTML вариант полной версии попрежнему доступен
ЗДЕСЬ


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
marykone
Дата 21.5.2006, 15:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2722
Регистрация: 2.5.2006
Где: Краснодар

Репутация: нет
Всего: 67



Цитата

А теперь попробуем правельно прочитать прочитать wav файл как этого требует формат.

alexeis1 по поводу функции readwave
Цитата

Код

 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);  //Копируем данные в память


в этой части кода пропущена строка
Код

result. wSamplesPerSec:=header. wSamplesPerSec;
 


--------------------
получил ответ, пометь вопрос как решенный (правый верхний угол вашей темы).

PM   Вверх
Alexeis
Дата 12.6.2006, 09:39 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 55
Всего: 459



marykone, спасибо за замечание! На самом деле эта ошибка в последней части уже была исправлена. Конечно же wSamplesPerSec куда полезней чем wAvgBytesPerSec. Код первой части поправил.  smile  


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
GOST800
Дата 28.7.2008, 21:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 22
Регистрация: 25.7.2008

Репутация: нет
Всего: нет



а можно в функции чтения(ReadWave) записывать данные в Data минуя заголовок?

Это сообщение отредактировал(а) GOST800 - 28.7.2008, 21:47
PM MAIL   Вверх
Alexeis
Дата 28.7.2008, 21:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Амеба
Group Icon


Профиль
Группа: Админ
Сообщений: 11743
Регистрация: 12.10.2005
Где: Зеленоград

Репутация: 55
Всего: 459



Можно структуру типа TWaveResult заполнить самому и передать на сохранение, для этого функция ReadWave вообще не нужна.


--------------------
Vit вечная память.

Обсуждение действий администрации форума производятся только в этом форуме

гениальность идеи состоит в том, что ее невозможно придумать
PM ICQ Skype   Вверх
GOST800
Дата 29.7.2008, 10:37 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 22
Регистрация: 25.7.2008

Репутация: нет
Всего: нет



Не, думаю, лучше наверное через ReadWave заполнить TWaveResult, но как откинуть заголовок? 

Код

Function WriteWave(Stream : TMemoryStream; data : TWaveResult) : WORD;
var
  wFileSize     : Cardinal;
  wChankSize    : Cardinal;
  ID            : array[0..3] of Char;
  Header        : TWaveHeaderChank;

Begin
   Try
    ID := 'data';
    Stream.WriteBuffer(ID, 4);                  //Запись чанка данных
    wChankSize := data.Data.Size;          //Запись размера чанка
    Stream.WriteBuffer(wChankSize, 4);          //Запись данных
    data.Data.Seek(0, soFromBeginning);
    Stream.CopyFrom(data.Data, data.Data.Size);
   except
    Result := StreamError;
   end;
   f.Seek(SizeOf(ID), soFromBeginning);     //Поиск записи размера файла
end;


так?

Это сообщение отредактировал(а) GOST800 - 29.7.2008, 10:46
PM MAIL   Вверх
Закрытая темаСоздание новой темы Создание опроса
Правила форума "Delphi: Звук, графика и видео"
Girder
Snowy
Alexeis

Запрещено:

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делится вскрытыми компонентами

  • Литературу по Дельфи обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи
  • По вопросам разработки игр стоит заглянуть сюда

FAQ раздела лежит здесь!


Если Вам помогли и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, Girder, Snowy.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: Звук, графика и видео | Следующая тема »


 




[ Время генерации скрипта: 0.1252 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.