IRIX 5.3 » Books » Developer »
IRIS Digital Media Programming Guide
(document number: 007-1799-040 / published: 1994-11-14)
table of contents | additional info | download find in page
Chapter 8. Programming with the CD Audio Library
The IRIS Media Libraries have two libraries that help you retrieve and process digital audio and related information from two sources. This chapter describes the CD Audio Library, libcdaudio.a, which gives you access to the data on an audio compact disc (CD), including nonaudio information. Chapter 9, “Programming with the DAT Audio Library,” describes the DAT Audio Library, libdataudio, which helps you process audio information stored on digital audio tape (DAT).
Because these libraries deal with digital audio information, they contain many analogous routines for processing audio data. But the libraries diverge when it comes to writing audio data and controlling their respective devices. libcdaudio includes calls that control the CD-ROM drive; the DAT drive uses the standard IRIX device drivers.
In this chapter:
The CD Audio Library lets you:
control the CD-ROM drive (eject CDs, prohibit ejection of CDs)
read or play information from that drive
parse and process digital information
This section describes the basic concepts that underlie libcdaudio. Because both CDs and DATs digitally encode an audio signal as a series of samples, the concepts and terms used when dealing with these media are similar; however, there are differences between the two.
CD Frames, Samples, and Subcodes
Per second of playing time, a CD contains 75 CD frames, each containing 588 stereo audio frames (that is, pairs of left and right channel audio samples). A CD frame has both audio and nonaudio information. The sum of the nonaudio information in a frame composes a single complete chunk of subcode. When in audio mode and reading from a CD, you need complete subcodes. Thus, in audio mode, a CD frame is the smallest parcel of information you can read from a CD.
To give you controlled access to either the audio data or the subcode in a CD frame, libcdaudio hands you a CDFRAME structure:
typedef struct cdframe {
char audio[CDDA_DATASIZE];
struct subcodeQ ;subcode;
} ;CDFRAME;
|
An audio sample is linearly encoded in a 16-bit two's-complement format. Because a complete stereo audio sample contains two interleaved channels, it takes four bytes of audio[] to contain a complete stereo audio sample.
Figure 8-1 shows the structure of a CD audio sample.
The byte ordering of the samples in audio[] is the raw data from the CD; its byte ordering is reversed from that on the Indigo workstation. The sampling rate at which CD audio data is originally recorded is 44.1 kHz; therefore, CDDA_DATASIZE (the size of audio[]) is defined as 2352. This allows for 588 stereo audio samples per CD frame, which, at 75 frames per second, allows for a sample rate of 44.1 kHz.
The subcode member has three information modes:
| mode1 | | for reporting on the track, index, and timing for the current CD track; or, if the current track is the nonaudio lead-in track (see “CD Tracks, Indices, and Time Codes” ), mode1 contains a table of contents for the CD
| | mode2 | | for reporting the catalog number for the CD as well as an absolute CD frame count
| | mode3 | | for reporting the International Standard Recording Code (ISRC) identification information: country, owner, year, and serial number
|
Thus, the subcodeQ structure in the CDFRAME structure contains a union of three structures: mode1, mode2, and mode3. Which mode is used depends on the information from the CD.
For more information on the CDFRAME structure and the subcodeQ structure, see the CDFRAME(4) man page.
CD Tracks, Indices, and Time Codes
As many as 99 audio program tracks are allowed on a CD. These tracks are numbered 01 through 99. Two nonaudio tracks of general interest are also available: the lead-in track (numbered 00) and the lead-out track (numbered AA). Track 00, the lead-in track, contains a table of contents in its subcodes.
A track can have up to 99 subdivisions containing audio information. These subdivisions use the index numbers 01 through 99. Index number 00 is used for the pause between the tracks. The time code gives the current minute, second, and CD frame for the current track. The subcodeQ structure with mode1 uses a cdtimecode structure to contain time codes.
CD Seeking, Reading, and Playing
Accessing information from a CD-ROM drive is analogous to accessing information from a standard disk drive. To read a particular piece of information from the CD, you must move to that location. The process of moving to a location on the CD is known as seeking.
Reading from a CD-ROM drive is analogous to reading from a disk drive—you copy information from the device to a memory-resident buffer for further processing.
Playing the CD is a variation on reading it. But instead of transferring the information to a buffer for processing, the information is dumped out the audio jacks on the back of the CD-ROM drive, with a minimum of buffering and with no real chance to process it. For information on processing audio from a CD through the workstation's audio hardware, see “Reading Audio Data from the CD-ROM Drive”.
The parser lets your application change state in response to changes in the subcode data on a CD. This lets you deal with the audio data in a way that is based on its content. To use the parser, you must give it callback routines that can deal the subcode changes that interest you. Then you set up a loop that reads CD frames from the CD and calls the parser for each CD frame.
The parser checks the subcode in every submitted CD frame. If the parts of the subcode you care about have changed from the previous CD frame, the parser executes one of your callbacks and hands it the new subcode information. Within your callback, you can examine the subcode information and change the state of your application as needed.
Opening and Closing the CD-ROM Device
The CD-ROM device does not use a standard IRIX device driver. So, a session with the CD-ROM device starts by calling CDclose(). For detailed information on these routines, see the man pages CDopen(3) and CDclose(3).
Controlling the CD-ROM Drive Caddy
To give your application control over the caddy-eject feature on the CD-ROM drive, libcdaudio defines the following routines:
For more information on these routines, see the appropriate man pages.
To move through a CD, you use one of the libcdaudio calls: CDseek(), CDseektrack(), or CDseekblock(). But before you can call these routines, you need to know where you are going. For most applications, locations can come from either of two sources, the end user or calculations internal to your application.
Seek destinations can be in any one of three forms:
integer CD frame counts
<minute, second, CD frame> integer triples
"minute:second:CD frame" ASCII strings
The ASCII format is the one you receive from an end user of your application; the other two formats are used for internal calculation.
Getting CD Locations from the End User
If your application wants to give end users the option of seeking to a CD location defined in terms of time, your application can prompt the user for the time and then call CDatomsf() to convert the ASCII string to a <minute, second, CD frame> triple that you can use for seeking. You can also let the user specify a track number, convert that track number to an integer and seek to that track.
Getting CD Locations from Calculations Internal to Your Application
Generally, the pure CD frame count is the most convenient format to use when comparing two locations.
To convert to pure CD frame counts, call:
You can then make your calculations and determine the destination to which you want to seek. Despite the convenience of pure CD frame counts for calculation, they are not suitable for seeking. To seek, you must call CDframetomsf() to convert the pure CD frame count to a <minute, second, CD frame> triple.
It is also possible to make comparisons between locations expressed in terms of minutes, seconds, and CD frames. In that case, you can convert locations into <minute, second, CD frame> triples by calling:
After making these calculations, the location is in terms suitable for seeking.
Getting the Current CD Location
To get your current location within a CD, call CDgetstatus(). This routine takes a CDSTATUS structure and fills it with information on current track, minute, second, CD frame, and additional data. To make it easier to compare your current location to another location, you should express the locations in terms of pure CD frame counts. But depending on how you got a location, it could be expressed as three separate integers giving the minute, second, and CD frame, or as an ASCII string, or as a cdtimecode structure. For more information on this routine, see the appropriate man pages.
Seeking sets up the read pointer to retrieve data from a particular location on the CD. You can define the seek location in terms of:
| track | | To seek to a track, call CDseektrack().
| | absolute time | | To seek to a location defined in terms of minute, second, and CD frame, call CDseek().
| | logical block | | To seek to a location defined in terms of a logical block number, call CDseekblock(). (On a CD-ROM, one logical block contains a single CD frame, which is 588 stereo audio samples plus one complete subcode.)
|
To do a series of consecutive seeks, your first seek can be defined in any of the formats mentioned above. But, because all seek routines return the logical block number of the next logical block, it is often more convenient to define the subsequent seeks in terms of logical blocks.
If you want to do all seeks using CDseekblock(), but your first seek is defined in terms of time, call CDmsftoblock() to convert time to logical block number.
 | Note: Although logical blocks and CD frames are the same size, you cannot use CD frame counts as if they were logical block counts. The CD frame counts are relative to the start of the CD. A logical block count is offset from the start of the CD. In addition, the size of the offset varies from device to device.
|
This section explains how to use the CD Audio Library routines for:
playing an audio CD from the CD-ROM drive
reading audio data from the CD-ROM drive
parsing CD information
communicating CD status to the end user
Playing an Audio CD from the CD-ROM Drive
This section explains how to use these libcdaudio routines to play audio from the CD-ROM as if it were a standard CD player:
When these routines play a CD, they direct the sound to the drive's headphones and to the audio jacks.
Reading Audio Data from the CD-ROM Drive
Once you have set the read pointer with a call to one of the seek routines, you are ready to read data from the CD. But how much data should you read at a time in order to create a continuous flow of data from the CD? To determine this, call CDbestreadsize(). The returned value of this function is the number of CD frames to request in your read call. To actually read data from the CD, call CDreadda().
Because libcdaudio already includes routines for playing audio data from the CD, you might think that you would never need to read from the CD; however, the libcdaudio play routines allow for only a very simple CD-player application—one that cannot even display the current program time while the CD is playing.
Thus, if you are writing a real-world application, you probably want to read samples from the CD into the workstation's memory through the CD-ROM's SCSI interface, play the audio samples from the audio hardware using the Audio Library, parse the CD frames for the current program time, and display the program time in a continuously updated field of the control panel for your application.
To do this, you can write your own play routine that executes as a cd_audio callback. You should also write a cd_ptime callback to get the current program time and to update your “program time” display.
Controlling the CD Parser
After you have read data from the CD into a buffer, you can start to process it. Typically, how you process the audio data depends on what its associated subcodes tell you about the data. To make it possible for your application to avoid dealing with the complex CDFRAME structure directly, libcdaudio includes a parser.
If you write a loop that passes all read CD frames through the parser, the parser can examine all CD frames for changes in the subcode. When the parser finds a change (seeing a subcode for the first time counts as a change), it executes the appropriate callback routine—depending on what sort of subcode change occurred—and passes the new subcode data into your callback routine.
The CD parser distinguishes among eight categories of subcode information. Thus, if you are interested in subcode changes for only one category of subcode data, the parser does not bother your application with subcode changes that you consider irrelevant.
Allocating and Initializing the CD Parser
To allocate and initialize the parser data structures, call CDcreateparser(). To reset the parser after the user changes the CD in the CD-ROM drive, call CDresetparser(). This clears out any information the parser has about the last CD frame but leaves the callback routines in place.
Defining Callbacks for the CD Parser
When you define a callback for the parser, write a function of the form:
MyCDSomethingCallBack( void* arg, CDDATATYPES type,
void* data) {
/* your code here */
}
|
The parser uses the third parameter to pass in information it reads from the subcodes. The parser uses the second parameter to pass in the type of callback it thinks it is calling. You can use this to assign the same function to different types of callbacks. Internally, you can switch on the type. This feature is useful if two callbacks are essentially the same, with the exception of a few lines.
The parser does not use the first parameter. You can use that to pass in information if your application needs to call the callback directly.
Adding Callbacks to the CD Parser
To add callback routines to the parser, call CDaddcallback(). If you do not specify a callback for a category, the parser assumes that you are not interested in changes of that type. You can add callbacks that respond to changes in any of the following categories of subcode data:
| cd_audio | | callbacks respond to changes in the audio data in a CD frame. You can use this class of callback to notify you when you are beyond the lead-in track and have started to see audio samples. When the parser calls this routine, it passes in the audio sample data. If this callback routine is a play routine for your application, it should write the audio sample to an audio port using the Audio Library. See the ALwritesamps(3) man page and Chapter 6, “Programming with the Audio Library.”
| | cd_pnum | | callbacks respond to changes in the program number. You can use this callback to notice when you have moved from one program (track) to the next.
| | cd_index | | callbacks respond to changes in the index number. You can use this callback to notice when you have moved from one subsection of a track to the next.
| | cd_ptime | | callbacks respond to changes in the program time. You can use this callback to continuously update a “program time display” in a CD-playing application.
| | cd_atime | | callbacks respond to changes in the absolute time elapsed since the start of the CD. You can use this callback to continuously update your application's information about total elapsed time.
| | cd_catalog | | callbacks respond to changes in the catalog number for the CD. Because this information should not change within the CD, this sort of callback executes only once—typically during the lead-in track for the CD.
| | cd_ident | | callbacks respond to changes in the ISRC identification number for the recording on the CD. Because this information should not change within the CD, this sort of callback executes only once—typically during the lead-in track for the CD.
| | cd_control | | callbacks respond to changes in the control bits. These bits can tell you things such as whether the CD is copy protected and whether preemphasis is off or on. Because this information should not change within the CD, this sort of callback executes only once—typically during the lead-in track.
|
For more information on each callback type, see the CDaddcallback(3) man page.
Deleting and Changing a CD Parser Callback
To delete a callback, call CDremovecallback(). To change a callback, call CDremovecallback() followed by CDaddcallback().
To submit a group of CD frames to the parser, your loop should set up a loop that calls CDparseframe() for each frame that you have read into your buffer.
Freeing the Memory Allocated for the Parser
If you are done with the parser and want to free the memory it uses, call CDdeleteparser() to delete the parser.
Communicating CD Status to the End User
In addition to playing a CD or processing the information read from a CD, your application probably needs to tell the user something about the CD (even if it is only the number of the current track). Also, sometimes your application must take data from the end user and convert it to a form that the CD-ROM device can understand.
To get information for the end user, call:
The CD frames, however, sometimes contain information that is not accessible to the routines mentioned above. For example, the subcodes of track 00 on a CD contain a table of contents. To access this information, you can inspect the subcodes in the CDFRAME structures, or, better still, you can submit that track to the parser. If you have added callbacks for the categories of subcode information that you want, the parser passes that information into your callbacks.
To help you present the information the parser hands to your callbacks (or that you read directly from a CDFRAME structure), libcdaudio contains the routines:
For more information on the CDFRAME structure and the format of its data, set the CDFRAME(4) man page.
CD Time Code Conversion Routines
Other libcdaudio routines that you might find useful are:
Example 8-1 contains a listing of cdsample.c, a program that lets you copy timed amounts of data from a CD to an audio file.
Example 8-1. Copying CD Data to an Audio File: cdsample.c
/*
* cdsample--command line tool to read audio data off CD,
* record it in an AIFF file. Hacked together from various
* other sample programs.
*
* Compile with
* cc -o cdsample cdsample.c -lcdaudio -lds -laudiofile -lm
*/
#include <sys/types.h>
#include <cdaudio.h>
#include <audio.h>
#include <audiofile.h>
#include <stdio.h>
#include <string.h>
AFfilehandle audiofile;
openAudioFile(char *filename)
{
AFfilesetup filesetup;
filesetup = AFnewfilesetup();
AFinitfilefmt(filesetup, AF_FILE_AIFFC);
AFinitchannels(filesetup, AF_DEFAULT_TRACK, 2);
AFinitrate(filesetup, AF_DEFAULT_TRACK, 44100.0);
AFinitsampfmt(filesetup, AF_DEFAULT_TRACK, AF_SAMPFMT_TWOSCOMP, 16);
AFinitcompression(filesetup, AF_DEFAULT_TRACK, AF_COMPRESSION_G722);
audiofile = AFopenfile(filename, “w”, filesetup);
}
closeAudioFile()
{
AFclosefile(audiofile);
}
writeAudioFile(void *arg, CDDATATYPES type, short *audio)
{
AFwritesamps(audiofile, AF_DEFAULT_TRACK, audio, CDDA_NUMSAMPLES);
}
void parseTime(char *timestr, int *min, int *sec)
{
char *tmp, buf[5];
int n;
tmp = strchr(timestr, `:');
if (tmp == NULL) {
*sec = atoi(timestr);
} else {
*tmp = `\0';
tmp++;
*min = atoi(timestr);
*sec = atoi(tmp);
}
}
main(int argc, char **argv)
{
CDPLAYER *cd;
CDPARSER *cdp;
CDSTATUS status;
CDTRACKINFO trackinfo;
CDFRAME buf[12];
int i, n;
int track, numframes, frame;
char *filename;
char *tmp, strbuf[12];
int startmin, startsec, endmin, endsec, totalsec;
extern int errno;
if (argc != 5) {
fprintf(stderr, “Usage: cdsample filename track start_time end_time\n”);
exit(1);
}
filename = argv[1];
track = atoi(argv[2]);
/*
* Note that we do not check if the arguments are sane ...
*/
parseTime(argv[3], &startmin, &startsec);
parseTime(argv[4], &endmin, &endsec);
if ((cd = CDopen(NULL, “r”)) == NULL) {
fprintf(stderr, “Can't open CD device\n”);
exit(1);
}
if ((cdp = CDcreateparser()) == NULL) {
fprintf(stderr, “Can't create parser\n”);
exit(1);
}
/*
* Set up a callback function to process the CD data.
* In this case, CDparseframe() will feed the data to the
* writeAudioFile() function (defined above).
*/
CDsetcallback(cdp,cd_audio,(CDCALLBACKFUNC) writeAudioFile, 0);
openAudioFile(filename);
/*
* Determine the number of frames in the requested
* snippet (75 frames/sec)
*/
numframes = ((endmin * 60 + endsec) - (startmin * 60 + startsec)) * 75;
if (CDgetstatus(cd, &status) == 0) {
fprintf(stderr, “Couldn't get status\n”);
exit(1);
} else {
if (!status.scsi_audio) {
fprintf(stderr, “This CD-ROM can't do SCSI audio\n”);
exit(1);
}
/*
* Convert relative time (in track) to absolute time
* (on disk) so we can seek to the proper position.
*/
CDgettrackinfo(cd, track, &trackinfo);
totalsec = (trackinfo.start_min + startmin) * 60 +
trackinfo.start_sec + startsec;
startmin = totalsec / 60;
startsec = totalsec % 60;
CDseek(cd, startmin, startsec, 0);
for (frame=0;frame<numframes;frame += 12) {
n = CDreadda(cd, buf, 12);
if (n < 0) {
fprintf(stderr, “Error reading CD data\n”);
exit(1);
}
if (n == 0) /* We're at the end of the disc */
break;
for (i = 0; i < 12; i++)
CDparseframe(cdp, &buf[i]);
}
CDclose(cd);
closeAudioFile();
exit(0);
}
}
|
IRIS Digital Media Programming Guide
(document number: 007-1799-040 / published: 1994-11-14)
table of contents | additional info | download
Front Matter
About This Guide
Part I. Digital Media Programming
Part II. Digital Audio and MIDI Programming
Part III. Video Programming
Part IV. IndigoVideo Programming
Part V. Compression Programming
Part VI. Movie Programming
Appendix A. Audio Specifications
Appendix B. Aware Scalable Audio Compression Software
Glossary
Chapter 33.
Glossary
Chapter 34.
Glossary
Index
home/search |
what's new |
help
|