better handling of chunk buffer
This commit is contained in:
@@ -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
|
||||||
|
|||||||
+60
-294
@@ -4,322 +4,88 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include "audio_decoder.hpp"
|
||||||
|
#include "audio_task.hpp"
|
||||||
|
#include "chunk.hpp"
|
||||||
|
#include "fatfs_audio_input.hpp"
|
||||||
|
#include "freertos/portmacro.h"
|
||||||
|
#include "gpio_expander.hpp"
|
||||||
|
#include "i2s_audio_output.hpp"
|
||||||
|
#include "storage.hpp"
|
||||||
|
#include "stream_buffer.hpp"
|
||||||
|
#include "stream_info.hpp"
|
||||||
|
#include "stream_message.hpp"
|
||||||
|
|
||||||
#include "aac_decoder.h"
|
namespace audio {
|
||||||
#include "amr_decoder.h"
|
|
||||||
#include "audio_element.h"
|
|
||||||
#include "audio_event_iface.h"
|
|
||||||
#include "audio_pipeline.h"
|
|
||||||
#include "esp_err.h"
|
|
||||||
#include "flac_decoder.h"
|
|
||||||
#include "mp3_decoder.h"
|
|
||||||
#include "ogg_decoder.h"
|
|
||||||
#include "opus_decoder.h"
|
|
||||||
#include "wav_decoder.h"
|
|
||||||
|
|
||||||
#include "audio_output.hpp"
|
// TODO: idk
|
||||||
|
static const std::size_t kMinElementBufferSize = 1024;
|
||||||
|
|
||||||
static const char* kTag = "PLAYBACK";
|
auto AudioPlayback::create(drivers::GpioExpander* expander,
|
||||||
static const char* kSource = "src";
|
std::shared_ptr<drivers::SdStorage> storage)
|
||||||
static const char* kDecoder = "dec";
|
|
||||||
static const char* kSink = "sink";
|
|
||||||
|
|
||||||
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 =
|
auto sink_res = I2SAudioOutput::create(expander);
|
||||||
audio_pipeline_cfg_t(DEFAULT_AUDIO_PIPELINE_CONFIG());
|
if (sink_res.has_error()) {
|
||||||
pipeline = audio_pipeline_init(&pipeline_config);
|
return cpp::fail(ERR_INIT_ELEMENT);
|
||||||
if (pipeline == NULL) {
|
|
||||||
return cpp::fail(Error::PIPELINE_INIT);
|
|
||||||
}
|
}
|
||||||
|
auto sink = sink_res.value();
|
||||||
|
|
||||||
fatfs_stream_cfg_t fatfs_stream_config =
|
auto playback = std::make_unique<AudioPlayback>();
|
||||||
fatfs_stream_cfg_t(FATFS_STREAM_CFG_DEFAULT());
|
|
||||||
fatfs_stream_config.type = AUDIO_STREAM_READER;
|
|
||||||
fatfs_stream_reader = fatfs_stream_init(&fatfs_stream_config);
|
|
||||||
if (fatfs_stream_reader == NULL) {
|
|
||||||
return cpp::fail(Error::FATFS_INIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
audio_event_iface_cfg_t event_config = AUDIO_EVENT_IFACE_DEFAULT_CFG();
|
// Configure the pipeline
|
||||||
event_interface = audio_event_iface_init(&event_config);
|
source->InputBuffer(&playback->stream_start_);
|
||||||
|
sink->OutputBuffer(&playback->stream_end_);
|
||||||
|
playback->ConnectElements(source.get(), codec.get());
|
||||||
|
playback->ConnectElements(codec.get(), sink.get());
|
||||||
|
|
||||||
audio_pipeline_set_listener(pipeline, event_interface);
|
// Launch!
|
||||||
audio_element_msg_set_listener(fatfs_stream_reader, event_interface);
|
StartAudioTask("src", source);
|
||||||
audio_element_msg_set_listener(output->GetAudioElement(), event_interface);
|
StartAudioTask("dec", codec);
|
||||||
|
StartAudioTask("sink", sink);
|
||||||
|
|
||||||
audio_pipeline_register(pipeline, fatfs_stream_reader, kSource);
|
return playback;
|
||||||
audio_pipeline_register(pipeline, output->GetAudioElement(), kSink);
|
|
||||||
|
|
||||||
return std::make_unique<AudioPlayback>(output, pipeline, fatfs_stream_reader,
|
|
||||||
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) {
|
auto AudioPlayback::Play(const std::string& filename) -> void {
|
||||||
output_->SetSoftMute(true);
|
StreamInfo info;
|
||||||
|
info.Path(filename);
|
||||||
|
|
||||||
if (playback_state_ != STOPPED) {
|
std::array<std::byte, 128> dest;
|
||||||
audio_pipeline_stop(pipeline_);
|
auto len = WriteMessage(
|
||||||
audio_pipeline_wait_for_stop(pipeline_);
|
TYPE_STREAM_INFO, [&](auto enc) { return info.Encode(enc); }, dest);
|
||||||
audio_pipeline_terminate(pipeline_);
|
|
||||||
}
|
|
||||||
|
|
||||||
playback_state_ = PLAYING;
|
if (len.has_error()) {
|
||||||
Decoder decoder = GetDecoderForFilename(filename);
|
// TODO.
|
||||||
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() {
|
|
||||||
if (playback_state_ == PLAYING) {
|
|
||||||
Pause();
|
|
||||||
} 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (1) {
|
// TODO: short delay, return error on fail
|
||||||
audio_event_iface_msg_t event;
|
xMessageBufferSend(*stream_start_.Handle(), dest.data(), len.value(),
|
||||||
esp_err_t err = audio_event_iface_listen(event_interface_, &event,
|
portMAX_DELAY);
|
||||||
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 &&
|
|
||||||
event.source == (void*)source_element_ &&
|
|
||||||
event.cmd == AEL_MSG_CMD_REPORT_STATUS) {
|
|
||||||
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 &&
|
|
||||||
event.source == (void*)output_->GetAudioElement() &&
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
auto AudioPlayback::ConnectElements(IAudioElement* src, IAudioElement* sink)
|
||||||
next_filename_ = filename;
|
-> void {
|
||||||
|
std::size_t chunk_size =
|
||||||
|
std::max(src->InputMinChunkSize(), sink->InputMinChunkSize());
|
||||||
|
std::size_t buffer_size = std::max(kMinElementBufferSize, chunk_size * 2);
|
||||||
|
|
||||||
|
auto buffer = std::make_unique<StreamBuffer>(chunk_size, buffer_size);
|
||||||
|
src->OutputBuffer(buffer.get());
|
||||||
|
sink->OutputBuffer(buffer.get());
|
||||||
|
element_buffers_.push_back(std::move(buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioPlayback::SetVolume(uint8_t volume) {
|
} // namespace audio
|
||||||
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
|
|
||||||
-> audio_element_handle_t {
|
|
||||||
if (decoder == MP3) {
|
|
||||||
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) {
|
|
||||||
if (decoder_type_ == decoder) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
decoder_ = CreateDecoder(decoder);
|
|
||||||
decoder_type_ = decoder;
|
|
||||||
audio_pipeline_register(pipeline_, decoder_, kDecoder);
|
|
||||||
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
|
|
||||||
|
|||||||
+16
-22
@@ -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,24 +41,22 @@ 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);
|
if (process_res.has_value()) {
|
||||||
if (process_res.has_value()) {
|
return process_res.value();
|
||||||
return process_res.value();
|
} else {
|
||||||
} else {
|
return {};
|
||||||
return {};
|
}
|
||||||
}
|
},
|
||||||
},
|
element->IdleTimeout());
|
||||||
element->IdleTimeout());
|
|
||||||
|
|
||||||
if (chunk_res == CHUNK_PROCESSING_ERROR ||
|
if (chunk_res == CHUNK_PROCESSING_ERROR ||
|
||||||
chunk_res == CHUNK_DECODING_ERROR) {
|
chunk_res == CHUNK_DECODING_ERROR) {
|
||||||
break; // TODO.
|
break; // TODO.
|
||||||
} 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) {
|
||||||
|
|||||||
+17
-33
@@ -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 };
|
StreamBuffer stream_start_;
|
||||||
auto GetDecoderForFilename(std::string filename) const -> Decoder;
|
StreamBuffer stream_end_;
|
||||||
auto CreateDecoder(Decoder decoder) const -> audio_element_handle_t;
|
std::vector<std::unique_ptr<StreamBuffer>> element_buffers_;
|
||||||
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;
|
|
||||||
Decoder decoder_type_ = NONE;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // 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;
|
||||||
|
|||||||
+5
-16
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user