Overhaul CoreAudio output code: Downmix properly from 8 channels, redo converter process, and no longer crash when forced to output to a low quality device. Switchover to stereo from mono isn't perfect, however.

CQTexperiment
Christopher Snowhill 2021-12-25 15:02:13 -08:00
parent d4614a2f03
commit 9c9d71cd9c
3 changed files with 93 additions and 35 deletions

View File

@ -20,14 +20,19 @@
AudioConverterRef converter;
AudioConverterRef converterFloat;
void *callbackBuffer;
size_t callbackBufferSize;
float sampleRatio;
float volumeScale;
void *floatBuffer;
size_t floatBufferSize;
int floatSize, floatOffset;
AudioStreamBasicDescription inputFormat;
AudioStreamBasicDescription floatFormat;
AudioStreamBasicDescription dmFloatFormat; // downmixed float format
AudioStreamBasicDescription outputFormat;
}

View File

@ -40,7 +40,9 @@ void PrintStreamDesc (AudioStreamBasicDescription *inDesc)
converterFloat = NULL;
converter = NULL;
floatBuffer = NULL;
floatBufferSize = 0;
callbackBuffer = NULL;
callbackBufferSize = 0;
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.volumeScaling" options:0 context:nil];
}
@ -81,7 +83,7 @@ static const float STEREO_DOWNMIX[8-2][8][2]={
static void downmix_to_stereo(float * buffer, int channels, int count)
{
if (channels >= 3 && channels < 8)
if (channels >= 3 && channels <= 8)
for (int i = 0; i < count; ++i)
{
float left = 0, right = 0;
@ -90,8 +92,8 @@ static void downmix_to_stereo(float * buffer, int channels, int count)
left += buffer[i * channels + j] * STEREO_DOWNMIX[channels - 3][j][0];
right += buffer[i * channels + j] * STEREO_DOWNMIX[channels - 3][j][1];
}
buffer[i * channels + 0] = left;
buffer[i * channels + 1] = right;
buffer[i * 2 + 0] = left;
buffer[i * 2 + 1] = right;
}
}
@ -124,7 +126,8 @@ static OSStatus ACInputProc(AudioConverterRef inAudioConverter,
amountToWrite = (*ioNumberDataPackets)*(converter->inputFormat.mBytesPerPacket);
converter->callbackBuffer = realloc(converter->callbackBuffer, amountToWrite);
if (!converter->callbackBuffer || converter->callbackBufferSize < amountToWrite)
converter->callbackBuffer = realloc(converter->callbackBuffer, converter->callbackBufferSize = amountToWrite + 1024);
amountRead = [converter readData:converter->callbackBuffer amount:amountToWrite];
if (amountRead == 0 && [converter endOfStream] == NO)
@ -161,15 +164,21 @@ static OSStatus ACFloatProc(AudioConverterRef inAudioConverter,
return noErr;
}
amountToWrite = (*ioNumberDataPackets)*(converter->floatFormat.mBytesPerPacket);
amountToWrite = (*ioNumberDataPackets) * (converter->dmFloatFormat.mBytesPerPacket);
if ( amountToWrite + converter->floatOffset > converter->floatSize )
{
amountToWrite = converter->floatSize - converter->floatOffset;
*ioNumberDataPackets = amountToWrite / (converter->dmFloatFormat.mBytesPerPacket);
}
ioData->mBuffers[0].mData = converter->floatBuffer + converter->floatOffset;
ioData->mBuffers[0].mDataByteSize = amountToWrite;
ioData->mBuffers[0].mNumberChannels = (converter->floatFormat.mChannelsPerFrame);
ioData->mBuffers[0].mNumberChannels = (converter->dmFloatFormat.mChannelsPerFrame);
ioData->mNumberBuffers = 1;
if (amountToWrite == 0)
return 100;
converter->floatOffset += amountToWrite;
@ -190,28 +199,41 @@ static OSStatus ACFloatProc(AudioConverterRef inAudioConverter,
- (int)convert:(void *)dest amount:(int)amount
{
AudioBufferList ioData;
UInt32 ioNumberFrames;
UInt32 ioNumberPackets;
OSStatus err;
int amountReadFromFC;
int amountRead = 0;
tryagain2:
amountReadFromFC = 0;
if (floatOffset == floatSize) {
ioNumberFrames = amount / outputFormat.mBytesPerFrame;
floatBuffer = realloc( floatBuffer, ioNumberFrames * floatFormat.mBytesPerFrame );
UInt32 ioWantedNumberPackets;
ioNumberPackets = amount / outputFormat.mBytesPerPacket;
ioNumberPackets = (UInt32)((float)ioNumberPackets * sampleRatio);
ioNumberPackets = (ioNumberPackets + 255) & ~255;
ioWantedNumberPackets = ioNumberPackets;
size_t newSize = ioNumberPackets * floatFormat.mBytesPerPacket;
if (!floatBuffer || floatBufferSize < newSize)
floatBuffer = realloc( floatBuffer, floatBufferSize = newSize + 1024 );
ioData.mBuffers[0].mData = floatBuffer;
ioData.mBuffers[0].mDataByteSize = ioNumberFrames * floatFormat.mBytesPerFrame;
ioData.mBuffers[0].mDataByteSize = ioNumberPackets * floatFormat.mBytesPerPacket;
ioData.mBuffers[0].mNumberChannels = floatFormat.mChannelsPerFrame;
ioData.mNumberBuffers = 1;
tryagain:
err = AudioConverterFillComplexBuffer(converterFloat, ACInputProc, (__bridge void * _Nullable)(self), &ioNumberFrames, &ioData, NULL);
amountRead += ioData.mBuffers[0].mDataByteSize;
err = AudioConverterFillComplexBuffer(converterFloat, ACInputProc, (__bridge void * _Nullable)(self), &ioNumberPackets, &ioData, NULL);
amountReadFromFC += ioNumberPackets * floatFormat.mBytesPerPacket;
if (err == 100)
{
DLog(@"INSIZE: %i", amountRead);
ioData.mBuffers[0].mData = floatBuffer + amountRead;
ioNumberFrames = ( amount / outputFormat.mBytesPerFrame ) - ( amountRead / floatFormat.mBytesPerFrame );
ioData.mBuffers[0].mDataByteSize = ioNumberFrames * floatFormat.mBytesPerFrame;
ioData.mBuffers[0].mData = (void *)(((uint8_t*)floatBuffer) + amountReadFromFC);
ioNumberPackets = ioWantedNumberPackets - ioNumberPackets;
ioWantedNumberPackets = ioNumberPackets;
ioData.mBuffers[0].mDataByteSize = ioNumberPackets * floatFormat.mBytesPerPacket;
usleep(10000);
goto tryagain;
}
@ -222,31 +244,28 @@ static OSStatus ACFloatProc(AudioConverterRef inAudioConverter,
}
if ( inputFormat.mChannelsPerFrame > 2 && outputFormat.mChannelsPerFrame == 2 )
downmix_to_stereo( (float*) floatBuffer, inputFormat.mChannelsPerFrame, amountRead / floatFormat.mBytesPerFrame );
{
int samples = amountReadFromFC / floatFormat.mBytesPerFrame;
downmix_to_stereo( (float*) floatBuffer, inputFormat.mChannelsPerFrame, samples );
amountReadFromFC = samples * sizeof(float) * 2;
}
scale_by_volume( (float*) floatBuffer, amountRead / sizeof(float), volumeScale);
scale_by_volume( (float*) floatBuffer, amountReadFromFC / sizeof(float), volumeScale);
floatSize = amountRead;
floatSize = amountReadFromFC;
floatOffset = 0;
}
ioNumberFrames = amount / outputFormat.mBytesPerFrame;
ioData.mBuffers[0].mData = dest;
ioData.mBuffers[0].mDataByteSize = amount;
ioNumberPackets = amount / outputFormat.mBytesPerPacket;
ioData.mBuffers[0].mData = dest + amountRead;
ioData.mBuffers[0].mDataByteSize = amount - amountRead;
ioData.mBuffers[0].mNumberChannels = outputFormat.mChannelsPerFrame;
ioData.mNumberBuffers = 1;
amountRead = 0;
tryagain2:
err = AudioConverterFillComplexBuffer(converter, ACFloatProc, (__bridge void *)(self), &ioNumberFrames, &ioData, NULL);
amountRead += ioData.mBuffers[0].mDataByteSize;
err = AudioConverterFillComplexBuffer(converter, ACFloatProc, (__bridge void *)(self), &ioNumberPackets, &ioData, NULL);
amountRead += ioNumberPackets * outputFormat.mBytesPerPacket;
if (err == 100)
{
DLog(@"INSIZE: %i", amountRead);
ioData.mBuffers[0].mData = dest + amountRead;
ioNumberFrames = ( amount - amountRead ) / outputFormat.mBytesPerFrame;
ioData.mBuffers[0].mDataByteSize = ioNumberFrames * outputFormat.mBytesPerFrame;
goto tryagain2;
}
else if (err != noErr && err != kAudioConverterErr_InvalidInputSize)
@ -342,6 +361,11 @@ static float db_to_scale(float db)
return NO;
}
dmFloatFormat = floatFormat;
floatFormat.mChannelsPerFrame = outputFormat.mChannelsPerFrame;
floatFormat.mBytesPerFrame = (32/8)*floatFormat.mChannelsPerFrame;
floatFormat.mBytesPerPacket = floatFormat.mBytesPerFrame * floatFormat.mFramesPerPacket;
stat = AudioConverterNew ( &floatFormat, &outputFormat, &converter );
if (stat != noErr)
{
@ -349,6 +373,8 @@ static float db_to_scale(float db)
return NO;
}
#if 0
// These mappings don't do what I want, so avoid them.
if (inputFormat.mChannelsPerFrame > 2 && outputFormat.mChannelsPerFrame == 2)
{
SInt32 channelMap[2] = { 0, 1 };
@ -360,11 +386,26 @@ static float db_to_scale(float db)
return NO;
}
}
else if (inputFormat.mChannelsPerFrame == 1)
else if (inputFormat.mChannelsPerFrame > 1 && outputFormat.mChannelsPerFrame == 1)
{
SInt32 channelMap[1] = { 0 };
stat = AudioConverterSetProperty(converter,kAudioConverterChannelMap,(int)sizeof(channelMap),channelMap);
if (stat != noErr)
{
ALog(@"Error mapping channels %i", stat);
return NO;
}
}
else
#endif
if (inputFormat.mChannelsPerFrame == 1 && outputFormat.mChannelsPerFrame > 1)
{
SInt32 channelMap[2] = { 0, 0 };
SInt32 channelMap[outputFormat.mChannelsPerFrame];
memset(channelMap, 0, sizeof(channelMap));
stat = AudioConverterSetProperty(converter,kAudioConverterChannelMap,sizeof(channelMap),channelMap);
stat = AudioConverterSetProperty(converter,kAudioConverterChannelMap,(int)sizeof(channelMap),channelMap);
if (stat != noErr)
{
ALog(@"Error mapping channels %i", stat);
@ -377,6 +418,8 @@ static float db_to_scale(float db)
[self refreshVolumeScaling];
sampleRatio = (float)inputFormat.mSampleRate / (float)outputFormat.mSampleRate;
return YES;
}
@ -427,10 +470,12 @@ static float db_to_scale(float db)
{
free(floatBuffer);
floatBuffer = NULL;
floatBufferSize = 0;
}
if (callbackBuffer) {
free(callbackBuffer);
callbackBuffer = NULL;
callbackBufferSize = 0;
}
floatOffset = 0;
floatSize = 0;

View File

@ -361,6 +361,14 @@ static OSStatus Sound_Renderer(void *inRefCon, AudioUnitRenderActionFlags *ioAc
///Seems some 3rd party devices return incorrect stuff...or I just don't like noninterleaved data.
deviceFormat.mFormatFlags &= ~kLinearPCMFormatFlagIsNonInterleaved;
// Bluetooth devices in communications mode tend to have reduced settings,
// so let's work around that.
// For some reason, mono output just doesn't work, so bleh.
if (deviceFormat.mChannelsPerFrame < 2)
deviceFormat.mChannelsPerFrame = 2;
// And sample rate will be cruddy for the duration of playback, so fix it.
if (deviceFormat.mSampleRate < 32000)
deviceFormat.mSampleRate = 48000;
// deviceFormat.mFormatFlags &= ~kLinearPCMFormatFlagIsFloat;
// deviceFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
deviceFormat.mBytesPerFrame = deviceFormat.mChannelsPerFrame*(deviceFormat.mBitsPerChannel/8);