Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qpulsehelpers_p.h"
++
++ QT_BEGIN_NAMESPACE
++
++ Q_LOGGING_CATEGORY(qLcPulseAudioOut, "qt.multimedia.pulseaudio.output")
++ Q_LOGGING_CATEGORY(qLcPulseAudioIn, "qt.multimedia.pulseaudio.input")
++ Q_LOGGING_CATEGORY(qLcPulseAudioEngine, "qt.multimedia.pulseaudio.engine")
++
++ namespace QPulseAudioInternal
++ {
++ pa_sample_spec audioFormatToSampleSpec(const QAudioFormat &format)
++ {
++ pa_sample_spec spec;
++
++ spec.rate = format.sampleRate();
++ spec.channels = format.channelCount();
++ spec.format = PA_SAMPLE_INVALID;
++ const bool isBigEndian = QSysInfo::ByteOrder == QSysInfo::BigEndian;
++
++ if (format.sampleFormat() == QAudioFormat::UInt8) {
++ spec.format = PA_SAMPLE_U8;
++ } else if (format.sampleFormat() == QAudioFormat::Int16) {
++ spec.format = isBigEndian ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE;
++ } else if (format.sampleFormat() == QAudioFormat::Int32) {
++ spec.format = isBigEndian ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE;
++ } else if (format.sampleFormat() == QAudioFormat::Float) {
++ spec.format = isBigEndian ? PA_SAMPLE_FLOAT32BE : PA_SAMPLE_FLOAT32LE;
++ }
++
++ return spec;
++ }
++
++ pa_channel_map channelMapForAudioFormat(const QAudioFormat &format)
++ {
++ pa_channel_map map{};
++ map.channels = 0;
++
++ auto config = format.channelConfig();
++ if (config == QAudioFormat::ChannelConfigUnknown)
++ config = QAudioFormat::defaultChannelConfigForChannelCount(format.channelCount());
++
++ if (config == QAudioFormat::ChannelConfigMono) {
++ map.channels = 1;
++ map.map[0] = PA_CHANNEL_POSITION_MONO;
++ } else {
++ if (config & QAudioFormat::channelConfig(QAudioFormat::FrontLeft))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_LEFT;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::FrontRight))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_RIGHT;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::FrontCenter))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_CENTER;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::LFE))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_LFE;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::BackLeft))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_LEFT;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::BackRight))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_RIGHT;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::FrontLeftOfCenter))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::FrontRightOfCenter))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::BackCenter))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_REAR_CENTER;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::LFE2))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_LFE;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::SideLeft))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_SIDE_LEFT;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::SideRight))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_SIDE_RIGHT;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::TopFrontLeft))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_FRONT_LEFT;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::TopFrontRight))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::TopFrontCenter))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_FRONT_CENTER;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::TopCenter))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_CENTER;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::TopBackLeft))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_REAR_LEFT;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::TopBackRight))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::TopSideLeft))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_AUX0;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::TopSideRight))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_AUX1;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::TopBackCenter))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_TOP_REAR_CENTER;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::BottomFrontCenter))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_AUX2;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::BottomFrontLeft))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_AUX3;
++ if (config & QAudioFormat::channelConfig(QAudioFormat::BottomFrontRight))
++ map.map[map.channels++] = PA_CHANNEL_POSITION_AUX4;
++ }
++
++ Q_ASSERT(qPopulationCount(config) == map.channels);
++ return map;
++ }
++
++ QAudioFormat::ChannelConfig channelConfigFromMap(const pa_channel_map &map)
++ {
++ quint32 config = 0;
++ for (int i = 0; i < map.channels; ++i) {
++ switch (map.map[i]) {
++ case PA_CHANNEL_POSITION_MONO:
++ case PA_CHANNEL_POSITION_FRONT_CENTER:
++ config |= QAudioFormat::channelConfig(QAudioFormat::FrontCenter);
++ break;
++ case PA_CHANNEL_POSITION_FRONT_LEFT:
++ config |= QAudioFormat::channelConfig(QAudioFormat::FrontLeft);
++ break;
++ case PA_CHANNEL_POSITION_FRONT_RIGHT:
++ config |= QAudioFormat::channelConfig(QAudioFormat::FrontRight);
++ break;
++ case PA_CHANNEL_POSITION_REAR_CENTER:
++ config |= QAudioFormat::channelConfig(QAudioFormat::BackCenter);
++ break;
++ case PA_CHANNEL_POSITION_REAR_LEFT:
++ config |= QAudioFormat::channelConfig(QAudioFormat::BackLeft);
++ break;
++ case PA_CHANNEL_POSITION_REAR_RIGHT:
++ config |= QAudioFormat::channelConfig(QAudioFormat::BackRight);
++ break;
++ case PA_CHANNEL_POSITION_LFE:
++ config |= QAudioFormat::channelConfig(QAudioFormat::LFE);
++ break;
++ case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER:
++ config |= QAudioFormat::channelConfig(QAudioFormat::FrontLeftOfCenter);
++ break;
++ case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER:
++ config |= QAudioFormat::channelConfig(QAudioFormat::FrontRightOfCenter);
++ break;
++ case PA_CHANNEL_POSITION_SIDE_LEFT:
++ config |= QAudioFormat::channelConfig(QAudioFormat::SideLeft);
++ break;
++ case PA_CHANNEL_POSITION_SIDE_RIGHT:
++ config |= QAudioFormat::channelConfig(QAudioFormat::SideRight);
++ break;
++
++ case PA_CHANNEL_POSITION_TOP_CENTER:
++ config |= QAudioFormat::channelConfig(QAudioFormat::TopCenter);
++ break;
++ case PA_CHANNEL_POSITION_TOP_FRONT_LEFT:
++ config |= QAudioFormat::channelConfig(QAudioFormat::TopFrontLeft);
++ break;
++ case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT:
++ config |= QAudioFormat::channelConfig(QAudioFormat::TopFrontRight);
++ break;
++ case PA_CHANNEL_POSITION_TOP_FRONT_CENTER:
++ config |= QAudioFormat::channelConfig(QAudioFormat::TopFrontCenter);
++ break;
++ case PA_CHANNEL_POSITION_TOP_REAR_LEFT:
++ config |= QAudioFormat::channelConfig(QAudioFormat::TopBackLeft);
++ break;
++ case PA_CHANNEL_POSITION_TOP_REAR_RIGHT:
++ config |= QAudioFormat::channelConfig(QAudioFormat::TopBackRight);
++ break;
++ case PA_CHANNEL_POSITION_TOP_REAR_CENTER:
++ config |= QAudioFormat::channelConfig(QAudioFormat::TopBackCenter);
++ break;
++ default:
++ break;
++ }
++ }
++ return QAudioFormat::ChannelConfig(config);
++ }
++
++ QAudioFormat sampleSpecToAudioFormat(const pa_sample_spec &spec)
++ {
++ QAudioFormat format;
++
++ format.setSampleRate(spec.rate);
++ format.setChannelCount(spec.channels);
++ QAudioFormat::SampleFormat sampleFormat;
++ switch (spec.format) {
++ case PA_SAMPLE_U8:
++ sampleFormat = QAudioFormat::UInt8;
++ break;
++ case PA_SAMPLE_S16LE:
++ case PA_SAMPLE_S16BE:
++ sampleFormat = QAudioFormat::Int16;
++ break;
++ case PA_SAMPLE_FLOAT32LE:
++ case PA_SAMPLE_FLOAT32BE:
++ sampleFormat = QAudioFormat::Float;
++ break;
++ case PA_SAMPLE_S32LE:
++ case PA_SAMPLE_S32BE:
++ sampleFormat = QAudioFormat::Int32;
++ break;
++ default:
++ return {};
++ }
++
++ format.setSampleFormat(sampleFormat);
++ return format;
++ }
++
++ QUtf8StringView currentError(const pa_context *context)
++ {
++ return pa_strerror(pa_context_errno(context));
++ }
++
++ QUtf8StringView currentError(const pa_stream *stream)
++ {
++ return currentError(pa_stream_get_context(stream));
++ }
++
++ PAOperationHandle streamCork(const PAStreamHandle &stream, bool corkStream)
++ {
++ return PAOperationHandle{
++ pa_stream_cork(stream.get(), corkStream ? 1 : 0, nullptr, nullptr),
++ PAOperationHandle::HasRef,
++ };
++ }
++
++ PulseaudioServerType pulseaudioDetectServerType()
++ {
++ using namespace Qt::StringLiterals;
++
++ auto ml = std::unique_ptr<pa_mainloop, PaMainLoopDeleter>(pa_mainloop_new());
++ pa_mainloop_api *api = pa_mainloop_get_api(ml.get());
++ PAContextHandle ctx{
++ pa_context_new(api, "pipewire-check"),
++ PAContextHandle::HasRef,
++ };
++
++ struct ContextData
++ {
++ bool ready = false;
++ bool is_pipewire = false;
++ };
++ ContextData data;
++
++ pa_context_set_state_callback(ctx.get(), [](pa_context *ctx, void *userdata) {
++ auto *data = static_cast<ContextData *>(userdata);
++ switch (pa_context_get_state(ctx)) {
++ case PA_CONTEXT_READY:
++ case PA_CONTEXT_FAILED:
++ case PA_CONTEXT_TERMINATED:
++ data->ready = true;
++ break;
++ default:
++ break;
++ }
++ }, &data);
++ pa_context_connect(ctx.get(), nullptr, PA_CONTEXT_NOAUTOSPAWN, nullptr);
++ auto disconnect = qScopeGuard([&] {
++ pa_context_disconnect(ctx.get());
++ });
++
++ while (!data.ready)
++ pa_mainloop_iterate(ml.get(), 1, nullptr);
++
++ if (pa_context_get_state(ctx.get()) != PA_CONTEXT_READY)
++ return PulseaudioServerType::NotConnected;
++
++ PAOperationHandle op{
++ pa_context_get_server_info(ctx.get(),
++ [](pa_context *, const pa_server_info *info, void *userdata) {
++ auto *data = static_cast<ContextData *>(userdata);
++ if (info && info->server_name)
++ data->is_pipewire = QLatin1StringView(info->server_name).contains("PipeWire"_L1);
++ }, &data),
++ PAOperationHandle::HasRef,
++ };
++
++ while (pa_operation_get_state(op.get()) == PA_OPERATION_RUNNING)
++ pa_mainloop_iterate(ml.get(), 1, nullptr);
++
++ return data.is_pipewire ? PulseaudioServerType::Pipewire : PulseaudioServerType::Pulseaudio;
++ }
++
++ } // namespace QPulseAudioInternal
++
++ static QLatin1StringView stateToQStringView(pa_stream_state_t state)
++ {
++ using namespace Qt::StringLiterals;
++ switch (state)
++ {
++ case PA_STREAM_UNCONNECTED: return "Unconnected"_L1;
++ case PA_STREAM_CREATING: return "Creating"_L1;
++ case PA_STREAM_READY: return "Ready"_L1;
++ case PA_STREAM_FAILED: return "Failed"_L1;
++ case PA_STREAM_TERMINATED: return "Terminated"_L1;
++ default: Q_UNREACHABLE_RETURN("Unknown stream state"_L1);
++ }
++ }
++
++ static QLatin1StringView sampleFormatToQStringView(pa_sample_format format)
++ {
++ using namespace Qt::StringLiterals;
++ switch (format)
++ {
++ case PA_SAMPLE_U8: return "Unsigned 8 Bit PCM."_L1;
++ case PA_SAMPLE_ALAW: return "8 Bit a-Law "_L1;
++ case PA_SAMPLE_ULAW: return "8 Bit mu-Law"_L1;
++ case PA_SAMPLE_S16LE: return "Signed 16 Bit PCM, little endian (PC)."_L1;
++ case PA_SAMPLE_S16BE: return "Signed 16 Bit PCM, big endian."_L1;
++ case PA_SAMPLE_FLOAT32LE: return "32 Bit IEEE floating point, little endian (PC), range -1.0 to 1.0"_L1;
++ case PA_SAMPLE_FLOAT32BE: return "32 Bit IEEE floating point, big endian, range -1.0 to 1.0"_L1;
++ case PA_SAMPLE_S32LE: return "Signed 32 Bit PCM, little endian (PC)."_L1;
++ case PA_SAMPLE_S32BE: return "Signed 32 Bit PCM, big endian."_L1;
++ case PA_SAMPLE_S24LE: return "Signed 24 Bit PCM packed, little endian (PC)."_L1;
++ case PA_SAMPLE_S24BE: return "Signed 24 Bit PCM packed, big endian."_L1;
++ case PA_SAMPLE_S24_32LE: return "Signed 24 Bit PCM in LSB of 32 Bit words, little endian (PC)."_L1;
++ case PA_SAMPLE_S24_32BE: return "Signed 24 Bit PCM in LSB of 32 Bit words, big endian."_L1;
++ case PA_SAMPLE_MAX: return "Upper limit of valid sample types."_L1;
++ case PA_SAMPLE_INVALID: return "Invalid sample format"_L1;
++ default: Q_UNREACHABLE_RETURN("Unknown sample format"_L1);
++ }
++ }
++
++ static QLatin1StringView stateToQStringView(pa_context_state_t state)
++ {
++ using namespace Qt::StringLiterals;
++ switch (state)
++ {
++ case PA_CONTEXT_UNCONNECTED: return "Unconnected"_L1;
++ case PA_CONTEXT_CONNECTING: return "Connecting"_L1;
++ case PA_CONTEXT_AUTHORIZING: return "Authorizing"_L1;
++ case PA_CONTEXT_SETTING_NAME: return "Setting Name"_L1;
++ case PA_CONTEXT_READY: return "Ready"_L1;
++ case PA_CONTEXT_FAILED: return "Failed"_L1;
++ case PA_CONTEXT_TERMINATED: return "Terminated"_L1;
++ default: Q_UNREACHABLE_RETURN("Unknown context state"_L1);
++ }
++ }
++
++
++ QDebug operator<<(QDebug dbg, pa_stream_state_t state)
++ {
++ return dbg << stateToQStringView(state);
++ }
++
++ QDebug operator<<(QDebug dbg, pa_sample_format format)
++ {
++ return dbg << sampleFormatToQStringView(format);
++ }
++
++ QDebug operator<<(QDebug dbg, pa_context_state_t state)
++ {
++ return dbg << stateToQStringView(state);
++ }
++
++ QT_END_NAMESPACE