|
|
@ -43,20 +43,64 @@ namespace task { |
|
|
|
|
|
|
|
|
|
|
|
static const char* kTag = "task"; |
|
|
|
static const char* kTag = "task"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// The default amount of time to wait between pipeline iterations for a single
|
|
|
|
|
|
|
|
// song.
|
|
|
|
|
|
|
|
static constexpr uint_fast16_t kDefaultDelayTicks = pdMS_TO_TICKS(5); |
|
|
|
|
|
|
|
static constexpr uint_fast16_t kMaxDelayTicks = pdMS_TO_TICKS(10); |
|
|
|
|
|
|
|
static constexpr uint_fast16_t kMinDelayTicks = pdMS_TO_TICKS(1); |
|
|
|
|
|
|
|
|
|
|
|
void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) { |
|
|
|
void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) { |
|
|
|
|
|
|
|
// The stream format for bytes currently in the sink buffer.
|
|
|
|
std::optional<StreamInfo::Format> output_format; |
|
|
|
std::optional<StreamInfo::Format> output_format; |
|
|
|
uint_fast16_t delay_ticks = pdMS_TO_TICKS(5); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<Pipeline*> elements = pipeline->GetIterationOrder(); |
|
|
|
// How long to wait between pipeline iterations. This is reset for each song,
|
|
|
|
|
|
|
|
// and readjusted on the fly to maintain a reasonable amount playback buffer.
|
|
|
|
|
|
|
|
// Buffering too much will mean we process samples inefficiently, wasting CPU
|
|
|
|
|
|
|
|
// time, whilst buffering too little will affect the quality of the output.
|
|
|
|
|
|
|
|
uint_fast16_t delay_ticks = kDefaultDelayTicks; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<Pipeline*> all_elements = pipeline->GetIterationOrder(); |
|
|
|
|
|
|
|
|
|
|
|
events::EventQueue &event_queue = events::EventQueue::GetInstance(); |
|
|
|
events::EventQueue& event_queue = events::EventQueue::GetInstance(); |
|
|
|
while (1) { |
|
|
|
while (1) { |
|
|
|
event_queue.ServiceAudio(delay_ticks); |
|
|
|
// First, see if we actually have any pipeline work to do in this iteration.
|
|
|
|
|
|
|
|
bool has_work = false; |
|
|
|
|
|
|
|
// We always have work to do if there's still bytes to be sunk.
|
|
|
|
|
|
|
|
has_work = all_elements.back()->OutStream().info->bytes_in_stream > 0; |
|
|
|
|
|
|
|
if (!has_work) { |
|
|
|
|
|
|
|
for (Pipeline* p : all_elements) { |
|
|
|
|
|
|
|
has_work = p->OutputElement()->NeedsToProcess(); |
|
|
|
|
|
|
|
if (has_work) { |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// See if there's any new events.
|
|
|
|
|
|
|
|
event_queue.ServiceAudio(has_work ? delay_ticks : portMAX_DELAY); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!has_work) { |
|
|
|
|
|
|
|
// See if we've been given work by this event.
|
|
|
|
|
|
|
|
for (Pipeline* p : all_elements) { |
|
|
|
|
|
|
|
has_work = p->OutputElement()->NeedsToProcess(); |
|
|
|
|
|
|
|
if (has_work) { |
|
|
|
|
|
|
|
delay_ticks = kDefaultDelayTicks; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!has_work) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < elements.size(); i++) { |
|
|
|
// We have work to do! Allow each element in the pipeline to process one
|
|
|
|
|
|
|
|
// chunk. We iterate from input nodes first, so this should result in
|
|
|
|
|
|
|
|
// samples in the output buffer.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < all_elements.size(); i++) { |
|
|
|
std::vector<RawStream> raw_in_streams; |
|
|
|
std::vector<RawStream> raw_in_streams; |
|
|
|
elements.at(i)->InStreams(&raw_in_streams); |
|
|
|
all_elements.at(i)->InStreams(&raw_in_streams); |
|
|
|
RawStream raw_out_stream = elements.at(i)->OutStream(); |
|
|
|
RawStream raw_out_stream = all_elements.at(i)->OutStream(); |
|
|
|
|
|
|
|
|
|
|
|
// Crop the input and output streams to the ranges that are safe to
|
|
|
|
// Crop the input and output streams to the ranges that are safe to
|
|
|
|
// touch. For the input streams, this is the region that contains
|
|
|
|
// touch. For the input streams, this is the region that contains
|
|
|
@ -67,14 +111,14 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) { |
|
|
|
[&](RawStream& s) { in_streams.emplace_back(&s); }); |
|
|
|
[&](RawStream& s) { in_streams.emplace_back(&s); }); |
|
|
|
OutputStream out_stream(&raw_out_stream); |
|
|
|
OutputStream out_stream(&raw_out_stream); |
|
|
|
|
|
|
|
|
|
|
|
elements.at(i)->OutputElement()->Process(in_streams, &out_stream); |
|
|
|
all_elements.at(i)->OutputElement()->Process(in_streams, &out_stream); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
RawStream raw_sink_stream = elements.front()->OutStream(); |
|
|
|
RawStream raw_sink_stream = all_elements.back()->OutStream(); |
|
|
|
InputStream sink_stream(&raw_sink_stream); |
|
|
|
InputStream sink_stream(&raw_sink_stream); |
|
|
|
|
|
|
|
|
|
|
|
if (sink_stream.info().bytes_in_stream == 0) { |
|
|
|
if (sink_stream.info().bytes_in_stream == 0) { |
|
|
|
vTaskDelay(pdMS_TO_TICKS(100)); |
|
|
|
// No new bytes to sink, so skip sinking completely.
|
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -86,24 +130,36 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) { |
|
|
|
ESP_LOGI(kTag, "reconfiguring dac"); |
|
|
|
ESP_LOGI(kTag, "reconfiguring dac"); |
|
|
|
output_format = sink_stream.info().format; |
|
|
|
output_format = sink_stream.info().format; |
|
|
|
sink->Configure(*output_format); |
|
|
|
sink->Configure(*output_format); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// We've reconfigured the sink, or it was already configured correctly.
|
|
|
|
// We've reconfigured the sink, or it was already configured correctly.
|
|
|
|
// Send through some data.
|
|
|
|
// Send through some data.
|
|
|
|
if (output_format == sink_stream.info().format && |
|
|
|
std::size_t bytes_sunk = |
|
|
|
!std::holds_alternative<std::monostate>(*output_format)) { |
|
|
|
|
|
|
|
std::size_t sent = |
|
|
|
|
|
|
|
xStreamBufferSend(sink->buffer(), sink_stream.data().data(), |
|
|
|
xStreamBufferSend(sink->buffer(), sink_stream.data().data(), |
|
|
|
sink_stream.data().size_bytes(), 0); |
|
|
|
sink_stream.data().size_bytes(), 0); |
|
|
|
if (sent > 0) { |
|
|
|
|
|
|
|
ESP_LOGI( |
|
|
|
// Adjust how long we wait for the next iteration if we're getting too far
|
|
|
|
kTag, "sunk %u bytes out of %u (%d %%)", sent, |
|
|
|
// ahead or behind.
|
|
|
|
sink_stream.info().bytes_in_stream, |
|
|
|
float sunk_percent = static_cast<float>(bytes_sunk) / |
|
|
|
(int)(((float)sent / (float)sink_stream.info().bytes_in_stream) * |
|
|
|
static_cast<float>(sink_stream.info().bytes_in_stream); |
|
|
|
100)); |
|
|
|
|
|
|
|
|
|
|
|
if (sunk_percent > 0.66f) { |
|
|
|
|
|
|
|
// We're sinking a lot of the output buffer per iteration, so we need to
|
|
|
|
|
|
|
|
// be running faster.
|
|
|
|
|
|
|
|
delay_ticks--; |
|
|
|
|
|
|
|
} else if (sunk_percent < 0.33f) { |
|
|
|
|
|
|
|
// We're not sinking much of the output buffer per iteration, so we can
|
|
|
|
|
|
|
|
// slow down to save some cycles.
|
|
|
|
|
|
|
|
delay_ticks++; |
|
|
|
} |
|
|
|
} |
|
|
|
sink_stream.consume(sent); |
|
|
|
delay_ticks = std::clamp(delay_ticks, kMinDelayTicks, kMaxDelayTicks); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Finally, actually mark the bytes we sunk as consumed.
|
|
|
|
|
|
|
|
if (bytes_sunk > 0) { |
|
|
|
|
|
|
|
sink_stream.consume(bytes_sunk); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|