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