| 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..f68657718f547503f24d66bc58efb1e319ee340c
|
| --- /dev/null
|
| +++ b/media/base/audio_rechunker_unittest.cc
|
| @@ -0,0 +1,237 @@
|
| +// 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);
|
| + }
|
| +
|
| + // Pushes constant-sized batches of input samples and checks that the input
|
| + // data is re-chunked correctly.
|
| + void RunPushSameNumberOfFramesTest(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) {
|
| + RunPushSameNumberOfFramesTest(1);
|
| +}
|
| +
|
| +// Tests that re-chunking the audio from common platform input chunk sizes
|
| +// works.
|
| +TEST_P(AudioRechunkerTest, Push128FramesAtATime) {
|
| + RunPushSameNumberOfFramesTest(128);
|
| +}
|
| +TEST_P(AudioRechunkerTest, Push512FramesAtATime) {
|
| + RunPushSameNumberOfFramesTest(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) {
|
| + RunPushSameNumberOfFramesTest(441);
|
| +}
|
| +TEST_P(AudioRechunkerTest, Push480FramesAtATime) {
|
| + RunPushSameNumberOfFramesTest(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
|
|
|