mirror of
https://github.com/esphome/esphome.git
synced 2026-10-26 16:02:13 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f9b38976c | ||
|
|
af8b53e8c4 | ||
|
|
f2c3174cb7 | ||
|
|
f2883123b5 | ||
|
|
20649644f5 |
No files matched your search
@@ -270,21 +270,23 @@ void I2SAudioMicrophone::mic_task(void *params) {
|
||||
|
||||
{ // Ensures the samples vector is freed when the task stops
|
||||
|
||||
const size_t bytes_to_read = this_microphone->audio_stream_info_.ms_to_bytes(READ_DURATION_MS);
|
||||
// Copied so the format cannot change while the task runs
|
||||
const audio::AudioStreamInfo stream_info = this_microphone->audio_stream_info_;
|
||||
const size_t bytes_to_read = stream_info.ms_to_bytes(READ_DURATION_MS);
|
||||
std::vector<uint8_t> samples;
|
||||
samples.reserve(bytes_to_read);
|
||||
|
||||
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_RUNNING);
|
||||
|
||||
while (!(xEventGroupGetBits(this_microphone->event_group_) & MicrophoneEventGroupBits::COMMAND_STOP)) {
|
||||
if (this_microphone->data_callbacks_.size() > 0) {
|
||||
if (this_microphone->has_data_callbacks_()) {
|
||||
samples.resize(bytes_to_read);
|
||||
size_t bytes_read = this_microphone->read_(samples.data(), bytes_to_read, 2 * READ_DURATION_MS);
|
||||
samples.resize(bytes_read);
|
||||
if (this_microphone->correct_dc_offset_) {
|
||||
this_microphone->fix_dc_offset_(samples);
|
||||
}
|
||||
this_microphone->data_callbacks_.call(samples);
|
||||
this_microphone->publish_audio_(samples, stream_info);
|
||||
} else {
|
||||
vTaskDelay(pdMS_TO_TICKS(READ_DURATION_MS));
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ void MicroWakeWord::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
this->microphone_source_->add_data_callback([this](const std::vector<uint8_t> &data) {
|
||||
this->microphone_source_->add_data_callback([this](const microphone::AudioCapture &capture) {
|
||||
if (this->state_ == State::STOPPED) {
|
||||
return;
|
||||
}
|
||||
@@ -141,7 +141,7 @@ void MicroWakeWord::setup() {
|
||||
// Producer-only write: never touches consumer state. If the buffer is full, ask the inference task
|
||||
// to drain it - reset() is a consumer operation and must run on the inference task's thread.
|
||||
// Disable partial writes so audio chunks are either fully accepted or rejected and handled below.
|
||||
if (temp_ring_buffer->write_without_replacement(data.data(), data.size(), 0, false) == 0) {
|
||||
if (temp_ring_buffer->write_without_replacement(capture.data.data(), capture.data.size(), 0, false) == 0) {
|
||||
xEventGroupSetBits(this->event_group_,
|
||||
EventGroupBits::WARNING_FULL_RING_BUFFER | EventGroupBits::COMMAND_RESET_RING_BUFFER);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MICROPHONE,
|
||||
CONF_ON_DATA,
|
||||
CONF_TRIGGER_ID,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.coroutine import CoroPriority, coroutine_with_priority
|
||||
@@ -28,13 +29,19 @@ microphone_ns = cg.esphome_ns.namespace("microphone")
|
||||
|
||||
Microphone = microphone_ns.class_("Microphone")
|
||||
MicrophoneSource = microphone_ns.class_("MicrophoneSource")
|
||||
DataTrigger = microphone_ns.class_(
|
||||
"DataTrigger",
|
||||
automation.Trigger.template(
|
||||
cg.std_vector.template(cg.uint8).operator("ref").operator("const")
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def setup_microphone_core_(var: MockObj, config: ConfigType) -> None:
|
||||
for conf in config.get(CONF_ON_DATA, []):
|
||||
await automation.build_callback_automation(
|
||||
var,
|
||||
"add_data_callback",
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger,
|
||||
[(cg.std_vector.template(cg.uint8).operator("ref").operator("const"), "x")],
|
||||
conf,
|
||||
)
|
||||
@@ -48,7 +55,9 @@ async def register_microphone(var: MockObj, config: ConfigType) -> None:
|
||||
|
||||
MICROPHONE_SCHEMA = cv.Schema.extend(audio.AUDIO_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.Optional(CONF_ON_DATA): automation.validate_automation({}),
|
||||
cv.Optional(CONF_ON_DATA): automation.validate_automation(
|
||||
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DataTrigger)}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "microphone.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::microphone {
|
||||
|
||||
/// Reuses one vector so `x` stays valid in automations with a delay.
|
||||
class DataTrigger final : public Trigger<const std::vector<uint8_t> &> {
|
||||
public:
|
||||
explicit DataTrigger(Microphone *parent) {
|
||||
parent->add_data_callback([this](const AudioCapture &capture) {
|
||||
this->data_.assign(capture.data.begin(), capture.data.end());
|
||||
this->trigger(this->data_);
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<uint8_t> data_;
|
||||
};
|
||||
|
||||
} // namespace esphome::microphone
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
@@ -16,18 +19,68 @@ enum State : uint8_t {
|
||||
STATE_STOPPING,
|
||||
};
|
||||
|
||||
struct AudioCapture {
|
||||
/// Only valid during the callback
|
||||
std::span<const uint8_t> data;
|
||||
/// Format of data
|
||||
audio::AudioStreamInfo stream_info;
|
||||
/// esp_timer_get_time() of the first frame, or 0 if unknown
|
||||
int64_t capture_time_us{0};
|
||||
};
|
||||
|
||||
// Remove before 2027.5.0. A template, so it only warns for the callbacks that use it.
|
||||
template<typename F>
|
||||
ESPDEPRECATED("Take const microphone::AudioCapture & in data callbacks instead. Removed in 2027.5.0", "2026.11.0")
|
||||
constexpr void warn_vector_data_callback() {}
|
||||
|
||||
template<typename F> auto as_capture_callback(F &&callback) {
|
||||
using Fn = std::decay_t<F>;
|
||||
if constexpr (std::is_invocable_v<const Fn &, const AudioCapture &>) {
|
||||
return Fn(std::forward<F>(callback));
|
||||
} else {
|
||||
// Remove before 2027.5.0
|
||||
static_assert(std::is_invocable_v<const Fn &, const std::vector<uint8_t> &>,
|
||||
"A data callback takes (const microphone::AudioCapture &)");
|
||||
warn_vector_data_callback<Fn>();
|
||||
return [callback = Fn(std::forward<F>(callback))](const AudioCapture &capture) {
|
||||
const std::vector<uint8_t> copy(capture.data.begin(), capture.data.end());
|
||||
callback(copy);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
using DataCallbackManagerBase = CallbackManager<void(const AudioCapture &)>;
|
||||
|
||||
class DataCallbackManager : public DataCallbackManagerBase {
|
||||
public:
|
||||
explicit DataCallbackManager(const audio::AudioStreamInfo *stream_info) : stream_info_(stream_info) {}
|
||||
|
||||
using DataCallbackManagerBase::call;
|
||||
|
||||
// Remove before 2027.5.0, along with this class (use a plain CallbackManager)
|
||||
ESPDEPRECATED("Use Microphone::publish_audio_() instead. Removed in 2027.5.0", "2026.11.0")
|
||||
void call(const std::vector<uint8_t> &data) { this->call(AudioCapture{data, *this->stream_info_, 0}); }
|
||||
|
||||
protected:
|
||||
const audio::AudioStreamInfo *stream_info_;
|
||||
};
|
||||
|
||||
class Microphone {
|
||||
public:
|
||||
virtual void start() = 0;
|
||||
virtual void stop() = 0;
|
||||
|
||||
/// Callbacks take (const AudioCapture &) and run on the microphone's task.
|
||||
template<typename F> void add_data_callback(F &&data_callback) {
|
||||
this->data_callbacks_.add([this, data_callback](const std::vector<uint8_t> &data) {
|
||||
if (this->mute_state_) {
|
||||
data_callback(std::vector<uint8_t>(data.size(), 0));
|
||||
} else {
|
||||
data_callback(data);
|
||||
}
|
||||
});
|
||||
this->data_callbacks_.add(
|
||||
[this, callback = as_capture_callback(std::forward<F>(data_callback))](const AudioCapture &capture) {
|
||||
if (this->mute_state_) {
|
||||
const std::vector<uint8_t> zeros(capture.data.size(), 0);
|
||||
callback(AudioCapture{zeros, capture.stream_info, capture.capture_time_us});
|
||||
} else {
|
||||
callback(capture);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool is_running() const { return this->state_ == STATE_RUNNING; }
|
||||
@@ -39,12 +92,20 @@ class Microphone {
|
||||
audio::AudioStreamInfo get_audio_stream_info() { return this->audio_stream_info_; }
|
||||
|
||||
protected:
|
||||
void publish_audio_(std::span<const uint8_t> data, const audio::AudioStreamInfo &stream_info,
|
||||
int64_t capture_time_us = 0) {
|
||||
this->data_callbacks_.call(AudioCapture{data, stream_info, capture_time_us});
|
||||
}
|
||||
|
||||
bool has_data_callbacks_() const { return this->data_callbacks_.size() > 0; }
|
||||
|
||||
State state_{STATE_STOPPED};
|
||||
bool mute_state_{false};
|
||||
|
||||
audio::AudioStreamInfo audio_stream_info_;
|
||||
|
||||
CallbackManager<void(const std::vector<uint8_t> &)> data_callbacks_{};
|
||||
// Make private when the deprecated call(data) is removed in 2027.5.0
|
||||
DataCallbackManager data_callbacks_{&this->audio_stream_info_};
|
||||
};
|
||||
|
||||
} // namespace esphome::microphone
|
||||
@@ -36,8 +36,8 @@ void MicrophoneSource::stop() {
|
||||
}
|
||||
}
|
||||
|
||||
void MicrophoneSource::process_audio_(const std::vector<uint8_t> &data, std::vector<uint8_t> &filtered_data) {
|
||||
const audio::AudioStreamInfo source_info = this->mic_->get_audio_stream_info();
|
||||
void MicrophoneSource::process_audio_(std::span<const uint8_t> data, const audio::AudioStreamInfo &source_info,
|
||||
std::vector<uint8_t> &filtered_data) {
|
||||
const uint32_t total_frames = source_info.bytes_to_frames(data.size());
|
||||
const uint8_t target_bytes_per_sample = (this->bits_per_sample_ + 7) / 8;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <bitset>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::microphone {
|
||||
@@ -45,20 +46,25 @@ class MicrophoneSource final {
|
||||
/// @param channel 0-indexed channel number to enable
|
||||
void add_channel(uint8_t channel) { this->channels_.set(channel); }
|
||||
|
||||
/// Same callbacks as Microphone::add_data_callback; stream_info is the processed format.
|
||||
template<typename F> void add_data_callback(F &&data_callback) {
|
||||
this->mic_->add_data_callback([this, data_callback](const std::vector<uint8_t> &data) {
|
||||
if (this->enabled_ || this->passive_) {
|
||||
if (this->processed_samples_ == nullptr) {
|
||||
// Create vector if its unused
|
||||
this->processed_samples_ = std::make_shared<std::vector<uint8_t>>();
|
||||
}
|
||||
this->mic_->add_data_callback(
|
||||
[this, callback = as_capture_callback(std::forward<F>(data_callback))](const AudioCapture &capture) {
|
||||
if (this->enabled_ || this->passive_) {
|
||||
if (this->processed_samples_ == nullptr) {
|
||||
// Create vector if its unused
|
||||
this->processed_samples_ = std::make_shared<std::vector<uint8_t>>();
|
||||
}
|
||||
|
||||
// Take temporary ownership of samples vector to avoid deallocation before the callback finishes
|
||||
std::shared_ptr<std::vector<uint8_t>> output_samples = this->processed_samples_;
|
||||
this->process_audio_(data, *output_samples);
|
||||
data_callback(*output_samples);
|
||||
}
|
||||
});
|
||||
// Take temporary ownership of samples vector to avoid deallocation before the callback finishes
|
||||
std::shared_ptr<std::vector<uint8_t>> output_samples = this->processed_samples_;
|
||||
this->process_audio_(capture.data, capture.stream_info, *output_samples);
|
||||
callback(AudioCapture{*output_samples,
|
||||
audio::AudioStreamInfo(this->bits_per_sample_, this->channels_.count(),
|
||||
capture.stream_info.get_sample_rate()),
|
||||
capture.capture_time_us});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void set_gain_factor(int32_t gain_factor) { this->gain_factor_ = clamp<int32_t>(gain_factor, 1, MAX_GAIN_FACTOR); }
|
||||
@@ -76,7 +82,8 @@ class MicrophoneSource final {
|
||||
bool is_stopped() const { return !this->is_running(); };
|
||||
|
||||
protected:
|
||||
void process_audio_(const std::vector<uint8_t> &data, std::vector<uint8_t> &filtered_data);
|
||||
void process_audio_(std::span<const uint8_t> data, const audio::AudioStreamInfo &source_info,
|
||||
std::vector<uint8_t> &filtered_data);
|
||||
|
||||
std::shared_ptr<std::vector<uint8_t>> processed_samples_;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ void ResamplerMicrophone::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
this->source_->add_data_callback([this](const std::vector<uint8_t> &data) { this->process_audio_(data); });
|
||||
this->source_->add_data_callback([this](const microphone::AudioCapture &capture) { this->process_audio_(capture); });
|
||||
|
||||
this->disable_loop();
|
||||
}
|
||||
@@ -127,8 +127,8 @@ bool ResamplerMicrophone::init_resampler_(const audio::AudioStreamInfo &input_st
|
||||
return true;
|
||||
}
|
||||
|
||||
void ResamplerMicrophone::process_audio_(const std::vector<uint8_t> &data) {
|
||||
const audio::AudioStreamInfo input_stream_info = this->source_->get_audio_stream_info();
|
||||
void ResamplerMicrophone::process_audio_(const microphone::AudioCapture &capture) {
|
||||
const audio::AudioStreamInfo &input_stream_info = capture.stream_info;
|
||||
if (input_stream_info != this->input_stream_info_) {
|
||||
this->init_resampler_(input_stream_info);
|
||||
}
|
||||
@@ -137,7 +137,7 @@ void ResamplerMicrophone::process_audio_(const std::vector<uint8_t> &data) {
|
||||
}
|
||||
|
||||
if (this->resampler_ == nullptr) {
|
||||
this->data_callbacks_.call(data);
|
||||
this->publish_audio_(capture.data, input_stream_info, capture.capture_time_us);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -146,8 +146,8 @@ void ResamplerMicrophone::process_audio_(const std::vector<uint8_t> &data) {
|
||||
// Both limits match the sizes the resampler's internal buffers were allocated with in init_resampler_
|
||||
const uint32_t max_output_frames = this->audio_stream_info_.ms_to_frames(BUFFER_DURATION_MS);
|
||||
|
||||
const uint8_t *input = data.data();
|
||||
uint32_t input_frames = input_stream_info.bytes_to_frames(data.size());
|
||||
const uint8_t *input = capture.data.data();
|
||||
uint32_t input_frames = input_stream_info.bytes_to_frames(capture.data.size());
|
||||
while (input_frames > 0) {
|
||||
// Stays within the reserved capacity, so this never reallocates
|
||||
this->output_buffer_.resize(this->audio_stream_info_.frames_to_bytes(max_output_frames));
|
||||
@@ -162,7 +162,7 @@ void ResamplerMicrophone::process_audio_(const std::vector<uint8_t> &data) {
|
||||
|
||||
if (results.frames_generated > 0) {
|
||||
this->output_buffer_.resize(this->audio_stream_info_.frames_to_bytes(results.frames_generated));
|
||||
this->data_callbacks_.call(this->output_buffer_);
|
||||
this->publish_audio_(this->output_buffer_, this->audio_stream_info_);
|
||||
} else if (results.frames_used == 0) {
|
||||
break; // No progress; drop the rest of the chunk instead of spinning
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class ResamplerMicrophone final : public Component, public microphone::Microphon
|
||||
bool init_resampler_(const audio::AudioStreamInfo &input_stream_info);
|
||||
|
||||
/// @brief Resamples a chunk of source audio and passes it to the data callbacks. Source microphone task only.
|
||||
void process_audio_(const std::vector<uint8_t> &data);
|
||||
void process_audio_(const microphone::AudioCapture &capture);
|
||||
|
||||
microphone::MicrophoneSource *source_;
|
||||
std::unique_ptr<esp_audio_libs::resampler::Resampler> resampler_;
|
||||
|
||||
@@ -28,10 +28,10 @@ void SoundLevelComponent::dump_config() {
|
||||
}
|
||||
|
||||
void SoundLevelComponent::setup() {
|
||||
this->microphone_source_->add_data_callback([this](const std::vector<uint8_t> &data) {
|
||||
this->microphone_source_->add_data_callback([this](const microphone::AudioCapture &capture) {
|
||||
std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = this->ring_buffer_.lock();
|
||||
if (temp_ring_buffer != nullptr) {
|
||||
temp_ring_buffer->write((void *) data.data(), data.size());
|
||||
temp_ring_buffer->write((void *) capture.data.data(), capture.data.size());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ void USBAudioMicrophone::mic_task_loop_() {
|
||||
|
||||
if (err == ESP_OK && bytes_read > 0) {
|
||||
this->frame_.resize(bytes_read);
|
||||
this->data_callbacks_.call(this->frame_);
|
||||
this->publish_audio_(this->frame_, this->audio_stream_info_);
|
||||
} else if (err == ESP_ERR_TIMEOUT) {
|
||||
// The driver already waited READ_TIMEOUT_MS for data; read again.
|
||||
} else if (err == ESP_ERR_INVALID_STATE) {
|
||||
|
||||
@@ -40,19 +40,19 @@ static const uint32_t AUDIO_CHANNEL_STALL_TIMEOUT_MS = 2000;
|
||||
VoiceAssistant::VoiceAssistant() { global_voice_assistant = this; }
|
||||
|
||||
void VoiceAssistant::setup() {
|
||||
this->mic_source_->add_data_callback([this](const std::vector<uint8_t> &data) {
|
||||
this->mic_source_->add_data_callback([this](const microphone::AudioCapture &capture) {
|
||||
std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = this->ring_buffer_.lock();
|
||||
if (temp_ring_buffer != nullptr) {
|
||||
temp_ring_buffer->write((void *) data.data(), data.size());
|
||||
temp_ring_buffer->write((void *) capture.data.data(), capture.data.size());
|
||||
}
|
||||
});
|
||||
|
||||
// Second microphone channel
|
||||
if (this->mic_source2_ != nullptr) {
|
||||
this->mic_source2_->add_data_callback([this](const std::vector<uint8_t> &data) {
|
||||
this->mic_source2_->add_data_callback([this](const microphone::AudioCapture &capture) {
|
||||
std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = this->ring_buffer2_.lock();
|
||||
if (temp_ring_buffer != nullptr) {
|
||||
temp_ring_buffer->write((void *) data.data(), data.size());
|
||||
temp_ring_buffer->write((void *) capture.data.data(), capture.data.size());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
|
||||
|
||||
def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
# MicrophoneSource needs esp-audio-libs, which only builds for the ESP32
|
||||
manifest.resources = [
|
||||
resource
|
||||
for resource in manifest.resources
|
||||
if resource.resource != "microphone_source.cpp"
|
||||
]
|
||||
@@ -0,0 +1,118 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
// The deprecation warning for vector callbacks is reported inside microphone.h. Remove in 2027.5.0.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#include "esphome/components/microphone/automation.h"
|
||||
#include "esphome/components/microphone/microphone.h"
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::microphone::testing {
|
||||
|
||||
class TestMicrophone : public Microphone {
|
||||
public:
|
||||
void start() override {}
|
||||
void stop() override {}
|
||||
|
||||
void publish(std::span<const uint8_t> data, const audio::AudioStreamInfo &stream_info, int64_t capture_time_us) {
|
||||
this->publish_audio_(data, stream_info, capture_time_us);
|
||||
}
|
||||
bool has_callbacks() const { return this->has_data_callbacks_(); }
|
||||
|
||||
// Remove in 2027.5.0 with the deprecated DataCallbackManager::call(data)
|
||||
void set_stream_info(const audio::AudioStreamInfo &stream_info) { this->audio_stream_info_ = stream_info; }
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
void publish_deprecated(const std::vector<uint8_t> &data) { this->data_callbacks_.call(data); }
|
||||
#pragma GCC diagnostic pop
|
||||
};
|
||||
|
||||
class RecordAction : public Action<const std::vector<uint8_t> &> {
|
||||
public:
|
||||
explicit RecordAction(std::vector<uint8_t> *out) : out_(out) {}
|
||||
|
||||
protected:
|
||||
void play(const std::vector<uint8_t> &x) override { *this->out_ = x; }
|
||||
std::vector<uint8_t> *out_;
|
||||
};
|
||||
|
||||
static const audio::AudioStreamInfo STEREO_48K(16, 2, 48000);
|
||||
static const std::vector<uint8_t> SAMPLES{1, 2, 3, 4, 5, 6, 7, 8};
|
||||
|
||||
TEST(MicrophoneDataCallbacks, CallbackGetsDataFormatAndCaptureTime) {
|
||||
TestMicrophone mic;
|
||||
EXPECT_FALSE(mic.has_callbacks());
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
audio::AudioStreamInfo stream_info;
|
||||
int64_t capture_time_us = 0;
|
||||
mic.add_data_callback([&](const AudioCapture &capture) {
|
||||
data.assign(capture.data.begin(), capture.data.end());
|
||||
stream_info = capture.stream_info;
|
||||
capture_time_us = capture.capture_time_us;
|
||||
});
|
||||
EXPECT_TRUE(mic.has_callbacks());
|
||||
|
||||
mic.publish(SAMPLES, STEREO_48K, 123456);
|
||||
EXPECT_EQ(data, SAMPLES);
|
||||
EXPECT_EQ(stream_info, STEREO_48K);
|
||||
EXPECT_EQ(capture_time_us, 123456);
|
||||
}
|
||||
|
||||
TEST(MicrophoneDataCallbacks, MutedCallbackGetsZeros) {
|
||||
TestMicrophone mic;
|
||||
std::vector<uint8_t> data;
|
||||
mic.add_data_callback([&](const AudioCapture &capture) { data.assign(capture.data.begin(), capture.data.end()); });
|
||||
|
||||
mic.set_mute_state(true);
|
||||
mic.publish(SAMPLES, STEREO_48K, 0);
|
||||
EXPECT_EQ(data, std::vector<uint8_t>(SAMPLES.size(), 0));
|
||||
}
|
||||
|
||||
TEST(MicrophoneDataCallbacks, DataTriggerPassesACopy) {
|
||||
TestMicrophone mic;
|
||||
DataTrigger trigger(&mic);
|
||||
std::vector<uint8_t> data;
|
||||
Automation<const std::vector<uint8_t> &> automation(&trigger);
|
||||
RecordAction record(&data);
|
||||
automation.add_action(&record);
|
||||
|
||||
std::vector<uint8_t> buffer = SAMPLES;
|
||||
mic.publish(buffer, STEREO_48K, 0);
|
||||
EXPECT_EQ(data, SAMPLES);
|
||||
}
|
||||
|
||||
// Remove in 2027.5.0 with the deprecated vector callbacks; nothing else in the tree compiles that adapter
|
||||
TEST(MicrophoneDataCallbacks, DeprecatedVectorCallbackGetsData) {
|
||||
TestMicrophone mic;
|
||||
std::vector<uint8_t> data;
|
||||
mic.add_data_callback([&](const std::vector<uint8_t> &x) { data = x; });
|
||||
|
||||
std::vector<uint8_t> buffer = SAMPLES;
|
||||
mic.publish(buffer, STEREO_48K, 0);
|
||||
EXPECT_EQ(data, SAMPLES);
|
||||
}
|
||||
|
||||
// Remove in 2027.5.0 with the deprecated DataCallbackManager::call(data)
|
||||
TEST(MicrophoneDataCallbacks, DeprecatedCallUsesMicrophoneFormat) {
|
||||
TestMicrophone mic;
|
||||
mic.set_stream_info(STEREO_48K);
|
||||
std::vector<uint8_t> data;
|
||||
audio::AudioStreamInfo stream_info;
|
||||
int64_t capture_time_us = -1;
|
||||
mic.add_data_callback([&](const AudioCapture &capture) {
|
||||
data.assign(capture.data.begin(), capture.data.end());
|
||||
stream_info = capture.stream_info;
|
||||
capture_time_us = capture.capture_time_us;
|
||||
});
|
||||
|
||||
mic.publish_deprecated(SAMPLES);
|
||||
EXPECT_EQ(data, SAMPLES);
|
||||
EXPECT_EQ(stream_info, STEREO_48K);
|
||||
EXPECT_EQ(capture_time_us, 0);
|
||||
}
|
||||
|
||||
} // namespace esphome::microphone::testing
|
||||
Reference in new issue
Block a user