Files
OpenEmuKit/Source/OEAudioUnit.mm
2020-11-16 06:11:16 +11:00

269 lines
10 KiB
Plaintext

// Copyright (c) 2019, OpenEmu Team
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the OpenEmu Team nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY OpenEmu Team ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL OpenEmu Team BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <AudioUnit/AudioUnit.h>
#import <AVFoundation/AVFoundation.h>
#include <algorithm>
#import "OEAudioUnit.h"
#import "OELogging.h"
typedef struct {
AURenderPullInputBlock pullInput;
AudioTimeStamp const *timestamp;
void **buffer;
UInt32 *bufferSizeFrames;
UInt32 *bytesPerFrame;
} inputData;
static OSStatus audioConverterComplexInputDataProc(AudioConverterRef inAudioConverter, AVAudioPacketCount * ioNumberDataPackets, AudioBufferList * ioData, AudioStreamPacketDescription * __nullable * __nullable ioDataPacketDescription, void * inUserData)
{
inputData *inp = (inputData *)inUserData;
AudioUnitRenderActionFlags pullFlags = 0;
ioData->mBuffers[0].mData = *inp->buffer;
ioData->mBuffers[0].mDataByteSize = (*inp->bufferSizeFrames) * (*inp->bytesPerFrame);
/* cap the bytes we return to the amount of bytes available to guard
* against core audio requesting more bytes that they fit in the buffer
* EVEN THOUGH THE BUFFER IS ALREADY LARGER THAN MAXIMUMFRAMESTORENDER */
*ioNumberDataPackets = std::min(*inp->bufferSizeFrames, *ioNumberDataPackets);
return inp->pullInput(&pullFlags, inp->timestamp, *ioNumberDataPackets, 0, ioData);
}
@interface CustomBus: AUAudioUnitBus
@end
@interface OEAudioUnit () {
AudioConverterRef _conv;
void *_convBuffer;
UInt32 _convInputFrameCount;
UInt32 _convInputBytePerFrame;
}
@property (nonatomic, readonly) BOOL requiresConversion;
@property AUAudioUnitBus *inputBus;
@property AUAudioUnitBusArray *inputBusArray;
@property AUAudioUnitBus *outputBus;
@property AUAudioUnitBusArray *outputBusArray;
@end
@implementation OEAudioUnit
+ (void)registerSelf {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
AudioComponentDescription desc = {
.componentType = kAudioUnitType_Generator,
.componentSubType = kAudioUnitSubType_Emulator,
.componentManufacturer = kAudioUnitManufacturer_OpenEmu,
.componentFlags = 0,
.componentFlagsMask = 0,
};
[AUAudioUnit registerSubclass: self.class
asComponentDescription: desc
name: @"OEAudioUnit"
version: UINT32_MAX];
});
}
- (instancetype)initWithComponentDescription:(AudioComponentDescription)componentDescription options:(AudioComponentInstantiationOptions)options error:(NSError **)outError {
self = [super initWithComponentDescription:componentDescription options:options error:outError];
if (self == nil) {
return nil;
}
// Initialize a default format for the busses. It doesn't matter what you put here.
AVAudioFormat *defaultFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:48000 channels:2];
_inputBus = [[CustomBus alloc] initWithFormat:defaultFormat error:nil];
_inputBusArray = [[AUAudioUnitBusArray alloc] initWithAudioUnit:self
busType:AUAudioUnitBusTypeInput
busses:@[_inputBus]];
_outputBus = [[AUAudioUnitBus alloc] initWithFormat:defaultFormat error:nil];
_outputBusArray = [[AUAudioUnitBusArray alloc] initWithAudioUnit:self
busType:AUAudioUnitBusTypeOutput
busses: @[_outputBus]];
self.maximumFramesToRender = 512;
return self;
}
- (void)dealloc {
[self _freeResources];
}
#pragma mark - AUAudioUnit (Overrides)
- (AUAudioUnitBusArray *)inputBusses {
return _inputBusArray;
}
- (AUAudioUnitBusArray *)outputBusses {
return _outputBusArray;
}
- (BOOL)requiresConversion {
return ![_inputBus.format isEqual:_outputBus.format];
}
- (BOOL)allocateRenderResourcesAndReturnError:(NSError *__autoreleasing _Nullable *)outError {
if (![super allocateRenderResourcesAndReturnError:outError]) {
return NO;
}
if (!self.requiresConversion) {
return YES;
}
AudioStreamBasicDescription const *srcDesc = _inputBus.format.streamDescription;
AudioStreamBasicDescription const *dstDesc = _outputBus.format.streamDescription;
OSStatus status = AudioConverterNew(srcDesc, dstDesc, &_conv);
if (status != noErr) {
os_log_error(OE_LOG_AUDIO, "Unable to create audio converter: %d", status);
return NO;
}
/* 64 bytes of padding above self.maximumFramesToRender because
* CoreAudio is stupid and likes to request more bytes than the maximum
* even though IT TAKES CARE TO SET THE MAXIMUM VALUE ITSELF! */
_convInputFrameCount = (UInt32)self.maximumFramesToRender + 64;
_convInputBytePerFrame = (UInt32)srcDesc->mBytesPerFrame;
UInt32 bufferSize = _convInputFrameCount * _convInputBytePerFrame;
_convBuffer = malloc(_convInputFrameCount * _convInputBytePerFrame);
os_log_info(OE_LOG_AUDIO, "Audio converter buffer size = %{public}u bytes", bufferSize);
return YES;
}
- (void)deallocateRenderResources {
[super deallocateRenderResources];
[self _freeResources];
}
- (void)_freeResources {
_convInputFrameCount = 0;
if (_convBuffer) {
free(_convBuffer);
_convBuffer = nil;
}
if (_conv) {
AudioConverterDispose(_conv);
_conv = nil;
}
}
#pragma mark - AUAudioUnit (AUAudioUnitImplementation)
- (AUInternalRenderBlock)internalRenderBlock {
AURenderPullInputBlock pullInput = _outputProvider;
if (self.requiresConversion) {
AudioConverterRef *conv = &_conv;
__block inputData data = {
.buffer = &_convBuffer,
.bufferSizeFrames = &_convInputFrameCount,
.bytesPerFrame = &_convInputBytePerFrame,
.pullInput = _outputProvider,
};
return ^AUAudioUnitStatus(
AudioUnitRenderActionFlags *actionFlags,
const AudioTimeStamp *timestamp,
AVAudioFrameCount frameCount,
NSInteger outputBusNumber,
AudioBufferList *outputData,
const AURenderEvent *realtimeEventListHead,
AURenderPullInputBlock pullInputBlock) {
if (pullInput == nil) {
return kAudioUnitErr_NoConnection;
}
data.timestamp = timestamp;
UInt32 packetSize = frameCount;
OSStatus res = AudioConverterFillComplexBuffer(*conv,
audioConverterComplexInputDataProc,
(void *)&data,
&packetSize,
outputData,
nil);
return res;
};
}
return ^AUAudioUnitStatus(
AudioUnitRenderActionFlags *actionFlags,
const AudioTimeStamp *timestamp,
AVAudioFrameCount frameCount,
NSInteger outputBusNumber,
AudioBufferList *outputData,
const AURenderEvent *realtimeEventListHead,
AURenderPullInputBlock pullInputBlock) {
if (pullInput == nil) {
return kAudioUnitErr_NoConnection;
}
AudioUnitRenderActionFlags pullFlags = 0;
return pullInput(&pullFlags, timestamp, frameCount, 0, outputData);
};
}
@end
@implementation CustomBus {
AVAudioFormat *_format;
}
- (BOOL)setFormat:(AVAudioFormat *)format error:(NSError *__autoreleasing _Nullable *)outError {
if (outError) {
*outError = nil;
}
if ([_format isEqual:format]) {
return YES;
}
[self willChangeValueForKey:@"format"];
_format = format;
[self didChangeValueForKey:@"format"];
return YES;
}
- (AVAudioFormat *)format {
return _format;
}
@end