// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chromecast/media/audio/cast_audio_output_stream.h" #include #include #include #include "base/bind.h" #include "base/macros.h" #include "base/synchronization/waitable_event.h" #include "chromecast/base/metrics/cast_metrics_test_helper.h" #include "chromecast/media/audio/cast_audio_manager.h" #include "chromecast/media/base/media_message_loop.h" #include "chromecast/media/cma/backend/media_pipeline_backend_default.h" #include "chromecast/public/media/cast_decoder_buffer.h" #include "chromecast/public/media/decoder_config.h" #include "chromecast/public/media/decrypt_context.h" #include "chromecast/public/media/media_pipeline_backend.h" #include "testing/gtest/include/gtest/gtest.h" namespace chromecast { namespace media { namespace { const char kDefaultDeviceId[] = ""; void RunUntilIdle(base::TaskRunner* task_runner) { base::WaitableEvent completion_event(false, false); task_runner->PostTask(FROM_HERE, base::Bind(&base::WaitableEvent::Signal, base::Unretained(&completion_event))); completion_event.Wait(); } class FakeAudioDecoder : public MediaPipelineBackend::AudioDecoder { public: enum PipelineStatus { PIPELINE_STATUS_OK, PIPELINE_STATUS_BUSY, PIPELINE_STATUS_ERROR, PIPELINE_STATUS_ASYNC_ERROR, }; FakeAudioDecoder() : volume_(1.0f), pipeline_status_(PIPELINE_STATUS_OK), pending_push_(false), pushed_buffer_count_(0), last_buffer_(nullptr), delegate_(nullptr) {} ~FakeAudioDecoder() override {} // MediaPipelineBackend::AudioDecoder implementation: void SetDelegate(Delegate* delegate) override { DCHECK(delegate); delegate_ = delegate; } BufferStatus PushBuffer(CastDecoderBuffer* buffer) override { last_buffer_ = buffer; ++pushed_buffer_count_; switch (pipeline_status_) { case PIPELINE_STATUS_OK: return MediaPipelineBackend::kBufferSuccess; case PIPELINE_STATUS_BUSY: pending_push_ = true; return MediaPipelineBackend::kBufferPending; case PIPELINE_STATUS_ERROR: return MediaPipelineBackend::kBufferFailed; case PIPELINE_STATUS_ASYNC_ERROR: pending_push_ = true; delegate_->OnDecoderError(); return MediaPipelineBackend::kBufferPending; default: NOTREACHED(); return MediaPipelineBackend::kBufferFailed; } } void GetStatistics(Statistics* statistics) override {} bool SetConfig(const AudioConfig& config) override { config_ = config; return true; } bool SetVolume(float volume) override { volume_ = volume; return true; } RenderingDelay GetRenderingDelay() override { return RenderingDelay(); } const AudioConfig& config() const { return config_; } float volume() const { return volume_; } void set_pipeline_status(PipelineStatus status) { if (status == PIPELINE_STATUS_OK && pending_push_) { pending_push_ = false; delegate_->OnPushBufferComplete(MediaPipelineBackend::kBufferSuccess); } pipeline_status_ = status; } unsigned pushed_buffer_count() const { return pushed_buffer_count_; } CastDecoderBuffer* last_buffer() { return last_buffer_; } private: AudioConfig config_; float volume_; PipelineStatus pipeline_status_; bool pending_push_; int pushed_buffer_count_; CastDecoderBuffer* last_buffer_; Delegate* delegate_; }; class FakeMediaPipelineBackend : public MediaPipelineBackend { public: enum State { kStateStopped, kStateRunning, kStatePaused }; FakeMediaPipelineBackend() : state_(kStateStopped), audio_decoder_(nullptr) {} ~FakeMediaPipelineBackend() override {} // MediaPipelineBackend implementation: AudioDecoder* CreateAudioDecoder() override { DCHECK(!audio_decoder_); audio_decoder_ = new FakeAudioDecoder(); return audio_decoder_; } VideoDecoder* CreateVideoDecoder() override { NOTREACHED(); return nullptr; } bool Initialize() override { return true; } bool Start(int64_t start_pts) override { EXPECT_EQ(kStateStopped, state_); state_ = kStateRunning; return true; } bool Stop() override { EXPECT_TRUE(state_ == kStateRunning || state_ == kStatePaused); state_ = kStateStopped; return true; } bool Pause() override { EXPECT_EQ(kStateRunning, state_); state_ = kStatePaused; return true; } bool Resume() override { EXPECT_EQ(kStatePaused, state_); state_ = kStateRunning; return true; } int64_t GetCurrentPts() override { return 0; } bool SetPlaybackRate(float rate) override { return true; } State state() const { return state_; } FakeAudioDecoder* decoder() const { return audio_decoder_; } private: State state_; FakeAudioDecoder* audio_decoder_; }; class FakeAudioSourceCallback : public ::media::AudioOutputStream::AudioSourceCallback { public: FakeAudioSourceCallback() : error_(false) {} bool error() const { return error_; } // ::media::AudioOutputStream::AudioSourceCallback overrides. int OnMoreData(::media::AudioBus* audio_bus, uint32_t total_bytes_delay, uint32_t frames_skipped) override { audio_bus->Zero(); return audio_bus->frames(); } void OnError(::media::AudioOutputStream* stream) override { error_ = true; } private: bool error_; }; class FakeAudioManager : public CastAudioManager { public: FakeAudioManager() : CastAudioManager(nullptr), media_pipeline_backend_(nullptr) {} ~FakeAudioManager() override {} // CastAudioManager overrides. scoped_ptr CreateMediaPipelineBackend( const MediaPipelineDeviceParams& params) override { DCHECK(media::MediaMessageLoop::GetTaskRunner()->BelongsToCurrentThread()); DCHECK(!media_pipeline_backend_); scoped_ptr backend( new FakeMediaPipelineBackend()); // Cache the backend locally to be used by tests. media_pipeline_backend_ = backend.get(); return std::move(backend); } void ReleaseOutputStream(::media::AudioOutputStream* stream) override { DCHECK(media_pipeline_backend_); media_pipeline_backend_ = nullptr; CastAudioManager::ReleaseOutputStream(stream); } // Returns the MediaPipelineBackend being used by the AudioOutputStream. // Note: here is a valid MediaPipelineBackend only while the stream is open. // Returns NULL at all other times. FakeMediaPipelineBackend* media_pipeline_backend() { return media_pipeline_backend_; } private: FakeMediaPipelineBackend* media_pipeline_backend_; }; class CastAudioOutputStreamTest : public ::testing::Test { public: CastAudioOutputStreamTest() : format_(::media::AudioParameters::AUDIO_PCM_LINEAR), channel_layout_(::media::CHANNEL_LAYOUT_MONO), sample_rate_(::media::AudioParameters::kAudioCDSampleRate), bits_per_sample_(16), frames_per_buffer_(256) {} ~CastAudioOutputStreamTest() override {} protected: void SetUp() override { metrics::InitializeMetricsHelperForTesting(); audio_manager_.reset(new FakeAudioManager); audio_task_runner_ = audio_manager_->GetTaskRunner(); backend_task_runner_ = media::MediaMessageLoop::GetTaskRunner(); } void TearDown() override { audio_manager_.reset(); } ::media::AudioParameters GetAudioParams() { return ::media::AudioParameters(format_, channel_layout_, sample_rate_, bits_per_sample_, frames_per_buffer_); } FakeMediaPipelineBackend* GetBackend() { return audio_manager_->media_pipeline_backend(); } FakeAudioDecoder* GetAudio() { FakeMediaPipelineBackend* backend = GetBackend(); return (backend ? backend->decoder() : nullptr); } // Synchronous utility functions. ::media::AudioOutputStream* CreateStream() { ::media::AudioOutputStream* stream = nullptr; base::WaitableEvent completion_event(false, false); audio_task_runner_->PostTask( FROM_HERE, base::Bind(&CastAudioOutputStreamTest::CreateStreamOnAudioThread, base::Unretained(this), GetAudioParams(), &stream, &completion_event)); completion_event.Wait(); return stream; } bool OpenStream(::media::AudioOutputStream* stream) { DCHECK(stream); bool success = false; base::WaitableEvent completion_event(false, false); audio_task_runner_->PostTask( FROM_HERE, base::Bind(&CastAudioOutputStreamTest::OpenStreamOnAudioThread, base::Unretained(this), stream, &success, &completion_event)); completion_event.Wait(); // Drain the backend task runner so that appropriate states are set on // the backend pipeline devices. RunUntilIdle(backend_task_runner_.get()); return success; } void CloseStream(::media::AudioOutputStream* stream) { audio_task_runner_->PostTask(FROM_HERE, base::Bind(&::media::AudioOutputStream::Close, base::Unretained(stream))); RunUntilIdle(audio_task_runner_.get()); RunUntilIdle(backend_task_runner_.get()); // Backend task runner may have posted more tasks to the audio task runner. // So we need to drain it once more. RunUntilIdle(audio_task_runner_.get()); } void StartStream( ::media::AudioOutputStream* stream, ::media::AudioOutputStream::AudioSourceCallback* source_callback) { audio_task_runner_->PostTask( FROM_HERE, base::Bind(&::media::AudioOutputStream::Start, base::Unretained(stream), source_callback)); // Drain the audio task runner twice so that tasks posted by // media::AudioOutputStream::Start are run as well. RunUntilIdle(audio_task_runner_.get()); RunUntilIdle(audio_task_runner_.get()); // Drain the backend task runner so that appropriate states are set on // the backend pipeline devices. RunUntilIdle(backend_task_runner_.get()); // Drain the audio task runner again to run the tasks posted by the // backend on audio task runner. RunUntilIdle(audio_task_runner_.get()); } void StopStream(::media::AudioOutputStream* stream) { audio_task_runner_->PostTask(FROM_HERE, base::Bind(&::media::AudioOutputStream::Stop, base::Unretained(stream))); RunUntilIdle(audio_task_runner_.get()); // Drain the backend task runner so that appropriate states are set on // the backend pipeline devices. RunUntilIdle(backend_task_runner_.get()); } void SetStreamVolume(::media::AudioOutputStream* stream, double volume) { audio_task_runner_->PostTask( FROM_HERE, base::Bind(&::media::AudioOutputStream::SetVolume, base::Unretained(stream), volume)); RunUntilIdle(audio_task_runner_.get()); // Drain the backend task runner so that appropriate states are set on // the backend pipeline devices. RunUntilIdle(backend_task_runner_.get()); } double GetStreamVolume(::media::AudioOutputStream* stream) { double volume = 0.0; audio_task_runner_->PostTask( FROM_HERE, base::Bind(&::media::AudioOutputStream::GetVolume, base::Unretained(stream), &volume)); RunUntilIdle(audio_task_runner_.get()); // No need to drain the backend task runner because getting the volume // does not involve posting any task to the backend. return volume; } void CreateStreamOnAudioThread(const ::media::AudioParameters& audio_params, ::media::AudioOutputStream** stream, base::WaitableEvent* completion_event) { DCHECK(audio_task_runner_->BelongsToCurrentThread()); *stream = audio_manager_->MakeAudioOutputStream(GetAudioParams(), kDefaultDeviceId); completion_event->Signal(); } void OpenStreamOnAudioThread(::media::AudioOutputStream* stream, bool* success, base::WaitableEvent* completion_event) { DCHECK(audio_task_runner_->BelongsToCurrentThread()); *success = stream->Open(); completion_event->Signal(); } scoped_ptr audio_manager_; scoped_refptr audio_task_runner_; scoped_refptr backend_task_runner_; // AudioParameters used to create AudioOutputStream. // Tests can modify these parameters before calling CreateStream. ::media::AudioParameters::Format format_; ::media::ChannelLayout channel_layout_; int sample_rate_; int bits_per_sample_; int frames_per_buffer_; }; TEST_F(CastAudioOutputStreamTest, Format) { ::media::AudioParameters::Format format[] = { //::media::AudioParameters::AUDIO_PCM_LINEAR, ::media::AudioParameters::AUDIO_PCM_LOW_LATENCY}; for (size_t i = 0; i < arraysize(format); ++i) { format_ = format[i]; ::media::AudioOutputStream* stream = CreateStream(); ASSERT_TRUE(stream); EXPECT_TRUE(OpenStream(stream)); FakeAudioDecoder* audio_decoder = GetAudio(); ASSERT_TRUE(audio_decoder); const AudioConfig& audio_config = audio_decoder->config(); EXPECT_EQ(kCodecPCM, audio_config.codec); EXPECT_EQ(kSampleFormatS16, audio_config.sample_format); EXPECT_FALSE(audio_config.is_encrypted); CloseStream(stream); } } TEST_F(CastAudioOutputStreamTest, ChannelLayout) { ::media::ChannelLayout layout[] = {::media::CHANNEL_LAYOUT_MONO, ::media::CHANNEL_LAYOUT_STEREO}; for (size_t i = 0; i < arraysize(layout); ++i) { channel_layout_ = layout[i]; ::media::AudioOutputStream* stream = CreateStream(); ASSERT_TRUE(stream); EXPECT_TRUE(OpenStream(stream)); FakeAudioDecoder* audio_decoder = GetAudio(); ASSERT_TRUE(audio_decoder); const AudioConfig& audio_config = audio_decoder->config(); EXPECT_EQ(::media::ChannelLayoutToChannelCount(channel_layout_), audio_config.channel_number); CloseStream(stream); } } TEST_F(CastAudioOutputStreamTest, SampleRate) { sample_rate_ = ::media::AudioParameters::kAudioCDSampleRate; ::media::AudioOutputStream* stream = CreateStream(); ASSERT_TRUE(stream); EXPECT_TRUE(OpenStream(stream)); FakeAudioDecoder* audio_decoder = GetAudio(); ASSERT_TRUE(audio_decoder); const AudioConfig& audio_config = audio_decoder->config(); EXPECT_EQ(sample_rate_, audio_config.samples_per_second); CloseStream(stream); } TEST_F(CastAudioOutputStreamTest, BitsPerSample) { bits_per_sample_ = 16; ::media::AudioOutputStream* stream = CreateStream(); ASSERT_TRUE(stream); EXPECT_TRUE(OpenStream(stream)); FakeAudioDecoder* audio_decoder = GetAudio(); ASSERT_TRUE(audio_decoder); const AudioConfig& audio_config = audio_decoder->config(); EXPECT_EQ(bits_per_sample_ / 8, audio_config.bytes_per_channel); CloseStream(stream); } TEST_F(CastAudioOutputStreamTest, DeviceState) { ::media::AudioOutputStream* stream = CreateStream(); ASSERT_TRUE(stream); EXPECT_FALSE(GetAudio()); EXPECT_TRUE(OpenStream(stream)); FakeAudioDecoder* audio_decoder = GetAudio(); ASSERT_TRUE(audio_decoder); FakeMediaPipelineBackend* backend = GetBackend(); ASSERT_TRUE(backend); EXPECT_EQ(FakeMediaPipelineBackend::kStateStopped, backend->state()); scoped_ptr source_callback( new FakeAudioSourceCallback); StartStream(stream, source_callback.get()); EXPECT_EQ(FakeMediaPipelineBackend::kStateRunning, backend->state()); StopStream(stream); EXPECT_EQ(FakeMediaPipelineBackend::kStatePaused, backend->state()); CloseStream(stream); EXPECT_FALSE(GetAudio()); } TEST_F(CastAudioOutputStreamTest, PushFrame) { ::media::AudioOutputStream* stream = CreateStream(); ASSERT_TRUE(stream); EXPECT_TRUE(OpenStream(stream)); FakeAudioDecoder* audio_decoder = GetAudio(); ASSERT_TRUE(audio_decoder); // Verify initial state. EXPECT_EQ(0u, audio_decoder->pushed_buffer_count()); EXPECT_FALSE(audio_decoder->last_buffer()); scoped_ptr source_callback( new FakeAudioSourceCallback); StartStream(stream, source_callback.get()); StopStream(stream); // Verify that the stream pushed frames to the backend. EXPECT_LT(0u, audio_decoder->pushed_buffer_count()); EXPECT_TRUE(audio_decoder->last_buffer()); // Verify decoder buffer. ::media::AudioParameters audio_params = GetAudioParams(); const size_t expected_frame_size = static_cast(audio_params.GetBytesPerBuffer()); const CastDecoderBuffer* buffer = audio_decoder->last_buffer(); EXPECT_TRUE(buffer->data()); EXPECT_EQ(expected_frame_size, buffer->data_size()); EXPECT_FALSE(buffer->decrypt_config()); // Null because of raw audio. EXPECT_FALSE(buffer->end_of_stream()); // No error must be reported to source callback. EXPECT_FALSE(source_callback->error()); CloseStream(stream); } TEST_F(CastAudioOutputStreamTest, DeviceBusy) { ::media::AudioOutputStream* stream = CreateStream(); ASSERT_TRUE(stream); EXPECT_TRUE(OpenStream(stream)); FakeAudioDecoder* audio_decoder = GetAudio(); ASSERT_TRUE(audio_decoder); audio_decoder->set_pipeline_status(FakeAudioDecoder::PIPELINE_STATUS_BUSY); scoped_ptr source_callback( new FakeAudioSourceCallback); StartStream(stream, source_callback.get()); // Make sure that one frame was pushed. EXPECT_EQ(1u, audio_decoder->pushed_buffer_count()); // No error must be reported to source callback. EXPECT_FALSE(source_callback->error()); // Sleep for a few frames and verify that more frames were not pushed // because the backend device was busy. ::media::AudioParameters audio_params = GetAudioParams(); base::TimeDelta pause = audio_params.GetBufferDuration() * 5; base::PlatformThread::Sleep(pause); RunUntilIdle(audio_task_runner_.get()); RunUntilIdle(backend_task_runner_.get()); EXPECT_EQ(1u, audio_decoder->pushed_buffer_count()); // Unblock the pipeline and verify that PushFrame resumes. // (have to post because this directly calls buffer complete) backend_task_runner_->PostTask( FROM_HERE, base::Bind(&FakeAudioDecoder::set_pipeline_status, base::Unretained(audio_decoder), FakeAudioDecoder::PIPELINE_STATUS_OK)); base::PlatformThread::Sleep(pause); RunUntilIdle(audio_task_runner_.get()); RunUntilIdle(backend_task_runner_.get()); EXPECT_LT(1u, audio_decoder->pushed_buffer_count()); EXPECT_FALSE(source_callback->error()); StopStream(stream); CloseStream(stream); } TEST_F(CastAudioOutputStreamTest, DeviceError) { ::media::AudioOutputStream* stream = CreateStream(); ASSERT_TRUE(stream); EXPECT_TRUE(OpenStream(stream)); FakeAudioDecoder* audio_decoder = GetAudio(); ASSERT_TRUE(audio_decoder); audio_decoder->set_pipeline_status(FakeAudioDecoder::PIPELINE_STATUS_ERROR); scoped_ptr source_callback( new FakeAudioSourceCallback); StartStream(stream, source_callback.get()); // Make sure that AudioOutputStream attempted to push the initial frame. EXPECT_LT(0u, audio_decoder->pushed_buffer_count()); // AudioOutputStream must report error to source callback. EXPECT_TRUE(source_callback->error()); StopStream(stream); CloseStream(stream); } TEST_F(CastAudioOutputStreamTest, DeviceAsyncError) { ::media::AudioOutputStream* stream = CreateStream(); ASSERT_TRUE(stream); EXPECT_TRUE(OpenStream(stream)); FakeAudioDecoder* audio_decoder = GetAudio(); ASSERT_TRUE(audio_decoder); audio_decoder->set_pipeline_status( FakeAudioDecoder::PIPELINE_STATUS_ASYNC_ERROR); scoped_ptr source_callback( new FakeAudioSourceCallback); StartStream(stream, source_callback.get()); // Make sure that one frame was pushed. EXPECT_EQ(1u, audio_decoder->pushed_buffer_count()); // Unblock the pipeline and verify that PushFrame resumes. // (have to post because this directly calls buffer complete) backend_task_runner_->PostTask( FROM_HERE, base::Bind(&FakeAudioDecoder::set_pipeline_status, base::Unretained(audio_decoder), FakeAudioDecoder::PIPELINE_STATUS_OK)); RunUntilIdle(audio_task_runner_.get()); RunUntilIdle(backend_task_runner_.get()); // AudioOutputStream must report error to source callback. EXPECT_TRUE(source_callback->error()); StopStream(stream); CloseStream(stream); } TEST_F(CastAudioOutputStreamTest, Volume) { ::media::AudioOutputStream* stream = CreateStream(); ASSERT_TRUE(stream); ASSERT_TRUE(OpenStream(stream)); FakeAudioDecoder* audio_decoder = GetAudio(); ASSERT_TRUE(audio_decoder); double volume = GetStreamVolume(stream); EXPECT_EQ(1.0, volume); EXPECT_EQ(1.0f, audio_decoder->volume()); SetStreamVolume(stream, 0.5); volume = GetStreamVolume(stream); EXPECT_EQ(0.5, volume); EXPECT_EQ(0.5f, audio_decoder->volume()); CloseStream(stream); } TEST_F(CastAudioOutputStreamTest, StartStopStart) { ::media::AudioOutputStream* stream = CreateStream(); ASSERT_TRUE(stream); ASSERT_TRUE(OpenStream(stream)); scoped_ptr source_callback( new FakeAudioSourceCallback); audio_task_runner_->PostTask( FROM_HERE, base::Bind(&::media::AudioOutputStream::Start, base::Unretained(stream), source_callback.get())); audio_task_runner_->PostTask( FROM_HERE, base::Bind(&::media::AudioOutputStream::Stop, base::Unretained(stream))); audio_task_runner_->PostTask( FROM_HERE, base::Bind(&::media::AudioOutputStream::Start, base::Unretained(stream), source_callback.get())); RunUntilIdle(audio_task_runner_.get()); RunUntilIdle(backend_task_runner_.get()); FakeAudioDecoder* audio_device = GetAudio(); EXPECT_TRUE(audio_device); EXPECT_EQ(FakeMediaPipelineBackend::kStateRunning, GetBackend()->state()); CloseStream(stream); } TEST_F(CastAudioOutputStreamTest, CloseWithoutStart) { ::media::AudioOutputStream* stream = CreateStream(); ASSERT_TRUE(stream); ASSERT_TRUE(OpenStream(stream)); CloseStream(stream); } } // namespace } // namespace media } // namespace chromecast