File indexing completed on 2024-09-08 05:01:28
0001 /* 0002 SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "volumemonitor.h" 0008 0009 #include <pulse/pulseaudio.h> 0010 0011 #include "context.h" 0012 #include "debug.h" 0013 #include "sink.h" 0014 #include "sinkinput.h" 0015 #include "source.h" 0016 #include "sourceoutput.h" 0017 #include "volumeobject.h" 0018 0019 #include <QtGlobal> 0020 0021 using namespace QPulseAudio; 0022 0023 VolumeMonitor::VolumeMonitor(QObject *parent) 0024 : QObject(parent) 0025 { 0026 Context::instance()->ref(); 0027 } 0028 0029 VolumeMonitor::~VolumeMonitor() 0030 { 0031 setTarget(nullptr); 0032 Context::instance()->unref(); 0033 } 0034 0035 bool VolumeMonitor::isAvailable() const 0036 { 0037 return m_stream != nullptr; 0038 } 0039 0040 void VolumeMonitor::updateVolume(qreal volume) 0041 { 0042 // qFuzzyCompare cannot compare against 0. 0043 if (qFuzzyCompare(1 + m_volume, 1 + volume)) { 0044 return; 0045 } 0046 0047 m_volume = volume; 0048 Q_EMIT volumeChanged(); 0049 } 0050 0051 QPulseAudio::VolumeObject *QPulseAudio::VolumeMonitor::target() const 0052 { 0053 return m_target; 0054 } 0055 0056 void QPulseAudio::VolumeMonitor::setTarget(QPulseAudio::VolumeObject *target) 0057 { 0058 if (target == m_target) { 0059 return; 0060 } 0061 0062 if (m_stream) { 0063 pa_stream_set_read_callback(m_stream, nullptr, nullptr); 0064 pa_stream_set_suspended_callback(m_stream, nullptr, nullptr); 0065 if (pa_stream_get_state(m_stream) == PA_STREAM_CREATING) { 0066 pa_stream_set_state_callback( 0067 m_stream, 0068 [](pa_stream *s, void *) { 0069 pa_stream_disconnect(s); 0070 pa_stream_set_state_callback(s, nullptr, nullptr); 0071 }, 0072 nullptr); 0073 } else { 0074 pa_stream_disconnect(m_stream); 0075 } 0076 pa_stream_unref(m_stream); 0077 m_stream = nullptr; 0078 Q_EMIT availableChanged(); 0079 } 0080 0081 m_target = target; 0082 0083 if (target) { 0084 connect(target, &QObject::destroyed, this, [this] { 0085 setTarget(nullptr); 0086 }); 0087 createStream(); 0088 } 0089 0090 Q_EMIT targetChanged(); 0091 } 0092 0093 void VolumeMonitor::createStream() 0094 { 0095 Q_ASSERT(!m_stream); 0096 0097 uint32_t sourceIdx = PA_INVALID_INDEX; 0098 uint32_t streamIdx = PA_INVALID_INDEX; 0099 0100 if (auto *sinkInput = qobject_cast<SinkInput *>(m_target)) { 0101 Sink *sink = Context::instance()->sinks().data().value(sinkInput->deviceIndex()); 0102 if (sink) { 0103 sourceIdx = sink->monitorIndex(); 0104 } 0105 streamIdx = sinkInput->index(); 0106 } else if (auto *sourceOutput = qobject_cast<SourceOutput *>(m_target)) { 0107 sourceIdx = sourceOutput->deviceIndex(); 0108 } else if (auto *sink = qobject_cast<Sink *>(m_target)) { 0109 sourceIdx = sink->monitorIndex(); 0110 } else if (auto *source = qobject_cast<Source *>(m_target)) { 0111 sourceIdx = source->index(); 0112 } else { 0113 Q_UNREACHABLE(); 0114 } 0115 0116 if (sourceIdx == PA_INVALID_INDEX) { 0117 return; 0118 } 0119 0120 char t[16]; 0121 pa_buffer_attr attr; 0122 pa_sample_spec ss; 0123 pa_stream_flags_t flags; 0124 0125 ss.channels = 1; 0126 ss.format = PA_SAMPLE_FLOAT32; 0127 ss.rate = 25; 0128 0129 memset(&attr, 0, sizeof(attr)); 0130 attr.fragsize = sizeof(float); 0131 attr.maxlength = (uint32_t)-1; 0132 0133 snprintf(t, sizeof(t), "%u", sourceIdx); 0134 0135 if (!(m_stream = pa_stream_new(Context::instance()->context(), "PlasmaPA-VolumeMeter", &ss, nullptr))) { 0136 qCWarning(PLASMAPA) << "Failed to create stream"; 0137 return; 0138 } 0139 0140 if (streamIdx != PA_INVALID_INDEX) { 0141 pa_stream_set_monitor_stream(m_stream, streamIdx); 0142 } 0143 0144 pa_stream_set_read_callback(m_stream, read_callback, this); 0145 pa_stream_set_suspended_callback(m_stream, suspended_callback, this); 0146 0147 flags = (pa_stream_flags_t)(PA_STREAM_DONT_MOVE | PA_STREAM_PEAK_DETECT | PA_STREAM_ADJUST_LATENCY); 0148 0149 if (pa_stream_connect_record(m_stream, t, &attr, flags) < 0) { 0150 pa_stream_unref(m_stream); 0151 m_stream = nullptr; 0152 return; 0153 } 0154 Q_EMIT availableChanged(); 0155 } 0156 0157 void VolumeMonitor::suspended_callback(pa_stream *s, void *userdata) 0158 { 0159 auto *w = static_cast<VolumeMonitor *>(userdata); 0160 if (pa_stream_is_suspended(s)) { 0161 w->updateVolume(-1); 0162 } 0163 } 0164 0165 void VolumeMonitor::read_callback(pa_stream *s, size_t length, void *userdata) 0166 { 0167 auto *w = static_cast<VolumeMonitor *>(userdata); 0168 const void *data; 0169 double volume; 0170 0171 if (pa_stream_peek(s, &data, &length) < 0) { 0172 qCWarning(PLASMAPA) << "Failed to read data from stream"; 0173 return; 0174 } 0175 0176 if (!data) { 0177 /* nullptr data means either a hole or empty buffer. 0178 * Only drop the stream when there is a hole (length > 0) */ 0179 if (length) { 0180 pa_stream_drop(s); 0181 } 0182 return; 0183 } 0184 0185 Q_ASSERT(length > 0); 0186 Q_ASSERT(length % sizeof(float) == 0); 0187 0188 volume = ((const float *)data)[length / sizeof(float) - 1]; 0189 0190 pa_stream_drop(s); 0191 0192 volume = qBound(0.0, volume, 1.0); 0193 w->updateVolume(volume); 0194 }