minimal besser
This commit is contained in:
parent
cb97174697
commit
e50ad54021
3 changed files with 183 additions and 175 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Gitignore settings for ESPHome
|
||||||
|
# This is an example and may include too much for your use-case.
|
||||||
|
# You can modify this file to suit your needs.
|
||||||
|
/.esphome/
|
||||||
|
/secrets.yaml
|
||||||
|
|
@ -4,41 +4,58 @@
|
||||||
// --- WLAN-Konfiguration ---
|
// --- WLAN-Konfiguration ---
|
||||||
const char* ssid = "Stockfeld";
|
const char* ssid = "Stockfeld";
|
||||||
const char* password = "70691554384740972413"; // Hier dein echtes Passwort eintragen
|
const char* password = "70691554384740972413"; // Hier dein echtes Passwort eintragen
|
||||||
|
#define HTTP_PORT 8080
|
||||||
|
#define SAMPLE_RATE 48000
|
||||||
|
// 480 Samples = 960 Bytes (Extrem sichere Größe für den WLAN-Puffer)
|
||||||
|
#define SAMPLES_PER_PKT 480
|
||||||
|
|
||||||
// --- Audio- & I2S-Parameter ---
|
#define PIN_I2S_BCLK GPIO_NUM_0 // D0
|
||||||
#define SAMPLE_RATE 48000
|
#define PIN_I2S_WS GPIO_NUM_1 // D1
|
||||||
#define SAMPLES_PER_PKT 480 // 10 ms Audio pro Frame (verhindert IP-Fragmentierung)
|
#define PIN_I2S_DIN GPIO_NUM_2 // D2
|
||||||
|
|
||||||
#define PIN_I2S_BCLK GPIO_NUM_0 // D0
|
#define DIGITAL_GAIN 1
|
||||||
#define PIN_I2S_WS GPIO_NUM_1 // D1
|
|
||||||
#define PIN_I2S_DIN GPIO_NUM_2 // D2
|
|
||||||
|
|
||||||
// Optionaler digitaler Gain nach korrektem 16-Bit-Downsampling (1 = kein Gain).
|
// 100 Chunks = exakt 1 Sekunde Audio-Puffer im RAM
|
||||||
// Vorsicht: >1 kann bei lauten Signalen übersteuern, wird aber sauber geclippt.
|
#define AUDIO_QUEUE_LEN 100
|
||||||
#define DIGITAL_GAIN 1
|
|
||||||
|
|
||||||
WiFiServer rtspServer(8554);
|
WiFiServer httpServer(HTTP_PORT);
|
||||||
WiFiClient rtspClient;
|
WiFiClient httpClient;
|
||||||
i2s_chan_handle_t rx_chan = NULL;
|
i2s_chan_handle_t rx_chan = NULL;
|
||||||
|
|
||||||
uint16_t rtpSeqNum = 0;
|
volatile bool isStreaming = false;
|
||||||
uint32_t rtpTimestamp = 0;
|
|
||||||
const uint32_t rtpSSRC = 0x12345678;
|
struct AudioChunk {
|
||||||
bool isStreaming = false;
|
uint16_t sampleCount;
|
||||||
|
int16_t samples[SAMPLES_PER_PKT];
|
||||||
|
};
|
||||||
|
|
||||||
|
QueueHandle_t audioQueue;
|
||||||
|
|
||||||
|
const uint8_t wavHeader[44] = {
|
||||||
|
'R', 'I', 'F', 'F',
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
'W', 'A', 'V', 'E',
|
||||||
|
'f', 'm', 't', ' ',
|
||||||
|
16, 0, 0, 0,
|
||||||
|
1, 0,
|
||||||
|
1, 0,
|
||||||
|
0x80, 0xBB, 0x00, 0x00,
|
||||||
|
0x00, 0x77, 0x01, 0x00,
|
||||||
|
2, 0,
|
||||||
|
16, 0,
|
||||||
|
'd', 'a', 't', 'a',
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF
|
||||||
|
};
|
||||||
|
|
||||||
void setupI2S() {
|
void setupI2S() {
|
||||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
||||||
chan_cfg.dma_desc_num = 6;
|
chan_cfg.dma_desc_num = 6;
|
||||||
chan_cfg.dma_frame_num = 240;
|
chan_cfg.dma_frame_num = 240;
|
||||||
|
|
||||||
esp_err_t err = i2s_new_channel(&chan_cfg, NULL, &rx_chan);
|
i2s_new_channel(&chan_cfg, NULL, &rx_chan);
|
||||||
if (err != ESP_OK) {
|
|
||||||
Serial.printf("FEHLER: i2s_new_channel() fehlgeschlagen: %s\n", esp_err_to_name(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
i2s_std_config_t std_cfg = {
|
i2s_std_config_t std_cfg = {
|
||||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
|
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
|
||||||
// ICS-43434 liefert 24-bit-Samples, MSB-aligned in einem 32-bit-Wort:
|
|
||||||
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO),
|
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO),
|
||||||
.gpio_cfg = {
|
.gpio_cfg = {
|
||||||
.mclk = I2S_GPIO_UNUSED,
|
.mclk = I2S_GPIO_UNUSED,
|
||||||
|
|
@ -49,196 +66,137 @@ void setupI2S() {
|
||||||
.invert_flags = { .mclk_inv = false, .bclk_inv = false, .ws_inv = false }
|
.invert_flags = { .mclk_inv = false, .bclk_inv = false, .ws_inv = false }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// WICHTIG: Der L/R-Pin des ICS-43434 muss fest auf GND liegen, damit dieser
|
|
||||||
// Slot (links) tatsächlich die Mikrofondaten enthält. Liegt L/R auf 3.3V,
|
|
||||||
// hier auf I2S_STD_SLOT_RIGHT umstellen.
|
|
||||||
std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT;
|
std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT;
|
||||||
|
|
||||||
err = i2s_channel_init_std_mode(rx_chan, &std_cfg);
|
i2s_channel_init_std_mode(rx_chan, &std_cfg);
|
||||||
if (err != ESP_OK) {
|
i2s_channel_enable(rx_chan);
|
||||||
Serial.printf("FEHLER: i2s_channel_init_std_mode() fehlgeschlagen: %s\n", esp_err_to_name(err));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
err = i2s_channel_enable(rx_chan);
|
void i2sReaderTask(void *param) {
|
||||||
if (err != ESP_OK) {
|
static int32_t i2s_raw[SAMPLES_PER_PKT];
|
||||||
Serial.printf("FEHLER: i2s_channel_enable() fehlgeschlagen: %s\n", esp_err_to_name(err));
|
AudioChunk chunk;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
size_t bytes_read = 0;
|
||||||
|
esp_err_t res = i2s_channel_read(rx_chan, i2s_raw, sizeof(i2s_raw), &bytes_read, portMAX_DELAY);
|
||||||
|
if (res != ESP_OK || bytes_read == 0) continue;
|
||||||
|
|
||||||
|
int samples = bytes_read / sizeof(int32_t);
|
||||||
|
chunk.sampleCount = samples;
|
||||||
|
|
||||||
|
for (int i = 0; i < samples; i++) {
|
||||||
|
int32_t val = (i2s_raw[i] >> 16) * DIGITAL_GAIN;
|
||||||
|
if (val > 32767) val = 32767;
|
||||||
|
if (val < -32768) val = -32768;
|
||||||
|
chunk.samples[i] = (int16_t)val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xQueueSend(audioQueue, &chunk, 0) != pdTRUE) {
|
||||||
|
AudioChunk dummy;
|
||||||
|
xQueueReceive(audioQueue, &dummy, 0);
|
||||||
|
xQueueSend(audioQueue, &chunk, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleRTSP() {
|
void handleHTTP() {
|
||||||
if (rtspServer.hasClient()) {
|
if (httpServer.hasClient()) {
|
||||||
WiFiClient newClient = rtspServer.accept();
|
WiFiClient newClient = httpServer.accept();
|
||||||
if (newClient) {
|
if (newClient) {
|
||||||
if (rtspClient) rtspClient.stop();
|
if (httpClient) httpClient.stop();
|
||||||
rtspClient = newClient;
|
httpClient = newClient;
|
||||||
rtspClient.setTimeout(50); // Timeout auf 50 ms senken, um Blockaden zu verhindern
|
|
||||||
|
// 3 Sekunden Timeout auf Socket-Ebene
|
||||||
|
httpClient.setTimeout(3000);
|
||||||
|
|
||||||
isStreaming = false;
|
isStreaming = false;
|
||||||
|
Serial.printf("Client verbunden: %s (Heap: %u Bytes)\n",
|
||||||
|
httpClient.remoteIP().toString().c_str(), ESP.getFreeHeap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rtspClient || !rtspClient.connected()) return;
|
if (!httpClient || !httpClient.connected()) {
|
||||||
|
if (isStreaming) {
|
||||||
if (rtspClient.available()) {
|
Serial.println("Client getrennt.");
|
||||||
String req = rtspClient.readStringUntil('\n');
|
|
||||||
req.trim();
|
|
||||||
if (req.length() == 0) return;
|
|
||||||
|
|
||||||
int cseq = 1;
|
|
||||||
// Robuster Header-Parser: Wartet bis zum Ende der HTTP/RTSP-Header
|
|
||||||
while (rtspClient.connected()) {
|
|
||||||
String line = rtspClient.readStringUntil('\n');
|
|
||||||
line.trim();
|
|
||||||
if (line.length() == 0) break;
|
|
||||||
|
|
||||||
if (line.startsWith("CSeq:")) {
|
|
||||||
cseq = line.substring(5).toInt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.startsWith("OPTIONS")) {
|
|
||||||
String resp = "RTSP/1.0 200 OK\r\n"
|
|
||||||
"CSeq: " + String(cseq) + "\r\n"
|
|
||||||
"Public: OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN, GET_PARAMETER\r\n\r\n";
|
|
||||||
rtspClient.print(resp);
|
|
||||||
}
|
|
||||||
else if (req.startsWith("DESCRIBE")) {
|
|
||||||
String ipStr = WiFi.localIP().toString();
|
|
||||||
// RFC-konformes SDP mit Verbindungszeile (c=)
|
|
||||||
String sdp = "v=0\r\n"
|
|
||||||
"o=- 0 0 IN IP4 " + ipStr + "\r\n"
|
|
||||||
"s=ESP32-BirdNET-Mic\r\n"
|
|
||||||
"c=IN IP4 " + ipStr + "\r\n"
|
|
||||||
"t=0 0\r\n"
|
|
||||||
"m=audio 0 RTP/AVP 96\r\n"
|
|
||||||
"a=rtpmap:96 L16/48000/1\r\n"
|
|
||||||
"a=control:*\r\n";
|
|
||||||
|
|
||||||
String resp = "RTSP/1.0 200 OK\r\n"
|
|
||||||
"CSeq: " + String(cseq) + "\r\n"
|
|
||||||
"Content-Type: application/sdp\r\n"
|
|
||||||
"Content-Base: rtsp://" + ipStr + ":8554/stream/\r\n"
|
|
||||||
"Content-Length: " + String(sdp.length()) + "\r\n\r\n" + sdp;
|
|
||||||
rtspClient.print(resp);
|
|
||||||
}
|
|
||||||
else if (req.startsWith("SETUP")) {
|
|
||||||
// TCP Interleaved zwingend vorgeben (Docker-Bridge kompatibel).
|
|
||||||
// Hinweis: BirdNET-Go/ffmpeg sollte den Stream mit
|
|
||||||
// "-rtsp_transport tcp" konsumieren, damit es hier keinen
|
|
||||||
// Mismatch mit einer eventuell angefragten UDP-Transport gibt.
|
|
||||||
String resp = "RTSP/1.0 200 OK\r\n"
|
|
||||||
"CSeq: " + String(cseq) + "\r\n"
|
|
||||||
"Session: 12345678\r\n"
|
|
||||||
"Transport: RTP/AVP/TCP;unicast;interleaved=0-1\r\n\r\n";
|
|
||||||
rtspClient.print(resp);
|
|
||||||
}
|
|
||||||
else if (req.startsWith("PLAY")) {
|
|
||||||
String resp = "RTSP/1.0 200 OK\r\n"
|
|
||||||
"CSeq: " + String(cseq) + "\r\n"
|
|
||||||
"Session: 12345678\r\n"
|
|
||||||
"Range: npt=0.000-\r\n\r\n";
|
|
||||||
rtspClient.print(resp);
|
|
||||||
isStreaming = true;
|
|
||||||
}
|
|
||||||
else if (req.startsWith("TEARDOWN")) {
|
|
||||||
rtspClient.print("RTSP/1.0 200 OK\r\nCSeq: " + String(cseq) + "\r\n\r\n");
|
|
||||||
isStreaming = false;
|
isStreaming = false;
|
||||||
}
|
}
|
||||||
else if (req.startsWith("GET_PARAMETER")) {
|
return;
|
||||||
// Wird von ffmpeg/VLC oft als Keepalive während des Streamings
|
}
|
||||||
// gesendet. Unbeantwortet würde der Client in ein Timeout laufen
|
|
||||||
// und ggf. die Verbindung neu aufbauen.
|
if (!isStreaming && httpClient.available()) {
|
||||||
rtspClient.print("RTSP/1.0 200 OK\r\nCSeq: " + String(cseq) + "\r\n\r\n");
|
String req = httpClient.readStringUntil('\n');
|
||||||
|
req.trim();
|
||||||
|
|
||||||
|
while (httpClient.connected()) {
|
||||||
|
String line = httpClient.readStringUntil('\n');
|
||||||
|
line.trim();
|
||||||
|
if (line.length() == 0) break;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
// Unbekannte Methode: trotzdem höflich antworten statt zu schweigen.
|
if (req.startsWith("GET")) {
|
||||||
String resp = "RTSP/1.0 501 Not Implemented\r\n"
|
httpClient.print("HTTP/1.0 200 OK\r\n"
|
||||||
"CSeq: " + String(cseq) + "\r\n\r\n";
|
"Content-Type: audio/wav\r\n"
|
||||||
rtspClient.print(resp);
|
"Connection: keep-alive\r\n\r\n");
|
||||||
|
|
||||||
|
httpClient.write(wavHeader, sizeof(wavHeader));
|
||||||
|
xQueueReset(audioQueue);
|
||||||
|
isStreaming = true;
|
||||||
|
Serial.println("Streaming aktiv (Sende-Schleife aktiviert).");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void streamAudio() {
|
void streamAudio() {
|
||||||
if (!isStreaming || !rtspClient || !rtspClient.connected()) return;
|
if (!isStreaming || !httpClient || !httpClient.connected()) return;
|
||||||
|
|
||||||
static int32_t i2s_raw[SAMPLES_PER_PKT];
|
AudioChunk chunk;
|
||||||
size_t bytes_read = 0;
|
if (xQueueReceive(audioQueue, &chunk, pdMS_TO_TICKS(100)) == pdTRUE) {
|
||||||
|
size_t bytesToSend = chunk.sampleCount * sizeof(int16_t);
|
||||||
|
const uint8_t* p = (const uint8_t*)chunk.samples;
|
||||||
|
size_t remaining = bytesToSend;
|
||||||
|
|
||||||
// DMA liest 480 Samples (10 ms Audio) blockierend mit 20 ms Timeout
|
// KERN-FIX: Sende-Schleife! Behandelt 'Partial Writes' nun sauber,
|
||||||
esp_err_t res = i2s_channel_read(rx_chan, i2s_raw, sizeof(i2s_raw), &bytes_read, pdMS_TO_TICKS(20));
|
// ohne die Verbindung versehentlich selbst zu kappen.
|
||||||
if (res != ESP_OK || bytes_read == 0) return;
|
while (remaining > 0 && httpClient.connected()) {
|
||||||
|
size_t written = httpClient.write(p, remaining);
|
||||||
int samples = bytes_read / sizeof(int32_t);
|
|
||||||
static uint8_t packet[4 + 12 + SAMPLES_PER_PKT * 2]; // 4B TCP-Frame + 12B RTP-Header + Audio-Payload
|
if (written == 0) {
|
||||||
|
Serial.println("Fataler Netzwerk-Timeout -> Abbruch.");
|
||||||
// Konvertierung von 32-Bit I2S (24-bit-Sample, MSB-aligned) auf 16-Bit Big-Endian (RFC 3550 L16).
|
httpClient.stop();
|
||||||
// WICHTIG: >>16 extrahiert die oberen 16 Bit des links-bündigen 24-Bit-Samples korrekt.
|
isStreaming = false;
|
||||||
// Ein optionaler digitaler Gain wird NACH dem Downsampling angewendet und sauber geclippt,
|
return;
|
||||||
// statt die Rohdaten vor dem int16-Cast zu verstärken (das würde bei Übersteuerung
|
}
|
||||||
// zu Wrap-around-Artefakten statt sauberem Clipping führen).
|
|
||||||
for (int i = 0; i < samples; i++) {
|
p += written;
|
||||||
int32_t sample = (i2s_raw[i] >> 16) * DIGITAL_GAIN;
|
remaining -= written;
|
||||||
if (sample > 32767) sample = 32767;
|
}
|
||||||
if (sample < -32768) sample = -32768;
|
|
||||||
int16_t s16 = (int16_t)sample;
|
|
||||||
|
|
||||||
packet[16 + i * 2] = (s16 >> 8) & 0xFF; // MSB
|
|
||||||
packet[16 + i * 2 + 1] = s16 & 0xFF; // LSB
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t rtpSize = 12 + samples * 2;
|
|
||||||
|
|
||||||
// 4-Byte TCP Interleaved Header ($ = 0x24, Channel 0)
|
|
||||||
packet[0] = '$';
|
|
||||||
packet[1] = 0;
|
|
||||||
packet[2] = (rtpSize >> 8) & 0xFF;
|
|
||||||
packet[3] = rtpSize & 0xFF;
|
|
||||||
|
|
||||||
// 12-Byte RTP-Header
|
|
||||||
packet[4] = 0x80;
|
|
||||||
packet[5] = 96 & 0x7F; // Dynamischer Payload-Typ 96
|
|
||||||
packet[6] = (rtpSeqNum >> 8) & 0xFF;
|
|
||||||
packet[7] = rtpSeqNum & 0xFF;
|
|
||||||
packet[8] = (rtpTimestamp >> 24) & 0xFF;
|
|
||||||
packet[9] = (rtpTimestamp >> 16) & 0xFF;
|
|
||||||
packet[10] = (rtpTimestamp >> 8) & 0xFF;
|
|
||||||
packet[11] = rtpTimestamp & 0xFF;
|
|
||||||
packet[12] = (rtpSSRC >> 24) & 0xFF;
|
|
||||||
packet[13] = (rtpSSRC >> 16) & 0xFF;
|
|
||||||
packet[14] = (rtpSSRC >> 8) & 0xFF;
|
|
||||||
packet[15] = rtpSSRC & 0xFF;
|
|
||||||
|
|
||||||
rtpSeqNum++;
|
|
||||||
rtpTimestamp += samples;
|
|
||||||
|
|
||||||
// In einem Frame absenden, um Fragmentierung zu vermeiden.
|
|
||||||
// Rückgabewert prüfen: bei vollem TCP-Sendepuffer werden sonst
|
|
||||||
// stillschweigend Pakete verworfen/unvollständig gesendet.
|
|
||||||
size_t written = rtspClient.write(packet, 4 + rtpSize);
|
|
||||||
if (written != (size_t)(4 + rtpSize)) {
|
|
||||||
Serial.printf("WARNUNG: Nur %u von %u Bytes gesendet (TCP-Puffer voll?)\n",
|
|
||||||
(unsigned)written, (unsigned)(4 + rtpSize));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
delay(200);
|
||||||
|
|
||||||
setupI2S();
|
setupI2S();
|
||||||
|
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
WiFi.setSleep(false); // Verhindert Modem-Sleep und Ping-Latenzspitzen
|
|
||||||
WiFi.begin(ssid, password);
|
WiFi.begin(ssid, password);
|
||||||
while (WiFi.status() != WL_CONNECTED) {
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
delay(250);
|
delay(250);
|
||||||
}
|
}
|
||||||
|
|
||||||
rtspServer.begin();
|
WiFi.setSleep(false);
|
||||||
Serial.print("RTSP URL: rtsp://");
|
|
||||||
Serial.print(WiFi.localIP());
|
audioQueue = xQueueCreate(AUDIO_QUEUE_LEN, sizeof(AudioChunk));
|
||||||
Serial.println(":8554/stream");
|
|
||||||
|
xTaskCreate(i2sReaderTask, "i2s_reader", 8192, NULL, 3, NULL);
|
||||||
|
|
||||||
|
httpServer.begin();
|
||||||
|
Serial.printf("\n=== HTTP Streamer bereit: http://%s:%d/stream ===\n",
|
||||||
|
WiFi.localIP().toString().c_str(), HTTP_PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
handleRTSP();
|
handleHTTP();
|
||||||
streamAudio();
|
streamAudio();
|
||||||
}
|
}
|
||||||
45
birdnet-mic.yaml
Normal file
45
birdnet-mic.yaml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
substitutions:
|
||||||
|
name: "birdnet-mic"
|
||||||
|
friendly_name: "BirdNET ESP32-C6 Mikrofon"
|
||||||
|
|
||||||
|
esphome:
|
||||||
|
name: ${name}
|
||||||
|
friendly_name: ${friendly_name}
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
board: esp32-c6-devkitc-1
|
||||||
|
variant: esp32c6
|
||||||
|
framework:
|
||||||
|
type: esp-idf
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ssid: "Stockfeld"
|
||||||
|
password: "70691554384740972413"
|
||||||
|
# Zwingend nötig: Verhindert Latenzspitzen und Paketverluste im WLAN
|
||||||
|
power_save_mode: none
|
||||||
|
|
||||||
|
# Native API für den Audioabgriff
|
||||||
|
api:
|
||||||
|
reboot_timeout: 0s
|
||||||
|
|
||||||
|
ota:
|
||||||
|
- platform: esphome
|
||||||
|
|
||||||
|
logger:
|
||||||
|
level: INFO
|
||||||
|
|
||||||
|
# I2S Audio Bus (Master-Modus)
|
||||||
|
i2s_audio:
|
||||||
|
- id: i2s_bus
|
||||||
|
i2s_lrclk_pin: GPIO1 # WS / D1
|
||||||
|
i2s_bclk_pin: GPIO0 # SCK / D0
|
||||||
|
|
||||||
|
# ICS-43434 MEMS-Mikrofon
|
||||||
|
microphone:
|
||||||
|
- platform: i2s_audio
|
||||||
|
id: ics43434
|
||||||
|
i2s_audio_id: i2s_bus
|
||||||
|
i2s_din_pin: GPIO2 # SD / D2
|
||||||
|
adc_type: external
|
||||||
|
pdm: false
|
||||||
|
channel: left # Da L/R auf GND liegt
|
||||||
Loading…
Reference in a new issue