3. Управление устройством CD ROM

В этой главе мы расскажем вам об использовании устройства чтения CD ROM для проигрывания звуковых компакт-дисков. Приложения Windows работают с этим устройством через интерфейс MCI, которым вы уже умеете пользоваться для записи и воспроизведения wav-файлов. Так как объем книги ограничен, мы не будем подробно рассказывать об использовании всех команд MCI для управления устройством чтения CD ROM, ограничившись только особенностями.

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

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

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

Приложение может устанавливать лазерное устройство чтения в произвольное место спирали, причем драйвер обеспечивает позиционирование в режиме прямого доступа как на начало любой дорожки, так и в произвольную позицию внутри дорожки. К сожалению, процесс позиционирования занимает много времени, около 0,5 секунды, поэтому (а также из-за небольшой скорости передачи, составляющей 150-300 Кбайт в секунду) устройства чтения CD ROM нельзя называть быстродействующими.

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

Вы можете работать с устройством чтения CD ROM при помощи интерфейса управляющих строк MCI или интерфейса управляющих сообщений.

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

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

Открытие и закрытие устройства CD ROM

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

open cdaudio alias cd wait

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

При открытии драйвера CD ROM можно указать параметр shareable , в этом случае устройством смогут пользоваться одновременно несколько приложений (если они все откроют устройство с параметром shareable).

Драйвер устройства CD ROM не работает с файлами, поэтому в управляющей строке open путь к файлу не указывается.

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

close cd

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

Команда sysinfo не имеет особенностей. В качестве имени устройства для этой команды следует указывать строку cdaudio, даже если при открытии был использован алиас.

Для команды info можно указывать только параметр product.

С помощью команды capability с параметром can eject вы можете узнать, имеет ли устройство CD ROM возможность автоматического извлечения компакт-дисков. Вы можете также использовать и другие параметры: can play, can record, can save, compound device, device type, has audio, has video, uses files.

Для определения текущего состояния CD ROM следует использовать команду status . Вы можете указать следующие параметры:

current track

Номер текущей дорожки

length

Общая длина

length track track_number

Длина заданной дорожки

media present

Если в устройство вставлен компакт-диск, возвращается строка true

mode

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

number of tracks

Количество дорожек

position

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

position track track_number

Текущая позиция на заданной дорожке

ready

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

start position

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

time format

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

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

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

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

audio all off

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

audio all on

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

audio left off

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

audio left on

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

audio right off

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

audio right on

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

door closed

Загрузка компакт-диска и фиксирование его в устройстве. Этот параметр может поддерживаться не всеми устройствами (так же, как и параметр door open)

door open

Извлечение компакт-диска

time format milliseconds

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

time format msf

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

time format tmsf

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

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

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

play

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

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

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

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

open cdaudio alias cd wait
set cd time format tmsf wait
play cd from 11 to 12 wait
close cd

Вы можете попробовать эту последовательность команд с помощью приложения MCITEST из Microsoft SDK for Windows 3.1. Загрузочный модуль приложения MCITEST есть на дискете, которая продается вместе с книгой.

Если вам нужно указать позицию внутри дорожки, используйте более полный формат времени с указанием минут и секунд. Например, следующая команда выполнит проигрывание фрагмента 11-ой дорожки, начало которого отстоит на 10 секунд от начала дорожки, а длительность составляет 5 секунд:

play cd from 11:0:10 to 11:0:15 wait

stop

Останов проигрывания

stop device_id

pause

Временный останов (пауза). Для устройства чтения CD ROM эта команда работает как команда полного останова stop, при этом команда продолжения работы после временного останова resume не поддерживается

pause device_id

seek

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

seek device_id parameter [notify] [wait] 

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

to position

Позиционирование в заданное место компакт-диска

to start

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

to end

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

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

Как правило, большинство приложений, составленных на языках программирования C и C++, управляют устройством чтения CD ROM с помощью интерфейса управляющих сообщений MCI. Напомним, что приложения, использующие этот интерфейс, посылают устройствам мультимедиа управляющие сообщения с помощью функции mciSendCommand .

Рассмотрим особенности команд MCI, предназначенных для устройства чтения компакт-дисков.

MCI_OPEN

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

Поле lpstrDeviceType структуры MCI_OPEN_PARMS содержит указатель на строку имени устройства, или константный идентификатор устройства. Для устройства чтения CD ROM вы можете указать имя "cdaudio " или константу MCI_DEVTYPE_CD_AUDIO .

Параметр lpstrElementName не используется, так как устройство чтения компакт-дисков не работает с файлами.

Приведенный ниже фрагмент кода открывает устройство чтения компакт-дисков:

MCIOpen.lpstrDeviceType = (LPSTR)MCI_DEVTYPE_CD_AUDIO;
dwrc = mciSendCommand(NULL, MCI_OPEN,
   MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID, (DWORD)(LPVOID)&MCIOpen);

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

MCI_CLOSE

Эта команда закрывает устройство. Она также не имеет никаких особенностей.

MCI_PLAY

Команда MCI_PLAY не имеет расширений для устройства чтения CD ROM.

MCI_PAUSE

Команда MCI_PAUSE останавливает выполнение операции проигрывания звукового компакт-диска и действует точно так же, как и команда MCI_STOP.

MCI_STOP

Команда MCI_STOP останавливает выполнение проигрывания компакт-диска.

MCI_SEEK

Команда MCI_SEEK позволяет выполнять позиционирование. Она не имеет расширений, специально предназначенных для устройства чтения CD ROM.

MCI_BREAK

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

MCI_GETDEVCAPS

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

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

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

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

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

MCI_INFO

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

Используется блок параметров в формате структуры 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_SYSINFO

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

MCI_STATUS

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

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

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

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

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

Значение параметра dwItem Описание получаемой информации
MCI_STATUS_CURRENT_TRACK Номер текущей дорожки
MCI_STATUS_LENGTH Общий размер всех дорожек компакт-диска
MCI_STATUS_MODE Текущий режим устройства. Может иметь следующие значения:MCI_MODE_NOT_READY не готово;MCI_MODE_PAUSE пауза;MCI_MODE_PLAY проигрывание;MCI_MODE_STOP останов;MCI_MODE_OPEN открывание;MCI_MODE_RECORD запись;MCI_MODE_SEEK позиционирование
MCI_STATUS_NUMBER_OF_TRACKS Общее количество дорожек, которые можно проиграть
MCI_STATUS_POSITION Текущая позиция
MCI_STATUS_READY Если устройство готово, возвращается значение TRUE, в противном случае - FALSE
MCI_STATUS_TIME_FORMAT Текущий формат времени. Может иметь следующие значения:MCI_FORMAT_MILLISECONDS MCI_FORMAT_MSF MCI_FORMAT_TMSF
MCI_STATUS_START Начальная позиция
MCI_STATUS_TRACK В поле dwTrack записывается либо начальная позиция заданной дорожки (если дополнительно используется MCI_STATUS_POSITION), либо размер дорожки (если дополнительно используется MCI_STATUS_LENGTH)
MCI_STATUS_MEDIA_PRESENT Возвращается TRUE, если компакт-диск вставлен в устройство

MCI_SET

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

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

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

Приведем список флагов, которые используются вместе с командой 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_TIME_FORMAT Установить формат времени. Используется вместе со следующими константами:MCI_FORMAT_MSF минуты, секунды, фреймы;MCI_FORMAT_MILLISECONDS в миллисекундах;MCI_FORMAT_TMSF треки, минуты, секунды, фреймы
MCI_SET_ON Включение заданного канала
MCI_SET_OFF Выключение заданного канала

При использовании формата времени MCI_FORMAT_MSF старший байт старшего слова поля dwTimeFormat не используется, младший содержит номер фрейма. Старший байт младшего слова содержит секунды, младший - минуты. Формат времени MCI_FORMAT_TMSF аналогичный, за исключением того, что старший байт старшего слова содержит номер дорожки.

Приложение MCICDPL

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

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

Исходный текст приложения представлен в листинге 3.1.

Листинг 3.1. Файл mcicdpl/mcicdpl.cpp

// ----------------------------------------
// Проигрыватель звуковых компакт-дисков
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <mmsystem.h>
#include <mem.h>
#include <stdlib.h>

#include "mcicdpl.hpp"

#define CD_EMPTY   0
#define CD_READY   1
#define CD_PLAYING 2
#define CD_PAUSED  3

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

// Имя класса окна
char const szClassName[]   = "MCICDP";

// Заголовок окна
char const szWindowTitle[] = "MCI CD Player";

HINSTANCE hInst;

DWORD dwrc;
UINT nTimerID;

MCI_OPEN_PARMS   MCIOpen;
MCI_SET_PARMS    MCISet;
MCI_STATUS_PARMS MCIStatus;
MCI_PLAY_PARMS   MCIPlay;

BOOL bMediaPresent = FALSE;
BOOL bPaused       = FALSE;
UINT nMode = 0;
UINT nCurTrack = 0;
UINT nTrackCnt = 0;

HWND hwndCurTrack = NULL;

// =====================================
// Функция 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;

  // Открываем устройство чтения компакт-дисков
  MCIOpen.lpstrDeviceType = (LPSTR)MCI_DEVTYPE_CD_AUDIO;
  dwrc = mciSendCommand(NULL, MCI_OPEN,
    MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID,
    (DWORD)(LPVOID)&MCIOpen);
  if(dwrc)
  {
    mciwioError(dwrc);
    return -1;
  }

  // Устанавливаем формат времени
  MCISet.dwTimeFormat = MCI_FORMAT_TMSF;
  dwrc = mciSendCommand(MCIOpen.wDeviceID, MCI_SET,
    MCI_SET_TIME_FORMAT,
    (DWORD)(LPVOID)&MCISet);
  if(dwrc)
  {
    mciwioError(dwrc);
    return -1;
  }

  // Создаем диалоговую панель вместо главного окна
  hwnd = CreateDialog(hInstance, szClassName, 0, NULL);

  // Если создать окно не удалось, завершаем приложение
  if(!hwnd)
    return FALSE;

  // Определяем идентификатор поля, которое используется
  // для отображения номера текущей дорожки
  hwndCurTrack = GetDlgItem(hwnd, IDT_CURTRACK);

  // Рисуем главное окно
  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, NULL, 0, 0))
  {
    if((hwnd == 0) || (!IsDialogMessage(hwnd, &msg)))
      DispatchMessage(&msg);
  }
  return msg.wParam;
}

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

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

  memset(&wc, 0, sizeof(wc));
  wc.style = 0;
  wc.lpfnWndProc    = (WNDPROC) WndProc;
  wc.cbClsExtra     = 0;
  wc.cbWndExtra     = DLGWINDOWEXTRA;
  wc.hInstance      = hInstance;
  wc.hIcon          = LoadIcon(hInstance, "APPICON");
  wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground  = NULL;
  wc.lpszMenuName   = (LPSTR)NULL;
  wc.lpszClassName  = (LPSTR)szClassName;

  // Регистрация класса
  aWndClass = RegisterClass(&wc);

  return (aWndClass != 0);
}

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

LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
    // ---------------------------------------
    // Обработчик сообщения WM_CREATE
    // ---------------------------------------
    case WM_CREATE:
    {
      // Создаем таймер, который нужен для периодического
      // определения состояния устройства чтения CD
      nTimerID = SetTimer(hwnd, 1, 1000, NULL);

      return 0;
    }

    // ---------------------------------------
    // Обработчик сообщения WM_COMMAND
    // ---------------------------------------
    case WM_COMMAND:
    {
      switch(wParam)
      {
        // Запуск режима проигрывания 
        case IDB_PLAY:
        {
          // Если в проигрывателе есть компакт-диск,
          // запускаем проигрывание
          if(bMediaPresent)
            Play(hwnd, 1);
          return 0;
        }

        // Останов проигрывания
        case IDB_STOP:
        {
          if(bMediaPresent)
          {
            bPaused = FALSE;
            nCurTrack = 0;
            mciSendCommand(MCIOpen.wDeviceID, MCI_STOP,
              NULL, NULL);
          }
          return 0;
        }

        // Временный останов проигрывания
        case IDB_PAUSE:
        {
          if(bMediaPresent)
          {
            if(!bPaused)
            {
              bPaused = TRUE;
              mciSendCommand(MCIOpen.wDeviceID, MCI_PAUSE,
                NULL, NULL);
            }
          }
          return 0;
        }

        // Продолжение проигрывания после
        // временного останова
        case IDB_RESUME:
        {
          if(bMediaPresent)
          {
            if(bPaused)
            {
              bPaused = FALSE;
              MCIPlay.dwCallback = (DWORD)hwnd;
              mciSendCommand(MCIOpen.wDeviceID, MCI_PLAY,
                MCI_NOTIFY, (DWORD)(LPVOID)&MCIPlay);
            }
          }
          return 0;
        }

        // Позиционирование на следующую дорожку
        case IDB_NEXT:
        {
          if(bMediaPresent)
          {
            UINT nNewTrack;

            // Если текущая дорожка - последняя,
            // начинаем проигрывание с первой дорожки.
            // Если нет - проигрываем следующую дорожку
            if(nCurTrack == nTrackCnt)
              nNewTrack = 1;
            else
              nNewTrack = nCurTrack + 1;

            Play(hwnd, nNewTrack);
          }
          return 0;
        }

        // Позиционирование на предыдущую дорожку 
        case IDB_PREV:
        {
          if(bMediaPresent)
          {
            UINT nNewTrack;

            // Если текущая дорожка - первая,
            // проигрываем последнюю дорожку
            if(nCurTrack <= 1)
              nNewTrack = nTrackCnt;
            else
              nNewTrack = nCurTrack - 1;

            Play(hwnd, nNewTrack);
          }
          return 0;
        }

        // Завершаем работу приложения
        case IDOK:
        case IDCANCEL:
        {
          SendMessage(hwnd, WM_CLOSE, 0, 0L);
          return 0;
        }

        // Выполняем команду извлечения диска из
        // устройства чтения
        case IDB_EJECT:
        {
          mciSendCommand(MCIOpen.wDeviceID, MCI_SET,
            MCI_SET_DOOR_OPEN, NULL);
          return 0;
        }
      }
    }

    // ---------------------------------------
    // Обработчик сообщения WM_TIMER
    // ---------------------------------------
    case WM_TIMER:
    {
      UINT nCurMode;

      // Если окно свернуто в пиктограмму, ничего не делаем,
      // чтобы не снижать производительность системы
      if(IsIconic(hwnd))
        return 0;

      // Определяем текущее состояние проигрывателя CD
      MCIStatus.dwItem = MCI_STATUS_MODE;
      mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS,
        MCI_STATUS_ITEM | MCI_WAIT,
        (DWORD)(LPVOID)&MCIStatus);

      // Проверяем, готово ли устройство чтения к работе 
      if((MCIStatus.dwReturn == MCI_MODE_NOT_READY) ||
         (MCIStatus.dwReturn == MCI_MODE_OPEN))
      {
        // Устройство не готово
        nCurMode = CD_EMPTY;
      }
       else if((MCIStatus.dwReturn == MCI_MODE_STOP) &&
         bPaused)
      {
        // Устройство остановлено 
        nCurMode = CD_PAUSED;
      }
      else if(MCIStatus.dwReturn == MCI_MODE_PLAY)
      {  
        // Устройство находится в режиме проигрывания
        nCurMode = CD_PLAYING;
      }
      else
      {
        // Устройство готово
        nCurMode = CD_READY;
      }

      // Если с момента последней проверки произошло
      // изменение режима, записываем код нового режима
      if(nMode != nCurMode)
      {
        nMode = nCurMode;
      }

      // Проверяем, вставлен ли компакт-диск
      MCIStatus.dwItem = MCI_STATUS_MEDIA_PRESENT;
      mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS,
        MCI_STATUS_ITEM | MCI_WAIT,
        (DWORD)(LPVOID)&MCIStatus);

      // Если компакт-диск вставлен, определяем
      // количество звуковых дорожек
      if((!bMediaPresent) && MCIStatus.dwReturn)
      {
        bMediaPresent = TRUE;
        bPaused = FALSE;
        nCurTrack = 0;

        MCIStatus.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
        mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS,
          MCI_STATUS_ITEM | MCI_WAIT,
          (DWORD)(LPVOID)&MCIStatus);

        nTrackCnt = MCIStatus.dwReturn;
      }

      // Если компакт-диск не вставлен, сбрасываем
      // номер текущей дорожке в поле диалоговой панели
      else if((bMediaPresent) && !MCIStatus.dwReturn)
      {
        bMediaPresent = FALSE;
        bPaused = FALSE;
        SetWindowText(hwndCurTrack, (LPSTR)"");
      }

      // Если приложение находится в режиме проигрывания,
      // определяем номер текущей дорожки
      if(nCurMode == CD_PLAYING)
      {
        // Определяем текущую позицию
        MCIStatus.dwItem = MCI_STATUS_POSITION;
        mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS,
          MCI_STATUS_ITEM | MCI_WAIT,
          (DWORD)(LPVOID)&MCIStatus);

        // Если номер дорожки изменился, отображаем новое
        // значение в соответствующем поле диалоговой панели 
        if(nCurTrack != (UINT)MCI_TMSF_TRACK(MCIStatus.dwReturn))
        {
          BYTE szBuf[20];
          nCurTrack = (UINT)MCI_TMSF_TRACK(MCIStatus.dwReturn);
          SetWindowText(hwndCurTrack, itoa(nCurTrack, szBuf, 10));
        }
      }
      return 0;
    }

    // ---------------------------------------
    // Обработчик сообщения MM_MCINOTIFY
    // ---------------------------------------
    case MM_MCINOTIFY:
    {
      if(wParam == MCI_NOTIFY_SUCCESSFUL)
      {
        if(bMediaPresent)
           Play(hwnd, 1);
      }
      return 0;
    }

    // ---------------------------------------
    // Обработчик сообщения WM_CLOSE
    // ---------------------------------------
    case WM_CLOSE:
    {
      DestroyWindow(hwnd);
      return 0;
    }

    // ---------------------------------------
    // Обработчик сообщения WM_DESTROY
    // ---------------------------------------
    case WM_DESTROY:
    {
      // Закрываем устройство чтения компакт-дисков
      dwrc = mciSendCommand(MCIOpen.wDeviceID, MCI_CLOSE,
        NULL, NULL);
      if(dwrc)
        mciwioError(dwrc);

      // Уничтожаем таймер
      KillTimer(hwnd, nTimerID);

      PostQuitMessage(0);
      return 0;
    }
  }
  return DefDlgProc(hwnd, msg, wParam, lParam);
}

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

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

//-----------------------------------------------------
// Play
// Запуск проигрывания дорожки
//-----------------------------------------------------
void Play(HWND hwnd, UINT nTrack)
{
  bPaused = FALSE;

  MCIPlay.dwCallback = (DWORD)hwnd;
  MCIPlay.dwFrom     = MCI_MAKE_TMSF(nTrack, 0, 0, 0);

  dwrc = mciSendCommand(MCIOpen.wDeviceID, MCI_PLAY,
    MCI_FROM | MCI_NOTIFY, (DWORD)(LPVOID)&MCIPlay);
  if(dwrc)
  {
    mciwioError(dwrc);
    return;
  }
}

Особенностью данного приложения является отсутствие главного окна - его роль выполняет диалоговая панель.

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

Далее устанавливается формат времени MCI_FORMAT_TMSF, так как приложение будет выполнять позиционирование по дорожкам компакт-диска.

Далее с помощью функции CreateDialog создается диалоговая панель, при этом указывается зарегистрированный приложением класс окна szClassName (строка "MCICDP"):

hwnd = CreateDialog(hInstance, szClassName, 0, NULL);

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

CLASS "MCICDP"

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

hwndCurTrack = GetDlgItem(hwnd, IDT_CURTRACK);

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

while(GetMessage(&msg, NULL, 0, 0))
{
  if((hwnd == 0) || (!IsDialogMessage(hwnd, &msg)))
    DispatchMessage(&msg);
}

Во время обработки сообщения WM_CREATE создается таймер с периодом 1 секунда. Этот таймер будет использоваться для определения текущего состояния устройства чтения компакт-дисков.

Если нажать на кнопку "Play", функция окна получит сообщение WM_COMMAND с параметром IDB_PLAY. При этом приложение проверит состояние флага bMediaPresent (наличие компакт-диска в устройстве) и, если этот флаг установлен, запустит проигрывание первой дорожки. Содержимое флага bMediaPresent периодически обновляется в соответствии с действительным состоянием устройства обработчиком сообщений таймера.

Кнопка "Stop" позволяет остановить процесс проигрывания. При этом устройству посылается команда MCI_STOP. Алогично, кнопка "Pause" выполняет временный останов, соответствующий обработчик посылает управляющее сообщение с кодом MCI_PAUSE. Для продолжения проигрывания после временного останова используется команда MCI_PLAY, для которой не задается начальная позиция (команда MCI_RESUME не поддерживается драйвером устройства чтения CD ROM). В этом случае проигрывание возобновляется с текущей позиции, то есть с прерванного места.

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

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

Код текущего состояния устройства записывается в переменную nMode. Далее обработчик сообщения WM_TIMER с помощью команды MCI_STATUS проверяет, вставлен ли в устройство компакт-диск. Если диск вставлен, определяется количество дорожек. определенное значение сохраняется в переменной nTrackCnt.

Номер текущей дорожки также определяется каждый раз при обработке сообщения таймера (при условии, что компакт-диск вставлен в устройство и устройство находится в режиме проигрывания). Если этот номер изменился, новое значение отображается в статическом органе управления диалоговой панели с идентификатором hwndCurTrack:

if(nCurTrack != (UINT)MCI_TMSF_TRACK(MCIStatus.dwReturn))
{
  BYTE szBuf[20];
  nCurTrack = (UINT)MCI_TMSF_TRACK(MCIStatus.dwReturn);
  SetWindowText(hwndCurTrack, itoa(nCurTrack, szBuf, 10));
}

Макрокоманда MCI_TMSF_TRACK извлекает номер дорожки из значения, возвращенного командой MCI_STATUS с параметром MCI_STATUS_POSITION.

В файле mmsystem.h определены и другие макрокоманды, которые используются аналогичным образом для получения других полей: MCI_TMSF_FRAME , MCI_TMSF_MINUTE , MCI_TMSF_SECOND , MCI_MSF_FRAME , MCI_MSF_MINUTE , MCI_MSF_SECOND . Можно сделать и обратные преобразования. Например, можно использовать макрокоманду MCI_MAKE_TMSF для упаковки в двойное слово номера дорожки, минут, секунд и номера фрейма:

dwFormat = MCI_MAKE_TMSF(track, min, sec, frame);

В нашем приложении предусмотрена обработка сообщения MM_MCINOTIFY . Это сообщение используется для того чтобы "зациклить" проигрывание компакт-диска. После того как команда проигрывания будет выполнена до конца (то есть после того как будет завершено проигрывание последней дорожки компакт-диска), функция окна приложения получит сообщение MM_MCINOTIFY с параметром MCI_NOTIFY_SUCCESSFUL. Обработчик этого сообщения выглядит очень просто - он запускает проигрывание заново с первой дорожки:

case MM_MCINOTIFY:
{
  if(wParam == MCI_NOTIFY_SUCCESSFUL)
  {
    if(bMediaPresent)
       Play(hwnd, 1);
  }
  return 0;
}

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

Функция Play, определенная в нашем приложении, запускает проигрывание компакт-диска начиная с заданной дорожки. В ней для формирования позиции используется макрокоманда MCI_MAKE_TMSF :

MCIPlay.dwFrom     = MCI_MAKE_TMSF(nTrack, 0, 0, 0);

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

Файл mcicdpl.hpp (листинг 3.2) содержит определения констант, используемых в приложении.

Листинг 3.2. Файл mcicdpl/mcicdpl.hpp

#define IDT_CURTRACK 200
#define IDB_STOP   101
#define IDB_PAUSE  102
#define IDB_RESUME 103
#define IDB_NEXT   104
#define IDB_PREV   105
#define IDB_EJECT  106
#define IDB_PLAY   100

Файл описания ресурсов приложения представлен в листинге 3.3. Он содержит определение пиктограммы и диалоговой панели, выступающей в роли главного окна приложения.

Листинг 3.3. Файл mcicdpl/mcicdpl.rc

#include "g:\tcwin\include\windows.h"
#include "mcicdpl.hpp"

APPICON ICON "mcicdpl.ico"

MCICDP DIALOG 45, 20, 153, 57
STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
CLASS "MCICDP"
CAPTION "Compact Disk Player"
BEGIN
  PUSHBUTTON "Play", IDB_PLAY, 84, 11, 31, 11,
     WS_CHILD | WS_VISIBLE | WS_TABSTOP
  PUSHBUTTON "Stop", IDB_STOP, 118, 11, 31, 11,
     WS_CHILD | WS_VISIBLE | WS_TABSTOP
  PUSHBUTTON "Pause", IDB_PAUSE, 84, 26, 31, 11,
     WS_CHILD | WS_VISIBLE | WS_TABSTOP
  PUSHBUTTON "Resume", IDB_RESUME, 118, 26, 31, 11,
     WS_CHILD | WS_VISIBLE | WS_TABSTOP
  PUSHBUTTON ">>I", IDB_NEXT, 6, 26, 31, 11,
     WS_CHILD | WS_VISIBLE | WS_TABSTOP
  PUSHBUTTON "I<<", IDB_PREV, 40, 26, 31, 11,
     WS_CHILD | WS_VISIBLE | WS_TABSTOP
  PUSHBUTTON "Eject", IDB_EJECT, 6, 41, 65, 11,
     WS_CHILD | WS_VISIBLE | WS_TABSTOP
  DEFPUSHBUTTON "Exit", IDOK, 84, 41, 65, 11,
     WS_CHILD | WS_VISIBLE | WS_TABSTOP
  LTEXT "Track:", -1, 12, 7, 35, 8,
     WS_CHILD | WS_VISIBLE | WS_GROUP
  LTEXT "00", IDT_CURTRACK, 35, 7, 16, 8,
     WS_CHILD | WS_VISIBLE | WS_GROUP
  CONTROL "", -1, "static",
     SS_BLACKFRAME | WS_CHILD | WS_VISIBLE, 6, 4, 65, 15
END

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

Листинг 3.4. Файл mcicdpl/mcicdpl.def

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