3
votes

I wanted to record (save as a file in document directory) audio that is being played in the application. Basically can I hook to Audio output buffers and can read/save those buffers in the file?

I read some blogs and searched on a google. Most of them were pointing to the AudioUnit.

I have tried using AudioUnit - RemoteIO but I could not achieve what I wanted to, probably I messed something.

Following is the code that I have written for initializing the AudioUnit

OSStatus status;

// Describe audio component
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;

// Get component
AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc);

// Get audio units
status = AudioComponentInstanceNew(inputComponent, &mAudioUnit);

// Enable IO for recording
UInt32 flag = 1

// Enable IO for playback
status = AudioUnitSetProperty(mAudioUnit, 
                              kAudioOutputUnitProperty_EnableIO, 
                              kAudioUnitScope_Output, 
                              kOutputBus,
                              &flag, 
                              sizeof(flag));

// Describe format
AudioStreamBasicDescription audioFormat={0};
audioFormat.mSampleRate         = kSampleRate;
audioFormat.mFormatID           = kAudioFormatLinearPCM;
audioFormat.mFormatFlags        = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
audioFormat.mFramesPerPacket    = 1;
audioFormat.mChannelsPerFrame   = 1;
audioFormat.mBitsPerChannel     = 16;
audioFormat.mBytesPerPacket     = 2;
audioFormat.mBytesPerFrame      = 2;

// Apply format
status = AudioUnitSetProperty(mAudioUnit, 
                              kAudioUnitProperty_StreamFormat, 
                              kAudioUnitScope_Output, 
                              kInputBus, 
                              &audioFormat, 
                              sizeof(audioFormat));


// Set input callback
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = playbackCallback;
callbackStruct.inputProcRefCon = self;

status = AudioUnitSetProperty(mAudioUnit, 
                              kAudioUnitProperty_SetRenderCallback, 
                              kAudioUnitScope_Input, 
                              kOutputBus,
                              &callbackStruct, 
                              sizeof(callbackStruct));
AudioUnitInitialize(mAudioUnit);
AudioOutputUnitStart(mAudioUnit);

And in the playbackCallback :

static OSStatus playbackCallback(void *inRefCon, 
                             AudioUnitRenderActionFlags *ioActionFlags, 
                             const AudioTimeStamp *inTimeStamp, 
                             UInt32 inBusNumber, 
                             UInt32 inNumberFrames, 
                             AudioBufferList *ioData) {    
double timeInSeconds = inTimeStamp->mSampleTime / kSampleRate;
printf("\nPLAYBACK %fs inBusNumber: %lu inNumberFrames: %lu ", timeInSeconds, inBusNumber, inNumberFrames);

AudioBufferList bufferList;

SInt16 samples[inNumberFrames]; // A large enough size to not have to worry about buffer overrun
memset (&samples, 0, sizeof (samples));

bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0].mData = samples;
bufferList.mBuffers[0].mNumberChannels = 1;
bufferList.mBuffers[0].mDataByteSize = inNumberFrames*sizeof(SInt16);

ViewController* THIS = THIS = (__bridge ViewController *)inRefCon;

OSStatus status;
status = AudioUnitRender(THIS->mAudioUnit,     
                         ioActionFlags, 
                         inTimeStamp, 
                         kOutputBus, 
                         inNumberFrames, 
                         &bufferList);

if (noErr != status) {

    printf("AudioUnitRender error: %ld", status); 
    return noErr;
}

// Now, we have the samples we just read sitting in buffers in bufferList
ExtAudioFileWriteAsync(THIS->mAudioFileRef, inNumberFrames, &bufferList);

return noErr;
}

Above mAudioFileRef is reference of the mp3 file that I have created in the viewDidLoad.

When I run above program, I am getting AudioUnitRender Error: -50. and mp3 file is not getting populated with the data.

Probably I have made some mistake in setting up the audio unit or in reading the buffer from ioData.

Any kind of help is much appreciated.

1
Have you tried using novocaine? github.com/alexbw/novocaineNik Reiman

1 Answers

0
votes

Ok. Many things could be going wrong here.

  • I dont know how are you initialising your ExtAudioFileRef.
  • What client format are you setting to it.
  • What audio Format are you setting to it (yeah right mp3).
  • What file format are you using.
  • They all have to work together seamlessly.

Nevertheless, Have a look at this entry. It is a perfectly working sample code:

Recording to AAC from RemoteIO: data is getting written but file unplayable

And it kinda does what you need. Record compressed audio on the fly.

Have you initialise your ExtAudioFileWriteAsync like this(prior to the callback)?

ExtAudioFileWriteAsync(extAudioFileRefVar, 0, NULL);

It is really hard to tell what, out of all the possible things, could be going wrong, without more code.