aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authortrialuser02 <trialuser02@90c681e8-e032-0410-971d-27865f9a5e38>2018-11-10 14:41:38 +0000
committertrialuser02 <trialuser02@90c681e8-e032-0410-971d-27865f9a5e38>2018-11-10 14:41:38 +0000
commitf75b313569a44f191c1214842a4f93c3319521ce (patch)
tree2e18e0f6ffe76c63b8b8c0abd9f957010b074fe6 /src
parente7cbfaf4e7b7573c25e440fbd17057dabe6e1b47 (diff)
downloadqmmp-f75b313569a44f191c1214842a4f93c3319521ce.tar.gz
qmmp-f75b313569a44f191c1214842a4f93c3319521ce.tar.bz2
qmmp-f75b313569a44f191c1214842a4f93c3319521ce.zip
added PulseAudio volume control (#497)
git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@8413 90c681e8-e032-0410-971d-27865f9a5e38
Diffstat (limited to 'src')
-rw-r--r--src/plugins/Output/pulseaudio/outputpulseaudio.cpp345
-rw-r--r--src/plugins/Output/pulseaudio/outputpulseaudio.h45
-rw-r--r--src/plugins/Output/pulseaudio/outputpulseaudiofactory.cpp4
-rw-r--r--src/plugins/Output/pulseaudio/outputpulseaudiofactory.h2
4 files changed, 347 insertions, 49 deletions
diff --git a/src/plugins/Output/pulseaudio/outputpulseaudio.cpp b/src/plugins/Output/pulseaudio/outputpulseaudio.cpp
index 690dd6b63..fbe42aaa2 100644
--- a/src/plugins/Output/pulseaudio/outputpulseaudio.cpp
+++ b/src/plugins/Output/pulseaudio/outputpulseaudio.cpp
@@ -1,5 +1,5 @@
/***************************************************************************
- * Copyright (C) 2006-2014 by Ilya Kotov *
+ * Copyright (C) 2006-2018 by Ilya Kotov *
* forkotov02@ya.ru *
* *
* This program is free software; you can redistribute it and/or modify *
@@ -21,11 +21,20 @@
extern "C"{
#include <pulse/error.h>
}
+#include <math.h>
+#include <QThread>
+#include <QMetaObject>
#include "outputpulseaudio.h"
+OutputPulseAudio *OutputPulseAudio::instance = 0;
+VolumePulseAudio *OutputPulseAudio::volumeControl = 0;
+
OutputPulseAudio::OutputPulseAudio(): Output()
{
- m_connection = 0;
+ //m_connection = 0;
+ m_loop = 0;
+ m_ctx = 0;
+ m_stream = 0;
m_pa_channels[Qmmp::CHAN_NULL] = PA_CHANNEL_POSITION_INVALID;
m_pa_channels[Qmmp::CHAN_FRONT_CENTER] = PA_CHANNEL_POSITION_MONO;
@@ -38,15 +47,48 @@ OutputPulseAudio::OutputPulseAudio(): Output()
m_pa_channels[Qmmp::CHAN_SIDE_LEFT] = PA_CHANNEL_POSITION_SIDE_LEFT;
m_pa_channels[Qmmp::CHAN_SIDE_RIGHT] = PA_CHANNEL_POSITION_SIDE_RIGHT;
m_pa_channels[Qmmp::CHAN_REAR_CENTER] = PA_CHANNEL_POSITION_REAR_CENTER;
+
+ instance = this;
}
OutputPulseAudio::~OutputPulseAudio()
{
uninitialize();
+ instance = 0;
}
bool OutputPulseAudio::initialize(quint32 freq, ChannelMap map, Qmmp::AudioFormat format)
{
+ if(!(m_loop = pa_mainloop_new()))
+ {
+ qWarning("OutputPulseAudio: unable to allocate a new main loop object");
+ return false;
+ }
+
+ if(!(m_ctx = pa_context_new(pa_mainloop_get_api(m_loop), "Qmmp")))
+ {
+ qWarning("OutputPulseAudio: unable to instantiate a new connection context");
+ return false;
+ }
+
+ if(pa_context_connect(m_ctx, 0, (pa_context_flags_t)0, 0) < 0)
+ {
+ qWarning("OutputPulseAudio: unable to connect the context: %s", pa_strerror(pa_context_errno(m_ctx)));
+ return false;
+ }
+
+ //waiting context connection
+ pa_context_state_t context_state = PA_CONTEXT_UNCONNECTED;
+ while((context_state = pa_context_get_state(m_ctx)) != PA_CONTEXT_READY)
+ {
+ if (context_state == PA_CONTEXT_TERMINATED || context_state == PA_CONTEXT_FAILED)
+ {
+ qWarning("OutputPulseAudio: unable to connect the context: %s", pa_strerror(pa_context_errno(m_ctx)));
+ return false;
+ }
+ poll();
+ }
+
pa_sample_spec ss;
switch (format)
@@ -69,82 +111,299 @@ bool OutputPulseAudio::initialize(quint32 freq, ChannelMap map, Qmmp::AudioForma
ss.channels = map.count();
ss.rate = freq;
- int error = 0;
pa_channel_map pa_map;
pa_map.channels = map.count();
for(int i = 0; i < map.count(); i++)
- {
pa_map.map[i] = m_pa_channels[map.value(i)];
+
+ if(!(m_stream = pa_stream_new(m_ctx, "Qmmp", &ss, &pa_map)))
+ {
+ qWarning("OutputPulseAudio: unable to create stream: %s", pa_strerror(pa_context_errno(m_ctx)));
+ return false;
}
- m_connection = pa_simple_new(NULL, // Use the default server.
- "Qmmp", // Our application's name.
- PA_STREAM_PLAYBACK,
- NULL, // Use the default device.
- "Music", // Description of our stream.
- &ss, // Our sample format.
- &pa_map, // Our channel map
- NULL, // Use default buffering attributes.
- &error // Error code.
- );
- if (!m_connection)
+ pa_buffer_attr attr;
+ attr.maxlength = (uint32_t) -1;
+ attr.tlength = (uint32_t) -1;//pa_usec_to_bytes((pa_usec_t)50 * 1000, &ss); //50 ms
+ attr.prebuf = (uint32_t) -1;
+ attr.minreq = (uint32_t) -1;
+ attr.fragsize = attr.tlength;
+
+ pa_stream_flags_t flags = pa_stream_flags_t(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE);
+ if(pa_stream_connect_playback(m_stream, 0, &attr, flags, 0, 0) < 0)
{
- qWarning("OutputPulseAudio: pa_simple_new() failed: %s", pa_strerror(error));
+ qWarning("OutputPulseAudio: unable to connect playback: %s", pa_strerror(pa_context_errno(m_ctx)));
return false;
}
+
+ //waiting stream connection
+ pa_stream_state_t stream_state = PA_STREAM_UNCONNECTED;
+ while((stream_state = pa_stream_get_state(m_stream)) != PA_STREAM_READY)
+ {
+ if (stream_state == PA_STREAM_FAILED || stream_state == PA_STREAM_TERMINATED)
+ {
+ qWarning("OutputPulseAudio: unable to connect playback: %s", pa_strerror(pa_context_errno(m_ctx)));
+ return false;
+ }
+ poll();
+ }
+
+ pa_context_set_subscribe_callback(m_ctx, OutputPulseAudio::subscribe_cb, this);
+
+ bool success = false;
+ pa_operation *op = pa_context_subscribe(m_ctx, PA_SUBSCRIPTION_MASK_SINK_INPUT, OutputPulseAudio::context_success_cb, &success);
+ if(!op)
+ {
+ qWarning("OutputPulseAudio: pa_context_subscribe failed: %s", pa_strerror(pa_context_errno(m_ctx)));
+ return false;
+ }
+
+ pa_operation_state_t op_state;
+ while((op_state = pa_operation_get_state(op)) != PA_OPERATION_DONE && isReady())
+ {
+ poll();
+ }
+ pa_operation_unref(op);
+
+ if(op_state != PA_OPERATION_DONE || !success)
+ {
+ qWarning("OutputPulseAudio: pa_context_subscribe failed: %s", pa_strerror(pa_context_errno(m_ctx)));
+ return false;
+ }
+ success = false;
+ if(!(op = pa_context_get_sink_input_info(m_ctx, pa_stream_get_index(m_stream), OutputPulseAudio::info_cb, &success)))
+ {
+ qWarning("OutputPulseAudio: pa_context_get_sink_input_info failed: %s", pa_strerror(pa_context_errno(m_ctx)));
+ return false;
+ }
+
+ while((op_state = pa_operation_get_state(op)) != PA_OPERATION_DONE && isReady())
+ {
+ poll();
+ }
+ pa_operation_unref(op);
+
+ if(op_state != PA_OPERATION_DONE || !success)
+ {
+ qWarning("OutputPulseAudio: pa_context_get_sink_input_info failed: %s", pa_strerror(pa_context_errno(m_ctx)));
+ return false;
+ }
+
Output::configure(freq, map, format);
return true;
}
qint64 OutputPulseAudio::latency()
{
- if (!m_connection)
- return 0;
- int error = 0;
- qint64 delay = pa_simple_get_latency(m_connection, &error)/1000;
- if (error)
- {
- qWarning("OutputPulseAudio: %s", pa_strerror (error));
- delay = 0;
- }
- return delay;
+ pa_usec_t usec;
+ int negative;
+
+ int r = pa_stream_get_latency(m_stream, &usec, &negative);
+ return (r == PA_OK && negative == 0) ? (usec / 1000) : 0;
}
qint64 OutputPulseAudio::writeAudio(unsigned char *data, qint64 maxSize)
{
- int error;
- if (!m_connection)
- return -1;
- int i = 0;
- if ((i = pa_simple_write(m_connection, data, maxSize, &error)) < 0)
+ /*bool success = false;
+ pa_operation *op = pa_stream_trigger(m_stream, OutputPulseAudio::stream_success_cb, &success);
+
+ while(pa_operation_get_state (op) != PA_OPERATION_DONE && isReady())
+ {
+ pa_mainloop_prepare(m_loop, -1);
+ pa_mainloop_poll(m_loop);
+ pa_mainloop_dispatch(m_loop);
+
+ }
+ pa_operation_unref (op);*/
+
+ while(!pa_stream_writable_size(m_stream) || !isReady())
+ {
+ poll();
+ }
+
+ size_t length = qMin(size_t(maxSize), pa_stream_writable_size(m_stream));
+ if(pa_stream_write(m_stream, data, length, 0, 0, PA_SEEK_RELATIVE) < 0)
{
- qWarning("OutputPulseAudio: pa_simple_write() failed: %s", pa_strerror(error));
+ qWarning("OutputPulseAudio: pa_stream_write failed: %s", pa_strerror(pa_context_errno(m_ctx)));
return -1;
}
- return maxSize;
+ return qint64(length);
}
void OutputPulseAudio::drain()
{
- int error;
- if (m_connection)
- pa_simple_drain(m_connection, &error);
+ pa_operation *op = pa_stream_drain(m_stream, OutputPulseAudio::stream_success_cb, 0);
+
+ while(pa_operation_get_state(op) != PA_OPERATION_DONE && isReady())
+ {
+ poll();
+ }
+ pa_operation_unref(op);
}
void OutputPulseAudio::reset()
{
- int error;
- if (m_connection)
- pa_simple_flush(m_connection, &error);
+ pa_operation *op = pa_stream_flush(m_stream, OutputPulseAudio::stream_success_cb, 0);
+
+ while(pa_operation_get_state(op) != PA_OPERATION_DONE && isReady())
+ {
+ poll();
+ }
+ pa_operation_unref(op);
+}
+
+void OutputPulseAudio::setVolume(const VolumeSettings &v)
+{
+ pa_cvolume volume;
+
+ if(audioParameters().channels() == 2)
+ {
+ volume.values[0] = v.left * PA_VOLUME_NORM / 100;
+ volume.values[1] = v.right * PA_VOLUME_NORM / 100;
+ volume.channels = 2;
+ }
+ else
+ {
+ for(int i = 0; i < audioParameters().channels(); ++i)
+ volume.values[i] = qMax(v.left, v.right) * PA_VOLUME_NORM / 100;
+ volume.channels = audioParameters().channels();
+ }
+
+ pa_operation *op = pa_context_set_sink_input_volume(m_ctx, pa_stream_get_index(m_stream), &volume, OutputPulseAudio::context_success_cb, 0);
+
+ /*while(pa_operation_get_state(op) != PA_OPERATION_DONE && isReady())
+ {
+ pa_mainloop_prepare(m_loop, -1);
+ pa_mainloop_poll(m_loop);
+ pa_mainloop_dispatch(m_loop);
+ }*/
+ pa_operation_unref(op);
}
void OutputPulseAudio::uninitialize()
+{
+ if(m_stream)
+ {
+ pa_stream_disconnect(m_stream);
+ pa_stream_unref(m_stream);
+ m_stream = 0;
+ }
+
+ if(m_ctx)
+ {
+ pa_context_disconnect(m_ctx);
+ pa_context_unref(m_ctx);
+ m_ctx = 0;
+ }
+
+ if(m_loop)
+ {
+ pa_mainloop_free(m_loop);
+ m_loop = 0;
+ }
+}
+
+bool OutputPulseAudio::isReady() const
+{
+ return m_ctx && m_stream &&
+ pa_context_get_state(m_ctx) == PA_CONTEXT_READY &&
+ pa_stream_get_state(m_stream) == PA_STREAM_READY;
+}
+
+void OutputPulseAudio::poll()
+{
+ pa_mainloop_prepare(m_loop, -1);
+ pa_mainloop_poll(m_loop);
+ pa_mainloop_dispatch(m_loop);
+}
+//callbacks
+void OutputPulseAudio::subscribe_cb(pa_context *ctx, pa_subscription_event_type t, uint32_t index, void *data)
+{
+ pa_operation *op;
+ OutputPulseAudio *output = (OutputPulseAudio *)data;
+
+ if (!output || !output->m_stream || index != pa_stream_get_index(output->m_stream) ||
+ (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_NEW) &&
+ t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_CHANGE)))
+ return;
+
+ if(!(op = pa_context_get_sink_input_info (ctx, index, OutputPulseAudio::info_cb, 0)))
+ {
+ qWarning("OutputPulseAudio: pa_context_get_sink_input_info failed: %s", pa_strerror(pa_context_errno(ctx)));
+ return;
+ }
+
+ pa_operation_unref(op);
+}
+
+void OutputPulseAudio::info_cb(pa_context *, const pa_sink_input_info *info, int, void *data)
{
- if (m_connection)
+ if(!info)
+ return;
+
+ if(volumeControl)
+ volumeControl->updateVolume(info->volume);
+
+ if(data)
+ *(bool *) data = true;
+}
+
+void OutputPulseAudio::context_success_cb(pa_context *, int success, void *data)
+{
+ if(data)
+ *(bool *)data = success != 0;
+}
+
+void OutputPulseAudio::stream_success_cb(pa_stream *, int success, void *data)
+{
+ if(data)
+ *(bool *)data = success != 0;
+}
+
+//volume control
+VolumePulseAudio::VolumePulseAudio()
+{
+ OutputPulseAudio::volumeControl = this;
+}
+
+VolumePulseAudio::~VolumePulseAudio()
+{
+ OutputPulseAudio::volumeControl = 0;
+}
+
+void VolumePulseAudio::updateVolume(const pa_cvolume &v)
+{
+ if(v.channels == 2)
{
- qDebug("OutputPulseAudio: closing connection");
- pa_simple_free(m_connection);
- m_connection = 0;
+ m_volume.left = ceilf(float(v.values[0]) * 100 / PA_VOLUME_NORM);
+ m_volume.right = ceilf(float(v.values[1]) * 100 / PA_VOLUME_NORM);
}
+ else
+ {
+ m_volume.left = ceilf(float(pa_cvolume_avg(&v)) * 100 / PA_VOLUME_NORM);
+ m_volume.right = m_volume.left;
+ }
+ emit changed();
+}
+
+void VolumePulseAudio::setVolume(const VolumeSettings &vol)
+{
+ if(OutputPulseAudio::instance)
+ OutputPulseAudio::instance->setVolume(vol);
+}
+
+VolumeSettings VolumePulseAudio::volume() const
+{
+ return m_volume;
+}
+
+void VolumePulseAudio::restore()
+{
+
+}
+
+bool VolumePulseAudio::hasNotifySignal() const
+{
+ return true;
}
diff --git a/src/plugins/Output/pulseaudio/outputpulseaudio.h b/src/plugins/Output/pulseaudio/outputpulseaudio.h
index 8ea4e69dd..7ca5b20d4 100644
--- a/src/plugins/Output/pulseaudio/outputpulseaudio.h
+++ b/src/plugins/Output/pulseaudio/outputpulseaudio.h
@@ -1,5 +1,5 @@
/***************************************************************************
- * Copyright (C) 2006-2014 by Ilya Kotov *
+ * Copyright (C) 2006-2018 by Ilya Kotov *
* forkotov02@ya.ru *
* *
* This program is free software; you can redistribute it and/or modify *
@@ -24,9 +24,12 @@
#include <QObject>
#include <QHash>
extern "C"{
-#include <pulse/simple.h>
+#include <pulse/pulseaudio.h>
}
#include <qmmp/output.h>
+#include <qmmp/volume.h>
+
+class VolumePulseAudio;
/**
@author Ilya Kotov <forkotov02@ya.ru>
@@ -43,12 +46,48 @@ public:
qint64 writeAudio(unsigned char *data, qint64 maxSize);
void drain();
void reset();
+ void setVolume(const VolumeSettings &v);
+
+ static OutputPulseAudio *instance;
+ static VolumePulseAudio *volumeControl;
private:
// helper functions
void uninitialize();
- pa_simple *m_connection;
+ bool isReady() const;
+ void poll();
+ //callbacks
+ static void subscribe_cb(pa_context *ctx, pa_subscription_event_type t, uint32_t index, void *data);
+ static void info_cb (pa_context *, const pa_sink_input_info * info, int, void * data);
+ static void context_success_cb (pa_context *, int success, void *data);
+ static void stream_success_cb (pa_stream *, int success, void *data);
+
+ //pa_simple *m_connection;
+ pa_mainloop *m_loop;
+ pa_context *m_ctx;
+ pa_stream *m_stream;
QHash <Qmmp::ChannelPosition, pa_channel_position_t> m_pa_channels;
+ bool changed = false;
+ bool flushed = false;
+};
+
+/**
+ @author Ilya Kotov <forkotov02@ya.ru>
+*/
+class VolumePulseAudio : public Volume
+{
+public:
+ VolumePulseAudio();
+ ~VolumePulseAudio();
+
+ void updateVolume(const pa_cvolume &v);
+ void setVolume(const VolumeSettings &vol);
+ VolumeSettings volume() const;
+ void restore();
+ bool hasNotifySignal() const;
+
+private:
+ VolumeSettings m_volume;
};
#endif // OUTPUTPULSEAUDIO_H
diff --git a/src/plugins/Output/pulseaudio/outputpulseaudiofactory.cpp b/src/plugins/Output/pulseaudio/outputpulseaudiofactory.cpp
index 049bc110a..35743a69c 100644
--- a/src/plugins/Output/pulseaudio/outputpulseaudiofactory.cpp
+++ b/src/plugins/Output/pulseaudio/outputpulseaudiofactory.cpp
@@ -1,5 +1,5 @@
/***************************************************************************
- * Copyright (C) 2007-2015 by Ilya Kotov *
+ * Copyright (C) 2007-2018 by Ilya Kotov *
* forkotov02@ya.ru *
* *
* This program is free software; you can redistribute it and/or modify *
@@ -41,7 +41,7 @@ Output* OutputPulseAudioFactory::create()
Volume *OutputPulseAudioFactory::createVolume()
{
- return 0;
+ return new VolumePulseAudio;
}
void OutputPulseAudioFactory::showSettings(QWidget* parent)
diff --git a/src/plugins/Output/pulseaudio/outputpulseaudiofactory.h b/src/plugins/Output/pulseaudio/outputpulseaudiofactory.h
index 3628b2f2b..1a029c88e 100644
--- a/src/plugins/Output/pulseaudio/outputpulseaudiofactory.h
+++ b/src/plugins/Output/pulseaudio/outputpulseaudiofactory.h
@@ -1,5 +1,5 @@
/***************************************************************************
- * Copyright (C) 2007-2012 by Ilya Kotov *
+ * Copyright (C) 2007-2018 by Ilya Kotov *
* forkotov02@ya.ru *
* *
* This program is free software; you can redistribute it and/or modify *