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