Progress on own pipeline. Still very WIP
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
#include "audio_decoder.hpp"
|
||||
#include <cstddef>
|
||||
#include "esp_heap_caps.h"
|
||||
#include "include/audio_element.hpp"
|
||||
#include "include/fatfs_audio_input.hpp"
|
||||
|
||||
namespace audio {
|
||||
|
||||
static const TickType_t kMaxWaitTicks = portMAX_DELAY;
|
||||
// TODO: could this be larger? depends on the codecs i guess
|
||||
static const std::size_t kWorkingBufferSize = kMaxFrameSize;
|
||||
|
||||
AudioDecoder::AudioDecoder() {
|
||||
working_buffer_ = heap_caps_malloc(kWorkingBufferSize, MALLOC_CAP_SPIRAM);
|
||||
}
|
||||
|
||||
AudioDecoder::~AudioDecoder() {
|
||||
free(working_buffer_);
|
||||
}
|
||||
|
||||
auto AudioDecoder::InputCommandQueue() -> QueueHandle_t {
|
||||
return input_queue_;
|
||||
}
|
||||
|
||||
auto AudioDecoder::SetInputCommandQueue(QueueHandle_t queue) -> void {
|
||||
input_queue_ = queue;
|
||||
}
|
||||
|
||||
auto AudioDecoder::SetOutputCommandQueue(QueueHandle_t queue) -> void {
|
||||
output_queue_ = queue;
|
||||
}
|
||||
|
||||
auto AudioDecoder::InputBuffer() -> StreamBufferHandle_t {
|
||||
return input_buffer_;
|
||||
}
|
||||
|
||||
auto AudioDecoder::SetInputBuffer(StreamBufferHandle_t buffer) -> void {
|
||||
input_buffer_ = buffer;
|
||||
}
|
||||
|
||||
auto AudioDecoder::SetOutputBuffer(StreamBufferHandle_t buffer) -> void {
|
||||
output_buffer_ = buffer;
|
||||
}
|
||||
|
||||
auto AudioDecoder::ProcessElementCommand(void* command) -> ProcessResult {
|
||||
FatfsAudioInput::OutputCommand *real = std::reinterpret_cast<FatfsAudioInput::OutputCommand*>(command);
|
||||
|
||||
if (current_codec_->CanHandleExtension(real->extension)) {
|
||||
// TODO: Do we need to reset the codec?
|
||||
delete real;
|
||||
return OK;
|
||||
}
|
||||
|
||||
auto result = codecs::CreateCodecForExtension(real->extension);
|
||||
// TODO: handle error case
|
||||
if (result.has_value()) {
|
||||
current_codec_ = result.value();
|
||||
}
|
||||
|
||||
delete real;
|
||||
return OK;
|
||||
}
|
||||
|
||||
auto AudioDecoder::SkipElementCommand(void* command) -> void {
|
||||
FatfsAudioInput::OutputCommand *real = std::reinterpret_cast<FatfsAudioInput::OutputCommand*>(command);
|
||||
delete real;
|
||||
}
|
||||
|
||||
auto AudioDecoder::ProcessData(uint8_t* data, uint16_t length) -> ProcessResult {
|
||||
if (current_codec_ == nullptr) {
|
||||
// TODO: signal this
|
||||
return OK;
|
||||
}
|
||||
|
||||
auto result = current_codec_->Process(data, length, working_buffer_, kWorkingBufferSize);
|
||||
if (result.has_value()) {
|
||||
xStreamBufferSend(&output_buffer_, working_buffer_, result.value(), kMaxWaitTicks);
|
||||
} else {
|
||||
// TODO: handle i guess
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
auto AudioDecoder::ProcessIdle() -> ProcessResult {
|
||||
// Not used.
|
||||
return OK;
|
||||
}
|
||||
|
||||
auto AudioDecoder::Pause() -> void {
|
||||
// TODO.
|
||||
}
|
||||
auto AudioDecoder::IsPaused() -> bool {
|
||||
// TODO.
|
||||
}
|
||||
|
||||
auto AudioDecoder::Resume() -> void {
|
||||
// TODO.
|
||||
}
|
||||
|
||||
|
||||
} // namespace audio
|
||||
@@ -10,10 +10,12 @@
|
||||
#include "freertos/stream_buffer.h"
|
||||
|
||||
#include "audio_element.hpp"
|
||||
#include "include/audio_element.hpp"
|
||||
|
||||
namespace audio {
|
||||
|
||||
static const TickType_t kCommandWaitTicks = 1;
|
||||
static const TickType_t kIdleTaskDelay = 1;
|
||||
|
||||
void audio_task(void* args) {
|
||||
AudioTaskArgs* real_args = reinterpret_cast<AudioTaskArgs*>(args);
|
||||
@@ -30,8 +32,16 @@ void audio_task(void* args) {
|
||||
|
||||
while (1) {
|
||||
IAudioElement::Command command;
|
||||
ProcessResult result;
|
||||
|
||||
if (!xQueueReceive(commands, &command, kCommandWaitTicks)) {
|
||||
element->ProcessIdle();
|
||||
result = element->ProcessIdle();
|
||||
if (result == IAudioElement::ERROR) {
|
||||
break;
|
||||
}
|
||||
if (result == IAudioElement::OUTPUT_FULL) {
|
||||
vTaskDelay(kIdleTaskDelay);
|
||||
}
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -39,7 +49,6 @@ void audio_task(void* args) {
|
||||
if (command.sequence_number > current_sequence_number) {
|
||||
current_sequence_number = command.sequence_number;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -49,7 +58,13 @@ void audio_task(void* args) {
|
||||
xStreamBufferReceive(stream, &frame_buffer, command.read_size, 0);
|
||||
|
||||
if (command.sequence_number == current_sequence_number) {
|
||||
element->ProcessData(frame_buffer, command.read_size);
|
||||
result = element->ProcessData(frame_buffer, command.read_size);
|
||||
if (result == IAudioElement::ERROR) {
|
||||
break;
|
||||
}
|
||||
if (result == IAudioElement::OUTPUT_FULL) {
|
||||
// TODO: Do we care about this? could just park indefinitely.
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -58,7 +73,13 @@ void audio_task(void* args) {
|
||||
if (command.type == IAudioElement::ELEMENT) {
|
||||
assert(command.data != NULL);
|
||||
if (command.sequence_number == current_sequence_number) {
|
||||
element->ProcessElementCommand(command.data);
|
||||
result = element->ProcessElementCommand(command.data);
|
||||
if (result == IAudioElement::ERROR) {
|
||||
break;
|
||||
}
|
||||
if (result == IAudioElement::OUTPUT_FULL) {
|
||||
// TODO: what does this mean lol
|
||||
}
|
||||
} else {
|
||||
element->SkipElementCommand(command.data);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,42 @@
|
||||
#include "fatfs_audio_input.hpp"
|
||||
#include "fatfs_audio_input.hpp<D-c>ccc
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "esp-adf/components/input_key_service/include/input_key_service.h"
|
||||
#include "esp_heap_caps.h"
|
||||
|
||||
#include "audio_element.hpp"
|
||||
#include "freertos/portmacro.h"
|
||||
#include "include/audio_element.hpp"
|
||||
|
||||
namespace audio {
|
||||
|
||||
static const size_t kQueueItems = 0;
|
||||
static constexpr size_t kQueueItemSize = sizeof(IAudioElement::Command);
|
||||
static constexpr size_t kQueueSize = kQueueItems * kQueueItemSize;
|
||||
static const TickType_t kMaxWaitTicks = portMAX_DELAY;
|
||||
|
||||
static const size_t kOutputBufferSize = 1024;
|
||||
// Large output buffer size, so that we can keep a get as much of the input file
|
||||
// into memory as soon as possible.
|
||||
static constexpr std::size_t kOutputBufferSize = 1024 * 128;
|
||||
static constexpr std::size_t kQueueItemSize = sizeof(IAudioElement::Command);
|
||||
// Use a large enough command queue size that we can fit reads for the full
|
||||
// buffer into the queue.
|
||||
static constexpr std::size_t kOutputQueueItemNumber = kOutputBufferSize / kMaxFrameSize;
|
||||
static constexpr std::size_t kOutputQueueSize = kOutputQueueItemNumber * kQueueItemSize;
|
||||
|
||||
// This should be a relatively responsive element, so no need for a particularly
|
||||
// large queue.
|
||||
static constexpr std::size_t kInputQueueItemNumber = 4;
|
||||
static constexpr std::size_t kInputQueueSize = kInputQueueItemNumber * kQueueItemSize;
|
||||
|
||||
FatfsAudioInput::FatfsAudioInput(std::shared_ptr<drivers::SdStorage> storage)
|
||||
: IAudioElement(), storage_(storage) {
|
||||
input_queue_memory_ = heap_caps_malloc(kQueueSize, MALLOC_CAP_SPIRAM);
|
||||
input_queue_ = xQueueCreateStatic(
|
||||
kQueueItems, kQueueItemSize, input_queue_memory_, &input_queue_metadata_);
|
||||
working_buffer_ = heap_caps_malloc(kMaxFrameSize, MALLOC_CAP_SPIRAM);
|
||||
|
||||
output_queue_memory_ = heap_caps_malloc(kQueueSize, MALLOC_CAP_SPIRAM);
|
||||
input_queue_memory_ = heap_caps_malloc(kInputQueueSize, MALLOC_CAP_SPIRAM);
|
||||
input_queue_ = xQueueCreateStatic(
|
||||
kInputQueueItemNumber, kQueueItemSize, input_queue_memory_, &input_queue_metadata_);
|
||||
|
||||
output_queue_memory_ = heap_caps_malloc(kOutputQueueSize, MALLOC_CAP_SPIRAM);
|
||||
output_queue_ =
|
||||
xQueueCreateStatic(kQueueItems, kQueueItemSize, output_queue_memory_,
|
||||
xQueueCreateStatic(kOutputQueueItems, kQueueItemSize, output_queue_memory_,
|
||||
&output_queue_metadata_);
|
||||
|
||||
output_buffer_memory_ =
|
||||
@@ -33,6 +47,7 @@ FatfsAudioInput::FatfsAudioInput(std::shared_ptr<drivers::SdStorage> storage)
|
||||
}
|
||||
|
||||
FatfsAudioInput::~FatfsAudioInput() {
|
||||
free(working_buffer_);
|
||||
vStreamBufferDelete(output_buffer_);
|
||||
free(output_buffer_memory_);
|
||||
vQueueDelete(output_queue_);
|
||||
@@ -57,23 +72,84 @@ auto FatfsAudioInput::OutputBuffer() -> StreamBufferHandle_t {
|
||||
return output_buffer_;
|
||||
}
|
||||
|
||||
auto FatfsAudioInput::ProcessElementCommand(void* command) -> void {
|
||||
InputCommand *real = std::reinterpret_pointer_cast<input_key_service_add_key*>(command);
|
||||
auto FatfsAudioInput::ProcessElementCommand(void* command) -> ProcessResult {
|
||||
InputCommand *real = std::reinterpret_cast<InputCommand*>(command);
|
||||
|
||||
// TODO.
|
||||
if (uxQueueSpacesAvailable(output_queue_) < 2) {
|
||||
return OUTPUT_FULL;
|
||||
}
|
||||
|
||||
if (is_file_open_) {
|
||||
f_close(¤t_file_);
|
||||
}
|
||||
|
||||
FRESULT res = f_open(¤t_file_, real->filename.c_str(), FA_READ);
|
||||
if (res != FR_OK) {
|
||||
delete real;
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
if (real->seek_to && f_lseek(¤t_file_, real->seek_to) {
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
is_file_open_ = true;
|
||||
current_sequence_++;
|
||||
|
||||
Command sequence_update;
|
||||
sequence_update.type = SEQUENCE_NUMBER;
|
||||
sequence_update.sequence_number = current_sequence_;
|
||||
|
||||
if (real->interrupt) {
|
||||
xQueueSendToFront(output_queue_, &sequence_update, kMaxWaitTicks);
|
||||
} else {
|
||||
xQueueSendToBack(output_queue_, &sequence_update, kMaxWaitTicks);
|
||||
}
|
||||
|
||||
OutputCommand *data = new OutputCommand;
|
||||
data->extension = "txt";
|
||||
Command file_info;
|
||||
file_info.type = ELEMENT;
|
||||
file_info.sequence_number = current_sequence_;
|
||||
file_info.data = &data;
|
||||
xQueueSendToBack(output_queue_, &file_info, kMaxWaitTicks);
|
||||
|
||||
delete real;
|
||||
return OK;
|
||||
}
|
||||
|
||||
auto FatfsAudioInput::SkipElementCommand(void* command) -> void {
|
||||
InputCommand *real = std::reinterpret_pointer_cast<input_key_service_add_key*>(command);
|
||||
InputCommand *real = std::reinterpret_cast<input_key_service_add_key*>(command);
|
||||
delete real;
|
||||
}
|
||||
|
||||
auto FatfsAudioInput::ProcessData(uint8_t* data, uint16_t length) -> void {
|
||||
// Not implemented.
|
||||
// Not used, since we have no input stream.
|
||||
}
|
||||
|
||||
auto FatfsAudioInput::ProcessIdle() -> void {
|
||||
// TODO.
|
||||
auto FatfsAudioInput::ProcessIdle() -> ProcessResult {
|
||||
if (!is_file_open_) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
if (xStreamBufferSpacesAvailable(output_buffer) < kMaxFrameSize) {
|
||||
return OUTPUT_FULL;
|
||||
}
|
||||
|
||||
UINT bytes_read = 0;
|
||||
FRESULT result = f_read(¤t_file_, working_buffer_, kMaxFrameSize, &bytes_read);
|
||||
if (!FR_OK) {
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
xStreamBufferSend(&output_buffer_, working_buffer_, bytes_read, kMaxWaitTicks);
|
||||
|
||||
if (f_eof(¤t_file_)) {
|
||||
f_close(¤t_file_);
|
||||
is_file_open_ = false;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
} // namespace audio
|
||||
|
||||
@@ -1,34 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include "audio_element.hpp"
|
||||
#include "ff.h"
|
||||
#include "codec.hpp"
|
||||
|
||||
namespace audio {
|
||||
|
||||
enum SampleRate {};
|
||||
enum BitDepth {};
|
||||
|
||||
struct PcmStreamHeader {
|
||||
SampleRate sample_rate;
|
||||
BitDepth bit_depth;
|
||||
bool configure_now;
|
||||
};
|
||||
|
||||
class AudioDecoder {
|
||||
class AudioDecoder : public IAudioElement {
|
||||
public:
|
||||
AudioDecoder();
|
||||
~AudioDecoder();
|
||||
|
||||
auto SetSource(RingbufHandle_t& source) -> void;
|
||||
auto Pause() -> void;
|
||||
auto IsPaused() -> bool;
|
||||
|
||||
enum Status {};
|
||||
auto ProcessChunk() -> Status;
|
||||
auto Resume() -> void;
|
||||
|
||||
auto GetOutputStream() const -> RingbufHandle_t;
|
||||
auto SetInputCommandQueue(QueueHandle_t) -> void;
|
||||
auto SetOutputCommandQueue(QueueHandle_t) -> void;
|
||||
auto SetInputBuffer(StreamBufferHandle_t) -> void;
|
||||
auto SetOutputBuffer(StreamBufferHandle_t) -> void;
|
||||
|
||||
private:
|
||||
RingbufHandle_t input_;
|
||||
RingbufHandle_t output_;
|
||||
std::unique_ptr<codecs::ICodec> current_codec_;
|
||||
|
||||
uint8_t *working_buffer_;
|
||||
|
||||
QueueHandle_t input_queue_;
|
||||
QueueHandle_t output_queue_;
|
||||
StreamBufferHandle_t input_buffer_;
|
||||
StreamBufferHandle_t output_buffer_;
|
||||
};
|
||||
|
||||
} // namespace audio
|
||||
|
||||
@@ -33,6 +33,7 @@ class IAudioElement {
|
||||
struct Command {
|
||||
CommandType type;
|
||||
uint8_t sequence_number;
|
||||
// TODO: tag data's type
|
||||
union {
|
||||
void* data;
|
||||
std::size_t frame_size;
|
||||
@@ -52,22 +53,28 @@ class IAudioElement {
|
||||
*/
|
||||
virtual auto InputBuffer() -> StreamBufferHandle_t = 0;
|
||||
|
||||
enum ProcessResult {
|
||||
OK,
|
||||
OUTPUT_FULL,
|
||||
ERROR,
|
||||
};
|
||||
|
||||
/*
|
||||
* Called when an element-specific command has been received.
|
||||
*/
|
||||
virtual auto ProcessElementCommand(void* command) -> void = 0;
|
||||
virtual auto ProcessElementCommand(void* command) -> ProcessResult = 0;
|
||||
|
||||
virtual auto SkipElementCommand(void* command) -> void = 0;
|
||||
|
||||
/*
|
||||
* Called with the result of a read bytes command.
|
||||
*/
|
||||
virtual auto ProcessData(uint8_t* data, uint16_t length) -> void = 0;
|
||||
virtual auto ProcessData(uint8_t* data, uint16_t length) -> ProcessResult = 0;
|
||||
|
||||
/*
|
||||
* Called periodically when there are no pending commands.
|
||||
*/
|
||||
virtual auto ProcessIdle() -> void = 0;
|
||||
virtual auto ProcessIdle() -> ProcessResult = 0;
|
||||
};
|
||||
|
||||
} // namespace audio
|
||||
|
||||
@@ -5,25 +5,25 @@
|
||||
|
||||
#include "audio_common.h"
|
||||
#include "audio_element.h"
|
||||
#include "audio_element.hpp"
|
||||
|
||||
namespace drivers {
|
||||
namespace audio {
|
||||
|
||||
class IAudioOutput {
|
||||
class AudioOutput : IAudioElement {
|
||||
public:
|
||||
IAudioOutput(audio_element_handle_t element) : element_(element) {}
|
||||
virtual ~IAudioOutput() { audio_element_deinit(element_); }
|
||||
AudioOutput();
|
||||
~AudioOutput();
|
||||
|
||||
auto GetAudioElement() -> audio_element_handle_t { return element_; }
|
||||
|
||||
virtual auto SetVolume(uint8_t volume) -> void = 0;
|
||||
virtual auto GetVolume() const -> uint8_t { return volume_; }
|
||||
auto SetInputBuffer(StreamBufferHandle_t buffer) -> void;
|
||||
|
||||
virtual auto Configure(audio_element_info_t& info) -> void = 0;
|
||||
virtual auto SetSoftMute(bool enabled) -> void = 0;
|
||||
|
||||
protected:
|
||||
private:
|
||||
audio_element_handle_t element_;
|
||||
uint8_t volume_;
|
||||
};
|
||||
|
||||
} // namespace drivers
|
||||
} // namespace audio
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
@@ -16,10 +17,12 @@ class FatfsAudioInput : public IAudioElement {
|
||||
public:
|
||||
struct InputCommand {
|
||||
std::string filename;
|
||||
size_t seek_to;
|
||||
bool interrupt;
|
||||
};
|
||||
|
||||
struct OutputCommand {
|
||||
// TODO: does this actually need any special output?
|
||||
std::string extension;
|
||||
};
|
||||
|
||||
FatfsAudioInput(std::shared_ptr<drivers::SdStorage> storage);
|
||||
@@ -31,7 +34,11 @@ class FatfsAudioInput : public IAudioElement {
|
||||
private:
|
||||
std::shared_ptr<drivers::SdStorage> storage_;
|
||||
|
||||
uint8_t current_sequence = 0;
|
||||
uint8_t *working_buffer_;
|
||||
|
||||
uint8_t current_sequence_ = 0;
|
||||
FIL current_file_;
|
||||
bool is_file_open_ = false;
|
||||
|
||||
uint8_t* input_queue_memory_;
|
||||
StaticQueue_t input_queue_metadata_;
|
||||
|
||||
+18
-6
@@ -2,16 +2,28 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "result.hpp"
|
||||
|
||||
namespace codecs {
|
||||
|
||||
class IAudioDecoder {
|
||||
public:
|
||||
virtual ~IAudioDecoder() {}
|
||||
enum CreateCodecError {};
|
||||
|
||||
virtual auto ProcessData(
|
||||
auto CreateCodecForExtension(std::string extension) -> cpp::result<std::unique_ptr<ICodec>, CreateCodecError>;
|
||||
|
||||
class ICodec {
|
||||
public:
|
||||
virtual ~ICodec() {}
|
||||
|
||||
virtual auto CanHandleExtension(std::string extension) -> bool = 0;
|
||||
|
||||
enum Error {};
|
||||
|
||||
virtual auto Process(
|
||||
uint8_t *input,
|
||||
size_t input_len,
|
||||
uint8_t *output) -> size_t = 0;
|
||||
std::size_t input_len,
|
||||
uint8_t *output,
|
||||
std::size_t output_length) -> cpp::result<size_t, Error> = 0;
|
||||
};
|
||||
|
||||
} // namespace codecs
|
||||
|
||||
@@ -2,5 +2,5 @@ idf_component_register(
|
||||
SRCS "dac.cpp" "gpio_expander.cpp" "battery.cpp" "storage.cpp" "i2c.cpp"
|
||||
"audio_playback.cpp" "i2s_audio_output.cpp" "display.cpp" "display_init.cpp" "spi.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES "esp_adc_cal" "fatfs" "audio_pipeline" "audio_stream" "result" "lvgl")
|
||||
REQUIRES "esp_adc_cal" "fatfs" "result" "lvgl")
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "driver/sdspi_host.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "ff.h"
|
||||
#include "result.hpp"
|
||||
|
||||
#include "gpio_expander.hpp"
|
||||
@@ -38,6 +39,8 @@ class SdStorage {
|
||||
auto HandleTransaction(sdspi_dev_handle_t handle, sdmmc_command_t* cmdinfo)
|
||||
-> esp_err_t;
|
||||
|
||||
auto GetFs() -> FATFS*;
|
||||
|
||||
// Not copyable or movable.
|
||||
// TODO: maybe this could be movable?
|
||||
SdStorage(const SdStorage&) = delete;
|
||||
|
||||
@@ -146,4 +146,8 @@ auto SdStorage::HandleTransaction(sdspi_dev_handle_t handle,
|
||||
return do_transaction_(handle, cmdinfo);
|
||||
}
|
||||
|
||||
auto SdStorage::GetFs() -> FATFS* {
|
||||
return fs_;
|
||||
}
|
||||
|
||||
} // namespace drivers
|
||||
|
||||
Reference in New Issue
Block a user