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:
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).
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.
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.
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);
|
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:
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:
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:
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.
This section explains how to use the DAT Audio Library routines for:
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:
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.)
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.
Open the audio port.
Open the DAT drive.
Create a parser.
Add your dt_sampfreq and dt_audio callbacks to the parser.
Read samples from the DAT.
Parse the samples.
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:
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:
create an empty DTFRAME
set the program number contained in the DAT frame to 0x0BB (beginning-of-tape, or BOT, code)
set the START bit in the control ID
set the subcode packs to 0x0AA (readable, not valid)
fill the audio data block with zeros
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().
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.
This section contains datplay.c, a simple program for reading and processing DAT data.
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
|