WIP on our own pipeline

custom
jacqueline 2 years ago
parent 1d340db871
commit 60169cdd91
  1. 59
      src/audio/README.md
  2. 78
      src/audio/audio_task.cpp
  3. 79
      src/audio/fatfs_audio_input.cpp
  4. 20
      src/audio/include/audio_backend.hpp
  5. 34
      src/audio/include/audio_decoder.hpp
  6. 73
      src/audio/include/audio_element.hpp
  7. 0
      src/audio/include/audio_output.hpp
  8. 0
      src/audio/include/audio_playback.hpp
  9. 15
      src/audio/include/audio_task.hpp
  10. 49
      src/audio/include/fatfs_audio_input.hpp
  11. 0
      src/codecs/CMakeLists.txt
  12. 3
      src/codecs/README.md
  13. 17
      src/codecs/codec.hpp
  14. 40
      src/drivers/include/fatfs_audio_input.hpp

@ -0,0 +1,59 @@
FatfsAudioReader
- input if a queue of filenames.
- output is a cbor stream
- 1 header, like "this is a new file! this is the file type!
- followed by length-prefixed chunks of bytes
- runs in a task, which prompts it to read/write one chunk, then returns.
- task watches for kill signal, owns storage, etc.
AudioDecoder
- input is the chunked bytes above.
- output is also a cbor stream
- 1 header, which is like a reconfiguration packet thing.
- "data that follows is this depth, this sample rate"
- also indicates whether the configuration is 'sudden' for soft muting?
- then length-prefixed chunks of bytes
AudioOutput
- input is the output of the decoder
- outputs via writing to i2s_write, which copies data to a dma buffer
- therefore, safe for us to consume any kind of reconfiguration here.
- only issue is that we will need to wait for the dma buffers to drain before
we can reconfigure the driver. (i2s_zero_dma_buffer)
- this is important for i2s speed; we should avoid extra copy steps for the raw
- pcm stream
- input therefore needs to be two channels: one configuration channel, one bytes
channel
How do things like seeking, and progress work?
- Reader knows where we are in terms of file size and position
- Decoder knows sample rate, frames, etc. for knowing how that maps into
- the time progress
- Output knows where we are as well in a sense, but only in terms of the PCM
output. this doesn't correspond to anything very well.
So, to seek:
- come up with your position. this is likely "where we are plus 10", or a
specific timecode. the decoder has what we need for the byte position of this
- tell the reader "hey we need to be in this file at this byte position
- reader clears its own output buffer (since it's been doing readahead) and
starts again at the given location
For current position, the decoder will need to track where in the file it's up
to.
HEADERS + DATA:
- cbor seems sensible for headers. allocate a little working buffer, encode the
data, then send it out on the ringbuffer.
- the data itself is harder, since tinycbor doesn't support writing chunked indefinite
length stuff. this is a problem bc we need to give cbor the buffer up front, but
we don't know exactly how long things will be, so it ends up being slightly awkward
and inefficient.
- we could also just like... write the struct i guess? that might be okay.
- gives us a format like <TYPE ENUM> <LENGTH> <DATA>
- could be smart with the type, use like a 32 bit int, and encode the length
- in there?
- then from the reader's perspective, it's:
- read 4 bytes, work out what's next
- read the next X bytes

@ -0,0 +1,78 @@
#include "audio_task.hpp"
#include <stdlib.h>
#include <cstdint>
#include "esp_heap_caps.h"
#include "freertos/portmacro.h"
#include "freertos/queue.h"
#include "freertos/stream_buffer.h"
#include "audio_element.hpp"
namespace audio {
static const TickType_t kCommandWaitTicks = 1;
void audio_task(void* args) {
AudioTaskArgs* real_args = reinterpret_cast<AudioTaskArgs*>(args);
std::shared_ptr<IAudioElement> element = real_args->element;
delete real_args;
QueueHandle_t commands = element->InputCommandQueue();
StreamBufferHandle_t stream = element->InputBuffer();
// TODO: think about overflow.
uint8_t current_sequence_number;
uint8_t* frame_buffer =
(uint8_t*)heap_caps_malloc(kFrameSize, MALLOC_CAP_SPIRAM);
while (1) {
IAudioElement::Command command;
if (!xQueueReceive(commands, &command, kCommandWaitTicks)) {
element->ProcessIdle();
continue;
};
if (command.type == IAudioElement::SEQUENCE_NUMBER) {
if (command.sequence_number > current_sequence_number) {
current_sequence_number = command.sequence_number;
}
continue;
}
if (command.type == IAudioElement::READ) {
assert(command.read_size <= kFrameSize);
assert(stream != NULL);
xStreamBufferReceive(stream, &frame_buffer, command.read_size, 0);
if (command.sequence_number == current_sequence_number) {
element->ProcessData(frame_buffer, command.read_size);
}
continue;
}
if (command.type == IAudioElement::ELEMENT) {
assert(command.data != NULL);
if (command.sequence_number == current_sequence_number) {
element->ProcessElementCommand(command.data);
} else {
element->SkipElementCommand(command.data);
}
}
if (command.type == IAudioElement::QUIT) {
break;
}
}
element = nullptr;
free(frame_buffer);
xTaskDelete(NULL);
}
} // namespace audio

@ -0,0 +1,79 @@
#include "fatfs_audio_input.hpp"
#include <memory>
#include "esp-adf/components/input_key_service/include/input_key_service.h"
#include "esp_heap_caps.h"
#include "audio_element.hpp"
namespace audio {
static const size_t kQueueItems = 0;
static constexpr size_t kQueueItemSize = sizeof(IAudioElement::Command);
static constexpr size_t kQueueSize = kQueueItems * kQueueItemSize;
static const size_t kOutputBufferSize = 1024;
FatfsAudioInput::FatfsAudioInput(std::shared_ptr<drivers::SdStorage> storage)
: IAudioElement(), storage_(storage) {
input_queue_memory_ = heap_caps_malloc(kQueueSize, MALLOC_CAP_SPIRAM);
input_queue_ = xQueueCreateStatic(
kQueueItems, kQueueItemSize, input_queue_memory_, &input_queue_metadata_);
output_queue_memory_ = heap_caps_malloc(kQueueSize, MALLOC_CAP_SPIRAM);
output_queue_ =
xQueueCreateStatic(kQueueItems, kQueueItemSize, output_queue_memory_,
&output_queue_metadata_);
output_buffer_memory_ =
heap_caps_malloc(kOutputBufferSize, MALLOC_CAP_SPIRAM);
output_buffer_ =
xStreamBufferCreateStatic(kOutputBufferSize - 1, 1, output_buffer_memory_,
&output_buffer_metadata_);
}
FatfsAudioInput::~FatfsAudioInput() {
vStreamBufferDelete(output_buffer_);
free(output_buffer_memory_);
vQueueDelete(output_queue_);
free(output_queue_memory_);
vQueueDelete(input_queue_);
free(input_queue_memory_);
}
auto FatfsAudioInput::InputCommandQueue() -> QueueHandle_t {
return input_queue_;
}
auto FatfsAudioInput::OutputCommandQueue() -> QueueHandle_t {
return output_queue_;
}
auto FatfsAudioInput::InputBuffer() -> StreamBufferHandle_t {
return nullptr;
}
auto FatfsAudioInput::OutputBuffer() -> StreamBufferHandle_t {
return output_buffer_;
}
auto FatfsAudioInput::ProcessElementCommand(void* command) -> void {
InputCommand *real = std::reinterpret_pointer_cast<input_key_service_add_key*>(command);
// TODO.
}
auto FatfsAudioInput::SkipElementCommand(void* command) -> void {
InputCommand *real = std::reinterpret_pointer_cast<input_key_service_add_key*>(command);
delete real;
}
auto FatfsAudioInput::ProcessData(uint8_t* data, uint16_t length) -> void {
// Not implemented.
}
auto FatfsAudioInput::ProcessIdle() -> void {
// TODO.
}
} // namespace audio

@ -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

@ -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

@ -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

@ -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

@ -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

@ -0,0 +1,3 @@
# Software Codecs
This component contains a collection of software decoders for various

@ -0,0 +1,17 @@
#pragma once
#include <cstddef>
#include <cstdint>
namespace codecs {
class IAudioDecoder {
public:
virtual ~IAudioDecoder() {}
virtual auto ProcessData(
uint8_t *input,
size_t input_len,
uint8_t *output) -> size_t = 0;
};
} // namespace codecs

@ -0,0 +1,40 @@
#pragma once
namespace drivers {
class FatfsAudioInput {
public:
FatfsAudioInput(std::shared_ptr<SdStorage> storage);
~FatfsAudioInput();
enum Status {
/*
* Successfully read data into the output buffer, and there is still
* data remaining in the file.
*/
OKAY,
/*
* The ringbuffer was full. No data was read.
*/
RINGBUF_FULL,
/*
* Some data may have been read into the output buffer, but the file is
* now empty.
*/
FILE_EMPTY,
};
auto Process() -> Status;
auto GetOutputBuffer() -> RingbufHandle_t;
private:
std::shared_ptr<SdStorage> storage_;
RingbufHandle_t output_;
std::string path_;
};
} // namespace drivers
Loading…
Cancel
Save