VR
Virtual Reality On-line   Железо
Новости   |     Журнал    |    Хаkер   |     Магазин   |   Проекты
[   Вход    ]
[Kарта сайтa]
[ Download  ]
[  Конкурс  ]
[  Анекдоты ]
[  Ссылки   ]
[  Реклама  ]
[ Почтальон ]
[ О проекте ]






TopList
Программирование звука.
Боевой комплект
:
Logo

Начиная с сегодняшнего номера я начинаю цикл статей про программирование звука. Я покажу тебе, как эффективно выводить звук в колонки и как его записывать. Для воспроизведения и записи звука нам придётся познакомиться как минимум с одним форматом хранения файлов. Для начала это будет WAV, но в разделе форматы файлов я опишу ещё пару. Помимо этого, нам предстоит научиться преобразовывать файлы из одного формата, в другой. Как всегда, для реализации примеров будет использован язык Delphi.

Для понимания технологии программирования звука тебе понадобится немного познакомится с внутренностью работы цифрового звука. Вот именно с этого мы и начнём рассмотрение цифрового звука.

Звуковые данные хранятся в компьютере с помощью метода импульсно-кодовой модуляции. Ты наверно не раз встречал сокращение PCM, она именно это и означает. Расшифровывается PCM как Pulse-Code Modulation. При этом методе аналоговый звук квантуется по времени и амплитуде. При выводе звука на колонки происходит обратное преобразование. Для большего понимания я нарисовал рис. 1, на котором всё представлено удобно для твоего глаза.
Logo
Рис 1. Квантование

На графике слева нарисована амплитуда аналогового (ну и словечко) сигнала. Вверх направлена ось амплитуды звука, а вправо - время звучания. Вертикальными штриховыми линиями показано время, в которое произошёл замер амплитуды и сохранение её величины.

На правом рисунке показан результат. Как видишь, кривая звука превратилась в точки. Всё, что между точками потеряно, поэтому, чем чаще происходит замер амплитуды, тем выше качество звука и меньше потерь. Значение 1/время между замерами (дельта Т) называется частотой дискретизации. Для качественного звука, частота дискретизации должна быть вдвое больше чем высшая частота в обрабатываемом звуке.
Анекдот:

Пpогpаммиста спpашивают:
- Как вам yдалось так быстpо выyчить английский язык?!!
- Да, еpyнда какая. Они там почти все слова из С++ взяли.
Подробнее

С физикой мы разобрались. Теперь давай двинем в сторону рукоблудия (никакой пошлости, я имел ввиду программирование). Сколько бы не ругали Windows за его глючность, я его буду хвалить за то, что здесь есть практически всё необходимо программисту, т.е. мне. Так что переходим к делу и начинаем программить.

Для работы со звуковухой используется всё тот же алгоритм, как и с любым другим устройством.

  • Инициализировать драйвер звуковой.
  • Установить свои параметры.
  • Воспроизвести или записать звуковые данные.
  • Помахать ручкой и закрыть драйвер.

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

Для сегодняшнего примера тебе понадобится пять основных функции. Все они определены в модуле mmsystem, поэтому тебе придётся подключить его в раздел uses.

 Для С++
  MMRESULT waveOutOpen(
    LPHWAVEOUT phwo,	
    UINT uDeviceID,	
    LPWAVEFORMATEX pwfx,	
    DWORD dwCallback,	
    DWORD dwCallbackInstance,	
    DWORD fdwOpen	
   );	

 Для Delphi

  function waveOutOpen(
   lphWaveOut: PHWaveOut; 
   uDeviceID: UINT;
   lpFormat: PWaveFormatEx; 
   dwCallback, dwInstance, 
   dwFlags: DWORD
  ): MMRESULT;
  • lphWaveOut - адрес, по которому будет записан указатель на устройство воспроизведения.
  • uDeviceID - идентификатор устройства, которое ты хочешь открыть. Если ты поставишь сюда константу WAVE_MAPPER, то откроется устройство по умолчанию.
  • lpFormat - указатель на структуру типа WAVEFORMATEX, в которой описан формат воспроизводимых звуковых данных.
  • dwCallback - указатель на функцию семафор. Эта функция будет вызываться, чтобы сообщить тебе о происходящем.
  • dwFlags - Параметры открываемого устройства. Может принимать следующие значения:
    • CALLBACK_EVENT - в dwCallback находится событие THandle, через которое будет происходить информирования о ходе воспроизведения.
    • CALLBACK_THREAD - в dwCallback находится идентификатор потока
    • CALLBACK_FUNCTION - в dwCallback находится указатель на функцию.
    • CALLBACK_WINDOW - в dwCallback указатель на окно, которому будут посылаться сообщения.
    • CALLBACK_NULL - в dwCallback ничего нет.
    • WAVE_ALLOWSYNC - можно открыть устройство в синхронном режиме.
    • WAVE_FORMAT_DIRECT - запрещается преобразование данных с помощью ACM драйвера.
    • WAVE_FORMAT_QUERY - Если ты установишь этот параметр, то реального открытия звуковухи не произойдёт. Функция проверит возможность открытия с заданными тобой параметрами, и если всё ничтяк, то вернёт тебе MMSYSERR_NOERROR. Если твои параметры недопустимы, то вернётся код ошибки. В любом случае реального открытия устройства не произойдёт. Этот флаг можно использовать как тест на допустимость настроек.

Функция может вернуть следующие значения:

  • MMSYSERR_ALLOCATED - устройство уже открыто
  • MMSYSERR_BADDEVICEID - указан неправильный идентификатор устройства uDeviceID
  • MMSYSERR_NODRIVER - драйвер отсутствует
  • MMSYSERR_NOMEM - не могу выделить память, или она заблокирована
  • WAVERR_BADFORMAT - указан неправильный формат
  • WAVERR_SYNC - устройство в синхронно, но оно вызвано без WAVE_ALLOWSYNC флага

Теперь рассмотрим используемую здесь структуру WAVEFORMATEX:

 Для С++
  typedef struct {  
    WORD  wFormatTag; 
    WORD  nChannels; 
    DWORD nSamplesPerSec; 
    DWORD nAvgBytesPerSec; 
    WORD  nBlockAlign; 
    WORD  wBitsPerSample; 
    WORD  cbSize; 
  } WAVEFORMATEX; 

 Для Delphi

  PWaveFormatEx = ^TWaveFormatEx;
  tWAVEFORMATEX = packed record
    wFormatTag: Word;
    nChannels: Word;
    nSamplesPerSec: DWORD;
    nAvgBytesPerSec: DWORD;
    nBlockAlign: Word;
    wBitsPerSample: Word;
    cbSize: Word;
  end; 
  • wFormatTag - Формат звуковых данных. Мы будем использовать в основном WAVE_FORMAT_PCM.
  • nChannels - Количество каналов (1- моно, 2 - стерео).
  • nSamplesPerSec - Частота дискретизации (возможны значения 8000, 11025. 22050 и 44100).
  • nAvgBytesPerSec - Количество байт в секунду. Для WAVE_FORMAT_PCM это является результатом nSamplesPerSec* nBlockAlign.
  • nBlockAlign - Выравнивание блока. Для WAVE_FORMAT_PCM равен wBitsPerSample/8* nChannels
  • wBitsPerSample -Количество бит в одной выборке. Для WAVE_FORMAT_PCM может быть 8 или 16.
  • cbSize - Размер дополнительной информации, которая располагается после структуры. Если ничего нет, то должен быть 0.

Теперь у нас есть вся информация о том, как открыть звуковое устройство. На первый взгляд уже можно приступить к воспроизведению звуковых данных, но это не так. Теперь нам предстоит подготовить заголовки, которые и будут отправляться драйверу звуковухи. Для этого есть функция waveOutPrepareHeader :

 Для С++
  MMRESULT waveOutPrepareHeader(
    HWAVEOUT hwo,	
    LPWAVEHDR pwh,	
    UINT cbwh	
   );	
 
 Для Delphi

  function waveOutPrepareHeader(
    hWaveOut: HWAVEOUT; 
    lpWaveOutHdr: PWaveHdr;
    uSize: UINT
   ): MMRESULT; stdcall;

Возвращаемые параметры те же, а вот внутренности давай рассмотрим:

  • hWaveOut идентификатор устройства воспроизведения. Ты его получил после вызова функции waveOutOpen.
  • lpWaveOutHdr Указатель на структуру wavehdr_tag. Она уже должны быть специально подготовлена. Как это сделать см. ниже.
  • uSize Размер структуры wavehdr_tag.

Теперь структура wavehdr_tag.

 Для С++
  typedef struct { 
    LPSTR  lpData;                   // address of the waveform buffer 
    DWORD  dwBufferLength;           // length, in bytes, of the buffer 
    DWORD  dwBytesRecorded;          // see below 
    DWORD  dwUser;                   // 32 bits of user data 
    DWORD  dwFlags;                  // see below 
    DWORD  dwLoops;                  // see below 
    struct wavehdr_tag far * lpNext; // reserved; must be zero 
    DWORD  reserved;                 // reserved; must be zero 
  } WAVEHDR; 

 Для Delphi

  wavehdr_tag = record
    lpData: PChar;
    dwBufferLength: DWORD;
    dwBytesRecorded: DWORD;
    dwUser: DWORD;
    dwFlags: DWORD;
    dwLoops: DWORD;
    lpNext: PWaveHdr;
    reserved: DWORD;
  end;

Что же это за зверь? Посмотрим на его кишки:

  • lpData Указатель на звуковые данные.
  • dwBufferLength Размер звуковых данных.
  • dwBytesRecorded Количество записанных байт. Я думаю ты догадался, что при воспроизведении этот параметр не хляется.
  • dwUser Любая чушь.
  • dwFlags Здесь происходит описание заголовка. Возможны значения:
    • WHDR_BEGINLOOP Звуковые данные являются первыми в цикле.
    • WHDR_ENDLOOP Звуковые данные являются последними в цикле.
    • WHDR_DONE Окончание воспроизведение. Этот флаг может выставить только драйвер.
    • WHDR_QUEUE Данные помещены в очередь. Этот флаг может выставить только драйвер.
    • WHDR_PREPARED Заголовок инициализирован. Этот флаг может выставить только драйвер.
  • dwLoops Количество воспроизведений звуковых данных.
  • lpNext Зарезервировано.
  • reserved Зарезервировано.

Прежде чем вызывать waveOutPrepareHeader, ты должен заполнить структуру wavehdr_tag. Для этого её нужно сначала наполнить нулями, а затем установить правильные значения в поля указывающие размер и положение звуковых данных. А если необходимы флаги, то они тоже должны быть уже выставлены. После подготовки заголовка с помощью waveOutPrepareHeader, в заголовке нельзя уже ничего менять, кроме: lpData (можно подставить следующие данные), dwBufferLength (можно только уменьшать) и dwFlags (можно добавлять и удалять флаги WHDR_BEGINLOOP и WHDR_ENDLOOP).

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

Теперь уже можно смело отправлять подготовленный заголовок драйверу.

 Для С++
  MMRESULT waveOutWrite(
    HWAVEOUT hwo,	
    LPWAVEHDR pwh,	
    UINT cbwh	
   );	
 
 Для Delphi

  function waveOutWrite(
   hWaveOut: HWAVEOUT; 
   lpWaveOutHdr: PWaveHdr;
   uSize: UINT
  ): MMRESULT; stdcall;

  • hWaveOut идентификатор устройства воспроизведения. Ты его получил после вызова функции waveOutOpen.
  • lpWaveOutHdr это указатель на структуру, которую ты сделал с помощью waveOutPrepareHeader.
  • uSize Размер структуры wavehdr.

Возвращаемые значения те же, что и при открытии звуковухи.

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

 Для С++
  MMRESULT waveOutUnprepareHeader(
    HWAVEOUT hwo,	
    LPWAVEHDR pwh,	
    UINT cbwh	
   );	

 Для Delphi

  function waveOutUnprepareHeader(
    hWaveOut: HWAVEOUT; 
    lpWaveOutHdr: PWaveHdr;
    uSize: UINT
   ): MMRESULT; stdcall;

Давай рассмотрим все параметры.

  • hWaveOut идентификатор устройства воспроизведения. Ты его получил после вызова функции waveOutOpen.
  • lpWaveOutHdr Указатель на структуру wavehdr_tag. Она уже должны быть специально подготовлена. Как это сделать см. ниже.
  • uSize Размер структуры wavehdr_tag.

Как видишь, параметры те же, что и у функции waveOutPrepareHeader. Теперь нам осталось только закрыть устройство:

 Для С++
  MMRESULT waveOutClose(
    HWAVEOUT hwo	
   );	

 Для Delphi

  function waveOutClose(
   hWaveOut: HWAVEOUT
  ): MMRESULT; stdcall;

Давай рассмотрим все параметры.

  • hWaveOut идентификатор устройства воспроизведения. Ты его получил после вызова функции waveOutOpen.

Вот и всё. Пример мы рассмотрим в следующий раз, потому что ещё многое чего надо узнать: формат файла Wav из которого будет происходить загрузка данных и ещё несколько функций улучшающих воспроизведение.


Copyright©: Horrific aka Флёнов Михаил
Design by FMk group©