/* * Copyright (C) 2023 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 "AHAL_StreamUsb" #include #include #include "UsbAlsaMixerControl.h" #include "UsbAlsaUtils.h" #include "core-impl/Module.h" #include "core-impl/StreamUsb.h" extern "C" { #include "alsa_device_profile.h" } using aidl::android::hardware::audio::common::getChannelCount; using aidl::android::hardware::audio::common::SinkMetadata; using aidl::android::hardware::audio::common::SourceMetadata; using aidl::android::media::audio::common::AudioDevice; using aidl::android::media::audio::common::AudioDeviceAddress; using aidl::android::media::audio::common::AudioOffloadInfo; using aidl::android::media::audio::common::AudioPortExt; using aidl::android::media::audio::common::MicrophoneDynamicInfo; using aidl::android::media::audio::common::MicrophoneInfo; using android::OK; using android::status_t; namespace aidl::android::hardware::audio::core { DriverUsb::DriverUsb(const StreamContext& context, bool isInput) : mFrameSizeBytes(context.getFrameSize()), mIsInput(isInput) { struct pcm_config config; config.channels = usb::getChannelCountFromChannelMask(context.getChannelLayout(), isInput); if (config.channels == 0) { LOG(ERROR) << __func__ << ": invalid channel=" << context.getChannelLayout().toString(); return; } config.format = usb::aidl2legacy_AudioFormatDescription_pcm_format(context.getFormat()); if (config.format == PCM_FORMAT_INVALID) { LOG(ERROR) << __func__ << ": invalid format=" << context.getFormat().toString(); return; } config.rate = context.getSampleRate(); if (config.rate == 0) { LOG(ERROR) << __func__ << ": invalid sample rate=" << config.rate; return; } mConfig = config; } ::android::status_t DriverUsb::init() { return mConfig.has_value() ? ::android::OK : ::android::NO_INIT; } ::android::status_t DriverUsb::setConnectedDevices( const std::vector& connectedDevices) { if (mIsInput && connectedDevices.size() > 1) { LOG(ERROR) << __func__ << ": wrong device size(" << connectedDevices.size() << ") for input stream"; return ::android::BAD_VALUE; } for (const auto& connectedDevice : connectedDevices) { if (connectedDevice.address.getTag() != AudioDeviceAddress::alsa) { LOG(ERROR) << __func__ << ": bad device address" << connectedDevice.address.toString(); return ::android::BAD_VALUE; } } std::lock_guard guard(mLock); mAlsaDeviceProxies.clear(); mConnectedDevices.clear(); for (const auto& connectedDevice : connectedDevices) { mConnectedDevices.push_back(connectedDevice.address); } return ::android::OK; } ::android::status_t DriverUsb::drain(StreamDescriptor::DrainMode) { usleep(1000); return ::android::OK; } ::android::status_t DriverUsb::flush() { usleep(1000); return ::android::OK; } ::android::status_t DriverUsb::pause() { usleep(1000); return ::android::OK; } ::android::status_t DriverUsb::transfer(void* buffer, size_t frameCount, size_t* actualFrameCount, int32_t* latencyMs) { if (!mConfig.has_value() || mConnectedDevices.empty()) { LOG(ERROR) << __func__ << ": failed, has config: " << mConfig.has_value() << ", has connected devices: " << mConnectedDevices.empty(); return ::android::NO_INIT; } if (mIsStandby) { if (::android::status_t status = exitStandby(); status != ::android::OK) { LOG(ERROR) << __func__ << ": failed to exit standby, status=" << status; return status; } } std::vector> alsaDeviceProxies; { std::lock_guard guard(mLock); alsaDeviceProxies = mAlsaDeviceProxies; } const size_t bytesToTransfer = frameCount * mFrameSizeBytes; if (mIsInput) { // For input case, only support single device. proxy_read(alsaDeviceProxies[0].get(), buffer, bytesToTransfer); } else { for (auto& proxy : alsaDeviceProxies) { proxy_write(proxy.get(), buffer, bytesToTransfer); } } *actualFrameCount = frameCount; *latencyMs = Module::kLatencyMs; return ::android::OK; } ::android::status_t DriverUsb::standby() { if (!mIsStandby) { std::lock_guard guard(mLock); mAlsaDeviceProxies.clear(); mIsStandby = true; } return ::android::OK; } ::android::status_t DriverUsb::exitStandby() { std::vector connectedDevices; { std::lock_guard guard(mLock); connectedDevices = mConnectedDevices; } std::vector> alsaDeviceProxies; for (const auto& device : connectedDevices) { alsa_device_profile profile; profile_init(&profile, mIsInput ? PCM_IN : PCM_OUT); profile.card = device.get()[0]; profile.device = device.get()[1]; if (!profile_read_device_info(&profile)) { LOG(ERROR) << __func__ << ": unable to read device info, device address=" << device.toString(); return ::android::UNKNOWN_ERROR; } auto proxy = std::shared_ptr(new alsa_device_proxy(), [](alsa_device_proxy* proxy) { proxy_close(proxy); free(proxy); }); // Always ask for alsa configure as required since the configuration should be supported // by the connected device. That is guaranteed by `setAudioPortConfig` and // `setAudioPatch`. if (int err = proxy_prepare(proxy.get(), &profile, &mConfig.value(), true /*is_bit_perfect*/); err != 0) { LOG(ERROR) << __func__ << ": fail to prepare for device address=" << device.toString() << " error=" << err; return ::android::UNKNOWN_ERROR; } if (int err = proxy_open(proxy.get()); err != 0) { LOG(ERROR) << __func__ << ": failed to open device, address=" << device.toString() << " error=" << err; return ::android::UNKNOWN_ERROR; } alsaDeviceProxies.push_back(std::move(proxy)); } { std::lock_guard guard(mLock); mAlsaDeviceProxies = alsaDeviceProxies; } mIsStandby = false; return ::android::OK; } // static ndk::ScopedAStatus StreamInUsb::createInstance(const SinkMetadata& sinkMetadata, StreamContext&& context, const std::vector& microphones, std::shared_ptr* result) { std::shared_ptr stream = ndk::SharedRefBase::make(sinkMetadata, std::move(context), microphones); if (auto status = initInstance(stream); !status.isOk()) { return status; } *result = std::move(stream); return ndk::ScopedAStatus::ok(); } StreamInUsb::StreamInUsb(const SinkMetadata& sinkMetadata, StreamContext&& context, const std::vector& microphones) : StreamIn( sinkMetadata, std::move(context), [](const StreamContext& ctx) -> DriverInterface* { return new DriverUsb(ctx, true /*isInput*/); }, [](const StreamContext& ctx, DriverInterface* driver) -> StreamWorkerInterface* { // The default worker implementation is used. return new StreamInWorker(ctx, driver); }, microphones) {} ndk::ScopedAStatus StreamInUsb::getActiveMicrophones( std::vector* _aidl_return __unused) { LOG(DEBUG) << __func__ << ": not supported"; return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } // static ndk::ScopedAStatus StreamOutUsb::createInstance(const SourceMetadata& sourceMetadata, StreamContext&& context, const std::optional& offloadInfo, std::shared_ptr* result) { if (offloadInfo.has_value()) { LOG(ERROR) << __func__ << ": offload is not supported"; return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); } std::shared_ptr stream = ndk::SharedRefBase::make(sourceMetadata, std::move(context), offloadInfo); if (auto status = initInstance(stream); !status.isOk()) { return status; } *result = std::move(stream); return ndk::ScopedAStatus::ok(); } StreamOutUsb::StreamOutUsb(const SourceMetadata& sourceMetadata, StreamContext&& context, const std::optional& offloadInfo) : StreamOut( sourceMetadata, std::move(context), [](const StreamContext& ctx) -> DriverInterface* { return new DriverUsb(ctx, false /*isInput*/); }, [](const StreamContext& ctx, DriverInterface* driver) -> StreamWorkerInterface* { // The default worker implementation is used. return new StreamOutWorker(ctx, driver); }, offloadInfo) { mChannelCount = getChannelCount(mContext.getChannelLayout()); } ndk::ScopedAStatus StreamOutUsb::getHwVolume(std::vector* _aidl_return) { *_aidl_return = mHwVolumes; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus StreamOutUsb::setHwVolume(const std::vector& in_channelVolumes) { for (const auto& device : mConnectedDevices) { if (device.address.getTag() != AudioDeviceAddress::alsa) { LOG(DEBUG) << __func__ << ": skip as the device address is not alsa"; continue; } const int card = device.address.get()[0]; if (auto result = usb::UsbAlsaMixerControl::getInstance().setVolumes(card, in_channelVolumes); !result.isOk()) { LOG(ERROR) << __func__ << ": failed to set volume for device, card=" << card; return result; } } mHwVolumes = in_channelVolumes; return ndk::ScopedAStatus::ok(); } } // namespace aidl::android::hardware::audio::core