ВЛАДИМИР МЕШКОВ
Запись 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.