~aleteoryx/muditaos

2e04d268c49b4e661fc3488a0b4e341a871a1fa1 — Lefucjusz 1 year, 6 months ago 09efb75
[MOS-1068] Fix A2DP stream not restarting after song changes

Workaround for the issue that A2DP stream
would sometimes not restart when music
player changes song to the next one.
M module-apps/application-music-player/AudioNotificationsHandler.cpp => module-apps/application-music-player/AudioNotificationsHandler.cpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, 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 "AudioNotificationsHandler.hpp"


@@ 10,7 10,7 @@ namespace app::music_player

    AudioNotificationsHandler::AudioNotificationsHandler(
        std::shared_ptr<app::music_player::SongsContract::Presenter> presenter)
        : presenter(presenter)
        : presenter(std::move(presenter))
    {}

    sys::MessagePointer AudioNotificationsHandler::handleAudioStopNotification(

M module-apps/application-music-player/AudioNotificationsHandler.hpp => module-apps/application-music-player/AudioNotificationsHandler.hpp +2 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, 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


@@ 8,6 8,7 @@
class AudioStopNotification;
class AudioPausedNotification;
class AudioResumedNotification;

namespace app::music_player
{
    class AudioNotificationsHandler

M module-apps/application-music-player/widgets/SongItem.cpp => module-apps/application-music-player/widgets/SongItem.cpp +1 -2
@@ 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

#include "module-apps/application-music-player/widgets/SongItem.hpp"


@@ 7,7 7,6 @@

namespace gui
{

    using namespace musicPlayerStyle;

    SongItem::SongItem(const std::string &authorName,

M module-apps/application-music-player/windows/MusicPlayerMainWindow.cpp => module-apps/application-music-player/windows/MusicPlayerMainWindow.cpp +2 -2
@@ 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 "MusicPlayerMainWindow.hpp"


@@ 40,7 40,7 @@ namespace
        }

        return std::string(timeStringBuffer);
    };
    }
} // namespace

namespace gui

M module-audio/Audio/Audio.cpp => module-audio/Audio/Audio.cpp +5 -3
@@ 9,9 9,9 @@

namespace audio
{
    Audio::Audio(AudioServiceMessage::Callback callback) : currentOperation(), serviceCallback(callback)
    Audio::Audio(AudioServiceMessage::Callback callback) : currentOperation(), serviceCallback(std::move(callback))
    {
        auto ret = Operation::Create(Operation::Type::Idle, "", audio::PlaybackType::None, callback);
        auto ret = Operation::Create(Operation::Type::Idle, "", audio::PlaybackType::None, serviceCallback);
        if (ret) {
            currentOperation = std::move(ret);
        }


@@ 85,7 85,9 @@ namespace audio

    audio::RetCode Audio::Start()
    {
        currentOperation->Stop();
        if (currentState != State::Idle) {
            currentOperation->Stop();
        }
        return Start(currentOperation->GetOperationType(),
                     currentOperation->GetToken(),
                     currentOperation->GetFilePath(),

M module-audio/Audio/Audio.hpp => module-audio/Audio/Audio.hpp +2 -3
@@ 31,8 31,7 @@ namespace audio
            Routing,
        };

        Audio(AudioServiceMessage::Callback callback);

        explicit Audio(AudioServiceMessage::Callback callback);
        virtual ~Audio() = default;

        // Events


@@ 70,7 69,7 @@ namespace audio
        const Operation &GetCurrentOperation() const
        {
            // currentOperation always exists - null pattern design
            return *(currentOperation.get());
            return *currentOperation;
        }

        virtual audio::PlaybackType GetCurrentOperationPlaybackType() const

M module-audio/Audio/AudioDeviceFactory.hpp => module-audio/Audio/AudioDeviceFactory.hpp +1 -1
@@ 33,4 33,4 @@ namespace audio
      private:
        Observer *_observer = nullptr;
    };
}; // namespace audio
} // namespace audio

M module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.cpp => module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.cpp +30 -4
@@ 12,6 12,7 @@
#include <Audio/VolumeScaler.hpp>

#include <Utils.hpp>
#include <ticks.hpp>

#include <chrono>
#include <cassert>


@@ 311,11 312,36 @@ void CVSDAudioDevice::setAclHandle(hci_con_handle_t handle)
    aclHandle = handle;
}

audio::AudioDevice::RetCode A2DPAudioDevice::waitUntilStreamPaused() const
{
    /* Wait until A2DP stream is paused before proceeding. This is hacky way to prevent
     * race condition when music player track automatically changes to the next one.
     * a2dp_source functions are asynchronous, so in such case sometimes play request is
     * sent before pause request has been fully processed, what results in A2DP stream
     * not being restarted. */
    constexpr auto timeoutMs    = 200;
    constexpr auto retryDelayMs = 10;

    auto timeoutLoops = timeoutMs / retryDelayMs;
    while ((AVRCP::playInfo.status == AVRCP_PLAYBACK_STATUS_PLAYING) && (timeoutLoops > 0)) {
        vTaskDelay(cpp_freertos::Ticks::MsToTicks(retryDelayMs));
        timeoutLoops--;
    }

    return (timeoutLoops == 0) ? audio::AudioDevice::RetCode::Failure : audio::AudioDevice::RetCode::Success;
}

audio::AudioDevice::RetCode A2DPAudioDevice::Start()
{
    return (a2dp_source_start_stream(AVRCP::mediaTracker.a2dp_cid, AVRCP::mediaTracker.local_seid) == 0)
               ? audio::AudioDevice::RetCode::Success
               : audio::AudioDevice::RetCode::Failure;
    const auto retCode = a2dp_source_start_stream(AVRCP::mediaTracker.a2dp_cid, AVRCP::mediaTracker.local_seid);
    switch (retCode) {
    case ERROR_CODE_SUCCESS:
        return audio::AudioDevice::RetCode::Success;
    case ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER:
        return audio::AudioDevice::RetCode::Disconnected;
    default:
        return audio::AudioDevice::RetCode::Failure;
    }
}

audio::AudioDevice::RetCode A2DPAudioDevice::Stop()


@@ 323,7 349,7 @@ audio::AudioDevice::RetCode A2DPAudioDevice::Stop()
    const auto retCode = a2dp_source_pause_stream(AVRCP::mediaTracker.a2dp_cid, AVRCP::mediaTracker.local_seid);
    switch (retCode) {
    case ERROR_CODE_SUCCESS:
        return audio::AudioDevice::RetCode::Success;
        return waitUntilStreamPaused();
    case ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER:
        return audio::AudioDevice::RetCode::Disconnected; // Device disconnected during playback, e.g. headphones
                                                          // discharged

M module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.hpp => module-bluetooth/Bluetooth/audio/BluetoothAudioDevice.hpp +7 -4
@@ 34,8 34,8 @@ namespace bluetooth
        void disableOutput() override;

      protected:
        auto isInputEnabled() const -> bool;
        auto isOutputEnabled() const -> bool;
        [[nodiscard]] auto isInputEnabled() const -> bool;
        [[nodiscard]] auto isOutputEnabled() const -> bool;
        auto fillSbcAudioBuffer() -> int;
        auto scaleVolume(audio::Stream::Span &dataSpan) const -> void;



@@ 59,13 59,16 @@ namespace bluetooth
        void onDataSend() override;
        void onDataReceive() override;
        auto getSupportedFormats() -> std::vector<audio::AudioFormat> override;
        auto getTraits() const -> Traits override;
        [[nodiscard]] auto getTraits() const -> Traits override;
        auto getSourceFormat() -> ::audio::AudioFormat override;

        audio::AudioDevice::RetCode Start() override;
        audio::AudioDevice::RetCode Stop() override;
        audio::AudioDevice::RetCode Resume() override;
        audio::AudioDevice::RetCode Pause() override;

      private:
        [[nodiscard]] audio::AudioDevice::RetCode waitUntilStreamPaused() const;
    };

    class CVSDAudioDevice : public BluetoothAudioDevice


@@ 82,7 85,7 @@ namespace bluetooth
        void onDataSend(std::uint16_t scoHandle);
        void onDataReceive() override;
        auto getSupportedFormats() -> std::vector<audio::AudioFormat> override;
        auto getTraits() const -> Traits override;
        [[nodiscard]] auto getTraits() const -> Traits override;
        auto getSourceFormat() -> ::audio::AudioFormat override;
        void enableInput() override;
        void setAclHandle(hci_con_handle_t handle);

M module-bluetooth/Bluetooth/interface/profiles/A2DP/A2DP.cpp => module-bluetooth/Bluetooth/interface/profiles/A2DP/A2DP.cpp +4 -13
@@ 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 "A2DP.hpp"


@@ 32,7 32,7 @@ namespace bluetooth
    A2DP::~A2DP()
    {
        pimpl->disconnect();
        pimpl->deInit();
        pimpl->deinit();
    }

    A2DP::A2DP(A2DP &other) : pimpl(new A2DPImpl(*other.pimpl))


@@ 114,13 114,11 @@ namespace bluetooth

    btstack_packet_callback_registration_t A2DP::A2DPImpl::hciEventCallbackRegistration;

    QueueHandle_t A2DP::A2DPImpl::sourceQueue = nullptr;

    bool A2DP::A2DPImpl::isConnected = false;

    auto A2DP::A2DPImpl::init() -> Result::Code
    {
        // request role change on reconnecting headset to always use them in slave mode
        // Request role change on reconnecting headset to always use them in slave mode
        hci_set_master_slave_policy(0);

        Profile::initL2cap();


@@ 169,7 167,6 @@ namespace bluetooth
        std::memset(AVRCP::sdpControllerServiceBuffer, 0, sizeof(AVRCP::sdpControllerServiceBuffer));

        const std::uint16_t controllerSupportedFeatures = AVRCP_FEATURE_MASK_CATEGORY_MONITOR_OR_AMPLIFIER;

        avrcp_controller_create_sdp_record(AVRCP::sdpControllerServiceBuffer,
                                           avrcpControllerSdpRecordHandle,
                                           controllerSupportedFeatures,


@@ 446,12 443,6 @@ namespace bluetooth
                     AVRCP::mediaTracker.local_seid,
                     a2dp_subevent_stream_established_get_remote_seid(packet));

            sourceQueue = xQueueCreate(sourceQueueLength, sizeof(AudioData_t));
            if (sourceQueue == nullptr) {
                LOG_ERROR("Failed to create sourceQueue!");
                break;
            }

            sendAudioEvent(audio::EventType::BluetoothA2DPDeviceState, audio::Event::DeviceState::Connected);
        } break;



@@ 614,7 605,7 @@ namespace bluetooth
        A2DP::A2DPImpl::audioDevice = std::move(newAudioDevice);
    }

    void A2DP::A2DPImpl::deInit()
    void A2DP::A2DPImpl::deinit()
    {
        sdp_unregister_service(a2dpSdpRecordHandle);
        sdp_unregister_service(avrcpControllerSdpRecordHandle);

M module-bluetooth/Bluetooth/interface/profiles/A2DP/A2DPImpl.hpp => module-bluetooth/Bluetooth/interface/profiles/A2DP/A2DPImpl.hpp +3 -4
@@ 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


@@ 21,11 21,12 @@ extern "C"
namespace bluetooth
{
    class AVRCP;

    class A2DP::A2DPImpl
    {
      public:
        auto init() -> Result::Code;
        void deInit();
        void deinit();
        void connect();
        void disconnect();
        void start();


@@ 65,8 66,6 @@ namespace bluetooth

        static btstack_packet_callback_registration_t hciEventCallbackRegistration;

        static QueueHandle_t sourceQueue;

        static bool isConnected;
    };
} // namespace bluetooth

M module-bluetooth/Bluetooth/interface/profiles/Profile.hpp => module-bluetooth/Bluetooth/interface/profiles/Profile.hpp +2 -2
@@ 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


@@ 79,6 79,6 @@ namespace bluetooth
        [[nodiscard]] virtual auto setNetworkRegistrationStatus(bool registered) const noexcept -> Result::Code = 0;
        /// Sets the roaming status in HFP profile
        /// @return Error code that determines, whether operation was successful or not
        virtual auto setRoamingStatus(bool enabled) const noexcept -> Result::Code = 0;
        [[nodiscard]] virtual auto setRoamingStatus(bool enabled) const noexcept -> Result::Code = 0;
    };
} // namespace bluetooth

M module-bsp/board/rt1051/bluetooth/BlueKitchen.cpp => module-bsp/board/rt1051/bluetooth/BlueKitchen.cpp +5 -5
@@ 31,16 31,16 @@ BlueKitchen &BlueKitchen::getInstance()
    return *instance;
}

BTDevice::Error BlueKitchen::read(uint8_t *buf, size_t nbytes)
BTDevice::Error BlueKitchen::read(std::uint8_t *buf, std::size_t size)
{
    logHciStack("BlueKitchen requested to read %d bytes", nbytes);
    logHciStack("BlueKitchen requested to read %d bytes", size);

    std::uint8_t val;

    readBuffer = buf;
    readLength = nbytes;
    readLength = size;

    if (BluetoothCommon::read(buf, nbytes) == BTDevice::Error::Success) {
    if (BluetoothCommon::read(buf, size) == BTDevice::Error::Success) {
        val = bluetooth::Message::EvtReceiving;
        xQueueSend(qHandle, &val, portMAX_DELAY);
        return BTDevice::Error::Success;


@@ 52,7 52,7 @@ BTDevice::Error BlueKitchen::read(uint8_t *buf, size_t nbytes)
    }
}

BTDevice::Error BlueKitchen::write(const uint8_t *buf, size_t size)
BTDevice::Error BlueKitchen::write(const std::uint8_t *buf, std::size_t size)
{
    logHciStack("BlueKitchen sends %d bytes", size);


M module-bsp/bsp/bluetooth/Bluetooth.hpp => module-bsp/bsp/bluetooth/Bluetooth.hpp +2 -2
@@ 119,8 119,8 @@ namespace bsp
        virtual ~BlueKitchen();
        static BlueKitchen &getInstance();

        virtual Error read(uint8_t *buf, size_t size) override;
        virtual Error write(const uint8_t *buf, size_t size) override;
        virtual Error read(std::uint8_t *buf, std::size_t size) override;
        virtual Error write(const std::uint8_t *buf, std::size_t size) override;

        std::uint32_t readLength = 0;
        std::uint8_t *readBuffer;

M module-services/service-audio/ServiceAudio.cpp => module-services/service-audio/ServiceAudio.cpp +2 -2
@@ 432,7 432,7 @@ std::unique_ptr<AudioResponseMessage> ServiceAudio::HandleStart(const Operation:
        auto input = audioMux.GetPlaybackInput(playbackType);

        if (playbackType == audio::PlaybackType::CallRingtone && bluetoothVoiceProfileConnected && input) {
            // don't play ringtone on HFP connection on Pure, but do vibrate
            // Don't play ringtone on HFP connection on Pure, but do vibrate
            VibrationUpdate(playbackType, input);
            return std::make_unique<AudioStartPlaybackResponse>(audio::RetCode::Success, retToken);
        }


@@ 513,7 513,7 @@ std::unique_ptr<AudioResponseMessage> ServiceAudio::HandleStop(const std::vector

    // stop by token
    if (const auto tokenInput = audioMux.GetInput(token); token.IsValid() && tokenInput) {
        retCodes.emplace_back(std::make_pair(token, StopInput(tokenInput.value())));
        retCodes.emplace_back(token, StopInput(tokenInput.value()));
    }
    else if (token.IsValid()) {
        return std::make_unique<AudioStopResponse>(RetCode::TokenNotFound, Token::MakeBadToken());

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

### Fixed
* Fixed crash when deleting all contacts from MC with Phonebook app opened.
* Fixed sound not being heard in Bluetooth device despite song being played in Music Player.

### Added