Replaced AudioQueue output with AUAudioUnit

CQTexperiment
Christopher Snowhill 2022-01-11 07:06:40 -08:00
parent c4c9a741ef
commit 72210c67e4
4 changed files with 127 additions and 247 deletions

View File

@ -240,7 +240,7 @@ static void upmix(float * buffer, int inchannels, int outchannels, size_t count)
}
}
static void scale_by_volume(float * buffer, size_t count, float volume)
void scale_by_volume(float * buffer, size_t count, float volume)
{
if ( volume != 1.0 )
for (size_t i = 0; i < count; ++i )
@ -378,6 +378,7 @@ tryagain:
amountReadFromFC = 0;
if (floatOffset == floatSize) // skip this step if there's still float buffered
while (inpOffset == inpSize) {
size_t samplesRead = 0;

View File

@ -91,6 +91,7 @@
832BEEF9278D4A32005E1BC4 /* retro_assert.h in Headers */ = {isa = PBXBuildFile; fileRef = 832BEED9278D4A32005E1BC4 /* retro_assert.h */; };
832BEEFA278D4A32005E1BC4 /* stdstring.h in Headers */ = {isa = PBXBuildFile; fileRef = 832BEEDB278D4A32005E1BC4 /* stdstring.h */; };
832BEEFB278D4A32005E1BC4 /* filters.h in Headers */ = {isa = PBXBuildFile; fileRef = 832BEEDC278D4A32005E1BC4 /* filters.h */; };
832BEF04278DD06D005E1BC4 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 832BEF03278DD06D005E1BC4 /* AVFoundation.framework */; };
8384912718080FF100E7332D /* Logging.h in Headers */ = {isa = PBXBuildFile; fileRef = 8384912618080FF100E7332D /* Logging.h */; };
839366671815923C006DD712 /* CogPluginMulti.h in Headers */ = {isa = PBXBuildFile; fileRef = 839366651815923C006DD712 /* CogPluginMulti.h */; };
839366681815923C006DD712 /* CogPluginMulti.m in Sources */ = {isa = PBXBuildFile; fileRef = 839366661815923C006DD712 /* CogPluginMulti.m */; };
@ -204,6 +205,7 @@
832BEED9278D4A32005E1BC4 /* retro_assert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = retro_assert.h; sourceTree = "<group>"; };
832BEEDB278D4A32005E1BC4 /* stdstring.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stdstring.h; sourceTree = "<group>"; };
832BEEDC278D4A32005E1BC4 /* filters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = filters.h; sourceTree = "<group>"; };
832BEF03278DD06D005E1BC4 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
8384912618080FF100E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = "<group>"; };
839366651815923C006DD712 /* CogPluginMulti.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CogPluginMulti.h; sourceTree = "<group>"; };
839366661815923C006DD712 /* CogPluginMulti.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CogPluginMulti.m; sourceTree = "<group>"; };
@ -223,6 +225,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
832BEF04278DD06D005E1BC4 /* AVFoundation.framework in Frameworks */,
8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */,
17D21DAD0B8BE76800D1EBDE /* AudioToolbox.framework in Frameworks */,
17D21DAE0B8BE76800D1EBDE /* AudioUnit.framework in Frameworks */,
@ -251,6 +254,7 @@
089C1665FE841158C02AAC07 /* Resources */,
0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */,
034768DFFF38A50411DB9C8B /* Products */,
832BEF02278DD06D005E1BC4 /* Frameworks */,
);
name = CogAudio;
sourceTree = "<group>";
@ -558,6 +562,14 @@
path = string;
sourceTree = "<group>";
};
832BEF02278DD06D005E1BC4 /* Frameworks */ = {
isa = PBXGroup;
children = (
832BEF03278DD06D005E1BC4 /* AVFoundation.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */

View File

@ -12,13 +12,13 @@
#import <CoreAudio/AudioHardware.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AudioUnit/AudioUnit.h>
#import <AVFoundation/AVFoundation.h>
@class OutputNode;
@interface OutputCoreAudio : NSObject {
OutputNode * outputController;
BOOL primed;
BOOL running;
BOOL stopping;
BOOL stopped;
@ -30,13 +30,8 @@
AudioDeviceID outputDeviceID;
AudioStreamBasicDescription deviceFormat; // info about the default device
AudioQueueRef audioQueue;
AudioQueueBufferRef *buffers;
UInt32 numberOfBuffers;
UInt32 bufferByteSize;
AudioUnit outputUnit;
AURenderCallbackStruct renderCallback;
AUAudioUnit *_au;
size_t _bufferSize;
}
- (id)initWithController:(OutputNode *)c;

View File

@ -11,9 +11,7 @@
#import "Logging.h"
@interface OutputCoreAudio (Private)
- (void)prime;
@end
extern void scale_by_volume(float * buffer, size_t count, float volume);
@implementation OutputCoreAudio
@ -23,10 +21,8 @@
if (self)
{
outputController = c;
outputUnit = NULL;
audioQueue = NULL;
buffers = NULL;
numberOfBuffers = 0;
_au = nil;
_bufferSize = 0;
volume = 1.0;
outputDeviceID = -1;
listenerapplied = NO;
@ -37,54 +33,6 @@
return self;
}
static void Sound_Renderer(void *userData, AudioQueueRef queue, AudioQueueBufferRef buffer)
{
OutputCoreAudio *output = (__bridge OutputCoreAudio *)userData;
void *readPointer = buffer->mAudioData;
int amountToRead, amountRead;
int framesToRead = buffer->mAudioDataByteSize / (output->deviceFormat.mBytesPerPacket);
amountToRead = framesToRead * (output->deviceFormat.mBytesPerPacket);
if (output->stopping == YES)
{
output->stopped = YES;
return;
}
if ([output->outputController shouldContinue] == NO)
{
// [output stop];
memset(readPointer, 0, amountToRead);
buffer->mAudioDataByteSize = amountToRead;
AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
return;
}
amountRead = [output->outputController readData:(readPointer) amount:amountToRead];
if ((amountRead < amountToRead) && [output->outputController endOfStream] == NO) //Try one more time! for track changes!
{
int amountRead2; //Use this since return type of readdata isnt known...may want to fix then can do a simple += to readdata
amountRead2 = [output->outputController readData:(readPointer+amountRead) amount:amountToRead-amountRead];
amountRead += amountRead2;
}
if (amountRead < amountToRead)
{
// Either underrun, or no data at all. Caller output tends to just
// buffer loop if it doesn't get anything, so always produce a full
// buffer, and silence anything we couldn't supply.
memset(readPointer + amountRead, 0, amountToRead - amountRead);
amountRead = amountToRead;
}
buffer->mAudioDataByteSize = amountRead;
AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
}
static OSStatus
default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData)
{
@ -107,7 +55,6 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
{
OSStatus err;
BOOL defaultDevice = NO;
UInt32 thePropSize;
AudioObjectPropertyAddress theAddress = {
.mSelector = kAudioHardwarePropertyDefaultOutputDevice,
.mScope = kAudioObjectPropertyScopeGlobal,
@ -126,7 +73,7 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
}
}
if (audioQueue) {
if (_au) {
AudioObjectPropertyAddress defaultDeviceAddress = theAddress;
if (listenerapplied && !defaultDevice) {
@ -135,31 +82,14 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
}
if (outputDeviceID != deviceID) {
printf("DEVICE: %i\n", deviceID);
DLog(@"Device: %i\n", deviceID);
outputDeviceID = deviceID;
CFStringRef theDeviceUID;
theAddress.mSelector = kAudioDevicePropertyDeviceUID;
theAddress.mScope = kAudioDevicePropertyScopeOutput;
thePropSize = sizeof(theDeviceUID);
err = AudioObjectGetPropertyData(outputDeviceID, &theAddress, 0, NULL, &thePropSize, &theDeviceUID);
if (err) {
DLog(@"Error getting device UID as string");
return err;
NSError *nserr;
[_au setDeviceID:outputDeviceID error:&nserr];
if (nserr != nil) {
return (OSErr)[nserr code];
}
err = AudioQueueStop(audioQueue, true);
if (err) {
DLog(@"Error stopping stream to set device");
CFRelease(theDeviceUID);
return err;
}
primed = NO;
err = AudioQueueSetProperty(audioQueue, kAudioQueueProperty_CurrentDevice, &theDeviceUID, sizeof(theDeviceUID));
CFRelease(theDeviceUID);
if (running)
[self start];
}
if (!listenerapplied && defaultDevice) {
@ -167,15 +97,6 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
listenerapplied = YES;
}
}
else if (outputUnit) {
err = AudioUnitSetProperty(outputUnit,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Output,
0,
&deviceID,
sizeof(AudioDeviceID));
}
else {
err = noErr;
}
@ -298,93 +219,29 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
- (BOOL)setup
{
if (outputUnit || audioQueue)
if (_au)
[self stop];
stopping = NO;
stopped = NO;
outputDeviceID = -1;
AVAudioFormat *format, *renderFormat;
AudioComponentDescription desc;
OSStatus err;
NSError *err;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
desc.componentSubType = kAudioUnitSubType_HALOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
AudioComponent comp = AudioComponentFindNext(NULL, &desc); //Finds an component that meets the desc spec's
if (comp == NULL)
return NO;
err = AudioComponentInstanceNew(comp, &outputUnit); //gains access to the services provided by the component
if (err)
return NO;
// Initialize AudioUnit
err = AudioUnitInitialize(outputUnit);
if (err != noErr)
return NO;
// Setup the output device before mucking with settings
NSDictionary *device = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"];
if (device) {
BOOL ok = [self setOutputDeviceWithDeviceDict:device];
if (!ok) {
//Ruh roh.
[self setOutputDeviceWithDeviceDict:nil];
[[[NSUserDefaultsController sharedUserDefaultsController] defaults] removeObjectForKey:@"outputDevice"];
}
}
else {
[self setOutputDeviceWithDeviceDict:nil];
}
UInt32 size = sizeof (AudioStreamBasicDescription);
Boolean outWritable;
//Gets the size of the Stream Format Property and if it is writable
AudioUnitGetPropertyInfo(outputUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
0,
&size,
&outWritable);
//Get the current stream format of the output
err = AudioUnitGetProperty (outputUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
0,
&deviceFormat,
&size);
if (err != noErr)
return NO;
AudioUnitUninitialize (outputUnit);
AudioComponentInstanceDispose(outputUnit);
outputUnit = NULL;
///Seems some 3rd party devices return incorrect stuff...or I just don't like noninterleaved data.
deviceFormat.mFormatFlags &= ~kLinearPCMFormatFlagIsNonInterleaved;
// deviceFormat.mFormatFlags &= ~kLinearPCMFormatFlagIsFloat;
// deviceFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
if (@available(macOS 12.0, *)) {
// Let's enable surround upmixing, for surround and spatial output
deviceFormat.mChannelsPerFrame = 8;
}
// And force a default rate for crappy devices
if (deviceFormat.mSampleRate < 32000)
deviceFormat.mSampleRate = 48000;
deviceFormat.mBytesPerFrame = deviceFormat.mChannelsPerFrame*(deviceFormat.mBitsPerChannel/8);
deviceFormat.mBytesPerPacket = deviceFormat.mBytesPerFrame * deviceFormat.mFramesPerPacket;
err = AudioQueueNewOutput(&deviceFormat, Sound_Renderer, (__bridge void * _Nullable)(self), NULL, NULL, 0, &audioQueue);
if (err != noErr)
_au = [[AUAudioUnit alloc] initWithComponentDescription:desc error:&err];
if (err != nil)
return NO;
// Setup the output device before mucking with settings
NSDictionary *device = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"];
if (device) {
BOOL ok = [self setOutputDeviceWithDeviceDict:device];
if (!ok) {
@ -398,95 +255,128 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
[self setOutputDeviceWithDeviceDict:nil];
}
format = _au.outputBusses[0].format;
deviceFormat = *(format.streamDescription);
///Seems some 3rd party devices return incorrect stuff...or I just don't like noninterleaved data.
deviceFormat.mFormatFlags &= ~kLinearPCMFormatFlagIsNonInterleaved;
// deviceFormat.mFormatFlags &= ~kLinearPCMFormatFlagIsFloat;
// deviceFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
if (@available(macOS 12.0, *)) {
// Let's enable surround upmixing, for surround and spatial output
deviceFormat.mChannelsPerFrame = 8;
}
// And force a default rate for crappy devices
if (deviceFormat.mSampleRate < 32000)
deviceFormat.mSampleRate = 48000;
deviceFormat.mBytesPerFrame = deviceFormat.mChannelsPerFrame*(deviceFormat.mBitsPerChannel/8);
deviceFormat.mBytesPerPacket = deviceFormat.mBytesPerFrame * deviceFormat.mFramesPerPacket;
/* Set the channel layout for the audio queue */
AudioChannelLayout layout = {0};
AudioChannelLayoutTag tag = 0;
switch (deviceFormat.mChannelsPerFrame) {
case 1:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
tag = kAudioChannelLayoutTag_Mono;
break;
case 2:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
tag = kAudioChannelLayoutTag_Stereo;
break;
case 3:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_4;
tag = kAudioChannelLayoutTag_DVD_4;
break;
case 4:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Quadraphonic;
tag = kAudioChannelLayoutTag_Quadraphonic;
break;
case 5:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_0_A;
tag = kAudioChannelLayoutTag_MPEG_5_0_A;
break;
case 6:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_1_A;
tag = kAudioChannelLayoutTag_MPEG_5_1_A;
break;
case 7:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_6_1_A;
tag = kAudioChannelLayoutTag_MPEG_6_1_A;
break;
case 8:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_A;
tag = kAudioChannelLayoutTag_MPEG_7_1_A;
break;
}
if (layout.mChannelLayoutTag != 0) {
err = AudioQueueSetProperty(audioQueue, kAudioQueueProperty_ChannelLayout, &layout, sizeof(layout));
if (err != noErr) {
return NO;
}
}
numberOfBuffers = 4;
bufferByteSize = deviceFormat.mBytesPerPacket * 512;
buffers = calloc(sizeof(buffers[0]), numberOfBuffers);
if (!buffers)
{
AudioQueueDispose(audioQueue, true);
audioQueue = NULL;
renderFormat = [[AVAudioFormat alloc] initWithStreamDescription:&deviceFormat channelLayout:[[AVAudioChannelLayout alloc] initWithLayoutTag:tag]];
[_au.inputBusses[0] setFormat:renderFormat error:&err];
if (err != nil)
return NO;
}
for (UInt32 i = 0; i < numberOfBuffers; ++i)
float * volume = &self->volume;
_au.outputProvider = ^AUAudioUnitStatus(AudioUnitRenderActionFlags * actionFlags, const AudioTimeStamp * timestamp, AUAudioFrameCount frameCount, NSInteger inputBusNumber, AudioBufferList * inputData)
{
err = AudioQueueAllocateBuffer(audioQueue, bufferByteSize, buffers + i);
if (err != noErr || buffers[i] == NULL)
void *readPointer = inputData->mBuffers[0].mData;
int amountToRead, amountRead;
int framesToRead = inputData->mBuffers[0].mDataByteSize / (self->deviceFormat.mBytesPerPacket);
amountToRead = framesToRead * (self->deviceFormat.mBytesPerPacket);
if (self->stopping == YES)
{
err = AudioQueueDispose(audioQueue, true);
audioQueue = NULL;
return NO;
self->stopped = YES;
memset(readPointer, 0, amountToRead);
inputData->mBuffers[0].mDataByteSize = amountToRead;
return 0;
}
if ([self->outputController shouldContinue] == NO)
{
memset(readPointer, 0, amountToRead);
inputData->mBuffers[0].mDataByteSize = amountToRead;
return 0;
}
buffers[i]->mAudioDataByteSize = bufferByteSize;
}
[self prime];
[outputController setFormat:&deviceFormat];
return (err == noErr);
}
amountRead = [self->outputController readData:(readPointer) amount:amountToRead];
- (void)prime
{
for (UInt32 i = 0; i < numberOfBuffers; ++i)
Sound_Renderer((__bridge void * _Nullable)(self), audioQueue, buffers[i]);
primed = YES;
if ((amountRead < amountToRead) && [self->outputController endOfStream] == NO) //Try one more time! for track changes!
{
int amountRead2; //Use this since return type of readdata isnt known...may want to fix then can do a simple += to readdata
amountRead2 = [self->outputController readData:(readPointer+amountRead) amount:amountToRead-amountRead];
amountRead += amountRead2;
}
int framesRead = amountRead / sizeof(float);
scale_by_volume((float*)readPointer, framesRead, *volume);
if (amountRead < amountToRead)
{
// Either underrun, or no data at all. Caller output tends to just
// buffer loop if it doesn't get anything, so always produce a full
// buffer, and silence anything we couldn't supply.
memset(readPointer + amountRead, 0, amountToRead - amountRead);
amountRead = amountToRead;
}
inputData->mBuffers[0].mDataByteSize = amountRead;
return 0;
};
[_au allocateRenderResourcesAndReturnError:&err];
[outputController setFormat:&deviceFormat];
return (err == nil);
}
- (void)setVolume:(double)v
{
volume = v * 0.01f;
AudioQueueSetParameter(audioQueue, kAudioQueueParam_VolumeRampTime, 0);
AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, volume);
}
- (void)start
{
AudioQueueSetParameter(audioQueue, kAudioQueueParam_VolumeRampTime, 0);
AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, volume);
AudioQueueStart(audioQueue, NULL);
NSError *err;
[_au startHardwareAndReturnError:&err];
running = YES;
if (!primed)
[self prime];
}
- (void)stop
@ -501,27 +391,10 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &theAddress, default_device_changed, (__bridge void * _Nullable)(self));
listenerapplied = NO;
}
if (outputUnit) {
AudioUnitUninitialize (outputUnit);
AudioComponentInstanceDispose(outputUnit);
outputUnit = NULL;
}
if (audioQueue && buffers) {
AudioQueuePause(audioQueue);
AudioQueueStop(audioQueue, true);
if (_au) {
[_au stopHardware];
running = NO;
for (UInt32 i = 0; i < numberOfBuffers; ++i) {
if (buffers[i])
AudioQueueFreeBuffer(audioQueue, buffers[i]);
buffers[i] = NULL;
}
free(buffers);
buffers = NULL;
}
if (audioQueue) {
AudioQueueDispose(audioQueue, true);
audioQueue = NULL;
_au = nil;
}
}
@ -534,16 +407,15 @@ default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const
- (void)pause
{
AudioQueuePause(audioQueue);
[_au stopHardware];
running = NO;
}
- (void)resume
{
AudioQueueStart(audioQueue, NULL);
NSError *err;
[_au startHardwareAndReturnError:&err];
running = YES;
if (!primed)
[self prime];
}
@end