From 7290b39664f87dd8a3bf81589749f65694ea61f8 Mon Sep 17 00:00:00 2001 From: Jooncheol Park Date: Wed, 15 Apr 2015 19:42:24 +0900 Subject: Initial commit for AM Monitor --- AudioManagerMonitor.pro | 36 +++ Button.qml | 96 ++++++ Diagram.qml | 675 ++++++++++++++++++++++++++++++++++++++++++ Graph.qml | 264 +++++++++++++++++ README | 16 + audio/car_reverse.wav | Bin 0 -> 1801796 bytes audio/media.wav | Bin 0 -> 9580594 bytes audio/navigation.wav | Bin 0 -> 328370 bytes audio/telephone-ring.wav | Bin 0 -> 4894540 bytes audio/tts.wav | Bin 0 -> 2051370 bytes audiomanagerdbusinterface.cpp | 635 +++++++++++++++++++++++++++++++++++++++ audiomanagerdbusinterface.h | 236 +++++++++++++++ code.js | 376 +++++++++++++++++++++++ commandlist.txt | 21 ++ deployment.pri | 27 ++ images/genivi-logo.png | Bin 0 -> 156183 bytes images/icon_car.png | Bin 0 -> 2542 bytes images/icon_microphone.png | Bin 0 -> 3292 bytes images/icon_music.png | Bin 0 -> 2727 bytes images/icon_phone.png | Bin 0 -> 2611 bytes images/icon_reply.png | Bin 0 -> 2430 bytes images/pulseaudio-logo.png | Bin 0 -> 15316 bytes images/speaker.png | Bin 0 -> 3035 bytes images/volume-up.png | Bin 0 -> 3119 bytes images/windriver-logo.png | Bin 0 -> 8978 bytes main.cpp | 56 ++++ main.qml | 99 +++++++ pc-env | 22 ++ pulseaudiocontroller.cpp | 274 +++++++++++++++++ pulseaudiocontroller.h | 94 ++++++ pulseplayer.cpp | 347 ++++++++++++++++++++++ pulseplayer.h | 132 +++++++++ qml.qrc | 24 ++ 33 files changed, 3430 insertions(+) create mode 100644 AudioManagerMonitor.pro create mode 100644 Button.qml create mode 100644 Diagram.qml create mode 100644 Graph.qml create mode 100644 README create mode 100755 audio/car_reverse.wav create mode 100755 audio/media.wav create mode 100755 audio/navigation.wav create mode 100755 audio/telephone-ring.wav create mode 100755 audio/tts.wav create mode 100644 audiomanagerdbusinterface.cpp create mode 100644 audiomanagerdbusinterface.h create mode 100644 code.js create mode 100644 commandlist.txt create mode 100644 deployment.pri create mode 100644 images/genivi-logo.png create mode 100644 images/icon_car.png create mode 100644 images/icon_microphone.png create mode 100644 images/icon_music.png create mode 100644 images/icon_phone.png create mode 100644 images/icon_reply.png create mode 100644 images/pulseaudio-logo.png create mode 100644 images/speaker.png create mode 100644 images/volume-up.png create mode 100644 images/windriver-logo.png create mode 100644 main.cpp create mode 100644 main.qml create mode 100644 pc-env create mode 100644 pulseaudiocontroller.cpp create mode 100644 pulseaudiocontroller.h create mode 100644 pulseplayer.cpp create mode 100644 pulseplayer.h create mode 100644 qml.qrc 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 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 + Daewon Park + +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 Binary files /dev/null and b/audio/car_reverse.wav differ diff --git a/audio/media.wav b/audio/media.wav new file mode 100755 index 0000000..f4e4a40 Binary files /dev/null and b/audio/media.wav differ diff --git a/audio/navigation.wav b/audio/navigation.wav new file mode 100755 index 0000000..46b147b Binary files /dev/null and b/audio/navigation.wav differ diff --git a/audio/telephone-ring.wav b/audio/telephone-ring.wav new file mode 100755 index 0000000..fcc896c Binary files /dev/null and b/audio/telephone-ring.wav differ diff --git a/audio/tts.wav b/audio/tts.wav new file mode 100755 index 0000000..00ca58c Binary files /dev/null and b/audio/tts.wav 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 +#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 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 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(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType>(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType>(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType>(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType>(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType>(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType>(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType>(); +} + +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 result = ret.arguments(); + mLastError = result.takeFirst().toInt(); + QDBusMessage retMessage; + retMessage.setArguments(result); + + if (mLastError == am::am_Error_e::E_OK) { + QDBusReply> reply(retMessage); + + QList connectionList = reply.value(); + mMainConnectionTypeList.clear(); + + for (int i = 0; i < connectionList.size(); i++) { + MainConnectionType info = connectionList[i]; + QMap 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 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> reply(retMessage); + + QList sinkList = reply.value(); + mMainSinkList.clear(); + + for (int i = 0; i < sinkList.size(); i++) { + SinkType info = sinkList[i]; + QMap 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 result = ret.arguments(); + mLastError = result.takeFirst().toInt(); + QDBusMessage retMessage; + retMessage.setArguments(result); + + if (mLastError == am::am_Error_e::E_OK) { + QDBusReply> reply(retMessage); + + QList sourceList = reply.value(); + mMainSourceList.clear(); + + for (int i = 0; i < sourceList.size(); i++) { + SourceType info = sourceList[i]; + QMap 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 result = ret.arguments(); + mLastError = result.takeFirst().toInt(); + QDBusMessage retMessage; + retMessage.setArguments(result); + + if (mLastError == am::am_Error_e::E_OK) { + QDBusReply> 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 result = ret.arguments(); + mLastError = result.takeFirst().toInt(); + QDBusMessage retMessage; + retMessage.setArguments(result); + + if (mLastError == am::am_Error_e::E_OK) { + QDBusReply> 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 result = ret.arguments(); + mLastError = result.takeFirst().toInt(); + QDBusMessage retMessage; + retMessage.setArguments(result); + + if (mLastError == am::am_Error_e::E_OK) { + QDBusReply> 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 AudioManagerDBusInterface::getMainSinkClassesList() +{ + return mMainSinkClassList; +} + +QList 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +typedef struct +{ + short available; + short reason; +} Availability; + + +typedef struct +{ + short classProperty; + short value; +} ClassProperty; + +typedef struct +{ + ushort sinkClassID; + QString name; + QList listClassProperties; +} SinkClasses; + +typedef struct +{ + ushort sourceClassID; + QString name; + QList 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 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) +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(QList) + +Q_DECLARE_METATYPE(QList) + + +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 getMainSourceTypeList(); + QList getMainSinkClassesList(); + QList 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 mCommandList; + QMap mSourceList; + QMap mSinkList; + + // DBusConnection + QDBusConnection mDBusConnection; + QString mServiceName; + QString mObjectPath; + QString mInterfaceName; + short mLastError; + + + // List Of MainConnections + QList mMainConnectionList; + QList mMainConnectionTypeList; + + // List for Sinks + QList mMainSinkClassList; + QList mMainSinkList; + + + // List for Sources + QList mMainSourceClassList; + QList mMainSourceList; + + + // SystemProperty + QList 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 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 Binary files /dev/null and b/images/genivi-logo.png differ diff --git a/images/icon_car.png b/images/icon_car.png new file mode 100644 index 0000000..cd58fd9 Binary files /dev/null and b/images/icon_car.png differ diff --git a/images/icon_microphone.png b/images/icon_microphone.png new file mode 100644 index 0000000..ab18517 Binary files /dev/null and b/images/icon_microphone.png differ diff --git a/images/icon_music.png b/images/icon_music.png new file mode 100644 index 0000000..b3d7b63 Binary files /dev/null and b/images/icon_music.png differ diff --git a/images/icon_phone.png b/images/icon_phone.png new file mode 100644 index 0000000..bcda19d Binary files /dev/null and b/images/icon_phone.png differ diff --git a/images/icon_reply.png b/images/icon_reply.png new file mode 100644 index 0000000..7862a2f Binary files /dev/null and b/images/icon_reply.png differ diff --git a/images/pulseaudio-logo.png b/images/pulseaudio-logo.png new file mode 100644 index 0000000..158a9e3 Binary files /dev/null and b/images/pulseaudio-logo.png differ diff --git a/images/speaker.png b/images/speaker.png new file mode 100644 index 0000000..054af9c Binary files /dev/null and b/images/speaker.png differ diff --git a/images/volume-up.png b/images/volume-up.png new file mode 100644 index 0000000..02a377a Binary files /dev/null and b/images/volume-up.png differ diff --git a/images/windriver-logo.png b/images/windriver-logo.png new file mode 100644 index 0000000..54feaad Binary files /dev/null and b/images/windriver-logo.png 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 +#include +#include +#include +#include +#include +#include +#include +#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("com.windriver.ammonitor", 1, 0, "PAPlayer"); + qmlRegisterType("com.windriver.ammonitor", 1, 0, "PAClient"); + qmlRegisterType("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 + +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(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(userdata); + if (!info) + return; + + QMap 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(userdata); + if (!info) + return; + + QMap 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(userdata); + + if (!info) + return; + + QMap 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(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(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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 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 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::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(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(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(userdata); + player->playInternal(stream, length, userdata); + pa_threaded_mainloop_signal(player->getMainloop(),0); +} +void InternalPlayer::contextDrainCallback(pa_context *, void *userdata) +{ + InternalPlayer *player = reinterpret_cast(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(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(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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PA_BUF_SIZE 2048 +class InternalPlayer : public QObject +{ + Q_OBJECT +public: + InternalPlayer(QString fileName, QString sinkName, QString sourceName, QMap 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 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 @@ + + + main.qml + Button.qml + audio/car_reverse.wav + audio/navigation.wav + audio/telephone-ring.wav + audio/tts.wav + audio/media.wav + images/icon_car.png + images/icon_music.png + images/icon_reply.png + images/icon_microphone.png + images/icon_phone.png + images/speaker.png + images/volume-up.png + images/windriver-logo.png + images/genivi-logo.png + images/pulseaudio-logo.png + Graph.qml + Diagram.qml + code.js + + -- cgit v1.2.1