From d7a274bbfbf6dcd4057df39b2e06c7d8823cd5a0 Mon Sep 17 00:00:00 2001 From: Thomas Guilbert Date: Thu, 16 Mar 2023 00:22:29 +0000 Subject: [PATCH] Add amplitude peak latency tracing This CL adds a new way to measure (estimate) end-to-end audio latency. It adds the AmplitudePeakDetector class, which listens for jumps in audio signal amplitude; when it detects a peak, it runs a callback, which we use to start or stop trace events. We use one AmplitudePeakDetector right after microphone input and one right before speaker output. By having the trace start after detecting a peak from the microphone, and stop before playing out the peak, we can measure how long it took for the signal to propagate. To actually measure latency using this technique, one should start a trace recording the `audio.latency` category. Then, one should open a test page which sets up an input via getUserMedia() and plays out that audio via any method (e.g. using WebAudio's MediaStreamAudioSourceNode or an audio element's srcObject). Then, one should clap in front of the microphone to produce audio peaks, or use any other method to generate clear, alternating loud and quiet audio input. Notes: - This method does not currently support having multiple inputs or multiple outputs. The test page should have exactly one input and output, and should be the only tab running in the browser at that time. - The peak detection threshold is roughly -6dbFS. One could adjust the microphone's gain if the peaks are never detected, or trigger too easily. Change-Id: I3a11069dc8f4843da09e625237d2f8a003428514 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4317311 Reviewed-by: Olga Sharonova Commit-Queue: Thomas Guilbert Reviewed-by: Siddhartha S Reviewed-by: Dale Curtis Cr-Commit-Position: refs/heads/main@{#1117871} --- base/trace_event/builtin_categories.h | 1 + media/audio/audio_manager.cc | 34 ++++ media/audio/audio_manager.h | 14 ++ media/audio/pulse/pulse_input.cc | 13 +- media/audio/pulse/pulse_input.h | 3 + media/audio/pulse/pulse_output.cc | 10 +- media/audio/pulse/pulse_output.h | 3 + .../audio/win/audio_low_latency_input_win.cc | 10 +- media/audio/win/audio_low_latency_input_win.h | 3 + .../audio/win/audio_low_latency_output_win.cc | 10 +- .../audio/win/audio_low_latency_output_win.h | 3 + media/base/BUILD.gn | 2 + media/base/amplitude_peak_detector.cc | 145 +++++++++++++++++ media/base/amplitude_peak_detector.h | 62 ++++++++ .../blink/manual_tests/audio_latency.html | 146 ++++++++++++++++++ 15 files changed, 452 insertions(+), 7 deletions(-) create mode 100644 media/base/amplitude_peak_detector.cc create mode 100644 media/base/amplitude_peak_detector.h create mode 100644 third_party/blink/manual_tests/audio_latency.html diff --git a/base/trace_event/builtin_categories.h b/base/trace_event/builtin_categories.h index 0a1d3f0b19128..7146ce14ee1a2 100644 --- a/base/trace_event/builtin_categories.h +++ b/base/trace_event/builtin_categories.h @@ -190,6 +190,7 @@ X(TRACE_DISABLED_BY_DEFAULT("android_view_hierarchy")) \ X(TRACE_DISABLED_BY_DEFAULT("animation-worklet")) \ X(TRACE_DISABLED_BY_DEFAULT("audio")) \ + X(TRACE_DISABLED_BY_DEFAULT("audio.latency")) \ X(TRACE_DISABLED_BY_DEFAULT("audio-worklet")) \ X(TRACE_DISABLED_BY_DEFAULT("base")) \ X(TRACE_DISABLED_BY_DEFAULT("blink.debug")) \ diff --git a/media/audio/audio_manager.cc b/media/audio/audio_manager.cc index 5b1e653e3ca75..cc5ec136f143b 100644 --- a/media/audio/audio_manager.cc +++ b/media/audio/audio_manager.cc @@ -17,6 +17,7 @@ #include "base/power_monitor/power_monitor.h" #include "base/task/single_thread_task_runner.h" #include "base/thread_annotations.h" +#include "base/trace_event/trace_event.h" #include "build/build_config.h" #include "media/audio/fake_audio_log_factory.h" #include "media/base/media_switches.h" @@ -164,4 +165,37 @@ bool AudioManager::Shutdown() { return true; } +void AudioManager::TraceAmplitudePeak(bool trace_start) { + base::AutoLock scoped_lock(tracing_lock_); + + constexpr char kTraceName[] = "AmplitudePeak"; + + if (trace_start) { + // We might have never closed the previous trace. Abort it now. + if (is_trace_started_) { + TRACE_EVENT_NESTABLE_ASYNC_END1( + TRACE_DISABLED_BY_DEFAULT("audio.latency"), kTraceName, + current_trace_id_, "aborted", true); + } + + TRACE_EVENT_NESTABLE_ASYNC_BEGIN0( + TRACE_DISABLED_BY_DEFAULT("audio.latency"), kTraceName, + ++current_trace_id_); + + is_trace_started_ = true; + return; + } + + if (!is_trace_started_) { + // Avoid ending traces that were never started. + return; + } + + TRACE_EVENT_NESTABLE_ASYNC_END1(TRACE_DISABLED_BY_DEFAULT("audio.latency"), + kTraceName, current_trace_id_, "aborted", + false); + + is_trace_started_ = false; +} + } // namespace media diff --git a/media/audio/audio_manager.h b/media/audio/audio_manager.h index 7842a16a24545..529f1d7746901 100644 --- a/media/audio/audio_manager.h +++ b/media/audio/audio_manager.h @@ -10,6 +10,7 @@ #include "base/functional/callback.h" #include "base/gtest_prod_util.h" +#include "base/synchronization/lock.h" #include "base/threading/thread_checker.h" #include "build/build_config.h" #include "media/audio/audio_device_description.h" @@ -181,6 +182,15 @@ class MEDIA_EXPORT AudioManager { // Limits the number of streams that can be created for testing purposes. virtual void SetMaxStreamCountForTesting(int max_input, int max_output); + // Starts or stops tracing when a peak in Audio signal amplitude is detected. + // Does nothing if a call to stop tracing is made without first starting the + // trace. Aborts the current trace if a call to start tracing is made without + // stopping the existing trace. + // Note: tracing is intended to be started from exactly one input stream and + // stopped from exactly one output stream. If multiple streams are starting + // and stopping traces, the latency measurements will not be valid. + void TraceAmplitudePeak(bool trace_start); + protected: FRIEND_TEST_ALL_PREFIXES(AudioManagerTest, AudioDebugRecording); friend class AudioDeviceInfoAccessorForTests; @@ -260,6 +270,10 @@ class MEDIA_EXPORT AudioManager { private: friend class AudioSystemHelper; + base::Lock tracing_lock_; + int current_trace_id_ GUARDED_BY(tracing_lock_) = 0; + bool is_trace_started_ GUARDED_BY(tracing_lock_) = false; + std::unique_ptr audio_thread_; bool shutdown_ = false; // True after |this| has been shutdown. diff --git a/media/audio/pulse/pulse_input.cc b/media/audio/pulse/pulse_input.cc index 26c8b8fdd0aa8..236e5af89f65e 100644 --- a/media/audio/pulse/pulse_input.cc +++ b/media/audio/pulse/pulse_input.cc @@ -42,7 +42,10 @@ PulseAudioInputStream::PulseAudioInputStream( pa_mainloop_(mainloop), pa_context_(context), log_callback_(std::move(log_callback)), - handle_(nullptr) { + handle_(nullptr), + peak_detector_(base::BindRepeating(&AudioManager::TraceAmplitudePeak, + base::Unretained(audio_manager_), + /*trace_start=*/true)) { DCHECK(mainloop); DCHECK(context); CHECK(params_.IsValid()); @@ -350,8 +353,12 @@ void PulseAudioInputStream::ReadData() { fifo_.IncreaseCapacity(increase_blocks_of_buffer); } - fifo_.Push(data, number_of_frames, - SampleFormatToBytesPerChannel(pulse::kInputSampleFormat)); + const int bytes_per_sample = + SampleFormatToBytesPerChannel(pulse::kInputSampleFormat); + + peak_detector_.FindPeak(data, number_of_frames, bytes_per_sample); + + fifo_.Push(data, number_of_frames, bytes_per_sample); // Checks if we still have data. pa_stream_drop(handle_); diff --git a/media/audio/pulse/pulse_input.h b/media/audio/pulse/pulse_input.h index a081a24470910..9f37ea77ae5b3 100644 --- a/media/audio/pulse/pulse_input.h +++ b/media/audio/pulse/pulse_input.h @@ -16,6 +16,7 @@ #include "media/audio/audio_device_name.h" #include "media/audio/audio_io.h" #include "media/audio/audio_manager.h" +#include "media/base/amplitude_peak_detector.h" #include "media/base/audio_block_fifo.h" #include "media/base/audio_parameters.h" @@ -95,6 +96,8 @@ class PulseAudioInputStream : public AgcAudioStream { // #addr-of RAW_PTR_EXCLUSION pa_stream* handle_; + AmplitudePeakDetector peak_detector_; + base::ThreadChecker thread_checker_; }; diff --git a/media/audio/pulse/pulse_output.cc b/media/audio/pulse/pulse_output.cc index a8414df12b421..1696cc0838f3e 100644 --- a/media/audio/pulse/pulse_output.cc +++ b/media/audio/pulse/pulse_output.cc @@ -60,7 +60,10 @@ PulseAudioOutputStream::PulseAudioOutputStream( pa_stream_(nullptr), volume_(1.0f), source_callback_(nullptr), - buffer_size_(params_.GetBytesPerBuffer(kSampleFormatF32)) { + buffer_size_(params_.GetBytesPerBuffer(kSampleFormatF32)), + peak_detector_(base::BindRepeating(&AudioManager::TraceAmplitudePeak, + base::Unretained(manager_), + /*trace_start=*/false)) { CHECK(params_.IsValid()); SendLogMessage("%s({device_id=%s}, {params=[%s]})", __func__, device_id.c_str(), params.AsHumanReadableString().c_str()); @@ -178,6 +181,11 @@ void PulseAudioOutputStream::FulfillWriteRequest(size_t requested_bytes) { unwritten_frames_in_bus - frames_filled); } + // TODO(tguilbert): Consider moving this before each of the individual + // `pa_stream_write()` calls in the loop below, to improve the accuracy of + // the latency measurements. + peak_detector_.FindPeak(audio_bus_.get()); + audio_bus_->Scale(volume_); size_t frame_size = buffer_size_ / unwritten_frames_in_bus; diff --git a/media/audio/pulse/pulse_output.h b/media/audio/pulse/pulse_output.h index e06e165ce8ffd..ca2771a3d6e43 100644 --- a/media/audio/pulse/pulse_output.h +++ b/media/audio/pulse/pulse_output.h @@ -30,6 +30,7 @@ #include "base/threading/thread_checker.h" #include "media/audio/audio_io.h" #include "media/audio/audio_manager.h" +#include "media/base/amplitude_peak_detector.h" #include "media/base/audio_parameters.h" struct pa_context; @@ -114,6 +115,8 @@ class PulseAudioOutputStream : public AudioOutputStream { const size_t buffer_size_; + AmplitudePeakDetector peak_detector_; + base::ThreadChecker thread_checker_; }; diff --git a/media/audio/win/audio_low_latency_input_win.cc b/media/audio/win/audio_low_latency_input_win.cc index c1a926f95eb4b..58010578a7c31 100644 --- a/media/audio/win/audio_low_latency_input_win.cc +++ b/media/audio/win/audio_low_latency_input_win.cc @@ -19,6 +19,7 @@ #include "base/metrics/histogram_macros.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" +#include "base/trace_event/common/trace_event_common.h" #include "base/trace_event/trace_event.h" #include "base/win/core_winrt_util.h" #include "base/win/scoped_propvariant.h" @@ -287,6 +288,9 @@ WASAPIAudioInputStream::WASAPIAudioInputStream( AudioManager::LogCallback log_callback) : manager_(manager), glitch_reporter_(SystemGlitchReporter::StreamType::kCapture), + peak_detector_(base::BindRepeating(&AudioManager::TraceAmplitudePeak, + base::Unretained(manager_), + /*trace_start=*/true)), data_discontinuity_reporter_( std::make_unique()), device_id_(device_id), @@ -956,8 +960,10 @@ void WASAPIAudioInputStream::PullCaptureDataAndPushToSink() { if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { fifo_->PushSilence(num_frames_to_read); } else { - fifo_->Push(data_ptr, num_frames_to_read, - input_format_.Format.wBitsPerSample / 8); + const int bytes_per_sample = input_format_.Format.wBitsPerSample / 8; + + peak_detector_.FindPeak(data_ptr, num_frames_to_read, bytes_per_sample); + fifo_->Push(data_ptr, num_frames_to_read, bytes_per_sample); } hr = audio_capture_client_->ReleaseBuffer(num_frames_to_read); diff --git a/media/audio/win/audio_low_latency_input_win.h b/media/audio/win/audio_low_latency_input_win.h index b9224d8060a58..c0032a59de85e 100644 --- a/media/audio/win/audio_low_latency_input_win.h +++ b/media/audio/win/audio_low_latency_input_win.h @@ -80,6 +80,7 @@ #include "media/audio/agc_audio_stream.h" #include "media/audio/system_glitch_reporter.h" #include "media/audio/win/audio_manager_win.h" +#include "media/base/amplitude_peak_detector.h" #include "media/base/audio_converter.h" #include "media/base/audio_parameters.h" #include "media/base/media_export.h" @@ -214,6 +215,8 @@ class MEDIA_EXPORT WASAPIAudioInputStream // text logs (when a stream ends). SystemGlitchReporter glitch_reporter_; + AmplitudePeakDetector peak_detector_; + // Used to track and log data discontinuity warnings from // IAudioCaptureClient::GetBuffer. std::unique_ptr data_discontinuity_reporter_; diff --git a/media/audio/win/audio_low_latency_output_win.cc b/media/audio/win/audio_low_latency_output_win.cc index e9a6de0f0661b..dd043fded7162 100644 --- a/media/audio/win/audio_low_latency_output_win.cc +++ b/media/audio/win/audio_low_latency_output_win.cc @@ -89,6 +89,9 @@ WASAPIAudioOutputStream::WASAPIAudioOutputStream( : creating_thread_id_(base::PlatformThread::CurrentId()), manager_(manager), glitch_reporter_(SystemGlitchReporter::StreamType::kRender), + peak_detector_(base::BindRepeating(&AudioManager::TraceAmplitudePeak, + base::Unretained(manager_), + /*trace_start=*/false)), format_(), params_(params), opened_(false), @@ -751,8 +754,11 @@ bool WASAPIAudioOutputStream::RenderAudioFromSource(UINT64 device_frequency) { audio_bus.get()); // During pause/seek, keep the pipeline filled with zero'ed frames. - if (!frames_filled) + if (!frames_filled) { memset(audio_data, 0, packet_size_frames_); + } + + peak_detector_.FindPeak(audio_bus_.get()); // Release the buffer space acquired in the GetBuffer() call. // Render silence if we were not able to fill up the buffer totally. @@ -772,6 +778,8 @@ bool WASAPIAudioOutputStream::RenderAudioFromSource(UINT64 device_frequency) { audio_bus_->ToInterleaved( frames_filled, reinterpret_cast(audio_data)); + peak_detector_.FindPeak(audio_bus_.get()); + // Release the buffer space acquired in the GetBuffer() call. // Render silence if we were not able to fill up the buffer totally. DWORD flags = (num_filled_bytes < packet_size_bytes_) diff --git a/media/audio/win/audio_low_latency_output_win.h b/media/audio/win/audio_low_latency_output_win.h index 19950f18971a5..dcb5c168e9a20 100644 --- a/media/audio/win/audio_low_latency_output_win.h +++ b/media/audio/win/audio_low_latency_output_win.h @@ -114,6 +114,7 @@ #include "media/audio/audio_io.h" #include "media/audio/system_glitch_reporter.h" #include "media/audio/win/audio_manager_win.h" +#include "media/base/amplitude_peak_detector.h" #include "media/base/audio_parameters.h" #include "media/base/media_export.h" @@ -198,6 +199,8 @@ class MEDIA_EXPORT WASAPIAudioOutputStream // text logs (when a stream ends). SystemGlitchReporter glitch_reporter_; + AmplitudePeakDetector peak_detector_; + // Rendering is driven by this thread (which has no message loop). // All OnMoreData() callbacks will be called from this thread. std::unique_ptr render_thread_; diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn index ab819c2090fd5..041ceeeb62008 100644 --- a/media/base/BUILD.gn +++ b/media/base/BUILD.gn @@ -43,6 +43,8 @@ source_set("base") { } sources = [ + "amplitude_peak_detector.cc", + "amplitude_peak_detector.h", "android_overlay_config.cc", "android_overlay_config.h", "android_overlay_mojo_factory.h", diff --git a/media/base/amplitude_peak_detector.cc b/media/base/amplitude_peak_detector.cc new file mode 100644 index 0000000000000..a84104928df31 --- /dev/null +++ b/media/base/amplitude_peak_detector.cc @@ -0,0 +1,145 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/base/amplitude_peak_detector.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/time/time.h" +#include "base/trace_event/trace_event.h" +#include "media/base/audio_sample_types.h" + +namespace media { + +constexpr float kLoudnessThreshold = 0.5; // Corresponds to approximately -6dbs + +AmplitudePeakDetector::AmplitudePeakDetector(PeakDetectedCB peak_detected_cb) + : peak_detected_cb_(std::move(peak_detected_cb)) { + // AmplitudePeakDetector might be created from different a thread than + // `FindPeak()` is called. This is fine, as long as all `FindPeak()` calls + // come from the same thread. + DETACH_FROM_THREAD(thread_checker_); + + // For performance reasons, we only check whether we are tracing once, at + // construction time, since we don't expect this category to be enabled often. + // This comes at a usability cost: tracing must be started before a website + // creates any streams. Refreshing a page after starting a trace might not be + // enough force the recreation of streams too: one must close the tab, + // navigate to the chrome://media-internals audio tab, and wait for all + // streams to disappear (usually 2-10s). + is_tracing_enabled_ = false; + TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("audio.latency"), + &is_tracing_enabled_); +} + +AmplitudePeakDetector::~AmplitudePeakDetector() = default; + +void AmplitudePeakDetector::FindPeak(const void* data, + int frames, + int bytes_per_sample) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (LIKELY(!is_tracing_enabled_)) { + return; + } + + MaybeReportPeak(AreFramesLoud(data, frames, bytes_per_sample)); +} + +void AmplitudePeakDetector::FindPeak(const AudioBus* audio_bus) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (LIKELY(!is_tracing_enabled_)) { + return; + } + + MaybeReportPeak(AreFramesLoud(audio_bus)); +} + +template +bool IsDataLoud(const T* audio_data, + int frames, + const T min_loudness, + const T max_loudness) { + int n = 0; + while (n++ < frames) { + if (audio_data[n] < min_loudness || audio_data[n] > max_loudness) { + return true; + } + } + return false; +} + +template +bool LoudDetector(const void* data, int frames) { + const T* audio_data = reinterpret_cast(data); + + constexpr T min_loudness = + FixedSampleTypeTraits::FromFloat(-kLoudnessThreshold); + constexpr T max_loudness = + FixedSampleTypeTraits::FromFloat(kLoudnessThreshold); + + return IsDataLoud(audio_data, frames, min_loudness, max_loudness); +} + +template <> +bool LoudDetector(const void* data, int frames) { + return IsDataLoud(reinterpret_cast(data), frames, + -kLoudnessThreshold, kLoudnessThreshold); +} + +// Returns whether if any of the samples in `audio_bus` surpass +// `kLoudnessThreshold`. +bool AmplitudePeakDetector::AreFramesLoud(const AudioBus* audio_bus) { + DCHECK(!audio_bus->is_bitstream_format()); + + for (int ch = 0; ch < audio_bus->channels(); ++ch) { + if (LoudDetector(audio_bus->channel(ch), audio_bus->frames())) { + return true; + } + } + return false; +} + +// Returns whether if any of the samples in `data` surpass `kLoudnessThreshold`. +bool AmplitudePeakDetector::AreFramesLoud(const void* data, + int frames, + int bytes_per_sample) { + switch (bytes_per_sample) { + case 1: + return LoudDetector(data, frames); + + case 2: + return LoudDetector(data, frames); + + case 4: + return LoudDetector(data, frames); + default: + NOTREACHED(); + return false; + }; +} + +void AmplitudePeakDetector::MaybeReportPeak(bool are_frames_loud) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + // No change. + if (in_a_peak_ == are_frames_loud) { + return; + } + + // TODO(tguilbert): consider only "exiting" a peak after a few consecutive + // quiet buffers; this should reduce the chance of accidentally detecting + // another rising edge. + in_a_peak_ = are_frames_loud; + + // Volume has transitioned from quiet to loud; we found a rising edge. + // `is_trace_start` indicates whether to start or stop the trace, whether we + // are tracing audio input or output respectively. + if (in_a_peak_) { + peak_detected_cb_.Run(); + } +} + +} // namespace media diff --git a/media/base/amplitude_peak_detector.h b/media/base/amplitude_peak_detector.h new file mode 100644 index 0000000000000..98d434e4f77bd --- /dev/null +++ b/media/base/amplitude_peak_detector.h @@ -0,0 +1,62 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_BASE_AMPLITUDE_PEAK_DETECTOR_H_ +#define MEDIA_BASE_AMPLITUDE_PEAK_DETECTOR_H_ + +#include "base/strings/string_piece.h" +#include "base/threading/thread_checker.h" +#include "media/base/audio_bus.h" +#include "media/base/media_export.h" + +namespace media { +// Helper class which acts as a filter to detect jumps in audio signal +// amplitude. When there is a large increase in amplitude, it will run its +// provided callback. +// +// This class can be used to start/stop tracing events, to measure internal +// audio latency. Traces should be started right after detecting a peak in the +// audio input, and stopped right before sending a peak to be played out; this +// should estimating the end-to-end latency from microphone input to speakers. +// +// An example test page with instructions can be found under +// third_party/blink/manual_tests/audio_latency.html +// +// Note: does nothing if the "audio.latency" tracing category is disabled. +// +// Note: AmplitudePeakDetector can be created on any thread, but all calls to +// `FindPeak()` must be made on the same thread. +class MEDIA_EXPORT AmplitudePeakDetector { + public: + using PeakDetectedCB = base::RepeatingClosure; + + explicit AmplitudePeakDetector(PeakDetectedCB peak_detected_cb); + ~AmplitudePeakDetector(); + + AmplitudePeakDetector(const AmplitudePeakDetector&) = delete; + AmplitudePeakDetector& operator=(const AmplitudePeakDetector&) = delete; + + // Detects increases in amplitude, and runs `peak_detected_cb_` when we cross + // a threshold (but not when amplitude falls back bellow the threshold). + void FindPeak(const void* data, int frames, int bytes_per_sample); + void FindPeak(const AudioBus* audio_bus); + + private: + bool AreFramesLoud(const AudioBus* audio_bus); + bool AreFramesLoud(const void* data, int frames, int bytes_per_sample); + + void MaybeReportPeak(bool are_frames_loud); + + const PeakDetectedCB peak_detected_cb_; + + bool in_a_peak_ GUARDED_BY_CONTEXT(thread_checker_) = false; + + bool is_tracing_enabled_; + + THREAD_CHECKER(thread_checker_); +}; + +} // namespace media + +#endif // MEDIA_BASE_AMPLITUDE_PEAK_DETECTOR_H_ diff --git a/third_party/blink/manual_tests/audio_latency.html b/third_party/blink/manual_tests/audio_latency.html new file mode 100644 index 0000000000000..419e840b34554 --- /dev/null +++ b/third_party/blink/manual_tests/audio_latency.html @@ -0,0 +1,146 @@ + + + + Audio latency tracing page + + + +
+

This page is meant to serve as an example of how to use "audio.latency" + tracing to measure internal audio latency. The "audio.latency" category + enables code which listens for jumps in amplitude (volume) and starts or + stops tracing. We start tracing right after receiving loud audio from a + microphone, and stop right before sending that loud audio to speakers. + The duration of the trace event (which should show up as an + "AmplitudePeak" in the tracing tools) encompases the total internal latency. +

+

Instructions: +

    +
  • [Prerequisite] Close all other tabs but this one.
  • +
  • [Prerequisite] Make sure there is a microphone plugged into the test + machine, and that the surrounding environment is not too loud.
  • +
  • Open chrome://tracing and start recording a trace which includes the + "audio.latency" category.
  • +
  • Click the "Initialize" button.
  • +
  • Select either the WebAudio or the HTMLAudioElement button.
  • +
  • Repeatedly clap next to the microphone a few times. Make sure to clap + clearly, and to leave time between claps (0.5s-1s should be enough).
  • +
  • Stop the trace. "AmplitudePeak" events should show up under the audio + service process
  • +
+

+

+ Note: The "audio.latency" category only expects one input and one output. + Multiple IOs will result in incoherent traces. Additionally, + tracing *must* be started before starting the test, or no traces will + be captured. Refreshing the page after starting a trace is also not enough: + one must verify that there are no InputStreams or OutputStreams alive, by + navigating to the "audio" tab of chrome://media-internals. Closing all tabs + and waiting 2-10s should be enough for all outstanding streams to close. +

+ +
+
+
+ +
+
+ + + + + + \ No newline at end of file