parent
1d340db871
commit
60169cdd91
@ -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…
Reference in new issue