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.
This commit is contained in:
parent
d4614a2f03
commit
9c9d71cd9c
3 changed files with 93 additions and 35 deletions
|
@ -20,14 +20,19 @@
|
||||||
AudioConverterRef converter;
|
AudioConverterRef converter;
|
||||||
AudioConverterRef converterFloat;
|
AudioConverterRef converterFloat;
|
||||||
void *callbackBuffer;
|
void *callbackBuffer;
|
||||||
|
size_t callbackBufferSize;
|
||||||
|
|
||||||
|
float sampleRatio;
|
||||||
|
|
||||||
float volumeScale;
|
float volumeScale;
|
||||||
|
|
||||||
void *floatBuffer;
|
void *floatBuffer;
|
||||||
|
size_t floatBufferSize;
|
||||||
int floatSize, floatOffset;
|
int floatSize, floatOffset;
|
||||||
|
|
||||||
AudioStreamBasicDescription inputFormat;
|
AudioStreamBasicDescription inputFormat;
|
||||||
AudioStreamBasicDescription floatFormat;
|
AudioStreamBasicDescription floatFormat;
|
||||||
|
AudioStreamBasicDescription dmFloatFormat; // downmixed float format
|
||||||
AudioStreamBasicDescription outputFormat;
|
AudioStreamBasicDescription outputFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,9 @@ void PrintStreamDesc (AudioStreamBasicDescription *inDesc)
|
||||||
converterFloat = NULL;
|
converterFloat = NULL;
|
||||||
converter = NULL;
|
converter = NULL;
|
||||||
floatBuffer = NULL;
|
floatBuffer = NULL;
|
||||||
|
floatBufferSize = 0;
|
||||||
callbackBuffer = NULL;
|
callbackBuffer = NULL;
|
||||||
|
callbackBufferSize = 0;
|
||||||
|
|
||||||
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.volumeScaling" options:0 context:nil];
|
[[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)
|
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)
|
for (int i = 0; i < count; ++i)
|
||||||
{
|
{
|
||||||
float left = 0, right = 0;
|
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];
|
left += buffer[i * channels + j] * STEREO_DOWNMIX[channels - 3][j][0];
|
||||||
right += buffer[i * channels + j] * STEREO_DOWNMIX[channels - 3][j][1];
|
right += buffer[i * channels + j] * STEREO_DOWNMIX[channels - 3][j][1];
|
||||||
}
|
}
|
||||||
buffer[i * channels + 0] = left;
|
buffer[i * 2 + 0] = left;
|
||||||
buffer[i * channels + 1] = right;
|
buffer[i * 2 + 1] = right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +126,8 @@ static OSStatus ACInputProc(AudioConverterRef inAudioConverter,
|
||||||
|
|
||||||
amountToWrite = (*ioNumberDataPackets)*(converter->inputFormat.mBytesPerPacket);
|
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];
|
amountRead = [converter readData:converter->callbackBuffer amount:amountToWrite];
|
||||||
if (amountRead == 0 && [converter endOfStream] == NO)
|
if (amountRead == 0 && [converter endOfStream] == NO)
|
||||||
|
@ -161,15 +164,21 @@ static OSStatus ACFloatProc(AudioConverterRef inAudioConverter,
|
||||||
return noErr;
|
return noErr;
|
||||||
}
|
}
|
||||||
|
|
||||||
amountToWrite = (*ioNumberDataPackets)*(converter->floatFormat.mBytesPerPacket);
|
amountToWrite = (*ioNumberDataPackets) * (converter->dmFloatFormat.mBytesPerPacket);
|
||||||
|
|
||||||
if ( amountToWrite + converter->floatOffset > converter->floatSize )
|
if ( amountToWrite + converter->floatOffset > converter->floatSize )
|
||||||
|
{
|
||||||
amountToWrite = converter->floatSize - converter->floatOffset;
|
amountToWrite = converter->floatSize - converter->floatOffset;
|
||||||
|
*ioNumberDataPackets = amountToWrite / (converter->dmFloatFormat.mBytesPerPacket);
|
||||||
|
}
|
||||||
|
|
||||||
ioData->mBuffers[0].mData = converter->floatBuffer + converter->floatOffset;
|
ioData->mBuffers[0].mData = converter->floatBuffer + converter->floatOffset;
|
||||||
ioData->mBuffers[0].mDataByteSize = amountToWrite;
|
ioData->mBuffers[0].mDataByteSize = amountToWrite;
|
||||||
ioData->mBuffers[0].mNumberChannels = (converter->floatFormat.mChannelsPerFrame);
|
ioData->mBuffers[0].mNumberChannels = (converter->dmFloatFormat.mChannelsPerFrame);
|
||||||
ioData->mNumberBuffers = 1;
|
ioData->mNumberBuffers = 1;
|
||||||
|
|
||||||
|
if (amountToWrite == 0)
|
||||||
|
return 100;
|
||||||
|
|
||||||
converter->floatOffset += amountToWrite;
|
converter->floatOffset += amountToWrite;
|
||||||
|
|
||||||
|
@ -190,28 +199,41 @@ static OSStatus ACFloatProc(AudioConverterRef inAudioConverter,
|
||||||
- (int)convert:(void *)dest amount:(int)amount
|
- (int)convert:(void *)dest amount:(int)amount
|
||||||
{
|
{
|
||||||
AudioBufferList ioData;
|
AudioBufferList ioData;
|
||||||
UInt32 ioNumberFrames;
|
UInt32 ioNumberPackets;
|
||||||
OSStatus err;
|
OSStatus err;
|
||||||
|
int amountReadFromFC;
|
||||||
int amountRead = 0;
|
int amountRead = 0;
|
||||||
|
|
||||||
|
tryagain2:
|
||||||
|
amountReadFromFC = 0;
|
||||||
|
|
||||||
if (floatOffset == floatSize) {
|
if (floatOffset == floatSize) {
|
||||||
ioNumberFrames = amount / outputFormat.mBytesPerFrame;
|
UInt32 ioWantedNumberPackets;
|
||||||
|
|
||||||
floatBuffer = realloc( floatBuffer, ioNumberFrames * floatFormat.mBytesPerFrame );
|
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].mData = floatBuffer;
|
||||||
ioData.mBuffers[0].mDataByteSize = ioNumberFrames * floatFormat.mBytesPerFrame;
|
ioData.mBuffers[0].mDataByteSize = ioNumberPackets * floatFormat.mBytesPerPacket;
|
||||||
ioData.mBuffers[0].mNumberChannels = floatFormat.mChannelsPerFrame;
|
ioData.mBuffers[0].mNumberChannels = floatFormat.mChannelsPerFrame;
|
||||||
ioData.mNumberBuffers = 1;
|
ioData.mNumberBuffers = 1;
|
||||||
|
|
||||||
tryagain:
|
tryagain:
|
||||||
err = AudioConverterFillComplexBuffer(converterFloat, ACInputProc, (__bridge void * _Nullable)(self), &ioNumberFrames, &ioData, NULL);
|
err = AudioConverterFillComplexBuffer(converterFloat, ACInputProc, (__bridge void * _Nullable)(self), &ioNumberPackets, &ioData, NULL);
|
||||||
amountRead += ioData.mBuffers[0].mDataByteSize;
|
amountReadFromFC += ioNumberPackets * floatFormat.mBytesPerPacket;
|
||||||
if (err == 100)
|
if (err == 100)
|
||||||
{
|
{
|
||||||
DLog(@"INSIZE: %i", amountRead);
|
ioData.mBuffers[0].mData = (void *)(((uint8_t*)floatBuffer) + amountReadFromFC);
|
||||||
ioData.mBuffers[0].mData = floatBuffer + amountRead;
|
ioNumberPackets = ioWantedNumberPackets - ioNumberPackets;
|
||||||
ioNumberFrames = ( amount / outputFormat.mBytesPerFrame ) - ( amountRead / floatFormat.mBytesPerFrame );
|
ioWantedNumberPackets = ioNumberPackets;
|
||||||
ioData.mBuffers[0].mDataByteSize = ioNumberFrames * floatFormat.mBytesPerFrame;
|
ioData.mBuffers[0].mDataByteSize = ioNumberPackets * floatFormat.mBytesPerPacket;
|
||||||
usleep(10000);
|
usleep(10000);
|
||||||
goto tryagain;
|
goto tryagain;
|
||||||
}
|
}
|
||||||
|
@ -222,31 +244,28 @@ static OSStatus ACFloatProc(AudioConverterRef inAudioConverter,
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( inputFormat.mChannelsPerFrame > 2 && outputFormat.mChannelsPerFrame == 2 )
|
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;
|
floatOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ioNumberFrames = amount / outputFormat.mBytesPerFrame;
|
ioNumberPackets = amount / outputFormat.mBytesPerPacket;
|
||||||
ioData.mBuffers[0].mData = dest;
|
ioData.mBuffers[0].mData = dest + amountRead;
|
||||||
ioData.mBuffers[0].mDataByteSize = amount;
|
ioData.mBuffers[0].mDataByteSize = amount - amountRead;
|
||||||
ioData.mBuffers[0].mNumberChannels = outputFormat.mChannelsPerFrame;
|
ioData.mBuffers[0].mNumberChannels = outputFormat.mChannelsPerFrame;
|
||||||
ioData.mNumberBuffers = 1;
|
ioData.mNumberBuffers = 1;
|
||||||
|
|
||||||
amountRead = 0;
|
err = AudioConverterFillComplexBuffer(converter, ACFloatProc, (__bridge void *)(self), &ioNumberPackets, &ioData, NULL);
|
||||||
|
amountRead += ioNumberPackets * outputFormat.mBytesPerPacket;
|
||||||
tryagain2:
|
|
||||||
err = AudioConverterFillComplexBuffer(converter, ACFloatProc, (__bridge void *)(self), &ioNumberFrames, &ioData, NULL);
|
|
||||||
amountRead += ioData.mBuffers[0].mDataByteSize;
|
|
||||||
if (err == 100)
|
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;
|
goto tryagain2;
|
||||||
}
|
}
|
||||||
else if (err != noErr && err != kAudioConverterErr_InvalidInputSize)
|
else if (err != noErr && err != kAudioConverterErr_InvalidInputSize)
|
||||||
|
@ -342,6 +361,11 @@ static float db_to_scale(float db)
|
||||||
return NO;
|
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 );
|
stat = AudioConverterNew ( &floatFormat, &outputFormat, &converter );
|
||||||
if (stat != noErr)
|
if (stat != noErr)
|
||||||
{
|
{
|
||||||
|
@ -349,6 +373,8 @@ static float db_to_scale(float db)
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// These mappings don't do what I want, so avoid them.
|
||||||
if (inputFormat.mChannelsPerFrame > 2 && outputFormat.mChannelsPerFrame == 2)
|
if (inputFormat.mChannelsPerFrame > 2 && outputFormat.mChannelsPerFrame == 2)
|
||||||
{
|
{
|
||||||
SInt32 channelMap[2] = { 0, 1 };
|
SInt32 channelMap[2] = { 0, 1 };
|
||||||
|
@ -360,11 +386,26 @@ static float db_to_scale(float db)
|
||||||
return NO;
|
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)
|
if (stat != noErr)
|
||||||
{
|
{
|
||||||
ALog(@"Error mapping channels %i", stat);
|
ALog(@"Error mapping channels %i", stat);
|
||||||
|
@ -377,6 +418,8 @@ static float db_to_scale(float db)
|
||||||
|
|
||||||
[self refreshVolumeScaling];
|
[self refreshVolumeScaling];
|
||||||
|
|
||||||
|
sampleRatio = (float)inputFormat.mSampleRate / (float)outputFormat.mSampleRate;
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,10 +470,12 @@ static float db_to_scale(float db)
|
||||||
{
|
{
|
||||||
free(floatBuffer);
|
free(floatBuffer);
|
||||||
floatBuffer = NULL;
|
floatBuffer = NULL;
|
||||||
|
floatBufferSize = 0;
|
||||||
}
|
}
|
||||||
if (callbackBuffer) {
|
if (callbackBuffer) {
|
||||||
free(callbackBuffer);
|
free(callbackBuffer);
|
||||||
callbackBuffer = NULL;
|
callbackBuffer = NULL;
|
||||||
|
callbackBufferSize = 0;
|
||||||
}
|
}
|
||||||
floatOffset = 0;
|
floatOffset = 0;
|
||||||
floatSize = 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.
|
///Seems some 3rd party devices return incorrect stuff...or I just don't like noninterleaved data.
|
||||||
deviceFormat.mFormatFlags &= ~kLinearPCMFormatFlagIsNonInterleaved;
|
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 &= ~kLinearPCMFormatFlagIsFloat;
|
||||||
// deviceFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
// deviceFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||||
deviceFormat.mBytesPerFrame = deviceFormat.mChannelsPerFrame*(deviceFormat.mBitsPerChannel/8);
|
deviceFormat.mBytesPerFrame = deviceFormat.mChannelsPerFrame*(deviceFormat.mBitsPerChannel/8);
|
||||||
|
|
Loading…
Reference in a new issue