| /****************************************************************************** |
| * |
| * Copyright 2019 HIMSA II K/S - www.himsa.com. Represented by EHIMA - |
| * www.ehima.com |
| * |
| * 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 "client_audio.h" |
| |
| #include "audio_hal_interface/le_audio_software.h" |
| #include "btu.h" |
| #include "common/repeating_timer.h" |
| #include "common/time_util.h" |
| #include "osi/include/wakelock.h" |
| |
| using bluetooth::audio::le_audio::LeAudioClientInterface; |
| |
| namespace { |
| LeAudioCodecConfiguration source_codec_config; |
| bluetooth::common::RepeatingTimer audio_timer; |
| LeAudioClientInterface* leAudioClientInterface = nullptr; |
| LeAudioClientInterface::Sink* sinkClientInterface = nullptr; |
| LeAudioClientInterface::Source* sourceClientInterface = nullptr; |
| LeAudioClientAudioSinkReceiver* localAudioSinkReceiver = nullptr; |
| LeAudioClientAudioSourceReceiver* localAudioSourceReceiver = nullptr; |
| |
| enum { |
| HAL_UNINITIALIZED, |
| HAL_STOPPED, |
| HAL_STARTED, |
| } le_audio_sink_hal_state, |
| le_audio_source_hal_state; |
| |
| struct AudioHalStats { |
| size_t media_read_total_underflow_bytes; |
| size_t media_read_total_underflow_count; |
| uint64_t media_read_last_underflow_us; |
| |
| AudioHalStats() { Reset(); } |
| |
| void Reset() { |
| media_read_total_underflow_bytes = 0; |
| media_read_total_underflow_count = 0; |
| media_read_last_underflow_us = 0; |
| } |
| }; |
| |
| AudioHalStats stats; |
| |
| bool le_audio_sink_on_resume_req(bool start_media_task); |
| bool le_audio_sink_on_suspend_req(); |
| |
| void send_audio_data() { |
| uint32_t bytes_per_tick = |
| (source_codec_config.num_channels * source_codec_config.sample_rate * |
| source_codec_config.data_interval_us / 1000 * |
| (source_codec_config.bits_per_sample / 8)) / |
| 1000; |
| |
| std::vector<uint8_t> data(bytes_per_tick); |
| |
| uint32_t bytes_read = 0; |
| if (sinkClientInterface != nullptr) { |
| bytes_read = sinkClientInterface->Read(data.data(), bytes_per_tick); |
| } else { |
| LOG(ERROR) << __func__ << ", no LE Audio sink client interface - aborting."; |
| return; |
| } |
| |
| // LOG(INFO) << __func__ << ", bytes_read: " << static_cast<int>(bytes_read) |
| // << ", bytes_per_tick: " << static_cast<int>(bytes_per_tick); |
| |
| if (bytes_read < bytes_per_tick) { |
| stats.media_read_total_underflow_bytes += bytes_per_tick - bytes_read; |
| stats.media_read_total_underflow_count++; |
| stats.media_read_last_underflow_us = |
| bluetooth::common::time_get_os_boottime_us(); |
| } |
| |
| if (localAudioSinkReceiver != nullptr) { |
| localAudioSinkReceiver->OnAudioDataReady(data); |
| } |
| } |
| |
| void start_audio_ticks() { |
| wakelock_acquire(); |
| audio_timer.SchedulePeriodic( |
| get_main_thread()->GetWeakPtr(), FROM_HERE, base::Bind(&send_audio_data), |
| base::TimeDelta::FromMicroseconds(source_codec_config.data_interval_us)); |
| } |
| |
| void stop_audio_ticks() { |
| audio_timer.CancelAndWait(); |
| wakelock_release(); |
| } |
| |
| bool le_audio_sink_on_resume_req(bool start_media_task) { |
| if (localAudioSinkReceiver != nullptr) { |
| // Call OnAudioResume and block till it returns. |
| std::promise<void> do_resume_promise; |
| std::future<void> do_resume_future = do_resume_promise.get_future(); |
| bt_status_t status = do_in_main_thread( |
| FROM_HERE, |
| base::BindOnce(&LeAudioClientAudioSinkReceiver::OnAudioResume, |
| base::Unretained(localAudioSinkReceiver), |
| std::move(do_resume_promise))); |
| if (status == BT_STATUS_SUCCESS) { |
| do_resume_future.wait(); |
| } else { |
| LOG(ERROR) << __func__ |
| << ": LE_AUDIO_CTRL_CMD_START: do_in_main_thread err=" |
| << status; |
| return false; |
| } |
| } else { |
| LOG(ERROR) << __func__ |
| << ": LE_AUDIO_CTRL_CMD_START: audio sink receiver not started"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool le_audio_source_on_resume_req(bool start_media_task) { |
| if (localAudioSourceReceiver != nullptr) { |
| // Call OnAudioResume and block till it returns. |
| std::promise<void> do_resume_promise; |
| std::future<void> do_resume_future = do_resume_promise.get_future(); |
| bt_status_t status = do_in_main_thread( |
| FROM_HERE, |
| base::BindOnce(&LeAudioClientAudioSourceReceiver::OnAudioResume, |
| base::Unretained(localAudioSourceReceiver), |
| std::move(do_resume_promise))); |
| if (status == BT_STATUS_SUCCESS) { |
| do_resume_future.wait(); |
| } else { |
| LOG(ERROR) << __func__ |
| << ": LE_AUDIO_CTRL_CMD_START: do_in_main_thread err=" |
| << status; |
| return false; |
| } |
| } else { |
| LOG(ERROR) |
| << __func__ |
| << ": LE_AUDIO_CTRL_CMD_START: audio source receiver not started"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool le_audio_sink_on_suspend_req() { |
| stop_audio_ticks(); |
| if (localAudioSinkReceiver != nullptr) { |
| // Call OnAudioSuspend and block till it returns. |
| std::promise<void> do_suspend_promise; |
| std::future<void> do_suspend_future = do_suspend_promise.get_future(); |
| bt_status_t status = do_in_main_thread( |
| FROM_HERE, |
| base::BindOnce(&LeAudioClientAudioSinkReceiver::OnAudioSuspend, |
| base::Unretained(localAudioSinkReceiver), |
| std::move(do_suspend_promise))); |
| if (status == BT_STATUS_SUCCESS) { |
| do_suspend_future.wait(); |
| return true; |
| } else { |
| LOG(ERROR) << __func__ |
| << ": LE_AUDIO_CTRL_CMD_SUSPEND: do_in_main_thread err=" |
| << status; |
| } |
| } else { |
| LOG(ERROR) << __func__ |
| << ": LE_AUDIO_CTRL_CMD_SUSPEND: audio receiver not started"; |
| } |
| return false; |
| } |
| |
| bool le_audio_source_on_suspend_req() { |
| if (localAudioSourceReceiver != nullptr) { |
| // Call OnAudioSuspend and block till it returns. |
| std::promise<void> do_suspend_promise; |
| std::future<void> do_suspend_future = do_suspend_promise.get_future(); |
| bt_status_t status = do_in_main_thread( |
| FROM_HERE, |
| base::BindOnce(&LeAudioClientAudioSourceReceiver::OnAudioSuspend, |
| base::Unretained(localAudioSourceReceiver), |
| std::move(do_suspend_promise))); |
| if (status == BT_STATUS_SUCCESS) { |
| do_suspend_future.wait(); |
| return true; |
| } else { |
| LOG(ERROR) << __func__ |
| << ": LE_AUDIO_CTRL_CMD_SUSPEND: do_in_main_thread err=" |
| << status; |
| } |
| } else { |
| LOG(ERROR) << __func__ |
| << ": LE_AUDIO_CTRL_CMD_SUSPEND: audio receiver not started"; |
| } |
| return false; |
| } |
| |
| bool le_audio_sink_on_metadata_update_req( |
| const source_metadata_t& source_metadata) { |
| if (localAudioSinkReceiver == nullptr) { |
| LOG(ERROR) << __func__ << ", audio receiver not started"; |
| return false; |
| } |
| |
| // Call OnAudioSuspend and block till it returns. |
| std::promise<void> do_update_metadata_promise; |
| std::future<void> do_update_metadata_future = |
| do_update_metadata_promise.get_future(); |
| bt_status_t status = do_in_main_thread( |
| FROM_HERE, |
| base::BindOnce(&LeAudioClientAudioSinkReceiver::OnAudioMetadataUpdate, |
| base::Unretained(localAudioSinkReceiver), |
| std::move(do_update_metadata_promise), source_metadata)); |
| |
| if (status == BT_STATUS_SUCCESS) { |
| do_update_metadata_future.wait(); |
| return true; |
| } |
| |
| LOG(ERROR) << __func__ << ", do_in_main_thread err=" << status; |
| |
| return false; |
| } |
| |
| } // namespace |
| |
| bool LeAudioClientAudioSource::Start( |
| const LeAudioCodecConfiguration& codec_configuration, |
| LeAudioClientAudioSinkReceiver* audioReceiver) { |
| LOG(INFO) << __func__; |
| |
| if (!sinkClientInterface) { |
| LOG(ERROR) << "sinkClientInterface is not Acquired!"; |
| return false; |
| } |
| |
| if (le_audio_sink_hal_state == HAL_STARTED) { |
| LOG(ERROR) << "LE audio device HAL is already in use!"; |
| return false; |
| } |
| |
| LOG(INFO) << __func__ << ": Le Audio Source Open, bits per sample: " |
| << int{codec_configuration.bits_per_sample} |
| << ", num channels: " << int{codec_configuration.num_channels} |
| << ", sample rate: " << codec_configuration.sample_rate |
| << ", data interval: " << codec_configuration.data_interval_us; |
| |
| stats.Reset(); |
| |
| /* Global config for periodic audio data */ |
| source_codec_config = codec_configuration; |
| LeAudioClientInterface::PcmParameters pcmParameters = { |
| .data_interval_us = codec_configuration.data_interval_us, |
| .sample_rate = codec_configuration.sample_rate, |
| .bits_per_sample = codec_configuration.bits_per_sample, |
| .channels_count = codec_configuration.num_channels}; |
| |
| sinkClientInterface->SetPcmParameters(pcmParameters); |
| sinkClientInterface->StartSession(); |
| |
| localAudioSinkReceiver = audioReceiver; |
| le_audio_sink_hal_state = HAL_STARTED; |
| |
| return true; |
| } |
| |
| void LeAudioClientAudioSource::Stop() { |
| LOG(INFO) << __func__; |
| if (!sinkClientInterface) { |
| LOG(ERROR) << __func__ << " sinkClientInterface stopped"; |
| return; |
| } |
| |
| if (le_audio_sink_hal_state != HAL_STARTED) { |
| LOG(ERROR) << "LE audio device HAL was not started!"; |
| return; |
| } |
| |
| LOG(INFO) << __func__ << ": Le Audio Source Close"; |
| |
| sinkClientInterface->StopSession(); |
| le_audio_sink_hal_state = HAL_STOPPED; |
| localAudioSinkReceiver = nullptr; |
| |
| stop_audio_ticks(); |
| } |
| |
| const void* LeAudioClientAudioSource::Acquire() { |
| LOG(INFO) << __func__; |
| if (sinkClientInterface != nullptr) { |
| LOG(WARNING) << __func__ << ", Sink client interface already initialized"; |
| return nullptr; |
| } |
| |
| /* Get pointer to singleton LE audio client interface */ |
| if (leAudioClientInterface == nullptr) { |
| leAudioClientInterface = LeAudioClientInterface::Get(); |
| |
| if (leAudioClientInterface == nullptr) { |
| LOG(ERROR) << __func__ << ", can't get LE audio client interface"; |
| return nullptr; |
| } |
| } |
| |
| auto sink_stream_cb = bluetooth::audio::le_audio::StreamCallbacks{ |
| .on_resume_ = le_audio_sink_on_resume_req, |
| .on_suspend_ = le_audio_sink_on_suspend_req, |
| .on_metadata_update_ = le_audio_sink_on_metadata_update_req, |
| }; |
| |
| sinkClientInterface = |
| leAudioClientInterface->GetSink(sink_stream_cb, get_main_thread()); |
| |
| if (sinkClientInterface == nullptr) { |
| LOG(ERROR) << __func__ << ", can't get LE audio sink client interface"; |
| return nullptr; |
| } |
| |
| le_audio_sink_hal_state = HAL_STOPPED; |
| return sinkClientInterface; |
| } |
| |
| void LeAudioClientAudioSource::Release(const void* instance) { |
| LOG(INFO) << __func__; |
| if (sinkClientInterface != instance) { |
| LOG(WARNING) << "Trying to release not own session"; |
| return; |
| } |
| |
| if (le_audio_sink_hal_state == HAL_UNINITIALIZED) { |
| LOG(WARNING) << "LE audio device HAL is not running."; |
| return; |
| } |
| |
| sinkClientInterface->Cleanup(); |
| leAudioClientInterface->ReleaseSink(sinkClientInterface); |
| le_audio_sink_hal_state = HAL_UNINITIALIZED; |
| sinkClientInterface = nullptr; |
| } |
| |
| void LeAudioClientAudioSource::ConfirmStreamingRequest() { |
| LOG(INFO) << __func__; |
| if ((sinkClientInterface == nullptr) || |
| (le_audio_sink_hal_state != HAL_STARTED)) { |
| LOG(ERROR) << "LE audio device HAL was not started!"; |
| return; |
| } |
| |
| sinkClientInterface->ConfirmStreamingRequest(); |
| LOG(INFO) << __func__ << ", start_audio_ticks"; |
| start_audio_ticks(); |
| } |
| |
| void LeAudioClientAudioSource::CancelStreamingRequest() { |
| LOG(INFO) << __func__; |
| if ((sinkClientInterface == nullptr) || |
| (le_audio_sink_hal_state != HAL_STARTED)) { |
| LOG(ERROR) << "LE audio device HAL was not started!"; |
| return; |
| } |
| |
| sinkClientInterface->CancelStreamingRequest(); |
| } |
| |
| void LeAudioClientAudioSource::UpdateRemoteDelay(uint16_t remote_delay_ms) { |
| LOG(INFO) << __func__; |
| if ((sinkClientInterface == nullptr) || |
| (le_audio_sink_hal_state != HAL_STARTED)) { |
| LOG(ERROR) << "LE audio device HAL was not started!"; |
| return; |
| } |
| |
| sinkClientInterface->SetRemoteDelay(remote_delay_ms); |
| } |
| |
| void LeAudioClientAudioSource::DebugDump(int fd) { |
| uint64_t now_us = bluetooth::common::time_get_os_boottime_us(); |
| std::stringstream stream; |
| stream << " Le Audio Audio HAL:" |
| << "\n Counts (underflow) : " |
| << stats.media_read_total_underflow_count |
| << "\n Bytes (underflow) : " |
| << stats.media_read_total_underflow_bytes |
| << "\n Last update time ago in ms (underflow) : " |
| << (stats.media_read_last_underflow_us > 0 |
| ? (unsigned long long)(now_us - |
| stats.media_read_last_underflow_us) / |
| 1000 |
| : 0) |
| << std::endl; |
| dprintf(fd, "%s", stream.str().c_str()); |
| } |
| |
| bool LeAudioClientAudioSink::Start( |
| const LeAudioCodecConfiguration& codec_configuration, |
| LeAudioClientAudioSourceReceiver* audioReceiver) { |
| LOG(INFO) << __func__; |
| if (!sourceClientInterface) { |
| LOG(ERROR) << "sourceClientInterface is not Acquired!"; |
| return false; |
| } |
| |
| if (le_audio_source_hal_state == HAL_STARTED) { |
| LOG(ERROR) << "LE audio device HAL is already in use!"; |
| return false; |
| } |
| |
| LOG(INFO) << __func__ << ": Le Audio Sink Open, bit rate: " |
| << codec_configuration.bits_per_sample |
| << ", num channels: " << int{codec_configuration.num_channels} |
| << ", sample rate: " << codec_configuration.sample_rate |
| << ", data interval: " << codec_configuration.data_interval_us; |
| |
| LeAudioClientInterface::PcmParameters pcmParameters = { |
| .data_interval_us = codec_configuration.data_interval_us, |
| .sample_rate = codec_configuration.sample_rate, |
| .bits_per_sample = codec_configuration.bits_per_sample, |
| .channels_count = codec_configuration.num_channels}; |
| |
| sourceClientInterface->SetPcmParameters(pcmParameters); |
| sourceClientInterface->StartSession(); |
| |
| localAudioSourceReceiver = audioReceiver; |
| le_audio_source_hal_state = HAL_STARTED; |
| return true; |
| } |
| |
| void LeAudioClientAudioSink::Stop() { |
| LOG(INFO) << __func__; |
| if (!sourceClientInterface) { |
| LOG(ERROR) << __func__ << " sourceClientInterface stopped"; |
| return; |
| } |
| |
| if (le_audio_source_hal_state != HAL_STARTED) { |
| LOG(ERROR) << "LE audio device HAL was not started!"; |
| return; |
| } |
| |
| LOG(INFO) << __func__ << ": Le Audio Sink Close"; |
| |
| sourceClientInterface->StopSession(); |
| le_audio_source_hal_state = HAL_STOPPED; |
| localAudioSourceReceiver = nullptr; |
| } |
| |
| const void* LeAudioClientAudioSink::Acquire() { |
| LOG(INFO) << __func__; |
| if (sourceClientInterface != nullptr) { |
| LOG(WARNING) << __func__ << ", Source client interface already initialized"; |
| return nullptr; |
| } |
| |
| /* Get pointer to singleton LE audio client interface */ |
| if (leAudioClientInterface == nullptr) { |
| leAudioClientInterface = LeAudioClientInterface::Get(); |
| |
| if (leAudioClientInterface == nullptr) { |
| LOG(ERROR) << __func__ << ", can't get LE audio client interface"; |
| return nullptr; |
| } |
| } |
| |
| auto source_stream_cb = bluetooth::audio::le_audio::StreamCallbacks{ |
| .on_resume_ = le_audio_source_on_resume_req, |
| .on_suspend_ = le_audio_source_on_suspend_req, |
| }; |
| |
| sourceClientInterface = |
| leAudioClientInterface->GetSource(source_stream_cb, get_main_thread()); |
| |
| if (sourceClientInterface == nullptr) { |
| LOG(ERROR) << __func__ << ", can't get LE audio source client interface"; |
| return nullptr; |
| } |
| |
| le_audio_source_hal_state = HAL_STOPPED; |
| return sourceClientInterface; |
| } |
| |
| void LeAudioClientAudioSink::Release(const void* instance) { |
| LOG(INFO) << __func__; |
| if (sourceClientInterface != instance) { |
| LOG(WARNING) << "Trying to release not own session"; |
| return; |
| } |
| |
| if (le_audio_source_hal_state == HAL_UNINITIALIZED) { |
| LOG(WARNING) << ", LE audio device source HAL is not running."; |
| return; |
| } |
| |
| sourceClientInterface->Cleanup(); |
| leAudioClientInterface->ReleaseSource(sourceClientInterface); |
| le_audio_source_hal_state = HAL_UNINITIALIZED; |
| sourceClientInterface = nullptr; |
| } |
| |
| size_t LeAudioClientAudioSink::SendData(uint8_t* data, uint16_t size) { |
| size_t bytes_written; |
| if (!sourceClientInterface) { |
| LOG(ERROR) << "sourceClientInterface not initialized!"; |
| return 0; |
| } |
| |
| if (le_audio_source_hal_state != HAL_STARTED) { |
| LOG(ERROR) << "LE audio device HAL was not started!"; |
| return 0; |
| } |
| |
| /* TODO: What to do if not all data is written ? */ |
| bytes_written = sourceClientInterface->Write(data, size); |
| if (bytes_written != size) |
| LOG(ERROR) << ", Not all data is written to source HAL. bytes written: " |
| << static_cast<int>(bytes_written) |
| << ", total: " << static_cast<int>(size); |
| |
| return bytes_written; |
| } |
| |
| void LeAudioClientAudioSink::ConfirmStreamingRequest() { |
| LOG(INFO) << __func__; |
| if ((sourceClientInterface == nullptr) || |
| (le_audio_source_hal_state != HAL_STARTED)) { |
| LOG(ERROR) << "LE audio device HAL was not started!"; |
| return; |
| } |
| |
| sourceClientInterface->ConfirmStreamingRequest(); |
| } |
| |
| void LeAudioClientAudioSink::CancelStreamingRequest() { |
| LOG(INFO) << __func__; |
| if ((sourceClientInterface == nullptr) || |
| (le_audio_source_hal_state != HAL_STARTED)) { |
| LOG(ERROR) << "LE audio device HAL was not started!"; |
| return; |
| } |
| |
| sourceClientInterface->CancelStreamingRequest(); |
| } |
| |
| void LeAudioClientAudioSink::UpdateRemoteDelay(uint16_t remote_delay_ms) { |
| if ((sourceClientInterface == nullptr) || |
| (le_audio_source_hal_state != HAL_STARTED)) { |
| LOG(ERROR) << "LE audio device HAL was not started!"; |
| return; |
| } |
| |
| sourceClientInterface->SetRemoteDelay(remote_delay_ms); |
| } |
| |
| void LeAudioClientAudioSink::DebugDump(int fd) { |
| /* TODO: Add some statistic for source client interface */ |
| } |