| Index: media/audio/linux/alsa_output_unittest.cc
|
| diff --git a/media/audio/linux/alsa_output_unittest.cc b/media/audio/linux/alsa_output_unittest.cc
|
| index e20526ef265bc6b32943903095c0d330a9074f88..72c4c90951e0821245e279650c4159bae57c0306 100644
|
| --- a/media/audio/linux/alsa_output_unittest.cc
|
| +++ b/media/audio/linux/alsa_output_unittest.cc
|
| @@ -3,6 +3,7 @@
|
| // found in the LICENSE file.
|
|
|
| #include "base/logging.h"
|
| +#include "base/string_util.h"
|
| #include "media/audio/linux/alsa_output.h"
|
| #include "media/audio/linux/alsa_wrapper.h"
|
| #include "media/audio/linux/audio_manager_linux.h"
|
| @@ -12,13 +13,24 @@
|
| using testing::_;
|
| using testing::DoAll;
|
| using testing::Eq;
|
| +using testing::InSequence;
|
| +using testing::Invoke;
|
| +using testing::Mock;
|
| +using testing::MockFunction;
|
| using testing::Return;
|
| using testing::SetArgumentPointee;
|
| using testing::StrictMock;
|
| using testing::StrEq;
|
| +using testing::Unused;
|
|
|
| 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));
|
| @@ -64,15 +76,7 @@ class AlsaPcmOutputStreamTest : public testing::Test {
|
| protected:
|
| AlsaPcmOutputStreamTest()
|
| : packet_(kTestPacketSize + 1) {
|
| - test_stream_ = new AlsaPcmOutputStream(kTestDeviceName,
|
| - kTestFormat,
|
| - kTestChannels,
|
| - kTestSampleRate,
|
| - kTestBitsPerSample,
|
| - &mock_alsa_wrapper_,
|
| - &mock_manager_,
|
| - &message_loop_);
|
| -
|
| + test_stream_ = CreateStreamWithChannels(kTestChannels);
|
| packet_.size = kTestPacketSize;
|
| }
|
|
|
| @@ -80,6 +84,27 @@ class AlsaPcmOutputStreamTest : public testing::Test {
|
| test_stream_ = NULL;
|
| }
|
|
|
| + AlsaPcmOutputStream* CreateStreamWithChannels(int channels) {
|
| + return new AlsaPcmOutputStream(kTestDeviceName,
|
| + kTestFormat,
|
| + channels,
|
| + kTestSampleRate,
|
| + kTestBitsPerSample,
|
| + &mock_alsa_wrapper_,
|
| + &mock_manager_,
|
| + &message_loop_);
|
| + }
|
| +
|
| + // 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");
|
| + }
|
| +
|
| static const int kTestChannels;
|
| static const int kTestSampleRate;
|
| static const int kTestBitsPerSample;
|
| @@ -92,6 +117,15 @@ class AlsaPcmOutputStreamTest : public testing::Test {
|
| 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_;
|
| StrictMock<MockAudioManagerLinux> mock_manager_;
|
| MessageLoop message_loop_;
|
| @@ -113,7 +147,7 @@ const AudioManager::Format AlsaPcmOutputStreamTest::kTestFormat =
|
| AudioManager::AUDIO_PCM_LINEAR;
|
| const char AlsaPcmOutputStreamTest::kTestDeviceName[] = "TestDevice";
|
| const char AlsaPcmOutputStreamTest::kDummyMessage[] = "dummy";
|
| -const int AlsaPcmOutputStreamTest::kTestFramesPerPacket = 100;
|
| +const int AlsaPcmOutputStreamTest::kTestFramesPerPacket = 1000;
|
| const size_t AlsaPcmOutputStreamTest::kTestPacketSize =
|
| AlsaPcmOutputStreamTest::kTestFramesPerPacket *
|
| AlsaPcmOutputStreamTest::kTestBytesPerFrame;
|
| @@ -121,32 +155,28 @@ 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 };
|
| +
|
| TEST_F(AlsaPcmOutputStreamTest, ConstructedState) {
|
| EXPECT_EQ(AlsaPcmOutputStream::kCreated,
|
| test_stream_->shared_data_.state());
|
|
|
| // Should support mono.
|
| - test_stream_ = new AlsaPcmOutputStream(kTestDeviceName,
|
| - kTestFormat,
|
| - 1, // Channels.
|
| - kTestSampleRate,
|
| - kTestBitsPerSample,
|
| - &mock_alsa_wrapper_,
|
| - &mock_manager_,
|
| - &message_loop_);
|
| + test_stream_ = CreateStreamWithChannels(1);
|
| EXPECT_EQ(AlsaPcmOutputStream::kCreated,
|
| test_stream_->shared_data_.state());
|
|
|
| - // Should not support multi-channel.
|
| - test_stream_ = new AlsaPcmOutputStream(kTestDeviceName,
|
| - kTestFormat,
|
| - 3, // Channels.
|
| - kTestSampleRate,
|
| - kTestBitsPerSample,
|
| - &mock_alsa_wrapper_,
|
| - &mock_manager_,
|
| - &message_loop_);
|
| - EXPECT_EQ(AlsaPcmOutputStream::kInError,
|
| + // Should support multi-channel.
|
| + test_stream_ = CreateStreamWithChannels(3);
|
| + EXPECT_EQ(AlsaPcmOutputStream::kCreated,
|
| test_stream_->shared_data_.state());
|
|
|
| // Bad bits per sample.
|
| @@ -174,6 +204,68 @@ TEST_F(AlsaPcmOutputStreamTest, ConstructedState) {
|
| test_stream_->shared_data_.state());
|
| }
|
|
|
| +TEST_F(AlsaPcmOutputStreamTest, LatencyFloor) {
|
| + const double kMicrosPerFrame =
|
| + static_cast<double>(1000000) / kTestSampleRate;
|
| + const double kPacketFramesInMinLatency =
|
| + AlsaPcmOutputStream::kMinLatencyMicros / kMicrosPerFrame / 2.0;
|
| + const int kMinLatencyPacketSize =
|
| + static_cast<int>(kPacketFramesInMinLatency * kTestBytesPerFrame);
|
| +
|
| + // 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));
|
| +
|
| + ASSERT_TRUE(test_stream_->Open(kMinLatencyPacketSize));
|
| + message_loop_.RunAllPending();
|
| +
|
| + // Now close it and test that everything was released.
|
| + EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle)) .WillOnce(Return(0));
|
| + EXPECT_CALL(mock_manager_, ReleaseStream(test_stream_.get()));
|
| + test_stream_->Close();
|
| + message_loop_.RunAllPending();
|
| +
|
| + Mock::VerifyAndClear(&mock_alsa_wrapper_);
|
| + Mock::VerifyAndClear(&mock_manager_);
|
| +
|
| + // Test that having more packets ends up with a latency based on packet size.
|
| + const int kOverMinLatencyPacketSize =
|
| + (kPacketFramesInMinLatency + 1) * kTestBytesPerFrame;
|
| + int64 expected_micros = 2 *
|
| + AlsaPcmOutputStream::FramesToMicros(
|
| + kOverMinLatencyPacketSize / kTestBytesPerFrame,
|
| + kTestSampleRate);
|
| +
|
| + // Recreate the stream to reset the state.
|
| + test_stream_ = CreateStreamWithChannels(kTestChannels);
|
| +
|
| + EXPECT_CALL(mock_alsa_wrapper_, PcmOpen(_, _, _, _))
|
| + .WillOnce(DoAll(SetArgumentPointee<0>(kFakeHandle), Return(0)));
|
| + EXPECT_CALL(mock_alsa_wrapper_,
|
| + PcmSetParams(_, _, _, _, _, _, expected_micros))
|
| + .WillOnce(Return(0));
|
| +
|
| + ASSERT_TRUE(test_stream_->Open(kOverMinLatencyPacketSize));
|
| + message_loop_.RunAllPending();
|
| +
|
| + // Now close it and test that everything was released.
|
| + EXPECT_CALL(mock_alsa_wrapper_, PcmClose(kFakeHandle))
|
| + .WillOnce(Return(0));
|
| + EXPECT_CALL(mock_manager_, ReleaseStream(test_stream_.get()));
|
| + test_stream_->Close();
|
| + message_loop_.RunAllPending();
|
| +
|
| + Mock::VerifyAndClear(&mock_alsa_wrapper_);
|
| + Mock::VerifyAndClear(&mock_manager_);
|
| +}
|
| +
|
| TEST_F(AlsaPcmOutputStreamTest, OpenClose) {
|
| int64 expected_micros = 2 *
|
| AlsaPcmOutputStream::FramesToMicros(kTestPacketSize / kTestBytesPerFrame,
|
| @@ -189,7 +281,7 @@ TEST_F(AlsaPcmOutputStreamTest, OpenClose) {
|
| Return(0)));
|
| EXPECT_CALL(mock_alsa_wrapper_,
|
| PcmSetParams(kFakeHandle,
|
| - SND_PCM_FORMAT_S8,
|
| + SND_PCM_FORMAT_U8,
|
| SND_PCM_ACCESS_RW_INTERLEAVED,
|
| kTestChannels,
|
| kTestSampleRate,
|
| @@ -226,11 +318,24 @@ TEST_F(AlsaPcmOutputStreamTest, PcmOpenFailed) {
|
| 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.
|
| - ASSERT_FALSE(test_stream_->Open(kTestPacketSize));
|
| - EXPECT_EQ(AlsaPcmOutputStream::kCreated,
|
| + // Open still succeeds since PcmOpen is delegated to another thread.
|
| + ASSERT_TRUE(test_stream_->Open(kTestPacketSize));
|
| + ASSERT_EQ(AlsaPcmOutputStream::kIsOpened,
|
| test_stream_->shared_data_.state());
|
| + ASSERT_FALSE(test_stream_->stop_stream_);
|
| + message_loop_.RunAllPending();
|
| +
|
| + // Ensure internal state is set for a no-op stream if PcmOpen() failes.
|
| + EXPECT_EQ(AlsaPcmOutputStream::kIsOpened,
|
| + test_stream_->shared_data_.state());
|
| + EXPECT_TRUE(test_stream_->stop_stream_);
|
| + EXPECT_TRUE(test_stream_->playback_handle_ == NULL);
|
| + EXPECT_FALSE(test_stream_->packet_.get());
|
| +
|
| + // Close the stream since we opened it to make destruction happy.
|
| + EXPECT_CALL(mock_manager_, ReleaseStream(test_stream_.get()));
|
| + test_stream_->Close();
|
| + message_loop_.RunAllPending();
|
| }
|
|
|
| TEST_F(AlsaPcmOutputStreamTest, PcmSetParamsFailed) {
|
| @@ -246,9 +351,23 @@ TEST_F(AlsaPcmOutputStreamTest, PcmSetParamsFailed) {
|
|
|
| // If open fails, the stream stays in kCreated because it has effectively had
|
| // no changes.
|
| - ASSERT_FALSE(test_stream_->Open(kTestPacketSize));
|
| - EXPECT_EQ(AlsaPcmOutputStream::kCreated,
|
| + ASSERT_TRUE(test_stream_->Open(kTestPacketSize));
|
| + EXPECT_EQ(AlsaPcmOutputStream::kIsOpened,
|
| + test_stream_->shared_data_.state());
|
| + ASSERT_FALSE(test_stream_->stop_stream_);
|
| + message_loop_.RunAllPending();
|
| +
|
| + // Ensure internal state is set for a no-op stream if PcmSetParams() failes.
|
| + EXPECT_EQ(AlsaPcmOutputStream::kIsOpened,
|
| test_stream_->shared_data_.state());
|
| + EXPECT_TRUE(test_stream_->stop_stream_);
|
| + EXPECT_TRUE(test_stream_->playback_handle_ == NULL);
|
| + EXPECT_FALSE(test_stream_->packet_.get());
|
| +
|
| + // Close the stream since we opened it to make destruction happy.
|
| + EXPECT_CALL(mock_manager_, ReleaseStream(test_stream_.get()));
|
| + test_stream_->Close();
|
| + message_loop_.RunAllPending();
|
| }
|
|
|
| TEST_F(AlsaPcmOutputStreamTest, StartStop) {
|
| @@ -386,6 +505,127 @@ TEST_F(AlsaPcmOutputStreamTest, BufferPacket_UnfinishedPacket) {
|
| EXPECT_EQ(kTestPacketSize, packet_.size);
|
| }
|
|
|
| +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, false,
|
| + false, false, false, false };
|
| +
|
| + for (int i = 1; i <= 9; ++i) {
|
| + SCOPED_TRACE(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));
|
| +
|
| +
|
| + test_stream_ = CreateStreamWithChannels(i);
|
| + EXPECT_TRUE(test_stream_->AutoSelectDevice(i));
|
| + EXPECT_EQ(kExpectedDownmix[i], test_stream_->should_downmix_);
|
| +
|
| + Mock::VerifyAndClearExpectations(&mock_alsa_wrapper_);
|
| + Mock::VerifyAndClearExpectations(&mock_manager_);
|
| + }
|
| +}
|
| +
|
| +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));
|
| +
|
| + test_stream_ = CreateStreamWithChannels(5);
|
| + EXPECT_FALSE(test_stream_->AutoSelectDevice(5));
|
| +}
|
| +
|
| +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));
|
| +
|
| + test_stream_ = CreateStreamWithChannels(5);
|
| + EXPECT_TRUE(test_stream_->AutoSelectDevice(5));
|
| + EXPECT_TRUE(test_stream_->should_downmix_);
|
| +}
|
| +
|
| TEST_F(AlsaPcmOutputStreamTest, BufferPacket_StopStream) {
|
| test_stream_->stop_stream_ = true;
|
| test_stream_->BufferPacket(&packet_);
|
|
|