Index: autotests/CMakeLists.txt =================================================================== --- autotests/CMakeLists.txt +++ autotests/CMakeLists.txt @@ -31,6 +31,8 @@ inputtest mediaplayertest jobstest + mediatest + noinputnooutputagenttest ) if(Qt5Qml_FOUND AND Qt5QuickTest_FOUND) Index: autotests/fakebluez/CMakeLists.txt =================================================================== --- autotests/fakebluez/CMakeLists.txt +++ autotests/fakebluez/CMakeLists.txt @@ -13,6 +13,7 @@ mediaplayerinterface.cpp obexagentmanager.cpp obexclient.cpp + media.cpp ) add_executable(fakebluez ${fakebluez_SRCS}) Index: autotests/fakebluez/fakebluez.h =================================================================== --- autotests/fakebluez/fakebluez.h +++ autotests/fakebluez/fakebluez.h @@ -29,6 +29,7 @@ class AgentManager; class ProfileManager; class DeviceManager; +class Media; class ObexObject; class ObexAgentManager; class ObexClient; @@ -52,6 +53,7 @@ void createAgentManager(); void createProfileManager(); void createDeviceManager(); + void createMedia(); void createObexObjectManager(); void createObexAgentManager(); void createObexClient(); @@ -72,6 +74,7 @@ AgentManager *m_agentManager; ProfileManager *m_profileManager; DeviceManager *m_deviceManager; + Media *m_media; ObexObject *m_obexObject; ObexAgentManager *m_obexAgentManager; Index: autotests/fakebluez/fakebluez.cpp =================================================================== --- autotests/fakebluez/fakebluez.cpp +++ autotests/fakebluez/fakebluez.cpp @@ -24,6 +24,7 @@ #include "agentmanager.h" #include "profilemanager.h" #include "devicemanager.h" +#include "media.h" #include "obexagentmanager.h" #include "obexclient.h" @@ -50,6 +51,7 @@ , m_agentManager(nullptr) , m_profileManager(nullptr) , m_deviceManager(nullptr) + , m_media(nullptr) , m_obexObject(nullptr) , m_obexAgentManager(nullptr) , m_obexClient(nullptr) @@ -101,6 +103,8 @@ m_agentManager->runAction(m_actionName, m_actionProperties); } else if (m_actionObject == QLatin1String("devicemanager")) { m_deviceManager->runAction(m_actionName, m_actionProperties); + } else if (m_actionObject == QLatin1String("media")) { + m_media->runAction(m_actionName, m_actionProperties); } QTimer::singleShot(0, m_testInterface, SLOT(emitActionFinished())); @@ -135,6 +139,12 @@ m_deviceManager = new DeviceManager(m_objectManager); } +void FakeBluez::createMedia() +{ + m_media = new Media(m_objectManager); + m_objectManager->addObject(m_media); +} + void FakeBluez::createObexObjectManager() { createObjectManager(); @@ -171,6 +181,7 @@ createObjectManager(); createAgentManager(); createProfileManager(); + createMedia(); } void FakeBluez::runBluezStandardTest() @@ -180,6 +191,7 @@ createDeviceManager(); createAgentManager(); createProfileManager(); + createMedia(); } void FakeBluez::runObexNotExportingInterfacesTest() Index: autotests/fakebluez/media.h =================================================================== --- /dev/null +++ autotests/fakebluez/media.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef MEDIA_H +#define MEDIA_H + +#include "object.h" + +#include + +class QDBusMessage; + +class Media : public QDBusAbstractAdaptor, public Object +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.bluez.Media1") + +public: + explicit Media(QObject *parent = nullptr); + + void runAction(const QString &actionName, const QVariantMap &properties); + +public Q_SLOTS: + void RegisterEndpoint(const QDBusObjectPath &path, const QVariantMap &properties, const QDBusMessage &msg); + void UnregisterEndpoint(const QDBusObjectPath &path, const QDBusMessage &msg); + +private: + void runSetConfigurationAction(const QVariantMap &properties); + void runSelectConfigurationAction(const QVariantMap &properties); + void runClearConfigurationAction(const QVariantMap &properties); + void runReleaseAction(); + + QDBusObjectPath m_endpoint; + QString m_service; + QVariantMap m_properties; +}; + +#endif // MEDIA_H Index: autotests/fakebluez/media.cpp =================================================================== --- /dev/null +++ autotests/fakebluez/media.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "media.h" + +#include +#include +#include +#include + +Media::Media(QObject *parent) + : QDBusAbstractAdaptor(parent) +{ + setName(QStringLiteral("org.bluez.Media1")); + setPath(QDBusObjectPath(QStringLiteral("/org/bluez"))); +} + +void Media::runAction(const QString &actionName, const QVariantMap &properties) +{ + if (actionName == QLatin1String("set-configuration")) { + runSetConfigurationAction(properties); + } else if (actionName == QLatin1String("select-configuration")) { + runSelectConfigurationAction(properties); + } else if (actionName == QLatin1String("clear-configuration")) { + runClearConfigurationAction(properties); + } else if (actionName == QLatin1String("release")) { + runReleaseAction(); + } +} + +void Media::RegisterEndpoint(const QDBusObjectPath &path, const QVariantMap &properties, const QDBusMessage &msg) +{ + m_endpoint = path; + m_service = msg.service(); + m_properties = properties; +} + +void Media::UnregisterEndpoint(const QDBusObjectPath &path, const QDBusMessage &msg) +{ + if (m_endpoint == path && m_service == msg.service()) { + m_endpoint = QDBusObjectPath(); + m_service.clear(); + m_properties.clear(); + } +} + +void Media::runSetConfigurationAction(const QVariantMap &properties) +{ + QDBusMessage call = QDBusMessage::createMethodCall(m_service, + m_endpoint.path(), + QStringLiteral("org.bluez.MediaEndpoint1"), + QStringLiteral("SetConfiguration")); + call << QVariant::fromValue(properties.value(QStringLiteral("Transport")).value()); + call << properties.value(QStringLiteral("Properties")); + QDBusConnection::sessionBus().asyncCall(call); +} + +void Media::runSelectConfigurationAction(const QVariantMap &properties) +{ + QDBusMessage call = QDBusMessage::createMethodCall(m_service, + m_endpoint.path(), + QStringLiteral("org.bluez.MediaEndpoint1"), + QStringLiteral("SelectConfiguration")); + call << properties.value(QStringLiteral("Capabilities")); + QDBusConnection::sessionBus().asyncCall(call); +} + +void Media::runClearConfigurationAction(const QVariantMap &properties) +{ + QDBusMessage call = QDBusMessage::createMethodCall(m_service, + m_endpoint.path(), + QStringLiteral("org.bluez.MediaEndpoint1"), + QStringLiteral("ClearConfiguration")); + call << QVariant::fromValue(properties.value(QStringLiteral("Transport")).value()); + QDBusConnection::sessionBus().asyncCall(call); +} + +void Media::runReleaseAction() +{ + QDBusMessage call = QDBusMessage::createMethodCall(m_service, + m_endpoint.path(), + QStringLiteral("org.bluez.MediaEndpoint1"), + QStringLiteral("Release")); + QDBusConnection::sessionBus().asyncCall(call); +} Index: autotests/mediatest.h =================================================================== --- /dev/null +++ autotests/mediatest.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef MEDIATEST_H +#define MEDIATEST_H + +#include "mediaendpoint.h" + +class TestEndpoint : public BluezQt::MediaEndpoint +{ + Q_OBJECT + +public: + using BluezQt::MediaEndpoint::MediaEndpoint; + void release() override; + + // release + bool m_releaseCalled = false; +}; + +class MediaTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void setConfigurationTest(); + void selectConfigurationTest(); + void clearConfigurationTest(); + void releaseTest(); + +private: + TestEndpoint* m_endpoint; +}; + +#endif // MEDIATEST_H Index: autotests/mediatest.cpp =================================================================== --- /dev/null +++ autotests/mediatest.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "mediatest.h" +#include "a2dp-codecs.h" +#include "autotests.h" +#include "initmanagerjob.h" +#include "manager.h" +#include "media.h" +#include "mediaendpoint.h" +#include "pendingcall.h" + +#include +#include +#include + +namespace BluezQt +{ +extern void bluezqt_initFakeBluezTestRun(); +} + +using namespace BluezQt; + +void TestEndpoint::release() +{ + m_releaseCalled = true; +} + +void MediaTest::initTestCase() +{ + bluezqt_initFakeBluezTestRun(); + FakeBluez::start(); + FakeBluez::runTest(QStringLiteral("bluez-standard")); + + Manager *manager = new Manager(this); + InitManagerJob *initJob = manager->init(); + initJob->exec(); + QVERIFY(!initJob->error()); + QVERIFY(manager->media()); + + m_endpoint = new TestEndpoint({MediaEndpoint::Role::AudioSink, MediaEndpoint::Codec::Sbc}, this); + manager->media()->registerEndpoint(m_endpoint)->waitForFinished(); +} + +void MediaTest::cleanupTestCase() +{ + FakeBluez::stop(); +} + +void MediaTest::setConfigurationTest() +{ + QSignalSpy endpointSpy(m_endpoint, SIGNAL(configurationSet(QString,QVariantMap))); + + QVariantMap props; + props.insert(QStringLiteral("Key"), QVariant::fromValue(int(123))); + + QVariantMap params; + params.insert(QStringLiteral("Transport"), QVariant::fromValue(m_endpoint->objectPath())); + params.insert(QStringLiteral("Properties"), props); + FakeBluez::runAction(QStringLiteral("media"), QStringLiteral("set-configuration"), params); + endpointSpy.wait(); + auto args = endpointSpy.takeFirst(); + QCOMPARE(args.at(0).toString(), m_endpoint->objectPath().path()); + QCOMPARE(args.at(1).value(), props); +} + +void MediaTest::selectConfigurationTest() +{ + QSignalSpy endpointSpy(m_endpoint, SIGNAL(configurationSelected(QByteArray,QByteArray))); + + a2dp_sbc_t sbcConfiguration; + sbcConfiguration.frequency = SBC_SAMPLING_FREQ_44100; + sbcConfiguration.channel_mode = SBC_CHANNEL_MODE_STEREO; + sbcConfiguration.block_length = SBC_BLOCK_LENGTH_16; + sbcConfiguration.subbands = SBC_SUBBANDS_8; + sbcConfiguration.allocation_method = SBC_ALLOCATION_LOUDNESS; + sbcConfiguration.min_bitpool = 2; + sbcConfiguration.max_bitpool = 53; + + QVariantMap params; + params.insert(QStringLiteral("Capabilities"), QByteArray(reinterpret_cast(&sbcCapabilities), sizeof(sbcCapabilities))); + FakeBluez::runAction(QStringLiteral("media"), QStringLiteral("select-configuration"), params); + endpointSpy.wait(); + auto args = endpointSpy.takeFirst(); + QCOMPARE(args.at(0).toByteArray(), QByteArray(reinterpret_cast(&sbcCapabilities), sizeof(sbcCapabilities))); + QCOMPARE(args.at(1).toByteArray(), QByteArray(reinterpret_cast(&sbcConfiguration), sizeof(sbcConfiguration))); + + params.insert(QStringLiteral("Capabilities"), QByteArray()); + FakeBluez::runAction(QStringLiteral("media"), QStringLiteral("select-configuration"), params); + endpointSpy.wait(); + args = endpointSpy.takeFirst(); + QCOMPARE(args.at(0).toByteArray(), QByteArray()); + QCOMPARE(args.at(1).toByteArray(), QByteArray()); +} + +void MediaTest::clearConfigurationTest() +{ + QSignalSpy endpointSpy(m_endpoint, SIGNAL(configurationCleared(QString))); + + QVariantMap params; + params.insert(QStringLiteral("Transport"), QVariant::fromValue(m_endpoint->objectPath())); + FakeBluez::runAction(QStringLiteral("media"), QStringLiteral("clear-configuration"), params); + endpointSpy.wait(); + auto args = endpointSpy.takeFirst(); + QCOMPARE(args.at(0).toString(), m_endpoint->objectPath().path()); +} + +void MediaTest::releaseTest() +{ + QCOMPARE(m_endpoint->m_releaseCalled, false); + + FakeBluez::runAction(QStringLiteral("media"), QStringLiteral("release")); + + QTRY_COMPARE(m_endpoint->m_releaseCalled, true); +} + +QTEST_MAIN(MediaTest) Index: autotests/noinputnooutputagenttest.h =================================================================== --- /dev/null +++ autotests/noinputnooutputagenttest.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef NOINPUTNOOUTPUTAGENTTEST_H +#define NOINPUTNOOUTPUTAGENTTEST_H + +#include +#include + +#include "device.h" + +namespace BluezQt +{ +class NoInputNoOutputAgent; +} + +class NoInputNoOutputAgentTest : public QObject +{ + Q_OBJECT + +public: + NoInputNoOutputAgentTest(); + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void authorizeServiceTest(); + +private: + BluezQt::NoInputNoOutputAgent *m_agent; + QDBusObjectPath m_device; +}; + +#endif // NOINPUTNOOUTPUTAGENTTEST_H Index: autotests/noinputnooutputagenttest.cpp =================================================================== --- /dev/null +++ autotests/noinputnooutputagenttest.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "noinputnooutputagenttest.h" +#include "autotests.h" +#include "device.h" +#include "manager.h" +#include "initmanagerjob.h" +#include "pendingcall.h" +#include "services.h" +#include "noinputnooutputagent.h" + +#include +#include +#include + +namespace BluezQt +{ +extern void bluezqt_initFakeBluezTestRun(); +} + +using namespace BluezQt; + +NoInputNoOutputAgentTest::NoInputNoOutputAgentTest() + : m_agent(nullptr) +{ + Autotests::registerMetatypes(); +} + +void NoInputNoOutputAgentTest::initTestCase() +{ + bluezqt_initFakeBluezTestRun(); + + FakeBluez::start(); + FakeBluez::runTest(QStringLiteral("bluez-standard")); + + // Create adapter + QVariantMap adapterProps; + adapterProps[QStringLiteral("Path")] = QVariant::fromValue(QDBusObjectPath(QStringLiteral("/org/bluez/hci0"))); + adapterProps[QStringLiteral("Address")] = QStringLiteral("1C:E5:C3:BC:94:7E"); + adapterProps[QStringLiteral("Name")] = QStringLiteral("TestAdapter"); + FakeBluez::runAction(QStringLiteral("devicemanager"), QStringLiteral("create-adapter"), adapterProps); + + // Create device + m_device = QDBusObjectPath(QStringLiteral("/org/bluez/hci0/dev_40_79_6A_0C_39_75")); + QVariantMap deviceProps; + deviceProps[QStringLiteral("Path")] = QVariant::fromValue(m_device); + deviceProps[QStringLiteral("Adapter")] = adapterProps.value(QStringLiteral("Path")); + deviceProps[QStringLiteral("Address")] = QStringLiteral("40:79:6A:0C:39:75"); + deviceProps[QStringLiteral("Name")] = QStringLiteral("TestDevice"); + FakeBluez::runAction(QStringLiteral("devicemanager"), QStringLiteral("create-device"), deviceProps); + + Manager *manager = new Manager(this); + InitManagerJob *job = manager->init(); + job->exec(); + + QVERIFY(!job->error()); + QCOMPARE(manager->adapters().count(), 1); + QCOMPARE(manager->devices().count(), 1); + + m_agent = new NoInputNoOutputAgent({Services::AdvancedAudioDistribution, Services::AudioVideoRemoteControl}, this); + manager->registerAgent(m_agent)->waitForFinished(); +} + +void NoInputNoOutputAgentTest::cleanupTestCase() +{ + FakeBluez::stop(); +} + +void NoInputNoOutputAgentTest::authorizeServiceTest() +{ + QSignalSpy agentSpy(m_agent, SIGNAL(serviceAuthorized(DevicePtr,QString,bool))); + + QVariantMap props; + props.insert(QStringLiteral("Device"), QVariant::fromValue(m_device)); + props.insert(QStringLiteral("UUID"), Services::AdvancedAudioDistribution); + FakeBluez::runAction(QStringLiteral("agentmanager"), QStringLiteral("authorize-service"), props); + agentSpy.wait(); + auto args = agentSpy.takeFirst(); + QCOMPARE(args.at(0).value()->ubi(), m_device.path()); + QCOMPARE(args.at(1).toString(), Services::AdvancedAudioDistribution); + QCOMPARE(args.at(2).toBool(), true); + + props.insert(QStringLiteral("UUID"), Services::ObexFileTransfer); + FakeBluez::runAction(QStringLiteral("agentmanager"), QStringLiteral("authorize-service"), props); + agentSpy.wait(); + args = agentSpy.takeFirst(); + QCOMPARE(args.at(1).toString(), Services::ObexFileTransfer); + QCOMPARE(args.at(2).toBool(), false); + + props.insert(QStringLiteral("UUID"), Services::AudioVideoRemoteControl); + FakeBluez::runAction(QStringLiteral("agentmanager"), QStringLiteral("authorize-service"), props); + agentSpy.wait(); + args = agentSpy.takeFirst(); + QCOMPARE(args.at(1).toString(), Services::AudioVideoRemoteControl); + QCOMPARE(args.at(2).toBool(), true); +} + +QTEST_MAIN(NoInputNoOutputAgentTest) Index: src/CMakeLists.txt =================================================================== --- src/CMakeLists.txt +++ src/CMakeLists.txt @@ -1,4 +1,5 @@ set(bluezqt_SRCS + a2dp-codecs.c manager.cpp manager_p.cpp adapter.cpp @@ -6,6 +7,10 @@ device.cpp device_p.cpp input.cpp + media.cpp + mediaendpoint.cpp + mediaendpoint_p.cpp + mediaendpointadaptor.cpp mediaplayer.cpp mediaplayer_p.cpp mediaplayertrack.cpp @@ -30,6 +35,7 @@ obexobjectpush.cpp obexfiletransfer.cpp obexfiletransferentry.cpp + noinputnooutputagent.cpp ) ecm_qt_declare_logging_category(bluezqt_SRCS HEADER debug.h IDENTIFIER BLUEZQT CATEGORY_NAME org.kde.bluez) @@ -47,6 +53,8 @@ qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.AgentManager1.xml bluezagentmanager1) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.ProfileManager1.xml bluezprofilemanager1) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.Device1.xml bluezdevice1) +qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.Media1.xml bluezmedia1) +qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.MediaEndpoint1.xml bluezmediaendpoint1) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.MediaPlayer1.xml bluezmediaplayer1) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.obex.AgentManager1.xml obexagentmanager1) qt5_add_dbus_interface(bluezqt_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/interfaces/org.bluez.obex.Client1.xml obexclient1) Index: src/a2dp-codecs.h =================================================================== --- /dev/null +++ src/a2dp-codecs.h @@ -0,0 +1,264 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef BLUEZQT_A2DPCODECS_H +#define BLUEZQT_A2DPCODECS_H + +#include + +#define A2DP_CODEC_SBC 0x00 +#define A2DP_CODEC_MPEG12 0x01 +#define A2DP_CODEC_MPEG24 0x02 +#define A2DP_CODEC_ATRAC 0x03 +#define A2DP_CODEC_VENDOR 0xFF + +#define SBC_SAMPLING_FREQ_16000 (1 << 3) +#define SBC_SAMPLING_FREQ_32000 (1 << 2) +#define SBC_SAMPLING_FREQ_44100 (1 << 1) +#define SBC_SAMPLING_FREQ_48000 1 + +#define SBC_CHANNEL_MODE_MONO (1 << 3) +#define SBC_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define SBC_CHANNEL_MODE_STEREO (1 << 1) +#define SBC_CHANNEL_MODE_JOINT_STEREO 1 + +#define SBC_BLOCK_LENGTH_4 (1 << 3) +#define SBC_BLOCK_LENGTH_8 (1 << 2) +#define SBC_BLOCK_LENGTH_12 (1 << 1) +#define SBC_BLOCK_LENGTH_16 1 + +#define SBC_SUBBANDS_4 (1 << 1) +#define SBC_SUBBANDS_8 1 + +#define SBC_ALLOCATION_SNR (1 << 1) +#define SBC_ALLOCATION_LOUDNESS 1 + +#define MAX_BITPOOL 64 +#define MIN_BITPOOL 2 + +#define MPEG_CHANNEL_MODE_MONO (1 << 3) +#define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define MPEG_CHANNEL_MODE_STEREO (1 << 1) +#define MPEG_CHANNEL_MODE_JOINT_STEREO 1 + +#define MPEG_LAYER_MP1 (1 << 2) +#define MPEG_LAYER_MP2 (1 << 1) +#define MPEG_LAYER_MP3 1 + +#define MPEG_SAMPLING_FREQ_16000 (1 << 5) +#define MPEG_SAMPLING_FREQ_22050 (1 << 4) +#define MPEG_SAMPLING_FREQ_24000 (1 << 3) +#define MPEG_SAMPLING_FREQ_32000 (1 << 2) +#define MPEG_SAMPLING_FREQ_44100 (1 << 1) +#define MPEG_SAMPLING_FREQ_48000 1 + +#define MPEG_BIT_RATE_VBR 0x8000 +#define MPEG_BIT_RATE_320000 0x4000 +#define MPEG_BIT_RATE_256000 0x2000 +#define MPEG_BIT_RATE_224000 0x1000 +#define MPEG_BIT_RATE_192000 0x0800 +#define MPEG_BIT_RATE_160000 0x0400 +#define MPEG_BIT_RATE_128000 0x0200 +#define MPEG_BIT_RATE_112000 0x0100 +#define MPEG_BIT_RATE_96000 0x0080 +#define MPEG_BIT_RATE_80000 0x0040 +#define MPEG_BIT_RATE_64000 0x0020 +#define MPEG_BIT_RATE_56000 0x0010 +#define MPEG_BIT_RATE_48000 0x0008 +#define MPEG_BIT_RATE_40000 0x0004 +#define MPEG_BIT_RATE_32000 0x0002 +#define MPEG_BIT_RATE_FREE 0x0001 + +#define AAC_OBJECT_TYPE_MPEG2_AAC_LC 0x80 +#define AAC_OBJECT_TYPE_MPEG4_AAC_LC 0x40 +#define AAC_OBJECT_TYPE_MPEG4_AAC_LTP 0x20 +#define AAC_OBJECT_TYPE_MPEG4_AAC_SCA 0x10 + +#define AAC_SAMPLING_FREQ_8000 0x0800 +#define AAC_SAMPLING_FREQ_11025 0x0400 +#define AAC_SAMPLING_FREQ_12000 0x0200 +#define AAC_SAMPLING_FREQ_16000 0x0100 +#define AAC_SAMPLING_FREQ_22050 0x0080 +#define AAC_SAMPLING_FREQ_24000 0x0040 +#define AAC_SAMPLING_FREQ_32000 0x0020 +#define AAC_SAMPLING_FREQ_44100 0x0010 +#define AAC_SAMPLING_FREQ_48000 0x0008 +#define AAC_SAMPLING_FREQ_64000 0x0004 +#define AAC_SAMPLING_FREQ_88200 0x0002 +#define AAC_SAMPLING_FREQ_96000 0x0001 + +#define AAC_CHANNELS_1 0x02 +#define AAC_CHANNELS_2 0x01 + +#define AAC_GET_BITRATE(a) ((a).bitrate1 << 16 | \ + (a).bitrate2 << 8 | (a).bitrate3) +#define AAC_GET_FREQUENCY(a) ((a).frequency1 << 4 | (a).frequency2) + +#define AAC_SET_BITRATE(a, b) \ + do { \ + (a).bitrate1 = (b >> 16) & 0x7f; \ + (a).bitrate2 = (b >> 8) & 0xff; \ + (a).bitrate3 = b & 0xff; \ + } while (0) +#define AAC_SET_FREQUENCY(a, f) \ + do { \ + (a).frequency1 = (f >> 4) & 0xff; \ + (a).frequency2 = f & 0x0f; \ + } while (0) + +#define AAC_INIT_BITRATE(b) \ + .bitrate1 = (b >> 16) & 0x7f, \ + .bitrate2 = (b >> 8) & 0xff, \ + .bitrate3 = b & 0xff, +#define AAC_INIT_FREQUENCY(f) \ + .frequency1 = (f >> 4) & 0xff, \ + .frequency2 = f & 0x0f, + +#define APTX_VENDOR_ID 0x0000004f +#define APTX_CODEC_ID 0x0001 + +#define APTX_CHANNEL_MODE_MONO 0x01 +#define APTX_CHANNEL_MODE_STEREO 0x02 + +#define APTX_SAMPLING_FREQ_16000 0x08 +#define APTX_SAMPLING_FREQ_32000 0x04 +#define APTX_SAMPLING_FREQ_44100 0x02 +#define APTX_SAMPLING_FREQ_48000 0x01 + +#define LDAC_VENDOR_ID 0x0000012d +#define LDAC_CODEC_ID 0x00aa + +typedef struct { + uint32_t vendor_id; + uint16_t codec_id; +} __attribute__ ((packed)) a2dp_vendor_codec_t; + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +typedef struct { + uint8_t channel_mode:4; + uint8_t frequency:4; + uint8_t allocation_method:2; + uint8_t subbands:2; + uint8_t block_length:4; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) a2dp_sbc_t; + +typedef struct { + uint8_t channel_mode:4; + uint8_t crc:1; + uint8_t layer:3; + uint8_t frequency:6; + uint8_t mpf:1; + uint8_t rfa:1; + uint16_t bitrate; +} __attribute__ ((packed)) a2dp_mpeg_t; + +typedef struct { + uint8_t object_type; + uint8_t frequency1; + uint8_t rfa:2; + uint8_t channels:2; + uint8_t frequency2:4; + uint8_t bitrate1:7; + uint8_t vbr:1; + uint8_t bitrate2; + uint8_t bitrate3; +} __attribute__ ((packed)) a2dp_aac_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t channel_mode:4; + uint8_t frequency:4; +} __attribute__ ((packed)) a2dp_aptx_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t unknown[2]; +} __attribute__ ((packed)) a2dp_ldac_t; + +#elif __BYTE_ORDER == __BIG_ENDIAN + +typedef struct { + uint8_t frequency:4; + uint8_t channel_mode:4; + uint8_t block_length:4; + uint8_t subbands:2; + uint8_t allocation_method:2; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) a2dp_sbc_t; + +typedef struct { + uint8_t layer:3; + uint8_t crc:1; + uint8_t channel_mode:4; + uint8_t rfa:1; + uint8_t mpf:1; + uint8_t frequency:6; + uint16_t bitrate; +} __attribute__ ((packed)) a2dp_mpeg_t; + +typedef struct { + uint8_t object_type; + uint8_t frequency1; + uint8_t frequency2:4; + uint8_t channels:2; + uint8_t rfa:2; + uint8_t vbr:1; + uint8_t bitrate1:7; + uint8_t bitrate2; + uint8_t bitrate3; +} __attribute__ ((packed)) a2dp_aac_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t frequency:4; + uint8_t channel_mode:4; +} __attribute__ ((packed)) a2dp_aptx_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t unknown[2]; +} __attribute__ ((packed)) a2dp_ldac_t; + +#else +#error "Unknown byte order" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include "bluezqt_export.h" + +BLUEZQT_EXPORT extern const a2dp_sbc_t sbcCapabilities; +BLUEZQT_EXPORT extern const a2dp_aac_t aacCapabilities; + +#ifdef __cplusplus +} +#endif + +#endif Index: src/a2dp-codecs.c =================================================================== --- /dev/null +++ src/a2dp-codecs.c @@ -0,0 +1,77 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "a2dp-codecs.h" + +/* Currently our capabilites are limited to the most common use cases. + iOS has a fixed stream configuration anyway (for SBC and AAC). */ +const a2dp_sbc_t sbcCapabilities = { + .frequency = + /*SBC_SAMPLING_FREQ_16000 | + SBC_SAMPLING_FREQ_32000 |*/ + SBC_SAMPLING_FREQ_44100 | + SBC_SAMPLING_FREQ_48000, + .channel_mode = + /*SBC_CHANNEL_MODE_MONO | + SBC_CHANNEL_MODE_DUAL_CHANNEL |*/ + SBC_CHANNEL_MODE_STEREO | + SBC_CHANNEL_MODE_JOINT_STEREO, + .block_length = + SBC_BLOCK_LENGTH_4 | + SBC_BLOCK_LENGTH_8 | + SBC_BLOCK_LENGTH_12 | + SBC_BLOCK_LENGTH_16, + .subbands = + SBC_SUBBANDS_4 | + SBC_SUBBANDS_8, + .allocation_method = + SBC_ALLOCATION_SNR | + SBC_ALLOCATION_LOUDNESS, + .min_bitpool = MIN_BITPOOL, + .max_bitpool = MAX_BITPOOL, +}; + +const a2dp_aac_t aacCapabilities = { + .object_type = + AAC_OBJECT_TYPE_MPEG2_AAC_LC | + AAC_OBJECT_TYPE_MPEG4_AAC_LC, + /*AAC_OBJECT_TYPE_MPEG4_AAC_LTP | + AAC_OBJECT_TYPE_MPEG4_AAC_SCA, */ + AAC_INIT_FREQUENCY( + /*AAC_SAMPLING_FREQ_8000 | + AAC_SAMPLING_FREQ_11025 | + AAC_SAMPLING_FREQ_12000 | + AAC_SAMPLING_FREQ_16000 | + AAC_SAMPLING_FREQ_22050 | + AAC_SAMPLING_FREQ_24000 | + AAC_SAMPLING_FREQ_32000 |*/ + AAC_SAMPLING_FREQ_44100 | + AAC_SAMPLING_FREQ_48000) + /*AAC_SAMPLING_FREQ_64000 | + AAC_SAMPLING_FREQ_88200 | + AAC_SAMPLING_FREQ_96000)*/ + .channels = + /*AAC_CHANNELS_1 |*/ + AAC_CHANNELS_2, + .vbr = 1, + AAC_INIT_BITRATE(0xFFFF) +}; Index: src/interfaces/org.bluez.Media1.xml =================================================================== --- /dev/null +++ src/interfaces/org.bluez.Media1.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + Index: src/interfaces/org.bluez.MediaEndpoint1.xml =================================================================== --- /dev/null +++ src/interfaces/org.bluez.MediaEndpoint1.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + Index: src/manager.h =================================================================== --- src/manager.h +++ src/manager.h @@ -297,6 +297,13 @@ */ PendingCall *unregisterProfile(Profile *profile); + /** + * Returns the media interface. + * + * @return media + */ + MediaPtr media() const; + Q_SIGNALS: /** * Indicates that operational state have changed. Index: src/manager.cpp =================================================================== --- src/manager.cpp +++ src/manager.cpp @@ -246,4 +246,9 @@ PendingCall::ReturnVoid, this); } +MediaPtr Manager::media() const +{ + return d->m_media; +} + } // namespace BluezQt Index: src/manager_p.h =================================================================== --- src/manager_p.h +++ src/manager_p.h @@ -81,6 +81,7 @@ DBusObjectManager *m_dbusObjectManager; BluezAgentManager *m_bluezAgentManager; BluezProfileManager *m_bluezProfileManager; + MediaPtr m_media; QHash m_adapters; QHash m_devices; Index: src/manager_p.cpp =================================================================== --- src/manager_p.cpp +++ src/manager_p.cpp @@ -28,6 +28,7 @@ #include "adapter_p.h" #include "debug.h" #include "utils.h" +#include "media.h" #include #include @@ -156,6 +157,9 @@ if (interfaces.contains(Strings::orgBluezProfileManager1())) { m_bluezProfileManager = new BluezProfileManager(Strings::orgBluez(), path, DBusConnection::orgBluez(), this); } + if (interfaces.contains(Strings::orgBluezMedia1())) { + m_media = MediaPtr(new Media(path)); + } } if (!m_bluezAgentManager) { @@ -168,6 +172,11 @@ return; } + if (!m_media) { + Q_EMIT initError(QStringLiteral("Cannot find org.bluez.Media1 object!")); + return; + } + connect(m_dbusObjectManager, &DBusObjectManager::InterfacesAdded, this, &ManagerPrivate::interfacesAdded); connect(m_dbusObjectManager, &DBusObjectManager::InterfacesRemoved, Index: src/media.h =================================================================== --- /dev/null +++ src/media.h @@ -0,0 +1,95 @@ +/* + * BluezQt - Asynchronous BlueZ wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef BLUEZQT_MEDIA_H +#define BLUEZQT_MEDIA_H + +#include + +#include "bluezqt_export.h" + +namespace BluezQt +{ + +class MediaEndpoint; +class PendingCall; + +/** + * @class BluezQt::Media Media.h + * + * Bluetooth Media. + * + * This allows media endpoints to be established in accordance with the + * capabilities of a specific media service profile. + * + * For example, an A2DP media endpoint could be created allowing data from a + * remote device to be streamed to/from the sender. + * + * Each media endpoint is associated with a service object instance that + * implements the required behaviours of the endpoint. The service object + * must be created at a given path before it is registered. + * + * @see MediaEndpoint + */ +class BLUEZQT_EXPORT Media : public QObject +{ + Q_OBJECT + +public: + /** + * Destroys a Media object. + */ + ~Media(); + + /** + * Registers endpoint. + * + * Register a local end point to sender, the sender can register as many end points as it likes. + * + * Note: If the sender disconnects the end points are automatically unregistered. + * + * Possible errors: PendingCall::InvalidArguments, PendingCall::NotSupported + * + * @param endpoint endpoint to be registered + * @return void pending call + */ + PendingCall *registerEndpoint(MediaEndpoint *endpoint); + + /** + * Unregisters endpoint. + * + * @param endpoint endpoint to be unregistered + * @return void pending call + */ + PendingCall *unregisterEndpoint(MediaEndpoint *endpoint); + +private: + explicit Media(const QString &path, QObject *parent = nullptr); + + class MediaPrivate *const d; + + friend class ManagerPrivate; +}; + +} // namespace BluezQt + +#endif // BLUEZQT_MEDIA_H Index: src/media.cpp =================================================================== --- /dev/null +++ src/media.cpp @@ -0,0 +1,78 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "media.h" +#include "media_p.h" +#include "mediaendpoint.h" +#include "mediaendpointadaptor.h" +#include "pendingcall.h" +#include "utils.h" +#include "debug.h" + +namespace BluezQt +{ + +Media::Media(const QString &path, QObject *parent) + : QObject(parent) + , d(new MediaPrivate()) +{ + d->m_bluezMedia = new BluezMedia(Strings::orgBluez(), path, DBusConnection::orgBluez(), this); +} + +Media::~Media() +{ + delete d; +} + +PendingCall *Media::registerEndpoint(MediaEndpoint *endpoint) +{ + Q_ASSERT(endpoint); + + if (!d->m_bluezMedia) { + return new PendingCall(PendingCall::InternalError, QStringLiteral("Media not operational!")); + } + + new MediaEndpointAdaptor(endpoint); + + if (!DBusConnection::orgBluez().registerObject(endpoint->objectPath().path(), endpoint)) { + qCDebug(BLUEZQT) << "Cannot register object" << endpoint->objectPath().path(); + } + + return new PendingCall(d->m_bluezMedia->RegisterEndpoint(endpoint->objectPath(), endpoint->properties()), + PendingCall::ReturnVoid, this); +} + +PendingCall *Media::unregisterEndpoint(MediaEndpoint *endpoint) +{ + Q_ASSERT(endpoint); + + if (!d->m_bluezMedia) { + return new PendingCall(PendingCall::InternalError, QStringLiteral("Media not operational!")); + } + + DBusConnection::orgBluez().unregisterObject(endpoint->objectPath().path()); + + return new PendingCall(d->m_bluezMedia->UnregisterEndpoint(endpoint->objectPath()), + PendingCall::ReturnVoid, this); +} + +} // namespace BluezQt Index: src/media_p.h =================================================================== --- /dev/null +++ src/media_p.h @@ -0,0 +1,41 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef BLUEZQT_MEDIA_P_H +#define BLUEZQT_MEDIA_P_H + +#include "bluezmedia1.h" + +namespace BluezQt +{ + +typedef org::bluez::Media1 BluezMedia; + +class MediaPrivate +{ +public: + BluezMedia *m_bluezMedia = nullptr; +}; + +} // namespace BluezQt + +#endif // BLUEZQT_MEDIA_P_H Index: src/mediaendpoint.h =================================================================== --- /dev/null +++ src/mediaendpoint.h @@ -0,0 +1,154 @@ +/* + * BluezQt - Asynchronous BlueZ wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef BLUEZQT_MEDIAENDPOINT_H +#define BLUEZQT_MEDIAENDPOINT_H + +#include + +#include "bluezqt_export.h" +#include "request.h" + +class QDBusObjectPath; + +namespace BluezQt +{ + +/** + * @class BluezQt::MediaEndpoint MediaEndpoint.h + * + * Bluetooth MediaEndpoint. + * + * This class represents a Bluetooth MediaEndpoint. + */ +class BLUEZQT_EXPORT MediaEndpoint : public QObject +{ + Q_OBJECT + +public: + /** Role which this MediaEndpoint acts as. */ + enum class Role { + AudioSource, + AudioSink + }; + + /** Codec which this MediaEndpoint supports. */ + enum class Codec { + Sbc, + Aac + }; + + /** Configuration for MediaEndpoint construction. */ + struct Configuration { + Role role; + Codec codec; + }; + + /** + * Creates a new MediaEndpoint object. + * + * @param parent + */ + explicit MediaEndpoint(const Configuration &configuration, QObject *parent = nullptr); + + /** + * Destroys a MediaEndpoint object. + */ + ~MediaEndpoint(); + + /** + * D-Bus object path of the MediaEndpoint. + * + * The path where the MediaEndpoint will be registered. + * + * @note You must provide valid object path! + * + * @return object path of MediaEndpoint + */ + virtual QDBusObjectPath objectPath() const; + + /** + * Properties of the endpoint. + * + * @return Properties of the endpoint + */ + virtual const QVariantMap &properties() const; + + /** + * Set configuration for the transport. + * + * @param transport transport to be configured + * @param properties properties to be set for transport + */ + virtual void setConfiguration(const QString &transportObjectPath, const QVariantMap &properties); + + /** + * Select preferable configuration from the supported capabilities. + * + * @note There is no need to cache the selected configuration since on success + * the configuration is send back as parameter of SetConfiguration. + * + * @param capabilities supported capabilities + * @param request request to be used for sending reply + */ + virtual void selectConfiguration(const QByteArray &capabilities, const Request &request); + + /** + * Clear transport configuration. + */ + virtual void clearConfiguration(const QString &transportObjectPath); + + /** + * Indicates that the MediaEndpoint was unregistered. + * + * This method gets called when the Bluetooth daemon + * unregisters the MediaEndpoint. + * + * An MediaEndpoint can use it to do cleanup tasks. There is no need + * to unregister the MediaEndpoint, because when this method gets called + * it has already been unregistered. + */ + virtual void release(); + +Q_SIGNALS: + /** + * Indicates that configuration was selected. + */ + void configurationSelected(const QByteArray &capabilities, const QByteArray &configuration); + + /** + * Indicates that configuration was set for transport. + */ + void configurationSet(const QString &transportObjectPath, const QVariantMap &properties); + + /** + * Indicates that configuration was cleared for transport. + */ + void configurationCleared(const QString &transportObjectPath); + +private: + class MediaEndpointPrivate *const d; +}; + +} // namespace BluezQt + +#endif // BLUEZQT_MEDIAENDPOINT_H Index: src/mediaendpoint.cpp =================================================================== --- /dev/null +++ src/mediaendpoint.cpp @@ -0,0 +1,150 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "mediaendpoint.h" +#include "mediaendpoint_p.h" +#include "a2dp-codecs.h" + +namespace BluezQt +{ + +MediaEndpoint::MediaEndpoint(const Configuration& configuration, QObject *parent) + : QObject(parent), + d(new MediaEndpointPrivate(configuration)) +{ +} + +MediaEndpoint::~MediaEndpoint() +{ + delete d; +} + +QDBusObjectPath MediaEndpoint::objectPath() const +{ + return d->m_objectPath; +} + +const QVariantMap &MediaEndpoint::properties() const +{ + return d->m_properties; +} + +void MediaEndpoint::setConfiguration(const QString &transportObjectPath, const QVariantMap &properties) +{ + emit configurationSet(transportObjectPath, properties); +} + +void MediaEndpoint::selectConfiguration(const QByteArray &capabilities, const Request &request) +{ + switch (d->m_configuration.codec) { + case MediaEndpoint::Codec::Sbc: + { + if (capabilities.size() != sizeof(a2dp_sbc_t)) { + emit configurationSelected(capabilities, QByteArray()); + request.reject(); + return; + } + + a2dp_sbc_t caps = *reinterpret_cast(capabilities.constData()); + if (caps.frequency & SBC_SAMPLING_FREQ_44100) { + caps.frequency = SBC_SAMPLING_FREQ_44100; + } else if (caps.frequency & SBC_SAMPLING_FREQ_48000) { + caps.frequency = SBC_SAMPLING_FREQ_48000; + } else { + break; + } + + if (caps.channel_mode & SBC_CHANNEL_MODE_STEREO) { + caps.channel_mode = SBC_CHANNEL_MODE_STEREO; + } else if (caps.channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) { + caps.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO; + } else { + break; + } + + if (caps.block_length & SBC_BLOCK_LENGTH_16) { + caps.block_length = SBC_BLOCK_LENGTH_16; + } else if (caps.block_length & SBC_BLOCK_LENGTH_12) { + caps.block_length = SBC_BLOCK_LENGTH_12; + } else if (caps.block_length & SBC_BLOCK_LENGTH_8) { + caps.block_length = SBC_BLOCK_LENGTH_8; + } else if (caps.block_length & SBC_BLOCK_LENGTH_4) { + caps.block_length = SBC_BLOCK_LENGTH_4; + } else { + break; + } + + if (caps.subbands & SBC_SUBBANDS_8) { + caps.subbands = SBC_SUBBANDS_8; + } else if (caps.subbands & SBC_SUBBANDS_4) { + caps.subbands = SBC_SUBBANDS_4; + } else { + break; + } + + if (caps.allocation_method & SBC_ALLOCATION_LOUDNESS) { + caps.allocation_method = SBC_ALLOCATION_LOUDNESS; + } else if (caps.allocation_method & SBC_ALLOCATION_SNR) { + caps.allocation_method = SBC_ALLOCATION_SNR; + } else { + break; + } + + caps.min_bitpool = 2; + caps.max_bitpool = 53; + + const QByteArray configuration(reinterpret_cast(&caps), sizeof(caps)); + emit configurationSelected(capabilities, configuration); + request.accept(configuration); + return; + + break; + } + case MediaEndpoint::Codec::Aac: + if (capabilities.size() != sizeof(a2dp_aac_t)) { + emit configurationSelected(capabilities, QByteArray()); + request.reject(); + return; + } + + // TODO: implement AAC. However selectConfiguration seems not to be used by iOS nor Android. + emit configurationSelected(capabilities, QByteArray()); + request.reject(); + return; + + break; + } + + emit configurationSelected(capabilities, QByteArray()); + request.reject(); +} + +void MediaEndpoint::clearConfiguration(const QString &transportObjectPath) +{ + emit configurationCleared(transportObjectPath); +} + +void MediaEndpoint::release() +{ +} + +} // namespace BluezQt Index: src/mediaendpoint_p.h =================================================================== --- /dev/null +++ src/mediaendpoint_p.h @@ -0,0 +1,48 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef BLUEZQT_MEDIAENDPOINT_P_H +#define BLUEZQT_MEDIAENDPOINT_P_H + +#include +#include + +#include "mediaendpoint.h" + +namespace BluezQt +{ + +class MediaEndpointPrivate +{ +public: + explicit MediaEndpointPrivate(const MediaEndpoint::Configuration &configuration); + + void init(const MediaEndpoint::Configuration &configuration); + + QVariantMap m_properties; + MediaEndpoint::Configuration m_configuration; + QDBusObjectPath m_objectPath; +}; + +} // namespace BluezQt + +#endif // BLUEZQT_MEDIAENDPOINT_P_H Index: src/mediaendpoint_p.cpp =================================================================== --- /dev/null +++ src/mediaendpoint_p.cpp @@ -0,0 +1,72 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "mediaendpoint_p.h" + +#include "a2dp-codecs.h" +#include "services.h" + +namespace BluezQt +{ + +MediaEndpointPrivate::MediaEndpointPrivate(const MediaEndpoint::Configuration &configuration) + : m_configuration(configuration) +{ + init(configuration); +} + +void MediaEndpointPrivate::init(const MediaEndpoint::Configuration &configuration) +{ + const QString uuid = QStringLiteral("UUID"); + const QString codec = QStringLiteral("Codec"); + const QString capabilities = QStringLiteral("Capabilities"); + + QString objectPath = QStringLiteral("/MediaEndpoint"); + + switch (configuration.role) { + case MediaEndpoint::Role::AudioSource: + m_properties[uuid] = Services::AudioSource; + objectPath += QStringLiteral("/Source"); + break; + case MediaEndpoint::Role::AudioSink: + m_properties[uuid] = Services::AudioSink; + objectPath += QStringLiteral("/Sink"); + break; + } + + switch (configuration.codec) { + case MediaEndpoint::Codec::Sbc: + m_properties[codec] = QVariant::fromValue(uchar(A2DP_CODEC_SBC)); + m_properties[capabilities] = QByteArray(reinterpret_cast(&sbcCapabilities), sizeof(sbcCapabilities)); + objectPath += QStringLiteral("/Sbc"); + break; + case MediaEndpoint::Codec::Aac: + m_properties[codec] = QVariant::fromValue(uchar(A2DP_CODEC_MPEG24)); + m_properties[capabilities] = QByteArray(reinterpret_cast(&aacCapabilities), sizeof(aacCapabilities)); + objectPath += QStringLiteral("/Aac"); + break; + } + + m_objectPath.setPath(objectPath); +} + +} // namespace BluezQt Index: src/mediaendpointadaptor.h =================================================================== --- /dev/null +++ src/mediaendpointadaptor.h @@ -0,0 +1,56 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef BLUEZQT_MEDIAENDPOINTADAPTOR_H +#define BLUEZQT_MEDIAENDPOINTADAPTOR_H + +#include + +class QDBusMessage; +class QDBusObjectPath; + +namespace BluezQt +{ + +class MediaEndpoint; + +class MediaEndpointAdaptor : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.bluez.MediaEndpoint1") + +public: + explicit MediaEndpointAdaptor(MediaEndpoint *parent); + +public Q_SLOTS: + void SetConfiguration(const QDBusObjectPath &transport, const QVariantMap &properties); + QByteArray SelectConfiguration(const QByteArray &capabilities, const QDBusMessage &msg); + void ClearConfiguration(const QDBusObjectPath &transport); + Q_NOREPLY void Release(); + +private: + MediaEndpoint *m_endpoint; +}; + +} // namespace BluezQt + +#endif // BLUEZQT_MEDIAENDPOINTADAPTOR_H Index: src/mediaendpointadaptor.cpp =================================================================== --- /dev/null +++ src/mediaendpointadaptor.cpp @@ -0,0 +1,63 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "mediaendpointadaptor.h" +#include "mediaendpoint.h" +#include "request.h" + +#include +#include + +namespace BluezQt +{ + +MediaEndpointAdaptor::MediaEndpointAdaptor(MediaEndpoint *parent) + : QDBusAbstractAdaptor(parent) + , m_endpoint(parent) +{ +} + +void MediaEndpointAdaptor::SetConfiguration(const QDBusObjectPath &transport, const QVariantMap &properties) +{ + m_endpoint->setConfiguration(transport.path(), properties); +} + +QByteArray MediaEndpointAdaptor::SelectConfiguration(const QByteArray &capabilities, const QDBusMessage &msg) +{ + msg.setDelayedReply(true); + Request req(OrgBluezMediaEndpoint, msg); + + m_endpoint->selectConfiguration(capabilities, req); + return QByteArray(); +} + +void MediaEndpointAdaptor::ClearConfiguration(const QDBusObjectPath &transport) +{ + m_endpoint->clearConfiguration(transport.path()); +} + +void MediaEndpointAdaptor::Release() +{ + m_endpoint->release(); +} + +} // namespace BluezQt Index: src/noinputnooutputagent.h =================================================================== --- /dev/null +++ src/noinputnooutputagent.h @@ -0,0 +1,71 @@ +/* + * BluezQt - Asynchronous BlueZ wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef BLUEZQT_NOINPUTNOOUTPUTAGENT_H +#define BLUEZQT_NOINPUTNOOUTPUTAGENT_H + +#include "agent.h" + +namespace BluezQt +{ + +/** + * @class BluezQt::NoInputNoOutputAgent noinputnooutputagent.h + * + * Bluetooth no input, no output agent. + * + * This agent will auto accept any authorization request from any device with the provided service UUIDs. + */ +class BLUEZQT_EXPORT NoInputNoOutputAgent : public Agent +{ + Q_OBJECT + +public: + /** + * Creates a new NoInputNoOutputAgent object. + * + * @param parent + */ + explicit NoInputNoOutputAgent(const QStringList &uuids, QObject *parent = nullptr); + + /** + * Destroys a NoInputNoOutputAgent object. + */ + ~NoInputNoOutputAgent() override; + +Q_SIGNALS: + /** + * Indicates that a service was authorized. + */ + void serviceAuthorized(DevicePtr device, const QString &uuid, bool allowed); + +private: + QDBusObjectPath objectPath() const override; + Capability capability() const override; + void authorizeService(DevicePtr device, const QString &uuid, const Request<> &request) override; + + class NoInputNoOutputAgentPrivate *const d; +}; + +} // namespace BluezQt + +#endif // BLUEZQT_NOINPUTNOOUTPUTAGENT_H Index: src/noinputnooutputagent.cpp =================================================================== --- /dev/null +++ src/noinputnooutputagent.cpp @@ -0,0 +1,66 @@ +/* + * BluezQt - Asynchronous Bluez wrapper library + * + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "noinputnooutputagent.h" + +#include + +namespace BluezQt +{ + +class NoInputNoOutputAgentPrivate +{ +public: + QStringList allowedUuids; +}; + +NoInputNoOutputAgent::NoInputNoOutputAgent(const QStringList &uuids, QObject *parent) + : Agent(parent) + , d(new NoInputNoOutputAgentPrivate) +{ + d->allowedUuids = uuids; +} + +NoInputNoOutputAgent::~NoInputNoOutputAgent() +{ + delete d; +} + +QDBusObjectPath NoInputNoOutputAgent::objectPath() const +{ + return QDBusObjectPath("/Agent/NoInputNoOutput"); +} + +Agent::Capability NoInputNoOutputAgent::capability() const +{ + return Agent::NoInputNoOutput; +} + +void NoInputNoOutputAgent::authorizeService(DevicePtr device, const QString &uuid, const Request<> &request) +{ + Q_UNUSED(device) + + d->allowedUuids.contains(uuid) ? request.accept() : request.reject(); + emit serviceAuthorized(device, uuid, d->allowedUuids.contains(uuid)); +} + +} // namespace BluezQt Index: src/pendingcall.h =================================================================== --- src/pendingcall.h +++ src/pendingcall.h @@ -192,6 +192,7 @@ friend class Manager; friend class Adapter; friend class Device; + friend class Media; friend class MediaPlayer; friend class ObexManager; friend class ObexTransfer; Index: src/request.h =================================================================== --- src/request.h +++ src/request.h @@ -35,7 +35,8 @@ enum RequestOriginatingType { OrgBluezAgent, OrgBluezProfile, - OrgBluezObexAgent + OrgBluezObexAgent, + OrgBluezMediaEndpoint }; /** @@ -113,6 +114,7 @@ friend class AgentAdaptor; friend class ObexAgentAdaptor; friend class ProfileAdaptor; + friend class MediaEndpointAdaptor; }; // void Index: src/request.cpp =================================================================== --- src/request.cpp +++ src/request.cpp @@ -207,5 +207,6 @@ template class Request; template class Request; template class Request; +template class Request; } // namespace BluezQt Index: src/services.h =================================================================== --- src/services.h +++ src/services.h @@ -39,6 +39,7 @@ const QString ObexFileTransfer = QLatin1String("00001106-0000-1000-8000-00805F9B34FB"); const QString Headset = QLatin1String("00001108-0000-1000-8000-00805F9B34FB"); const QString AudioSource = QLatin1String("0000110A-0000-1000-8000-00805F9B34FB"); +const QString AudioSink = QLatin1String("0000110B-0000-1000-8000-00805F9B34FB"); const QString AudioVideoRemoteControlTarget = QLatin1String("0000110C-0000-1000-8000-00805F9B34FB"); const QString AdvancedAudioDistribution = QLatin1String("0000110D-0000-1000-8000-00805F9B34FB"); const QString AudioVideoRemoteControl = QLatin1String("0000110E-0000-1000-8000-00805F9B34FB"); Index: src/types.h =================================================================== --- src/types.h +++ src/types.h @@ -32,6 +32,7 @@ class Adapter; class Device; class Input; +class Media; class MediaPlayer; class MediaPlayerTrack; class Agent; @@ -52,6 +53,7 @@ typedef QSharedPointer AdapterPtr; typedef QSharedPointer DevicePtr; typedef QSharedPointer InputPtr; +typedef QSharedPointer MediaPtr; typedef QSharedPointer MediaPlayerPtr; typedef QSharedPointer ObexManagerPtr; typedef QSharedPointer ObexSessionPtr; Index: src/utils.h =================================================================== --- src/utils.h +++ src/utils.h @@ -41,6 +41,7 @@ QString orgBluezAdapter1(); QString orgBluezDevice1(); QString orgBluezInput1(); +QString orgBluezMedia1(); QString orgBluezMediaPlayer1(); QString orgBluezAgentManager1(); QString orgBluezProfileManager1(); Index: src/utils.cpp =================================================================== --- src/utils.cpp +++ src/utils.cpp @@ -44,6 +44,7 @@ QString orgBluezAdapter1; QString orgBluezDevice1; QString orgBluezInput1; + QString orgBluezMedia1; QString orgBluezMediaPlayer1; QString orgBluezAgentManager1; QString orgBluezProfileManager1; @@ -65,6 +66,7 @@ orgBluezAdapter1 = QStringLiteral("org.bluez.Adapter1"); orgBluezDevice1 = QStringLiteral("org.bluez.Device1"); orgBluezInput1 = QStringLiteral("org.bluez.Input1"); + orgBluezMedia1 = QStringLiteral("org.bluez.Media1"); orgBluezMediaPlayer1 = QStringLiteral("org.bluez.MediaPlayer1"); orgBluezAgentManager1 = QStringLiteral("org.bluez.AgentManager1"); orgBluezProfileManager1 = QStringLiteral("org.bluez.ProfileManager1"); @@ -115,6 +117,11 @@ return globalData->orgBluezInput1; } +QString Strings::orgBluezMedia1() +{ + return globalData->orgBluezMedia1; +} + QString Strings::orgBluezMediaPlayer1() { return globalData->orgBluezMediaPlayer1; Index: tests/CMakeLists.txt =================================================================== --- tests/CMakeLists.txt +++ tests/CMakeLists.txt @@ -12,4 +12,5 @@ adaptersreceiver devicereceiver chatprofile + mediaendpointconnector ) Index: tests/mediaendpointconnector.h =================================================================== --- /dev/null +++ tests/mediaendpointconnector.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef MEDIAENDPOINTCONNECTOR_H +#define MEDIAENDPOINTCONNECTOR_H + +#include + +#include "manager.h" +#include "types.h" + +class MediaEndpointConnector : public QObject +{ + Q_OBJECT + +public: + explicit MediaEndpointConnector(BluezQt::Manager *manager, QObject *parent = nullptr); + +private: + void onServiceAuthorized(BluezQt::DevicePtr device, const QString &uuid, bool allowed); + void onConfigurationSelected(const QByteArray &capabilities, const QByteArray &configuration); + void onConfigurationSet(const QString& transportObjectPath, const QVariantMap& properties); + void onConfigurationCleared(const QString& transportObjectPath); + + BluezQt::Manager *m_manager; +}; + +#endif // MEDIAENDPOINTCONNECTOR_H Index: tests/mediaendpointconnector.cpp =================================================================== --- /dev/null +++ tests/mediaendpointconnector.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2018 Manuel Weichselbaumer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "mediaendpointconnector.h" + +#include +#include + +#include "device.h" +#include "initmanagerjob.h" +#include "media.h" +#include "mediaendpoint.h" +#include "noinputnooutputagent.h" +#include "services.h" + +using namespace BluezQt; + +MediaEndpointConnector::MediaEndpointConnector(Manager *manager, QObject *parent) + : QObject(parent) + , m_manager(manager) +{ + NoInputNoOutputAgent *agent = new NoInputNoOutputAgent({Services::AdvancedAudioDistribution, Services::AudioVideoRemoteControl}); + connect(agent, &NoInputNoOutputAgent::serviceAuthorized, this, &MediaEndpointConnector::onServiceAuthorized); + manager->registerAgent(agent); + manager->requestDefaultAgent(agent); + + MediaEndpoint *sbcSink = new MediaEndpoint({MediaEndpoint::Role::AudioSink, MediaEndpoint::Codec::Sbc}, manager); + MediaEndpoint *aacSink = new MediaEndpoint({MediaEndpoint::Role::AudioSink, MediaEndpoint::Codec::Aac}, manager); + connect(sbcSink, &MediaEndpoint::configurationSelected, this, &MediaEndpointConnector::onConfigurationSelected); + connect(aacSink, &MediaEndpoint::configurationSelected, this, &MediaEndpointConnector::onConfigurationSelected); + connect(sbcSink, &MediaEndpoint::configurationSet, this, &MediaEndpointConnector::onConfigurationSet); + connect(aacSink, &MediaEndpoint::configurationSet, this, &MediaEndpointConnector::onConfigurationSet); + connect(sbcSink, &MediaEndpoint::configurationCleared, this, &MediaEndpointConnector::onConfigurationCleared); + connect(aacSink, &MediaEndpoint::configurationCleared, this, &MediaEndpointConnector::onConfigurationCleared); + manager->media()->registerEndpoint(sbcSink); + manager->media()->registerEndpoint(aacSink); +} + +void MediaEndpointConnector::onServiceAuthorized(BluezQt::DevicePtr device, const QString &uuid, bool allowed) +{ + qDebug() << (allowed ? "Accepted" : "Rejected") << "service:" << uuid << "from" << device->friendlyName(); +} + +void MediaEndpointConnector::onConfigurationSelected(const QByteArray &capabilities, const QByteArray &configuration) +{ + if (configuration.isEmpty()) { + qDebug() << "No useable configuration found for capabilities:" << capabilities; + } else { + qDebug() << "Selected configuration:" << configuration << "for capabilities:" << capabilities; + } +} + +void MediaEndpointConnector::onConfigurationSet(const QString& transportObjectPath, const QVariantMap& properties) +{ + qDebug() << "Set configuration for transport:" << transportObjectPath << "to:" << properties; +} + +void MediaEndpointConnector::onConfigurationCleared(const QString& transportObjectPath) +{ + qDebug() << "Cleared configuration for transport:" << transportObjectPath; +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + qDebug() << "Waiting for bluetooth audio source to connect. Ctrl + C to cancel..."; + + Manager *manager = new Manager(); + InitManagerJob *initJob = manager->init(); + initJob->exec(); + if (initJob->error()) { + qWarning() << "Error initializing manager:" << initJob->errorText(); + return 1; + } + + new MediaEndpointConnector(manager); + + return app.exec(); +}