root/media/audio/alsa/alsa_output_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ReleaseOutputStream
  2. GetTaskRunner
  3. CreateStream
  4. CreateStream
  5. EchoHint
  6. OutputHint
  7. InitBuffer
  8. ACTION
  9. TEST_F
  10. TEST_F
  11. TEST_F
  12. TEST_F
  13. TEST_F
  14. TEST_F
  15. TEST_F
  16. TEST_F
  17. TEST_F
  18. TEST_F
  19. TEST_F
  20. TEST_F
  21. TEST_F
  22. TEST_F
  23. TEST_F
  24. TEST_F
  25. TEST_F
  26. TEST_F
  27. TEST_F
  28. TEST_F

// Copyright 2013 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 "base/strings/stringprintf.h"
#include "media/audio/alsa/alsa_output.h"
#include "media/audio/alsa/alsa_wrapper.h"
#include "media/audio/alsa/audio_manager_alsa.h"
#include "media/audio/fake_audio_log_factory.h"
#include "media/audio/mock_audio_source_callback.h"
#include "media/base/data_buffer.h"
#include "media/base/seekable_buffer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::_;
using testing::AllOf;
using testing::AtLeast;
using testing::DoAll;
using testing::Field;
using testing::InSequence;
using testing::Invoke;
using testing::InvokeWithoutArgs;
using testing::Mock;
using testing::MockFunction;
using testing::Return;
using testing::SetArgumentPointee;
using testing::StrictMock;
using testing::StrEq;
using testing::Unused;

namespace media {

class MockAlsaWrapper : public AlsaWrapper {
 public:
  MOCK_METHOD3(DeviceNameHint, int(int card,
                                   const char* iface,
                                   void*** hints));
  MOCK_METHOD2(DeviceNameGetHint, char*(const void* hint, const char* id));
  MOCK_METHOD1(DeviceNameFreeHint, int(void** hints));

  MOCK_METHOD4(PcmOpen, int(snd_pcm_t** handle, const char* name,
                            snd_pcm_stream_t stream, int mode));
  MOCK_METHOD1(PcmClose, int(snd_pcm_t* handle));
  MOCK_METHOD1(PcmPrepare, int(snd_pcm_t* handle));
  MOCK_METHOD1(PcmDrop, int(snd_pcm_t* handle));
  MOCK_METHOD2(PcmDelay, int(snd_pcm_t* handle, snd_pcm_sframes_t* delay));
  MOCK_METHOD3(PcmWritei, snd_pcm_sframes_t(snd_pcm_t* handle,
                                            const void* buffer,
                                            snd_pcm_uframes_t size));
  MOCK_METHOD3(PcmReadi, snd_pcm_sframes_t(snd_pcm_t* handle,
                                           void* buffer,
                                           snd_pcm_uframes_t size));
  MOCK_METHOD3(PcmRecover, int(snd_pcm_t* handle, int err, int silent));
  MOCK_METHOD7(PcmSetParams, int(snd_pcm_t* handle, snd_pcm_format_t format,
                                 snd_pcm_access_t access, unsigned int channels,
                                 unsigned int rate, int soft_resample,
                                 unsigned int latency));
  MOCK_METHOD3(PcmGetParams, int(snd_pcm_t* handle,
                                 snd_pcm_uframes_t* buffer_size,
                                 snd_pcm_uframes_t* period_size));
  MOCK_METHOD1(PcmName, const char*(snd_pcm_t* handle));
  MOCK_METHOD1(PcmAvailUpdate, snd_pcm_sframes_t(snd_pcm_t* handle));
  MOCK_METHOD1(PcmState, snd_pcm_state_t(snd_pcm_t* handle));
  MOCK_METHOD1(PcmStart, int(snd_pcm_t* handle));

  MOCK_METHOD1(StrError, const char*(int errnum));
};

class MockAudioManagerAlsa : public AudioManagerAlsa {
 public:
  MockAudioManagerAlsa() : AudioManagerAlsa(&fake_audio_log_factory_) {}
  MOCK_METHOD0(Init, void());
  MOCK_METHOD0(HasAudioOutputDevices, bool());
  MOCK_METHOD0(HasAudioInputDevices, bool());
  MOCK_METHOD1(MakeLinearOutputStream, AudioOutputStream*(
      const AudioParameters& params));
  MOCK_METHOD2(MakeLowLatencyOutputStream, AudioOutputStream*(
      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 task runner.
  virtual scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner() OVERRIDE {
    return base::MessageLoop::current()->message_loop_proxy();
  }

 private:
  FakeAudioLogFactory fake_audio_log_factory_;
};

class AlsaPcmOutputStreamTest : public testing::Test {
 protected:
  AlsaPcmOutputStreamTest() {
    mock_manager_.reset(new StrictMock<MockAudioManagerAlsa>());
  }

  virtual ~AlsaPcmOutputStreamTest() {
  }

  AlsaPcmOutputStream* CreateStream(ChannelLayout layout) {
    return CreateStream(layout, kTestFramesPerPacket);
  }

  AlsaPcmOutputStream* CreateStream(ChannelLayout layout,
                                    int32 samples_per_packet) {
    AudioParameters params(kTestFormat, layout, kTestSampleRate,
                           kTestBitsPerSample, samples_per_packet);
    return new AlsaPcmOutputStream(kTestDeviceName,
                                   params,
                                   &mock_alsa_wrapper_,
                                   mock_manager_.get());
  }

  // Helper function to malloc the string returned by DeviceNameHint for NAME.
  static char* EchoHint(const void* name, Unused) {
    return strdup(static_cast<const char*>(name));
  }

  // Helper function to malloc the string returned by DeviceNameHint for IOID.
  static char* OutputHint(Unused, Unused) {
    return strdup("Output");
  }

  // Helper function to initialize |test_stream->buffer_|. Must be called
  // in all tests that use buffer_ without opening the stream.
  void InitBuffer(AlsaPcmOutputStream* test_stream) {
    DCHECK(test_stream);
    packet_ = new media::DataBuffer(kTestPacketSize);
    packet_->set_data_size(kTestPacketSize);
    test_stream->buffer_.reset(new media::SeekableBuffer(0, kTestPacketSize));
    test_stream->buffer_->Append(packet_.get());
  }

  static const ChannelLayout kTestChannelLayout;
  static const int kTestSampleRate;
  static const int kTestBitsPerSample;
  static const int kTestBytesPerFrame;
  static const AudioParameters::Format kTestFormat;
  static const char kTestDeviceName[];
  static const char kDummyMessage[];
  static const uint32 kTestFramesPerPacket;
  static const int kTestPacketSize;
  static const int kTestFailedErrno;
  static snd_pcm_t* const kFakeHandle;

  // Used to simulate DeviceNameHint.
  static char kSurround40[];
  static char kSurround41[];
  static char kSurround50[];
  static char kSurround51[];
  static char kSurround70[];
  static char kSurround71[];
  static void* kFakeHints[];

  StrictMock<MockAlsaWrapper> mock_alsa_wrapper_;
  scoped_ptr<StrictMock<MockAudioManagerAlsa> > mock_manager_;
  base::MessageLoop message_loop_;
  scoped_refptr<media::DataBuffer> packet_;

 private:
  DISALLOW_COPY_AND_ASSIGN(AlsaPcmOutputStreamTest);
};

const ChannelLayout AlsaPcmOutputStreamTest::kTestChannelLayout =
    CHANNEL_LAYOUT_STEREO;
const int AlsaPcmOutputStreamTest::kTestSampleRate =
    AudioParameters::kAudioCDSampleRate;
const int AlsaPcmOutputStreamTest::kTestBitsPerSample = 8;
const int AlsaPcmOutputStreamTest::kTestBytesPerFrame =
    AlsaPcmOutputStreamTest::kTestBitsPerSample / 8 *
    ChannelLayoutToChannelCount(AlsaPcmOutputStreamTest::kTestChannelLayout);
const AudioParameters::Format AlsaPcmOutputStreamTest::kTestFormat =
    AudioParameters::AUDIO_PCM_LINEAR;
const char AlsaPcmOutputStreamTest::kTestDeviceName[] = "TestDevice";
const char AlsaPcmOutputStreamTest::kDummyMessage[] = "dummy";
const uint32 AlsaPcmOutputStreamTest::kTestFramesPerPacket = 1000;
const int AlsaPcmOutputStreamTest::kTestPacketSize =
    AlsaPcmOutputStreamTest::kTestFramesPerPacket *
    AlsaPcmOutputStreamTest::kTestBytesPerFrame;
const int AlsaPcmOutputStreamTest::kTestFailedErrno = -EACCES;
snd_pcm_t* const AlsaPcmOutputStreamTest::kFakeHandle =
    reinterpret_cast<snd_pcm_t*>(1);

char AlsaPcmOutputStreamTest::kSurround40[] = "surround40:CARD=foo,DEV=0";
char AlsaPcmOutputStreamTest::kSurround41[] = "surround41:CARD=foo,DEV=0";
char AlsaPcmOutputStreamTest::kSurround50[] = "surround50:CARD=foo,DEV=0";
char AlsaPcmOutputStreamTest::kSurround51[] = "surround51:CARD=foo,DEV=0";
char AlsaPcmOutputStreamTest::kSurround70[] = "surround70:CARD=foo,DEV=0";
char AlsaPcmOutputStreamTest::kSurround71[] = "surround71:CARD=foo,DEV=0";
void* AlsaPcmOutputStreamTest::kFakeHints[] = {
    kSurround40, kSurround41, kSurround50, kSurround51,
    kSurround70, kSurround71, NULL };

// Custom action to clear a memory buffer.
ACTION(ClearBuffer) {
  arg0->Zero();
}

TEST_F(AlsaPcmOutputStreamTest, ConstructedState) {
  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state());
  test_stream->Close();

  // Should support mono.
  test_stream = CreateStream(CHANNEL_LAYOUT_MONO);
  EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state());
  test_stream->Close();

  // Should support multi-channel.
  test_stream = CreateStream(CHANNEL_LAYOUT_SURROUND);
  EXPECT_EQ(AlsaPcmOutputStream::kCreated, test_stream->state());
  test_stream->Close();

  // Bad bits per sample.
  AudioParameters bad_bps_params(kTestFormat, kTestChannelLayout,
                                 kTestSampleRate, kTestBitsPerSample - 1,
                                 kTestFramesPerPacket);
  test_stream = new AlsaPcmOutputStream(kTestDeviceName,
                                        bad_bps_params,
                                        &mock_alsa_wrapper_,
                                        mock_manager_.get());
  EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state());
  test_stream->Close();

  // Bad format.
  AudioParameters bad_format_params(
      AudioParameters::AUDIO_LAST_FORMAT, kTestChannelLayout, kTestSampleRate,
      kTestBitsPerSample, kTestFramesPerPacket);
  test_stream = new AlsaPcmOutputStream(kTestDeviceName,
                                        bad_format_params,
                                        &mock_alsa_wrapper_,
                                        mock_manager_.get());
  EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state());
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) {
  const double kMicrosPerFrame =
      static_cast<double>(1000000) / kTestSampleRate;
  const double kPacketFramesInMinLatency =
      AlsaPcmOutputStream::kMinLatencyMicros / kMicrosPerFrame / 2.0;

  // Test that packets which would cause a latency under less than
  // AlsaPcmOutputStream::kMinLatencyMicros will get clipped to
  // AlsaPcmOutputStream::kMinLatencyMicros,
  EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
      .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
                      Return(0)));
  EXPECT_CALL(mock_alsa_wrapper_,
              PcmSetParams(_, _, _, _, _, _,
                           AlsaPcmOutputStream::kMinLatencyMicros))
      .WillOnce(Return(0));
  EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
      .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
                      SetArgumentPointee<2>(kTestFramesPerPacket / 2),
                      Return(0)));

  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout,
                                                  kPacketFramesInMinLatency);
  ASSERT_TRUE(test_stream->Open());

  // Now close it and test that everything was released.
  EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)).WillOnce(Return(0));
  EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
      .WillOnce(Return(kTestDeviceName));
  test_stream->Close();

  Mock::VerifyAndClear(&mock_alsa_wrapper_);
  Mock::VerifyAndClear(mock_manager_.get());

  // Test that having more packets ends up with a latency based on packet size.
  const int kOverMinLatencyPacketSize = kPacketFramesInMinLatency + 1;
  int64 expected_micros = AlsaPcmOutputStream::FramesToTimeDelta(
      kOverMinLatencyPacketSize * 2, kTestSampleRate).InMicroseconds();

  EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
      .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0)));
  EXPECT_CALL(mock_alsa_wrapper_,
              PcmSetParams(_, _, _, _, _, _, expected_micros))
      .WillOnce(Return(0));
  EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
      .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
                      SetArgumentPointee<2>(kTestFramesPerPacket / 2),
                      Return(0)));

  test_stream = CreateStream(kTestChannelLayout,
                             kOverMinLatencyPacketSize);
  ASSERT_TRUE(test_stream->Open());

  // Now close it and test that everything was released.
  EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
      .WillOnce(Return(0));
  EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
      .WillOnce(Return(kTestDeviceName));
  test_stream->Close();

  Mock::VerifyAndClear(&mock_alsa_wrapper_);
  Mock::VerifyAndClear(mock_manager_.get());
}

TEST_F(AlsaPcmOutputStreamTest, OpenClose) {
  int64 expected_micros = AlsaPcmOutputStream::FramesToTimeDelta(
      2 * kTestFramesPerPacket, kTestSampleRate).InMicroseconds();

  // Open() call opens the playback device, sets the parameters, posts a task
  // with the resulting configuration data, and transitions the object state to
  // kIsOpened.
  EXPECT_CALL(mock_alsa_wrapper_,
              PcmOpen(_, StrEq(kTestDeviceName),
                      SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK))
      .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
                      Return(0)));
  EXPECT_CALL(mock_alsa_wrapper_,
              PcmSetParams(kFakeHandle,
                           SND_PCM_FORMAT_U8,
                           SND_PCM_ACCESS_RW_INTERLEAVED,
                           ChannelLayoutToChannelCount(kTestChannelLayout),
                           kTestSampleRate,
                           1,
                           expected_micros))
      .WillOnce(Return(0));
  EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(kFakeHandle, _, _))
      .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
                      SetArgumentPointee<2>(kTestFramesPerPacket / 2),
                      Return(0)));

  // Open the stream.
  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  ASSERT_TRUE(test_stream->Open());

  EXPECT_EQ(AlsaPcmOutputStream::kIsOpened, test_stream->state());
  EXPECT_EQ(kFakeHandle, test_stream->playback_handle_);
  EXPECT_EQ(kTestFramesPerPacket, test_stream->frames_per_packet_);
  EXPECT_TRUE(test_stream->buffer_.get());
  EXPECT_FALSE(test_stream->stop_stream_);

  // Now close it and test that everything was released.
  EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
      .WillOnce(Return(0));
  EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
      .WillOnce(Return(kTestDeviceName));
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, PcmOpenFailed) {
  EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
      .WillOnce(Return(kTestFailedErrno));
  EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
      .WillOnce(Return(kDummyMessage));

  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  ASSERT_FALSE(test_stream->Open());
  ASSERT_EQ(AlsaPcmOutputStream::kInError, test_stream->state());

  // Ensure internal state is set for a no-op stream if PcmOpen() failes.
  EXPECT_TRUE(test_stream->stop_stream_);
  EXPECT_TRUE(test_stream->playback_handle_ == NULL);
  EXPECT_FALSE(test_stream->buffer_.get());

  // Close the stream since we opened it to make destruction happy.
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, PcmSetParamsFailed) {
  EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
      .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
                      Return(0)));
  EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
      .WillOnce(Return(kTestFailedErrno));
  EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
      .WillOnce(Return(0));
  EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
      .WillOnce(Return(kTestDeviceName));
  EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
      .WillOnce(Return(kDummyMessage));

  // If open fails, the stream stays in kCreated because it has effectively had
  // no changes.
  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  ASSERT_FALSE(test_stream->Open());
  EXPECT_EQ(AlsaPcmOutputStream::kInError, test_stream->state());

  // Ensure internal state is set for a no-op stream if PcmSetParams() failes.
  EXPECT_TRUE(test_stream->stop_stream_);
  EXPECT_TRUE(test_stream->playback_handle_ == NULL);
  EXPECT_FALSE(test_stream->buffer_.get());

  // Close the stream since we opened it to make destruction happy.
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, StartStop) {
  // Open() call opens the playback device, sets the parameters, posts a task
  // with the resulting configuration data, and transitions the object state to
  // kIsOpened.
  EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
      .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
                      Return(0)));
  EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
      .WillOnce(Return(0));
  EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
      .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
                      SetArgumentPointee<2>(kTestFramesPerPacket / 2),
                      Return(0)));

  // Open the stream.
  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  ASSERT_TRUE(test_stream->Open());

  // Expect Device setup.
  EXPECT_CALL(mock_alsa_wrapper_, PcmDrop(kFakeHandle))
      .WillOnce(Return(0));
  EXPECT_CALL(mock_alsa_wrapper_, PcmPrepare(kFakeHandle))
      .WillOnce(Return(0));

  // Expect the pre-roll.
  MockAudioSourceCallback mock_callback;
  EXPECT_CALL(mock_alsa_wrapper_, PcmState(kFakeHandle))
      .WillRepeatedly(Return(SND_PCM_STATE_RUNNING));
  EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(kFakeHandle, _))
      .WillRepeatedly(DoAll(SetArgumentPointee<1>(0), Return(0)));
  EXPECT_CALL(mock_callback, OnMoreData(_, _))
      .WillRepeatedly(DoAll(ClearBuffer(), Return(kTestFramesPerPacket)));
  EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _))
      .WillRepeatedly(Return(kTestFramesPerPacket));

  // Expect scheduling.
  EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
      .Times(AtLeast(2))
      .WillRepeatedly(Return(kTestFramesPerPacket));

  test_stream->Start(&mock_callback);
  // Start() will issue a WriteTask() directly and then schedule the next one,
  // call Stop() immediately after to ensure we don't run the message loop
  // forever.
  test_stream->Stop();
  message_loop_.RunUntilIdle();

  EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
      .WillOnce(Return(0));
  EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
      .WillOnce(Return(kTestDeviceName));
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, WritePacket_FinishedPacket) {
  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  InitBuffer(test_stream);
  test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
  test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);

  // Nothing should happen.  Don't set any expectations and Our strict mocks
  // should verify most of this.

  // Test empty buffer.
  test_stream->buffer_->Clear();
  test_stream->WritePacket();
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, WritePacket_NormalPacket) {
  // We need to open the stream before writing data to ALSA.
  EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
      .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
                      Return(0)));
  EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
      .WillOnce(Return(0));
  EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
      .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
                      SetArgumentPointee<2>(kTestFramesPerPacket / 2),
                      Return(0)));
  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  ASSERT_TRUE(test_stream->Open());
  InitBuffer(test_stream);
  test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);

  // Write a little less than half the data.
  int written = packet_->data_size() / kTestBytesPerFrame / 2 - 1;
  EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
        .WillOnce(Return(written));
  EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, packet_->data(), _))
      .WillOnce(Return(written));

  test_stream->WritePacket();

  ASSERT_EQ(test_stream->buffer_->forward_bytes(),
            packet_->data_size() - written * kTestBytesPerFrame);

  // Write the rest.
  EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
      .WillOnce(Return(kTestFramesPerPacket - written));
  EXPECT_CALL(mock_alsa_wrapper_,
              PcmWritei(kFakeHandle,
                        packet_->data() + written * kTestBytesPerFrame,
                        _))
      .WillOnce(Return(packet_->data_size() / kTestBytesPerFrame - written));
  test_stream->WritePacket();
  EXPECT_EQ(0, test_stream->buffer_->forward_bytes());

  // Now close it and test that everything was released.
  EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
      .WillOnce(Return(0));
  EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
      .WillOnce(Return(kTestDeviceName));
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, WritePacket_WriteFails) {
  // We need to open the stream before writing data to ALSA.
  EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
      .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle),
                      Return(0)));
  EXPECT_CALL(mock_alsa_wrapper_, PcmSetParams(_, _, _, _, _, _, _))
      .WillOnce(Return(0));
  EXPECT_CALL(mock_alsa_wrapper_, PcmGetParams(_, _, _))
      .WillOnce(DoAll(SetArgumentPointee<1>(kTestFramesPerPacket),
                      SetArgumentPointee<2>(kTestFramesPerPacket / 2),
                      Return(0)));
  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  ASSERT_TRUE(test_stream->Open());
  InitBuffer(test_stream);
  test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);

  // Fail due to a recoverable error and see that PcmRecover code path
  // continues normally.
  EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
      .WillOnce(Return(kTestFramesPerPacket));
  EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _))
      .WillOnce(Return(-EINTR));
  EXPECT_CALL(mock_alsa_wrapper_, PcmRecover(kFakeHandle, _, _))
      .WillOnce(Return(0));

  test_stream->WritePacket();

  ASSERT_EQ(test_stream->buffer_->forward_bytes(), packet_->data_size());

  // Fail the next write, and see that stop_stream_ is set.
  EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(kFakeHandle))
        .WillOnce(Return(kTestFramesPerPacket));
  EXPECT_CALL(mock_alsa_wrapper_, PcmWritei(kFakeHandle, _, _))
      .WillOnce(Return(kTestFailedErrno));
  EXPECT_CALL(mock_alsa_wrapper_, PcmRecover(kFakeHandle, _, _))
      .WillOnce(Return(kTestFailedErrno));
  EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
      .WillOnce(Return(kDummyMessage));
  test_stream->WritePacket();
  EXPECT_EQ(test_stream->buffer_->forward_bytes(), packet_->data_size());
  EXPECT_TRUE(test_stream->stop_stream_);

  // Now close it and test that everything was released.
  EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
      .WillOnce(Return(0));
  EXPECT_CALL(mock_alsa_wrapper_, PcmName(kFakeHandle))
      .WillOnce(Return(kTestDeviceName));
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, WritePacket_StopStream) {
  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  InitBuffer(test_stream);
  test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
  test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);

  // No expectations set on the strict mock because nothing should be called.
  test_stream->stop_stream_ = true;
  test_stream->WritePacket();
  EXPECT_EQ(0, test_stream->buffer_->forward_bytes());
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, BufferPacket) {
  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  InitBuffer(test_stream);
  test_stream->buffer_->Clear();

  MockAudioSourceCallback mock_callback;
  EXPECT_CALL(mock_alsa_wrapper_, PcmState(_))
      .WillOnce(Return(SND_PCM_STATE_RUNNING));
  EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(_, _))
      .WillOnce(DoAll(SetArgumentPointee<1>(1), Return(0)));
  EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
      .WillRepeatedly(Return(0));  // Buffer is full.

  // Return a partially filled packet.
  EXPECT_CALL(mock_callback, OnMoreData(_, _))
      .WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2)));

  bool source_exhausted;
  test_stream->set_source_callback(&mock_callback);
  test_stream->packet_size_ = kTestPacketSize;
  test_stream->BufferPacket(&source_exhausted);

  EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes());
  EXPECT_FALSE(source_exhausted);
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Negative) {
  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  InitBuffer(test_stream);
  test_stream->buffer_->Clear();

  // Simulate where the underrun has occurred right after checking the delay.
  MockAudioSourceCallback mock_callback;
  EXPECT_CALL(mock_alsa_wrapper_, PcmState(_))
      .WillOnce(Return(SND_PCM_STATE_RUNNING));
  EXPECT_CALL(mock_alsa_wrapper_, PcmDelay(_, _))
      .WillOnce(DoAll(SetArgumentPointee<1>(-1), Return(0)));
  EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
      .WillRepeatedly(Return(0));  // Buffer is full.
  EXPECT_CALL(mock_callback, OnMoreData(_, _))
      .WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2)));

  bool source_exhausted;
  test_stream->set_source_callback(&mock_callback);
  test_stream->packet_size_ = kTestPacketSize;
  test_stream->BufferPacket(&source_exhausted);

  EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes());
  EXPECT_FALSE(source_exhausted);
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, BufferPacket_Underrun) {
  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  InitBuffer(test_stream);
  test_stream->buffer_->Clear();

  // If ALSA has underrun then we should assume a delay of zero.
  MockAudioSourceCallback mock_callback;
  EXPECT_CALL(mock_alsa_wrapper_, PcmState(_))
      .WillOnce(Return(SND_PCM_STATE_XRUN));
  EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
      .WillRepeatedly(Return(0));  // Buffer is full.
  EXPECT_CALL(mock_callback,
              OnMoreData(_, AllOf(
                  Field(&AudioBuffersState::pending_bytes, 0),
                  Field(&AudioBuffersState::hardware_delay_bytes, 0))))
      .WillOnce(DoAll(ClearBuffer(), Return(kTestFramesPerPacket / 2)));

  bool source_exhausted;
  test_stream->set_source_callback(&mock_callback);
  test_stream->packet_size_ = kTestPacketSize;
  test_stream->BufferPacket(&source_exhausted);

  EXPECT_EQ(kTestPacketSize / 2, test_stream->buffer_->forward_bytes());
  EXPECT_FALSE(source_exhausted);
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, BufferPacket_FullBuffer) {
  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  InitBuffer(test_stream);
  // No expectations set on the strict mock because nothing should be called.
  bool source_exhausted;
  test_stream->packet_size_ = kTestPacketSize;
  test_stream->BufferPacket(&source_exhausted);
  EXPECT_EQ(kTestPacketSize, test_stream->buffer_->forward_bytes());
  EXPECT_FALSE(source_exhausted);
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_DeviceSelect) {
  // Try channels from 1 -> 9. and see that we get the more specific surroundXX
  // device opened for channels 4-8.  For all other channels, the device should
  // default to |AlsaPcmOutputStream::kDefaultDevice|.  We should also not
  // downmix any channel in this case because downmixing is only defined for
  // channels 4-8, which we are guaranteeing to work.
  //
  // Note that the loop starts at "1", so the first parameter is ignored in
  // these arrays.
  const char* kExpectedDeviceName[] = { NULL,
                                        AlsaPcmOutputStream::kDefaultDevice,
                                        AlsaPcmOutputStream::kDefaultDevice,
                                        AlsaPcmOutputStream::kDefaultDevice,
                                        kSurround40, kSurround50, kSurround51,
                                        kSurround70, kSurround71,
                                        AlsaPcmOutputStream::kDefaultDevice };
  bool kExpectedDownmix[] = { false, false, false, false, false, true,
                              false, false, false, false };
  ChannelLayout kExpectedLayouts[] = { CHANNEL_LAYOUT_NONE,
                                       CHANNEL_LAYOUT_MONO,
                                       CHANNEL_LAYOUT_STEREO,
                                       CHANNEL_LAYOUT_SURROUND,
                                       CHANNEL_LAYOUT_4_0,
                                       CHANNEL_LAYOUT_5_0,
                                       CHANNEL_LAYOUT_5_1,
                                       CHANNEL_LAYOUT_7_0,
                                       CHANNEL_LAYOUT_7_1 };


  for (int i = 1; i < 9; ++i) {
    if (i == 3 || i == 4 || i == 5)  // invalid number of channels
      continue;
    SCOPED_TRACE(base::StringPrintf("Attempting %d Channel", i));

    // Hints will only be grabbed for channel numbers that have non-default
    // devices associated with them.
    if (kExpectedDeviceName[i] != AlsaPcmOutputStream::kDefaultDevice) {
      // The DeviceNameHint and DeviceNameFreeHint need to be paired to avoid a
      // memory leak.
      EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _))
          .WillOnce(DoAll(SetArgumentPointee<2>(&kFakeHints[0]), Return(0)));
      EXPECT_CALL(mock_alsa_wrapper_, DeviceNameFreeHint(&kFakeHints[0]))
          .Times(1);
    }

    EXPECT_CALL(mock_alsa_wrapper_,
                PcmOpen(_, StrEq(kExpectedDeviceName[i]), _, _))
        .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0)));
    EXPECT_CALL(mock_alsa_wrapper_,
                PcmSetParams(kFakeHandle, _, _, i, _, _, _))
        .WillOnce(Return(0));

    // The parameters are specified by ALSA documentation, and are in constants
    // in the implementation files.
    EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("IOID")))
        .WillRepeatedly(Invoke(OutputHint));
    EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("NAME")))
        .WillRepeatedly(Invoke(EchoHint));

    AlsaPcmOutputStream* test_stream = CreateStream(kExpectedLayouts[i]);
    EXPECT_TRUE(test_stream->AutoSelectDevice(i));
    EXPECT_EQ(kExpectedDownmix[i],
              static_cast<bool>(test_stream->channel_mixer_));

    Mock::VerifyAndClearExpectations(&mock_alsa_wrapper_);
    Mock::VerifyAndClearExpectations(mock_manager_.get());
    test_stream->Close();
  }
}

TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_FallbackDevices) {
  using std::string;

  // If there are problems opening a multi-channel device, it the fallbacks
  // operations should be as follows.  Assume the multi-channel device name is
  // surround50:
  //
  //   1) Try open "surround50"
  //   2) Try open "plug:surround50".
  //   3) Try open "default".
  //   4) Try open "plug:default".
  //   5) Give up trying to open.
  //
  const string first_try = kSurround50;
  const string second_try = string(AlsaPcmOutputStream::kPlugPrefix) +
                            kSurround50;
  const string third_try = AlsaPcmOutputStream::kDefaultDevice;
  const string fourth_try = string(AlsaPcmOutputStream::kPlugPrefix) +
                            AlsaPcmOutputStream::kDefaultDevice;

  EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _))
      .WillOnce(DoAll(SetArgumentPointee<2>(&kFakeHints[0]), Return(0)));
  EXPECT_CALL(mock_alsa_wrapper_, DeviceNameFreeHint(&kFakeHints[0]))
      .Times(1);
  EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("IOID")))
      .WillRepeatedly(Invoke(OutputHint));
  EXPECT_CALL(mock_alsa_wrapper_, DeviceNameGetHint(_, StrEq("NAME")))
      .WillRepeatedly(Invoke(EchoHint));
  EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
      .WillRepeatedly(Return(kDummyMessage));

  InSequence s;
  EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(first_try.c_str()), _, _))
      .WillOnce(Return(kTestFailedErrno));
  EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(second_try.c_str()), _, _))
      .WillOnce(Return(kTestFailedErrno));
  EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(third_try.c_str()), _, _))
      .WillOnce(Return(kTestFailedErrno));
  EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, StrEq(fourth_try.c_str()), _, _))
      .WillOnce(Return(kTestFailedErrno));

  AlsaPcmOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_5_0);
  EXPECT_FALSE(test_stream->AutoSelectDevice(5));
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, AutoSelectDevice_HintFail) {
  // Should get |kDefaultDevice|, and force a 2-channel downmix on a failure to
  // enumerate devices.
  EXPECT_CALL(mock_alsa_wrapper_, DeviceNameHint(_, _, _))
      .WillRepeatedly(Return(kTestFailedErrno));
  EXPECT_CALL(mock_alsa_wrapper_,
              PcmOpen(_, StrEq(AlsaPcmOutputStream::kDefaultDevice), _, _))
      .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0)));
  EXPECT_CALL(mock_alsa_wrapper_,
              PcmSetParams(kFakeHandle, _, _, 2, _, _, _))
      .WillOnce(Return(0));
  EXPECT_CALL(mock_alsa_wrapper_, StrError(kTestFailedErrno))
      .WillOnce(Return(kDummyMessage));

  AlsaPcmOutputStream* test_stream = CreateStream(CHANNEL_LAYOUT_5_0);
  EXPECT_TRUE(test_stream->AutoSelectDevice(5));
  EXPECT_TRUE(test_stream->channel_mixer_);
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, BufferPacket_StopStream) {
  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  InitBuffer(test_stream);
  test_stream->stop_stream_ = true;
  bool source_exhausted;
  test_stream->BufferPacket(&source_exhausted);
  EXPECT_EQ(0, test_stream->buffer_->forward_bytes());
  EXPECT_TRUE(source_exhausted);
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite) {
  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
  test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);
  InitBuffer(test_stream);
  DVLOG(1) << test_stream->state();
  EXPECT_CALL(mock_alsa_wrapper_, PcmAvailUpdate(_))
      .WillOnce(Return(10));
  test_stream->ScheduleNextWrite(false);
  DVLOG(1) << test_stream->state();
  // TODO(sergeyu): Figure out how to check that the task has been added to the
  // message loop.

  // Cleanup the message queue. Currently ~MessageQueue() doesn't free pending
  // tasks unless running on valgrind. The code below is needed to keep
  // heapcheck happy.

  test_stream->stop_stream_ = true;
  DVLOG(1) << test_stream->state();
  test_stream->TransitionTo(AlsaPcmOutputStream::kIsClosed);
  DVLOG(1) << test_stream->state();
  test_stream->Close();
}

TEST_F(AlsaPcmOutputStreamTest, ScheduleNextWrite_StopStream) {
  AlsaPcmOutputStream* test_stream = CreateStream(kTestChannelLayout);
  test_stream->TransitionTo(AlsaPcmOutputStream::kIsOpened);
  test_stream->TransitionTo(AlsaPcmOutputStream::kIsPlaying);

  InitBuffer(test_stream);

  test_stream->stop_stream_ = true;
  test_stream->ScheduleNextWrite(true);

  // TODO(ajwong): Find a way to test whether or not another task has been
  // posted so we can verify that the Alsa code will indeed break the task
  // posting loop.

  test_stream->TransitionTo(AlsaPcmOutputStream::kIsClosed);
  test_stream->Close();
}

}  // namespace media

/* [<][>][^][v][top][bottom][index][help] */