blob: e9a87a342c72b49603fb2b964933fca842cb8209 [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 <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 audio_sink_buffer_size,
int max_required_frames,
int default_required_frames,
int required_frames_increment,
int min_stable_played_frames)
: audio_sink_buffer_size_(audio_sink_buffer_size),
max_required_frames_(max_required_frames),
default_required_frames_(default_required_frames),
required_frames_increment_(required_frames_increment),
min_stable_played_frames_(min_stable_played_frames),
condition_variable_(mutex_),
destroyed_(false) {}
MinRequiredFramesTester::~MinRequiredFramesTester() {
SB_DCHECK(thread_checker_.CalledOnValidThread());
destroyed_.store(true);
if (SbThreadIsValid(tester_thread_)) {
{
ScopedLock scoped_lock(mutex_);
condition_variable_.Signal();
}
SbThreadJoin(tester_thread_, NULL);
tester_thread_ = kSbThreadInvalid;
}
}
void MinRequiredFramesTester::StartTest(
int number_of_channels,
SbMediaAudioSampleType sample_type,
int sample_rate,
OnMinRequiredFramesReceivedCallback received_cb) {
SB_DCHECK(thread_checker_.CalledOnValidThread());
// MinRequiredFramesTester only supports to do test once now.
SB_DCHECK(!SbThreadIsValid(tester_thread_));
number_of_channels_ = number_of_channels;
sample_type_ = sample_type;
sample_rate_ = sample_rate;
received_cb_ = received_cb;
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;
// Currently, we only support test once. But we can put following codes in
// a for loop easily to support test multiple times.
std::vector<uint8_t> silence_buffer(
max_required_frames_ * number_of_channels_ * GetSampleSize(sample_type_),
0);
void* frame_buffers[1];
frame_buffers[0] = silence_buffer.data();
// Set default values.
min_required_frames_ = default_required_frames_;
total_consumed_frames_ = 0;
last_underrun_count_ = -1;
last_total_consumed_frames_ = 0;
{
ScopedLock scoped_lock(mutex_);
// Need to check |destroyed_| before start, as MinRequiredFramesTester may
// be destroyed immediately after tester thread started.
if (!destroyed_.load()) {
audio_sink_ = new AudioTrackAudioSink(
NULL, number_of_channels_, sample_rate_, sample_type_, frame_buffers,
max_required_frames_,
audio_sink_buffer_size_ * number_of_channels_ *
GetSampleSize(sample_type_),
&MinRequiredFramesTester::UpdateSourceStatusFunc,
&MinRequiredFramesTester::ConsumeFramesFunc, this);
wait_timeout = !condition_variable_.WaitTimed(kSbTimeSecond * 5);
if (wait_timeout) {
SB_LOG(ERROR) << "Audio sink min required frames tester timeout.";
SB_NOTREACHED();
}
}
}
delete audio_sink_;
audio_sink_ = nullptr;
// Call |received_cb_| after audio sink thread is ended.
// |number_of_channels_|, |sample_type_|, |sample_rate_| and
// |min_required_frames_| are shared between two threads.
if (!destroyed_.load() && !wait_timeout) {
received_cb_(number_of_channels_, sample_type_, 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) {
SB_UNREFERENCED_PARAMETER(frames_consumed_at);
MinRequiredFramesTester* tester =
static_cast<MinRequiredFramesTester*>(context);
SB_DCHECK(tester);
tester->ConsumeFrames(frames_consumed);
}
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