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