Apple Developer Connection
Member Login Log In | Not a Member? Contact ADC

< Previous PageNext Page > Hide TOC

Using the Audio Toolbox

This usage section describes how to utilize the APIs that comprise the Audio Toolbox framework available for Mac OS X.

In this section:

Using Audio Converter
Using Audio Format
Using Audio File
Using AUGraph
Using Music Player and Music Sequence


Using Audio Converter

The Audio Converter API allows for the conversion between various audio formats. These examples are provided to give the developer a feel for using the Audio Converter tool.

Creating a New Audio Converter

AudioStreamBasicDescription in, out;
 /* ... Fill out stream descriptions ... */
AudioConverterRef converter;
OSStatus err = AudioConverterNew(&in, &out, &converter);

These steps should be followed when creating a new converter:

  1. Declare two AudioStreamBasicDescription instances, one for the input, and one for the output.

  2. Populate the two descriptions with the appropriate stream information.

  3. Declare a new converter instance.

  4. Invoke the AudioConverterNew() function, providing the input, output, and converter as parameters. Note that the parameters are passed by reference.

Converting Audio Data

AudioConverterRef converter;
 /* ... Set up the converter .. */
const UInt32 kRequestPackets = 8192;
AudioBufferList bufferList;
 /* ... Allocate the output buffer ... */
 
while( /* ... While there is data left to be converted ... */ )
{
 
    UInt32 ioOutputDataPacketSize = kRequestPackets;
 
    OSStatus err = AudioConverterFillComplexBuffer(converter, inputProcPtr,
                     userData, &ioOutputDataPacketSize, &bufferList, NULL);
 
}

These steps should be followed when pulling data from a converter:

  1. Allocate and set up a converter instance.

  2. Optional: Set up a constant for the amount of data to be pulled.

  3. Set up an AudioBufferList instance to hold the converted data. If the data is interleaved, then only one index is needed for the instance’s mBuffers array; if the data consists of multiple mono channels, then allocate one index in the mBuffers array for each channel.

  4. Enter into a loop which pulls data until the *AudioConverterComplexInputProc signals that no more data is left to be pulled, or until the desired amount of data is pulled.

  5. Inside of the loop, use AudioConverterFillComplexBuffer()to pull the data. The parameters passed in this example are:

    • converter - The converter to be used.

    • inputProcPtr - A callback which provides the input data for conversion.

    • userData - Any parameters or constants needed by the inputProcPtr callback.

    • ioOutputDataPacketSize - Upon input, the requested amount of converted data; on output, the actual amount of data converted.

    • bufferList - The buffer for the converted audio data.

    • NULL - An AudioStreamPacketDescription instance used to describe the size of the resulting packet; only needed when receiving variable bit rate (VBR) data.

Supplying Data for AudioConverterFillComplexBuffer()

OSStatus FromFloatInputProc (
    AudioConverterRef inAudioConverter,
    UInt32 *ioNumberDataPackets,
    AudioBufferList *ioData,
    AudioStreamPacketDescription **outDataPacketDescription,
    void *inUserData )
{
MyUserData *data = static_cast<MyUserData*>(inUserData);
AudioBufferList *bufferList = data->bufferList;
for (UInt32 i=0; i < bufferList->mNumberBuffers; ++i)
{
    ioData->mBuffers[i].mNumberChannels =
        bufferList->mBuffers[i].mNumberChannels;
    ioData->mBuffers[i].mData = bufferList->mBuffers[i].mData;
    ioData->mBuffers[i].mDataByteSize =
        bufferList->mBuffers[i].mDataByteSize;
}
*ioNumberDataPackets = ioData->mBuffers[0].mDataByteSize /
                                data->mInputASBD.mBytesPerPacket;
return noErr;
}

This example looks at creating an *AudioConverterComplexInputDataProc for use by AudioConverterFillComplexBuffer():

  1. An *AudioConverterComplexInputDataProc takes in the following arguments:

    • inAudioConverter - The converter in use.

    • ioNumberPackets - The number of packets requested.

    • ioData - The data to be returned to AudioConverterFillComplexBuffer() for conversion.

    • outPacketDescription - Provided to give the details of the packet format passed back when decoding. This should be an AudioStreamPacketDescription array, with each packet corresponding to a descrption.

    • inUserData - Data needed by the callback, for any purpose; in this case, it holds the location of the input data.

  2. In a loop, fill each buffer in ioData with the number of channels, requested amount of data, and data byte size.

  3. Calculate the number of provided packets, and place the value in ioNumberDataPackets.

Using Audio Format

The Audio Format API is provided to acquire information about formats and channel layouts. This example is provided to give the developer a feel for using the Audio Format API.

Getting Format ID Information

UInt32 size;
OSStatus err;
OSType *formatIDs;
 
err = AudioFormatGetPropertyInfo(
                    kAudioFormatProperty_EncodeFormatIDs, 0, NULL, &size);
if (err) return err;
 
formatIDs = (OSType*)malloc(size);
UInt32 numFormats = size / sizeof(OSType);
 
err = AudioFormatGetProperty(
         kAudioFormatProperty_EncodeFormatIDs, 0, NULL, &size, formatIDs);
if (err) return err;
 
for (UInt32 i=0; i<numFormats; ++i)
{
    AudioStreamBasicDescription absd;
    memset(&absd, 0, sizeof(absd));
    absd.mFormatID = formatIDs[i];
 
    CFStringRef name;
    size = sizeof(CFStringRef);
    err = AudioFormatGetProperty(
        kAudioFormatProperty_FormatName, sizeof(absd), &absd, &size, &name);
    if (err) return err;
 
    CFShow(name);
}

This example shows how to use the property management system available in Audio Format to acquire information about the encoder formats, and output their names:

  1. Use the AudioFormatGetPropertyInfo() function to get the size of the data that will be returned by calling AudioFormatGetProperty() for that property. The arguments for AudioFormatGetPropertyInfo() are:

    • kAudioFormatProperty_EncodeFormatIDs - The property we are querying for.

    • 0 - The size of the specifier; in this case, 0, since this property does not require a specifier.

    • NULL - The specifier; in this case, NULL, since the property does not require a specifier.

    • &size - The size of the data that will be returned when AudioFormatGetProperty() is called for this property.

  2. Once the size is obtained, AudioFormatGetProperty() may be called. The same parameters are passed in as with AudioFormatGetPropertyInfo(), with the addition of a void pointer, in this case formatIDs, to hold the returned data in.

  3. Now that an array of the format IDs have been attained, it may be iterated over by following these steps:

    1. Create an AudioStreamBasicDescription instance and clear its contents.

    2. Set the format ID of the AudioStreamBasicDescription instance to one of the format IDs returned when AudioFormatGetProperty() was called with the kAudioFormatProperty_EncodeFormatIDs property.

    3. Create a CFStringRef to hold the name of the format, and obtain its size.

    4. To obtain the format’s name, call AudioFormatGetProperty() with kAudioFormatProperty_FormatName as the property, the AudioStreamBasicDescription instance as the specifier, and the CFString as the outPropertyData value.

    5. Print out the name of the format, as stored in the CFString.

Using Audio File

The Audio File API is used to discover global file format information and to provide an interface for creating, opening, modifying, and saving audio files. This example is provided to give the developer a feel for using the Audio File API.

Acquiring Global File Information

OSStatus err;
UInt32 propertySize;
 
err = AudioFileGetGlobalInfoSize(
                kAudioFileGlobalInfo_WritableTypes, 0, NULL, &propertySize);
if (err) return err;
 
OSType *types = (OSType*)malloc(propertySize);
err = AudioFileGetGlobalInfo(
         kAudioFileGlobalInfo_WritableTypes, 0, NULL, &propertySize, types);
if (err) return err;
 
UInt32 numTypes = propertySize / sizeof(OSType);
for (UInt32 i=0; i<numTypes; ++i)
{
    CFStringRef name;
    UInt32 outSize = sizeof(name);
    err = AudioFileGetGlobalInfo(
kAudioFileGlobalInfo_FileTypeName, sizeof(OSType), types+i, &outSize, &name);
    if (err) return err;
 
    CFShow(name);
}

This example shows how to obtain an array of the writable file types for the system and output their names:

  1. Use the AudioFileGetGlobalInfoSize() function to get the size of the data that will be returned by calling AudioFileGetGlobalInfo() for that property. The arguments for AudioFileGetGlobalInfoSize() are:

    • kAudioFileGlobalInfo_WritableTypes - The property we are querying for.

    • 0 - The specifier’s size; in this case, 0, since there is now need for a specifier for this property.

    • NULL - The specifier; in this case, NULL, since this property does not require a specifier.

    • &propertySize - The size of the data that will be returned when AudioFileGetGlobalInfo() is called for this property.

  2. Once the size is obtained, it will be used by the AudioFileGetGlobalInfo() function. In addition to the four parameters used for AudioFileGetGlobalInfoSize(), a fifth is used to point to the actual property data returned by this method. Note that the space for holding this returned data is allocated beforehand, using the propertySize obtained previously.

  3. Once the types have been returned, a loop is used to cycle through the types and query Audio File for the name of the type, which is then printed:

    1. Create a CFStringRef to hold the name of the type, and obtain its size.

    2. To obtain the type’s name, call AudioFileGetGlobalInfo() with kAudioFileGlobalInfo_FileTypeName as the property, the type as the specifier, and the CFString as the outPropertyData value.

    3. Print out the name of the format, as stored in the CFString.

Using AUGraph

Audio Unit Graph State

An audio unit graph maintains its representation using the AUNode type, even when the Audio Unit components themselves are not instantiated.

The AUGraph states are defined as open, initialized, running, and closed. These correspond directly with the Audio Unit states.

The AUGraph APIs are responsible for representing the description of a set of Audio Unit components, as well as the audio connections between their inputs and outputs. This representation may be saved and restored persistently and instantiated by opening all of the Audio Units (AUGraphOpen()), and making the physical connections between them stored in the representation (AUGraphInitialize()). Thus, the graph is a description of the various Audio Units and their connections, but also manage the actual instantiated Audio Units.

The AUGraph is a complete description of an audio signal processing network.

The AUGraph may be introspected in order to get complete information about all of the Audio Units in the graph. The various nodes (AUNode) in the graph representing Audio Units may be added or removed, and the connections between them modified.

An AUNode representing an Audio Unit component is created by specifying a ComponentDescription record (from the Component Manager), as well as optional “class” data, which is passed to the Audio Unit when it is opened.

This class data is in an arbitrary format, and may differ depending on the particular Audio Unit. In general, the data is used by the Audio Unit to configure itself when it is opened (in object-oriented terms, it corresponds to constructor arguments). In addition, certain AudioUnits may provide their own class data when they are closed, allowing their current state to be saved for the next time they are instantiated. This provides a general mechanism for persistence.

An AUGraph's state can be manipulated in both the rendering thread and in other threads. Consequently, any activities that effect the state of the graph are guarded with locks.To avoid blocking the render thread, many of the calls to AUGraph may return kAUGraphErr_CannotDoInCurrentContext. This result is only generated when an graph modification is called from within a render callback. It means that the lock that it required was held at that time by another thread. If this result code is returned, the action may be retried, typically on the next render cycle (so in the mean time the lock can be cleared), or the action may be delegated to another thread. As a general rule, the render thread should not be allowed to spin.

Setting up an Audio Unit Graph

When using AUGraph, certain steps need to be followed in order for the graph to function properly. These steps must be followed in this order:

Modifying an Audio Unit Graph

Once a graph has been created, its contents may be modified by adding and removing connections and nodes. These functions are provided for performing these actions:

After the graph is initialized, any of these functions may be called, and the changes will occur immediately, meaning that the node will be initialized right way, and connections will be made immediately as well.

When a graph is running, however, changes to not immediately take effect. Calling any of these functions is allowed, but the actions they perform are queued. To apply the actions to a running graph, AUGraphUpdate must be called.

Calling an update signals to the render thread that an update is ready to occur. When the render thread gets to a point in its cycle where updates are allowed (usually before and after a render), the update is actually performed. Before calling an update, check the format, sample rates, and channel layouts of the connections to avoid errors. If an error does occur, all updates are halted.

Closing an Audio Unit Graph

When audio data rendering is no longer needed, the graph may be stopped by calling AUGraphStop. This does not alter the graph in any way; it simply halts the pull on the output node. However, AUGraph uses a reference counting scheme to ensure that one process does not stop the graph while another may still be accessing it. Each AUGraphStart() invocation adds one to the reference, while each AUGraphStop() subtracts one from the reference. When the reference becomes zero, it then stops rendering. To determine if a graph is still running, use AUGraphIsRunning.

Uninitializing the graph is done by calling AUGraphUninitialize. First, doing so will stop audio render, no matter what the reference count for the graph is. Beyond that, it also calls the Uninitialize() function for each Audio Unit and subgraph.

If the graph is no longer needed, calling AUGraphClose will close all the Audio Unit components in the graph, leaving only a nodal representation of the graph. As with uninitialization, if the graph is rendering audio data, calling this function halts the render.

It is worth noting that the graph’s structure may be serialized using the AUGraphGetNodeInfo and AUGraphGetNodeConnections functions at any time the graph exists.

When the AUGraph is no longer needed, use DisposeAUGraph to deallocate it.

Using Music Player and Music Sequence

Setting Up a Music Sequence

A music sequence is designed to hold various music tracks, which are intended to be logical groupings of events. To use a music sequence, these functions need to be called:

Also of note is the fact that you can reverse all the events in all of the tracks of a sequence by calling MusicSequenceReverse.

Adding Events to Tracks

Events trigger changes to the destination of a track. There are eight different types of events:

Setting Destinations for Sequences and Tracks

A sequence or track must address either a MIDI endpoint or an audio unit (when used inside of an audio unit graph). All of the events belonging to a sequence or track will then be sent to its assigned destination.

An entire sequence can be assigned to an endpoint or a graph via:

When targeting a sequence to a specific graph, its tracks need to be assigned to units within the track; this is done via MusicTrackSetDestNode. Within this context, endpoints can still be addressed by a track using MusicTrackSetDestMIDIEndpoint.

Using the Tempo Track

Each sequence has a tempo track assigned to it, which can not be removed. This track is designed to control the rate of playback across the sequence’s tracks. The units of measurement used here are beats-per-minute (bpm), which can be any floating point value. An event in the tempo track will change the playback rate to the new event’s specified rate.

All of the events for this track must be of type ExtendedTempoEvent; no others are allowed in the tempo track, and this event type is not allowed in event tracks. Each tempo track starts out with one of these, specifying the initial playback rate. By default, this rate is 120 bpm, but it can be modified to be any floating point value.

To access the tempo track, call MusicSequenceGetTempoTrack. Once you acquire the tempo track, and tempo event can be added to it by calling MusicTrackExtendedTempoEvent and passing in a new event. The event should have the intended rate in it. This means that, once the event is reached, the new rate will be used from that point on, until playback stops, or the next tempo event is reached. During that time, all events in all tracks within the sequence will occur at the new rate. For example, if the initial tempo was 120 bpm, and the next tempo event was set to occur at the 90th beat, it will occur after 45 seconds. If that event changes the tempo to 60 bpm, all of the events from that point on will happen at half the rate of the previous tempo. So if the next tempo event is set to occur at the 120th beat, will happen 75 seconds after playback has begun.

Disposing of Sequences and Tracks

When a sewuence is no longer needed, it may be disposed of. This is done by calling DisposeMusicSequence.

To delete a track and its accompanying events, call MusicSequenceDisposeTrack.

Getting Information about a Sequences and Tracks

A number of functions are provided with the Music Sequence API to get information about a sequence and its tracks:

Using Music Track Properties

Properties are used to change the status of various tracks, as described in “Music Track Properties.” Use MusicTrackGetProperty to retrieve the current value of any of the properties, and MusicTrackSetProperty to change the value of the property.

Accessing Events within a Track

To gain access to the events within a track, the track needs to be iterated over. Iterating involves creating a new iterator, and then moving forward or backward between the events within the track. These functions are used when setting up an iterator and iterating on a track:

Editing a Track

Events within a track can be modified based on their placement within a track. The idea is to be able to grab the events within a certain amount period of time and then to move them elsewhere, or delete them.

To move events within a track, use MusicTrackMoveEvents. All you need to do is to specify the range of events to move, and where to move them to.

The NewMusicTrackFrom function will take the specified range of events, and will create a new track with the range in it.

MusicTrackClear will remove the events in the given range, while MusicTrackCut will remove the given range, and move the events after the range up to fill the space left by the cut.

Use MusicTrackCopyInsert to copy a series of event from one track to another. Doing so with this function move the events behind the insertion point back to the end of the range in the destination track.

Finally, MusicTrackMerge will take the source range and merge it with the events following the insertion point in the destination.

Setting Up a Music Player

A music player is associated with a music sequence, in a one-to-one relationship. The player keeps track of the playhead for the sequence, and allows for movement within the sequence. Activating and stopping the sequence is done via a player.

Of note is when the playhead is moved within the player. This is done using MusicPlayerSetTime, which also prerolls the sequence for you. Prerolling is when the sequence is prepared to begin in mid-sequence, with all parameters and endpoints being adjusted to the points they should be at the playhead. To determine what time the player is currently at, use MusicPlayerGetTime.

If a new event is added to a sequence, it will need to be manually prerolled using MusicPlayerPreroll.

Reading in Standard MIDI Files or MIDI Data

The Music Player API is used to read in MIDI files. To do so, simply call MusicSequenceLoadSMF, specifying the file from which the data is to be read in, or MusicSequenceLoadSMFData when the MIDI is to be read in from memory. When these functions are used, the MIDI data is parsed and placed, as events, in a track inside of a sequence.

Calling MusicSequenceLoadSMFWithFlags and MusicSequenceLoadSMFDataWithFlags, with kMusicSequenceLoadSMF_ChannelsToTracks passed in as the flag will result in each channel in the MIDI data being parsed into its own track in the sequence. Beyond that, any meta data that is found in the MIDI sequence in placed in the last track of the sequence.

Saving MIDI Data

When you want to save incoming MIDI data to disk, first you need to capture the incoming MIDI data. The data then needs to be parsed and placed into a sequence, specifically into a track. You will need to use the MusicPlayerGetBeatsForHostTime function to determine how far into the sequence the new MIDI event will need to be. This can only be done as the sequence in running, so that calling this will return the current beat in the sequence when it is invoked.

Once all of the incoming MIDI data has been captured and placed into a sequence, it needs to be saved to disk. To do this, call the MusicSequenceSaveSMF function, if saving the data to disk, or, if saving it to memory, call MusicSequenceSaveSMFData.



< Previous PageNext Page > Hide TOC


Last updated: 2004-03-25




Did this document help you?
Yes: Tell us what works for you.

It’s good, but: Report typos, inaccuracies, and so forth.

It wasn’t helpful: Tell us what would have helped.
Get information on Apple products.
Visit the Apple Store online or at retail locations.
1-800-MY-APPLE

Copyright © 2007 Apple Inc.
All rights reserved. | Terms of use | Privacy Notice