avs-device-sdk/BluetoothImplementations/BlueZ/src/PulseAudioBluetoothInitiali...

432 lines
14 KiB
C++

/*
* Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file 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 <chrono>
#include <string>
#include "BlueZ/PulseAudioBluetoothInitializer.h"
#include <AVSCommon/Utils/Logger/Logger.h>
/// String to identify log entries originating from this file.
static const std::string TAG{"PulseAudioBluetoothInitializer"};
/**
* Create a LogEntry using this file's TAG and the specified event string.
*
* @param The event string for this @c LogEntry.
*/
#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event)
namespace alexaClientSDK {
namespace bluetoothImplementations {
namespace blueZ {
using namespace avsCommon::utils::bluetooth;
/// The PulseAudio module related to device discovery.
static std::string BLUETOOTH_DISCOVER = "module-bluetooth-discover";
/// The PulseAudio module related to stack related policies.
static std::string BLUETOOTH_POLICY = "module-bluetooth-policy";
/// Return indicating an operation was successful.
static const int PA_CONTEXT_CB_SUCCESS{1};
/// Return for a module callback indicating that this is the eol.
static const int PA_MODULE_CB_EOL_EOL{1};
/// Return for a module callback indicating that an error occurred.
static const int PA_MODULE_CB_EOL_ERR{-1};
/// Timeout for blocking operations.
static const std::chrono::seconds TIMEOUT{2};
/**
* Converts a pa_context_state_t enum to a string.
*/
static std::string stateToString(pa_context_state_t state) {
switch (state) {
case PA_CONTEXT_UNCONNECTED:
return "PA_CONTEXT_UNCONNECTED";
case PA_CONTEXT_CONNECTING:
return "PA_CONTEXT_CONNECTING";
case PA_CONTEXT_AUTHORIZING:
return "PA_CONTEXT_AUTHORIZING";
case PA_CONTEXT_SETTING_NAME:
return "PA_CONTEXT_SETTING_NAME";
case PA_CONTEXT_READY:
return "PA_CONTEXT_READY";
case PA_CONTEXT_FAILED:
return "PA_CONTEXT_FAILED";
case PA_CONTEXT_TERMINATED:
return "PA_CONTEXT_TERMINATED";
}
return "UNKNOWN";
}
std::shared_ptr<PulseAudioBluetoothInitializer> PulseAudioBluetoothInitializer::create(
std::shared_ptr<BluetoothEventBus> eventBus) {
ACSDK_DEBUG5(LX(__func__));
if (!eventBus) {
ACSDK_ERROR(LX("createFailed").d("reason", "nullEventBus"));
return nullptr;
}
auto pulseAudio = std::shared_ptr<PulseAudioBluetoothInitializer>(new PulseAudioBluetoothInitializer(eventBus));
pulseAudio->init();
return pulseAudio;
}
PulseAudioBluetoothInitializer::PulseAudioBluetoothInitializer(
std::shared_ptr<avsCommon::utils::bluetooth::BluetoothEventBus> eventBus) :
m_eventBus{eventBus},
m_paLoop{nullptr},
m_paLoopStarted{false},
m_context{nullptr},
m_policyState{ModuleState::UNKNOWN},
m_discoverState{ModuleState::UNKNOWN},
m_connected{false} {
}
void PulseAudioBluetoothInitializer::init() {
ACSDK_DEBUG5(LX(__func__));
m_eventBus->addListener({BluetoothEventType::BLUETOOTH_DEVICE_MANAGER_INITIALIZED}, shared_from_this());
}
void PulseAudioBluetoothInitializer::onLoadDiscoverResult(pa_context* context, uint32_t index, void* userdata) {
ACSDK_DEBUG5(LX(__func__));
if (!userdata) {
ACSDK_ERROR(LX("onLoadDiscoverResultFailed").d("reason", "nullUserData"));
return;
}
PulseAudioBluetoothInitializer* initializer = static_cast<PulseAudioBluetoothInitializer*>(userdata);
initializer->handleLoadModuleResult(context, index, BLUETOOTH_DISCOVER);
}
void PulseAudioBluetoothInitializer::onLoadPolicyResult(pa_context* context, uint32_t index, void* userdata) {
ACSDK_DEBUG5(LX(__func__));
if (!userdata) {
ACSDK_ERROR(LX("onLoadPolicyResultFailed").d("reason", "nullUserData"));
return;
}
PulseAudioBluetoothInitializer* initializer = static_cast<PulseAudioBluetoothInitializer*>(userdata);
initializer->handleLoadModuleResult(context, index, BLUETOOTH_POLICY);
}
void PulseAudioBluetoothInitializer::handleLoadModuleResult(
pa_context* context,
uint32_t index,
const std::string& moduleName) {
ACSDK_DEBUG5(LX(__func__).d("module", moduleName).d("index", index));
std::unique_lock<std::mutex> lock(m_mutex);
if (!context) {
ACSDK_ERROR(LX("handleLoadModuleResultFailed").d("reason", "nullContext"));
return;
} else if (PA_INVALID_INDEX == index) {
ACSDK_ERROR(LX("handleLoadModuleResultFailed").d("reason", "loadFailed"));
return;
}
if (updateStateLocked(ModuleState::LOADED_BY_SDK, moduleName)) {
m_mainThreadCv.notify_one();
}
}
void PulseAudioBluetoothInitializer::onUnloadPolicyResult(pa_context* context, int success, void* userdata) {
ACSDK_DEBUG5(LX(__func__));
if (!userdata) {
ACSDK_ERROR(LX("onUnloadPolicyResultFailed").d("reason", "nullUserData"));
return;
}
PulseAudioBluetoothInitializer* initializer = static_cast<PulseAudioBluetoothInitializer*>(userdata);
initializer->handleUnloadModuleResult(context, success, BLUETOOTH_POLICY);
}
void PulseAudioBluetoothInitializer::onUnloadDiscoverResult(pa_context* context, int success, void* userdata) {
ACSDK_DEBUG5(LX(__func__));
if (!userdata) {
ACSDK_ERROR(LX("onUnloadDiscoverResultFailed").d("reason", "nullUserData"));
return;
}
PulseAudioBluetoothInitializer* initializer = static_cast<PulseAudioBluetoothInitializer*>(userdata);
initializer->handleUnloadModuleResult(context, success, BLUETOOTH_DISCOVER);
}
void PulseAudioBluetoothInitializer::handleUnloadModuleResult(
pa_context* context,
int success,
const std::string& moduleName) {
ACSDK_DEBUG5(LX(__func__).d("module", moduleName).d("success", success));
if (!context) {
ACSDK_ERROR(LX("handleUnloadModuleResultFailed").d("reason", "nullContext"));
return;
} else if (PA_CONTEXT_CB_SUCCESS != success) {
ACSDK_ERROR(LX("handleUnloadModuleResult").d("reason", "unloadFailed"));
return;
}
if (updateStateLocked(ModuleState::UNLOADED, moduleName)) {
m_mainThreadCv.notify_one();
}
}
void PulseAudioBluetoothInitializer::onModuleFound(
pa_context* context,
const pa_module_info* info,
int eol,
void* userdata) {
ACSDK_DEBUG9(LX(__func__));
if (!context) {
ACSDK_ERROR(LX("moduleFoundFailed").d("reason", "nullContext"));
return;
} else if (!userdata) {
ACSDK_ERROR(LX("moduleFoundFailed").d("reason", "nullUserData"));
return;
} else if (PA_MODULE_CB_EOL_ERR == eol) {
ACSDK_ERROR(LX("moduleFoundFailed").d("reason", "pulseAudioError").d("eol", PA_MODULE_CB_EOL_ERR));
return;
}
PulseAudioBluetoothInitializer* initializer = static_cast<PulseAudioBluetoothInitializer*>(userdata);
std::unique_lock<std::mutex> lock(initializer->m_mutex);
// If end of the list info object is not valid.
if (PA_MODULE_CB_EOL_EOL == eol) {
ACSDK_DEBUG(LX(__func__).m("endOfList"));
if (ModuleState::INITIALLY_LOADED != initializer->m_policyState) {
initializer->updateStateLocked(ModuleState::UNLOADED, BLUETOOTH_POLICY);
}
if (ModuleState::INITIALLY_LOADED != initializer->m_discoverState) {
initializer->updateStateLocked(ModuleState::UNLOADED, BLUETOOTH_DISCOVER);
}
initializer->m_mainThreadCv.notify_one();
return;
} else if (!info || !info->name) {
ACSDK_ERROR(LX("moduleFoundFailed").d("reason", "invalidInfo"));
return;
}
ACSDK_DEBUG9(LX(__func__).d("name", info->name));
if (BLUETOOTH_POLICY == info->name) {
initializer->updateStateLocked(ModuleState::INITIALLY_LOADED, BLUETOOTH_POLICY);
pa_context_unload_module(context, info->index, &PulseAudioBluetoothInitializer::onUnloadPolicyResult, userdata);
} else if (BLUETOOTH_DISCOVER == info->name) {
initializer->updateStateLocked(ModuleState::INITIALLY_LOADED, BLUETOOTH_DISCOVER);
pa_context_unload_module(
context, info->index, &PulseAudioBluetoothInitializer::onUnloadDiscoverResult, userdata);
}
}
bool PulseAudioBluetoothInitializer::updateStateLocked(const ModuleState& state, const std::string& module) {
ModuleState currentState = ModuleState::UNKNOWN;
if (BLUETOOTH_POLICY == module) {
currentState = m_policyState;
m_policyState = state;
} else if (BLUETOOTH_DISCOVER == module) {
currentState = m_discoverState;
m_discoverState = state;
} else {
ACSDK_ERROR(LX("updateStateLockedFailed").d("reason", "invalidModule"));
return false;
}
ACSDK_DEBUG5(LX(__func__).d("module", module).d("currentState", currentState).d("desiredState", state));
return true;
}
void PulseAudioBluetoothInitializer::onStateChanged(pa_context* context, void* userdata) {
ACSDK_DEBUG5(LX(__func__));
if (!context) {
ACSDK_ERROR(LX("onStateChangedFailed").d("reason", "nullContext"));
return;
} else if (!userdata) {
ACSDK_ERROR(LX("onStateChangedFailed").d("reason", "nullUserData"));
return;
}
pa_context_state_t state;
state = pa_context_get_state(context);
ACSDK_DEBUG5(LX(__func__).d("state", stateToString(state)));
PulseAudioBluetoothInitializer* initializer = static_cast<PulseAudioBluetoothInitializer*>(userdata);
initializer->setStateAndNotify(state);
}
void PulseAudioBluetoothInitializer::setStateAndNotify(pa_context_state_t state) {
ACSDK_DEBUG5(LX(__func__));
std::unique_lock<std::mutex> lock(m_mutex);
switch (state) {
// Connected and ready to receive calls.
case PA_CONTEXT_READY:
m_connected = true;
// These are failed cases.
case PA_CONTEXT_FAILED:
case PA_CONTEXT_TERMINATED:
m_mainThreadCv.notify_one();
break;
// Intermediate states that can be ignored.
case PA_CONTEXT_UNCONNECTED:
case PA_CONTEXT_CONNECTING:
case PA_CONTEXT_AUTHORIZING:
case PA_CONTEXT_SETTING_NAME:
default:
break;
}
}
void PulseAudioBluetoothInitializer::cleanup() {
ACSDK_DEBUG5(LX(__func__));
if (m_context) {
pa_context_disconnect(m_context);
pa_context_unref(m_context);
m_context = nullptr;
}
if (m_paLoop) {
pa_threaded_mainloop_stop(m_paLoop);
pa_threaded_mainloop_free(m_paLoop);
m_paLoop = nullptr;
}
ACSDK_DEBUG(LX("cleanup").m("cleanupCompleted"));
}
PulseAudioBluetoothInitializer::~PulseAudioBluetoothInitializer() {
ACSDK_DEBUG5(LX(__func__));
// Ensure there are no references to PA resources being used.
m_executor.shutdown();
// cleanup() likely to have been previously called, but call again in case executor is shutdown prematurely.
cleanup();
}
void PulseAudioBluetoothInitializer::run() {
ACSDK_DEBUG5(LX(__func__));
/*
* pa_threaded_mainloop_new creates a separate thread that PulseAudio uses for callbacks.
* Do this so we can block and wait on the main thread and terminate early on error conditions.
*/
m_paLoop = pa_threaded_mainloop_new();
// Owned by m_paLoop, do not need to free.
pa_mainloop_api* mainLoopApi = pa_threaded_mainloop_get_api(m_paLoop);
m_context = pa_context_new(mainLoopApi, "Application to unload and reload Pulse Audio BT modules");
pa_context_set_state_callback(m_context, &PulseAudioBluetoothInitializer::onStateChanged, this);
pa_context_connect(m_context, NULL, PA_CONTEXT_NOFLAGS, NULL);
if (pa_threaded_mainloop_start(m_paLoop) < 0) {
ACSDK_ERROR(LX("runFailed").d("reason", "runningMainLoopFailed"));
cleanup();
return;
}
{
std::unique_lock<std::mutex> lock(m_mutex);
if (std::cv_status::timeout == m_mainThreadCv.wait_for(lock, TIMEOUT) || !m_connected) {
cleanup();
return;
}
}
/*
* Get a list of modules. If we find module-bluetooth-discover and module-bluetooth-policy already loaded, we will
* unload them.
*/
pa_context_get_module_info_list(m_context, &PulseAudioBluetoothInitializer::onModuleFound, this);
{
std::unique_lock<std::mutex> lock(m_mutex);
if (m_mainThreadCv.wait_for(lock, TIMEOUT, [this] {
return ModuleState::UNLOADED == m_policyState && ModuleState::UNLOADED == m_discoverState;
})) {
ACSDK_DEBUG(LX(__func__).d("success", "bluetoothModulesUnloaded"));
} else {
ACSDK_ERROR(LX("runFailed").d("reason", "unloadModulesFailed"));
cleanup();
return;
}
}
// (Re)load the modules.
pa_context_load_module(
m_context, BLUETOOTH_POLICY.c_str(), nullptr, &PulseAudioBluetoothInitializer::onLoadPolicyResult, this);
pa_context_load_module(
m_context, BLUETOOTH_DISCOVER.c_str(), nullptr, &PulseAudioBluetoothInitializer::onLoadDiscoverResult, this);
{
std::unique_lock<std::mutex> lock(m_mutex);
if (m_mainThreadCv.wait_for(lock, TIMEOUT, [this] {
return ModuleState::LOADED_BY_SDK == m_policyState && ModuleState::LOADED_BY_SDK == m_discoverState;
})) {
ACSDK_DEBUG(LX(__func__).d("reason", "loadModulesSuccesful"));
} else {
ACSDK_ERROR(LX("runFailed").d("reason", "loadModulesFailed"));
cleanup();
return;
}
}
ACSDK_DEBUG(LX(__func__).m("Reloading PulseAudio Bluetooth Modules Successful"));
cleanup();
}
void PulseAudioBluetoothInitializer::onEventFired(const BluetoothEvent& event) {
ACSDK_DEBUG5(LX(__func__));
if (BluetoothEventType::BLUETOOTH_DEVICE_MANAGER_INITIALIZED != event.getType()) {
ACSDK_ERROR(LX("onEventFiredFailed").d("reason", "unexpectedEventReceived"));
return;
}
m_executor.submit([this] {
if (!m_paLoopStarted) {
m_paLoopStarted = true;
run();
} else {
ACSDK_WARN(LX(__func__).d("reason", "loopAlreadyStarted"));
}
});
}
} // namespace blueZ
} // namespace bluetoothImplementations
} // namespace alexaClientSDK