summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio/audio_decoder.cpp20
-rw-r--r--src/audio/fatfs_audio_input.cpp69
-rw-r--r--src/audio/include/fatfs_audio_input.hpp8
-rw-r--r--src/codecs/codec.cpp12
-rw-r--r--src/codecs/include/codec.hpp9
-rw-r--r--src/codecs/include/mad.hpp2
-rw-r--r--src/codecs/include/types.hpp8
-rw-r--r--src/codecs/mad.cpp8
-rw-r--r--src/database/include/song.hpp7
-rw-r--r--src/database/tag_parser.cpp10
10 files changed, 111 insertions, 42 deletions
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<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;
- // 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<InputStream>& 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 <stdint.h>
#include <algorithm>
#include <cstdint>
@@ -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<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);
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<InputStream>& 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<InputStream>& inputs,
f_close(&current_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<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
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<codecs::StreamType>;
+
FIL current_file_;
bool is_file_open_;
+
+ std::optional<database::Encoding> current_container_;
+ std::optional<StreamInfo::Format> 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 <memory>
+#include <optional>
#include "mad.hpp"
+#include "types.hpp"
namespace codecs {
-auto CreateCodecForType(StreamType type)
- -> cpp::result<std::unique_ptr<ICodec>, CreateCodecError> {
- return std::make_unique<MadMp3Decoder>(); // TODO.
+auto CreateCodecForType(StreamType type) -> std::optional<ICodec*> {
+ 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<const std::byte> input) -> void = 0;
/*
@@ -69,9 +65,6 @@ class ICodec {
-> std::pair<std::size_t, bool> = 0;
};
-enum CreateCodecError { UNKNOWN_EXTENSION };
-
-auto CreateCodecForType(StreamType type)
- -> cpp::result<std::unique_ptr<ICodec>, CreateCodecError>;
+auto CreateCodecForType(StreamType type) -> std::optional<ICodec*>;
} // 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<OutputFormat> override;
- auto ResetForNewStream() -> void override;
auto SetInput(cpp::span<const std::byte> input) -> void override;
auto GetInputPosition() -> std::size_t override;
auto ProcessNextFrame() -> cpp::result<bool, ProcessingError> 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<OutputFormat> {
if (synth_.pcm.channels == 0 || synth_.pcm.samplerate == 0) {
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 {
mad_stream_buffer(&stream_,
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++) {
- // 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<std::byte>((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<std::string> artist;
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
* 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;
}