diff --git a/BluetoothAudio/AudioEndpoint.h b/BluetoothAudio/AudioEndpoint.h index 4ccaac691..3dd16b3a1 100644 --- a/BluetoothAudio/AudioEndpoint.h +++ b/BluetoothAudio/AudioEndpoint.h @@ -243,6 +243,8 @@ namespace A2DP { ASSERT(_handler != nullptr); + TRACE(ServerFlow, (_T("Configuring endpoint %d by client"), Id())); + _lock.Lock(); // Configure Media Transport @@ -287,6 +289,8 @@ namespace A2DP { { uint32_t result = Core::ERROR_ILLEGAL_STATE; + TRACE(ServerFlow, (_T("Starting endpoint %d by client"), Id())); + _lock.Lock(); if (State() == StreamEndPoint::OPENED) { @@ -308,6 +312,8 @@ namespace A2DP { _lock.Lock(); + TRACE(ServerFlow, (_T("Suspending endpoint %d by client"), Id())); + if (State() == StreamEndPoint::STARTED) { result = _remote.Suspend(*this); @@ -325,6 +331,8 @@ namespace A2DP { { uint32_t result = Core::ERROR_ILLEGAL_STATE; + TRACE(ServerFlow, (_T("Opening endpoint %d by client"), Id())); + _lock.Lock(); if (State() == StreamEndPoint::CONFIGURED) { @@ -344,6 +352,8 @@ namespace A2DP { { uint32_t result = Core::ERROR_ILLEGAL_STATE; + TRACE(ServerFlow, (_T("Closing endpoint %d by client"), Id())); + _lock.Lock(); if ((State() == StreamEndPoint::OPENED) || (State() == StreamEndPoint::STARTED)) { @@ -363,12 +373,14 @@ namespace A2DP { { uint32_t result = Core::ERROR_ILLEGAL_STATE; + TRACE(ServerFlow, (_T("Aborting endpoint %d by client"), Id())); + _lock.Lock(); if ((State() == StreamEndPoint::CONFIGURED) || (State() == StreamEndPoint::OPENED) || (State() == StreamEndPoint::STARTED)) { result = _remote.Abort(*this); - if (result == Core::ERROR_NONE) {; + if (result == Core::ERROR_NONE) { result = OnAbort(); } } @@ -385,7 +397,7 @@ namespace A2DP { && (State() != Bluetooth::AVDTP::StreamEndPoint::CLOSING) && (State() != Bluetooth::AVDTP::StreamEndPoint::ABORTING)) { - TRACE(ServerFlow, (_T("Endpoint %d device disconnected unexpectedly"), Id())); + TRACE(ServerFlow, (_T("Endpoint %d device disconnected unexpectedly!"), Id())); // Deallocate the sink and teardown internal data, no point in closing the remote // device as it's not longer reachable. @@ -441,8 +453,7 @@ namespace A2DP { else { failedCategory = Service::MEDIA_CODEC; - TRACE(ServerFlow, (_T("Requested MEDIA_CODEC configuration is unsupported or invalid for codec %02x"), - Codec()->Type())); + TRACE(ServerFlow, (_T("Requested MEDIA_CODEC configuration is unsupported or invalid for codec %02x"), Codec()->Type())); } } } @@ -467,20 +478,20 @@ namespace A2DP { _lock.Lock(); if (State() == StreamEndPoint::CONFIGURED) { - if (_handler->OnAcquire() == Core::ERROR_NONE) { + result = _handler->OnAcquire(); + + if (result == Core::ERROR_NONE) { TRACE(ServerFlow, (_T("Endpoint %d opened"), Id())); State(StreamEndPoint::OPENED); } - - result = Core::ERROR_NONE; } + _lock.Unlock(); + if (result != Core::ERROR_NONE) { TRACE(Trace::Error, (_T("Failed to open endpoint %d [%d]"), Id(), result)); } - _lock.Unlock(); - return (result); } uint32_t OnClose() override diff --git a/BluetoothAudio/BluetoothAudio.cpp b/BluetoothAudio/BluetoothAudio.cpp index 3ca87c00f..f49383069 100644 --- a/BluetoothAudio/BluetoothAudio.cpp +++ b/BluetoothAudio/BluetoothAudio.cpp @@ -91,11 +91,8 @@ namespace Plugin { /* virtual */ void BluetoothAudio::Deinitialize(PluginHost::IShell* service) { if (_service != nullptr) { - ASSERT(_service == service); - SignallingServer::Instance().Stop(); - if (_source != nullptr) { _source->Deinitialize(service); _source->Release(); @@ -111,6 +108,9 @@ namespace Plugin { service->Unregister(&_comNotificationSink); service->Release(); + + SignallingServer::Instance().Clear(); + _service = nullptr; } } diff --git a/BluetoothAudio/BluetoothAudioSink.h b/BluetoothAudio/BluetoothAudioSink.h index a4aec4c6b..ed7fcacbe 100644 --- a/BluetoothAudio/BluetoothAudioSink.h +++ b/BluetoothAudio/BluetoothAudioSink.h @@ -1024,7 +1024,8 @@ namespace Plugin { result = Core::ERROR_UNAVAILABLE; } else { - TRACE(SinkFlow, (_T("Configuring audio endpoint 0x%02x to: sample rate: %i Hz, resolution: %i bits per sample, channels: %i, frame rate: %i.%02i Hz"), _endpoint->Id(), format.SampleRate, format.Resolution, format.Channels, (format.FrameRate / 100), (format.FrameRate % 100))); + TRACE(SinkFlow, (_T("Configuring audio endpoint 0x%02x to: sample rate: %d Hz, resolution: %d bits per sample, channels: %d, frame rate: %d.%02d Hz"), + _endpoint->Id(), format.SampleRate, format.Resolution, format.Channels, (format.FrameRate / 100), (format.FrameRate % 100))); Bluetooth::A2DP::IAudioCodec::StreamFormat streamFormat; streamFormat.SampleRate = format.SampleRate; @@ -1548,17 +1549,14 @@ namespace Plugin { { _lock.Lock(); - ASSERT(_callback == nullptr); if (_callback != nullptr) { _callback->Release(); } - ASSERT(_source == nullptr); if (_source != nullptr) { _source->Release(); } - ASSERT(_service == nullptr); if (_service != nullptr) { _service->Release(); } diff --git a/BluetoothAudio/BluetoothAudioSource.h b/BluetoothAudio/BluetoothAudioSource.h index 3eb0af85c..0f7bb0f4d 100644 --- a/BluetoothAudio/BluetoothAudioSource.h +++ b/BluetoothAudio/BluetoothAudioSource.h @@ -240,6 +240,7 @@ namespace Plugin { ctrl->Release(); for (auto& device : _devices) { + device.second->Control()->Callback(static_cast(nullptr)); device.second->Release(); } } @@ -352,8 +353,8 @@ namespace Plugin { }); }); - if ((result != Core::ERROR_NONE) && (result != Core::ERROR_ALREADY_RELEASED)) { - TRACE(Trace::Error, (_T("Failed to relinquish endpoint [%d]"), result)); + if (result != Core::ERROR_NONE) { + TRACE(Trace::Error, (_T("Failed to abort endpoint [%d]"), result)); } return (result); @@ -374,6 +375,10 @@ namespace Plugin { else { result = Core::ERROR_UNAVAILABLE; } + + if (result != Core::ERROR_NONE) { + TRACE_GLOBAL(Trace::Error, (_T("Failed to set speed!"))); + } }); }); @@ -409,7 +414,9 @@ namespace Plugin { event.SetEvent(); }); - event.Lock(CallTimeoutMs); + if (event.Lock(CallTimeoutMs) == Core::ERROR_TIMEDOUT) { + TRACE_GLOBAL(Trace::Error, (_T("Async call timed out!"))); + } } private: @@ -423,6 +430,7 @@ namespace Plugin { A2DPSource(BluetoothAudioSource& parent, const Config& config) : _parent(parent) , _lock() + , _bufferLock() , _broker(*this) , _localClient(Core::Service::Create(*this)) , _sink(nullptr) @@ -435,6 +443,8 @@ namespace Plugin { , _endpoint(nullptr) , _signallingChannel() , _transportChannel() + , _streamProperties() + , _reconnectionEvent(false, true) { } ~A2DPSource() override @@ -532,43 +542,76 @@ namespace Plugin { } private: - // DeviceBoker callbaks - void OnDeviceConnected(DeviceBroker::Device& device) + void MaybeReconnect(DeviceBroker::Device& device, const uint8_t psm) { - // One of the observed devices has been connected, examine it! + _reconnectJob.Schedule([this, &device, psm]() { - std::list services; + if (_reconnectionEvent.IsSet() == false) { + TRACE(Trace::Information, (_T("Attemping reconnection to %s..."), device.Address().c_str())); - _discovery.LocalNode(Designator(device.Control())); - _discovery.RemoteNode(Designator(device.Control(), Bluetooth::SDP::PSM)); + _initiatedConnection = SignallingServer::Instance().Create(Designator(device.Control()), Designator(device.Control(), psm)); - if (_discovery.Connect() == Core::ERROR_NONE) { + if (_initiatedConnection.IsValid() == true) { + _initiatedConnection->Open(1000); - if (_discovery.Discover({Bluetooth::SDP::ClassID::AudioSource}, services) != Core::ERROR_NONE) { - TRACE(Trace::Error, (_T("Service discovery failed!"))); - } - else { - if (services.empty() == false) { - ServiceDiscovery::AudioService& service = services.front(); + if (_initiatedConnection->IsOpen() == true) { + _initiatedConnection->Latch(true); - if (services.size() > 1) { - TRACE(SourceFlow, (_T("More than one audio source available, using the first one!"))); + // If they know us they may re-establish the connection, give them some time to decide. + if (_reconnectionEvent.Lock(100000) == Core::ERROR_TIMEDOUT) { + _initiatedConnection->Latch(false); + _initiatedConnection->Close(0); + } } + } + } - TRACE(SourceFlow, (_T("Audio source service available: A2DP v%d.%d, AVDTP v%d.%d, L2CAP PSM: %i, features: 0b%s"), - (service.ProfileVersion() >> 8), (service.ProfileVersion() & 0xFF), - (service.TransportVersion() >> 8), (service.TransportVersion() & 0xFF), - service.PSM(), std::bitset<8>(service.Features()).to_string().c_str())); + }, 500); + } - device.Type(service.Features()); + // DeviceBoker callbaks + void OnDeviceConnected(DeviceBroker::Device& device) + { + // One of the observed devices has been connected, examine it! + + if ((_discovery.IsOpen() == false) && (_discovery.IsConnecting() == false)) { + std::list services; + + _discovery.LocalNode(Designator(device.Control())); + _discovery.RemoteNode(Designator(device.Control(), Bluetooth::SDP::PSM)); + + if (_discovery.Connect() == Core::ERROR_NONE) { + + if (_discovery.Discover({Bluetooth::SDP::ClassID::AudioSource}, services) != Core::ERROR_NONE) { + TRACE(Trace::Error, (_T("Service discovery failed!"))); } else { - TRACE(SourceFlow, (_T("Device %s is not an audio source device, stop observing"), device.Address().c_str())); - _broker.Forget(device); + if (services.empty() == false) { + ServiceDiscovery::AudioService& service = services.front(); + + if (services.size() > 1) { + TRACE(SourceFlow, (_T("More than one audio source available, using the first one!"))); + } + + TRACE(SourceFlow, (_T("Audio source service available: A2DP v%d.%d, AVDTP v%d.%d, L2CAP PSM: %i, features: 0b%s"), + (service.ProfileVersion() >> 8), (service.ProfileVersion() & 0xFF), + (service.TransportVersion() >> 8), (service.TransportVersion() & 0xFF), + service.PSM(), std::bitset<8>(service.Features()).to_string().c_str())); + + device.Type(service.Features()); + + if (_reconnectionEvent.IsSet() == false) { + MaybeReconnect(device, service.PSM()); + } + } + else { + TRACE(SourceFlow, (_T("Device %s is not an audio source device, stop observing"), device.Address().c_str())); + _broker.Forget(device); + } } - } - _discovery.Disconnect(); + _discovery.Disconnect(); + } } } @@ -631,6 +674,10 @@ namespace Plugin { _signallingChannel.Release(); } + if (_initiatedConnection.IsValid() == true) { + _initiatedConnection.Release(); + } + if (_device != nullptr) { _device->Release(); _device = nullptr; @@ -638,6 +685,8 @@ namespace Plugin { _endpoint = nullptr; + _reconnectionEvent.ResetEvent(); + State(Exchange::IBluetoothAudio::DISCONNECTED); } @@ -668,26 +717,20 @@ namespace Plugin { _lock.Unlock(); } else { - if (channel->Type() == Bluetooth::AVDTP::Socket::SIGNALLING) { + _cleanupJob.Submit([this, channel]() { + _lock.Lock(); - _lock.Lock(); - - if ((_signallingChannel.IsValid() == true) - && (_signallingChannel->RemoteNode().HostName() == channel->RemoteNode().HostName())) { - - // Looks like our signalling connection has been abruptly closed, clean up! - ASSERT(_endpoint != nullptr); - - // The endpoint is a static resource. - A2DP::AudioEndpoint* endpoint = _endpoint; + if ((_signallingChannel.IsValid() == true) + && (_signallingChannel->RemoteNode().HostName() == channel->RemoteNode().HostName())) { - _cleanupJob.Submit([endpoint]() { - endpoint->Disconnected(); - }); - } + // Looks like our signalling connection has been abruptly closed, clean up! + ASSERT(_endpoint != nullptr); + _endpoint->Disconnected(); + } - _lock.Unlock(); + _lock.Unlock(); + }); } } } @@ -709,6 +752,8 @@ namespace Plugin { TRACE(SourceFlow, (_T("Signalling channel is connecting"))); State(Exchange::IBluetoothAudio::CONNECTING); + _reconnectionEvent.SetEvent(); + Bluetooth::A2DP::IAudioCodec::StreamFormat streamFormat; string _; @@ -724,13 +769,18 @@ namespace Plugin { format.Channels = streamFormat.Channels; format.Resolution = streamFormat.Resolution; + if (format.FrameRate == 0) { + format.FrameRate = 7500; // redbook audio + } + if (_sink->Configure(format) == Core::ERROR_NONE) { - TRACE(SourceFlow, (_T("Raw stream format: %d Hz, %d bits, %d channels"), format.SampleRate, format.Resolution, format.Channels)); + TRACE(SourceFlow, (_T("Raw stream format: %d Hz, %d bits, %d channels, frame rate: %d.%02d Hz"), + format.SampleRate, format.Resolution, format.Channels, (format.FrameRate / 100), (format.FrameRate % 100))); _streamProperties.SampleRate = streamFormat.SampleRate; - _streamProperties.BitRate = 0; // TODO _streamProperties.Channels = streamFormat.Channels; _streamProperties.Resolution = streamFormat.Resolution; + _streamProperties.BitRate = endpoint.Codec()->BitRate(); _streamProperties.IsResampled = false; Assign(endpoint, SignallingServer::Instance().Claim(channel->RemoteNode())); @@ -768,12 +818,16 @@ namespace Plugin { else { ASSERT(_sendBuffer == nullptr); + _bufferLock.Lock(); + // Create the transport medium. - _sendBuffer.reset(new (std::nothrow) SendBuffer(_connector, 16*1024)); + _sendBuffer.reset(new (std::nothrow) SendBuffer(_connector, (32 * 1024))); ASSERT(_sendBuffer.get() != nullptr); if (_sendBuffer->IsValid() == true) { + _bufferLock.Unlock(); + // See if the receiver is ready to handle this format. if (_sink->Acquire(_connector) == Core::ERROR_NONE) { // All OK!! @@ -789,6 +843,8 @@ namespace Plugin { } } else { + _bufferLock.Unlock(); + TRACE(Trace::Error, (_T("Failed to open the transport medium"))); result = Core::ERROR_UNAVAILABLE; } @@ -806,6 +862,8 @@ namespace Plugin { { uint32_t result = Core::ERROR_ILLEGAL_STATE; + TRACE(Trace::Information, (_T("Releasing receiver audio sink..."))); + _lock.Lock(); if (_sink == nullptr) { @@ -813,6 +871,11 @@ namespace Plugin { TRACE(Trace::Information, (_T("No receiver audio sink available to relinquish"))); } else { + // Enforce the flow, even if the client or remote device do not. + if (_sink->Speed(0) != Core::ERROR_NONE) { + TRACE(Trace::Error, (_T("Failed to stop audio sink"))); + } + result = _sink->Relinquish(); if ((result != Core::ERROR_NONE) && (result != Core::ERROR_ALREADY_RELEASED)) { @@ -821,15 +884,20 @@ namespace Plugin { } // Even if failed earlier, continue with teardown... + + _bufferLock.Lock(); + _sendBuffer.reset(); - result = Core::ERROR_NONE; + _bufferLock.Unlock(); Unassign(); + _lock.Unlock(); + TRACE(Trace::Information, (_T("Relinquished receiver audio sink%s"), (result != Core::ERROR_NONE? " (with errors)" : ""))); - _lock.Unlock(); + result = Core::ERROR_NONE; return (result); } @@ -868,23 +936,30 @@ namespace Plugin { // SignallingServer::IMediaTransport overrides void Packet(const uint8_t data[], const uint16_t length) override { - ASSERT(_sendBuffer != nullptr); - ASSERT(_endpoint != nullptr); + _bufferLock.Lock(); - Bluetooth::RTP::MediaPacket packet(data, length); + if (_sendBuffer != nullptr) { - uint8_t stream[32*1024]; + ASSERT(_sendBuffer != nullptr); + ASSERT(_endpoint != nullptr); - uint16_t produced = sizeof(stream); - packet.Unpack(*_endpoint->Codec(), stream, produced); + Bluetooth::RTP::MediaPacket packet(data, length); - // CMD_DUMP("audio stream", stream, produced); + uint8_t stream[32*1024]; - if (produced != 0) { - if (_sendBuffer->Write(produced, stream) != produced) { - TRACE(Trace::Error, (_T("Receiver failed to consume stream data"))); + uint16_t produced = sizeof(stream); + packet.Unpack(*_endpoint->Codec(), stream, produced); + + // CMD_DUMP("audio stream", stream, produced); + + if (produced != 0) { + if (_sendBuffer->Write(produced, stream) != produced) { + TRACE(Trace::Error, (_T("Receiver failed to consume stream data"))); + } } } + + _bufferLock.Unlock(); } public: @@ -963,6 +1038,7 @@ namespace Plugin { private: BluetoothAudioSource& _parent; mutable Core::CriticalSection _lock; + mutable Core::CriticalSection _bufferLock; DeviceBroker _broker; LocalClient* _localClient; Exchange::IBluetoothAudio::IStream* _sink; @@ -970,6 +1046,7 @@ namespace Plugin { string _connector; DecoupledJob _updateJob; DecoupledJob _cleanupJob; + DecoupledJob _reconnectJob; Bluetooth::A2DP::IAudioCodec* _codec; std::unique_ptr _sendBuffer; ServiceDiscovery _discovery; @@ -978,6 +1055,8 @@ namespace Plugin { Core::ProxyType _signallingChannel; Core::ProxyType _transportChannel; Exchange::IBluetoothAudio::StreamProperties _streamProperties; + Core::ProxyType _initiatedConnection; + Core::Event _reconnectionEvent; }; //class A2DPSource public: @@ -996,20 +1075,21 @@ namespace Plugin { } ~BluetoothAudioSource() { - ASSERT(_callback == nullptr); + _lock.Lock(); + if (_callback != nullptr) { _callback->Release(); } - ASSERT(_source == nullptr); if (_source != nullptr) { _source->Release(); }; - ASSERT(_service == nullptr); if (_service != nullptr) { _service->Release(); } + + _lock.Unlock(); } public: diff --git a/BluetoothAudio/ServiceDiscovery.h b/BluetoothAudio/ServiceDiscovery.h index 1816a6919..7aa1c4c52 100644 --- a/BluetoothAudio/ServiceDiscovery.h +++ b/BluetoothAudio/ServiceDiscovery.h @@ -27,7 +27,7 @@ namespace Plugin { class ServiceDiscovery : public Bluetooth::SDP::ClientSocket { private: - static constexpr uint16_t OpenTimeout = 500; // ms + static constexpr uint16_t OpenTimeout = 5000; // ms static constexpr uint16_t CloseTimeout = 5000; static constexpr uint16_t DiscoverTimeout = 5000; diff --git a/BluetoothAudio/SignallingServer.h b/BluetoothAudio/SignallingServer.h index 6ca60c5f9..e8bdfc8b8 100644 --- a/BluetoothAudio/SignallingServer.h +++ b/BluetoothAudio/SignallingServer.h @@ -365,7 +365,7 @@ namespace Plugin { } _peerConnections.Visit([this, &timeNow](const string& address, Core::ProxyType& connection) { - if (connection->IsLatched() == false) { + if ((connection->IsOpen() == true) && (connection->IsLatched() == false)) { const uint64_t lastActivity = connection->LastActivity(); const uint64_t elapsed = (timeNow > lastActivity? timeNow - lastActivity : 0); @@ -407,6 +407,7 @@ namespace Plugin { SignallingServer() : _socketServer() , _lock() + , _observersLock() , _endpoints() , _observers() , _mediaReceiver(nullptr) @@ -414,7 +415,7 @@ namespace Plugin { } ~SignallingServer() { - Stop(); + Clear(); } public: @@ -423,6 +424,13 @@ namespace Plugin { static SignallingServer& server = Core::SingletonType::Instance(); return (server); } + void Clear() + { + Stop(); + _observers.clear(); + _endpoints.clear(); + _mediaReceiver = nullptr; + } public: uint8_t Add(const bool sink, Bluetooth::A2DP::IAudioCodec* codec, A2DP::AudioEndpoint::IHandler& handler) @@ -487,19 +495,19 @@ namespace Plugin { { // Register a new AVDTP connection observer. - _lock.Lock(); + _observersLock.Lock(); ASSERT(std::find(_observers.begin(), _observers.end(), observer) == _observers.end()); _observers.push_back(observer); - _lock.Unlock(); + _observersLock.Unlock(); } void Unregister(const IObserver* observer) { // Unregister an AVDTP connection observer. - _lock.Lock(); + _observersLock.Lock(); auto it = std::find(_observers.begin(), _observers.end(), observer); @@ -507,7 +515,7 @@ namespace Plugin { _observers.erase(it); } - _lock.Unlock(); + _observersLock.Unlock(); } public: @@ -568,13 +576,13 @@ namespace Plugin { { ASSERT(channel.IsValid() == true); - _lock.Lock(); + _observersLock.Lock(); for (auto& observer : _observers) { observer->Operational(channel, isRunning); } - _lock.Unlock(); + _observersLock.Unlock(); } void OnSignal(const Bluetooth::AVDTP::Signal& signal, const Bluetooth::AVDTP::Socket::ResponseHandler& handler) { @@ -610,6 +618,7 @@ namespace Plugin { private: Core::ProxyType _socketServer; Core::CriticalSection _lock; + Core::CriticalSection _observersLock; std::map _endpoints; std::list _observers; IMediaReceiver* _mediaReceiver;