audio_low_latency_input_mac_unittest.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "base/basictypes.h" 6#include "base/environment.h" 7#include "base/message_loop/message_loop.h" 8#include "base/test/test_timeouts.h" 9#include "base/threading/platform_thread.h" 10#include "media/audio/audio_io.h" 11#include "media/audio/audio_manager_base.h" 12#include "media/audio/mac/audio_low_latency_input_mac.h" 13#include "media/base/seekable_buffer.h" 14#include "testing/gmock/include/gmock/gmock.h" 15#include "testing/gtest/include/gtest/gtest.h" 16 17using ::testing::_; 18using ::testing::AnyNumber; 19using ::testing::AtLeast; 20using ::testing::Ge; 21using ::testing::NotNull; 22 23namespace media { 24 25ACTION_P3(CheckCountAndPostQuitTask, count, limit, loop) { 26 if (++*count >= limit) { 27 loop->PostTask(FROM_HERE, base::MessageLoop::QuitClosure()); 28 } 29} 30 31class MockAudioInputCallback : public AudioInputStream::AudioInputCallback { 32 public: 33 MOCK_METHOD5(OnData, void(AudioInputStream* stream, 34 const uint8* src, uint32 size, 35 uint32 hardware_delay_bytes, double volume)); 36 MOCK_METHOD1(OnError, void(AudioInputStream* stream)); 37}; 38 39// This audio sink implementation should be used for manual tests only since 40// the recorded data is stored on a raw binary data file. 41// The last test (WriteToFileAudioSink) - which is disabled by default - 42// can use this audio sink to store the captured data on a file for offline 43// analysis. 44class WriteToFileAudioSink : public AudioInputStream::AudioInputCallback { 45 public: 46 // Allocate space for ~10 seconds of data @ 48kHz in stereo: 47 // 2 bytes per sample, 2 channels, 10ms @ 48kHz, 10 seconds <=> 1920000 bytes. 48 static const int kMaxBufferSize = 2 * 2 * 480 * 100 * 10; 49 50 explicit WriteToFileAudioSink(const char* file_name) 51 : buffer_(0, kMaxBufferSize), 52 file_(fopen(file_name, "wb")), 53 bytes_to_write_(0) { 54 } 55 56 virtual ~WriteToFileAudioSink() { 57 int bytes_written = 0; 58 while (bytes_written < bytes_to_write_) { 59 const uint8* chunk; 60 int chunk_size; 61 62 // Stop writing if no more data is available. 63 if (!buffer_.GetCurrentChunk(&chunk, &chunk_size)) 64 break; 65 66 // Write recorded data chunk to the file and prepare for next chunk. 67 fwrite(chunk, 1, chunk_size, file_); 68 buffer_.Seek(chunk_size); 69 bytes_written += chunk_size; 70 } 71 fclose(file_); 72 } 73 74 // AudioInputStream::AudioInputCallback implementation. 75 virtual void OnData(AudioInputStream* stream, 76 const uint8* src, uint32 size, 77 uint32 hardware_delay_bytes, double volume) OVERRIDE { 78 // Store data data in a temporary buffer to avoid making blocking 79 // fwrite() calls in the audio callback. The complete buffer will be 80 // written to file in the destructor. 81 if (buffer_.Append(src, size)) { 82 bytes_to_write_ += size; 83 } 84 } 85 86 virtual void OnError(AudioInputStream* stream) OVERRIDE {} 87 88 private: 89 media::SeekableBuffer buffer_; 90 FILE* file_; 91 int bytes_to_write_; 92}; 93 94class MacAudioInputTest : public testing::Test { 95 protected: 96 MacAudioInputTest() : audio_manager_(AudioManager::CreateForTesting()) {} 97 virtual ~MacAudioInputTest() {} 98 99 // Convenience method which ensures that we are not running on the build 100 // bots and that at least one valid input device can be found. 101 bool CanRunAudioTests() { 102 bool has_input = audio_manager_->HasAudioInputDevices(); 103 if (!has_input) 104 LOG(WARNING) << "No input devices detected"; 105 return has_input; 106 } 107 108 // Convenience method which creates a default AudioInputStream object using 109 // a 10ms frame size and a sample rate which is set to the hardware sample 110 // rate. 111 AudioInputStream* CreateDefaultAudioInputStream() { 112 int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate()); 113 int samples_per_packet = fs / 100; 114 AudioInputStream* ais = audio_manager_->MakeAudioInputStream( 115 AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 116 CHANNEL_LAYOUT_STEREO, fs, 16, samples_per_packet), 117 AudioManagerBase::kDefaultDeviceId); 118 EXPECT_TRUE(ais); 119 return ais; 120 } 121 122 // Convenience method which creates an AudioInputStream object with a 123 // specified channel layout. 124 AudioInputStream* CreateAudioInputStream(ChannelLayout channel_layout) { 125 int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate()); 126 int samples_per_packet = fs / 100; 127 AudioInputStream* ais = audio_manager_->MakeAudioInputStream( 128 AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 129 channel_layout, fs, 16, samples_per_packet), 130 AudioManagerBase::kDefaultDeviceId); 131 EXPECT_TRUE(ais); 132 return ais; 133 } 134 135 scoped_ptr<AudioManager> audio_manager_; 136}; 137 138// Test Create(), Close(). 139TEST_F(MacAudioInputTest, AUAudioInputStreamCreateAndClose) { 140 if (!CanRunAudioTests()) 141 return; 142 AudioInputStream* ais = CreateDefaultAudioInputStream(); 143 ais->Close(); 144} 145 146// Test Open(), Close(). 147TEST_F(MacAudioInputTest, AUAudioInputStreamOpenAndClose) { 148 if (!CanRunAudioTests()) 149 return; 150 AudioInputStream* ais = CreateDefaultAudioInputStream(); 151 EXPECT_TRUE(ais->Open()); 152 ais->Close(); 153} 154 155// Test Open(), Start(), Close(). 156TEST_F(MacAudioInputTest, AUAudioInputStreamOpenStartAndClose) { 157 if (!CanRunAudioTests()) 158 return; 159 AudioInputStream* ais = CreateDefaultAudioInputStream(); 160 EXPECT_TRUE(ais->Open()); 161 MockAudioInputCallback sink; 162 ais->Start(&sink); 163 ais->Close(); 164} 165 166// Test Open(), Start(), Stop(), Close(). 167TEST_F(MacAudioInputTest, AUAudioInputStreamOpenStartStopAndClose) { 168 if (!CanRunAudioTests()) 169 return; 170 AudioInputStream* ais = CreateDefaultAudioInputStream(); 171 EXPECT_TRUE(ais->Open()); 172 MockAudioInputCallback sink; 173 ais->Start(&sink); 174 ais->Stop(); 175 ais->Close(); 176} 177 178// Test some additional calling sequences. 179TEST_F(MacAudioInputTest, AUAudioInputStreamMiscCallingSequences) { 180 if (!CanRunAudioTests()) 181 return; 182 AudioInputStream* ais = CreateDefaultAudioInputStream(); 183 AUAudioInputStream* auais = static_cast<AUAudioInputStream*>(ais); 184 185 // Open(), Open() should fail the second time. 186 EXPECT_TRUE(ais->Open()); 187 EXPECT_FALSE(ais->Open()); 188 189 MockAudioInputCallback sink; 190 191 // Start(), Start() is a valid calling sequence (second call does nothing). 192 ais->Start(&sink); 193 EXPECT_TRUE(auais->started()); 194 ais->Start(&sink); 195 EXPECT_TRUE(auais->started()); 196 197 // Stop(), Stop() is a valid calling sequence (second call does nothing). 198 ais->Stop(); 199 EXPECT_FALSE(auais->started()); 200 ais->Stop(); 201 EXPECT_FALSE(auais->started()); 202 203 ais->Close(); 204} 205 206// Verify that recording starts and stops correctly in mono using mocked sink. 207TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyMonoRecording) { 208 if (!CanRunAudioTests()) 209 return; 210 211 int count = 0; 212 base::MessageLoopForUI loop; 213 214 // Create an audio input stream which records in mono. 215 AudioInputStream* ais = CreateAudioInputStream(CHANNEL_LAYOUT_MONO); 216 EXPECT_TRUE(ais->Open()); 217 218 int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate()); 219 int samples_per_packet = fs / 100; 220 int bits_per_sample = 16; 221 uint32 bytes_per_packet = samples_per_packet * (bits_per_sample / 8); 222 223 MockAudioInputCallback sink; 224 225 // We use 10ms packets and will run the test until ten packets are received. 226 // All should contain valid packets of the same size and a valid delay 227 // estimate. 228 EXPECT_CALL(sink, OnData(ais, NotNull(), bytes_per_packet, _, _)) 229 .Times(AtLeast(10)) 230 .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop)); 231 ais->Start(&sink); 232 loop.Run(); 233 ais->Stop(); 234 ais->Close(); 235} 236 237// Verify that recording starts and stops correctly in mono using mocked sink. 238TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyStereoRecording) { 239 if (!CanRunAudioTests()) 240 return; 241 242 int count = 0; 243 base::MessageLoopForUI loop; 244 245 // Create an audio input stream which records in stereo. 246 AudioInputStream* ais = CreateAudioInputStream(CHANNEL_LAYOUT_STEREO); 247 EXPECT_TRUE(ais->Open()); 248 249 int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate()); 250 int samples_per_packet = fs / 100; 251 int bits_per_sample = 16; 252 uint32 bytes_per_packet = 2 * samples_per_packet * (bits_per_sample / 8); 253 254 MockAudioInputCallback sink; 255 256 // We use 10ms packets and will run the test until ten packets are received. 257 // All should contain valid packets of the same size and a valid delay 258 // estimate. 259 // TODO(henrika): http://crbug.com/154352 forced us to run the capture side 260 // using a native buffer size of 128 audio frames and combine it with a FIFO 261 // to match the requested size by the client. This change might also have 262 // modified the delay estimates since the existing Ge(bytes_per_packet) for 263 // parameter #4 does no longer pass. I am removing this restriction here to 264 // ensure that we can land the patch but will revisit this test again when 265 // more analysis of the delay estimates are done. 266 EXPECT_CALL(sink, OnData(ais, NotNull(), bytes_per_packet, _, _)) 267 .Times(AtLeast(10)) 268 .WillRepeatedly(CheckCountAndPostQuitTask(&count, 10, &loop)); 269 ais->Start(&sink); 270 loop.Run(); 271 ais->Stop(); 272 ais->Close(); 273} 274 275// This test is intended for manual tests and should only be enabled 276// when it is required to store the captured data on a local file. 277// By default, GTest will print out YOU HAVE 1 DISABLED TEST. 278// To include disabled tests in test execution, just invoke the test program 279// with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS 280// environment variable to a value greater than 0. 281TEST_F(MacAudioInputTest, DISABLED_AUAudioInputStreamRecordToFile) { 282 if (!CanRunAudioTests()) 283 return; 284 const char* file_name = "out_stereo_10sec.pcm"; 285 286 int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate()); 287 AudioInputStream* ais = CreateDefaultAudioInputStream(); 288 EXPECT_TRUE(ais->Open()); 289 290 fprintf(stderr, " File name : %s\n", file_name); 291 fprintf(stderr, " Sample rate: %d\n", fs); 292 WriteToFileAudioSink file_sink(file_name); 293 fprintf(stderr, " >> Speak into the mic while recording...\n"); 294 ais->Start(&file_sink); 295 base::PlatformThread::Sleep(TestTimeouts::action_timeout()); 296 ais->Stop(); 297 fprintf(stderr, " >> Recording has stopped.\n"); 298 ais->Close(); 299} 300 301} // namespace media 302