ВЛАДИМИР МЕШКОВ
Запись CD-R/RW-дисков в Linux
Часть 4
В статье рассматривается порядок записи информации на CD-R/RW-диски в режиме Session-at-once (SAO).
Работоспособность всех примеров программ была проверена для ОС Linux, ядро 2.4.28. В ядре включены режим SCSI-эмуляции для ATAPI-устройств и поддержка SCSI Generic-драйвера. Использовались следующие модели приводов:
- TEAC CD-W524E Rev.1.0E
 
- ASUS DRW-1604P Rev.1.09
 
- MITSUMI CR-48XATE Rev.1.0E
 
Особенности записи информации в режиме SAO
В отличие от ранее рассмотренного режима записи Track-at-once (TAO) (см. [1, 2]), в режиме Session-at-once (SAO) треки примыкают друг к другу вплотную, промежутки (паузы) между ними отсутствуют. Для управления процессом записи устройству передается специальная структура, содержащая информацию о расположении (координатах) треков, форматах блоков основного канала и субканалов трека. Эта структура, которая называется CUE SHEET, является своего рода картой, на основании которой устройство сформирует входную (Lead-In) и выходную (Lead-Out) области сессии (диска).
Структура CUE SHEET состоит из последовательности 8-байтовых блоков, самый первый блок описывает входную область сессии, последний – выходную область, остальные блоки содержат информацию о треках.
Блоки имеют следующий формат:
| 
 1 
 | 
 2 
 | 
 3 
 | 
 4 
 | 
 5 
 | 
 6 
 | 
 7 
 | 
 8 
 | 
| 
 CTL/ADR 
 | 
 TNO 
 | 
 INDEX 
 | 
 DATA FORM 
 | 
 SCMS 
 | 
 ABSOLUTE TIME 
 | 
Рисунок 1. Формат блока структуры CUE SHEET
Назначение полей:
- CTL/ADR – значение байта CTL/ADR-трека.
 
- TNO – номер трека.
 
- INDEX – индекс трека.
 
- DATA FORM – формат данных трека.
 
- SCMS – байт системы управления копированием.
 
- ABSOLUTE TIME – стартовые координаты трека в MSF-формате.
 
Формат байта CTL/ADR представлен на рис. 2. Старшие 4 разряда байта CTL/ADR занимает поле управления CTL, младшие 4 разряда – поле ADR.
| 
 7 
 | 
 6 
 | 
 5 
 | 
 4 
 | 
 3 
 | 
 2 
 | 
 1 
 | 
 0 
 | 
| 
 CTL Field 
 | 
 ADR Field 
 | 
Рисунок 2. Байт CTL/ADR
Поле CTL определяет тип информации, находящейся в треке, и может принимать следующие значения:
- 00xxb – 2 аудиоканала;
 
- 10xxb – 4 аудиоканала;
 
- 01xxb – трек данных;
 
- 11xxb – зарезервировано.
 
Поле ADR может принимать следующие значения:
- 01b – начальное время трека;
 
- 10b – код носителя по каталогу;
 
- 11b – код ISRC.
 
Для Lead-In и Lead-Out-областей значение байта CTL/ADR должно быть равно 0x01 (за исключением случая, когда поле Data Form = 0x41, однако он в этой статье не рассматривается). Байт TNO и INDEX для Lead-In-области принимают значение 0x00. Lead-Out-область, согласно спецификации, кодируется как трек под номером 0xAA, поле INDEX всегда равно 0x01.
| 
 7 
 | 
 6 
 | 
 5 
 | 
 4 
 | 
 3 
 | 
 2 
 | 
 1 
 | 
 0 
 | 
| 
 Data   Form of Sub-Channel 
 | 
 Data   Form of Main Data 
 | 
Рисунок 3. Структура поля DATA FORM
Поле Data Form of Main Data определяет формат блоков данных основного канала – CD-DA (аудиоданные), CD-ROM Mode 1, CD-ROM XA. При записи аудио это поле принимает значение 0x00, и устройству передается блок аудиоданных размером 2352 байта. При записи данных в формате Mode 1 (см. [1], таблица 1) поле Data Form of Main Data может принимать следующие значения:
- 0x10 – приложение передает устройству блок данных User Data размером 2048 байт, поля Sync/Header и EDC/ECC генерируются устройством;
 
- 0x11 – приложение передает устройству блок данных размером 2352 байт в составе полей User Data (2048 байт), Sync/Header (16 байт) и EDC/ECC (288 байт), однако устройство игнорирует содержимое полей Sync/Header и EDC/ECC и генерирует собственные значения.
 
Поле Data Form of Sub-Channel определяет формат данных субканалов, передаваемых устройству, но в рамках данной статьи структура этого поля не рассматривается. Согласно спецификации [3], данные субканалов P и Q, переданные в структуре CUE SHEET, устройство игнорирует и генерирует собственные значения. Формат поля Data Form of Sub-Channel определен в [3], табл. 516. Значение поля DATA FORM при записи данных в формате CD-ROM XA представлены в табл. 514 спецификации [3]. Для Lead-In и Lead-Out областей значение поля DATA FORM всегда равно 0x01.
После того как структура CUE SHEET сформирована, она передается устройству при помощи команды SEND CUE SHEET.
SEND CUE SHEET CDB
| 
 Bit  
Byte 
 | 
 7 
 | 
 6 
 | 
 5 
 | 
 4 
 | 
 3 
 | 
 2 
 | 
 1 
 | 
 0 
 | 
| 
 0 
 | 
 Operation Code   (5Dh) 
 | 
| 
 1 
 | 
 Reserved 
 | 
| 
 2 
 | 
 Reserved 
 | 
| 
 3 
 | 
 Reserved 
 | 
| 
 4 
 | 
 Reserved 
 | 
| 
 5 
 | 
 Reserved 
 | 
| 
 6 
 | 
 Reserved 
 | 
| 
 7 
 | 
 (MSB) 
 | 
 CUE SHEET SIZE 
 | 
| 
 8 
 | 
 (LSB) 
 | 
| 
 9 
 | 
 Control 
 | 
Рисунок 4. Формат команды SEND CUE SHEET
Параметр Cue Sheet Size содержит размер структуры CUE SHEET в байтах.
Примеры программ для записи дисков в режиме SAO
Запись данных на компакт-диск
Рассмотрим пример программы, выполняющей запись односессионного CD-R/RW-диска в режиме SAO.
В сессии находится только один трек, формат данных – CD-ROM Mode 1.
Алгоритм работы программы следующий:
- проверяем возможность записи информации в режиме SAO;
 
- в странице параметров режима записи указываем требуемый режим записи – SAO: Write Type = 0x02 (см. [1]);
 
- формируем структуру CUE SHEET и отправляем ее устройству;
 
- выполняем запись данных на носитель.
 
Для возможности записи в режиме SAO устройство должно обладать свойством CD Mastering (код 0x002E).
| 
 Bit 
Byte 
 | 
 7 
 | 
 6 
 | 
 5 
 | 
 4 
 | 
 3 
 | 
 2 
 | 
 1 
 | 
 0 
 | 
| 
 0 
 | 
 (MSB) 
 | 
 Feature Code = 002Eh 
(LSB) 
 | 
| 
 1 
 | 
| 
 2 
 | 
 Reserved 
 | 
 Version 
 | 
 Persistent 
 | 
 Current 
 | 
| 
 3 
 | 
 Additional Length =   04h 
 | 
| 
 4 
 | 
 Resvd 
 | 
 BUF 
 | 
 SAO 
 | 
 Raw   MS 
 | 
 Raw 
 | 
 Test Write 
 | 
 CD-RW 
 | 
 R-W 
 | 
| 
 5 
 | 
 (MSB) 
 | 
| 
 6 
 | 
   
 | 
 Maximum CUE SHEET Length 
(LSB) 
 | 
| 
 7 
 | 
Рисунок 5. Формат дескриптора свойства CD Mastering
Устройство поддерживает режим записи SAO, если бит SAO установлен в единицу. Формат дескриптора свойства можно описать при помощи следующей структуры:
/* Свойство CD Mastering */
typedef struct {
    __u16 f_code;
    __u8 current        :1;
    __u8 persistent     :1;
    __u8 version        :4;
    __u8 res1                  :2;
    __u8 add_length;
    __u8 rw                    :1;
    __u8 cd_rw          :1;
    __u8 test_write     :1;
    __u8 raw                   :1;
    __u8 raw_ms         :1;
    __u8 sao                   :1;
    __u8 BUF                   :1;
    __u8 res2                  :1;
    __u8 max_cue_len[3];
}  cd_sao_t;
Проверка наличия свойства выполняет при помощи команды GET CONFIGURATION (см. [1], «Свойства и профили устройства», [3]). Проверку выполняет функция check_ feature(). Входные параметры функции – код проверяемого свойства, в нашем случае это 0x002E:
int check_feature(__u16 f_num)
{
#define FEATURE_LEN 16
    __u8 get_conf_cmd[10]; // CDB
    __u8 data_buff[FEATURE_LEN]; // результаты чтения
    __u32 data_length = 0; // реальная длина данных
    __u16 cur_prof = 0; // текущий профиль, Current Profile
    cd_sao_t *cd_sao;
    test_unit_ready();
    memset(data_buff, 0, sizeof(data_buff));
    cd_sao = (void *)(data_buff + 8);
/* Для определения свойств устройства используется команда GET CONFIGURATION */
    memset(get_conf_cmd, 0, 10);
    get_conf_cmd[0] = 0x46; // код команды GET CONFIGURATION
    get_conf_cmd[1] = 2;       // RT = 10b, считываем только запрашиваемое свойство
    get_conf_cmd[2] = *((__u8 *)&f_num + 1);
    get_conf_cmd[3] = *((__u8 *)&f_num);
    get_conf_cmd[8] = FEATURE_LEN;
    send_cmd(get_conf_cmd, 10, SG_DXFER_FROM_DEV, data_buff, FEATURE_LEN, 20);
    memcpy((void *)&data_length, data_buff, 4);
    data_length = __swab32(data_length);
    if(data_length == 4) return -1; // свойство не поддерживается
/* Проверяем, является ли свойство текущим */
    if(cd_sao->current != 1) return -1;
/* Определяем текущий профиль */
    *((__u8 *)&cur_prof) = data_buff[7];
    *((__u8 *)&cur_prof + 1) = data_buff[6];
    printf("\nТекущий профиль - 0x%04X\n", cur_prof);
/* Возвращаем значение бита SAO */
    return (cd_sao->sao);
}
Установку требуемого режима записи – SAO – выполняет связка функций mode_sense() и mode_select(). Эти функции были рассмотрены в [1] и [2], здесь мы только немного модифицируем функцию mode_sense() – при вызове этой функции сначала определяется реальная длина страницы параметров режима записи, а после этого считывается сама страница.
/* Глобальные переменные */
__u16 page5_len;
__u8 *page5_data;
wpm_t *wpm; // данные страницы параметров записи
int mode_sense()
{
    __u8 mode_sense_cmd[10];
    test_unit_ready();
/* Определяем размер страницы параметров записи – первые два байта блока данных,
 * возвращаемых устройством по команде MODE_SENSE
 */
    page5_data = (__u8 *)malloc(2);
    memset(mode_sense_cmd, 0, 10);
    mode_sense_cmd[0] = MODE_SENSE_10;
    mode_sense_cmd[2] = 5; // страница параметров записи
    mode_sense_cmd[8] = 2;
    send_cmd(mode_sense_cmd, 10, SG_DXFER_FROM_DEV, page5_data, 2, 200);
    *((__u8 *)&page5_len) = page5_data[1];
    *((__u8 *)&page5_len + 1) = page5_data[0];
    page5_len += 2;
    printf("Размер страницы параметров записи – %d байт\n", page5_len);
    free(page5_data);
/* Размер страницы параметров режима записи известен, выделяем память для страницы */
    page5_data = (__u8 *)malloc(page5_len);
    memset(page5_data, 0, page5_len);
/* Структура wpm_t *wpm хранит данные страницы параметров записи, ее формат был рассмотрен в [2]. wpm указывает
 * на начало страницы параметров режима записи
 */
    wpm = (void *)(page5_data + 8);
/* Формируем команду MODE_SENSE_10 */
    memset(mode_sense_cmd, 0, 10);
    mode_sense_cmd[0] = MODE_SENSE_10;
    mode_sense_cmd[2] = 5; // страница параметров записи
    mode_sense_cmd[8] = page5_len;
    send_cmd(mode_sense_cmd, 10, SG_DXFER_FROM_DEV, page5_data, page5_len, 200);
    return 0;
}
Установку режима записи SAO выполняет функция mode_ select():
int mode_select()
{
    __u8 mode_select_cmd[10];
/* Считываем страницу параметров режима записи */
    mode_sense();
/* Устанавливаем необходимый режим */
    wpm->write_type = 2; // режим SAO
    wpm->multises = 0; // односессионный диск
    test_unit_ready();
/* Формируем команду MODE_SELECT_10 */
    memset(mode_select_cmd, 0, 10);
    mode_select_cmd[0] = MODE_SELECT_10;
    mode_select_cmd[1] = 0x10;
    mode_select_cmd[7] = *((__u8 *)&page5_len + 1);
    mode_select_cmd[8] = *(__u8 *)&page5_len;
    if(send_cmd(mode_select_cmd, 10, SG_DXFER_TO_DEV, page5_data, page5_len, 200) < 0) return -1;
    free(page5_data);
    return 0;
}
Формирование структуры CUE SHEET выполняет функция send_cue_sheet(). По условиям задачи на диск записывается сессия, состоящая из одного трека, формат данных – CD-ROM Mode 1. Структура CUE SHEET будет состоять из четырех 8-байтовых блоков, и ее размер равен 32 байта (4х8). Первый и последний блоки описывают входную и выходную области сессии, второй блок – Pre-Gap-область трека, третий блок описывает сам трек.
Для указания стартовых координат (поле ABSOLUTE TIME) необходимо выполнить преобразование адреса из формата LBA в MSF. Для этого спецификацией [3] (табл. 584) предусмотрены следующие формулы:
M = IP((LBA + 150) / (60 * 75))
S = IP((LBA + 150 - M * 60 * 75) / 75)
F = IP(LBA + 150 - M * 60 *7 5 - S * 75)
Здесь IP – это Integer Part (целая часть), параметр LBA принимает значение: -151 < LBA < 404850.
Пересчет координат из LBA в MSF выполняется при помощи следующих макроопределений:
#define LBA2MIN(LBA) ((LBA + 150) / (60 * 75))
#define LBA2SEC(LBA, MIN) ((LBA + 150 - (MIN * 60 * 75)) / 75)
#define LBA2FRAME(LBA, MIN, SEC) ((LBA + 150 - (MIN * 60 * 75)) - (SEC * 75))
В параметрах функции send_cue_sheet() передается размер трека, исчисляемый в блоках по 2048 байт.
int send_cue_sheet(__u32 trk_size)
{
    __u8 cue_sheet_cmd[10];
    __u8 cue_buff[32]; // структура CUE SHEET, размер 32 байта
    __u8 min, sec, frame; // координаты Lead-Out-области
    memset(cue_sheet_cmd, 0, 10);
    memset(buff, 0, 32);
/* Заполняем поля CUE SHEET. Начинаем с Lead-In-области */
    cue_buff[0] = 0x01; // CTL/ARD
    cue_buff[1] = 0x00; // номер трека, всегда 0
    cue_buff[2] = 0x00; // индекс трека, всегда 0
    cue_buff[3] = 0x01; // DATA FORM, всегда 1
    cue_buff[4] = 0x00; // SCMS
    cue_buff[5] = 0x00; // Minute = 0
    cue_buff[6] = 0x00; // Second = 0
    cue_buff[7] = 0x00; // Frame = 0
/* Pre-gap область первого трека */
    cue_buff[8] = 0x41; // CTL = 4 – трек содержит данные
    cue_buff[9] = 0x01; // номер трека = 1
    cue_buff[10] = 0x00; // индекс трека = 0
    cue_buff[11] = 0x10; // данные в формате Mode 1
    cue_buff[12] = 0x00; // SCMS
/* Координаты Pre-Gap-области – (-150) в формате LBA, или 0/0/0 в MSF-формате */
    cue_buff[13] = 0x00; // Minute
    cue_buff[14] = 0x00; // Second
    cue_buff[15] = 0x00; // Frame
/* Первый трек */
    cue_buff[16] = 0x41; // CTL = 4 – трек содержит данные
    cue_buff[17] = 0x01; // номер трека = 1
    cue_buff[18] = 0x01; // индекс трека = 1
    cue_buff[19] = 0x10; // данные в формате Mode 1
    cue_buff[20] = 0x00; // SCMS
/* Координаты первого трека – 0 в формате LBA, или 0/2/0 в MSF-формате */
    cue_buff[21] = 0x00; // Minute
    cue_buff[22] = 0x02; // Second
    cue_buff[23] = 0x00; // Frame
/* Lead-Out */
    cue_buff[24] = 0x01; // CTL/ADR
    cue_buff[25] = 0xAA; // номер трека выходной области
    cue_buff[26] = 0x01; // индекс, для Lead-Out всегда 1
    cue_buff[27] = 0x01; // DATA FORM, всегда 1
    cue_buff[28] = 0x00; // SCMC
/* Определяем координаты Lead-Out-области. Эта область расположена сразу за первым треком.
 * Размер трека нам известен
 */
    min = LBA2MIN(trk_size);
    sec = LBA2SEC(trk_size, min);
    frame = LBA2FRAME(trk_size, min, sec);
    printf("\nMIN/SEC/FRAME: %.2d/%.2d/%.2d\n", min, sec, frame);
    cue_buff[29] = min;
    cue_buff[30] = sec;
    cue_buff[31] = frame;
/* Формируем команду SEND CUE SHEET */
    cue_sheet_cmd[0] = 0x5D;
    cue_sheet_cmd[8] = 32;
    if(send_cmd(cue_sheet_cmd, 10, SG_DXFER_TO_DEV, cue_buff, 32, 200) < 0) return -1;
    return 0;
}
После того как структура CUE SHEET отправлена устройству, можно приступить к записи данных на носитель. Запись выполняет функция write_iso(), параметр функции – имя файла-образа. Вначале записывается Pre-Gap-область первого трека, а затем сам трек. Стартовый адрес Pre-Gap-области в формате MSF равен 0/0/0 (-150 в LBA-формате), размер Pre-Gap-области составляет 150 секторов.
int write_iso(__u8 *file_name)
{
#define PREGAP_SIZE 307200 // размер Pre-Gap-области: 150 секторов по 2048 байт
    int ret, in_f;
    __u8 write_cmd[10];
    __u8 *write_buff;
    int lba, lba1 = -150;
    in_f = open(file_name, O_RDONLY);
    memset(write_cmd, 0, 10);
    write_cmd[0] = WRITE_10;
    write_cmd[8] = 150; // размер Pre-Gap в секторах
    write_buff = (__u8 *)malloc(PREGAP_SIZE);
    memset(write_buff, 0, PREGAP_SIZE);
/* Записываем Pre-gap-область. Она начинается с сектора – 150 и содержит нули */
    lba = __swab32(lba1);
    memcpy((write_cmd + 2), (void *)&lba, 4);
    lba1 += 150;
    while(1) {
           ret = send_cmd(write_cmd, 10, SG_DXFER_TO_DEV, write_buff, PREGAP_SIZE, 200);
                 if(ret == 0) break;
                 if(ret < 0) return -1;
    }
    free(write_buff);
/* Дальше выполняется запись данных первого трека аналогично рассмотренным ранее примерам (см. [2]) */
    . . . . . . .
}
Функция посылки команды устройству send_cmd() также претерпела некоторые изменения (полный текст не приводится):
int send_cmd(__u8 *cmd, __u8 cmdlen, int direction, __u8 *data, __u32 datalen, unsigned int timeout)
{
    ............
rep:
    if(ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
                 perror("SG_IO ioctl");
                        return -1;
    }
    if((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) {
           if(io_hdr.sb_len_wr > 0) {
/* Если SK/ASC/ASCQ == 02/04/08 (Not Ready. Long Write in Progress), то необходимо повторить команду WRITE_10 */
           if((SK == NOT_READY) && (ASC == 0x04) && (ASCQ == 0x08)) goto rep;
    ...........
}
Здесь в случае возникновения ошибки типа «NOT READY. LONG WRITE IN PROGRESS» сразу выполняется переход на метку rep и повтор команды WRITE, без необходимости формирования командного пакета. Отметим, что эти изменения никак не связаны с рассматриваемым режимом записи.
Полный текст программы для записи данных на CD-R/RW диск в режиме SAO находится по адресу: http://bob.netport.com.ua/sao.tar.gz.
Запись Audio-CD
При записи аудиоданных на компакт-диск в режиме SAO в одной сессии будет находиться несколько треков. Рассмотрим, как в этом случае формируется структура CUE SHEET, и для начала изучим ее формат на реальном примере. С этой целью внесем небольшое дополнение в исходный код утилиты cdrecord, а именно в функцию fillcue() (файл cdrecord/drv_mmc.c), в результате чего функция приобретает следующий вид:
LOCAL void
fillcue(cp, ca, tno, idx, dataform, scms, mp)
    struct mmc_cue *cp; /* The target cue entry */
    int    ca; /* Control/adr for this entry */
    int    tno; /* Track number for this entry */
    int    idx; /* Index for this entry */
    int    dataform; /* Data format for this entry */
    int    scms; /* Serial copy management */
    msf_t  *mp; /* MSF value for this entry */
{
    cp->cs_ctladr = ca; /* XXX wie lead in */
    cp->cs_tno = tno;
    cp->cs_index = idx;
    cp->cs_dataform = dataform;/* XXX wie lead in */
    cp->cs_scms = scms;
    cp->cs_min = mp->msf_min;
    cp->cs_sec = mp->msf_sec;
    cp->cs_frame = mp->msf_frame;
/* Этот код добавлен нами в учебных целях! */
    printf("\n0x%.2X\t", cp->cs_ctladr);
    printf("0x%.2X\t",cp->cs_tno);
    printf("0x%.2X\t",cp->cs_index);
    printf("0x%.2X\t",cp->cs_dataform);
    printf("0x%.2X\t",cp->cs_scms);
    printf("0x%.2X\t",cp->cs_min);
    printf("0x%.2X\t",cp->cs_sec);
    printf("0x%.2X\n",cp->cs_frame);
}
После внесения изменений пересоберем утилиту и запишем с ее помощью три аудиотрека:
# cdrecord -dev=X,Y,Z -dao -pad -audio trk1.wav rk2.wav trk3.wav
Размер первого трека равен 17087 блоков, размер второго трека – 12923, размер третьего трека – 20580. Структура CUE SHEET имеет следующее содержание:
| 
 1 
 | 
 0x01 
 | 
 0x00 
 | 
 0x00 
 | 
 0x01 
 | 
 0x00 
 | 
 0x00 
 | 
 0x00 
 | 
 0x00 
 | 
| 
 2 
 | 
 0x01 
 | 
 0x01 
 | 
 0x00 
 | 
 0x00 
 | 
 0x00 
 | 
 0x00 
 | 
 0x00 
 | 
 0x00 
 | 
| 
 3 
 | 
 0x01 
 | 
 0x01 
 | 
 0x01 
 | 
 0x00 
 | 
 0x00 
 | 
 0x00 
 | 
 0x02 
 | 
 0x00 
 | 
| 
 4 
 | 
 0x01 
 | 
 0x02 
 | 
 0x00 
 | 
 0x00 
 | 
 0x00 
 | 
 0x03 
 | 
 0x2F 
 | 
 0x3E 
 | 
| 
 5 
 | 
 0x01 
 | 
 0x02 
 | 
 0x01 
 | 
 0x00 
 | 
 0x00 
 | 
 0x03 
 | 
 0x31 
 | 
 0x3E 
 | 
| 
 6 
 | 
 0x01 
 | 
 0x03 
 | 
 0x00 
 | 
 0x00 
 | 
 0x00 
 | 
 0x06 
 | 
 0x28 
 | 
 0x0A 
 | 
| 
 7 
 | 
 0x01 
 | 
 0x03 
 | 
 0x01 
 | 
 0x00 
 | 
 0x00 
 | 
 0x06 
 | 
 0x2A 
 | 
 0x0A 
 | 
| 
 8 
 | 
 0x01 
 | 
 0xAA 
 | 
 0x01 
 | 
 0x01 
 | 
 0x00 
 | 
 0x0B 
 | 
 0x10 
 | 
 0x28 
 | 
Структура CUE SHEET состоит из восьми блоков, ее размер равен 64 байта. Первый и последний блоки – это входная и выходная области сессии. Блок 2 – Pre-Gap-область первого трека, стартовый адрес равен 150. Третий блок описывает первый трек, стартовый адрес трека равен 0. Четвертый блок – Pre-Gap-область второго трека, стартовый адрес трека 16937. Пятый блок – второй трек, стартовый адрес трека равен размеру первого трека – 17087 блоков. Шестой блок – Pre-Gap-область третьего трека, стартовый адрес равен 29860. Седьмой блок содержит описание третьего трека, его стартовый адрес равен 30010 (17087 + 12923). Параметр DATA FORM равен 0x00. Это означает, что на диск выполняется запись аудиоданных, и размер блока равен 2352 байта.
Таким образом, опытным путем установлено, что треки действительно расположены вплотную друг к другу, и Pre-Gap-область трека захватывает последние 150 секторов области данных предыдущего трека. Исключение составляет Pre-Gap-область первого трека.
Перепишем функцию send_cue_sheet для возможности записи на диск трех аудиотреков.
Входные параметры функции – размеры треков в блоках по 2352 байта:
int send_cue_sheet(int trk1_size, int trk2_size, int trk3_size)
{
    __u8 cue_sheet_cmd[10];
    __u8 *cue_buff;
    __u8 min, sec, frame;
    int cb_size = 0;
    int trk = -150;
/* Определяем размер структуры CUE SHEET. В ее состав входят три Pre-Gap-области, три трека,
 * входная и выходная области
 */
    cb_size = 3 * 8 + 3 * 8 + 16; // Итого 64 байта
    cue_buff = (__u8 *)malloc(cb_size);
    memset(cue_sheet_cmd, 0, 10);
    memset(cue_buff, 0, cb_size);
/* Формируем Lead-In-область */
    cue_buff[0] = 0x01; cue_buff[1] = 0x00;
    cue_buff[2] = 0x00; cue_buff[3] = 0x01;
    cue_buff[4] = 0x00; cue_buff[5] = 0x00;
    cue_buff[6] = 0x00; cue_buff[7] = 0x00;
/* Pre-gap первого трека */
    cue_buff[8] = 0x01; cue_buff[9] = 0x01;
    cue_buff[10] = 0x00; cue_buff[11] = 0x00;
    cue_buff[12] = 0x00; cue_buff[13] = 0x00;
    cue_buff[14] = 0x00; cue_buff[15] = 0x00;
/* Первый трек */
    cue_buff[16] = 0x01; cue_buff[17] = 0x01;
    cue_buff[18] = 0x01; cue_buff[19] = 0x00;
    cue_buff[20] = 0x00; cue_buff[21] = 0x00;
    cue_buff[22] = 0x02; cue_buff[23] = 0x00;
/* Pre-gap-область второго трека захватывает последние 150 секторов первого трека */
    trk += trk1_size;
    min = LBA2MIN(trk);
    sec = LBA2SEC(trk, min);
    frame = LBA2FRAME(trk, min, sec);
    cue_buff[24] = 0x01; cue_buff[25] = 0x02;
    cue_buff[26] = 0x00; cue_buff[27] = 0x00;
    cue_buff[28] = 0x00; cue_buff[29] = min;
    cue_buff[30] = sec; cue_buff[31] = frame;
/* Второй трек расположен сразу за первым, т.е. пауза между треками отсутствует */
    sec += 2; // длина Pre-Gap – 2 секунды, или 150 секторов
    cue_buff[32] = 0x01; cue_buff[33] = 0x02;
    cue_buff[34] = 0x01; cue_buff[35] = 0x00;
    cue_buff[36] = 0x00; cue_buff[37] = min;
    cue_buff[38] = sec; cue_buff[39] = frame;
/* Pre-gap-область третьего трека захватывает последние 150 секторов второго трека */
    trk += trk2_size;
    min = LBA2MIN(trk);
    sec = LBA2SEC(trk, min);
    frame = LBA2FRAME(trk, min, sec);
    cue_buff[40] = 0x01; cue_buff[41] = 0x03;
    cue_buff[42] = 0x00; cue_buff[43] = 0x00;
    cue_buff[44] = 0x00; cue_buff[45] = min;
    cue_buff[46] = sec; cue_buff[47] = frame;
/* Третий трек */
    sec += 2;
    cue_buff[48] = 0x01; cue_buff[49] = 0x03;
    cue_buff[50] = 0x01; cue_buff[51] = 0x00;
    cue_buff[52] = 0x00; cue_buff[53] = min;
    cue_buff[54] = sec; cue_buff[55] = frame;
/* Lead-Out-область */
    trk += trk3_size + 150;
    min = LBA2MIN(trk);
    sec = LBA2SEC(trk, min);
    frame = LBA2FRAME(trk, min, sec);
    cue_buff[56] = 0x01; cue_buff[57] = 0xAA;
    cue_buff[58] = 0x01; cue_buff[59] = 0x01;
    cue_buff[60] = 0x00; cue_buff[61] = min;
    cue_buff[62] = sec; cue_buff[63] = frame;
/* Формируем командный пакет */
    cue_sheet_cmd[0] = 0x5D;
    cue_sheet_cmd[8] = cb_size;
/* Отправляем устройству команду SEND_CUE_SHEET, освобождаем память и выходим из функции */
    if(send_cmd(cue_sheet_cmd, 10, SG_DXFER_TO_DEV, cue_buff, cb_size, 200) < 0) return -1;
    free(cue_buff);
    return 0;
}
Отправив устройству структуру CUE SHEET, приступаем к записи данных.
Запись данных выполняет функция write_audio(). Входные параметры функции – имя файла в формате WAV и номер записываемого трека.
Если записывается первый трек, то перед записью данных этого трека выполняется запись Pre-Gap-области размером 150 секторов.
int write_audio(__u8 *file_name, int flag)
{
#define PREGAP_SIZE 352800 // 150 секторов по 2352 байта
    int ret, i;
    int in_f;
    __u8 write_cmd[10];
    __u8 *write_buff;
    int lba = 0;
    static int lba1 = -150; // стартовый адрес для записи
    in_f = open(file_name, O_RDONLY);
/* Перешагиваем через WAV-заголовок */
    lseek(in_f, 44, 0);
    memset(write_cmd, 0, 10);
    write_cmd[0] = WRITE_10;
    if(flag == 1) { // записываем первый трек
           write_buff = (__u8 *)malloc(PREGAP_SIZE);
                 memset(write_buff, 0, PREGAP_SIZE);
                        write_cmd[8] = 150;
/* Записываем Pre-gap-область первого трека */
    lba = __swab32(lba1);
    memcpy((write_cmd + 2), (void *)&lba, 4);
                        lba1 += 150;
    while(1) {
           ret = send_cmd(write_cmd, 10, SG_DXFER_TO_DEV, write_buff, PREGAP_SIZE, 200);
                 if(ret == 0) break;
                 if(ret < 0) return 0;
                 }
 
                 free(write_buff);
    }
/* Дальше выполняется запись данных аудиотрека */
    . . . . . . .
}
Из всех Pre-Gap-областей на диск записывается только Pre-Gap-область первого трека, остальные просто обозначены в структуре CUE SHEET.
Возникает вопрос – если не все Pre-Gap-области подлежат записи, то зачем их указывать в структуре CUE SHEET? Исключим «лишние» Pre-Gap-области из структуры CUE SHEET, и функция send_ cue_sheet() примет следующий вид:
int send_cue_sheet(int trk1_size, int trk2_size, int trk3_size)
{
    __u8 cue_sheet_cmd[10];
    __u8 *cue_buff;
    __u8 min, sec, frame;
    int cb_size = 0;
    int trk = 0;
/* Вычисляем размер CUE SHEET – Pre-Gap-область первого трека, три трека, Lead-In- и Lead-Out-области */
    cb_size = 3 * 8 + 8 + 16; // Итого 48 байт
    cue_buff = (__u8 *)malloc(cb_size);
    memset(cue_sheet_cmd, 0, 10);
    memset(cue_buff, 0, cb_size);
/* Описание Lead-In-области, Pre-gap-области первого трека и непосредственно первого трека точно такие же,
 * как и в предыдущем примере
 */
/* Lead-In-область */
    .......
/* Pre-gap первого трека */
    .......
/* Первый трек */
    .......
/* Второй трек */
    trk += trk1_size;
    min = LBA2MIN(trk);
    sec = LBA2SEC(trk, min);
    frame = LBA2FRAME(trk, min, sec);
    cue_buff[24] = 0x01; cue_buff[25] = 0x02;
    cue_buff[26] = 0x01; cue_buff[27] = 0x00;
    cue_buff[28] = 0x00; cue_buff[29] = min;
    cue_buff[30] = sec; cue_buff[31] = frame;
/* Третий трек */
    trk += trk2_size;
    min = LBA2MIN(trk);
    sec = LBA2SEC(trk, min);
    frame = LBA2FRAME(trk, min, sec);
    cue_buff[32] = 0x01; cue_buff[33] = 0x03;
    cue_buff[34] = 0x01; cue_buff[35] = 0x00;
    cue_buff[36] = 0x00; cue_buff[37] = min;
    cue_buff[38] = sec; cue_buff[39] = frame;
/* Lead-Out */
    trk += trk3_size;
    min = LBA2MIN(trk);
    sec = LBA2SEC(trk, min);
    frame = LBA2FRAME(trk, min, sec);
    cue_buff[40] = 0x01; cue_buff[41] = 0xAA;
    cue_buff[42] = 0x01; cue_buff[43] = 0x01;
    cue_buff[44] = 0x00; cue_buff[45] = min;
    cue_buff[46] = sec; cue_buff[47] = frame;
    cue_sheet_cmd[0] = 0x5D;
    cue_sheet_cmd[8] = cb_size;
    if(send_cmd(cue_sheet_cmd, 10, SG_DXFER_TO_DEV, cue_buff, cb_size, 200) < 0) return -1;
    free(cue_buff);
    return 0;
}
Перепишем функцию send_cue_sheet() для возможности записи произвольного числа аудиотреков. Параметры функции – указатель на массив, содержащий имена файлов в формате WAV, и количество записываемых треков.
int send_cue_sheet(char **argv, int trk_num)
{
    int i = 0, n;
    __u8 cue_sheet_cmd[10];
    __u8 *cue_buff = NULL;
    __u8 min, sec, frame;
    struct stat s;
    int cb_size = 0;
    int trk = 0;
    cb_size = trk_num * 8 + 8 + 16;
    cue_buff = (__u8 *)malloc(cb_size);
    memset(cue_sheet_cmd, 0, 10);
    memset(cue_buff, 0, cb_size);
/* Lead-In-область, Pre-gap первого трека, первый трек (см. предыдущий пример) */
    .......
/* Записываем в структуру CUE SHEET информацию о треках. Начинаем со второго трека,
 * т.к. для первого запись уже сформирована
 */
    for(i = 1, n = 24; i < trk_num; i++, n += 8) {
/* Определяем размер трека */
    memset((void *)&s, 0, sizeof(struct stat));
    stat(argv[i], &s);
/* Стартовые координаты трека */
    trk += s.st_size/CD_FRAMESIZE_RAW;
    min = LBA2MIN(trk);
    sec = LBA2SEC(trk, min);
    frame = LBA2FRAME(trk, min, sec);
/* Формируем в структуре CUE SHEET запись для трека */
    cue_buff[n] = 0x01; cue_buff[n + 1] = i + 1;
    cue_buff[n + 2] = 0x01; cue_buff[n + 3] = 0x00;
    cue_buff[n + 4] = 0x00; cue_buff[n + 5] = min;
    cue_buff[n + 6] = sec; cue_buff[n + 7] = frame;
    }
/* Определяем размер последнего трека */
    memset((void *)&s, 0, sizeof(struct stat));
    stat(argv[trk_num], &s);
/* Формируем Lead-Out-область */
    trk += s.st_size/CD_FRAMESIZE_RAW;
    min = LBA2MIN(trk);
    sec = LBA2SEC(trk, min);
    frame = LBA2FRAME(trk, min, sec);
    cue_buff[n] = 0x01; cue_buff[n + 1] = 0xAA;
    cue_buff[n + 2] = 0x01; cue_buff[n + 3] = 0x01;
    cue_buff[n + 4] = 0x00; cue_buff[n + 5] = min;
    cue_buff[n + 6] = sec; cue_buff[n + 7] = frame;
    cue_sheet_cmd[0] = 0x5D;
    cue_sheet_cmd[8] = cb_size;
    if(send_cmd(cue_sheet_cmd, 10, SG_DXFER_TO_DEV, cue_buff, cb_size, 200) < 0) return -1;
    free(cue_buff);
    return 0;
}
Исходный текст программы для записи Audio-CD в режиме SAO находится по адресу: http://bob.netport.com.ua/sao.tar.gz.
Литература:
- Мешков В. Запись CD-R/RW-дисков в Linux. Часть 1. – Журнал «Системный администратор», №11(24), ноябрь 2004 г.
 
- Мешков В. Запись CD-R/RW-дисков в Linux. Часть 2. – Журнал «Системный администратор», №12(25), декабрь 2004 г.
 
- SCSI Multimedia Command Set: http://www.t10.org/ftp/t10/drafts/mmc5/mmc5r01.pdf.