This usage section describes how to utilize the APIs that comprise the Audio Toolbox framework available for Mac OS X.
Using Audio Converter
Using Audio Format
Using Audio File
Using AUGraph
Using Music Player and Music Sequence
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.
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:
Declare two AudioStreamBasicDescription instances,
one for the input, and one for the output.
Populate the two descriptions with the appropriate stream information.
Declare a new converter instance.
Invoke the AudioConverterNew() function,
providing the input, output, and converter as parameters. Note that
the parameters are passed by reference.
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:
Allocate and set up a converter instance.
Optional: Set up a constant for the amount of data to be pulled.
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.
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.
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.
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():
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.
In a loop, fill each buffer in ioData with
the number of channels, requested amount of data, and data byte
size.
Calculate the number of provided packets, and place the value in ioNumberDataPackets.
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.
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:
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.
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.
Now that an array of the format IDs have been attained, it may be iterated over by following these steps:
Create
an AudioStreamBasicDescription instance and
clear its contents.
Set the format ID of the AudioStreamBasicDescription instance
to one of the format IDs returned when AudioFormatGetProperty() was
called with the kAudioFormatProperty_EncodeFormatIDs property.
Create a CFStringRef to hold the name
of the format, and obtain its size.
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.
Print out the name of the format, as stored in the CFString.
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.
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:
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.
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.
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:
Create a CFStringRef to
hold the name of the type, and obtain its size.
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.
Print out the name of the format, as stored in the CFString.
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.
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:
Create the graph. Call NewAUGraph on an AUGraph instance.
Populate the graph. Use AUGraphNewNode and AUGraphNewNodeSubGraph to populate the
graph.
Make connections between nodes. AUGraphConnectNodeInput sets
up connections between the nodes. Nodes may have multiple inputs
and outputs, though sharing an input or output is not allowed. This
is otherwise known as “fan in” and “fan out,” and is not allowed
in AUGraph.
Open the graph. Up until this point,
each node was being used in the abstract AUNode representation.
Upon calling AUGraphOpen,
each node is instantiated. This allows for properties to be set
for each Audio Unit inside of the graph.
Set output sample rates and channel layouts. A common error is for sample rates and channel layouts to be mismatched between nodes. If any format changes occur, it is imperative that sample rates and channel number be set for all Audio Unit outputs prior to initialization
Initialize the graph. Once setup has
occurred, AUGraphInitialize may
be called. This makes all the connections between the nodes, and
initializes all of the Audio Units that are part of a connection.
At the least, the output unit is initialized, even if no connections
lead to it.
Start the graph. Calling AUGraphStart begins
rendering, starting with the output unit and traversing through
the 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.
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.
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:
NewMusicSequence -
This creates a new music sequence. The sequence, as is, contains
only a tempo track.
MusicSequenceNewTrack -
Call this function for each new track that you want in the sequence.
Also of note is the fact that you can reverse all the events
in all of the tracks of a sequence by calling MusicSequenceReverse.
Events trigger changes to the destination of a track. There are eight different types of events:
MIDIChannelMessage -
For use with MusicTrackNewMIDIChannelEvent.
These events will pass the assigned data on to the channel when
triggered.
MIDINoteMessage - For
use with MusicTrackNewMIDINoteEvent.
These events will pass the information for a note to the targeted
endpoint when triggered.
MIDIRawData - For use
with MusicTrackNewMIDIRawDataEvent.
These events will pass raw MIDI data on to an endpoint when triggered.
MIDIMetaEvent - For
use with MusicTrackNewMetaEvent.
These events should be used when MIDI meta data needs to be passed
on to an endpoint.
MusicEventUserData -
For use with MusicTrackNewUserEvent.
This event will call a MusicSequenceUserCallback callback,
passing in the structure’s user data to the callback. The callback
is registered via MusicSequenceSetUserCallback,
with each sequence allowing for one callback to be registered to
it.
ExtendedNoteOnEvent -
For use with MusicTrackNewExtendedNoteEvent.
These events will send a note message to a music device audio unit
when triggered.
ExtendedControlEvent -
For use with MusicTrackNewExtendedControlEvent.
These events will send a control message, changing the parameters
of a music device audio unit when triggered.
ParameterEvent - For
use with MusicTrackNewParameterEvent.
These events will issue a parameter change in an audio unit when
triggered.
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.
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.
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.
A number of functions are provided with the Music Sequence API to get information about a sequence and its tracks:
MusicSequenceGetTrackCount -
Returns the number of tracks in the current sequence.
MusicSequenceGetIndTrack -
Returns a pointer towards a track for a particular index.
MusicSequenceGetTrackIndex -
Returns the index of a track.
MusicSequenceGetAUGraph -
Returns a pointer for the audio unit graph currently assigned to the
sequence. Returns NULL if there is no graph assigned.
MusicTrackGetSequence -
Returns a pointer to the sequence which contains the current track.
MusicTrackGetDestNode -
Returns a pointer towards the node used by the selected track.
MusicTrackGetDestMIDIEndpoint -
Returns a pointer towards the MIDI endpoint used by the selected
track.
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.
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:
NewMusicEventIterator -
Creates a new iterator.
DisposeMusicEventIterator -
Disposes of the iterator.
MusicEventIteratorNextEvent -
Moves the iterator to the next event.
MusicEventIteratorPreviousEvent -
Moves the iterator to the previous event
MusicEventIteratorGetEventInfo -
Retrieves information about the current event.
MusicEventIteratorSetEventInfo -
Sets information about the current event.
MusicEventIteratorDeleteEvent -
Removes the event from the track.
MusicEventIteratorSetEventTime -
Moves the event to the new time.
MusicEventIteratorHasPreviousEvent -
Returns a boolean signifying if there is an event before the iterator.
MusicEventIteratorHasNextEvent -
Returns a boolean signifying if there is an event after the iterator.
MusicEventIteratorHasCurrentEvent -
Returns a boolean signifying if the iterator currently points towards
an event, or is at the end of the 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.
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.
NewMusicPlayer -
Creates a new music player.
DisposeMusicPlayer -
Disposes of the music player. Note that this does not dispose of
the sequence attached to the player.
MusicPlayerSetSequence -
Sets the sequence controlled by the player.
MusicPlayerStart -
Begins playback of the sequence.
MusicPlayerStop - Halts
sequence playback.
MusicPlayerIsPlaying -
Returns a boolean signifying if the player is in use.
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.
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.
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.
Last updated: 2004-03-25