Skip to content

Commit c867861

Browse files
[AN-765] Support for runtime Audio Usage Switching for HiFi Audio Playback (#63)
* Exposed method in JavaaudioDeviceModule to switch between Stereo and Mono playout * Removed call to native layer to get the bufbufferSizeFactor. Use default of bufferSizeFactor = 1.0 while switching b/w mono and stereo * Make setChannelConfigurationAndUsage thread safe by calling a thread safe method initPlayoutThreadSafe which does not have any thread checking logic * Make setChannelConfigurationAndUsage thread safe by calling a thread safe method initPlayoutThreadSafe which does not have any thread checking logic * If playout is currently active, it will be stopped and automatically restarted with the new configuration. * Remove unnecessary check in WebRtcAudioTrack which was preventing audioAttribute to be updated. * Refactored to change only the usage and contentType of AudioTrack Audio Attribute when it is changed from the app * Avoids unnecessary work when usage hasn't changed -no-op if usage is the same * Removed un-necessary null checks and improved comments
1 parent 9db66cf commit c867861

File tree

3 files changed

+118
-9
lines changed

3 files changed

+118
-9
lines changed

sdk/android/api/org/webrtc/audio/JavaAudioDeviceModule.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,23 @@ public void release() {
452452
}
453453
}
454454

455+
/**
456+
* Updates the audio usage of the AudioTrack.
457+
* This method updates the AudioAttributes to change audio behavior
458+
* (e.g., voice communication vs media playback).
459+
* If playout is currently active, it will be stopped and automatically restarted
460+
* with the new usage.
461+
*
462+
* @param usage Audio usage type (AudioAttributes.USAGE_*)
463+
* @return true if successful, false if failed
464+
*/
465+
public boolean updateAudioTrackUsage(int usage) {
466+
if (audioOutput != null) {
467+
return audioOutput.updateAudioTrackUsage(usage);
468+
}
469+
return false;
470+
}
471+
455472
@Override
456473
public void setSpeakerMute(boolean mute) {
457474
Logging.d(TAG, "setSpeakerMute: " + mute);

sdk/android/src/java/org/webrtc/audio/WebRtcAudioTrack.java

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ class WebRtcAudioTrack {
7575
private byte[] emptyBytes;
7676
private boolean useLowLatency;
7777
private int initialBufferSizeInFrames;
78+
79+
// Cached values from native initPlayout
80+
private int cachedSampleRate;
81+
private int cachedChannels;
82+
private double cachedBufferSizeFactor;
7883

7984
private final @Nullable AudioTrackErrorCallback errorCallback;
8085
private final @Nullable AudioTrackStateCallback stateCallback;
@@ -198,8 +203,16 @@ public void setNativeAudioTrack(long nativeAudioTrack) {
198203
}
199204

200205
@CalledByNative
201-
private int initPlayout(int sampleRate, int channels, double bufferSizeFactor) {
202-
threadChecker.checkIsOnValidThread();
206+
private int initPlayout(int sampleRate, int channels, double bufferSizeFactor, boolean checkThread) {
207+
if (checkThread) {
208+
threadChecker.checkIsOnValidThread();
209+
}
210+
211+
// Cache the values for reuse from app layer
212+
cachedSampleRate = sampleRate;
213+
cachedChannels = channels;
214+
cachedBufferSizeFactor = bufferSizeFactor;
215+
203216
Logging.d(TAG,
204217
"initPlayout(sampleRate=" + sampleRate + ", channels=" + channels
205218
+ ", bufferSizeFactor=" + bufferSizeFactor + ")");
@@ -285,8 +298,10 @@ private int initPlayout(int sampleRate, int channels, double bufferSizeFactor) {
285298
}
286299

287300
@CalledByNative
288-
private boolean startPlayout() {
289-
threadChecker.checkIsOnValidThread();
301+
private boolean startPlayout(boolean checkThread) {
302+
if (checkThread) {
303+
threadChecker.checkIsOnValidThread();
304+
}
290305
if (volumeLogger != null) {
291306
volumeLogger.start();
292307
}
@@ -319,8 +334,10 @@ private boolean startPlayout() {
319334
}
320335

321336
@CalledByNative
322-
private boolean stopPlayout() {
323-
threadChecker.checkIsOnValidThread();
337+
private boolean stopPlayout(boolean checkThread) {
338+
if (checkThread) {
339+
threadChecker.checkIsOnValidThread();
340+
}
324341
if (volumeLogger != null) {
325342
volumeLogger.stop();
326343
}
@@ -556,6 +573,81 @@ public void setSpeakerMute(boolean mute) {
556573
speakerMute = mute;
557574
}
558575

576+
// ============================================================================
577+
// PUBLIC API FOR APP LAYER
578+
// ============================================================================
579+
580+
/**
581+
* Updates the audio usage of the AudioTrack.
582+
* This method updates the AudioAttributes to change audio behavior (e.g., voice communication vs media playback).
583+
* If playout is currently active, it will be stopped and automatically restarted
584+
* with the new usage.
585+
*
586+
* This method can be called from any thread (app layer).
587+
*
588+
* @param usage Audio usage type (AudioAttributes.USAGE_*)
589+
* @return true if successful, false if failed
590+
*/
591+
public boolean updateAudioTrackUsage(int usage) {
592+
Logging.d(TAG, "updateAudioTrackUsage(usage=" + usage + ")");
593+
594+
// Check if the usage is already the same
595+
if (audioAttributes != null && audioAttributes.getUsage() == usage) {
596+
Logging.d(TAG, "Usage is already set to " + usage + ", no update needed");
597+
return true;
598+
}
599+
600+
// Check if playout was active before reconfiguration
601+
boolean wasPlaying = audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING;
602+
603+
// if AudioTrack is currently playing, stop it first
604+
if (wasPlaying) {
605+
Logging.d(TAG, "Stopping current playout for usage change");
606+
if (!stopPlayout(false)) {
607+
Logging.w(TAG, "Failed to stop playout before usage change");
608+
return false;
609+
}
610+
}
611+
612+
// Update audio attributes with new usage
613+
int contentType = AudioAttributes.CONTENT_TYPE_SPEECH;
614+
if (usage == AudioAttributes.USAGE_MEDIA) {
615+
contentType = AudioAttributes.CONTENT_TYPE_MUSIC;
616+
}
617+
audioAttributes = new AudioAttributes.Builder()
618+
.setUsage(usage)
619+
.setContentType(contentType)
620+
.build();
621+
622+
// Use cached values from native initPlayout
623+
int sampleRate = cachedSampleRate;
624+
int channels = cachedChannels;
625+
double bufferSizeFactor = cachedBufferSizeFactor;
626+
627+
Logging.d(TAG, "Using cached sampleRate=" + sampleRate + ", channels=" + channels + ", usage=" + usage);
628+
629+
// Reinitialize with cached parameters but new usage
630+
int result = initPlayout(sampleRate, channels, bufferSizeFactor, false);
631+
if (result < 0) {
632+
Logging.e(TAG, "Failed to reinitialize AudioTrack with new usage");
633+
return false;
634+
}
635+
636+
Logging.d(TAG, "Successfully updated usage to " + usage);
637+
638+
// If playout was active before reconfiguration, restart it automatically
639+
if (wasPlaying) {
640+
Logging.d(TAG, "Restarting playout with new usage");
641+
if (!startPlayout(false)) {
642+
Logging.e(TAG, "Failed to restart playout after usage change");
643+
return false;
644+
}
645+
}
646+
647+
return true;
648+
}
649+
650+
559651
// Releases the native AudioTrack resources.
560652
private void releaseAudioResources() {
561653
Logging.d(TAG, "releaseAudioResources");

sdk/android/src/jni/audio_device/audio_track_jni.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ int32_t AudioTrackJni::InitPlayout() {
9393
buffer_size_factor = 1.0;
9494
int requested_buffer_size_bytes = Java_WebRtcAudioTrack_initPlayout(
9595
env_, j_audio_track_, audio_parameters_.sample_rate(),
96-
static_cast<int>(audio_parameters_.channels()), buffer_size_factor);
96+
static_cast<int>(audio_parameters_.channels()), buffer_size_factor, true);
9797
if (requested_buffer_size_bytes < 0) {
9898
RTC_LOG(LS_ERROR) << "InitPlayout failed";
9999
return -1;
@@ -138,7 +138,7 @@ int32_t AudioTrackJni::StartPlayout() {
138138
<< "Playout can not start since InitPlayout must succeed first";
139139
return 0;
140140
}
141-
if (!Java_WebRtcAudioTrack_startPlayout(env_, j_audio_track_)) {
141+
if (!Java_WebRtcAudioTrack_startPlayout(env_, j_audio_track_, true)) {
142142
RTC_LOG(LS_ERROR) << "StartPlayout failed";
143143
return -1;
144144
}
@@ -164,7 +164,7 @@ int32_t AudioTrackJni::StopPlayout() {
164164
sample_rate_hz,
165165
-500, 100, 100);
166166

167-
if (!Java_WebRtcAudioTrack_stopPlayout(env_, j_audio_track_)) {
167+
if (!Java_WebRtcAudioTrack_stopPlayout(env_, j_audio_track_, true)) {
168168
RTC_LOG(LS_ERROR) << "StopPlayout failed";
169169
return -1;
170170
}

0 commit comments

Comments
 (0)