mahboudz
mahboudz

Reputation: 39376

How to install a tap on the default inputNode of AudioEngine and write to file

I'm trying to do something really simple, but I've only made progress in fits and starts. I only want to get audio data from a microphone into a file, for now. I'll do more processing of the data in the block once I get over this hurdle. I'm getting tired of some of ideosyncracies I am finding with AVAudioEngine, and i am wondering if I should go back to AudioUnits which is probably a bit better debugged (although more complex).

First off, it seems that the AudioEngine can't be started until the inputNode is instantiated, and then you can't use its outputFormatForBus to get its format (the samples rate is zero); you have to use inputFormatForBus.

Now that I have it working and I am getting calls on the block with what seems to be valid data, I can't write to the file without it generating an exception, with error:

'error writing buffer data to file, The operation couldn’t be completed. (com.apple.coreaudio.avfaudio error 1768846202.)'

('insz') That seems to indicate that there is some sort of error with either the format I am providing or the format that the block is getting.

Any thoughts?

_engine = [[AVAudioEngine alloc] init];

_outputFileURL = [NSURL URLWithString:[NSTemporaryDirectory() stringByAppendingString:@"tempOutput.caf"]];

AVAudioInputNode *inputNode = [_engine inputNode];
AVAudioFormat *format = [inputNode inputFormatForBus:1];
NSMutableDictionary *recordSettings = format.settings.mutableCopy;
[recordSettings addEntriesFromDictionary:@{
                              AVFormatIDKey : @(kAudioFormatMPEG4AAC),
                              AVEncoderAudioQualityKey : @(AVAudioQualityMedium)
                              }];

AVAudioFile *outputFile = [[AVAudioFile alloc] initForWriting:_outputFileURL settings:recordSettings error:&error];

[inputNode installTapOnBus:1 bufferSize:4096 format:format block:^(AVAudioPCMBuffer *buffer, AVAudioTime *when) {
    NSError *error;

    // as AVAudioPCMBuffer's are delivered this will write sequentially. The buffer's frameLength signifies how much of the buffer is to be written
    // IMPORTANT: The buffer format MUST match the file's processing format which is why outputFormatForBus: was used when creating the AVAudioFile object above
    NSAssert([outputFile writeFromBuffer:buffer error:&error], @"error writing buffer data to file, %@", [error localizedDescription]);
}];
if (!_engine.isRunning) [self startEngine];

Thank you.

Updated code based on @matt's comment

Upvotes: 3

Views: 2720

Answers (1)

mahboudz
mahboudz

Reputation: 39376

Here is how I got around this, in case anyone else runs across the same issue.

@matt commented correctly that I should create independent settings, and not use the format that was associated with the inputNode.

commonFormat = [[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32 sampleRate:44100 channels:2 interleaved:NO];

engine = [[AVAudioEngine alloc] init];
AVAudioInputNode *inputNode = engine.inputNode;

NSError *error;
AVAudioFile *outputFile = [[AVAudioFile alloc] initForWriting:_outputFileURL settings:commonFormat.settings error:&error];

[inputNode installTapOnBus:0 bufferSize:4096 format:commonFormat block:^(AVAudioPCMBuffer *buffer, AVAudioTime *when) {
    NSError *error;

    NSAssert([outputFile writeFromBuffer:buffer error:&error], @"error writing buffer data to file, %@", [error localizedDescription]);
}];

Upvotes: 2

Related Questions