| // 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(); |
| // TODO: Replace it with EXPECT_GT once audio time reporting is more |
| // accurate. |
| 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(); |
| // TODO: Replace it with EXPECT_GT once audio time reporting is more |
| // accurate. |
| 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(); |
| // TODO: Replace it with EXPECT_GT once audio time reporting is more |
| // accurate. |
| 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(); |
| // TODO: Replace it with EXPECT_GT once audio time reporting is more |
| // accurate. |
| 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(); |
| // TODO: Replace it with EXPECT_GT once audio time reporting is more |
| // accurate. |
| 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 |