2. Запись и воспроизведение звука

В этой главе мы расскажем вам о том, как приложения Windows могут записывать и воспроизводить звуковые фрагменты, используя программный интерфейс системы мультимедиа различных уровней. Вы научитесь создавать "звучащие" приложения, и приложения, которые могут записывать звук, познакомитесь со структурой wav-файлов, предназначенных для хранения записанных фонограмм.

Как мы уже говорили, для работы со звуковым адаптером в среде операционной системы Windows вам не потребуется программировать на уровне портов ввода/вывода, прерываний и каналов прямого доступа. Весь необходимый интерфейс (высокого или низкого уровня) предоставляется приложению DLL-библиотекой mmsystem.dll . Эту библиотеку можно рассматривать как расширение Windows для обеспечения возможности работы с мультимедиа.

Библиотека mmsystem.dll поставляется в составе Windows версии 3.1 (версия 3.0 не могла работать с мультимедиа, однако можно было приобрести изделие Microsoft Multimedia Extension, содержащее эту библиотеку и приложения, предназначенные для работы со звуком). Все функции, входящие в библиотеку mmsystem.dll, описаны в файле mmsystem.h , который поставляется со всеми системами разработки приложений для Windows и находится в каталоге include вместе с файлом windows.h.

Что же содержится в библиотеке mmsystem.dll?

В этой библиотеке определены функции двух уровней: функции низкого уровня (Low-Level Functions ) и функции высокого уровня, представляющих собой интерфейс управления средой MCI (Media Control Interface ).

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

Функции интерфейса MCI работают с драйверами устройств MCI (например, драйверами устройств чтения компакт-дисков или лазерных видеодисков) и вызывают функции низкого уровня.

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

Что же касается драйверов звуковых устройств, то можно выделить четыре типа таких драйверов.

Драйвер для ввода звука (Waveform Input Driver )

Драйвер для вывода звука (Waveform Output Driver )

Драйвер для ввода музыки в стандарте MIDI (MIDI Input Driver )

Драйвер для вывода музыки в стандарте MIDI (MIDI Output Driver )

Все эти драйверы поставляются вместе со звуковым адаптером и устанавливаются после установки операционной системы Windows. В зависимости от типа звукового адаптера состав драйверов может изменяться (например, могут отсутствовать драйверы для работы с MIDI).

Вы можете также приобрести звуковой драйвер для работы с динамиком, встроенным в корпус компьютера (Sound Driver for PC Speaker ). В комплект поставки входят два файла - speaker.drv и oemsetup.inf . Этот драйвер можно найти в библиотеке дополнительных драйверов для Windows, которая называется Windows Driver Library , или на одной из электронных досок объявлений BBS.

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

2.1. Самые простые способы воспроизведения звука

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

Функция MessageBeep

Ранее, в предыдущих томах "Библиотеки системного программиста", мы упоминали функцию MessageBeep :

void MessageBeep(UINT uAlert);

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

Если же звуковой драйвер установлен, в зависимости от значения параметра uAlert функция MessageBeep может воспроизводить один из звуковых фрагментов, записанных в wav-файле.

Обратите внимание, что в файле win.ini имеется раздел [sound], в котором перечислены различные ситуации. Для ситуации может быть указано имя wav-файла, который должен быть воспроизведен при ее возникновении:

[sounds]
SystemAsterisk=chimes.wav,Asterisk
SystemHand=ding.wav,Critical Stop
SystemDefault=,Default Beep
SystemExclamation=ding.wav,Exclamation
SystemQuestion=ding.wav,Question
SystemExit=bye.wav,Windows Exit
SystemStart=,Windows Start

У вас нет необходимости изменять этот раздел вручную, так как это можно сделать при помощи приложения Control Panel (рис. 1.8).

Возможные значения параметра uAlert для функции MessageBeep приведены в следующей таблице.

Значение Описание
-1 Стандартный звуковой сигнал, который выдается на встроенный в компьютер динамик
MB_ICONASTERISK Проигрывается wav-файл, определенный в строке SystemAsterisk раздела [sound] файла win.ini
MB_ICONEXLAMATION Аналогично для строки SystemExclamation
MB_ICONHAND Аналогично для строки SystemHand
MB_ICONQUESTION Аналогично для строки SystemQuestion
MB_OK Аналогично для строки SystemDefault

Функция MessageBeep пытается проиграть звуковой фрагмент в асинхронном (фоновом) режиме, если это позволяет звуковой драйвер. Если в системе установлен драйвер Sound Driver for PC Speaker, функция MessageBeep возвращает управление только после того, как проигрывание будет закончено. Если же функция не может проиграть нужный фрагмент, будет "исполнен" стандартный системный звук, определенный в строке SystemDefault раздела [sound] файла win.ini. Если же и это невозможно, вы услышите "бип" из встроенного в компьютер динамика.

Функция sndPlaySound

А есть ли простой способ проигрывания произвольного wav-файла?

Есть, и он действительно прост. Этот способ основан на использовании функции sndPlaySound , которая находится в библиотеке mmsystem.dll. Ее прототип определен в файле mmsystem.h:

BOOL sndPlaySound(LPCSTR lpszSoundFile, UINT wFlags);

Через параметр lpszSoundFile этой функции можно передать путь к wav-файлу, идентификатор ресурса, содержащего звуковой фрагмент (вы можете записать звуковой фрагмент в ресурсы приложения), или текстовую строку, определенную в разделе [sound] файла win.ini.

Параметр wFlags определяет способ проигрывания звукового фрагмента. Используются следующие значения (некоторые из них можно комбинировать при помощи операции ИЛИ):

Значение Описание
SND_SYNC Синхронный режим работы. Функция sndPlaySound вернет управление только после завершения проигрывания звукового фрагмента
SND_ASYNC Асинхронный режим работы. Функция вернет управление немедленно, проигрывание звукового фрагмента будет выполняться в фоновом режиме параллельно с работой приложения
SND_NODEFAULT Если указанный файл не найден, функция "тихо" возвращает управление, не проигрывая никаких звуков. Если же этот флаг не указан, и файл не найден, будет проигран стандартный системный звук, определенный в строке SystemDefault раздела [sound] файла win.ini. А если и это невозможно, функция не будет ничего проигрывать и вернет значение FALSE
SND_MEMORY Это значение используется для проигрывания звуковых файлов, загруженных в оперативную память, например, из ресурсов приложения
SND_LOOP Если указано значение SND_ASYNC, проигрывание звукового фрагмента будет зациклено. Для того чтобы остановить проигрывание, необходимо вызвать функцию sndPlaySound, указав ей в качестве параметра lpszSoundFile значение NULL
SND_NOSTOP При указании этого значения функция проверяет, выполняется ли в настоящий момент проигрывание фрагмента. Если да, функция возвращает значение FALSE

Во всех случаях, если не указан параметр SND_NOSTOP, функция sndPlaySound возвращает значение TRUE, если выполняется проигрывание, и FALSE - если нет. Учтите, что при использовании функций MessageBeep и sndPlaySound есть ограничение на размер wav-файла - он должен целиком помещаться в физическую память. Поэтому самые простые способы проигрывания звуковых фрагментов хороши только для относительно небольших файлов.

Приложение SNDPLAY

Наше первое приложение SNDPLAY, имеющее зачатки мультимедиа, предназначено для демонстрации различных способов работы с функцией sndPlaySound (листинг 2.1).

Листинг 2.1. Файл sndplay\sndplay.cpp

// ----------------------------------------
// Использование функций
// MessageBeep и sndPlaySound
// ----------------------------------------
#define STRICT
#include <windows.h>
#include <mmsystem.h>

#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR lpszCmdLine, int nCmdShow)
{
   HANDLE hWaveRes, hRes;
   LPSTR lpRes;
   BOOL rc;
   HFILE hf;
   DWORD dwFileSize;
   HGLOBAL hWave;
   char huge *lpBuf;

   // Проигрываем звук, соответствующий строке
   // SystemQuestion раздела [sound] файла win.ini
   MessageBeep(MB_ICONQUESTION);

   MessageBox(NULL, "Начнем, что ли?",
     "SndPlay", MB_OK | MB_ICONQUESTION);

   // Проигрываем файл sndplay.snd в синхронном режиме
   rc = sndPlaySound((LPSTR)"sndplay.wav", SND_SYNC);
   if(!rc)
   {
     MessageBeep(MB_ICONHAND);
     MessageBox(NULL, "Не могу проиграть файл sndplay.wav",
       "SndPlay", MB_OK | MB_ICONHAND);
     return -1;
   }

   // Загружаем звуковой фрагмент из ресурсов приложения
   // и проигрываем его

   // Находим нужный ресурс
   hWaveRes = FindResource(hInstance, "APP_SOUND", "WAVE");
   if(hWaveRes)
   {
     // Загружаем ресурс в память
     hRes = LoadResource(hInstance, (HRSRC)hWaveRes);
     if(hRes)
     {
       // Фиксируем ресурс в памяти, получая
       // указатель на данные
       lpRes = (LPSTR)LockResource(hRes);
       if(lpRes)
       {
         // Проигрываем звук в цикле
         rc = sndPlaySound(lpRes, SND_MEMORY | SND_ASYNC | SND_LOOP);

         MessageBox(NULL,
           "Для завершения нажмите кнопку OK",
           "SndPlay", MB_OK | MB_ICONINFORMATION);

         // Останавливаем проигрывание
         sndPlaySound(NULL, 0);

         // Расфиксируем и освобождаем ресурс
         UnlockResource(hRes);
         FreeResource(hRes);

         // Загружаем звуковой фрагмент непосредственно из
         // wav-файла в память и проигрываем его

         // Открываем wav-файл
         hf = _lopen((LPSTR)"uff.wav", OF_READ);

         // Определяем размер файла
         dwFileSize = _llseek(hf, 0l, 2);
         _llseek(hf, 0l, 0);

         // Заказываем глобальный блок памяти,
         // размер которого равен длине файла
         hWave = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, dwFileSize);

         // Фиксируем блок памяти
         lpBuf = (char huge *)GlobalLock(hWave);
         if(lpBuf != NULL)
         {
           // Читаем файл в полученный блок памяти
           _hread(hf, lpBuf, dwFileSize);

           // Проигрываем звуковой фрагмент, загруженный в память
           rc = sndPlaySound((LPCSTR)lpBuf, SND_MEMORY | SND_SYNC);
           if(!rc)
           {
             MessageBeep(MB_ICONHAND);
             MessageBox(NULL, "Не могу проиграть файл uff.wav",
               "SndPlay", MB_OK | MB_ICONHAND);
           }

           // Расфиксируем и освобождаем память
           GlobalUnlock(hWave);
           GlobalFree(hWave);

           // Закрываем файл
           _lclose(hf);
         }
       }
     }
   }
   return 0;
}

Приложение не имеет главного окна и функции окна. Сразу после запуска приложение SNDPLAY вызывает функцию MessageBeep, с помощью которой проигрывается звук, соответствующий строке SystemQuestion раздела [sound] файла win.ini.

Затем приложение вызывает функцию sndPlaySound для проигрывания файла sndplay.wav в синхронном режиме:

rc = sndPlaySound((LPSTR)"sndplay.wav", SND_SYNC);

Как только этот файл будет проигран, функция sndPlaySound вернет управление и работа приложения будет продолжена.

Далее приложение загружает звуковой фрагмент из ресурсов и проигрывает его асинхронно в циклическом режиме. При этом на экран выводится диалоговая панель с сообщением о то, что для прекращения циклического проигрывания следует нажать кнопку OK. Методика работы с ресурсами была описана нами в 12 томе "Библиотеки системного программиста". После поиска и фиксирования ресурса адрес соответствующего блока памяти передается в качестве первого параметра функции sndPlaySound:

rc = sndPlaySound(lpRes, SND_MEMORY | SND_ASYNC | SND_LOOP);

Так как звуковой файл находится в памяти, во втором параметре этой функции необходимо указать флаги SND_MEMORY и SND_ASYNC. Для циклического проигрывания следует также указать флаг SND_LOOP.

Для прерывания циклического проигрывания функция sndPlaySound вызывается с нулевыми параметрами:

sndPlaySound(NULL, 0);

В некоторых случаях может оказаться удобным проигрывать звуковой фрагмент, загруженный в память не из ресурсов приложения, а непосредственно из wav-файла. Финальная часть приложения SNDPLAY демонстрирует этот способ.

Вначале приложение открывает wav-файл с помощью функции _lopen и определяет его размер, вызывая функцию _llseek :

hf = _lopen((LPSTR)"uff.wav", OF_READ);
dwFileSize = _llseek(hf, 0l, 2);
_llseek(hf, 0l, 0);

Далее приложение заказывает глобальный блок памяти такого размера, чтобы в нем мог поместиться весь wav-файл. Блок фиксируется в памяти:

hWave = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, dwFileSize);
lpBuf = (char huge *)GlobalLock(hWave);

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

rc = sndPlaySound((LPCSTR)lpBuf, SND_MEMORY | SND_SYNC);

Только после того, как проигрывание закончено, можно расфиксировать и освободить память с образом wav-файла:

GlobalUnlock(hWave);
GlobalFree(hWave);

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

Файл ресурсов приложения (листинг 2.2) содержит описание ресурса типа WAVE (можно использовать любой другой нестандартный тип ресурса):

Листинг 2.2. Файл sndplay\sndplay.rc

APP_SOUND WAVE loop.wav
APPICON ICON "sndplay.ico"

Файл определения модуля приложения SNDPLAY приведен в листинге 2.3.

Листинг 2.3. Файл sndplay\sndplay.def

NAME        SNDPLAY
DESCRIPTION 'Приложение SNDPLAY, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   5120
HEAPSIZE    1024
CODE        preload moveable discardable
DATA        preload moveable multiple

2.2. Интерфейс управляющих строк MCI

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

Все функции интерфейса MCI экспортируются библиотекой mmsystem.dll . Эти функции обращаются непосредственно к драйверам устройств ввода/вывода, а также к функциям более низкого уровня, определенным в библиотеке mmsystem.dll.

Приложения могут использовать два типа программного интерфейса MCI .

Первый тип называется интерфейс управляющих строк (Command-String Interface ). Он основан на использовании текстовых команд (таких, как open, play, close).

Второй тип - это интерфейс управляющих сообщений (Command-Message Interface ). Для управления устройствам посылаются сообщения, коды которых определены в файле mmsystem.h через символические константы (например, MCI_OPEN, MCI_PLAY, MCI_CLOSE).

Интерфейс управляющих строк удобен для использования в системах программирования высокого уровня, так как позволяет быстро получить необходимый результат. Например, для проигрывания звукового файла ding.wav достаточно передать звуковому адаптеру следующую последовательность управляющих строк:

open ding.wav type waveaudio alias snd wait
play snd wait
close snd

Не намного сложнее выглядят управляющие строки (команды) для записи звукового фрагмента в wav-файл или для проигрывания видеофрагмента из avi-файла.

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

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

Мы рассмотрим основные приемы использования обоих типов интерфейса MCI на примере работы со звуковым адаптером и wav-файлами. В дальнейшем, при описании работы с устройствами других типов, мы ограничимся только особенностями команд MCI этих устройств.

Использование интерфейса управляющих строк

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

Прежде чем работать с устройством средствами MCI, его следует открыть при помощи команды open. Далее при необходимости можно задать режим работы устройства, послав ему команду set с параметрами. Для включения режима проигрывания или записи используются, соответственно, команды play и record . В любой момент времени можно узнать состояние устройства, если послать ему команду status . После использования устройства его необходимо закрыть при помощи команды close .

Команды MCI

Какие бывают команды?

Все команды можно разделить на четыре группы : системные (System), обязательные (Required), базовые (Basic) и расширенные (Extended).

Системные команды не передаются драйверу устройства, они обрабатываются непосредственно системой MCI.

Обязательные команды поддерживаются любыми устройствами. Примером таких команд могут послужить строки open и close .

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

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

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

Команда Тип Описание
break Системная Назначение виртуального кода клавиши, с помощью которой можно прервать работу устройства.
sysinfo - Получение системной информации об устройстве (в виде текстовой строки)
capability Обязательная Определение возможностей устройства
close - Закрывание устройства
info - Получение текстовой информации об устройстве
open - Открывание устройства
status - Определение состояния устройства
load Базовая Загрузка данных из файла
pause - Пауза при проигрывании
play - Включение режима проигрывания
record - Включение режима записи
resume - Продолжение проигрывания после паузы
save - Сохранение данных в файле
seek - Позиционирование
set - Установка режима работы устройства
stop - Останов проигрывания
cue Расширенная Подготовка устройства для проигрывания или записи
delete - Удаление фрагмента данных

Далее мы расскажем об использовании этих команд применительно к звуковому адаптеру. Особенности других устройств мультимедиа будут рассмотрены позже.

Функция mciSendString

Как передать устройству управляющую строку?

Очень просто - для этого можно воспользоваться функцией mciSendString . Прототип функции mciSendString находится в файле mmsystem.h:

Функция mciSendString
DWORD mciSendString(
  LPCSTR lpstrCommand,     // управляющая строка
  LPSTR lpstrReturnString, // буфер для результата
  UINT wReturnLength,      // размер буфера
  HANDLE hCallback)        // идентификатор окна извещения

Параметры функции:

lpstrCommand

Дальний указатель на текстовую управляющую строку

lpstrReturnString

Указатель на буфер, в который будет записан результат выполнения команды (в текстовом виде). Этот параметр можно указать как NULL, если приложение не интересуется результатом выполнения команды

wReturnLength

Размер буфера для записи результата выполнения команды

hCallback

Идентификатор окна, которое получит извещение (сообщение MM_MCINOTIFY) после того как устройство завершит операцию. Этот параметр можно указать как NULL, если извещение не используется

Возвращаемое значение:

Нуль при успешном завершении или код ошибки (в младшем слове возвращаемого значения):

MCIERR_BAD_CONSTANT 

Указана константа, неправильная для данной команды

MCIERR_BAD_INTEGER 

Указано значение, неправильное для данной команды

MCIERR_DUPLICATE_FLAGS 

Двойное определение параметра или значения

MCIERR_MISSING_COMMAND_STRING 

Не указана управляющая строка

MCIERR_MISSING_DEVICE_NAME 

Не указано имя устройства, драйвера, файла или алиас

MCIERR_MISSING_STRING_ARGUMENT 

Не указан обязательный параметр команды

MCIERR_NEW_REQUIRED_ALIAS 

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

MCIERR_NO_CLOSING_QUOTE 

В команде отсутствуют закрывающие двойные кавычки

MCIERR_NOTIFY_ON_AUTO_OPEN 

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

MCIERR_PARAM_OVERFLOW 

Строка параметров не помещается в буфер. Необходимо увеличить размер буфера

MCIERR_PARSER_INTERNAL 

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

MCIERR_UNRECOGNIZED_KEYWORD 

Драйвер не распознал параметр управляющей строки

Например, для проигрывания wav-файла вы можете использовать следующую последовательность вызовов функции mciSendString :

mciSendString(
  (LPSTR)"open ding.wav type waveaudio alias snd wait",
  (LPSTR)szBuf, 256, NULL);
mciSendString((LPSTR)"play snd wait", (LPSTR)szBuf, 256, NULL);
mciSendString((LPSTR)"close snd", (LPSTR)szBuf, 256, NULL);

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

Функция mciGetErrorString
UINT mciGetErrorString(
   DWORD dwError,      // код ошибки
   LPSTR lpstrBuffer,  // буфер для записи текстовой строки
   UINT  wLength);     // размер буфера

Параметры функции:

dwError

Код ошибки, полученный от функции mciSendString или mciSendCommand (функция mciSendCommand предназначена для передачи управляющих сообщений, она будет рассмотрена позже)

lpstrBuffer

Буфер, в который будет записано текстовое описание ошибки

wLength

Размер буфера в байтах

Возвращаемое значение:

TRUE при успешном завершении или FALSE, если переданному коду ошибки не соответствует ни одно текстовое описание

Сообщение MM_MCINOTIFY

Немного о сообщении MM_MCINOTIFY .

Как мы уже говорили, приложение может передать функции mciSendString через последний параметр идентификатор окна. Если команда MCI выдана с параметром notify, после ее завершения функция окна получит сообщение MM_MCINOTIFY. Это сообщение - извещение о завершении (удачном или нет) процесса выполнения команды.

Через параметр wParam сообщения MM_MCINOTIFY функция окна получает код извещения, по которому можно судить о результатах выполнения команды. Возможны следующие значения (описанные в файле mmsystem.h):

Значение Описание
MCI_NOTIFY_ABORTED Устройство получило такую команду, в результате которой не будет получено извещение о завершении выполнения предыдущей команды. Если новая команда прерывает выполнение текущей команды и также требует извещения, функция окна получит сообщение MCI_NOTIFY_ABORTED (но не MCI_NOTIFY_SUPERSEDED)
MCI_NOTIFY_SUCCESSFUL Успешное завершение команды
MCI_NOTIFY_SUPERSEDED Устройство получило еще одну команду, так же требующую извещения, в результате чего извещение от первой команды не будет получено
MCI_NOTIFY_FAILURE В устройстве произошла ошибка во время выполнения команды

Параметр lParam содержит идентификатор устройства, приславшего извещение.

В случае успешного завершения обработчик сообщения MM_MCINOTIFY должен вернуть нулевое значение, при ошибке - соответствующий код ошибки MCI.

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

Команды инициализации и завершения работы

В этой группе всего две команды - open и close, предназначенные, соответственно, для открытия и закрытия устройства ввода/вывода звука.

open

Управляющая строка open посылается перед началом работы для открытия устройства. Эта строка имеет следующий формат:

open device [type device_name] [alias alias] [buffer size] [notify] [wait]

Параметры, указанные в квадратных скобках, необязательные.

В качестве параметра device можно указывать имя драйвера устройства, имя самого устройства или имя звукового файла (можно также указать полный путь к файлу). Так как имя драйвера зависит от устройства, лучше пользоваться именем устройства или именем файла. Для звукового адаптера можно указать устройство waveaudio :

open waveaudio

Это устройство обслуживается драйвером mciwave.drv, входящим в состав операционной системы Windows 3.1. Интерфейс управляющих строк MCI непригоден для работы с драйвером Sound Driver for PC Speaker , поэтому, если в системе установлен только такой драйвер, пользуйтесь функциями MessageBeep или sndPlaySound, рассмотренными нами ранее.

Если при открытии устройства указывается путь к файлу, тип устройства определяется по расширению имени с использованием раздела [mci extensions] файла win.ini:

[mci extensions]
wav=waveaudio
mid=sequencer
rmi=sequencer
avi=AVIVideo

Поэтому следующая командная строка приведет к открытию устройства waveaudio:

open c:\wave\bye.wav

Если через параметр device передается имя файла, можно указать тип устройства при помощи параметра type device_name. Например:

open c:\wave\bye.wav type waveaudio

Это позволит использовать имена файлов с нестандартными расширениями. Например, вы можете переименовать файл bye.wav в файл bye.snd, при этом несмотря на то, что в разделе [mci extensions] файла win.ini расширение snd не описано, результат выполнения следующей команды будет правильный:

open c:\wave\bye.snd type waveaudio

Вы можете также указать алиас (альтернативное имя) для работы с устройством, использовав параметр alias:

open c:\wave\bye.wav alias sound

Параметр buffersize size задает размер буфера, который используется драйвером звукового адаптера (в секундах звучания).

Если указан параметр notify, и при передаче строки в последнем параметре функции mciSendString был указан идентификатор окна для оповещения, после того как устройство будет открыто, функция этого окна получит сообщение MM_MCINOTIFY.

С помощью команды open можно открыть устройство не только на воспроизведение, но и на запись. При этом в качестве параметра device нужно указать строку new. Следует также указать алиас. В качестве примера приведем последовательность команд, выполняющих запись:

open new type waveaudio alias nsound wait
record nsound

Для остановки записи следует выдать команду stop. Для сохранения записанного фрагмента в wav-файле нужно использовать команду save (команда close закрывает устройство, она будет описана ниже):

stop nsound wait
save nsound newsound.wav wait
close nsound

Если указан параметр wait, функция mciSendString вернет управление только после завершения операции. Заметим, что параметры notify и wait используются практически со всеми управляющими строками.

close

Команда close закрывает устройство и освобождает все связанные с ним ресурсы. Формат команды:

close device_id [notify] [wait]

Необходимо указать идентификатор устройства device_id, использованный при открытии устройства командой open.

Например, для того чтобы закрыть устройство с алиасом nsound, вы можете использовать следующую управляющую строку:

close nsound

Справочные команды

Эта группа команд предназначена для получения различной справочной информации об устройстве. Перед использованием справочных команд следует открыть устройство командой open.

sysinfo

Команда sysinfo предназначена для получения системной информации об MCI-устройстве:

sysinfo device_id parameter [notify] [wait]

В качестве параметра parameter можно указывать одну из следующих строк:

installname

Имя, использованное в файле system.ini при установке драйвера устройства

quantity

Количество MCI-устройств типа device_id, установленных в системе и указанных в файле system.ini. Если в качестве device_id указать строку all, будет подсчитано общее количество установленных в системе MCI-драйверов

quantity open

Количество открытых MCI-устройств типа device_id, установленных в системе и указанных в файле system.ini

name index

Имя устройства MCI, номер которого задан строкой index. Первому устройству соответствует строка 1

name index open

Имя открытого устройства MCI, номер которого задан строкой index

info

С помощью команды info можно получить информацию об MCI-устройстве:

info device_id parameter [notify] [wait]

В качестве параметра parameter можно указывать одну из следующих строк:

product

Текстовое описание звукового адаптера

input

Текстовое описание устройства ввода звуковой информации

output

Текстовое описание устройства вывода звуковой информации

file

Имя текущего wav-файла

capability

С помощью команды capability приложение может определить возможности устройства:

capability device_id parameter [notify] [wait]

В качестве параметра parameter можно указывать одну из следующих строк:

can play

Если драйвер звукового адаптера может проигрывать wav-файлы, в ответ на эту строку он возвратит строку true, а если нет - то false

can record

Если устройство может записывать, возвращается true, в противном случае - false

can save

Используется для определения возможности сохранения записанного звукового фрагмента в wav-файле. Если такая возможность есть, возвращается строка true, в противном случае - false

compound device

Все MCI-устройства можно разделить на простые и составные (compound). Простые устройства, такие как проигрыватель звуковых компакт-дисков или лазерных видеодисков, не работают с файлами. Составные, такие как звуковой адаптер, используют файлы. Поэтому в ответ на эту строку, переданную драйверу звукового адаптера, приложение получит строку true

device type

Для звукового адаптера возвращается строка waveaudio

has audio

Для звукового адаптера возвращается строка true

inputs

Общее количество устройств ввода

outputs

Общее количество устройств вывода

uses files

Для звукового адаптера возвращается строка true, так как он работает с wav-файлами

status

Команда status позволяет определить текущее состояние устройства.

status device_id parameter [notify] [wait]

В качестве параметра parameter можно указывать одну из следующих строк:

alignment

Выравнивание блока данных в байтах

bitspersample

Количество байт на одну выборку сигнала

bytespersec

Скорость проигрывания или записи, байт в секунду

channels

Количество каналов, 1 - моно, 2 - стерео

current track

Номер текущей дорожки. Для звукового адаптера всегда равно 1

format tag

Тег формата

input

Устройство ввода

length

Общая длина звукового фрагмента

length track track_number

Длина фрагмента, соответствующая заданной дорожке

level

Текущий уровень звукового сигнала

media present

Признак присутствия носителя (среды). Для звукового адаптера всегда равно true

mode

Текущий режим работы: not ready (не готов), playing (проигрывание), stopped (останов), recording (запись), seeking (позиционирование)

number of tracks

Количество дорожек. Для звукового адаптера всегда равно 1

output

Устройство вывода

position

Текущая позиция

position track track_number

Текущая позиция на заданной дорожке. Для звукового адаптера всегда равно 0

ready

Если устройство готово, возвращается строка true

samplespersec

Количество выборок сигнала в секунду при проигрывании или записи (частота дискретизации)

start position

Начальная позиция

time format

Текущий формат времени

Команды установки режима работы

В этой группе есть две команды - set и break.

set

Команда set в зависимости от параметров позволяет выполнять установку различных режимов работы.

set device_id parameter [notify] [wait] 

В качестве параметра parameter можно указывать одну из следующих строк (за один раз можно указывать сразу несколько параметров):

alignment int

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

any input

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

any output

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

audio all off

Отключение звукового выхода

audio all on

Включение звукового выхода

audio left off

Отключение левого канала

audio left on

Включение левого канала

audio right off

Отключение правого канала

audio right on

Включение правого канала

bitspersample bit_count

Установка количества бит для представления выборки сигнала. Параметр bit_count задает количество бит (8 или 16)

bytespersec byte_rate

Установка частоты дискретизации при записи или воспроизведении. Параметр byte_rate задает частоту (байты в секунду)

channels channel_count

Установка количества каналов для записи или воспроизведения (1 - монофонический режим, 2 - стереофонический режим)

format tag tag

Установка типа формата

format tag pcm

Установка формата PCM (импульсно-кодовая модуляция)

input int

Выбор канала для ввода

output int

Выбор канала для вывода

samplepersec int

Установка скорости записи или воспроизведения

time format bytes

В качестве единицы измерения при позиционировании используются байты блока звуковых данных

time format milliseconds

В качестве единицы измерения при позиционировании используются миллисекунды. Строку milliseconds можно также указывать как ms

time format samples

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

break

Команда break позволяет определить код виртуальной клавиши, предназначенной для прерывания (по умолчанию используется комбинация клавиш <Control+Break>):

break device_id parameter [notify] [wait] 

В качестве параметра parameter можно указывать одну из следующих строк:

on virt_key

Для прерывания будет использована клавиша с виртуальным кодом virt_key

off

Действие клавиши прерывания отменяется

Если команда выдана с параметром wait, функция mciSendString вернет управление только после завершения операции. Если пользователь не желает дожидаться завершения длительной операции, он может нажать клавишу, прерывающую выполнение команды.

Команды для воспроизведения, записи и позиционирования

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

play

Команда play предназначена для запуска процесса воспроизведения. Она имеет следующий формат:

play device_id [from position [to position]] [notify] [wait] 

Идентификатор устройства device_id создается при открытии устройства командой open. Например, если для открытия устройства была использована строка

open c:\windows\ding.wav alias ding

то в качестве параметра device_id можно использовать алиас ding:

play ding

Если не указан параметр from position, проигрывание начинается с текущей позиции. Сразу после открытия текущая позиция устанавливается в начало файла. Параметр to position позволяет указать конечную позицию, при достижении которой проигрывание прекращается. Перед использованием параметров from и to необходимо установить формат для позиционирования при помощи команды set (см. ниже).

Если задан параметр notify, и при передаче строки в последнем параметре функции mciSendString был указан идентификатор окна для оповещения, после завершения операции проигрывания функция этого окна получит сообщение MM_MCINOTIFY. Обработчик этого сообщения может закрыть устройство или выполнить повторное проигрывание фрагмента, например, с самого начала.

Указав параметр wait, можно установить псевдосинхронный режим воспроизведения. В этом случае функция mciSendString вернет управление только после завершения операции, однако работа других приложений Windows будет продолжаться во время воспроизведения. Если же этот параметр не указан, функция mciSendString запустит процесс в асинхронном режиме и немедленно вернет управление.

record

Эта команда запускает запись звукового фрагмента.

В качестве необязательного параметра parameter можно указывать одну из следующих строк:

insert

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

from position

Определение начальной позиции для записи. Если начальная позиция не задана, данные будут вставлены начиная с текущей позиции. Если используются параметры from или to, необходимо задать формат времени командой set time format

to position

Определение конечной позиции при записи. Если конечная позиция не задана, запись будет продолжаться до тех пор, пока не будет выдана команда stop или pause

overwrite

Новые данные должны заместить записанные ранее

save

Сохранение записанного звукового фрагмента в файле

save device_id [filename] [notify] [wait] 

Параметр filename задает путь к файлу, в который должен быть записан звуковой фрагмент

pause

Временный останов при воспроизведении или записи (пауза)

pause device_id

resume

Продолжение воспроизведения или записи после временного останова по команде pause.

resume device_id [notify] [wait] 

seek

Позиционирование с последующим остановом. Перед использованием этой команды необходимо задать формат времени командой set time format.

seek device_id parameter [notify] [wait] 

В качестве необязательного параметра parameter можно указывать одну из следующих строк:

to position

Позиционирование в указанное место фрагмента

to start

Позиционирование в начало

to end

Позиционирование в конец

cue

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

В качестве параметра parameter можно указывать одну из следующих строк:

input

Подготовка для записи

output

Подготовка для воспроизведения

delete

Удаление сегмента из фрагмента звуковых данных

delete device_id from position [to position] [notify] [wait] 

Для этой команды можно указать либо оба параметра (from и to), либо только параметр from.

Приложение MCISTRVW

Приложение MCISTRVW (листинг 2.4) демонстрирует использование строчного интерфейса MCI для воспроизведения звукового файла с именем kaas.wav, расположенного в текущем каталоге (файл kaas.wav есть в каталоге mcistrvw на дискете, которая продается вместе с книгой). Это простейшее приложение не создает ни одного окна и, следовательно, не обрабатывает сообщения.

Листинг 2.4. Файл mcistrvw\mcistrvw.cpp

// -----------------------------------------------------
// Приложение MCISTRVW
// Демонстрирует использование командных строк MCI
// -----------------------------------------------------
#define STRICT
#include <windows.h>
#include <mmsystem.h>

void mciwioError(DWORD dwrc);

#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance, 
        HINSTANCE hPrevInstance,
        LPSTR     lpszCmdLine, 
        int       nCmdShow)
{
  DWORD dwrc;
  BYTE szBuf[256], szBuf1[256];

  // Открываем файл kaas.wav
  dwrc = mciSendString(
    (LPSTR)"open kaas.wav type waveaudio alias patr wait",
    (LPSTR)szBuf, 256, NULL);
  if(dwrc) mciwioError(dwrc);

  // Получаем имя устройства, под которым оно установлено
  // в файле system.ini
  dwrc = mciSendString((LPSTR)"sysinfo patr installname wait",
    (LPSTR)szBuf, 256, NULL);
  if(dwrc) mciwioError(dwrc);

  lstrcat(szBuf, (LPSTR)"\n");

  // Добавляем к нему текстовое описание аппаратуры
  dwrc = mciSendString((LPSTR)"info patr product wait",
    (LPSTR)szBuf1, 256, NULL);
  if(dwrc) mciwioError(dwrc);
  lstrcat(szBuf, szBuf1);

  // Выводим на экран полученную информацию об устройстве
  MessageBox(NULL, szBuf, "MCISTRWV", MB_ICONINFORMATION);

  // Запускаем проигрывание в синхронном режиме
  dwrc = mciSendString((LPSTR)"play patr wait",
    (LPSTR)szBuf, 256, NULL);
  if(dwrc) mciwioError(dwrc);

  // После завершения проигрывания закрываем устройство 
  dwrc = mciSendString((LPSTR)"close patr",
    (LPSTR)szBuf, 256, NULL);
  if(dwrc) mciwioError(dwrc);

  return 0;
}

// -----------------------------------------------------
// Функция mciwioError
// Выводит текстовое описание ошибки
// -----------------------------------------------------
void mciwioError(DWORD dwrc)
{
  BYTE szBuf[MAXERRORLENGTH];

  // Если коду ошибки, переданному через параметр dwrc
  // соответствует текстовое описание, выводим его на экран 
  if(mciGetErrorString(dwrc, (LPSTR)szBuf, MAXERRORLENGTH))
    MessageBox(NULL, szBuf, "MCISTRVW Error",
      MB_ICONEXCLAMATION);

  // В противном случае выводим сообщение о том, что это
  // неизвестная ошибка
  else
    MessageBox(NULL, "Неизвестная ошибка", "MCISTRVW Error",
      MB_ICONEXCLAMATION);
}

Сразу после запуска приложение открывает устройство waveaudio с файлом kaas.wav, передавая ему следующую команду:

open kaas.wav type waveaudio alias patr wait

При этом устройству назначается алиас patr. Так как задан параметр wait, работа приложения будет продолжена только после завершения процесса открытия.

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

Для проигрывания выдается следующая команда (используется алиас, назначенный при открытии устройства):

play patr wait

Работа приложения MCISTRVW приостанавливается до завершения процесса воспроизведения, однако при этом другие приложения могут работать.

Перед завершением своей работы приложение закрывает устройство:

close patr

Каждый раз после выдачи команды приложение проверяет код возврата функции mciSendString. Если он не равен нулю, вызывается обработчик ошибок (функция mciwioError), задача которого заключается в выводе текстового описания ошибки на экран. Для преобразования кода ошибки в текстовое сообщение используется функция mciGetErrorString.

Файл ресурсов приложения MCISTRVW приведен в листинге 2.5.

Листинг 2.5. Файл mcistrvw\mcistrvw.def

APPICON ICON "mcistrwv.ico"

Файл определения модуля вы сможете найти в листинге 2.6.

Листинг 2.6. Файл mcistrvw\mcistrvw.def

NAME        MCISTRVW
DESCRIPTION 'Приложение MCISTRVW, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   8120
HEAPSIZE    1024
CODE        preload moveable discardable
DATA        preload moveable multiple

Приложение MCITEST

В составе системы разработки приложений Microsoft SDK в качестве примера поставляются исходные тексты приложения MCITEST (загрузочный модуль этого приложения есть в каталоге SDK\MCITEST на дискете, которая продается вместе с книгой). Приложение MCITEST (рис. 2.1) удобно использовать для изучения строчного интерфейса MCI и для отладки последовательностей строчных команд MCI.

Рис. 2.1. Главное окно приложения MCITEST

Окно, расположенное в верхней части экрана, предназначено для ввода командных строк. Это окно представляет собой многострочный редактор текста, поэтому вы можете вводить сразу несколько строк. Введенные команды можно выполнять по отдельности, нажимая клавишу <Enter> или кнопку "Step", или все вместе (при помощи кнопки "Go!").

В окне "MCI Output" отображается результат выполнения операции. Если произошла ошибка, ее текстовое описание появляется в окне "Error".

Если в качестве одного из параметров команды была указана строка notyfy, в окне "Notification" отображается результат, переданный с сообщением MM_MCINOTIFY.

С помощью меню "File" вы можете сохранять и загружать последовательности команд MCI, проверяя их в работе.

2.3. Интерфейс управляющих сообщений MCI

Интерфейс управляющих сообщений более удобен для приложений, составленных на языках программирования С и С++. Он предполагает посылку устройствам мультимедиа управляющих сообщений с помощью функции mciSendCommand .

В качестве одного из параметров этой функции передается двоичный код управляющего сообщения (команды). Символические константы кодов команд определены в файле mmsystem.h. В дополнение к коду команды функции mciSendCommand передается структура параметров, формат которой зависит от кода команды, а также другая дополнительная информация, необходимая для выполнения команды.

Функция mciSendCommand

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

Функция mciSendCommand
DWORD mciSendCommand(
  UINT wDeviceID,  // идентификатор устройства
  UINT wMessage,   // код сообщения
  DWORD dwParam1,  // флаги команды
  DWORD dwParam2); // указатель на структуру параметров

Параметры функции:

wDeviceID

Идентификатор устройства, которому посылается сообщение. Для команды MCI_OPEN не используется, так как идентификатор создается в результате выполнения этой команды

wMessage

Код сообщения. Соответствует требуемой функции. Список кодов сообщений (команд) для звукового адаптера приведен в следующем разделе

dwParam1

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

dwParam2

Указатель на структуру параметров. Формат этой структуры зависит от кода сообщения

Возвращаемое значение:

Нуль при нормальном завершении или код ошибки. Список кодов ошибок для функции mciSendCommand приведен в приложении 1. С помощью функции mciGetErrorString, рассмотренной нами ранее, вы можете преобразовать код ошибки в текстовую строку, передав ей этот код в качестве параметра

Формат структуры параметров (блока параметров) и используемые флаги зависят от кода управляющего сообщения, передаваемого функции mciSendCommand через параметр wMessage.

Коды управляющих сообщений MCI

Коды управляющих сообщений делятся на системные (System), обязательные (Required), базовые (Basic) и расширенные (Extended), точно также как и команды, используемые в интерфейсе управляющих строк, рассмотренном нами в предыдущем разделе.

Приведем список кодов управляющих сообщений (в дальнейшем просто команд), которые используются для управления звуковым адаптером.

Команда Тип Описание
MCI_BREAK Системная Назначение виртуального кода клавиши, с помощью которой можно прервать работу устройства
MCI_SYSINFO - Получение системной информации об устройстве (в виде текстовой строки)
MCI_GETDEVCAPS Обязательная Определение возможностей устройства
MCI_CLOSE - Закрытие устройства
MCI_INFO - Получение текстовой информации об устройстве
MCI_OPEN - Открытие устройства
MCI_STАTUS - Определение состояния устройства
MCI_LOAD Базовая Загрузка данных из файла
MCI_PAUSE - Пауза при проигрывании
MCI_PLAY - Включение режима проигрывания
MCI_RECORD - Включение режима записи
MCI_RESUME - Продолжение проигрывания после паузы
MCI_SAVE - Сохранение данных в файле
MCI_SEEK - Позиционирование
MCI_SET - Установка режима работы устройства
MCI_STOP - Останов проигрывания
MCI_CUE Расширенная Подготовка устройства для проигрывания или записи
MCI_DELETE - Удаление фрагмента данных

Как нетрудно заметить, состав и назначение групп управляющих сообщений полностью соответствует составу и назначению групп управляющих строк. Расскажем об использовании наиболее полезных управляющих сообщений (команд MCI).

MCI_OPEN

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

Первый параметр (идентификатор устройства) можно указать как 0, так как пока мы не открыли устройство, ему не присвоен никакой идентификатор.

Через последний (четвертый) параметр нужно передать функции адрес заполненной структуры MCI_OPEN_PARMS , определенной в файле mmsystem.h:

typedef struct tagMCI_WAVE_OPEN_PARMS {
    DWORD   dwCallback;
    UINT    wDeviceID;
    UINT    wReserved0;
    LPCSTR  lpstrDeviceType;
    LPCSTR  lpstrElementName;
    LPCSTR  lpstrAlias;
} MCI_OPEN_PARMS;
typedef MCI_OPEN_PARMS FAR *LPMCI_OPEN_PARMS;

В этой структуре младшее слово поля dwCallback должно содержать идентификатор окна, которому после выполнения команды будет посылаться извещение в виде сообщения MM_MCINOTIFY . Если извещение не посылается, значение этого поля игнорируется.

В поле wDeviceID после возвращения из функции mciSendCommand будет записан идентификатор, присвоенный устройству при открытии. Все последующие команды MCI должны ссылаться на этот идентификатор.

Поле wReserved0 зарезервировано, в него следует записать нулевое значение.

Поле lpstrDeviceType содержит указатель на строку имени устройства, или константный идентификатор устройства. Для звукового адаптера вы можете указать имя "waveaudio " или константу MCI_DEVTYPE_WAVWFORM_AUDIO .

Через параметр lpstrElementName передается указатель на путь к файлу, если нужно проиграть звуковой фрагмент, записанный в wav-файле.

Дополнительно при открытии устройства ему можно назначить алиасное имя, записав в поле lpstrAlias указатель на строку алиасного имени.

Третий параметр функции mciSendCommand предназначен для флагов, определяющих, какие из полей структуры параметров следует использовать при открытии устройства, а также для флага MCI_WAIT, устанавливающего режим работы функции (с ожиданием или без ожидания). Флаги можно объединять с помощью логической операции ИЛИ.

Для структуры MCI_OPEN_PARMS определены следующие флаги:

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса открытия устройства
MCI_OPEN_ALIAS Используется алиасное имя, адрес строки имени должен быть указан в поле lpstrAlias
MCI_OPEN_SHAREABLE Устройство открывается в режиме совместного использования несколькими приложениями одновременно
MCI_OPEN_ELEMENT Поле lpstrElementName содержит указатель на строку, в которой находится путь к файлу
MCI_OPEN_TYPE Поле lpstrDeviceType содержит указатель на строку имени устройства, например, адрес строки "waveaudio"
MCI_OPEN_TYPE_ID Поле lpstrDeviceType содержит константный идентификатор устройства, например, константу MCI_DEVTYPE_WAVWFORM_AUDIO

Приведенный ниже фрагмент кода открывает устройство "waveaudio", причем будет открыт файл, адрес пути к которому записан в переменной szFileName:

MCI_OPEN_PARMS mciOpen;
DWORD dwFlags;
mciOpen.lpstrDeviceType  = (LPSTR)"waveaudio";
mciOpen.lpstrElementName = (LPSTR)szFileName;
mciOpen.dwCallback = 0;
mciOpen.wDeviceID  = 0;
mciOpen.wReserved0 = 0;
mciOpen.lpstrAlias = NULL;
dwFlags = MCI_OPEN_TYPE | MCI_OPEN_ELEMENT | MCI_WAIT;
dwrc = mciSendCommand(0, MCI_OPEN,  dwFlags,
       (DWORD)(LPVOID)&mciOpen);

После выполнения этого фрагмента в переменную dwrc, будет записан код результата завершения. При успешном завершении в поле wDeviceID структуры mciOpen будет находиться идентификатор открытого устройства.

Для звукового адаптера вы можете использовать расширенную структуру MCI_WAVE_OPEN_PARMS , также определенную в файле mmsystem.h:

typedef struct tagMCI_WAVE_OPEN_PARMS {
    DWORD   dwCallback;
    UINT    wDeviceID;
    UINT    wReserved0;
    LPCSTR  lpstrDeviceType;
    LPCSTR  lpstrElementName;
    LPCSTR  lpstrAlias;
    DWORD   dwBufferSeconds;
} MCI_WAVE_OPEN_PARMS;
typedef MCI_WAVE_OPEN_PARMS FAR *LPMCI_WAVE_OPEN_PARMS;

По сравнению со структурой MCI_OPEN_PARMS в ней есть дополнительное поле dwBufferSeconds. Это поле определяет размер внутреннего буфера системы MCI для звукового драйвера. Численно размер буфера должен быть равен длительности звучания в секундах.

Чтобы задействовать это дополнительное поле, следует указать функции mciSendCommand флаг MCI_WAVE_OPEN_BUFFER .

MCI_CLOSE

Эта команда закрывает устройство. Ее необходимо выдавать после завершения работы с устройством.

Для команды MCI_CLOSE используется блок параметров в виде структуры MCI_GENERIC_PARMS , описанной в файле mmsystem.h:

typedef struct tagMCI_GENERIC_PARMS {
    DWORD   dwCallback;
} MCI_GENERIC_PARMS;
typedef MCI_GENERIC_PARMS FAR *LPMCI_GENERIC_PARMS;

Эта упрощенная структура предназначена для тех случаев, когда команде не требуются дополнительные параметры. Поле dwCallback используется также, как и в команде MCI_OPEN.

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

MCI_GENERIC_PARMS mcigen;
DWORD dwrc;
mcigen.dwCallback = 0;
dwrc = mciSendCommand(mciOpen.wDeviceID, MCI_CLOSE, MCI_WAIT,
  (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);

MCI_PLAY

Команда MCI_PLAY , как это видно из ее названия, предназначена для проигрывания файлов. Для этой команды в файле mmsystem.h определена структура блока параметров MCI_PLAY_PARMS :

typedef struct tagMCI_PLAY_PARMS {
    DWORD   dwCallback;
    DWORD   dwFrom;
    DWORD   dwTo;
} MCI_PLAY_PARMS;
typedef MCI_PLAY_PARMS FAR *LPMCI_PLAY_PARMS;

В структуре параметров можно указать начальную и конечную позиции для проигрывания. Начальная позиция задается в поле dwFrom, конечная - в поле dwTo. Перед использованием начальной позиции следует установить формат времени при помощи команды MCI_SET_TIME_FORMAT, которую мы рассмотрим позже. Формат времени определяет единицу измерения для позиции, например, миллисекунды, байты, выборки сигнала или кадры.

Для структуры MCI_PLAY_PARMS можно указывать следующие флаги:

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса проигрывания
MCI_FROM Поле dwFrom содержит начальную позицию для проигрывания
MCI_TO Поле dwTo содержит конечную позицию для проигрывания

В приведенном ниже фрагменте запускается проигрывание с текущей позиции:

MCI_PLAY_PARMS mciPlayParms;
DWORD dwrc;
mciPlayParms.dwCallback = (DWORD)hwnd;
dwrc=mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY,
  (DWORD)(LPVOID)&mciPlayParms);

Для изменения текущей позиции можно воспользоваться командой MCI_SEEK.

При завершении проигрывания окно с идентификатором hwnd получит сообщение MM_MCINOTIFY.

MCI_RECORD

Команда MCI_RECORD позволяет выполнить запись в существующий или новый файл. Если при открытии устройства вы указали имя файла, будет выполняться запись в существующий файл. Для записи в новый файл нужно использовать имя нулевой длины. Результат записи в этом случае можно сохранить в файле при помощи команды MCI_SAVE.

Приведем формат блока параметров для команды MCI_RECORD:

typedef struct tagMCI_RECORD_PARMS {
    DWORD   dwCallback;
    DWORD   dwFrom;
    DWORD   dwTo;
} MCI_RECORD_PARMS;
typedef MCI_RECORD_PARMS FAR *LPMCI_RECORD_PARMS;

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

Для структуры MCI_RECORD_PARMS можно указывать следующие флаги:

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса записи
MCI_RECORD_INSERT Необходимо вставить новую запись в уже существующие данные
MCI_RECORD_OWERWRITE Новая запись должна заместить существующие данные
MCI_FROM Поле dwFrom содержит начальную позицию для записи
MCI_TO Поле dwTo содержит конечную позицию для записи

Если при выдаче команды MCI_RECORD вы не указали конечную позицию записи, запись будет продолжаться до тех пор, пока приложение не выдаст команду MCI_STOP или пока не будет израсходовано все свободное место на диске.

В приведенном ниже фрагменте кода запускается запись, которая будет продолжаться до достижения позиции dwMSec (запись может быть также остановлена раньше при помощи команды MCI_STOP):

MCI_RECORD_PARMS mciRecordParms;
mciRecordParms.dwTo       = dwMSec;
mciRecordParms.dwCallback = (DWORD)hwnd;
dwrc=mciSendCommand(wInDeviceID,
   MCI_RECORD, MCI_NOTIFY | MCI_TO,
  (DWORD)(LPVOID)&mciRecordParms);

После завершения процесса записи окно с идентификатором hwnd получит сообщение MM_MCINOTIFY.

MCI_SAVE

Команда MCI_SAVE позволяет сохранить результат записи в файле. Для нее используется блок параметров MCI_SAVE_PARMS :

typedef struct tagMCI_SAVE_PARMS {
    DWORD   dwCallback;
    LPCSTR  lpfilename;
} MCI_SAVE_PARMS;
typedef MCI_SAVE_PARMS FAR * LPMCI_SAVE_PARMS;

Поле lpfilename должно содержать указатель на путь к файлу.

Флаги для этой команды:

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса сохранения
MCI_SAVE_FILE Поле lpfilename содержит путь к файлу, в котором необходимо сохранить результат записи

В следующем фрагменте кода выполняется сохранение записанных данных в файле с именем recorded.wav, который будет создан в текущем каталоге:

MCI_SAVE_PARMS mciSave;
mciSave.lpfilename = "recorded.wav";
dwrc=mciSendCommand(wInDeviceID, MCI_SAVE,
   MCI_SAVE_FILE | MCI_WAIT, (DWORD)(LPVOID)&mciSave);

MCI_PAUSE

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

Флаги для этой команды:

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса

Пример использования команды MCI_PAUSE:

MCI_GENERIC_PARMS mcigen;
dwrc = mciSendCommand(wDeviceID, MCI_PAUSE, MCI_WAIT,
  (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);

MCI_RESUME

Эта команда отменяет действие команды MCI_PAUSE, при этом приостановленная операция будет продолжена. Для команды MCI_RESUME используется блок параметров MCI_GENERIC_PARMS .

Флаги для этой команды:

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса

Пример использования команды MCI_RESUME:

MCI_GENERIC_PARMS mcigen;
dwrc = mciSendCommand(wDeviceID, MCI_RESUME, MCI_WAIT,
  (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);

MCI_STOP

Команда MCI_STOP останавливает выполнение операции записи или воспроизведения, после чего освобождает все буфера, которые были использованы для операции. Эта команда использует блок параметров MCI_GENERIC_PARMS .

Флаги для этой команды:

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса

Пример использования команды MCI_STOP:

MCI_GENERIC_PARMS mcigen;
dwrc = mciSendCommand(wDeviceID, MCI_STOP, MCI_WAIT,
  (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);

MCI_SEEK

Команда MCI_SEEK позволяет выполнять позиционирование в пределах файла. Для этой команды используется блок параметров MCI_SEEK_PARMS :

typedef struct tagMCI_SEEK_PARMS {
    DWORD   dwCallback;
    DWORD   dwTo;
} MCI_SEEK_PARMS;
typedef MCI_SEEK_PARMS FAR *LPMCI_SEEK_PARMS;

Поле dwTo задает новую позицию в единицах, установленных командой MCI_SET_TIME_FORMAT.

Флаги для этой команды:

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса позиционирования
MCI_SEEK_TO_START Позиционирование на начало
MCI_SEEK_TO_END Позиционирование в конец
MCI_SEEK_TO Позиция определяется содержимым поля dwTo

MCI_BREAK

С помощью команды MCI_BREAK указывается виртуальный код клавиши, предназначенной для прерывания выполнения операции. Для этой команды необходимо использовать следующую структуру блока параметров:

typedef struct tagMCI_BREAK_PARMS {
    DWORD   dwCallback;
    int     nVirtKey;
    UINT    wReserved0;
    HWND    hwndBreak;
    UINT    wReserved1;
} MCI_BREAK_PARMS;
typedef MCI_BREAK_PARMS  FAR * LPMCI_BREAK_PARMS;

Поле nVirtKey определяет виртуальный код клавиши прерывания.

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

Поля wReserved0 и wReserved1 зарезервированы.

Для команды MCI_BREAK можно указывать следующие флаги:

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса позиционирования
MCI_BREAK_KEY Поле nVirtKey содержит виртуальный код клавиши прерывания команды
MCI_BREAK_HWND Поле hwndBreak содержит идентификатор окна, которое должно быть текущим для обеспечения возможности прерывания команды
MCI_BREAK_OFF Используется для отключения прерывания

По умолчанию для прерывания используется комбинация клавиш <Control+Break>.

MCI_CUE

Команда MCI_CUE используется для подготовки устройства к записи или воспроизведению, после которой эти операции выполняются с минимальной задержкой. Для нее используется блок параметров в формате структуры MCI_GENERIC_PARMS и следующий набор флагов:

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса

MCI_GETDEVCAPS

С помощью команды MCI_GETDEVCAPS можно определить возможности устройства мультимедиа. Для нее используется блок параметров в формате структуры MCI_GETDEVCAPS_PARMS , определенной в файле mmsystem.h следующим образом:

typedef struct tagMCI_GETDEVCAPS_PARMS {
    DWORD   dwCallback;
    DWORD   dwReturn;
    DWORD   dwItem;
} MCI_GETDEVCAPS_PARMS;
typedef MCI_GETDEVCAPS_PARMS FAR * LPMCI_GETDEVCAPS_PARMS;

В поле dwReturn после возврата из функции mciSendCommand будет записано значение требуемого параметра. Код нужного параметра следует записать в поле dwItem перед вызовом функции mciSendCommand.

Приведем возможные значения параметра dwItem:

Значение параметра dwItem Описание
MCI_GETDEVCAPS_CAN_EJECT Если устройство может выталкивать носитель данных (например, компакт-диск), после возврата из функции mciSendCommand в поле dwReturn будет ненулевое значение TRUE
MCI_GETDEVCAPS_CAN_PLAY Устройство может проигрывать
MCI_GETDEVCAPS_CAN_RECORD Устройство может записывать
MCI_GETDEVCAPS_CAN_SAVE Устройство может сохранять записанные данные в файле
MCI_GETDEVCAPS_COMPOUND_DEVICE Устройство может работать с файлами
MCI_GETDEVCAPS_DEVICE_TYPE Требуется определить тип устройства. Для звукового адаптера возвращается константа MCI_DEVTYPE_WAVEFORM_AUDIO
MCI_GETDEVCAPS_HAS_AUDIO Устройство имеет звуковой выход
MCI_GETDEVCAPS_HAS_VIDEO Устройство имеет видеовыход
MCI_GETDEVCAPS_USES_FILES При открытии устройства требуется указывать имя файла
MCI_WAVE_GETDEVCAPS_INPUT Количество звуковых входов
MCI_WAVE_GETDEVCAPS_OUTPUT Количество звуковых выходов (каналов)

Для команды MCI_GETDEVCAPS можно использовать следующий набор флагов:

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса
MCI_GETDEVCAPS_ITEM Поле dwItem содержит константу, соответствующую определяемому параметру устройства

MCI_INFO

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

Используется блок параметров в формате структуры MCI_INFO_PARMS :

typedef struct tagMCI_INFO_PARMS {
    DWORD   dwCallback;
    LPSTR   lpstrReturn;
    DWORD   dwRetSize;
} MCI_INFO_PARMS;
typedef MCI_INFO_PARMS FAR * LPMCI_INFO_PARMS;

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

Приведем набор флагов для команды MCI_INFO:

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса
MCI_INFO_PRODUCT Требуется получить описание аппаратуры устройства
MCI_INFO_FILE Требуется получить имя текущего файла, связанного с устройством
MCI_WAWE_INPUT Имя текущего устройства ввода
MCI_WAVE_OUTPUT Имя текущего устройства вывода

MCI_SYSINFO

С помощью этой команды можно получить системную информацию об устройстве в виде текстовой строки. Используется блок параметров в формате структуры MCI_SYSINFO_PARMS :

typedef struct tagMCI_SYSINFO_PARMS {
    DWORD   dwCallback;
    LPSTR   lpstrReturn;
    DWORD   dwRetSize;
    DWORD   dwNumber;
    UINT    wDeviceType;
    UINT    wReserved0;
} MCI_SYSINFO_PARMS;
typedef MCI_SYSINFO_PARMS FAR * LPMCI_SYSINFO_PARMS;

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

Поле dwNumber определяет положение устройства в таблице устройств MCI или в списке открытых устройств, если установлен флаг MCI_SYSINFO_OPEN. Поле wDeviceType определяет тип устройства. Поле wReserved0 зарезервировано.

Приведем набор флагов для команды MCI_INFO:

Флаг Описание
MCI_SYSINFO_INSTALLNAME Требуется получить имя, которое было использовано в файле win.ini при установке драйвера устройства
MCI_SYSINFO_NAME Требуется получить имя устройства, соответствующего устройству с номером, заданным в поле dwNumber
MCI_SYSINFO_OPEN Определить имя открытого устройства или количество открытых устройств
MCI_SYSINFO_QUANTITY Определить количество устройств заданного типа, перечисленных в разделе [mci] файла system.ini. Если дополнительно установлен флаг MCI_SYSINFO_OPEN, возвращается количество открытых устройств

MCI_STATUS

Команда MCI_STATUS используется для определения текущего состояния устройства.

Формат соответствующего блока параметров описывается структурой MCI_STATUS_PARMS :

typedef struct tagMCI_STATUS_PARMS {
    DWORD   dwCallback;
    DWORD   dwReturn;
    DWORD   dwItem;
    DWORD   dwTrack;
} MCI_STATUS_PARMS;
typedef MCI_STATUS_PARMS FAR * LPMCI_STATUS_PARMS;

Через поле dwReturn передается возвращаемая информация. Вид запрашиваемой информации определяется содержимым поля dwItem. Для устройств, которые работают с дорожками (например, устройство чтения компакт-дисков), в поле dwTrack можно указать размер или номер дорожки.

Приведем возможные значения параметра dwItem (для звукового адаптера):

Значение параметра dwItem Описание получаемой информации
MCI_STATUS_CURRENT_TRACK Номер текущей дорожки
MCI_STATUS_LENGTH Общий размер (длина) фрагмента
MCI_STATUS_MODE Текущий режим устройства. Может иметь следующие значения:MCI_MODE_NOT_READY не готово;MCI_MODE_PAUSE пауза;MCI_MODE_PLAY проигрывание;MCI_MODE_STOP останов;MCI_MODE_OPEN открытие;MCI_MODE_RECORD запись;MCI_MODE_SEEK позиционирование
MCI_STATUS_NUMBER_OF_TRACKS Общее количество дорожек, которые можно проиграть
MCI_STATUS_POSITION Текущая позиция
MCI_STATUS_READY Если устройство готово, возвращается значение TRUE, в противном случае - FALSE
MCI_STATUS_TIME_FORMAT Текущий формат времени. Может иметь следующие значения:MCI_FORMAT_BYTES MCI_FORMAT_FRAMES MCI_FORMAT_HMS MCI_FORMAT_MILLISECONDS MCI_FORMAT_MSF MCI_FORMAT_SAMPLES MCI_FORMAT_TMSF
MCI_STATUS_START Начальная позиция
MCI_STATUS_TRACK В поле dwTrack записывается либо начальная позиция заданной дорожки (если дополнительно используется MCI_STATUS_POSITION), либо размер дорожки (если дополнительно используется MCI_STATUS_LENGTH)
MCI_STATUS_MEDIA_PRESENT Возвращается TRUE, если носитель данных вставлен в устройство
MCI_WAVE_INPUT Устройство, используемое для записи
MCI_WAVE_OUTPUT Устройство, используемое для воспроизведения
MCI_WAVE_STATUS_AVGBYTESPERSEC Скорость потока данных при записи и воспроизведении, байты в секунду
MCI_WAVE_STATUS_BITSPERSAMPLE Количество бит, используемых для представления одной выборки сигнала
MCI_WAVE_STATUS_BLOCKALIGN Текущее выравнивание блока
MCI_WAVE_STATUS_CHANNELS Количество каналов
MCI_WAVE_FORMATTAG Тег формата, используемого для записи, воспроизведения или сохранения данных в файле
MCI_WAVE_STATUS_LEVEL Текущий уровень записи или воспроизведения, используется 8- или 16-битовое значение в зависимости от формата данных. Младшее слово содержит уровень для монофонической записи или уровень правого канала для стереофонической записи. Уровень левого канала передается через старшее слово
MCI_WAVE_STATUS_SAMPLESPERSEC Скорость выборки сигнала (частота дискретизации)

Приведем также список флагов для команды MCI_STATUS:

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса
MCI_STATUS_ITEM Поле wItem содержит код получаемой информации

В приведенном ниже фрагменте кода определяется длительность звучания:

mciStatus.dwItem = MCI_STATUS_LENGTH;
dwrc = mciSendCommand(mciOpen.wDeviceID, MCI_STATUS,
  MCI_STATUS_ITEM | MCI_WAIT, (DWORD)(LPVOID)&mciStatus);

MCI_SET

Команда MCI_SET предназначена для установки режима работы устройства. Вместе с этой командой используется блок параметров в формате структуры MCI_SET_PARMS :

typedef struct tagMCI_SET_PARMS {
    DWORD   dwCallback;
    DWORD   dwTimeFormat;
    DWORD   dwAudio;
} MCI_SET_PARMS;
typedef MCI_SET_PARMS FAR *LPMCI_SET_PARMS;

Поле dwTimeFormat определяет формат времени для устройства, поле dwAudio определяет выходной канал.

Для звуковых устройств можно использовать другую структуру:

typedef struct tagMCI_WAVE_SET_PARMS {
    DWORD   dwCallback;
    DWORD   dwTimeFormat;
    DWORD   dwAudio;
    UINT    wInput;
    UINT    wReserved0;
    UINT    wOutput;
    UINT    wReserved1;
    UINT    wFormatTag;
    UINT    wReserved2;
    UINT    nChannels;
    UINT    wReserved3;
    DWORD   nSamplesPerSec;
    DWORD   nAvgBytesPerSec;
    UINT    nBlockAlign;
    UINT    wReserved4;
    UINT    wBitsPerSample;
    UINT    wReserved5;
} MCI_WAVE_SET_PARMS;
typedef MCI_WAVE_SET_PARMS  FAR * LPMCI_WAVE_SET_PARMS;

В этой структуре поле wInput определяет номер канала для записи, wOutput - номер канала для воспроизведения. Поле wFormatTag используется для определения формата звуковых данных. С помощью поля nChannels можно указать количество каналов - 1 (моно) или 2 (стерео). Поле nSamplesPerSec предназначено для задания частоты дискретизации (количество выборок сигнала в секунду). Поле nAvgBytesPerSec содержит скорость передачи данных (байты в секунду). С помощью поля nBlockAlign можно задать выравнивание блока, а с помощью поля wBitsPerSample - количество бит, используемых для представления одной выборки (8 или 16). Остальные поля зарезервированы.

Приведем список флагов, которые используются вместе с командой MCI_SET:

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса
MCI_SET_AUDIO Включение или выключение каналов, используется вместе с флагами MCI_SET_ON и MCI_SET_OFF. Поле dwAudio содержит номера канала. Дополнительно можно указать следующие константы:MCI_SET_AUDIO_ALL все каналыMCI_SET_AUDIO_LEFT левый каналMCI_SET_AUDIO_RIGHT правый канал
MCI_SET_DOOR_CLOSED По этой команде устройство защелкивает носитель данных (например, компакт-диск)
MCI_SET_DOOR_OPEN Освобождение носителя данных
MCI_SET_VIDEO Включение или выключение видеосигнала, используется вместе с флагами MCI_SET_ON и MCI_SET_OFF
MCI_SET_ON Включение заданного канала
MCI_SET_OFF Выключение заданного канала
MCI_WAVE_INPUT Установка канала для записи. Номер канала должен быть указан в поле wInput структуры MCI_WAVE_SET_PARAMS
MCI_WAVE_OUTPUT Установка канала для воспроизведения. Номер канала должен быть указан в поле wInput структуры MCI_WAVE_SET_PARAMS
MCI_WAVE_SET_ANYINPUT При записи следует использовать любое устройство, совместимое с заданным форматом данных
MCI_WAVE_SET_ANYOUTPUT При воспроизведении следует использовать любое устройство, совместимое с заданным форматом данных
MCI_WAVE_SET_AVGBYTESPERSEC Установить скорость потока данных при записи и воспроизведении из поля nAvgBytesPerSec
MCI_WAVE_SET_BITSPERSAMPLE Установить количество бит, используемых для представления одной выборки сигнала из поля wBitsPerSample
MCI_WAVE_SET_BLOCKALIGN Установить выравнивание блока из поля nBlockAlign
MCI_WAVE_SET_CHANNELS Поле nChannels содержит номер канала
MCI_WAVE_SET_FORMATTAG Установить формат из поля wFormatTag
MCI_WAVE_SET_SAMPLESPERSEC Установить частоту выборки из поля nSamplesPerSec
MCI_WAVE_SET_TIME_FORMAT Установить формат времени. Используется вместе со следующими константами:MCI_FORMAT_BYTES в байтах;MCI_FORMAT_MILLISECONDS в миллисекундах;MCI_FORMAT_SAMPLES в выборках сигнала

MCI_COPY

Команда MCI_COPY предназначена для копирования данных в универсальный буфер обмена Clipboard. Для нее используется блок параметров в формате структуры MCI_GENERIC_PARMS, флаги MCI_NOTIFY и MCI_WAIT.

MCI_PASTE

Команда MCI_PASTE вставляет данные из Clipboard в текущий буфер устройства. Для нее, как и для команды MCI_COPY, используется блок параметров в формате структуры MCI_GENERIC_PARMS, флаги MCI_NOTIFY и MCI_WAIT.

MCI_CUT

Команда MCI_CUT удаляет данные из текущего буфера устройства и копирует их в универсальный буфер обмена Clipboard. Для нее используется блок параметров в формате структуры MCI_GENERIC_PARMS, флаги MCI_NOTIFY и MCI_WAIT.

MCI_DELETE

Команда MCI_DELETE удаляет данные из текущего буфера устройства без копирования их в Clipboard. Для нее используется блок параметров в формате структуры MCI_GENERIC_PARMS.

Вместе с этой командой при работе со звуковым адаптером можно использовать структуру MCI_WAVE_DELETE_PARMS :

typedef struct tagMCI_WAVE_DELETE_PARMS {
    DWORD   dwCallback;
    DWORD   dwFrom;
    DWORD   dwTo;
} MCI_WAVE_DELETE_PARMS;
typedef MCI_WAVE_DELETE_PARMS FAR *LPMCI_WAVE_DELETE_PARMS;

Поле dwFrom используется для передачи команде начальной позиции для удаления, поле dwTo - для передачи конечной позиции удаления.

Вместе с командой MCI_DELETE используются перечисленные ниже флаги.

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса
MCI_FROM Поле dwFrom содержит начальную позицию для удаления
MCI_TO Поле dwTo содержит конечную позицию для удаления

MCI_LOAD

Команда MCI_LOAD предназначена для загрузки файла. Она использует блок параметров в формате структуры MCI_LOAD_PARMS :

typedef struct tagMCI_LOAD_PARMS {
    DWORD   dwCallback;
    LPCSTR  lpfilename;
} MCI_LOAD_PARMS;
typedef MCI_LOAD_PARMS FAR * LPMCI_LOAD_PARMS;

Через поле lpfilename передается указатель на буфер, содержащий путь к файлу.

Вместе с командой MCI_LOAD можно использовать следующие флаги:

Флаг Описание
MCI_NOTIFY Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY
MCI_WAIT Функция mciSendCommand вернет управление только после завершения процесса
MCI_LOAD_FILE Поле lpfilename содержит указатель на строку пути к файлу

Приложение MCIWAVER

В качестве примера использования интерфейса сообщений MCI приведем исходные тексты несложного приложения MCIWAVER, с помощью которого можно записывать и воспроизводить wav-файлы (рис. 2.2).

Рис. 2.2. Главное окно приложения MCIWAVER

Если выбрать из главного меню приложения строку "Record!", включится запись. Вы сможете записать звуковой фрагмент длительностью до 60 секунд (максимальное время записи определяется константой MAXRECORDTIME, вы можете изменить значение этой константы в исходном тексте приложения). Для прерывания процесса записи в любой момент времени можно выбрать из меню строки "Stop!" или "Pause!". Результат записи всегда сохраняется в файле с именем recorded.wav, который создается в текущем каталоге.

С помощью строки "Open..." меню "File" можно выбрать wav-файл для воспроизведения. Путь к выбранному файлу отобразится в заголовке окна. Для прослушивания загруженного wav-файла воспользуйтесь строкой "Play!". Прослушивание можно прекратить (строка "Stop!") или временно приостановить (строка "Pause!"). Для продолжения прослушивания после временного останова выберите строку "Resume!".

Полоса просмотра служит для отображения текущей позиции при записи и воспроизведении. Над полосой просмотра выводится текущий режим работы приложения.

Основной файл приложения MCIWAWER приведен в листинге 2.7.

Листинг 2.7. Файл mciwaver\mciwaver.cpp

// ------------------------------------------------
// Приложение MCIWAVE
// Проигрывание и запись wav-файлов
// с помощью интерфейса сообщений MCI
// ------------------------------------------------

#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <mem.h>
#pragma hdrstop

#include "mciwave.hpp"
#include "mciwavio.hpp"

// Идентификатор таймера
#define BEEP_TIMER 1

// Идентификатор полосы просмотра
#define ID_SCROLL 10

// Длина полосы просмотра
#define SCROLL_SIZE 400

// Длительность записи в миллисекундах
#define MAXRECORDTIME 60000L

// Прототипы функций
BOOL    InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

// Глобальные переменные
int      nMode = MODE_STOP;
MMTIME   mmtimeIn, mmtimeOut;
BOOL     fFileLoaded = FALSE;
int      nPosition;
HWND     hScroll;
UINT     wOutDeviceID;
UINT     wInDeviceID;
BYTE     szFileName[128];
DWORD    dwFileSize;

char const szClassName[]   = "MCIWaveClass";
char const szWindowTitle[] = "MCIWaver";
HINSTANCE  hInst;

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR lpszCmdLine, int nCmdShow)
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  if(hPrevInstance)
    return FALSE;

  if(!InitApp(hInstance))
    return FALSE;

  hInst = hInstance;

  hwnd = CreateWindow(
    szClassName,         // имя класса окна
    szWindowTitle,       // заголовок окна
    WS_OVERLAPPEDWINDOW, // стиль окна
    CW_USEDEFAULT,       // размеры и расположение окна
    CW_USEDEFAULT,       
    450, 120, 0, 0, hInstance, NULL);
                       
  if(!hwnd)
    return FALSE;

  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  while(GetMessage(&msg, 0, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

// =====================================
// Функция InitApp
// Выполняет регистрацию класса окна
// =====================================

BOOL
InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации

  memset(&wc, 0, sizeof(wc));
  wc.lpszMenuName  = "APP_MENU";
  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = (WNDPROC) WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = LoadIcon(hInstance, "APPICON");
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  wc.lpszClassName = (LPSTR)szClassName;

  aWndClass = RegisterClass(&wc);
  return (aWndClass != 0);
}

// =====================================
// Функция WndProc
// =====================================

LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  PAINTSTRUCT ps;
  int rc;

  switch (msg)
  {
// ------------------------------------------------------------
// WM_CREATE
// Создание главного окна приложения
// ------------------------------------------------------------
    case WM_CREATE:
    {
       nMode        = MODE_STOP;
       fFileLoaded  = FALSE;
       wOutDeviceID = 0;
       wInDeviceID  = 0;

       // Создаем таймер
       SetTimer(hwnd, BEEP_TIMER, 100, NULL);

       // Создаем полосу просмотра
       hScroll = CreateWindow("scrollbar", NULL,
         WS_CHILD | WS_VISIBLE | SBS_HORZ,
         10, 40, SCROLL_SIZE, 15, hwnd,
         (HMENU) ID_SCROLL, hInst, NULL);

       // Устанавливаем текущую позицию
       nPosition = 0;

       // Устанавливаем минимальное и максимальное
       // значения для полосы просмотра
       SetScrollRange(hScroll, SB_CTL, 1, SCROLL_SIZE, TRUE);

       // Устанавливаем ползунок
       SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);
       return 0;
    }

// ------------------------------------------------------------
// WM_PAINT
// Рисование в окне
// ------------------------------------------------------------
    case WM_PAINT:
    {
      // Получаем контекст отображения для
      // рисования во внутренней области окна 
      hdc = BeginPaint(hwnd, &ps);

      // Отображаем текущий режим работы
      if(nMode == MODE_STOP)
        TextOut(hdc, 10, 10, "Остановлено", 11);
      else if(nMode == MODE_RECORDING)
        TextOut(hdc, 10, 10, "Идет запись...", 14);
      else if(nMode == MODE_PLAYING)
        TextOut(hdc, 10, 10, "Идет проигрывание...", 20);
      else if(nMode == MODE_RECORDINGPAUSED)
        TextOut(hdc, 10, 10, "Запись остановлена", 18);
      else if(nMode == MODE_PLAYINGPAUSED)
        TextOut(hdc, 10, 10, "Проигрывание остановлено", 24);
      else
        TextOut(hdc, 10, 10, "Неправильный режим!", 19);

      // Освобождаем контекст отображения
      EndPaint(hwnd, &ps);
      return 0;
    }

// ------------------------------------------------------------
// WM_COMMAND
// Обработка сообщений от меню
// ------------------------------------------------------------
    case WM_COMMAND:
    {
      switch (wParam)
      {
        // -------------------------------------------------
        // Строка "About" меню "Help"
        // -------------------------------------------------
        case CM_HELPABOUT:
        {
          MessageBox(hwnd,
            "MCIWaver, v.1.0\n"
            "(C) Frolov A.V., 1994",
            "About MCIWaver", MB_OK | MB_ICONINFORMATION);
          return 0;
        }

        // -------------------------------------------------
        // Строка "Open" меню "File"
        // -------------------------------------------------
        case CM_FILEOPEN:
        {
          char szTitle[256];

          // Загружаем новый файл
          if(!mciwioSelectFile(szFileName))
            return 0;

          // Отображаем в заголовке окна путь к файлу
          lstrcpy(szTitle, szWindowTitle);
          lstrcat(szTitle, " - ");
          lstrcat(szTitle, szFileName);
          SetWindowText(hwnd, szTitle);

          // Если было запущено воспроизведение,
          // останавливаем его и закрываем устройство вывода
          if(wOutDeviceID)
          {
            mciwioStop(wOutDeviceID);
            mciwioClose(wOutDeviceID);
            wOutDeviceID = 0;

            // Новый режим
            nMode = MODE_STOP;
          }

          // Устанавливаем движок в начало полосы просмотра
          nPosition = 0;
          SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);

          // Устанавливаем флаг загрузки файла
          fFileLoaded = TRUE;
          return 0;
        }

        // -------------------------------------------------
        // Строка "Play!"
        // Проигрывание загруженного wav-файла
        // -------------------------------------------------
        case CM_CTLPLAY:
        {
          // Если файл загружен и не проигрывается,
          // запускаем проигрывание файла
          if((fFileLoaded == TRUE) && (nMode == MODE_STOP))
          {
            // Новый режим
            nMode = MODE_PLAYING;

            // Перерисовываем окно для отображения строки,
            // соответствующей новому режиму
            InvalidateRect(hwnd, NULL, TRUE);

            // Открываем устройство
            wOutDeviceID = mciwioOpen((LPSTR)szFileName);

            // Проигрываем файл
            mciwioPlay(hwnd, wOutDeviceID);
          }
          return 0;
        }

        // -------------------------------------------------
        // Строка "Record!"
        // Запись wav-файла
        // -------------------------------------------------
        case CM_CTLRECORD:
        {
          // Запись возможна только из состояния останова
          if(nMode == MODE_STOP)
          {
            nMode = MODE_RECORDING;
            InvalidateRect(hwnd, NULL, TRUE);

            // Запись файла
            wInDeviceID = mciwioRecord(hwnd, MAXRECORDTIME);
          }
          return 0;
        }

        // -------------------------------------------------
        // Строка "Stop!"
        // Останов проигрывания или записи wav-файла
        // -------------------------------------------------
        case CM_CTLSTOP:
        {
          if(nMode == MODE_RECORDING 
             || nMode == MODE_RECORDINGPAUSED)
          {
            // Останавливаем запись
            mciwioStop(wInDeviceID);
          }

          else if(nMode == MODE_PLAYING 
             || nMode == MODE_PLAYINGPAUSED)
          {
            // Останавливаем проигрывание
            mciwioStop(wOutDeviceID);
          }

          // Устанавливаем движок в начало полосы просмотра
          nPosition = 0;
          SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);

          // Новый режим
          nMode = MODE_STOP;
          InvalidateRect(hwnd, NULL, TRUE);
          return 0;
        }

        // -------------------------------------------------
        // Строка "Pause!"
        // Временный останов проигрывания или
        // полный останов записи wav-файла
        // -------------------------------------------------
        case CM_CTLPAUSE:
        {
          if(nMode == MODE_RECORDING)
          {
            // Останов записи
            mciwioStop(wInDeviceID);
          }

          else if(nMode == MODE_PLAYING)
          {
            // Временный останов проигрывания
            mciwioPause(wOutDeviceID);
            nMode = MODE_PLAYINGPAUSED;
          }

          InvalidateRect(hwnd, NULL, TRUE);
          return 0;
        }

        // -------------------------------------------------
        // Строка "Resume!"
        // Продолжение проигрывания после останова
        // -------------------------------------------------
        case CM_CTLRESUME:
        {
          if(nMode == MODE_PLAYINGPAUSED)
          {
            // Продолжаем проигрывание
            mciwioResume(wOutDeviceID);
            nMode = MODE_PLAYING;
            InvalidateRect(hwnd, NULL, TRUE);
          }
          return 0;
        }

        // -------------------------------------------------
        // Строка "Exit" меню "File"
        // Завершение работы приложения
        // -------------------------------------------------
        case CM_FILEEXIT:
        {
          DestroyWindow(hwnd);
          return 0;
        }
        default:
          return 0;
      }
    }

// ------------------------------------------------------------
// MM_MCINOTIFY
// ------------------------------------------------------------
    case MM_MCINOTIFY:
    {
      // Если находились в режиме записи, сохраняем файл
      if(wInDeviceID)
      {
        MCI_SAVE_PARMS mciSave;
        MCI_GENERIC_PARMS mcigen;
        DWORD dwrc;

        // Имя файла, в котором будет сохранен звуковой фрагмент
        mciSave.lpfilename = "recorded.wav";

        dwrc=mciSendCommand(wInDeviceID, MCI_SAVE,
           MCI_SAVE_FILE | MCI_WAIT, (DWORD)(LPVOID)&mciSave);
        if(dwrc)
        {
          mciwioError(dwrc);
        }

        // Закрываем устройство записи
        dwrc = mciSendCommand(wInDeviceID, MCI_CLOSE, MCI_WAIT,
          (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);
        if(dwrc)
        {
          mciwioError(dwrc);
        }
        wInDeviceID = 0;
      }

      // Если находились в режиме воспроизведения, останавливаем
      // и закрываем устройство вывода
      else if(wOutDeviceID)
      {
        mciwioStop(wOutDeviceID);
        mciwioClose(wOutDeviceID);
        wOutDeviceID=0;
      }

      nMode = MODE_STOP;
      InvalidateRect(hwnd, NULL, TRUE);
      return 0;
    }

// ------------------------------------------------------------
// WM_TIMER
// Сообщение от таймера
// ------------------------------------------------------------
    case WM_TIMER:
    {
      MCI_STATUS_PARMS mciStatus;
      DWORD dwPos;

      // Режим записи
      if(nMode == MODE_RECORDING)
      {
        // Определяем текущую позицию внутри  блока
        mciStatus.dwItem = MCI_STATUS_POSITION;
        mciSendCommand(wInDeviceID, MCI_STATUS,
          MCI_STATUS_ITEM, (DWORD)(LPVOID)&mciStatus);
        dwPos = mciStatus.dwReturn;

        // Вычисляем новое положение движка полосы просмотра
        nPosition = ((DWORD)SCROLL_SIZE * dwPos) / MAXRECORDTIME;

        // Ограничиваем пределы изменения текущей
        // позиции значениями от 1 до SCROLL_SIZE
        if(nPosition > SCROLL_SIZE) nPosition = SCROLL_SIZE;
        if(nPosition < 1) nPosition = 1;

        // Устанавливаем движок полосы просмотра
        // в соответствии с новым значением текущей позиции
        SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);
      }

      // Режим воспроизведения
      else if(nMode == MODE_PLAYING)
      {
        // Определяем текущую позицию внутри блока
        mciStatus.dwItem = MCI_STATUS_POSITION;
        mciSendCommand(wOutDeviceID, MCI_STATUS,
          MCI_STATUS_ITEM, (DWORD)(LPVOID)&mciStatus);
        dwPos = mciStatus.dwReturn;

        // Вычисляем новое положение движка полосы просмотра
        nPosition = ((DWORD)SCROLL_SIZE * dwPos) / dwFileSize;

        // Ограничиваем пределы изменения текущей
        // позиции значениями от 1 до SCROLL_SIZE
        if(nPosition > SCROLL_SIZE) nPosition = SCROLL_SIZE;
        if(nPosition < 1) nPosition = 1;

        // Устанавливаем ползунок полосы просмотра
        // в соответствии с новым значением текущей позиции
        SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);
      }
      return 0;
    }

// ------------------------------------------------------------
// WM_DESTROY
// Уничтожение главного окна приложения
// ------------------------------------------------------------
    case WM_DESTROY:
    {
      // Удаляем таймер и полосу просмотра
      KillTimer(hwnd, BEEP_TIMER);
      DestroyWindow(hScroll);

      // Если находимся в режиме записи, останавливаем
      // запись и закрываем устройство ввода
      if(nMode == MODE_RECORDING || nMode == MODE_RECORDINGPAUSED)
      {
        mciwioStop(wInDeviceID);
        mciwioClose(wInDeviceID);
      }

      else if(fFileLoaded)
      {
      // Если находимся в режиме проигрывания, останавливаем
      // запись и закрываем устройство вывода
        if(nMode == MODE_PLAYING || nMode == MODE_PLAYINGPAUSED)
        {
          mciwioStop(wOutDeviceID);
          mciwioClose(wOutDeviceID);
        }
        nMode = MODE_STOP;
      }
      PostQuitMessage(0);
      return 0;
    }
    default:
      break;
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

При создании главного окна приложения обработчиком сообщения WM_CREATE выполняется инициализация глобальных переменных. Устанавливается текущий режим работы nMode (останов), флаг загрузки файла для проигрывания fFileLoaded, в переменные, соответствующие идентификаторам устройств ввода и вывода (wOutDeviceID и wInDeviceID) записываются нулевые значения.

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

Во время обработки сообщения WM_PAINT в верхней части окна отображается текущий режим работы, для чего используется функция TextOut.

Когда вы выбираете wav-файл для воспроизведения, функция окна получает сообщение WM_COMMAND с параметром wParam, равным значению CM_FILEOPEN. Соответствующий обработчик загружает файл, вызывая функцию mciwioSelectFile. Эта функция определена в нашем приложении, ее исходный текст находится в файле mciwaveio.cpp (листинг 2.9). С помощью функции SetWindowText путь к файлу отображается в заголовке окна приложения.

Если в момент выбора нового файла приложение находилось в режиме проигрывания, следует остановить и закрыть устройство вывода. Для этого мы используем функции mciwioStop и mciwioClose, соответственно. После того, как устройство вывода закрыто, мы сбрасываем содержимое переменной wOutDeviceID (идентификатор устройства вывода). Кроме того, в переменную nMode записываем код нового состояния (останов).

В завершении выполняется установка движка полосы просмотра в начальное положение. В переменную fFileLoaded записывается значение TRUE (загружен файл для воспроизведения).

После выбора строки "Play!" проверяется текущий режим и флаг загрузки файла. Если файл загружен, и приложение находится в состоянии останова, можно начинать воспроизведение. В переменную nMode записывается константа MODE_PLAYING (воспроизведение), открывается устройство вывода (функция mciwioOpen) и запускается воспроизведение (функция mciwioPlay). Для того чтобы название нового режима было отображено на экране, выполняется перерисовка окна (функция InvalidateRect). Исходные тексты функций mciwioOpen и mciwioPlay находятся в файле mciwaveio.cpp (листинг 2.9).

Запись также можно запустить только из состояния останова. Для записи вызывается функция mciwioRecord (ее исходный текст также находится в листинге 2.9), которой в качестве второго параметра передается максимальная длительность записи в миллисекундах.

При выполнении команды останова (строка "Stop!" в меню приложения) анализируется текущий режим работы. Если приложение находится в режиме записи, вызывается функция mciwioStop (останов устройства), причем в качестве параметра ей передается идентификатор устройства ввода. Если же приложение находится в состоянии воспроизведения, вызывается эта же функция, но в качестве параметра ей передается идентификатор устройства вывода. Далее движок полосы просмотра устанавливается в начальное положение, а переменную nMode записывается код состояния останова.

В ответ на команду временного останова (строка "Pause!") выполняется полный останов записи или временный останов воспроизведения. В последнем случае вызывается функция mciwioPause, которой в качестве параметра передается идентификатор устройства вывода. В переменную nMode записывается значение константы MODE_PLAYINGPAUSED, которое обозначает временный останов воспроизведения.

Для продолжения воспроизведения после временного останова вызывается функция mciwioResume, которой в качестве параметра передается идентификатор устройства вывода.

Функция окна приложения обрабатывает сообщение MM_MCINOTIFY, поступающее после завершения записи или воспроизведения. Обработчик этого сообщения сохраняет записанные данные в том случае, если было открыто устройство ввода. Для этого он вызывает функцию mciSendCommand, передавая с ее помощью устройству ввода команду MCI_SAVE_FILE:

mciSave.lpfilename = "recorded.wav";
dwrc=mciSendCommand(wInDeviceID, MCI_SAVE,
   MCI_SAVE_FILE | MCI_WAIT, (DWORD)(LPVOID)&mciSave);

После сохранения устройство ввода закрывается, в переменную wInDeviceID записывается нулевое значение:

dwrc = mciSendCommand(wInDeviceID, MCI_CLOSE, MCI_WAIT,
  (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);
if(dwrc)
{
  mciwioError(dwrc);
}
wInDeviceID = 0;

Если сообщение MM_MCINOTIFY пришло при воспроизведении, устройство вывода останавливается и закрывается. В переменную wOutDeviceID записывается нулевое значение.

В любом случае обработчик сообщения MM_MCINOTIFY переводит приложение в режим останова.

Обработчик сообщения WM_TIMER предназначен для определения и отображения текущей позиции в режимах записи и воспроизведения.

В режиме записи текущая позиция определяется при помощи сообщения MCI_STATUS:

mciStatus.dwItem = MCI_STATUS_POSITION;
mciSendCommand(wInDeviceID, MCI_STATUS,
  MCI_STATUS_ITEM, (DWORD)(LPVOID)&mciStatus);
dwPos = mciStatus.dwReturn;

По умолчанию для звукового устройства ввода/вывода устанавливается формат времени в миллисекундах. Поэтому в переменную dwPos будет записана текущая позиция в миллисекундах.

Далее обработчик вычисляет положение движка полосы просмотра, исходя из диапазона изменения значений полосы просмотра SCROLL_SIZE, максимального времени записи MAXRECORDTIME и текущей позиции dwPos:

nPosition = ((DWORD)SCROLL_SIZE * dwPos) / MAXRECORDTIME;

Затем движок устанавливается в новое положение при помощи функции SetScrollPos.

В режиме воспроизведения новое положение движка просмотра вычисляется исходя из размера файла dwFileSize:

nPosition = ((DWORD)SCROLL_SIZE * dwPos) / dwFileSize;

Размер файла в миллисекундах записывается в глобальную переменную dwFileSize функцией mciwioOpen при открытии файла.

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

Константы для работы с меню определены в файле mciwave.hpp (листинг 2.8).

Листинг 2.8. Файл mciwaver\mciwave.hpp

#define CM_HELPABOUT   301
#define CM_FILEEXIT    302
#define CM_FILEOPEN    303
#define CM_FILESAVEAS  304
#define CM_FILENEW     305

#define CM_CTLPLAY     401
#define CM_CTLRECORD   402
#define CM_CTLRESUME   403
#define CM_CTLPAUSE    404
#define CM_CTLSTOP     405

Файл mciwaveio.cpp (листинг 2.9) содержит функции для работы с интерфейсом сообщений MCI.

Листинг 2.9. Файл mciwaver\mciwavio.cpp

#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <mmsystem.h>
#include <mem.h>
#pragma hdrstop

#include "mciwavio.hpp"

// Глобальные переменные
extern int  nMode;
extern int  nPosition;
extern DWORD dwFileSize;

//-----------------------------------------------------
// mciwioOpen
// Открытие устройства вывода
//-----------------------------------------------------

UINT mciwioOpen(LPSTR szFileName)
{
  MCI_OPEN_PARMS mciOpen;
  MCI_STATUS_PARMS mciStatus;
  DWORD dwrc;
  DWORD dwFlags;

  // Готовим блок параметров
  mciOpen.lpstrDeviceType= (LPSTR)"waveaudio";
  mciOpen.lpstrElementName = (LPSTR)szFileName;
  mciOpen.dwCallback = 0;
  mciOpen.wDeviceID = 0;
  mciOpen.wReserved0 = 0;
  mciOpen.lpstrAlias = NULL;

  // Устанавливаем флаги
  dwFlags = MCI_OPEN_TYPE | 
    MCI_OPEN_ELEMENT | MCI_WAIT;

  // Открываем устройство
  dwrc = mciSendCommand(0, MCI_OPEN,
    dwFlags, (DWORD)(LPVOID)&mciOpen);
  if(dwrc)
  {
    mciwioError(dwrc);
    return 0;
  }

  // Если устройство открыто успешно, определяем
  // длительность звучания в миллисекундах 
  else
  {
    mciStatus.dwItem = MCI_STATUS_LENGTH;
    dwrc = mciSendCommand(mciOpen.wDeviceID, MCI_STATUS,
      MCI_STATUS_ITEM | MCI_WAIT,
      (DWORD)(LPVOID)&mciStatus);
    if(dwrc)
    {
      mciwioError(dwrc);
      return 0;
    }

    // Сохраняем длительность звучания в глобальной
    // переменной и возвращаем идентификатор устройства вывода
    dwFileSize = mciStatus.dwReturn;
    return mciOpen.wDeviceID;
  }
}

//-----------------------------------------------------
// mciwioPlay
// Проигрывание загруженного wav-файла
//-----------------------------------------------------
DWORD mciwioPlay(HWND hwnd, UINT wDeviceID)
{
  MCI_PLAY_PARMS mciPlayParms;
  DWORD dwrc;

  // Позиционирование на начало фрагмента
  dwrc = mciSendCommand(wDeviceID, MCI_SEEK,
    MCI_WAIT | MCI_SEEK_TO_START, NULL);

  // Идентификатор окна, функция которого получит
  // сообщение MM_MCINOTIFY
  mciPlayParms.dwCallback = (DWORD)hwnd;

  // Запускаем проигрывание
  dwrc=mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY,
    (DWORD)(LPVOID)&mciPlayParms);

  return dwrc;
}

//-----------------------------------------------------
// mciwioStop
// Останов проигрывания загруженного wav-файла
//-----------------------------------------------------
DWORD mciwioStop(UINT wDeviceID)
{
  MCI_GENERIC_PARMS mcigen;
  DWORD dwrc;

  dwrc = mciSendCommand(wDeviceID, MCI_STOP, MCI_WAIT,
    (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);
  if(dwrc)
  {
    mciwioError(dwrc);
  }

  return dwrc;
}

//-----------------------------------------------------
// mciwioResume
// Проигрывание после временного останова
//-----------------------------------------------------
DWORD mciwioResume(UINT wDeviceID)
{
  MCI_GENERIC_PARMS mcigen;
  DWORD dwrc;

  dwrc = mciSendCommand(wDeviceID, MCI_RESUME, MCI_WAIT,
    (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);
  if(dwrc)
  {
    mciwioError(dwrc);
  }

  return dwrc;
}

//-----------------------------------------------------
// mciwioPause
// Временный останов проигрывания загруженного wav-файла
//-----------------------------------------------------
DWORD mciwioPause(UINT wDeviceID)
{
  MCI_GENERIC_PARMS mcigen;
  DWORD dwrc;

  dwrc = mciSendCommand(wDeviceID, MCI_PAUSE, MCI_WAIT,
    (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);
  if(dwrc)
  {
    mciwioError(dwrc);
  }

  return dwrc;
}

//-----------------------------------------------------
// mciwioSelectFile
// Выбор wav-файла
//-----------------------------------------------------
BOOL mciwioSelectFile(LPSTR lpszFileName)
{
  OPENFILENAME ofn;

  char szFile[256];
  char szFileTitle[256];
  char szFilter[256] =
         "Wave Files\0*.wav\0Any Files\0*.*\0";
  szFile[0] = '\0';
  memset(&ofn, 0, sizeof(OPENFILENAME));

  // Инициализируем нужные нам поля
  ofn.lStructSize       = sizeof(OPENFILENAME);
  ofn.hwndOwner         = NULL;
  ofn.lpstrFilter       = szFilter;
  ofn.nFilterIndex      = 1;
  ofn.lpstrFile         = szFile;
  ofn.nMaxFile          = sizeof(szFile);
  ofn.lpstrFileTitle    = szFileTitle;
  ofn.nMaxFileTitle     = sizeof(szFileTitle);
  ofn.lpstrInitialDir   = NULL;
  ofn.Flags =   OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST
               | OFN_HIDEREADONLY;
  // Выбираем входной файл
  if (GetOpenFileName(&ofn))
  {
    // Копируем путь к выбранному файлу
    lstrcpy(lpszFileName, (LPSTR)szFile);
    return TRUE;
  }
  else
    return FALSE;
}

//-----------------------------------------------------
// mciwioClose
// Закрытие устройства вывода
//-----------------------------------------------------
void mciwioClose(UINT wDeviceID)
{
  MCI_GENERIC_PARMS mcigen;
  DWORD dwrc;

  dwrc = mciSendCommand(wDeviceID, MCI_CLOSE, MCI_WAIT,
    (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);
  if(dwrc)
  {
    mciwioError(dwrc);
    return;
  }
}

//-----------------------------------------------------
// mciwioError
// Обработка ошибок
//-----------------------------------------------------
void mciwioError(DWORD dwrc)
{
  BYTE szBuf[MAXERRORLENGTH];

  if(mciGetErrorString(dwrc, (LPSTR)szBuf, MAXERRORLENGTH))
    MessageBox(NULL, szBuf,
      "MCIWAVER Error", MB_ICONEXCLAMATION);
  else
    MessageBox(NULL, "Неизвестная ошибка",
      "MCIWAVER Error", MB_ICONEXCLAMATION);
}

//-----------------------------------------------------
// mciwioRecord
// Запись wav-файла
//-----------------------------------------------------
WORD mciwioRecord(HWND hwnd, DWORD dwMSec)
{
  MCI_RECORD_PARMS mciRecordParms;
  MCI_OPEN_PARMS mciOpen;
  DWORD dwrc;
  DWORD dwFlags;
  WORD wInDeviceID;

  // Готовим блок параметров
  // для команды открытия устройства
  mciOpen.lpstrDeviceType  = (LPSTR)"waveaudio";
  mciOpen.lpstrElementName = (LPSTR)"";
  mciOpen.dwCallback = 0;
  mciOpen.wDeviceID = 0;
  mciOpen.wReserved0 = 0;
  mciOpen.lpstrAlias = NULL;

  // Устанавливаем флаги
  dwFlags = MCI_OPEN_TYPE| 
    MCI_OPEN_ELEMENT | MCI_WAIT;

  // Открываем устройство ввода
  dwrc = mciSendCommand(0, MCI_OPEN, dwFlags,
    (DWORD)(LPVOID)&mciOpen);
  if(dwrc)
  {
    mciwioError(dwrc);
    return 0;
  }
  else
  {
    // В случае успеха сохраняем идентификатор
    // устройства ввода в глобальной переменной
    wInDeviceID = mciOpen.wDeviceID;
  }

  // Готовим блок параметров для команды записи
  mciRecordParms.dwTo       = dwMSec;
  mciRecordParms.dwCallback = (DWORD)hwnd;

  // Запускаем запись
  dwrc=mciSendCommand(wInDeviceID,
     MCI_RECORD, MCI_NOTIFY | MCI_TO,
    (DWORD)(LPVOID)&mciRecordParms);
  if(dwrc)
  {
    mciwioError(dwrc);
    return 0;
  }
  return wInDeviceID;
}

Функция mciwioOpen открывает устройство вывода, посылая ему сообщение MCI_OPEN. В блоке параметров мы указываем тип устройства ("waveaudio") и путь к wav-файлу szFileName. Так как тип устройства указан в виде текстовой строки, мы используем флаг MCI_OPEN_TYPE. Необходимо также указать флаг MCI_OPEN_ELEMENT, так как устройство будет работать с файлом. Для того чтобы функция вернула управление только после открытия устройства, используется флаг MCI_WAIT.

Если при открытии устройства произошла ошибка, функция mciSendCommand вернет ненулевой код ошибки. Мы передаем этот код функции mciwioError, которая выводит текстовое описание ошибки на экран.

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

Функция mciwioPlay предназначена для проигрывания загруженного файла с самого начала. Перед запуском воспроизведения текущая позиция устанавливается на начало, для чего используется сообщение MCI_SEEK с флагом MCI_SEEK_TO_START:

dwrc = mciSendCommand(wDeviceID, MCI_SEEK,
  MCI_WAIT | MCI_SEEK_TO_START, NULL);

Далее готовится блок параметров для сообщения MCI_PLAY. В поле dwCallback записывается идентификатор главного окна приложения (из глобальной переменной hwnd). Это окно получит извещение о завершении проигрывания в виде сообщения MM_MCINOTIFY.

Функция mciwioStop посылает устройству, идентификатор которого передается ей через параметр wDeviceID, сообщение MCI_STOP.

Функция mciwioPause предназначена для временного останова проигрывания wav-файла. Она посылает устройству, идентификатор которого передается ей через параметр wDeviceID, сообщение MCI_PAUSE.

С помощью функции mciwioResume можно возобновить прерванный функцией mciwioPause процесс записи или воспроизведения. Эта функция посылает устройству сообщение MCI_RESUME.

Функция mciwioSelectFile предназначена для выбора wav-файла. Она пользуется стандартной диалоговой панелью выбора файла и функцией GetOpenFileName, определенной в DLL-библиотеке commdlg.dll. Мы уже пользовались этой функцией в предыдущих томах "Библиотеки системного программиста". Путь к выбранному файлу копируется в буфер, адрес которого передается функции mciwioSelectFile в качестве единственного параметра.

Функция mciwioClose закрывает устройство ввода или вывода, посылая ему сообщение MCI_CLOSE.

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

Перед началом записи открывается устройство ввода "waveaudio", причем в качестве имени файла используется пустая строка:

mciOpen.lpstrDeviceType  = (LPSTR)"waveaudio";
mciOpen.lpstrElementName = (LPSTR)"";
mciOpen.dwCallback = 0;
mciOpen.wDeviceID = 0;
mciOpen.wReserved0 = 0;
mciOpen.lpstrAlias = NULL;

dwFlags = MCI_OPEN_TYPE | 
  MCI_OPEN_ELEMENT | MCI_WAIT;

dwrc = mciSendCommand(0, MCI_OPEN, dwFlags,
  (DWORD)(LPVOID)&mciOpen);
if(dwrc)
{
  mciwioError(dwrc);
  return 0;
}
else
{
  wInDeviceID = mciOpen.wDeviceID;
}

Далее готовится блок параметров для записи и посылается сообщение MCI_RECORD:

mciRecordParms.dwTo       = dwMSec;
mciRecordParms.dwCallback = (DWORD)hwnd;
dwrc=mciSendCommand(wInDeviceID,
   MCI_RECORD, MCI_NOTIFY | MCI_TO,
  (DWORD)(LPVOID)&mciRecordParms);

При этом указывается конечная позиция для записи dwTo и идентификатор окна, которое получит извещение о завершении процесса записи dwCallback. Для того чтобы перечисленные параметры были приняты во внимание, устанавливаются флаги MCI_TO и MCI_NOTIFY.

Константы и прототипы функций для файла mciwavio.cpp находятся в файле mciwaveio.hpp (листинг 2.10).

Листинг 2.10. Файл mciwaver\mciwavio.hpp

#include <windows.h>
#include <mmsystem.h>

#define MODE_STOP            0
#define MODE_PLAYING         1
#define MODE_RECORDING       2
#define MODE_PLAYINGPAUSED   3
#define MODE_RECORDINGPAUSED 4

UINT  mciwioOpen(LPSTR szFileName);
BOOL  mciwioSelectFile(LPSTR lpszFileName);
void  mciwioClose(UINT wDeviceID);
DWORD mciwioPlay(HWND hwnd, UINT wDeviceID);
void  mciwioError(DWORD dwrc);
DWORD mciwioStop(UINT wDeviceID);
DWORD mciwioPause(UINT wDeviceID);
DWORD mciwioResume(UINT wDeviceID);
WORD  mciwioRecord(HWND hwnd, DWORD dwMSec);

Файл описания ресурсов (листинг 2.11) содержит определение пиктограммы и шаблон меню.

Листинг 2.11. Файл mciwaver\mciwaver.rc

#include "mciwave.hpp"
APPICON ICON "mciwaver.ico"
APP_MENU MENU 
BEGIN
  POPUP "&File"
    BEGIN
      MENUITEM "&Open...", CM_FILEOPEN
      MENUITEM SEPARATOR
      MENUITEM "E&xit",    CM_FILEEXIT
    END

  MENUITEM "&Play!",     CM_CTLPLAY
  MENUITEM "&Stop!",     CM_CTLSTOP
  MENUITEM "Resu&me!",   CM_CTLRESUME
  MENUITEM "P&ause!",    CM_CTLPAUSE
  MENUITEM "&Record!",   CM_CTLRECORD

  POPUP "&Help"
    BEGIN
      MENUITEM "&About...", CM_HELPABOUT
    END
END

Файл определения модуля для приложения MCIWAVER приведен в листинге 2.12.

Листинг 2.12. Файл mciwaver\mciwaver.def

NAME        MCIWAVER
DESCRIPTION 'Приложение MCIWAVER, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   10240
HEAPSIZE    1024
CODE        preload moveable discardable
DATA        preload moveable multiple

2.4. Интерфейс низкого уровня

При необходимости иметь непосредственный доступ к буферам, содержащим звуковые данные, приложение должно использовать интерфейс низкого уровня, обеспечиваемый несколькими функциями с префиксом имени wave, например, waveInOpen, waveOutOpen, waveOutWrite, waveAddBuffer и т. д.

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

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

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

Дальнейшее изложение материала будет происходить по следующему плану.

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

Затем мы расскажем о том, как определить состав установленных в системе драйверов устройств мультимедиа, а также возможности отдельных драйверов.

После этого мы перейдем к описанию процесса воспроизведения и записи на низком уровне. В конце раздела мы опишем приложение WAVE, демонстрирующее способы работы со звуковыми данными на низком уровне.

Формат wav-файла

Данные, имеющие отношение к мультимедиа (звук, видео и т. п.) хранятся в файлах в так называемом RIFF-формате (Resource Interchange File Format - формат файла для обмена ресурсами). Как wav-файлы, содержащие звук, так и avi-файлы, содержащие видеоинформацию, имеют формат RIFF.

Файл в формате RIFF содержит вложенные фрагменты (chunk's ). Внешний фрагмент состоит из заголовка и области данных (рис. 2.3).

Рис. 2.3. Фрагмент "RIFF"

Первое двойное слово заголовка содержит четырехбуквенный код FOURCC, который идентифицирует данные, хранящиеся во фрагменте. Второе двойное слово заголовка - размер области данных в байтах (без учета размера самого заголовка).

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

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

Область, обозначенная на рис. 2.3 как "Данные", может содержать внутри себя другие фрагменты. Для файла, в котором хранятся звуковые данные (wav-файл), эта область содержит идентификатор данных "WAVE", фрагмент формата звуковых данных "fmt " (три символа "fmt" и пробел на конце), а также фрагмент звуковых данных (рис. 2.4). Файл может дополнительно содержать фрагменты других типов, поэтому не следует думать, что заголовок wav-файла имеет фиксированный формат. Например, в файле может присутствовать фрагмент "LIST" или "INFO", содержащий информацию о правах копирования и другую дополнительную информацию. Из-за ограниченного объема книги мы не будем рассматривать форматы других фрагментов, при необходимости вы можете узнать их из документации, которая поставляется в составе Microsoft SDK for Windows 3.1.

Рис. 2.4. Формат wav-файла

Область, обозначенная на рис. 2.4 как "Формат данных", описывает звуковые данные. Формат этой области для файлов PCM (записанных с использованием импульсно-кодовой модуляции) соответствует структуре PCMWAVEFORMAT , определенной в файле mmsystem.h следующим образом:

typedef struct pcmwaveformat_tag {
    WAVEFORMAT  wf;
    WORD        wBitsPerSample;
} PCMWAVEFORMAT;
typedef PCMWAVEFORMAT       *PPCMWAVEFORMAT;
typedef PCMWAVEFORMAT NEAR *NPPCMWAVEFORMAT;
typedef PCMWAVEFORMAT FAR  *LPPCMWAVEFORMAT;

Структура WAVEFORMAT также описана в файле mmsystem.h:

typedef struct waveformat_tag {
    WORD    wFormatTag;      // тип формата
    WORD    nChannels;       // количество каналов (моно или стерео)
    DWORD   nSamplesPerSec;  // частота дискретизации
    DWORD   nAvgBytesPerSec; // скорость потока данных
    WORD    nBlockAlign;     // выравнивание блока данных
} WAVEFORMAT;
typedef WAVEFORMAT       *PWAVEFORMAT;
typedef WAVEFORMAT NEAR *NPWAVEFORMAT;
typedef WAVEFORMAT FAR  *LPWAVEFORMAT;

Поле wFormatTag описывает тип формата звуковых данных. Для импульсно-кодовой модуляции PCM, которая поддерживается стандартной библиотекой mmsystem.dll, в этом поле должно находиться значение WAVE_FORMAT_PCM , определенное в файле mmsystem.h:

#define WAVE_FORMAT_PCM 1

Поле nChannels содержит количество каналов. В нем могут находиться значения 1 (моно) или 2 (стерео).

В поле nSamplesPerSec записана частота дискретизации, то есть количество выборок сигнала в секунду. В этом поле могут находиться стандартные значения (11025 Кгц, 22050 Кгц или 44100 Кгц), либо нестандартные значения, такие как 5000 Кгц или 4400 Кгц. Учтите, что не все драйверы звуковых адаптеров могут работать с нестандартными частотами дискретизации.

Поле nAvgBytesPerSec содержит среднюю скорость потока данных, то есть количество байт в секунду, передаваемых драйверу устройства или получаемых от него. Эта информация может быть использована приложением для оценки размера буфера, необходимого для размещения звуковых данных. Для монофонического сигнала с дискретностью 8 бит численное значение скорости совпадает со значением частоты дискретизации. Для стереофонического сигнала с дискретностью 8 бит она в два раза выше. Точное значение вы можете подсчитать по формуле:

nAvgBytesPerSec = (nChannels * nSamplesPerSec * wBitsPerSample) / 8

В поле nBlockAlign находится выравнивание блока в байтах, которое подсчитывается по формуле:

nBlockAlign = (nChannels * wBitsPerSample) / 8

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

Что же касается формата самих звуковых данных, то он зависит от количества каналов и от дискретности.

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

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

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

Диапазон изменения значений выборок сигнала определяется дискретизацией. Для 8-битовых данных он составляет от 0 до 255 (0xff), причем отсутствию сигнала (полной тишине) соответствует значение 128 (0x80). Для 16-битовых данных диапазон изменения составляет от -32768 (-0x8000) до 32767 (0x7fff), отсутствию сигнала соответствует значение 0.

Функции для работы с файлами

Для чтения или записи wav-файлов вы, конечно, можете использовать стандартные функции или такие функции, как _hread или _hwrite. Однако в библиотеке mmsystem.dll есть более удобные функции, специально предназначенные для работы с файлами в стандарте RIFF. Все эти функции могут работать с блоками памяти большого размера (больше 64 Кбайт), что очень удобно, так как звуковые данные редко помещаются в одном сегменте памяти.

Открытие файла

Для открытия файла предназначена функция mmioOpen , прототип которой есть в файле mmsystem.h. Эта функция может открывать файл для буферизованного или небуферизованного ввода, файл в оперативной памяти. Она может работать с файлами, уже открытыми средствами MS-DOS или использовать дополнительные функции для выполнения нестандартных процедур ввода/вывода. Из-за ограниченного объема книги мы сможем рассмотреть только основные возможности функции mmioOpen, более подробное описание вы сможете найти в документации, которая поставляется вместе с Microsoft SDK.

Функция mmioOpen
HMMIO mmioOpen(
  LPSTR szFilename,      // путь к файлу
  LPMMIOINFO lpmmioinfo, // указатель на структуру MMIOINFO
  DWORD dwOpenFlags);    // флаги для операции открытия

Параметры функции:

szFilename

Дальний указатель на текстовую строку, содержащую путь к открываемому файлу

lpmmioinfo

Указатель на структуру MMIOINFO, которая содержит дополнительные параметры для операции открытия файла. Может быть задан как NULL

dwOpenFlags

Флаги, определяющие режим открытия файла

Возвращаемое значение:

При успехе возвращается идентификатор открытого файла. Этот идентификатор можно использовать только в функциях с префиксом имени mmio. В случае ошибки возвращается значение NULL. Код ошибки можно определить из поля wErrorRet структуры MMIOINFO

Формат структуры MMIOINFO описан в файле mmsystem.h:

typedef struct _MMIOINFO
{
  // Поля общего назначения   
   DWORD      dwFlags;   // общий флаг состояния
   FOURCC     fccIOProc; // код идентификации
                         //   процедуры ввода/вывода
   LPMMIOPROC pIOProc;   // указатель на процедуру ввода/вывода
   UINT       wErrorRet; // код завершения
   HTASK      htask;     // идентификатор локальной процедуры 
                         //   ввода/вывода
 // Поля для буферизованного ввода/вывода
   LONG      cchBuffer;  // размер буфера или 0L
   HPSTR     pchBuffer;  // начало буфера или NULL
   HPSTR     pchNext;    // указатель на следующий байт для 
                         //    чтения или записи
   HPSTR     pchEndRead; // указатель на последний прочитанный 
                         //    байт
   HPSTR     pchEndWrite;// указатель на последний
                         // записанный байт
   LONG      lBufOffset; // дисковое смещение начала буфера
// Поля для процедур ввода/вывода
   LONG      lDiskOffset; // дисковое смещение для следующей
                          // операции чтения или записи
   DWORD     adwInfo[3];  // дополнительные данные для типа  MMIOPROC
// Прочие поля
   DWORD    dwReserved1;   // зарезервировано
   DWORD    dwReserved2;   // зарезервировано
   HMMIO    hmmio;         // идентификатор открытого файла
} MMIOINFO;
typedef MMIOINFO       *PMMIOINFO;
typedef MMIOINFO NEAR *NPMMIOINFO;
typedef MMIOINFO FAR  *LPMMIOINFO;

Структура MMIOINFO позволяет задать многочисленные способы работы с файлами. Можно использовать файлы в памяти, можно определить собственную процедуру для выполнения нестандартного ввода или вывода или работать с идентификаторами файлов, открытых средствами MS-DOS. В простых случаях вы можете указать второй параметр функции mmioOpen как NULL и не использовать структуру MMIOINFO вообще:

hmmio = mmioOpen((LPSTR)lpszFileName, NULL, 
  MMIO_READ | MMIO_ALLOCBUF);

Последний параметр функции mmioOpen предназначен для определения режима открытия файла в виде логической комбинации ИЛИ отдельных флагов. Приведем список флагов.

Флаг Описание режима открытия файла
MMIO_READ Чтение
MMIO_WRITE Запись
MMIO_READWRITE Чтение и запись
MMIO_CREATE Создание нового файла. Если файл с таким именем уже есть, он обрезается до нулевой длины
MMIO_DELETE Удаление файла. Если удаление выполнено без ошибок, возвращается значение TRUE, в противном случае - FALSE
MMIO_PARSE Создание текстовой строки, содержащей полный путь к файлу на базе пути, переданного функции через параметр szFilename. Результат помещается обратно в буфер szFilename
MMIO_EXIST Определяется, существует ли указанный файл, и если существует, для него создается текстовая строка, содержащая полный путь к файлу
MMIO_ALLOCBUF Файл будет открыт для буферизованного ввода/вывода. По умолчанию буфер имеет размер 8 Кбайт. Приложение может изменить размер буфера, указав его в поле cchBuffer в структуре MMIOINFO
MMIO_COMPAT Файл будет открыт в режиме совместимости. В этом режиме он может быть открыт несколько раз
MMIO_EXCLUSIVE Файл будет открыт в монопольном режиме
MMIO_DENYWRITE Другим приложениям запрещено открывать файл на запись
MMIO_DENYREAD Другим приложениям запрещено открывать файл на чтение
MMIO_DENYNONE Другие приложения могут открывать файл и на запись, и на чтение
MMIO_GETTEMP Создание текстовой строки для открытия временного файла. Текстовая строка будет записана в буфер, адрес которого передается через первый параметр. Открытие файла не выполняется

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

hFile = mmioOpen(szFileName, NULL, MMIO_CREATE | MMIO_READWRITE);

Закрытие файла

Если приложение открыло файл функцией mmioOpen, после завершения работы с ним оно должно закрыть этот файл функцией mmioClose .

Функция mmioClose
UINT mmioClose(
  HMMIO hmmio,      // идентификатор открытого файла
  UINT wFlags);     // флаги для операции закрытия файла

Параметры функции:

hmmio

Идентификатор открытого файла, полученный с помощью функции mmioOpen

wFlags

Флаги, определяющие режим закрытия файла. Можно указать флаг MMIO_FHOPEN , при этом функция mmioClose закроет файл, открытый средствами MS-DOS

Возвращаемое значение:

При успехе возвращается нулевое значение. В противном случае - код ошибки

Запись в файл

Для записи в файл, открытый при помощи функции mmioOpen, следует использовать функцию mmioWrite . Эта функция позволяет за один вызов записать в файл блок данных размером, большим 64 Кбайт. После записи выполняется перемещение текущей позиции вперед на количество записанных байт.

Функция mmioWrite
LONG mmioWrite(
  HMMIO hmmio,      // идентификатор открытого файла
  HPSTR hpBuff,     // указатель на буфер с данными
  LONG  dwBytes);   // размер буфера

Параметры функции:

hmmio

Идентификатор открытого файла, полученный с помощью функции mmioOpen

hpBuff

Указатель типа huge на буфер, содержимое которого будет записано в файл

dwBytes

Размер буфера

Возвращаемое значение:

Возвращается количество записанных байт данных или -1 при возникновении ошибки

Чтение из файла

Для чтения файла, открытого при помощи функции mmioOpen, следует использовать функцию mmioRead . Эта функция позволяет за один вызов прочитать из файла блок данных размером, большим чем 64 Кбайт. После чтения выполняется перемещение текущей позиции вперед на количество прочитанных байт.

Функция mmioRead
LONG mmioRead(
  HMMIO hmmio,      // идентификатор открытого файла
  HPSTR hpBuff,     // указатель на буфер с данными
  LONG  dwBytes);   // размер буфера

Параметры функции:

hmmio

Идентификатор открытого файла, полученный с помощью функции mmioOpen

hpBuff

Указатель типа huge на буфер, в который будут прочитаны данные

dwBytes

Размер буфера

Возвращаемое значение:

Возвращается количество прочитанных байт данных или -1 при возникновении ошибки. При достижении конца файла возвращается нулевое значение

Позиционирование

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

Функция mmioSeek
LONG mmioSeek(
  HMMIO hmmio,      // идентификатор открытого файла
  LONG  dwOffset,   // смещение для текущей позиции
  int   nOrigin);   // интерпретация смещения

Параметры функции:

hmmio

Идентификатор открытого файла, полученный с помощью функции mmioOpen

dwOffset

Величина смещения в байтах, на которое будет продвинута текущая позиция в файле. Интерпретация этого значения зависит от параметра nOrigin

nOrigin

Этот параметр определяет способ использования смещения, заданного параметром dwOffset. Можно использовать константы SEEK_SET (смещение от начала файла), SEEK_CUR (смещение от текущей позиции в файле), SEEK_END (смещение от конца файла).

Возвращаемое значение:

Возвращается новое смещение текущей позиции в файле от начала файла (в байтах) или -1 при возникновении ошибки

Функции для работы с RIFF-файлами

Ваше приложение может работать с RIFF-файлами с использованием обычных функций ввода/вывода или с помощью функций, описанных выше (что удобнее). Дополнительно в библиотеке mmsystem.dll есть функции, сильно облегчающие работу с фрагментами RIFF-файлов. Эти функции помогут вам заполнить четырехбайтовый идентификатор фрагмента, найти в файле нужный фрагмент и установить на него (или за него) текущую позицию файла, а также создать новый фрагмент в новом файле.

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

Функция mmioFOURCC
FOURCC mmioFOURCC(
  CHAR ch0,   // первая буква кода
  CHAR ch1,   // вторая буква кода
  CHAR ch2,   // третья буква кода
  CHAR ch3);  // четвертая буква кода

Параметры функции:

ch0, ch1, ch2, ch3

 Коды букв, из которых будет составлен четырехбуквенный код

Возвращаемое значение:

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

Функция mmioFOURCC реализована как макрокоманда, выполняющая упаковку четырех байт в двойное слово:

#define mmioFOURCC(ch0, ch1, ch2, ch3) \
   ((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \
   ((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24))

Для формирования, например, идентификатора фрагмента "WAVE" вы можете использовать эту макрокоманда следующим образом:

FOURCC fourccWaveID;
fourccWaveID = mmioFOURCC('W', 'A', 'V', 'E');

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

Функция mmioStringToFOURCC
FOURCC mmioStringToFOURCC(
  LPCSTR szString, // преобразуемая строка
  UINT   wFlags);  // режим преобразования

Параметры функции:

szString

Указатель на преобразуемую строку, закрытую двоичным нулем

wFlags

Если указан флаг MMIO_TOUPPER , все буквы строки будут преобразованы в заглавные

Возвращаемое значение:

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

Пример использования функции mmioStringToFOURCC:

FOURCC fourccWaveID;
fourccWaveID = mmioStringToFOURCC("wave", MMIO_TOUPPER);

Для создания нового фрагмента в RIFF-файле удобно использовать функцию mmioCreateChunk . Новый фрагмент создается в текущей позиции файла, открытого с помощью функции mmioOpen.

Функция mmioCreateChunk
UINT mmioCreateChunk(
  HMMIO hmmio,      // идентификатор открытого файла
  LPMMCKINFO lpck,  // указатель на структуру MMCKINFO
  UINT   wFlags);   // тип фрагмента

Параметры функции:

hmmio

Идентификатор открытого файла, полученный с помощью функции mmioOpen

lpck

Указатель на структуру MMCKINFO, содержащую информацию о создаваемом фрагменте

wFlags

Если указан флаг MMIO_CREATERIFF , создается фрагмент "RIFF", а если MMIO_CREATELIST - создается фрагмент "LIST"

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки

Структура MMCKINFO и указатели на нее определены в файле mmsystem.h:

typedef struct _MMCKINFO
{
    FOURCC   ckid;
    DWORD    cksize;
    FOURCC   fccType;
    DWORD    dwDataOffset;
    DWORD    dwFlags;
} MMCKINFO;
typedef MMCKINFO       *PMMCKINFO;
typedef MMCKINFO NEAR *NPMMCKINFO;
typedef MMCKINFO FAR  *LPMMCKINFO;

Опишем назначение отдельных полей этой структуры.

Поле Описание
ckid Код, соответствующий четырехбуквенному идентификатору фрагмента
cksize Размер фрагмента в байтах без учета идентификатора фрагмента, поля длины фрагмента и дополнительных байтов выравнивания, которые могут находиться в конце фрагмента
fccType Тип фрагмента
dwDataOffset Смещение области данных относительно начала файла в байтах
dwFlags В этом поле может находиться нулевое значение или флаг MMIO_DIRTY, в последнем случае длина фрагмента может быть изменена, поэтому для ее обновления следует вызвать функцию mmioAscend. Флаг MMIO_DIRTY может быть установлен при создании фрагмента функцией mmioCreateChunk

В приведенном ниже фрагменте кода создается новый файл, подготавливается структура MMCKINFO, а затем создается фрагмент "RIFF", для чего вызывается функция mmioCreateChunk:

hFile = mmioOpen(szFileName, NULL, MMIO_CREATE | MMIO_READWRITE);
if(hFile != NULL)
{
  ck.ckid = MMIO_CREATERIFF;
  ck.cksize = waveiocbIn.lpWaveHdr->dwBytesRecorded
              + sizeof(PCMWAVEFORMAT) + 20;
  ck.fccType = mmioFOURCC('W', 'A', 'V', 'E');
  mmioCreateChunk(hFile, (LPMMCKINFO)&ck, MMIO_CREATERIFF);
}

Более подробно этот фрагмент кода будет описан позже, когда мы будем рассказывать вам о приложении WAVE, работающим с wav-файлами и звуковым адаптером на низком уровне.

Для поиска нужного фрагмента внутри RIFF-файла у вас нет необходимости выполнять побайтовое чтение файла и анализ его внутренней структуры. Найти нужный фрагмент и выполнить позиционирование относительно этого фрагмента вам помогут функции mmioDescend и mmioAscend.

Функция mmioDescend ищет заданный фрагмент начиная с текущей позиции. Если фрагмент найден, текущая позиция устанавливается на область данных. Напомним, что область данных расположена на 8 байт ближе к концу файла от начала фрагмента (рис. 2.3).

Функция mmioDescend
UINT mmioDescend(
  HMMIO hmmio,           // идентификатор открытого файла
  LPMMCKINFO lpck,       // указатель на структуру MMCKINFO
                         //   для текущего фрагмента
  LPMMCKINFO lpckParent, // указатель на структуру MMCKINFO
                         //   для внешнего фрагмента
  UINT   wFlags);        // режим поиска

Параметры функции:

hmmio

Идентификатор открытого файла, полученный с помощью функции mmioOpen

lpck

Указатель на структуру MMCKINFO, в которую будет записана информация о текущем фрагменте

lpckParent

Указатель на структуру MMCKINFO, описывающую внешний фрагмент, внутри которого выполняется поиск. В качестве внешнего фрагмента могут выступать только фрагменты "RIFF" или "LIST". Этот параметр можно указывать как NULL, если внешний фрагмент отсутствует

wFlags

Если указан флаг MMIO_FINDCHUNK , выполняется поиск фрагмента, заданного своим идентификатором, если MMIO_FINDLIST - выполняется поиск фрагмента внутри фрагмента "LIST", если MMIO_FINDRIFF - внутри фрагмента "RIFF".

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки

В приведенном ниже фрагменте кода открывается на чтение wav-файл, затем в нем выполняется поиск фрагментов "WAVE" и "fmt ":

hmmio = mmioOpen((LPSTR)lpszFileName, NULL,
                  MMIO_READ | MMIO_ALLOCBUF);
if(!hmmio)
  return WIOERR_FILEERROR;

memset(&ckRIFF, 0, sizeof(MMCKINFO));
ckRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E');
if(mmioDescend(hmmio, &ckRIFF, NULL, MMIO_FINDRIFF))
{
  mmioClose(hmmio,0);
  return WIOERR_BADFORMAT;
}

memset(&ckFMT, 0, sizeof(MMCKINFO));
ckFMT.ckid = mmioFOURCC('f', 'm', 't', ' ');
if(mmioDescend(hmmio,
  &ckFMT, &ckRIFF, MMIO_FINDCHUNK))
{
  mmioClose(hmmio,0);
  return WIOERR_BADFORMAT;
}

Функция mmioAscend предназначена для продвижения текущей позиции к началу следующего фрагмента.

Функция mmioAscend
UINT mmioAscend(
  HMMIO hmmio,           // идентификатор открытого файла
  LPMMCKINFO lpck,       // указатель на структуру MMCKINFO
  UINT   wFlags);        // режим поиска

Параметры функции:

hmmio

Идентификатор открытого файла, полученный с помощью функции mmioOpen

lpck

Указатель на структуру MMCKINFO, предварительно заполненную функцией mmioDescend или mmioCreatechunk

wFlags

Параметр не используется, необходимо передавать нулевое значение.

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки

Определение возможностей звуковых устройств мультимедиа

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

В системе могут быть установлены устройства для записи и воспроизведения звука методом импульсно-кодовой модуляции PCM (waveform audio), устройства для записи и проигрывания музыкальных MIDI-файлов, дополнительные (auxiliary) устройства, такие, как проигрыватель звуковых компакт-дисков и другие.

Библиотека mmsystem.dll содержит набор функций, с помощью которых приложение может определить состав устройств и их возможности.

Функция waveOutGetNumDevs , не имеющая параметров, возвращает количество устройств, способных воспроизводить звуковые данные, записанные с использованием импульсно-кодовой модуляции. Аналогично, функция waveInGetNumDevs возвращает количество устройств, способных записывать такие данные.

Количество устройств, пригодных для записи и воспроизведения MIDI-файлов, можно узнать при помощи, соответственно, функций midiOutGetNumDevs и midiInGetNumDevs .

Для определения количества дополнительных устройств предназначена функция auxGetNumDevs .

Все перечисленные функции не имеют параметров и возвращают значение типа UINT (количество установленных в системе устройств того или иного типа).

Для определения возможностей устройств используются функции auxGetDevCaps (возможности дополнительных устройств), midiInGetDevCaps (возможности устройств записи в формате MIDI), midiOutGetDevCaps (возможности устройств воспроизведения в формате MIDI), waveInGetDevCaps (возможности устройств записи данных методом импульсно-кодовой модуляции), waveOutGetDevCaps (возможности устройств вывода данных, записанных методом импульсно-кодовой модуляции).

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

Второй параметр является дальним указателем на структуру, формат которой зависит от типа устройства. Это может быть структура AUXCAPS (дополнительное устройство), MIDIINCAPS (устройство ввода данных MIDI), MIDIOUTCAPS (устройство вывода данных MIDI), WAVEINCAPS (устройство ввода методом импульсно-кодовой модуляции), WAVEOUTCAPS (устройство вывода данных, записанных методом импульсно-кодовой модуляции).

Третий параметр - размер соответствующей структуры в байтах.

Все эти структуры и указатели на них определены в файле mmsystem.h.

Структура AUXCAPS выглядит следующим образом:

typedef struct auxcaps_tag {
  UINT    wMid;         // код изготовителя драйвера
  UINT    wPid;         // код устройства
  VERSION vDriverVersion;        // версия драйвера
  char    szPname[MAXPNAMELEN];  // название устройства
  UINT    wTechnology;           // тип устройства
  DWORD   dwSupport;    // поддерживаемые функции
} AUXCAPS;
typedef AUXCAPS       *PAUXCAPS;
typedef AUXCAPS NEAR *NPAUXCAPS;
typedef AUXCAPS FAR  *LPAUXCAPS;

Поля wMid, wPid, vDriverVersion и szPname определены во всех структурах, используемых для определения возможностей устройств мультимедиа.

В поле wMid находится код изготовителя драйвера для устройства (список кодов некоторых фирм-изготовителей есть в приложении 2).

Поле wPid содержит код устройства, назначенный изготовителем (приложение 3).

Старший байт поля vDriverVersion содержит верхний (major) номер версии драйвера устройства, младший - нижний (minor) номер версии драйвера устройства.

В поле szPname располагается описание устройства в виде текстовой строки.

Поле wTechnology специфично для структуры AUXCAPS. В нем могут быть установлены флаги AUXCAPS_CDAUDIO (имеется звуковой вход от внутреннего устройства проигрывания компакт-дисков) и AUXCAPS_AUXIN (предусмотрен звуковой вход от входной линии, расположенной на плате звукового адаптера).

Поле dwSupport может содержать флаги AUXCAPS_VOLUME (есть возможность регулировки громкости) и AUXCAPS_LRVOLUME (есть возможность раздельной регулировки громкости для левого и правого каналов).

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

typedef struct midiincaps_tag {
    UINT    wMid;
    UINT    wPid;
    VERSION vDriverVersion;
    char    szPname[MAXPNAMELEN];
} MIDIINCAPS;
typedef MIDIINCAPS      *PMIDIINCAPS;
typedef MIDIINCAPS NEAR *NPMIDIINCAPS;
typedef MIDIINCAPS FAR  *LPMIDIINCAPS;

Структура MIDIOUTCAPS дополнительно содержит поля wTechnology (тип устройства), wVoices (количество голосов для встроенного синтезатора), wNotes (количество нот для встроенного синтезатора), wChannelMask (количество каналов для встроенного синтезатора) и dwSupport (поддерживаемые функции):

typedef struct midioutcaps_tag {
    UINT    wMid;
    UINT    wPid;
    VERSION vDriverVersion;
    char    szPname[MAXPNAMELEN];
    UINT    wTechnology;
    UINT    wVoices;
    UINT    wNotes;
    UINT    wChannelMask;
    DWORD   dwSupport;
} MIDIOUTCAPS;
typedef MIDIOUTCAPS       *PMIDIOUTCAPS;
typedef MIDIOUTCAPS NEAR *NPMIDIOUTCAPS;
typedef MIDIOUTCAPS FAR  *LPMIDIOUTCAPS;

В поле wTechnology могут находиться значения MOD_MIDIPORT (устройство является аппаратным портом MIDI), MOD_SQSYNTH (устройство является синтезатором с выходным сигналом прямоугольной формы), MOD_FMSYNTH (FM-синтезатор, то есть синтезатор с частотной модуляцией), MOD_MAPPER (устройство отображения Microsoft MIDI Mapper ).

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

Структура WAVEINCAPS определена следующим образом:

typedef struct waveincaps_tag {
    UINT    wMid;
    UINT    wPid;
    VERSION vDriverVersion;
    char    szPname[MAXPNAMELEN];
    DWORD   dwFormats;
    UINT    wChannels;  
} WAVEINCAPS;
typedef WAVEINCAPS       *PWAVEINCAPS;
typedef WAVEINCAPS NEAR *NPWAVEINCAPS;
typedef WAVEINCAPS FAR  *LPWAVEINCAPS;

В поле wChannels находится количество каналов (1 - моно, 2 - стерео).

В поле dwFormats могут располагаться флаги, соответствующие стандартным форматам звуковых данных, с которыми может работать устройство. Флаги объединены при помощи логической операции ИЛИ. Для них в файле mmsystem.h определены символические константы:

Константа Частота дискретизации, количество каналов (моно, стерео) и количество бит для представления выборки сигнала
WAVE_FORMAT_1M08 11.025 Кгц, моно, 8 бит
WAVE_FORMAT_1S08 11.025 Кгц, стерео, 8 бит
WAVE_FORMAT_1M16 11.025 Кгц, моно, 16 бит
WAVE_FORMAT_1S16 11.025 Кгц, стерео, 16 бит
WAVE_FORMAT_2M08 22.05 Кгц, моно, 8 бит
WAVE_FORMAT_2S08 22.05 Кгц, стерео, 8 бит
WAVE_FORMAT_2M16 22.05 Кгц, моно, 16 бит
WAVE_FORMAT_2S16 22.05 Кгц, стерео, 16 бит
WAVE_FORMAT_4M08 44.1 Кгц, моно, 8 бит
WAVE_FORMAT_4S08 44.1 Кгц, стерео, 8 бит
WAVE_FORMAT_4M16 44.1 Кгц, моно, 16 бит
WAVE_FORMAT_4S16 44.1 Кгц, стерео, 16 бит

Структура WAVEOUTCAPS используется для определения возможностей устройств вывода звуковых сигналов с использованием импульсно-кодовой модуляции:

typedef struct waveoutcaps_tag {
    UINT    wMid;
    UINT    wPid;
    VERSION vDriverVersion;
    char    szPname[MAXPNAMELEN];
    DWORD   dwFormats;
    UINT    wChannels;
    DWORD   dwSupport;  
} WAVEOUTCAPS;
typedef WAVEOUTCAPS       *PWAVEOUTCAPS;
typedef WAVEOUTCAPS NEAR *NPWAVEOUTCAPS;
typedef WAVEOUTCAPS FAR  *LPWAVEOUTCAPS;

В этой структуре поля dwFormats и wChannels имеют такое же назначение, что и в только что рассмотренной нами структуре WAVEINCAPS.

Поле dwSupport содержит флаги, соответствующие различным возможностям устройства вывода. Символические константы для них определены в файле mmsystem.h:

Константа Описание
WAVECAPS_PITCH Изменение высоты тона
WAVECAPS_PLAYBACKRATE Изменение скорости проигрывания
WAVECAPS_SYNC Драйвер устройства вывода работает в синхронном режиме (во время проигрывания работа приложений приостанавливается)
WAVECAPS_VOLUME Управление громкостью
WAVECAPS_LRVOLUME Раздельное управление громкостью для левого и правого каналов

Приложение DRVLIST

Приложение DRVLIST (листинг 2.13) поможет вам исследовать конфигурацию драйверов устройств мультимедиа, установленных в системе. Это приложение формирует в текущем каталоге текстовый файл с именем drvlist.txt и записывает в него конфигурацию драйверов.

Листинг 2.13 Файл drvlist\drvlist.cpp

// ----------------------------------------
// Просмотр параметров драйверов
// для системы мультимедиа
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
#include <string.h>

// ===========================================
// Функция WinMain
// ===========================================

#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance,
                HINSTANCE hPrevInstance,
                LPSTR     lpszCmdLine,
                int       nCmdShow)
{
  FILE *out;     // файл для вывода
  int i;         // рабочий счетчик
  char buf[512];  // рабочий буфер

  UINT nMMSystemVersion;

  UINT nNumInDevs, nNumOutDevs;
  UINT nNumAuxDevs;
  UINT nNumMidiInDevs, nNumMidiOutDevs;
  UINT nNumJoyDevs;

  WAVEOUTCAPS wcapsOutCaps;
  WAVEINCAPS  wcapsInCaps;
  AUXCAPS     auxcaps;
  MIDIINCAPS  midicapsInCaps;
  MIDIOUTCAPS midicapsOutCaps;
  JOYCAPS     joycaps;
  TIMECAPS    timecaps; 

  DWORD dwFmt, dwSup;
  UINT wTech;

  // Открываем выходной файл для вывода
  // текста потоком
  if ((out = fopen("drvlist.txt", "wt")) == NULL)
  {
    MessageBox(NULL,
     "Не могу открыть файл drvlist.txt",
     "Ошибка", MB_OK | MB_ICONSTOP);
    return 1;
  }

  // Выводим заголовок файла
  fputs("* ================================= *\n", out);
  fputs("*   DRVLIST, (C) Frolov A.V., 1994  *\n", out);
  fputs("* ================================= *\n", out);

  nMMSystemVersion = mmsystemGetVersion();
  wsprintf(buf, "\nВерсия mmsystem.dll: %d.%d\n\n",
    HIBYTE(nMMSystemVersion),
    LOBYTE(nMMSystemVersion));

  // Выводим строку в файл
  fputs(buf, out);

  nNumInDevs      = waveInGetNumDevs();
  nNumOutDevs     = waveOutGetNumDevs();
  nNumAuxDevs     = auxGetNumDevs();
  nNumMidiInDevs  = midiInGetNumDevs();
  nNumMidiOutDevs = midiOutGetNumDevs();
  nNumJoyDevs     = joyGetNumDevs();

  for(i=0; i<nNumOutDevs; i++)
  {
    waveOutGetDevCaps(i, &wcapsOutCaps, sizeof(WAVEOUTCAPS));

    wsprintf(buf, "\n%s, v. %X, "
                  "wMid=%d, wPid=%d, wChannels=%d\n",
      (LPSTR)wcapsOutCaps.szPname,
      wcapsOutCaps.vDriverVersion,
      wcapsOutCaps.wMid,
      wcapsOutCaps.wPid,
      wcapsOutCaps.wChannels);

    dwFmt = wcapsOutCaps.dwFormats;
    dwSup = wcapsOutCaps.dwSupport;

    if(dwFmt & WAVE_FORMAT_1M08)
      strcat(buf, "11025 КГц, моно,\t8 бит\n");
    if(dwFmt & WAVE_FORMAT_1S08)
      strcat(buf, "11025 КГц, стерео,\t8 бит\n");
    if(dwFmt & WAVE_FORMAT_1M16)
      strcat(buf, "11025 КГц, моно,\t16 бит\n");
    if(dwFmt & WAVE_FORMAT_1S16)
      strcat(buf, "11025 КГц, стерео,\t16 бит\n");

    if(dwFmt & WAVE_FORMAT_2M08)
      strcat(buf, "22050 КГц, моно,\t8 бит\n");
    if(dwFmt & WAVE_FORMAT_2S08)
      strcat(buf, "22050 КГц, стерео,\t8 бит\n");
    if(dwFmt & WAVE_FORMAT_2M16)
      strcat(buf, "22050 КГц, моно,\t16 бит\n");
    if(dwFmt & WAVE_FORMAT_2S16)
      strcat(buf, "22050 КГц, стерео,\t16 бит\n");

    if(dwFmt & WAVE_FORMAT_4M08)
      strcat(buf, "44100 КГц, моно,\t8 бит\n");
    if(dwFmt & WAVE_FORMAT_4S08)
      strcat(buf, "44100 КГц, стерео,\t8 бит\n");
    if(dwFmt & WAVE_FORMAT_4M16)
      strcat(buf, "44100 КГц, моно,\t16 бит\n");
    if(dwFmt & WAVE_FORMAT_4S16)
      strcat(buf, "44100 КГц, стерео,\t16 бит\n");

    if(dwSup & WAVECAPS_PITCH)
      strcat(buf, "Регулировка высоты тона\n");
    if(dwSup & WAVECAPS_PLAYBACKRATE)
      strcat(buf, "Регулировка скорости воспроизведения\n");
    if(dwSup & WAVECAPS_SYNC)
      strcat(buf, "Синхронный драйвер\n");
    if(dwSup & WAVECAPS_VOLUME)
      strcat(buf, "Регулировка громкости\n");
    if(dwSup & WAVECAPS_LRVOLUME)
      strcat(buf, "Раздельная регулировка громкости\n");

    // Выводим строку в файл
    fputs(buf, out);
  }

  for(i=0; i<nNumInDevs; i++)
  {
    waveInGetDevCaps(i, &wcapsInCaps, sizeof(WAVEINCAPS));
    wsprintf(buf, "\n%s, v. %X, "
                  "wMid=%d, wPid=%d, wChannels=%d\n",
      (LPSTR)wcapsInCaps.szPname,
      wcapsInCaps.vDriverVersion,
      wcapsInCaps.wMid,
      wcapsInCaps.wPid,
      wcapsInCaps.wChannels);

    dwFmt = wcapsInCaps.dwFormats;

    if(dwFmt & WAVE_FORMAT_1M08)
      strcat(buf, "11025 КГц, моно,\t8 бит\n");
    if(dwFmt & WAVE_FORMAT_1S08)
      strcat(buf, "11025 КГц, стерео,\t8 бит\n");
    if(dwFmt & WAVE_FORMAT_1M16)
      strcat(buf, "11025 КГц, моно,\t16 бит\n");
    if(dwFmt & WAVE_FORMAT_1S16)
      strcat(buf, "11025 КГц, стерео,\t16 бит\n");

    if(dwFmt & WAVE_FORMAT_2M08)
      strcat(buf, "22050 КГц, моно,\t8 бит\n");
    if(dwFmt & WAVE_FORMAT_2S08)
      strcat(buf, "22050 КГц, стерео,\t8 бит\n");
    if(dwFmt & WAVE_FORMAT_2M16)
      strcat(buf, "22050 КГц, моно,\t16 бит\n");
    if(dwFmt & WAVE_FORMAT_2S16)
      strcat(buf, "22050 КГц, стерео,\t16 бит\n");

    if(dwFmt & WAVE_FORMAT_4M08)
      strcat(buf, "44100 КГц, моно,\t8 бит\n");
    if(dwFmt & WAVE_FORMAT_4S08)
      strcat(buf, "44100 КГц, стерео,\t8 бит\n");
    if(dwFmt & WAVE_FORMAT_4M16)
      strcat(buf, "44100 КГц, моно,\t16 бит\n");
    if(dwFmt & WAVE_FORMAT_4S16)
      strcat(buf, "44100 КГц, стерео,\t16 бит\n");

    // Выводим строку в файл
    fputs(buf, out);
  }

  for(i=0; i<nNumAuxDevs; i++)
  {
    auxGetDevCaps(i, &auxcaps, sizeof(AUXCAPS));

    wsprintf(buf, "\n%s, v. %X, wMid=%d, wPid=%d\n",
      (LPSTR)auxcaps.szPname,
      auxcaps.vDriverVersion,
      auxcaps.wMid,
      auxcaps.wPid);

    wTech = auxcaps.wTechnology;
    dwSup = auxcaps.dwSupport;

    if(wTech & AUXCAPS_CDAUDIO)
      strcat(buf, "Звуковой выход для внутреннего CD-ROM\n");
    if(wTech & AUXCAPS_AUXIN)
      strcat(buf, "Ввод с линии\n");

    if(dwSup & AUXCAPS_VOLUME)
      strcat(buf, "Регулировка громкости\n");
    if(dwSup & AUXCAPS_LRVOLUME)
      strcat(buf, "Раздельная регулировка громкости\n");

    // Выводим строку в файл
    fputs(buf, out);
  }

 for(i=0; i<nNumMidiInDevs; i++)
  {
    midiInGetDevCaps(i, &midicapsInCaps, sizeof(MIDIINCAPS));

    wsprintf(buf, "\n%s, v. %X, wMid=%d, wPid=%d\n",
      (LPSTR)midicapsInCaps.szPname,
      midicapsInCaps.vDriverVersion,
      midicapsInCaps.wMid,
      midicapsInCaps.wPid);

    // Выводим строку в файл
    fputs(buf, out);
  }

 for(i=0; i<nNumMidiOutDevs; i++)
  {
    midiOutGetDevCaps(i, &midicapsOutCaps, sizeof(MIDIOUTCAPS));

    wsprintf(buf, "\n%s, v. %X, wMid=%d, wPid=%d\n",
      (LPSTR)midicapsOutCaps.szPname,
      midicapsOutCaps.vDriverVersion,
      midicapsOutCaps.wMid,
      midicapsOutCaps.wPid);

    // Выводим строку в файл
    fputs(buf, out);
  }

 for(i=0; i<nNumJoyDevs; i++)
  {
    joyGetDevCaps(i, &joycaps, sizeof(JOYCAPS));

    wsprintf(buf, "\n%s, wMid=%d, wPid=%d\n",
      (LPSTR)joycaps.szPname,
      joycaps.wMid,
      joycaps.wPid);

    // Выводим строку в файл
    fputs(buf, out);
  }

  timeGetDevCaps(&timecaps, sizeof(TIMECAPS));

  wsprintf(buf, "\nТаймер: wPeriodMin=%u, wPeriodMax=%u\n",
    (UINT)timecaps.wPeriodMin,
    (UINT)timecaps.wPeriodMax);

  // Выводим строку в файл
  fputs(buf, out);

  // Закрываем файл
  fclose(out);

  MessageBox(NULL,
    "Список драйверов записан "
    "в файл drvlist.txt", "DRVLIST", MB_OK);

  return 0;
}

Приложение создает выходной файл и открывает его на запись с помощью стандартной функции fopen.

Затем вызывается функция mmsystemGetVersion, не имеющая параметров. Она возвращает слово, содержащее версию библиотеки mmsystem.dll. Старший байт этого слова содержит верхний (major) номер версии, младший - нижний (minor). Определенный с помощью этой функции номер версии библиотеки mmsystem.dll преобразуется в текстовую строку (при помощи функции wsprintf) и записывается в выходной файл функцией fputs.

Далее приложение определяет количество устройств мультимедиа, вызывая функции waveInGetNumDevs, waveOutGetNumDevs, auxGetNumDevs, midiInGetNumDevs, midiOutGetNumDevs. Приложение вызывает также функцию joyGetNumDevs , которая возвращает количество джойстиков, установленных в системе.

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

Перед завершением работы приложение определяет возможности таймера, который тоже относится к устройствам мультимедиа. Для этого вызывается функция timeGetDevCaps . Соответствующая структура TIMECAPS и указатели на нее определены в файле mmsystem.h следующим образом:

typedef struct timecaps_tag {
    UINT    wPeriodMin;
    UINT    wPeriodMax;
} TIMECAPS;
typedef TIMECAPS       *PTIMECAPS;
typedef TIMECAPS NEAR *NPTIMECAPS;
typedef TIMECAPS FAR  *LPTIMECAPS;

Поле wPeriodMin определяет минимальное значение периода, которое может использовать таймер, поле wPeriodMax - максимальное значение периода таймера (в миллисекундах). Эти параметры таймера могут быть различными не только для различных систем, но и для различных режимов работы операционной системы Windows (стандартном или расширенном).

Файл описания ресурсов содержит определение пиктограммы (листинг 2.14).

Листинг 2.14. Файл drvlist\drvlist.rc

APPICON ICON "drvlist.ico"

Файл определения модуля для приложения DRVLIST приведен в листинге 2.15.

Листинг 2.15. Файл drvlist\drvlist.def

NAME        DRVLIST
DESCRIPTION 'Приложение DRVLIST, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   5120
HEAPSIZE    1024
CODE        preload moveable discardable
DATA        preload moveable multiple

Приведем образец выходного файла, полученный с помощью приложения DRVLIST, запущенного на компьютере, оснащенном аппаратурой Sound Galaxy NX-Pro мультимедиа Upgrade Kit:

* ================================= *
*   DRVLIST, (C) Frolov A.V., 1994  *
* ================================= *
Версия mmsystem.dll: 1.1

Galaxy Wave-Out, v. 204, wMid=2, wPid=103, wChannels=2
11025 КГц, моно,         8 бит
11025 КГц, стерео, 8 бит
22050 КГц, моно,         8 бит
22050 КГц, стерео, 8 бит
44100 КГц, моно,         8 бит
Регулировка громкости
Раздельная регулировка громкости

Galaxy Wave-In, v. 204, wMid=2, wPid=3, wChannels=2
11025 КГц, моно,         8 бит
11025 КГц, стерео, 8 бит
22050 КГц, моно,         8 бит
22050 КГц, стерео, 8 бит
44100 КГц, моно,         8 бит

Sound Galaxy CD Audio, v. 101, wMid=2, wPid=401
Звуковой выход для внутреннего CD-ROM
Регулировка громкости
Раздельная регулировка громкости

Sound Galaxy Line In, v. 101, wMid=2, wPid=402
Ввод с линии
Регулировка громкости
Раздельная регулировка громкости

Sound Galaxy Microphone, v. 101, wMid=2, wPid=403
Регулировка громкости

Galaxy MIDI-In Port, v. 204, wMid=2, wPid=202
Sound Galaxy OPL3 FM, v. 100, wMid=7, wPid=32
Galaxy MIDI-Out Port, v. 204, wMid=2, wPid=201
Sound Galaxy NX-Pro FM Synth, v. 101, wMid=2, wPid=250
Sound Galaxy OPL3 FM, v. 100, wMid=7, wPid=32
Таймер: wPeriodMin=1, wPeriodMax=65535

Из содержимого файла видно, что в операционной системе Windows используется библиотека mmsystem.dll версии 1.1.

Для вывода звука, записанного с помощью импульсно-кодовой модуляции, используется устройство Galaxy Wave-Out, которое может работать со стандартными частотами дискретизации 11025, 22050 и 44100 Кгц (в действительности устройство Sound Galaxy NX-Pro может работать и с нестандартными значениями частоты дискретизации, однако функция waveOutGetDevCaps на дает возможности определить это). Устройство вывода имеет два канала (то есть способно выводить стереофонический сигнал), причем возможна раздельная регулировка громкости в каждом канале. Для представления одной выборки сигнала используется 8 бит.

В системе установлен также драйвер устройства ввода звуковой информации Galaxy Wave-In, который также является 8-битовым стереофоническим устройством.

Непосредственно на плате звукового адаптера Sound Galaxy NX-Pro имеется интерфейс устройства чтения компакт дисков. В системе установлен драйвер Sound Galaxy CD Audio, позволяющий проигрывать звуковые компакт-диски, а также драйверы других устройств, таких, как устройство ввода сигнала с линии, микрофона и музыкального синтезатора.

Воспроизведение звуковых данных

Для воспроизведения звуковых данных на низком уровне после определения возможностей устройства вывода необходимо открыть устройство. Это можно сделать с помощью функции waveOutOpen .

Функция waveOutOpen
UINT waveOutOpen(
  LPHWAVEOUT lphWaveOut, // указатель на идентификатор устройства
  UINT wDeviceID,        // номер открываемого устройства
  LPWAVEFORMAT lpFormat, // указатель на структуру WAVEFORMAT
  DWORD dwCallback,      // адрес функции обратного вызова
                         //   или идентификатор окна
  DWORD dwCallbackInstance, // данные для функции обратного вызова
  DWORD dwFlags);        // режим открытия устройства

Параметры функции:

lphWaveOut

Дальний указатель на переменную типа HWAVEOUT . В эту переменную будет записан идентификатор устройства вывода, который необходим для выполнения всех операций с устройством. Функция waveOutOpen может быть использована для определения возможности воспроизведения звуковых данных заданного формата (в том числе нестандартного), в этом случае параметр lphWaveOut может иметь значение NULL. Дополнительно в параметре dwFlags следует установить флаг WAVE_FORMAT_QUERY

wDeviceID

Через параметр wDeviceID приложение должно передать функции waveOutOpen номер устройства вывода, которое оно собирается открыть или константу WAVE_MAPPER , определенную в файле mmsystem.h.

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

Обычно приложение использует константу WAVE_MAPPER, при этом функция waveOutOpen пытается самостоятельно выбрать и открыть устройство вывода, подходящее для проигрывания звуковых данных указанного формата

lpFormat

Через параметр lpFormat приложение должно передать функции waveOutOpen адрес заполненной структуры WAVEFORMAT . Эта структура и указатели на нее описаны в файле mmsystem.h:

typedef struct waveformat_tag {
    WORD    wFormatTag;      // тип формата
    WORD    nChannels;       // количество каналов (моно или стерео)
    DWORD   nSamplesPerSec;  // частота дискретизации
    DWORD   nAvgBytesPerSec; // скорость потока данных
    WORD    nBlockAlign;     // выравнивание блока данных
} WAVEFORMAT;
typedef WAVEFORMAT       *PWAVEFORMAT;
typedef WAVEFORMAT NEAR *NPWAVEFORMAT;
typedef WAVEFORMAT FAR  *LPWAVEFORMAT;

Мы уже рассказывали вам об этой структуре в разделе, посвященном формату wav-файлов. Там вы сможете найти подробное описание полей структуры

dwCallback

Через параметр dwCallback вы можете передать функции waveOutOpen адрес функции обратного вызова. Эту функцию будет вызывать драйвер устройства вывода при возникновении событий, имеющих отношение к проигрыванию блока данных. При использовании функции обратного вызова в параметре dwFlags следует установить флаг CALLBACK_FUNCTION .

Неудобство использования функции обратного вызова заключается в том, что она должна располагаться в фиксированном сегменте dll-библиотеки, так как вызов функции выполняется во время обработки прерывания. Кроме того, если функция обратного вызова использует какие-либо данные, то для их хранения следует либо использовать память из фиксированного сегмента данных, либо заказывать ее из глобальной области памяти с параметрами GMEM_MOVEABLE и GMEM_SHARE с последующей фиксацией при помощи функций GlobalLock и GlobalPageLock. Функция обратного вызова не может использовать никакие функции программного интерфейса Windows за исключением функции PostMessage и функций из dll-библиотеки mmsystem.dll, имеющих отношение к службе времени.

Другой более простой способ извещения приложения о возникновении события заключается в посылке сообщений функции окна. Для этого параметр dwCallback должен содержать идентификатор окна. Кроме этого, в параметре dwFlags следует установить флаг CALLBACK_WINDOW

dwCallbackInstance

Идентификатор данных, который передается в функцию обратного вызова. Не используется совместно с флагом CALLBACK_WINDOW

dwFlags

Вы можете указывать в этом поле следующие флаги:

Флаг Описание
WAVE_FORMAT_QUERY Функция waveOutOpen вызывается только для проверки возможности использования формата звуковых данных, определенного в структуре WAVEFORMAT, адрес которой передается через параметр lpFormat. Этим способом вы можете проверить, способно ли устройство работать с нестандартным форматом, например, с нестандартной частотой дискретизации
WAVE_ALLOWSYNC Этот флаг необходимо использовать для открытия синхронного устройства вывода, во время работы которого все приложения блокируются
CALLBACK_WINDOW Для извещения о наступлении событий используется окно, идентификатор которого передается через параметр dwCallback
CALLBACK_FUNCTION Для извещения о наступлении событий используется функция обратного вызова, адрес которой передается через параметр dwCallback

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_BADDEVICEID 

Указан неправильный номер устройства

MMSYSERR_ALLOCATED 

Это устройство уже открыто

MMSYSERR_NOMEM 

Для выполнения операции не хватает памяти

WAVERR_BADFORMAT 

Указанный формат звуковых данных не поддерживается драйвером устройства вывода

WAVERR_SYNC 

Была выполнена попытка открыть синхронное устройство вывода без использования флага WAVE_ALLOWSYNC

Как правило, при проигрывании wav-файлов приложение вызывает функцию waveOutOpen два раза. В первый раз она вызывается для проверки возможности проигрывания звуковых данных заданного формата:

if(waveOutOpen(NULL, WAVE_MAPPER, (WAVEFORMAT FAR *)lpwiocb->lpFmt,
     NULL, 0L, WAVE_FORMAT_QUERY | WAVE_ALLOWSYNC))
{
  // Формат не поддерживается
}

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

rc = waveOutOpen(&hWaveOut, WAVE_MAPPER, 
       (WAVEFORMAT FAR *)lpwiocb->lpFmt,
       (UINT)hwnd, 0L, CALLBACK_WINDOW | WAVE_ALLOWSYNC);

Такая методика позволяет определить возможность работы с нестандартными форматами.

Что же касается структуры WAVEFORMAT, то проще всего заполнить ее непосредственно из заголовка проигрываемого wav-файла, как это мы сделали в приложении WAVE (см. ниже).

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

Блоки данных, передаваемые драйверу, должны быть заказаны как глобальные с флагами GMEM_MOVEABLE и GMEM_SHARE. Вы можете заказать один такой блок и переписать в него содержимое wav-файла (как мы это сделали в приложении WAVE), либо использовать очередь или массив блоков, отдавая блоки драйверу по мере необходимости.

Перед тем как отдать блок драйверу, его надо подготовить при помощи функции waveOutPrepareHeader .

Функция waveOutPrepareHeader
UINT waveOutPrepareHeader(
  HWAVEOUT hWaveOut,      // идентификатор устройства
  LPWAVEHDR lpWaveOutHdr, // указатель на структуру WAVEHDR
  UINT wSize);            // размер структуры WAVEHDR

Параметры функции:

hWaveOut

Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства

lpWaveOutHdr

Через параметр lpWaveOutHdr приложение должно передать функции waveOutPrepareHeader адрес заполненной структуры WAVEHDR , описывающей передаваемый блок данных. Эта структура и указатели на нее описаны в файле mmsystem.h:

typedef struct wavehdr_tag {
  LPSTR lpData;          // адрес блока данных
  DWORD dwBufferLength;  // размер блока данных
  DWORD dwBytesRecorded; // количество записанных байт 
                         //  (используется только при записи)
  DWORD dwUser;          // пользовательские данные
  DWORD dwFlags;         // флаги состояния буфера данных
  DWORD dwLoops;      // кратность проигрывания буфера
                      //   (используется только при воспроизведении)
  struct wavehdr_tag far *lpNext; // зарезервировано
  DWORD       reserved;           // зарезервировано
} WAVEHDR;
typedef WAVEHDR       *PWAVEHDR;
typedef WAVEHDR NEAR *NPWAVEHDR;
typedef WAVEHDR FAR  *LPWAVEHDR;

Заказав блок памяти функцией GlobalAlloc с флагами GMEM_MOVEABLE и GMEM_SHARE, вы должны зафиксировать его функцией GlobalLock. Полученный в результате фиксирования адрес блока следует записать в поле lpData структуры WAVEHDR. Размер блока нужно записать в поле dwBufferLength.

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

Структура WAVEHDR используется не только для воспроизведения, но и для записи. В этом случае после завершения записи блока в поле dwBytesRecorded будет находиться количество записанных байт звуковых данных. При воспроизведении это поле не используется.

Через поле dwUser приложение может передать функции обратного вызова или обработчику сообщения для данного устройства вывода любую дополнительную информацию

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

Флаги Описание
WHDR_DONE Работа с буфером данных закончена. Он был успешно проигран или записан, после чего драйвер вернул буфер приложению
WHDR_BEGINLOOP Данный буфер является первым в цикле. Флаг используется только при воспроизведении. Если необходимо проиграть в цикле только один блок, он должен быть отмечен и флагом WHDR_BEGINLOOP, и флагом WHDR_ENDLOOP
WHDR_ENDLOOP Данный буфер является последним в цикле. Флаг используется только при воспроизведении
WHDR_PREPARED Буфер подготовлен для воспроизведения функцией waveOutPrepareHeader или для записи функцией waveInPrepareHeader

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

Поля lpNext и reserved зарезервированы и не должны использоваться приложением.

wSize

Поле wSize должно содержать размер структуры WAVEHDR

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

MMSYSERR_NOMEM 

Для выполнения операции не хватает памяти

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

Функция waveOutWrite
UINT waveOutWrite(
  HWAVEOUT hWaveOut,      // идентификатор устройства
  LPWAVEHDR lpWaveOutHdr, // указатель на структуру WAVEHDR
  UINT wSize);            // размер структуры WAVEHDR

Параметры функции:

hWaveOut

Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства

lpWaveOutHdr

Через параметр lpWaveOutHdr приложение должно передать функции адрес заполненной структуры WAVEHDR, которая соответствует подготовленному блоку данных

wSize

Поле wSize должно содержать размер структуры WAVEHDR

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

MMSYSERR_UNPREPARED 

Переданный блок данных не был подготовлен функцией waveOutPrepareHeader

Сразу после вызова функции waveOutWrite начинается проигрывание блока.

Если блок будет проигран до конца или если проигрывание блока будет остановлено, функция окна, идентификатор которой был указан при открытии устройства через параметр dwCallback, получит сообщение MM_WOM_DONE .

Через параметр wParam сообщения MM_WOM_DONE передается идентификатор устройства, которое было использовано для проигрывания блока. Параметр lParam содержит адрес структуры WAVEHDR, соответствующей проигранному блоку.

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

После того как приложение получило сообщение MM_WOM_DONE, оно должно передать блок функции waveOutUnprepareHeader, затем разблокировать его функцией GlobalUnlock и освободить (если данный блок памяти больше не нужен) функцией GlobalFree.

Приведем формат вызова функции waveOutUnprepareHeader .

Функция waveOutUnprepareHeader
UINT waveOutUnprepareHeader(
  HWAVEOUT hWaveOut,      // идентификатор устройства
  LPWAVEHDR lpWaveOutHdr, // указатель на структуру WAVEHDR
  UINT wSize);            // размер структуры WAVEHDR

Параметры функции:

hWaveOut

Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства

lpWaveOutHdr

Адрес заполненной структуры WAVEHDR, которая соответствует подготовленному блоку данных

wSize

Поле wSize должно содержать размер структуры WAVEHDR

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

MMSYSERR_STILLPLAYING 

Указанный блок все еще находится в очереди для проигрывания

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

Функция waveOutClose
UINT waveOutClose(
  HWAVEOUT hWaveOut);      // идентификатор устройства

Параметры функции:

hWaveOut

Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

MMSYSERR_STILLPLAYING 

Очередь данного устройства еще содержит блоки для проигрывания

Запись звуковых данных

Процесс записи похож на процесс воспроизведения.

Вначале необходимо открыть устройство записи, вызвав функцию waveInOpen :

Функция waveInOpen
UINT waveInOpen(
  LPHWAVEIN lphWaveIn,   // указатель на идентификатор устройства
  UINT wDeviceID,        // номер открываемого устройства
  LPWAVEFORMAT lpFormat, // указатель на структуру WAVEFORMAT
  DWORD dwCallback,      // адрес функции обратного вызова
                         //   или идентификатор окна
  DWORD dwCallbackInstance, // данные для функции обратного вызова
  DWORD dwFlags);        // режим открытия устройства

Параметры функции:

lphWaveOut

Дальний указатель на переменную типа HWAVEIN . В эту переменную будет записан идентификатор устройства ввода, который необходим для выполнения всех операций с устройством. Функция waveOutOpen может быть использована для определения возможности записи звуковых данных в заданном формате (например, нестандартном), в этом случае параметр lphWaveIn может иметь значение NULL. Дополнительно в параметре dwFlags следует установить флаг WAVE_FORMAT_QUERY

wDeviceID

Через параметр wDeviceID приложение должно передать функции waveInOpen номер устройства ввода, которое оно собирается открыть или константу WAVE_MAPPER , определенную в файле mmsystem.h.

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

Если приложение использует константу WAVE_MAPPER, функция waveInOpen пытается самостоятельно выбрать и открыть устройство вывода, подходящее для записи звуковых данных в указанном формате

lpFormat

Через параметр lpFormat приложение должно передать функции waveInOpen адрес заполненной структуры WAVEFORMAT. Мы уже рассказывали вам об этой структуре в предыдущем разделе.

dwCallback

Через параметр dwCallback вы можете передать функции waveInOpen адрес функции обратного вызова. Эту функцию будет вызывать драйвер устройства ввода при возникновении событий, имеющих отношение к записи блока данных. При использовании функции обратного вызова в параметре dwFlags следует установить флаг CALLBACK_FUNCTION.

Можно использовать другой способ извещения приложения о возникновении события, который заключается в посылке сообщений функции окна. Для этого параметр dwCallback должен содержать идентификатор окна. Кроме этого, в параметре dwFlags следует установить флаг CALLBACK_WINDOW

dwCallbackInstance

Идентификатор данных, который передается в функцию обратного вызова. Не используется совместно с флагом CALLBACK_WINDOW

dwFlags

Вы можете указывать в этом поле следующие флаги:

Флаг Описание
WAVE_FORMAT_QUERY Функция waveInOpen вызывается только для проверки возможности использования формата звуковых данных, определенного в структуре WAVEFORMAT
WAVE_ALLOWSYNC Этот флаг необходимо использовать для открытия синхронного устройства ввода, во время работы которого все приложения блокируются
CALLBACK_WINDOW Для извещения о наступлении событий используется окно, идентификатор которого передается через параметр dwCallback
CALLBACK_FUNCTION Для извещения о наступлении событий используется функция обратного вызова, адрес которой передается через параметр dwCallback

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_NODRIVER 

В системе нет нужного для работы с устройством ввода драйвера

MMSYSERR_BADDEVICEID 

Указан неправильный номер устройства

MMSYSERR_ALLOCATED 

Это устройство уже открыто

MMSYSERR_NOMEM 

Для выполнения операции не хватает памяти

WAVERR_BADFORMAT 

Указанный формат звуковых данных не поддерживается драйвером устройства ввода

WAVERR_SYNC 

Была выполнена попытка открыть синхронное устройство ввода без использования флага WAVE_ALLOWSYNC

После открытия устройства ввода необходимо подготовить один или несколько блоков памяти, в который (или которые) будет записана введенная звуковая информация. Требования к блокам памяти, используемым для записи, такие же, как и требования к блокам памяти, используемым для воспроизведения. Они должны быть заказаны как глобальные с флагами GMEM_MOVEABLE и GMEM_SHARE. Вы можете заказать один блок (как мы это сделали в приложении WAVE), либо использовать очередь или массив блоков, отдавая блоки драйверу по мере необходимости, переписывая каждый раз содержимое записанного блока в wav-файл.

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

Функция waveInPrepareHeader
UINT waveInPrepareHeader(
  HWAVEIN hWaveIn,       // идентификатор устройства
  LPWAVEHDR lpWaveInHdr, // указатель на структуру WAVEHDR
  UINT wSize);           // размер структуры WAVEHDR

Параметры функции:

hWaveIn

Идентификатор устройства ввода, полученный от функции waveInOpen при открытии устройства

lpWaveInHdr

Через параметр lpWaveInHdr приложение должно передать функции waveInPrepareHeader адрес заполненной структуры WAVEHDR, описывающей блок данных, в который будет записана введенная звуковая информация. Формат этой структуры был описан в предыдущем разделе.

wSize

Поле wSize должно содержать размер структуры WAVEHDR

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

MMSYSERR_NOMEM 

Для выполнения операции не хватает памяти

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

Функция waveInAddBuffer
UINT waveInAddBuffer(
  HWAVEIN hWaveIn,       // идентификатор устройства
  LPWAVEHDR lpWaveInHdr, // указатель на структуру WAVEHDR
  UINT wSize);           // размер структуры WAVEHDR

Параметры функции:

hWaveIn

Идентификатор устройства ввода, полученный от функции waveInOpen при открытии устройства

lpWaveInHdr

Через параметр lpWaveInHdr приложение должно передать функции адрес заполненной структуры WAVEHDR, которая соответствует подготовленному блоку данных

wSize

Поле wSize должно содержать размер структуры WAVEHDR

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

MMSYSERR_UNPREPARED 

Переданный блок данных не был подготовлен функцией waveOutPrepareHeader

Для того чтобы устройство ввода могло приступить к записи, его надо запустить, вызвав функцию waveInStart :

Функция waveInStart
UINT waveInStart(HWAVEIN hWaveIn); // идентификатор устройства

Параметры функции:

hWaveIn

Идентификатор устройства ввода, полученный от функции waveInOpen при открытии устройства

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

Запись будет продолжаться до тех пор, пока не будет записан весь буфер или пока устройство ввода не будет остановлено функцией waveInStop :

Функция waveInStop
UINT waveInStop(HWAVEIN hWaveIn); // идентификатор устройства

Параметры функции:

hWaveIn

Идентификатор устройства ввода, полученный от функции waveInOpen при открытии устройства

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

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

Через параметр wParam сообщения MM_WIM_DONE передается идентификатор устройства, которое было использовано для записи блока. Параметр lParam содержит адрес структуры WAVEHDR, соответствующей записанному блоку.

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

После того как приложение получило сообщение MM_WIM_DONE, оно должно передать блок функции waveInUnprepareHeader, затем разблокировать его функцией GlobalUnlock и при необходимости освободить функцией GlobalFree.

Приведем формат вызова функции waveInUnprepareHeader .

Функция waveInUnprepareHeader
UINT waveOutUnprepareHeader(
  HWAVEIN hWaveIn,       // идентификатор устройства
  LPWAVEHDR lpWaveInHdr, // указатель на структуру WAVEHDR
  UINT wSize);           // размер структуры WAVEHDR

Параметры функции:

hWaveIn

Идентификатор устройства вывода, полученный от функции waveInOpen при открытии устройства

lpWaveOutHdr

Адрес заполненной структуры WAVEHDR, которая соответствует подготовленному блоку данных

wSize

Параметр wSize должно содержать размер структуры WAVEHDR

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

MMSYSERR_STILLPLAYING 

Указанный блок все еще находится в очереди

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

Функция waveInClose
UINT waveInClose(
  HWAVEIN hWaveIn);      // идентификатор устройства

Параметры функции:

hWaveIn

Идентификатор устройства ввода, полученный от функции waveInOpen при открытии устройства

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

MMSYSERR_STILLPLAYING 

Очередь данного устройства еще содержит блоки для записи

Другие функции низкого уровня

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

Текстовое описание ошибки

Когда мы рассказывали об использовании функции mciSendString , то упоминали функцию mciGetErrorString, с помощью которой можно преобразовать код ошибки в текстовое описание в виде строки символов. Аналогичная возможность есть и у приложений, работающих со звуковым адаптером на низком уровне. Для выполнения такого преобразования приложение может воспользоваться функцией waveInGetErrorText (для устройства ввода) и waveOutGetErrorText (для устройства вывода).

Приведем описание функции waveInGetErrorText:

Функция waveInGetErrorText
UINT waveInGetErrorText(
   UINT wError,        // код ошибки
   LPSTR lpstrBuffer,  // буфер для записи текстовой строки
   UINT  wLength);     // размер буфера

Параметры функции:

wError

Код ошибки, полученный от функций низкого уровня

lpstrBuffer

Буфер, в который будет записано текстовое описание ошибки

wLength

Размер буфера в байтах. В файле mmsystem.h определена константа MAXERRORLENGTH , которая соответствует размеру самого длинного сообщения об ошибке

Возвращаемое значение:

Функция возвращает нулевое значение при успешном завершении или значение MMSYSERR_BADERRNUM , если переданному коду ошибки не соответствует ни одно текстовое описание

Функция waveOutGetErrorText используется аналогично функции waveInGetErrorText:

Функция waveOutGetErrorText
UINT waveOutGetErrorText(
   UINT wError,        // код ошибки
   LPSTR lpstrBuffer,  // буфер для записи текстовой строки
   UINT  wLength);     // размер буфера

Параметры функции:

Аналогичны параметрам функции waveInGetErrorText

Возвращаемое значение:

Аналогично функции waveInGetErrorText

Определение номера устройства по идентификатору

Если вы открыли устройство ввода или вывода с указанием константы WAVE_MAPPER, функция waveInOpen (или waveOutOpen, если открывается устройство вывода) может использовать любое подходящее устройство, установленное в системе. Для определения номера выбранного устройства по идентификатору, полученному от функций waveInOpen или waveOutOpen, можно использовать, соответственно, функцию waveInGetID или waveOutGetID.

Приведем описание функции waveInGetID :

Функция waveInGetID
UINT waveInGetID(
   HWAVEIN hWaveIn,        // идентификатор устройства ввода
   UINT FAR* lpwDeviceID); // адрес переменной для записи
                           //   номера устройства

Параметры функции:

hWaveIn

Идентификатор устройства ввода, полученный от функции waveInOpen при открытии устройства

lpwDeviceID

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

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

Функция waveOutGetID используется аналогично:

Функция waveOutGetID
UINT waveOutGetID(
   HWAVEOUT hWaveOut,      // идентификатор устройства вывода
   UINT FAR* lpwDeviceID); // адрес переменной для записи
                           //   номера устройства

Параметры функции:

hWaveOut

Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства

lpwDeviceID

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

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

Сброс устройства

Функции waveInReset и waveOutReset выполняют, соответственно, останов устройства ввода или вывода и сброс текущей позиции для устройства в 0.

Функция waveInReset
UINT waveInReset(
   HWAVEIN hWaveIn); // идентификатор устройства ввода

Параметры функции:

hWaveIn

Идентификатор устройства ввода, полученный от функции waveInOpen при открытии устройства

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

Функция waveOutReset
UINT waveOutReset (
   HWAVEOUT hWaveOut); // идентификатор устройства вывода

Параметры функции:

hWaveOut

Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

Запуск устройства

Для запуска устройства ввода используется рассмотренная нами ранее функция waveInStart . Если же нужно продолжить работу приостановленного устройства вывода, следует вызвать функцию waveOutRestart :

Функция waveOutRestart
UINT waveOutRestart(
   HWAVEOUT hWaveOut); // идентификатор устройства вывода

Параметры функции:

hWaveOut

Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

Останов устройства

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

Функция waveOutPause
UINT waveOutPause(
   HWAVEOUT hWaveOut); // идентификатор устройства вывода

Параметры функции:

hWaveOut

Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

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

Функция waveOutBreakLoop
UINT waveOutBreakLoop(
   HWAVEOUT hWaveOut); // идентификатор устройства вывода

Параметры функции:

hWaveOut

Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

Определение текущей позиции

Приложение может определить текущую позицию в блоке при записи или воспроизведении, вызвав функцию waveInGetPosition или waveOutGetPosition, соответственно.

Приведем описание функции waveInGetPosition :

Функция waveInGetPosition
UINT waveInGetPosition(
   HWAVEIN hWaveIn, // идентификатор устройства ввода
   LPMMTIME lpInfo, // указатель на структуру MMTIME
   UNIT wSize);     // размер структуры MMTIME

Параметры функции:

hWaveIn

Идентификатор устройства ввода, полученный от функции waveInOpen при открытии устройства

lpInfo

Указатель на структуру MMTIME . В нее будет записана информация о текущей позиции. Эта структура определена в файле mmsystem.h следующим образом:

typedef struct mmtime_tag {
    UINT    wType;      // формат времени
    union {
        DWORD ms;       // миллисекунды
        DWORD sample;   // выборки
        DWORD cb;       // счетчик байт
        struct {    // формат SMPTE
            BYTE hour;  // часы
            BYTE min;   // минуты
            BYTE sec;   // секунды
            BYTE frame; // фреймы
            BYTE fps;   // фреймы в секунду
            BYTE dummy; // байт для выравнивания
        } smpte;
        struct {    // формат MIDI
            DWORD songptrpos;  // указатель позиции в мелодии
        } midi;
    } u;
} MMTIME;
typedef MMTIME       *PMMTIME;
typedef MMTIME NEAR *NPMMTIME;
typedef MMTIME FAR  *LPMMTIME;

Перед вызовом функции waveInGetPosition необходимо записать в поле wType нужный формат времени. Можно использовать следующие значения:

Значение Описание
TIME_MS Время измеряется в миллисекундах, при этом в объединении u следует использовать поле ms
TIME_SAMPLES Время измеряется в выборках сигнала, при этом в объединении u следует использовать поле sample
TIME_BYTES Для измерения времени выполняется подсчет байтов данных, в объединении u следует использовать поле cb
TIME_SMPTE Время измеряется в так называемом формате SMPTE (Society of Motion Picture and Television Engineers), при этом в объединении u следует использовать структуру smpte. Для поля fps возможны значения 24, 25, 29 или 30 фреймов (кадров) в секунду
TIME_MIDI Время измеряется в формате MIDI (Musical Instruments Digital Interface), при этом в объединении u следует использовать структуру midi
wSize

Размер структуры MMTIME в байтах

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

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

Для определения текущей позиции устройства вывода следует использовать функцию waveOutGetPosition :

Функция waveOutGetPosition
UINT waveOutGetPosition(
   HWAVEOUT hWaveOut, // идентификатор устройства вывода
   LPMMTIME lpInfo,   // указатель на структуру MMTIME
   UNIT wSize);       // размер структуры MMTIME

Параметры функции:

hWaveOut

Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства

lpInfo

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

wSize

Размер структуры MMTIME в байтах

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

Управление громкостью

Ваше приложение может управлять громкостью сигнала при его воспроизведении. Для установки громкости следует использовать функцию waveOutSetVolume :

Функция waveOutSetVolume
UINT waveOutSetVolume(
   UINT  wDeviceID,  // номер устройства вывода
   DWORD dwVolume);  // громкость

Параметры функции:

wDeviceID

Параметр wDeviceID служит для выбора устройства. Заметим, что для функции waveOutSetVolume нужно указывать не идентификатор открытого устройства, а номер устройства, который может изменяться от 0 и до значения, определенного с помощью функции waveOutGetNumDevs. Если известен только идентификатор открытого устройства, номер этого устройства можно получить, вызвав функцию waveOutGetID, рассмотренную нами ранее

dwVolume

Младшее слово параметра dwVolume задает громкость для левого канала (или единственного монофонического канала), старшее - для правого. Максимальной громкости соответствует значение 0xffff, минимальной - 0x0000. Промежуточные значения интерпретируются в логарифмическом масштабе

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

MMSYSERR_NOTSUPPORTED 

Функция не поддерживается драйвером

MMSYSERR_NODRIVER 

В системе нет нужного драйвера

Как правило, вместе со звуковым адаптером проставляется приложение, выполняющее функции единой управляющей панели, с помощью которой можно регулировать громкость и тембр звука для разных каналов и устройств. Если ваше приложение изменяет громкость, перед завершением своей работы оно должно восстановить первоначальный уровень громкости (если от него не требуется обратного). Это можно сделать, если перед изменением определить текущий уровень громкости с помощью функции waveOutGetVolume :

Функция waveOutGetVolume
UINT waveOutGetVolume(
   UINT  wDeviceID,     // номер устройства вывода
   LPDWORD lpdwVolume); // текущая громкость

Параметры функции:

wDeviceID

Параметр wDeviceID содержит номер устройства, который может изменяться от 0 и до значения, определенного с помощью функции waveOutGetNumDevs

lpdwVolume

Указатель на переменную размером в двойное слово, в которую будет записано значение, соответствующее текущей громкости для левого и правого каналов. Младшее слово переменной будет содержать громкость для левого канала (или монофонического канала), старшее - для правого. Максимальной громкости соответствует значение 0xffff, минимальной - 0x0000. Промежуточные значения интерпретируются в логарифмическом масштабе

Возвращаемое значение:

При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки:

MMSYSERR_INVALHANDLE 

Указан неправильный идентификатор устройства

MMSYSERR_NOTSUPPORTED 

Функция не поддерживается драйвером

MMSYSERR_NODRIVER 

В системе нет нужного драйвера

Приложение WAVE

В качестве примера использования интерфейса нижнего уровня для записи и воспроизведения wav-файлов мы представим вам исходные тексты приложения WAVE (рис. 2.5).

Рис. 2.5. Главное окно приложения WAVE

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

Приложение WAVE заказывает два глобальных блока памяти, один из которых используется при записи, другой - при воспроизведении. Исходный текст основного модуля приложения представлен в листинге 2.16.

Листинг 2.16. Файл wave\wave.cpp

// ----------------------------------------
// Приложение WAVE
// Проигрывание и запись wav-файлов на низком уровне
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <mem.h>
#pragma hdrstop

#include "wave.hpp"
#include "waveio.hpp"

// Идентификатор таймера
#define BEEP_TIMER 1

// Идентификатор полосы просмотра
#define ID_SCROLL 10

// Длина полосы просмотра
#define SCROLL_SIZE 400

// Прототипы функций
BOOL    InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
BOOL    WAVELoad(LPWAVEIOCB lpwiocb);

// Глобальные переменные
HWAVEOUT hWaveOut;
WAVEIOCB waveiocbOut;
HWAVEIN  hWaveIn;
WAVEIOCB waveiocbIn;
int      nMode = MODE_STOP;
MMTIME   mmtimeIn, mmtimeOut;
BOOL     fNeedSave = FALSE;
BOOL     fFileLoaded = FALSE;
int      nPosition;
HWND     hScroll;
char const szClassName[]   = "WaveClass";
char const szWindowTitle[] = "Wave Player/Recorder";
HINSTANCE  hInst;

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR lpszCmdLine, int nCmdShow)
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  if(hPrevInstance)
    return FALSE;

  if(!InitApp(hInstance))
    return FALSE;

  hInst = hInstance;

  hwnd = CreateWindow(
    szClassName,         // имя класса окна
    szWindowTitle,       // заголовок окна
    WS_OVERLAPPEDWINDOW, // стиль окна
    CW_USEDEFAULT,       // размеры и расположение окна
    CW_USEDEFAULT,       
    450, 120, 0, 0, hInstance, NULL);
                       
  if(!hwnd)
    return FALSE;

  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  while(GetMessage(&msg, 0, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

// =====================================
// Функция InitApp
// Выполняет регистрацию класса окна
// =====================================

BOOL
InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации

  memset(&wc, 0, sizeof(wc));
  wc.lpszMenuName  = "APP_MENU";
  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = (WNDPROC) WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = LoadIcon(hInstance, "APPICON");
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  wc.lpszClassName = (LPSTR)szClassName;

  aWndClass = RegisterClass(&wc);
  return (aWndClass != 0);
}

// =====================================
// Функция WndProc
// =====================================

LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  PAINTSTRUCT ps;
  int rc;

  switch (msg)
  {

// ------------------------------------------------------------
// WM_CREATE
// Создание главного окна приложения
// ------------------------------------------------------------
    case WM_CREATE:
    {
       nMode       = MODE_STOP;
       fNeedSave   = FALSE;
       fFileLoaded = FALSE;
       hWaveIn     = NULL;
       hWaveOut    = NULL;

       // Создаем таймер
       SetTimer(hwnd, BEEP_TIMER, 100, NULL);

       // Создаем полосу просмотра
       hScroll = CreateWindow("scrollbar", NULL,
         WS_CHILD | WS_VISIBLE | SBS_HORZ,
         10, 40, SCROLL_SIZE, 15, hwnd,
         (HMENU) ID_SCROLL, hInst, NULL);

       // Устанавливаем текущую позицию
       nPosition = 0;

       // Устанавливаем минимальное и максимальное
       // значения для полосы просмотра
       SetScrollRange(hScroll, SB_CTL, 1, SCROLL_SIZE, TRUE);

       // Устанавливаем ползунок
       SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);
       return 0;
    }

// ------------------------------------------------------------
// WM_PAINT
// Рисование в окне
// ------------------------------------------------------------
    case WM_PAINT:
    {
      // Получаем контекст отображения для
      // рисования во внутренней области окна 
      hdc = BeginPaint(hwnd, &ps);

      // Отображаем текущий режим работы
      if(nMode == MODE_STOP)
        TextOut(hdc, 10, 10, "Остановлено", 11);
      else if(nMode == MODE_RECORDING)
        TextOut(hdc, 10, 10, "Идет запись...", 14);
      else if(nMode == MODE_PLAYING)
        TextOut(hdc, 10, 10, "Идет проигрывание...", 20);
      else if(nMode == MODE_RECORDINGPAUSED)
        TextOut(hdc, 10, 10, "Запись остановлена", 18);
      else if(nMode == MODE_PLAYINGPAUSED)
        TextOut(hdc, 10, 10, "Проигрывание остановлено", 24);
      else
        TextOut(hdc, 10, 10, "Неправильный режим!", 19);

      // Освобождаем контекст отображения
      EndPaint(hwnd, &ps);
      return 0;
    }

// ------------------------------------------------------------
// WM_COMMAND
// Обработка сообщений от меню
// ------------------------------------------------------------
    case WM_COMMAND:
    {
      switch (wParam)
      {
        // -------------------------------------------------
        // Строка "About" меню "Help"
        // -------------------------------------------------
        case CM_HELPABOUT:
        {
          MessageBox(hwnd,
            "Wave, v.1.0\nWave Player/Recorder\n"
            "(C) Frolov A.V., 1994",
            "About Wave", MB_OK | MB_ICONINFORMATION);
          return 0;
        }

        // -------------------------------------------------
        // Строка "Open" меню "File"
        // -------------------------------------------------
        case CM_FILEOPEN:
        {
          // Если файл уже был загружен, возвращаем WAVE в
          // исходное состояние
          if(fFileLoaded)
          {
            if(hWaveOut)
            {
              // Останавливаем устройство вывода
              rc=waveOutReset(hWaveOut);
              if(rc) wioOutError(rc);

              // Закрываем устройство вывода
              rc=waveOutClose(hWaveOut);
              if(rc) wioOutError(rc);
            }

            // Освобождаем буфера, предназначенные для вывода
            GlobalFreePtr(waveiocbOut.lpWaveHdr);
            GlobalFreePtr(waveiocbOut.lpData);
            GlobalFreePtr(waveiocbOut.lpFmt);
          }

          // Загружаем новый файл
          if(!WAVELoad(&waveiocbOut))
            return 0;

          // Устанавливаем флаг загрузки файла
          fFileLoaded = TRUE;
          return 0;
        }

        // -------------------------------------------------
        // Строка "Play!"
        // Проигрывание загруженного wav-файла
        // -------------------------------------------------
        case CM_CTLPLAY:
        {
       if(nMode == MODE_STOP)
          {
            // Если файл загружен и не проигрывается,
            // запускаем проигрывание файла
            if((fFileLoaded == TRUE) && (nMode != MODE_PLAYING))
            {
              // Новый режим
              nMode = MODE_PLAYING;

              // Перерисовываем окно для отображения строки,
              // соответствующей новому режиму
              InvalidateRect(hwnd, NULL, TRUE);

              // Проигрываем файл
              rc=wioPlay(&waveiocbOut, hwnd);
              if(rc) wioOutError(rc);
            }
          }
          return 0;
        }

        // -------------------------------------------------
        // Строка "Record!"
        // Запись wav-файла
        // -------------------------------------------------
        case CM_CTLRECORD:
        {
          // Запись возможна только из состояния останова
          if(nMode == MODE_STOP)
          {
            nMode = MODE_RECORDING;
            InvalidateRect(hwnd, NULL, TRUE);

            // Требуется сохранить записанный файл
            fNeedSave = TRUE;

            // Запись файла
            wioRecord(&waveiocbIn, hwnd);
          }
          return 0;
        }

        // -------------------------------------------------
        // Строка "Stop!"
        // Останов проигрывания или записи wav-файла
        // -------------------------------------------------
        case CM_CTLSTOP:
        {
          if(nMode == MODE_RECORDING || 
             nMode == MODE_RECORDINGPAUSED)
          {
            if(hWaveIn)
            {
              // Останавливаем запись
              rc=waveInReset(hWaveIn);
              if(rc) wioInError(rc);

              // Закрываем устройство записи
              rc=waveInClose(hWaveIn);
              if(rc) wioInError(rc);
              hWaveIn = 0;
            }
          }

          else if(nMode == MODE_PLAYING || 
                  nMode == MODE_PLAYINGPAUSED)
          {
            if(hWaveOut)
            {
              // Останавливаем проигрывание
              rc=waveOutReset(hWaveOut);
              if(rc) wioOutError(rc);

              // Закрываем устройство вывода
              rc=waveOutClose(hWaveOut);
              if(rc) wioOutError(rc);
              hWaveOut = 0;
            }
          }

          // Устанавливаем движок в начало полосы просмотра
          nPosition = 0;
          SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);

          // Новый режим
          nMode = MODE_STOP;
          InvalidateRect(hwnd, NULL, TRUE);
          return 0;
        }

        // -------------------------------------------------
        // Строка "Pause!"
        // Временный останов проигрывания или
        // полный останов записи wav-файла
        // -------------------------------------------------
        case CM_CTLPAUSE:
        {
          if(nMode == MODE_RECORDING)
          {
            // Останов записи
            rc=waveInStop(hWaveIn);
            if(rc) wioInError(rc);
            nMode = MODE_STOP;
          }

          else if(nMode == MODE_PLAYING)
          {
            // Временный останов проигрывания
            rc=waveOutPause(hWaveOut);
            if(rc) wioOutError(rc);
            nMode = MODE_PLAYINGPAUSED;
          }

          InvalidateRect(hwnd, NULL, TRUE);
          return 0;
        }

        // -------------------------------------------------
        // Строка "Resume!"
        // Продолжение проигрывания после останова
        // -------------------------------------------------
        case CM_CTLRESUME:
        {
          if(nMode == MODE_PLAYINGPAUSED)
          {
            rc=waveOutRestart(hWaveOut);
            if(rc) wioOutError(rc);
            nMode = MODE_PLAYING;
            InvalidateRect(hwnd, NULL, TRUE);
          }
          return 0;
        }

        // -------------------------------------------------
        // Строка "Exit" меню "File"
        // Завершение работы приложения
        // -------------------------------------------------
        case CM_FILEEXIT:
        {
          DestroyWindow(hwnd);
          return 0;
        }

        default:
          return 0;
      }
    }

// ------------------------------------------------------------
// MM_WOM_DONE
// Завершение проигрывания блока
// ------------------------------------------------------------
    case MM_WOM_DONE:
    {
      nMode = MODE_STOP;
      nPosition = 0;
      SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);
      InvalidateRect(hwnd, NULL, TRUE);

      // Удаляем блок из очереди проигрывания
      waveOutUnprepareHeader((HWAVEOUT)wParam,
       (LPWAVEHDR)lParam, sizeof(WAVEHDR));

      // Останавливаем и закрываем устройство вывода
      waveOutReset((HWAVEOUT)wParam);
      waveOutClose((HWAVEOUT)wParam);
      hWaveOut = 0;
      return 0;
    }

// ------------------------------------------------------------
// MM_WIM_DATA
// Завершение записи блока
// ------------------------------------------------------------
    case MM_WIM_DATA:
    {
      nMode = MODE_STOP;
      nPosition = 0;
      SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);
      InvalidateRect(hwnd, NULL, TRUE);

      // Удаляем блок из очереди записи
      waveInUnprepareHeader((HWAVEIN)wParam,
       (LPWAVEHDR)lParam, sizeof(WAVEHDR));

      // Сохраняем записанный блок в файле
      wioFileSave("RECORDED.WAV");
      fNeedSave = FALSE;

      // Освобождаем буфера, связанные с блоком
      GlobalFreePtr(waveiocbIn.lpWaveHdr);
      GlobalFreePtr(waveiocbIn.lpData);

      // Останавливаем и закрываем устройство ввода
      waveInReset((HWAVEIN)wParam);
      waveInClose((HWAVEIN)wParam);
      hWaveIn = 0;
      return 0;
    }

// ------------------------------------------------------------
// WM_TIMER
// Сообщение от таймера
// ------------------------------------------------------------
    case WM_TIMER:
    {
      if(nMode == MODE_RECORDING)
      {
        // Определяем текущую позицию внутри проигрываемого блока
        mmtimeIn.wType = TIME_SAMPLES;
        waveInGetPosition(hWaveIn,
          (LPMMTIME)&mmtimeIn, sizeof(MMTIME));

        // Вычисляем новое положение движка полосы просмотра
        nPosition =
          ((DWORD)SCROLL_SIZE * mmtimeIn.u.sample) / MAXSAMPLES;

        // Ограничиваем пределы изменения текущей
        // позиции значениями от 1 до SCROLL_SIZE
        if(nPosition > SCROLL_SIZE) nPosition = SCROLL_SIZE;
        if(nPosition < 1) nPosition = 1;

        // Устанавливаем ползунок полосы просмотра
        // в соответствии с новым значением текущей позиции
        SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);
      }

      else if(nMode == MODE_PLAYING)
      {
        // Определяем текущую позицию внутри записываемого блока
        mmtimeOut.wType = TIME_SAMPLES;
        waveOutGetPosition(hWaveOut,
        (LPMMTIME)&mmtimeOut, sizeof(MMTIME));

        // Вычисляем новое положение движка полосы просмотра
        nPosition =
          ((DWORD)SCROLL_SIZE * mmtimeOut.u.sample) /
           (waveiocbOut.dwDataSize / waveiocbOut.wBytesPerSample);

        // Ограничиваем пределы изменения текущей
        // позиции значениями от 1 до SCROLL_SIZE
        if(nPosition > SCROLL_SIZE) nPosition = SCROLL_SIZE;
        if(nPosition < 1) nPosition = 1;

        // Устанавливаем ползунок полосы просмотра
        // в соответствии с новым значением текущей позиции
        SetScrollPos(hScroll, SB_CTL, nPosition, TRUE);
      }
      return 0;
    }

// ------------------------------------------------------------
// WM_DESTROY
// Уничтожение главного окна приложения
// ------------------------------------------------------------
    case WM_DESTROY:
    {
      // Удаляем таймер и полосу просмотра
      KillTimer(hwnd, BEEP_TIMER);
      DestroyWindow(hScroll);

      // Если находимся в режиме записи, останавливаем
      // запись и закрываем устройство ввода
      if(nMode == MODE_RECORDING || nMode == MODE_RECORDINGPAUSED)
      {
        if(hWaveIn)
        {
          rc=waveInReset(hWaveIn);
            if(rc) wioInError(rc);
          rc=waveInClose(hWaveIn);
            if(rc) wioInError(rc);
        }
      }

      // Если запись началась, но еще не закончилась, после
      // остановки записи удаляем буфера
      if(fNeedSave)
      {
        GlobalFreePtr(waveiocbIn.lpWaveHdr);
        GlobalFreePtr(waveiocbIn.lpData);
      }

      else if(fFileLoaded)
      {
      // Если находимся в режиме проигрывания, останавливаем
      // запись и закрываем устройство вывода
        if(nMode == MODE_PLAYING || nMode == MODE_PLAYINGPAUSED)
        {
          if(hWaveOut)
          {
            rc=waveOutReset(hWaveOut);
            if(rc) wioOutError(rc);

            rc=waveOutUnprepareHeader(hWaveOut,
               waveiocbOut.lpWaveHdr, sizeof(WAVEHDR));
            if(rc) wioOutError(rc);

            rc=waveOutClose(hWaveOut);
            if(rc) wioOutError(rc);
          }
        }

        nMode = MODE_STOP;

        // Освобождаем буфера
        GlobalFreePtr(waveiocbOut.lpWaveHdr);
        GlobalFreePtr(waveiocbOut.lpData);
        GlobalFreePtr(waveiocbOut.lpFmt);
      }
      PostQuitMessage(0);
      return 0;
    }
    default:
      break;
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

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

Обработчик сообщения WM_PAINT отображает в верхней части главного окна текстовую строку, соответствующую текущему режиму работы приложения (переменная nMode).

При выборе из меню "File" строки "Open" приложение проверяет, не был ли раньше загружен файл. Если файл был загружен и устройство вывода открыто, это устройство останавливается функцией waveOutReset и закрывается функцией waveOutClose. После этого освобождаются буфера, которые использовались для вывода.

Затем вызывается функция WAVELoad, определенная в нашем приложении в файле waveio.cpp (листинг 2.18). В переменной fFileLoaded устанавливается признак того, что файл загружен и готов для воспроизведения.

Для проигрывания файла нужно выбрать из главного меню приложения строку "Play!". Соответствующий обработчик проверит текущий режим работы приложения. Если wav-файл загружен, и приложение уже не находится в режиме воспроизведения, вызывается функция wioPlay, выполняющая проигрывание файла. Эта функция также определена в нашем приложении в файле waveio.cpp.

С помощью строки "Record!" главного меню приложения можно включить режим записи (если приложение находится в состоянии останова). При этом устанавливается флаг fNeedSave, который используется как признак необходимости сохранения записанных звуковых данных в wav-файле. Запись выполняется функцией wioRecord, определенной в файле waveio.cpp.

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

Строка "Pause!" главного меню приложения предназначена для останова записи и временного останова воспроизведения. В режиме записи устройство останавливается функцией waveInStop. В режиме воспроизведения вызывается функция waveOutPause, выполняющая временный останов.

Для продолжения воспроизведения после временного останова из главного меню приложения следует выбрать строку "Resume!". В этом случае будет вызвана функция waveOutRestart, которая возобновит работу устройства вывода.

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

В режиме записи при достижении конца блока памяти или при останове записи главное окно приложения получит сообщение MM_WIM_DATA. Обработчик этого сообщения также установит полосу просмотра в исходное состояние и удалит блок из очереди записи, вызвав функцию waveInUnprepareHeader. Затем содержимое блока будет сохранено в wav-файле с именем recorded.wav, который будет создан или перезаписан в текущем каталоге. Для записи файла вызывается функция wioFileSave, определенная в файле waveio.cpp.

Далее освобождаются буфера, использовавшиеся при записи, устройство записи останавливается и закрывается.

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

В режиме записи с помощью функции waveInGetPosition определяется текущая позиция в формате TIME_SAMPLES:

mmtimeIn.wType = TIME_SAMPLES;
waveInGetPosition(hWaveIn, (LPMMTIME)&mmtimeIn, sizeof(MMTIME));

Новое положение движка полосы просмотра в режиме записи определяется на основании текущей позиции и максимального размера буфера записи MAXSAMPLES, заданного как константа:

nPosition = ((DWORD)SCROLL_SIZE * mmtimeIn.u.sample) / MAXSAMPLES;

В режиме воспроизведения для вычисления положения движка используется размер загруженного wav-файла:

nPosition = ((DWORD)SCROLL_SIZE * mmtimeOut.u.sample) /
  (waveiocbOut.dwDataSize / waveiocbOut.wBytesPerSample);

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

Файл wave.hpp содержит определения символических имен констант (листинг 2.17).

Листинг 2.15. Файл wave\wave.hpp

#define CM_HELPABOUT   301
#define CM_FILEEXIT    302
#define CM_FILEOPEN    303
#define CM_FILESAVEAS  304
#define CM_FILENEW     305

#define CM_CTLPLAY     401
#define CM_CTLRECORD   402
#define CM_CTLRESUME   403
#define CM_CTLPAUSE    404
#define CM_CTLSTOP     405

Исходные тексты всех функций, используемых для работы с wav-файлами на низком уровне, определены в файле waveio.cpp (листинг 2.18).

Листинг 2.18. Файл wave\waveio.cpp

#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <mmsystem.h>
#include <mem.h>
#pragma hdrstop
#include "waveio.hpp"

BOOL WAVEPlay(HWND);

extern WAVEIOCB waveiocbOut;
extern WAVEIOCB waveiocbIn;
extern HWAVEOUT hWaveOut;
extern HWAVEIN  hWaveIn;
extern int  nMode;
extern int  nPosition;

//-----------------------------------------------------
// WAVELoad
// Загрузка wav-файла для проигрывания
//-----------------------------------------------------
BOOL WAVELoad(LPWAVEIOCB lpwiocb)
{
  BYTE szFileName[256];
  OPENFILENAME ofn;
  int rc;
  BYTE szBuf[256];

  // Проверяем наличие драйвера, способного выводить
  // звуковые файлы
  rc=waveOutGetNumDevs();
  if(!rc)
  {
    MessageBox(NULL,
    (LPSTR)"Нет устройств для вывода звуковых файлов",
    "Wave Error", MB_OK | MB_ICONHAND);
    return FALSE;
  }

  // Выбираем wav-файл
  if(!wioSelectFile(szFileName))
    return FALSE;

  // Открываем и загружаем в память выбранный файл
  rc = wioFileOpen(lpwiocb, (LPSTR)szFileName);

  if(rc == WIOERR_NOERROR)
    return TRUE;
  else if(rc == WIOERR_FILEERROR)
    lstrcpy(szBuf, "Ошибка при открытии файла");
  else if(rc == WIOERR_BADFORMAT)
    lstrcpy(szBuf, "Неправильный или неподдерживаемый формат файла");
  else if(rc == WIOERR_NOMEM)
    lstrcpy(szBuf, "Мало памяти");
  else if(rc == WIOERR_READERROR)
    lstrcpy(szBuf, "Ошибка при чтении");
  else
    lstrcpy(szBuf, "Неизвестная ошибка");

  MessageBox(NULL, (LPSTR)szBuf,
    "Wave Error", MB_OK | MB_ICONHAND);
  return FALSE;
}

//-----------------------------------------------------
// wioSelectFile
// Выбор wav-файла
//-----------------------------------------------------
BOOL wioSelectFile(LPSTR lpszFileName)
{
  OPENFILENAME ofn;

  char szFile[256];
  char szFileTitle[256];
  char szFilter[256] =
         "Wave Files\0*.wav\0Any Files\0*.*\0";
  szFile[0] = '\0';
  memset(&ofn, 0, sizeof(OPENFILENAME));

  // Инициализируем нужные нам поля
  ofn.lStructSize       = sizeof(OPENFILENAME);
  ofn.hwndOwner         = NULL;
  ofn.lpstrFilter       = szFilter;
  ofn.nFilterIndex      = 1;
  ofn.lpstrFile         = szFile;
  ofn.nMaxFile          = sizeof(szFile);
  ofn.lpstrFileTitle    = szFileTitle;
  ofn.nMaxFileTitle     = sizeof(szFileTitle);
  ofn.lpstrInitialDir   = NULL;
  ofn.Flags =   OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST
               | OFN_HIDEREADONLY;

  // Выбираем входной файл
  if (GetOpenFileName(&ofn))
  {
    // Копируем путь к выбранному файлу
    lstrcpy(lpszFileName, (LPSTR)szFile);
    return TRUE;
  }
  else
    return FALSE;
}

//---------------------------------------------------------
//  wioFileOpen
//  Открытие и загрузка wav-файла
//---------------------------------------------------------
int wioFileOpen(LPWAVEIOCB lpwiocb, LPSTR lpszFileName)
{
  HMMIO hmmio;
  MMCKINFO ckRIFF, ckFMT;
  DWORD dwFmtSize;

  // Открываем wav-файл
  hmmio = mmioOpen((LPSTR)lpszFileName, NULL,
                    MMIO_READ | MMIO_ALLOCBUF);
  if(!hmmio)
    return WIOERR_FILEERROR;

  // Ищем фрагмент "WAVE"
  memset(&ckRIFF, 0, sizeof(MMCKINFO));
  ckRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E');

  if(mmioDescend(hmmio, &ckRIFF, NULL, MMIO_FINDRIFF))
  {
    mmioClose(hmmio,0);
    return WIOERR_BADFORMAT;
  }

  // Ищем фрагмент "fmt "
  memset(&ckFMT, 0, sizeof(MMCKINFO));
  ckFMT.ckid = mmioFOURCC('f', 'm', 't', ' ');

  if(mmioDescend(hmmio,
    &ckFMT, &ckRIFF, MMIO_FINDCHUNK))
  {
    mmioClose(hmmio,0);
    return WIOERR_BADFORMAT;
  }

  dwFmtSize = sizeof(PCMWAVEFORMAT);  //ckFMT.cksize;

  // Получаем память для загрузки формата звукового файла 
  lpwiocb->lpFmt = (PCMWAVEFORMAT FAR *)GlobalAllocPtr(GPTR,
     dwFmtSize);

  if(!lpwiocb->lpFmt)
  {
    mmioClose(hmmio,0);
    return WIOERR_NOMEM;
  }

  // Загружаем формат звукового файла
  if(mmioRead(hmmio, (HPSTR)lpwiocb->lpFmt, dwFmtSize) != 
     (LONG)dwFmtSize)
  {
    GlobalFreePtr(lpwiocb->lpFmt);
    mmioClose(hmmio,0);
    return WIOERR_READERROR;
  }

  // Проверяем формат звукового файла
  if(lpwiocb->lpFmt->wf.wFormatTag != WAVE_FORMAT_PCM)
  {
    GlobalFreePtr(lpwiocb->lpFmt);
    mmioClose(hmmio,0);
    return WIOERR_BADFORMAT;
  }

  // Проверяем способность драйвера работать с указанным форматом
  if(waveOutOpen(NULL, WAVE_MAPPER, (WAVEFORMAT FAR *)lpwiocb->lpFmt,
       NULL, 0L, WAVE_FORMAT_QUERY | WAVE_ALLOWSYNC))
  {
    GlobalFreePtr(lpwiocb->lpFmt);
    mmioClose(hmmio,0);
    return WIOERR_BADFORMAT;
  }

  // Вычисляем количество байт, необходимых для
  // хранения одной выборки звукового сигнала
  lpwiocb->wBitsPerSample = lpwiocb->lpFmt->wBitsPerSample;
  lpwiocb->wBytesPerSample =
    (waveiocbOut.wBitsPerSample/8) * waveiocbOut.lpFmt->wf.nChannels;

  // Ищем фрагмент "data"
  mmioAscend(hmmio, &ckFMT, 0);
  ckFMT.ckid = mmioFOURCC('d', 'a', 't', 'a');

  if(mmioDescend(hmmio, &ckFMT, &ckRIFF, MMIO_FINDCHUNK))
  {
    GlobalFreePtr(lpwiocb->lpFmt);
    mmioClose(hmmio,0);
    return WIOERR_BADFORMAT;
  }

  // Определяем размер фрагмента сегмента звуковых
  // данных и его смещение в wav-файле
  lpwiocb->dwDataSize   = ckFMT.cksize;
  lpwiocb->dwDataOffset = ckFMT.dwDataOffset;

  // Проверяем, что файл не пуст
  if(lpwiocb->dwDataSize == 0L)
  {
    GlobalFreePtr(lpwiocb->lpFmt);
    mmioClose(hmmio,0);
    return WIOERR_BADFORMAT;
  }

  // Получаем память для заголовка блока
  lpwiocb->lpWaveHdr =
    (LPWAVEHDR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
                              sizeof(WAVEHDR));
  if(!lpwiocb->lpWaveHdr)
    return WIOERR_NOMEM;

  // Получаем память для звуковых данных 
  lpwiocb->lpData =
    (HPSTR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
                          lpwiocb->dwDataSize);
  if(!lpwiocb->lpData)
    return WIOERR_NOMEM;

  // Позиционирование на начало звуковых данных
  mmioSeek(hmmio, SEEK_SET, lpwiocb->dwDataOffset);

  // Читаем звуковые данные
  mmioRead(hmmio, lpwiocb->lpData, lpwiocb->dwDataSize);

  // Закрываем wav-файл
  mmioClose(hmmio,0);

  return WIOERR_NOERROR;
}

//---------------------------------------------------------
// wioPlay
// Проигрывание загруженного блока звуковых данных
//---------------------------------------------------------
int wioPlay(LPWAVEIOCB lpwiocb, HWND hwnd)
{
  WORD rc;

  // Открываем устройство вывода
  rc = waveOutOpen(&hWaveOut, WAVE_MAPPER, 
                 (WAVEFORMAT FAR *)lpwiocb->lpFmt,
                 (UINT)hwnd, 0L, CALLBACK_WINDOW | WAVE_ALLOWSYNC);
  if(rc) return rc;

  // Заполняем заголовок блока данных
  lpwiocb->lpWaveHdr->lpData          = (LPSTR)lpwiocb->lpData;
  lpwiocb->lpWaveHdr->dwBufferLength  = lpwiocb->dwDataSize;
  lpwiocb->lpWaveHdr->dwBytesRecorded = 0;
  lpwiocb->lpWaveHdr->dwFlags         = 0;
  lpwiocb->lpWaveHdr->dwLoops         = 0;
  lpwiocb->lpWaveHdr->dwUser          = 0;
  lpwiocb->lpWaveHdr->lpNext          = 0;
  lpwiocb->lpWaveHdr->reserved        = 0;

  // Подготавливаем заголовок для вывода
  rc = waveOutPrepareHeader(hWaveOut, lpwiocb->lpWaveHdr,
                          sizeof(WAVEHDR));
  if(rc)
  {
    GlobalFreePtr(lpwiocb->lpWaveHdr);
    return rc;
  }

  // Запускаем проигрывание блока
  rc = waveOutWrite(hWaveOut, lpwiocb->lpWaveHdr, sizeof(WAVEHDR));
  if(rc)
  {
    waveOutUnprepareHeader(hWaveOut, lpwiocb->lpWaveHdr,
                           sizeof(WAVEHDR));
    GlobalFreePtr(lpwiocb->lpWaveHdr);
    return rc;
  }

  return 0;
}

//---------------------------------------------------------
//  wioRecord
//  Запись звуковых данных
//---------------------------------------------------------
int wioRecord(LPWAVEIOCB lpwiocb, HWND hwnd)
{
  int rc;

  // Проверяем наличие драйвера, способного
  // выполнять запись звука 
  rc = waveInGetNumDevs();
  if(!rc)
  {
    MessageBox(NULL,
    (LPSTR)"Нет устройств для записи звуковых файлов",
    "Wave Error", MB_OK | MB_ICONHAND);
    return WIOERR_NODEVICE;
  }

  // Максимальный размер блока в байтах
  lpwiocb->dwDataSize = MAXSAMPLES;

  // Получаем память для заголовка блока
  lpwiocb->lpWaveHdr =
    (LPWAVEHDR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
                              sizeof(WAVEHDR));
  if(!lpwiocb->lpWaveHdr)
    return WIOERR_NOMEM;

  // Получаем память для блока звуковых данных
  lpwiocb->lpData =
    (HPSTR)GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
                          lpwiocb->dwDataSize);
  if(!lpwiocb->lpData)
    return WIOERR_NOMEM;

  // Получаем память для блока формата 
  lpwiocb->lpFmt =
    (PCMWAVEFORMAT FAR *)GlobalAllocPtr(GPTR, sizeof(WAVEFORMAT));

  if(!lpwiocb->lpFmt)
  {
    return WIOERR_NOMEM;
  }

  // Заполняем блок формата. Наше приложение способно
  // записывать монофонические файлы в формате WAVE_FORMAT_PCM
  // с частотой дискретизации 22,05 Кгц
  lpwiocb->lpFmt->wf.wFormatTag      = WAVE_FORMAT_PCM;
  lpwiocb->lpFmt->wf.nChannels       = 1;
  lpwiocb->lpFmt->wf.nSamplesPerSec  = 22050;
  lpwiocb->lpFmt->wf.nAvgBytesPerSec = 22050;
  lpwiocb->lpFmt->wf.nBlockAlign     = 1;

  // Открываем устройство записи
  rc=waveInOpen(&hWaveIn, WAVE_MAPPER,
                (WAVEFORMAT FAR *)lpwiocb->lpFmt,
                (UINT)hwnd, 0L, CALLBACK_WINDOW | WAVE_ALLOWSYNC);
  if(rc)
  {
    wioInError(rc);
    return WIOERR_BADFORMAT;
  }

  // Заполняем заголовок блока
  lpwiocb->lpWaveHdr->lpData         = (LPSTR)lpwiocb->lpData;
  lpwiocb->lpWaveHdr->dwBufferLength = lpwiocb->dwDataSize;
  lpwiocb->lpWaveHdr->dwFlags        = 0L;
  lpwiocb->lpWaveHdr->dwLoops        = 0L;
  lpwiocb->lpWaveHdr->dwUser         = 0L;

  // Подготавливаем блок для записи
  rc = waveInPrepareHeader(hWaveIn, lpwiocb->lpWaveHdr,
                           sizeof(WAVEHDR));
  if(rc)
  {
    wioInError(rc);
    GlobalFreePtr(lpwiocb->lpWaveHdr);
    return WIOERR_BADFORMAT;
  }

  // Передаем блок устройству записи
  rc = waveInAddBuffer(hWaveIn, lpwiocb->lpWaveHdr, sizeof(WAVEHDR));
  if(rc)
  {
    wioInError(rc);
    waveInUnprepareHeader(hWaveIn, lpwiocb->lpWaveHdr,
                          sizeof(WAVEHDR));
    GlobalFreePtr(lpwiocb->lpWaveHdr);
    GlobalFreePtr(lpwiocb->lpFmt);
    GlobalFreePtr(lpwiocb->lpData);
    return WIOERR_ERROR;
  }

  // Запускаем запись
  rc = waveInStart(hWaveIn);
  if(rc)  wioInError(rc);

  return TRUE;
}

//---------------------------------------------------------
//  wioFileSave
//  Сохранение записанного звука в wav-файле
//---------------------------------------------------------
BOOL wioFileSave(LPSTR szFileName)
{
   DWORD dwDataSize;
   char szdata[] = "data";
   HMMIO hFile;
   MMCKINFO ck;
   WORD wBitsPerSample = 8;
   char szfmt[] = "fmt ";
   DWORD dwFmtSize = sizeof(PCMWAVEFORMAT);

   // Создаем новый файл или перезаписываем существующий
   hFile = mmioOpen(szFileName, NULL, MMIO_CREATE | MMIO_READWRITE);
   if(hFile != NULL)
   {
     // Создаем заголовок wav-файла 
     ck.ckid = MMIO_CREATERIFF;
     ck.cksize = waveiocbIn.lpWaveHdr->dwBytesRecorded
                 + sizeof(PCMWAVEFORMAT) + 20;
     ck.fccType = mmioFOURCC('W', 'A', 'V', 'E');
     mmioCreateChunk(hFile, (LPMMCKINFO)&ck, MMIO_CREATERIFF);

     // Записываем фрагмент "fmt "
     mmioWrite(hFile, (HPSTR)szfmt, 4);
     mmioWrite(hFile, (HPSTR)&dwFmtSize, sizeof(DWORD));

     mmioWrite(hFile, (HPSTR)waveiocbIn.lpFmt, sizeof(WAVEFORMAT));
     mmioWrite(hFile, (HPSTR)&wBitsPerSample, sizeof(WORD));

     mmioWrite(hFile, (HPSTR)szdata, 4);
     dwDataSize = waveiocbIn.lpWaveHdr->dwBytesRecorded;
     mmioWrite(hFile, (HPSTR)&dwDataSize, sizeof(DWORD));

     // Записываем данные
     mmioWrite(hFile, (HPSTR)waveiocbIn.lpData,
               waveiocbIn.lpWaveHdr->dwBytesRecorded);

     // Закрываем файл
     mmioClose(hFile, 0);
     return TRUE;
   }
   return FALSE;
}

//---------------------------------------------------------
//  wioOutError
//  Вывод сообщения об ошибке в процессе проигрывания
//---------------------------------------------------------
void wioOutError(int rc)
{
  BYTE szBuf[MAXERRORLENGTH];

  if(waveOutGetErrorText(rc, (LPSTR)szBuf, MAXERRORLENGTH) ==
    MMSYSERR_BADERRNUM)
  {
    lstrcpy(szBuf, "Unknown Error");
  }
  MessageBox(NULL, (LPSTR)szBuf,
    "Wave Error", MB_OK | MB_ICONHAND);
}

//---------------------------------------------------------
//  wioInError
//  Вывод сообщения об ошибке в процессе записи
//---------------------------------------------------------
void wioInError(int rc)
{
  BYTE szBuf[MAXERRORLENGTH];

  if(waveInGetErrorText(rc, (LPSTR)szBuf, MAXERRORLENGTH) ==
    MMSYSERR_BADERRNUM)
  {
    lstrcpy(szBuf, "Unknown Error");
  }
  MessageBox(NULL, (LPSTR)szBuf,
    "Wave Error", MB_OK | MB_ICONHAND);
}

Функция WAVLoad предназначена для выбора и загрузки проигрываемого wav-файла.

В самом начале своей работы она определяет наличие драйвера, способного воспроизводить звуковую информацию, для чего вызывает функцию waveOutGetNumDevs. Если нужный драйвер есть, вызывается функция wioSelectFile, предоставляющая пользователю возможность выбрать файл при помощи стандартной диалоговой панели "Open". Выбранный файл открывается и загружается в память функцией wioFileOpen.

Функция wioSelectFile не имеет никаких особенностей. Для выбора файла в ней используется функция GetOpenFileName из библиотеки commdlg.dll. Путь к выбранному файлу копируется в буфер, адрес которого передается функции wioSelectFile в качестве единственного параметра.

Функция wioFileOpen выполняет всю работу по загрузке и анализу wav-файла.

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

Далее в wav-файле ищутся фрагменты "WAVE" и "fmt ", для чего используется функция mmioDescend:

memset(&ckRIFF, 0, sizeof(MMCKINFO));
ckRIFF.fccType = mmioFOURCC('W', 'A', 'V', 'E');
if(mmioDescend(hmmio, &ckRIFF, NULL, MMIO_FINDRIFF))
{
  mmioClose(hmmio,0);
  return WIOERR_BADFORMAT;
}

memset(&ckFMT, 0, sizeof(MMCKINFO));
ckFMT.ckid = mmioFOURCC('f', 'm', 't', ' ');
if(mmioDescend(hmmio,
  &ckFMT, &ckRIFF, MMIO_FINDCHUNK))
{
  mmioClose(hmmio,0);
  return WIOERR_BADFORMAT;
}

После этого приложение заказывает память для структуры PCMWAVEFORMAT, которая находится во фрагменте "fmt " и содержит сведения о формате wav-файла. Загрузка данных в структуру выполняется функцией mmioRead:

if(mmioRead(hmmio, (HPSTR)lpwiocb->lpFmt, dwFmtSize) != 
   (LONG)dwFmtSize)
{
  GlobalFreePtr(lpwiocb->lpFmt);
  mmioClose(hmmio,0);
  return WIOERR_READERROR;
}

Далее проверяется формат звукового файла: поле wFormatTag структуры WAVEFORMAT должно содержать значение WAVE_FORMAT_PCM.

Для того чтобы определить, может ли установленный в системе драйвер звукового адаптера работать с форматом загруженного файла, вызывается функция waveOutOpen с флагом WAVE_FORMAT_QUERY:

if(waveOutOpen(NULL, WAVE_MAPPER, (WAVEFORMAT FAR *)lpwiocb->lpFmt,
     NULL, 0L, WAVE_FORMAT_QUERY | WAVE_ALLOWSYNC))
{
  GlobalFreePtr(lpwiocb->lpFmt);
  mmioClose(hmmio,0);
  return WIOERR_BADFORMAT;
}

Так как пользователь может загрузить wav-файл любого формата, как монофонический, так и стереофонический, с использованием 8 или 16 битов для представления одной выборки сигнала, приложение должно динамически определять количество байт памяти, необходимых для хранения одной выборки сигнала.

После анализа формата наше приложение ищет фрагмент "data", в котором находятся звуковые данные. Для поиска используется функция mmioDescend. После определения размера фрагмента данных приложение заказывает память для заголовка блока и самого блока, пользуясь макрокомандой GlobalAllocPtr.

Перед чтением звуковых данных в заказанный буфер выполняется позиционирование на начало звуковых данных, для чего используется функция mmioSeek. Чтение данных выполняется функцией mmioRead. Сразу после чтения файл закрывается функцией mmioClose, так как он нам больше не нужен.

Функция wioPlay проигрывает загруженный блок. Адрес блока и его заголовка передаются функции через первый параметр. Через второй параметр функция wioPlay получает идентификатор окна, в которое после завершения проигрывания поступит сообщение MM_WOM_DONE.

Прежде всего функция wioPlay открывает устройство вывода, вызывая для этого функцию waveOutOpen:

rc = waveOutOpen(&hWaveOut, WAVE_MAPPER, 
   (WAVEFORMAT FAR *)lpwiocb->lpFmt,
   (UINT)hwnd, 0L, CALLBACK_WINDOW | WAVE_ALLOWSYNC);

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

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

Затем заголовок блока подготавливается для вывода при помощи функции waveOutPrepareHeader:

rc = waveOutPrepareHeader(hWaveOut, lpwiocb->lpWaveHdr,
         sizeof(WAVEHDR));

Для запуска процесса проигрывания вызывается функция waveOutWrite:

rc = waveOutWrite(hWaveOut, lpwiocb->lpWaveHdr, sizeof(WAVEHDR));

Функция wioRecord запускает процесс записи.

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

Затем функция wioRecord заказывает память для блока формата и заполняет этот блок. Для простоты мы использовали только один формат, а именно монофонический 8-битовый формат с частотой дискретизации 22,05 Кгц.

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

Далее блок передается устройству записи функцией waveInAddBuffer:

rc = waveInAddBuffer(hWaveIn, lpwiocb->lpWaveHdr, sizeof(WAVEHDR));

Для запуска записи вызывается функция waveInStart:

rc = waveInStart(hWaveIn);

Функция wioFileSave сохраняет содержимое буфера записи в wav-файле.

Для создания нового или перезаписи существующего файла он открывается функцией mmioOpen с флагами MMIO_CREATE и MMIO_READWRITE.

Далее создается заголовок файла при помощи функции mmioCreateChunk:

ck.cksize = waveiocbIn.lpWaveHdr->dwBytesRecorded
            + sizeof(PCMWAVEFORMAT) + 20;
ck.fccType = mmioFOURCC('W', 'A', 'V', 'E');
mmioCreateChunk(hFile, (LPMMCKINFO)&ck, MMIO_CREATERIFF);

При этом в заголовке указывается длина с учетом объема записанных звуковых данных.

После этого с помощью функции mmioWrite в файл записывается фрагмент "fmt " и содержимое буфера звуковых данных. Файл закрывается функцией mmioClose.

Для обработки ошибок в процессе воспроизведения используется функция wioOutError, которая с помощью функции waveOutGetErrorText преобразует код ошибки, передаваемый через параметр, в текстовое сообщение и выводит его на экран. Аналогичные действия выполняются при записи функцией wioInError, преобразующей код ошибки в текстовую строку при помощи функции waveInGetErrorText.

Файл waveio.hpp (листинг 2.19) содержит определение структуры WAVEIOCB и указателей на нее, а также определения констант и прототипы функций. Структура WAVEIOCB используется функциями записи и проигрывания блоков звуковых данных.

Листинг 2.19. Файл wave\waveio.hpp

#include <windows.h>
#include <mmsystem.h>

typedef struct tagWAVEIOCB
{
  DWORD dwDataSize;
  DWORD dwDataOffset;
  PCMWAVEFORMAT FAR *lpFmt;
  LPWAVEHDR lpWaveHdr;
  HPSTR lpData;
  WORD wBitsPerSample;
  WORD wBytesPerSample;

} WAVEIOCB, *PWAVEIOCB, FAR *LPWAVEIOCB;

#define WIOERR_BASE         (100)
#define WIOERR_NOERROR      (0)
#define WIOERR_ERROR        (WIOERR_BASE+1)
#define WIOERR_BADHANDLE    (WIOERR_BASE+2)
#define WIOERR_BADFLAGS     (WIOERR_BASE+3)
#define WIOERR_BADPARAM     (WIOERR_BASE+4)
#define WIOERR_BADSIZE      (WIOERR_BASE+5)
#define WIOERR_FILEERROR    (WIOERR_BASE+6)
#define WIOERR_NOMEM        (WIOERR_BASE+7)
#define WIOERR_BADFILE      (WIOERR_BASE+8)
#define WIOERR_NODEVICE     (WIOERR_BASE+9)
#define WIOERR_BADFORMAT    (WIOERR_BASE+10)
#define WIOERR_ALLOCATED    (WIOERR_BASE+11)
#define WIOERR_NOTSUPPORTED (WIOERR_BASE+12)
#define WIOERR_READERROR    (WIOERR_BASE+13)

#define MAXSAMPLES           1024000L

#define MODE_STOP            0
#define MODE_PLAYING         1
#define MODE_RECORDING       2
#define MODE_PLAYINGPAUSED   3
#define MODE_RECORDINGPAUSED 4

BOOL wioSelectFile(LPSTR);
int  wioFileOpen(LPWAVEIOCB, LPSTR);
int  wioPlay(LPWAVEIOCB, HWND);
int  wioRecord(LPWAVEIOCB, HWND);
BOOL wioFileSave(LPSTR szFileName);
void wioOutError(int rc);
void wioInError(int rc);

Файл определения ресурсов приложения WAVE представлен в листинге 2.20.

Листинг 2.20. Файл wave\wave.rc

#include "wave.hpp"
APPICON ICON "wave.ico"
APP_MENU MENU 
BEGIN
  POPUP "&File"
    BEGIN
      MENUITEM "&Open...",     CM_FILEOPEN
      MENUITEM SEPARATOR
      MENUITEM "E&xit",        CM_FILEEXIT
    END

  MENUITEM "&Play!",     CM_CTLPLAY
  MENUITEM "&Stop!",     CM_CTLSTOP
  MENUITEM "Resu&me!",   CM_CTLRESUME
  MENUITEM "P&ause!",    CM_CTLPAUSE
  MENUITEM "&Record!",   CM_CTLRECORD

  POPUP "&Help"
    BEGIN
      MENUITEM "&About...",      CM_HELPABOUT
    END
END

Файл определения модуля приложения WAVE вы сможете найти в листинге 2.21.

Листинг 2.21. Файл wave\wave.def

NAME        WAVE
DESCRIPTION 'Приложение WAVE, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   10240
HEAPSIZE    1024
CODE        preload moveable discardable
DATA        preload moveable multiple