Initial commit

This commit is contained in:
Tha_14
2024-02-22 21:43:11 +02:00
commit 1b96a031d2
1108 changed files with 157706 additions and 0 deletions

View File

@ -0,0 +1,33 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#import "OCTAudioEngine.h"
#import "TPCircularBuffer.h"
@import AVFoundation;
extern int kBufferLength;
extern int kNumberOfChannels;
extern int kDefaultSampleRate;
extern int kSampleCount_incoming_audio;
extern int kSampleCount_outgoing_audio;
extern int kBitsPerByte;
extern int kFramesPerPacket;
extern int kBytesPerSample;
extern int kNumberOfAudioQueueBuffers;
@class OCTAudioQueue;
@interface OCTAudioEngine ()
#if ! TARGET_OS_IPHONE
@property (strong, nonatomic, readonly) NSString *inputDeviceID;
@property (strong, nonatomic, readonly) NSString *outputDeviceID;
#endif
@property (nonatomic, strong) OCTAudioQueue *outputQueue;
@property (nonatomic, strong) OCTAudioQueue *inputQueue;
- (void)makeQueues:(NSError **)error;
@end

View File

@ -0,0 +1,92 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#import <Foundation/Foundation.h>
#import "OCTToxAV.h"
@interface OCTAudioEngine : NSObject
@property (weak, nonatomic) OCTToxAV *toxav;
@property (nonatomic, assign) OCTToxFriendNumber friendNumber;
/**
* YES to send audio frames over to tox, otherwise NO.
* Default is YES.
*/
@property (nonatomic, assign) BOOL enableMicrophone;
/**
* Starts the Audio Processing Graph.
* @param error Pointer to error object.
* @return YES on success, otherwise NO.
*/
- (BOOL)startAudioFlow:(NSError **)error;
/**
* Stops the Audio Processing Graph.
* @param error Pointer to error object.
* @return YES on success, otherwise NO.
*/
- (BOOL)stopAudioFlow:(NSError **)error;
/**
* Checks if the Audio Graph is processing.
* @param error Pointer to error object.
* @return YES if Audio Graph is running, otherwise NO.
*/
- (BOOL)isAudioRunning:(NSError **)error;
/**
* Provide audio data that will be placed in buffer to be played in speaker.
* @param pcm An array of audio samples (sample_count * channels elements).
* @param sampleCount The number of audio samples per channel in the PCM array.
* @param channels Number of audio channels.
* @param sampleRate Sampling rate used in this frame.
*/
- (void)provideAudioFrames:(OCTToxAVPCMData *)pcm sampleCount:(OCTToxAVSampleCount)sampleCount channels:(OCTToxAVChannels)channels sampleRate:(OCTToxAVSampleRate)sampleRate fromFriend:(OCTToxFriendNumber)friendNumber;
@end
#if ! TARGET_OS_IPHONE
@interface OCTAudioEngine (MacDevice)
/**
* Set the input device (not available on iOS).
* @param inputDeviceID Core Audio's unique ID for the device. See
* public OCTSubmanagerCalls.h for what these should be.
* @param error If this method returns NO, contains more information on the
* underlying error.
* @return YES on success, otherwise NO.
*/
- (BOOL)setInputDeviceID:(NSString *)inputDeviceID error:(NSError **)error;
/**
* Set the output device (not available on iOS).
* @param outputDeviceID Core Audio's unique ID for the device. See
* public OCTSubmanagerCalls.h for what these should be.
* @param error If this method returns NO, contains more information on the
* underlying error.
* @return YES on success, otherwise NO.
*/
- (BOOL)setOutputDeviceID:(NSString *)outputDeviceID error:(NSError **)error;
@end
#else
@interface OCTAudioEngine (iOSDevice)
/**
* Switch the output to/from the device's speaker.
* @param speaker Whether we should use the speaker for output.
* @param error If this method returns NO, contains more information on the
* underlying error.
* @return YES on success, otherwise NO.
*/
- (BOOL)routeAudioToSpeaker:(BOOL)speaker error:(NSError **)error;
@end
#endif

View File

@ -0,0 +1,191 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#import "OCTAudioEngine+Private.h"
#import "OCTToxAV+Private.h"
#import "OCTAudioQueue.h"
#import "OCTLogging.h"
@import AVFoundation;
@interface OCTAudioEngine ()
@property (nonatomic, assign) OCTToxAVSampleRate outputSampleRate;
@property (nonatomic, assign) OCTToxAVChannels outputNumberOfChannels;
@end
@implementation OCTAudioEngine
#pragma mark - LifeCycle
- (instancetype)init
{
self = [super init];
if (! self) {
return nil;
}
_outputSampleRate = kDefaultSampleRate;
_outputNumberOfChannels = kNumberOfChannels;
_enableMicrophone = YES;
return self;
}
#pragma mark - SPI
#if ! TARGET_OS_IPHONE
- (BOOL)setInputDeviceID:(NSString *)inputDeviceID error:(NSError **)error
{
// if audio is not active, we can't really be bothered to check that the
// device exists; we rely on startAudioFlow: to fail later.
if (! self.inputQueue) {
_inputDeviceID = inputDeviceID;
return YES;
}
if ([self.inputQueue setDeviceID:inputDeviceID error:error]) {
_inputDeviceID = inputDeviceID;
return YES;
}
return NO;
}
- (BOOL)setOutputDeviceID:(NSString *)outputDeviceID error:(NSError **)error
{
if (! self.outputQueue) {
_outputDeviceID = outputDeviceID;
return YES;
}
if ([self.outputQueue setDeviceID:outputDeviceID error:error]) {
_outputDeviceID = outputDeviceID;
return YES;
}
return NO;
}
#else
- (BOOL)routeAudioToSpeaker:(BOOL)speaker error:(NSError **)error
{
AVAudioSession *session = [AVAudioSession sharedInstance];
AVAudioSessionPortOverride override;
if (speaker) {
override = AVAudioSessionPortOverrideSpeaker;
}
else {
override = AVAudioSessionPortOverrideNone;
}
return [session overrideOutputAudioPort:override error:error];
}
#endif
- (BOOL)startAudioFlow:(NSError **)error
{
#if TARGET_OS_IPHONE
AVAudioSession *session = [AVAudioSession sharedInstance];
if (! ([session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:error] &&
[session setPreferredSampleRate:kDefaultSampleRate error:error] &&
[session setMode:AVAudioSessionModeVoiceChat error:error] &&
[session setActive:YES error:error])) {
return NO;
}
#endif
[self makeQueues:error];
if (! (self.outputQueue && self.inputQueue)) {
return NO;
}
OCTAudioEngine *__weak welf = self;
self.inputQueue.sendDataBlock = ^(void *data, OCTToxAVSampleCount samples, OCTToxAVSampleRate rate, OCTToxAVChannels channelCount) {
OCTAudioEngine *aoi = welf;
if (aoi.enableMicrophone) {
// TOXAUDIO: -outgoing-audio-
[aoi.toxav sendAudioFrame:data
sampleCount:samples
channels:channelCount
sampleRate:rate
toFriend:aoi.friendNumber
error:nil];
}
};
// TOXAUDIO: -incoming-audio-
[self.outputQueue updateSampleRate:self.outputSampleRate numberOfChannels:self.outputNumberOfChannels error:nil];
if (! [self.inputQueue begin:error] || ! [self.outputQueue begin:error]) {
return NO;
}
return YES;
}
- (BOOL)stopAudioFlow:(NSError **)error
{
if (! [self.inputQueue stop:error] || ! [self.outputQueue stop:error]) {
return NO;
}
#if TARGET_OS_IPHONE
AVAudioSession *session = [AVAudioSession sharedInstance];
BOOL ret = [session setActive:NO error:error];
#else
BOOL ret = YES;
#endif
self.inputQueue = nil;
self.outputQueue = nil;
return ret;
}
- (void)provideAudioFrames:(OCTToxAVPCMData *)pcm sampleCount:(OCTToxAVSampleCount)sampleCount channels:(OCTToxAVChannels)channels sampleRate:(OCTToxAVSampleRate)sampleRate fromFriend:(OCTToxFriendNumber)friendNumber
{
// TOXAUDIO: -incoming-audio-
int32_t len = (int32_t)(channels * sampleCount * sizeof(OCTToxAVPCMData));
TPCircularBuffer *buf = [self.outputQueue getBufferPointer];
if (buf) {
TPCircularBufferProduceBytes(buf, pcm, len);
}
if ((self.outputSampleRate != sampleRate) || (self.outputNumberOfChannels != channels)) {
// failure is logged by OCTAudioQueue.
[self.outputQueue updateSampleRate:sampleRate numberOfChannels:channels error:nil];
self.outputSampleRate = sampleRate;
self.outputNumberOfChannels = channels;
}
}
- (BOOL)isAudioRunning:(NSError **)error
{
return self.inputQueue.running && self.outputQueue.running;
}
- (void)makeQueues:(NSError **)error
{
// Note: OCTAudioQueue handles the case where the device ids are nil - in that case
// we don't set the device explicitly, and the default is used.
#if TARGET_OS_IPHONE
// TOXAUDIO: -incoming-audio-
self.outputQueue = [[OCTAudioQueue alloc] initWithOutputDeviceID:nil error:error];
// TOXAUDIO: -outgoing-audio-
self.inputQueue = [[OCTAudioQueue alloc] initWithInputDeviceID:nil error:error];
#else
self.outputQueue = [[OCTAudioQueue alloc] initWithOutputDeviceID:self.outputDeviceID error:error];
self.inputQueue = [[OCTAudioQueue alloc] initWithInputDeviceID:self.inputDeviceID error:error];
#endif
}
@end

View File

@ -0,0 +1,74 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#import <Foundation/Foundation.h>
#import "TPCircularBuffer.h"
@import AudioToolbox;
#pragma mark - C declarations
extern OSStatus (*_AudioQueueAllocateBuffer)(AudioQueueRef inAQ,
UInt32 inBufferByteSize,
AudioQueueBufferRef *outBuffer);
extern OSStatus (*_AudioQueueDispose)(AudioQueueRef inAQ,
Boolean inImmediate);
extern OSStatus (*_AudioQueueEnqueueBuffer)(AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
UInt32 inNumPacketDescs,
const AudioStreamPacketDescription *inPacketDescs);
extern OSStatus (*_AudioQueueFreeBuffer)(AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer);
extern OSStatus (*_AudioQueueNewInput)(const AudioStreamBasicDescription *inFormat,
AudioQueueInputCallback inCallbackProc,
void *inUserData,
CFRunLoopRef inCallbackRunLoop,
CFStringRef inCallbackRunLoopMode,
UInt32 inFlags,
AudioQueueRef *outAQ);
extern OSStatus (*_AudioQueueNewOutput)(const AudioStreamBasicDescription *inFormat,
AudioQueueOutputCallback inCallbackProc,
void *inUserData,
CFRunLoopRef inCallbackRunLoop,
CFStringRef inCallbackRunLoopMode,
UInt32 inFlags,
AudioQueueRef *outAQ);
extern OSStatus (*_AudioQueueSetProperty)(AudioQueueRef inAQ,
AudioQueuePropertyID inID,
const void *inData,
UInt32 inDataSize);
extern OSStatus (*_AudioQueueStart)(AudioQueueRef inAQ,
const AudioTimeStamp *inStartTime);
extern OSStatus (*_AudioQueueStop)(AudioQueueRef inAQ,
Boolean inImmediate);
#if ! TARGET_OS_IPHONE
extern OSStatus (*_AudioObjectGetPropertyData)(AudioObjectID inObjectID,
const AudioObjectPropertyAddress *inAddress,
UInt32 inQualifierDataSize,
const void *inQualifierData,
UInt32 *ioDataSize,
void *outData);
#endif
/* no idea what to name this thing, so here it is */
@interface OCTAudioQueue : NSObject
@property (strong, nonatomic, readonly) NSString *deviceID;
@property (copy, nonatomic) void (^sendDataBlock)(void *, OCTToxAVSampleCount, OCTToxAVSampleRate, OCTToxAVChannels);
@property (assign, nonatomic, readonly) BOOL running;
- (instancetype)initWithInputDeviceID:(NSString *)devID error:(NSError **)error;
- (instancetype)initWithOutputDeviceID:(NSString *)devID error:(NSError **)error;
- (TPCircularBuffer *)getBufferPointer;
- (BOOL)updateSampleRate:(OCTToxAVSampleRate)sampleRate numberOfChannels:(OCTToxAVChannels)numberOfChannels error:(NSError **)err;
#if ! TARGET_OS_IPHONE
- (BOOL)setDeviceID:(NSString *)deviceID error:(NSError **)err;
#endif
- (BOOL)begin:(NSError **)error;
- (BOOL)stop:(NSError **)error;
@end

View File

@ -0,0 +1,425 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#import "OCTToxAV.h"
#import "OCTAudioQueue.h"
#import "TPCircularBuffer.h"
#import "OCTLogging.h"
@import AVFoundation;
@import AudioToolbox;
const int kBufferLength = 384000;
const int kNumberOfChannels = 1;
const int kDefaultSampleRate = 48000;
const int kSampleCount_incoming_audio = 1920;
const int kSampleCount_outgoing_audio = (1920 / 2);
const int kBitsPerByte = 8;
const int kFramesPerPacket = 1;
// if you make this too small, the output queue will silently not play,
// but you will still get fill callbacks; it's really weird
const int kFramesPerOutputBuffer_incoming_audio = kSampleCount_incoming_audio / 4;
const int kFramesPerOutputBuffer_outgoing_audio = kSampleCount_outgoing_audio / 4;
const int kBytesPerSample = sizeof(SInt16);
const int kNumberOfAudioQueueBuffers = 8;
OSStatus (*_AudioQueueAllocateBuffer)(AudioQueueRef inAQ,
UInt32 inBufferByteSize,
AudioQueueBufferRef *outBuffer) = AudioQueueAllocateBuffer;
OSStatus (*_AudioQueueDispose)(AudioQueueRef inAQ,
Boolean inImmediate) = AudioQueueDispose;
OSStatus (*_AudioQueueEnqueueBuffer)(AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
UInt32 inNumPacketDescs,
const AudioStreamPacketDescription *inPacketDescs) = AudioQueueEnqueueBuffer;
OSStatus (*_AudioQueueFreeBuffer)(AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer) = AudioQueueFreeBuffer;
OSStatus (*_AudioQueueNewInput)(const AudioStreamBasicDescription *inFormat,
AudioQueueInputCallback inCallbackProc,
void *inUserData,
CFRunLoopRef inCallbackRunLoop,
CFStringRef inCallbackRunLoopMode,
UInt32 inFlags,
AudioQueueRef *outAQ) = AudioQueueNewInput;
OSStatus (*_AudioQueueNewOutput)(const AudioStreamBasicDescription *inFormat,
AudioQueueOutputCallback inCallbackProc,
void *inUserData,
CFRunLoopRef inCallbackRunLoop,
CFStringRef inCallbackRunLoopMode,
UInt32 inFlags,
AudioQueueRef *outAQ) = AudioQueueNewOutput;
OSStatus (*_AudioQueueSetProperty)(AudioQueueRef inAQ,
AudioQueuePropertyID inID,
const void *inData,
UInt32 inDataSize) = AudioQueueSetProperty;
OSStatus (*_AudioQueueStart)(AudioQueueRef inAQ,
const AudioTimeStamp *inStartTime) = AudioQueueStart;
OSStatus (*_AudioQueueStop)(AudioQueueRef inAQ,
Boolean inImmediate) = AudioQueueStop;
#if ! TARGET_OS_IPHONE
OSStatus (*_AudioObjectGetPropertyData)(AudioObjectID inObjectID,
const AudioObjectPropertyAddress *inAddress,
UInt32 inQualifierDataSize,
const void *inQualifierData,
UInt32 *ioDataSize,
void *outData) = AudioObjectGetPropertyData;
#endif
static NSError *OCTErrorFromCoreAudioCode(OSStatus resultCode)
{
return [NSError errorWithDomain:NSOSStatusErrorDomain
code:resultCode
userInfo:@{NSLocalizedDescriptionKey : @"Consult the CoreAudio header files/google for the meaning of the error code."}];
}
#if ! TARGET_OS_IPHONE
static NSString *OCTGetSystemAudioDevice(AudioObjectPropertySelector sel, NSError **err)
{
AudioDeviceID devID = 0;
OSStatus ok = 0;
UInt32 size = sizeof(AudioDeviceID);
AudioObjectPropertyAddress address = {
.mSelector = sel,
.mScope = kAudioObjectPropertyScopeGlobal,
.mElement = kAudioObjectPropertyElementMaster
};
ok = _AudioObjectGetPropertyData(kAudioObjectSystemObject, &address, 0, NULL, &size, &devID);
if (ok != kAudioHardwareNoError) {
OCTLogCCError(@"failed AudioObjectGetPropertyData for system object: %d! Crash may or may not be imminent", ok);
if (err) {
*err = OCTErrorFromCoreAudioCode(ok);
}
return nil;
}
address.mSelector = kAudioDevicePropertyDeviceUID;
CFStringRef unique = NULL;
size = sizeof(unique);
ok = _AudioObjectGetPropertyData(devID, &address, 0, NULL, &size, &unique);
if (ok != kAudioHardwareNoError) {
OCTLogCCError(@"failed AudioObjectGetPropertyData for selected device: %d! Crash may or may not be imminent", ok);
if (err) {
*err = OCTErrorFromCoreAudioCode(ok);
}
return nil;
}
return (__bridge NSString *)unique;
}
#endif
@interface OCTAudioQueue ()
// use this to track what nil means in terms of audio device
@property (assign, nonatomic) BOOL isOutput;
@property (assign, nonatomic) AudioStreamBasicDescription streamFmt;
@property (assign, nonatomic) AudioQueueRef audioQueue;
@property (assign, nonatomic) TPCircularBuffer buffer;
@property (assign, nonatomic) BOOL running;
@end
@implementation OCTAudioQueue
{
AudioQueueBufferRef _AQBuffers[kNumberOfAudioQueueBuffers];
}
- (instancetype)initWithDeviceID:(NSString *)devID isOutput:(BOOL)output error:(NSError **)error
{
#if TARGET_OS_IPHONE
AVAudioSession *session = [AVAudioSession sharedInstance];
_streamFmt.mSampleRate = session.sampleRate;
#else
_streamFmt.mSampleRate = kDefaultSampleRate;
#endif
_streamFmt.mFormatID = kAudioFormatLinearPCM;
_streamFmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
_streamFmt.mChannelsPerFrame = kNumberOfChannels;
_streamFmt.mBytesPerFrame = kBytesPerSample * kNumberOfChannels;
_streamFmt.mBitsPerChannel = kBitsPerByte * kBytesPerSample;
_streamFmt.mFramesPerPacket = kFramesPerPacket;
_streamFmt.mBytesPerPacket = kBytesPerSample * kNumberOfChannels * kFramesPerPacket;
_isOutput = output;
_deviceID = devID;
TPCircularBufferInit(&_buffer, kBufferLength);
OSStatus res = [self createAudioQueue];
if (res != 0) {
if (error) {
*error = OCTErrorFromCoreAudioCode(res);
}
return nil;
}
return self;
}
- (instancetype)initWithInputDeviceID:(NSString *)devID error:(NSError **)error
{
// TOXAUDIO: -outgoing-audio-
return [self initWithDeviceID:devID isOutput:NO error:error];
}
- (instancetype)initWithOutputDeviceID:(NSString *)devID error:(NSError **)error
{
// TOXAUDIO: -incoming-audio-
return [self initWithDeviceID:devID isOutput:YES error:error];
}
- (void)dealloc
{
if (self.running) {
[self stop:nil];
}
if (self.audioQueue) {
_AudioQueueDispose(self.audioQueue, true);
}
TPCircularBufferCleanup(&_buffer);
}
- (OSStatus)createAudioQueue
{
OSStatus err;
if (self.isOutput) {
// TOXAUDIO: -incoming-audio-
err = _AudioQueueNewOutput(&_streamFmt, (void *)&FillOutputBuffer, (__bridge void *)self, NULL, kCFRunLoopCommonModes, 0, &_audioQueue);
}
else {
// TOXAUDIO: -outgoing-audio-
err = _AudioQueueNewInput(&_streamFmt, (void *)&InputAvailable, (__bridge void *)self, NULL, kCFRunLoopCommonModes, 0, &_audioQueue);
}
if (err != 0) {
return err;
}
if (_deviceID) {
err = _AudioQueueSetProperty(self.audioQueue, kAudioQueueProperty_CurrentDevice, &_deviceID, sizeof(CFStringRef));
}
return err;
}
- (BOOL)begin:(NSError **)error
{
OCTLogVerbose(@"begin");
if (! self.audioQueue) {
OSStatus res = [self createAudioQueue];
if (res != 0) {
OCTLogError(@"Can't create the audio queue again after a presumably failed updateSampleRate:... call.");
if (error) {
*error = OCTErrorFromCoreAudioCode(res);
}
return NO;
}
}
for (int i = 0; i < kNumberOfAudioQueueBuffers; ++i) {
if (self.isOutput) {
// TOXAUDIO: -incoming-audio-
_AudioQueueAllocateBuffer(self.audioQueue, kBytesPerSample * kNumberOfChannels * kFramesPerOutputBuffer_incoming_audio, &(_AQBuffers[i]));
} else {
// TOXAUDIO: -outgoing-audio-
_AudioQueueAllocateBuffer(self.audioQueue, kBytesPerSample * kNumberOfChannels * kFramesPerOutputBuffer_outgoing_audio, &(_AQBuffers[i]));
}
_AudioQueueEnqueueBuffer(self.audioQueue, _AQBuffers[i], 0, NULL);
if (self.isOutput) {
// TOXAUDIO: -outgoing-audio-
// For some reason we have to fill it with zero or the callback never gets called.
FillOutputBuffer(self, self.audioQueue, _AQBuffers[i]);
}
}
OCTLogVerbose(@"Allocated buffers; starting now!");
OSStatus res = _AudioQueueStart(self.audioQueue, NULL);
if (res != 0) {
if (error) {
*error = OCTErrorFromCoreAudioCode(res);
}
return NO;
}
self.running = YES;
return YES;
}
- (BOOL)stop:(NSError **)error
{
OCTLogVerbose(@"stop");
OSStatus res = _AudioQueueStop(self.audioQueue, true);
if (res != 0) {
if (error) {
*error = OCTErrorFromCoreAudioCode(res);
}
return NO;
}
for (int i = 0; i < kNumberOfAudioQueueBuffers; ++i) {
_AudioQueueFreeBuffer(self.audioQueue, _AQBuffers[i]);
}
OCTLogVerbose(@"Freed buffers");
self.running = NO;
return YES;
}
- (TPCircularBuffer *)getBufferPointer
{
return &_buffer;
}
- (BOOL)setDeviceID:(NSString *)deviceID error:(NSError **)err
{
#if ! TARGET_OS_IPHONE
if (deviceID == nil) {
OCTLogVerbose(@"using the default device because nil passed to OCTAudioQueue setDeviceID:");
deviceID = OCTGetSystemAudioDevice(self.isOutput ?
kAudioHardwarePropertyDefaultOutputDevice :
kAudioHardwarePropertyDefaultInputDevice, err);
if (! deviceID) {
return NO;
}
}
BOOL needToRestart = self.running;
// we need to pause the queue for a sec
if (needToRestart && ! [self stop:err]) {
return NO;
}
OSStatus ok = _AudioQueueSetProperty(self.audioQueue, kAudioQueueProperty_CurrentDevice, &deviceID, sizeof(CFStringRef));
if (ok != 0) {
OCTLogError(@"setDeviceID: Error while live setting device to '%@': %d", deviceID, ok);
if (err) {
*err = OCTErrorFromCoreAudioCode(ok);
}
}
else {
_deviceID = deviceID;
OCTLogVerbose(@"Successfully set the device id to %@", deviceID);
}
if ((needToRestart && ! [self begin:err]) || (ok != 0)) {
return NO;
}
else {
return YES;
}
#else
return NO;
#endif
}
- (BOOL)updateSampleRate:(OCTToxAVSampleRate)sampleRate numberOfChannels:(OCTToxAVChannels)numberOfChannels error:(NSError **)err
{
OCTLogVerbose(@"updateSampleRate %u, %u", sampleRate, (unsigned int)numberOfChannels);
BOOL needToRestart = self.running;
if (needToRestart && ! [self stop:err]) {
return NO;
}
AudioQueueRef aq = self.audioQueue;
self.audioQueue = nil;
_AudioQueueDispose(aq, true);
_streamFmt.mSampleRate = sampleRate;
_streamFmt.mChannelsPerFrame = numberOfChannels;
_streamFmt.mBytesPerFrame = kBytesPerSample * numberOfChannels;
_streamFmt.mBytesPerPacket = kBytesPerSample * numberOfChannels * kFramesPerPacket;
OSStatus res = [self createAudioQueue];
if (res != 0) {
OCTLogError(@"oops, could not recreate the audio queue: %d after samplerate/nc change. enjoy your overflowing buffer", (int)res);
if (err) {
*err = OCTErrorFromCoreAudioCode(res);
}
return NO;
}
else if (needToRestart) {
return [self begin:err];
}
else {
return YES;
}
}
// avoid annoying bridge cast in 1st param!
static void InputAvailable(OCTAudioQueue *__unsafe_unretained context,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp *inStartTime,
UInt32 inNumPackets,
const AudioStreamPacketDescription *inPacketDesc)
{
TPCircularBufferProduceBytes(&(context->_buffer),
inBuffer->mAudioData,
inBuffer->mAudioDataByteSize);
int32_t availableBytesToConsume;
void *tail = TPCircularBufferTail(&context->_buffer, &availableBytesToConsume);
// TOXAUDIO: -outgoing-audio-
int32_t minimalBytesToConsume = kSampleCount_outgoing_audio * kNumberOfChannels * sizeof(SInt16);
if (context.isOutput) {
// TOXAUDIO: -incoming-audio-
minimalBytesToConsume = kSampleCount_incoming_audio * kNumberOfChannels * sizeof(SInt16);
}
int32_t cyclesToConsume = availableBytesToConsume / minimalBytesToConsume;
for (int32_t i = 0; i < cyclesToConsume; i++) {
if (context.isOutput) {
// TOXAUDIO: -incoming-audio-
context.sendDataBlock(tail, kSampleCount_incoming_audio, context.streamFmt.mSampleRate, kNumberOfChannels);
} else {
// TOXAUDIO: -outgoing-audio-
context.sendDataBlock(tail, kSampleCount_outgoing_audio, context.streamFmt.mSampleRate, kNumberOfChannels);
}
TPCircularBufferConsume(&context->_buffer, minimalBytesToConsume);
tail = TPCircularBufferTail(&context->_buffer, &availableBytesToConsume);
}
_AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
}
static void FillOutputBuffer(OCTAudioQueue *__unsafe_unretained context,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer)
{
int32_t targetBufferSize = inBuffer->mAudioDataBytesCapacity;
SInt16 *targetBuffer = inBuffer->mAudioData;
int32_t availableBytes;
SInt16 *buffer = TPCircularBufferTail(&context->_buffer, &availableBytes);
if (buffer) {
uint32_t cpy = MIN(availableBytes, targetBufferSize);
memcpy(targetBuffer, buffer, cpy);
TPCircularBufferConsume(&context->_buffer, cpy);
if (cpy != targetBufferSize) {
memset(targetBuffer + cpy, 0, targetBufferSize - cpy);
OCTLogCCWarn(@"warning not enough frames!!!");
}
inBuffer->mAudioDataByteSize = targetBufferSize;
}
else {
memset(targetBuffer, 0, targetBufferSize);
inBuffer->mAudioDataByteSize = targetBufferSize;
}
_AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, NULL);
}
@end