Index: media/base/audio_rechunker_unittest.cc |
diff --git a/media/base/audio_rechunker_unittest.cc b/media/base/audio_rechunker_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..34ffd4bb4d037e2115be0e7e82a47af957212791 |
--- /dev/null |
+++ b/media/base/audio_rechunker_unittest.cc |
@@ -0,0 +1,239 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <limits> |
+#include <vector> |
+ |
+#include "base/bind.h" |
+#include "base/bind_helpers.h" |
+#include "base/macros.h" |
+#include "media/base/audio_bus.h" |
+#include "media/base/audio_rechunker.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace media { |
+ |
+namespace { |
+ |
+struct TestParams { |
+ int64_t duration_us; |
+ int sample_rate; |
+ int output_chunk_size; |
+ bool expect_evenly_divided; |
+ TestParams(int64_t d, int s, int e, bool e2) |
+ : duration_us(d), |
+ sample_rate(s), |
+ output_chunk_size(e), |
+ expect_evenly_divided(e2) {} |
+}; |
+ |
+class AudioRechunkerTest : public testing::TestWithParam<TestParams> { |
+ public: |
+ AudioRechunkerTest() {} |
+ ~AudioRechunkerTest() override {} |
+ |
+ void SetUp() final { |
+ rechunker_.reset(new AudioRechunker( |
+ output_duration(), |
+ base::Bind(&AudioRechunkerTest::ReceiveAndCheckNextChunk, |
+ base::Unretained(this)))); |
+ ASSERT_EQ(GetParam().expect_evenly_divided, |
+ rechunker_->SetSampleRate(GetParam().sample_rate)); |
+ ASSERT_EQ(GetParam().output_chunk_size, rechunker_->output_frames()); |
+ } |
+ |
+ protected: |
+ struct OutputChunkResult { |
+ int num_frames; |
+ base::TimeDelta reference_timestamp; |
+ float first_sample_value; |
+ float last_sample_value; |
+ }; |
+ |
+ base::TimeDelta output_duration() const { |
+ return base::TimeDelta::FromMicroseconds(GetParam().duration_us); |
+ } |
+ |
+ // Returns the number of output chunks that should have been emitted given the |
+ // number of input frames pushed so far. |
+ size_t GetExpectedOutputChunks(int frames_pushed) const { |
+ return static_cast<size_t>(frames_pushed / GetParam().output_chunk_size); |
+ } |
+ |
+ // Returns the number of Push() calls to make in order to get at least 3 |
+ // output chunks. |
+ int GetNumPushTestIterations(int input_chunk_size) const { |
+ return 3 * std::max(1, GetParam().output_chunk_size / input_chunk_size); |
+ } |
+ |
+ // Repeatedly pushes constant-sized batches of input samples and checks that |
+ // the input data is re-chunked correctly. |
+ void RunSimpleRechunkTest(int input_chunk_size) { |
+ const int num_iterations = GetNumPushTestIterations(input_chunk_size); |
+ |
+ int sample_value = 0; |
+ const scoped_ptr<AudioBus> audio_bus = |
+ AudioBus::Create(1, input_chunk_size); |
+ |
+ for (int i = 0; i < num_iterations; ++i) { |
+ EXPECT_EQ(GetExpectedOutputChunks(i * input_chunk_size), results_.size()); |
+ |
+ // Fill audio data with predictable values. |
+ for (int j = 0; j < audio_bus->frames(); ++j) |
+ audio_bus->channel(0)[j] = static_cast<float>(sample_value++); |
+ |
+ rechunker_->Push(*audio_bus, base::TimeDelta::FromMicroseconds( |
+ i * input_chunk_size * INT64_C(1000000) / |
+ GetParam().sample_rate)); |
+ // Note: Rechunker has just called ReceiveAndCheckNextChunk() zero or more |
+ // times. |
+ } |
+ EXPECT_EQ(GetExpectedOutputChunks(num_iterations * input_chunk_size), |
+ results_.size()); |
+ |
+ ASSERT_FALSE(results_.empty()); |
+ EXPECT_EQ(0.0f, results_.front().first_sample_value); |
+ const float last_value_in_last_chunk = static_cast<float>( |
+ GetExpectedOutputChunks(num_iterations * input_chunk_size) * |
+ GetParam().output_chunk_size - |
+ 1); |
+ EXPECT_EQ(last_value_in_last_chunk, results_.back().last_sample_value); |
+ } |
+ |
+ // Returns a "random" integer in the range [begin,end). |
+ int GetRandomInRange(int begin, int end) { |
+ const int len = end - begin; |
+ const int rand_offset = (len == 0) ? 0 : (NextRandomInt() % (end - begin)); |
+ return begin + rand_offset; |
+ } |
+ |
+ scoped_ptr<AudioRechunker> rechunker_; |
+ std::vector<OutputChunkResult> results_; |
+ |
+ private: |
+ // Called by |rechunker_| to deliver another chunk of audio. Sanity checks |
+ // the sample values are as expected, and without any dropped/duplicated, and |
+ // adds a result to |results_|. |
+ void ReceiveAndCheckNextChunk(const AudioBus& audio_bus, |
+ base::TimeDelta reference_timestamp) { |
+ OutputChunkResult result; |
+ result.num_frames = audio_bus.frames(); |
+ result.reference_timestamp = reference_timestamp; |
+ result.first_sample_value = audio_bus.channel(0)[0]; |
+ result.last_sample_value = audio_bus.channel(0)[audio_bus.frames() - 1]; |
+ |
+ // Check that each sample value is the previous sample value plus one. |
+ for (int i = 1; i < audio_bus.frames(); ++i) { |
+ ASSERT_EQ(result.first_sample_value + i, audio_bus.channel(0)[i]) |
+ << "Sample at offset " << i << " is incorrect."; |
+ } |
+ |
+ // Check that no audio samples were dropped, and that the reference |
+ // timestamps are monotonically increasing. |
+ if (!results_.empty()) { |
+ const OutputChunkResult& last_result = results_.back(); |
+ ASSERT_EQ(last_result.last_sample_value + 1, result.first_sample_value); |
+ ASSERT_LT(last_result.reference_timestamp, result.reference_timestamp); |
+ } |
+ |
+ results_.push_back(result); |
+ } |
+ |
+ // Note: Not using base::RandInt() because it is horribly slow on debug |
+ // builds. The following is a very simple, deterministic LCG: |
+ int NextRandomInt() { |
+ rand_seed_ = (1103515245 * rand_seed_ + 12345) % (1 << 31); |
+ return static_cast<int>(rand_seed_); |
+ } |
+ |
+ uint32_t rand_seed_ = 0x7e110; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(AudioRechunkerTest); |
+}; |
+ |
+// Tests an atypical edge case: Push()ing one frame at a time. |
+TEST_P(AudioRechunkerTest, PushOneFrameAtATime) { |
+ RunSimpleRechunkTest(1); |
+} |
+ |
+// Tests that re-chunking the audio from common platform input chunk sizes |
+// works. |
+TEST_P(AudioRechunkerTest, Push128FramesAtATime) { |
+ RunSimpleRechunkTest(128); |
+} |
+TEST_P(AudioRechunkerTest, Push512FramesAtATime) { |
+ RunSimpleRechunkTest(512); |
+} |
+ |
+// Tests that re-chunking the audio from common "10 ms" input chunk sizes |
+// works (44100 Hz * 10 ms = 441, and 48000 Hz * 10 ms = 480). |
+TEST_P(AudioRechunkerTest, Push441FramesAtATime) { |
+ RunSimpleRechunkTest(441); |
+} |
+TEST_P(AudioRechunkerTest, Push480FramesAtATime) { |
+ RunSimpleRechunkTest(480); |
+} |
+ |
+// Tests that re-chunking when input audio is provided in varying chunk sizes |
+// works. |
+TEST_P(AudioRechunkerTest, PushArbitraryNumbersOfFramesAtATime) { |
+ // The loop below will run until both: 1) kMinNumIterations loops have |
+ // occurred; and 2) there are at least 3 entries in |results_|. |
+ const int kMinNumIterations = 30; |
+ |
+ int sample_value = 0; |
+ int frames_pushed_so_far = 0; |
+ for (int i = 0; i < kMinNumIterations || results_.size() < 3; ++i) { |
+ EXPECT_EQ(GetExpectedOutputChunks(frames_pushed_so_far), results_.size()); |
+ |
+ // Create an AudioBus of a random length, populated with sample values. |
+ const int input_chunk_size = GetRandomInRange(1, 1920); |
+ const scoped_ptr<AudioBus> audio_bus = |
+ AudioBus::Create(1, input_chunk_size); |
+ for (int j = 0; j < audio_bus->frames(); ++j) |
+ audio_bus->channel(0)[j] = static_cast<float>(sample_value++); |
+ |
+ rechunker_->Push(*audio_bus, base::TimeDelta::FromMicroseconds( |
+ frames_pushed_so_far * INT64_C(1000000) / |
+ GetParam().sample_rate)); |
+ // Note: Rechunker has just called ReceiveAndCheckNextChunk() zero or more |
+ // times. |
+ |
+ frames_pushed_so_far += input_chunk_size; |
+ } |
+ EXPECT_EQ(GetExpectedOutputChunks(frames_pushed_so_far), results_.size()); |
+ |
+ ASSERT_FALSE(results_.empty()); |
+ EXPECT_EQ(0.0f, results_.front().first_sample_value); |
+ const float last_value_in_last_chunk = |
+ static_cast<float>(GetExpectedOutputChunks(frames_pushed_so_far) * |
+ GetParam().output_chunk_size - |
+ 1); |
+ EXPECT_EQ(last_value_in_last_chunk, results_.back().last_sample_value); |
+} |
+ |
+INSTANTIATE_TEST_CASE_P(, |
+ AudioRechunkerTest, |
+ ::testing::Values( |
+ // 1 ms output chunks at common sample rates. |
+ TestParams(1000, 16000, 16, true), |
+ TestParams(1000, 22050, 22, false), |
+ TestParams(1000, 44100, 44, false), |
+ TestParams(1000, 48000, 48, true), |
+ |
+ // 10 ms output chunks at common sample rates. |
+ TestParams(10000, 16000, 160, true), |
+ TestParams(10000, 22050, 220, false), |
+ TestParams(10000, 44100, 441, true), |
+ TestParams(10000, 48000, 480, true), |
+ |
+ // 60 ms output chunks at common sample rates. |
+ TestParams(60000, 16000, 960, true), |
+ TestParams(60000, 22050, 1323, true), |
+ TestParams(60000, 44100, 2646, true), |
+ TestParams(60000, 48000, 2880, true))); |
+ |
+} // namespace |
+ |
+} // namespace media |