Автор: Илья Великанов

Опубликовано: 14 сентября 2013 г.

Постоянная ссылка

Комментарии [0]

Вывод звука в Windows

Когда приходится осваивать новую область, самое трудное - это первый шаг. Так что, когда я решил разобраться с выводом звука в среде Windows, труднее всего было выбрать - с чего начать. Средства есть самые разные, от простейших функций типа PlaySound до мощных библиотек типа DirectX. Что же лучше всего подойдет для вашего конкретного приложения?


Оригинал статьи был опубликован в журнале «Программист» (№3, 2001 г.)


Наиболее известных «звуковых» Windows-интерфейсов три:
  • Media Control Interface (MCI) – простейшее управление мультимедиа-устройствами
  • Waveform Audio – это набор WinAPI-функций для вывода «волнового» (wave) звука
  • DirectSound (компонент DirectX) – низкоуровневый интерфейс для игр и мультимедийных приложений (требующих особо высокой производительности)

Далее – приведу краткое описание работы с этими интерфейсами (с минимальными примерами на Delphi); постараюсь отметить плюсы и минусы каждой технологии.


Media Control Interface



MCI – это очень высокоуровневый интерфейс; команды посылаются устройствам в виде достаточно общих инструкций – например, текстовых строк:

{проиграть 2-ой трэк; по окончании - известить}
mciSendString("play cdaudio 2 to 3 notify", nil, 0, handle);


Если работать с текстовыми строками неудобно – есть параллельная функция mciSendCommand, принимающая те же параметры «в числовом виде» (соответствующие константы). Интерфейс MCI вполне годится для простейших операций (например, запустить проигрывание avi-файла или CD-трэка), но в целом – довольно ограничен, и вряд ли подходит для серьёзного программирования. Поэтому подробно его рассматривать не будем.


Waveform Audio



Этот интерфейс входит в стандартный Windows API и позволяет воспроизводить программно генерируемые звуки.

Первым делом нужно выбрать устройство, которое будет воспроизводить звук. Функция waveOutGetNumDevs() вернет общее количество таких устройств в системе, а waveOutGetDevCaps() опишет возможности конкретного устройства.

Впрочем, я бы советовал оставить этот выбор на усмотрение пользователя – для этого нужно при вызове функции waveOutOpen() вместо индентификатора устройства указывать флаг WAVE_MAPPER.

Перед началом вывода звука надо открыть устройство: waveOutOpen(), а затем – выводить блоки данных последовательными вызовами waveOutWrite(); причем, если вы начинаете вывод следующего блока, пока ещё не доигрался предыдущий – звук будет непрерывным. Эксперементальным путем я установил, что звук обычно начинает прерываться, если блок не успели вывести более чем за 200 мс до конца предыдущего (эта величина зависит от драйвера звуковой платы). Следовательно, нельзя достичь непрерывности звука, если используются блоки менее чем по 200 мс.

Каждый выводимый блок данных нужно сначала подготовить к воспроизведению функцией waveOutPrepareHeader(), а после того, как он отзвучал – освободить функцией waveOutUnPrepareHeader().

Сколько времени звук воспроизводится – выясняет функция waveOutGetPosition(). Функция waveOutSetVolume() регулирует громкость. Для приостановления вывода звука и его продолжения – есть функции waveOutPause() и waveOutRestart(). Вызов waveOutClose() завершает вывод звука.

Контроль над воспроизведением сводится к оповещению программы в момент окончания проигрывания очередного блока; Windows умеет это делать тремя способами:
  • послать сообщение заданному окну
  • возбудить указанное событие
  • вызвать указанную callback-функцию (типа waveOutProc())

Способ оповещения задаётся в момент открытия устройства функцией waveOutOpen().

Советы:

  • Лучше не использовать функции waveOutSetPitch() и waveOutSetPlaybackRate() – они не поддерживаются большинством звуковых карт.
  • Все функции возвращают значение MMSYSERR_NOERROR в случае успешного выполнения и другие значения в случае ошибки. Всегда проверяйте возвращаемые функциями значения; если что – текстовое описание произошедшей ошибки можно запросить функцией waveOutGetErrorText().
  • Большинство функций waveOutxxx() имеют парные функции waveInxxx(), которые предназначены для записи звука; работа с ними – абсолютно аналогична.

Имейте в виду: функции Waveform Audio «монополизируют» вывод звука, лишая другие приложения этой возможности. Если хотите этого избежать – используйте DirectSound.


DirectSound



DirectSound – очень мощный инструмент работы со звуком. Он позволяет нескольким приложениям одновременно проигрывать на одном звуковом устройстве неограниченное (в разумных пределах) количество звуков разной дискретизации и форматов. К сожалению, нельзя одновременно использовать функции DirectSound и Waveform Audio.

При установке Windows 9x содержит следующие версии DirectX:
  • Windows 95 OSR2 - DirectX 3.0
  • Windows 98 - DirectX 5.0
  • Windows ME - DirectX 7.0

Более новую версию можно скачать прямо с сайта www.microsoft.com. Для серьёзного программирования советую установить DirectX SDK, в котором содержится полная документация и примеры использования интерфейсов.

Работать с DirectSound на Delphi можно с помощью экспорта функций из библиотеки DSOUND.DLL; но я бы посоветовал использовать готовые заголовки к интерфейсам функций, которые легко найти в Интернете (если будете искать - не перепутайте версии DirectX; заголовки могут немного отличаться).

Начать работу следует с создания СОМ-объекта типа «интерфейс DirectSound»:

var DS: IDirectSound;
DirectSoundCreate(NIL, DS, NIL);


После этого обязательно установить приоритет вывода звука:

DS.SetCooperativeLevel(hWnd, DSSCL_PRIORITY);


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

var DSB1, DSB2: IDirectSoundBuffer;
DSBD: TDSBufferDesc;
DS.CreateSoundBuffer(DSBD, DSB1, NIL);
DS.CreateSoundBuffer(DSBD, DSB2, NIL);


Перед заполнением буфера данными нужно заблокировать его функцией Lock(), а после заполнения – разблокировать функцией UnLock(). В отличие от Waveform Audio, вы можете изменять данные в буфере прямо во время его проигрывания. Единственное ограничение: если вы попытаетесь писать в проигрываемую в данный момент позицию (или менее чем в 20 мс после неё) – услышите довольно неприятное «хрюканье». Чтобы записать данные в заведомо ещё не проигранном месте – при вызове функции Lock() устанавливайте флаг DSBLOCK_FROMWRITECURSOR (это вернёт для записи позицию, котора только начнет проигрываться).

Заполнив буфера каналов, можно начинать их проигрывать:

DSB1.Play(0, 0, 0);
DSB2.Play(0, 0, 0);


и, соответственно, останавливать:

DSB1.Stop;
DSB2.Stop;


Освобождение созданных СОМ-объектов произойдет автоматически при выходе из программы; но можно это форсировать конструкцией:

DS := NIL;


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

Теперь про некоторые полезные детали:

Если нужно будет управлять буфером «в процессе», при его создании надо указать дополнительные флаги:
  • для регулировки громкости функцией DSB1.SetVolume() – флаг DSBCAPS_CTRLVOLUME
  • для регулировки частоты воспроизведения функцией DSB1.Frequency() – флаг DSBCAPS_CTRLFREQUENCY
  • и т.д.

Для динамического управления проигрыванием звука есть интерфейс IDirectSoundNotify. С его помощью можно создать в буфере точки, при достижении которых будет возбуждаться определенное событие (не забудьте включить флаг DSBCAPS_CTRLPOSITIONNOTIFY). Тут есть небольшая особенность – событие наступает примерно на 20 мс раньше, чем на самом деле. Это из-за того, что на самом деле событие возбуждается не в момент собственно проигрывания точки, а в момент переноса данных (блоками по 20 мс) на звуковое устройство. Чем это чревато? Если вы установите точку на 43 мс, и по приходу события попросите вызвать Stop, то услышите только 43 - 20 = 23 мс. Будьте бдительны!

Узнать текущую точку проигрывания можно функцией GetCurrentPosition(). Эта функция возвращает реально проигрываемую позицию в буфере; рекомендую устанавливать флаг DSBCAPS_GETCURRENTPOSITION2.

Советы:

  • По умолчанию звук воспроизводится только пока ваше приложение в фокусе. При переключении на другое приложение – проигрывание звука приостановится. Чтобы этого избежать, при создании буфера включайте флаг DSBCAPS_GLOBALFOCUS.
  • Все функции возвращают значение DS_OK в случае успешного выполнения и другие значения в случае ошибки. Всегда проверяйте возвращаемые функциями значения; текстовое описание ошибки – функция DSErrorString().

Имейте в виду:

  • При изменении частоты проигрывания буфера в процессе воспроизведения вы услышите неприятные щелчки.
  • Если вы используете DirectSound, запаздывание выводимых данных не превышает 20 мс (в отличие от 200 мс при Waveform Audio). Т.е. DirectSound позволяет писать приложения, где звук практически синхронен с изображением или любыми программными событиями.
  • Если при выводе звука входная частота дискретизации не равна выходной, происходит сглаживание звуковой кривой (т.е. можно проигрывать звук низкого качества с более высоким, и наоборот).

Вот; для более полного описания – рекомендую обратиться к help'ам по Microsoft SDK и DirectX соответственно...