Интерфейс управления устройствами MCI (Media Control Interface)

Источник: cyberguru
форум visual c++

Интерфейс управления устройствами MCI (Media Control Interface) позволяет программам для Windows работать с различными устройствами мультимедиа которые включают в себя CD-ROM, звуковую карту, проигрыватель видеодисков, даже видеомагнитофон и т.д. При помощи функций MCI мы можем управлять выше перечисленными устройствами, посылая им специальные команды такие как: воспроизведение, остановка, воспроизведение с заданной позиции и т.д.

Конкретный набор команд соответствует каждому устройству свой. Эту команду мы передаем в функцию интерфейса MCI.

Все функции интерфейса MCI начинаются с префикса mci и делятся на три группы:

  • интерфейс команд сообщений:
    mciSendCommand()
    mciGetDeviceID()
  • интерфейс команд строк:
    mciSendString()
  • оба типа интерфейса:
    mciGetErrorString()
    mciSetYieldProc()

Две функции высокого уровня - mciSendCommand() и mciSendString() выполняют одну и туже функцию. Отличие между ними заключается в способе подачи команд.

Первую функцию, которую мы рассмотрим будет mciSendCommand(). Она описывается следующим образом:

DWORD mciSendCommand(MCIDEVICEID IDDevice,
UINT uMsg,
DWORD fdwCommand,
DWORD dwParam)

где:

  • IDDevice - идентификатор MCI-устройства, которому адресуется команда. При открытии устройства этот параметр не используется, т.е. NULL.
  • uMsg - команда
  • fdwCommand - флаги для команды
  • dwParam - указатель на структуру с параметрами для командного сообщения

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

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

Что же представляет из себя функция mciSendString() ?
Вот ее описание:

DWORD mciSendString(LPTSTR lpszCommand,
LPTSTR lpszReturnString,
UINT cchReturn,
HANDLE hwndCallback)

где:

  • lpszCommand - указывает на завершающуюся нулем строку с командой в следующей форме:
    [команда][устройство][параметры]
  • lpszReturnString - указывает на буфер для получения информации о результате. Если такая информация не нужна, то этот параметр устанавливается в NULL
  • cchReturn - этот параметр указывает размер в символах определенного предыдущим параметром буфера. Если он не нужен, то устанавливается в 0
  • hwndCallback - указывает на окно отклика "возврата", если в командной строке указан параметр 'notify'. Если 'notify' не указан, то этот параметр устанавливается в NULL.

Итак, функция возвращает 0 в случае удачи и, соответственно, код ошибки в противном лучае. С помощью кода ошибки можно получить дополнительную информацию. Это относится и к mciSendCommand()! Не путайте это с параметром lpszReturnString, он просто хранит информацию о том, какую работу выполняет данная функция.

Вот маленький примерчик: воспроизведение wave-файла.

#include "stdafx.h"
#include <mmsystem.h>

void main()
{
DWORD result = mciSendString("play c:\\windows\\media\\tada.wav wait", NULL, 0, NULL);
if (result)
printf("Ошибка");
}

Необходимо добавить библиотеку winmm.lib !!! Иначе "пролетите"!

Обработка ошибок

Как уже было сказано, функции MCI возвращают код ошибки при неудачном их выполнении. И тутже возникает вопрос: "а куда, собственно, девать этот код?". Здесь нам на помощь приходит другая функция - mciGetErrorString(), которая по коду ошибки выводит ее текстовое описание. Вот как она выглядит:

BOOL mciGetErrorString(DWORD fdwError,
LPTSTR lpszErrorText,
UINT cchErrorText)

где:

  • fdwError - код ошибки, полученный от одной из функций
  • lpszErrorText - указатель на буфер, который будет хранить описание ошибки
  • cchErrorText - указывает размер lpszErrorText буфера в символах

Как и большинство функций, она тоже возвращает результат своей деятельности. Если она вернула TRUE, то можно расслабиться и посмотреть, что же натворила функция находящаяся перед ней. Иначе (FALSE) придется поднопрячь извилины, потому что наша функция не смогла определить причину возникновения ошибки.

Давайте рассмотрим конкретный пример, где все встанет на свои места.

#include "stdafx.h"
#include <mmsystem.h>

void main()
{
const int cchErrorText = 128;
char lpszErrorText[cchErrorText];
DWORD fdwError = mciSendString("play c:\\windows\\tada.wav wait", NULL, 0, NULL);
if (fdwError)
{
BOOL dummy = mciGetErrorString(fdwError, lpszErrorText, cchErrorText);
if (dummy)
printf("%s\n", lpszErrorText);
else
printf("напряги извилины :-)");
}
}

Я специально указал неверный путь до файла, чтобы сработала наша функция. Все очень просто...правда?

 Рассмотрим некоторые возможности функции mciSendString() в работе с устройствами. Для примера возьмем CD-ROM и опять вернемся к команде play. Как раньше я говорил, командную строку можно разделить на три под команды: сама команда, устройство, которое будем использовать и параметры. Т.к. мы используем команду play, то ей характерны, только для CD-ROM'а, два параметра: from position и to position. Под position подразумевается передаваемое значение, в данном случае, для нашего устройства, это будут минуты.секунды.

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

Итак, рассмотрим пример. Допустим трек будет проигрываться 7 минут 22 секунды, как увертюра "The Magic Flute" у Моцарта. А нам надо, что бы проигрывался его кусочек: скажем с 4 минут до 5 минут.

#include "stdafx.h"
#include <mmsystem.h>

void main()
{
char lpstrCommand[256];

// Задаем интервал времени
char lpszPlayFlags[] = "from 4 to 5";
// Компонуем командную строку, где cdaudio - идентификатор устройства
wsprintf(lpstrCommand, "play %s %s %s", "cdaudio", lpszPlayFlags, "wait");
// Без комментариев
mciSendString(lpstrCommand, NULL, 0, NULL);
}

Следует немного поэкспериментировать, чтобы понять "принцип работы" этих параметров.

digitalvideo

Продолжаем использовать команду play ! На этот раз мы будем работать с видео. Все, наверное, знают такую программу, как универсальный проигрыватель. Вы можете посмотреть сколько форматов она может воспроизводить, это впечатляет, а также впечатляет то, что все эти форматы реализованы в самой ОС, и Вы можете этим воспользоваться! Как уже вы догадались речь пойдет о digitalvideo - цифровое видео.

Вот список реализованных параметров:

  • from position - определяет стартовую позицию для воспроизведения. Дополнительная информация находится в шаге 3.
  • to position - соответственно конец воспроизведения.
  • fullscreen - задает полноэкранный режим воспроизведения.
  • reverse - определяет время, которое осталось до конца воспроизведения.
  • repeat - по завершению воспроизведения файл переигрывается. Не советую устанавливать этот параметр, если Вы не разобрались с так называемой функцией возврата (Callback)!
  • window - этот параметр говорит сам за себя. Он устанавливается по умолчанию и, соответственно, воспроизведение видео будет происходить в окне, соответствующему размеру изображения.

Код программы я приводить не буду, т.к. за основу Вы можете взять программку из 3 шага, где за место устройства cdaudio Вы должны указать путь до видеофайла (естественно в кавычках!), и поэкспериментировать. Дополнительно хочу сказать, что dat-файлы эта программка тоже проигрывает.

mciSendCommand() и CD-ROM

Помимо функции mciSendString(), мы упоминали функцию mciSendCommand(). И сейчас ею конкретно займемся. Этот проект будет достаточно насыщенный, так что держитесь. Устройство выберем самое распространенное - CD-ROM.

Первым делом, прежде чем воспользоваться устройством, нам надо его открыть (это логично), т.е. узнать его ID. Нам понадобится для этого структура MCI_OPEN_PARMS:

typedef struct {
DWORD dwCallback;
MCIDEVICEID wDeviceID;
LPCTSTR lpstrDeviceType;
LPCTSTR lpstrElementName;
LPCTSTR lpstrAlias;
} MCI_OPEN_PARMS;

где:

  • dwCallback - для функции "возврата"
  • wDeviceID - возвращаемый идентификатор устройства
  • lpstrDeviceType - указатель на имя устройства
  • lpstrElementName - обычно путь до файла
  • lpstrAlias - указатель на буфер, содержащий alias устройство

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

typedef struct {
DWORD dwCallback;
DWORD dwReturn;
DWORD dwItem;
DWORD dwTrack;
} MCI_STATUS_PARMS;

где:

  • dwCallback - смотрите выше
  • dwReturn - возвращаемая информация
  • dwItem - опрашиваемое свойство
  • dwTrack - параметр трека

Эта структура используется довольно часто. Скоро Вы в этом убедитесь.

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

Начнем с первого пункта. Открываем наше устройство:

 // Объявляем структуру 
MCI_OPEN_PARMS mciOpenParms;

// Присваиваем значение типа устройства
mciOpenParms.lpstrDeviceType = "cdaudio";

// Устанавливаем флаги, которые укажут интерфейсу, как нужно
// интерпретировать информацию в структуре mciOpenParms.
// В данном случае мы сообщаем MCI о том, что передаем ему тип
// устройства.
DWORD flags = MCI_OPEN_TYPE;

// Давайте рассмотрим, как эта функция будет использовать ту
// информацию, которую мы заложили. Начнем с первого параметра - 0.
// Так как мы не знаем еще идентификатор нашего устройства, поэтому
// просто устанавливаем его в 0. Второй параметр говорит MCI, что мы
// хотим открыть устройство, имя которого она извлечет из структуры
// mciOpenParms. По поводу третьего параметра смотрите выше. И,
// наконец, последним параметром нашей функции будет указатель на
// структуру из которой она извлечет необходимые ей данные. В данном
// случае, как я уже говорил, имя устройства.

DWORD resume = mciSendCommand(0, MCI_OPEN, flags,
(DWORD) (LPVOID)& mciOpenParms);

// Если все было в порядке, получаем идентификатор устройства
if (resume == 0)
wDeviceID = mciOpenParms.wDeviceID;

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

 // Т.к. мы опрашиваем устройство, то заполняем соответствующий
// элемент структуры MCI_STATUS_PARMS - dwItem. Флаг
// MCI_STATUS_READY как раз и определяет устройство на готовность.
mciStatusParms.dwItem = MCI_STATUS_READY;

// В качестве первого параметра передаем идентификатор устройства.
// Второй параметр - это команда, которая связана со структурой
// MCI_STATUS_PARMS и которая извлекает информацию об устройстве.
// Третий параметр, даже не знаю как сказать...подтверждает то,
// что информация, которой мы заполнили структуру, относиться
// только к свойствам устройства. Ну и последним параметром мы
// передаем указатель на структуру MCI_STATUS_PARMS.
mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM,
(DWORD)& mciStatusParms);

Если устройство у нас готово, то переходим к третьему пункту. Узнаем количество треков. Код я приводить не буду, т.к. далее все идет по аналогии. Опять мы хотим получить интересующую нас информацию. По этому будем продолжать использовать структуру MCI_STATUS_PARMS, только команда у нас поменяется и станет MCI_STATUS_NUMBER_OF_TRACKS, которую мы отправим в dwItem.

Теперь давайте определим длину каждого трека. Как я уже говорил это нам нужно, что бы узнать время проигрывания трека. Для того, что бы определить длину ,к примеру n-ого трека, нам следует обратиться по номеру этого трека. Как Вы уже догадались этот номер мы поместим в dwTrack структуры MCI_STATUS_PARMS, а команду определения длинны трека -MCI_STATUS_LENGTH в dwItem. Здесь мы уже начали работать непосредственно с треками, и я прошу акцентировать ваше внимание на третьем пераметре функции mciSendCommand(). В этом случае необходимо к флагу MCI_STATUS_ITEM добавить еще и флаг MCI_TRACK. Это очень важно.

Проверим информацию о типе трека. Записываем номер трека в dwTrack, а в dwItam команду проверки трека - MCI_CDA_STATUS_TYPE_TRACK. После извлекаем из структуры результат, который храниться в dwReturn и сравниваем с MCI_CDA_TRACK_AUDIO. Если значения равны, то трек к которому мы обратились несет аудио информацию.

И последнее, что нам осталось - это определить время проигрывания треков. Для этого воспользуемся функциями MCI_MSF_MINUTE() и MCI_MSF_SECOND(). Передаваемым значением является длинна трека.

"Исполнение желаний" команда MCI_SET

Продолжаем мучать наш CD-ROM. Теперь он будет непосредственно выполнять наши команды. Команды эти самые различные такие как пауза, проигрывание, остановка, открытие и закрытие, запись и переход на другой трек. А начнем мы естественно с самого примитивного - открытия и закрытия. Для установки управления устройства используется команда MCI_SET с принадлежащей ей структурой MCI_SET_PARMS. Эта команда сообщает MCI, что устройство должно выполнить какое-то действие, а что оно должно выполнить сообщается третьему параметру функции. В приведенном примере выполняется команда открытия CD, а чтобы его закрыть нужно поменять MCI_SET_DOOR_OPEN на MCI_SET_DOOR_CLOSED.

Пример:

mciSendCommand(wDeviceID, MCI_SET, MCI_SET_DOOR_OPEN,
(DWORD)& mciSetParms);

Попробуйте теперь это совместить с предыдущим проектом (CDPlayer).

MCI Play&Stop

Рассмотрим пример воспроизведения CD-треков.

mciSetParms.dwTimeFormat = MCI_FORMAT_TMSF; 
flags = MCI_SET_TIME_FORMAT;
mciSendCommand(wDeviceID, MCI_SET, flags, (DWORD)& mciSetParms);

Первым делом устанавливаем формат времени. Используем уже знакомую структуру - MCI_SET_PARMS, в которой устанавливаем параметр MCI_FORMAT_TMSF. Он говорит о том, что формат времени устанавливается в треках, минутах, секундах и фреймах. В качестве флага передаем флаг параметра времени - MCI_SET_TIME_FORMAT. Ну, а дальше запускаем нашу функцию.

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

typedef struct { 
DWORD dwCallback;
DWORD dwFrom;
DWORDdwTo;
} MCI_PLAY_PARMS;

Где

  • dwCallback - указатель на функцию "возврата"
  • dwFrom - позиция начала воспроизведения
  • dwTo - позиция конца воспроизведения

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

 // Определяем длину трека через уже "готовую" 
//функцию, где track - это номер трека
DWORD lengthTrack = LengthTrack(track);

// С помощью функции MCI_MAKE_TMSF зададим начало и конец
// воспроизведения воспользовавшись структурой MCI_PLAY_PARMS.
mciPlayParms.dwFrom = MCI_MAKE_TMSF(track, 0, 0, 0);
mciPlayParms.dwTo = MCI_MAKE_TMSF(track,
MCI_MSF_MINUTE(lengthTrack),
MCI_MSF_SECOND(lengthTrack),
MCI_MSF_FRAME(lengthTrack));

// Зададим нужные флаги
flags = MCI_FROM / MCI_TO;

// Начнем воспроизведение
mciSendCommand(wDeviceID, MCI_PLAY, flags, (DWORD)& mciPlayParms);

При использовании флагов MCI_FROM и MCI_TO необходимо устанавливать формат времени, как мы это сделали в начале этого упражнения.

Теперь пора рассмотреть другую команду - MCI_STOP (комментариев я думаю не надо). Действуем по аналогии с командами открытия и закрытия CD.

mciSendCommand(wDeviceID, MCI_STOP, 0, (DWORD)& mciGenericParms);

Это все, что нужно сделать.

Проигрывание WAV файла из памяти

> Hello ig,
> Помогите разобраться с таким вопросом
> необходимо проиграть фрагмент из .wav файла
> я прочитал его в буфер
> char pbuf[100];
> UINT nBytesRead = cfile.Read( pbuf, 100 );
> как я могу проиграть его с помощью PlaySound
>
> --

Увы, вынужден разочаровать, в таком виде ничего не получится. В принципе, PlaySound действительно может проигрывать wav-ы из памяти, но при этом подразумевается, что изображение звука в памяти имеет правильный формат (т.е. снабжено заголовком с указанием формата, затем следует собственно блок данных и т.д.). То-есть если ты прочитаешь wav-файл в память ЦЕЛИКОМ, то он честно проиграется командой:

PlaySound( pbuf, NULL, SND_MEMORY );

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

Одно из решений - использование MCI функций. В качестве примера приведу работающий кусок из программы (я его несколько упростил для примера):

MCIERROR mciError;
MCI_OPEN_PARMS mciOpenParms;

mciOpenParms.lpstrDeviceType = "waveaudio";
mciOpenParms.lpstrElementName = "sound38.wav";
if (mciError = mciSendCommand(0, MCI_OPEN,
MCI_OPEN_TYPE / MCI_OPEN_ELEMENT,
(DWORD)(LPMCI_OPEN_PARMS)&mciOpenParms))
{
AfxMessageBox("Can't open");
} else {
MCIDEVICEID mciDeviceID;
mciDeviceID = mciOpenParms.wDeviceID;
MCI_PLAY_PARMS mciPlayParms;
mciPlayParms.dwFrom = 300;
mciPlayParms.dwTo = 600;
if (mciError = mciSendCommand(mciDeviceID, MCI_PLAY,
MCI_FROM / MCI_TO / MCI_WAIT, (DWORD)(LPMCI_PLAY_PARMS)&mciPlayParms))
{
AfxMessageBox("Can't play");
}
if (mciError = mciSendCommand(mciDeviceID, MCI_CLOSE, NULL, NULL))
{
AfxMessageBox("Can't close");
}
}

Где:

  • "sound38.wav" - звуковой файл
  • mciPlayParms.dwFrom = 300; - начало проигрываемого участка (в миллисекундах)
  • mciPlayParms.dwTo = 600; - его конец.

Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=22463