mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-27 10:10:57 +00:00
13604b047f
for now)
393 lines
9.0 KiB
Objective-C
393 lines
9.0 KiB
Objective-C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2019 - Stuart Carnie
|
|
*
|
|
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with RetroArch.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#import <Foundation/Foundation.h>
|
|
#import <AudioToolbox/AudioToolbox.h>
|
|
#import <AVFoundation/AVFoundation.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdatomic.h>
|
|
#include <stdlib.h>
|
|
#include <memory.h>
|
|
|
|
#include "../../retroarch.h"
|
|
|
|
#pragma mark - ringbuffer
|
|
|
|
typedef struct ringbuffer
|
|
{
|
|
float *buffer;
|
|
size_t cap;
|
|
size_t write_ptr;
|
|
size_t read_ptr;
|
|
atomic_int len;
|
|
} ringbuffer_t;
|
|
|
|
typedef ringbuffer_t * ringbuffer_h;
|
|
|
|
static inline size_t rb_len(ringbuffer_h r)
|
|
{
|
|
return atomic_load_explicit(&r->len, memory_order_relaxed);
|
|
}
|
|
|
|
static inline size_t rb_cap(ringbuffer_h r)
|
|
{
|
|
return (r->read_ptr + r->cap - r->write_ptr) % r->cap;
|
|
}
|
|
|
|
static inline size_t rb_avail(ringbuffer_h r)
|
|
{
|
|
return r->cap - rb_len(r);
|
|
}
|
|
|
|
static inline void rb_advance_write(ringbuffer_h r)
|
|
{
|
|
r->write_ptr = (r->write_ptr + 1) % r->cap;
|
|
}
|
|
|
|
static inline void rb_advance_write_n(ringbuffer_h r, size_t n)
|
|
{
|
|
r->write_ptr = (r->write_ptr + n) % r->cap;
|
|
}
|
|
|
|
static inline void rb_advance_read(ringbuffer_h r)
|
|
{
|
|
r->read_ptr = (r->read_ptr + 1) % r->cap;
|
|
}
|
|
|
|
static inline void rb_len_add(ringbuffer_h r, int n)
|
|
{
|
|
atomic_fetch_add(&r->len, n);
|
|
}
|
|
|
|
static inline void rb_len_sub(ringbuffer_h r, int n)
|
|
{
|
|
atomic_fetch_sub(&r->len, n);
|
|
}
|
|
|
|
static void rb_init(ringbuffer_h r, size_t cap)
|
|
{
|
|
r->buffer = malloc(cap * sizeof(float));
|
|
r->cap = cap;
|
|
atomic_init(&r->len, 0);
|
|
r->write_ptr = 0;
|
|
r->read_ptr = 0;
|
|
}
|
|
|
|
static void rb_free(ringbuffer_h r)
|
|
{
|
|
free(r->buffer);
|
|
memset(r, 0, sizeof(*r));
|
|
}
|
|
|
|
#define UNLIKELY(x) __builtin_expect((x), 0)
|
|
#define LIKELY(x) __builtin_expect((x), 1)
|
|
|
|
static void rb_write_data(ringbuffer_h r, const float *data, size_t len)
|
|
{
|
|
size_t avail = rb_avail(r);
|
|
size_t n = MIN(len, avail);
|
|
size_t first_write = n;
|
|
size_t rest_write = 0;
|
|
|
|
if (r->write_ptr + n > r->cap)
|
|
{
|
|
first_write = r->cap - r->write_ptr;
|
|
rest_write = n - first_write;
|
|
}
|
|
|
|
memcpy(r->buffer + r->write_ptr, data, first_write*sizeof(float));
|
|
memcpy(r->buffer, data + first_write, rest_write*sizeof(float));
|
|
|
|
rb_advance_write_n(r, n);
|
|
rb_len_add(r, (int)n);
|
|
}
|
|
|
|
static void rb_read_data(ringbuffer_h r,
|
|
float *d0, float *d1, size_t len)
|
|
{
|
|
size_t need = len * 2;
|
|
|
|
do {
|
|
size_t have = rb_len(r);
|
|
size_t n = MIN(have, need);
|
|
int i = 0;
|
|
for (; i < n/2; i++)
|
|
{
|
|
d0[i] = r->buffer[r->read_ptr];
|
|
rb_advance_read(r);
|
|
d1[i] = r->buffer[r->read_ptr];
|
|
rb_advance_read(r);
|
|
}
|
|
|
|
need -= n;
|
|
rb_len_sub(r, (int)n);
|
|
|
|
if (UNLIKELY(need > 0))
|
|
{
|
|
const float quiet = 0.0f;
|
|
size_t fill;
|
|
|
|
/* we got more data */
|
|
if (rb_len(r) > 0)
|
|
continue;
|
|
|
|
/* underflow */
|
|
fill = (need/2)*sizeof(float);
|
|
memset_pattern4(&d0[i], &quiet, fill);
|
|
memset_pattern4(&d1[i], &quiet, fill);
|
|
}
|
|
} while (0);
|
|
}
|
|
|
|
#pragma mark - CoreAudio3
|
|
|
|
static bool g_interrupted;
|
|
|
|
@interface CoreAudio3 : NSObject {
|
|
ringbuffer_t _rb;
|
|
dispatch_semaphore_t _sema;
|
|
AUAudioUnit *_au;
|
|
size_t _bufferSize;
|
|
BOOL _nonBlock;
|
|
}
|
|
|
|
@property (nonatomic, readwrite) BOOL nonBlock;
|
|
@property (nonatomic, readonly) BOOL paused;
|
|
@property (nonatomic, readonly) size_t writeAvailableInBytes;
|
|
@property (nonatomic, readonly) size_t bufferSizeInBytes;
|
|
|
|
- (instancetype)initWithRate:(NSUInteger)rate
|
|
latency:(NSUInteger)latency;
|
|
- (ssize_t)writeFloat:(const float *)data samples:(size_t)samples;
|
|
- (void)start;
|
|
- (void)stop;
|
|
|
|
@end
|
|
|
|
@implementation CoreAudio3
|
|
|
|
- (instancetype)initWithRate:(NSUInteger)rate
|
|
latency:(NSUInteger)latency {
|
|
if (self = [super init])
|
|
{
|
|
NSError *err;
|
|
AUAudioUnit *au;
|
|
AudioComponentDescription desc;
|
|
AVAudioFormat *format, *renderFormat;
|
|
|
|
_sema = dispatch_semaphore_create(0);
|
|
|
|
_bufferSize = (latency * rate) / 1000;
|
|
_bufferSize *= 2; /* stereo */
|
|
rb_init(&_rb, _bufferSize);
|
|
|
|
desc.componentType = kAudioUnitType_Output;
|
|
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
|
|
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
|
|
|
au = [[AUAudioUnit alloc] initWithComponentDescription:desc error:&err];
|
|
if (err != nil)
|
|
return nil;
|
|
|
|
format = au.outputBusses[0].format;
|
|
if (format.channelCount != 2)
|
|
return nil;
|
|
|
|
renderFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:rate channels:2];
|
|
[au.inputBusses[0] setFormat:renderFormat error:&err];
|
|
if (err != nil)
|
|
return nil;
|
|
|
|
ringbuffer_h rb = &_rb;
|
|
__block dispatch_semaphore_t sema = _sema;
|
|
au.outputProvider = ^AUAudioUnitStatus(AudioUnitRenderActionFlags * actionFlags, const AudioTimeStamp * timestamp, AUAudioFrameCount frameCount, NSInteger inputBusNumber, AudioBufferList * inputData)
|
|
{
|
|
rb_read_data(rb, inputData->mBuffers[0].mData, inputData->mBuffers[1].mData, frameCount);
|
|
dispatch_semaphore_signal(sema);
|
|
return 0;
|
|
};
|
|
|
|
[au allocateRenderResourcesAndReturnError:&err];
|
|
if (err != nil)
|
|
return nil;
|
|
|
|
_au = au;
|
|
|
|
RARCH_LOG("[CoreAudio3]: Using buffer size of %u bytes: (latency = %u ms)\n", (unsigned)self.bufferSizeInBytes, latency);
|
|
|
|
[self start];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
rb_free(&_rb);
|
|
}
|
|
|
|
- (BOOL)paused {
|
|
return !_au.running;
|
|
}
|
|
|
|
- (size_t)bufferSizeInBytes {
|
|
return _bufferSize * sizeof(float);
|
|
}
|
|
|
|
- (size_t)writeAvailableInBytes {
|
|
return rb_avail(&_rb) * sizeof(float);
|
|
}
|
|
|
|
- (void)start {
|
|
NSError *err;
|
|
[_au startHardwareAndReturnError:&err];
|
|
}
|
|
|
|
- (void)stop {
|
|
[_au stopHardware];
|
|
}
|
|
|
|
- (ssize_t)writeFloat:(const float *)data samples:(size_t)samples {
|
|
size_t written = 0;
|
|
while (!g_interrupted && samples > 0)
|
|
{
|
|
size_t write_avail = rb_avail(&_rb);
|
|
if (write_avail > samples)
|
|
write_avail = samples;
|
|
|
|
rb_write_data(&_rb, data, write_avail);
|
|
data += write_avail;
|
|
written += write_avail;
|
|
samples -= write_avail;
|
|
|
|
if (_nonBlock)
|
|
break;
|
|
|
|
if (write_avail == 0)
|
|
dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER);
|
|
}
|
|
|
|
return written;
|
|
}
|
|
|
|
@end
|
|
|
|
static void coreaudio3_free(void *data)
|
|
{
|
|
CoreAudio3 *dev = (__bridge_transfer CoreAudio3 *)data;
|
|
if (dev == nil)
|
|
return;
|
|
|
|
[dev stop];
|
|
dev = nil;
|
|
}
|
|
|
|
static void *coreaudio3_init(const char *device,
|
|
unsigned rate, unsigned latency,
|
|
unsigned block_frames,
|
|
unsigned *new_rate)
|
|
{
|
|
CoreAudio3 *dev = [[CoreAudio3 alloc] initWithRate:rate
|
|
latency:latency];
|
|
|
|
*new_rate = rate;
|
|
|
|
return (__bridge_retained void *)dev;
|
|
}
|
|
|
|
static ssize_t coreaudio3_write(void *data,
|
|
const void *buf_, size_t size)
|
|
{
|
|
CoreAudio3 *dev = (__bridge CoreAudio3 *)data;
|
|
return [dev writeFloat:(const float *)
|
|
buf_ samples:size/sizeof(float)] * sizeof(float);
|
|
}
|
|
|
|
static void coreaudio3_set_nonblock_state(void *data, bool state)
|
|
{
|
|
CoreAudio3 *dev = (__bridge CoreAudio3 *)data;
|
|
if (dev == nil)
|
|
return;
|
|
|
|
dev.nonBlock = state;
|
|
}
|
|
|
|
static bool coreaudio3_alive(void *data)
|
|
{
|
|
CoreAudio3 *dev = (__bridge CoreAudio3 *)data;
|
|
if (dev == nil)
|
|
return NO;
|
|
|
|
return !dev.paused;
|
|
}
|
|
|
|
static bool coreaudio3_stop(void *data)
|
|
{
|
|
CoreAudio3 *dev = (__bridge CoreAudio3 *)data;
|
|
if (dev == nil)
|
|
return NO;
|
|
|
|
[dev stop];
|
|
return dev.paused;
|
|
}
|
|
|
|
static bool coreaudio3_start(void *data, bool is_shutdown)
|
|
{
|
|
CoreAudio3 *dev = (__bridge CoreAudio3 *)data;
|
|
if (dev == nil)
|
|
return NO;
|
|
|
|
[dev start];
|
|
return !dev.paused;
|
|
}
|
|
|
|
static bool coreaudio3_use_float(void *data)
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
static size_t coreaudio3_write_avail(void *data)
|
|
{
|
|
CoreAudio3 *dev = (__bridge CoreAudio3 *)data;
|
|
if (dev == nil)
|
|
return 0;
|
|
|
|
return dev.writeAvailableInBytes;
|
|
}
|
|
|
|
static size_t coreaudio3_buffer_size(void *data)
|
|
{
|
|
CoreAudio3 *dev = (__bridge CoreAudio3 *)data;
|
|
if (dev == nil)
|
|
return 0;
|
|
|
|
return dev.bufferSizeInBytes;
|
|
}
|
|
|
|
audio_driver_t audio_coreaudio3 = {
|
|
coreaudio3_init,
|
|
coreaudio3_write,
|
|
coreaudio3_stop,
|
|
coreaudio3_start,
|
|
coreaudio3_alive,
|
|
coreaudio3_set_nonblock_state,
|
|
coreaudio3_free,
|
|
coreaudio3_use_float,
|
|
"coreaudio3",
|
|
NULL, /* device_list_new */
|
|
NULL, /* device_list_free */
|
|
coreaudio3_write_avail,
|
|
coreaudio3_buffer_size,
|
|
};
|