Commit ee280bde authored by Oskar Sundbom's avatar Oskar Sundbom Committed by Commit Bot

Add audio playback glitch stats for Windows

Glitches are detected by comparing the differences between the device
clock's position and the performance counter value at which the
position was recorded. If the performance counter has moved further
than the device position has, then it's treated as a glitch.

Initially, I tried detecting when the write position (based on the
audio device's clock) moved more than a single buffer's duration ahead
between two calls. This did not prove reliable on my machine, as the
device clock stopped moving forward unless fed enough data.

Bug: 864463
Change-Id: Idb1d6c732964b5c4ca30f98f3521f3dbefe3b937
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1611919
Commit-Queue: Oskar Sundbom <ossu@chromium.org>
Commit-Queue: Dale Curtis <dalecurtis@chromium.org>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#659633}
parent efc656f9
......@@ -12,6 +12,7 @@
#include "base/command_line.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
......@@ -286,6 +287,8 @@ void WASAPIAudioOutputStream::Start(AudioSourceCallback* callback) {
source_ = callback;
num_written_frames_ = endpoint_buffer_size_frames_;
last_position_ = 0;
last_qpc_position_ = 0;
// Create and start the thread that will drive the rendering by waiting for
// render events.
......@@ -333,6 +336,8 @@ void WASAPIAudioOutputStream::Stop() {
callback->OnError();
}
ReportAndResetStats();
// Extra safety check to ensure that the buffers are cleared.
// If the buffers are not cleared correctly, the next call to Start()
// would fail with AUDCLNT_E_BUFFER_ERROR at IAudioRenderClient::GetBuffer().
......@@ -527,6 +532,43 @@ bool WASAPIAudioOutputStream::RenderAudioFromSource(UINT64 device_frequency) {
const uint64_t played_out_frames =
format_.Format.nSamplesPerSec * position / device_frequency;
// Check for glitches. Records a glitch whenever the stream's position has
// moved forward significantly less than the performance counter has. The
// threshold is set to half the buffer size, to limit false positives.
if (last_qpc_position_ != 0) {
const int64_t buffer_duration_us = packet_size_frames_ *
base::Time::kMicrosecondsPerSecond /
format_.Format.nSamplesPerSec;
const int64_t position_us =
position * base::Time::kMicrosecondsPerSecond / device_frequency;
const int64_t last_position_us = last_position_ *
base::Time::kMicrosecondsPerSecond /
device_frequency;
// The QPC values are in 100 ns units.
const int64_t qpc_position_us = qpc_position / 10;
const int64_t last_qpc_position_us = last_qpc_position_ / 10;
const int64_t position_diff_us = position_us - last_position_us;
const int64_t qpc_position_diff_us =
qpc_position_us - last_qpc_position_us;
if (qpc_position_diff_us - position_diff_us > buffer_duration_us / 2) {
++num_glitches_detected_;
base::TimeDelta glitch_duration = base::TimeDelta::FromMicroseconds(
qpc_position_diff_us - position_diff_us);
if (glitch_duration > largest_glitch_)
largest_glitch_ = glitch_duration;
cumulative_audio_lost_ += glitch_duration;
}
}
last_position_ = position;
last_qpc_position_ = qpc_position;
// Number of frames that have been written to the buffer but not yet
// played out.
const uint64_t delay_frames = num_written_frames_ - played_out_frames;
......@@ -667,4 +709,21 @@ void WASAPIAudioOutputStream::StopThread() {
source_ = NULL;
}
void WASAPIAudioOutputStream::ReportAndResetStats() {
// Even if there aren't any glitches, we want to record it to get a feel for
// how often we get no glitches vs the alternative.
UMA_HISTOGRAM_CUSTOM_COUNTS("Media.Audio.Render.Glitches",
num_glitches_detected_, 1, 999999, 100);
// Don't record these unless there actually was a glitch, though.
if (num_glitches_detected_ != 0) {
UMA_HISTOGRAM_COUNTS_1M("Media.Audio.Render.LostFramesInMs",
cumulative_audio_lost_.InMilliseconds());
UMA_HISTOGRAM_COUNTS_1M("Media.Audio.Render.LargestGlitchMs",
largest_glitch_.InMilliseconds());
}
num_glitches_detected_ = 0;
cumulative_audio_lost_ = base::TimeDelta();
largest_glitch_ = base::TimeDelta();
}
} // namespace media
......@@ -170,6 +170,9 @@ class MEDIA_EXPORT WASAPIAudioOutputStream :
// |source_| is set to NULL.
void StopThread();
// Reports audio stream glitch stats and resets them to their initial values.
void ReportAndResetStats();
// Contains the thread ID of the creating thread.
const base::PlatformThreadId creating_thread_id_;
......@@ -220,6 +223,21 @@ class MEDIA_EXPORT WASAPIAudioOutputStream :
// Counts the number of audio frames written to the endpoint buffer.
UINT64 num_written_frames_;
// The position read during the last call to RenderAudioFromSource
UINT64 last_position_ = 0;
// The performance counter read during the last call to RenderAudioFromSource
UINT64 last_qpc_position_ = 0;
// The number of glitches detected while this stream was active.
int num_glitches_detected_ = 0;
// The approximate amount of audio lost due to glitches.
base::TimeDelta cumulative_audio_lost_;
// The largest single glitch recorded.
base::TimeDelta largest_glitch_;
// Pointer to the client that will deliver audio samples to be played out.
AudioSourceCallback* source_;
......
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