Obtain TOC from non-CD via 52h READ TRACK INFORMATION

This commit is contained in:
Thomas Schmitt 2007-02-01 16:15:09 +00:00
parent dde52c1971
commit e876f90e6c
7 changed files with 385 additions and 58 deletions

View File

@ -149,9 +149,6 @@ Note: The support for sequentially recordable media is still emerging.
DVD-R have not been tested yet. If no Incremental Streaming feature (21h)
is offered, then the media is regarded as unsuitable without further
explanation. (One can torture DVD-RW to make them offer only DAO (2Fh)).
Multi-session works, but -toc is not implemented yet. -msinfo works although
otherwise predicted by MMC-3 to MMC-5. Be mistrusting when appending a third
session for the first time with your drive.
.br
---------------
.br

View File

@ -129,6 +129,7 @@ or
#define Cdrskin_libburn_from_pykix_svN 1
#define Cdrskin_libburn_has_get_msc1 1
#define Cdrskin_libburn_has_toc_entry_extensionS 1
#endif /* Cdrskin_libburn_0_3_1 */
@ -3509,8 +3510,17 @@ int Cdrskin_msinfo(struct CdrskiN *skin, int flag)
if(tracks==NULL || num_tracks<=0)
continue;
burn_track_get_entry(tracks[0],&toc_entry);
#ifdef Cdrskin_libburn_has_toc_entry_extensionS
if(toc_entry.extensions_valid&1) { /* DVD extension valid */
lba= toc_entry.start_lba;
} else {
#else
{
#endif
lba= burn_msf_to_lba(toc_entry.pmin,toc_entry.psec,toc_entry.pframe);
}
}
if(lba==-123456789) {
fprintf(stderr,"cdrskin: FATAL : Cannot find any track on CD\n");
{ret= 0; goto ex;}
@ -3522,7 +3532,15 @@ obtain_nwa:;
fprintf(stderr,
"cdrskin: NOTE : Guessing next writeable address from leadout\n");
burn_session_get_leadout_entry(sessions[num_sessions-1],&toc_entry);
#ifdef Cdrskin_libburn_has_toc_entry_extensionS
if(toc_entry.extensions_valid&1) { /* DVD extension valid */
aux_lba= toc_entry.start_lba;
} else {
#else
{
#endif
aux_lba= burn_msf_to_lba(toc_entry.pmin,toc_entry.psec,toc_entry.pframe);
}
if(num_sessions>0)
nwa= aux_lba+6900;
else
@ -3570,7 +3588,7 @@ ex:;
int Cdrskin_toc(struct CdrskiN *skin, int flag)
{
int num_sessions= 0,num_tracks= 0,lba= 0,track_count= 0,total_tracks= 0;
int session_no, track_no;
int session_no, track_no, pmin, psec, pframe;
struct burn_drive *drive;
struct burn_disc *disc= NULL;
struct burn_session **sessions;
@ -3599,11 +3617,26 @@ int Cdrskin_toc(struct CdrskiN *skin, int flag)
for(track_no= 0; track_no<num_tracks; track_no++) {
track_count++;
burn_track_get_entry(tracks[track_no], &toc_entry);
lba= burn_msf_to_lba(toc_entry.pmin,toc_entry.psec,toc_entry.pframe);
#ifdef Cdrskin_libburn_has_toc_entry_extensionS
if(toc_entry.extensions_valid&1) { /* DVD extension valid */
lba= toc_entry.start_lba;
burn_lba_to_msf(lba, &pmin, &psec, &pframe);
} else {
#else
{
#endif
pmin= toc_entry.pmin;
psec= toc_entry.psec;
pframe= toc_entry.pframe;
lba= burn_msf_to_lba(pmin,psec,pframe);
}
if(track_no==0 && burn_session_get_hidefirst(sessions[session_no]))
printf("cdrskin: NOTE : first track is marked as \"hidden\".\n");
printf("track: %2d lba: %9d (%9d) %2.2u:%2.2u:%2.2u",track_count,
lba,4*lba,toc_entry.pmin,toc_entry.psec,toc_entry.pframe);
printf("track: %2d lba: %9d (%9d) %2.2d:%2.2d:%2.2d",track_count,
lba,4*lba,pmin,psec,pframe);
printf(" adr: %d control: %d",toc_entry.adr,toc_entry.control);
/* >>> From where does cdrecord take "mode" ? */
@ -3618,9 +3651,23 @@ int Cdrskin_toc(struct CdrskiN *skin, int flag)
if((flag&1) && session_no<num_sessions-1)
continue;
burn_session_get_leadout_entry(sessions[session_no],&toc_entry);
lba= burn_msf_to_lba(toc_entry.pmin,toc_entry.psec,toc_entry.pframe);
printf("track:lout lba: %9d (%9d) %2.2u:%2.2u:%2.2u",
lba,4*lba,toc_entry.pmin,toc_entry.psec,toc_entry.pframe);
#ifdef Cdrskin_libburn_has_toc_entry_extensionS
if(toc_entry.extensions_valid&1) { /* DVD extension valid */
lba= toc_entry.start_lba;
burn_lba_to_msf(lba, &pmin, &psec, &pframe);
} else {
#else
{
#endif
pmin= toc_entry.pmin;
psec= toc_entry.psec;
pframe= toc_entry.pframe;
lba= burn_msf_to_lba(pmin,psec,pframe);
}
printf("track:lout lba: %9d (%9d) %2.2d:%2.2d:%2.2d",
lba,4*lba,pmin,psec,pframe);
printf(" adr: %d control: %d",toc_entry.adr,toc_entry.control);
printf(" mode: -1\n");
}

View File

@ -1 +1 @@
#define Cdrskin_timestamP "2007.01.31.173611"
#define Cdrskin_timestamP "2007.02.01.161634"

View File

@ -241,9 +241,18 @@ enum burn_drive_status
};
/** Information about a track on a disc - this is from the q sub channel of the
lead-in area of a disc. The documentation here is very terse.
See a document such as mmc3 for proper information.
CAUTION : This structure is prone to future extension !
Do not restrict your application to unsigned char with any counter like
"session", "point", "pmin", ...
Do not rely on the current size of a burn_toc_entry.
ts A70201 : DVD extension, see below
*/
struct burn_toc_entry
{
@ -267,6 +276,23 @@ struct burn_toc_entry
unsigned char psec;
/** Track start time frames for normal tracks */
unsigned char pframe;
/* Indicates wether extension data are valid and eventually override
older elements in this structure:
bit0= DVD extension is valid
*/
unsigned char extensions_valid;
/* ts A70201 : DVD extension.
If invalid the members are guaranteed to be 0. */
/* Tracks and session numbers are 16 bit. Here are the high bytes. */
unsigned char session_msb;
unsigned char point_msb;
/* pmin, psec, and pframe may be too small if DVD extension is valid */
int start_lba;
/* min, sec, and frame may be too small if DVD extension is valid */
int track_blocks;
};

View File

@ -150,6 +150,25 @@ int mmc_function_spy_ctrl(int do_tell)
}
/* ts A70201 */
int mmc_four_char_to_int(unsigned char *data)
{
return (data[0] << 24) | (data[1] << 16) |
(data[2] << 8) | data[3];
}
/* ts A70201 */
int mmc_int_to_four_char(unsigned char *data, int num)
{
data[0] = (num >> 24) & 0xff;
data[1] = (num >> 16) & 0xff;
data[2] = (num >> 8) & 0xff;
data[3] = num & 0xff;
return 1;
}
void mmc_send_cue_sheet(struct burn_drive *d, struct cue_sheet *s)
{
struct buffer buf;
@ -171,16 +190,16 @@ void mmc_send_cue_sheet(struct burn_drive *d, struct cue_sheet *s)
d->issue_command(d, &c);
}
/* ts A61110 : added parameters trackno, lba, nwa. Redefined return value.
@return 1=nwa is valid , 0=nwa is not valid , -1=error */
int mmc_get_nwa(struct burn_drive *d, int trackno, int *lba, int *nwa)
/* ts A70201 :
Common track info fetcher for mmc_get_nwa() and mmc_fake_toc()
*/
int mmc_read_track_info(struct burn_drive *d, int trackno, struct buffer *buf)
{
struct buffer buf;
struct command c;
unsigned char *data;
int i;
mmc_function_spy("mmc_get_nwa");
mmc_function_spy("mmc_read_track_info");
c.retry = 1;
c.oplen = sizeof(MMC_TRACK_INFO);
memcpy(c.opcode, MMC_TRACK_INFO, sizeof(MMC_TRACK_INFO));
@ -196,6 +215,52 @@ int mmc_get_nwa(struct burn_drive *d, int trackno, int *lba, int *nwa)
else /* mmc5r03c.pdf: valid only for CD, DVD+R, DVD+R DL */
trackno = 0xFF;
}
for (i = 0; i < 4; i++)
c.opcode[2 + i] = (trackno >> (24 - 8 * i)) & 0xff;
c.page = buf;
memset(buf->data, 0, BUFFER_SIZE);
c.dir = FROM_DRIVE;
d->issue_command(d, &c);
if (c.error)
return 0;
return 1;
}
/* ts A61110 : added parameters trackno, lba, nwa. Redefined return value.
@return 1=nwa is valid , 0=nwa is not valid , -1=error */
/* ts A70201 : outsourced 52h READ TRACK INFO command */
int mmc_get_nwa(struct burn_drive *d, int trackno, int *lba, int *nwa)
{
struct buffer buf;
int ret;
#ifdef Libburn_get_nwa_standalonE
struct command c;
int i;
#endif
unsigned char *data;
mmc_function_spy("mmc_get_nwa");
if(trackno<=0) {
if (d->current_profile == 0x1a || d->current_profile == 0x13 ||
d->current_profile == 0x12 )
/* DVD+RW , DVD-RW restricted overwrite , DVD-RAM */
trackno = 1;
else if (d->current_profile == 0x11 ||
d->current_profile == 0x14) /* DVD-R[W] Sequential */
trackno = d->last_track_no;
else /* mmc5r03c.pdf: valid only for CD, DVD+R, DVD+R DL */
trackno = 0xFF;
}
#ifdef Libburn_get_nwa_standalonE
c.retry = 1;
c.oplen = sizeof(MMC_TRACK_INFO);
memcpy(c.opcode, MMC_TRACK_INFO, sizeof(MMC_TRACK_INFO));
c.opcode[1] = 1;
for (i = 0; i < 4; i++)
c.opcode[2 + i] = (trackno >> (24 - 8 * i)) & 0xff;
c.page = &buf;
@ -203,6 +268,16 @@ int mmc_get_nwa(struct burn_drive *d, int trackno, int *lba, int *nwa)
d->issue_command(d, &c);
data = c.page->data;
#else /* Libburn_get_nwa_standalonE */
ret = mmc_read_track_info(d, trackno, &buf);
if (ret <= 0)
return ret;
data = buf.data;
#endif /* ! Libburn_get_nwa_standalonE */
*lba = (data[8] << 24) + (data[9] << 16)
+ (data[10] << 8) + data[11];
*nwa = (data[12] << 24) + (data[13] << 16)
@ -410,9 +485,156 @@ int mmc_write(struct burn_drive *d, int start, struct buffer *buf)
return 0;
}
/* ts A70201 : Set up an entry for mmc_fake_toc() */
int mmc_fake_toc_entry(struct burn_toc_entry *entry, int session_number,
int track_number,
unsigned char *size_data, unsigned char *start_data)
{
int min, sec, frames, num;
/* mark DVD extensions as valid */
entry->extensions_valid |= 1;
/* defaults are as of mmc5r03.pdf 6.26.3.2.4 Fabricated TOC */
entry->session = session_number & 0xff;
entry->session_msb = (session_number >> 8) & 0xff;
entry->adr = 1;
entry->control = 4;
entry->tno = 0;
entry->point = track_number & 0xff;
entry->point_msb = (track_number >> 8) & 0xff;
num = (size_data[0] << 24) | (size_data[1] << 16) |
(size_data[2] << 8) | size_data[3];
entry->track_blocks = num;
burn_lba_to_msf(num, &min, &sec, &frames);
if (min > 255) {
min = 255;
sec = 255;
frames = 255;
}
entry->min = min;
entry->sec = sec;
entry->frame = frames;
entry->zero = 0;
num = (start_data[0] << 24) | (start_data[1] << 16) |
(start_data[2] << 8) | start_data[3];
entry->start_lba = num;
burn_lba_to_msf(num, &min, &sec, &frames);
if (min > 255) {
min = 255;
sec = 255;
frames = 255;
}
entry->pmin = min;
entry->psec = sec;
entry->pframe = frames;
return 1;
}
/* ts A70131 : compose a disc TOC structure from d->complete_sessions
and 52h READ TRACK INFORMATION */
int mmc_fake_toc(struct burn_drive *d)
{
struct burn_track *track;
struct burn_session *session;
struct burn_toc_entry *entry;
struct buffer buf;
int i, session_number, prev_session = -1, ret, lba;
unsigned char *tdata, size_data[4], start_data[4];
if (d->last_track_no <= 0 || d->complete_sessions <= 0 ||
d->status == BURN_DISC_BLANK)
return 2;
d->disc = burn_disc_create();
if (d->disc == NULL)
return -1;
d->toc_entries = d->last_track_no + d->complete_sessions;
d->toc_entry = malloc(d->toc_entries * sizeof(struct burn_toc_entry));
if (d->toc_entry == NULL)
return -1;
memset(d->toc_entry, 0,d->toc_entries * sizeof(struct burn_toc_entry));
for (i = 0; i < d->complete_sessions; i++) {
session = burn_session_create();
burn_disc_add_session(d->disc, session, BURN_POS_END);
burn_session_free(session);
}
memset(size_data, 0, 4);
memset(start_data, 0, 4);
/* Entry Layout :
session 1 track 1 entry 0
...
session 1 track N entry N-1
leadout 1 entry N
session 2 track N+1 entry N+1
...
session 2 track M+1 entry M+1
leadout 2 entry M+2
session X track K entry (K-1)+(X-1)
...
session X track i+1 entry i+(X-1)
leadout X entry i+X
*/
for (i = 0; i < d->last_track_no; i++) {
ret = mmc_read_track_info(d, i+1, &buf);
if (ret < 0)
return ret;
if (ret == 0)
continue;
tdata = buf.data;
session_number = (tdata[33] << 8) | tdata[3];
if (session_number <= 0)
continue;
if (session_number != prev_session && prev_session > 0) {
entry = &(d->toc_entry[(i - 1) + prev_session]);
lba = mmc_four_char_to_int(start_data) +
mmc_four_char_to_int(size_data);
mmc_int_to_four_char(start_data, lba);
mmc_int_to_four_char(size_data, 0);
mmc_fake_toc_entry(entry, session_number, 0xA2,
size_data, start_data);
entry->min= entry->sec= entry->frame= 0;
d->disc->session[prev_session - 1]->leadout_entry =
entry;
}
if (session_number > d->disc->sessions)
continue;
entry = &(d->toc_entry[i + session_number - 1]);
track = burn_track_create();
if (track == NULL)
return -1;
burn_session_add_track(
d->disc->session[session_number - 1],
track, BURN_POS_END);
track->entry = entry;
burn_track_free(track);
memcpy(size_data, tdata + 24, 4);
memcpy(start_data, tdata + 8, 4);
mmc_fake_toc_entry(entry, session_number, i + 1,
size_data, start_data);
if (session_number < d->disc->sessions) {
if (prev_session != session_number)
d->disc->session[session_number - 1]->
firsttrack = i+1;
d->disc->session[session_number - 1]->lasttrack = i+1;
}
prev_session = session_number;
}
return 1;
}
void mmc_read_toc(struct burn_drive *d)
{
/* read full toc, all sessions, in m/s/f form, 4k buffer */
/* ts A70201 : or fake a toc from track information */
struct burn_track *track;
struct burn_session *session;
struct buffer buf;
@ -422,19 +644,24 @@ void mmc_read_toc(struct burn_drive *d)
unsigned char *tdata;
mmc_function_spy("mmc_read_toc");
memcpy(c.opcode, MMC_GET_TOC, sizeof(MMC_GET_TOC));
if(!d->current_is_cd_profile) {
if (!(d->current_profile == -1 || d->current_is_cd_profile)) {
/* ts A70131 : MMC_GET_TOC uses Response Format 2
For DVD this fails with 5,24,00 */
/* One could try Response Format 0: mmc5r03.pdf 6.26.3.2 */
/* One could try
51h READ DISC INFORMATION and 52h READ TRACK INFORMATION
where 51h gives the number of tracks and 52h tells the
session number with each track. */
/* >>> One must do someting */;
/* One could try Response Format 0: mmc5r03.pdf 6.26.3.2
which does not yield the same result wit the same disc
on different drives.
*/
/* ts A70201 :
This uses the session count from 51h READ DISC INFORMATION
and the track records from 52h READ TRACK INFORMATION
*/
mmc_fake_toc(d);
if (d->status == BURN_DISC_UNREADY)
d->status = BURN_DISC_FULL;
return;
}
memcpy(c.opcode, MMC_GET_TOC, sizeof(MMC_GET_TOC));
c.retry = 1;
c.oplen = sizeof(MMC_GET_TOC);
c.page = &buf;
@ -461,6 +688,7 @@ void mmc_read_toc(struct burn_drive *d)
d->toc_entries = 0;
/* Prefering memory leaks over fandangos */
d->toc_entry = malloc(sizeof(struct burn_toc_entry));
memset(&(d->toc_entry[0]), 0, sizeof(struct burn_toc_entry));
return;
}
@ -474,6 +702,8 @@ void mmc_read_toc(struct burn_drive *d)
a ssert(((dlen - 2) % 11) == 0);
*/
d->toc_entry = malloc(d->toc_entries * sizeof(struct burn_toc_entry));
for (i = 0; i < d->toc_entries; i++)
memset(&(d->toc_entry[i]), 0, sizeof(struct burn_toc_entry));
tdata = c.page->data + 4;
burn_print(12, "TOC:\n");
@ -497,8 +727,7 @@ void mmc_read_toc(struct burn_drive *d)
burn_print(bpl, " MSF(%d:%d:%d)", tdata[4],tdata[5],tdata[6]);
burn_print(bpl, " PMSF(%d:%d:%d %d)",
tdata[8], tdata[9], tdata[10],
burn_msf_to_lba(tdata[8], tdata[9], tdata[10])
);
burn_msf_to_lba(tdata[8], tdata[9], tdata[10]));
burn_print(bpl, " - control %d, adr %d\n", tdata[1] & 0xF,
tdata[1] >> 4);
@ -550,7 +779,8 @@ void mmc_read_toc(struct burn_drive *d)
/* ts A61022 */
burn_print(bpl, "-----------------------------------\n");
if (d->status != BURN_DISC_APPENDABLE)
/* ts A70131 : was (d->status != BURN_DISC_BLANK) */
if (d->status == BURN_DISC_UNREADY)
d->status = BURN_DISC_FULL;
toc_find_modes(d);
}
@ -571,11 +801,13 @@ int mmc_read_multi_session_c1(struct burn_drive *d, int *trackno, int *start)
My drives return useful data, though.
MMC-3 states that DVD had not tracks. So maybe this fake is
a legacy ?
>>> Possibly one will have to fake this reply from
51h READ DISC INFORMATION and 52h READ TRACK INFORMATION
(I still have to find out what growisofs is using)
*/
/* >>>
mmc_fake_toc() meanwhile tries to establish a useable TOC.
Evaluate this first before issueing a MMC command.
*/
memcpy(c.opcode, MMC_GET_MSINFO, sizeof(MMC_GET_MSINFO));
c.retry = 1;
c.oplen = sizeof(MMC_GET_MSINFO);
@ -602,6 +834,8 @@ void mmc_read_disc_info(struct burn_drive *d)
unsigned char *data;
struct command c;
char msg[160];
/* ts A70131 : had to move mmc_read_toc() to end of function */
int do_read_toc = 0, session_state;
/* ts A61020 */
d->start_lba = d->end_lba = -2000000000;
@ -647,10 +881,6 @@ void mmc_read_disc_info(struct burn_drive *d)
data = c.page->data;
d->erasable = !!(data[2] & 16);
/*
fprintf(stderr, "libburn_experimental: data[2]= %d 0x%x\n",
(unsigned) data[2], (unsigned) data[2]);
*/
switch (data[2] & 3) {
case 0:
d->toc_entries = 0;
@ -668,8 +898,9 @@ void mmc_read_disc_info(struct burn_drive *d)
case 1:
d->status = BURN_DISC_APPENDABLE;
case 2:
if (d->current_profile == -1 || d->current_is_cd_profile)
mmc_read_toc(d);
if ((data[2] & 3) == 2)
d->status = BURN_DISC_FULL;
do_read_toc = 1;
break;
}
@ -687,13 +918,22 @@ void mmc_read_disc_info(struct burn_drive *d)
*/
d->bg_format_status = data[7] & 3;
if (d->status == BURN_DISC_BLANK)
if (d->status == BURN_DISC_BLANK) {
d->last_track_no = 1; /* The "incomplete track" */
else
d->complete_sessions = 0;
} else {
/* ts A70131 : number of non-empty sessions */
d->complete_sessions = (data[9] << 8) | data[4];
session_state = (data[2] >> 2) & 3;
/* mmc5r03c.pdf 6.22.3.1.3 State of Last Session: 3=complete */
if (session_state != 3 && d->complete_sessions >= 1)
d->complete_sessions--;
/* ts A70129 : mmc5r03c.pdf 6.22.3.1.7
This includes the "incomplete track" if the disk is
appendable. I.e number of complete tracks + 1. */
d->last_track_no = (data[11] << 8) | data[6];
}
/* Preliminarily declare blank:
ts A61219 : DVD+RW (is not bg_format_status==0 "blank")
@ -703,6 +943,9 @@ void mmc_read_disc_info(struct burn_drive *d)
if (d->current_profile == 0x1a || d->current_profile == 0x13 ||
d->current_profile == 0x12)
d->status = BURN_DISC_BLANK;
if (do_read_toc)
mmc_read_toc(d);
}
void mmc_read_atip(struct burn_drive *d)
@ -1885,6 +2128,7 @@ int mmc_setup_drive(struct burn_drive *d)
d->needs_close_session = 0;
d->bg_format_status = -1;
d->num_format_descr = 0;
d->complete_sessions = 0;
d->last_track_no = 1;
return 1;

View File

@ -199,10 +199,14 @@ struct burn_drive
int start_lba;
int end_lba;
/* ts A70131 : from 51h READ DISC INFORMATION Number of Sessions (-1)*/
int complete_sessions;
/* ts A70129 :
from 51h READ DISC INFORMATION Last Track Number in Last Session */
int last_track_no;
int toc_temp;
struct burn_disc *disc; /* disc structure */
int block_types[4];

View File

@ -375,7 +375,7 @@ int telltoc_formatlist(struct burn_drive *drive)
int telltoc_toc(struct burn_drive *drive)
{
int num_sessions = 0 , num_tracks = 0 , lba = 0;
int num_sessions = 0 , num_tracks = 0 , lba = 0, pmin, psec, pframe;
int track_count = 0;
int session_no, track_no;
struct burn_disc *disc= NULL;
@ -397,27 +397,36 @@ int telltoc_toc(struct burn_drive *drive)
for(track_no= 0; track_no<num_tracks; track_no++) {
track_count++;
burn_track_get_entry(tracks[track_no], &toc_entry);
lba= burn_msf_to_lba(toc_entry.pmin, toc_entry.psec,
toc_entry.pframe);
if (toc_entry.extensions_valid & 1) {
/* DVD extension valid */
lba = toc_entry.start_lba;
burn_lba_to_msf(lba, &pmin, &psec, &pframe);
} else {
pmin = toc_entry.pmin;
psec = toc_entry.psec;
pframe = toc_entry.pframe;
lba= burn_msf_to_lba(pmin, psec, pframe);
}
printf("Media content: session %2d ", session_no+1);
printf("track %2d %s lba: %9d %2.2u:%2.2u:%2.2u\n",
printf("track %2d %s lba: %9d %4.2d:%2.2d:%2.2d\n",
track_count,
((toc_entry.control&7)<4?"audio":"data "),
lba,
toc_entry.pmin,
toc_entry.psec,
toc_entry.pframe);
lba, pmin, psec, pframe);
}
burn_session_get_leadout_entry(sessions[session_no],
&toc_entry);
lba = burn_msf_to_lba(toc_entry.pmin,
toc_entry.psec, toc_entry.pframe);
if (toc_entry.extensions_valid & 1) {
lba = toc_entry.start_lba;
burn_lba_to_msf(lba, &pmin, &psec, &pframe);
} else {
pmin = toc_entry.pmin;
psec = toc_entry.psec;
pframe = toc_entry.pframe;
lba= burn_msf_to_lba(pmin, psec, pframe);
}
printf("Media content: session %2d ", session_no+1);
printf("leadout lba: %9d %2.2u:%2.2u:%2.2u\n",
lba,
toc_entry.pmin,
toc_entry.psec,
toc_entry.pframe);
printf("leadout lba: %9d %4.2d:%2.2d:%2.2d\n",
lba, pmin, psec, pframe);
}
if (disc!=NULL)
burn_disc_free(disc);