summaryrefslogtreecommitdiff
path: root/src/audio/include
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2022-11-17 13:30:16 +1100
committerjacqueline <me@jacqueline.id.au>2022-11-17 13:30:16 +1100
commit60169cdd91c46fb9c5cd22c2864e47b8e2dc413e (patch)
treefd8d11915c9bcbc9b89cad27921de5f7fada0d78 /src/audio/include
parent1d340db87193ca589c3bd5df241ae0ed51a3100d (diff)
downloadtangara-fw-60169cdd91c46fb9c5cd22c2864e47b8e2dc413e.tar.gz
WIP on our own pipeline
Diffstat (limited to 'src/audio/include')
-rw-r--r--src/audio/include/audio_backend.hpp20
-rw-r--r--src/audio/include/audio_decoder.hpp34
-rw-r--r--src/audio/include/audio_element.hpp73
-rw-r--r--src/audio/include/audio_output.hpp29
-rw-r--r--src/audio/include/audio_playback.hpp97
-rw-r--r--src/audio/include/audio_task.hpp15
-rw-r--r--src/audio/include/fatfs_audio_input.hpp49
7 files changed, 317 insertions, 0 deletions
diff --git a/src/audio/include/audio_backend.hpp b/src/audio/include/audio_backend.hpp
new file mode 100644
index 00000000..85985cc2
--- /dev/null
+++ b/src/audio/include/audio_backend.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <cstdint>
+namespace drivers {
+
+class IAudioBackend {
+ public:
+ virtual ~IAudioBackend() {}
+
+ enum SampleRate {};
+ enum BitDepth {};
+
+ virtual auto Configure(SampleRate sample_rate, BitDepth bit_depth)
+ -> bool = 0;
+ virtual auto WritePcmData(uint8_t* data, size_t length) -> bool = 0;
+
+ virtual auto SetVolume(uint8_t percent) -> void = 0;
+};
+
+} // namespace drivers
diff --git a/src/audio/include/audio_decoder.hpp b/src/audio/include/audio_decoder.hpp
new file mode 100644
index 00000000..f460f9e9
--- /dev/null
+++ b/src/audio/include/audio_decoder.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <cstddef>
+#include "ff.h"
+
+namespace audio {
+
+enum SampleRate {};
+enum BitDepth {};
+
+struct PcmStreamHeader {
+ SampleRate sample_rate;
+ BitDepth bit_depth;
+ bool configure_now;
+};
+
+class AudioDecoder {
+ public:
+ AudioDecoder();
+ ~AudioDecoder();
+
+ auto SetSource(RingbufHandle_t& source) -> void;
+
+ enum Status {};
+ auto ProcessChunk() -> Status;
+
+ auto GetOutputStream() const -> RingbufHandle_t;
+
+ private:
+ RingbufHandle_t input_;
+ RingbufHandle_t output_;
+};
+
+} // namespace audio
diff --git a/src/audio/include/audio_element.hpp b/src/audio/include/audio_element.hpp
new file mode 100644
index 00000000..ea4256ac
--- /dev/null
+++ b/src/audio/include/audio_element.hpp
@@ -0,0 +1,73 @@
+#pragma once
+
+#include <cstdint>
+
+namespace audio {
+
+extern const std::size_t kMaxFrameSize;
+
+class IAudioElement {
+ public:
+ virtual ~IAudioElement();
+
+ enum CommandType {
+ /*
+ * Sets the sequence number of the most recent byte stream. Any commands
+ * received that have a lower sequence number than this will be discarded.
+ */
+ SEQUENCE_NUMBER,
+ /*
+ * Instructs this element to read a specific number of bytes from its
+ * input buffer.
+ */
+ READ_FRAME,
+ /*
+ * Represents an element-specific command. This handling of this is
+ * delegated to element implementations.
+ */
+ ELEMENT,
+ /* Instructs this element to shut down. */
+ QUIT,
+ };
+
+ struct Command {
+ CommandType type;
+ uint8_t sequence_number;
+ union {
+ void* data;
+ std::size_t frame_size;
+ };
+ };
+
+ /*
+ * Returns a queue that should be used for all communication with this
+ * element.
+ */
+ virtual auto InputCommandQueue() -> QueueHandle_t = 0;
+
+ /*
+ * Returns a buffer that will be used to stream input bytes to this element.
+ * This may be NULL, if this element represents a source, e.g. a FATFS
+ * reader.
+ */
+ virtual auto InputBuffer() -> StreamBufferHandle_t = 0;
+
+ /*
+ * Called when an element-specific command has been received.
+ */
+ virtual auto ProcessElementCommand(void* command) -> void = 0;
+
+ virtual auto SkipElementCommand(void* command) -> void = 0;
+
+ /*
+ * Called with the result of a read bytes command.
+ */
+ virtual auto ProcessData(uint8_t* data, uint16_t length) -> void = 0;
+
+ /*
+ * Called periodically when there are no pending commands.
+ */
+ virtual auto ProcessIdle() -> void = 0;
+};
+
+} // namespace audio
diff --git a/src/audio/include/audio_output.hpp b/src/audio/include/audio_output.hpp
new file mode 100644
index 00000000..82dca82d
--- /dev/null
+++ b/src/audio/include/audio_output.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <cstdint>
+#include <memory>
+
+#include "audio_common.h"
+#include "audio_element.h"
+
+namespace drivers {
+
+class IAudioOutput {
+ public:
+ IAudioOutput(audio_element_handle_t element) : element_(element) {}
+ virtual ~IAudioOutput() { audio_element_deinit(element_); }
+
+ auto GetAudioElement() -> audio_element_handle_t { return element_; }
+
+ virtual auto SetVolume(uint8_t volume) -> void = 0;
+ virtual auto GetVolume() const -> uint8_t { return volume_; }
+
+ virtual auto Configure(audio_element_info_t& info) -> void = 0;
+ virtual auto SetSoftMute(bool enabled) -> void = 0;
+
+ protected:
+ audio_element_handle_t element_;
+ uint8_t volume_;
+};
+
+} // namespace drivers
diff --git a/src/audio/include/audio_playback.hpp b/src/audio/include/audio_playback.hpp
new file mode 100644
index 00000000..41ab46d2
--- /dev/null
+++ b/src/audio/include/audio_playback.hpp
@@ -0,0 +1,97 @@
+#pragma once
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "audio_common.h"
+#include "audio_element.h"
+#include "audio_event_iface.h"
+#include "audio_pipeline.h"
+#include "esp_err.h"
+#include "fatfs_stream.h"
+#include "i2s_stream.h"
+#include "mp3_decoder.h"
+#include "result.hpp"
+
+#include "audio_output.hpp"
+#include "dac.hpp"
+#include "storage.hpp"
+
+namespace drivers {
+
+/*
+ * Sends an I2S audio stream to the DAC. Includes basic controls for pausing
+ * and resuming the stream, as well as support for gapless playback of the next
+ * queued song, but does not implement any kind of sophisticated queing or
+ * playback control; these should be handled at a higher level.
+ */
+class AudioPlayback {
+ public:
+ enum Error { FATFS_INIT, I2S_INIT, PIPELINE_INIT };
+ static auto create(std::unique_ptr<IAudioOutput> output)
+ -> cpp::result<std::unique_ptr<AudioPlayback>, Error>;
+
+ AudioPlayback(std::unique_ptr<IAudioOutput>& output,
+ audio_pipeline_handle_t pipeline,
+ audio_element_handle_t source_element,
+ audio_event_iface_handle_t event_interface);
+ ~AudioPlayback();
+
+ /*
+ * Replaces any currently playing file with the one given, and begins
+ * playback.
+ *
+ * Any value set in `set_next_file` is cleared by this method.
+ */
+ auto Play(const std::string& filename) -> void;
+ /* Toogle between resumed and paused. */
+ auto Toggle() -> void;
+ auto Resume() -> void;
+ auto Pause() -> void;
+
+ enum PlaybackState { PLAYING, PAUSED, STOPPED };
+ auto GetPlaybackState() const -> PlaybackState;
+
+ /*
+ * Handles any pending events from the underlying audio pipeline. This must
+ * be called regularly in order to handle configuring the I2S stream for
+ * different audio types (e.g. sample rate, bit depth), and for gapless
+ * playback.
+ */
+ auto ProcessEvents(uint16_t max_time_ms) -> void;
+
+ /*
+ * Sets the file that should be played immediately after the current file
+ * finishes. This is used for gapless playback
+ */
+ auto SetNextFile(const std::string& filename) -> void;
+
+ auto SetVolume(uint8_t volume) -> void;
+ auto GetVolume() const -> uint8_t;
+
+ // Not copyable or movable.
+ AudioPlayback(const AudioPlayback&) = delete;
+ AudioPlayback& operator=(const AudioPlayback&) = delete;
+
+ private:
+ PlaybackState playback_state_;
+
+ enum Decoder { NONE, MP3, AMR, OPUS, OGG, FLAC, WAV, AAC };
+ auto GetDecoderForFilename(std::string filename) const -> Decoder;
+ auto CreateDecoder(Decoder decoder) const -> audio_element_handle_t;
+ auto ReconfigurePipeline(Decoder decoder) -> void;
+
+ std::unique_ptr<IAudioOutput> output_;
+
+ std::string next_filename_ = "";
+
+ audio_pipeline_handle_t pipeline_;
+ audio_element_handle_t source_element_;
+ audio_event_iface_handle_t event_interface_;
+
+ audio_element_handle_t decoder_ = nullptr;
+ Decoder decoder_type_ = NONE;
+};
+
+} // namespace drivers
diff --git a/src/audio/include/audio_task.hpp b/src/audio/include/audio_task.hpp
new file mode 100644
index 00000000..79604f33
--- /dev/null
+++ b/src/audio/include/audio_task.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <memory>
+
+#include "audio_element.hpp"
+
+namespace audio {
+
+struct AudioTaskArgs {
+ std::shared_ptr<IAudioElement>& element;
+};
+
+void audio_task(void* args);
+
+} // namespace audio
diff --git a/src/audio/include/fatfs_audio_input.hpp b/src/audio/include/fatfs_audio_input.hpp
new file mode 100644
index 00000000..ed4da55e
--- /dev/null
+++ b/src/audio/include/fatfs_audio_input.hpp
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <cstdint>
+#include <memory>
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/queue.h"
+#include "freertos/stream_buffer.h"
+
+#include "audio_element.hpp"
+#include "storage.hpp"
+
+namespace audio {
+
+class FatfsAudioInput : public IAudioElement {
+ public:
+ struct InputCommand {
+ std::string filename;
+ };
+
+ struct OutputCommand {
+ // TODO: does this actually need any special output?
+ };
+
+ FatfsAudioInput(std::shared_ptr<drivers::SdStorage> storage);
+ ~FatfsAudioInput();
+
+ auto OutputCommandQueue() -> QueueHandle_t;
+ auto OutputBuffer() -> StreamBufferHandle_t;
+
+ private:
+ std::shared_ptr<drivers::SdStorage> storage_;
+
+ uint8_t current_sequence = 0;
+
+ uint8_t* input_queue_memory_;
+ StaticQueue_t input_queue_metadata_;
+ QueueHandle_t input_queue_;
+
+ uint8_t* output_queue_memory_;
+ StaticQueue_t output_queue_metadata_;
+ QueueHandle_t output_queue_;
+
+ uint8_t* output_buffer_memory_;
+ StaticStreamBuffer_t output_buffer_metadata_;
+ StreamBufferHandle_t output_buffer_;
+};
+
+} // namespace audio