back to back flac playback is working :)
This commit is contained in:
+76
-50
@@ -36,37 +36,27 @@ AudioDecoder::AudioDecoder()
|
|||||||
current_codec_(),
|
current_codec_(),
|
||||||
current_input_format_(),
|
current_input_format_(),
|
||||||
current_output_format_(),
|
current_output_format_(),
|
||||||
|
has_prepared_output_(false),
|
||||||
has_samples_to_send_(false),
|
has_samples_to_send_(false),
|
||||||
has_input_remaining_(false) {}
|
has_input_remaining_(false) {}
|
||||||
|
|
||||||
AudioDecoder::~AudioDecoder() {}
|
AudioDecoder::~AudioDecoder() {}
|
||||||
|
|
||||||
auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info) -> bool {
|
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)) {
|
if (!std::holds_alternative<StreamInfo::Encoded>(info.format)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ESP_LOGI(kTag, "got new stream");
|
|
||||||
const auto& encoded = std::get<StreamInfo::Encoded>(info.format);
|
|
||||||
|
|
||||||
// Reuse the existing codec if we can. This will help with gapless playback,
|
const auto& new_format = std::get<StreamInfo::Encoded>(info.format);
|
||||||
// since we can potentially just continue to decode as we were before,
|
|
||||||
// without any setup overhead.
|
|
||||||
// TODO(jacqueline): Reconsider this. It makes a lot of things harder to smash
|
|
||||||
// streams together at this layer.
|
|
||||||
/*
|
|
||||||
if (current_codec_ != nullptr && current_input_format_) {
|
|
||||||
auto cur_encoding = std::get<StreamInfo::Encoded>(*current_input_format_);
|
|
||||||
if (cur_encoding.type == encoded.type) {
|
|
||||||
ESP_LOGI(kTag, "reusing existing decoder");
|
|
||||||
current_input_format_ = info.format;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
current_input_format_ = info.format;
|
current_input_format_ = info.format;
|
||||||
|
|
||||||
ESP_LOGI(kTag, "creating new decoder");
|
ESP_LOGI(kTag, "creating new decoder");
|
||||||
auto result = codecs::CreateCodecForType(encoded.type);
|
auto result = codecs::CreateCodecForType(new_format.type);
|
||||||
if (result.has_value()) {
|
if (result.has_value()) {
|
||||||
current_codec_.reset(result.value());
|
current_codec_.reset(result.value());
|
||||||
} else {
|
} else {
|
||||||
@@ -86,18 +76,31 @@ auto AudioDecoder::Process(const std::vector<InputStream>& inputs,
|
|||||||
auto input = inputs.begin();
|
auto input = inputs.begin();
|
||||||
const StreamInfo& info = input->info();
|
const StreamInfo& info = input->info();
|
||||||
|
|
||||||
// Check the input stream's format has changed (or, by extension, if this is
|
// Is this a completely new stream?
|
||||||
// the first stream).
|
if (!current_input_format_) {
|
||||||
if (!current_input_format_ || *current_input_format_ != info.format) {
|
|
||||||
has_samples_to_send_ = false;
|
|
||||||
if (!ProcessStreamInfo(info)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
ESP_LOGI(kTag, "beginning new stream");
|
}
|
||||||
|
|
||||||
|
// Have we determined what kind of samples this stream decodes to?
|
||||||
|
if (!current_output_format_) {
|
||||||
auto res = current_codec_->BeginStream(input->data());
|
auto res = current_codec_->BeginStream(input->data());
|
||||||
input->consume(res.first);
|
input->consume(res.first);
|
||||||
|
|
||||||
if (res.second.has_error()) {
|
if (res.second.has_error()) {
|
||||||
// TODO(jacqueline): Handle errors.
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,59 +119,82 @@ auto AudioDecoder::Process(const std::vector<InputStream>& inputs,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (seek_to_sample_) {
|
if (seek_to_sample_) {
|
||||||
ESP_LOGI(kTag, "seeking forwards...");
|
ESP_LOGI(kTag, "seeking forwards...");
|
||||||
auto res = current_codec_->SeekStream(input->data(), *seek_to_sample_);
|
auto res = current_codec_->SeekStream(input->data(), *seek_to_sample_);
|
||||||
input->consume(res.first);
|
input->consume(res.first);
|
||||||
|
|
||||||
if (res.second.has_error()) {
|
if (res.second.has_error()) {
|
||||||
auto err = res.second.error();
|
auto err = res.second.error();
|
||||||
if (err == codecs::ICodec::Error::kOutOfInput) {
|
if (err == codecs::ICodec::Error::kOutOfInput) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// TODO(jacqueline): Handle errors.
|
// TODO(jacqueline): Handle errors.
|
||||||
seek_to_sample_.reset();
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
seek_to_sample_.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
seek_to_sample_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
has_input_remaining_ = true;
|
has_input_remaining_ = true;
|
||||||
while (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?
|
// TODO(jacqueline): Pass through seek info here?
|
||||||
if (!output->prepare(*current_output_format_)) {
|
if (!has_prepared_output_ && !output->prepare(*current_output_format_)) {
|
||||||
ESP_LOGI(kTag, "waiting for buffer to become free");
|
ESP_LOGI(kTag, "waiting for buffer to become free");
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto res = current_codec_->ContinueStream(input->data(), output->data());
|
|
||||||
input->consume(res.first);
|
|
||||||
if (res.second.has_error()) {
|
|
||||||
if (res.second.error() == codecs::ICodec::Error::kOutOfInput) {
|
|
||||||
ESP_LOGW(kTag, "out of input");
|
|
||||||
ESP_LOGW(kTag, "(%u bytes left)", input->data().size_bytes());
|
|
||||||
has_input_remaining_ = false;
|
|
||||||
// We can't be halfway through sending samples if the codec is asking
|
|
||||||
// for more input.
|
|
||||||
has_samples_to_send_ = false;
|
|
||||||
input->mark_incomplete();
|
|
||||||
} else {
|
|
||||||
// TODO(jacqueline): Handle errors.
|
|
||||||
ESP_LOGE(kTag, "codec return fatal error");
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
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) {
|
||||||
|
ESP_LOGI(kTag, "codec needs more data");
|
||||||
|
has_input_remaining_ = false;
|
||||||
|
has_samples_to_send_ = false;
|
||||||
|
if (input->is_producer_finished()) {
|
||||||
|
ESP_LOGI(kTag, "codec is all done.");
|
||||||
|
|
||||||
|
// 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();
|
codecs::ICodec::OutputInfo out_info = res.second.value();
|
||||||
output->add(out_info.bytes_written);
|
output->add(out_info.bytes_written);
|
||||||
has_samples_to_send_ = !out_info.is_finished_writing;
|
has_samples_to_send_ = !out_info.is_finished_writing;
|
||||||
|
|
||||||
if (has_samples_to_send_) {
|
if (has_samples_to_send_) {
|
||||||
// We weren't able to fit all the generated samples into the output
|
// The codec wasn't able to finish writing all of its samples into the
|
||||||
// buffer. Stop trying; we'll finish up during the next pass.
|
// output buffer. We need to return so that we can get a new buffer.
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
current_codec_.reset();
|
||||||
|
current_input_format_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace audio
|
} // namespace audio
|
||||||
|
|||||||
@@ -125,8 +125,13 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) {
|
|||||||
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) {
|
||||||
|
if (sink_stream.is_producer_finished()) {
|
||||||
|
sink_stream.mark_consumer_finished();
|
||||||
|
} else {
|
||||||
|
// The user is probably about to hear a skip :(
|
||||||
|
ESP_LOGW(kTag, "!! audio sink is underbuffered !!");
|
||||||
|
}
|
||||||
// No new bytes to sink, so skip sinking completely.
|
// No new bytes to sink, so skip sinking completely.
|
||||||
ESP_LOGW(kTag, "no bytes to sink");
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ FatfsAudioInput::FatfsAudioInput()
|
|||||||
pending_path_(),
|
pending_path_(),
|
||||||
current_file_(),
|
current_file_(),
|
||||||
is_file_open_(false),
|
is_file_open_(false),
|
||||||
|
has_prepared_output_(false),
|
||||||
current_container_(),
|
current_container_(),
|
||||||
current_format_() {}
|
current_format_() {}
|
||||||
|
|
||||||
@@ -57,10 +58,13 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> bool {
|
|||||||
if (is_file_open_) {
|
if (is_file_open_) {
|
||||||
f_close(¤t_file_);
|
f_close(¤t_file_);
|
||||||
is_file_open_ = false;
|
is_file_open_ = false;
|
||||||
|
has_prepared_output_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pending_path_) {
|
if (pending_path_) {
|
||||||
pending_path_ = {};
|
pending_path_ = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGI(kTag, "opening file %s", path.c_str());
|
ESP_LOGI(kTag, "opening file %s", path.c_str());
|
||||||
|
|
||||||
database::TagParserImpl tag_parser;
|
database::TagParserImpl tag_parser;
|
||||||
@@ -112,13 +116,11 @@ auto FatfsAudioInput::NeedsToProcess() const -> bool {
|
|||||||
auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
|
auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
|
||||||
OutputStream* output) -> void {
|
OutputStream* output) -> void {
|
||||||
if (pending_path_) {
|
if (pending_path_) {
|
||||||
ESP_LOGI(kTag, "waiting for path");
|
|
||||||
if (!pending_path_->valid()) {
|
if (!pending_path_->valid()) {
|
||||||
pending_path_ = {};
|
pending_path_ = {};
|
||||||
} else {
|
} else {
|
||||||
if (pending_path_->wait_for(std::chrono::seconds(0)) ==
|
if (pending_path_->wait_for(std::chrono::seconds(0)) ==
|
||||||
std::future_status::ready) {
|
std::future_status::ready) {
|
||||||
ESP_LOGI(kTag, "path ready!");
|
|
||||||
auto result = pending_path_->get();
|
auto result = pending_path_->get();
|
||||||
if (result) {
|
if (result) {
|
||||||
OpenFile(*result);
|
OpenFile(*result);
|
||||||
@@ -131,9 +133,11 @@ auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!output->prepare(*current_format_)) {
|
if (!has_prepared_output_ && !output->prepare(*current_format_)) {
|
||||||
|
ESP_LOGI(kTag, "waiting for buffer to free up");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
has_prepared_output_ = true;
|
||||||
|
|
||||||
std::size_t max_size = output->data().size_bytes();
|
std::size_t max_size = output->data().size_bytes();
|
||||||
if (max_size < output->data().size_bytes() / 2) {
|
if (max_size < output->data().size_bytes() / 2) {
|
||||||
@@ -152,14 +156,11 @@ auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
|
|||||||
output->add(size);
|
output->add(size);
|
||||||
|
|
||||||
if (size < max_size || f_eof(¤t_file_)) {
|
if (size < max_size || f_eof(¤t_file_)) {
|
||||||
|
ESP_LOGI(kTag, "file finished. closing.");
|
||||||
f_close(¤t_file_);
|
f_close(¤t_file_);
|
||||||
is_file_open_ = false;
|
is_file_open_ = false;
|
||||||
|
has_prepared_output_ = false;
|
||||||
// HACK: libmad requires an 8 byte padding at the end of each file.
|
output->mark_producer_finished();
|
||||||
if (current_container_ == database::Encoding::kMp3) {
|
|
||||||
std::fill_n(output->data().begin(), 8, std::byte(0));
|
|
||||||
output->add(8);
|
|
||||||
}
|
|
||||||
|
|
||||||
events::Dispatch<InputFileFinished, AudioState>({});
|
events::Dispatch<InputFileFinished, AudioState>({});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class AudioDecoder : public IAudioElement {
|
|||||||
std::optional<StreamInfo::Format> current_input_format_;
|
std::optional<StreamInfo::Format> current_input_format_;
|
||||||
std::optional<StreamInfo::Format> current_output_format_;
|
std::optional<StreamInfo::Format> current_output_format_;
|
||||||
std::optional<std::size_t> seek_to_sample_;
|
std::optional<std::size_t> seek_to_sample_;
|
||||||
|
bool has_prepared_output_;
|
||||||
bool has_samples_to_send_;
|
bool has_samples_to_send_;
|
||||||
bool has_input_remaining_;
|
bool has_input_remaining_;
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class FatfsAudioInput : public IAudioElement {
|
|||||||
std::optional<std::future<std::optional<std::string>>> pending_path_;
|
std::optional<std::future<std::optional<std::string>>> pending_path_;
|
||||||
FIL current_file_;
|
FIL current_file_;
|
||||||
bool is_file_open_;
|
bool is_file_open_;
|
||||||
|
bool has_prepared_output_;
|
||||||
|
|
||||||
std::optional<database::Encoding> current_container_;
|
std::optional<database::Encoding> current_container_;
|
||||||
std::optional<StreamInfo::Format> current_format_;
|
std::optional<StreamInfo::Format> current_format_;
|
||||||
|
|||||||
@@ -26,10 +26,9 @@ struct StreamInfo {
|
|||||||
// stream's buffer.
|
// stream's buffer.
|
||||||
std::size_t bytes_in_stream{0};
|
std::size_t bytes_in_stream{0};
|
||||||
|
|
||||||
// The total length of this stream, in case its source is finite (e.g. a
|
bool is_producer_finished = true;
|
||||||
// file on disk). May be absent for endless streams (internet streams,
|
|
||||||
// generated audio, etc.)
|
bool is_consumer_finished = true;
|
||||||
std::optional<std::size_t> length_bytes{};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
std::optional<uint32_t> seek_to_seconds{};
|
std::optional<uint32_t> seek_to_seconds{};
|
||||||
@@ -62,10 +61,8 @@ class RawStream {
|
|||||||
public:
|
public:
|
||||||
StreamInfo* info;
|
StreamInfo* info;
|
||||||
cpp::span<std::byte> data;
|
cpp::span<std::byte> data;
|
||||||
bool is_incomplete;
|
|
||||||
|
|
||||||
RawStream(StreamInfo* i, cpp::span<std::byte> d)
|
RawStream(StreamInfo* i, cpp::span<std::byte> d) : info(i), data(d) {}
|
||||||
: info(i), data(d), is_incomplete(false) {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -78,7 +75,9 @@ class InputStream {
|
|||||||
|
|
||||||
void consume(std::size_t bytes) const;
|
void consume(std::size_t bytes) const;
|
||||||
|
|
||||||
void mark_incomplete() const;
|
bool is_producer_finished() const;
|
||||||
|
|
||||||
|
void mark_consumer_finished() const;
|
||||||
|
|
||||||
const StreamInfo& info() const;
|
const StreamInfo& info() const;
|
||||||
|
|
||||||
@@ -100,7 +99,9 @@ class OutputStream {
|
|||||||
|
|
||||||
cpp::span<std::byte> data() const;
|
cpp::span<std::byte> data() const;
|
||||||
|
|
||||||
bool is_incomplete() const;
|
bool is_consumer_finished() const;
|
||||||
|
|
||||||
|
void mark_producer_finished() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RawStream* raw_;
|
RawStream* raw_;
|
||||||
|
|||||||
+16
-13
@@ -28,8 +28,12 @@ void InputStream::consume(std::size_t bytes) const {
|
|||||||
raw_->info->bytes_in_stream = new_data.size_bytes();
|
raw_->info->bytes_in_stream = new_data.size_bytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputStream::mark_incomplete() const {
|
void InputStream::mark_consumer_finished() const {
|
||||||
raw_->is_incomplete = true;
|
raw_->info->is_consumer_finished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InputStream::is_producer_finished() const {
|
||||||
|
return raw_->info->is_producer_finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StreamInfo& InputStream::info() const {
|
const StreamInfo& InputStream::info() const {
|
||||||
@@ -46,17 +50,12 @@ void OutputStream::add(std::size_t bytes) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool OutputStream::prepare(const StreamInfo::Format& new_format) {
|
bool OutputStream::prepare(const StreamInfo::Format& new_format) {
|
||||||
if (std::holds_alternative<std::monostate>(raw_->info->format)) {
|
if (std::holds_alternative<std::monostate>(raw_->info->format) ||
|
||||||
raw_->info->format = new_format;
|
raw_->info->is_consumer_finished) {
|
||||||
raw_->info->bytes_in_stream = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (new_format == raw_->info->format) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (raw_->is_incomplete) {
|
|
||||||
raw_->info->format = new_format;
|
raw_->info->format = new_format;
|
||||||
raw_->info->bytes_in_stream = 0;
|
raw_->info->bytes_in_stream = 0;
|
||||||
|
raw_->info->is_producer_finished = false;
|
||||||
|
raw_->info->is_consumer_finished = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -70,8 +69,12 @@ cpp::span<std::byte> OutputStream::data() const {
|
|||||||
return raw_->data.subspan(raw_->info->bytes_in_stream);
|
return raw_->data.subspan(raw_->info->bytes_in_stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OutputStream::is_incomplete() const {
|
void OutputStream::mark_producer_finished() const {
|
||||||
return raw_->is_incomplete;
|
raw_->info->is_producer_finished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OutputStream::is_consumer_finished() const {
|
||||||
|
return raw_->info->is_consumer_finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace audio
|
} // namespace audio
|
||||||
|
|||||||
+6
-5
@@ -44,6 +44,7 @@ MadMp3Decoder::~MadMp3Decoder() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto MadMp3Decoder::GetInputPosition() -> std::size_t {
|
auto MadMp3Decoder::GetInputPosition() -> std::size_t {
|
||||||
|
assert(stream_.next_frame >= stream_.buffer);
|
||||||
return stream_.next_frame - stream_.buffer;
|
return stream_.next_frame - stream_.buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ auto MadMp3Decoder::BeginStream(const cpp::span<const std::byte> input)
|
|||||||
-> Result<OutputFormat> {
|
-> Result<OutputFormat> {
|
||||||
mad_stream_buffer(&stream_,
|
mad_stream_buffer(&stream_,
|
||||||
reinterpret_cast<const unsigned char*>(input.data()),
|
reinterpret_cast<const unsigned char*>(input.data()),
|
||||||
input.size());
|
input.size_bytes());
|
||||||
// Whatever was last synthesized is now invalid, so ensure we don't try to
|
// Whatever was last synthesized is now invalid, so ensure we don't try to
|
||||||
// send it.
|
// send it.
|
||||||
current_sample_ = -1;
|
current_sample_ = -1;
|
||||||
@@ -65,11 +66,11 @@ auto MadMp3Decoder::BeginStream(const cpp::span<const std::byte> input)
|
|||||||
// Recoverable errors are usually malformed parts of the stream.
|
// Recoverable errors are usually malformed parts of the stream.
|
||||||
// We can recover from them by just retrying the decode.
|
// We can recover from them by just retrying the decode.
|
||||||
continue;
|
continue;
|
||||||
} else {
|
|
||||||
// Don't bother checking for other errors; if the first part of the stream
|
|
||||||
// doesn't even contain a header then something's gone wrong.
|
|
||||||
return {GetInputPosition(), cpp::fail(Error::kMalformedData)};
|
|
||||||
}
|
}
|
||||||
|
if (stream_.error == MAD_ERROR_BUFLEN) {
|
||||||
|
return {GetInputPosition(), cpp::fail(Error::kOutOfInput)};
|
||||||
|
}
|
||||||
|
return {GetInputPosition(), cpp::fail(Error::kMalformedData)};
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t channels = MAD_NCHANNELS(&header);
|
uint8_t channels = MAD_NCHANNELS(&header);
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ void Running::entry() {
|
|||||||
vTaskDelay(pdMS_TO_TICKS(250));
|
vTaskDelay(pdMS_TO_TICKS(250));
|
||||||
|
|
||||||
ESP_LOGI(kTag, "opening database");
|
ESP_LOGI(kTag, "opening database");
|
||||||
database::Database::Destroy();
|
|
||||||
auto database_res = database::Database::Open();
|
auto database_res = database::Database::Open();
|
||||||
if (database_res.has_error()) {
|
if (database_res.has_error()) {
|
||||||
ESP_LOGW(kTag, "failed to open!");
|
ESP_LOGW(kTag, "failed to open!");
|
||||||
|
|||||||
Reference in New Issue
Block a user