Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qsoundeffect.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtMultimedia/private/qtmultimediaglobal_p.h>
5#include "qsoundeffect.h"
6#include "qsamplecache_p.h"
7#include "qaudiodevice.h"
8#include "qaudiosink.h"
9#include "qmediadevices.h"
10#include "qaudiobuffer.h"
11#include <QtCore/qloggingcategory.h>
12#include <private/qplatformmediadevices_p.h>
13#include <private/qplatformmediaintegration_p.h>
14#include <private/qplatformaudioresampler_p.h>
15
16static Q_LOGGING_CATEGORY(qLcSoundEffect, "qt.multimedia.soundeffect")
17
19
20Q_GLOBAL_STATIC(QSampleCache, sampleCache)
21
22namespace
23{
24struct AudioSinkDeleter
25{
26 void operator ()(QAudioSink* sink) const
27 {
28 sink->stop();
29 // Investigate:should we just delete?
30 sink->deleteLater();
31 }
32};
33
34struct SampleDeleter
35{
36 void operator ()(QSample* sample) const
37 {
38 sample->release();
39 }
40};
41}
42
44{
45public:
47 ~QSoundEffectPrivate() override = default;
48
49 qint64 readData(char *data, qint64 len) override;
50 qint64 writeData(const char *data, qint64 len) override;
51 qint64 size() const override {
52 if (m_sample->state() != QSample::Ready)
53 return 0;
55 }
56 qint64 bytesAvailable() const override {
57 if (m_sample->state() != QSample::Ready)
58 return 0;
60 return std::numeric_limits<qint64>::max();
62 }
63 bool isSequential() const override {
65 }
66 bool atEnd() const override {
67 return m_runningCount == 0;
68 }
69
70 void setLoopsRemaining(int loopsRemaining);
72 void setPlaying(bool playing);
73
74public Q_SLOTS:
75 void sampleReady();
76 void decoderError();
78
79public:
82 int m_loopCount = 1;
84 bool m_playing = false;
86 std::unique_ptr<QAudioSink, AudioSinkDeleter> m_audioSink;
87 std::unique_ptr<QSample, SampleDeleter> m_sample;
89 bool m_muted = false;
90 float m_volume = 1.0;
91 bool m_sampleReady = false;
94};
95
97 : QIODevice(q)
98 , q_ptr(q)
99 , m_audioDevice(audioDevice)
100{
102
103 QPlatformMediaIntegration::instance()->mediaDevices()->prepareAudio();
104}
105
107{
109 return;
110
111 qCDebug(qLcSoundEffect) << this << "sampleReady: sample size:" << m_sample->data().size();
114 if (!m_audioSink) {
115 const auto audioDevice =
117
118 if (audioDevice.isNull()) {
119 // We are likely on a virtual machine, for example in CI
120 qCCritical(qLcSoundEffect) << "Failed to play sound. No audio devices present.";
122 return;
123 }
124
125 const auto &sampleFormat = m_sample->format();
126 const auto sampleChannelConfig =
127 sampleFormat.channelConfig() == QAudioFormat::ChannelConfigUnknown
128 ? QAudioFormat::defaultChannelConfigForChannelCount(sampleFormat.channelCount())
129 : sampleFormat.channelConfig();
130
131 if (sampleChannelConfig != audioDevice.channelConfiguration()
132 && audioDevice.channelConfiguration() != QAudioFormat::ChannelConfigUnknown) {
133 qCDebug(qLcSoundEffect) << "Create resampler for channels mapping: config"
134 << sampleFormat.channelConfig() << "=> config"
135 << audioDevice.channelConfiguration();
136 auto outputFormat = sampleFormat;
137 outputFormat.setChannelConfig(audioDevice.channelConfiguration());
138
139 const auto resampler = QPlatformMediaIntegration::instance()->createAudioResampler(
140 m_sample->format(), outputFormat);
141 if (resampler)
142 m_audioBuffer = resampler.value()->resample(m_sample->data().constData(),
143 m_sample->data().size());
144 else
145 qCDebug(qLcSoundEffect) << "Cannot create resampler for channels mapping";
146 }
147
148 if (!m_audioBuffer.isValid())
149 m_audioBuffer = QAudioBuffer(m_sample->data(), m_sample->format());
150
151 m_audioSink.reset(new QAudioSink(audioDevice, m_audioBuffer.format()));
152
154 if (!m_muted)
155 m_audioSink->setVolume(m_volume);
156 else
157 m_audioSink->setVolume(0);
158 }
159 m_sampleReady = true;
161
162 if (m_playing && m_audioSink->state() == QAudio::StoppedState) {
163 qCDebug(qLcSoundEffect) << this << "starting playback on audiooutput";
164 m_audioSink->start(this);
165 }
166}
167
176
178{
179 qCDebug(qLcSoundEffect) << this << "stateChanged " << state;
181 q_ptr->stop();
182}
183
185{
186 qCDebug(qLcSoundEffect) << this << "readData" << len << m_runningCount;
187 if (!len)
188 return 0;
189 if (m_sample->state() != QSample::Ready)
190 return 0;
191 if (m_runningCount == 0 || !m_playing)
192 return 0;
193
195
196 const int sampleSize = m_audioBuffer.byteCount();
197 const char *sampleData = m_audioBuffer.constData<char>();
198
199 while (len && m_runningCount) {
200 int toWrite = qMin(sampleSize - m_offset, len);
201 memcpy(data, sampleData + m_offset, toWrite);
202 bytesWritten += toWrite;
203 data += toWrite;
204 len -= toWrite;
205 m_offset += toWrite;
206 if (m_offset >= sampleSize) {
209 m_offset = 0;
210 }
211 }
212
213 return bytesWritten;
214}
215
217{
218 Q_UNUSED(data);
219 Q_UNUSED(len);
220 return 0;
221}
222
224{
225 if (m_runningCount == loopsRemaining)
226 return;
227 qCDebug(qLcSoundEffect) << this << "setLoopsRemaining " << loopsRemaining;
228 m_runningCount = loopsRemaining;
230}
231
233{
234 qCDebug(qLcSoundEffect) << this << "setStatus" << status;
235 if (m_status == status)
236 return;
237 bool oldLoaded = q_ptr->isLoaded();
238 m_status = status;
240 if (oldLoaded != q_ptr->isLoaded())
242}
243
245{
246 qCDebug(qLcSoundEffect) << this << "setPlaying(" << playing << ")" << m_playing;
247 if (m_audioSink) {
248 m_audioSink->stop();
249 if (playing && !m_sampleReady)
250 return;
251 }
252
253 if (m_playing == playing)
254 return;
255 m_playing = playing;
256
257 if (m_audioSink && playing)
258 m_audioSink->start(this);
259
261}
262
333
338 : QObject(parent)
339 , d(new QSoundEffectPrivate(this, audioDevice))
340{
341}
342
347{
348 stop();
349 d->m_audioSink.reset();
350 d->m_sample.reset();
351 delete d;
352}
353
360{
361 // Only return supported mime types if we have a audio device available
362 const QList<QAudioDevice> devices = QMediaDevices::audioOutputs();
363 if (devices.isEmpty())
364 return QStringList();
365
366 return QStringList() << QLatin1String("audio/x-wav")
367 << QLatin1String("audio/wav")
368 << QLatin1String("audio/wave")
369 << QLatin1String("audio/x-pn-wav");
370}
371
390{
391 return d->m_url;
392}
393
396{
397 qCDebug(qLcSoundEffect) << this << "setSource current=" << d->m_url << ", to=" << url;
398 if (d->m_url == url)
399 return;
400
401 Q_ASSERT(d->m_url != url);
402
403 stop();
404
405 d->m_url = url;
406
407 d->m_sampleReady = false;
408
409 if (url.isEmpty()) {
411 return;
412 }
413
414 if (!url.isValid()) {
416 return;
417 }
418
419 if (d->m_sample) {
420 if (!d->m_sampleReady) {
423 }
424 d->m_sample->release();
425 d->m_sample = nullptr;
426 }
427
428 if (d->m_audioSink) {
430 d->m_audioSink.reset();
431 }
432
434 d->m_sample.reset(sampleCache()->requestSample(url));
437
438 switch (d->m_sample->state()) {
439 case QSample::Ready:
440 d->sampleReady();
441 break;
442 case QSample::Error:
443 d->decoderError();
444 break;
445 default:
446 break;
447 }
448
450}
451
477{
478 return d->m_loopCount;
479}
480
497void QSoundEffect::setLoopCount(int loopCount)
498{
499 if (loopCount < 0 && loopCount != Infinite) {
500 qWarning("SoundEffect: loops should be SoundEffect.Infinite, 0 or positive integer");
501 return;
502 }
503 if (loopCount == 0)
504 loopCount = 1;
505 if (d->m_loopCount == loopCount)
506 return;
507
509 if (d->m_playing)
512}
513
520{
521 return d->m_audioDevice;
522}
523
525{
526 if (d->m_audioDevice == device)
527 return;
528 // ### recreate the QAudioSink if needed
531}
532
546{
547 return d->m_runningCount;
548}
549
550
576{
577 if (d->m_audioSink && !d->m_muted)
578 return d->m_audioSink->volume();
579
580 return d->m_volume;
581}
582
595void QSoundEffect::setVolume(float volume)
596{
597 volume = qBound(0.0f, volume, 1.0f);
598 if (d->m_volume == volume)
599 return;
600
601 d->m_volume = volume;
602
603 if (d->m_audioSink && !d->m_muted)
604 d->m_audioSink->setVolume(volume);
605
607}
608
623{
624 return d->m_muted;
625}
626
635{
636 if (d->m_muted == muted)
637 return;
638
639 if (muted && d->m_audioSink)
640 d->m_audioSink->setVolume(0);
641 else if (!muted && d->m_audioSink && d->m_muted)
642 d->m_audioSink->setVolume(d->m_volume);
643
644 d->m_muted = muted;
646}
647
659{
660 return d->m_status == QSoundEffect::Ready;
661}
662
680{
681 d->m_offset = 0;
683 qCDebug(qLcSoundEffect) << this << "play" << d->m_loopCount << d->m_runningCount;
686 return;
687 }
688 d->setPlaying(true);
689}
690
704{
705 return d->m_playing;
706}
707
744{
745 return d->m_status;
746}
747
761{
762 if (!d->m_playing)
763 return;
764 qCDebug(qLcSoundEffect) << "stop()";
765 d->m_offset = 0;
766
767 d->setPlaying(false);
768}
769
770/* Signals */
771
860
861#include "moc_qsoundeffect.cpp"
IOBluetoothDevice * device
\inmodule QtMultimedia
bool isValid() const noexcept
Returns true if this is a valid buffer.
qsizetype byteCount() const noexcept
Returns the size of this buffer, in bytes.
const T * constData() const
Returns a pointer to this buffer's data.
QAudioFormat format() const noexcept
Returns the \l {QAudioFormat}{format} of this buffer.
The QAudioDevice class provides an information about audio devices and their functionality.
bool isNull() const
Returns whether this QAudioDevice object holds a valid device definition.
static Q_MULTIMEDIA_EXPORT ChannelConfig defaultChannelConfigForChannelCount(int channelCount)
Returns a default channel configuration for channelCount.
The QAudioSink class provides an interface for sending audio data to an audio output device.
Definition qaudiosink.h:24
void stateChanged(QAudio::State state)
This signal is emitted when the device state has changed.
\inmodule QtCore \reentrant
Definition qiodevice.h:34
virtual bool open(QIODeviceBase::OpenMode mode)
Opens the device and sets its OpenMode to mode.
QList< QAudioDevice > audioOutputs
\qmlproperty list<audioDevice> QtMultimedia::MediaDevices::audioOutputs Contains a list of available ...
QAudioDevice defaultAudioOutput
\qmlproperty audioDevice QtMultimedia::MediaDevices::defaultAudioOutput Returns the default audio out...
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
static QPlatformMediaIntegration * instance()
void ready()
void error()
QSoundEffect * q_ptr
std::unique_ptr< QAudioSink, AudioSinkDeleter > m_audioSink
QAudioBuffer m_audioBuffer
void setLoopsRemaining(int loopsRemaining)
qint64 writeData(const char *data, qint64 len) override
Writes up to maxSize bytes from data to the device.
QSoundEffectPrivate(QSoundEffect *q, const QAudioDevice &audioDevice=QAudioDevice())
~QSoundEffectPrivate() override=default
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...
QSoundEffect::Status m_status
qint64 size() const override
For open random-access devices, this function returns the size of the device.
void stateChanged(QAudio::State)
void setPlaying(bool playing)
bool atEnd() const override
Returns true if the current read and write position is at the end of the device (i....
qint64 bytesAvailable() const override
Returns the number of bytes that are available for reading.
std::unique_ptr< QSample, SampleDeleter > m_sample
QAudioDevice m_audioDevice
void setStatus(QSoundEffect::Status status)
bool isSequential() const override
Returns true if this device is sequential; otherwise returns false.
The QSoundEffect class provides a way to play low latency sound effects.
void setLoopCount(int loopCount)
Set the total number of times to play this sound effect to loopCount.
void sourceChanged()
The sourceChanged signal is emitted when the source has been changed.
int loopCount() const
Returns the total number of times that this sound effect will be played before stopping.
void setVolume(float volume)
Sets the sound effect volume to volume.
void playingChanged()
\qmlsignal QtMultimedia::SoundEffect::mutedChanged()
Status status
\qmlproperty enumeration QtMultimedia::SoundEffect::status
bool isMuted() const
Returns whether this sound effect is muted.
void stop()
\qmlmethod QtMultimedia::SoundEffect::stop()
~QSoundEffect()
Destroys this sound effect.
bool muted
\qmlproperty bool QtMultimedia::SoundEffect::muted
QUrl source
\qmlproperty url QtMultimedia::SoundEffect::source
void audioDeviceChanged()
void loopsRemainingChanged()
\qmlsignal QtMultimedia::SoundEffect::loopCountChanged()
bool isLoaded() const
Returns whether the sound effect has finished loading the \l source().
void loadedChanged()
\qmlsignal QtMultimedia::SoundEffect::sourceChanged()
float volume
\qmlproperty qreal QtMultimedia::SoundEffect::volume
bool isPlaying() const
Returns true if the sound effect is currently playing, or false otherwise.
void volumeChanged()
\qmlsignal QtMultimedia::SoundEffect::loopsRemainingChanged()
void play()
\qmlmethod QtMultimedia::SoundEffect::play()
QAudioDevice audioDevice
Returns the QAudioDevice instance.
int loopsRemaining
\qmlproperty int QtMultimedia::SoundEffect::loopsRemaining
void setAudioDevice(const QAudioDevice &device)
void setMuted(bool muted)
Sets whether to mute this sound effect's playback.
static QStringList supportedMimeTypes()
Returns a list of the supported mime types for this platform.
Status
\value Null No source has been set or the source is null.
void setSource(const QUrl &url)
Set the current URL to play to url.
void mutedChanged()
\qmlsignal QtMultimedia::SoundEffect::volumeChanged()
void loopCountChanged()
\qmlsignal QtMultimedia::SoundEffect::loadedChanged()
void statusChanged()
\qmlsignal QtMultimedia::SoundEffect::playingChanged()
QSoundEffect(QObject *parent=nullptr)
\qmltype SoundEffect \instantiates QSoundEffect
\inmodule QtCore
\inmodule QtCore
Definition qurl.h:94
bool isValid() const
Returns true if the URL is non-empty and valid; otherwise returns false.
Definition qurl.cpp:1882
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
Definition qurl.cpp:1896
QString toString(FormattingOptions options=FormattingOptions(PrettyDecoded)) const
Returns a string representation of the URL.
Definition qurl.cpp:2831
#define this
Definition dialogs.cpp:9
else opt state
[0]
State
Definition qaudio.h:29
@ StoppedState
Definition qaudio.h:29
@ IdleState
Definition qaudio.h:29
Combined button and popup list for selecting options.
QList< QString > QStringList
Constructs a string list that contains the given string, str.
EGLDeviceEXT * devices
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCCritical(category,...)
#define qCDebug(category,...)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
GLenum GLsizei GLuint GLint * bytesWritten
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLsizei GLenum GLboolean sink
GLenum GLsizei len
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define qUtf16Printable(string)
Definition qstring.h:1543
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define Q_SLOTS
#define emit
#define Q_UNUSED(x)
long long qint64
Definition qtypes.h:60
QUrl url("example.com")
[constructor-url-reference]
myObject disconnect()
[26]
QByteArray readData()