Do some prep cleanup for multiple filetypes

custom
jacqueline 2 years ago
parent 7e96482087
commit e12ac1d963
  1. 16
      src/audio/audio_decoder.cpp
  2. 65
      src/audio/fatfs_audio_input.cpp
  3. 8
      src/audio/include/fatfs_audio_input.hpp
  4. 12
      src/codecs/codec.cpp
  5. 9
      src/codecs/include/codec.hpp
  6. 2
      src/codecs/include/mad.hpp
  7. 8
      src/codecs/include/types.hpp
  8. 8
      src/codecs/mad.cpp
  9. 7
      src/database/include/song.hpp
  10. 10
      src/database/tag_parser.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, // 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, // since we can potentially just continue to decode as we were before,
// without any setup overhead. // without any setup overhead.
if (current_codec_ != nullptr && if (current_codec_ != nullptr && current_input_format_) {
current_codec_->CanHandleType(encoded.type)) { auto cur_encoding = std::get<StreamInfo::Encoded>(*current_input_format_);
current_codec_->ResetForNewStream(); if (cur_encoding.type == encoded.type) {
ESP_LOGI(kTag, "reusing existing decoder"); ESP_LOGI(kTag, "reusing existing decoder");
current_input_format_ = info.format;
return true; 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); auto result = codecs::CreateCodecForType(encoded.type);
if (result.has_value()) { if (result.has_value()) {
ESP_LOGI(kTag, "creating new decoder"); current_codec_.reset(result.value());
current_codec_ = std::move(result.value());
} else { } else {
ESP_LOGE(kTag, "no codec for this file"); ESP_LOGE(kTag, "no codec for this file");
return false; return false;
@ -88,9 +90,7 @@ auto AudioDecoder::Process(const std::vector<InputStream>& inputs,
if (!current_input_format_ || *current_input_format_ != info.format) { if (!current_input_format_ || *current_input_format_ != info.format) {
// The input stream has changed! Immediately throw everything away and // The input stream has changed! Immediately throw everything away and
// start from scratch. // start from scratch.
current_input_format_ = info.format;
has_samples_to_send_ = false; has_samples_to_send_ = false;
ProcessStreamInfo(info); ProcessStreamInfo(info);
} }

@ -5,6 +5,7 @@
*/ */
#include "fatfs_audio_input.hpp" #include "fatfs_audio_input.hpp"
#include <stdint.h>
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
@ -23,10 +24,12 @@
#include "audio_element.hpp" #include "audio_element.hpp"
#include "chunk.hpp" #include "chunk.hpp"
#include "song.hpp"
#include "stream_buffer.hpp" #include "stream_buffer.hpp"
#include "stream_event.hpp" #include "stream_event.hpp"
#include "stream_info.hpp" #include "stream_info.hpp"
#include "stream_message.hpp" #include "stream_message.hpp"
#include "tag_parser.hpp"
#include "types.hpp" #include "types.hpp"
static const char* kTag = "SRC"; static const char* kTag = "SRC";
@ -34,7 +37,11 @@ static const char* kTag = "SRC";
namespace audio { namespace audio {
FatfsAudioInput::FatfsAudioInput() FatfsAudioInput::FatfsAudioInput()
: IAudioElement(), current_file_(), is_file_open_(false) {} : IAudioElement(),
current_file_(),
is_file_open_(false),
current_container_(),
current_format_() {}
FatfsAudioInput::~FatfsAudioInput() {} FatfsAudioInput::~FatfsAudioInput() {}
@ -44,6 +51,36 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> bool {
is_file_open_ = false; is_file_open_ = false;
} }
ESP_LOGI(kTag, "opening file %s", path.c_str()); 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<uint8_t>(*tags.channels),
.bits_per_sample = static_cast<uint8_t>(*tags.bits_per_sample),
.sample_rate = static_cast<uint32_t>(*tags.sample_rate),
};
} else {
current_format_ = StreamInfo::Encoded{*stream_type};
}
FRESULT res = f_open(&current_file_, path.c_str(), FA_READ); FRESULT res = f_open(&current_file_, path.c_str(), FA_READ);
if (res != FR_OK) { if (res != FR_OK) {
ESP_LOGE(kTag, "failed to open file! res: %i", res); 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<InputStream>& inputs, auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
OutputStream* output) -> void { OutputStream* output) -> void {
if (!is_file_open_) { if (!is_file_open_) {
// TODO(jacqueline): should we clear the stream format?
// output->prepare({});
return; return;
} }
StreamInfo::Format format = StreamInfo::Encoded{codecs::STREAM_MP3}; if (!output->prepare(*current_format_)) {
if (!output->prepare(format)) {
return; return;
} }
@ -91,12 +125,31 @@ auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
f_close(&current_file_); f_close(&current_file_);
is_file_open_ = false; is_file_open_ = false;
// TODO(jacqueline): MP3 only // 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)); std::fill_n(output->data().begin(), 8, std::byte(0));
output->add(8); output->add(8);
}
events::Dispatch<InputFileFinished, AudioState>({}); events::Dispatch<InputFileFinished, AudioState>({});
} }
} }
auto FatfsAudioInput::ContainerToStreamType(database::Encoding enc)
-> std::optional<codecs::StreamType> {
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 } // namespace audio

@ -18,11 +18,13 @@
#include "ff.h" #include "ff.h"
#include "freertos/message_buffer.h" #include "freertos/message_buffer.h"
#include "freertos/queue.h" #include "freertos/queue.h"
#include "song.hpp"
#include "span.hpp" #include "span.hpp"
#include "audio_element.hpp" #include "audio_element.hpp"
#include "stream_buffer.hpp" #include "stream_buffer.hpp"
#include "stream_info.hpp" #include "stream_info.hpp"
#include "types.hpp"
namespace audio { namespace audio {
@ -42,8 +44,14 @@ class FatfsAudioInput : public IAudioElement {
FatfsAudioInput& operator=(const FatfsAudioInput&) = delete; FatfsAudioInput& operator=(const FatfsAudioInput&) = delete;
private: private:
auto ContainerToStreamType(database::Encoding)
-> std::optional<codecs::StreamType>;
FIL current_file_; FIL current_file_;
bool is_file_open_; bool is_file_open_;
std::optional<database::Encoding> current_container_;
std::optional<StreamInfo::Format> current_format_;
}; };
} // namespace audio } // namespace audio

@ -7,13 +7,19 @@
#include "codec.hpp" #include "codec.hpp"
#include <memory> #include <memory>
#include <optional>
#include "mad.hpp" #include "mad.hpp"
#include "types.hpp"
namespace codecs { namespace codecs {
auto CreateCodecForType(StreamType type) auto CreateCodecForType(StreamType type) -> std::optional<ICodec*> {
-> cpp::result<std::unique_ptr<ICodec>, CreateCodecError> { switch (type) {
return std::make_unique<MadMp3Decoder>(); // TODO. case StreamType::kMp3:
return new MadMp3Decoder();
default:
return {};
}
} }
} // namespace codecs } // namespace codecs

@ -25,8 +25,6 @@ class ICodec {
public: public:
virtual ~ICodec() {} virtual ~ICodec() {}
virtual auto CanHandleType(StreamType type) -> bool = 0;
struct OutputFormat { struct OutputFormat {
uint8_t num_channels; uint8_t num_channels;
uint8_t bits_per_sample; uint8_t bits_per_sample;
@ -37,8 +35,6 @@ class ICodec {
enum ProcessingError { MALFORMED_DATA }; enum ProcessingError { MALFORMED_DATA };
virtual auto ResetForNewStream() -> void = 0;
virtual auto SetInput(cpp::span<const std::byte> input) -> void = 0; virtual auto SetInput(cpp::span<const std::byte> input) -> void = 0;
/* /*
@ -69,9 +65,6 @@ class ICodec {
-> std::pair<std::size_t, bool> = 0; -> std::pair<std::size_t, bool> = 0;
}; };
enum CreateCodecError { UNKNOWN_EXTENSION }; auto CreateCodecForType(StreamType type) -> std::optional<ICodec*>;
auto CreateCodecForType(StreamType type)
-> cpp::result<std::unique_ptr<ICodec>, CreateCodecError>;
} // namespace codecs } // namespace codecs

@ -24,9 +24,7 @@ class MadMp3Decoder : public ICodec {
MadMp3Decoder(); MadMp3Decoder();
~MadMp3Decoder(); ~MadMp3Decoder();
auto CanHandleType(StreamType type) -> bool override;
auto GetOutputFormat() -> std::optional<OutputFormat> override; auto GetOutputFormat() -> std::optional<OutputFormat> override;
auto ResetForNewStream() -> void override;
auto SetInput(cpp::span<const std::byte> input) -> void override; auto SetInput(cpp::span<const std::byte> input) -> void override;
auto GetInputPosition() -> std::size_t override; auto GetInputPosition() -> std::size_t override;
auto ProcessNextFrame() -> cpp::result<bool, ProcessingError> override; auto ProcessNextFrame() -> cpp::result<bool, ProcessingError> override;

@ -10,9 +10,11 @@
namespace codecs { namespace codecs {
enum StreamType { enum class StreamType {
STREAM_MP3, kMp3,
kPcm,
kOgg,
kFlac,
}; };
auto GetStreamTypeFromFilename(std::string filename);
} // namespace codecs } // namespace codecs

@ -42,10 +42,6 @@ MadMp3Decoder::~MadMp3Decoder() {
mad_synth_finish(&synth_); mad_synth_finish(&synth_);
} }
auto MadMp3Decoder::CanHandleType(StreamType type) -> bool {
return type == STREAM_MP3;
}
auto MadMp3Decoder::GetOutputFormat() -> std::optional<OutputFormat> { auto MadMp3Decoder::GetOutputFormat() -> std::optional<OutputFormat> {
if (synth_.pcm.channels == 0 || synth_.pcm.samplerate == 0) { if (synth_.pcm.channels == 0 || synth_.pcm.samplerate == 0) {
return {}; return {};
@ -57,8 +53,6 @@ auto MadMp3Decoder::GetOutputFormat() -> std::optional<OutputFormat> {
}); });
} }
auto MadMp3Decoder::ResetForNewStream() -> void {}
auto MadMp3Decoder::SetInput(cpp::span<const std::byte> input) -> void { auto MadMp3Decoder::SetInput(cpp::span<const std::byte> input) -> void {
mad_stream_buffer(&stream_, mad_stream_buffer(&stream_,
reinterpret_cast<const unsigned char*>(input.data()), reinterpret_cast<const unsigned char*>(input.data()),
@ -115,8 +109,6 @@ auto MadMp3Decoder::WriteOutputSamples(cpp::span<std::byte> output)
} }
for (int channel = 0; channel < synth_.pcm.channels; channel++) { 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 = uint32_t sample_24 =
scaleToBits(synth_.pcm.samples[channel][current_sample_], 24); scaleToBits(synth_.pcm.samples[channel][current_sample_], 24);
output[output_byte++] = static_cast<std::byte>((sample_24 >> 16) & 0xFF); output[output_byte++] = static_cast<std::byte>((sample_24 >> 16) & 0xFF);

@ -36,6 +36,9 @@ typedef uint32_t SongId;
enum class Encoding { enum class Encoding {
kUnsupported = 0, kUnsupported = 0,
kMp3 = 1, kMp3 = 1,
kWav = 2,
kOgg = 3,
kFlac = 4,
}; };
/* /*
@ -53,6 +56,10 @@ struct SongTags {
std::optional<std::string> artist; std::optional<std::string> artist;
std::optional<std::string> album; std::optional<std::string> album;
std::optional<int> channels;
std::optional<int> sample_rate;
std::optional<int> bits_per_sample;
/* /*
* Returns a hash of the 'identifying' tags of this song. That is, a hash that * 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 * can be used to determine if one song is likely the same as another, across

@ -107,6 +107,16 @@ auto TagParserImpl::ReadAndParseTags(const std::string& path, SongTags* out)
out->encoding = Encoding::kUnsupported; 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; return true;
} }

Loading…
Cancel
Save