blob: c7513b53faa046ccdf22369fa07e63930ac3619f [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_HANDLER_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_HANDLER_H_
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_channel_count_mode.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_channel_interpretation.h"
#include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/platform/audio/audio_bus.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h"
// Higher values produce more debugging output.
#define DEBUG_AUDIONODE_REFERENCES 0
namespace blink {
class BaseAudioContext;
class AudioNode;
class AudioNodeInput;
class AudioNodeOutput;
class DeferredTaskHandler;
class ExceptionState;
// Helper class for UMA reporting of AudioHandler related metrics.
class AudioHandlerUmaReporter {
public:
explicit AudioHandlerUmaReporter(const std::string& handler_type_name,
float sample_rate)
: metric_name_("WebAudio.AudioNode.ProcessTimeRatio." +
handler_type_name),
sample_rate_(sample_rate) {}
// Records processing duration and frame count for a Process() call.
// Reports the average processing time ratio to UMA after `kReportingInterval`
// calls. |duration|: Time taken by the Process() call. |process_frames|:
// Number of frames processed in the call.
void AddProcessDuration(base::TimeDelta duration, int process_frames) {
total_process_duration_ += duration;
total_process_frames_ += process_frames;
process_count_++;
if (process_count_ >= kReportingInterval) {
ReportAverageRatioAsPercentage();
total_process_duration_ = base::TimeDelta();
total_process_frames_ = 0;
process_count_ = 0;
}
}
private:
void ReportAverageRatioAsPercentage() {
float total_render_time = total_process_frames_ / sample_rate_;
float average_ratio =
total_process_duration_.InSecondsF() / total_render_time;
// Report as a percentage (e.g., 0.5 ratio -> 50%).
int percentage_to_report = static_cast<int>(average_ratio * 100.0);
base::UmaHistogramExactLinear(metric_name_.c_str(), percentage_to_report,
101);
}
static constexpr int kReportingInterval = 1000;
std::string metric_name_;
float sample_rate_;
// Total number of frames processed over kReportingInterval calls.
uint32_t total_process_frames_ = 0;
// Total processing duration accumulated over kReportingInterval calls.
base::TimeDelta total_process_duration_;
// Counter for Process() calls, reset every kReportingInterval.
int process_count_ = 0;
};
class MODULES_EXPORT AudioHandler : public ThreadSafeRefCounted<AudioHandler> {
public:
enum class NodeType {
kNodeTypeUnknown = 0,
kNodeTypeDestination = 1,
kNodeTypeOscillator = 2,
kNodeTypeAudioBufferSource = 3,
kNodeTypeMediaElementAudioSource = 4,
kNodeTypeMediaStreamAudioDestination = 5,
kNodeTypeMediaStreamAudioSource = 6,
kNodeTypeScriptProcessor = 7,
kNodeTypeBiquadFilter = 8,
kNodeTypePanner = 9,
kNodeTypeStereoPanner = 10,
kNodeTypeConvolver = 11,
kNodeTypeDelay = 12,
kNodeTypeGain = 13,
kNodeTypeChannelSplitter = 14,
kNodeTypeChannelMerger = 15,
kNodeTypeAnalyser = 16,
kNodeTypeDynamicsCompressor = 17,
kNodeTypeWaveShaper = 18,
kNodeTypeIIRFilter = 19,
kNodeTypeConstantSource = 20,
kNodeTypeAudioWorklet = 21,
kNodeTypeEnd = 22
};
AudioHandler(NodeType, AudioNode&, float sample_rate);
virtual ~AudioHandler();
// dispose() is called when the owner AudioNode is about to be
// destructed. This must be called in the main thread, and while the graph
// lock is held.
// Do not release resources used by an audio rendering thread in dispose().
virtual void Dispose();
// GetNode() returns a valid object until the AudioNode is collected on the
// main thread, and nullptr thereafter. We must not call GetNode() in an audio
// rendering thread.
AudioNode* GetNode() const;
// context() returns a valid object until the BaseAudioContext dies, and
// returns nullptr otherwise. This always returns a valid object in an audio
// rendering thread, and inside dispose(). We must not call context() in the
// destructor.
virtual BaseAudioContext* Context() const;
void ClearContext() { context_ = nullptr; }
DeferredTaskHandler& GetDeferredTaskHandler() const {
return *deferred_task_handler_;
}
NodeType GetNodeType() const { return node_type_; }
String NodeTypeName() const;
// This object has been connected to another object. This might have
// existing connections from others.
// This function must be called after acquiring a connection reference.
void MakeConnection();
// This object will be disconnected from another object. This might have
// remaining connections from others. This function must be called before
// releasing a connection reference.
//
// This can be called from main thread or context's audio thread. It must be
// called while the context's graph lock is held.
void BreakConnectionWithLock();
// The AudioNodeInput(s) (if any) will already have their input data available
// when process() is called. Subclasses will take this input data and put the
// results in the AudioBus(s) of its AudioNodeOutput(s) (if any).
// Called from context's audio thread.
virtual void Process(uint32_t frames_to_process) = 0;
// Like process(), but only causes the automations to process; the
// normal processing of the node is bypassed. By default, we assume
// no AudioParams need to be updated.
virtual void ProcessOnlyAudioParams(uint32_t frames_to_process) {}
// No significant resources should be allocated until initialize() is called.
// Processing may not occur until a node is initialized.
virtual void Initialize();
virtual void Uninitialize();
bool IsInitialized() const { return is_initialized_; }
unsigned NumberOfInputs() const { return inputs_.size(); }
unsigned NumberOfOutputs() const { return outputs_.size(); }
// The argument must be less than numberOfInputs().
AudioNodeInput& Input(unsigned);
// The argument must be less than numberOfOutputs().
AudioNodeOutput& Output(unsigned);
const AudioNodeOutput& Output(unsigned) const;
// processIfNecessary() is called by our output(s) when the rendering graph
// needs this AudioNode to process. This method ensures that the AudioNode
// will only process once per rendering time quantum even if it's called
// repeatedly. This handles the case of "fanout" where an output is connected
// to multiple AudioNode inputs. Called from context's audio thread.
virtual void ProcessIfNecessary(uint32_t frames_to_process);
// Called when a new connection has been made to one of our inputs or the
// connection number of channels has changed. This potentially gives us
// enough information to perform a lazy initialization or, if necessary, a
// re-initialization. Called from main thread.
virtual void CheckNumberOfChannelsForInput(AudioNodeInput*);
#if DEBUG_AUDIONODE_REFERENCES
static void PrintNodeCounts();
#endif
#if DEBUG_AUDIONODE_REFERENCES > 1
void TailProcessingDebug(const char* debug_note, bool flag);
void AddTailProcessingDebug();
void RemoveTailProcessingDebug(bool disable_outputs);
#endif
// True if the node has a tail time or latency time that requires
// special tail processing to behave properly. Ideally, this can be
// checked using TailTime and LatencyTime, but these aren't
// available on the main thread, and the tail processing check can
// happen on the main thread.
virtual bool RequiresTailProcessing() const = 0;
// TailTime() is the length of time (not counting latency time) where
// non-zero output may occur after continuous silent input.
virtual double TailTime() const = 0;
// LatencyTime() is the length of time it takes for non-zero output to
// appear after non-zero input is provided. This only applies to processing
// delay which is an artifact of the processing algorithm chosen and is
// *not* part of the intrinsic desired effect. For example, a "delay" effect
// is expected to delay the signal, and thus would not be considered
// latency.
virtual double LatencyTime() const = 0;
// PropagatesSilence() should return true if the node will generate silent
// output when given silent input. By default, AudioNode will take TailTime()
// and LatencyTime() into account when determining whether the node will
// propagate silence.
virtual bool PropagatesSilence() const;
bool InputsAreSilent() const;
void SilenceOutputs();
void UnsilenceOutputs();
void EnableOutputsIfNecessary();
void DisableOutputsIfNecessary();
void DisableOutputs();
unsigned ChannelCount() const;
virtual void SetChannelCount(unsigned, ExceptionState&);
V8ChannelCountMode::Enum GetChannelCountMode() const;
virtual void SetChannelCountMode(V8ChannelCountMode::Enum, ExceptionState&);
V8ChannelInterpretation::Enum ChannelInterpretation() const;
virtual void SetChannelInterpretation(V8ChannelInterpretation::Enum,
ExceptionState&);
V8ChannelCountMode::Enum InternalChannelCountMode() const {
return channel_count_mode_;
}
AudioBus::ChannelInterpretation InternalChannelInterpretation() const {
return channel_interpretation_;
}
void UpdateChannelCountMode();
void UpdateChannelInterpretation();
// Called when this node's outputs may have become connected or disconnected
// to handle automatic pull nodes.
virtual void UpdatePullStatusIfNeeded() {}
protected:
// Inputs and outputs must be created before the AudioHandler is
// initialized.
void AddInput();
void AddOutput(unsigned number_of_channels);
// Called by processIfNecessary() to cause all parts of the rendering graph
// connected to us to process. Each rendering quantum, the audio data for
// each of the AudioNode's inputs will be available after this method is
// called. Called from context's audio thread.
virtual void PullInputs(uint32_t frames_to_process);
// Force all inputs to take any channel interpretation changes into account.
void UpdateChannelsForInputs();
// Set the (internal) channelCountMode and channelInterpretation
// accordingly. Use this in the node constructors to set the internal state
// correctly if the node uses values different from the defaults.
void SetInternalChannelCountMode(V8ChannelCountMode::Enum);
void SetInternalChannelInterpretation(AudioBus::ChannelInterpretation);
// The last time (context time) that his handler ran its Process() method.
// For each render quantum, we only want to process just once to handle fanout
// of this handler.
double last_processing_time_ = -1;
// The last time (context time) when this node did not have silent inputs.
double last_non_silent_time_ = 0;
unsigned channel_count_ = 2;
// The new channel count mode that will be used to set the actual mode in the
// pre or post rendering phase.
V8ChannelCountMode::Enum new_channel_count_mode_;
// The new channel interpretation that will be used to set the actual
// interpretation in the pre or post rendering phase.
AudioBus::ChannelInterpretation new_channel_interpretation_;
std::unique_ptr<AudioHandlerUmaReporter> uma_reporter_;
private:
void SetNodeType(NodeType);
// https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/media/capture/README.md#logs
void SendLogMessage(const char* const function_name, const String& message);
bool is_initialized_ = false;
NodeType node_type_ = NodeType::kNodeTypeUnknown;
// The owner AudioNode. Accessed only on the main thread.
const WeakPersistent<AudioNode> node_;
// This untraced member is safe because this is cleared for all of live
// AudioHandlers when the BaseAudioContext dies. Do not access m_context
// directly, use context() instead.
// See http://crbug.com/404527 for the detail.
UntracedMember<BaseAudioContext> context_;
// Legal to access even when `context_` may be gone, such as during the
// destructor.
const scoped_refptr<DeferredTaskHandler> deferred_task_handler_;
Vector<std::unique_ptr<AudioNodeInput>> inputs_;
Vector<std::unique_ptr<AudioNodeOutput>> outputs_;
int connection_ref_count_ = 0;
bool is_disabled_ = false;
// Used to trigger one single textlog indicating that processing started as
// intended. Set to true once in the first call to the ProcessIfNecessary
// callback.
bool is_processing_ = false;
#if DEBUG_AUDIONODE_REFERENCES
static bool is_node_count_initialized_;
static int node_count_[static_cast<int>(NodeType::kNodeTypeEnd)];
#endif
V8ChannelCountMode::Enum channel_count_mode_;
AudioBus::ChannelInterpretation channel_interpretation_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_HANDLER_H_