diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp index af026262..eb19b75f 100644 --- a/src/audio/audio_decoder.cpp +++ b/src/audio/audio_decoder.cpp @@ -50,18 +50,20 @@ auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info) -> bool { // Reuse the existing codec if we can. This will help with gapless playback, // since we can potentially just continue to decode as we were before, // without any setup overhead. - if (current_codec_ != nullptr && - current_codec_->CanHandleType(encoded.type)) { - current_codec_->ResetForNewStream(); - ESP_LOGI(kTag, "reusing existing decoder"); - return true; + if (current_codec_ != nullptr && current_input_format_) { + auto cur_encoding = std::get(*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; - // TODO: use audio type from stream + ESP_LOGI(kTag, "creating new decoder"); auto result = codecs::CreateCodecForType(encoded.type); if (result.has_value()) { - ESP_LOGI(kTag, "creating new decoder"); - current_codec_ = std::move(result.value()); + current_codec_.reset(result.value()); } else { ESP_LOGE(kTag, "no codec for this file"); return false; @@ -88,9 +90,7 @@ auto AudioDecoder::Process(const std::vector& inputs, if (!current_input_format_ || *current_input_format_ != info.format) { // The input stream has changed! Immediately throw everything away and // start from scratch. - current_input_format_ = info.format; has_samples_to_send_ = false; - ProcessStreamInfo(info); } diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp index 9affcf1a..a89858ca 100644 --- a/src/audio/fatfs_audio_input.cpp +++ b/src/audio/fatfs_audio_input.cpp @@ -5,6 +5,7 @@ */ #include "fatfs_audio_input.hpp" +#include #include #include @@ -23,10 +24,12 @@ #include "audio_element.hpp" #include "chunk.hpp" +#include "song.hpp" #include "stream_buffer.hpp" #include "stream_event.hpp" #include "stream_info.hpp" #include "stream_message.hpp" +#include "tag_parser.hpp" #include "types.hpp" static const char* kTag = "SRC"; @@ -34,7 +37,11 @@ static const char* kTag = "SRC"; namespace audio { FatfsAudioInput::FatfsAudioInput() - : IAudioElement(), current_file_(), is_file_open_(false) {} + : IAudioElement(), + current_file_(), + is_file_open_(false), + current_container_(), + current_format_() {} FatfsAudioInput::~FatfsAudioInput() {} @@ -44,6 +51,36 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> bool { is_file_open_ = false; } ESP_LOGI(kTag, "opening file %s", path.c_str()); + + database::TagParserImpl tag_parser; + database::SongTags tags; + if (!tag_parser.ReadAndParseTags(path, &tags)) { + ESP_LOGE(kTag, "failed to read tags"); + return false; + } + + auto stream_type = ContainerToStreamType(tags.encoding); + if (!stream_type.has_value()) { + return false; + } + + current_container_ = tags.encoding; + + if (*stream_type == codecs::StreamType::kPcm && tags.channels && + tags.bits_per_sample && tags.channels) { + // WAV files are a special case bc they contain raw PCM streams. These don't + // need decoding, but we *do* need to parse the PCM format from the header. + // TODO(jacqueline): Maybe we should have a decoder for this just to deal + // with endianness differences? + current_format_ = StreamInfo::Pcm{ + .channels = static_cast(*tags.channels), + .bits_per_sample = static_cast(*tags.bits_per_sample), + .sample_rate = static_cast(*tags.sample_rate), + }; + } else { + current_format_ = StreamInfo::Encoded{*stream_type}; + } + FRESULT res = f_open(¤t_file_, path.c_str(), FA_READ); if (res != FR_OK) { ESP_LOGE(kTag, "failed to open file! res: %i", res); @@ -61,13 +98,10 @@ auto FatfsAudioInput::NeedsToProcess() const -> bool { auto FatfsAudioInput::Process(const std::vector& inputs, OutputStream* output) -> void { if (!is_file_open_) { - // TODO(jacqueline): should we clear the stream format? - // output->prepare({}); return; } - StreamInfo::Format format = StreamInfo::Encoded{codecs::STREAM_MP3}; - if (!output->prepare(format)) { + if (!output->prepare(*current_format_)) { return; } @@ -91,12 +125,31 @@ auto FatfsAudioInput::Process(const std::vector& inputs, f_close(¤t_file_); is_file_open_ = false; - // TODO(jacqueline): MP3 only - std::fill_n(output->data().begin(), 8, std::byte(0)); - output->add(8); + // HACK: libmad requires an 8 byte padding at the end of each file. + if (current_container_ == database::Encoding::kMp3) { + std::fill_n(output->data().begin(), 8, std::byte(0)); + output->add(8); + } events::Dispatch({}); } } +auto FatfsAudioInput::ContainerToStreamType(database::Encoding enc) + -> std::optional { + switch (enc) { + case database::Encoding::kMp3: + return codecs::StreamType::kMp3; + case database::Encoding::kWav: + return codecs::StreamType::kPcm; + case database::Encoding::kFlac: + return codecs::StreamType::kFlac; + case database::Encoding::kOgg: + return codecs::StreamType::kOgg; + case database::Encoding::kUnsupported: + default: + return {}; + } +} + } // namespace audio diff --git a/src/audio/include/fatfs_audio_input.hpp b/src/audio/include/fatfs_audio_input.hpp index bfc0064e..1f9f36a1 100644 --- a/src/audio/include/fatfs_audio_input.hpp +++ b/src/audio/include/fatfs_audio_input.hpp @@ -18,11 +18,13 @@ #include "ff.h" #include "freertos/message_buffer.h" #include "freertos/queue.h" +#include "song.hpp" #include "span.hpp" #include "audio_element.hpp" #include "stream_buffer.hpp" #include "stream_info.hpp" +#include "types.hpp" namespace audio { @@ -42,8 +44,14 @@ class FatfsAudioInput : public IAudioElement { FatfsAudioInput& operator=(const FatfsAudioInput&) = delete; private: + auto ContainerToStreamType(database::Encoding) + -> std::optional; + FIL current_file_; bool is_file_open_; + + std::optional current_container_; + std::optional current_format_; }; } // namespace audio diff --git a/src/codecs/codec.cpp b/src/codecs/codec.cpp index 4f9e8892..73bc9032 100644 --- a/src/codecs/codec.cpp +++ b/src/codecs/codec.cpp @@ -7,13 +7,19 @@ #include "codec.hpp" #include +#include #include "mad.hpp" +#include "types.hpp" namespace codecs { -auto CreateCodecForType(StreamType type) - -> cpp::result, CreateCodecError> { - return std::make_unique(); // TODO. +auto CreateCodecForType(StreamType type) -> std::optional { + switch (type) { + case StreamType::kMp3: + return new MadMp3Decoder(); + default: + return {}; + } } } // namespace codecs diff --git a/src/codecs/include/codec.hpp b/src/codecs/include/codec.hpp index c8a68ff3..31c67e13 100644 --- a/src/codecs/include/codec.hpp +++ b/src/codecs/include/codec.hpp @@ -25,8 +25,6 @@ class ICodec { public: virtual ~ICodec() {} - virtual auto CanHandleType(StreamType type) -> bool = 0; - struct OutputFormat { uint8_t num_channels; uint8_t bits_per_sample; @@ -37,8 +35,6 @@ class ICodec { enum ProcessingError { MALFORMED_DATA }; - virtual auto ResetForNewStream() -> void = 0; - virtual auto SetInput(cpp::span input) -> void = 0; /* @@ -69,9 +65,6 @@ class ICodec { -> std::pair = 0; }; -enum CreateCodecError { UNKNOWN_EXTENSION }; - -auto CreateCodecForType(StreamType type) - -> cpp::result, CreateCodecError>; +auto CreateCodecForType(StreamType type) -> std::optional; } // namespace codecs diff --git a/src/codecs/include/mad.hpp b/src/codecs/include/mad.hpp index ea16cdc8..5ba4db84 100644 --- a/src/codecs/include/mad.hpp +++ b/src/codecs/include/mad.hpp @@ -24,9 +24,7 @@ class MadMp3Decoder : public ICodec { MadMp3Decoder(); ~MadMp3Decoder(); - auto CanHandleType(StreamType type) -> bool override; auto GetOutputFormat() -> std::optional override; - auto ResetForNewStream() -> void override; auto SetInput(cpp::span input) -> void override; auto GetInputPosition() -> std::size_t override; auto ProcessNextFrame() -> cpp::result override; diff --git a/src/codecs/include/types.hpp b/src/codecs/include/types.hpp index 66f0c840..61d36a28 100644 --- a/src/codecs/include/types.hpp +++ b/src/codecs/include/types.hpp @@ -10,9 +10,11 @@ namespace codecs { -enum StreamType { - STREAM_MP3, +enum class StreamType { + kMp3, + kPcm, + kOgg, + kFlac, }; -auto GetStreamTypeFromFilename(std::string filename); } // namespace codecs diff --git a/src/codecs/mad.cpp b/src/codecs/mad.cpp index 5044c22f..fbe85213 100644 --- a/src/codecs/mad.cpp +++ b/src/codecs/mad.cpp @@ -42,10 +42,6 @@ MadMp3Decoder::~MadMp3Decoder() { mad_synth_finish(&synth_); } -auto MadMp3Decoder::CanHandleType(StreamType type) -> bool { - return type == STREAM_MP3; -} - auto MadMp3Decoder::GetOutputFormat() -> std::optional { if (synth_.pcm.channels == 0 || synth_.pcm.samplerate == 0) { return {}; @@ -57,8 +53,6 @@ auto MadMp3Decoder::GetOutputFormat() -> std::optional { }); } -auto MadMp3Decoder::ResetForNewStream() -> void {} - auto MadMp3Decoder::SetInput(cpp::span input) -> void { mad_stream_buffer(&stream_, reinterpret_cast(input.data()), @@ -115,8 +109,6 @@ auto MadMp3Decoder::WriteOutputSamples(cpp::span output) } for (int channel = 0; channel < synth_.pcm.channels; channel++) { - // TODO(jacqueline): output 24 bit samples when (if?) we have a downmix - // step in the pipeline. uint32_t sample_24 = scaleToBits(synth_.pcm.samples[channel][current_sample_], 24); output[output_byte++] = static_cast((sample_24 >> 16) & 0xFF); diff --git a/src/database/include/song.hpp b/src/database/include/song.hpp index 2791c0ca..d03660dc 100644 --- a/src/database/include/song.hpp +++ b/src/database/include/song.hpp @@ -36,6 +36,9 @@ typedef uint32_t SongId; enum class Encoding { kUnsupported = 0, kMp3 = 1, + kWav = 2, + kOgg = 3, + kFlac = 4, }; /* @@ -53,6 +56,10 @@ struct SongTags { std::optional artist; std::optional album; + std::optional channels; + std::optional sample_rate; + std::optional bits_per_sample; + /* * Returns a hash of the 'identifying' tags of this song. That is, a hash that * can be used to determine if one song is likely the same as another, across diff --git a/src/database/tag_parser.cpp b/src/database/tag_parser.cpp index d6109671..27d4163f 100644 --- a/src/database/tag_parser.cpp +++ b/src/database/tag_parser.cpp @@ -107,6 +107,16 @@ auto TagParserImpl::ReadAndParseTags(const std::string& path, SongTags* out) out->encoding = Encoding::kUnsupported; } + if (ctx.channels > 0) { + out->channels = ctx.channels; + } + if (ctx.samplerate > 0) { + out->sample_rate = ctx.samplerate; + } + if (ctx.bitrate > 0) { + out->bits_per_sample = ctx.bitrate; + } + return true; }