Do some prep cleanup for multiple filetypes
This commit is contained in:
+10
-10
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(¤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<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(¤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<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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
|
||||
namespace codecs {
|
||||
|
||||
enum StreamType {
|
||||
STREAM_MP3,
|
||||
enum class StreamType {
|
||||
kMp3,
|
||||
kPcm,
|
||||
kOgg,
|
||||
kFlac,
|
||||
};
|
||||
|
||||
auto GetStreamTypeFromFilename(std::string filename);
|
||||
} // namespace codecs
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user