6#include <QtCore/private/qsystemerror_p.h>
7#include <QtCore/private/qfunctions_win_p.h>
8#include <QtMultimedia/private/qmemory_resource_tlsf_p.h>
9#include <QtMultimedia/private/qwindowsaudiodevice_p.h>
10#include <QtMultimedia/private/qwindowsresampler_p.h>
12#include <audioclient.h>
13#include <mmdeviceapi.h>
20using namespace std::chrono_literals;
24QAudioFormat makeHostFormatForSink(
const QAudioDevice &device,
const QAudioFormat &format)
26 const QWindowsAudioDevice *winDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(device);
28 auto status = winDevice->m_probeDataFuture.wait_for(QAudioDevicePrivate::formatProbeTimeout);
30 case std::future_status::ready:
31 case std::future_status::deferred:
33 case std::future_status::timeout:
34 return QAudioFormat{};
39 auto [minProbedChannels, maxProbedChannels] = winDevice->m_probeDataFuture.get().channelCountRange;
41 QAudioFormat hostFormat = format;
42 const int requestedChannelCount = format.channelCount();
43 if (requestedChannelCount < device.minimumChannelCount()) {
44 hostFormat.setChannelCount(minProbedChannels);
45 hostFormat.setChannelConfig(
46 QAudioFormat::defaultChannelConfigForChannelCount(minProbedChannels));
47 }
else if (requestedChannelCount > device.maximumChannelCount()) {
48 hostFormat.setChannelCount(maxProbedChannels);
49 hostFormat.setChannelConfig(
50 QAudioFormat::defaultChannelConfigForChannelCount(maxProbedChannels));
59 QWindowsAudioSink *parent,
float volume, std::optional<int32_t> hardwareBufferFrames, AudioEndpointRole role):
89 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
93 bool clientOpen = openAudioClient(std::move(immDevice), m_role);
97 setQIODevice(ioDevice);
98 createQIODeviceConnections(ioDevice);
106 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
110 bool clientOpen = openAudioClient(std::move(immDevice), m_role);
114 QIODevice *ioDevice = createRingbufferWriterDevice();
116 m_parent->updateStreamIdle(
true, QWindowsAudioSink::EmitStateSignal::False);
118 setQIODevice(ioDevice);
119 createQIODeviceConnections(ioDevice);
122 return started ? ioDevice :
nullptr;
127 auto immDevice = QAudioDevicePrivate::handle<QWindowsAudioDevice>(m_audioDevice)->open();
131 bool clientOpen = openAudioClient(std::move(immDevice), m_role);
135 m_audioCallback = std::move(audioCallback);
143 QWindowsAudioUtils::audioClientStop(m_audioClient);
149 QWindowsAudioUtils::audioClientStart(m_audioClient);
154 using namespace QWindowsAudioUtils;
157 m_shutdownPolicy = shutdownPolicy;
160 switch (shutdownPolicy) {
161 case ShutdownPolicy::DiscardRingbuffer: {
162 audioClientStop(m_audioClient);
164 audioClientReset(m_audioClient);
168 case ShutdownPolicy::DrainRingbuffer: {
169 m_ringbufferDrained.callOnActivated([self = shared_from_this()]()
mutable {
170 self->joinWorkerThread();
176 Q_UNREACHABLE_RETURN();
183 m_parent->updateStreamIdle(streamIsIdle);
188 using namespace QWindowsAudioUtils;
190 std::optional<AudioClientCreationResult> clientData =
191 createAudioClient(device, m_hostFormat, m_hardwareBufferFrames, m_wasapiHandle, role);
196 m_audioClient = std::move(clientData->client);
197 m_periodSize = clientData->periodSize;
198 m_audioClientFrames = clientData->audioClientFrames;
200 HRESULT hr = m_audioClient->GetService(IID_PPV_ARGS(m_renderClient.GetAddressOf()));
202 qWarning() <<
"IAudioClient3::GetService failed to obtain IAudioRenderClient"
203 << audioClientErrorString(hr);
207 if (m_audioDevice.preferredFormat().sampleRate() != m_hostFormat.sampleRate())
208 audioClientSetRate(m_audioClient, m_hostFormat.sampleRate());
215 using namespace QWindowsAudioUtils;
216 m_workerThread.reset(QThread::create([
this, streamType] {
217 setMCSSForPeriodSize(m_periodSize);
218 fillInitialHostBuffer(streamType);
219 std::optional<QComHelper> m_comHelper;
221 if (m_hostFormat != m_format) {
222 m_comHelper.emplace();
223 m_resampler = std::make_unique<QWindowsResampler>();
224 m_resampler->setup(m_format, m_hostFormat);
226 m_memoryResource = std::make_unique<QTlsfMemoryResource>(512 * 1024);
229 switch (streamType) {
230 case StreamType::Ringbuffer:
231 return runProcessRingbufferLoop();
232 case StreamType::Callback:
233 return runProcessCallbackLoop();
236 m_workerThread->setObjectName(u"QWASAPIAudioSinkStream");
237 m_workerThread->start();
239 bool started = QWindowsAudioUtils::audioClientStart(m_audioClient);
249 switch (streamType) {
259 Q_UNREACHABLE_RETURN();
265 using namespace QWindowsAudioUtils;
268 constexpr std::chrono::milliseconds timeout = 2s;
269 DWORD retval = WaitForSingleObject(m_wasapiHandle.get(), timeout.count());
270 if (retval != WAIT_OBJECT_0) {
274 handleAudioClientError();
278 if (isStopRequested()) {
279 switch (m_shutdownPolicy.load(std::memory_order_relaxed)) {
280 case ShutdownPolicy::DiscardRingbuffer:
282 case ShutdownPolicy::DrainRingbuffer: {
283 bool bufferDrained = visitRingbuffer([](
const auto &ringbuffer) {
284 return ringbuffer.used() == 0;
287 audioClientStop(m_audioClient);
288 audioClientReset(m_audioClient);
290 m_ringbufferDrained.set();
296 Q_UNREACHABLE_RETURN();
300 bool success = processRingbuffer();
302 handleAudioClientError();
310 using namespace QWindowsAudioUtils;
313 constexpr std::chrono::milliseconds timeout = 2s;
314 DWORD retval = WaitForSingleObject(m_wasapiHandle.get(), timeout.count());
315 if (retval != WAIT_OBJECT_0) {
319 handleAudioClientError();
323 if (isStopRequested())
326 bool success = processCallback();
328 handleAudioClientError();
334template <
typename Functor>
337 uint32_t numFramesPadding;
338 HRESULT hr = m_audioClient->GetCurrentPadding(&numFramesPadding);
340 qWarning() <<
"IAudioClient3::GetCurrentPadding failed" << audioClientErrorString(hr);
344 const uint32_t requiredFrames = m_audioClientFrames - numFramesPadding;
345 if (requiredFrames == 0)
349 unsigned char *hostBuffer{};
350 hr = m_renderClient->GetBuffer(requiredFrames, &hostBuffer);
352 qWarning() <<
"IAudioRenderClient::getBuffer failed" << audioClientErrorString(hr);
356 QSpan<std::byte> hostBufferSpan{
357 reinterpret_cast<std::byte *>(hostBuffer),
358 m_hostFormat.bytesForFrames(requiredFrames),
361 uint64_t consumedFrames;
365 std::pmr::vector<std::byte> resampleBuffer{
366 size_t(m_format.bytesForFrames(requiredFrames)),
367 m_memoryResource.get(),
369 consumedFrames = f(as_writable_bytes(QSpan{ resampleBuffer }), requiredFrames);
371 auto resampledBuffer = m_resampler->resample(resampleBuffer, m_memoryResource.get());
373 Q_ASSERT(resampledBuffer.size() == size_t(hostBufferSpan.size()));
374 std::copy_n(resampledBuffer.data(), resampledBuffer.size(), hostBufferSpan.data());
376 consumedFrames = f(hostBufferSpan, requiredFrames);
379 const DWORD flags = consumedFrames != 0 ? 0 : AUDCLNT_BUFFERFLAGS_SILENT;
381 hr = m_renderClient->ReleaseBuffer(requiredFrames, flags);
383 qWarning() <<
"IAudioRenderClient::ReleaseBuffer failed" << audioClientErrorString(hr);
Combined button and popup list for selecting options.
void updateStreamIdle(bool) override
QWASAPIAudioSinkStream(QAudioDevice, const QAudioFormat &, std::optional< qsizetype > ringbufferSize, QWindowsAudioSink *parent, float volume, std::optional< int32_t > hardwareBufferSize, AudioEndpointRole)
QAudioFormat::SampleFormat SampleFormat
bool start(AudioCallback)
void stop(ShutdownPolicy)