cog/Audio/Output/OutputCoreAudio.m

550 lines
16 KiB
Matlab
Raw Normal View History

2020-03-23 08:46:41 +00:00
//
2005-09-07 22:33:16 +00:00
// OutputCoreAudio.m
// Cog
//
// Created by Vincent Spader on 8/2/05.
// Copyright 2005 Vincent Spader. All rights reserved.
2005-09-07 22:33:16 +00:00
//
#import "OutputCoreAudio.h"
#import "OutputNode.h"
2005-09-07 22:33:16 +00:00
#import "Logging.h"
@interface OutputCoreAudio (Private)
- (void)prime;
@end
2005-09-07 22:33:16 +00:00
@implementation OutputCoreAudio
- (id)initWithController:(OutputNode *)c
2005-09-07 22:33:16 +00:00
{
2006-01-20 15:34:02 +00:00
self = [super init];
if (self)
2005-09-07 22:33:16 +00:00
{
2006-01-20 15:34:02 +00:00
outputController = c;
outputUnit = NULL;
audioQueue = NULL;
buffers = NULL;
numberOfBuffers = 0;
volume = 1.0;
outputDeviceID = -1;
listenerapplied = NO;
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.outputDevice" options:0 context:NULL];
2005-09-07 22:33:16 +00:00
}
2006-01-20 15:34:02 +00:00
return self;
}
static void Sound_Renderer(void *userData, AudioQueueRef queue, AudioQueueBufferRef buffer)
2006-01-20 15:34:02 +00:00
{
OutputCoreAudio *output = (__bridge OutputCoreAudio *)userData;
void *readPointer = buffer->mAudioData;
2006-01-20 15:34:02 +00:00
int amountToRead, amountRead;
int framesToRead = buffer->mAudioDataByteSize / (output->deviceFormat.mBytesPerPacket);
amountToRead = framesToRead * (output->deviceFormat.mBytesPerPacket);
if (output->stopping == YES)
{
output->stopped = YES;
return;
}
2006-01-20 15:34:02 +00:00
if ([output->outputController shouldContinue] == NO)
2005-09-07 22:33:16 +00:00
{
// [output stop];
memset(readPointer, 0, amountToRead);
buffer->mAudioDataByteSize = amountToRead;
AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
return;
2005-09-07 22:33:16 +00:00
}
2006-01-20 15:34:02 +00:00
amountRead = [output->outputController readData:(readPointer) amount:amountToRead];
2007-05-26 22:13:11 +00:00
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;
}
2005-09-07 22:33:16 +00:00
buffer->mAudioDataByteSize = amountRead;
AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
}
static OSStatus
default_device_changed(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData)
{
OutputCoreAudio *this = (__bridge OutputCoreAudio *) inUserData;
return [this setOutputDeviceByID:-1];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:@"values.outputDevice"]) {
2007-07-11 01:20:32 +00:00
NSDictionary *device = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"outputDevice"];
2020-02-01 12:59:30 +00:00
[self setOutputDeviceWithDeviceDict:device];
}
}
2020-02-17 17:20:48 +00:00
- (OSStatus)setOutputDeviceByID:(AudioDeviceID)deviceID
{
OSStatus err;
BOOL defaultDevice = NO;
UInt32 thePropSize;
AudioObjectPropertyAddress theAddress = {
.mSelector = kAudioHardwarePropertyDefaultOutputDevice,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMaster
};
2020-02-01 12:59:30 +00:00
if (deviceID == -1) {
defaultDevice = YES;
UInt32 size = sizeof(AudioDeviceID);
err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &size, &deviceID);
if (err != noErr) {
2020-02-17 17:20:48 +00:00
DLog(@"THERE'S NO DEFAULT OUTPUT DEVICE");
2020-02-01 12:59:30 +00:00
return err;
}
}
if (audioQueue) {
AudioObjectPropertyAddress defaultDeviceAddress = theAddress;
if (listenerapplied && !defaultDevice) {
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &defaultDeviceAddress, default_device_changed, (__bridge void * _Nullable)(self));
listenerapplied = NO;
}
if (outputDeviceID != deviceID) {
printf("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;
}
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));
2021-12-27 02:08:53 +00:00
CFRelease(theDeviceUID);
if (running)
[self start];
}
if (!listenerapplied && defaultDevice) {
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &defaultDeviceAddress, default_device_changed, (__bridge void * _Nullable)(self));
listenerapplied = YES;
}
}
else if (outputUnit) {
err = AudioUnitSetProperty(outputUnit,
kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Output,
0,
&deviceID,
sizeof(AudioDeviceID));
}
else {
err = noErr;
}
2020-02-17 17:20:48 +00:00
if (err != noErr) {
DLog(@"No output device with ID %d could be found.", deviceID);
return err;
}
2020-02-01 12:59:30 +00:00
return err;
}
- (BOOL)setOutputDeviceWithDeviceDict:(NSDictionary *)deviceDict
{
NSNumber *deviceIDNum = [deviceDict objectForKey:@"deviceID"];
AudioDeviceID outputDeviceID = [deviceIDNum unsignedIntValue] ?: -1;
__block OSStatus err = [self setOutputDeviceByID:outputDeviceID];
if (err != noErr) {
2020-02-01 12:59:30 +00:00
// Try matching by name.
NSString *userDeviceName = deviceDict[@"name"];
2020-02-17 17:20:48 +00:00
2020-02-01 12:59:30 +00:00
[self enumerateAudioOutputsUsingBlock:
^(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop) {
2020-02-17 17:20:48 +00:00
if ([deviceName isEqualToString:userDeviceName]) {
err = [self setOutputDeviceByID:deviceID];
#if 0
2020-02-17 17:20:48 +00:00
// Disable. Would cause loop by triggering `-observeValueForKeyPath:ofObject:change:context:` above.
// Update `outputDevice`, in case the ID has changed.
NSDictionary *deviceInfo = @{
@"name": deviceName,
@"deviceID": @(deviceID),
};
[[NSUserDefaults standardUserDefaults] setObject:deviceInfo forKey:@"outputDevice"];
#endif
2020-02-17 17:20:48 +00:00
DLog(@"Found output device: \"%@\" (%d).", deviceName, deviceID);
*stop = YES;
}
2020-02-01 12:59:30 +00:00
}];
}
if (err != noErr) {
ALog(@"No output device could be found, your random error code is %d. Have a nice day!", err);
return NO;
}
return YES;
}
2005-09-07 22:33:16 +00:00
2020-02-01 12:59:30 +00:00
// The following is largely a copy pasta of -awakeFromNib from "OutputsArrayController.m".
// TODO: Share the code. (How to do this across xcodeproj?)
- (void)enumerateAudioOutputsUsingBlock:(void (NS_NOESCAPE ^ _Nonnull)(NSString *deviceName, AudioDeviceID deviceID, AudioDeviceID systemDefaultID, BOOL *stop))block
{
2020-02-01 12:59:30 +00:00
UInt32 propsize;
AudioObjectPropertyAddress theAddress = {
.mSelector = kAudioHardwarePropertyDevices,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMaster
};
2020-02-01 12:59:30 +00:00
__Verify_noErr(AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize));
UInt32 nDevices = propsize / (UInt32)sizeof(AudioDeviceID);
2020-02-01 12:59:30 +00:00
AudioDeviceID *devids = malloc(propsize);
__Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, devids));
theAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
AudioDeviceID systemDefault;
propsize = sizeof(systemDefault);
__Verify_noErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, &theAddress, 0, NULL, &propsize, &systemDefault));
theAddress.mScope = kAudioDevicePropertyScopeOutput;
for (UInt32 i = 0; i < nDevices; ++i) {
2020-02-01 12:59:30 +00:00
CFStringRef name = NULL;
propsize = sizeof(name);
theAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
__Verify_noErr(AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propsize, &name));
propsize = 0;
theAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
__Verify_noErr(AudioObjectGetPropertyDataSize(devids[i], &theAddress, 0, NULL, &propsize));
2021-12-27 02:08:53 +00:00
if (propsize < sizeof(UInt32)) {
CFRelease(name);
continue;
}
2020-02-01 12:59:30 +00:00
AudioBufferList * bufferList = (AudioBufferList *) malloc(propsize);
__Verify_noErr(AudioObjectGetPropertyData(devids[i], &theAddress, 0, NULL, &propsize, bufferList));
UInt32 bufferCount = bufferList->mNumberBuffers;
free(bufferList);
2021-12-27 02:08:53 +00:00
if (!bufferCount) {
CFRelease(name);
continue;
}
2020-02-01 12:59:30 +00:00
BOOL stop = NO;
block([NSString stringWithString:(__bridge NSString *)name],
devids[i],
systemDefault,
2020-02-01 12:59:30 +00:00
&stop);
CFRelease(name);
if (stop) {
break;
}
}
free(devids);
}
2006-01-20 15:34:02 +00:00
- (BOOL)setup
2005-09-07 22:33:16 +00:00
{
if (outputUnit || audioQueue)
[self stop];
stopping = NO;
stopped = NO;
outputDeviceID = -1;
2020-03-23 08:46:41 +00:00
AudioComponentDescription desc;
2005-09-07 22:33:16 +00:00
OSStatus err;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
2020-03-23 08:46:41 +00:00
AudioComponent comp = AudioComponentFindNext(NULL, &desc); //Finds an component that meets the desc spec's
2005-09-07 22:33:16 +00:00
if (comp == NULL)
return NO;
2020-03-23 08:46:41 +00:00
err = AudioComponentInstanceNew(comp, &outputUnit); //gains access to the services provided by the component
2005-09-07 22:33:16 +00:00
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) {
2020-02-01 12:59:30 +00:00
BOOL ok = [self setOutputDeviceWithDeviceDict:device];
if (!ok) {
//Ruh roh.
2020-02-01 12:59:30 +00:00
[self setOutputDeviceWithDeviceDict:nil];
[[[NSUserDefaultsController sharedUserDefaultsController] defaults] removeObjectForKey:@"outputDevice"];
}
}
else {
2020-02-01 12:59:30 +00:00
[self setOutputDeviceWithDeviceDict:nil];
}
2005-09-07 22:33:16 +00:00
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;
2005-09-07 22:33:16 +00:00
///Seems some 3rd party devices return incorrect stuff...or I just don't like noninterleaved data.
deviceFormat.mFormatFlags &= ~kLinearPCMFormatFlagIsNonInterleaved;
2006-01-20 15:34:02 +00:00
// 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;
2005-09-07 22:33:16 +00:00
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)
return NO;
if (device) {
BOOL ok = [self setOutputDeviceWithDeviceDict:device];
if (!ok) {
//Ruh roh.
[self setOutputDeviceWithDeviceDict:nil];
[[[NSUserDefaultsController sharedUserDefaultsController] defaults] removeObjectForKey:@"outputDevice"];
}
}
else {
[self setOutputDeviceWithDeviceDict:nil];
}
/* Set the channel layout for the audio queue */
AudioChannelLayout layout = {0};
switch (deviceFormat.mChannelsPerFrame) {
case 1:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
break;
case 2:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
break;
case 3:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_4;
break;
case 4:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Quadraphonic;
break;
case 5:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_0_A;
break;
case 6:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_1_A;
break;
case 7:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_6_1_A;
break;
case 8:
layout.mChannelLayoutTag = 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;
return NO;
}
for (UInt32 i = 0; i < numberOfBuffers; ++i)
{
err = AudioQueueAllocateBuffer(audioQueue, bufferByteSize, buffers + i);
if (err != noErr || buffers[i] == NULL)
{
err = AudioQueueDispose(audioQueue, true);
audioQueue = NULL;
return NO;
}
buffers[i]->mAudioDataByteSize = bufferByteSize;
}
[self prime];
2006-01-20 15:34:02 +00:00
[outputController setFormat:&deviceFormat];
2005-09-07 22:33:16 +00:00
return (err == noErr);
}
- (void)prime
{
for (UInt32 i = 0; i < numberOfBuffers; ++i)
Sound_Renderer((__bridge void * _Nullable)(self), audioQueue, buffers[i]);
primed = YES;
}
2006-01-20 15:34:02 +00:00
- (void)setVolume:(double)v
{
volume = v * 0.01f;
AudioQueueSetParameter(audioQueue, kAudioQueueParam_VolumeRampTime, 0);
AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, volume);
}
2006-01-20 15:34:02 +00:00
2005-09-07 22:33:16 +00:00
- (void)start
{
AudioQueueSetParameter(audioQueue, kAudioQueueParam_VolumeRampTime, 0);
AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, volume);
AudioQueueStart(audioQueue, NULL);
running = YES;
if (!primed)
[self prime];
2005-09-07 22:33:16 +00:00
}
- (void)stop
{
stopping = YES;
if (listenerapplied) {
AudioObjectPropertyAddress theAddress = {
.mSelector = kAudioHardwarePropertyDefaultOutputDevice,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMaster
};
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &theAddress, default_device_changed, (__bridge void * _Nullable)(self));
listenerapplied = NO;
}
if (outputUnit) {
2006-01-20 15:34:02 +00:00
AudioUnitUninitialize (outputUnit);
2020-03-23 08:46:41 +00:00
AudioComponentInstanceDispose(outputUnit);
outputUnit = NULL;
2006-01-20 15:34:02 +00:00
}
if (audioQueue && buffers) {
AudioQueuePause(audioQueue);
AudioQueueStop(audioQueue, true);
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;
}
2005-09-07 22:33:16 +00:00
}
- (void)dealloc
{
[self stop];
2007-10-13 07:09:46 +00:00
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.outputDevice"];
}
2006-01-29 14:57:48 +00:00
- (void)pause
{
AudioQueuePause(audioQueue);
running = NO;
2006-01-29 14:57:48 +00:00
}
- (void)resume
{
AudioQueueStart(audioQueue, NULL);
running = YES;
if (!primed)
[self prime];
2006-01-29 14:57:48 +00:00
}
2005-09-07 22:33:16 +00:00
@end