1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
media / base / amplitude_peak_detector.cc [blame]
// 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.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#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)) {
// 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::SetIsTracingEnabledForTests(
bool is_tracing_enabled) {
is_tracing_enabled_ = is_tracing_enabled;
}
void AmplitudePeakDetector::FindPeak(const void* data,
int frames,
int bytes_per_sample) {
if (!is_tracing_enabled_) [[likely]] {
return;
}
MaybeReportPeak(AreFramesLoud(data, frames, bytes_per_sample));
}
void AmplitudePeakDetector::FindPeak(const AudioBus* audio_bus) {
if (!is_tracing_enabled_) [[likely]] {
return;
}
MaybeReportPeak(AreFramesLoud(audio_bus));
}
template <class T>
bool IsDataLoud(const T* audio_data,
int frames,
const T min_loudness,
const T max_loudness) {
int n = 0;
do {
if (audio_data[n] < min_loudness || audio_data[n] > max_loudness) {
return true;
}
} while (++n < frames);
return false;
}
template <class T>
bool LoudDetector(const void* data, int frames) {
const T* audio_data = reinterpret_cast<const T*>(data);
constexpr T min_loudness =
FixedSampleTypeTraits<T>::FromFloat(-kLoudnessThreshold);
constexpr T max_loudness =
FixedSampleTypeTraits<T>::FromFloat(kLoudnessThreshold);
return IsDataLoud<T>(audio_data, frames, min_loudness, max_loudness);
}
template <>
bool LoudDetector<float>(const void* data, int frames) {
return IsDataLoud<float>(reinterpret_cast<const float*>(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<float>(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<uint8_t>(data, frames);
case 2:
return LoudDetector<int16_t>(data, frames);
case 4:
return LoudDetector<int32_t>(data, frames);
default:
NOTREACHED();
};
}
void AmplitudePeakDetector::MaybeReportPeak(bool are_frames_loud) {
// We never expect two threads to be calling into the peak detector at the
// same time. However, some platform implementations can unpredictably change
// underlying realtime audio threads (e.g. during a device change).
// This prevents us from using a ThreadChecker, which is bound to a specific
// thread ID.
// Instead, check that there is never be contention on `lock_`. If there ever
// was, we would know there is a valid threading issue that needs to be
// investigated.
lock_.AssertNotHeld();
base::AutoLock auto_lock(lock_);
// 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