parent
							
								
									9f8cfaa7a8
								
							
						
					
					
						commit
						9176ef1872
					
				@ -1,6 +1,7 @@ | 
				
			||||
idf_component_register( | 
				
			||||
  SRCS "audio_decoder.cpp" "fatfs_audio_input.cpp" "audio_task.cpp" | 
				
			||||
  SRCS "audio_decoder.cpp" "audio_task.cpp" "fatfs_audio_input.cpp" "chunk.cpp" | 
				
			||||
  "i2s_audio_output.cpp" "stream_info.cpp" | 
				
			||||
  INCLUDE_DIRS "include" | 
				
			||||
  REQUIRES "codecs" "drivers") | 
				
			||||
  REQUIRES "codecs" "drivers" "cbor") | 
				
			||||
 | 
				
			||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) | 
				
			||||
 | 
				
			||||
@ -0,0 +1,115 @@ | 
				
			||||
#include "chunk.hpp" | 
				
			||||
 | 
				
			||||
#include "cbor_encoder.hpp" | 
				
			||||
#include "cbor_decoder.hpp" | 
				
			||||
#include <string.h> | 
				
			||||
#include <cstdint> | 
				
			||||
#include "esp-idf/components/cbor/tinycbor/src/cbor.h" | 
				
			||||
#include "stream_message.hpp" | 
				
			||||
 | 
				
			||||
namespace audio { | 
				
			||||
 | 
				
			||||
/*
 | 
				
			||||
 * The maximum size that we expect a header to take up. | 
				
			||||
 */ | 
				
			||||
// TODO: tune this.
 | 
				
			||||
static const size_t kMaxHeaderSize = 64; | 
				
			||||
 | 
				
			||||
auto WriteChunksToStream(MessageBufferHandle_t *stream, uint8_t *working_buffer, size_t working_buffer_length, std::function<size_t(uint8_t*,size_t)> callback, TickType_t max_wait) -> EncodeWriteResult { | 
				
			||||
  while (1) { | 
				
			||||
    // First, ask the callback for some data to write.
 | 
				
			||||
    size_t chunk_size = | 
				
			||||
      callback( | 
				
			||||
          working_buffer + kMaxHeaderSize, | 
				
			||||
          working_buffer_length - kMaxHeaderSize); | 
				
			||||
 | 
				
			||||
    if (chunk_size == 0) { | 
				
			||||
      // They had nothing for us, so bail out.
 | 
				
			||||
      return CHUNK_OUT_OF_DATA; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    // Put together a header.
 | 
				
			||||
    cbor::Encoder encoder(cbor::CONTAINER_ARRAY, 3, working_buffer, working_buffer_length); | 
				
			||||
    encoder.WriteUnsigned(TYPE_CHUNK_HEADER); | 
				
			||||
    // Note here that we need to write the offset of the chunk into the header.
 | 
				
			||||
    // We could be smarter here and write the actual header size, allowing us to
 | 
				
			||||
    // pack slightly more data into each message, but this is hard so I haven't
 | 
				
			||||
    // done it. Please make my code better for me.
 | 
				
			||||
    encoder.WriteUnsigned(kMaxHeaderSize); | 
				
			||||
    encoder.WriteUnsigned(chunk_size); | 
				
			||||
    if (encoder.Finish().has_error()) { | 
				
			||||
      return CHUNK_ENCODING_ERROR; | 
				
			||||
    }; | 
				
			||||
 | 
				
			||||
    // Try to write to the buffer. Note the return type here will be either 0 or
 | 
				
			||||
    // kMaxHeaderSize + chunk_size, as MessageBuffer doesn't allow partial
 | 
				
			||||
    // writes.
 | 
				
			||||
    size_t actual_write_size = | 
				
			||||
      xMessageBufferSend( | 
				
			||||
          *stream, working_buffer, kMaxHeaderSize + chunk_size, max_wait); | 
				
			||||
 | 
				
			||||
    if (actual_write_size == 0) { | 
				
			||||
      // We failed to write in time, so bail out. This is techinically data loss
 | 
				
			||||
      // unless the caller wants to go and parse our working buffer, but we
 | 
				
			||||
      // assume the caller has a good reason to time us out.
 | 
				
			||||
      return CHUNK_WRITE_TIMEOUT; | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
auto ReadChunksFromStream(MessageBufferHandle_t *stream, uint8_t *working_buffer, size_t working_buffer_length, std::function<size_t(uint8_t*,size_t)> callback, TickType_t max_wait) -> EncodeReadResult { | 
				
			||||
  // Spillover if the previous iteration did not consume all of the input.
 | 
				
			||||
  size_t leftover_bytes = 0; | 
				
			||||
  while (1) { | 
				
			||||
    // First, wait for a message to arrive over the buffer.
 | 
				
			||||
    size_t read_size = | 
				
			||||
      xMessageBufferReceive( | 
				
			||||
          *stream, working_buffer + leftover_bytes, working_buffer_length - leftover_bytes, max_wait); | 
				
			||||
 | 
				
			||||
    if (read_size == 0) { | 
				
			||||
      return CHUNK_READ_TIMEOUT; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    auto decoder = cbor::MapDecoder::Create(working_buffer + leftover_bytes, read_size); | 
				
			||||
    if (decoder.has_error()) { | 
				
			||||
      // Weird; this implies someone is shoving invalid data into the buffer.
 | 
				
			||||
      return CHUNK_DECODING_ERROR; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    MessageType type = decoder.value().ParseUnsigned().value_or(TYPE_UNKNOWN); | 
				
			||||
    if (type != TYPE_CHUNK_HEADER) { | 
				
			||||
      // This message wasn't for us, so put it in a consistent place and let the
 | 
				
			||||
      // caller handle it.
 | 
				
			||||
      memmove(working_buffer, working_buffer + leftover_bytes, read_size); | 
				
			||||
      return CHUNK_STREAM_ENDED; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    // Work the size and position of the chunk (don't assume it's at
 | 
				
			||||
    // kMaxHeaderSize offset for future-proofing).
 | 
				
			||||
    header_length = decoder.ParseUnsigned().value_or(0); | 
				
			||||
    chunk_length = decoder.ParseUnsigned().value_or(0); | 
				
			||||
    if (decoder.Failed()) { | 
				
			||||
      return CHUNK_DECODING_ERROR; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    // 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
 | 
				
			||||
    // is shorter, and therefore faster to move.
 | 
				
			||||
    uint8_t *combined_buffer = working_buffer + header_length - leftover_bytes; | 
				
			||||
    size_t combined_buffer_size = leftover_bytes + chunk_length; | 
				
			||||
    if (leftover_bytes > 0) { | 
				
			||||
      memmove(combined_buffer, working_buffer, leftover_bytes); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    // Tell the callback about the new data.
 | 
				
			||||
    size_t amount_processed = callback(combined_buffer, combined_buffer_size); | 
				
			||||
 | 
				
			||||
    // Prepare for the next iteration.
 | 
				
			||||
    leftover_bytes = combined_buffer_size - amount_processed; | 
				
			||||
    if (leftover_bytes > 0) { | 
				
			||||
      memmove(working_buffer, combined_buffer + amount_processed, leftover_bytes); | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
} // namespace audio
 | 
				
			||||
@ -0,0 +1,57 @@ | 
				
			||||
#pragma once | 
				
			||||
 | 
				
			||||
#include <cstddef> | 
				
			||||
#include <cstdint> | 
				
			||||
#include <optional> | 
				
			||||
#include <string> | 
				
			||||
#include "esp-idf/components/cbor/tinycbor/src/cbor.h" | 
				
			||||
#include "freertos/portmacro.h" | 
				
			||||
#include "result.hpp" | 
				
			||||
 | 
				
			||||
namespace audio { | 
				
			||||
 | 
				
			||||
enum ChunkWriteResult { | 
				
			||||
  // Returned when the callback does not write any data.
 | 
				
			||||
  CHUNK_OUT_OF_DATA, | 
				
			||||
  // Returned when there is an error encoding a chunk header using cbor.
 | 
				
			||||
  CHUNK_ENCODING_ERROR, | 
				
			||||
  // Returned when max_wait expires without room in the stream buffer becoming
 | 
				
			||||
  // available.
 | 
				
			||||
  CHUNK_WRITE_TIMEOUT, | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/*
 | 
				
			||||
 * Invokes the given callback to receive data, breaks the received data up into | 
				
			||||
 * chunks with headers, and writes those chunks to the given output stream. | 
				
			||||
 * | 
				
			||||
 * The callback will be invoked with a byte buffer and its size. The callback | 
				
			||||
 * should write as much data as it can to this buffer, and then return the | 
				
			||||
 * number of bytes it wrote. Return a value of 0 to indicate that there is no | 
				
			||||
 * more input to read. | 
				
			||||
 */ | 
				
			||||
auto WriteChunksToStream(MessageBufferHandle_t *stream, uint8_t *working_buffer, size_t working_buffer_length, std::function<size_t(uint8_t*,size_t)> callback, TickType_t max_wait) -> EncodeWriteResult; | 
				
			||||
 | 
				
			||||
  enum ChunkReadResult { | 
				
			||||
    // Returned an error in parsing the cbor-encoded header.
 | 
				
			||||
    CHUNK_DECODING_ERROR, | 
				
			||||
    // Returned when max_wait expired before any data was read.
 | 
				
			||||
    CHUNK_READ_TIMEOUT, | 
				
			||||
    // Returned when a non-chunk message is received.
 | 
				
			||||
    CHUNK_STREAM_ENDED, | 
				
			||||
  }; | 
				
			||||
 | 
				
			||||
/*
 | 
				
			||||
 * Reads chunks of data from the given input stream, and invokes the given | 
				
			||||
 * callback to process each of them in turn. | 
				
			||||
 * 
 | 
				
			||||
 * The callback will be invoked with a byte buffer and its size. The callback | 
				
			||||
 * should process as much data as it can from this buffer, and then return the | 
				
			||||
 * number of bytes it was able to read. Any leftover bytes will be added as a | 
				
			||||
 * prefix to the next chunk. | 
				
			||||
 * | 
				
			||||
 * If this function encounters a message in the stream that is not a chunk, it | 
				
			||||
 * will place the message at the start of the working_buffer and then return. | 
				
			||||
 */ | 
				
			||||
auto ReadChunksFromStream(MessageBufferHandle_t *stream, uint8_t *working_buffer, size_t working_buffer_length, std::function<size_t(uint8_t*,size_t)> callback, TickType_t max_wait) -> EncodeReadResult; | 
				
			||||
 | 
				
			||||
} // namespace audio
 | 
				
			||||
@ -0,0 +1,44 @@ | 
				
			||||
#pragma once | 
				
			||||
 | 
				
			||||
#include <cstdint> | 
				
			||||
#include <optional> | 
				
			||||
#include <string> | 
				
			||||
#include "esp-idf/components/cbor/tinycbor/src/cbor.h" | 
				
			||||
#include "result.hpp" | 
				
			||||
 | 
				
			||||
namespace audio { | 
				
			||||
 | 
				
			||||
class StreamInfo { | 
				
			||||
  public: | 
				
			||||
    enum ParseError { | 
				
			||||
      WRONG_TYPE, | 
				
			||||
      MISSING_MAP, | 
				
			||||
    }; | 
				
			||||
 | 
				
			||||
    static auto Create(const uint8_t *buffer, size_t length) -> cpp::result<StreamInfo, ParseError>; | 
				
			||||
    StreamInfo(CborValue& map); | 
				
			||||
 | 
				
			||||
    StreamInfo() = default; | 
				
			||||
    StreamInfo(const StreamInfo&) = default; | 
				
			||||
 | 
				
			||||
    ~StreamInfo() = default; | 
				
			||||
 | 
				
			||||
    auto Path() const -> const std::optional<std::string>& { return path_; } | 
				
			||||
    auto Channels() const -> const std::optional<uint8_t>& { return channels_; } | 
				
			||||
    auto BitsPerSample() const -> const std::optional<uint8_t>& { return bits_per_sample_; } | 
				
			||||
    auto SampleRate() const -> const std::optional<uint16_t>& { return sample_rate_; } | 
				
			||||
 | 
				
			||||
    enum EncodeError { | 
				
			||||
      OUT_OF_MEMORY, | 
				
			||||
    }; | 
				
			||||
 | 
				
			||||
    auto WriteToStream(CborEncoder encoder) -> cpp::result<void, EncodeError>; | 
				
			||||
  private: | 
				
			||||
 | 
				
			||||
    std::optional<std::string> path_; | 
				
			||||
    std::optional<uint8_t> channels_; | 
				
			||||
    std::optional<uint8_t> bits_per_sample_; | 
				
			||||
    std::optional<uint16_t> sample_rate_; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
} // namespace audio
 | 
				
			||||
@ -0,0 +1,11 @@ | 
				
			||||
#pragma once | 
				
			||||
 | 
				
			||||
namespace audio { | 
				
			||||
  
 | 
				
			||||
  enum MessageType { | 
				
			||||
    TYPE_UNKNOWN, | 
				
			||||
    TYPE_CHUNK_HEADER, | 
				
			||||
    TYPE_STREAM_INFO, | 
				
			||||
  }; | 
				
			||||
 | 
				
			||||
} // namespace audio
 | 
				
			||||
@ -0,0 +1,90 @@ | 
				
			||||
#include "stream_info.hpp" | 
				
			||||
#include "stream_message.hpp" | 
				
			||||
#include <cstdint> | 
				
			||||
#include "esp-idf/components/cbor/tinycbor/src/cbor.h" | 
				
			||||
 | 
				
			||||
namespace audio { | 
				
			||||
 | 
				
			||||
  static const char* kKeyPath = "p"; | 
				
			||||
  static const char* kKeyChannels = "c"; | 
				
			||||
  static const char* kKeyBitsPerSample = "b"; | 
				
			||||
  static const char* kKeySampleRate = "r"; | 
				
			||||
 | 
				
			||||
  static auto find_uint64(CborValue &map, char *key) -> cpp::optional<uint64_t> { | 
				
			||||
    CborValue val; | 
				
			||||
    cbor_value_map_find_value(&map, key, &val); | 
				
			||||
    if (cbor_value_is_unsigned_integer(&val)) { | 
				
			||||
      uint64_t raw_val; | 
				
			||||
      cbor_value_get_uint64(&val, &raw_val); | 
				
			||||
      return raw_val; | 
				
			||||
    } | 
				
			||||
    return {}; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
 | 
				
			||||
  static auto write_uint64(CborEncoder &map, const char *key, const optional<uint64_t> &val) -> cpp::result<void, StreamInfo::EncodeError> { | 
				
			||||
    if (val) { | 
				
			||||
      cbor_encode_byte_string(&map, key, 1); | 
				
			||||
      cbor_encode_uint(&map, *val); | 
				
			||||
    } | 
				
			||||
    return {}; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
static auto StreamInfo::Create(const uint8_t *buffer, size_t length) -> cpp::result<StreamInfo, ParseError> { | 
				
			||||
  CborParser parser; | 
				
			||||
  CborValue value; | 
				
			||||
 | 
				
			||||
  cbor_parser_init(buffer, len, 0, &parser, &value); | 
				
			||||
 | 
				
			||||
  uint8_t type = 0; | 
				
			||||
  if (!cbor_value_is_integer(&value) | 
				
			||||
      || !cbor_value_get_integer(&value, &type) | 
				
			||||
      || type != STREAM_INFO) { | 
				
			||||
    return cpp::fail(WRONG_TYPE); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  cbor_value_advance_fixed(&value); | 
				
			||||
 | 
				
			||||
  if (!cbor_value_is_map(&value)) { | 
				
			||||
    return cpp::fail(MISSING_MAP); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  return StreamInfo(value); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
StreamInfo::StreamInfo(CborValue& map) { | 
				
			||||
  // 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_ = find_uint64(map, kKeyChannels); | 
				
			||||
  bits_per_sample_ = find_uint64(map, kKeyBitsPerSample); | 
				
			||||
  sample_rate_ = find_uint64(map, kKeySampleRate); | 
				
			||||
 | 
				
			||||
  CborValue val; | 
				
			||||
  cbor_value_map_find_value(&map, kKeyPath, &val); | 
				
			||||
  if (cbor_value_is_text_string(&val)) { | 
				
			||||
    size_t len; | 
				
			||||
    char *str; | 
				
			||||
    cbor_value_dup_text_string(&val, &str, &len, &val); | 
				
			||||
    path_ = std::string(str, len); | 
				
			||||
    free(str); | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
auto StreamInfo::WriteToStream(CborEncoder encoder) -> cpp::result<void, EncodeError> { | 
				
			||||
  cbor_encode_int(&encoder, STREAM_INFO); | 
				
			||||
 | 
				
			||||
  CborEncoder map; | 
				
			||||
  cbor_encoder_create_map(&encoder, &map, length); | 
				
			||||
 | 
				
			||||
  write_uint64(&map, kKeyChannels, channels_); | 
				
			||||
  write_uint64(&map, kKeyBitsPerSample, bits_per_sample_); | 
				
			||||
  write_uint64(&map, kKeySampleRate, sample_rate_); | 
				
			||||
 | 
				
			||||
  if (path_) { | 
				
			||||
    cbor_encode_text_string(&map, path_->c_str(), path_->size()); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  cbor_encoder_close_container(&encoder, &map); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
} // namespace audio
 | 
				
			||||
@ -0,0 +1,6 @@ | 
				
			||||
idf_component_register( | 
				
			||||
  SRCS "cbor_decoder.cpp" "cbor_encoder.cpp" | 
				
			||||
  INCLUDE_DIRS "include" | 
				
			||||
  REQUIRES "cbor" "result") | 
				
			||||
 | 
				
			||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) | 
				
			||||
@ -0,0 +1,158 @@ | 
				
			||||
#include "cbor_decoder.hpp" | 
				
			||||
#include <cstdint> | 
				
			||||
#include "esp-idf/components/cbor/tinycbor/src/cbor.h" | 
				
			||||
#include "include/cbor_decoder.hpp" | 
				
			||||
 | 
				
			||||
namespace cbor { | 
				
			||||
 | 
				
			||||
static 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, &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::ParseString() -> cpp::result<std::string, CborError> { | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return cpp::fail(error_); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  if (!cbor_value_is_byte_string(&it_)) { | 
				
			||||
    error_ = CborErrorIllegalType; | 
				
			||||
    return cpp::fail(error_); | 
				
			||||
  } | 
				
			||||
  uint8_t *buf; size_t len; CborValue new_val; | 
				
			||||
  error_ = cbor_value_dup_byte_string(&it_, &buf, &len, &new_val); | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return cpp::fail(error_); | 
				
			||||
  } | 
				
			||||
  std::string ret(buf, len); | 
				
			||||
  free(buf); | 
				
			||||
  val_ = new_val; | 
				
			||||
  return ret; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
auto ArrayDecoder::ParseUnsigned() -> cpp::result<uint32_t, CborError> { | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return cpp::fail(error_); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  if (!cbor_value_is_unsigned_integer(&it_)) { | 
				
			||||
    error_ = CborErrorIllegalType; | 
				
			||||
    return cpp::fail(error_); | 
				
			||||
  } | 
				
			||||
  uint64_t ret; | 
				
			||||
  error_ = cbor_value_get_uint64(&it_, &ret); | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return cpp::fail(error_); | 
				
			||||
  } | 
				
			||||
  error_ = cbor_value_advance(&it_); | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return cpp::fail(error_); | 
				
			||||
  } | 
				
			||||
  return ret; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
auto ArrayDecoder::ParseSigned() -> cpp::result<int32_t, CborError> { | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return cpp::fail(error_); | 
				
			||||
  } | 
				
			||||
  if (!cbor_value_is_unsigned_integer(&it_)) { | 
				
			||||
    error_ = CborErrorIllegalType; | 
				
			||||
    return cpp::fail(error_); | 
				
			||||
  } | 
				
			||||
  uint64_t ret; | 
				
			||||
  error_ = cbor_value_get_uint64(&it_, &ret); | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return cpp::fail(error_); | 
				
			||||
  } | 
				
			||||
  error_ = cbor_value_advance(&it_); | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return cpp::fail(error_); | 
				
			||||
  } | 
				
			||||
  return ret; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
static 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, &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::FindString(const std::string &key) -> std::optional<std::string> { | 
				
			||||
  CborValue val; | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return {}; | 
				
			||||
  } | 
				
			||||
  if (cbor_value_map_find_value(&it_, key.c_str(), &val) != CborNoError) { | 
				
			||||
    return {}; | 
				
			||||
  } | 
				
			||||
  if (!cbor_value_is_byte_string(&val)) { | 
				
			||||
    error_ = CborErrorIllegalType; | 
				
			||||
    return {}; | 
				
			||||
  } | 
				
			||||
  uint8_t *buf; size_t len; | 
				
			||||
  error_ = cbor_value_dup_byte_string(&val, &buf, &len, NULL); | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return cpp::fail(error_); | 
				
			||||
  } | 
				
			||||
  std::string ret(buf, len); | 
				
			||||
  free(buf); | 
				
			||||
  return ret; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
auto MapDecoder::FindUnsigned(const std::string &key) -> std::optional<uint32_t> { | 
				
			||||
  CborValue val; | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return {}; | 
				
			||||
  } | 
				
			||||
  if (cbor_value_map_find_value(&it_, key.c_str(), &val) != CborNoError) { | 
				
			||||
    return {}; | 
				
			||||
  } | 
				
			||||
  if (!cbor_value_is_unsigned_integer(&val)) { | 
				
			||||
    error_ = CborErrorIllegalType; | 
				
			||||
    return {}; | 
				
			||||
  } | 
				
			||||
  uint64_t ret; | 
				
			||||
  error_ = cbor_value_get_uint64(&val, &ret); | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return cpp::fail(error_); | 
				
			||||
  } | 
				
			||||
  return ret; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
auto MapDecoder::FindSigned(const std::string &key) -> std::optional<int32_t> { | 
				
			||||
  CborValue val; | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return {}; | 
				
			||||
  } | 
				
			||||
  if (cbor_value_map_find_value(&it_, key.c_str(), &val) != CborNoError) { | 
				
			||||
    return {}; | 
				
			||||
  } | 
				
			||||
  if (!cbor_value_is_integer(&val)) { | 
				
			||||
    error_ = CborErrorIllegalType; | 
				
			||||
    return {}; | 
				
			||||
  } | 
				
			||||
  int32_t ret; | 
				
			||||
  error_ = cbor_value_get_int(&val, &ret); | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return cpp::fail(error_); | 
				
			||||
  } | 
				
			||||
  return ret; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
} // namespace cbor
 | 
				
			||||
@ -0,0 +1,53 @@ | 
				
			||||
#include "cbor_encoder.hpp" | 
				
			||||
#include <cstdint> | 
				
			||||
#include "esp-idf/components/cbor/tinycbor/src/cbor.h" | 
				
			||||
 | 
				
			||||
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(&encoder, &container_encoder_, container_len); | 
				
			||||
      break; | 
				
			||||
    case CONTAINER_MAP: | 
				
			||||
      error_ = cbor_encoder_create_map(&encoder, &container_encoder_, container_len); | 
				
			||||
      break; | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
auto Encoder::WriteString(const std::string &val) -> void { | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
  error_ = cbor_encode_byte_string(&container_encoder_, val.c_str(), val.size()); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
auto Encoder::WriteUnsigned(uint32_t val) -> void { | 
				
			||||
  if (error_ != CborNoError) { | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
  error_ = cbor_encode_uint(&container_encoder_, val); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
auto Encoder::WriteSigned(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) { | 
				
			||||
    return cpp::fail(error_); | 
				
			||||
  } | 
				
			||||
  if (CborError final_error = cbor_encoder_close_container(&root_encoder, &container_encoder_) != CborNoError) { | 
				
			||||
    return cpp::fail(final_error); | 
				
			||||
  } | 
				
			||||
  return cbor_encoder_get_buffer_size(&root_encoder); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
} // namespace cbor
 | 
				
			||||
 | 
				
			||||
@ -0,0 +1,47 @@ | 
				
			||||
#pragma once | 
				
			||||
 | 
				
			||||
#include <cstdint> | 
				
			||||
namespace cbor { | 
				
			||||
 | 
				
			||||
  class ArrayDecoder { | 
				
			||||
    public: | 
				
			||||
      static auto Create(uint8_t *buffer, size_t buffer_len) -> cpp::result<std::unique_ptr<ArrayDecoder>, CborError>; | 
				
			||||
 | 
				
			||||
      auto ParseString() -> cpp::result<std::string, CborError>; | 
				
			||||
      auto ParseUnsigned() -> cpp::result<uint32_t, CborError>; | 
				
			||||
      auto ParseSigned() -> cpp::result<int32_t, CborError>; | 
				
			||||
 | 
				
			||||
      auto Failed() -> CborError { return error_; } | 
				
			||||
 | 
				
			||||
      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>; | 
				
			||||
 | 
				
			||||
      auto FindString(const std::string &key) -> std::optional<std::string>; | 
				
			||||
      auto FindUnsigned(const std::string &key) -> std::optional<uint32_t>; | 
				
			||||
      auto FindSigned(const std::string &key) -> std::optional<int32_t>; | 
				
			||||
 | 
				
			||||
      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
 | 
				
			||||
@ -0,0 +1,30 @@ | 
				
			||||
#pragma once | 
				
			||||
 | 
				
			||||
#include <cstdint> | 
				
			||||
#include "esp-idf/components/cbor/tinycbor/src/cbor.h" | 
				
			||||
namespace cbor { | 
				
			||||
 | 
				
			||||
  class Encoder { | 
				
			||||
    public: | 
				
			||||
      enum ContainerType { | 
				
			||||
        CONTAINER_ARRAY, | 
				
			||||
        CONTAINER_MAP | 
				
			||||
      }; | 
				
			||||
      Encoder(ContainerType type, uint32_t container_len, uint8_t *buffer, size_t buffer_len); | 
				
			||||
 | 
				
			||||
      auto WriteString(const std::string &val) -> void; | 
				
			||||
      auto WriteUnsigned(uint32_t val) -> void; | 
				
			||||
      auto WriteSigned(int32_t val) -> void; | 
				
			||||
 | 
				
			||||
      auto Finish() -> cpp::result<size_t, CborError>; | 
				
			||||
 | 
				
			||||
      Encoder(const Encoder&) = delete; | 
				
			||||
      Encoder& operator=(const Encoder&) = delete; | 
				
			||||
    private: | 
				
			||||
      CborEncoder root_encoder_; | 
				
			||||
      CborEncoder container_encoder_; | 
				
			||||
 | 
				
			||||
      CborError error_ = CborNoError; | 
				
			||||
  }; | 
				
			||||
 | 
				
			||||
} // namespace cbor
 | 
				
			||||
@ -0,0 +1,12 @@ | 
				
			||||
#pragma once | 
				
			||||
 | 
				
			||||
#include <string> | 
				
			||||
 | 
				
			||||
namespace codecs  { | 
				
			||||
 | 
				
			||||
  enum StreamType { | 
				
			||||
    STREAM_MP3, | 
				
			||||
  }; | 
				
			||||
 | 
				
			||||
  auto GetStreamTypeFromFilename(std::string filename); | 
				
			||||
} | 
				
			||||
@ -1,6 +1,6 @@ | 
				
			||||
idf_component_register( | 
				
			||||
  SRCS "dac.cpp" "gpio_expander.cpp" "battery.cpp" "storage.cpp" "i2c.cpp" | 
				
			||||
  "audio_playback.cpp" "i2s_audio_output.cpp" "display.cpp" "display_init.cpp" "spi.cpp"  | 
				
			||||
  "spi.cpp" "display.cpp" "display_init.cpp" | 
				
			||||
  INCLUDE_DIRS "include" | 
				
			||||
  REQUIRES "esp_adc_cal" "fatfs" "result" "lvgl") | 
				
			||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) | 
				
			||||
 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue