おはようございます。今回は、Android用のlibfluidsynth.soについて、WindowsのAndroidStudioから作成できました。という報告です。ついでに音飛びについても少々。
実行バイナリにこちらを含めるとソースコードを開示しないとダメなルールですので、公式リポジトリから取得したもの限定でアプリ公開を行うほうが、多くの場合セーフでしょう。
実験などの動作確認で、自主ビルドが必要な場合もありそうです。私の場合もそうでした。
Ubuntu+CMakeの記事は後日作成します。
それをしたくなった、理由です。
私のPixel9aは、ある日のOSアップデート以降、以下の条件でノイズが発生するようになってしまいまして、対応を行いました。
- libfluidsynth.soの2.5.1を使っている
一応、2.4.7でも同じでしたので要素ではなさそう - Pixel9aとAndroid15を使う端末だけ発生。14ではあまりなかった気がした
おそらくどちらかがトリガーかもしれない - oboeのパフォーマンスモードをLowLatencyにした場合に発生する
Normalでは発生していなかった気がした - 同時に大量の音がなるMIDIファイルを再生した際に、顕著になる
そして、順番に、調査したいものを絞りました。
もしかして、「レンダリングスレッドの処理を時間内に終わらせられていない」のではないか?
それについて、考えられる要因は以下です。
- 昔、WindowsでVST関連の処理を記述していたとき、AudioレンダリングThread内で、スレッドを待機させるMutexを使うと起きていたノイズに似ています。Javaでいうところのsynchronizedにあたるものです
- あるいは単純に処理が重く、まにあわない頻度でレンダリングがコールされている
1.が怪しかったため、コードをコンパイルして実証しようと思いました。
Fluidsynth公式のコンパイルスクリプトをUbuntu(WSL2.0)で動かしてみました。成功するまで2日くらいかかりました。
(近ううちに成功談の記事を作成します。書式を最新にするのみですが)
この場合、WSLのコンソールと、Windowsを切り替えて作業することになりますから、別の方法が必要だと感じて、リアクションを起こしました。アーキテクチャごとにCMake対応しないといけない。
ひょっとして、AndroidStudioからCMakeを呼べばいいのでは?、、、つまり、可能です。
プロジェクトを作成してみよう
とりあえず、C:\Users\—\AndroidStudioProjects\MidiFluidに、MidiFluidというプロジェクトを、Android Native Libraryとして作ります。
将来的に、アーキテクチャごとにビルドできるようにしたいです。
C:\Users\—\AndroidStudioProjects\MidiFluid\app\src\main\cpp\fluid_synth_2.5.1
に、fluidsynthの公式GitHubより入手したソースコードZIPを展開します。
https://github.com/FluidSynth/fluidsynth/releases/tag/v2.5.1
ここでは、liboboeのバージョンをそろえるため、fluidsynth-2.5.1-android24.zipもあったほうが、あとで楽ではあります。後述いたします。
C:\Users\—\AndroidStudioProjects\MidiFluid\app\に2つのファイルをおきます。
build.gradle.ktsplugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
id("com.google.android.gms.oss-licenses-plugin")
}
android {
namespace = "jp.gr.java_conf.---.midifluid"
defaultConfig {
applicationId = "jp.gr.java_conf.---.midifluid"
minSdk = 29
targetSdk = 35
compileSdk = 35
versionCode = 1
versionName = "1.0"
versionNameSuffix = "1"
androidResources.localeFilters += listOf("en", "ja")
testApplicationId = buildToolsVersion
externalNativeBuild {
ndk {
abiFilters.remove("armelf_linux_eabi");
}
cmake {
arguments.add("-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON")
arguments.add("-DANDROID_STL=c++_shared")
arguments.add("-Dosal=cpp11")
arguments.add("-Denable-libinstpatch=0")
/*arguments.add("-DANDROID_ARM_NEON=ON")
cppFlags.add("-fopenmp -static-openmp")*/
arguments.add("-Denable-libsndfile=0")
arguments.add("-Denable-opensles=1")
arguments.add("-Denable-oboe=1")
arguments.add("-Denable-dbus=0")
arguments.add("-Denable-oss=0")
/*
-DCMAKE_TOOLCHAIN_FILE=${NDK}/build/cmake/android.toolchain.cmake \
-DANDROID_NATIVE_API_LEVEL=${ANDROID_API} \
-DANDROID_ABI=${ANDROID_ABI_CMAKE} \
-DANDROID_TOOLCHAIN=clang \
-DANDROID_NDK=${NDK} \
-DCMAKE_INSTALL_PREFIX=${PREFIX} \
-DCMAKE_VERBOSE_MAKEFILE=1 \
*/
}
}
}
buildTypes {
release {
isMinifyEnabled = true
}
debug {
isMinifyEnabled = false
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
tasks {
withType<JavaCompile> {
options.compilerArgs.add("-Xlint:unchecked")
options.compilerArgs.add("-deprecation")
}
}
externalNativeBuild {
cmake {
path("CMakeLists.txt")
}
}
viewBinding {
enable = true
}
dataBinding {
enable = true
}
buildFeatures {
prefab = true
viewBinding = true
}
buildToolsVersion = "35.0.1"
kotlinOptions {
jvmTarget = "1.8"
}
dependenciesInfo {
includeInApk = true
includeInBundle = true
}
ndkVersion = "29.0.14206865"
compileSdk {
version = release(35)
}
}
dependencies {
implementation("com.google.oboe:oboe:1.10.0")
implementation(libs.appcompat)
implementation(libs.activity)
implementation(libs.material)
implementation(libs.constraintlayout)
implementation(libs.lifecycle.livedata.ktx)
implementation(libs.lifecycle.viewmodel.ktx)
implementation(libs.legacy.support.v4)
implementation(libs.core.ktx)
implementation(libs.recyclerview)
implementation(libs.navigation.fragment.ktx)
implementation(libs.navigation.ui.ktx)
//implementation(libs.oss.licenses.plugin)
implementation(libs.play.services.oss.licenses)
}
CMakeLists.txtcmake_minimum_required(VERSION 3.4.1)
project(mylib
VERSION 1.0
DESCRIPTION "mylib project"
LANGUAGES CXX
)
add_library(native-lib SHARED src/main/cpp/native-lib.cpp)
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
target_compile_features(native-lib PUBLIC cxx_std_20)
# Create a variable fluidsynth_DIR to specify where the fluidsynth library is located.
set(fluidsynth_DIR C:/github/fluidsynth-2.5.1-android24)
#message(“Architecture1 = ${ANDROID_ABI}")
#message(“Architecture2 = ${CMAKE_ANDROID_ARCH_ABI}")
#find_package(OpenMP REQUIRED)
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_C_FLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set(NDK_DIR C:/Users/---/AppData/Local/Android/Sdk/ndk/android-ndk-r26d)
set(NDK_SUBDIR ${NDK_DIR}/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/17.0.2/lib/linux)
set(NDK_DIR C:/Users/---/AppData/Local/Android/Sdk/ndk/27.2.12479018)
set(NDK_SUBDIR ${NDK_DIR}/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/18/lib/linux)
set(NDK_DIR C:/Users/---/AppData/Local/Android/Sdk/ndk/28.2.13676358)
set(NDK_SUBDIR ${NDK_DIR}/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/19/lib/linux)
set(NDK_DIR C:/Users/---/AppData/Local/Android/Sdk/ndk/29.0.14206865)
set(NDK_SUBDIR ${NDK_DIR}/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/21/lib/linux)
#add_library(libclang_rt SHARED IMPORTED)
if (${ANDROID_ABI} STREQUAL "arm64-v8a")
set(lib_omp_DIR ${NDK_SUBDIR}/aarch64)
#set(lib_c_DIR ${NDK_DIR}/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/lib/aarch64-linux-android)
#set_target_properties(libclang_rt PROPERTIES IMPORTED_LOCATION ${NDK_DIR}/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/18/lib/linux/libclang_rt.asan-aarch64-android.so)
elseif (${ANDROID_ABI} STREQUAL "armeabi-v7a")
set(lib_omp_DIR ${NDK_SUBDIR}/arm)
#set(lib_c_DIR ${NDK_DIR}/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/lib/arm-linux-androideabi)
#set_target_properties(libclang_rt PROPERTIES IMPORTED_LOCATION ${NDK_DIR}/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/18/lib/linux/libclang_rt.asan-arm-android.so)
elseif (${ANDROID_ABI} STREQUAL "x86_64")
set(lib_omp_DIR ${NDK_SUBDIR}/x86_64)
#set(lib_c_DIR ${NDK_DIR}/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/lib/x86_64-linux-android)
#set_target_properties(libclang_rt PROPERTIES IMPORTED_LOCATION ${NDK_DIR}/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/18/lib/linux/libclang_rt.asan-x86_64-android.so)
elseif (${ANDROID_ABI} STREQUAL "x86")
set(lib_omp_DIR ${NDK_SUBDIR}/i386)
#set(lib_c_DIR ${NDK_DIR}/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/lib/i686-linux-android)
#set_target_properties(libclang_rt PROPERTIES IMPORTED_LOCATION ${NDK_DIR}/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/18/lib/linux/libclang_rt.asan-i686-android.so)
endif ()
# Create a variable lib_other_DIR to specify where the other non-fluidsynth libraries is located.
#set(lib_c_DIR C:/github/android-ndk-r27c/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/lib/aarch64-linux-android/)
# set(lib_c_DIR C:/github/back/MixAndCC/app/build/intermediates/cxx/Debug/5ea1y5rp/obj/arm64-v8a)
# Fluidsynth library code will be calling some non-fluidsynth functions which are not part of
# default NDK, so we add the binaries as dependencies of our code.
#add_library(libc++_shared SHARED IMPORTED)
#set_target_properties(libc++_shared PROPERTIES IMPORTED_LOCATION ${lib_c_DIR}/libc++_shared.so)
#add_library(libomp SHARED IMPORTED)
#set_target_properties(libomp PROPERTIES IMPORTED_LOCATION ${lib_omp_DIR}/libomp.so)
# Our code (native-lib.cpp) will be calling fluidsynth functions, so adding the fluidsynth binaries as dependencies.
add_library(libFLAC SHARED IMPORTED)
set_target_properties(libFLAC PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libFLAC.so)
#add_library(libfluidsynth SHARED IMPORTED)
#set_target_properties(libfluidsynth PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libfluidsynth.so)
#set_target_properties(libfluidsynth PROPERTIES IMPORTED_LOCATION C:/Users/---/AndroidStudioProjects/MidiFluid/app/build/intermediates/merged_native_libs/debug/mergeDebugNativeLibs/out/lib/${ANDROID_ABI}/libfluidsynth.so)
add_library(libfluidsynth-assetloader SHARED IMPORTED)
set_target_properties(libfluidsynth-assetloader PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libfluidsynth-assetloader.so)
#add_library(libgio-2.0 SHARED IMPORTED)
#set_target_properties(libgio-2.0 PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libgio-2.0.so)
#add_library(libglib-2.0 SHARED IMPORTED)
#set_target_properties(libglib-2.0 PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libglib-2.0.so)
#add_library(libgmodule-2.0 SHARED IMPORTED)
#set_target_properties(libgmodule-2.0 PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libgmodule-2.0.so)
#add_library(libgobject-2.0 SHARED IMPORTED)
#set_target_properties(libgobject-2.0 PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libgobject-2.0.so)
#add_library(libgthread-2.0 SHARED IMPORTED)
#set_target_properties(libgthread-2.0 PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libgthread-2.0.so)
#add_library(libinstpatch-1.0 SHARED IMPORTED)
#set_target_properties(libinstpatch-1.0 PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libinstpatch-1.0.so)
#find_package(PkgConfig REQUIRED)
#pkg_search_module(GLIB REQUIRED glib-2.0)
#target_include_directories(native-lib PRIVATE ${GLIB_INCLUDE_DIRS})
#target_link_libraries(native-lib INTERFACE ${GLIB_LDFLAGS})
#add_library(liboboe SHARED IMPORTED)
#set_target_properties(liboboe PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/liboboe.so)
add_library(libogg SHARED IMPORTED)
set_target_properties(libogg PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libogg.so)
add_library(libopus SHARED IMPORTED)
set_target_properties(libopus PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libopus.so)
add_library(libpcre SHARED IMPORTED)
set_target_properties(libpcre PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libpcre.so)
add_library(libsndfile SHARED IMPORTED)
set_target_properties(libsndfile PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libsndfile.so)
add_library(libvorbis SHARED IMPORTED)
set_target_properties(libvorbis PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libvorbis.so)
add_library(libvorbisenc SHARED IMPORTED)
set_target_properties(libvorbisenc PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libvorbisenc.so)
add_library(libvorbisfile SHARED IMPORTED)
set_target_properties(libvorbisfile PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libvorbisfile.so)
# Specifies the directory where the C or C++ source code will look the #include <yourlibrary.h> header files
target_include_directories(native-lib PRIVATE ${fluidsynth_DIR}/include)
find_library(ANDROID_LIB android)
list(APPEND PAG_SHARED_LIBS ${ANDROID_LIB})
find_package (oboe REQUIRED CONFIG)
add_subdirectory("src/main/cpp/fluid_synth_2.5.1")
# Link everything all together. Notice that native-lib should be the first element in the list.
target_link_libraries(
native-lib
android
#libclang_rt
# Non-fluidsynth binaries
#libomp
#libc++_shared
log
#Gnu Package
#libgmodule-2.0
#libglib-2.0
#libgobject-2.0
#libgthread-2.0
#libgio-2.0
#libinstpatch-1.0
# fluidsynth binaries
libFLAC
libfluidsynth
libfluidsynth-assetloader
#liboboe
libogg
libopus
libpcre
libsndfile
libvorbis
libvorbisenc
libvorbisfile
oboe::oboe
)
FluidsynthのCMakeを、このプロジェクトのCMakeを使い呼び出す形です。メニューのビルドや、構成の変更を用いることができます。
このときの、CMakeの指示ファイルには、oboe::oboeをつかうようにしていますが。前述のZIPから展開した、liboboe.soを用いたほうが、そのZIPを用いているプロジェクトと親和性が高いです。(構造体が変更されてて、ビルドできても、実行時にフィールドが異なってしまう)
使ってるSDKのバージョンによっては、このURLのままでは警告やエラーがありますので、上の2つのファイルと見比べるといいかもしれません。Anrdoidの世界では、新しいが絶対的な正義ではありませんので、両方残します。
自分のアプリだけで、oboeを用いるなら、下記の対応で十分です。FluidSynth自体が、オーディオドライバーとして、Oboeをサポートしています。モジュールがあります。アクティベートするには、CMake時のフラグを変更して、ライブラリをリンクするだけです。find_packageではなくライブラリを直リンクする場合には、強制的にフラグをたてる変更をCMakeにほどこさないと、見つからないことになってしまいます。(後述)
Oboe 向けにビルド設定を更新する
https://developer.android.com/games/sdk/oboe/update-build-settings?hl=ja
関連しますが、fluid_synth_2.5.1\CMakeLists.txtを編集しました。
706:# find_package ( oboe )
707: find_package (oboe REQUIRED CONFIG)
oboeをいうパッケージ検索するさい、ローカルではなく、特定のリポジトリから見つけるためです、コマンドの意味は、上記のURLをご参照ください。リポジトリは標準のものではありません。
結局ここまでコンパイルして実行して、Glibを無効にしたりしても、音飛びが発生しましたので、ライブラリや、FluidSynth自体の構造的な問題ではなさそうです。(つづく)
広告を表示しています。(つづき)すると、考えられる要因の2番目です。
「単純に処理が重い」のかもしれない
FluidSynthには、さまざまなパフォーマンス用のパラメータがあります。
設定方法はこちらです。
https://www.fluidsynth.org/api/CreatingSettings.html
設定可能な一覧がこちらです。
https://www.fluidsynth.org/api/fluidsettings.html > Synthesizer settings
fluid_settings_setint(handle->settings, "synth.polyphony", 180); //256
fluid_settings_setint(handle->settings, "synth.note-cut", 2); //0
fluid_settings_setnum(handle->settings, "synth.overflow.sustained", 0); //-1000
fluid_settings_setnum(handle->settings, "synth.overflow.age", -1000); //1000
//の右が、デフォルト値です。
synth.polyphone (256 → 180)
発音数を指定します。おそらくですが、ステレオは2づつ、レイヤーの場合もその分も消費されていると思います。
synth-note-cut (0 → 2)
あるキーがなっているとき、そのキーのNoteOnが再び送られたときの処理。これカットするようにしたところ、とてもDTM音源ぽい響きになりましたね。逆にいうと、DTM音源ぽさをなくすには、この逆の対応がよさそうです。ただ、元のままだと、シンセエンジンの消費するポリ数は、それなりに増加するようです。
synth.overflow.sustained (-1000 → 0) // -が消音しやすいぽい
サスティーンされているノートが発音数たりなくなったとき、消音する優先度だと思っています。
ポリ数を減らしたところ、サスティーンされている重要な音が消されてしまうので。消失しない優先度に変更しました。
synth.overflow.age (1000 → -1000) // -が消音しやすいぽい
古いキーの音符というだけでは、消失の対象になりにくいという設定でした。一般的なシンセメーカーに合わせて、消失させるようにしました。この修正が、DTM音源ぽさがました原因です。でも、発音数を減らすためには、、、
大きなMIDIファイルを再生するとき、この設定のほうが、負荷がさがるので、音飛びは防ぎやすいです。でも、鑑賞目的であれば、デフォルトの設定のほうがいいかもしれません。
端末が過剰なチューニングをしている端末や、バックグランドタスクが増えると、また音割れはおこるかもしれない。なので、オプションを追加する予定です。3択くらいの最大発音数、2択くらいの優先順位。クリスマスまでには間に合わせたいです!

