~aleteoryx/muditaos

72b06448e1c9ecbbc415f7b63cbfe49400e48d2c — Lefucjusz 3 years ago 3d13f10
[MOS-647][MOS-671] BT volume control fixes

Set of fixes for Bluetooth volume control issues:

* split OS volume from A2DP device volume;
* added translations for additional popup required
to indicate what volume is being set;
* fixed issue that on some devices setting the lowest
volume level would set the highest in reality;
* fixed Bluetooth auto-turnoff functionality;
* minor code cleanup.
M image/assets/lang/Deutsch.json => image/assets/lang/Deutsch.json +1 -0
@@ 301,6 301,7 @@
  "app_desktop_clear_all": "KPL.-LEER.",
  "app_desktop_replay": "ANTWORTEN",
  "app_popup_volume_text": "LAUTSTÄRKE",
  "app_popup_bt_volume_text": "BLUETOOTH-LAUTSTÄRKE",
  "app_popup_music_volume_text": "MUSIKLAUTSTÄRKE",
  "app_popup_call_volume_text": "ANRUFLAUTSTÄRKE",
  "app_popup_muted_text": "STUMM",

M image/assets/lang/English.json => image/assets/lang/English.json +2 -1
@@ 269,6 269,7 @@
  "app_desktop_clear_all": "CLEAR ALL",
  "app_desktop_replay": "REPLY",
  "app_popup_volume_text": "VOLUME",
  "app_popup_bt_volume_text": "BLUETOOTH VOLUME",
  "app_popup_music_volume_text": "MUSIC VOLUME",
  "app_popup_call_volume_text": "CALL VOLUME",
  "app_popup_muted_text": "MUTED",


@@ 481,7 482,7 @@
  "app_settings_option_connected": "CONNECTED",
  "app_settings_option_connected_audio": "CONNECTED AUDIO",
  "app_settings_option_connected_voice": "CONNECTED VOICE",
  "app_settings_option_connected_both": "CONNECTED VOICE,AUDIO",
  "app_settings_option_connected_both": "CONNECTED VOICE, AUDIO",
  "app_settings_option_connecting": "CONNECTING",
  "app_settings_option_pairing": "PAIRING",
  "app_settings_title_do_not_disturb": "Do not disturb",

M image/assets/lang/Espanol.json => image/assets/lang/Espanol.json +1 -0
@@ 301,6 301,7 @@
  "app_desktop_clear_all": "BORRAR TODO",
  "app_desktop_replay": "RESPONDER",
  "app_popup_volume_text": "VOLUMEN",
  "app_popup_bt_volume_text": "VOLUMEN DEL BLUETOOTH",
  "app_popup_music_volume_text": "VOLUMEN DE MÚSICA",
  "app_popup_call_volume_text": "VOLUMEN DE LLAMADAS",
  "app_popup_muted_text": "SILENCIADO",

M image/assets/lang/Francais.json => image/assets/lang/Francais.json +1 -0
@@ 270,6 270,7 @@
  "app_desktop_clear_all": "TOUT EFFACER",
  "app_desktop_replay": "RÉPONDRE",
  "app_popup_volume_text": "VOLUME",
  "app_popup_bt_volume_text": "VOLUME DE LA BLUETOOTH",
  "app_popup_music_volume_text": "VOLUME DE LA MUSIQUE",
  "app_popup_call_volume_text": "VOLUME D'APPEL",
  "app_popup_muted_text": "SILENCIEUX",

M image/assets/lang/Polski.json => image/assets/lang/Polski.json +1 -0
@@ 311,6 311,7 @@
  "app_desktop_clear_all": "WYCZYŚĆ",
  "app_desktop_replay": "ODPOWIEDZ",
  "app_popup_volume_text": "GŁOŚNOŚĆ",
  "app_popup_bt_volume_text": "GŁOŚNOŚĆ BLUETOOTH",
  "app_popup_music_volume_text": "GŁOŚNOŚĆ MUZYKI",
  "app_popup_call_volume_text": "GŁOŚNOŚĆ POŁĄCZEŃ",
  "app_popup_muted_text": "WYCISZONE",

M module-apps/apps-common/ApplicationCommonPopupBlueprints.cpp => module-apps/apps-common/ApplicationCommonPopupBlueprints.cpp +5 -4
@@ 49,14 49,15 @@ namespace app
                         std::to_string(volumeParams->getVolume()).c_str());
                auto volume          = volumeParams->getVolume();
                auto context         = volumeParams->getAudioContext();
                auto source          = volumeParams->getRequestSource();
                auto popupData       = std::make_unique<gui::VolumePopupData>(volume, context, source);

                const auto popupName = resolveWindowName(gui::popup::ID::Volume);
                if (const auto currentWindowName = getCurrentWindow()->getName(); currentWindowName == popupName) {
                    updateCurrentWindow(std::make_unique<gui::VolumePopupData>(volume, context));
                    updateCurrentWindow(std::move(popupData));
                }
                else {
                    switchWindowPopup(popupName,
                                      volumeParams->getDisposition(),
                                      std::make_unique<gui::VolumePopupData>(volume, context));
                    switchWindowPopup(popupName, volumeParams->getDisposition(), std::move(popupData));
                }
                return true;
            });

M module-apps/apps-common/popups/VolumeWindow.cpp => module-apps/apps-common/popups/VolumeWindow.cpp +36 -8
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <module-gui/gui/input/InputEvent.hpp>


@@ 55,21 55,40 @@ namespace gui
    {
        WindowWithTimer::onBeforeShow(mode, data);
        const auto popupData = dynamic_cast<VolumePopupData *>(data);
        if (popupData) {
        if (popupData != nullptr) {
            volume       = popupData->getVolume();
            audioContext = popupData->getAudioContext();
            source       = popupData->getVolumeChangeRequestSource();
            if (volumeBar != nullptr) {
                volumeBar->setValue(volume);
            }
            if (volumeText != nullptr) {
                showProperText(audioContext, volume);
                showProperText(audioContext, volume, source);
            }
        }
    }

    void VolumeWindow::showProperText(const audio::Context &audioContext, const audio::Volume volume) noexcept
    void VolumeWindow::showProperText(const audio::Context &audioContext,
                                      const audio::Volume volume,
                                      const audio::VolumeChangeRequestSource source) noexcept
    {
        volumeText->setText(utils::translate(style::window::volume::base_title_key));
        if (volume == 0) {
            showMuted();
            return;
        }

        switch (source) {
        case audio::VolumeChangeRequestSource::A2DP:
            showBTAudioDeviceVolume();
            return;
        case audio::VolumeChangeRequestSource::HFP:
        case audio::VolumeChangeRequestSource::HSP:
            showCalling();
            return;
        default:
            break;
        }

        const auto [profileType, playbackType] = audioContext;
        if (playbackType == audio::PlaybackType::Multimedia) {
            showMultimediaPlayback();


@@ 81,12 100,21 @@ namespace gui
                 profileType == audio::Profile::Type::RoutingLoudspeaker) {
            showCalling();
        }

        if (volume == 0) {
            showMuted();
        else {
            showBaseTitle();
        }
    }

    void VolumeWindow::showBaseTitle() noexcept
    {
        volumeText->setText(utils::translate(style::window::volume::base_title_key));
    }

    void VolumeWindow::showBTAudioDeviceVolume() noexcept
    {
        volumeText->setText(utils::translate(style::window::volume::bt_audio_title_key));
    }

    void VolumeWindow::showMultimediaPlayback() noexcept
    {
        volumeText->setText(utils::translate(style::window::volume::music_title_key));

M module-apps/apps-common/popups/VolumeWindow.hpp => module-apps/apps-common/popups/VolumeWindow.hpp +8 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 15,6 15,7 @@ namespace style::window::volume
{
    constexpr inline auto title_height    = 33;
    constexpr inline auto base_title_key  = "app_popup_volume_text";
    constexpr inline auto bt_audio_title_key = "app_popup_bt_volume_text";
    constexpr inline auto music_title_key = "app_popup_music_volume_text";
    constexpr inline auto call_title_key  = "app_popup_call_volume_text";
    constexpr inline auto mute_title_key  = "app_popup_muted_text";


@@ 39,8 40,13 @@ namespace gui
      private:
        audio::Volume volume = 0;
        audio::Context audioContext;
        audio::VolumeChangeRequestSource source;

        void showProperText(const audio::Context &audioContext, const audio::Volume volume) noexcept;
        void showProperText(const audio::Context &audioContext,
                            audio::Volume volume,
                            audio::VolumeChangeRequestSource source) noexcept;
        void showBaseTitle() noexcept;
        void showBTAudioDeviceVolume() noexcept;
        void showMultimediaPlayback() noexcept;
        void showCalling() noexcept;
        void showMuted() noexcept;

M module-apps/apps-common/popups/data/PopupData.hpp => module-apps/apps-common/popups/data/PopupData.hpp +10 -2
@@ 11,8 11,10 @@ namespace gui
    class VolumePopupData : public SwitchData
    {
      public:
        explicit VolumePopupData(const audio::Volume volume, const audio::Context audioContext)
            : SwitchData(), volume{volume}, audioContext{audioContext}
        explicit VolumePopupData(const audio::Volume volume,
                                 const audio::Context audioContext,
                                 audio::VolumeChangeRequestSource source)
            : SwitchData(), volume{volume}, audioContext{audioContext}, source{source}
        {}

        [[nodiscard]] auto getVolume() const noexcept -> audio::Volume


@@ 25,9 27,15 @@ namespace gui
            return audioContext;
        }

        [[nodiscard]] auto getVolumeChangeRequestSource() const noexcept -> audio::VolumeChangeRequestSource
        {
            return source;
        }

      private:
        const audio::Volume volume;
        const audio::Context audioContext;
        const audio::VolumeChangeRequestSource source;
    };

    class ModesPopupData : public SwitchData

M module-apps/apps-common/popups/data/PopupRequestParams.hpp => module-apps/apps-common/popups/data/PopupRequestParams.hpp +10 -2
@@ 146,8 146,10 @@ namespace gui
    class VolumePopupRequestParams : public PopupRequestParams
    {
      public:
        VolumePopupRequestParams(audio::Volume volume, const audio::Context audioContext)
            : PopupRequestParams{gui::popup::ID::Volume}, volume{volume}, audioContext{audioContext}
        VolumePopupRequestParams(audio::Volume volume,
                                 const audio::Context audioContext,
                                 audio::VolumeChangeRequestSource source)
            : PopupRequestParams{gui::popup::ID::Volume}, volume{volume}, audioContext{audioContext}, source{source}
        {}

        [[nodiscard]] auto getVolume() const noexcept


@@ 160,9 162,15 @@ namespace gui
            return audioContext;
        }

        [[nodiscard]] auto getRequestSource() const noexcept
        {
            return source;
        }

      private:
        const audio::Volume volume;
        const audio::Context audioContext;
        const audio::VolumeChangeRequestSource source;
    };

} // namespace gui

M module-audio/Audio/AudioCommon.hpp => module-audio/Audio/AudioCommon.hpp +8 -0
@@ 72,6 72,14 @@ namespace audio
        Last = Bedtime,
    };

    enum class VolumeChangeRequestSource
    {
        A2DP,
        HFP,
        HSP,
        Other
    };

    /// Used to describe audio operations
    using Context = std::pair<Profile::Type, PlaybackType>;


M module-audio/Audio/VolumeScaler.cpp => module-audio/Audio/VolumeScaler.cpp +1 -6
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "VolumeScaler.hpp"


@@ 38,11 38,6 @@ namespace audio::volume::scaler
        {
            return btProfileToSystemVolume(avrcpVolume, static_cast<float>(avrcpMaxVolume));
        }

        std::uint8_t toAvrcpVolume(float systemVolume) noexcept
        {
            return systemToBtProfileVolume(systemVolume, avrcpMaxVolume);
        }
    } // namespace a2dp
    namespace hsp
    {

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

#pragma once


@@ 13,11 13,6 @@ namespace audio::volume::scaler
        /// @param avrcpVolume - AVRCP volume level.
        /// @return Volume level scaled to satisfy system's range [audio::minVolume, audio::maxVolume].
        Volume toSystemVolume(std::uint8_t avrcpVolume) noexcept;
        /// @brief Takes volume level and converts it to according one for the AVRCP.
        /// @param systemVolume - system volume level.
        /// @return Volume level scaled to satisfy AVRCP's range [0, 127].
        std::uint8_t toAvrcpVolume(float systemVolume) noexcept;

    } // namespace a2dp
    namespace hsp
    {

M module-audio/Audio/test/unittest_scaler.cpp => module-audio/Audio/test/unittest_scaler.cpp +0 -12
@@ 64,10 64,6 @@ SCENARIO("Scale volume levels between system and bluetooth")
    {
        WHEN("Volume is set to 10")
        {
            THEN("AVRCP volume is 127")
            {
                REQUIRE(audio::volume::scaler::a2dp::toAvrcpVolume(10) == std::uint8_t{127});
            }
            THEN("HSP speaker gain is 15")
            {
                REQUIRE(audio::volume::scaler::hsp::toHSPGain(10) == std::uint8_t{15});


@@ 75,10 71,6 @@ SCENARIO("Scale volume levels between system and bluetooth")
        }
        WHEN("System volume is set to 7")
        {
            THEN("AVRCP volume is 89")
            {
                REQUIRE(audio::volume::scaler::a2dp::toAvrcpVolume(7) == std::uint8_t{89});
            }
            THEN("HSP speaker gain is 7")
            {
                REQUIRE(audio::volume::scaler::hsp::toHSPGain(7) == std::uint8_t{11});


@@ 86,10 78,6 @@ SCENARIO("Scale volume levels between system and bluetooth")
        }
        WHEN("System volume is set to 1")
        {
            THEN("AVRCP volume is 13")
            {
                REQUIRE(audio::volume::scaler::a2dp::toAvrcpVolume(1) == std::uint8_t{13});
            }
            THEN("HSP speaker gain is 2")
            {
                REQUIRE(audio::volume::scaler::hsp::toHSPGain(1) == std::uint8_t{2});

M module-audio/board/rt1051/puretx/PureTxAudioDeviceFactory.cpp => module-audio/board/rt1051/puretx/PureTxAudioDeviceFactory.cpp +6 -4
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "PureTxAudioDeviceFactory.hpp"


@@ 16,21 16,23 @@ using audio::RT1051CellularAudio;
std::shared_ptr<AudioDevice> PureTxAudioDeviceFactory::getDevice(const audio::Profile &profile)
{
    std::shared_ptr<AudioDevice> device;
    const auto initialVolume = profile.GetOutputVolume();

    switch (profile.GetAudioDeviceType()) {
    case AudioDevice::Type::Audiocodec: {
        device = std::make_shared<PureTxAudioCodec>(profile.GetAudioConfiguration());
    } break;

    case AudioDevice::Type::BluetoothA2DP: {
        device = std::make_shared<bluetooth::A2DPAudioDevice>();
        device = std::make_shared<bluetooth::A2DPAudioDevice>(initialVolume);
    } break;

    case AudioDevice::Type::BluetoothHSP: {
        device = std::make_shared<bluetooth::CVSDAudioDevice>(bluetooth::AudioProfile::HSP);
        device = std::make_shared<bluetooth::CVSDAudioDevice>(initialVolume, bluetooth::AudioProfile::HSP);
    } break;

    case AudioDevice::Type::BluetoothHFP: {
        device = std::make_shared<bluetooth::CVSDAudioDevice>(bluetooth::AudioProfile::HFP);
        device = std::make_shared<bluetooth::CVSDAudioDevice>(initialVolume, bluetooth::AudioProfile::HFP);
    } break;

    case AudioDevice::Type::Cellular: {

M module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.cpp => module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.cpp +16 -9
@@ 23,7 23,9 @@ using bluetooth::CVSDAudioDevice;
using namespace std::chrono_literals;

BluetoothAudioDevice::BluetoothAudioDevice(AudioProfile audioProfile) : profile(audioProfile)
{}
{
    LOG_DEBUG("Creating bluetooth device with %s profile", magic_enum::enum_name(audioProfile).data());
}

BluetoothAudioDevice::~BluetoothAudioDevice()
{


@@ 52,13 54,6 @@ auto BluetoothAudioDevice::isOutputEnabled() const -> bool

auto A2DPAudioDevice::setOutputVolume(float vol) -> audio::AudioDevice::RetCode
{
    const auto volumeToSet = audio::volume::scaler::a2dp::toAvrcpVolume(vol);
    const auto status      = avrcp_controller_set_absolute_volume(AVRCP::mediaTracker.avrcp_cid, volumeToSet);
    if (status != ERROR_CODE_SUCCESS) {
        LOG_ERROR("Can't set volume level. Status %x", status);
        return audio::AudioDevice::RetCode::Failure;
    }

    outputVolume = vol;
    return audio::AudioDevice::RetCode::Success;
}


@@ 234,7 229,19 @@ auto BluetoothAudioDevice::fillSbcAudioBuffer() -> int
        audio::Stream::Span dataSpan;

        Sink::_stream->peek(dataSpan);
        btstack_sbc_encoder_process_data(reinterpret_cast<int16_t *>(dataSpan.data));

        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);
        Sink::_stream->consume();

        uint16_t sbcFrameSize = btstack_sbc_encoder_sbc_buffer_length();

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

#pragma once


@@ 48,8 48,10 @@ namespace bluetooth
    class A2DPAudioDevice : public BluetoothAudioDevice
    {
      public:
        explicit A2DPAudioDevice() : BluetoothAudioDevice(AudioProfile::A2DP)
        {}
        explicit A2DPAudioDevice(const float volume) : BluetoothAudioDevice(AudioProfile::A2DP)
        {
            outputVolume = volume;
        }

        auto setOutputVolume(float vol) -> audio::AudioDevice::RetCode override;
        void onDataSend() override;


@@ 65,8 67,9 @@ namespace bluetooth
    class CVSDAudioDevice : public BluetoothAudioDevice
    {
      public:
        explicit CVSDAudioDevice(const AudioProfile &profile) : BluetoothAudioDevice(profile)
        explicit CVSDAudioDevice(const float volume, const AudioProfile &profile) : BluetoothAudioDevice(profile)
        {
            outputVolume = volume;
            btstack_cvsd_plc_init(&cvsdPlcState);
        }


M module-bluetooth/Bluetooth/interface/profiles/A2DP/A2DP.cpp => module-bluetooth/Bluetooth/interface/profiles/A2DP/A2DP.cpp +19 -15
@@ 12,7 12,6 @@
#include <Bluetooth/Device.hpp>
#include <Bluetooth/Error.hpp>
#include <log/log.hpp>
#include <service-bluetooth/BluetoothMessage.hpp>
#include <Audio/AudioCommon.hpp>
#include <service-audio/AudioMessage.hpp>
#include <service-evtmgr/Constants.hpp>


@@ 108,8 107,8 @@ namespace bluetooth
    QueueHandle_t A2DP::A2DPImpl::sinkQueue   = nullptr;
    DeviceMetadata_t A2DP::A2DPImpl::metadata;
    btstack_packet_callback_registration_t A2DP::A2DPImpl::hciEventCallbackRegistration;
    std::array<uint8_t, 150> A2DP::A2DPImpl::sdpSourceServiceBuffer;
    std::array<uint8_t, 4> A2DP::A2DPImpl::mediaSbcCodecCapabilities = {
    std::array<std::uint8_t, A2DP::A2DPImpl::SDP_BUFFER_LENGTH> A2DP::A2DPImpl::sdpSourceServiceBuffer;
    std::array<std::uint8_t, A2DP::A2DPImpl::MEDIA_CAP_SIZE> A2DP::A2DPImpl::mediaSbcCodecCapabilities = {
        (AVDTP_SBC_44100 << 4) | AVDTP_SBC_STEREO,
        0xFF, //(AVDTP_SBC_BLOCK_LENGTH_16 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_LOUDNESS,
        2,


@@ 160,7 159,7 @@ namespace bluetooth

        // Create AVRCP target service record and register it with SDP.
        AVRCP::sdpTargetServiceBuffer.fill(0);
        uint16_t supportedFeatures = AVRCP_FEATURE_MASK_CATEGORY_PLAYER_OR_RECORDER;
        std::uint16_t supportedFeatures = AVRCP_FEATURE_MASK_CATEGORY_PLAYER_OR_RECORDER;
#ifdef AVRCP_BROWSING_ENABLED
        supported_features |= AVRCP_FEATURE_MASK_BROWSING;
#endif


@@ 173,7 172,7 @@ namespace bluetooth

        // setup AVRCP Controller
        AVRCP::sdpControllerServiceBuffer.fill(0);
        uint16_t controllerSupportedFeatures              = AVRCP_FEATURE_MASK_CATEGORY_PLAYER_OR_RECORDER;
        uint16_t controllerSupportedFeatures = AVRCP_FEATURE_MASK_CATEGORY_MONITOR_OR_AMPLIFIER;
        avrcp_controller_create_sdp_record(AVRCP::sdpControllerServiceBuffer.data(),
                                           avrcpControllerSdpRecordHandle,
                                           controllerSupportedFeatures,


@@ 197,7 196,7 @@ namespace bluetooth
    {
        int numBytesInFrame = btstack_sbc_encoder_sbc_buffer_length();
        int bytesInStorage  = AVRCP::mediaTracker.sbc_storage_count;
        uint8_t numFrames   = bytesInStorage / numBytesInFrame;
        std::uint8_t numFrames = bytesInStorage / numBytesInFrame;
        a2dp_source_stream_send_media_payload(AVRCP::mediaTracker.a2dp_cid,
                                              AVRCP::mediaTracker.local_seid,
                                              AVRCP::mediaTracker.sbc_storage,


@@ 213,14 212,14 @@ namespace bluetooth
        auto *context = static_cast<MediaContext *>(btstack_run_loop_get_timer_context(timer));
        btstack_run_loop_set_timer(&context->audio_timer, AUDIO_TIMEOUT_MS);
        btstack_run_loop_add_timer(&context->audio_timer);
        uint32_t now = btstack_run_loop_get_time_ms();
        std::uint32_t now = btstack_run_loop_get_time_ms();

        uint32_t updatePeriodMs = AUDIO_TIMEOUT_MS;
        std::uint32_t updatePeriodMs = AUDIO_TIMEOUT_MS;
        if (context->time_audio_data_sent_in_ms > 0) {
            updatePeriodMs = now - context->time_audio_data_sent_in_ms;
        }

        uint32_t numSamples = (updatePeriodMs * AVDTP::sampleRate) / 1000;
        std::uint32_t numSamples = (updatePeriodMs * AVDTP::sampleRate) / 1000;
        context->acc_num_missed_samples += (updatePeriodMs * AVDTP::sampleRate) % 1000;

        while (context->acc_num_missed_samples >= 1000) {


@@ 274,19 273,25 @@ namespace bluetooth
        btstack_run_loop_remove_timer(&context->audio_timer);
    }

    void A2DP::A2DPImpl::hciPacketHandler(uint8_t packetType, uint16_t channel, uint8_t *packet, uint16_t size)
    void A2DP::A2DPImpl::hciPacketHandler(uint8_t packetType,
                                          [[maybe_unused]] uint16_t channel,
                                          [[maybe_unused]] uint8_t *packet,
                                          [[maybe_unused]] uint16_t size)
    {
        if (packetType != HCI_EVENT_PACKET) {
            return;
        }
    }

    void A2DP::A2DPImpl::sourcePacketHandler(uint8_t packetType, uint16_t channel, uint8_t *packet, uint16_t size)
    void A2DP::A2DPImpl::sourcePacketHandler(uint8_t packetType,
                                             [[maybe_unused]] uint16_t channel,
                                             uint8_t *packet,
                                             [[maybe_unused]] uint16_t size)
    {
        uint8_t status;
        uint8_t local_seid;
        std::uint8_t status;
        std::uint8_t local_seid;
        bd_addr_t address;
        uint16_t cid;
        std::uint16_t cid;

        if (packetType != HCI_EVENT_PACKET) {
            return;


@@ 446,7 451,6 @@ namespace bluetooth
                     a2dp_subevent_stream_established_get_remote_seid(packet));

            sourceQueue = xQueueCreate(5, sizeof(AudioData_t));
            sinkQueue   = nullptr;
            if (sourceQueue != nullptr) {
                sendAudioEvent(audio::EventType::BlutoothA2DPDeviceState, audio::Event::DeviceState::Connected);
            }

M module-bluetooth/Bluetooth/interface/profiles/A2DP/AVRCP.cpp => module-bluetooth/Bluetooth/interface/profiles/A2DP/AVRCP.cpp +6 -17
@@ 46,11 46,12 @@ namespace bluetooth
            avrcp_target_support_event(AVRCP::mediaTracker.avrcp_cid, AVRCP_NOTIFICATION_EVENT_TRACK_CHANGED);
            avrcp_target_support_event(AVRCP::mediaTracker.avrcp_cid,
                                       AVRCP_NOTIFICATION_EVENT_NOW_PLAYING_CONTENT_CHANGED);

            avrcp_controller_enable_notification(AVRCP::mediaTracker.avrcp_cid,
                                                 AVRCP_NOTIFICATION_EVENT_VOLUME_CHANGED);

            avrcp_target_set_now_playing_info(
                AVRCP::mediaTracker.avrcp_cid, NULL, sizeof(AVRCP::tracks) / sizeof(avrcp_track_t));
                AVRCP::mediaTracker.avrcp_cid, nullptr, sizeof(AVRCP::tracks) / sizeof(avrcp_track_t));
            avrcp_target_set_unit_info(AVRCP::mediaTracker.avrcp_cid, AVRCP_SUBUNIT_TYPE_AUDIO, AVRCP::companyId);
            avrcp_target_set_subunit_info(AVRCP::mediaTracker.avrcp_cid,
                                          AVRCP_SUBUNIT_TYPE_AUDIO,


@@ 85,12 86,6 @@ namespace bluetooth

        const auto subevent_code = hci_event_avrcp_meta_get_subevent_code(packet);
        switch (subevent_code) {
        case AVRCP_SUBEVENT_NOTIFICATION_VOLUME_CHANGED:
            AVRCP::mediaTracker.volume = avrcp_subevent_notification_volume_changed_get_absolute_volume(packet);
            LOG_INFO("AVRCP Target: Volume set to %u%% (%u)\n",
                     AVRCP::targetVolumeToPercent(AVRCP::mediaTracker.volume),
                     AVRCP::mediaTracker.volume);
            break;
        case AVRCP_SUBEVENT_PLAY_STATUS_QUERY:
            status = avrcp_target_play_status(AVRCP::mediaTracker.avrcp_cid,
                                              AVRCP::playInfo.song_length_ms,


@@ 146,20 141,14 @@ namespace bluetooth
            return;
        }

        const std::uint8_t status = packet[5];

        // ignore INTERIM status
        if (status == AVRCP_CTYPE_RESPONSE_INTERIM) {
            return;
        }

        const auto subevent_code = hci_event_avrcp_meta_get_subevent_code(packet);
        switch (subevent_code) {
        /* BT device requested volume change */
        case AVRCP_SUBEVENT_NOTIFICATION_VOLUME_CHANGED: {
            const auto volume = avrcp_subevent_notification_volume_changed_get_absolute_volume(packet);
            auto &busProxy    = AVRCP::ownerService->bus;
            busProxy.sendUnicast(std::make_shared<message::bluetooth::A2DPVolume>(volume), service::name::bluetooth);
            LOG_INFO("AVRCP Controller: notification absolute volume changed %u%% (%u)\n",
            LOG_INFO("AVRCP Controller: BT device volume changed to %u%% (%u)\n",
                     AVRCP::controllerVolumeToPercent(volume),
                     volume);
        } break;


@@ 194,12 183,12 @@ namespace bluetooth

    std::uint16_t AVRCP::targetVolumeToPercent(std::uint16_t volume)
    {
        return volume * 127 / 100;
        return volume * AVRCP::maxVolumeValue / 100;
    }

    std::uint16_t AVRCP::controllerVolumeToPercent(std::uint16_t volume)
    {
        return volume * 100 / 127;
        return volume * 100 / AVRCP::maxVolumeValue;
    }

} // namespace bluetooth

M module-bluetooth/Bluetooth/interface/profiles/A2DP/AVRCP.hpp => module-bluetooth/Bluetooth/interface/profiles/A2DP/AVRCP.hpp +1 -0
@@ 20,6 20,7 @@ namespace bluetooth
    class AVRCP
    {
      private:
        static constexpr std::uint16_t maxVolumeValue = 0x7F; // From Bluetooth AVRCP 1.6.2 spec, p. 83
        static std::uint16_t targetVolumeToPercent(std::uint16_t volume);
        static std::uint16_t controllerVolumeToPercent(std::uint16_t volume);


M module-services/service-audio/ServiceAudio.cpp => module-services/service-audio/ServiceAudio.cpp +12 -10
@@ 5,13 5,11 @@
#include <ServiceAudio.hpp>

#include <Audio/Operation/IdleOperation.hpp>
#include <Audio/Operation/PlaybackOperation.hpp>
#include <Bluetooth/audio/BluetoothAudioDevice.hpp>
#include <module-audio/Audio/VolumeScaler.hpp>
#include <system/messages/SentinelRegistrationMessage.hpp>
#include <service-bluetooth/BluetoothMessage.hpp>
#include <service-bluetooth/Constants.hpp>
#include <service-bluetooth/messages/AudioRouting.hpp>
#include <service-bluetooth/messages/AudioNotify.hpp>
#include <service-db/Settings.hpp>
#include <service-evtmgr/EventManagerServiceAPI.hpp>


@@ 20,7 18,6 @@
#include <algorithm>
#include <initializer_list>
#include <iterator>
#include <type_traits>
#include <utility>

using namespace audio;


@@ 602,7 599,8 @@ auto ServiceAudio::HandleKeyPressed(const int step) -> sys::MessagePointer
        // update volume of currently active sound
        setSetting(Setting::Volume, std::to_string(newVolume));
    }
    bus.sendMulticast(std::make_shared<VolumeChanged>(newVolume, context), sys::BusChannel::ServiceAudioNotifications);
    bus.sendMulticast(std::make_shared<VolumeChanged>(newVolume, context, VolumeChangeRequestSource::Other),
                      sys::BusChannel::ServiceAudioNotifications);
    return sys::msgHandled();
}



@@ 833,21 831,25 @@ void ServiceAudio::settingsChanged(const std::string &name, std::string value)
    LOG_ERROR("ServiceAudio::settingsChanged received notification about not registered setting: %s", name.c_str());
}

void ServiceAudio::onVolumeChanged(Volume volume)
void ServiceAudio::onVolumeChanged(const Volume volume, const VolumeChangeRequestSource source)
{
    const auto [profileType, playbackType] = getCurrentContext();
    settingsProvider->setValue(dbPath(Setting::Volume, playbackType, profileType), std::to_string(volume));
    settingsCache[dbPath(Setting::Volume, playbackType, profileType)] = std::to_string(volume);
    bus.sendMulticast(std::make_shared<VolumeChanged>(volume, std::make_pair(profileType, playbackType)),
    bus.sendMulticast(std::make_shared<VolumeChanged>(volume, std::make_pair(profileType, playbackType), source),
                      sys::BusChannel::ServiceAudioNotifications);
}

auto ServiceAudio::handleA2DPVolumeChangedOnBluetoothDevice(sys::Message *msgl) -> sys::MessagePointer
{
    auto *a2dpMsg = dynamic_cast<A2DPDeviceVolumeChanged *>(msgl);
    const auto a2dpMsg = dynamic_cast<A2DPDeviceVolumeChanged *>(msgl);
    assert(a2dpMsg != nullptr);

    const auto context = getCurrentContext();
    const auto volume = volume::scaler::a2dp::toSystemVolume(a2dpMsg->getVolume());
    onVolumeChanged(volume);
    bus.sendMulticast(std::make_shared<VolumeChanged>(volume, context, VolumeChangeRequestSource::A2DP),
                      sys::BusChannel::ServiceAudioNotifications);

    return sys::msgHandled();
}



@@ 856,7 858,7 @@ auto ServiceAudio::handleHSPVolumeChangedOnBluetoothDevice(sys::Message *msgl) -
    auto *hspMsg = dynamic_cast<HSPDeviceVolumeChanged *>(msgl);
    assert(hspMsg != nullptr);
    const auto volume = volume::scaler::hsp::toSystemVolume(hspMsg->getVolume());
    onVolumeChanged(volume);
    onVolumeChanged(volume, VolumeChangeRequestSource::HSP);
    return sys::msgHandled();
}



@@ 865,7 867,7 @@ auto ServiceAudio::handleHFPVolumeChangedOnBluetoothDevice(sys::Message *msgl) -
    auto *hfpMsg = dynamic_cast<HFPDeviceVolumeChanged *>(msgl);
    assert(hfpMsg != nullptr);
    const auto volume = volume::scaler::hfp::toSystemVolume(hfpMsg->getVolume());
    onVolumeChanged(volume);
    onVolumeChanged(volume, VolumeChangeRequestSource::HFP);
    return sys::msgHandled();
}
auto ServiceAudio::handleA2DPAudioPause() -> sys::MessagePointer

M module-services/service-audio/include/service-audio/AudioMessage.hpp => module-services/service-audio/include/service-audio/AudioMessage.hpp +7 -4
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 260,19 260,22 @@ class AudioKeyPressedRequest : public AudioMessage
class VolumeChanged : public sys::DataMessage, public app::manager::actions::ConvertibleToAction
{
  public:
    VolumeChanged(audio::Volume volume, audio::Context context)
        : sys::DataMessage{MessageType::MessageTypeUninitialized}, volume{volume}, context{context}
    VolumeChanged(audio::Volume volume, audio::Context context, audio::VolumeChangeRequestSource source)
        : sys::DataMessage{MessageType::MessageTypeUninitialized}, volume{volume}, context{context}, source{source}
    {}

    [[nodiscard]] auto toAction() const -> std::unique_ptr<app::manager::ActionRequest> override
    {
        return std::make_unique<app::manager::ActionRequest>(
            sender, app::manager::actions::ShowPopup, std::make_unique<gui::VolumePopupRequestParams>(volume, context));
            sender,
            app::manager::actions::ShowPopup,
            std::make_unique<gui::VolumePopupRequestParams>(volume, context, source));
    }

  private:
    const audio::Volume volume;
    audio::Context context;
    audio::VolumeChangeRequestSource source;
};

class BluetoothDeviceVolumeChanged : public AudioMessage

M module-services/service-audio/include/service-audio/ServiceAudio.hpp => module-services/service-audio/include/service-audio/ServiceAudio.hpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 123,7 123,7 @@ class ServiceAudio : public sys::Service

    const audio::Context getCurrentContext();
    void settingsChanged(const std::string &name, std::string value);
    void onVolumeChanged(audio::Volume volume);
    void onVolumeChanged(audio::Volume volume, audio::VolumeChangeRequestSource source);
    auto handleA2DPVolumeChangedOnBluetoothDevice(sys::Message *msgl) -> sys::MessagePointer;
    auto handleHSPVolumeChangedOnBluetoothDevice(sys::Message *msgl) -> sys::MessagePointer;
    auto handleHFPVolumeChangedOnBluetoothDevice(sys::Message *msgl) -> sys::MessagePointer;

M module-services/service-bluetooth/ServiceBluetooth.cpp => module-services/service-bluetooth/ServiceBluetooth.cpp +0 -10
@@ 86,7 86,6 @@ sys::ReturnCodes ServiceBluetooth::InitHandler()
            LOG_INFO("Turning off Bluetooth due to inactivity timeout");
            handleTurnOff();
        });
    startTimeoutTimer();

    connectHandler<BluetoothAddrMessage>();
    connectHandler<BluetoothAudioStartMessage>();


@@ 403,7 402,6 @@ auto ServiceBluetooth::handle(message::bluetooth::ResponseAuthenticatePairCancel
auto ServiceBluetooth::handle(BluetoothMessage *msg) -> std::shared_ptr<sys::Message>
{
    LOG_INFO("Bluetooth request!");
    resetTimeoutTimer();

    switch (msg->req) {
    case BluetoothMessage::Scan:


@@ 525,14 523,6 @@ void ServiceBluetooth::stopTimeoutTimer()
    }
}

void ServiceBluetooth::resetTimeoutTimer()
{
    if (connectionTimeoutTimer.isValid() && connectionTimeoutTimer.isActive()) {
        connectionTimeoutTimer.stop();
        connectionTimeoutTimer.start();
    }
}

void ServiceBluetooth::handleTurnOff()
{
    sendWorkerCommand(std::make_unique<bluetooth::event::PowerOff>());

M module-services/service-bluetooth/service-bluetooth/ServiceBluetooth.hpp => module-services/service-bluetooth/service-bluetooth/ServiceBluetooth.hpp +0 -1
@@ 108,7 108,6 @@ class ServiceBluetooth : public sys::Service

    void startTimeoutTimer();
    void stopTimeoutTimer();
    void resetTimeoutTimer();

    template <typename T> auto connectHandler() -> bool
    {

M pure_changelog.md => pure_changelog.md +2 -0
@@ 4,9 4,11 @@

### Changed / Improved
* Changed USB logging
* Separated system volume from Bluetooth device volume for A2DP

### Fixed
* Fixed improper duration of the rejected outgoing call shown in calls log
* Fixed Bluetooth volume control issues
* Fixed turning on loudspeaker before outgoing call is answered
* Fixed PLAY label translation in German
* Fixed USB connection/disconnection detection