better cbor handling
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "audio_decoder.cpp" "audio_task.cpp" "chunk.cpp" "fatfs_audio_input.cpp"
|
||||
"stream_info.cpp"
|
||||
"stream_info.cpp" "stream_message.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES "codecs" "drivers" "cbor_wrapper" "result" "tasks")
|
||||
REQUIRES "codecs" "drivers" "cbor" "result" "tasks")
|
||||
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
|
||||
|
||||
@@ -39,8 +39,8 @@ auto AudioDecoder::SetOutputBuffer(MessageBufferHandle_t* buffer) -> void {
|
||||
output_buffer_ = buffer;
|
||||
}
|
||||
|
||||
auto AudioDecoder::ProcessStreamInfo(StreamInfo&& info)
|
||||
-> cpp::result<void, StreamError> {
|
||||
auto AudioDecoder::ProcessStreamInfo(StreamInfo& info)
|
||||
-> cpp::result<void, AudioProcessingError> {
|
||||
stream_info_ = info;
|
||||
|
||||
// Reuse the existing codec if we can. This will help with gapless playback,
|
||||
@@ -62,7 +62,7 @@ auto AudioDecoder::ProcessStreamInfo(StreamInfo&& info)
|
||||
}
|
||||
|
||||
auto AudioDecoder::ProcessChunk(uint8_t* data, std::size_t length)
|
||||
-> cpp::result<size_t, StreamError> {
|
||||
-> cpp::result<size_t, AudioProcessingError> {
|
||||
if (current_codec_ == nullptr) {
|
||||
// Should never happen, but fail explicitly anyway.
|
||||
return cpp::fail(UNSUPPORTED_STREAM);
|
||||
@@ -111,7 +111,7 @@ auto AudioDecoder::ProcessChunk(uint8_t* data, std::size_t length)
|
||||
return current_codec_->GetInputPosition();
|
||||
}
|
||||
|
||||
auto AudioDecoder::ProcessIdle() -> cpp::result<void, StreamError> {
|
||||
auto AudioDecoder::ProcessIdle() -> cpp::result<void, AudioProcessingError> {
|
||||
// Not used; we delay forever when waiting on IO.
|
||||
return {};
|
||||
}
|
||||
|
||||
+12
-24
@@ -4,9 +4,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "cbor_decoder.hpp"
|
||||
#include "chunk.hpp"
|
||||
#include "esp-idf/components/cbor/tinycbor/src/cbor.h"
|
||||
#include "cbor.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "freertos/portmacro.h"
|
||||
#include "freertos/queue.h"
|
||||
@@ -14,6 +12,7 @@
|
||||
|
||||
#include "audio_element.hpp"
|
||||
#include "chunk.hpp"
|
||||
#include "stream_info.hpp"
|
||||
#include "stream_message.hpp"
|
||||
#include "tasks.hpp"
|
||||
|
||||
@@ -39,7 +38,7 @@ void AudioTaskMain(void* args) {
|
||||
ChunkReader chunk_reader = ChunkReader(element->InputBuffer());
|
||||
|
||||
while (1) {
|
||||
cpp::result<size_t, StreamError> process_res;
|
||||
cpp::result<size_t, AudioProcessingError> process_res;
|
||||
|
||||
// If this element has an input stream, then our top priority is
|
||||
// processing any chunks from it. Try doing this first, then fall back to
|
||||
@@ -67,26 +66,15 @@ void AudioTaskMain(void* args) {
|
||||
|
||||
if (has_received_message) {
|
||||
auto [buffer, length] = chunk_reader.GetLastMessage();
|
||||
auto decoder_res = cbor::ArrayDecoder::Create(buffer, length);
|
||||
if (decoder_res.has_error()) {
|
||||
// TODO.
|
||||
break;
|
||||
}
|
||||
auto decoder = std::move(decoder_res.value());
|
||||
// TODO: this can be more elegant i think
|
||||
cpp::result<uint64_t, CborError> message_type =
|
||||
decoder->NextValue<uint64_t>();
|
||||
if (message_type.has_error()) {
|
||||
break; // TODO.
|
||||
} else if (message_type.value() == TYPE_STREAM_INFO) {
|
||||
auto info_decoder = cbor::MapDecoder::Create(decoder->Iterator());
|
||||
if (info_decoder.has_value()) {
|
||||
auto process_error = element->ProcessStreamInfo(
|
||||
StreamInfo(info_decoder.value().get()));
|
||||
if (process_error.has_error()) {
|
||||
break; // TODO.
|
||||
}
|
||||
} else {
|
||||
MessageType msg = ReadMessageType(buffer, length);
|
||||
if (msg == TYPE_STREAM_INFO) {
|
||||
auto parse_res =
|
||||
ReadMessage<StreamInfo>(&StreamInfo::Parse, buffer, length);
|
||||
if (parse_res.has_error()) {
|
||||
break; // TODO.
|
||||
}
|
||||
auto info_res = element->ProcessStreamInfo(parse_res.value());
|
||||
if (info_res.has_error()) {
|
||||
break; // TODO.
|
||||
}
|
||||
}
|
||||
|
||||
+20
-24
@@ -1,14 +1,12 @@
|
||||
#include "chunk.hpp"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
|
||||
#include "cbor.h"
|
||||
|
||||
#include "cbor_decoder.hpp"
|
||||
#include "cbor_encoder.hpp"
|
||||
#include "stream_message.hpp"
|
||||
|
||||
namespace audio {
|
||||
@@ -25,7 +23,7 @@ static const std::size_t kWorkingBufferSize = kMaxChunkSize * 1.5;
|
||||
* space for future headers more compactly.
|
||||
*/
|
||||
// TODO: measure how big headers tend to be to pick a better value.
|
||||
static const size_t kInitialHeaderSize = 32;
|
||||
static const std::size_t kInitialHeaderSize = 32;
|
||||
|
||||
auto WriteChunksToStream(MessageBufferHandle_t* stream,
|
||||
uint8_t* working_buffer,
|
||||
@@ -44,14 +42,20 @@ auto WriteChunksToStream(MessageBufferHandle_t* stream,
|
||||
}
|
||||
|
||||
// Put together a header.
|
||||
cbor::Encoder encoder(cbor::CONTAINER_ARRAY, 3, working_buffer,
|
||||
working_buffer_length);
|
||||
encoder.WriteValue(TYPE_CHUNK_HEADER);
|
||||
encoder.WriteValue(header_size);
|
||||
encoder.WriteValue(chunk_size);
|
||||
cpp::result<size_t, CborError> encoder_res;
|
||||
CborEncoder arr;
|
||||
WriteMessage(
|
||||
TYPE_CHUNK_HEADER,
|
||||
[&](CborEncoder& container) {
|
||||
cbor_encoder_create_array(&container, &arr, 2);
|
||||
cbor_encode_uint(&arr, header_size);
|
||||
cbor_encode_uint(&arr, chunk_size);
|
||||
cbor_encoder_close_container(&container, &arr);
|
||||
return std::nullopt;
|
||||
},
|
||||
working_buffer, working_buffer_length);
|
||||
|
||||
size_t new_header_size = header_size;
|
||||
cpp::result<size_t, CborError> encoder_res = encoder.Finish();
|
||||
if (encoder_res.has_error()) {
|
||||
return CHUNK_ENCODING_ERROR;
|
||||
} else {
|
||||
@@ -108,15 +112,9 @@ auto ChunkReader::ReadChunkFromStream(
|
||||
return CHUNK_READ_TIMEOUT;
|
||||
}
|
||||
|
||||
auto decoder = cbor::ArrayDecoder::Create(working_buffer_ + leftover_bytes_,
|
||||
last_message_size_);
|
||||
if (decoder.has_error()) {
|
||||
// Weird; this implies someone is shoving invalid data into the buffer.
|
||||
return CHUNK_DECODING_ERROR;
|
||||
}
|
||||
MessageType type =
|
||||
ReadMessageType(working_buffer_ + leftover_bytes_, last_message_size_);
|
||||
|
||||
MessageType type = static_cast<MessageType>(
|
||||
decoder.value()->NextValue<uint64_t>().value_or(TYPE_UNKNOWN));
|
||||
if (type != TYPE_CHUNK_HEADER) {
|
||||
// This message wasn't for us, so let the caller handle it.
|
||||
Reset();
|
||||
@@ -124,11 +122,9 @@ auto ChunkReader::ReadChunkFromStream(
|
||||
}
|
||||
|
||||
// Work the size and position of the chunk.
|
||||
size_t header_length = decoder.value()->NextValue<uint64_t>().value_or(0);
|
||||
size_t chunk_length = decoder.value()->NextValue<uint64_t>().value_or(0);
|
||||
if (decoder.value()->Failed()) {
|
||||
return CHUNK_DECODING_ERROR;
|
||||
}
|
||||
size_t header_length = 0, chunk_length = 0;
|
||||
|
||||
// TODO: chunker header type to encapsulate this?
|
||||
|
||||
// Now we need to stick the end of the last chunk (if it exists) onto the
|
||||
// front of the new chunk. Do it this way around bc we assume the old chunk
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
|
||||
#include "audio_element.hpp"
|
||||
#include "chunk.hpp"
|
||||
#include "stream_message.hpp"
|
||||
|
||||
static const char* kTag = "SRC";
|
||||
|
||||
namespace audio {
|
||||
|
||||
@@ -43,14 +46,17 @@ FatfsAudioInput::~FatfsAudioInput() {
|
||||
free(output_buffer_);
|
||||
}
|
||||
|
||||
auto FatfsAudioInput::ProcessStreamInfo(StreamInfo&& info)
|
||||
-> cpp::result<void, StreamError> {
|
||||
auto FatfsAudioInput::ProcessStreamInfo(StreamInfo& info)
|
||||
-> cpp::result<void, AudioProcessingError> {
|
||||
if (is_file_open_) {
|
||||
f_close(¤t_file_);
|
||||
is_file_open_ = false;
|
||||
}
|
||||
|
||||
std::string path = info.Path().value_or("");
|
||||
if (!info.Path()) {
|
||||
return cpp::fail(UNSUPPORTED_STREAM);
|
||||
}
|
||||
std::string path = info.Path().value();
|
||||
FRESULT res = f_open(¤t_file_, path.c_str(), FA_READ);
|
||||
if (res != FR_OK) {
|
||||
return cpp::fail(IO_ERROR);
|
||||
@@ -58,15 +64,24 @@ auto FatfsAudioInput::ProcessStreamInfo(StreamInfo&& info)
|
||||
|
||||
is_file_open_ = true;
|
||||
|
||||
// TODO: pass on stream info.
|
||||
auto write_res =
|
||||
WriteMessage(TYPE_STREAM_INFO,
|
||||
std::bind(&StreamInfo::Encode, info, std::placeholders::_1),
|
||||
chunk_buffer_, kMaxChunkSize);
|
||||
|
||||
if (write_res.has_error()) {
|
||||
return cpp::fail(IO_ERROR);
|
||||
} else {
|
||||
xMessageBufferSend(output_buffer_, chunk_buffer_, write_res.value(),
|
||||
portMAX_DELAY);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto FatfsAudioInput::ProcessChunk(uint8_t* data, std::size_t length)
|
||||
-> cpp::result<size_t, StreamError> {
|
||||
// TODO.
|
||||
return 0;
|
||||
-> cpp::result<size_t, AudioProcessingError> {
|
||||
return cpp::fail(UNSUPPORTED_STREAM);
|
||||
}
|
||||
|
||||
auto FatfsAudioInput::GetRingBufferDistance() -> size_t {
|
||||
@@ -83,7 +98,7 @@ auto FatfsAudioInput::GetRingBufferDistance() -> size_t {
|
||||
+ (file_buffer_write_pos_ - file_buffer_);
|
||||
}
|
||||
|
||||
auto FatfsAudioInput::ProcessIdle() -> cpp::result<void, StreamError> {
|
||||
auto FatfsAudioInput::ProcessIdle() -> cpp::result<void, AudioProcessingError> {
|
||||
// First, see if we're able to fill up the input buffer with any more of the
|
||||
// file's contents.
|
||||
if (is_file_open_) {
|
||||
@@ -102,7 +117,8 @@ auto FatfsAudioInput::ProcessIdle() -> cpp::result<void, StreamError> {
|
||||
FRESULT result = f_read(¤t_file_, file_buffer_write_pos_, read_size,
|
||||
&bytes_read);
|
||||
if (result != FR_OK) {
|
||||
return cpp::fail(IO_ERROR); // TODO;
|
||||
ESP_LOGE(kTag, "file I/O error %d", result);
|
||||
return cpp::fail(IO_ERROR);
|
||||
}
|
||||
|
||||
if (f_eof(¤t_file_)) {
|
||||
@@ -128,9 +144,11 @@ auto FatfsAudioInput::ProcessIdle() -> cpp::result<void, StreamError> {
|
||||
switch (result) {
|
||||
case CHUNK_WRITE_TIMEOUT:
|
||||
case CHUNK_OUT_OF_DATA:
|
||||
return {}; // TODO.
|
||||
// Both of these are fine; SendChunk keeps track of where it's up to
|
||||
// internally, so we will pick back up where we left off.
|
||||
return {};
|
||||
default:
|
||||
return {}; // TODO.
|
||||
return cpp::fail(IO_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,11 @@ class AudioDecoder : public IAudioElement {
|
||||
auto SetInputBuffer(MessageBufferHandle_t*) -> void;
|
||||
auto SetOutputBuffer(MessageBufferHandle_t*) -> void;
|
||||
|
||||
auto ProcessStreamInfo(StreamInfo&& info) -> cpp::result<void, StreamError>;
|
||||
auto ProcessStreamInfo(StreamInfo& info)
|
||||
-> cpp::result<void, AudioProcessingError>;
|
||||
auto ProcessChunk(uint8_t* data, std::size_t length)
|
||||
-> cpp::result<size_t, StreamError>;
|
||||
auto ProcessIdle() -> cpp::result<void, StreamError>;
|
||||
-> cpp::result<size_t, AudioProcessingError>;
|
||||
auto ProcessIdle() -> cpp::result<void, AudioProcessingError>;
|
||||
|
||||
AudioDecoder(const AudioDecoder&) = delete;
|
||||
AudioDecoder& operator=(const AudioDecoder&) = delete;
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
|
||||
namespace audio {
|
||||
|
||||
/* Errors that may be returned by any of the Process* methods. */
|
||||
enum StreamError {
|
||||
/*
|
||||
* Errors that may be returned by any of the Process* methods of an audio
|
||||
* element.
|
||||
*/
|
||||
enum AudioProcessingError {
|
||||
// Indicates that this element is unable to handle the upcoming chunks.
|
||||
UNSUPPORTED_STREAM,
|
||||
// Indicates an error with reading or writing stream data.
|
||||
@@ -65,8 +68,8 @@ class IAudioElement {
|
||||
* Called when a StreamInfo message is received. Used to configure this
|
||||
* element in preperation for incoming chunks.
|
||||
*/
|
||||
virtual auto ProcessStreamInfo(StreamInfo&& info)
|
||||
-> cpp::result<void, StreamError> = 0;
|
||||
virtual auto ProcessStreamInfo(StreamInfo& info)
|
||||
-> cpp::result<void, AudioProcessingError> = 0;
|
||||
|
||||
/*
|
||||
* Called when a ChunkHeader message is received. Includes the data associated
|
||||
@@ -75,14 +78,14 @@ class IAudioElement {
|
||||
* prepended to the next call.
|
||||
*/
|
||||
virtual auto ProcessChunk(uint8_t* data, std::size_t length)
|
||||
-> cpp::result<size_t, StreamError> = 0;
|
||||
-> cpp::result<size_t, AudioProcessingError> = 0;
|
||||
|
||||
/*
|
||||
* Called when there has been no data received over the input buffer for some
|
||||
* time. This could be used to synthesize output, or to save memory by
|
||||
* releasing unused resources.
|
||||
*/
|
||||
virtual auto ProcessIdle() -> cpp::result<void, StreamError> = 0;
|
||||
virtual auto ProcessIdle() -> cpp::result<void, AudioProcessingError> = 0;
|
||||
|
||||
protected:
|
||||
MessageBufferHandle_t* input_buffer_;
|
||||
|
||||
@@ -19,10 +19,11 @@ class FatfsAudioInput : public IAudioElement {
|
||||
FatfsAudioInput(std::shared_ptr<drivers::SdStorage> storage);
|
||||
~FatfsAudioInput();
|
||||
|
||||
auto ProcessStreamInfo(StreamInfo&& info) -> cpp::result<void, StreamError>;
|
||||
auto ProcessStreamInfo(StreamInfo& info)
|
||||
-> cpp::result<void, AudioProcessingError>;
|
||||
auto ProcessChunk(uint8_t* data, std::size_t length)
|
||||
-> cpp::result<size_t, StreamError>;
|
||||
auto ProcessIdle() -> cpp::result<void, StreamError>;
|
||||
-> cpp::result<size_t, AudioProcessingError>;
|
||||
auto ProcessIdle() -> cpp::result<void, AudioProcessingError>;
|
||||
|
||||
auto SendChunk(uint8_t* buffer, size_t size) -> size_t;
|
||||
|
||||
|
||||
@@ -7,22 +7,11 @@
|
||||
#include "cbor.h"
|
||||
#include "result.hpp"
|
||||
|
||||
#include "cbor_decoder.hpp"
|
||||
#include "cbor_encoder.hpp"
|
||||
|
||||
namespace audio {
|
||||
|
||||
class StreamInfo {
|
||||
public:
|
||||
enum ParseError {
|
||||
WRONG_TYPE,
|
||||
MISSING_MAP,
|
||||
CBOR_ERROR,
|
||||
};
|
||||
|
||||
static auto Create(const uint8_t* buffer, size_t length)
|
||||
-> cpp::result<StreamInfo, ParseError>;
|
||||
StreamInfo(cbor::MapDecoder*);
|
||||
static auto Parse(CborValue& container) -> cpp::result<StreamInfo, CborError>;
|
||||
|
||||
StreamInfo() = default;
|
||||
StreamInfo(const StreamInfo&) = default;
|
||||
@@ -38,7 +27,7 @@ class StreamInfo {
|
||||
return sample_rate_;
|
||||
}
|
||||
|
||||
auto WriteToMap(cbor::Encoder& encoder) -> cpp::result<size_t, CborError>;
|
||||
auto Encode(CborEncoder& enc) -> std::optional<CborError>;
|
||||
|
||||
private:
|
||||
std::optional<std::string> path_;
|
||||
|
||||
@@ -1,11 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
#include "cbor.h"
|
||||
#include "result.hpp"
|
||||
|
||||
namespace audio {
|
||||
|
||||
extern const int kEncoderFlags;
|
||||
extern const int kDecoderFlags;
|
||||
|
||||
enum MessageType {
|
||||
TYPE_UNKNOWN,
|
||||
TYPE_CHUNK_HEADER,
|
||||
TYPE_STREAM_INFO,
|
||||
};
|
||||
|
||||
template <typename Writer>
|
||||
auto WriteMessage(MessageType type,
|
||||
Writer&& writer,
|
||||
uint8_t* buffer,
|
||||
size_t length) -> cpp::result<size_t, CborError> {
|
||||
CborEncoder root;
|
||||
CborEncoder container;
|
||||
|
||||
cbor_encoder_init(&root, buffer, length, kEncoderFlags);
|
||||
cbor_encoder_create_array(&root, &container, 2);
|
||||
cbor_encode_uint(&container, type);
|
||||
|
||||
std::optional<CborError> inner_err = std::invoke(writer, container);
|
||||
if (inner_err) {
|
||||
return cpp::fail(inner_err.value());
|
||||
}
|
||||
|
||||
cbor_encoder_close_container(&root, &container);
|
||||
return cbor_encoder_get_buffer_size(&root, buffer);
|
||||
}
|
||||
|
||||
template <typename Result, typename Reader>
|
||||
auto ReadMessage(Reader&& reader, uint8_t* buffer, size_t length)
|
||||
-> cpp::result<Result, CborError> {
|
||||
CborParser parser;
|
||||
CborValue root;
|
||||
CborValue container;
|
||||
|
||||
cbor_parser_init(buffer, length, kDecoderFlags, &parser, &root);
|
||||
cbor_value_enter_container(&root, &container);
|
||||
// Skip the type header
|
||||
cbor_value_advance_fixed(&container);
|
||||
|
||||
return std::invoke(reader, container);
|
||||
}
|
||||
|
||||
auto ReadMessageType(uint8_t* buffer, size_t length) -> MessageType;
|
||||
|
||||
} // namespace audio
|
||||
|
||||
+59
-37
@@ -5,8 +5,6 @@
|
||||
|
||||
#include "cbor.h"
|
||||
|
||||
#include "cbor_decoder.hpp"
|
||||
#include "cbor_encoder.hpp"
|
||||
#include "stream_message.hpp"
|
||||
|
||||
namespace audio {
|
||||
@@ -16,48 +14,72 @@ static const std::string kKeyChannels = "c";
|
||||
static const std::string kKeyBitsPerSample = "b";
|
||||
static const std::string kKeySampleRate = "r";
|
||||
|
||||
auto StreamInfo::Create(const uint8_t* buffer, size_t length)
|
||||
-> cpp::result<StreamInfo, ParseError> {
|
||||
CborParser parser;
|
||||
CborValue value;
|
||||
auto StreamInfo::Parse(CborValue& container)
|
||||
-> cpp::result<StreamInfo, CborError> {
|
||||
CborValue map;
|
||||
cbor_value_enter_container(&container, &map);
|
||||
|
||||
cbor_parser_init(buffer, length, 0, &parser, &value);
|
||||
CborValue entry;
|
||||
StreamInfo ret;
|
||||
|
||||
int type = 0;
|
||||
if (!cbor_value_is_integer(&value) || !cbor_value_get_int(&value, &type) ||
|
||||
type != TYPE_STREAM_INFO) {
|
||||
return cpp::fail(WRONG_TYPE);
|
||||
cbor_value_map_find_value(&map, kKeyPath.c_str(), &entry);
|
||||
if (cbor_value_get_type(&entry) != CborInvalidType) {
|
||||
char* val;
|
||||
size_t len;
|
||||
cbor_value_dup_text_string(&entry, &val, &len, NULL);
|
||||
ret.path_ = std::string(val, len);
|
||||
free(val);
|
||||
}
|
||||
cbor_value_map_find_value(&map, kKeyChannels.c_str(), &entry);
|
||||
if (cbor_value_get_type(&entry) != CborInvalidType) {
|
||||
uint64_t val;
|
||||
cbor_value_get_uint64(&entry, &val);
|
||||
ret.channels_ = val;
|
||||
}
|
||||
cbor_value_map_find_value(&map, kKeyBitsPerSample.c_str(), &entry);
|
||||
if (cbor_value_get_type(&entry) != CborInvalidType) {
|
||||
uint64_t val;
|
||||
cbor_value_get_uint64(&entry, &val);
|
||||
ret.bits_per_sample_ = val;
|
||||
}
|
||||
cbor_value_map_find_value(&map, kKeySampleRate.c_str(), &entry);
|
||||
if (cbor_value_get_type(&entry) != CborInvalidType) {
|
||||
uint64_t val;
|
||||
cbor_value_get_uint64(&entry, &val);
|
||||
ret.sample_rate_ = val;
|
||||
}
|
||||
|
||||
cbor_value_advance_fixed(&value);
|
||||
|
||||
if (!cbor_value_is_map(&value)) {
|
||||
return cpp::fail(MISSING_MAP);
|
||||
}
|
||||
|
||||
auto map_decoder = cbor::MapDecoder::Create(value);
|
||||
if (map_decoder.has_value()) {
|
||||
return StreamInfo(map_decoder.value().get());
|
||||
}
|
||||
return cpp::fail(CBOR_ERROR);
|
||||
return ret;
|
||||
}
|
||||
|
||||
StreamInfo::StreamInfo(cbor::MapDecoder* decoder) {
|
||||
// TODO: this method is n^2, which seems less than ideal. But you don't do it
|
||||
// that frequently, so maybe it's okay? Needs investigation.
|
||||
channels_ = decoder->FindValue<uint64_t>(kKeyChannels);
|
||||
bits_per_sample_ = decoder->FindValue<uint64_t>(kKeyBitsPerSample);
|
||||
sample_rate_ = decoder->FindValue<uint64_t>(kKeySampleRate);
|
||||
path_ = decoder->FindValue<std::string>(kKeyPath);
|
||||
}
|
||||
auto StreamInfo::Encode(CborEncoder& enc) -> std::optional<CborError> {
|
||||
CborEncoder map;
|
||||
size_t num_items = 0 + channels_.has_value() + bits_per_sample_.has_value() +
|
||||
sample_rate_.has_value() + path_.has_value();
|
||||
cbor_encoder_create_map(&enc, &map, num_items);
|
||||
|
||||
auto StreamInfo::WriteToMap(cbor::Encoder& map_encoder)
|
||||
-> cpp::result<size_t, CborError> {
|
||||
map_encoder.WriteKeyValue<uint64_t>(kKeyChannels, channels_);
|
||||
map_encoder.WriteKeyValue<uint64_t>(kKeyBitsPerSample, bits_per_sample_);
|
||||
map_encoder.WriteKeyValue<uint64_t>(kKeySampleRate, sample_rate_);
|
||||
map_encoder.WriteKeyValue<std::string>(kKeyPath, path_);
|
||||
return map_encoder.Finish();
|
||||
if (channels_) {
|
||||
cbor_encode_text_string(&map, kKeyChannels.c_str(), kKeyChannels.size());
|
||||
cbor_encode_uint(&map, channels_.value());
|
||||
}
|
||||
if (bits_per_sample_) {
|
||||
cbor_encode_text_string(&map, kKeyBitsPerSample.c_str(),
|
||||
kKeyBitsPerSample.size());
|
||||
cbor_encode_uint(&map, bits_per_sample_.value());
|
||||
}
|
||||
if (sample_rate_) {
|
||||
cbor_encode_text_string(&map, kKeySampleRate.c_str(),
|
||||
kKeySampleRate.size());
|
||||
cbor_encode_uint(&map, sample_rate_.value());
|
||||
}
|
||||
if (path_) {
|
||||
cbor_encode_text_string(&map, kKeyPath.c_str(), kKeyPath.size());
|
||||
cbor_encode_text_string(&map, path_.value().c_str(), path_.value().size());
|
||||
}
|
||||
|
||||
cbor_encoder_close_container(&enc, &map);
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace audio
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "stream_message.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "cbor.h"
|
||||
|
||||
namespace audio {
|
||||
|
||||
const int kEncoderFlags = 0;
|
||||
const int kDecoderFlags = 0;
|
||||
|
||||
auto ReadMessageType(uint8_t* buffer, size_t length) -> MessageType {
|
||||
CborParser parser;
|
||||
CborValue root;
|
||||
CborValue container;
|
||||
|
||||
cbor_parser_init(buffer, length, kDecoderFlags, &parser, &root);
|
||||
cbor_value_enter_container(&root, &container);
|
||||
|
||||
uint64_t header = 0;
|
||||
cbor_value_get_uint64(&container, &header);
|
||||
|
||||
return static_cast<MessageType>(header);
|
||||
}
|
||||
|
||||
} // namespace audio
|
||||
@@ -1,6 +0,0 @@
|
||||
idf_component_register(
|
||||
SRCS "cbor_decoder.cpp" "cbor_encoder.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES "cbor" "result")
|
||||
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
|
||||
@@ -1,112 +0,0 @@
|
||||
#include "cbor_decoder.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "cbor.h"
|
||||
#include "result.hpp"
|
||||
|
||||
static const int kDecoderFlags = 0;
|
||||
|
||||
namespace cbor {
|
||||
|
||||
auto parse_stdstring(const CborValue* val, std::string* out) -> CborError {
|
||||
char* buf;
|
||||
size_t len;
|
||||
CborError err = cbor_value_dup_text_string(val, &buf, &len, NULL);
|
||||
if (err != CborNoError) {
|
||||
return err;
|
||||
}
|
||||
*out = std::string(buf, len);
|
||||
free(buf);
|
||||
return err;
|
||||
}
|
||||
|
||||
auto ArrayDecoder::Create(uint8_t* buffer, size_t buffer_len)
|
||||
-> cpp::result<std::unique_ptr<ArrayDecoder>, CborError> {
|
||||
auto decoder = std::make_unique<ArrayDecoder>();
|
||||
cbor_parser_init(buffer, buffer_len, kDecoderFlags, &decoder->parser_,
|
||||
&decoder->root_);
|
||||
if (!cbor_value_is_array(&decoder->root_)) {
|
||||
return cpp::fail(CborErrorIllegalType);
|
||||
}
|
||||
CborError err = cbor_value_enter_container(&decoder->root_, &decoder->it_);
|
||||
if (err != CborNoError) {
|
||||
return cpp::fail(err);
|
||||
}
|
||||
return std::move(decoder);
|
||||
}
|
||||
|
||||
auto ArrayDecoder::Create(CborValue& root)
|
||||
-> cpp::result<std::unique_ptr<ArrayDecoder>, CborError> {
|
||||
auto decoder = std::make_unique<ArrayDecoder>();
|
||||
decoder->root_ = root;
|
||||
if (!cbor_value_is_array(&decoder->root_)) {
|
||||
return cpp::fail(CborErrorIllegalType);
|
||||
}
|
||||
|
||||
CborError err = cbor_value_enter_container(&decoder->root_, &decoder->it_);
|
||||
if (err != CborNoError) {
|
||||
return cpp::fail(err);
|
||||
}
|
||||
return std::move(decoder);
|
||||
}
|
||||
|
||||
template <>
|
||||
auto ArrayDecoder::NextValue() -> cpp::result<int64_t, CborError> {
|
||||
return NextValue(&cbor_value_is_integer, &cbor_value_get_int);
|
||||
}
|
||||
template <>
|
||||
auto ArrayDecoder::NextValue() -> cpp::result<uint64_t, CborError> {
|
||||
return NextValue(&cbor_value_is_unsigned_integer, &cbor_value_get_uint64);
|
||||
}
|
||||
template <>
|
||||
auto ArrayDecoder::NextValue() -> cpp::result<std::string, CborError> {
|
||||
return NextValue(&cbor_value_is_byte_string, &parse_stdstring);
|
||||
}
|
||||
|
||||
auto MapDecoder::Create(uint8_t* buffer, size_t buffer_len)
|
||||
-> cpp::result<std::unique_ptr<MapDecoder>, CborError> {
|
||||
auto decoder = std::make_unique<MapDecoder>();
|
||||
cbor_parser_init(buffer, buffer_len, kDecoderFlags, &decoder->parser_,
|
||||
&decoder->root_);
|
||||
if (!cbor_value_is_map(&decoder->root_)) {
|
||||
return cpp::fail(CborErrorIllegalType);
|
||||
}
|
||||
CborError err = cbor_value_enter_container(&decoder->root_, &decoder->it_);
|
||||
if (err != CborNoError) {
|
||||
return cpp::fail(err);
|
||||
}
|
||||
return std::move(decoder);
|
||||
}
|
||||
|
||||
auto MapDecoder::Create(CborValue& root)
|
||||
-> cpp::result<std::unique_ptr<MapDecoder>, CborError> {
|
||||
auto decoder = std::make_unique<MapDecoder>();
|
||||
decoder->root_ = root;
|
||||
if (!cbor_value_is_map(&decoder->root_)) {
|
||||
return cpp::fail(CborErrorIllegalType);
|
||||
}
|
||||
CborError err = cbor_value_enter_container(&decoder->root_, &decoder->it_);
|
||||
if (err != CborNoError) {
|
||||
return cpp::fail(err);
|
||||
}
|
||||
return std::move(decoder);
|
||||
}
|
||||
|
||||
template <>
|
||||
auto MapDecoder::FindValue(const std::string& key) -> std::optional<int64_t> {
|
||||
return FindValue(key, &cbor_value_is_integer, &cbor_value_get_int);
|
||||
}
|
||||
template <>
|
||||
auto MapDecoder::FindValue(const std::string& key) -> std::optional<uint64_t> {
|
||||
return FindValue(key, &cbor_value_is_unsigned_integer,
|
||||
&cbor_value_get_uint64);
|
||||
}
|
||||
template <>
|
||||
auto MapDecoder::FindValue(const std::string& key)
|
||||
-> std::optional<std::string> {
|
||||
return FindValue(key, &cbor_value_is_byte_string, &parse_stdstring);
|
||||
}
|
||||
|
||||
} // namespace cbor
|
||||
@@ -1,63 +0,0 @@
|
||||
#include "cbor_encoder.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "cbor.h"
|
||||
#include "cbor_decoder.hpp"
|
||||
#include "result.hpp"
|
||||
|
||||
namespace cbor {
|
||||
|
||||
static const int kEncoderFlags = 0;
|
||||
|
||||
Encoder::Encoder(ContainerType type,
|
||||
uint32_t container_len,
|
||||
uint8_t* buffer,
|
||||
size_t buffer_len) {
|
||||
cbor_encoder_init(&root_encoder_, buffer, buffer_len, kEncoderFlags);
|
||||
switch (type) {
|
||||
case CONTAINER_ARRAY:
|
||||
error_ = cbor_encoder_create_array(&root_encoder_, &container_encoder_,
|
||||
container_len);
|
||||
break;
|
||||
case CONTAINER_MAP:
|
||||
error_ = cbor_encoder_create_map(&root_encoder_, &container_encoder_,
|
||||
container_len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto Encoder::WriteValue(const std::string& val) -> void {
|
||||
if (error_ != CborNoError) {
|
||||
return;
|
||||
}
|
||||
error_ =
|
||||
cbor_encode_text_string(&container_encoder_, val.c_str(), val.size());
|
||||
}
|
||||
|
||||
auto Encoder::WriteValue(uint32_t val) -> void {
|
||||
if (error_ != CborNoError) {
|
||||
return;
|
||||
}
|
||||
error_ = cbor_encode_uint(&container_encoder_, val);
|
||||
}
|
||||
|
||||
auto Encoder::WriteValue(int32_t val) -> void {
|
||||
if (error_ != CborNoError) {
|
||||
return;
|
||||
}
|
||||
error_ = cbor_encode_int(&container_encoder_, val);
|
||||
}
|
||||
|
||||
auto Encoder::Finish() -> cpp::result<size_t, CborError> {
|
||||
if (error_ == CborNoError) {
|
||||
error_ = cbor_encoder_close_container(&root_encoder_, &container_encoder_);
|
||||
}
|
||||
if (error_ != CborNoError) {
|
||||
return cpp::fail(error_);
|
||||
}
|
||||
return cbor_encoder_get_buffer_size(&root_encoder_, buffer_);
|
||||
}
|
||||
|
||||
} // namespace cbor
|
||||
@@ -1,113 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "cbor.h"
|
||||
#include "result.hpp"
|
||||
|
||||
namespace cbor {
|
||||
|
||||
class ArrayDecoder {
|
||||
public:
|
||||
static auto Create(uint8_t* buffer, size_t buffer_len)
|
||||
-> cpp::result<std::unique_ptr<ArrayDecoder>, CborError>;
|
||||
|
||||
static auto Create(CborValue& root)
|
||||
-> cpp::result<std::unique_ptr<ArrayDecoder>, CborError>;
|
||||
|
||||
ArrayDecoder() {}
|
||||
|
||||
template <typename T>
|
||||
auto NextValue() -> cpp::result<T, CborError>;
|
||||
|
||||
template <typename T>
|
||||
auto NextValue(bool (*is_valid)(const CborValue*),
|
||||
CborError (*parse)(const CborValue*, T*))
|
||||
-> cpp::result<T, CborError> {
|
||||
if (error_ != CborNoError) {
|
||||
return cpp::fail(error_);
|
||||
}
|
||||
if (!is_valid(&it_)) {
|
||||
error_ = CborErrorIllegalType;
|
||||
return cpp::fail(error_);
|
||||
}
|
||||
T ret;
|
||||
error_ = parse(&it_, &ret);
|
||||
if (error_ != CborNoError) {
|
||||
return cpp::fail(error_);
|
||||
}
|
||||
error_ = cbor_value_advance(&it_);
|
||||
if (error_ != CborNoError) {
|
||||
return cpp::fail(error_);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto Failed() -> CborError { return error_; }
|
||||
|
||||
auto Iterator() -> CborValue& { return it_; }
|
||||
|
||||
ArrayDecoder(const ArrayDecoder&) = delete;
|
||||
ArrayDecoder& operator=(const ArrayDecoder&) = delete;
|
||||
|
||||
private:
|
||||
CborParser parser_;
|
||||
CborValue root_;
|
||||
|
||||
CborValue it_;
|
||||
CborError error_ = CborNoError;
|
||||
};
|
||||
|
||||
class MapDecoder {
|
||||
public:
|
||||
static auto Create(uint8_t* buffer, size_t buffer_len)
|
||||
-> cpp::result<std::unique_ptr<MapDecoder>, CborError>;
|
||||
|
||||
static auto Create(CborValue& root)
|
||||
-> cpp::result<std::unique_ptr<MapDecoder>, CborError>;
|
||||
|
||||
MapDecoder() {}
|
||||
|
||||
template <typename T>
|
||||
auto FindValue(const std::string& key) -> std::optional<T>;
|
||||
|
||||
template <typename T>
|
||||
auto FindValue(const std::string& key,
|
||||
bool (*is_valid)(const CborValue*),
|
||||
CborError (*parse)(const CborValue*, T*)) -> std::optional<T> {
|
||||
if (error_ != CborNoError) {
|
||||
return {};
|
||||
}
|
||||
CborValue val;
|
||||
if (cbor_value_map_find_value(&it_, key.c_str(), &val) != CborNoError) {
|
||||
return {};
|
||||
}
|
||||
if (!is_valid(&val)) {
|
||||
error_ = CborErrorIllegalType;
|
||||
return {};
|
||||
}
|
||||
T ret;
|
||||
error_ = parse(&val, &ret);
|
||||
if (error_ != CborNoError) {
|
||||
return {};
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto Failed() -> CborError { return error_; }
|
||||
|
||||
MapDecoder(const MapDecoder&) = delete;
|
||||
MapDecoder& operator=(const MapDecoder&) = delete;
|
||||
|
||||
private:
|
||||
CborParser parser_;
|
||||
CborValue root_;
|
||||
|
||||
CborValue it_;
|
||||
CborError error_ = CborNoError;
|
||||
};
|
||||
|
||||
} // namespace cbor
|
||||
@@ -1,52 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "cbor.h"
|
||||
#include "result.hpp"
|
||||
|
||||
namespace cbor {
|
||||
|
||||
enum ContainerType { CONTAINER_ARRAY, CONTAINER_MAP };
|
||||
|
||||
class Encoder {
|
||||
public:
|
||||
Encoder(ContainerType type,
|
||||
uint32_t container_len,
|
||||
uint8_t* buffer,
|
||||
size_t buffer_len);
|
||||
|
||||
auto WriteValue(const std::string& val) -> void;
|
||||
auto WriteValue(uint32_t val) -> void;
|
||||
auto WriteValue(int32_t val) -> void;
|
||||
|
||||
template <typename T>
|
||||
auto WriteKeyValue(const std::string& key, const T&& val) -> void {
|
||||
WriteValue(key);
|
||||
WriteValue(val);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto WriteKeyValue(const std::string& key, const std::optional<T>& val)
|
||||
-> void {
|
||||
if (val) {
|
||||
WriteKeyValue<T>(key, val.value());
|
||||
}
|
||||
}
|
||||
|
||||
auto Finish() -> cpp::result<size_t, CborError>;
|
||||
|
||||
Encoder(const Encoder&) = delete;
|
||||
Encoder& operator=(const Encoder&) = delete;
|
||||
|
||||
private:
|
||||
uint8_t* buffer_;
|
||||
CborEncoder root_encoder_;
|
||||
CborEncoder container_encoder_;
|
||||
|
||||
CborError error_ = CborNoError;
|
||||
};
|
||||
|
||||
} // namespace cbor
|
||||
Reference in New Issue
Block a user