В самом простейшем случае приложение должно уметь выдавать звуковые сигналы или проигрывать небольшие звуковые сообщения при появлении каких-либо непредвиденных событий, когда нужно привлечь внимание пользователя. Для этого можно воспользоваться функциями MessageBeep и sndPlaySound .
MCI представляет собой универсальный, независимый от особенностей аппаратного обеспечения интерфейс, предназначенный для управления устройствами мультимедиа, такими как звуковые и видеоадаптеры, устройства чтения (проигрывания) звуковых компакт-дисков и лазерных видеодисков. В большинстве случаев возможности, предоставляемые этим интерфейсом, удовлетворят потребности любого приложения мультимедиа, предназначенного для записи и воспроизведения звуковой или видеоинформации. Если же приложение должно обрабатывать данные на низком уровне или в реальном времени (задачи редактирования и преобразования wav-файлов, распознавания речи, образов, преобразования речи в реальном времени), оно может воспользоваться низкоуровневым интерфейсом, который мы рассмотрим позже.
Все функции интерфейса MCI экспортируются библиотекой mmsystem.dll . Эти функции обращаются непосредственно к драйверам устройств ввода/вывода, а также к функциям более низкого уровня, определенным в библиотеке mmsystem.dll.
Приложения могут использовать два типа программного интерфейса MCI .
Первый тип называется интерфейс управляющих строк (Command-String Interface ). Он основан на использовании текстовых команд (таких, как open, play, close).
Второй тип - это интерфейс управляющих сообщений (Command-Message Interface ). Для управления устройствам посылаются сообщения, коды которых определены в файле mmsystem.h через символические константы (например, MCI_OPEN, MCI_PLAY, MCI_CLOSE).
Интерфейс управляющих строк удобен для использования в системах программирования высокого уровня, так как позволяет быстро получить необходимый результат. Например, для проигрывания звукового файла ding.wav достаточно передать звуковому адаптеру следующую последовательность управляющих строк: open ding.wav type waveaudio alias snd wait play snd wait close snd
Не намного сложнее выглядят управляющие строки (команды) для записи звукового фрагмента в wav-файл или для проигрывания видеофрагмента из avi-файла.
Для передачи команд используется функция mciSendString , которой в качестве первого параметра передается указатель на строку команды.
Мы рассмотрим основные приемы использования обоих типов интерфейса MCI на примере работы со звуковым адаптером и wav-файлами. В дальнейшем, при описании работы с устройствами других типов, мы ограничимся только особенностями команд MCI этих устройств.
Интерфейс управляющих сообщений более удобен для приложений, составленных на языках программирования С и С++. Он предполагает посылку устройствам мультимедиа управляющих сообщений с помощью функции mciSendCommand .
В качестве одного из параметров этой функции передается двоичный код управляющего сообщения (команды). Символические константы кодов команд определены в файле mmsystem.h. В дополнение к коду команды функции mciSendCommand передается структура параметров, формат которой зависит от кода команды, а также другая дополнительная информация, необходимая для выполнения команды.
При необходимости иметь непосредственный доступ к буферам, содержащим звуковые данные, приложение должно использовать интерфейс низкого уровня, обеспечиваемый несколькими функциями с префиксом имени wave, например, waveInOpen, waveOutOpen, waveOutWrite, waveAddBuffer и т. д.
Общая методика использования интерфейса низкого уровня для воспроизведения wav-файлов такова. Сначала выполняется чтение и проверка формата заголовка wav-файла, открытие устройства вывода с указанием конкретного формата звуковых данных. Затем звуковые данные читаются блоками непосредственно из wav-файла, подготавливаются специальной функцией для вывода и затем передаются драйверу устройства вывода. Драйвер выводит их в звуковой адаптер. Приложение имеет полный контроль над процессом воспроизведения, так как оно само должно подготовить блоки данных в оперативной памяти.
Аналогично выполняется запись звуковых данных. Вначале требуется открыть устройство ввода, указав ему формат звуковых данных. Затем нужно заказать один или несколько блоков памяти и подготовить их для ввода, вызвав специальную функцию. После этого подготовленные блоки нужно по мере необходимости передавать драйверу устройства ввода, который заполнит их записанными звуковыми данными. Для сохранения записанных данных в wav-файле приложение должно сформировать и записать в файл заголовок wav-файла и звуковые данные из подготовленных и заполненных драйвером устройства ввода блоков памяти.
В отличие от интерфейса MCI, где многие параметры принимаются по умолчанию, интерфейс низкого уровня требует внимательного и тщательного учета всех деталей процесса записи и воспроизведения. В качестве компенсации за дополнительно затраченные усилия вы получаете большую гибкость и возможность работать со звуковыми данными в реальном времени.
Дальнейшее изложение материала будет происходить по следующему плану.
Вначале мы расскажем вам о формате wav-файлов, в которых хранятся звуковые данные. Вы узнаете о функциях, предназначенных для работы с такими файлами. Эти функции экспортируются библиотекой mmsystem.dll. Они сильно облегчают работу с файлами на низком уровне. Попутно вы подробно познакомитесь с форматами звуковых данных.
Затем мы расскажем о том, как определить состав установленных в системе драйверов устройств мультимедиа, а также возможности отдельных драйверов.
После этого мы перейдем к описанию процесса воспроизведения и записи на низком уровне. В конце раздела мы опишем приложение WAVE, демонстрирующее способы работы со звуковыми данными на низком уровне.
2.1. Самые простые способы воспроизведения звука
2.2. Интерфейс управляющих строк MCI
2.3. Интерфейс управляющих сообщений MCI
В этой главе мы расскажем вам о том, как приложения Windows могут записывать и воспроизводить звуковые фрагменты, используя программный интерфейс системы мультимедиа различных уровней. Вы научитесь создавать "звучащие" приложения, и приложения, которые могут записывать звук, познакомитесь со структурой wav-файлов, предназначенных для хранения записанных фонограмм.
Как мы уже говорили, для работы со звуковым адаптером в среде операционной системы Windows вам не потребуется программировать на уровне портов ввода/вывода, прерываний и каналов прямого доступа. Весь необходимый интерфейс (высокого или низкого уровня) предоставляется приложению DLL-библиотекой mmsystem.dll . Эту библиотеку можно рассматривать как расширение Windows для обеспечения возможности работы с мультимедиа.
Библиотека mmsystem.dll поставляется в составе Windows версии 3.1 (версия 3.0 не могла работать с мультимедиа, однако можно было приобрести изделие Microsoft Multimedia Extension, содержащее эту библиотеку и приложения, предназначенные для работы со звуком). Все функции, входящие в библиотеку mmsystem.dll, описаны в файле mmsystem.h , который поставляется со всеми системами разработки приложений для Windows и находится в каталоге include вместе с файлом windows.h.
Что же содержится в библиотеке mmsystem.dll?
В этой библиотеке определены функции двух уровней: функции низкого уровня (Low-Level Functions ) и функции высокого уровня, представляющих собой интерфейс управления средой MCI (Media Control Interface ).
Функции низкого уровня работают непосредственно с драйверами устройств ввода/вывода, такими, как драйверы звукового адаптера, джойстика или устройства ввода/вывода MIDI .
Функции интерфейса MCI работают с драйверами устройств MCI (например, драйверами устройств чтения компакт-дисков или лазерных видеодисков) и вызывают функции низкого уровня.
В любом случае для работы с устройствами мультимедиа приложение должно вызывать ту или иную функцию, определенную в библиотеке mmsystem.dll, вне зависимости от уровня интерфейса.
Что же касается драйверов звуковых устройств, то можно выделить четыре типа таких драйверов.
Драйвер для ввода звука (Waveform Input Driver )
Драйвер для вывода звука (Waveform Output Driver )
Драйвер для ввода музыки в стандарте MIDI (MIDI Input Driver )
Драйвер для вывода музыки в стандарте MIDI (MIDI Output Driver )
Все эти драйверы поставляются вместе со звуковым адаптером и устанавливаются после установки операционной системы Windows. В зависимости от типа звукового адаптера состав драйверов может изменяться (например, могут отсутствовать драйверы для работы с MIDI).
Вы можете также приобрести звуковой драйвер для работы с динамиком, встроенным в корпус компьютера (Sound Driver for PC Speaker ). В комплект поставки входят два файла - speaker.drv и oemsetup.inf . Этот драйвер можно найти в библиотеке дополнительных драйверов для Windows, которая называется Windows Driver Library , или на одной из электронных досок объявлений BBS.
Конечно, качество звучания встроенного динамика не сравнимо с качеством звучания настоящей звуковой системы, но самый главный недостаток этого драйвера заключается в невозможности воспроизведения звука в фоновом режиме. Однако даже небольшие звуковые возможности, полученные практически даром, оживят Windows и сделают эту операционную систему более привлекательной.
Команда break позволяет определить код виртуальной клавиши, предназначенной для прерывания (по умолчанию используется комбинация клавиш <Control+Break>): break device_id parameter [notify] [wait]
В качестве параметра parameter можно указывать одну из следующих строк: on virt_key
Для прерывания будет использована клавиша с виртуальным кодом virt_key off
Действие клавиши прерывания отменяется
Если команда выдана с параметром wait, функция mciSendString вернет управление только после завершения операции. Если пользователь не желает дожидаться завершения длительной операции, он может нажать клавишу, прерывающую выполнение команды.
С помощью команды capability приложение может определить возможности устройства: capability device_id parameter [notify] [wait]
В качестве параметра parameter можно указывать одну из следующих строк: can play
Если драйвер звукового адаптера может проигрывать wav-файлы, в ответ на эту строку он возвратит строку true, а если нет - то false can record
Если устройство может записывать, возвращается true, в противном случае - false can save
Используется для определения возможности сохранения записанного звукового фрагмента в wav-файле. Если такая возможность есть, возвращается строка true, в противном случае - false compound device
Все MCI-устройства можно разделить на простые и составные (compound). Простые устройства, такие как проигрыватель звуковых компакт-дисков или лазерных видеодисков, не работают с файлами. Составные, такие как звуковой адаптер, используют файлы. Поэтому в ответ на эту строку, переданную драйверу звукового адаптера, приложение получит строку true device type
Для звукового адаптера возвращается строка waveaudio has audio
Для звукового адаптера возвращается строка true inputs
Общее количество устройств ввода outputs
Общее количество устройств вывода uses files
Для звукового адаптера возвращается строка true, так как он работает с wav-файлами
Для чтения файла, открытого при помощи функции mmioOpen, следует использовать функцию mmioRead . Эта функция позволяет за один вызов прочитать из файла блок данных размером, большим чем 64 Кбайт. После чтения выполняется перемещение текущей позиции вперед на количество прочитанных байт. Функция mmioRead LONG mmioRead( HMMIO hmmio, // идентификатор открытого файла HPSTR hpBuff, // указатель на буфер с данными LONG dwBytes); // размер буфера
Параметры функции: hmmio
Идентификатор открытого файла, полученный с помощью функции mmioOpen hpBuff
Указатель типа huge на буфер, в который будут прочитаны данные dwBytes
Размер буфера
Возвращаемое значение:
Возвращается количество прочитанных байт данных или -1 при возникновении ошибки. При достижении конца файла возвращается нулевое значение
Команда close закрывает устройство и освобождает все связанные с ним ресурсы. Формат команды: close device_id [notify] [wait]
Необходимо указать идентификатор устройства device_id, использованный при открытии устройства командой open.
Например, для того чтобы закрыть устройство с алиасом nsound, вы можете использовать следующую управляющую строку: close nsound
Подготовка для записи или воспроизведения. После выполнения такой подготовки устройство будет готово быстро приступить к выполнению требуемой операции. Эту команду нельзя выдавать во время записи или воспроизведения.
В качестве параметра parameter можно указывать одну из следующих строк: input
Подготовка для записи output
Подготовка для воспроизведения
Удаление сегмента из фрагмента звуковых данных delete device_id from position [to position] [notify] [wait]
Для этой команды можно указать либо оба параметра (from и to), либо только параметр from.
Перед тем как перейти к описанию приложения WAVE, перечислим еще несколько полезных функций, предназначенных для работы со звуком на низком уровне.
Данные, имеющие отношение к мультимедиа (звук, видео и т. п.) хранятся в файлах в так называемом RIFF-формате (Resource Interchange File Format - формат файла для обмена ресурсами). Как wav-файлы, содержащие звук, так и avi-файлы, содержащие видеоинформацию, имеют формат RIFF.
Файл в формате RIFF содержит вложенные фрагменты (chunk's ). Внешний фрагмент состоит из заголовка и области данных (рис. 2.3).
Для чтения или записи wav-файлов вы, конечно, можете использовать стандартные функции или такие функции, как _hread или _hwrite. Однако в библиотеке mmsystem.dll есть более удобные функции, специально предназначенные для работы с файлами в стандарте RIFF. Все эти функции могут работать с блоками памяти большого размера (больше 64 Кбайт), что очень удобно, так как звуковые данные редко помещаются в одном сегменте памяти.
Ваше приложение может работать с 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
Параметр не используется, необходимо передавать нулевое значение.
Возвращаемое значение:
При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки
Для передачи управляющих сообщений драйверам устройств мультимедиа используется функция mciSendCommand , описание которой приведено ниже. Функция mciSendCommand DWORD mciSendCommand( UINT wDeviceID, // идентификатор устройства UINT wMessage, // код сообщения DWORD dwParam1, // флаги команды DWORD dwParam2); // указатель на структуру параметров
Параметры функции: wDeviceID
Идентификатор устройства, которому посылается сообщение. Для команды MCI_OPEN не используется, так как идентификатор создается в результате выполнения этой команды wMessage
Код сообщения. Соответствует требуемой функции. Список кодов сообщений (команд) для звукового адаптера приведен в следующем разделе dwParam1
Флаги команды. Флаги используются для того чтобы сообщить функции, какие параметры, заданные в структуре параметров, следует использовать при выполнении команды. Если ни один флаг не указан, параметры игнорируются dwParam2
Указатель на структуру параметров. Формат этой структуры зависит от кода сообщения
Возвращаемое значение:
Нуль при нормальном завершении или код ошибки. Список кодов ошибок для функции mciSendCommand приведен в приложении 1. С помощью функции mciGetErrorString, рассмотренной нами ранее, вы можете преобразовать код ошибки в текстовую строку, передав ей этот код в качестве параметра
Формат структуры параметров (блока параметров) и используемые флаги зависят от кода управляющего сообщения, передаваемого функции mciSendCommand через параметр wMessage.
Как передать устройству управляющую строку?
Очень просто - для этого можно воспользоваться функцией mciSendString . Прототип функции mciSendString находится в файле mmsystem.h: Функция mciSendString DWORD mciSendString( LPCSTR lpstrCommand, // управляющая строка LPSTR lpstrReturnString, // буфер для результата UINT wReturnLength, // размер буфера HANDLE hCallback) // идентификатор окна извещения
Параметры функции: lpstrCommand
Дальний указатель на текстовую управляющую строку lpstrReturnString
Указатель на буфер, в который будет записан результат выполнения команды (в текстовом виде). Этот параметр можно указать как NULL, если приложение не интересуется результатом выполнения команды wReturnLength
Размер буфера для записи результата выполнения команды hCallback
Идентификатор окна, которое получит извещение (сообщение MM_MCINOTIFY) после того как устройство завершит операцию. Этот параметр можно указать как NULL, если извещение не используется
Возвращаемое значение:
Нуль при успешном завершении или код ошибки (в младшем слове возвращаемого значения): MCIERR_BAD_CONSTANT
Указана константа, неправильная для данной команды MCIERR_BAD_INTEGER
Указано значение, неправильное для данной команды MCIERR_DUPLICATE_FLAGS
Двойное определение параметра или значения MCIERR_MISSING_COMMAND_STRING
Не указана управляющая строка MCIERR_MISSING_DEVICE_NAME
Не указано имя устройства, драйвера, файла или алиас MCIERR_MISSING_STRING_ARGUMENT
Не указан обязательный параметр команды MCIERR_NEW_REQUIRED_ALIAS
При использовании параметра new следует указать алиас MCIERR_NO_CLOSING_QUOTE
В команде отсутствуют закрывающие двойные кавычки MCIERR_NOTIFY_ON_AUTO_OPEN
Для устройств, открываемых автоматически, нельзя указывать флаг notify (флаг notify указывает на необходимость генерации извещающего сообщения при завершении выполнения операции, мы расскажем об этом флаге позже) MCIERR_PARAM_OVERFLOW
Строка параметров не помещается в буфер. Необходимо увеличить размер буфера MCIERR_PARSER_INTERNAL
Ошибка в драйвере устройства. Следует заменить драйвер на новый, более поздней версии MCIERR_UNRECOGNIZED_KEYWORD
Драйвер не распознал параметр управляющей строки
Например, для проигрывания wav-файла вы можете использовать следующую последовательность вызовов функции mciSendString : mciSendString( (LPSTR)"open ding.wav type waveaudio alias snd wait", (LPSTR)szBuf, 256, NULL); mciSendString((LPSTR)"play snd wait", (LPSTR)szBuf, 256, NULL); mciSendString((LPSTR)"close snd", (LPSTR)szBuf, 256, NULL);
Для преобразования кода ошибки, полученного от функции mciSendString, в текстовую строку, можно воспользоваться функцией mciGetErrorString , которой необходимо передать двойное слово кода ошибки. Функция mciGetErrorString UINT mciGetErrorString( DWORD dwError, // код ошибки LPSTR lpstrBuffer, // буфер для записи текстовой строки UINT wLength); // размер буфера
Параметры функции: dwError
Код ошибки, полученный от функции mciSendString или mciSendCommand (функция mciSendCommand предназначена для передачи управляющих сообщений, она будет рассмотрена позже) lpstrBuffer
Буфер, в который будет записано текстовое описание ошибки wLength
Размер буфера в байтах
Возвращаемое значение:
TRUE при успешном завершении или FALSE, если переданному коду ошибки не соответствует ни одно текстовое описание
Ранее, в предыдущих томах "Библиотеки системного программиста", мы упоминали функцию MessageBeep : void MessageBeep(UINT uAlert);
Эта функция как раз и предназначена для выдачи звуковых сигналов. Правда, если в компьютере не установлен драйвер звукового адаптера или драйвер для работы с динамиком, при вызове этой функции можно услышать только короткий звуковой сигнал "бип".
Если же звуковой драйвер установлен, в зависимости от значения параметра uAlert функция MessageBeep может воспроизводить один из звуковых фрагментов, записанных в wav-файле.
Обратите внимание, что в файле win.ini имеется раздел [sound], в котором перечислены различные ситуации. Для ситуации может быть указано имя wav-файла, который должен быть воспроизведен при ее возникновении: [sounds] SystemAsterisk=chimes.wav,Asterisk SystemHand=ding.wav,Critical Stop SystemDefault=,Default Beep SystemExclamation=ding.wav,Exclamation SystemQuestion=ding.wav,Question SystemExit=bye.wav,Windows Exit SystemStart=,Windows Start
У вас нет необходимости изменять этот раздел вручную, так как это можно сделать при помощи приложения Control Panel (рис. 1.8).
Возможные значения параметра uAlert для функции MessageBeep приведены в следующей таблице.
Значение | Описание |
-1 | Стандартный звуковой сигнал, который выдается на встроенный в компьютер динамик |
MB_ICONASTERISK | Проигрывается wav-файл, определенный в строке SystemAsterisk раздела [sound] файла win.ini |
MB_ICONEXLAMATION | Аналогично для строки SystemExclamation |
MB_ICONHAND | Аналогично для строки SystemHand |
MB_ICONQUESTION | Аналогично для строки SystemQuestion |
MB_OK | Аналогично для строки SystemDefault |
Функция MessageBeep пытается проиграть звуковой фрагмент в асинхронном (фоновом) режиме, если это позволяет звуковой драйвер. Если в системе установлен драйвер Sound Driver for PC Speaker, функция MessageBeep возвращает управление только после того, как проигрывание будет закончено. Если же функция не может проиграть нужный фрагмент, будет "исполнен" стандартный системный звук, определенный в строке SystemDefault раздела [sound] файла win.ini. Если же и это невозможно, вы услышите "бип" из встроенного в компьютер динамика.
А есть ли простой способ проигрывания произвольного wav-файла?
Есть, и он действительно прост. Этот способ основан на использовании функции sndPlaySound , которая находится в библиотеке mmsystem.dll. Ее прототип определен в файле mmsystem.h: BOOL sndPlaySound(LPCSTR lpszSoundFile, UINT wFlags);
Через параметр lpszSoundFile этой функции можно передать путь к wav-файлу, идентификатор ресурса, содержащего звуковой фрагмент (вы можете записать звуковой фрагмент в ресурсы приложения), или текстовую строку, определенную в разделе [sound] файла win.ini.
Параметр wFlags определяет способ проигрывания звукового фрагмента. Используются следующие значения (некоторые из них можно комбинировать при помощи операции ИЛИ):
Значение | Описание |
SND_SYNC | Синхронный режим работы. Функция sndPlaySound вернет управление только после завершения проигрывания звукового фрагмента |
SND_ASYNC | Асинхронный режим работы. Функция вернет управление немедленно, проигрывание звукового фрагмента будет выполняться в фоновом режиме параллельно с работой приложения |
SND_NODEFAULT | Если указанный файл не найден, функция "тихо" возвращает управление, не проигрывая никаких звуков. Если же этот флаг не указан, и файл не найден, будет проигран стандартный системный звук, определенный в строке SystemDefault раздела [sound] файла win.ini. А если и это невозможно, функция не будет ничего проигрывать и вернет значение FALSE |
SND_MEMORY | Это значение используется для проигрывания звуковых файлов, загруженных в оперативную память, например, из ресурсов приложения |
SND_LOOP | Если указано значение SND_ASYNC, проигрывание звукового фрагмента будет зациклено. Для того чтобы остановить проигрывание, необходимо вызвать функцию sndPlaySound, указав ей в качестве параметра lpszSoundFile значение NULL |
SND_NOSTOP | При указании этого значения функция проверяет, выполняется ли в настоящий момент проигрывание фрагмента. Если да, функция возвращает значение FALSE |
Во всех случаях, если не указан параметр SND_NOSTOP, функция sndPlaySound возвращает значение TRUE, если выполняется проигрывание, и FALSE - если нет. Учтите, что при использовании функций MessageBeep и sndPlaySound есть ограничение на размер wav-файла - он должен целиком помещаться в физическую память. Поэтому самые простые способы проигрывания звуковых фрагментов хороши только для относительно небольших файлов.
С помощью команды info можно получить информацию об MCI-устройстве: info device_id parameter [notify] [wait]
В качестве параметра parameter можно указывать одну из следующих строк: product
Текстовое описание звукового адаптера input
Текстовое описание устройства ввода звуковой информации output
Текстовое описание устройства вывода звуковой информации file
Имя текущего wav-файла
Работа с устройствами ввода/вывода системы мультимедиа напоминает работу с обычными файлами в том смысле, что вначале вы открываете устройство, затем выполняете с ним те или иные операции, затем закрываете устройство.
Прежде чем работать с устройством средствами MCI, его следует открыть при помощи команды open. Далее при необходимости можно задать режим работы устройства, послав ему команду set с параметрами. Для включения режима проигрывания или записи используются, соответственно, команды play и record . В любой момент времени можно узнать состояние устройства, если послать ему команду status . После использования устройства его необходимо закрыть при помощи команды close .
Коды управляющих сообщений делятся на системные (System), обязательные (Required), базовые (Basic) и расширенные (Extended), точно также как и команды, используемые в интерфейсе управляющих строк, рассмотренном нами в предыдущем разделе.
Приведем список кодов управляющих сообщений (в дальнейшем просто команд), которые используются для управления звуковым адаптером.
Команда | Тип | Описание |
MCI_BREAK | Системная | Назначение виртуального кода клавиши, с помощью которой можно прервать работу устройства |
MCI_SYSINFO | - | Получение системной информации об устройстве (в виде текстовой строки) |
MCI_GETDEVCAPS | Обязательная | Определение возможностей устройства |
MCI_CLOSE | - | Закрытие устройства |
MCI_INFO | - | Получение текстовой информации об устройстве |
MCI_OPEN | - | Открытие устройства |
MCI_STАTUS | - | Определение состояния устройства |
MCI_LOAD | Базовая | Загрузка данных из файла |
MCI_PAUSE | - | Пауза при проигрывании |
MCI_PLAY | - | Включение режима проигрывания |
MCI_RECORD | - | Включение режима записи |
MCI_RESUME | - | Продолжение проигрывания после паузы |
MCI_SAVE | - | Сохранение данных в файле |
MCI_SEEK | - | Позиционирование |
MCI_SET | - | Установка режима работы устройства |
MCI_STOP | - | Останов проигрывания |
MCI_CUE | Расширенная | Подготовка устройства для проигрывания или записи |
MCI_DELETE | - | Удаление фрагмента данных |
Как нетрудно заметить, состав и назначение групп управляющих сообщений полностью соответствует составу и назначению групп управляющих строк. Расскажем об использовании наиболее полезных управляющих сообщений (команд MCI).
С помощью команд этой группы можно выполнять запуск воспроизведения или записи, временный или полный останов, позиционирование и т. д.
В этой группе всего две команды - open и close, предназначенные, соответственно, для открытия и закрытия устройства ввода/вывода звука.
Какие бывают команды?
Все команды можно разделить на четыре группы : системные (System), обязательные (Required), базовые (Basic) и расширенные (Extended).
Системные команды не передаются драйверу устройства, они обрабатываются непосредственно системой MCI.
Обязательные команды поддерживаются любыми устройствами. Примером таких команд могут послужить строки open и close .
Некоторые из базовых команд могут не поддерживаться устройством. Например, устройство может только проигрывать дорожки звукового компакт-диска, но не записывать их.
И, наконец, расширенные команды, которые дополняют возможности базовых команд, могут поддерживаться только некоторыми типами устройств.
Приведем список команд, которые используются для управления звуковым адаптером.
Команда | Тип | Описание |
break | Системная | Назначение виртуального кода клавиши, с помощью которой можно прервать работу устройства. |
sysinfo | - | Получение системной информации об устройстве (в виде текстовой строки) |
capability | Обязательная | Определение возможностей устройства |
close | - | Закрывание устройства |
info | - | Получение текстовой информации об устройстве |
open | - | Открывание устройства |
status | - | Определение состояния устройства |
load | Базовая | Загрузка данных из файла |
pause | - | Пауза при проигрывании |
play | - | Включение режима проигрывания |
record | - | Включение режима записи |
resume | - | Продолжение проигрывания после паузы |
save | - | Сохранение данных в файле |
seek | - | Позиционирование |
set | - | Установка режима работы устройства |
stop | - | Останов проигрывания |
cue | Расширенная | Подготовка устройства для проигрывания или записи |
delete | - | Удаление фрагмента данных |
Далее мы расскажем об использовании этих команд применительно к звуковому адаптеру. Особенности других устройств мультимедиа будут рассмотрены позже.
В этой группе есть две команды - set и break.
С помощью команды MCI_BREAK указывается виртуальный код клавиши, предназначенной для прерывания выполнения операции. Для этой команды необходимо использовать следующую структуру блока параметров: typedef struct tagMCI_BREAK_PARMS { DWORD dwCallback; int nVirtKey; UINT wReserved0; HWND hwndBreak; UINT wReserved1; } MCI_BREAK_PARMS; typedef MCI_BREAK_PARMS FAR * LPMCI_BREAK_PARMS;
Поле nVirtKey определяет виртуальный код клавиши прерывания.
В поле hwndBreak можно указать идентификатор окна, которое должно быть текущим для обеспечения возможности прерывания с помощью заданной клавиши.
Поля wReserved0 и wReserved1 зарезервированы.
Для команды MCI_BREAK можно указывать следующие флаги:
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса позиционирования |
MCI_BREAK_KEY | Поле nVirtKey содержит виртуальный код клавиши прерывания команды |
MCI_BREAK_HWND | Поле hwndBreak содержит идентификатор окна, которое должно быть текущим для обеспечения возможности прерывания команды |
MCI_BREAK_OFF | Используется для отключения прерывания |
По умолчанию для прерывания используется комбинация клавиш <Control+Break>.
Эта команда закрывает устройство. Ее необходимо выдавать после завершения работы с устройством.
Для команды MCI_CLOSE используется блок параметров в виде структуры MCI_GENERIC_PARMS , описанной в файле mmsystem.h: typedef struct tagMCI_GENERIC_PARMS { DWORD dwCallback; } MCI_GENERIC_PARMS; typedef MCI_GENERIC_PARMS FAR *LPMCI_GENERIC_PARMS;
Эта упрощенная структура предназначена для тех случаев, когда команде не требуются дополнительные параметры. Поле dwCallback используется также, как и в команде MCI_OPEN.
В следующем фрагменте кода закрывается устройство с идентификатором, записанным в поле wDeviceID структуры mciOpen: MCI_GENERIC_PARMS mcigen; DWORD dwrc; mcigen.dwCallback = 0; dwrc = mciSendCommand(mciOpen.wDeviceID, MCI_CLOSE, MCI_WAIT, (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);
Команда MCI_COPY предназначена для копирования данных в универсальный буфер обмена Clipboard. Для нее используется блок параметров в формате структуры MCI_GENERIC_PARMS, флаги MCI_NOTIFY и MCI_WAIT.
Команда MCI_CUE используется для подготовки устройства к записи или воспроизведению, после которой эти операции выполняются с минимальной задержкой. Для нее используется блок параметров в формате структуры MCI_GENERIC_PARMS и следующий набор флагов:
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса |
Команда MCI_CUT удаляет данные из текущего буфера устройства и копирует их в универсальный буфер обмена Clipboard. Для нее используется блок параметров в формате структуры MCI_GENERIC_PARMS, флаги MCI_NOTIFY и MCI_WAIT.
Команда MCI_DELETE удаляет данные из текущего буфера устройства без копирования их в Clipboard. Для нее используется блок параметров в формате структуры MCI_GENERIC_PARMS.
Вместе с этой командой при работе со звуковым адаптером можно использовать структуру MCI_WAVE_DELETE_PARMS : typedef struct tagMCI_WAVE_DELETE_PARMS { DWORD dwCallback; DWORD dwFrom; DWORD dwTo; } MCI_WAVE_DELETE_PARMS; typedef MCI_WAVE_DELETE_PARMS FAR *LPMCI_WAVE_DELETE_PARMS;
Поле dwFrom используется для передачи команде начальной позиции для удаления, поле dwTo - для передачи конечной позиции удаления.
Вместе с командой MCI_DELETE используются перечисленные ниже флаги.
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса |
MCI_FROM | Поле dwFrom содержит начальную позицию для удаления |
MCI_TO | Поле dwTo содержит конечную позицию для удаления |
С помощью команды MCI_GETDEVCAPS можно определить возможности устройства мультимедиа. Для нее используется блок параметров в формате структуры MCI_GETDEVCAPS_PARMS , определенной в файле mmsystem.h следующим образом: typedef struct tagMCI_GETDEVCAPS_PARMS { DWORD dwCallback; DWORD dwReturn; DWORD dwItem; } MCI_GETDEVCAPS_PARMS; typedef MCI_GETDEVCAPS_PARMS FAR * LPMCI_GETDEVCAPS_PARMS;
В поле dwReturn после возврата из функции mciSendCommand будет записано значение требуемого параметра. Код нужного параметра следует записать в поле dwItem перед вызовом функции mciSendCommand.
Приведем возможные значения параметра dwItem:
Значение параметра dwItem | Описание |
MCI_GETDEVCAPS_CAN_EJECT | Если устройство может выталкивать носитель данных (например, компакт-диск), после возврата из функции mciSendCommand в поле dwReturn будет ненулевое значение TRUE |
MCI_GETDEVCAPS_CAN_PLAY | Устройство может проигрывать |
MCI_GETDEVCAPS_CAN_RECORD | Устройство может записывать |
MCI_GETDEVCAPS_CAN_SAVE | Устройство может сохранять записанные данные в файле |
MCI_GETDEVCAPS_COMPOUND_DEVICE | Устройство может работать с файлами |
MCI_GETDEVCAPS_DEVICE_TYPE | Требуется определить тип устройства. Для звукового адаптера возвращается константа MCI_DEVTYPE_WAVEFORM_AUDIO |
MCI_GETDEVCAPS_HAS_AUDIO | Устройство имеет звуковой выход |
MCI_GETDEVCAPS_HAS_VIDEO | Устройство имеет видеовыход |
MCI_GETDEVCAPS_USES_FILES | При открытии устройства требуется указывать имя файла |
MCI_WAVE_GETDEVCAPS_INPUT | Количество звуковых входов |
MCI_WAVE_GETDEVCAPS_OUTPUT | Количество звуковых выходов (каналов) |
Для команды MCI_GETDEVCAPS можно использовать следующий набор флагов:
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса |
MCI_GETDEVCAPS_ITEM | Поле dwItem содержит константу, соответствующую определяемому параметру устройства |
С помощью этой команды можно получить информацию об устройстве в виде текстовой строки, такую как описание аппаратуры, имя файла, связанного с устройством.
Используется блок параметров в формате структуры MCI_INFO_PARMS : typedef struct tagMCI_INFO_PARMS { DWORD dwCallback; LPSTR lpstrReturn; DWORD dwRetSize; } MCI_INFO_PARMS; typedef MCI_INFO_PARMS FAR * LPMCI_INFO_PARMS;
Поле lpstrReturn должно содержать дальний указатель на буфер, в который будет записана строка информации. Размер этого буфера следует передать через поле dwRetSize.
Приведем набор флагов для команды MCI_INFO:
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса |
MCI_INFO_PRODUCT | Требуется получить описание аппаратуры устройства |
MCI_INFO_FILE | Требуется получить имя текущего файла, связанного с устройством |
MCI_WAWE_INPUT | Имя текущего устройства ввода |
MCI_WAVE_OUTPUT | Имя текущего устройства вывода |
Команда MCI_LOAD предназначена для загрузки файла. Она использует блок параметров в формате структуры MCI_LOAD_PARMS : typedef struct tagMCI_LOAD_PARMS { DWORD dwCallback; LPCSTR lpfilename; } MCI_LOAD_PARMS; typedef MCI_LOAD_PARMS FAR * LPMCI_LOAD_PARMS;
Через поле lpfilename передается указатель на буфер, содержащий путь к файлу.
Вместе с командой MCI_LOAD можно использовать следующие флаги:
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса |
MCI_LOAD_FILE | Поле lpfilename содержит указатель на строку пути к файлу |
Перед использованием устройства мультимедиа вы должны его открыть, вызвав функцию mciSendCommand и указав ей в качестве второго параметра код команды MCI_OPEN .
Первый параметр (идентификатор устройства) можно указать как 0, так как пока мы не открыли устройство, ему не присвоен никакой идентификатор.
Через последний (четвертый) параметр нужно передать функции адрес заполненной структуры MCI_OPEN_PARMS , определенной в файле mmsystem.h: typedef struct tagMCI_WAVE_OPEN_PARMS { DWORD dwCallback; UINT wDeviceID; UINT wReserved0; LPCSTR lpstrDeviceType; LPCSTR lpstrElementName; LPCSTR lpstrAlias; } MCI_OPEN_PARMS; typedef MCI_OPEN_PARMS FAR *LPMCI_OPEN_PARMS;
В этой структуре младшее слово поля dwCallback должно содержать идентификатор окна, которому после выполнения команды будет посылаться извещение в виде сообщения MM_MCINOTIFY . Если извещение не посылается, значение этого поля игнорируется.
В поле wDeviceID после возвращения из функции mciSendCommand будет записан идентификатор, присвоенный устройству при открытии. Все последующие команды MCI должны ссылаться на этот идентификатор.
Поле wReserved0 зарезервировано, в него следует записать нулевое значение.
Поле lpstrDeviceType содержит указатель на строку имени устройства, или константный идентификатор устройства. Для звукового адаптера вы можете указать имя "waveaudio " или константу MCI_DEVTYPE_WAVWFORM_AUDIO .
Через параметр lpstrElementName передается указатель на путь к файлу, если нужно проиграть звуковой фрагмент, записанный в wav-файле.
Дополнительно при открытии устройства ему можно назначить алиасное имя, записав в поле lpstrAlias указатель на строку алиасного имени.
Третий параметр функции mciSendCommand предназначен для флагов, определяющих, какие из полей структуры параметров следует использовать при открытии устройства, а также для флага MCI_WAIT, устанавливающего режим работы функции (с ожиданием или без ожидания). Флаги можно объединять с помощью логической операции ИЛИ.
Для структуры MCI_OPEN_PARMS определены следующие флаги:
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса открытия устройства |
MCI_OPEN_ALIAS | Используется алиасное имя, адрес строки имени должен быть указан в поле lpstrAlias |
MCI_OPEN_SHAREABLE | Устройство открывается в режиме совместного использования несколькими приложениями одновременно |
MCI_OPEN_ELEMENT | Поле lpstrElementName содержит указатель на строку, в которой находится путь к файлу |
MCI_OPEN_TYPE | Поле lpstrDeviceType содержит указатель на строку имени устройства, например, адрес строки "waveaudio" |
MCI_OPEN_TYPE_ID | Поле lpstrDeviceType содержит константный идентификатор устройства, например, константу MCI_DEVTYPE_WAVWFORM_AUDIO |
Приведенный ниже фрагмент кода открывает устройство "waveaudio", причем будет открыт файл, адрес пути к которому записан в переменной szFileName: MCI_OPEN_PARMS mciOpen; DWORD dwFlags; mciOpen.lpstrDeviceType = (LPSTR)"waveaudio"; mciOpen.lpstrElementName = (LPSTR)szFileName; mciOpen.dwCallback = 0; mciOpen.wDeviceID = 0; mciOpen.wReserved0 = 0; mciOpen.lpstrAlias = NULL; dwFlags = MCI_OPEN_TYPE | MCI_OPEN_ELEMENT | MCI_WAIT; dwrc = mciSendCommand(0, MCI_OPEN, dwFlags, (DWORD)(LPVOID)&mciOpen);
После выполнения этого фрагмента в переменную dwrc, будет записан код результата завершения. При успешном завершении в поле wDeviceID структуры mciOpen будет находиться идентификатор открытого устройства.
Для звукового адаптера вы можете использовать расширенную структуру MCI_WAVE_OPEN_PARMS , также определенную в файле mmsystem.h: typedef struct tagMCI_WAVE_OPEN_PARMS { DWORD dwCallback; UINT wDeviceID; UINT wReserved0; LPCSTR lpstrDeviceType; LPCSTR lpstrElementName; LPCSTR lpstrAlias; DWORD dwBufferSeconds; } MCI_WAVE_OPEN_PARMS; typedef MCI_WAVE_OPEN_PARMS FAR *LPMCI_WAVE_OPEN_PARMS;
По сравнению со структурой MCI_OPEN_PARMS в ней есть дополнительное поле dwBufferSeconds. Это поле определяет размер внутреннего буфера системы MCI для звукового драйвера. Численно размер буфера должен быть равен длительности звучания в секундах.
Чтобы задействовать это дополнительное поле, следует указать функции mciSendCommand флаг MCI_WAVE_OPEN_BUFFER .
Команда MCI_PASTE вставляет данные из Clipboard в текущий буфер устройства. Для нее, как и для команды MCI_COPY, используется блок параметров в формате структуры MCI_GENERIC_PARMS, флаги MCI_NOTIFY и MCI_WAIT.
Команда MCI_PAUSE приостанавливает выполнение операции записи или воспроизведения. Она используется совместно с блоком параметров MCI_GENERIC_PARMS , который был рассмотрен выше.
Флаги для этой команды:
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса |
Пример использования команды MCI_PAUSE: MCI_GENERIC_PARMS mcigen; dwrc = mciSendCommand(wDeviceID, MCI_PAUSE, MCI_WAIT, (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);
Команда MCI_PLAY , как это видно из ее названия, предназначена для проигрывания файлов. Для этой команды в файле mmsystem.h определена структура блока параметров MCI_PLAY_PARMS : typedef struct tagMCI_PLAY_PARMS { DWORD dwCallback; DWORD dwFrom; DWORD dwTo; } MCI_PLAY_PARMS; typedef MCI_PLAY_PARMS FAR *LPMCI_PLAY_PARMS;
В структуре параметров можно указать начальную и конечную позиции для проигрывания. Начальная позиция задается в поле dwFrom, конечная - в поле dwTo. Перед использованием начальной позиции следует установить формат времени при помощи команды MCI_SET_TIME_FORMAT, которую мы рассмотрим позже. Формат времени определяет единицу измерения для позиции, например, миллисекунды, байты, выборки сигнала или кадры.
Для структуры MCI_PLAY_PARMS можно указывать следующие флаги:
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса проигрывания |
MCI_FROM | Поле dwFrom содержит начальную позицию для проигрывания |
MCI_TO | Поле dwTo содержит конечную позицию для проигрывания |
В приведенном ниже фрагменте запускается проигрывание с текущей позиции: MCI_PLAY_PARMS mciPlayParms; DWORD dwrc; mciPlayParms.dwCallback = (DWORD)hwnd; dwrc=mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)(LPVOID)&mciPlayParms);
Для изменения текущей позиции можно воспользоваться командой MCI_SEEK.
При завершении проигрывания окно с идентификатором hwnd получит сообщение MM_MCINOTIFY.
Команда MCI_RECORD позволяет выполнить запись в существующий или новый файл. Если при открытии устройства вы указали имя файла, будет выполняться запись в существующий файл. Для записи в новый файл нужно использовать имя нулевой длины. Результат записи в этом случае можно сохранить в файле при помощи команды MCI_SAVE.
Приведем формат блока параметров для команды MCI_RECORD: typedef struct tagMCI_RECORD_PARMS { DWORD dwCallback; DWORD dwFrom; DWORD dwTo; } MCI_RECORD_PARMS; typedef MCI_RECORD_PARMS FAR *LPMCI_RECORD_PARMS;
Параметры dwFrom и dwTo задают, соответственно, начальную и конечную позицию для записи.
Для структуры MCI_RECORD_PARMS можно указывать следующие флаги:
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса записи |
MCI_RECORD_INSERT | Необходимо вставить новую запись в уже существующие данные |
MCI_RECORD_OWERWRITE | Новая запись должна заместить существующие данные |
MCI_FROM | Поле dwFrom содержит начальную позицию для записи |
MCI_TO | Поле dwTo содержит конечную позицию для записи |
Если при выдаче команды MCI_RECORD вы не указали конечную позицию записи, запись будет продолжаться до тех пор, пока приложение не выдаст команду MCI_STOP или пока не будет израсходовано все свободное место на диске.
В приведенном ниже фрагменте кода запускается запись, которая будет продолжаться до достижения позиции dwMSec (запись может быть также остановлена раньше при помощи команды MCI_STOP): MCI_RECORD_PARMS mciRecordParms; mciRecordParms.dwTo = dwMSec; mciRecordParms.dwCallback = (DWORD)hwnd; dwrc=mciSendCommand(wInDeviceID, MCI_RECORD, MCI_NOTIFY | MCI_TO, (DWORD)(LPVOID)&mciRecordParms);
После завершения процесса записи окно с идентификатором hwnd получит сообщение MM_MCINOTIFY.
Эта команда отменяет действие команды MCI_PAUSE, при этом приостановленная операция будет продолжена. Для команды MCI_RESUME используется блок параметров MCI_GENERIC_PARMS .
Флаги для этой команды:
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса |
Пример использования команды MCI_RESUME: MCI_GENERIC_PARMS mcigen; dwrc = mciSendCommand(wDeviceID, MCI_RESUME, MCI_WAIT, (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);
Команда MCI_SAVE позволяет сохранить результат записи в файле. Для нее используется блок параметров MCI_SAVE_PARMS : typedef struct tagMCI_SAVE_PARMS { DWORD dwCallback; LPCSTR lpfilename; } MCI_SAVE_PARMS; typedef MCI_SAVE_PARMS FAR * LPMCI_SAVE_PARMS;
Поле lpfilename должно содержать указатель на путь к файлу.
Флаги для этой команды:
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса сохранения |
MCI_SAVE_FILE | Поле lpfilename содержит путь к файлу, в котором необходимо сохранить результат записи |
В следующем фрагменте кода выполняется сохранение записанных данных в файле с именем recorded.wav, который будет создан в текущем каталоге: MCI_SAVE_PARMS mciSave; mciSave.lpfilename = "recorded.wav"; dwrc=mciSendCommand(wInDeviceID, MCI_SAVE, MCI_SAVE_FILE | MCI_WAIT, (DWORD)(LPVOID)&mciSave);
Команда MCI_SEEK позволяет выполнять позиционирование в пределах файла. Для этой команды используется блок параметров MCI_SEEK_PARMS : typedef struct tagMCI_SEEK_PARMS { DWORD dwCallback; DWORD dwTo; } MCI_SEEK_PARMS; typedef MCI_SEEK_PARMS FAR *LPMCI_SEEK_PARMS;
Поле dwTo задает новую позицию в единицах, установленных командой MCI_SET_TIME_FORMAT.
Флаги для этой команды:
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса позиционирования |
MCI_SEEK_TO_START | Позиционирование на начало |
MCI_SEEK_TO_END | Позиционирование в конец |
MCI_SEEK_TO | Позиция определяется содержимым поля dwTo |
Команда MCI_SET предназначена для установки режима работы устройства. Вместе с этой командой используется блок параметров в формате структуры MCI_SET_PARMS : typedef struct tagMCI_SET_PARMS { DWORD dwCallback; DWORD dwTimeFormat; DWORD dwAudio; } MCI_SET_PARMS; typedef MCI_SET_PARMS FAR *LPMCI_SET_PARMS;
Поле dwTimeFormat определяет формат времени для устройства, поле dwAudio определяет выходной канал.
Для звуковых устройств можно использовать другую структуру: typedef struct tagMCI_WAVE_SET_PARMS { DWORD dwCallback; DWORD dwTimeFormat; DWORD dwAudio; UINT wInput; UINT wReserved0; UINT wOutput; UINT wReserved1; UINT wFormatTag; UINT wReserved2; UINT nChannels; UINT wReserved3; DWORD nSamplesPerSec; DWORD nAvgBytesPerSec; UINT nBlockAlign; UINT wReserved4; UINT wBitsPerSample; UINT wReserved5; } MCI_WAVE_SET_PARMS; typedef MCI_WAVE_SET_PARMS FAR * LPMCI_WAVE_SET_PARMS;
В этой структуре поле wInput определяет номер канала для записи, wOutput - номер канала для воспроизведения. Поле wFormatTag используется для определения формата звуковых данных. С помощью поля nChannels можно указать количество каналов - 1 (моно) или 2 (стерео). Поле nSamplesPerSec предназначено для задания частоты дискретизации (количество выборок сигнала в секунду). Поле nAvgBytesPerSec содержит скорость передачи данных (байты в секунду). С помощью поля nBlockAlign можно задать выравнивание блока, а с помощью поля wBitsPerSample - количество бит, используемых для представления одной выборки (8 или 16). Остальные поля зарезервированы.
Приведем список флагов, которые используются вместе с командой MCI_SET:
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса |
MCI_SET_AUDIO | Включение или выключение каналов, используется вместе с флагами MCI_SET_ON и MCI_SET_OFF. Поле dwAudio содержит номера канала. Дополнительно можно указать следующие константы:MCI_SET_AUDIO_ALL все каналыMCI_SET_AUDIO_LEFT левый каналMCI_SET_AUDIO_RIGHT правый канал |
MCI_SET_DOOR_CLOSED | По этой команде устройство защелкивает носитель данных (например, компакт-диск) |
MCI_SET_DOOR_OPEN | Освобождение носителя данных |
MCI_SET_VIDEO | Включение или выключение видеосигнала, используется вместе с флагами MCI_SET_ON и MCI_SET_OFF |
MCI_SET_ON | Включение заданного канала |
MCI_SET_OFF | Выключение заданного канала |
MCI_WAVE_INPUT | Установка канала для записи. Номер канала должен быть указан в поле wInput структуры MCI_WAVE_SET_PARAMS |
MCI_WAVE_OUTPUT | Установка канала для воспроизведения. Номер канала должен быть указан в поле wInput структуры MCI_WAVE_SET_PARAMS |
MCI_WAVE_SET_ANYINPUT | При записи следует использовать любое устройство, совместимое с заданным форматом данных |
MCI_WAVE_SET_ANYOUTPUT | При воспроизведении следует использовать любое устройство, совместимое с заданным форматом данных |
MCI_WAVE_SET_AVGBYTESPERSEC | Установить скорость потока данных при записи и воспроизведении из поля nAvgBytesPerSec |
MCI_WAVE_SET_BITSPERSAMPLE | Установить количество бит, используемых для представления одной выборки сигнала из поля wBitsPerSample |
MCI_WAVE_SET_BLOCKALIGN | Установить выравнивание блока из поля nBlockAlign |
MCI_WAVE_SET_CHANNELS | Поле nChannels содержит номер канала |
MCI_WAVE_SET_FORMATTAG | Установить формат из поля wFormatTag |
MCI_WAVE_SET_SAMPLESPERSEC | Установить частоту выборки из поля nSamplesPerSec |
MCI_WAVE_SET_TIME_FORMAT | Установить формат времени. Используется вместе со следующими константами:MCI_FORMAT_BYTES в байтах;MCI_FORMAT_MILLISECONDS в миллисекундах;MCI_FORMAT_SAMPLES в выборках сигнала |
Команда MCI_STATUS используется для определения текущего состояния устройства.
Формат соответствующего блока параметров описывается структурой MCI_STATUS_PARMS : typedef struct tagMCI_STATUS_PARMS { DWORD dwCallback; DWORD dwReturn; DWORD dwItem; DWORD dwTrack; } MCI_STATUS_PARMS; typedef MCI_STATUS_PARMS FAR * LPMCI_STATUS_PARMS;
Через поле dwReturn передается возвращаемая информация. Вид запрашиваемой информации определяется содержимым поля dwItem. Для устройств, которые работают с дорожками (например, устройство чтения компакт-дисков), в поле dwTrack можно указать размер или номер дорожки.
Приведем возможные значения параметра dwItem (для звукового адаптера):
Значение параметра dwItem | Описание получаемой информации |
MCI_STATUS_CURRENT_TRACK | Номер текущей дорожки |
MCI_STATUS_LENGTH | Общий размер (длина) фрагмента |
MCI_STATUS_MODE | Текущий режим устройства. Может иметь следующие значения:MCI_MODE_NOT_READY не готово;MCI_MODE_PAUSE пауза;MCI_MODE_PLAY проигрывание;MCI_MODE_STOP останов;MCI_MODE_OPEN открытие;MCI_MODE_RECORD запись;MCI_MODE_SEEK позиционирование |
MCI_STATUS_NUMBER_OF_TRACKS | Общее количество дорожек, которые можно проиграть |
MCI_STATUS_POSITION | Текущая позиция |
MCI_STATUS_READY | Если устройство готово, возвращается значение TRUE, в противном случае - FALSE |
MCI_STATUS_TIME_FORMAT | Текущий формат времени. Может иметь следующие значения:MCI_FORMAT_BYTES MCI_FORMAT_FRAMES MCI_FORMAT_HMS MCI_FORMAT_MILLISECONDS MCI_FORMAT_MSF MCI_FORMAT_SAMPLES MCI_FORMAT_TMSF |
MCI_STATUS_START | Начальная позиция |
MCI_STATUS_TRACK | В поле dwTrack записывается либо начальная позиция заданной дорожки (если дополнительно используется MCI_STATUS_POSITION), либо размер дорожки (если дополнительно используется MCI_STATUS_LENGTH) |
MCI_STATUS_MEDIA_PRESENT | Возвращается TRUE, если носитель данных вставлен в устройство |
MCI_WAVE_INPUT | Устройство, используемое для записи |
MCI_WAVE_OUTPUT | Устройство, используемое для воспроизведения |
MCI_WAVE_STATUS_AVGBYTESPERSEC | Скорость потока данных при записи и воспроизведении, байты в секунду |
MCI_WAVE_STATUS_BITSPERSAMPLE | Количество бит, используемых для представления одной выборки сигнала |
MCI_WAVE_STATUS_BLOCKALIGN | Текущее выравнивание блока |
MCI_WAVE_STATUS_CHANNELS | Количество каналов |
MCI_WAVE_FORMATTAG | Тег формата, используемого для записи, воспроизведения или сохранения данных в файле |
MCI_WAVE_STATUS_LEVEL | Текущий уровень записи или воспроизведения, используется 8- или 16-битовое значение в зависимости от формата данных. Младшее слово содержит уровень для монофонической записи или уровень правого канала для стереофонической записи. Уровень левого канала передается через старшее слово |
MCI_WAVE_STATUS_SAMPLESPERSEC | Скорость выборки сигнала (частота дискретизации) |
Приведем также список флагов для команды MCI_STATUS:
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса |
MCI_STATUS_ITEM | Поле wItem содержит код получаемой информации |
В приведенном ниже фрагменте кода определяется длительность звучания: mciStatus.dwItem = MCI_STATUS_LENGTH; dwrc = mciSendCommand(mciOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD)(LPVOID)&mciStatus);
Команда MCI_STOP останавливает выполнение операции записи или воспроизведения, после чего освобождает все буфера, которые были использованы для операции. Эта команда использует блок параметров MCI_GENERIC_PARMS .
Флаги для этой команды:
Флаг | Описание |
MCI_NOTIFY | Если установлен этот флаг, после завершения команды функции окна, адрес которой передан через поле dwCallback, будет послано сообщение MM_MCINOTIFY |
MCI_WAIT | Функция mciSendCommand вернет управление только после завершения процесса |
Пример использования команды MCI_STOP: MCI_GENERIC_PARMS mcigen; dwrc = mciSendCommand(wDeviceID, MCI_STOP, MCI_WAIT, (DWORD)(LPMCI_GENERIC_PARMS)&mcigen);
С помощью этой команды можно получить системную информацию об устройстве в виде текстовой строки. Используется блок параметров в формате структуры MCI_SYSINFO_PARMS : typedef struct tagMCI_SYSINFO_PARMS { DWORD dwCallback; LPSTR lpstrReturn; DWORD dwRetSize; DWORD dwNumber; UINT wDeviceType; UINT wReserved0; } MCI_SYSINFO_PARMS; typedef MCI_SYSINFO_PARMS FAR * LPMCI_SYSINFO_PARMS;
Поле lpstrReturn должно содержать дальний указатель на буфер, в который будет записана строка системной информации. Размер этого буфера следует передать через поле dwRetSize.
Поле dwNumber определяет положение устройства в таблице устройств MCI или в списке открытых устройств, если установлен флаг MCI_SYSINFO_OPEN. Поле wDeviceType определяет тип устройства. Поле wReserved0 зарезервировано.
Приведем набор флагов для команды MCI_INFO:
Флаг | Описание |
MCI_SYSINFO_INSTALLNAME | Требуется получить имя, которое было использовано в файле win.ini при установке драйвера устройства |
MCI_SYSINFO_NAME | Требуется получить имя устройства, соответствующего устройству с номером, заданным в поле dwNumber |
MCI_SYSINFO_OPEN | Определить имя открытого устройства или количество открытых устройств |
MCI_SYSINFO_QUANTITY | Определить количество устройств заданного типа, перечисленных в разделе [mci] файла system.ini. Если дополнительно установлен флаг MCI_SYSINFO_OPEN, возвращается количество открытых устройств |
Управляющая строка open посылается перед началом работы для открытия устройства. Эта строка имеет следующий формат: open device [type device_name] [alias alias] [buffer size] [notify] [wait]
Параметры, указанные в квадратных скобках, необязательные.
В качестве параметра device можно указывать имя драйвера устройства, имя самого устройства или имя звукового файла (можно также указать полный путь к файлу). Так как имя драйвера зависит от устройства, лучше пользоваться именем устройства или именем файла. Для звукового адаптера можно указать устройство waveaudio : open waveaudio
Это устройство обслуживается драйвером mciwave.drv, входящим в состав операционной системы Windows 3.1. Интерфейс управляющих строк MCI непригоден для работы с драйвером Sound Driver for PC Speaker , поэтому, если в системе установлен только такой драйвер, пользуйтесь функциями MessageBeep или sndPlaySound, рассмотренными нами ранее.
Если при открытии устройства указывается путь к файлу, тип устройства определяется по расширению имени с использованием раздела [mci extensions] файла win.ini: [mci extensions] wav=waveaudio mid=sequencer rmi=sequencer avi=AVIVideo
Поэтому следующая командная строка приведет к открытию устройства waveaudio: open c:\wave\bye.wav
Если через параметр device передается имя файла, можно указать тип устройства при помощи параметра type device_name. Например: open c:\wave\bye.wav type waveaudio
Это позволит использовать имена файлов с нестандартными расширениями. Например, вы можете переименовать файл bye.wav в файл bye.snd, при этом несмотря на то, что в разделе [mci extensions] файла win.ini расширение snd не описано, результат выполнения следующей команды будет правильный: open c:\wave\bye.snd type waveaudio
Вы можете также указать алиас (альтернативное имя) для работы с устройством, использовав параметр alias: open c:\wave\bye.wav alias sound
Параметр buffersize size задает размер буфера, который используется драйвером звукового адаптера (в секундах звучания).
Если указан параметр notify, и при передаче строки в последнем параметре функции mciSendString был указан идентификатор окна для оповещения, после того как устройство будет открыто, функция этого окна получит сообщение MM_MCINOTIFY.
С помощью команды open можно открыть устройство не только на воспроизведение, но и на запись. При этом в качестве параметра device нужно указать строку new. Следует также указать алиас. В качестве примера приведем последовательность команд, выполняющих запись: open new type waveaudio alias nsound wait record nsound
Для остановки записи следует выдать команду stop. Для сохранения записанного фрагмента в wav-файле нужно использовать команду save (команда close закрывает устройство, она будет описана ниже): stop nsound wait save nsound newsound.wav wait close nsound
Если указан параметр wait, функция mciSendString вернет управление только после завершения операции. Заметим, что параметры notify и wait используются практически со всеми управляющими строками.
Если вы открыли устройство ввода или вывода с указанием константы 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
Указан неправильный идентификатор устройства
Приложение может определить текущую позицию в блоке при записи или воспроизведении, вызвав функцию 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 |
Размер структуры 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
Указан неправильный идентификатор устройства
Теперь, когда вы познакомились со структурой 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 | Раздельное управление громкостью для левого и правого каналов |
Для останова устройства ввода используется функция waveInStop, которая была рассмотрена нами в разделе, посвященному записи звука на низком уровне. Для временного останова работы устройства вывода следует использовать функцию waveOutPause : Функция waveOutPause UINT waveOutPause( HWAVEOUT hWaveOut); // идентификатор устройства вывода
Параметры функции: hWaveOut
Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства
Возвращаемое значение:
При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки: MMSYSERR_INVALHANDLE
Указан неправильный идентификатор устройства
Если требуется прервать вывод, выполняемый в цикле, используйте функцию waveOutBreakLoop : Функция waveOutBreakLoop UINT waveOutBreakLoop( HWAVEOUT hWaveOut); // идентификатор устройства вывода
Параметры функции: hWaveOut
Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства
Возвращаемое значение:
При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки: MMSYSERR_INVALHANDLE
Указан неправильный идентификатор устройства
Для открытия файла предназначена функция 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);
Временный останов при воспроизведении или записи (пауза) pause device_id
Команда play предназначена для запуска процесса воспроизведения. Она имеет следующий формат: play device_id [from position [to position]] [notify] [wait]
Идентификатор устройства device_id создается при открытии устройства командой open. Например, если для открытия устройства была использована строка open c:\windows\ding.wav alias ding
то в качестве параметра device_id можно использовать алиас ding: play ding
Если не указан параметр from position, проигрывание начинается с текущей позиции. Сразу после открытия текущая позиция устанавливается в начало файла. Параметр to position позволяет указать конечную позицию, при достижении которой проигрывание прекращается. Перед использованием параметров from и to необходимо установить формат для позиционирования при помощи команды set (см. ниже).
Если задан параметр notify, и при передаче строки в последнем параметре функции mciSendString был указан идентификатор окна для оповещения, после завершения операции проигрывания функция этого окна получит сообщение MM_MCINOTIFY. Обработчик этого сообщения может закрыть устройство или выполнить повторное проигрывание фрагмента, например, с самого начала.
Указав параметр wait, можно установить псевдосинхронный режим воспроизведения. В этом случае функция mciSendString вернет управление только после завершения операции, однако работа других приложений Windows будет продолжаться во время воспроизведения. Если же этот параметр не указан, функция mciSendString запустит процесс в асинхронном режиме и немедленно вернет управление.
Для позиционирования внутри файла, открытого при помощи функции mmioOpen, следует использовать функцию mmioSeek . Функция mmioSeek LONG mmioSeek( HMMIO hmmio, // идентификатор открытого файла LONG dwOffset, // смещение для текущей позиции int nOrigin); // интерпретация смещения
Параметры функции: hmmio
Идентификатор открытого файла, полученный с помощью функции mmioOpen dwOffset
Величина смещения в байтах, на которое будет продвинута текущая позиция в файле. Интерпретация этого значения зависит от параметра nOrigin nOrigin
Этот параметр определяет способ использования смещения, заданного параметром dwOffset. Можно использовать константы SEEK_SET (смещение от начала файла), SEEK_CUR (смещение от текущей позиции в файле), SEEK_END (смещение от конца файла).
Возвращаемое значение:
Возвращается новое смещение текущей позиции в файле от начала файла (в байтах) или -1 при возникновении ошибки
Приложение 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, позволяющий проигрывать звуковые компакт-диски, а также драйверы других устройств, таких, как устройство ввода сигнала с линии, микрофона и музыкального синтезатора.
Приложение MCISTRVW (листинг 2.4) демонстрирует использование строчного интерфейса MCI для воспроизведения звукового файла с именем kaas.wav, расположенного в текущем каталоге (файл kaas.wav есть в каталоге mcistrvw на дискете, которая продается вместе с книгой). Это простейшее приложение не создает ни одного окна и, следовательно, не обрабатывает сообщения.
Листинг 2.4. Файл mcistrvw\mcistrvw.cpp // ----------------------------------------------------- // Приложение MCISTRVW // Демонстрирует использование командных строк MCI // ----------------------------------------------------- #define STRICT #include <windows.h> #include <mmsystem.h> void mciwioError(DWORD dwrc); #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { DWORD dwrc; BYTE szBuf[256], szBuf1[256]; // Открываем файл kaas.wav dwrc = mciSendString( (LPSTR)"open kaas.wav type waveaudio alias patr wait", (LPSTR)szBuf, 256, NULL); if(dwrc) mciwioError(dwrc); // Получаем имя устройства, под которым оно установлено // в файле system.ini dwrc = mciSendString((LPSTR)"sysinfo patr installname wait", (LPSTR)szBuf, 256, NULL); if(dwrc) mciwioError(dwrc); lstrcat(szBuf, (LPSTR)"\n"); // Добавляем к нему текстовое описание аппаратуры dwrc = mciSendString((LPSTR)"info patr product wait", (LPSTR)szBuf1, 256, NULL); if(dwrc) mciwioError(dwrc); lstrcat(szBuf, szBuf1); // Выводим на экран полученную информацию об устройстве MessageBox(NULL, szBuf, "MCISTRWV", MB_ICONINFORMATION); // Запускаем проигрывание в синхронном режиме dwrc = mciSendString((LPSTR)"play patr wait", (LPSTR)szBuf, 256, NULL); if(dwrc) mciwioError(dwrc); // После завершения проигрывания закрываем устройство dwrc = mciSendString((LPSTR)"close patr", (LPSTR)szBuf, 256, NULL); if(dwrc) mciwioError(dwrc); return 0; } // ----------------------------------------------------- // Функция mciwioError // Выводит текстовое описание ошибки // ----------------------------------------------------- void mciwioError(DWORD dwrc) { BYTE szBuf[MAXERRORLENGTH]; // Если коду ошибки, переданному через параметр dwrc // соответствует текстовое описание, выводим его на экран if(mciGetErrorString(dwrc, (LPSTR)szBuf, MAXERRORLENGTH)) MessageBox(NULL, szBuf, "MCISTRVW Error", MB_ICONEXCLAMATION); // В противном случае выводим сообщение о том, что это // неизвестная ошибка else MessageBox(NULL, "Неизвестная ошибка", "MCISTRVW Error", MB_ICONEXCLAMATION); }
Сразу после запуска приложение открывает устройство waveaudio с файлом kaas.wav, передавая ему следующую команду: open kaas.wav type waveaudio alias patr wait
При этом устройству назначается алиас patr. Так как задан параметр wait, работа приложения будет продолжена только после завершения процесса открытия.
После открытия устройства приложение получает от него некоторую справочную информацию, и затем выводит ее на экран с помощью функции MessageBox.
Для проигрывания выдается следующая команда (используется алиас, назначенный при открытии устройства): play patr wait
Работа приложения MCISTRVW приостанавливается до завершения процесса воспроизведения, однако при этом другие приложения могут работать.
Перед завершением своей работы приложение закрывает устройство: close patr
Каждый раз после выдачи команды приложение проверяет код возврата функции mciSendString. Если он не равен нулю, вызывается обработчик ошибок (функция mciwioError), задача которого заключается в выводе текстового описания ошибки на экран. Для преобразования кода ошибки в текстовое сообщение используется функция mciGetErrorString.
Файл ресурсов приложения MCISTRVW приведен в листинге 2.5.
Листинг 2.5. Файл mcistrvw\mcistrvw.def APPICON ICON "mcistrwv.ico"
Файл определения модуля вы сможете найти в листинге 2.6.
Листинг 2.6. Файл mcistrvw\mcistrvw.def NAME MCISTRVW DESCRIPTION 'Приложение MCISTRVW, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
В составе системы разработки приложений Microsoft SDK в качестве примера поставляются исходные тексты приложения MCITEST (загрузочный модуль этого приложения есть в каталоге SDK\MCITEST на дискете, которая продается вместе с книгой). Приложение MCITEST (рис. 2.1) удобно использовать для изучения строчного интерфейса MCI и для отладки последовательностей строчных команд MCI.
В качестве примера использования интерфейса сообщений MCI приведем исходные тексты несложного приложения MCIWAVER, с помощью которого можно записывать и воспроизводить wav-файлы (рис. 2.2).
Наше первое приложение SNDPLAY, имеющее зачатки мультимедиа, предназначено для демонстрации различных способов работы с функцией sndPlaySound (листинг 2.1).
Листинг 2.1. Файл sndplay\sndplay.cpp // ---------------------------------------- // Использование функций // MessageBeep и sndPlaySound // ---------------------------------------- #define STRICT #include <windows.h> #include <mmsystem.h> #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { HANDLE hWaveRes, hRes; LPSTR lpRes; BOOL rc; HFILE hf; DWORD dwFileSize; HGLOBAL hWave; char huge *lpBuf; // Проигрываем звук, соответствующий строке // SystemQuestion раздела [sound] файла win.ini MessageBeep(MB_ICONQUESTION); MessageBox(NULL, "Начнем, что ли?", "SndPlay", MB_OK | MB_ICONQUESTION); // Проигрываем файл sndplay.snd в синхронном режиме rc = sndPlaySound((LPSTR)"sndplay.wav", SND_SYNC); if(!rc) { MessageBeep(MB_ICONHAND); MessageBox(NULL, "Не могу проиграть файл sndplay.wav", "SndPlay", MB_OK | MB_ICONHAND); return -1; } // Загружаем звуковой фрагмент из ресурсов приложения // и проигрываем его // Находим нужный ресурс hWaveRes = FindResource(hInstance, "APP_SOUND", "WAVE"); if(hWaveRes) { // Загружаем ресурс в память hRes = LoadResource(hInstance, (HRSRC)hWaveRes); if(hRes) { // Фиксируем ресурс в памяти, получая // указатель на данные lpRes = (LPSTR)LockResource(hRes); if(lpRes) { // Проигрываем звук в цикле rc = sndPlaySound(lpRes, SND_MEMORY | SND_ASYNC | SND_LOOP); MessageBox(NULL, "Для завершения нажмите кнопку OK", "SndPlay", MB_OK | MB_ICONINFORMATION); // Останавливаем проигрывание sndPlaySound(NULL, 0); // Расфиксируем и освобождаем ресурс UnlockResource(hRes); FreeResource(hRes); // Загружаем звуковой фрагмент непосредственно из // wav-файла в память и проигрываем его // Открываем wav-файл hf = _lopen((LPSTR)"uff.wav", OF_READ); // Определяем размер файла dwFileSize = _llseek(hf, 0l, 2); _llseek(hf, 0l, 0); // Заказываем глобальный блок памяти, // размер которого равен длине файла hWave = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, dwFileSize); // Фиксируем блок памяти lpBuf = (char huge *)GlobalLock(hWave); if(lpBuf != NULL) { // Читаем файл в полученный блок памяти _hread(hf, lpBuf, dwFileSize); // Проигрываем звуковой фрагмент, загруженный в память rc = sndPlaySound((LPCSTR)lpBuf, SND_MEMORY | SND_SYNC); if(!rc) { MessageBeep(MB_ICONHAND); MessageBox(NULL, "Не могу проиграть файл uff.wav", "SndPlay", MB_OK | MB_ICONHAND); } // Расфиксируем и освобождаем память GlobalUnlock(hWave); GlobalFree(hWave); // Закрываем файл _lclose(hf); } } } } return 0; }
Приложение не имеет главного окна и функции окна. Сразу после запуска приложение SNDPLAY вызывает функцию MessageBeep, с помощью которой проигрывается звук, соответствующий строке SystemQuestion раздела [sound] файла win.ini.
Затем приложение вызывает функцию sndPlaySound для проигрывания файла sndplay.wav в синхронном режиме: rc = sndPlaySound((LPSTR)"sndplay.wav", SND_SYNC);
Как только этот файл будет проигран, функция sndPlaySound вернет управление и работа приложения будет продолжена.
Далее приложение загружает звуковой фрагмент из ресурсов и проигрывает его асинхронно в циклическом режиме. При этом на экран выводится диалоговая панель с сообщением о то, что для прекращения циклического проигрывания следует нажать кнопку OK. Методика работы с ресурсами была описана нами в 12 томе "Библиотеки системного программиста". После поиска и фиксирования ресурса адрес соответствующего блока памяти передается в качестве первого параметра функции sndPlaySound: rc = sndPlaySound(lpRes, SND_MEMORY | SND_ASYNC | SND_LOOP);
Так как звуковой файл находится в памяти, во втором параметре этой функции необходимо указать флаги SND_MEMORY и SND_ASYNC. Для циклического проигрывания следует также указать флаг SND_LOOP.
Для прерывания циклического проигрывания функция sndPlaySound вызывается с нулевыми параметрами: sndPlaySound(NULL, 0);
В некоторых случаях может оказаться удобным проигрывать звуковой фрагмент, загруженный в память не из ресурсов приложения, а непосредственно из wav-файла. Финальная часть приложения SNDPLAY демонстрирует этот способ.
Вначале приложение открывает wav-файл с помощью функции _lopen и определяет его размер, вызывая функцию _llseek : hf = _lopen((LPSTR)"uff.wav", OF_READ); dwFileSize = _llseek(hf, 0l, 2); _llseek(hf, 0l, 0);
Далее приложение заказывает глобальный блок памяти такого размера, чтобы в нем мог поместиться весь wav-файл. Блок фиксируется в памяти: hWave = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, dwFileSize); lpBuf = (char huge *)GlobalLock(hWave);
Теперь, если передать адрес этого блока функции sndPlaySound, можно проиграть его в асинхронном или синхронном режиме. Так как мы освобождаем память сразу после возврата управления из функции sndPlaySound, для простоты мы выбрали синхронный режим: rc = sndPlaySound((LPCSTR)lpBuf, SND_MEMORY | SND_SYNC);
Только после того, как проигрывание закончено, можно расфиксировать и освободить память с образом wav-файла: GlobalUnlock(hWave); GlobalFree(hWave);
Если освободить память до момента окончания проигрывания, Windows перейдет в нестабильное состояние, требующее полного перезапуска.
Файл ресурсов приложения (листинг 2.2) содержит описание ресурса типа WAVE (можно использовать любой другой нестандартный тип ресурса):
Листинг 2.2. Файл sndplay\sndplay.rc APP_SOUND WAVE loop.wav APPICON ICON "sndplay.ico"
Файл определения модуля приложения SNDPLAY приведен в листинге 2.3.
Листинг 2.3. Файл sndplay\sndplay.def NAME SNDPLAY DESCRIPTION 'Приложение SNDPLAY, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
В качестве примера использования интерфейса нижнего уровня для записи и воспроизведения wav-файлов мы представим вам исходные тексты приложения WAVE (рис.2.5).
Эта команда запускает запись звукового фрагмента.
В качестве необязательного параметра parameter можно указывать одну из следующих строк: insert
Новые данные будут добавлены к ранее записанным from position
Определение начальной позиции для записи. Если начальная позиция не задана, данные будут вставлены начиная с текущей позиции. Если используются параметры from или to, необходимо задать формат времени командой set time format to position
Определение конечной позиции при записи. Если конечная позиция не задана, запись будет продолжаться до тех пор, пока не будет выдана команда stop или pause overwrite
Новые данные должны заместить записанные ранее
Продолжение воспроизведения или записи после временного останова по команде pause. resume device_id [notify] [wait]
Окно, расположенное в верхней части экрана, предназначено для ввода командных строк. Это окно представляет собой многострочный редактор текста, поэтому вы можете вводить сразу несколько строк. Введенные команды можно выполнять по отдельности, нажимая клавишу <Enter> или кнопку "Step", или все вместе (при помощи кнопки "Go!").
В окне "MCI Output" отображается результат выполнения операции. Если произошла ошибка, ее текстовое описание появляется в окне "Error".
Если в качестве одного из параметров команды была указана строка notyfy, в окне "Notification" отображается результат, переданный с сообщением MM_MCINOTIFY.
С помощью меню "File" вы можете сохранять и загружать последовательности команд MCI, проверяя их в работе.
Если выбрать из главного меню приложения строку "Record!", включится запись. Вы сможете записать звуковой фрагмент длительностью до 60 секунд (максимальное время записи определяется константой MAXRECORDTIME, вы можете изменить значение этой константы в исходном тексте приложения). Для прерывания процесса записи в любой момент времени можно выбрать из меню строки "Stop!" или "Pause!". Результат записи всегда сохраняется в файле с именем recorded.wav, который создается в текущем каталоге.
С помощью строки "Open..." меню "File" можно выбрать wav-файл для воспроизведения. Путь к выбранному файлу отобразится в заголовке окна. Для прослушивания загруженного wav-файла воспользуйтесь строкой "Play!". Прослушивание можно прекратить (строка "Stop!") или временно приостановить (строка "Pause!"). Для продолжения прослушивания после временного останова выберите строку "Resume!".
Полоса просмотра служит для отображения текущей позиции при записи и воспроизведении. Над полосой просмотра выводится текущий режим работы приложения.
Основной файл приложения MCIWAWER приведен в листинге 2.7.
Листинг 2.7. Файл mciwaver\mciwaver.cpp // ------------------------------------------------ // Приложение MCIWAVE // Проигрывание и запись wav-файлов // с помощью интерфейса сообщений MCI // ------------------------------------------------ #define STRICT #include <windows.h> #include <windowsx.h> #include <mmsystem.h> #include <mem.h> #pragma hdrstop #include "mciwave.hpp" #include "mciwavio.hpp" // Идентификатор таймера #define BEEP_TIMER 1 // Идентификатор полосы просмотра #define ID_SCROLL 10 // Длина полосы просмотра #define SCROLL_SIZE 400 // Длительность записи в миллисекундах #define MAXRECORDTIME 60000L // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); // Глобальные переменные int nMode = MODE_STOP; MMTIME mmtimeIn, mmtimeOut; BOOL fFileLoaded = FALSE; int nPosition; HWND hScroll; UINT wOutDeviceID; UINT wInDeviceID; BYTE szFileName[128]; DWORD dwFileSize; char const szClassName[] = "MCIWaveClass"; char const szWindowTitle[] = "MCIWaver"; HINSTANCE hInst; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения if(hPrevInstance) return FALSE; if(!InitApp(hInstance)) return FALSE; hInst = hInstance; hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // размеры и расположение окна CW_USEDEFAULT, 450, 120, 0, 0, hInstance, NULL); if(!hwnd) return FALSE; ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации memset(&wc, 0, sizeof(wc)); wc.lpszMenuName = "APP_MENU"; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APPICON"); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszClassName = (LPSTR)szClassName; aWndClass = RegisterClass(&wc); return (aWndClass != 0); } // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; int rc; switch (msg) { // ------------------------------------------------------------ // WM_CREATE // Создание главного окна приложения // ------------------------------------------------------------ case WM_CREATE: { nMode = MODE_STOP; fFileLoaded = FALSE; wOutDeviceID = 0; wInDeviceID = 0; // Создаем таймер SetTimer(hwnd, BEEP_TIMER, 100, NULL); // Создаем полосу просмотра hScroll = CreateWindow("scrollbar", NULL, WS_CHILD | WS_VISIBLE | SBS_HORZ, 10, 40, SCROLL_SIZE, 15, hwnd, (HMENU) ID_SCROLL, hInst, NULL); // Устанавливаем текущую позицию nPosition = 0; // Устанавливаем минимальное и максимальное // значения для полосы просмотра SetScrollRange(hScroll, SB_CTL, 1, SCROLL_SIZE, TRUE); // Устанавливаем ползунок SetScrollPos(hScroll, SB_CTL, nPosition, TRUE); return 0; } // ------------------------------------------------------------ // WM_PAINT // Рисование в окне // ------------------------------------------------------------ case WM_PAINT: { // Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps); // Отображаем текущий режим работы if(nMode == MODE_STOP) TextOut(hdc, 10, 10, "Остановлено", 11); else if(nMode == MODE_RECORDING) TextOut(hdc, 10, 10, "Идет запись...", 14); else if(nMode == MODE_PLAYING) TextOut(hdc, 10, 10, "Идет проигрывание...", 20); else if(nMode == MODE_RECORDINGPAUSED) TextOut(hdc, 10, 10, "Запись остановлена", 18); else if(nMode == MODE_PLAYINGPAUSED) TextOut(hdc, 10, 10, "Проигрывание остановлено", 24); else TextOut(hdc, 10, 10, "Неправильный режим!", 19); // Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; } // ------------------------------------------------------------ // WM_COMMAND // Обработка сообщений от меню // ------------------------------------------------------------ case WM_COMMAND: { switch (wParam) { // ------------------------------------------------- // Строка "About" меню "Help" // ------------------------------------------------- case CM_HELPABOUT: { MessageBox(hwnd, "MCIWaver, v.1.0\n" "(C) Frolov A.V., 1994", "About MCIWaver", MB_OK | MB_ICONINFORMATION); return 0; } // ------------------------------------------------- // Строка "Open" меню "File" // ------------------------------------------------- case CM_FILEOPEN: { char szTitle[256]; // Загружаем новый файл if(!mciwioSelectFile(szFileName)) return 0; // Отображаем в заголовке окна путь к файлу lstrcpy(szTitle, szWindowTitle); lstrcat(szTitle, " - "); lstrcat(szTitle, szFileName); SetWindowText(hwnd, szTitle); // Если было запущено воспроизведение, // останавливаем его и закрываем устройство вывода if(wOutDeviceID) { mciwioStop(wOutDeviceID); mciwioClose(wOutDeviceID); wOutDeviceID = 0; // Новый режим nMode = MODE_STOP; } // Устанавливаем движок в начало полосы просмотра nPosition = 0; SetScrollPos(hScroll, SB_CTL, nPosition, TRUE); // Устанавливаем флаг загрузки файла fFileLoaded = TRUE; return 0; } // ------------------------------------------------- // Строка "Play!" // Проигрывание загруженного wav-файла // ------------------------------------------------- case CM_CTLPLAY: { // Если файл загружен и не проигрывается, // запускаем проигрывание файла if((fFileLoaded == TRUE) && (nMode == MODE_STOP)) { // Новый режим nMode = MODE_PLAYING; // Перерисовываем окно для отображения строки, // соответствующей новому режиму InvalidateRect(hwnd, NULL, TRUE); // Открываем устройство wOutDeviceID = mciwioOpen((LPSTR)szFileName); // Проигрываем файл mciwioPlay(hwnd, wOutDeviceID); } return 0; } // ------------------------------------------------- // Строка "Record!" // Запись wav-файла // ------------------------------------------------- case CM_CTLRECORD: { // Запись возможна только из состояния останова if(nMode == MODE_STOP) { nMode = MODE_RECORDING; InvalidateRect(hwnd, NULL, TRUE); // Запись файла wInDeviceID = mciwioRecord(hwnd, MAXRECORDTIME); } return 0; } // ------------------------------------------------- // Строка "Stop!" // Останов проигрывания или записи wav-файла // ------------------------------------------------- case CM_CTLSTOP: { if(nMode == MODE_RECORDING || nMode == MODE_RECORDINGPAUSED) { // Останавливаем запись mciwioStop(wInDeviceID); } else if(nMode == MODE_PLAYING || nMode == MODE_PLAYINGPAUSED) { // Останавливаем проигрывание mciwioStop(wOutDeviceID); } // Устанавливаем движок в начало полосы просмотра nPosition = 0; SetScrollPos(hScroll, SB_CTL, nPosition, TRUE); // Новый режим nMode = MODE_STOP; InvalidateRect(hwnd, NULL, TRUE); return 0; } // ------------------------------------------------- // Строка "Pause!" // Временный останов проигрывания или // полный останов записи wav-файла // ------------------------------------------------- case CM_CTLPAUSE: { if(nMode == MODE_RECORDING) { // Останов записи mciwioStop(wInDeviceID); } else if(nMode == MODE_PLAYING) { // Временный останов проигрывания mciwioPause(wOutDeviceID); nMode = MODE_PLAYINGPAUSED; } InvalidateRect(hwnd, NULL, TRUE); return 0; } // ------------------------------------------------- // Строка "Resume!" // Продолжение проигрывания после останова // ------------------------------------------------- case CM_CTLRESUME: { if(nMode == MODE_PLAYINGPAUSED) { // Продолжаем проигрывание mciwioResume(wOutDeviceID); nMode = MODE_PLAYING; InvalidateRect(hwnd, NULL, TRUE); } return 0; } // ------------------------------------------------- // Строка "Exit" меню "File" // Завершение работы приложения // ------------------------------------------------- case CM_FILEEXIT: { DestroyWindow(hwnd); return 0; } default: return 0; } } // ------------------------------------------------------------ // MM_MCINOTIFY // ------------------------------------------------------------ case MM_MCINOTIFY: { // Если находились в режиме записи, сохраняем файл if(wInDeviceID) { MCI_SAVE_PARMS mciSave; MCI_GENERIC_PARMS mcigen; DWORD dwrc; // Имя файла, в котором будет сохранен звуковой фрагмент mciSave.lpfilename = "recorded.wav"; dwrc=mciSendCommand(wInDeviceID, MCI_SAVE, MCI_SAVE_FILE | MCI_WAIT, (DWORD)(LPVOID)&mciSave); if(dwrc) { mciwioError(dwrc); } // Закрываем устройство записи dwrc = mciSendCommand(wInDeviceID, MCI_CLOSE, MCI_WAIT, (DWORD)(LPMCI_GENERIC_PARMS)&mcigen); if(dwrc) { mciwioError(dwrc); } wInDeviceID = 0; } // Если находились в режиме воспроизведения, останавливаем // и закрываем устройство вывода else if(wOutDeviceID) { mciwioStop(wOutDeviceID); mciwioClose(wOutDeviceID); wOutDeviceID=0; } nMode = MODE_STOP; InvalidateRect(hwnd, NULL, TRUE); return 0; } // ------------------------------------------------------------ // WM_TIMER // Сообщение от таймера // ------------------------------------------------------------ case WM_TIMER: { MCI_STATUS_PARMS mciStatus; DWORD dwPos; // Режим записи if(nMode == MODE_RECORDING) { // Определяем текущую позицию внутри блока mciStatus.dwItem = MCI_STATUS_POSITION; mciSendCommand(wInDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD)(LPVOID)&mciStatus); dwPos = mciStatus.dwReturn; // Вычисляем новое положение движка полосы просмотра nPosition = ((DWORD)SCROLL_SIZE * dwPos) / MAXRECORDTIME; // Ограничиваем пределы изменения текущей // позиции значениями от 1 до SCROLL_SIZE if(nPosition > SCROLL_SIZE) nPosition = SCROLL_SIZE; if(nPosition < 1) nPosition = 1; // Устанавливаем движок полосы просмотра // в соответствии с новым значением текущей позиции SetScrollPos(hScroll, SB_CTL, nPosition, TRUE); } // Режим воспроизведения else if(nMode == MODE_PLAYING) { // Определяем текущую позицию внутри блока mciStatus.dwItem = MCI_STATUS_POSITION; mciSendCommand(wOutDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD)(LPVOID)&mciStatus); dwPos = mciStatus.dwReturn; // Вычисляем новое положение движка полосы просмотра nPosition = ((DWORD)SCROLL_SIZE * dwPos) / dwFileSize; // Ограничиваем пределы изменения текущей // позиции значениями от 1 до SCROLL_SIZE if(nPosition > SCROLL_SIZE) nPosition = SCROLL_SIZE; if(nPosition < 1) nPosition = 1; // Устанавливаем ползунок полосы просмотра // в соответствии с новым значением текущей позиции SetScrollPos(hScroll, SB_CTL, nPosition, TRUE); } return 0; } // ------------------------------------------------------------ // WM_DESTROY // Уничтожение главного окна приложения // ------------------------------------------------------------ case WM_DESTROY: { // Удаляем таймер и полосу просмотра KillTimer(hwnd, BEEP_TIMER); DestroyWindow(hScroll); // Если находимся в режиме записи, останавливаем // запись и закрываем устройство ввода if(nMode == MODE_RECORDING || nMode == MODE_RECORDINGPAUSED) { mciwioStop(wInDeviceID); mciwioClose(wInDeviceID); } else if(fFileLoaded) { // Если находимся в режиме проигрывания, останавливаем // запись и закрываем устройство вывода if(nMode == MODE_PLAYING || nMode == MODE_PLAYINGPAUSED) { mciwioStop(wOutDeviceID); mciwioClose(wOutDeviceID); } nMode = MODE_STOP; } PostQuitMessage(0); return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
При создании главного окна приложения обработчиком сообщения WM_CREATE выполняется инициализация глобальных переменных. Устанавливается текущий режим работы nMode (останов), флаг загрузки файла для проигрывания fFileLoaded, в переменные, соответствующие идентификаторам устройств ввода и вывода (wOutDeviceID и wInDeviceID) записываются нулевые значения.
Положение движка на полосе просмотра, отражающее текущую позицию при записи и воспроизведении, определяется по сообщениям таймера. На этапе инициализации создается таймер, который посылает сообщения каждую десятую долю секунды. Кроме того, создается полоса просмотра, устанавливается текущая позиция, диапазон изменений значений и начальное положение движка.
Во время обработки сообщения WM_PAINT в верхней части окна отображается текущий режим работы, для чего используется функция TextOut.
Когда вы выбираете wav-файл для воспроизведения, функция окна получает сообщение WM_COMMAND с параметром wParam, равным значению CM_FILEOPEN. Соответствующий обработчик загружает файл, вызывая функцию mciwioSelectFile. Эта функция определена в нашем приложении, ее исходный текст находится в файле mciwaveio.cpp (листинг 2.9). С помощью функции SetWindowText путь к файлу отображается в заголовке окна приложения.
Если в момент выбора нового файла приложение находилось в режиме проигрывания, следует остановить и закрыть устройство вывода. Для этого мы используем функции mciwioStop и mciwioClose, соответственно. После того, как устройство вывода закрыто, мы сбрасываем содержимое переменной wOutDeviceID (идентификатор устройства вывода). Кроме того, в переменную nMode записываем код нового состояния (останов).
В завершении выполняется установка движка полосы просмотра в начальное положение. В переменную fFileLoaded записывается значение TRUE (загружен файл для воспроизведения).
После выбора строки "Play!" проверяется текущий режим и флаг загрузки файла. Если файл загружен, и приложение находится в состоянии останова, можно начинать воспроизведение. В переменную nMode записывается константа MODE_PLAYING (воспроизведение), открывается устройство вывода (функция mciwioOpen) и запускается воспроизведение (функция mciwioPlay). Для того чтобы название нового режима было отображено на экране, выполняется перерисовка окна (функция InvalidateRect). Исходные тексты функций mciwioOpen и mciwioPlay находятся в файле mciwaveio.cpp (листинг 2.9).
Запись также можно запустить только из состояния останова. Для записи вызывается функция mciwioRecord (ее исходный текст также находится в листинге 2.9), которой в качестве второго параметра передается максимальная длительность записи в миллисекундах.
При выполнении команды останова (строка "Stop!" в меню приложения) анализируется текущий режим работы. Если приложение находится в режиме записи, вызывается функция mciwioStop (останов устройства), причем в качестве параметра ей передается идентификатор устройства ввода. Если же приложение находится в состоянии воспроизведения, вызывается эта же функция, но в качестве параметра ей передается идентификатор устройства вывода. Далее движок полосы просмотра устанавливается в начальное положение, а переменную nMode записывается код состояния останова.
В ответ на команду временного останова (строка "Pause!") выполняется полный останов записи или временный останов воспроизведения. В последнем случае вызывается функция mciwioPause, которой в качестве параметра передается идентификатор устройства вывода. В переменную nMode записывается значение константы MODE_PLAYINGPAUSED, которое обозначает временный останов воспроизведения.
Для продолжения воспроизведения после временного останова вызывается функция mciwioResume, которой в качестве параметра передается идентификатор устройства вывода.
Функция окна приложения обрабатывает сообщение MM_MCINOTIFY, поступающее после завершения записи или воспроизведения. Обработчик этого сообщения сохраняет записанные данные в том случае, если было открыто устройство ввода. Для этого он вызывает функцию mciSendCommand, передавая с ее помощью устройству ввода команду MCI_SAVE_FILE: mciSave.lpfilename = "recorded.wav"; dwrc=mciSendCommand(wInDeviceID, MCI_SAVE, MCI_SAVE_FILE | MCI_WAIT, (DWORD)(LPVOID)&mciSave);
После сохранения устройство ввода закрывается, в переменную wInDeviceID записывается нулевое значение: dwrc = mciSendCommand(wInDeviceID, MCI_CLOSE, MCI_WAIT, (DWORD)(LPMCI_GENERIC_PARMS)&mcigen); if(dwrc) { mciwioError(dwrc); } wInDeviceID = 0;
Если сообщение MM_MCINOTIFY пришло при воспроизведении, устройство вывода останавливается и закрывается. В переменную wOutDeviceID записывается нулевое значение.
В любом случае обработчик сообщения MM_MCINOTIFY переводит приложение в режим останова.
Обработчик сообщения WM_TIMER предназначен для определения и отображения текущей позиции в режимах записи и воспроизведения.
В режиме записи текущая позиция определяется при помощи сообщения MCI_STATUS: mciStatus.dwItem = MCI_STATUS_POSITION; mciSendCommand(wInDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD)(LPVOID)&mciStatus); dwPos = mciStatus.dwReturn;
По умолчанию для звукового устройства ввода/вывода устанавливается формат времени в миллисекундах. Поэтому в переменную dwPos будет записана текущая позиция в миллисекундах.
Далее обработчик вычисляет положение движка полосы просмотра, исходя из диапазона изменения значений полосы просмотра SCROLL_SIZE, максимального времени записи MAXRECORDTIME и текущей позиции dwPos: nPosition = ((DWORD)SCROLL_SIZE * dwPos) / MAXRECORDTIME;
Затем движок устанавливается в новое положение при помощи функции SetScrollPos.
В режиме воспроизведения новое положение движка просмотра вычисляется исходя из размера файла dwFileSize: nPosition = ((DWORD)SCROLL_SIZE * dwPos) / dwFileSize;
Размер файла в миллисекундах записывается в глобальную переменную dwFileSize функцией mciwioOpen при открытии файла.
При завершении работы приложения удаляются таймер и полоса просмотра. Далее, если приложение находилось в режиме записи, устройство ввода останавливается и закрывается. Аналогичные действия выполняются с устройством вывода в режиме воспроизведения.
Константы для работы с меню определены в файле mciwave.hpp (листинг 2.8).
Листинг 2.8. Файл mciwaver\mciwave.hpp #define CM_HELPABOUT 301 #define CM_FILEEXIT 302 #define CM_FILEOPEN 303 #define CM_FILESAVEAS 304 #define CM_FILENEW 305 #define CM_CTLPLAY 401 #define CM_CTLRECORD 402 #define CM_CTLRESUME 403 #define CM_CTLPAUSE 404 #define CM_CTLSTOP 405
Файл mciwaveio.cpp (листинг 2.9) содержит функции для работы с интерфейсом сообщений MCI.
Листинг 2.9. Файл mciwaver\mciwavio.cpp #define STRICT #include <windows.h> #include <windowsx.h> #include <commdlg.h> #include <mmsystem.h> #include <mem.h> #pragma hdrstop #include "mciwavio.hpp" // Глобальные переменные extern int nMode; extern int nPosition; extern DWORD dwFileSize; //----------------------------------------------------- // mciwioOpen // Открытие устройства вывода //----------------------------------------------------- UINT mciwioOpen(LPSTR szFileName) { MCI_OPEN_PARMS mciOpen; MCI_STATUS_PARMS mciStatus; DWORD dwrc; DWORD dwFlags; // Готовим блок параметров mciOpen.lpstrDeviceType= (LPSTR)"waveaudio"; mciOpen.lpstrElementName = (LPSTR)szFileName; mciOpen.dwCallback = 0; mciOpen.wDeviceID = 0; mciOpen.wReserved0 = 0; mciOpen.lpstrAlias = NULL; // Устанавливаем флаги dwFlags = MCI_OPEN_TYPE | MCI_OPEN_ELEMENT | MCI_WAIT; // Открываем устройство dwrc = mciSendCommand(0, MCI_OPEN, dwFlags, (DWORD)(LPVOID)&mciOpen); if(dwrc) { mciwioError(dwrc); return 0; } // Если устройство открыто успешно, определяем // длительность звучания в миллисекундах else { mciStatus.dwItem = MCI_STATUS_LENGTH; dwrc = mciSendCommand(mciOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD)(LPVOID)&mciStatus); if(dwrc) { mciwioError(dwrc); return 0; } // Сохраняем длительность звучания в глобальной // переменной и возвращаем идентификатор устройства вывода dwFileSize = mciStatus.dwReturn; return mciOpen.wDeviceID; } } //----------------------------------------------------- // mciwioPlay // Проигрывание загруженного wav-файла //----------------------------------------------------- DWORD mciwioPlay(HWND hwnd, UINT wDeviceID) { MCI_PLAY_PARMS mciPlayParms; DWORD dwrc; // Позиционирование на начало фрагмента dwrc = mciSendCommand(wDeviceID, MCI_SEEK, MCI_WAIT | MCI_SEEK_TO_START, NULL); // Идентификатор окна, функция которого получит // сообщение MM_MCINOTIFY mciPlayParms.dwCallback = (DWORD)hwnd; // Запускаем проигрывание dwrc=mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)(LPVOID)&mciPlayParms); return dwrc; } //----------------------------------------------------- // mciwioStop // Останов проигрывания загруженного wav-файла //----------------------------------------------------- DWORD mciwioStop(UINT wDeviceID) { MCI_GENERIC_PARMS mcigen; DWORD dwrc; dwrc = mciSendCommand(wDeviceID, MCI_STOP, MCI_WAIT, (DWORD)(LPMCI_GENERIC_PARMS)&mcigen); if(dwrc) { mciwioError(dwrc); } return dwrc; } //----------------------------------------------------- // mciwioResume // Проигрывание после временного останова //----------------------------------------------------- DWORD mciwioResume(UINT wDeviceID) { MCI_GENERIC_PARMS mcigen; DWORD dwrc; dwrc = mciSendCommand(wDeviceID, MCI_RESUME, MCI_WAIT, (DWORD)(LPMCI_GENERIC_PARMS)&mcigen); if(dwrc) { mciwioError(dwrc); } return dwrc; } //----------------------------------------------------- // mciwioPause // Временный останов проигрывания загруженного wav-файла //----------------------------------------------------- DWORD mciwioPause(UINT wDeviceID) { MCI_GENERIC_PARMS mcigen; DWORD dwrc; dwrc = mciSendCommand(wDeviceID, MCI_PAUSE, MCI_WAIT, (DWORD)(LPMCI_GENERIC_PARMS)&mcigen); if(dwrc) { mciwioError(dwrc); } return dwrc; } //----------------------------------------------------- // mciwioSelectFile // Выбор wav-файла //----------------------------------------------------- BOOL mciwioSelectFile(LPSTR lpszFileName) { OPENFILENAME ofn; char szFile[256]; char szFileTitle[256]; char szFilter[256] = "Wave Files\0*.wav\0Any Files\0*.*\0"; szFile[0] = '\0'; memset(&ofn, 0, sizeof(OPENFILENAME)); // Инициализируем нужные нам поля ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = NULL; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; // Выбираем входной файл if (GetOpenFileName(&ofn)) { // Копируем путь к выбранному файлу lstrcpy(lpszFileName, (LPSTR)szFile); return TRUE; } else return FALSE; } //----------------------------------------------------- // mciwioClose // Закрытие устройства вывода //----------------------------------------------------- void mciwioClose(UINT wDeviceID) { MCI_GENERIC_PARMS mcigen; DWORD dwrc; dwrc = mciSendCommand(wDeviceID, MCI_CLOSE, MCI_WAIT, (DWORD)(LPMCI_GENERIC_PARMS)&mcigen); if(dwrc) { mciwioError(dwrc); return; } } //----------------------------------------------------- // mciwioError // Обработка ошибок //----------------------------------------------------- void mciwioError(DWORD dwrc) { BYTE szBuf[MAXERRORLENGTH]; if(mciGetErrorString(dwrc, (LPSTR)szBuf, MAXERRORLENGTH)) MessageBox(NULL, szBuf, "MCIWAVER Error", MB_ICONEXCLAMATION); else MessageBox(NULL, "Неизвестная ошибка", "MCIWAVER Error", MB_ICONEXCLAMATION); } //----------------------------------------------------- // mciwioRecord // Запись wav-файла //----------------------------------------------------- WORD mciwioRecord(HWND hwnd, DWORD dwMSec) { MCI_RECORD_PARMS mciRecordParms; MCI_OPEN_PARMS mciOpen; DWORD dwrc; DWORD dwFlags; WORD wInDeviceID; // Готовим блок параметров // для команды открытия устройства mciOpen.lpstrDeviceType = (LPSTR)"waveaudio"; mciOpen.lpstrElementName = (LPSTR)""; mciOpen.dwCallback = 0; mciOpen.wDeviceID = 0; mciOpen.wReserved0 = 0; mciOpen.lpstrAlias = NULL; // Устанавливаем флаги dwFlags = MCI_OPEN_TYPE| MCI_OPEN_ELEMENT | MCI_WAIT; // Открываем устройство ввода dwrc = mciSendCommand(0, MCI_OPEN, dwFlags, (DWORD)(LPVOID)&mciOpen); if(dwrc) { mciwioError(dwrc); return 0; } else { // В случае успеха сохраняем идентификатор // устройства ввода в глобальной переменной wInDeviceID = mciOpen.wDeviceID; } // Готовим блок параметров для команды записи mciRecordParms.dwTo = dwMSec; mciRecordParms.dwCallback = (DWORD)hwnd; // Запускаем запись dwrc=mciSendCommand(wInDeviceID, MCI_RECORD, MCI_NOTIFY | MCI_TO, (DWORD)(LPVOID)&mciRecordParms); if(dwrc) { mciwioError(dwrc); return 0; } return wInDeviceID; }
Функция mciwioOpen открывает устройство вывода, посылая ему сообщение MCI_OPEN. В блоке параметров мы указываем тип устройства ("waveaudio") и путь к wav-файлу szFileName. Так как тип устройства указан в виде текстовой строки, мы используем флаг MCI_OPEN_TYPE. Необходимо также указать флаг MCI_OPEN_ELEMENT, так как устройство будет работать с файлом. Для того чтобы функция вернула управление только после открытия устройства, используется флаг MCI_WAIT.
Если при открытии устройства произошла ошибка, функция mciSendCommand вернет ненулевой код ошибки. Мы передаем этот код функции mciwioError, которая выводит текстовое описание ошибки на экран.
После успешного открытия устройства вывода определяется размер файла (длительность звучания файла в миллисекундах). Для этого используется сообщение с кодом MCI_STATUS. Полученный размер сохраняется в глобальной переменной dwFileSize. Он будет использован для вычисления положения движка полосы просмотра, соответствующего текущей позиции при воспроизведении.
Функция mciwioPlay предназначена для проигрывания загруженного файла с самого начала. Перед запуском воспроизведения текущая позиция устанавливается на начало, для чего используется сообщение MCI_SEEK с флагом MCI_SEEK_TO_START: dwrc = mciSendCommand(wDeviceID, MCI_SEEK, MCI_WAIT | MCI_SEEK_TO_START, NULL);
Далее готовится блок параметров для сообщения MCI_PLAY. В поле dwCallback записывается идентификатор главного окна приложения (из глобальной переменной hwnd). Это окно получит извещение о завершении проигрывания в виде сообщения MM_MCINOTIFY.
Функция mciwioStop посылает устройству, идентификатор которого передается ей через параметр wDeviceID, сообщение MCI_STOP.
Функция mciwioPause предназначена для временного останова проигрывания wav-файла. Она посылает устройству, идентификатор которого передается ей через параметр wDeviceID, сообщение MCI_PAUSE.
С помощью функции mciwioResume можно возобновить прерванный функцией mciwioPause процесс записи или воспроизведения. Эта функция посылает устройству сообщение MCI_RESUME.
Функция mciwioSelectFile предназначена для выбора wav-файла. Она пользуется стандартной диалоговой панелью выбора файла и функцией GetOpenFileName, определенной в DLL-библиотеке commdlg.dll. Мы уже пользовались этой функцией в предыдущих томах "Библиотеки системного программиста". Путь к выбранному файлу копируется в буфер, адрес которого передается функции mciwioSelectFile в качестве единственного параметра.
Функция mciwioClose закрывает устройство ввода или вывода, посылая ему сообщение MCI_CLOSE.
Запись файла инициируется функцией mciwioRecord. В качестве первого параметра этой функции передается идентификатор окна, которое получит сообщение MM_MCINOTIFY после завершения процесса записи. Второй параметр определяет максимальную длительность записи в миллисекундах.
Перед началом записи открывается устройство ввода "waveaudio", причем в качестве имени файла используется пустая строка: mciOpen.lpstrDeviceType = (LPSTR)"waveaudio"; mciOpen.lpstrElementName = (LPSTR)""; mciOpen.dwCallback = 0; mciOpen.wDeviceID = 0; mciOpen.wReserved0 = 0; mciOpen.lpstrAlias = NULL; dwFlags = MCI_OPEN_TYPE | MCI_OPEN_ELEMENT | MCI_WAIT; dwrc = mciSendCommand(0, MCI_OPEN, dwFlags, (DWORD)(LPVOID)&mciOpen); if(dwrc) { mciwioError(dwrc); return 0; } else { wInDeviceID = mciOpen.wDeviceID; }
Далее готовится блок параметров для записи и посылается сообщение MCI_RECORD: mciRecordParms.dwTo = dwMSec; mciRecordParms.dwCallback = (DWORD)hwnd; dwrc=mciSendCommand(wInDeviceID, MCI_RECORD, MCI_NOTIFY | MCI_TO, (DWORD)(LPVOID)&mciRecordParms);
При этом указывается конечная позиция для записи dwTo и идентификатор окна, которое получит извещение о завершении процесса записи dwCallback. Для того чтобы перечисленные параметры были приняты во внимание, устанавливаются флаги MCI_TO и MCI_NOTIFY.
Константы и прототипы функций для файла mciwavio.cpp находятся в файле mciwaveio.hpp (листинг 2.10).
Листинг 2.10. Файл mciwaver\mciwavio.hpp #include <windows.h> #include <mmsystem.h> #define MODE_STOP 0 #define MODE_PLAYING 1 #define MODE_RECORDING 2 #define MODE_PLAYINGPAUSED 3 #define MODE_RECORDINGPAUSED 4 UINT mciwioOpen(LPSTR szFileName); BOOL mciwioSelectFile(LPSTR lpszFileName); void mciwioClose(UINT wDeviceID); DWORD mciwioPlay(HWND hwnd, UINT wDeviceID); void mciwioError(DWORD dwrc); DWORD mciwioStop(UINT wDeviceID); DWORD mciwioPause(UINT wDeviceID); DWORD mciwioResume(UINT wDeviceID); WORD mciwioRecord(HWND hwnd, DWORD dwMSec);
Файл описания ресурсов (листинг 2.11) содержит определение пиктограммы и шаблон меню.
Листинг 2.11. Файл mciwaver\mciwaver.rc #include "mciwave.hpp" APPICON ICON "mciwaver.ico" APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "&Open...", CM_FILEOPEN MENUITEM SEPARATOR MENUITEM "E&xit", CM_FILEEXIT END MENUITEM "&Play!", CM_CTLPLAY MENUITEM "&Stop!", CM_CTLSTOP MENUITEM "Resu&me!", CM_CTLRESUME MENUITEM "P&ause!", CM_CTLPAUSE MENUITEM "&Record!", CM_CTLRECORD POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END
Файл определения модуля для приложения MCIWAVER приведен в листинге 2.12.
Листинг 2.12. Файл mciwaver\mciwaver.def NAME MCIWAVER DESCRIPTION 'Приложение MCIWAVER, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 10240 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Первое двойное слово заголовка содержит четырехбуквенный код FOURCC, который идентифицирует данные, хранящиеся во фрагменте. Второе двойное слово заголовка - размер области данных в байтах (без учета размера самого заголовка).
Область данных имеет переменную длину, однако она должна быть выравнена на границу слова и при необходимости дополнена в конце нулевым байтом до целого числа слов.
Заметим, что формат RIFF не описывает формат данных. Практически файл в формате RIFF может содержать любые данные для мультимедиа, причем формат данных зависит от типа данных.
Область, обозначенная на рис. 2.3 как "Данные", может содержать внутри себя другие фрагменты. Для файла, в котором хранятся звуковые данные (wav-файл), эта область содержит идентификатор данных "WAVE", фрагмент формата звуковых данных "fmt " (три символа "fmt" и пробел на конце), а также фрагмент звуковых данных (рис. 2.4). Файл может дополнительно содержать фрагменты других типов, поэтому не следует думать, что заголовок wav-файла имеет фиксированный формат. Например, в файле может присутствовать фрагмент "LIST" или "INFO", содержащий информацию о правах копирования и другую дополнительную информацию. Из-за ограниченного объема книги мы не будем рассматривать форматы других фрагментов, при необходимости вы можете узнать их из документации, которая поставляется в составе Microsoft SDK for Windows 3.1.
Область, обозначенная на рис. 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.
Это приложение по своему внешнему виду и функциям аналогично приложению 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
Сохранение записанного звукового фрагмента в файле save device_id [filename] [notify] [wait]
Параметр filename задает путь к файлу, в который должен быть записан звуковой фрагмент
Функции waveInReset и waveOutReset выполняют, соответственно, останов устройства ввода или вывода и сброс текущей позиции для устройства в 0. Функция waveInReset UINT waveInReset( HWAVEIN hWaveIn); // идентификатор устройства ввода
Параметры функции: hWaveIn
Идентификатор устройства ввода, полученный от функции waveInOpen при открытии устройства
Возвращаемое значение:
При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки: MMSYSERR_INVALHANDLE
Указан неправильный идентификатор устройства Функция waveOutReset UINT waveOutReset ( HWAVEOUT hWaveOut); // идентификатор устройства вывода
Параметры функции: hWaveOut
Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства
Возвращаемое значение:
При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки: MMSYSERR_INVALHANDLE
Указан неправильный идентификатор устройства
Позиционирование с последующим остановом. Перед использованием этой команды необходимо задать формат времени командой set time format. seek device_id parameter [notify] [wait]
В качестве необязательного параметра parameter можно указывать одну из следующих строк: to position
Позиционирование в указанное место фрагмента to start
Позиционирование в начало to end
Позиционирование в конец
Команда set в зависимости от параметров позволяет выполнять установку различных режимов работы. set device_id parameter [notify] [wait]
В качестве параметра parameter можно указывать одну из следующих строк (за один раз можно указывать сразу несколько параметров): alignment int
Установка выравнивания блока данных относительно начала данных звукового фрагмента, переданного драйверу звукового адаптера any input
Использование любого устройства ввода, поддерживающего текущий формат при записи. Этот режим включен по умолчанию any output
Использование любого устройства вывода, поддерживающего текущий формат при воспроизведении. Этот режим включен по умолчанию audio all off
Отключение звукового выхода audio all on
Включение звукового выхода audio left off
Отключение левого канала audio left on
Включение левого канала audio right off
Отключение правого канала audio right on
Включение правого канала bitspersample bit_count
Установка количества бит для представления выборки сигнала. Параметр bit_count задает количество бит (8 или 16) bytespersec byte_rate
Установка частоты дискретизации при записи или воспроизведении. Параметр byte_rate задает частоту (байты в секунду) channels channel_count
Установка количества каналов для записи или воспроизведения (1 - монофонический режим, 2 - стереофонический режим) format tag tag
Установка типа формата format tag pcm
Установка формата PCM (импульсно-кодовая модуляция) input int
Выбор канала для ввода output int
Выбор канала для вывода samplepersec int
Установка скорости записи или воспроизведения time format bytes
В качестве единицы измерения при позиционировании используются байты блока звуковых данных time format milliseconds
В качестве единицы измерения при позиционировании используются миллисекунды. Строку milliseconds можно также указывать как ms time format samples
В качестве единицы измерения при позиционировании используются выборки сигнала
Немного о сообщении MM_MCINOTIFY .
Как мы уже говорили, приложение может передать функции mciSendString через последний параметр идентификатор окна. Если команда MCI выдана с параметром notify, после ее завершения функция окна получит сообщение MM_MCINOTIFY. Это сообщение - извещение о завершении (удачном или нет) процесса выполнения команды.
Через параметр wParam сообщения MM_MCINOTIFY функция окна получает код извещения, по которому можно судить о результатах выполнения команды. Возможны следующие значения (описанные в файле mmsystem.h):
Значение | Описание |
MCI_NOTIFY_ABORTED | Устройство получило такую команду, в результате которой не будет получено извещение о завершении выполнения предыдущей команды. Если новая команда прерывает выполнение текущей команды и также требует извещения, функция окна получит сообщение MCI_NOTIFY_ABORTED (но не MCI_NOTIFY_SUPERSEDED) |
MCI_NOTIFY_SUCCESSFUL | Успешное завершение команды |
MCI_NOTIFY_SUPERSEDED | Устройство получило еще одну команду, так же требующую извещения, в результате чего извещение от первой команды не будет получено |
MCI_NOTIFY_FAILURE | В устройстве произошла ошибка во время выполнения команды |
Параметр lParam содержит идентификатор устройства, приславшего извещение.
В случае успешного завершения обработчик сообщения MM_MCINOTIFY должен вернуть нулевое значение, при ошибке - соответствующий код ошибки MCI.
Далее мы перейдем к изучению отдельных групп команд, предназначенных для работы со звуковым адаптером.
Эта группа команд предназначена для получения различной справочной информации об устройстве. Перед использованием справочных команд следует открыть устройство командой open.
Команда status позволяет определить текущее состояние устройства. status device_id parameter [notify] [wait]
В качестве параметра parameter можно указывать одну из следующих строк: alignment
Выравнивание блока данных в байтах bitspersample
Количество байт на одну выборку сигнала bytespersec
Скорость проигрывания или записи, байт в секунду channels
Количество каналов, 1 - моно, 2 - стерео current track
Номер текущей дорожки. Для звукового адаптера всегда равно 1 format tag
Тег формата input
Устройство ввода length
Общая длина звукового фрагмента length track track_number
Длина фрагмента, соответствующая заданной дорожке level
Текущий уровень звукового сигнала media present
Признак присутствия носителя (среды). Для звукового адаптера всегда равно true mode
Текущий режим работы: not ready (не готов), playing (проигрывание), stopped (останов), recording (запись), seeking (позиционирование) number of tracks
Количество дорожек. Для звукового адаптера всегда равно 1 output
Устройство вывода position
Текущая позиция position track track_number
Текущая позиция на заданной дорожке. Для звукового адаптера всегда равно 0 ready
Если устройство готово, возвращается строка true samplespersec
Количество выборок сигнала в секунду при проигрывании или записи (частота дискретизации) start position
Начальная позиция time format
Текущий формат времени
Команда sysinfo предназначена для получения системной информации об MCI-устройстве: sysinfo device_id parameter [notify] [wait]
В качестве параметра parameter можно указывать одну из следующих строк: installname
Имя, использованное в файле system.ini при установке драйвера устройства quantity
Количество MCI-устройств типа device_id, установленных в системе и указанных в файле system.ini. Если в качестве device_id указать строку all, будет подсчитано общее количество установленных в системе MCI-драйверов quantity open
Количество открытых MCI-устройств типа device_id, установленных в системе и указанных в файле system.ini name index
Имя устройства MCI, номер которого задан строкой index. Первому устройству соответствует строка 1 name index open
Имя открытого устройства MCI, номер которого задан строкой index
Когда мы рассказывали об использовании функции 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
Ваше приложение может управлять громкостью сигнала при его воспроизведении. Для установки громкости следует использовать функцию 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
В системе нет нужного драйвера
Для воспроизведения звуковых данных на низком уровне после определения возможностей устройства вывода необходимо открыть устройство. Это можно сделать с помощью функции 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
Очередь данного устройства еще содержит блоки для проигрывания
Если приложение открыло файл функцией 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 при возникновении ошибки
Процесс записи похож на процесс воспроизведения.
Вначале необходимо открыть устройство записи, вызвав функцию 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
Очередь данного устройства еще содержит блоки для записи
Для запуска устройства ввода используется рассмотренная нами ранее функция waveInStart . Если же нужно продолжить работу приостановленного устройства вывода, следует вызвать функцию waveOutRestart : Функция waveOutRestart UINT waveOutRestart( HWAVEOUT hWaveOut); // идентификатор устройства вывода
Параметры функции: hWaveOut
Идентификатор устройства вывода, полученный от функции waveOutOpen при открытии устройства
Возвращаемое значение:
При нормальном завершении возвращается нулевое значение. В противном случае возвращается код ошибки: MMSYSERR_INVALHANDLE
Указан неправильный идентификатор устройства