Skip to content

Commit 6b08824

Browse files
authored
android: Enable AudioTrack playback params in tunnel mode (youtube#9168)
This commit enables dynamic playback rate control for audio tracks on Android when operating in tunnel mode. It integrates the Android AudioTrack.setPlaybackParams() API to delegate playback speed adjustments to the platform. Previously, only playback rates of 0.0 (pause) and 1.0 (normal) were directly supported by the audio sink. Other rates were handled by software time stretching or explicitly not implemented at the platform level. By utilizing setPlaybackParams() for tunnel mode audio, Cobalt can leverage hardware acceleration or platform-optimized handling for variable speed playback, potentially improving performance and reducing CPU overhead. The change also ensures the audio buffer size is sufficiently increased to accommodate faster playback speeds. Bug: 311422213
1 parent 18cbe74 commit 6b08824

File tree

7 files changed

+89
-16
lines changed

7 files changed

+89
-16
lines changed

cobalt/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import android.media.AudioFormat;
2121
import android.media.AudioManager;
2222
import android.media.AudioTrack;
23+
import android.media.PlaybackParams;
2324
import android.os.Build;
2425
import androidx.annotation.GuardedBy;
2526
import androidx.annotation.RequiresApi;
@@ -227,11 +228,34 @@ public void release() {
227228
mAvSyncPacketBytesRemaining = 0;
228229
}
229230

231+
232+
@CalledByNative
233+
public boolean setPlaybackRate(float playbackRate) {
234+
if (mAudioTrack == null) {
235+
Log.e(TAG, "Unable to setPlaybackRate with NULL audio track.");
236+
return false;
237+
}
238+
if (!mTunnelModeEnabled) {
239+
Log.i(TAG, "Skip SetPlaybackRate for non tunnel mode tracks.");
240+
return true;
241+
}
242+
243+
try {
244+
PlaybackParams params = mAudioTrack.getPlaybackParams();
245+
params.setSpeed(playbackRate);
246+
mAudioTrack.setPlaybackParams(params);
247+
} catch (IllegalArgumentException | IllegalStateException e) {
248+
Log.e(TAG, String.format("Unable to setPlaybackRate, error: %s", e.toString()));
249+
return false;
250+
}
251+
return true;
252+
}
253+
230254
@CalledByNative
231255
public int setVolume(float gain) {
232256
if (mAudioTrack == null) {
233257
Log.e(TAG, "Unable to setVolume with NULL audio track.");
234-
return 0;
258+
return AudioTrack.ERROR_INVALID_OPERATION;
235259
}
236260
return mAudioTrack.setVolume(gain);
237261
}

starboard/android/shared/audio_track_audio_sink_type.cc

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,19 @@ AudioTrackAudioSink::~AudioTrackAudioSink() {
188188

189189
void AudioTrackAudioSink::SetPlaybackRate(double playback_rate) {
190190
SB_DCHECK_GE(playback_rate, 0.0);
191-
if (playback_rate != 0.0 && playback_rate != 1.0) {
192-
SB_NOTIMPLEMENTED() << "TODO: Only playback rates of 0.0 and 1.0 are "
193-
"currently supported.";
194-
playback_rate = (playback_rate > 0.0) ? 1.0 : 0.0;
191+
SB_DLOG(INFO) << "Set playback rate to " << playback_rate;
192+
193+
{
194+
std::lock_guard lock(mutex_);
195+
playback_rate_ = playback_rate;
196+
}
197+
198+
// AudioTrack doesn't support playback speed of 0.
199+
if (playback_rate > 0.0) {
200+
// AudioTrackBridge.setPlaybackRate() currently is only enabled for tunnel
201+
// mode. It will be no-op for non tunnel player.
202+
bridge_.SetPlaybackRate(playback_rate);
195203
}
196-
std::lock_guard lock(mutex_);
197-
playback_rate_ = playback_rate;
198204
}
199205

200206
// TODO: Break down the function into manageable pieces.
@@ -439,6 +445,21 @@ int AudioTrackAudioSinkType::GetMinBufferSizeInFrames(
439445
int sampling_frequency_hz) {
440446
SB_CHECK(audio_track_audio_sink_type_);
441447
JNIEnv* env = AttachCurrentThread();
448+
449+
const bool force_tunnel_mode =
450+
features::FeatureList::IsEnabled(features::kForceTunnelMode);
451+
if (force_tunnel_mode) {
452+
// AudioTrack.setPlaybackParams() needs extra buffer to support playback
453+
// speed greater than 1.0x.
454+
const double kMaxPlaybackSpeed = 2.0;
455+
return std::max<int>(
456+
AudioOutputManager::GetInstance()->GetMinBufferSizeInFrames(
457+
env, sample_type, channels, sampling_frequency_hz),
458+
audio_track_audio_sink_type_->GetMinBufferSizeInFramesInternal(
459+
channels, sample_type, sampling_frequency_hz) *
460+
kMaxPlaybackSpeed);
461+
}
462+
442463
return std::max(
443464
AudioOutputManager::GetInstance()->GetMinBufferSizeInFrames(
444465
env, sample_type, channels, sampling_frequency_hz),

starboard/android/shared/audio_track_bridge.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "starboard/android/shared/audio_output_manager.h"
2020
#include "starboard/android/shared/media_common.h"
2121
#include "starboard/audio_sink.h"
22+
#include "starboard/common/check_op.h"
2223
#include "starboard/common/log.h"
2324
#include "starboard/shared/starboard/media/media_util.h"
2425

@@ -241,6 +242,21 @@ int AudioTrackBridge::WriteSample(const uint8_t* samples,
241242
return bytes_written;
242243
}
243244

245+
void AudioTrackBridge::SetPlaybackRate(
246+
double playback_rate,
247+
JNIEnv* env /*= AttachCurrentThread()*/) {
248+
SB_DCHECK(env);
249+
SB_DCHECK(is_valid());
250+
// AudioTrack doesn't support playback speed of 0.
251+
SB_DCHECK_GT(playback_rate, 0.0);
252+
253+
jboolean status = Java_AudioTrackBridge_setPlaybackRate(
254+
env, j_audio_track_bridge_, static_cast<float>(playback_rate));
255+
if (!status) {
256+
SB_LOG(ERROR) << "Failed to set playback rate to " << playback_rate;
257+
}
258+
}
259+
244260
void AudioTrackBridge::SetVolume(double volume,
245261
JNIEnv* env /*= AttachCurrentThread()*/) {
246262
SB_DCHECK(env);

starboard/android/shared/audio_track_bridge.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ class AudioTrackBridge {
7171
int64_t sync_time,
7272
JNIEnv* env = base::android::AttachCurrentThread());
7373

74+
void SetPlaybackRate(double playback_rate,
75+
JNIEnv* env = base::android::AttachCurrentThread());
7476
void SetVolume(double volume,
7577
JNIEnv* env = base::android::AttachCurrentThread());
7678

starboard/android/shared/player_components_factory.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ class AudioRendererSinkAndroid : public AudioRendererSinkImpl {
9494
return tunnel_mode_audio_session_id_ != -1;
9595
}
9696

97+
bool AllowDirectPlaybackRateSetting() const override {
98+
return tunnel_mode_audio_session_id_ != -1;
99+
}
100+
97101
private:
98102
bool IsAudioSampleTypeSupported(
99103
SbMediaAudioSampleType audio_sample_type) const override {

starboard/shared/starboard/player/filter/audio_renderer_internal_pcm.cc

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,13 @@ void AudioRendererPcm::SetPlaybackRate(double playback_rate) {
214214

215215
playback_rate_ = playback_rate;
216216

217-
audio_renderer_sink_->SetPlaybackRate(playback_rate_ > 0.0 ? 1.0 : 0.0);
217+
double adjusted_playback_rate = playback_rate_ > 0.0 ? 1.0 : 0.0;
218+
if (audio_renderer_sink_->AllowDirectPlaybackRateSetting()) {
219+
adjusted_playback_rate = playback_rate_;
220+
}
221+
222+
audio_renderer_sink_->SetPlaybackRate(adjusted_playback_rate);
218223
if (audio_renderer_sink_->HasStarted()) {
219-
// TODO: Remove SetPlaybackRate() support from audio sink as it only need to
220-
// support play/pause.
221224
if (playback_rate_ > 0.0) {
222225
if (process_audio_data_job_token_.is_valid()) {
223226
RemoveJobByToken(process_audio_data_job_token_);
@@ -334,14 +337,12 @@ int64_t AudioRendererPcm::GetCurrentMediaTime(bool* is_playing,
334337
frames_played =
335338
audio_frame_tracker_.GetFutureFramesPlayedAdjustedToPlaybackRate(
336339
elapsed_frames, playback_rate);
337-
#if BUILDFLAG(IS_ANDROID)
338340
if (audio_renderer_sink_->AllowOverflowAudioSamples()) {
339341
// A simple workaround to handle silence frames for tunnel mode player.
340342
// |playback_rate| is ignored as tunnel mode doesn't support
341343
// vsp.
342344
frames_played += audio_frame_tracker_.GetOverflowedFrames();
343345
}
344-
#endif // BUILDFLAG(IS_ANDROID)
345346
media_time =
346347
seeking_to_time_ + frames_played * 1'000'000LL / samples_per_second;
347348
if (media_time < last_media_time_) {
@@ -490,15 +491,13 @@ void AudioRendererPcm::UpdateVariablesOnSinkThread_Locked(
490491
frames_consumed_set_at_ = system_time_on_consume_frames;
491492
}
492493

493-
#if BUILDFLAG(IS_ANDROID)
494494
if (audio_renderer_sink_->AllowOverflowAudioSamples()) {
495495
auto silence_frames_consumed =
496496
frames_consumed_on_sink_thread_ - non_silence_frames_consumed;
497497
frames_consumed_by_sink_since_last_get_current_time_ +=
498498
silence_frames_consumed;
499499
frames_consumed_set_at_ = system_time_on_consume_frames;
500500
}
501-
#endif // BUILDFLAG(IS_ANDROID)
502501

503502
consume_frames_called_ = true;
504503
frames_consumed_on_sink_thread_ = 0;
@@ -723,16 +722,22 @@ bool AudioRendererPcm::AppendAudioToFrameBuffer(bool* is_frame_buffer_full) {
723722

724723
int offset_to_append = total_frames_sent_to_sink_ % max_cached_frames_;
725724

725+
double adjusted_playback_rate = playback_rate_;
726+
if (audio_renderer_sink_->AllowDirectPlaybackRateSetting()) {
727+
adjusted_playback_rate = 1.0;
728+
}
729+
726730
scoped_refptr<DecodedAudio> decoded_audio = time_stretcher_.Read(
727-
max_cached_frames_ - frames_in_buffer, playback_rate_);
731+
max_cached_frames_ - frames_in_buffer, adjusted_playback_rate);
728732
SB_DCHECK(decoded_audio);
729733

730734
{
731735
std::lock_guard lock(mutex_);
732736
if (decoded_audio->frames() == 0 && eos_state_ == kEOSDecoded) {
733737
eos_state_ = kEOSSentToSink;
734738
}
735-
audio_frame_tracker_.AddFrames(decoded_audio->frames(), playback_rate_);
739+
audio_frame_tracker_.AddFrames(decoded_audio->frames(),
740+
adjusted_playback_rate);
736741
}
737742

738743
// |time_stretcher_| only support kSbMediaAudioSampleTypeFloat32 and

starboard/shared/starboard/player/filter/audio_renderer_sink.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class AudioRendererSink {
6868
virtual void SetPlaybackRate(double playback_rate) = 0;
6969

7070
virtual bool AllowOverflowAudioSamples() const { return false; }
71+
virtual bool AllowDirectPlaybackRateSetting() const { return false; }
7172
};
7273

7374
} // namespace starboard

0 commit comments

Comments
 (0)