6#include <condition_variable>
11#include <rclcpp/rclcpp.hpp>
12#include <std_msgs/msg/int16_multi_array.hpp>
57 void initialize(
const rclcpp::Node::SharedPtr& node)
override
60 node->declare_parameter(
"driver.audio.MicrophoneAudioDriver.name",
"MicrophoneAudioDriver");
61 node->declare_parameter(
"driver.audio.MicrophoneAudioDriver.device_id", -1);
62 node->declare_parameter(
"driver.audio.MicrophoneAudioDriver.device_name",
"");
63 node->declare_parameter(
"driver.audio.MicrophoneAudioDriver.publish",
false);
64 node->declare_parameter(
"driver.audio.MicrophoneAudioDriver.chunk_size", 256);
65 node->declare_parameter(
"driver.audio.MicrophoneAudioDriver.sample_rate", 44100);
66 node->declare_parameter(
"driver.audio.MicrophoneAudioDriver.channels", 1);
67 node->declare_parameter(
"driver.audio.MicrophoneAudioDriver.capture_buffer_window", 10);
68 node->declare_parameter(
"driver.audio.MicrophoneAudioDriver.timeout_sec", -1);
70 name_ = node->get_parameter(
"driver.audio.MicrophoneAudioDriver.name").as_string();
71 device_id_ = node->get_parameter(
"driver.audio.MicrophoneAudioDriver.device_id").as_int();
72 device_name_ = node->get_parameter(
"driver.audio.MicrophoneAudioDriver.device_name").as_string();
73 chunk_size_ = node->get_parameter(
"driver.audio.MicrophoneAudioDriver.chunk_size").as_int();
74 sample_rate_ = node->get_parameter(
"driver.audio.MicrophoneAudioDriver.sample_rate").as_int();
75 channels_ = node->get_parameter(
"driver.audio.MicrophoneAudioDriver.channels").as_int();
76 capture_buffer_window_ = node->get_parameter(
"driver.audio.MicrophoneAudioDriver.capture_buffer_window").as_int();
77 timeout_sec_ = node->get_parameter(
"driver.audio.MicrophoneAudioDriver.timeout_sec").as_int();
83 err = Pa_Initialize();
86 RCLCPP_ERROR(
node_->get_logger(),
"PortAudio initialization failed: %s", Pa_GetErrorText(
err));
90 const int device_count = Pa_GetDeviceCount();
93 RCLCPP_ERROR(
node_->get_logger(),
"Pa_GetDeviceCount failed: %s", Pa_GetErrorText(device_count));
105 RCLCPP_INFO(
node_->get_logger(),
"Resolved microphone device_name '%s' to device_id %d.",
device_name_.c_str(),
110 RCLCPP_WARN(
node_->get_logger(),
111 "Failed to map microphone device_name '%s' to an ID: %s. Falling back to default input device.",
113 resolved_device_id = Pa_GetDefaultInputDevice();
116 else if (resolved_device_id < 0 || resolved_device_id >= device_count)
118 resolved_device_id = Pa_GetDefaultInputDevice();
121 if (resolved_device_id < 0 || resolved_device_id >= device_count)
123 for (
int i = 0; i < device_count; ++i)
125 const PaDeviceInfo* info = Pa_GetDeviceInfo(i);
126 if (info && info->maxInputChannels > 0)
128 resolved_device_id = i;
129 RCLCPP_WARN(
node_->get_logger(),
"Falling back to first available microphone device_id %d ('%s').",
130 resolved_device_id, info->name);
136 const PaDeviceInfo* device_info = Pa_GetDeviceInfo(resolved_device_id);
140 std::to_string(resolved_device_id));
144 RCLCPP_WARN(
node_->get_logger(),
"Microphone device id %d was replaced by %d (%s).",
device_id_,
145 resolved_device_id, device_info->name);
148 if (device_info->maxInputChannels <= 0)
151 "') has no input channels");
158 RCLCPP_INFO(
node_->get_logger(),
"Assigned driver name: %s",
name_.c_str());
159 RCLCPP_INFO(
node_->get_logger(),
"Assigned driver device_id: %d",
device_id_);
161 RCLCPP_INFO(
node_->get_logger(),
"Assigned driver chunk_size: %lu",
chunk_size_);
162 RCLCPP_INFO(
node_->get_logger(),
"Assigned driver sample_rate: %d",
sample_rate_);
163 RCLCPP_INFO(
node_->get_logger(),
"Assigned driver channels: %d",
channels_);
165 RCLCPP_INFO(
node_->get_logger(),
"Assigned driver timeout_sec: %d",
timeout_sec_);
166 RCLCPP_INFO(
node_->get_logger(),
"Assigned driver buffer size: %d",
buffer_size_);
170 PaStreamParameters inputParams;
173 inputParams.sampleFormat = paInt16;
174 inputParams.suggestedLatency = device_info->defaultLowInputLatency;
175 inputParams.hostApiSpecificStreamInfo =
nullptr;
183 if (
err == paInvalidSampleRate)
185 const PaDeviceInfo* info = Pa_GetDeviceInfo(
device_id_);
186 const int fallback_rate = info ?
static_cast<int>(std::lround(info->defaultSampleRate)) :
sample_rate_;
187 RCLCPP_WARN(
node_->get_logger(),
188 "Requested mic sample_rate (%d) not supported by device '%s'. Falling back to %d and resampling.",
189 sample_rate_, (info && info->name) ? info->name :
"unknown", fallback_rate);
196 if (
err != paNoError)
198 RCLCPP_ERROR(
node_->get_logger(),
"Failed to open microphone stream: %s", Pa_GetErrorText(
err));
209 RCLCPP_INFO(
node_->get_logger(),
"Assigned driver final buffer size: %d samples",
buffer_size_);
214 if (
err != paNoError)
217 RCLCPP_ERROR(
node_->get_logger(),
"Failed to start microphone stream: %s", Pa_GetErrorText(
err));
221 if (!Pa_IsStreamActive(
stream_))
224 RCLCPP_ERROR(
node_->get_logger(),
"Stream is not active after starting.");
229 RCLCPP_INFO(
node_->get_logger(),
"starting callback-driven audio capture...");
235 RCLCPP_INFO(
node_->get_logger(),
"Initialized");
253 RCLCPP_INFO(
node_->get_logger(),
"stopped.");
276 const size_t chunk_samples =
static_cast<size_t>(
chunk_size_) *
static_cast<size_t>(std::max(1,
channels_));
278 const auto has_enough_audio = [
this, chunk_samples] {
303 std::vector<int16_t> captured(samples_to_copy);
304 if (samples_to_copy > 0)
307 data.
chunk_count =
static_cast<int>(samples_to_copy / chunk_samples);
316 std::max<int>(1,
static_cast<int>(data.
samples.size() /
static_cast<size_t>(std::max(1,
channels_))));
321 data.
samples = std::move(captured);
324 std::max<int>(1,
static_cast<int>(data.
samples.size() /
static_cast<size_t>(std::max(1,
channels_))));
338 RCLCPP_INFO(
node_->get_logger(),
"Testing started. Please speak into the microphone. waiting 5 seconds...");
342 unsigned long frames_captured = 0;
347 while (frames_captured < frames_to_capture)
353 frames_captured += (data.chunk_size * data.chunk_count);
355 accumilated_samples.
samples.insert(accumilated_samples.
samples.end(), data.samples.begin(), data.samples.end());
356 accumilated_samples.
sample_rate = data.sample_rate;
357 accumilated_samples.
channels = data.channels;
358 accumilated_samples.
chunk_size = data.chunk_size;
359 accumilated_samples.
chunk_count += data.chunk_count;
361 RCLCPP_INFO(
node_->get_logger(),
"Captured %lu/%lu frames of audio data.", frames_captured, frames_to_capture);
365 RCLCPP_ERROR(
node_->get_logger(),
"Error during test: %s", e.
what());
366 std::this_thread::sleep_for(std::chrono::milliseconds(100));
375 long double sum_squares = 0.0;
376 for (
const auto sample : accumilated_samples.
samples)
378 max_abs = std::max(max_abs, std::abs(
static_cast<int>(sample)));
379 sum_squares +=
static_cast<long double>(sample) *
static_cast<long double>(sample);
381 const double rms = accumilated_samples.
samples.empty() ?
383 std::sqrt(
static_cast<double>(sum_squares / accumilated_samples.
samples.size()));
384 RCLCPP_INFO(
node_->get_logger(),
"Microphone test signal stats: samples=%zu max_abs=%d rms=%.2f",
385 accumilated_samples.
samples.size(), max_abs, rms);
388 RCLCPP_WARN(
node_->get_logger(),
"Microphone test appears nearly silent. Check capture device_id, input source, "
389 "gain, mute state, and channel count.");
395 RCLCPP_INFO(
node_->get_logger(),
"Audio data written to file: test/mic_test.wav");
397 RCLCPP_INFO(
node_->get_logger(),
"Test completed.");
401 static int pa_input_callback(
const void* input_buffer,
void* output_buffer,
unsigned long frames_per_buffer,
402 const PaStreamCallbackTimeInfo* time_info, PaStreamCallbackFlags status_flags,
416 PaStreamCallbackFlags status_flags)
421 if (status_flags & paInputOverflow)
424 if (!input_buffer || frames_per_buffer == 0)
427 const auto* samples =
static_cast<const int16_t*
>(input_buffer);
428 const size_t sample_count =
static_cast<size_t>(frames_per_buffer) *
static_cast<size_t>(std::max(1,
channels_));
438 int input_rate,
int output_rate)
440 if (input_rate <= 0 || output_rate <= 0)
443 channels = std::max(1, channels);
444 const size_t input_frames = input.size() /
static_cast<size_t>(channels);
446 if (input_frames == 0 || input_rate == output_rate)
449 const double ratio =
static_cast<double>(output_rate) /
static_cast<double>(input_rate);
450 const size_t output_frames = std::max<size_t>(1,
static_cast<size_t>(std::llround(input_frames * ratio)));
451 std::vector<int16_t> output(output_frames *
static_cast<size_t>(channels));
453 for (
int ch = 0; ch < channels; ++ch)
455 for (
size_t out_i = 0; out_i < output_frames; ++out_i)
457 const double src_pos =
static_cast<double>(out_i) / ratio;
458 const size_t i0 =
static_cast<size_t>(std::floor(src_pos));
459 const size_t i1 = std::min(i0 + 1, input_frames - 1);
460 const double frac = src_pos -
static_cast<double>(i0);
462 const int16_t s0 = input[i0 *
static_cast<size_t>(channels) +
static_cast<size_t>(ch)];
463 const int16_t s1 = input[i1 *
static_cast<size_t>(channels) +
static_cast<size_t>(ch)];
465 const double mixed = (1.0 - frac) *
static_cast<double>(s0) + frac *
static_cast<double>(s1);
466 const long rounded = std::lround(mixed);
467 const long clamped = std::clamp<long>(rounded, -32768, 32767);
469 output[out_i *
static_cast<size_t>(channels) +
static_cast<size_t>(ch)] =
static_cast<int16_t
>(clamped);
479 [
this](diagnostic_updater::DiagnosticStatusWrapper& status) {
produce_diagnostics(status); });
488 size_t buffered_samples = 0;
494 const size_t buffered_frames = buffered_samples /
static_cast<size_t>(std::max(1,
channels_));
497 status.summary(diagnostic_msgs::msg::DiagnosticStatus::ERROR,
"Microphone stream inactive");
498 else if (overflow_count > 0 || drop_count > 0)
499 status.summary(diagnostic_msgs::msg::DiagnosticStatus::WARN,
"Microphone callback overruns observed");
501 status.summary(diagnostic_msgs::msg::DiagnosticStatus::OK,
"Microphone capture healthy");
508 status.add(
"chunk_size",
static_cast<int>(
chunk_size_));
511 status.add(
"buffered_samples", buffered_samples);
512 status.add(
"buffered_frames", buffered_frames);
513 status.add(
"input_overflow_count", overflow_count);
514 status.add(
"dropped_callback_chunks", drop_count);
525 std::copy(samples + (sample_count - keep), samples + sample_count,
audio_buffer_.begin());
533 if (sample_count > free_space)
535 const size_t overflow = sample_count - free_space;
543 const size_t remaining = sample_count - first_copy;
545 std::copy(samples + first_copy, samples + sample_count,
audio_buffer_.begin());
553 if (!destination || sample_count == 0)
560 const size_t remaining = sample_count - first_copy;
563 destination + first_copy);
Definition audio_source_driver.hpp:10
void check_directory(std::string folder_name)
Check if the test directory exists, create it if not.
Definition driver_base.hpp:68
std::mutex buffer_mutex_
Mutex to protect access to the driver data.
Definition driver_base.hpp:149
std::atomic< bool > is_running_
Flag to indicate if the driver thread is running.
Definition driver_base.hpp:161
std::string name_
Name of the driver.
Definition driver_base.hpp:143
std::condition_variable buffer_cv_
Condition variable to notify when new data is available.
Definition driver_base.hpp:155
rclcpp::Node::SharedPtr node_
ROS node for the driver.
Definition driver_base.hpp:138
void initialize_base(const rclcpp::Node::SharedPtr &node)
Initializer base driver in place of constructor due to plugin semantics.
Definition driver_base.hpp:95
void disable_diagnostics()
Definition driver_base.hpp:129
bool diagnostics_enabled() const
Definition driver_base.hpp:105
void enable_diagnostics(const std::string &hardware_id, const std::string &task_name, std::function< void(diagnostic_updater::DiagnosticStatusWrapper &)> task, std::chrono::milliseconds period=std::chrono::seconds(1))
Definition driver_base.hpp:110
MicrophoneAudioDriver class for handling audio input from a microphone.
Definition microphone_audio_driver.hpp:28
int capture_buffer_window_
Definition microphone_audio_driver.hpp:578
int capture_sample_rate_
Definition microphone_audio_driver.hpp:576
PaStream * stream_
Definition microphone_audio_driver.hpp:569
void write_samples_locked(const int16_t *samples, size_t sample_count)
Definition microphone_audio_driver.hpp:517
static std::vector< int16_t > resample_linear_interleaved(const std::vector< int16_t > &input, int channels, int input_rate, int output_rate)
Definition microphone_audio_driver.hpp:437
void deinitialize() override
Stop driver streaming. This function stops the audio stream and closes it. It also terminates the Por...
Definition microphone_audio_driver.hpp:242
void setup_diagnostics()
Definition microphone_audio_driver.hpp:476
std::string device_name_
Definition microphone_audio_driver.hpp:573
void produce_diagnostics(diagnostic_updater::DiagnosticStatusWrapper &status)
Definition microphone_audio_driver.hpp:482
size_t read_index_
Definition microphone_audio_driver.hpp:581
std::atomic< uint64_t > dropped_callback_chunks_
Definition microphone_audio_driver.hpp:585
unsigned long chunk_size_
Definition microphone_audio_driver.hpp:574
size_t buffered_sample_count_
Definition microphone_audio_driver.hpp:583
audio_data readChunk() override
Get latest audio data from the driver as a stream. This function reads audio data from the microphone...
Definition microphone_audio_driver.hpp:271
int buffer_size_
Definition microphone_audio_driver.hpp:580
std::vector< int16_t > audio_buffer_
Definition microphone_audio_driver.hpp:571
size_t write_index_
Definition microphone_audio_driver.hpp:582
~MicrophoneAudioDriver() override
Start the audio stream.
Definition microphone_audio_driver.hpp:44
void test() override
Test the driver.
Definition microphone_audio_driver.hpp:336
int capture_input_buffer(const void *input_buffer, unsigned long frames_per_buffer, PaStreamCallbackFlags status_flags)
Definition microphone_audio_driver.hpp:415
PaError err
Definition microphone_audio_driver.hpp:570
void read_samples_locked(int16_t *destination, size_t sample_count)
Definition microphone_audio_driver.hpp:551
void initialize(const rclcpp::Node::SharedPtr &node) override
Initialize the driver.
Definition microphone_audio_driver.hpp:57
MicrophoneAudioDriver()
Constructor for MicrophoneAudioDriver.
Definition microphone_audio_driver.hpp:35
static int pa_input_callback(const void *input_buffer, void *output_buffer, unsigned long frames_per_buffer, const PaStreamCallbackTimeInfo *time_info, PaStreamCallbackFlags status_flags, void *user_data)
Definition microphone_audio_driver.hpp:401
int channels_
Definition microphone_audio_driver.hpp:577
int sample_rate_
Definition microphone_audio_driver.hpp:575
int device_id_
Definition microphone_audio_driver.hpp:572
int timeout_sec_
Definition microphone_audio_driver.hpp:579
std::atomic< uint64_t > input_overflow_count_
Definition microphone_audio_driver.hpp:584
Definition audio_buffer.hpp:16
int getDeviceIdByName(const std::string &target_name)
Definition utils.hpp:63
void writeWavFile(const std::string &filename, const audio_data &data)
Write audio data to a WAV file.
Definition wav.hpp:63
std::string describePortAudioDevice(int device_id)
Definition utils.hpp:28
void logPortAudioDevices(const LoggerT &logger)
Definition utils.hpp:46
Struct to hold audio data.
Definition structs.hpp:16
int chunk_count
Number of chunks in the audio data.
Definition structs.hpp:21
std::vector< int16_t > samples
Audio samples.
Definition structs.hpp:17
int sample_rate
Sample rate in Hz.
Definition structs.hpp:18
int channels
Number of audio channels.
Definition structs.hpp:19
int chunk_size
Size of each audio chunk in samples.
Definition structs.hpp:20
Base class for driver exceptions.
Definition exceptions.hpp:14
virtual const char * what() const noexcept override
Definition exceptions.hpp:21