|
| 1 | +#include "esp-box.hpp" |
| 2 | + |
| 3 | +using namespace espp; |
| 4 | + |
| 5 | +//////////////////////// |
| 6 | +// Audio Functions // |
| 7 | +//////////////////////// |
| 8 | + |
| 9 | +static TaskHandle_t play_audio_task_handle_ = NULL; |
| 10 | + |
| 11 | +static bool audio_tx_sent_callback(i2s_chan_handle_t handle, i2s_event_data_t *event, |
| 12 | + void *user_ctx) { |
| 13 | + // notify the main task that we're done |
| 14 | + vTaskNotifyGiveFromISR(play_audio_task_handle_, NULL); |
| 15 | + return true; |
| 16 | +} |
| 17 | + |
| 18 | +bool EspBox::initialize_codec() { |
| 19 | + logger_.info("initializing codec"); |
| 20 | + |
| 21 | + set_es8311_write(std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, |
| 22 | + std::placeholders::_2, std::placeholders::_3)); |
| 23 | + set_es8311_read(std::bind(&espp::I2c::read_at_register, &internal_i2c_, std::placeholders::_1, |
| 24 | + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); |
| 25 | + |
| 26 | + esp_err_t ret_val = ESP_OK; |
| 27 | + audio_hal_codec_config_t cfg; |
| 28 | + memset(&cfg, 0, sizeof(cfg)); |
| 29 | + cfg.codec_mode = AUDIO_HAL_CODEC_MODE_DECODE; |
| 30 | + cfg.dac_output = AUDIO_HAL_DAC_OUTPUT_LINE1; |
| 31 | + cfg.i2s_iface.bits = AUDIO_HAL_BIT_LENGTH_16BITS; |
| 32 | + cfg.i2s_iface.fmt = AUDIO_HAL_I2S_NORMAL; |
| 33 | + cfg.i2s_iface.mode = AUDIO_HAL_MODE_SLAVE; |
| 34 | + cfg.i2s_iface.samples = AUDIO_HAL_16K_SAMPLES; |
| 35 | + |
| 36 | + ret_val |= es8311_codec_init(&cfg); |
| 37 | + ret_val |= es8311_set_bits_per_sample(cfg.i2s_iface.bits); |
| 38 | + ret_val |= es8311_config_fmt((es_i2s_fmt_t)cfg.i2s_iface.fmt); |
| 39 | + ret_val |= es8311_codec_set_voice_volume(volume_); |
| 40 | + ret_val |= es8311_codec_ctrl_state(cfg.codec_mode, AUDIO_HAL_CTRL_START); |
| 41 | + |
| 42 | + if (ESP_OK != ret_val) { |
| 43 | + logger_.error("Codec initialization failed"); |
| 44 | + return false; |
| 45 | + } else { |
| 46 | + logger_.info("Codec initialized"); |
| 47 | + return true; |
| 48 | + } |
| 49 | +} |
| 50 | + |
| 51 | +bool EspBox::initialize_i2s(uint32_t default_audio_rate) { |
| 52 | + logger_.info("initializing i2s driver"); |
| 53 | + logger_.debug("Using newer I2S standard"); |
| 54 | + i2s_chan_config_t chan_cfg = { |
| 55 | + .id = i2s_port, |
| 56 | + .role = I2S_ROLE_MASTER, |
| 57 | + .dma_desc_num = 16, // TODO: calculate form audio rate |
| 58 | + .dma_frame_num = 48, // TODO: calculate from audio rate |
| 59 | + .auto_clear = true, |
| 60 | + .intr_priority = 0, |
| 61 | + }; |
| 62 | + |
| 63 | + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &audio_tx_handle, nullptr)); |
| 64 | + |
| 65 | + audio_std_cfg = { |
| 66 | + .clk_cfg = |
| 67 | + { |
| 68 | + .sample_rate_hz = default_audio_rate, |
| 69 | + .clk_src = I2S_CLK_SRC_DEFAULT, |
| 70 | + .ext_clk_freq_hz = 0, |
| 71 | + .mclk_multiple = I2S_MCLK_MULTIPLE_256, |
| 72 | + }, |
| 73 | + .slot_cfg = |
| 74 | + I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), |
| 75 | + .gpio_cfg = |
| 76 | + { |
| 77 | + .mclk = i2s_mck_io, |
| 78 | + .bclk = i2s_bck_io, |
| 79 | + .ws = i2s_ws_io, |
| 80 | + .dout = i2s_do_io, |
| 81 | + .din = i2s_di_io, |
| 82 | + .invert_flags = |
| 83 | + { |
| 84 | + .mclk_inv = false, |
| 85 | + .bclk_inv = false, |
| 86 | + .ws_inv = false, |
| 87 | + }, |
| 88 | + }, |
| 89 | + }; |
| 90 | + audio_std_cfg.clk_cfg.mclk_multiple = I2S_MCLK_MULTIPLE_256; |
| 91 | + |
| 92 | + ESP_ERROR_CHECK(i2s_channel_init_std_mode(audio_tx_handle, &audio_std_cfg)); |
| 93 | + |
| 94 | + auto buffer_size = calc_audio_buffer_size(default_audio_rate); |
| 95 | + audio_tx_buffer.resize(buffer_size); |
| 96 | + |
| 97 | + audio_tx_stream = xStreamBufferCreate(buffer_size * 4, 0); |
| 98 | + |
| 99 | + play_audio_task_handle_ = xTaskGetCurrentTaskHandle(); |
| 100 | + |
| 101 | + memset(&audio_tx_callbacks_, 0, sizeof(audio_tx_callbacks_)); |
| 102 | + audio_tx_callbacks_.on_sent = audio_tx_sent_callback; |
| 103 | + i2s_channel_register_event_callback(audio_tx_handle, &audio_tx_callbacks_, NULL); |
| 104 | + |
| 105 | + xStreamBufferReset(audio_tx_stream); |
| 106 | + |
| 107 | + ESP_ERROR_CHECK(i2s_channel_enable(audio_tx_handle)); |
| 108 | + |
| 109 | + return true; |
| 110 | +} |
| 111 | + |
| 112 | +bool EspBox::initialize_sound(uint32_t default_audio_rate, |
| 113 | + const espp::Task::BaseConfig &task_config) { |
| 114 | + |
| 115 | + if (!initialize_i2s(default_audio_rate)) { |
| 116 | + logger_.error("Could not initialize I2S driver"); |
| 117 | + return false; |
| 118 | + } |
| 119 | + if (!initialize_codec()) { |
| 120 | + logger_.error("Could not initialize codec"); |
| 121 | + return false; |
| 122 | + } |
| 123 | + |
| 124 | + // Config power control IO |
| 125 | + gpio_set_direction(sound_power_pin, GPIO_MODE_OUTPUT); |
| 126 | + enable_sound(true); |
| 127 | + |
| 128 | + using namespace std::placeholders; |
| 129 | + audio_task_ = espp::Task::make_unique({ |
| 130 | + .callback = std::bind(&EspBox::audio_task_callback, this, _1, _2, _3), |
| 131 | + .task_config = task_config, |
| 132 | + }); |
| 133 | + |
| 134 | + return audio_task_->start(); |
| 135 | +} |
| 136 | + |
| 137 | +void EspBox::enable_sound(bool enable) { gpio_set_level(sound_power_pin, enable); } |
| 138 | + |
| 139 | +bool EspBox::audio_task_callback(std::mutex &m, std::condition_variable &cv, bool &task_notified) { |
| 140 | + // Queue the next I2S out frame to write |
| 141 | + uint16_t available = xStreamBufferBytesAvailable(audio_tx_stream); |
| 142 | + int buffer_size = audio_tx_buffer.size(); |
| 143 | + available = std::min<uint16_t>(available, buffer_size); |
| 144 | + uint8_t *buffer = &audio_tx_buffer[0]; |
| 145 | + memset(buffer, 0, buffer_size); |
| 146 | + |
| 147 | + if (available == 0) { |
| 148 | + i2s_channel_write(audio_tx_handle, buffer, buffer_size, NULL, portMAX_DELAY); |
| 149 | + } else { |
| 150 | + xStreamBufferReceive(audio_tx_stream, buffer, available, 0); |
| 151 | + i2s_channel_write(audio_tx_handle, buffer, available, NULL, portMAX_DELAY); |
| 152 | + } |
| 153 | + return false; // don't stop the task |
| 154 | +} |
| 155 | + |
| 156 | +void EspBox::update_volume_output() { |
| 157 | + if (mute_) { |
| 158 | + es8311_codec_set_voice_volume(0); |
| 159 | + } else { |
| 160 | + es8311_codec_set_voice_volume(volume_); |
| 161 | + } |
| 162 | +} |
| 163 | + |
| 164 | +void EspBox::mute(bool mute) { |
| 165 | + mute_ = mute; |
| 166 | + update_volume_output(); |
| 167 | +} |
| 168 | + |
| 169 | +bool EspBox::is_muted() const { return mute_; } |
| 170 | + |
| 171 | +void EspBox::volume(float volume) { |
| 172 | + volume_ = volume; |
| 173 | + update_volume_output(); |
| 174 | +} |
| 175 | + |
| 176 | +float EspBox::volume() const { return volume_; } |
| 177 | + |
| 178 | +uint32_t EspBox::audio_sample_rate() const { return audio_std_cfg.clk_cfg.sample_rate_hz; } |
| 179 | + |
| 180 | +size_t EspBox::audio_buffer_size() const { return audio_tx_buffer.size(); } |
| 181 | + |
| 182 | +void EspBox::audio_sample_rate(uint32_t sample_rate) { |
| 183 | + logger_.info("Setting audio sample rate to {} Hz", sample_rate); |
| 184 | + // stop the channel |
| 185 | + i2s_channel_disable(audio_tx_handle); |
| 186 | + // update the sample rate |
| 187 | + audio_std_cfg.clk_cfg.sample_rate_hz = sample_rate; |
| 188 | + i2s_channel_reconfig_std_clock(audio_tx_handle, &audio_std_cfg.clk_cfg); |
| 189 | + // clear the buffer |
| 190 | + xStreamBufferReset(audio_tx_stream); |
| 191 | + // restart the channel |
| 192 | + i2s_channel_enable(audio_tx_handle); |
| 193 | +} |
| 194 | + |
| 195 | +void EspBox::play_audio(const std::vector<uint8_t> &data) { play_audio(data.data(), data.size()); } |
| 196 | + |
| 197 | +void EspBox::play_audio(const uint8_t *data, uint32_t num_bytes) { |
| 198 | + play_audio_task_handle_ = xTaskGetCurrentTaskHandle(); |
| 199 | + if (has_sound) { |
| 200 | + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); |
| 201 | + } |
| 202 | + // don't block here |
| 203 | + xStreamBufferSendFromISR(audio_tx_stream, data, num_bytes, NULL); |
| 204 | + has_sound = true; |
| 205 | +} |
0 commit comments