Technical Q&A QA1717

Using the iPodTime audio unit for playing audio books

Q:  What properties can be set for AUiPodTime and how do I configure it?

A: What properties can be set for AUiPodTime and how do I configure it?

AUiPodTime is a time-stretching audio unit able to perform rate changes from 0.5x to 2x. This unit is designed to be used with audio books and speech recordings such as a recorded lecture allowing playback speed changes without affecting pitch.

The AUiPodTime audio unit is described by the kAudioUnitType_FormatConverter type and kAudioUnitSubType_AUiPodTime subtype. The input and output stream format must be the canonical audio unit data format and may contain one or two channels. Both formats must have the same number of channels and the same sample rate; all common sample rates are supported.

When using this audio unit in an audio unit graph, remember to set the output stream format -- each nodes output stream format (including sample rate) must be set explicitly. This stream format is then propagated to its destination's input stream format.

Listing 1  Typical example of setting up an audio unit graph.

- (void)initializeAUGraph:(Float64)inSampleRate
{
    printf("initializeAUGraph\n");

    AUNode outputNode;
    AUNode iPodTimeNode;
    AUNode mixerNode;
    AUNode converterNode;
    AudioUnit converterAU;

    printf("create client format ASBD\n");

    // client format audio going into the converter
    mClientFormat.SetCanonical(2, true); 
    mClientFormat.mSampleRate = 22050.0;
    mClientFormat.Print();

    printf("create output format ASBD\n");

    // output format
    mOutputFormat.SetAUCanonical(2, false); 
    mOutputFormat.mSampleRate = inSampleRate;
    mOutputFormat.Print();

    OSStatus result = noErr;

    printf("-----------\n");
    printf("new AUGraph\n");

    // create a new AUGraph
    result = NewAUGraph(&mGraph);
    if (result) { printf("NewAUGraph result %ld %08X %4.4s\n", result,
                    (unsigned int)result, (char*)&result); return; }

    // create three CAComponentDescription for the AUs we want in the graph

    // output unit
    CAComponentDescription output_desc(kAudioUnitType_Output,
                                       kAudioUnitSubType_RemoteIO,
                                       kAudioUnitManufacturer_Apple);

    // iPodTime unit
    CAComponentDescription iPodTime_desc(kAudioUnitType_FormatConverter,
                                         kAudioUnitSubType_AUiPodTime,
                                         kAudioUnitManufacturer_Apple);

    // multichannel mixer unit
    CAComponentDescription mixer_desc(kAudioUnitType_Mixer,
                                      kAudioUnitSubType_MultiChannelMixer,
                                      kAudioUnitManufacturer_Apple);

    // AU Converter
    CAComponentDescription converter_desc(kAudioUnitType_FormatConverter,
                                          kAudioUnitSubType_AUConverter,
                                          kAudioUnitManufacturer_Apple);

    printf("add nodes\n");

    // create a node in the graph that is an AudioUnit, using the supplied
    // AudioComponentDescription to find and open that unit
    result = AUGraphAddNode(mGraph, &output_desc, &outputNode);
    if (result) { printf("AUGraphNewNode 1 result %lu %4.4s\n", result, (char*)&result);
                    return; }

    result = AUGraphAddNode(mGraph, &iPodTime_desc, &iPodTimeNode);
    if (result) { printf("AUGraphNewNode 2 result %lu %4.4s\n", result, (char*)&result);
                    return; }

    result = AUGraphAddNode(mGraph, &mixer_desc, &mixerNode);
    if (result) { printf("AUGraphNewNode 3 result %lu %4.4s\n", result, (char*)&result);
                    return; }

    result = AUGraphAddNode(mGraph, &converter_desc, &converterNode);
    if (result) { printf("AUGraphNewNode 3 result %lu %4.4s\n", result, (char*)&result);
                    return; }

    // connect a node's output to a node's input
    // converter -> mixer -> iPodTime -> output

    result = AUGraphConnectNodeInput(mGraph, converterNode, 0, mixerNode, 0);
    if (result) { printf("AUGraphConnectNodeInput result %lu %4.4s\n", result,
                    (char*)&result); return; }

    result = AUGraphConnectNodeInput(mGraph, mixerNode, 0, iPodTimeNode, 0);
    if (result) { printf("AUGraphConnectNodeInput result %lu %4.4s\n", result,
                    (char*)&result); return; }

    result = AUGraphConnectNodeInput(mGraph, iPodTimeNode, 0, outputNode, 0);
    if (result) { printf("AUGraphConnectNodeInput result %lu %4.4s\n", result,
                    (char*)&result); return; }

    // open the graph -- AudioUnits are open but not initialized
    // (no resource allocation occurs here)
    result = AUGraphOpen(mGraph);
    if (result) { printf("AUGraphOpen result %ld %08X %4.4s\n", result,
                    (unsigned int)result, (char*)&result); return; }

    // grab audio unit instances from the nodes
    result = AUGraphNodeInfo(mGraph, converterNode, NULL, &converterAU);
    if (result) { printf("AUGraphNodeInfo result %ld %08X %4.4s\n", result,
                    (unsigned int)result, (char*)&result); return; }

    result = AUGraphNodeInfo(mGraph, mixerNode, NULL, &mMixer);
    if (result) { printf("AUGraphNodeInfo result %ld %08X %4.4s\n", result,
                    (unsigned int)result, (char*)&result); return; }

    result = AUGraphNodeInfo(mGraph, iPodTimeNode, NULL, &mIPodTime);
    if (result) { printf("AUGraphNodeInfo result %ld %08X %4.4s\n", result,
                    (unsigned int)result, (char*)&result); return; }

    // set bus count
    UInt32 numbuses = 1;

    printf("set input bus count %lu\n", numbuses);

    result = AudioUnitSetProperty(mMixer, kAudioUnitProperty_ElementCount,
                    kAudioUnitScope_Input, 0, &numbuses, sizeof(numbuses));
    if (result) { printf("AudioUnitSetProperty result %ld %08X %4.4s\n", result,
                    (unsigned int)result, (char*)&result); return; }

    // setup render callback struct
    AURenderCallbackStruct rcbs;
    rcbs.inputProc = &renderInput;
    rcbs.inputProcRefCon = &mUserData;

    printf("set AUGraphSetNodeInputCallback\n");

    // set a callback for the specified node's specified input bus (bus 1)
    result = AUGraphSetNodeInputCallback(mGraph, converterNode, 0, &rcbs);
    if (result) { printf("AUGraphSetNodeInputCallback result %ld %08X %4.4s\n", result,
                    (unsigned int)result, (char*)&result); return; }

    printf("set converter input bus %d client kAudioUnitProperty_StreamFormat\n", 0);

    // set the input stream format, this is the format of the audio
    // for the converter input bus (bus 1)
    result = AudioUnitSetProperty(converterAU, kAudioUnitProperty_StreamFormat,
                kAudioUnitScope_Input, 0, &mClientFormat, sizeof(mClientFormat));
    if (result) { printf("AudioUnitSetProperty result %ld %08X %4.4s\n", result,
                    (unsigned int)result, (char*)&result); return; }

    // in an au graph, each nodes output stream format (including sample rate)
    // needs to be set explicitly this stream format is propagated to its
    // destination's input stream format

    printf("set converter output kAudioUnitProperty_StreamFormat\n");

    // set the output stream format of the converter
    result = AudioUnitSetProperty(converterAU, kAudioUnitProperty_StreamFormat,
                kAudioUnitScope_Output, 0, &mOutputFormat, sizeof(mOutputFormat));
    if (result) { printf("AudioUnitSetProperty result %ld %08X %4.4s\n", result,
                    (unsigned int)result, (char*)&result); return; }

    printf("set mixer output kAudioUnitProperty_StreamFormat\n");

    // set the output stream format of the mixer
    result = AudioUnitSetProperty(mMixer, kAudioUnitProperty_StreamFormat,
                kAudioUnitScope_Output, 0, &mOutputFormat, sizeof(mOutputFormat));
    if (result) { printf("AudioUnitSetProperty result %ld %08X %4.4s\n", result,
                    (unsigned int)result, (char*)&result); return; }

    printf("set iPodTime output kAudioUnitProperty_StreamFormat\n");

    // set the output stream format of the iPodTime unit
    result = AudioUnitSetProperty(mIPodTime, kAudioUnitProperty_StreamFormat,
                kAudioUnitScope_Output, 0, &mOutputFormat, sizeof(mOutputFormat));
    if (result) { printf("AudioUnitSetProperty result %ld %08X %4.4s\n", result,
                    (unsigned int)result, (char*)&result); return; }

    printf("AUGraphInitialize\n");

    // now that we've set everything up we can initialize the graph, this will also
    // validate the connections
    result = AUGraphInitialize(mGraph);
    if (result) { printf("AUGraphInitialize result %ld %08X %4.4s\n", result,
                    (unsigned int)result, (char*)&result); return; }

    CAShow(mGraph);
}

The kTimePitchParam_Rate parameter declared in AudioUnitParameters.h is used to control audio playback rate from 0.5x to 2.0x speed. AudioUnitParameterValue is a Float32 rounded by the unit to whichever of the following is closest: 0.5, 0.66667, 0.8, 1.0, 1.25, 1.5, 2. A playback rate of 1.0 is the default.

Listing 2  Method setting the AUiPodTime rate.

// sets the rate of the iPodTime Audio Unit
- (void)setIPodTimeRate:(AudioUnitParameterValue)value
{
    OSStatus result = AudioUnitSetParameter(mIPodTime,
            kTimePitchParam_Rate, kAudioUnitScope_Global, 0, value, 0);
    if (result) { printf("AudioUnitSetParameter kTimePitchParam_Rate
     Global result %ld %08X %4.4s\n", result, (unsigned int)result, (char*)&result); return; }
}


Document Revision History


DateNotes
2010-09-03

New document that describes the usage of the AUiPodTime unit which is useful when playing back audio books at different rates without changing pitch.