diff --git a/.github/workflows/Linux build template.yml b/.github/workflows/Linux build template.yml index ed81b286..4b097071 100644 --- a/.github/workflows/Linux build template.yml +++ b/.github/workflows/Linux build template.yml @@ -68,8 +68,6 @@ jobs: -DCMAKE_C_FLAGS="-m${{matrix.architecture}}" \ -DCMAKE_INSTALL_PREFIX="${{matrix.build_type}}/install/usr" \ -DCMAKE_MODULE_PATH="${PWD}/${{matrix.build_type}}/install/usr/include/WPEFramework/Modules" \ - -DBLUETOOTHAUDIOSINK=ON \ - -DBLUETOOTHAUDIOSOURCE=ON \ -DCDMI=ON \ -DCOMPOSITORCLIENT=ON \ -DPLUGIN_COMPOSITOR_IMPLEMENTATION=Mesa \ diff --git a/.github/workflows/MacOS build template.yml b/.github/workflows/MacOS build template.yml index 4510b74b..db65b2ed 100644 --- a/.github/workflows/MacOS build template.yml +++ b/.github/workflows/MacOS build template.yml @@ -61,8 +61,6 @@ jobs: cmake -G Ninja -S ThunderClientLibraries -B ${{matrix.build_type}}/build/ThunderClientLibraries \ -DCMAKE_INSTALL_PREFIX="${{matrix.build_type}}/install/usr" \ -DCMAKE_MODULE_PATH="${PWD}/${{matrix.build_type}}/install/usr/include/WPEFramework/Modules" \ - -DBLUETOOTHAUDIOSINK=ON \ - -DBLUETOOTHAUDIOSOURCE=ON \ -DCDMI=ON \ -DCRYPTOGRAPHY=ON \ -DCRYPTOGRAPHY_IMPLEMENTATION="OpenSSL" \ diff --git a/.gitignore b/.gitignore index eaf91c0d..9ed30d6a 100644 --- a/.gitignore +++ b/.gitignore @@ -36,8 +36,6 @@ install_manifest.txt Source/deviceinfo/device_info/device_info Source/displayinfo/display_info/display_info Source/playerinfo/player_info/player_info -Source/bluetoothaudiosink/btaudioplayer/btaudioplayer -Source/bluetoothaudiosource/btaudiorecorder/btaudiorecorder */*/*/*/*tests */*/generated */*/*/*/lib*.a diff --git a/CMakeLists.txt b/CMakeLists.txt index a17c888d..3f53ddd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,10 +21,6 @@ project(ClientLibraries) find_package(Thunder) -option(BLUETOOTHAUDIOSINK - "Include the bluetoothaudiosink library." OFF) -option(BLUETOOTHAUDIOSOURCE - "Include the bluetoothaudiosource library." OFF) option(COMPOSITORCLIENT "Include a graphics backend abstraction for external applications." OFF) option(DEVICEINFO diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index db7d15d1..6a4ecfe7 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -15,14 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -if(BLUETOOTHAUDIOSINK) - add_subdirectory(bluetoothaudiosink) -endif() - -if(BLUETOOTHAUDIOSOURCE) - add_subdirectory(bluetoothaudiosource) -endif() - if(GRAPHICSBUFFER) add_subdirectory(graphicsbuffer) endif() diff --git a/Source/bluetoothaudiosink/BluetoothAudioSink.cpp b/Source/bluetoothaudiosink/BluetoothAudioSink.cpp deleted file mode 100644 index 523a6363..00000000 --- a/Source/bluetoothaudiosink/BluetoothAudioSink.cpp +++ /dev/null @@ -1,744 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2021 Metrological - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define __DEBUG__ // TODO: Remove this eventually - -#include "Module.h" -#include "include/bluetoothaudiosink.h" -#include - -#define CONNECTOR _T("/tmp/bluetoothaudiosink") - -#define PRINT(format, ...) fprintf(stderr, _T("bluetoothaudiosink: ") format _T("\n"), ##__VA_ARGS__) - -namespace Thunder { - -static bluetoothaudiosink_state_t ConvertState(const Exchange::IBluetoothAudio::state state) -{ - bluetoothaudiosink_state_t out_state = BLUETOOTHAUDIOSINK_STATE_UNASSIGNED; - - switch (state) { - case Exchange::IBluetoothAudio::UNASSIGNED: - out_state = BLUETOOTHAUDIOSINK_STATE_UNASSIGNED; - break; - case Exchange::IBluetoothAudio::DISCONNECTED: - out_state = BLUETOOTHAUDIOSINK_STATE_DISCONNECTED; - break; - case Exchange::IBluetoothAudio::CONNECTING: - out_state = BLUETOOTHAUDIOSINK_STATE_CONNECTING; - break; - case Exchange::IBluetoothAudio::CONNECTED: - out_state = BLUETOOTHAUDIOSINK_STATE_CONNECTED; - break; - case Exchange::IBluetoothAudio::CONNECTED_BAD: - out_state = BLUETOOTHAUDIOSINK_STATE_CONNECTED_BAD; - break; - case Exchange::IBluetoothAudio::CONNECTED_RESTRICTED: - out_state = BLUETOOTHAUDIOSINK_STATE_CONNECTED_RESTRICTED; - break; - case Exchange::IBluetoothAudio::READY: - out_state = BLUETOOTHAUDIOSINK_STATE_READY; - break; - case Exchange::IBluetoothAudio::STREAMING: - out_state = BLUETOOTHAUDIOSINK_STATE_STREAMING; - break; - default: - out_state = BLUETOOTHAUDIOSINK_STATE_UNASSIGNED; - ASSERT(!"Unmapped state enum!"); - break; - } - - return (out_state); -} - -namespace BluetoothAudioSinkClient { - - class AudioSink : protected RPC::SmartInterfaceType { - private: - static constexpr uint32_t WriteTimeout = 50; - - private: - class SendBuffer : public Core::SharedBuffer { - public: - SendBuffer() = delete; - SendBuffer(const SendBuffer&) = delete; - SendBuffer& operator=(const SendBuffer&) = delete; - - SendBuffer(const char *connector, const uint32_t bufferSize) - : Core::SharedBuffer(connector, 0777, bufferSize, 0) - , _bufferSize(bufferSize) - { - } - - ~SendBuffer() = default; - - public: - uint16_t Write(uint32_t length, const uint8_t data[]) - { - ASSERT(IsValid() == true); - ASSERT(data != nullptr); - - uint16_t result = 0; - - if (RequestProduce(WriteTimeout) == Core::ERROR_NONE) { - result = std::min(length, _bufferSize); - Size(result); - ::memcpy(Buffer(), data, result); - Produced(); - } - - return (result); - } - - private: - uint32_t _bufferSize; - }; // class SendBuffer - - private: - using OperationalStateUpdateCallbacks = std::map; - using SinkStateChangedCallbacks = std::map; - - private: - AudioSink(const string& callsign) - : SmartInterfaceType() - , _callback(*this) - , _refCount(1) - , _lock() - , _operationalStateCallbacks() - , _sinkStateCallbacks() - , _sinkLock() - , _sink(nullptr) - , _sinkControl(nullptr) - , _frameSize(0) - , _buffer() - { - TRACE_L5("Bluetooth audio sink is being constructed..."); - if (SmartInterfaceType::Open(RPC::CommunicationTimeOut, SmartInterfaceType::Connector(), callsign) != Core::ERROR_NONE) { - PRINT(_T("Failed to open the smart interface!")); - } else { - TRACE_L1("Opened smart interface ('%s')", callsign.c_str()); - } - - TRACE_L5("Bluetooth audio sink constructed"); - } - - public: - AudioSink() = delete; - AudioSink(const AudioSink&) = delete; - AudioSink& operator=(const AudioSink&) = delete; - - private: - ~AudioSink() override - { - TRACE_L5("Bluetooth audio sink is being destructed..."); - - Relinquish(); - - _sinkLock.Lock(); - - _buffer.reset(); - - _sinkLock.Unlock(); - - _lock.Lock(); - - if (_sinkControl != nullptr) { - _sinkControl->Release(); - _sinkControl = nullptr; - } - - if (_sink != nullptr) { - _sink->Callback(nullptr); - _sink->Release(); - _sink = nullptr; - } - - _lock.Unlock(); - - SmartInterfaceType::Close(Core::infinite); - - TRACE_L5("Bluetooth audio sink destructed"); - } - - private: - class Callback : public Exchange::IBluetoothAudio::ISink::ICallback { - public: - Callback() = delete; - Callback(const Callback&) = delete; - Callback& operator=(const Callback&) = delete; - - Callback(AudioSink& parent) - : _parent(parent) - { - } - - ~Callback() = default; - - public: - void StateChanged(const Exchange::IBluetoothAudio::state state) override - { - _parent.StateChanged(state); - } - - public: - BEGIN_INTERFACE_MAP(Callback) - INTERFACE_ENTRY(Exchange::IBluetoothAudio::ISink::ICallback) - END_INTERFACE_MAP - - private: - AudioSink& _parent; - }; // class Callback - - private: - void StateChanged(const Exchange::IBluetoothAudio::state state) - { - TRACE_L5("State changed to %d", state); - - _lock.Lock(); - - for (auto& cb : _sinkStateCallbacks) { - cb.first(ConvertState(state), cb.second); - } - - _lock.Unlock(); - } - - private: - void Operational(const bool upAndRunning) override - { - _lock.Lock(); - - if (upAndRunning == true) { - TRACE_L1("Bluetooth audio sink is now operational!"); - - if (_sink == nullptr) { - _sink = SmartInterfaceType::Interface(); - - ASSERT(_sink != nullptr); - - if (_sink == nullptr) { - PRINT(_T("Failed to retrieve IBluetoothAudio::ISink interface!")); - } - else if (_sink->Callback(&_callback) != Core::ERROR_NONE) { - PRINT(_T("Failed to register the sink callback!")); - } - else if (_sinkControl == nullptr) { - _sinkControl = _sink->QueryInterface(); - - ASSERT(_sinkControl != nullptr); - - if (_sinkControl == nullptr) { - PRINT(_T("Failed to retrieve IBluetoothAudio::ISink::IControl interface!")); - } - } - } - } else { - TRACE_L1("Bluetooth audio sink is no longer operational!"); - - _sinkLock.Lock(); - - _buffer.reset(); - - _sinkLock.Unlock(); - - if (_sinkControl != nullptr) { - _sinkControl->Release(); - _sinkControl = nullptr; - } - - if (_sink != nullptr) { - _sink->Release(); - _sink = nullptr; - } - } - - for (auto& cb : _operationalStateCallbacks) { - cb.first(upAndRunning, cb.second); - } - - _lock.Unlock(); - } - - public: - static AudioSink& Instance() - { - ASSERT(_instance != nullptr); - return (*_instance); - } - static void Create() - { - _instanceLock.Lock(); - if (_instance == nullptr) { - _instance = new AudioSink("BluetoothAudio"); - } else { - _instance->AddRef(); - } - _instanceLock.Unlock(); - ASSERT(_instance != nullptr); - } - static void Destroy() - { - ASSERT(_instance != nullptr); - _instanceLock.Lock(); - if (_instance->Release() == Core::ERROR_DESTRUCTION_SUCCEEDED) { - _instance = nullptr; - } - _instanceLock.Unlock(); - } - - public: - uint32_t RegisterOperationalStateUpdateCallback(const bluetoothaudiosink_operational_state_update_cb callback, void* user_data) - { - uint32_t result = Core::ERROR_ALREADY_CONNECTED; - - ASSERT(callback != nullptr); - - _lock.Lock(); - - if (_operationalStateCallbacks.find(callback) == _operationalStateCallbacks.end()) { - _operationalStateCallbacks.emplace(std::piecewise_construct, std::forward_as_tuple(callback), std::forward_as_tuple(user_data)); - result = Core::ERROR_NONE; - } - - _lock.Unlock(); - - if (result == Core::ERROR_NONE) { - // Call the callback immediately if the service is operational already. - if (SmartInterfaceType::IsOperational() == true) { - callback(true, user_data); - } - } - else { - TRACE_L1("Failed to register an operational state callback"); - } - - return (result); - } - - uint32_t UnregisterOperationalStateUpdateCallback(const bluetoothaudiosink_operational_state_update_cb callback) - { - uint32_t result = Core::ERROR_ALREADY_RELEASED; - - ASSERT(callback != nullptr); - - _lock.Lock(); - - auto it = _operationalStateCallbacks.find(callback); - - if (it != _operationalStateCallbacks.end()) { - _operationalStateCallbacks.erase(it); - result = Core::ERROR_NONE; - } - else { - TRACE_L1("Failed to unregister an operational state callback"); - } - - _lock.Unlock(); - - return (result); - } - - public: - uint32_t RegisterSinkStateChangedCallback(const bluetoothaudiosink_state_changed_cb callback, void* user_data) - { - uint32_t result = Core::ERROR_ALREADY_CONNECTED; - - ASSERT(callback != nullptr); - - Exchange::IBluetoothAudio::state state{}; - - _lock.Lock(); - - if (_sinkStateCallbacks.find(callback) == _sinkStateCallbacks.end()) { - _sinkStateCallbacks.emplace(std::piecewise_construct, std::forward_as_tuple(callback), std::forward_as_tuple(user_data)); - - result = _sink->State(state); - } - else { - TRACE_L1("Failed to register a sink state callback"); - } - - if ((result == Core::ERROR_NONE) && (state != Exchange::IBluetoothAudio::UNASSIGNED)) { - callback(ConvertState(state), user_data); - } - - _lock.Unlock(); - - return (result); - } - - uint32_t UnregisterSinkStateChangedCallback(const bluetoothaudiosink_state_changed_cb callback) - { - uint32_t result = Core::ERROR_ALREADY_RELEASED; - - ASSERT(callback != nullptr); - - _lock.Lock(); - - auto it = _sinkStateCallbacks.find(callback); - - if (it != _sinkStateCallbacks.end()) { - _sinkStateCallbacks.erase(it); - result = Core::ERROR_NONE; - } - else { - TRACE_L1("Failed to unregister a sink state callback"); - } - - _lock.Unlock(); - - return (result); - } - - public: - uint32_t State(Exchange::IBluetoothAudio::state& state) const - { - uint32_t result = Core::ERROR_ILLEGAL_STATE; - - _sinkLock.Lock(); - - if (_sink != nullptr) { - result = _sink->State(state); - - if (result != Core::ERROR_NONE) { - TRACE_L1("ISink::State() failed! [%i]", result); - } - } - - _sinkLock.Unlock(); - - return (result); - } - uint32_t Configure(const Exchange::IBluetoothAudio::IStream::Format& format) - { - uint32_t result = Core::ERROR_ILLEGAL_STATE; - - _sinkLock.Lock(); - - if (_sinkControl != nullptr) { - _frameSize = (100UL * format.Resolution * format.Channels * format.SampleRate) / (format.FrameRate * 8); - - TRACE_L1("Configuring sink with settings: %i Hz, %i channels, %i bits per sample, %i.%i fps, frame size is %i bytes", - format.SampleRate, format.Channels, format.Resolution, format.FrameRate/100, format.FrameRate%100, _frameSize); - - result = _sinkControl->Configure(format); - - if (result != Core::ERROR_NONE) { - TRACE_L1("IStream::Configure() failed! [%i]", result); - } - } - - _sinkLock.Unlock(); - - return (result); - } - uint32_t Acquire() - { - uint32_t result = Core::ERROR_ILLEGAL_STATE; - - _sinkLock.Lock(); - - if ((_sinkControl != nullptr) && (_frameSize != 0)) { - - TRACE_L1("Acquiring sink with connector '%s'", CONNECTOR); - - _buffer.reset(new SendBuffer(CONNECTOR, _frameSize)); - ASSERT(_buffer.get() != nullptr); - - if (_buffer->IsValid() == true) { - result = _sinkControl->Acquire(CONNECTOR); - - if (result != Core::ERROR_NONE) { - TRACE_L1("IStream::Acquire() failed! [%i]", result); - _buffer.reset(); - } - } - else { - TRACE_L1("Failed to open buffer!"); - result = Core::ERROR_OPENING_FAILED; - } - } - - _sinkLock.Unlock(); - - return (result); - } - uint32_t Relinquish() - { - uint32_t result = Core::ERROR_ILLEGAL_STATE; - - _sinkLock.Lock(); - - if (_sinkControl != nullptr) { - - result = _sinkControl->Relinquish(); - if (result != Core::ERROR_NONE) { - TRACE_L1("IStream::Relinquish failed! [%i]", result); - } - else { - TRACE_L1("Client released the audio sink"); - } - - _buffer.reset(); - - _frameSize = 0; - } - - _sinkLock.Unlock(); - - return (result); - } - - uint32_t Speed(const int8_t speed) - { - uint32_t result = Core::ERROR_ILLEGAL_STATE; - - _sinkLock.Lock(); - - if (_sinkControl != nullptr) { - result = _sinkControl->Speed(speed); - - if (result != Core::ERROR_NONE) { - TRACE_L1("IStream::Speed(%i) failed [%i]", speed, result); - } - } - - _sinkLock.Unlock(); - - return (result); - } - - uint32_t Frame(const uint16_t length, const uint8_t data[], uint16_t& consumed) - { - uint32_t result = Core::ERROR_NONE; - - _sinkLock.Lock(); - - if (_buffer != nullptr) { - consumed = _buffer->Write(length, data); - } - else { - result = Core::ERROR_ILLEGAL_STATE; - } - - _sinkLock.Unlock(); - - return (result); - } - - uint32_t Time(uint32_t& timeMs) - { - uint32_t result = Core::ERROR_ILLEGAL_STATE; - - _sinkLock.Lock(); - - if ((_sinkControl != nullptr) && (_buffer != nullptr)) { - result = _sinkControl->Time(timeMs); - - if (result != Core::ERROR_NONE) { - TRACE_L1("IStream::Time() failed! [%i]", result); - } - } - - _sinkLock.Unlock(); - - return (result); - } - - uint32_t Delay(uint32_t& delaySamples) - { - uint32_t result = Core::ERROR_ILLEGAL_STATE; - - _sinkLock.Lock(); - - if (_sinkControl != nullptr) { - result = _sinkControl->Delay(delaySamples); - - if (result != Core::ERROR_NONE) { - TRACE_L1("IStream::Delay() failed! [%i]", result); - } - } - - _sinkLock.Unlock(); - - return (result); - } - - uint32_t AddRef() const - { - Core::InterlockedIncrement(_refCount); - return Core::ERROR_NONE; - } - - uint32_t Release() const - { - uint32_t result = Core::ERROR_NONE; - if (Core::InterlockedDecrement(_refCount) == 0) { - delete this; - result = Core::ERROR_DESTRUCTION_SUCCEEDED; - } - return result; - } - - private: - static AudioSink* _instance; - static Core::CriticalSection _instanceLock; - - Core::SinkType _callback; - - mutable uint32_t _refCount; - mutable Core::CriticalSection _lock; - OperationalStateUpdateCallbacks _operationalStateCallbacks; - SinkStateChangedCallbacks _sinkStateCallbacks; - - mutable Core::CriticalSection _sinkLock; - Exchange::IBluetoothAudio::ISink* _sink; - Exchange::IBluetoothAudio::IStream* _sinkControl; - uint32_t _frameSize; - std::unique_ptr _buffer; - }; - - AudioSink* AudioSink::_instance = nullptr; - Core::CriticalSection AudioSink::_instanceLock; - -} // namespace BluetoothAudioSinkClient - -} - - -using namespace Thunder; - -extern "C" { - -uint32_t bluetoothaudiosink_register_operational_state_update_callback(const bluetoothaudiosink_operational_state_update_cb callback, void *user_data) -{ - if (callback == nullptr) { - return (Core::ERROR_BAD_REQUEST); - } else { - return (BluetoothAudioSinkClient::AudioSink::Instance().RegisterOperationalStateUpdateCallback(callback, user_data)); - } -} - -uint32_t bluetoothaudiosink_unregister_operational_state_update_callback(const bluetoothaudiosink_operational_state_update_cb callback) -{ - if (callback == nullptr) { - return (Core::ERROR_BAD_REQUEST); - } else { - return (BluetoothAudioSinkClient::AudioSink::Instance().UnregisterOperationalStateUpdateCallback(callback)); - } -} - -uint32_t bluetoothaudiosink_register_state_changed_callback(const bluetoothaudiosink_state_changed_cb callback, void *user_data) -{ - if (callback == nullptr) { - return (Core::ERROR_BAD_REQUEST); - } else { - return (BluetoothAudioSinkClient::AudioSink::Instance().RegisterSinkStateChangedCallback(callback, user_data)); - } -} - -uint32_t bluetoothaudiosink_unregister_state_changed_callback(const bluetoothaudiosink_state_changed_cb callback) -{ - if (callback == nullptr) { - return (Core::ERROR_BAD_REQUEST); - } else { - return (BluetoothAudioSinkClient::AudioSink::Instance().UnregisterSinkStateChangedCallback(callback)); - } -} - -uint32_t bluetoothaudiosink_state(bluetoothaudiosink_state_t* out_state) -{ - if (out_state == nullptr) { - return (Core::ERROR_BAD_REQUEST); - } else { - Exchange::IBluetoothAudio::state state{}; - - uint32_t result = BluetoothAudioSinkClient::AudioSink::Instance().State(state); - if (result == Core::ERROR_NONE) { - (*out_state) = ConvertState(state); - } - - return (result); - } -} - -uint32_t bluetoothaudiosink_configure(const bluetoothaudiosink_format_t *format) -{ - if (format == nullptr) { - return (Core::ERROR_BAD_REQUEST); - } else { - return (BluetoothAudioSinkClient::AudioSink::Instance().Configure({format->sample_rate, format->frame_rate, format->resolution, format->channels})); - } -} - -uint32_t bluetoothaudiosink_acquire() -{ - return (BluetoothAudioSinkClient::AudioSink::Instance().Acquire()); -} - -uint32_t bluetoothaudiosink_relinquish() -{ - return (BluetoothAudioSinkClient::AudioSink::Instance().Relinquish()); -} - -uint32_t bluetoothaudiosink_speed(const int8_t speed) -{ - return (BluetoothAudioSinkClient::AudioSink::Instance().Speed(speed)); -} - -uint32_t bluetoothaudiosink_time(uint32_t* out_time_ms) -{ - if (out_time_ms == nullptr) { - return (Core::ERROR_BAD_REQUEST); - } else { - return (BluetoothAudioSinkClient::AudioSink::Instance().Time(*out_time_ms)); - } -} - -uint32_t bluetoothaudiosink_delay(uint32_t* out_delay_samples) -{ - if (out_delay_samples == nullptr) { - return (Core::ERROR_BAD_REQUEST); - } else { - return (BluetoothAudioSinkClient::AudioSink::Instance().Delay(*out_delay_samples)); - } -} - -uint32_t bluetoothaudiosink_frame(const uint16_t length, const uint8_t data[], uint16_t* out_consumed) -{ - if ((data == nullptr) || (out_consumed == nullptr)) { - return (Core::ERROR_BAD_REQUEST); - } else { - return (BluetoothAudioSinkClient::AudioSink::Instance().Frame(length, data, *out_consumed)); - } -} - -uint32_t bluetoothaudiosink_init() -{ - BluetoothAudioSinkClient::AudioSink::Create(); - return (Core::ERROR_NONE); -} - -uint32_t bluetoothaudiosink_deinit() -{ - BluetoothAudioSinkClient::AudioSink::Destroy(); - return (Core::ERROR_NONE); -} - -} // extern "C" diff --git a/Source/bluetoothaudiosink/CMakeLists.txt b/Source/bluetoothaudiosink/CMakeLists.txt deleted file mode 100644 index ae0d2802..00000000 --- a/Source/bluetoothaudiosink/CMakeLists.txt +++ /dev/null @@ -1,86 +0,0 @@ - # If not stated otherwise in this file or this component's LICENSE file the -# following copyright and licenses apply: -# -# Copyright 2021 Metrological -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -cmake_minimum_required(VERSION 3.15) - -find_package(Thunder) - -project(BluetoothAudioSink) - -project_version(1.0.0) - -set(TARGET Client${PROJECT_NAME}) - -message("Setup ${TARGET} v${PROJECT_VERSION}") - -find_package(${NAMESPACE}Core REQUIRED) -find_package(${NAMESPACE}COM REQUIRED) -find_package(CompileSettingsDebug CONFIG REQUIRED) - -option(BLUETOOTHAUDIOSINK_EXAMPLEPLAYER "Build audio player" OFF) -set(PUBLIC_HEADERS "include/bluetoothaudiosink.h") - -add_library(${TARGET} - Module.cpp - BluetoothAudioSink.cpp -) - -target_link_libraries(${TARGET} - PRIVATE - ${NAMESPACE}Core::${NAMESPACE}Core - ${NAMESPACE}COM::${NAMESPACE}COM - CompileSettingsDebug::CompileSettingsDebug -) - -set_target_properties(${TARGET} PROPERTIES - CXX_STANDARD ${CXX_STD} - CXX_STANDARD_REQUIRED YES - FRAMEWORK FALSE - PUBLIC_HEADER "${PUBLIC_HEADERS}" - VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR} -) - -target_include_directories( ${TARGET} - PUBLIC - $ - $ - $ -) - -install( - TARGETS ${TARGET} EXPORT ${TARGET}Targets - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Development - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Runtime NAMELINK_COMPONENT ${NAMESPACE}_Development - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${NAMESPACE}_Runtime - FRAMEWORK DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${NAMESPACE}_Runtime - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${NAMESPACE}/bluetoothaudiosink COMPONENT ${NAMESPACE}_Development - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${NAMESPACE}/bluetoothaudiosink -) - -InstallCMakeConfig( - TARGETS ${TARGET} -) - -InstallPackageConfig( - TARGETS ${TARGET} - DESCRIPTION "Bluetooth audio playback abstraction" -) - -if(BLUETOOTHAUDIOSINK_EXAMPLEPLAYER) - add_subdirectory(btaudioplayer) -endif() diff --git a/Source/bluetoothaudiosink/Module.cpp b/Source/bluetoothaudiosink/Module.cpp deleted file mode 100644 index 6b042775..00000000 --- a/Source/bluetoothaudiosink/Module.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2021 Metrological - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Module.h" - -MODULE_NAME_DECLARATION(BUILD_REFERENCE) - diff --git a/Source/bluetoothaudiosink/Module.h b/Source/bluetoothaudiosink/Module.h deleted file mode 100644 index 5037542d..00000000 --- a/Source/bluetoothaudiosink/Module.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2021 Metrological - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#ifndef MODULE_NAME -#define MODULE_NAME ClientLibrary_BluetoothAudioSink -#endif - -#include -#include -#include -#include diff --git a/Source/bluetoothaudiosink/btaudioplayer/CMakeLists.txt b/Source/bluetoothaudiosink/btaudioplayer/CMakeLists.txt deleted file mode 100644 index bc62b448..00000000 --- a/Source/bluetoothaudiosink/btaudioplayer/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -# If not stated otherwise in this file or this component's LICENSE file the -# following copyright and licenses apply: -# -# Copyright 2021 Metrological -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -project(btaudioplayer) - -set(TARGET ${PROJECT_NAME}) - -cmake_minimum_required(VERSION 3.15) - -add_executable(${PROJECT_NAME} - main.c -) - -target_include_directories(btaudioplayer - PRIVATE - $ - $ -) - -target_link_libraries(${TARGET} - PUBLIC - ClientBluetoothAudioSink::ClientBluetoothAudioSink - pthread -) - -install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${NAMESPACE}_Runtime) diff --git a/Source/bluetoothaudiosink/btaudioplayer/main.c b/Source/bluetoothaudiosink/btaudioplayer/main.c deleted file mode 100644 index c8630e1e..00000000 --- a/Source/bluetoothaudiosink/btaudioplayer/main.c +++ /dev/null @@ -1,368 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2021 Metrological - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../include/bluetoothaudiosink.h" - -#define CDDA_FRAMERATE (7500 /* cHz */) - -#define PRINT(format, ...) printf(format "\n", ##__VA_ARGS__) -#define PRINT_NO_CR(format, ...) printf(format, ##__VA_ARGS__) -#define TRACE(format, ...) fprintf(stderr, "btaudioplayer: " format "\n", ##__VA_ARGS__) -#define ERROR(format, ...) fprintf(stderr, "btaudioplayer: [\033[31merror\033[0m]: " format "\n", ##__VA_ARGS__) - - -typedef struct { - const char* file; - volatile bool connected; - volatile bool playing; - volatile bool release; - volatile bool exit; - bluetoothaudiosink_format_t format; - pthread_t thread; - sem_t connect_sync; - sem_t playback_sync; -} context_t; - -typedef struct { - char riff[4]; - uint32_t riff_size; - char wave[4]; - char fmt[4]; - uint32_t fmt_size; - uint16_t type; - uint16_t channels; - uint32_t sample_rate; - uint32_t pad1; - uint16_t pad2; - uint16_t resolution; - char data[4]; - uint32_t data_size; -} __attribute__((packed)) wav_header_t; - -static volatile bool user_break = false; - - -static void* playback_task(void *user_data) -{ - context_t *context = (context_t*)user_data; - assert(context); - - TRACE("WAV streaming thread started"); - - if (bluetoothaudiosink_configure(&context->format) != BLUETOOTHAUDIOSINK_SUCCESS) { - TRACE("Failed to configure Bluetooth audio sink device!"); - } - else if (bluetoothaudiosink_acquire() != BLUETOOTHAUDIOSINK_SUCCESS) { - TRACE("Failed to acquire Bluetooth audio sink device!"); - } else { - TRACE("Starting a playback session..."); - context->release = true; - - if (bluetoothaudiosink_speed(100) == 0) { - FILE *f = fopen(context->file, "rb+"); - if (f != NULL) { - - const uint16_t bufferSize = ((100UL * context->format.channels * context->format.resolution * (context->format.sample_rate / context->format.frame_rate)) / 8); - uint8_t *data = alloca(bufferSize); - uint16_t to_play = 0; - - TRACE("Opened file '%s' (read buffer size: %i bytes)", context->file, bufferSize); - - fseek(f, sizeof(wav_header_t), SEEK_SET); - - while (context->exit == false) { - sem_wait(&context->playback_sync); - - TRACE("Unpaused..."); - - while (context->playing == true) { - uint16_t played = 0; - - if (to_play == 0) { - to_play = fread(data, 1, bufferSize, f); - } - - if (bluetoothaudiosink_frame(to_play, data, &played) != 0) { - TRACE("Failed to send audio frame!"); - context->exit = true; - break; - } - - if (to_play != bufferSize) { - TRACE("EOF reached"); - context->exit = true; - break; - } - - if (user_break == true) { - TRACE("User break!"); - context->exit = true; - break; - } - - if (played < to_play) { - // we're sending too fast... - usleep(1 * 1000); - } - - to_play -= played; - } - - TRACE("Paused..."); - } - - context->playing = false; - - fclose(f); - TRACE("Closed file '%s'", context->file); - } else { - TRACE("Failed to open file '%s'", context->file); - } - - if (context->release) { - if (bluetoothaudiosink_speed(0) != 0) { - TRACE("Failed to set audio speed 0%%!"); - } - - if (bluetoothaudiosink_relinquish() != 0) { - TRACE("Failed to reqlinquish bluetooth audio sink device!"); - } - } - } else { - TRACE("Failed to set audio speed 100%%!"); - } - } - - TRACE("File streaming thread terminated!"); - - return (NULL); -} - -static void audio_sink_disconnected(context_t *context) -{ - assert(context); - - if (context->connected) { - TRACE("Remote device closed. Stopping playback!"); - context->exit = true; - context->release = false; - context->playing = false; - sem_post(&context->playback_sync); - } -} - -static void audio_sink_connected(context_t * context) -{ - assert(context); - - if (!context->playing) { - context->connected = true; - sem_post(&context->connect_sync); - } else { - context->playing = false; - } -} - -static void audio_sink_playing(context_t * context, const bool enable) -{ - assert(context); - - if (enable) { - context->playing = true; - sem_post(&context->playback_sync); - } - else { - context->playing = false; - } -} - -static void audio_sink_state_changed(const bluetoothaudiosink_state_t state, void *user_data) -{ - switch (state) { - case BLUETOOTHAUDIOSINK_STATE_UNASSIGNED: - TRACE("State: BLUETOOTHAUDIOSINK_STATE_UNASSIGNED"); - break; - case BLUETOOTHAUDIOSINK_STATE_CONNECTING: - TRACE("State: BLUETOOTHAUDIOSINK_STATE_CONNECTING"); - break; - case BLUETOOTHAUDIOSINK_STATE_CONNECTED: - TRACE("State: BLUETOOTHAUDIOSINK_STATE_CONNECTED"); - audio_sink_connected((context_t*)user_data); - break; - case BLUETOOTHAUDIOSINK_STATE_CONNECTED_BAD: - TRACE("State: BLUETOOTHAUDIOSINK_STATE_CONNECTED_BAD"); - break; - case BLUETOOTHAUDIOSINK_STATE_CONNECTED_RESTRICTED: - TRACE("State: BLUETOOTHAUDIOSINK_STATE_CONNECTED_RESTRICTED"); - break; - case BLUETOOTHAUDIOSINK_STATE_DISCONNECTED: - TRACE("State: BLUETOOTHAUDIOSINK_STATE_DISCONNECTED"); - audio_sink_disconnected((context_t*)user_data); - break; - case BLUETOOTHAUDIOSINK_STATE_READY: - TRACE("State: BLUETOOTHAUDIOSINK_STATE_READY"); - audio_sink_playing((context_t*)user_data, false); - break; - case BLUETOOTHAUDIOSINK_STATE_STREAMING: - TRACE("State: BLUETOOTHAUDIOSINK_STATE_STREAMING"); - audio_sink_playing((context_t*)user_data, true); - break; - default: - assert(!"Unmapped enum"); - break; - } -} - -static void audio_sink_operational_state_update(const uint8_t running, void *user_data) -{ - if (running) { - // Register for the sink updates - if (bluetoothaudiosink_register_state_changed_callback(audio_sink_state_changed, user_data) != 0) { - ERROR("Failed to register sink sink update callback!"); - } - } else { - TRACE("Bluetooth Audio Sink service is now unavailable"); - audio_sink_disconnected((context_t*)user_data); - } -} - -void ctrl_c_handler(int signal) -{ - // Ctrl+C! - (void) signal; - user_break = true; -} - - -int main(int argc, const char* argv[]) -{ - int result = 1; - - PRINT("Plays a .wav file over a Bluetooth speaker device"); - - if (argc == 2) { - FILE *f; - context_t context; - memset(&context, 0, sizeof(context)); - - context.file = argv[1]; - context.playing = false; - - f = fopen(context.file, "rb+"); - if (f != NULL) { - wav_header_t header = { 0 }; - - if (fread(&header, 1, sizeof(header), f) == sizeof(header)) { - if (memcmp(header.wave, "WAVE", 4) == 0) { - context.format.sample_rate = le32toh(header.sample_rate); - context.format.frame_rate = CDDA_FRAMERATE; - context.format.channels = (uint8_t) le16toh(header.channels); - context.format.resolution = (uint8_t) le16toh(header.resolution); - - TRACE("Input format: PCM %i Hz, %i bit (signed, little endian), %i channels @ %i.%02i fps", - context.format.sample_rate, context.format.resolution, context.format.channels, - (context.format.frame_rate/100), (context.format.frame_rate%100)); - } - } - - fclose(f); - f = NULL; - - if (((context.format.sample_rate >= 8000) && (context.format.channels == 1)) || ((context.format.channels == 2) && (context.format.resolution == 16))) { - TRACE("Waiting for Bluetooth audio sink device to be available... (Press Ctrl+C to quit at any time.)"); - - setbuf(stdout, NULL); - - sem_init(&context.connect_sync, 0, 0); - sem_init(&context.playback_sync, 0, 0); - - // Register for the audio sink service updates - if (bluetoothaudiosink_register_operational_state_update_callback(&audio_sink_operational_state_update, &context) != BLUETOOTHAUDIOSINK_SUCCESS) { - ERROR("Failed to register Bluetooths Audio Sink operational callback"); - } else { - const uint32_t timeout = 120 /* sec */; - struct timespec ts; - - // Install a Ctrl+C handler - struct sigaction handler; - handler.sa_handler = ctrl_c_handler; - sigemptyset(&handler.sa_mask); - handler.sa_flags = 0; - sigaction(SIGINT, &handler, NULL); - - clock_gettime(CLOCK_REALTIME, &ts); - ts.tv_sec += timeout; - - bluetoothaudiosink_init(); - - // Wait for connection... - if (sem_timedwait(&context.connect_sync, &ts) != -1) { - - pthread_create(&context.thread, NULL, playback_task, (void*)&context); - - while (context.exit == false) { - uint32_t time; - - if (bluetoothaudiosink_time(&time) == BLUETOOTHAUDIOSINK_SUCCESS) { - printf("playing %d:%02d.%03d\r", time/60000, (time/1000)%60, time%1000); - } - - usleep(10 * 1000); - } - - TRACE("Exiting..."); - - pthread_join(context.thread, NULL); - - } else { - ERROR("Bluetooth audio sink device not connected in %d seconds, terminating!", timeout); - } - - // And we're done... - TRACE("Cleaning up..."); - bluetoothaudiosink_unregister_state_changed_callback(&audio_sink_state_changed); // this can fail, it's OK - bluetoothaudiosink_unregister_operational_state_update_callback(&audio_sink_operational_state_update); - bluetoothaudiosink_deinit(); - } - } else { - ERROR("Invalid file format!"); - } - } else { - ERROR("Failed to open the source file!"); - } - } else { - PRINT("arguments:\n%s ", argv[0]); - } - - return (result); -} diff --git a/Source/bluetoothaudiosink/btaudioplayer/readme.txt b/Source/bluetoothaudiosink/btaudioplayer/readme.txt deleted file mode 100644 index d30570dd..00000000 --- a/Source/bluetoothaudiosink/btaudioplayer/readme.txt +++ /dev/null @@ -1,10 +0,0 @@ -This tool plays a .wav file over a Bluetooth audio sink device. It requires the -BluetoothAudio plugin to be available. If a Bluetooth audio device is not -already connected when the program is started, it will allow 2 minutes for the -Bluetooth audio device to connect. - -Usage: - btaudioplayer /root/test.wav - -Ctrl+C to close the playback session and quit. - diff --git a/Source/bluetoothaudiosink/include/bluetoothaudiosink.h b/Source/bluetoothaudiosink/include/bluetoothaudiosink.h deleted file mode 100644 index 8e9033b6..00000000 --- a/Source/bluetoothaudiosink/include/bluetoothaudiosink.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2021 Metrological - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef BLUETOOTHAUDIOSINK_H -#define BLUETOOTHAUDIOSINK_H - -#include - -#undef EXTERNAL -#define EXTERNAL __attribute__((visibility("default"))) - -#ifdef __cplusplus -extern "C" { -#endif - -#define BLUETOOTHAUDIOSINK_SUCCESS (0) - -typedef struct { - uint32_t sample_rate; - uint16_t frame_rate; - uint8_t resolution; - uint8_t channels; -} bluetoothaudiosink_format_t; - -typedef enum { - BLUETOOTHAUDIOSINK_STATE_UNASSIGNED = 0, - BLUETOOTHAUDIOSINK_STATE_DISCONNECTED, - BLUETOOTHAUDIOSINK_STATE_CONNECTING, - BLUETOOTHAUDIOSINK_STATE_CONNECTED, - BLUETOOTHAUDIOSINK_STATE_CONNECTED_BAD, - BLUETOOTHAUDIOSINK_STATE_CONNECTED_RESTRICTED, - BLUETOOTHAUDIOSINK_STATE_READY, - BLUETOOTHAUDIOSINK_STATE_STREAMING -} bluetoothaudiosink_state_t; - -typedef void (*bluetoothaudiosink_operational_state_update_cb)(const uint8_t running /* 0 == not running */, void *user_data); -typedef void (*bluetoothaudiosink_state_changed_cb)(const bluetoothaudiosink_state_t state, void *user_data); - - -EXTERNAL uint32_t bluetoothaudiosink_register_operational_state_update_callback(const bluetoothaudiosink_operational_state_update_cb callback, void *user_data); -EXTERNAL uint32_t bluetoothaudiosink_unregister_operational_state_update_callback(const bluetoothaudiosink_operational_state_update_cb callback); - -EXTERNAL uint32_t bluetoothaudiosink_register_state_changed_callback(const bluetoothaudiosink_state_changed_cb callback, void *user_data); -EXTERNAL uint32_t bluetoothaudiosink_unregister_state_changed_callback(const bluetoothaudiosink_state_changed_cb callback); - -EXTERNAL uint32_t bluetoothaudiosink_state(bluetoothaudiosink_state_t *state); - -EXTERNAL uint32_t bluetoothaudiosink_configure(const bluetoothaudiosink_format_t *format); -EXTERNAL uint32_t bluetoothaudiosink_acquire(void); -EXTERNAL uint32_t bluetoothaudiosink_relinquish(void); -EXTERNAL uint32_t bluetoothaudiosink_speed(const int8_t speed); -EXTERNAL uint32_t bluetoothaudiosink_time(uint32_t *out_time_ms); -EXTERNAL uint32_t bluetoothaudiosink_delay(uint32_t *out_delay_samples); -EXTERNAL uint32_t bluetoothaudiosink_frame(const uint16_t length, const uint8_t data[], uint16_t *consumed); - -EXTERNAL uint32_t bluetoothaudiosink_init(void); -EXTERNAL uint32_t bluetoothaudiosink_deinit(void); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // BLUETOOTHAUDIOSINK_H diff --git a/Source/bluetoothaudiosource/BluetoothAudioSource.cpp b/Source/bluetoothaudiosource/BluetoothAudioSource.cpp deleted file mode 100644 index 943522ab..00000000 --- a/Source/bluetoothaudiosource/BluetoothAudioSource.cpp +++ /dev/null @@ -1,1066 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Metrological - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -#include "Module.h" -#include -#include "include/bluetoothaudiosource.h" -#include - -namespace Thunder { - -namespace BluetoothAudioSourceClient { - - class AudioSource : protected RPC::SmartInterfaceType { - private: - static constexpr uint32_t WriteTimeout = 50; - - class Receiver : public Core::Thread { - private: - class ReceiveBuffer : public Core::SharedBuffer { - public: - ReceiveBuffer() = delete; - ReceiveBuffer(const ReceiveBuffer&) = delete; - ReceiveBuffer& operator=(const ReceiveBuffer&) = delete; - ~ReceiveBuffer() = default; - - ReceiveBuffer(const string& name) - : Core::SharedBuffer(name.c_str()) - { - } - - public: - uint32_t Get(const uint32_t length, uint8_t buffer[]) - { - uint32_t result = 0; - - ASSERT(IsValid() == true); - ASSERT(Buffer() != nullptr); - - if (RequestConsume(100) == Core::ERROR_NONE) { - ASSERT(BytesWritten() <= length); - - const uint32_t size = std::min(BytesWritten(), length); - ::memcpy(buffer, Buffer(), size); - result = size; - - Consumed(); - } - - return (result); - } - }; // class ReceiveBuffer - - public: - Receiver() = delete; - Receiver(const Receiver&) = delete; - Receiver& operator=(const Receiver&) = delete; - - Receiver(AudioSource& parent, const string& connector) - : _parent(parent) - , _receiveBuffer(connector) - , _maxFrameSize(0) - , _buffer() - , _bufferSize(0) - { - TRACE_L1("Receive buffer at '%s'", connector.c_str()); - - _maxFrameSize = _receiveBuffer.Size(); - _bufferSize = _maxFrameSize; - - _buffer.reset(new (std::nothrow) uint8_t[_bufferSize]); - ASSERT(_buffer.get() != nullptr); - - Block(); - Stop(); - } - - ~Receiver() - { - Stop(); - } - - public: - void Start() - { - _running = true; - Thread::Run(); - } - void Stop() - { - _running = false; - Thread::Wait(BLOCKED, Core::infinite); - } - - public: - bool IsValid() const { - return (_receiveBuffer.IsValid()); - } - - private: - uint32_t Worker() - { - uint32_t delay = 0; - - const uint16_t bytesRead = _receiveBuffer.Get(_maxFrameSize, _buffer.get()); - ASSERT(bytesRead < _maxFrameSize); - - if (bytesRead != 0) { - _parent.OnFrameReceived(_buffer.get(), bytesRead); - } - - if (_running == false) { - Thread::Block(); - delay = Core::infinite; - } - - return (delay); - } - - private: - AudioSource& _parent; - ReceiveBuffer _receiveBuffer; - uint16_t _maxFrameSize; - std::unique_ptr _buffer; - uint32_t _bufferSize; - bool _running; - }; // class Receiver - - private: - using OperationalStateUpdateCallbacks = std::map; - using SourceStateChangedCallbacks = std::map; - - private: - AudioSource(const string& callsign) - : SmartInterfaceType() - , _callsign(callsign) - , _callback(*this) - , _sinkStream(*this) - , _lock() - , _source(nullptr) - , _sourceControl(nullptr) - , _sourceStream(nullptr) - , _sinkLock() - , _operationalStateCallbacks() - , _sinkStateCallbacks() - , _sinkCallbacks(nullptr) - , _sinkCallbacksUserData(nullptr) - , _receiver() - { - TRACE_L1("Constructing Bluetooth Audio Source client library..."); - - ASSERT(_singleton == nullptr); - _singleton = this; - - if (SmartInterfaceType::Open(RPC::CommunicationTimeOut, SmartInterfaceType::Connector(), callsign) != Core::ERROR_NONE) { - TRACE_L1("Failed to open the smart interface!"); - } - else { - TRACE_L1("Opened smart interface ('%s')", callsign.c_str()); - } - - TRACE_L1("Bluetooth Audio Source client library constructed"); - } - - public: - AudioSource() = delete; - AudioSource(const AudioSource&) = delete; - AudioSource& operator=(const AudioSource&) = delete; - - private: - ~AudioSource() override - { - TRACE_L1("Destructing Bluetooth Audio Source client library..."); - - _lock.Lock(); - - Relinquish(); - - Cleanup(); - - _lock.Unlock(); - - SmartInterfaceType::Close(Core::infinite); - - ASSERT(_singleton != nullptr); - _singleton = nullptr; - - TRACE_L1("Bluetooth Audio Source client library destructed"); - } - - void Cleanup(const bool terminated = false) - { - if (_sourceStream != nullptr) { - _sourceStream->Release(); - _sourceStream = nullptr; - } - - if (_sourceControl != nullptr) { - - if (terminated == FALSE) { - _sourceControl->Sink(nullptr); - } - - _sourceControl->Release(); - _sourceControl = nullptr; - } - - if (_source != nullptr) { - - if (terminated == FALSE) { - _source->Callback(nullptr); - } - - _source->Release(); - _source = nullptr; - } - } - - private: - class Callback : public Exchange::IBluetoothAudio::ISource::ICallback { - public: - Callback() = delete; - Callback(const Callback&) = delete; - Callback& operator=(const Callback&) = delete; - ~Callback() = default; - - Callback(AudioSource& parent) - : _parent(parent) - { - } - - public: - void StateChanged(const Exchange::IBluetoothAudio::state state) override - { - _parent.StateChanged(state); - } - - public: - BEGIN_INTERFACE_MAP(Callback) - INTERFACE_ENTRY(Exchange::IBluetoothAudio::ISource::ICallback) - END_INTERFACE_MAP - - private: - AudioSource& _parent; - }; // class Callback - - private: - class SinkStream : public Exchange::IBluetoothAudio::IStream { - // Calls from source device to sink component. - public: - SinkStream() = delete; - SinkStream(const SinkStream&) = delete; - SinkStream& operator=(const SinkStream&) = delete; - ~SinkStream() = default; - - SinkStream(AudioSource& parent) - : _parent(parent) - { - } - - public: - Core::hresult Configure(const Exchange::IBluetoothAudio::IStream::Format& format) override - { - return (_parent.OnConfigure(format)); - } - Core::hresult Acquire(const string& connector) override - { - return (_parent.OnAcquire(connector)); - } - Core::hresult Relinquish() override - { - return (_parent.OnRelinquish()); - } - Core::hresult Speed(const int8_t speed) override - { - return (_parent.OnSetSpeed(speed)); - } - Core::hresult Time(uint32_t& timeMs) const override - { - return (_parent.OnGetTime(timeMs)); - } - Core::hresult Delay(uint32_t& delaySamples) const override - { - return (_parent.OnGetDelay(delaySamples)); - } - - public: - BEGIN_INTERFACE_MAP(SinkStream) - INTERFACE_ENTRY(Exchange::IBluetoothAudio::IStream) - END_INTERFACE_MAP - - private: - AudioSource& _parent; - }; // class SinkStream - - private: - void StateChanged(const Exchange::IBluetoothAudio::state state) - { - _sinkLock.Lock(); - - for (auto& cb : _sinkStateCallbacks) { - cb.first(ConvertState(state), cb.second); - } - - _sinkLock.Unlock(); - } - - private: - Core::hresult OnConfigure(const Exchange::IBluetoothAudio::IStream::Format& format) - { - Core::hresult result = Core::ERROR_UNAVAILABLE; - - TRACE_L1("Remote source is configuring sink..."); - - _sinkLock.Lock(); - - if (_sinkCallbacks == nullptr) { - TRACE_L1("No sink available!"); - } - else { - ASSERT(_sinkCallbacks->configure_cb != nullptr); - - bluetoothaudiosource_format_t cFormat; - cFormat.sample_rate = format.SampleRate; - cFormat.frame_rate = format.FrameRate; - cFormat.resolution = format.Resolution; - cFormat.channels = format.Channels; - - const uint32_t sinkResult = _sinkCallbacks->configure_cb(&cFormat, _sinkCallbacksUserData); - - if (sinkResult != BLUETOOTHAUDIOSOURCE_SUCCESS) { - TRACE_L1("Sink configure_cb() failed!"); - result = Core::ERROR_GENERAL; - } - else { - result = Core::ERROR_NONE; - TRACE_L1("Sink configured (%d Hz, %d bits, %d channels, %d.%02d fps)", - cFormat.sample_rate, cFormat.resolution, cFormat.channels, (cFormat.frame_rate / 100), (cFormat.frame_rate % 100)); - } - } - - _sinkLock.Unlock(); - - return (result); - - } - Core::hresult OnAcquire(const string& connector) - { - Core::hresult result = Core::ERROR_ILLEGAL_STATE; - - TRACE_L1("Remote source is acquiring sink..."); - - _sinkLock.Lock(); - - if (_receiver != nullptr) { - TRACE_L1("Sink already acquired!"); - } - else { - if (_sinkCallbacks == nullptr) { - TRACE_L1("No sink available!"); - result = Core::ERROR_UNAVAILABLE; - } - else { - ASSERT(_sinkCallbacks->acquire_cb != nullptr); - - const uint32_t sinkResult = _sinkCallbacks->acquire_cb(_sinkCallbacksUserData); - - if (sinkResult != BLUETOOTHAUDIOSOURCE_SUCCESS) { - TRACE_L1("Sink acquire_cb() failed!"); - result = Core::ERROR_GENERAL; - } - else { - _receiver.reset(new Receiver(*this, connector)); - ASSERT(_receiver.get() != nullptr); - - if (_receiver->IsValid() != true) { - _receiver.reset(); - - ASSERT(_sinkCallbacks->relinquish_cb != nullptr); - - _sinkCallbacks->relinquish_cb(_sinkCallbacksUserData); - - TRACE_L1("Failed to open the shared buffer!"); - result = Core::ERROR_OPENING_FAILED; - } - else { - TRACE_L1("Sink acquired"); - result = Core::ERROR_NONE; - } - } - } - } - - _sinkLock.Unlock(); - - return (result); - } - Core::hresult OnRelinquish() - { - Core::hresult result = Core::ERROR_ALREADY_RELEASED; - - _sinkLock.Lock(); - - TRACE_L1("Remote source is relinquishing sink..."); - - if ((_receiver != nullptr) || (_sinkCallbacks != nullptr)) { - - _receiver.reset(); - - if (_sinkCallbacks != nullptr) { - ASSERT(_sinkCallbacks->relinquish_cb != nullptr); - - const uint32_t sinkResult = _sinkCallbacks->relinquish_cb(_sinkCallbacksUserData); - - if (sinkResult != BLUETOOTHAUDIOSOURCE_SUCCESS) { - result = Core::ERROR_GENERAL; - TRACE_L1("Sink relinquish_cb() failed!"); - } - else { - TRACE_L1("Sink relinquished"); - result = Core::ERROR_NONE; - } - } - } - - _sinkLock.Unlock(); - - return (result); - } - Core::hresult OnSetSpeed(const int8_t speed) - { - Core::hresult result = Core::ERROR_NONE; - - TRACE_L1("Remote source is changing sink speed to %d%%...", speed); - - _sinkLock.Lock(); - - if (_receiver == nullptr) { - TRACE_L1("Recevier not aquired!"); - result = Core::ERROR_ILLEGAL_STATE; - } - else { - if ((speed == 0) || (speed == 100)) { - - if (_sinkCallbacks != nullptr) { - ASSERT(_sinkCallbacks->set_speed_cb != nullptr); - - const uint32_t sinkResult = _sinkCallbacks->set_speed_cb(speed, _sinkCallbacksUserData); - - if (sinkResult != 0) { - TRACE_L1("Sink set_speed_cb() failed!"); - result = Core::ERROR_NOT_SUPPORTED; - } - else { - if (speed == 0) { - _receiver->Stop(); - } - else if (speed == 100) { - _receiver->Start(); - } - - TRACE_L1("Sink speed changed to %d%%", speed); - } - } - else { - result = Core::ERROR_UNAVAILABLE; - } - } - else { - TRACE_L1("Unsupported speed setting (%d)", speed); - result = Core::ERROR_NOT_SUPPORTED; - } - } - - _sinkLock.Unlock(); - - return (result); - } - Core::hresult OnGetTime(uint32_t& timeMs) const - { - Core::hresult result = Core::ERROR_UNAVAILABLE; - - _sinkLock.Lock(); - - if (_sinkCallbacks != nullptr) { - ASSERT(_sinkCallbacks->get_time_cb != nullptr); - - const uint32_t sinkResult = _sinkCallbacks->get_time_cb(&timeMs, _sinkCallbacksUserData); - - if (sinkResult != BLUETOOTHAUDIOSOURCE_SUCCESS) { - TRACE_L1("Sink get_time_cb() failed!"); - result = Core::ERROR_NOT_SUPPORTED; - } - else { - result = Core::ERROR_NONE; - } - } - - _sinkLock.Unlock(); - - return (result); - } - Core::hresult OnGetDelay(uint32_t& delaySamples) const - { - Core::hresult result = Core::ERROR_UNAVAILABLE; - - _sinkLock.Lock(); - - if (_sinkCallbacks != nullptr) { - ASSERT(_sinkCallbacks->get_delay_cb != nullptr); - - const uint32_t sinkResult = _sinkCallbacks->get_delay_cb(&delaySamples, _sinkCallbacksUserData); - - if (sinkResult != BLUETOOTHAUDIOSOURCE_SUCCESS) { - TRACE_L1("Sink get_delay_cb() failed!"); - result = Core::ERROR_GENERAL; - } - else { - result = Core::ERROR_NONE; - } - } - - _sinkLock.Unlock(); - - return (result); - } - void OnFrameReceived(const uint8_t frame[], const uint16_t length) - { - ASSERT(frame != nullptr); - ASSERT(_sinkCallbacks->frame_cb != nullptr); - _sinkCallbacks->frame_cb(length, frame, _sinkCallbacksUserData); - } - - private: - void Operational(const bool upAndRunning) override - { - bool notify = false; - - _lock.Lock(); - - if (upAndRunning == true) { - TRACE_L1("'%s' service is now operational!", _callsign.c_str()); - - ASSERT(_source == nullptr); - ASSERT(_sourceControl == nullptr); - ASSERT(_sourceStream == nullptr); - - if (_source == nullptr) { - _source = SmartInterfaceType::Interface(); - - ASSERT(_source != nullptr); - - if (_source == nullptr) { - TRACE_L1("Failed to retrieve the IBluetoothAudio::ISource interface!"); - } - else { - if (_source->Callback(&_callback) != Core::ERROR_NONE) { - TRACE_L1("Failed to register the source callback!"); - } - - if (_sourceControl == nullptr) { - _sourceControl = _source->QueryInterface(); - - ASSERT(_sourceControl != nullptr); - - if (_sourceControl == nullptr) { - TRACE_L1("Failed to retrieve the IBluetoothAudio::ISource::IControl interface!"); - } - else if (_sourceControl->Sink(&_sinkStream) != Core::ERROR_NONE) { - TRACE_L1("Failed to register source stream callback!"); - } - else { - _sourceStream = _sourceControl->QueryInterface(); - ASSERT(_sourceStream != nullptr); - - if (_sourceStream == nullptr) { - TRACE_L1("Failed retrieve the IBluetoothAudio::IStream interface!"); - } - else { - // All OK! - notify = true; - } - } - } - } - } - } - else { - TRACE_L1("'%s' service is no longer operational!", _callsign.c_str()); - - OnSetSpeed(0); - - OnRelinquish(); - - Cleanup(true); - - notify = true; - } - - _lock.Unlock(); - - if (notify == true) { - _sinkLock.Lock(); - - for (auto& cb : _operationalStateCallbacks) { - - cb.first(upAndRunning, cb.second); - } - - _sinkLock.Unlock(); - } - } - - public: - static AudioSource& Instance() - { - static AudioSource* instance = new AudioSource("BluetoothAudio"); - ASSERT(instance != nullptr); - - return (*instance); - } - static void Dispose() - { - ASSERT(_singleton != nullptr); - delete _singleton; - } - - public: - uint32_t RegisterOperationalStateUpdateCallback(const bluetoothaudiosource_operational_state_update_cb callback, void* user_data) - { - uint32_t result = Core::ERROR_ALREADY_CONNECTED; - - ASSERT(callback != nullptr); - - const PluginHost::IShell* controller = SmartInterfaceType::ControllerInterface(); - - if (controller == nullptr) { - TRACE_L1("Framework is not running!"); - result = Core::ERROR_UNAVAILABLE; - } - else { - controller->Release(); - _sinkLock.Lock(); - - if (_operationalStateCallbacks.find(callback) == _operationalStateCallbacks.end()) { - _operationalStateCallbacks.emplace(std::piecewise_construct, std::forward_as_tuple(callback), std::forward_as_tuple(user_data)); - result = Core::ERROR_NONE; - } - - _sinkLock.Unlock(); - - if (result == Core::ERROR_NONE) { - // Call the callback immediately if the service is operational already. - if (SmartInterfaceType::IsOperational() == true) { - callback(true, user_data); - } - } - else { - TRACE_L1("Failed to register operational state callback!"); - } - } - - return (result); - } - uint32_t UnregisterOperationalStateUpdateCallback(const bluetoothaudiosource_operational_state_update_cb callback) - { - uint32_t result = Core::ERROR_ALREADY_RELEASED; - - ASSERT(callback != nullptr); - - _sinkLock.Lock(); - - auto it = _operationalStateCallbacks.find(callback); - - if (it != _operationalStateCallbacks.end()) { - _operationalStateCallbacks.erase(it); - result = Core::ERROR_NONE; - } - - _sinkLock.Unlock(); - - return (result); - } - - public: - uint32_t RegisterSourceStateChangedCallback(const bluetoothaudiosource_state_changed_cb callback, void* user_data) - { - uint32_t result = Core::ERROR_ALREADY_CONNECTED; - - ASSERT(callback != nullptr); - - _sinkLock.Lock(); - - if (_sinkStateCallbacks.find(callback) == _sinkStateCallbacks.end()) { - _sinkStateCallbacks.emplace(std::piecewise_construct, std::forward_as_tuple(callback), std::forward_as_tuple(user_data)); - result = Core::ERROR_NONE; - } - - _sinkLock.Unlock(); - - if (result == Core::ERROR_NONE) { - - Exchange::IBluetoothAudio::state state = Exchange::IBluetoothAudio::DISCONNECTED; - - _lock.Lock(); - - if (_source != nullptr) { - _source->State(state); - } - - _lock.Unlock(); - - callback(ConvertState(state), user_data); - } - else { - TRACE_L1("Failed to register a source state callback!"); - } - - return (result); - } - uint32_t UnregisterSourceStateChangedCallback(const bluetoothaudiosource_state_changed_cb callback) - { - uint32_t result = Core::ERROR_ALREADY_RELEASED; - - ASSERT(callback != nullptr); - - _sinkLock.Lock(); - - auto it = _sinkStateCallbacks.find(callback); - - if (it != _sinkStateCallbacks.end()) { - _sinkStateCallbacks.erase(it); - result = Core::ERROR_NONE; - } - - _sinkLock.Unlock(); - - return (result); - } - - public: - uint32_t SetSink(const bluetoothaudiosource_sink_t* sink, void* user_data) - { - uint32_t result = Core::ERROR_ALREADY_CONNECTED; - - TRACE_L1("Client is %s sink callbacks...", (sink == nullptr? "clearing" : "setting")); - - _sinkLock.Lock(); - - if ((_sinkCallbacks == nullptr) || (sink == nullptr)) { - - if ((_receiver != nullptr) && (sink == nullptr)) { - TRACE_L1("Warning: Removing callbacks for an acquired receiver!"); - - if (Relinquish() != Core::ERROR_NONE) { - TRACE_L1("Failed to relinquish on behalf of the client"); - } - } - - if ((sink == nullptr) || ((sink->acquire_cb != nullptr) && (sink->frame_cb != nullptr) - && (sink->relinquish_cb != nullptr) && (sink->set_speed_cb != nullptr) - && (sink->get_time_cb != nullptr) && (sink->get_delay_cb != nullptr))) { - - _sinkCallbacks = sink; - _sinkCallbacksUserData = user_data; - - result = Core::ERROR_NONE; - } - else { - TRACE_L1("Invalid sink callbacks!"); - result = Core::ERROR_BAD_REQUEST; - } - } - - _sinkLock.Unlock(); - - return (result); - } - uint32_t Relinquish() - { - uint32_t result = Core::ERROR_ILLEGAL_STATE; - - TRACE_L1("Client is relinquishing source..."); - - _lock.Lock(); - - if (_sourceStream != nullptr) { - - result = _sourceStream->Relinquish(); - - if ((result != Core::ERROR_NONE) && (result != Core::ERROR_ALREADY_RELEASED)) { - TRACE_L1("Relinquish() failed! [%d]", result); - } - } - - _lock.Unlock(); - - return (result); - } - uint32_t Speed(const int8_t speed) - { - uint32_t result = Core::ERROR_ILLEGAL_STATE; - - TRACE_L1("Client is changing source speed to %d%%...", speed); - - _lock.Lock(); - - if (_sourceStream != nullptr) { - result = _sourceStream->Speed(speed); - - if (result != Core::ERROR_NONE) { - TRACE_L1("Speed() failed! [%d]", result); - } - } - - _lock.Unlock(); - - return (result); - } - uint32_t Time(uint32_t& timeMs) - { - uint32_t result = Core::ERROR_ILLEGAL_STATE; - - _lock.Lock(); - - if (_sourceStream != nullptr) { - - result = _sourceStream->Time(timeMs); - - if (result != Core::ERROR_NONE) { - TRACE_L1("Time() failed! [%d]", result); - } - } - - _lock.Unlock(); - - return (result); - } - - public: - uint32_t State(Exchange::IBluetoothAudio::state& state) const - { - uint32_t result = Core::ERROR_ILLEGAL_STATE; - - _lock.Lock(); - - if (_source != nullptr) { - result = _source->State(state); - - if (result != Core::ERROR_NONE) { - TRACE_L1("State() failed! [%d]", result); - } - } - - _lock.Unlock(); - - return (result); - } - uint32_t Device(string& address) - { - uint32_t result = Core::ERROR_ILLEGAL_STATE; - - _lock.Lock(); - - if (_source != nullptr) { - - result = _source->Device(address); - - if (result != Core::ERROR_NONE) { - TRACE_L1("Device() failed! [%d]", result); - } - } - - _lock.Unlock(); - - return (result); - } - - private: - static bluetoothaudiosource_state_t ConvertState(const Exchange::IBluetoothAudio::state state) - { - bluetoothaudiosource_state_t out_state = BLUETOOTHAUDIOSOURCE_STATE_DISCONNECTED; - - switch (state) { - case Exchange::IBluetoothAudio::UNASSIGNED: - case Exchange::IBluetoothAudio::DISCONNECTED: - break; - case Exchange::IBluetoothAudio::CONNECTING: - out_state = BLUETOOTHAUDIOSOURCE_STATE_CONNECTING; - break; - case Exchange::IBluetoothAudio::CONNECTED: - out_state = BLUETOOTHAUDIOSOURCE_STATE_CONNECTED; - break; - case Exchange::IBluetoothAudio::CONNECTED_BAD: - case Exchange::IBluetoothAudio::CONNECTED_RESTRICTED: - out_state = BLUETOOTHAUDIOSOURCE_STATE_CONNECTED_BAD; - break; - case Exchange::IBluetoothAudio::READY: - out_state = BLUETOOTHAUDIOSOURCE_STATE_READY; - break; - case Exchange::IBluetoothAudio::STREAMING: - out_state = BLUETOOTHAUDIOSOURCE_STATE_STREAMING; - break; - default: - TRACE_L1("Unmapped state value!"); - break; - } - - return (out_state); - } - - private: - static AudioSource* _singleton; - - string _callsign; - - Core::SinkType _callback; - Core::SinkType _sinkStream; - - mutable Core::CriticalSection _lock; - Exchange::IBluetoothAudio::ISource* _source; - Exchange::IBluetoothAudio::ISource::IControl* _sourceControl; - Exchange::IBluetoothAudio::IStream* _sourceStream; - - mutable Core::CriticalSection _sinkLock; - OperationalStateUpdateCallbacks _operationalStateCallbacks; - SourceStateChangedCallbacks _sinkStateCallbacks; - const bluetoothaudiosource_sink_t* _sinkCallbacks; - void* _sinkCallbacksUserData; - std::unique_ptr _receiver; - }; - - AudioSource* AudioSource::_singleton = nullptr; - -} // namespace BluetoothAudiSourceClient - -} - - -using namespace Thunder; - -extern "C" { - -uint32_t bluetoothaudiosource_register_operational_state_update_callback(const bluetoothaudiosource_operational_state_update_cb callback, void *user_data) -{ - if (callback == nullptr) { - return (Core::ERROR_BAD_REQUEST); - } - else { - return (BluetoothAudioSourceClient::AudioSource::Instance().RegisterOperationalStateUpdateCallback(callback, user_data)); - } -} - -uint32_t bluetoothaudiosource_unregister_operational_state_update_callback(const bluetoothaudiosource_operational_state_update_cb callback) -{ - if (callback == nullptr) { - return (Core::ERROR_BAD_REQUEST); - } - else { - return (BluetoothAudioSourceClient::AudioSource::Instance().UnregisterOperationalStateUpdateCallback(callback)); - } -} - -uint32_t bluetoothaudiosource_register_state_changed_callback(const bluetoothaudiosource_state_changed_cb callback, void *user_data) -{ - if (callback == nullptr) { - return (Core::ERROR_BAD_REQUEST); - } - else { - return (BluetoothAudioSourceClient::AudioSource::Instance().RegisterSourceStateChangedCallback(callback, user_data)); - } -} - -uint32_t bluetoothaudiosource_unregister_state_changed_callback(const bluetoothaudiosource_state_changed_cb callback) -{ - if (callback == nullptr) { - return (Core::ERROR_BAD_REQUEST); - } - else { - return (BluetoothAudioSourceClient::AudioSource::Instance().UnregisterSourceStateChangedCallback(callback)); - } -} - -uint32_t bluetoothaudiosource_set_sink(const bluetoothaudiosource_sink_t *sink, void *user_data) -{ - return (BluetoothAudioSourceClient::AudioSource::Instance().SetSink(sink, user_data)); -} - -uint32_t bluetoothaudiosource_get_state(bluetoothaudiosource_state_t* out_state) -{ - if (out_state == nullptr) { - return (Core::ERROR_BAD_REQUEST); - } - else { - Exchange::IBluetoothAudio::state state{}; - - uint32_t result = BluetoothAudioSourceClient::AudioSource::Instance().State(state); - - if (result == Core::ERROR_NONE) { - } - - return (result); - } -} - -uint32_t bluetoothaudiosource_get_device(uint8_t out_address[6]) -{ - if (out_address == nullptr) { - return (Core::ERROR_BAD_REQUEST); - } - else { - string addressStr; - uint32_t result = BluetoothAudioSourceClient::AudioSource::Instance().Device(addressStr); - - // Pack the string into byte array. - for (uint8_t i = 0; i < 6; i++) { - out_address[i] = strtoul(&addressStr.c_str()[i*3], NULL, 16); - } - - return (result); - } -} - -uint32_t bluetoothaudiosource_get_time(uint32_t *out_time_ms) -{ - if (out_time_ms == nullptr) { - return (Core::ERROR_BAD_REQUEST); - } - else { - return (BluetoothAudioSourceClient::AudioSource::Instance().Time(*out_time_ms)); - } -} - -uint32_t bluetoothaudiosource_set_speed(const int8_t speed) -{ - return (BluetoothAudioSourceClient::AudioSource::Instance().Speed(speed)); -} - -uint32_t bluetoothaudiosource_relinquish(void) -{ - return (BluetoothAudioSourceClient::AudioSource::Instance().Relinquish()); -} - -uint32_t bluetoothaudiosource_dispose(void) -{ - BluetoothAudioSourceClient::AudioSource::Dispose(); - return (Core::ERROR_NONE); -} - -} // extern "C" diff --git a/Source/bluetoothaudiosource/CMakeLists.txt b/Source/bluetoothaudiosource/CMakeLists.txt deleted file mode 100644 index c06a0bf7..00000000 --- a/Source/bluetoothaudiosource/CMakeLists.txt +++ /dev/null @@ -1,86 +0,0 @@ -# If not stated otherwise in this file or this component's LICENSE file the -# following copyright and licenses apply: -# -# Copyright 2023 Metrological -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -cmake_minimum_required(VERSION 3.15) - -find_package(Thunder) - -project(BluetoothAudioSource) - -project_version(1.0.0) - -set(TARGET Client${PROJECT_NAME}) - -message("Setup ${TARGET} ${PROJECT_VERSION}") - -find_package(${NAMESPACE}Core REQUIRED) -find_package(${NAMESPACE}COM REQUIRED) -find_package(CompileSettingsDebug CONFIG REQUIRED) - -option(BLUETOOTHAUDIOSOURCE_EXAMPLERECORDER "Build audio recorder" OFF) -set(PUBLIC_HEADERS "include/bluetoothaudiosource.h") - -add_library(${TARGET} - Module.cpp - BluetoothAudioSource.cpp -) - -target_link_libraries(${TARGET} - PRIVATE - ${NAMESPACE}Core::${NAMESPACE}Core - ${NAMESPACE}COM::${NAMESPACE}COM - CompileSettingsDebug::CompileSettingsDebug -) - -set_target_properties(${TARGET} PROPERTIES - CXX_STANDARD ${CXX_STD} - CXX_STANDARD_REQUIRED YES - FRAMEWORK FALSE - PUBLIC_HEADER "${PUBLIC_HEADERS}" - VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR} -) - -target_include_directories(${TARGET} - PUBLIC - $ - $ - $ -) - -install( - TARGETS ${TARGET} EXPORT ${TARGET}Targets - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Development - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${NAMESPACE}_Runtime NAMELINK_COMPONENT ${NAMESPACE}_Development - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${NAMESPACE}_Runtime - FRAMEWORK DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${NAMESPACE}_Runtime - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${NAMESPACE}/bluetoothaudiosource COMPONENT ${NAMESPACE}_Development - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${NAMESPACE}/bluetoothaudiosource -) - -InstallCMakeConfig( - TARGETS ${TARGET} -) - -InstallPackageConfig( - TARGETS ${TARGET} - DESCRIPTION "Bluetooth audio capture abstraction" -) - -if(BLUETOOTHAUDIOSOURCE_EXAMPLERECORDER) - add_subdirectory(btaudiorecorder) -endif() diff --git a/Source/bluetoothaudiosource/Module.cpp b/Source/bluetoothaudiosource/Module.cpp deleted file mode 100644 index c0dd8266..00000000 --- a/Source/bluetoothaudiosource/Module.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Metrological - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Module.h" - -MODULE_NAME_DECLARATION(BUILD_REFERENCE) - diff --git a/Source/bluetoothaudiosource/Module.h b/Source/bluetoothaudiosource/Module.h deleted file mode 100644 index dc9a84b6..00000000 --- a/Source/bluetoothaudiosource/Module.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Metrological - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#ifndef MODULE_NAME -#define MODULE_NAME ClientLibrary_BluetoothAudioSource -#endif - -#include -#include -#include -#include diff --git a/Source/bluetoothaudiosource/btaudiorecorder/CMakeLists.txt b/Source/bluetoothaudiosource/btaudiorecorder/CMakeLists.txt deleted file mode 100644 index 74da5304..00000000 --- a/Source/bluetoothaudiosource/btaudiorecorder/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -# If not stated otherwise in this file or this component's LICENSE file the -# following copyright and licenses apply: -# -# Copyright 2023 Metrological -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -project(btaudiorecorder) - -set(TARGET ${PROJECT_NAME}) - -cmake_minimum_required(VERSION 3.15) - -add_executable(${PROJECT_NAME} - main.c -) - -target_include_directories(btaudiorecorder - PRIVATE - $ - $ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../include/bluetoothaudiosource.h" - - -#define TRACE(format, ...) printf("btaudiorecorder: " format "\n", ##__VA_ARGS__); -#define ERROR(format, ...) fprintf(stderr, "btaudiorecorder: [\033[31merror\033[0m]: " format "\n", ##__VA_ARGS__) - -static volatile bool user_break = false; - -typedef struct { - const char* file_name; - FILE* file; - bluetoothaudiosource_format_t format; - bool recording; - uint32_t recorded_bytes; - sem_t connect_sync; - sem_t record_sync; -} context_t; - -typedef struct { - // riff chunk - char riff[4]; - uint32_t riff_size; - char wave[4]; - // fmt chunk - char fmt[4]; - uint32_t fmt_size; - uint16_t type; - uint16_t channels; - uint32_t sample_rate; - uint32_t byte_rate; - uint16_t block_align; - uint16_t resolution; - // data chun - char data[4]; - uint32_t data_size; -} __attribute__((packed)) wav_header_t; - - -static uint32_t on_configure_sink(const bluetoothaudiosource_format_t *format, void *user_data) -{ - uint32_t result = ~BLUETOOTHAUDIOSOURCE_SUCCESS; - - if (user_data && format) { - context_t *context = ((context_t *) user_data); - - TRACE("Bluetooth audio source was configured"); - - TRACE("Configuring sink to parameters: %d Hz, %d bits, %d channel(s), %d.%02d fps", - format->sample_rate, format->resolution, format->channels, - (format->frame_rate / 100), (format->frame_rate % 100)); - - // Just cache the stream parameters... - memcpy(&context->format, format, sizeof(*format)); - - result = BLUETOOTHAUDIOSOURCE_SUCCESS; - } - - return (result); -} - -static uint32_t on_acquire_sink(void *user_data) -{ - uint32_t result = ~BLUETOOTHAUDIOSOURCE_SUCCESS; - - if (user_data) { - context_t *context = (context_t *) user_data; - - TRACE("Bluetooth audio source was acquired"); - - context->file = fopen(context->file_name, "wb"); - - if (context->file) { - // Fill in the header data accordingly to the source format. - // We're a simple .WAV writer: we accept anything they throw at us. - wav_header_t header; - memset(&header, 0, sizeof(header)); - memcpy(&header.riff, "RIFF", 4); - memcpy(&header.wave, "WAVE", 4); - memcpy(&header.fmt, "fmt ", 4); - memcpy(&header.data, "data", 4); - header.fmt_size = htole32(16); // "fmt" chunk size - header.type = htole16(1); // uncompressed linear PCM, signed if > 8 bit, little endian if > 8 bit - header.channels = htole16(context->format.channels); - header.sample_rate = htole32(context->format.sample_rate); - header.resolution = htole16(context->format.resolution); - - // Whatever... :) - header.byte_rate = htole32((context->format.sample_rate * context->format.resolution * context->format.channels) / 8); - header.block_align = htole16((context->format.resolution * context->format.channels) / 8); - - // And push the header to the file... - fwrite(&header, sizeof(header), 1, context->file); - - TRACE("Created wave file '%s'", context->file_name); - - TRACE("Successfully acquired audio sink"); - - sem_post(&context->connect_sync); - - result = BLUETOOTHAUDIOSOURCE_SUCCESS; - } - else { - ERROR("Failed to create wave file '%s'!", context->file_name); - } - } - - return (result); -} - -static uint32_t on_relinquish_sink(void *user_data) -{ - uint32_t result = ~BLUETOOTHAUDIOSOURCE_SUCCESS; - - if (user_data) { - context_t *context = user_data; - - TRACE("Bluetooth audio source was relinquished"); - - context->recording = false; - - if (context->file) { - // Retrospectively write file size back into the WAV header. - uint32_t size = 0; - fseek(context->file, 0, SEEK_END); - size = ftell(context->file); - size -= 8; // without "RIFF"+size - size = htole32(size); - fseek(context->file, 4, SEEK_SET); - fwrite(&size, sizeof(size), 1, context->file); - - // And data size... - size = htole32(context->recorded_bytes); - fseek(context->file, (sizeof(wav_header_t) - 4), SEEK_SET); - fwrite(&size, sizeof(size), 1, context->file); - - // Over and done! - fclose(context->file); - context->file = NULL; - - TRACE("Successfully relinquished audio sink"); - - result = BLUETOOTHAUDIOSOURCE_SUCCESS; - } - } - - return (result); -} - -static uint32_t on_change_sink_speed(const int8_t speed, void *user_data) -{ - uint32_t result = ~BLUETOOTHAUDIOSOURCE_SUCCESS; - - if (user_data) { - context_t *context = user_data; - - TRACE("Bluetooth audio source changed playback speed to %d%%", speed); - - if (speed == 100) { - context->recording = true; - result = BLUETOOTHAUDIOSOURCE_SUCCESS; - sem_post(&context->record_sync); - } - else if (speed == 0) { - result = BLUETOOTHAUDIOSOURCE_SUCCESS; - context->recording = false; - } - } - - return (result); -} - -static uint32_t on_get_time(uint32_t *out_time_ms, void *user_data) -{ - uint32_t result = ~BLUETOOTHAUDIOSOURCE_SUCCESS; - - if (out_time_ms && user_data) { - context_t *context = user_data; - - (*out_time_ms) = ((1000ULL * context->recorded_bytes) / (context->format.frame_rate * context->format.channels * (context->format.resolution / 8))); - - result = BLUETOOTHAUDIOSOURCE_SUCCESS; - } - - return (result); -} - -static uint32_t on_get_delay(uint32_t *out_delay_samples, void *user_data) -{ - uint32_t result = ~BLUETOOTHAUDIOSOURCE_SUCCESS; - - if (out_delay_samples && user_data) { - - // This is a disk writer, we're super fast! - (*out_delay_samples) = 0; - - result = BLUETOOTHAUDIOSOURCE_SUCCESS; - } - - return (result); -} - -static void on_frame_received(const uint16_t length, const uint8_t frame[], void *user_data) -{ - // TRACE("Frame received (%d bytes)", length); - - if (frame && user_data) { - context_t *context = user_data; - - if (context->file && context->recording) { - fwrite(frame, length, 1, context->file); - context->recorded_bytes += length; - } - } -} - -static bluetoothaudiosource_sink_t callbacks = { - on_configure_sink, - on_acquire_sink, - on_relinquish_sink, - on_change_sink_speed, - on_get_time, - on_get_delay, - on_frame_received -}; - -static void state_changed(const bluetoothaudiosource_state_t state, __attribute__((unused)) void *user_data) -{ - assert(user_data != NULL); - - switch (state) { - case BLUETOOTHAUDIOSOURCE_STATE_DISCONNECTED: - TRACE("State: BLUETOOTHAUDIOSOURCE_STATE_DISCONNECTED"); - break; - case BLUETOOTHAUDIOSOURCE_STATE_CONNECTING: - TRACE("State: BLUETOOTHAUDIOSOURCE_STATE_CONNECTING"); - break; - case BLUETOOTHAUDIOSOURCE_STATE_CONNECTED: - TRACE("State: BLUETOOTHAUDIOSOURCE_STATE_CONNECTED"); - break; - case BLUETOOTHAUDIOSOURCE_STATE_CONNECTED_BAD: - TRACE("State: BLUETOOTHAUDIOSOURCE_STATE_CONNECTED_BAD"); - break; - case BLUETOOTHAUDIOSOURCE_STATE_READY: - TRACE("State: BLUETOOTHAUDIOSOURCE_STATE_READY"); - break; - case BLUETOOTHAUDIOSOURCE_STATE_STREAMING: - TRACE("State: BLUETOOTHAUDIOSOURCE_STATE_STREAMING"); - break; - default: - assert(!"Unmapped enum"); - break; - } -} - -static void operational_state_update(const uint8_t running, void *user_data) -{ - if (running) { - TRACE("Bluetooth Audio Source service now available"); - - // Register for the source updates. - if (bluetoothaudiosource_register_state_changed_callback(state_changed, user_data) != 0) { - ERROR("Failed to register state update callback!"); - } - else if (bluetoothaudiosource_set_sink(&callbacks, user_data) != 0) { - ERROR("Failed to set sink callbacks!"); - } - } - else { - TRACE("Bluetooth Audio Source service is now unavailable"); - } -} - -void ctrl_c_handler(int signal) -{ - // Ctrl+C! - (void) signal; - user_break = true; -} - -int main(const int argc, const char* argv[]) -{ - int result = 1; - - TRACE("Records incoming Bluetooth audio stream to a .wav file"); - - if (argc != 2) { - TRACE("arguments:\n\t%s ", argv[0]); - } - else { - context_t context; - memset(&context, 0, sizeof(context)); - - context.file_name = argv[1]; - - sem_init(&context.connect_sync, 0, 0); - sem_init(&context.record_sync, 0, 0); - - if (bluetoothaudiosource_register_operational_state_update_callback(&operational_state_update, &context) != BLUETOOTHAUDIOSOURCE_SUCCESS) { - ERROR("Failed to register Bluetooth Audio Source service operational callback"); - } - else { - TRACE("Waiting for audio source to connect..."); - setbuf(stdout, NULL); - - uint32_t timeout = 120 /* sec */; - struct timespec ts; - - // Install a Ctrl+C handler. - struct sigaction handler; - handler.sa_handler = ctrl_c_handler; - sigemptyset(&handler.sa_mask); - handler.sa_flags = 0; - sigaction(SIGINT, &handler, NULL); - - clock_gettime(CLOCK_REALTIME, &ts); - ts.tv_sec += timeout; - - // Wait for connection... - if (sem_timedwait(&context.connect_sync, &ts) != -1) { - - TRACE("Waiting for audio source to start streaming..."); - - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - timeout = 600; - ts.tv_sec += timeout; - - while (!user_break) { - // Wait for playback start... - if (sem_timedwait(&context.record_sync, &ts) != -1) { - TRACE("Recording..."); - - while (context.recording == true) { - - usleep(50 * 1000); - - printf("Captured %i kilobytes\r", (context.recorded_bytes / 1024)); - - if ((user_break != false) && (context.recording == true)) { - // Ctrl+C! - printf("\n"); - TRACE("User break! Stopping recording..."); - context.recording = false; - } - } - - - if (!user_break) { - printf("\n"); - TRACE("Recording paused"); - } - } - else if (!user_break) { - ERROR("Streaming has not started in %i seconds since connecting, terminating!", timeout); - break; - } - } - - TRACE("Recording complete. Exiting..."); - - // Let them know we're exiting. - if (bluetoothaudiosource_relinquish() != BLUETOOTHAUDIOSOURCE_SUCCESS) { - TRACE("Failed to relinquish source!"); - } - - // Unregister ourselves. - if (bluetoothaudiosource_set_sink(NULL, NULL) != BLUETOOTHAUDIOSOURCE_SUCCESS) { - TRACE("Failed to unset sink callbacks!"); - } - - result = 0; - } - else { - ERROR("Bluetooth audio source device not connected in %i seconds, terminating!", timeout); - } - - bluetoothaudiosource_dispose(); - } - } - - return (result); -} diff --git a/Source/bluetoothaudiosource/btaudiorecorder/readme.txt b/Source/bluetoothaudiosource/btaudiorecorder/readme.txt deleted file mode 100644 index 433f9aec..00000000 --- a/Source/bluetoothaudiosource/btaudiorecorder/readme.txt +++ /dev/null @@ -1,10 +0,0 @@ -This tool records audio stream from a Bluetooth source device to a .wav file. -It requires the BluetoothAudio plugin to be available. Tool must be running when -the remote device connects to establish the streaming session. If no connection -is made in 2 minutes the program will exit. - -Usage: - btaudiorecorder /tmp/recording.wav - -Ctrl+C to close the recording session and quit. - diff --git a/Source/bluetoothaudiosource/include/bluetoothaudiosource.h b/Source/bluetoothaudiosource/include/bluetoothaudiosource.h deleted file mode 100644 index 08301746..00000000 --- a/Source/bluetoothaudiosource/include/bluetoothaudiosource.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2023 Metrological - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef BLUETOOTHAUDIOSOURCE_H -#define BLUETOOTHAUDIOSOURCE_H - -#include - -#undef EXTERNAL -#define EXTERNAL __attribute__((visibility("default"))) - -#ifdef __cplusplus -extern "C" { -#endif - -#define BLUETOOTHAUDIOSOURCE_SUCCESS (0) - -typedef struct { - uint32_t sample_rate; - uint16_t frame_rate; - uint8_t resolution; - uint8_t channels; -} bluetoothaudiosource_format_t; - -typedef enum { - BLUETOOTHAUDIOSOURCE_STATE_DISCONNECTED = 0, - BLUETOOTHAUDIOSOURCE_STATE_CONNECTING, - BLUETOOTHAUDIOSOURCE_STATE_CONNECTED, - BLUETOOTHAUDIOSOURCE_STATE_CONNECTED_BAD, - BLUETOOTHAUDIOSOURCE_STATE_READY, - BLUETOOTHAUDIOSOURCE_STATE_STREAMING -} bluetoothaudiosource_state_t; - -typedef void *bluetoothaudiosurce_player_t; - -typedef void (*bluetoothaudiosource_operational_state_update_cb)(const uint8_t running /* 0 == not running */, void *user_data); -typedef void (*bluetoothaudiosource_state_changed_cb)(const bluetoothaudiosource_state_t state, void *user_data); -typedef uint32_t (*bluetoothaudiosource_configure_cb)(const bluetoothaudiosource_format_t *format, void *user_data); -typedef uint32_t (*bluetoothaudiosource_acquire_cb)(void *user_data); -typedef uint32_t (*bluetoothaudiosource_relinquish_cb)(void *user_data); -typedef uint32_t (*bluetoothaudiosource_set_speed_cb)(const int8_t speed, void *user_data); -typedef uint32_t (*bluetoothaudiosource_get_time_cb)(uint32_t* time_ms, void *user_data); -typedef uint32_t (*bluetoothaudiosource_get_delay_cb)(uint32_t* delay_samples, void *user_data); -typedef void (*bluetoothaudiosource_frame_cb)(const uint16_t length_bytes, const uint8_t frame[], void *user_data); - -typedef struct bluetoothaudiosource_sink_tag { - bluetoothaudiosource_configure_cb configure_cb; - bluetoothaudiosource_acquire_cb acquire_cb; - bluetoothaudiosource_relinquish_cb relinquish_cb; - bluetoothaudiosource_set_speed_cb set_speed_cb; - bluetoothaudiosource_get_time_cb get_time_cb; - bluetoothaudiosource_get_delay_cb get_delay_cb; - bluetoothaudiosource_frame_cb frame_cb; -} bluetoothaudiosource_sink_t; - - -EXTERNAL uint32_t bluetoothaudiosource_register_operational_state_update_callback(const bluetoothaudiosource_operational_state_update_cb callback, void *user_data); -EXTERNAL uint32_t bluetoothaudiosource_unregister_operational_state_update_callback(const bluetoothaudiosource_operational_state_update_cb callback); - -EXTERNAL uint32_t bluetoothaudiosource_register_state_changed_callback(const bluetoothaudiosource_state_changed_cb callback, void *user_data); -EXTERNAL uint32_t bluetoothaudiosource_unregister_state_changed_callback(const bluetoothaudiosource_state_changed_cb callback); - -EXTERNAL uint32_t bluetoothaudiosource_set_sink(const bluetoothaudiosource_sink_t *sink, void *user_data); - -EXTERNAL uint32_t bluetoothaudiosource_get_state(bluetoothaudiosource_state_t *out_state); -EXTERNAL uint32_t bluetoothaudiosource_get_device(uint8_t out_address[6]); -EXTERNAL uint32_t bluetoothaudiosource_get_time(uint32_t *out_time_ms); - -EXTERNAL uint32_t bluetoothaudiosource_set_speed(const int8_t speed); - -EXTERNAL uint32_t bluetoothaudiosource_relinquish(void); - -EXTERNAL uint32_t bluetoothaudiosource_dispose(void); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // BLUETOOTHAUDIOSOURCE_H diff --git a/docs/README.md b/docs/README.md index 644298f8..f5c404c0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,7 +4,7 @@ ThunderClientLibraries is a collection of lightweight, process-boundary client l ThunderClientLibraries provides a stable abstraction for any component — whether a standalone application, an out-of-process runtime, or a plugin library — that needs to access services hosted inside the WPEFramework (Thunder) plugin host without a direct dependency on Thunder internals. The libraries are independent of one another; a component links only the libraries it needs. -Typical consumers include media pipeline processes requiring DRM session creation through OpenCDM, web runtimes needing display or device capability metadata, Bluetooth audio pipelines streaming audio to or from a paired device, and plugin libraries that need access to Thunder-hosted functionality without a direct dependency on Thunder internals. +Typical consumers include media pipeline processes requiring DRM session creation through OpenCDM, web runtimes needing display or device capability metadata and plugin libraries that need access to Thunder-hosted functionality without a direct dependency on Thunder internals. **Key Features & Responsibilities:** @@ -16,8 +16,6 @@ Typical consumers include media pipeline processes requiring DRM session creatio - **CompositorClient**: Provides a C++ graphics and input abstraction layer (`Thunder::Compositor::IDisplay`, `Thunder::Compositor::IDisplay::ISurface`, `Thunder::Compositor::IDisplay::IKeyboard`, `Thunder::Compositor::IDisplay::IPointer`, `Thunder::Compositor::IDisplay::IWheel`, `Thunder::Compositor::IDisplay::ITouchPanel`) that allows out-of-process renderers to create surfaces and receive input events through the compositor without being tightly coupled to a specific display server. - **Cryptography Client**: Bridges callers to the Svalbard cryptographic vault service, exposing hash, cipher, Diffie-Hellman key exchange, random number generation, and persistent key storage operations through `Exchange::ICryptography` and related interfaces. - **ProvisionProxy Client**: Provides `GetDeviceId()` and `GetDRMId()` functions to retrieve the unique device identifier and DRM credentials from the Provisioning service, connecting over `Exchange::IProvisioning`. -- **BluetoothAudioSink Client**: Delivers a C API through which an audio pipeline process can configure an active Bluetooth audio sink, acquire/relinquish the sink, submit audio frames over a shared memory buffer, and track connection state changes. -- **BluetoothAudioSource Client**: Delivers a C API through which an audio pipeline process can receive audio frames from a connected Bluetooth audio source via a shared memory buffer, and register callbacks for source state changes and audio stream events. --- @@ -33,8 +31,6 @@ Typical consumers include media pipeline processes requiring DRM session creatio | `PlayerInfo` | Singleton wrapping `RPC::SmartInterfaceType`. Queries `Exchange::Dolby::IOutput` and registers `Dolby::IOutput::INotification` for Dolby mode-change events. | `PlayerInfo.cpp`, `playerinfo.h` | | `CryptographyLink` | Singleton wrapping `RPC::SmartInterfaceType`. Acquires `Exchange::ICryptography` and `Exchange::IDeviceObjects` from the Svalbard plugin. Adapter classes (`RPCDiffieHellmanImpl`, etc.) wrap the remote interfaces and handle connection loss. | `Cryptography.cpp`, `cryptography.h` | | `ProvisionProxy ipclink` | Per-call COM-RPC client connecting to the Provisioning plugin. Implements `GetDeviceId()` and `GetDRMId()` via `Exchange::IProvisioning`. | `ipclink.cpp` (provisionproxy) | -| `AudioSink` | Singleton wrapping `RPC::SmartInterfaceType`. Maintains a `Core::SharedBuffer` (`SendBuffer`) for frame delivery. Tracks sink connection state and delivers state-change callbacks. | `BluetoothAudioSink.cpp`, `bluetoothaudiosink.h` | -| `AudioSource` | Singleton wrapping `RPC::SmartInterfaceType`. Contains a `Receiver` worker thread that reads audio frames from a `Core::SharedBuffer` and dispatches them to the registered frame callback. | `BluetoothAudioSource.cpp`, `bluetoothaudiosource.h` | | `CompositorClient` | C++ abstraction layer (`Thunder::Compositor::IDisplay`, `Thunder::Compositor::IDisplay::ISurface`, `Thunder::Compositor::IDisplay::IKeyboard`, `Thunder::Compositor::IDisplay::IPointer`, `Thunder::Compositor::IDisplay::IWheel`, `Thunder::Compositor::IDisplay::ITouchPanel`). Implementation is selected at build time via `PLUGIN_COMPOSITOR_IMPLEMENTATION` and compiled from the corresponding subdirectory (e.g., `Wayland`, `Mesa`, `RPI`). | `Client.h`, `src/CMakeLists.txt` | --- @@ -53,11 +49,9 @@ Typical consumers include media pipeline processes requiring DRM session creatio | `PlayerInfo` | Query playback capabilities and receive Dolby mode change events | `Exchange::IPlayerProperties`, `Exchange::Dolby::IOutput` | | `Svalbard` | Cryptographic operations (cipher, hash, DH, vault, random) | `Exchange::ICryptography`, `Exchange::IDiffieHellman`, `Exchange::INetflixSecurity`, `Exchange::IDeviceObjects` | | `Provisioning` | Device ID and DRM credential retrieval | `Exchange::IProvisioning::DeviceId()`, `Exchange::IProvisioning` (DRM ID via `DRMInfo`) | -| `BluetoothAudio` | Bluetooth audio sink/source streaming and state management | `Exchange::IBluetoothAudio::ISink`, `Exchange::IBluetoothAudio::ISource` | | `Compositor` | Display surface creation and input event delivery | `Thunder::Compositor::IDisplay`, `Thunder::Compositor::IDisplay::ISurface`, `Thunder::Compositor::IDisplay::IKeyboard`, `Thunder::Compositor::IDisplay::IPointer`, `Thunder::Compositor::IDisplay::IWheel`, `Thunder::Compositor::IDisplay::ITouchPanel` | | **IPC Transport** | | | | COM-RPC (UNIX domain socket / TCP) | Plugin communication uses Thunder COM-RPC (UNIX domain sockets on Linux; TCP loopback endpoints are used on Windows when configured by the client / environment) | `RPC::CommunicatorClient`, `RPC::SmartInterfaceType`, `Core::NodeId` | -| Shared Memory Buffer | BluetoothAudioSink and BluetoothAudioSource use `Core::SharedBuffer` for audio frame transfer | `Core::SharedBuffer` | ### Events Published @@ -67,10 +61,6 @@ Typical consumers include media pipeline processes requiring DRM session creatio | `displayinfo_operational_state_change_cb` | DisplayInfo plugin goes offline or comes back online | Callers registered with `displayinfo_register_operational_state_change_callback()` | | `playerinfo_dolby_audio_updated_cb` | Dolby sound mode changes, delivered via `Exchange::Dolby::IOutput::INotification::AudioModeChanged` | Callers registered with `playerinfo_register_dolby_sound_mode_updated_callback()` | | `playerinfo_operational_state_change_cb` | PlayerInfo plugin goes offline or comes back online | Callers registered with `playerinfo_register_operational_state_change_callback()` | -| `bluetoothaudiosink_state_changed_cb` | Bluetooth audio sink connection state transitions (UNASSIGNED → DISCONNECTED → CONNECTING → CONNECTED / CONNECTED_BAD / CONNECTED_RESTRICTED → READY → STREAMING) | Callers registered with `bluetoothaudiosink_register_state_changed_callback()` | -| `bluetoothaudiosink_operational_state_update_cb` | BluetoothAudio plugin goes offline or comes back online | Callers registered with `bluetoothaudiosink_register_operational_state_update_callback()` | -| `bluetoothaudiosource_state_changed_cb` | Bluetooth audio source connection state transitions | Callers registered with `bluetoothaudiosource_register_state_changed_callback()` | -| `bluetoothaudiosource_frame_cb` | Audio frame received from connected Bluetooth source | Caller-supplied sink struct registered via `bluetoothaudiosource_set_sink()` | ### IPC Flow Patterns @@ -122,13 +112,11 @@ ThunderClientLibraries operates above the HAL boundary. Each library calls into | `Exchange::IPlayerProperties` / `Dolby::IOutput` | `VideoCodecs()`, `AudioCodecs()`, `Resolution()`, `SoundMode()`, `EnableAtmosOutput()`, `AudioModeChanged()` notification | PlayerInfo | | `Exchange::ICryptography` / `IDiffieHellman` / `IVault` | `Hash()`, `Cipher()`, `DiffieHellman()`, `Vault()`, `Random()` | Cryptography | | `Exchange::IProvisioning` | `DeviceId()`, `DRMInfo()` | ProvisionProxy | -| `Exchange::IBluetoothAudio::ISink` | `Acquire()`, `Relinquish()`, `Speed()`, `Time()`, `Latency()`, state change notifications | BluetoothAudioSink | -| `Exchange::IBluetoothAudio::ISource` | `Assign()`, `Revoke()`, `Frame()`, state change notifications | BluetoothAudioSource | | `Thunder::Compositor::IDisplay` / `ISurface` | `Create()`, `Destroy()`, keyboard/pointer/touch input event dispatch | CompositorClient | ### Key Implementation Logic -- **State / Lifecycle Management**: Libraries using `RPC::SmartInterfaceType` override the `Operational(bool)` virtual method. When `Operational(true)` is called, the library acquires interface pointers (e.g., `_displayConnection`, `_playerInterface`, `_dolbyInterface`) by calling `BaseClass::Interface()` and queries any sub-interfaces. When `Operational(false)` is called, all cached pointers are released. This is implemented in `DisplayInfo.cpp`, `PlayerInfo.cpp`, `BluetoothAudioSink.cpp`, and `BluetoothAudioSource.cpp`. +- **State / Lifecycle Management**: Libraries using `RPC::SmartInterfaceType` override the `Operational(bool)` virtual method. When `Operational(true)` is called, the library acquires interface pointers (e.g., `_displayConnection`, `_playerInterface`, `_dolbyInterface`) by calling `BaseClass::Interface()` and queries any sub-interfaces. When `Operational(false)` is called, all cached pointers are released. This is implemented in `DisplayInfo.cpp` and `PlayerInfo.cpp`. - **Event Processing**: Notification sinks are registered with the remote plugin interface immediately after the interface pointer is acquired. Incoming COM-RPC notification calls arrive on the RPC engine thread and are dispatched synchronously to all caller-registered callbacks by iterating the callback map under a `Core::CriticalSection` lock.