OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 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 <limits> |
| 6 #include <vector> |
| 7 |
| 8 #include "base/bind.h" |
| 9 #include "base/bind_helpers.h" |
| 10 #include "base/macros.h" |
| 11 #include "media/base/audio_bus.h" |
| 12 #include "media/base/audio_push_fifo.h" |
| 13 #include "testing/gtest/include/gtest/gtest.h" |
| 14 |
| 15 namespace media { |
| 16 |
| 17 namespace { |
| 18 |
| 19 class AudioPushFifoTest : public testing::TestWithParam<int> { |
| 20 public: |
| 21 AudioPushFifoTest() {} |
| 22 ~AudioPushFifoTest() override {} |
| 23 |
| 24 int output_chunk_size() const { return GetParam(); } |
| 25 |
| 26 void SetUp() final { |
| 27 fifo_.reset(new AudioPushFifo(base::Bind( |
| 28 &AudioPushFifoTest::ReceiveAndCheckNextChunk, base::Unretained(this)))); |
| 29 fifo_->Reset(output_chunk_size()); |
| 30 ASSERT_EQ(output_chunk_size(), fifo_->frames_per_buffer()); |
| 31 } |
| 32 |
| 33 protected: |
| 34 struct OutputChunkResult { |
| 35 int num_frames; |
| 36 float first_sample_value; |
| 37 float last_sample_value; |
| 38 int frame_delay; |
| 39 }; |
| 40 |
| 41 // Returns the number of output chunks that should have been emitted given the |
| 42 // number of input frames pushed so far. |
| 43 size_t GetExpectedOutputChunks(int frames_pushed) const { |
| 44 return static_cast<size_t>(frames_pushed / output_chunk_size()); |
| 45 } |
| 46 |
| 47 // Returns the number of Push() calls to make in order to get at least 3 |
| 48 // output chunks. |
| 49 int GetNumPushTestIterations(int input_chunk_size) const { |
| 50 return 3 * std::max(1, output_chunk_size() / input_chunk_size); |
| 51 } |
| 52 |
| 53 // Repeatedly pushes constant-sized batches of input samples and checks that |
| 54 // the input data is re-chunked correctly. |
| 55 void RunSimpleRechunkTest(int input_chunk_size) { |
| 56 const int num_iterations = GetNumPushTestIterations(input_chunk_size); |
| 57 |
| 58 int sample_value = 0; |
| 59 const scoped_ptr<AudioBus> audio_bus = |
| 60 AudioBus::Create(1, input_chunk_size); |
| 61 |
| 62 for (int i = 0; i < num_iterations; ++i) { |
| 63 EXPECT_EQ(GetExpectedOutputChunks(i * input_chunk_size), results_.size()); |
| 64 |
| 65 // Fill audio data with predictable values. |
| 66 for (int j = 0; j < audio_bus->frames(); ++j) |
| 67 audio_bus->channel(0)[j] = static_cast<float>(sample_value++); |
| 68 |
| 69 fifo_->Push(*audio_bus); |
| 70 // Note: AudioPushFifo has just called ReceiveAndCheckNextChunk() zero or |
| 71 // more times. |
| 72 } |
| 73 EXPECT_EQ(GetExpectedOutputChunks(num_iterations * input_chunk_size), |
| 74 results_.size()); |
| 75 |
| 76 // Confirm first and last sample values that have been output are the |
| 77 // expected ones. |
| 78 ASSERT_FALSE(results_.empty()); |
| 79 EXPECT_EQ(0.0f, results_.front().first_sample_value); |
| 80 const float last_value_in_last_chunk = static_cast<float>( |
| 81 GetExpectedOutputChunks(num_iterations * input_chunk_size) * |
| 82 output_chunk_size() - |
| 83 1); |
| 84 EXPECT_EQ(last_value_in_last_chunk, results_.back().last_sample_value); |
| 85 |
| 86 // Confirm the expected frame delays for the first output chunk (or two). |
| 87 if (input_chunk_size < output_chunk_size()) { |
| 88 const int num_queued_before_first_output = |
| 89 ((output_chunk_size() - 1) / input_chunk_size) * input_chunk_size; |
| 90 EXPECT_EQ(-num_queued_before_first_output, results_.front().frame_delay); |
| 91 } else if (input_chunk_size >= output_chunk_size()) { |
| 92 EXPECT_EQ(0, results_[0].frame_delay); |
| 93 if (input_chunk_size >= 2 * output_chunk_size()) { |
| 94 EXPECT_EQ(output_chunk_size(), results_[1].frame_delay); |
| 95 } else { |
| 96 const int num_remaining_after_first_output = |
| 97 input_chunk_size - output_chunk_size(); |
| 98 EXPECT_EQ(-num_remaining_after_first_output, results_[1].frame_delay); |
| 99 } |
| 100 } |
| 101 |
| 102 const size_t num_results_before_flush = results_.size(); |
| 103 fifo_->Flush(); |
| 104 const size_t num_results_after_flush = results_.size(); |
| 105 if (num_results_after_flush > num_results_before_flush) { |
| 106 EXPECT_NE(0, results_.back().frame_delay); |
| 107 EXPECT_LT(-output_chunk_size(), results_.back().frame_delay); |
| 108 } |
| 109 } |
| 110 |
| 111 // Returns a "random" integer in the range [begin,end). |
| 112 int GetRandomInRange(int begin, int end) { |
| 113 const int len = end - begin; |
| 114 const int rand_offset = (len == 0) ? 0 : (NextRandomInt() % (end - begin)); |
| 115 return begin + rand_offset; |
| 116 } |
| 117 |
| 118 scoped_ptr<AudioPushFifo> fifo_; |
| 119 std::vector<OutputChunkResult> results_; |
| 120 |
| 121 private: |
| 122 // Called by |fifo_| to deliver another chunk of audio. Sanity checks |
| 123 // the sample values are as expected, and without any dropped/duplicated, and |
| 124 // adds a result to |results_|. |
| 125 void ReceiveAndCheckNextChunk(const AudioBus& audio_bus, int frame_delay) { |
| 126 OutputChunkResult result; |
| 127 result.num_frames = audio_bus.frames(); |
| 128 result.first_sample_value = audio_bus.channel(0)[0]; |
| 129 result.last_sample_value = audio_bus.channel(0)[audio_bus.frames() - 1]; |
| 130 result.frame_delay = frame_delay; |
| 131 |
| 132 // Check that each sample value is the previous sample value plus one. |
| 133 for (int i = 1; i < audio_bus.frames(); ++i) { |
| 134 const float expected_value = result.first_sample_value + i; |
| 135 const float actual_value = audio_bus.channel(0)[i]; |
| 136 if (actual_value != expected_value) { |
| 137 if (actual_value == 0.0f) { |
| 138 // This chunk is probably being emitted by a Flush(). If that's true |
| 139 // then the frame_delay will be negative and the rest of the |
| 140 // |audio_bus| should be all zeroes. |
| 141 ASSERT_GT(0, frame_delay); |
| 142 for (int j = i + 1; j < audio_bus.frames(); ++j) |
| 143 ASSERT_EQ(0.0f, audio_bus.channel(0)[j]); |
| 144 break; |
| 145 } else { |
| 146 ASSERT_EQ(expected_value, actual_value) << "Sample at offset " << i |
| 147 << " is incorrect."; |
| 148 } |
| 149 } |
| 150 } |
| 151 |
| 152 results_.push_back(result); |
| 153 } |
| 154 |
| 155 // Note: Not using base::RandInt() because it is horribly slow on debug |
| 156 // builds. The following is a very simple, deterministic LCG: |
| 157 int NextRandomInt() { |
| 158 rand_seed_ = (1103515245 * rand_seed_ + 12345) % (1 << 31); |
| 159 return static_cast<int>(rand_seed_); |
| 160 } |
| 161 |
| 162 uint32_t rand_seed_ = 0x7e110; |
| 163 |
| 164 DISALLOW_COPY_AND_ASSIGN(AudioPushFifoTest); |
| 165 }; |
| 166 |
| 167 // Tests an atypical edge case: Push()ing one frame at a time. |
| 168 TEST_P(AudioPushFifoTest, PushOneFrameAtATime) { |
| 169 RunSimpleRechunkTest(1); |
| 170 } |
| 171 |
| 172 // Tests that re-chunking the audio from common platform input chunk sizes |
| 173 // works. |
| 174 TEST_P(AudioPushFifoTest, Push128FramesAtATime) { |
| 175 RunSimpleRechunkTest(128); |
| 176 } |
| 177 TEST_P(AudioPushFifoTest, Push512FramesAtATime) { |
| 178 RunSimpleRechunkTest(512); |
| 179 } |
| 180 |
| 181 // Tests that re-chunking the audio from common "10 ms" input chunk sizes |
| 182 // works (44100 Hz * 10 ms = 441, and 48000 Hz * 10 ms = 480). |
| 183 TEST_P(AudioPushFifoTest, Push441FramesAtATime) { |
| 184 RunSimpleRechunkTest(441); |
| 185 } |
| 186 TEST_P(AudioPushFifoTest, Push480FramesAtATime) { |
| 187 RunSimpleRechunkTest(480); |
| 188 } |
| 189 |
| 190 // Tests that re-chunking when input audio is provided in varying chunk sizes |
| 191 // works. |
| 192 TEST_P(AudioPushFifoTest, PushArbitraryNumbersOfFramesAtATime) { |
| 193 // The loop below will run until both: 1) kMinNumIterations loops have |
| 194 // occurred; and 2) there are at least 3 entries in |results_|. |
| 195 const int kMinNumIterations = 30; |
| 196 |
| 197 int sample_value = 0; |
| 198 int frames_pushed_so_far = 0; |
| 199 for (int i = 0; i < kMinNumIterations || results_.size() < 3; ++i) { |
| 200 EXPECT_EQ(GetExpectedOutputChunks(frames_pushed_so_far), results_.size()); |
| 201 |
| 202 // Create an AudioBus of a random length, populated with sample values. |
| 203 const int input_chunk_size = GetRandomInRange(1, 1920); |
| 204 const scoped_ptr<AudioBus> audio_bus = |
| 205 AudioBus::Create(1, input_chunk_size); |
| 206 for (int j = 0; j < audio_bus->frames(); ++j) |
| 207 audio_bus->channel(0)[j] = static_cast<float>(sample_value++); |
| 208 |
| 209 fifo_->Push(*audio_bus); |
| 210 // Note: AudioPushFifo has just called ReceiveAndCheckNextChunk() zero or |
| 211 // more times. |
| 212 |
| 213 frames_pushed_so_far += input_chunk_size; |
| 214 } |
| 215 EXPECT_EQ(GetExpectedOutputChunks(frames_pushed_so_far), results_.size()); |
| 216 |
| 217 ASSERT_FALSE(results_.empty()); |
| 218 EXPECT_EQ(0.0f, results_.front().first_sample_value); |
| 219 const float last_value_in_last_chunk = static_cast<float>( |
| 220 GetExpectedOutputChunks(frames_pushed_so_far) * output_chunk_size() - 1); |
| 221 EXPECT_EQ(last_value_in_last_chunk, results_.back().last_sample_value); |
| 222 |
| 223 const size_t num_results_before_flush = results_.size(); |
| 224 fifo_->Flush(); |
| 225 const size_t num_results_after_flush = results_.size(); |
| 226 if (num_results_after_flush > num_results_before_flush) { |
| 227 EXPECT_NE(0, results_.back().frame_delay); |
| 228 EXPECT_LT(-output_chunk_size(), results_.back().frame_delay); |
| 229 } |
| 230 } |
| 231 |
| 232 INSTANTIATE_TEST_CASE_P(, |
| 233 AudioPushFifoTest, |
| 234 ::testing::Values( |
| 235 // 1 ms output chunks at common sample rates. |
| 236 16, // 16000 Hz |
| 237 22, // 22050 Hz |
| 238 44, // 44100 Hz |
| 239 48, // 48000 Hz |
| 240 |
| 241 // 10 ms output chunks at common sample rates. |
| 242 160, // 16000 Hz |
| 243 220, // 22050 Hz |
| 244 441, // 44100 Hz |
| 245 480, // 48000 Hz |
| 246 |
| 247 // 60 ms output chunks at common sample rates. |
| 248 960, // 16000 Hz |
| 249 1323, // 22050 Hz |
| 250 2646, // 44100 Hz |
| 251 2880 // 48000 Hz |
| 252 )); |
| 253 |
| 254 } // namespace |
| 255 |
| 256 } // namespace media |
OLD | NEW |