SGI Techpubs Library

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 9. Programming with the DAT Audio Library

This chapter describes the DAT Audio Library, libdataudio, which you can use to process audio information stored on digital audio tape (DAT).

In this chapter:

DAT Audio Library Basics

The DAT Audio Library (libdataudio) supports processing the data from a digital audio tape (DAT). Because the device driver for the DAT drive is a standard IRIX tape device driver, the libdataudio library does not need the special positioning and status calls. Instead, you can use the standard open(), close(), read(), write(), and ioctl() system calls.

This section describes the basic concepts that underlie libdataudio. 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 some differences between them.

DAT Frames, Samples, and Subcodes

A DAT contains 33.33 DAT frames per second of playing time. A DAT frame has both audio and nonaudio information. The sum of the nonaudio information in a DAT frame composes a single complete DAT subcode. When in audio mode and reading from a DAT, you need complete subcodes. Thus, in audio mode, a DAT frame is the smallest parcel of information you should read from a DAT.

To give you controlled access to either the audio data or the subcode in a DAT frame, libdataudio hands you a DTFRAME structure:

typedef struct dtframe {
char audio[DTDA_DATASIZE];
struct dtsubcode sc;
} DTFRAME;

A DAT 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 (see Figure 9-1).

Figure 9-1. DAT Audio Sample Structure


The byte ordering of audio sample frames in audio[] is based on the raw data from the DAT; its byte ordering is reversed from that on the IRIS workstation. DTDA_DATASIZE (the size of audio[]) is defined as 5760. This allows for 1440 audio sample frames per DAT frame, which, at 33.33 DAT frames per second, is enough to deal with audio sampled at rates of up to 48 kHz.

The subcode member uses a dtsubcode structure to contain the subcode read from the DAT. The subcodes contain information on sampling frequency, the number of channels, table of contents, catalog number, and more. For more information on the dtsubcode structure, see the DATFRAME(3) man page.

DAT Audio Program Numbers and Indices

A DAT can have as many as 99 audio programs, each typically corresponding to a single song or musical piece. These programs are numbered 01 through 99. An audio program 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 audio programs.

DAT Run Time, Absolute Time, and Program Time

A time code gives the hour, minute, second, and DAT frame offset into a DAT. When dealing with program time, the time code is a measure of the time elapsed since the start of the audio program. When dealing with absolute time, the time code measures the time elapsed since the start of the DAT. When dealing with run time, the time code measures the time elapsed since the beginning of the recording and contains several audio programs.

DAT Seeking and Reading

Accessing information from a DAT drive is analogous to reading information from a standard tape drive. To read a particular piece of information from the DAT, you must move to that location. The process of moving to a location on the DAT is known as seeking. Reading from the DAT is analogous to reading from a tape drive. You copy information from the device to a memory-resident buffer for further processing.

DAT Parser

The parser lets your application change state in response to changes in the subcode data on a DAT. 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 with the subcode changes that interest you. Then you set up a loop that reads DAT frames from the DAT and calls the parser for each DAT frame.

The parser checks the subcode in every submitted DAT frame. If the parts of the subcode you care about have changed from the previous DAT 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 DAT Device for Audio

The DAT device driver is a standard IRIX device, so you can use the generic open(), close(), and ioctl() calls that you would use for any other tape device; however, unlike a standard tape drive, the DAT drive has an audio mode in addition to a straight data mode.

To put the DAT drive in audio mode, use ioctl() with MTIOCTOP and an mtop type structure, but set the mt_count member of the mtop structure to 1 before submitting that mtop structure to ioctl(). For example:

struct mtop mt_com;
mt_com.mt_op = MTAUD;
mt_com.mt_count = 1; /* 1 == audio mode, 0 == data mode */
ioctl(fd, MTIOCTOP, &mt_com);

Navigating through a DAT

To move through a DAT tape, you use ioctl(), a standard IRIX system call. But before you can call ioctl(), you need to know where you are going. For most applications, destinations can come from either of two sources, the end user or calculations internal to your application.

Destinations from the end user come to your application in the form of ASCII strings. Destinations from internal calculations typically come in the form of a DAT frame count or, sometimes, as four values that specify the location in terms of hours, minutes, seconds, and DAT frames. Unfortunately, these forms are not suitable for seeking, so you must convert them before you can use them.

Getting DAT Locations from the End User

If your application wants to give end users the option of seeking to a DAT location defined in terms of time, your application can prompt the user for the time and then call DTatotime() to convert the string to a time code that you can submit to ioctl() for seeking.

Getting DAT Locations from Calculations Internal to Your Application

Generally, the pure DAT frame count is the most convenient format to use when comparing two locations.

To convert to pure DAT frame counts, call:

DTtctoframe()

to extract a pure DAT frame count from a dttimecode structure

DThmsftoframe()

to convert hours, minutes, seconds, and DAT frames to a pure DAT frame count

DTatohmsf() followed by DThmsftoframe()

to convert an ASCII string to a pure DAT frame count

You can then make your calculations and call DTframetotc() to convert the DAT frame count to a time code suitable for seeking.

It is also possible to make comparisons between locations expressed in terms of hours, minutes, seconds, and DAT frames. In that case, you can convert all locations into hours, minutes, seconds, DAT frames format by calling:

DTatohmsf()

to convert an ASCII string to hours, minutes, seconds, and DAT frames

DTframetohmsf(

to convert a pure frame count to hours, minutes, seconds, and frames

DTtctoframe() followed by DTframetohmsf()

to convert a time code to hours, minutes, seconds, and frames

After making your calculations, convert the destination to a time code suitable for seeking by calling DThmsftoframe() followed by DTframetotc().

Seeking to a DAT Location

To seek to a location on a DAT, call ioctl() with MTSETAUDIO and an mtaudio type structure.

To specify the type of seek, set the seektype member of the mtaudio type structure to the appropriate MTAUDPOSN_* constant:

MTAUDPOSN_PROG

to seek to a program number

MTAUDPOSN_ABS

to seek to an absolute time

MTAUDPOSN_RUN

to seek to a running time

MTAUDPOSN_PTIME

to seek to a program time (within program)

To seek to a particular audio program on the DAT, set seektype to MTAUDPOSN_PROG, and use pno1, pno2, and pno3 members to pass in the three BCD numbers that identify the audio program you want. Program numbers range from 001 to 799. The pno1 member contains the most significant digit and pn3 contains the least significant digit. Thus, to seek to program 578, set the pn* members as follows:

struct mtaudio AudioProgNum;

AudioProgNum.pn1 = 5;
AudioProgNum.pn2 = 7;
AudioProgNum.pn3 = 8;

To seek to a location on the tape defined in terms of time, set the mtaudio seektype member to MTAUDPOSN_ABS, MTAUDPOSN_RUN, or MTAUDPOSN_PTIME and then specify the time location in the mtaudio members:

atime 

for MTAUDPOSN_ABS

rtime 

for MTAUDPOSN_RUN

ptime 

for MTAUDPOSN_PTIME

These atime, rtime, and ptime members contain structures of type mtaudtimecode:

struct mtaudtimecode {
    unchar hhi:4, hlo:4; /* hours */
    unchar mhi:4, mlo:4; /* minutes */
    unchar shi:4, slo:4; /* seconds */
    unchar fhi:4, flo:4; /* DAT frame # */
};

The hhi and hlo members expect two digits that specify the hour to which you want to seek. The valid range for these two digits is from 00 to 99. The mhi and mhl expect the two digits that specify the minute to which you want to seek. The valid range for these two digits if from 00 to 59. The shi and slo expect the two digits that specify the second to which you want to seek. The valid range for these two digits is from 00 to 59. The fhi and fhl expect the two digits that specify the DAT frame to which you want to seek. The valid range for these two digits is from 00 to 33.

Using the DAT Drive

This section explains how to use the DAT Audio Library routines for:

  • playing a DAT

  • recording a DAT

  • reading and writing audio data from a DAT

  • parsing DAT information

  • communicating DAT status to the end user

Playing a Tape in the DAT Drive

Playing audio from a DAT is a little more complicated than playing a CD. For example, the sample rate for all CDs is 44.1 kHz; however, DAT audio may have been recorded at a sampling rate of 48 kHz, 44.1 kHz, or 32 kHz. Fortunately, a DAT records its sampling rate in the subcodes at the start of the tape, so you can read this sampling rate from the DAT before you must write DAT audio samples to the audio port.

In outline, a simple DAT-playing application must:

  1. Define a callback routine for dt_sampfreq.
    When the parser calls this routine, it passes in the frequency just read from the tape. Your callback should set a global variable to the frequency it gets from the parser. (See the DTaddcallback(3) man page.)

  2. Define a callback routine for dt_audio.
    When the parser calls this routine, it passes in the audio data from the DAT frame just parsed. The callback routine should write this data to the audio port using the sampling rate set by the dt_sampfreq callback.

  3. Open the audio port.

  4. Open the DAT drive.

  5. Create a parser.

  6. Add your dt_sampfreq and dt_audio callbacks to the parser.

  7. Read samples from the DAT.

  8. Parse the samples.

  9. Write the samples to an audio port using the Audio Library.

When the application first starts reading the tape, it sees the frequency, calls your dt_sampfreq callback, and hands it the sampling frequency. As the parser continues to parse DAT frames, it also sees the audio data and executes your dt_audio callback for each new DAT frame containing audio.

For an example of a simple program that plays a tape in the DAT drive, see “DAT Sample Program”. For more information on using the audio port, see Chapter 6, “Programming with the Audio Library.”

Making DAT Recordings for Playback on the DAT Drive

When making recordings on DAT recorders that you want to play on a Silicon Graphics DAT drive, you must make sure you record at least one of the time codes. Most recorders will let you record audio without any time codes, so be certain you record the time codes. Record in standard mode; the DAT drive does not support long play (LP) mode or 4-channel (4CH) mode tapes.

Reading Audio Data from the DAT Drive

To read audio data from the DAT drive, you need to open the DAT drive and put it in audio mode. Then you can call the standard IRIX read() system call as you would for any other tape device. The only complicating factor is that you need to ensure that you read complete DAT frames. This is not particularly difficult if you declare your receiving buffer to be an array of DTFRAME structures.

For example:

DTFRAME MyDATbuffer[4];

declares a buffer of four DTFRAME structures. If you then do a read such as:

n = read( MyDATtapeDevice, MyDATbuffer, sizeof(MyDATbuffer) );

you read in complete DAT frames and can easily access those complete DAT frames when you want to parse them.

Writing Audio Data to the DAT Drive

To write audio data to the DAT drive, you need to open the DAT drive and put it in audio mode. Then you can call the standard IRIX write() system call as you would for any other tape device. Writing the tape is just a matter of writing DAT frames to the tape.

But setting the contents of the DAT frames is not just a matter of gathering together your audio samples. You must write subcode information that specifies things such as the sampling rate at which the audio was recorded. You must also update the DAT time code for each DAT frame that you write to the tape.

To help you set the subcode information for the DAT frames you want to write, libdataudio contains these routines:

DTsetdate() 

to set a date pack to the current time (useful for timestamps)

DTinctime() 

to increment a DAT time code

DTtcvalid() 

to check that a time code is valid (use it after calling DTinctime())

For more information on the time code routines, see the appropriate man pages. For information on what you can write into the DAT frame subcodes, see the DTFRAME(4) man page. For additional information about properly writing DAT subcodes, see the DAT specification.

Ensuring that your DAT Recording Is Recognized as Audio

The DAT drive determines whether a tape is audio by looking for valid audio DAT frames. These frames must contain at least one valid time code field (absolute time, run time, or program time). When making recordings on DAT recorders that you want to later play on the Silicon Graphics DAT drive, you must make sure you record one of these time codes and that you record in standard mode.

Recording the DAT Lead-in Area

The DAT specification requires that a tape begin with a special lead-in area of 100 DAT frames. Recording 100 frames ensures that the real recording will not begin over the plastic leader on the tape.

The following procedure provides the proper lead-in area:

  1. create an empty DTFRAME

  2. set the program number contained in the DAT frame to 0x0BB (beginning-of-tape, or BOT, code)

  3. set the START bit in the control ID

  4. set the subcode packs to 0x0AA (readable, not valid)

  5. fill the audio data block with zeros

  6. rewind the tape and repeatedly write the DAT frame at least 100 times

Recording Digital Audio over Digital Data Storage (DDS) Tapes

This section explains special precautions that must be taken when recording audio onto a tape that has previously been used as a data (DDS) tape.

When you insert a DDS tape into the DAT drive, it is rewound to the logical beginning-of-tape (BOT). On data tapes, the logical BOT differs from the physical BOT by approximately 10 centimeters (30 seconds). If you attempt to write data to the drive in audio mode, writing begins at the logical BOT. When you then rewind and play this tape, there is an initial 30-second gap before playback starts. If the tape is removed and then reinserted into a DAT drive, it is recognized as a data tape because DDS format data exists between the physical BOT and the DDS logical BOT.


Note: With the current DAT drives (firmware revision 2.63), the following procedure is necessary to work around the problem: Check to see if the tape in the drive is DDS media and at BOT. If so, switch the drive to audio mode and write a frame of data to move the tape off logical BOT, and then issue a rewind. This rewinds the tape all the way back to the physical BOT.


Example Programs Demonstrating DAT Recording

Two sample programs are available to help you with DAT recording:

  • cdtodat.c , in /usr/people/4Dgifts/examples/dmedia/cd+dat

    This program copies audio from a CD to a DAT. It contains example code for recording to DAT, including handling of the lead-in area and recording over data tapes.

  • verifydat.c , in /usr/people/4Dgifts/examples/dmedia/cd+dat

    This program verifies that a DAT has been recorded correctly and has continuously running absolute time code.

Controlling the DAT Parser

After you have read in data from a DAT, you can start to process it. Typically, how you process the audio data depends on what its associated subcodes tell you about the data (for example, the sample rate at which the audio was recorded). If you want, you can directly examine the subcode associated with each DAT frame and respond appropriately.

The DTFRAME structure, however, is large and complicated and subject to change. libdataudio includes a parser so that your application can avoid dealing with the DTFRAME structure directly.

If you write a loop that passes all the read DAT frames through the parser, the parser can examine all the DAT 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 DAT parser distinguishes among 14 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 DAT Parser

To allocate and initialize the data structures for the DAT parser, you must call DTcreateparser().

To reset the parser after the user changes the tape in the DAT drive, call DTresetparser(). This clears out any information the parser has about the last DAT frame but leaves the callback routines in place.

Defining Callbacks for the DAT Parser

When you define a callback for the parser, you must write a function of the form:

My_dat_SomethingCallBack( void* arg, DTDATATYPES type,
void* data)
{ 
/* your code here */
}

The parser uses the third parameter to pass in information it read 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 the same except for 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 and Removing DAT Parser Callbacks

To add callback routines to the parser, call DTaddcallback(). If you do not specify a callback for a category, the parser assumes 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:

dt_audio 

callbacks respond to changes in the audio data in a DAT frame. You can use this class of callback to notify you when you have gotten past 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.”

dt_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.

dt_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.

dt_ptime 

callbacks respond to changes in the program time. You can use this callback to continuously update a “program time display” in a DAT-playing application.

dt_atime 

callbacks respond to changes in the absolute time elapsed since the start of the DAT. You can use this callback to continuously update your application's information about total elapsed time.

dt_rtime 

callbacks respond to changes in the run time elapsed since the start of a recording on the DAT. You can use this callback to continuously update your application's information about total elapsed time since the start of a recording.

dt_prortime  

callbacks are like dt_rtime callbacks in that they respond to changes in the elapsed run time—however, the parser hands the callback more information than it gives to a dt_rtime callback—this type of callback is intended for professional uses

dt_mainid 

callbacks respond to changes in the contents of the ID field

dt_sampfreq 

callbacks respond to changes in the subcodes that describe the sampling frequency for the recording on the DAT

dt_toc 

callbacks respond to changes in the subcode data that describe the table of contents for the tape


Note: When parsing the DAT, you should note a separate subcode for each entry in the table of contents.


dt_date 

callbacks respond to changes in the timestamp for a recording

dt_catalog 

callbacks respond to changes in the DAT catalog number

dt_ident 

callbacks respond to changes in the ISRC identification number for the recording on the DAT

dt_probinary 

callbacks respond to changes in the IEC (SMPTE) or Pro DIO time codes

For more information on each subcode category, see the DTaddcallback(3) man page and the DAT specification.

Deleting or Changing a DAT Parser Callback

To delete a callback, call DTremovecallback(). To change a callback, call DTremovecallback() followed by DTaddcallback().

Parsing DAT Frames

To submit a group of DAT frames to the parser, set up a loop that calls DTparseframe() for each DAT frame that you have read into your buffer.

Freeing the Memory Reserved for the DAT Parser

If you are done with the parser and want to free the memory it uses, call DTdeleteparser() to delete the parser.

Communicating DAT Status to the End User

Whether you get status information for the DAT directly from the DTFRAME structure or from one of your parser-callback routines, you need to convert that information to an ASCII string.

libdataudio includes these conversion routines:

DTsbtoa() 

to convert a 6-bit country and owner code to an ASCII string

DTtimetoa() 

to convert a time code to an ASCII string

DTpnotodec() 

to convert a BDC program number to a decimal, which you can then check and convert to ASCII if appropriate

For more information on these routines, see the relevant man pages.

DAT Sample Program

This section contains datplay.c, a simple program for reading and processing DAT data.

Playing a DAT

Example 9-1 reads samples from the DAT and uses the parser and two callbacks to process the data read. One callback, frequency(), extracts the sampling rate from the subcodes on the DAT. The other callback, playaudio(), extracts audio samples from the frames and writes them to the audio port.

Example 9-1. Reading DAT Samples

/* DAT example from digital audio and MIDI programming guide */

#include <stdio.h>
#include <sys/fcntl.h>
#include <sigfpe.h>     /* Floating point exception error handling package */
                        /* with this you'll need to compile w/ -lfpe */

#include <dataudio.h>   /* DAT audio library. */
                        /* with this you'll need to compile w/ -ldataudio */

#include <audio.h>      /* audio library */
                        /* with this you'll need to compile w/ -laudio */

static int sampsperframe = DTDA_NUMSAMPS48K;    /* Number of samples you'll */
                                                /* get in 1 DAT frame when  */
                                                /* the sampling rate is at  */
                                                /* 48kHz.                   */

ALport audioport;                               /* Audio port to output the */
                                                /* DAT sample data */

/* Our dt_audio callback */
/* It gets called when there's a change in the audio data in a frame. */

playaudio(void *arg, DTDATATYPES type, short *audio)
{
   ALwritesamps(audioport, audio, sampsperframe);
        /* Send the audio samples read out to the audio port */
}

/* Our dt_sampfreq callback. */
/* It gets called when there's a change in the subcodes that describe the */
/* sampling frequency for the recording on the DAT. */
frequency(void *arg, DTDATATYPES type, int *freq)
{
   switch (*freq) 
   {
     case DT_FREQ48000:
       sampsperframe = DTDA_NUMSAMPS48K;        /* Number of samples you'll */
                                                /* get in 1 DAT frame when  */
                                                /* the sampling rate is at  */
                                                /* 48kHz.                   */
       break;
     case DT_FREQ44100:
       sampsperframe = DTDA_NUMSAMPS44K;        /* Number of samples you'll */
                                                /* get in 1 DAT frame when  */
                                                /* the sampling rate is at  */
                                                /* 44kHz.                   */
       break;
     case DT_FREQ32000:
       sampsperframe = DTDA_NUMSAMPS32K;        /* Number of samples you'll */
                                                /* get in 1 DAT frame when  */
                                                /* the sampling rate is at  */
                                                /* 32kHz.                   */
       break;
   }

main()
{
int tape = open("/dev/nrtape", O_RDONLY);
                /* Open the file descriptor for reading data off of  */
                /* DAT Tape assumed to be /dev/nrtape.               */

DTPARSER *dtp = DTcreateparser();       /* Initialize DAT parser.   */

DTFRAME buf[4];                         /* Will describe content of current */
                                        /* DAT frame. This is what gets     */
                                        /* sent to the DAT parser.          */
                                        /* See man pagfe for DATFRAME for   */
                                        /* detailed info.                   */

struct mtop mt_com;                     /* Message structure for magntic    */
                                        /* tape device interface for use in */
                                        /* passing to the ioctl command. De-*/
                                        /* fined in /usr/include/sys/mtio.h */
                                        /* See mtio and dataudio man pages. */
}
int i, n;
   audioport = ALopenport("DAT Test", "w", 0);  /* Open audio port. */
   if (dtp)                                     /* Check for DAT parser. */
   {
      DTsetcallback(dtp, dt_audio, (DTCALLBACKFUNC)playaudio, 0);
                                /* Set up function to be called when there's */
                                /* a change in audio information.            */

      DTsetcallback(dtp, dt_sampfreq, (DTCALLBACKFUNC)frequency, 0);
                                /* Set up function to be called when there's  */
                                /* a change in sampling frequency information.*/
        /* Make sure we get sane underflow exception handling */

        sigfpe_[_UNDERFL].repls = _ZERO;
        handle_sigfpes(_ON, _EN_UNDERFL, NULL, _ABORT_ON_ERROR, NULL);
                                /* See man page for sigfpe */

   } 
   else
      exit(1);                  /* Can't do much without a DAT parser. */

   if (tape >= 0)               /* Check for tape reading OK. */ 
   {

       mt_com.mt_op = MTAUD;    /* Set up MT(io) AUD(io) message to tell */
                                /* DAT drive to turn audio mode on/off.  */
       mt_com.mt_count = 1;     /* 1 == audio mode, 0 == data mode       */

       ioctl(tape, MTIOCTOP, &mt_com);  /* Perform M(agnetic) T(ape) I/O */
                                        /* C(ontrol) T(ape) OP(eration). */
                                        /* This is needed for both       */
                                        /* reading and writing audio to  */
                                        /* or from the DAT drive.        */

       for (;;) 
       {
           n = read(tape, buf, sizeof(buf));    /* Read frame of DAT audio */
                                                /* data from the tape's    */
                                                /* file descriptor.        */
           if (n < 0) 
           {
               printf("Couldn't read DAT tape.\n");       /* Report error */
               exit(2);
           }
           if (n == 0)                    /* We're at the end of the tape */
               break;
           for (i = 0; i < 4; i++)
               DTparseframe(dtp, &buf[i]);      /* Sort out what info was in  */
                                                /* the frame we just read and */
                                                /* invoke the callbacks we    */
                                                /* specified previously.      */
       }
       exit(0);
   }
   exit(3);                     /* Can't do much without a tape to read from */
}


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