/* * Copyright 2018 The Android Open Source Project * * 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 "BTAudioProviderSession" #include "BluetoothAudioSession.h" #include #include namespace android { namespace bluetooth { namespace audio { using ::android::hardware::audio::common::V5_0::AudioContentType; using ::android::hardware::audio::common::V5_0::AudioUsage; using ::android::hardware::audio::common::V5_0::PlaybackTrackMetadata; using ::android::hardware::audio::common::V5_0::SourceMetadata; using ::android::hardware::bluetooth::audio::V2_0::CodecType; using ::android::hardware::bluetooth::audio::V2_0::TimeSpec; const CodecConfiguration BluetoothAudioSession::kInvalidCodecConfiguration = { .codecType = CodecType::UNKNOWN, .encodedAudioBitrate = 0x00000000, .peerMtu = 0xffff, .isScmstEnabled = false, .config = {}}; AudioConfiguration BluetoothAudioSession::invalidSoftwareAudioConfiguration = {}; AudioConfiguration BluetoothAudioSession::invalidOffloadAudioConfiguration = {}; static constexpr int kFmqSendTimeoutMs = 1000; // 1000 ms timeout for sending static constexpr int kWritePollMs = 1; // polled non-blocking interval static inline timespec timespec_convert_from_hal(const TimeSpec& TS) { return {.tv_sec = static_cast(TS.tvSec), .tv_nsec = static_cast(TS.tvNSec)}; } BluetoothAudioSession::BluetoothAudioSession(const SessionType& session_type) : session_type_(session_type), stack_iface_(nullptr), mDataMQ(nullptr) { invalidSoftwareAudioConfiguration.pcmConfig(kInvalidPcmParameters); invalidOffloadAudioConfiguration.codecConfig(kInvalidCodecConfiguration); } // The report function is used to report that the Bluetooth stack has started // this session without any failure, and will invoke session_changed_cb_ to // notify those registered bluetooth_audio outputs void BluetoothAudioSession::OnSessionStarted( const sp stack_iface, const DataMQ::Descriptor* dataMQ, const AudioConfiguration& audio_config) { std::lock_guard guard(mutex_); if (stack_iface == nullptr) { LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) << ", IBluetoothAudioPort Invalid"; } else if (!UpdateAudioConfig(audio_config)) { LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) << ", AudioConfiguration=" << toString(audio_config) << " Invalid"; } else if (!UpdateDataPath(dataMQ)) { LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) << " DataMQ Invalid"; audio_config_ = (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH ? kInvalidOffloadAudioConfiguration : kInvalidSoftwareAudioConfiguration); } else { stack_iface_ = stack_iface; LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) << ", AudioConfiguration=" << toString(audio_config); ReportSessionStatus(); } } // The report function is used to report that the Bluetooth stack has ended the // session, and will invoke session_changed_cb_ to notify registered // bluetooth_audio outputs void BluetoothAudioSession::OnSessionEnded() { std::lock_guard guard(mutex_); if (IsSessionReady()) { ReportSessionStatus(); } audio_config_ = (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH ? kInvalidOffloadAudioConfiguration : kInvalidSoftwareAudioConfiguration); stack_iface_ = nullptr; UpdateDataPath(nullptr); } // invoking the registered session_changed_cb_ void BluetoothAudioSession::ReportSessionStatus() { // This is locked already by OnSessionStarted / OnSessionEnded if (observers_.empty()) { LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) << " has NO port state observer"; return; } for (auto& observer : observers_) { uint16_t cookie = observer.first; std::shared_ptr cb = observer.second; LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) << " notify to bluetooth_audio=0x" << android::base::StringPrintf("%04x", cookie); cb->session_changed_cb_(cookie); } } // The report function is used to report that the Bluetooth stack has notified // the result of startStream or suspendStream, and will invoke // control_result_cb_ to notify registered bluetooth_audio outputs void BluetoothAudioSession::ReportControlStatus( bool start_resp, const BluetoothAudioStatus& status) { std::lock_guard guard(mutex_); if (observers_.empty()) { LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_) << " has NO port state observer"; return; } for (auto& observer : observers_) { uint16_t cookie = observer.first; std::shared_ptr cb = observer.second; LOG(INFO) << __func__ << " - status=" << toString(status) << " for SessionType=" << toString(session_type_) << ", bluetooth_audio=0x" << android::base::StringPrintf("%04x", cookie) << (start_resp ? " started" : " suspended"); cb->control_result_cb_(cookie, start_resp, status); } } // The function helps to check if this session is ready or not // @return: true if the Bluetooth stack has started the specified session bool BluetoothAudioSession::IsSessionReady() { std::lock_guard guard(mutex_); bool dataMQ_valid = (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH || (mDataMQ != nullptr && mDataMQ->isValid())); return stack_iface_ != nullptr && dataMQ_valid; } bool BluetoothAudioSession::UpdateDataPath(const DataMQ::Descriptor* dataMQ) { if (dataMQ == nullptr) { // usecase of reset by nullptr mDataMQ = nullptr; return true; } std::unique_ptr tempDataMQ; tempDataMQ.reset(new DataMQ(*dataMQ)); if (!tempDataMQ || !tempDataMQ->isValid()) { mDataMQ = nullptr; return false; } mDataMQ = std::move(tempDataMQ); return true; } bool BluetoothAudioSession::UpdateAudioConfig( const AudioConfiguration& audio_config) { bool is_software_session = (session_type_ == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH || session_type_ == SessionType::HEARING_AID_SOFTWARE_ENCODING_DATAPATH); bool is_offload_session = (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH); auto audio_config_discriminator = audio_config.getDiscriminator(); bool is_software_audio_config = (is_software_session && audio_config_discriminator == AudioConfiguration::hidl_discriminator::pcmConfig); bool is_offload_audio_config = (is_offload_session && audio_config_discriminator == AudioConfiguration::hidl_discriminator::codecConfig); if (!is_software_audio_config && !is_offload_audio_config) { return false; } audio_config_ = audio_config; return true; } // The control function helps the bluetooth_audio module to register // PortStatusCallbacks // @return: cookie - the assigned number to this bluetooth_audio output uint16_t BluetoothAudioSession::RegisterStatusCback( const PortStatusCallbacks& cbacks) { std::lock_guard guard(mutex_); uint16_t cookie = ObserversCookieGetInitValue(session_type_); uint16_t cookie_upper_bound = ObserversCookieGetUpperBound(session_type_); while (cookie < cookie_upper_bound) { if (observers_.find(cookie) == observers_.end()) { break; } ++cookie; } if (cookie >= cookie_upper_bound) { LOG(ERROR) << __func__ << " - SessionType=" << toString(session_type_) << " has " << observers_.size() << " observers already (No Resource)"; return kObserversCookieUndefined; } std::shared_ptr cb = std::make_shared(); *cb = cbacks; observers_[cookie] = cb; return cookie; } // The control function helps the bluetooth_audio module to unregister // PortStatusCallbacks // @param: cookie - indicates which bluetooth_audio output is void BluetoothAudioSession::UnregisterStatusCback(uint16_t cookie) { std::lock_guard guard(mutex_); if (observers_.erase(cookie) != 1) { LOG(WARNING) << __func__ << " - SessionType=" << toString(session_type_) << " no such provider=0x" << android::base::StringPrintf("%04x", cookie); } } // The control function is for the bluetooth_audio module to get the current // AudioConfiguration const AudioConfiguration& BluetoothAudioSession::GetAudioConfig() { std::lock_guard guard(mutex_); if (IsSessionReady()) { return audio_config_; } else if (session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) { return kInvalidOffloadAudioConfiguration; } else { return kInvalidSoftwareAudioConfiguration; } } // Those control functions are for the bluetooth_audio module to start, suspend, // stop stream, to check position, and to update metadata. bool BluetoothAudioSession::StartStream() { std::lock_guard guard(mutex_); if (!IsSessionReady()) { LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) << " has NO session"; return false; } auto hal_retval = stack_iface_->startStream(); if (!hal_retval.isOk()) { LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" << toString(session_type_) << " failed"; return false; } return true; } bool BluetoothAudioSession::SuspendStream() { std::lock_guard guard(mutex_); if (!IsSessionReady()) { LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) << " has NO session"; return false; } auto hal_retval = stack_iface_->suspendStream(); if (!hal_retval.isOk()) { LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" << toString(session_type_) << " failed"; return false; } return true; } void BluetoothAudioSession::StopStream() { std::lock_guard guard(mutex_); if (!IsSessionReady()) { return; } auto hal_retval = stack_iface_->stopStream(); if (!hal_retval.isOk()) { LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" << toString(session_type_) << " failed"; } } bool BluetoothAudioSession::GetPresentationPosition( uint64_t* remote_delay_report_ns, uint64_t* total_bytes_readed, timespec* data_position) { std::lock_guard guard(mutex_); if (!IsSessionReady()) { LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) << " has NO session"; return false; } bool retval = false; auto hal_retval = stack_iface_->getPresentationPosition( [&retval, &remote_delay_report_ns, &total_bytes_readed, &data_position]( BluetoothAudioStatus status, const uint64_t& remoteDeviceAudioDelayNanos, uint64_t transmittedOctets, const TimeSpec& transmittedOctetsTimeStamp) { if (status == BluetoothAudioStatus::SUCCESS) { if (remote_delay_report_ns) *remote_delay_report_ns = remoteDeviceAudioDelayNanos; if (total_bytes_readed) *total_bytes_readed = transmittedOctets; if (data_position) *data_position = timespec_convert_from_hal(transmittedOctetsTimeStamp); retval = true; } }); if (!hal_retval.isOk()) { LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" << toString(session_type_) << " failed"; return false; } return retval; } void BluetoothAudioSession::UpdateTracksMetadata( const struct source_metadata* source_metadata) { std::lock_guard guard(mutex_); if (!IsSessionReady()) { LOG(DEBUG) << __func__ << " - SessionType=" << toString(session_type_) << " has NO session"; return; } ssize_t track_count = source_metadata->track_count; LOG(INFO) << __func__ << " - SessionType=" << toString(session_type_) << ", " << track_count << " track(s)"; if (session_type_ == SessionType::A2DP_SOFTWARE_ENCODING_DATAPATH || session_type_ == SessionType::A2DP_HARDWARE_OFFLOAD_DATAPATH) { return; } struct playback_track_metadata* track = source_metadata->tracks; SourceMetadata sourceMetadata; PlaybackTrackMetadata* halMetadata; sourceMetadata.tracks.resize(track_count); halMetadata = sourceMetadata.tracks.data(); while (track_count && track) { halMetadata->usage = static_cast(track->usage); halMetadata->contentType = static_cast(track->content_type); halMetadata->gain = track->gain; LOG(VERBOSE) << __func__ << " - SessionType=" << toString(session_type_) << ", usage=" << toString(halMetadata->usage) << ", content=" << toString(halMetadata->contentType) << ", gain=" << halMetadata->gain; --track_count; ++track; ++halMetadata; } auto hal_retval = stack_iface_->updateMetadata(sourceMetadata); if (!hal_retval.isOk()) { LOG(WARNING) << __func__ << " - IBluetoothAudioPort SessionType=" << toString(session_type_) << " failed"; } } // The control function writes stream to FMQ size_t BluetoothAudioSession::OutWritePcmData(const void* buffer, size_t bytes) { if (buffer == nullptr || !bytes) return 0; size_t totalWritten = 0; int ms_timeout = kFmqSendTimeoutMs; do { std::unique_lock lock(mutex_); if (!IsSessionReady()) break; size_t availableToWrite = mDataMQ->availableToWrite(); if (availableToWrite) { if (availableToWrite > (bytes - totalWritten)) { availableToWrite = bytes - totalWritten; } if (!mDataMQ->write(static_cast(buffer) + totalWritten, availableToWrite)) { ALOGE("FMQ datapath writting %zu/%zu failed", totalWritten, bytes); return totalWritten; } totalWritten += availableToWrite; } else if (ms_timeout >= kWritePollMs) { lock.unlock(); usleep(kWritePollMs * 1000); ms_timeout -= kWritePollMs; } else { ALOGD("data %zu/%zu overflow %d ms", totalWritten, bytes, (kFmqSendTimeoutMs - ms_timeout)); return totalWritten; } } while (totalWritten < bytes); return totalWritten; } std::unique_ptr BluetoothAudioSessionInstance::instance_ptr = std::unique_ptr( new BluetoothAudioSessionInstance()); // API to fetch the session of A2DP / Hearing Aid std::shared_ptr BluetoothAudioSessionInstance::GetSessionInstance( const SessionType& session_type) { std::lock_guard guard(instance_ptr->mutex_); if (!instance_ptr->sessions_map_.empty()) { auto entry = instance_ptr->sessions_map_.find(session_type); if (entry != instance_ptr->sessions_map_.end()) { return entry->second; } } std::shared_ptr session_ptr = std::make_shared(session_type); instance_ptr->sessions_map_[session_type] = session_ptr; return session_ptr; } } // namespace audio } // namespace bluetooth } // namespace android