summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJooncheol Park <jooncheol.park@windriver.com>2015-04-15 19:42:24 +0900
committerJooncheol Park <jooncheol.park@windriver.com>2015-04-15 19:42:24 +0900
commit7290b39664f87dd8a3bf81589749f65694ea61f8 (patch)
tree1c8fb18abe93ef520f530058199c5d304e798487
downloadaudiomanagerdemo-7290b39664f87dd8a3bf81589749f65694ea61f8.tar.gz
Initial commit for AM Monitor
-rw-r--r--AudioManagerMonitor.pro36
-rw-r--r--Button.qml96
-rw-r--r--Diagram.qml675
-rw-r--r--Graph.qml264
-rw-r--r--README16
-rwxr-xr-xaudio/car_reverse.wavbin0 -> 1801796 bytes
-rwxr-xr-xaudio/media.wavbin0 -> 9580594 bytes
-rwxr-xr-xaudio/navigation.wavbin0 -> 328370 bytes
-rwxr-xr-xaudio/telephone-ring.wavbin0 -> 4894540 bytes
-rwxr-xr-xaudio/tts.wavbin0 -> 2051370 bytes
-rw-r--r--audiomanagerdbusinterface.cpp635
-rw-r--r--audiomanagerdbusinterface.h236
-rw-r--r--code.js376
-rw-r--r--commandlist.txt21
-rw-r--r--deployment.pri27
-rw-r--r--images/genivi-logo.pngbin0 -> 156183 bytes
-rw-r--r--images/icon_car.pngbin0 -> 2542 bytes
-rw-r--r--images/icon_microphone.pngbin0 -> 3292 bytes
-rw-r--r--images/icon_music.pngbin0 -> 2727 bytes
-rw-r--r--images/icon_phone.pngbin0 -> 2611 bytes
-rw-r--r--images/icon_reply.pngbin0 -> 2430 bytes
-rw-r--r--images/pulseaudio-logo.pngbin0 -> 15316 bytes
-rw-r--r--images/speaker.pngbin0 -> 3035 bytes
-rw-r--r--images/volume-up.pngbin0 -> 3119 bytes
-rw-r--r--images/windriver-logo.pngbin0 -> 8978 bytes
-rw-r--r--main.cpp56
-rw-r--r--main.qml99
-rw-r--r--pc-env22
-rw-r--r--pulseaudiocontroller.cpp274
-rw-r--r--pulseaudiocontroller.h94
-rw-r--r--pulseplayer.cpp347
-rw-r--r--pulseplayer.h132
-rw-r--r--qml.qrc24
33 files changed, 3430 insertions, 0 deletions
diff --git a/AudioManagerMonitor.pro b/AudioManagerMonitor.pro
new file mode 100644
index 0000000..e881732
--- /dev/null
+++ b/AudioManagerMonitor.pro
@@ -0,0 +1,36 @@
+TEMPLATE = app
+
+QT += qml quick dbus
+CONFIG += link_pkgconfig
+
+CONFIG += c++11
+SOURCES += main.cpp \
+ pulseaudiocontroller.cpp \
+ pulseplayer.cpp \
+ audiomanagerdbusinterface.cpp \
+
+RESOURCES += qml.qrc
+
+QML_IMPORT_PATH =
+
+# Default rules for deployment.
+include(deployment.pri)
+
+FORMS +=
+
+HEADERS += \
+ pulseaudiocontroller.h \
+ pulseplayer.h \
+ audiomanagerdbusinterface.h \
+
+
+#unix:!macx: LIBS += -L$$PWD/../../../../../../usr/lib/x86_64-linux-gnu/ -lpulse
+#INCLUDEPATH += $$PWD/../../../../../../usr/include
+#DEPENDPATH += $$PWD/../../../../../../usr/include
+#unix:!macx: LIBS += -lpulse-mainloop-glib -lpulse-simple
+
+PKGCONFIG += libpulse
+
+
+DISTFILES += \
+ commandlist.txt
diff --git a/Button.qml b/Button.qml
new file mode 100644
index 0000000..2607ccb
--- /dev/null
+++ b/Button.qml
@@ -0,0 +1,96 @@
+import QtQuick 2.0
+import com.windriver.ammonitor 1.0
+
+Item {
+ id :button
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: 100
+ height: 100
+ property bool pressed: hitbox.pressed && hitbox.containsMouse
+
+ property var amCommandIF
+ property string amSource
+ property string amSink
+ property string mediaRole
+ property string audioFilePath
+ property int connectionID : 0
+ property string iconName
+ property string title : "no title"
+
+ Rectangle {
+ anchors.fill: parent
+ anchors.margins: 4
+ color: paplayer.playing?"darkGray":"lightGray"
+ radius: 10
+ border.width: 1
+ border.color: "gray"
+
+ Image {
+ id: icon
+ height: (pressed||paplayer.playing)?(parent.height/3):(parent.height/2)
+ fillMode: Image.PreserveAspectFit
+ anchors.horizontalCenter: parent.horizontalCenter
+ y: (parent.height - (height + titleText.height)) / 2
+ source: "images/icon_"+iconName+".png"
+ }
+ Image {
+ height: parent.height / 5
+ fillMode: Image.PreserveAspectFit
+ x: parent.width - width - (width/2)
+ y: (height/2)
+ source: "images/volume-up.png"
+ visible: paplayer.playing
+ }
+ Text {
+ id: titleText
+ anchors.top: icon.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: title
+ font.pixelSize: parent.height / 6
+ }
+ Text {
+ id: descText
+ visible: pressed||paplayer.playing
+ anchors.top: titleText.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ //text: "Connecting '"+amSource+"' to '"+amSink+"'"
+ text: amSource+" => "+amSink
+ font.pixelSize: parent.height / 10
+ }
+ PAPlayer {
+ id: paplayer
+ role: mediaRole
+ file: audioFilePath
+ source: amSource
+ sink: amSink
+ onPlayStateChanged: {
+ if(!playing && button.connectionID != 0) {
+ console.log('11 trying to disconnect AM connection: '+button.connectionID)
+ amCommandIF.disconnect(button.connectionID);
+ button.connectionID = 0
+ }
+ }
+ }
+
+ MouseArea {
+ id: hitbox
+ anchors.fill: parent
+ onClicked : {
+ if(paplayer.isPlaying()) {
+ console.log('trying to disconnect AM connection: '+button.connectionID)
+ if(button.connectionID != 0) {
+ if(amCommandIF.disconnect(button.connectionID))
+ paplayer.stop()
+ button.connectionID = 0
+ }
+ } else {
+ button.connectionID = amCommandIF.connect(amSource, amSink);
+ console.log('AM Connected: '+button.connectionID)
+ if(button.connectionID > 0)
+ paplayer.play()
+ }
+ }
+ }
+ }
+}
+
diff --git a/Diagram.qml b/Diagram.qml
new file mode 100644
index 0000000..055ba7f
--- /dev/null
+++ b/Diagram.qml
@@ -0,0 +1,675 @@
+import QtQuick 2.1
+import QtQuick.Window 2.0
+import com.windriver.ammonitor 1.0
+import "code.js" as Code
+
+Item {
+ id: mainBox
+
+ PAClient {
+ id: paClient
+ signal sinkInputProcessed(int processType, int index)
+ signal sinkInfoProcessed(int processType, int index)
+
+ onSinkInputChanged : {
+
+ if (sinkinput.role && sinkinput.role != "event" && sinkinput.role != "filter") {
+ console.log('onSinkInputChanged '+sinkinput.index+' Volume'+sinkinput.volume);
+ Code.savePASinkInput(sinkinput);
+ architectureDiagram.requestPaint();
+ pulseaudioChart.updateData(sinkinput.role, sinkinput.index, sinkinput.volume);
+
+ console.log('----');
+ for (var prop in sinkinput)
+ console.log("Object item:", prop, "=", sinkinput[prop])
+ console.log('----');
+ sinkInputProcessed(0, sinkinput.index);
+ }
+ }
+ onSinkInputRemoved : {
+ console.log('onSinkInputRemoved '+index);
+ var sinkinput = Code.takePASinkInput(index);
+
+ if (sinkinput && sinkinput.role != "event") {
+ console.log('----');
+ for (var prop in sinkinput)
+ console.log("Object item:", prop, "=", sinkinput[prop])
+ console.log('----');
+
+ pulseaudioChart.removeData(sinkinput.role, sinkinput.index);
+ }
+ sinkInputProcessed(1, index);
+ architectureDiagram.requestPaint();
+
+ }
+
+ onSinkInfoChanged: {
+ console.log("onSinkInfoChanged " + sinkinfo.index + " Volume " + sinkinfo.volume);
+ Code.savePASinkInfo(sinkinfo);
+ architectureDiagram.requestPaint();
+
+ audiomanagerChart.updateData(sinkinfo.name, sinkinfo.index, sinkinfo.volume);
+ sinkInfoProcessed(0, sinkinfo.index);
+ }
+
+ onSinkInfoRemoved: {
+ console.log("onSinkInfoRemoved " + index);
+ var sinkinfo = Code.takePASinkInfo(index);
+ audiomanagerChart.removeData(sinkinfo.name, sinkinfo.index);
+ architectureDiagram.requestPaint();
+
+ }
+
+ onClientChanged: {
+ console.log("onClientChanged " + client.index + " name " + client.name);
+ Code.savePAClient(client);
+ architectureDiagram.requestPaint();
+ }
+
+ onClientRemoved: {
+ console.log("onClientRemoved " + index);
+ var client = Code.takePAClient(index);
+ architectureDiagram.requestPaint();
+
+ }
+ }
+
+ AMClient {
+ id: amClient
+ property bool initialized: false
+ onSinkAdded: {
+ // skip default AM Sinks
+ if(sink.name.substr(0, 2) == "my")
+ return;
+ if(!initialized) {
+ Code.amSinks[Code.amSinks.length] = sink
+ console.log("SINK ADDED : " + sink.id + " / " + sink.name);
+ }
+ }
+
+ onSinkRemoved: {
+
+ }
+
+ onVolumeChanged: {
+ console.log("**********************************");
+ console.log("QML : VOLUME CHANGED : SINKID = " + sinkid + " / VOLUME = " + volume);
+ console.log("**********************************");
+ }
+
+ onSourceAdded: {
+ // skip default AM Sources
+ if(source.name.substr(0, 2) == "my")
+ return;
+ if(!initialized) {
+ Code.amSources[Code.amSources.length] = source
+ console.log("SOURCE ADDED : " + source.id + " / " + source.name);
+ }
+ }
+
+ onSourceRemoved: {
+
+ }
+
+ onConnectionAdded: {
+ console.log("**********************************");
+ console.log("QML : CONNECTION : " + connection.id+ " "+initialized);
+ console.log("**********************************");
+ if(!initialized) {
+ // remove previous connection
+ amClient.disconnect(connection.id);
+ return;
+ }
+ Code.saveAMConnection(connection);
+ }
+
+ onConnectionRemoved: {
+ console.log("**********************************");
+ console.log("QML : CONNECTION REMOVED : " + index);
+ console.log("**********************************");
+ var conn = Code.takeAMConnection(index);
+ }
+
+ onInitAMMonitorCompleted: {
+ console.log("onInitDBusCallCompleted");
+ amClient.initialized = true;
+ }
+
+ }
+
+
+ Rectangle {
+ id: buttonPanel
+ color: "transparent"
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.topMargin: parent.height / 40
+ width: parent.width / 6
+ height: parent.height
+ property int buttonWidth : width * 9 / 10
+ property int buttonHeight : height / 7
+
+ ListView {
+ id: buttonsView
+
+ anchors.top: parent.top
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.fill: parent
+ orientation: ListView.Vertical
+
+ model: VisualItemModel {
+ Text {
+ text: "Audio Players of IVI"
+ font.pixelSize: parent.height / 30
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ Button {
+ width: buttonPanel.buttonWidth
+ height: buttonPanel.buttonHeight
+
+ iconName: "music"
+ title: "Media"
+
+ amCommandIF: amClient
+ amSource: "MediaPlayer"
+ amSink: "AlsaPrimary"
+ mediaRole: "MEDIA"
+ audioFilePath: "audio/media.wav"
+ }
+
+ Button {
+ width: buttonPanel.buttonWidth
+ height: buttonPanel.buttonHeight
+
+ iconName: "car"
+ title: "Navi"
+
+ amCommandIF: amClient
+ amSource: "NaviPlayer"
+ amSink: "AlsaSecondary"
+ mediaRole: "NAVI"
+ audioFilePath: "audio/navigation.wav"
+ }
+
+ Button {
+ width: buttonPanel.buttonWidth
+ height: buttonPanel.buttonHeight
+
+ iconName: "phone"
+ title: "Phone"
+
+ amCommandIF: amClient
+ amSource: "Skype"
+ amSink: "AlsaSecondary"
+ mediaRole: "skype"
+ audioFilePath: "audio/telephone-ring.wav"
+ }
+
+ Button {
+ width: buttonPanel.buttonWidth
+ height: buttonPanel.buttonHeight
+
+ iconName: "microphone"
+ title: "TTS"
+
+ amCommandIF: amClient
+ amSource: "TTSPlayer"
+ amSink: "AlsaSecondary"
+ mediaRole: "TextToSpeach"
+ audioFilePath: "audio/tts.wav"
+ }
+
+ Button {
+ width: buttonPanel.buttonWidth
+ height: buttonPanel.buttonHeight
+
+ iconName: "reply"
+ title: "Reverse"
+
+ amCommandIF: amClient
+ amSource: "ReverseBeep"
+ amSink: "AlsaSecondary"
+ mediaRole: "reverse"
+ audioFilePath: "audio/car_reverse.wav"
+ }
+ Text {
+ text: "(Press audio play button to\nconnect the AM source and sink)"
+ font.pixelSize: buttonPanel.height / 60
+ anchors.horizontalCenter: parent.horizontalCenter
+ height: 50
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ id: amVolumeChartPanel
+ anchors.left: buttonPanel.right
+ anchors.top: parent.top
+ width: parent.width * 2 / 6
+ height: parent.height / 2
+
+ /*
+ Text {
+ text: "50"
+ font.pixelSize: parent.height / 20
+ anchors.right: audiomanagerChart.left
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.rightMargin: 5
+ }
+ */
+ Graph {
+ id: audiomanagerChart
+ graphName : "AudiomanagerChart"
+ title: "Sinks of GENIVI® Audio Manager"
+ description: "AM's Sink volume changes by Control Plugin"
+ anchors.fill: parent
+ defaultValue : 0
+ maxDataLength: 100
+ width: parent.width
+ height: parent.height
+ }
+ }
+
+ Rectangle {
+ id: pulseaudioVolumeChartPanel
+ anchors.left: buttonPanel.right
+ anchors.top: amVolumeChartPanel.bottom
+ width: parent.width * 2 / 6
+ height: parent.height / 2
+
+ Graph {
+ id: pulseaudioChart
+ title: "Sink Inputs of PulseAudio"
+ description: "PA's Sink Input volume changes"
+ anchors.fill: parent
+ graphName : "PulseAudioChart"
+ defaultValue : 0
+ maxDataLength: 100
+ width: parent.width
+ height: parent.height
+ }
+ }
+
+ Rectangle {
+ id: dialogPanel
+ anchors.left: amVolumeChartPanel.right
+ anchors.top: parent.top
+ width: parent.width * 3 / 6
+ height: parent.height
+
+
+ Canvas {
+ id: architectureDiagram
+ anchors.fill: parent
+ property string geniviLogo:"images/genivi-logo.png"
+ property string pulseaudioLogo:"images/pulseaudio-logo.png"
+ property string speaker:"images/speaker.png"
+ Component.onCompleted: {
+ loadImage(geniviLogo);
+ loadImage(pulseaudioLogo);
+ loadImage(speaker);
+ }
+ function plugInSocket(x, y, name, length, rotate) {
+ var ctx = getContext('2d');
+ var r = 5;
+ var w = 16;
+ var h = 16;
+ ctx.save();
+
+ // adjust position
+ if(rotate == 90) {
+ ctx.translate(x+w, y-h/2)
+ ctx.rotate(Math.PI/2)
+ } else if(rotate == 180) {
+ ctx.translate(x-w/2, y-h)
+ } else { // 0
+ ctx.translate(x+w/2, y+h)
+ ctx.rotate(Math.PI) // default degree
+ }
+ length -= h
+
+ ctx.strokeStyle = "#222222"
+ ctx.lineWidth = 1.0
+ ctx.fillStyle = "#0099ff"
+ ctx.font = "12px sans-serif"
+
+ ctx.beginPath();
+ // top
+ ctx.lineTo(w-r,0);
+ // draw top right corner
+ ctx.arcTo(w,0,w,r,r);
+ ctx.lineTo(w,h); // right side
+ ctx.lineTo(0,h); // bottom side
+ ctx.lineTo(0,r); // left side
+ // draw top left corner
+ ctx.arcTo(0,0,r,0,r);
+ ctx.closePath();
+ ctx.fill();
+ ctx.stroke();
+
+
+ // plug
+ ctx.fillStyle = "white"
+ var plugW = 4
+ var plugH = 7
+ ctx.beginPath();
+ ctx.lineTo(w/3 - plugW/2, h);
+ ctx.lineTo(w/3 - plugW/2, h+plugH);
+ ctx.lineTo(w/3 + plugW/2, h+plugH);
+ ctx.lineTo(w/3 + plugW/2, h);
+ ctx.closePath();
+ ctx.fill();
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.lineTo(w*2/3 - plugW/2, h);
+ ctx.lineTo(w*2/3 - plugW/2, h+plugH);
+ ctx.lineTo(w*2/3 + plugW/2, h+plugH);
+ ctx.lineTo(w*2/3 + plugW/2, h);
+ ctx.closePath();
+ ctx.fill();
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.lineTo(w/2 - plugW/2, 0);
+ ctx.lineTo(w/2 - plugW/2, - length);
+ ctx.lineTo(w/2 + plugW/2, - length);
+ ctx.lineTo(w/2 + plugW/2, 0);
+ ctx.closePath();
+ ctx.fill();
+ ctx.stroke();
+
+
+ ctx.restore();
+
+ ctx.save()
+ ctx.translate(x, y)
+ ctx.rotate(0)
+ ctx.fillStyle = "black";
+ var m = ctx.measureText(name);
+ var tx = (w - m.width)/2, ty = length
+ if(rotate == 90)
+ tx = (w - m.width)/2, ty = length/3
+ else if(rotate == 180)
+ tx = (w - m.width)/2, ty = - length/3
+ ctx.fillText(name, tx, ty)
+ ctx.restore();
+
+ }
+ function roundRect(x, y, w, h, name, verticalCenter,initCB,postCB) {
+ var ctx = getContext('2d');
+ var r = 10;
+ ctx.save();
+ ctx.translate(x, y)
+
+ var fontSize = 12;
+ ctx.font = ""+fontSize+"px sans-serif"
+
+ if(initCB != null)
+ initCB(ctx);
+ else {
+ ctx.strokeStyle = "#222222"
+ ctx.lineWidth = 1.0
+ ctx.fillStyle = "lightGray"
+ }
+
+ ctx.beginPath();
+ // top
+ ctx.lineTo(w-r,0);
+ // draw top right corner
+ ctx.arcTo(w,0,w,r,r);
+ ctx.lineTo(w,h-r); // right side
+ // draw bottom right corner
+ ctx.arcTo(w,h,w-r,h,r);
+ ctx.lineTo(r,h); // bottom side
+ // draw bottom left corner
+ ctx.arcTo(0,h,0,h-r,r);
+ ctx.lineTo(0,r); // left side
+ // draw top left corner
+ ctx.arcTo(0,0,r,0,r);
+
+ ctx.closePath();
+ ctx.fill();
+ ctx.stroke();
+
+ ctx.fillStyle = "black";
+ var m = ctx.measureText(name);
+ if(verticalCenter)
+ ctx.fillText(name, (w - m.width)/2, (h+fontSize)/2);
+ else
+ ctx.fillText(name, (w - m.width)/2, 16);
+
+ if(postCB != null)
+ postCB(ctx);
+
+ ctx.restore();
+
+ }
+ function audioManager() {
+ this.w=parent.width*2/3, this.h=parent.height*2/5
+ this.x = (parent.width - this.w)/2 - this.w/10, this.y = parent.height/6
+
+ var logoWidth = this.h/2
+ var logoHeight = this.h/2
+ var logoX = (this.w - logoWidth)/2
+ var logoY = (this.h - logoHeight)/2
+ roundRect(this.x, this.y, this.w, this.h, "GENIVI® Audio Manager",
+ false, null,
+ function(ctx) {
+ ctx.drawImage(geniviLogo, logoX, logoY, logoWidth, logoHeight);
+ });
+
+
+ // AM Sources
+ var ctx = getContext('2d');
+ var bx = this.x + this.w/16;
+ var by = this.y+this.h/10;
+ var tw = this.w/4
+ var th = this.h/8
+ var sourceJCX = bx+tw
+ var sourceJCY = by+10
+ ctx.fillText(Code.amSources.length+" AM Source(s)", bx, by)
+ for(var i=0; i<Code.amSources.length; i++) {
+ var source = Code.amSources[i];
+ var tx = bx;
+ var ty = by+10;
+ roundRect(tx, ty+(th+th/4)*i, tw, th, source.name, true,
+ function(ctx) {
+ ctx.lineWidth = 1.0
+ ctx.fillStyle = "#99ccff"
+ }
+ );
+ }
+
+ // AM Sinks
+ bx = this.x+this.w-tw-this.w/16
+ by = this.y+this.h/10;
+ var sinkJCX = bx
+ var sinkJCY = by+10
+ ctx.fillText(Code.amSinks.length+" AM Sink(s)", bx, by)
+ for(var i=0; i<Code.amSinks.length; i++) {
+ var sink = Code.amSinks[i];
+ var tx = bx;
+ var ty = by+10
+ roundRect(tx, ty+(th+th/4)*i, tw, th, sink.name, true,
+ function(ctx) {
+ ctx.lineWidth = 1.0
+ ctx.fillStyle = "#99ccff"
+ }
+ );
+ }
+
+ // Connection
+ for(var i=0; i<Code.amConnections.length; i++) {
+ var conn = Code.amConnections[i];
+ var source = null;
+ var sink = null;
+ for(var j=0; j<Code.amSources.length; j++) {
+ var src = Code.amSources[j];
+ if(src.id == conn.sourceId) {
+ source = src;
+ source.idx = j
+ break;
+ }
+ }
+ for(var j=0; j<Code.amSinks.length; j++) {
+ var snk = Code.amSinks[j];
+ if(snk.id == conn.sinkId) {
+ sink = snk;
+ sink.idx = j
+ break;
+ }
+ }
+ if(!source || !sink)
+ continue;
+ ctx.fillText(" Connection id: "+conn.id, sourceJCX,sourceJCY+(th+th/4)*source.idx+th/2-2);
+ ctx.beginPath();
+ ctx.moveTo(sourceJCX,sourceJCY+(th+th/4)*source.idx+th/2);
+ ctx.lineTo(sinkJCX,sinkJCY+(th+th/4)*sink.idx+th/2);
+ ctx.closePath();
+ ctx.fill();
+ ctx.stroke();
+ }
+
+ }
+ function pulseaudio() {
+ this.w=parent.width/3, this.h=(parent.height*7/24)
+ this.x = (parent.width - this.w)*2/3, this.y = parent.height*15/24
+ var logoWidth = this.w*3/4
+ var logoHeight = 85*logoWidth/470; // logo 480px x 85px
+ var logoX = (this.w - logoWidth)/2
+ var logoY = this.h / 30
+
+ roundRect(this.x, this.y, this.w, this.h, "", false,
+ null,
+ function(ctx) {
+ ctx.drawImage(pulseaudioLogo, logoX, logoY, logoWidth, logoHeight);
+ });
+
+ var ctx = getContext('2d');
+ // sink devices
+ var tw = this.w*3/4
+ var th = this.h/8
+ var tx = this.x + (this.w - tw)/2;
+ var ty = this.y + this.h*2/7;
+ var sinkJCX = tx;
+ var sinkJCY = ty;
+ var sinkH = th;
+
+ var bw = (this.w*2/3);
+ var bx = this.x - bw*3/2;
+ var by = this.y-this.h/4;
+ ctx.fillText(Code.paSinks.length+" PA Sink(s)", tx, ty-5)
+ for(var i=0; i<Code.paSinks.length; i++) {
+ var sink = Code.paSinks[i];
+ roundRect(tx, ty+(th+th/4)*i, tw, th, sink.name, true,
+ function(ctx) {
+ ctx.lineWidth = 1.0
+ ctx.fillStyle = "#99ccff"
+ }
+ );
+ ctx.save();
+ ctx.strokeStyle = Code.getGraphNodeData("AudiomanagerChart", sink.name).color;
+ ctx.beginPath();
+ ctx.moveTo(tx+tw, ty+(th+th/4)*i+th/2);
+ ctx.lineTo(tx+tw+tw*2/5, ty+(th+th/4)*i+th/2);
+ ctx.closePath();
+ ctx.stroke();
+
+ ctx.drawImage(speaker, tx+tw+tw*2/5, ty+(th+th/4)*i, th, th)
+ ctx.restore();
+
+ }
+
+
+ // find active PA clients
+ var activeClients = [];
+ for(var i=0; i<Code.paClients.length; i++) {
+ var client = Code.paClients[i];
+ for(var j=0; j<Code.paSinkInputs.length; j++) {
+ var sinkInput = Code.paSinkInputs[j];
+ if(sinkInput.clientIndex == client.index) {
+ client.sinkInput = sinkInput
+ activeClients[activeClients.length] = client;
+ break;
+ }
+ }
+ }
+
+ // draw active PA clients
+ var bw = (this.w*3/4);
+ var bx = this.x - bw*3/2;
+ var by = this.y-this.h/10;
+ ctx.fillText(activeClients.length+" PA's Active Client(s)", bx, by)
+ for(var i=0; i<activeClients.length; i++) {
+ var client = activeClients[i];
+ var tw = bw;
+ var th = this.h/5
+ var tx = bx;
+ var ty = this.y - this.h/12;
+ roundRect(tx, ty+(th+th/4)*i, tw, th,
+ client.name, false,
+ function(ctx) {
+ ctx.lineWidth = 1.0
+ ctx.fillStyle = "#99ccff"
+ },
+ function(ctx) {
+ var mediaRole = 'media.role='+client.sinkInput.role;
+ var m = ctx.measureText(mediaRole);
+ ctx.fillText(mediaRole, (tw - m.width)/2, th-6);
+ }
+ );
+ ctx.save();
+ // sink input line to sink
+ for(var j=0; j<Code.paSinks.length; j++) {
+ var sink = Code.paSinks[j];
+ if(sink.index == client.sinkInput.sinkIndex) {
+ ctx.fillText(" Stream idx: "+sinkInput.index, tx+tw,ty+(th+th/4)*i+th/2);
+ ctx.strokeStyle = Code.getGraphNodeColor("PulseAudioChart", client.sinkInput.role);
+ ctx.beginPath();
+ ctx.moveTo(tx+tw,ty+(th+th/4)*i+th/2);
+ ctx.lineTo(sinkJCX, sinkJCY+(sinkH+sinkH/4)*j+sinkH/2);
+ ctx.closePath();
+ ctx.stroke();
+ break;
+ }
+ }
+ ctx.restore();
+ }
+ }
+ onPaint: {
+ var ctx = getContext('2d');
+ ctx.save();
+ ctx.clearRect(0, 0, width, height);
+ var am = new audioManager();
+ var pa = new pulseaudio();
+ plugInSocket(am.x+am.w*2/3, am.y+am.h,
+ "Routing Plugin for PulseAudio", pa.y-(am.y+am.h))
+
+ plugInSocket(am.x+am.w/2, am.y,
+ "Command Plugin for D-Bus", am.y*3/4, 180)
+
+ roundRect(am.x+am.w/2-parent.width/3/2, am.y/4,
+ parent.width/3, parent.height/30,
+ "Audio Manager Monitor", true);
+
+ plugInSocket(am.x+am.w, am.y+am.h/2,
+ "Control Plugin", am.y*3/4, 90)
+
+ roundRect(am.x+am.w+am.w/14, am.y+am.h/2-(parent.height/30)/2,
+ parent.width/6, parent.height/30,
+ "Audio Policy", true);
+
+ ctx.restore();
+ }
+ }
+ Text {
+ text: "(LIVE Architecture Diagram)"
+ font.pixelSize: parent.height / 40
+ anchors.bottom: dialogPanel.bottom
+ anchors.bottomMargin: parent.height/60
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+}
diff --git a/Graph.qml b/Graph.qml
new file mode 100644
index 0000000..e6434da
--- /dev/null
+++ b/Graph.qml
@@ -0,0 +1,264 @@
+import QtQuick 2.0
+import com.windriver.ammonitor 1.0
+import "code.js" as Code
+
+Rectangle {
+
+ property string title : "No name"
+ property string description : "No description"
+ property string backgroundColor : "#F0F0F0"
+ property string backgroundLineColor : "#aaaaaa"
+ property real backgroundLineWidth : 0.1
+ property int refreshInterval : 500
+ property string graphName : ""
+ property int defaultValue : 0
+ property int maxDataLength : 100
+ property int maxValue : 100
+ property int graphLineWidth : 1
+
+ Component.onCompleted: {
+ Code.addGraphDataset(graphName,refreshInterval);
+ refreshTimer.start();
+ }
+
+ function updateData(name, id, value) {
+ Code.updateGraphNode(graphName, name, id, maxDataLength, value);
+ }
+
+ function removeData(name, id) {
+ Code.removeGraphNode(graphName, name, id);
+ }
+
+ function startTimer() {
+ refreshTimer.start();
+ }
+
+ function stopTimer() {
+ refreshTimer.stop();
+ }
+
+
+ Text {
+ text: title
+ font.pixelSize: parent.height / 20
+ anchors.bottom: background.top
+ anchors.left: background.left
+ }
+
+ Text {
+ text: maxValue
+ font.pixelSize: parent.height / 30
+ anchors.right: background.left
+ anchors.rightMargin: 5
+ y: parent.height * 0.25
+ }
+ Text {
+ text: maxValue / 2
+ font.pixelSize: parent.height / 30
+ anchors.right: background.left
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.rightMargin: 5
+ y: parent.height * 0.5
+ }
+ Text {
+ id: volume0
+ text: "0"
+ font.pixelSize: parent.height / 30
+ anchors.right: background.left
+ anchors.rightMargin: 5
+ y: parent.height * 0.70
+ }
+
+ Text {
+ text: "Volume"
+ font.pixelSize: parent.height / 30
+ anchors.right: background.left
+ anchors.rightMargin: 2
+ anchors.top: volume0.bottom
+ }
+
+ Canvas {
+ id: background
+ width: parent.width
+ height: parent.height
+ anchors.fill: parent
+ anchors.margins: parent.width / 10
+ antialiasing: true
+
+ function drawBackground() {
+ // Get Drawing Context
+ var context = getContext('2d');
+ context.clearRect(0, 0, width, height);
+ var bgColor = parent.backgroundColor;
+ var bgLineColor = parent.backgroundLineColor;
+ var bgLineWidth = parent.backgroundLineWidth;
+
+ // Fill background color
+ context.save();
+ /*
+ context.beginPath();
+ context.fillStyle = bgColor;
+ context.fillRect(0,0,width, height);
+ context.closePath();
+ context.fill();
+ */
+
+ // Draw Guide Line
+
+ context.translate(0.8,0.8);
+ var horizontalWidth = width / 10;
+ var verticalWidth = height / 10;
+
+ for(var i = 0; i < 10; i++) {
+ if(i==0)
+ continue
+ // Draw Horizontal Line
+ context.beginPath();
+ context.lineWidth = bgLineWidth;
+ context.strokeStyle = bgLineColor;
+ context.moveTo(0, verticalWidth * i);
+ context.lineTo(width-1, verticalWidth * i);
+ context.closePath();
+ context.stroke();
+
+ // Draw Vertical Line
+ context.moveTo(horizontalWidth * i, 0);
+ context.lineTo(horizontalWidth * i, height);
+
+ // Stroke!!
+ context.stroke();
+ }
+
+ // Draw Right, Bottom border line
+
+ context.translate(-0.8, -0.8);
+ var w = width -1;
+ var h = height -1;
+ context.beginPath();
+ context.strokeStyle = "black";
+ context.lineWidth = 1;
+ context.moveTo(0,0);
+ context.lineTo(0, height);
+ context.moveTo(0, verticalWidth * 8);
+ context.lineTo(width, verticalWidth * 8);
+ context.closePath();
+ context.stroke();
+
+ context.restore();
+
+ }
+
+ function drawGraphNode() {
+ var context = getContext('2d');
+ var graphWidth = width-2;
+ var graphHeight = height * (0.6);
+ var nodeList = Code.takeGraphDataset(graphName);
+ var lineColor = "black";
+ var dataDrawingWidth = graphWidth / maxDataLength;
+
+ if (nodeList.length == 0) {
+ return;
+ }
+
+ context.lineWidth = graphLineWidth;
+
+ for (var i = 0; i < nodeList.length; i++) {
+ var node = nodeList[i];
+ var startX = width-1;
+ var startY = height-1;
+ var endX = 0;
+ var endY = 0;
+
+ context.beginPath();
+ lineColor = node.color;
+ context.strokeStyle = lineColor;
+ if (node.removed)
+ startX = dataDrawingWidth * node.data.length;
+
+ for (var j = node.data.length-1; j > 0; j--) {
+ var value = node.data[j];
+ var adjustY = 0;
+ if (j == node.data.length-1) {
+ startY = getYPosition(value, graphHeight, adjustY);
+ }
+
+ for (var k = 0; k < nodeList.length; k++) {
+ if (k != i && nodeList[k].data.length >= j && nodeList[k].data[j] == value) {
+ adjustY = i * graphLineWidth * 2 + 1;
+ break;
+ }
+ }
+
+ endX = startX - dataDrawingWidth;
+ endY = getYPosition(value, graphHeight, adjustY);
+
+ if ((value < 0 && node.data[j-1] > 0) ||
+ (value < 0 && j == 0) ||
+ (j!= nodeList.length -1 &&value < 0 && node.data[j+1] > 0)) {
+
+ context.moveTo(startX, startY);
+ context.fillStyle = node.color;
+ context.arc(startX, startY, dataDrawingWidth, 0, Math.PI * 2, true);
+ context.fill();
+ } else {
+ context.moveTo(startX, startY);
+ context.lineTo(endX, endY);
+ }
+
+ context.stroke();
+ startX = endX;
+ startY = endY;
+ }
+ context.closePath();
+
+ }
+ }
+
+ function getYPosition(value, graphHeight, adjustY) {
+ var pos = 0;
+ if (value < 0)
+ value = value * -1;
+
+ pos = graphHeight - (graphHeight * (value / maxValue));
+
+ if (pos < 0)
+ pos = graphHeight;
+
+ pos = pos + ((height - graphHeight) / 2);
+ pos = pos + adjustY;
+
+ return pos
+ }
+
+ onPaint:{
+ drawBackground();
+ drawGraphNode();
+ }
+ }
+
+ Canvas {
+ id: node
+ width: parent.width
+ height: parent.height
+ anchors.fill: parent
+ }
+
+ Timer {
+ id: refreshTimer
+ interval: parent.refreshInterval
+ repeat: true
+ onTriggered: {
+ Code.refreshGraphNode(graphName);
+ background.requestPaint();
+ }
+ }
+
+ Text {
+ text: "("+description+")"
+ font.pixelSize: parent.height / 30
+ anchors.bottom: parent.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottomMargin: parent.height/14
+ }
+}
+
diff --git a/README b/README
new file mode 100644
index 0000000..3f5f928
--- /dev/null
+++ b/README
@@ -0,0 +1,16 @@
+Audio Manager Monitor
+=====================
+
+Author: JoonCheol Park <jooncheol.park@windriver.com>
+ Daewon Park <daewon.park@windriver.com>
+
+Build Instruction
+=================
+
+[build on Ubuntu 14.04]
+ $ qtchooser -run-tool=qmake -qt=qt5 INCLUDEPATH=$PREFIX-OF-AM/include
+ $ make
+
+[Run on Ubuntu 14.04]
+ $ . pc-env # for testing audio environment
+ $ ./AudioManagerMonitor
diff --git a/audio/car_reverse.wav b/audio/car_reverse.wav
new file mode 100755
index 0000000..ee22f68
--- /dev/null
+++ b/audio/car_reverse.wav
Binary files differ
diff --git a/audio/media.wav b/audio/media.wav
new file mode 100755
index 0000000..f4e4a40
--- /dev/null
+++ b/audio/media.wav
Binary files differ
diff --git a/audio/navigation.wav b/audio/navigation.wav
new file mode 100755
index 0000000..46b147b
--- /dev/null
+++ b/audio/navigation.wav
Binary files differ
diff --git a/audio/telephone-ring.wav b/audio/telephone-ring.wav
new file mode 100755
index 0000000..fcc896c
--- /dev/null
+++ b/audio/telephone-ring.wav
Binary files differ
diff --git a/audio/tts.wav b/audio/tts.wav
new file mode 100755
index 0000000..00ca58c
--- /dev/null
+++ b/audio/tts.wav
Binary files differ
diff --git a/audiomanagerdbusinterface.cpp b/audiomanagerdbusinterface.cpp
new file mode 100644
index 0000000..96ae120
--- /dev/null
+++ b/audiomanagerdbusinterface.cpp
@@ -0,0 +1,635 @@
+#include <QTimer>
+#include "audiomanagerdbusinterface.h"
+
+AudioManagerDBusInterface::AudioManagerDBusInterface(QObject* parent, QString serviceName, QString objectPath, QString interfaceName) :
+ QObject(parent),
+ mDBusConnection(QDBusConnection::sessionBus())
+{
+ initDBusInterface(serviceName, objectPath, interfaceName);
+ log("AudioManagerDBusInterface created");
+ connectInternalSignalToSlot();
+
+ QTimer::singleShot(0, this, SLOT(callAllDataForInit()));
+}
+
+
+AudioManagerDBusInterface::AudioManagerDBusInterface(QObject* parent) :
+ QObject(parent),
+ mDBusConnection(QDBusConnection::sessionBus())
+{
+ initDBusInterface("org.genivi.audiomanager", "/org/genivi/audiomanager/commandinterface", "org.genivi.audiomanager.commandinterface");
+ log("AudioManagerDBusInterface created");
+ connectInternalSignalToSlot();
+
+ QTimer::singleShot(0, this, SLOT(callAllDataForInit()));
+}
+
+void AudioManagerDBusInterface::callAllDataForInit()
+{
+ qDebug() << "callAllDataForInit";
+ getListMainConnections();
+ for (int i = 0; i < mMainConnectionTypeList.size(); i++)
+ emit connectionAdded(mMainConnectionTypeList.at(i));
+
+ getListMainSinkClasses();
+ getListMainSinks();
+ getListMainSourceClasses();
+ getListMainSources();
+
+ emit initAMMonitorCompleted();
+}
+
+void AudioManagerDBusInterface::connectInternalSignalToSlot()
+{
+ mDBusConnection.connect(mServiceName, mObjectPath, mInterfaceName, "MainConnectionStateChanged", this, SLOT(onMainConnectionStateChanged(ushort,short)));
+ mDBusConnection.connect(mServiceName,mObjectPath,mInterfaceName,"VolumeChanged",this,SLOT(onVolumeChanged(ushort,short)));
+ mDBusConnection.connect(mServiceName,mObjectPath,mInterfaceName,"NumberOfMainConnectionsChanged",this,SLOT(onNumberOfMainConnectionsChanged()));
+}
+
+void AudioManagerDBusInterface::onMainConnectionStateChanged(ushort mainConnectionID, short state)
+{
+ qDebug() << "GET MAIN CONNECTION STATE CHANGED";
+
+ getListMainConnections();
+
+ if (state == am::CS_CONNECTED) {
+ QMap<QString, QVariant> connection;
+ for (int i = 0; i < mMainConnectionTypeList.size(); i++) {
+ QVariantMap element = mMainConnectionTypeList.at(i);
+ if (element["id"] == mainConnectionID) {
+ connection = element;
+ connection["state"] = QVariant::fromValue(state);
+ }
+ }
+
+ emit connectionAdded(connection);
+ } else if (state == am::CS_DISCONNECTED) {
+ QMap<QString, QVariant> connection;
+ for (int i = 0; i < mMainConnectionTypeList.size(); i++) {
+ QVariantMap element = mMainConnectionTypeList.at(i);
+ if (element["id"] == mainConnectionID) {
+ mMainConnectionTypeList.removeAt(i);
+ break;
+ }
+ }
+ emit connectionRemoved(mainConnectionID);
+ }
+}
+
+void AudioManagerDBusInterface::onNumberOfMainConnectionsChanged()
+{
+ //getListMainConnections();
+}
+
+void AudioManagerDBusInterface::onVolumeChanged(ushort sinkID, short volume)
+{
+ qDebug() << "Volume Changed";
+
+ emit volumeChanged(sinkID, volume);
+
+// getListMainSinks();
+// getListMainSources();
+}
+
+void AudioManagerDBusInterface::initDBusInterface(QString serviceName, QString objectPath, QString interfaceName)
+{
+ mServiceName = serviceName;
+ mObjectPath = objectPath;
+ mInterfaceName = interfaceName;
+
+ if (!mDBusConnection.isConnected()) {
+ mDBusConnection.connectToBus(QDBusConnection::SessionBus, serviceName);
+ return;
+ }
+
+ qDBusRegisterMetaType<Availability>();
+ qDBusRegisterMetaType<MainConnectionType>();
+ qDBusRegisterMetaType<QList<MainConnectionType>>();
+ qDBusRegisterMetaType<ClassProperty>();
+ qDBusRegisterMetaType<QList<ClassProperty>>();
+ qDBusRegisterMetaType<SinkClasses>();
+ qDBusRegisterMetaType<QList<SinkClasses>>();
+ qDBusRegisterMetaType<SourceClasses>();
+ qDBusRegisterMetaType<QList<SourceClasses>>();
+ qDBusRegisterMetaType<SinkType>();
+ qDBusRegisterMetaType<QList<SinkType>>();
+ qDBusRegisterMetaType<SourceType>();
+ qDBusRegisterMetaType<QList<SourceType>>();
+ qDBusRegisterMetaType<SoundProperty>();
+ qDBusRegisterMetaType<QList<SoundProperty>>();
+}
+
+void AudioManagerDBusInterface::log(QString message)
+{
+ /*
+ mTempLogFile = fopen("/tmp/tempQTLog","a");
+ fprintf(mTempLogFile,"%s\n", message.toUtf8().constData());
+ fclose(mTempLogFile);
+ */
+ qDebug() << message;
+}
+
+ushort AudioManagerDBusInterface::getSourceIDFromName(QString name)
+{
+ for (int i = 0; i < mMainSourceList.size(); i++) {
+ QVariantMap item = mMainSourceList.at(i);
+ if(item["name"] == name)
+ return (ushort)item["id"].toInt();
+ }
+ return 0;
+}
+
+ushort AudioManagerDBusInterface::getSinkIDFromName(QString name)
+{
+ for (int i = 0; i < mMainSinkList.size(); i++) {
+ QVariantMap item = mMainSinkList.at(i);
+ if(item["name"] == name)
+ return (ushort)item["id"].toInt();
+ }
+ return 0;
+}
+
+void AudioManagerDBusInterface::connectToSignal(QString signalName, QObject *receiver, const char* slot)
+{
+ mDBusConnection.connect(mServiceName,mObjectPath,mInterfaceName,signalName, receiver,slot);
+}
+
+void AudioManagerDBusInterface::disconnectToSignal(QString signalName, QObject* receiver, const char* slot)
+{
+ mDBusConnection.disconnect(mServiceName, mObjectPath, mInterfaceName, signalName, receiver, slot);
+}
+
+QDBusMessage AudioManagerDBusInterface::getDBusMessage(QString command)
+{
+ return QDBusMessage::createMethodCall(mServiceName, mObjectPath, mInterfaceName,command);
+}
+
+ushort AudioManagerDBusInterface::connect(QString sourceName, QString sinkName)
+{
+ QDBusMessage message = getDBusMessage("Connect");
+ ushort sourceID = getSourceIDFromName(sourceName);
+ ushort sinkID = getSinkIDFromName(sinkName);
+
+ message << QVariant::fromValue(sourceID);
+ message << QVariant::fromValue(sinkID);
+
+ qDebug() << "=====";
+ qDebug() << "source " << sourceName << sourceID;
+ qDebug() << "sink " << sinkName << sinkID;
+ qDebug() << "=====";
+
+ QDBusMessage result = mDBusConnection.call(message);
+ if(result.type() == QDBusMessage::ReplyMessage) {
+ qDebug() << result.arguments();
+ return (ushort)result.arguments().at(1).toInt();
+ }
+ qDebug() << result.errorMessage();
+ return 0;
+}
+
+bool AudioManagerDBusInterface::setVolume(QString sinkName, short value)
+{
+ QDBusMessage message = getDBusMessage("SetVolume");
+ ushort sinkID = getSinkIDFromName(sinkName);
+ if (sinkID == 0)
+ return false;
+ message << QVariant::fromValue(sinkID);
+ message << QVariant::fromValue(value);
+ QDBusMessage result = mDBusConnection.call(message);
+
+ if (result.type() != QDBusMessage::ReplyMessage) {
+ qDebug() << "Error occured in setVolume()";
+ return false;
+ }
+
+ mLastError = result.arguments().takeFirst().toInt();
+
+ if (mLastError == am::am_Error_e::E_OK)
+ return true;
+ else
+ return false;
+}
+
+bool AudioManagerDBusInterface::disconnect(ushort connectionID)
+{
+ QDBusMessage message = getDBusMessage("Disconnect");
+ message << QVariant::fromValue(connectionID);
+ QDBusMessage result = mDBusConnection.call(message);
+ if(result.type() == QDBusMessage::ReplyMessage)
+ return true;
+ return false;
+}
+
+bool AudioManagerDBusInterface::getListMainConnections()
+{
+ QDBusMessage message = getDBusMessage("GetListMainConnections");
+ QDBusMessage ret = mDBusConnection.call(message);
+ if (ret.type() != QDBusMessage::ReplyMessage) {
+ qDebug() << "DBus Error occured in getListMainConenctions";
+ return false;
+ }
+
+ QList<QVariant> result = ret.arguments();
+ mLastError = result.takeFirst().toInt();
+ QDBusMessage retMessage;
+ retMessage.setArguments(result);
+
+ if (mLastError == am::am_Error_e::E_OK) {
+ QDBusReply<QList<MainConnectionType>> reply(retMessage);
+
+ QList<MainConnectionType> connectionList = reply.value();
+ mMainConnectionTypeList.clear();
+
+ for (int i = 0; i < connectionList.size(); i++) {
+ MainConnectionType info = connectionList[i];
+ QMap<QString, QVariant> connection;
+ connection["id"] = QVariant::fromValue(info.mainConnectionID);
+ connection["sourceId"] = QVariant::fromValue(info.sourceID);
+ connection["sinkId"] = QVariant::fromValue(info.sinkID);
+ connection["state"] = QVariant::fromValue(info.connectionState);
+
+ mMainConnectionTypeList << connection;
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+bool AudioManagerDBusInterface::getListMainSinks()
+{
+
+ QDBusMessage message = getDBusMessage("GetListMainSinks");
+ QDBusMessage ret = mDBusConnection.call(message);
+
+ if (ret.type() != QDBusMessage::ReplyMessage) {
+ qDebug() << "DBus Error occured in getListMainConenctions";
+ return false;
+ }
+
+ QList<QVariant> result = ret.arguments();
+ mLastError = result.takeFirst().toInt();
+ QDBusMessage retMessage;
+ retMessage.setArguments(result);
+
+ qDebug() << "REPLY MESSAGE SIZE : " + QString::number(ret.arguments().size());
+
+ if (mLastError == am::am_Error_e::E_OK) {
+ QDBusReply<QList<SinkType>> reply(retMessage);
+
+ QList<SinkType> sinkList = reply.value();
+ mMainSinkList.clear();
+
+ for (int i = 0; i < sinkList.size(); i++) {
+ SinkType info = sinkList[i];
+ QMap<QString, QVariant> element;
+ element["name"] = info.name;
+ element["volume"] = info.volume;
+ element["id"] = info.sinkID;
+ element["muteState"] = info.muteState;
+ emit sinkAdded(element);
+ mMainSinkList << element;
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+bool AudioManagerDBusInterface::getListMainSources()
+{
+
+ QDBusMessage message = getDBusMessage("GetListMainSources");
+ QDBusMessage ret = mDBusConnection.call(message);
+
+ if (ret.type() != QDBusMessage::ReplyMessage) {
+ qDebug() << "DBus Error occured in getListMainConenctions";
+ return false;
+ }
+ QList<QVariant> result = ret.arguments();
+ mLastError = result.takeFirst().toInt();
+ QDBusMessage retMessage;
+ retMessage.setArguments(result);
+
+ if (mLastError == am::am_Error_e::E_OK) {
+ QDBusReply<QList<SourceType>> reply(retMessage);
+
+ QList<SourceType> sourceList = reply.value();
+ mMainSourceList.clear();
+
+ for (int i = 0; i < sourceList.size(); i++) {
+ SourceType info = sourceList[i];
+ QMap<QString, QVariant> element;
+ element["name"] = info.name;
+ element["id"] = info.sourceID;
+ mMainSourceList << element;
+ emit sourceAdded(element);
+ }
+ } else {
+ return false;
+ }
+ return true;
+}
+
+bool AudioManagerDBusInterface::getListMainSourceClasses()
+{
+ mMainSourceClassList.clear();
+
+ QDBusMessage message = getDBusMessage("GetListSourceClasses");
+ QDBusMessage ret = mDBusConnection.call(message);
+
+ if (ret.type() != QDBusMessage::ReplyMessage) {
+ qDebug() << "Error occured in getListMainSourceClasses";
+ return false;
+ }
+
+ QList<QVariant> result = ret.arguments();
+ mLastError = result.takeFirst().toInt();
+ QDBusMessage retMessage;
+ retMessage.setArguments(result);
+
+ if (mLastError == am::am_Error_e::E_OK) {
+ QDBusReply<QList<SourceClasses>> reply(retMessage);
+ mMainSourceClassList = reply.value();
+ log(QString("CLASS SOURCE RECEIVE SUCCESS : ") + QString::number(mMainSourceClassList.size()));
+
+ for (int i = 0; i < mMainSourceClassList.size(); i++) {
+ log(QString("SOURCE CLASSES : ") + mMainSourceClassList.at(i).name);
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+bool AudioManagerDBusInterface::getListMainSinkClasses()
+{
+ mMainSinkClassList.clear();
+
+ QDBusMessage message = getDBusMessage("GetListSinkClasses");
+ QDBusMessage ret = mDBusConnection.call(message);
+ if (ret.type() != QDBusMessage::ReplyMessage) {
+ qDebug() << "Error occured in getListMainSinkClasses()";
+ return false;
+ }
+
+ QList<QVariant> result = ret.arguments();
+ mLastError = result.takeFirst().toInt();
+ QDBusMessage retMessage;
+ retMessage.setArguments(result);
+
+ if (mLastError == am::am_Error_e::E_OK) {
+ QDBusReply<QList<SinkClasses>> reply(retMessage);
+ mMainSinkClassList = reply.value();
+
+ qDebug() << QString("CLASS SINK RECEIVE SUCCESS : ") + QString::number(mMainSinkClassList.size());
+ for (int i = 0; i < mMainSinkClassList.size(); i++) {
+
+ qDebug() << QString("SINK CLASSES : ") << mMainSinkClassList.at(i).name;
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+bool AudioManagerDBusInterface::getListSystemProperties()
+{
+ mSystemPropList.clear();
+
+ QDBusMessage message = getDBusMessage("GetListSystemProperties");
+ QDBusMessage ret = mDBusConnection.call(message);
+
+ if (ret.type() != QDBusMessage::ReplyMessage) {
+ qDebug() << "Error occured in getListSystemProperties()";
+ return false;
+ }
+
+ QList<QVariant> result = ret.arguments();
+ mLastError = result.takeFirst().toInt();
+ QDBusMessage retMessage;
+ retMessage.setArguments(result);
+
+ if (mLastError == am::am_Error_e::E_OK) {
+ QDBusReply<QList<SystemProperty>> reply(retMessage);
+
+ mSystemPropList = reply.value();
+ qDebug() << QString("GET SYSTEM PROPERTY LIST SUCCESS ") << mSystemPropList.size();
+
+ for (int i = 0; i < mSystemPropList.size(); i++) {
+ qDebug() << QString("System Prop :[") + QString::number(mSystemPropList.at(i).type) + " , " + QString::number(mSystemPropList.at(i).value);
+ }
+
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+
+
+QList<SinkClasses> AudioManagerDBusInterface::getMainSinkClassesList()
+{
+ return mMainSinkClassList;
+}
+
+QList<SourceClasses> AudioManagerDBusInterface::getMainSourceClassesList()
+{
+ return mMainSourceClassList;
+}
+
+QString AudioManagerDBusInterface::getConnectionResultString(short connectionState)
+{
+ switch(connectionState) {
+ case am::CS_CONNECTED:
+ return "Connected";
+ case am::CS_CONNECTING:
+ return "Connecting";
+ case am::CS_DISCONNECTED:
+ return "Disconnected";
+ case am::CS_DISCONNECTING:
+ return "Disconnecting";
+ case am::CS_MAX:
+ return "Connection MAX";
+ case am::CS_SUSPENDED:
+ return "Suspended";
+ default:
+ return "Unknown";
+ }
+
+ return "Unknown"; // Maybe not reached
+}
+
+QDBusArgument &operator << (QDBusArgument &dest, const Availability &source)
+{
+ dest.beginStructure();
+ dest << source.available
+ << source.reason;
+ dest.endStructure();
+ return dest;
+}
+
+const QDBusArgument &operator>>(const QDBusArgument &argument, Availability &theStruct)
+{
+ argument.beginStructure();
+ argument >> theStruct.available
+ >> theStruct.reason;
+ argument.endStructure();
+ return argument;
+}
+
+QDBusArgument &operator << (QDBusArgument& dest, const MainConnectionType& source)
+{
+ dest.beginStructure();
+ dest << source.mainConnectionID
+ << source.sourceID
+ << source.sinkID
+ << source.delay
+ << source.connectionState;
+ dest.endStructure();
+ return dest;
+}
+const QDBusArgument &operator >> (const QDBusArgument& source, MainConnectionType& dest)
+{
+ source.beginStructure();
+ source >> dest.mainConnectionID
+ >> dest.sourceID
+ >> dest.sinkID
+ >> dest.delay
+ >> dest.connectionState;
+ source.endStructure();
+ return source;
+}
+
+QDBusArgument &operator << (QDBusArgument& dest, const SoundProperty& source)
+{
+ dest.beginStructure();
+ dest << source.type
+ << source.value;
+ dest.endStructure();
+ return dest;
+}
+
+const QDBusArgument &operator >> (const QDBusArgument& source, SoundProperty& dest)
+{
+ source.beginStructure();
+ source >> dest.type
+ >> dest.value;
+ source.endStructure();
+ return source;
+}
+
+
+QDBusArgument &operator << (QDBusArgument& dest, const SourceClasses& source)
+{
+ dest.beginStructure();
+ dest << source.sourceClassID
+ << source.name
+ << source.listClassProperties;
+ dest.endStructure();
+ return dest;
+}
+
+const QDBusArgument &operator >> (const QDBusArgument& source, SourceClasses& dest)
+{
+ source.beginStructure();
+ source >> dest.sourceClassID
+ >> dest.name
+ >> dest.listClassProperties;
+ source.endStructure();
+ return source;
+}
+
+QDBusArgument &operator << (QDBusArgument& dest, const SinkClasses& source)
+{
+ dest.beginStructure();
+ dest << source.sinkClassID
+ << source.name
+ << source.listClassProperties;
+ dest.endStructure();
+ return dest;
+}
+
+const QDBusArgument &operator >> (const QDBusArgument& source, SinkClasses& dest)
+{
+ source.beginStructure();
+ source >> dest.sinkClassID
+ >> dest.name
+ >> dest.listClassProperties;
+ source.endStructure();
+ return source;
+}
+
+QDBusArgument &operator << (QDBusArgument& dest, const ClassProperty& source)
+{
+ dest.beginStructure();
+ dest << source.classProperty
+ << source.value;
+ dest.endStructure();
+ return dest;
+}
+
+const QDBusArgument &operator >> (const QDBusArgument& source, ClassProperty& dest)
+{
+ source.beginStructure();
+ source >> dest.classProperty
+ >> dest.value;
+ source.endStructure();
+ return source;
+}
+
+
+QDBusArgument &operator << (QDBusArgument& dest, const SinkType& source)
+{
+ dest.beginStructure();
+ dest << source.sinkID
+ << source.name
+ << source.availability
+ << source.volume
+ << source.muteState
+ << source.sinkClassID;
+ dest.endStructure();
+ return dest;
+}
+
+const QDBusArgument &operator >> (const QDBusArgument& source, SinkType& dest)
+{
+ source.beginStructure();
+ source >> dest.sinkID
+ >> dest.name
+ >> dest.availability
+ >> dest.volume
+ >> dest.muteState
+ >> dest.sinkClassID;
+ source.endStructure();
+ return source;
+}
+
+QDBusArgument &operator << (QDBusArgument& dest, const SourceType& source)
+{
+ dest.beginStructure();
+ dest << source.sourceID
+ << source.name
+ << source.availability
+ << source.sourceClassID;
+ dest.endStructure();
+
+ return dest;
+}
+
+const QDBusArgument &operator >> (const QDBusArgument& source, SourceType& dest)
+{
+ source.beginStructure();
+ source >> dest.sourceID
+ >> dest.name
+ >> dest.availability
+ >> dest.sourceClassID;
+ source.endStructure();
+
+ return source;
+}
diff --git a/audiomanagerdbusinterface.h b/audiomanagerdbusinterface.h
new file mode 100644
index 0000000..5c167ed
--- /dev/null
+++ b/audiomanagerdbusinterface.h
@@ -0,0 +1,236 @@
+#ifndef AUDIOMANAGERDBUSINTERFACE_H
+#define AUDIOMANAGERDBUSINTERFACE_H
+
+#include <iostream>
+
+#include <QDebug>
+#include <QString>
+#include <QObject>
+#include <QQuickView>
+#include <QQuickItem>
+#include <QMap>
+#include <QMetaType>
+#include <QtDBus/QDBusConnection>
+#include <QtDBus/QDBusVariant>
+#include <QtDBus/QDBusArgument>
+#include <QtDBus/QDBusMetaType>
+#include <QtDBus/QDBusReply>
+#include <QFile>
+#include <audiomanagertypes.h>
+
+
+typedef struct
+{
+ short available;
+ short reason;
+} Availability;
+
+
+typedef struct
+{
+ short classProperty;
+ short value;
+} ClassProperty;
+
+typedef struct
+{
+ ushort sinkClassID;
+ QString name;
+ QList<ClassProperty> listClassProperties;
+} SinkClasses;
+
+typedef struct
+{
+ ushort sourceClassID;
+ QString name;
+ QList<ClassProperty> listClassProperties;
+} SourceClasses;
+
+typedef struct
+{
+ ushort sinkID;
+ QString name;
+ Availability availability;
+ short volume;
+ short muteState;
+ ushort sinkClassID;
+} SinkType;
+
+
+typedef struct
+{
+ ushort sourceID;
+ QString name;
+ Availability availability;
+ ushort sourceClassID;
+} SourceType;
+
+typedef struct
+{
+ ushort type;
+ short value;
+} SoundProperty;
+
+typedef struct
+{
+ ushort type;
+ short value;
+} SystemProperty;
+
+typedef struct
+{
+ ushort mainConnectionID;
+ ushort sourceID;
+ ushort sinkID;
+ short delay;
+ short connectionState;
+} MainConnectionType;
+
+typedef struct
+{
+ ushort mainConenctionID;
+ short connectionState;
+ ushort sinkID;
+ ushort sourceID;
+ short delay;
+ QList<ushort> listConnectionID;
+} MainConnections;
+
+
+Q_DECLARE_METATYPE(Availability)
+Q_DECLARE_METATYPE(ClassProperty)
+Q_DECLARE_METATYPE(SinkClasses)
+Q_DECLARE_METATYPE(SourceClasses)
+Q_DECLARE_METATYPE(SinkType)
+Q_DECLARE_METATYPE(SourceType)
+Q_DECLARE_METATYPE(SoundProperty)
+Q_DECLARE_METATYPE(SystemProperty)
+Q_DECLARE_METATYPE(MainConnectionType)
+
+Q_DECLARE_METATYPE(QList<ClassProperty>)
+Q_DECLARE_METATYPE(QList<SinkClasses>)
+Q_DECLARE_METATYPE(QList<SourceClasses>)
+Q_DECLARE_METATYPE(QList<SinkType>)
+Q_DECLARE_METATYPE(QList<SourceType>)
+Q_DECLARE_METATYPE(QList<SoundProperty>)
+Q_DECLARE_METATYPE(QList<MainConnectionType>)
+
+Q_DECLARE_METATYPE(QList<ushort>)
+
+
+class AudioManagerDBusInterface : public QObject
+{
+Q_OBJECT
+
+public:
+
+ AudioManagerDBusInterface(QObject* parent, QString serviceName, QString objectPath, QString interfaceName);
+ AudioManagerDBusInterface(QObject* parent=0);
+
+ QDBusMessage getDBusMessage(QString command);
+
+ Q_INVOKABLE ushort connect(QString sourceName, QString sinkName);
+ Q_INVOKABLE bool disconnect(ushort connectionID);
+ QList<SourceType> getMainSourceTypeList();
+ QList<SinkClasses> getMainSinkClassesList();
+ QList<SourceClasses> getMainSourceClassesList();
+
+ Q_INVOKABLE bool getListMainConnections();
+
+ Q_INVOKABLE bool getListMainSinks();
+
+ Q_INVOKABLE bool getListMainSources();
+
+ Q_INVOKABLE bool getListMainSinkClasses();
+
+ Q_INVOKABLE bool getListMainSourceClasses();
+
+ Q_INVOKABLE bool getListSystemProperties();
+
+ QString getConnectionResultString(short connectionState);
+
+ bool setVolume(QString sinkName, short value);
+
+ void connectToSignal(QString signalName, QObject* receiver, const char* slot);
+ void disconnectToSignal(QString signalName, QObject* receiver, const char* slot);
+ ushort getSourceIDFromName(QString name);
+ ushort getSinkIDFromName(QString name);
+ void log(QString message);
+
+signals:
+ void sinkAdded(QVariantMap sink);
+ void sinkRemoved(int index);
+ void sourceAdded(QVariantMap source);
+ void sourceRemoved(int index);
+ void connectionAdded(QVariantMap connection);
+ void connectionRemoved(int index);
+ void volumeChanged(int sinkid, int volume);
+ void initAMMonitorCompleted();
+
+private slots:
+ void onMainConnectionStateChanged(ushort mainConnectionID, short state);
+ void onVolumeChanged(ushort sinkID, short volume);
+ void onNumberOfMainConnectionsChanged();
+ void callAllDataForInit();
+ /*
+ void onSinkAdded(SinkType sink);
+ void onSinkRemoved(ushort sinkID);
+ void onSourceAdded(SourceType source);
+ void onSourceRemoved(ushort sourceID);
+ void onNumberOfSinkClassesChanged();
+ void onNumberOfSourceClassesChanged();
+ */
+
+private:
+ // Parent UI Object Pointer
+ QQuickView* mParent;
+
+ // Source, Sink and Connection state list
+
+ /*
+ const QString mSourceNameList[5] = {"MediaPlayer","NaviPlayer", "TTSPlayer", "Skype", "ReverseBeep"};
+ const QString mSinkNameList[2] = {ALSA_PRIMARY, ALSA_SECONDARY};
+ const QString mConnectionStateList[6] = {"Unknown", "Connecting", "Connected", "Disconnecting", "DisConnected", "Suspend"};
+ */
+
+ const QString mDBusMessageStringList[21];
+ QObject *mMainView;
+ QMap<ushort,QString> mCommandList;
+ QMap<QString,ushort> mSourceList;
+ QMap<QString,ushort> mSinkList;
+
+ // DBusConnection
+ QDBusConnection mDBusConnection;
+ QString mServiceName;
+ QString mObjectPath;
+ QString mInterfaceName;
+ short mLastError;
+
+
+ // List Of MainConnections
+ QList<short> mMainConnectionList;
+ QList<QVariantMap> mMainConnectionTypeList;
+
+ // List for Sinks
+ QList<SinkClasses> mMainSinkClassList;
+ QList<QVariantMap> mMainSinkList;
+
+
+ // List for Sources
+ QList<SourceClasses> mMainSourceClassList;
+ QList<QVariantMap> mMainSourceList;
+
+
+ // SystemProperty
+ QList<SystemProperty> mSystemPropList;
+
+ FILE *mTempLogFile;
+
+
+ // Declare Private functions
+
+ void initDBusInterface(QString serviceName, QString objectPath, QString interfacename);
+ void connectInternalSignalToSlot();
+};
+
+#endif // AUDIOMANAGERDBUSINTERFACE_H
diff --git a/code.js b/code.js
new file mode 100644
index 0000000..911c77c
--- /dev/null
+++ b/code.js
@@ -0,0 +1,376 @@
+.pragma library
+var amSources = [];
+var amSinks = [];
+var amConnections = [];
+var paSinkInputs = [];
+var paSinks = [];
+var paClients = [];
+var colorListByName = [["MEDIA","red"], ["NAVI","green"],["skype","blue"],["TextToSpeach","orange"], ["reverse","cyan"]];
+
+function saveAMConnection(connection) {
+ var exist = false;
+
+
+ for (var i = 0; i < amConnections.length; i++) {
+ var info = amConnections[i];
+
+ if (info.id == connection.id) {
+ exist = true;
+ amConnections[i] = connection;
+ break;
+ }
+ }
+
+ if (!exist)
+ amConnections[amConnections.length] = connection;
+ return connection;
+}
+
+function takeAMConnection(id) {
+ for (var i = 0; i < amConnections.length; i++) {
+ var info = amConnections[i];
+
+ if (info.id == id) {
+ amConnections.splice(i,1);
+ return info;
+ }
+ }
+
+ return null;
+}
+
+function findAMConnection(id) {
+ for (var i = 0; i < amConnections.length; i++) {
+ var info = amConnections[i];
+
+ if (info.id == id) {
+ return amConnections[i];
+ }
+ }
+ return null;
+}
+
+function savePAClient(client) {
+ var exist = false;
+
+ for (var i = 0; i < paClients.length; i++) {
+ var info = paClients[i];
+
+ if (info.index == client.index) {
+ exist = true;
+ paClients[i] = client;
+ break;
+ }
+ }
+
+ if (!exist)
+ paClients[paClients.length] = client;
+ return client;
+}
+
+function takePAClient(index) {
+ for (var i = 0; i < paClients.length; i++) {
+ var info = paClients[i];
+
+ if (info.index == index) {
+ paClients.splice(i,1);
+ return info;
+ }
+ }
+
+ return null;
+}
+
+function findPAClient(index) {
+ for (var i = 0; i < paClients.length; i++) {
+ var info = paClients[i];
+
+ if (info.index == index) {
+ return paClients[i];
+ }
+ }
+
+ return null;
+}
+
+
+function savePASinkInfo(sinkinfo) {
+ var exist = false;
+
+ for (var i = 0; i < paSinks.length; i++) {
+ var info = paSinks[i];
+
+ if (info.index == sinkinfo.index) {
+ exist = true;
+ paSinks[i] = sinkinfo;
+ break;
+ }
+ }
+
+ if (!exist)
+ paSinks[paSinks.length] = sinkinfo;
+ return sinkinfo;
+}
+
+function takePASinkInfo(index) {
+ for (var i = 0; i < paSinks.length; i++) {
+ var info = paSinks[i];
+
+ if (info.index == index) {
+ paSinks.splice(i,1);
+ return info;
+ }
+ }
+
+ return null;
+}
+
+function findPASinkInfo(index) {
+ for (var i = 0; i < paSinks.length; i++) {
+ var info = paSinks[i];
+
+ if (info.index == index) {
+ return paSinks[i];
+ }
+ }
+
+ return null;
+}
+
+function findPASinkInput(index) {
+ for(var i=0; i<paSinkInputs.length; i++) {
+ var si = paSinkInputs[i];
+ if(si.index == index)
+ return si;
+ }
+ return null;
+}
+
+function savePASinkInput(sinkinput) {
+ var exist = false;
+ for(var i=0; i<paSinkInputs.length; i++) {
+ var si = paSinkInputs[i];
+ if(si.index == sinkinput.index) {
+ exist = true;
+ paSinkInputs[i] = sinkinput;
+ break;
+ }
+ }
+ if(!exist)
+ paSinkInputs[paSinkInputs.length] = sinkinput;
+ return sinkinput;
+}
+
+
+function takePASinkInput(index) {
+ for(var i=0; i<paSinkInputs.length; i++) {
+ var si = paSinkInputs[i];
+ if(si.index == index) {
+ paSinkInputs.splice(i, 1);
+ return si;
+ }
+ }
+ return null;
+}
+
+var graphDataset = [];
+var colorList = ["red", "orange", "green", "blue", "black", "cyan"];
+var colorListLength = colorList.length;
+
+function Graph() {
+ var name = "";
+ var dataset;
+ var nextColorIndex = 0;
+}
+
+function Node() {
+ var nodeName = "";
+ var color ="";
+ var data;
+ var id = -1;
+ var removed = false;
+}
+
+function addGraphDataset(name) {
+ var newGraphDataset = new Graph();
+
+ newGraphDataset.name = name;
+ newGraphDataset.dataset = new Array();
+ newGraphDataset.nextColorIndex = 0;
+
+ graphDataset[graphDataset.length] = newGraphDataset;
+}
+
+function removeGraphDataset(name) {
+ for (var i = 0; i < graphDataset.length; i++) {
+ if (graphDataset[i].name == name) {
+ graphDataset.splice(i,1);
+ break;
+ }
+ }
+}
+
+function addGraphNode(graphName, nodeName, id, maxDatalength, defaultValue) {
+ var newNode = new Node();
+ newNode.data = new Array(maxDatalength);
+
+ for (var i = 0; i < maxDatalength; i++)
+ newNode.data[i] = defaultValue;
+
+ newNode.nodeName = nodeName;
+ newNode.id = id;
+
+ for (var i = 0; i < graphDataset.length; i++) {
+ var graphdata = graphDataset[i];
+ if (graphdata.name == graphName) {
+ newNode.color = getGraphNodeColor(graphName, nodeName);
+ graphDataset[i].dataset[graphdata.dataset.length] = newNode;
+ return;
+ }
+ }
+
+}
+
+function getGraphNodeColor(graphName, nodeName) {
+ console.log(colorList);
+ for (var i = 0; i < colorListByName.length; i++) {
+ if (nodeName == colorListByName[i][0]) {
+ console.log("RETURN FIXED COLOR : " + nodeName + " / " + colorListByName[i][1]);
+
+ return colorListByName[i][1];
+ }
+ }
+
+ for (var i = 0; i < graphDataset.length; i++) {
+ if (graphDataset[i].name == graphName) {
+ var index = graphDataset[i].nextColorIndex;
+ console.log("NEXT INDEX : " + index);
+
+ if (index == colorListLength) {
+ graphDataset[i].nextColorIndex = 0;
+ return colorList[0];
+ }
+ graphDataset[i].nextColorIndex++;
+
+ console.log("RETURN DEFAULT VALUE : " + colorList[index]);
+ return colorList[index];
+ }
+ }
+
+ console.log("RETURN DEFAULT VALUE : " + colorList[0]);
+ return colorList[0];
+}
+
+function updateGraphNode(graphName, nodeName, id, maxDatalength, value) {
+
+ for (var i = 0; i < graphDataset.length; i++) {
+ if (graphDataset[i].name == graphName && graphDataset[i].dataset !== "undefined") {
+ for (var j = 0; j < graphDataset[i].dataset.length; j++) {
+ console.log("UPDATE GRAPH NODE : " + graphDataset[i].name + " //// " + graphName + " //// " + nodeName + graphDataset[i].dataset[j].nodeName);
+ if (graphDataset[i].dataset[j].nodeName == nodeName) {
+ var nodeData = graphDataset[i].dataset[j];
+ var isRebirth = false;
+
+ // Node Rebirth
+ if (nodeData.id != id) {
+ console.log("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
+ console.log("REBIRTH NODE : " + nodeName);
+ console.log("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
+ var startIndexToFillMinus = graphDataset[i].dataset[j].data.length-1;
+ var filledData = graphDataset[i].dataset[j].data[startIndexToFillMinus -1] * -1;
+ for (var k = startIndexToFillMinus; k < maxDatalength-1; k++)
+ graphDataset[i].dataset[j].data[k] = filledData;
+ graphDataset[i].dataset[j].removed = false;
+ graphDataset[i].dataset[j].id = id;
+ isRebirth = true;
+ }
+
+ if (isRebirth || (!nodeData.removed && nodeData.id == id))
+ graphDataset[i].dataset[j].data[graphDataset[i].dataset[j].data.length] = value;
+
+ if (nodeData.data.length > maxDatalength)
+ graphDataset[i].dataset[j].data.splice(0,nodeData.data.length - maxDatalength);
+ return;
+ }
+ }
+ }
+ }
+
+ addGraphNode(graphName, nodeName, id, maxDatalength, value);
+}
+
+function takeGraphDataset(graphName) {
+ for (var i = 0; i < graphDataset.length; i++) {
+ var dataset = graphDataset[i];
+ if (dataset.name == graphName)
+ return dataset.dataset;
+ }
+ return 0;
+}
+
+function getGraphNodeData(graphName, nodeName) {
+ for (var i = 0; i < graphDataset.length; i++) {
+ var dataset = graphDataset[i];
+
+ if (dataset.name == graphName) {
+ for (var j = 0; j < dataset.dataset.length; j++) {
+ if (dataset.dataset[j].nodeName == nodeName)
+ return dataset.dataset[j];
+ }
+ }
+ }
+}
+
+function removeGraphNode(graphName, name, id) {
+
+ for (var i = 0; i < graphDataset.length; i++) {
+ if (graphDataset[i].name == graphName) {
+ for (var j = 0; j < graphDataset[i].dataset.length; j++) {
+ if (graphDataset[i].dataset[j].nodeName == name &&
+ graphDataset[i].dataset[j].id == id) {
+ console.log("REMOVE CHECK : " + graphName + " / " + name + " / " + id );
+ graphDataset[i].dataset[j].removed = true;
+ return;
+ }
+ }
+ }
+ }
+}
+
+function eraseGraphNode(graphName, name, id) {
+
+ for (var i = 0; i < graphDataset.length; i++) {
+ if (graphDataset[i].name == graphName) {
+ for (var j = 0; j < graphDataset[i].dataset.length; j++) {
+ if (graphDataset[i].dataset[j].nodeName == name &&
+ graphDataset[i].dataset[j].id == id &&
+ graphDataset[i].dataset[j].removed) {
+ console.log("ERASE IT " + name + " / " + id );
+ graphDataset[i].dataset.splice(j,1);
+ return;
+ }
+ }
+ }
+ }
+}
+function refreshGraphNode(graphName) {
+ for (var i = 0; i < graphDataset.length; i++) {
+ if (graphDataset[i].name == graphName) {
+ for (var j = 0; j < graphDataset[i].dataset.length; j++) {
+ var lastIndex = graphDataset[i].dataset[j].data.length-1;
+ var lastValue = graphDataset[i].dataset[j].data[lastIndex];
+ if (lastIndex < 0) {
+ eraseGraphNode(graphName, graphDataset[i].dataset[j].nodeName, graphDataset[i].dataset[j].id);
+ return;
+ }
+
+ graphDataset[i].dataset[j].data.splice(0,1);
+
+ if (!graphDataset[i].dataset[j].removed)
+ graphDataset[i].dataset[j].data[lastIndex] = lastValue;
+ }
+ }
+ }
+}
+
+
diff --git a/commandlist.txt b/commandlist.txt
new file mode 100644
index 0000000..b77abf5
--- /dev/null
+++ b/commandlist.txt
@@ -0,0 +1,21 @@
+Connect
+Disconnect
+SetVolume
+VolumeStep
+SetSinkMuteState
+SetMainSinkSoundProperty
+SetMainSourceSoundProperty
+SetSystemProperty
+GetListMainConnections
+GetListMainSinks
+GetListMainSources
+GetListMainSinkSoundProperties
+GetListMainSourceSoundProperties
+GetListSourceClasses
+GetListSinkClasses
+GetListSystemProperties
+GetTiminigInformation
+getListSinkMainNotificationConfigurations
+getListSourceMainNotificationConfigurations
+setSinkMainNotificationConfiguration
+setSourceMainNotificationConfiguration
diff --git a/deployment.pri b/deployment.pri
new file mode 100644
index 0000000..5441b63
--- /dev/null
+++ b/deployment.pri
@@ -0,0 +1,27 @@
+android-no-sdk {
+ target.path = /data/user/qt
+ export(target.path)
+ INSTALLS += target
+} else:android {
+ x86 {
+ target.path = /libs/x86
+ } else: armeabi-v7a {
+ target.path = /libs/armeabi-v7a
+ } else {
+ target.path = /libs/armeabi
+ }
+ export(target.path)
+ INSTALLS += target
+} else:unix {
+ isEmpty(target.path) {
+ qnx {
+ target.path = /tmp/$${TARGET}/bin
+ } else {
+ target.path = /opt/$${TARGET}/bin
+ }
+ export(target.path)
+ }
+ INSTALLS += target
+}
+
+export(INSTALLS)
diff --git a/images/genivi-logo.png b/images/genivi-logo.png
new file mode 100644
index 0000000..6d8aafe
--- /dev/null
+++ b/images/genivi-logo.png
Binary files differ
diff --git a/images/icon_car.png b/images/icon_car.png
new file mode 100644
index 0000000..cd58fd9
--- /dev/null
+++ b/images/icon_car.png
Binary files differ
diff --git a/images/icon_microphone.png b/images/icon_microphone.png
new file mode 100644
index 0000000..ab18517
--- /dev/null
+++ b/images/icon_microphone.png
Binary files differ
diff --git a/images/icon_music.png b/images/icon_music.png
new file mode 100644
index 0000000..b3d7b63
--- /dev/null
+++ b/images/icon_music.png
Binary files differ
diff --git a/images/icon_phone.png b/images/icon_phone.png
new file mode 100644
index 0000000..bcda19d
--- /dev/null
+++ b/images/icon_phone.png
Binary files differ
diff --git a/images/icon_reply.png b/images/icon_reply.png
new file mode 100644
index 0000000..7862a2f
--- /dev/null
+++ b/images/icon_reply.png
Binary files differ
diff --git a/images/pulseaudio-logo.png b/images/pulseaudio-logo.png
new file mode 100644
index 0000000..158a9e3
--- /dev/null
+++ b/images/pulseaudio-logo.png
Binary files differ
diff --git a/images/speaker.png b/images/speaker.png
new file mode 100644
index 0000000..054af9c
--- /dev/null
+++ b/images/speaker.png
Binary files differ
diff --git a/images/volume-up.png b/images/volume-up.png
new file mode 100644
index 0000000..02a377a
--- /dev/null
+++ b/images/volume-up.png
Binary files differ
diff --git a/images/windriver-logo.png b/images/windriver-logo.png
new file mode 100644
index 0000000..54feaad
--- /dev/null
+++ b/images/windriver-logo.png
Binary files differ
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..b6ac629
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,56 @@
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QQmlContext>
+#include <QQuickWindow>
+#include <QQuickView>
+#include <QUrl>
+#include <QTimer>
+#include <QScreen>
+#include "pulseaudiocontroller.h"
+#include "audiomanagerdbusinterface.h"
+#include "pulseplayer.h"
+
+#define AM_POC_SURFACE_ID 20
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ qmlRegisterType<PulsePlayer>("com.windriver.ammonitor", 1, 0, "PAPlayer");
+ qmlRegisterType<PulseAudioController>("com.windriver.ammonitor", 1, 0, "PAClient");
+ qmlRegisterType<AudioManagerDBusInterface>("com.windriver.ammonitor", 1, 0, "AMClient");
+
+
+ QQuickView view;
+
+ view.setProperty("IVI-Surface-ID", AM_POC_SURFACE_ID);
+
+ if(app.arguments().contains("--debug")) {
+ view.setSource(QUrl("main.qml"));
+ QSize size(1365, 768);
+ if(app.arguments().contains("--sd"))
+ size = QSize(1024, 768-68 /* bottom panel height */);
+ view.rootObject()->setWidth(size.width());
+ view.rootObject()->setHeight(size.height());
+ view.resize(size);
+ } else if(app.arguments().contains("--help")) {
+ qCritical() << "Usage: ";
+ qCritical() << " " << app.arguments().at(0) << " [Options]";
+ qCritical() << "";
+ qCritical() << "[Options]";
+ qCritical() << " --debug show as window size 1366x768 (default is fullscreen)";
+ qCritical() << " and load QML resources from filesystem";
+ qCritical() << " --sd show as window size 1024x700";
+ qCritical() << " --help show help options";
+ return 1;
+ }else {
+ view.setSource(QUrl("qrc:///main.qml"));
+ view.showFullScreen();
+ QSize size = app.primaryScreen()->size();
+ view.rootObject()->setWidth(size.width());
+ view.rootObject()->setHeight(size.height());
+ }
+ view.show();
+
+ return app.exec();
+}
diff --git a/main.qml b/main.qml
new file mode 100644
index 0000000..677cd03
--- /dev/null
+++ b/main.qml
@@ -0,0 +1,99 @@
+import QtQuick 2.1
+import QtQuick.Window 2.0
+
+Rectangle {
+ id: root
+ //color: "lightgray"
+ width: 1024
+ height: 768
+
+/* XXX for next phase
+ VisualItemModel {
+ id: itemModel
+ Rectangle {
+ width: view.width; height: view.height
+ Diagram {
+ //width: 1024*parent.height/768
+ //height: 768*width/1024
+ anchors.fill: parent
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+ Rectangle {
+ width: view.width; height: view.height
+ color: "#F0FFF7"
+ Text { text: "Policy Rule File Viewer"; font.bold: true; anchors.centerIn: parent }
+ }
+ }
+*/
+
+ Rectangle {
+ id: topMenu
+ //color: "lightGray"
+ width: parent.width
+ height: parent.height/12
+ Image {
+ height: parent.height*2/3
+ fillMode: Image.PreserveAspectFit
+ source: "images/windriver-logo.png"
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ Text {
+ text: "GENIVI® Audio Manager Monitor"
+ anchors.right: parent.right
+ font.pointSize: parent.height / 3
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.rightMargin: parent.width/40
+
+ }
+ }
+ Diagram {
+ anchors.top: topMenu.bottom
+ //width: 1024*parent.height/768
+ //height: 768*width/1024
+ width: parent.width
+ height: parent.height * 11/12
+ anchors.horizontalCenter: parent.horizontalCenter
+ }
+
+/* XXX for next phase
+ ListView {
+ id: view
+ anchors { fill: parent; bottomMargin: pageIndexer.height ; topMargin: topMenu.height}
+ model: itemModel
+ preferredHighlightBegin: 0; preferredHighlightEnd: 0
+ highlightRangeMode: ListView.StrictlyEnforceRange
+ orientation: ListView.Horizontal
+ snapMode: ListView.SnapOneItem; flickDeceleration: 2000
+ }
+
+ Rectangle {
+ id: pageIndexer
+ width: parent.width; height: parent.height/25
+ //anchors { top: view.bottom; bottom: parent.bottom }
+ //color: "gray"
+ anchors.bottom: parent.bottom
+
+ Row {
+ anchors.centerIn: parent
+ spacing: 20
+
+ Repeater {
+ model: itemModel.count
+
+ Rectangle {
+ width: 5; height: 5
+ radius: 3
+ color: view.currentIndex == index ? "black" : "darkGray"
+
+ MouseArea {
+ width: 20; height: 20
+ anchors.centerIn: parent
+ onClicked: view.currentIndex = index
+ }
+ }
+ }
+ }
+ }
+*/
+}
diff --git a/pc-env b/pc-env
new file mode 100644
index 0000000..9b3de1b
--- /dev/null
+++ b/pc-env
@@ -0,0 +1,22 @@
+OLDPS1=$PS1
+PS1="(AM-Monitor Testing) "
+
+function deactivate () {
+ echo "unload alsa primary"
+ pactl unload-module $MOD_SINKP
+ echo "unload alsa secondary"
+ pactl unload-module $MOD_SINKS
+ PS1=$OLDPS1
+}
+
+MOD_SINKP=`pactl load-module module-remap-sink sink_name="AlsaPrimary"`
+MOD_SINKS=`pactl load-module module-remap-sink sink_name="AlsaSecondary"`
+
+
+pacmd list-sinks
+#pacmd list-sink-inputs
+#pacmd list-sources
+#pacmd list-source-outputs
+
+echo "2 sink remapped"
+echo "(Run 'deactivate' to finish testing env)"
diff --git a/pulseaudiocontroller.cpp b/pulseaudiocontroller.cpp
new file mode 100644
index 0000000..fae01d3
--- /dev/null
+++ b/pulseaudiocontroller.cpp
@@ -0,0 +1,274 @@
+#include "pulseaudiocontroller.h"
+#include <QDebug>
+
+class MainLoopLocker {
+public:
+ MainLoopLocker(pa_threaded_mainloop *mainloop) : m_mainloop(mainloop) {
+ pa_threaded_mainloop_lock(mainloop);
+ }
+ ~MainLoopLocker() {
+ pa_threaded_mainloop_unlock(m_mainloop);
+ }
+private:
+ pa_threaded_mainloop *m_mainloop;
+};
+
+PulseAudioController::PulseAudioController(QObject *parent) :
+ QObject(parent), mController(0)
+{
+ setupController();
+
+}
+
+void PulseAudioController::setupController()
+{
+ if (!mController) {
+ mController = new InternalController();
+ mWorker = new QThread;
+ mController->moveToThread(mWorker);
+ mWorker->start();
+
+ connect(mController, SIGNAL(sinkInputChanged(QVariantMap)), this, SIGNAL(sinkInputChanged(QVariantMap)));
+ connect(mController, SIGNAL(sinkInputRemoved(int)), this, SIGNAL(sinkInputRemoved(int)));
+
+ connect(mController, SIGNAL(sinkInfoChanged(QVariantMap)), this, SIGNAL(sinkInfoChanged(QVariantMap)));
+ connect(mController, SIGNAL(sinkInfoRemoved(int)), this, SIGNAL(sinkInfoRemoved(int)));
+ connect(mController, SIGNAL(clientChanged(QVariantMap)), this, SIGNAL(clientChanged(QVariantMap)));
+ connect(mController, SIGNAL(clientRemoved(int)), this, SIGNAL(clientRemoved(int)));
+
+ }
+}
+
+
+
+
+InternalController::InternalController(PulseAudioController *parent) : QObject(parent)
+{
+ mMainloop = pa_threaded_mainloop_new();
+
+ if (pa_threaded_mainloop_start(mMainloop)) {
+ qDebug("Unable to start pulseaudio mainloop");
+ pa_threaded_mainloop_free(mMainloop);
+ mMainloop = 0;
+ return;
+ }
+
+ mApi = pa_threaded_mainloop_get_api(mMainloop);
+
+ QTimer::singleShot(0, this, SLOT(connectToContext()));
+}
+
+void InternalController::waitForOperation(pa_operation *operation)
+{
+ while(pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
+ pa_threaded_mainloop_wait(mMainloop);
+ pa_operation_unref(operation);
+}
+
+void InternalController::connectToContext()
+{
+ MainLoopLocker looplock(mMainloop);
+ mContext = pa_context_new(mApi,"AM Monitor PulseAudio Observer");
+
+ if (!mContext) {
+ qDebug("Cannot create new pulseaudio context");
+ pa_threaded_mainloop_free(mMainloop);
+ return;
+ }
+
+ pa_context_set_state_callback(mContext, &InternalController::contextStateCallback, this);
+ pa_context_set_event_callback(mContext, NULL, NULL);
+
+ if (pa_context_connect(mContext,NULL, (pa_context_flags_t)0, NULL) < 0) {
+ qDebug("Cannot create a connection to the pulseaudio context");
+ pa_context_unref(mContext);
+ pa_threaded_mainloop_free(mMainloop);
+ return;
+ }
+}
+
+void InternalController::setupSubscription()
+{
+ qDebug("SETUP SUBSCRIPTION");
+ pa_operation *o;
+ pa_context_set_subscribe_callback(mContext,&InternalController::contextSubscriptionCallback, this);
+
+ if(!(o = pa_context_subscribe(mContext,
+ (pa_subscription_mask_t)
+ (PA_SUBSCRIPTION_MASK_CLIENT
+ |PA_SUBSCRIPTION_MASK_SINK_INPUT
+ | PA_SUBSCRIPTION_MASK_SINK
+ /*|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT*/),
+ &InternalController::contextSuccessCallback,this)))
+ qCritical() << "pa_context_subscribe() failed";
+ pa_operation_unref(o);
+
+}
+
+
+
+void InternalController::contextStateCallback(pa_context *context, void *userdata)
+{
+ InternalController *controller = reinterpret_cast<InternalController*>(userdata);
+
+ pa_context_state_t state = pa_context_get_state(context);
+ switch(state) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ case PA_CONTEXT_READY:
+ controller->setupSubscription();
+ break;
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ break;
+ }
+
+ pa_threaded_mainloop_signal(controller->getMainloop(),0);
+}
+
+void InternalController::clientCallback(pa_context *context, const pa_client_info *info, int eol, void *userdata) {
+ (void)eol;
+ (void)context;
+ InternalController *controller = reinterpret_cast<InternalController*>(userdata);
+ if (!info)
+ return;
+
+ QMap<QString, QVariant> client;
+ client["index"] = info->index;
+ client["name"] = QString::fromLatin1(info->name);
+ client["ownerModule"] = info->owner_module;
+ client["driver"] = QString::fromLatin1(info->driver);
+
+ emit controller->clientChanged(client);
+}
+
+
+
+void InternalController::sinkInputCallback(pa_context *context, const pa_sink_input_info *info, int eol, void *userdata) {
+ (void)eol;
+ (void)context;
+ InternalController *controller = reinterpret_cast<InternalController*>(userdata);
+ if (!info)
+ return;
+
+ QMap<QString, QVariant> sinkinput;
+ sinkinput["index"] = info->index;
+ sinkinput["name"] = QString::fromLatin1(info->name);
+ sinkinput["volume"] = InternalController::getVolume(info->volume,0);
+ sinkinput["clientIndex"] = info->client;
+ sinkinput["sinkIndex"] = info->sink;
+ sinkinput["corked"] = info->corked;
+ sinkinput["appName"] = QString(pa_proplist_gets(info->proplist,"application.name"));
+ sinkinput["role"] = QString(pa_proplist_gets(info->proplist,"media.role"));
+ //sinkinput["propList"] = info->proplist;
+ //sinkinput["sampleSpec"] = info->sample_spec;
+ //sinkinput["channelMap"] = info->channel_map;
+
+ emit controller->sinkInputChanged(sinkinput);
+}
+
+void InternalController::sinkInfoCallback(pa_context *context, const pa_sink_info *info, int eol, void *userdata)
+{
+ (void)eol;
+ (void)context;
+ InternalController *controller = reinterpret_cast<InternalController*>(userdata);
+
+ if (!info)
+ return;
+
+ QMap<QString, QVariant> sinkInfo;
+ sinkInfo["index"] = info->index;
+ sinkInfo["name"] = info->name;
+ sinkInfo["volume"] = InternalController::getVolume(info->volume, 0);
+ sinkInfo["mute"] = info->mute;
+
+ emit controller->sinkInfoChanged(sinkInfo);
+}
+
+int InternalController::getVolume(pa_cvolume volume, int channel)
+{
+ char ret[PA_VOLUME_SNPRINT_MAX];
+ pa_volume_snprint(ret, sizeof(ret), volume.values[channel]);
+ return atoi(ret);
+}
+
+void InternalController::contextSubscriptionCallback(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata)
+{
+ InternalController *controller = reinterpret_cast<InternalController*>(userdata);
+ switch( t&PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
+ case PA_SUBSCRIPTION_EVENT_CLIENT:
+ {
+ qDebug() << "XXXXXXXevent: " << (t&PA_SUBSCRIPTION_EVENT_TYPE_MASK);
+ if((t&PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ emit controller->clientRemoved(index);
+ qDebug() << "removed client" << index;
+ } else {
+ qDebug() << "added client" << index;
+ pa_operation *o;
+ if (!(o=pa_context_get_client_info(context, index, &InternalController::clientCallback, controller)))
+ qCritical("pa_context_get_sink_input_info() failed");
+ pa_operation_unref(o);
+ }
+ break;
+ }
+ case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
+ {
+ qDebug() << "event: " << (t&PA_SUBSCRIPTION_EVENT_TYPE_MASK);
+ if((t&PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ emit controller->sinkInputRemoved(index);
+ qDebug() << "removed sink input" << index;
+ } else {
+ qDebug() << "added sink input" << index;
+ pa_operation *o;
+ if (!(o=pa_context_get_sink_input_info(context, index, &InternalController::sinkInputCallback, controller)))
+ qCritical("pa_context_get_sink_input_info() failed");
+ pa_operation_unref(o);
+ }
+ }
+ break;
+ case PA_SUBSCRIPTION_EVENT_SINK:
+ {
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
+ emit controller->sinkInfoRemoved(index);
+ } else {
+ pa_operation *o;
+ if (!(o=pa_context_get_sink_info_by_index(context, index, &InternalController::sinkInfoCallback, controller)))
+ qCritical("pa_context_get_sink_info_by_index() failed");
+ pa_operation_unref(o);
+ }
+ }
+ break;
+ case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
+ break;
+ }
+ pa_threaded_mainloop_signal(controller->getMainloop(),0);
+}
+
+void InternalController::contextSuccessCallback(pa_context *context, int success, void *userdata)
+{
+ (void)success;
+ InternalController *controller = reinterpret_cast<InternalController*>(userdata);
+ pa_operation *o;
+ if (!(o=pa_context_get_client_info_list(context, &InternalController::clientCallback, controller)))
+ qCritical("pa_context_get_client_info_list() failed");
+ pa_operation_unref(o);
+
+ if (!(o=pa_context_get_sink_input_info_list(context, &InternalController::sinkInputCallback, controller)))
+ qCritical("pa_context_get_sink_input_info_list() failed");
+ pa_operation_unref(o);
+
+ if (!(o=pa_context_get_sink_info_list(context, &InternalController::sinkInfoCallback, controller)))
+ qCritical("pa_context_get_sink_info_list() failed");
+ pa_operation_unref(o);
+
+ pa_threaded_mainloop_signal(controller->getMainloop(),0);
+}
+
+InternalController::~InternalController()
+{
+
+}
+
diff --git a/pulseaudiocontroller.h b/pulseaudiocontroller.h
new file mode 100644
index 0000000..bdef9be
--- /dev/null
+++ b/pulseaudiocontroller.h
@@ -0,0 +1,94 @@
+#ifndef PULSEAUDIOCONTROLLER_H
+#define PULSEAUDIOCONTROLLER_H
+
+#include <QObject>
+#include <QThread>
+#include <QMap>
+#include <QtQml>
+#include <thread>
+#include <pulse/mainloop.h>
+#include <pulse/thread-mainloop.h>
+#include <pulse/context.h>
+#include <pulse/pulseaudio.h>
+#include <pulse/proplist.h>
+
+
+class InternalController;
+
+class PulseLocker {
+public:
+ PulseLocker(pa_threaded_mainloop *mainloop)
+ : mMainloop(mainloop) {
+ pa_threaded_mainloop_lock(mainloop);
+ }
+ ~PulseLocker() {
+ pa_threaded_mainloop_unlock(mMainloop);
+ }
+private:
+ pa_threaded_mainloop *mMainloop;
+};
+
+class PulseAudioController : public QObject
+{
+ Q_OBJECT
+public:
+ explicit PulseAudioController(QObject *parent = 0);
+ void setupController();
+
+
+signals:
+ void sinkInputChanged(QVariantMap sinkinput);
+ void sinkInputRemoved(int index);
+ void sinkInfoChanged(QVariantMap sinkinfo);
+ void sinkInfoRemoved(int index);
+ void clientChanged(QVariantMap client);
+ void clientRemoved(int index);
+
+
+private:
+ InternalController *mController;
+ QThread *mWorker;
+
+
+};
+
+class InternalController : public QObject
+{
+ Q_OBJECT
+public:
+ explicit InternalController(PulseAudioController *parent = 0);
+
+ pa_threaded_mainloop* getMainloop() { return mMainloop; }
+
+ ~InternalController();
+signals:
+ void sinkInputChanged(QVariantMap sinkinput);
+ void sinkInputRemoved(int index);
+ void sinkInfoChanged(QVariantMap sinkinfo);
+ void sinkInfoRemoved(int index);
+ void clientChanged(QVariantMap client);
+ void clientRemoved(int index);
+
+private slots:
+ void connectToContext();
+
+private:
+ PulseAudioController* mParent;
+ pa_threaded_mainloop *mMainloop;
+ pa_mainloop_api* mApi;
+ pa_context* mContext;
+
+ void setupSubscription();
+
+ static void contextStateCallback(pa_context *context, void *userdata);
+ static void contextSubscriptionCallback(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata);
+ static void sinkInputCallback(pa_context *, const pa_sink_input_info *, int, void *);
+ static void clientCallback(pa_context *, const pa_client_info *, int, void *);
+ static void sinkInfoCallback(pa_context *context, const pa_sink_info *info,int eol, void *userdata);
+ static void contextSuccessCallback(pa_context *context, int success, void *userdata);
+ static void eventCallback(pa_context *context, const char *name, pa_proplist *pl, void *userdata);
+ static int getVolume(pa_cvolume volume, int channel = 0);
+ void waitForOperation(pa_operation *operation);
+};
+
+#endif // PULSEAUDIOCONTROLLER_H
diff --git a/pulseplayer.cpp b/pulseplayer.cpp
new file mode 100644
index 0000000..ae924a7
--- /dev/null
+++ b/pulseplayer.cpp
@@ -0,0 +1,347 @@
+#include "pulseplayer.h"
+
+//PulsePlayer::PulsePlayer(QString filePath, QString sinkName, QString sourceName, QObject *parent) :
+// QObject(parent)
+PulsePlayer::PulsePlayer(QObject *parent) :
+ QObject(parent), mPlayer(0), mWorker(0)
+{
+}
+
+void PulsePlayer::play()
+{
+ QMap<QString, QString> prop;
+ prop.insert("media.role", mRoleName);
+
+ if(!mPlayer) {
+ mPlayer = new InternalPlayer(mFileName, mSinkName, mSourceName, prop, this);
+ mWorker = new QThread;
+ mPlayer->moveToThread(mWorker);
+ mWorker->start();
+ connect(this,SIGNAL(doPlay(QString)),mPlayer,SLOT(play(QString)));
+ connect(this,SIGNAL(doStop(QString)),mPlayer,SLOT(stop(QString)));
+ connect(mPlayer,SIGNAL(playStateChanged()),this,SIGNAL(playStateChanged()));
+ }
+
+ mPlayer->mIsAlive = true;
+ while(pa_context_get_state(mPlayer->getContext()) != PA_CONTEXT_READY) {
+ //qDebug() << " Waiting for ready state";
+ }
+
+ emit doPlay(mPlayer->mSourceName);
+}
+
+void PulsePlayer::stop()
+{
+ qDebug() << "STOP : " << mPlayer->mSourceName;
+ mPlayer->mIsAlive = false;
+ emit doStop(mPlayer->mSourceName);
+}
+
+InternalPlayer::InternalPlayer(QString fileName, QString sinkName, QString sourceName, QMap<QString, QString> proplist, QObject *parent)
+ : QObject(parent)
+{
+
+ mIsPlaying = false;
+ mIsInitSuccess = false;
+ mBufferIndex = 0;
+ mBufferLength = 0;
+ mBuffer = 0;
+ mStream = 0;
+ mAttrBuff = (pa_buffer_attr*)malloc(sizeof(pa_buffer_attr));
+ mAttrBuff->maxlength = (uint32_t) -1;
+ mAttrBuff->prebuf = (uint32_t) -1;
+ mAttrBuff->tlength = (uint32_t) -1;
+ mAttrBuff->minreq = 8192;
+ mDevice = NULL;
+ mFileName = fileName;
+
+ mPropMap = proplist;
+ mPropList = pa_proplist_new();
+ QMap<QString, QString>::iterator i = mPropMap.begin();
+
+ for(;i != mPropMap.end(); i++) {
+ QString key = i.key();
+ QString value = i.value();
+ pa_proplist_sets(mPropList, key.toUtf8().constData(), value.toUtf8().constData());
+ qDebug() << "Prop List Set " << key.toUtf8().constData() << " / " << value.toUtf8().constData();
+ }
+
+ mFile = new QFile(QString(":/")+fileName);
+ qDebug() << "FILE NAME : " << fileName << " , Exists = " << mFile->exists();
+ bool opened = mFile->open(QIODevice::ReadOnly);
+
+ mSinkName = sinkName;
+ mSourceName = sourceName;
+ if (!opened) {
+ qDebug() << "Cannot open wave file";
+ return;
+ } else {
+ QByteArray content = mFile->readAll();
+ QDataStream header(&content, QIODevice::ReadOnly);
+ header.setByteOrder(QDataStream::LittleEndian);
+ char tempBuffer[1024];
+ short channel;
+ int sampleRate;
+
+ header.readRawData(tempBuffer,22);
+ header >> channel;
+ header >> sampleRate;
+
+ content.clear();
+ qDebug() << "CHANNEL : " << channel;
+ qDebug() << "sampleRate : " << sampleRate;
+
+ mPASampleSpec.channels = channel;
+ mPASampleSpec.format = PA_SAMPLE_S16LE;
+ mPASampleSpec.rate = sampleRate;
+ mFile->close();
+ }
+
+ setupPulseAudio();
+}
+
+bool InternalPlayer::setupPulseAudio()
+{
+ mMainloop = pa_threaded_mainloop_new();
+ if (pa_threaded_mainloop_start(mMainloop)) {
+ qDebug() << "Unable to start pulseaudio threaded mainloop";
+ pa_threaded_mainloop_free(mMainloop);
+ mMainloop= 0;
+ return false;
+ }
+
+ mApi = pa_threaded_mainloop_get_api(mMainloop);
+ mContext = pa_context_new(mApi, "AM Monitor Player");
+ pa_threaded_mainloop_lock(mMainloop);
+ pa_context_set_state_callback(mContext, &InternalPlayer::contextStateCallback,this);
+
+ if (pa_context_connect(mContext, NULL, (pa_context_flags_t)0, NULL)) {
+ qDebug() << " Cannot connect to context" << pa_strerror(pa_context_errno(mContext));
+ pa_context_unref(mContext);
+ pa_threaded_mainloop_free(mMainloop);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(mMainloop);
+
+ return true;
+}
+
+void InternalPlayer::setupStream()
+{
+ while(pa_context_get_state(mContext) == PA_OPERATION_RUNNING)
+ pa_threaded_mainloop_wait(mMainloop);
+
+ // Connect or Reconnect the stream
+ if(mStream) {
+ qDebug() << "Reconnect the stream";
+ pa_stream_disconnect(mStream);
+ pa_stream_unref(mStream);
+ mStream = 0;
+ }
+
+ mStream = pa_stream_new_with_proplist(mContext, mFile->fileName().toUtf8().constData(), &mPASampleSpec,NULL, mPropList);
+ qDebug() << "Stream created";
+
+ pa_stream_connect_playback(mStream, mDevice, mAttrBuff, (pa_stream_flags_t)0, NULL, NULL);
+ pa_stream_set_state_callback(mStream, &InternalPlayer::streamStateCallback, this);
+}
+
+void InternalPlayer::waitForOperation(pa_operation *)
+{
+ while(pa_context_get_state(mContext) == PA_OPERATION_RUNNING)
+ pa_threaded_mainloop_wait(mMainloop);
+
+ pa_context_unref(mContext);
+}
+
+void InternalPlayer::streamStateCallback(pa_stream *stream, void *userdata)
+{
+ InternalPlayer *player = reinterpret_cast<InternalPlayer*>(userdata);
+ qDebug() << "stream State Callback " << QString::number(pa_stream_get_state(stream));
+ switch(pa_stream_get_state(stream)) {
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_TERMINATED:
+ case PA_STREAM_CREATING:
+ break;
+ case PA_STREAM_READY:
+ pa_stream_set_write_callback(stream, &InternalPlayer::streamWriteCallback, userdata);
+ qDebug() << "Start to play";
+ break;
+ case PA_STREAM_FAILED:
+ break;
+ }
+
+ pa_threaded_mainloop_signal(player->getMainloop(),0);
+}
+
+void InternalPlayer::streamDrainCallback(pa_stream *, int success, void *userdata)
+{
+ InternalPlayer *player = reinterpret_cast<InternalPlayer*>(userdata);
+ //pa_operation *o = NULL;
+ pa_context *context = player->getContext();
+ qDebug() << "stream Drain Callback";
+
+ if (!success) {
+ qDebug("Failed to drain stream : %s", pa_strerror(pa_context_errno(context)));
+ return;
+ }
+
+ /*
+ pa_stream_disconnect(stream);
+ pa_stream_unref(stream);
+ stream = NULL;
+ player->mIsAlive = false;
+
+ if (!(o = pa_context_drain(context,&InternalPlayer::contextDrainCallback,userdata))) {
+ qDebug("Failed to context drain");
+ pa_context_disconnect(context);
+ } else {
+ pa_operation_unref(o);
+ }
+
+ */
+ player->disconnectStream();
+ pa_threaded_mainloop_signal(player->getMainloop(),0);
+}
+
+void InternalPlayer::disconnectStream()
+{
+ pa_operation *o = NULL;
+ pa_stream_disconnect(mStream);
+ pa_stream_unref(mStream);
+ mStream = 0;
+ mIsAlive = false;
+
+ if (!(o = pa_context_drain(mContext,&InternalPlayer::contextDrainCallback, this))) {
+ qDebug("Failed to context drain");
+ pa_context_disconnect(mContext);
+ } else {
+ pa_operation_unref(o);
+ }
+}
+
+void InternalPlayer::streamWriteCallback(pa_stream *stream, size_t length, void *userdata)
+{
+ InternalPlayer *player = reinterpret_cast<InternalPlayer*>(userdata);
+ player->playInternal(stream, length, userdata);
+ pa_threaded_mainloop_signal(player->getMainloop(),0);
+}
+void InternalPlayer::contextDrainCallback(pa_context *, void *userdata)
+{
+ InternalPlayer *player = reinterpret_cast<InternalPlayer*>(userdata);
+
+ qDebug() << " Context Drain Callback";
+ pa_threaded_mainloop_signal(player->getMainloop(),0);
+}
+
+void InternalPlayer::playInternal(pa_stream *stream, size_t length, void *userdata)
+{
+ InternalPlayer *player = reinterpret_cast<InternalPlayer*>(userdata);
+ pa_operation *o = NULL;
+
+
+ mBufferLength = length;
+
+ //qDebug(" playInternal : %d, %d", mBufferLength, player->isPlaying());
+
+ while (mBufferLength >0 && player->isPlaying()) {
+
+ mBuffer = mFile->read(mBufferLength);
+ int l = mBuffer.size();
+
+ if (l <= 0) {
+ mIsPlaying = false;
+ emit playStateChanged();
+ pa_stream_set_write_callback(stream, NULL, NULL);
+ if (!(o = pa_stream_drain(stream, &InternalPlayer::streamDrainCallback, this))) {
+ qDebug("pa_stream_drain failed : %s", pa_strerror(pa_context_errno(mContext)));
+ }
+ if(o)
+ pa_operation_unref(o);
+ break;
+ }
+
+ if (pa_stream_write(stream, (uint8_t*)mBuffer.constData(), l, NULL, 0, PA_SEEK_RELATIVE) < 0) {
+ qDebug("pa_stream_write() failed : %s", pa_strerror(pa_context_errno(mContext)));
+ break;
+ }
+
+ mBufferLength -= l;
+ mBufferIndex += l;
+ if (!mBufferLength) {
+ mBufferLength = mBufferIndex = 0;
+ }
+ }
+}
+
+void InternalPlayer::contextStateCallback(pa_context *context, void *userdata)
+{
+ InternalPlayer *player = reinterpret_cast<InternalPlayer*>(userdata);
+
+ pa_context_state_t state = pa_context_get_state(context);
+ qDebug() << "Context Received " << QString::number(state);
+ switch(state) {
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ case PA_CONTEXT_READY:
+ // Ready to play, Start setup the stream
+ break;
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ case PA_CONTEXT_UNCONNECTED:
+ break;
+ }
+
+ pa_threaded_mainloop_signal(player->getMainloop(),0);
+}
+
+bool InternalPlayer::openFile()
+{
+ if (!mFile->exists()) {
+ qDebug() << "File Not exists";
+ return false;
+ }
+
+ if (!mFile->isOpen()) {
+ mFile->open(QIODevice::ReadOnly);
+ }
+ mFile->seek(22);
+ return true;
+}
+
+void InternalPlayer::play(QString sourceName)
+{
+
+ mIsPlaying = true;
+ qDebug() << "Try to play";
+ if (!openFile()) {
+ qDebug() << "Error occured";
+ goto finish;
+ }
+
+ if (sourceName != mSourceName) {
+ qDebug() << "Different source name " << sourceName << " / " << mSourceName;
+ return;
+ }
+
+ setupStream();
+ emit playStateChanged();
+ return;
+finish:
+ mIsPlaying = false;
+ mIsAlive = false;
+ emit playStateChanged();
+}
+
+void InternalPlayer::stop(QString sourceName)
+{
+ qDebug() << "SOURCE NAME : " << sourceName << " / " << mSourceName;
+ if (sourceName == mSourceName) {
+ mIsPlaying = false;
+ disconnectStream();
+ emit playStateChanged();
+ }
+}
diff --git a/pulseplayer.h b/pulseplayer.h
new file mode 100644
index 0000000..0e33b7f
--- /dev/null
+++ b/pulseplayer.h
@@ -0,0 +1,132 @@
+#ifndef PULSEPLAYER_H
+#define PULSEPLAYER_H
+
+#include <QFile>
+#include <QByteArray>
+#include <QDataStream>
+#include <QIODevice>
+#include <QObject>
+#include <QThread>
+#include <QString>
+#include <QDebug>
+#include <pulse/simple.h>
+#include <pulse/error.h>
+#include <pulse/thread-mainloop.h>
+#include <pulse/context.h>
+#include <pulse/pulseaudio.h>
+#include <pulse/proplist.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pulseaudiocontroller.h>
+
+#define PA_BUF_SIZE 2048
+class InternalPlayer : public QObject
+{
+ Q_OBJECT
+public:
+ InternalPlayer(QString fileName, QString sinkName, QString sourceName, QMap<QString, QString> proplist, QObject *parent = 0);
+ QString mFilePath;
+ QString mSourceName;
+ QString mSinkName;
+ bool mIsAlive;
+
+ pa_threaded_mainloop* getMainloop() { return mMainloop; }
+ pa_context* getContext() { return mContext; }
+ bool isPlaying() { return mIsPlaying; }
+ char* getDevice() { return mDevice; }
+ pa_buffer_attr* gerAttrBuff() { return mAttrBuff; }
+ void setupStream();
+ void disconnectStream();
+
+public slots:
+ void play(QString sourceName);
+ void stop(QString sourceName);
+
+signals:
+ void playStateChanged();
+
+private:
+ QMap<QString,QString> mPropMap;
+ QString mFileName;
+ QFile *mFile;
+ int mLastError;
+ bool mIsInitSuccess;
+ bool mIsPlaying;
+ //void *mBuffer;
+ QByteArray mBuffer;
+ size_t mBufferIndex;
+ size_t mBufferLength;
+ pa_buffer_attr *mAttrBuff;
+
+
+ // Member variables to control pulseaudio...
+ pa_threaded_mainloop *mMainloop;
+ pa_mainloop_api *mApi;
+ pa_context *mContext;
+ pa_stream *mStream;
+ pa_proplist *mPropList;
+
+ pa_sample_spec mPASampleSpec;
+ char *mDevice;
+
+
+ // PulseAudio Callback functions...
+ static void contextStateCallback(pa_context *context, void *userdata);
+ static void streamStateCallback(pa_stream *stream, void *userdata);
+ static void streamWriteCallback(pa_stream *stream, size_t length, void *userdata);
+ static void streamDrainCallback(pa_stream *stream, int success, void *userdata);
+ static void contextDrainCallback(pa_context *context, void *userdata);
+
+ bool setupPulseAudio();
+ bool openFile();
+
+
+ // PulseAudio Processing functions...
+ void waitForOperation(pa_operation *operation);
+ void playInternal(pa_stream *stream, size_t length, void *userdata);
+};
+
+class PulsePlayer : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(bool playing READ isPlaying NOTIFY playStateChanged)
+ Q_PROPERTY(QString source READ sourceName WRITE setSourceName)
+ Q_PROPERTY(QString sink READ sinkName WRITE setSinkName)
+ Q_PROPERTY(QString role READ roleName WRITE setRoleName)
+ Q_PROPERTY(QString file READ fileName WRITE setFileName)
+public:
+ explicit PulsePlayer(QObject *parent = 0);
+
+ Q_INVOKABLE bool isPlaying() { return mPlayer ? mPlayer->isPlaying() : false; }
+ Q_INVOKABLE void play();
+ Q_INVOKABLE void stop();
+ const QString sourceName() { return mSourceName; }
+ void setSourceName(const QString &name) { mSourceName = name; }
+ const QString sinkName() { return mSinkName; }
+ void setSinkName(const QString &name) { mSinkName = name; }
+ const QString roleName() { return mRoleName; }
+ void setRoleName(const QString &name) { mRoleName = name; }
+ const QString fileName() { return mFileName; }
+ void setFileName(const QString &name) { mFileName = name; }
+
+
+signals:
+ void doPlay(QString sourceName);
+ void doStop(QString sourceName);
+ void playStateChanged();
+
+private:
+ InternalPlayer *mPlayer;
+ QThread *mWorker;
+ QString mSourceName;
+ QString mSinkName;
+ QString mRoleName;
+ QString mFileName;
+
+};
+
+#endif // PULSEPLAYER_H
diff --git a/qml.qrc b/qml.qrc
new file mode 100644
index 0000000..0a46daf
--- /dev/null
+++ b/qml.qrc
@@ -0,0 +1,24 @@
+<RCC>
+ <qresource prefix="/">
+ <file>main.qml</file>
+ <file>Button.qml</file>
+ <file>audio/car_reverse.wav</file>
+ <file>audio/navigation.wav</file>
+ <file>audio/telephone-ring.wav</file>
+ <file>audio/tts.wav</file>
+ <file>audio/media.wav</file>
+ <file>images/icon_car.png</file>
+ <file>images/icon_music.png</file>
+ <file>images/icon_reply.png</file>
+ <file>images/icon_microphone.png</file>
+ <file>images/icon_phone.png</file>
+ <file>images/speaker.png</file>
+ <file>images/volume-up.png</file>
+ <file>images/windriver-logo.png</file>
+ <file>images/genivi-logo.png</file>
+ <file>images/pulseaudio-logo.png</file>
+ <file>Graph.qml</file>
+ <file>Diagram.qml</file>
+ <file>code.js</file>
+ </qresource>
+</RCC>