 ВЛАДИМИР МЕШКОВ
ВЛАДИМИР МЕШКОВ
Запись дисков CD-R/RW в Linux
Во второй части статьи рассмотрены примеры программ, выполняющих запись различной информации – музыкальных треков и данных – на компакт-диск CD-R/RW.
Запись информации на компакт-диск
Запись данных на компакт-диск
Рассмотрим пример программы, выполняющей запись данных на компакт-диск. Под термином «данные» понимается файл в формате ISO (файл-образ), полученный при помощи утилиты mkisofs или каким-либо другим способом.
Исходные тексты всех программ доступны на сайте журнала (www.samag.ru/source).
Алгоритм выполнения операции записи данных на компакт-диск следующий:
- Определяются текущие параметры режима записи устройства путем считывания страницы параметров режима записи, код 0x05.
- Устанавливаются требуемые значения полей страницы параметров режима записи – режим записи, тип блока данных, режим трека (аудио/данные) и др.
- Из файла образа считываются блоки данных, и команда WRITE_10 выполняет запись этих блоков в соответствующие сектора компакт-диска. Стартовый номер сектора, с которого начинается запись, равен 0. Размер блока определяет значение поля «Data Block Type» страницы параметров режима записи (см. рис. 15 первой части статьи).
- По окончании записи данных (достигнут конец файла-образа) последовательно выполняются команды SYNC-HRONIZE CACHE, CLOSE TRACK, CLOSE SESSION. По команде SYNCHRONIZE CACHE все данные, подлежащие записи, переносятся на носитель, кэш устройства освобождается. Команда CLOSE TRACK завершает трек, а CLOSE SESSION завершает сессию – формирует Lead-In и Lead-Out-области сессии (диска).
Для определения текущих параметров устройства применяется команда MODE SENSE, а для установки параметров – команда MODE SELECT. Формат команды MODE SENSE приведен на рисунке:
Mode Sense CDB
| Bit Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 
| 0 | Operation code   (5Ah) | 
| 1 | Reserved | LLBAA | DBD | Reserved | 
| 2 | PC | Page Code | 
| 3 | Reserved | 
| 4 | Reserved | 
| 5 | Reserved | 
| 6 | Reserved | 
| 7 | (MSB) | Allocation Length (LSB) | 
| 8 | 
| 9 | Control | 
Рисунок  1. Формат команды MODE SENSE
Поле Page Code содержит код запрашиваемой страницы режимов, поле Allocation Length – размер считываемых данных.
Формат команды MODE SELECT приведен на рисунке:
Mode Select CDB
| Bit Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 
| 0 | Operation code   (55h) | 
| 1 | Reserved | PF=1 | Reserved | SP | 
| 2 | Reserved | 
| 3 | Reserved | 
| 4 | Reserved | 
| 5 | Reserved | 
| 6 | Reserved | 
| 7 | (MSB) | Parameter List Length (LSB) | 
| 8 | 
| 9 | Control | 
Рисунок 2. Формат команды MODE SELECT
Поле Parameter List Length содержит размер передаваемого списка параметров в байтах. Бит PF (Page Format) установлен в единицу. Это означает, что формат страницы соответствует стандарту SCSI.
Запись данных на диск выполняет команда WRITE_10. Формат этой команды представлен на рис. 3.
WRITE (10) CDB
| Bit Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 
| 0 | Operation code   (2Ah) | 
| 1 | Reserved | DPO | FUA | Reserved | RelAdr | 
| 2 | (MSB) Logical   Block Address   (LSB) | 
| 3 | 
| 4 | 
| 5 | 
| 6 | Reserved | 
| 7 | (MSB) | Transfer Length (LSB) | 
| 8 | 
| 9 | Control | 
Рисунок 3. Формат команды WRITE_10
Назначение полей:
- Logical Block Address – адрес блока, в который будет записана информация.
- Transfer Length – число блоков для записи на диск.
В команде SYNCHRONIZE CACHE используется только нулевой байт – он содержит код операции 0x35.
Формат команды CLOSE TRACK/SESSION представлен на рисунке:
CLOSE TRACK/SESSION CDB
| Bit Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 
| 0 | Operation code   (5Bh) | 
| 1 | Reserved | IMMED | 
| 2 | Reserved | Close Function | 
| 3 | Reserved | 
| 4 | (MSB) | Track Number (LSB) | 
| 5 | 
| 6 | Reserved | 
| 7 | Reserved | 
| 8 | Reserved | 
| 9 | Control Byte | 
Рисунок 4. Формат команды CLOSE TRACK/SESSION
Поле Close Function может принимать следующие значения:
- 001b – закрыть трек, номер которого указан в поле Track Number.
- 010b – закрыть последнюю незавершенную сессию, значение поля Track Number игнорируется.
Следующая структура описывает формат страницы параметров режима записи (см. рис. 15 в первой части статьи):
typedef struct {
    __u8 page_code      :6;
    __u8 rez            :1;
    __u8 ps             :1;
    __u8 page_length;
    __u8 write_type     :4;
    __u8 test_write     :1;
    __u8 ls_v           :1;
    __u8 BUFE           :1;
    __u8 rez1           :1;
    __u8 track_mode     :4;
    __u8 copy           :1;
    __u8 FP             :1;
    __u8 multises       :2;
    __u8 dbt            :4;
    __u8 rez2           :4;
    __u8 link_size;
    __u8 rez3;
    __u8 hac            :6;
    __u8 rez4           :2;
    __u8 s_format;
    __u8 rez5;
    __u32 packet_size;
    __u16 apl;
    __u8 mcn[16];
    __u8 isrc[16];
    __u32 sh;
} __attribute__ ((packed)) wpm_t;
Определим глобальные переменные:
// дескриптор файла sg-устройства
int sg_fd;
// указатель на данные страницы параметров режима записи
wpm_t *wpm;
// значение поля Mode Data Length заголовка Mode Parameter Header
__u16 mode_page_len = 0;
// размер списка страниц режима Mode Parameter List – 8 байт заголовка списка Mode Parameter Header + 52 байта самой
// страницы (см. рис. 13-15, первой части статьи)
__u16 data_len = 60;
// блок памяти для чтения списка Mode Parameter List
__u8 data_buff[60];
Функция mode_sense() считывает страницу параметров режима записи устройства:
int mode_sense()
{
    __u8 mode_sense_cmd[10];
/* Устройства готово? */
    if(test_unit_ready() < 0) exit(-1);
    memset(data_buff, 0, 60);
    // wpm указывает на начало страницы параметров режима записи
    wpm = (void *)(data_buff + 8);
/* Формируем командый пакет */
    memset(mode_sense_cmd, 0, 10);
    // код команды MODE SENSE
    mode_sense_cmd[0] = MODE_SENSE_10;
    // код страницы параметров режима записи
    mode_sense_cmd[2] = 5;
    // размер данных - 60 байт
    mode_sense_cmd[8] = data_len;
/* Посылаем команду устройству */
    if(send_cmd(mode_sense_cmd, 10, SG_DXFER_FROM_DEV, data_buff, data_len, 20000) < 0) return -1;
/* Размер считанных данных – значение поля Mode Data Length заголовка списка страниц */
    memcpy((void *)&mode_page_len, data_buff, 2);
    mode_page_len = __swab16(mode_page_len);
    printf("Mode data length - %d\n", mode_page_len);
/* Отобразим текущие параметры режима записи */
    // код страницы
    printf("Page code - %d\n", wpm->page_code);
    // размер страницы
    printf("Page length - %d\n", wpm->page_length);
    // режим записи
    printf("Write type - %d\n", wpm->write_type);
    // тип блока данных
    printf("Data block type - %d\n", wpm->dbt);
    // формат сессии
    printf("Session format - %d\n", wpm->s_format);
    // режим трека (поле Control Field)
    printf("Track mode  - %d\n", wpm->track_mode);
    return 0;
}
Установку требуемых параметров режима записи выполняет функция mode_select(). Этой функции мы передаем два параметра – тип блока данных (поле dbt структуры wpm_t) и тип информации, находящейся в треке (поле track_mode структуры wpm_t):
int mode_select(__u8 dbt, __u8 track_mode)
{
    __u8 mode_select_cmd[10];
/* Определяем текущие параметры режима записи */
    if(mode_sense() < 0) exit(-1);
/* Устанавливаем параметры для записи данных */
    wpm->write_type = 1; // режим записи – TAO
    wpm->dbt = dbt; // тип блока данных
    wpm->s_format = 0; // открытие новой сессии запрещено
    wpm->track_mode = track_mode; // режим трека
/* Устройство готово? */
    if(test_unit_ready() < 0) exit(-1);
/* Формируем командный пакет */
    memset(mode_select_cmd, 0, 10);
    // код команды MODE SELECT
    mode_select_cmd[0] = MODE_SELECT_10;
    // устанавливаем в 1 бит PF (Page Format)
    mode_select_cmd[1] = 1 << 4;
/* В поле Parameter List Header записываем размер блока данных */
    data_len = 60;
    mode_page_len = __swab16(data_len);
    memcpy((void *)(mode_select_cmd + 7),(void *)&mode_page_len, 2);
/* Посылаем устройству команду */
    if(send_cmd(mode_select_cmd, 10, SG_DXFER_TO_DEV, data_buff, data_len, 20000) < 0) return -1;
/* Для контроля отобразим установленные параметры режима записи */
    if(mode_sense() < 0) exit(-1);
    return 0;
}
Запись данных на компакт-диск выполняет функция write_iso(). Входные параметры функции – указатель на строку, содержащую имя файла-образа:
int write_iso(__u8 *file_name)
{
    int in_f;
    __u8 write_cmd[10];
    __u8 write_buff[CD_FRAMESIZE];
    __u32 lba = 0, lba1 = 0;
    if(test_unit_ready() < 0) exit(-1);
/* Открываем файл-образ */
    in_f = open(file_name, O_RDONLY, 0600);
    memset(write_buff, 0, CD_FRAMESIZE);
/* Цикл чтения блоков данных из файла. Размер блока – 2048 байт */
    while(read(in_f, write_buff, CD_FRAMESIZE) > 0) {
/* Формируем командный пакет */
    memset(write_cmd, 0, 10);
    write_cmd[0] = WRITE_10; // код команды
    write_cmd[8] = 1; // количество секторов для записи
    printf("%c", 0x0D);
    printf("lba - %6d", lba1);
/* Адрес блока для записи */
    lba = __swab32(lba1);
    memcpy((write_cmd + 2), (void *)&lba, 4);
    lba1 += 1;
/* Посылаем устройству команду */
    if(send_cmd(write_cmd, 10, SG_DXFER_TO_DEV, write_buff, CD_FRAMESIZE, 20000) < 0) return -1;
    }
    printf("\n");
    return 0;
}
По окончании записи данных необходимо, как было уже сказано выше, послать устройству три команды: SYNCHRO-NIZE CACHE, CLOSE TRACK, CLOSE SESSION. Команду SYNCHRONIZE CACHE формирует и посылает устройству функция sync_cache():
int sync_cache()
{
    __u8 flush_cache[10];
    if(test_unit_ready() < 0) exit(-1);
/* Формируем и посылаем команду */
    memset(flush_cache, 0, 10);
    flush_cache[0] = SYNCHRONIZE_CACHE; // код команды
    if(send_cmd(flush_cache, 10, SG_DXFER_NONE, NULL, 0, 20000) < 0) return -1;
    return 0;
}
Закрывает трек функция close_track():
int close_track()
{
    __u8 close_trk_cmd[10];
    if(sync_cache() < 0) {
    printf("Cannot synchronize cache\n");
    return -1;
    }
    if(test_unit_ready() < 0) exit(-1);
/* Формируем команду закрытия трека и посылаем ее устройству */
    memset(close_trk_cmd, 0, 10);
    close_trk_cmd[0] = 0x5B; // код команды CLOSE TRACK
    close_trk_cmd[2] = 1; // флаг закрытия трека
    close_trk_cmd[5] = 1; // номер трека, он у нас один
    if(send_cmd(close_trk_cmd, 10, SG_DXFER_NONE, NULL, 0, 20000) < 0) return -1;
    return 0;
}
Функция close_session() закрывает текущую сессию:
int close_session()
{
    __u8 close_sess_cmd[10];
    if(test_unit_ready() < 0) exit(-1);
    memset(close_sess_cmd, 0, 10);
    close_sess_cmd[0] = 0x5B; // код команды CLOSE SESSION
    close_sess_cmd[2] = 2; // флаг закрытия текущей сессии
    if(send_cmd(close_sess_cmd, 10, SG_DXFER_NONE, NULL, 0, 60000) < 0) return -1;
    return 0;
}
После того как данные записаны, извлекаем компакт-диск из привода, послав устройству команду START/STOP UNIT (см. [5]):
void eject_cd()
{
    __u8 start_stop_cmd[6];
    memset(start_stop_cmd, 0, 6);
    start_stop_cmd[0] = 0x1B; // код команды START/STOP UNIT
    start_stop_cmd[4] = 2; // извлечь компакт-диск
    send_cmd(start_stop_cmd, 6, SG_DXFER_NONE, NULL, 0, 20000);
    return;
}
Имя файла-образа передается главной функции программы в виде параметра. Функция main() выглядит следующим образом:
int main(int argc, char **argv)
{
/* Проверяем наличие входных параметров */
    if(argc != 2) {
    printf("\n\tUsage: write_iso [ISO-image]\n\n");
    return 0;
    }
/* Открываем файл sg-устройства */
    if((sg_fd = open(SG_DEV, O_RDWR)) < 0) {
           perror("open");
           return -1;
    }
/* Устанавливаем параметры режима записи данных: тип блока данных dbt = 8 – формат блока Mode 1,
 * размер блока 2048 байт; тип информации, находящейся в треке track_mode = 4 – трек содержит данные.
 */
    mode_select(8, 4);
/* Выполняем запись данных на компакт-диск */
    if(write_iso(argv[1]) < 0) {
    printf("Cannot write image %s\n", argv[1]);
    return -1;
    }
/* Закрываем трек, сессию и извлекаем компакт-диск из привода*/
    close_track();
    close_session();
    eject_cd();
    close(sg_fd);
    return 0;
}
Полный текст программы записи ISO-образа на компакт-диск находится в файле SG/iso_write.c.
Запись аудиоданных
Алгоритм записи аудиоданных практически не отличается от приведенного ранее алгоритма записи данных. Но так как в одной сессии теперь будет находиться несколько треков, то при записи аудиотрека на компакт-диск необходимо будет знать стартовый адрес трека. Этот адрес можно вычислить самостоятельно, можно определить его как адрес невидимого, незавершенного трека (invisible, incomplete track) либо предварительно зарезервировать на компакт-диске пространство для каждого трека.
Резервирование пространства для трека выполняет команда RESERVE TRACK. Формат этой команды представлен на рисунке:
RESERVE TRACK CDB
| Bit Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 
| 0 | Operation code   (53h) | 
| 1 | Reserved | 
| 2 | Reserved | 
| 3 | Reserved | 
| 4 | Reserved | 
| 5 | (MSB) Reservation Size (LSB) | 
| 6 | 
| 7 | 
| 8 | 
| 9 | Control Byte | 
Рисунок 5. Формат команды RESERVE TRACK
В поле Reservation size указывается размер трека в блоках. Каждый вызов команды RESERVE TRACK модифицирует PMA-область, добавляя в нее запись о координатах нового трека (таблица 4 первой части статьи). Считать информацию о координатах трека можно при помощи команды READ TRACK INFORMATION. Формат команды:
READ TRACK INFORMATION CDB
| Bit Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 
| 0 | Operation code   (52h) | 
| 1 | Reserved | Address/Number Type | 
| 2 | (MSB) Logical   Block Address/ Track/Session   Number (LSB) | 
| 3 | 
| 4 | 
| 5 | 
| 6 | Reserved | 
| 7 | (MSB) | Allocation Length (LSB) | 
| 8 | 
| 9 | Control Byte | 
Рисунок 6. Формат команды READ TRACK INFORMATION
Значение поля Address/Number Type определяет содержание поля Logical Block Address/Track/Session Number. Если Address/Number Type = 01b, то устройство вернет блок информации о треке, номер которого находится в поле Logical Block Address/Track/Session Number. Формат блока информации о треке представлен на рисунке.
Track Information Block
| Bit Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 
| 0 | (MSB) | Data Length (LSB) | 
| 1 | 
| 2 | Track   Number (Least Significant Byte) | 
| 3 | Session   Number  (Least Significant Byte) | 
| 4 | Reserved | 
| 5 | Reserved | Damage | Copy | Track Mode | 
| 6 | RT | Blank | Packet/Inc | FP | Data Mode | 
| 7 | Reserved | LRA_V | NWA_V | 
| 8 | (MSB) Track   Start Address (LSB) | 
| 9 | 
| 10 | 
| 11 | 
| 12 | (MSB) Next   Writable Address (LSB) | 
| 13 | 
| 14 | 
| 15 | 
| 16 | (MSB) Free Blocks (LSB) | 
| 17 | 
| 18 | 
| 19 | 
| 20 | (MSB) Fixed   Packet Size/ Blocking   Factor (LSB) | 
| 21 | 
| 22 | 
| 23 | 
| 24 | (MSB) Track Size   (LSB) | 
| 25 | 
| 26 | 
| 27 | 
| 28 | (MSB) Last   Recorded Address   (LSB) | 
| 29 | 
| 30 | 
| 31 | 
| 32 | Track   Number   (Most Significant Byte) | 
| 33 | Session   Number   (Most Significant Byte) | 
| 34 | Reserved | 
| 35 | Reserved | 
| 36 | (MSB) Read   Compatibility LBA (LSB) | 
| …… | 
| 39 | 
Рисунок 7. Формат блока информации о треке
Из всего многообразия данных, находящихся в блоке, нас интересует только поле Track Start Address. Это поле содержит стартовый адрес трека, с этого адреса выполняется запись трека. Назначение остальных полей приведено в спецификации SCSI MMC-4 ([1]).
Функция reserv_track() выполняет резервирование на компакт-диске пространства для трека. Входной параметр функции – размер трека в блоках.
int reserv_track(__u32 track_size)
{
    __u8 reserv_track_cmd[10];
    __u32 size = 0;
    if(test_unit_ready() < 0) exit(-1);
/* Формируем командный пакет */
    memset(reserv_track_cmd, 0, 10);
    reserv_track_cmd[0] = 0x53; // код команды RESERVE TRACK
/* Заполняем поле Reservation size */
    size = __swab32(track_size);
    memcpy((void *)(reserv_track_cmd + 5), (void *)&size, 4);
    if(send_cmd(reserv_track_cmd, 10, SG_DXFER_NONE, NULL, 0, 20000) < 0) return -1;
/* Cчитываем PMA */
    read_pma();
    return 0;
}
Чтение информации о треке выполняет функция read_track_info(). Параметр функции – номер трека. Возвращаемое значение – стартовый адрес трека.
__u32 read_track_info(int trk_num)
{
    __u8 read_track_info_cmd[10];
    __u8 data_buff[40]; // блок информации о треке
    __u16 len = 40; // размер блока
    __u32 lba = 0;
    if(test_unit_ready() < 0) exit(-1);
/* Формируем командный пакет */
    memset(data_buff, 0, 40);
    memset(read_track_info_cmd, 0, 10);
    read_track_info_cmd[0] = 0x52;
    // поле Address/Number Type = 01b
    read_track_info_cmd[1] = 1;
    read_track_info_cmd[5] = trk_num; // номер трека
    len = __swab16(len);
    memcpy((void *)(read_track_info_cmd + 7), (void *)&len, 2);
    if(send_cmd(read_track_info_cmd, 10, SG_DXFER_FROM_DEV, data_buff, 40, 20000) < 0) {
    printf("Cannot read track #%d info!\n", trk_num);
    exit(-1);
    }
/* Из блока информации о треке считываем данные о размере трека (поле Track Size ) */
    memcpy((void *)&lba, (void *)(data_buff + 24), 4);
    printf("Track #%d size - %u sectors\n", trk_num, __swab32(lba));
/* Стартовый адрес трека */
    memcpy((void *)&lba, (void *)(data_buff + 8), 4);
    return __swab32(lba);
}
Рассмотрим главную функцию программы записи аудиоданных. Входные параметры – список файлов в формате Ogg Vorbis.
int main(int argc, char **argv)
{
    int i = 1;
    struct stat s;
    __u32 start_lba = 0, total_sectors = 0;
    __u32 track_size = 0;
/* Проверяем наличие входных параметров */
    if(argc == 1) {
    printf("\n\tUsage: write_audio [OGG-files]\n\n");
    return 0;
    }
/* Открываем файл устройства */
    sg_fd = open(SG_DEV, O_RDWR);
/* Устанавливаем параметры режима записи: тип блока данных dbt = 0 – «сырые» данные, размер блока 2352 байта;
 * track_mode = 0 – трек содержит аудиоданные.
 */
    mode_select(0, 0);
/* Считываем информацию из PMA */
    printf("Display PMA:\n");
    read_pma();
/* Цикл записи треков на диск */
    for(i = 1; i < argc; i++) {
/* Преобразуем файл из формата Ogg Vorbis в WAV (без заголовка). Имя выходного файла – track.cdr */
    ogg_decoder(argv[i]);
/* Определяем размер файла track.cdr в байтах*/
    memset((void *)&s, 0, sizeof(struct stat));
    stat("./track.cdr", &s);
/* Размер файла в блоках */
    track_size = s.st_size / CD_FRAMESIZE_RAW;
    printf("File size - %u\n", s.st_size);
    printf("Sectors in file - %u\n", track_size);
    printf("Reserve track #%d\n", i);
/* Резервируем пространство для трека */
    reserv_track(track_size);
/* Определяем стартовый адрес трека */
    start_lba = read_track_info(i);
    printf("Start LBA for track #%d - %u\n", i, start_lba);
/* Записываем в трек аудиоданные. Номер стартового блока равен start_lba */
    total_sectors = write_audio("./track.cdr", start_lba);
    if(total_sectors == 0) return -1;
/* Закрываем трек */
    close_track(i);
    }
/* Цикл записи треков завершен. Закрываем сессию */
    close_session();
/* Удаляем файл track.cdr и извлекаем диск из привода */
    unlink("./track.cdr");
    eject_cd();
    close(sg_fd);
    return 0;
}
Преобразование файла из формата Ogg Vorbis в WAV выполняет функция ogg_decoder(). Входной параметр функции – имя файла в формате Ogg Vorbis. Для конвертирования используются библиотека libvorbis (http://www.vorbis.org). На выходе получается файл track.cdr в формате WAV, но без RIFF-заголовка.
void ogg_decoder(__u8 *file_name)
{
    __u8 pcmout[8192];
    FILE *f;
    int out;
    OggVorbis_File vf;
    int current_section;
    printf("\nDecoding file %s..", file_name);
    f = fopen(file_name,"r");
    out = open("./track.cdr", O_CREAT|O_RDWR|O_TRUNC, 0600);
    if(ov_open(f, &vf, NULL, 0) < 0) {
    printf("Input does not appear to be an Ogg bitstream.\n");
    exit(-1);
    }
    for(;;) {
           memset(pcmout, 0, 8192);
           long ret = ov_read(&vf, pcmout, sizeof(pcmout), 0, 2, 1, ¤t_section);
           if (ret == 0) break;
           if(ret < 0) {
                 printf("Error OGG bitsream");
                 exit(-1);
           }
           if(write(out, pcmout, ret) < 0) {
                 printf("write");
                 exit(-1);
           }
    }
    fclose(f);
    close(out);
    printf("OK\n");
    return;
}
Функция write_audio() выполняет запись аудиотрека на диск. Входные параметры – имя файла WAV-формата (без заголовка), содержащего аудиоданные, и стартовый адрес, с которого начинается запись. Возвращаемое значение – общее число записанных на диск блоков:
__u32 write_audio(__u8 *file_name, __u32 start_lba)
{
    int in_f;
    __u8 write_cmd[10];
    // блок для  аудиоданных, 2352 байт
    __u8 write_buff[CD_FRAMESIZE_RAW];
    __u32 lba = 0, lba1 = 0;
    if(test_unit_ready() < 0) exit(-1);
    lba1 = start_lba;
/* Открываем файл с аудиоданными */
    in_f = open(file_name, O_RDONLY, 0600);
    memset(write_buff, 0, CD_FRAMESIZE_RAW);
/* Цикл чтения данных из файла и записи */
    while(read(in_f, write_buff, CD_FRAMESIZE_RAW) > 0) {
/* Формируем командный пакет */
    memset(write_cmd, 0, 10);
    write_cmd[0] = WRITE_10;
    write_cmd[8] = 1; // число блоков для записи
    printf("%c", 0x0D);
    printf("lba - %6d", lba1);
    // адрес блока, в который выполняется запись данных
    lba = __swab32(lba1);
    memcpy((write_cmd + 2), (void *)&lba, 4);
    lba1 += 1;
    if(send_cmd(write_cmd, 10, SG_DXFER_TO_DEV, write_buff, CD_FRAMESIZE_RAW, 20000) < 0) return 0;
    memset(write_buff, 0, CD_FRAMESIZE_RAW);
    }
    close(in_f);
    printf("\n");
    return lba1; // число блоков, записанных на диск
}
Полный текст программы создания аудиодиска из файлов формата Ogg Vorbis находится с файле SG/ogg2cdda.c.
Получаем исполняемый файл при помощи команды:
# gcc -o ogg2cdda ogg2cdda.c -lvorbis -lvorbisfile
Рассмотрим пример работы программы ogg2cdda. Устанавливаем в привод диск CD-RW и запускаем программу на выполнение, указав в командной строке имена трех файлов в формате Ogg Vorbis:
# ./ogg2cdda trk1.ogg trk2.ogg trk3.ogg
Результаты работы программы:
| Decoding file trk1.ogg..OK File size – 41505744 bytes Sectors in file - 17647 Reserve track #1 PMA data length - 24 PMA entries - 2 Entry      ADR      CTRL      Point      Min      Sec      Frame      PMin      Psec      Pframe            LBA 0            2      0      0      80      71      82      0      0      0            --- 1            1      0      1      3      57      24      0      2      0            0 Track #1 size - 17647 sectors Start LBA for track #1 - 0   Decoding file trk2.ogg..OK File size – 36604176 bytes Sectors in file - 15563 Reserve track #2 PMA data length - 35 PMA entries - 3 Entry      ADR      CTRL      Point      Min      Sec      Frame      PMin      Psec      Pframe            LBA 0            2      0      0      80      71      82      0      0      0            --- 1            1      0      1      3      57      24      0      2      0            0 2            1      0      2      7      26      64      3      59      24            17799 Track #2 size - 15563 sectors Start LBA for track #2 - 17799   Decoding file trk3.ogg..OK File size – 47684448 bytes Sectors in file - 20274 Reserve track #3 PMA data length - 46 PMA entries - 4 Entry      ADR      CTRL      Point      Min      Sec      Frame      PMin      Psec      PFrame            LBA 0            2      0      0      80      71      82      0      0      0            --- 1            1      0      1      3      57      24      0      2      0            0 2            1      0      2      7      26      64      3      59      24            17799 3            1      0      3      11      59      15      7      28      64            33514 Track #3 size - 20274 sectors Start LBA for track #3 - 33514   Close session..OK | 
Хорошо видно, что после каждого вызова функции reserv_track в PMA добавляется запись о новом треке. Расстояние между треками – 2 секунды.
Теперь рассмотрим пример записи аудиоданных без предварительного резервирования треков. Для определения стартового адреса трека при помощи команды READ TRACK INFORMATION считывается адрес невидимого, незавершенного (invisible) трека. В этом случае поле Address/Number Type команды READ TRACK INFORMATION содержит 01b, поле Logical Block Address/Track/Session Number – значение 0xFF (см. табл. 452, стр. 374 спецификации [1]).
Функция определения стартового адреса невидимого трека:
__u32 read_track_info()
{
    __u8 read_track_info_cmd[10];
    __u8 data_buff[40]; // блок информации о треке
    __u16 len = 40; // размер блока информации о треке
    __u32 lba = 0;
    if(test_unit_ready() < 0) exit(-1);
/* Формируем командный пакет */
    memset(data_buff, 0, 40);
    memset(read_track_info_cmd, 0, 10);
    read_track_info_cmd[0] = 0x52;
    read_track_info_cmd[1] = 1;
    read_track_info_cmd[5] = 0xFF; // invisible track
    len = __swab16(len);
    memcpy((void *)(read_track_info_cmd + 7), (void *)&len, 2);
    if(send_cmd(read_track_info_cmd, 10, SG_DXFER_FROM_DEV, data_buff, 40, 20000) < 0) {
    printf("Cannot read track info!\n");
    exit(-1);
    }
/* Стартовый адрес невидимого трека. С этого адреса будет выполняться запись */
    memcpy((void *)&lba, (void *)(data_buff + 8), 4);
    return __swab32(lba);
}
Рассмотрим главную функцию программы записи аудиоданных. Входные параметры – список файлов в формате MP3.
int main(int argc, char **argv)
{
    int i = 1;
    __u32 start_lba = 0, total_sectors = 0;
/* Проверяем входные параметры */
    if(argc == 1) {
    printf("\n\tUsage: write_audio [MP3-files]\n\n");
    return 0;
    }
/* Открываем файл устройства */
    if((sg_fd = open(SG_DEV, O_RDWR)) < 0) {
           perror("open");
           return -1;
    }
/* Устанавливаем параметры режима записи (см. предыдущий пример) */
    mode_select(0, 0);
/* Цикл записи треков на диск */
    for(i = 1; i < argc; i++) {
/* Декодируем файл из MP3-формата в WAV */
    printf("\nDecoding file %s..\n", argv[i]);
    mp3_decoder(argv[i]);
/* Определяем стартовый адрес трека */
    start_lba = read_track_info();
    printf("Start LBA for track #%d - %u\n", i, start_lba);
/* Записываем трек на диск и закрываем трек */
    total_sectors = write_audio("./track.wav", start_lba);
    if(total_sectors == 0) return -1;
    close_track(i);
    }
/* Цикл записи треков завершился, закрываем сессию */
    close_session();
/* Удаляем файл track.wav и извлекаем диск из привода */
    unlink("./track.wav");
    eject_cd();
    close(sg_fd);
    return 0;
}
Преобразование файлов из формата MP3 в WAV выполняет функция mp3_decoder(). Входные параметры функции – имя файла формата MP3. На выходе получаем файл track.wav в формате WAV. Для конвертирования используется программа mpg321:
void mp3_decoder(__u8 *file_name)
{
    static pid_t pid;
    int status;
    switch(pid = fork()) {
    case -1:
           perror("fork");
           exit(-1);
    case 0:
           execl("/usr/bin/mpg321", "mpg321", "-q", "-w", "track.wav", file_name, 0);
           exit(-1);
    }
    if((pid = waitpid(pid, &status, 0)) && ї
           WIFEXITED(status)) return;
}
При записи WAV-файла необходимо отбросить его заголовок. В функции записи файла write_audio() после открытия файла необходимо сместиться к началу аудиоданных при помощи вызова:
lseek(in_f, WAV_HEADER_SIZE, 0)
где WAV_HEADER_SIZE = 44 – размер заголовка WAV-файла.
Полный текст программы создания аудиодиска из файлов формата MP3 находится в файле SG/mp2cd_inv_track.c.
Следующий фрагмент программы выполняет запись треков, и стартовый адрес мы вычисляем самостоятельно:
total_sectors = 0;
/* Цикл записи треков */
    for(i = 1; i < argc; i++) {
           printf("\nWriting track #%d:\n", i);
/* Определяем стартовый адрес трека и записываем трек на диск. После этого закрываем трек */
    // стартовый адрес трека  
           if(i > 1) start_lba = total_sectors + apl + 2;
           printf("Start sector - %u\n", start_lba);
           total_sectors = write_audio(argv[i], start_lba);
           if(total_sectors == 0) return -1;
           close_track(i);
    }
Стартовый адрес первого трека нам известен – он равен нулю. Адрес нового трека вычисляется по формуле:
start_lba = total_sectors + apl + 2
где total_sector – общее число секторов, записанных на диск, apl – длина аудиопаузы, значение поля Audio Pause Length (APL) страницы параметров режима записи (рис. 15 первой части статьи).
Значение APL хранится в поле apl структуры wpm, и определяется следующим образом:
mode_sense();
apl = __swab16(wpm->apl);
В файле SG/write_audio.c находится полный текст программы, выполняющей запись WAV-файлов на аудиокомпакт. Стартовый адрес трека вычисляется вышеприведенным способом.
А теперь давайте сотрем с диска все, что мы на него записали. Для стирания информации с диска используется команда BLANK. Формат этой команды приведен на рис. 8.
Поле Blanking type устанавливает режим очистки диска. Может принимать следующие значения:
- 000b – стирается вся информация, находящаяся на диске. Значение поля Start Address игнорируется.
- 001b – минимальная очистка диска. Стирается PMA, Lead-In-область диска и pre-gap-область первого трека. Параметр Start Address игнорируется.
BLANK CDB
| Bit Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 
| 0 | Operation code   (A1h) | 
| 1 | Reserved | IMMED | Reserved | Blanking Type | 
| 2 | (MSB) Start   Address/Track Number   (LSB) | 
| 3 | 
| 4 | 
| 5 | 
| 6 | Reserved | 
| 7 | Reserved | 
| 8 | Reserved | 
| 9 | Reserved | 
| 10 | Reserved | 
| 11 | Control Byte | 
Рисунок 8. Формат команды BLANK
Остальные значения поля Blanking type и их описание приведены в спецификации [1], табл. 219 «Blanking Types CD-RW and DDCD-RW».
Функция blank() выполняет очистку CD-RW-диска. Параметр функции blank_type устанавливает режим очистки диска. Допустимые значения этого параметра – 0 или 1:
int blank(__u8 blank_type)
{
    __u8 blank_cmd[12];
    if(test_unit_ready() < 0) exit(-1);
    memset(blank_cmd, 0, 12);
    blank_cmd[0] = 0xA1; // код команды BLANK
    // режим очистки: 0 – полная, 1 – минимальная
    blank_cmd[1] = blank_type;
    if(send_cmd(blank_cmd, 12, SG_DXFER_NONE, NULL, 0, 9600*1000) < 0) return -1;
    return 0;
}
Полный текст программы очистки CD-RW-диска приведен в файле SG/blank.c.
Общее замечание по работе всех программ. Иногда программа после запуска выдает сообщение об ошибке:
| Sense data: 0x70 0x00 0x06 0x00 0x00 0x00 0x00 0x0a 0x00 0x00 0x00 0x00 0x28 0x00 0x00 0x00 0x00 0x00 Driver_status=0x28 Unit not ready | 
и завершает работу. Здесь Sense Key = 0x06, ASC = 0x28, ASCQ = 0x00 (см. [3, 5]), что означает «UNIT ATTENTION. Indicates that the removable medium may have been changed or the ATAPI CD-ROM Drive has been reset. NOT READY TO READY TRANSITION, MEDIUM MAY HAVE CHANGED» (Носитель был заменен или выполнен сброс ATAPI-контроллера).
При повторном запуске программа выполняется без ошибок.
Литература и ссылки:
- Спецификация SCSI Multimedia Commands-4 (SCSI MMC-4), http://www.t10.org/ftp/t10/drafts/mmc4/mmc4r03d.pdf.
- Спецификация SCSI-3 Multimedia Commands, http://www.t10.org/ftp/t10/drafts/mmc/mmc-r10a.pdf.
- Specification for ATAPI DVD Devices, ftp.seagate.com/sff/INF-8090.pdf.
- SCSI-Generic-HOWTO, http://www.linux.org/docs/ldp/howto/SCSI-Generic-HOWTO/index.html.
- Мешков В. «Пакетные команды интерфейса ATAPI» – Журнал «Системный администратор», № 9, сентябрь 2004 г. – 70-84 с.