| // Copyright 2018 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/shared/starboard/player/filter/audio_decoder_internal.h" |
| |
| #include <algorithm> |
| #include <deque> |
| #include <functional> |
| #include <map> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "starboard/common/condition_variable.h" |
| #include "starboard/common/media.h" |
| #include "starboard/common/mutex.h" |
| #include "starboard/common/ref_counted.h" |
| #include "starboard/common/scoped_ptr.h" |
| #include "starboard/configuration_constants.h" |
| #include "starboard/media.h" |
| #include "starboard/memory.h" |
| #include "starboard/shared/starboard/media/media_support_internal.h" |
| #include "starboard/shared/starboard/media/media_util.h" |
| #include "starboard/shared/starboard/player/decoded_audio_internal.h" |
| #include "starboard/shared/starboard/player/filter/player_components.h" |
| #include "starboard/shared/starboard/player/filter/stub_player_components_factory.h" |
| #include "starboard/shared/starboard/player/filter/testing/test_util.h" |
| #include "starboard/shared/starboard/player/input_buffer_internal.h" |
| #include "starboard/shared/starboard/player/job_queue.h" |
| #include "starboard/shared/starboard/player/video_dmp_reader.h" |
| #include "starboard/system.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::Bool; |
| using ::testing::Combine; |
| using ::testing::ValuesIn; |
| using video_dmp::VideoDmpReader; |
| |
| const SbTimeMonotonic kWaitForNextEventTimeOut = 5 * kSbTimeSecond; |
| |
| scoped_refptr<DecodedAudio> ConsolidateDecodedAudios( |
| const std::vector<scoped_refptr<DecodedAudio>>& decoded_audios) { |
| if (decoded_audios.empty()) { |
| return new DecodedAudio(2, kSbMediaAudioSampleTypeFloat32, |
| kSbMediaAudioFrameStorageTypeInterleaved, 0, 0); |
| } |
| |
| int total_size_in_bytes = 0; |
| int channels = decoded_audios.front()->channels(); |
| auto sample_type = decoded_audios.front()->sample_type(); |
| |
| for (auto decoded_audio : decoded_audios) { |
| SB_DCHECK(decoded_audio->channels() == channels); |
| SB_DCHECK(decoded_audio->sample_type() == sample_type); |
| SB_DCHECK(decoded_audio->storage_type() == |
| kSbMediaAudioFrameStorageTypeInterleaved); |
| total_size_in_bytes += decoded_audio->size_in_bytes(); |
| } |
| |
| scoped_refptr<DecodedAudio> consolidated = new DecodedAudio( |
| channels, sample_type, kSbMediaAudioFrameStorageTypeInterleaved, |
| decoded_audios.front()->timestamp(), total_size_in_bytes); |
| |
| int offset_in_bytes = 0; |
| for (auto decoded_audio : decoded_audios) { |
| memcpy(consolidated->data() + offset_in_bytes, decoded_audio->data(), |
| decoded_audio->size_in_bytes()); |
| offset_in_bytes += decoded_audio->size_in_bytes(); |
| } |
| |
| return consolidated; |
| } |
| |
| int GetTotalFrames( |
| const std::vector<scoped_refptr<DecodedAudio>>& decoded_audios) { |
| int total_frames = 0; |
| for (auto decoded_audio : decoded_audios) { |
| total_frames += decoded_audio->frames(); |
| } |
| return total_frames; |
| } |
| |
| class AudioDecoderTest |
| : public ::testing::TestWithParam<std::tuple<const char*, bool>> { |
| public: |
| AudioDecoderTest() |
| : test_filename_(std::get<0>(GetParam())), |
| using_stub_decoder_(std::get<1>(GetParam())), |
| dmp_reader_(test_filename_, VideoDmpReader::kEnableReadOnDemand) { |
| SB_LOG(INFO) << "Testing " << test_filename_ |
| << (using_stub_decoder_ ? " with stub audio decoder." : "."); |
| } |
| void SetUp() override { |
| ASSERT_NE(dmp_reader_.audio_stream_info().codec, kSbMediaAudioCodecNone); |
| ASSERT_GT(dmp_reader_.number_of_audio_buffers(), 0); |
| |
| CreateComponents(dmp_reader_.audio_stream_info(), &audio_decoder_, |
| &audio_renderer_sink_); |
| } |
| |
| protected: |
| enum Event { kConsumed, kOutput, kError }; |
| |
| void CreateComponents(const media::AudioStreamInfo& audio_stream_info, |
| scoped_ptr<AudioDecoder>* audio_decoder, |
| scoped_ptr<AudioRendererSink>* audio_renderer_sink) { |
| if (CreateAudioComponents(using_stub_decoder_, audio_stream_info, |
| audio_decoder, audio_renderer_sink)) { |
| SB_CHECK(*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); |
| } |
| |
| 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; |
| } |
| |
| // 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/reset 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_ = GetAudioInputBuffer(index); |
| audio_decoder_->Decode({last_input_buffer_}, consumed_cb()); |
| } |
| |
| void WriteSingleInput(size_t index, |
| SbTime discarded_duration_from_front, |
| SbTime discarded_duration_from_back) { |
| SB_DCHECK(IsPartialAudioSupported()); |
| |
| ASSERT_TRUE(can_accept_more_input_); |
| ASSERT_LT(index, dmp_reader_.number_of_audio_buffers()); |
| |
| can_accept_more_input_ = false; |
| |
| last_input_buffer_ = GetAudioInputBuffer( |
| index, discarded_duration_from_front, discarded_duration_from_back); |
| 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); |
| |
| int decoded_sample_rate; |
| scoped_refptr<DecodedAudio> local_decoded_audio = |
| audio_decoder_->Read(&decoded_sample_rate); |
| ASSERT_TRUE(local_decoded_audio); |
| if (!first_output_received_) { |
| first_output_received_ = true; |
| decoded_audio_sample_type_ = local_decoded_audio->sample_type(); |
| decoded_audio_sample_rate_ = decoded_sample_rate; |
| } |
| |
| if (local_decoded_audio->is_end_of_stream()) { |
| *decoded_audio = local_decoded_audio; |
| return; |
| } |
| |
| ASSERT_EQ(decoded_audio_sample_type_, local_decoded_audio->sample_type()); |
| ASSERT_EQ(decoded_audio_sample_rate_, decoded_sample_rate); |
| |
| if (!decoded_audios_.empty()) { |
| ASSERT_LT(decoded_audios_.back()->timestamp(), |
| local_decoded_audio->timestamp()); |
| } |
| decoded_audios_.push_back(local_decoded_audio); |
| *decoded_audio = local_decoded_audio; |
| } |
| |
| void WriteMultipleInputs(size_t start_index, |
| size_t number_of_inputs_to_write, |
| bool* error_occurred = nullptr) { |
| ASSERT_LE(start_index + number_of_inputs_to_write, |
| dmp_reader_.number_of_audio_buffers()); |
| |
| if (error_occurred) { |
| *error_occurred = false; |
| } |
| |
| 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; |
| } |
| if (event == kError) { |
| ASSERT_TRUE(error_occurred); |
| *error_occurred = true; |
| return; |
| } |
| 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()); |
| } |
| } |
| |
| // The start_index will be updated to the new position. |
| void WriteTimeLimitedInputs(int* start_index, SbTime time_limit) { |
| SB_DCHECK(start_index); |
| SB_DCHECK(*start_index >= 0); |
| SB_DCHECK(*start_index < dmp_reader_.number_of_audio_buffers()); |
| ASSERT_NO_FATAL_FAILURE( |
| WriteSingleInput(static_cast<size_t>(*start_index))); |
| SB_DCHECK(last_input_buffer_); |
| SbTime last_timestamp = last_input_buffer_->timestamp(); |
| SbTime first_timestamp = last_timestamp; |
| ++(*start_index); |
| |
| while (last_timestamp - first_timestamp < time_limit && |
| *start_index < dmp_reader_.number_of_audio_buffers()) { |
| Event event = kError; |
| ASSERT_NO_FATAL_FAILURE(WaitForNextEvent(&event)); |
| if (event == kConsumed) { |
| ASSERT_NO_FATAL_FAILURE( |
| WriteSingleInput(static_cast<size_t>(*start_index))); |
| SB_DCHECK(last_input_buffer_); |
| last_timestamp = last_input_buffer_->timestamp(); |
| ++(*start_index); |
| 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 = nullptr) { |
| 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_ = nullptr; |
| decoded_audios_.clear(); |
| eos_written_ = false; |
| decoded_audio_sample_rate_ = 0; |
| first_output_received_ = false; |
| } |
| |
| void WaitForDecodedAudio() { |
| Event event; |
| while (decoded_audios_.empty()) { |
| ASSERT_NO_FATAL_FAILURE(WaitForNextEvent(&event)); |
| if (event == kConsumed) { |
| 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()); |
| } |
| } |
| |
| scoped_refptr<InputBuffer> GetAudioInputBuffer(size_t index) { |
| auto input_buffer = testing::GetAudioInputBuffer(&dmp_reader_, index); |
| auto iter = invalid_inputs_.find(index); |
| if (iter != invalid_inputs_.end()) { |
| std::vector<uint8_t> content(input_buffer->size(), iter->second); |
| // Replace the content with invalid data. |
| input_buffer->SetDecryptedContent(std::move(content)); |
| } |
| return input_buffer; |
| } |
| |
| scoped_refptr<InputBuffer> GetAudioInputBuffer( |
| size_t index, |
| SbTime discarded_duration_from_front, |
| SbTime discarded_duration_from_back) { |
| SB_DCHECK(IsPartialAudioSupported()); |
| |
| auto input_buffer = testing::GetAudioInputBuffer( |
| &dmp_reader_, index, discarded_duration_from_front, |
| discarded_duration_from_back); |
| auto iter = invalid_inputs_.find(index); |
| if (iter != invalid_inputs_.end()) { |
| std::vector<uint8_t> content(input_buffer->size(), iter->second); |
| // Replace the content with invalid data. |
| input_buffer->SetDecryptedContent(std::move(content)); |
| } |
| return input_buffer; |
| } |
| |
| void UseInvalidDataForInput(size_t index, uint8_t byte_to_fill) { |
| invalid_inputs_[index] = byte_to_fill; |
| } |
| |
| void WriteEndOfStream() { |
| SB_DCHECK(!eos_written_); |
| audio_decoder_->WriteEndOfStream(); |
| eos_written_ = true; |
| } |
| |
| void AssertOutputFormatValid() { |
| ASSERT_TRUE(decoded_audio_sample_type_ == kSbMediaAudioSampleTypeFloat32 || |
| decoded_audio_sample_type_ == |
| kSbMediaAudioSampleTypeInt16Deprecated); |
| ASSERT_TRUE(decoded_audio_sample_rate_ > 0 && |
| decoded_audio_sample_rate_ <= 480000); |
| } |
| |
| void AssertExpectedAndOutputFramesMatch(int expected_output_frames) { |
| if (using_stub_decoder_) { |
| // The number of output frames is not applicable in the case of the |
| // StubAudioDecoder, because it is not actually doing any decoding work. |
| return; |
| } |
| |
| ASSERT_LE(abs(expected_output_frames - GetTotalFrames(decoded_audios_)), 1); |
| } |
| |
| Mutex event_queue_mutex_; |
| std::deque<Event> event_queue_; |
| |
| // Test parameter for the filename to load with the VideoDmpReader. |
| const char* test_filename_; |
| |
| // Test parameter to configure whether the test is run with the |
| // StubAudioDecoder, or the platform-specific AudioDecoderImpl |
| bool using_stub_decoder_; |
| |
| 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_; |
| std::vector<scoped_refptr<DecodedAudio>> decoded_audios_; |
| |
| bool eos_written_ = false; |
| |
| std::map<size_t, uint8_t> invalid_inputs_; |
| |
| SbMediaAudioSampleType decoded_audio_sample_type_ = |
| kSbMediaAudioSampleTypeInt16Deprecated; |
| int decoded_audio_sample_rate_ = 0; |
| |
| bool first_output_received_ = false; |
| }; |
| |
| std::string GetAudioDecoderTestConfigName( |
| ::testing::TestParamInfo<std::tuple<const char*, bool>> info) { |
| std::string filename(std::get<0>(info.param)); |
| bool using_stub_decoder = std::get<1>(info.param); |
| |
| std::replace(filename.begin(), filename.end(), '.', '_'); |
| |
| return filename + (using_stub_decoder ? "__stub" : ""); |
| } |
| |
| TEST_P(AudioDecoderTest, MultiDecoders) { |
| const int kDecodersToCreate = 100; |
| const int kMinimumNumberOfExtraDecodersRequired = 3; |
| |
| scoped_ptr<AudioDecoder> audio_decoders[kDecodersToCreate]; |
| scoped_ptr<AudioRendererSink> audio_renderer_sinks[kDecodersToCreate]; |
| |
| for (int i = 0; i < kDecodersToCreate; ++i) { |
| CreateComponents(dmp_reader_.audio_stream_info(), &audio_decoders[i], |
| &audio_renderer_sinks[i]); |
| if (!audio_decoders[i]) { |
| ASSERT_GE(i, kMinimumNumberOfExtraDecodersRequired); |
| } |
| } |
| } |
| |
| TEST_P(AudioDecoderTest, SingleInput) { |
| ASSERT_NO_FATAL_FAILURE(WriteSingleInput(0)); |
| WriteEndOfStream(); |
| |
| ASSERT_NO_FATAL_FAILURE(DrainOutputs()); |
| ASSERT_FALSE(decoded_audios_.empty()); |
| ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid()); |
| } |
| |
| TEST_P(AudioDecoderTest, SingleInputHEAAC) { |
| static const int kAacFrameSize = 1024; |
| |
| if (dmp_reader_.audio_codec() != kSbMediaAudioCodecAac) { |
| return; |
| } |
| |
| ASSERT_NO_FATAL_FAILURE(WriteSingleInput(0)); |
| WriteEndOfStream(); |
| |
| ASSERT_NO_FATAL_FAILURE(DrainOutputs()); |
| ASSERT_FALSE(decoded_audios_.empty()); |
| ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid()); |
| |
| int input_sample_rate = |
| last_input_buffer_->audio_stream_info().samples_per_second; |
| ASSERT_NE(0, decoded_audio_sample_rate_); |
| int expected_output_frames = |
| kAacFrameSize * decoded_audio_sample_rate_ / input_sample_rate; |
| AssertExpectedAndOutputFramesMatch(expected_output_frames); |
| } |
| |
| TEST_P(AudioDecoderTest, InvalidCodec) { |
| auto invalid_codec = dmp_reader_.audio_codec() == kSbMediaAudioCodecAac |
| ? kSbMediaAudioCodecOpus |
| : kSbMediaAudioCodecAac; |
| auto audio_stream_info = dmp_reader_.audio_stream_info(); |
| |
| audio_stream_info.codec = invalid_codec; |
| |
| CreateComponents(audio_stream_info, &audio_decoder_, &audio_renderer_sink_); |
| if (!audio_decoder_) { |
| return; |
| } |
| |
| WriteSingleInput(0); |
| WriteEndOfStream(); |
| |
| bool error_occurred = true; |
| ASSERT_NO_FATAL_FAILURE(DrainOutputs(&error_occurred)); |
| } |
| |
| TEST_P(AudioDecoderTest, InvalidConfig) { |
| auto original_audio_stream_info = dmp_reader_.audio_stream_info(); |
| |
| for (size_t i = 0; |
| i < original_audio_stream_info.audio_specific_config.size(); ++i) { |
| auto audio_stream_info = original_audio_stream_info; |
| |
| audio_stream_info.audio_specific_config[i] = |
| ~audio_stream_info.audio_specific_config[i]; |
| |
| CreateComponents(audio_stream_info, &audio_decoder_, &audio_renderer_sink_); |
| if (!audio_decoder_) { |
| return; |
| } |
| WriteSingleInput(0); |
| WriteEndOfStream(); |
| |
| bool error_occurred = true; |
| ASSERT_NO_FATAL_FAILURE(DrainOutputs(&error_occurred)); |
| |
| ResetDecoder(); |
| } |
| |
| for (size_t i = 0; |
| i < original_audio_stream_info.audio_specific_config.size(); ++i) { |
| auto audio_stream_info = original_audio_stream_info; |
| |
| audio_stream_info.audio_specific_config.resize(i); |
| |
| CreateComponents(audio_stream_info, &audio_decoder_, &audio_renderer_sink_); |
| if (!audio_decoder_) { |
| return; |
| } |
| WriteSingleInput(0); |
| WriteEndOfStream(); |
| |
| bool error_occurred = true; |
| ASSERT_NO_FATAL_FAILURE(DrainOutputs(&error_occurred)); |
| |
| ResetDecoder(); |
| } |
| } |
| |
| TEST_P(AudioDecoderTest, SingleInvalidInput) { |
| UseInvalidDataForInput(0, 0xab); |
| |
| WriteSingleInput(0); |
| WriteEndOfStream(); |
| |
| bool error_occurred = true; |
| ASSERT_NO_FATAL_FAILURE(DrainOutputs(&error_occurred)); |
| } |
| |
| TEST_P(AudioDecoderTest, MultipleValidInputsAfterInvalidInput) { |
| const size_t kMaxNumberOfInputToWrite = 10; |
| const size_t number_of_input_to_write = |
| std::min(kMaxNumberOfInputToWrite, dmp_reader_.number_of_audio_buffers()); |
| |
| UseInvalidDataForInput(0, 0xab); |
| |
| bool error_occurred = true; |
| // Write first few frames. The first one is invalid and the rest are valid. |
| WriteMultipleInputs(0, number_of_input_to_write, &error_occurred); |
| |
| if (!error_occurred) { |
| WriteEndOfStream(); |
| ASSERT_NO_FATAL_FAILURE(DrainOutputs(&error_occurred)); |
| } |
| } |
| |
| TEST_P(AudioDecoderTest, MultipleInvalidInput) { |
| const size_t kMaxNumberOfInputToWrite = 128; |
| const size_t number_of_input_to_write = |
| std::min(kMaxNumberOfInputToWrite, dmp_reader_.number_of_audio_buffers()); |
| // Replace the content of the first few input buffers with invalid data. |
| // Every test instance loads its own copy of data so this won't affect other |
| // tests. |
| for (size_t i = 0; i < number_of_input_to_write; ++i) { |
| UseInvalidDataForInput(i, static_cast<uint8_t>(0xab + i)); |
| } |
| |
| bool error_occurred = true; |
| WriteMultipleInputs(0, number_of_input_to_write, &error_occurred); |
| if (!error_occurred) { |
| WriteEndOfStream(); |
| ASSERT_NO_FATAL_FAILURE(DrainOutputs(&error_occurred)); |
| } |
| } |
| |
| TEST_P(AudioDecoderTest, EndOfStreamWithoutAnyInput) { |
| WriteEndOfStream(); |
| |
| ASSERT_NO_FATAL_FAILURE(DrainOutputs()); |
| ASSERT_TRUE(decoded_audios_.empty()); |
| ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid()); |
| } |
| |
| TEST_P(AudioDecoderTest, ResetBeforeInput) { |
| ResetDecoder(); |
| |
| ASSERT_NO_FATAL_FAILURE(WriteSingleInput(0)); |
| WriteEndOfStream(); |
| |
| ASSERT_NO_FATAL_FAILURE(DrainOutputs()); |
| ASSERT_FALSE(decoded_audios_.empty()); |
| ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid()); |
| } |
| |
| 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)); |
| |
| WriteEndOfStream(); |
| |
| ASSERT_NO_FATAL_FAILURE(DrainOutputs()); |
| ASSERT_FALSE(decoded_audios_.empty()); |
| ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid()); |
| } |
| |
| TEST_P(AudioDecoderTest, LimitedInput) { |
| SbTime duration = kSbTimeSecond / 2; |
| #if SB_API_VERSION < 15 |
| SbMediaSetAudioWriteDuration(duration); |
| #endif // SB_API_VERSION < 15 |
| |
| ASSERT_TRUE(decoded_audios_.empty()); |
| int start_index = 0; |
| ASSERT_NO_FATAL_FAILURE(WriteTimeLimitedInputs(&start_index, duration)); |
| |
| if (start_index >= dmp_reader_.number_of_audio_buffers()) { |
| WriteEndOfStream(); |
| } |
| |
| // Wait for decoded audio. |
| WaitForDecodedAudio(); |
| } |
| |
| TEST_P(AudioDecoderTest, ContinuedLimitedInput) { |
| constexpr int kMaxAccessUnitsToDecode = 256; |
| SbTime duration = kSbTimeSecond / 2; |
| #if SB_API_VERSION < 15 |
| SbMediaSetAudioWriteDuration(duration); |
| #endif // SB_API_VERSION < 15 |
| |
| SbTime start = SbTimeGetMonotonicNow(); |
| int start_index = 0; |
| Event event; |
| while (true) { |
| ASSERT_NO_FATAL_FAILURE(WriteTimeLimitedInputs(&start_index, duration)); |
| if (start_index >= std::min<int>(dmp_reader_.number_of_audio_buffers(), |
| kMaxAccessUnitsToDecode)) { |
| break; |
| } |
| SB_DCHECK(last_input_buffer_); |
| WaitForDecodedAudio(); |
| ASSERT_FALSE(decoded_audios_.empty()); |
| while ((last_input_buffer_->timestamp() - |
| decoded_audios_.back()->timestamp()) > duration || |
| !can_accept_more_input_) { |
| ASSERT_NO_FATAL_FAILURE(WaitForNextEvent(&event)); |
| if (event == kConsumed) { |
| 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()); |
| } |
| } |
| WriteEndOfStream(); |
| ASSERT_NO_FATAL_FAILURE(DrainOutputs()); |
| SbTime elapsed = SbTimeGetMonotonicNow() - start; |
| SB_LOG(INFO) << "Decoding " << dmp_reader_.number_of_audio_buffers() |
| << " access units of " |
| << GetMediaAudioCodecName(dmp_reader_.audio_codec()) << " takes " |
| << elapsed << " microseconds."; |
| |
| ASSERT_FALSE(decoded_audios_.empty()); |
| ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid()); |
| } |
| |
| TEST_P(AudioDecoderTest, PartialAudio) { |
| if (!IsPartialAudioSupported()) { |
| return; |
| } |
| |
| const int max_number_of_input_to_write = |
| std::min(7, static_cast<int>(dmp_reader_.number_of_audio_buffers())); |
| |
| for (int number_of_input_to_write = 1; |
| number_of_input_to_write < max_number_of_input_to_write; |
| ++number_of_input_to_write) { |
| SB_LOG(INFO) << "Testing " << number_of_input_to_write |
| << " access units for partial audio."; |
| ResetDecoder(); |
| |
| // Decode InputBuffers without partial audio and use the output as reference |
| for (int i = 0; i < number_of_input_to_write; ++i) { |
| ASSERT_NO_FATAL_FAILURE(WriteSingleInput(i)); |
| if (i == number_of_input_to_write - 1) { |
| WriteEndOfStream(); |
| break; |
| } |
| |
| for (;;) { |
| Event event = kError; |
| ASSERT_NO_FATAL_FAILURE(WaitForNextEvent(&event)); |
| ASSERT_NE(event, kError); |
| if (event == kConsumed) { |
| break; |
| } |
| ASSERT_EQ(kOutput, event); |
| scoped_refptr<DecodedAudio> decoded_audio; |
| ASSERT_NO_FATAL_FAILURE(ReadFromDecoder(&decoded_audio)); |
| ASSERT_TRUE(decoded_audio); |
| } |
| } |
| |
| ASSERT_NO_FATAL_FAILURE(DrainOutputs()); |
| ASSERT_FALSE(decoded_audios_.empty()); |
| ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid()); |
| |
| auto reference_decoded_audio = ConsolidateDecodedAudios(decoded_audios_); |
| ASSERT_GT(reference_decoded_audio->frames(), 1); |
| |
| // Discard 1/4 of the duration from front, and back. The resulting audio |
| // will keep 1/2 of the frames in the middle. This has to be called before |
| // `ResetDecoder()` as it resets `decoded_audio_sample_rate_`. |
| ASSERT_GT(decoded_audio_sample_rate_, 0); |
| auto frames_per_access_unit = |
| reference_decoded_audio->frames() / number_of_input_to_write; |
| SbTime duration_to_discard = |
| media::AudioFramesToDuration(frames_per_access_unit, |
| decoded_audio_sample_rate_) / |
| 4; |
| |
| ResetDecoder(); |
| |
| for (int i = 0; i < number_of_input_to_write; ++i) { |
| SbTime duration_to_discard_from_front = i == 0 ? duration_to_discard : 0; |
| SbTime duration_to_discard_from_back = |
| i == number_of_input_to_write - 1 ? duration_to_discard : 0; |
| ASSERT_NO_FATAL_FAILURE(WriteSingleInput( |
| i, duration_to_discard_from_front, duration_to_discard_from_back)); |
| |
| if (i == number_of_input_to_write - 1) { |
| WriteEndOfStream(); |
| break; |
| } |
| for (;;) { |
| Event event = kError; |
| ASSERT_NO_FATAL_FAILURE(WaitForNextEvent(&event)); |
| ASSERT_NE(event, kError); |
| if (event == kConsumed) { |
| break; |
| } |
| ASSERT_EQ(kOutput, event); |
| scoped_refptr<DecodedAudio> decoded_audio; |
| ASSERT_NO_FATAL_FAILURE(ReadFromDecoder(&decoded_audio)); |
| ASSERT_TRUE(decoded_audio); |
| } |
| } |
| |
| ASSERT_NO_FATAL_FAILURE(DrainOutputs()); |
| ASSERT_FALSE(decoded_audios_.empty()); |
| ASSERT_NO_FATAL_FAILURE(AssertOutputFormatValid()); |
| |
| auto partial_decoded_audio = ConsolidateDecodedAudios(decoded_audios_); |
| |
| ASSERT_EQ(reference_decoded_audio->sample_type(), |
| partial_decoded_audio->sample_type()); |
| ASSERT_EQ(reference_decoded_audio->storage_type(), |
| partial_decoded_audio->storage_type()); |
| ASSERT_GT(reference_decoded_audio->frames(), |
| partial_decoded_audio->frames()); |
| |
| auto bytes_per_frame = reference_decoded_audio->size_in_bytes() / |
| reference_decoded_audio->frames(); |
| // |partial_decoded_audio| should contain exactly the same data as |
| // |reference_decoded_audio|, begin from about 1/4 of an access unit. We |
| // search from (1/4 access unit - 1) in |reference_decoded_audio| to allow |
| // for up to one frame of error during calculation. |
| auto reference_search_begin = |
| reference_decoded_audio->data() + |
| (frames_per_access_unit / 4 - 1) * bytes_per_frame; |
| auto reference_search_end = reference_decoded_audio->data() + |
| reference_decoded_audio->size_in_bytes(); |
| auto offset_in_bytes = |
| std::search(reference_search_begin, reference_search_end, |
| partial_decoded_audio->data(), |
| partial_decoded_audio->data() + |
| partial_decoded_audio->size_in_bytes()) - |
| reference_search_begin; |
| auto offset_in_frames = offset_in_bytes / bytes_per_frame; |
| |
| constexpr int kEpsilonInFrames = 2; |
| EXPECT_LE(offset_in_frames, kEpsilonInFrames); |
| EXPECT_NEAR(reference_decoded_audio->frames() - frames_per_access_unit / 2, |
| partial_decoded_audio->frames(), kEpsilonInFrames); |
| } |
| } |
| |
| INSTANTIATE_TEST_CASE_P( |
| AudioDecoderTests, |
| AudioDecoderTest, |
| Combine(ValuesIn(GetSupportedAudioTestFiles(kIncludeHeaac, |
| 6, |
| "audiopassthrough=false")), |
| Bool()), |
| GetAudioDecoderTestConfigName); |
| |
| } // namespace |
| } // namespace testing |
| } // namespace filter |
| } // namespace player |
| } // namespace starboard |
| } // namespace shared |
| } // namespace starboard |