Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(128)

Unified Diff: media/audio/linux/alsa_output_unittest.cc

Issue 275022: Move Alsa device opening into the audio thread, and add in support for multi-channel audio. (Closed)
Patch Set: Fix up the unittests since we not only downmix for a very small set of channels. Created 11 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « media/audio/linux/alsa_output.cc ('k') | media/audio/linux/alsa_wrapper.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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_);
« no previous file with comments | « media/audio/linux/alsa_output.cc ('k') | media/audio/linux/alsa_wrapper.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698