Skip to content

File audio_driver.h

File List > espos_audio > include > espos_audio > audio_driver.h

Go to the documentation of this file

/* SPDX-FileCopyrightText: 2026 Dirk Wahrheit */
/* SPDX-License-Identifier: Apache-2.0 */
#pragma once

#include <cstddef>
#include <cstdint>

namespace espos_audio {

class AudioDriver {
 public:
  virtual ~AudioDriver() = default;

  virtual void init() = 0;

  virtual bool ready() const = 0;

  virtual uint32_t sample_rate() const = 0;

  virtual void play_pcm(const int16_t* samples, size_t frames) = 0;

  virtual void play_cue(const int16_t* samples, size_t frames) {
    play_pcm(samples, frames);
  }

  virtual void set_volume(uint8_t /*pct*/) {}

  virtual void set_enabled(bool /*on*/) {}

  // --- Streaming playback (voice / TTS) -----------------------------------
  //
  // A continuous stream, unlike play_pcm()'s disposable fixed clips: no
  // length cap, no per-chunk silence tail (that would gap the audio), and
  // write_stream() BLOCKS to apply backpressure instead of dropping. The
  // three calls bracket one audio-start / audio-chunk* / audio-stop span.
  //
  // Default no-ops so speaker-less boards still compile.

  virtual bool begin_stream(uint32_t /*rate*/, uint8_t /*bits*/,
                            uint8_t /*channels*/) {
    return false;
  }

  virtual size_t write_stream(const int16_t* /*samples*/, size_t /*frames*/) {
    return 0;
  }

  virtual void end_stream() {}

  // --- Microphone capture (voice-in) --------------------------------------
  //
  // Default: no capture. A board with a wired mic overrides these.

  virtual bool can_capture() const { return false; }

  virtual uint32_t capture_rate() const { return 16000; }

  virtual size_t record_pcm(int16_t* /*out*/, size_t /*max_frames*/) {
    return 0;
  }

  virtual void start_capture() {}
  virtual void stop_capture() {}

  // --- Dual-mic capture (on-device wake feed only) ------------------------
  //
  // The Waveshare panel has TWO live mics (ES7210 MIC1 + MIC2). The mono
  // record_pcm() path above feeds STT/PTT one mic; esp-sr's AFE can take BOTH
  // (format "MM") for noise suppression / beamforming, which lifts far-field
  // wake SNR. These give the wake engine a SEPARATE 2-channel capture handle so
  // the mono STT path stays byte-identical. The two handles open the SAME I2S
  // RX, so they must never be open at once — the wake engine pauses (releasing
  // the 2ch handle) before the STT pipeline runs, so they don't overlap.
  //
  // Default: unsupported (single-mic boards keep the mono "M" AFE).

  virtual bool supports_dual_mic() const { return false; }

  virtual void start_capture2() {}
  virtual void stop_capture2() {}

  virtual size_t record_pcm2(int16_t* /*out*/, size_t /*max_frames*/) {
    return 0;
  }
  virtual void set_mic_gain_db(float /*db*/) {}
  virtual float mic_gain_db() const { return 0.0f; }

  // --- Diagnostic: per-input mic level probe --------------------------------
  //
  // Which physical ADC input each mic is wired to isn't documented for this
  // board, so this measures all four ES7210 inputs to find the live mic(s).
  // Levels for MIC1..MIC4 (index 0..3): RMS and peak magnitude of a short
  // capture. A live mic tracks speech; an unpopulated input flatlines near the
  // noise floor. Returns false if the board has no probe path. Must NOT be
  // called while normal capture is running (it re-opens the ADC).
  struct MicLevels {
    uint16_t rms[4] = {0, 0, 0, 0};
    uint16_t peak[4] = {0, 0, 0, 0};
  };
  virtual bool probe_mic_channels(MicLevels& /*out*/) { return false; }
};

}  // namespace espos_audio