Commit 1492075f authored by hongchan's avatar hongchan Committed by Commit bot

Add WebThread in AudioDestination to support AudioWorkletThread

This CL to introduce a WebThread to run WebAudio graph rendering.

FYI: with the box 2d demo on my local machine (MacPro), the release
build of this patch was able to render 50 cubes concurrently before
glitch, which is similar to the current stable build that runs only on the
audio device thread. No significant performance regression is observed.

Design doc: https://docs.google.com/document/d/1WnqZHcA2TQjS1aKvzx2nnJ2y2tWK1Xp_0BhFGpsryaQ/edit#

BUG=706133

Review-Url: https://codereview.chromium.org/2777903005
Cr-Commit-Position: refs/heads/master@{#467819}
parent a0d005f4
...@@ -2207,6 +2207,11 @@ source_set("unit_tests") { ...@@ -2207,6 +2207,11 @@ source_set("unit_tests") {
# TODO(toyoshim): Remove Platform dependency and move to loader/BUILD.gn # TODO(toyoshim): Remove Platform dependency and move to loader/BUILD.gn
"loader/fetch/ResourceResponseTest.cpp", "loader/fetch/ResourceResponseTest.cpp",
# TODO(hongchan): Platform::Current()->CreateThread() returns nullptr
# without adding the test file into this section. Remove platform dependency
# when creating a valid thread becomes possible in the Blink unit test.
"audio/PushPullFIFOMultithreadTest.cpp",
] ]
configs += [ configs += [
......
...@@ -29,7 +29,9 @@ ...@@ -29,7 +29,9 @@
#include "platform/audio/AudioDestination.h" #include "platform/audio/AudioDestination.h"
#include <memory> #include <memory>
#include "platform/CrossThreadFunctional.h"
#include "platform/Histogram.h" #include "platform/Histogram.h"
#include "platform/WebTaskRunner.h"
#include "platform/audio/AudioUtilities.h" #include "platform/audio/AudioUtilities.h"
#include "platform/audio/PushPullFIFO.h" #include "platform/audio/PushPullFIFO.h"
#include "platform/weborigin/SecurityOrigin.h" #include "platform/weborigin/SecurityOrigin.h"
...@@ -37,6 +39,7 @@ ...@@ -37,6 +39,7 @@
#include "public/platform/Platform.h" #include "public/platform/Platform.h"
#include "public/platform/WebAudioLatencyHint.h" #include "public/platform/WebAudioLatencyHint.h"
#include "public/platform/WebSecurityOrigin.h" #include "public/platform/WebSecurityOrigin.h"
#include "public/platform/WebThread.h"
namespace blink { namespace blink {
...@@ -64,14 +67,16 @@ AudioDestination::AudioDestination(AudioIOCallback& callback, ...@@ -64,14 +67,16 @@ AudioDestination::AudioDestination(AudioIOCallback& callback,
PassRefPtr<SecurityOrigin> security_origin) PassRefPtr<SecurityOrigin> security_origin)
: number_of_output_channels_(number_of_output_channels), : number_of_output_channels_(number_of_output_channels),
is_playing_(false), is_playing_(false),
callback_(callback), rendering_thread_(WTF::WrapUnique(
Platform::Current()->CreateThread("WebAudio Rendering Thread"))),
fifo_(WTF::WrapUnique(
new PushPullFIFO(number_of_output_channels, kFIFOSize))),
output_bus_(AudioBus::Create(number_of_output_channels, output_bus_(AudioBus::Create(number_of_output_channels,
AudioUtilities::kRenderQuantumFrames, AudioUtilities::kRenderQuantumFrames,
false)), false)),
render_bus_(AudioBus::Create(number_of_output_channels, render_bus_(AudioBus::Create(number_of_output_channels,
AudioUtilities::kRenderQuantumFrames)), AudioUtilities::kRenderQuantumFrames)),
fifo_(WTF::WrapUnique( callback_(callback),
new PushPullFIFO(number_of_output_channels, kFIFOSize))),
frames_elapsed_(0) { frames_elapsed_(0) {
// Create WebAudioDevice. blink::WebAudioDevice is designed to support the // Create WebAudioDevice. blink::WebAudioDevice is designed to support the
// local input (e.g. loopback from OS audio system), but Chromium's media // local input (e.g. loopback from OS audio system), but Chromium's media
...@@ -97,6 +102,9 @@ void AudioDestination::Render(const WebVector<float*>& destination_data, ...@@ -97,6 +102,9 @@ void AudioDestination::Render(const WebVector<float*>& destination_data,
double delay, double delay,
double delay_timestamp, double delay_timestamp,
size_t prior_frames_skipped) { size_t prior_frames_skipped) {
// This method is called by AudioDeviceThread.
DCHECK(!IsRenderingThread());
CHECK_EQ(destination_data.size(), number_of_output_channels_); CHECK_EQ(destination_data.size(), number_of_output_channels_);
CHECK_EQ(number_of_frames, callback_buffer_size_); CHECK_EQ(number_of_frames, callback_buffer_size_);
...@@ -106,25 +114,36 @@ void AudioDestination::Render(const WebVector<float*>& destination_data, ...@@ -106,25 +114,36 @@ void AudioDestination::Render(const WebVector<float*>& destination_data,
if (!fifo_ || fifo_->length() < number_of_frames) if (!fifo_ || fifo_->length() < number_of_frames)
return; return;
frames_elapsed_ -= std::min(frames_elapsed_, prior_frames_skipped);
double output_position =
frames_elapsed_ / static_cast<double>(web_audio_device_->SampleRate()) -
delay;
output_position_.position = output_position;
output_position_.timestamp = delay_timestamp;
output_position_received_timestamp_ = base::TimeTicks::Now();
// Associate the destination data array with the output bus then fill the // Associate the destination data array with the output bus then fill the
// FIFO. // FIFO.
for (unsigned i = 0; i < number_of_output_channels_; ++i) for (unsigned i = 0; i < number_of_output_channels_; ++i)
output_bus_->SetChannelMemory(i, destination_data[i], number_of_frames); output_bus_->SetChannelMemory(i, destination_data[i], number_of_frames);
// Number of frames to render via WebAudio graph. |framesToRender > 0| means size_t frames_to_render = fifo_->Pull(output_bus_.Get(), number_of_frames);
// the frames in FIFO is not enough to fulfill the requested frames from the
// audio device. rendering_thread_->GetWebTaskRunner()->PostTask(
size_t frames_to_render = number_of_frames > fifo_->FramesAvailable() BLINK_FROM_HERE,
? number_of_frames - fifo_->FramesAvailable() CrossThreadBind(&AudioDestination::RequestRenderOnWebThread,
: 0; CrossThreadUnretained(this),
number_of_frames, frames_to_render,
delay, delay_timestamp, prior_frames_skipped));
}
void AudioDestination::RequestRenderOnWebThread(size_t frames_requested,
size_t frames_to_render,
double delay,
double delay_timestamp,
size_t prior_frames_skipped) {
// This method is called by WebThread.
DCHECK(IsRenderingThread());
frames_elapsed_ -= std::min(frames_elapsed_, prior_frames_skipped);
AudioIOPosition output_position;
output_position.position =
frames_elapsed_ / static_cast<double>(web_audio_device_->SampleRate()) -
delay;
output_position.timestamp = delay_timestamp;
base::TimeTicks received_timestamp = base::TimeTicks::Now();
for (size_t pushed_frames = 0; pushed_frames < frames_to_render; for (size_t pushed_frames = 0; pushed_frames < frames_to_render;
pushed_frames += AudioUtilities::kRenderQuantumFrames) { pushed_frames += AudioUtilities::kRenderQuantumFrames) {
...@@ -132,27 +151,23 @@ void AudioDestination::Render(const WebVector<float*>& destination_data, ...@@ -132,27 +151,23 @@ void AudioDestination::Render(const WebVector<float*>& destination_data,
// we do not want output position to get stuck so we promote it // we do not want output position to get stuck so we promote it
// using the elapsed time from the moment it was initially obtained. // using the elapsed time from the moment it was initially obtained.
if (callback_buffer_size_ > AudioUtilities::kRenderQuantumFrames * 2) { if (callback_buffer_size_ > AudioUtilities::kRenderQuantumFrames * 2) {
double delta = double delta = (base::TimeTicks::Now() - received_timestamp).InSecondsF();
(base::TimeTicks::Now() - output_position_received_timestamp_) output_position.position += delta;
.InSecondsF(); output_position.timestamp += delta;
output_position_.position += delta;
output_position_.timestamp += delta;
} }
// Some implementations give only rough estimation of |delay| so // Some implementations give only rough estimation of |delay| so
// we might have negative estimation |outputPosition| value. // we might have negative estimation |outputPosition| value.
if (output_position_.position < 0.0) if (output_position.position < 0.0)
output_position_.position = 0.0; output_position.position = 0.0;
// Process WebAudio graph and push the rendered output to FIFO. // Process WebAudio graph and push the rendered output to FIFO.
callback_.Render(nullptr, render_bus_.Get(), callback_.Render(nullptr, render_bus_.Get(),
AudioUtilities::kRenderQuantumFrames, output_position_); AudioUtilities::kRenderQuantumFrames, output_position);
fifo_->Push(render_bus_.Get()); fifo_->Push(render_bus_.Get());
} }
fifo_->Pull(output_bus_.Get(), number_of_frames); frames_elapsed_ += frames_requested;
frames_elapsed_ += number_of_frames;
} }
void AudioDestination::Start() { void AudioDestination::Start() {
...@@ -204,4 +219,9 @@ bool AudioDestination::CheckBufferSize() { ...@@ -204,4 +219,9 @@ bool AudioDestination::CheckBufferSize() {
return is_buffer_size_valid; return is_buffer_size_valid;
} }
bool AudioDestination::IsRenderingThread() {
return static_cast<ThreadIdentifier>(rendering_thread_->ThreadId()) ==
CurrentThread();
}
} // namespace blink } // namespace blink
...@@ -43,6 +43,7 @@ namespace blink { ...@@ -43,6 +43,7 @@ namespace blink {
class PushPullFIFO; class PushPullFIFO;
class SecurityOrigin; class SecurityOrigin;
class WebAudioLatencyHint; class WebAudioLatencyHint;
class WebThread;
// The AudioDestination class is an audio sink interface between the media // The AudioDestination class is an audio sink interface between the media
// renderer and the Blink's WebAudio module. It has a FIFO to adapt the // renderer and the Blink's WebAudio module. It has a FIFO to adapt the
...@@ -73,6 +74,14 @@ class PLATFORM_EXPORT AudioDestination : public WebAudioDevice::RenderCallback { ...@@ -73,6 +74,14 @@ class PLATFORM_EXPORT AudioDestination : public WebAudioDevice::RenderCallback {
double delay_timestamp, double delay_timestamp,
size_t prior_frames_skipped) override; size_t prior_frames_skipped) override;
// The actual render request to the WebAudio destination node. This triggers
// the WebAudio rendering pipe line on the web thread.
void RequestRenderOnWebThread(size_t frames_requested,
size_t frames_to_render,
double delay,
double delay_timestamp,
size_t prior_frames_skipped);
virtual void Start(); virtual void Start();
virtual void Stop(); virtual void Stop();
...@@ -90,32 +99,38 @@ class PLATFORM_EXPORT AudioDestination : public WebAudioDevice::RenderCallback { ...@@ -90,32 +99,38 @@ class PLATFORM_EXPORT AudioDestination : public WebAudioDevice::RenderCallback {
static unsigned long MaxChannelCount(); static unsigned long MaxChannelCount();
private: private:
// Check if the buffer size chosen by the WebAudioDevice is too large.
bool CheckBufferSize();
size_t HardwareBufferSize();
bool IsRenderingThread();
std::unique_ptr<WebAudioDevice> web_audio_device_; std::unique_ptr<WebAudioDevice> web_audio_device_;
unsigned number_of_output_channels_; const unsigned number_of_output_channels_;
size_t callback_buffer_size_; size_t callback_buffer_size_;
bool is_playing_; bool is_playing_;
// The render callback function of WebAudio engine. (i.e. DestinationNode) // Rendering thread for WebAudio graph.
AudioIOCallback& callback_; std::unique_ptr<WebThread> rendering_thread_;
// To pass the data from FIFO to the audio device callback. // Accessed by both threads: resolves the buffer size mismatch between the
// WebAudio engine and the callback function from the actual audio device.
std::unique_ptr<PushPullFIFO> fifo_;
// Accessed by device thread: to pass the data from FIFO to the device.
RefPtr<AudioBus> output_bus_; RefPtr<AudioBus> output_bus_;
// To push the rendered result from WebAudio graph into the FIFO. // Accessed by rendering thread: to push the rendered result from WebAudio
// graph into the FIFO.
RefPtr<AudioBus> render_bus_; RefPtr<AudioBus> render_bus_;
// Resolves the buffer size mismatch between the WebAudio engine and // Accessed by rendering thread: the render callback function of WebAudio
// the callback function from the actual audio device. // engine. (i.e. DestinationNode)
std::unique_ptr<PushPullFIFO> fifo_; AudioIOCallback& callback_;
// Accessed by rendering thread.
size_t frames_elapsed_; size_t frames_elapsed_;
AudioIOPosition output_position_;
base::TimeTicks output_position_received_timestamp_;
// Check if the buffer size chosen by the WebAudioDevice is too large.
bool CheckBufferSize();
size_t HardwareBufferSize();
}; };
} // namespace blink } // namespace blink
......
...@@ -19,21 +19,18 @@ const unsigned kMaxMessagesToLog = 100; ...@@ -19,21 +19,18 @@ const unsigned kMaxMessagesToLog = 100;
const size_t PushPullFIFO::kMaxFIFOLength = 65536; const size_t PushPullFIFO::kMaxFIFOLength = 65536;
PushPullFIFO::PushPullFIFO(unsigned number_of_channels, size_t fifo_length) PushPullFIFO::PushPullFIFO(unsigned number_of_channels, size_t fifo_length)
: fifo_length_(fifo_length), : fifo_length_(fifo_length) {
frames_available_(0),
index_read_(0),
index_write_(0),
overflow_count_(0),
underflow_count_(0) {
CHECK_LE(fifo_length_, kMaxFIFOLength); CHECK_LE(fifo_length_, kMaxFIFOLength);
fifo_bus_ = AudioBus::Create(number_of_channels, fifo_length_); fifo_bus_ = AudioBus::Create(number_of_channels, fifo_length_);
} }
PushPullFIFO::~PushPullFIFO() {} PushPullFIFO::~PushPullFIFO() {}
// Push the data from |inputBus| to FIFO. The size of push is determined by // Push the data from |input_bus| to FIFO. The size of push is determined by
// the length of |inputBus|. // the length of |input_bus|.
void PushPullFIFO::Push(const AudioBus* input_bus) { void PushPullFIFO::Push(const AudioBus* input_bus) {
MutexLocker locker(lock_);
CHECK(input_bus); CHECK(input_bus);
CHECK_EQ(input_bus->length(), AudioUtilities::kRenderQuantumFrames); CHECK_EQ(input_bus->length(), AudioUtilities::kRenderQuantumFrames);
SECURITY_CHECK(input_bus->length() <= fifo_length_); SECURITY_CHECK(input_bus->length() <= fifo_length_);
...@@ -61,8 +58,8 @@ void PushPullFIFO::Push(const AudioBus* input_bus) { ...@@ -61,8 +58,8 @@ void PushPullFIFO::Push(const AudioBus* input_bus) {
// Update the write index; wrap it around if necessary. // Update the write index; wrap it around if necessary.
index_write_ = (index_write_ + input_bus_length) % fifo_length_; index_write_ = (index_write_ + input_bus_length) % fifo_length_;
// In case of overflow, move the |indexRead| to the updated |indexWrite| to // In case of overflow, move the |index_read_| to the updated |index_write_|
// avoid reading overwritten frames by the next pull. // to avoid reading overwritten frames by the next pull.
if (input_bus_length > fifo_length_ - frames_available_) { if (input_bus_length > fifo_length_ - frames_available_) {
index_read_ = index_write_; index_read_ = index_write_;
if (++overflow_count_ < kMaxMessagesToLog) { if (++overflow_count_ < kMaxMessagesToLog) {
...@@ -80,16 +77,18 @@ void PushPullFIFO::Push(const AudioBus* input_bus) { ...@@ -80,16 +77,18 @@ void PushPullFIFO::Push(const AudioBus* input_bus) {
DCHECK_EQ((index_read_ + frames_available_) % fifo_length_, index_write_); DCHECK_EQ((index_read_ + frames_available_) % fifo_length_, index_write_);
} }
// Pull the data out of FIFO to |outputBus|. If remaining frame in the FIFO // Pull the data out of FIFO to |output_bus|. If remaining frame in the FIFO
// is less than the frames to pull, provides remaining frame plus the silence. // is less than the frames to pull, provides remaining frame plus the silence.
void PushPullFIFO::Pull(AudioBus* output_bus, size_t frames_requested) { size_t PushPullFIFO::Pull(AudioBus* output_bus, size_t frames_requested) {
MutexLocker locker(lock_);
#if OS(ANDROID) #if OS(ANDROID)
if (!output_bus) { if (!output_bus) {
// Log when outputBus or FIFO object is invalid. (crbug.com/692423) // Log when outputBus or FIFO object is invalid. (crbug.com/692423)
LOG(WARNING) << "[WebAudio/PushPullFIFO::pull <" << static_cast<void*>(this) LOG(WARNING) << "[WebAudio/PushPullFIFO::pull <" << static_cast<void*>(this)
<< ">] |outputBus| is invalid."; << ">] |outputBus| is invalid.";
// Silently return to avoid crash. // Silently return to avoid crash.
return; return 0;
} }
// The following checks are in place to catch the inexplicable crash. // The following checks are in place to catch the inexplicable crash.
...@@ -110,6 +109,7 @@ void PushPullFIFO::Pull(AudioBus* output_bus, size_t frames_requested) { ...@@ -110,6 +109,7 @@ void PushPullFIFO::Pull(AudioBus* output_bus, size_t frames_requested) {
<< " >= " << fifo_length_ << ")"; << " >= " << fifo_length_ << ")";
} }
#endif #endif
CHECK(output_bus); CHECK(output_bus);
SECURITY_CHECK(frames_requested <= output_bus->length()); SECURITY_CHECK(frames_requested <= output_bus->length());
SECURITY_CHECK(frames_requested <= fifo_length_); SECURITY_CHECK(frames_requested <= fifo_length_);
...@@ -162,10 +162,16 @@ void PushPullFIFO::Pull(AudioBus* output_bus, size_t frames_requested) { ...@@ -162,10 +162,16 @@ void PushPullFIFO::Pull(AudioBus* output_bus, size_t frames_requested) {
// Update the number of frames in FIFO. // Update the number of frames in FIFO.
frames_available_ -= frames_to_fill; frames_available_ -= frames_to_fill;
DCHECK_EQ((index_read_ + frames_available_) % fifo_length_, index_write_); DCHECK_EQ((index_read_ + frames_available_) % fifo_length_, index_write_);
// |frames_requested > frames_available_| means the frames in FIFO is not
// enough to fulfill the requested frames from the audio device.
return frames_requested > frames_available_
? frames_requested - frames_available_
: 0;
} }
const PushPullFIFOStateForTest PushPullFIFO::GetStateForTest() const { const PushPullFIFOStateForTest PushPullFIFO::GetStateForTest() const {
return {length(), NumberOfChannels(), FramesAvailable(), index_read_, return {length(), NumberOfChannels(), frames_available_, index_read_,
index_write_, overflow_count_, underflow_count_}; index_write_, overflow_count_, underflow_count_};
} }
......
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
#include "platform/audio/AudioBus.h" #include "platform/audio/AudioBus.h"
#include "platform/wtf/Allocator.h" #include "platform/wtf/Allocator.h"
#include "platform/wtf/Functional.h"
#include "platform/wtf/Threading.h"
#include "platform/wtf/ThreadingPrimitives.h"
#include "public/platform/WebCommon.h" #include "public/platform/WebCommon.h"
namespace blink { namespace blink {
...@@ -26,6 +29,14 @@ struct PushPullFIFOStateForTest { ...@@ -26,6 +29,14 @@ struct PushPullFIFOStateForTest {
// Blink-WebAudio and the renderer. The renderer's hardware callback buffer size // Blink-WebAudio and the renderer. The renderer's hardware callback buffer size
// varies on the platform, but the WebAudio always renders 128 frames (render // varies on the platform, but the WebAudio always renders 128 frames (render
// quantum, RQ) thus FIFO is needed to handle the general case. // quantum, RQ) thus FIFO is needed to handle the general case.
//
// Note that this object is concurrently accessed by two threads; WebAudio
// rendering thread (WebThread) in Blink and the audio device thread
// (AudioDeviceThread) from the media renderer. The push/pull operations touch
// most of variables in the class (index_write_, index_read_, frames_available_,
// and fifo_Bus_) so the thread safety must be handled with care.
//
// TODO(hongchan): add a unit test for multi-thread access.
class BLINK_PLATFORM_EXPORT PushPullFIFO { class BLINK_PLATFORM_EXPORT PushPullFIFO {
USING_FAST_MALLOC(PushPullFIFO); USING_FAST_MALLOC(PushPullFIFO);
WTF_MAKE_NONCOPYABLE(PushPullFIFO); WTF_MAKE_NONCOPYABLE(PushPullFIFO);
...@@ -34,50 +45,53 @@ class BLINK_PLATFORM_EXPORT PushPullFIFO { ...@@ -34,50 +45,53 @@ class BLINK_PLATFORM_EXPORT PushPullFIFO {
// Maximum FIFO length. (512 render quanta) // Maximum FIFO length. (512 render quanta)
static const size_t kMaxFIFOLength; static const size_t kMaxFIFOLength;
// |fifoLength| cannot exceed |kMaxFIFOLength|. Otherwise it crashes. // |fifo_length| cannot exceed |kMaxFIFOLength|. Otherwise it crashes.
explicit PushPullFIFO(unsigned number_of_channels, size_t fifo_length); explicit PushPullFIFO(unsigned number_of_channels, size_t fifo_length);
~PushPullFIFO(); ~PushPullFIFO();
// Pushes the rendered frames by WebAudio engine. // Pushes the rendered frames by WebAudio engine.
// - The |inputBus| length is 128 frames (1 render quantum), fixed. // - The |input_bus| length is 128 frames (1 render quantum), fixed.
// - In case of overflow (FIFO full while push), the existing frames in FIFO // - In case of overflow (FIFO full while push), the existing frames in FIFO
// will be overwritten and |indexRead| will be forcibly moved to // will be overwritten and |index_read_| will be forcibly moved to
// |indexWrite| to avoid reading overwritten frames. // |index_write_| to avoid reading overwritten frames.
void Push(const AudioBus* input_bus); void Push(const AudioBus* input_bus);
// Pulling |framesRequested| by the audio device thread. // Pulls |frames_requested| by the audio device thread and returns the actual
// - If |framesRequested| is bigger than the length of |outputBus|, it // number of frames to be rendered by the source. (i.e. WebAudio graph)
// - If |frames_requested| is bigger than the length of |output_bus|, it
// violates SECURITY_CHECK(). // violates SECURITY_CHECK().
// - If |framesRequested| is bigger than FIFO length, it violates // - If |frames_requested| is bigger than FIFO length, it violates
// SECURITY_CHECK(). // SECURITY_CHECK().
// - In case of underflow (FIFO empty while pull), the remaining space in the // - In case of underflow (FIFO empty while pull), the remaining space in the
// requested output bus will be filled with silence. Thus it will fulfill // requested output bus will be filled with silence. Thus it will fulfill
// the request from the consumer without causing error, but with a glitch. // the request from the consumer without causing error, but with a glitch.
void Pull(AudioBus* output_bus, size_t frames_requested); size_t Pull(AudioBus* output_bus, size_t frames_requested);
size_t FramesAvailable() const { return frames_available_; }
size_t length() const { return fifo_length_; } size_t length() const { return fifo_length_; }
unsigned NumberOfChannels() const { return fifo_bus_->NumberOfChannels(); } unsigned NumberOfChannels() const { return fifo_bus_->NumberOfChannels(); }
AudioBus* Bus() const { return fifo_bus_.Get(); }
// For unit test. Get the current configuration that consists of FIFO length, // TODO(hongchan): For single thread unit test only. Consider refactoring.
// number of channels, read/write index position and under/overflow count. AudioBus* GetFIFOBusForTest() const { return fifo_bus_.Get(); }
// For single thread unit test only. Get the current configuration that
// consists of FIFO length, number of channels, read/write index position and
// under/overflow count.
const PushPullFIFOStateForTest GetStateForTest() const; const PushPullFIFOStateForTest GetStateForTest() const;
private: private:
// The size of the FIFO. // The size of the FIFO.
const size_t fifo_length_ = 0; const size_t fifo_length_ = 0;
RefPtr<AudioBus> fifo_bus_; unsigned overflow_count_ = 0;
unsigned underflow_count_ = 0;
// This lock protects variables below.
Mutex lock_;
// The number of frames in the FIFO actually available for pulling. // The number of frames in the FIFO actually available for pulling.
size_t frames_available_; size_t frames_available_ = 0;
size_t index_read_ = 0;
size_t index_read_; size_t index_write_ = 0;
size_t index_write_; RefPtr<AudioBus> fifo_bus_;
unsigned overflow_count_;
unsigned underflow_count_;
}; };
} // namespace blink } // namespace blink
......
// 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
...@@ -6,51 +6,57 @@ ...@@ -6,51 +6,57 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "platform/CrossThreadFunctional.h"
#include "platform/WebTaskRunner.h"
#include "platform/audio/AudioUtilities.h" #include "platform/audio/AudioUtilities.h"
#include "platform/testing/TestingPlatformSupport.h" #include "platform/testing/TestingPlatformSupport.h"
#include "platform/wtf/Functional.h"
#include "platform/wtf/PtrUtil.h" #include "platform/wtf/PtrUtil.h"
#include "public/platform/Platform.h"
#include "public/platform/WebThread.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace blink { namespace blink {
namespace { namespace {
// Check the basic contract of FIFO. // Check the basic contract of FIFO. This test only covers the single thread
// scenario.
TEST(PushPullFIFOBasicTest, BasicTests) { TEST(PushPullFIFOBasicTest, BasicTests) {
// This suppresses the multi-thread warning for GTest. Potently it increases // This suppresses the multi-thread warning for GTest. Potently it increases
// the test execution time, but this specific test is very short and simple. // the test execution time, but this specific test is very short and simple.
::testing::FLAGS_gtest_death_test_style = "threadsafe"; ::testing::FLAGS_gtest_death_test_style = "threadsafe";
// FIFO length exceeding the maximum length allowed will cause crash. // FIFO length exceeding the maximum length allowed will cause crash.
// i.e.) m_fifoLength <= kMaxFIFOLength // i.e.) fifo_length_ <= kMaxFIFOLength
EXPECT_DEATH(new PushPullFIFO(2, PushPullFIFO::kMaxFIFOLength + 1), ""); EXPECT_DEATH(new PushPullFIFO(2, PushPullFIFO::kMaxFIFOLength + 1), "");
std::unique_ptr<PushPullFIFO> test_fifo = std::unique_ptr<PushPullFIFO> test_fifo =
WTF::WrapUnique(new PushPullFIFO(2, 1024)); WTF::WrapUnique(new PushPullFIFO(2, 1024));
// The input bus length must be |AudioUtilities::kRenderQuantumFrames|. // The input bus length must be |AudioUtilities::kRenderQuantumFrames|.
// i.e.) inputBus->length() == kRenderQuantumFrames // i.e.) input_bus->length() == kRenderQuantumFrames
RefPtr<AudioBus> input_bus_of129_frames = RefPtr<AudioBus> input_bus_129_frames =
AudioBus::Create(2, AudioUtilities::kRenderQuantumFrames + 1); AudioBus::Create(2, AudioUtilities::kRenderQuantumFrames + 1);
EXPECT_DEATH(test_fifo->Push(input_bus_of129_frames.Get()), ""); EXPECT_DEATH(test_fifo->Push(input_bus_129_frames.Get()), "");
RefPtr<AudioBus> input_bus_of127_frames = RefPtr<AudioBus> input_bus_127_frames =
AudioBus::Create(2, AudioUtilities::kRenderQuantumFrames - 1); AudioBus::Create(2, AudioUtilities::kRenderQuantumFrames - 1);
EXPECT_DEATH(test_fifo->Push(input_bus_of127_frames.Get()), ""); EXPECT_DEATH(test_fifo->Push(input_bus_127_frames.Get()), "");
// Pull request frames cannot exceed the length of output bus. // Pull request frames cannot exceed the length of output bus.
// i.e.) framesRequested <= outputBus->length() // i.e.) frames_requested <= output_bus->length()
RefPtr<AudioBus> output_bus_of512_frames = AudioBus::Create(2, 512); RefPtr<AudioBus> output_bus_512_frames = AudioBus::Create(2, 512);
EXPECT_DEATH(test_fifo->Pull(output_bus_of512_frames.Get(), 513), ""); EXPECT_DEATH(test_fifo->Pull(output_bus_512_frames.Get(), 513), "");
// Pull request frames cannot exceed the length of FIFO. // Pull request frames cannot exceed the length of FIFO.
// i.e.) framesRequested <= m_fifoLength // i.e.) frames_requested <= fifo_length_
RefPtr<AudioBus> output_bus_of1025_frames = AudioBus::Create(2, 1025); RefPtr<AudioBus> output_bus_1025_frames = AudioBus::Create(2, 1025);
EXPECT_DEATH(test_fifo->Pull(output_bus_of1025_frames.Get(), 1025), ""); EXPECT_DEATH(test_fifo->Pull(output_bus_1025_frames.Get(), 1025), "");
} }
// Fills each AudioChannel in an AudioBus with a series of linearly increasing // Fills each AudioChannel in an AudioBus with a series of linearly increasing
// values starting from |startingValue| and incrementing by 1. Then return value // values starting from |starting_value| and incrementing by 1. Then return
// will be |startingValue| + |bus_length|. // value will be |starting_value| + |bus_length|.
size_t FillBusWithLinearRamp(AudioBus* target_bus, size_t starting_value) { size_t FillBusWithLinearRamp(AudioBus* target_bus, size_t starting_value) {
for (unsigned c = 0; c < target_bus->NumberOfChannels(); ++c) { for (unsigned c = 0; c < target_bus->NumberOfChannels(); ++c) {
float* bus_channel = target_bus->Channel(c)->MutableData(); float* bus_channel = target_bus->Channel(c)->MutableData();
...@@ -169,7 +175,8 @@ TEST_P(PushPullFIFOFeatureTest, FeatureTests) { ...@@ -169,7 +175,8 @@ TEST_P(PushPullFIFOFeatureTest, FeatureTests) {
// Verify in-FIFO samples. // Verify in-FIFO samples.
for (const auto& sample : expected_state.fifo_samples) { for (const auto& sample : expected_state.fifo_samples) {
EXPECT_TRUE(VerifyBusValueAtIndex(fifo->Bus(), sample.index, sample.value)); EXPECT_TRUE(VerifyBusValueAtIndex(fifo->GetFIFOBusForTest(),
sample.index, sample.value));
} }
// Verify samples from the most recent output bus. // Verify samples from the most recent output bus.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment