blob: 66f778e862d34bf5cd2baa55fae4f82744dd5531 [file] [log] [blame]
// Copyright 2018 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_decoder_internal.h"
#include <deque>
#include <functional>
#include "starboard/common/scoped_ptr.h"
#include "starboard/condition_variable.h"
#include "starboard/media.h"
#include "starboard/memory.h"
#include "starboard/mutex.h"
#include "starboard/shared/starboard/media/media_support_internal.h"
#include "starboard/shared/starboard/media/media_util.h"
#include "starboard/shared/starboard/player/filter/player_components.h"
#include "starboard/shared/starboard/player/video_dmp_reader.h"
#include "starboard/thread.h"
#include "testing/gtest/include/gtest/gtest.h"
#if SB_HAS(PLAYER_FILTER_TESTS)
// TODO: Write test for HE-AAC
namespace starboard {
namespace shared {
namespace starboard {
namespace player {
namespace filter {
namespace testing {
namespace {
using ::testing::ValuesIn;
using video_dmp::VideoDmpReader;
const SbTimeMonotonic kWaitForNextEventTimeOut = 5 * kSbTimeSecond;
std::string GetTestInputDirectory() {
const size_t kPathSize = SB_FILE_MAX_PATH + 1;
char content_path[kPathSize];
SB_CHECK(SbSystemGetPath(kSbSystemPathContentDirectory, content_path,
kPathSize));
std::string directory_path =
std::string(content_path) + SB_FILE_SEP_CHAR + "test" +
SB_FILE_SEP_CHAR + "starboard" + SB_FILE_SEP_CHAR + "shared" +
SB_FILE_SEP_CHAR + "starboard" + SB_FILE_SEP_CHAR + "player" +
SB_FILE_SEP_CHAR + "testdata";
SB_CHECK(SbDirectoryCanOpen(directory_path.c_str()))
<< "Cannot open directory " << directory_path;
return directory_path;
}
std::string ResolveTestFileName(const char* filename) {
return GetTestInputDirectory() + SB_FILE_SEP_CHAR + filename;
}
class AudioDecoderTest : public ::testing::TestWithParam<const char*> {
public:
AudioDecoderTest() : dmp_reader_(ResolveTestFileName(GetParam()).c_str()) {}
void SetUp() override {
ASSERT_NE(dmp_reader_.audio_codec(), kSbMediaAudioCodecNone);
ASSERT_GT(dmp_reader_.number_of_audio_buffers(), 0);
PlayerComponents::AudioParameters audio_parameters = {
dmp_reader_.audio_codec(), dmp_reader_.audio_header(),
kSbDrmSystemInvalid};
scoped_ptr<PlayerComponents> components = PlayerComponents::Create();
components->CreateAudioComponents(audio_parameters, &audio_decoder_,
&audio_renderer_sink_);
ASSERT_TRUE(audio_decoder_);
audio_decoder_->Initialize(std::bind(&AudioDecoderTest::OnOutput, this),
std::bind(&AudioDecoderTest::OnError, this));
}
void OnOutput() {
ScopedLock scoped_lock(event_queue_mutex_);
event_queue_.push_back(kOutput);
}
void OnError() {
ScopedLock scoped_lock(event_queue_mutex_);
event_queue_.push_back(kError);
}
protected:
enum Event { kConsumed, kOutput, kError };
void OnConsumed() {
ScopedLock scoped_lock(event_queue_mutex_);
event_queue_.push_back(kConsumed);
}
void WaitForNextEvent(Event* event) {
SbTimeMonotonic start = SbTimeGetMonotonicNow();
while (SbTimeGetMonotonicNow() - start < kWaitForNextEventTimeOut) {
job_queue_.RunUntilIdle();
{
ScopedLock scoped_lock(event_queue_mutex_);
if (!event_queue_.empty()) {
*event = event_queue_.front();
event_queue_.pop_front();
if (*event == kConsumed) {
ASSERT_FALSE(can_accept_more_input_);
can_accept_more_input_ = true;
}
return;
}
}
SbThreadSleep(kSbTimeMillisecond);
}
*event = kError;
FAIL();
}
// TODO: Add test to ensure that |consumed_cb| is not reused by the decoder.
AudioDecoder::ConsumedCB consumed_cb() {
return std::bind(&AudioDecoderTest::OnConsumed, this);
}
// This has to be called when the decoder is just initialized/reseted or when
// OnConsumed() is called.
void WriteSingleInput(size_t index) {
ASSERT_TRUE(can_accept_more_input_);
ASSERT_LT(index, dmp_reader_.number_of_audio_buffers());
can_accept_more_input_ = false;
last_input_buffer_ = dmp_reader_.GetAudioInputBuffer(index);
audio_decoder_->Decode(last_input_buffer_, consumed_cb());
}
// This has to be called when OnOutput() is called.
void ReadFromDecoder(scoped_refptr<DecodedAudio>* decoded_audio) {
ASSERT_TRUE(decoded_audio);
scoped_refptr<DecodedAudio> local_decoded_audio = audio_decoder_->Read();
ASSERT_TRUE(local_decoded_audio);
if (local_decoded_audio->is_end_of_stream()) {
*decoded_audio = local_decoded_audio;
return;
}
if (last_decoded_audio_) {
ASSERT_LT(last_decoded_audio_->timestamp(),
local_decoded_audio->timestamp());
}
last_decoded_audio_ = local_decoded_audio;
*decoded_audio = local_decoded_audio;
}
void WriteMultipleInputs(size_t start_index,
size_t number_of_inputs_to_write) {
ASSERT_LE(start_index + number_of_inputs_to_write,
dmp_reader_.number_of_audio_buffers());
ASSERT_NO_FATAL_FAILURE(WriteSingleInput(start_index));
++start_index;
--number_of_inputs_to_write;
while (number_of_inputs_to_write > 0) {
Event event = kError;
ASSERT_NO_FATAL_FAILURE(WaitForNextEvent(&event));
if (event == kConsumed) {
ASSERT_NO_FATAL_FAILURE(WriteSingleInput(start_index));
++start_index;
--number_of_inputs_to_write;
continue;
}
ASSERT_EQ(kOutput, event);
scoped_refptr<DecodedAudio> decoded_audio;
ASSERT_NO_FATAL_FAILURE(ReadFromDecoder(&decoded_audio));
ASSERT_TRUE(decoded_audio);
ASSERT_FALSE(decoded_audio->is_end_of_stream());
}
}
void DrainOutputs(bool* error_occurred = NULL) {
if (error_occurred) {
*error_occurred = false;
}
for (;;) {
Event event = kError;
ASSERT_NO_FATAL_FAILURE(WaitForNextEvent(&event));
if (event == kError) {
if (error_occurred) {
*error_occurred = true;
} else {
FAIL();
}
return;
}
if (event == kConsumed) {
continue;
}
ASSERT_EQ(kOutput, event);
scoped_refptr<DecodedAudio> decoded_audio;
ASSERT_NO_FATAL_FAILURE(ReadFromDecoder(&decoded_audio));
ASSERT_TRUE(decoded_audio);
if (decoded_audio->is_end_of_stream()) {
break;
}
}
}
void ResetDecoder() {
audio_decoder_->Reset();
can_accept_more_input_ = true;
last_input_buffer_ = NULL;
last_decoded_audio_ = NULL;
}
Mutex event_queue_mutex_;
std::deque<Event> event_queue_;
JobQueue job_queue_;
VideoDmpReader dmp_reader_;
scoped_ptr<AudioDecoder> audio_decoder_;
scoped_ptr<AudioRendererSink> audio_renderer_sink_;
bool can_accept_more_input_ = true;
scoped_refptr<InputBuffer> last_input_buffer_;
scoped_refptr<DecodedAudio> last_decoded_audio_;
};
TEST_P(AudioDecoderTest, ThreeMoreDecoders) {
const int kDecodersToCreate = 3;
PlayerComponents::AudioParameters audio_parameters = {
dmp_reader_.audio_codec(), dmp_reader_.audio_header(),
kSbDrmSystemInvalid};
scoped_ptr<PlayerComponents> components = PlayerComponents::Create();
scoped_ptr<AudioDecoder> audio_decoders[kDecodersToCreate];
scoped_ptr<AudioRendererSink> audio_renderer_sinks[kDecodersToCreate];
for (int i = 0; i < kDecodersToCreate; ++i) {
components->CreateAudioComponents(audio_parameters, &audio_decoders[i],
&audio_renderer_sinks[i]);
ASSERT_TRUE(audio_decoders[i]);
audio_decoders[i]->Initialize(std::bind(&AudioDecoderTest::OnOutput, this),
std::bind(&AudioDecoderTest::OnError, this));
}
}
TEST_P(AudioDecoderTest, SingleInput) {
ASSERT_NO_FATAL_FAILURE(WriteSingleInput(0));
audio_decoder_->WriteEndOfStream();
ASSERT_NO_FATAL_FAILURE(DrainOutputs());
ASSERT_TRUE(last_decoded_audio_);
}
TEST_P(AudioDecoderTest, SingleInvalidInput) {
can_accept_more_input_ = false;
last_input_buffer_ = dmp_reader_.GetAudioInputBuffer(0);
std::vector<uint8_t> content(last_input_buffer_->size(), 0xab);
// Replace the content with invalid data.
last_input_buffer_->SetDecryptedContent(content.data(),
static_cast<int>(content.size()));
audio_decoder_->Decode(last_input_buffer_, consumed_cb());
audio_decoder_->WriteEndOfStream();
bool error_occurred = false;
ASSERT_NO_FATAL_FAILURE(DrainOutputs(&error_occurred));
// The decoder is allowed to either signal an error or return dummy audio
// data but not both.
if (error_occurred) {
ASSERT_FALSE(last_decoded_audio_);
} else {
ASSERT_TRUE(last_decoded_audio_);
}
}
TEST_P(AudioDecoderTest, EndOfStreamWithoutAnyInput) {
audio_decoder_->WriteEndOfStream();
ASSERT_NO_FATAL_FAILURE(DrainOutputs());
ASSERT_FALSE(last_decoded_audio_);
}
TEST_P(AudioDecoderTest, ResetBeforeInput) {
ResetDecoder();
ASSERT_NO_FATAL_FAILURE(WriteSingleInput(0));
audio_decoder_->WriteEndOfStream();
ASSERT_NO_FATAL_FAILURE(DrainOutputs());
ASSERT_TRUE(last_decoded_audio_);
}
TEST_P(AudioDecoderTest, MultipleInputs) {
const size_t kMaxNumberOfInputsToWrite = 5;
const size_t number_of_inputs_to_write = std::min(
kMaxNumberOfInputsToWrite, dmp_reader_.number_of_audio_buffers());
ASSERT_NO_FATAL_FAILURE(WriteMultipleInputs(0, number_of_inputs_to_write));
audio_decoder_->WriteEndOfStream();
ASSERT_NO_FATAL_FAILURE(DrainOutputs());
ASSERT_TRUE(last_decoded_audio_);
}
std::vector<const char*> GetSupportedTests() {
const char* kFilenames[] = {"beneath_the_canopy_avc_aac.dmp",
"beneath_the_canopy_vp9_opus.dmp"};
static std::vector<const char*> test_params;
if (!test_params.empty()) {
return test_params;
}
for (auto filename : kFilenames) {
VideoDmpReader dmp_reader(ResolveTestFileName(filename).c_str());
SB_DCHECK(dmp_reader.number_of_audio_buffers() > 0);
if (SbMediaIsAudioSupported(dmp_reader.audio_codec(),
dmp_reader.audio_bitrate())) {
test_params.push_back(filename);
}
}
SB_DCHECK(!test_params.empty());
return test_params;
}
INSTANTIATE_TEST_CASE_P(AudioDecoderTests,
AudioDecoderTest,
ValuesIn(GetSupportedTests()));
} // namespace
} // namespace testing
} // namespace filter
} // namespace player
} // namespace starboard
} // namespace shared
} // namespace starboard
#endif // SB_HAS(PLAYER_FILTER_TESTS)