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.
parent
d4614a2f03
commit
9c9d71cd9c
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue