Skip to content

Commit

Permalink
Add currentFrame in AudioWorkletGlobalScope
Browse files Browse the repository at this point in the history
This CL adds |currentFrame| property to AudioWorkletGlobalScope according
to: WebAudio/web-audio-api#1493.

1. Previously the time stamp (currentTime) was updated when the first
   worklet processor gets called, and it was inconsistent with how it is
   updated in the other nodes. Now updating the frame number is
   streamlined, so the worklet processor also gets the same treatment.

2. In order to streamline this process, WorkerThread reference was
   added to BaseAudioContext. Due to the GC rule, BaseAudioContext cannot
   keep a reference of WorkletGlobalScope, so it always derives the
   global scope from the worker thread.

Bug: 814794
Test: http/tests/webaudio/audio-worklet/timing-info.html
Change-Id: I4b313377d80f8678c473cf788211e373fd1644cb
Reviewed-on: https://chromium-review.googlesource.com/935157
Commit-Queue: Hongchan Choi <[email protected]>
Reviewed-by: Kentaro Hara <[email protected]>
Reviewed-by: Raymond Toy <[email protected]>
Cr-Commit-Position: refs/heads/master@{#539300}
  • Loading branch information
hoch authored and Commit Bot committed Feb 26, 2018
1 parent 384c9d4 commit d8b32f7
Show file tree
Hide file tree
Showing 17 changed files with 151 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @class TimingInfoProcessor
* @extends AudioWorkletProcessor
*
* This processor class is to test the timing information in AWGS.
*/
class TimingInfoProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.port.onmessage = this.echoMessage.bind(this);
}

echoMessage(event) {
this.port.postMessage({
currentTime: currentTime,
currentFrame: currentFrame
});
}

process() {
return true;
}
}

registerProcessor('timing-info-processor', TimingInfoProcessor);
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<title>
Test currentTime and currentFrame in AudioWorkletGlobalScope
</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../../webaudio-resources/audit.js"></script>
<script src="audio-worklet-common.js"></script>
</head>
<body>
<script id="layout-test-code">
// TODO(hongchan): remove this assertion when AudioWorklet shipped.
assertAudioWorklet();

let audit = Audit.createTaskRunner();

let sampleRate = 48000;
let renderLength = 512;
let context = new OfflineAudioContext(1, renderLength, sampleRate);

audit.define(
'Check the timing information from AudioWorkletProcessor',
(task, should) => {
let portWorkletNode =
new AudioWorkletNode(context, 'timing-info-processor');
portWorkletNode.connect(context.destination);

// Suspend at render quantum boundary and check the timing
// information between the main thread and the rendering thread.
[0, 128, 256, 384].map((suspendFrame) => {
context.suspend(suspendFrame/sampleRate).then(() => {
portWorkletNode.port.onmessage = (event) => {
should(event.data.currentFrame,
'currentFrame from the processor at ' + suspendFrame)
.beEqualTo(suspendFrame);
should(event.data.currentTime,
'currentTime from the processor at '
+ context.currentTime)
.beEqualTo(context.currentTime);
context.resume();
};

portWorkletNode.port.postMessage('query-timing-info');
});
});

context.startRendering().then(() => {
task.done();
});
});

context.audioWorklet.addModule('timing-info-processor.js').then(() => {
audit.run();
});
</script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ void AudioDestinationHandler::Render(AudioBus* source_bus,
// Advance current sample-frame.
size_t new_sample_frame = current_sample_frame_ + number_of_frames;
ReleaseStore(&current_sample_frame_, new_sample_frame);

Context()->UpdateWorkletGlobalScopeOnRenderingThread();
}

// ----------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ void AudioWorklet::NotifyGlobalScopeIsUpdated() {
WebThread* AudioWorklet::GetBackingThread() {
DCHECK(IsMainThread());
DCHECK(GetMessagingProxy());
return GetMessagingProxy()->GetWorkletBackingThread();
return GetMessagingProxy()->GetBackingWebThread();
}

BaseAudioContext* AudioWorklet::GetBaseAudioContext() const {
Expand Down
6 changes: 3 additions & 3 deletions third_party/WebKit/Source/modules/webaudio/AudioWorklet.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ class MODULES_EXPORT AudioWorklet final : public Worklet {

BaseAudioContext* GetBaseAudioContext() const;

// Returns |nullptr| if there is no active WorkletGlobalScope().
AudioWorkletMessagingProxy* GetMessagingProxy();

const Vector<CrossThreadAudioParamInfo> GetParamInfoListForProcessor(
const String& name);

Expand All @@ -62,9 +65,6 @@ class MODULES_EXPORT AudioWorklet final : public Worklet {
bool NeedsToCreateGlobalScope() final;
WorkletGlobalScopeProxy* CreateGlobalScope() final;

// Returns |nullptr| if there is no active WorkletGlobalScope().
AudioWorkletMessagingProxy* GetMessagingProxy();

// To catch the first global scope update and notify the context.
bool worklet_started_ = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,10 @@ bool AudioWorkletGlobalScope::Process(
AudioWorkletProcessor* processor,
Vector<AudioBus*>* input_buses,
Vector<AudioBus*>* output_buses,
HashMap<String, std::unique_ptr<AudioFloatArray>>* param_value_map,
double current_time) {
HashMap<String, std::unique_ptr<AudioFloatArray>>* param_value_map) {
CHECK_GE(input_buses->size(), 0u);
CHECK_GE(output_buses->size(), 0u);

// Note that all AudioWorkletProcessors share this method for the processing.
// AudioWorkletGlobalScope's |current_time_| must be updated only once per
// render quantum.
if (current_time_ < current_time) {
current_time_ = current_time;
}

ScriptState* script_state = ScriptController()->GetScriptState();
ScriptState::Scope scope(script_state);

Expand Down Expand Up @@ -376,10 +368,20 @@ ProcessorCreationParams* AudioWorkletGlobalScope::GetProcessorCreationParams() {
return processor_creation_params_.get();
}

void AudioWorkletGlobalScope::SetCurrentFrame(size_t current_frame) {
current_frame_ = current_frame;
}

void AudioWorkletGlobalScope::SetSampleRate(float sample_rate) {
sample_rate_ = sample_rate;
}

double AudioWorkletGlobalScope::currentTime() const {
return sample_rate_ > 0.0
? current_frame_ / static_cast<double>(sample_rate_)
: 0.0;
}

void AudioWorkletGlobalScope::Trace(blink::Visitor* visitor) {
visitor->Trace(processor_definition_map_);
visitor->Trace(processor_instances_);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ class MODULES_EXPORT AudioWorkletGlobalScope final
AudioWorkletProcessor*,
Vector<AudioBus*>* input_buses,
Vector<AudioBus*>* output_buses,
HashMap<String, std::unique_ptr<AudioFloatArray>>* param_value_map,
double current_time);
HashMap<String, std::unique_ptr<AudioFloatArray>>* param_value_map);

AudioWorkletProcessorDefinition* FindDefinition(const String& name);

Expand All @@ -95,10 +94,12 @@ class MODULES_EXPORT AudioWorkletGlobalScope final
// is no on-going processor construction, this MUST return nullptr.
ProcessorCreationParams* GetProcessorCreationParams();

void SetCurrentFrame(size_t current_frame);
void SetSampleRate(float sample_rate);

// IDL
double currentTime() const { return current_time_; }
unsigned long long currentFrame() const { return current_frame_; }
double currentTime() const;
float sampleRate() const { return sample_rate_; }

void Trace(blink::Visitor*);
Expand All @@ -125,7 +126,7 @@ class MODULES_EXPORT AudioWorkletGlobalScope final
// detail.
std::unique_ptr<ProcessorCreationParams> processor_creation_params_;

double current_time_ = 0.0;
size_t current_frame_ = 0;
float sample_rate_ = 0.0;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
OriginTrialEnabled=AudioWorklet
] interface AudioWorkletGlobalScope : WorkletGlobalScope {
[RaisesException, MeasureAs=AudioWorkletGlobalScopeRegisterProcessor] void registerProcessor(DOMString name, Function processorConstructor);
readonly attribute unsigned long long currentFrame;
readonly attribute double currentTime;
readonly attribute float sampleRate;
};
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ class AudioWorkletGlobalScopeTest : public PageTestBase {

// Then invoke the process() method to perform JS buffer manipulation. The
// output buffer should contain a constant value of 2.
processor->Process(&input_buses, &output_buses, &param_data_map, 0.0);
processor->Process(&input_buses, &output_buses, &param_data_map);
for (unsigned i = 0; i < output_channel->length(); ++i) {
EXPECT_EQ(output_channel->Data()[i], 2);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,15 @@ AudioWorkletMessagingProxy::GetParamInfoListForProcessor(
return processor_info_map_.at(name);
}

WebThread* AudioWorkletMessagingProxy::GetWorkletBackingThread() {
WebThread* AudioWorkletMessagingProxy::GetBackingWebThread() {
auto worklet_thread = static_cast<AudioWorkletThread*>(GetWorkerThread());
return worklet_thread->GetSharedBackingThread();
}

WorkerThread* AudioWorkletMessagingProxy::GetBackingWorkerThread() {
return GetWorkerThread();
}

std::unique_ptr<ThreadedWorkletObjectProxy>
AudioWorkletMessagingProxy::CreateObjectProxy(
ThreadedWorkletMessagingProxy* messaging_proxy,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ class AudioWorkletMessagingProxy final : public ThreadedWorkletMessagingProxy {
const Vector<CrossThreadAudioParamInfo> GetParamInfoListForProcessor(
const String& name) const;

WebThread* GetWorkletBackingThread();
WebThread* GetBackingWebThread();
WorkerThread* GetBackingWorkerThread();

void Trace(Visitor*);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,7 @@ void AudioWorkletHandler::Process(size_t frames_to_process) {

// Run the render code and check the state of processor. Finish the
// processor if needed.
if (!processor_->Process(&inputBuses, &outputBuses, &param_value_map_,
Context()->currentTime()) ||
if (!processor_->Process(&inputBuses, &outputBuses, &param_value_map_) ||
!processor_->IsRunnable()) {
FinishProcessorOnRenderThread();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,11 @@ AudioWorkletProcessor::AudioWorkletProcessor(
bool AudioWorkletProcessor::Process(
Vector<AudioBus*>* input_buses,
Vector<AudioBus*>* output_buses,
HashMap<String, std::unique_ptr<AudioFloatArray>>* param_value_map,
double current_time) {
HashMap<String, std::unique_ptr<AudioFloatArray>>* param_value_map) {
DCHECK(global_scope_->IsContextThread());
DCHECK(IsRunnable());
return global_scope_->Process(this, input_buses, output_buses,
param_value_map, current_time);
param_value_map);
}

MessagePort* AudioWorkletProcessor::port() const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ class MODULES_EXPORT AudioWorkletProcessor : public ScriptWrappable {
bool Process(
Vector<AudioBus*>* input_buses,
Vector<AudioBus*>* output_buses,
HashMap<String, std::unique_ptr<AudioFloatArray>>* param_value_map,
double current_time);
HashMap<String, std::unique_ptr<AudioFloatArray>>* param_value_map);

const String& Name() const { return name_; }

Expand Down
20 changes: 20 additions & 0 deletions third_party/WebKit/Source/modules/webaudio/BaseAudioContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "core/inspector/ConsoleMessage.h"
#include "core/inspector/ConsoleTypes.h"
#include "core/origin_trials/origin_trials.h"
#include "core/workers/WorkerThread.h"
#include "modules/mediastream/MediaStream.h"
#include "modules/webaudio/AnalyserNode.h"
#include "modules/webaudio/AudioBuffer.h"
Expand All @@ -47,6 +48,7 @@
#include "modules/webaudio/AudioNodeInput.h"
#include "modules/webaudio/AudioNodeOutput.h"
#include "modules/webaudio/AudioWorklet.h"
#include "modules/webaudio/AudioWorkletGlobalScope.h"
#include "modules/webaudio/AudioWorkletMessagingProxy.h"
#include "modules/webaudio/BiquadFilterNode.h"
#include "modules/webaudio/ChannelMergerNode.h"
Expand Down Expand Up @@ -1000,8 +1002,12 @@ AudioWorklet* BaseAudioContext::audioWorklet() const {
}

void BaseAudioContext::NotifyWorkletIsReady() {
DCHECK(IsMainThread());
DCHECK(audioWorklet()->IsReady());

worklet_backing_worker_thread_ =
audioWorklet()->GetMessagingProxy()->GetBackingWorkerThread();

// If the context is running or suspended, restart the destination to switch
// the render thread with the worklet thread. Note that restarting can happen
// right after the context construction.
Expand All @@ -1010,4 +1016,18 @@ void BaseAudioContext::NotifyWorkletIsReady() {
}
}

void BaseAudioContext::UpdateWorkletGlobalScopeOnRenderingThread() {
DCHECK(!IsMainThread());

// Only if the worklet is properly initialized and ready.
if (worklet_backing_worker_thread_) {
AudioWorkletGlobalScope* global_scope =
ToAudioWorkletGlobalScope(
worklet_backing_worker_thread_->GlobalScope());
DCHECK(global_scope);

global_scope->SetCurrentFrame(CurrentSampleFrame());
}
}

} // namespace blink
10 changes: 10 additions & 0 deletions third_party/WebKit/Source/modules/webaudio/BaseAudioContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class ScriptState;
class SecurityOrigin;
class StereoPannerNode;
class WaveShaperNode;
class WorkerThread;

// BaseAudioContext is the cornerstone of the web audio API and all AudioNodes
// are created from it. For thread safety between the audio thread and the main
Expand Down Expand Up @@ -340,6 +341,11 @@ class MODULES_EXPORT BaseAudioContext
// the first script evaluation.
void NotifyWorkletIsReady();

// Update the information in AudioWorkletGlobalScope if necessary. Must be
// called from the rendering thread. Does nothing when the global scope
// does not exist.
void UpdateWorkletGlobalScopeOnRenderingThread();

protected:
enum ContextType { kRealtimeContext, kOfflineContext };

Expand Down Expand Up @@ -511,6 +517,10 @@ class MODULES_EXPORT BaseAudioContext
AudioIOPosition output_position_;

Member<AudioWorklet> audio_worklet_;

// Only for the access to AudioWorkletGlobalScope from the render thread.
// Use the WebThread in the destination nodes for the task scheduling.
WorkerThread* worklet_backing_worker_thread_ = nullptr;
};

} // namespace blink
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,8 @@ bool OfflineAudioDestinationHandler::RenderIfNotSuspended(
size_t new_sample_frame = current_sample_frame_ + number_of_frames;
ReleaseStore(&current_sample_frame_, new_sample_frame);

Context()->UpdateWorkletGlobalScopeOnRenderingThread();

return false;
}

Expand Down

0 comments on commit d8b32f7

Please sign in to comment.