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:
Michael Goddard
2012-04-13 13:51:09 +10:00
committed by Qt by Nokia
parent 8441d2e32e
commit 5f7b64346d
11 changed files with 198 additions and 4 deletions

View File

@@ -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.

View File

@@ -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();

View File

@@ -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);

View File

@@ -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()

View File

@@ -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();

View File

@@ -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);

View File

@@ -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;

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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;