better handling of chunk buffer

custom
jacqueline 2 years ago
parent f35bb64c2b
commit 01be69eca1
  1. 1
      src/audio/CMakeLists.txt
  2. 23
      src/audio/audio_decoder.cpp
  3. 358
      src/audio/audio_playback.cpp
  4. 8
      src/audio/audio_task.cpp
  5. 50
      src/audio/chunk.cpp
  6. 26
      src/audio/fatfs_audio_input.cpp
  7. 7
      src/audio/i2s_audio_output.cpp
  8. 13
      src/audio/include/audio_decoder.hpp
  9. 18
      src/audio/include/audio_element.hpp
  10. 85
      src/audio/include/audio_playback.hpp
  11. 3
      src/audio/include/audio_task.hpp
  12. 13
      src/audio/include/chunk.hpp
  13. 11
      src/audio/include/fatfs_audio_input.hpp
  14. 8
      src/audio/include/i2s_audio_output.hpp
  15. 37
      src/audio/include/stream_buffer.hpp
  16. 5
      src/audio/include/stream_info.hpp
  17. 26
      src/audio/stream_buffer.cpp
  18. 2
      src/codecs/codec.cpp
  19. 2
      src/codecs/include/codec.hpp
  20. 2
      src/drivers/include/storage.hpp
  21. 2
      src/drivers/storage.cpp
  22. 14
      src/main/app_console.cpp
  23. 7
      src/main/app_console.hpp
  24. 21
      src/main/main.cpp

@ -1,6 +1,7 @@
idf_component_register( idf_component_register(
SRCS "audio_decoder.cpp" "audio_task.cpp" "chunk.cpp" "fatfs_audio_input.cpp" SRCS "audio_decoder.cpp" "audio_task.cpp" "chunk.cpp" "fatfs_audio_input.cpp"
"stream_info.cpp" "stream_message.cpp" "i2s_audio_output.cpp" "stream_info.cpp" "stream_message.cpp" "i2s_audio_output.cpp"
"stream_buffer.cpp" "audio_playback.cpp"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span") REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span")

@ -19,26 +19,9 @@ static const char* kTag = "DEC";
namespace audio { namespace audio {
AudioDecoder::AudioDecoder() AudioDecoder::AudioDecoder() : IAudioElement(), stream_info_({}) {}
: IAudioElement(),
stream_info_({}),
raw_chunk_buffer_(static_cast<std::byte*>(
heap_caps_malloc(kMaxChunkSize, MALLOC_CAP_SPIRAM))),
chunk_buffer_(raw_chunk_buffer_, kMaxChunkSize)
{} AudioDecoder::~AudioDecoder() {}
AudioDecoder::~AudioDecoder() {
free(raw_chunk_buffer_);
}
auto AudioDecoder::SetInputBuffer(MessageBufferHandle_t* buffer) -> void {
input_buffer_ = buffer;
}
auto AudioDecoder::SetOutputBuffer(MessageBufferHandle_t* buffer) -> void {
output_buffer_ = buffer;
}
auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info) auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info)
-> cpp::result<void, AudioProcessingError> { -> cpp::result<void, AudioProcessingError> {
@ -75,7 +58,7 @@ auto AudioDecoder::ProcessChunk(const cpp::span<std::byte>& chunk)
bool needs_more_input = false; bool needs_more_input = false;
std::optional<codecs::ICodec::ProcessingError> error = std::nullopt; std::optional<codecs::ICodec::ProcessingError> error = std::nullopt;
WriteChunksToStream( WriteChunksToStream(
output_buffer_, chunk_buffer_, output_buffer_,
[&](cpp::span<std::byte> buffer) -> std::size_t { [&](cpp::span<std::byte> buffer) -> std::size_t {
std::size_t bytes_written = 0; std::size_t bytes_written = 0;
// Continue filling up the output buffer so long as we have samples // Continue filling up the output buffer so long as we have samples

@ -4,322 +4,88 @@
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <string_view> #include <string_view>
#include "audio_decoder.hpp"
#include "aac_decoder.h" #include "audio_task.hpp"
#include "amr_decoder.h" #include "chunk.hpp"
#include "audio_element.h" #include "fatfs_audio_input.hpp"
#include "audio_event_iface.h" #include "freertos/portmacro.h"
#include "audio_pipeline.h" #include "gpio_expander.hpp"
#include "esp_err.h" #include "i2s_audio_output.hpp"
#include "flac_decoder.h" #include "storage.hpp"
#include "mp3_decoder.h" #include "stream_buffer.hpp"
#include "ogg_decoder.h" #include "stream_info.hpp"
#include "opus_decoder.h" #include "stream_message.hpp"
#include "wav_decoder.h"
namespace audio {
#include "audio_output.hpp"
// TODO: idk
static const char* kTag = "PLAYBACK"; static const std::size_t kMinElementBufferSize = 1024;
static const char* kSource = "src";
static const char* kDecoder = "dec"; auto AudioPlayback::create(drivers::GpioExpander* expander,
static const char* kSink = "sink"; std::shared_ptr<drivers::SdStorage> storage)
static audio_element_status_t toStatus(void* status) {
uintptr_t as_pointer_int = reinterpret_cast<uintptr_t>(status);
return static_cast<audio_element_status_t>(as_pointer_int);
}
static bool endsWith(std::string_view str, std::string_view suffix) {
return str.size() >= suffix.size() &&
0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix);
}
static void toLower(std::string& str) {
std::transform(str.begin(), str.end(), str.begin(),
[](unsigned char c) { return std::tolower(c); });
}
namespace drivers {
auto AudioPlayback::create(std::unique_ptr<IAudioOutput> output)
-> cpp::result<std::unique_ptr<AudioPlayback>, Error> { -> cpp::result<std::unique_ptr<AudioPlayback>, Error> {
audio_pipeline_handle_t pipeline; // Create everything
audio_element_handle_t fatfs_stream_reader; auto source = std::make_shared<FatfsAudioInput>(storage);
audio_event_iface_handle_t event_interface; auto codec = std::make_shared<AudioDecoder>();
audio_pipeline_cfg_t pipeline_config =
audio_pipeline_cfg_t(DEFAULT_AUDIO_PIPELINE_CONFIG());
pipeline = audio_pipeline_init(&pipeline_config);
if (pipeline == NULL) {
return cpp::fail(Error::PIPELINE_INIT);
}
fatfs_stream_cfg_t fatfs_stream_config = auto sink_res = I2SAudioOutput::create(expander);
fatfs_stream_cfg_t(FATFS_STREAM_CFG_DEFAULT()); if (sink_res.has_error()) {
fatfs_stream_config.type = AUDIO_STREAM_READER; return cpp::fail(ERR_INIT_ELEMENT);
fatfs_stream_reader = fatfs_stream_init(&fatfs_stream_config);
if (fatfs_stream_reader == NULL) {
return cpp::fail(Error::FATFS_INIT);
} }
auto sink = sink_res.value();
audio_event_iface_cfg_t event_config = AUDIO_EVENT_IFACE_DEFAULT_CFG(); auto playback = std::make_unique<AudioPlayback>();
event_interface = audio_event_iface_init(&event_config);
audio_pipeline_set_listener(pipeline, event_interface); // Configure the pipeline
audio_element_msg_set_listener(fatfs_stream_reader, event_interface); source->InputBuffer(&playback->stream_start_);
audio_element_msg_set_listener(output->GetAudioElement(), event_interface); sink->OutputBuffer(&playback->stream_end_);
playback->ConnectElements(source.get(), codec.get());
playback->ConnectElements(codec.get(), sink.get());
audio_pipeline_register(pipeline, fatfs_stream_reader, kSource); // Launch!
audio_pipeline_register(pipeline, output->GetAudioElement(), kSink); StartAudioTask("src", source);
StartAudioTask("dec", codec);
StartAudioTask("sink", sink);
return std::make_unique<AudioPlayback>(output, pipeline, fatfs_stream_reader, return playback;
event_interface);
} }
AudioPlayback::AudioPlayback(std::unique_ptr<IAudioOutput>& output, // TODO(jacqueline): think about sizes
audio_pipeline_handle_t pipeline, AudioPlayback::AudioPlayback()
audio_element_handle_t source_element, : stream_start_(128, 128), stream_end_(128, 128) {}
audio_event_iface_handle_t event_interface)
: output_(std::move(output)),
pipeline_(pipeline),
source_element_(source_element),
event_interface_(event_interface) {}
AudioPlayback::~AudioPlayback() { AudioPlayback::~AudioPlayback() {
audio_pipeline_remove_listener(pipeline_); // TODO(jacqueline): signal the end of all things, and maybe wait for it?
audio_element_msg_remove_listener(source_element_, event_interface_);
audio_element_msg_remove_listener(output_->GetAudioElement(),
event_interface_);
audio_pipeline_stop(pipeline_);
audio_pipeline_wait_for_stop(pipeline_);
audio_pipeline_terminate(pipeline_);
ReconfigurePipeline(NONE);
audio_pipeline_unregister(pipeline_, source_element_);
audio_pipeline_unregister(pipeline_, output_->GetAudioElement());
audio_event_iface_destroy(event_interface_);
audio_pipeline_deinit(pipeline_);
audio_element_deinit(source_element_);
}
void AudioPlayback::Play(const std::string& filename) {
output_->SetSoftMute(true);
if (playback_state_ != STOPPED) {
audio_pipeline_stop(pipeline_);
audio_pipeline_wait_for_stop(pipeline_);
audio_pipeline_terminate(pipeline_);
}
playback_state_ = PLAYING;
Decoder decoder = GetDecoderForFilename(filename);
ReconfigurePipeline(decoder);
audio_element_set_uri(source_element_, filename.c_str());
audio_pipeline_reset_ringbuffer(pipeline_);
audio_pipeline_reset_elements(pipeline_);
audio_pipeline_run(pipeline_);
output_->SetSoftMute(false);
} }
void AudioPlayback::Toggle() { auto AudioPlayback::Play(const std::string& filename) -> void {
if (playback_state_ == PLAYING) { StreamInfo info;
Pause(); info.Path(filename);
} else if (playback_state_ == PAUSED) {
Resume();
}
}
void AudioPlayback::Resume() {
if (playback_state_ == PAUSED) {
ESP_LOGI(kTag, "resuming");
playback_state_ = PLAYING;
audio_pipeline_resume(pipeline_);
output_->SetSoftMute(false);
}
}
void AudioPlayback::Pause() {
if (GetPlaybackState() == PLAYING) {
ESP_LOGI(kTag, "pausing");
output_->SetSoftMute(true);
playback_state_ = PAUSED;
audio_pipeline_pause(pipeline_);
}
}
auto AudioPlayback::GetPlaybackState() const -> PlaybackState {
return playback_state_;
}
void AudioPlayback::ProcessEvents(uint16_t max_time_ms) {
if (playback_state_ == STOPPED) {
return;
}
while (1) {
audio_event_iface_msg_t event;
esp_err_t err = audio_event_iface_listen(event_interface_, &event,
pdMS_TO_TICKS(max_time_ms));
if (err != ESP_OK) {
// Error should only be timeouts, so use a 'failure' as an indication that
// we're out of events to process.
break;
}
if (event.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
event.source == (void*)decoder_ &&
event.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) {
audio_element_info_t music_info;
audio_element_getinfo(decoder_, &music_info);
ESP_LOGI(kTag, "sample_rate=%d, bits=%d, ch=%d", music_info.sample_rates,
music_info.bits, music_info.channels);
}
if (event.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && std::array<std::byte, 128> dest;
event.source == (void*)source_element_ && auto len = WriteMessage(
event.cmd == AEL_MSG_CMD_REPORT_STATUS) { TYPE_STREAM_INFO, [&](auto enc) { return info.Encode(enc); }, dest);
audio_element_status_t status = toStatus(event.data);
if (status == AEL_STATUS_STATE_FINISHED) {
// TODO: Could we change the uri here? hmm.
ESP_LOGI(kTag, "finished reading input.");
}
}
if (event.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && if (len.has_error()) {
event.source == (void*)output_->GetAudioElement() && // TODO.
event.cmd == AEL_MSG_CMD_REPORT_STATUS) {
audio_element_status_t status = toStatus(event.data);
if (status == AEL_STATUS_STATE_FINISHED) {
if (next_filename_ != "") {
ESP_LOGI(kTag, "finished writing output. enqueing next.");
Decoder decoder = GetDecoderForFilename(next_filename_);
if (decoder == decoder_type_) {
output_->SetSoftMute(true);
audio_element_set_uri(source_element_, next_filename_.c_str());
audio_pipeline_reset_ringbuffer(pipeline_);
audio_pipeline_reset_elements(pipeline_);
audio_pipeline_change_state(pipeline_, AEL_STATE_INIT);
audio_pipeline_run(pipeline_);
output_->SetSoftMute(true);
} else {
Play(next_filename_);
}
next_filename_ = "";
} else {
ESP_LOGI(kTag, "finished writing output. stopping.");
audio_pipeline_wait_for_stop(pipeline_);
audio_pipeline_terminate(pipeline_);
playback_state_ = STOPPED;
}
return; return;
} }
}
if (event.need_free_data) {
// AFAICT this never happens in practice, but it doesn't hurt to follow
// the api here anyway.
free(event.data);
}
}
}
void AudioPlayback::SetNextFile(const std::string& filename) {
next_filename_ = filename;
}
void AudioPlayback::SetVolume(uint8_t volume) {
output_->SetVolume(volume);
}
auto AudioPlayback::GetVolume() const -> uint8_t {
return output_->GetVolume();
}
auto AudioPlayback::GetDecoderForFilename(std::string filename) const
-> Decoder {
toLower(filename);
if (endsWith(filename, "mp3")) {
return MP3;
}
if (endsWith(filename, "amr") || endsWith(filename, "wamr")) {
return AMR;
}
if (endsWith(filename, "opus")) {
return OPUS;
}
if (endsWith(filename, "ogg")) {
return OGG;
}
if (endsWith(filename, "flac")) {
return FLAC;
}
if (endsWith(filename, "wav")) {
return WAV;
}
if (endsWith(filename, "aac") || endsWith(filename, "m4a") ||
endsWith(filename, "ts") || endsWith(filename, "mp4")) {
return AAC;
}
return NONE;
}
auto AudioPlayback::CreateDecoder(Decoder decoder) const // TODO: short delay, return error on fail
-> audio_element_handle_t { xMessageBufferSend(*stream_start_.Handle(), dest.data(), len.value(),
if (decoder == MP3) { portMAX_DELAY);
mp3_decoder_cfg_t config = DEFAULT_MP3_DECODER_CONFIG();
return mp3_decoder_init(&config);
}
if (decoder == AMR) {
amr_decoder_cfg_t config = DEFAULT_AMR_DECODER_CONFIG();
return amr_decoder_init(&config);
}
if (decoder == OPUS) {
opus_decoder_cfg_t config = DEFAULT_OPUS_DECODER_CONFIG();
return decoder_opus_init(&config);
}
if (decoder == OGG) {
ogg_decoder_cfg_t config = DEFAULT_OGG_DECODER_CONFIG();
return ogg_decoder_init(&config);
}
if (decoder == FLAC) {
flac_decoder_cfg_t config = DEFAULT_FLAC_DECODER_CONFIG();
return flac_decoder_init(&config);
}
if (decoder == WAV) {
wav_decoder_cfg_t config = DEFAULT_WAV_DECODER_CONFIG();
return wav_decoder_init(&config);
}
if (decoder == AAC) {
aac_decoder_cfg_t config = DEFAULT_AAC_DECODER_CONFIG();
return aac_decoder_init(&config);
}
return nullptr;
} }
void AudioPlayback::ReconfigurePipeline(Decoder decoder) { auto AudioPlayback::ConnectElements(IAudioElement* src, IAudioElement* sink)
if (decoder_type_ == decoder) { -> void {
return; std::size_t chunk_size =
} std::max(src->InputMinChunkSize(), sink->InputMinChunkSize());
std::size_t buffer_size = std::max(kMinElementBufferSize, chunk_size * 2);
if (decoder_type_ != NONE) {
audio_pipeline_unlink(pipeline_);
audio_element_msg_remove_listener(decoder_, event_interface_);
audio_pipeline_unregister(pipeline_, decoder_);
audio_element_deinit(decoder_);
}
if (decoder != NONE) { auto buffer = std::make_unique<StreamBuffer>(chunk_size, buffer_size);
decoder_ = CreateDecoder(decoder); src->OutputBuffer(buffer.get());
decoder_type_ = decoder; sink->OutputBuffer(buffer.get());
audio_pipeline_register(pipeline_, decoder_, kDecoder); element_buffers_.push_back(std::move(buffer));
audio_element_msg_set_listener(decoder_, event_interface_);
static const char* link_tag[3] = {kSource, kDecoder, kSink};
audio_pipeline_link(pipeline_, &link_tag[0], 3);
}
} }
} // namespace drivers } // namespace audio

@ -19,12 +19,8 @@
namespace audio { namespace audio {
static const TickType_t kCommandWaitTicks = 1;
static const TickType_t kIdleTaskDelay = 1;
static const size_t kChunkBufferSize = kMaxChunkSize * 1.5;
auto StartAudioTask(const std::string& name, auto StartAudioTask(const std::string& name,
std::shared_ptr<IAudioElement>& element) -> void { std::shared_ptr<IAudioElement> element) -> void {
AudioTaskArgs* args = new AudioTaskArgs{.element = element}; AudioTaskArgs* args = new AudioTaskArgs{.element = element};
xTaskCreate(&AudioTaskMain, name.c_str(), element->StackSizeBytes(), args, xTaskCreate(&AudioTaskMain, name.c_str(), element->StackSizeBytes(), args,
kTaskPriorityAudio, NULL); kTaskPriorityAudio, NULL);
@ -45,7 +41,6 @@ void AudioTaskMain(void* args) {
// processing any chunks from it. Try doing this first, then fall back to // processing any chunks from it. Try doing this first, then fall back to
// the other cases. // the other cases.
bool has_received_message = false; bool has_received_message = false;
if (element->InputBuffer() != nullptr) {
ChunkReadResult chunk_res = chunk_reader.ReadChunkFromStream( ChunkReadResult chunk_res = chunk_reader.ReadChunkFromStream(
[&](cpp::span<std::byte> data) -> std::optional<size_t> { [&](cpp::span<std::byte> data) -> std::optional<size_t> {
process_res = element->ProcessChunk(data); process_res = element->ProcessChunk(data);
@ -63,7 +58,6 @@ void AudioTaskMain(void* args) {
} else if (chunk_res == CHUNK_STREAM_ENDED) { } else if (chunk_res == CHUNK_STREAM_ENDED) {
has_received_message = true; has_received_message = true;
} }
}
if (has_received_message) { if (has_received_message) {
auto message = chunk_reader.GetLastMessage(); auto message = chunk_reader.GetLastMessage();

@ -8,31 +8,19 @@
#include "cbor.h" #include "cbor.h"
#include "stream_buffer.hpp"
#include "stream_message.hpp" #include "stream_message.hpp"
namespace audio { namespace audio {
/* auto WriteChunksToStream(StreamBuffer* stream,
* The maximum size of a single chunk of stream data. This should be comfortably
* larger than the largest size of a frame of audio we should expect to handle.
*
* 128 kbps MPEG-1 @ 44.1 kHz is approx. 418 bytes according to the internet.
*
* TODO(jacqueline): tune as more codecs are added.
*/
const std::size_t kMaxChunkSize = 2048;
// TODO: tune
static const std::size_t kWorkingBufferSize = kMaxChunkSize * 1.5;
auto WriteChunksToStream(MessageBufferHandle_t* stream,
cpp::span<std::byte> working_buffer,
std::function<size_t(cpp::span<std::byte>)> callback, std::function<size_t(cpp::span<std::byte>)> callback,
TickType_t max_wait) -> ChunkWriteResult { TickType_t max_wait) -> ChunkWriteResult {
cpp::span<std::byte> write_buffer = stream->WriteBuffer();
while (1) { while (1) {
// First, write out our chunk header so we know how much space to give to // First, write out our chunk header so we know how much space to give to
// the callback. // the callback.
auto header_size = WriteTypeOnlyMessage(TYPE_CHUNK_HEADER, working_buffer); auto header_size = WriteTypeOnlyMessage(TYPE_CHUNK_HEADER, write_buffer);
if (header_size.has_error()) { if (header_size.has_error()) {
return CHUNK_ENCODING_ERROR; return CHUNK_ENCODING_ERROR;
} }
@ -40,8 +28,8 @@ auto WriteChunksToStream(MessageBufferHandle_t* stream,
// Now we can ask the callback to fill the remaining space. // Now we can ask the callback to fill the remaining space.
size_t chunk_size = std::invoke( size_t chunk_size = std::invoke(
callback, callback,
working_buffer.subspan(header_size.value(), write_buffer.subspan(header_size.value(),
working_buffer.size() - header_size.value())); write_buffer.size() - header_size.value()));
if (chunk_size == 0) { if (chunk_size == 0) {
// They had nothing for us, so bail out. // They had nothing for us, so bail out.
@ -51,7 +39,7 @@ auto WriteChunksToStream(MessageBufferHandle_t* stream,
// Try to write to the buffer. Note the return type here will be either 0 or // Try to write to the buffer. Note the return type here will be either 0 or
// header_size + chunk_size, as MessageBuffer doesn't allow partial writes. // header_size + chunk_size, as MessageBuffer doesn't allow partial writes.
size_t actual_write_size = size_t actual_write_size =
xMessageBufferSend(*stream, working_buffer.data(), xMessageBufferSend(stream->Handle(), write_buffer.data(),
header_size.value() + chunk_size, max_wait); header_size.value() + chunk_size, max_wait);
if (actual_write_size == 0) { if (actual_write_size == 0) {
@ -63,15 +51,9 @@ auto WriteChunksToStream(MessageBufferHandle_t* stream,
} }
} }
ChunkReader::ChunkReader(MessageBufferHandle_t* stream) ChunkReader::ChunkReader(StreamBuffer* stream) : stream_(stream) {}
: stream_(stream),
raw_working_buffer_(static_cast<std::byte*>(
heap_caps_malloc(kWorkingBufferSize, MALLOC_CAP_SPIRAM))),
working_buffer_(raw_working_buffer_, kWorkingBufferSize) {}
ChunkReader::~ChunkReader() { ChunkReader::~ChunkReader() {}
free(raw_working_buffer_);
}
auto ChunkReader::Reset() -> void { auto ChunkReader::Reset() -> void {
leftover_bytes_ = 0; leftover_bytes_ = 0;
@ -79,16 +61,17 @@ auto ChunkReader::Reset() -> void {
} }
auto ChunkReader::GetLastMessage() -> cpp::span<std::byte> { auto ChunkReader::GetLastMessage() -> cpp::span<std::byte> {
return working_buffer_.subspan(leftover_bytes_, last_message_size_); return stream_->ReadBuffer().subspan(leftover_bytes_, last_message_size_);
} }
auto ChunkReader::ReadChunkFromStream( auto ChunkReader::ReadChunkFromStream(
std::function<std::optional<size_t>(cpp::span<std::byte>)> callback, std::function<std::optional<size_t>(cpp::span<std::byte>)> callback,
TickType_t max_wait) -> ChunkReadResult { TickType_t max_wait) -> ChunkReadResult {
// First, wait for a message to arrive over the buffer. // First, wait for a message to arrive over the buffer.
last_message_size_ = cpp::span<std::byte> new_data_dest = stream_->ReadBuffer().last(
xMessageBufferReceive(*stream_, raw_working_buffer_ + leftover_bytes_, stream_->ReadBuffer().size() - leftover_bytes_);
working_buffer_.size() - leftover_bytes_, max_wait); last_message_size_ = xMessageBufferReceive(
stream_->Handle(), new_data_dest.data(), new_data_dest.size(), max_wait);
if (last_message_size_ == 0) { if (last_message_size_ == 0) {
return CHUNK_READ_TIMEOUT; return CHUNK_READ_TIMEOUT;
@ -109,7 +92,8 @@ auto ChunkReader::ReadChunkFromStream(
// Now we need to stick the end of the last chunk (if it exists) onto the // Now we need to stick the end of the last chunk (if it exists) onto the
// front of the new chunk. Do it this way around bc we assume the old chunk // front of the new chunk. Do it this way around bc we assume the old chunk
// is shorter, and therefore faster to move. // is shorter, and therefore faster to move.
cpp::span<std::byte> leftover_data = working_buffer_.first(leftover_bytes_); cpp::span<std::byte> leftover_data =
stream_->ReadBuffer().first(leftover_bytes_);
cpp::span<std::byte> combined_data(chunk_data.data() - leftover_data.size(), cpp::span<std::byte> combined_data(chunk_data.data() - leftover_data.size(),
leftover_data.size() + chunk_data.size()); leftover_data.size() + chunk_data.size());
if (leftover_bytes_ > 0) { if (leftover_bytes_ > 0) {
@ -127,7 +111,7 @@ auto ChunkReader::ReadChunkFromStream(
leftover_bytes_ = combined_data.size() - amount_processed.value(); leftover_bytes_ = combined_data.size() - amount_processed.value();
if (leftover_bytes_ > 0) { if (leftover_bytes_ > 0) {
std::copy(combined_data.begin() + amount_processed.value(), std::copy(combined_data.begin() + amount_processed.value(),
combined_data.end(), working_buffer_.begin()); combined_data.end(), stream_->ReadBuffer().begin());
return CHUNK_LEFTOVER_DATA; return CHUNK_LEFTOVER_DATA;
} }

@ -19,7 +19,6 @@ static const TickType_t kServiceInterval = pdMS_TO_TICKS(50);
static const std::size_t kFileBufferSize = 1024 * 128; static const std::size_t kFileBufferSize = 1024 * 128;
static const std::size_t kMinFileReadSize = 1024 * 4; static const std::size_t kMinFileReadSize = 1024 * 4;
static const std::size_t kOutputBufferSize = 1024 * 4;
FatfsAudioInput::FatfsAudioInput(std::shared_ptr<drivers::SdStorage> storage) FatfsAudioInput::FatfsAudioInput(std::shared_ptr<drivers::SdStorage> storage)
: IAudioElement(), : IAudioElement(),
@ -29,24 +28,11 @@ FatfsAudioInput::FatfsAudioInput(std::shared_ptr<drivers::SdStorage> storage)
file_buffer_(raw_file_buffer_, kFileBufferSize), file_buffer_(raw_file_buffer_, kFileBufferSize),
file_buffer_read_pos_(file_buffer_.begin()), file_buffer_read_pos_(file_buffer_.begin()),
file_buffer_write_pos_(file_buffer_.begin()), file_buffer_write_pos_(file_buffer_.begin()),
raw_chunk_buffer_(static_cast<std::byte*>(
heap_caps_malloc(kMaxChunkSize, MALLOC_CAP_SPIRAM))),
chunk_buffer_(raw_chunk_buffer_, kMaxChunkSize),
current_file_(), current_file_(),
is_file_open_(false), is_file_open_(false) {}
output_buffer_memory_(static_cast<uint8_t*>(
heap_caps_malloc(kOutputBufferSize, MALLOC_CAP_SPIRAM))) {
output_buffer_ = new MessageBufferHandle_t;
*output_buffer_ = xMessageBufferCreateStatic(
kOutputBufferSize, output_buffer_memory_, &output_buffer_metadata_);
}
FatfsAudioInput::~FatfsAudioInput() { FatfsAudioInput::~FatfsAudioInput() {
free(raw_file_buffer_); free(raw_file_buffer_);
free(raw_chunk_buffer_);
vMessageBufferDelete(output_buffer_);
free(output_buffer_memory_);
free(output_buffer_);
} }
auto FatfsAudioInput::ProcessStreamInfo(const StreamInfo& info) auto FatfsAudioInput::ProcessStreamInfo(const StreamInfo& info)
@ -70,13 +56,13 @@ auto FatfsAudioInput::ProcessStreamInfo(const StreamInfo& info)
auto write_size = auto write_size =
WriteMessage(TYPE_STREAM_INFO, WriteMessage(TYPE_STREAM_INFO,
std::bind(&StreamInfo::Encode, info, std::placeholders::_1), std::bind(&StreamInfo::Encode, info, std::placeholders::_1),
chunk_buffer_); output_buffer_->WriteBuffer());
if (write_size.has_error()) { if (write_size.has_error()) {
return cpp::fail(IO_ERROR); return cpp::fail(IO_ERROR);
} else { } else {
xMessageBufferSend(output_buffer_, chunk_buffer_.data(), write_size.value(), xMessageBufferSend(output_buffer_, output_buffer_->WriteBuffer().data(),
portMAX_DELAY); write_size.value(), portMAX_DELAY);
} }
return {}; return {};
@ -142,8 +128,8 @@ auto FatfsAudioInput::ProcessIdle() -> cpp::result<void, AudioProcessingError> {
// Now stream data into the output buffer until it's full. // Now stream data into the output buffer until it's full.
pending_read_pos_ = file_buffer_read_pos_; pending_read_pos_ = file_buffer_read_pos_;
ChunkWriteResult result = WriteChunksToStream( ChunkWriteResult result = WriteChunksToStream(
output_buffer_, chunk_buffer_, output_buffer_, [&](cpp::span<std::byte> d) { return SendChunk(d); },
[&](cpp::span<std::byte> d) { return SendChunk(d); }, kServiceInterval); kServiceInterval);
switch (result) { switch (result) {
case CHUNK_WRITE_TIMEOUT: case CHUNK_WRITE_TIMEOUT:

@ -16,7 +16,7 @@ static const char* kTag = "I2SOUT";
namespace audio { namespace audio {
auto I2SAudioOutput::create(drivers::GpioExpander* expander) auto I2SAudioOutput::create(drivers::GpioExpander* expander)
-> cpp::result<std::unique_ptr<I2SAudioOutput>, Error> { -> cpp::result<std::shared_ptr<I2SAudioOutput>, Error> {
// First, we need to perform initial configuration of the DAC chip. // First, we need to perform initial configuration of the DAC chip.
auto dac_result = drivers::AudioDac::create(expander); auto dac_result = drivers::AudioDac::create(expander);
if (dac_result.has_error()) { if (dac_result.has_error()) {
@ -27,9 +27,10 @@ auto I2SAudioOutput::create(drivers::GpioExpander* expander)
// Soft mute immediately, in order to minimise any clicks and pops caused by // Soft mute immediately, in order to minimise any clicks and pops caused by
// the initial output element and pipeline configuration. // the initial output element and pipeline configuration.
dac->WriteVolume(255); // dac->WriteVolume(255);
dac->WriteVolume(120); // for testing
return std::make_unique<I2SAudioOutput>(expander, std::move(dac)); return std::make_shared<I2SAudioOutput>(expander, std::move(dac));
} }
I2SAudioOutput::I2SAudioOutput(drivers::GpioExpander* expander, I2SAudioOutput::I2SAudioOutput(drivers::GpioExpander* expander,

@ -21,11 +21,15 @@ class AudioDecoder : public IAudioElement {
AudioDecoder(); AudioDecoder();
~AudioDecoder(); ~AudioDecoder();
auto SetInputBuffer(MessageBufferHandle_t*) -> void;
auto SetOutputBuffer(MessageBufferHandle_t*) -> void;
auto StackSizeBytes() const -> std::size_t override { return 8196; }; auto StackSizeBytes() const -> std::size_t override { return 8196; };
auto InputMinChunkSize() const -> std::size_t override {
// 128 kbps MPEG-1 @ 44.1 kHz is approx. 418 bytes according to the
// internet.
// TODO(jacqueline): tune as more codecs are added.
return 1024;
}
auto ProcessStreamInfo(const StreamInfo& info) auto ProcessStreamInfo(const StreamInfo& info)
-> cpp::result<void, AudioProcessingError> override; -> cpp::result<void, AudioProcessingError> override;
auto ProcessChunk(const cpp::span<std::byte>& chunk) auto ProcessChunk(const cpp::span<std::byte>& chunk)
@ -38,9 +42,6 @@ class AudioDecoder : public IAudioElement {
private: private:
std::unique_ptr<codecs::ICodec> current_codec_; std::unique_ptr<codecs::ICodec> current_codec_;
std::optional<StreamInfo> stream_info_; std::optional<StreamInfo> stream_info_;
std::byte* raw_chunk_buffer_;
cpp::span<std::byte> chunk_buffer_;
}; };
} // namespace audio } // namespace audio

@ -2,6 +2,7 @@
#include <cstdint> #include <cstdint>
#include "chunk.hpp"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/message_buffer.h" #include "freertos/message_buffer.h"
@ -9,6 +10,7 @@
#include "result.hpp" #include "result.hpp"
#include "span.hpp" #include "span.hpp"
#include "stream_buffer.hpp"
#include "stream_info.hpp" #include "stream_info.hpp"
#include "types.hpp" #include "types.hpp"
@ -41,7 +43,7 @@ enum AudioProcessingError {
class IAudioElement { class IAudioElement {
public: public:
IAudioElement() : input_buffer_(nullptr), output_buffer_(nullptr) {} IAudioElement() : input_buffer_(nullptr), output_buffer_(nullptr) {}
virtual ~IAudioElement(); virtual ~IAudioElement() {}
/* /*
* Returns the stack size in bytes that this element requires. This should * Returns the stack size in bytes that this element requires. This should
@ -57,11 +59,17 @@ class IAudioElement {
*/ */
virtual auto IdleTimeout() const -> TickType_t { return portMAX_DELAY; } virtual auto IdleTimeout() const -> TickType_t { return portMAX_DELAY; }
virtual auto InputMinChunkSize() const -> std::size_t { return 0; }
/* Returns this element's input buffer. */ /* Returns this element's input buffer. */
auto InputBuffer() const -> MessageBufferHandle_t* { return input_buffer_; } auto InputBuffer() const -> StreamBuffer* { return input_buffer_; }
/* Returns this element's output buffer. */ /* Returns this element's output buffer. */
auto OutputBuffer() const -> MessageBufferHandle_t* { return output_buffer_; } auto OutputBuffer() const -> StreamBuffer* { return output_buffer_; }
auto InputBuffer(StreamBuffer* b) -> void { input_buffer_ = b; }
auto OutputBuffer(StreamBuffer* b) -> void { output_buffer_ = b; }
/* /*
* Called when a StreamInfo message is received. Used to configure this * Called when a StreamInfo message is received. Used to configure this
@ -87,8 +95,8 @@ class IAudioElement {
virtual auto ProcessIdle() -> cpp::result<void, AudioProcessingError> = 0; virtual auto ProcessIdle() -> cpp::result<void, AudioProcessingError> = 0;
protected: protected:
MessageBufferHandle_t* input_buffer_; StreamBuffer* input_buffer_;
MessageBufferHandle_t* output_buffer_; StreamBuffer* output_buffer_;
}; };
} // namespace audio } // namespace audio

@ -3,95 +3,44 @@
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector>
#include "audio_common.h" #include "audio_element.hpp"
#include "audio_element.h"
#include "audio_event_iface.h"
#include "audio_pipeline.h"
#include "esp_err.h" #include "esp_err.h"
#include "fatfs_stream.h" #include "gpio_expander.hpp"
#include "i2s_stream.h"
#include "mp3_decoder.h"
#include "result.hpp" #include "result.hpp"
#include "span.hpp"
#include "audio_output.hpp"
#include "dac.hpp"
#include "storage.hpp" #include "storage.hpp"
#include "stream_buffer.hpp"
namespace drivers { namespace audio {
/* /*
* Sends an I2S audio stream to the DAC. Includes basic controls for pausing * TODO.
* and resuming the stream, as well as support for gapless playback of the next
* queued song, but does not implement any kind of sophisticated queing or
* playback control; these should be handled at a higher level.
*/ */
class AudioPlayback { class AudioPlayback {
public: public:
enum Error { FATFS_INIT, I2S_INIT, PIPELINE_INIT }; enum Error { ERR_INIT_ELEMENT, ERR_MEM };
static auto create(std::unique_ptr<IAudioOutput> output) static auto create(drivers::GpioExpander* expander,
std::shared_ptr<drivers::SdStorage> storage)
-> cpp::result<std::unique_ptr<AudioPlayback>, Error>; -> cpp::result<std::unique_ptr<AudioPlayback>, Error>;
AudioPlayback(std::unique_ptr<IAudioOutput>& output, // TODO(jacqueline): configure on the fly once we have things to configure.
audio_pipeline_handle_t pipeline, AudioPlayback();
audio_element_handle_t source_element,
audio_event_iface_handle_t event_interface);
~AudioPlayback(); ~AudioPlayback();
/*
* Replaces any currently playing file with the one given, and begins
* playback.
*
* Any value set in `set_next_file` is cleared by this method.
*/
auto Play(const std::string& filename) -> void; auto Play(const std::string& filename) -> void;
/* Toogle between resumed and paused. */
auto Toggle() -> void;
auto Resume() -> void;
auto Pause() -> void;
enum PlaybackState { PLAYING, PAUSED, STOPPED };
auto GetPlaybackState() const -> PlaybackState;
/*
* Handles any pending events from the underlying audio pipeline. This must
* be called regularly in order to handle configuring the I2S stream for
* different audio types (e.g. sample rate, bit depth), and for gapless
* playback.
*/
auto ProcessEvents(uint16_t max_time_ms) -> void;
/*
* Sets the file that should be played immediately after the current file
* finishes. This is used for gapless playback
*/
auto SetNextFile(const std::string& filename) -> void;
auto SetVolume(uint8_t volume) -> void;
auto GetVolume() const -> uint8_t;
// Not copyable or movable. // Not copyable or movable.
AudioPlayback(const AudioPlayback&) = delete; AudioPlayback(const AudioPlayback&) = delete;
AudioPlayback& operator=(const AudioPlayback&) = delete; AudioPlayback& operator=(const AudioPlayback&) = delete;
private: private:
PlaybackState playback_state_; auto ConnectElements(IAudioElement* src, IAudioElement* sink) -> void;
enum Decoder { NONE, MP3, AMR, OPUS, OGG, FLAC, WAV, AAC };
auto GetDecoderForFilename(std::string filename) const -> Decoder;
auto CreateDecoder(Decoder decoder) const -> audio_element_handle_t;
auto ReconfigurePipeline(Decoder decoder) -> void;
std::unique_ptr<IAudioOutput> output_;
std::string next_filename_ = "";
audio_pipeline_handle_t pipeline_;
audio_element_handle_t source_element_;
audio_event_iface_handle_t event_interface_;
audio_element_handle_t decoder_ = nullptr; StreamBuffer stream_start_;
Decoder decoder_type_ = NONE; StreamBuffer stream_end_;
std::vector<std::unique_ptr<StreamBuffer>> element_buffers_;
}; };
} // namespace drivers } // namespace audio

@ -10,7 +10,8 @@ struct AudioTaskArgs {
std::shared_ptr<IAudioElement>& element; std::shared_ptr<IAudioElement>& element;
}; };
auto StartAudioTask(std::shared_ptr<IAudioElement>& element) -> void; auto StartAudioTask(const std::string& name,
std::shared_ptr<IAudioElement> element) -> void;
void AudioTaskMain(void* args); void AudioTaskMain(void* args);

@ -13,11 +13,10 @@
#include "freertos/queue.h" #include "freertos/queue.h"
#include "result.hpp" #include "result.hpp"
#include "span.hpp" #include "span.hpp"
#include "stream_buffer.hpp"
namespace audio { namespace audio {
extern const std::size_t kMaxChunkSize;
enum ChunkWriteResult { enum ChunkWriteResult {
// Returned when the callback does not write any data. // Returned when the callback does not write any data.
CHUNK_OUT_OF_DATA, CHUNK_OUT_OF_DATA,
@ -37,8 +36,7 @@ enum ChunkWriteResult {
* number of bytes it wrote. Return a value of 0 to indicate that there is no * number of bytes it wrote. Return a value of 0 to indicate that there is no
* more input to read. * more input to read.
*/ */
auto WriteChunksToStream(MessageBufferHandle_t* stream, auto WriteChunksToStream(StreamBuffer* stream,
cpp::span<std::byte> working_buffer,
std::function<size_t(cpp::span<std::byte>)> callback, std::function<size_t(cpp::span<std::byte>)> callback,
TickType_t max_wait) -> ChunkWriteResult; TickType_t max_wait) -> ChunkWriteResult;
@ -59,7 +57,7 @@ enum ChunkReadResult {
class ChunkReader { class ChunkReader {
public: public:
ChunkReader(MessageBufferHandle_t* stream); explicit ChunkReader(StreamBuffer* buffer);
~ChunkReader(); ~ChunkReader();
auto Reset() -> void; auto Reset() -> void;
@ -83,10 +81,7 @@ class ChunkReader {
TickType_t max_wait) -> ChunkReadResult; TickType_t max_wait) -> ChunkReadResult;
private: private:
MessageBufferHandle_t* stream_; StreamBuffer* stream_;
std::byte* raw_working_buffer_;
cpp::span<std::byte> working_buffer_;
std::size_t leftover_bytes_ = 0; std::size_t leftover_bytes_ = 0;
std::size_t last_message_size_ = 0; std::size_t last_message_size_ = 0;
}; };

@ -4,6 +4,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include "chunk.hpp"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/message_buffer.h" #include "freertos/message_buffer.h"
@ -12,6 +13,7 @@
#include "audio_element.hpp" #include "audio_element.hpp"
#include "storage.hpp" #include "storage.hpp"
#include "stream_buffer.hpp"
namespace audio { namespace audio {
@ -28,6 +30,9 @@ class FatfsAudioInput : public IAudioElement {
auto SendChunk(cpp::span<std::byte> dest) -> size_t; auto SendChunk(cpp::span<std::byte> dest) -> size_t;
FatfsAudioInput(const FatfsAudioInput&) = delete;
FatfsAudioInput& operator=(const FatfsAudioInput&) = delete;
private: private:
auto GetRingBufferDistance() const -> size_t; auto GetRingBufferDistance() const -> size_t;
@ -39,14 +44,8 @@ class FatfsAudioInput : public IAudioElement {
cpp::span<std::byte>::iterator pending_read_pos_; cpp::span<std::byte>::iterator pending_read_pos_;
cpp::span<std::byte>::iterator file_buffer_write_pos_; cpp::span<std::byte>::iterator file_buffer_write_pos_;
std::byte* raw_chunk_buffer_;
cpp::span<std::byte> chunk_buffer_;
FIL current_file_; FIL current_file_;
bool is_file_open_; bool is_file_open_;
uint8_t* output_buffer_memory_;
StaticMessageBuffer_t output_buffer_metadata_;
}; };
} // namespace audio } // namespace audio

@ -16,13 +16,17 @@ class I2SAudioOutput : public IAudioElement {
public: public:
enum Error { DAC_CONFIG, I2S_CONFIG, STREAM_INIT }; enum Error { DAC_CONFIG, I2S_CONFIG, STREAM_INIT };
static auto create(drivers::GpioExpander* expander) static auto create(drivers::GpioExpander* expander)
-> cpp::result<std::unique_ptr<I2SAudioOutput>, Error>; -> cpp::result<std::shared_ptr<I2SAudioOutput>, Error>;
I2SAudioOutput(drivers::GpioExpander* expander, I2SAudioOutput(drivers::GpioExpander* expander,
std::unique_ptr<drivers::AudioDac> dac); std::unique_ptr<drivers::AudioDac> dac);
~I2SAudioOutput(); ~I2SAudioOutput();
auto SetInputBuffer(MessageBufferHandle_t* in) -> void { input_buffer_ = in; } auto InputMinChunkSize() const -> std::size_t override {
// TODO(jacqueline): work out a good value here. Maybe similar to the total
// DMA buffer size?
return 128;
}
auto IdleTimeout() const -> TickType_t override; auto IdleTimeout() const -> TickType_t override;
auto ProcessStreamInfo(const StreamInfo& info) auto ProcessStreamInfo(const StreamInfo& info)

@ -0,0 +1,37 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include "freertos/FreeRTOS.h"
#include "freertos/message_buffer.h"
#include "span.hpp"
namespace audio {
class StreamBuffer {
public:
explicit StreamBuffer(std::size_t chunk_size, std::size_t buffer_size);
~StreamBuffer();
auto Handle() -> MessageBufferHandle_t* { return &handle_; }
auto ReadBuffer() -> cpp::span<std::byte> { return input_chunk_; }
auto WriteBuffer() -> cpp::span<std::byte> { return output_chunk_; }
StreamBuffer(const StreamBuffer&) = delete;
StreamBuffer& operator=(const StreamBuffer&) = delete;
private:
std::byte* raw_memory_;
StaticMessageBuffer_t metadata_;
MessageBufferHandle_t handle_;
std::byte* raw_input_chunk_;
cpp::span<std::byte> input_chunk_;
std::byte* raw_output_chunk_;
cpp::span<std::byte> output_chunk_;
};
} // namespace audio

@ -3,6 +3,7 @@
#include <cstdint> #include <cstdint>
#include <optional> #include <optional>
#include <string> #include <string>
#include <string_view>
#include "cbor.h" #include "cbor.h"
#include "result.hpp" #include "result.hpp"
@ -19,10 +20,14 @@ class StreamInfo {
~StreamInfo() = default; ~StreamInfo() = default;
auto Path() const -> const std::optional<std::string>& { return path_; } auto Path() const -> const std::optional<std::string>& { return path_; }
auto Path(const std::string_view& d) -> void { path_ = d; }
auto Channels() const -> const std::optional<uint8_t>& { return channels_; } auto Channels() const -> const std::optional<uint8_t>& { return channels_; }
auto BitsPerSample() const -> const std::optional<uint8_t>& { auto BitsPerSample() const -> const std::optional<uint8_t>& {
return bits_per_sample_; return bits_per_sample_;
} }
auto SampleRate() const -> const std::optional<uint16_t>& { auto SampleRate() const -> const std::optional<uint16_t>& {
return sample_rate_; return sample_rate_;
} }

@ -0,0 +1,26 @@
#include "stream_buffer.hpp"
namespace audio {
StreamBuffer::StreamBuffer(std::size_t chunk_size, std::size_t buffer_size)
: raw_memory_(static_cast<std::byte*>(
heap_caps_malloc(buffer_size, MALLOC_CAP_SPIRAM))),
handle_(
xMessageBufferCreateStatic(buffer_size,
reinterpret_cast<uint8_t*>(raw_memory_),
&metadata_)),
raw_input_chunk_(static_cast<std::byte*>(
heap_caps_malloc(chunk_size * 1.5, MALLOC_CAP_SPIRAM))),
input_chunk_(raw_input_chunk_, chunk_size * 1.5),
raw_output_chunk_(static_cast<std::byte*>(
heap_caps_malloc(chunk_size, MALLOC_CAP_SPIRAM))),
output_chunk_(raw_output_chunk_, chunk_size) {}
StreamBuffer::~StreamBuffer() {
vMessageBufferDelete(handle_);
free(raw_memory_);
free(raw_input_chunk_);
free(raw_output_chunk_);
}
} // namespace audio

@ -5,7 +5,7 @@
namespace codecs { namespace codecs {
auto CreateCodecForExtension(std::string extension) auto CreateCodecForFile(const std::string& file)
-> cpp::result<std::unique_ptr<ICodec>, CreateCodecError> { -> cpp::result<std::unique_ptr<ICodec>, CreateCodecError> {
return std::make_unique<MadMp3Decoder>(); // TODO. return std::make_unique<MadMp3Decoder>(); // TODO.
} }

@ -63,7 +63,7 @@ class ICodec {
enum CreateCodecError { UNKNOWN_EXTENSION }; enum CreateCodecError { UNKNOWN_EXTENSION };
auto CreateCodecForFile(const std::string& extension) auto CreateCodecForFile(const std::string& file)
-> cpp::result<std::unique_ptr<ICodec>, CreateCodecError>; -> cpp::result<std::unique_ptr<ICodec>, CreateCodecError>;
} // namespace codecs } // namespace codecs

@ -26,7 +26,7 @@ class SdStorage {
}; };
static auto create(GpioExpander* gpio) static auto create(GpioExpander* gpio)
-> cpp::result<std::unique_ptr<SdStorage>, Error>; -> cpp::result<std::shared_ptr<SdStorage>, Error>;
SdStorage(GpioExpander* gpio, SdStorage(GpioExpander* gpio,
esp_err_t (*do_transaction)(sdspi_dev_handle_t, sdmmc_command_t*), esp_err_t (*do_transaction)(sdspi_dev_handle_t, sdmmc_command_t*),

@ -50,7 +50,7 @@ static esp_err_t do_transaction(sdspi_dev_handle_t handle,
} // namespace callback } // namespace callback
auto SdStorage::create(GpioExpander* gpio) auto SdStorage::create(GpioExpander* gpio)
-> cpp::result<std::unique_ptr<SdStorage>, Error> { -> cpp::result<std::shared_ptr<SdStorage>, Error> {
// Acquiring the bus will also flush the mux switch change. // Acquiring the bus will also flush the mux switch change.
gpio->set_pin(GpioExpander::SD_MUX_SWITCH, GpioExpander::SD_MUX_ESP); gpio->set_pin(GpioExpander::SD_MUX_SWITCH, GpioExpander::SD_MUX_ESP);

@ -8,11 +8,14 @@
#include <iostream> #include <iostream>
#include <string> #include <string>
#include "audio_playback.hpp"
#include "esp_console.h" #include "esp_console.h"
namespace console { namespace console {
std::string toSdPath(std::string filepath) { static AppConsole* sInstance = nullptr;
std::string toSdPath(const std::string& filepath) {
return std::string(drivers::kStoragePath) + "/" + filepath; return std::string(drivers::kStoragePath) + "/" + filepath;
} }
@ -57,12 +60,7 @@ int CmdPlayFile(int argc, char** argv) {
return 1; return 1;
} }
/*
sInstance->playback_->Play(toSdPath(argv[1])); sInstance->playback_->Play(toSdPath(argv[1]));
if (argc == 3) {
sInstance->playback_->SetNextFile(toSdPath(argv[2]));
}
*/
return 0; return 0;
} }
@ -125,14 +123,12 @@ void RegisterVolume() {
esp_console_cmd_register(&cmd); esp_console_cmd_register(&cmd);
} }
/* AppConsole::AppConsole(audio::AudioPlayback* playback) : playback_(playback) {
AppConsole::AppConsole() {
sInstance = this; sInstance = this;
} }
AppConsole::~AppConsole() { AppConsole::~AppConsole() {
sInstance = nullptr; sInstance = nullptr;
} }
*/
auto AppConsole::RegisterExtraComponents() -> void { auto AppConsole::RegisterExtraComponents() -> void {
RegisterListDir(); RegisterListDir();

@ -2,6 +2,7 @@
#include <memory> #include <memory>
#include "audio_playback.hpp"
#include "console.hpp" #include "console.hpp"
#include "storage.hpp" #include "storage.hpp"
@ -9,8 +10,10 @@ namespace console {
class AppConsole : public Console { class AppConsole : public Console {
public: public:
AppConsole() {} explicit AppConsole(audio::AudioPlayback* playback);
virtual ~AppConsole() {} virtual ~AppConsole();
audio::AudioPlayback* playback_;
protected: protected:
virtual auto RegisterExtraComponents() -> void; virtual auto RegisterExtraComponents() -> void;

@ -24,6 +24,7 @@
#include "widgets/lv_label.h" #include "widgets/lv_label.h"
#include "app_console.hpp" #include "app_console.hpp"
#include "audio_playback.hpp"
#include "battery.hpp" #include "battery.hpp"
#include "dac.hpp" #include "dac.hpp"
#include "display.hpp" #include "display.hpp"
@ -102,7 +103,7 @@ extern "C" void app_main(void) {
ESP_LOGE(TAG, "Failed: %d", storage_res.error()); ESP_LOGE(TAG, "Failed: %d", storage_res.error());
return; return;
} }
std::unique_ptr<drivers::SdStorage> storage = std::move(storage_res.value()); std::shared_ptr<drivers::SdStorage> storage = std::move(storage_res.value());
LvglArgs* lvglArgs = (LvglArgs*)calloc(1, sizeof(LvglArgs)); LvglArgs* lvglArgs = (LvglArgs*)calloc(1, sizeof(LvglArgs));
lvglArgs->gpio_expander = expander; lvglArgs->gpio_expander = expander;
@ -110,32 +111,20 @@ extern "C" void app_main(void) {
(void*)lvglArgs, 1, sLvglStack, (void*)lvglArgs, 1, sLvglStack,
&sLvglTaskBuffer, 1); &sLvglTaskBuffer, 1);
/*
ESP_LOGI(TAG, "Init audio output (I2S)");
auto sink_res = drivers::I2SAudioOutput::create(expander);
if (sink_res.has_error()) {
ESP_LOGE(TAG, "Failed: %d", sink_res.error());
return;
}
std::unique_ptr<drivers::IAudioOutput> sink = std::move(sink_res.value());
ESP_LOGI(TAG, "Init audio pipeline"); ESP_LOGI(TAG, "Init audio pipeline");
auto playback_res = drivers::AudioPlayback::create(std::move(sink)); auto playback_res = audio::AudioPlayback::create(expander, storage);
if (playback_res.has_error()) { if (playback_res.has_error()) {
ESP_LOGE(TAG, "Failed: %d", playback_res.error()); ESP_LOGE(TAG, "Failed: %d", playback_res.error());
return; return;
} }
std::unique_ptr<drivers::AudioPlayback> playback = std::shared_ptr<audio::AudioPlayback> playback =
std::move(playback_res.value()); std::move(playback_res.value());
playback->SetVolume(130);
*/
ESP_LOGI(TAG, "Launch console"); ESP_LOGI(TAG, "Launch console");
console::AppConsole console; console::AppConsole console(playback.get());
console.Launch(); console.Launch();
while (1) { while (1) {
// playback->ProcessEvents(5);
vTaskDelay(pdMS_TO_TICKS(100)); vTaskDelay(pdMS_TO_TICKS(100));
} }
} }

Loading…
Cancel
Save