9. Устройство чтения CD-ROM

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

Не останавливаясь подробно на описании различных форматов компакт-дисков, которое само по себе может составить предмет для отдельной книги, мы рассмотрим средства MS-DOS, предназначенные для работы с компакт-дисками формата ISO-9960 и со звуковыми компакт-дисками стандарта Redbook. Первые из них чаще всего применяются для хранения программ и компьютерных баз данных, вторые – для записи звуковой информации. О том, как работать со звуковыми дисками в среде операционной системы Microsoft Windows, мы рассказывали в 15 томе «Библиотеки системного программиста», который называется «Мультимедиа для Windows».

Драйвер устройства чтения CD-ROM

Для работы с устройством чтения CD-ROM в среде операционных систем MS-DOS и Microsoft Windows 95 вы должны установить в файле CONFIG.SYS специальный драйвер. Этот драйвер обычно поставляется вместе с устройством и программой установки на дискете.

Вот пример строки файла CONFIG.SYS, в которой загружается драйвер устройства чтения CD-ROM:

device=c:\cd\cpqidecd.sys /d:idecd01

Здесь мы передаем драйверу параметр /d:idecd01, который задает имя устройства чтения CD-ROM.

Заметим, что устройство чтения CD-ROM имеет символьный драйвер, несмотря на то что оно является дисковым и, казалось бы, для него должен применяться блочный драйвер (подробнее о типах драйверов вы можете прочитать в 18 томе “Библиотеки системного программиста”, который называется “MS-DOS для программиста»). На самом деле устройство чтения CD-ROM не похоже на обычное дисковое устройство. Операционная система MS-DOS работает с ним как с сетевым устройством через интерфейс Network Redirector.

Расширение MSCDEX

Вторая компонента, необходимая для работы в среде MS-DOS с устройством чтения CD-ROM – программа Microsoft CD-ROM Extention, которая находится в файле MSCDEX.EXE. Этот файл входит в комплект MS-DOS и иногда поставляется вместе с устройством чтения CD-ROM на той же дискете, что и драйвер. Для того чтобы избежать несовместимости, мы рекомендуем всегда использовать ту программу MSCDEX.EXE, что устанавливается на диск вместе с MS-DOS.

Программа MSCDEX.EXE подключается в файле AUTOEXEC.BAT операционной системы MS-DOS следующим образом:

c:\dos\mscdex /d:idecd01

Обратите внимание, что значение параметра /d должно совпадать со значением аналогичного параметра для драйвера.

В среде операционной системы Microsoft Windows 95 программа MSCDEX.EXE не нужна, так как все выполняемые ей функции встроены в операционную систему.

Что же касается Microsoft Windows NT, то в ней только часть функций расширения MSCDEX.EXE моделируется для виртуальной машины DOS. Поэтому не все программы, исходные тексты которых приведены в этой главе, будут там правильно работать.

Функции MSCDEX

В этом разделе мы приведем краткое описание основных функций программного интерфейса расширения MSCDEX.EXE, доступные в среде MS-DOS.

Все функции расширения MSCDEX.EXE вызываются через мультиплексное прерывание INT 2Fh. При этом в регситр AH записывается значение 15h, а в регистр AL – код функции.

Определение количества устройств CD-ROM

Сегодня все большее количество компьютеров оснащается сразу несколькими устойствами чтения CD-ROM. С помощью функции 00h можно определить количество устройств чтения CD-ROM, имеющихся в системе, номер первого устройства CD-ROM, а также проверить, установлена ли программа MSCDEX:

Регистры на входе: AX = 1500h;
Регистры на выходе: BX = количество устройств чтения CD-ROM, установленных в системе;
CX = номер первого устройства чтения CD-ROM. Значение 0 соответствует устройству A:, 1 – B: и так далее

Заметим, что буквенные обозначения устройств не обязательно должны идти последовательно, начинаясь со значения, которое функция 00h возвращает в регистре CX. Если вам нужно определить обозначения всех устройств чтения CD-ROM, следует воспользоваться функцией 150Dh, о которой мы расскажем ниже в этом разделе.

Получение списка устройств CD-ROM

С помощью функции 01h вы можете получить список структур CD_ROM_Driver_Desc, описывающих установленные устройства CD-ROM:

Регистры на входе: AX = 1501h;
ES:BX = адрес буфера, в который будет скопирован список устройств CD-ROM
Регистры на выходе: Не используются

Первый байт такой структуры содержит номер устройства (unit nubmer), следом за которым идут четыре байта адреса заголовка драйвера, обслуживающего данное устройство:

typedef struct _CD_ROM_Driver_Desc
{
  unsigned char cSubUnit;
  unsigned long dwDevHeader;
} CD_ROM_Driver_Desc;

Перед вызовом этой функции необходимо подготовить буфер достаточного размера. Размер буфера нетрудно определить, узнав предварительно количество устройств CD-ROM, установленных в системе, с помощью функции 00h. Для каждого устройства в буфере требуется пять байт оперативной памяти.

Получение имени файла прав собственности

Функция 02h записывает в буфер имя файла, содержащего права собственности на компакт-диск, установленный в устройстве чтения CD-ROM:

Регистры на входе: AX = 1502h;
ES:BX = адрес буфера размером 38 байт
CX = номер устройства чтения CD-ROM
Регистры на выходе: CY = 1 при ошибке в номере устройства чтения CD-ROM;
AX = код ошибки

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

Получение имени файла резюме

Функция 03h записывает в буфер имя файла, содержащего резюме компакт-диска, установленного в устройстве чтения CD-ROM:

Регистры на входе: AX = 1503h;
ES:BX = адрес буфера размером 38 байт
CX = номер устройства чтения CD-ROM
Регистры на выходе: CY = 1 при ошибке в номере устройства чтения CD-ROM;
AX = код ошибки

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

Получение имени файла библиографической документации

Функция 04h записывает в буфер имя файла, содержащего библиографическую документацию компакт-диска, установленного в устройстве чтения CD-ROM:

Регистры на входе: AX = 1504h;
ES:BX = адрес буфера размером 38 байт
CX = номер устройства чтения CD-ROM
Регистры на выходе: CY = 1 при ошибке в номере устройства чтения CD-ROM;
AX = код ошибки

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

Чтение сектора оглавления компакт-диска

При помощи функции 05h вы можете найти все оглавления тома Volume Descriptor:

Регистры на входе: AX = 1505h;
ES:BX = адрес буфера размером 2048 байт;
CX = номер устройства чтения CD-ROM;
DX = номер дескриптора тома (0 – первый, 1 – второй и так далее)
Регистры на выходе: CY = 0, если не было ошибок. При этом содержимое регистра AX определяет тип прочитанного дескриптора:
AX= 1, стандартный дескриптор тома;
AX = 0FFh, завершающий дескриптор;
AX = 0, дескриптор другого типа;
CY = 1 при ошибке в номере устройства чтения CD-ROM. При этом регистр AX содержит код ошибки

Чтение сектора по абсолютному адресу

Функция 08h предназначена для прямого чтения секторов компакт-диска и напоминает прерывание INT 25h опреационной системы MS-DOS:

Регистры на входе: AX = 1508h;
ES:BX = адрес буфера, в который будут прочитаны данные;
CX = номер устройства чтения CD-ROM;
DX = количество секторов, которые нужно прочитать;
SI:DI = номер начального сектора
Регистры на выходе: CY = 1 при ошибке в номере устройства чтения CD-ROM;
AL = код ошибки

Проверка устройства чтения CD-ROM

При помощи функции 0Bh вы можете проверить, является диск устройством чтения CD-ROM, доступ к которому возможен через функции MSCDEX:

Регистры на входе: AX = 150Bh;
CX = номер устройства чтения CD-ROM
Регистры на выходе: BX = ADADh, если программа MSCDEX установлена;
AX = признак, является ли диск устройством чтения CD-ROM:
если содержимое AX не равно нулю, то диск – устройство CD-ROM;
если содержимое AX равно нулю, доступ к данному устройству с помощью функций MSCDEX невозможен

Определение версии MSCDEX

Функция 0Ch предназначена для определения версии установленной программы MSCDEX:

Регистры на входе: AX = 150Ch;
Регистры на выходе: BH = старший номер верии MSCDEX;
BL = младший номер версии MSCDEX

Определение обозначения устройств чтения CD-ROM

При помощи функции 0Dh вы можете заполнить массив номерами установленных в системе устройств чтения CD-ROM:

Регистры на входе: AX = 150Dh;
ES:BX = адрес массива, в который будут записаны обозначения устройств CD-ROM
Регистры на выходе: Не используются

Размер массива должен быть равен количеству установленных в системе устройств чтения CD-ROM, которое можно определить с помощью функции 00h.

Вызов драйвера CD-ROM

Функция 10h предназначена для прямого вызова драйвера устройства чтения CD-ROM:

Регистры на входе: AX = 1510h;
CX = номер устройства чтения CD-ROM;
ES:BX = адрес предварительно заполненного заголовка запроса драйвера
Регистры на выходе: Не используются

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

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

Работа через драйвер CD-ROM

Перед тем как приступить к чтению этого раздела, мы рекомендуем вам обратиться к 6 главе 18 тома “Библиотеки системного программиста”, которая называется “Драйверы”. В ней мы привели минимум сведений, которые необходимы для создания собственных драйверов устройств, а также для работы с уже имеющимися драйверами.

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

Внутри драйвера есть две функции, одна из которых называется программой стратегии, другая – программой прерывания.

Задача программы стратегии – сохранение в области данных драйвера адреса заголовка запроса, который подготавливается для драйвера операционной системой и выполняется программой прерывания.

Как найти адреса этих программ?

Они есть в заголовке драйвера, который, однако, тоже еще нужно найти. Операционная система MS-DOS не имеет в своем составе документированных средств для поиска заголовков драйверов. В 18 томе “Библиотеки системного программиста” мы описали, как это можно сделать с применением недокументированной векторной таблицы связи MS-DOS.

Однако для вызова драйвера устройства чтения CD-ROM вам не потребуются недокументированные средства, а также прямые вызовы программ стратегии и прерывания. С помощью описанной выше функции 10h расширения MSCDEX.EXE вы можете передавать драйверу заголовки запросов вполне документированным способом.

Заголовок запроса

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

Определение структуры ReqHdr приведено ниже:

typedef unsigned char BYTE;
typedef unsigned int  WORD;
typedef unsigned long DWORD;

#pragma pack(1)
typedef struct _ReqHdr
{
  BYTE bSize;        // размер заголовка запроса в байтах
  BYTE bSubUnit;     // номер устройства subunit
  BYTE bCmd;         // код команды
  WORD wStatus;      // слово состояния
  BYTE bReserved[8]; // зарезервировано
} ReqHdr;

Для удобства мы также определили типы BYTE, WORD и DWORD, которыми будем пользоваться в этой главе.

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

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

В поле bCmd необходимо записать код команды, которую должен выполнить драйвер. Коды и описание команд мы приведем ниже.

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

Формат слова состояния:

Поле Описание
0-7 Код ошибки (если в слове состояния установлен бит 15)
8 Выполнение команды завершено
9 Устройство занято
10-14 Зарезервировано
15 При выполнении команды произошла ошибка

Если команда выполнилась (с ошибкой или без ошибки), в слове состояния установлен бит 8.

При возникновении ошибки также устанавливается бит 15. При этом в поле 0-7 находится код ошибки.

Список кодов ошибок приведен ниже:

Код ошибки Описание
00h Защита записи
01h Неизвестное устройство
02h Устройство не готово
03h Неизвестная команда
04h Ошибка циклической контрольной суммы CRC
05h Неправильная длина структуры запроа
06h Ошибка при поиске
07h Неизвестный носитель данных
08h Сектор не найден
09h В принтере нет бумаги
0Ah Ошиба при записи
0Bh Ошибка при чтении
0Ch Общая ошибка
0Dh Зарезервировано
0Eh Зарезервировано
0Fh Неправильная смена диска

Здесь приведены коды ошибок не только для устройства чтения CD-ROM, но и для других устройств (например, для принтера).

Команды драйвера CD-ROM

В этом разделе мы расскажем о командах драйвера CD-ROM. Заметим, что в рамках одной команды может выполняться несколько функций. Код функции при этом записывается в расширение загловка запроса.

Инициализация

Команда инициализации вызывается из MS-DOS только один раз.

Ниже мы привели формат заголовка запроса для этой команды:

// ---------------
// Код команды 0
// ---------------
#pragma pack(1)
typedef struct _Init
{
  ReqHdr rh;              
  BYTE   bNumberOfUnits;
  DWORD  lpEndAddress;
  DWORD  lpAddressOfBPB;
  BYTE   bNumberOfBlockDevice;
} Init;

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

Поле Описание
rh.wStatus Слово состояния
bNumberOfUnits Количество устройств, обслуживаемых драйвером. Равно 0
lpEndAddress Конечный адрес резидентной порции драйвера в оперативной памяти
lpAddressOfBPB Указатель на символ = в строке файла CONFIG.SYS, с помощью которой загружен драйвер CD-ROM. Может быть использован для анализа параметров драйвера
bNumberOfBlockDevice Равно 0

Чтение IOCTL Input

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

Формат заголовка запроса:

// ---------------
// Код команды 3
// ---------------
#pragma pack(1)
typedef struct _IOCTL_Input
{             
  ReqHdr rh;
  BYTE   bMediaDescriptor;
  DWORD  lpTransferAddress;
  WORD   wDataSize;
  WORD   wStartSector;
  DWORD  lpVolID;
} IOCTL_Input;

Заполнение полей заголовка запроса:

Поле Описание
rh.wStatus После вызова драйвера содержит слово состояния
bMediaDescriptor Байт описания среды носителя данных
lpTransferAddress Адрес буфера
wDataSize Размер буфера
wStartSector Номер начального сектора
lpVolID Указатель на идентификатор тома, если при выполнении команды возникла ошибка с кодом 0Fh

Команда IOCTL Input может выполнять много функций. Перед вызовом драйвера вы должны подготовить заголовок функции, указав в одном из его полей код выполняемой функции. Адрес и размер этой структуры необходимо записать в поля lpTransferAddress и wDataSize, соответственно.

Рассмотрим форматы заголовков различных функций, выполняемых в рамках команды IOCTL Input.

Определение адреса заголовка драйвера CD-ROM

// ---------------
// Код функции 0
// ---------------
#pragma pack(1)
typedef struct _RAddr
{             
  BYTE   bFunctionCode;
  DWORD  lpDeviceHeader;
} RAddr;
Поле Описание
bFunctionCode Код функции
lpDeviceHeader Адрес заголовка драйвера CD-ROM

Зная адрес заголовка драйвера, вы можете определить имя драйвера и его атрибуты. Подробности об атрибутах драйверов вы найдете в 18 томе «Библиотеки системного програмиста».

Определение положения головки

// ---------------
// Код функции 1
// ---------------
#pragma pack(1)
typedef struct _HeadLocation
{             
  BYTE   bFunctionCode;
  BYTE   bAddressMode;
  DWORD  lpHeadLocation;
} HeadLocation;
Поле Описание
bFunctionCode Код функции
bAddressMode Режим адресации:
0 – HSG;
1 – Redbook;
2 – 255 – зарезервировано
lpHeadLocation Положении головки. Значение зависит от режима адресации

Здесь необходимо сделать замечание относительно режимов адресации.

По умолчанию устройство чтения CD-ROM находится в режиме адресации HSG, описанный в стандарте High Sierra. При этом в качестве адреса указывается логический номер блока.

Другой режим адресации описан в стандарте Redbook. В нем адрес представляет собой набор из трех значений: минуты (MIN), секунды (SEC), фреймы (FRAME). Каждое значение занимает один байт, причем в младшем байте хранится значение FRAME, в следующем байте - значение SEC, и в последнем, третьем байте, - значение MIN.

С помощью следующей формулы вы можете преобразовать адрес из формата Redbook в формат HSG:

SECTOR = MIN * 60 * 75 + SEC * 75 + FRAME - 150

Получение информации о звуковых каналах

// ---------------
// Код функции 4
// ---------------
#pragma pack(1)
typedef struct _ChanInfo
{             
  BYTE   bFunctionCode;
  BYTE   bInpChannel0;
  BYTE   bVolControl0;
  BYTE   bInpChannel1;
  BYTE   bVolControl1;
  BYTE   bInpChannel2;
  BYTE   bVolControl2;
  BYTE   bInpChannel3;
  BYTE   bVolControl3;
} ChanInfo;
Поле Описание
bFunctionCode Код функции
bInpChannel0 Номер входного канала, присвоенного выходному каналу 0. По умолчанию равно 0
bVolControl0 Уровень громкости для канала 0. По умолчанию равно 0FFh
bInpChannel1 Номер входного канала, присвоенного выходному каналу 1. По умолчанию равно 1
bVolControl1 Уровень громкости для канала 1. По умолчанию равно 0FFh
bInpChannel2 Номер входного канала, присвоенного выходному каналу 2. По умолчанию равно 2
bVolControl2 Уровень громкости для канала 2. По умолчанию равно 0FFh
bInpChannel3 Номер входного канала, присвоенного выходному каналу 3. По умолчанию равно 3
bVolControl3 Уровень громкости для канала 3. По умолчанию равно 0FFh

Чтение данных из устройства

// ---------------
// Код функции 5
// ---------------
#pragma pack(1)
typedef struct _DriveBytes
{             
  BYTE   bFunctionCode;
  BYTE   bNumBytes;
  BYTE   bReadBuff[128];
} DriveBytes;
Поле Описание
bFunctionCode Код функции
bNumBytes Количество байт, которые необходимо прочитать из устройства
bReadBuff Буфер для чтения размером 128 байт. После выполнения команды в него записывается информация, которая зависит от типа устройства чтения CD-ROM

Определение состояния устройства

// ---------------
// Код функции 6
// ---------------
#pragma pack(1)
typedef struct _DeviceStatus
{             
  BYTE   bFunctionCode;
  DWORD  dwDeviceParameters;
} DeviceStatus;
Поле Описание
bFunctionCode Код функции
dwDeviceParameters 32-разрядное слово состояния устройства

Описания отдельных бит слова состояния приведено ниже:

Бит Описание
0 1 – устройство открыто, в него можно вставлять диск;
0 – устройство закрыто
1 0 – устройство заблокировано, из него нельзя извлечь диск;
1 – устройство разблокировано
2 0 – устройство может работать только в режиме Cooked Reading;
1 – дополнительно может использоваться режим Raw Reading
3 0 – устройстов может только читать компакт-диски;
1 – устройство может читать и записывать компакт-диски
4 0 – устройство может работать только с дорожками, содержащими данные:
1 – дополнительно устройство может проигрывать звуковые дорожки или дорожки с видео
5 0 – устройство не способно работать с чередованием данных;
1 – устройство работает с чередованием данных в стандарте ISO‑9660
6 Зарезервировано
7 0 – предварительная выборка не применяется;
1 – устройство способно выполнять запросы на предварительную выборку
8 0 – устройство не работает со звуковыми каналами;
1 – устройство может работать со звуковыми каналами
9 0 – устройство может работать в режиме адресации HSG;
1 – дополнительно можно использовать режим адресации Redbook
10 Зарезервировано
11 0 – в устройстве находится компакт-диск;
1 – в устройстве нет компакт-диска
12 0 – устрйство не работает с подканалами R-W;
1 – указанные каналы используются
13-31 Зарезервировано и равно нулю

Немного о режимах Cooked и Raw.

По умолчанию устройство чтения CD-ROM работает в режиме Cooked. Этот режим предполагает аппаратную обработку циклической контрольной суммы. Размер блока данных равен 2048 байт.

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

Определение размера сектора

// ---------------
// Код функции 7
// ---------------
#pragma pack(1)
typedef struct _SectorSize
{             
  BYTE   bFunctionCode;
  BYTE   bReadMode;
  DWORD  dwSectorSize;
} SectorSize;
Поле Описание
bFunctionCode Код функции
bReadMode Режим чтения
dwSectorSize Размер сектора. Для режима Cooked Reading он равен 2048 байт, для режима Raw Reading – 2352 байт

Определение размера тома

// ---------------
// Код функции 8
// ---------------
#pragma pack(1)
typedef struct _VolumeSize
{             
  BYTE   bFunctionCode;
  DWORD  dwVolumeSize;
} VolumeSize;
Поле Описание
bFunctionCode Код функции
dwVolumeSize Размер тома

Функция возвращает адрес дорожки Lead-out, преобразованное в численное значение по следующей формуле:

frame + (sec * 75) + (min * 60 * 75)

Это значение является адресом первого сектора, который располагается за самым последним адресуемым сектором диска.

Проверка замены носителя данных

// ---------------
// Код функции 9
// ---------------
#pragma pack(1)
typedef struct _MediaChange
{             
  BYTE   bFunctionCode;
  BYTE   bMedia;
} MediaChange;
Поле Описание
bFunctionCode Код функции
bMedia 1 – носитель не заменялся;
0 – не известно, выполнялась замена носителя данных, или нет;
0FFh – носитель заменялся

Получение информации о компакт-диске

// ---------------
// Код функции 10
// ---------------
#pragma pack(1)
typedef struct _DiskInfo
{
  BYTE   bFunctionCode;
  BYTE   bLowest;
  BYTE   bHighest;
  DWORD  dwTotal;
} DiskInfo;
Поле Описание
bFunctionCode Код функции
bLowest Номер первой дорожки
bHighest Номер последней дорожки
dwTotal Адрес дорожки Lead-out

Получение информации о дорожке компакт-диска

// ---------------
// Код функции 11
// ---------------
#pragma pack(1)
typedef struct _TrackInfo
{
  BYTE   bFunctionCode;
  BYTE   bTrack;
  DWORD  dwLoc;
  BYTE   bInfo;
} TrackInfo;
Поле Описание
bFunctionCode Код функции
bTrack Номер дорожки
dwLoc Первый сектор дорожки
bInfo Формат дорожки

Старшая тетрада формата дорожки имеет следующий формат:

Значение Описание
00*0 Два звуковых канала без предискажений
00*1 Два звуковых канала с предискажениями
10*1 Четыре звуковых канала без предискажений
10*0 Четыре звуковых канала с предискажениями
01*0 Дорожка с данными
01*1 Зарезервировано
11** Зарезервировано
**0* Копирование дорожки запрещено
**1* Копирование дорожки разрешено

Состояние бита, отмеченного символом *, значения не имеет.

Младшая тетрада формата дорожки содержит тип режима ADR, описанного в Redbook.

Получение информации о канале Q

// ---------------
// Код функции 12
// ---------------
#pragma pack(1)
typedef struct _QInfo
{
  BYTE   bFunctionCode;
  BYTE   bCtrlAndARD;
  BYTE   bTrackNumb;
  BYTE   bIndex;
  BYTE   bMin;
  BYTE   bSec;
  BYTE   bFrame;
  BYTE   bRunningTime;
  BYTE   bAminOrPmin;
  BYTE   bAsecOrPsec;
  BYTE   bAframeOrPframe;
} QInfo;
Поле Описание
bFunctionCode Код функции
bCtrlAndARD Формат дорожки
bTrackNumb Номер дорожки
bIndex Текущее время, которое прошло с начала проигрывания дорожки
bMin Компонента MIN адреса дорожки
bSec Компонента SEC адреса дорожки
bFrame Компонента FRAME адреса дорожки
bRunningTime Общее время проигрывания диска
bAminOrPmin Значение AMIN или PMIN
bAsecOrPsec Значение ASEC или PSEC
bAframeOrPframe Значение AFRAME или PFRAME

Получение информации о подканале

// ---------------
// Код функции 13
// ---------------
#pragma pack(1)
typedef struct _QInfo
{
  BYTE   bFunctionCode;
  DWORD  dwStartSector;
  DWORD  dwTransferAddress;
  DWORD  dwNumberOfSectors;
} QInfo;
Поле Описание
bFunctionCode Код функции
dwStartSector Адрес начального сектора
dwTransferAddress Адрес в оперативной памяти, по которому будет скопирована информация из подканала
dwNumberOfSectors Количество секторов, которые необходимо скопировать

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

// ---------------
// Код функции 14
// ---------------
#pragma pack(1)
typedef struct _UPCCode
{
  BYTE   bFunctionCode;
  BYTE   bCtrlAndARD;
  BYTE   bUPCCode[7];
  BYTE   bZero;
  BYTE   bAFrame;
} UPCCode;
Поле Описание
bFunctionCode Код функции
bCtrlAndARD Формат дорожек
bUPCCode Штрих-код
bZero Равно 0
bAFrame Значение AFRAME

Сброс входных буферов

Команда освобождает все входные буферы и отменяет все запущенные команды.

Формат заголовка запроса:

// ---------------
// Код команды 7
// ---------------
#pragma pack(1)
typedef struct _Flush
{             
  ReqHdr rh;
} Flush;

Заполнение полей заголовка запроса:

Поле Описание
rh.wStatus После вызова драйвера содержит слово состояния

Запись IOCTL Output

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

Формат заголовка запроса:

// ---------------
// Код команды 12
// ---------------
#pragma pack(1)
typedef struct _IOCTL_Output
{             
  ReqHdr rh;
  BYTE   bMediaDescriptor;
  DWORD  lpTransferAddress;
  WORD   wDataSize;
  WORD   wStartSector;
  DWORD  lpVolID;
} IOCTL_Output;       

Заполнение полей заголовка запроса:

Поле Описание
rh.wStatus После вызова драйвера содержит слово состояния
bMediaDescriptor Байт описания среды носителя данных, должен быть равен нулю
lpTransferAddress Адрес буфера
wDataSize Размер буфера
wStartSector Номер начального сектора, должен быть равен нулю
lpVolID Указатель на идентификатор тома, если при выполнении команды возникла ошибка с кодом 0Fh

Рассмотрим форматы заголовков различных функций, выполняемых в рамках команды IOCTL Output.

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

// ---------------
// Код функции 0
// ---------------
#pragma pack(1)
typedef struct _EjectDisk
{
  BYTE   bFunctionCode;
} EjectDisk;
Поле Описание
bFunctionCode Код функции

Блокирование и разблокирование компакт-диска в устройстве

// ---------------
// Код функции 1
// ---------------
#pragma pack(1)
typedef struct _LockDisk
{
  BYTE   bFunctionCode;
  BYTE   bLock;
} LockDisk;
Поле Описание
bFunctionCode Код функции
bLock 1 – блокирование компакт-диска;
0 – разблокирование компакт-диска

Сброс устройства чтения CD-ROM

// ---------------
// Код функции 2
// ---------------
#pragma pack(1)
typedef struct _ResetDrive
{
  BYTE   bFunctionCode;
} ResetDrive;
Поле Описание
bFunctionCode Код функции

Управление звуковыми каналами

// ---------------
// Код функции 3
// ---------------
#pragma pack(1)
typedef struct _ChanControl
{             
  BYTE   bFunctionCode;
  BYTE   bInpChannel0;
  BYTE   bVolControl0;
  BYTE   bInpChannel1;
  BYTE   bVolControl1;
  BYTE   bInpChannel2;
  BYTE   bVolControl2;
  BYTE   bInpChannel3;
  BYTE   bVolControl3;
} ChanControl;
Поле Описание
bFunctionCode Код функции
bInpChannel0 Номер входного канала, присвоенного выходному каналу 0
bVolControl0 Уровень громкости для канала 0
bInpChannel1 Номер входного канала, присвоенного выходному каналу 1
bVolControl1 Уровень громкости для канала 1
bInpChannel2 Номер входного канала, присвоенного выходному каналу 2
bVolControl2 Уровень громкости для канала 2
bInpChannel3 Номер входного канала, присвоенного выходному каналу 3
bVolControl3 Уровень громкости для канала 3

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

// ---------------
// Код функции 4
// ---------------
#pragma pack(1)
typedef struct _DriveControlBytes
{             
  BYTE bFunctionCode;
  BYTE bWriteBuff[...]; // размер зависит от устройства
} DriveControlBytes;
Поле Описание
bFunctionCode Код функции
bWriteBuff Буфер с управляющими данными, которые будут записаны в устройство чтения CD-ROM. Формат зависит от типа устройства

Закрывание приемного устройства для компакт-диска

// ---------------
// Код функции 5
// ---------------
#pragma pack(1)
typedef struct _CloseTray
{
  BYTE   bFunctionCode;
} CloseTray;
Поле Описание
bFunctionCode Код функции

Открывание устройства

Команда открывает устройство чтения CD-ROM, сообщая драйверу о том, что данное устройство будет использовано еще одной программой.

Формат заголовка запроса:

// ---------------
// Код команды 13
// ---------------
#pragma pack(1)
typedef struct _RsumePlay
{             
  ReqHdr rh;
} RsumePlay;

Заполнение полей заголовка запроса:

Поле Описание
rh.wStatus После вызова драйвера содержит слово состояния

Закрывание устройства

Команда закрывает устройство чтения CD-ROM, открытое предыдущей командой.

Формат заголовка запроса:

// ---------------
// Код команды 14
// ---------------
#pragma pack(1)
typedef struct _RsumePlay
{             
  ReqHdr rh;
} RsumePlay;

Заполнение полей заголовка запроса:

Поле Описание
rh.wStatus После вызова драйвера содержит слово состояния

Чтение длинное

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

Формат заголовка запроса:

// ---------------
// Код команды 128
// ---------------
#pragma pack(1)
typedef struct _ReadLong
{             
  ReqHdr rh;
  BYTE   bAddressMode;
  DWORD  lpTransferAddress;
  WORD   wDataSize;
  WORD   wStartSector;
  BYTE   bDataReadMode;
  BYTE   bInterleaveSise;
  BYTE   bInterleaveSkip;
} ReadLong;       

Заполнение полей заголовка запроса:

Поле Описание
rh.wStatus После вызова драйвера содержит слово состояния
bAddressMode Режим адресации:
0 – режим HSG (по умолчанию);
1 – режим Readbook;
2-255 – зарезервировано
lpTransferAddress Адрес буфера
wDataSize Размер буфера
wStartSector Номер начального сектора, должен быть равен нулю
bDataReadMode Режим чтения данных:
0 – режим Cooked;
1 – режим Raw;
2-255 – зарезервировано
bInterleaveSize Количество блоков или секторов, которые записаны последовательно друг за другом
bInterleaveSkip Фактор пропуска при чередовании: количество последовательно расположенных блоков или секторов, которые разделяют фрагменты файла с чередованием

Чтение длинное с предварительной выборкой

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

Формат заголовка запроса:

// ---------------
// Код команды 130
// ---------------
#pragma pack(1)
typedef struct _ReadLongPrefetch
{             
  ReqHdr rh;
  BYTE   bAddressMode;
  DWORD  lpTransferAddress;
  WORD   wDataSize;
  WORD   wStartSector;
  BYTE   bDataReadMode;
  BYTE   bInterleaveSise;
  BYTE   bInterleaveSkip;
} ReadLongPrefetch;       

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

Поиск

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

Формат заголовка запроса:

// ---------------
// Код команды 131
// ---------------
#pragma pack(1)
typedef struct _Seek
{             
  ReqHdr rh;
  BYTE   bAddressMode;
  DWORD  lpTransferAddress;
  WORD   wDataSize;
  WORD   wStartSector;
} Seek;       

Заполнение полей заголовка запроса:

Поле Описание
rh.wStatus После вызова драйвера содержит слово состояния
bAddressMode Режим адресации:
0 – режим HSG (по умолчанию);
1 – режим Readbook;
2-255 – зарезервировано
lpTransferAddress Адрес буфера
wDataSize Размер буфера
wStartSector Номер начального сектора, должен быть равен нулю

Проигрывание звуковой дорожки

Команда запускает проигрывание звуковой дорожки начиная с указанного сектора.

Формат заголовка запроса:

// ---------------
// Код команды 132
// ---------------
#pragma pack(1)
typedef struct _Play
{             
  ReqHdr rh;
  BYTE   bAddressMode;
  WORD   wStartSector;
  WORD   wNumberOfSectors;
} Play;

Заполнение полей заголовка запроса:

Поле Описание
rh.wStatus После вызова драйвера содержит слово состояния
bAddressMode Режим адресации:
0 – режим HSG (по умолчанию);
1 – режим Readbook;
2-255 – зарезервировано
wStartSector Номер начального сектора
wNumberOfSectors Количество секторов, которые нужно проиграть

Остановка проигрывания звуковой дорожки

Команда останавливает проигрывание звуковой дорожки.

Формат заголовка запроса:

// ---------------
// Код команды 133
// ---------------
#pragma pack(1)
typedef struct _StopPlay
{             
  ReqHdr rh;
} StopPlay;

Заполнение полей заголовка запроса:

Поле Описание
rh.wStatus После вызова драйвера содержит слово состояния

Возобновление проигрывания звуковой дорожки

Команда останавливает проигрывание звуковой дорожки.

Формат заголовка запроса:

// ---------------
// Код команды 136
// ---------------
#pragma pack(1)
typedef struct _RsumePlay
{             
  ReqHdr rh;
} RsumePlay;

Заполнение полей заголовка запроса:

Поле Описание
rh.wStatus После вызова драйвера содержит слово состояния

Программа CDINFO

Программа CDINFO вызывает как функции расширения MSCDEX.EXE, так и драйвер устройства чтения CD-ROM, получая различную информацию как о расширении, так и о компакт-диске.

Вот пример листинга, полученного при работе программы CDINFO на виртуальной машине DOS в среде Microsoft Windows 95:

CDINFO, (c) A. Frolov, 1997

MSCDEX version: 2.95
Found 1 CD Unit, start unit: G
CD-ROM letters: G

Status of CD Drive G: 00000390
VolumeSize: 29460 blocks
Tracks: (1 - 1) 8252
track 1: location: 512, info: 41 * digital, copy prohibited *

Здесь в компьютере установлено только одно устройство чтения CD‑ROM, в котором находился один цифровой компакт-диск.

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

CDINFO, (c) A. Frolov, 1997

MSCDEX version: 2.21
Found 2 CD Unit, start unit: H
CD-ROM letters: H I 

Status of CD Drive H: 00000390
VolumeSize: 302375 blocks
Tracks: (1 - 15) 2866
track 1: location:  512,     info: 01 * audio, copy prohibited *
track 2: location:  79922,   info: 01 * audio, copy prohibited *
track 3: location:  466492,  info: 01 * audio, copy prohibited *
track 4: location:  788490,  info: 01 * audio, copy prohibited *
track 5: location:  1120281, info: 01 * audio, copy prohibited *
track 6: location:  1453334, info: 01 * audio, copy prohibited *
track 7: location:  1778979, info: 01 * audio, copy prohibited *
track 8: location:  2042649, info: 01 * audio, copy prohibited *
track 9: location:  2363412, info: 01 * audio, copy prohibited *
track 10: location: 2565639, info: 01 * audio, copy prohibited *
track 11: location: 2823479, info: 01 * audio, copy prohibited *
track 12: location: 3094814, info: 01 * audio, copy prohibited *
track 13: location: 3419404, info: 01 * audio, copy prohibited *
track 14: location: 3740478, info: 01 * audio, copy prohibited *
track 15: location: 4130306, info: 01 * audio, copy prohibited *

Status of CD Drive I: 00000390
VolumeSize: 278505 blocks
Tracks: (1 - 4) 13598
track 1: location: 512,     info: 41 * digital, copy prohibited *
track 2: location: 3282733, info: 01 * audio,   copy prohibited *
track 3: location: 3608079, info: 01 * audio,   copy prohibited *
track 4: location: 3801921, info: 01 * audio,   copy prohibited *

Заметим, что в среде виртуальной машины операционной системы Microsoft Windows NT эта программа показывает неверные результаты. Скорее всего это происходит из-за неправильной работы эмулятора расширения MSCDEX.

Исходный текст программы CDINFO вы найдете в листинге 9.1.

Листинг 9.1. Файл cdinfo\cdinfo.с

// =====================================================
// Прсмотр различной информации об устройствах
// чтения CD-ROM и компакт-дисках 
// с помощью интерфейса MSCDEX и обращением к драйверу
// устройства чтения CD-ROM
//
// (C) Фролов А.В, 1997
//
// E-mail: frolov@glas.apc.org
// WWW:    http://www.glasnet.ru/~frolov
//            или
//         http://www.dials.ccas.ru/frolov
// =====================================================
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <dos.h>
#include <memory.h>
  
typedef unsigned char BYTE;
typedef unsigned int  WORD;
typedef unsigned long DWORD;

// Необходимо для обеспечения выравнивания 
// полей структур на границу байта
#pragma pack(1)

// Заголовок запроса для обращения к драйверу
typedef struct _ReqHdr
{
  BYTE bSize;
  BYTE bSubUnit;
  BYTE bCmd;
  WORD wStatus;
  BYTE bReserved[8];    
} ReqHdr;
  
// Запрос IOCTL Input
typedef struct _IOCTL_Input
{             
  ReqHdr rh;
  BYTE   bMediaDescriptor;
  DWORD  lpTransferAddress;
  WORD   wDataSize;
  WORD   wStartSector;
  DWORD  lpVolID;
} IOCTL_Input;       

// Запрос на получение информации о компакт-диске
typedef struct _DiskInfo
{
  BYTE bControl;
  BYTE bLowest;
  BYTE bHighest;
  DWORD dwTotal;
} DiskInfo;

// Запрос на получение информации 
// о дорожке компакт-диска
typedef struct _TrackInfo
{
  BYTE  bControl;
  BYTE  bTrack;
  DWORD dwLoc;
  BYTE  bInfo;
} TrackInfo;

// Запрос для определения текущего состояния
// устройства чтения CD-ROM
typedef struct _DeviceStatus
{
  BYTE bControl;
  DWORD dwParam;
} DeviceStatus;

// Запрос для определения общего количества
// секторов на компакт-диске
typedef struct _VolumeSize
{
  BYTE bControl;
  DWORD dwVolumeSize;
} VolumeSize;

#pragma pack()

// Прототипы функций
void GetCDLetters(BYTE *bLetters);
void CallCDDriver(void *rh, int nCDUnit);
int GetDiskInfo(int nCDUnit);
int GetDeviceStatus(int nCDUnit);
int GetVolumeSize(int nCDUnit);
int GetTrackInfo(int iTrack, int nCDUnit);
void delay(int ms);

// Регистры для вызова функции int86
union REGS rg;                      

// Количество установленных устройств чтения CD-ROM
int nCDUnits;

// Номер первого устройства чтения CD-ROM
int nCDStartUnit;

// Слово состояния после вызова драйвера CD-ROM
int iStatus;

// Информация о компакт-диске
DiskInfo     di;             

// Состояние устройства чтения CD-ROM
DeviceStatus ds;                     

// Объем компакт-диска
VolumeSize   vs;      

// Информация о дорожке
TrackInfo    ti;

BYTE bLetters[26];

// ---------------------------------------------------
// main
// Точка входа в программу
// ---------------------------------------------------
int main() 
{
  int iRetry;
  int i, j;
  
  printf("CDINFO, (c) A. Frolov, 1997\n\n");
  
  // Проверяем, установлена ли программа MSCDEX
  rg.x.ax = 0x1500;
  rg.x.bx = 0;
  int86(0x2f, &rg, &rg);

  if(rg.x.bx == 0)
  {
    printf("MSCDEX is not installed\n");
    return -1;
  }
  
  else        
  {
    // Сохраняем общее количество устройств чтения CD-ROM
    nCDUnits = rg.x.bx;
    
    // Сохраняем номер первого такого устройства
    nCDStartUnit = rg.x.cx;

    // Определяем и отображаем вресию MSCDEX
    rg.x.ax = 0x150c;
    int86(0x2f, &rg, &rg);
    printf("MSCDEX version: %d.%d\n", rg.h.bh, rg.h.bl);

    // Отображаем количество найденных устройств чтения
    // CD-ROM и номер первого устройства
    printf("Found %d CD Unit, start unit: %c\n",
      nCDUnits, nCDStartUnit + 'A');
  }

  // Получаем массив номеров устройств чтения CD-ROM
  GetCDLetters(bLetters);
  
  // Отображаем обозначения всех устройств CD-ROM
  printf("CD-ROM letters: ");
  for(i = 0; i < nCDUnits; i++)
  {
    printf("%c ", bLetters[i] + 'A');  
  }
  printf("\n");

  // Цикл по всем устройствам чтения CD-ROM
  for(i = 0; i < nCDUnits; i++)
  {
    // Определяем и отображаем состояние устройства
    iStatus = GetDeviceStatus(bLetters[i]);
    if(iStatus != 0x0100)
    {
      printf("GetDeviceStatus status: %04.4X\n", iStatus);
      printf("GetDeviceStatus failed\n");
      exit(1);               
    }
    printf("\nStatus of CD Drive %c: %08.8X\n", 
      bLetters[i] + 'A', ds.dwParam);

    // Определяем и отображаем объем устройства
    iStatus = GetVolumeSize(bLetters[i]);
    if(iStatus != 0x0100)
    {
      printf("GetVolumeSize status: %04.4X\n", iStatus);
      printf("GetVolumeSize failed\n");
    }
    else
      printf("VolumeSize: %ld blocks\n", vs.dwVolumeSize);

    // Определяем и отображаем информацию о
    // компакт-диске
    iStatus = GetDiskInfo(bLetters[i]);
    
    // Делаем три попытки получения информации
    iRetry = 0;
    while(iStatus != 0x0100)
    {
      printf("GetDiskInfo status: %04.4X\n", iStatus);
    
      // Задержка длительностью 1 с
      delay(1000);
    
      iRetry++;
    
      if(iRetry == 3)
      {
        printf("GetDiskInfo failed\n");
        break;
      }

      iStatus = GetDiskInfo(bLetters[i]);
    }
  
    // Если удалось получить информацию о компакт-диске,
    // исследуем его дорожки
    if(iRetry != 3)
    {
      // Выводим номера дорожек
      printf("Tracks: (%d - %d) %d\n", 
        di.bLowest, di.bHighest, di.dwTotal);

      // Цикл по всем дорожкам диска
      for(j = di.bLowest; j <= di.bHighest; j++)
      {
        // Получаем информацию о дорожке и отображаем ее
        GetTrackInfo(j, bLetters[i]);
        printf("track %d: location: %ld, info: %02.2X",
          j, ti.dwLoc, ti.bInfo);
      
        // Определяем тип дорожки - звуковая дорожка
        // или дорожка с данными
        if(ti.bInfo & 0x40)
          printf(" * digital");
        else
          printf(" * audio");

        // Определяем, разрашено ли копирование дорожки
        if(ti.bInfo & 0x20)
          printf(", copy permitted *\n");
        else
          printf(", copy prohibited *\n");
      }
    }  
  }
  return 0;
}

// ---------------------------------------------------
// GetDiskInfo
// Получение информации о компакт-диске
// ---------------------------------------------------
int GetDiskInfo(int nCDUnit)
{
  // Заголовок команды IOCTL Input
  IOCTL_Input cmd;
  
  // Очищаем заголовок
  memset(&cmd, 0, sizeof(IOCTL_Input ));
  
  // Заполняем заголовок
  cmd.rh.bSize    = 26;
  cmd.rh.bSubUnit = 0;
  cmd.rh.bCmd     = 3;
  
  cmd.bMediaDescriptor  = 0;
  cmd.lpTransferAddress = (DWORD)(void far *)&di;
  cmd.wDataSize         = 7;
  cmd.wStartSector      = 0;
  cmd.lpVolID           = (DWORD)(void far *)NULL;
  
  di.bControl = 10;
  
  // Вызываем драйвер
  CallCDDriver(&cmd, nCDUnit);
  
  return cmd.rh.wStatus;
}  

// ---------------------------------------------------
// GetTrackInfo
// Получение информации о дорожке компакт-диска
// ---------------------------------------------------
int GetTrackInfo(int iTrack, int nCDUnit)
{
  IOCTL_Input cmd;
  
  memset(&cmd, 0, sizeof(IOCTL_Input ));
  
  cmd.rh.bSize    = 26;
  cmd.rh.bSubUnit = 0;
  cmd.rh.bCmd     = 3;
  
  cmd.bMediaDescriptor  = 0;
  cmd.lpTransferAddress = (DWORD)(void far *)&ti;
  cmd.wDataSize         = 7;
  cmd.wStartSector      = 0;
  cmd.lpVolID           = (DWORD)(void far *)NULL;
  
  ti.bControl = 11;
  ti.bTrack = iTrack;
  
  CallCDDriver(&cmd, nCDUnit);
  
  return cmd.rh.wStatus;
}  

// ---------------------------------------------------
// GetDeviceStatus
// Определение состояния устройства чтения CD-ROM
// ---------------------------------------------------
int GetDeviceStatus(int nCDUnit)
{
  IOCTL_Input cmd;
  
  memset(&cmd, 0, sizeof(IOCTL_Input ));
  
  cmd.rh.bSize    = 26;
  cmd.rh.bSubUnit = 0;
  cmd.rh.bCmd     = 3;
  
  cmd.bMediaDescriptor  = 0;
  cmd.lpTransferAddress = (DWORD)(void far *)&ds;
  cmd.wDataSize         = 5;
  cmd.wStartSector      = 0;
  cmd.lpVolID           = (DWORD)(void far *)NULL;
  
  ds.bControl = 6;
  
  CallCDDriver(&cmd, nCDUnit);
  return cmd.rh.wStatus;
}  

// ---------------------------------------------------
// GetVolumeSize
// Определение объема компакт-диска
// ---------------------------------------------------
int GetVolumeSize(int nCDUnit)
{
  IOCTL_Input cmd;
  
  memset(&cmd, 0, sizeof(IOCTL_Input ));
  
  cmd.rh.bSize    = 26;
  cmd.rh.bSubUnit = 0;
  cmd.rh.bCmd     = 3;
  
  cmd.bMediaDescriptor  = 0;
  cmd.lpTransferAddress = (DWORD)(void far *)&vs;
  cmd.wDataSize         = 5;
  cmd.wStartSector      = 0;
  cmd.lpVolID           = (DWORD)(void far *)NULL;
  
  vs.bControl = 8;
  
  CallCDDriver(&cmd, nCDUnit);
  return cmd.rh.wStatus;
}  

// ---------------------------------------------------
// CallCDDriver
// Вызов драйвера компакт-диска
// ---------------------------------------------------
void CallCDDriver(void *rh, int nCDUnit)
{
  static union REGS rg;
  static struct SREGS srg;
  
  segread(&srg);
  rg.x.ax = 0x1510;
  rg.x.cx = nCDUnit;
  rg.x.bx = FP_OFF(rh);  
  
  int86x(0x2f, &rg, &rg, &srg);
}

// ---------------------------------------------------
// GetCDLetters
// Заполнение массива номерами установленных 
// в системе устройств чтения компакт-диска
// ---------------------------------------------------
void GetCDLetters(BYTE *bLetters)
{
  static union REGS rg;
  static struct SREGS srg;
  
  segread(&srg);
  rg.x.ax = 0x150d;
  rg.x.bx = FP_OFF(bLetters);  
  
  int86x(0x2f, &rg, &rg, &srg);
}

// ---------------------------------------------------
// delay
// Формирование задержки по таймеру
// ---------------------------------------------------
void delay(int ms) 
{
  int ticks;    
  
  ticks = ms / 55;
  _asm 
  {
    push si

    mov  si, ticks
    mov  ah, 0
    int  1ah

    mov  bx, dx
    add  bx, si

delay_loop:

    int  1ah
    cmp  dx, bx
    jne  delay_loop

    pop  si
  }
}

Программа CDPLAY

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

Ниже мы привели пример запуска программы, передав ей адрес 512:

CDPLAY, (c) A. Frolov, 1997

Track Red book: 512
Track Sierra: 0
MSCDEX version: 2.95
Found 1 CD Unit, start unit: G
CD-ROM letters: G
Started. Press any key to stop and eject CD

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

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

Исходный текст программы CDPLAY приведен в листинге 9.2.

Листинг 9.2. Файл cdplay\cdplay.с

// =====================================================
// Проигрывание звуковых компакт-дисков
//
// (C) Фролов А.В, 1997
//
// E-mail: frolov@glas.apc.org
// WWW:    http://www.glasnet.ru/~frolov
//            или
//         http://www.dials.ccas.ru/frolov
// =====================================================
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <dos.h>
#include <memory.h>
  
typedef unsigned char BYTE;
typedef unsigned int  WORD;
typedef unsigned long DWORD;

// Необходимо для обеспечения выравнивания 
// полей структур на границу байта
#pragma pack(1)

// Заголовок запроса для обращения к драйверу
typedef struct _ReqHdr
{
  BYTE bSize;
  BYTE bSubUnit;
  BYTE bCmd;
  WORD wStatus;
  BYTE bReserved[8];    
} ReqHdr;
  
typedef struct _PlayAudio
{
  ReqHdr rh;
  BYTE bMode;
  DWORD dwLoc;
  DWORD dwSectorNum;
} PlayAudio;

// Запрос IOCTL Output
typedef struct _IOCTL_Output
{             
  ReqHdr rh;
  BYTE   bMediaDescriptor;
  DWORD  lpTransferAddress;
  WORD   wDataSize;
  WORD   wStartSector;
  DWORD  lpVolID;
} IOCTL_Output;       

// Запрос на извлечение компакт-диска
typedef struct _EjectDisk
{
  BYTE  bControl;
} EjectDisk;

#pragma pack()

// Прототипы функций
void GetCDLetters(BYTE *bLetters);
void CallCDDriver(void *rh, int nCDUnit);
int PlayAudioTrack(DWORD dwLoc, DWORD dwSectorNum, int nCDUnit);
int StopAudio(int nCDUnit);
int DeviceOpen(int nCDUnit);
int DeviceClose(int nCDUnit);
int EjectCD(int nCDUnit);
DWORD Red2Sierra(DWORD dwRedLoc);

// Регистры для вызова функции int86
union REGS rg;                      

// Количество установленных устройств чтения CD-ROM
int nCDUnits;

// Номер первого устройства чтения CD-ROM
int nCDStartUnit;

// Слово состояния после вызова драйвера CD-ROM
int iStatus;
                               
// Массив номеров установленных устройств CD-ROM
BYTE bLetters[26];

// ---------------------------------------------------
// main
// Точка входа в программу
// ---------------------------------------------------
int main(int argc, char *argv[]) 
{
  int i;
  
  DWORD dwStartTrack;
  
  printf("CDPLAY, (c) A. Frolov, 1997\n\n");
  
  dwStartTrack = 1;
  if(argc == 2)
  {
    dwStartTrack = atol(argv[1]);
    printf("Track Red book: %ld\n", dwStartTrack);
    
  }
  else
  {
    printf("Usage: CDPLAY <Red book sector address>\n");
    return -1;
  }
  
  // Преобразование адреса сектора в формат Sierra
  dwStartTrack = Red2Sierra(dwStartTrack);
  printf("Track Sierra: %ld\n", dwStartTrack);
  
  // Проверяем, установлена ли программа MSCDEX
  rg.x.ax = 0x1500;
  rg.x.bx = 0;
  int86(0x2f, &rg, &rg);

  if(rg.x.bx == 0)
  {
    printf("MSCDEX is not installed\n");
    return -1;
  }
  
  else        
  {
    // Сохраняем общее количество устройств чтения CD-ROM
    nCDUnits = rg.x.bx;
    
    // Сохраняем номер первого такого устройства
    nCDStartUnit = rg.x.cx;

    // Определяем и отображаем вресию MSCDEX
    rg.x.ax = 0x150c;
    int86(0x2f, &rg, &rg);
    printf("MSCDEX version: %d.%d\n", rg.h.bh, rg.h.bl);

    // Отображаем количество найденных устройств чтения
    // CD-ROM и номер первого устройства
    printf("Found %d CD Unit, start unit: %c\n",
      nCDUnits, nCDStartUnit + 'A');
  }

  // Получаем массив номеров устройств чтения CD-ROM
  GetCDLetters(bLetters);
  
  // Отображаем обозначения всех устройств CD-ROM
  printf("CD-ROM letters: ");
  for(i = 0; i < nCDUnits; i++)
  {
    printf("%c ", bLetters[i] + 'A');  
  }
  printf("\n");

  // Открываем устройство
  iStatus = DeviceOpen(bLetters[0]);
  if(iStatus & 0x8000)
  {
    printf("DeviceOpen status: %04.4X\n", iStatus);
    return -1;
  }

  // Запускаем проигрывание
  iStatus = 
    PlayAudioTrack(dwStartTrack, 0xffffffff, bLetters[0]);
  if(iStatus & 0x8000)
  {
    printf("PlayAudioTrack status: %04.4X\n", iStatus);
    return -1;
  }
  printf("Started. Press any key to stop and eject CD\n");
  
  // Ожидаем, пока пользователь не нажмет клавишу
  getch();

  // Останавливаем проигрывание
  iStatus = StopAudio(bLetters[0]);
  if(iStatus & 0x8000)
  {
    printf("StopAudio status: %04.4X\n", iStatus);
    return -1;
  }

  // Извлекаем диск
  iStatus = EjectCD(bLetters[0]);
  if(iStatus & 0x8000)
  {
    printf("EjectCD status: %04.4X\n", iStatus);
    return -1;
  }

  // Закрываем устройство
  iStatus = DeviceClose(bLetters[0]);
  if(iStatus & 0x8000)
  {
    printf("DeviceClose status: %04.4X\n", iStatus);
    return -1;
  }
  return 0;
}

// ---------------------------------------------------
// PlayAudioTrack
// Запуск проигрывания звукового компакт-диска
// ---------------------------------------------------
int PlayAudioTrack(DWORD dwLoc, DWORD dwSectorNum, int nCDUnit)
{
  PlayAudio cmd;
  
  memset(&cmd, 0, sizeof(PlayAudio));
  
  cmd.rh.bSize    = 22;
  cmd.rh.bSubUnit = 0;
  cmd.rh.bCmd     = 132;
  
  cmd.bMode  = 0;
  cmd.dwLoc  = dwLoc;
  cmd.dwSectorNum  = dwSectorNum;
  
  CallCDDriver(&cmd, nCDUnit);
  return cmd.rh.wStatus;
}  

// ---------------------------------------------------
// StopAudio
// Остановка проигрывания звукового компакт-диска
// ---------------------------------------------------
int StopAudio(int nCDUnit)
{
  ReqHdr cmd;
  
  memset(&cmd, 0, sizeof(ReqHdr));
  
  cmd.bSize    = 13;
  cmd.bSubUnit = 0;
  cmd.bCmd     = 133;
  
  CallCDDriver(&cmd, nCDUnit);
  return (cmd.wStatus);
}  

// ---------------------------------------------------
// DeviceOpen
// Открывание устройства
// ---------------------------------------------------
int DeviceOpen(int nCDUnit)
{
  ReqHdr cmd;
  
  memset(&cmd, 0, sizeof(ReqHdr));
  
  cmd.bSize    = 13;
  cmd.bSubUnit = 0;
  cmd.bCmd     = 13;
  
  CallCDDriver(&cmd, nCDUnit);
  return (cmd.wStatus);
}  


// ---------------------------------------------------
// DeviceClose
// Закрывание устройства
// ---------------------------------------------------
int DeviceClose(int nCDUnit)
{
  ReqHdr cmd;
  
  memset(&cmd, 0, sizeof(ReqHdr));
  
  cmd.bSize    = 13;
  cmd.bSubUnit = 0;
  cmd.bCmd     = 14;
  
  CallCDDriver(&cmd, nCDUnit);
  return (cmd.wStatus);
}  

// ---------------------------------------------------
// EjectCD
// Извлечение компакт-диска
// ---------------------------------------------------
int EjectCD(int nCDUnit)
{
  IOCTL_Output cmd;
  EjectDisk ed;
  
  memset(&cmd, 0, sizeof(IOCTL_Output));
  
  cmd.rh.bSize    = 14;
  cmd.rh.bSubUnit = 0;
  cmd.rh.bCmd     = 12;
  
  cmd.bMediaDescriptor  = 0;
  cmd.lpTransferAddress = (DWORD)(void far *)&ed;
  cmd.wDataSize         = 1;
  cmd.wStartSector      = 0;
  cmd.lpVolID           = (DWORD)(void far *)NULL;
  
  ed.bControl = 0;
  
  CallCDDriver(&cmd, nCDUnit);
  return cmd.rh.wStatus;
}  

// ---------------------------------------------------
// CallCDDriver
// Вызов драйвера компакт-диска
// ---------------------------------------------------
void CallCDDriver(void *rh, int nCDUnit)
{
  static union REGS rg;
  static struct SREGS srg;
  
  segread(&srg);
  rg.x.ax = 0x1510;
  rg.x.cx = nCDUnit;
  rg.x.bx = FP_OFF(rh);  
  
  int86x(0x2f, &rg, &rg, &srg);
}

// ---------------------------------------------------
// GetCDLetters
// Заполнение массива номерами установленных 
// в системе устройств чтения компакт-диска
// ---------------------------------------------------
void GetCDLetters(BYTE *bLetters)
{
  static union REGS rg;
  static struct SREGS srg;
  
  segread(&srg);
  rg.x.ax = 0x150d;
  rg.x.bx = FP_OFF(bLetters);  
  
  int86x(0x2f, &rg, &rg, &srg);
}

// ---------------------------------------------------
// Преобразование адреса дорожки из формата Red book
// в формат Sierra
// ---------------------------------------------------
DWORD Red2Sierra(DWORD dwRedLoc)
{
  BYTE bMin, bSec, bFrame;

  bMin   = (BYTE)((dwRedLoc >> 16) & 0xff);
  bSec   = (BYTE)((dwRedLoc >> 8) & 0xff);
  bFrame = (BYTE)(dwRedLoc & 0xff);
  
  return (DWORD)bMin * 75 * 60 + (DWORD)bSec * 75 + 
    (DWORD)bFrame - 150;
}