| /* |
| * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. |
| * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * * Neither the name of The Linux Foundation nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
| * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
| * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN |
| * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * Changes from Qualcomm Innovation Center are provided under the following license: |
| * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted (subject to the limitations in the |
| * disclaimer below) provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * |
| * * Neither the name of Qualcomm Innovation Center, Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE |
| * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT |
| * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
| * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR |
| * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
| * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER |
| * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN |
| * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #define LOG_TAG "PAL: Stream" |
| #include <semaphore.h> |
| #include "Stream.h" |
| #include "StreamPCM.h" |
| #include "StreamInCall.h" |
| #include "StreamCompress.h" |
| #include "StreamSoundTrigger.h" |
| #include "StreamACD.h" |
| #include "StreamContextProxy.h" |
| #include "StreamUltraSound.h" |
| #include "StreamSensorPCMData.h" |
| #include "Session.h" |
| #include "SessionAlsaPcm.h" |
| #include "ResourceManager.h" |
| #include "Device.h" |
| #include "USBAudio.h" |
| |
| std::shared_ptr<ResourceManager> Stream::rm = nullptr; |
| std::mutex Stream::mBaseStreamMutex; |
| std::mutex Stream::pauseMutex; |
| std::condition_variable Stream::pauseCV; |
| |
| |
| void Stream::handleSoftPauseCallBack(uint64_t hdl, uint32_t event_id, |
| void *data __unused, |
| uint32_t event_size __unused, uint32_t miid __unused) { |
| |
| PAL_DBG(LOG_TAG,"Event id %x ", event_id); |
| |
| if (event_id == EVENT_ID_SOFT_PAUSE_PAUSE_COMPLETE) { |
| PAL_DBG(LOG_TAG, "Pause done"); |
| pauseCV.notify_all(); |
| } |
| } |
| |
| Stream* Stream::create(struct pal_stream_attributes *sAttr, struct pal_device *dAttr, |
| uint32_t noOfDevices, struct modifier_kv *modifiers, uint32_t noOfModifiers) |
| { |
| std::lock_guard<std::mutex> lock(mBaseStreamMutex); |
| Stream* stream = NULL; |
| int status = 0; |
| uint32_t count = 0; |
| struct pal_device *palDevsAttr = nullptr; |
| std::vector <Stream *> streamsToSwitch; |
| struct pal_device streamDevAttr; |
| |
| PAL_DBG(LOG_TAG, "Enter."); |
| |
| if (!sAttr || ((noOfDevices > 0) && !dAttr)) { |
| PAL_ERR(LOG_TAG, "Invalid input paramters"); |
| goto exit; |
| } |
| |
| /* get RM instance */ |
| if (!rm) { |
| rm = ResourceManager::getInstance(); |
| if (!rm) { |
| PAL_ERR(LOG_TAG, "ResourceManager getInstance failed"); |
| goto exit; |
| } |
| } |
| PAL_VERBOSE(LOG_TAG,"get RM instance success and noOfDevices %d \n", noOfDevices); |
| |
| if (sAttr->type == PAL_STREAM_NON_TUNNEL || sAttr->type == PAL_STREAM_CONTEXT_PROXY) |
| goto stream_create; |
| |
| palDevsAttr = (pal_device *)calloc(noOfDevices, sizeof(struct pal_device)); |
| |
| if (!palDevsAttr) { |
| PAL_ERR(LOG_TAG, "palDevsAttr not created"); |
| goto exit; |
| } |
| if (sAttr->type == PAL_STREAM_VOICE_CALL_MUSIC) |
| goto stream_create; |
| for (int i = 0; i < noOfDevices; i++) { |
| struct pal_device_info devinfo = {}; |
| palDevsAttr[i] = {}; |
| |
| if (sAttr->type == PAL_STREAM_ULTRASOUND) { |
| if (i == 0) { // first assign output device |
| if (rm->IsVirtualPortForUPDEnabled()) |
| dAttr[i].id = PAL_DEVICE_OUT_ULTRASOUND; |
| else if (rm->IsDedicatedBEForUPDEnabled()) |
| dAttr[i].id = PAL_DEVICE_OUT_ULTRASOUND_DEDICATED; |
| else |
| dAttr[i].id = PAL_DEVICE_OUT_HANDSET; |
| } else { // then assign input device |
| dAttr[i].id = PAL_DEVICE_IN_ULTRASOUND_MIC; |
| } |
| } |
| |
| //TODO: shift this to rm or somewhere else where we can read the supported config from xml |
| palDevsAttr[count].id = dAttr[i].id; |
| if (palDevsAttr[count].id == PAL_DEVICE_OUT_USB_DEVICE || |
| palDevsAttr[count].id == PAL_DEVICE_OUT_USB_HEADSET || |
| palDevsAttr[count].id == PAL_DEVICE_IN_USB_DEVICE || |
| palDevsAttr[count].id == PAL_DEVICE_IN_USB_HEADSET || |
| rm->isBtDevice(palDevsAttr[count].id)) { |
| palDevsAttr[count].address = dAttr[i].address; |
| } |
| PAL_VERBOSE(LOG_TAG, "count: %d, i: %d, length of dAttr custom_config: %d", count, i, strlen(dAttr[i].custom_config.custom_key)); |
| if (strlen(dAttr[i].custom_config.custom_key)) { |
| strlcpy(palDevsAttr[count].custom_config.custom_key, dAttr[i].custom_config.custom_key, PAL_MAX_CUSTOM_KEY_SIZE); |
| PAL_DBG(LOG_TAG, "found custom key %s", dAttr[i].custom_config.custom_key); |
| |
| } else { |
| strlcpy(palDevsAttr[count].custom_config.custom_key, "", PAL_MAX_CUSTOM_KEY_SIZE); |
| PAL_DBG(LOG_TAG, "no custom key found"); |
| } |
| |
| if (palDevsAttr[count].address.card_id != DUMMY_SND_CARD) { |
| status = rm->getDeviceConfig((struct pal_device *)&palDevsAttr[count], sAttr); |
| if (status) { |
| PAL_ERR(LOG_TAG, "Not able to get Device config %d", status); |
| goto exit; |
| } |
| // check if it's grouped device and group config needs to update |
| rm->lockActiveStream(); |
| status = rm->checkAndUpdateGroupDevConfig((struct pal_device *)&palDevsAttr[count], sAttr, |
| streamsToSwitch, &streamDevAttr, true); |
| rm->unlockActiveStream(); |
| if (status) { |
| PAL_ERR(LOG_TAG, "no valid group device config found"); |
| } |
| } |
| |
| count++; |
| } |
| |
| stream_create: |
| PAL_DBG(LOG_TAG, "stream type 0x%x", sAttr->type); |
| if (rm->isStreamSupported(sAttr, palDevsAttr, noOfDevices)) { |
| try { |
| switch (sAttr->type) { |
| case PAL_STREAM_LOW_LATENCY: |
| case PAL_STREAM_DEEP_BUFFER: |
| case PAL_STREAM_SPATIAL_AUDIO: |
| case PAL_STREAM_GENERIC: |
| case PAL_STREAM_VOIP_TX: |
| case PAL_STREAM_VOIP_RX: |
| case PAL_STREAM_PCM_OFFLOAD: |
| case PAL_STREAM_VOICE_CALL: |
| case PAL_STREAM_LOOPBACK: |
| case PAL_STREAM_ULTRA_LOW_LATENCY: |
| case PAL_STREAM_PROXY: |
| case PAL_STREAM_HAPTICS: |
| case PAL_STREAM_RAW: |
| case PAL_STREAM_VOICE_RECOGNITION: |
| //TODO:for now keeping PAL_STREAM_PLAYBACK_GENERIC for ULLA need to check |
| stream = new StreamPCM(sAttr, |
| palDevsAttr, |
| noOfDevices, |
| modifiers, |
| noOfModifiers, |
| rm); |
| break; |
| case PAL_STREAM_COMPRESSED: |
| stream = new StreamCompress(sAttr, |
| palDevsAttr, |
| noOfDevices, |
| modifiers, |
| noOfModifiers, |
| rm); |
| break; |
| case PAL_STREAM_VOICE_UI: |
| stream = new StreamSoundTrigger(sAttr, |
| palDevsAttr, |
| noOfDevices, |
| modifiers, |
| noOfModifiers, |
| rm); |
| break; |
| case PAL_STREAM_VOICE_CALL_RECORD: |
| case PAL_STREAM_VOICE_CALL_MUSIC: |
| stream = new StreamInCall(sAttr, |
| palDevsAttr, |
| noOfDevices, |
| modifiers, |
| noOfModifiers, |
| rm); |
| break; |
| case PAL_STREAM_NON_TUNNEL: |
| stream = new StreamNonTunnel(sAttr, |
| NULL, |
| 0, |
| modifiers, |
| noOfModifiers, |
| rm); |
| break; |
| case PAL_STREAM_ACD: |
| stream = new StreamACD(sAttr, |
| palDevsAttr, |
| noOfDevices, |
| modifiers, |
| noOfModifiers, |
| rm); |
| break; |
| case PAL_STREAM_CONTEXT_PROXY: |
| stream = new StreamContextProxy(sAttr, |
| NULL, |
| 0, |
| modifiers, |
| noOfModifiers, |
| rm); |
| break; |
| case PAL_STREAM_ULTRASOUND: |
| stream = new StreamUltraSound(sAttr, |
| palDevsAttr, |
| noOfDevices, |
| modifiers, |
| noOfModifiers, |
| rm); |
| break; |
| case PAL_STREAM_SENSOR_PCM_DATA: |
| stream = new StreamSensorPCMData(sAttr, |
| palDevsAttr, |
| noOfDevices, |
| modifiers, |
| noOfModifiers, |
| rm); |
| break; |
| default: |
| PAL_ERR(LOG_TAG, "unsupported stream type 0x%x", sAttr->type); |
| break; |
| } |
| } |
| catch (const std::exception& e) { |
| PAL_ERR(LOG_TAG, "Stream create failed for stream type 0x%x", sAttr->type); |
| if (palDevsAttr) { |
| free(palDevsAttr); |
| } |
| throw std::runtime_error(e.what()); |
| } |
| } else { |
| PAL_ERR(LOG_TAG,"Requested config not supported"); |
| goto exit; |
| } |
| exit: |
| if (palDevsAttr) { |
| free(palDevsAttr); |
| } |
| if (!stream) { |
| PAL_ERR(LOG_TAG, "stream creation failed"); |
| } |
| |
| PAL_DBG(LOG_TAG, "Exit stream %pK create %s", stream, |
| stream ? "successful" : "failed"); |
| return stream; |
| } |
| |
| int32_t Stream::getStreamAttributes(struct pal_stream_attributes *sAttr) |
| { |
| int32_t status = 0; |
| |
| if (!sAttr || !mStreamAttr) { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid stream attribute pointer, status %d", status); |
| goto exit; |
| } |
| ar_mem_cpy(sAttr, sizeof(pal_stream_attributes), mStreamAttr, |
| sizeof(pal_stream_attributes)); |
| PAL_VERBOSE(LOG_TAG, "stream_type %d stream_flags %d direction %d", |
| sAttr->type, sAttr->flags, sAttr->direction); |
| |
| exit: |
| return status; |
| } |
| |
| const std::string& Stream::getStreamSelector() const { |
| return mStreamSelector; |
| } |
| |
| const std::string& Stream::getDevicePPSelector() const { |
| return mDevPPSelector; |
| } |
| |
| int32_t Stream::getModifiers(struct modifier_kv *modifiers,uint32_t *noOfModifiers) |
| { |
| int32_t status = 0; |
| |
| if (!mModifiers || !noOfModifiers) { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid modifers pointer, status %d", status); |
| goto exit; |
| } |
| ar_mem_cpy (modifiers, sizeof(modifier_kv), mModifiers, |
| sizeof(modifier_kv)); |
| *noOfModifiers = mNoOfModifiers; |
| PAL_DBG(LOG_TAG, "noOfModifiers %u", *noOfModifiers); |
| exit: |
| return status; |
| } |
| |
| int32_t Stream::getEffectParameters(void *effect_query) |
| { |
| int32_t status = 0; |
| |
| if (!effect_query) { |
| PAL_ERR(LOG_TAG, "invalid query"); |
| return -EINVAL; |
| } |
| |
| mStreamMutex.lock(); |
| if (currentState == STREAM_IDLE) { |
| PAL_ERR(LOG_TAG, "Invalid stream state: IDLE"); |
| mStreamMutex.unlock(); |
| return -EINVAL; |
| } |
| pal_param_payload *pal_param = (pal_param_payload *)effect_query; |
| effect_pal_payload_t *effectPayload = (effect_pal_payload_t *)pal_param->payload; |
| status = session->getEffectParameters(this, effectPayload); |
| if (status) { |
| PAL_ERR(LOG_TAG, "getParameters failed with %d", status); |
| } |
| mStreamMutex.unlock(); |
| |
| return status; |
| } |
| |
| int32_t Stream::setEffectParameters(void *effect_param) |
| { |
| int32_t status = 0; |
| |
| if (!effect_param) { |
| PAL_ERR(LOG_TAG, "invalid query"); |
| return -EINVAL; |
| } |
| |
| mStreamMutex.lock(); |
| if (currentState == STREAM_IDLE) { |
| PAL_ERR(LOG_TAG, "Invalid stream state: IDLE"); |
| mStreamMutex.unlock(); |
| return -EINVAL; |
| } |
| |
| pal_param_payload *pal_param = (pal_param_payload *)effect_param; |
| effect_pal_payload_t *effectPayload = (effect_pal_payload_t *)pal_param->payload; |
| status = session->setEffectParameters(this, effectPayload); |
| if (status) { |
| PAL_ERR(LOG_TAG, "setEffectParameters failed with %d", status); |
| } |
| |
| mStreamMutex.unlock(); |
| |
| return status; |
| } |
| int32_t Stream::rwACDBParameters(void *payload, uint32_t sampleRate, |
| bool isParamWrite) |
| { |
| int32_t status = 0; |
| |
| mStreamMutex.lock(); |
| if (currentState == STREAM_IDLE) { |
| PAL_ERR(LOG_TAG, "Invalid stream state: IDLE"); |
| mStreamMutex.unlock(); |
| return -EINVAL; |
| } |
| status = session->rwACDBParameters(payload, sampleRate, isParamWrite); |
| mStreamMutex.unlock(); |
| |
| return status; |
| } |
| |
| int32_t Stream::getStreamType (pal_stream_type_t* streamType) |
| { |
| int32_t status = 0; |
| |
| if (!streamType || !mStreamAttr) { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid stream type, status %d", status); |
| goto exit; |
| } |
| *streamType = mStreamAttr->type; |
| PAL_DBG(LOG_TAG, "streamType - %d", *streamType); |
| |
| exit: |
| return status; |
| } |
| |
| int32_t Stream::getStreamDirection(pal_stream_direction_t *dir) |
| { |
| int32_t status = 0; |
| |
| if (!dir || !mStreamAttr) { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid inputs, status %d", status); |
| } else { |
| *dir = mStreamAttr->direction; |
| PAL_DBG(LOG_TAG, "stream direction - %d", *dir); |
| } |
| return status; |
| } |
| |
| uint32_t Stream::getRenderLatency() |
| { |
| uint32_t delayMs = 0; |
| |
| if (!mStreamAttr) { |
| PAL_ERR(LOG_TAG, "invalid mStreamAttr"); |
| return delayMs; |
| } |
| |
| switch (mStreamAttr->type) { |
| case PAL_STREAM_DEEP_BUFFER: |
| delayMs = PAL_DEEP_BUFFER_PLATFORM_DELAY / 1000; |
| break; |
| case PAL_STREAM_GENERIC: |
| delayMs = PAL_GENERIC_PLATFORM_DELAY / 1000; |
| break; |
| case PAL_STREAM_LOW_LATENCY: |
| delayMs = PAL_LOW_LATENCY_PLATFORM_DELAY / 1000; |
| break; |
| case PAL_STREAM_SPATIAL_AUDIO: |
| delayMs = PAL_SPATIAL_AUDIO_PLATFORM_DELAY / 1000; |
| break; |
| case PAL_STREAM_COMPRESSED: |
| case PAL_STREAM_PCM_OFFLOAD: |
| delayMs = PAL_PCM_OFFLOAD_PLATFORM_DELAY / 1000; |
| break; |
| case PAL_STREAM_ULTRA_LOW_LATENCY: |
| delayMs = PAL_ULL_PLATFORM_DELAY / 1000; |
| break; |
| case PAL_STREAM_VOIP_RX: |
| delayMs = PAL_VOIP_PLATFORM_DELAY / 1000; |
| break; |
| default: |
| break; |
| } |
| |
| return delayMs; |
| } |
| |
| uint32_t Stream::getLatency() |
| { |
| uint32_t latencyMs = 0; |
| |
| if (!mStreamAttr) { |
| PAL_ERR(LOG_TAG, "invalid mStreamAttr"); |
| return latencyMs; |
| } |
| |
| switch (mStreamAttr->type) { |
| case PAL_STREAM_GENERIC: |
| latencyMs = PAL_GENERIC_OUTPUT_PERIOD_DURATION * |
| PAL_GENERIC_PLAYBACK_PERIOD_COUNT; |
| break; |
| case PAL_STREAM_DEEP_BUFFER: |
| latencyMs = PAL_DEEP_BUFFER_OUTPUT_PERIOD_DURATION * |
| PAL_DEEP_BUFFER_PLAYBACK_PERIOD_COUNT; |
| break; |
| case PAL_STREAM_LOW_LATENCY: |
| latencyMs = PAL_LOW_LATENCY_OUTPUT_PERIOD_DURATION * |
| PAL_LOW_LATENCY_PLAYBACK_PERIOD_COUNT; |
| break; |
| case PAL_STREAM_SPATIAL_AUDIO: |
| latencyMs = PAL_SPATIAL_AUDIO_PERIOD_DURATION * |
| PAL_SPATIAL_AUDIO_PLAYBACK_PERIOD_COUNT; |
| break; |
| case PAL_STREAM_COMPRESSED: |
| case PAL_STREAM_PCM_OFFLOAD: |
| latencyMs = PAL_PCM_OFFLOAD_OUTPUT_PERIOD_DURATION * |
| PAL_PCM_OFFLOAD_PLAYBACK_PERIOD_COUNT; |
| break; |
| case PAL_STREAM_VOIP_RX: |
| latencyMs = PAL_VOIP_OUTPUT_PERIOD_DURATION * |
| PAL_VOIP_PLAYBACK_PERIOD_COUNT; |
| break; |
| default: |
| break; |
| } |
| |
| latencyMs += getRenderLatency(); |
| return latencyMs; |
| } |
| |
| int32_t Stream::getAssociatedDevices(std::vector <std::shared_ptr<Device>> &aDevices) |
| { |
| int32_t status = 0; |
| |
| PAL_DBG(LOG_TAG, "no. of devices %zu", mDevices.size()); |
| for (int32_t i=0; i < mDevices.size(); i++) { |
| aDevices.push_back(mDevices[i]); |
| } |
| |
| return status; |
| } |
| |
| int32_t Stream::getPalDevices(std::vector <std::shared_ptr<Device>> &PalDevices) |
| { |
| int32_t status = 0; |
| |
| PAL_DBG(LOG_TAG, "no. of devices %zu", mDevices.size()); |
| for (int32_t i=0; i < mPalDevices.size(); i++) { |
| PalDevices.push_back(mPalDevices[i]); |
| } |
| |
| return status; |
| } |
| |
| void Stream::clearOutPalDevices(Stream* streamHandle) |
| { |
| std::vector <std::shared_ptr<Device>>::iterator dIter; |
| int devId; |
| |
| for (dIter = mPalDevices.begin(); dIter != mPalDevices.end();) { |
| devId = (*dIter)->getSndDeviceId(); |
| if (!rm->isInputDevId(devId)) { |
| (*dIter)->removeStreamDeviceAttr(streamHandle); |
| mPalDevices.erase(dIter); |
| } else { |
| dIter++; |
| } |
| } |
| } |
| |
| void Stream::addPalDevice(Stream* streamHandle, struct pal_device *dattr) |
| { |
| std::shared_ptr<Device> dev = nullptr; |
| |
| dev = Device::getInstance(dattr, rm); |
| if (!dev) { |
| PAL_ERR(LOG_TAG, "No device instance found"); |
| return; |
| } |
| dev->insertStreamDeviceAttr(dattr, streamHandle); |
| mPalDevices.push_back(dev); |
| } |
| |
| int32_t Stream::getSoundCardId() |
| { |
| struct pal_device devAttr; |
| if (mDevices.size()) { |
| mDevices[0]->getDeviceAttributes(&devAttr); |
| PAL_DBG(LOG_TAG, "sound card id = 0x%x", |
| devAttr.address.card_id); |
| return devAttr.address.card_id; |
| } |
| |
| return -EINVAL; |
| } |
| |
| int32_t Stream::getAssociatedSession(Session **s) |
| { |
| int32_t status = 0; |
| |
| if (!s) { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid session, status %d", status); |
| goto exit; |
| } |
| *s = session; |
| PAL_DBG(LOG_TAG, "session %pK", *s); |
| exit: |
| return status; |
| } |
| |
| int32_t Stream::getVolumeData(struct pal_volume_data *vData) |
| { |
| int32_t status = 0; |
| |
| if (!vData) { |
| status = -EINVAL; |
| PAL_INFO(LOG_TAG, "Volume Data not set yet"); |
| goto exit; |
| } |
| |
| if (mVolumeData != NULL) { |
| ar_mem_cpy(vData, sizeof(uint32_t) + |
| (sizeof(struct pal_channel_vol_kv) * (mVolumeData->no_of_volpair)), |
| mVolumeData, sizeof(uint32_t) + |
| (sizeof(struct pal_channel_vol_kv) * (mVolumeData->no_of_volpair))); |
| |
| PAL_DBG(LOG_TAG, "num config %x", (mVolumeData->no_of_volpair)); |
| for(int32_t i=0; i < (mVolumeData->no_of_volpair); i++) { |
| PAL_VERBOSE(LOG_TAG,"Volume payload mask:%x vol:%f", |
| (mVolumeData->volume_pair[i].channel_mask), (mVolumeData->volume_pair[i].vol)); |
| } |
| } else { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "volume has not been set, status %d", status); |
| } |
| exit: |
| return status; |
| } |
| |
| int32_t Stream::setBufInfo(pal_buffer_config *in_buffer_cfg, |
| pal_buffer_config *out_buffer_cfg) |
| { |
| int32_t status = 0; |
| struct pal_stream_attributes sattr; |
| int16_t nBlockAlignIn, nBlockAlignOut ; // block size of data |
| |
| status = getStreamAttributes(&sattr); |
| /*In case of extern mem mode PAL wont get any buffer size info from clients |
| If clients still call this api then it could be to notify the metadata size*/ |
| if (sattr.flags & PAL_STREAM_FLAG_EXTERN_MEM) { |
| if (out_buffer_cfg) |
| outMaxMetadataSz = out_buffer_cfg->max_metadata_size; |
| if (in_buffer_cfg) |
| inMaxMetadataSz = in_buffer_cfg->max_metadata_size; |
| /*in EXTERN_MEM mode set buf count and size to 0*/ |
| inBufCount = 0; |
| inBufSize = 0; |
| outBufCount = 0; |
| outBufSize = 0; |
| } else { |
| if (in_buffer_cfg) { |
| PAL_DBG(LOG_TAG, "In Buffer size %zu, In Buffer count %zu metadata sz %zu", |
| in_buffer_cfg->buf_size, in_buffer_cfg->buf_count, in_buffer_cfg->max_metadata_size); |
| inBufCount = in_buffer_cfg->buf_count; |
| inMaxMetadataSz = in_buffer_cfg->max_metadata_size; |
| } |
| if (out_buffer_cfg) { |
| PAL_DBG(LOG_TAG, "Out Buffer size %zu and Out Buffer count %zu metaData sz %zu", |
| out_buffer_cfg->buf_size, out_buffer_cfg->buf_count, out_buffer_cfg->max_metadata_size); |
| outBufCount = out_buffer_cfg->buf_count; |
| outMaxMetadataSz = out_buffer_cfg->max_metadata_size; |
| } |
| |
| if (sattr.direction == PAL_AUDIO_OUTPUT) { |
| if(!out_buffer_cfg) { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid output buffer size status %d", status); |
| goto exit; |
| } |
| outBufSize = out_buffer_cfg->buf_size; |
| nBlockAlignOut = ((sattr.out_media_config.bit_width) / 8) * |
| (sattr.out_media_config.ch_info.channels); |
| PAL_DBG(LOG_TAG, "no of buf %zu and send buf %zu", outBufCount, outBufSize); |
| |
| //If the read size is not a multiple of BlockAlign; |
| //Make sure we read blockaligned bytes from the file. |
| if ((outBufSize % nBlockAlignOut) != 0) { |
| outBufSize = ((outBufSize / nBlockAlignOut) * nBlockAlignOut); |
| } |
| out_buffer_cfg->buf_size = outBufSize; |
| |
| } else if (sattr.direction == PAL_AUDIO_INPUT) { |
| if (!in_buffer_cfg) { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid input buffer size status %d", status); |
| goto exit; |
| } |
| inBufSize = in_buffer_cfg->buf_size; |
| //inBufSize = (sattr->in_media_config.bit_width) * (sattr->in_media_config.ch_info.channels) * 32; |
| nBlockAlignIn = ((sattr.in_media_config.bit_width) / 8) * |
| (sattr.in_media_config.ch_info.channels); |
| //If the read size is not a multiple of BlockAlign; |
| //Make sure we read blockaligned bytes from the file. |
| if ((inBufSize % nBlockAlignIn) != 0) { |
| inBufSize = ((inBufSize / nBlockAlignIn) * nBlockAlignIn); |
| } |
| in_buffer_cfg->buf_size = inBufSize; |
| } else { |
| if (!in_buffer_cfg || !out_buffer_cfg) { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid buffer size status %d", status); |
| goto exit; |
| } |
| outBufSize = out_buffer_cfg->buf_size; |
| nBlockAlignOut = ((sattr.out_media_config.bit_width) / 8) * |
| (sattr.out_media_config.ch_info.channels); |
| PAL_DBG(LOG_TAG, "no of buf %zu and send buf %zu", outBufCount, outBufSize); |
| |
| //If the read size is not a multiple of BlockAlign; |
| //Make sure we read blockaligned bytes from the file. |
| if ((outBufSize % nBlockAlignOut) != 0) { |
| outBufSize = ((outBufSize / nBlockAlignOut) * nBlockAlignOut); |
| } |
| out_buffer_cfg->buf_size = outBufSize; |
| |
| inBufSize = in_buffer_cfg->buf_size; |
| nBlockAlignIn = ((sattr.in_media_config.bit_width) / 8) * |
| (sattr.in_media_config.ch_info.channels); |
| //If the read size is not a multiple of BlockAlign; |
| //Make sure we read blockaligned bytes from the file. |
| if ((inBufSize % nBlockAlignIn) != 0) { |
| inBufSize = ((inBufSize / nBlockAlignIn) * nBlockAlignIn); |
| } |
| in_buffer_cfg->buf_size = inBufSize; |
| } |
| } |
| exit: |
| return status; |
| } |
| |
| int32_t Stream::getMaxMetadataSz(size_t *in_max_metdata_sz, size_t *out_max_metadata_sz) |
| { |
| int32_t status = 0; |
| |
| if (in_max_metdata_sz) |
| *in_max_metdata_sz = inMaxMetadataSz; |
| if (out_max_metadata_sz) |
| *out_max_metadata_sz = outMaxMetadataSz; |
| |
| if (in_max_metdata_sz) |
| PAL_DBG(LOG_TAG, "in max metadataSize %zu", |
| *in_max_metdata_sz); |
| |
| if (out_max_metadata_sz) |
| PAL_DBG(LOG_TAG, "in max metadataSize %zu", |
| *out_max_metadata_sz); |
| |
| return status; |
| } |
| |
| int32_t Stream::getBufInfo(size_t *in_buf_size, size_t *in_buf_count, |
| size_t *out_buf_size, size_t *out_buf_count) |
| { |
| int32_t status = 0; |
| |
| if (in_buf_size) |
| *in_buf_size = inBufSize; |
| if (in_buf_count) |
| *in_buf_count = inBufCount; |
| if (out_buf_size) |
| *out_buf_size = outBufSize; |
| if (out_buf_count) |
| *out_buf_count = outBufCount; |
| |
| if (in_buf_size && in_buf_count) |
| PAL_DBG(LOG_TAG, "In Buffer size %zu, In Buffer count %zu", |
| *in_buf_size, *in_buf_count); |
| |
| if (out_buf_size && out_buf_count) |
| PAL_DBG(LOG_TAG, "Out Buffer size %zu and Out Buffer count %zu", |
| *out_buf_size, *out_buf_count); |
| |
| return status; |
| } |
| |
| bool Stream::isStreamAudioOutFmtSupported(pal_audio_fmt_t format) |
| { |
| switch (format) { |
| case PAL_AUDIO_FMT_PCM_S8: |
| case PAL_AUDIO_FMT_PCM_S16_LE: |
| case PAL_AUDIO_FMT_PCM_S24_3LE: |
| case PAL_AUDIO_FMT_PCM_S24_LE: |
| case PAL_AUDIO_FMT_PCM_S32_LE: |
| case PAL_AUDIO_FMT_MP3: |
| case PAL_AUDIO_FMT_AAC: |
| case PAL_AUDIO_FMT_AAC_ADTS: |
| case PAL_AUDIO_FMT_AAC_ADIF: |
| case PAL_AUDIO_FMT_AAC_LATM: |
| case PAL_AUDIO_FMT_WMA_STD: |
| case PAL_AUDIO_FMT_ALAC: |
| case PAL_AUDIO_FMT_APE: |
| case PAL_AUDIO_FMT_WMA_PRO: |
| case PAL_AUDIO_FMT_FLAC: |
| case PAL_AUDIO_FMT_VORBIS: |
| case PAL_AUDIO_FMT_AMR_NB: |
| case PAL_AUDIO_FMT_AMR_WB: |
| case PAL_AUDIO_FMT_AMR_WB_PLUS: |
| case PAL_AUDIO_FMT_EVRC: |
| case PAL_AUDIO_FMT_G711: |
| case PAL_AUDIO_FMT_QCELP: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| int32_t Stream::getTimestamp(struct pal_session_time *stime) |
| { |
| int32_t status = 0; |
| if (!stime) { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Invalid session time pointer, status %d", status); |
| goto exit; |
| } |
| if (rm->cardState == CARD_STATUS_OFFLINE) { |
| status = -EINVAL; |
| PAL_ERR(LOG_TAG, "Sound card offline, status %d", status); |
| goto exit; |
| } |
| rm->lockResourceManagerMutex(); |
| status = session->getTimestamp(stime); |
| rm->unlockResourceManagerMutex(); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "Failed to get session timestamp status %d", status); |
| if (errno == -ENETRESET && |
| rm->cardState != CARD_STATUS_OFFLINE) { |
| PAL_ERR(LOG_TAG, "Sound card offline, informing RM"); |
| rm->ssrHandler(CARD_STATUS_OFFLINE); |
| status = -EINVAL; |
| } |
| } |
| exit: |
| return status; |
| } |
| |
| int32_t Stream::handleBTDeviceNotReady(bool& a2dpSuspend) |
| { |
| int32_t status = 0; |
| struct pal_device dattr = {}; |
| struct pal_device spkrDattr = {}; |
| struct pal_device handsetDattr = {}; |
| std::shared_ptr<Device> dev = nullptr; |
| std::shared_ptr<Device> spkrDev = nullptr; |
| std::shared_ptr<Device> handsetDev = nullptr; |
| pal_param_bta2dp_t *param_bt_a2dp = nullptr; |
| std::vector <Stream *> activeStreams; |
| |
| a2dpSuspend = false; |
| |
| /* SCO device is not ready */ |
| if (rm->isDeviceAvailable(mDevices, PAL_DEVICE_OUT_BLUETOOTH_SCO) && |
| !rm->isDeviceReady(PAL_DEVICE_OUT_BLUETOOTH_SCO)) { |
| // If it's sco + speaker combo device, route to speaker. |
| // Otherwise, return -EAGAIN. |
| if (rm->isDeviceAvailable(mDevices, PAL_DEVICE_OUT_SPEAKER)) { |
| PAL_INFO(LOG_TAG, "BT SCO output device is not ready, route to speaker"); |
| for (auto iter = mDevices.begin(); iter != mDevices.end();) { |
| if ((*iter)->getSndDeviceId() == PAL_DEVICE_OUT_SPEAKER) { |
| iter++; |
| continue; |
| } |
| |
| // Invoke session API to explicitly update the device metadata |
| rm->lockGraph(); |
| status = session->disconnectSessionDevice(this, mStreamAttr->type, (*iter)); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "disconnectSessionDevice failed:%d", status); |
| rm->unlockGraph(); |
| goto exit; |
| } |
| |
| status = (*iter)->close(); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "device close failed with status %d", status); |
| rm->unlockGraph(); |
| goto exit; |
| } |
| rm->unlockGraph(); |
| iter = mDevices.erase(iter); |
| } |
| } else { |
| PAL_ERR(LOG_TAG, "BT SCO output device is not ready"); |
| status = -EAGAIN; |
| goto exit; |
| } |
| } |
| |
| /* A2DP/BLE device is not ready */ |
| if (rm->isDeviceAvailable(mDevices, PAL_DEVICE_OUT_BLUETOOTH_A2DP) || |
| rm->isDeviceAvailable(mDevices, PAL_DEVICE_OUT_BLUETOOTH_BLE) || |
| rm->isDeviceAvailable(mDevices, PAL_DEVICE_OUT_BLUETOOTH_BLE_BROADCAST)) { |
| if (rm->isDeviceAvailable(mDevices, PAL_DEVICE_OUT_BLUETOOTH_A2DP)) { |
| dattr.id = PAL_DEVICE_OUT_BLUETOOTH_A2DP; |
| } else if (rm->isDeviceAvailable(mDevices, PAL_DEVICE_OUT_BLUETOOTH_BLE_BROADCAST)){ |
| dattr.id = PAL_DEVICE_OUT_BLUETOOTH_BLE_BROADCAST; |
| } else { |
| dattr.id = PAL_DEVICE_OUT_BLUETOOTH_BLE; |
| } |
| dev = Device::getInstance(&dattr, rm); |
| if (!dev) { |
| status = -ENODEV; |
| PAL_ERR(LOG_TAG, "failed to get a2dp/ble device object"); |
| goto exit; |
| } |
| dev->getDeviceParameter(PAL_PARAM_ID_BT_A2DP_SUSPENDED, |
| (void **)¶m_bt_a2dp); |
| if (param_bt_a2dp->a2dp_suspended == false) { |
| PAL_DBG(LOG_TAG, "BT A2DP/BLE output device is good to go"); |
| goto exit; |
| } |
| |
| if (rm->isDeviceAvailable(mDevices, PAL_DEVICE_OUT_SPEAKER)) { |
| // If it's a2dp + speaker combo device, route to speaker. |
| PAL_INFO(LOG_TAG, "BT A2DP/BLE output device is not ready, route to speaker"); |
| |
| /* In combo use case, if ringtone routed to a2dp + spkr and at that time a2dp/ble |
| * device is in suspended state, so during resume ringtone won't be able to route |
| * to BLE device. In that case, add both speaker and a2dp/ble into suspended devices |
| * list so that a2dp/ble will be restored during a2dpResume without removing speaker |
| * from stream |
| */ |
| suspendedDevIds.clear(); |
| suspendedDevIds.push_back(PAL_DEVICE_OUT_SPEAKER); |
| suspendedDevIds.push_back(dattr.id); |
| |
| for (auto iter = mDevices.begin(); iter != mDevices.end();) { |
| if ((*iter)->getSndDeviceId() == PAL_DEVICE_OUT_SPEAKER) { |
| iter++; |
| continue; |
| } |
| |
| rm->lockGraph(); |
| status = session->disconnectSessionDevice(this, mStreamAttr->type, (*iter)); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "disconnectSessionDevice failed:%d", status); |
| rm->unlockGraph(); |
| goto exit; |
| } |
| |
| status = (*iter)->close(); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "device close failed with status %d", status); |
| rm->unlockGraph(); |
| goto exit; |
| } |
| rm->unlockGraph(); |
| iter = mDevices.erase(iter); |
| } |
| } else { |
| // For non-combo device, mute the stream and route to speaker or handset |
| PAL_INFO(LOG_TAG, "BT A2DP/BLE output device is not ready"); |
| |
| // Mark the suspendedDevIds state early - As a2dpResume may happen during this time. |
| a2dpSuspend = true; |
| suspendedDevIds.clear(); |
| suspendedDevIds.push_back(dattr.id); |
| |
| for (int i = 0; i < mDevices.size(); i++) { |
| rm->lockGraph(); |
| status = session->disconnectSessionDevice(this, mStreamAttr->type, mDevices[i]); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "disconnectSessionDevice failed:%d", status); |
| rm->unlockGraph(); |
| goto exit; |
| } |
| /* Special handling for aaudio usecase on A2DP/BLE. |
| * A2DP/BLE device starts even when stream is not in START state, |
| * hence stop A2DP/BLE device to match device start&stop count. |
| */ |
| if (((mDevices[i]->getSndDeviceId() == PAL_DEVICE_OUT_BLUETOOTH_A2DP) || |
| (mDevices[i]->getSndDeviceId() == PAL_DEVICE_OUT_BLUETOOTH_BLE)) && isMMap) { |
| status = mDevices[i]->stop(); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "BT A2DP/BLE device stop failed with status %d", status); |
| } |
| } |
| status = mDevices[i]->close(); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "device close failed with status %d", status); |
| rm->unlockGraph(); |
| goto exit; |
| } |
| rm->unlockGraph(); |
| } |
| mDevices.clear(); |
| clearOutPalDevices(this); |
| |
| /* Check whether there's active stream associated with handset or speaker |
| * - Device selected to switch by default is speaker. |
| * - Check handset as well if no stream on speaker. |
| */ |
| // NOTE: lockGraph is not intended for speaker or handset device |
| spkrDattr.id = PAL_DEVICE_OUT_SPEAKER; |
| spkrDev = Device::getInstance(&spkrDattr , rm); |
| handsetDattr.id = PAL_DEVICE_OUT_HANDSET; |
| handsetDev = Device::getInstance(&handsetDattr , rm); |
| if (!spkrDev || !handsetDev) { |
| status = -ENODEV; |
| PAL_ERR(LOG_TAG, "Failed to get speaker or handset instance"); |
| goto exit; |
| } |
| |
| dattr.id = spkrDattr.id; |
| dev = spkrDev; |
| |
| rm->getActiveStream_l(activeStreams, spkrDev); |
| if (activeStreams.empty()) { |
| rm->getActiveStream_l(activeStreams, handsetDev); |
| if (!activeStreams.empty()) { |
| // active streams found on handset |
| dattr.id = PAL_DEVICE_OUT_HANDSET; |
| dev = handsetDev; |
| } else { |
| // no active stream found on both speaker and handset, get the deafult |
| pal_device_info devInfo; |
| memset(&devInfo, 0, sizeof(pal_device_info)); |
| status = rm->getDeviceConfig(&dattr, NULL); |
| if (!status) { |
| // get the default device info and update snd name |
| rm->getDeviceInfo(dattr.id, (pal_stream_type_t)0, |
| dattr.custom_config.custom_key, &devInfo); |
| rm->updateSndName(dattr.id, devInfo.sndDevName); |
| } |
| dev->setDeviceAttributes(dattr); |
| } |
| } |
| |
| PAL_INFO(LOG_TAG, "mute stream and route to device %d", dattr.id); |
| |
| status = dev->open(); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "device open failed with status %d", status); |
| goto exit; |
| } |
| |
| mDevices.push_back(dev); |
| status = session->setupSessionDevice(this, mStreamAttr->type, dev); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "setupSessionDevice failed:%d", status); |
| dev->close(); |
| mDevices.pop_back(); |
| goto exit; |
| } |
| |
| status = session->connectSessionDevice(this, mStreamAttr->type, dev); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "connectSessionDevice failed:%d", status); |
| dev->close(); |
| mDevices.pop_back(); |
| goto exit; |
| } |
| dev->getDeviceAttributes(&dattr, this); |
| addPalDevice(this, &dattr); |
| } |
| } |
| |
| exit: |
| return status; |
| } |
| |
| int32_t Stream::disconnectStreamDevice(Stream* streamHandle, pal_device_id_t dev_id) |
| { |
| int32_t status = 0; |
| mStreamMutex.lock(); |
| status = disconnectStreamDevice_l(streamHandle, dev_id); |
| mStreamMutex.unlock(); |
| |
| return status; |
| } |
| |
| int32_t Stream::disconnectStreamDevice_l(Stream* streamHandle, pal_device_id_t dev_id) |
| { |
| int32_t status = 0; |
| |
| if (currentState == STREAM_IDLE) { |
| PAL_DBG(LOG_TAG, "stream is in %d state, no need to switch device", currentState); |
| status = 0; |
| goto exit; |
| } |
| |
| // Stream does not know if the same device is being used by other streams or not |
| // So if any other streams are using the same device that has to be handled outside of stream |
| // resouce manager ?? |
| for (int i = 0; i < mDevices.size(); i++) { |
| if (dev_id == mDevices[i]->getSndDeviceId()) { |
| PAL_DBG(LOG_TAG, "device %d name %s, going to stop", |
| mDevices[i]->getSndDeviceId(), mDevices[i]->getPALDeviceName().c_str()); |
| if (currentState != STREAM_STOPPED) { |
| rm->deregisterDevice(mDevices[i], this); |
| } |
| rm->lockGraph(); |
| status = session->disconnectSessionDevice(streamHandle, mStreamAttr->type, mDevices[i]); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "disconnectSessionDevice failed:%d", status); |
| rm->unlockGraph(); |
| goto exit; |
| } |
| /* Special handling for aaudio usecase on A2DP/BLE. |
| * A2DP/BLE device starts even when stream is not in START state, |
| * hence stop A2DP/BLE device to match device start&stop count. |
| */ |
| |
| if ((currentState != STREAM_INIT && currentState != STREAM_STOPPED) || |
| (currentState == STREAM_INIT && |
| ((mDevices[i]->getSndDeviceId() == PAL_DEVICE_OUT_BLUETOOTH_A2DP) || |
| (mDevices[i]->getSndDeviceId() == PAL_DEVICE_OUT_BLUETOOTH_BLE)) && |
| isMMap)) { |
| status = mDevices[i]->stop(); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "device stop failed with status %d", status); |
| rm->unlockGraph(); |
| goto exit; |
| } |
| } |
| rm->unlockGraph(); |
| |
| status = mDevices[i]->close(); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "device close failed with status %d", status); |
| goto exit; |
| } |
| mDevices.erase(mDevices.begin() + i); |
| break; |
| } |
| } |
| rm->checkAndSetDutyCycleParam(); |
| |
| exit: |
| return status; |
| } |
| |
| int32_t Stream::connectStreamDevice(Stream* streamHandle, struct pal_device *dattr) |
| { |
| int32_t status = 0; |
| mStreamMutex.lock(); |
| status = connectStreamDevice_l(streamHandle, dattr); |
| mStreamMutex.unlock(); |
| |
| return status; |
| } |
| |
| int32_t Stream::connectStreamDevice_l(Stream* streamHandle, struct pal_device *dattr) |
| { |
| int32_t status = 0; |
| std::shared_ptr<Device> dev = nullptr; |
| std::string newBackEndName; |
| std::string curBackEndName; |
| |
| if (!dattr) { |
| PAL_ERR(LOG_TAG, "invalid params"); |
| status = -EINVAL; |
| goto exit; |
| } |
| |
| dev = Device::getInstance(dattr, rm); |
| if (!dev) { |
| PAL_ERR(LOG_TAG, "Device creation failed"); |
| goto exit; |
| } |
| |
| dev->setDeviceAttributes(*dattr); |
| |
| if (currentState == STREAM_IDLE) { |
| PAL_DBG(LOG_TAG, "stream is in %d state, no need to switch device", currentState); |
| status = 0; |
| goto exit; |
| } |
| |
| /* Avoid stream connecting to devices sharing the same backend. |
| * - For A2DP streams may play on combo devices like Speaker and A2DP. |
| * However, if a2dp suspend is called, all streams on a2dp will temporarily |
| * move to speaker. If the stream is already connected to speaker, speaker |
| * will be connected twice. |
| * - For multi-recording stream connecting to bt-sco-mic and handset-mic, |
| * if a2dp suspend arrives, stream will switch from bt-sco-mic to speaker-mic. |
| * Hence, both speaker-mic and handset-mic will be enabled. |
| */ |
| rm->getBackendName(dev->getSndDeviceId(), newBackEndName); |
| for (auto iter = mDevices.begin(); iter != mDevices.end(); iter++) { |
| rm->getBackendName((*iter)->getSndDeviceId(), curBackEndName); |
| if (newBackEndName == curBackEndName) { |
| PAL_INFO(LOG_TAG, |
| "stream is already connected to device %d name %s - return", |
| dev->getSndDeviceId(), dev->getPALDeviceName().c_str()); |
| status = 0; |
| goto exit; |
| } |
| } |
| |
| /* For UC2: USB insertion on playback, after disabling PA, notify PMIC |
| * assuming that current Concurrent Boost status is false and Limiter |
| * is not configured for speaker.Audio will continue to playback irrespective |
| * of success/failure after notifying PMIC about enabling concurrency |
| */ |
| if (ResourceManager::isChargeConcurrencyEnabled && dev |
| && dev->getSndDeviceId() == PAL_DEVICE_OUT_SPEAKER && |
| !rm->getConcurrentBoostState() && !rm->getInputCurrentLimitorConfigStatus()) |
| status = rm->chargerListenerSetBoostState(true, PB_ON_CHARGER_INSERT); |
| |
| PAL_DBG(LOG_TAG, "device %d name %s, going to start", |
| dev->getSndDeviceId(), dev->getPALDeviceName().c_str()); |
| |
| status = dev->open(); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "device %d open failed with status %d", |
| dev->getSndDeviceId(), status); |
| goto exit; |
| } |
| |
| mDevices.push_back(dev); |
| status = session->setupSessionDevice(streamHandle, mStreamAttr->type, dev); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "setupSessionDevice for %d failed with status %d", |
| dev->getSndDeviceId(), status); |
| goto dev_close; |
| } |
| |
| /* Special handling for aaudio usecase on A2DP/BLE. |
| * For mmap usecase, if device switch happens to A2DP/BLE device |
| * before stream_start then start A2DP/BLE dev. since it won't be |
| * started again as a part of pal_stream_start(). |
| */ |
| |
| rm->lockGraph(); |
| if ((currentState != STREAM_INIT && currentState != STREAM_STOPPED) || |
| (currentState == STREAM_INIT && |
| ((dev->getSndDeviceId() == PAL_DEVICE_OUT_BLUETOOTH_A2DP) || |
| (dev->getSndDeviceId() == PAL_DEVICE_OUT_BLUETOOTH_BLE)) && |
| isMMap)) { |
| status = dev->start(); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "device %d name %s, start failed with status %d", |
| dev->getSndDeviceId(), dev->getPALDeviceName().c_str(), status); |
| rm->unlockGraph(); |
| goto dev_close; |
| } |
| } else if (rm->isBtDevice((pal_device_id_t)dev->getSndDeviceId())) { |
| PAL_DBG(LOG_TAG, "stream is in %d state, no need to switch to BT", currentState); |
| status = 0; |
| rm->unlockGraph(); |
| goto dev_close; |
| } |
| |
| status = session->connectSessionDevice(streamHandle, mStreamAttr->type, dev); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG, "connectSessionDevice failed:%d", status); |
| rm->unlockGraph(); |
| goto dev_stop; |
| } |
| rm->unlockGraph(); |
| if (currentState != STREAM_STOPPED) { |
| rm->registerDevice(dev, this); |
| } |
| |
| rm->checkAndSetDutyCycleParam(); |
| |
| /* For UC2: USB insertion on playback, After USB online notification, |
| * As enabling PA is done assuming that current Concurrent Boost state |
| * is True and Audio will config Limiter for speaker. |
| */ |
| if (ResourceManager::isChargeConcurrencyEnabled && dev && |
| (dev->getSndDeviceId() == PAL_DEVICE_OUT_SPEAKER) && rm->getConcurrentBoostState() |
| && !rm->getInputCurrentLimitorConfigStatus() && rm->getChargerOnlineState()) |
| status = rm->setSessionParamConfig(PAL_PARAM_ID_CHARGER_STATE, streamHandle, |
| CHARGE_CONCURRENCY_ON_TAG); |
| goto exit; |
| |
| dev_stop: |
| if (status != -ENETRESET) { |
| dev->stop(); |
| } |
| |
| dev_close: |
| /* Do not pop the current device from stream, if session connect failed due to SSR down |
| * event so that when SSR is up that device will be associated to stream. |
| */ |
| if (status != -ENETRESET) { |
| mDevices.pop_back(); |
| dev->close(); |
| } |
| |
| exit: |
| /* check if USB is not available restore to default device */ |
| if (dev && status && (dev->getSndDeviceId() == PAL_DEVICE_OUT_USB_HEADSET || |
| dev->getSndDeviceId() == PAL_DEVICE_IN_USB_HEADSET)) { |
| if (USB::isUsbConnected(dattr->address)) { |
| PAL_ERR(LOG_TAG, "USB still connected, connect failed"); |
| } else { |
| status = -ENOSYS; |
| PAL_ERR(LOG_TAG, "failed to connect to USB device"); |
| } |
| |
| } |
| return status; |
| } |
| |
| /* |
| legend: |
| s1 - current stream |
| s2 - existing stream |
| d1, d11, d2 - PAL_DEVICE enums |
| B1, B2 - backend strings |
| |
| case 1 |
| a) s1: d1(B1) -> d2(B2) |
| OR |
| b) s1: d1(B1) -> d2(B1) |
| |
| resolution: tear down d1 and setup d2. |
| |
| case 2 |
| s1: d1(B1) -> d1(B1) , d2(B2) |
| |
| resolution: check if d1 needs device switch by calling isDeviceSwitchRequired. |
| If needed, tear down and setup d1 again. Also setup d2. |
| |
| case 3: |
| s1: d1(B1), d2(B2) -> d1(B1) |
| |
| resolution: check if d1 needs device switch by calling isDeviceSwitchRequired. |
| If needed, tear down and setup d1 again. Also teardown d2. |
| |
| case 4 |
| s2->dev d1(B1) |
| s1: d2(B2) -> d1(B1) |
| |
| resolution: check if s2 is voice call stream, then use same device attribute |
| for s1. Else check if isDeviceSwitchRequired() returns true for s2 d1. |
| If yes, tear down and setup d1 again for s2. Also, setup d1 for s2. |
| |
| case 5 |
| s2->dev d2(B1) |
| s1: d11(B2) -> d1(B1) |
| resolution: If s2 is voice call stream, then setup d2 for s1 instead of d1 |
| and do nothing for s2. Else, tear down d2 from s2 and setup d1 for both |
| s1 and s2. |
| |
| case 6 |
| s2->dev d2(B2) |
| s1: d2(B2) -> d1(B2) |
| resolution: same handling as case 5 |
| |
| case 7 |
| s2->dev d1(B1), d2(B2) |
| s1: d1(B1) -> d2(B2) |
| |
| resolution: nothing to be done for s2+d1. For s2+d2, it is |
| same as case 4. |
| |
| */ |
| int32_t Stream::switchDevice(Stream* streamHandle, uint32_t numDev, struct pal_device *newDevices) |
| { |
| int32_t status = 0; |
| int32_t connectCount = 0, disconnectCount = 0; |
| bool isNewDeviceA2dp = false; |
| bool isCurDeviceA2dp = false; |
| bool isCurrentDeviceProxyOut = false; |
| bool isCurrentDeviceDpOut = false; |
| bool matchFound = false; |
| bool voice_call_switch = false; |
| uint32_t force_switch_dev_id = PAL_DEVICE_IN_MAX; |
| uint32_t curDeviceSlots[PAL_DEVICE_IN_MAX], newDeviceSlots[PAL_DEVICE_IN_MAX]; |
| std::vector <std::tuple<Stream *, uint32_t>> streamDevDisconnect, sharedBEStreamDev; |
| std::vector <std::tuple<Stream *, struct pal_device *>> StreamDevConnect; |
| struct pal_device dAttr; |
| struct pal_device_info deviceInfo; |
| uint32_t temp_prio = MIN_USECASE_PRIORITY; |
| pal_stream_attributes strAttr; |
| char CurrentSndDeviceName[DEVICE_NAME_MAX_SIZE] = {0}; |
| std::vector <Stream *> streamsToSwitch; |
| struct pal_device streamDevAttr; |
| std::vector <Stream*>::iterator sIter; |
| bool has_out_device = false, has_in_device = false; |
| std::vector <std::shared_ptr<Device>>::iterator dIter; |
| struct pal_volume_data *volume = NULL; |
| pal_device_id_t curBtDevId; |
| pal_device_id_t newBtDevId; |
| bool isBtReady = false; |
| |
| rm->lockActiveStream(); |
| mStreamMutex.lock(); |
| |
| if ((numDev == 0) || (numDev > PAL_DEVICE_IN_MAX) || (!newDevices) || (!streamHandle)) { |
| PAL_ERR(LOG_TAG, "invalid param for device switch"); |
| mStreamMutex.unlock(); |
| rm->unlockActiveStream(); |
| return -EINVAL; |
| } |
| |
| if (rm->cardState == CARD_STATUS_OFFLINE) { |
| PAL_ERR(LOG_TAG, "Sound card offline"); |
| mStreamMutex.unlock(); |
| rm->unlockActiveStream(); |
| return 0; |
| } |
| |
| streamHandle->getStreamAttributes(&strAttr); |
| |
| for (int i = 0; i < mDevices.size(); i++) { |
| pal_device_id_t curDevId = (pal_device_id_t)mDevices[i]->getSndDeviceId(); |
| |
| if (curDevId == PAL_DEVICE_OUT_BLUETOOTH_A2DP || |
| curDevId == PAL_DEVICE_OUT_BLUETOOTH_BLE || |
| curDevId == PAL_DEVICE_OUT_BLUETOOTH_BLE_BROADCAST) { |
| isCurDeviceA2dp = true; |
| curBtDevId = curDevId; |
| } |
| |
| if (curDevId == PAL_DEVICE_OUT_PROXY) |
| isCurrentDeviceProxyOut = true; |
| |
| if (curDevId == PAL_DEVICE_OUT_AUX_DIGITAL || |
| curDevId == PAL_DEVICE_OUT_AUX_DIGITAL_1 || |
| curDevId == PAL_DEVICE_OUT_HDMI) |
| isCurrentDeviceDpOut = true; |
| |
| /* |
| * If stream is currently running on same device, then check if |
| * it needs device switch. If not needed, then do not add it to |
| * streamDevConnect and streamDevDisconnect list. This handles case 2,3. |
| */ |
| matchFound = false; |
| for (int j = 0; j < numDev; j++) { |
| if (curDevId == newDevices[j].id) { |
| matchFound = true; |
| /* special handle if same device switch is triggered by different custom key */ |
| struct pal_device curDevAttr; |
| mDevices[i]->getDeviceAttributes(&curDevAttr, streamHandle); |
| if (strcmp(newDevices[j].custom_config.custom_key, |
| curDevAttr.custom_config.custom_key) != 0) { |
| PAL_DBG(LOG_TAG, "found diff custom key is %s, running dev has %s, device switch needed", |
| newDevices[j].custom_config.custom_key, |
| curDevAttr.custom_config.custom_key); |
| force_switch_dev_id = newDevices[j].id; |
| } |
| break; |
| } |
| } |
| /* If mDevice[i] does not match with any of new device, |
| then update disconnect list */ |
| if (matchFound == false) { |
| curDeviceSlots[disconnectCount] = i; |
| disconnectCount++; |
| } |
| } |
| |
| /* remove stream-device attr and mPalDevices which has same dir with new devices*/ |
| for (int i = 0; i < numDev; i++) { |
| if (rm->isOutputDevId(newDevices[i].id)) |
| has_out_device = true; |
| if (rm->isInputDevId(newDevices[i].id)) |
| has_in_device = true; |
| } |
| for (dIter = mPalDevices.begin(); dIter != mPalDevices.end(); ) { |
| if (rm->isOutputDevId((*dIter)->getSndDeviceId()) && |
| has_out_device) { |
| (*dIter)->removeStreamDeviceAttr(streamHandle); |
| dIter = mPalDevices.erase(dIter); |
| } else if (rm->isInputDevId((*dIter)->getSndDeviceId()) && |
| has_in_device) { |
| (*dIter)->removeStreamDeviceAttr(streamHandle); |
| dIter = mPalDevices.erase(dIter); |
| } else { |
| dIter++; |
| } |
| } |
| |
| for (int i = 0; i < numDev; i++) { |
| struct pal_device_info devinfo = {}; |
| std::shared_ptr<Device> dev = nullptr; |
| bool devReadyStatus = 0; |
| uint32_t retryCnt = 20; |
| uint32_t retryPeriodMs = 100; |
| pal_param_bta2dp_t* param_bt_a2dp = nullptr; |
| /* |
| * When A2DP, Out Proxy and DP device is disconnected the |
| * music playback is paused and the policy manager sends routing=0 |
| * But the audioflinger continues to write data until standby time |
| * (3sec). As BT is turned off, the write gets blocked. |
| * Avoid this by routing audio to speaker until standby. |
| */ |
| // This assumes that PAL_DEVICE_NONE comes as single device |
| if ((newDevices[i].id == PAL_DEVICE_NONE) && |
| (((isCurDeviceA2dp == true) && (!rm->isDeviceReady(curBtDevId))) || |
| (isCurrentDeviceProxyOut) || (isCurrentDeviceDpOut))) { |
| newDevices[i].id = PAL_DEVICE_OUT_SPEAKER; |
| |
| if (rm->getDeviceConfig(&newDevices[i], mStreamAttr)) { |
| continue; |
| } |
| } |
| |
| if (newDevices[i].id == PAL_DEVICE_NONE) { |
| mStreamMutex.unlock(); |
| rm->unlockActiveStream(); |
| return 0; |
| } |
| /* Retry isDeviceReady check is required for BT devices only. |
| * In case of BT disconnection event from BT stack, if stream |
| * is still associated with BT but the BT device is not in |
| * ready state, explicit dev switch from APM to BT keep on retrying |
| * for 2 secs causing audioserver to stuck for processing |
| * disconnection. Thus check for isCurDeviceA2dp and a2dp_suspended |
| * state to avoid unnecessary sleep over 2 secs. |
| */ |
| if ((newDevices[i].id == PAL_DEVICE_OUT_BLUETOOTH_A2DP) || |
| (newDevices[i].id == PAL_DEVICE_OUT_BLUETOOTH_BLE) || |
| (newDevices[i].id == PAL_DEVICE_OUT_BLUETOOTH_BLE_BROADCAST)) { |
| isNewDeviceA2dp = true; |
| newBtDevId = newDevices[i].id; |
| dev = Device::getInstance(&newDevices[i], rm); |
| if (!dev) { |
| status = -ENODEV; |
| PAL_ERR(LOG_TAG, "failed to get a2dp/ble device object"); |
| goto done; |
| } |
| dev->getDeviceParameter(PAL_PARAM_ID_BT_A2DP_SUSPENDED, |
| (void**)¶m_bt_a2dp); |
| |
| if (!param_bt_a2dp->a2dp_suspended) { |
| while (!devReadyStatus && --retryCnt) { |
| devReadyStatus = rm->isDeviceReady(newDevices[i].id); |
| if (devReadyStatus) { |
| isBtReady = true; |
| break; |
| } else if (isCurDeviceA2dp) { |
| break; |
| } |
| |
| usleep(retryPeriodMs * 1000); |
| } |
| } |
| } else { |
| devReadyStatus = rm->isDeviceReady(newDevices[i].id); |
| } |
| |
| if (!devReadyStatus) { |
| PAL_ERR(LOG_TAG, "Device %d is not ready", newDevices[i].id); |
| if (((newDevices[i].id == PAL_DEVICE_OUT_BLUETOOTH_A2DP) || |
| (newDevices[i].id == PAL_DEVICE_OUT_BLUETOOTH_BLE) || |
| (newDevices[i].id == PAL_DEVICE_OUT_BLUETOOTH_BLE_BROADCAST)) && |
| !(rm->isDeviceAvailable(newDevices, numDev, PAL_DEVICE_OUT_SPEAKER))) { |
| /* update suspended device to a2dp and don't route as BT returned error |
| * However it is still possible a2dp routing called as part of a2dp restore |
| */ |
| PAL_ERR(LOG_TAG, "A2DP profile is not ready, ignoring routing request"); |
| suspendedDevIds.clear(); |
| suspendedDevIds.push_back(newDevices[i].id); |
| } |
| } else { |
| newDeviceSlots[connectCount] = i; |
| connectCount++; |
| } |
| /* insert current stream-device attr to Device */ |
| dev = Device::getInstance(&newDevices[i],rm); |
| if (!dev) { |
| PAL_ERR(LOG_TAG, "No device instance found"); |
| mStreamMutex.unlock(); |
| return -EINVAL; |
| } |
| dev->insertStreamDeviceAttr(&newDevices[i], streamHandle); |
| mPalDevices.push_back(dev); |
| } |
| |
| /* No new device is ready */ |
| if ((numDev != 0) && (connectCount == 0)) { |
| PAL_INFO(LOG_TAG, "No new device is ready to connect"); |
| mStreamMutex.unlock(); |
| rm->unlockActiveStream(); |
| return 0; |
| } |
| |
| PAL_INFO(LOG_TAG,"number of active devices %zu, new devices %d", mDevices.size(), connectCount); |
| |
| /* created stream device connect and disconnect list */ |
| streamDevDisconnect.clear(); |
| StreamDevConnect.clear(); |
| |
| for (int i = 0; i < connectCount; i++) { |
| std::vector <Stream *> activeStreams; |
| pal_device_id_t newDeviceId = newDevices[newDeviceSlots[i]].id; |
| |
| sharedBEStreamDev.clear(); |
| // get active stream device pairs sharing the same backend with new devices. |
| rm->getSharedBEActiveStreamDevs(sharedBEStreamDev, newDeviceId); |
| |
| rm->getDeviceInfo((pal_device_id_t)newDeviceId, |
| strAttr.type, newDevices[newDeviceSlots[i]].custom_config.custom_key, |
| &deviceInfo); |
| |
| /* This can result in below situation: |
| * 1) No matching SharedBEStreamDev (handled in else part). |
| * e.g. - case 1a, 2. |
| * 2) sharedBEStreamDev having same stream as current stream but different device id. |
| * e.g. case 1b. |
| * 3) sharedBEStreamDev having different stream from current but same id as current id. |
| * e.g. - case 4,7. |
| * 4) sharedBEStreamDev having different stream from current and different device id. |
| * e.g. case 5,6. |
| * 5) sharedBEStreamDev having same stream as current stream and same device id. |
| * e.g. spkr -> spkr + hs. This will only come in list if speaker needs device switch. |
| * This list excludes same stream and same id which does not need device switch as it |
| * is removed above. |
| */ |
| if (sharedBEStreamDev.size() > 0) { |
| /* compare new stream-device attr with current active stream-device attr */ |
| bool switchStreams = rm->compareSharedBEStreamDevAttr(sharedBEStreamDev, &newDevices[newDeviceSlots[i]], true); |
| for (const auto &elem : sharedBEStreamDev) { |
| Stream *sharedStream = std::get<0>(elem); |
| struct pal_device curDevAttr; |
| bool custom_switch = false; |
| |
| curDevAttr.id = (pal_device_id_t)std::get<1>(elem); |
| /* |
| * for current stream, if custom key updated, even reset of the attr |
| * like sample rate/channels/bit width/... are the same, still need |
| * to switch device to update custom config like devicePP |
| */ |
| if ((sharedStream == streamHandle) && (force_switch_dev_id == curDevAttr.id)) { |
| custom_switch = true; |
| } |
| /* If prioirty based attr diffs with running dev switch all devices */ |
| if (switchStreams || custom_switch) { |
| streamDevDisconnect.push_back(elem); |
| StreamDevConnect.push_back({std::get<0>(elem), &newDevices[newDeviceSlots[i]]}); |
| matchFound = true; |
| custom_switch = false; |
| } else { |
| matchFound = false; |
| } |
| } |
| /*In case of SWB-->WB/NB SCO voip SHO, APM sends explicit routing separately |
| * for RX and TX path, it causes ar_osal timewait crash at the DSP to |
| * route just one RX/TX path with new GKV and add graph for feedback path |
| * since other TX/RX path still intact with SWB codec. Thus, forcedly route |
| * other path also to WB/NB if it's active on SCO device. |
| */ |
| if (matchFound && ((strAttr.type == PAL_STREAM_VOIP_RX && |
| newDeviceId == PAL_DEVICE_OUT_BLUETOOTH_SCO && |
| rm->isDeviceActive(PAL_DEVICE_IN_BLUETOOTH_SCO_HEADSET)) || |
| (strAttr.type == PAL_STREAM_VOIP_TX && |
| newDeviceId == PAL_DEVICE_IN_BLUETOOTH_SCO_HEADSET && |
| rm->isDeviceActive(PAL_DEVICE_OUT_BLUETOOTH_SCO)))) { |
| struct pal_device sco_Dattr = {}; |
| std::shared_ptr<Device> scoDev = nullptr; |
| std::vector <Stream*> activeStreams; |
| if (newDeviceId == PAL_DEVICE_OUT_BLUETOOTH_SCO) { |
| sco_Dattr.id = PAL_DEVICE_IN_BLUETOOTH_SCO_HEADSET; |
| } else { |
| sco_Dattr.id = PAL_DEVICE_OUT_BLUETOOTH_SCO; |
| } |
| scoDev = Device::getInstance(&sco_Dattr, rm); |
| status = rm->getDeviceConfig(&sco_Dattr, NULL); |
| if (status) { |
| PAL_ERR(LOG_TAG, "getDeviceConfig for bt-sco failed"); |
| mStreamMutex.unlock(); |
| rm->unlockActiveStream(); |
| return 0; |
| } |
| |
| rm->getActiveStream_l(activeStreams, scoDev); |
| for (sIter = activeStreams.begin(); sIter != activeStreams.end(); sIter++) { |
| (*sIter)->lockStreamMutex(); |
| streamDevDisconnect.push_back({ (*sIter), sco_Dattr.id }); |
| StreamDevConnect.push_back({ (*sIter), &sco_Dattr }); |
| (*sIter)->unlockStreamMutex(); |
| } |
| } |
| } else { |
| rm->updateSndName(newDeviceId, deviceInfo.sndDevName); |
| matchFound = true; |
| /* |
| * check if virtual port group config needs to update, this handles scenario below: |
| * upd is already active on ultrasound device, active audio stream switched |
| * from other devices to speaker/handset, so group config needs to be updated to |
| * concurrency config |
| */ |
| status = rm->checkAndUpdateGroupDevConfig(&newDevices[newDeviceSlots[i]], mStreamAttr, |
| streamsToSwitch, &streamDevAttr, true); |
| if (status) { |
| PAL_ERR(LOG_TAG, "no valid group device config found"); |
| streamsToSwitch.clear(); |
| } |
| if (!streamsToSwitch.empty()) { |
| for(sIter = streamsToSwitch.begin(); sIter != streamsToSwitch.end(); sIter++) { |
| streamDevDisconnect.push_back({(*sIter), streamDevAttr.id}); |
| StreamDevConnect.push_back({(*sIter), &streamDevAttr}); |
| } |
| } |
| streamsToSwitch.clear(); |
| |
| } |
| // check if headset config needs to update when haptics is active |
| rm->checkHapticsConcurrency(&newDevices[newDeviceSlots[i]], NULL, streamsToSwitch/* not used */, NULL); |
| /* |
| * switch all streams that are running on the current device if |
| * switching device for Voice Call |
| */ |
| sharedBEStreamDev.clear(); |
| for (int j = 0; j < mDevices.size(); j++) { |
| uint32_t mDeviceId = mDevices[j]->getSndDeviceId(); |
| if (rm->matchDevDir(newDeviceId, mDeviceId) && newDeviceId != mDeviceId) { |
| if (mDeviceId == PAL_DEVICE_OUT_PROXY || newDeviceId == PAL_DEVICE_OUT_PROXY) |
| continue; |
| rm->getSharedBEActiveStreamDevs(sharedBEStreamDev, mDevices[j]->getSndDeviceId()); |
| if (strAttr.type == PAL_STREAM_VOICE_CALL && |
| newDeviceId != PAL_DEVICE_OUT_HEARING_AID) { |
| voice_call_switch = true; |
| } |
| if (voice_call_switch) { |
| for (const auto &elem : sharedBEStreamDev) { |
| struct pal_stream_attributes sAttr; |
| Stream *strm = std::get<0>(elem); |
| pal_device_id_t newDeviceId = newDevices[newDeviceSlots[i]].id; |
| int status = strm->getStreamAttributes(&sAttr); |
| |
| if (status) { |
| PAL_ERR(LOG_TAG,"getStreamAttributes Failed \n"); |
| mStreamMutex.unlock(); |
| rm->unlockActiveStream(); |
| goto done; |
| } |
| |
| if (sAttr.type == PAL_STREAM_ULTRASOUND && |
| (newDeviceId != PAL_DEVICE_OUT_HANDSET && newDeviceId != PAL_DEVICE_OUT_SPEAKER)) { |
| PAL_DBG(LOG_TAG, "Ultrasound stream running on speaker/handset. Not switching to device (%d)", newDeviceId); |
| continue; |
| } |
| |
| streamDevDisconnect.push_back(elem); |
| StreamDevConnect.push_back({std::get<0>(elem), &newDevices[newDeviceSlots[i]]}); |
| } |
| } |
| } |
| } |
| |
| /* Add device associated with current stream to streamDevDisconnect/StreamDevConnect list */ |
| for (int j = 0; j < disconnectCount; j++) { |
| // check to make sure device direction is the same |
| if (rm->matchDevDir(mDevices[curDeviceSlots[j]]->getSndDeviceId(), newDeviceId)) { |
| streamDevDisconnect.push_back({streamHandle, mDevices[curDeviceSlots[j]]->getSndDeviceId()}); |
| // if something disconnected incoming device and current dev diff so push on a switchwe need to add the deivce |
| matchFound = true; |
| // for switching from speaker/handset to other devices, if this is the last stream that active |
| // on speaker/handset, need to update group config from concurrency to standalone |
| // if switching happens between speaker & handset, skip the group config check as they're shared backend |
| if ((mDevices[curDeviceSlots[j]]->getDeviceCount() == 1) && |
| (newDeviceId != PAL_DEVICE_OUT_SPEAKER && |
| newDeviceId != PAL_DEVICE_OUT_HANDSET)) { |
| mDevices[curDeviceSlots[j]]->getDeviceAttributes(&dAttr); |
| status = rm->checkAndUpdateGroupDevConfig(&dAttr, mStreamAttr, streamsToSwitch, &streamDevAttr, false); |
| if (status) { |
| PAL_ERR(LOG_TAG, "no valid group device config found"); |
| streamsToSwitch.clear(); |
| } |
| if (!streamsToSwitch.empty()) { |
| for(sIter = streamsToSwitch.begin(); sIter != streamsToSwitch.end(); sIter++) { |
| streamDevDisconnect.push_back({(*sIter), streamDevAttr.id}); |
| StreamDevConnect.push_back({(*sIter), &streamDevAttr}); |
| } |
| } |
| streamsToSwitch.clear(); |
| } |
| } |
| } |
| |
| if (matchFound || (disconnectCount == 0)) { |
| StreamDevConnect.push_back({streamHandle, &newDevices[newDeviceSlots[i]]}); |
| } |
| } |
| |
| /* Handle scenario when there is only device to disconnect. |
| e.g. case 3 : device switch from spkr+hs to spkr */ |
| if (connectCount == 0) { |
| for (int j = 0; j < disconnectCount; j++) { |
| if (rm->matchDevDir(mDevices[curDeviceSlots[j]]->getSndDeviceId(), newDevices[0].id)) |
| streamDevDisconnect.push_back({streamHandle, mDevices[curDeviceSlots[j]]->getSndDeviceId()}); |
| } |
| } |
| |
| /* Check if there is device to disconnect or connect */ |
| if (!streamDevDisconnect.size() && !StreamDevConnect.size()) { |
| PAL_INFO(LOG_TAG, "No device to switch, returning"); |
| mStreamMutex.unlock(); |
| rm->unlockActiveStream(); |
| goto done; |
| } |
| mStreamMutex.unlock(); |
| rm->unlockActiveStream(); |
| |
| status = rm->streamDevSwitch(streamDevDisconnect, StreamDevConnect); |
| if (status) { |
| PAL_ERR(LOG_TAG, "Device switch failed"); |
| } |
| |
| done: |
| mStreamMutex.lock(); |
| if (a2dpMuted) { |
| if (mVolumeData) { |
| volume = (struct pal_volume_data *)calloc(1, (sizeof(uint32_t) + |
| (sizeof(struct pal_channel_vol_kv) * (mVolumeData->no_of_volpair)))); |
| } |
| if (!volume) { |
| PAL_ERR(LOG_TAG, "pal_volume_data memory allocation failure"); |
| mStreamMutex.unlock(); |
| rm->unlockActiveStream(); |
| return -ENOMEM; |
| } |
| status = streamHandle->getVolumeData(volume); |
| if (status) { |
| PAL_ERR(LOG_TAG, "getVolumeData failed %d", status); |
| } |
| a2dpMuted = false; |
| status = streamHandle->setVolume(volume); //apply cached volume. |
| if (status) { |
| PAL_ERR(LOG_TAG, "setVolume failed %d", status); |
| } |
| mute_l(false); |
| if (volume) { |
| free(volume); |
| } |
| } |
| if ((numDev > 1) && isNewDeviceA2dp && !isBtReady) { |
| suspendedDevIds.clear(); |
| suspendedDevIds.push_back(newBtDevId); |
| suspendedDevIds.push_back(PAL_DEVICE_OUT_SPEAKER); |
| } else { |
| suspendedDevIds.clear(); |
| } |
| mStreamMutex.unlock(); |
| return status; |
| } |
| |
| bool Stream::checkStreamMatch(pal_device_id_t pal_device_id, |
| pal_stream_type_t pal_stream_type) |
| { |
| int status = 0; |
| struct pal_device dAttr; |
| bool match = false; |
| |
| if (!mStreamAttr) { |
| PAL_ERR(LOG_TAG, "stream attribute is null"); |
| return false; |
| } |
| |
| if (pal_stream_type == mStreamAttr->type || |
| pal_stream_type == PAL_STREAM_GENERIC) |
| match = true; |
| else |
| return false; |
| |
| //device |
| for (int i = 0; i < mDevices.size();i++) { |
| status = mDevices[i]->getDeviceAttributes(&dAttr); |
| if (0 != status) { |
| PAL_ERR(LOG_TAG,"getDeviceAttributes Failed \n"); |
| return false; |
| } |
| if (pal_device_id == dAttr.id || pal_device_id == PAL_DEVICE_NONE) { |
| match = true; |
| // as long as one device matches, it is enough. |
| break; |
| } else |
| match = false; |
| } |
| |
| return match; |
| } |
| |
| int Stream::initStreamSmph() |
| { |
| return sem_init(&mInUse, 0, 1); |
| } |
| |
| int Stream::deinitStreamSmph() |
| { |
| return sem_destroy(&mInUse); |
| } |
| |
| int Stream::postStreamSmph() |
| { |
| return sem_post(&mInUse); |
| } |
| |
| int Stream::waitStreamSmph() |
| { |
| return sem_wait(&mInUse); |
| } |
| |
| void Stream::handleStreamException(struct pal_stream_attributes *attributes, |
| pal_stream_callback cb, uint64_t cookie) |
| { |
| if (!attributes || !cb) { |
| PAL_ERR(LOG_TAG,"Invalid params for exception handling \n"); |
| return; |
| } |
| |
| if (attributes->type == PAL_STREAM_COMPRESSED) { |
| PAL_DBG(LOG_TAG, "Exception for Compressed clip"); |
| // Notify flinger about the error occurred while creating compress |
| // offload session |
| cb(NULL, PAL_STREAM_CBK_EVENT_ERROR, 0, 0, cookie); |
| |
| } |
| } |
| |
| /* GetPalDevice only applies to Sound Trigger streams */ |
| std::shared_ptr<Device> Stream::GetPalDevice(Stream *streamHandle, pal_device_id_t dev_id) |
| { |
| std::shared_ptr<CaptureProfile> cap_prof = nullptr; |
| std::shared_ptr<Device> device = nullptr; |
| StreamSoundTrigger *st_st = nullptr; |
| StreamACD *st_acd = nullptr; |
| StreamSensorPCMData *st_sns_pcm_data = nullptr; |
| struct pal_device dev; |
| |
| if (!streamHandle) { |
| PAL_ERR(LOG_TAG, "Stream is invalid"); |
| goto exit; |
| } |
| |
| if (!mStreamAttr) { |
| PAL_ERR(LOG_TAG, "Stream attribute is null"); |
| goto exit; |
| } |
| |
| PAL_DBG(LOG_TAG, "Enter, stream: %d, device_id: %d", mStreamAttr->type, dev_id); |
| |
| if (mStreamAttr->type == PAL_STREAM_VOICE_UI) { |
| st_st = dynamic_cast<StreamSoundTrigger*>(streamHandle); |
| cap_prof = st_st->GetCurrentCaptureProfile(); |
| } else if (mStreamAttr->type == PAL_STREAM_ACD) { |
| st_acd = dynamic_cast<StreamACD*>(streamHandle); |
| cap_prof = st_acd->GetCurrentCaptureProfile(); |
| } else { |
| st_sns_pcm_data = dynamic_cast<StreamSensorPCMData*>(streamHandle); |
| cap_prof = st_sns_pcm_data->GetCurrentCaptureProfile(); |
| } |
| |
| if (!cap_prof && !rm->GetSoundTriggerCaptureProfile()) { |
| PAL_ERR(LOG_TAG, "Failed to get local and common cap_prof for stream: %d", |
| mStreamAttr->type); |
| goto exit; |
| } |
| |
| if (rm->GetSoundTriggerCaptureProfile()) { |
| /* Use the rm's common capture profile if local capture profile is not |
| * available, or the common capture profile has the highest priority. |
| */ |
| if (!cap_prof || rm->GetSoundTriggerCaptureProfile()->ComparePriority(cap_prof) > 0) { |
| PAL_DBG(LOG_TAG, "common cap_prof %s has the highest priority.", |
| rm->GetSoundTriggerCaptureProfile()->GetName().c_str()); |
| cap_prof = rm->GetSoundTriggerCaptureProfile(); |
| } |
| } |
| |
| dev.id = dev_id; |
| dev.config.bit_width = cap_prof->GetBitWidth(); |
| dev.config.ch_info.channels = cap_prof->GetChannels(); |
| dev.config.sample_rate = cap_prof->GetSampleRate(); |
| dev.config.aud_fmt_id = PAL_AUDIO_FMT_PCM_S16_LE; |
| |
| device = Device::getInstance(&dev, rm); |
| if (!device) { |
| PAL_ERR(LOG_TAG, "Failed to get device instance"); |
| goto exit; |
| } |
| |
| device->setDeviceAttributes(dev); |
| device->setSndName(cap_prof->GetSndName()); |
| |
| exit: |
| PAL_DBG(LOG_TAG, "Exit"); |
| return device; |
| } |
| |
| void Stream::setCachedState(stream_state_t state) |
| { |
| mStreamMutex.lock(); |
| cachedState = state; |
| PAL_DBG(LOG_TAG, "set cachedState to %d", cachedState); |
| mStreamMutex.unlock(); |
| } |