blob: 8dc94caa396add1f11610746a298aac0f78c7b3b [file] [log] [blame]
// Copyright 2017 Google Inc. 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/shared/starboard/player/filter/audio_renderer_impl_internal.h"
#include <set>
#include "starboard/common/scoped_ptr.h"
#include "starboard/media.h"
#include "starboard/memory.h"
#include "starboard/shared/starboard/media/media_util.h"
#include "starboard/shared/starboard/player/filter/mock_audio_decoder.h"
#include "starboard/thread.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace starboard {
namespace shared {
namespace starboard {
namespace player {
namespace filter {
namespace testing {
namespace {
using ::testing::_;
using ::testing::DoAll;
using ::testing::Return;
using ::testing::SaveArg;
// TODO: Inject and test the renderer using SbAudioSink mock. Otherwise the
// tests rely on a correctly implemented audio sink and may fail on other audio
// sinks.
class AudioRendererImplTest : public ::testing::Test {
protected:
static const int kDefaultNumberOfChannels = 2;
static const int kDefaultSamplesPerSecond = 100000;
static const SbMediaAudioSampleType kDefaultAudioSampleType =
kSbMediaAudioSampleTypeFloat32;
static const SbMediaAudioFrameStorageType kDefaultAudioFrameStorageType =
kSbMediaAudioFrameStorageTypeInterleaved;
AudioRendererImplTest() {
ResetToFormat(kSbMediaAudioSampleTypeFloat32,
kSbMediaAudioFrameStorageTypeInterleaved);
}
// This function should be called in the fixture before any other functions
// to set the desired format of the decoder.
void ResetToFormat(SbMediaAudioSampleType sample_type,
SbMediaAudioFrameStorageType storage_type) {
audio_renderer_.reset(NULL);
sample_type_ = sample_type;
storage_type_ = storage_type;
audio_decoder_ = new MockAudioDecoder(sample_type_, storage_type_,
kDefaultSamplesPerSecond);
EXPECT_CALL(*audio_decoder_, Initialize(_))
.WillOnce(SaveArg<0>(&output_cb_));
audio_renderer_.reset(
new AudioRendererImpl(make_scoped_ptr<AudioDecoder>(audio_decoder_),
GetDefaultAudioHeader()));
}
void WriteSample(const scoped_refptr<InputBuffer>& input_buffer) {
ASSERT_TRUE(input_buffer != NULL);
ASSERT_FALSE(consumed_cb_.is_valid());
buffers_in_decoder_.insert(input_buffer->data());
EXPECT_CALL(*audio_decoder_, Decode(input_buffer, _))
.WillOnce(SaveArg<1>(&consumed_cb_));
audio_renderer_->WriteSample(input_buffer);
job_queue_.RunUntilIdle();
ASSERT_TRUE(consumed_cb_.is_valid());
}
void WriteEndOfStream() {
EXPECT_CALL(*audio_decoder_, WriteEndOfStream());
audio_renderer_->WriteEndOfStream();
job_queue_.RunUntilIdle();
}
void Seek(SbMediaTime seek_to_pts) {
audio_renderer_->Seek(seek_to_pts);
job_queue_.RunUntilIdle();
EXPECT_TRUE(audio_renderer_->IsSeekingInProgress());
}
void CallConsumedCB() {
ASSERT_TRUE(consumed_cb_.is_valid());
consumed_cb_.Run();
consumed_cb_.reset();
job_queue_.RunUntilIdle();
}
void SendDecoderOutput(const scoped_refptr<DecodedAudio>& decoded_audio) {
ASSERT_TRUE(output_cb_.is_valid());
EXPECT_CALL(*audio_decoder_, Read()).WillOnce(Return(decoded_audio));
output_cb_.Run();
job_queue_.RunUntilIdle();
}
scoped_refptr<InputBuffer> CreateInputBuffer(SbMediaTime pts) {
const int kInputBufferSize = 4;
return new InputBuffer(kSbMediaTypeAudio, DeallocateSampleCB, NULL, this,
SbMemoryAllocate(kInputBufferSize), kInputBufferSize,
pts, NULL, NULL);
}
scoped_refptr<DecodedAudio> CreateDecodedAudio(SbMediaTime pts, int frames) {
scoped_refptr<DecodedAudio> decoded_audio = new DecodedAudio(
kDefaultNumberOfChannels, sample_type_, storage_type_, pts,
frames * kDefaultNumberOfChannels *
media::GetBytesPerSample(sample_type_));
SbMemorySet(decoded_audio->buffer(), 0, decoded_audio->size());
return decoded_audio;
}
SbMediaAudioSampleType sample_type_;
SbMediaAudioFrameStorageType storage_type_;
JobQueue job_queue_;
std::set<const void*> buffers_in_decoder_;
Closure output_cb_;
Closure consumed_cb_;
scoped_ptr<AudioRenderer> audio_renderer_;
MockAudioDecoder* audio_decoder_;
private:
void OnDeallocateSample(const void* sample_buffer) {
ASSERT_TRUE(buffers_in_decoder_.find(sample_buffer) !=
buffers_in_decoder_.end());
buffers_in_decoder_.erase(buffers_in_decoder_.find(sample_buffer));
SbMemoryDeallocate(const_cast<void*>(sample_buffer));
}
static SbMediaAudioHeader GetDefaultAudioHeader() {
SbMediaAudioHeader audio_header = {};
audio_header.number_of_channels = kDefaultNumberOfChannels;
audio_header.samples_per_second = kDefaultSamplesPerSecond;
audio_header.bits_per_sample = 32;
audio_header.average_bytes_per_second = audio_header.samples_per_second *
audio_header.number_of_channels *
audio_header.bits_per_sample / 8;
audio_header.block_alignment = 4;
audio_header.audio_specific_config_size = 0;
return audio_header;
}
static void DeallocateSampleCB(SbPlayer player,
void* context,
const void* sample_buffer) {
AudioRendererImplTest* test = static_cast<AudioRendererImplTest*>(context);
EXPECT_TRUE(test != NULL);
test->OnDeallocateSample(sample_buffer);
}
};
TEST_F(AudioRendererImplTest, StateAfterConstructed) {
EXPECT_FALSE(audio_renderer_->IsEndOfStreamWritten());
EXPECT_FALSE(audio_renderer_->IsEndOfStreamPlayed());
EXPECT_TRUE(audio_renderer_->CanAcceptMoreData());
EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0);
}
TEST_F(AudioRendererImplTest, SunnyDay) {
Seek(0);
const int kFramesPerBuffer = 1024;
int frames_written = 0;
while (audio_renderer_->IsSeekingInProgress()) {
SbMediaTime pts =
frames_written * kSbMediaTimeSecond / kDefaultSamplesPerSecond;
WriteSample(CreateInputBuffer(pts));
CallConsumedCB();
SendDecoderOutput(CreateDecodedAudio(pts, kFramesPerBuffer));
frames_written += kFramesPerBuffer;
}
WriteEndOfStream();
EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0);
EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
audio_renderer_->Play();
SendDecoderOutput(new DecodedAudio);
SbMediaTime media_time = audio_renderer_->GetCurrentTime();
while (!audio_renderer_->IsEndOfStreamPlayed()) {
SbThreadSleep(kSbTimeMillisecond);
SbMediaTime new_media_time = audio_renderer_->GetCurrentTime();
EXPECT_GE(new_media_time, media_time);
media_time = new_media_time;
}
}
TEST_F(AudioRendererImplTest, SunnyDayWithDoublePlaybackRateAndInt16Samples) {
const int kPlaybackRate = 2;
ResetToFormat(kSbMediaAudioSampleTypeInt16,
kSbMediaAudioFrameStorageTypeInterleaved);
audio_renderer_->SetPlaybackRate(static_cast<float>(kPlaybackRate));
Seek(0);
const int kFramesPerBuffer = 1024;
int frames_written = 0;
while (audio_renderer_->IsSeekingInProgress()) {
SbMediaTime pts =
frames_written * kSbMediaTimeSecond / kDefaultSamplesPerSecond;
WriteSample(CreateInputBuffer(pts));
CallConsumedCB();
SendDecoderOutput(CreateDecodedAudio(pts, kFramesPerBuffer));
frames_written += kFramesPerBuffer;
}
WriteEndOfStream();
EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0);
EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
audio_renderer_->Play();
SendDecoderOutput(new DecodedAudio);
SbMediaTime media_time = audio_renderer_->GetCurrentTime();
while (!audio_renderer_->IsEndOfStreamPlayed()) {
SbThreadSleep(kSbTimeMillisecond);
SbMediaTime new_media_time = audio_renderer_->GetCurrentTime();
EXPECT_GE(new_media_time, media_time);
media_time = new_media_time;
}
}
TEST_F(AudioRendererImplTest, StartPlayBeforePreroll) {
Seek(0);
const int kFramesPerBuffer = 1024;
audio_renderer_->Play();
int frames_written = 0;
while (audio_renderer_->IsSeekingInProgress()) {
SbMediaTime pts =
frames_written * kSbMediaTimeSecond / kDefaultSamplesPerSecond;
WriteSample(CreateInputBuffer(pts));
CallConsumedCB();
SendDecoderOutput(CreateDecodedAudio(pts, kFramesPerBuffer));
frames_written += kFramesPerBuffer;
}
WriteEndOfStream();
SendDecoderOutput(new DecodedAudio);
SbMediaTime media_time = audio_renderer_->GetCurrentTime();
while (!audio_renderer_->IsEndOfStreamPlayed()) {
SbThreadSleep(kSbTimeMillisecond);
SbMediaTime new_media_time = audio_renderer_->GetCurrentTime();
EXPECT_GE(new_media_time, media_time);
media_time = new_media_time;
}
}
TEST_F(AudioRendererImplTest, DecoderReturnsEOSWithoutAnyData) {
Seek(0);
WriteSample(CreateInputBuffer(0));
CallConsumedCB();
WriteEndOfStream();
EXPECT_TRUE(audio_renderer_->IsEndOfStreamWritten());
EXPECT_FALSE(audio_renderer_->CanAcceptMoreData());
EXPECT_TRUE(audio_renderer_->IsSeekingInProgress());
// Return EOS from decoder without sending any audio data, which is valid.
SendDecoderOutput(new DecodedAudio);
EXPECT_TRUE(audio_renderer_->IsEndOfStreamPlayed());
EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0);
}
// Test decoders that take many input samples before returning any output.
TEST_F(AudioRendererImplTest, DecoderConsumeAllInputBeforeReturningData) {
Seek(0);
for (int i = 0; i < 128; ++i) {
WriteSample(CreateInputBuffer(i));
CallConsumedCB();
if (!audio_renderer_->CanAcceptMoreData()) {
break;
}
}
// Send an EOS to "force" the decoder return data.
WriteEndOfStream();
EXPECT_TRUE(audio_renderer_->IsEndOfStreamWritten());
EXPECT_FALSE(audio_renderer_->CanAcceptMoreData());
EXPECT_TRUE(audio_renderer_->IsSeekingInProgress());
// Return EOS from decoder without sending any audio data, which is valid.
SendDecoderOutput(new DecodedAudio);
EXPECT_TRUE(audio_renderer_->IsEndOfStreamPlayed());
EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0);
}
TEST_F(AudioRendererImplTest, MoreNumberOfOuputBuffersThanInputBuffers) {
Seek(0);
const int kFramesPerBuffer = 1024;
int frames_written = 0;
while (audio_renderer_->IsSeekingInProgress()) {
SbMediaTime pts =
frames_written * kSbMediaTimeSecond / kDefaultSamplesPerSecond;
WriteSample(CreateInputBuffer(pts));
CallConsumedCB();
SendDecoderOutput(CreateDecodedAudio(pts, kFramesPerBuffer / 2));
frames_written += kFramesPerBuffer / 2;
pts = frames_written * kSbMediaTimeSecond / kDefaultSamplesPerSecond;
SendDecoderOutput(CreateDecodedAudio(pts, kFramesPerBuffer / 2));
frames_written += kFramesPerBuffer / 2;
}
WriteEndOfStream();
EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0);
EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
audio_renderer_->Play();
SendDecoderOutput(new DecodedAudio);
SbMediaTime media_time = audio_renderer_->GetCurrentTime();
while (!audio_renderer_->IsEndOfStreamPlayed()) {
SbThreadSleep(kSbTimeMillisecond);
SbMediaTime new_media_time = audio_renderer_->GetCurrentTime();
EXPECT_GE(new_media_time, media_time);
media_time = new_media_time;
}
}
TEST_F(AudioRendererImplTest, LessNumberOfOuputBuffersThanInputBuffers) {
Seek(0);
const int kFramesPerBuffer = 1024;
int frames_written = 0;
while (audio_renderer_->IsSeekingInProgress()) {
SbMediaTime pts =
frames_written * kSbMediaTimeSecond / kDefaultSamplesPerSecond;
SbMediaTime output_pts = pts;
WriteSample(CreateInputBuffer(pts));
CallConsumedCB();
frames_written += kFramesPerBuffer / 2;
pts = frames_written * kSbMediaTimeSecond / kDefaultSamplesPerSecond;
WriteSample(CreateInputBuffer(pts));
CallConsumedCB();
frames_written += kFramesPerBuffer / 2;
SendDecoderOutput(CreateDecodedAudio(output_pts, kFramesPerBuffer));
}
WriteEndOfStream();
EXPECT_EQ(audio_renderer_->GetCurrentTime(), 0);
EXPECT_FALSE(audio_renderer_->IsSeekingInProgress());
audio_renderer_->Play();
SendDecoderOutput(new DecodedAudio);
SbMediaTime media_time = audio_renderer_->GetCurrentTime();
while (!audio_renderer_->IsEndOfStreamPlayed()) {
SbThreadSleep(kSbTimeMillisecond);
SbMediaTime new_media_time = audio_renderer_->GetCurrentTime();
EXPECT_GE(new_media_time, media_time);
media_time = new_media_time;
}
}
// TODO: Implement test for Seek()
TEST_F(AudioRendererImplTest, Seek) {}
} // namespace
} // namespace testing
} // namespace filter
} // namespace player
} // namespace starboard
} // namespace shared
} // namespace starboard