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();
}
/*!
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)
This signal is emitted when the device \a state has changed.

View File

@@ -100,6 +100,9 @@ public:
void setVolume(qreal);
qreal volume() const;
QString category() const;
void setCategory(const QString &category);
Q_SIGNALS:
void stateChanged(QAudio::State);
void notify();

View File

@@ -97,6 +97,8 @@ public:
virtual QAudioFormat format() const = 0;
virtual void setVolume(qreal) {}
virtual qreal volume() const { return 1.0; }
virtual QString category() const { return QString(); }
virtual void setCategory(const QString &) { }
Q_SIGNALS:
void errorChanged(QAudio::Error);

View File

@@ -218,6 +218,7 @@ QSoundEffect::QSoundEffect(QObject *parent) :
connect(d, SIGNAL(loadedChanged()), SIGNAL(loadedChanged()));
connect(d, SIGNAL(playingChanged()), SIGNAL(playingChanged()));
connect(d, SIGNAL(statusChanged()), SIGNAL(statusChanged()));
connect(d, SIGNAL(categoryChanged()), SIGNAL(categoryChanged()));
}
/*!
@@ -366,6 +367,57 @@ QSoundEffect::Status QSoundEffect::status() const
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()

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 playing READ isPlaying NOTIFY playingChanged)
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
Q_PROPERTY(QString category READ category WRITE setCategory NOTIFY categoryChanged)
Q_ENUMS(Loop)
Q_ENUMS(Status)
@@ -108,6 +109,9 @@ public:
bool isPlaying() const;
Status status() const;
QString category() const;
void setCategory(const QString &category);
Q_SIGNALS:
void sourceChanged();
void loopCountChanged();
@@ -117,6 +121,7 @@ Q_SIGNALS:
void loadedChanged();
void playingChanged();
void statusChanged();
void categoryChanged();
public Q_SLOTS:
void play();

View File

@@ -351,7 +351,8 @@ QSoundEffectPrivate::QSoundEffectPrivate(QObject* parent):
m_volume(100),
m_loopCount(1),
m_runningCount(0),
m_sample(0) ,
m_sample(0),
m_reloadCategory(false),
m_position(0)
{
m_ref = new QSoundEffectRef(this);
@@ -373,6 +374,32 @@ void QSoundEffectPrivate::release()
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()
{
m_ref->release();
@@ -700,6 +727,7 @@ void QSoundEffectPrivate::unloadPulseStream()
pa_stream_unref(m_pulseStream);
disconnect(pulseDaemon(), SIGNAL(volumeChanged()), this, SLOT(updateVolume()));
m_pulseStream = 0;
m_reloadCategory = false; // category will be reloaded when we connect anyway
}
}
@@ -808,11 +836,16 @@ void QSoundEffectPrivate::stop()
setPlaying(false);
PulseDaemonLocker locker;
m_stopping = true;
if (m_pulseStream)
if (m_pulseStream) {
emptyStream();
if (m_reloadCategory) {
unloadPulseStream(); // upon play we reconnect anyway
}
}
setLoopsRemaining(0);
m_position = 0;
m_playQueued = false;
m_reloadCategory = false;
}
void QSoundEffectPrivate::underRun()
@@ -846,7 +879,12 @@ void QSoundEffectPrivate::createPulseStream()
#endif
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_proplist_free(propList);

View File

@@ -94,6 +94,9 @@ public:
void release();
QString category() const;
void setCategory(const QString &category);
public Q_SLOTS:
void play();
void stop();
@@ -105,6 +108,7 @@ Q_SIGNALS:
void loadedChanged();
void playingChanged();
void statusChanged();
void categoryChanged();
private Q_SLOTS:
void decoderError();
@@ -157,6 +161,8 @@ private:
int m_runningCount;
QUrl m_source;
QByteArray m_name;
QString m_category;
bool m_reloadCategory;
QSample *m_sample;
int m_position;

View File

@@ -250,6 +250,20 @@ void QSoundEffectPrivate::setLoopsRemaining(int loopsRemaining)
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
#include "moc_qsoundeffect_qmedia_p.cpp"

View File

@@ -91,6 +91,10 @@ public:
void release();
// Categories are not really supported with QMediaPlayer
QString category() const;
void setCategory(const QString &);
public Q_SLOTS:
void play();
void stop();
@@ -102,6 +106,7 @@ Q_SIGNALS:
void loadedChanged();
void playingChanged();
void statusChanged();
void categoryChanged();
private Q_SLOTS:
void stateChanged(QMediaPlayer::State);
@@ -118,6 +123,7 @@ private:
bool m_playing;
QSoundEffect::Status m_status;
QMediaPlayer *m_player;
QString m_category;
};
QT_END_NAMESPACE

View File

@@ -53,6 +53,7 @@
QT_BEGIN_NAMESPACE
const int PeriodTimeMs = 20;
const int LowLatencyBufferSizeMs = 40;
static void outputStreamWriteCallback(pa_stream *stream, size_t length, void *userdata)
{
@@ -267,7 +268,23 @@ bool QPulseAudioOutput::open()
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
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_write_callback(m_stream, outputStreamWriteCallback, this);
@@ -619,6 +636,18 @@ qreal QPulseAudioOutput::volume() const
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
#include "moc_qaudiooutput_pulse.cpp"

View File

@@ -98,6 +98,9 @@ public:
void setVolume(qreal volume);
qreal volume() const;
void setCategory(const QString &category);
QString category() const;
public:
void streamUnderflowCallback();
@@ -130,6 +133,7 @@ private:
QTime m_timeStamp;
qint64 m_elapsedTimeOffset;
bool m_resuming;
QString m_category;
qreal m_volume;
pa_cvolume m_chVolume;