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 | |
16 | static Q_LOGGING_CATEGORY(qLcSoundEffect, "qt.multimedia.soundeffect") |
17 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | Q_GLOBAL_STATIC(QSampleCache, sampleCache) |
21 | |
22 | namespace |
23 | { |
24 | struct AudioSinkDeleter |
25 | { |
26 | void operator ()(QAudioSink* sink) const |
27 | { |
28 | sink->stop(); |
29 | // Investigate:should we just delete? |
30 | sink->deleteLater(); |
31 | } |
32 | }; |
33 | |
34 | struct SampleDeleter |
35 | { |
36 | void operator ()(QSample* sample) const |
37 | { |
38 | sample->release(); |
39 | } |
40 | }; |
41 | } |
42 | |
43 | class QSoundEffectPrivate : public QIODevice |
44 | { |
45 | public: |
46 | QSoundEffectPrivate(QSoundEffect *q, const QAudioDevice &audioDevice = QAudioDevice()); |
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; |
54 | return m_loopCount == QSoundEffect::Infinite ? 0 : m_loopCount * m_audioBuffer.byteCount(); |
55 | } |
56 | qint64 bytesAvailable() const override { |
57 | if (m_sample->state() != QSample::Ready) |
58 | return 0; |
59 | if (m_loopCount == QSoundEffect::Infinite) |
60 | return std::numeric_limits<qint64>::max(); |
61 | return m_runningCount * m_audioBuffer.byteCount() - m_offset; |
62 | } |
63 | bool isSequential() const override { |
64 | return m_loopCount == QSoundEffect::Infinite; |
65 | } |
66 | bool atEnd() const override { |
67 | return m_runningCount == 0; |
68 | } |
69 | |
70 | void setLoopsRemaining(int loopsRemaining); |
71 | void setStatus(QSoundEffect::Status status); |
72 | void setPlaying(bool playing); |
73 | bool updateAudioOutput(); |
74 | |
75 | public Q_SLOTS: |
76 | void sampleReady(QSample *); |
77 | void decoderError(QSample *); |
78 | void stateChanged(QAudio::State); |
79 | |
80 | public: |
81 | QSoundEffect *q_ptr; |
82 | QUrl m_url; |
83 | int m_loopCount = 1; |
84 | int m_runningCount = 0; |
85 | bool m_playing = false; |
86 | QSoundEffect::Status m_status = QSoundEffect::Null; |
87 | std::unique_ptr<QAudioSink, AudioSinkDeleter> m_audioSink; |
88 | std::unique_ptr<QSample, SampleDeleter> m_sample; |
89 | QAudioBuffer m_audioBuffer; |
90 | bool m_muted = false; |
91 | float m_volume = 1.0; |
92 | bool m_sampleReady = false; |
93 | qint64 m_offset = 0; |
94 | QAudioDevice m_audioDevice; |
95 | }; |
96 | |
97 | QSoundEffectPrivate::QSoundEffectPrivate(QSoundEffect *q, const QAudioDevice &audioDevice) |
98 | : QIODevice(q) |
99 | , q_ptr(q) |
100 | , m_audioDevice(audioDevice) |
101 | { |
102 | open(mode: QIODevice::ReadOnly); |
103 | |
104 | QPlatformMediaIntegration::instance()->mediaDevices()->prepareAudio(); |
105 | } |
106 | |
107 | void QSoundEffectPrivate::sampleReady(QSample *sample) |
108 | { |
109 | if (sample && sample != m_sample.get()) |
110 | return; |
111 | |
112 | if (m_status == QSoundEffect::Error) |
113 | return; |
114 | |
115 | qCDebug(qLcSoundEffect) << this << "sampleReady: sample size:"<< m_sample->data().size(); |
116 | disconnect(sender: m_sample.get(), signal: &QSample::error, receiver: this, slot: &QSoundEffectPrivate::decoderError); |
117 | disconnect(sender: m_sample.get(), signal: &QSample::ready, receiver: this, slot: &QSoundEffectPrivate::sampleReady); |
118 | |
119 | if (!m_audioSink) { |
120 | if (!updateAudioOutput()) // Create audio sink |
121 | return; // Returns if no audio devices are available |
122 | } |
123 | |
124 | m_sampleReady = true; |
125 | setStatus(QSoundEffect::Ready); |
126 | |
127 | if (m_playing && m_audioSink->state() == QAudio::StoppedState) { |
128 | qCDebug(qLcSoundEffect) << this << "starting playback on audiooutput"; |
129 | m_audioSink->start(device: this); |
130 | } |
131 | } |
132 | |
133 | void QSoundEffectPrivate::decoderError(QSample *sample) |
134 | { |
135 | if (sample && sample != m_sample.get()) |
136 | return; |
137 | |
138 | qWarning(msg: "QSoundEffect(qaudio): Error decoding source %ls", qUtf16Printable(m_url.toString())); |
139 | disconnect(sender: m_sample.get(), signal: &QSample::ready, receiver: this, slot: &QSoundEffectPrivate::sampleReady); |
140 | disconnect(sender: m_sample.get(), signal: &QSample::error, receiver: this, slot: &QSoundEffectPrivate::decoderError); |
141 | m_playing = false; |
142 | setStatus(QSoundEffect::Error); |
143 | } |
144 | |
145 | void QSoundEffectPrivate::stateChanged(QAudio::State state) |
146 | { |
147 | qCDebug(qLcSoundEffect) << this << "stateChanged "<< state; |
148 | if ((state == QAudio::IdleState && m_runningCount == 0) || state == QAudio::StoppedState) |
149 | q_ptr->stop(); |
150 | } |
151 | |
152 | bool QSoundEffectPrivate::updateAudioOutput() |
153 | { |
154 | const auto audioDevice = |
155 | m_audioDevice.isNull() ? QMediaDevices::defaultAudioOutput() : m_audioDevice; |
156 | |
157 | if (audioDevice.isNull()) { |
158 | // We are likely on a virtual machine, for example in CI |
159 | qCCritical(qLcSoundEffect) << "Failed to update audio output. No audio devices available."; |
160 | setStatus(QSoundEffect::Error); |
161 | return false; |
162 | } |
163 | |
164 | if (m_audioDevice.isNull()) { |
165 | q_ptr->setAudioDevice(audioDevice); // Updates m_audioDevice and emits audioDeviceChanged |
166 | } |
167 | |
168 | m_audioBuffer = {}; |
169 | |
170 | Q_ASSERT(m_sample); |
171 | |
172 | const auto &sampleFormat = m_sample->format(); |
173 | const auto sampleChannelConfig = |
174 | sampleFormat.channelConfig() == QAudioFormat::ChannelConfigUnknown |
175 | ? QAudioFormat::defaultChannelConfigForChannelCount(channelCount: sampleFormat.channelCount()) |
176 | : sampleFormat.channelConfig(); |
177 | |
178 | if (sampleChannelConfig != audioDevice.channelConfiguration() |
179 | && audioDevice.channelConfiguration() != QAudioFormat::ChannelConfigUnknown) { |
180 | qCDebug(qLcSoundEffect) << "Create resampler for channels mapping: config" |
181 | << sampleFormat.channelConfig() << "=> config" |
182 | << audioDevice.channelConfiguration(); |
183 | auto outputFormat = sampleFormat; |
184 | outputFormat.setChannelConfig(audioDevice.channelConfiguration()); |
185 | |
186 | const auto resampler = QPlatformMediaIntegration::instance()->createAudioResampler( |
187 | m_sample->format(), outputFormat); |
188 | if (resampler) |
189 | m_audioBuffer = resampler.value()->resample(data: m_sample->data().constData(), |
190 | size: m_sample->data().size()); |
191 | else |
192 | qCDebug(qLcSoundEffect) << "Cannot create resampler for channels mapping"; |
193 | } |
194 | |
195 | if (!m_audioBuffer.isValid()) |
196 | m_audioBuffer = QAudioBuffer(m_sample->data(), m_sample->format()); |
197 | |
198 | m_audioSink.reset(p: new QAudioSink(audioDevice, m_audioBuffer.format())); |
199 | |
200 | connect(sender: m_audioSink.get(), signal: &QAudioSink::stateChanged, context: this, slot: &QSoundEffectPrivate::stateChanged); |
201 | if (!m_muted) |
202 | m_audioSink->setVolume(m_volume); |
203 | else |
204 | m_audioSink->setVolume(0); |
205 | |
206 | return true; |
207 | } |
208 | |
209 | qint64 QSoundEffectPrivate::readData(char *data, qint64 len) |
210 | { |
211 | qCDebug(qLcSoundEffect) << this << "readData"<< len << m_runningCount; |
212 | if (!len) |
213 | return 0; |
214 | if (m_sample->state() != QSample::Ready) |
215 | return 0; |
216 | if (m_runningCount == 0 || !m_playing) |
217 | return 0; |
218 | |
219 | qint64 bytesWritten = 0; |
220 | |
221 | const int sampleSize = m_audioBuffer.byteCount(); |
222 | const char *sampleData = m_audioBuffer.constData<char>(); |
223 | |
224 | while (len && m_runningCount) { |
225 | int toWrite = qMax(a: 0, b: qMin(a: sampleSize - m_offset, b: len)); |
226 | memcpy(dest: data, src: sampleData + m_offset, n: toWrite); |
227 | bytesWritten += toWrite; |
228 | data += toWrite; |
229 | len -= toWrite; |
230 | m_offset += toWrite; |
231 | if (m_offset >= sampleSize) { |
232 | if (m_runningCount > 0 && m_runningCount != QSoundEffect::Infinite) |
233 | setLoopsRemaining(m_runningCount - 1); |
234 | m_offset = 0; |
235 | } |
236 | } |
237 | |
238 | return bytesWritten; |
239 | } |
240 | |
241 | qint64 QSoundEffectPrivate::writeData(const char *data, qint64 len) |
242 | { |
243 | Q_UNUSED(data); |
244 | Q_UNUSED(len); |
245 | return 0; |
246 | } |
247 | |
248 | void QSoundEffectPrivate::setLoopsRemaining(int loopsRemaining) |
249 | { |
250 | if (m_runningCount == loopsRemaining) |
251 | return; |
252 | qCDebug(qLcSoundEffect) << this << "setLoopsRemaining "<< loopsRemaining; |
253 | m_runningCount = loopsRemaining; |
254 | emit q_ptr->loopsRemainingChanged(); |
255 | } |
256 | |
257 | void QSoundEffectPrivate::setStatus(QSoundEffect::Status status) |
258 | { |
259 | qCDebug(qLcSoundEffect) << this << "setStatus"<< status; |
260 | if (m_status == status) |
261 | return; |
262 | bool oldLoaded = q_ptr->isLoaded(); |
263 | m_status = status; |
264 | emit q_ptr->statusChanged(); |
265 | if (oldLoaded != q_ptr->isLoaded()) |
266 | emit q_ptr->loadedChanged(); |
267 | } |
268 | |
269 | void QSoundEffectPrivate::setPlaying(bool playing) |
270 | { |
271 | qCDebug(qLcSoundEffect) << this << "setPlaying("<< playing << ")"<< m_playing; |
272 | if (m_audioSink) { |
273 | m_audioSink->stop(); |
274 | if (playing && !m_sampleReady) |
275 | return; |
276 | } |
277 | |
278 | if (m_playing == playing) |
279 | return; |
280 | m_playing = playing; |
281 | |
282 | if (m_audioSink && playing) |
283 | m_audioSink->start(device: this); |
284 | |
285 | emit q_ptr->playingChanged(); |
286 | } |
287 | |
288 | /*! |
289 | \class QSoundEffect |
290 | \brief The QSoundEffect class provides a way to play low latency sound effects. |
291 | |
292 | \ingroup multimedia |
293 | \ingroup multimedia_audio |
294 | \inmodule QtMultimedia |
295 | |
296 | This class allows you to play uncompressed audio files (typically WAV files) in |
297 | a generally lower latency way, and is suitable for "feedback" type sounds in |
298 | response to user actions (e.g. virtual keyboard sounds, positive or negative |
299 | feedback for popup dialogs, or game sounds). If low latency is not important, |
300 | consider using the QMediaPlayer class instead, since it supports a wider |
301 | variety of media formats and is less resource intensive. |
302 | |
303 | This example shows how a looping, somewhat quiet sound effect |
304 | can be played: |
305 | |
306 | \snippet multimedia-snippets/qsound.cpp 2 |
307 | |
308 | Typically the sound effect should be reused, which allows all the |
309 | parsing and preparation to be done ahead of time, and only triggered |
310 | when necessary. This assists with lower latency audio playback. |
311 | |
312 | \snippet multimedia-snippets/qsound.cpp 3 |
313 | |
314 | Since QSoundEffect requires slightly more resources to achieve lower |
315 | latency playback, the platform may limit the number of simultaneously playing |
316 | sound effects. |
317 | */ |
318 | |
319 | |
320 | /*! |
321 | \qmltype SoundEffect |
322 | \nativetype QSoundEffect |
323 | \brief The SoundEffect type provides a way to play sound effects in QML. |
324 | |
325 | \inmodule QtMultimedia |
326 | \ingroup multimedia_qml |
327 | \ingroup multimedia_audio_qml |
328 | \inqmlmodule QtMultimedia |
329 | |
330 | This type allows you to play uncompressed audio files (typically WAV files) in |
331 | a generally lower latency way, and is suitable for "feedback" type sounds in |
332 | response to user actions (e.g. virtual keyboard sounds, positive or negative |
333 | feedback for popup dialogs, or game sounds). If low latency is not important, |
334 | consider using the MediaPlayer type instead, since it support a wider |
335 | variety of media formats and is less resource intensive. |
336 | |
337 | Typically the sound effect should be reused, which allows all the |
338 | parsing and preparation to be done ahead of time, and only triggered |
339 | when necessary. This is easy to achieve with QML, since you can declare your |
340 | SoundEffect instance and refer to it elsewhere. |
341 | |
342 | The following example plays a WAV file on mouse click. |
343 | |
344 | \snippet multimedia-snippets/soundeffect.qml complete snippet |
345 | |
346 | Since SoundEffect requires slightly more resources to achieve lower |
347 | latency playback, the platform may limit the number of simultaneously playing |
348 | sound effects. |
349 | */ |
350 | |
351 | /*! |
352 | Creates a QSoundEffect with the given \a parent. |
353 | */ |
354 | QSoundEffect::QSoundEffect(QObject *parent) |
355 | : QSoundEffect(QAudioDevice(), parent) |
356 | { |
357 | } |
358 | |
359 | /*! |
360 | Creates a QSoundEffect with the given \a audioDevice and \a parent. |
361 | */ |
362 | QSoundEffect::QSoundEffect(const QAudioDevice &audioDevice, QObject *parent) |
363 | : QObject(parent) |
364 | , d(new QSoundEffectPrivate(this, audioDevice)) |
365 | { |
366 | } |
367 | |
368 | /*! |
369 | Destroys this sound effect. |
370 | */ |
371 | QSoundEffect::~QSoundEffect() |
372 | { |
373 | stop(); |
374 | d->m_audioSink.reset(); |
375 | d->m_sample.reset(); |
376 | delete d; |
377 | } |
378 | |
379 | /*! |
380 | \fn QSoundEffect::supportedMimeTypes() |
381 | |
382 | Returns a list of the supported mime types for this platform. |
383 | */ |
384 | QStringList QSoundEffect::supportedMimeTypes() |
385 | { |
386 | // Only return supported mime types if we have a audio device available |
387 | const QList<QAudioDevice> devices = QMediaDevices::audioOutputs(); |
388 | if (devices.isEmpty()) |
389 | return QStringList(); |
390 | |
391 | return QStringList() << QLatin1String("audio/x-wav") |
392 | << QLatin1String("audio/wav") |
393 | << QLatin1String("audio/wave") |
394 | << QLatin1String("audio/x-pn-wav"); |
395 | } |
396 | |
397 | /*! |
398 | \qmlproperty url QtMultimedia::SoundEffect::source |
399 | |
400 | This property holds the url for the sound to play. For the SoundEffect |
401 | to attempt to load the source, the URL must exist and the application must have read permission |
402 | in the specified directory. If the desired source is a local file the URL may be specified |
403 | using either absolute or relative (to the file that declared the SoundEffect) pathing. |
404 | */ |
405 | /*! |
406 | \property QSoundEffect::source |
407 | |
408 | This property holds the url for the sound to play. For the SoundEffect |
409 | to attempt to load the source, the URL must exist and the application must have read permission |
410 | in the specified directory. |
411 | */ |
412 | |
413 | /*! Returns the URL of the current source to play */ |
414 | QUrl QSoundEffect::source() const |
415 | { |
416 | return d->m_url; |
417 | } |
418 | |
419 | /*! Set the current URL to play to \a url. */ |
420 | void QSoundEffect::setSource(const QUrl &url) |
421 | { |
422 | qCDebug(qLcSoundEffect) << this << "setSource current="<< d->m_url << ", to="<< url; |
423 | if (d->m_url == url) |
424 | return; |
425 | |
426 | Q_ASSERT(d->m_url != url); |
427 | |
428 | stop(); |
429 | |
430 | d->m_url = url; |
431 | d->m_sampleReady = false; |
432 | |
433 | if (url.isEmpty()) { |
434 | d->setStatus(QSoundEffect::Null); |
435 | return; |
436 | } |
437 | |
438 | if (!url.isValid()) { |
439 | d->setStatus(QSoundEffect::Error); |
440 | return; |
441 | } |
442 | |
443 | if (d->m_sample) { |
444 | if (!d->m_sampleReady) { |
445 | disconnect(sender: d->m_sample.get(), signal: &QSample::error, receiver: d, slot: &QSoundEffectPrivate::decoderError); |
446 | disconnect(sender: d->m_sample.get(), signal: &QSample::ready, receiver: d, slot: &QSoundEffectPrivate::sampleReady); |
447 | } |
448 | d->m_sample.reset(); |
449 | } |
450 | |
451 | if (d->m_audioSink) { |
452 | disconnect(sender: d->m_audioSink.get(), signal: &QAudioSink::stateChanged, receiver: d, slot: &QSoundEffectPrivate::stateChanged); |
453 | d->m_audioSink.reset(); |
454 | } |
455 | |
456 | d->setStatus(QSoundEffect::Loading); |
457 | d->m_sample.reset(p: sampleCache()->requestSample(url)); |
458 | connect(sender: d->m_sample.get(), signal: &QSample::error, context: d, slot: &QSoundEffectPrivate::decoderError); |
459 | connect(sender: d->m_sample.get(), signal: &QSample::ready, context: d, slot: &QSoundEffectPrivate::sampleReady); |
460 | |
461 | switch (d->m_sample->state()) { |
462 | case QSample::Ready: |
463 | d->sampleReady(sample: d->m_sample.get()); |
464 | break; |
465 | case QSample::Error: |
466 | d->decoderError(sample: d->m_sample.get()); |
467 | break; |
468 | default: |
469 | break; |
470 | } |
471 | |
472 | emit sourceChanged(); |
473 | } |
474 | |
475 | /*! |
476 | \qmlproperty int QtMultimedia::SoundEffect::loops |
477 | |
478 | This property holds the number of times the sound is played. A value of 0 or 1 means |
479 | the sound will be played only once; set to SoundEffect.Infinite to enable infinite looping. |
480 | |
481 | The value can be changed while the sound effect is playing, in which case it will update |
482 | the remaining loops to the new value. |
483 | */ |
484 | |
485 | /*! |
486 | \property QSoundEffect::loops |
487 | This property holds the number of times the sound is played. A value of 0 or 1 means |
488 | the sound will be played only once; set to SoundEffect.Infinite to enable infinite looping. |
489 | |
490 | The value can be changed while the sound effect is playing, in which case it will update |
491 | the remaining loops to the new value. |
492 | */ |
493 | |
494 | /*! |
495 | Returns the total number of times that this sound effect will be played before stopping. |
496 | |
497 | See the \l loopsRemaining() method for the number of loops currently remaining. |
498 | */ |
499 | int QSoundEffect::loopCount() const |
500 | { |
501 | return d->m_loopCount; |
502 | } |
503 | |
504 | /*! |
505 | \enum QSoundEffect::Loop |
506 | |
507 | \value Infinite Used as a parameter to \l setLoopCount() for infinite looping |
508 | */ |
509 | |
510 | /*! |
511 | Set the total number of times to play this sound effect to \a loopCount. |
512 | |
513 | Setting the loop count to 0 or 1 means the sound effect will be played only once; |
514 | pass \c QSoundEffect::Infinite to repeat indefinitely. The loop count can be changed while |
515 | the sound effect is playing, in which case it will update the remaining loops to |
516 | the new \a loopCount. |
517 | |
518 | \sa loopsRemaining() |
519 | */ |
520 | void QSoundEffect::setLoopCount(int loopCount) |
521 | { |
522 | if (loopCount < 0 && loopCount != Infinite) { |
523 | qWarning(msg: "SoundEffect: loops should be SoundEffect.Infinite, 0 or positive integer"); |
524 | return; |
525 | } |
526 | if (loopCount == 0) |
527 | loopCount = 1; |
528 | if (d->m_loopCount == loopCount) |
529 | return; |
530 | |
531 | d->m_loopCount = loopCount; |
532 | if (d->m_playing) |
533 | d->setLoopsRemaining(loopCount); |
534 | emit loopCountChanged(); |
535 | } |
536 | |
537 | /*! |
538 | \property QSoundEffect::audioDevice |
539 | |
540 | Returns the QAudioDevice instance. |
541 | */ |
542 | QAudioDevice QSoundEffect::audioDevice() |
543 | { |
544 | return d->m_audioDevice; |
545 | } |
546 | |
547 | void QSoundEffect::setAudioDevice(const QAudioDevice &device) |
548 | { |
549 | qCDebug(qLcSoundEffect) << this << "setAudioDevice:"<< device.description(); |
550 | |
551 | if (d->m_audioDevice == device) |
552 | return; |
553 | |
554 | d->m_audioDevice = device; |
555 | |
556 | if (!d->m_sampleReady) { |
557 | emit audioDeviceChanged(); |
558 | return; // The audio sink will be recreated later by QSoundEffect::sampleReady() |
559 | } |
560 | |
561 | bool playing = d->m_playing; |
562 | std::chrono::microseconds current_time{d->m_audioBuffer.format().durationForBytes(byteCount: d->m_offset)}; |
563 | |
564 | // Recreate the QAudioSink with the new audio device and current sample |
565 | if (d->updateAudioOutput() && playing) { |
566 | // Resume playback from current position |
567 | d->m_offset = d->m_audioBuffer.format().bytesForDuration(microseconds: current_time.count()); |
568 | d->setPlaying(true); |
569 | } |
570 | |
571 | emit audioDeviceChanged(); |
572 | } |
573 | |
574 | /*! |
575 | \qmlproperty int QtMultimedia::SoundEffect::loopsRemaining |
576 | |
577 | This property contains the number of loops remaining before the sound effect |
578 | stops by itself, or SoundEffect.Infinite if that's what has been set in \l loops. |
579 | */ |
580 | /*! |
581 | \property QSoundEffect::loopsRemaining |
582 | |
583 | This property contains the number of loops remaining before the sound effect |
584 | stops by itself, or QSoundEffect::Infinite if that's what has been set in \l loops. |
585 | */ |
586 | int QSoundEffect::loopsRemaining() const |
587 | { |
588 | return d->m_runningCount; |
589 | } |
590 | |
591 | |
592 | /*! |
593 | \qmlproperty qreal QtMultimedia::SoundEffect::volume |
594 | |
595 | This property holds the volume of the sound effect playback. |
596 | |
597 | The volume is scaled linearly from \c 0.0 (silence) to \c 1.0 (full volume). Values outside this |
598 | range will be clamped. |
599 | |
600 | The default volume is \c 1.0. |
601 | |
602 | UI volume controls should usually be scaled non-linearly. For example, using a logarithmic scale |
603 | will produce linear changes in perceived loudness, which is what a user would normally expect |
604 | from a volume control. See \l {QtAudio::convertVolume()}{convertVolume()} |
605 | for more details. |
606 | */ |
607 | /*! |
608 | \property QSoundEffect::volume |
609 | |
610 | This property holds the volume of the sound effect playback, from 0.0 (silence) to 1.0 (full volume). |
611 | */ |
612 | |
613 | /*! |
614 | Returns the current volume of this sound effect, from 0.0 (silent) to 1.0 (maximum volume). |
615 | */ |
616 | float QSoundEffect::volume() const |
617 | { |
618 | if (d->m_audioSink && !d->m_muted) |
619 | return d->m_audioSink->volume(); |
620 | |
621 | return d->m_volume; |
622 | } |
623 | |
624 | /*! |
625 | Sets the sound effect volume to \a volume. |
626 | |
627 | The volume is scaled linearly from \c 0.0 (silence) to \c 1.0 (full volume). Values outside this |
628 | range will be clamped. |
629 | |
630 | The default volume is \c 1.0. |
631 | |
632 | UI volume controls should usually be scaled non-linearly. For example, using a logarithmic scale |
633 | will produce linear changes in perceived loudness, which is what a user would normally expect |
634 | from a volume control. See QtAudio::convertVolume() for more details. |
635 | */ |
636 | void QSoundEffect::setVolume(float volume) |
637 | { |
638 | volume = qBound(min: 0.0f, val: volume, max: 1.0f); |
639 | if (d->m_volume == volume) |
640 | return; |
641 | |
642 | d->m_volume = volume; |
643 | |
644 | if (d->m_audioSink && !d->m_muted) |
645 | d->m_audioSink->setVolume(volume); |
646 | |
647 | emit volumeChanged(); |
648 | } |
649 | |
650 | /*! |
651 | \qmlproperty bool QtMultimedia::SoundEffect::muted |
652 | |
653 | This property provides a way to control muting. A value of \c true will mute this effect. |
654 | Otherwise, playback will occur with the currently specified \l volume. |
655 | */ |
656 | /*! |
657 | \property QSoundEffect::muted |
658 | |
659 | This property provides a way to control muting. A value of \c true will mute this effect. |
660 | */ |
661 | |
662 | /*! Returns whether this sound effect is muted */ |
663 | bool QSoundEffect::isMuted() const |
664 | { |
665 | return d->m_muted; |
666 | } |
667 | |
668 | /*! |
669 | Sets whether to mute this sound effect's playback. |
670 | |
671 | If \a muted is true, playback will be muted (silenced), |
672 | and otherwise playback will occur with the currently |
673 | specified volume(). |
674 | */ |
675 | void QSoundEffect::setMuted(bool muted) |
676 | { |
677 | if (d->m_muted == muted) |
678 | return; |
679 | |
680 | if (muted && d->m_audioSink) |
681 | d->m_audioSink->setVolume(0); |
682 | else if (!muted && d->m_audioSink && d->m_muted) |
683 | d->m_audioSink->setVolume(d->m_volume); |
684 | |
685 | d->m_muted = muted; |
686 | emit mutedChanged(); |
687 | } |
688 | |
689 | /*! |
690 | \fn QSoundEffect::isLoaded() const |
691 | |
692 | Returns whether the sound effect has finished loading the \l source(). |
693 | */ |
694 | /*! |
695 | \qmlmethod bool QtMultimedia::SoundEffect::isLoaded() |
696 | |
697 | Returns whether the sound effect has finished loading the \l source. |
698 | */ |
699 | bool QSoundEffect::isLoaded() const |
700 | { |
701 | return d->m_status == QSoundEffect::Ready; |
702 | } |
703 | |
704 | /*! |
705 | \qmlmethod QtMultimedia::SoundEffect::play() |
706 | |
707 | Start playback of the sound effect, looping the effect for the number of |
708 | times as specified in the loops property. |
709 | |
710 | This is the default method for SoundEffect. |
711 | |
712 | \snippet multimedia-snippets/soundeffect.qml play sound on click |
713 | */ |
714 | /*! |
715 | \fn QSoundEffect::play() |
716 | |
717 | Start playback of the sound effect, looping the effect for the number of |
718 | times as specified in the loops property. |
719 | */ |
720 | void QSoundEffect::play() |
721 | { |
722 | d->m_offset = 0; |
723 | d->setLoopsRemaining(d->m_loopCount); |
724 | qCDebug(qLcSoundEffect) << this << "play"<< d->m_loopCount << d->m_runningCount; |
725 | if (d->m_status == QSoundEffect::Null || d->m_status == QSoundEffect::Error) { |
726 | d->setStatus(QSoundEffect::Null); |
727 | return; |
728 | } |
729 | d->setPlaying(true); |
730 | } |
731 | |
732 | /*! |
733 | \qmlproperty bool QtMultimedia::SoundEffect::playing |
734 | |
735 | This property indicates whether the sound effect is playing or not. |
736 | */ |
737 | /*! |
738 | \property QSoundEffect::playing |
739 | |
740 | This property indicates whether the sound effect is playing or not. |
741 | */ |
742 | |
743 | /*! Returns true if the sound effect is currently playing, or false otherwise */ |
744 | bool QSoundEffect::isPlaying() const |
745 | { |
746 | return d->m_playing; |
747 | } |
748 | |
749 | /*! |
750 | \enum QSoundEffect::Status |
751 | |
752 | \value Null No source has been set or the source is null. |
753 | \value Loading The SoundEffect is trying to load the source. |
754 | \value Ready The source is loaded and ready for play. |
755 | \value Error An error occurred during operation, such as failure of loading the source. |
756 | |
757 | */ |
758 | |
759 | /*! |
760 | \qmlproperty enumeration QtMultimedia::SoundEffect::status |
761 | |
762 | This property indicates the current status of the SoundEffect |
763 | as enumerated within SoundEffect. |
764 | Possible statuses are listed below. |
765 | |
766 | \table |
767 | \header \li Value \li Description |
768 | \row \li SoundEffect.Null \li No source has been set or the source is null. |
769 | \row \li SoundEffect.Loading \li The SoundEffect is trying to load the source. |
770 | \row \li SoundEffect.Ready \li The source is loaded and ready for play. |
771 | \row \li SoundEffect.Error \li An error occurred during operation, such as failure of loading the source. |
772 | \endtable |
773 | */ |
774 | /*! |
775 | \property QSoundEffect::status |
776 | |
777 | This property indicates the current status of the sound effect |
778 | from the \l QSoundEffect::Status enumeration. |
779 | */ |
780 | |
781 | /*! |
782 | Returns the current status of this sound effect. |
783 | */ |
784 | QSoundEffect::Status QSoundEffect::status() const |
785 | { |
786 | return d->m_status; |
787 | } |
788 | |
789 | /*! |
790 | \qmlmethod QtMultimedia::SoundEffect::stop() |
791 | |
792 | Stop current playback. |
793 | |
794 | */ |
795 | /*! |
796 | \fn QSoundEffect::stop() |
797 | |
798 | Stop current playback. |
799 | |
800 | */ |
801 | void QSoundEffect::stop() |
802 | { |
803 | if (!d->m_playing) |
804 | return; |
805 | qCDebug(qLcSoundEffect) << "stop()"; |
806 | d->m_offset = 0; |
807 | |
808 | d->setPlaying(false); |
809 | } |
810 | |
811 | /* Signals */ |
812 | |
813 | /*! |
814 | \fn void QSoundEffect::sourceChanged() |
815 | |
816 | The \c sourceChanged signal is emitted when the source has been changed. |
817 | */ |
818 | /*! |
819 | \qmlsignal QtMultimedia::SoundEffect::sourceChanged() |
820 | |
821 | The \c sourceChanged signal is emitted when the source has been changed. |
822 | */ |
823 | /*! |
824 | \fn void QSoundEffect::loadedChanged() |
825 | |
826 | The \c loadedChanged signal is emitted when the loading state has changed. |
827 | */ |
828 | /*! |
829 | \qmlsignal QtMultimedia::SoundEffect::loadedChanged() |
830 | |
831 | The \c loadedChanged signal is emitted when the loading state has changed. |
832 | */ |
833 | |
834 | /*! |
835 | \fn void QSoundEffect::loopCountChanged() |
836 | |
837 | The \c loopCountChanged signal is emitted when the initial number of loops has changed. |
838 | */ |
839 | /*! |
840 | \qmlsignal QtMultimedia::SoundEffect::loopCountChanged() |
841 | |
842 | The \c loopCountChanged signal is emitted when the initial number of loops has changed. |
843 | */ |
844 | |
845 | /*! |
846 | \fn void QSoundEffect::loopsRemainingChanged() |
847 | |
848 | The \c loopsRemainingChanged signal is emitted when the remaining number of loops has changed. |
849 | */ |
850 | /*! |
851 | \qmlsignal QtMultimedia::SoundEffect::loopsRemainingChanged() |
852 | |
853 | The \c loopsRemainingChanged signal is emitted when the remaining number of loops has changed. |
854 | */ |
855 | |
856 | /*! |
857 | \fn void QSoundEffect::volumeChanged() |
858 | |
859 | The \c volumeChanged signal is emitted when the volume has changed. |
860 | */ |
861 | /*! |
862 | \qmlsignal QtMultimedia::SoundEffect::volumeChanged() |
863 | |
864 | The \c volumeChanged signal is emitted when the volume has changed. |
865 | */ |
866 | |
867 | /*! |
868 | \fn void QSoundEffect::mutedChanged() |
869 | |
870 | The \c mutedChanged signal is emitted when the mute state has changed. |
871 | */ |
872 | /*! |
873 | \qmlsignal QtMultimedia::SoundEffect::mutedChanged() |
874 | |
875 | The \c mutedChanged signal is emitted when the mute state has changed. |
876 | */ |
877 | |
878 | /*! |
879 | \fn void QSoundEffect::playingChanged() |
880 | |
881 | The \c playingChanged signal is emitted when the playing property has changed. |
882 | */ |
883 | /*! |
884 | \qmlsignal QtMultimedia::SoundEffect::playingChanged() |
885 | |
886 | The \c playingChanged signal is emitted when the playing property has changed. |
887 | */ |
888 | |
889 | /*! |
890 | \fn void QSoundEffect::statusChanged() |
891 | |
892 | The \c statusChanged signal is emitted when the status property has changed. |
893 | */ |
894 | /*! |
895 | \qmlsignal QtMultimedia::SoundEffect::statusChanged() |
896 | |
897 | The \c statusChanged signal is emitted when the status property has changed. |
898 | */ |
899 | |
900 | QT_END_NAMESPACE |
901 | |
902 | #include "moc_qsoundeffect.cpp" |
903 |
Definitions
- qLcSoundEffect
- sampleCache
- AudioSinkDeleter
- operator ()
- SampleDeleter
- operator ()
- QSoundEffectPrivate
- ~QSoundEffectPrivate
- size
- bytesAvailable
- isSequential
- atEnd
- QSoundEffectPrivate
- sampleReady
- decoderError
- stateChanged
- updateAudioOutput
- readData
- writeData
- setLoopsRemaining
- setStatus
- setPlaying
- QSoundEffect
- QSoundEffect
- ~QSoundEffect
- supportedMimeTypes
- source
- setSource
- loopCount
- setLoopCount
- audioDevice
- setAudioDevice
- loopsRemaining
- volume
- setVolume
- isMuted
- setMuted
- isLoaded
- play
- isPlaying
- status
Start learning QML with our Intro Training
Find out more