basic i2s output element
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS "audio_decoder.cpp" "audio_task.cpp" "chunk.cpp" "fatfs_audio_input.cpp"
|
||||
"stream_info.cpp" "stream_message.cpp"
|
||||
"stream_info.cpp" "stream_message.cpp" "i2s_audio_output.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span")
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ auto AudioDecoder::SetOutputBuffer(MessageBufferHandle_t* buffer) -> void {
|
||||
output_buffer_ = buffer;
|
||||
}
|
||||
|
||||
auto AudioDecoder::ProcessStreamInfo(StreamInfo& info)
|
||||
auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info)
|
||||
-> cpp::result<void, AudioProcessingError> {
|
||||
stream_info_ = info;
|
||||
|
||||
@@ -62,7 +62,7 @@ auto AudioDecoder::ProcessStreamInfo(StreamInfo& info)
|
||||
return {};
|
||||
}
|
||||
|
||||
auto AudioDecoder::ProcessChunk(cpp::span<std::byte>& chunk)
|
||||
auto AudioDecoder::ProcessChunk(const cpp::span<std::byte>& chunk)
|
||||
-> cpp::result<size_t, AudioProcessingError> {
|
||||
if (current_codec_ == nullptr) {
|
||||
// Should never happen, but fail explicitly anyway.
|
||||
|
||||
+11
-4
@@ -12,8 +12,15 @@
|
||||
|
||||
namespace audio {
|
||||
|
||||
// TODO: tune.
|
||||
const std::size_t kMaxChunkSize = 512;
|
||||
/*
|
||||
* The maximum size of a single chunk of stream data. This should be comfortably
|
||||
* larger than the largest size of a frame of audio we should expect to handle.
|
||||
*
|
||||
* 128 kbps MPEG-1 @ 44.1 kHz is approx. 418 bytes according to the internet.
|
||||
*
|
||||
* TODO(jacqueline): tune as more codecs are added.
|
||||
*/
|
||||
const std::size_t kMaxChunkSize = 2048;
|
||||
|
||||
// TODO: tune
|
||||
static const std::size_t kWorkingBufferSize = kMaxChunkSize * 1.5;
|
||||
@@ -60,11 +67,11 @@ ChunkReader::ChunkReader(MessageBufferHandle_t* stream)
|
||||
: stream_(stream),
|
||||
raw_working_buffer_(static_cast<std::byte*>(
|
||||
heap_caps_malloc(kWorkingBufferSize, MALLOC_CAP_SPIRAM))),
|
||||
working_buffer_(raw_working_buffer_, kWorkingBufferSize){};
|
||||
working_buffer_(raw_working_buffer_, kWorkingBufferSize) {}
|
||||
|
||||
ChunkReader::~ChunkReader() {
|
||||
free(raw_working_buffer_);
|
||||
};
|
||||
}
|
||||
|
||||
auto ChunkReader::Reset() -> void {
|
||||
leftover_bytes_ = 0;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "audio_element.hpp"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "freertos/portmacro.h"
|
||||
|
||||
@@ -50,7 +49,7 @@ FatfsAudioInput::~FatfsAudioInput() {
|
||||
free(output_buffer_);
|
||||
}
|
||||
|
||||
auto FatfsAudioInput::ProcessStreamInfo(StreamInfo& info)
|
||||
auto FatfsAudioInput::ProcessStreamInfo(const StreamInfo& info)
|
||||
-> cpp::result<void, AudioProcessingError> {
|
||||
if (is_file_open_) {
|
||||
f_close(¤t_file_);
|
||||
@@ -83,12 +82,12 @@ auto FatfsAudioInput::ProcessStreamInfo(StreamInfo& info)
|
||||
return {};
|
||||
}
|
||||
|
||||
auto FatfsAudioInput::ProcessChunk(cpp::span<std::byte>& chunk)
|
||||
auto FatfsAudioInput::ProcessChunk(const cpp::span<std::byte>& chunk)
|
||||
-> cpp::result<size_t, AudioProcessingError> {
|
||||
return cpp::fail(UNSUPPORTED_STREAM);
|
||||
}
|
||||
|
||||
auto FatfsAudioInput::GetRingBufferDistance() -> size_t {
|
||||
auto FatfsAudioInput::GetRingBufferDistance() const -> size_t {
|
||||
if (file_buffer_read_pos_ == file_buffer_write_pos_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2,18 +2,20 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "audio_element.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "esp_err.h"
|
||||
#include "freertos/portmacro.h"
|
||||
#include "i2s_stream.h"
|
||||
|
||||
static const i2s_port_t kI2SPort = I2S_NUM_0;
|
||||
#include "audio_element.hpp"
|
||||
#include "dac.hpp"
|
||||
#include "gpio_expander.hpp"
|
||||
#include "result.hpp"
|
||||
|
||||
static const TickType_t kIdleTimeBeforeMute = pdMS_TO_TICKS(1000);
|
||||
static const char* kTag = "I2SOUT";
|
||||
|
||||
namespace drivers {
|
||||
namespace audio {
|
||||
|
||||
auto I2SAudioOutput::create(GpioExpander* expander)
|
||||
auto I2SAudioOutput::create(drivers::GpioExpander* expander)
|
||||
-> cpp::result<std::unique_ptr<I2SAudioOutput>, Error> {
|
||||
// First, we need to perform initial configuration of the DAC chip.
|
||||
auto dac_result = drivers::AudioDac::create(expander);
|
||||
@@ -21,74 +23,84 @@ auto I2SAudioOutput::create(GpioExpander* expander)
|
||||
ESP_LOGE(kTag, "failed to init dac: %d", dac_result.error());
|
||||
return cpp::fail(DAC_CONFIG);
|
||||
}
|
||||
std::unique_ptr<AudioDac> dac = std::move(dac_result.value());
|
||||
std::unique_ptr<drivers::AudioDac> dac = std::move(dac_result.value());
|
||||
|
||||
// Soft mute immediately, in order to minimise any clicks and pops caused by
|
||||
// the initial output element and pipeline configuration.
|
||||
dac->WriteVolume(255);
|
||||
|
||||
i2s_stream_cfg_t i2s_stream_config = i2s_stream_cfg_t{
|
||||
.type = AUDIO_STREAM_WRITER,
|
||||
.i2s_config =
|
||||
{
|
||||
// static_cast bc esp-adf uses enums incorrectly
|
||||
.mode = static_cast<i2s_mode_t>(I2S_MODE_MASTER | I2S_MODE_TX),
|
||||
.sample_rate = 44100,
|
||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
|
||||
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
|
||||
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LOWMED,
|
||||
.dma_buf_count = 8,
|
||||
.dma_buf_len = 64,
|
||||
.use_apll = false,
|
||||
.tx_desc_auto_clear = false,
|
||||
.fixed_mclk = 0,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
|
||||
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
||||
},
|
||||
.i2s_port = kI2SPort,
|
||||
.use_alc = false,
|
||||
.volume = 0, // Does nothing; use AudioDac to change this.
|
||||
.out_rb_size = I2S_STREAM_RINGBUFFER_SIZE,
|
||||
.task_stack = I2S_STREAM_TASK_STACK,
|
||||
.task_core = I2S_STREAM_TASK_CORE,
|
||||
.task_prio = I2S_STREAM_TASK_PRIO,
|
||||
.stack_in_ext = false,
|
||||
.multi_out_num = 0,
|
||||
.uninstall_drv = true,
|
||||
.need_expand = false,
|
||||
.expand_src_bits = I2S_BITS_PER_SAMPLE_16BIT,
|
||||
};
|
||||
audio_element_handle_t i2s_stream_writer =
|
||||
i2s_stream_init(&i2s_stream_config);
|
||||
if (i2s_stream_writer == NULL) {
|
||||
return cpp::fail(Error::STREAM_INIT);
|
||||
}
|
||||
|
||||
// NOTE: i2s_stream_init does some additional setup that hardcodes MCK as
|
||||
// GPIO0. This happens to work fine for us, but be careful if changing.
|
||||
i2s_pin_config_t pin_config = {.mck_io_num = GPIO_NUM_0,
|
||||
.bck_io_num = GPIO_NUM_26,
|
||||
.ws_io_num = GPIO_NUM_27,
|
||||
.data_out_num = GPIO_NUM_5,
|
||||
.data_in_num = I2S_PIN_NO_CHANGE};
|
||||
if (esp_err_t err = i2s_set_pin(kI2SPort, &pin_config) != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to configure i2s pins %x", err);
|
||||
return cpp::fail(Error::I2S_CONFIG);
|
||||
}
|
||||
|
||||
return std::make_unique<I2SAudioOutput>(dac, i2s_stream_writer);
|
||||
return std::make_unique<I2SAudioOutput>(expander, std::move(dac));
|
||||
}
|
||||
|
||||
I2SAudioOutput::I2SAudioOutput(std::unique_ptr<AudioDac>& dac,
|
||||
audio_element_handle_t element)
|
||||
: IAudioOutput(element), dac_(std::move(dac)) {
|
||||
volume_ = 255;
|
||||
}
|
||||
I2SAudioOutput::I2SAudioOutput(drivers::GpioExpander* expander,
|
||||
std::unique_ptr<drivers::AudioDac> dac)
|
||||
: expander_(expander),
|
||||
dac_(std::move(dac)),
|
||||
volume_(255),
|
||||
is_soft_muted_(false) {}
|
||||
|
||||
I2SAudioOutput::~I2SAudioOutput() {
|
||||
// TODO: power down the DAC.
|
||||
}
|
||||
|
||||
auto I2SAudioOutput::ProcessStreamInfo(const StreamInfo& info)
|
||||
-> cpp::result<void, AudioProcessingError> {
|
||||
// TODO(jacqueline): probs do something with the channel hey
|
||||
|
||||
if (!info.BitsPerSample() && !info.SampleRate()) {
|
||||
return cpp::fail(UNSUPPORTED_STREAM);
|
||||
}
|
||||
|
||||
drivers::AudioDac::BitsPerSample bps;
|
||||
switch (*info.BitsPerSample()) {
|
||||
case 16:
|
||||
bps = drivers::AudioDac::BPS_16;
|
||||
break;
|
||||
case 24:
|
||||
bps = drivers::AudioDac::BPS_24;
|
||||
break;
|
||||
case 32:
|
||||
bps = drivers::AudioDac::BPS_32;
|
||||
break;
|
||||
default:
|
||||
return cpp::fail(UNSUPPORTED_STREAM);
|
||||
}
|
||||
|
||||
drivers::AudioDac::SampleRate sample_rate;
|
||||
switch (*info.SampleRate()) {
|
||||
case 44100:
|
||||
sample_rate = drivers::AudioDac::SAMPLE_RATE_44_1;
|
||||
break;
|
||||
case 48000:
|
||||
sample_rate = drivers::AudioDac::SAMPLE_RATE_48;
|
||||
break;
|
||||
default:
|
||||
return cpp::fail(UNSUPPORTED_STREAM);
|
||||
}
|
||||
|
||||
dac_->Reconfigure(bps, sample_rate);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto I2SAudioOutput::ProcessChunk(const cpp::span<std::byte>& chunk)
|
||||
-> cpp::result<std::size_t, AudioProcessingError> {
|
||||
SetSoftMute(false);
|
||||
// TODO(jacqueline): write smaller parts with a small delay so that we can
|
||||
// be responsive to pause and seek commands.
|
||||
return dac_->WriteData(chunk, portMAX_DELAY);
|
||||
}
|
||||
|
||||
auto I2SAudioOutput::IdleTimeout() const -> TickType_t {
|
||||
return kIdleTimeBeforeMute;
|
||||
}
|
||||
|
||||
auto I2SAudioOutput::ProcessIdle() -> cpp::result<void, AudioProcessingError> {
|
||||
// TODO(jacqueline): Consider powering down the dac completely maybe?
|
||||
SetSoftMute(true);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto I2SAudioOutput::SetVolume(uint8_t volume) -> void {
|
||||
volume_ = volume;
|
||||
if (!is_soft_muted_) {
|
||||
@@ -97,18 +109,15 @@ auto I2SAudioOutput::SetVolume(uint8_t volume) -> void {
|
||||
}
|
||||
|
||||
auto I2SAudioOutput::SetSoftMute(bool enabled) -> void {
|
||||
if (enabled) {
|
||||
is_soft_muted_ = true;
|
||||
if (enabled == is_soft_muted_) {
|
||||
return;
|
||||
}
|
||||
is_soft_muted_ = enabled;
|
||||
if (is_soft_muted_) {
|
||||
dac_->WriteVolume(255);
|
||||
} else {
|
||||
is_soft_muted_ = false;
|
||||
dac_->WriteVolume(volume_);
|
||||
}
|
||||
}
|
||||
|
||||
auto I2SAudioOutput::Configure(audio_element_info_t& info) -> void {
|
||||
audio_element_setinfo(element_, &info);
|
||||
i2s_stream_set_clk(element_, info.sample_rates, info.bits, info.channels);
|
||||
}
|
||||
|
||||
} // namespace drivers
|
||||
} // namespace audio
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "ff.h"
|
||||
#include "span.hpp"
|
||||
@@ -23,11 +24,13 @@ class AudioDecoder : public IAudioElement {
|
||||
auto SetInputBuffer(MessageBufferHandle_t*) -> void;
|
||||
auto SetOutputBuffer(MessageBufferHandle_t*) -> void;
|
||||
|
||||
auto ProcessStreamInfo(StreamInfo& info)
|
||||
-> cpp::result<void, AudioProcessingError>;
|
||||
auto ProcessChunk(cpp::span<std::byte>& chunk)
|
||||
-> cpp::result<std::size_t, AudioProcessingError>;
|
||||
auto ProcessIdle() -> cpp::result<void, AudioProcessingError>;
|
||||
auto StackSizeBytes() const -> std::size_t override { return 8196; };
|
||||
|
||||
auto ProcessStreamInfo(const StreamInfo& info)
|
||||
-> cpp::result<void, AudioProcessingError> override;
|
||||
auto ProcessChunk(const cpp::span<std::byte>& chunk)
|
||||
-> cpp::result<std::size_t, AudioProcessingError> override;
|
||||
auto ProcessIdle() -> cpp::result<void, AudioProcessingError> override;
|
||||
|
||||
AudioDecoder(const AudioDecoder&) = delete;
|
||||
AudioDecoder& operator=(const AudioDecoder&) = delete;
|
||||
|
||||
@@ -48,26 +48,26 @@ class IAudioElement {
|
||||
* be tuned according to the observed stack size of each element, as different
|
||||
* elements have fairly different stack requirements.
|
||||
*/
|
||||
virtual auto StackSizeBytes() -> std::size_t { return 2048; };
|
||||
virtual auto StackSizeBytes() const -> std::size_t { return 2048; };
|
||||
|
||||
/*
|
||||
* How long to wait for new data on the input stream before triggering a call
|
||||
* to ProcessIdle(). If this is portMAX_DELAY (the default), then ProcessIdle
|
||||
* will never be called.
|
||||
*/
|
||||
virtual auto IdleTimeout() -> TickType_t { return portMAX_DELAY; }
|
||||
virtual auto IdleTimeout() const -> TickType_t { return portMAX_DELAY; }
|
||||
|
||||
/* Returns this element's input buffer. */
|
||||
auto InputBuffer() -> MessageBufferHandle_t* { return input_buffer_; }
|
||||
auto InputBuffer() const -> MessageBufferHandle_t* { return input_buffer_; }
|
||||
|
||||
/* Returns this element's output buffer. */
|
||||
auto OutputBuffer() -> MessageBufferHandle_t* { return output_buffer_; }
|
||||
auto OutputBuffer() const -> MessageBufferHandle_t* { return output_buffer_; }
|
||||
|
||||
/*
|
||||
* Called when a StreamInfo message is received. Used to configure this
|
||||
* element in preperation for incoming chunks.
|
||||
*/
|
||||
virtual auto ProcessStreamInfo(StreamInfo& info)
|
||||
virtual auto ProcessStreamInfo(const StreamInfo& info)
|
||||
-> cpp::result<void, AudioProcessingError> = 0;
|
||||
|
||||
/*
|
||||
@@ -76,7 +76,7 @@ class IAudioElement {
|
||||
* bytes in this chunk that were actually used; leftover bytes will be
|
||||
* prepended to the next call.
|
||||
*/
|
||||
virtual auto ProcessChunk(cpp::span<std::byte>& chunk)
|
||||
virtual auto ProcessChunk(const cpp::span<std::byte>& chunk)
|
||||
-> cpp::result<std::size_t, AudioProcessingError> = 0;
|
||||
|
||||
/*
|
||||
|
||||
@@ -17,19 +17,19 @@ namespace audio {
|
||||
|
||||
class FatfsAudioInput : public IAudioElement {
|
||||
public:
|
||||
FatfsAudioInput(std::shared_ptr<drivers::SdStorage> storage);
|
||||
explicit FatfsAudioInput(std::shared_ptr<drivers::SdStorage> storage);
|
||||
~FatfsAudioInput();
|
||||
|
||||
auto ProcessStreamInfo(StreamInfo& info)
|
||||
-> cpp::result<void, AudioProcessingError>;
|
||||
auto ProcessChunk(cpp::span<std::byte>& chunk)
|
||||
-> cpp::result<std::size_t, AudioProcessingError> = 0;
|
||||
auto ProcessIdle() -> cpp::result<void, AudioProcessingError>;
|
||||
auto ProcessStreamInfo(const StreamInfo& info)
|
||||
-> cpp::result<void, AudioProcessingError> override;
|
||||
auto ProcessChunk(const cpp::span<std::byte>& chunk)
|
||||
-> cpp::result<std::size_t, AudioProcessingError> override;
|
||||
auto ProcessIdle() -> cpp::result<void, AudioProcessingError> override;
|
||||
|
||||
auto SendChunk(cpp::span<std::byte> dest) -> size_t;
|
||||
|
||||
private:
|
||||
auto GetRingBufferDistance() -> size_t;
|
||||
auto GetRingBufferDistance() const -> size_t;
|
||||
|
||||
std::shared_ptr<drivers::SdStorage> storage_;
|
||||
|
||||
|
||||
@@ -3,30 +3,46 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "audio_element.hpp"
|
||||
#include "result.hpp"
|
||||
|
||||
#include "dac.hpp"
|
||||
#include "gpio_expander.hpp"
|
||||
#include "sys/_stdint.h"
|
||||
|
||||
namespace drivers {
|
||||
namespace audio {
|
||||
|
||||
class I2SAudioOutput : public IAudioOutput {
|
||||
class I2SAudioOutput : public IAudioElement {
|
||||
public:
|
||||
enum Error { DAC_CONFIG, I2S_CONFIG, STREAM_INIT };
|
||||
static auto create(GpioExpander* expander)
|
||||
static auto create(drivers::GpioExpander* expander)
|
||||
-> cpp::result<std::unique_ptr<I2SAudioOutput>, Error>;
|
||||
|
||||
I2SAudioOutput(std::unique_ptr<AudioDac>& dac,
|
||||
audio_element_handle_t element);
|
||||
I2SAudioOutput(drivers::GpioExpander* expander,
|
||||
std::unique_ptr<drivers::AudioDac> dac);
|
||||
~I2SAudioOutput();
|
||||
|
||||
virtual auto SetVolume(uint8_t volume) -> void;
|
||||
virtual auto Configure(audio_element_info_t& info) -> void;
|
||||
virtual auto SetSoftMute(bool enabled) -> void;
|
||||
auto SetInputBuffer(MessageBufferHandle_t* in) -> void { input_buffer_ = in; }
|
||||
|
||||
auto IdleTimeout() const -> TickType_t override;
|
||||
auto ProcessStreamInfo(const StreamInfo& info)
|
||||
-> cpp::result<void, AudioProcessingError> override;
|
||||
auto ProcessChunk(const cpp::span<std::byte>& chunk)
|
||||
-> cpp::result<std::size_t, AudioProcessingError> override;
|
||||
auto ProcessIdle() -> cpp::result<void, AudioProcessingError> override;
|
||||
|
||||
I2SAudioOutput(const I2SAudioOutput&) = delete;
|
||||
I2SAudioOutput& operator=(const I2SAudioOutput&) = delete;
|
||||
|
||||
private:
|
||||
std::unique_ptr<AudioDac> dac_;
|
||||
bool is_soft_muted_ = false;
|
||||
auto SetVolume(uint8_t volume) -> void;
|
||||
auto SetSoftMute(bool enabled) -> void;
|
||||
|
||||
drivers::GpioExpander* expander_;
|
||||
std::unique_ptr<drivers::AudioDac> dac_;
|
||||
|
||||
uint8_t volume_;
|
||||
bool is_soft_muted_;
|
||||
};
|
||||
|
||||
} // namespace drivers
|
||||
} // namespace audio
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "mad.h"
|
||||
#include "span.hpp"
|
||||
|
||||
@@ -1,101 +1,100 @@
|
||||
#include <cstdint>
|
||||
|
||||
std::uint8_t test_mp3[] = {
|
||||
0xff, 0xfb, 0x90, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x69, 0x6e,
|
||||
0x67, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x04,
|
||||
0x7b, 0x00, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb,
|
||||
0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb,
|
||||
0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb,
|
||||
0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb,
|
||||
0xdb, 0xdb, 0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x50, 0x4c, 0x41, 0x4d,
|
||||
0x45, 0x33, 0x2e, 0x31, 0x30, 0x30, 0x04, 0xb9, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x15, 0x20, 0x24, 0x06, 0xb6, 0x41, 0x00, 0x01,
|
||||
0xe0, 0x00, 0x00, 0x04, 0x7b, 0xe4, 0x2f, 0x1b, 0xba, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfb, 0xb0,
|
||||
0xc4, 0x00, 0x00, 0x0a, 0x00, 0x03, 0x6b, 0x80, 0x80, 0x00, 0x21, 0xc4,
|
||||
0x86, 0x6c, 0x7c, 0xf7, 0xb0, 0x41, 0x05, 0x4f, 0x59, 0x20, 0x0d, 0x53,
|
||||
0x58, 0xc1, 0xf0, 0x7c, 0x1f, 0x85, 0xcf, 0x83, 0xe2, 0x70, 0x70, 0x10,
|
||||
0x04, 0x01, 0x00, 0x40, 0x1f, 0x07, 0xc1, 0xf0, 0x7c, 0x1c, 0x04, 0x01,
|
||||
0x0f, 0xa8, 0x13, 0x07, 0xc1, 0xf0, 0x7c, 0x1f, 0x04, 0x03, 0x00, 0x87,
|
||||
0xd0, 0x0f, 0x83, 0xe0, 0xf8, 0x3e, 0x0e, 0x02, 0x01, 0x8f, 0xd2, 0x5c,
|
||||
0x1f, 0x07, 0xc3, 0xe0, 0x80, 0x20, 0x08, 0x3b, 0xf1, 0x38, 0x3e, 0x0f,
|
||||
0x83, 0x80, 0x80, 0x63, 0xfc, 0x1f, 0x07, 0xc1, 0xf0, 0x40, 0x10, 0x0c,
|
||||
0x7f, 0xe8, 0x77, 0x86, 0x56, 0x64, 0x34, 0x52, 0x03, 0x4d, 0x37, 0x14,
|
||||
0xc4, 0x18, 0x4d, 0x84, 0xd8, 0x0c, 0xc0, 0x66, 0x00, 0x20, 0x02, 0x04,
|
||||
0x81, 0xa8, 0x00, 0x80, 0x08, 0x00, 0x80, 0x50, 0x22, 0x24, 0x98, 0x98,
|
||||
0xae, 0x5c, 0xb9, 0x72, 0xe5, 0xcb, 0x82, 0xbe, 0x17, 0x02, 0x82, 0x41,
|
||||
0x41, 0x41, 0x41, 0x42, 0x82, 0x82, 0x82, 0x8e, 0x05, 0x05, 0xff, 0xe8,
|
||||
0x28, 0x28, 0x28, 0x30, 0x50, 0x50, 0x51, 0x41, 0x5f, 0xff, 0xe0, 0xa0,
|
||||
0xa0, 0xa0, 0xa1, 0x41, 0x41, 0x41, 0x47, 0x7f, 0xff, 0xff, 0xf8, 0x28,
|
||||
0x30, 0x50, 0x50, 0x50, 0x57, 0xff, 0xff, 0xff, 0xf4, 0x14, 0x28, 0x28,
|
||||
0x28, 0x28, 0xaf, 0xff, 0xff, 0xff, 0x82, 0x83, 0x05, 0x05, 0x05, 0x05,
|
||||
0x02, 0x8a, 0xff, 0xff, 0xfe, 0x82, 0x85, 0x2a, 0x4c, 0x41, 0x4d, 0x45,
|
||||
0x33, 0x2e, 0x31, 0x30, 0x30, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xff,
|
||||
0xfb, 0x10, 0xc4, 0xcd, 0x83, 0xc0, 0x00, 0x01, 0xa4, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x00, 0x34, 0x80, 0x00, 0x00, 0x04, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa
|
||||
};
|
||||
0xff, 0xfb, 0x90, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x69, 0x6e,
|
||||
0x67, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x04,
|
||||
0x7b, 0x00, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb,
|
||||
0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb,
|
||||
0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb,
|
||||
0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb,
|
||||
0xdb, 0xdb, 0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x50, 0x4c, 0x41, 0x4d,
|
||||
0x45, 0x33, 0x2e, 0x31, 0x30, 0x30, 0x04, 0xb9, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x15, 0x20, 0x24, 0x06, 0xb6, 0x41, 0x00, 0x01,
|
||||
0xe0, 0x00, 0x00, 0x04, 0x7b, 0xe4, 0x2f, 0x1b, 0xba, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfb, 0xb0,
|
||||
0xc4, 0x00, 0x00, 0x0a, 0x00, 0x03, 0x6b, 0x80, 0x80, 0x00, 0x21, 0xc4,
|
||||
0x86, 0x6c, 0x7c, 0xf7, 0xb0, 0x41, 0x05, 0x4f, 0x59, 0x20, 0x0d, 0x53,
|
||||
0x58, 0xc1, 0xf0, 0x7c, 0x1f, 0x85, 0xcf, 0x83, 0xe2, 0x70, 0x70, 0x10,
|
||||
0x04, 0x01, 0x00, 0x40, 0x1f, 0x07, 0xc1, 0xf0, 0x7c, 0x1c, 0x04, 0x01,
|
||||
0x0f, 0xa8, 0x13, 0x07, 0xc1, 0xf0, 0x7c, 0x1f, 0x04, 0x03, 0x00, 0x87,
|
||||
0xd0, 0x0f, 0x83, 0xe0, 0xf8, 0x3e, 0x0e, 0x02, 0x01, 0x8f, 0xd2, 0x5c,
|
||||
0x1f, 0x07, 0xc3, 0xe0, 0x80, 0x20, 0x08, 0x3b, 0xf1, 0x38, 0x3e, 0x0f,
|
||||
0x83, 0x80, 0x80, 0x63, 0xfc, 0x1f, 0x07, 0xc1, 0xf0, 0x40, 0x10, 0x0c,
|
||||
0x7f, 0xe8, 0x77, 0x86, 0x56, 0x64, 0x34, 0x52, 0x03, 0x4d, 0x37, 0x14,
|
||||
0xc4, 0x18, 0x4d, 0x84, 0xd8, 0x0c, 0xc0, 0x66, 0x00, 0x20, 0x02, 0x04,
|
||||
0x81, 0xa8, 0x00, 0x80, 0x08, 0x00, 0x80, 0x50, 0x22, 0x24, 0x98, 0x98,
|
||||
0xae, 0x5c, 0xb9, 0x72, 0xe5, 0xcb, 0x82, 0xbe, 0x17, 0x02, 0x82, 0x41,
|
||||
0x41, 0x41, 0x41, 0x42, 0x82, 0x82, 0x82, 0x8e, 0x05, 0x05, 0xff, 0xe8,
|
||||
0x28, 0x28, 0x28, 0x30, 0x50, 0x50, 0x51, 0x41, 0x5f, 0xff, 0xe0, 0xa0,
|
||||
0xa0, 0xa0, 0xa1, 0x41, 0x41, 0x41, 0x47, 0x7f, 0xff, 0xff, 0xf8, 0x28,
|
||||
0x30, 0x50, 0x50, 0x50, 0x57, 0xff, 0xff, 0xff, 0xf4, 0x14, 0x28, 0x28,
|
||||
0x28, 0x28, 0xaf, 0xff, 0xff, 0xff, 0x82, 0x83, 0x05, 0x05, 0x05, 0x05,
|
||||
0x02, 0x8a, 0xff, 0xff, 0xfe, 0x82, 0x85, 0x2a, 0x4c, 0x41, 0x4d, 0x45,
|
||||
0x33, 0x2e, 0x31, 0x30, 0x30, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xff,
|
||||
0xfb, 0x10, 0xc4, 0xcd, 0x83, 0xc0, 0x00, 0x01, 0xa4, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x00, 0x34, 0x80, 0x00, 0x00, 0x04, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa};
|
||||
std::size_t test_mp3_len = 1147;
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
#include "mad.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
#include "span.hpp"
|
||||
#include "catch2/catch.hpp"
|
||||
#include "span.hpp"
|
||||
|
||||
#include "test.mp3.hpp"
|
||||
|
||||
void load_mp3(cpp::span<std::byte> dest) {
|
||||
cpp::span<std::byte> src(reinterpret_cast<std::byte*>(test_mp3), test_mp3_len);
|
||||
cpp::span<std::byte> src(reinterpret_cast<std::byte*>(test_mp3),
|
||||
test_mp3_len);
|
||||
std::copy(src.begin(), src.begin() + dest.size(), dest.begin());
|
||||
}
|
||||
|
||||
@@ -24,7 +25,7 @@ TEST_CASE("libmad mp3 decoder", "[unit]") {
|
||||
}
|
||||
|
||||
SECTION("processes streams correctly") {
|
||||
std::array<std::byte, 256> input;
|
||||
std::array<std::byte, 2048> input;
|
||||
input.fill(std::byte{0});
|
||||
|
||||
decoder.ResetForNewStream();
|
||||
@@ -38,7 +39,7 @@ TEST_CASE("libmad mp3 decoder", "[unit]") {
|
||||
}
|
||||
|
||||
SECTION("invalid stream fails") {
|
||||
input.fill(std::byte{0xFF});
|
||||
input.fill(std::byte{0x69});
|
||||
|
||||
auto result = decoder.ProcessNextFrame();
|
||||
|
||||
@@ -65,23 +66,26 @@ TEST_CASE("libmad mp3 decoder", "[unit]") {
|
||||
|
||||
// Just check that some kind of data was written. We don't care
|
||||
// about what.
|
||||
REQUIRE(std::to_integer<uint8_t>(output[0]) != 0);
|
||||
REQUIRE(std::to_integer<uint8_t>(output[1]) != 0);
|
||||
REQUIRE(std::to_integer<uint8_t>(output[2]) != 0);
|
||||
REQUIRE(std::to_integer<uint8_t>(output[3]) != 0);
|
||||
bool wrote_something = false;
|
||||
for (int i = 0; i < output.size(); i++) {
|
||||
if (std::to_integer<uint8_t>(output[0]) != 0) {
|
||||
wrote_something = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
REQUIRE(wrote_something == true);
|
||||
}
|
||||
|
||||
SECTION("output format correct") {
|
||||
auto format = decoder.GetOutputFormat();
|
||||
|
||||
// Matches the test data.
|
||||
REQUIRE(format.num_channels == 2);
|
||||
REQUIRE(format.num_channels == 1);
|
||||
REQUIRE(format.sample_rate_hz == 44100);
|
||||
// Matches libmad output
|
||||
REQUIRE(format.bits_per_sample == 24);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@ idf_component_register(
|
||||
SRCS "dac.cpp" "gpio_expander.cpp" "battery.cpp" "storage.cpp" "i2c.cpp"
|
||||
"spi.cpp" "display.cpp" "display_init.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES "esp_adc_cal" "fatfs" "result" "lvgl")
|
||||
REQUIRES "esp_adc_cal" "fatfs" "result" "lvgl" "span")
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
|
||||
|
||||
+72
-8
@@ -4,11 +4,13 @@
|
||||
|
||||
#include "assert.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "hal/i2c_types.h"
|
||||
|
||||
#include "gpio_expander.hpp"
|
||||
#include "hal/i2s_types.h"
|
||||
#include "i2c.hpp"
|
||||
|
||||
namespace drivers {
|
||||
@@ -16,11 +18,55 @@ namespace drivers {
|
||||
static const char* kTag = "AUDIODAC";
|
||||
static const uint8_t kPcm5122Address = 0x4C;
|
||||
static const uint8_t kPcm5122Timeout = 100 / portTICK_RATE_MS;
|
||||
static const i2s_port_t kI2SPort = I2S_NUM_0;
|
||||
|
||||
static const AudioDac::SampleRate kDefaultSampleRate =
|
||||
AudioDac::SAMPLE_RATE_44_1;
|
||||
static const AudioDac::BitsPerSample kDefaultBps = AudioDac::BPS_16;
|
||||
|
||||
auto AudioDac::create(GpioExpander* expander)
|
||||
-> cpp::result<std::unique_ptr<AudioDac>, Error> {
|
||||
// First, instantiate the instance so it can do all of its power on
|
||||
// configuration.
|
||||
std::unique_ptr<AudioDac> dac = std::make_unique<AudioDac>(expander);
|
||||
|
||||
// Whilst we wait for the initial boot, we can work on installing the I2S
|
||||
// driver.
|
||||
i2s_config_t i2s_config = {
|
||||
// static_cast bc esp-adf uses enums incorrectly
|
||||
.mode = static_cast<i2s_mode_t>(I2S_MODE_MASTER | I2S_MODE_TX),
|
||||
.sample_rate = 44100,
|
||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
|
||||
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
|
||||
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
|
||||
.intr_alloc_flags = ESP_INTR_FLAG_LOWMED,
|
||||
// TODO(jacqueline): tune dma buffer size. this seems very smol.
|
||||
.dma_buf_count = 8,
|
||||
.dma_buf_len = 64,
|
||||
.use_apll = false,
|
||||
.tx_desc_auto_clear = false,
|
||||
.fixed_mclk = 0,
|
||||
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
|
||||
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
||||
};
|
||||
|
||||
if (esp_err_t err =
|
||||
i2s_driver_install(kI2SPort, &i2s_config, 0, NULL) != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to configure i2s pins %x", err);
|
||||
return cpp::fail(Error::FAILED_TO_INSTALL_I2S);
|
||||
}
|
||||
|
||||
i2s_pin_config_t pin_config = {.mck_io_num = GPIO_NUM_0,
|
||||
.bck_io_num = GPIO_NUM_26,
|
||||
.ws_io_num = GPIO_NUM_27,
|
||||
.data_out_num = GPIO_NUM_5,
|
||||
.data_in_num = I2S_PIN_NO_CHANGE};
|
||||
if (esp_err_t err = i2s_set_pin(kI2SPort, &pin_config) != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to configure i2s pins %x", err);
|
||||
return cpp::fail(Error::FAILED_TO_INSTALL_I2S);
|
||||
}
|
||||
|
||||
// Now let's double check that the DAC itself came up whilst we we working.
|
||||
bool is_booted = dac->WaitForPowerState(
|
||||
[](bool booted, PowerState state) { return booted; });
|
||||
if (!is_booted) {
|
||||
@@ -28,8 +74,9 @@ auto AudioDac::create(GpioExpander* expander)
|
||||
return cpp::fail(Error::FAILED_TO_BOOT);
|
||||
}
|
||||
|
||||
// Write the initial configuration.
|
||||
dac->WriteRegister(Register::DE_EMPHASIS, 1 << 4);
|
||||
dac->WriteVolume(100);
|
||||
dac->WriteVolume(255);
|
||||
|
||||
bool is_configured =
|
||||
dac->WaitForPowerState([](bool booted, PowerState state) {
|
||||
@@ -43,14 +90,16 @@ auto AudioDac::create(GpioExpander* expander)
|
||||
return dac;
|
||||
}
|
||||
|
||||
AudioDac::AudioDac(GpioExpander* gpio) {
|
||||
this->gpio_ = gpio;
|
||||
};
|
||||
AudioDac::AudioDac(GpioExpander* gpio) : gpio_(gpio) {
|
||||
gpio_->set_pin(GpioExpander::AUDIO_POWER_ENABLE, true);
|
||||
gpio_->Write();
|
||||
}
|
||||
|
||||
AudioDac::~AudioDac(){
|
||||
// TODO: reset stuff like de-emphasis? Reboot the whole dac? Need to think
|
||||
// about this.
|
||||
};
|
||||
AudioDac::~AudioDac() {
|
||||
i2s_driver_uninstall(kI2SPort);
|
||||
gpio_->set_pin(GpioExpander::AUDIO_POWER_ENABLE, false);
|
||||
gpio_->Write();
|
||||
}
|
||||
|
||||
void AudioDac::WriteVolume(uint8_t volume) {
|
||||
WriteRegister(Register::DIGITAL_VOLUME_L, volume);
|
||||
@@ -93,6 +142,21 @@ bool AudioDac::WaitForPowerState(
|
||||
return has_matched;
|
||||
}
|
||||
|
||||
auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> bool {
|
||||
// TODO(jacqueline): investigate how reliable the auto-clocking of the dac
|
||||
// is. We might need to explicit reconfigure the dac here as well if it's not
|
||||
// good enough.
|
||||
i2s_set_clk(kI2SPort, rate, bps, I2S_CHANNEL_STEREO);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto AudioDac::WriteData(const cpp::span<std::byte>& data, TickType_t max_wait)
|
||||
-> std::size_t {
|
||||
std::size_t res = 0;
|
||||
i2s_write(kI2SPort, data.data(), data.size(), &res, max_wait);
|
||||
return res;
|
||||
}
|
||||
|
||||
void AudioDac::WriteRegister(Register reg, uint8_t val) {
|
||||
I2CTransaction transaction;
|
||||
transaction.start()
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
#include <functional>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "freertos/portmacro.h"
|
||||
#include "hal/i2s_types.h"
|
||||
#include "result.hpp"
|
||||
#include "span.hpp"
|
||||
|
||||
#include "gpio_expander.hpp"
|
||||
|
||||
@@ -19,7 +22,9 @@ class AudioDac {
|
||||
enum Error {
|
||||
FAILED_TO_BOOT,
|
||||
FAILED_TO_CONFIGURE,
|
||||
FAILED_TO_INSTALL_I2S,
|
||||
};
|
||||
|
||||
static auto create(GpioExpander* expander)
|
||||
-> cpp::result<std::unique_ptr<AudioDac>, Error>;
|
||||
|
||||
@@ -47,6 +52,22 @@ class AudioDac {
|
||||
/* Returns the current boot-up status and internal state of the DAC */
|
||||
std::pair<bool, PowerState> ReadPowerState();
|
||||
|
||||
enum BitsPerSample {
|
||||
BPS_16 = I2S_BITS_PER_SAMPLE_16BIT,
|
||||
BPS_24 = I2S_BITS_PER_SAMPLE_24BIT,
|
||||
BPS_32 = I2S_BITS_PER_SAMPLE_32BIT
|
||||
};
|
||||
enum SampleRate {
|
||||
SAMPLE_RATE_44_1 = 44100,
|
||||
SAMPLE_RATE_48 = 48000,
|
||||
};
|
||||
|
||||
// TODO(jacqueline): worth supporting channels here as well?
|
||||
auto Reconfigure(BitsPerSample bps, SampleRate rate) -> bool;
|
||||
|
||||
auto WriteData(const cpp::span<std::byte>& data, TickType_t max_wait)
|
||||
-> std::size_t;
|
||||
|
||||
// Not copyable or movable.
|
||||
AudioDac(const AudioDac&) = delete;
|
||||
AudioDac& operator=(const AudioDac&) = delete;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
idf_component_register(
|
||||
SRCS "main.cpp" "app_console.cpp"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES "drivers" "dev_console")
|
||||
REQUIRES "audio" "drivers" "dev_console" "drivers")
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
|
||||
|
||||
Reference in New Issue
Block a user