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