diff options
author | dgreid@chromium.org <dgreid@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-27 00:05:12 +0000 |
---|---|---|
committer | dgreid@chromium.org <dgreid@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-27 00:05:12 +0000 |
commit | ec3d2d8aefc6df5c297fdd04e552c70fadbcbc1d (patch) | |
tree | be2608fc1906a53227ecdaaae86aca131a943934 /media/audio | |
parent | bdc06d5f501ee2252efd40929bbdcd95b4532fcf (diff) | |
download | chromium_src-ec3d2d8aefc6df5c297fdd04e552c70fadbcbc1d.zip chromium_src-ec3d2d8aefc6df5c297fdd04e552c70fadbcbc1d.tar.gz chromium_src-ec3d2d8aefc6df5c297fdd04e552c70fadbcbc1d.tar.bz2 |
Add the output interface for the ChromeOS Audio Server(CRAS).
Add cras_output.cc/h for talking to the server and cras_wrapper to
allow unit testing. Code structure borrowed from alsa_output and
mac/audio_low_latency_output. Introduce a gyp setting that can be used
to enable cras_output.
BUG=chromium-os:25409
TEST=New unit test for cras_output. Build and run on desktop linux
and on mario.
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Review URL: http://codereview.chromium.org/9374011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@129055 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/audio')
-rw-r--r-- | media/audio/linux/audio_manager_linux.cc | 11 | ||||
-rw-r--r-- | media/audio/linux/cras_output.cc | 321 | ||||
-rw-r--r-- | media/audio/linux/cras_output.h | 117 | ||||
-rw-r--r-- | media/audio/linux/cras_output_unittest.cc | 216 |
4 files changed, 665 insertions, 0 deletions
diff --git a/media/audio/linux/audio_manager_linux.cc b/media/audio/linux/audio_manager_linux.cc index c081c03..27d5749 100644 --- a/media/audio/linux/audio_manager_linux.cc +++ b/media/audio/linux/audio_manager_linux.cc @@ -17,6 +17,9 @@ #if defined(USE_PULSEAUDIO) #include "media/audio/pulse/pulse_output.h" #endif +#if defined(USE_CRAS) +#include "media/audio/linux/cras_output.h" +#endif #include "media/base/limits.h" #include "media/base/media_switches.h" @@ -263,6 +266,11 @@ AudioInputStream* AudioManagerLinux::MakeLowLatencyInputStream( AudioOutputStream* AudioManagerLinux::MakeOutputStream( const AudioParameters& params) { AudioOutputStream* stream = NULL; +#if defined(USE_CRAS) + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kUseCras)) { + stream = new CrasOutputStream(params, this); + } else { +#endif #if defined(USE_PULSEAUDIO) if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kUsePulseAudio)) { stream = new PulseAudioOutputStream(params, this); @@ -278,6 +286,9 @@ AudioOutputStream* AudioManagerLinux::MakeOutputStream( #if defined(USE_PULSEAUDIO) } #endif +#if defined(USE_CRAS) + } +#endif DCHECK(stream); return stream; } diff --git a/media/audio/linux/cras_output.cc b/media/audio/linux/cras_output.cc new file mode 100644 index 0000000..dd65d6d --- /dev/null +++ b/media/audio/linux/cras_output.cc @@ -0,0 +1,321 @@ +// Copyright (c) 2012 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. +// +// The object has one error state: |state_| == kInError. When |state_| == +// kInError, all public API functions will fail with an error (Start() will call +// the OnError() function on the callback immediately), or no-op themselves with +// the exception of Close(). Even if an error state has been entered, if Open() +// has previously returned successfully, Close() must be called. + +#include "media/audio/linux/cras_output.h" + +#include <cras_client.h> + +#include "base/logging.h" +#include "media/audio/audio_util.h" +#include "media/audio/linux/alsa_util.h" +#include "media/audio/linux/audio_manager_linux.h" + +// Helps make log messages readable. +std::ostream& operator<<(std::ostream& os, + CrasOutputStream::InternalState state) { + switch (state) { + case CrasOutputStream::kInError: + os << "kInError"; + break; + case CrasOutputStream::kCreated: + os << "kCreated"; + break; + case CrasOutputStream::kIsOpened: + os << "kIsOpened"; + break; + case CrasOutputStream::kIsPlaying: + os << "kIsPlaying"; + break; + case CrasOutputStream::kIsStopped: + os << "kIsStopped"; + break; + case CrasOutputStream::kIsClosed: + os << "kIsClosed"; + break; + default: + os << "UnknownState"; + break; + }; + return os; +} + +// Overview of operation: +// 1) An object of CrasOutputStream is created by the AudioManager +// factory: audio_man->MakeAudioStream(). +// 2) Next some thread will call Open(), at that point a client is created and +// configured for the correct format and sample rate. +// 3) Then Start(source) is called and a stream is added to the CRAS client +// which will create its own thread that periodically calls the source for more +// data as buffers are being consumed. +// 4) When finished Stop() is called, which is handled by stopping the stream. +// 5) Finally Close() is called. It cleans up and notifies the audio manager, +// which likely will destroy this object. + +CrasOutputStream::CrasOutputStream(const AudioParameters& params, + AudioManagerLinux* manager) + : client_(NULL), + stream_id_(0), + samples_per_packet_(params.frames_per_buffer()), + frame_rate_(params.sample_rate()), + num_channels_(params.channels()), + pcm_format_(alsa_util::BitsToFormat(params.bits_per_sample())), + state_(kCreated), + volume_(1.0), + manager_(manager), + source_callback_(NULL) { + // We must have a manager. + DCHECK(manager_); + + // Sanity check input values. + if (params.sample_rate() <= 0) { + LOG(WARNING) << "Unsupported audio frequency."; + TransitionTo(kInError); + return; + } + + if (AudioParameters::AUDIO_PCM_LINEAR != params.format() && + AudioParameters::AUDIO_PCM_LOW_LATENCY != params.format()) { + LOG(WARNING) << "Unsupported audio format."; + TransitionTo(kInError); + return; + } + + if (pcm_format_ == SND_PCM_FORMAT_UNKNOWN) { + LOG(WARNING) << "Unsupported bits per sample: " << params.bits_per_sample(); + TransitionTo(kInError); + return; + } +} + +CrasOutputStream::~CrasOutputStream() { + InternalState current_state = state(); + DCHECK(current_state == kCreated || + current_state == kIsClosed || + current_state == kInError); +} + +bool CrasOutputStream::Open() { + if (!CanTransitionTo(kIsOpened)) { + NOTREACHED() << "Invalid state: " << state(); + return false; + } + + // We do not need to check if the transition was successful because + // CanTransitionTo() was checked above, and it is assumed that this + // object's public API is only called on one thread so the state cannot + // transition out from under us. + TransitionTo(kIsOpened); + + // Create the client and connect to the CRAS server. + int err = cras_client_create(&client_); + if (err < 0) { + LOG(WARNING) << "Couldn't create CRAS client.\n"; + TransitionTo(kInError); + return false; + } + err = cras_client_connect(client_); + if (err) { + LOG(WARNING) << "Couldn't connect CRAS client.\n"; + cras_client_destroy(client_); + TransitionTo(kInError); + return false; + } + // Then start running the client. + err = cras_client_run_thread(client_); + if (err) { + LOG(WARNING) << "Couldn't run CRAS client.\n"; + cras_client_destroy(client_); + TransitionTo(kInError); + return false; + } + + return true; +} + +void CrasOutputStream::Close() { + // Sanity Check that we can transition to closed. + if (TransitionTo(kIsClosed) != kIsClosed) { + NOTREACHED() << "Unable to transition Closed."; + return; + } + + cras_client_stop(client_); + cras_client_destroy(client_); + + // Signal to the manager that we're closed and can be removed. + // Should be last call in the method as it deletes "this". + manager_->ReleaseOutputStream(this); +} + +void CrasOutputStream::Start(AudioSourceCallback* callback) { + CHECK(callback); + source_callback_ = callback; + + // Only start if we can enter the playing state. + if (TransitionTo(kIsPlaying) != kIsPlaying) + return; + + // Prepare |audio_format| and |stream_params| for the stream we + // will create. + cras_audio_format* audio_format = cras_audio_format_create( + pcm_format_, + frame_rate_, + num_channels_); + if (audio_format == NULL) { + LOG(WARNING) << "Error setting up audio parameters."; + TransitionTo(kInError); + callback->OnError(this, -ENOMEM); + return; + } + cras_stream_params* stream_params = cras_client_stream_params_create( + CRAS_STREAM_OUTPUT, + samples_per_packet_ * 2, // Total latency. + samples_per_packet_ / 2, // Call back when this many left. + samples_per_packet_, // Call back with at least this much space. + CRAS_STREAM_TYPE_DEFAULT, + 0, + this, + CrasOutputStream::PutSamples, + CrasOutputStream::StreamError, + audio_format); + if (stream_params == NULL) { + LOG(WARNING) << "Error setting up stream parameters."; + TransitionTo(kInError); + callback->OnError(this, -ENOMEM); + cras_audio_format_destroy(audio_format); + return; + } + + // Adding the stream will start the audio callbacks requesting data. + int err = cras_client_add_stream(client_, &stream_id_, stream_params); + if (err < 0) { + LOG(WARNING) << "Failed to add the stream"; + TransitionTo(kInError); + callback->OnError(this, err); + cras_audio_format_destroy(audio_format); + cras_client_stream_params_destroy(stream_params); + return; + } + + // Set initial volume. + cras_client_set_stream_volume(client_, stream_id_, volume_); + // Done with config params. + cras_audio_format_destroy(audio_format); + cras_client_stream_params_destroy(stream_params); +} + +void CrasOutputStream::Stop() { + if (!client_) + return; + // Removing the stream from the client stops audio. + cras_client_rm_stream(client_, stream_id_); + TransitionTo(kIsStopped); +} + +void CrasOutputStream::SetVolume(double volume) { + if (!client_) + return; + volume_ = static_cast<float>(volume); + cras_client_set_stream_volume(client_, stream_id_, volume_); +} + +void CrasOutputStream::GetVolume(double* volume) { + *volume = volume_; +} + +// Static callback asking for samples. +int CrasOutputStream::PutSamples(cras_client* client, + cras_stream_id_t stream_id, + uint8* samples, + size_t frames, + const timespec* sample_ts, + void* arg) { + CrasOutputStream* me = static_cast<CrasOutputStream*>(arg); + return me->Render(frames, samples, sample_ts); +} + +// Static callback for stream errors. +int CrasOutputStream::StreamError(cras_client* client, + cras_stream_id_t stream_id, + int err, + void* arg) { + CrasOutputStream* me = static_cast<CrasOutputStream*>(arg); + me->NotifyStreamError(err); + return 0; +} + +// Note this is run from a real time thread, so don't waste cycles here. +uint32 CrasOutputStream::Render(size_t frames, + uint8* buffer, + const timespec* sample_ts) { + uint32 bytes_per_frame = cras_client_bytes_per_frame(client_, stream_id_); + timespec latency_ts = {0, 0}; + + // Determine latency and pass that on to the source. + cras_client_calc_latency(client_, stream_id_, sample_ts, &latency_ts); + uint32 latency_usec = (latency_ts.tv_sec * 1000000) + + latency_ts.tv_nsec / 1000; + + uint32 frames_latency = latency_usec * frame_rate_ / 1000000; + uint32 bytes_latency = frames_latency * bytes_per_frame; + uint32 rendered = source_callback_->OnMoreData(this, buffer, + frames * bytes_per_frame, + AudioBuffersState(0, bytes_latency)); + return rendered / bytes_per_frame; +} + +void CrasOutputStream::NotifyStreamError(int err) { + // This will remove the stream from the client. + if (state_ == kIsClosed || state_ == kInError) + return; // Don't care about error if we aren't using it. + TransitionTo(kInError); + if (source_callback_) + source_callback_->OnError(this, err); +} + +bool CrasOutputStream::CanTransitionTo(InternalState to) { + switch (state_) { + case kCreated: + return to == kIsOpened || to == kIsClosed || to == kInError; + + case kIsOpened: + return to == kIsPlaying || to == kIsStopped || + to == kIsClosed || to == kInError; + + case kIsPlaying: + return to == kIsPlaying || to == kIsStopped || + to == kIsClosed || to == kInError; + + case kIsStopped: + return to == kIsPlaying || to == kIsStopped || + to == kIsClosed || to == kInError; + + case kInError: + return to == kIsClosed || to == kInError; + + case kIsClosed: + return false; + } + return false; +} + +CrasOutputStream::InternalState +CrasOutputStream::TransitionTo(InternalState to) { + if (!CanTransitionTo(to)) { + state_ = kInError; + } else { + state_ = to; + } + return state_; +} + +CrasOutputStream::InternalState CrasOutputStream::state() { + return state_; +} diff --git a/media/audio/linux/cras_output.h b/media/audio/linux/cras_output.h new file mode 100644 index 0000000..d5de15a --- /dev/null +++ b/media/audio/linux/cras_output.h @@ -0,0 +1,117 @@ +// Copyright (c) 2012 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. +// +// Creates an output stream based on the cras (ChromeOS audio server) interface. +// +// CrasOutputStream object is *not* thread-safe and should only be used +// from the audio thread. + +#ifndef MEDIA_AUDIO_LINUX_CRAS_OUTPUT_H_ +#define MEDIA_AUDIO_LINUX_CRAS_OUTPUT_H_ + +#include <alsa/asoundlib.h> +#include <cras_client.h> +#include <ostream> + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "media/audio/audio_io.h" + +class AudioManagerLinux; +class AudioParameters; + +// Implementation of AudioOuputStream for Chrome OS using the Chrome OS audio +// server. +class MEDIA_EXPORT CrasOutputStream : public AudioOutputStream { + public: + // The ctor takes all the usual parameters, plus |manager| which is the + // audio manager who is creating this object. + CrasOutputStream(const AudioParameters& params, AudioManagerLinux* manager); + + // The dtor is typically called by the AudioManager only and it is usually + // triggered by calling AudioOutputStream::Close(). + virtual ~CrasOutputStream(); + + // Implementation of AudioOutputStream. + virtual bool Open() OVERRIDE; + virtual void Close() OVERRIDE; + virtual void Start(AudioSourceCallback* callback) OVERRIDE; + virtual void Stop() OVERRIDE; + virtual void SetVolume(double volume) OVERRIDE; + virtual void GetVolume(double* volume) OVERRIDE; + + // Flags indicating the state of the stream. + enum InternalState { + kInError = 0, + kCreated, + kIsOpened, + kIsPlaying, + kIsStopped, + kIsClosed + }; + friend std::ostream& operator<<(std::ostream& os, InternalState); + // Reports the current state for unit testing. + InternalState state(); + + private: + // Handles requests to put samples in the provided buffer. This will be + // called by the audio server when it needs more data. + static int PutSamples(cras_client* client, + cras_stream_id_t stream_id, + uint8* samples, + size_t frames, + const timespec* sample_ts, + void* arg); + + // Handles notificaiton that there was an error with the playback stream. + static int StreamError(cras_client* client, + cras_stream_id_t stream_id, + int err, + void* arg); + + // Actually fills buffer with audio data. Called from PutSamples(). + uint32 Render(size_t frames, uint8* buffer, const timespec* sample_ts); + + // Deals with an error that occured in the stream. Called from StreamError(). + void NotifyStreamError(int err); + + // Functions to safeguard state transitions. All changes to the object state + // should go through these functions. + bool CanTransitionTo(InternalState to); + InternalState TransitionTo(InternalState to); + + // The client used to communicate with the audio server. + cras_client* client_; + + // ID of the playing stream. + cras_stream_id_t stream_id_; + + // Packet size in samples. + uint32 samples_per_packet_; + + // Rate in Hz. + size_t frame_rate_; + + // Number of channels. + size_t num_channels_; + + // PCM format for Alsa. + const snd_pcm_format_t pcm_format_; + + // Current state. + InternalState state_; + + // Volume level from 0.0 to 1.0. + float volume_; + + // Audio manager that created us. Used to report that we've been closed. + AudioManagerLinux* manager_; + + // Callback to get audio samples. + AudioSourceCallback* source_callback_; + + DISALLOW_COPY_AND_ASSIGN(CrasOutputStream); +}; + +#endif // MEDIA_AUDIO_LINUX_CRAS_OUTPUT_H_ diff --git a/media/audio/linux/cras_output_unittest.cc b/media/audio/linux/cras_output_unittest.cc new file mode 100644 index 0000000..7f6e06b --- /dev/null +++ b/media/audio/linux/cras_output_unittest.cc @@ -0,0 +1,216 @@ +// Copyright (c) 2012 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 "media/audio/linux/audio_manager_linux.h" +#include "media/audio/linux/cras_output.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::DoAll; +using testing::Return; +using testing::SetArgumentPointee; +using testing::StrictMock; + +class MockAudioSourceCallback : public AudioOutputStream::AudioSourceCallback { + public: + MOCK_METHOD4(OnMoreData, uint32(AudioOutputStream* stream, + uint8* dest, uint32 max_size, + AudioBuffersState buffers_state)); + MOCK_METHOD2(OnError, void(AudioOutputStream* stream, int code)); +}; + +class MockAudioManagerLinux : public AudioManagerLinux { + public: + MOCK_METHOD0(Init, void()); + MOCK_METHOD0(HasAudioOutputDevices, bool()); + MOCK_METHOD0(HasAudioInputDevices, bool()); + MOCK_METHOD0(MuteAll, void()); + MOCK_METHOD0(UnMuteAll, void()); + MOCK_METHOD1(MakeLinearOutputStream, AudioOutputStream*( + const AudioParameters& params)); + MOCK_METHOD1(MakeLowLatencyOutputStream, AudioOutputStream*( + const AudioParameters& params)); + MOCK_METHOD2(MakeLinearOutputStream, AudioInputStream*( + const AudioParameters& params, const std::string& device_id)); + MOCK_METHOD2(MakeLowLatencyInputStream, AudioInputStream*( + const AudioParameters& params, const std::string& device_id)); + + // We need to override this function in order to skip the checking the number + // of active output streams. It is because the number of active streams + // is managed inside MakeAudioOutputStream, and we don't use + // MakeAudioOutputStream to create the stream in the tests. + virtual void ReleaseOutputStream(AudioOutputStream* stream) OVERRIDE { + DCHECK(stream); + delete stream; + } + + // We don't mock this method since all tests will do the same thing + // and use the current message loop. + virtual scoped_refptr<base::MessageLoopProxy> GetMessageLoop() OVERRIDE { + return MessageLoop::current()->message_loop_proxy(); + } +}; + +class CrasOutputStreamTest : public testing::Test { + protected: + CrasOutputStreamTest() { + mock_manager_.reset(new StrictMock<MockAudioManagerLinux>()); + } + + virtual ~CrasOutputStreamTest() { + } + + CrasOutputStream* CreateStream(ChannelLayout layout) { + return CreateStream(layout, kTestFramesPerPacket); + } + + CrasOutputStream* CreateStream(ChannelLayout layout, + int32 samples_per_packet) { + AudioParameters params(kTestFormat, layout, kTestSampleRate, + kTestBitsPerSample, samples_per_packet); + return new CrasOutputStream(params, + mock_manager_.get()); + } + + MockAudioManagerLinux& mock_manager() { + return *(mock_manager_.get()); + } + + static const ChannelLayout kTestChannelLayout; + static const int kTestSampleRate; + static const int kTestBitsPerSample; + static const int kTestBytesPerFrame; + static const AudioParameters::Format kTestFormat; + static const uint32 kTestFramesPerPacket; + static const uint32 kTestPacketSize; + static struct cras_audio_format* const kFakeAudioFormat; + static struct cras_stream_params* const kFakeStreamParams; + static struct cras_client* const kFakeClient; + + scoped_ptr<StrictMock<MockAudioManagerLinux> > mock_manager_; + + private: + DISALLOW_COPY_AND_ASSIGN(CrasOutputStreamTest); +}; + +const ChannelLayout CrasOutputStreamTest::kTestChannelLayout = + CHANNEL_LAYOUT_STEREO; +const int CrasOutputStreamTest::kTestSampleRate = + AudioParameters::kAudioCDSampleRate; +const int CrasOutputStreamTest::kTestBitsPerSample = 16; +const int CrasOutputStreamTest::kTestBytesPerFrame = + CrasOutputStreamTest::kTestBitsPerSample / 8 * + ChannelLayoutToChannelCount(CrasOutputStreamTest::kTestChannelLayout); +const AudioParameters::Format CrasOutputStreamTest::kTestFormat = + AudioParameters::AUDIO_PCM_LINEAR; +const uint32 CrasOutputStreamTest::kTestFramesPerPacket = 1000; +const uint32 CrasOutputStreamTest::kTestPacketSize = + CrasOutputStreamTest::kTestFramesPerPacket * + CrasOutputStreamTest::kTestBytesPerFrame; +struct cras_audio_format* const CrasOutputStreamTest::kFakeAudioFormat = + reinterpret_cast<struct cras_audio_format*>(1); +struct cras_stream_params* const CrasOutputStreamTest::kFakeStreamParams = + reinterpret_cast<struct cras_stream_params*>(1); +struct cras_client* const CrasOutputStreamTest::kFakeClient = + reinterpret_cast<struct cras_client*>(1); + +TEST_F(CrasOutputStreamTest, ConstructedState) { + // Should support mono. + CrasOutputStream *test_stream = CreateStream(CHANNEL_LAYOUT_MONO); + EXPECT_EQ(CrasOutputStream::kCreated, test_stream->state()); + test_stream->Close(); + + // Should support stereo. + test_stream = CreateStream(CHANNEL_LAYOUT_SURROUND); + EXPECT_EQ(CrasOutputStream::kCreated, test_stream->state()); + test_stream->Close(); + + // Bad bits per sample. + AudioParameters bad_bps_params(kTestFormat, kTestChannelLayout, + kTestSampleRate, kTestBitsPerSample - 1, + kTestFramesPerPacket); + test_stream = new CrasOutputStream(bad_bps_params, mock_manager_.get()); + EXPECT_EQ(CrasOutputStream::kInError, test_stream->state()); + test_stream->Close(); + + // Bad format. + AudioParameters bad_format_params(AudioParameters::AUDIO_LAST_FORMAT, + kTestChannelLayout, kTestSampleRate, + kTestBitsPerSample, kTestFramesPerPacket); + test_stream = new CrasOutputStream(bad_format_params, mock_manager_.get()); + EXPECT_EQ(CrasOutputStream::kInError, test_stream->state()); + test_stream->Close(); + + // Bad sample rate. + AudioParameters bad_rate_params(kTestFormat, kTestChannelLayout, + 0, kTestBitsPerSample, kTestFramesPerPacket); + test_stream = new CrasOutputStream(bad_rate_params, mock_manager_.get()); + EXPECT_EQ(CrasOutputStream::kInError, test_stream->state()); + test_stream->Close(); +} + +TEST_F(CrasOutputStreamTest, OpenClose) { + CrasOutputStream *test_stream = CreateStream(CHANNEL_LAYOUT_MONO); + // Open the stream. + ASSERT_TRUE(test_stream->Open()); + EXPECT_EQ(CrasOutputStream::kIsOpened, test_stream->state()); + + // Close the stream. + test_stream->Close(); +} + +TEST_F(CrasOutputStreamTest, StartFailBeforeOpen) { + CrasOutputStream *test_stream = CreateStream(CHANNEL_LAYOUT_MONO); + MockAudioSourceCallback mock_callback; + + test_stream->Start(&mock_callback); + EXPECT_EQ(CrasOutputStream::kInError, test_stream->state()); +} + +TEST_F(CrasOutputStreamTest, StartStop) { + CrasOutputStream *test_stream = CreateStream(CHANNEL_LAYOUT_MONO); + MockAudioSourceCallback mock_callback; + + // Open the stream. + ASSERT_TRUE(test_stream->Open()); + EXPECT_EQ(CrasOutputStream::kIsOpened, test_stream->state()); + + // Start. + test_stream->Start(&mock_callback); + EXPECT_EQ(CrasOutputStream::kIsPlaying, test_stream->state()); + + // Stop. + test_stream->Stop(); + EXPECT_EQ(CrasOutputStream::kIsStopped, test_stream->state()); + + // Close the stream. + test_stream->Close(); +} + +TEST_F(CrasOutputStreamTest, RenderFrames) { + CrasOutputStream *test_stream = CreateStream(CHANNEL_LAYOUT_MONO); + MockAudioSourceCallback mock_callback; + const uint32 amount_rendered_return = 2048; + + // Open the stream. + ASSERT_TRUE(test_stream->Open()); + EXPECT_EQ(CrasOutputStream::kIsOpened, test_stream->state()); + + // Render Callback. + EXPECT_CALL(mock_callback, OnMoreData(test_stream, _, + kTestFramesPerPacket * kTestBytesPerFrame, _)) + .WillRepeatedly(Return(amount_rendered_return)); + + // Start. + test_stream->Start(&mock_callback); + EXPECT_EQ(CrasOutputStream::kIsPlaying, test_stream->state()); + + // Stop. + test_stream->Stop(); + EXPECT_EQ(CrasOutputStream::kIsStopped, test_stream->state()); + + // Close the stream. + test_stream->Close(); +} |