From dfe2ac7f86acec41476e2d53f3517cf8ac79db52 Mon Sep 17 00:00:00 2001 From: Michael Maltese Date: Fri, 28 Oct 2016 17:30:24 -0700 Subject: [PATCH 1/9] Add (empty) Android OpenSL ES backend --- CMakeLists.txt | 33 ++++++++++++++++++++++++++++----- cmake/FindAndroidOpenSLES.cmake | 16 ++++++++++++++++ soundio/soundio.h | 1 + src/android.c | 13 +++++++++++++ src/android.h | 16 ++++++++++++++++ src/config.h.in | 1 + src/soundio.c | 10 ++++++++++ src/soundio_private.h | 4 ++++ 8 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 cmake/FindAndroidOpenSLES.cmake create mode 100644 src/android.c create mode 100644 src/android.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ce04ab0..1862410 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ option(ENABLE_PULSEAUDIO "Enable PulseAudio backend" ON) option(ENABLE_ALSA "Enable ALSA backend" ON) option(ENABLE_COREAUDIO "Enable CoreAudio backend" ON) option(ENABLE_WASAPI "Enable WASAPI backend" ON) +option(ENABLE_ANDROID "Enable Android OpenSL ES backend" ON) find_package(Threads) if(Threads_FOUND) @@ -138,6 +139,21 @@ else() set(SOUNDIO_HAVE_WASAPI false) endif() +if(ENABLE_ANDROID) + find_package(AndroidOpenSLES) + if(ANDROID_OPENSLES_FOUND) + set(STATUS_ANDROID "OK") + set(SOUNDIO_HAVE_ANDROID true) + else() + set(STATUS_ANDROID "not found") + set(SOUNDIO_HAVE_ANDROID false) + set(ANDROID_OPENSLES_LIBRARY "") + endif() +else() + set(STATUS_ANDROID "disabled") + set(SOUNDIO_HAVE_ANDROID false) + set(ANDROID_OPENSLES_LIBRARY "") +endif() set(LIBSOUNDIO_SOURCES "${libsoundio_SOURCE_DIR}/src/soundio.c" @@ -179,6 +195,11 @@ if(SOUNDIO_HAVE_WASAPI) "${libsoundio_SOURCE_DIR}/src/wasapi.c" ) endif() +if(SOUNDIO_HAVE_ANDROID) + set(LIBSOUNDIO_SOURCES ${LIBSOUNDIO_SOURCES} + "${libsoundio_SOURCE_DIR}/src/android.c" + ) +endif() include_directories( ${libsoundio_SOURCE_DIR} @@ -195,6 +216,7 @@ set(LIBSOUNDIO_LIBS ${COREFOUNDATION_LIBRARY} ${AUDIOUNIT_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} + ${ANDROID_OPENSLES_LIBRARY} ) if(MSVC) @@ -380,9 +402,10 @@ message( "System Dependencies\n" "-------------------\n" "* threads : ${STATUS_THREADS}\n" - "* JACK (optional) : ${STATUS_JACK}\n" - "* PulseAudio (optional) : ${STATUS_PULSEAUDIO}\n" - "* ALSA (optional) : ${STATUS_ALSA}\n" - "* CoreAudio (optional) : ${STATUS_COREAUDIO}\n" - "* WASAPI (optional) : ${STATUS_WASAPI}\n" + "* JACK (optional) : ${STATUS_JACK}\n" + "* PulseAudio (optional) : ${STATUS_PULSEAUDIO}\n" + "* ALSA (optional) : ${STATUS_ALSA}\n" + "* CoreAudio (optional) : ${STATUS_COREAUDIO}\n" + "* WASAPI (optional) : ${STATUS_WASAPI}\n" + "* Android OpenSL ES (optional) : ${STATUS_ANDROID}\n" ) diff --git a/cmake/FindAndroidOpenSLES.cmake b/cmake/FindAndroidOpenSLES.cmake new file mode 100644 index 0000000..22e406b --- /dev/null +++ b/cmake/FindAndroidOpenSLES.cmake @@ -0,0 +1,16 @@ +# Copyright (c) 2016 libsoundio +# This file is MIT licensed. +# See http://opensource.org/licenses/MIT + +# ANDROID_OPENSLES_FOUND +# ANDROID_OPENSLES_INCLUDE_DIR +# ANDROID_OPENSLES_LIBRARY + +find_path(ANDROID_OPENSLES_INCLUDE_DIR NAMES SLES/OpenSLES_Android.h) + +find_library(ANDROID_OPENSLES_LIBRARY NAMES OpenSLES) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(ANDROID_OPENSLES DEFAULT_MSG ANDROID_OPENSLES_LIBRARY ANDROID_OPENSLES_INCLUDE_DIR) + +mark_as_advanced(ANDROID_OPENSLES_INCLUDE_DIR ANDROID_OPENSLES_LIBRARY) diff --git a/soundio/soundio.h b/soundio/soundio.h index 9890ba6..ede75f6 100644 --- a/soundio/soundio.h +++ b/soundio/soundio.h @@ -223,6 +223,7 @@ enum SoundIoBackend { SoundIoBackendAlsa, SoundIoBackendCoreAudio, SoundIoBackendWasapi, + SoundIoBackendAndroid }; enum SoundIoDeviceAim { diff --git a/src/android.c b/src/android.c new file mode 100644 index 0000000..4f78e31 --- /dev/null +++ b/src/android.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2016 libsoundio + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#include "android.h" +#include "soundio_private.h" + +enum SoundIoError soundio_android_init(struct SoundIoPrivate *si) { + return SoundIoErrorInitAudioBackend; +} diff --git a/src/android.h b/src/android.h new file mode 100644 index 0000000..5f97072 --- /dev/null +++ b/src/android.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2016 libsoundio + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#ifndef SOUNDIO_ANDROID_H +#define SOUNDIO_ANDROID_H + +#include "soundio_internal.h" + +struct SoundIoPrivate; +enum SoundIoError soundio_android_init(struct SoundIoPrivate *si); + +#endif diff --git a/src/config.h.in b/src/config.h.in index ff22769..18cf08c 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -18,5 +18,6 @@ #cmakedefine SOUNDIO_HAVE_ALSA #cmakedefine SOUNDIO_HAVE_COREAUDIO #cmakedefine SOUNDIO_HAVE_WASAPI +#cmakedefine SOUNDIO_HAVE_ANDROID #endif diff --git a/src/soundio.c b/src/soundio.c index 273b9cf..98fc845 100644 --- a/src/soundio.c +++ b/src/soundio.c @@ -29,6 +29,9 @@ static const enum SoundIoBackend available_backends[] = { #endif #ifdef SOUNDIO_HAVE_WASAPI SoundIoBackendWasapi, +#endif +#ifdef SOUNDIO_HAVE_ANDROID + SoundIoBackendAndroid, #endif SoundIoBackendDummy, }; @@ -67,6 +70,12 @@ static backend_init_t backend_init_fns[] = { NULL, #endif +#ifdef SOUNDIO_HAVE_ANDROID + soundio_android_init, +#else + NULL, +#endif + &soundio_dummy_init, }; @@ -165,6 +174,7 @@ const char *soundio_backend_name(enum SoundIoBackend backend) { case SoundIoBackendAlsa: return "ALSA"; case SoundIoBackendCoreAudio: return "CoreAudio"; case SoundIoBackendWasapi: return "WASAPI"; + case SoundIoBackendAndroid: return "Android OpenSL ES"; case SoundIoBackendDummy: return "Dummy"; } return "(invalid backend)"; diff --git a/src/soundio_private.h b/src/soundio_private.h index 1e1ab4a..3528b32 100644 --- a/src/soundio_private.h +++ b/src/soundio_private.h @@ -32,6 +32,10 @@ #include "wasapi.h" #endif +#ifdef SOUNDIO_HAVE_ANDROID +#include "android.h" +#endif + #include "dummy.h" union SoundIoBackendData { From e75329f585080b844ac4c028e7fe0bb156ea340e Mon Sep 17 00:00:00 2001 From: Michael Maltese Date: Sun, 30 Oct 2016 15:08:46 -0700 Subject: [PATCH 2/9] Android: open-able output device --- src/android.c | 139 +++++++++++++++++++++++++++++++++++++++++- src/android.h | 6 ++ src/soundio_private.h | 3 + 3 files changed, 146 insertions(+), 2 deletions(-) diff --git a/src/android.c b/src/android.c index 4f78e31..016627c 100644 --- a/src/android.c +++ b/src/android.c @@ -8,6 +8,141 @@ #include "android.h" #include "soundio_private.h" -enum SoundIoError soundio_android_init(struct SoundIoPrivate *si) { - return SoundIoErrorInitAudioBackend; +static void destroy_android(struct SoundIoPrivate *si) { + struct SoundIoAndroid *sia = &si->backend_data.android; + + if (sia->cond) + soundio_os_cond_destroy(sia->cond); + +} + +static void flush_events_android(struct SoundIoPrivate *si) { + struct SoundIo *soundio = &si->pub; + struct SoundIoAndroid *sia = &si->backend_data.android; + if (sia->devices_emitted) + return; + sia->devices_emitted = true; + soundio->on_devices_change(soundio); +} + +static void wait_events_android(struct SoundIoPrivate *si) { + struct SoundIoAndroid *sia = &si->backend_data.android; + flush_events_android(si); + soundio_os_cond_wait(sia->cond, NULL); +} + +static void wakeup_android(struct SoundIoPrivate *si) { + struct SoundIoAndroid *sia = &si->backend_data.android; + soundio_os_cond_signal(sia->cond, NULL); +} + +static void force_device_scan_android(struct SoundIoPrivate *si) { + // nothing to do; Android devices never change +} + +enum SoundIoError soundio_android_init(struct SoundIoPrivate *si) { + struct SoundIo *soundio = &si->pub; + struct SoundIoAndroid *sia = &si->backend_data.android; + + sia->cond = soundio_os_cond_create(); + if (!sia->cond) { + destroy_android(si); + return SoundIoErrorNoMem; + } + + si->safe_devices_info = ALLOCATE(struct SoundIoDevicesInfo, 1); + if (!si->safe_devices_info) { + destroy_android(si); + return SoundIoErrorNoMem; + } + + // TODO: no input devices yet + si->safe_devices_info->default_input_index = -1; + si->safe_devices_info->default_output_index = 0; + si->destroy = destroy_android; + si->flush_events = flush_events_android; + si->wait_events = wait_events_android; + si->wakeup = wakeup_android; + si->force_device_scan = force_device_scan_android; + + // create output device + { + struct SoundIoDevicePrivate *dev = ALLOCATE(struct SoundIoDevicePrivate, 1); + if (!dev) { + destroy_android(si); + return SoundIoErrorNoMem; + } + struct SoundIoDevice *device = &dev->pub; + + device->ref_count = 1; + device->soundio = soundio; + device->id = strdup("android-out"); + device->name = strdup("Android Output"); + if (!device->id || !device->name) { + soundio_device_unref(device); + destroy_android(si); + return SoundIoErrorNoMem; + } + + // The following information is according to: + // https://developer.android.com/ndk/guides/audio/opensl-for-android.html + + // TODO: could be prealloc'd? + device->layout_count = 2; + device->layouts = ALLOCATE(struct SoundIoChannelLayout, device->layout_count); + if (!device->layouts) { + soundio_device_unref(device); + destroy_android(si); + return SoundIoErrorNoMem; + } + 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. + // TODO: Could be prealloc'd? + device->format_count = 2; + device->formats = ALLOCATE(enum SoundIoFormat, device->format_count); + if (!device->formats) { + soundio_device_unref(device); + destroy_android(si); + return SoundIoErrorNoMem; + } + device->formats[0] = SoundIoFormatU8; + device->formats[1] = SoundIoFormatS16LE; + + // TODO: Could be prealloc'd? + device->sample_rate_count = 9; + device->sample_rates = ALLOCATE(struct SoundIoSampleRateRange, device->sample_rate_count); + if (!device->sample_rates) { + soundio_device_unref(device); + destroy_android(si); + return SoundIoErrorNoMem; + } + device->sample_rates[0].min = device->sample_rates[0].max = 8000; + device->sample_rates[1].min = device->sample_rates[1].max = 11025; + device->sample_rates[2].min = device->sample_rates[2].max = 12000; + device->sample_rates[3].min = device->sample_rates[3].max = 16000; + device->sample_rates[4].min = device->sample_rates[4].max = 22050; + device->sample_rates[5].min = device->sample_rates[5].max = 24000; + device->sample_rates[6].min = device->sample_rates[6].max = 32000; + 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; + device->software_latency_min = 0.01; + 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)) { + soundio_device_unref(device); + destroy_android(si); + return SoundIoErrorNoMem; + } + } + + return SoundIoErrorNone; } diff --git a/src/android.h b/src/android.h index 5f97072..a71ef9b 100644 --- a/src/android.h +++ b/src/android.h @@ -8,9 +8,15 @@ #ifndef SOUNDIO_ANDROID_H #define SOUNDIO_ANDROID_H +#include "os.h" #include "soundio_internal.h" struct SoundIoPrivate; enum SoundIoError soundio_android_init(struct SoundIoPrivate *si); +struct SoundIoAndroid { + struct SoundIoOsCond *cond; + bool devices_emitted; +}; + #endif diff --git a/src/soundio_private.h b/src/soundio_private.h index 3528b32..94b1b57 100644 --- a/src/soundio_private.h +++ b/src/soundio_private.h @@ -53,6 +53,9 @@ union SoundIoBackendData { #endif #ifdef SOUNDIO_HAVE_WASAPI struct SoundIoWasapi wasapi; +#endif +#ifdef SOUNDIO_HAVE_ANDROID + struct SoundIoAndroid android; #endif struct SoundIoDummy dummy; }; From 852403f4051b6433958307e954d0e1a4fd2b90aa Mon Sep 17 00:00:00 2001 From: Michael Maltese Date: Sun, 30 Oct 2016 16:00:36 -0700 Subject: [PATCH 3/9] Android: create and destroy OpenSL ES engine --- src/android.c | 17 +++++++++++++++++ src/android.h | 5 +++++ 2 files changed, 22 insertions(+) diff --git a/src/android.c b/src/android.c index 016627c..bd48519 100644 --- a/src/android.c +++ b/src/android.c @@ -14,6 +14,8 @@ static void destroy_android(struct SoundIoPrivate *si) { if (sia->cond) soundio_os_cond_destroy(sia->cond); + if (sia->engineObject) + (*sia->engineObject)->Destroy(sia->engineObject); } static void flush_events_android(struct SoundIoPrivate *si) { @@ -44,6 +46,21 @@ 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) + return SoundIoErrorInitAudioBackend; + + if ((*sia->engineObject)->Realize(sia->engineObject, SL_BOOLEAN_FALSE) != SL_RESULT_SUCCESS) + { + destroy_android(si); + return SoundIoErrorInitAudioBackend; + } + + if ((*sia->engineObject)->GetInterface(sia->engineObject, SL_IID_ENGINE, &sia->engineEngine) != SL_RESULT_SUCCESS) + { + destroy_android(si); + return SoundIoErrorInitAudioBackend; + } + sia->cond = soundio_os_cond_create(); if (!sia->cond) { destroy_android(si); diff --git a/src/android.h b/src/android.h index a71ef9b..d6a2ffc 100644 --- a/src/android.h +++ b/src/android.h @@ -8,6 +8,8 @@ #ifndef SOUNDIO_ANDROID_H #define SOUNDIO_ANDROID_H +#include + #include "os.h" #include "soundio_internal.h" @@ -17,6 +19,9 @@ enum SoundIoError soundio_android_init(struct SoundIoPrivate *si); struct SoundIoAndroid { struct SoundIoOsCond *cond; bool devices_emitted; + + SLObjectItf engineObject; + SLEngineItf engineEngine; }; #endif From 22e13b1c48a411dee15b69e2acc8754d69266577 Mon Sep 17 00:00:00 2001 From: Michael Maltese Date: Mon, 31 Oct 2016 01:48:05 -0700 Subject: [PATCH 4/9] Android: implement functioning outstream --- src/android.c | 292 +++++++++++++++++++++++++++++++++++++++--- src/android.h | 15 +++ src/soundio_private.h | 3 + 3 files changed, 293 insertions(+), 17 deletions(-) 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; }; From ddbe4a8bf2037a96a8796d3e05af0d8530966efd Mon Sep 17 00:00:00 2001 From: Michael Maltese Date: Mon, 31 Oct 2016 16:33:32 -0700 Subject: [PATCH 5/9] android: input device support --- src/android.c | 397 ++++++++++++++++++++++++++++++++++++++++-- src/android.h | 11 ++ src/soundio_private.h | 3 + 3 files changed, 394 insertions(+), 17 deletions(-) diff --git a/src/android.c b/src/android.c index b58cdd0..73da69f 100644 --- a/src/android.c +++ b/src/android.c @@ -47,6 +47,37 @@ static void force_device_scan_android(struct SoundIoPrivate *si) { // nothing to do; Android devices never change } +static SLAndroidDataFormat_PCM_EX build_format_pcm_android(enum SoundIoFormat format, + int channel_count, int sample_rate) +{ + // SLAndroidDataFormat_PCM_EX is backwards compatible with the deprecated + // SLDataFormat_PCM, but uses a different formatType. This new formatType + // is supported for output starting at API level 21, and for input starting + // at API level 23. It's only required for floating-point data, so for U8 and + // S16 data we use the old way. + SLAndroidDataFormat_PCM_EX format_pcm = { + .formatType = format == SoundIoFormatFloat32LE ? + SL_ANDROID_DATAFORMAT_PCM_EX : SL_DATAFORMAT_PCM, + .numChannels = channel_count, + .sampleRate = sample_rate * 1000, + .bitsPerSample = soundio_get_bytes_per_sample(format) * 8, + .containerSize = soundio_get_bytes_per_sample(format) * 8, + .channelMask = channel_count == 2 ? + SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT : + SL_SPEAKER_FRONT_CENTER, + .endianness = SL_BYTEORDER_LITTLEENDIAN, + + // Only the float representation matters, since otherwise we use SL_DATAFORMAT_PCM, + // but it's nice for correctness. + .representation = format == SoundIoFormatFloat32LE ? + SL_ANDROID_PCM_REPRESENTATION_FLOAT : + format == SoundIoFormatS16LE ? + SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT : + SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT + }; + return format_pcm; +} + static void write_callback_android(SLAndroidSimpleBufferQueueItf bq, void* context) { struct SoundIoOutStreamPrivate *os = context; struct SoundIoOutStreamAndroid *osa = &os->backend_data.android; @@ -122,21 +153,11 @@ static enum SoundIoError outstream_open_android(struct SoundIoPrivate *si, 2 }; - SLAndroidDataFormat_PCM_EX format_pcm = { - SL_ANDROID_DATAFORMAT_PCM_EX, + SLAndroidDataFormat_PCM_EX format_pcm = build_format_pcm_android( + outstream->format, 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 - }; + outstream->sample_rate + ); SLDataSource audioSrc = {&loc_bufq, &format_pcm}; SLDataLocator_OutputMix loc_outmix = { @@ -268,6 +289,229 @@ static enum SoundIoError outstream_get_latency_android(struct SoundIoPrivate *si return 0; } +static void instream_destroy_android(struct SoundIoPrivate *si, + struct SoundIoInStreamPrivate *is) +{ + struct SoundIoInStreamAndroid *isa = &is->backend_data.android; + + if (isa->audioRecorderObject) + (*isa->audioRecorderObject)->Destroy(isa->audioRecorderObject); + + if (isa->buffers[0]) + free(isa->buffers[0]); +} + +static void read_callback_android(SLAndroidSimpleBufferQueueItf bq, void* context) { + struct SoundIoInStreamPrivate *is = context; + struct SoundIoInStreamAndroid *isa = &is->backend_data.android; + struct SoundIoInStream *instream = &is->pub; + + int frame_count = isa->bytes_per_buffer / instream->bytes_per_frame; + instream->read_callback(instream, frame_count, frame_count); + + if (SL_RESULT_SUCCESS != (*isa->recorderBufferQueue)->Enqueue( + isa->recorderBufferQueue, isa->buffers[isa->curBuffer], isa->bytes_per_buffer)) + { + instream->error_callback(instream, SoundIoErrorStreaming); + return; + } + isa->curBuffer ^= 1; +} + +static enum SoundIoError instream_open_android(struct SoundIoPrivate *si, + struct SoundIoInStreamPrivate *is) +{ + struct SoundIoInStreamAndroid *isa = &is->backend_data.android; + struct SoundIoAndroid *sia = &si->backend_data.android; + struct SoundIoInStream *instream = &is->pub; + struct SoundIoDevice *device = instream->device; + + if (instream->software_latency == 0.0) { + instream->software_latency = soundio_double_clamp( + device->software_latency_min, 1.0, device->software_latency_max); + } + + SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, + SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL}; + SLDataSource audioSrc = {&loc_dev, NULL}; + + SLDataLocator_AndroidSimpleBufferQueue loc_bq = { + 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 = build_format_pcm_android( + instream->format, + instream->layout.channel_count, + instream->sample_rate + ); + SLDataSink audioSnk = {&loc_bq, &format_pcm}; + + const SLInterfaceID ids[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; + const SLboolean reqs[1] = {SL_BOOLEAN_TRUE}; + + if (SL_RESULT_SUCCESS != (*sia->engineEngine)->CreateAudioRecorder( + sia->engineEngine, &isa->audioRecorderObject, &audioSrc, &audioSnk, 1, ids, reqs)) + { + instream_destroy_android(si, is); + return SoundIoErrorOpeningDevice; + } + + if (SL_RESULT_SUCCESS != (*isa->audioRecorderObject)->Realize(isa->audioRecorderObject, + SL_BOOLEAN_FALSE)) + { + // TODO: On an LG G4 running Android 6.0 / API 23, the error code is + // SL_RESULT_CONTENT_UNSUPPORTED when a process does not have permission + // to record. Are there any other situations where this error code is seen? + instream_destroy_android(si, is); + return SoundIoErrorOpeningDevice; + } + + if(SL_RESULT_SUCCESS != (*isa->audioRecorderObject)->GetInterface( + isa->audioRecorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &isa->recorderBufferQueue)) + { + instream_destroy_android(si, is); + return SoundIoErrorOpeningDevice; + } + + if(SL_RESULT_SUCCESS != (*isa->recorderBufferQueue)->RegisterCallback( + isa->recorderBufferQueue, read_callback_android, is)) + { + instream_destroy_android(si, is); + return SoundIoErrorOpeningDevice; + } + + if (SL_RESULT_SUCCESS != (*isa->audioRecorderObject)->GetInterface(isa->audioRecorderObject, + SL_IID_RECORD, &isa->recorderRecord)) { + instream_destroy_android(si, is); + return SoundIoErrorOpeningDevice; + } + + // TODO: The documentation seems to imply that the optimal output buffer size + // is also the optimal input buffer size. See the documentation on SoundIoOutStreamAndroid + // for more information. + isa->bytes_per_buffer = instream->bytes_per_frame * instream->sample_rate + * instream->software_latency / 2; + size_t samples_per_buffer = isa->bytes_per_buffer / instream->bytes_per_sample; + isa->buffers[0] = calloc(samples_per_buffer * 2, instream->bytes_per_sample); + if (!isa->buffers[0]) { + instream_destroy_android(si, is); + return SoundIoErrorNoMem; + } + isa->buffers[1] = &isa->buffers[0][isa->bytes_per_buffer]; + + return 0; +} + +static enum SoundIoError instream_pause_android(struct SoundIoPrivate *si, + struct SoundIoInStreamPrivate *is, bool pause) +{ + struct SoundIoInStreamAndroid *isa = &is->backend_data.android; + + if (SL_RESULT_SUCCESS != (*isa->recorderRecord)->SetRecordState(isa->recorderRecord, + pause ? SL_RECORDSTATE_PAUSED : SL_RECORDSTATE_RECORDING)) + { + return SoundIoErrorInvalid; + } + + return 0; +} + +static enum SoundIoError instream_start_android(struct SoundIoPrivate *si, + struct SoundIoInStreamPrivate *is) +{ + struct SoundIoInStreamAndroid *isa = &is->backend_data.android; + + if (SL_RESULT_SUCCESS != (*isa->recorderRecord)->SetRecordState(isa->recorderRecord, + SL_RECORDSTATE_RECORDING)) + { + return SoundIoErrorInvalid; + } + + if (SL_RESULT_SUCCESS != (*isa->recorderBufferQueue)->Enqueue( + isa->recorderBufferQueue, isa->buffers[isa->curBuffer], isa->bytes_per_buffer)) + { + return SoundIoErrorOpeningDevice; + } + if (SL_RESULT_SUCCESS != (*isa->recorderBufferQueue)->Enqueue( + isa->recorderBufferQueue, isa->buffers[isa->curBuffer ^ 1], isa->bytes_per_buffer)) + { + return SoundIoErrorOpeningDevice; + } + + return 0; +} + +static enum SoundIoError instream_begin_read_android(struct SoundIoPrivate *si, + struct SoundIoInStreamPrivate *is, struct SoundIoChannelArea **out_areas, + int *frame_count) +{ + struct SoundIoInStream *instream = &is->pub; + struct SoundIoInStreamAndroid *isa = &is->backend_data.android; + + if (*frame_count != isa->bytes_per_buffer / instream->bytes_per_frame) + return SoundIoErrorInvalid; + + char *read_ptr = isa->buffers[isa->curBuffer]; + for (int ch = 0; ch < instream->layout.channel_count; ch += 1) { + isa->areas[ch].ptr = read_ptr + instream->bytes_per_sample * ch; + isa->areas[ch].step = instream->bytes_per_frame; + } + *out_areas = isa->areas; + + return 0; +} + +static enum SoundIoError instream_end_read_android(struct SoundIoPrivate *si, + struct SoundIoInStreamPrivate *is) +{ + return 0; +} + +static enum SoundIoError instream_get_latency_android(struct SoundIoPrivate *si, + struct SoundIoInStreamPrivate *is, double *out_latency) +{ + struct SoundIoInStream *instream = &is->pub; + *out_latency = instream->software_latency; + return 0; +} + +static enum SoundIoError check_input_floating_point_support(struct SoundIoPrivate *si) { + struct SoundIoAndroid *sia = &si->backend_data.android; + + SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, + SL_DEFAULTDEVICEID_AUDIOINPUT, NULL}; + SLDataSource audioSrc = {&loc_dev, NULL}; + + SLDataLocator_AndroidSimpleBufferQueue loc_bq = { + SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; + + SLAndroidDataFormat_PCM_EX format_pcm = build_format_pcm_android( + SoundIoFormatFloat32LE, + // TODO: do the rest of these parameters always work? + 2, // channel count + 48000 // sample rate + ); + SLDataSink audioSnk = {&loc_bq, &format_pcm}; + + const SLInterfaceID ids[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; + const SLboolean reqs[1] = {SL_BOOLEAN_TRUE}; + + SLObjectItf audioRecorderObject; + SLuint32 status = (*sia->engineEngine)->CreateAudioRecorder( + sia->engineEngine, &audioRecorderObject, &audioSrc, &audioSnk, 1, ids, reqs); + if (audioRecorderObject) + (*audioRecorderObject)->Destroy(audioRecorderObject); + + if (SL_RESULT_SUCCESS == status) + return SoundIoErrorNone; + else if (SL_RESULT_PARAMETER_INVALID == status) + return SoundIoErrorIncompatibleDevice; + else + return SoundIoErrorOpeningDevice; +} + enum SoundIoError soundio_android_init(struct SoundIoPrivate *si) { struct SoundIo *soundio = &si->pub; struct SoundIoAndroid *sia = &si->backend_data.android; @@ -304,9 +548,6 @@ enum SoundIoError soundio_android_init(struct SoundIoPrivate *si) { return SoundIoErrorNoMem; } - // TODO: no input devices yet - si->safe_devices_info->default_input_index = -1; - si->safe_devices_info->default_output_index = 0; si->destroy = destroy_android; si->flush_events = flush_events_android; si->wait_events = wait_events_android; @@ -322,7 +563,16 @@ enum SoundIoError soundio_android_init(struct SoundIoPrivate *si) { si->outstream_pause = outstream_pause_android; si->outstream_get_latency = outstream_get_latency_android; + si->instream_open = instream_open_android; + si->instream_destroy = instream_destroy_android; + si->instream_start = instream_start_android; + si->instream_begin_read = instream_begin_read_android; + si->instream_end_read = instream_end_read_android; + si->instream_pause = instream_pause_android; + si->instream_get_latency = instream_get_latency_android; + // create output device + si->safe_devices_info->default_output_index = 0; { struct SoundIoDevicePrivate *dev = ALLOCATE(struct SoundIoDevicePrivate, 1); if (!dev) { @@ -419,5 +669,118 @@ enum SoundIoError soundio_android_init(struct SoundIoPrivate *si) { } } + // create input device + // TODO: Should an input device still be created if the application doesn't + // have permission to access recording devices? + // TODO: Additional recording profiles, such as CAMCORDER and VOICE_RECOGNITION, + // are described in . Should these be + // exposed? How? + si->safe_devices_info->default_input_index = 0; + { + struct SoundIoDevicePrivate *dev = ALLOCATE(struct SoundIoDevicePrivate, 1); + if (!dev) { + destroy_android(si); + return SoundIoErrorNoMem; + } + struct SoundIoDevice *device = &dev->pub; + + device->ref_count = 1; + device->aim = SoundIoDeviceAimInput; + device->soundio = soundio; + device->id = strdup("android-in"); + device->name = strdup("Android Generic Input"); + if (!device->id || !device->name) { + soundio_device_unref(device); + destroy_android(si); + return SoundIoErrorNoMem; + } + + // TODO: Is there a case where these don't work? + // TODO: could be prealloc'd? + device->layout_count = 2; + device->layouts = ALLOCATE(struct SoundIoChannelLayout, device->layout_count); + if (!device->layouts) { + soundio_device_unref(device); + destroy_android(si); + return SoundIoErrorNoMem; + } + device->layouts[0] = + *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono); + device->layouts[1] = + *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdStereo); + + // TODO: Might support additional formats as well? + // TODO: Could be prealloc'd? + device->format_count = 3; + device->formats = ALLOCATE(enum SoundIoFormat, device->format_count); + if (!device->formats) { + soundio_device_unref(device); + destroy_android(si); + return SoundIoErrorNoMem; + } + device->formats[0] = SoundIoFormatU8; + device->formats[1] = SoundIoFormatS16LE; + { + // Floating-point was added in API level 23 / Android M + enum SoundIoError err = check_input_floating_point_support(si); + if (SoundIoErrorNone == err) { + device->formats[2] = SoundIoFormatFloat32LE; + } else if (SoundIoErrorIncompatibleDevice == err) { + device->format_count -= 1; + } else { + soundio_device_unref(device); + destroy_android(si); + return err; + } + } + + // TODO: Could be prealloc'd? + // TODO: Supposedly, not all devices support all sample rates. This has + // only been tested on an LG G4 with Android 6.0 / API level 23, where + // all sample rates seem to be supported. + device->sample_rate_count = 9; + device->sample_rates = ALLOCATE(struct SoundIoSampleRateRange, + device->sample_rate_count); + if (!device->sample_rates) { + soundio_device_unref(device); + destroy_android(si); + return SoundIoErrorNoMem; + } + device->sample_rates[0].min = device->sample_rates[0].max = 8000; + device->sample_rates[1].min = device->sample_rates[1].max = 11025; + device->sample_rates[2].min = device->sample_rates[2].max = 12000; + device->sample_rates[3].min = device->sample_rates[3].max = 16000; + device->sample_rates[4].min = device->sample_rates[4].max = 22050; + device->sample_rates[5].min = device->sample_rates[5].max = 24000; + device->sample_rates[6].min = device->sample_rates[6].max = 32000; + device->sample_rates[7].min = device->sample_rates[7].max = 44100; + device->sample_rates[8].min = device->sample_rates[8].max = 48000; + + // TODO: The documentation seems to imply that the optimal output sample + // rate is also the optimal input sample rate. See the documentation on + // SoundIoOutStreamAndroid for more information. + 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; + + if (SoundIoListDevicePtr_append(&si->safe_devices_info->input_devices, + device)) + { + soundio_device_unref(device); + destroy_android(si); + return SoundIoErrorNoMem; + } + } + return SoundIoErrorNone; } diff --git a/src/android.h b/src/android.h index 4814b13..5bb910e 100644 --- a/src/android.h +++ b/src/android.h @@ -39,4 +39,15 @@ struct SoundIoOutStreamAndroid { struct SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; }; +struct SoundIoInStreamAndroid { + SLObjectItf audioRecorderObject; + SLRecordItf recorderRecord; + SLAndroidSimpleBufferQueueItf recorderBufferQueue; + + int curBuffer; + 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 0e558f1..6aecf5e 100644 --- a/src/soundio_private.h +++ b/src/soundio_private.h @@ -116,6 +116,9 @@ union SoundIoInStreamBackendData { #endif #ifdef SOUNDIO_HAVE_WASAPI struct SoundIoInStreamWasapi wasapi; +#endif +#ifdef SOUNDIO_HAVE_ANDROID + struct SoundIoInStreamAndroid android; #endif struct SoundIoInStreamDummy dummy; }; From dd74dd773d32707f1c06350d19c6a2fc4dfb18e3 Mon Sep 17 00:00:00 2001 From: Michael Maltese Date: Thu, 3 Nov 2016 23:18:36 -0700 Subject: [PATCH 6/9] android: ashmem implementation of mirrored memory --- src/os.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/os.c b/src/os.c index b542ad3..e2949e4 100644 --- a/src/os.c +++ b/src/os.c @@ -78,6 +78,12 @@ #include #endif +#ifdef __ANDROID__ +#include +#include +#include +#endif + struct SoundIoOsThread { #if defined(SOUNDIO_OS_WINDOWS) HANDLE handle; @@ -670,11 +676,24 @@ int soundio_os_init_mirrored_memory(struct SoundIoOsMirroredMemory *mem, size_t break; } #else + + int fd; +#ifdef __ANDROID__ + fd = open("/dev/ashmem", O_RDWR); + if (fd < 0) + return SoundIoErrorSystemResources; + + int ret = ioctl(fd, ASHMEM_SET_SIZE, actual_capacity); + if (ret < 0) { + close(fd); + return SoundIoErrorSystemResources; + } +#else char shm_path[] = "/dev/shm/soundio-XXXXXX"; char tmp_path[] = "/tmp/soundio-XXXXXX"; char *chosen_path; - int fd = mkstemp(shm_path); + fd = mkstemp(shm_path); if (fd < 0) { fd = mkstemp(tmp_path); if (fd < 0) { @@ -695,6 +714,7 @@ int soundio_os_init_mirrored_memory(struct SoundIoOsMirroredMemory *mem, size_t close(fd); return SoundIoErrorSystemResources; } +#endif char *address = (char*)mmap(NULL, actual_capacity * 2, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (address == MAP_FAILED) { From 1ecded57cf22a826153b0ddd584cc92867dd7dd6 Mon Sep 17 00:00:00 2001 From: Michael Maltese Date: Tue, 8 Nov 2016 16:47:29 -0800 Subject: [PATCH 7/9] cmake: surround *_CFLAGS and *_LDFLAGS with quotes So that the build doesn't fail if they're empty. --- CMakeLists.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1862410..5668e1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -251,7 +251,7 @@ if(BUILD_DYNAMIC_LIBS) OUTPUT_NAME soundio SOVERSION ${LIBSOUNDIO_VERSION_MAJOR} VERSION ${LIBSOUNDIO_VERSION} - COMPILE_FLAGS ${LIB_CFLAGS} + COMPILE_FLAGS "${LIB_CFLAGS}" LINKER_LANGUAGE C ) target_link_libraries(libsoundio_shared LINK_PUBLIC ${LIBSOUNDIO_LIBS}) @@ -262,7 +262,7 @@ if(BUILD_STATIC_LIBS) add_library(libsoundio_static STATIC ${LIBSOUNDIO_SOURCES}) set_target_properties(libsoundio_static PROPERTIES OUTPUT_NAME ${SOUNDIO_STATIC_LIBNAME} - COMPILE_FLAGS ${LIB_CFLAGS} + COMPILE_FLAGS "${LIB_CFLAGS}" LINKER_LANGUAGE C ) install(TARGETS libsoundio_static DESTINATION ${CMAKE_INSTALL_LIBDIR}) @@ -325,21 +325,21 @@ if(BUILD_TESTS) target_link_libraries(unit_tests LINK_PUBLIC ${LIBSOUNDIO_LIBS}) set_target_properties(unit_tests PROPERTIES LINKER_LANGUAGE C - COMPILE_FLAGS ${TEST_CFLAGS} - LINK_FLAGS ${TEST_LDFLAGS} + COMPILE_FLAGS "${TEST_CFLAGS}" + LINK_FLAGS "${TEST_LDFLAGS}" ) add_executable(latency "${libsoundio_SOURCE_DIR}/test/latency.c" ${LIBSOUNDIO_SOURCES}) target_link_libraries(latency LINK_PUBLIC ${LIBSOUNDIO_LIBS} ${LIBM}) set_target_properties(latency PROPERTIES LINKER_LANGUAGE C - COMPILE_FLAGS ${LIB_CFLAGS} + COMPILE_FLAGS "${LIB_CFLAGS}" ) add_executable(underflow test/underflow.c) set_target_properties(underflow PROPERTIES LINKER_LANGUAGE C - COMPILE_FLAGS ${EXAMPLE_CFLAGS}) + COMPILE_FLAGS "${EXAMPLE_CFLAGS}") if(BUILD_DYNAMIC_LIBS) target_link_libraries(underflow libsoundio_shared ${LIBM}) else() From a5c8d514a28451b34e5df39c53297e32dc1af7dc Mon Sep 17 00:00:00 2001 From: Michael Maltese Date: Tue, 8 Nov 2016 16:48:25 -0800 Subject: [PATCH 8/9] cmake: conditionally enable profiling flags on unit_tests --- CMakeLists.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5668e1b..63c979e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 2.8.5) project(libsoundio C) set(CMAKE_MODULE_PATH ${libsoundio_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) +include(CheckCCompilerFlag) if(CMAKE_VERSION VERSION_LESS 3.0.0) set(CMAKE_INSTALL_LIBDIR "lib" CACHE PATH "library install dir (lib)") @@ -230,8 +231,13 @@ else() set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Werror -pedantic") set(LIB_CFLAGS "-std=c11 -fvisibility=hidden -Wall -Werror=strict-prototypes -Werror=old-style-definition -Werror=missing-prototypes -D_REENTRANT -D_POSIX_C_SOURCE=200809L -Wno-missing-braces") set(EXAMPLE_CFLAGS "-std=c99 -Wall") - set(TEST_CFLAGS "${LIB_CFLAGS} -fprofile-arcs -ftest-coverage") - set(TEST_LDFLAGS "-fprofile-arcs -ftest-coverage") + + set(PROFILING_FLAGS "-fprofile-arcs -ftest-coverage") + check_c_compiler_flag("${PROFILING_FLAGS}" PROFILING_FLAGS_SUPPORTED) + if(PROFILING_FLAGS_SUPPORTED) + set(TEST_CFLAGS "${LIB_CFLAGS} ${PROFILING_FLAGS}") + set(TEST_LDFLAGS "${PROFILING_FLAGS}") + endif() set(LIBM "m") endif() From 89438ba0acb15285c992aacbc3bc43d29bd64916 Mon Sep 17 00:00:00 2001 From: Michael Maltese Date: Tue, 8 Nov 2016 16:52:09 -0800 Subject: [PATCH 9/9] android: update readme with build and testing info --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index e08f3cc..d940742 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,24 @@ cmake .. -DCMAKE_TOOLCHAIN_FILE=/path/to/mxe/usr/x86_64-w64-mingw32.static/share make ``` +### Building for Android + +Install the dependencies: + + * cmake + * Android NDK + +The Android NDK provides a CMake toolchain file for cross-compilation starting in version r13. Run the following, where "ANDROID_NDK_HOME" is the root folder of the NDK: + +``` +mkdir build +cd build +cmake .. -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake" -DANDROID_PLATFORM=android-21 +make +``` + +If you are compiling for an emulator, you probably also want to add the CMake variable `-DANDROID_ARCH=x86`. Further variables are documented in the toolchain file. + ### Testing For each backend, do the following: @@ -254,6 +272,12 @@ For each backend, do the following: 0. Run `./latency` and make sure the printed beeps line up with the beeps that you hear. +### Testing for Android + +Currently when run from the command-line, input streams always fail at +`soundio_instream_open` due to the Android permissions model. Input devices will +only work in full-featured Android apps. + ### Building the Documentation Ensure that [doxygen](http://www.stack.nl/~dimitri/doxygen/) is installed,