AUGraph FormatConverter(AUConverter)渲染通知包含NULL ioData缓冲区

时间:2016-09-06 14:41:12

标签: ios core-audio

我正在研究iOS项目,需要从麦克风捕获输入并将其转换为ULaw(发送数据流)。我正在使用带有转换器节点的AUGraph来完成此任务。图表已成功创建并初始化,但在我的渲染通知回调中,ioData缓冲区始终包含NULL,即使inNumberFrame包含的值为93.我认为它可能会因格式转换器缓冲区的大小不正确而有所作为,但我可以想象发生了什么事。

以下是代码:

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);

}

图形节点初始化为:

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]

渲染通知回调:

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;
}

注意:如果我直接将麦克风输入发送到输出(跳过转换器节点),我会听到输出,所以我知道AUGraph正在工作。

我有一个接收AUGraph设置,从流中接收ULaw并通过转换器播放通过扬声器,这是没有问题的工作。

无法弄清楚为什么转换器出现故障并且没有返回任何数据。

有没有人有过这类问题的经验?

1 个答案:

答案 0 :(得分:1)

<强>更新
所以你在其他地方调用AUGraphStart,但是ulaw转换器拒绝为你进行一般的速率转换:(你可以在图表中添加另一个速率转换器,或者只是让vpio单元为你做这个。代码

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));

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

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

将使整个图形为8kHz,并为您提供非空的ioData缓冲区:

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)

旧回答

你需要

  1. AUGraphStart您的图表
  2. 将您的ulaw mSampleRate更改为11025,22050或44100
  3. 然后您将在kAudioUnitRenderAction_PostRender阶段看到非空的ioData。

    转换为8kHz甚至16kHz ulaw似乎是音频转换器应该能够做的事情。我不知道它为什么不起作用,但是当您将采样率设置为除第2点中的值以外的任何值时,ulaw转换器会报告kAUGraphErr_CannotDoInCurrentContext( - 10863)个错误,这对于我。