Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions samples/powerplay/src/main/cpp/PowerPlayJNI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,13 @@ Java_com_google_oboe_samples_powerplay_engine_PowerPlayAudioPlayer_isOffloadedNa
return player.isOffloaded();
}

JNIEXPORT jint JNICALL
Java_com_google_oboe_samples_powerplay_engine_PowerPlayAudioPlayer_getSessionIdNative(
JNIEnv *env,
jobject) {
return player.getSessionId();
}

/**
* Native (JNI) implementation of PowerPlayAudioPlayer.getCurrentlyPlayingIndexNative()
*/
Expand Down
13 changes: 12 additions & 1 deletion samples/powerplay/src/main/cpp/PowerPlayMultiPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,13 @@ bool PowerPlayMultiPlayer::openStream(oboe::PerformanceMode performanceMode) {
->setUsage(Usage::Media)
->setContentType(ContentType::Music)
->setFramesPerDataCallback(128)
->setSharingMode(SharingMode::Exclusive);
->setSessionId(oboe::SessionId::Allocate);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By allocating session id, all the paths will not be on a low latency path...

Please try playing with the settings in OboeTester to see what happens when you do this.

That's why we have been telling folks to add their own effects in the app if possible and not rely on platform effects.

BassBoost, Equalizer, and Loudness effects can easily be written without using the AudioEffect from Android. The app can easily implement this as it renders audio


if (performanceMode == oboe::PerformanceMode::PowerSavingOffloaded) {
builder.setSharingMode(oboe::SharingMode::Shared);
} else {
builder.setSharingMode(oboe::SharingMode::Exclusive);
}

Result result = builder.openStream(mAudioStream);
if (result != Result::OK) {
Expand Down Expand Up @@ -372,3 +378,8 @@ bool PowerPlayMultiPlayer::setPlaybackParameters(float speed, float pitch) {
mPlaybackPitch = pitch;
return true;
}

int32_t PowerPlayMultiPlayer::getSessionId() {
if (mAudioStream == nullptr) return -1;
return static_cast<int32_t>(mAudioStream->getSessionId());
}
2 changes: 2 additions & 0 deletions samples/powerplay/src/main/cpp/PowerPlayMultiPlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ class PowerPlayMultiPlayer : public iolib::SimpleMultiPlayer {

bool setPlaybackParameters(float speed, float pitch);

int32_t getSessionId();

private:
class MyPresentationCallback : public oboe::AudioStreamPresentationCallback {
public:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Menu
import androidx.compose.foundation.background
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
Expand Down Expand Up @@ -136,6 +139,7 @@ import android.os.Handler
import android.os.Looper
import android.util.Log
import com.google.oboe.samples.powerplay.automation.IntentBasedTestSupport
import com.google.oboe.samples.powerplay.ui.effects.EffectsBottomSheet
import com.google.oboe.samples.powerplay.automation.IntentBasedTestSupport.LOG_TAG
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -483,6 +487,9 @@ class MainActivity : ComponentActivity() {
var showBottomSheet by remember { mutableStateOf(false) }
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)

var showEffectsBottomSheet by remember { mutableStateOf(false) }
val effectsSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)

var showInfoDialog by remember { mutableStateOf(false) }
var assetsReady by remember { mutableStateOf(false) }
var playbackPosition by remember { mutableLongStateOf(0L) }
Expand All @@ -508,7 +515,6 @@ class MainActivity : ComponentActivity() {

playbackPosition = player.getPlaybackPositionMillis()
}

LaunchedEffect(pagerState) {
snapshotFlow { pagerState.currentPage }
.distinctUntilChanged()
Expand Down Expand Up @@ -542,7 +548,6 @@ class MainActivity : ComponentActivity() {
pendingAutomationIntent = null
}
}

Box(
modifier = Modifier
.fillMaxSize()
Expand Down Expand Up @@ -577,7 +582,19 @@ class MainActivity : ComponentActivity() {
modifier = Modifier.size(32.dp)
)
}

IconButton(
onClick = { showEffectsBottomSheet = true },
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(32.dp)
) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = "Effects",
tint = Color.Black,
modifier = Modifier.size(32.dp)
)
}
IconButton(
onClick = { filePickerLauncher.launch(arrayOf("audio/wav", "audio/x-wav")) },
enabled = !isLoading,
Expand Down Expand Up @@ -610,7 +627,6 @@ class MainActivity : ComponentActivity() {
)
}
}

if (isLoading) {
Box(
modifier = Modifier
Expand All @@ -620,7 +636,6 @@ class MainActivity : ComponentActivity() {
CircularProgressIndicator(modifier = Modifier.size(24.dp))
}
}

Column(horizontalAlignment = Alignment.CenterHorizontally) {
AnimatedContent(targetState = playingSongIndex.intValue, transitionSpec = {
(scaleIn() + fadeIn()) togetherWith (scaleOut() + fadeOut())
Expand Down Expand Up @@ -699,9 +714,7 @@ class MainActivity : ComponentActivity() {
}
}
}

Spacer(modifier = Modifier.height(16.dp))

Row(
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
Expand All @@ -725,7 +738,6 @@ class MainActivity : ComponentActivity() {
}
}
}

if (showBottomSheet) {
ModalBottomSheet(
onDismissRequest = { showBottomSheet = false },
Expand Down Expand Up @@ -761,7 +773,20 @@ class MainActivity : ComponentActivity() {
)
}
}

if (showEffectsBottomSheet) {
ModalBottomSheet(
onDismissRequest = { showEffectsBottomSheet = false },
sheetState = effectsSheetState,
containerColor = Color.White,
shape = androidx.compose.foundation.shape.RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp)
) {
EffectsBottomSheet(
effectsController = player.effectsController,
isOffloadMode = offload.intValue == 3,
onDismiss = { showEffectsBottomSheet = false }
)
}
}
if (showInfoDialog) {
val performanceModeText = when (offload.intValue) {
0 -> "None"
Expand Down Expand Up @@ -874,7 +899,6 @@ class MainActivity : ComponentActivity() {
val requestedFrames = remember { mutableIntStateOf(0) }
val actualFrames = remember { mutableIntStateOf(0) }
var isModified by remember { mutableStateOf(playbackSpeed != 1.0f || playbackPitch != 1.0f) }

var localSpeed by remember { mutableFloatStateOf(playbackSpeed) }
var localPitch by remember { mutableFloatStateOf(playbackPitch) }

Expand Down Expand Up @@ -1072,7 +1096,6 @@ class MainActivity : ComponentActivity() {
}
}
}

AnimatedVisibility(
visible = offload.intValue == 3,
enter = androidx.compose.animation.expandVertically() + fadeIn(),
Expand Down Expand Up @@ -1139,7 +1162,6 @@ class MainActivity : ComponentActivity() {
}
}
}

@Composable
fun ControlButton(icon: Int, size: Dp, onClick: () -> Unit) {
Box(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.oboe.samples.powerplay.effects
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, please add copyright declaration for all new added files.


import android.media.audiofx.BassBoost
import android.util.Log

class BassBoostManager(sessionId: Int) {
private val TAG = "BassBoostManager"
private var bassBoost: BassBoost? = null

init {
try {
bassBoost = BassBoost(0, sessionId).apply {
enabled = true
}
Log.i(TAG, "BassBoost created for session $sessionId")
} catch (e: Exception) {
Log.e(TAG, "Failed to create BassBoost", e)
}
}

val isAvailable: Boolean get() = bassBoost != null

fun setStrength(strength: Short) {
if (bassBoost?.strengthSupported == true) {
bassBoost?.setStrength(strength)
}
}

fun getStrength(): Short = bassBoost?.roundedStrength ?: 0

fun release() {
bassBoost?.release()
bassBoost = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.oboe.samples.powerplay.effects

import android.media.audiofx.AudioEffect
import android.media.audiofx.BassBoost
import android.media.audiofx.Equalizer
import android.media.audiofx.LoudnessEnhancer

import android.util.Log
import java.util.UUID

class EffectsController {
private val TAG = "EffectsController"

var equalizer: EqualizerManager? = null
private set
var bassBoost: BassBoostManager? = null
private set
var loudness: LoudnessManager? = null
private set

fun initialize(sessionId: Int) {
if (sessionId <= 0) {
Log.w(TAG, "Invalid session ID $sessionId, cannot initialize effects")
return
}

Log.i(TAG, "Initializing effects for session $sessionId")

if (isEffectSupported(Equalizer.EFFECT_TYPE_EQUALIZER)) {
equalizer = EqualizerManager(sessionId)
} else {
Log.w(TAG, "Equalizer not supported")
}

if (isEffectSupported(BassBoost.EFFECT_TYPE_BASS_BOOST)) {
bassBoost = BassBoostManager(sessionId)
} else {
Log.w(TAG, "Bass Boost not supported")
}

if (isEffectSupported(LoudnessEnhancer.EFFECT_TYPE_LOUDNESS_ENHANCER)) {
loudness = LoudnessManager(sessionId)
} else {
Log.w(TAG, "Loudness Enhancer not supported")
}
}

private fun isEffectSupported(effectTypeUuid: UUID): Boolean {
val descriptors = AudioEffect.queryEffects()
for (descriptor in descriptors) {
if (descriptor.type == effectTypeUuid) {
return true
}
}
return false
}

fun release() {
Log.i(TAG, "Releasing effects")
equalizer?.release()
bassBoost?.release()
loudness?.release()
equalizer = null
bassBoost = null
loudness = null
}

enum class EffectType {
EQUALIZER, BASS_BOOST, LOUDNESS
}

fun getSupportedEffects(): List<EffectType> {
val list = mutableListOf<EffectType>()
if (equalizer?.isAvailable == true) list.add(EffectType.EQUALIZER)
if (bassBoost?.isAvailable == true) list.add(EffectType.BASS_BOOST)
if (loudness?.isAvailable == true) list.add(EffectType.LOUDNESS)
return list
}
}
Loading
Loading