-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaudiocapture.cpp
More file actions
208 lines (184 loc) · 6.03 KB
/
Copy pathaudiocapture.cpp
File metadata and controls
208 lines (184 loc) · 6.03 KB
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#include "audiocapture.h"
#include <cmath>
#if TT_HAVE_AUDIO_CAPTURE
# include <QAudioSource>
# include <QMediaDevices>
# include <QIODevice>
# include <QDebug>
#endif
namespace {
// Bytes occupied by `ms` milliseconds of the STT capture format.
int msToBytes(int ms)
{
return AudioCapture::kSampleRate * AudioCapture::kChannelCount
* AudioCapture::kBytesPerSample * ms / 1000;
}
} // namespace
AudioCapture::AudioCapture(QObject* parent)
: QObject(parent)
{
m_chunker.configure(msToBytes(kWindowMs), msToBytes(kOverlapMs));
}
AudioCapture::~AudioCapture()
{
teardown();
}
void AudioCapture::setInputDevice(const QString& id)
{
if (id == m_requestedId)
return;
m_requestedId = id;
// Apply immediately when live so the meter/STT follow the new mic without a
// manual restart; otherwise the id is picked up at the next start().
if (isCapturing()) {
stop();
start();
}
}
QList<AudioInputDevice> AudioCapture::availableDevices()
{
QList<AudioInputDevice> list;
#if TT_HAVE_AUDIO_CAPTURE
const QAudioDevice def = QMediaDevices::defaultAudioInput();
const auto inputs = QMediaDevices::audioInputs();
list.reserve(inputs.size());
for (const QAudioDevice& d : inputs) {
AudioInputDevice e;
e.id = QString::fromLatin1(d.id().toHex());
e.name = d.description();
e.isDefault = (d.id() == def.id());
list.append(e);
}
#endif
return list;
}
#if TT_HAVE_AUDIO_CAPTURE
bool AudioCapture::isCapturing() const
{
return m_source != nullptr;
}
void AudioCapture::resolveFormatAndDevice()
{
const QAudioDevice def = QMediaDevices::defaultAudioInput();
m_device = def;
if (!m_requestedId.isEmpty()) {
const QByteArray want = QByteArray::fromHex(m_requestedId.toLatin1());
for (const QAudioDevice& d : QMediaDevices::audioInputs()) {
if (d.id() == want) { m_device = d; break; }
}
}
m_format = QAudioFormat();
m_format.setSampleRate(kSampleRate);
m_format.setChannelCount(kChannelCount);
m_format.setSampleFormat(QAudioFormat::Int16);
// QAudioSource does not resample. If the backend cannot deliver our exact
// STT format, fall back to the device's preferred format so capture still
// works; converting that to 16 kHz mono for the STT engine is a later step.
if (!m_device.isNull() && !m_device.isFormatSupported(m_format)) {
const QAudioFormat pref = m_device.preferredFormat();
qWarning("AudioCapture: '%s' does not support 16 kHz mono Int16; "
"falling back to preferred format (%d Hz, %d ch). "
"Resampling for STT is not yet implemented.",
qPrintable(m_device.description()),
pref.sampleRate(), pref.channelCount());
m_format = pref;
}
}
void AudioCapture::start()
{
if (m_source)
return; // already capturing — start() is idempotent
resolveFormatAndDevice();
if (m_device.isNull()) {
qWarning("AudioCapture: no audio input device available.");
emit captureError(tr("No microphone found."));
return;
}
m_chunker.reset();
m_source = new QAudioSource(m_device, m_format, this);
m_io = m_source->start(); // pull mode: read from the returned QIODevice
if (!m_io) {
qWarning("AudioCapture: failed to start capture (state %d).",
int(m_source->state()));
teardown();
emit captureError(tr("Could not open the selected microphone."));
return;
}
connect(m_io, &QIODevice::readyRead, this, [this]{ onReadyRead(); });
emit captureStarted();
}
void AudioCapture::stop()
{
if (!m_source)
return;
teardown();
emit captureStopped();
}
void AudioCapture::teardown()
{
if (m_io) {
m_io->disconnect(this);
m_io = nullptr; // owned by m_source; do not delete separately
}
if (m_source) {
m_source->stop();
m_source->disconnect(this);
// Delete synchronously (not deleteLater): QAudioSource's destructor tears
// down the backend stream and its audio thread here and now, so rapid
// start/stop toggling can never accumulate live sources or threads.
delete m_source;
m_source = nullptr;
}
m_chunker.reset();
}
void AudioCapture::onReadyRead()
{
if (!m_io)
return;
const QByteArray pcm = m_io->readAll();
if (!pcm.isEmpty())
processPcm(pcm);
}
void AudioCapture::processPcm(const QByteArray& pcm)
{
emit bufferReady(pcm);
emit levelChanged(computeLevel(pcm));
const QList<QByteArray> windows = m_chunker.append(pcm);
for (const QByteArray& w : windows)
emit chunkReady(w);
}
qreal AudioCapture::computeLevel(const QByteArray& pcm) const
{
// RMS amplitude, normalised to [0,1]. Handles the primary Int16 path and
// the Float fallback that some backends prefer.
switch (m_format.sampleFormat()) {
case QAudioFormat::Int16: {
const int n = int(pcm.size() / int(sizeof(qint16)));
if (n <= 0) return 0.0;
const auto* s = reinterpret_cast<const qint16*>(pcm.constData());
double sumsq = 0.0;
for (int i = 0; i < n; ++i) {
const double v = s[i] / 32768.0;
sumsq += v * v;
}
return qBound(0.0, std::sqrt(sumsq / n), 1.0);
}
case QAudioFormat::Float: {
const int n = int(pcm.size() / int(sizeof(float)));
if (n <= 0) return 0.0;
const auto* s = reinterpret_cast<const float*>(pcm.constData());
double sumsq = 0.0;
for (int i = 0; i < n; ++i)
sumsq += double(s[i]) * double(s[i]);
return qBound(0.0, std::sqrt(sumsq / n), 1.0);
}
default:
return 0.0;
}
}
#else // ── Qt5 stub: no QAudioSource/QMediaDevices available ───────────────────
bool AudioCapture::isCapturing() const { return false; }
void AudioCapture::start() {}
void AudioCapture::stop() {}
void AudioCapture::teardown() {}
#endif // TT_HAVE_AUDIO_CAPTURE