Search code examples
iosobjective-caudioavfoundationcore-audio

Click During Inter-App Audio Recording


I have been attempting to recording my input during an inter-app audio session on iOS 9. The speaker output sounds fine but the recorded file has a rhythmic clicking sound. The waveform looks like below...

enter image description here

I have tweaked every setting and parameter I can think of and nothing seems to work.

Here are the format settings (stream settings are identical)...

    AudioStreamBasicDescription fileFormat;
fileFormat.mSampleRate          = kSessionSampleRate;
fileFormat.mFormatID            = kAudioFormatLinearPCM;
fileFormat.mFormatFlags         = kAudioFormatFlagsNativeFloatPacked;
fileFormat.mFramesPerPacket     = 1;
fileFormat.mChannelsPerFrame    = 1;
fileFormat.mBitsPerChannel      = 32;       //tone is correct but there is still pops
fileFormat.mBytesPerPacket      = sizeof(Float32);
fileFormat.mBytesPerFrame       = sizeof(Float32);

Here are the stream settings...

        //connect instrument to output
AudioComponentDescription componentDescription = unit.componentDescription;
AudioComponent inputComponent = AudioComponentFindNext(NULL, &componentDescription);
OSStatus status = AudioComponentInstanceNew(inputComponent, &_instrumentUnit);
NSLog(@"%d",status);
AudioUnitElement instrumentOutputBus = 0;
AudioUnitElement ioUnitInputElement = 0;

    //connect instrument unit to remoteIO output's input bus
AudioUnitConnection connection;
connection.sourceAudioUnit = _instrumentUnit;
connection.sourceOutputNumber = instrumentOutputBus;
connection.destInputNumber = ioUnitInputElement;
status = AudioUnitSetProperty(_ioUnit,
                              kAudioUnitProperty_MakeConnection,
                              kAudioUnitScope_Output,
                              ioUnitInputElement,
                              &connection,
                              sizeof(connection));
NSLog(@"%d",status);
UInt32 maxFrames = 1024; //I tried setting this to 4096 but it did not help
status = AudioUnitSetProperty(_instrumentUnit,
                                kAudioUnitProperty_MaximumFramesPerSlice,
                                kAudioUnitScope_Output,
                                0,
                                &maxFrames,
                                sizeof(maxFrames));
NSLog(@"%d",status);



_connectedInstrument = YES;
_instrumentIconImageView.image = unit.icon;
NSLog(@"Remote Instrument connected");
status = AudioUnitInitialize(_ioUnit);
NSLog(@"%d",status);
status = AudioOutputUnitStart(_ioUnit);
NSLog(@"%d",status);
status = AudioUnitInitialize(_instrumentUnit);
NSLog(@"%d",status);
[self setupFile];

Here is my callback...

static OSStatus recordingCallback(void                              *inRefCon,
                                 AudioUnitRenderActionFlags         *ioActionFlags,
                                 const AudioTimeStamp               *inTimeStamp,
                                 UInt32                             inBusNumber,
                                 UInt32                             inNumberFrames,
                                 AudioBufferList                    *ioData)
{
    ViewController* This = This = (__bridge ViewController *)inRefCon;
    if (inBusNumber == 0 && !(*ioActionFlags & kAudioUnitRenderAction_PostRenderError))
    {
            ExtAudioFileWriteAsync(This->fileRef, inNumberFrames, ioData);
    }

    return noErr;
}

Full view controller code here

Thanks for your help.


Solution

  • You are writing to file pre and post render. In your render callback, change your if statement to only write on post render.

    if (inBusNumber == 0 && *ioActionFlags == kAudioUnitRenderAction_PostRender){
        ExtAudioFileWriteAsync(This->fileRef, inNumberFrames, ioData);
    } 
    

    ExtAudioFileWriteAsync does some internal copying and buffering so it's fine to use in the render callback as long as you prime it before the first write.