Skip to content

Commit

Permalink
[android] Refactor MinRequiredFramesTester
Browse files Browse the repository at this point in the history
b/327287075
  • Loading branch information
xiaomings committed Dec 27, 2024
1 parent f79e30f commit 70845b5
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 177 deletions.
130 changes: 107 additions & 23 deletions starboard/android/shared/audio_sink_min_required_frames_tester.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,18 @@
#include <vector>

#include "starboard/android/shared/audio_track_audio_sink_type.h"
#include "starboard/android/shared/media_capabilities_cache.h"
#include "starboard/shared/pthread/thread_create_priority.h"

namespace starboard {
namespace android {
namespace shared {

namespace {

const int kCheckpointFramesInterval = 1024;
const int kSampleFrequency22050 = 22050;
const int kSampleFrequency48000 = 48000;

// Helper function to compute the size of the two valid starboard audio sample
// types.
Expand All @@ -39,30 +43,83 @@ size_t GetSampleSize(SbMediaAudioSampleType sample_type) {
SB_NOTREACHED();
return 0u;
}

bool HasRemoteAudioOutput() {
// SbPlayerBridge::GetAudioConfigurations() reads up to 32 configurations. The
// limit here is to avoid infinite loop and also match
// SbPlayerBridge::GetAudioConfigurations().
const int kMaxAudioConfigurations = 32;
SbMediaAudioConfiguration configuration;
int index = 0;
while (index < kMaxAudioConfigurations &&
MediaCapabilitiesCache::GetInstance()->GetAudioConfiguration(
index, &configuration)) {
switch (configuration.connector) {
case kSbMediaAudioConnectorUnknown:
case kSbMediaAudioConnectorAnalog:
case kSbMediaAudioConnectorBuiltIn:
case kSbMediaAudioConnectorHdmi:
case kSbMediaAudioConnectorSpdif:
case kSbMediaAudioConnectorUsb:
break;
case kSbMediaAudioConnectorBluetooth:
case kSbMediaAudioConnectorRemoteWired:
case kSbMediaAudioConnectorRemoteWireless:
case kSbMediaAudioConnectorRemoteOther:
return true;
}
index++;
}
return false;
}

} // namespace

MinRequiredFramesTester::MinRequiredFramesTester(int max_required_frames,
int required_frames_increment,
int min_stable_played_frames)
: max_required_frames_(max_required_frames),
required_frames_increment_(required_frames_increment),
min_stable_played_frames_(min_stable_played_frames),
condition_variable_(mutex_),
destroying_(false) {}
MinRequiredFramesTester::MinRequiredFramesTester() {
Start();
}

MinRequiredFramesTester::~MinRequiredFramesTester() {
SB_DCHECK(thread_checker_.CalledOnValidThread());

destroying_.store(true);
if (tester_thread_ != 0) {
{
ScopedLock scoped_lock(mutex_);
ScopedLock scoped_lock(condition_variable_mutex_);
condition_variable_.Signal();
}
pthread_join(tester_thread_, NULL);
pthread_join(tester_thread_, nullptr);
tester_thread_ = 0;
}
}

int MinRequiredFramesTester::GetMinBufferSizeInFrames(
int channels,
SbMediaAudioSampleType sample_type,
int sampling_frequency_hz) {
bool has_remote_audio_output = HasRemoteAudioOutput();
ScopedLock lock(min_required_frames_map_mutex_);
if (has_remote_audio_output == has_remote_audio_output_) {
// There's no audio output type change, we can use the numbers we got from
// the tests at app launch.
if (sampling_frequency_hz <= kSampleFrequency22050) {
if (min_required_frames_map_.find(kSampleFrequency22050) !=
min_required_frames_map_.end()) {
return min_required_frames_map_[kSampleFrequency22050];
}
} else if (sampling_frequency_hz <= kSampleFrequency48000) {
if (min_required_frames_map_.find(kSampleFrequency48000) !=
min_required_frames_map_.end()) {
return min_required_frames_map_[kSampleFrequency48000];
}
}
}
// We cannot find a matched result from our tests, or the audio output type
// has changed. We use the default max required frames to avoid underruns.
return has_remote_audio_output ? kMaxRequiredFramesRemote
: kMaxRequiredFramesLocal;
}

void MinRequiredFramesTester::AddTest(
int number_of_channels,
SbMediaAudioSampleType sample_type,
Expand All @@ -82,6 +139,33 @@ void MinRequiredFramesTester::Start() {
// MinRequiredFramesTester only supports to start once.
SB_DCHECK(tester_thread_ == 0);

auto onMinRequiredFramesForWebAudioReceived =
[&](int number_of_channels, SbMediaAudioSampleType sample_type,
int sample_rate, int min_required_frames) {
bool has_remote_audio_output = HasRemoteAudioOutput();
SB_LOG(INFO) << "Received min required frames " << min_required_frames
<< " for " << number_of_channels << " channels, "
<< sample_rate << "hz, with "
<< (has_remote_audio_output ? "remote" : "local")
<< " audio output device.";
ScopedLock lock(min_required_frames_map_mutex_);
has_remote_audio_output_ = has_remote_audio_output;
min_required_frames_map_[sample_rate] =
std::min(min_required_frames, has_remote_audio_output_
? kMaxRequiredFramesRemote
: kMaxRequiredFramesLocal);
};

SbMediaAudioSampleType sample_type = kSbMediaAudioSampleTypeFloat32;
if (!SbAudioSinkIsAudioSampleTypeSupported(sample_type)) {
sample_type = kSbMediaAudioSampleTypeInt16Deprecated;
SB_DCHECK(SbAudioSinkIsAudioSampleTypeSupported(sample_type));
}
AddTest(2, sample_type, kSampleFrequency48000,
onMinRequiredFramesForWebAudioReceived, 8 * 1024);
AddTest(2, sample_type, kSampleFrequency22050,
onMinRequiredFramesForWebAudioReceived, 4 * 1024);

pthread_create(&tester_thread_, nullptr,
&MinRequiredFramesTester::TesterThreadEntryPoint, this);
SB_DCHECK(tester_thread_ != 0);
Expand All @@ -97,7 +181,7 @@ void* MinRequiredFramesTester::TesterThreadEntryPoint(void* context) {
MinRequiredFramesTester* tester =
static_cast<MinRequiredFramesTester*>(context);
tester->TesterThreadFunc();
return NULL;
return nullptr;
}

void MinRequiredFramesTester::TesterThreadFunc() {
Expand All @@ -108,7 +192,7 @@ void MinRequiredFramesTester::TesterThreadFunc() {
if (destroying_.load()) {
break;
}
std::vector<uint8_t> silence_buffer(max_required_frames_ *
std::vector<uint8_t> silence_buffer(kMaxRequiredFrames *
task.number_of_channels *
GetSampleSize(task.sample_type),
0);
Expand All @@ -123,15 +207,15 @@ void MinRequiredFramesTester::TesterThreadFunc() {
last_total_consumed_frames_ = 0;

audio_sink_ = new AudioTrackAudioSink(
NULL, task.number_of_channels, task.sample_rate, task.sample_type,
frame_buffers, max_required_frames_,
nullptr, task.number_of_channels, task.sample_rate, task.sample_type,
frame_buffers, kMaxRequiredFrames,
min_required_frames_ * task.number_of_channels *
GetSampleSize(task.sample_type),
&MinRequiredFramesTester::UpdateSourceStatusFunc,
&MinRequiredFramesTester::ConsumeFramesFunc,
&MinRequiredFramesTester::ErrorFunc, 0, -1, false, this);
{
ScopedLock scoped_lock(mutex_);
ScopedLock scoped_lock(condition_variable_mutex_);
wait_timeout = !condition_variable_.WaitTimed(5'000'000);
}

Expand All @@ -149,14 +233,14 @@ void MinRequiredFramesTester::TesterThreadFunc() {
if (wait_timeout) {
SB_LOG(ERROR) << "Audio sink min required frames tester timeout.";
// Overwrite |min_required_frames_| if failed to get a stable result.
min_required_frames_ = max_required_frames_;
min_required_frames_ = kMaxRequiredFrames;
}

if (has_error_) {
SB_LOG(ERROR) << "There's an error while running the test. Fallback to "
"max required frames "
<< max_required_frames_ << ".";
min_required_frames_ = max_required_frames_;
<< kMaxRequiredFrames << ".";
min_required_frames_ = kMaxRequiredFrames;
}

if (start_threshold > min_required_frames_) {
Expand Down Expand Up @@ -242,8 +326,8 @@ void MinRequiredFramesTester::ConsumeFrames(int frames_consumed) {
// we need to write more buffers into audio sink.
int underrun_count = audio_sink_->GetUnderrunCount();
if (underrun_count > last_underrun_count_) {
min_required_frames_ += required_frames_increment_;
if (min_required_frames_ >= max_required_frames_) {
min_required_frames_ += kRequiredFramesIncrement_;
if (min_required_frames_ >= kMaxRequiredFrames) {
SB_LOG(WARNING) << "Min required frames reached maximum.";
} else {
last_underrun_count_ = -1;
Expand All @@ -252,13 +336,13 @@ void MinRequiredFramesTester::ConsumeFrames(int frames_consumed) {
}
}

if (min_required_frames_ >= max_required_frames_ ||
total_consumed_frames_ - min_stable_played_frames_ >=
if (min_required_frames_ >= kMaxRequiredFrames ||
total_consumed_frames_ - kMinStablePlayedFrames_ >=
last_total_consumed_frames_) {
// |min_required_frames_| reached maximum, or playback is stable and
// doesn't have underruns. Stop the test.
last_total_consumed_frames_ = INT_MAX;
ScopedLock scoped_lock(mutex_);
ScopedLock scoped_lock(condition_variable_mutex_);
condition_variable_.Signal();
}
}
Expand Down
49 changes: 31 additions & 18 deletions starboard/android/shared/audio_sink_min_required_frames_tester.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include <atomic>
#include <functional>
#include <map>
#include <string>
#include <vector>

Expand All @@ -43,18 +44,16 @@ class MinRequiredFramesTester {
int min_required_frames)>
OnMinRequiredFramesReceivedCallback;

MinRequiredFramesTester(int max_required_frames,
int required_frames_increment,
int min_stable_played_frames);
~MinRequiredFramesTester();
MinRequiredFramesTester();

void AddTest(int number_of_channels,
SbMediaAudioSampleType sample_type,
int sample_rate,
const OnMinRequiredFramesReceivedCallback& received_cb,
int default_required_frames);
MinRequiredFramesTester(const MinRequiredFramesTester&) = delete;
MinRequiredFramesTester& operator=(const MinRequiredFramesTester&) = delete;

void Start();
~MinRequiredFramesTester();

int GetMinBufferSizeInFrames(int channels,
SbMediaAudioSampleType sample_type,
int sampling_frequency_hz);

private:
struct TestTask {
Expand All @@ -76,6 +75,14 @@ class MinRequiredFramesTester {
const int default_required_frames;
};

void AddTest(int number_of_channels,
SbMediaAudioSampleType sample_type,
int sample_rate,
const OnMinRequiredFramesReceivedCallback& received_cb,
int default_required_frames);

void Start();

static void* TesterThreadEntryPoint(void* context);
void TesterThreadFunc();

Expand All @@ -96,12 +103,12 @@ class MinRequiredFramesTester {
bool* is_eos_reached);
void ConsumeFrames(int frames_consumed);

MinRequiredFramesTester(const MinRequiredFramesTester&) = delete;
MinRequiredFramesTester& operator=(const MinRequiredFramesTester&) = delete;
static constexpr int kMaxRequiredFramesLocal = 16 * 1024;
static constexpr int kMaxRequiredFramesRemote = 32 * 1024;

const int max_required_frames_;
const int required_frames_increment_;
const int min_stable_played_frames_;
static constexpr int kMaxRequiredFrames = kMaxRequiredFramesRemote;
static constexpr int kRequiredFramesIncrement_ = 4 * 1024;
static constexpr int kMinStablePlayedFrames_ = 12 * 1024;

::starboard::shared::starboard::ThreadChecker thread_checker_;

Expand All @@ -115,10 +122,16 @@ class MinRequiredFramesTester {
int last_underrun_count_;
int last_total_consumed_frames_;

Mutex mutex_;
ConditionVariable condition_variable_;
Mutex condition_variable_mutex_; // Only used by `condition_variable_` below
ConditionVariable condition_variable_{condition_variable_mutex_};

pthread_t tester_thread_ = 0;
std::atomic_bool destroying_;
std::atomic_bool destroying_ = false;

Mutex min_required_frames_map_mutex_;
// The minimum frames required to avoid underruns of different frequencies.
std::map<int, int> min_required_frames_map_;
bool has_remote_audio_output_ = false;
};

} // namespace shared
Expand Down
Loading

0 comments on commit 70845b5

Please sign in to comment.