| Index: third_party/WebKit/Source/platform/audio/PushPullFIFOMultithreadTest.cpp
|
| diff --git a/third_party/WebKit/Source/platform/audio/PushPullFIFOMultithreadTest.cpp b/third_party/WebKit/Source/platform/audio/PushPullFIFOMultithreadTest.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..24b0fa91a8b8fb70333625ddbf89b97c459dda16
|
| --- /dev/null
|
| +++ b/third_party/WebKit/Source/platform/audio/PushPullFIFOMultithreadTest.cpp
|
| @@ -0,0 +1,235 @@
|
| +// Copyright 2017 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 "platform/audio/PushPullFIFO.h"
|
| +
|
| +#include <memory>
|
| +#include <vector>
|
| +#include "platform/CrossThreadFunctional.h"
|
| +#include "platform/WaitableEvent.h"
|
| +#include "platform/WebTaskRunner.h"
|
| +#include "platform/audio/AudioUtilities.h"
|
| +#include "platform/testing/UnitTestHelpers.h"
|
| +#include "platform/wtf/Functional.h"
|
| +#include "platform/wtf/PtrUtil.h"
|
| +#include "public/platform/Platform.h"
|
| +#include "public/platform/WebThread.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +namespace blink {
|
| +
|
| +namespace {
|
| +
|
| +// To forcibly stop the message loop.
|
| +// TODO(hongchan): move this hack into Test class when the solution is found.
|
| +void FinishTest() {
|
| + LOG(INFO) << "FinishTest";
|
| + testing::ExitRunLoop();
|
| +}
|
| +
|
| +// To wait for spawned threads to finish their tasks.
|
| +// TODO(hongchan): move this hack into Test class when the solution is found.
|
| +void HoldTestForDuration(double duration_ms) {
|
| + LOG(INFO) << "HoldTestForDuration";
|
| + Platform::Current()->CurrentThread()->GetWebTaskRunner()->PostDelayedTask(
|
| + BLINK_FROM_HERE,
|
| + WTF::Bind(&FinishTest),
|
| + duration_ms);
|
| + testing::EnterRunLoop();
|
| +}
|
| +
|
| +// Base FIFOClient with an extra thread for looping and jitter control. The
|
| +// child class must define a specific task to run on the thread.
|
| +class FIFOClient {
|
| + public:
|
| + FIFOClient(PushPullFIFO* fifo, size_t bus_length, size_t jitter_range_ms)
|
| + : fifo_(fifo),
|
| + bus_(AudioBus::Create(fifo->NumberOfChannels(), bus_length)),
|
| + client_thread_(WTF::WrapUnique(
|
| + Platform::Current()->CreateThread("client thread"))),
|
| + jitter_range_ms_(jitter_range_ms) {}
|
| +
|
| + void Start(double duration_ms, double interval_ms) {
|
| + duration_ms_ = duration_ms;
|
| + interval_ms_ = interval_ms;
|
| + client_thread_->GetWebTaskRunner()->PostTask(
|
| + BLINK_FROM_HERE,
|
| + CrossThreadBind(&FIFOClient::RunTaskOnOwnThread,
|
| + CrossThreadUnretained(this)));
|
| + }
|
| +
|
| + virtual void Stop(int callback_counter) = 0;
|
| + virtual void RunTask() = 0;
|
| +
|
| + void Pull(size_t frames_to_pull) {
|
| + fifo_->Pull(bus_.Get(), frames_to_pull);
|
| + }
|
| +
|
| + void Push() {
|
| + fifo_->Push(bus_.Get());
|
| + }
|
| +
|
| + private:
|
| + void RunTaskOnOwnThread() {
|
| + double interval_with_jitter = interval_ms_
|
| + + (static_cast<double>(std::rand()) / RAND_MAX) * jitter_range_ms_;
|
| + elapsed_ms_ += interval_with_jitter;
|
| + ++counter_;
|
| + RunTask();
|
| + if (elapsed_ms_ < duration_ms_) {
|
| + client_thread_->GetWebTaskRunner()->PostDelayedTask(
|
| + BLINK_FROM_HERE,
|
| + CrossThreadBind(&FIFOClient::RunTaskOnOwnThread,
|
| + CrossThreadUnretained(this)),
|
| + interval_with_jitter);
|
| + } else {
|
| + Stop(counter_);
|
| + }
|
| + }
|
| +
|
| + PushPullFIFO* fifo_;
|
| + RefPtr<AudioBus> bus_;
|
| + std::unique_ptr<WebThread> client_thread_;
|
| +
|
| + // Test duration.
|
| + double duration_ms_;
|
| +
|
| + // Interval between each callback.
|
| + double interval_ms_;
|
| +
|
| + // Jitter added to the regular pushing/pulling interval.
|
| + // (where j is 0 < j < jitter_range_ms)
|
| + double jitter_range_ms_;
|
| +
|
| + // Elapsed test duration.
|
| + double elapsed_ms_ = 0;
|
| +
|
| + // Counter variable for the total number of callbacks invoked.
|
| + int counter_ = 0;
|
| +};
|
| +
|
| +// FIFO-pulling client (consumer). This mimics the audio device thread.
|
| +// |frames_to_pull| is variable.
|
| +class PullClient : public FIFOClient {
|
| + public:
|
| + PullClient(PushPullFIFO* fifo, size_t frames_to_pull, double jitter_range_ms)
|
| + : FIFOClient(fifo, frames_to_pull, jitter_range_ms),
|
| + frames_to_pull_(frames_to_pull) {
|
| + }
|
| +
|
| + void RunTask() override {
|
| + Pull(frames_to_pull_);
|
| + }
|
| +
|
| + void Stop(int callback_counter) override {
|
| + LOG(INFO) << "PullClient stopped. (" << callback_counter << " calls)";
|
| + }
|
| +
|
| + private:
|
| + size_t frames_to_pull_;
|
| +};
|
| +
|
| +// FIFO-pushing client (producer). This mimics the WebAudio rendering thread.
|
| +// The frames to push are static as 128 frames.
|
| +class PushClient : public FIFOClient {
|
| + public:
|
| + PushClient(PushPullFIFO* fifo, size_t frames_to_push, double jitter_range_ms)
|
| + : FIFOClient(fifo, frames_to_push, jitter_range_ms) {}
|
| +
|
| + void RunTask() override {
|
| + Push();
|
| + }
|
| +
|
| + void Stop(int callback_counter) override {
|
| + LOG(INFO) << "PushClient stopped. (" << callback_counter << " calls)";
|
| + }
|
| +};
|
| +
|
| +struct FIFOSmokeTestParam {
|
| + const double sample_rate;
|
| + const unsigned number_of_channels;
|
| + const size_t fifo_length;
|
| + const double test_duration_ms;
|
| + // Buffer size for pulling. Equivalent of |callback_buffer_size|.
|
| + const size_t pull_buffer_size;
|
| + // Jitter range for the pulling interval.
|
| + const double pull_jitter_range_ms;
|
| + // Buffer size for pushing. Equivalent of WebAudio render quantum.
|
| + const size_t push_buffer_size;
|
| + // Jitter range for the pushing interval.
|
| + const double push_jitter_range_ms;
|
| +};
|
| +
|
| +class PushPullFIFOSmokeTest
|
| + : public ::testing::TestWithParam<FIFOSmokeTestParam> {};
|
| +
|
| +TEST_P(PushPullFIFOSmokeTest, SmokeTests) {
|
| + const FIFOSmokeTestParam param = GetParam();
|
| + const double sample_rate = param.sample_rate * 4;
|
| +
|
| + const double pull_interval_ms =
|
| + param.pull_buffer_size / sample_rate * 1000;
|
| + const double push_interval_ms =
|
| + param.push_buffer_size / sample_rate * 1000;
|
| +
|
| + std::unique_ptr<PushPullFIFO> test_fifo = WTF::WrapUnique(
|
| + new PushPullFIFO(param.number_of_channels, param.fifo_length));
|
| + std::unique_ptr<PullClient> pull_client = WTF::WrapUnique(new PullClient(
|
| + test_fifo.get(), param.pull_buffer_size, param.pull_jitter_range_ms));
|
| + std::unique_ptr<PushClient> push_client = WTF::WrapUnique(new PushClient(
|
| + test_fifo.get(), param.push_buffer_size, param.push_jitter_range_ms));
|
| +
|
| + LOG(INFO) << "PushPullFIFOSmokeTest - Start";
|
| +
|
| + pull_client->Start(param.test_duration_ms, pull_interval_ms);
|
| + push_client->Start(param.test_duration_ms, push_interval_ms);
|
| +
|
| + // If the operation does not cause a crash for the test period, it's passed.
|
| + // Also give a bit more time to finish the tear-down process.
|
| + HoldTestForDuration(param.test_duration_ms + 150);
|
| +}
|
| +
|
| +FIFOSmokeTestParam smoke_test_params[] = {
|
| + // Test case 0 (OSX): 256 Pull, 128 Push, Minimal jitter.
|
| + // WebThread's priority is lower than the device thread, so its jitter range
|
| + // is slightly bigger than the other.
|
| + {48000, 2, 8192, 1000, 256, 1, 128, 2},
|
| +
|
| + // Test case 1 (Windows): 441 Pull, 128 Push. Moderate Jitter.
|
| + // Windows' audio callback is known to be ~10ms and UMA data shows the
|
| + // evidence for it. The jitter range was determined speculatively.
|
| + {44100, 2, 8192, 1000, 441, 2, 128, 3},
|
| +
|
| + // Test case 2 (Ubuntu/Linux): 512 Pull, 128 Push. Unstable callback, but
|
| + // fast CPU. A typical configuration for Ubuntu + PulseAudio setup.
|
| + // PulseAudio's callback is known to be rather unstable.
|
| + {48000, 2, 8192, 1000, 512, 8, 128, 1},
|
| +
|
| + // Test case 3 (Android-Reference): 512 Pull, 128 Push. Similar to Linux, but
|
| + // low profile CPU.
|
| + {44100, 2, 8192, 1000, 512, 8, 128, 3},
|
| +
|
| + // Test case 4 (Android-ExternalA): 441 Pull, 128 Push. Extreme jitter with
|
| + // low profile CPU.
|
| + {44100, 2, 8192, 1000, 441, 24, 128, 8},
|
| +
|
| + // Test case 5 (Android-ExternalB): 5768 Pull, 128 Push. Huge callback with
|
| + // large jitter. Low profile CPU.
|
| + {44100, 2, 8192, 1000, 5768, 120, 128, 12},
|
| +
|
| + // Test case 6 (User-specified buffer size): 960 Pull, 128 Push. Minimal
|
| + // Jitter. 960 frames = 20ms at 48KHz.
|
| + {48000, 2, 8192, 1000, 960, 1, 128, 1},
|
| +
|
| + // Test case 7 (Longer test duration): 256 Pull, 128 Push. 10 seconds.
|
| + {48000, 2, 8192, 10000, 256, 0, 128, 1}
|
| +};
|
| +
|
| +INSTANTIATE_TEST_CASE_P(PushPullFIFOSmokeTest,
|
| + PushPullFIFOSmokeTest,
|
| + ::testing::ValuesIn(smoke_test_params));
|
| +
|
| +} // namespace
|
| +
|
| +} // namespace blink
|
|
|