Программа для прослушивания wav. Загрузка и воспроизведение несжатого wav-файла. Воспроизведение звука микроконтроллером

Воспроизведение WAV-аудио

Платформа.NET Framework имеет небогатую историю поддержки звука. Версии 1.0 и 1.1 не предлагали никакого управляемого способа воспроизведения аудио, а когда долгожданная поддержка, наконец, появилась в.NET 2.0, она была представлена в форме не приводящего в восторг класса SoundPlayer (который можно найти в "малонаселенном" пространстве имен System.Media). Класс SoundPlayer довольно ограничен: он может воспроизводить только файлы в формате WAV, не поддерживает воспроизведения одновременно более одного звука и совсем не предоставляет возможностей управления никакими аспектами воспроизведения аудио (например, громкостью и балансом).

Чтобы получить эти возможности, разработчики, использующие Windows Forms, вынуждены были работать с библиотекой неуправляемого кода quartz.dll. Библиотека quartz.dll - ключевая часть DirectX, и она присутствует в проигрывателе Windows Media и операционной системе Windows. (Тот же компонент известен под названием DirectShow, а предыдущие версии назывались ActiveMovie.)

Класс SoundPlayer поддерживается в приложениях WPF. Если смириться с его существенными ограничениями, то можно сказать, что он предлагает наиболее простой и легкий способ добавления работы с аудио в приложения. Класс SoundPlayer также упаковывается в класс SoundPlayerAction. который позволяет воспроизводить звук через декларативный триггер (вместо написания нескольких строк кода C# в обработчике событий). В следующих разделах будет представлен краткий обзор обоих классов, а затем уже описания более мощных WPF-классов MediaPlayer и MediaElement.

Класс SoundPlayer

Чтобы воспроизвести звук с помощью класса SoundPlayer, понадобится выполнить перечисленные ниже шаги:

    Создать экземпляр SoundPlayer.

    Указать звуковое содержимое, установив либо свойство Stream , либо свойство SoundLocation . Если есть объект Stream, содержащий звук в формате WAV, используйте свойство Stream. Если же есть путь к файлу или URL, указывающий на файл WAV, применяйте свойство SoundLocation.

    Если аудио-содержимое хранится в виде двоичного ресурса и встроено в приложение, то потребуется доступ к нему в виде потока и использование свойства SoundPlayer.Stream. Причина в том, что SoundPlayer не поддерживает синтаксис упакованных URL в WPF.

    Установив свойство Stream или SoundLocation, можно заставить SoundPlayer в действительности загрузить аудиоданные, вызвав метод Load() или LoadAsync(). Метод Load() наиболее прост - он останавливает выполнение кода до тех пор, пока весь звуковой фрагмент не будет загружен в память. LoadAsync() выполняет свою работу в другом потоке и по завершении инициирует событие LoadCompleted.

    Формально использовать Load() или LoadAsync() не обязательно. Экземпляр SoundPlayer загружает аудиоданные по мере необходимости, когда вызывается метод Play() или PlaySync(). Однако явно загрузить аудио-фрагмент - хорошая идея; это не только позволит снизить накладные расходы при многократном воспроизведении, но также упростит обработку исключений, связанных с файловыми проблемами, отдельно от исключений, вызванных причинами, относящимися к процессу воспроизведения.

    После этого можно вызвать PlaySync() , который приостановит код на время воспроизведения аудио-фрагмента, или же применить Play() для воспроизведения в другом потоке, обеспечивая интерфейсу приложения способность реагировать на действия пользователя. Единственный другой доступный вариант - это метод PlayLooping() , воспроизводящий аудио-фрагмент асинхронно в бесконечном цикле (что идеально для саундтреков). Чтобы остановить текущее воспроизведение в любой момент, необходимо вызвать метод Stop() .

В следующем фрагменте кода демонстрируется простейший подход к загрузке и асинхронному воспроизведению аудиофайла:

SoundPlayer sp = new SoundPlayer(); sp.SoundLocation = "tada.wav"; sp.Load(); sp.PlayLooping()

До сих пор в коде предполагалось, что аудиофайл присутствует в том же каталоге, что и скомпилированное приложение. Однако загружать SoundPlayer-аудио из файла не обязательно. Для коротких звуков, которые воспроизводятся в нескольких местах приложения, возможно, разумнее встроить звуковые файлы непосредственно в скомпилированную сборку в виде двоичных ресурсов (не путайте их с декларативными ресурсами, определяемыми в коде разметки XAML). Эта техника работает со звуковыми файлами так же хорошо, как и с графическими изображениями.

Например, если добавить файл ding.wav как ресурс по имени Ding (просто перейдите к узлу Properties --> Resources (Свойства --> ресурсы) в окне Solution Explorer и воспользуйтесь поддержкой визуального конструктора), то можно будет применить следующий код для его воспроизведения:

SoundPlayer player = new SoundPlayer(); player.Stream = Properties.Resources.Ding; player.Play();

Класс SoundPlayer не слишком хорошо работает с большими аудиофайлами, поскольку он должен загрузить в память весь файл целиком. Может показаться, что данную проблему можно разрешить, разбив большой аудиофайл на куски, однако класс SoundPlayer не предназначен для этого. Не существует простого способа такой синхронизации SoundPlayer, чтобы он мог воспроизвести множество аудиофрагментов друг за другом, поскольку он не обеспечивает никаких средств для организации очередей. Всякий раз, когда вызывается метод PlaySound() или Play(), текущее воспроизведение останавливается. Обходные пути возможны, но намного лучше вместо этого воспользоваться классом MediaElement.

Класс SoundPlayerAction

Класс SoundPlayerAction позволяет более удобно использовать класс SoundPlayer. Класс SoundPlayerAction унаследован от TriggerAction, который позволяет использовать его в ответ на любое событие.

Ниже приведена разметка кнопки, применяющей SoundPlayerAction для подключения события Click к звуку. Триггер организован так, что его можно применить к множеству кнопок (если перенести его в коллекцию Resources):

При использовании SoundPlayerAction звук всегда воспроизводится асинхронно.

Я не являюсь автором заметки (нашел ее у себя на компьютере, разбирая завалы). Материал полезный, но найти автора не получилось (как и найти этот же материал в интернете). Публикую, надеюсь автор не будет против (не для того ведь он писал это сокровище, чтобы оно пылилось только на моем жестком диске?).

1. Введение

В этой статье я расскажу о том, как загружать и проигрывать wav-файл, проигрывать будем при помощи DirectSound, поэтому, если у Вас не стоит DirectX SDK, то идите на microsoft.com, и скачайте там 9-ую версию. Писаться всё это дело будет под C++, примеры тестировались в MSDEV2005.

2. Структура wav – файла

Для начала рассмотрим структуру, wav-файла, я не раскрою Вам эту тайну полностью, но то о чём говорил в заголовке статьи, наверное, сделаю.

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

Вот некоторые из этих блоков:

2.1. Блок RIFF

2.2. Блок WAVE

2.3. Блок FACT(необязательный)

2.4. Блок DATA:

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

3. Алгоритм

Вообще, мы не учтём то, что все блоки стоят не в нормальном порядке. Почему? А потому что стандартная виндовская программа «Звукозапись», читает любые нормальные wav-файлы, а пишет, нам нужные, а мы ведь пишем игру, а не плеер;)?

Итак, сначала при откроем файл, потом прочитаем первые четыре байта, проверим, что они равны MAKEFOURCC(‘R’,’I’,’F’,’F’), или, если вы считывали в строку, то просто “RIFF”, и если проверка провалилась, то бьём тревогу, мол, это не wav-файл.

Далее пропускаем информацию о размере, и читаем ‘WAVE’-блок, так же проверяем, иначе бьём тревогу, далее идёт блок ‘fmt ‘, считываем ещё 4 байта и проверяем их, заметьте, что все буквы в блоке ‘fmt ‘ маленькие!!!

Кстати, в структуре WAVEFORMATEX, есть поле под названием “wFormatTag”, если оно не равно 1, значит данные либо запакованные, либо не нашего формата, короче вывод состоит в том, что прочитать мы его не сможем… Из этого следует, что лучше в этом случае считывание прекратить и выдать ошибку.

Дальше самое интересное при считывании этого вида файла. Когда я искал информацию по данному поводу, я зашёл на форум, задал вопрос, через месяц (!!!) мне ответили и дали пример, я разобрался, и теперь пишу эту статью, но не один (!!!) пример не был рассчитан на блок ‘fact’, что он делает полезного, я так и не понял, но я на него просто
забиваю, т.к. после наименования блока, обычно идёт его размер, так я и сделал, и теперь всё работает…

А теперь по делу, читаем 4-е байта, проверяем, если в них записано “fact”, то считываем 4-е байта, количество байтов, которое мы должны пропустить будет считанным числом. Тем самым мы добираемся до секции ‘data’.

Может быть такая ситуация, что Вы не найдёте блока ‘fact’, а найдёте сразу ‘data’, не пугайтесь в любом случае всё будет работать нормально.

Итак, обработка блока ‘data’, как обычно считываем имя блока (эти самые 4-е байта), если оно неравно ‘data’, то бунтуем, потом считываем размер данных, заметьте, что этот размер теперь для нас важен!!! Т.к. это тот размер, который обозначает размер аудио-данных!!!

Считали, а за ним, та самая вкусность ради которой мы всё это проделали =). Ням-ням, считываем, дальше всё раскидываем по буферам, и вуаля! Усе готово!!!

Теперь переходим непосредственно к коду.

4. Программная часть

Создаём консольное приложение, пустое, никаких сервисов, типа MFC не используем.

Сначала о заголовках, надо подключить сам DirectSound, а так же всё стандартное для консольного приложения считывающего из файла, т.е. потоки ввода-вывода и поток чтения из файла, ну и, разумеется windows, что бы узнать окно, к которому привязывать DirectSound:

#include #include #include #include
Не забываем подключить необходимые lib’ы:
#pragma comment(lib,"dsound.lib") #pragma comment(lib,"dxguid.lib")
Отключаем предупреждение об устаревшей функции…
#pragma warning(disable:4996) //ОТключаем предупреждение strcat
Используем пространство имен std:
using namespace std;
DirectSound у нас будет общим, поэтому мы его объявляем в глобальных переменных:
IDirectSound8 *g_pDirectSound;
Прототип функции, которая будет проигрывать файл:
void PlayWav(const char* szFileName);
Наша главная функция 🙂
int main() { DirectSoundCreate8(0,&g_pDirectSound,0); g_pDirectSound->SetCooperativeLevel(GetForegroundWindow(),DSSCL_NORMAL); PlayWav("chord.wav"); g_pDirectSound->Release(); g_pDirectSound=NULL; return 0; }
В ней мы инициализируем DirectSound и проигрываем файл.

Теперь, непосредственно насчёт функции PlayWav:

Void PlayWav(const char* szName) { char szFileName; memset((void*)szFileName,0,255); GetWindowsDirectoryA((char*)szFileName,255); strcat((char*)szFileName,"\Media\"); strcat((char*)szFileName,szName);
Все файлы, которые нам указали лежат в папке «WINDOWSMedia», поэтому мы прицепляем это к каждому имени файла, мы ведь не знаем, на какой диск у пользователя установлен WINDOWS?
cout<<"Loading file: "< Проверяем файл на наличие, открываем, если такой имеется.
cout<<"Finding chunk "RIFF""< Прыгаем по блокам, кстати взгляните на последнюю строку, хоть размер структуры WAVEFPRMATEX==16, мы всё равно игнорируем байты, которые идут после описания формата, потому что ещё при различной кодировке файла, может быть добавлена дополнительная информация.

If(waveFormat.wFormatTag!=1) { cout<<"This file is compressed!"< Дальше идёт переломный момент с блоком ‘fact’, я это уже объяснял в теоретической части, теперь можно взглянуть и на код, возможно, будет более понятнее =)
DWORD dwNextChunk=0; fIn.read((char*)&dwNextChunk,4); if(dwNextChunk==MAKEFOURCC("f","a","c","t")) { cout<<"Finded chunk "fact"!"< Я думаю, что этот код понятен, мы просто проверяем стоит-ли перед блоком ‘data’, не нужный нам блок ‘fact’, и пропускаем его в случае нахождения.

DWORD dwDataSize=0; fIn.read((char*)&dwDataSize,4); char *pData=new char; fIn.read(pData,dwDataSize); cout<<"Closing file, Loading melody!"< Теперь идёт работа с DirectSound, создаём буфер… IDirectSoundBuffer *pSoundBuffer=NULL; DSBUFFERDESC dsbd=DSBUFFERDESC(); dsbd.dwBufferBytes =dwDataSize; dsbd.dwFlags =DSBCAPS_STATIC; dsbd.dwSize =sizeof(DSBUFFERDESC); dsbd.lpwfxFormat =&waveFormat; g_pDirectSound->CreateSoundBuffer(&dsbd,&pSoundBuffer,0);
Заполняем…
void *pDst=0; DWORD dwSize=0; pSoundBuffer->Lock(0,0,&pDst,&dwSize,0,0,DSBLOCK_ENTIREBUFFER); memcpy(pDst,pData,dwSize); pSoundBuffer->Unlock(pDst,dwSize,0,0); pSoundBuffer->Play(0,0,0); DWORD hrStatus=0;
Задерживаем окончание выполнения функции до тех пор, пока проигрывается файл:
do { pSoundBuffer->GetStatus(&hrStatus); } while(hrStatus==DSBSTATUS_PLAYING); return; };
Вот и всё!!!

В этой статье мы рассмотрим практический пример использования SD карты с микроконтроллером AVR. По просьбе трудящихся я написал проект, который читает с SD карты wav файл и воспроизводит его.

Для проекта я использовал микроконтроллер atmega16, тактируемый от внешнего кварца с частотой 6 МГц. В качестве ЦАПа задействована функция формирования ШИМ сигнала таймера Т0. Wav файл для воспроизведения был выбран с такими параметрами: 8 бит, 22 кГц, моно.

Низкоуровневые функции для работы с SD картой

Чтобы использовать библиотеку Petit FatFs с SD картой, нужно реализовать три низкоуровневые функции для работы с ней - это функция инициализации, чтения и записи. Если вы читали предыдущий материал, в котором была описана библиотека Petit FatFs , то должны помнить, что "пустышки" этих функций находятся в файле diskio.c

На сайте Elm Chan`a есть примеры использования библиотеки с SD картами, поэтому можно взять уже готовые функции из этих проектов, что я и сделал. Я позаимствовал из одного примера файл mmc.c и заменил им файл diskio.c , однако файл mmc.c тоже потребовал небольшого "допиливания".

В начале файла определены макросы, реализующие работу с SPI интерфейсом. Можно определить эти макросы для работы с аппаратным SPI модулем, а можно реализовать программный SPI. Это уже вопрос ресурсов и желания пользователя.

Макросы такие:

#define SELECT() - формирует низкий уровень на CS выводе карты
#define DESELECT() - формирует высокий уровень на СS выводе карты
#define MMC_SEL - возвращает единицу, если на выводе CS низкий уровень
#define init_spi() - инициализирует SPI модуль
#define dly_100us() - формирует задержку на 100 микросекунд
#define xmit_spi(d) - передает байт данных по SPI
#define rcv_spi() - принимает байт данных по SPI
#define FORWARD(d) - перенаправляет поток данных, этот макрос можно оставить пустым

Все эти макросы легко реализовать, если прочитать материал про SPI модуль AVR микроконтроллера. Я как раз взял оттуда spi драйвер и "прицепил" его к файлу mmc.c.

Короче, получается такая последовательность. Мы берем библиотеку Petit FatFs добавляем к ней файл mmc.c из примеров Elm Chan`a, описываем макросы реализующие spi и после этого можем работать с SD картой. Немного заморочено, но если один раз разобрался, то все становится понятно.

Итак, я все это проделал и теперь могу использовать SD карту. И чтобы показать, что это действительно работает, я написал проект, в котором микроконтроллер читает с SD карты wav файл и воспроизводит.

Воспроизведение звука микроконтроллером

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

В качестве файла для воспроизведения я взял произвольный mp3 трек (мне попалась песня группы Prodigy) и перекодировал его в wav файл с такими параметрами: 8 бит, 22 кГц, моно. Для конвертирования файла я использовал видео редактор Sony Vegas, но можно найти программу и попроще. Например, такая функция есть во многих аудио редакторах вроде Sound Forge, WaveLab, Cool Edit и т.д.

"8 бит" - это разрядность одной выборки аналогового сигнала. Звук хорошего качества обычно имеет разрядность 16 (CD качество) или 24 бита (студийная запись), но для микроконтроллерной "говорилки" 8-и разрядов хватит за глаза.

"22 кГц" - это частота дискретизации. То есть частота, с которой из аналогового сигнала "брались" выборки. С этой же частотой мы должны преобразовывать цифровые выборки сигнала в аналоговые напряжения. Цифровой звук хорошего качества обычно имеет частоту дискретизации 44.1 кГц (CD качество), 96 кГц (студийная запись) и т.д.

"моно" - означает одну звуковую дорожку, которая будет воспроизводиться как в правом, так и в левом аудио каналах.

Итак, для воспроизведения wav файла с параметрами 8 бит, 22 кГц, моно, нам понадобится одноканальный 8-и разрядный ЦАП, способный формировать на выходе аналоговые напряжения с частотой 22 кГц. Поскольку у большинства AVR`ок нет встроенного цифро-аналогового преобразователя, мы можем использовать следующие варианты:

Аппаратный ШИМ,
- программный ШИМ,
- внешний интегральный ЦАП,
- внешняя схема ЦАП`a .

Реализация программного ШИМ`a требует от микроконтроллера большого быстродействия, поэтому я не захотел с ним связываться. Внешний ЦАП обычно использует SPI, который нужен для SD карты. Внешняя схема ЦАП`а, например схема R-2R, неплохой вариант, но под нее нужно отдать целый порт микроконтроллера.

Исходя из этого, я остановил свой выбор на аппаратном 8-и разрядном ШИМ`е. Во первых, эта функция есть во всех микроконтроллерах AVR, а во-вторых, для реализации ЦАП`а требуется всего один вывод микроконтроллера.

В одном из старых материалов я уже описывал принцип формирования аналогового сигнала с помощью ШИМ , однако в случае wav файла здесь не все так просто.

С какой частотой можно формировать аналоговые напряжения с помощью ШИМ? Это зависит от трех параметров: тактовой частоты микроконтроллера, делителя таймера и его разрядности. Например, для 8-и разрядного ШИМ сигнала при тактовой частоте микроконтроллера 16 МГц можно получить следующие частоты.

Тактовая частота микроконтроллера Fcpu = 16000000 Гц
Тактовая частота таймера Т0 Ftim = Fcpu/k , где k - 1, 8, 64, 256, 1024.
Частота ШИМ сигнала Fpwm = (Fcpu/k)/2^8 = Fcpu/(k*256)

при k = 1 Fpwm = 62500
при k = 8 Fpwm = 7812
при k = 64 Fpwm = 976
при k = 256 Fpwmn = 244
при k = 1024 Fpwm = 61

Ближайшая частота к требуемым 22 килогерцам - это 7812, но такая частота не подойдет. Файл, воспроизводимый с такой частотой, будет уж слишком замедленным. Надо подобрать такую тактовую частоту микроконтроллера, при которой можно получить требуемую частоту формирования аналоговых напряжений. Неплохой результат получается при 6 МГц и k = 1

Fcpu = 6000000 Гц
Fan = 6000000/(2^8 * 1) = 23437 Гц

Звуковой файл будет немного ускоренно воспроизводиться, но на слух это почти незаметно.

Итак, аналоговые напряжения будут формироваться с помощью ШИМ функции аппаратного таймера Т0, но как разнести процесс чтения данных с процессом воспроизведения? Считывать с SD карты по одной выборке сигнала с частотой 22 кГц не получится, микроконтроллер не будет успевать это сделать.

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

Я подбирал размер буфера следующим образом. Задал максимальную частоту SPI модуля микроконтроллера atmega16 и посмотрел сколько времени затрачивается на чтение данных с SD карты. То есть сколько времени выполняется функция pf_read(..).

При тактовый частоте Fcpu = 6 МГц эта функция выполнялась ~2.5 мс, но иногда попадались циклы по 5 мс (наверное из-за чтения на границе секторов.. напишите в комментариях, если знаете). Причем это время не зависело от количества данных - и 32, и 64, и 128 байт читались за одно и то же время.

Затем я посчитал сколько данных будет передано в ЦАП за время 5 мс. Частота нашего псевдо ЦАП`a = 23437 Гц, соответственно период = 42.6 мкс. Делим 5 мс на 42.6 и получаем искомую цифру.

n = 0.005/(1/23437) = 117

То есть за 5 мс микроконтроллер выдаст 117 выборок сигнала, при этом за это же время успеет прочитать с карты 128 выборок. Получается, что если взять буфер размером 256 байт микроконтроллер будет успевать выполнять обе задачи и даже остается небольшой запас времени. Он, кстати, необходим, потому что в процесс чтения данных с SD карты, будут вклиниваться прерывания таймера Т0.

Так я и сделал. Выбрал размер буфера равным 256 байт.

Схема для проекта

Схема питается от двух стабилизаторов - 3.3 В и 5 В. Как вариант можно запитать все схему от 3-х вольтового источника (тогда даже не понадобятся преобразователи уровней) или понизить 5-и вольтовое напряжение с помощью трех последовательно включенных диодов и запитать таким образом SD карту.

Микроконтроллер тактируется от внешнего кварцевого резонатора с частотой 6 МГц. SD карта подключена к микроконтроллеру по одной из приведенных ранее схем.

Для преобразования ШИМ сигнала в постоянное напряжение используется низкочастотный RC фильтр из двух каскадов. Частота среза фильтра около 10 кГц, что соответствует полосе воспроизводимого цифрового сигнала.

Для индикации состояния устройства используется светодиод LED1. Если карта не считывается в момент включения устройства, светодиод начинает моргать.

Код проекта

Все основное действо заключено в файле main.c. При старте программы происходит инициализация переменных и настройка выводов - настраивается ШИМ выход и выход для светодиода.

Затем монтируется SD карта, открывается файл под названием 1.wav и из него (в буфер) читаются 256 байт. Далее проверяется результат выполнения операций с картой. Если операции завершились неудачно, программа зацикливается и начинает моргать светодиод. Если чтение прошло успешно, программа инициализирует таймер Т0 и зажигает светодиод.

Далее разрешаются прерывания и программа попадает в бесконечный цикл, внутри которого расположен конечный автомат с тремя состояниями. В первом состоянии идет заполнение нижней половины буфера, во втором - верхней половины, а в третьем программа ничего не делает. В третье состояние автомат переходит, когда wav файл заканчивается. При этом гасится светодиод и выключается таймер Т0.

Параллельно основному циклу программы выполняется прерывание таймера Т0. Оно сделано предельно коротким. В микроконтроллер загружает в регистр сравнения OCR0 содержимое ячейки буфера, на которую указывает индексная переменная, а затем инкрементирует ее. Поскольку размер буфера равен 256 байт, индексную переменную не приходится проверять на граничное значение.

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

Неиспользуемые функции библиотеки Petit FatFs я отключил в файле pff.h.

Из недостатков проекта можно отметить два момента:

При воспроизведении не пропускается заголовок wav файла
- когда wav файл заканчивается таймер выключается, не "доигрывая" оставшееся содержимое буфера.



// Author(s)...: Pashgan
// Target(s)...: Mega16
// Compiler....: GCC
// Description.: Воспроизведение wav файла с SD карты
//****************************************

#include "compilers_4.h"
#include "diskio.h"
#include "pff.h"

/* выводы микроконтроллера */
#define LED_PORT PORTD
#define LED_DIR DDRD
#define LED_PIN 4

#define PWM_PORT PORTB
#define PWM_DIR DDRB
#define PWM_PIN 3

/* буфер */
#define BUF_SIZE 256UL
#define HALF_BUF ((BUF_SIZE)/2)

uint8_t buf;

/*переменные для доступа к SD*/
FATFS fs;
WORD s1;
FRESULT res;

/*остальные переменные*/
typedef enum{ST_LOW_BUF, ST_HI_BUF, ST_STOP}state_t;
static state_t st;

static volatile uint8_t i;
static char f = "1.wav";

int main(void)
{
st = 0;
i = 0;

/*настройка шим выхода*/
PWM_DIR |= (1<PWM_PORT &= ~(1<

/*статусный светодиод*/
LED_DIR |= (1< LED_PORT &= ~(1<

/*монтируем диск, открываем файл, заполняем буфер*/
res = pf_mount(&fs);
if (res == FR_OK){
res = pf_open(f);
if (res == FR_OK){
res = pf_read(buf, BUF_SIZE, &s1);
}
}

/*если файл прочитался, то запускаем таймер*/
if (res == FR_OK){
TCCR0 = 0;
TCNT0 = 0;
TIMSK |= (1< TIFR = (1< TCCR0 = (1<

LED_PORT |= (1< }
else{

/*в противном случае зацикливаемся
и мигаем светодиодом */
while(1){
LED_PORT ^= (1< _delay_ms(300);
}
}

Sei();
while(1){

Switch (st){
case ST_LOW_BUF:

/*если индекс указывает на верхнюю половину
буфера, то заполняем нижнюю половину*/
if (i >= HALF_BUF) {
pf_read(buf, HALF_BUF, &s1);
if (s1 < HALF_BUF){
TCCR0 = 0;
PWM_PORT &= ~(1<LED_PORT &= ~(1< st = ST_STOP;
}
else{
st = ST_HI_BUF;
}
}
break;

Case ST_HI_BUF:
/*если индекс указывает на нижнюю половину
буфера, то заполняем верхнюю половину*/
if (i < HALF_BUF) {
pf_read(&buf, HALF_BUF, &s1);
if (s1 < HALF_BUF){
TCCR0 = 0;
PWM_PORT &= ~(1<LED_PORT &= ~(1< st = ST_STOP;
}
else{
st = ST_LOW_BUF;
}
}
break;

Case ST_STOP:
/*файл закончился, ничего не делаем*/
break;

Default:
break;
}

}
return 0;
}

/*ШИМ, который формирует нам аналоговый сигнал*/
ISR(TIMER0_OVF_vect)
{
uint8_t tmp = i;

OCR0 = buf;
tmp++;

I = tmp;
}

Заключение

Подведем итог. Wav файл - это последовательность выборок сигнала. Он имеет такие характеристики, как разрядность и частота дискретизации. Чтобы воспроизвести wav файл мы должны преобразовывать выборки сигнала в аналоговые напряжения, причем делать это с частотой дискретизации, иначе файл будет воспроизводиться замедленно или наоборот ускоренно.

Для формирования аналогового напряжения можно использовать широтно-импульсную модуляцию или микросхемы цифро-аналоговых преобразователей. При этому нужно учитывать производительность микроконтроллера и этих микросхем. Разрядность цифро-аналогового преобразователя должна быть равной или выше разрядности воспроизводимых данных, иначе их придется округлять.

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




Top