summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorMatt Porter <mporter@konsulko.com>2016-12-19 13:55:11 -0500
committerMatt Porter <mporter@konsulko.com>2016-12-21 08:24:39 -0500
commit392effc544e3d94b82f806378d4ac1d11a185422 (patch)
tree6467743066dd6153941529087fef244dc8639c24 /app
AGL-style PulseAudio mixer app
Change-Id: I566050a1a8f241f140523df236de81ab951c1394 Signed-off-by: Matt Porter <mporter@konsulko.com>
Diffstat (limited to 'app')
-rw-r--r--app/Mixer.qml93
-rw-r--r--app/Mixer.qrc5
-rw-r--r--app/app.pri12
-rw-r--r--app/app.pro18
-rw-r--r--app/main.cpp56
-rw-r--r--app/pac.c225
-rw-r--r--app/pac.h54
-rw-r--r--app/pacontrolmodel.cpp172
-rw-r--r--app/pacontrolmodel.h87
9 files changed, 722 insertions, 0 deletions
diff --git a/app/Mixer.qml b/app/Mixer.qml
new file mode 100644
index 0000000..7a099ce
--- /dev/null
+++ b/app/Mixer.qml
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2016 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import QtQuick 2.6
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 2.0
+import AGL.Demo.Controls 1.0
+import PaControlModel 1.0
+
+ApplicationWindow {
+ id: root
+
+ Label {
+ id: title
+ font.pixelSize: 48
+ text: "Mixer"
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+ Component {
+ id: ctldesc
+ Label {
+ font.pixelSize: 32
+ width: listView.width
+ wrapMode: Text.WordWrap
+ property var typeString: {modelType ? "Output" : "Input"}
+ text: "[" + typeString + " #" + modelCIndex + "]: " + modelDesc
+ }
+ }
+
+ Component {
+ id: empty
+ Item {
+ }
+ }
+
+ ListView {
+ id: listView
+ anchors.left: parent.left
+ anchors.top: title.bottom
+ anchors.margins: 80
+ anchors.fill: parent
+ model: PaControlModel {}
+ delegate: ColumnLayout {
+ width: parent.width
+ spacing: 40
+ Loader {
+ property int modelType: type
+ property int modelCIndex: cindex
+ property string modelDesc: desc
+ sourceComponent: (channel == 0) ? ctldesc : empty
+ }
+ RowLayout {
+ Layout.minimumHeight: 75
+ Label {
+ font.pixelSize: 24
+ text: cdesc
+ Layout.minimumWidth: 150
+ }
+ Label {
+ font.pixelSize: 24
+ text: "0 %"
+ }
+ Slider {
+ Layout.fillWidth: true
+ from: 0
+ to: 65536
+ stepSize: 256
+ snapMode: Slider.SnapOnRelease
+ onValueChanged: volume = value
+ Component.onCompleted: value = volume
+ }
+ Label {
+ font.pixelSize: 24
+ text: "100 %"
+ }
+ }
+ }
+ }
+}
diff --git a/app/Mixer.qrc b/app/Mixer.qrc
new file mode 100644
index 0000000..27f216c
--- /dev/null
+++ b/app/Mixer.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>Mixer.qml</file>
+ </qresource>
+</RCC>
diff --git a/app/app.pri b/app/app.pri
new file mode 100644
index 0000000..014646f
--- /dev/null
+++ b/app/app.pri
@@ -0,0 +1,12 @@
+TEMPLATE = app
+
+load(configure)
+qtCompileTest(libhomescreen)
+
+config_libhomescreen {
+ CONFIG += link_pkgconfig
+ PKGCONFIG += homescreen
+ DEFINES += HAVE_LIBHOMESCREEN
+}
+
+DESTDIR = $${OUT_PWD}/../package/root/bin
diff --git a/app/app.pro b/app/app.pro
new file mode 100644
index 0000000..e8fe05b
--- /dev/null
+++ b/app/app.pro
@@ -0,0 +1,18 @@
+TARGET = mixer
+QT = quickcontrols2
+
+HEADERS += \
+ pacontrolmodel.h \
+ pac.h
+
+SOURCES = main.cpp \
+ pacontrolmodel.cpp \
+ pac.c
+
+CONFIG += link_pkgconfig
+PKGCONFIG += libpulse
+
+RESOURCES += \
+ Mixer.qrc
+
+include(app.pri)
diff --git a/app/main.cpp b/app/main.cpp
new file mode 100644
index 0000000..d89287c
--- /dev/null
+++ b/app/main.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ * Copyright (C) 2016 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <QtCore/QDebug>
+#include <QtCore/QDir>
+#include <QtCore/QStandardPaths>
+#include <QtGui/QGuiApplication>
+#include <QtQml/QQmlApplicationEngine>
+#include <QtQml/QQmlContext>
+#include <QtQml/qqml.h>
+#include <QtQuickControls2/QQuickStyle>
+#include <QtQuick/qquickitem.h>
+#include <QtQuick/qquickview.h>
+
+#include "pacontrolmodel.h"
+
+#ifdef HAVE_LIBHOMESCREEN
+#include <libhomescreen.hpp>
+#endif
+
+int main(int argc, char *argv[])
+{
+#ifdef HAVE_LIBHOMESCREEN
+ LibHomeScreen libHomeScreen;
+
+ if (!libHomeScreen.renderAppToAreaAllowed(0, 1)) {
+ qWarning() << "renderAppToAreaAllowed is denied";
+ return -1;
+ }
+#endif
+
+ QGuiApplication app(argc, argv);
+
+ QQuickStyle::setStyle("AGL");
+
+ qmlRegisterType<PaControlModel>("PaControlModel", 1, 0, "PaControlModel");
+
+ QQmlApplicationEngine engine;
+ engine.load(QUrl(QStringLiteral("qrc:/Mixer.qml")));
+
+ return app.exec();
+}
diff --git a/app/pac.c b/app/pac.c
new file mode 100644
index 0000000..83b4604
--- /dev/null
+++ b/app/pac.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2016 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <errno.h>
+#include <unistd.h>
+#include <limits.h>
+#include <assert.h>
+
+#include <pulse/pulseaudio.h>
+#include <sys/queue.h>
+
+#include "pacontrolmodel.h"
+#include "pac.h"
+
+/* FIXME: move these into a pac context */
+static pa_threaded_mainloop* m;
+TAILQ_HEAD(pac_cstateq, pac_cstate);
+static struct pac_cstateq cstateq;
+
+static void add_one_cstate(int type, int index, const pa_cvolume *cvolume)
+{
+ struct pac_cstate *cstate;
+ int i;
+
+ cstate = pa_xnew(struct pac_cstate, 1);
+ cstate->type = type;
+ cstate->index = index;
+ cstate->cvolume.channels = cvolume->channels;
+ for (i = 0; i < cvolume->channels; i++)
+ cstate->cvolume.values[i] = cvolume->values[i];
+
+ TAILQ_INSERT_TAIL(&cstateq, cstate, tailq);
+}
+
+static void get_source_list_cb(pa_context *c,
+ const pa_source_info *i,
+ int eol,
+ void *data)
+{
+ int chan;
+
+ if (eol < 0) {
+ fprintf(stderr, "get source list: %s\n",
+ pa_strerror(pa_context_errno(c)));
+
+ pa_threaded_mainloop_stop(m);
+ return;
+ }
+
+ if (!eol) {
+ assert(i);
+ add_one_cstate(C_SOURCE, i->index, &i->volume);
+ for (chan = 0; chan < i->channel_map.channels; chan++) {
+ add_one_control(data, i->index, i->description,
+ C_SOURCE, chan,
+ channel_position_string[i->channel_map.map[chan]],
+ i->volume.values[chan]);
+ }
+ }
+}
+
+static void get_sink_list_cb(pa_context *c,
+ const pa_sink_info *i,
+ int eol,
+ void *data)
+{
+ int chan;
+
+ if(eol < 0) {
+ fprintf(stderr, "get sink list: %s\n",
+ pa_strerror(pa_context_errno(c)));
+
+ pa_threaded_mainloop_stop(m);
+ return;
+ }
+
+ if(!eol) {
+ assert(i);
+ add_one_cstate(C_SINK, i->index, &i->volume);
+ for (chan = 0; chan < i->channel_map.channels; chan++) {
+ add_one_control(data, i->index, i->description,
+ C_SINK, chan,
+ channel_position_string[i->channel_map.map[chan]],
+ i->volume.values[chan]);
+ }
+ }
+}
+
+static void context_state_cb(pa_context *c, void *data) {
+ pa_operation *o;
+
+ assert(c);
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ case PA_CONTEXT_READY:
+ /* Fetch the controls of interest */
+ if (!(o = pa_context_get_source_info_list(c, get_source_list_cb, data))) {
+ fprintf(stderr, "get source info list: %s\n",
+ pa_strerror(pa_context_errno(c)));
+ return;
+ }
+ pa_operation_unref(o);
+ if (!(o = pa_context_get_sink_info_list(c, get_sink_list_cb, data))) {
+ fprintf(stderr, "get sink info list: %s\n",
+ pa_strerror(pa_context_errno(c)));
+ return;
+ }
+ break;
+ case PA_CONTEXT_TERMINATED:
+ pa_threaded_mainloop_stop(m);
+ break;
+ case PA_CONTEXT_FAILED:
+ default:
+ fprintf(stderr, "PA connection failed: %s\n",
+ pa_strerror(pa_context_errno(c)));
+ pa_threaded_mainloop_stop(m);
+ }
+}
+
+static void pac_set_source_volume_cb(pa_context *c, int success, void *userdata __attribute__((unused))) {
+ assert(c);
+ if (!success)
+ fprintf(stderr, "Set source volume: %s\n",
+ pa_strerror(pa_context_errno(c)));
+}
+
+static void pac_set_sink_volume_cb(pa_context *c, int success, void *userdata __attribute__((unused))) {
+ assert(c);
+ if (!success)
+ fprintf(stderr, "Set source volume: %s\n",
+ pa_strerror(pa_context_errno(c)));
+}
+
+void pac_set_volume(pa_context *c, uint32_t type, uint32_t idx, uint32_t channel, uint32_t volume)
+{
+ pa_operation *o;
+ struct pac_cstate *cstate;
+
+ TAILQ_FOREACH(cstate, &cstateq, tailq)
+ if (cstate->index == idx)
+ break;
+ cstate->cvolume.values[channel] = volume;
+
+ if (type == C_SOURCE) {
+ if (!(o = pa_context_set_source_volume_by_index(c, idx, &cstate->cvolume, pac_set_source_volume_cb, NULL))) {
+ fprintf(stderr, "set source #%d channel #%d volume: %s\n",
+ idx, channel, pa_strerror(pa_context_errno(c)));
+ return;
+ }
+ pa_operation_unref(o);
+ } else if (type == C_SINK) {
+ if (!(o = pa_context_set_sink_volume_by_index(c, idx, &cstate->cvolume, pac_set_sink_volume_cb, NULL))) {
+ fprintf(stderr, "set sink #%d channel #%d volume: %s\n",
+ idx, channel, pa_strerror(pa_context_errno(c)));
+ return;
+ }
+ pa_operation_unref(o);
+ }
+}
+
+pa_context *pac_init(void *this, const char *name) {
+ pa_context *c;
+ pa_mainloop_api *mapi;
+ char *server = NULL;
+ char *client = pa_xstrdup(name);
+
+ TAILQ_INIT(&cstateq);
+
+ if (!(m = pa_threaded_mainloop_new())) {
+ fprintf(stderr, "pa_mainloop_new() failed.\n");
+ return NULL;
+ }
+
+ pa_threaded_mainloop_set_name(m, "pa_mainloop");
+ mapi = pa_threaded_mainloop_get_api(m);
+
+ if (!(c = pa_context_new(mapi, client))) {
+ fprintf(stderr, "pa_context_new() failed.\n");
+ goto exit;
+ }
+
+ pa_context_set_state_callback(c, context_state_cb, this);
+ if (pa_context_connect(c, server, 0, NULL) < 0) {
+ fprintf(stderr, "pa_context_connect(): %s", pa_strerror(pa_context_errno(c)));
+ goto exit;
+ }
+
+ if (pa_threaded_mainloop_start(m) < 0) {
+ fprintf(stderr, "pa_mainloop_run() failed.\n");
+ goto exit;
+ }
+
+ return c;
+
+exit:
+ if (c)
+ pa_context_unref(c);
+
+ if (m)
+ pa_threaded_mainloop_free(m);
+
+ pa_xfree(client);
+
+ return NULL;
+}
diff --git a/app/pac.h b/app/pac.h
new file mode 100644
index 0000000..ef0209d
--- /dev/null
+++ b/app/pac.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <pulse/pulseaudio.h>
+#include <sys/queue.h>
+
+#ifdef __cplusplus
+extern "C" void pac_set_volume(pa_context *, uint32_t, uint32_t, uint32_t, uint32_t);
+extern "C" pa_context *pac_init(void *, const char *);
+#else
+static char * channel_position_string[] =
+{
+ "Mono",
+ "Front Left",
+ "Front Right",
+ "Center",
+ "Rear Center",
+ "Rear Left",
+ "Rear Right",
+ "LFE",
+ "Left Center",
+ "Right Center",
+ "Side Left",
+ "Side Right",
+};
+
+enum control_type
+{
+ C_SOURCE,
+ C_SINK
+};
+
+struct pac_cstate
+{
+ TAILQ_ENTRY(pac_cstate) tailq;
+ int type;
+ uint32_t index;
+ pa_cvolume cvolume;
+};
+
+#endif
diff --git a/app/pacontrolmodel.cpp b/app/pacontrolmodel.cpp
new file mode 100644
index 0000000..520233b
--- /dev/null
+++ b/app/pacontrolmodel.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2016 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pacontrolmodel.h"
+#include "pac.h"
+
+PaControl::PaControl(const quint32 &cindex, const QString &desc, const quint32 &type, const quint32 &channel, const QString &cdesc, const quint32 &volume)
+ : m_cindex(cindex), m_desc(desc), m_type(type), m_channel(channel), m_cdesc(cdesc), m_volume(volume)
+{
+}
+
+quint32 PaControl::cindex() const
+{
+ return m_cindex;
+}
+
+QString PaControl::desc() const
+{
+ return m_desc;
+}
+
+quint32 PaControl::type() const
+{
+ return m_type;
+}
+
+quint32 PaControl::channel() const
+{
+ return m_channel;
+}
+
+QString PaControl::cdesc() const
+{
+ return m_cdesc;
+}
+
+
+quint32 PaControl::volume() const
+{
+ return m_volume;
+}
+
+// FIXME: Not all of these should be editable roles
+void PaControl::setCIndex(const QVariant &cindex)
+{
+ m_cindex = cindex.toUInt();
+}
+
+void PaControl::setDesc(const QVariant &desc)
+{
+ m_desc = desc.toString();
+}
+
+void PaControl::setType(const QVariant &type)
+{
+ m_type = type.toUInt();
+}
+
+void PaControl::setChannel(const QVariant &channel)
+{
+ m_channel = channel.toUInt();
+}
+
+void PaControl::setCDesc(const QVariant &cdesc)
+{
+ m_cdesc = cdesc.toString();
+}
+
+void PaControl::setVolume(pa_context *pa_ctx, const QVariant &volume)
+{
+ if (volume != m_volume) {
+ m_volume = volume.toUInt();
+ pac_set_volume(pa_ctx, type(), cindex(), channel(), m_volume);
+ }
+}
+
+PaControlModel::PaControlModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+ pa_ctx = pac_init(this, "Mixer");
+}
+
+void PaControlModel::addControl(const PaControl &control)
+{
+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
+ m_controls << control;
+ endInsertRows();
+}
+
+void add_one_control(void *ctx, int cindex, const char *desc, int type, int channel, const char *cdesc, int volume)
+{
+ // Get the PaControlModel object from the opaque pointer context
+ PaControlModel *pacm = static_cast<PaControlModel*>(ctx);
+ pacm->addControl(PaControl(cindex, desc, type, channel, cdesc, volume));
+}
+
+int PaControlModel::rowCount(const QModelIndex & parent) const {
+ Q_UNUSED(parent);
+ return m_controls.count();
+}
+
+bool PaControlModel::setData(const QModelIndex &index, const QVariant &value, int role) {
+ if (index.row() < 0 || index.row() >= m_controls.count())
+ return false;
+ PaControl &control = m_controls[index.row()];
+ if (role == CIndexRole)
+ control.setCIndex(value);
+ else if (role == DescRole)
+ control.setDesc(value);
+ else if (role == TypeRole)
+ control.setType(value);
+ else if (role == ChannelRole)
+ control.setChannel(value);
+ else if (role == CDescRole)
+ control.setCDesc(value);
+ else if (role == VolumeRole)
+ control.setVolume(pa_ctx, value);
+ emit dataChanged(index, index);
+ return true;
+}
+
+QVariant PaControlModel::data(const QModelIndex & index, int role) const {
+ if (index.row() < 0 || index.row() >= m_controls.count())
+ return QVariant();
+
+ const PaControl &control = m_controls[index.row()];
+ if (role == CIndexRole)
+ return control.cindex();
+ else if (role == DescRole)
+ return control.desc();
+ else if (role == TypeRole)
+ return control.type();
+ else if (role == ChannelRole)
+ return control.channel();
+ else if (role == CDescRole)
+ return control.cdesc();
+ else if (role == VolumeRole)
+ return control.volume();
+ return QVariant();
+}
+
+Qt::ItemFlags PaControlModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return Qt::ItemIsEnabled;
+
+ return QAbstractListModel::flags(index) | Qt::ItemIsEditable;
+}
+
+QHash<int, QByteArray> PaControlModel::roleNames() const {
+ QHash<int, QByteArray> roles;
+ roles[CIndexRole] = "cindex";
+ roles[DescRole] = "desc";
+ roles[TypeRole] = "type";
+ roles[ChannelRole] = "channel";
+ roles[CDescRole] = "cdesc";
+ roles[VolumeRole] = "volume";
+ return roles;
+}
diff --git a/app/pacontrolmodel.h b/app/pacontrolmodel.h
new file mode 100644
index 0000000..aa34a79
--- /dev/null
+++ b/app/pacontrolmodel.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 Konsulko Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <pulse/pulseaudio.h>
+
+#ifndef __cplusplus
+extern void add_one_control(void *ctx, int, const char *, int, int, const char *, int);
+#else
+extern "C" void add_one_control(void *ctx, int, const char *, int, int, const char *, int);
+
+#include <QAbstractListModel>
+#include <QStringList>
+
+class PaControlModel;
+
+class PaControl
+{
+ public:
+ PaControl(const quint32 &index, const QString &desc, const quint32 &type, const quint32 &channel, const QString &cdesc, const quint32 &volume);
+
+ quint32 cindex() const;
+ QString desc() const;
+ quint32 type() const;
+ quint32 channel() const;
+ QString cdesc() const;
+ quint32 volume() const;
+ void setCIndex(const QVariant&);
+ void setDesc(const QVariant&);
+ void setType(const QVariant&);
+ void setChannel(const QVariant&);
+ void setCDesc(const QVariant&);
+ void setVolume(pa_context *, const QVariant&);
+
+ private:
+ quint32 m_cindex;
+ QString m_desc;
+ quint32 m_type;
+ quint32 m_channel;
+ QString m_cdesc;
+ quint32 m_volume;
+};
+
+class PaControlModel : public QAbstractListModel
+{
+ Q_OBJECT
+ public:
+ enum PaControlRoles {
+ CIndexRole = Qt::UserRole + 1,
+ DescRole,
+ TypeRole,
+ ChannelRole,
+ CDescRole,
+ VolumeRole
+ };
+
+ PaControlModel(QObject *parent = 0);
+
+ void addControl(const PaControl &control);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+
+ protected:
+ QHash<int, QByteArray> roleNames() const;
+ private:
+ QList<PaControl> m_controls;
+ pa_context *pa_ctx;
+};
+#endif // __cplusplus