~aleteoryx/muditaos

6e600784c7a54d8969e9a6c15e0dd385f8cbf8fd — Lefucjusz 1 year, 6 months ago 49f432c
[MOS-1069] Change A2DP stream volume scale to exponential

Fix of the issue that A2DP stream volume
was controlled using naive approach
with linear scaling instead of
exponential one, what resulted in
highly non-linear volume control
experience.
M module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.cpp => module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.cpp +48 -28
@@ 1,4 1,4 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "BluetoothAudioDevice.hpp"


@@ 10,7 10,8 @@

#include <Audio/AudioCommon.hpp>
#include <Audio/VolumeScaler.hpp>
#include <Audio/Stream.hpp>

#include <Utils.hpp>

#include <chrono>
#include <cassert>


@@ 215,48 216,66 @@ void CVSDAudioDevice::enableInput()

auto BluetoothAudioDevice::fillSbcAudioBuffer() -> int
{
    // perform sbc encodin
    int totalNumBytesRead                    = 0;
    unsigned int numAudioSamplesPerSbcBuffer = btstack_sbc_encoder_num_audio_frames();
    auto context                             = &AVRCP::mediaTracker;

    assert(context != nullptr);
    const auto audioSamplesPerSbcBuffer      = btstack_sbc_encoder_num_audio_frames();
    const auto context                       = &AVRCP::mediaTracker;

    while (context->samples_ready >= numAudioSamplesPerSbcBuffer &&
           (context->max_media_payload_size - context->sbc_storage_count) >= btstack_sbc_encoder_sbc_buffer_length()) {
    while (
        (context->samples_ready >= audioSamplesPerSbcBuffer) &&
        ((context->max_media_payload_size - context->sbc_storage_count) >= btstack_sbc_encoder_sbc_buffer_length())) {
        audio::Stream::Span dataSpan;

        Sink::_stream->peek(dataSpan);

        constexpr size_t bytesPerSample =
            sizeof(std::int16_t) / sizeof(std::uint8_t); // Samples are signed 16-bit, but stored in uint8_t array
        const float outputVolumeNormalized =
            outputVolume / static_cast<float>(audio::maxVolume); // Volume in <0;1> range

        std::int16_t *firstSample = reinterpret_cast<std::int16_t *>(dataSpan.data);
        std::int16_t *lastSample  = firstSample + dataSpan.dataSize / bytesPerSample;

        /* Scale each sample to reduce volume */
        std::for_each(firstSample, lastSample, [&](std::int16_t &sample) { sample *= outputVolumeNormalized; });

        btstack_sbc_encoder_process_data(firstSample);
        scaleVolume(dataSpan);
        btstack_sbc_encoder_process_data(reinterpret_cast<std::int16_t *>(dataSpan.data));
        Sink::_stream->consume();

        uint16_t sbcFrameSize = btstack_sbc_encoder_sbc_buffer_length();
        uint8_t *sbcFrame     = btstack_sbc_encoder_sbc_buffer();
        const auto sbcFrameSize = btstack_sbc_encoder_sbc_buffer_length();
        const auto sbcFrame     = btstack_sbc_encoder_sbc_buffer();

        totalNumBytesRead += numAudioSamplesPerSbcBuffer;
        memcpy(&context->sbc_storage[context->sbc_storage_count], sbcFrame, sbcFrameSize);
        totalNumBytesRead += audioSamplesPerSbcBuffer;
        std::memcpy(&context->sbc_storage[context->sbc_storage_count], sbcFrame, sbcFrameSize);
        context->sbc_storage_count += sbcFrameSize;
        context->samples_ready -= numAudioSamplesPerSbcBuffer;
        context->samples_ready -= audioSamplesPerSbcBuffer;
    }

    return totalNumBytesRead;
}

auto BluetoothAudioDevice::scaleVolume(audio::Stream::Span &dataSpan) const -> void
{
    constexpr auto bytesPerSample =
        sizeof(std::int16_t) / sizeof(std::uint8_t); // Samples are signed 16-bit, but stored in uint8_t array
    constexpr auto volumeLevels   = (audio::maxVolume - audio::minVolume) + 1;
    constexpr auto volumeScaleLut = utils::makeArray<float, volumeLevels>([](auto index) {
        /* Return zero when muted */
        if (index == 0) {
            return 0.0f;
        }

        /* Normalize volume to <0;1> range */
        const auto volumeNormalized = static_cast<float>(index) / audio::maxVolume;

        /* Coefficients for a curve with a dynamic range of 42dB
         * For 10 available steps this gives ~4dB change between each step, what seems
         * to be a fair compromise between step size and volume at the lowest level.
         * For more info check: https://www.dr-lex.be/info-stuff/volumecontrols.html */
        constexpr auto a = 7.943e-3f;
        constexpr auto b = 4.835f;
        return std::clamp(a * std::exp(b * volumeNormalized), 0.0f, 1.0f);
    });

    auto firstSample = reinterpret_cast<std::int16_t *>(dataSpan.data);
    auto lastSample  = &firstSample[dataSpan.dataSize / bytesPerSample];

    /* Scale each sample to reduce volume */
    const auto lutIndex = static_cast<std::size_t>(outputVolume);
    std::for_each(firstSample, lastSample, [&](std::int16_t &sample) { sample *= volumeScaleLut[lutIndex]; });
}

auto A2DPAudioDevice::getSupportedFormats() -> std::vector<audio::AudioFormat>
{
    constexpr static auto supportedBitWidth = 16U;
    constexpr auto supportedBitWidth = 16U;
    return std::vector<AudioFormat>{AudioFormat{static_cast<unsigned>(AVDTP::sbcConfig.samplingFrequency),
                                                supportedBitWidth,
                                                static_cast<unsigned>(AVDTP::sbcConfig.numChannels)}};


@@ 286,6 305,7 @@ auto CVSDAudioDevice::getSourceFormat() -> ::audio::AudioFormat
{
    return AudioFormat{bluetooth::SCO::CVSD_SAMPLE_RATE, supportedBitWidth, supportedChannels};
}

void CVSDAudioDevice::setAclHandle(hci_con_handle_t handle)
{
    aclHandle = handle;

M module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.hpp => module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.hpp +7 -6
@@ 1,4 1,4 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 6,6 6,7 @@
#include <Audio/Endpoint.hpp>
#include <Audio/AudioDevice.hpp>
#include <Audio/AudioFormat.hpp>
#include <Audio/Stream.hpp>
#include <interface/profiles/A2DP/MediaContext.hpp>
#include <interface/profiles/AudioProfile.hpp>



@@ 16,7 17,6 @@ extern "C"

namespace bluetooth
{

    class BluetoothAudioDevice : public audio::AudioDevice
    {
      public:


@@ 37,6 37,8 @@ namespace bluetooth
        auto isInputEnabled() const -> bool;
        auto isOutputEnabled() const -> bool;
        auto fillSbcAudioBuffer() -> int;
        auto scaleVolume(audio::Stream::Span &dataSpan) const -> void;

        float outputVolume;

      private:


@@ 95,10 97,10 @@ namespace bluetooth
        static constexpr auto packetLengthOffset = 2;
        static constexpr auto packetDataOffset   = 3;

        constexpr static auto supportedBitWidth = 16U;
        constexpr static auto supportedChannels = 1;
        static constexpr auto supportedBitWidth = 16U;
        static constexpr auto supportedChannels = 1;

        constexpr static auto allGoodMask = 0x30;
        static constexpr auto allGoodMask = 0x30;

        auto decodeCVSD(audio::AbstractStream::Span dataToDecode) -> audio::AbstractStream::Span;



@@ 108,5 110,4 @@ namespace bluetooth
        btstack_cvsd_plc_state_t cvsdPlcState;
        hci_con_handle_t aclHandle;
    };

} // namespace bluetooth

M module-utils/utility/Utils.hpp => module-utils/utility/Utils.hpp +11 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 354,6 354,16 @@ namespace utils
#endif
    }

    template <typename T, std::size_t N, typename Generator>
    [[nodiscard]] inline constexpr std::array<T, N> makeArray(Generator g)
    {
        std::array<T, N> array{};
        for (std::size_t i = 0; i < N; ++i) {
            array[i] = g(i);
        }
        return array;
    }

    [[nodiscard]] std::string generateRandomId(std::size_t length) noexcept;

    namespace filesystem

M pure_changelog.md => pure_changelog.md +1 -0
@@ 9,6 9,7 @@

### Changed / Improved
* Increased speed of update process.
* Changed A2DP volume control scale from linear to exponential.

## [1.12.0 2024-03-07]