20#include <QtCore/QDataStream>
21#include <QtCore/qtimer.h>
23#include <private/qaudiohelpers_p.h>
25#include <qloggingcategory.h>
27#include <audioclient.h>
28#include <mmdeviceapi.h>
43 qint64 readData(
char* data, qint64 len)
override {
return m_audioSource.read(data, len); }
50QWindowsAudioSource::QWindowsAudioSource(ComPtr<IMMDevice> device, QObject *parent)
52 m_timer(
new QTimer(
this)),
53 m_device(std::move(device)),
54 m_ourSink(
new OurSink(*
this))
56 m_ourSink->open(QIODevice::ReadOnly|QIODevice::Unbuffered);
57 m_timer->setTimerType(Qt::PreciseTimer);
58 m_timer->setSingleShot(
true);
59 m_timer->callOnTimeout(
this, &QWindowsAudioSource::pullCaptureClient);
64 m_volume = qBound(qreal(0), volume, qreal(1));
89 if (m_deviceState == QAudio::StoppedState) {
92 if (m_format != fmt) {
93 qWarning() <<
"Cannot set a new audio format, in the current state ("
94 << m_deviceState <<
")";
106 if (state != m_deviceState) {
107 bool wasActive = m_deviceState == QAudio::ActiveState || m_deviceState == QAudio::IdleState;
108 bool isActive = state == QAudio::ActiveState || state == QAudio::IdleState;
110 if (isActive && !wasActive) {
111 m_audioClient->Start();
112 qCDebug(qLcAudioSource) <<
"Audio client started";
114 }
else if (wasActive && !isActive) {
116 m_audioClient->Stop();
117 qCDebug(qLcAudioSource) <<
"Audio client stopped";
120 m_deviceState = state;
121 emit stateChanged(m_deviceState);
124 if (error != m_errorState) {
125 m_errorState = error;
126 emit errorChanged(error);
132 if (m_deviceState == QAudio::StoppedState)
135 UINT32 actualFrames = 0;
136 BYTE *data =
nullptr;
138 HRESULT hr = m_captureClient->GetBuffer(&data, &actualFrames, &flags,
nullptr,
nullptr);
140 qWarning() <<
"IAudioCaptureClient::GetBuffer failed" << errorString(hr);
141 deviceStateChange(QAudio::IdleState, QAudio::IOError);
145 if (actualFrames == 0)
149 if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
150 out.resize(m_resampler.outputFormat().bytesForDuration(
151 m_resampler.inputFormat().framesForDuration(actualFrames)),
154 out = m_resampler.resample(
155 { data, m_resampler.inputFormat().bytesForFrames(actualFrames) });
156 QAudioHelperInternal::qMultiplySamples(m_volume, m_resampler.outputFormat(), out.data(), out.data(), out.size());
159 hr = m_captureClient->ReleaseBuffer(actualFrames);
161 qWarning() <<
"IAudioCaptureClient::ReleaseBuffer failed" << errorString(hr);
162 deviceStateChange(QAudio::IdleState, QAudio::IOError);
171 auto allocated = allocatedFrames(m_audioClient.Get());
172 auto inUse = usedFrames(m_audioClient.Get());
174 if (!allocated || !inUse) {
175 deviceStateChange(QAudio::IdleState, QAudio::IOError);
179 if (*inUse > *allocated / 2) {
182 auto timeToHalfBuffer = m_resampler.inputFormat().durationForFrames(*allocated / 2 - *inUse);
183 m_timer->start(timeToHalfBuffer / 1000);
190 qCDebug(qLcAudioSource) <<
"Pull captureClient";
192 auto out = readCaptureClientBuffer();
197 qint64 written = m_clientSink->write(out.data(), out.size());
198 if (written != out.size())
199 qCDebug(qLcAudioSource) <<
"Did not write all data to the output";
202 m_clientBufferResidue += out;
203 emit m_ourSink->readyRead();
207 if (m_deviceState != QAudio::StoppedState)
213 qCDebug(qLcAudioSource) <<
"start(ioDevice)";
214 if (m_deviceState != QAudio::StoppedState)
217 if (device ==
nullptr)
221 m_errorState = QAudio::OpenError;
222 emit errorChanged(QAudio::OpenError);
226 m_clientSink = device;
228 deviceStateChange(QAudio::ActiveState, QAudio::NoError);
233 qCDebug(qLcAudioSource) <<
"start()";
234 if (m_deviceState != QAudio::StoppedState)
238 m_errorState = QAudio::OpenError;
239 emit errorChanged(QAudio::OpenError);
244 deviceStateChange(QAudio::IdleState, QAudio::NoError);
250 if (m_deviceState == QAudio::StoppedState)
258 HRESULT hr = m_device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER,
259 nullptr, (
void**)m_audioClient.GetAddressOf());
261 qCWarning(qLcAudioSource) <<
"Failed to activate audio device" << errorString(hr);
265 QComTaskResource<WAVEFORMATEX> pwfx;
266 hr = m_audioClient->GetMixFormat(pwfx.address());
268 qCWarning(qLcAudioSource) <<
"Format unsupported" << errorString(hr);
272 if (!m_resampler.setup(waveFormatExToFormat(*pwfx), m_format)) {
273 qCWarning(qLcAudioSource) <<
"Failed to set up resampler";
277 if (m_bufferSize == 0)
278 m_bufferSize = m_format.sampleRate() * m_format.bytesPerFrame() / 5;
280 REFERENCE_TIME requestedDuration = m_format.durationForBytes(m_bufferSize);
282 hr = m_audioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, requestedDuration, 0, pwfx.get(),
286 qCWarning(qLcAudioSource) <<
"Failed to initialize audio client" << errorString(hr);
290 auto framesAllocated = allocatedFrames(m_audioClient.Get());
291 if (!framesAllocated) {
292 qCWarning(qLcAudioSource) <<
"Failed to get audio client buffer size";
296 m_bufferSize = m_format.bytesForDuration(
297 m_resampler.inputFormat().durationForFrames(*framesAllocated));
299 hr = m_audioClient->GetService(IID_PPV_ARGS(m_captureClient.GetAddressOf()));
301 qCWarning(qLcAudioSource) <<
"Failed to obtain audio client rendering service" << errorString(hr);
310 qCDebug(qLcAudioSource) <<
"close()";
311 if (m_deviceState == QAudio::StoppedState)
314 deviceStateChange(QAudio::StoppedState, QAudio::NoError);
316 m_clientBufferResidue.clear();
317 m_captureClient.Reset();
318 m_audioClient.Reset();
319 m_clientSink =
nullptr;
324 if (m_deviceState == QAudio::StoppedState || m_deviceState == QAudio::SuspendedState)
327 auto frames = usedFrames(m_audioClient.Get());
329 auto clientBufferSize = m_resampler.outputFormat().bytesForDuration(
330 m_resampler.inputFormat().durationForFrames(*frames));
332 return clientBufferSize + m_clientBufferResidue.size();
341 deviceStateChange(QAudio::ActiveState, QAudio::NoError);
344 if (data ==
nullptr || len < 0)
348 if (!m_clientBufferResidue.isEmpty()) {
349 auto copyLen = qMin(m_clientBufferResidue.size(), len);
350 memcpy(data, m_clientBufferResidue.data(), copyLen);
355 m_clientBufferResidue = QByteArray{ m_clientBufferResidue.data() + offset,
356 m_clientBufferResidue.size() - offset };
359 auto out = readCaptureClientBuffer();
360 if (!out.isEmpty()) {
361 qsizetype copyLen = qMin(out.size(), len);
362 memcpy(data + offset, out.data(), copyLen);
365 m_clientBufferResidue = QByteArray{ out.data() + copyLen, out.size() - copyLen };
374 qCDebug(qLcAudioSource) <<
"resume()";
375 if (m_deviceState == QAudio::SuspendedState) {
376 deviceStateChange(QAudio::ActiveState, QAudio::NoError);
383 m_bufferSize = value;
393 if (m_deviceState == QAudio::StoppedState)
396 return m_resampler.outputFormat().durationForBytes(m_resampler.totalOutputBytes());
401 qCDebug(qLcAudioSource) <<
"suspend";
402 if (m_deviceState == QAudio::ActiveState || m_deviceState == QAudio::IdleState)
403 deviceStateChange(QAudio::SuspendedState, QAudio::NoError);
qint64 readData(char *data, qint64 len) override
Reads up to maxSize bytes from the device into data, and returns the number of bytes read or -1 if an...
qint64 bytesAvailable() const override
Returns the number of bytes that are available for reading.
qint64 writeData(const char *, qint64) override
Writes up to maxSize bytes from data to the device.
OurSink(QWindowsAudioSource &source)
qint64 processedUSecs() const override
qsizetype bufferSize() const override
qreal volume() const override
qint64 read(char *data, qint64 len)
void start(QIODevice *device) override
void setVolume(qreal volume) override
void setFormat(const QAudioFormat &fmt) override
QAudio::State state() const override
qsizetype bytesReady() const override
void setBufferSize(qsizetype value) override
QAudio::Error error() const override
QAudioFormat format() const override
QIODevice * start() override
Combined button and popup list for selecting options.
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)