summaryrefslogtreecommitdiffstats
path: root/media/audio
diff options
context:
space:
mode:
authordgreid@chromium.org <dgreid@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-27 00:05:12 +0000
committerdgreid@chromium.org <dgreid@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-27 00:05:12 +0000
commitec3d2d8aefc6df5c297fdd04e552c70fadbcbc1d (patch)
treebe2608fc1906a53227ecdaaae86aca131a943934 /media/audio
parentbdc06d5f501ee2252efd40929bbdcd95b4532fcf (diff)
downloadchromium_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.cc11
-rw-r--r--media/audio/linux/cras_output.cc321
-rw-r--r--media/audio/linux/cras_output.h117
-rw-r--r--media/audio/linux/cras_output_unittest.cc216
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();
+}