Skip to content

Commit

Permalink
[nabu] Improve error logging (#175)
Browse files Browse the repository at this point in the history
* reimplement flac decoder improvements

* add more descriptive decoding errors

* increase reader task stack size

* remove unused variable
  • Loading branch information
kahrendt authored Oct 21, 2024
1 parent d5cb074 commit 1bd12f6
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 20 deletions.
30 changes: 19 additions & 11 deletions esphome/components/nabu/audio_decoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,12 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
}

if ((this->input_buffer_length_ == 0) || ((this->potentially_failed_count_ > 0) && (bytes_read == 0))) {
// No input data available or no new data has been read, so we can't do any more processing
state = FileDecoderState::IDLE;
if ((this->input_buffer_length_ && stop_gracefully) || bytes_to_read == 0) {
// data in buffer won't change, don't try again
state = FileDecoderState::FAILED;
} else {
state = FileDecoderState::IDLE;
}
} else {
switch (this->media_file_type_) {
case media_player::MediaFileType::FLAC:
Expand All @@ -164,7 +168,7 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
this->end_of_file_ = true;
} else if (state == FileDecoderState::FAILED) {
return AudioDecoderState::FAILED;
} else {
} else if (state == FileDecoderState::MORE_TO_PROCESS) {
this->potentially_failed_count_ = 0;
}
}
Expand Down Expand Up @@ -205,19 +209,19 @@ FileDecoderState AudioDecoder::decode_flac_() {
this->input_buffer_current_ += bytes_consumed;
this->input_buffer_length_ = this->flac_decoder_->get_bytes_left();

size_t flac_decoder_output_buffer_min_size = flac_decoder_->get_output_buffer_size();
if (this->internal_buffer_size_ < flac_decoder_output_buffer_min_size * sizeof(int16_t)) {
// Output buffer is not big enough
return FileDecoderState::FAILED;
}

audio::AudioStreamInfo audio_stream_info;
audio_stream_info.channels = this->flac_decoder_->get_num_channels();
audio_stream_info.sample_rate = this->flac_decoder_->get_sample_rate();
audio_stream_info.bits_per_sample = this->flac_decoder_->get_sample_depth();

this->audio_stream_info_ = audio_stream_info;

size_t flac_decoder_output_buffer_min_size = flac_decoder_->get_output_buffer_size();
if (this->internal_buffer_size_ < flac_decoder_output_buffer_min_size * sizeof(int16_t)) {
// Output buffer is not big enough
return FileDecoderState::FAILED;
}

return FileDecoderState::MORE_TO_PROCESS;
}

Expand All @@ -229,8 +233,12 @@ FileDecoderState AudioDecoder::decode_flac_() {
// Not an issue, just needs more data that we'll get next time.
return FileDecoderState::POTENTIALLY_FAILED;
} else if (result > flac::FLAC_DECODER_ERROR_OUT_OF_DATA) {
// Serious error, can't recover
return FileDecoderState::FAILED;
// Corrupted frame, don't retry with current buffer content, wait for new sync
size_t bytes_consumed = this->flac_decoder_->get_bytes_index();
this->input_buffer_current_ += bytes_consumed;
this->input_buffer_length_ = this->flac_decoder_->get_bytes_left();

return FileDecoderState::POTENTIALLY_FAILED;
}

// We have successfully decoded some input data and have new output data
Expand Down
49 changes: 40 additions & 9 deletions esphome/components/nabu/audio_pipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@
namespace esphome {
namespace nabu {

static const size_t QUEUE_COUNT = 10;

static const size_t FILE_BUFFER_SIZE = 32 * 1024;
static const size_t FILE_RING_BUFFER_SIZE = 64 * 1024;
static const size_t BUFFER_SIZE_SAMPLES = 32768;
static const size_t BUFFER_SIZE_BYTES = BUFFER_SIZE_SAMPLES * sizeof(int16_t);

static const uint32_t READER_TASK_STACK_SIZE = 4096;
static const uint32_t DECODER_TASK_STACK_SIZE = 3072;
static const uint32_t RESAMPLER_TASK_STACK_SIZE = 3072;
static const uint32_t READER_TASK_STACK_SIZE = 5 * 1024;
static const uint32_t DECODER_TASK_STACK_SIZE = 3 * 1024;
static const uint32_t RESAMPLER_TASK_STACK_SIZE = 3 * 1024;

static const size_t INFO_ERROR_QUEUE_COUNT = 5;

Expand Down Expand Up @@ -180,11 +178,27 @@ AudioPipelineState AudioPipeline::get_state() {
case InfoErrorSource::DECODER:
if (event.err.has_value()) {
ESP_LOGE(TAG, "Decoder encountered an error: %s", esp_err_to_name(event.err.value()));
} else if (event.audio_stream_info.has_value()) {
}

if (event.audio_stream_info.has_value()) {
ESP_LOGD(TAG, "Decoded audio has %d channels, %" PRId32 " Hz sample rate, and %d bits per sample",
event.audio_stream_info.value().channels, event.audio_stream_info.value().sample_rate,
event.audio_stream_info.value().bits_per_sample);
}

if (event.decoding_err.has_value()) {
switch (event.decoding_err.value()) {
case DecodingError::FAILED_HEADER:
ESP_LOGE(TAG, "Failed to parse the file's header.");
break;
case DecodingError::INCOMPATIBLE_BITS_PER_SAMPLE:
ESP_LOGE(TAG, "Incompatible bits per sample. Only 16 bits per sample is supported");
break;
case DecodingError::INCOMPATIBLE_CHANNELS:
ESP_LOGE(TAG, "Incompatible number of channels. Only 1 or 2 channel audio is supported.");
break;
}
}
break;
case InfoErrorSource::RESAMPLER:
if (event.err.has_value()) {
Expand Down Expand Up @@ -415,6 +429,10 @@ void AudioPipeline::decode_task_(void *params) {
if (decoder_state == AudioDecoderState::FINISHED) {
break;
} else if (decoder_state == AudioDecoderState::FAILED) {
if (!has_stream_info) {
event.decoding_err = DecodingError::FAILED_HEADER;
xQueueSend(this_pipeline->info_error_queue_, &event, portMAX_DELAY);
}
xEventGroupSetBits(this_pipeline->event_group_,
EventGroupBits::DECODER_MESSAGE_ERROR | EventGroupBits::PIPELINE_COMMAND_STOP);
break;
Expand All @@ -427,10 +445,23 @@ void AudioPipeline::decode_task_(void *params) {

// Send the stream information to the pipeline
event.audio_stream_info = this_pipeline->current_audio_stream_info_;
xQueueSend(this_pipeline->info_error_queue_, &event, portMAX_DELAY);

// Inform the resampler that the stream information is available
xEventGroupSetBits(this_pipeline->event_group_, EventGroupBits::DECODER_MESSAGE_LOADED_STREAM_INFO);
if (this_pipeline->current_audio_stream_info_.bits_per_sample != 16) {
// Error state, incompatible bits per sample
event.decoding_err = DecodingError::INCOMPATIBLE_BITS_PER_SAMPLE;
xEventGroupSetBits(this_pipeline->event_group_,
EventGroupBits::DECODER_MESSAGE_ERROR | EventGroupBits::PIPELINE_COMMAND_STOP);
} else if ((this_pipeline->current_audio_stream_info_.channels > 2)) {
// Error state, incompatible number of channels
event.decoding_err = DecodingError::INCOMPATIBLE_CHANNELS;
xEventGroupSetBits(this_pipeline->event_group_,
EventGroupBits::DECODER_MESSAGE_ERROR | EventGroupBits::PIPELINE_COMMAND_STOP);
} else {
// Inform the resampler that the stream information is available
xEventGroupSetBits(this_pipeline->event_group_, EventGroupBits::DECODER_MESSAGE_LOADED_STREAM_INFO);
}

xQueueSend(this_pipeline->info_error_queue_, &event, portMAX_DELAY);
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions esphome/components/nabu/audio_pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,20 @@ enum class InfoErrorSource : uint8_t {
RESAMPLER,
};

enum class DecodingError : uint8_t {
FAILED_HEADER = 0,
INCOMPATIBLE_BITS_PER_SAMPLE,
INCOMPATIBLE_CHANNELS,
};

// Used to pass information from each task.
struct InfoErrorEvent {
InfoErrorSource source;
optional<esp_err_t> err;
optional<media_player::MediaFileType> file_type;
optional<audio::AudioStreamInfo> audio_stream_info;
optional<ResampleInfo> resample_info;
optional<DecodingError> decoding_err;
};

class AudioPipeline {
Expand Down

0 comments on commit 1bd12f6

Please sign in to comment.