|
| 1 | +/* Copyright (c) 2020, Peter Barrett |
| 2 | +** |
| 3 | +** Permission to use, copy, modify, and/or distribute this software for |
| 4 | +** any purpose with or without fee is hereby granted, provided that the |
| 5 | +** above copyright notice and this permission notice appear in all copies. |
| 6 | +** |
| 7 | +** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL |
| 8 | +** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED |
| 9 | +** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR |
| 10 | +** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES |
| 11 | +** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, |
| 12 | +** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OpR OTHER TORTIOUS ACTION, |
| 13 | +** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS |
| 14 | +** SOFTWARE. |
| 15 | +*/ |
| 16 | + |
| 17 | +#include "esp_wifi.h" |
| 18 | +#include "tcpip_adapter.h" |
| 19 | +#include "esp_event_loop.h" |
| 20 | +#include "esp_event.h" |
| 21 | +#include "esp_system.h" |
| 22 | +#include "esp_int_wdt.h" |
| 23 | +#include "esp_task_wdt.h" |
| 24 | +#include "soc/rtc.h" |
| 25 | +#include "freertos/task.h" |
| 26 | +#include "driver/i2s.h" |
| 27 | +#include "nvs_flash.h" |
| 28 | +#include "nvs.h" |
| 29 | + |
| 30 | +#define PERF |
| 31 | +#include "src/streamer.h" |
| 32 | +#include "src/player.h" |
| 33 | +#include "src/video.h" |
| 34 | + |
| 35 | +#include <map> |
| 36 | +#include <string> |
| 37 | +using namespace std; |
| 38 | + |
| 39 | +//================================================================================ |
| 40 | +//================================================================================ |
| 41 | +// Audio |
| 42 | +// Single pin with second order software PDM modulation |
| 43 | +// see https://www.sigma-delta.de/sdopt/index.html for the tool used to design the Delta Sigma modulator |
| 44 | +// 48000hz sample rate, 32x oversampling for PDM |
| 45 | +// Generous 192k bitrate, low complexity, low latency SBC codec |
| 46 | + |
| 47 | +void audio_init_hw() |
| 48 | +{ |
| 49 | + mem("audio_init_hw"); |
| 50 | + pinMode(IR_PIN,INPUT); |
| 51 | + |
| 52 | + i2s_config_t i2s_config; |
| 53 | + i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX); |
| 54 | + i2s_config.sample_rate = 48000; |
| 55 | + i2s_config.bits_per_sample = (i2s_bits_per_sample_t)16, |
| 56 | + i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, |
| 57 | + i2s_config.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S); |
| 58 | + i2s_config.dma_buf_count = 2; |
| 59 | + i2s_config.dma_buf_len = 512; // 2k * 2 bytes for audio buffers, pretty tight |
| 60 | + i2s_config.use_apll = false; |
| 61 | + i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; |
| 62 | + i2s_driver_install((i2s_port_t)1, &i2s_config, 0, NULL); |
| 63 | + mem("after audio_init_hw"); |
| 64 | + |
| 65 | + i2s_pin_config_t pin_config; |
| 66 | + pin_config.bck_io_num = I2S_PIN_NO_CHANGE; |
| 67 | + pin_config.ws_io_num = I2S_PIN_NO_CHANGE; |
| 68 | + pin_config.data_out_num = 18; |
| 69 | + pin_config.data_in_num = I2S_PIN_NO_CHANGE; |
| 70 | + i2s_set_pin((i2s_port_t)1, &pin_config); |
| 71 | +} |
| 72 | + |
| 73 | +void pdm_second_order(uint16_t* dst, const int16_t* src, int len, int32_t a1 = 0x7FFF*1.18940, int a2 = 0x7FFF*2.12340) |
| 74 | +{ |
| 75 | + static int32_t _i0; |
| 76 | + static int32_t _i1; |
| 77 | + static int32_t _i2; |
| 78 | + int32_t i0 = _i0; // force compiler to use registers |
| 79 | + int32_t i1 = _i1; |
| 80 | + int32_t i2 = _i2; |
| 81 | + |
| 82 | + uint32_t b = 0; |
| 83 | + int32_t s = 0; |
| 84 | + len <<= 1; |
| 85 | + while (len--) |
| 86 | + { |
| 87 | + if (len & 1) |
| 88 | + s = *src++ * 2; |
| 89 | + i0 = (i0 + s) >> 1; // lopass |
| 90 | + int n = 16; |
| 91 | + while (n--) { |
| 92 | + b <<= 1; |
| 93 | + if (i2 >= 0) { |
| 94 | + i1 += i0 - a1 - (i2 >> 7); // feedback |
| 95 | + i2 += i1 - a2; |
| 96 | + b |= 1; |
| 97 | + } else { |
| 98 | + i1 += i0 + a1 - (i2 >> 7); // feedback |
| 99 | + i2 += i1 + a2; |
| 100 | + } |
| 101 | + } |
| 102 | + *dst++ = b; |
| 103 | + } |
| 104 | + _i0 = i0; |
| 105 | + _i1 = i1; |
| 106 | + _i2 = i2; |
| 107 | +} |
| 108 | + |
| 109 | +const int16_t _sin32[32] = { |
| 110 | + 0x0000,0xE708,0xCF05,0xB8E4,0xA57F,0x9594,0x89C0,0x8277, |
| 111 | + 0x8001,0x8277,0x89C0,0x9594,0xA57F,0xB8E4,0xCF05,0xE708, |
| 112 | + 0x0000,0x18F8,0x30FB,0x471C,0x5A81,0x6A6C,0x7640,0x7D89, |
| 113 | + 0x7FFF,0x7D89,0x7640,0x6A6C,0x5A81,0x471C,0x30FB,0x18F8, |
| 114 | +}; |
| 115 | + |
| 116 | +uint8_t _beep; |
| 117 | +void beep() |
| 118 | +{ |
| 119 | + _beep = 5; |
| 120 | +} |
| 121 | + |
| 122 | +// this routine turns PCM into PDM. Also handles generating slience and beeps |
| 123 | +void write_pcm_16(const int16_t* s, int n, int channels) |
| 124 | +{ |
| 125 | + uint16_t samples_data[128*2]; |
| 126 | + |
| 127 | + PLOG(PDM_START); |
| 128 | + if (_beep) { |
| 129 | + int16_t* b = (int16_t*)samples_data + 128; // both src and dst buffer to save stack |
| 130 | + for (int i = 0; i < 128; i++) |
| 131 | + b[i] = _sin32[i & 31] >> 2; // sinewave beep plz |
| 132 | + _beep--; |
| 133 | + pdm_second_order(samples_data,b,128); |
| 134 | + } else { |
| 135 | + if (s) |
| 136 | + pdm_second_order(samples_data,s,n); |
| 137 | + else { |
| 138 | + for (int i = 0; i < 128*2; i++) |
| 139 | + samples_data[i] = 0xAAAA; // PDM silence |
| 140 | + } |
| 141 | + } |
| 142 | + size_t i2s_bytes_write; |
| 143 | + i2s_write((i2s_port_t)1, samples_data, sizeof(samples_data), &i2s_bytes_write, portMAX_DELAY); // audio thread will block here |
| 144 | + PLOG(PDM_END); |
| 145 | +} |
| 146 | + |
| 147 | +//================================================================================ |
| 148 | +//================================================================================ |
| 149 | +// Store the pts of the current movie in non-volatile storage |
| 150 | + |
| 151 | +nvs_handle _nvs; |
| 152 | +static int nv_open() |
| 153 | +{ |
| 154 | + if (_nvs || (nvs_open("espflix", NVS_READWRITE, &_nvs) == 0)) |
| 155 | + return 0; |
| 156 | + return -1; |
| 157 | +} |
| 158 | + |
| 159 | +// force key to max 15 chars |
| 160 | +static const char* limit_key(const char* key) |
| 161 | +{ |
| 162 | + int n = strlen(key); |
| 163 | + return (n < 15) ? key : key + (n-15); |
| 164 | +} |
| 165 | + |
| 166 | +int64_t nv_read(const char* key) |
| 167 | +{ |
| 168 | + int64_t pts = 0; |
| 169 | + if (nv_open() == 0) |
| 170 | + nvs_get_i64(_nvs,limit_key(key),&pts); |
| 171 | + return pts; |
| 172 | +} |
| 173 | + |
| 174 | +void nv_write(const char* key, int64_t pts) |
| 175 | +{ |
| 176 | + if (nv_open() == 0) |
| 177 | + nvs_set_i64(_nvs,limit_key(key),pts); |
| 178 | +} |
| 179 | + |
| 180 | +//================================================================================ |
| 181 | +//================================================================================ |
| 182 | +// WIFI |
| 183 | + |
| 184 | +extern EventGroupHandle_t _event_group; |
| 185 | +WiFiState _wifi_state = NONE; |
| 186 | +WiFiState wifi_state() |
| 187 | +{ |
| 188 | + return _wifi_state; |
| 189 | +} |
| 190 | + |
| 191 | +std::map<string,int> _ssids; |
| 192 | +std::map<string,int>& wifi_list() |
| 193 | +{ |
| 194 | + return _ssids; |
| 195 | +} |
| 196 | + |
| 197 | +// manually entered ssid/password |
| 198 | +void wifi_join(const char* ssid, const char* pwd) |
| 199 | +{ |
| 200 | + wifi_config_t wifi_config = {0}; |
| 201 | + strcpy((char*)wifi_config.sta.ssid, ssid); |
| 202 | + strcpy((char*)wifi_config.sta.password, pwd); |
| 203 | + esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config); |
| 204 | + esp_wifi_connect(); |
| 205 | + _wifi_state = CONNECTING; |
| 206 | +} |
| 207 | + |
| 208 | +// current ssid if any |
| 209 | +std::string wifi_ssid() |
| 210 | +{ |
| 211 | + wifi_config_t wifi_config = {0}; |
| 212 | + esp_wifi_get_config(ESP_IF_WIFI_STA,&wifi_config); |
| 213 | + return (char*)wifi_config.sta.ssid; |
| 214 | +} |
| 215 | + |
| 216 | +void wifi_scan() |
| 217 | +{ |
| 218 | + wifi_scan_config_t config = {0}; |
| 219 | + config.scan_type = WIFI_SCAN_TYPE_ACTIVE; |
| 220 | + esp_wifi_scan_start(&config,false); |
| 221 | + _wifi_state = SCANNING; |
| 222 | +} |
| 223 | + |
| 224 | +void wifi_disconnect() |
| 225 | +{ |
| 226 | + esp_wifi_disconnect(); |
| 227 | +} |
| 228 | + |
| 229 | +static esp_err_t event_handler(void *ctx, system_event_t *event) |
| 230 | +{ |
| 231 | + printf("EVENT %d\n",event->event_id); |
| 232 | + switch(event->event_id) |
| 233 | + { |
| 234 | + case SYSTEM_EVENT_STA_START: |
| 235 | + printf("SYSTEM_EVENT_STA_START\n"); |
| 236 | + esp_wifi_connect(); |
| 237 | + break; |
| 238 | + |
| 239 | + case SYSTEM_EVENT_STA_GOT_IP: |
| 240 | + tcpip_adapter_ip_info_t ip_info; |
| 241 | + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); |
| 242 | + printf("Local IP Address: %s\n", ip4addr_ntoa(&ip_info.ip)); |
| 243 | + _ssids.clear(); |
| 244 | + _wifi_state = CONNECTED; |
| 245 | + break; |
| 246 | + |
| 247 | + case SYSTEM_EVENT_STA_DISCONNECTED: |
| 248 | + printf("SYSTEM_EVENT_STA_DISCONNECTED %d\n",event->event_info.disconnected.reason); |
| 249 | + wifi_scan(); |
| 250 | + break; |
| 251 | + |
| 252 | + case SYSTEM_EVENT_SCAN_DONE: |
| 253 | + { |
| 254 | + uint16_t apCount = 0; |
| 255 | + esp_wifi_scan_get_ap_num(&apCount); |
| 256 | + printf("SYSTEM_EVENT_SCAN_DONE %d %d\n",apCount,sizeof(wifi_ap_record_t)*apCount); |
| 257 | + if (apCount > 16) |
| 258 | + apCount = 16; |
| 259 | + wifi_ap_record_t list[16]; // careful of large stack 16*80 bytes. Limitation of the 16 highest power APs |
| 260 | + esp_wifi_scan_get_ap_records(&apCount, list); |
| 261 | + _ssids.clear(); |
| 262 | + for (uint16_t i = 0; i < apCount; i++) { |
| 263 | + wifi_ap_record_t *ap = list + i; |
| 264 | + const char* ssid = (const char *)ap->ssid; |
| 265 | + if (_ssids.find(ssid) == _ssids.end()) { |
| 266 | + _ssids[ssid] = (ap->rssi << 8) | ap->authmode; |
| 267 | + //printf("\"%s\",%d,\n",ssid,(ap->rssi << 8) | ap->authmode); |
| 268 | + } |
| 269 | + } |
| 270 | + } |
| 271 | + _wifi_state = SCAN_COMPLETE; |
| 272 | + break; |
| 273 | + } |
| 274 | + return ESP_OK; |
| 275 | +} |
| 276 | + |
| 277 | +void init_wifi() |
| 278 | +{ |
| 279 | + esp_log_level_set("wifi", ESP_LOG_VERBOSE); |
| 280 | + tcpip_adapter_init(); |
| 281 | + esp_event_loop_init(event_handler, NULL); |
| 282 | + |
| 283 | + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); |
| 284 | + cfg.static_tx_buf_num = 8; |
| 285 | + cfg.static_rx_buf_num = 16; |
| 286 | + esp_wifi_init(&cfg); |
| 287 | + esp_wifi_set_mode(WIFI_MODE_STA); |
| 288 | + esp_wifi_start(); |
| 289 | + printf("connecting to %s\n",wifi_ssid().c_str()); |
| 290 | + |
| 291 | + mem("after esp_wifi_start"); |
| 292 | + //wifi_join("failure","test"); |
| 293 | +} |
| 294 | + |
| 295 | +//================================================================================ |
| 296 | +//================================================================================ |
| 297 | +// Decide on a video standard |
| 298 | + |
| 299 | +#define PAL 0 |
| 300 | +#define NTSC 1 |
| 301 | + |
| 302 | +void setup() |
| 303 | +{ |
| 304 | + rtc_clk_cpu_freq_set(RTC_CPU_FREQ_240M); |
| 305 | + _event_group = xEventGroupCreate(); |
| 306 | + mem("setup"); |
| 307 | + init_wifi(); |
| 308 | +} |
| 309 | + |
| 310 | +// this loop always runs on app_core (1). |
| 311 | +void loop() |
| 312 | +{ |
| 313 | + espflix_run(1); // never returns |
| 314 | +} |
0 commit comments