Skip to content

Commit 515488e

Browse files
authored
Support playback parameters. (#2279)
* Support playback parameters. Fixes #2272.
1 parent 999a9e1 commit 515488e

16 files changed

Lines changed: 639 additions & 13 deletions

apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,23 @@ int64_t ActivityTestOutput::flushFromFrame(int32_t accuracy, int64_t frame) {
606606
}
607607
}
608608

609+
oboe::Result ActivityTestOutput::setPlaybackParameters(const oboe::PlaybackParameters& parameters) {
610+
std::shared_ptr<oboe::AudioStream> oboeStream = getOutputStream();
611+
if (oboeStream == nullptr) {
612+
return oboe::Result::ErrorInvalidState;
613+
}
614+
615+
return oboeStream->setPlaybackParameters(parameters);
616+
}
617+
618+
oboe::ResultWithValue<oboe::PlaybackParameters> ActivityTestOutput::getPlaybackParameters() {
619+
std::shared_ptr<oboe::AudioStream> oboeStream = getOutputStream();
620+
if (oboeStream == nullptr) {
621+
return {oboe::Result::ErrorInvalidState};
622+
}
623+
return oboeStream->getPlaybackParameters();
624+
}
625+
609626
// ======================================================================= ActivityTestInput
610627
void ActivityTestInput::configureAfterOpen() {
611628
mInputAnalyzer.reset();

apps/OboeTester/app/src/main/cpp/NativeAudioContext.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,14 @@ class ActivityContext {
300300

301301
virtual void setAmplitude(float amplitude) {}
302302

303+
virtual oboe::Result setPlaybackParameters(const oboe::PlaybackParameters& parameters) {
304+
return oboe::Result::ErrorUnimplemented;
305+
}
306+
307+
virtual oboe::ResultWithValue<oboe::PlaybackParameters> getPlaybackParameters() {
308+
return oboe::ResultWithValue<oboe::PlaybackParameters>(oboe::Result::ErrorUnimplemented);
309+
}
310+
303311
virtual int32_t saveWaveFile(const char *filename);
304312

305313
virtual void setMinimumFramesBeforeRead(int32_t numFrames) {}
@@ -478,6 +486,10 @@ class ActivityTestOutput : public ActivityContext {
478486

479487
int64_t flushFromFrame(int32_t accuracy, int64_t frame) final;
480488

489+
oboe::Result setPlaybackParameters(const oboe::PlaybackParameters& parameters) final;
490+
491+
oboe::ResultWithValue<oboe::PlaybackParameters> getPlaybackParameters() final;
492+
481493
protected:
482494
SignalType mSignalType = SignalType::Sine;
483495

apps/OboeTester/app/src/main/cpp/jni-bridge.cpp

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ static NativeAudioContext engine;
4343
/*********************************************************************************/
4444
extern "C" {
4545

46+
// --- Cached JNI IDs ---
47+
static jclass g_callbackStatusClass = nullptr;
48+
static jmethodID g_callbackStatusConstructor = nullptr;
49+
50+
static jclass g_arrayListClass = nullptr;
51+
static jmethodID g_arrayListConstructor = nullptr;
52+
static jmethodID g_arrayListAddMethod = nullptr;
53+
54+
static jclass g_playbackParametersClass = nullptr;
55+
static jmethodID g_playbackParametersConstructor = nullptr;
56+
static jfieldID g_fallbackModeField = nullptr;
57+
static jfieldID g_stretchModeField = nullptr;
58+
static jfieldID g_pitchField = nullptr;
59+
static jfieldID g_speedField = nullptr;
60+
4661
JNIEXPORT jint JNICALL
4762
Java_com_mobileer_oboetester_OboeAudioStream_openNative(JNIEnv *env, jobject,
4863
jint nativeApi,
@@ -254,6 +269,35 @@ Java_com_mobileer_oboetester_TestAudioActivity_setupMemoryBuffer(JNIEnv *env, jo
254269
engine.getCurrentActivity()->setupMemoryBuffer(buf, length);
255270
}
256271

272+
JNIEXPORT jint JNICALL
273+
Java_com_mobileer_oboetester_TestAudioActivity_setPlaybackParametersNative(
274+
JNIEnv *env, jobject, jobject playbackParameters) {
275+
oboe::PlaybackParameters params{};
276+
params.fallbackMode = static_cast<oboe::FallbackMode>(
277+
env->GetIntField(playbackParameters, g_fallbackModeField));
278+
params.stretchMode = static_cast<oboe::StretchMode>(
279+
env->GetIntField(playbackParameters, g_stretchModeField));
280+
params.pitch = static_cast<float>(env->GetFloatField(playbackParameters, g_pitchField));
281+
params.speed = static_cast<float>(env->GetFloatField(playbackParameters, g_speedField));
282+
283+
return static_cast<jint>(engine.getCurrentActivity()->setPlaybackParameters(params));
284+
}
285+
286+
JNIEXPORT jobject JNICALL
287+
Java_com_mobileer_oboetester_TestAudioActivity_getPlaybackParametersNative(
288+
JNIEnv *env, jobject) {
289+
oboe::ResultWithValue<oboe::PlaybackParameters> result =
290+
engine.getCurrentActivity()->getPlaybackParameters();
291+
if (!result) {
292+
return nullptr;
293+
}
294+
oboe::PlaybackParameters params = result.value();
295+
296+
return env->NewObject(g_playbackParametersClass, g_playbackParametersConstructor,
297+
(jint)params.fallbackMode,
298+
(jint)params.stretchMode, params.pitch, params.speed);
299+
}
300+
257301
JNIEXPORT jint JNICALL
258302
Java_com_mobileer_oboetester_OboeAudioStream_startPlaybackNative(JNIEnv *env, jobject) {
259303
return (jint) engine.getCurrentActivity()->startPlayback();
@@ -1185,14 +1229,6 @@ Java_com_mobileer_oboetester_AudioWorkloadTestActivity_close(JNIEnv *env, jobjec
11851229
return sAudioWorkload.close();
11861230
}
11871231

1188-
// --- Cached JNI IDs ---
1189-
static jclass g_callbackStatusClass = nullptr;
1190-
static jmethodID g_callbackStatusConstructor = nullptr;
1191-
1192-
static jclass g_arrayListClass = nullptr;
1193-
static jmethodID g_arrayListConstructor = nullptr;
1194-
static jmethodID g_arrayListAddMethod = nullptr;
1195-
11961232
// Store the JavaVM pointer to get JNIEnv in JNI_OnLoad/OnUnload
11971233
static JavaVM* g_javaVM = nullptr;
11981234

@@ -1256,6 +1292,33 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
12561292
return JNI_ERR;
12571293
}
12581294

1295+
const char* playbackParametersClassName = "com/mobileer/oboetester/PlaybackParameters";
1296+
jclass localPlaybackParametersClass = env->FindClass(playbackParametersClassName);
1297+
if (localPlaybackParametersClass == nullptr) {
1298+
LOGE("JNI_OnLoad: Could not find class %s", playbackParametersClassName);
1299+
if (env->ExceptionCheck()) env->ExceptionDescribe();
1300+
return JNI_ERR;
1301+
}
1302+
g_playbackParametersClass = (jclass)env->NewGlobalRef(localPlaybackParametersClass);
1303+
env->DeleteLocalRef(localPlaybackParametersClass);
1304+
if (g_playbackParametersClass == nullptr) {
1305+
LOGE("JNI_OnLoad: Could not create global ref for %s", playbackParametersClassName);
1306+
return JNI_ERR;
1307+
}
1308+
1309+
g_playbackParametersConstructor = env->GetMethodID(
1310+
g_playbackParametersClass, "<init>", "(IIFF)V");
1311+
if (g_playbackParametersConstructor == nullptr) {
1312+
LOGE("JNI_OnLoad: Could not find constructor for %s", playbackParametersClassName);
1313+
if (env->ExceptionCheck()) env->ExceptionDescribe();
1314+
return JNI_ERR;
1315+
}
1316+
1317+
g_fallbackModeField = env->GetFieldID(g_playbackParametersClass, "mFallbackMode", "I");
1318+
g_stretchModeField = env->GetFieldID(g_playbackParametersClass, "mStretchMode", "I");
1319+
g_pitchField = env->GetFieldID(g_playbackParametersClass, "mPitch", "F");
1320+
g_speedField = env->GetFieldID(g_playbackParametersClass, "mSpeed", "F");
1321+
12591322
std::cout << "JNI_OnLoad: Successfully cached JNI class and method IDs." << std::endl;
12601323
return JNI_VERSION_1_6;
12611324
}
@@ -1277,10 +1340,19 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
12771340
env->DeleteGlobalRef(g_arrayListClass);
12781341
g_arrayListClass = nullptr;
12791342
}
1343+
if (g_playbackParametersClass != nullptr) {
1344+
env->DeleteGlobalRef(g_playbackParametersClass);
1345+
g_playbackParametersClass = nullptr;
1346+
}
12801347

12811348
g_callbackStatusConstructor = nullptr;
12821349
g_arrayListConstructor = nullptr;
12831350
g_arrayListAddMethod = nullptr;
1351+
g_playbackParametersConstructor = nullptr;
1352+
g_fallbackModeField = nullptr;
1353+
g_stretchModeField = nullptr;
1354+
g_pitchField = nullptr;
1355+
g_speedField = nullptr;
12841356

12851357
g_javaVM = nullptr;
12861358
std::cout << "JNI_OnUnload: Released global JNI references." << std::endl;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.mobileer.oboetester;
17+
18+
import java.util.Locale;
19+
20+
public class PlaybackParameters {
21+
public static final int FALLBACK_MODE_DEFAULT = 0;
22+
public static final int FALLBACK_MODE_MUTE = 1;
23+
public static final int FALLBACK_MODE_FAIL = 2;
24+
25+
public static final int STRETCH_MODE_DEFAULT = 0;
26+
public static final int STRETCH_MODE_VOICE = 1;
27+
28+
public int mFallbackMode;
29+
public int mStretchMode;
30+
public float mPitch;
31+
public float mSpeed;
32+
33+
public PlaybackParameters(int fallbackMode, int stretchMode, float pitch, float speed) {
34+
mFallbackMode = fallbackMode;
35+
mStretchMode = stretchMode;
36+
mPitch = pitch;
37+
mSpeed = speed;
38+
}
39+
40+
public String getFallbackModeAsStr() {
41+
switch (mFallbackMode) {
42+
case FALLBACK_MODE_DEFAULT: return "Default";
43+
case FALLBACK_MODE_FAIL: return "Fail";
44+
case FALLBACK_MODE_MUTE: return "Mute";
45+
default: return "Unknown";
46+
}
47+
}
48+
49+
public String getStretchModeAsStr() {
50+
switch (mStretchMode) {
51+
case STRETCH_MODE_DEFAULT: return "Default";
52+
case STRETCH_MODE_VOICE: return "Voice";
53+
default: return "Unknown";
54+
}
55+
}
56+
57+
@Override
58+
public String toString() {
59+
return String.format(Locale.getDefault(),
60+
"Fallback: %s, Stretch: %s, Pitch: %.2f, Speed: %.2f",
61+
getFallbackModeAsStr(), getFallbackModeAsStr(), mPitch, mSpeed);
62+
}
63+
}

apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestAudioActivity.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,10 @@ public void enableForegroundService(boolean enabled) {
408408
}
409409
}
410410

411+
protected boolean isStreamClosed() {
412+
return mAudioState == AUDIO_STATE_CLOSED;
413+
}
414+
411415
protected void updateEnabledWidgets() {
412416
if (mOpenButton != null) {
413417
mOpenButton.setBackgroundColor(mAudioState == AUDIO_STATE_OPEN ? COLOR_ACTIVE : COLOR_IDLE);
@@ -789,6 +793,9 @@ private void openStreamContext(StreamContext streamContext) throws IOException {
789793

790794
private static native void setDefaultAudioValues(int audioManagerSampleRate, int audioManagerFramesPerBurst);
791795

796+
protected native int setPlaybackParametersNative(PlaybackParameters parameters);
797+
protected native PlaybackParameters getPlaybackParametersNative();
798+
792799
public void startAudio() throws IOException {
793800
Log.i(TAG, "startAudio() called =========================");
794801
int result = startNative();

apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestOutputActivity.java

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,33 +52,39 @@ public final class TestOutputActivity extends TestOutputActivityBase {
5252
private Spinner mFlushFromAccuracySpinner;
5353
private Button mFlushFromFrameButton;
5454

55+
private LinearLayout mPlaybackParametersLayout;
56+
private Spinner mFallbackModeSpinner;
57+
private TextView mFallbackModeTextView;
58+
private Spinner mStretchModeSpinner;
59+
private TextView mStretchModeTextView;
60+
private EditText mPitchEditText;
61+
private TextView mPitchTextView;
62+
private EditText mSpeedEditText;
63+
private TextView mSpeedTextView;
64+
private Button mSetPlaybackParametersButton;
65+
5566
private class OutputSignalSpinnerListener implements android.widget.AdapterView.OnItemSelectedListener {
5667
@Override
5768
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
5869
mAudioOutTester.setSignalType(pos);
5970
}
60-
6171
@Override
6272
public void onNothingSelected(AdapterView<?> parent) {
6373
mAudioOutTester.setSignalType(0);
6474
}
6575
}
66-
6776
private SeekBar.OnSeekBarChangeListener mVolumeChangeListener = new SeekBar.OnSeekBarChangeListener() {
6877
@Override
6978
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
7079
setVolume(progress);
7180
}
72-
7381
@Override
7482
public void onStartTrackingTouch(SeekBar seekBar) {
7583
}
76-
7784
@Override
7885
public void onStopTrackingTouch(SeekBar seekBar) {
7986
}
8087
};
81-
8288
@Override
8389
protected void inflateActivity() {
8490
setContentView(R.layout.activity_test_output);
@@ -134,6 +140,58 @@ public void onClick(View v) {
134140
flushFromFrame();
135141
}
136142
});
143+
144+
mPlaybackParametersLayout = (LinearLayout) findViewById(R.id.playbackParametersLayout);
145+
mPlaybackParametersLayout.setVisibility(View.GONE);
146+
mFallbackModeSpinner = (Spinner) findViewById(R.id.fallbackModeSpinner);
147+
mFallbackModeTextView = (TextView) findViewById(R.id.fallbackModeText);
148+
mStretchModeSpinner = (Spinner) findViewById(R.id.stretchModeSpinner);
149+
mStretchModeTextView = (TextView) findViewById(R.id.stretchModeText);
150+
mPitchEditText = (EditText) findViewById(R.id.pitchEditText);
151+
mPitchTextView = (TextView) findViewById(R.id.pitchText);
152+
mSpeedEditText = (EditText) findViewById(R.id.speedEditText);
153+
mSpeedTextView = (TextView) findViewById(R.id.speedText);
154+
mSetPlaybackParametersButton = (Button) findViewById(R.id.setPlaybackParametersButton);
155+
mSetPlaybackParametersButton.setOnClickListener(new View.OnClickListener() {
156+
@Override
157+
public void onClick(View v) {
158+
setPlaybackParameters();
159+
}
160+
});
161+
}
162+
163+
private void setPlaybackParameters() {
164+
try {
165+
int fallbackMode = mFallbackModeSpinner.getSelectedItemPosition();
166+
int stretchMode = mStretchModeSpinner.getSelectedItemPosition();
167+
float pitch = Float.parseFloat(mPitchEditText.getText().toString());
168+
float speed = Float.parseFloat(mSpeedEditText.getText().toString());
169+
PlaybackParameters params = new PlaybackParameters(fallbackMode, stretchMode, pitch, speed);
170+
int result = setPlaybackParametersNative(params);
171+
if (result == 0) {
172+
Toast.makeText(this, "Successfully set playback parameters",
173+
Toast.LENGTH_SHORT).show();
174+
updatePlaybackParametersText();
175+
} else {
176+
Toast.makeText(this, "Failed to set playback parameters, result: " + result,
177+
Toast.LENGTH_SHORT).show();
178+
}
179+
} catch (NumberFormatException e) {
180+
Log.e(TAG, "Failed to setPlaybackParameters, invalid pitch or speed");
181+
showErrorToast("Invalid pitch or speed");
182+
}
183+
}
184+
185+
private void updatePlaybackParametersText() {
186+
PlaybackParameters playbackParameters = getPlaybackParametersNative();
187+
if (playbackParameters == null) {
188+
Toast.makeText(this, "Failed to get playback parameters", Toast.LENGTH_SHORT).show();
189+
return;
190+
}
191+
mFallbackModeTextView.setText(playbackParameters.getFallbackModeAsStr());
192+
mStretchModeTextView.setText(playbackParameters.getStretchModeAsStr());
193+
mPitchTextView.setText(Float.toString(playbackParameters.mPitch));
194+
mSpeedTextView.setText(Float.toString(playbackParameters.mSpeed));
137195
}
138196

139197
@Override
@@ -146,6 +204,10 @@ public void openAudio() throws IOException {
146204
mShouldSetStreamControlByAttributes.setEnabled(false);
147205
mShouldDisableForCompressedFormat = StreamConfiguration.isCompressedFormat(
148206
mAudioOutTester.getCurrentAudioStream().getFormat());
207+
if (!isStreamClosed()) {
208+
mPlaybackParametersLayout.setVisibility(View.VISIBLE);
209+
updatePlaybackParametersText();
210+
}
149211
}
150212

151213
private void configureChannelBoxes(int channelCount, boolean shouldDisable) {
@@ -192,6 +254,9 @@ public void closeAudio() {
192254
mOutputSignalSpinner.setEnabled(true);
193255
mShouldSetStreamControlByAttributes.setEnabled(true);
194256
super.closeAudio();
257+
if (isStreamClosed()) {
258+
mPlaybackParametersLayout.setVisibility(View.GONE);
259+
}
195260
}
196261

197262
public void startAudio() throws IOException {

0 commit comments

Comments
 (0)