Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c337a879 authored by Mikhail Naganov's avatar Mikhail Naganov
Browse files

audio: Move tinyALSA-specific code to Module/StreamAlsa

Extract code interacting with tinyALSA which is not
specific to USB into "abstract" module and stream
implementations ModuleAlsa and StreamAlsa. Also, move
utility code which does not need module or stream
context into Utils.

This facilitates implementation of the CF core HAL which
also uses tinyALSA, allowing to share common code.

Bug: 264712385
Test: atest VtsHalAudioCoreTargetTest

Change-Id: I2134b15e970c78e8a48b254e15199b8207a8ab34
parent d74d81bf
Loading
Loading
Loading
Loading
+4 −1
Original line number Original line Diff line number Diff line
@@ -77,6 +77,10 @@ cc_library {
        "Stream.cpp",
        "Stream.cpp",
        "StreamStub.cpp",
        "StreamStub.cpp",
        "Telephony.cpp",
        "Telephony.cpp",
        "alsa/Mixer.cpp",
        "alsa/ModuleAlsa.cpp",
        "alsa/StreamAlsa.cpp",
        "alsa/Utils.cpp",
        "r_submix/ModuleRemoteSubmix.cpp",
        "r_submix/ModuleRemoteSubmix.cpp",
        "r_submix/RemoteSubmixUtils.cpp",
        "r_submix/RemoteSubmixUtils.cpp",
        "r_submix/SubmixRoute.cpp",
        "r_submix/SubmixRoute.cpp",
@@ -84,7 +88,6 @@ cc_library {
        "usb/ModuleUsb.cpp",
        "usb/ModuleUsb.cpp",
        "usb/StreamUsb.cpp",
        "usb/StreamUsb.cpp",
        "usb/UsbAlsaMixerControl.cpp",
        "usb/UsbAlsaMixerControl.cpp",
        "usb/UsbAlsaUtils.cpp",
    ],
    ],
    generated_sources: [
    generated_sources: [
        "audio_policy_configuration_aidl_default",
        "audio_policy_configuration_aidl_default",
+154 −0
Original line number Original line Diff line number Diff line
/*
 * 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_AlsaMixer"
#include <android-base/logging.h>

#include <cmath>

#include <android/binder_status.h>

#include "Mixer.h"

namespace aidl::android::hardware::audio::core::alsa {

//-----------------------------------------------------------------------------

MixerControl::MixerControl(struct mixer_ctl* ctl)
    : mCtl(ctl),
      mNumValues(mixer_ctl_get_num_values(ctl)),
      mMinValue(mixer_ctl_get_range_min(ctl)),
      mMaxValue(mixer_ctl_get_range_max(ctl)) {}

unsigned int MixerControl::getNumValues() const {
    return mNumValues;
}

int MixerControl::getMaxValue() const {
    return mMaxValue;
}

int MixerControl::getMinValue() const {
    return mMinValue;
}

int MixerControl::setArray(const void* array, size_t count) {
    const std::lock_guard guard(mLock);
    return mixer_ctl_set_array(mCtl, array, count);
}

//-----------------------------------------------------------------------------

// static
const std::map<Mixer::Control, std::vector<Mixer::ControlNamesAndExpectedCtlType>>
        Mixer::kPossibleControls = {
                {Mixer::MASTER_SWITCH, {{"Master Playback Switch", MIXER_CTL_TYPE_BOOL}}},
                {Mixer::MASTER_VOLUME, {{"Master Playback Volume", MIXER_CTL_TYPE_INT}}},
                {Mixer::HW_VOLUME,
                 {{"Headphone Playback Volume", MIXER_CTL_TYPE_INT},
                  {"Headset Playback Volume", MIXER_CTL_TYPE_INT},
                  {"PCM Playback Volume", MIXER_CTL_TYPE_INT}}}};

// static
std::map<Mixer::Control, std::shared_ptr<MixerControl>> Mixer::initializeMixerControls(
        struct mixer* mixer) {
    std::map<Mixer::Control, std::shared_ptr<MixerControl>> mixerControls;
    std::string mixerCtlNames;
    for (const auto& [control, possibleCtls] : kPossibleControls) {
        for (const auto& [ctlName, expectedCtlType] : possibleCtls) {
            struct mixer_ctl* ctl = mixer_get_ctl_by_name(mixer, ctlName.c_str());
            if (ctl != nullptr && mixer_ctl_get_type(ctl) == expectedCtlType) {
                mixerControls.emplace(control, std::make_unique<MixerControl>(ctl));
                if (!mixerCtlNames.empty()) {
                    mixerCtlNames += ",";
                }
                mixerCtlNames += ctlName;
                break;
            }
        }
    }
    LOG(DEBUG) << __func__ << ": available mixer control names=[" << mixerCtlNames << "]";
    return mixerControls;
}

Mixer::Mixer(struct mixer* mixer)
    : mMixer(mixer), mMixerControls(initializeMixerControls(mMixer)) {}

Mixer::~Mixer() {
    mixer_close(mMixer);
}

namespace {

int volumeFloatToInteger(float fValue, int maxValue, int minValue) {
    return minValue + std::ceil((maxValue - minValue) * fValue);
}

}  // namespace

ndk::ScopedAStatus Mixer::setMasterMute(bool muted) {
    auto it = mMixerControls.find(Mixer::MASTER_SWITCH);
    if (it == mMixerControls.end()) {
        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
    }
    const int numValues = it->second->getNumValues();
    std::vector<int> values(numValues, muted ? 0 : 1);
    if (int err = it->second->setArray(values.data(), numValues); err != 0) {
        LOG(ERROR) << __func__ << ": failed to set master mute, err=" << err;
        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
    }
    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus Mixer::setMasterVolume(float volume) {
    auto it = mMixerControls.find(Mixer::MASTER_VOLUME);
    if (it == mMixerControls.end()) {
        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
    }
    const int numValues = it->second->getNumValues();
    std::vector<int> values(numValues, volumeFloatToInteger(volume, it->second->getMaxValue(),
                                                            it->second->getMinValue()));
    if (int err = it->second->setArray(values.data(), numValues); err != 0) {
        LOG(ERROR) << __func__ << ": failed to set master volume, err=" << err;
        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
    }
    return ndk::ScopedAStatus::ok();
}

ndk::ScopedAStatus Mixer::setVolumes(const std::vector<float>& volumes) {
    auto it = mMixerControls.find(Mixer::HW_VOLUME);
    if (it == mMixerControls.end()) {
        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
    }
    const int numValues = it->second->getNumValues();
    if (numValues < 0) {
        LOG(FATAL) << __func__ << ": negative number of values: " << numValues;
    }
    const int maxValue = it->second->getMaxValue();
    const int minValue = it->second->getMinValue();
    std::vector<int> values;
    size_t i = 0;
    for (; i < static_cast<size_t>(numValues) && i < values.size(); ++i) {
        values.emplace_back(volumeFloatToInteger(volumes[i], maxValue, minValue));
    }
    if (int err = it->second->setArray(values.data(), values.size()); err != 0) {
        LOG(ERROR) << __func__ << ": failed to set volume, err=" << err;
        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
    }
    return ndk::ScopedAStatus::ok();
}

}  // namespace aidl::android::hardware::audio::core::alsa
+82 −0
Original line number Original line Diff line number Diff line
/*
 * 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.
 */

#pragma once

#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <vector>

#include <android-base/thread_annotations.h>
#include <android/binder_auto_utils.h>

extern "C" {
#include <tinyalsa/mixer.h>
}

namespace aidl::android::hardware::audio::core::alsa {

class MixerControl {
  public:
    explicit MixerControl(struct mixer_ctl* ctl);

    unsigned int getNumValues() const;
    int getMaxValue() const;
    int getMinValue() const;
    int setArray(const void* array, size_t count);

  private:
    std::mutex mLock;
    // The mixer_ctl object is owned by ALSA and will be released when the mixer is closed.
    struct mixer_ctl* mCtl GUARDED_BY(mLock);
    const unsigned int mNumValues;
    const int mMinValue;
    const int mMaxValue;
};

class Mixer {
  public:
    explicit Mixer(struct mixer* mixer);

    ~Mixer();

    bool isValid() const { return mMixer != nullptr; }

    ndk::ScopedAStatus setMasterMute(bool muted);
    ndk::ScopedAStatus setMasterVolume(float volume);
    ndk::ScopedAStatus setVolumes(const std::vector<float>& volumes);

  private:
    enum Control {
        MASTER_SWITCH,
        MASTER_VOLUME,
        HW_VOLUME,
    };
    using ControlNamesAndExpectedCtlType = std::pair<std::string, enum mixer_ctl_type>;
    static const std::map<Control, std::vector<ControlNamesAndExpectedCtlType>> kPossibleControls;
    static std::map<Control, std::shared_ptr<MixerControl>> initializeMixerControls(
            struct mixer* mixer);

    // The mixer object is owned by ALSA and will be released when the mixer is closed.
    struct mixer* mMixer;
    // `mMixerControls` will only be initialized in constructor. After that, it wil only be
    // read but not be modified.
    const std::map<Control, std::shared_ptr<MixerControl>> mMixerControls;
};

}  // namespace aidl::android::hardware::audio::core::alsa
+67 −0
Original line number Original line Diff line number Diff line
/*
 * 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_ModuleAlsa"

#include <vector>

#include <android-base/logging.h>

#include "Utils.h"
#include "core-impl/ModuleAlsa.h"

extern "C" {
#include "alsa_device_profile.h"
}

using aidl::android::media::audio::common::AudioChannelLayout;
using aidl::android::media::audio::common::AudioFormatType;
using aidl::android::media::audio::common::AudioPort;
using aidl::android::media::audio::common::AudioProfile;

namespace aidl::android::hardware::audio::core {

ndk::ScopedAStatus ModuleAlsa::populateConnectedDevicePort(AudioPort* audioPort) {
    auto deviceProfile = alsa::getDeviceProfile(*audioPort);
    if (!deviceProfile.has_value()) {
        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
    }
    auto profile = alsa::readAlsaDeviceInfo(*deviceProfile);
    if (!profile.has_value()) {
        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
    }

    std::vector<AudioChannelLayout> channels = alsa::getChannelMasksFromProfile(&profile.value());
    std::vector<int> sampleRates = alsa::getSampleRatesFromProfile(&profile.value());

    for (size_t i = 0; i < std::min(MAX_PROFILE_FORMATS, AUDIO_PORT_MAX_AUDIO_PROFILES) &&
                       profile->formats[i] != PCM_FORMAT_INVALID;
         ++i) {
        auto audioFormatDescription =
                alsa::c2aidl_pcm_format_AudioFormatDescription(profile->formats[i]);
        if (audioFormatDescription.type == AudioFormatType::DEFAULT) {
            LOG(WARNING) << __func__ << ": unknown pcm type=" << profile->formats[i];
            continue;
        }
        AudioProfile audioProfile = {.format = audioFormatDescription,
                                     .channelMasks = channels,
                                     .sampleRates = sampleRates};
        audioPort->profiles.push_back(std::move(audioProfile));
    }
    return ndk::ScopedAStatus::ok();
}

}  // namespace aidl::android::hardware::audio::core
+103 −0
Original line number Original line Diff line number Diff line
/*
 * 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.
 */

#include <limits>

#define LOG_TAG "AHAL_StreamAlsa"
#include <android-base/logging.h>

#include <Utils.h>
#include <error/expected_utils.h>

#include "core-impl/StreamAlsa.h"

namespace aidl::android::hardware::audio::core {

StreamAlsa::StreamAlsa(const Metadata& metadata, StreamContext&& context)
    : StreamCommonImpl(metadata, std::move(context)),
      mFrameSizeBytes(getContext().getFrameSize()),
      mIsInput(isInput(metadata)),
      mConfig(alsa::getPcmConfig(getContext(), mIsInput)) {}

::android::status_t StreamAlsa::init() {
    return mConfig.has_value() ? ::android::OK : ::android::NO_INIT;
}

::android::status_t StreamAlsa::standby() {
    mAlsaDeviceProxies.clear();
    return ::android::OK;
}

::android::status_t StreamAlsa::start() {
    decltype(mAlsaDeviceProxies) alsaDeviceProxies;
    for (const auto& device : getDeviceProfiles()) {
        auto profile = alsa::readAlsaDeviceInfo(device);
        if (!profile.has_value()) {
            LOG(ERROR) << __func__ << ": unable to read device info, device address=" << device;
            return ::android::UNKNOWN_ERROR;
        }

        auto proxy = alsa::makeDeviceProxy();
        // 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.value(),
                                    const_cast<struct pcm_config*>(&mConfig.value()),
                                    true /*require_exact_match*/);
            err != 0) {
            LOG(ERROR) << __func__ << ": fail to prepare for device address=" << device
                       << " error=" << err;
            return ::android::UNKNOWN_ERROR;
        }
        if (int err = proxy_open(proxy.get()); err != 0) {
            LOG(ERROR) << __func__ << ": failed to open device, address=" << device
                       << " error=" << err;
            return ::android::UNKNOWN_ERROR;
        }
        alsaDeviceProxies.push_back(std::move(proxy));
    }
    mAlsaDeviceProxies = std::move(alsaDeviceProxies);
    return ::android::OK;
}

::android::status_t StreamAlsa::transfer(void* buffer, size_t frameCount, size_t* actualFrameCount,
                                         int32_t* latencyMs) {
    const size_t bytesToTransfer = frameCount * mFrameSizeBytes;
    unsigned maxLatency = 0;
    if (mIsInput) {
        if (mAlsaDeviceProxies.empty()) {
            LOG(FATAL) << __func__ << ": no input devices";
            return ::android::NO_INIT;
        }
        // For input case, only support single device.
        proxy_read(mAlsaDeviceProxies[0].get(), buffer, bytesToTransfer);
        maxLatency = proxy_get_latency(mAlsaDeviceProxies[0].get());
    } else {
        for (auto& proxy : mAlsaDeviceProxies) {
            proxy_write(proxy.get(), buffer, bytesToTransfer);
            maxLatency = std::max(maxLatency, proxy_get_latency(proxy.get()));
        }
    }
    *actualFrameCount = frameCount;
    maxLatency = std::min(maxLatency, static_cast<unsigned>(std::numeric_limits<int32_t>::max()));
    *latencyMs = maxLatency;
    return ::android::OK;
}

void StreamAlsa::shutdown() {
    mAlsaDeviceProxies.clear();
}

}  // namespace aidl::android::hardware::audio::core
Loading