diff --git a/src/android.c b/src/android.c index bd48519..b58cdd0 100644 --- a/src/android.c +++ b/src/android.c @@ -7,15 +7,20 @@ #include "android.h" #include "soundio_private.h" +#include "util.h" static void destroy_android(struct SoundIoPrivate *si) { struct SoundIoAndroid *sia = &si->backend_data.android; - if (sia->cond) + if (sia->cond) { soundio_os_cond_destroy(sia->cond); + sia->cond = NULL; + } - if (sia->engineObject) + if (sia->engineObject) { (*sia->engineObject)->Destroy(sia->engineObject); + sia->engineObject = NULL; + } } static void flush_events_android(struct SoundIoPrivate *si) { @@ -42,20 +47,246 @@ static void force_device_scan_android(struct SoundIoPrivate *si) { // nothing to do; Android devices never change } +static void write_callback_android(SLAndroidSimpleBufferQueueItf bq, void* context) { + struct SoundIoOutStreamPrivate *os = context; + struct SoundIoOutStreamAndroid *osa = &os->backend_data.android; + struct SoundIoOutStream *outstream = &os->pub; + + osa->write_frame_count = 0; + outstream->write_callback(outstream, 1, osa->bytes_per_buffer / + outstream->bytes_per_frame); + + // Sometimes write callbacks return without writing to any buffers. They're + // supposed to honor min_frames, but if they don't, give them a more informative + // error message and keep the stream alive. + if (osa->write_frame_count == 0) { + outstream->error_callback(outstream, SoundIoErrorInvalid); + osa->write_frame_count = 1; + osa->buffers[osa->curBuffer][0] = 0; + } + + if (SL_RESULT_SUCCESS != (*osa->playerBufferQueue)->Enqueue(osa->playerBufferQueue, + osa->buffers[osa->curBuffer], osa->write_frame_count * outstream->bytes_per_frame)) + { + outstream->error_callback(outstream, SoundIoErrorStreaming); + } + + osa->curBuffer ^= 1; +} + +static void outstream_destroy_android(struct SoundIoPrivate *si, + struct SoundIoOutStreamPrivate *os) +{ + struct SoundIoOutStreamAndroid *osa = &os->backend_data.android; + + if (osa->playerObject) + (*osa->playerObject)->Destroy(osa->playerObject); + + if (osa->outputMixObject) + (*osa->outputMixObject)->Destroy(osa->outputMixObject); + + if (osa->buffers[0]) + free(osa->buffers[0]); +} + +static enum SoundIoError outstream_open_android(struct SoundIoPrivate *si, + struct SoundIoOutStreamPrivate *os) +{ + struct SoundIoOutStreamAndroid *osa = &os->backend_data.android; + struct SoundIoAndroid *sia = &si->backend_data.android; + struct SoundIoOutStream *outstream = &os->pub; + struct SoundIoDevice *device = outstream->device; + + if (outstream->software_latency == 0.0) { + outstream->software_latency = soundio_double_clamp( + device->software_latency_min, 1.0, device->software_latency_max); + } + + if (SL_RESULT_SUCCESS != (*sia->engineEngine)->CreateOutputMix(sia->engineEngine, + &osa->outputMixObject, 0, 0, 0)) + { + return SoundIoErrorOpeningDevice; + } + + if(SL_RESULT_SUCCESS != (*osa->outputMixObject)->Realize(osa->outputMixObject, + SL_BOOLEAN_FALSE)) + { + outstream_destroy_android(si, os); + return SoundIoErrorOpeningDevice; + } + + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, + // TODO: The minimum buffer count is 1. When would you want to have + // a single buffer? + 2 + }; + + SLAndroidDataFormat_PCM_EX format_pcm = { + SL_ANDROID_DATAFORMAT_PCM_EX, + outstream->layout.channel_count, + outstream->sample_rate * 1000, + outstream->bytes_per_sample * 8, + outstream->bytes_per_sample * 8, + outstream->layout.channel_count == 1 ? SL_SPEAKER_FRONT_CENTER : + (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT), + SL_BYTEORDER_LITTLEENDIAN, + outstream->format == SoundIoFormatFloat32LE ? + SL_ANDROID_PCM_REPRESENTATION_FLOAT : + outstream->format == SoundIoFormatS16LE ? + SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT : + SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT + }; + + SLDataSource audioSrc = {&loc_bufq, &format_pcm}; + SLDataLocator_OutputMix loc_outmix = { + SL_DATALOCATOR_OUTPUTMIX, + osa->outputMixObject + }; + SLDataSink audioSnk = {&loc_outmix, NULL}; + SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE}; + const SLboolean req[1] = {SL_BOOLEAN_TRUE}; + + if (SL_RESULT_SUCCESS != (*sia->engineEngine)->CreateAudioPlayer(sia->engineEngine, + &osa->playerObject, &audioSrc, &audioSnk, 1, ids, req)) + { + outstream_destroy_android(si, os); + return SoundIoErrorOpeningDevice; + } + if (SL_RESULT_SUCCESS != (*osa->playerObject)->Realize(osa->playerObject, + SL_BOOLEAN_FALSE)) + { + outstream_destroy_android(si, os); + return SoundIoErrorOpeningDevice; + } + + if (SL_RESULT_SUCCESS != (*osa->playerObject)->GetInterface(osa->playerObject, + SL_IID_PLAY, &osa->playerPlay)) + { + outstream_destroy_android(si, os); + return SoundIoErrorOpeningDevice; + } + + if (SL_RESULT_SUCCESS != (*osa->playerObject)->GetInterface(osa->playerObject, + SL_IID_BUFFERQUEUE, &osa->playerBufferQueue)) + { + outstream_destroy_android(si, os); + return SoundIoErrorOpeningDevice; + } + if (SL_RESULT_SUCCESS != (*osa->playerBufferQueue)->RegisterCallback( + osa->playerBufferQueue, write_callback_android, os)) + { + outstream_destroy_android(si, os); + return SoundIoErrorOpeningDevice; + } + + // TODO: An audio stream's latency and jitter can be reduced by using the + // native buffer size. Unfortunately, there is no officially supported way + // to get the native buffer size without access to a JVM. + osa->bytes_per_buffer = outstream->bytes_per_frame * outstream->sample_rate + * outstream->software_latency / 2; + size_t samples_per_buffer = osa->bytes_per_buffer / outstream->bytes_per_sample; + osa->buffers[0] = calloc(samples_per_buffer * 2, outstream->bytes_per_sample); + if (!osa->buffers[0]) { + outstream_destroy_android(si, os); + return SoundIoErrorNoMem; + } + osa->buffers[1] = &osa->buffers[0][osa->bytes_per_buffer]; + + return 0; +} + +static enum SoundIoError outstream_pause_android(struct SoundIoPrivate *si, + struct SoundIoOutStreamPrivate *os, bool pause) +{ + struct SoundIoOutStreamAndroid *osa = &os->backend_data.android; + + if (SL_RESULT_SUCCESS != (*osa->playerPlay)->SetPlayState(osa->playerPlay, + pause ? SL_PLAYSTATE_PAUSED : SL_PLAYSTATE_PLAYING)) + { + return SoundIoErrorInvalid; + } + + return 0; +} + +static enum SoundIoError outstream_start_android(struct SoundIoPrivate *si, + struct SoundIoOutStreamPrivate *os) +{ + struct SoundIoOutStreamAndroid *osa = &os->backend_data.android; + + if (SL_RESULT_SUCCESS != (*osa->playerPlay)->SetPlayState(osa->playerPlay, + SL_PLAYSTATE_PLAYING)) + { + return SoundIoErrorInvalid; + } + write_callback_android(osa->playerBufferQueue, os); + write_callback_android(osa->playerBufferQueue, os); + + return 0; +} + +static enum SoundIoError outstream_begin_write_android(struct SoundIoPrivate *si, + struct SoundIoOutStreamPrivate *os, struct SoundIoChannelArea **out_areas, + int *frame_count) +{ + struct SoundIoOutStream *outstream = &os->pub; + struct SoundIoOutStreamAndroid *osa = &os->backend_data.android; + + if (*frame_count > osa->bytes_per_buffer / outstream->bytes_per_frame) + return SoundIoErrorInvalid; + + char *write_ptr = osa->buffers[osa->curBuffer]; + for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { + osa->areas[ch].ptr = write_ptr + outstream->bytes_per_sample * ch; + osa->areas[ch].step = outstream->bytes_per_frame; + } + osa->write_frame_count = *frame_count; + *out_areas = osa->areas; + + return 0; +} + +static enum SoundIoError outstream_end_write_android(struct SoundIoPrivate *si, + struct SoundIoOutStreamPrivate *os) +{ + return 0; +} + +static enum SoundIoError outstream_clear_buffer_android(struct SoundIoPrivate *si, + struct SoundIoOutStreamPrivate *os) +{ + // TODO: This might be supported, actually. + return SoundIoErrorIncompatibleBackend; +} + +static enum SoundIoError outstream_get_latency_android(struct SoundIoPrivate *si, + struct SoundIoOutStreamPrivate *os, double *out_latency) +{ + struct SoundIoOutStream *outstream = &os->pub; + *out_latency = outstream->software_latency; + return 0; +} + enum SoundIoError soundio_android_init(struct SoundIoPrivate *si) { struct SoundIo *soundio = &si->pub; struct SoundIoAndroid *sia = &si->backend_data.android; - if (slCreateEngine(&sia->engineObject, 0, NULL, 0, NULL, NULL) != SL_RESULT_SUCCESS) + if (SL_RESULT_SUCCESS != slCreateEngine(&sia->engineObject, 0, NULL, 0, NULL, + NULL)) + { return SoundIoErrorInitAudioBackend; + } - if ((*sia->engineObject)->Realize(sia->engineObject, SL_BOOLEAN_FALSE) != SL_RESULT_SUCCESS) + if (SL_RESULT_SUCCESS != (*sia->engineObject)->Realize(sia->engineObject, + SL_BOOLEAN_FALSE)) { destroy_android(si); return SoundIoErrorInitAudioBackend; } - if ((*sia->engineObject)->GetInterface(sia->engineObject, SL_IID_ENGINE, &sia->engineEngine) != SL_RESULT_SUCCESS) + if (SL_RESULT_SUCCESS != (*sia->engineObject)->GetInterface(sia->engineObject, + SL_IID_ENGINE, &sia->engineEngine)) { destroy_android(si); return SoundIoErrorInitAudioBackend; @@ -82,6 +313,15 @@ enum SoundIoError soundio_android_init(struct SoundIoPrivate *si) { si->wakeup = wakeup_android; si->force_device_scan = force_device_scan_android; + si->outstream_open = outstream_open_android; + si->outstream_destroy = outstream_destroy_android; + si->outstream_start = outstream_start_android; + si->outstream_begin_write = outstream_begin_write_android; + si->outstream_end_write = outstream_end_write_android; + si->outstream_clear_buffer = outstream_clear_buffer_android; + si->outstream_pause = outstream_pause_android; + si->outstream_get_latency = outstream_get_latency_android; + // create output device { struct SoundIoDevicePrivate *dev = ALLOCATE(struct SoundIoDevicePrivate, 1); @@ -92,6 +332,7 @@ enum SoundIoError soundio_android_init(struct SoundIoPrivate *si) { struct SoundIoDevice *device = &dev->pub; device->ref_count = 1; + device->aim = SoundIoDeviceAimOutput; device->soundio = soundio; device->id = strdup("android-out"); device->name = strdup("Android Output"); @@ -112,12 +353,15 @@ enum SoundIoError soundio_android_init(struct SoundIoPrivate *si) { destroy_android(si); return SoundIoErrorNoMem; } - device->layouts[0] = *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono); - device->layouts[1] = *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdStereo); + device->layouts[0] = + *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono); + device->layouts[1] = + *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdStereo); - // TODO: Android 5.0 (API level 21) and above support floating-point data. + // Might support additional formats as well? But there's no way to query + // for them. // TODO: Could be prealloc'd? - device->format_count = 2; + device->format_count = 3; device->formats = ALLOCATE(enum SoundIoFormat, device->format_count); if (!device->formats) { soundio_device_unref(device); @@ -126,10 +370,13 @@ enum SoundIoError soundio_android_init(struct SoundIoPrivate *si) { } device->formats[0] = SoundIoFormatU8; device->formats[1] = SoundIoFormatS16LE; + // Floating-point was added in API level 21 / Android 5.0 Lollipop + device->formats[2] = SoundIoFormatFloat32LE; // TODO: Could be prealloc'd? device->sample_rate_count = 9; - device->sample_rates = ALLOCATE(struct SoundIoSampleRateRange, device->sample_rate_count); + device->sample_rates = ALLOCATE(struct SoundIoSampleRateRange, + device->sample_rate_count); if (!device->sample_rates) { soundio_device_unref(device); destroy_android(si); @@ -145,16 +392,27 @@ enum SoundIoError soundio_android_init(struct SoundIoPrivate *si) { device->sample_rates[7].min = device->sample_rates[7].max = 44100; device->sample_rates[8].min = device->sample_rates[8].max = 48000; - // TODO: Not sure what to do with these right now - device->software_latency_current = 0.1; + // TODO: An audio stream's latency can be greatly reduced by using the + // native sampling rate (and thus bypassing software remixing in AudioFlinger + // (a.k.a. the "fast track")). Unfortunately, there is no officially + // supported way to get the native sampling rate without access to a JVM. + device->sample_rate_current = 48000; + + // In general, Android makes no guarantees about audio latency. There + // are hardware feature flags that do make guarantees: + // - android.hardware.audio.low_latency: output latency of <=45ms + // - android.hardware.audio.pro: round-trip latency of <=20ms + // There is currently no way to check these flags without access to a + // JVM. The following number is a very optimistic lower-bound. device->software_latency_min = 0.01; + + // TODO: These numbers are completely made up + device->software_latency_current = 0.1; device->software_latency_max = 4.0; - // TODO: Any way to get device optimal sample rate here? - device->sample_rate_current = 48000; - device->aim = SoundIoDeviceAimOutput; - - if (SoundIoListDevicePtr_append(&si->safe_devices_info->output_devices, device)) { + if (SoundIoListDevicePtr_append(&si->safe_devices_info->output_devices, + device)) + { soundio_device_unref(device); destroy_android(si); return SoundIoErrorNoMem; diff --git a/src/android.h b/src/android.h index d6a2ffc..4814b13 100644 --- a/src/android.h +++ b/src/android.h @@ -9,7 +9,9 @@ #define SOUNDIO_ANDROID_H #include +#include +#include "soundio_internal.h" #include "os.h" #include "soundio_internal.h" @@ -24,4 +26,17 @@ struct SoundIoAndroid { SLEngineItf engineEngine; }; +struct SoundIoOutStreamAndroid { + SLObjectItf outputMixObject; + SLObjectItf playerObject; + SLPlayItf playerPlay; + SLAndroidSimpleBufferQueueItf playerBufferQueue; + + int curBuffer; + size_t write_frame_count; + size_t bytes_per_buffer; + char *buffers[2]; + struct SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; +}; + #endif diff --git a/src/soundio_private.h b/src/soundio_private.h index 94b1b57..0e558f1 100644 --- a/src/soundio_private.h +++ b/src/soundio_private.h @@ -94,6 +94,9 @@ union SoundIoOutStreamBackendData { #endif #ifdef SOUNDIO_HAVE_WASAPI struct SoundIoOutStreamWasapi wasapi; +#endif +#ifdef SOUNDIO_HAVE_ANDROID + struct SoundIoOutStreamAndroid android; #endif struct SoundIoOutStreamDummy dummy; };