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