скрыть

скрыть

  Форум  

Delphi FAQ - Часто задаваемые вопросы

| Базы данных | Графика и Игры | Интернет и Сети | Компоненты и Классы | Мультимедиа |
| ОС и Железо | Программа и Интерфейс | Рабочий стол | Синтаксис | Технологии | Файловая система |



Google  
 

Waveform Audio Win32 API



Автор: Александр Галилов

Введение

Одной из наиболее важных частей Multimedia-API Windows 95/98/NT по праву может считаться Waveform Audio. Предоставляя наиболее широкие возможности по работе с оцифрованным звуком, эта группа функций таит в себе немало "подводных камней". Автор довольно много сил приложил к исследованию вопроса оптимального применения этих функций и хотел бы поделиться своими "открытиями" с читателями. Приводимые здесь примеры могут использоваться совершенно свободно, за исключением особо оговоренных случаев. Первая часть открывает небольшую серию статей, посвященных обработке звука в режиме реального времени (это когда время реакции системы на событие строго ограничено и не превышает заранее заданной величины - т.н. "жесткие" системы реального времени). Если Вы рассчитываете увидеть здесь разбор каких-то особенно сложных алгоритмов кодирования или сжатия звука - разочарую Вас. Далее применения быстрого преобразования Фурье пока ничего шибко математического не планируется. Обратите внимание, что вся информация почерпнута из Microsoft Multimedia Programmer's Reference, поэтому всячески рекомендую обращаться туда, тем более, что эти файлы включены в поставку Delphi 3, 4, 5.

В первой части рассматривается использование функций Waveform Audio Win32 API. Автор приводит описания наиболее важных, по его мнению, функций и рассматривает пример реализации программы, записывающей звук в WAV-файл в течение "неограниченного" времени. Приведенный в статье пример реализован на Delphi 4.

Часть 1

Оцифрованный звук может быть представлен самыми различными способами. В числе наиболее широко применяемого способа цифрового представления звука можно отметить формат PCM - pulse code modulation - импульсно-кодовая модуляция. В контексте нашей тематики под этим термином подразумевается такой способ кодирования данных, при котором каждая выборка (отсчет), произведенная аналого-цифровым преобразователем (здесь - в смысле звуковой карты), представляется в памяти в виде числа, пропорционального по своему значению мгновенной величине сигнала в момент выборки. Скорость выборок или, другими словами, частота выполнения отсчетов (частота дискретизации), прямо связана с максимальной частотой поступающего аналогового сигнала. Если сигнал имеет гармоническую природу и ограничен в некотором диапазоне частот, т.е. может быть представлен в виде конечного числа членов ряда Фурье, то для его корректной оцифровки, согласно теореме отсчетов, достаточно иметь частоту дискретизации вдвое превосходящей частоту максимальной гармоники сигнала.

Таким образом, если мы хотим без потери качества производить цифровую запись скажем, телефонного разговора, частота сигнала которого находится в диапазоне 300..3400 Гц, нам вполне достаточно установить частоту дискретизации 8000 отсчетов/сек. Величина 8000 выбрана из соображений совместимости с различными звуковыми картами и драйверами, поскольку для некоторых из них это является наименьшим возможным значением частоты дискретизации сигнала. Если же Вы хотите записывать радиопередачи в диапазоне FM (88 - 108 MHz), то необходимо выбрать частоту дискретизации 12500*2=25000 отсчетов/сек, т.к. звуковой диапазон FM-станции 12.5 килогерц.

Как Вы уже наверное догадались, запись с компакт-диска для сохранения качества нужно производить с частотой дискретизации 44100 выборок/секунду. Замечу, что это вовсе не гарантирует качество звучание записи "как на CD-ROM". Звуковая карта вносит некоторые искажения в любом случае. Как правило, это напрямую связано со стоимостью карты. Более дорогие обычно обеспечивают лучшее качество.

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

Прежде всего, рассмотрим функцию waveInGetNumDevs:


// функция возвращает количество устройств ввода,
// поддерживающих оцифровку звукового сигнала.
// Если функция вернула 0, то таких устройств в системе нет.

function waveInGetNumDevs: UINT; stdcall;

Функция waveInGetDevCaps позволяет получить характеристики указанных устройств.


function waveInGetDevCaps(
  hwi: HWAVEIN;
  lpCaps: PWaveInCaps;
  uSize: UINT
  ): MMRESULT; stdcall;

Здесь:

hwi - идентификатор открытого функцией waveInOpen (см. ниже) устройства или порядковый номер неоткрытого устройства в диапазоне от 0 до значения, возвращаемого функцией waveInGetNumDevs, уменьшенного на 1;

lpCaps - адрес структуры (записи) TWAVEINCAPS; uSize - размер в байтах структуры TWAVEINCAPS.


type
  TWaveInCaps = record
    wMid: Word;
    wPid: Word;
    vDriverVersion: MMVERSION;
    szPname: array[0..MAXPNAMELEN - 1] of AnsiChar;
    dwFormats: DWORD;
    wChannels: Word;
    wReserved1: Word;
  end;

Структура TWAVEINCAPS описывает параметры заданного устройства. Т.е. после вызова функции waveInGetDevCaps поля структуры содержат следующие значения:

wMid: Word - идентификатор производителя;
wPid: Word - идентификатор продукции производителя;
vDriverVersion: MMVERSION - версия драйвера;
szPname: array[0..MAXPNAMELEN-1] of AnsiChar - наименование продукта (строка заканчивается символом с кодом 0);
dwFormats: DWORD - стандартные форматы данных, поддерживаемые устройством:


WAVE_FORMAT_1M08 11.025 kHz, mono, 8-bit
WAVE_FORMAT_1M16 11.025 kHz, mono, 16-bit 
WAVE_FORMAT_1S08 11.025 kHz, stereo, 8-bit 
WAVE_FORMAT_1S16 11.025 kHz, stereo, 16-bit 
WAVE_FORMAT_2M08 22.05 kHz, mono, 8-bit 
WAVE_FORMAT_2M16 22.05 kHz, mono, 16-bit 
WAVE_FORMAT_2S08 22.05 kHz, stereo, 8-bit 
WAVE_FORMAT_2S16 22.05 kHz, stereo, 16-bit 
WAVE_FORMAT_4M08 44.1 kHz, mono, 8-bit 
WAVE_FORMAT_4M16 44.1 kHz, mono, 16-bit 
WAVE_FORMAT_4S08 44.1 kHz, stereo, 8-bit 
WAVE_FORMAT_4S16 44.1 kHz, stereo, 16-bit 

Обратите внимание на то, что подавляющее большинство (если не все) звуковые карты поддерживают промежуточные режимы записи-воспроизведения. Т.е. вполне возможно на карте с максимальной частотой дискретизации 44100 выборок/сек производить запись со скоростью 16000 выборок/сек, хотя это и не сообщается по запросу waveInGetDevCaps.

wChannels: Word - количество входных каналов (1-моно, 2-стерео)
wReserved1: Word - зарезервировано

Функция waveInGetErrorText возвращает текстовое описание возникших в ходе выполнения ошибок.


function waveInGetErrorText(
  mmrError: MMRESULT;
  lpText: PChar;
  uSize: UINT
  ): MMRESULT; stdcall;

mmrError - код ошибки;
lpText - адрес с которого будет размещена нуль-терминированная строка-описание;
uSize - размер участка памяти, на который ссылается lpText;

Ниже приведен пример процедуры, выводящей сведения об устройствах аудиоввода.


uses Windows, MMSystem;
type
  TModeDescr = record
    mode: DWORD; // код режима работы
    descr: string[32]; // словесное описание
  end;

const
  // массив содержит сопоставления режима работы и словесного описания
  modes: array[1..12] of TModeDescr = ((mode: WAVE_FORMAT_1M08; descr:
    '11.025 kHz, mono, 8-bit'),
    (mode: WAVE_FORMAT_1M16; descr: '11.025 kHz, mono, 16-bit'),
    (mode: WAVE_FORMAT_1S08; descr: '11.025 kHz, stereo, 8-bit'),
    (mode: WAVE_FORMAT_1S16; descr: '11.025 kHz, stereo, 16-bit'),
    (mode: WAVE_FORMAT_2M08; descr: '22.05 kHz, mono, 8-bit'),
    (mode: WAVE_FORMAT_2M16; descr: '22.05 kHz, mono, 16-bit'),
    (mode: WAVE_FORMAT_2S08; descr: '22.05 kHz, stereo, 8-bit'),
    (mode: WAVE_FORMAT_2S16; descr: '22.05 kHz, stereo, 16-bit'),
    (mode: WAVE_FORMAT_4M08; descr: '44.1 kHz, mono, 8-bit'),
    (mode: WAVE_FORMAT_4M16; descr: '44.1 kHz, mono, 16-bit'),
    (mode: WAVE_FORMAT_4S08; descr: '44.1 kHz, stereo, 8-bit'),
    (mode: WAVE_FORMAT_4S16; descr: '44.1 kHz, stereo, 16-bit'));

procedure ShowInfo;
var
  WaveNums, i, j: integer;
  WaveInCaps: TWaveInCaps;
    // структура в которую помещается информация об устройстве
begin
  WaveNums := waveInGetNumDevs;
  if WaveNums > 0 then // если в системе есть устройства аудиоввода,то
  begin
    for i := 0 to WaveNums - 1 do
      // получаем характеристики всех имеющихся устройств
    begin
      waveInGetDevCaps(i, @WaveInCaps, sizeof(TWaveInCaps));
      // добавляем наименование устройства
      MainForm.Memo.Lines.Add(PChar(@WaveInCaps.szPname));
      for j := 1 to High(modes) do
      begin
        // выводим поддерживаемые устройством режимы работы
        if (modes[j].mode and WaveInCaps.dwFormats) = modes[j].mode then
          Memo.Lines.Add(modes[j].descr);
      end;
    end;
  end;
end;


Рис 1. Сведения, выводимые процедурой ShowInfo.

Теперь Вы можете определить количество устройств аудиоввода Waveform audio и поддерживаемые ими режимы. Далее рассмотрим еще несколько функций, непосредственно обеспечивающих работу с звуковыми устройствами.

Функция waveInOpen открывает имеющееся устройство ввода Waveform audio для оцифровки сигнала.


function waveInOpen(
  lphWaveIn: PHWAVEIN;
  uDeviceID: UINT;
  lpFormatEx: PWaveFormatEx;
  dwCallback,
  dwInstance,
  dwFlags: DWORD
  ): MMRESULT; stdcall;

Здесь

lphWaveIn - указатель на идентификатор открытого Waveform audio устройства. Идентификатор используется после того, как устройство открыто, в других функциях Waveform audio;

uDeviceID - номер открываемого устройства (см. waveInGetNumDevs). Это может быть также идентификатор уже открытого ранее устройства. Вы можете использовать значение WAVE_MAPPER для того, чтобы функция автоматически выбрала совместимое с требуемым форматом данных устройство;

lpFormatEx - указатель на структуру типа TWaveFormatEx


type
  TWaveFormatEx = packed record
    wFormatTag: Word; { format type }
    nChannels: Word; { number of channels (i.e. mono, stereo, etc.) }
    nSamplesPerSec: DWORD; { sample rate }
    nAvgBytesPerSec: DWORD; { for buffer estimation }
    nBlockAlign: Word; { block size of data }
    wBitsPerSample: Word; { number of bits per sample of mono data }
    cbSize: Word; { the count in bytes of the size of }
  end;

В этой структуре значения полей следующее:

wFormatTag - формат Waveform audio. Мы будем использовать значение WAVE_FORMAT_PCM (это означает импульсно-кодовая модуляция) другие возможные значения смотрите в заголовочном файле MMREG.H;
nChannels - количество каналов. Обычно 1 (моно) или 2(стерео);
nSamplesPerSec - частота дискретизации. Для формата PCM - в классическом смысле, т.е. количество выборок в секунду. Согласно теореме отсчетов должна вдвое превышать частоту оцифровываемого сигнала. Обычно находится в диапазоне от 8000 до 44100 выборок в секунду;
nAvgBytesPerSec - средняя скорость передачи данных. Для PCM равна nSamplesPerSec*nBlockAlign;
nBlockAlign - для PCM равен (nChannels*wBitsPerSample)/8;
wBitsPerSample - количество бит в одной выборке. Для PCM равно 8 или 16;
cbSize - равно 0. Подробности в Microsoft Multimedia Programmer's Reference;

dwCallback - адрес callback-функции, идентификатор окна или потока, вызываемого при наступлении события;
dwInstance - пользовательский параметр в callback-механизме. Сам по себе не используется
dwFlags - флаги для открываемого устройства:


CALLBACK_EVENT dwCallback-параметр - код сообщения (an event handle); 
CALLBACK_FUNCTION dwCallback-параметр - адрес процедуры-обработчика; 
CALLBACK_NULL dwCallback-параметр не используется; 
CALLBACK_THREAD dwCallback-параметр - идентификатор потока команд;   
CALLBACK_WINDOW dwCallback-параметр - идентификатор окна; 
WAVE_FORMAT_DIRECT если указан этот флаг, ACM-драйвер не выполняет преобразование данных; 
WAVE_FORMAT_QUERY функция запрашивает устройство для определения, 
  поддерживает ли оно указанный формат, но не открывает его; 

В случае использование Callback процедуры она имеет следующий вид:


procedure waveInProc(hwi: HWAVEIN; uMsg, dwInstance,
  dwParam1, dwParam2: DWORD); stdcall;
begin
  // что-то делаем
end;

Параметры процедуры имеют следующее значение:
hwi - идентификатор связанного с функцией открытого устройства;
uMsg - Waveform audio сообщение. Может принимать значения:

WIM_CLOSE посылается, когда устройство закрывается функцией waveInClose;
WIM_DATA устройство завершило передачу данных в блок памяти, установленный процедурой waveInAddBuffer;
WIM_OPEN сообщение посылается если устройство открыто функцией waveInOpen;

dwInstance - данные, определенные пользователем при вызове waveInOpen;
dwParam1, dwParam2 - параметры сообщения.

Необходимо заметить, что в Microsoft Multimedia Programmer's Reference написано, что из callback-процедуры нельзя вызывать никаких системных функций кроме: EnterCriticalSection, LeaveCriticalSection, midiOutLongMsg, midiOutShortMsg, OutputDebugString, PostMessage, PostThreadMessage, SetEvent, timeGetSystemTime, timeGetTime, timeKillEvent, и timeSetEvent , поскольку это вызывает deadlock. Я столкнулся с весьма серьезным препятствием из-за этого ограничения, и решил все-таки рискнуть. В ходе небольших экспериментов я выяснил, что данное ограничение не распространяется на группу waveInAddBuffer, waveInReset и waveInClose, и возможно, некоторые другие. Не было проблем и с использованием функций Reset, BlockWrite, BlockRead, CloseFile. Говоря более точно, я так и не обнаружил возникновения deadlock, какие бы функции не вызывал изнутри waveInProc. Самое главное - не инициировать бесконечный рекурсивный вызов waveInProc. Для этого необходимо хорошо продумать обработчики поступающих в waveInProc сообщений. Вообще, рекомендую использовать механизм оконных сообщений вместо callback. Это позволяет избежать ненужных экспериментов и возможной неработоспособности программы в других версиях ОС. Более подробно реализация этого механизма приведена в примере.

Функция waveInPrepareHeader выполняет подготовку буфера для операции загрузки данных:


function waveInPrepareHeader(
  hWaveIn: HWAVEIN;
  lpWaveInHdr: PWaveHdr;
  uSize: UINT
  ): MMRESULT; stdcall;

Здесь:
hWaveIn - идентификатор открытого устройства;
lpWaveInHdr - адрес структуры WaveHdr:


type
  TWaveHdr = record
    lpData: PChar; { pointer to locked data buffer }
    dwBufferLength: DWORD; { length of data buffer }
    dwBytesRecorded: DWORD; { used for input only }
    dwUser: DWORD; { for client's use }
    dwFlags: DWORD; { assorted flags}
    dwLoops: DWORD; { loop control counter }
    lpNext: PWaveHdr; { reserved for driver }
    reserved: DWORD; { reserved for driver }
  end;

lpData - адрес буфера для загрузки данных;
dwBufferLength - длина буфера в байтах;
dwBytesRecorded - для режима загрузки данных определяет количество загруженных в буфер байт;
dwUser - пользовательские данные
dwFlags - флаги. Могут иметь следующие значения:

WHDR_DONE устанавливается драйвером при завершении загрузки буфера данными;
WHDR_PREPARED устанавливается системой. Показывает готовность буфера к загрузке данных;
WHDR_INQUEUE устанавливается системой когда буфер установлен в очередь;

dwLoops - используется только при воспроизведении. При записи звука всегда 0;
lpNext - зарезервировано;
reserved - зарезервировано;
uSize - размер структуры WaveHdr в байтах;

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

Функция waveInAddBuffer ставит в очередь на загрузку данными буфер памяти. Когда буфер заполнен, система уведомляет об этом приложение (см. выше waveInOpen).


function waveInAddBuffer(
  hWaveIn: HWAVEIN;
  lpWaveInHdr: PWaveHdr;
  uSize: UINT
  ): MMRESULT; stdcall;

Здесь:
hWaveIn - идентификатор открытого Waveform audio устройства ввода;
lpWaveInHdr - адрес структуры TWaveHdr;
uSize - размер WaveHdr в байтах;

Функция waveInReset останавливает операцию загрузки данных. Все текущие буферы отмечаются как обработанные и приложение уведомляется о завершении загрузки данных (см. waveInOpen).


function waveInReset(
  hWaveIn: HWAVEIN
  ): MMRESULT; stdcall;

Здесь:
hWaveIn - идентификатор открытого Waveform audio устройства.

Функция waveInClose закрывает открытое устройство ввода:


function waveInClose(
  hWaveIn: HWAVEIN
  ): MMRESULT; stdcall;

hWaveIn - идентификатор открытого устройства;

MMRESULT может принимать следующие значения:


MMSYSERR_NOERROR нет ошибок; 
MMSYSERR_ALLOCATED указанный ресурс уже выделен; 
MMSYSERR_BADDEVICEID указанный идентификатор устройства вне диапазона; 
MMSYSERR_NODRIVER отсутствует драйвер устройства; 
MMSYSERR_NOMEM невозможно выделить или зафиксировать блок памяти;   
WAVERR_BADFORMAT попытка открытия с неподдерживаемым форматом данных; 
MMSYSERR_INVALHANDLE параметром является недопустимый идентификатор; 
WAVERR_STILLPLAYING указанный буфер все еще в очереди; 
WAVERR_UNPREPARED буфер не был подготовлен; 

Пример реализации описанного в статье механизма (Delphi 3) Вы можете скачать здесь (17.7 K)






Copyright © 2004-2016 "Delphi Sources". Delphi World FAQ




Группа ВКонтакте   Ссылка на Twitter   Группа на Facebook