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]