AUGraph FormatConverter (AUConverter) render notify contains NULL ioData buffer

3

I am working on a iOS project and need to capture input from the microphone and convert it to ULaw (to send out a data stream). I am using an AUGraph with a converter node to accomplish this. The graph is created successfully and initialized, however in my render notify callback, the ioData buffer always contains NULL even thought inNumberFrame contains a value of 93. I think it might have something to due with incorrect size of format converter buffers, but I can figure out what is happening.

Here is the code:

OSStatus status;

// ************************** DEFINE AUDIO STREAM FORMATS ******************************

double currentSampleRate;
currentSampleRate = [[AVAudioSession sharedInstance] sampleRate];

// Describe stream format
AudioStreamBasicDescription streamAudioFormat = {0};
streamAudioFormat.mSampleRate             = 8000.00;
streamAudioFormat.mFormatID               = kAudioFormatULaw;
streamAudioFormat.mFormatFlags            = kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
streamAudioFormat.mFramesPerPacket        = 1;
streamAudioFormat.mChannelsPerFrame       = 1;
streamAudioFormat.mBitsPerChannel         = 8;
streamAudioFormat.mBytesPerPacket         = 1;
streamAudioFormat.mBytesPerFrame          = streamAudioFormat.mBytesPerPacket * streamAudioFormat.mFramesPerPacket;


// ************************** SETUP SEND AUDIO ******************************

AUNode ioSendNode;
AUNode convertToULAWNode;
AUNode convertToLPCMNode;
AudioUnit convertToULAWUnit;
AudioUnit convertToLPCMUnit;

status = NewAUGraph(&singleChannelSendGraph);
if (status != noErr)
{
    NSLog(@"Unable to create send audio graph.");
    return;
}

AudioComponentDescription ioDesc = {0};
ioDesc.componentType = kAudioUnitType_Output;
ioDesc.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
ioDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
ioDesc.componentFlags = 0;
ioDesc.componentFlagsMask = 0;

status = AUGraphAddNode(singleChannelSendGraph, &ioDesc, &ioSendNode);
if (status != noErr)
{
    NSLog(@"Unable to add IO node.");
    return;
}

AudioComponentDescription converterDesc = {0};
converterDesc.componentType = kAudioUnitType_FormatConverter;
converterDesc.componentSubType = kAudioUnitSubType_AUConverter;
converterDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
converterDesc.componentFlags = 0;
converterDesc.componentFlagsMask = 0;

status = AUGraphAddNode(singleChannelSendGraph, &converterDesc, &convertToULAWNode);
if (status != noErr)
{
    NSLog(@"Unable to add ULAW converter node.");
    return;
}

status = AUGraphAddNode(singleChannelSendGraph, &converterDesc, &convertToLPCMNode);
if (status != noErr)
{
    NSLog(@"Unable to add LPCM converter node.");
    return;
}

status = AUGraphOpen(singleChannelSendGraph);
if (status != noErr)
{
    return;
}

// get the io audio unit
status = AUGraphNodeInfo(singleChannelSendGraph, ioSendNode, NULL, &ioSendUnit);
if (status != noErr)
{
    NSLog(@"Unable to get IO unit.");
    return;
}

UInt32 enableInput = 1;
status = AudioUnitSetProperty (ioSendUnit,
                               kAudioOutputUnitProperty_EnableIO,
                               kAudioUnitScope_Input,
                               1,       // microphone bus
                               &enableInput,
                               sizeof (enableInput)
                               );
if (status != noErr)
{
    return;
}

UInt32 sizeASBD = sizeof(AudioStreamBasicDescription);
AudioStreamBasicDescription ioASBDin;
AudioStreamBasicDescription ioASBDout;
status = AudioUnitGetProperty(ioSendUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 1, &ioASBDin, &sizeASBD);
if (status != noErr)
{
    NSLog(@"Unable to get IO stream input format.");
    return;
}

status = AudioUnitGetProperty(ioSendUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &ioASBDout, &sizeASBD);
if (status != noErr)
{
    NSLog(@"Unable to get IO stream output format.");
    return;
}

ioASBDin.mSampleRate = currentSampleRate;
ioASBDout.mSampleRate = currentSampleRate;

status = AudioUnitSetProperty(ioSendUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &ioASBDin, sizeof(ioASBDin));
if (status != noErr)
{
    NSLog(@"Unable to set IO stream output format.");
    return;
}

status = AudioUnitSetProperty(ioSendUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &ioASBDin, sizeof(ioASBDin));
if (status != noErr)
{
    NSLog(@"Unable to set IO stream input format.");
    return;
}

// get the converter audio unit
status = AUGraphNodeInfo(singleChannelSendGraph, convertToULAWNode, NULL, &convertToULAWUnit);
if (status != noErr)
{
    NSLog(@"Unable to get ULAW converter unit.");
    return;
}

status = AudioUnitSetProperty(convertToULAWUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &ioASBDin, sizeof(ioASBDin));
if (status != noErr)
{
    NSLog(@"Unable to set ULAW stream input format.");
    return;
}

status = AudioUnitSetProperty(convertToULAWUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamAudioFormat, sizeof(streamAudioFormat));
if (status != noErr)
{
    NSLog(@"Unable to set ULAW stream output format.");
    return;
}

// get the converter audio unit
status = AUGraphNodeInfo(singleChannelSendGraph, convertToLPCMNode, NULL, &convertToLPCMUnit);
if (status != noErr)
{
    NSLog(@"Unable to get LPCM converter unit.");
    return;
}

status = AudioUnitSetProperty(convertToLPCMUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamAudioFormat, sizeof(streamAudioFormat));
if (status != noErr)
{
    NSLog(@"Unable to set LPCM stream input format.");
    return;
}

status = AudioUnitSetProperty(convertToLPCMUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &ioASBDin, sizeof(ioASBDin));
if (status != noErr)
{
    NSLog(@"Unable to set LPCM stream output format.");
    return;
}

status = AUGraphConnectNodeInput(singleChannelSendGraph, ioSendNode, 1, convertToULAWNode, 0);
if (status != noErr)
{
    NSLog(@"Unable to set ULAW node input.");
    return;
}

status = AUGraphConnectNodeInput(singleChannelSendGraph, convertToULAWNode, 0, convertToLPCMNode, 0);
if (status != noErr)
{
    NSLog(@"Unable to set LPCM node input.");
    return;
}

status = AUGraphConnectNodeInput(singleChannelSendGraph, convertToLPCMNode, 0, ioSendNode, 0);
if (status != noErr)
{
    NSLog(@"Unable to set IO node input.");
    return;
}

status = AudioUnitAddRenderNotify(convertToULAWUnit, &outputULAWCallback, (__bridge void*)self);
if (status != noErr)
{
    NSLog(@"Unable to add ULAW render notify.");
    return;
}

status = AUGraphInitialize(singleChannelSendGraph);
if (status != noErr)
{
    NSLog(@"Unable to initialize send graph.");
    return;
}

CAShow (singleChannelSendGraph);

}

And the graph nodes are initialized as:

Member Nodes:
node 1: 'auou' 'vpio' 'appl', instance 0x7fd5faf8fac0 O I
node 2: 'aufc' 'conv' 'appl', instance 0x7fd5fad05420 O I
node 3: 'aufc' 'conv' 'appl', instance 0x7fd5fad05810 O I
Connections:
node   1 bus   1 => node   2 bus   0  [ 1 ch,  44100 Hz, 'lpcm' (0x0000000C) 16-bit little-endian signed integer]
node   2 bus   0 => node   3 bus   0  [ 1 ch,   8000 Hz, 'ulaw' (0x0000000C) 8 bits/channel, 1 bytes/packet, 1 frames/packet, 1 bytes/frame]
node   3 bus   0 => node   1 bus   0  [ 1 ch,  44100 Hz, 'lpcm' (0x0000000C) 16-bit little-endian signed integer]

And the render notify callback:

static OSStatus outputULAWCallback(void *inRefCon,
                            AudioUnitRenderActionFlags *ioActionFlags,
                            const AudioTimeStamp *inTimeStamp,
                            UInt32 inBusNumber,
                            UInt32 inNumberFrames,
                            AudioBufferList *ioData)
{
    AudioManager *audioManager = (__bridge AudioManager*)inRefCon;
    if ((*ioActionFlags) & kAudioUnitRenderAction_PostRender)
    {
        if (!audioManager.mute && ioData->mBuffers[0].mData != NULL)
        {
            TPCircularBufferProduceBytes(audioManager.activeChannel == 0 ? audioManager.channel1StreamOutBufferPtr : audioManager.channel2StreamOutBufferPtr,
                                     ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize);

            // do not want to playback our audio into local speaker
            SilenceData(ioData);
        }
    }

    return noErr;
}

Note: if I send the microphone input to the output directly (skipping the converter nodes), I do hear output, so I know the AUGraph is working.

I have a receive AUGraph setup to receive ULaw from a stream and run through a converter to play through the speakers and that is working without an issue.

Just can't figure out why the converter is failing and returning no data.

Has anyone had any experience with this type of issue?

ios
core-audio
asked on Stack Overflow Sep 6, 2016 by DRourke

1 Answer

1

UPDATE
So you're calling AUGraphStart elsewhere, but the ulaw converter is refusing to do general rate conversion for you :( You could add another rate converter to the graph or simply get the vpio unit to do it for you. Changing this code

ioASBDin.mSampleRate = currentSampleRate;    // change me to 8000Hz
ioASBDout.mSampleRate = currentSampleRate;   // delete me, I'm ignored

status = AudioUnitSetProperty(ioSendUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &ioASBDin, sizeof(ioASBDin));

into

ioASBDin.mSampleRate = streamAudioFormat.mSampleRate;    // a.k.a 8000Hz 

status = AudioUnitSetProperty(ioSendUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &ioASBDin, sizeof(ioASBDin));

will make the whole graph do 8kHz and give you non-null ioData buffers:

AudioUnitGraph 0xCA51000:
  Member Nodes:
    node 1: 'auou' 'vpio' 'appl', instance 0x7b5bb320 O I
    node 2: 'aufc' 'conv' 'appl', instance 0x7c878d50 O I
    node 3: 'aufc' 'conv' 'appl', instance 0x7c875eb0 O I
Connections:
    node   1 bus   1 => node   2 bus   0  [ 1 ch,   8000 Hz, 'lpcm' (0x0000000C) 16-bit little-endian signed integer]
    node   2 bus   0 => node   3 bus   0  [ 1 ch,   8000 Hz, 'ulaw' (0x0000000C) 8 bits/channel, 1 bytes/packet, 1 frames/packet, 1 bytes/frame]
    node   3 bus   0 => node   1 bus   0  [ 1 ch,   8000 Hz, 'lpcm' (0x0000000C) 16-bit little-endian signed integer]
 CurrentState:
    mLastUpdateError=0, eventsToProcess=F, isInitialized=T, isRunning=T (1)

old answer

You need to

  1. AUGraphStart your graph
  2. Change your ulaw mSampleRate to 11025, 22050 or 44100

then you will see non-null ioData in the kAudioUnitRenderAction_PostRender phase.

Converting to 8kHz or even 16kHz ulaw seems like something an audio converter should be able to do. I have no idea why it doesn't work, but when you do set the sample rate to anything other than the values in point 2., the ulaw converter reports kAUGraphErr_CannotDoInCurrentContext (-10863) errors, which makes no sense to me.

answered on Stack Overflow Sep 8, 2016 by Rhythmic Fistman • edited Sep 9, 2016 by Rhythmic Fistman

User contributions licensed under CC BY-SA 3.0