#if defined(SOKOL_IMPL) && !defined(SOKOL_AUDIO_IMPL)
#define SOKOL_AUDIO_IMPL
#endif
#ifndef SOKOL_AUDIO_INCLUDED
#define SOKOL_AUDIO_INCLUDED (1)
#include <stdint.h>
#include <stdbool.h>
#if defined(SOKOL_API_DECL) && !defined(SOKOL_AUDIO_API_DECL)
#define SOKOL_AUDIO_API_DECL SOKOL_API_DECL
#endif
#ifndef SOKOL_AUDIO_API_DECL
#if defined(_WIN32) && defined(SOKOL_DLL) && defined(SOKOL_AUDIO_IMPL)
#define SOKOL_AUDIO_API_DECL __declspec(dllexport)
#elif defined(_WIN32) && defined(SOKOL_DLL)
#define SOKOL_AUDIO_API_DECL __declspec(dllimport)
#else
#define SOKOL_AUDIO_API_DECL extern
#endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef struct saudio_desc {
int sample_rate;
int num_channels;
int buffer_frames;
int packet_frames;
int num_packets;
void (*stream_cb)(float* buffer, int num_frames, int num_channels);
void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data);
void* user_data;
} saudio_desc;
SOKOL_AUDIO_API_DECL void saudio_setup(const saudio_desc* desc);
SOKOL_AUDIO_API_DECL void saudio_shutdown(void);
SOKOL_AUDIO_API_DECL bool saudio_isvalid(void);
SOKOL_AUDIO_API_DECL void* saudio_userdata(void);
SOKOL_AUDIO_API_DECL saudio_desc saudio_query_desc(void);
SOKOL_AUDIO_API_DECL int saudio_sample_rate(void);
SOKOL_AUDIO_API_DECL int saudio_buffer_frames(void);
SOKOL_AUDIO_API_DECL int saudio_channels(void);
SOKOL_AUDIO_API_DECL int saudio_expect(void);
SOKOL_AUDIO_API_DECL int saudio_push(const float* frames, int num_frames);
#ifdef __cplusplus
}
inline void saudio_setup(const saudio_desc& desc) { return saudio_setup(&desc); }
#endif
#endif
#ifdef SOKOL_AUDIO_IMPL
#define SOKOL_AUDIO_IMPL_INCLUDED (1)
#include <string.h>
#include <stddef.h>
#ifndef SOKOL_API_IMPL
#define SOKOL_API_IMPL
#endif
#ifndef SOKOL_DEBUG
#ifndef NDEBUG
#define SOKOL_DEBUG (1)
#endif
#endif
#ifndef SOKOL_ASSERT
#include <assert.h>
#define SOKOL_ASSERT(c) assert(c)
#endif
#ifndef SOKOL_MALLOC
#include <stdlib.h>
#define SOKOL_MALLOC(s) malloc(s)
#define SOKOL_FREE(p) free(p)
#endif
#ifndef SOKOL_LOG
#ifdef SOKOL_DEBUG
#include <stdio.h>
#define SOKOL_LOG(s) { SOKOL_ASSERT(s); puts(s); }
#else
#define SOKOL_LOG(s)
#endif
#endif
#ifndef _SOKOL_PRIVATE
#if defined(__GNUC__) || defined(__clang__)
#define _SOKOL_PRIVATE __attribute__((unused)) static
#else
#define _SOKOL_PRIVATE static
#endif
#endif
#ifndef _SOKOL_UNUSED
#define _SOKOL_UNUSED(x) (void)(x)
#endif
#if defined(SOKOL_DUMMY_BACKEND)
#elif (defined(__APPLE__) || defined(__linux__) || defined(__unix__)) && !defined(__EMSCRIPTEN__)
#include <pthread.h>
#elif defined(_WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <synchapi.h>
#if (defined(WINAPI_FAMILY_PARTITION) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP))
#define SOKOL_WIN32_NO_MMDEVICE
#pragma comment (lib, "WindowsApp")
#else
#pragma comment (lib, "kernel32")
#pragma comment (lib, "ole32")
#if defined(SOKOL_WIN32_NO_MMDEVICE)
#pragma comment (lib, "mmdevapi")
#endif
#endif
#endif
#if defined(SOKOL_DUMMY_BACKEND)
#elif defined(__APPLE__)
#include <TargetConditionals.h>
#include <AudioToolbox/AudioToolbox.h>
#if TARGET_OS_IOS
#if !defined(__cplusplus)
#if __has_feature(objc_arc) && !__has_feature(objc_arc_fields)
#error "sokol_audio.h on iOS requires __has_feature(objc_arc_field) if ARC is enabled (use a more recent compiler version)"
#endif
#endif
#include <AVFoundation/AVFoundation.h>
#endif
#elif (defined(__linux__) || defined(__unix__)) && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__)
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>
#elif defined(__ANDROID__)
#include "SLES/OpenSLES_Android.h"
#elif defined(_WIN32)
#ifndef CINTERFACE
#define CINTERFACE
#endif
#ifndef COBJMACROS
#define COBJMACROS
#endif
#ifndef CONST_VTABLE
#define CONST_VTABLE
#endif
#include <mmdeviceapi.h>
#include <audioclient.h>
static const IID _saudio_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
static const IID _saudio_IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35, { 0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6 } };
static const CLSID _saudio_CLSID_IMMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c, { 0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e } };
static const IID _saudio_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483,{ 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } };
static const IID _saudio_IID_Devinterface_Audio_Render = { 0xe6327cad, 0xdcec, 0x4949, {0xae, 0x8a, 0x99, 0x1e, 0x97, 0x6a, 0x79, 0xd2 } };
static const IID _saudio_IID_IActivateAudioInterface_Completion_Handler = { 0x94ea2b94, 0xe9cc, 0x49e0, {0xc0, 0xff, 0xee, 0x64, 0xca, 0x8f, 0x5b, 0x90} };
#if defined(__cplusplus)
#define _SOKOL_AUDIO_WIN32COM_ID(x) (x)
#else
#define _SOKOL_AUDIO_WIN32COM_ID(x) (&x)
#endif
#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
#endif
#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
#endif
#elif defined(__EMSCRIPTEN__)
#include <emscripten/emscripten.h>
#endif
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4505)
#endif
#define _saudio_def(val, def) (((val) == 0) ? (def) : (val))
#define _saudio_def_flt(val, def) (((val) == 0.0f) ? (def) : (val))
#define _SAUDIO_DEFAULT_SAMPLE_RATE (44100)
#define _SAUDIO_DEFAULT_BUFFER_FRAMES (2048)
#define _SAUDIO_DEFAULT_PACKET_FRAMES (128)
#define _SAUDIO_DEFAULT_NUM_PACKETS ((_SAUDIO_DEFAULT_BUFFER_FRAMES/_SAUDIO_DEFAULT_PACKET_FRAMES)*4)
#ifndef SAUDIO_RING_MAX_SLOTS
#define SAUDIO_RING_MAX_SLOTS (1024)
#endif
#if defined(SOKOL_DUMMY_BACKEND)
typedef struct { int dummy_mutex; } _saudio_mutex_t;
#elif (defined(__APPLE__) || defined(__linux__) || defined(__unix__)) && !defined(__EMSCRIPTEN__)
typedef struct {
pthread_mutex_t mutex;
} _saudio_mutex_t;
#elif defined(_WIN32)
typedef struct {
CRITICAL_SECTION critsec;
} _saudio_mutex_t;
#else
typedef struct { int dummy_mutex; } _saudio_mutex_t;
#endif
#if defined(SOKOL_DUMMY_BACKEND)
typedef struct {
int dummy_backend;
} _saudio_backend_t;
#elif defined(__APPLE__)
typedef struct {
AudioQueueRef ca_audio_queue;
#if TARGET_OS_IOS
id ca_interruption_handler;
#endif
} _saudio_backend_t;
#elif (defined(__linux__) || defined(__unix__)) && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__)
typedef struct {
snd_pcm_t* device;
float* buffer;
int buffer_byte_size;
int buffer_frames;
pthread_t thread;
bool thread_stop;
} _saudio_backend_t;
#elif defined(__ANDROID__)
#define SAUDIO_NUM_BUFFERS 2
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
int count;
} _saudio_semaphore_t;
typedef struct {
SLObjectItf engine_obj;
SLEngineItf engine;
SLObjectItf output_mix_obj;
SLVolumeItf output_mix_vol;
SLDataLocator_OutputMix out_locator;
SLDataSink dst_data_sink;
SLObjectItf player_obj;
SLPlayItf player;
SLVolumeItf player_vol;
SLAndroidSimpleBufferQueueItf player_buffer_queue;
int16_t* output_buffers[SAUDIO_NUM_BUFFERS];
float* src_buffer;
int active_buffer;
_saudio_semaphore_t buffer_sem;
pthread_t thread;
volatile int thread_stop;
SLDataLocator_AndroidSimpleBufferQueue in_locator;
} _saudio_backend_t;
#elif defined(_WIN32)
typedef struct {
HANDLE thread_handle;
HANDLE buffer_end_event;
bool stop;
UINT32 dst_buffer_frames;
int src_buffer_frames;
int src_buffer_byte_size;
int src_buffer_pos;
float* src_buffer;
} _saudio_wasapi_thread_data_t;
typedef struct {
#if defined(SOKOL_WIN32_NO_MMDEVICE)
LPOLESTR interface_activation_audio_interface_uid_string;
IActivateAudioInterfaceAsyncOperation* interface_activation_operation;
BOOL interface_activation_success;
HANDLE interface_activation_mutex;
#else
IMMDeviceEnumerator* device_enumerator;
IMMDevice* device;
#endif
IAudioClient* audio_client;
IAudioRenderClient* render_client;
int si16_bytes_per_frame;
_saudio_wasapi_thread_data_t thread;
} _saudio_backend_t;
#elif defined(__EMSCRIPTEN__)
typedef struct {
uint8_t* buffer;
} _saudio_backend_t;
#else
typedef struct { } _saudio_backend_t;
#endif
typedef struct {
int head; int tail; int num; int queue[SAUDIO_RING_MAX_SLOTS];
} _saudio_ring_t;
typedef struct {
bool valid;
int packet_size;
int num_packets;
uint8_t* base_ptr;
int cur_packet;
int cur_offset;
_saudio_mutex_t mutex;
_saudio_ring_t read_queue;
_saudio_ring_t write_queue;
} _saudio_fifo_t;
typedef struct {
bool valid;
void (*stream_cb)(float* buffer, int num_frames, int num_channels);
void (*stream_userdata_cb)(float* buffer, int num_frames, int num_channels, void* user_data);
void* user_data;
int sample_rate;
int buffer_frames;
int bytes_per_frame;
int packet_frames;
int num_packets;
int num_channels;
saudio_desc desc;
_saudio_fifo_t fifo;
_saudio_backend_t backend;
} _saudio_state_t;
static _saudio_state_t _saudio;
_SOKOL_PRIVATE bool _saudio_has_callback(void) {
return (_saudio.stream_cb || _saudio.stream_userdata_cb);
}
_SOKOL_PRIVATE void _saudio_stream_callback(float* buffer, int num_frames, int num_channels) {
if (_saudio.stream_cb) {
_saudio.stream_cb(buffer, num_frames, num_channels);
}
else if (_saudio.stream_userdata_cb) {
_saudio.stream_userdata_cb(buffer, num_frames, num_channels, _saudio.user_data);
}
}
#if defined(SOKOL_DUMMY_BACKEND)
_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) { (void)m; }
_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) { (void)m; }
_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) { (void)m; }
_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) { (void)m; }
#elif (defined(__APPLE__) || defined(__linux__) || defined(__unix__)) && !defined(__EMSCRIPTEN__)
_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) {
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutex_init(&m->mutex, &attr);
}
_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) {
pthread_mutex_destroy(&m->mutex);
}
_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) {
pthread_mutex_lock(&m->mutex);
}
_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) {
pthread_mutex_unlock(&m->mutex);
}
#elif defined(_WIN32)
_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) {
InitializeCriticalSection(&m->critsec);
}
_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) {
DeleteCriticalSection(&m->critsec);
}
_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) {
EnterCriticalSection(&m->critsec);
}
_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) {
LeaveCriticalSection(&m->critsec);
}
#else
_SOKOL_PRIVATE void _saudio_mutex_init(_saudio_mutex_t* m) { (void)m; }
_SOKOL_PRIVATE void _saudio_mutex_destroy(_saudio_mutex_t* m) { (void)m; }
_SOKOL_PRIVATE void _saudio_mutex_lock(_saudio_mutex_t* m) { (void)m; }
_SOKOL_PRIVATE void _saudio_mutex_unlock(_saudio_mutex_t* m) { (void)m; }
#endif
_SOKOL_PRIVATE int _saudio_ring_idx(_saudio_ring_t* ring, int i) {
return (i % ring->num);
}
_SOKOL_PRIVATE void _saudio_ring_init(_saudio_ring_t* ring, int num_slots) {
SOKOL_ASSERT((num_slots + 1) <= SAUDIO_RING_MAX_SLOTS);
ring->head = 0;
ring->tail = 0;
ring->num = num_slots + 1;
}
_SOKOL_PRIVATE bool _saudio_ring_full(_saudio_ring_t* ring) {
return _saudio_ring_idx(ring, ring->head + 1) == ring->tail;
}
_SOKOL_PRIVATE bool _saudio_ring_empty(_saudio_ring_t* ring) {
return ring->head == ring->tail;
}
_SOKOL_PRIVATE int _saudio_ring_count(_saudio_ring_t* ring) {
int count;
if (ring->head >= ring->tail) {
count = ring->head - ring->tail;
}
else {
count = (ring->head + ring->num) - ring->tail;
}
SOKOL_ASSERT(count < ring->num);
return count;
}
_SOKOL_PRIVATE void _saudio_ring_enqueue(_saudio_ring_t* ring, int val) {
SOKOL_ASSERT(!_saudio_ring_full(ring));
ring->queue[ring->head] = val;
ring->head = _saudio_ring_idx(ring, ring->head + 1);
}
_SOKOL_PRIVATE int _saudio_ring_dequeue(_saudio_ring_t* ring) {
SOKOL_ASSERT(!_saudio_ring_empty(ring));
int val = ring->queue[ring->tail];
ring->tail = _saudio_ring_idx(ring, ring->tail + 1);
return val;
}
_SOKOL_PRIVATE void _saudio_fifo_init_mutex(_saudio_fifo_t* fifo) {
_saudio_mutex_init(&fifo->mutex);
}
_SOKOL_PRIVATE void _saudio_fifo_init(_saudio_fifo_t* fifo, int packet_size, int num_packets) {
_saudio_mutex_lock(&fifo->mutex);
SOKOL_ASSERT((packet_size > 0) && (num_packets > 0));
fifo->packet_size = packet_size;
fifo->num_packets = num_packets;
fifo->base_ptr = (uint8_t*) SOKOL_MALLOC((size_t)(packet_size * num_packets));
SOKOL_ASSERT(fifo->base_ptr);
fifo->cur_packet = -1;
fifo->cur_offset = 0;
_saudio_ring_init(&fifo->read_queue, num_packets);
_saudio_ring_init(&fifo->write_queue, num_packets);
for (int i = 0; i < num_packets; i++) {
_saudio_ring_enqueue(&fifo->write_queue, i);
}
SOKOL_ASSERT(_saudio_ring_full(&fifo->write_queue));
SOKOL_ASSERT(_saudio_ring_count(&fifo->write_queue) == num_packets);
SOKOL_ASSERT(_saudio_ring_empty(&fifo->read_queue));
SOKOL_ASSERT(_saudio_ring_count(&fifo->read_queue) == 0);
fifo->valid = true;
_saudio_mutex_unlock(&fifo->mutex);
}
_SOKOL_PRIVATE void _saudio_fifo_shutdown(_saudio_fifo_t* fifo) {
SOKOL_ASSERT(fifo->base_ptr);
SOKOL_FREE(fifo->base_ptr);
fifo->base_ptr = 0;
fifo->valid = false;
_saudio_mutex_destroy(&fifo->mutex);
}
_SOKOL_PRIVATE int _saudio_fifo_writable_bytes(_saudio_fifo_t* fifo) {
_saudio_mutex_lock(&fifo->mutex);
int num_bytes = (_saudio_ring_count(&fifo->write_queue) * fifo->packet_size);
if (fifo->cur_packet != -1) {
num_bytes += fifo->packet_size - fifo->cur_offset;
}
_saudio_mutex_unlock(&fifo->mutex);
SOKOL_ASSERT((num_bytes >= 0) && (num_bytes <= (fifo->num_packets * fifo->packet_size)));
return num_bytes;
}
_SOKOL_PRIVATE int _saudio_fifo_write(_saudio_fifo_t* fifo, const uint8_t* ptr, int num_bytes) {
int all_to_copy = num_bytes;
while (all_to_copy > 0) {
if (fifo->cur_packet == -1) {
_saudio_mutex_lock(&fifo->mutex);
if (!_saudio_ring_empty(&fifo->write_queue)) {
fifo->cur_packet = _saudio_ring_dequeue(&fifo->write_queue);
}
_saudio_mutex_unlock(&fifo->mutex);
SOKOL_ASSERT(fifo->cur_offset == 0);
}
if (fifo->cur_packet != -1) {
int to_copy = all_to_copy;
const int max_copy = fifo->packet_size - fifo->cur_offset;
if (to_copy > max_copy) {
to_copy = max_copy;
}
uint8_t* dst = fifo->base_ptr + fifo->cur_packet * fifo->packet_size + fifo->cur_offset;
memcpy(dst, ptr, (size_t)to_copy);
ptr += to_copy;
fifo->cur_offset += to_copy;
all_to_copy -= to_copy;
SOKOL_ASSERT(fifo->cur_offset <= fifo->packet_size);
SOKOL_ASSERT(all_to_copy >= 0);
}
else {
int bytes_copied = num_bytes - all_to_copy;
SOKOL_ASSERT((bytes_copied >= 0) && (bytes_copied < num_bytes));
return bytes_copied;
}
if (fifo->cur_offset == fifo->packet_size) {
_saudio_mutex_lock(&fifo->mutex);
_saudio_ring_enqueue(&fifo->read_queue, fifo->cur_packet);
_saudio_mutex_unlock(&fifo->mutex);
fifo->cur_packet = -1;
fifo->cur_offset = 0;
}
}
SOKOL_ASSERT(all_to_copy == 0);
return num_bytes;
}
_SOKOL_PRIVATE int _saudio_fifo_read(_saudio_fifo_t* fifo, uint8_t* ptr, int num_bytes) {
_saudio_mutex_lock(&fifo->mutex);
int num_bytes_copied = 0;
if (fifo->valid) {
SOKOL_ASSERT(0 == (num_bytes % fifo->packet_size));
SOKOL_ASSERT(num_bytes <= (fifo->packet_size * fifo->num_packets));
const int num_packets_needed = num_bytes / fifo->packet_size;
uint8_t* dst = ptr;
if (_saudio_ring_count(&fifo->read_queue) >= num_packets_needed) {
for (int i = 0; i < num_packets_needed; i++) {
int packet_index = _saudio_ring_dequeue(&fifo->read_queue);
_saudio_ring_enqueue(&fifo->write_queue, packet_index);
const uint8_t* src = fifo->base_ptr + packet_index * fifo->packet_size;
memcpy(dst, src, (size_t)fifo->packet_size);
dst += fifo->packet_size;
num_bytes_copied += fifo->packet_size;
}
SOKOL_ASSERT(num_bytes == num_bytes_copied);
}
}
_saudio_mutex_unlock(&fifo->mutex);
return num_bytes_copied;
}
#if defined(SOKOL_DUMMY_BACKEND)
_SOKOL_PRIVATE bool _saudio_backend_init(void) {
_saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float);
return true;
};
_SOKOL_PRIVATE void _saudio_backend_shutdown(void) { };
#elif defined(__APPLE__)
#if TARGET_OS_IOS
#if __has_feature(objc_arc)
#define _SAUDIO_OBJC_RELEASE(obj) { obj = nil; }
#else
#define _SAUDIO_OBJC_RELEASE(obj) { [obj release]; obj = nil; }
#endif
@interface _saudio_interruption_handler : NSObject { }
@end
@implementation _saudio_interruption_handler
-(id)init {
self = [super init];
AVAudioSession* session = [AVAudioSession sharedInstance];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_interruption:) name:AVAudioSessionInterruptionNotification object:session];
return self;
}
-(void)dealloc {
[self remove_handler];
#if !__has_feature(objc_arc)
[super dealloc];
#endif
}
-(void)remove_handler {
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"AVAudioSessionInterruptionNotification" object:nil];
}
-(void)handle_interruption:(NSNotification*)notification {
AVAudioSession* session = [AVAudioSession sharedInstance];
SOKOL_ASSERT(session);
NSDictionary* dict = notification.userInfo;
SOKOL_ASSERT(dict);
NSInteger type = [[dict valueForKey:AVAudioSessionInterruptionTypeKey] integerValue];
switch (type) {
case AVAudioSessionInterruptionTypeBegan:
AudioQueuePause(_saudio.backend.ca_audio_queue);
[session setActive:false error:nil];
break;
case AVAudioSessionInterruptionTypeEnded:
[session setActive:true error:nil];
AudioQueueStart(_saudio.backend.ca_audio_queue, NULL);
break;
default:
break;
}
}
@end
#endif
_SOKOL_PRIVATE void _saudio_coreaudio_callback(void* user_data, AudioQueueRef queue, AudioQueueBufferRef buffer) {
_SOKOL_UNUSED(user_data);
if (_saudio_has_callback()) {
const int num_frames = (int)buffer->mAudioDataByteSize / _saudio.bytes_per_frame;
const int num_channels = _saudio.num_channels;
_saudio_stream_callback((float*)buffer->mAudioData, num_frames, num_channels);
}
else {
uint8_t* ptr = (uint8_t*)buffer->mAudioData;
int num_bytes = (int) buffer->mAudioDataByteSize;
if (0 == _saudio_fifo_read(&_saudio.fifo, ptr, num_bytes)) {
memset(ptr, 0, (size_t)num_bytes);
}
}
AudioQueueEnqueueBuffer(queue, buffer, 0, NULL);
}
_SOKOL_PRIVATE bool _saudio_backend_init(void) {
SOKOL_ASSERT(0 == _saudio.backend.ca_audio_queue);
#if TARGET_OS_IOS
AVAudioSession* session = [AVAudioSession sharedInstance];
SOKOL_ASSERT(session != nil);
[session setCategory: AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:nil];
[session setActive:true error:nil];
_saudio.backend.ca_interruption_handler = [[_saudio_interruption_handler alloc] init];
#endif
AudioStreamBasicDescription fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.mSampleRate = (Float64) _saudio.sample_rate;
fmt.mFormatID = kAudioFormatLinearPCM;
fmt.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagIsPacked;
fmt.mFramesPerPacket = 1;
fmt.mChannelsPerFrame = (uint32_t) _saudio.num_channels;
fmt.mBytesPerFrame = (uint32_t)sizeof(float) * (uint32_t)_saudio.num_channels;
fmt.mBytesPerPacket = fmt.mBytesPerFrame;
fmt.mBitsPerChannel = 32;
OSStatus res = AudioQueueNewOutput(&fmt, _saudio_coreaudio_callback, 0, NULL, NULL, 0, &_saudio.backend.ca_audio_queue);
SOKOL_ASSERT((res == 0) && _saudio.backend.ca_audio_queue);
for (int i = 0; i < 2; i++) {
AudioQueueBufferRef buf = NULL;
const uint32_t buf_byte_size = (uint32_t)_saudio.buffer_frames * fmt.mBytesPerFrame;
res = AudioQueueAllocateBuffer(_saudio.backend.ca_audio_queue, buf_byte_size, &buf);
SOKOL_ASSERT((res == 0) && buf);
buf->mAudioDataByteSize = buf_byte_size;
memset(buf->mAudioData, 0, buf->mAudioDataByteSize);
AudioQueueEnqueueBuffer(_saudio.backend.ca_audio_queue, buf, 0, NULL);
}
_saudio.bytes_per_frame = (int)fmt.mBytesPerFrame;
res = AudioQueueStart(_saudio.backend.ca_audio_queue, NULL);
SOKOL_ASSERT(0 == res);
return true;
}
_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
AudioQueueStop(_saudio.backend.ca_audio_queue, true);
AudioQueueDispose(_saudio.backend.ca_audio_queue, false);
_saudio.backend.ca_audio_queue = NULL;
#if TARGET_OS_IOS
if (_saudio.backend.ca_interruption_handler != nil) {
[_saudio.backend.ca_interruption_handler remove_handler];
_SAUDIO_OBJC_RELEASE(_saudio.backend.ca_interruption_handler);
}
AVAudioSession* session = [AVAudioSession sharedInstance];
SOKOL_ASSERT(session);
[session setActive:false error:nil];;
#endif
}
#elif (defined(__linux__) || defined(__unix__)) && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__)
_SOKOL_PRIVATE void* _saudio_alsa_cb(void* param) {
_SOKOL_UNUSED(param);
while (!_saudio.backend.thread_stop) {
int write_res = snd_pcm_writei(_saudio.backend.device, _saudio.backend.buffer, (snd_pcm_uframes_t)_saudio.backend.buffer_frames);
if (write_res < 0) {
snd_pcm_prepare(_saudio.backend.device);
}
else {
if (_saudio_has_callback()) {
_saudio_stream_callback(_saudio.backend.buffer, _saudio.backend.buffer_frames, _saudio.num_channels);
}
else {
if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.buffer, _saudio.backend.buffer_byte_size)) {
memset(_saudio.backend.buffer, 0, (size_t)_saudio.backend.buffer_byte_size);
}
}
}
}
return 0;
}
_SOKOL_PRIVATE bool _saudio_backend_init(void) {
int dir; uint32_t rate;
int rc = snd_pcm_open(&_saudio.backend.device, "default", SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0) {
SOKOL_LOG("sokol_audio.h: snd_pcm_open() failed");
return false;
}
snd_pcm_hw_params_t* params = 0;
snd_pcm_hw_params_alloca(¶ms);
snd_pcm_hw_params_any(_saudio.backend.device, params);
snd_pcm_hw_params_set_access(_saudio.backend.device, params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (0 > snd_pcm_hw_params_set_format(_saudio.backend.device, params, SND_PCM_FORMAT_FLOAT_LE)) {
SOKOL_LOG("sokol_audio.h: float samples not supported");
goto error;
}
if (0 > snd_pcm_hw_params_set_buffer_size(_saudio.backend.device, params, (snd_pcm_uframes_t)_saudio.buffer_frames)) {
SOKOL_LOG("sokol_audio.h: requested buffer size not supported");
goto error;
}
if (0 > snd_pcm_hw_params_set_channels(_saudio.backend.device, params, (uint32_t)_saudio.num_channels)) {
SOKOL_LOG("sokol_audio.h: requested channel count not supported");
goto error;
}
rate = (uint32_t) _saudio.sample_rate;
dir = 0;
if (0 > snd_pcm_hw_params_set_rate_near(_saudio.backend.device, params, &rate, &dir)) {
SOKOL_LOG("sokol_audio.h: snd_pcm_hw_params_set_rate_near() failed");
goto error;
}
if (0 > snd_pcm_hw_params(_saudio.backend.device, params)) {
SOKOL_LOG("sokol_audio.h: snd_pcm_hw_params() failed");
goto error;
}
_saudio.sample_rate = (int)rate;
_saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float);
_saudio.backend.buffer_byte_size = _saudio.buffer_frames * _saudio.bytes_per_frame;
_saudio.backend.buffer_frames = _saudio.buffer_frames;
_saudio.backend.buffer = (float*) SOKOL_MALLOC((size_t)_saudio.backend.buffer_byte_size);
memset(_saudio.backend.buffer, 0, (size_t)_saudio.backend.buffer_byte_size);
if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_alsa_cb, 0)) {
SOKOL_LOG("sokol_audio.h: pthread_create() failed");
goto error;
}
return true;
error:
if (_saudio.backend.device) {
snd_pcm_close(_saudio.backend.device);
_saudio.backend.device = 0;
}
return false;
};
_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
SOKOL_ASSERT(_saudio.backend.device);
_saudio.backend.thread_stop = true;
pthread_join(_saudio.backend.thread, 0);
snd_pcm_drain(_saudio.backend.device);
snd_pcm_close(_saudio.backend.device);
SOKOL_FREE(_saudio.backend.buffer);
};
#elif defined(_WIN32)
#if defined(SOKOL_WIN32_NO_MMDEVICE)
_SOKOL_PRIVATE HRESULT STDMETHODCALLTYPE _saudio_interface_completion_handler_queryinterface(IActivateAudioInterfaceCompletionHandler* instance, REFIID riid, void** ppvObject) {
if (!ppvObject) {
return E_POINTER;
}
if (IsEqualIID(riid, _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IActivateAudioInterface_Completion_Handler)) || IsEqualIID(riid, _SOKOL_AUDIO_WIN32COM_ID(IID_IUnknown)))
{
*ppvObject = (void*)instance;
return S_OK;
}
*ppvObject = NULL;
return E_NOINTERFACE;
}
_SOKOL_PRIVATE ULONG STDMETHODCALLTYPE _saudio_interface_completion_handler_addref_release(IActivateAudioInterfaceCompletionHandler* instance) {
_SOKOL_UNUSED(instance);
return 1;
}
_SOKOL_PRIVATE HRESULT STDMETHODCALLTYPE _saudio_backend_activate_audio_interface_cb(IActivateAudioInterfaceCompletionHandler* instance, IActivateAudioInterfaceAsyncOperation* activateOperation) {
_SOKOL_UNUSED(instance);
WaitForSingleObject(_saudio.backend.interface_activation_mutex, INFINITE);
_saudio.backend.interface_activation_success = TRUE;
HRESULT activation_result;
if (FAILED(activateOperation->lpVtbl->GetActivateResult(activateOperation, &activation_result, (IUnknown**)(&_saudio.backend.audio_client))) || FAILED(activation_result)) {
_saudio.backend.interface_activation_success = FALSE;
}
ReleaseMutex(_saudio.backend.interface_activation_mutex);
return S_OK;
}
#endif
_SOKOL_PRIVATE void _saudio_wasapi_fill_buffer(void) {
if (_saudio_has_callback()) {
_saudio_stream_callback(_saudio.backend.thread.src_buffer, _saudio.backend.thread.src_buffer_frames, _saudio.num_channels);
}
else {
if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.thread.src_buffer, _saudio.backend.thread.src_buffer_byte_size)) {
memset(_saudio.backend.thread.src_buffer, 0, (size_t)_saudio.backend.thread.src_buffer_byte_size);
}
}
}
_SOKOL_PRIVATE void _saudio_wasapi_submit_buffer(int num_frames) {
BYTE* wasapi_buffer = 0;
if (FAILED(IAudioRenderClient_GetBuffer(_saudio.backend.render_client, num_frames, &wasapi_buffer))) {
return;
}
SOKOL_ASSERT(wasapi_buffer);
const int num_samples = num_frames * _saudio.num_channels;
int16_t* dst = (int16_t*) wasapi_buffer;
int buffer_pos = _saudio.backend.thread.src_buffer_pos;
const int buffer_float_size = _saudio.backend.thread.src_buffer_byte_size / (int)sizeof(float);
float* src = _saudio.backend.thread.src_buffer;
for (int i = 0; i < num_samples; i++) {
if (0 == buffer_pos) {
_saudio_wasapi_fill_buffer();
}
dst[i] = (int16_t) (src[buffer_pos] * 0x7FFF);
buffer_pos += 1;
if (buffer_pos == buffer_float_size) {
buffer_pos = 0;
}
}
_saudio.backend.thread.src_buffer_pos = buffer_pos;
IAudioRenderClient_ReleaseBuffer(_saudio.backend.render_client, num_frames, 0);
}
_SOKOL_PRIVATE DWORD WINAPI _saudio_wasapi_thread_fn(LPVOID param) {
(void)param;
_saudio_wasapi_submit_buffer(_saudio.backend.thread.src_buffer_frames);
IAudioClient_Start(_saudio.backend.audio_client);
while (!_saudio.backend.thread.stop) {
WaitForSingleObject(_saudio.backend.thread.buffer_end_event, INFINITE);
UINT32 padding = 0;
if (FAILED(IAudioClient_GetCurrentPadding(_saudio.backend.audio_client, &padding))) {
continue;
}
SOKOL_ASSERT(_saudio.backend.thread.dst_buffer_frames >= padding);
int num_frames = (int)_saudio.backend.thread.dst_buffer_frames - (int)padding;
if (num_frames > 0) {
_saudio_wasapi_submit_buffer(num_frames);
}
}
return 0;
}
_SOKOL_PRIVATE void _saudio_wasapi_release(void) {
if (_saudio.backend.thread.src_buffer) {
SOKOL_FREE(_saudio.backend.thread.src_buffer);
_saudio.backend.thread.src_buffer = 0;
}
if (_saudio.backend.render_client) {
IAudioRenderClient_Release(_saudio.backend.render_client);
_saudio.backend.render_client = 0;
}
if (_saudio.backend.audio_client) {
IAudioClient_Release(_saudio.backend.audio_client);
_saudio.backend.audio_client = 0;
}
#if defined(SOKOL_WIN32_NO_MMDEVICE)
if (_saudio.backend.interface_activation_audio_interface_uid_string) {
CoTaskMemFree(_saudio.backend.interface_activation_audio_interface_uid_string);
_saudio.backend.interface_activation_audio_interface_uid_string = 0;
}
if (_saudio.backend.interface_activation_operation) {
IActivateAudioInterfaceAsyncOperation_Release(_saudio.backend.interface_activation_operation);
_saudio.backend.interface_activation_operation = 0;
}
#else
if (_saudio.backend.device) {
IMMDevice_Release(_saudio.backend.device);
_saudio.backend.device = 0;
}
if (_saudio.backend.device_enumerator) {
IMMDeviceEnumerator_Release(_saudio.backend.device_enumerator);
_saudio.backend.device_enumerator = 0;
}
#endif
if (0 != _saudio.backend.thread.buffer_end_event) {
CloseHandle(_saudio.backend.thread.buffer_end_event);
_saudio.backend.thread.buffer_end_event = 0;
}
}
_SOKOL_PRIVATE bool _saudio_backend_init(void) {
REFERENCE_TIME dur;
#if (defined(WINAPI_FAMILY_PARTITION) && WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP))
HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
_SOKOL_UNUSED(hr);
#endif
_saudio.backend.thread.buffer_end_event = CreateEvent(0, FALSE, FALSE, 0);
if (0 == _saudio.backend.thread.buffer_end_event) {
SOKOL_LOG("sokol_audio wasapi: failed to create buffer_end_event");
goto error;
}
#if defined(SOKOL_WIN32_NO_MMDEVICE)
_saudio.backend.interface_activation_mutex = CreateMutexA(NULL, FALSE, "interface_activation_mutex");
if (_saudio.backend.interface_activation_mutex == NULL) {
SOKOL_LOG("sokol_audio wasapi: failed to create interface activation mutex");
goto error;
}
if (FAILED(StringFromIID(_SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_Devinterface_Audio_Render), &_saudio.backend.interface_activation_audio_interface_uid_string))) {
SOKOL_LOG("sokol_audio wasapi: failed to get default audio device ID string");
goto error;
}
static IActivateAudioInterfaceCompletionHandlerVtbl completion_handler_interface_vtable = {
_saudio_interface_completion_handler_queryinterface,
_saudio_interface_completion_handler_addref_release,
_saudio_interface_completion_handler_addref_release,
_saudio_backend_activate_audio_interface_cb
};
static IActivateAudioInterfaceCompletionHandler completion_handler_interface = { &completion_handler_interface_vtable };
if (FAILED(ActivateAudioInterfaceAsync(_saudio.backend.interface_activation_audio_interface_uid_string, _SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioClient), NULL, &completion_handler_interface, &_saudio.backend.interface_activation_operation))) {
SOKOL_LOG("sokol_audio wasapi: failed to get default audio device ID string");
goto error;
}
while (!(_saudio.backend.audio_client)) {
if (WaitForSingleObject(_saudio.backend.interface_activation_mutex, 10) != WAIT_TIMEOUT) {
ReleaseMutex(_saudio.backend.interface_activation_mutex);
}
}
if (!(_saudio.backend.interface_activation_success)) {
SOKOL_LOG("sokol_audio wasapi: interface activation failed. Unable to get audio client");
goto error;
}
#else
if (FAILED(CoCreateInstance(_SOKOL_AUDIO_WIN32COM_ID(_saudio_CLSID_IMMDeviceEnumerator),
0, CLSCTX_ALL,
_SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IMMDeviceEnumerator),
(void**)&_saudio.backend.device_enumerator)))
{
SOKOL_LOG("sokol_audio wasapi: failed to create device enumerator");
goto error;
}
if (FAILED(IMMDeviceEnumerator_GetDefaultAudioEndpoint(_saudio.backend.device_enumerator,
eRender, eConsole,
&_saudio.backend.device)))
{
SOKOL_LOG("sokol_audio wasapi: GetDefaultAudioEndPoint failed");
goto error;
}
if (FAILED(IMMDevice_Activate(_saudio.backend.device,
_SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioClient),
CLSCTX_ALL, 0,
(void**)&_saudio.backend.audio_client)))
{
SOKOL_LOG("sokol_audio wasapi: device activate failed");
goto error;
}
#endif
WAVEFORMATEX fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.nChannels = (WORD)_saudio.num_channels;
fmt.nSamplesPerSec = (DWORD)_saudio.sample_rate;
fmt.wFormatTag = WAVE_FORMAT_PCM;
fmt.wBitsPerSample = 16;
fmt.nBlockAlign = (fmt.nChannels * fmt.wBitsPerSample) / 8;
fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign;
dur = (REFERENCE_TIME)
(((double)_saudio.buffer_frames) / (((double)_saudio.sample_rate) * (1.0/10000000.0)));
if (FAILED(IAudioClient_Initialize(_saudio.backend.audio_client,
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK|AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM|AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY,
dur, 0, &fmt, 0)))
{
SOKOL_LOG("sokol_audio wasapi: audio client initialize failed");
goto error;
}
if (FAILED(IAudioClient_GetBufferSize(_saudio.backend.audio_client, &_saudio.backend.thread.dst_buffer_frames))) {
SOKOL_LOG("sokol_audio wasapi: audio client get buffer size failed");
goto error;
}
if (FAILED(IAudioClient_GetService(_saudio.backend.audio_client,
_SOKOL_AUDIO_WIN32COM_ID(_saudio_IID_IAudioRenderClient),
(void**)&_saudio.backend.render_client)))
{
SOKOL_LOG("sokol_audio wasapi: audio client GetService failed");
goto error;
}
if (FAILED(IAudioClient_SetEventHandle(_saudio.backend.audio_client, _saudio.backend.thread.buffer_end_event))) {
SOKOL_LOG("sokol_audio wasapi: audio client SetEventHandle failed");
goto error;
}
_saudio.backend.si16_bytes_per_frame = _saudio.num_channels * (int)sizeof(int16_t);
_saudio.bytes_per_frame = _saudio.num_channels * (int)sizeof(float);
_saudio.backend.thread.src_buffer_frames = _saudio.buffer_frames;
_saudio.backend.thread.src_buffer_byte_size = _saudio.backend.thread.src_buffer_frames * _saudio.bytes_per_frame;
_saudio.backend.thread.src_buffer = (float*) SOKOL_MALLOC((size_t)_saudio.backend.thread.src_buffer_byte_size);
SOKOL_ASSERT(_saudio.backend.thread.src_buffer);
_saudio.backend.thread.thread_handle = CreateThread(NULL, 0, _saudio_wasapi_thread_fn, 0, 0, 0);
if (0 == _saudio.backend.thread.thread_handle) {
SOKOL_LOG("sokol_audio wasapi: CreateThread failed");
goto error;
}
return true;
error:
_saudio_wasapi_release();
return false;
}
_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
if (_saudio.backend.thread.thread_handle) {
_saudio.backend.thread.stop = true;
SetEvent(_saudio.backend.thread.buffer_end_event);
WaitForSingleObject(_saudio.backend.thread.thread_handle, INFINITE);
CloseHandle(_saudio.backend.thread.thread_handle);
_saudio.backend.thread.thread_handle = 0;
}
if (_saudio.backend.audio_client) {
IAudioClient_Stop(_saudio.backend.audio_client);
}
_saudio_wasapi_release();
#if (defined(WINAPI_FAMILY_PARTITION) && WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP))
CoUninitialize();
#endif
}
#elif defined(__EMSCRIPTEN__)
#ifdef __cplusplus
extern "C" {
#endif
EMSCRIPTEN_KEEPALIVE int _saudio_emsc_pull(int num_frames) {
SOKOL_ASSERT(_saudio.backend.buffer);
if (num_frames == _saudio.buffer_frames) {
if (_saudio_has_callback()) {
_saudio_stream_callback((float*)_saudio.backend.buffer, num_frames, _saudio.num_channels);
}
else {
const int num_bytes = num_frames * _saudio.bytes_per_frame;
if (0 == _saudio_fifo_read(&_saudio.fifo, _saudio.backend.buffer, num_bytes)) {
memset(_saudio.backend.buffer, 0, (size_t)num_bytes);
}
}
int res = (int) _saudio.backend.buffer;
return res;
}
else {
return 0;
}
}
#ifdef __cplusplus
}
#endif
EM_JS(int, saudio_js_init, (int sample_rate, int num_channels, int buffer_size), {
Module._saudio_context = null;
Module._saudio_node = null;
if (typeof AudioContext !== 'undefined') {
Module._saudio_context = new AudioContext({
sampleRate: sample_rate,
latencyHint: 'interactive',
});
}
else if (typeof webkitAudioContext !== 'undefined') {
Module._saudio_context = new webkitAudioContext({
sampleRate: sample_rate,
latencyHint: 'interactive',
});
}
else {
Module._saudio_context = null;
console.log('sokol_audio.h: no WebAudio support');
}
if (Module._saudio_context) {
console.log('sokol_audio.h: sample rate ', Module._saudio_context.sampleRate);
Module._saudio_node = Module._saudio_context.createScriptProcessor(buffer_size, 0, num_channels);
Module._saudio_node.onaudioprocess = function pump_audio(event) {
var num_frames = event.outputBuffer.length;
var ptr = __saudio_emsc_pull(num_frames);
if (ptr) {
var num_channels = event.outputBuffer.numberOfChannels;
for (var chn = 0; chn < num_channels; chn++) {
var chan = event.outputBuffer.getChannelData(chn);
for (var i = 0; i < num_frames; i++) {
chan[i] = HEAPF32[(ptr>>2) + ((num_channels*i)+chn)]
}
}
}
};
Module._saudio_node.connect(Module._saudio_context.destination);
var resume_webaudio = function() {
if (Module._saudio_context) {
if (Module._saudio_context.state === 'suspended') {
Module._saudio_context.resume();
}
}
};
document.addEventListener('click', resume_webaudio, {once:true});
document.addEventListener('touchstart', resume_webaudio, {once:true});
document.addEventListener('keydown', resume_webaudio, {once:true});
return 1;
}
else {
return 0;
}
});
EM_JS(void, saudio_js_shutdown, (void), {
if (Module._saudio_context !== null) {
if (Module._saudio_node) {
Module._saudio_node.disconnect();
}
Module._saudio_context.close();
Module._saudio_context = null;
Module._saudio_node = null;
}
});
EM_JS(int, saudio_js_sample_rate, (void), {
if (Module._saudio_context) {
return Module._saudio_context.sampleRate;
}
else {
return 0;
}
});
EM_JS(int, saudio_js_buffer_frames, (void), {
if (Module._saudio_node) {
return Module._saudio_node.bufferSize;
}
else {
return 0;
}
});
_SOKOL_PRIVATE bool _saudio_backend_init(void) {
if (saudio_js_init(_saudio.sample_rate, _saudio.num_channels, _saudio.buffer_frames)) {
_saudio.bytes_per_frame = (int)sizeof(float) * _saudio.num_channels;
_saudio.sample_rate = saudio_js_sample_rate();
_saudio.buffer_frames = saudio_js_buffer_frames();
const size_t buf_size = (size_t) (_saudio.buffer_frames * _saudio.bytes_per_frame);
_saudio.backend.buffer = (uint8_t*) SOKOL_MALLOC(buf_size);
return true;
}
else {
return false;
}
}
_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
saudio_js_shutdown();
if (_saudio.backend.buffer) {
SOKOL_FREE(_saudio.backend.buffer);
_saudio.backend.buffer = 0;
}
}
#elif defined(__ANDROID__)
#ifdef __cplusplus
extern "C" {
#endif
_SOKOL_PRIVATE void _saudio_semaphore_init(_saudio_semaphore_t* sem) {
sem->count = 0;
int r = pthread_mutex_init(&sem->mutex, NULL);
SOKOL_ASSERT(r == 0);
r = pthread_cond_init(&sem->cond, NULL);
SOKOL_ASSERT(r == 0);
(void)(r);
}
_SOKOL_PRIVATE void _saudio_semaphore_destroy(_saudio_semaphore_t* sem)
{
pthread_cond_destroy(&sem->cond);
pthread_mutex_destroy(&sem->mutex);
}
_SOKOL_PRIVATE void _saudio_semaphore_post(_saudio_semaphore_t* sem, int count)
{
int r = pthread_mutex_lock(&sem->mutex);
SOKOL_ASSERT(r == 0);
for (int ii = 0; ii < count; ii++) {
r = pthread_cond_signal(&sem->cond);
SOKOL_ASSERT(r == 0);
}
sem->count += count;
r = pthread_mutex_unlock(&sem->mutex);
SOKOL_ASSERT(r == 0);
(void)(r);
}
_SOKOL_PRIVATE bool _saudio_semaphore_wait(_saudio_semaphore_t* sem)
{
int r = pthread_mutex_lock(&sem->mutex);
SOKOL_ASSERT(r == 0);
while (r == 0 && sem->count <= 0) {
r = pthread_cond_wait(&sem->cond, &sem->mutex);
}
bool ok = (r == 0);
if (ok) {
--sem->count;
}
r = pthread_mutex_unlock(&sem->mutex);
(void)(r);
return ok;
}
_SOKOL_PRIVATE void _saudio_opensles_fill_buffer(void) {
int src_buffer_frames = _saudio.buffer_frames;
if (_saudio_has_callback()) {
_saudio_stream_callback(_saudio.backend.src_buffer, src_buffer_frames, _saudio.num_channels);
}
else {
const int src_buffer_byte_size = src_buffer_frames * _saudio.num_channels * (int)sizeof(float);
if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.src_buffer, src_buffer_byte_size)) {
memset(_saudio.backend.src_buffer, 0x0, (size_t)src_buffer_byte_size);
}
}
}
_SOKOL_PRIVATE void SLAPIENTRY _saudio_opensles_play_cb(SLPlayItf player, void *context, SLuint32 event) {
(void)(context);
(void)(player);
if (event & SL_PLAYEVENT_HEADATEND) {
_saudio_semaphore_post(&_saudio.backend.buffer_sem, 1);
}
}
_SOKOL_PRIVATE void* _saudio_opensles_thread_fn(void* param) {
while (!_saudio.backend.thread_stop) {
int16_t* out_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer];
_saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_NUM_BUFFERS;
int16_t* next_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer];
const int buffer_size_bytes = _saudio.buffer_frames * _saudio.num_channels * (int)sizeof(short);
(*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, out_buffer, (SLuint32)buffer_size_bytes);
_saudio_opensles_fill_buffer();
const int num_samples = _saudio.num_channels * _saudio.buffer_frames;
for (int i = 0; i < num_samples; ++i) {
next_buffer[i] = (int16_t) (_saudio.backend.src_buffer[i] * 0x7FFF);
}
_saudio_semaphore_wait(&_saudio.backend.buffer_sem);
}
return 0;
}
_SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
_saudio.backend.thread_stop = 1;
pthread_join(_saudio.backend.thread, 0);
if (_saudio.backend.player_obj) {
(*_saudio.backend.player_obj)->Destroy(_saudio.backend.player_obj);
}
if (_saudio.backend.output_mix_obj) {
(*_saudio.backend.output_mix_obj)->Destroy(_saudio.backend.output_mix_obj);
}
if (_saudio.backend.engine_obj) {
(*_saudio.backend.engine_obj)->Destroy(_saudio.backend.engine_obj);
}
for (int i = 0; i < SAUDIO_NUM_BUFFERS; i++) {
SOKOL_FREE(_saudio.backend.output_buffers[i]);
}
SOKOL_FREE(_saudio.backend.src_buffer);
}
_SOKOL_PRIVATE bool _saudio_backend_init(void) {
_saudio.bytes_per_frame = (int)sizeof(float) * _saudio.num_channels;
for (int i = 0; i < SAUDIO_NUM_BUFFERS; ++i) {
const int buffer_size_bytes = (int)sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames;
_saudio.backend.output_buffers[i] = (int16_t*) SOKOL_MALLOC((size_t)buffer_size_bytes);
SOKOL_ASSERT(_saudio.backend.output_buffers[i]);
memset(_saudio.backend.output_buffers[i], 0x0, (size_t)buffer_size_bytes);
}
{
const int buffer_size_bytes = _saudio.bytes_per_frame * _saudio.buffer_frames;
_saudio.backend.src_buffer = (float*) SOKOL_MALLOC((size_t)buffer_size_bytes);
SOKOL_ASSERT(_saudio.backend.src_buffer);
memset(_saudio.backend.src_buffer, 0x0, (size_t)buffer_size_bytes);
}
const SLEngineOption opts[] = { SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE };
if (slCreateEngine(&_saudio.backend.engine_obj, 1, opts, 0, NULL, NULL ) != SL_RESULT_SUCCESS) {
SOKOL_LOG("sokol_audio opensles: slCreateEngine failed");
_saudio_backend_shutdown();
return false;
}
(*_saudio.backend.engine_obj)->Realize(_saudio.backend.engine_obj, SL_BOOLEAN_FALSE);
if ((*_saudio.backend.engine_obj)->GetInterface(_saudio.backend.engine_obj, SL_IID_ENGINE, &_saudio.backend.engine) != SL_RESULT_SUCCESS) {
SOKOL_LOG("sokol_audio opensles: GetInterface->Engine failed");
_saudio_backend_shutdown();
return false;
}
{
const SLInterfaceID ids[] = { SL_IID_VOLUME };
const SLboolean req[] = { SL_BOOLEAN_FALSE };
if( (*_saudio.backend.engine)->CreateOutputMix(_saudio.backend.engine, &_saudio.backend.output_mix_obj, 1, ids, req) != SL_RESULT_SUCCESS)
{
SOKOL_LOG("sokol_audio opensles: CreateOutputMix failed");
_saudio_backend_shutdown();
return false;
}
(*_saudio.backend.output_mix_obj)->Realize(_saudio.backend.output_mix_obj, SL_BOOLEAN_FALSE);
if((*_saudio.backend.output_mix_obj)->GetInterface(_saudio.backend.output_mix_obj, SL_IID_VOLUME, &_saudio.backend.output_mix_vol) != SL_RESULT_SUCCESS) {
SOKOL_LOG("sokol_audio opensles: GetInterface->OutputMixVol failed");
}
}
_saudio.backend.in_locator.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
_saudio.backend.in_locator.numBuffers = SAUDIO_NUM_BUFFERS;
SLDataFormat_PCM format;
format.formatType = SL_DATAFORMAT_PCM;
format.numChannels = (SLuint32)_saudio.num_channels;
format.samplesPerSec = (SLuint32) (_saudio.sample_rate * 1000);
format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
format.containerSize = 16;
format.endianness = SL_BYTEORDER_LITTLEENDIAN;
if (_saudio.num_channels == 2) {
format.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
} else {
format.channelMask = SL_SPEAKER_FRONT_CENTER;
}
SLDataSource src;
src.pLocator = &_saudio.backend.in_locator;
src.pFormat = &format;
_saudio.backend.out_locator.locatorType = SL_DATALOCATOR_OUTPUTMIX;
_saudio.backend.out_locator.outputMix = _saudio.backend.output_mix_obj;
_saudio.backend.dst_data_sink.pLocator = &_saudio.backend.out_locator;
_saudio.backend.dst_data_sink.pFormat = NULL;
{
const SLInterfaceID ids[] = { SL_IID_VOLUME, SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
const SLboolean req[] = { SL_BOOLEAN_FALSE, SL_BOOLEAN_TRUE };
(*_saudio.backend.engine)->CreateAudioPlayer(_saudio.backend.engine, &_saudio.backend.player_obj, &src, &_saudio.backend.dst_data_sink, sizeof(ids) / sizeof(ids[0]), ids, req);
(*_saudio.backend.player_obj)->Realize(_saudio.backend.player_obj, SL_BOOLEAN_FALSE);
(*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_PLAY, &_saudio.backend.player);
(*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_VOLUME, &_saudio.backend.player_vol);
(*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &_saudio.backend.player_buffer_queue);
}
{
const int buffer_size_bytes = (int)sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames;
(*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, _saudio.backend.output_buffers[0], (SLuint32)buffer_size_bytes);
_saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_NUM_BUFFERS;
(*_saudio.backend.player)->RegisterCallback(_saudio.backend.player, _saudio_opensles_play_cb, NULL);
(*_saudio.backend.player)->SetCallbackEventsMask(_saudio.backend.player, SL_PLAYEVENT_HEADATEND);
(*_saudio.backend.player)->SetPlayState(_saudio.backend.player, SL_PLAYSTATE_PLAYING);
}
if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_opensles_thread_fn, 0)) {
_saudio_backend_shutdown();
return false;
}
return true;
}
#ifdef __cplusplus
}
#endif
#else
_SOKOL_PRIVATE bool _saudio_backend_init(void) { return false; };
_SOKOL_PRIVATE void _saudio_backend_shutdown(void) { };
#endif
SOKOL_API_IMPL void saudio_setup(const saudio_desc* desc) {
SOKOL_ASSERT(!_saudio.valid);
SOKOL_ASSERT(desc);
memset(&_saudio, 0, sizeof(_saudio));
_saudio.desc = *desc;
_saudio.stream_cb = desc->stream_cb;
_saudio.stream_userdata_cb = desc->stream_userdata_cb;
_saudio.user_data = desc->user_data;
_saudio.sample_rate = _saudio_def(_saudio.desc.sample_rate, _SAUDIO_DEFAULT_SAMPLE_RATE);
_saudio.buffer_frames = _saudio_def(_saudio.desc.buffer_frames, _SAUDIO_DEFAULT_BUFFER_FRAMES);
_saudio.packet_frames = _saudio_def(_saudio.desc.packet_frames, _SAUDIO_DEFAULT_PACKET_FRAMES);
_saudio.num_packets = _saudio_def(_saudio.desc.num_packets, _SAUDIO_DEFAULT_NUM_PACKETS);
_saudio.num_channels = _saudio_def(_saudio.desc.num_channels, 1);
_saudio_fifo_init_mutex(&_saudio.fifo);
if (_saudio_backend_init()) {
if (0 != (_saudio.buffer_frames % _saudio.packet_frames)) {
SOKOL_LOG("sokol_audio.h: actual backend buffer size isn't multiple of requested packet size");
_saudio_backend_shutdown();
return;
}
SOKOL_ASSERT(_saudio.bytes_per_frame > 0);
_saudio_fifo_init(&_saudio.fifo, _saudio.packet_frames * _saudio.bytes_per_frame, _saudio.num_packets);
_saudio.valid = true;
}
}
SOKOL_API_IMPL void saudio_shutdown(void) {
if (_saudio.valid) {
_saudio_backend_shutdown();
_saudio_fifo_shutdown(&_saudio.fifo);
_saudio.valid = false;
}
}
SOKOL_API_IMPL bool saudio_isvalid(void) {
return _saudio.valid;
}
SOKOL_API_IMPL void* saudio_userdata(void) {
return _saudio.desc.user_data;
}
SOKOL_API_IMPL saudio_desc saudio_query_desc(void) {
return _saudio.desc;
}
SOKOL_API_IMPL int saudio_sample_rate(void) {
return _saudio.sample_rate;
}
SOKOL_API_IMPL int saudio_buffer_frames(void) {
return _saudio.buffer_frames;
}
SOKOL_API_IMPL int saudio_channels(void) {
return _saudio.num_channels;
}
SOKOL_API_IMPL int saudio_expect(void) {
if (_saudio.valid) {
const int num_frames = _saudio_fifo_writable_bytes(&_saudio.fifo) / _saudio.bytes_per_frame;
return num_frames;
}
else {
return 0;
}
}
SOKOL_API_IMPL int saudio_push(const float* frames, int num_frames) {
SOKOL_ASSERT(frames && (num_frames > 0));
if (_saudio.valid) {
const int num_bytes = num_frames * _saudio.bytes_per_frame;
const int num_written = _saudio_fifo_write(&_saudio.fifo, (const uint8_t*)frames, num_bytes);
return num_written / _saudio.bytes_per_frame;
}
else {
return 0;
}
}
#undef _saudio_def
#undef _saudio_def_flt
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif