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