summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Blechmann <[email protected]>2025-05-27 09:44:03 +0800
committerTim Blechmann <[email protected]>2025-07-03 14:44:20 +0800
commit0658aced75ec92ff4e798dee4b45b17493afc0c5 (patch)
treec1bb5c9976a853e5fd6a445b47209ef9af0f95a4
parentfa4e7b29a01a7cc9c6a058c5de56c4f8819f8b24 (diff)
Audio: prepare callback-based audio sourcesHEADdev
Pick-to: 6.8 6.9 6.10 Change-Id: Ib0e806f88e1266b91308f5b0fc510fbf9a1b6671 Reviewed-by: Timur Pocheptsov <[email protected]>
-rw-r--r--src/multimedia/audio/qaudio_platform_implementation_support_p.h2
-rw-r--r--src/multimedia/audio/qaudioformat.cpp19
-rw-r--r--src/multimedia/audio/qaudiosystem_p.h71
-rw-r--r--src/multimedia/darwin/qdarwinaudiosink.mm15
-rw-r--r--src/multimedia/pipewire/qpipewire_audiosink.cpp2
-rw-r--r--src/multimedia/pulseaudio/qpulseaudiosink.cpp4
-rw-r--r--src/multimedia/windows/qwindowsaudiosink.cpp10
-rw-r--r--tests/auto/unit/multimedia/qaudiohelpers/tst_qaudiohelpers.cpp35
8 files changed, 124 insertions, 34 deletions
diff --git a/src/multimedia/audio/qaudio_platform_implementation_support_p.h b/src/multimedia/audio/qaudio_platform_implementation_support_p.h
index 4f140ff6a..fde09c02e 100644
--- a/src/multimedia/audio/qaudio_platform_implementation_support_p.h
+++ b/src/multimedia/audio/qaudio_platform_implementation_support_p.h
@@ -154,7 +154,7 @@ template <STREAM_TYPE_ARG, typename DerivedType>
void QPlatformAudioSinkImplementation<StreamType, DerivedType>::start(AudioCallback &&audioCallback)
{
using namespace QtMultimediaPrivate;
- if (!validateAudioSinkCallback(audioCallback, m_format)) {
+ if (!validateAudioCallback(audioCallback, m_format)) {
setError(QAudio::OpenError);
return;
}
diff --git a/src/multimedia/audio/qaudioformat.cpp b/src/multimedia/audio/qaudioformat.cpp
index 2483d490f..d4dd0d4e7 100644
--- a/src/multimedia/audio/qaudioformat.cpp
+++ b/src/multimedia/audio/qaudioformat.cpp
@@ -309,9 +309,24 @@ qint32 QAudioFormat::bytesForFrames(qint32 frameCount) const
qint32 QAudioFormat::framesForBytes(qint32 byteCount) const
{
int size = bytesPerFrame();
- if (size > 0)
+
+ // bitshifting to avoid integer division
+ switch (size) {
+ case 0:
+ return 0;
+ case 1:
+ return byteCount;
+ case 2:
+ return byteCount / 2;
+ case 4:
+ return byteCount / 4;
+ case 8:
+ return byteCount / 8;
+ case 16:
+ return byteCount / 16;
+ default:
return byteCount / size;
- return 0;
+ }
}
/*!
diff --git a/src/multimedia/audio/qaudiosystem_p.h b/src/multimedia/audio/qaudiosystem_p.h
index bdf436256..de338e786 100644
--- a/src/multimedia/audio/qaudiosystem_p.h
+++ b/src/multimedia/audio/qaudiosystem_p.h
@@ -20,6 +20,7 @@
#include <QtMultimedia/qaudio.h>
#include <QtMultimedia/qaudiodevice.h>
#include <QtMultimedia/qaudioformat.h>
+#include <QtMultimedia/private/qmultimedia_assume_p.h>
#include <QtCore/qelapsedtimer.h>
#include <QtCore/qspan.h>
@@ -50,6 +51,9 @@ enum class AudioEndpointRole : uint8_t
template <typename SampleType>
using AudioSinkCallbackType = std::function<void(QSpan<SampleType>)>;
+template <typename SampleType>
+using AudioSourceCallbackType = std::function<void(QSpan<const SampleType>)>;
+
template <typename>
struct GetSampleTypeImpl;
@@ -85,6 +89,11 @@ struct GetSampleTypeImpl<AudioSinkCallbackType<T>> : GetSampleTypeImpl<T>
{
};
+template <typename T>
+struct GetSampleTypeImpl<AudioSourceCallbackType<T>> : GetSampleTypeImpl<T>
+{
+};
+
template <typename SampleTypeOrCallbackType>
using GetSampleType = typename GetSampleTypeImpl<SampleTypeOrCallbackType>::type;
@@ -97,8 +106,12 @@ static constexpr QAudioFormat::SampleFormat getSampleFormat()
using AudioSinkCallback =
std::variant<AudioSinkCallbackType<float>, AudioSinkCallbackType<uint8_t>,
AudioSinkCallbackType<int16_t>, AudioSinkCallbackType<int32_t>>;
+using AudioSourceCallback =
+ std::variant<AudioSourceCallbackType<float>, AudioSourceCallbackType<uint8_t>,
+ AudioSourceCallbackType<int16_t>, AudioSourceCallbackType<int32_t>>;
-constexpr bool validateAudioSinkCallback(const AudioSinkCallback &audioCallback,
+template <typename AnyAudioCallback>
+constexpr bool validateAudioCallbackImpl(const AnyAudioCallback &audioCallback,
const QAudioFormat &format)
{
bool isNonNullFunction = std::visit([](const auto &cb) {
@@ -115,37 +128,61 @@ constexpr bool validateAudioSinkCallback(const AudioSinkCallback &audioCallback,
return hasCorrectFormat;
}
-inline void runAudioSinkCallback(const AudioSinkCallback &audioCallback, std::byte *hostBuffer,
- qsizetype numberOfSamples, const QAudioFormat &format)
+constexpr bool validateAudioCallback(const AudioSinkCallback &audioCallback,
+ const QAudioFormat &format)
+{
+ return validateAudioCallbackImpl(audioCallback, format);
+}
+
+constexpr bool validateAudioCallback(const AudioSourceCallback &audioCallback,
+ const QAudioFormat &format)
+{
+ return validateAudioCallbackImpl(audioCallback, format);
+}
+
+template <bool IsSink>
+inline void runAudioCallback(
+ const std::conditional_t<IsSink, AudioSinkCallback, AudioSourceCallback> &audioCallback,
+ QSpan<std::conditional_t<IsSink, std::byte, const std::byte>> hostBuffer,
+ const QAudioFormat &format)
{
- Q_ASSERT(numberOfSamples > 0);
+ Q_ASSERT(!hostBuffer.empty());
+
+ bool callbackIsValid = validateAudioCallback(audioCallback, format);
+ QT_MM_ASSUME(callbackIsValid);
- bool callbackIsValid = validateAudioSinkCallback(audioCallback, format);
- Q_ASSERT(callbackIsValid);
-#if __has_cpp_attribute(assume)
- [[assume(callbackIsValid)]];
-#endif
+ int numberOfSamples = format.framesForBytes(hostBuffer.size()) * format.channelCount();
std::visit([&](const auto &callback) {
using FunctorType = std::decay_t<decltype(callback)>;
- constexpr QAudioFormat::SampleFormat functorSampleFormat = getSampleFormat<FunctorType>();
- Q_ASSERT(functorSampleFormat == format.sampleFormat());
+ Q_ASSERT(getSampleFormat<FunctorType>() == format.sampleFormat());
using SampleType = GetSampleType<FunctorType>;
bool audioCallbackIsValid = bool(callback);
- Q_ASSERT(audioCallbackIsValid);
-#if __has_cpp_attribute(assume)
- [[assume(audioCallbackIsValid)]];
-#endif
- auto buffer = QSpan<SampleType>{
- reinterpret_cast<SampleType *>(hostBuffer),
+ QT_MM_ASSUME(audioCallbackIsValid);
+ using HostBufferType = std::conditional_t<IsSink, SampleType, const SampleType>;
+
+ auto buffer = QSpan<HostBufferType>{
+ reinterpret_cast<HostBufferType *>(hostBuffer.data()),
numberOfSamples,
};
callback(buffer);
}, audioCallback);
}
+inline void runAudioCallback(const AudioSinkCallback &audioCallback, QSpan<std::byte> hostBuffer,
+ const QAudioFormat &format)
+{
+ return runAudioCallback<true>(audioCallback, hostBuffer, format);
+}
+
+inline void runAudioCallback(const AudioSourceCallback &audioCallback,
+ QSpan<const std::byte> hostBuffer, const QAudioFormat &format)
+{
+ return runAudioCallback<false>(audioCallback, hostBuffer, format);
+}
+
///////////////////////////////////////////////////////////////////////////////////////////////////
} // namespace QtMultimediaPrivate
diff --git a/src/multimedia/darwin/qdarwinaudiosink.mm b/src/multimedia/darwin/qdarwinaudiosink.mm
index bdd638618..e1449aea6 100644
--- a/src/multimedia/darwin/qdarwinaudiosink.mm
+++ b/src/multimedia/darwin/qdarwinaudiosink.mm
@@ -241,7 +241,7 @@ void QCoreAudioSinkStream::resumeIfNecessary()
OSStatus QCoreAudioSinkStream::processRingbuffer(uint32_t numberOfFrames,
AudioBufferList *ioData) noexcept QT_MM_NONBLOCKING
{
- Q_ASSERT(ioData->mBuffers[0].mDataByteSize == numberOfFrames * m_format.bytesPerFrame());
+ Q_ASSERT(int64_t(ioData->mBuffers[0].mDataByteSize) == m_format.bytesForFrames(numberOfFrames));
QSpan audioBufferSpan{
reinterpret_cast<std::byte *>(ioData->mBuffers[0].mData),
@@ -257,12 +257,15 @@ OSStatus QCoreAudioSinkStream::processAudioCallback(uint32_t numberOfFrames,
AudioBufferList *ioData) noexcept QT_MM_NONBLOCKING
{
using namespace QtMultimediaPrivate;
- const uint32_t bytesPerFrame = m_format.bytesPerFrame();
- const uint32_t numberOfSamples = numberOfFrames * m_format.channelCount();
- Q_ASSERT(ioData->mBuffers[0].mDataByteSize == numberOfFrames * bytesPerFrame);
- runAudioSinkCallback(m_audioCallback, reinterpret_cast<std::byte *>(ioData->mBuffers[0].mData),
- numberOfSamples, m_format);
+ Q_ASSERT(int64_t(ioData->mBuffers[0].mDataByteSize) == m_format.bytesForFrames(numberOfFrames));
+
+ QSpan<std::byte> inputSpan{
+ reinterpret_cast<std::byte *>(ioData->mBuffers[0].mData),
+ ioData->mBuffers[0].mDataByteSize,
+ };
+
+ runAudioCallback(m_audioCallback, inputSpan, m_format);
return noErr;
}
diff --git a/src/multimedia/pipewire/qpipewire_audiosink.cpp b/src/multimedia/pipewire/qpipewire_audiosink.cpp
index 02364d972..a7924728c 100644
--- a/src/multimedia/pipewire/qpipewire_audiosink.cpp
+++ b/src/multimedia/pipewire/qpipewire_audiosink.cpp
@@ -309,7 +309,7 @@ void QPipewireAudioSinkStream::processCallback() noexcept QT_MM_NONBLOCKING
auto [writeBuffer, requestedSamples, totalNumberOfFrames] = resolveHostBuffer(b, m_format);
- runAudioSinkCallback(*m_audioCallback, writeBuffer.data(), requestedSamples, m_format);
+ runAudioCallback(*m_audioCallback, writeBuffer, m_format);
queueBuffer(b, requestedSamples);
}
diff --git a/src/multimedia/pulseaudio/qpulseaudiosink.cpp b/src/multimedia/pulseaudio/qpulseaudiosink.cpp
index 83ce1ec7b..77c88bab0 100644
--- a/src/multimedia/pulseaudio/qpulseaudiosink.cpp
+++ b/src/multimedia/pulseaudio/qpulseaudiosink.cpp
@@ -335,9 +335,7 @@ void QPulseAudioSinkStream::writeCallbackAudioCallback(size_t requestedBytes)
});
}
QSpan<std::byte> hostBuffer{ reinterpret_cast<std::byte *>(dest), qsizetype(nbytes) };
-
- runAudioSinkCallback(*m_audioCallback, hostBuffer.data(),
- requestedFrames * m_format.channelCount(), m_format);
+ runAudioCallback(*m_audioCallback, hostBuffer, m_format);
status = pa_stream_write(m_stream.get(), hostBuffer.data(), nbytes,
/*free_cb= */ nullptr, /*offset=*/0, PA_SEEK_RELATIVE);
diff --git a/src/multimedia/windows/qwindowsaudiosink.cpp b/src/multimedia/windows/qwindowsaudiosink.cpp
index ca9530e52..076c24b1d 100644
--- a/src/multimedia/windows/qwindowsaudiosink.cpp
+++ b/src/multimedia/windows/qwindowsaudiosink.cpp
@@ -314,8 +314,6 @@ bool QWASAPIAudioSinkStream::processCallback() noexcept QT_MM_NONBLOCKING
if (requiredFrames == 0)
return true;
- uint32_t requiredSamples = requiredFrames * m_format.channelCount();
-
// Grab the next empty buffer from the audio device.
unsigned char *hostBuffer{};
hr = m_renderClient->GetBuffer(requiredFrames, &hostBuffer);
@@ -324,8 +322,12 @@ bool QWASAPIAudioSinkStream::processCallback() noexcept QT_MM_NONBLOCKING
return false;
}
- runAudioSinkCallback(m_audioCallback, reinterpret_cast<std::byte *>(hostBuffer),
- requiredSamples, m_format);
+ QSpan<std::byte> hostBufferSpan{
+ reinterpret_cast<std::byte *>(hostBuffer),
+ m_format.bytesForFrames(requiredFrames),
+ };
+
+ runAudioCallback(m_audioCallback, hostBufferSpan, m_format);
constexpr DWORD flags = 0;
hr = m_renderClient->ReleaseBuffer(requiredFrames, flags);
diff --git a/tests/auto/unit/multimedia/qaudiohelpers/tst_qaudiohelpers.cpp b/tests/auto/unit/multimedia/qaudiohelpers/tst_qaudiohelpers.cpp
index 1d93528a8..d60643d21 100644
--- a/tests/auto/unit/multimedia/qaudiohelpers/tst_qaudiohelpers.cpp
+++ b/tests/auto/unit/multimedia/qaudiohelpers/tst_qaudiohelpers.cpp
@@ -5,6 +5,7 @@
#include <QtTest/QtTest>
#include <QtMultimedia/private/qaudiohelpers_p.h>
+#include <QtMultimedia/private/qaudiosystem_p.h>
#include <QtMultimedia/private/qaudio_alignment_support_p.h>
#include <QtMultimedia/private/qaudio_qspan_support_p.h>
@@ -29,6 +30,8 @@ private slots:
void wordConverter_checkLoopUnroll();
void findBestNativeSampleFormat();
+
+ void validateAudioCallbacks();
};
namespace WordConverter {
@@ -387,6 +390,38 @@ void tst_QAudioHelpers::findBestNativeSampleFormat()
QCOMPARE_EQ(bestNativeSampleFormat(fmtFloat, telephoneFormats), NativeSampleFormat::uint8_t);
}
+static constexpr QAudioFormat fmtFloat = [] {
+ QAudioFormat fmt;
+ fmt.setSampleFormat(QAudioFormat::Float);
+ fmt.setSampleRate(44100);
+ return fmt;
+}();
+
+void tst_QAudioHelpers::validateAudioCallbacks()
+{
+ using namespace QtMultimediaPrivate;
+
+ std::function<void(QSpan<float>)> nullFunction;
+ QVERIFY(!validateAudioCallback(nullFunction, fmtFloat));
+
+ std::function<void(QSpan<float>)> trivialFunction = [](QSpan<float>) {
+ };
+ QVERIFY(validateAudioCallback(trivialFunction, fmtFloat));
+
+ QVERIFY(validateAudioCallback([](QSpan<float>) {
+ }, fmtFloat));
+ QVERIFY(!validateAudioCallback([](QSpan<int16_t>) {
+ }, fmtFloat));
+
+ static_assert(getSampleFormat<std::function<void(QSpan<float>)>>()
+ == QAudioFormat::SampleFormat::Float);
+ static_assert(getSampleFormat<std::function<void(QSpan<int16_t>)>>()
+ == QAudioFormat::SampleFormat::Int16);
+
+ static_assert(getSampleFormat<float>() == QAudioFormat::SampleFormat::Float);
+ static_assert(getSampleFormat<int16_t>() == QAudioFormat::SampleFormat::Int16);
+}
+
QTEST_APPLESS_MAIN(tst_QAudioHelpers);
#include "tst_qaudiohelpers.moc"