Files
ESP32_ZACUS/plip_voice/main/audio.h
T
clement aa7ae277ed
CI / platformio (pull_request) Failing after 4m0s
CI / platformio (push) Failing after 14m58s
feat(plip): voice loop + DTMF + ring cadence
- hook polarity active-HIGH + auto-resync (was LOW)
- ring cadence FT 1.5s ON / 3.5s OFF
- DTMF Goertzel decoder (dtmf.c/h) + rotary debounce
- LISTEN half-duplex: capture → /v1/voice/reply → play
- WAV playback buffered PSRAM + mono→stereo upmix
- SPIFFS mount at boot for pre-loaded greetings
- ES8388: DAC digital vol + mic PGA + GPIO INPUT_OUTPUT
- turn_client multipart + 90s timeout + fixed routing
- debug endpoints: vol/dacvol/offhook/getfile/hookmon
2026-06-15 21:12:33 +02:00

94 lines
3.6 KiB
C

#pragma once
/*
* audio.h — I2S + ES8388 audio interface for the PLIP voice annex.
*
* Provides:
* - es8388 + I2S initialisation (called once from app_main)
* - Blocking 440 Hz tone (Phase A proof)
* - WAV file playback from SD or embedded asset
* - Ring tone (400/450 Hz cadence) for Phase D
* - Async playback command queue (used by cmd_exec / ESP-NOW)
*/
#include <stdint.h>
#include <stddef.h>
#include "esp_err.h"
#include "driver/i2s_std.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Initialise ES8388 and I2S channels. Must be called before any other
* audio_* function. Returns ESP_OK on success. */
esp_err_t audio_init(void);
/* Return the speaker I2S TX channel handle (used by cmd_exec for WAV play). */
i2s_chan_handle_t audio_spk_handle(void);
/* Play a pure sine tone for duration_ms (blocking). */
void audio_play_tone(float frequency_hz, int duration_ms);
/* Enqueue an async play command. path may be:
* - "/sdcard/<file>.wav" — read from SD
* - "embedded://" — built-in C5-E5-G5 cue
* - "" or NULL — same as embedded://
* Returns ESP_OK if the command was enqueued (non-blocking from any task). */
esp_err_t audio_play_async(const char *path);
/* Stop current playback immediately. */
esp_err_t audio_stop(void);
/* True while the audio worker is playing a clip (tone/WAV). The conversation
* LISTEN loop polls this to stay half-duplex: never capture while playing
* (avoids the earpiece→mic feedback that saturated the line). */
bool audio_is_playing(void);
/* Start ring tone cadence (ON 1s / OFF 2s) at ~440 Hz. Continues until
* audio_stop() is called. Non-blocking — spawns an internal task. */
esp_err_t audio_ring_start(void);
/* Enable or disable the power amplifier (GPIO21 PA_ENABLE).
* Call audio_pa_set(true) on off-hook, audio_pa_set(false) on on-hook.
* ring_start forces PA on internally; ring_stop calls audio_pa_set(false)
* only if the handset is on-hook. */
void audio_pa_set(bool enable);
/*
* Capture microphone audio and encode as WAV (16 kHz, mono, S16-LE).
*
* out : caller-provided buffer (must be >= 44 + PCM bytes)
* out_max : size of out in bytes
* max_ms : hard cap on capture duration in milliseconds
* silence_ms: milliseconds of silence after voice onset to trigger stop
*
* Returns total bytes written (44-byte WAV header + PCM), or -1 on error.
* Uses half-duplex: disables TX speaker during capture and re-enables it
* on return. Safe to call from any task; not reentrant.
*/
int audio_capture_wav(uint8_t *out, size_t out_max, int max_ms, int silence_ms);
/*
* Streaming capture API — avoids a large output buffer by letting the caller
* write chunks incrementally (e.g. directly into a TCP socket).
*
* Usage:
* audio_capture_begin(max_ms, silence_ms) — enable RX, returns 0 on OK
* audio_capture_read_frame(mono_out, n_samples, rms_sq_out)
* — read one 20 ms frame (mono S16)
* fills n_samples mono samples,
* sets *rms_sq_out (energy metric)
* returns actual samples read, 0=timeout, -1=error
* audio_capture_end() — disable RX, re-enable TX
*
* n_samples must be 320 (= SAMPLE_RATE/1000*20, one 20 ms frame).
* The caller is responsible for VAD and stop logic.
*/
int audio_capture_begin(int max_ms, int silence_ms);
int audio_capture_read_frame(int16_t *mono_out, int n_samples, int64_t *rms_sq_out);
void audio_capture_end(void);
#ifdef __cplusplus
}
#endif