Add Android audio drivers based on OpenSLES and Oboe (#464)

This set of changes implements audio drivers for Android, OpenSLES and Oboe. The changes in the original sources are kept minimal so that it should be easily maintained.
This commit is contained in:
Atsushi Eno 2019-03-28 02:02:23 +09:00 committed by Tom M
parent eb0c8eab9f
commit daa037b0d3
20 changed files with 1118 additions and 7 deletions

View file

@ -55,6 +55,7 @@ option ( enable-fpe-check "enable Floating Point Exception checks and debug mess
option ( enable-portaudio "compile PortAudio support" off )
option ( enable-profiling "profile the dsp code" off )
option ( enable-trap-on-fpe "enable SIGFPE trap on Floating Point Exceptions" off )
option ( enable-ubsan "compile and link against UBSan (for debugging fluidsynth internals)" off )
# Options enabled by default
option ( enable-aufile "compile support for sound file output" on )
@ -65,6 +66,8 @@ option ( enable-jack "compile JACK support (if it is available)" on )
option ( enable-ladspa "enable LADSPA effect units" on )
option ( enable-libsndfile "compile libsndfile support (if it is available)" on )
option ( enable-midishare "compile MidiShare support (if it is available)" on )
option ( enable-opensles "compile OpenSLES support (if it is available)" off )
option ( enable-oboe "compile Oboe support (requires OpenSLES and/or AAudio)" off )
option ( enable-network "enable network support (requires BSD sockets)" on )
option ( enable-oss "compile OSS support (if it is available)" on )
option ( enable-dsound "compile DirectSound support (if it is available)" on )
@ -169,6 +172,7 @@ unset ( LIBFLUID_CPPFLAGS CACHE )
unset ( LIBFLUID_LIBS CACHE )
unset ( FLUID_CPPFLAGS CACHE )
unset ( FLUID_LIBS CACHE )
unset ( ENABLE_UBSAN CACHE )
# Options for the GNU C compiler only
if ( CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE_C_COMPILER_ID STREQUAL "Intel" )
@ -182,17 +186,23 @@ if ( CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE_C_
# define some warning flags
set ( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wpointer-arith -Wcast-qual -Wstrict-prototypes -Wno-unused-parameter -Wdeclaration-after-statement" )
# prepend to build type specific flags, to allow users to override
set ( CMAKE_C_FLAGS_DEBUG "-g -DDEBUG ${CMAKE_C_FLAGS_DEBUG}" )
if ( CMAKE_C_COMPILER_ID STREQUAL "Intel" )
# icc needs the restrict flag to recognize C99 restrict pointers
set ( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -restrict" )
else () # not intel
# gcc and clang support bad function cast and alignment warnings; add them as well.
set ( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wbad-function-cast -Wcast-align" )
if ( enable-ubsan )
set ( CMAKE_C_FLAGS "-fsanitize=undefined ${CMAKE_C_FLAGS}" )
set ( CMAKE_EXE_LINKER_FLAGS "-fsanitize=undefined ${CMAKE_EXE_LINKER_FLAGS}" )
set ( CMAKE_SHARED_LINKER_FLAGS "-fsanitize=undefined ${CMAKE_SHARED_LINKER_FLAGS}" )
set ( ENABLE_UBSAN 1 )
endif ( enable-ubsan )
endif (CMAKE_C_COMPILER_ID STREQUAL "Intel" )
# prepend to build type specific flags, to allow users to override
set ( CMAKE_C_FLAGS_DEBUG "-g -DDEBUG -fsanitize=undefined ${CMAKE_C_FLAGS_DEBUG}" )
endif ( CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE_C_COMPILER_ID STREQUAL "Intel" )
# Windows
@ -598,7 +608,32 @@ else ( enable-midishare )
unset ( MidiShare_LIBS CACHE )
endif ( enable-midishare )
unset ( OPENSLES_SUPPORT CACHE )
unset ( OpenSLES_LIBS CACHE )
if ( enable-opensles )
check_include_file ( SLES/OpenSLES.h OPENSLES_SUPPORT )
if ( OPENSLES_SUPPORT )
find_library ( OpenSLES_LIBS OpenSLES )
if ( NOT OpenSLES_LIBS )
unset ( OPENSLES_SUPPORT )
endif ( NOT OpenSLES_LIBS )
endif ( OPENSLES_SUPPORT )
endif ( enable-opensles )
unset ( OBOE_SUPPORT CACHE )
unset ( OBOE_LIBS CACHE )
if ( enable-oboe )
# enable C++ as it's needed for oboe
enable_language ( CXX )
pkg_check_modules ( OBOE oboe-1.0 )
if ( OBOE_FOUND )
set ( OBOE_SUPPORT 1 )
set ( OBOE_LIBS ${OBOE_LIBRARIES} )
endif ( OBOE_FOUND )
endif ( enable-oboe )
unset ( WITH_READLINE CACHE )
unset ( READLINE_LIBS CACHE )
if ( enable-readline )
find_package ( Readline )
set ( FOUND_READLINE ${HAVE_READLINE} )
@ -606,8 +641,6 @@ if ( enable-readline )
set ( WITH_READLINE 1 )
set ( READLINE_LIBS ${READLINE_LIBRARIES} )
endif ( HAVE_READLINE )
else ( enable-readline )
unset ( READLINE_LIBS CACHE )
endif ( enable-readline )
unset ( ENABLE_MIXER_THREADS CACHE )
@ -649,6 +682,7 @@ link_directories (
${PORTAUDIO_LIBRARY_DIRS}
${LIBSNDFILE_LIBRARY_DIRS}
${DBUS_LIBRARY_DIRS}
${OBOE_LIBRARY_DIRS}
)
# Process subdirectories

View file

@ -50,6 +50,18 @@ else ( OSS_SUPPORT )
message ( "OSS: no" )
endif ( OSS_SUPPORT )
if ( OPENSLES_SUPPORT )
message ( "OpenSLES: yes" )
else ( OPENSLES_SUPPORT )
message ( "OpenSLES: no" )
endif ( OPENSLES_SUPPORT )
if ( OBOE_SUPPORT )
message ( "Oboe: yes" )
else ( OBOE_SUPPORT )
message ( "Oboe: no" )
endif ( OBOE_SUPPORT )
if ( MIDISHARE_SUPPORT )
message ( "MidiShare: yes" )
else ( MIDISHARE_SUPPORT )
@ -188,4 +200,10 @@ else ( ENABLE_FPECHECK )
message ( "Check FPE (debug): no" )
endif ( ENABLE_FPECHECK )
if ( ENABLE_UBSAN )
message ( "UBSan (debug): yes" )
else ( ENABLE_UBSAN )
message ( "UBSan (debug): no" )
endif ( ENABLE_UBSAN )
message ( "**************************************************************\n\n" )

2
doc/android/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
external

View file

@ -0,0 +1,130 @@
#
# The public targets in this Makefile are: build, clean, wipe
#
# What `build` target does:
#
# - build cerbero to build glib
# - build glib-2.0.so and many other dependency shared libraries
# - build Oboe shared library
# - build libfluidsynth.so
#
# Android app developers are supposed to copy all those shared
# libraries into their apks (per ABI).
#
PWD=$(shell pwd)
CERBERO=$(PWD)/external/cerbero
OBOE=$(PWD)/external/oboe
CMAKE=cmake
ANDROID_NDK = $(PWD)/external/cerbero/build/android-ndk-18
ABIS_SIMPLE = x86 x86-64 armv7 arm64
DIST_PATH=$(CERBERO)/build/dist
OBOE_BUILD_PATH=$(OBOE)/build
all: build
.PHONY: prepare
prepare: checkout-oboe checkout-cerbero
for abi in $(ABIS_SIMPLE) ; do \
cd $(CERBERO) && ./cerbero-uninstalled -c config/cross-android-$$abi.cbc bootstrap && cd $(PWD) ; \
done
.PHONY: checkout-oboe
checkout-oboe: $(OBOE)
cd $(OBOE) && git checkout 9bf3943
$(OBOE):
git clone https://github.com/Google/oboe.git $(OBOE)
.PHONY: checkout-cerbero
checkout-cerbero: $(CERBERO)
cd $(CERBERO) && git checkout 7a6fd79
$(CERBERO):
git clone https://github.com/atsushieno/cerbero.git $(CERBERO)
.PHONY: build
build: build-oboe dist-oboe build-deps-cerbero dist-deps-cerbero build-fluidsynth dist-fluidsynth build-fluidsynth-assetloader dist-fluidsynth-assetloader
.PHONY: build-deps-cerbero
build-deps-cerbero:
for abi in $(ABIS_SIMPLE) ; do \
cd $(CERBERO) && ./cerbero-uninstalled -c config/cross-android-$$abi.cbc build glib && cd $(PWD) ; \
done
define run_make_abi_target
make -f Makefile.android BUILD_ABI=$(1) A_ABI=$(2) $(3)
endef
define run_make_abi_target-unsafe
if make -f Makefile.android BUILD_ABI=$(1) A_ABI=$(2) $(3) ; then \
echo "ignore failure for $(1)..." ; \
fi
endef
define run_make_for_all_abi
$(call run_make_abi_target,x86,x86,$(1) )
$(call run_make_abi_target,x86_64,x86_64,$(1) )
$(call run_make_abi_target,armv7,armeabi-v7a,$(1) )
$(call run_make_abi_target-unsafe,arm64,arm64-v8a,$(1) )
endef
.PHONY: dist-deps-cerbero
dist-deps-cerbero:
$(call run_make_for_all_abi, dist-deps-cerbero-one)
.PHONY: dist-fluidsynth
dist-fluidsynth:
$(call run_make_for_all_abi, dist-fluidsynth-one)
.PHONY: build-oboe
build-oboe:
$(call run_make_for_all_abi, build-oboe-one)
.PHONY: dist-oboe
dist-oboe:
$(call run_make_for_all_abi, dist-oboe-one)
.PHONY: build-fluidsynth
build-fluidsynth:
$(call run_make_for_all_abi, build-fluidsynth-one)
build-fluidsynth-one:
mkdir -p build/$(A_ABI) && cd build/$(A_ABI) && \
LD_RUN_PATH=$(DIST_PATH)/android-$(BUILD_ABI)/lib:$(OBOE_BUILD_PATH)/$(A_ABI) LD_LIBRARY_PATH=$(DIST_PATH)/android_$(BUILD_ABI)/lib PKG_CONFIG_PATH=$(DIST_PATH)/android_$(BUILD_ABI)/lib/pkgconfig/:$(OBOE_BUILD_PATH)/$(A_ABI) \
$(CMAKE) -DCMAKE_INSTALL_PREFIX=$(PWD)/dist/$(A_ABI) -DCMAKE_TOOLCHAIN_FILE=$(ANDROID_NDK)/build/cmake/android.toolchain.cmake -Denable-opensles=on -Denable-oboe=on -Denable-jack=off -Denable-oss=off -Denable-pulseaudio=off -Denable-libsndfile=off -Denable-dbus=off -Denable-debug=on -DANDROID_NATIVE_API_LEVEL=android-27 -DANDROID_PLATFORM=android-27 -DANDROID_ABI=$(A_ABI) ../../../.. && make
build-oboe-one:
mkdir -p $(OBOE)/build/$(A_ABI) && cd $(OBOE)/build/$(A_ABI) && \
$(CMAKE) -DCMAKE_TOOLCHAIN_FILE=$(ANDROID_NDK)/build/cmake/android.toolchain.cmake -DANDROID_ABI=$(A_ABI) -DANDROID_NATIVE_API_LEVEL=android-27 -DANDROID_PLATFORM=android-27 -DBUILD_SHARED_LIBS=on ../.. && make
cp oboe-1.0.pc $(OBOE)/build/$(A_ABI)
dist-oboe-one:
mkdir -p dist/$(A_ABI) && cp $(OBOE)/build/$(A_ABI)/*.so dist/$(A_ABI)/
dist-deps-cerbero-one:
mkdir -p dist/$(A_ABI) && cd dist/$(A_ABI) && cp ../../external/cerbero/build/dist/android_$(BUILD_ABI)/lib/*.so . && cd ../..
dist-fluidsynth-one:
mkdir -p dist/$(A_ABI) && cd dist/$(A_ABI) && cp ../../build/$(A_ABI)/src/libfluidsynth.so . && cd ../..
cp -r ../../include/fluidsynth build/$(A_ABI)/include/
build-fluidsynth-assetloader:
cd fluidsynth-assetloader && ./ext-build.sh
dist-fluidsynth-assetloader:
cp fluidsynth-assetloader/build/x86/*.so dist/x86/
cp fluidsynth-assetloader/build/x86_64/*.so dist/x86_64/
cp fluidsynth-assetloader/build/armeabi-v7a/*.so dist/armeabi-v7a/
cp fluidsynth-assetloader/build/arm64-v8a/*.so dist/arm64-v8a/
clean:
rm -rf dist/* build/* external/oboe/build/* obj/local/* fluidsynth-asset-loader/build/*
.PHONY: wipe
wipe: $(CERBERO)
for abi in $(ABIS_SIMPLE) ; do \
cd $(CERBERO) && ./cerbero-uninstalled -c config/cross-android-$$abi.cbc wipe && cd ../.. ; \
done

View file

@ -0,0 +1,38 @@
# Android support in Fluidsynth
Fluidsynth supports Android audio outputs by Oboe and OpenSLES audio drivers.
Android also has Android MIDI API which is exposed only in Android Java API, but it is not exposed as a native API, therefore there is no `mdriver` support for Android. There is an example MidiDeviceService implementation for Fluidsynth at: https://github.com/atsushieno/fluidsynth-midi-service-j
## Usage
`libfluidsynth.so` and `libfluidsynth-assetloader.so` are the library that should be packaged into apk. The latter is for asset-based "sfloader".
By default, "oboe" is the default driver for Android. You can also explicitly specify "opensles" instead, with "audio.driver" setting:
```
fluid_settings_setstr (settings_handle, "audio.driver", "opensles");
```
## Custom SoundFont loader
Since Android file access is quite limited and there is no common place
to store soundfonts unlike Linux desktop (e.g. `/usr/share/sounds/sf2`), you
will most likely have to provide custom soundfont loader.
Since version 2.0.0 Fluidsynth comes with `fluid_sfloader_set_callbacks()` which brings
[customizible file/stream reader](https://github.com/FluidSynth/fluidsynth/issues/241) (open/read/seek/tell/close). It is useful to implement simplified
custom SF loader e.g. with Android assets or OBB streams.
The Android implementation is in separate library called `libfluidsynth-assetloader.so`. It comes with native Asset sfloader. However, its usage is a bit tricky because AssetManager needs to be passed from Java code (even though we use AAssetManager API).
Use `Java_fluidsynth_androidextensions_NativeHandler_setAssetManagerContext()` to initialize the this loader, then call `new_fluid_android_asset_sfloader()` to create a new sfloader. If you already have AAssetManager instance, then the first JNI function is ignorable and you only have to specify the manager to the second function.
There is [an example source code](https://github.com/atsushieno/fluidsynth-midi-service-j/blob/a2a56b/fluidsynthjna/src/main/java/fluidsynth/androidextensions/AndroidNativeAssetSoundFontLoader.kt#L17) on how to do it.
## Building
By default, you are supposed to provide `PKG_CONFIG_PATH` to glib etc. as well as oboe. There is nothing special.
However, in reality, Oboe does not come up with an official package specification, so you will have to create it manually... unless you use `oboe-1.0.pc` in this directory as well as the build system set up here.
There are "non-normative" build scripts i.e. `Makefile.android` and a couple of helper files in this directory. In case you don't have any dependencies such as glib for Android, then it would be helpful.

View file

@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.6.0)
project ( fluidsynth-assetloader C )
set ( fluidsynth-assetloader_sources fluid_androidasset.c )
add_library ( fluidsynth-assetloader SHARED ${fluidsynth-assetloader_sources} )
target_compile_options ( fluidsynth-assetloader
PRIVATE -v
PRIVATE -Wall
PRIVATE "$<$<CONFIG:DEBUG>:-Werror>") # Only include -Werror when building debug config
include_directories ( ../../../include )
set ( CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -L../../../dist/${ANDROID_ABI} -lfluidsynth" )
target_link_libraries ( fluidsynth-assetloader PRIVATE log android )

View file

@ -0,0 +1,16 @@
PWD=`pwd`
ABIS="x86 x86_64 armeabi-v7a arm64-v8a"
HOST_OS=`uname | tr [:upper:] [:lower:]`
ANDROID_NDK_PATH=~/android-sdk-$HOST_OS/ndk-bundle
CMAKEFILE=$ANDROID_NDK_PATH/build/cmake/android.toolchain.cmake
for A_ABI in $ABIS ; do
mkdir -p build/$A_ABI && \
cd build/$A_ABI && \
cmake -DCMAKE_TOOLCHAIN_FILE=$CMAKEFILE -DANDROID_PLATFORM=android-27 -DANDROID_ABI=$A_ABI ../.. && \
make &&
cd ../.. ;
done

View file

@ -0,0 +1,110 @@
/* FluidSynth - A Software Synthesizer
*
* Copyright (C) 2003 Peter Hanappe and others.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*/
#if defined(ANDROID) || defined(__DOXYGEN__)
#define FLUIDSYNTH_API
#include <stdlib.h>
#include <jni.h>
#include "fluid_androidasset.h"
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>
AAssetManager *fluid_android_asset_manager;
fluid_sfloader_t* new_fluid_android_asset_sfloader(fluid_settings_t *settings, void *assetManager)
{
fluid_sfloader_t *loader;
if (settings == NULL)
return NULL;
if (!fluid_android_asset_manager)
fluid_android_asset_manager = (AAssetManager*) assetManager;
if (fluid_android_asset_manager == NULL)
return NULL;
loader = new_fluid_defsfloader(settings);
if (loader == NULL)
return NULL;
fluid_sfloader_set_callbacks(loader,
asset_open,
asset_read,
asset_seek,
asset_tell,
asset_close);
return loader;
}
/* This is a compromised solution for JNAerator for that 1) it cannot handle jobject with JNIEnv as parameters, and that 2) the returned pointer can be converted in the same manner that JNAerated methods. (Most likely my JNA usage issue but no one has answer for it.) */
void Java_fluidsynth_androidextensions_NativeHandler_setAssetManagerContext(JNIEnv *env, jobject _this, jobject assetManager)
{
if (assetManager == NULL)
return;
fluid_android_asset_manager = AAssetManager_fromJava (env, assetManager);
}
void *asset_open(const char *path)
{
if (fluid_android_asset_manager == NULL)
return NULL;
return AAssetManager_open (fluid_android_asset_manager, path, AASSET_MODE_RANDOM);
}
int asset_close(void *handle)
{
AAsset *asset;
asset = (AAsset*) handle;
AAsset_close (asset);
return 0;
}
long asset_tell(void *handle)
{
AAsset *asset;
asset = (AAsset*) handle;
return AAsset_getLength(asset) - AAsset_getRemainingLength(asset);
}
int asset_seek(void *handle, long offset, int origin)
{
AAsset *asset;
asset = (AAsset*) handle;
return AAsset_seek (asset, (off_t) offset, origin);
}
int asset_read(void *buf, int count, void *handle)
{
AAsset *asset;
asset = (AAsset*) handle;
return AAsset_read (asset, buf, (size_t) count);
}
#endif /* if defined(ANDROID) || defined(__DOXYGEN__) */

View file

@ -0,0 +1,45 @@
/* FluidSynth - A Software Synthesizer
*
* Copyright (C) 2003 Peter Hanappe and others.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*/
#ifndef _PRIV_FLUID_ANDROIDASSET_H
#define _PRIV_FLUID_ANDROIDASSET_H
#ifdef __cplusplus
extern "C" {
#endif
#include <fluidsynth/types.h>
#include <fluidsynth/sfont.h>
fluid_sfloader_t* new_fluid_android_asset_sfloader(fluid_settings_t *settings, void *assetManager);
void Java_fluidsynth_androidextensions_NativeHandler_setAssetManagerContext(JNIEnv *env, jobject _this, jobject assetManager);
void *asset_open(const char *path);
int asset_close(void *handle);
long asset_tell(void *handle);
int asset_seek(void *handle, long offset, int origin);
int asset_read(void *buf, int count, void *handle);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* _PRIV_FLUID_ANDROIDASSET_H */

View file

@ -0,0 +1,44 @@
LOCAL_PATH := $(call my-dir)
TARGET_PLATFORM := android-27
GLIB_LIB = ../dep/$(APP_ABI)/
include $(CLEAR_VARS)
LOCAL_MODULE := glib-2.0
LOCAL_SRC_FILES := $(GLIB_LIB)/libglib-2.0.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := iconv
LOCAL_SRC_FILES := $(GLIB_LIB)/libiconv.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := intl
LOCAL_SRC_FILES := $(GLIB_LIB)/libintl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := oboe
LOCAL_SRC_FILES := $(GLIB_LIB)/liboboe.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := fluidsynth_static
LOCAL_SRC_FILES := ../dep/$(APP_ABI)/libfluidsynth.a
include $(PREBUILT_STATIC_LIBRARY)
LOCAL_MODULE := fluidsynth
ifeq ($(NDK_DEBUG),1)
cmd-strip :=
endif
LOCAL_STATIC_LIBRARIES := glib-2.0 iconv intl oboe
LOCAL_WHOLE_STATIC_LIBRARIES := fluidsynth_static
LOCAL_LDLIBS := -lc -lOpenSLES -ldl -llog -landroid -L$(LOCAL_PATH)/../dist/$(APP_ABI) -loboe-c
include $(BUILD_SHARED_LIBRARY)

View file

@ -0,0 +1,2 @@
APP_PLATFORM := android-21
APP_ABI := x86 x86_64 armeabi-v7a arm64-v8a

10
doc/android/oboe-1.0.pc Normal file
View file

@ -0,0 +1,10 @@
prefix=${pcfiledir}
exec_prefix=${prefix}
libdir=${prefix}
includedir=${prefix}/../../include/
Name: Oboe
Description: Oboe library
Version: 1.0.0
Libs: -L${libdir} -loboe -landroid -llog -lstdc++
Cflags: -I${includedir}

View file

@ -508,7 +508,7 @@ Developers: Settings can be deprecated by adding: <deprecated>SOME TEXT</depreca
<type>str</type>
<def>fluidsynth</def>
<desc>
ID used when creating Jack client connection.
Unique identifier used when creating Jack client connection.
</desc>
</setting>
<setting>
@ -527,6 +527,34 @@ Developers: Settings can be deprecated by adding: <deprecated>SOME TEXT</depreca
Jack server to connect to. Defaults to an empty string, which uses default Jack server.
</desc>
</setting>
<setting>
<name>oboe.id</name>
<type>int</type>
<def>0</def>
<min>0</min>
<max>2147483647</max>
<desc>
Request an audio device identified device using an ID as pointed out by Oboe's documentation.
</desc>
</setting>
<setting>
<name>oboe.sharing-mode</name>
<type>str</type>
<def>Shared</def>
<vals>Shared, Exclusive</vals>
<desc>
Sets the sharing mode as pointed out by Oboe's documentation.
</desc>
</setting>
<setting>
<name>oboe.performance-mode</name>
<type>str</type>
<def>None</def>
<vals>None, PowerSaving, LowLatency</vals>
<desc>
Sets the performance mode as pointed out by Oboe's documentation.
</desc>
</setting>
<setting>
<name>oss.device</name>
<type>str</type>

View file

@ -220,6 +220,8 @@ Creating the audio driver is straightforward: set the <code>audio.driver</code>
- portaudio: PortAudio Library (Mac OS 9 & X, Windows, Linux)
- sndman: Apple SoundManager (Mac OS Classic)
- dart: DART sound driver (OS/2)
- opensles: OpenSL ES (Android)
- oboe: Oboe (Android)
- file: Driver to output audio to a file
- sdl2*: Simple DirectMedia Layer (Linux, Windows, Mac OS X, iOS, Android, FreeBSD, Haiku, etc.)

View file

@ -126,6 +126,16 @@ if ( AUFILE_SUPPORT )
set ( fluid_aufile_SOURCES drivers/fluid_aufile.c )
endif ( AUFILE_SUPPORT )
if ( OPENSLES_SUPPORT )
set ( fluid_opensles_SOURCES drivers/fluid_opensles.c )
include_directories ( ${OpenSLES_INCLUDE_DIRS} )
endif ( OPENSLES_SUPPORT )
if ( OBOE_SUPPORT )
set ( fluid_oboe_SOURCES drivers/fluid_oboe.cpp )
include_directories ( ${OBOE_INCLUDE_DIRS} )
endif ( OBOE_SUPPORT )
set ( config_SOURCES ${CMAKE_BINARY_DIR}/config.h )
set ( libfluidsynth_SOURCES
@ -257,6 +267,8 @@ add_library ( libfluidsynth-OBJ OBJECT
${fluid_jack_SOURCES}
${fluid_lash_SOURCES}
${fluid_midishare_SOURCES}
${fluid_opensles_SOURCES}
${fluid_oboe_SOURCES}
${fluid_oss_SOURCES}
${fluid_portaudio_SOURCES}
${fluid_pulse_SOURCES}
@ -339,6 +351,8 @@ target_link_libraries ( libfluidsynth
${COREMIDI_LIBS}
${WINDOWS_LIBS}
${MidiShare_LIBS}
${OpenSLES_LIBS}
${OBOE_LIBS}
${LIBFLUID_LIBS}
)

View file

@ -172,6 +172,12 @@
/* Define to enable OSS driver */
#cmakedefine OSS_SUPPORT @OSS_SUPPORT@
/* Define to enable OPENSLES driver */
#cmakedefine OPENSLES_SUPPORT @OPENSLES_SUPPORT@
/* Define to enable Oboe driver */
#cmakedefine OBOE_SUPPORT @OBOE_SUPPORT@
/* Name of package */
#cmakedefine PACKAGE "@PACKAGE@"

View file

@ -80,6 +80,26 @@ static const fluid_audriver_definition_t fluid_audio_drivers[] =
},
#endif
#if OBOE_SUPPORT
{
"oboe",
new_fluid_oboe_audio_driver,
NULL,
delete_fluid_oboe_audio_driver,
fluid_oboe_audio_driver_settings
},
#endif
#if OPENSLES_SUPPORT
{
"opensles",
new_fluid_opensles_audio_driver,
NULL,
delete_fluid_opensles_audio_driver,
fluid_opensles_audio_driver_settings
},
#endif
#if COREAUDIO_SUPPORT
{
"coreaudio",

View file

@ -66,6 +66,22 @@ void delete_fluid_oss_audio_driver(fluid_audio_driver_t *p);
void fluid_oss_audio_driver_settings(fluid_settings_t *settings);
#endif
#if OPENSLES_SUPPORT
fluid_audio_driver_t*
new_fluid_opensles_audio_driver(fluid_settings_t* settings,
fluid_synth_t* synth);
void delete_fluid_opensles_audio_driver(fluid_audio_driver_t* p);
void fluid_opensles_audio_driver_settings(fluid_settings_t* settings);
#endif
#if OBOE_SUPPORT
fluid_audio_driver_t*
new_fluid_oboe_audio_driver(fluid_settings_t* settings,
fluid_synth_t* synth);
void delete_fluid_oboe_audio_driver(fluid_audio_driver_t* p);
void fluid_oboe_audio_driver_settings(fluid_settings_t* settings);
#endif
#if COREAUDIO_SUPPORT
fluid_audio_driver_t *new_fluid_core_audio_driver(fluid_settings_t *settings,
fluid_synth_t *synth);

225
src/drivers/fluid_oboe.cpp Normal file
View file

@ -0,0 +1,225 @@
/* FluidSynth - A Software Synthesizer
*
* Copyright (C) 2003 Peter Hanappe and others.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*/
/* fluid_oboe.c
*
* Audio driver for Android Oboe.
*
*/
extern "C" {
#include "fluid_adriver.h"
#include "fluid_settings.h"
} // extern "C"
#if OBOE_SUPPORT
#include <oboe/Oboe.h>
using namespace oboe;
static const int NUM_CHANNELS = 2;
class OboeAudioStreamCallback;
/** fluid_oboe_audio_driver_t
*
* This structure should not be accessed directly. Use audio port
* functions instead.
*/
typedef struct
{
fluid_audio_driver_t driver;
fluid_synth_t *synth;
int cont;
OboeAudioStreamCallback *oboe_callback;
AudioStream *stream;
} fluid_oboe_audio_driver_t;
class OboeAudioStreamCallback : public AudioStreamCallback
{
public:
OboeAudioStreamCallback(void *userData)
: user_data(userData)
{
}
DataCallbackResult onAudioReady(AudioStream *stream, void *audioData, int32_t numFrames)
{
fluid_oboe_audio_driver_t *dev = static_cast<fluid_oboe_audio_driver_t *>(this->user_data);
if(!dev->cont)
{
return DataCallbackResult::Stop;
}
if(stream->getFormat() == AudioFormat::Float)
{
fluid_synth_write_float(dev->synth, numFrames, static_cast<float *>(audioData), 0, 2, static_cast<float *>(audioData), 1, 2);
}
else
{
fluid_synth_write_s16(dev->synth, numFrames, static_cast<short *>(audioData), 0, 2, static_cast<short *>(audioData), 1, 2);
}
return DataCallbackResult::Continue;
}
private:
void *user_data;
};
void fluid_oboe_audio_driver_settings(fluid_settings_t *settings)
{
fluid_settings_register_int(settings, "audio.oboe.id", 0, 0, 0x7FFFFFFF, 0);
fluid_settings_register_str(settings, "audio.oboe.sharing-mode", "Shared", 0);
fluid_settings_add_option(settings, "audio.oboe.sharing-mode", "Shared");
fluid_settings_add_option(settings, "audio.oboe.sharing-mode", "Exclusive");
fluid_settings_register_str(settings, "audio.oboe.performance-mode", "None", 0);
fluid_settings_add_option(settings, "audio.oboe.performance-mode", "None");
fluid_settings_add_option(settings, "audio.oboe.performance-mode", "PowerSaving");
fluid_settings_add_option(settings, "audio.oboe.performance-mode", "LowLatency");
}
/*
* new_fluid_oboe_audio_driver
*/
fluid_audio_driver_t *
new_fluid_oboe_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
{
Result result;
fluid_oboe_audio_driver_t *dev;
AudioStreamBuilder builder_obj;
AudioStreamBuilder *builder = &builder_obj;
AudioStream *stream;
int period_frames;
double sample_rate;
int is_sample_format_float;
int device_id;
int sharing_mode; // 0: Shared, 1: Exclusive
int performance_mode; // 0: None, 1: PowerSaving, 2: LowLatency
try
{
dev = FLUID_NEW(fluid_oboe_audio_driver_t);
if(dev == NULL)
{
FLUID_LOG(FLUID_ERR, "Out of memory");
return NULL;
}
FLUID_MEMSET(dev, 0, sizeof(fluid_oboe_audio_driver_t));
dev->synth = synth;
dev->oboe_callback = new(std::nothrow) OboeAudioStreamCallback(dev);
if(!dev->oboe_callback)
{
FLUID_LOG(FLUID_ERR, "Out of memory");
goto error_recovery;
}
fluid_settings_getint(settings, "audio.period-size", &period_frames);
fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate);
is_sample_format_float = fluid_settings_str_equal(settings, "audio.sample-format", "float");
fluid_settings_getint(settings, "audio.oboe.id", &device_id);
sharing_mode =
fluid_settings_str_equal(settings, "audio.oboe.sharing-mode", "Exclusive") ? 1 : 0;
performance_mode =
fluid_settings_str_equal(settings, "audio.oboe.performance-mode", "PowerSaving") ? 1 :
fluid_settings_str_equal(settings, "audio.oboe.performance-mode", "LowLatency") ? 2 : 0;
builder->setDeviceId(device_id)
->setDirection(Direction::Output)
->setChannelCount(NUM_CHANNELS)
->setSampleRate(sample_rate)
->setFramesPerCallback(period_frames)
->setFormat(is_sample_format_float ? AudioFormat::Float : AudioFormat::I16)
->setSharingMode(sharing_mode == 1 ? SharingMode::Exclusive : SharingMode::Shared)
->setPerformanceMode(
performance_mode == 1 ? PerformanceMode::PowerSaving :
performance_mode == 2 ? PerformanceMode::LowLatency : PerformanceMode::None)
->setUsage(Usage::Media)
->setContentType(ContentType::Music)
->setCallback(dev->oboe_callback);
result = builder->openStream(&stream);
dev->stream = stream;
if(result != Result::OK)
{
goto error_recovery;
}
dev->cont = 1;
FLUID_LOG(FLUID_INFO, "Using Oboe driver");
stream->start();
return reinterpret_cast<fluid_audio_driver_t *>(dev);
}
catch(...)
{
FLUID_LOG(FLUID_ERR, "Unexpected Oboe driver initialization error");
}
error_recovery:
delete_fluid_oboe_audio_driver(reinterpret_cast<fluid_audio_driver_t *>(dev));
return NULL;
}
void delete_fluid_oboe_audio_driver(fluid_audio_driver_t *p)
{
fluid_oboe_audio_driver_t *dev = reinterpret_cast<fluid_oboe_audio_driver_t *>(p);
fluid_return_if_fail(dev != NULL);
try
{
dev->cont = 0;
if(dev->stream != NULL)
{
dev->stream->stop();
dev->stream->close();
}
}
catch(...) {}
delete dev->oboe_callback;
FLUID_FREE(dev);
}
#endif // OBOE_SUPPORT

View file

@ -0,0 +1,333 @@
/* FluidSynth - A Software Synthesizer
*
* Copyright (C) 2003 Peter Hanappe and others.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*/
/* fluid_opensles.c
*
* Audio driver for OpenSLES.
*
*/
#include "fluid_adriver.h"
#if OPENSLES_SUPPORT
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
static const int NUM_CHANNELS = 2;
/** fluid_opensles_audio_driver_t
*
* This structure should not be accessed directly. Use audio port
* functions instead.
*/
typedef struct
{
fluid_audio_driver_t driver;
SLObjectItf engine;
SLObjectItf output_mix_object;
SLObjectItf audio_player;
SLPlayItf audio_player_interface;
SLAndroidSimpleBufferQueueItf player_buffer_queue_interface;
void *synth;
int period_frames;
int is_sample_format_float;
/* used only by callback mode */
short *sles_buffer_short;
float *sles_buffer_float;
int cont;
double sample_rate;
} fluid_opensles_audio_driver_t;
static void opensles_callback(SLAndroidSimpleBufferQueueItf caller, void *pContext);
static void process_fluid_buffer(fluid_opensles_audio_driver_t *dev);
void fluid_opensles_audio_driver_settings(fluid_settings_t *settings)
{
}
/*
* new_fluid_opensles_audio_driver
*/
fluid_audio_driver_t *
new_fluid_opensles_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth)
{
SLresult result;
fluid_opensles_audio_driver_t *dev;
double sample_rate;
int period_size;
int realtime_prio = 0;
int is_sample_format_float;
SLEngineItf engine_interface;
dev = FLUID_NEW(fluid_opensles_audio_driver_t);
if(dev == NULL)
{
FLUID_LOG(FLUID_ERR, "Out of memory");
return NULL;
}
FLUID_MEMSET(dev, 0, sizeof(*dev));
fluid_settings_getint(settings, "audio.period-size", &period_size);
fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate);
fluid_settings_getint(settings, "audio.realtime-prio", &realtime_prio);
is_sample_format_float = fluid_settings_str_equal(settings, "audio.sample-format", "float");
dev->synth = synth;
dev->is_sample_format_float = is_sample_format_float;
dev->period_frames = period_size;
dev->sample_rate = sample_rate;
dev->cont = 1;
result = slCreateEngine(&(dev->engine), 0, NULL, 0, NULL, NULL);
if(!dev->engine)
{
FLUID_LOG(FLUID_ERR, "Failed to create OpenSLES connection");
goto error_recovery;
}
result = (*dev->engine)->Realize(dev->engine, SL_BOOLEAN_FALSE);
if(result != SL_RESULT_SUCCESS)
{
goto error_recovery;
}
result = (*dev->engine)->GetInterface(dev->engine, SL_IID_ENGINE, &engine_interface);
if(result != SL_RESULT_SUCCESS)
{
goto error_recovery;
}
result = (*engine_interface)->CreateOutputMix(engine_interface, &dev->output_mix_object, 0, 0, 0);
if(result != SL_RESULT_SUCCESS)
{
goto error_recovery;
}
result = (*dev->output_mix_object)->Realize(dev->output_mix_object, SL_BOOLEAN_FALSE);
if(result != SL_RESULT_SUCCESS)
{
goto error_recovery;
}
SLDataLocator_AndroidSimpleBufferQueue loc_buffer_queue =
{
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
2 /* number of buffers */
};
SLAndroidDataFormat_PCM_EX format_pcm =
{
SL_ANDROID_DATAFORMAT_PCM_EX,
NUM_CHANNELS,
((SLuint32) sample_rate) * 1000,
is_sample_format_float ? SL_PCMSAMPLEFORMAT_FIXED_32 : SL_PCMSAMPLEFORMAT_FIXED_16,
is_sample_format_float ? SL_PCMSAMPLEFORMAT_FIXED_32 : SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
SL_BYTEORDER_LITTLEENDIAN,
is_sample_format_float ? SL_ANDROID_PCM_REPRESENTATION_FLOAT : SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT
};
SLDataSource audio_src =
{
&loc_buffer_queue,
&format_pcm
};
SLDataLocator_OutputMix loc_outmix =
{
SL_DATALOCATOR_OUTPUTMIX,
dev->output_mix_object
};
SLDataSink audio_sink = {&loc_outmix, NULL};
const SLInterfaceID ids1[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
const SLboolean req1[] = {SL_BOOLEAN_TRUE};
result = (*engine_interface)->CreateAudioPlayer(engine_interface,
&(dev->audio_player), &audio_src, &audio_sink, 1, ids1, req1);
if(result != SL_RESULT_SUCCESS)
{
goto error_recovery;
}
result = (*dev->audio_player)->Realize(dev->audio_player, SL_BOOLEAN_FALSE);
if(result != SL_RESULT_SUCCESS)
{
goto error_recovery;
}
result = (*dev->audio_player)->GetInterface(dev->audio_player,
SL_IID_PLAY, &(dev->audio_player_interface));
if(result != SL_RESULT_SUCCESS)
{
goto error_recovery;
}
result = (*dev->audio_player)->GetInterface(dev->audio_player,
SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &(dev->player_buffer_queue_interface));
if(result != SL_RESULT_SUCCESS)
{
goto error_recovery;
}
if(dev->is_sample_format_float)
{
dev->sles_buffer_float = FLUID_ARRAY(float, dev->period_frames * NUM_CHANNELS);
}
else
{
dev->sles_buffer_short = FLUID_ARRAY(short, dev->period_frames * NUM_CHANNELS);
}
if(dev->sles_buffer_float == NULL && dev->sles_buffer_short == NULL)
{
FLUID_LOG(FLUID_ERR, "Out of memory.");
goto error_recovery;
}
result = (*dev->player_buffer_queue_interface)->RegisterCallback(dev->player_buffer_queue_interface, opensles_callback, dev);
if(result != SL_RESULT_SUCCESS)
{
goto error_recovery;
}
if(dev->is_sample_format_float)
{
(*dev->player_buffer_queue_interface)->Enqueue(dev->player_buffer_queue_interface, dev->sles_buffer_float, dev->period_frames * NUM_CHANNELS * sizeof(float));
}
else
{
(*dev->player_buffer_queue_interface)->Enqueue(dev->player_buffer_queue_interface, dev->sles_buffer_short, dev->period_frames * NUM_CHANNELS * sizeof(short));
}
(*dev->audio_player_interface)->SetCallbackEventsMask(dev->audio_player_interface, SL_PLAYEVENT_HEADATEND);
result = (*dev->audio_player_interface)->SetPlayState(dev->audio_player_interface, SL_PLAYSTATE_PLAYING);
if(result != SL_RESULT_SUCCESS)
{
goto error_recovery;
}
FLUID_LOG(FLUID_INFO, "Using OpenSLES driver.");
return (fluid_audio_driver_t *) dev;
error_recovery:
delete_fluid_opensles_audio_driver((fluid_audio_driver_t *) dev);
return NULL;
}
void delete_fluid_opensles_audio_driver(fluid_audio_driver_t *p)
{
fluid_opensles_audio_driver_t *dev = (fluid_opensles_audio_driver_t *) p;
fluid_return_if_fail(dev != NULL);
dev->cont = 0;
if(dev->audio_player)
{
(*dev->audio_player)->Destroy(dev->audio_player);
}
if(dev->output_mix_object)
{
(*dev->output_mix_object)->Destroy(dev->output_mix_object);
}
if(dev->engine)
{
(*dev->engine)->Destroy(dev->engine);
}
if(dev->sles_buffer_float)
{
FLUID_FREE(dev->sles_buffer_float);
}
if(dev->sles_buffer_short)
{
FLUID_FREE(dev->sles_buffer_short);
}
FLUID_FREE(dev);
}
void opensles_callback(SLAndroidSimpleBufferQueueItf caller, void *pContext)
{
fluid_opensles_audio_driver_t *dev = (fluid_opensles_audio_driver_t *) pContext;
SLresult result;
process_fluid_buffer(dev);
if(dev->is_sample_format_float)
{
result = (*caller)->Enqueue(
dev->player_buffer_queue_interface, dev->sles_buffer_float, dev->period_frames * sizeof(float) * NUM_CHANNELS);
}
else
{
result = (*caller)->Enqueue(
dev->player_buffer_queue_interface, dev->sles_buffer_short, dev->period_frames * sizeof(short) * NUM_CHANNELS);
}
/*
if (result != SL_RESULT_SUCCESS)
{
// Do not simply break at just one single insufficient buffer. Go on.
}
*/
}
void process_fluid_buffer(fluid_opensles_audio_driver_t *dev)
{
short *out_short = dev->sles_buffer_short;
float *out_float = dev->sles_buffer_float;
int period_frames = dev->period_frames;
if(dev->is_sample_format_float)
{
fluid_synth_write_float(dev->synth, period_frames, out_float, 0, 2, out_float, 1, 2);
}
else
{
fluid_synth_write_s16(dev->synth, period_frames, out_short, 0, 2, out_short, 1, 2);
}
}
#endif