session-ios/Signal/src/audio/incall_audio/RemoteIOAudio.m

345 lines
14 KiB
Objective-C

#import "AppAudioManager.h"
#import "Environment.h"
#import "Constraints.h"
#import "RemoteIOAudio.h"
#import "ThreadManager.h"
#import "Util.h"
#define INPUT_BUS 1
#define OUTPUT_BUS 0
#define INITIAL_NUMBER_OF_BUFFERS 100
#define SAMPLE_SIZE_IN_BYTES 2
#define BUFFER_SIZE 8000
#define FLAG_MUTED 1
#define FLAG_UNMUTED 0
@implementation RemoteIOAudio
@synthesize playbackQueue, recordingQueue, rioAudioUnit, state;
static bool doesActiveInstanceExist;
+(RemoteIOAudio*) remoteIOInterfaceStartedWithDelegate:(id<AudioCallbackHandler>)delegateIn untilCancelled:(id<CancelToken>)untilCancelledToken {
checkOperationDescribe(!doesActiveInstanceExist, @"Only one RemoteIOInterfance instance can exist at a time. Adding more will break previous instances.");
doesActiveInstanceExist = true;
RemoteIOAudio* newRemoteIoInterface = [RemoteIOAudio new];
newRemoteIoInterface->starveLogger = [[Environment logging] getOccurrenceLoggerForSender:newRemoteIoInterface withKey:@"starve"];
newRemoteIoInterface->conditionLogger = [[Environment logging] getConditionLoggerForSender:newRemoteIoInterface];
newRemoteIoInterface->recordingQueue = [CyclicalBuffer new];
newRemoteIoInterface->playbackQueue = [CyclicalBuffer new];
newRemoteIoInterface->unusedBuffers = [NSMutableSet set];
newRemoteIoInterface->state = NOT_STARTED;
newRemoteIoInterface->playbackBufferSizeLogger = [[Environment logging] getValueLoggerForValue:@"|playback queue|" from:newRemoteIoInterface];
newRemoteIoInterface->recordingQueueSizeLogger = [[Environment logging] getValueLoggerForValue:@"|recording queue|" from:newRemoteIoInterface];
while ([newRemoteIoInterface->unusedBuffers count] < INITIAL_NUMBER_OF_BUFFERS) {
[newRemoteIoInterface addUnusedBuffer];
}
[newRemoteIoInterface setupAudio];
[newRemoteIoInterface startWithDelegate:delegateIn untilCancelled:untilCancelledToken];
return newRemoteIoInterface;
}
-(void)setupAudio {
[[AppAudioManager sharedInstance] requestRecordingPrivlege];
rioAudioUnit = [self makeAudioUnit];
[self setAudioEnabled];
[self setAudioStreamFormat];
[self setAudioCallbacks];
[self unsetAudioShouldAllocateBuffer];
[self checkDone:AudioUnitInitialize(rioAudioUnit)];
}
-(AudioUnit)makeAudioUnit {
AudioComponentDescription audioUnitDescription = [self makeAudioComponentDescription];
AudioComponent component = AudioComponentFindNext(NULL, &audioUnitDescription);
AudioUnit unit;
[self checkDone:AudioComponentInstanceNew(component, &unit)];
return unit;
}
-(AudioComponentDescription) makeAudioComponentDescription {
AudioComponentDescription d;
d.componentType = kAudioUnitType_Output;
d.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
d.componentManufacturer = kAudioUnitManufacturer_Apple;
d.componentFlags = 0;
d.componentFlagsMask = 0;
return d;
}
-(void)setAudioEnabled {
const UInt32 enable = 1;
[self checkDone:AudioUnitSetProperty(rioAudioUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
INPUT_BUS,
&enable,
sizeof(enable))];
[self checkDone:AudioUnitSetProperty(rioAudioUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output,
OUTPUT_BUS,
&enable,
sizeof(enable))];
}
-(void)setAudioStreamFormat {
const AudioStreamBasicDescription streamDesc = [self makeAudioStreamBasicDescription];
[self checkDone:AudioUnitSetProperty(rioAudioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
OUTPUT_BUS,
&streamDesc,
sizeof(streamDesc))];
[self checkDone:AudioUnitSetProperty(rioAudioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
INPUT_BUS,
&streamDesc,
sizeof(streamDesc))];
}
-(AudioStreamBasicDescription) makeAudioStreamBasicDescription {
const UInt32 framesPerPacket = 1;
AudioStreamBasicDescription d;
d.mSampleRate = SAMPLE_RATE;
d.mFormatID = kAudioFormatLinearPCM;
d.mFramesPerPacket = framesPerPacket;
d.mChannelsPerFrame = 1;
d.mBitsPerChannel = 16;
d.mBytesPerPacket = SAMPLE_SIZE_IN_BYTES;
d.mBytesPerFrame = framesPerPacket*SAMPLE_SIZE_IN_BYTES;
d.mReserved = 0;
d.mFormatFlags = kAudioFormatFlagIsSignedInteger
| kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked;
return d;
}
-(void)setAudioCallbacks {
const AURenderCallbackStruct recordingCallbackStruct = {recordingCallback, (__bridge void *)(self)};
[self checkDone:AudioUnitSetProperty(rioAudioUnit,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global,
INPUT_BUS,
&recordingCallbackStruct,
sizeof(recordingCallbackStruct))];
const AURenderCallbackStruct playbackCallbackStruct = {playbackCallback, (__bridge void *)(self)};
[self checkDone:AudioUnitSetProperty(rioAudioUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Global,
OUTPUT_BUS,
&playbackCallbackStruct,
sizeof(playbackCallbackStruct))];
}
-(void)unsetAudioShouldAllocateBuffer {
const UInt32 shouldAllocateBuffer = 0;
[self checkDone:AudioUnitSetProperty(rioAudioUnit,
kAudioUnitProperty_ShouldAllocateBuffer,
kAudioUnitScope_Output,
INPUT_BUS,
&shouldAllocateBuffer,
sizeof(shouldAllocateBuffer))];
}
-(RemoteIOBufferListWrapper*) addUnusedBuffer {
RemoteIOBufferListWrapper* buf = [RemoteIOBufferListWrapper remoteIOBufferListWithMonoBufferSize:BUFFER_SIZE];
[unusedBuffers addObject:buf];
return buf;
}
-(RemoteIOBufferListWrapper*) tryTakeUnusedBuffer {
RemoteIOBufferListWrapper* buffer = (RemoteIOBufferListWrapper*)[unusedBuffers anyObject];
if (buffer == nil) return nil;
[unusedBuffers removeObject:buffer];
return buffer;
}
-(void) returnUsedBuffer:(RemoteIOBufferListWrapper*)buffer {
require(buffer != nil);
if (state == TERMINATED) return; // in case a buffer was in use as termination occurred
[unusedBuffers addObject:buffer];
}
-(void) startWithDelegate:(id<AudioCallbackHandler>)delegateIn untilCancelled:(id<CancelToken>)untilCancelledToken {
require(delegateIn != nil);
@synchronized(self){
requireState(state == NOT_STARTED);
delegate = delegateIn;
[self checkDone:AudioOutputUnitStart(rioAudioUnit)];
state = STARTED;
}
[untilCancelledToken whenCancelled:^{
@synchronized(self) {
state = TERMINATED;
doesActiveInstanceExist = false;
[self checkDone:AudioOutputUnitStop(rioAudioUnit)];
[[AppAudioManager sharedInstance] releaseRecordingPrivlege];
[unusedBuffers removeAllObjects];
}
}];
}
static OSStatus recordingCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberSamples,
AudioBufferList *ioData) {
@autoreleasepool {
RemoteIOAudio *instance = (__bridge RemoteIOAudio*)inRefCon;
RemoteIOBufferListWrapper* buffer;
@synchronized(instance) {
buffer = [instance tryTakeUnusedBuffer];
}
if (buffer == nil) {
// All buffers in use. Drop recorded audio.
return 1; // arbitrary error code
}
AudioBufferList bufferList = *[buffer audioBufferList];
[instance checkDone:AudioUnitRender([instance rioAudioUnit],
ioActionFlags,
inTimeStamp,
inBusNumber,
inNumberSamples,
&bufferList)];
buffer.sampleCount = inNumberSamples;
[instance performSelector:@selector(onRecordedDataIntoBuffer:)
onThread:[ThreadManager lowLatencyThread]
withObject:buffer
waitUntilDone:NO];
}
return noErr;
}
-(void) onRecordedDataIntoBuffer:(RemoteIOBufferListWrapper*)buffer {
@synchronized(self){
if (state == TERMINATED) return;
NSData* recordedAudioVolatile = [NSData dataWithBytesNoCopy:[buffer audioBufferList]->mBuffers[0].mData
length:[buffer sampleCount]*SAMPLE_SIZE_IN_BYTES
freeWhenDone:NO];
[recordingQueue enqueueData:recordedAudioVolatile];
[self returnUsedBuffer:buffer];
}
[recordingQueueSizeLogger logValue:[recordingQueue enqueuedLength]];
[delegate handleNewDataRecorded:recordingQueue];
}
-(void)populatePlaybackQueueWithData:(NSData*)data {
require(data != nil);
if ([data length] == 0) return;
@synchronized(self){
[playbackQueue enqueueData:data];
}
}
static OSStatus playbackCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberSamples,
AudioBufferList *ioData) {
RemoteIOAudio* instance = (__bridge RemoteIOAudio*)inRefCon;
NSUInteger requestedByteCount = inNumberSamples * SAMPLE_SIZE_IN_BYTES;
NSUInteger availableByteCount;
@synchronized(instance) {
availableByteCount = [[instance playbackQueue] enqueuedLength];
if (availableByteCount < requestedByteCount) {
NSUInteger starveAmount = requestedByteCount - availableByteCount;
[instance->starveLogger markOccurrence:@(starveAmount)];
} else {
NSData* audioToCopyVolatile = [[instance playbackQueue] dequeuePotentialyVolatileDataWithLength:requestedByteCount];
memcpy(ioData->mBuffers[0].mData, [audioToCopyVolatile bytes], [audioToCopyVolatile length]);
}
}
[Operation asyncRun:^{[instance onRequestedPlaybackDataAmount:requestedByteCount
andHadAvailableAmount:availableByteCount];}
onThread:[ThreadManager lowLatencyThread]];
if (availableByteCount < requestedByteCount) {
return 1; // arbitrary error code
}
return noErr;
}
-(void) onRequestedPlaybackDataAmount:(NSUInteger)requestedByteCount andHadAvailableAmount:(NSUInteger)availableByteCount {
@synchronized(self) {
if (state == TERMINATED) return;
}
NSUInteger consumedByteCount = availableByteCount >= requestedByteCount ? requestedByteCount : 0;
NSUInteger remainingByteCount = availableByteCount - consumedByteCount;
[playbackBufferSizeLogger logValue:remainingByteCount];
[delegate handlePlaybackOccurredWithBytesRequested:requestedByteCount andBytesRemaining:remainingByteCount];
}
-(void) dealloc{
if (state != TERMINATED) {
doesActiveInstanceExist = false;
}
}
-(NSUInteger)getSampleRateInHertz {
return SAMPLE_RATE;
}
-(void)checkDone:(OSStatus)resultCode {
if (resultCode == kAudioSessionNoError) return;
NSString* failure;
if (resultCode == kAudioServicesUnsupportedPropertyError) {
failure = @"unsupportedPropertyError";
} else if (resultCode == kAudioServicesBadPropertySizeError) {
failure = @"badPropertySizeError";
} else if (resultCode == kAudioServicesBadSpecifierSizeError) {
failure = @"badSpecifierSizeError";
} else if (resultCode == kAudioServicesSystemSoundUnspecifiedError) {
failure = @"systemSoundUnspecifiedError";
} else if (resultCode == kAudioServicesSystemSoundClientTimedOutError) {
failure = @"systemSoundClientTimedOutError";
} else if (resultCode == errSecParam){
failure = @"oneOrMoreNonValidParameter";
}else {
failure = [@(resultCode) description];
}
[conditionLogger logError:[NSString stringWithFormat:@"StatusCheck failed: %@", failure]];
}
-(bool) isAudioMuted {
UInt32 currentMuteFlag;
UInt32 propertyByteSize;
[self checkDone:AudioUnitGetProperty(rioAudioUnit,
kAUVoiceIOProperty_MuteOutput,
kAudioUnitScope_Global,
OUTPUT_BUS,
&currentMuteFlag,
&propertyByteSize)];
return (FLAG_MUTED == currentMuteFlag);
}
-(BOOL) toggleMute {
BOOL shouldBeMuted = ![self isAudioMuted];
UInt32 newValue = shouldBeMuted ? FLAG_MUTED : FLAG_UNMUTED;
[self checkDone:AudioUnitSetProperty(rioAudioUnit,
kAUVoiceIOProperty_MuteOutput,
kAudioUnitScope_Global,
OUTPUT_BUS,
&newValue,
sizeof(newValue))];
return shouldBeMuted;
}
@end