Commit 45a0c949 authored by Raymond Toy's avatar Raymond Toy Committed by Commit Bot

Implement tail processing for AudioNodes

Enable tail processing by calling the necessary routines.  A
continuation of https://chromium-review.googlesource.com/c/chromium/src/+/949762
where tail processing is actually running.

Keep nodes alive when there are no input connections so that the node
has time to flush out any internal memory. When the output of the node
is going to be disabled (because there are no inputs), place the node
on a list, without disabling the output.

The list is processed every rendering quantum to see if the tail time
of node has passed.  If the tail time has not passed, nothing is done;
otherwise, the output is disabled, and the node is removed from the
list.  This allows the node to be collected, if possible.

Bug:357843, 731518
Test:AudioNode/tail-processing.html,DynamicsCompressor/dynamicscompressor-clear-internal-state.html

Change-Id: I5ba37015787fbbb2342eacb57b4851b99a36b3c5
Reviewed-on: https://chromium-review.googlesource.com/661165
Commit-Queue: Raymond Toy <rtoy@chromium.org>
Reviewed-by: default avatarHongchan Choi <hongchan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543881}
parent 56d8b279
...@@ -3385,6 +3385,3 @@ crbug.com/681919 [ Linux Debug ] http/tests/media/media-src-suspend-before-have- ...@@ -3385,6 +3385,3 @@ crbug.com/681919 [ Linux Debug ] http/tests/media/media-src-suspend-before-have-
# Sheriff 2018-03-07 # Sheriff 2018-03-07
crbug.com/819591 virtual/threaded/animations/hit-testing/composited-with-hit-testing.html [ Failure Pass ] crbug.com/819591 virtual/threaded/animations/hit-testing/composited-with-hit-testing.html [ Failure Pass ]
crbug.com/819778 [ Linux ] external/wpt/css/cssom-view/interfaces.html [ Pass Timeout ] crbug.com/819778 [ Linux ] external/wpt/css/cssom-view/interfaces.html [ Pass Timeout ]
# Sheriff 2018-03-08
crbug.com/731518 [ Win Linux Mac ] webaudio/DynamicsCompressor/dynamicscompressor-clear-internal-state.html [ Failure Pass ]
...@@ -53,7 +53,8 @@ ...@@ -53,7 +53,8 @@
let errorThreshold = 4.8223e-2; let errorThreshold = 4.8223e-2;
should( should(
Math.abs(compressor.reduction), Math.abs(compressor.reduction),
'Math.abs(compressor.reduction)') 'Math.abs(compressor.reduction) ('
+ Math.abs(compressor.reduction) + ')')
.beLessThanOrEqualTo(errorThreshold); .beLessThanOrEqualTo(errorThreshold);
task.done(); task.done();
}); });
......
...@@ -103,8 +103,6 @@ void AudioHandler::Uninitialize() { ...@@ -103,8 +103,6 @@ void AudioHandler::Uninitialize() {
is_initialized_ = false; is_initialized_ = false;
} }
void AudioHandler::ClearInternalStateWhenDisabled() {}
void AudioHandler::Dispose() { void AudioHandler::Dispose() {
DCHECK(IsMainThread()); DCHECK(IsMainThread());
DCHECK(Context()->IsGraphOwner()); DCHECK(Context()->IsGraphOwner());
...@@ -338,14 +336,6 @@ void AudioHandler::ProcessIfNecessary(size_t frames_to_process) { ...@@ -338,14 +336,6 @@ void AudioHandler::ProcessIfNecessary(size_t frames_to_process) {
PullInputs(frames_to_process); PullInputs(frames_to_process);
bool silent_inputs = InputsAreSilent(); bool silent_inputs = InputsAreSilent();
if (!silent_inputs) {
// Update |last_non_silent_time| AFTER processing this block.
// Doing it before causes |PropagateSilence()| to be one render
// quantum longer than necessary.
last_non_silent_time_ =
(Context()->CurrentSampleFrame() + frames_to_process) /
static_cast<double>(Context()->sampleRate());
}
if (silent_inputs && PropagatesSilence()) { if (silent_inputs && PropagatesSilence()) {
SilenceOutputs(); SilenceOutputs();
// AudioParams still need to be processed so that the value can be updated // AudioParams still need to be processed so that the value can be updated
...@@ -360,6 +350,15 @@ void AudioHandler::ProcessIfNecessary(size_t frames_to_process) { ...@@ -360,6 +350,15 @@ void AudioHandler::ProcessIfNecessary(size_t frames_to_process) {
UnsilenceOutputs(); UnsilenceOutputs();
Process(frames_to_process); Process(frames_to_process);
} }
if (!silent_inputs) {
// Update |last_non_silent_time| AFTER processing this block.
// Doing it before causes |PropagateSilence()| to be one render
// quantum longer than necessary.
last_non_silent_time_ =
(Context()->CurrentSampleFrame() + frames_to_process) /
static_cast<double>(Context()->sampleRate());
}
} }
} }
...@@ -437,25 +436,16 @@ void AudioHandler::DisableOutputsIfNecessary() { ...@@ -437,25 +436,16 @@ void AudioHandler::DisableOutputsIfNecessary() {
// they're connected to. disable() can recursively deref connections (and // they're connected to. disable() can recursively deref connections (and
// call disable()) down a whole chain of connected nodes. // call disable()) down a whole chain of connected nodes.
// TODO(rtoy,hongchan): we need special cases the convolver, delay, biquad, // If a node requires tail processing, we defer the disabling of
// and IIR since they have a significant tail-time and shouldn't be // the outputs so that the tail for the node can be output.
// disconnected simply because they no longer have any input connections. // Otherwise, we can disable the outputs right away.
// This needs to be handled more generally where AudioNodes have a tailTime if (RequiresTailProcessing()) {
// attribute. Then the AudioNode only needs to remain "active" for tailTime if (Context()->ContextState() !=
// seconds after there are no longer any active connections. BaseAudioContext::AudioContextState::kClosed) {
// Context()->GetDeferredTaskHandler().AddTailProcessingHandler(this);
// The analyser node also requires special handling because we }
// need the internal state to be updated for the time and FFT data } else {
// even if it has no connections. DisableOutputs();
if (GetNodeType() != kNodeTypeConvolver &&
GetNodeType() != kNodeTypeDelay &&
GetNodeType() != kNodeTypeBiquadFilter &&
GetNodeType() != kNodeTypeIIRFilter &&
GetNodeType() != kNodeTypeAnalyser) {
is_disabled_ = true;
ClearInternalStateWhenDisabled();
for (auto& output : outputs_)
output->Disable();
} }
} }
} }
...@@ -469,11 +459,14 @@ void AudioHandler::DisableOutputs() { ...@@ -469,11 +459,14 @@ void AudioHandler::DisableOutputs() {
void AudioHandler::MakeConnection() { void AudioHandler::MakeConnection() {
AtomicIncrement(&connection_ref_count_); AtomicIncrement(&connection_ref_count_);
Context()->GetDeferredTaskHandler().RemoveTailProcessingHandler(this);
#if DEBUG_AUDIONODE_REFERENCES #if DEBUG_AUDIONODE_REFERENCES
fprintf(stderr, fprintf(
"[%16p]: %16p: %2d: AudioHandler::MakeConnection %3d [%3d]\n", stderr,
Context(), this, GetNodeType(), connection_ref_count_, "[%16p]: %16p: %2d: AudioHandler::MakeConnection %3d [%3d] @%.15g\n",
node_count_[GetNodeType()]); Context(), this, GetNodeType(), connection_ref_count_,
node_count_[GetNodeType()], Context()->currentTime());
#endif #endif
// See the disabling code in disableOutputsIfNecessary(). This handles // See the disabling code in disableOutputsIfNecessary(). This handles
// the case where a node is being re-connected after being used at least // the case where a node is being re-connected after being used at least
......
...@@ -152,14 +152,6 @@ class MODULES_EXPORT AudioHandler : public ThreadSafeRefCounted<AudioHandler> { ...@@ -152,14 +152,6 @@ class MODULES_EXPORT AudioHandler : public ThreadSafeRefCounted<AudioHandler> {
virtual void Initialize(); virtual void Initialize();
virtual void Uninitialize(); virtual void Uninitialize();
// Clear internal state when the node is disabled. When a node is disabled,
// it is no longer pulled so any internal state is never updated. But some
// nodes (DynamicsCompressorNode) have internal state that is still
// accessible by the user. Update the internal state as if the node were
// still connected but processing all zeroes. This gives a consistent view
// to the user.
virtual void ClearInternalStateWhenDisabled();
bool IsInitialized() const { return is_initialized_; } bool IsInitialized() const { return is_initialized_; }
unsigned NumberOfInputs() const { return inputs_.size(); } unsigned NumberOfInputs() const { return inputs_.size(); }
......
...@@ -122,10 +122,6 @@ void DynamicsCompressorHandler::Initialize() { ...@@ -122,10 +122,6 @@ void DynamicsCompressorHandler::Initialize() {
Context()->sampleRate(), defaultNumberOfOutputChannels); Context()->sampleRate(), defaultNumberOfOutputChannels);
} }
void DynamicsCompressorHandler::ClearInternalStateWhenDisabled() {
reduction_ = 0;
}
bool DynamicsCompressorHandler::RequiresTailProcessing() const { bool DynamicsCompressorHandler::RequiresTailProcessing() const {
// Always return true even if the tail time and latency might both be zero. // Always return true even if the tail time and latency might both be zero.
return true; return true;
......
...@@ -55,7 +55,6 @@ class MODULES_EXPORT DynamicsCompressorHandler final : public AudioHandler { ...@@ -55,7 +55,6 @@ class MODULES_EXPORT DynamicsCompressorHandler final : public AudioHandler {
void Process(size_t frames_to_process) override; void Process(size_t frames_to_process) override;
void ProcessOnlyAudioParams(size_t frames_to_process) override; void ProcessOnlyAudioParams(size_t frames_to_process) override;
void Initialize() override; void Initialize() override;
void ClearInternalStateWhenDisabled() override;
float ReductionValue() const { return reduction_; } float ReductionValue() const { return reduction_; }
......
...@@ -102,7 +102,7 @@ PannerHandler::~PannerHandler() { ...@@ -102,7 +102,7 @@ PannerHandler::~PannerHandler() {
void PannerHandler::Process(size_t frames_to_process) { void PannerHandler::Process(size_t frames_to_process) {
AudioBus* destination = Output(0).Bus(); AudioBus* destination = Output(0).Bus();
if (!IsInitialized() || !Input(0).IsConnected() || !panner_.get()) { if (!IsInitialized() || !panner_.get()) {
destination->Zero(); destination->Zero();
return; return;
} }
......
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