audio_low_latency_input_mac_unittest.cc revision 0529e5d033099cbfc42635f6f6183833b09dff6e
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/run_loop.h" 9#include "base/test/test_timeouts.h" 10#include "base/threading/platform_thread.h" 11#include "media/audio/audio_io.h" 12#include "media/audio/audio_manager_base.h" 13#include "media/audio/mac/audio_low_latency_input_mac.h" 14#include "media/base/seekable_buffer.h" 15#include "testing/gmock/include/gmock/gmock.h" 16#include "testing/gtest/include/gtest/gtest.h" 17 18using ::testing::_; 19using ::testing::AnyNumber; 20using ::testing::AtLeast; 21using ::testing::Ge; 22using ::testing::NotNull; 23 24namespace media { 25 26ACTION_P4(CheckCountAndPostQuitTask, count, limit, loop, closure) { 27 if (++*count >= limit) { 28 loop->PostTask(FROM_HERE, closure); 29 } 30} 31 32class MockAudioInputCallback : public AudioInputStream::AudioInputCallback { 33 public: 34 MOCK_METHOD5(OnData, void(AudioInputStream* stream, 35 const uint8* src, uint32 size, 36 uint32 hardware_delay_bytes, double volume)); 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 OnError(AudioInputStream* stream) OVERRIDE {} 88 89 private: 90 media::SeekableBuffer buffer_; 91 FILE* file_; 92 int bytes_to_write_; 93}; 94 95class MacAudioInputTest : public testing::Test { 96 protected: 97 MacAudioInputTest() 98 : message_loop_(base::MessageLoop::TYPE_UI), 99 audio_manager_(AudioManager::CreateForTesting()) { 100 // Wait for the AudioManager to finish any initialization on the audio loop. 101 base::RunLoop().RunUntilIdle(); 102 } 103 104 virtual ~MacAudioInputTest() { 105 base::RunLoop().RunUntilIdle(); 106 } 107 108 // Convenience method which ensures that we are not running on the build 109 // bots and that at least one valid input device can be found. 110 bool CanRunAudioTests() { 111 bool has_input = audio_manager_->HasAudioInputDevices(); 112 if (!has_input) 113 LOG(WARNING) << "No input devices detected"; 114 return has_input; 115 } 116 117 // Convenience method which creates a default AudioInputStream object using 118 // a 10ms frame size and a sample rate which is set to the hardware sample 119 // rate. 120 AudioInputStream* CreateDefaultAudioInputStream() { 121 int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate()); 122 int samples_per_packet = fs / 100; 123 AudioInputStream* ais = audio_manager_->MakeAudioInputStream( 124 AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 125 CHANNEL_LAYOUT_STEREO, fs, 16, samples_per_packet), 126 AudioManagerBase::kDefaultDeviceId); 127 EXPECT_TRUE(ais); 128 return ais; 129 } 130 131 // Convenience method which creates an AudioInputStream object with a 132 // specified channel layout. 133 AudioInputStream* CreateAudioInputStream(ChannelLayout channel_layout) { 134 int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate()); 135 int samples_per_packet = fs / 100; 136 AudioInputStream* ais = audio_manager_->MakeAudioInputStream( 137 AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, 138 channel_layout, fs, 16, samples_per_packet), 139 AudioManagerBase::kDefaultDeviceId); 140 EXPECT_TRUE(ais); 141 return ais; 142 } 143 144 base::MessageLoop message_loop_; 145 scoped_ptr<AudioManager> audio_manager_; 146}; 147 148// Test Create(), Close(). 149TEST_F(MacAudioInputTest, AUAudioInputStreamCreateAndClose) { 150 if (!CanRunAudioTests()) 151 return; 152 AudioInputStream* ais = CreateDefaultAudioInputStream(); 153 ais->Close(); 154} 155 156// Test Open(), Close(). 157TEST_F(MacAudioInputTest, AUAudioInputStreamOpenAndClose) { 158 if (!CanRunAudioTests()) 159 return; 160 AudioInputStream* ais = CreateDefaultAudioInputStream(); 161 EXPECT_TRUE(ais->Open()); 162 ais->Close(); 163} 164 165// Test Open(), Start(), Close(). 166TEST_F(MacAudioInputTest, AUAudioInputStreamOpenStartAndClose) { 167 if (!CanRunAudioTests()) 168 return; 169 AudioInputStream* ais = CreateDefaultAudioInputStream(); 170 EXPECT_TRUE(ais->Open()); 171 MockAudioInputCallback sink; 172 ais->Start(&sink); 173 ais->Close(); 174} 175 176// Test Open(), Start(), Stop(), Close(). 177TEST_F(MacAudioInputTest, AUAudioInputStreamOpenStartStopAndClose) { 178 if (!CanRunAudioTests()) 179 return; 180 AudioInputStream* ais = CreateDefaultAudioInputStream(); 181 EXPECT_TRUE(ais->Open()); 182 MockAudioInputCallback sink; 183 ais->Start(&sink); 184 ais->Stop(); 185 ais->Close(); 186} 187 188// Test some additional calling sequences. 189TEST_F(MacAudioInputTest, AUAudioInputStreamMiscCallingSequences) { 190 if (!CanRunAudioTests()) 191 return; 192 AudioInputStream* ais = CreateDefaultAudioInputStream(); 193 AUAudioInputStream* auais = static_cast<AUAudioInputStream*>(ais); 194 195 // Open(), Open() should fail the second time. 196 EXPECT_TRUE(ais->Open()); 197 EXPECT_FALSE(ais->Open()); 198 199 MockAudioInputCallback sink; 200 201 // Start(), Start() is a valid calling sequence (second call does nothing). 202 ais->Start(&sink); 203 EXPECT_TRUE(auais->started()); 204 ais->Start(&sink); 205 EXPECT_TRUE(auais->started()); 206 207 // Stop(), Stop() is a valid calling sequence (second call does nothing). 208 ais->Stop(); 209 EXPECT_FALSE(auais->started()); 210 ais->Stop(); 211 EXPECT_FALSE(auais->started()); 212 213 ais->Close(); 214} 215 216// Verify that recording starts and stops correctly in mono using mocked sink. 217TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyMonoRecording) { 218 if (!CanRunAudioTests()) 219 return; 220 221 int count = 0; 222 223 // Create an audio input stream which records in mono. 224 AudioInputStream* ais = CreateAudioInputStream(CHANNEL_LAYOUT_MONO); 225 EXPECT_TRUE(ais->Open()); 226 227 int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate()); 228 int samples_per_packet = fs / 100; 229 int bits_per_sample = 16; 230 uint32 bytes_per_packet = samples_per_packet * (bits_per_sample / 8); 231 232 MockAudioInputCallback sink; 233 234 // We use 10ms packets and will run the test until ten packets are received. 235 // All should contain valid packets of the same size and a valid delay 236 // estimate. 237 base::RunLoop run_loop; 238 EXPECT_CALL(sink, OnData(ais, NotNull(), bytes_per_packet, _, _)) 239 .Times(AtLeast(10)) 240 .WillRepeatedly(CheckCountAndPostQuitTask( 241 &count, 10, &message_loop_, run_loop.QuitClosure())); 242 ais->Start(&sink); 243 run_loop.Run(); 244 ais->Stop(); 245 ais->Close(); 246} 247 248// Verify that recording starts and stops correctly in mono using mocked sink. 249TEST_F(MacAudioInputTest, AUAudioInputStreamVerifyStereoRecording) { 250 if (!CanRunAudioTests()) 251 return; 252 253 int count = 0; 254 255 // Create an audio input stream which records in stereo. 256 AudioInputStream* ais = CreateAudioInputStream(CHANNEL_LAYOUT_STEREO); 257 EXPECT_TRUE(ais->Open()); 258 259 int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate()); 260 int samples_per_packet = fs / 100; 261 int bits_per_sample = 16; 262 uint32 bytes_per_packet = 2 * samples_per_packet * (bits_per_sample / 8); 263 264 MockAudioInputCallback sink; 265 266 // We use 10ms packets and will run the test until ten packets are received. 267 // All should contain valid packets of the same size and a valid delay 268 // estimate. 269 // TODO(henrika): http://crbug.com/154352 forced us to run the capture side 270 // using a native buffer size of 128 audio frames and combine it with a FIFO 271 // to match the requested size by the client. This change might also have 272 // modified the delay estimates since the existing Ge(bytes_per_packet) for 273 // parameter #4 does no longer pass. I am removing this restriction here to 274 // ensure that we can land the patch but will revisit this test again when 275 // more analysis of the delay estimates are done. 276 base::RunLoop run_loop; 277 EXPECT_CALL(sink, OnData(ais, NotNull(), bytes_per_packet, _, _)) 278 .Times(AtLeast(10)) 279 .WillRepeatedly(CheckCountAndPostQuitTask( 280 &count, 10, &message_loop_, run_loop.QuitClosure())); 281 ais->Start(&sink); 282 run_loop.Run(); 283 ais->Stop(); 284 ais->Close(); 285} 286 287// This test is intended for manual tests and should only be enabled 288// when it is required to store the captured data on a local file. 289// By default, GTest will print out YOU HAVE 1 DISABLED TEST. 290// To include disabled tests in test execution, just invoke the test program 291// with --gtest_also_run_disabled_tests or set the GTEST_ALSO_RUN_DISABLED_TESTS 292// environment variable to a value greater than 0. 293TEST_F(MacAudioInputTest, DISABLED_AUAudioInputStreamRecordToFile) { 294 if (!CanRunAudioTests()) 295 return; 296 const char* file_name = "out_stereo_10sec.pcm"; 297 298 int fs = static_cast<int>(AUAudioInputStream::HardwareSampleRate()); 299 AudioInputStream* ais = CreateDefaultAudioInputStream(); 300 EXPECT_TRUE(ais->Open()); 301 302 fprintf(stderr, " File name : %s\n", file_name); 303 fprintf(stderr, " Sample rate: %d\n", fs); 304 WriteToFileAudioSink file_sink(file_name); 305 fprintf(stderr, " >> Speak into the mic while recording...\n"); 306 ais->Start(&file_sink); 307 base::PlatformThread::Sleep(TestTimeouts::action_timeout()); 308 ais->Stop(); 309 fprintf(stderr, " >> Recording has stopped.\n"); 310 ais->Close(); 311} 312 313} // namespace media 314