| /****************************************************************************** |
| * |
| * Copyright (C) 2017 Google, Inc. |
| * |
| * 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 LOG_TAG "btif_a2dp_audio_interface" |
| |
| #include "btif_a2dp_audio_interface.h" |
| |
| #include <mutex> |
| |
| #include <a2dp_vendor.h> |
| #include <a2dp_vendor_ldac_constants.h> |
| #include <android/hardware/bluetooth/a2dp/1.0/IBluetoothAudioHost.h> |
| #include <android/hardware/bluetooth/a2dp/1.0/IBluetoothAudioOffload.h> |
| #include <android/hardware/bluetooth/a2dp/1.0/types.h> |
| #include <base/logging.h> |
| #include <hwbinder/ProcessState.h> |
| #include <utils/RefBase.h> |
| #include "a2dp_sbc.h" |
| #include "bt_common.h" |
| #include "bta/av/bta_av_int.h" |
| #include "btif_a2dp.h" |
| #include "btif_a2dp_control.h" |
| #include "btif_a2dp_sink.h" |
| #include "btif_a2dp_source.h" |
| #include "btif_av.h" |
| #include "btif_av_co.h" |
| #include "btif_hf.h" |
| #include "osi/include/metrics.h" |
| #include "osi/include/osi.h" |
| |
| using system_bt_osi::A2dpSessionMetrics; |
| using system_bt_osi::BluetoothMetricsLogger; |
| |
| using android::hardware::bluetooth::a2dp::V1_0::IBluetoothAudioOffload; |
| using android::hardware::bluetooth::a2dp::V1_0::IBluetoothAudioHost; |
| using android::hardware::bluetooth::a2dp::V1_0::Status; |
| using android::hardware::bluetooth::a2dp::V1_0::CodecConfiguration; |
| using android::hardware::bluetooth::a2dp::V1_0::CodecType; |
| using android::hardware::bluetooth::a2dp::V1_0::SampleRate; |
| using android::hardware::bluetooth::a2dp::V1_0::BitsPerSample; |
| using android::hardware::bluetooth::a2dp::V1_0::ChannelMode; |
| using android::hardware::ProcessState; |
| using ::android::hardware::Return; |
| using ::android::hardware::Void; |
| using ::android::hardware::hidl_death_recipient; |
| using ::android::hardware::hidl_vec; |
| using ::android::sp; |
| using ::android::wp; |
| android::sp<IBluetoothAudioOffload> btAudio; |
| |
| #define CASE_RETURN_STR(const) \ |
| case const: \ |
| return #const; |
| static uint8_t a2dp_cmd_pending = A2DP_CTRL_CMD_NONE; |
| static Status mapToStatus(uint8_t resp); |
| uint8_t btif_a2dp_audio_process_request(uint8_t cmd); |
| |
| static void btif_a2dp_audio_send_start_req(); |
| static void btif_a2dp_audio_send_suspend_req(); |
| static void btif_a2dp_audio_send_stop_req(); |
| static void btif_a2dp_audio_interface_init(); |
| static void btif_a2dp_audio_interface_deinit(); |
| static void btif_a2dp_audio_interface_restart_session(); |
| // Delay reporting |
| // static void btif_a2dp_audio_send_sink_latency(); |
| |
| class A2dpOffloadAudioStats { |
| public: |
| A2dpOffloadAudioStats() { Reset(); } |
| void Reset() { |
| std::lock_guard<std::recursive_mutex> lock(lock_); |
| ResetPreserveSession(); |
| codec_index_ = -1; |
| } |
| void ResetPreserveSession() { |
| std::lock_guard<std::recursive_mutex> lock(lock_); |
| audio_start_time_ms_ = -1; |
| audio_stop_time_ms_ = -1; |
| } |
| void StoreMetrics() { |
| std::lock_guard<std::recursive_mutex> lock(lock_); |
| if (audio_start_time_ms_ < 0 || audio_stop_time_ms_ < 0) { |
| return; |
| } |
| A2dpSessionMetrics metrics; |
| metrics.codec_index = codec_index_; |
| metrics.is_a2dp_offload = true; |
| if (audio_stop_time_ms_ > audio_start_time_ms_) { |
| metrics.audio_duration_ms = audio_stop_time_ms_ - audio_start_time_ms_; |
| } |
| BluetoothMetricsLogger::GetInstance()->LogA2dpSession(metrics); |
| } |
| void LogAudioStart() { |
| std::lock_guard<std::recursive_mutex> lock(lock_); |
| audio_start_time_ms_ = time_get_os_boottime_ms(); |
| } |
| void LogAudioStop() { |
| std::lock_guard<std::recursive_mutex> lock(lock_); |
| audio_stop_time_ms_ = time_get_os_boottime_ms(); |
| } |
| void LogAudioStopMetricsAndReset() { |
| std::lock_guard<std::recursive_mutex> lock(lock_); |
| LogAudioStop(); |
| StoreMetrics(); |
| ResetPreserveSession(); |
| } |
| void SetCodecIndex(int64_t codec_index) { |
| std::lock_guard<std::recursive_mutex> lock(lock_); |
| codec_index_ = codec_index; |
| } |
| |
| private: |
| std::recursive_mutex lock_; |
| int64_t audio_start_time_ms_ = -1; |
| int64_t audio_stop_time_ms_ = -1; |
| int64_t codec_index_ = -1; |
| }; |
| |
| static A2dpOffloadAudioStats a2dp_offload_audio_stats; |
| |
| class BluetoothAudioHost : public IBluetoothAudioHost { |
| public: |
| Return<void> startStream() { |
| btif_a2dp_audio_send_start_req(); |
| return Void(); |
| } |
| Return<void> suspendStream() { |
| btif_a2dp_audio_send_suspend_req(); |
| return Void(); |
| } |
| Return<void> stopStream() { |
| btif_a2dp_audio_send_stop_req(); |
| return Void(); |
| } |
| |
| // TODO : Delay reporting |
| /* Return<void> a2dp_get_sink_latency() { |
| LOG_INFO(LOG_TAG,"%s:start ", __func__); |
| btif_a2dp_audio_send_sink_latency(); |
| return Void(); |
| }*/ |
| }; |
| |
| class BluetoothAudioDeathRecipient : public hidl_death_recipient { |
| public: |
| virtual void serviceDied( |
| uint64_t /*cookie*/, |
| const wp<::android::hidl::base::V1_0::IBase>& /*who*/) { |
| LOG_ERROR(LOG_TAG, "%s", __func__); |
| // Restart the session on the correct thread |
| do_in_bta_thread(FROM_HERE, |
| base::Bind(&btif_a2dp_audio_interface_restart_session)); |
| } |
| }; |
| sp<BluetoothAudioDeathRecipient> bluetoothAudioDeathRecipient = |
| new BluetoothAudioDeathRecipient(); |
| |
| static Status mapToStatus(uint8_t resp) { |
| switch (resp) { |
| case A2DP_CTRL_ACK_SUCCESS: |
| return Status::SUCCESS; |
| break; |
| case A2DP_CTRL_ACK_PENDING: |
| return Status::PENDING; |
| break; |
| case A2DP_CTRL_ACK_FAILURE: |
| case A2DP_CTRL_ACK_INCALL_FAILURE: |
| case A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS: |
| return Status::FAILURE; |
| default: |
| APPL_TRACE_WARNING("%s: unknown status recevied :%d", __func__, resp); |
| return Status::FAILURE; |
| break; |
| } |
| } |
| |
| static void btif_a2dp_get_codec_configuration( |
| CodecConfiguration* p_codec_info) { |
| LOG_INFO(LOG_TAG, "%s", __func__); |
| tBT_A2DP_OFFLOAD a2dp_offload; |
| A2dpCodecConfig* a2dpCodecConfig = bta_av_get_a2dp_current_codec(); |
| a2dpCodecConfig->getCodecSpecificConfig(&a2dp_offload); |
| btav_a2dp_codec_config_t codec_config; |
| codec_config = a2dpCodecConfig->getCodecConfig(); |
| a2dp_offload_audio_stats.SetCodecIndex(a2dpCodecConfig->codecIndex()); |
| switch (codec_config.codec_type) { |
| case BTAV_A2DP_CODEC_INDEX_SOURCE_SBC: |
| p_codec_info->codecType = |
| (::android::hardware::bluetooth::a2dp::V1_0::CodecType) |
| BTA_AV_CODEC_TYPE_SBC; |
| p_codec_info->codecSpecific.sbcData.codecParameters = |
| a2dp_offload.codec_info[0]; |
| LOG_INFO(LOG_TAG, " %s: codec parameters =%d", __func__, |
| a2dp_offload.codec_info[0]); |
| p_codec_info->codecSpecific.sbcData.minBitpool = |
| a2dp_offload.codec_info[1]; |
| p_codec_info->codecSpecific.sbcData.maxBitpool = |
| a2dp_offload.codec_info[2]; |
| break; |
| case BTAV_A2DP_CODEC_INDEX_SOURCE_AAC: |
| p_codec_info->codecType = |
| (::android::hardware::bluetooth::a2dp::V1_0::CodecType) |
| BTA_AV_CODEC_TYPE_AAC; |
| break; |
| case BTAV_A2DP_CODEC_INDEX_SOURCE_APTX: |
| p_codec_info->codecType = |
| (::android::hardware::bluetooth::a2dp::V1_0::CodecType) |
| BTA_AV_CODEC_TYPE_APTX; |
| break; |
| case BTAV_A2DP_CODEC_INDEX_SOURCE_APTX_HD: |
| p_codec_info->codecType = |
| (::android::hardware::bluetooth::a2dp::V1_0::CodecType) |
| BTA_AV_CODEC_TYPE_APTXHD; |
| break; |
| case BTAV_A2DP_CODEC_INDEX_SOURCE_LDAC: |
| p_codec_info->codecType = |
| (::android::hardware::bluetooth::a2dp::V1_0::CodecType) |
| BTA_AV_CODEC_TYPE_LDAC; |
| p_codec_info->codecSpecific.ldacData.bitrateIndex = |
| a2dp_offload.codec_info[6]; |
| break; |
| default: |
| APPL_TRACE_ERROR("%s: Unknown Codec type :%d ", __func__, |
| codec_config.codec_type); |
| } |
| |
| // Obtain the MTU |
| RawAddress peer_addr = btif_av_source_active_peer(); |
| tA2DP_ENCODER_INIT_PEER_PARAMS peer_param; |
| bta_av_co_get_peer_params(peer_addr, &peer_param); |
| int effectiveMtu = a2dpCodecConfig->getEffectiveMtu(); |
| if (effectiveMtu > 0 && effectiveMtu < peer_param.peer_mtu) { |
| p_codec_info->peerMtu = effectiveMtu; |
| } else { |
| p_codec_info->peerMtu = peer_param.peer_mtu; |
| } |
| LOG_INFO(LOG_TAG, "%s: peer MTU: %d effective MTU: %d result MTU: %d", |
| __func__, peer_param.peer_mtu, effectiveMtu, p_codec_info->peerMtu); |
| |
| p_codec_info->sampleRate = |
| (::android::hardware::bluetooth::a2dp::V1_0::SampleRate) |
| codec_config.sample_rate; |
| p_codec_info->bitsPerSample = |
| (::android::hardware::bluetooth::a2dp::V1_0::BitsPerSample) |
| codec_config.bits_per_sample; |
| p_codec_info->channelMode = |
| (::android::hardware::bluetooth::a2dp::V1_0::ChannelMode) |
| codec_config.channel_mode; |
| p_codec_info->encodedAudioBitrate = a2dpCodecConfig->getTrackBitRate(); |
| } |
| |
| static void btif_a2dp_audio_interface_init() { |
| LOG_INFO(LOG_TAG, "%s", __func__); |
| |
| btAudio = IBluetoothAudioOffload::getService(); |
| CHECK(btAudio != nullptr); |
| |
| auto death_link = btAudio->linkToDeath(bluetoothAudioDeathRecipient, 0); |
| if (!death_link.isOk()) { |
| LOG_ERROR(LOG_TAG, "%s: Cannot observe the Bluetooth Audio HAL's death", |
| __func__); |
| } |
| |
| LOG_DEBUG( |
| LOG_TAG, "%s: IBluetoothAudioOffload::getService() returned %p (%s)", |
| __func__, btAudio.get(), (btAudio->isRemote() ? "remote" : "local")); |
| |
| LOG_INFO(LOG_TAG, "%s:Init returned", __func__); |
| } |
| |
| static void btif_a2dp_audio_interface_deinit() { |
| LOG_INFO(LOG_TAG, "%s: start", __func__); |
| if (btAudio != nullptr) { |
| auto death_unlink = btAudio->unlinkToDeath(bluetoothAudioDeathRecipient); |
| if (!death_unlink.isOk()) { |
| LOG_ERROR(LOG_TAG, |
| "%s: Error unlinking death observer from Bluetooth Audio HAL", |
| __func__); |
| } |
| } |
| btAudio = nullptr; |
| } |
| |
| void btif_a2dp_audio_interface_start_session() { |
| LOG_INFO(LOG_TAG, "%s", __func__); |
| BluetoothMetricsLogger::GetInstance()->LogBluetoothSessionStart( |
| system_bt_osi::CONNECTION_TECHNOLOGY_TYPE_BREDR, 0); |
| a2dp_offload_audio_stats.Reset(); |
| btif_a2dp_audio_interface_init(); |
| CHECK(btAudio != nullptr); |
| CodecConfiguration codec_info; |
| btif_a2dp_get_codec_configuration(&codec_info); |
| android::sp<IBluetoothAudioHost> host_if = new BluetoothAudioHost(); |
| btAudio->startSession(host_if, codec_info); |
| } |
| |
| void btif_a2dp_audio_interface_end_session() { |
| LOG_INFO(LOG_TAG, "%s", __func__); |
| a2dp_offload_audio_stats.LogAudioStopMetricsAndReset(); |
| BluetoothMetricsLogger::GetInstance()->LogBluetoothSessionEnd( |
| system_bt_osi::DISCONNECT_REASON_UNKNOWN, 0); |
| a2dp_offload_audio_stats.Reset(); |
| if (btAudio == nullptr) return; |
| auto ret = btAudio->endSession(); |
| if (!ret.isOk()) { |
| LOG_ERROR(LOG_TAG, "HAL server is dead"); |
| } |
| btif_a2dp_audio_interface_deinit(); |
| } |
| |
| // Conditionally restart the session only if it was started before |
| static void btif_a2dp_audio_interface_restart_session() { |
| LOG_INFO(LOG_TAG, "%s", __func__); |
| if (btAudio == nullptr) { |
| LOG_INFO(LOG_TAG, "%s: nothing to restart - session was not started", |
| __func__); |
| return; |
| } |
| btAudio = nullptr; |
| btif_a2dp_audio_interface_start_session(); |
| } |
| |
| void btif_a2dp_audio_on_started(tBTA_AV_STATUS status) { |
| LOG_INFO(LOG_TAG, "%s: status = %d", __func__, status); |
| if (btAudio != nullptr) { |
| if (a2dp_cmd_pending == A2DP_CTRL_CMD_START) { |
| LOG_INFO(LOG_TAG, "%s: calling method onStarted", __func__); |
| auto hal_status = mapToStatus(status); |
| btAudio->streamStarted(hal_status); |
| if (hal_status == Status::SUCCESS) { |
| a2dp_offload_audio_stats.LogAudioStart(); |
| } |
| } |
| } |
| } |
| |
| void btif_a2dp_audio_on_suspended(tBTA_AV_STATUS status) { |
| LOG_INFO(LOG_TAG, "%s: status = %d", __func__, status); |
| if (btAudio != nullptr) { |
| if (a2dp_cmd_pending == A2DP_CTRL_CMD_SUSPEND) { |
| LOG_INFO(LOG_TAG, "calling method onSuspended"); |
| auto hal_status = mapToStatus(status); |
| btAudio->streamSuspended(hal_status); |
| if (hal_status == Status::SUCCESS) { |
| a2dp_offload_audio_stats.LogAudioStopMetricsAndReset(); |
| } |
| } |
| } |
| } |
| |
| void btif_a2dp_audio_on_stopped(tBTA_AV_STATUS status) { |
| LOG_INFO(LOG_TAG, "%s: status = %d", __func__, status); |
| if (btAudio != nullptr && a2dp_cmd_pending == A2DP_CTRL_CMD_START) { |
| LOG_INFO(LOG_TAG, "%s: Remote disconnected when start under progress", |
| __func__); |
| btAudio->streamStarted(mapToStatus(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS)); |
| a2dp_offload_audio_stats.LogAudioStopMetricsAndReset(); |
| } |
| } |
| void btif_a2dp_audio_send_start_req() { |
| LOG_INFO(LOG_TAG, "%s", __func__); |
| uint8_t resp; |
| resp = btif_a2dp_audio_process_request(A2DP_CTRL_CMD_START); |
| if (btAudio != nullptr) { |
| auto status = mapToStatus(resp); |
| auto ret = btAudio->streamStarted(status); |
| if (status == Status::SUCCESS) { |
| a2dp_offload_audio_stats.LogAudioStart(); |
| } |
| if (!ret.isOk()) LOG_ERROR(LOG_TAG, "HAL server died"); |
| } |
| } |
| void btif_a2dp_audio_send_suspend_req() { |
| LOG_INFO(LOG_TAG, "%s", __func__); |
| uint8_t resp; |
| resp = btif_a2dp_audio_process_request(A2DP_CTRL_CMD_SUSPEND); |
| if (btAudio != nullptr) { |
| auto status = mapToStatus(resp); |
| auto ret = btAudio->streamSuspended(status); |
| if (status == Status::SUCCESS) { |
| a2dp_offload_audio_stats.LogAudioStopMetricsAndReset(); |
| } |
| if (!ret.isOk()) LOG_ERROR(LOG_TAG, "HAL server died"); |
| } |
| } |
| |
| void btif_a2dp_audio_send_stop_req() { |
| LOG_INFO(LOG_TAG, "%s", __func__); |
| btif_a2dp_audio_process_request(A2DP_CTRL_CMD_STOP); |
| a2dp_offload_audio_stats.LogAudioStopMetricsAndReset(); |
| } |
| |
| /*void btif_a2dp_audio_send_sink_latency() |
| { |
| LOG_INFO(LOG_TAG, "%s", __func__); |
| uint16_t sink_latency = btif_av_get_sink_latency(); |
| if (btAudio != nullptr) { |
| auto ret = btAudio->a2dp_on_get_sink_latency(sink_latency); |
| if (!ret.isOk()) LOG_ERROR(LOG_TAG, "server died"); |
| } |
| }*/ |
| |
| uint8_t btif_a2dp_audio_process_request(uint8_t cmd) { |
| LOG_INFO(LOG_TAG, "%s: cmd: %s", __func__, |
| audio_a2dp_hw_dump_ctrl_event((tA2DP_CTRL_CMD)cmd)); |
| a2dp_cmd_pending = cmd; |
| uint8_t status; |
| switch (cmd) { |
| case A2DP_CTRL_CMD_START: |
| /* |
| * Don't send START request to stack while we are in a call. |
| * Some headsets such as "Sony MW600", don't allow AVDTP START |
| * while in a call, and respond with BAD_STATE. |
| */ |
| if (!bluetooth::headset::IsCallIdle()) { |
| APPL_TRACE_WARNING("%s: A2DP command %s failed as call state is busy", |
| __func__, |
| audio_a2dp_hw_dump_ctrl_event((tA2DP_CTRL_CMD)cmd)); |
| status = A2DP_CTRL_ACK_INCALL_FAILURE; |
| break; |
| } |
| if (btif_av_stream_started_ready()) { |
| /* |
| * Already started, setup audio data channel listener and ACK |
| * back immediately. |
| */ |
| status = A2DP_CTRL_ACK_SUCCESS; |
| break; |
| } |
| if (btif_av_stream_ready()) { |
| /* |
| * Post start event and wait for audio path to open. |
| * If we are the source, the ACK will be sent after the start |
| * procedure is completed, othewise send it now. |
| */ |
| btif_av_stream_start(); |
| if (btif_av_get_peer_sep() == AVDT_TSEP_SRC) { |
| status = A2DP_CTRL_ACK_SUCCESS; |
| break; |
| } |
| /*Return pending and ack when start stream cfm received from remote*/ |
| status = A2DP_CTRL_ACK_PENDING; |
| break; |
| } |
| |
| APPL_TRACE_WARNING("%s: A2DP command %s while AV stream is not ready", |
| __func__, |
| audio_a2dp_hw_dump_ctrl_event((tA2DP_CTRL_CMD)cmd)); |
| return A2DP_CTRL_ACK_FAILURE; |
| break; |
| |
| case A2DP_CTRL_CMD_STOP: |
| if (btif_av_get_peer_sep() == AVDT_TSEP_SNK && |
| !btif_a2dp_source_is_streaming()) { |
| /* We are already stopped, just ack back */ |
| status = A2DP_CTRL_ACK_SUCCESS; |
| break; |
| } |
| btif_av_stream_stop(RawAddress::kEmpty); |
| return A2DP_CTRL_ACK_SUCCESS; |
| break; |
| |
| case A2DP_CTRL_CMD_SUSPEND: |
| /* Local suspend */ |
| if (btif_av_stream_started_ready()) { |
| btif_av_stream_suspend(); |
| status = A2DP_CTRL_ACK_PENDING; |
| break; |
| } |
| /* If we are not in started state, just ack back ok and let |
| * audioflinger close the channel. This can happen if we are |
| * remotely suspended, clear REMOTE SUSPEND flag. |
| */ |
| btif_av_clear_remote_suspend_flag(); |
| status = A2DP_CTRL_ACK_SUCCESS; |
| break; |
| |
| case A2DP_CTRL_CMD_OFFLOAD_START: |
| btif_av_stream_start_offload(); |
| status = A2DP_CTRL_ACK_PENDING; |
| break; |
| |
| default: |
| APPL_TRACE_ERROR("UNSUPPORTED CMD (%d)", cmd); |
| status = A2DP_CTRL_ACK_FAILURE; |
| break; |
| } |
| LOG_INFO(LOG_TAG, "a2dp-ctrl-cmd : %s DONE returning status %d", |
| audio_a2dp_hw_dump_ctrl_event((tA2DP_CTRL_CMD)cmd), status); |
| return status; |
| } |