Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Мультимедиа для Windows

© Александр Фролов, Григорий Фролов
Том 15, М.: Диалог-МИФИ, 1994, 284 стр.

[Назад] [Содеожание] [Дальше]

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

[Назад] [Содеожание] [Дальше]