Yardu
Yardu

Reputation: 41

AudioConverterRef sample rate conversion (iOS)

I write a voip app that uses "novocaine" library for recording and playback of sound. I set sample rate as 8kHz. This sample rate is set in novocaine in AudioStreamBasicDescription of audio unit and as audio session property kAudioSessionProperty_PreferredHardwareSampleRate. I understand that setting preferred hardware sample rate has no guarantee that actual hardware sample rate will be changed but it worked for all devices except iPhone6s and iPhone6s+ (when route is changed to speaker). With iPhone6s(+) and speaker route I receive 48kHz sound from microphone. So I need to somehow convert this 48 kHz sound to 8kHz. In documentation I found that AudioConverterRef can be used in this case but I have troubles with using it.

I use AudioConverterFillComplexBuffer for sample rate conversion but it always returns -50 OSStatus (one or more parameters passed to the function were not valid). This is how I use audio converter:

// Setup AudioStreamBasicDescription for input
inputFormat.mSampleRate = 48000.0;
inputFormat.mFormatID = kAudioFormatLinearPCM;
inputFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
inputFormat.mChannelsPerFrame = 1;
inputFormat.mBitsPerChannel = 8 * sizeof(float);
inputFormat.mFramesPerPacket = 1;
inputFormat.mBytesPerFrame = sizeof(float) * inputFormat.mChannelsPerFrame;
inputFormat.mBytesPerPacket = inputFormat.mBytesPerFrame * inputFormat.mFramesPerPacket;

// Setup AudioStreamBasicDescription for output
outputFormat.mSampleRate = 8000.0;
outputFormat.mFormatID = kAudioFormatLinearPCM;
outputFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
outputFormat.mChannelsPerFrame = 1;
outputFormat.mBitsPerChannel = 8 * sizeof(float);
outputFormat.mFramesPerPacket = 1;
outputFormat.mBytesPerFrame = sizeof(float) * outputFormat.mChannelsPerFrame;
outputFormat.mBytesPerPacket = outputFormat.mBytesPerFrame * outputFormat.mFramesPerPacket;


// Create new instance of audio converter
AudioConverterNew(&inputFormat, &outputFormat, &converter);

// Set conversion quality
UInt32 tmp = kAudioConverterQuality_Medium;
AudioConverterSetProperty( converter, kAudioConverterCodecQuality,
                          sizeof( tmp ), &tmp );
AudioConverterSetProperty( converter, kAudioConverterSampleRateConverterQuality, sizeof( tmp ), &tmp );

// Get the size of the IO buffer(s)
UInt32 bufferSizeFrames = 0;
size = sizeof(UInt32);
AudioUnitGetProperty(self.inputUnit,
                                 kAudioDevicePropertyBufferFrameSize,
                                 kAudioUnitScope_Global,
                                 0,
                                 &bufferSizeFrames,
                                 &size);
UInt32 bufferSizeBytes = bufferSizeFrames * sizeof(Float32);

// Allocate an AudioBufferList plus enough space for array of AudioBuffers
UInt32 propsize = offsetof(AudioBufferList, mBuffers[0]) + (sizeof(AudioBuffer) * outputFormat.mChannelsPerFrame);

// Malloc buffer lists
convertedInputBuffer = (AudioBufferList *)malloc(propsize);
convertedInputBuffer->mNumberBuffers = 1;

// Pre-malloc buffers for AudioBufferLists
convertedInputBuffer->mBuffers[0].mNumberChannels = outputFormat.mChannelsPerFrame;
convertedInputBuffer->mBuffers[0].mDataByteSize = bufferSizeBytes;
convertedInputBuffer->mBuffers[0].mData = malloc(bufferSizeBytes);
memset(convertedInputBuffer->mBuffers[0].mData, 0, bufferSizeBytes);

// Setup callback for converter
static OSStatus inputProcPtr(AudioConverterRef               inAudioConverter,
                                 UInt32*                         ioNumberDataPackets,
                                 AudioBufferList*                ioData,
                                 AudioStreamPacketDescription* __nullable* __nullable  outDataPacketDescription,
                                 void* __nullable                inUserData)
{
    // Read data from buffer
}

// Perform actual sample rate conversion
AudioConverterFillComplexBuffer(converter, inputProcPtr, NULL, &numberOfFrames, convertedInputBuffer,  NULL)

inputProcPtr callback is never called. I tried to set different number of frames but still receive OSStatus -50.

1) Is using AudioConverterRef is correct way to make sample rate conversion or it could be done in different way?

2) What is wrong with my conversion implementation?

Thank you all in advance

Upvotes: 4

Views: 1736

Answers (1)

Gordon Childs
Gordon Childs

Reputation: 36169

One problem is this:

AudioUnitGetProperty(self.inputUnit,
                             kAudioDevicePropertyBufferFrameSize,
                             kAudioUnitScope_Global,
                             0,
                             &bufferSizeFrames,
                             &size);

kAudioDevicePropertyBufferFrameSize is an OSX property, and doesn't exist on iOS. How is this code even compiling?

If you've somehow made it compile, check the return code from this function! I've got a feeling that it's failing, and bufferSizeFrames is zero. That would make AudioConverterFillComplexBuffer return -50 (kAudio_ParamError).

So on iOS, either pick a bufferSizeFrames yourself or base it on AVAudioSession's IOBufferDuration if you must.

Another problem: check your return codes. All of them!

e.g.

UInt32 tmp = kAudioConverterQuality_Medium;
AudioConverterSetProperty( converter, kAudioConverterCodecQuality,
                      sizeof( tmp ), &tmp );

I'm pretty sure there's no codec to speak of in LPCM->LPCM conversions, and that kAudioConverterQuality_Medium is not the right value to use with kAudioConverterCodecQuality in any case. I don't see how this call can succeed.

Upvotes: 1

Related Questions