mirror of
https://github.com/Ryujinx/libsoundio.git
synced 2025-01-03 16:05:27 +00:00
Merge branch 'android' of https://github.com/ligfx/libsoundio into v2
This commit is contained in:
commit
98b85efba5
|
@ -1,6 +1,7 @@
|
||||||
cmake_minimum_required(VERSION 2.8.5)
|
cmake_minimum_required(VERSION 2.8.5)
|
||||||
project(libsoundio C)
|
project(libsoundio C)
|
||||||
set(CMAKE_MODULE_PATH ${libsoundio_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})
|
set(CMAKE_MODULE_PATH ${libsoundio_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})
|
||||||
|
include(CheckCCompilerFlag)
|
||||||
|
|
||||||
if(CMAKE_VERSION VERSION_LESS 3.0.0)
|
if(CMAKE_VERSION VERSION_LESS 3.0.0)
|
||||||
set(CMAKE_INSTALL_LIBDIR "lib" CACHE PATH "library install dir (lib)")
|
set(CMAKE_INSTALL_LIBDIR "lib" CACHE PATH "library install dir (lib)")
|
||||||
|
@ -36,6 +37,7 @@ option(ENABLE_PULSEAUDIO "Enable PulseAudio backend" ON)
|
||||||
option(ENABLE_ALSA "Enable ALSA backend" ON)
|
option(ENABLE_ALSA "Enable ALSA backend" ON)
|
||||||
option(ENABLE_COREAUDIO "Enable CoreAudio backend" ON)
|
option(ENABLE_COREAUDIO "Enable CoreAudio backend" ON)
|
||||||
option(ENABLE_WASAPI "Enable WASAPI backend" ON)
|
option(ENABLE_WASAPI "Enable WASAPI backend" ON)
|
||||||
|
option(ENABLE_ANDROID "Enable Android OpenSL ES backend" ON)
|
||||||
|
|
||||||
find_package(Threads)
|
find_package(Threads)
|
||||||
if(Threads_FOUND)
|
if(Threads_FOUND)
|
||||||
|
@ -138,6 +140,21 @@ else()
|
||||||
set(SOUNDIO_HAVE_WASAPI false)
|
set(SOUNDIO_HAVE_WASAPI false)
|
||||||
endif()
|
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
|
set(LIBSOUNDIO_SOURCES
|
||||||
"${libsoundio_SOURCE_DIR}/src/soundio.c"
|
"${libsoundio_SOURCE_DIR}/src/soundio.c"
|
||||||
|
@ -179,6 +196,11 @@ if(SOUNDIO_HAVE_WASAPI)
|
||||||
"${libsoundio_SOURCE_DIR}/src/wasapi.c"
|
"${libsoundio_SOURCE_DIR}/src/wasapi.c"
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
if(SOUNDIO_HAVE_ANDROID)
|
||||||
|
set(LIBSOUNDIO_SOURCES ${LIBSOUNDIO_SOURCES}
|
||||||
|
"${libsoundio_SOURCE_DIR}/src/android.c"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
${libsoundio_SOURCE_DIR}
|
${libsoundio_SOURCE_DIR}
|
||||||
|
@ -195,6 +217,7 @@ set(LIBSOUNDIO_LIBS
|
||||||
${COREFOUNDATION_LIBRARY}
|
${COREFOUNDATION_LIBRARY}
|
||||||
${AUDIOUNIT_LIBRARY}
|
${AUDIOUNIT_LIBRARY}
|
||||||
${CMAKE_THREAD_LIBS_INIT}
|
${CMAKE_THREAD_LIBS_INIT}
|
||||||
|
${ANDROID_OPENSLES_LIBRARY}
|
||||||
)
|
)
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
|
@ -208,8 +231,13 @@ else()
|
||||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Werror -pedantic")
|
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(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(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")
|
set(LIBM "m")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -229,7 +257,7 @@ if(BUILD_DYNAMIC_LIBS)
|
||||||
OUTPUT_NAME soundio
|
OUTPUT_NAME soundio
|
||||||
SOVERSION ${LIBSOUNDIO_VERSION_MAJOR}
|
SOVERSION ${LIBSOUNDIO_VERSION_MAJOR}
|
||||||
VERSION ${LIBSOUNDIO_VERSION}
|
VERSION ${LIBSOUNDIO_VERSION}
|
||||||
COMPILE_FLAGS ${LIB_CFLAGS}
|
COMPILE_FLAGS "${LIB_CFLAGS}"
|
||||||
LINKER_LANGUAGE C
|
LINKER_LANGUAGE C
|
||||||
)
|
)
|
||||||
target_link_libraries(libsoundio_shared LINK_PUBLIC ${LIBSOUNDIO_LIBS})
|
target_link_libraries(libsoundio_shared LINK_PUBLIC ${LIBSOUNDIO_LIBS})
|
||||||
|
@ -240,7 +268,7 @@ if(BUILD_STATIC_LIBS)
|
||||||
add_library(libsoundio_static STATIC ${LIBSOUNDIO_SOURCES})
|
add_library(libsoundio_static STATIC ${LIBSOUNDIO_SOURCES})
|
||||||
set_target_properties(libsoundio_static PROPERTIES
|
set_target_properties(libsoundio_static PROPERTIES
|
||||||
OUTPUT_NAME ${SOUNDIO_STATIC_LIBNAME}
|
OUTPUT_NAME ${SOUNDIO_STATIC_LIBNAME}
|
||||||
COMPILE_FLAGS ${LIB_CFLAGS}
|
COMPILE_FLAGS "${LIB_CFLAGS}"
|
||||||
LINKER_LANGUAGE C
|
LINKER_LANGUAGE C
|
||||||
)
|
)
|
||||||
install(TARGETS libsoundio_static DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
install(TARGETS libsoundio_static DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
|
@ -303,21 +331,21 @@ if(BUILD_TESTS)
|
||||||
target_link_libraries(unit_tests LINK_PUBLIC ${LIBSOUNDIO_LIBS})
|
target_link_libraries(unit_tests LINK_PUBLIC ${LIBSOUNDIO_LIBS})
|
||||||
set_target_properties(unit_tests PROPERTIES
|
set_target_properties(unit_tests PROPERTIES
|
||||||
LINKER_LANGUAGE C
|
LINKER_LANGUAGE C
|
||||||
COMPILE_FLAGS ${TEST_CFLAGS}
|
COMPILE_FLAGS "${TEST_CFLAGS}"
|
||||||
LINK_FLAGS ${TEST_LDFLAGS}
|
LINK_FLAGS "${TEST_LDFLAGS}"
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(latency "${libsoundio_SOURCE_DIR}/test/latency.c" ${LIBSOUNDIO_SOURCES})
|
add_executable(latency "${libsoundio_SOURCE_DIR}/test/latency.c" ${LIBSOUNDIO_SOURCES})
|
||||||
target_link_libraries(latency LINK_PUBLIC ${LIBSOUNDIO_LIBS} ${LIBM})
|
target_link_libraries(latency LINK_PUBLIC ${LIBSOUNDIO_LIBS} ${LIBM})
|
||||||
set_target_properties(latency PROPERTIES
|
set_target_properties(latency PROPERTIES
|
||||||
LINKER_LANGUAGE C
|
LINKER_LANGUAGE C
|
||||||
COMPILE_FLAGS ${LIB_CFLAGS}
|
COMPILE_FLAGS "${LIB_CFLAGS}"
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(underflow test/underflow.c)
|
add_executable(underflow test/underflow.c)
|
||||||
set_target_properties(underflow PROPERTIES
|
set_target_properties(underflow PROPERTIES
|
||||||
LINKER_LANGUAGE C
|
LINKER_LANGUAGE C
|
||||||
COMPILE_FLAGS ${EXAMPLE_CFLAGS})
|
COMPILE_FLAGS "${EXAMPLE_CFLAGS}")
|
||||||
if(BUILD_DYNAMIC_LIBS)
|
if(BUILD_DYNAMIC_LIBS)
|
||||||
target_link_libraries(underflow libsoundio_shared ${LIBM})
|
target_link_libraries(underflow libsoundio_shared ${LIBM})
|
||||||
else()
|
else()
|
||||||
|
@ -380,9 +408,10 @@ message(
|
||||||
"System Dependencies\n"
|
"System Dependencies\n"
|
||||||
"-------------------\n"
|
"-------------------\n"
|
||||||
"* threads : ${STATUS_THREADS}\n"
|
"* threads : ${STATUS_THREADS}\n"
|
||||||
"* JACK (optional) : ${STATUS_JACK}\n"
|
"* JACK (optional) : ${STATUS_JACK}\n"
|
||||||
"* PulseAudio (optional) : ${STATUS_PULSEAUDIO}\n"
|
"* PulseAudio (optional) : ${STATUS_PULSEAUDIO}\n"
|
||||||
"* ALSA (optional) : ${STATUS_ALSA}\n"
|
"* ALSA (optional) : ${STATUS_ALSA}\n"
|
||||||
"* CoreAudio (optional) : ${STATUS_COREAUDIO}\n"
|
"* CoreAudio (optional) : ${STATUS_COREAUDIO}\n"
|
||||||
"* WASAPI (optional) : ${STATUS_WASAPI}\n"
|
"* WASAPI (optional) : ${STATUS_WASAPI}\n"
|
||||||
|
"* Android OpenSL ES (optional) : ${STATUS_ANDROID}\n"
|
||||||
)
|
)
|
||||||
|
|
24
README.md
24
README.md
|
@ -228,6 +228,24 @@ cmake .. -DCMAKE_TOOLCHAIN_FILE=/path/to/mxe/usr/x86_64-w64-mingw32.static/share
|
||||||
make
|
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
|
### Testing
|
||||||
|
|
||||||
For each backend, do the following:
|
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
|
0. Run `./latency` and make sure the printed beeps line up with the beeps that
|
||||||
you hear.
|
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
|
### Building the Documentation
|
||||||
|
|
||||||
Ensure that [doxygen](http://www.stack.nl/~dimitri/doxygen/) is installed,
|
Ensure that [doxygen](http://www.stack.nl/~dimitri/doxygen/) is installed,
|
||||||
|
|
16
cmake/FindAndroidOpenSLES.cmake
Normal file
16
cmake/FindAndroidOpenSLES.cmake
Normal file
|
@ -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)
|
|
@ -223,6 +223,7 @@ enum SoundIoBackend {
|
||||||
SoundIoBackendAlsa,
|
SoundIoBackendAlsa,
|
||||||
SoundIoBackendCoreAudio,
|
SoundIoBackendCoreAudio,
|
||||||
SoundIoBackendWasapi,
|
SoundIoBackendWasapi,
|
||||||
|
SoundIoBackendAndroid
|
||||||
};
|
};
|
||||||
|
|
||||||
enum SoundIoDeviceAim {
|
enum SoundIoDeviceAim {
|
||||||
|
|
786
src/android.c
Normal file
786
src/android.c
Normal file
|
@ -0,0 +1,786 @@
|
||||||
|
/*
|
||||||
|
* 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"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
static void destroy_android(struct SoundIoPrivate *si) {
|
||||||
|
struct SoundIoAndroid *sia = &si->backend_data.android;
|
||||||
|
|
||||||
|
if (sia->cond) {
|
||||||
|
soundio_os_cond_destroy(sia->cond);
|
||||||
|
sia->cond = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sia->engineObject) {
|
||||||
|
(*sia->engineObject)->Destroy(sia->engineObject);
|
||||||
|
sia->engineObject = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
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 = build_format_pcm_android(
|
||||||
|
outstream->format,
|
||||||
|
outstream->layout.channel_count,
|
||||||
|
outstream->sample_rate
|
||||||
|
);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (SL_RESULT_SUCCESS != slCreateEngine(&sia->engineObject, 0, NULL, 0, NULL,
|
||||||
|
NULL))
|
||||||
|
{
|
||||||
|
return SoundIoErrorInitAudioBackend;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SL_RESULT_SUCCESS != (*sia->engineObject)->Realize(sia->engineObject,
|
||||||
|
SL_BOOLEAN_FALSE))
|
||||||
|
{
|
||||||
|
destroy_android(si);
|
||||||
|
return SoundIoErrorInitAudioBackend;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SL_RESULT_SUCCESS != (*sia->engineObject)->GetInterface(sia->engineObject,
|
||||||
|
SL_IID_ENGINE, &sia->engineEngine))
|
||||||
|
{
|
||||||
|
destroy_android(si);
|
||||||
|
return SoundIoErrorInitAudioBackend;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
destroy_android(si);
|
||||||
|
return SoundIoErrorNoMem;
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Might support additional formats as well? But there's no way to query
|
||||||
|
// for them.
|
||||||
|
// 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 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);
|
||||||
|
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: 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;
|
||||||
|
|
||||||
|
if (SoundIoListDevicePtr_append(&si->safe_devices_info->output_devices,
|
||||||
|
device))
|
||||||
|
{
|
||||||
|
soundio_device_unref(device);
|
||||||
|
destroy_android(si);
|
||||||
|
return SoundIoErrorNoMem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 <SLES/OpenSLES_AndroidConfiguration.h>. 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;
|
||||||
|
}
|
53
src/android.h
Normal file
53
src/android.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* 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 <SLES/OpenSLES.h>
|
||||||
|
#include <SLES/OpenSLES_Android.h>
|
||||||
|
|
||||||
|
#include "soundio_internal.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;
|
||||||
|
|
||||||
|
SLObjectItf engineObject;
|
||||||
|
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];
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
|
@ -18,5 +18,6 @@
|
||||||
#cmakedefine SOUNDIO_HAVE_ALSA
|
#cmakedefine SOUNDIO_HAVE_ALSA
|
||||||
#cmakedefine SOUNDIO_HAVE_COREAUDIO
|
#cmakedefine SOUNDIO_HAVE_COREAUDIO
|
||||||
#cmakedefine SOUNDIO_HAVE_WASAPI
|
#cmakedefine SOUNDIO_HAVE_WASAPI
|
||||||
|
#cmakedefine SOUNDIO_HAVE_ANDROID
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
22
src/os.c
22
src/os.c
|
@ -78,6 +78,12 @@
|
||||||
#include <mach/mach.h>
|
#include <mach/mach.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <linux/ashmem.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
struct SoundIoOsThread {
|
struct SoundIoOsThread {
|
||||||
#if defined(SOUNDIO_OS_WINDOWS)
|
#if defined(SOUNDIO_OS_WINDOWS)
|
||||||
HANDLE handle;
|
HANDLE handle;
|
||||||
|
@ -670,11 +676,24 @@ int soundio_os_init_mirrored_memory(struct SoundIoOsMirroredMemory *mem, size_t
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#else
|
#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 shm_path[] = "/dev/shm/soundio-XXXXXX";
|
||||||
char tmp_path[] = "/tmp/soundio-XXXXXX";
|
char tmp_path[] = "/tmp/soundio-XXXXXX";
|
||||||
char *chosen_path;
|
char *chosen_path;
|
||||||
|
|
||||||
int fd = mkstemp(shm_path);
|
fd = mkstemp(shm_path);
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
fd = mkstemp(tmp_path);
|
fd = mkstemp(tmp_path);
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
|
@ -695,6 +714,7 @@ int soundio_os_init_mirrored_memory(struct SoundIoOsMirroredMemory *mem, size_t
|
||||||
close(fd);
|
close(fd);
|
||||||
return SoundIoErrorSystemResources;
|
return SoundIoErrorSystemResources;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
char *address = (char*)mmap(NULL, actual_capacity * 2, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
char *address = (char*)mmap(NULL, actual_capacity * 2, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||||
if (address == MAP_FAILED) {
|
if (address == MAP_FAILED) {
|
||||||
|
|
|
@ -29,6 +29,9 @@ static const enum SoundIoBackend available_backends[] = {
|
||||||
#endif
|
#endif
|
||||||
#ifdef SOUNDIO_HAVE_WASAPI
|
#ifdef SOUNDIO_HAVE_WASAPI
|
||||||
SoundIoBackendWasapi,
|
SoundIoBackendWasapi,
|
||||||
|
#endif
|
||||||
|
#ifdef SOUNDIO_HAVE_ANDROID
|
||||||
|
SoundIoBackendAndroid,
|
||||||
#endif
|
#endif
|
||||||
SoundIoBackendDummy,
|
SoundIoBackendDummy,
|
||||||
};
|
};
|
||||||
|
@ -67,6 +70,12 @@ static backend_init_t backend_init_fns[] = {
|
||||||
NULL,
|
NULL,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef SOUNDIO_HAVE_ANDROID
|
||||||
|
soundio_android_init,
|
||||||
|
#else
|
||||||
|
NULL,
|
||||||
|
#endif
|
||||||
|
|
||||||
&soundio_dummy_init,
|
&soundio_dummy_init,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -165,6 +174,7 @@ const char *soundio_backend_name(enum SoundIoBackend backend) {
|
||||||
case SoundIoBackendAlsa: return "ALSA";
|
case SoundIoBackendAlsa: return "ALSA";
|
||||||
case SoundIoBackendCoreAudio: return "CoreAudio";
|
case SoundIoBackendCoreAudio: return "CoreAudio";
|
||||||
case SoundIoBackendWasapi: return "WASAPI";
|
case SoundIoBackendWasapi: return "WASAPI";
|
||||||
|
case SoundIoBackendAndroid: return "Android OpenSL ES";
|
||||||
case SoundIoBackendDummy: return "Dummy";
|
case SoundIoBackendDummy: return "Dummy";
|
||||||
}
|
}
|
||||||
return "(invalid backend)";
|
return "(invalid backend)";
|
||||||
|
|
|
@ -32,6 +32,10 @@
|
||||||
#include "wasapi.h"
|
#include "wasapi.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef SOUNDIO_HAVE_ANDROID
|
||||||
|
#include "android.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "dummy.h"
|
#include "dummy.h"
|
||||||
|
|
||||||
union SoundIoBackendData {
|
union SoundIoBackendData {
|
||||||
|
@ -49,6 +53,9 @@ union SoundIoBackendData {
|
||||||
#endif
|
#endif
|
||||||
#ifdef SOUNDIO_HAVE_WASAPI
|
#ifdef SOUNDIO_HAVE_WASAPI
|
||||||
struct SoundIoWasapi wasapi;
|
struct SoundIoWasapi wasapi;
|
||||||
|
#endif
|
||||||
|
#ifdef SOUNDIO_HAVE_ANDROID
|
||||||
|
struct SoundIoAndroid android;
|
||||||
#endif
|
#endif
|
||||||
struct SoundIoDummy dummy;
|
struct SoundIoDummy dummy;
|
||||||
};
|
};
|
||||||
|
@ -87,6 +94,9 @@ union SoundIoOutStreamBackendData {
|
||||||
#endif
|
#endif
|
||||||
#ifdef SOUNDIO_HAVE_WASAPI
|
#ifdef SOUNDIO_HAVE_WASAPI
|
||||||
struct SoundIoOutStreamWasapi wasapi;
|
struct SoundIoOutStreamWasapi wasapi;
|
||||||
|
#endif
|
||||||
|
#ifdef SOUNDIO_HAVE_ANDROID
|
||||||
|
struct SoundIoOutStreamAndroid android;
|
||||||
#endif
|
#endif
|
||||||
struct SoundIoOutStreamDummy dummy;
|
struct SoundIoOutStreamDummy dummy;
|
||||||
};
|
};
|
||||||
|
@ -106,6 +116,9 @@ union SoundIoInStreamBackendData {
|
||||||
#endif
|
#endif
|
||||||
#ifdef SOUNDIO_HAVE_WASAPI
|
#ifdef SOUNDIO_HAVE_WASAPI
|
||||||
struct SoundIoInStreamWasapi wasapi;
|
struct SoundIoInStreamWasapi wasapi;
|
||||||
|
#endif
|
||||||
|
#ifdef SOUNDIO_HAVE_ANDROID
|
||||||
|
struct SoundIoInStreamAndroid android;
|
||||||
#endif
|
#endif
|
||||||
struct SoundIoInStreamDummy dummy;
|
struct SoundIoInStreamDummy dummy;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue