STM32 Воспроизведение звука. Структура WAV-файлов

Возникла потребность в воспроизведении с помощью микроконтроллера WAV-файлов, расположенных на карте памяти. Учитывая, что STM32 имеют “на борту” ЦАП и ДМА, эта задача вполне выполнима. Помимо практической стороны она также полезна в целях самообразования – изучим работу ЦАПа и некоторые другие возможности контроллера.

Для начала необходимо разобраться в том, что из себя представляет WAV-файл. Он содержит оцифрованный звук: с помощью АЦП через строго заданные интервалы времени производится измерение уровня сигнала, после чего результат измерения сохраняется. Затем с помощью ЦАПа оцифрованные данные можно превратить в звук.

Прежде чем записать оцифрованные данные в файл их необходимо “упаковать в обертку”, на которой указать некоторую информацию об этих данных: их размер, сколько каналов, частоту дискретизации. Структура этой обертки, т.е. формат WAV-файла, изображена ниже:

 

image

Как видим, файл начинается маркером “RIFF” (4 байта), т.к. WAV-файлы являются подмножеством RIFF-формата (Resource Interchange File Format); затем следует поле Size (4 байта), которое указывает размер области данных в байтах без учета размера заголовка. Затем следует область данных.

Область данных начинается маркером “WAVE” (4 байта) и состоит из отдельных фрагментов (чанков) – звуковых чанков, чанков формата данных или чанков специальных данных (инфа об исполнителе и др). Любой чанк начинается из четырехбайтного маркера (например, “fmt “, “data”), затем следует четыре байта содержащих информацию о длине поля данных, затем – полезные данные.

Короче, весь файл состоит из чанков. Первый чанк — “RIFF”, он включает в себя все остальные (в нашем случае “WAVE”). “WAVE” включает в себя чанки  “fmt “ и “data”.

Бывалые утверждают, что в большинстве случаев файл состоит только из чанка “RIFF”, в который включен чанк “WAVE”, состоящий из чанков “fmt “ и “data” (как на нашем рисунке). Насколько это верно, проверю на практике.

Следует особо рассмотреть чанк “fmt “, который содержит информацию о формате звуковых данных. Он описывается следующей структурой:

1
2
3
4
5
typedef struct
{ 
  WaveFormat wf;             //Формат данных
  WORD vBitsPerSample;       //Количество бит на одну выборку сигнала                         /
} PCMWAVEFORMAT;

Поле vBitsPerSample в особых пояснениях не нуждается — это разрядность данных.

Поле wf — это структура, имеющая следующий вид:

1
2
3
4
5
6
7
8
typedef struct
{
  WORD  wFormatTag;          // Формат звуковых данных                                        /
  WORD  nChannels;           // Количество каналов
  DWORD nSamplesPerSec;      // Частота дискретизации аудиосигнала
  DWORD nAvgBytesPerSec;     // Количество байт передаваемых в секунду
  WORD  nBlockAlign;         // Выравнивание данных в чанке данных
} WaveFormat;

Подробнее о полях структуры WaveFormat:

  • wFormatTag – формат данных, может указывать на наличие компрессии сигнала (нам компрессия не нужна, т.е. этот параметр должен быть равен единице);
  • nChannels – количество каналов (моно – 1, стерео – 2);
  • nSamplesPerSec – частота дискретизации (обычно стандартные значения, например 44100 кГц);
  • nAvgBytesPerSec – скорость потока данных (можем вычислить и без этого значения – достаточно знать разрядность сигнала и частоту дискретизации);

Зачем для описания формата данных используют две структуры, а не запихнули все в одну? Не знаю.

Чанк данных “data” содержит интересующий нас аудио сигнал. Если это стереосигнал разрядностью 16 бит, одна выборка будет выглядеть следующим образом:

image

Остальные варианты получаются усечением данного примера.

Отследим все смещения полей относительно начала файла:

Смещение   Размер         Поле

     0×00             4              ‘RIFF’ – маркер начала 

     0х04             4              Размер чанка ‘RIFF’

     0х08             4              ‘WAVE’ – маркер чанка WAVE

     0х0С             4             ‘fmt ‘ – маркер чанка   fmt

     0х10             4              размер fmt-чанка

     0х14             2              wFormatTag = 1

     0х16             2              nChannels = ?

     0х18             4              nSamplesPerSec = ?

     0х1C             4              nAvgBytesPerSec = ?

     0х20             2              nBlockAlign = ?

     0х22             2              nBitsPerSample = ?

     0х24             4              ‘data’ – маркер чанка звуковых данных

     0х28             4              Size — размер чанка звуковых данных

     0х2С             Size         звуковые данные

                 
Прочитав первые 44 байта файла можно затем анализировать информацию – какова разрядность сигнала, его дискретизация, длина звуковых данных и  с какого места файла они начинаются. Затем эти звуковые данные нужно пихать в ЦАП с частотой дискретизации и в результате должны получить звук.

Для просмотра заголовка WAV-файлов я написал небольшую программу.

Скачать ее можно здесь: скачать

Эта программа выводит название полей, их hex-код, а также расшифровку кода. Помимо этого в папке имеется один короткий WAV-файл для примера. Ниже приведен скриншот программы с открытым прилагаемым файлом:

image

Как видно, размер файла 127838 байт   а  размер чанка данных 127528 байт. Если из этой разницы вычесть размер всего заголовка, то  останется больше двухсот байт. Получается, что после чанка ‘data’ следует еще какой-то чанк. Это могут быть информационные, а могут быть и звуковые данные. Короче, при написании программы для контроллера необходимо проверять весь файл до конца на факт наличия чанков.

Комментарии (3) на “STM32 Воспроизведение звука. Структура WAV-файлов”

  • barteroff:

    Спасибо за статьи! Каждый день жду новую =)

  • Для просмотра заголовка WAV-файлов я написал небольшую программу.
    Скачать ее можно здесь: скачать
    ===================================
    Не работает ссылка

  • Посоветовал бы вам поискать человека который уже это делал. Вы сэкономите очень много времени.

Оставить комментарий

Spam Protection by WP-SpamFree