M changelog.md => changelog.md +1 -0
@@ 17,6 17,7 @@
* `[PowerManagement]` Change hardware timers clock source
* `[bluetooth]` Underlying communication with the Bluetooth module over DMA (direct access)
* `[system]` Workers refactor and state transition fixes
+* Change audio data path synchronization.
M module-apps/application-alarm-clock/widgets/AlarmOptionsItem.hpp => module-apps/application-alarm-clock/widgets/AlarmOptionsItem.hpp +1 -1
@@ 8,7 8,7 @@
#include <Label.hpp>
#include <Image.hpp>
#include <BoxLayout.hpp>
-#include <module-audio/Audio/decoder/decoder.hpp>
+#include <Audio/decoder/Decoder.hpp>
namespace gui
{
M module-apps/application-music-player/ApplicationMusicPlayer.hpp => module-apps/application-music-player/ApplicationMusicPlayer.hpp +1 -1
@@ 4,7 4,7 @@
#pragma once
#include <Application.hpp>
-#include <module-audio/Audio/decoder/decoder.hpp>
+#include <Audio/decoder/Decoder.hpp>
namespace gui
{
M module-apps/application-music-player/models/SongsModel.hpp => module-apps/application-music-player/models/SongsModel.hpp +1 -1
@@ 8,7 8,7 @@
#include "Application.hpp"
#include <ListItemProvider.hpp>
-#include <module-audio/Audio/decoder/decoder.hpp>
+#include <Audio/decoder/Decoder.hpp>
class SongsModel : public app::InternalModel<gui::ListItem *>, public gui::ListItemProvider
{
M module-audio/Audio/Audio.cpp => module-audio/Audio/Audio.cpp +3 -1
@@ 28,7 28,7 @@ namespace audio
std::optional<Tags> Audio::GetFileTags(const char *filename)
{
- auto ret = decoder::Create(filename);
+ auto ret = Decoder::Create(filename);
if (ret == nullptr) {
return {};
}
@@ 91,6 91,8 @@ namespace audio
break;
}
currentOperation = std::move(ret);
+ currentOperation->SetDataStreams(&dataStreamOut, &dataStreamIn);
+
UpdateProfiles();
if (btData) {
M module-audio/Audio/Audio.hpp => module-audio/Audio/Audio.hpp +10 -1
@@ 11,8 11,9 @@
#include <service-bluetooth/ServiceBluetoothCommon.hpp>
#include "AudioCommon.hpp"
+#include "Stream.hpp"
#include "Operation/Operation.hpp"
-#include "decoder/decoder.hpp"
+#include "decoder/Decoder.hpp"
namespace audio
{
@@ 117,6 118,14 @@ namespace audio
AsyncCallback asyncCallback;
DbCallback dbCallback;
+
+ // for efficiency multiple of 24 and 32 (max audio samples size)
+ static constexpr auto defaultAudioStreamBlockSize = 2048;
+ StandardStreamAllocator allocatorOut;
+ Stream dataStreamOut{allocatorOut, defaultAudioStreamBlockSize};
+
+ StandardStreamAllocator allocatorIn;
+ Stream dataStreamIn{allocatorIn, defaultAudioStreamBlockSize};
};
} // namespace audio
A module-audio/Audio/Endpoint.cpp => module-audio/Audio/Endpoint.cpp +44 -0
@@ 0,0 1,44 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "Endpoint.hpp"
+
+#include <cassert> // assert
+
+using namespace audio;
+
+void Endpoint::setStream(Stream &stream)
+{
+ assert(_stream == nullptr);
+ _stream = &stream;
+}
+
+Stream *Endpoint::getStream() const noexcept
+{
+ return _stream;
+}
+
+void Endpoint::unsetStream()
+{
+ assert(_stream != nullptr);
+ _stream = nullptr;
+}
+
+bool Endpoint::isConnected() const noexcept
+{
+ return _stream != nullptr;
+}
+
+void Source::connect(Sink &sink, Stream &stream)
+{
+ connectedSink = &sink;
+ connectedSink->setStream(stream);
+ setStream(stream);
+}
+
+void Source::disconnectStream()
+{
+ unsetStream();
+ connectedSink->unsetStream();
+ connectedSink = nullptr;
+}
A module-audio/Audio/Endpoint.hpp => module-audio/Audio/Endpoint.hpp +35 -0
@@ 0,0 1,35 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "Stream.hpp"
+
+namespace audio
+{
+ class Endpoint
+ {
+ public:
+ void setStream(Stream &stream);
+ Stream *getStream() const noexcept;
+ void unsetStream();
+ bool isConnected() const noexcept;
+
+ private:
+ Stream *_stream = nullptr;
+ };
+
+ class Sink : public Endpoint
+ {};
+
+ class Source : public Endpoint
+ {
+ public:
+ void connect(Sink &sink, Stream &stream);
+ void disconnectStream();
+
+ private:
+ Sink *connectedSink = nullptr;
+ };
+
+}; // namespace audio
M module-audio/Audio/Operation/Operation.cpp => module-audio/Audio/Operation/Operation.cpp +1 -1
@@ 10,7 10,7 @@
#include "RecorderOperation.hpp"
#include "RouterOperation.hpp"
-#include "Audio/decoder/decoder.hpp"
+#include "Audio/decoder/Decoder.hpp"
#include <bsp/audio/bsp_audio.hpp>
#include <module-bsp/board/rt1051/bsp/audio/RT1051BluetoothAudio.hpp>
M module-audio/Audio/Operation/Operation.hpp => module-audio/Audio/Operation/Operation.hpp +10 -2
@@ 8,6 8,7 @@
#include <functional>
#include <Audio/AudioCommon.hpp>
+#include <Audio/Stream.hpp>
#include <Audio/encoder/Encoder.hpp>
#include <Audio/Profiles/Profile.hpp>
@@ 112,6 113,12 @@ namespace audio
audio::RetCode SwitchToPriorityProfile();
+ void SetDataStreams(Stream *dStreamOut, Stream *dStreamIn)
+ {
+ dataStreamOut = dStreamOut;
+ dataStreamIn = dStreamIn;
+ }
+
protected:
struct SupportedProfile
{
@@ 123,6 130,9 @@ namespace audio
bool isAvailable;
};
+ Stream *dataStreamOut = nullptr;
+ Stream *dataStreamIn = nullptr;
+
std::shared_ptr<Profile> currentProfile;
std::unique_ptr<bsp::AudioDevice> audioDevice;
@@ 132,8 142,6 @@ namespace audio
State state = State::Idle;
audio::AsyncCallback eventCallback;
- AudioSinkState audioSinkState;
-
audio::Token operationToken;
Type opType = Type::Idle;
std::string filePath;
M module-audio/Audio/Operation/PlaybackOperation.cpp => module-audio/Audio/Operation/PlaybackOperation.cpp +16 -20
@@ 3,12 3,11 @@
#include "PlaybackOperation.hpp"
-#include "Audio/decoder/decoder.hpp"
+#include "Audio/decoder/Decoder.hpp"
#include "Audio/Profiles/Profile.hpp"
#include "Audio/AudioCommon.hpp"
-#include <bsp/audio/bsp_audio.hpp>
#include <log/log.hpp>
namespace audio
@@ 22,23 21,6 @@ namespace audio
std::function<uint32_t(const std::string &path, const uint32_t &defaultValue)> dbCallback)
: Operation(playbackType), dec(nullptr)
{
- audioCallback = [this](const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer) -> int32_t {
-
-#if PERF_STATS_ON == 1
- auto tstamp = xTaskGetTickCount();
-#endif
- auto ret = dec->decode(framesPerBuffer, reinterpret_cast<int16_t *>(outputBuffer));
-#if PERF_STATS_ON == 1
- LOG_DEBUG("Dec:%dms", xTaskGetTickCount() - tstamp);
- // LOG_DEBUG("Watermark:%lu",uxTaskGetStackHighWaterMark2(NULL)); M.P: left here on purpose, it's handy
- // during sf tests on hardware
-#endif
- if (ret == 0) {
- state = State::Idle;
- eventCallback({PlaybackEventType::EndOfFile, operationToken});
- }
- return ret;
- };
constexpr audio::Volume defaultLoudspeakerVolume = 10;
constexpr audio::Volume defaultHeadphonesVolume = 2;
@@ 65,7 47,7 @@ namespace audio
}
currentProfile = defaultProfile;
- dec = decoder::Create(file);
+ dec = Decoder::Create(file);
if (dec == nullptr) {
throw AudioInitException("Error during initializing decoder", RetCode::FileDoesntExist);
}
@@ 74,6 56,11 @@ namespace audio
if (retCode != RetCode::Success) {
throw AudioInitException("Failed to switch audio profile", retCode);
}
+
+ endOfFileCallback = [this]() {
+ state = State::Idle;
+ eventCallback({PlaybackEventType::EndOfFile, operationToken});
+ };
}
audio::RetCode PlaybackOperation::Start(audio::AsyncCallback callback, audio::Token token)
@@ 83,6 70,9 @@ namespace audio
}
operationToken = token;
+ assert(dataStreamOut);
+ dec->startDecodingWorker(*dataStreamOut, endOfFileCallback);
+
if (!tags) {
tags = dec->fetchTags();
}
@@ 188,12 178,18 @@ namespace audio
return RetCode::UnsupportedProfile;
}
+ if (dec->isConnected()) {
+ dec->disconnectStream();
+ }
+
audioDevice = bsp::AudioDevice::Create(currentProfile->GetAudioDeviceType(), audioCallback).value_or(nullptr);
if (audioDevice == nullptr) {
LOG_ERROR("Error creating AudioDevice");
return RetCode::Failed;
}
+ dec->connect(audioDevice->sink, *dataStreamOut);
+
currentProfile->SetSampleRate(currentSampleRate);
currentProfile->SetInOutFlags(currentInOutFlags);
M module-audio/Audio/Operation/PlaybackOperation.hpp => module-audio/Audio/Operation/PlaybackOperation.hpp +8 -4
@@ 4,6 4,11 @@
#pragma once
#include "Operation.hpp"
+#include "Audio/Stream.hpp"
+#include "Audio/Endpoint.hpp"
+#include "Audio/decoder/DecoderWorker.hpp"
+#include "Audio/StreamQueuedEventsListener.hpp"
+#include "Audio/decoder/Decoder.hpp"
#include <bsp/audio/bsp_audio.hpp>
@@ 15,9 20,6 @@ namespace audio::playbackDefaults
namespace audio
{
- class decoder;
- struct Tags;
-
class PlaybackOperation : public Operation
{
public:
@@ 40,8 42,10 @@ namespace audio
Position GetPosition() final;
private:
- std::unique_ptr<decoder> dec;
+ std::unique_ptr<Decoder> dec;
std::unique_ptr<Tags> tags;
+
+ DecoderWorker::EndOfFileCallback endOfFileCallback;
};
} // namespace audio
M module-audio/Audio/Operation/RouterOperation.cpp => module-audio/Audio/Operation/RouterOperation.cpp +5 -83
@@ 14,10 14,6 @@
#include <optional>
#include <vector>
-// enforced optimization is needed for std::vector::insert and std::fill to be
-// as quick as memcpy and memset respectively
-#pragma GCC optimize("O3")
-
namespace audio
{
@@ 25,81 21,6 @@ namespace audio
[[maybe_unused]] const char *file,
std::function<std::uint32_t(const std::string &path, const std::uint32_t &defaultValue)> dbCallback)
{
-
- audioDeviceCallback =
- [this](const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer) -> std::int32_t {
- if (inputBuffer != nullptr) {
- cpp_freertos::LockGuard lock(audioMutex);
- receivedFramesDiffAudio++;
-
- if (framesPerBuffer > audioDeviceBuffer.size()) {
- audioDeviceBuffer.resize(framesPerBuffer, 0);
- }
-
- if (muteEnable) {
- std::fill(std::begin(audioDeviceBuffer), std::end(audioDeviceBuffer), 0);
- }
- else {
- auto rangeStart = static_cast<const std::uint16_t *>(inputBuffer);
- auto rangeEnd = rangeStart + framesPerBuffer;
- std::copy(rangeStart, rangeEnd, std::begin(audioDeviceBuffer));
- }
- }
-
- if (outputBuffer != nullptr) {
- cpp_freertos::LockGuard lock(cellularMutex);
- receivedFramesDiffCellular--;
-
- if (receivedFramesDiffCellular != 0) {
- LOG_FATAL("Audio router synchronization fail, diff = %d", receivedFramesDiffCellular);
- receivedFramesDiffCellular = 0;
- }
-
- if (framesPerBuffer > audioDeviceCellularBuffer.size()) {
- audioDeviceCellularBuffer.resize(framesPerBuffer, 0);
- }
-
- memcpy(outputBuffer, &audioDeviceCellularBuffer[0], framesPerBuffer * sizeof(std::int16_t));
- }
- return framesPerBuffer;
- };
-
- audioDeviceCellularCallback =
- [this](const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer) -> std::int32_t {
- if (inputBuffer != nullptr) {
- cpp_freertos::LockGuard lock(cellularMutex);
- receivedFramesDiffCellular++;
-
- if (framesPerBuffer > audioDeviceCellularBuffer.size()) {
- audioDeviceCellularBuffer.resize(framesPerBuffer, 0);
- }
-
- auto rangeStart = static_cast<const std::uint16_t *>(inputBuffer);
- auto rangeEnd = rangeStart + framesPerBuffer;
- std::copy(rangeStart, rangeEnd, std::begin(audioDeviceCellularBuffer));
- }
-
- if (outputBuffer != nullptr) {
- cpp_freertos::LockGuard lock(audioMutex);
- receivedFramesDiffAudio--;
-
- if (receivedFramesDiffAudio != 0) {
- LOG_FATAL("Audio router synchronization fail, diff = %d", receivedFramesDiffAudio);
- receivedFramesDiffAudio = 0;
- }
-
- if (framesPerBuffer > audioDeviceBuffer.size()) {
- audioDeviceBuffer.resize(framesPerBuffer, 0);
- }
-
- memcpy(outputBuffer, &audioDeviceBuffer[0], framesPerBuffer * sizeof(std::int16_t));
- }
- return framesPerBuffer;
- };
-
- audioDeviceBuffer.resize(INPUT_BUFFER_START_SIZE, 0);
- audioDeviceCellularBuffer.resize(INPUT_BUFFER_START_SIZE, 0);
-
constexpr audio::Gain defaultRoutingEarspeakerGain = 20;
constexpr audio::Volume defaultRoutingEarspeakerVolume = 10;
constexpr audio::Gain defaultRoutingSpeakerphoneGain = 20;
@@ 276,20 197,21 @@ namespace audio
return RetCode::UnsupportedProfile;
}
- audioDevice =
- bsp::AudioDevice::Create(currentProfile->GetAudioDeviceType(), audioDeviceCallback).value_or(nullptr);
+ audioDevice = bsp::AudioDevice::Create(currentProfile->GetAudioDeviceType(), nullptr).value_or(nullptr);
if (audioDevice == nullptr) {
LOG_ERROR("Error creating AudioDevice");
return RetCode::Failed;
}
- audioDeviceCellular =
- bsp::AudioDevice::Create(bsp::AudioDevice::Type::Cellular, audioDeviceCellularCallback).value_or(nullptr);
+ audioDeviceCellular = bsp::AudioDevice::Create(bsp::AudioDevice::Type::Cellular, nullptr).value_or(nullptr);
if (audioDeviceCellular == nullptr) {
LOG_ERROR("Error creating AudioDeviceCellular");
return RetCode::Failed;
}
+ audioDevice->source.connect(audioDeviceCellular->sink, *dataStreamIn);
+ audioDeviceCellular->source.connect(audioDevice->sink, *dataStreamOut);
+
switch (state) {
case State::Idle:
case State::Paused:
M module-audio/Audio/Operation/RouterOperation.hpp => module-audio/Audio/Operation/RouterOperation.hpp +0 -14
@@ 47,22 47,8 @@ namespace audio
bool Mute(bool enable);
bool muteEnable = false;
-
std::unique_ptr<Encoder> enc;
-
std::unique_ptr<bsp::AudioDevice> audioDeviceCellular;
-
- AudioCallback audioDeviceCallback = nullptr;
- AudioCallback audioDeviceCellularCallback = nullptr;
-
- std::vector<std::int16_t> audioDeviceBuffer;
- std::vector<std::int16_t> audioDeviceCellularBuffer;
-
- cpp_freertos::MutexStandard audioMutex;
- cpp_freertos::MutexStandard cellularMutex;
-
- int receivedFramesDiffAudio = 0;
- int receivedFramesDiffCellular = 0;
};
} // namespace audio
A module-audio/Audio/Stream.cpp => module-audio/Audio/Stream.cpp +325 -0
@@ 0,0 1,325 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "Stream.hpp"
+
+#include <macros.h>
+
+#include <algorithm>
+#include <iterator>
+
+using namespace audio;
+
+Stream::Stream(Allocator &allocator, std::size_t blockSize, unsigned int bufferingSize)
+ : _allocator(allocator), _blockSize(blockSize), _blockCount(bufferingSize),
+ _buffer(_allocator.allocate(_blockSize * _blockCount)), _emptyBuffer(_allocator.allocate(_blockSize)),
+ _dataStart(_buffer.get(), _blockSize * _blockCount, _buffer.get(), _blockSize), _dataEnd(_dataStart),
+ _peekPosition(_dataStart), _writeReservationPosition(_dataStart)
+{
+ std::fill(_emptyBuffer.get(), _emptyBuffer.get() + blockSize, 0);
+}
+
+bool Stream::push(void *data, std::size_t dataSize)
+{
+ /// wrapper - no synchronization needed
+ return push(Span{.data = static_cast<std::uint8_t *>(data), .dataSize = dataSize});
+}
+
+bool Stream::push(const Span &span)
+{
+ LockGuard lock();
+
+ /// sanity - do not store buffers different than internal block size
+ if (span.dataSize != _blockSize) {
+ return false;
+ }
+
+ /// write reservation in progress
+ if (_dataEnd != _writeReservationPosition) {
+ return false;
+ }
+
+ /// no space left
+ if (isFull()) {
+ broadcastEvent(Event::StreamOverflow);
+ return false;
+ }
+
+ auto nextDataBlock = *_dataEnd;
+ std::copy(span.data, span.dataEnd(), nextDataBlock.data);
+
+ _dataEnd++;
+ _blocksUsed++;
+ _writeReservationPosition = _dataEnd;
+
+ broadcastStateEvents();
+
+ return true;
+}
+
+bool Stream::push()
+{
+ /// wrapper - no synchronization needed
+ return push(getNullSpan());
+}
+
+bool Stream::pop(Span &span)
+{
+ LockGuard lock();
+
+ /// sanity - do not store buffers different than internal block size
+ if (span.dataSize != _blockSize) {
+ return false;
+ }
+
+ /// peek in progress
+ if (_dataStart != _peekPosition) {
+ return false;
+ }
+
+ if (isEmpty()) {
+ span = getNullSpan();
+ broadcastEvent(Event::StreamUnderFlow);
+ return false;
+ }
+
+ std::copy((*_dataStart).data, (*_dataStart).dataEnd(), span.data);
+
+ _dataStart++;
+ _blocksUsed--;
+ _peekPosition = _dataStart;
+
+ broadcastStateEvents();
+ return true;
+}
+
+void Stream::consume()
+{
+ LockGuard lock();
+
+ _blocksUsed -= _peekCount;
+ _peekCount = 0;
+ _dataStart = _peekPosition;
+
+ broadcastStateEvents();
+}
+
+bool Stream::peek(Span &span)
+{
+ LockGuard lock();
+
+ if (getPeekedCount() < getUsedBlockCount()) {
+ span = *_peekPosition++;
+ _peekCount++;
+ return true;
+ }
+
+ span = getNullSpan();
+ broadcastEvent(Event::StreamUnderFlow);
+ return false;
+}
+
+void Stream::unpeek()
+{
+ LockGuard lock();
+
+ _peekPosition = _dataStart;
+ _peekCount = 0;
+}
+
+bool Stream::reserve(Span &span)
+{
+ LockGuard lock();
+
+ if (getBlockCount() - getUsedBlockCount() > _reserveCount) {
+ span = *++_writeReservationPosition;
+ _reserveCount++;
+ return true;
+ }
+
+ broadcastEvent(Event::StreamOverflow);
+ return false;
+}
+
+void Stream::commit()
+{
+ LockGuard lock();
+
+ _blocksUsed += _reserveCount;
+ _reserveCount = 0;
+ _dataEnd = _writeReservationPosition;
+
+ broadcastStateEvents();
+}
+
+void Stream::release()
+{
+ LockGuard lock();
+
+ _reserveCount = 0;
+ _writeReservationPosition = _dataEnd;
+}
+
+std::size_t Stream::getBlockSize() const noexcept
+{
+ LockGuard lock();
+
+ return _blockSize;
+}
+
+void Stream::registerListener(EventListener *listener)
+{
+ LockGuard lock();
+
+ listeners.push_back(std::ref(listener));
+}
+
+void Stream::unregisterListeners(Stream::EventListener *listener)
+{
+ LockGuard lock();
+
+ auto it = std::find(listeners.begin(), listeners.end(), listener);
+ if (it != listeners.end()) {
+ listeners.erase(it);
+ }
+}
+
+void Stream::broadcastEvent(Event event)
+{
+ auto eventMode = isIRQ() ? EventSourceMode::ISR : EventSourceMode::Thread;
+
+ for (auto listener : listeners) {
+ listener->onEvent(this, event, eventMode);
+ }
+}
+
+void Stream::broadcastStateEvents()
+{
+ if (_blocksUsed == (getBlockCount() / 2)) {
+ broadcastEvent(Event::StreamHalfUsed);
+ }
+
+ else if (isEmpty()) {
+ broadcastEvent(Event::StreamEmpty);
+ }
+
+ else if (isFull()) {
+ broadcastEvent(Event::StreamFull);
+ }
+}
+
+std::size_t Stream::getBlockCount() const noexcept
+{
+ return _blockCount;
+}
+
+std::size_t Stream::getUsedBlockCount() const noexcept
+{
+ return _blocksUsed;
+}
+
+std::size_t Stream::getPeekedCount() const noexcept
+{
+ return _peekCount;
+}
+
+std::size_t Stream::getReservedCount() const noexcept
+{
+ return _reserveCount;
+}
+
+bool Stream::isEmpty() const noexcept
+{
+ LockGuard lock();
+ return getUsedBlockCount() == 0;
+}
+
+bool Stream::isFull() const noexcept
+{
+ LockGuard lock();
+ return getUsedBlockCount() == getBlockCount();
+}
+
+bool Stream::blocksAvailable() const noexcept
+{
+ return !isEmpty();
+}
+
+Stream::UniqueStreamBuffer StandardStreamAllocator::allocate(std::size_t size)
+{
+ return std::make_unique<uint8_t[]>(size);
+}
+
+Stream::UniqueStreamBuffer NonCacheableStreamAllocator::allocate(std::size_t size)
+{
+ return std::unique_ptr<std::uint8_t[], std::function<void(uint8_t[])>>(
+ allocator.allocate(size), [this, size](std::uint8_t ptr[]) { allocator.deallocate(ptr, size); });
+}
+
+Stream::RawBlockIterator::RawBlockIterator(std::uint8_t *bufStart,
+ std::size_t bufSize,
+ std::uint8_t *ptr,
+ std::size_t stepSize)
+ : _bufStart(bufStart), _bufEnd(bufStart + bufSize), _curPos(ptr), _stepSize(stepSize)
+{}
+
+Stream::RawBlockIterator &Stream::RawBlockIterator::operator++()
+{
+ _curPos += _stepSize;
+ if (_curPos == _bufEnd) {
+ _curPos = _bufStart;
+ }
+
+ return *this;
+}
+
+bool Stream::RawBlockIterator::operator==(const Stream::RawBlockIterator &rhs)
+{
+ return _curPos == rhs._curPos;
+}
+
+bool Stream::RawBlockIterator::operator!=(const Stream::RawBlockIterator &rhs)
+{
+ return !operator==(rhs);
+}
+
+Stream::RawBlockIterator Stream::RawBlockIterator::operator++(int)
+{
+ RawBlockIterator tmp(*this);
+ operator++();
+ return tmp;
+}
+
+Stream::RawBlockIterator &Stream::RawBlockIterator::operator--()
+{
+ if (_curPos == _bufStart) {
+ _curPos = _bufEnd - _stepSize;
+ }
+ else {
+ _curPos -= _stepSize;
+ }
+
+ return *this;
+}
+
+Stream::RawBlockIterator Stream::RawBlockIterator::operator--(int)
+{
+ RawBlockIterator tmp(*this);
+ operator--();
+ return tmp;
+}
+
+Stream::Span Stream::RawBlockIterator::operator*()
+{
+ return Stream::Span{.data = _curPos, .dataSize = _stepSize};
+}
+
+std::uint8_t *Stream::Span::dataEnd() const noexcept
+{
+ return data + dataSize;
+}
+
+Stream::Span Stream::getNullSpan() const noexcept
+{
+ return Span{.data = _emptyBuffer.get(), .dataSize = _blockSize};
+}
A module-audio/Audio/Stream.hpp => module-audio/Audio/Stream.hpp +166 -0
@@ 0,0 1,166 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <memory/NonCachedMemAllocator.hpp>
+#include <CriticalSectionGuard.hpp>
+
+#include <algorithm>
+#include <cstdint>
+#include <functional>
+#include <list>
+#include <memory>
+#include <utility>
+
+namespace audio
+{
+ class Stream
+ {
+ public:
+ using UniqueStreamBuffer = std::unique_ptr<std::uint8_t[], std::function<void(uint8_t[])>>;
+
+ struct Span
+ {
+ std::uint8_t *data = nullptr;
+ std::size_t dataSize = 0;
+
+ std::uint8_t *dataEnd() const noexcept;
+ };
+
+ class RawBlockIterator
+ {
+ public:
+ RawBlockIterator(std::uint8_t *bufStart, std::size_t bufSize, std::uint8_t *ptr, std::size_t stepSize);
+
+ bool operator==(const RawBlockIterator &rhs);
+ bool operator!=(const RawBlockIterator &rhs);
+ RawBlockIterator &operator++();
+ RawBlockIterator &operator--();
+ RawBlockIterator operator++(int);
+ RawBlockIterator operator--(int);
+ Span operator*();
+
+ private:
+ std::uint8_t *_bufStart = nullptr;
+ std::uint8_t *_bufEnd = nullptr;
+ std::uint8_t *_curPos = nullptr;
+ std::size_t _stepSize = 0;
+ };
+
+ class Allocator
+ {
+ public:
+ virtual UniqueStreamBuffer allocate(std::size_t size) = 0;
+ };
+
+ enum class Event
+ {
+ NoEvent,
+ StreamFull,
+ StreamHalfUsed,
+ StreamEmpty,
+ StreamOverflow,
+ StreamUnderFlow
+ };
+
+ enum class EventSourceMode
+ {
+ ISR,
+ Thread
+ };
+
+ class EventListener
+ {
+ public:
+ virtual void onEvent(Stream *stream, Event event, EventSourceMode source) = 0;
+ };
+
+ static constexpr auto defaultBufferingSize = 4U;
+
+ Stream(Allocator &allocator, std::size_t blockSize, unsigned int bufferingSize = defaultBufferingSize);
+
+ /// push
+ bool push(void *data, std::size_t dataSize);
+ bool push(const Span &span);
+ bool push();
+
+ /// pop
+ bool pop(Span &span);
+
+ /// zero copy write
+ bool reserve(Span &span);
+ void commit();
+ void release();
+
+ /// zero copy read
+ bool peek(Span &span);
+ void consume();
+ void unpeek();
+
+ /// get empty data span
+ Span getNullSpan() const noexcept;
+
+ [[nodiscard]] std::size_t getBlockSize() const noexcept;
+ [[nodiscard]] std::size_t getBlockCount() const noexcept;
+ [[nodiscard]] std::size_t getUsedBlockCount() const noexcept;
+ [[nodiscard]] std::size_t getPeekedCount() const noexcept;
+ [[nodiscard]] std::size_t getReservedCount() const noexcept;
+ [[nodiscard]] bool isEmpty() const noexcept;
+ [[nodiscard]] bool isFull() const noexcept;
+ [[nodiscard]] bool blocksAvailable() const noexcept;
+
+ void registerListener(EventListener *listener);
+ void unregisterListeners(EventListener *listener);
+
+ private:
+ using LockGuard = cpp_freertos::CriticalSectionGuard;
+
+ void broadcastEvent(Event event);
+ void broadcastStateEvents();
+
+ Allocator &_allocator;
+ std::size_t _blockSize = 0;
+ std::size_t _blockCount = 0;
+ std::size_t _blocksUsed = 0;
+ std::size_t _peekCount = 0;
+ std::size_t _reserveCount = 0;
+ UniqueStreamBuffer _buffer;
+ UniqueStreamBuffer _emptyBuffer;
+ std::list<EventListener *> listeners;
+
+ RawBlockIterator _dataStart;
+ RawBlockIterator _dataEnd;
+ RawBlockIterator _peekPosition;
+ RawBlockIterator _writeReservationPosition;
+ };
+
+ class StandardStreamAllocator : public Stream::Allocator
+ {
+ public:
+ Stream::UniqueStreamBuffer allocate(std::size_t size);
+ };
+
+ class NonCacheableStreamAllocator : public Stream::Allocator
+ {
+ public:
+ NonCacheableStreamAllocator() = default;
+ Stream::UniqueStreamBuffer allocate(std::size_t size);
+
+ private:
+ NonCachedMemAllocator<uint8_t> allocator;
+ };
+
+} // namespace audio
+
+namespace std
+{
+ template <> struct iterator_traits<audio::Stream::RawBlockIterator>
+ {
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = audio::Stream::Span;
+ using difference_type = std::size_t;
+ using pointer = audio::Stream::Span *;
+ using reference = audio::Stream::Span &;
+ };
+}; // namespace std
A module-audio/Audio/StreamQueuedEventsListener.cpp => module-audio/Audio/StreamQueuedEventsListener.cpp +50 -0
@@ 0,0 1,50 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "StreamQueuedEventsListener.hpp"
+
+using namespace audio;
+
+StreamQueuedEventsListener::StreamQueuedEventsListener(std::shared_ptr<Queue> eventsQueue) : queue(eventsQueue)
+{}
+
+void StreamQueuedEventsListener::onEvent(Stream *stream, Stream::Event event, Stream::EventSourceMode source)
+{
+ portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
+ EventStorage newStorage = {stream, event};
+
+ if (source == Stream::EventSourceMode::ISR) {
+ queue->EnqueueFromISR(&newStorage, &xHigherPriorityTaskWoken);
+ if (xHigherPriorityTaskWoken) {
+ taskYIELD();
+ }
+ }
+ else if (!queue->Enqueue(&newStorage)) {
+ LOG_ERROR("Queue full.");
+ }
+}
+
+StreamQueuedEventsListener::queuedEvent StreamQueuedEventsListener::waitForEvent()
+{
+ EventStorage queueStorage;
+ if (queue->Dequeue(&queueStorage)) {
+ return std::make_pair(queueStorage.stream, queueStorage.event);
+ }
+ return std::make_pair(nullptr, Stream::Event::NoEvent);
+}
+
+std::size_t StreamQueuedEventsListener::getEventsCount() const
+{
+ return queue->NumItems();
+}
+
+StreamQueuedEventsListener::queuedEvent StreamQueuedEventsListener::getEvent()
+{
+ EventStorage queueStorage;
+
+ if (queue->Dequeue(&queueStorage, 0)) {
+ return std::make_pair(queueStorage.stream, queueStorage.event);
+ }
+
+ return std::make_pair(nullptr, Stream::Event::NoEvent);
+}
A module-audio/Audio/StreamQueuedEventsListener.hpp => module-audio/Audio/StreamQueuedEventsListener.hpp +45 -0
@@ 0,0 1,45 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "Stream.hpp"
+
+#include <queue.hpp>
+
+#include <cstdint>
+#include <string>
+#include <utility>
+
+namespace audio
+{
+ using namespace cpp_freertos;
+
+ class StreamQueuedEventsListener : public Stream::EventListener
+ {
+ private:
+ struct EventStorage
+ {
+ Stream *stream = nullptr;
+ Stream::Event event = Stream::Event::NoEvent;
+ };
+
+ public:
+ using queueInfo = std::pair<QueueHandle_t, std::string>;
+ using queuedEvent = std::pair<Stream *, Stream::Event>;
+ static constexpr auto listenerElementSize = sizeof(EventStorage);
+
+ StreamQueuedEventsListener(std::shared_ptr<Queue> eventsQueue);
+
+ void onEvent(Stream *stream, Stream::Event event, Stream::EventSourceMode source);
+
+ queuedEvent waitForEvent();
+ queuedEvent getEvent();
+
+ std::size_t getEventsCount() const;
+
+ private:
+ std::shared_ptr<Queue> queue;
+ };
+
+}; // namespace audio
R module-audio/Audio/decoder/decoder.cpp => module-audio/Audio/decoder/Decoder.cpp +23 -7
@@ 3,7 3,7 @@
#include <cstdio>
#include <Utils.hpp>
-#include "decoder.hpp"
+#include "Decoder.hpp"
#include "decoderMP3.hpp"
#include "decoderFLAC.hpp"
#include "decoderWAV.hpp"
@@ 15,7 15,7 @@
namespace audio
{
- decoder::decoder(const char *fileName)
+ Decoder::Decoder(const char *fileName)
: filePath(fileName), workerBuffer(std::make_unique<int16_t[]>(workerBufferSize)), tag(std::make_unique<Tags>())
{
@@ 29,14 29,18 @@ namespace audio
std::rewind(fd);
}
- decoder::~decoder()
+ Decoder::~Decoder()
{
+ if (audioWorker) {
+ audioWorker->close();
+ }
+
if (fd) {
std::fclose(fd);
}
}
- std::unique_ptr<Tags> decoder::fetchTags()
+ std::unique_ptr<Tags> Decoder::fetchTags()
{
if (fd) {
auto inPos = std::ftell(fd);
@@ 79,9 83,9 @@ namespace audio
return std::make_unique<Tags>(*tag);
}
- std::unique_ptr<decoder> decoder::Create(const char *file)
+ std::unique_ptr<Decoder> Decoder::Create(const char *file)
{
- std::unique_ptr<decoder> dec;
+ std::unique_ptr<Decoder> dec;
if ((strstr(file, ".wav") != NULL) || (strstr(file, ".WAV") != NULL)) {
dec = std::make_unique<decoderWAV>(file);
}
@@ 103,7 107,7 @@ namespace audio
}
}
- void decoder::convertmono2stereo(int16_t *pcm, uint32_t samplecount)
+ void Decoder::convertmono2stereo(int16_t *pcm, uint32_t samplecount)
{
uint32_t i = 0, j = 0;
@@ 117,4 121,16 @@ namespace audio
memcpy(pcm, &workerBuffer[0], samplecount * 2 * sizeof(int16_t));
}
+ void Decoder::startDecodingWorker(Stream &audioStream, DecoderWorker::EndOfFileCallback endOfFileCallback)
+ {
+ if (!audioWorker) {
+ audioWorker = std::make_unique<DecoderWorker>(audioStream, this, endOfFileCallback);
+ audioWorker->init();
+ audioWorker->run();
+ }
+ else {
+ LOG_DEBUG("AudioWorker already running.");
+ }
+ }
+
} // namespace audio
R module-audio/Audio/decoder/decoder.hpp => module-audio/Audio/decoder/Decoder.hpp +14 -5
@@ 9,7 9,12 @@
#include <optional>
#include <cstring>
+#include <log/log.hpp>
+#include "Audio/Stream.hpp"
+#include "Audio/Endpoint.hpp"
+#include "Audio/AudioCommon.hpp"
+#include "DecoderWorker.hpp"
namespace audio
{
namespace channel
@@ 65,16 70,18 @@ namespace audio
}
};
- class decoder
+ class Decoder : public Source
{
public:
- decoder(const char *fileName);
+ Decoder(const char *fileName);
- virtual ~decoder();
+ virtual ~Decoder();
virtual uint32_t decode(uint32_t samplesToRead, int16_t *pcmData) = 0;
+ void startDecodingWorker(Stream &audioStream, DecoderWorker::EndOfFileCallback endOfFileCallback);
+
std::unique_ptr<Tags> fetchTags();
// Range 0 - 1
@@ 96,7 103,7 @@ namespace audio
}
// Factory method
- static std::unique_ptr<decoder> Create(const char *file);
+ static std::unique_ptr<Decoder> Create(const char *file);
protected:
virtual void fetchTagsSpecific(){};
@@ 116,7 123,9 @@ namespace audio
std::unique_ptr<int16_t[]> workerBuffer;
std::unique_ptr<Tags> tag;
bool isInitialized = false;
+
+ // decoding worker
+ std::unique_ptr<DecoderWorker> audioWorker;
};
} // namespace audio
-
A module-audio/Audio/decoder/DecoderWorker.cpp => module-audio/Audio/decoder/DecoderWorker.cpp +76 -0
@@ 0,0 1,76 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "DecoderWorker.hpp"
+#include "Audio/decoder/Decoder.hpp"
+
+audio::DecoderWorker::DecoderWorker(Stream &audioStreamOut, Decoder *decoder, EndOfFileCallback endOfFileCallback)
+ : sys::Worker(DecoderWorker::workerName, DecoderWorker::workerPriority), audioStreamOut(audioStreamOut),
+ decoder(decoder), endOfFileCallback(endOfFileCallback),
+ bufferSize(audioStreamOut.getBlockSize() / sizeof(BufferInternalType))
+{}
+
+audio::DecoderWorker::~DecoderWorker()
+{
+ audioStreamOut.unregisterListeners(queueListener.get());
+}
+
+auto audio::DecoderWorker::init(std::list<sys::WorkerQueueInfo> queues) -> bool
+{
+ std::list<sys::WorkerQueueInfo> list{
+ {listenerQueueName, StreamQueuedEventsListener::listenerElementSize, listenerQueueCapacity}};
+
+ auto isSuccessful = Worker::init(list);
+
+ queueListener = std::make_unique<StreamQueuedEventsListener>(getQueueByName(listenerQueueName));
+ if (!queueListener) {
+ return false;
+ }
+
+ audioStreamOut.registerListener(queueListener.get());
+
+ decoderBuffer = std::make_unique<BufferInternalType[]>(bufferSize);
+ if (!decoderBuffer) {
+ return false;
+ }
+
+ return isSuccessful;
+}
+
+bool audio::DecoderWorker::handleMessage(uint32_t queueID)
+{
+ auto queue = queues[queueID];
+ if (queue->GetQueueName() == listenerQueueName && queueListener) {
+ auto event = queueListener->getEvent();
+
+ switch (event.second) {
+ case Stream::Event::StreamOverflow:
+ break;
+ case Stream::Event::StreamUnderFlow:
+ break;
+ case Stream::Event::NoEvent:
+ break;
+ case Stream::Event::StreamFull:
+ break;
+ case Stream::Event::StreamHalfUsed:
+ [[fallthrough]];
+ case Stream::Event::StreamEmpty:
+ auto samplesRead = 0;
+
+ while (!audioStreamOut.isFull()) {
+ samplesRead = decoder->decode(bufferSize, decoderBuffer.get());
+
+ if (samplesRead == 0) {
+ endOfFileCallback();
+ break;
+ }
+
+ if (!audioStreamOut.push(decoderBuffer.get(), samplesRead * sizeof(BufferInternalType))) {
+ LOG_FATAL("Decoder failed to push to stream.");
+ break;
+ }
+ }
+ }
+ }
+ return true;
+}
A module-audio/Audio/decoder/DecoderWorker.hpp => module-audio/Audio/decoder/DecoderWorker.hpp +39 -0
@@ 0,0 1,39 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <Service/Worker.hpp>
+#include "Audio/StreamQueuedEventsListener.hpp"
+
+namespace audio
+{
+ class Decoder;
+ class DecoderWorker : public sys::Worker
+ {
+ public:
+ using EndOfFileCallback = std::function<void()>;
+
+ DecoderWorker(Stream &audioStreamOut, Decoder *decoder, EndOfFileCallback endOfFileCallback);
+ ~DecoderWorker() override;
+
+ virtual auto init(std::list<sys::WorkerQueueInfo> queues = std::list<sys::WorkerQueueInfo>()) -> bool override;
+ virtual auto handleMessage(uint32_t queueID) -> bool override;
+
+ private:
+ using BufferInternalType = int16_t;
+
+ static constexpr auto workerName = "DecoderWorker";
+ static constexpr auto workerPriority = static_cast<UBaseType_t>(sys::ServicePriority::Idle);
+ static constexpr auto listenerQueueName = "DecoderWorkerQueue";
+ static constexpr auto listenerQueueCapacity = 1024;
+
+ Stream &audioStreamOut;
+ Decoder *decoder = nullptr;
+ EndOfFileCallback endOfFileCallback;
+ std::unique_ptr<StreamQueuedEventsListener> queueListener;
+
+ const int bufferSize;
+ std::unique_ptr<BufferInternalType[]> decoderBuffer;
+ };
+} // namespace audio
M module-audio/Audio/decoder/decoderFLAC.cpp => module-audio/Audio/decoder/decoderFLAC.cpp +1 -1
@@ 15,7 15,7 @@
namespace audio
{
- decoderFLAC::decoderFLAC(const char *fileName) : decoder(fileName)
+ decoderFLAC::decoderFLAC(const char *fileName) : Decoder(fileName)
{
if (fileSize == 0) {
M module-audio/Audio/decoder/decoderFLAC.hpp => module-audio/Audio/decoder/decoderFLAC.hpp +2 -2
@@ 3,13 3,13 @@
#pragma once
-#include "decoder.hpp"
+#include "Decoder.hpp"
#include "dr_flac.h"
namespace audio
{
- class decoderFLAC : public decoder
+ class decoderFLAC : public Decoder
{
public:
M module-audio/Audio/decoder/decoderMP3.cpp => module-audio/Audio/decoder/decoderMP3.cpp +1 -1
@@ 13,7 13,7 @@
namespace audio
{
- decoderMP3::decoderMP3(const char *fileName) : decoder(fileName)
+ decoderMP3::decoderMP3(const char *fileName) : Decoder(fileName)
{
if (fileSize == 0) {
M module-audio/Audio/decoder/decoderMP3.hpp => module-audio/Audio/decoder/decoderMP3.hpp +2 -4
@@ 6,7 6,7 @@
#include "minimp3/minimp3.h"
#include <cstring>
-#include "decoder.hpp"
+#include "Decoder.hpp"
extern "C"
{
@@ 16,14 16,12 @@ extern "C"
namespace audio
{
- class decoderMP3 : public decoder
+ class decoderMP3 : public Decoder
{
public:
decoderMP3(const char *fileName);
- ~decoderMP3() = default;
-
uint32_t decode(uint32_t samplesToRead, int16_t *pcmData) override;
void setPosition(float pos) override;
M module-audio/Audio/decoder/decoderWAV.cpp => module-audio/Audio/decoder/decoderWAV.cpp +3 -1
@@ 3,10 3,12 @@
#include "decoderWAV.hpp"
+#include "Audio/AudioCommon.hpp"
+
namespace audio
{
- decoderWAV::decoderWAV(const char *fileName) : decoder(fileName)
+ decoderWAV::decoderWAV(const char *fileName) : Decoder(fileName)
{
if (fileSize == 0) {
M module-audio/Audio/decoder/decoderWAV.hpp => module-audio/Audio/decoder/decoderWAV.hpp +2 -5
@@ 3,21 3,18 @@
#pragma once
-#include "decoder.hpp"
+#include "Decoder.hpp"
#include <vector>
namespace audio
{
- class decoderWAV : public decoder
+ class decoderWAV : public Decoder
{
public:
decoderWAV(const char *fileName);
- ~decoderWAV()
- {}
-
uint32_t decode(uint32_t samplesToRead, int16_t *pcmData) override;
void setPosition(float pos) override;
M module-audio/Audio/test/CMakeLists.txt => module-audio/Audio/test/CMakeLists.txt +9 -0
@@ 10,4 10,13 @@ add_catch2_executable(
module-audio
)
+add_gtest_executable(
+ NAME
+ audio-stream
+ SRCS
+ unittest_stream.cpp
+ LIBS
+ module-audio
+)
+
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/testfiles" DESTINATION "${CMAKE_BINARY_DIR}")
M module-audio/Audio/test/unittest_audio.cpp => module-audio/Audio/test/unittest_audio.cpp +2 -2
@@ 5,7 5,7 @@
#include <catch2/catch.hpp>
-#include "Audio/decoder/decoder.hpp"
+#include "Audio/decoder/Decoder.hpp"
#include "Audio/decoder/decoderMP3.hpp"
#include "Audio/decoder/decoderFLAC.hpp"
@@ 24,7 24,7 @@ TEST_CASE("Test audio tags")
{
std::vector<std::string> testExtensions = {"flac", "wav", "mp3"};
for (auto ext : testExtensions) {
- auto dec = audio::decoder::Create(("testfiles/audio." + ext).c_str());
+ auto dec = audio::Decoder::Create(("testfiles/audio." + ext).c_str());
REQUIRE(dec);
auto tags = dec->fetchTags();
REQUIRE(tags);
A module-audio/Audio/test/unittest_stream.cpp => module-audio/Audio/test/unittest_stream.cpp +213 -0
@@ 0,0 1,213 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <gtest/gtest.h>
+
+#include <Audio/Stream.hpp>
+
+#include <cstdint>
+#include <cstring>
+
+constexpr std::size_t defaultBlockSize = 64U;
+constexpr std::size_t defaultBuffering = 4U;
+
+using namespace audio;
+
+static std::uint8_t testData[defaultBuffering][defaultBlockSize];
+static std::uint8_t emptyBlock[defaultBlockSize];
+
+#include <iostream>
+
+static void initTestData()
+{
+ auto fillbuf = [](std::uint8_t *b, std::size_t s, unsigned step) {
+ std::uint8_t v = 0;
+ for (unsigned int i = 0; i < s; ++i, v += step) {
+ b[i] = v; // & (UINT8_MAX - 1);
+ }
+ };
+
+ fillbuf(testData[0], defaultBlockSize, 1);
+ fillbuf(testData[1], defaultBlockSize, 3);
+ fillbuf(testData[2], defaultBlockSize, 7);
+ fillbuf(testData[3], defaultBlockSize, 13);
+ fillbuf(emptyBlock, defaultBlockSize, 0);
+}
+
+static void printBuf(std::uint8_t *buf, std::size_t s)
+{
+ for (unsigned int i = 0; i < s; i++) {
+ std::cout << static_cast<unsigned int>(buf[i]) << " ";
+ }
+
+ std::cout << std::endl;
+}
+
+[[maybe_unused]] static void printBuf(Stream::Span s)
+{
+ printBuf(s.data, s.dataSize);
+}
+
+TEST(Stream, Init)
+{
+ StandardStreamAllocator a;
+ constexpr auto bufferingSize = 2U;
+ Stream s(a, defaultBlockSize, bufferingSize);
+
+ EXPECT_EQ(s.getBlockCount(), bufferingSize);
+ EXPECT_EQ(s.getBlockSize(), defaultBlockSize);
+ EXPECT_EQ(s.getUsedBlockCount(), 0);
+}
+
+TEST(Stream, Push)
+{
+ StandardStreamAllocator a;
+ Stream s(a, defaultBlockSize);
+ auto block = testData[0];
+
+ EXPECT_TRUE(s.push(block, defaultBlockSize));
+ EXPECT_TRUE(s.push(block, defaultBlockSize));
+ EXPECT_TRUE(s.push(block, defaultBlockSize));
+ EXPECT_TRUE(s.push(block, defaultBlockSize));
+ EXPECT_EQ(s.getUsedBlockCount(), 4);
+ EXPECT_FALSE(s.push(block, defaultBlockSize));
+}
+
+TEST(Stream, PushPop)
+{
+ StandardStreamAllocator a;
+ Stream s(a, defaultBlockSize);
+
+ initTestData();
+
+ EXPECT_TRUE(s.push(testData[0], defaultBlockSize));
+ EXPECT_EQ(s.getUsedBlockCount(), 1);
+
+ {
+ std::uint8_t buf[defaultBlockSize];
+ Stream::Span popped = {.data = buf, .dataSize = defaultBlockSize};
+ EXPECT_TRUE(s.pop(popped));
+ ASSERT_EQ(popped.dataSize, defaultBlockSize);
+ ASSERT_EQ(popped.data, buf);
+ ASSERT_EQ(memcmp(popped.data, testData[0], defaultBlockSize), 0);
+ }
+
+ {
+ std::uint8_t buf[defaultBlockSize];
+ Stream::Span popped = {.data = buf, .dataSize = defaultBlockSize};
+ EXPECT_FALSE(s.pop(popped));
+ ASSERT_EQ(popped.dataSize, defaultBlockSize);
+ ASSERT_NE(popped.data, buf);
+ ASSERT_EQ(memcmp(popped.data, emptyBlock, defaultBlockSize), 0);
+ }
+
+ {
+ for (unsigned int i = 0; i < s.getBlockCount(); ++i) {
+ ASSERT_TRUE(s.push(testData[i], defaultBlockSize));
+ }
+
+ for (unsigned int i = 0; i < s.getBlockCount(); ++i) {
+ std::uint8_t buf[defaultBlockSize];
+ Stream::Span popped = {.data = buf, .dataSize = defaultBlockSize};
+ EXPECT_TRUE(s.pop(popped));
+ ASSERT_EQ(popped.dataSize, defaultBlockSize);
+ ASSERT_EQ(popped.data, buf);
+ ASSERT_EQ(memcmp(popped.data, testData[i], defaultBlockSize), 0);
+ }
+ }
+
+ ASSERT_EQ(s.getUsedBlockCount(), 0);
+}
+
+TEST(Stream, Peek)
+{
+ StandardStreamAllocator a;
+ Stream s(a, defaultBlockSize);
+
+ initTestData();
+
+ {
+ Stream::Span span;
+
+ EXPECT_FALSE(s.peek(span));
+ ASSERT_EQ(memcmp(span.data, emptyBlock, span.dataSize), 0);
+ }
+
+ {
+ Stream::Span span;
+ Stream::Span popped;
+
+ EXPECT_TRUE(s.push(testData[0], defaultBlockSize));
+ ASSERT_EQ(s.getPeekedCount(), 0);
+ ASSERT_EQ(s.getUsedBlockCount(), 1);
+ EXPECT_TRUE(s.peek(span));
+ ASSERT_EQ(memcmp(span.data, testData[0], defaultBlockSize), 0);
+ EXPECT_EQ(s.getUsedBlockCount(), 1);
+ EXPECT_FALSE(s.pop(popped));
+ EXPECT_EQ(s.getUsedBlockCount(), 1);
+
+ s.unpeek();
+ EXPECT_TRUE(s.peek(span));
+ ASSERT_EQ(memcmp(span.data, testData[0], defaultBlockSize), 0);
+ s.consume();
+ EXPECT_EQ(s.getUsedBlockCount(), 0);
+ }
+}
+
+TEST(Stream, GreedyPeek)
+{
+ StandardStreamAllocator a;
+ Stream s(a, defaultBlockSize);
+ Stream::Span span;
+
+ initTestData();
+
+ for (unsigned int i = 0; i < s.getBlockCount(); ++i) {
+ ASSERT_TRUE(s.push(testData[i], defaultBlockSize));
+ }
+
+ ASSERT_EQ(s.getUsedBlockCount(), defaultBuffering);
+
+ for (unsigned int i = 0; i < s.getBlockCount(); ++i) {
+ EXPECT_EQ(s.getPeekedCount(), i);
+ EXPECT_TRUE(s.peek(span));
+ EXPECT_EQ(s.getPeekedCount(), i + 1);
+ ASSERT_EQ(span.dataSize, defaultBlockSize);
+ ASSERT_EQ(memcmp(span.data, testData[i], defaultBlockSize), 0);
+ }
+
+ ASSERT_FALSE(s.peek(span));
+}
+
+TEST(Stream, Reserve)
+{
+ StandardStreamAllocator a;
+ Stream s(a, defaultBlockSize);
+ Stream::Span span;
+
+ EXPECT_EQ(s.getReservedCount(), 0);
+ EXPECT_TRUE(s.reserve(span));
+ EXPECT_EQ(s.getReservedCount(), 1);
+ EXPECT_EQ(s.getUsedBlockCount(), 0);
+
+ s.release();
+ EXPECT_EQ(s.getReservedCount(), 0);
+ EXPECT_EQ(s.getUsedBlockCount(), 0);
+
+ for (unsigned int i = 0; i < s.getBlockCount(); ++i) {
+ EXPECT_EQ(s.getReservedCount(), i);
+ EXPECT_TRUE(s.reserve(span));
+ EXPECT_EQ(s.getReservedCount(), i + 1);
+ }
+
+ EXPECT_FALSE(s.reserve(span));
+
+ s.commit();
+ EXPECT_EQ(s.getUsedBlockCount(), s.getBlockCount());
+}
+
+int main(int argc, char **argv)
+{
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
M module-audio/CMakeLists.txt => module-audio/CMakeLists.txt +6 -2
@@ 5,7 5,8 @@ project(module-audio VERSION 1.0
set(SOURCES
- "${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/decoder.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/Decoder.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/DecoderWorker.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/decoderFLAC.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/decoderMP3.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/decoder/decoderWAV.cpp"
@@ 17,13 18,16 @@ set(SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/Audio.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/AudioMux.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/AudioCommon.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Endpoint.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Stream.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/Audio/StreamQueuedEventsListener.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/Operation/Operation.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/Operation/PlaybackOperation.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/Operation/RecorderOperation.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/Operation/RouterOperation.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/Operation/IdleOperation.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Audio/Profiles/Profile.cpp"
- )
+)
if(NOT ${PROJECT_TARGET} STREQUAL "TARGET_Linux")
include(targets/Target_Cross.cmake)
M module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.cpp => module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.cpp +39 -184
@@ 20,12 20,10 @@ namespace bsp
std::shared_ptr<drivers::DriverDMA> RT1051Audiocodec::dma;
std::unique_ptr<drivers::DriverDMAHandle> RT1051Audiocodec::rxDMAHandle;
std::unique_ptr<drivers::DriverDMAHandle> RT1051Audiocodec::txDMAHandle;
- sai_config_t RT1051Audiocodec::config = {};
- std::uint32_t RT1051Audiocodec::mclkSourceClockHz = 0;
- sai_edma_handle_t RT1051Audiocodec::txHandle = {};
- sai_edma_handle_t RT1051Audiocodec::rxHandle = {};
- int16_t RT1051Audiocodec::inBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2] = {};
- int16_t RT1051Audiocodec::outBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2] = {};
+ sai_config_t RT1051Audiocodec::config = {};
+ std::uint32_t RT1051Audiocodec::mclkSourceClockHz = 0;
+ sai_edma_handle_t RT1051Audiocodec::txHandle = {};
+ sai_edma_handle_t RT1051Audiocodec::rxHandle = {};
RT1051Audiocodec::RT1051Audiocodec(bsp::AudioDevice::audioCallback_t callback)
: AudioDevice(callback), saiInFormat{}, saiOutFormat{}, codecParams{}, codec{}
@@ 106,16 104,6 @@ namespace bsp
InStop();
OutStop();
- if (outWorkerThread) {
- xTaskNotify(outWorkerThread, static_cast<std::uint32_t>(TransferState::Close), eSetBits);
- outWorkerThread = nullptr;
- }
-
- if (inWorkerThread) {
- xTaskNotify(inWorkerThread, static_cast<std::uint32_t>(TransferState::Close), eSetBits);
- inWorkerThread = nullptr;
- }
-
state = State::Stopped;
vTaskDelay(codecSettleTime);
@@ 224,10 212,6 @@ namespace bsp
void RT1051Audiocodec::InStart()
{
sai_transfer_format_t sai_format = {0};
- sai_transfer_t xfer = {0};
-
- saiInFormat.data = (uint8_t *)inBuffer;
- saiInFormat.dataSize = CODEC_CHANNEL_PCM_BUFFER_SIZE * saiInFormat.bitWidth / 8;
/* Configure the audio format */
sai_format.bitWidth = saiInFormat.bitWidth;
@@ 255,22 239,21 @@ namespace bsp
/* Reset SAI Rx internal logic */
SAI_RxSoftwareReset(BOARD_AUDIOCODEC_SAIx, kSAI_ResetTypeSoftware);
- xfer.data = saiInFormat.data;
- xfer.dataSize = saiInFormat.dataSize;
- SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &rxHandle, &xfer);
-
- if (xTaskCreate(inAudioCodecWorkerTask, "inaudiocodec", 1024, this, 0, &inWorkerThread) != pdPASS) {
- LOG_ERROR("Error during creating input audiocodec task");
+ if (!source.isConnected()) {
+ LOG_FATAL("No output stream connected!");
+ return;
}
+
+ /// initiate first read
+ audio::Stream::Span dataSpan;
+ source.getStream()->reserve(dataSpan);
+ auto xfer = sai_transfer_t{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
+ SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &rxHandle, &xfer);
}
void RT1051Audiocodec::OutStart()
{
sai_transfer_format_t sai_format = {0};
- sai_transfer_t xfer = {0};
-
- saiOutFormat.data = (uint8_t *)outBuffer;
- saiOutFormat.dataSize = CODEC_CHANNEL_PCM_BUFFER_SIZE * saiInFormat.bitWidth / 8;
/* Configure the audio format */
sai_format.bitWidth = saiOutFormat.bitWidth;
@@ 297,16 280,14 @@ namespace bsp
/* Reset SAI Tx internal logic */
SAI_TxSoftwareReset(BOARD_AUDIOCODEC_SAIx, kSAI_ResetTypeSoftware);
- xfer.data = saiOutFormat.data;
- xfer.dataSize = saiOutFormat.dataSize;
- SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &txHandle, &xfer);
-
- if (xTaskCreate(outAudioCodecWorkerTask, "outaudiocodec", 1024, this, 0, &outWorkerThread) != pdPASS) {
- LOG_ERROR("Error during creating output audiocodec task");
+ if (!sink.isConnected()) {
+ LOG_FATAL("No input stream connected!");
+ return;
}
- // Fill out buffer with data
- GetAudioCallback()(nullptr, outBuffer, RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE * 2);
+ auto nullSpan = sink.getStream()->getNullSpan();
+ auto xfer = sai_transfer_t{.data = nullSpan.data, .dataSize = nullSpan.dataSize};
+ SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &txHandle, &xfer);
}
void RT1051Audiocodec::OutStop()
@@ 327,168 308,42 @@ namespace bsp
memset(&rxHandle, 0, sizeof(rxHandle));
}
- void inAudioCodecWorkerTask(void *pvp)
- {
- std::uint32_t ulNotificationValue = 0;
- RT1051Audiocodec *inst = reinterpret_cast<RT1051Audiocodec *>(pvp);
-
- while (true) {
- xTaskNotifyWait(0x00, /* Don't clear any bits on entry. */
- UINT32_MAX, /* Clear all bits on exit. */
- &ulNotificationValue, /* Receives the notification value. */
- portMAX_DELAY); /* Block indefinitely. */
- {
- if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::Close)) {
- break;
- }
-
- if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::HalfTransfer)) {
- auto framesFetched = inst->GetAudioCallback()(
- inst->inBuffer, nullptr, RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE);
-
- if (framesFetched < RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
- memset(&inst->inBuffer[framesFetched],
- 0,
- RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
- }
- }
-
- if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::FullTransfer)) {
- auto framesFetched =
- inst->GetAudioCallback()(&inst->inBuffer[RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE],
- nullptr,
- RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE);
-
- if (framesFetched < RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
- memset(&inst->inBuffer[RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE + framesFetched],
- 0,
- RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
- }
- }
- }
- }
-
- vTaskDelete(nullptr);
- }
-
- void outAudioCodecWorkerTask(void *pvp)
- {
- std::uint32_t ulNotificationValue = 0;
- RT1051Audiocodec *inst = reinterpret_cast<RT1051Audiocodec *>(pvp);
-
- while (true) {
- xTaskNotifyWait(0x00, /* Don't clear any bits on entry. */
- UINT32_MAX, /* Clear all bits on exit. */
- &ulNotificationValue, /* Receives the notification value. */
- portMAX_DELAY); /* Block indefinitely. */
- {
- if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::Close)) {
- break;
- }
-
- if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::HalfTransfer)) {
- auto framesFetched = inst->GetAudioCallback()(
- nullptr, inst->outBuffer, RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE);
-
- if (framesFetched < RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
- memset(&inst->outBuffer[framesFetched],
- 0,
- RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
- }
- }
-
- if (ulNotificationValue & static_cast<std::uint32_t>(RT1051Audiocodec::TransferState::FullTransfer)) {
- auto framesFetched =
- inst->GetAudioCallback()(nullptr,
- &inst->outBuffer[RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE],
- RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE);
-
- if (framesFetched < RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
- memset(&inst->outBuffer[RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE + framesFetched],
- 0,
- RT1051Audiocodec::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
- }
- }
- }
- }
-
- vTaskDelete(nullptr);
- }
-
void rxAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
{
- static RT1051Audiocodec::TransferState state = RT1051Audiocodec::TransferState::HalfTransfer;
- RT1051Audiocodec *inst = (RT1051Audiocodec *)userData;
- sai_transfer_t xfer = {0};
- BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ audio::Stream::Span dataSpan;
+ auto self = static_cast<RT1051Audiocodec *>(userData);
+ auto &source = self->source;
- if (inst->state == RT1051Audiocodec::State::Stopped) {
+ /// exit if not connected to the stream
+ if (!source.isConnected()) {
return;
}
- if (state == RT1051Audiocodec::TransferState::HalfTransfer) {
-
- xfer.dataSize = inst->saiInFormat.dataSize;
- xfer.data = inst->saiInFormat.data + (inst->saiInFormat.dataSize);
- SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &inst->rxHandle, &xfer);
-
- /* Notify the task that the transmission is complete. */
- if (inst->inWorkerThread) {
- xTaskNotifyFromISR(
- inst->inWorkerThread, static_cast<uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
- }
- state = RT1051Audiocodec::TransferState::FullTransfer;
- }
- else {
-
- xfer.dataSize = inst->saiInFormat.dataSize;
- xfer.data = inst->saiInFormat.data;
- SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &inst->rxHandle, &xfer);
-
- /* Notify the task that the transmission is complete. */
- xTaskNotifyFromISR(inst->inWorkerThread, static_cast<uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
+ /// reserve space for the next read commiting previously reserved block before
+ source.getStream()->commit();
+ source.getStream()->reserve(dataSpan);
- state = RT1051Audiocodec::TransferState::HalfTransfer;
- }
-
- portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+ sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
+ SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &self->rxHandle, &xfer);
}
void txAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
{
- static RT1051Audiocodec::TransferState state = RT1051Audiocodec::TransferState::HalfTransfer;
- RT1051Audiocodec *inst = (RT1051Audiocodec *)userData;
- sai_transfer_t xfer = {0};
- BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ audio::Stream::Span dataSpan;
+ auto self = static_cast<RT1051Audiocodec *>(userData);
+ auto &sink = self->sink;
- if (inst->state == RT1051Audiocodec::State::Stopped) {
+ /// exit if not connected to the stream
+ if (!sink.isConnected()) {
return;
}
- if (state == RT1051Audiocodec::TransferState::HalfTransfer) {
-
- xfer.dataSize = inst->saiOutFormat.dataSize;
- xfer.data = inst->saiOutFormat.data + (inst->saiOutFormat.dataSize);
- SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &inst->txHandle, &xfer);
-
- /* Notify the task that the transmission is complete. */
- xTaskNotifyFromISR(
- inst->outWorkerThread, static_cast<uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
-
- state = RT1051Audiocodec::TransferState::FullTransfer;
- }
- else {
- xfer.dataSize = inst->saiOutFormat.dataSize;
- xfer.data = inst->saiOutFormat.data;
- SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &inst->txHandle, &xfer);
-
- /* Notify the task that the transmission is complete. */
- xTaskNotifyFromISR(
- inst->outWorkerThread, static_cast<uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
- state = RT1051Audiocodec::TransferState::HalfTransfer;
- }
+ /// pop previous read and peek next
+ sink.getStream()->consume();
+ sink.getStream()->peek(dataSpan);
- portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+ sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
+ SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &self->txHandle, &xfer);
}
} // namespace bsp
M module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.hpp => module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.hpp +2 -24
@@ 5,6 5,7 @@
#include "bsp/audio/bsp_audio.hpp"
#include "fsl_sai_edma.h"
+
#include "FreeRTOS.h"
#include "task.h"
#include "macros.h"
@@ 22,8 23,6 @@ namespace bsp
void txAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
void rxAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
- void inAudioCodecWorkerTask(void *pvp);
- void outAudioCodecWorkerTask(void *pvp);
class RT1051Audiocodec : public AudioDevice
{
@@ 34,8 33,6 @@ namespace bsp
friend void txAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
friend void rxAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
- friend void inAudioCodecWorkerTask(void *pvp);
- friend void outAudioCodecWorkerTask(void *pvp);
RT1051Audiocodec(AudioDevice::audioCallback_t callback);
virtual ~RT1051Audiocodec();
@@ 51,8 48,7 @@ namespace bsp
cpp_freertos::MutexStandard mutex;
private:
- static const uint32_t CODEC_CHANNEL_PCM_BUFFER_SIZE = 1024;
- const static TickType_t codecSettleTime = 20 * portTICK_PERIOD_MS;
+ constexpr static TickType_t codecSettleTime = 20 * portTICK_PERIOD_MS;
enum class State
{
@@ 60,21 56,11 @@ namespace bsp
Stopped
};
- /*! @brief Internals state of Rx/Tx callback, needed for double buffering technique */
- enum class TransferState
- {
- HalfTransfer = 1 << 0,
- FullTransfer = 1 << 1,
- Close = 1 << 2,
- };
-
struct SAIFormat
{
uint32_t sampleRate_Hz; /*!< Sample rate of audio data */
uint32_t bitWidth; /*!< Data length of audio data, usually 8/16/24/32 bits */
sai_mono_stereo_t stereo; /*!< Mono or stereo */
- uint8_t *data; /*!< Data start address to transfer. */
- size_t dataSize; /*!< Transfer size. */
};
static sai_config_t config;
@@ 83,8 69,6 @@ namespace bsp
State state = State::Stopped;
SAIFormat saiInFormat;
SAIFormat saiOutFormat;
- TaskHandle_t inWorkerThread = nullptr;
- TaskHandle_t outWorkerThread = nullptr;
CodecParamsMAX98090 codecParams;
CodecMAX98090 codec;
@@ 98,12 82,6 @@ namespace bsp
static AT_NONCACHEABLE_SECTION_INIT(sai_edma_handle_t txHandle);
static AT_NONCACHEABLE_SECTION_INIT(sai_edma_handle_t rxHandle);
- // CODEC_CHANNEL_PCM_BUFFER_SIZE * 2 for double buffering
- static ALIGN_(4) int16_t inBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2];
-
- // CODEC_CHANNEL_PCM_BUFFER_SIZE * 2 for double buffering
- static ALIGN_(4) int16_t outBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2];
-
void OutStart();
void InStart();
void OutStop();
M module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.cpp => module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.cpp +37 -184
@@ 15,10 15,8 @@ namespace bsp
using namespace drivers;
- sai_edma_handle_t RT1051CellularAudio::txHandle = {};
- sai_edma_handle_t RT1051CellularAudio::rxHandle = {};
- int16_t RT1051CellularAudio::inBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2] = {};
- int16_t RT1051CellularAudio::outBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2] = {};
+ sai_edma_handle_t RT1051CellularAudio::txHandle = {};
+ sai_edma_handle_t RT1051CellularAudio::rxHandle = {};
RT1051CellularAudio::RT1051CellularAudio(bsp::AudioDevice::audioCallback_t callback)
: AudioDevice(callback), saiInFormat{}, saiOutFormat{}, config{}
@@ 90,16 88,6 @@ namespace bsp
InStop();
OutStop();
- if (outWorkerThread) {
- xTaskNotify(outWorkerThread, static_cast<std::uint32_t>(TransferState::Close), eSetBits);
- outWorkerThread = nullptr;
- }
-
- if (inWorkerThread) {
- xTaskNotify(inWorkerThread, static_cast<std::uint32_t>(TransferState::Close), eSetBits);
- inWorkerThread = nullptr;
- }
-
currentFormat = {};
state = State::Stopped;
@@ 185,10 173,6 @@ namespace bsp
void RT1051CellularAudio::InStart()
{
sai_transfer_format_t sai_format = {0};
- sai_transfer_t xfer = {0};
-
- saiInFormat.data = (uint8_t *)inBuffer;
- saiInFormat.dataSize = CODEC_CHANNEL_PCM_BUFFER_SIZE * saiInFormat.bitWidth / 8;
/* Configure the audio format */
sai_format.bitWidth = saiInFormat.bitWidth;
@@ 216,22 200,21 @@ namespace bsp
/* Reset SAI Rx internal logic */
SAI_RxSoftwareReset(BOARD_CELLULAR_AUDIO_SAIx, kSAI_ResetTypeSoftware);
- xfer.data = saiInFormat.data;
- xfer.dataSize = saiInFormat.dataSize;
- SAI_TransferReceiveEDMA(BOARD_CELLULAR_AUDIO_SAIx, &rxHandle, &xfer);
-
- if (xTaskCreate(inCellularWorkerTask, "incellaudio", 512, this, 0, &inWorkerThread) != pdPASS) {
- LOG_ERROR("Error during creating input cellular audio task");
+ if (!source.isConnected()) {
+ LOG_FATAL("No output stream connected!");
+ return;
}
+
+ /// initiate first read
+ audio::Stream::Span dataSpan;
+ source.getStream()->reserve(dataSpan);
+ auto xfer = sai_transfer_t{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
+ SAI_TransferReceiveEDMA(BOARD_CELLULAR_AUDIO_SAIx, &rxHandle, &xfer);
}
void RT1051CellularAudio::OutStart()
{
sai_transfer_format_t sai_format = {0};
- sai_transfer_t xfer = {0};
-
- saiOutFormat.data = (uint8_t *)outBuffer;
- saiOutFormat.dataSize = CODEC_CHANNEL_PCM_BUFFER_SIZE * saiInFormat.bitWidth / 8;
/* Configure the audio format */
sai_format.bitWidth = saiOutFormat.bitWidth;
@@ 259,16 242,14 @@ namespace bsp
/* Reset SAI Tx internal logic */
SAI_TxSoftwareReset(BOARD_CELLULAR_AUDIO_SAIx, kSAI_ResetTypeSoftware);
- xfer.data = saiOutFormat.data;
- xfer.dataSize = saiOutFormat.dataSize;
- SAI_TransferSendEDMA(BOARD_CELLULAR_AUDIO_SAIx, &txHandle, &xfer);
-
- if (xTaskCreate(outCellularWorkerTask, "outcellaudio", 512, this, 0, &outWorkerThread) != pdPASS) {
- LOG_ERROR("Error during creating output cellular audio task");
+ if (!sink.isConnected()) {
+ LOG_FATAL("No input stream connected!");
+ return;
}
- // Fill out buffer with data
- GetAudioCallback()(nullptr, outBuffer, RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE * 2);
+ auto nullSpan = sink.getStream()->getNullSpan();
+ auto xfer = sai_transfer_t{.data = nullSpan.data, .dataSize = nullSpan.dataSize};
+ SAI_TransferSendEDMA(BOARD_CELLULAR_AUDIO_SAIx, &txHandle, &xfer);
}
void RT1051CellularAudio::OutStop()
@@ 289,170 270,42 @@ namespace bsp
memset(&rxHandle, 0, sizeof(rxHandle));
}
- void inCellularWorkerTask(void *pvp)
- {
- std::uint32_t ulNotificationValue = 0;
- RT1051CellularAudio *inst = reinterpret_cast<RT1051CellularAudio *>(pvp);
-
- while (true) {
- xTaskNotifyWait(0x00, /* Don't clear any bits on entry. */
- UINT32_MAX, /* Clear all bits on exit. */
- &ulNotificationValue, /* Receives the notification value. */
- portMAX_DELAY); /* Block indefinitely. */
- {
- if (ulNotificationValue & static_cast<std::uint32_t>(RT1051CellularAudio::TransferState::Close)) {
- LOG_DEBUG("Rx worker: received close event");
- break;
- }
-
- if (ulNotificationValue & static_cast<uint32_t>(RT1051CellularAudio::TransferState::HalfTransfer)) {
- auto framesFetched = inst->GetAudioCallback()(
- inst->inBuffer, nullptr, RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE);
-
- if (framesFetched < RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
- memset(&inst->inBuffer[framesFetched],
- 0,
- RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
- }
- }
-
- if (ulNotificationValue & static_cast<uint32_t>(RT1051CellularAudio::TransferState::FullTransfer)) {
- auto framesFetched =
- inst->GetAudioCallback()(&inst->inBuffer[RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE],
- nullptr,
- RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE);
-
- if (framesFetched < RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
- memset(&inst->inBuffer[RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE + framesFetched],
- 0,
- RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
- }
- }
- }
- }
-
- LOG_INFO("Killing Rx cellular audio codec worker thread");
- vTaskDelete(nullptr);
- }
-
- void outCellularWorkerTask(void *pvp)
- {
- std::uint32_t ulNotificationValue = 0;
-
- RT1051CellularAudio *inst = reinterpret_cast<RT1051CellularAudio *>(pvp);
-
- while (true) {
- xTaskNotifyWait(0x00, /* Don't clear any bits on entry. */
- UINT32_MAX, /* Clear all bits on exit. */
- &ulNotificationValue, /* Receives the notification value. */
- portMAX_DELAY); /* Block indefinitely. */
- {
- if (ulNotificationValue & static_cast<std::uint32_t>(RT1051CellularAudio::TransferState::Close)) {
- LOG_DEBUG("Tx worker: received close event");
- break;
- }
-
- if (ulNotificationValue & static_cast<uint32_t>(RT1051CellularAudio::TransferState::HalfTransfer)) {
- auto framesFetched = inst->GetAudioCallback()(
- nullptr, inst->outBuffer, RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE);
-
- if (framesFetched < RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
- memset(&inst->outBuffer[framesFetched],
- 0,
- RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
- }
- }
-
- if (ulNotificationValue & static_cast<uint32_t>(RT1051CellularAudio::TransferState::FullTransfer)) {
- auto framesFetched =
- inst->GetAudioCallback()(nullptr,
- &inst->outBuffer[RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE],
- RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE);
-
- if (framesFetched < RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE) {
- memset(&inst->outBuffer[RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE + framesFetched],
- 0,
- RT1051CellularAudio::CODEC_CHANNEL_PCM_BUFFER_SIZE - framesFetched);
- }
- }
- }
- }
-
- LOG_INFO("Killing Tx audio codec worker thread");
- vTaskDelete(nullptr);
- }
-
void rxCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
{
- static RT1051CellularAudio::TransferState state = RT1051CellularAudio::TransferState::HalfTransfer;
- RT1051CellularAudio *inst = (RT1051CellularAudio *)userData;
- sai_transfer_t xfer = {0};
- BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ audio::Stream::Span dataSpan;
+ auto self = static_cast<RT1051CellularAudio *>(userData);
+ auto &source = self->source;
- if (inst->state == RT1051CellularAudio::State::Stopped) {
+ /// exit if not connected to the stream
+ if (!source.isConnected()) {
return;
}
- if (state == RT1051CellularAudio::TransferState::HalfTransfer) {
+ /// reserve space for the next read commiting previously reserved block before
+ source.getStream()->commit();
+ source.getStream()->reserve(dataSpan);
- xfer.dataSize = inst->saiInFormat.dataSize;
- xfer.data = inst->saiInFormat.data + (inst->saiInFormat.dataSize);
- SAI_TransferReceiveEDMA(BOARD_CELLULAR_AUDIO_SAIx, &inst->rxHandle, &xfer);
-
- /* Notify the task that the transmission is complete. */
- xTaskNotifyFromISR(
- inst->inWorkerThread, static_cast<std::uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
- state = RT1051CellularAudio::TransferState::FullTransfer;
- }
- else {
-
- xfer.dataSize = inst->saiInFormat.dataSize;
- xfer.data = inst->saiInFormat.data;
- SAI_TransferReceiveEDMA(BOARD_CELLULAR_AUDIO_SAIx, &inst->rxHandle, &xfer);
-
- /* Notify the task that the transmission is complete. */
- xTaskNotifyFromISR(
- inst->inWorkerThread, static_cast<std::uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
- state = RT1051CellularAudio::TransferState::HalfTransfer;
- }
-
- portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+ sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
+ SAI_TransferReceiveEDMA(BOARD_CELLULAR_AUDIO_SAIx, &self->rxHandle, &xfer);
}
void txCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
{
- static RT1051CellularAudio::TransferState state = RT1051CellularAudio::TransferState::HalfTransfer;
- RT1051CellularAudio *inst = (RT1051CellularAudio *)userData;
- sai_transfer_t xfer = {0};
- BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ audio::Stream::Span dataSpan;
+ auto self = static_cast<RT1051CellularAudio *>(userData);
+ auto &sink = self->sink;
- if (inst->state == RT1051CellularAudio::State::Stopped) {
+ /// exit if not connected to the stream
+ if (!sink.isConnected()) {
return;
}
- if (state == RT1051CellularAudio::TransferState::HalfTransfer) {
-
- xfer.dataSize = inst->saiOutFormat.dataSize;
- xfer.data = inst->saiOutFormat.data + (inst->saiOutFormat.dataSize);
- SAI_TransferSendEDMA(BOARD_CELLULAR_AUDIO_SAIx, &inst->txHandle, &xfer);
-
- /* Notify the task that the transmission is complete. */
- xTaskNotifyFromISR(
- inst->outWorkerThread, static_cast<std::uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
- state = RT1051CellularAudio::TransferState::FullTransfer;
- }
- else {
- xfer.dataSize = inst->saiOutFormat.dataSize;
- xfer.data = inst->saiOutFormat.data;
- SAI_TransferSendEDMA(BOARD_CELLULAR_AUDIO_SAIx, &inst->txHandle, &xfer);
-
- /* Notify the task that the transmission is complete. */
- xTaskNotifyFromISR(
- inst->outWorkerThread, static_cast<std::uint32_t>(state), eSetBits, &xHigherPriorityTaskWoken);
- state = RT1051CellularAudio::TransferState::HalfTransfer;
- }
+ /// pop previous read and peek next
+ sink.getStream()->consume();
+ sink.getStream()->peek(dataSpan);
- portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+ sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
+ SAI_TransferSendEDMA(BOARD_CELLULAR_AUDIO_SAIx, &self->txHandle, &xfer);
}
} // namespace bsp
M module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.hpp => module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.hpp +1 -24
@@ 6,6 6,7 @@
#include "bsp/audio/bsp_audio.hpp"
#include "fsl_sai_edma.h"
+
#include "FreeRTOS.h"
#include "task.h"
#include "macros.h"
@@ 21,8 22,6 @@ namespace bsp
void txCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
void rxCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
- void inCellularWorkerTask(void *pvp);
- void outCellularWorkerTask(void *pvp);
class RT1051CellularAudio : public AudioDevice
{
@@ 30,8 29,6 @@ namespace bsp
public:
friend void txCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
friend void rxCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
- friend void inCellularWorkerTask(void *pvp);
- friend void outCellularWorkerTask(void *pvp);
RT1051CellularAudio(AudioDevice::audioCallback_t callback);
virtual ~RT1051CellularAudio();
@@ 47,29 44,17 @@ namespace bsp
cpp_freertos::MutexStandard mutex;
private:
- static const uint32_t CODEC_CHANNEL_PCM_BUFFER_SIZE = 1024;
-
enum class State
{
Running,
Stopped
};
- /*! @brief Internals state of Rx/Tx callback, needed for double buffering technique */
- enum class TransferState
- {
- HalfTransfer = 1 << 0,
- FullTransfer = 1 << 1,
- Close = 1 << 2,
- };
-
struct SAIFormat
{
uint32_t sampleRate_Hz; /*!< Sample rate of audio data */
uint32_t bitWidth; /*!< Data length of audio data, usually 8/16/24/32 bits */
sai_mono_stereo_t stereo; /*!< Mono or stereo */
- uint8_t *data; /*!< Data start address to transfer. */
- size_t dataSize; /*!< Transfer size. */
};
State state = State::Stopped;
@@ 77,8 62,6 @@ namespace bsp
SAIFormat saiOutFormat;
uint32_t mclkSourceClockHz = 0;
sai_config_t config;
- TaskHandle_t inWorkerThread = nullptr;
- TaskHandle_t outWorkerThread = nullptr;
// M.P: It is important to destroy these drivers in specific order
std::shared_ptr<drivers::DriverPLL> pll;
@@ 90,12 73,6 @@ namespace bsp
static AT_NONCACHEABLE_SECTION_INIT(sai_edma_handle_t txHandle);
static AT_NONCACHEABLE_SECTION_INIT(sai_edma_handle_t rxHandle);
- // CODEC_CHANNEL_PCM_BUFFER_SIZE * 2 for double buffering
- static ALIGN_(4) int16_t inBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2];
-
- // CODEC_CHANNEL_PCM_BUFFER_SIZE * 2 for double buffering
- static ALIGN_(4) int16_t outBuffer[CODEC_CHANNEL_PCM_BUFFER_SIZE * 2];
-
void Init();
void Deinit();
void OutStart();
M module-bsp/bsp/audio/bsp_audio.hpp => module-bsp/bsp/audio/bsp_audio.hpp +100 -65
@@ 1,34 1,42 @@
#pragma once
+#include <Audio/Endpoint.hpp>
+
#include <optional>
#include <memory>
#include <functional>
-namespace bsp {
+namespace bsp
+{
- class AudioDevice {
+ class AudioDevice
+ {
- public:
- enum class RetCode {
- Success = 0,
- Failure
+ public:
+ enum class RetCode
+ {
+ Success = 0,
+ Failure
};
- enum class Type {
+ enum class Type
+ {
None,
Audiocodec,
Cellular,
Bluetooth
};
- enum class InputPath{
+ enum class InputPath
+ {
Headphones,
Microphone,
BluetoothHSP,
None
};
- enum class OutputPath{
+ enum class OutputPath
+ {
Headphones,
HeadphonesMono,
Earspeaker,
@@ 39,52 47,54 @@ namespace bsp {
None
};
- enum class Flags {
- OutputMono = 1 << 0,
+ enum class Flags
+ {
+ OutputMono = 1 << 0,
OutputStereo = 1 << 1,
- InputLeft = 1 << 2,
- InputRight = 1 << 3,
- InputStereo = 1 << 4
+ InputLeft = 1 << 2,
+ InputRight = 1 << 3,
+ InputStereo = 1 << 4
};
- using Format = struct {
- uint32_t sampleRate_Hz = 0; /*!< Sample rate of audio data */
- uint32_t bitWidth = 0; /*!< Data length of audio data, usually 8/16/24/32 bits */
- uint32_t flags = 0; /*!< In/Out configuration flags */
- float outputVolume = 0.0f;
- float inputGain = 0.0f;
- InputPath inputPath = InputPath::None;
- OutputPath outputPath = OutputPath::None;
+ using Format = struct
+ {
+ uint32_t sampleRate_Hz = 0; /*!< Sample rate of audio data */
+ uint32_t bitWidth = 0; /*!< Data length of audio data, usually 8/16/24/32 bits */
+ uint32_t flags = 0; /*!< In/Out configuration flags */
+ float outputVolume = 0.0f;
+ float inputGain = 0.0f;
+ InputPath inputPath = InputPath::None;
+ OutputPath outputPath = OutputPath::None;
};
/**
- * User defined callback.
- * It will be invoked when opened stream needs more frames to process( outputBuffer will be != NULL) or if requested frames count
- * are available to user( inputBuffer will be != NULL).
- * From this callback you can safely use file operations, system calls etc This is because audiostream
- * callbacks are not invoked from IRQ context.
- *
- * If there is more data to process or read user should return:
- * 'AudiostreamCallbackContinue'
- * if there is no more data to process or read user should return:
- * 'AudiostreamCallbackComplete'
- * this will close stream and clean up all internally allocated resources.
- * In case of error return:
- * 'AudiostreamCallbackAbort'
- * this has the same effect as AudiostreamCallbackComplete.
- *
- * @param stream[in] - pointer to valid stream
- * @param inputBuffer[in] - pointer to buffer where user should copy PCM data
- * @param outputBuffer[out] - pointer to buffer containing valid PCM data
- * @param framesPerBuffer[in] - how many frames user should copy or read from buffer
- * @param userData[in] - user specified data
- * @return audiostream_callback_err_t
- */
- using audioCallback_t = std::function<int32_t(const void *inputBuffer,
- void *outputBuffer,
- unsigned long framesPerBuffer)>;
-
- explicit AudioDevice(audioCallback_t callback) : callback(callback) {}
+ * User defined callback.
+ * It will be invoked when opened stream needs more frames to process( outputBuffer will be != NULL) or if
+ * requested frames count are available to user( inputBuffer will be != NULL). From this callback you can safely
+ * use file operations, system calls etc This is because audiostream callbacks are not invoked from IRQ context.
+ *
+ * If there is more data to process or read user should return:
+ * 'AudiostreamCallbackContinue'
+ * if there is no more data to process or read user should return:
+ * 'AudiostreamCallbackComplete'
+ * this will close stream and clean up all internally allocated resources.
+ * In case of error return:
+ * 'AudiostreamCallbackAbort'
+ * this has the same effect as AudiostreamCallbackComplete.
+ *
+ * @param stream[in] - pointer to valid stream
+ * @param inputBuffer[in] - pointer to buffer where user should copy PCM data
+ * @param outputBuffer[out] - pointer to buffer containing valid PCM data
+ * @param framesPerBuffer[in] - how many frames user should copy or read from buffer
+ * @param userData[in] - user specified data
+ * @return audiostream_callback_err_t
+ */
+ using audioCallback_t =
+ std::function<int32_t(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer)>;
+
+ explicit AudioDevice(audioCallback_t callback) : callback(callback)
+ {}
+
AudioDevice() = delete;
virtual ~AudioDevice() = default;
@@ 92,26 102,51 @@ namespace bsp {
static std::optional<std::unique_ptr<AudioDevice>> Create(Type type, audioCallback_t callback);
virtual RetCode Start(const Format &format) = 0;
- virtual RetCode Stop() = 0;
+ virtual RetCode Stop() = 0;
- virtual RetCode OutputVolumeCtrl(float vol) = 0;
- virtual RetCode InputGainCtrl(float gain) = 0;
+ virtual RetCode OutputVolumeCtrl(float vol) = 0;
+ virtual RetCode InputGainCtrl(float gain) = 0;
virtual RetCode OutputPathCtrl(OutputPath outputPath) = 0;
- virtual RetCode InputPathCtrl(InputPath inputPath) = 0;
- virtual bool IsFormatSupported(const Format& format) = 0;
-
- float GetOutputVolume() const noexcept { return currentFormat.outputVolume; }
- float GetInputGain() const noexcept { return currentFormat.inputGain; }
- OutputPath GetOutputPath() const noexcept { return currentFormat.outputPath; }
- InputPath GetInputPath() const noexcept { return currentFormat.inputPath; }
-
- Format GetCurrentFormat() const noexcept { return currentFormat; }
- audioCallback_t GetAudioCallback() { return callback; }
-
- protected:
+ virtual RetCode InputPathCtrl(InputPath inputPath) = 0;
+ virtual bool IsFormatSupported(const Format &format) = 0;
+
+ float GetOutputVolume() const noexcept
+ {
+ return currentFormat.outputVolume;
+ }
+
+ float GetInputGain() const noexcept
+ {
+ return currentFormat.inputGain;
+ }
+
+ OutputPath GetOutputPath() const noexcept
+ {
+ return currentFormat.outputPath;
+ }
+
+ InputPath GetInputPath() const noexcept
+ {
+ return currentFormat.inputPath;
+ }
+
+ Format GetCurrentFormat() const noexcept
+ {
+ return currentFormat;
+ }
+
+ audioCallback_t GetAudioCallback()
+ {
+ return callback;
+ }
+
+ audio::Sink sink;
+ audio::Source source;
+
+ protected:
Format currentFormat;
audioCallback_t callback = nullptr;
bool isInitialized = false;
};
-}
+} // namespace bsp
M module-os/CMakeLists.txt => module-os/CMakeLists.txt +2 -0
@@ 28,6 28,8 @@ set(SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/FreeRTOS/timers.c
${CMAKE_CURRENT_SOURCE_DIR}/memory/usermem.c
+
+ ${CMAKE_CURRENT_SOURCE_DIR}/CriticalSectionGuard.cpp
)
#Substitute FreeRTOS SystemvView sources if enabled
A module-os/CriticalSectionGuard.cpp => module-os/CriticalSectionGuard.cpp +34 -0
@@ 0,0 1,34 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "CriticalSectionGuard.hpp"
+
+#include <macros.h>
+
+extern "C"
+{
+#include <FreeRTOS.h>
+#include <portmacro.h>
+}
+
+using namespace cpp_freertos;
+
+CriticalSectionGuard::CriticalSectionGuard()
+{
+ if (isIRQ()) {
+ savedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
+ }
+ else {
+ vPortEnterCritical();
+ }
+}
+
+CriticalSectionGuard::~CriticalSectionGuard()
+{
+ if (isIRQ()) {
+ portCLEAR_INTERRUPT_MASK_FROM_ISR(savedInterruptStatus);
+ }
+ else {
+ vPortExitCritical();
+ }
+}
A module-os/CriticalSectionGuard.hpp => module-os/CriticalSectionGuard.hpp +28 -0
@@ 0,0 1,28 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+extern "C"
+{
+#include <FreeRTOS.h>
+#include <portmacro.h>
+}
+
+namespace cpp_freertos
+{
+ class CriticalSectionGuard
+ {
+ public:
+ CriticalSectionGuard();
+ ~CriticalSectionGuard();
+ CriticalSectionGuard(const CriticalSectionGuard &) = delete;
+ CriticalSectionGuard(CriticalSectionGuard &&) = delete;
+
+ CriticalSectionGuard &operator=(const CriticalSectionGuard &) = delete;
+ CriticalSectionGuard &operator=(CriticalSectionGuard &&) = delete;
+
+ private:
+ UBaseType_t savedInterruptStatus;
+ };
+}; // namespace cpp_freertos
A module-os/memory/NonCachedMemAllocator.hpp => module-os/memory/NonCachedMemAllocator.hpp +36 -0
@@ 0,0 1,36 @@
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <cstdint>
+#include <cassert>
+
+#include <FreeRTOS.h>
+
+template <typename T> struct NonCachedMemAllocator
+{
+ using value_type = T;
+
+ T *allocate(std::size_t num);
+ void deallocate(T *ptr, std::size_t num);
+
+ NonCachedMemAllocator() = default;
+ NonCachedMemAllocator(const NonCachedMemAllocator &) = default;
+ NonCachedMemAllocator(NonCachedMemAllocator &&) = default;
+
+ NonCachedMemAllocator &operator=(const NonCachedMemAllocator &) = default;
+ NonCachedMemAllocator &operator=(NonCachedMemAllocator &&) = default;
+};
+
+template <typename T> T *NonCachedMemAllocator<T>::allocate(std::size_t num)
+{
+ T *ptr = reinterpret_cast<T *>(pvPortMalloc(sizeof(T) * num));
+ return ptr;
+}
+
+template <typename T> void NonCachedMemAllocator<T>::deallocate(T *ptr, std::size_t)
+{
+ assert(ptr != nullptr);
+ vPortFree(ptr);
+}
M module-services/service-audio/AudioServiceAPI.cpp => module-services/service-audio/AudioServiceAPI.cpp +1 -1
@@ 5,7 5,7 @@
#include "service-audio/ServiceAudio.hpp"
#include "service-audio/AudioMessage.hpp"
-#include <Audio/decoder/decoder.hpp>
+#include <Audio/decoder/Decoder.hpp>
#include <Service/Bus.hpp>
#include <Service/Common.hpp>
#include <log/log.hpp>
M module-services/service-audio/service-audio/AudioMessage.hpp => module-services/service-audio/service-audio/AudioMessage.hpp +1 -1
@@ 4,7 4,7 @@
#pragma once
#include <Audio/AudioCommon.hpp>
-#include <Audio/decoder/decoder.hpp>
+#include <Audio/decoder/Decoder.hpp>
#include <MessageType.hpp>
#include <Service/Message.hpp>
M module-services/service-audio/service-audio/AudioServiceAPI.hpp => module-services/service-audio/service-audio/AudioServiceAPI.hpp +1 -1
@@ 7,7 7,7 @@
#include <Audio/AudioCommon.hpp>
#include <Audio/Profiles/Profile.hpp>
-#include <Audio/decoder/decoder.hpp>
+#include <Audio/decoder/Decoder.hpp>
#include <memory>
#include <optional>
M test/CMakeLists.txt => test/CMakeLists.txt +53 -9
@@ 14,15 14,59 @@ add_custom_target(unittests)
set(ROOT_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE)
-function(add_gtest_executable TESTNAME TESTSRCS)
- add_executable(${TESTNAME} EXCLUDE_FROM_ALL ${TESTSRCS})
+function(add_gtest_executable)
+ cmake_parse_arguments(
+ _TEST_ARGS
+ ""
+ "NAME"
+ "SRCS;INCLUDE;LIBS;DEFS;DEPS"
+ ${ARGN}
+ )
+
+ if(NOT _TEST_ARGS_NAME)
+ message(FATAL_ERROR "You must provide a test name")
+ endif(NOT _TEST_ARGS_NAME)
+ set(_TESTNAME "googletest-${_TEST_ARGS_NAME}")
+
+ if(NOT _TEST_ARGS_SRCS)
+ message(FATAL_ERROR "You must provide test sources for ${_TESTNAME}")
+ endif(NOT _TEST_ARGS_SRCS)
+
+ add_executable(${_TESTNAME} EXCLUDE_FROM_ALL ${_TEST_ARGS_SRCS})
+
+ target_compile_options(${_TESTNAME} PUBLIC "-fsanitize=address")
+ target_link_options(${_TESTNAME} PUBLIC "-fsanitize=address")
+
+ # disable logs in unit tests
+ if (NOT ${ENABLE_TEST_LOGS})
+ target_sources(${_TESTNAME} PRIVATE ${ROOT_TEST_DIR}/mock-logs.cpp)
+ target_sources(${_TESTNAME} PRIVATE ${ROOT_TEST_DIR}/mock-freertos-tls.cpp)
+ endif (NOT ${ENABLE_TEST_LOGS})
- target_link_libraries(${TESTNAME} gtest_main gmock)
- target_compile_options(${TESTNAME} PUBLIC "-pthread")
- target_link_options(${TESTNAME} PUBLIC "-pthread")
+ target_link_libraries(${_TESTNAME} PRIVATE gtest_main gmock)
+ foreach(lib ${_TEST_ARGS_LIBS})
+ target_link_libraries(${_TESTNAME} PRIVATE ${lib})
+ endforeach(lib)
- add_dependencies(check ${TESTNAME})
- gtest_add_tests(${TESTNAME} "" AUTO)
+ foreach(include ${_TEST_ARGS_INCLUDE})
+ target_include_directories(${_TESTNAME} PRIVATE ${include})
+ endforeach(include)
+
+ foreach(def ${_TEST_ARGS_DEFS})
+ target_compile_definitions(${_TESTNAME} PRIVATE ${def})
+ endforeach(def)
+
+ foreach(dep ${_TEST_ARGS_DEPS})
+ add_dependencies(${_TESTNAME} ${dep})
+ endforeach(dep)
+
+ target_compile_options(${_TESTNAME} PUBLIC "-pthread")
+ target_link_options(${_TESTNAME} PUBLIC "-pthread")
+
+ add_dependencies(unittests ${_TESTNAME})
+ add_dependencies(check ${_TESTNAME})
+
+ gtest_add_tests(${_TESTNAME} "" AUTO)
endfunction()
function(add_catch2_executable)
@@ 50,8 94,8 @@ function(add_catch2_executable)
# disable logs in unit tests
if (NOT ${ENABLE_TEST_LOGS})
- target_sources(${_TESTNAME} PRIVATE ${ROOT_TEST_DIR}/mock-logs.cpp)
- target_sources(${_TESTNAME} PRIVATE ${ROOT_TEST_DIR}/mock-freertos-tls.cpp)
+ target_sources(${_TESTNAME} PRIVATE ${ROOT_TEST_DIR}/mock-logs.cpp)
+ target_sources(${_TESTNAME} PRIVATE ${ROOT_TEST_DIR}/mock-freertos-tls.cpp)
endif (NOT ${ENABLE_TEST_LOGS})
target_link_libraries(${_TESTNAME} PRIVATE Catch2::Catch2)