| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 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 <stdint.h> | |
| 6 | |
| 7 #include "base/files/file_util.h" | |
| 8 #include "base/memory/ptr_util.h" | |
| 9 #include "base/synchronization/waitable_event.h" | |
| 10 #include "base/sys_byteorder.h" | |
| 11 #include "content/browser/renderer_host/media/audio_debug_file_writer.h" | |
| 12 #include "content/public/browser/browser_thread.h" | |
| 13 #include "content/public/test/test_browser_thread_bundle.h" | |
| 14 #include "media/base/audio_bus.h" | |
| 15 #include "media/base/test_helpers.h" | |
| 16 #include "testing/gtest/include/gtest/gtest.h" | |
| 17 | |
| 18 namespace content { | |
| 19 | |
| 20 namespace { | |
| 21 | |
| 22 static const uint16_t kBytesPerSample = sizeof(uint16_t); | |
| 23 static const uint16_t kPcmEncoding = 1; | |
| 24 static const size_t kWavHeaderSize = 44; | |
| 25 | |
| 26 uint16_t ReadLE2(const char* buf) { | |
| 27 return static_cast<uint8_t>(buf[0]) | (static_cast<uint8_t>(buf[1]) << 8); | |
| 28 } | |
| 29 | |
| 30 uint32_t ReadLE4(const char* buf) { | |
| 31 return static_cast<uint8_t>(buf[0]) | (static_cast<uint8_t>(buf[1]) << 8) | | |
| 32 (static_cast<uint8_t>(buf[2]) << 16) | | |
| 33 (static_cast<uint8_t>(buf[3]) << 24); | |
| 34 } | |
| 35 | |
| 36 } // namespace | |
| 37 | |
| 38 // <channel layout, sample rate, frames per buffer, number of buffer writes | |
| 39 typedef std::tr1::tuple<media::ChannelLayout, int, int, int> | |
| 40 AudioDebugFileWriterTestData; | |
| 41 | |
| 42 class AudioDebugFileWriterTest | |
| 43 : public testing::TestWithParam<AudioDebugFileWriterTestData> { | |
| 44 public: | |
| 45 AudioDebugFileWriterTest() | |
| 46 : thread_bundle_(content::TestBrowserThreadBundle::REAL_FILE_THREAD), | |
| 47 params_(media::AudioParameters::Format::AUDIO_PCM_LINEAR, | |
| 48 std::tr1::get<0>(GetParam()), | |
| 49 std::tr1::get<1>(GetParam()), | |
| 50 kBytesPerSample * 8, | |
| 51 std::tr1::get<2>(GetParam())), | |
| 52 writes_(std::tr1::get<3>(GetParam())), | |
| 53 source_samples_(params_.frames_per_buffer() * params_.channels() * | |
| 54 writes_), | |
| 55 source_interleaved_(source_samples_ ? new int16_t[source_samples_] | |
| 56 : nullptr) { | |
| 57 InitSourceInterleaved(source_interleaved_.get(), source_samples_); | |
| 58 } | |
| 59 | |
| 60 protected: | |
| 61 virtual ~AudioDebugFileWriterTest() {} | |
| 62 | |
| 63 static void InitSourceInterleaved(int16_t* source_interleaved, | |
| 64 int source_samples) { | |
| 65 if (source_samples) { | |
| 66 // equal steps to cover int16_t range of values | |
| 67 int16_t step = 0xffff / source_samples; | |
| 68 int16_t val = std::numeric_limits<int16_t>::min(); | |
| 69 for (int i = 0; i < source_samples; ++i, val += step) | |
| 70 source_interleaved[i] = val; | |
| 71 } | |
| 72 } | |
| 73 | |
| 74 static void VerifyHeader(const char(&wav_header)[kWavHeaderSize], | |
| 75 const media::AudioParameters& params, | |
| 76 int writes, | |
| 77 int64_t file_length) { | |
| 78 uint32_t block_align = params.channels() * kBytesPerSample; | |
| 79 uint32_t data_size = | |
| 80 static_cast<uint32_t>(params.frames_per_buffer() * params.channels() * | |
| 81 writes * kBytesPerSample); | |
| 82 // Offset Length Content | |
| 83 // 0 4 "RIFF" | |
| 84 EXPECT_EQ(0, strncmp(wav_header, "RIFF", 4)); | |
| 85 // 4 4 <file length - 8> | |
| 86 ASSERT_GT(file_length, 8); | |
| 87 EXPECT_EQ(static_cast<uint64_t>(file_length - 8), ReadLE4(wav_header + 4)); | |
| 88 EXPECT_EQ(static_cast<uint32_t>(data_size + kWavHeaderSize - 8), | |
| 89 ReadLE4(wav_header + 4)); | |
| 90 // 8 4 "WAVE" | |
| 91 // 12 4 "fmt " | |
| 92 EXPECT_EQ(0, strncmp(wav_header + 8, "WAVEfmt ", 8)); | |
| 93 // 16 4 <length of the fmt data> (=16) | |
| 94 EXPECT_EQ(16U, ReadLE4(wav_header + 16)); | |
| 95 // 20 2 <WAVE file encoding tag> | |
| 96 EXPECT_EQ(kPcmEncoding, ReadLE2(wav_header + 20)); | |
| 97 // 22 2 <channels> | |
| 98 EXPECT_EQ(params.channels(), ReadLE2(wav_header + 22)); | |
| 99 // 24 4 <sample rate> | |
| 100 EXPECT_EQ(static_cast<uint32_t>(params.sample_rate()), | |
| 101 ReadLE4(wav_header + 24)); | |
| 102 // 28 4 <bytes per second> (sample rate * block align) | |
| 103 EXPECT_EQ(static_cast<uint32_t>(params.sample_rate()) * block_align, | |
| 104 ReadLE4(wav_header + 28)); | |
| 105 // 32 2 <block align> (channels * bits per sample / 8) | |
| 106 EXPECT_EQ(block_align, ReadLE2(wav_header + 32)); | |
| 107 // 34 2 <bits per sample> | |
| 108 EXPECT_EQ(kBytesPerSample * 8, ReadLE2(wav_header + 34)); | |
| 109 // 36 4 "data" | |
| 110 EXPECT_EQ(0, strncmp(wav_header + 36, "data", 4)); | |
| 111 // 40 4 <sample data size(n)> | |
| 112 EXPECT_EQ(data_size, ReadLE4(wav_header + 40)); | |
| 113 } | |
| 114 | |
| 115 // |result_interleaved| is expected to be little-endian. | |
| 116 static void VerifyDataRecording(const int16_t* source_interleaved, | |
| 117 const int16_t* result_interleaved, | |
| 118 int16_t source_samples) { | |
| 119 // Allow mismatch by 1 due to rounding error in int->float->int | |
| 120 // calculations. | |
| 121 for (int i = 0; i < source_samples; ++i) | |
| 122 EXPECT_LE(std::abs(static_cast<int16_t>( | |
| 123 base::ByteSwapToLE16(source_interleaved[i])) - | |
| 124 result_interleaved[i]), | |
| 125 1) | |
| 126 << "i = " << i << " source " << source_interleaved[i] << " result " | |
| 127 << result_interleaved[i]; | |
| 128 } | |
| 129 | |
| 130 void VerifyRecording(const base::FilePath& file_path) { | |
| 131 base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ); | |
| 132 EXPECT_TRUE(file.IsValid()); | |
| 133 | |
| 134 char wav_header[kWavHeaderSize]; | |
| 135 EXPECT_EQ(file.Read(0, wav_header, kWavHeaderSize), | |
| 136 static_cast<int>(kWavHeaderSize)); | |
| 137 VerifyHeader(wav_header, params_, writes_, file.GetLength()); | |
| 138 | |
| 139 if (source_samples_ > 0) { | |
| 140 std::unique_ptr<int16_t[]> result_interleaved( | |
| 141 new int16_t[source_samples_]); | |
| 142 memset(result_interleaved.get(), 0, source_samples_ * kBytesPerSample); | |
| 143 | |
| 144 // Recording is read from file as a byte sequence, so it stored as | |
| 145 // little-endian. | |
| 146 int read = file.Read(kWavHeaderSize, | |
| 147 reinterpret_cast<char*>(result_interleaved.get()), | |
| 148 source_samples_ * kBytesPerSample); | |
| 149 EXPECT_EQ(static_cast<int>(file.GetLength() - kWavHeaderSize), read); | |
| 150 | |
| 151 VerifyDataRecording(source_interleaved_.get(), result_interleaved.get(), | |
| 152 source_samples_); | |
| 153 } | |
| 154 } | |
| 155 | |
| 156 void TestDoneOnFileThread(const base::Closure& callback) { | |
| 157 DCHECK_CURRENTLY_ON(BrowserThread::FILE); | |
| 158 | |
| 159 callback.Run(); | |
| 160 } | |
| 161 | |
| 162 void DoDebugRecording() { | |
| 163 // Write tasks are posted to BrowserThread::FILE. | |
| 164 for (int i = 0; i < writes_; ++i) { | |
| 165 std::unique_ptr<media::AudioBus> bus = media::AudioBus::Create( | |
| 166 params_.channels(), params_.frames_per_buffer()); | |
| 167 | |
| 168 bus->FromInterleaved( | |
| 169 source_interleaved_.get() + | |
| 170 i * params_.channels() * params_.frames_per_buffer(), | |
| 171 params_.frames_per_buffer(), kBytesPerSample); | |
| 172 | |
| 173 input_debug_writer_->Write(std::move(bus)); | |
| 174 } | |
| 175 } | |
| 176 | |
| 177 void WaitForRecordingCompletion() { | |
| 178 media::WaitableMessageLoopEvent event; | |
| 179 | |
| 180 // Post a task to BrowserThread::FILE indicating that all the writes are | |
| 181 // done. | |
| 182 BrowserThread::PostTask( | |
| 183 BrowserThread::FILE, FROM_HERE, | |
| 184 base::Bind(&AudioDebugFileWriterTest::TestDoneOnFileThread, | |
| 185 base::Unretained(this), event.GetClosure())); | |
| 186 | |
| 187 // Wait for TestDoneOnFileThread() to call event's closure. | |
| 188 event.RunAndWait(); | |
| 189 } | |
| 190 | |
| 191 void RecordAndVerifyOnce() { | |
| 192 base::FilePath file_path; | |
| 193 EXPECT_TRUE(base::CreateTemporaryFile(&file_path)); | |
| 194 | |
| 195 input_debug_writer_->Start(file_path); | |
| 196 | |
| 197 DoDebugRecording(); | |
| 198 | |
| 199 input_debug_writer_->Stop(); | |
| 200 | |
| 201 WaitForRecordingCompletion(); | |
| 202 | |
| 203 VerifyRecording(file_path); | |
| 204 | |
| 205 if (::testing::Test::HasFailure()) { | |
| 206 LOG(ERROR) << "Test failed; keeping recording(s) at [" | |
| 207 << file_path.value().c_str() << "]."; | |
| 208 } else { | |
| 209 EXPECT_TRUE(base::DeleteFile(file_path, false)); | |
| 210 } | |
| 211 } | |
| 212 | |
| 213 protected: | |
| 214 TestBrowserThreadBundle thread_bundle_; | |
| 215 | |
| 216 // Writer under test. | |
| 217 std::unique_ptr<AudioDebugFileWriter> input_debug_writer_; | |
| 218 | |
| 219 // AudioBus parameters. | |
| 220 media::AudioParameters params_; | |
| 221 | |
| 222 // Number of times to write AudioBus to the file. | |
| 223 int writes_; | |
| 224 | |
| 225 // Number of samples in the source data. | |
| 226 int source_samples_; | |
| 227 | |
| 228 // Source data. | |
| 229 std::unique_ptr<int16_t[]> source_interleaved_; | |
| 230 | |
| 231 private: | |
| 232 DISALLOW_COPY_AND_ASSIGN(AudioDebugFileWriterTest); | |
| 233 }; | |
| 234 | |
| 235 class AudioDebugFileWriterBehavioralTest : public AudioDebugFileWriterTest {}; | |
| 236 | |
| 237 TEST_P(AudioDebugFileWriterTest, WaveRecordingTest) { | |
| 238 input_debug_writer_.reset(new AudioDebugFileWriter(params_)); | |
| 239 | |
| 240 RecordAndVerifyOnce(); | |
| 241 } | |
| 242 | |
| 243 TEST_P(AudioDebugFileWriterBehavioralTest, | |
| 244 DeletedBeforeRecordingFinishedOnFileThread) { | |
| 245 input_debug_writer_.reset(new AudioDebugFileWriter(params_)); | |
| 246 | |
| 247 base::FilePath file_path; | |
| 248 EXPECT_TRUE(base::CreateTemporaryFile(&file_path)); | |
| 249 | |
| 250 base::WaitableEvent* wait_for_deletion = | |
| 251 new base::WaitableEvent(base::WaitableEvent::ResetPolicy::MANUAL, | |
| 252 base::WaitableEvent::InitialState::NOT_SIGNALED); | |
| 253 | |
| 254 BrowserThread::PostTask( | |
| 255 BrowserThread::FILE, FROM_HERE, | |
| 256 base::Bind(&base::WaitableEvent::Wait, base::Owned(wait_for_deletion))); | |
| 257 | |
| 258 input_debug_writer_->Start(file_path); | |
| 259 | |
| 260 DoDebugRecording(); | |
| 261 | |
| 262 input_debug_writer_.reset(); | |
| 263 wait_for_deletion->Signal(); | |
| 264 | |
| 265 WaitForRecordingCompletion(); | |
| 266 | |
| 267 VerifyRecording(file_path); | |
| 268 | |
| 269 if (::testing::Test::HasFailure()) { | |
| 270 LOG(ERROR) << "Test failed; keeping recording(s) at [" | |
| 271 << file_path.value().c_str() << "]."; | |
| 272 } else { | |
| 273 EXPECT_TRUE(base::DeleteFile(file_path, false)); | |
| 274 } | |
| 275 } | |
| 276 | |
| 277 TEST_P(AudioDebugFileWriterBehavioralTest, FileCreationError) { | |
| 278 input_debug_writer_.reset(new AudioDebugFileWriter(params_)); | |
| 279 base::FilePath file_path; // Empty file name. | |
| 280 input_debug_writer_->Start(file_path); | |
| 281 DoDebugRecording(); | |
| 282 } | |
| 283 | |
| 284 TEST_P(AudioDebugFileWriterBehavioralTest, StartStopStartStop) { | |
| 285 input_debug_writer_.reset(new AudioDebugFileWriter(params_)); | |
| 286 RecordAndVerifyOnce(); | |
| 287 RecordAndVerifyOnce(); | |
| 288 } | |
| 289 | |
| 290 TEST_P(AudioDebugFileWriterBehavioralTest, DestroyNotStarted) { | |
| 291 input_debug_writer_.reset(new AudioDebugFileWriter(params_)); | |
| 292 input_debug_writer_.reset(); | |
| 293 } | |
| 294 | |
| 295 TEST_P(AudioDebugFileWriterBehavioralTest, DestroyStarted) { | |
| 296 input_debug_writer_.reset(new AudioDebugFileWriter(params_)); | |
| 297 base::FilePath file_path; | |
| 298 EXPECT_TRUE(base::CreateTemporaryFile(&file_path)); | |
| 299 input_debug_writer_->Start(file_path); | |
| 300 input_debug_writer_.reset(); | |
| 301 } | |
| 302 | |
| 303 INSTANTIATE_TEST_CASE_P( | |
| 304 AudioDebugFileWriterTest, | |
| 305 AudioDebugFileWriterTest, | |
| 306 // Using 10ms frames per buffer everywhere. | |
| 307 testing::Values( | |
| 308 // No writes. | |
| 309 std::tr1::make_tuple(media::ChannelLayout::CHANNEL_LAYOUT_MONO, | |
| 310 44100, | |
| 311 44100 / 100, | |
| 312 0), | |
| 313 // 1 write of mono. | |
| 314 std::tr1::make_tuple(media::ChannelLayout::CHANNEL_LAYOUT_MONO, | |
| 315 44100, | |
| 316 44100 / 100, | |
| 317 1), | |
| 318 // 1 second of mono. | |
| 319 std::tr1::make_tuple(media::ChannelLayout::CHANNEL_LAYOUT_MONO, | |
| 320 44100, | |
| 321 44100 / 100, | |
| 322 100), | |
| 323 // 1 second of mono, higher rate. | |
| 324 std::tr1::make_tuple(media::ChannelLayout::CHANNEL_LAYOUT_MONO, | |
| 325 48000, | |
| 326 48000 / 100, | |
| 327 100), | |
| 328 // 1 second of stereo. | |
| 329 std::tr1::make_tuple(media::ChannelLayout::CHANNEL_LAYOUT_STEREO, | |
| 330 44100, | |
| 331 44100 / 100, | |
| 332 100), | |
| 333 // 15 seconds of stereo, higher rate. | |
| 334 std::tr1::make_tuple(media::ChannelLayout::CHANNEL_LAYOUT_STEREO, | |
| 335 48000, | |
| 336 48000 / 100, | |
| 337 1500))); | |
| 338 | |
| 339 INSTANTIATE_TEST_CASE_P( | |
| 340 AudioDebugFileWriterBehavioralTest, | |
| 341 AudioDebugFileWriterBehavioralTest, | |
| 342 // Using 10ms frames per buffer everywhere. | |
| 343 testing::Values( | |
| 344 // No writes. | |
| 345 std::tr1::make_tuple(media::ChannelLayout::CHANNEL_LAYOUT_MONO, | |
| 346 44100, | |
| 347 44100 / 100, | |
| 348 100))); | |
| 349 | |
| 350 } // namespace content | |
| OLD | NEW |