clean up a bunch of obselete audio code
This commit is contained in:
@@ -3,9 +3,8 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-only
|
# SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS "audio_task.cpp" "chunk.cpp" "fatfs_audio_input.cpp"
|
SRCS "audio_task.cpp" "fatfs_audio_input.cpp" "i2s_audio_output.cpp"
|
||||||
"stream_message.cpp" "i2s_audio_output.cpp" "stream_buffer.cpp" "track_queue.cpp"
|
"track_queue.cpp" "audio_fsm.cpp" "sink_mixer.cpp" "resample.cpp"
|
||||||
"stream_event.cpp" "stream_info.cpp" "audio_fsm.cpp" "sink_mixer.cpp" "resample.cpp"
|
|
||||||
"fatfs_source.cpp" "bt_audio_output.cpp"
|
"fatfs_source.cpp" "bt_audio_output.cpp"
|
||||||
INCLUDE_DIRS "include"
|
INCLUDE_DIRS "include"
|
||||||
REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span" "memory" "tinyfsm"
|
REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span" "memory" "tinyfsm"
|
||||||
|
|||||||
@@ -1,208 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "audio_decoder.hpp"
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
#include "codec.hpp"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
|
|
||||||
#include "esp_heap_caps.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include "freertos/message_buffer.h"
|
|
||||||
#include "freertos/portmacro.h"
|
|
||||||
|
|
||||||
#include "audio_element.hpp"
|
|
||||||
#include "chunk.hpp"
|
|
||||||
#include "fatfs_audio_input.hpp"
|
|
||||||
#include "stream_info.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
|
||||||
|
|
||||||
static const char* kTag = "DEC";
|
|
||||||
|
|
||||||
AudioDecoder::AudioDecoder()
|
|
||||||
: IAudioElement(),
|
|
||||||
current_codec_(),
|
|
||||||
current_input_format_(),
|
|
||||||
current_output_format_(),
|
|
||||||
has_prepared_output_(false),
|
|
||||||
has_samples_to_send_(false),
|
|
||||||
has_input_remaining_(false) {}
|
|
||||||
|
|
||||||
AudioDecoder::~AudioDecoder() {}
|
|
||||||
|
|
||||||
auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info) -> bool {
|
|
||||||
has_prepared_output_ = false;
|
|
||||||
current_codec_.reset();
|
|
||||||
current_input_format_.reset();
|
|
||||||
current_output_format_.reset();
|
|
||||||
|
|
||||||
if (!std::holds_alternative<StreamInfo::Encoded>(info.format)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& new_format = std::get<StreamInfo::Encoded>(info.format);
|
|
||||||
current_input_format_ = new_format;
|
|
||||||
|
|
||||||
ESP_LOGI(kTag, "creating new decoder");
|
|
||||||
auto result = codecs::CreateCodecForType(new_format.type);
|
|
||||||
if (result.has_value()) {
|
|
||||||
current_codec_.reset(result.value());
|
|
||||||
} else {
|
|
||||||
ESP_LOGE(kTag, "no codec for this file");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto AudioDecoder::NeedsToProcess() const -> bool {
|
|
||||||
return has_samples_to_send_ || has_input_remaining_;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto AudioDecoder::Process(const std::vector<InputStream>& inputs,
|
|
||||||
OutputStream* output) -> void {
|
|
||||||
auto input = inputs.begin();
|
|
||||||
const StreamInfo& info = input->info();
|
|
||||||
|
|
||||||
// Is this a completely new stream?
|
|
||||||
if (!current_input_format_) {
|
|
||||||
if (!ProcessStreamInfo(info)) {
|
|
||||||
// We couldn't handle the new stream. Signal to the producer that we don't
|
|
||||||
// have anything to do.
|
|
||||||
input->mark_consumer_finished();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Have we determined what kind of samples this stream decodes to?
|
|
||||||
if (!current_output_format_) {
|
|
||||||
auto res = current_codec_->BeginStream(input->data());
|
|
||||||
input->consume(res.first);
|
|
||||||
|
|
||||||
if (res.second.has_error()) {
|
|
||||||
auto err = res.second.error();
|
|
||||||
if (err == codecs::ICodec::Error::kOutOfInput) {
|
|
||||||
// We didn't manage to clear whatever front matter is before this
|
|
||||||
// stream's header. We need to call BeginStream again with more data.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Somthing about the stream's header was malformed. Skip it.
|
|
||||||
ESP_LOGE(kTag, "error beginning stream");
|
|
||||||
input->mark_consumer_finished();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The stream started successfully. Record what format the samples are in.
|
|
||||||
codecs::ICodec::OutputFormat format = res.second.value();
|
|
||||||
current_output_format_ = StreamInfo::Pcm{
|
|
||||||
.channels = format.num_channels,
|
|
||||||
.bits_per_sample = format.bits_per_sample,
|
|
||||||
.sample_rate = format.sample_rate_hz,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (format.duration_seconds) {
|
|
||||||
duration_seconds_from_decoder_ = format.duration_seconds;
|
|
||||||
} else if (format.bits_per_second &&
|
|
||||||
current_input_format_->duration_bytes) {
|
|
||||||
duration_seconds_from_decoder_ =
|
|
||||||
(current_input_format_->duration_bytes.value() - res.first) * 8 /
|
|
||||||
format.bits_per_second.value() / format.num_channels;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.seek_to_seconds) {
|
|
||||||
seek_to_sample_ = *info.seek_to_seconds * format.sample_rate_hz;
|
|
||||||
} else {
|
|
||||||
seek_to_sample_.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seek_to_sample_) {
|
|
||||||
ESP_LOGI(kTag, "seeking forwards...");
|
|
||||||
auto res = current_codec_->SeekStream(input->data(), *seek_to_sample_);
|
|
||||||
input->consume(res.first);
|
|
||||||
|
|
||||||
if (res.second.has_error()) {
|
|
||||||
auto err = res.second.error();
|
|
||||||
if (err == codecs::ICodec::Error::kOutOfInput) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// TODO(jacqueline): Handle errors.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
seek_to_sample_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
has_input_remaining_ = true;
|
|
||||||
while (true) {
|
|
||||||
// Make sure the output buffer is ready to receive samples in our format
|
|
||||||
// before starting to process data.
|
|
||||||
// TODO(jacqueline): Pass through seek info here?
|
|
||||||
if (!has_prepared_output_ && !output->prepare(*current_output_format_)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (duration_seconds_from_decoder_) {
|
|
||||||
output->set_duration(*duration_seconds_from_decoder_);
|
|
||||||
}
|
|
||||||
has_prepared_output_ = true;
|
|
||||||
|
|
||||||
// Parse frames and produce samples.
|
|
||||||
auto res = current_codec_->ContinueStream(input->data(), output->data());
|
|
||||||
input->consume(res.first);
|
|
||||||
|
|
||||||
// Handle any errors during processing.
|
|
||||||
if (res.second.has_error()) {
|
|
||||||
// The codec ran out of input during processing. This is expected to
|
|
||||||
// happen throughout the stream.
|
|
||||||
if (res.second.error() == codecs::ICodec::Error::kOutOfInput) {
|
|
||||||
has_input_remaining_ = false;
|
|
||||||
has_samples_to_send_ = false;
|
|
||||||
if (input->is_producer_finished()) {
|
|
||||||
// We're out of data, and so is the producer. Nothing left to be done
|
|
||||||
// with the input stream.
|
|
||||||
input->mark_consumer_finished();
|
|
||||||
|
|
||||||
// Upstream isn't going to give us any more data. Tell downstream
|
|
||||||
// that they shouldn't expact any more samples from this stream.
|
|
||||||
output->mark_producer_finished();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO(jacqueline): Handle errors.
|
|
||||||
ESP_LOGE(kTag, "codec returned fatal error");
|
|
||||||
}
|
|
||||||
// Note that a codec that returns an error is not allowed to write
|
|
||||||
// samples. So it's safe to skip the latter part of the loop.
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// Some samples were written! Ensure the downstream element knows about
|
|
||||||
// them.
|
|
||||||
codecs::ICodec::OutputInfo out_info = res.second.value();
|
|
||||||
output->add(out_info.bytes_written);
|
|
||||||
has_samples_to_send_ = !out_info.is_finished_writing;
|
|
||||||
|
|
||||||
if (has_samples_to_send_) {
|
|
||||||
// The codec wasn't able to finish writing all of its samples into the
|
|
||||||
// output buffer. We need to return so that we can get a new buffer.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
current_codec_.reset();
|
|
||||||
current_input_format_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace audio
|
|
||||||
@@ -5,23 +5,24 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "audio_fsm.hpp"
|
#include "audio_fsm.hpp"
|
||||||
|
|
||||||
#include <future>
|
#include <future>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include "audio_decoder.hpp"
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/portmacro.h"
|
||||||
|
#include "freertos/projdefs.h"
|
||||||
|
|
||||||
#include "audio_events.hpp"
|
#include "audio_events.hpp"
|
||||||
#include "audio_task.hpp"
|
#include "audio_task.hpp"
|
||||||
#include "bluetooth.hpp"
|
#include "bluetooth.hpp"
|
||||||
#include "bt_audio_output.hpp"
|
#include "bt_audio_output.hpp"
|
||||||
#include "esp_log.h"
|
|
||||||
#include "event_queue.hpp"
|
#include "event_queue.hpp"
|
||||||
#include "fatfs_audio_input.hpp"
|
#include "fatfs_audio_input.hpp"
|
||||||
#include "freertos/portmacro.h"
|
|
||||||
#include "freertos/projdefs.h"
|
|
||||||
#include "future_fetcher.hpp"
|
#include "future_fetcher.hpp"
|
||||||
#include "i2s_audio_output.hpp"
|
#include "i2s_audio_output.hpp"
|
||||||
#include "i2s_dac.hpp"
|
#include "i2s_dac.hpp"
|
||||||
#include "pipeline.hpp"
|
|
||||||
#include "system_events.hpp"
|
#include "system_events.hpp"
|
||||||
#include "track.hpp"
|
#include "track.hpp"
|
||||||
#include "track_queue.hpp"
|
#include "track_queue.hpp"
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
#include "audio_task.hpp"
|
#include "audio_task.hpp"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <cstdint>
|
||||||
#include <sys/_stdint.h>
|
#include <cstdlib>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@@ -28,13 +28,10 @@
|
|||||||
#include "freertos/ringbuf.h"
|
#include "freertos/ringbuf.h"
|
||||||
#include "span.hpp"
|
#include "span.hpp"
|
||||||
|
|
||||||
#include "audio_decoder.hpp"
|
|
||||||
#include "audio_element.hpp"
|
|
||||||
#include "audio_events.hpp"
|
#include "audio_events.hpp"
|
||||||
#include "audio_fsm.hpp"
|
#include "audio_fsm.hpp"
|
||||||
#include "audio_sink.hpp"
|
#include "audio_sink.hpp"
|
||||||
#include "audio_source.hpp"
|
#include "audio_source.hpp"
|
||||||
#include "chunk.hpp"
|
|
||||||
#include "codec.hpp"
|
#include "codec.hpp"
|
||||||
#include "event_queue.hpp"
|
#include "event_queue.hpp"
|
||||||
#include "fatfs_audio_input.hpp"
|
#include "fatfs_audio_input.hpp"
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "bt_audio_output.hpp"
|
#include "bt_audio_output.hpp"
|
||||||
#include <stdint.h>
|
|
||||||
#include <sys/_stdint.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
@@ -17,14 +15,12 @@
|
|||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
#include "esp_heap_caps.h"
|
#include "esp_heap_caps.h"
|
||||||
#include "freertos/portmacro.h"
|
#include "freertos/portmacro.h"
|
||||||
|
|
||||||
#include "audio_element.hpp"
|
|
||||||
#include "freertos/projdefs.h"
|
#include "freertos/projdefs.h"
|
||||||
|
|
||||||
#include "gpios.hpp"
|
#include "gpios.hpp"
|
||||||
#include "i2c.hpp"
|
#include "i2c.hpp"
|
||||||
#include "i2s_dac.hpp"
|
#include "i2s_dac.hpp"
|
||||||
#include "result.hpp"
|
#include "result.hpp"
|
||||||
#include "stream_info.hpp"
|
|
||||||
#include "wm8523.hpp"
|
#include "wm8523.hpp"
|
||||||
|
|
||||||
static const char* kTag = "BTOUT";
|
static const char* kTag = "BTOUT";
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "chunk.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
#include "cbor.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
|
|
||||||
#include "stream_buffer.hpp"
|
|
||||||
#include "stream_message.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
|
||||||
|
|
||||||
static const std::size_t kWorkingBufferMultiple = 2;
|
|
||||||
|
|
||||||
ChunkReader::ChunkReader(std::size_t chunk_size)
|
|
||||||
: raw_working_buffer_(static_cast<std::byte*>(
|
|
||||||
heap_caps_malloc(chunk_size * kWorkingBufferMultiple,
|
|
||||||
MALLOC_CAP_SPIRAM))),
|
|
||||||
working_buffer_(raw_working_buffer_,
|
|
||||||
chunk_size * kWorkingBufferMultiple) {}
|
|
||||||
|
|
||||||
ChunkReader::~ChunkReader() {
|
|
||||||
free(raw_working_buffer_);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ChunkReader::HandleNewData(cpp::span<std::byte> data)
|
|
||||||
-> cpp::span<std::byte> {
|
|
||||||
assert(leftover_bytes_ + data.size() <= working_buffer_.size());
|
|
||||||
// Copy the new data onto the front for anything that was left over from the
|
|
||||||
// last portion. Note: this could be optimised for the '0 leftover bytes'
|
|
||||||
// case, which technically shouldn't need a copy.
|
|
||||||
std::copy(data.begin(), data.end(),
|
|
||||||
working_buffer_.begin() + leftover_bytes_);
|
|
||||||
last_data_in_working_buffer_ =
|
|
||||||
working_buffer_.first(leftover_bytes_ + data.size());
|
|
||||||
leftover_bytes_ = 0;
|
|
||||||
return last_data_in_working_buffer_;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ChunkReader::HandleBytesUsed(std::size_t bytes_used) -> void {
|
|
||||||
HandleBytesLeftOver(last_data_in_working_buffer_.size() - bytes_used);
|
|
||||||
}
|
|
||||||
auto ChunkReader::HandleBytesLeftOver(std::size_t bytes_left) -> void {
|
|
||||||
leftover_bytes_ = bytes_left;
|
|
||||||
|
|
||||||
// Ensure that we don't have more than a chunk of leftever bytes. This is
|
|
||||||
// bad, because we probably won't have enough data to store the next chunk.
|
|
||||||
assert(leftover_bytes_ <= working_buffer_.size() / kWorkingBufferMultiple);
|
|
||||||
|
|
||||||
if (leftover_bytes_ > 0) {
|
|
||||||
auto data_to_keep = last_data_in_working_buffer_.last(leftover_bytes_);
|
|
||||||
std::copy(data_to_keep.begin(), data_to_keep.end(),
|
|
||||||
working_buffer_.begin());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ChunkReader::GetLeftovers() -> cpp::span<std::byte> {
|
|
||||||
return working_buffer_.first(leftover_bytes_);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace audio
|
|
||||||
@@ -6,8 +6,6 @@
|
|||||||
|
|
||||||
#include "fatfs_audio_input.hpp"
|
#include "fatfs_audio_input.hpp"
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
@@ -19,22 +17,21 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include "codec.hpp"
|
|
||||||
#include "esp_heap_caps.h"
|
#include "esp_heap_caps.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "fatfs_source.hpp"
|
|
||||||
#include "ff.h"
|
#include "ff.h"
|
||||||
|
#include "freertos/portmacro.h"
|
||||||
|
#include "freertos/projdefs.h"
|
||||||
|
#include "idf_additions.h"
|
||||||
|
#include "span.hpp"
|
||||||
|
|
||||||
#include "audio_events.hpp"
|
#include "audio_events.hpp"
|
||||||
#include "audio_fsm.hpp"
|
#include "audio_fsm.hpp"
|
||||||
#include "audio_source.hpp"
|
#include "audio_source.hpp"
|
||||||
|
#include "codec.hpp"
|
||||||
#include "event_queue.hpp"
|
#include "event_queue.hpp"
|
||||||
#include "freertos/portmacro.h"
|
#include "fatfs_source.hpp"
|
||||||
#include "freertos/projdefs.h"
|
|
||||||
#include "future_fetcher.hpp"
|
#include "future_fetcher.hpp"
|
||||||
#include "idf_additions.h"
|
|
||||||
#include "span.hpp"
|
|
||||||
#include "stream_info.hpp"
|
|
||||||
#include "tag_parser.hpp"
|
#include "tag_parser.hpp"
|
||||||
#include "tasks.hpp"
|
#include "tasks.hpp"
|
||||||
#include "types.hpp"
|
#include "types.hpp"
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fatfs_source.hpp"
|
#include "fatfs_source.hpp"
|
||||||
#include <sys/_stdint.h>
|
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "i2s_audio_output.hpp"
|
#include "i2s_audio_output.hpp"
|
||||||
#include <stdint.h>
|
|
||||||
#include <sys/_stdint.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
@@ -14,18 +12,16 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include "audio_sink.hpp"
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
#include "esp_heap_caps.h"
|
#include "esp_heap_caps.h"
|
||||||
#include "freertos/portmacro.h"
|
#include "freertos/portmacro.h"
|
||||||
|
|
||||||
#include "audio_element.hpp"
|
|
||||||
#include "freertos/projdefs.h"
|
#include "freertos/projdefs.h"
|
||||||
|
|
||||||
|
#include "audio_sink.hpp"
|
||||||
#include "gpios.hpp"
|
#include "gpios.hpp"
|
||||||
#include "i2c.hpp"
|
#include "i2c.hpp"
|
||||||
#include "i2s_dac.hpp"
|
#include "i2s_dac.hpp"
|
||||||
#include "result.hpp"
|
#include "result.hpp"
|
||||||
#include "stream_info.hpp"
|
|
||||||
#include "wm8523.hpp"
|
#include "wm8523.hpp"
|
||||||
|
|
||||||
static const char* kTag = "I2SOUT";
|
static const char* kTag = "I2SOUT";
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
namespace drivers {
|
|
||||||
|
|
||||||
class IAudioBackend {
|
|
||||||
public:
|
|
||||||
virtual ~IAudioBackend() {}
|
|
||||||
|
|
||||||
enum SampleRate {};
|
|
||||||
enum BitDepth {};
|
|
||||||
|
|
||||||
virtual auto Configure(SampleRate sample_rate, BitDepth bit_depth)
|
|
||||||
-> bool = 0;
|
|
||||||
virtual auto WritePcmData(uint8_t* data, size_t length) -> bool = 0;
|
|
||||||
|
|
||||||
virtual auto SetVolume(uint8_t percent) -> void = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace drivers
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "chunk.hpp"
|
|
||||||
#include "ff.h"
|
|
||||||
#include "span.hpp"
|
|
||||||
|
|
||||||
#include "audio_element.hpp"
|
|
||||||
#include "codec.hpp"
|
|
||||||
#include "stream_info.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* An audio element that accepts various kinds of encoded audio streams as
|
|
||||||
* input, and converts them to uncompressed PCM output.
|
|
||||||
*/
|
|
||||||
class AudioDecoder {
|
|
||||||
public:
|
|
||||||
AudioDecoder();
|
|
||||||
~AudioDecoder();
|
|
||||||
|
|
||||||
auto Process(const InputStream& input, OutputStream* output) -> void;
|
|
||||||
|
|
||||||
AudioDecoder(const AudioDecoder&) = delete;
|
|
||||||
AudioDecoder& operator=(const AudioDecoder&) = delete;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::unique_ptr<codecs::ICodec> current_codec_;
|
|
||||||
std::optional<StreamInfo::Encoded> current_input_format_;
|
|
||||||
std::optional<StreamInfo::Format> current_output_format_;
|
|
||||||
std::optional<std::size_t> duration_seconds_from_decoder_;
|
|
||||||
std::optional<std::size_t> seek_to_sample_;
|
|
||||||
bool has_prepared_output_;
|
|
||||||
bool has_samples_to_send_;
|
|
||||||
bool has_input_remaining_;
|
|
||||||
|
|
||||||
auto ProcessStreamInfo(const StreamInfo& info) -> bool;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace audio
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <deque>
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
|
|
||||||
#include "chunk.hpp"
|
|
||||||
#include "freertos/message_buffer.h"
|
|
||||||
#include "freertos/portmacro.h"
|
|
||||||
#include "result.hpp"
|
|
||||||
#include "span.hpp"
|
|
||||||
|
|
||||||
#include "stream_buffer.hpp"
|
|
||||||
#include "stream_event.hpp"
|
|
||||||
#include "stream_info.hpp"
|
|
||||||
#include "types.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
|
||||||
|
|
||||||
static const size_t kEventQueueSize = 8;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* One indepedentent part of an audio pipeline. Each element has an input and
|
|
||||||
* output message stream, and is responsible for taking data from the input
|
|
||||||
* stream, applying some kind of transformation to it, then sending the result
|
|
||||||
* out via the output stream. All communication with an element should be done
|
|
||||||
* over these streams, as an element's methods are only safe to call from the
|
|
||||||
* task that owns it.
|
|
||||||
*
|
|
||||||
* Each element implementation will have its input stream automatically parsed
|
|
||||||
* by its owning task, and its various Process* methods will be invoked
|
|
||||||
* accordingly. Element implementations are responsible for managing their own
|
|
||||||
* writes to their output streams.
|
|
||||||
*/
|
|
||||||
class IAudioElement {
|
|
||||||
public:
|
|
||||||
IAudioElement() {}
|
|
||||||
virtual ~IAudioElement() {}
|
|
||||||
|
|
||||||
virtual auto NeedsToProcess() const -> bool = 0;
|
|
||||||
|
|
||||||
virtual auto Process(const std::vector<InputStream>& inputs,
|
|
||||||
OutputStream* output) -> void = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace audio
|
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "tinyfsm.hpp"
|
||||||
|
|
||||||
#include "audio_events.hpp"
|
#include "audio_events.hpp"
|
||||||
#include "audio_task.hpp"
|
#include "audio_task.hpp"
|
||||||
#include "bt_audio_output.hpp"
|
#include "bt_audio_output.hpp"
|
||||||
@@ -20,11 +22,9 @@
|
|||||||
#include "i2s_audio_output.hpp"
|
#include "i2s_audio_output.hpp"
|
||||||
#include "i2s_dac.hpp"
|
#include "i2s_dac.hpp"
|
||||||
#include "storage.hpp"
|
#include "storage.hpp"
|
||||||
#include "tag_parser.hpp"
|
|
||||||
#include "tinyfsm.hpp"
|
|
||||||
#include "track.hpp"
|
|
||||||
|
|
||||||
#include "system_events.hpp"
|
#include "system_events.hpp"
|
||||||
|
#include "tag_parser.hpp"
|
||||||
|
#include "track.hpp"
|
||||||
#include "track_queue.hpp"
|
#include "track_queue.hpp"
|
||||||
|
|
||||||
namespace audio {
|
namespace audio {
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "audio_common.h"
|
|
||||||
#include "audio_element.h"
|
|
||||||
#include "audio_element.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
|
||||||
|
|
||||||
class AudioOutput : IAudioElement {
|
|
||||||
public:
|
|
||||||
AudioOutput();
|
|
||||||
~AudioOutput();
|
|
||||||
|
|
||||||
auto GetAudioElement() -> audio_element_handle_t { return element_; }
|
|
||||||
|
|
||||||
auto SetInputBuffer(StreamBufferHandle_t buffer) -> void;
|
|
||||||
|
|
||||||
virtual auto Configure(audio_element_info_t& info) -> void = 0;
|
|
||||||
virtual auto SetSoftMute(bool enabled) -> void = 0;
|
|
||||||
|
|
||||||
private:
|
|
||||||
audio_element_handle_t element_;
|
|
||||||
uint8_t volume_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace audio
|
|
||||||
@@ -8,11 +8,10 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include "audio_element.hpp"
|
|
||||||
#include "esp_heap_caps.h"
|
#include "esp_heap_caps.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "idf_additions.h"
|
#include "idf_additions.h"
|
||||||
#include "stream_info.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
namespace audio {
|
||||||
|
|
||||||
|
|||||||
@@ -6,19 +6,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include <bitset>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/portmacro.h"
|
|
||||||
#include "freertos/semphr.h"
|
|
||||||
|
|
||||||
#include "codec.hpp"
|
#include "codec.hpp"
|
||||||
#include "stream_info.hpp"
|
|
||||||
#include "track.hpp"
|
|
||||||
#include "types.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
namespace audio {
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <sys/_stdint.h>
|
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "audio_decoder.hpp"
|
|
||||||
#include "audio_sink.hpp"
|
#include "audio_sink.hpp"
|
||||||
#include "audio_source.hpp"
|
#include "audio_source.hpp"
|
||||||
#include "codec.hpp"
|
#include "codec.hpp"
|
||||||
|
|||||||
@@ -6,20 +6,16 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <sys/_stdint.h>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "audio_element.hpp"
|
|
||||||
#include "audio_sink.hpp"
|
|
||||||
#include "bluetooth.hpp"
|
|
||||||
#include "chunk.hpp"
|
|
||||||
#include "result.hpp"
|
#include "result.hpp"
|
||||||
|
|
||||||
|
#include "audio_sink.hpp"
|
||||||
|
#include "bluetooth.hpp"
|
||||||
#include "gpios.hpp"
|
#include "gpios.hpp"
|
||||||
#include "i2s_dac.hpp"
|
#include "i2s_dac.hpp"
|
||||||
#include "stream_info.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
namespace audio {
|
||||||
|
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
|
|
||||||
#include "cbor.h"
|
|
||||||
#include "freertos/message_buffer.h"
|
|
||||||
#include "freertos/portmacro.h"
|
|
||||||
#include "freertos/queue.h"
|
|
||||||
#include "result.hpp"
|
|
||||||
#include "span.hpp"
|
|
||||||
#include "stream_buffer.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility for handling an input stream of chunk data, which simplifies needing
|
|
||||||
* access to blocks of data spanning two chunks.
|
|
||||||
*/
|
|
||||||
class ChunkReader {
|
|
||||||
public:
|
|
||||||
explicit ChunkReader(std::size_t chunk_size);
|
|
||||||
~ChunkReader();
|
|
||||||
|
|
||||||
auto HandleBytesLeftOver(std::size_t bytes_left) -> void;
|
|
||||||
auto HandleBytesUsed(std::size_t bytes_used) -> void;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Reads chunks of data from the given input stream, and invokes the given
|
|
||||||
* callback to process each of them in turn.
|
|
||||||
*
|
|
||||||
* The callback will be invoked with a byte buffer and its size. The callback
|
|
||||||
* should process as much data as it can from this buffer, and then return the
|
|
||||||
* number of bytes it was able to read. Any leftover bytes will be added as a
|
|
||||||
* prefix to the next chunk.
|
|
||||||
*
|
|
||||||
* If this function encounters a message in the stream that is not a chunk, it
|
|
||||||
* will place the message at the start of the working_buffer and then return.
|
|
||||||
*/
|
|
||||||
auto HandleNewData(cpp::span<std::byte> data) -> cpp::span<std::byte>;
|
|
||||||
|
|
||||||
auto GetLeftovers() -> cpp::span<std::byte>;
|
|
||||||
|
|
||||||
ChunkReader(const ChunkReader&) = delete;
|
|
||||||
ChunkReader& operator=(const ChunkReader&) = delete;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::byte* raw_working_buffer_;
|
|
||||||
cpp::span<std::byte> working_buffer_;
|
|
||||||
cpp::span<std::byte> last_data_in_working_buffer_;
|
|
||||||
std::size_t leftover_bytes_ = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace audio
|
|
||||||
@@ -12,13 +12,12 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "codec.hpp"
|
|
||||||
#include "ff.h"
|
#include "ff.h"
|
||||||
|
#include "freertos/portmacro.h"
|
||||||
|
|
||||||
#include "audio_source.hpp"
|
#include "audio_source.hpp"
|
||||||
#include "freertos/portmacro.h"
|
#include "codec.hpp"
|
||||||
#include "future_fetcher.hpp"
|
#include "future_fetcher.hpp"
|
||||||
#include "stream_info.hpp"
|
|
||||||
#include "tag_parser.hpp"
|
#include "tag_parser.hpp"
|
||||||
#include "types.hpp"
|
#include "types.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
/*
|
|
||||||
* FIR filter coefficients from resample-1.x smallfilter.h
|
|
||||||
* see Digital Audio Resampling Home Page located at
|
|
||||||
* http://ccrma.stanford.edu/~jos/resample/
|
|
||||||
*/
|
|
||||||
32767, 32766, 32764, 32760, 32755, 32749, 32741, 32731, 32721, 32708,
|
|
||||||
32695, 32679, 32663, 32645, 32625, 32604, 32582, 32558, 32533, 32506,
|
|
||||||
32478, 32448, 32417, 32385, 32351, 32316, 32279, 32241, 32202, 32161,
|
|
||||||
32119, 32075, 32030, 31984, 31936, 31887, 31836, 31784, 31731, 31676,
|
|
||||||
31620, 31563, 31504, 31444, 31383, 31320, 31256, 31191, 31124, 31056,
|
|
||||||
30987, 30916, 30845, 30771, 30697, 30621, 30544, 30466, 30387, 30306,
|
|
||||||
30224, 30141, 30057, 29971, 29884, 29796, 29707, 29617, 29525, 29433,
|
|
||||||
29339, 29244, 29148, 29050, 28952, 28852, 28752, 28650, 28547, 28443,
|
|
||||||
28338, 28232, 28125, 28017, 27908, 27797, 27686, 27574, 27461, 27346,
|
|
||||||
27231, 27115, 26998, 26879, 26760, 26640, 26519, 26398, 26275, 26151,
|
|
||||||
26027, 25901, 25775, 25648, 25520, 25391, 25262, 25131, 25000, 24868,
|
|
||||||
24735, 24602, 24467, 24332, 24197, 24060, 23923, 23785, 23647, 23507,
|
|
||||||
23368, 23227, 23086, 22944, 22802, 22659, 22515, 22371, 22226, 22081,
|
|
||||||
21935, 21789, 21642, 21494, 21346, 21198, 21049, 20900, 20750, 20600,
|
|
||||||
20449, 20298, 20146, 19995, 19842, 19690, 19537, 19383, 19230, 19076,
|
|
||||||
18922, 18767, 18612, 18457, 18302, 18146, 17990, 17834, 17678, 17521,
|
|
||||||
17365, 17208, 17051, 16894, 16737, 16579, 16422, 16264, 16106, 15949,
|
|
||||||
15791, 15633, 15475, 15317, 15159, 15001, 14843, 14685, 14527, 14369,
|
|
||||||
14212, 14054, 13896, 13739, 13581, 13424, 13266, 13109, 12952, 12795,
|
|
||||||
12639, 12482, 12326, 12170, 12014, 11858, 11703, 11548, 11393, 11238,
|
|
||||||
11084, 10929, 10776, 10622, 10469, 10316, 10164, 10011, 9860, 9708,
|
|
||||||
9557, 9407, 9256, 9106, 8957, 8808, 8659, 8511, 8364, 8216, 8070,
|
|
||||||
7924, 7778, 7633, 7488, 7344, 7200, 7057, 6914, 6773, 6631, 6490,
|
|
||||||
6350, 6210, 6071, 5933, 5795, 5658, 5521, 5385, 5250, 5115, 4981,
|
|
||||||
4848, 4716, 4584, 4452, 4322, 4192, 4063, 3935, 3807, 3680, 3554,
|
|
||||||
3429, 3304, 3180, 3057, 2935, 2813, 2692, 2572, 2453, 2335, 2217,
|
|
||||||
2101, 1985, 1870, 1755, 1642, 1529, 1418, 1307, 1197, 1088, 979, 872,
|
|
||||||
765, 660, 555, 451, 348, 246, 145, 44, -54, -153, -250, -347, -443,
|
|
||||||
-537, -631, -724, -816, -908, -998, -1087, -1175, -1263, -1349, -1435,
|
|
||||||
-1519, -1603, -1685, -1767, -1848, -1928, -2006, -2084, -2161, -2237,
|
|
||||||
-2312, -2386, -2459, -2531, -2603, -2673, -2742, -2810, -2878, -2944,
|
|
||||||
-3009, -3074, -3137, -3200, -3261, -3322, -3381, -3440, -3498, -3554,
|
|
||||||
-3610, -3665, -3719, -3772, -3824, -3875, -3925, -3974, -4022, -4069,
|
|
||||||
-4116, -4161, -4205, -4249, -4291, -4333, -4374, -4413, -4452, -4490,
|
|
||||||
-4527, -4563, -4599, -4633, -4666, -4699, -4730, -4761, -4791, -4820,
|
|
||||||
-4848, -4875, -4901, -4926, -4951, -4974, -4997, -5019, -5040, -5060,
|
|
||||||
-5080, -5098, -5116, -5133, -5149, -5164, -5178, -5192, -5205, -5217,
|
|
||||||
-5228, -5238, -5248, -5257, -5265, -5272, -5278, -5284, -5289, -5293,
|
|
||||||
-5297, -5299, -5301, -5303, -5303, -5303, -5302, -5300, -5298, -5295,
|
|
||||||
-5291, -5287, -5282, -5276, -5270, -5263, -5255, -5246, -5237, -5228,
|
|
||||||
-5217, -5206, -5195, -5183, -5170, -5157, -5143, -5128, -5113, -5097,
|
|
||||||
-5081, -5064, -5047, -5029, -5010, -4991, -4972, -4952, -4931, -4910,
|
|
||||||
-4889, -4867, -4844, -4821, -4797, -4774, -4749, -4724, -4699, -4673,
|
|
||||||
-4647, -4620, -4593, -4566, -4538, -4510, -4481, -4452, -4422, -4393,
|
|
||||||
-4363, -4332, -4301, -4270, -4238, -4206, -4174, -4142, -4109, -4076,
|
|
||||||
-4042, -4009, -3975, -3940, -3906, -3871, -3836, -3801, -3765, -3729,
|
|
||||||
-3693, -3657, -3620, -3584, -3547, -3510, -3472, -3435, -3397, -3360,
|
|
||||||
-3322, -3283, -3245, -3207, -3168, -3129, -3091, -3052, -3013, -2973,
|
|
||||||
-2934, -2895, -2855, -2816, -2776, -2736, -2697, -2657, -2617, -2577,
|
|
||||||
-2537, -2497, -2457, -2417, -2377, -2337, -2297, -2256, -2216, -2176,
|
|
||||||
-2136, -2096, -2056, -2016, -1976, -1936, -1896, -1856, -1817, -1777,
|
|
||||||
-1737, -1698, -1658, -1619, -1579, -1540, -1501, -1462, -1423, -1384,
|
|
||||||
-1345, -1306, -1268, -1230, -1191, -1153, -1115, -1077, -1040, -1002,
|
|
||||||
-965, -927, -890, -854, -817, -780, -744, -708, -672, -636, -600,
|
|
||||||
-565, -530, -494, -460, -425, -391, -356, -322, -289, -255, -222,
|
|
||||||
-189, -156, -123, -91, -59, -27, 4, 35, 66, 97, 127, 158, 188, 218,
|
|
||||||
247, 277, 306, 334, 363, 391, 419, 447, 474, 501, 528, 554, 581, 606,
|
|
||||||
632, 657, 683, 707, 732, 756, 780, 803, 827, 850, 872, 895, 917, 939,
|
|
||||||
960, 981, 1002, 1023, 1043, 1063, 1082, 1102, 1121, 1139, 1158, 1176,
|
|
||||||
1194, 1211, 1228, 1245, 1262, 1278, 1294, 1309, 1325, 1340, 1354,
|
|
||||||
1369, 1383, 1397, 1410, 1423, 1436, 1448, 1461, 1473, 1484, 1496,
|
|
||||||
1507, 1517, 1528, 1538, 1548, 1557, 1566, 1575, 1584, 1592, 1600,
|
|
||||||
1608, 1616, 1623, 1630, 1636, 1643, 1649, 1654, 1660, 1665, 1670,
|
|
||||||
1675, 1679, 1683, 1687, 1690, 1694, 1697, 1700, 1702, 1704, 1706,
|
|
||||||
1708, 1709, 1711, 1712, 1712, 1713, 1713, 1713, 1713, 1712, 1711,
|
|
||||||
1710, 1709, 1708, 1706, 1704, 1702, 1700, 1697, 1694, 1691, 1688,
|
|
||||||
1685, 1681, 1677, 1673, 1669, 1664, 1660, 1655, 1650, 1644, 1639,
|
|
||||||
1633, 1627, 1621, 1615, 1609, 1602, 1596, 1589, 1582, 1575, 1567,
|
|
||||||
1560, 1552, 1544, 1536, 1528, 1520, 1511, 1503, 1494, 1485, 1476,
|
|
||||||
1467, 1458, 1448, 1439, 1429, 1419, 1409, 1399, 1389, 1379, 1368,
|
|
||||||
1358, 1347, 1337, 1326, 1315, 1304, 1293, 1282, 1271, 1260, 1248,
|
|
||||||
1237, 1225, 1213, 1202, 1190, 1178, 1166, 1154, 1142, 1130, 1118,
|
|
||||||
1106, 1094, 1081, 1069, 1057, 1044, 1032, 1019, 1007, 994, 981, 969,
|
|
||||||
956, 943, 931, 918, 905, 892, 879, 867, 854, 841, 828, 815, 802, 790,
|
|
||||||
777, 764, 751, 738, 725, 713, 700, 687, 674, 662, 649, 636, 623, 611,
|
|
||||||
598, 585, 573, 560, 548, 535, 523, 510, 498, 486, 473, 461, 449, 437,
|
|
||||||
425, 413, 401, 389, 377, 365, 353, 341, 330, 318, 307, 295, 284, 272,
|
|
||||||
261, 250, 239, 228, 217, 206, 195, 184, 173, 163, 152, 141, 131, 121,
|
|
||||||
110, 100, 90, 80, 70, 60, 51, 41, 31, 22, 12, 3, -5, -14, -23, -32,
|
|
||||||
-41, -50, -59, -67, -76, -84, -93, -101, -109, -117, -125, -133, -140,
|
|
||||||
-148, -156, -163, -170, -178, -185, -192, -199, -206, -212, -219,
|
|
||||||
-226, -232, -239, -245, -251, -257, -263, -269, -275, -280, -286,
|
|
||||||
-291, -297, -302, -307, -312, -317, -322, -327, -332, -336, -341,
|
|
||||||
-345, -349, -354, -358, -362, -366, -369, -373, -377, -380, -384,
|
|
||||||
-387, -390, -394, -397, -400, -402, -405, -408, -411, -413, -416,
|
|
||||||
-418, -420, -422, -424, -426, -428, -430, -432, -433, -435, -436,
|
|
||||||
-438, -439, -440, -442, -443, -444, -445, -445, -446, -447, -447,
|
|
||||||
-448, -448, -449, -449, -449, -449, -449, -449, -449, -449, -449,
|
|
||||||
-449, -449, -448, -448, -447, -447, -446, -445, -444, -443, -443,
|
|
||||||
-442, -441, -440, -438, -437, -436, -435, -433, -432, -430, -429,
|
|
||||||
-427, -426, -424, -422, -420, -419, -417, -415, -413, -411, -409,
|
|
||||||
-407, -405, -403, -400, -398, -396, -393, -391, -389, -386, -384,
|
|
||||||
-381, -379, -376, -374, -371, -368, -366, -363, -360, -357, -355,
|
|
||||||
-352, -349, -346, -343, -340, -337, -334, -331, -328, -325, -322,
|
|
||||||
-319, -316, -313, -310, -307, -304, -301, -298, -294, -291, -288,
|
|
||||||
-285, -282, -278, -275, -272, -269, -265, -262, -259, -256, -252,
|
|
||||||
-249, -246, -243, -239, -236, -233, -230, -226, -223, -220, -217,
|
|
||||||
-213, -210, -207, -204, -200, -197, -194, -191, -187, -184, -181,
|
|
||||||
-178, -175, -172, -168, -165, -162, -159, -156, -153, -150, -147,
|
|
||||||
-143, -140, -137, -134, -131, -128, -125, -122, -120, -117, -114,
|
|
||||||
-111, -108, -105, -102, -99, -97, -94, -91, -88, -86, -83, -80, -78,
|
|
||||||
-75, -72, -70, -67, -65, -62, -59, -57, -55, -52, -50, -47, -45, -43,
|
|
||||||
-40, -38, -36, -33, -31, -29, -27, -25, -22, -20, -18, -16, -14, -12,
|
|
||||||
-10, -8, -6, -4, -2, 0, 0, 2, 4, 6, 8, 9, 11, 13, 14, 16, 17, 19, 21,
|
|
||||||
22, 24, 25, 27, 28, 29, 31, 32, 33, 35, 36, 37, 38, 40, 41, 42, 43,
|
|
||||||
44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 56, 57, 58, 59,
|
|
||||||
59, 60, 61, 62, 62, 63, 63, 64, 64, 65, 66, 66, 66, 67, 67, 68, 68,
|
|
||||||
69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 71, 72, 72, 72, 72, 72,
|
|
||||||
72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72,
|
|
||||||
72, 71, 71, 71, 71, 71, 70, 70, 70, 70, 69, 69, 69, 69, 68, 68, 68,
|
|
||||||
67, 67, 67, 66, 66, 66, 65, 65, 64, 64, 64, 63, 63, 62, 62, 62, 61,
|
|
||||||
61, 60, 60, 59, 59, 58, 58, 58, 57, 57, 56, 56, 55, 55, 54, 54, 53,
|
|
||||||
53, 52, 52, 51, 51, 50, 50, 49, 48, 48, 47, 47, 46, 46, 45, 45, 44,
|
|
||||||
44, 43, 43, 42, 42, 41, 41, 40, 39, 39, 38, 38, 37, 37, 36, 36, 35,
|
|
||||||
35, 34, 34, 33, 33, 32, 32, 31, 31, 30, 30, 29, 29, 28, 28, 27, 27,
|
|
||||||
26, 26, 25, 25, 24, 24, 23, 23, 23, 22, 22, 21, 21, 20, 20, 20, 19,
|
|
||||||
19, 18, 18, 17, 17, 17, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 12,
|
|
||||||
12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 9, 8, 8, 8, 7, 7, 7, 7, 6, 6,
|
|
||||||
6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1,
|
|
||||||
1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1,
|
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -2, -2,
|
|
||||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
|
|
||||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
|
|
||||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
|
|
||||||
-2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
||||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
||||||
@@ -6,19 +6,14 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <sys/_stdint.h>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "audio_element.hpp"
|
|
||||||
#include "audio_sink.hpp"
|
#include "audio_sink.hpp"
|
||||||
#include "chunk.hpp"
|
|
||||||
#include "result.hpp"
|
|
||||||
|
|
||||||
#include "gpios.hpp"
|
#include "gpios.hpp"
|
||||||
#include "i2s_dac.hpp"
|
#include "i2s_dac.hpp"
|
||||||
#include "stream_info.hpp"
|
#include "result.hpp"
|
||||||
|
|
||||||
namespace audio {
|
namespace audio {
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "freertos/portmacro.h"
|
|
||||||
|
|
||||||
#include "audio_element.hpp"
|
|
||||||
#include "stream_info.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
|
||||||
|
|
||||||
static const std::size_t kPipelineBufferSize = 64 * 1024;
|
|
||||||
|
|
||||||
class Pipeline {
|
|
||||||
public:
|
|
||||||
explicit Pipeline(IAudioElement* output);
|
|
||||||
~Pipeline();
|
|
||||||
auto AddInput(IAudioElement* input) -> Pipeline*;
|
|
||||||
|
|
||||||
auto OutputElement() const -> IAudioElement*;
|
|
||||||
|
|
||||||
auto NumInputs() const -> std::size_t;
|
|
||||||
|
|
||||||
auto InStreams(std::vector<RawStream>*) -> void;
|
|
||||||
|
|
||||||
auto OutStream() -> RawStream;
|
|
||||||
|
|
||||||
auto GetIterationOrder() -> std::vector<Pipeline*>;
|
|
||||||
|
|
||||||
// Not copyable or movable.
|
|
||||||
Pipeline(const Pipeline&) = delete;
|
|
||||||
Pipeline& operator=(const Pipeline&) = delete;
|
|
||||||
|
|
||||||
private:
|
|
||||||
IAudioElement* root_;
|
|
||||||
std::vector<std::unique_ptr<Pipeline>> subtrees_;
|
|
||||||
|
|
||||||
std::array<std::byte, kPipelineBufferSize> output_buffer_;
|
|
||||||
StreamInfo output_info_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace audio
|
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <sys/_stdint.h>
|
#include <cstdint>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "span.hpp"
|
#include "span.hpp"
|
||||||
|
|||||||
@@ -6,19 +6,15 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <sys/_stdint.h>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "resample.hpp"
|
#include "resample.hpp"
|
||||||
#include "sample.hpp"
|
#include "sample.hpp"
|
||||||
|
|
||||||
#include "audio_decoder.hpp"
|
|
||||||
#include "audio_sink.hpp"
|
#include "audio_sink.hpp"
|
||||||
#include "audio_source.hpp"
|
#include "audio_source.hpp"
|
||||||
#include "codec.hpp"
|
#include "codec.hpp"
|
||||||
#include "pipeline.hpp"
|
|
||||||
#include "stream_info.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
namespace audio {
|
||||||
|
|
||||||
@@ -38,7 +34,7 @@ class SinkMixer {
|
|||||||
private:
|
private:
|
||||||
auto Main() -> void;
|
auto Main() -> void;
|
||||||
|
|
||||||
auto SetTargetFormat(const StreamInfo::Pcm& format) -> void;
|
auto SetTargetFormat(const IAudioSink::Format& format) -> void;
|
||||||
auto HandleSamples(cpp::span<sample::Sample>, bool) -> size_t;
|
auto HandleSamples(cpp::span<sample::Sample>, bool) -> size_t;
|
||||||
|
|
||||||
struct Args {
|
struct Args {
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
|
|
||||||
#include "freertos/message_buffer.h"
|
|
||||||
#include "span.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A collection of the buffers required for two IAudioElement implementations to
|
|
||||||
* stream data between each other.
|
|
||||||
*
|
|
||||||
* Currently, we use a FreeRTOS MessageBuffer to hold the byte stream, and also
|
|
||||||
* maintain two chunk-sized buffers for the elements to stage their read and
|
|
||||||
* write operations (as MessageBuffer copies the given data into its memory
|
|
||||||
* space). A future optimisation here could be to instead post himem memory
|
|
||||||
* addresses to the message buffer, and then maintain address spaces into which
|
|
||||||
* we map these messages, rather than 'real' allocated buffers as we do now.
|
|
||||||
*/
|
|
||||||
class StreamBuffer {
|
|
||||||
public:
|
|
||||||
explicit StreamBuffer(std::size_t chunk_size, std::size_t buffer_size);
|
|
||||||
~StreamBuffer();
|
|
||||||
|
|
||||||
/* Returns the handle for the underlying message buffer. */
|
|
||||||
auto Handle() -> MessageBufferHandle_t* { return &handle_; }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns a chunk-sized staging buffer that should be used *only* by the
|
|
||||||
* reader (sink) element.
|
|
||||||
*/
|
|
||||||
auto ReadBuffer() -> cpp::span<std::byte> { return input_chunk_; }
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns a chunk-sized staging buffer that should be used *only* by the
|
|
||||||
* writer (source) element.
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "arena.hpp"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/queue.h"
|
|
||||||
|
|
||||||
#include "span.hpp"
|
|
||||||
#include "stream_info.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
|
||||||
|
|
||||||
struct StreamEvent {
|
|
||||||
static auto CreateStreamInfo(QueueHandle_t source, const StreamInfo& payload)
|
|
||||||
-> StreamEvent*;
|
|
||||||
static auto CreateArenaChunk(QueueHandle_t source, memory::ArenaPtr ptr)
|
|
||||||
-> StreamEvent*;
|
|
||||||
static auto CreateChunkNotification(QueueHandle_t source) -> StreamEvent*;
|
|
||||||
static auto CreateEndOfStream(QueueHandle_t source) -> StreamEvent*;
|
|
||||||
static auto CreateLogStatus() -> StreamEvent*;
|
|
||||||
|
|
||||||
StreamEvent();
|
|
||||||
~StreamEvent();
|
|
||||||
StreamEvent(StreamEvent&&);
|
|
||||||
|
|
||||||
QueueHandle_t source;
|
|
||||||
|
|
||||||
enum {
|
|
||||||
UNINITIALISED,
|
|
||||||
STREAM_INFO,
|
|
||||||
ARENA_CHUNK,
|
|
||||||
CHUNK_NOTIFICATION,
|
|
||||||
END_OF_STREAM,
|
|
||||||
LOG_STATUS,
|
|
||||||
} tag;
|
|
||||||
|
|
||||||
union {
|
|
||||||
StreamInfo* stream_info;
|
|
||||||
|
|
||||||
memory::ArenaPtr arena_chunk;
|
|
||||||
|
|
||||||
// FIXME: It would be nice to also support a pointer to himem data here, to
|
|
||||||
// save a little ordinary heap space.
|
|
||||||
};
|
|
||||||
|
|
||||||
StreamEvent(const StreamEvent&) = delete;
|
|
||||||
StreamEvent& operator=(const StreamEvent&) = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace audio
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <sys/_stdint.h>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <utility>
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
#include "esp_heap_caps.h"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/ringbuf.h"
|
|
||||||
#include "freertos/stream_buffer.h"
|
|
||||||
|
|
||||||
#include "result.hpp"
|
|
||||||
#include "span.hpp"
|
|
||||||
#include "types.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
|
||||||
|
|
||||||
class StreamInfo {
|
|
||||||
public:
|
|
||||||
StreamInfo() : bytes_in_stream_(0), total_length_bytes_(), format_() {}
|
|
||||||
|
|
||||||
// The number of bytes that are available for consumption within this
|
|
||||||
// stream's buffer.
|
|
||||||
auto bytes_in_stream() -> std::size_t& { return bytes_in_stream_; }
|
|
||||||
auto bytes_in_stream() const -> std::size_t { return bytes_in_stream_; }
|
|
||||||
|
|
||||||
auto total_length_bytes() -> std::optional<std::uint32_t>& {
|
|
||||||
return total_length_bytes_;
|
|
||||||
}
|
|
||||||
auto total_length_bytes() const -> std::optional<std::uint32_t> {
|
|
||||||
return total_length_bytes_;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto total_length_seconds() -> std::optional<std::uint32_t>& {
|
|
||||||
return total_length_seconds_;
|
|
||||||
}
|
|
||||||
auto total_length_seconds() const -> std::optional<std::uint32_t> {
|
|
||||||
return total_length_seconds_;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Encoded {
|
|
||||||
// The codec that this stream is associated with.
|
|
||||||
codecs::StreamType type;
|
|
||||||
|
|
||||||
bool operator==(const Encoded&) const = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Two-channel, interleaved, 32-bit floating point pcm samples.
|
|
||||||
*/
|
|
||||||
struct FloatingPointPcm {
|
|
||||||
// Number of channels in this stream.
|
|
||||||
uint8_t channels;
|
|
||||||
// The sample rate.
|
|
||||||
uint32_t sample_rate;
|
|
||||||
|
|
||||||
bool operator==(const FloatingPointPcm&) const = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Pcm {
|
|
||||||
// Number of channels in this stream.
|
|
||||||
uint8_t channels;
|
|
||||||
// Number of bits per sample.
|
|
||||||
uint8_t bits_per_sample;
|
|
||||||
// The sample rate.
|
|
||||||
uint32_t sample_rate;
|
|
||||||
|
|
||||||
auto bytes_per_sample() const -> uint8_t {
|
|
||||||
return bits_per_sample == 16 ? 2 : 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const Pcm&) const = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef std::variant<std::monostate, Encoded, FloatingPointPcm, Pcm> Format;
|
|
||||||
auto format() const -> const Format& { return format_; }
|
|
||||||
auto set_format(Format f) -> void { format_ = f; }
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
auto format_as() const -> std::optional<T> {
|
|
||||||
if (std::holds_alternative<T>(format_)) {
|
|
||||||
return std::get<T>(format_);
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const StreamInfo&) const = default;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::size_t bytes_in_stream_;
|
|
||||||
std::optional<std::uint32_t> total_length_bytes_;
|
|
||||||
std::optional<std::uint32_t> total_length_seconds_;
|
|
||||||
Format format_{};
|
|
||||||
};
|
|
||||||
|
|
||||||
class InputStream;
|
|
||||||
class OutputStream;
|
|
||||||
|
|
||||||
class RawStream {
|
|
||||||
public:
|
|
||||||
explicit RawStream(std::size_t size);
|
|
||||||
RawStream(std::size_t size, uint32_t);
|
|
||||||
~RawStream();
|
|
||||||
|
|
||||||
auto info() -> StreamInfo& { return info_; }
|
|
||||||
auto data() -> cpp::span<std::byte>;
|
|
||||||
template <typename T>
|
|
||||||
auto data_as() -> cpp::span<T> {
|
|
||||||
auto orig = data();
|
|
||||||
return {reinterpret_cast<T*>(orig.data()), orig.size_bytes() / sizeof(T)};
|
|
||||||
}
|
|
||||||
auto empty() const -> bool { return info_.bytes_in_stream() == 0; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
StreamInfo info_;
|
|
||||||
std::size_t buffer_size_;
|
|
||||||
std::byte* buffer_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class InputStream {
|
|
||||||
public:
|
|
||||||
explicit InputStream(RawStream* s) : raw_(s) {}
|
|
||||||
|
|
||||||
void consume(std::size_t bytes) const;
|
|
||||||
|
|
||||||
const StreamInfo& info() const;
|
|
||||||
|
|
||||||
cpp::span<const std::byte> data() const;
|
|
||||||
template <typename T>
|
|
||||||
auto data_as() const -> cpp::span<const T> {
|
|
||||||
auto orig = data();
|
|
||||||
return {reinterpret_cast<const T*>(orig.data()),
|
|
||||||
orig.size_bytes() / sizeof(T)};
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
RawStream* raw_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class OutputStream {
|
|
||||||
public:
|
|
||||||
explicit OutputStream(RawStream* s) : raw_(s) {}
|
|
||||||
|
|
||||||
void add(std::size_t bytes) const;
|
|
||||||
|
|
||||||
void prepare(const StreamInfo::Format& new_format,
|
|
||||||
std::optional<uint32_t> length);
|
|
||||||
|
|
||||||
const StreamInfo& info() const;
|
|
||||||
|
|
||||||
cpp::span<std::byte> data() const;
|
|
||||||
template <typename T>
|
|
||||||
auto data_as() const -> cpp::span<T> {
|
|
||||||
auto orig = data();
|
|
||||||
return {reinterpret_cast<T*>(orig.data()), orig.size_bytes() / sizeof(T)};
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
RawStream* raw_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace audio
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <functional>
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
#include "cbor.h"
|
|
||||||
#include "result.hpp"
|
|
||||||
#include "span.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
|
||||||
|
|
||||||
extern const int kEncoderFlags;
|
|
||||||
extern const int kDecoderFlags;
|
|
||||||
|
|
||||||
enum MessageType {
|
|
||||||
TYPE_UNKNOWN,
|
|
||||||
TYPE_CHUNK_HEADER,
|
|
||||||
TYPE_STREAM_INFO,
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Writer>
|
|
||||||
auto WriteMessage(MessageType type, Writer&& writer, cpp::span<std::byte> data)
|
|
||||||
-> cpp::result<size_t, CborError> {
|
|
||||||
CborEncoder root;
|
|
||||||
CborEncoder container;
|
|
||||||
uint8_t* cast_data = reinterpret_cast<uint8_t*>(data.data());
|
|
||||||
|
|
||||||
cbor_encoder_init(&root, cast_data, data.size(), kEncoderFlags);
|
|
||||||
cbor_encoder_create_array(&root, &container, 2);
|
|
||||||
cbor_encode_uint(&container, type);
|
|
||||||
|
|
||||||
std::optional<CborError> inner_err = std::invoke(writer, container);
|
|
||||||
if (inner_err) {
|
|
||||||
return cpp::fail(inner_err.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
cbor_encoder_close_container(&root, &container);
|
|
||||||
return cbor_encoder_get_buffer_size(&root, cast_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Result, typename Reader>
|
|
||||||
auto ReadMessage(Reader&& reader, cpp::span<std::byte> data)
|
|
||||||
-> cpp::result<Result, CborError> {
|
|
||||||
CborParser parser;
|
|
||||||
CborValue root;
|
|
||||||
CborValue container;
|
|
||||||
|
|
||||||
cbor_parser_init(reinterpret_cast<uint8_t*>(data.data()), data.size(),
|
|
||||||
kDecoderFlags, &parser, &root);
|
|
||||||
cbor_value_enter_container(&root, &container);
|
|
||||||
// Skip the type header
|
|
||||||
cbor_value_advance_fixed(&container);
|
|
||||||
|
|
||||||
return std::invoke(reader, container);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto WriteTypeOnlyMessage(MessageType type, cpp::span<std::byte> data)
|
|
||||||
-> cpp::result<size_t, CborError>;
|
|
||||||
auto ReadMessageType(cpp::span<std::byte> msg) -> MessageType;
|
|
||||||
auto GetAdditionalData(cpp::span<std::byte> msg) -> cpp::span<std::byte>;
|
|
||||||
|
|
||||||
} // namespace audio
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "pipeline.hpp"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <memory>
|
|
||||||
#include "stream_info.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
|
||||||
|
|
||||||
Pipeline::Pipeline(IAudioElement* output) : root_(output), subtrees_() {
|
|
||||||
assert(output != nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
Pipeline::~Pipeline() {}
|
|
||||||
|
|
||||||
auto Pipeline::AddInput(IAudioElement* input) -> Pipeline* {
|
|
||||||
subtrees_.push_back(std::make_unique<Pipeline>(input));
|
|
||||||
return subtrees_.back().get();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Pipeline::OutputElement() const -> IAudioElement* {
|
|
||||||
return root_;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Pipeline::NumInputs() const -> std::size_t {
|
|
||||||
return subtrees_.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Pipeline::InStreams(std::vector<RawStream>* out) -> void {
|
|
||||||
for (int i = 0; i < subtrees_.size(); i++) {
|
|
||||||
out->push_back(subtrees_[i]->OutStream());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Pipeline::OutStream() -> RawStream {
|
|
||||||
return {&output_info_, output_buffer_};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Pipeline::GetIterationOrder() -> std::vector<Pipeline*> {
|
|
||||||
std::vector<Pipeline*> to_search{this};
|
|
||||||
std::vector<Pipeline*> found;
|
|
||||||
|
|
||||||
while (!to_search.empty()) {
|
|
||||||
Pipeline* current = to_search.back();
|
|
||||||
to_search.pop_back();
|
|
||||||
found.push_back(current);
|
|
||||||
|
|
||||||
for (const auto& i : current->subtrees_) {
|
|
||||||
to_search.push_back(i.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::reverse(found.begin(), found.end());
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace audio
|
|
||||||
+4
-13
@@ -4,27 +4,18 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-only
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
*/
|
*/
|
||||||
#include "resample.hpp"
|
#include "resample.hpp"
|
||||||
/*
|
|
||||||
* This file contains the implementation for a 32-bit floating point resampler.
|
|
||||||
* It is largely based on David Bryant's ART resampler, which is BSD-licensed,
|
|
||||||
* and available at https://github.com/dbry/audio-resampler/.
|
|
||||||
*
|
|
||||||
* This resampler uses windowed sinc interpolation filters, with an additional
|
|
||||||
* lowpass filter to reduce aliasing.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
#include "speex/speex_resampler.h"
|
||||||
|
|
||||||
#include "sample.hpp"
|
#include "sample.hpp"
|
||||||
#include "speex/speex_resampler.h"
|
|
||||||
#include "stream_info.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
namespace audio {
|
||||||
|
|
||||||
|
|||||||
@@ -6,19 +6,18 @@
|
|||||||
|
|
||||||
#include "sink_mixer.hpp"
|
#include "sink_mixer.hpp"
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include "esp_heap_caps.h"
|
#include "esp_heap_caps.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "freertos/portmacro.h"
|
#include "freertos/portmacro.h"
|
||||||
#include "freertos/projdefs.h"
|
#include "freertos/projdefs.h"
|
||||||
#include "idf_additions.h"
|
#include "idf_additions.h"
|
||||||
|
|
||||||
#include "resample.hpp"
|
#include "resample.hpp"
|
||||||
#include "sample.hpp"
|
#include "sample.hpp"
|
||||||
|
|
||||||
#include "stream_info.hpp"
|
|
||||||
#include "tasks.hpp"
|
#include "tasks.hpp"
|
||||||
|
|
||||||
static constexpr char kTag[] = "mixer";
|
static constexpr char kTag[] = "mixer";
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "stream_buffer.hpp"
|
|
||||||
#include "assert.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
|
|
||||||
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) {
|
|
||||||
assert(input_chunk_.size() <= buffer_size);
|
|
||||||
assert(output_chunk_.size() <= buffer_size);
|
|
||||||
ESP_LOGI("streambuf", "created buffer of chunk size %d, total size %d",
|
|
||||||
chunk_size, buffer_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamBuffer::~StreamBuffer() {
|
|
||||||
vMessageBufferDelete(handle_);
|
|
||||||
free(raw_memory_);
|
|
||||||
free(raw_input_chunk_);
|
|
||||||
free(raw_output_chunk_);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace audio
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "stream_event.hpp"
|
|
||||||
#include <cstddef>
|
|
||||||
#include <memory>
|
|
||||||
#include "arena.hpp"
|
|
||||||
#include "stream_info.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
|
||||||
|
|
||||||
auto StreamEvent::CreateStreamInfo(QueueHandle_t source,
|
|
||||||
const StreamInfo& payload) -> StreamEvent* {
|
|
||||||
auto event = new StreamEvent;
|
|
||||||
event->tag = StreamEvent::STREAM_INFO;
|
|
||||||
event->source = source;
|
|
||||||
event->stream_info = new StreamInfo(payload);
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto StreamEvent::CreateArenaChunk(QueueHandle_t source, memory::ArenaPtr ptr)
|
|
||||||
-> StreamEvent* {
|
|
||||||
auto event = new StreamEvent;
|
|
||||||
event->tag = StreamEvent::ARENA_CHUNK;
|
|
||||||
event->source = source;
|
|
||||||
event->arena_chunk = ptr;
|
|
||||||
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto StreamEvent::CreateChunkNotification(QueueHandle_t source)
|
|
||||||
-> StreamEvent* {
|
|
||||||
auto event = new StreamEvent;
|
|
||||||
event->tag = StreamEvent::CHUNK_NOTIFICATION;
|
|
||||||
event->source = source;
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto StreamEvent::CreateEndOfStream(QueueHandle_t source) -> StreamEvent* {
|
|
||||||
auto event = new StreamEvent;
|
|
||||||
event->tag = StreamEvent::END_OF_STREAM;
|
|
||||||
event->source = source;
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto StreamEvent::CreateLogStatus() -> StreamEvent* {
|
|
||||||
auto event = new StreamEvent;
|
|
||||||
event->tag = StreamEvent::LOG_STATUS;
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamEvent::StreamEvent() : tag(StreamEvent::UNINITIALISED) {}
|
|
||||||
|
|
||||||
StreamEvent::~StreamEvent() {
|
|
||||||
switch (tag) {
|
|
||||||
case UNINITIALISED:
|
|
||||||
break;
|
|
||||||
case STREAM_INFO:
|
|
||||||
delete stream_info;
|
|
||||||
break;
|
|
||||||
case ARENA_CHUNK:
|
|
||||||
arena_chunk.owner->Return(arena_chunk);
|
|
||||||
break;
|
|
||||||
case CHUNK_NOTIFICATION:
|
|
||||||
break;
|
|
||||||
case END_OF_STREAM:
|
|
||||||
break;
|
|
||||||
case LOG_STATUS:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamEvent::StreamEvent(StreamEvent&& other) {
|
|
||||||
tag = other.tag;
|
|
||||||
source = other.source;
|
|
||||||
switch (tag) {
|
|
||||||
case UNINITIALISED:
|
|
||||||
break;
|
|
||||||
case STREAM_INFO:
|
|
||||||
stream_info = other.stream_info;
|
|
||||||
other.stream_info = nullptr;
|
|
||||||
break;
|
|
||||||
case ARENA_CHUNK:
|
|
||||||
arena_chunk = other.arena_chunk;
|
|
||||||
other.arena_chunk = {
|
|
||||||
.owner = nullptr, .start = nullptr, .size = 0, .used_size = 0};
|
|
||||||
break;
|
|
||||||
case CHUNK_NOTIFICATION:
|
|
||||||
break;
|
|
||||||
case END_OF_STREAM:
|
|
||||||
break;
|
|
||||||
case LOG_STATUS:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
other.tag = StreamEvent::UNINITIALISED;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace audio
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "stream_info.hpp"
|
|
||||||
#include <sys/_stdint.h>
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <utility>
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
#include "esp_heap_caps.h"
|
|
||||||
#include "result.hpp"
|
|
||||||
#include "span.hpp"
|
|
||||||
#include "types.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
|
||||||
|
|
||||||
RawStream::RawStream(std::size_t size)
|
|
||||||
: info_(),
|
|
||||||
buffer_size_(size),
|
|
||||||
buffer_(reinterpret_cast<std::byte*>(
|
|
||||||
heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT))) {
|
|
||||||
assert(buffer_ != NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
RawStream::RawStream(std::size_t size, uint32_t caps)
|
|
||||||
: info_(),
|
|
||||||
buffer_size_(size),
|
|
||||||
buffer_(reinterpret_cast<std::byte*>(heap_caps_malloc(size, caps))) {
|
|
||||||
assert(buffer_ != NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
RawStream::~RawStream() {
|
|
||||||
heap_caps_free(buffer_);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto RawStream::data() -> cpp::span<std::byte> {
|
|
||||||
return {buffer_, buffer_size_};
|
|
||||||
}
|
|
||||||
|
|
||||||
void InputStream::consume(std::size_t bytes) const {
|
|
||||||
assert(raw_->info().bytes_in_stream() >= bytes);
|
|
||||||
auto new_data =
|
|
||||||
raw_->data().subspan(bytes, raw_->info().bytes_in_stream() - bytes);
|
|
||||||
std::move(new_data.begin(), new_data.end(), raw_->data().begin());
|
|
||||||
raw_->info().bytes_in_stream() = new_data.size_bytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
const StreamInfo& InputStream::info() const {
|
|
||||||
return raw_->info();
|
|
||||||
}
|
|
||||||
|
|
||||||
cpp::span<const std::byte> InputStream::data() const {
|
|
||||||
return raw_->data().first(raw_->info().bytes_in_stream());
|
|
||||||
}
|
|
||||||
|
|
||||||
void OutputStream::add(std::size_t bytes) const {
|
|
||||||
assert(raw_->info().bytes_in_stream() + bytes <= raw_->data().size_bytes());
|
|
||||||
raw_->info().bytes_in_stream() += bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OutputStream::prepare(const StreamInfo::Format& new_format,
|
|
||||||
std::optional<uint32_t> length) {
|
|
||||||
raw_->info().set_format(new_format);
|
|
||||||
raw_->info().bytes_in_stream() = 0;
|
|
||||||
raw_->info().total_length_bytes() = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
const StreamInfo& OutputStream::info() const {
|
|
||||||
return raw_->info();
|
|
||||||
}
|
|
||||||
|
|
||||||
cpp::span<std::byte> OutputStream::data() const {
|
|
||||||
return raw_->data().subspan(raw_->info().bytes_in_stream());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace audio
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2023 jacqueline <me@jacqueline.id.au>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "stream_message.hpp"
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "cbor.h"
|
|
||||||
#include "span.hpp"
|
|
||||||
|
|
||||||
namespace audio {
|
|
||||||
|
|
||||||
const int kEncoderFlags = 0;
|
|
||||||
const int kDecoderFlags = 0;
|
|
||||||
|
|
||||||
auto WriteTypeOnlyMessage(MessageType type, cpp::span<std::byte> data)
|
|
||||||
-> cpp::result<size_t, CborError> {
|
|
||||||
CborEncoder root;
|
|
||||||
CborEncoder container;
|
|
||||||
uint8_t* cast_data = reinterpret_cast<uint8_t*>(data.data());
|
|
||||||
|
|
||||||
cbor_encoder_init(&root, cast_data, data.size(), kEncoderFlags);
|
|
||||||
cbor_encode_uint(&container, type);
|
|
||||||
|
|
||||||
return cbor_encoder_get_buffer_size(&root, cast_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ReadMessageType(cpp::span<std::byte> msg) -> MessageType {
|
|
||||||
CborParser parser;
|
|
||||||
CborValue root;
|
|
||||||
CborValue container;
|
|
||||||
|
|
||||||
cbor_parser_init(reinterpret_cast<uint8_t*>(msg.data()), msg.size(),
|
|
||||||
kDecoderFlags, &parser, &root);
|
|
||||||
|
|
||||||
uint64_t header = 0;
|
|
||||||
if (cbor_value_is_container(&root)) {
|
|
||||||
cbor_value_enter_container(&root, &container);
|
|
||||||
cbor_value_get_uint64(&container, &header);
|
|
||||||
} else {
|
|
||||||
cbor_value_get_uint64(&root, &header);
|
|
||||||
}
|
|
||||||
|
|
||||||
return static_cast<MessageType>(header);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto GetAdditionalData(cpp::span<std::byte> msg) -> cpp::span<std::byte> {
|
|
||||||
CborParser parser;
|
|
||||||
CborValue root;
|
|
||||||
uint8_t* cast_data = reinterpret_cast<uint8_t*>(msg.data());
|
|
||||||
|
|
||||||
cbor_parser_init(cast_data, msg.size(), kDecoderFlags, &parser, &root);
|
|
||||||
|
|
||||||
while (!cbor_value_at_end(&root)) {
|
|
||||||
cbor_value_advance(&root);
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint8_t* remaining = cbor_value_get_next_byte(&root);
|
|
||||||
size_t header_size = remaining - cast_data;
|
|
||||||
|
|
||||||
return cpp::span<std::byte>(msg.data() + header_size,
|
|
||||||
msg.size() - header_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace audio
|
|
||||||
Reference in New Issue
Block a user