blob: 92ca9e4271dd86d1533d5f4dc01443bf487b58a8 [file] [log] [blame]
// Copyright 2019 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/android/shared/audio_sink_min_required_frames_tester.h"
#include <string>
#include <vector>
#include "starboard/android/shared/audio_track_audio_sink_type.h"
namespace starboard {
namespace android {
namespace shared {
namespace {
const int kCheckpointFramesInterval = 1024;
// Helper function to compute the size of the two valid starboard audio sample
// types.
size_t GetSampleSize(SbMediaAudioSampleType sample_type) {
switch (sample_type) {
case kSbMediaAudioSampleTypeFloat32:
return sizeof(float);
case kSbMediaAudioSampleTypeInt16Deprecated:
return sizeof(int16_t);
}
SB_NOTREACHED();
return 0u;
}
} // 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() {
SB_DCHECK(thread_checker_.CalledOnValidThread());
destroying_.store(true);
if (SbThreadIsValid(tester_thread_)) {
{
ScopedLock scoped_lock(mutex_);
condition_variable_.Signal();
}
SbThreadJoin(tester_thread_, NULL);
tester_thread_ = kSbThreadInvalid;
}
}
void MinRequiredFramesTester::AddTest(
int number_of_channels,
SbMediaAudioSampleType sample_type,
int sample_rate,
const OnMinRequiredFramesReceivedCallback& received_cb,
int default_required_frames) {
SB_DCHECK(thread_checker_.CalledOnValidThread());
// MinRequiredFramesTester doesn't support to add test after starts.
SB_DCHECK(!SbThreadIsValid(tester_thread_));
test_tasks_.emplace_back(number_of_channels, sample_type, sample_rate,
received_cb, default_required_frames);
}
void MinRequiredFramesTester::Start() {
SB_DCHECK(thread_checker_.CalledOnValidThread());
// MinRequiredFramesTester only supports to start once.
SB_DCHECK(!SbThreadIsValid(tester_thread_));
tester_thread_ =
SbThreadCreate(0, kSbThreadPriorityLowest, kSbThreadNoAffinity, true,
"audio_track_tester",
&MinRequiredFramesTester::TesterThreadEntryPoint, this);
SB_DCHECK(SbThreadIsValid(tester_thread_));
}
// static
void* MinRequiredFramesTester::TesterThreadEntryPoint(void* context) {
SB_DCHECK(context);
MinRequiredFramesTester* tester =
static_cast<MinRequiredFramesTester*>(context);
tester->TesterThreadFunc();
return NULL;
}
void MinRequiredFramesTester::TesterThreadFunc() {
bool wait_timeout = false;
for (const TestTask& task : test_tasks_) {
// Need to check |destroying_| before start, as MinRequiredFramesTester may
// be destroyed immediately after tester thread started.
if (destroying_.load()) {
break;
}
std::vector<uint8_t> silence_buffer(max_required_frames_ *
task.number_of_channels *
GetSampleSize(task.sample_type),
0);
void* frame_buffers[1];
frame_buffers[0] = silence_buffer.data();
// Set default values.
min_required_frames_ = task.default_required_frames;
total_consumed_frames_ = 0;
last_underrun_count_ = -1;
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_,
min_required_frames_ * task.number_of_channels *
GetSampleSize(task.sample_type),
&MinRequiredFramesTester::UpdateSourceStatusFunc,
&MinRequiredFramesTester::ConsumeFramesFunc,
&MinRequiredFramesTester::ErrorFunc, 0, -1, false, false, false, this);
{
ScopedLock scoped_lock(mutex_);
wait_timeout = !condition_variable_.WaitTimed(kSbTimeSecond * 5);
}
// Get start threshold before release the audio sink.
int start_threshold = audio_sink_->GetStartThresholdInFrames();
// |min_required_frames_| is shared between two threads. Release audio sink
// to end audio sink thread before access |min_required_frames_| on this
// thread.
delete audio_sink_;
audio_sink_ = nullptr;
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_;
}
if (start_threshold > min_required_frames_) {
SB_LOG(INFO) << "Audio sink min required frames is overwritten from "
<< min_required_frames_ << " to audio track start threshold "
<< start_threshold << ".";
// Overwrite |min_required_frames_| to match |start_threshold|.
min_required_frames_ = start_threshold;
}
if (!destroying_.load()) {
task.received_cb(task.number_of_channels, task.sample_type,
task.sample_rate, min_required_frames_);
}
}
}
// static
void MinRequiredFramesTester::UpdateSourceStatusFunc(int* frames_in_buffer,
int* offset_in_frames,
bool* is_playing,
bool* is_eos_reached,
void* context) {
MinRequiredFramesTester* tester =
static_cast<MinRequiredFramesTester*>(context);
SB_DCHECK(tester);
SB_DCHECK(frames_in_buffer);
SB_DCHECK(offset_in_frames);
SB_DCHECK(is_playing);
SB_DCHECK(is_eos_reached);
tester->UpdateSourceStatus(frames_in_buffer, offset_in_frames, is_playing,
is_eos_reached);
}
// static
void MinRequiredFramesTester::ConsumeFramesFunc(int frames_consumed,
SbTime frames_consumed_at,
void* context) {
MinRequiredFramesTester* tester =
static_cast<MinRequiredFramesTester*>(context);
SB_DCHECK(tester);
tester->ConsumeFrames(frames_consumed);
}
// static
void MinRequiredFramesTester::ErrorFunc(bool capability_changed,
const std::string& error_message,
void* context) {
SB_LOG(ERROR) << "Error occurred while writing frames: " << error_message;
// TODO: Handle errors during minimum frames test, maybe by terminating the
// test earlier.
SB_NOTREACHED();
}
void MinRequiredFramesTester::UpdateSourceStatus(int* frames_in_buffer,
int* offset_in_frames,
bool* is_playing,
bool* is_eos_reached) {
*frames_in_buffer = min_required_frames_;
*offset_in_frames = 0;
*is_playing = true;
*is_eos_reached = false;
}
void MinRequiredFramesTester::ConsumeFrames(int frames_consumed) {
total_consumed_frames_ += frames_consumed;
// Wait until played enough frames.
if (total_consumed_frames_ - kCheckpointFramesInterval <
last_total_consumed_frames_) {
return;
}
if (last_underrun_count_ == -1) {
// |last_underrun_count_| is unknown, record the current underrun count
// and start to observe the underrun count.
last_underrun_count_ = audio_sink_->GetUnderrunCount();
last_total_consumed_frames_ = total_consumed_frames_;
return;
}
// The playback should be played for a while. If we still get new underruns,
// 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_) {
SB_LOG(WARNING) << "Min required frames reached maximum.";
} else {
last_underrun_count_ = -1;
last_total_consumed_frames_ = total_consumed_frames_;
return;
}
}
if (min_required_frames_ >= max_required_frames_ ||
total_consumed_frames_ - min_stable_played_frames_ >=
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_);
condition_variable_.Signal();
}
}
} // namespace shared
} // namespace android
} // namespace starboard