diff --git a/Mono.BlueZ.Console/A2DP_Test/BlueZA2DP.cs b/Mono.BlueZ.Console/A2DP_Test/BlueZA2DP.cs new file mode 100644 index 0000000..4ff1311 --- /dev/null +++ b/Mono.BlueZ.Console/A2DP_Test/BlueZA2DP.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DBus; +using org.freedesktop.DBus; +using Mono.BlueZ; +using Mono.BlueZ.DBus; +using System.Threading; + +namespace Mono.BlueZ.Console +{ + public class BlueZA2DP + { + private Bus _system; + public Exception _startupException { get; private set; } + private ManualResetEvent _started = new ManualResetEvent(false); + + private const string BlueZRootPath = "/org/bluez"; + private const string BlueZService = "org.bluez"; + + private ObjectPath ProfilePath = new ObjectPath("/profiles"); + private ObjectPath EndpointPath = new ObjectPath("/led_web/endpoint"); + private ObjectPath BlueZPath = new ObjectPath("/org/bluez"); + + private ObjectManager _objectManager; + private AgentManager1 _agentManager; + private ProfileManager1 _profileManager; + + private Endpoint _endpoint; + private FileDescriptor _filedescriptor; + + public BlueZA2DP() + { + var t = new Thread(DBusLoop); + t.IsBackground = true; + t.Start(); + _started.WaitOne(60 * 1000); + _started.Close(); + if (_startupException != null) + { + throw _startupException; + } + else + { + _objectManager = _system.GetObject(BlueZService, ObjectPath.Root); + _objectManager.InterfacesAdded += _objectManager_InterfacesAdded; + _objectManager.InterfacesRemoved += _objectManager_InterfacesRemoved; + } + } + + public void RegisterEndpoint()//A1. Application Startup + { + var adapter = BlueZUtils.find_adapter(); + var path = adapter.Key; + + var media = _system.GetObject(BlueZService, path); + //_endpoint = _system.GetObject(BlueZService, ObjectPath.Root); + _endpoint = new Endpoint(); + + _system.Register(EndpointPath, _endpoint); + media.RegisterEndpoint(EndpointPath, A2DP.MP3_SINK_PROPERTIES); + + var audiosource = _system.GetObject(BlueZService, ObjectPath.Root); + audiosource.PropertyChanged += AudioSourcePropertyChanged; + } + + private void GetTransportStream() + { + ObjectPath transportPath = _endpoint.TransportPath; + var transport = _system.GetObject(BlueZService, transportPath); + + _filedescriptor = transport.Acquire(); + } + + public FileDescriptor AudioStream + { + get { return _filedescriptor; } + } + + private void _objectManager_InterfacesRemoved(ObjectPath path, string[] interfaces) + { + } + + private void _objectManager_InterfacesAdded(ObjectPath path, IDictionary> interfaces) + { + } + + private void AudioSourcePropertyChanged(string name, object value)//C1. Start streaming event. + { + if (name == "State") + { + switch (value as string) + { + case AudioState.Playing: + GetTransportStream(); + break; + //TODO implement changes to stop stream + } + } + } + + private void DBusLoop() + { + try + { + _system = Bus.System; + } + catch (Exception ex) + { + _startupException = ex; + return; + } + finally + { + _started.Set(); + } + + while (true) + { + _system.Iterate(); + } + } + + private ProfileManager1 ProfileManager + { + get + { + if (_profileManager == null) + { + _profileManager = _system.GetObject(BlueZService, new ObjectPath(BlueZRootPath)); + } + return _profileManager; + } + } + + private ObjectPath GetObjectPathForLocalObject(object obj) + { + return new ObjectPath("/" + obj.GetHashCode()); + } + } +} diff --git a/Mono.BlueZ.Console/A2DP_Test/Endpoint.cs b/Mono.BlueZ.Console/A2DP_Test/Endpoint.cs new file mode 100644 index 0000000..0a4c91d --- /dev/null +++ b/Mono.BlueZ.Console/A2DP_Test/Endpoint.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Mono.BlueZ.DBus; +using Mono.BlueZ; +using DBus; + +namespace Mono.BlueZ.Console +{ + public class Endpoint : MediaEndpoint1 + { + private ObjectPath transportPath; + + public void ClearConfiguration(ObjectPath transport)//B2. Device Disconnection Events + { + transportPath = null; + return; + } + + public void Release() + { + return; + } + + public byte[] SelectConfiguration(byte[] capabilities)//B1. Device Connection Events + { + if(capabilities == A2DP.MP3_CAPABILITIES) + { + return A2DP.MP3_CONFIGURATION; + } + if(capabilities == A2DP.SBC_CAPABILITIES) + { + return A2DP.SBC_CONFIGURATION; + } + return null; + } + + public void SetConfiguration(ObjectPath transport, IDictionary properties)//B1. Device Connection Events + { + transportPath = transport; + return; + } + + public ObjectPath TransportPath + { + get { return transportPath; } + } + } +} diff --git a/Mono.BlueZ.Console/Mono.BlueZ.Console.csproj b/Mono.BlueZ.Console/Mono.BlueZ.Console.csproj index 3176ab3..637c8f9 100644 --- a/Mono.BlueZ.Console/Mono.BlueZ.Console.csproj +++ b/Mono.BlueZ.Console/Mono.BlueZ.Console.csproj @@ -1,4 +1,4 @@ - + Debug @@ -31,6 +31,8 @@ + + PreserveNewest @@ -55,4 +57,4 @@ dbus-sharp - + \ No newline at end of file diff --git a/Mono.BlueZ.Console/Program.cs b/Mono.BlueZ.Console/Program.cs index c735946..e1c0fea 100644 --- a/Mono.BlueZ.Console/Program.cs +++ b/Mono.BlueZ.Console/Program.cs @@ -8,12 +8,16 @@ class MainClass public static void Main (string[] args) { AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler (GlobalHandler); - var bootstrap = new BlendMicroBootstrap (); - bootstrap.Run (); - //var bootstrap = new PebbleBootstrap (); - //bootstrap.Run (true, null); - } + //var bootstrap = new BlendMicroBootstrap (); + //bootstrap.Run (); + + //var bootstrap = new PebbleBootstrap (); + //bootstrap.Run (true, null); + + var a2dp = new BlueZA2DP(); + a2dp.RegisterEndpoint(); + } static void GlobalHandler(object sender, UnhandledExceptionEventArgs args) { diff --git a/Mono.BlueZ.DBus/Audio.cs b/Mono.BlueZ.DBus/Audio.cs new file mode 100644 index 0000000..6d2a11c --- /dev/null +++ b/Mono.BlueZ.DBus/Audio.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using DBus; + +namespace Mono.BlueZ.DBus +{ + public delegate void PropertyChangedHandler(string name, object value); + + public static class AudioState + { + public const string Disconnected = "disconnected"; + public const string Connected = "connected"; + public const string Connecting = "connecting"; + public const string Playing = "playing"; + } +} diff --git a/Mono.BlueZ.DBus/AudioSink.cs b/Mono.BlueZ.DBus/AudioSink.cs new file mode 100644 index 0000000..b4e07b2 --- /dev/null +++ b/Mono.BlueZ.DBus/AudioSink.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using DBus; + +namespace Mono.BlueZ.DBus +{ + [Interface("org.bluez.AudioSink")] + public interface AudioSink + { + void Connect(); + void Disconnect(); + IDictionary GetProperties(); + event PropertyChangedHandler PropertyChanged; + string State { get; } + bool Playing { get; } + } +} diff --git a/Mono.BlueZ.DBus/AudioSource.cs b/Mono.BlueZ.DBus/AudioSource.cs new file mode 100644 index 0000000..1b34187 --- /dev/null +++ b/Mono.BlueZ.DBus/AudioSource.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using DBus; + +namespace Mono.BlueZ.DBus +{ + [Interface("org.bluez.AudioSource")] + public interface AudioSource + { + void Connect(); + void Disconnect(); + IDictionary GetProperties(); + event PropertyChangedHandler PropertyChanged; + string State { get; } + } +} diff --git a/Mono.BlueZ.DBus/MediaEndpoint1.cs b/Mono.BlueZ.DBus/MediaEndpoint1.cs new file mode 100644 index 0000000..8677cc3 --- /dev/null +++ b/Mono.BlueZ.DBus/MediaEndpoint1.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using DBus; + +namespace Mono.BlueZ.DBus +{ + // on /org/bluez/hciX + [Interface("org.bluez.MediaEndpoint1")] + public interface MediaEndpoint1 + { + void SetConfiguration(ObjectPath transport, IDictionary properties); + byte[] SelectConfiguration(byte[] capabilities); + void ClearConfiguration(ObjectPath transport); + void Release(); + } +} \ No newline at end of file diff --git a/Mono.BlueZ.DBus/MediaTransport1.cs b/Mono.BlueZ.DBus/MediaTransport1.cs new file mode 100644 index 0000000..e6ef645 --- /dev/null +++ b/Mono.BlueZ.DBus/MediaTransport1.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using DBus; + +namespace Mono.BlueZ.DBus +{ + // on /org/bluez/hciX + [Interface("org.bluez.MediaTransport1")] + public interface MediaTransport1 + { + FileDescriptor Acquire(); + //TODO fd, uint16, uint16 Acquire() "Acquire transport file descriptor and the MTU for readand write respectively." + FileDescriptor TryAcquire(); + //TODO fd, uint16, uint16 TryAcquire() + void Release(); + ObjectPath Device {get;} + string UUID {get;} + byte Codec {get;} + byte[] Configuration {get;} + string State {get;} + ushort Delay {get;set;} + ushort Volume {get;set;} + } + + public static class TransportState + { + public const string Idle = "idle"; + public const string Pending = "pending"; + public const string Active = "active"; + } +} diff --git a/Mono.BlueZ.DBus/Mono.BlueZ.DBus.csproj b/Mono.BlueZ.DBus/Mono.BlueZ.DBus.csproj index 205033c..eb6ab8f 100644 --- a/Mono.BlueZ.DBus/Mono.BlueZ.DBus.csproj +++ b/Mono.BlueZ.DBus/Mono.BlueZ.DBus.csproj @@ -1,4 +1,4 @@ - + Debug @@ -31,6 +31,11 @@ + + + + + @@ -64,4 +69,4 @@ dbus-sharp - + \ No newline at end of file diff --git a/Mono.BlueZ/A2DP.cs b/Mono.BlueZ/A2DP.cs new file mode 100644 index 0000000..be1bc57 --- /dev/null +++ b/Mono.BlueZ/A2DP.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; + +namespace Mono.BlueZ +{ + public static class A2DP + { + #region const + public const string A2DP_SOURCE_UUID = "0000110A-0000-1000-8000-00805F9B34FB"; + public const string A2DP_SINK_UUID = "0000110B-0000-1000-8000-00805F9B34FB"; + public const string HFP_AG_UUID = "0000111F-0000-1000-8000-00805F9B34FB"; + public const string HFP_HF_UUID = "0000111E-0000-1000-8000-00805F9B34FB"; + public const string HSP_AG_UUID = "00001112-0000-1000-8000-00805F9B34FB"; + + + public const byte SBC_CODEC = 0;//dbus.Byte(0x00) + //Channel Modes: Mono DualChannel Stereo JointStereo + //Frequencies: 16Khz 32Khz 44.1Khz 48Khz + //Subbands: 4 8 + //Blocks: 4 8 12 16 + //Bitpool Range: 2-64 + public static byte[] SBC_CAPABILITIES = new byte[] { 0xff, 0xff, 2, 64 };//dbus.Array([dbus.Byte(0xff), dbus.Byte(0xff), dbus.Byte(2), dbus.Byte(64)]) + //JointStereo 44.1Khz Subbands: Blocks: 16 Bitpool Range: 2-32 + public static byte[] SBC_CONFIGURATION = new byte[] { 0x21, 0x15, 2, 32 };//dbus.Array([dbus.Byte(0x21), dbus.Byte(0x15), dbus.Byte(2), dbus.Byte(32)]) + + + + public const byte MP3_CODEC = 1;//dbus.Byte(0x01) + //Channel Modes: Mono DualChannel Stereo JointStereo + //Frequencies: 32Khz 44.1Khz 48Khz + //CRC: YES + //Layer: 3 + //Bit Rate: All except Free format + //VBR: Yes + //Payload Format: RFC-2250 + public static byte[] MP3_CAPABILITIES = new byte[] { 0x3f, 0x07, 0xff, 0xfe };//dbus.Array([dbus.Byte(0x3f), dbus.Byte(0x07), dbus.Byte(0xff), dbus.Byte(0xfe)]) + //JointStereo 44.1Khz Layer: 3 Bit Rate: VBR Format: RFC-2250 + public static byte[] MP3_CONFIGURATION = new byte[] { 0x21, 0x02, 0x00, 0x80 };//dbus.Array([dbus.Byte(0x21), dbus.Byte(0x02), dbus.Byte(0x00), dbus.Byte(0x80)]) + + + + public const byte PCM_CODEC = 0;//dbus.Byte(0x00) + //public static const PCM_CONFIGURATION = dbus.Array([], signature="ay") + //TODO pcm config + + public const byte CVSD_CODEC = 1;//dbus.Byte(0x01) + #endregion + + #region Propterties + public static IDictionary SBC_SOURCE_PROPERTIES = new Dictionary + { + {"UUID" , A2DP_SOURCE_UUID}, + {"Codec" , SBC_CODEC}, + {"DelayReporting" , true}, + {"Capabilities" , SBC_CAPABILITIES} + }; + + public static IDictionary SBC_SINK_PROPERTIES = new Dictionary + { + {"UUID" , A2DP_SINK_UUID}, + {"Codec" , SBC_CODEC}, + {"DelayReporting" , true}, + {"Capabilities" , SBC_CAPABILITIES} + }; + + public static IDictionary MP3_SOURCE_PROPERTIES = new Dictionary + { + {"UUID" , A2DP_SOURCE_UUID}, + {"Codec" , MP3_CODEC}, + {"Capabilities" , MP3_CAPABILITIES} + }; + + public static IDictionary MP3_SINK_PROPERTIES = new Dictionary + { + {"UUID" , A2DP_SINK_UUID}, + {"Codec" , MP3_CODEC}, + {"Capabilities" , MP3_CAPABILITIES} + }; + + //TODO based on PCM_CONF + /*public static IDictionary HFP_AG_PROPERTIES = new Dictionary + { + {"UUID" , HFP_AG_UUID}, + {"Codec" , PCM_CODEC}, + {"Capabilities" , PCM_CONFIGURATION} + }; + + public static IDictionary HFP_HF_PROPERTIES = new Dictionary + { + {"UUID" , HFP_HF_UUID}, + {"Codec" , CVSD_CODEC}, + {"Capabilities" , PCM_CONFIGURATION} + };*/ + #endregion + } +} diff --git a/Mono.BlueZ/BluezUtils.cs b/Mono.BlueZ/BluezUtils.cs new file mode 100644 index 0000000..f19f04a --- /dev/null +++ b/Mono.BlueZ/BluezUtils.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using DBus; +using Mono.BlueZ.DBus; + +namespace Mono.BlueZ +{ + //https://git.kernel.org/cgit/bluetooth/bluez.git/tree/test/bluezutils.py + public static class BlueZUtils//TODO Address pattern + { + private const string ServiceName = "org.bluez"; + + public static IDictionary>> get_managed_objects() + { + var bus = Bus.System; + var manager = bus.GetObject(ServiceName, ObjectPath.Root); + return manager.GetManagedObjects(); + } + + public static KeyValuePair find_adapter() + { + return find_adapter_in_objects(get_managed_objects()); + } + + public static KeyValuePair find_adapter_in_objects(IDictionary>> managedObjects) + { + var bus = Bus.System; + ObjectPath adapterPath = null; + foreach (var obj in managedObjects.Keys) + { + if (managedObjects[obj].ContainsKey(typeof(Adapter1).DBusInterfaceName())) + { + adapterPath = obj; + break; + } + } + + var adapter = bus.GetObject(ServiceName, adapterPath); + + return new KeyValuePair(adapterPath, adapter); + } + + public static KeyValuePair find_device(string device_address) + { + return find_device_in_objects(get_managed_objects(), device_address); + } + + public static KeyValuePair find_device_in_objects(IDictionary>> managedObjects, string device_adress) + { + var bus = Bus.System; + ObjectPath devicePath = null; + foreach (var obj in managedObjects.Keys) + { + if (managedObjects[obj].ContainsKey(typeof(Device1).DBusInterfaceName())) + { + devicePath = obj; + break; + } + } + + var device = bus.GetObject(ServiceName, devicePath); + + return new KeyValuePair(devicePath, device); + } + } +} diff --git a/Mono.BlueZ/Mono.BlueZ.csproj b/Mono.BlueZ/Mono.BlueZ.csproj index 047227d..c188fe6 100644 --- a/Mono.BlueZ/Mono.BlueZ.csproj +++ b/Mono.BlueZ/Mono.BlueZ.csproj @@ -31,7 +31,9 @@ + + @@ -45,4 +47,4 @@ - + \ No newline at end of file