Expose the audio category information for streams.
QAudioOutput and QSoundEffect now have a category property so that system volume mixing or processing can be applied. Initially just pulseaudio supports this but Windows Vista etc should also work. Change-Id: I6855b08367e5a055ac7dfcffd644c98bfd7c5a4e Reviewed-by: Ling Hu <ling.hu@nokia.com>
This commit is contained in:
committed by
Qt by Nokia
parent
8441d2e32e
commit
5f7b64346d
@@ -363,6 +363,41 @@ qreal QAudioOutput::volume() const
|
|||||||
return d->volume();
|
return d->volume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the audio category of this audio stream.
|
||||||
|
|
||||||
|
Some platforms can group audio streams into categories
|
||||||
|
and manage their volumes independently, or display them
|
||||||
|
in a system mixer control. You can set this property to
|
||||||
|
allow the platform to distinguish the purpose of your streams.
|
||||||
|
|
||||||
|
\sa setCategory()
|
||||||
|
*/
|
||||||
|
QString QAudioOutput::category() const
|
||||||
|
{
|
||||||
|
return d->category();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Sets the audio category of this audio stream.
|
||||||
|
|
||||||
|
Some platforms can group audio streams into categories
|
||||||
|
and manage their volumes independently, or display them
|
||||||
|
in a system mixer control. You can set this property to
|
||||||
|
allow the platform to distinguish the purpose of your streams.
|
||||||
|
|
||||||
|
Not all platforms support audio stream categorization. In this
|
||||||
|
case, the function call will be ignored.
|
||||||
|
|
||||||
|
Changing an audio output stream's category while it is opened
|
||||||
|
will not take effect until it is reopened.
|
||||||
|
\sa category()
|
||||||
|
*/
|
||||||
|
void QAudioOutput::setCategory(const QString &category)
|
||||||
|
{
|
||||||
|
d->setCategory(category);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\fn QAudioOutput::stateChanged(QAudio::State state)
|
\fn QAudioOutput::stateChanged(QAudio::State state)
|
||||||
This signal is emitted when the device \a state has changed.
|
This signal is emitted when the device \a state has changed.
|
||||||
|
|||||||
@@ -100,6 +100,9 @@ public:
|
|||||||
void setVolume(qreal);
|
void setVolume(qreal);
|
||||||
qreal volume() const;
|
qreal volume() const;
|
||||||
|
|
||||||
|
QString category() const;
|
||||||
|
void setCategory(const QString &category);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void stateChanged(QAudio::State);
|
void stateChanged(QAudio::State);
|
||||||
void notify();
|
void notify();
|
||||||
|
|||||||
@@ -97,6 +97,8 @@ public:
|
|||||||
virtual QAudioFormat format() const = 0;
|
virtual QAudioFormat format() const = 0;
|
||||||
virtual void setVolume(qreal) {}
|
virtual void setVolume(qreal) {}
|
||||||
virtual qreal volume() const { return 1.0; }
|
virtual qreal volume() const { return 1.0; }
|
||||||
|
virtual QString category() const { return QString(); }
|
||||||
|
virtual void setCategory(const QString &) { }
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void errorChanged(QAudio::Error);
|
void errorChanged(QAudio::Error);
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ QSoundEffect::QSoundEffect(QObject *parent) :
|
|||||||
connect(d, SIGNAL(loadedChanged()), SIGNAL(loadedChanged()));
|
connect(d, SIGNAL(loadedChanged()), SIGNAL(loadedChanged()));
|
||||||
connect(d, SIGNAL(playingChanged()), SIGNAL(playingChanged()));
|
connect(d, SIGNAL(playingChanged()), SIGNAL(playingChanged()));
|
||||||
connect(d, SIGNAL(statusChanged()), SIGNAL(statusChanged()));
|
connect(d, SIGNAL(statusChanged()), SIGNAL(statusChanged()));
|
||||||
|
connect(d, SIGNAL(categoryChanged()), SIGNAL(categoryChanged()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -366,6 +367,57 @@ QSoundEffect::Status QSoundEffect::status() const
|
|||||||
return d->status();
|
return d->status();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlproperty string QtMultimedia5::SoundEffect::category
|
||||||
|
\property QSoundEffect::category
|
||||||
|
|
||||||
|
This property contains the \e category of this sound effect.
|
||||||
|
|
||||||
|
Some platforms can perform different audio routing
|
||||||
|
for different categories, or may allow the user to
|
||||||
|
set different volume levels for different categories.
|
||||||
|
|
||||||
|
This setting will be ignored on platforms that do not
|
||||||
|
support audio categories.
|
||||||
|
*/
|
||||||
|
/*!
|
||||||
|
Returns the current \e category for this sound effect.
|
||||||
|
|
||||||
|
Some platforms can perform different audio routing
|
||||||
|
for different categories, or may allow the user to
|
||||||
|
set different volume levels for different categories.
|
||||||
|
|
||||||
|
This setting will be ignored on platforms that do not
|
||||||
|
support audio categories.
|
||||||
|
|
||||||
|
\sa setCategory()
|
||||||
|
*/
|
||||||
|
QString QSoundEffect::category() const
|
||||||
|
{
|
||||||
|
return d->category();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Sets the \e category of this sound effect to \a category.
|
||||||
|
|
||||||
|
Some platforms can perform different audio routing
|
||||||
|
for different categories, or may allow the user to
|
||||||
|
set different volume levels for different categories.
|
||||||
|
|
||||||
|
This setting will be ignored on platforms that do not
|
||||||
|
support audio categories.
|
||||||
|
|
||||||
|
If this setting is changed while a sound effect is playing
|
||||||
|
it will only take effect when the sound effect has stopped
|
||||||
|
playing.
|
||||||
|
|
||||||
|
\sa category()
|
||||||
|
*/
|
||||||
|
void QSoundEffect::setCategory(const QString &category)
|
||||||
|
{
|
||||||
|
d->setCategory(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmlmethod QtMultimedia5::SoundEffect::stop()
|
\qmlmethod QtMultimedia5::SoundEffect::stop()
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ class Q_MULTIMEDIA_EXPORT QSoundEffect : public QObject
|
|||||||
Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
|
Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
|
||||||
Q_PROPERTY(bool playing READ isPlaying NOTIFY playingChanged)
|
Q_PROPERTY(bool playing READ isPlaying NOTIFY playingChanged)
|
||||||
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
|
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
|
||||||
|
Q_PROPERTY(QString category READ category WRITE setCategory NOTIFY categoryChanged)
|
||||||
Q_ENUMS(Loop)
|
Q_ENUMS(Loop)
|
||||||
Q_ENUMS(Status)
|
Q_ENUMS(Status)
|
||||||
|
|
||||||
@@ -108,6 +109,9 @@ public:
|
|||||||
bool isPlaying() const;
|
bool isPlaying() const;
|
||||||
Status status() const;
|
Status status() const;
|
||||||
|
|
||||||
|
QString category() const;
|
||||||
|
void setCategory(const QString &category);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void sourceChanged();
|
void sourceChanged();
|
||||||
void loopCountChanged();
|
void loopCountChanged();
|
||||||
@@ -117,6 +121,7 @@ Q_SIGNALS:
|
|||||||
void loadedChanged();
|
void loadedChanged();
|
||||||
void playingChanged();
|
void playingChanged();
|
||||||
void statusChanged();
|
void statusChanged();
|
||||||
|
void categoryChanged();
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void play();
|
void play();
|
||||||
|
|||||||
@@ -352,6 +352,7 @@ QSoundEffectPrivate::QSoundEffectPrivate(QObject* parent):
|
|||||||
m_loopCount(1),
|
m_loopCount(1),
|
||||||
m_runningCount(0),
|
m_runningCount(0),
|
||||||
m_sample(0),
|
m_sample(0),
|
||||||
|
m_reloadCategory(false),
|
||||||
m_position(0)
|
m_position(0)
|
||||||
{
|
{
|
||||||
m_ref = new QSoundEffectRef(this);
|
m_ref = new QSoundEffectRef(this);
|
||||||
@@ -373,6 +374,32 @@ void QSoundEffectPrivate::release()
|
|||||||
this->deleteLater();
|
this->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString QSoundEffectPrivate::category() const
|
||||||
|
{
|
||||||
|
return m_category;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QSoundEffectPrivate::setCategory(const QString &category)
|
||||||
|
{
|
||||||
|
if (m_category != category) {
|
||||||
|
m_category = category;
|
||||||
|
if (m_playing || m_playQueued) {
|
||||||
|
// Currently playing, we need to disconnect when
|
||||||
|
// playback stops
|
||||||
|
m_reloadCategory = true;
|
||||||
|
} else if (m_pulseStream) {
|
||||||
|
// We have to disconnect and reconnect
|
||||||
|
unloadPulseStream();
|
||||||
|
createPulseStream();
|
||||||
|
} else {
|
||||||
|
// Well, next time we create the pulse stream
|
||||||
|
// it should be set
|
||||||
|
}
|
||||||
|
|
||||||
|
emit categoryChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QSoundEffectPrivate::~QSoundEffectPrivate()
|
QSoundEffectPrivate::~QSoundEffectPrivate()
|
||||||
{
|
{
|
||||||
m_ref->release();
|
m_ref->release();
|
||||||
@@ -700,6 +727,7 @@ void QSoundEffectPrivate::unloadPulseStream()
|
|||||||
pa_stream_unref(m_pulseStream);
|
pa_stream_unref(m_pulseStream);
|
||||||
disconnect(pulseDaemon(), SIGNAL(volumeChanged()), this, SLOT(updateVolume()));
|
disconnect(pulseDaemon(), SIGNAL(volumeChanged()), this, SLOT(updateVolume()));
|
||||||
m_pulseStream = 0;
|
m_pulseStream = 0;
|
||||||
|
m_reloadCategory = false; // category will be reloaded when we connect anyway
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -808,11 +836,16 @@ void QSoundEffectPrivate::stop()
|
|||||||
setPlaying(false);
|
setPlaying(false);
|
||||||
PulseDaemonLocker locker;
|
PulseDaemonLocker locker;
|
||||||
m_stopping = true;
|
m_stopping = true;
|
||||||
if (m_pulseStream)
|
if (m_pulseStream) {
|
||||||
emptyStream();
|
emptyStream();
|
||||||
|
if (m_reloadCategory) {
|
||||||
|
unloadPulseStream(); // upon play we reconnect anyway
|
||||||
|
}
|
||||||
|
}
|
||||||
setLoopsRemaining(0);
|
setLoopsRemaining(0);
|
||||||
m_position = 0;
|
m_position = 0;
|
||||||
m_playQueued = false;
|
m_playQueued = false;
|
||||||
|
m_reloadCategory = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QSoundEffectPrivate::underRun()
|
void QSoundEffectPrivate::underRun()
|
||||||
@@ -846,7 +879,12 @@ void QSoundEffectPrivate::createPulseStream()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
pa_proplist *propList = pa_proplist_new();
|
pa_proplist *propList = pa_proplist_new();
|
||||||
pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, "soundeffect");
|
if (m_category.isNull()) {
|
||||||
|
// Meant to be one of the strings "video", "music", "game", "event", "phone", "animation", "production", "a11y", "test"
|
||||||
|
pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, "game");
|
||||||
|
} else {
|
||||||
|
pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, m_category.toLatin1().constData());
|
||||||
|
}
|
||||||
pa_stream *stream = pa_stream_new_with_proplist(pulseDaemon()->context(), m_name.constData(), &m_pulseSpec, 0, propList);
|
pa_stream *stream = pa_stream_new_with_proplist(pulseDaemon()->context(), m_name.constData(), &m_pulseSpec, 0, propList);
|
||||||
pa_proplist_free(propList);
|
pa_proplist_free(propList);
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,9 @@ public:
|
|||||||
|
|
||||||
void release();
|
void release();
|
||||||
|
|
||||||
|
QString category() const;
|
||||||
|
void setCategory(const QString &category);
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void play();
|
void play();
|
||||||
void stop();
|
void stop();
|
||||||
@@ -105,6 +108,7 @@ Q_SIGNALS:
|
|||||||
void loadedChanged();
|
void loadedChanged();
|
||||||
void playingChanged();
|
void playingChanged();
|
||||||
void statusChanged();
|
void statusChanged();
|
||||||
|
void categoryChanged();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void decoderError();
|
void decoderError();
|
||||||
@@ -157,6 +161,8 @@ private:
|
|||||||
int m_runningCount;
|
int m_runningCount;
|
||||||
QUrl m_source;
|
QUrl m_source;
|
||||||
QByteArray m_name;
|
QByteArray m_name;
|
||||||
|
QString m_category;
|
||||||
|
bool m_reloadCategory;
|
||||||
|
|
||||||
QSample *m_sample;
|
QSample *m_sample;
|
||||||
int m_position;
|
int m_position;
|
||||||
|
|||||||
@@ -250,6 +250,20 @@ void QSoundEffectPrivate::setLoopsRemaining(int loopsRemaining)
|
|||||||
emit loopsRemainingChanged();
|
emit loopsRemainingChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Categories are ignored */
|
||||||
|
QString QSoundEffectPrivate::category() const
|
||||||
|
{
|
||||||
|
return m_category;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QSoundEffectPrivate::setCategory(const QString &category)
|
||||||
|
{
|
||||||
|
if (m_category != category && !m_playing) {
|
||||||
|
m_category = category;
|
||||||
|
emit categoryChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
#include "moc_qsoundeffect_qmedia_p.cpp"
|
#include "moc_qsoundeffect_qmedia_p.cpp"
|
||||||
|
|||||||
@@ -91,6 +91,10 @@ public:
|
|||||||
|
|
||||||
void release();
|
void release();
|
||||||
|
|
||||||
|
// Categories are not really supported with QMediaPlayer
|
||||||
|
QString category() const;
|
||||||
|
void setCategory(const QString &);
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void play();
|
void play();
|
||||||
void stop();
|
void stop();
|
||||||
@@ -102,6 +106,7 @@ Q_SIGNALS:
|
|||||||
void loadedChanged();
|
void loadedChanged();
|
||||||
void playingChanged();
|
void playingChanged();
|
||||||
void statusChanged();
|
void statusChanged();
|
||||||
|
void categoryChanged();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void stateChanged(QMediaPlayer::State);
|
void stateChanged(QMediaPlayer::State);
|
||||||
@@ -118,6 +123,7 @@ private:
|
|||||||
bool m_playing;
|
bool m_playing;
|
||||||
QSoundEffect::Status m_status;
|
QSoundEffect::Status m_status;
|
||||||
QMediaPlayer *m_player;
|
QMediaPlayer *m_player;
|
||||||
|
QString m_category;
|
||||||
};
|
};
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
const int PeriodTimeMs = 20;
|
const int PeriodTimeMs = 20;
|
||||||
|
const int LowLatencyBufferSizeMs = 40;
|
||||||
|
|
||||||
static void outputStreamWriteCallback(pa_stream *stream, size_t length, void *userdata)
|
static void outputStreamWriteCallback(pa_stream *stream, size_t length, void *userdata)
|
||||||
{
|
{
|
||||||
@@ -267,7 +268,23 @@ bool QPulseAudioOutput::open()
|
|||||||
|
|
||||||
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
|
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
|
||||||
pa_threaded_mainloop_lock(pulseEngine->mainloop());
|
pa_threaded_mainloop_lock(pulseEngine->mainloop());
|
||||||
m_stream = pa_stream_new(pulseEngine->context(), m_streamName.constData(), &spec, 0);
|
|
||||||
|
pa_proplist *propList = pa_proplist_new();
|
||||||
|
if (m_category.isNull()) {
|
||||||
|
// Meant to be one of the strings "video", "music", "game", "event", "phone", "animation", "production", "a11y", "test"
|
||||||
|
// We choose music unless the buffer size is small, where we choose game..
|
||||||
|
qint64 bytesPerSecond = m_format.sampleRate() * m_format.channels() * m_format.sampleSize() / 8;
|
||||||
|
if (m_bufferSize > 0 && bytesPerSecond > 0 && (m_bufferSize * 1000LL / bytesPerSecond < LowLatencyBufferSizeMs)) {
|
||||||
|
pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, "game");
|
||||||
|
} else {
|
||||||
|
pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, "music");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, m_category.toLatin1().constData());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_stream = pa_stream_new_with_proplist(pulseEngine->context(), m_streamName.constData(), &spec, 0, propList);
|
||||||
|
pa_proplist_free(propList);
|
||||||
|
|
||||||
pa_stream_set_state_callback(m_stream, outputStreamStateCallback, this);
|
pa_stream_set_state_callback(m_stream, outputStreamStateCallback, this);
|
||||||
pa_stream_set_write_callback(m_stream, outputStreamWriteCallback, this);
|
pa_stream_set_write_callback(m_stream, outputStreamWriteCallback, this);
|
||||||
@@ -619,6 +636,18 @@ qreal QPulseAudioOutput::volume() const
|
|||||||
return m_volume;
|
return m_volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QPulseAudioOutput::setCategory(const QString &category)
|
||||||
|
{
|
||||||
|
if (m_category != category) {
|
||||||
|
m_category = category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QPulseAudioOutput::category() const
|
||||||
|
{
|
||||||
|
return m_category;
|
||||||
|
}
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
#include "moc_qaudiooutput_pulse.cpp"
|
#include "moc_qaudiooutput_pulse.cpp"
|
||||||
|
|||||||
@@ -98,6 +98,9 @@ public:
|
|||||||
void setVolume(qreal volume);
|
void setVolume(qreal volume);
|
||||||
qreal volume() const;
|
qreal volume() const;
|
||||||
|
|
||||||
|
void setCategory(const QString &category);
|
||||||
|
QString category() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void streamUnderflowCallback();
|
void streamUnderflowCallback();
|
||||||
|
|
||||||
@@ -130,6 +133,7 @@ private:
|
|||||||
QTime m_timeStamp;
|
QTime m_timeStamp;
|
||||||
qint64 m_elapsedTimeOffset;
|
qint64 m_elapsedTimeOffset;
|
||||||
bool m_resuming;
|
bool m_resuming;
|
||||||
|
QString m_category;
|
||||||
|
|
||||||
qreal m_volume;
|
qreal m_volume;
|
||||||
pa_cvolume m_chVolume;
|
pa_cvolume m_chVolume;
|
||||||
|
|||||||
Reference in New Issue
Block a user