FORUM
UK Gig Guide
UK Music Venues
Music News
Directory
ENCYCLOPAEDIA
History
Composers
Quotations
World
Instruments
Calendar
TECHNICAL
Hardware
MIDI
Audio
Science
Programming
THEORY
Tutorials
Notation
Tools
Home | Technical | Programming | Linux ATAPI CD Interface

Linux ATAPI CD Interface

Author: Kannan Vijayan

This is a little document that describes how to go about doing various operations on a Music CD using an ATAPI CD-ROM drive in Linux. Before any code can be written, you must know how the data on a music CD is arranged.

A music CD has 2 main parts, a header section, and the body. The header describes how the music is arranged on the CD, and the body contains the actual music data.

The Header

The first thing the header describes is the index of the first and last track. The "first track" index is zero, so you can pretty much ignore this. The "last track" is equal to the number of tracks on disc. So if you have a CD with only one track on it, the "first track" will equal zero, and the "last track" will equal one. "If there is only one track, why do the first track and last track have different values?" you might ask. This is because the CD always has one extra "empty" track called the "leadout" track. This becomes useful later on as you?ll see.

The next set of information contained in the header is a list of track addresses. This is simply a list of numbers that describe where each track begins. There is no other information available. The length of a track is calculated by subtracting its address from the address of the track after it, and then performing some number manipulations. The leadout track becomes useful here for calculating the length of the last track. The address of the leadout track describes the end address of the CD.

The Body

The body of the CD is nothing but data. It is simply one big stream of music bytes. There is no indication of where a track ends or begins ? all that information is available in the header.

Programming

Now we can start looking at the interface which Linux provides for handling music CDs. Please keep in mind that I?m going to be doing all the coding for this in plain vanilla C.

In UNIX systems, all devices (e.g. hard drives, printers, CD-ROM drives), are represented as files. Before manipulating a CD, a descriptor for the CD-ROM device must be obtained.

All the descriptions for the structures needed for CD manipulation are held in the include file. is also needed for the ioctl system call.

To control a CD, an ioctl call must be made on the file descriptor with the appropriate arguments. The first argument for ioctl is the file descriptor of the device you want to control (in this case, the CD-ROM device). The second argument is an integer "request" of the operation to perform on the descriptor. The third argument can be anything, and it varies depending on what the "request" argument is.

Discovering the number of tracks on a CD

To read the first track and last track indexes from a CD, call ioctl with the first argument being the file descriptor. The second argument (the "request") should be CDROMREADTOCHDR, which is defined in . The third argument should be a pointer to struct cdrom_tochdr. The pointer should already have pre-allocated space before making this call: ioctl will not allocate the memory automatically. The structure cdrom_tochdr has two elements in it: cdth_trk0 and cdth_trk1 (both integers). cdth_trk0 holds the first track index, and cdth_trk1 holds the last track index. Here?s some example code for finding this information:

struct cdrom_tochdr header; /* the header */
int fd; /* the CDROM file descriptor */
int first_track, last_track;
int number_of_tracks;

/***********
    code for opening file descriptor to CD device
***********/

/* read the header information */
if(ioctl(fd, CDROMREADTOCHDR, &header) != 0) {
    fprintf(stderr, "Error in calling ioctl\n");     /* error handling */
    exit(0);
}

/* store the resulting information */
 first_track = header.cdth_trk0;    /* you can assume this to be zero */
last_track = header.cdth_trk1;
number_of_tracks = last_track;    /*  this is perfectly valid */

/* print the information */
printf("This CD has %d tracks\n", number_of_tracks);
Addressing

Before going any further, a discussion of address formats is in order. There are two main addressing formats to choose from: LBA and MSF. LBA stands for "Logical Block Addressing". MSF stands for "Minute Second Frame". The MSF format is more suited for music CD handling than LBA. Each value of the MSF address stands for an offset. The "minute" value represents the number of minutes from the beginning of the disc. The "second" value represents the offset in seconds from the minute address, and the "frame" value represents the offset in frames from the minute and second values combined. The MSF address structure (struct cdrom_msf0) has 3 values: minute, second, and frame, corresponding to M, S, and F. There are 60 seconds in a minute, and 75 frames in a second. However, these numbers are defined as CD_SECS (the number of seconds in a minute), and CD_FRAMES (the number of frames in a second), and you should use these instead of hard-coding the numbers yourself.

>h5>Discovering track information on a CD

The next setp is to discover the track information provided on the CD-ROM: the addresses and lengths of all the playable tracks on the disc. This is done by calling ioctl with CDROMREADTOCENTRY as the second argument. The third argument you pass to ioctl should be a pointer to a preallocated struct cdrom_tocentry. There are several variables defined in struct cdrom_tocentry:

u_char cdte_track; /* pre-set this value to the number of the track you want information about */
u_char cdte_adr; /* irrelavent for our purposes */
u_char cdte_ctrl; /* information about the type of track (music or data?) is returnd here  */
u_char cdte_format; /* pre-set this value to the addressing format you want (eg. CDROM_MSF) */
union cdrom_addr cdte_addr; /* the address information is returned in this union */
u_char cdte_datamode; /* irrelavent for our purposes */

You need to set the appropriate values in struct cdrom_tocentry before passing it to ioctl. cdte_format should be set to CDROM_MSF to retrieve address information in the MSF format. cdte_track should be set to the number of the track you want information about. [Note: The computer science convention about the "first" element being addressed at 0 is NOT TRUE in this situation. If you want information about the first track, cdte_track needs to be set to 1, NOT zero]. If you want information about the "leadout" track, cdte_track nees to be set to CDROM_LEADOUT.

When ioctl returns, the rest of the values in struct cdrom_tocentry are filled out. The CDROM_DATA_TRACK bitmask can be used on the cdte_ctrl element to find out wether the track is a data or music track. The address of the track is located in the cdte_addr element, which is defined as a union cdrom_addr type. union cdrom_addr has 2 elements: an integer representing the LBA address, and a struct cdrom_msf0 representing the MSF address. A code sample explains it best:

/* an example to find out the length of the first track on a CD */
/* note: this code assumes that there is at least 2 tracks on the CD.  In reality, you need to keep in mind
    that there might be only 1 track, in which case, the track request for the second ioctl call would have
    been set to CDROM_LEADOUT
*/

struct cdrom_tocentry entry;
int fd; /* file descriptor */
int number_of_tracks;
int offset_min, offset_sec, offset_frame;    /* the offsets for the first track */
int length_min, length_sec, length_frame; /* the length of the first track */
int temp1, temp2; /* temporary values */

/* open CD device and put file descriptor into "fd"
    call CDROMREADTOCHDR to find the number of tracks and put it in "number_of_tracks"
*/

entry.cdte_track = 1;    /* find the address of the first track */
entry.cdte_format = CDROM_MSF;    /* choose MSF addressing */

if(ioctl(fd, CDROMREADTOCENTRY, &entry) != 0) {
    fprintf(stderr, "Error calling ioctl for reading 1st track entry\n");
    exit(0);
}

/* store the address information */
offset_min = entry.cdte_addr.msf.minute;
offset_sec = entry.cdte_addr.msf.second;
offset_frame = entry.cdte_addr.msf.frame;

entry.cdte_track = 2; /* find the address of the second track */
if(ioctl(fd, CDROMREADTOCENTRY, &entry) != 0) {
    fprintf(stderr, "Error calling ioctcl for reading 2nd track entry\n");
    exit(0);
}

/* to calculate the length of a track, you need to convert both addresses to integers, and then subtract them,
    and then convert them back into MSF format
*/
/* set temp1 to the address of track1 in simply "frames" */
temp1 = 0;
temp1 += offset_frame; /* frame is basic unit */
temp1 += offset_sec * CD_FRAMES; /* there are "CD_FRAMES" frames in 1 second */
temp1 += offset_min * CD_SECS * CD_FRAMES; /* there are "CD_SECS" seconds in 1 minute */

/* set temp2 to the address of track2 in simply "frames" */
/* "entry" contains the information about track2 from the last ioctl call */
temp2 = 0;
temp2 += entry.cdte_addr.msf.frame;
temp2 += entry.cdte_addr.msf.second * CD_FRAMES;
temp2 += entry.cdte_addr.msf.minute * CD_SECS * CD_FRAMES;

/* the length is the address of the second track minus the address of the first track */
temp2 -= temp1;    /* temp2 now has length of track1 in frames */
length_min = temp2 / (CD_SECS * CD_FRAMES);    /* calculate the number of minutes */
temp2 %= CD_SECS * CD_FRAMES;    /* calculate the left-over frames */
length_sec = temp2 / CD_FRAMES;    /* calculate the number of seconds */
length_frame = temp2 % CD_SECS;    /* calculate the left over frames */

printf("Info for track 1:\n");
printf("Address is at %d minutes, %d seconds, and %d frames", offset_min, offset_sec, offset_frame);
printf("Length is %d minutes, %d seconds, and %d frames", length_min, length_sec, length_frame);
Playing

This is the major function that most people associate with a CD player, but it's relatively simple compared to the previous two operations. To play any part of a CD, ioctl must be called with CDROMPLAYMSF, and a third argument specifying the beginning and ending addresses for the region to be played. This information must be passed as a pointer to struct cdrom_msf. The structure has the following elements:

u_char cdmsf_min0; /* the minute offset to start at */
u_char cdmsf_sec0; /* the second offset to start at */
u_char cdmsf_frame0; /* the frame offset to start at */
u_char cdmsf_min1; /* the minute offset to end at */
u_char cdmsf_sec1; /* the second offset to end at */
u_char cdmsf_frame1; /* the frame offset to end at */

When ioctl is called with these arguments, the CD-ROM will play from the beginning address to the ending address, and then automatically stop. There is no way to ask a single track to be played - the addresses for the beginning and the end of the track must be discovered, an then passed to the ioctl call. The following code will play the first track of a music CD. It is assumed that the address and length of the track have already been discovered.

struct cdrom_msf region;
/* play the first track of a CD */
int offset_min, offset_sec, offset_frame;    /* offset for address of first track */
int length_min, length_sec, length_frame;    /* length of first track */
int fd;
int end_min = 0, end_sec = 0, end_frame = 0;    /* ending address for first track */

/*******
    code for getting the file descriptor and offset and length of the first track
*******/

end_frame = offset_frame + length_frame;    /* calculate the frame offset */
if(end_frame >= CD_FRAMES) {    /* make sure that ending frame address is within it's limits */
    end_sec += end_frame / CD_FRAMES;
    end_frame %= CD_FRAMES;
}
end_sec += offset_sec + length_sec;    /* calculate the second offset */
if(end_sec > CD_SECS) {    /* make sure that ending second address is withing limits */
    end_min += end_sec / CD_SECS;
    end_sec %= CD_SECS;
}
end_min = offset_min + length_min;    /* calcualte the minute offset */

/* specify the region to play */
region.cdmsf_min0 = offset_min;
region.cdmsf_sec0 = offset_sec;
region.cdmsf_frame0 = offset_frame;
region.cdmsf_min1 = end_min;
region.cdmsf_sec1 = end_sec;
region.cdmsf_frame1 = end_frame;

/* call ioctl */
if(ioctl(fd, CDROMPLAYMSF, ®ion) != 0) {
    fprintf(stderr, "Error playing first track of CD\n");
    exit(1);
}

Once the CD is playing, your program is not needed at all. You can even exit the program and the music will keep playing.

Other operations

I've already covered most of the more difficult operations. There are only a few loose ends to be tied up. All of these operations pass the file descriptor to as the first argument to ioctl. Most of these operations, do not give a third argument to ioctl unless explicitly stated.

To stop a CD, call ioctl with CDROMSTOP as the second argument.

To start the CD drive motor spinning (this is automatically done for you when you play, but you might want to start it spinning before you play so that the user notices no "lag" - sometimes it takes about 2 seconds for the CD to start spinning), call ioctl with CDROMSTART as the second argument.

To pause, use CDROMPAUSE, and to resume use CDROMRESUME.

To eject the CD tray or disc use CDROMEJECT.

It is also possible to cause the CD to "lock", so that the tray will not open even if the user presses the eject button on his CD-ROM panel. To do this, call ioctl with CDROMEJECT_SW as a second argument, and an integer (0 or 1), as the third argument. Passing 0 as the third argument will cause the CD-ROM to lock, while passing 1 will cause it to unlock.

To check if a track is a music track or a data track, perform a bitwise "and" on the cdte_ctrl element of struct cdrom_tocentry it with the CDROM_DATA_TRACK mask. Here is an example:

struct cdrom_tocentry entry;

/* perform all operations necessary to fill "entry" with track information */

if(entry & CDROM_DATA_TRACK) {
    printf("Track %d is a data track", entry.cdte_track);
} else {
    printf("Track %d is a music track", entry.cdte_track);
}
Loose ends

As some of you may have noticed, I indeed have left some loose ends untied. I never discussed how to control the volume of a CD-ROM drive through software. I've done this becaise I myself do not completely undertsand how software volume control works. The test programs which I have written that use the software volume control either do nothing noticeable, or cause a segmentation fault.

Acknowledgements

The author of the GTCD/TCD program - whose sources helped me understand this stuff (I can't find his name - his website is down, apparently). Dave Miller and Eberhard Moenkeberg, who wrote the general Linux CD-ROM interface.

Source Files


Errors, omissions, comments? Send us your feedback