Skip to content

Commit

Permalink
feat: add convolver benchmarking support
Browse files Browse the repository at this point in the history
  • Loading branch information
timschneeb committed Aug 11, 2023
1 parent c6e654e commit 586dcd5
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 19 deletions.
2 changes: 1 addition & 1 deletion app/src/main/cpp/libjamesdsp
13 changes: 7 additions & 6 deletions app/src/main/cpp/libjamesdsp-wrapper/JamesDspWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,14 @@ Java_me_timschneeberger_rootlessjamesdsp_interop_JamesDspWrapper_free(JNIEnv *en
LOGD("JamesDspWrapper::dtor: memory freed");
}

extern "C" JNIEXPORT jint JNICALL
Java_me_timschneeberger_rootlessjamesdsp_interop_JamesDspWrapper_getBenchmarkSize(JNIEnv *env, jobject obj) {
return MAX_BENCHMARK;
}

extern "C" JNIEXPORT void JNICALL
Java_me_timschneeberger_rootlessjamesdsp_interop_JamesDspWrapper_runBenchmark(JNIEnv *env, jobject obj, jlong self, jdoubleArray jc0, jdoubleArray jc1)
Java_me_timschneeberger_rootlessjamesdsp_interop_JamesDspWrapper_runBenchmark(JNIEnv *env, jobject obj, jdoubleArray jc0, jdoubleArray jc1)
{
DECLARE_DSP_V

LOGD("JamesDspWrapper::runBenchmark: started");

auto c0 = env->GetDoubleArrayElements(jc0, nullptr);
Expand All @@ -183,10 +186,8 @@ Java_me_timschneeberger_rootlessjamesdsp_interop_JamesDspWrapper_runBenchmark(JN
}

extern "C" JNIEXPORT void JNICALL
Java_me_timschneeberger_rootlessjamesdsp_interop_JamesDspWrapper_loadBenchmark(JNIEnv *env, jobject obj, jlong self, jdoubleArray jc0, jdoubleArray jc1)
Java_me_timschneeberger_rootlessjamesdsp_interop_JamesDspWrapper_loadBenchmark(JNIEnv *env, jobject obj, jdoubleArray jc0, jdoubleArray jc1)
{
DECLARE_DSP_V

LOGD("JamesDspWrapper::loadBenchmark: loading data");

auto c0 = env->GetDoubleArrayElements(jc0, nullptr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import android.content.Intent
import android.os.Bundle
import androidx.preference.ListPreference
import androidx.preference.Preference
import me.timschneeberger.rootlessjamesdsp.BuildConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import me.timschneeberger.rootlessjamesdsp.R
import me.timschneeberger.rootlessjamesdsp.activity.OnboardingActivity
import me.timschneeberger.rootlessjamesdsp.interop.BenchmarkManager
import me.timschneeberger.rootlessjamesdsp.preference.MaterialSeekbarPreference
import me.timschneeberger.rootlessjamesdsp.preference.MaterialSwitchPreference
import me.timschneeberger.rootlessjamesdsp.service.RootAudioProcessorService
Expand All @@ -29,15 +32,18 @@ class SettingsAudioFormatFragment : SettingsBaseFragment() {
private val legacyMode by lazy { findPreference<MaterialSwitchPreference>(getString(R.string.key_audioformat_processing)) }
private val enhancedMode by lazy { findPreference<MaterialSwitchPreference>(getString(R.string.key_audioformat_enhanced_processing)) }
private val enhancedModeInfo by lazy { findPreference<Preference>(getString(R.string.key_audioformat_enhanced_processing_info)) }
private val benchmark by lazy { findPreference<MaterialSwitchPreference>(getString(R.string.key_audioformat_optimization_benchmark)) }
private val benchmarkRefresh by lazy { findPreference<Preference>(getString(R.string.key_audioformat_optimization_refresh)) }

private val preferences: Preferences.App by inject()

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.sharedPreferencesName = Constants.PREF_APP
setPreferencesFromResource(R.xml.app_audio_format_preferences, rootKey)

// Root: Hide audio format category
// Root: Hide audio format & benchmark category
encoding?.parent?.isVisible = isRootless()
benchmark?.parent?.isVisible = !isRoot()

// Rootless: Hide audio processing category
legacyMode?.parent?.isVisible = isRoot()
Expand Down Expand Up @@ -79,6 +85,27 @@ class SettingsAudioFormatFragment : SettingsBaseFragment() {
true
}

fun runBenchmark() = context?.let { ctx ->
BenchmarkManager.runBenchmarks(ctx) {
CoroutineScope(Dispatchers.Main).launch {
benchmark?.isChecked = BenchmarkManager.hasBenchmarksCached()
}
}
}

benchmark?.isChecked = BenchmarkManager.hasBenchmarksCached()
benchmark?.setOnPreferenceChangeListener { _, newValue ->
if(newValue as Boolean)
runBenchmark()
else
BenchmarkManager.clearBenchmarks()
true
}
benchmarkRefresh?.setOnPreferenceClickListener {
runBenchmark()
true
}

bufferSize?.setDefaultValue(preferences.getDefault<Float>(R.string.key_audioformat_buffersize))
bufferSize?.setOnPreferenceChangeListener { _, newValue ->
if((newValue as Float) <= 1024){
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package me.timschneeberger.rootlessjamesdsp.fragment.settings

import android.os.Bundle
import androidx.preference.*
import me.timschneeberger.rootlessjamesdsp.BuildConfig
import androidx.preference.Preference
import me.timschneeberger.rootlessjamesdsp.R
import me.timschneeberger.rootlessjamesdsp.utils.isPlugin
import me.timschneeberger.rootlessjamesdsp.utils.isRootless
Expand All @@ -17,11 +16,13 @@ class SettingsFragment : SettingsBaseFragment() {
setPreferencesFromResource(R.xml.app_preferences, rootKey)

processing?.summary = getString(
if(isRootless()) R.string.audio_format_summary
else R.string.audio_format_summary_root
when {
isRootless() -> R.string.audio_format_summary
isPlugin() -> R.string.audio_format_summary_plugin
else -> R.string.audio_format_summary_root
}
)
troubleshooting?.isVisible = isRootless()
processing?.isVisible = !isPlugin()
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package me.timschneeberger.rootlessjamesdsp.interop

import android.content.Context
import android.os.Build
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.timschneeberger.rootlessjamesdsp.R
import me.timschneeberger.rootlessjamesdsp.utils.preferences.Preferences
import me.timschneeberger.rootlessjamesdsp.utils.sdkAbove
import me.timschneeberger.rootlessjamesdsp.view.ProgressDialog
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import timber.log.Timber

object BenchmarkManager : KoinComponent {
private val varPrefs by inject<Preferences.Var>()

fun hasBenchmarksCached(): Boolean {
val c0 = varPrefs.get<String>(R.string.key_benchmark_c0)
val c1 = varPrefs.get<String>(R.string.key_benchmark_c1)
val expectedSize = JamesDspWrapper.getBenchmarkSize()
return c0.split(';').size == expectedSize && c1.split(';').size == expectedSize
}

fun loadBenchmarksFromCache() {
Timber.d("Loading benchmarks from cache")

val c0 = varPrefs.get<String>(R.string.key_benchmark_c0)
.split(";")
.mapNotNull { it.toDoubleOrNull() }
.toDoubleArray()
val c1 = varPrefs.get<String>(R.string.key_benchmark_c1)
.split(";")
.mapNotNull { it.toDoubleOrNull() }
.toDoubleArray()

val expectedSize = JamesDspWrapper.getBenchmarkSize()
if(c0.size != expectedSize || c1.size != expectedSize) {
Timber.e("Benchmarks missing or malformed")
return
}

JamesDspWrapper.loadBenchmark(c0, c1)
}

fun runBenchmarks(context: Context, onFinished: (success: Boolean) -> Unit) {
sdkAbove(Build.VERSION_CODES.S) {
assert(context.isUiContext)
}

var job: Job? = null
val dialog = ProgressDialog(context) {
job?.cancel()
onFinished(false)
}.apply {
isIndeterminate = true
title = context.getString(R.string.preparing)
}
dialog.isIndeterminate = true

job = CoroutineScope(Dispatchers.Default).launch {
flow {
emit(BenchmarkState.Benchmarking)

val size = JamesDspWrapper.getBenchmarkSize()
val c0 = DoubleArray(size)
val c1 = DoubleArray(size)

JamesDspWrapper.runBenchmark(c0, c1)

varPrefs.set(R.string.key_benchmark_c0, c0.joinToString(";"))
varPrefs.set(R.string.key_benchmark_c1, c1.joinToString(";"))

emit(BenchmarkState.BenchmarkDone)

Timber.d("benchmark c0: " + c0.joinToString(";"))
Timber.d("benchmark c1: " + c1.joinToString(";"))

}
.cancellable()
.collect {
withContext(Dispatchers.Main) {
when (it) {
BenchmarkState.BenchmarkDone -> dialog.dismiss()
BenchmarkState.Benchmarking -> dialog.title = context.getString(R.string.audio_format_optimization_benchmark_ongoing)
}
}
}
}

job.invokeOnCompletion { thr ->
onFinished(thr == null)
}
}

fun clearBenchmarks() {
varPrefs.set(R.string.key_benchmark_c0, "")
varPrefs.set(R.string.key_benchmark_c1, "")
}

sealed class BenchmarkState {
object Benchmarking : BenchmarkState()
object BenchmarkDone: BenchmarkState()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import me.timschneeberger.rootlessjamesdsp.utils.extensions.ContextExtensions.se
import timber.log.Timber
import java.util.Timer
import kotlin.concurrent.schedule
import kotlin.reflect.jvm.internal.impl.renderer.ClassifierNamePolicy.SHORT

class JamesDspLocalEngine(context: Context, callbacks: JamesDspWrapper.JamesDspCallbacks? = null) : JamesDspBaseEngine(context, callbacks) {
var handle: JamesDspHandle = JamesDspWrapper.alloc(callbacks ?: DummyCallbacks())
Expand All @@ -22,6 +21,11 @@ class JamesDspLocalEngine(context: Context, callbacks: JamesDspWrapper.JamesDspC
get() = super.sampleRate
override var enabled: Boolean = true

init {
if(BenchmarkManager.hasBenchmarksCached())
BenchmarkManager.loadBenchmarksFromCache()
}

override fun close() {
val oldHandle = handle
handle = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ object JamesDspWrapper {
external fun isHandleValid(self: JamesDspHandle): Boolean

// Benchmarking
external fun runBenchmark(self: JamesDspHandle, c0: DoubleArray, c1: DoubleArray)
external fun loadBenchmark(self: JamesDspHandle, c0: DoubleArray, c1: DoubleArray)
external fun getBenchmarkSize(): Int
external fun runBenchmark(c0: DoubleArray, c1: DoubleArray)
external fun loadBenchmark(c0: DoubleArray, c1: DoubleArray)

// Processing (interleaved)
external fun processInt16(self: JamesDspHandle, input: ShortArray, output: ShortArray, offset: Int = -1, length: Int = -1)
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/res/values/defaults.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
<bool name="default_is_activity_active" translatable="false" tools:keep="@bool/default_is_activity_active">false</bool>
<bool name="default_is_app_compat_activity_active" translatable="false" tools:keep="@bool/default_is_app_compat_activity_active">false</bool>

<!-- Internal DSP (root only) -->
<!-- Internal DSP -->
<bool name="default_powered_on" translatable="false" tools:keep="@bool/default_powered_on">true</bool>
<string name="default_benchmark_c0" translatable="false" tools:keep="@string/default_benchmark_c0" />
<string name="default_benchmark_c1" translatable="false" tools:keep="@string/default_benchmark_c0" />

<!-- Settings -->
<bool name="default_share_crash_reports" translatable="false">true</bool>
Expand All @@ -32,6 +34,7 @@
<integer name="default_audioformat_buffersize" translatable="false">8192</integer>
<bool name="default_audioformat_processing" translatable="false">true</bool>
<bool name="default_audioformat_enhanced_processing" translatable="false">false</bool>
<bool name="default_audioformat_optimization_benchmark" translatable="false">false</bool>

<!-- Device profiles -->
<bool name="default_device_profiles_enable" translatable="false">false</bool>
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/res/values/keys.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
<string name="key_is_activity_active" translatable="false">is_activity_active</string>
<string name="key_is_app_compat_activity_active" translatable="false">is_app_compat_activity_active</string>

<!-- Internal DSP (root only) -->
<!-- Internal DSP -->
<string name="key_powered_on" translatable="false">powered_on</string>
<string name="key_benchmark_c0" translatable="false">benchmark_c0</string>
<string name="key_benchmark_c1" translatable="false">benchmark_c1</string>

<!-- Settings categories (static) -->
<string name="key_appearance" translatable="false">appearance</string>
Expand Down Expand Up @@ -38,6 +40,7 @@
<string name="key_audioformat_buffersize" translatable="false">audioformat_buffersize</string>
<string name="key_audioformat_processing" translatable="false">audioformat_processing</string>
<string name="key_audioformat_enhanced_processing" translatable="false">audioformat_enhanced_processing</string>
<string name="key_audioformat_optimization_benchmark" translatable="false">audioformat_optimization_benchmark</string>

<!-- Device profiles -->
<string name="key_device_profiles_enable" translatable="false">device_profiles_enable</string>
Expand Down Expand Up @@ -66,6 +69,7 @@
<string name="key_credits_check_update" translatable="false">credits_check_update</string>
<string name="key_credits_google_play" translatable="false">credits_google_play</string>
<string name="key_audioformat_enhanced_processing_info" translatable="false">audioformat_enhanced_processing_info</string>
<string name="key_audioformat_optimization_refresh" translatable="false">audioformat_optimization_refresh</string>
<string name="key_device_profiles_info" translatable="false">device_profiles_info</string>
<string name="key_misc_permission_skip_prompt" translatable="false">misc_permission_skip_prompt</string>
<string name="key_misc_permission_auto_start" translatable="false">misc_permission_auto_start</string>
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
<string name="no_activity_found">No Activity found to handle action</string>
<string name="permission_allowed">Allowed</string>
<string name="permission_not_allowed">Not allowed</string>
<string name="preparing">Preparing…</string>
<string name="error_projection_api_missing">Your device manufacturer has disabled the internal audio recording APIs. This application cannot work without them.</string>

<!-- Main screen -->
Expand Down Expand Up @@ -305,12 +306,18 @@
<string name="audio_format_media_apps_need_restart">You need to restart all active media apps after changing this setting to resume audio processing.</string>
<string name="audio_format_summary">Preferred audio encoding, buffer size</string>
<string name="audio_format_summary_root">Legacy mode, enhanced processing</string>
<string name="audio_format_summary_plugin">Convolver optimizations</string>
<string name="audio_format_encoding">Audio encoding</string>
<string name="audio_format_encoding_int16">16-bit integer PCM</string>
<string name="audio_format_encoding_float">32-bit float PCM</string>
<string name="audio_format_buffer_size">Buffer size</string>
<string name="audio_format_buffer_size_unit">&#xa0;samples</string>
<string name="audio_format_buffer_size_warning_low_value">Warning: Low buffer sizes may cause audio issues such as clipping!</string>
<string name="audio_format_optimization_header">Convolver module optimizations</string>
<string name="audio_format_optimization_refresh">Refresh benchmarking data</string>
<string name="audio_format_optimization_benchmark">Use benchmarks to optimize performance</string>
<string name="audio_format_optimization_benchmark_summary">Increases performance of effects that use convolver modules</string>
<string name="audio_format_optimization_benchmark_ongoing">Benchmark in progress…</string>
<string name="profiles_section_header">Device profiles</string>
<string name="profiles_summary">Per-device profiles, automatic switching</string>
<string name="profiles_enable">Per-device profiles</string>
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/res/xml/app_audio_format_preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,23 @@
app:iconSpaceReserved="false"/>

</PreferenceCategory>

<PreferenceCategory
android:title="@string/audio_format_optimization_header"
app:iconSpaceReserved="false">

<me.timschneeberger.rootlessjamesdsp.preference.MaterialSwitchPreference
android:key="@string/key_audioformat_optimization_benchmark"
android:defaultValue="@bool/default_audioformat_optimization_benchmark"
android:title="@string/audio_format_optimization_benchmark"
android:summary="@string/audio_format_optimization_benchmark_summary"
app:iconSpaceReserved="false" />

<Preference
android:key="@string/key_audioformat_optimization_refresh"
android:title="@string/audio_format_optimization_refresh"
android:dependency="@string/key_audioformat_optimization_benchmark"
app:iconSpaceReserved="false"/>

</PreferenceCategory>
</PreferenceScreen>

0 comments on commit 586dcd5

Please sign in to comment.