Skip to content
Merged
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
17 changes: 17 additions & 0 deletions sdk/android/api/org/webrtc/audio/JavaAudioDeviceModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,23 @@ public void release() {
}
}

/**
* Updates the audio usage of the AudioTrack.
* This method updates the AudioAttributes to change audio behavior
* (e.g., voice communication vs media playback).
* If playout is currently active, it will be stopped and automatically restarted
* with the new usage.
*
* @param usage Audio usage type (AudioAttributes.USAGE_*)
* @return true if successful, false if failed
*/
public boolean updateAudioTrackUsage(int usage) {
if (audioOutput != null) {
return audioOutput.updateAudioTrackUsage(usage);
}
return false;
}

@Override
public void setSpeakerMute(boolean mute) {
Logging.d(TAG, "setSpeakerMute: " + mute);
Expand Down
104 changes: 98 additions & 6 deletions sdk/android/src/java/org/webrtc/audio/WebRtcAudioTrack.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ class WebRtcAudioTrack {
private byte[] emptyBytes;
private boolean useLowLatency;
private int initialBufferSizeInFrames;

// Cached values from native initPlayout
private int cachedSampleRate;
private int cachedChannels;
private double cachedBufferSizeFactor;

private final @Nullable AudioTrackErrorCallback errorCallback;
private final @Nullable AudioTrackStateCallback stateCallback;
Expand Down Expand Up @@ -198,8 +203,16 @@ public void setNativeAudioTrack(long nativeAudioTrack) {
}

@CalledByNative
private int initPlayout(int sampleRate, int channels, double bufferSizeFactor) {
threadChecker.checkIsOnValidThread();
private int initPlayout(int sampleRate, int channels, double bufferSizeFactor, boolean checkThread) {
if (checkThread) {
threadChecker.checkIsOnValidThread();
}

// Cache the values for reuse from app layer
cachedSampleRate = sampleRate;
cachedChannels = channels;
cachedBufferSizeFactor = bufferSizeFactor;

Logging.d(TAG,
"initPlayout(sampleRate=" + sampleRate + ", channels=" + channels
+ ", bufferSizeFactor=" + bufferSizeFactor + ")");
Expand Down Expand Up @@ -285,8 +298,10 @@ private int initPlayout(int sampleRate, int channels, double bufferSizeFactor) {
}

@CalledByNative
private boolean startPlayout() {
threadChecker.checkIsOnValidThread();
private boolean startPlayout(boolean checkThread) {
if (checkThread) {
threadChecker.checkIsOnValidThread();
}
if (volumeLogger != null) {
volumeLogger.start();
}
Expand Down Expand Up @@ -319,8 +334,10 @@ private boolean startPlayout() {
}

@CalledByNative
private boolean stopPlayout() {
threadChecker.checkIsOnValidThread();
private boolean stopPlayout(boolean checkThread) {
if (checkThread) {
threadChecker.checkIsOnValidThread();
}
if (volumeLogger != null) {
volumeLogger.stop();
}
Expand Down Expand Up @@ -556,6 +573,81 @@ public void setSpeakerMute(boolean mute) {
speakerMute = mute;
}

// ============================================================================
// PUBLIC API FOR APP LAYER
// ============================================================================

/**
* Updates the audio usage of the AudioTrack.
* This method updates the AudioAttributes to change audio behavior (e.g., voice communication vs media playback).
* If playout is currently active, it will be stopped and automatically restarted
* with the new usage.
*
* This method can be called from any thread (app layer).
*
* @param usage Audio usage type (AudioAttributes.USAGE_*)
* @return true if successful, false if failed
*/
public boolean updateAudioTrackUsage(int usage) {
Logging.d(TAG, "updateAudioTrackUsage(usage=" + usage + ")");

// Check if the usage is already the same
if (audioAttributes != null && audioAttributes.getUsage() == usage) {
Logging.d(TAG, "Usage is already set to " + usage + ", no update needed");
return true;
}

// Check if playout was active before reconfiguration
boolean wasPlaying = audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING;

// if AudioTrack is currently playing, stop it first
if (wasPlaying) {
Logging.d(TAG, "Stopping current playout for usage change");
if (!stopPlayout(false)) {
Logging.w(TAG, "Failed to stop playout before usage change");
return false;
}
}

// Update audio attributes with new usage
int contentType = AudioAttributes.CONTENT_TYPE_SPEECH;
if (usage == AudioAttributes.USAGE_MEDIA) {
contentType = AudioAttributes.CONTENT_TYPE_MUSIC;
}
audioAttributes = new AudioAttributes.Builder()
.setUsage(usage)
.setContentType(contentType)
.build();

// Use cached values from native initPlayout
int sampleRate = cachedSampleRate;
int channels = cachedChannels;
double bufferSizeFactor = cachedBufferSizeFactor;

Logging.d(TAG, "Using cached sampleRate=" + sampleRate + ", channels=" + channels + ", usage=" + usage);

// Reinitialize with cached parameters but new usage
int result = initPlayout(sampleRate, channels, bufferSizeFactor, false);
if (result < 0) {
Logging.e(TAG, "Failed to reinitialize AudioTrack with new usage");
return false;
}

Logging.d(TAG, "Successfully updated usage to " + usage);

// If playout was active before reconfiguration, restart it automatically
if (wasPlaying) {
Logging.d(TAG, "Restarting playout with new usage");
if (!startPlayout(false)) {
Logging.e(TAG, "Failed to restart playout after usage change");
return false;
}
}

return true;
}


// Releases the native AudioTrack resources.
private void releaseAudioResources() {
Logging.d(TAG, "releaseAudioResources");
Expand Down
6 changes: 3 additions & 3 deletions sdk/android/src/jni/audio_device/audio_track_jni.cc
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ int32_t AudioTrackJni::InitPlayout() {
buffer_size_factor = 1.0;
int requested_buffer_size_bytes = Java_WebRtcAudioTrack_initPlayout(
env_, j_audio_track_, audio_parameters_.sample_rate(),
static_cast<int>(audio_parameters_.channels()), buffer_size_factor);
static_cast<int>(audio_parameters_.channels()), buffer_size_factor, true);
if (requested_buffer_size_bytes < 0) {
RTC_LOG(LS_ERROR) << "InitPlayout failed";
return -1;
Expand Down Expand Up @@ -138,7 +138,7 @@ int32_t AudioTrackJni::StartPlayout() {
<< "Playout can not start since InitPlayout must succeed first";
return 0;
}
if (!Java_WebRtcAudioTrack_startPlayout(env_, j_audio_track_)) {
if (!Java_WebRtcAudioTrack_startPlayout(env_, j_audio_track_, true)) {
RTC_LOG(LS_ERROR) << "StartPlayout failed";
return -1;
}
Expand All @@ -164,7 +164,7 @@ int32_t AudioTrackJni::StopPlayout() {
sample_rate_hz,
-500, 100, 100);

if (!Java_WebRtcAudioTrack_stopPlayout(env_, j_audio_track_)) {
if (!Java_WebRtcAudioTrack_stopPlayout(env_, j_audio_track_, true)) {
RTC_LOG(LS_ERROR) << "StopPlayout failed";
return -1;
}
Expand Down