FP Perception 0.1.2
Loading...
Searching...
No Matches
speaker_audio_driver.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <portaudio.h>
4#include <vector>
5#include <deque>
6#include <mutex>
7#include <condition_variable>
8#include <map>
9#include <fstream>
10#include <algorithm>
11#include <cmath>
12#include <atomic>
13#include <cstdint>
18
19namespace fp_perception
20{
21
29{
30public:
37 {
38 }
39
46 {
48 Pa_Terminate();
49 }
50
58 void initialize(const rclcpp::Node::SharedPtr& node) override
59 {
60 // Configure parameters for the nodevision
61 node->declare_parameter("driver.audio.SpeakerAudioDriver.name", "SpeakerAudioDriver");
62 node->declare_parameter("driver.audio.SpeakerAudioDriver.device_id", -1);
63 node->declare_parameter("driver.audio.SpeakerAudioDriver.device_name", "");
64 node->declare_parameter("driver.audio.SpeakerAudioDriver.sample_rate", 44100); // default sample rate
65 node->declare_parameter("driver.audio.SpeakerAudioDriver.channels", 1); // default number of channels
66 node->declare_parameter("driver.audio.SpeakerAudioDriver.test_file_path",
67 "test/mic_test.wav"); // default device ID
68
69 // Load parameters from the node
70 name_ = node->get_parameter("driver.audio.SpeakerAudioDriver.name").as_string();
71 device_id_ = node->get_parameter("driver.audio.SpeakerAudioDriver.device_id").as_int();
72 device_name_ = node->get_parameter("driver.audio.SpeakerAudioDriver.device_name").as_string();
73 sample_rate_ = node->get_parameter("driver.audio.SpeakerAudioDriver.sample_rate").as_int();
74 channels_ = node->get_parameter("driver.audio.SpeakerAudioDriver.channels").as_int();
75 test_file_path_ = node->get_parameter("driver.audio.SpeakerAudioDriver.test_file_path").as_string();
76
77 // Initialize the base driver
78 initialize_base(node);
79
80 // Initialize PortAudio
81 err = Pa_Initialize();
82 if (err != paNoError)
83 {
84 RCLCPP_ERROR(node_->get_logger(), "PortAudio initialization failed: %s", Pa_GetErrorText(err));
85 throw fp_perception_exception("PortAudio initialization failed: " + std::string(Pa_GetErrorText(err)));
86 }
87
88 const int device_count = Pa_GetDeviceCount();
89 if (device_count < 0)
90 {
91 RCLCPP_ERROR(node_->get_logger(), "Pa_GetDeviceCount failed: %s", Pa_GetErrorText(device_count));
92 throw fp_perception_exception("PortAudio failed to enumerate devices");
93 }
94
95 logPortAudioDevices(node_->get_logger());
96
97 int resolved_device_id = device_id_;
98 if (!device_name_.empty())
99 {
100 try
101 {
102 resolved_device_id = getDeviceIdByName(device_name_);
103 RCLCPP_INFO(node_->get_logger(), "Resolved speaker device_name '%s' to device_id %d.", device_name_.c_str(),
104 resolved_device_id);
105 }
106 catch (const fp_perception_exception& e)
107 {
108 RCLCPP_WARN(node_->get_logger(),
109 "Failed to map speaker device_name '%s' to an ID: %s. Falling back to default output device.",
110 device_name_.c_str(), e.what());
111 resolved_device_id = Pa_GetDefaultOutputDevice();
112 }
113 }
114 else if (resolved_device_id < 0 || resolved_device_id >= device_count)
115 {
116 resolved_device_id = Pa_GetDefaultOutputDevice();
117 }
118
119 if (resolved_device_id < 0 || resolved_device_id >= device_count)
120 {
121 for (int i = 0; i < device_count; ++i)
122 {
123 const PaDeviceInfo* info = Pa_GetDeviceInfo(i);
124 if (info && info->maxOutputChannels > 0)
125 {
126 resolved_device_id = i;
127 RCLCPP_WARN(node_->get_logger(), "Falling back to first available speaker device_id %d ('%s').",
128 resolved_device_id, info->name);
129 break;
130 }
131 }
132 }
133
134 const PaDeviceInfo* device_info = Pa_GetDeviceInfo(resolved_device_id);
135 if (!device_info)
136 {
137 throw fp_perception_exception("PortAudio returned null device info for device_id " +
138 std::to_string(resolved_device_id));
139 }
140 if (resolved_device_id != device_id_)
141 {
142 RCLCPP_WARN(node_->get_logger(), "Speaker device id %d was replaced by %d (%s).", device_id_, resolved_device_id,
143 device_info->name);
144 device_id_ = resolved_device_id;
145 }
146 if (device_info->maxOutputChannels <= 0)
147 {
148 throw fp_perception_exception("Selected device_id " + std::to_string(device_id_) + " ('" + device_info->name +
149 "') has no output channels");
150 }
151
152 // Publish about the assigned driver parameters
153 RCLCPP_INFO(node_->get_logger(), "Assigned driver name: %s", name_.c_str());
154 RCLCPP_INFO(node_->get_logger(), "Assigned driver device_id: %d", device_id_);
155 RCLCPP_INFO(node_->get_logger(), "Assigned driver device: %s", describePortAudioDevice(device_id_).c_str());
156 RCLCPP_INFO(node_->get_logger(), "Assigned driver sample_rate: %d", sample_rate_);
157 RCLCPP_INFO(node_->get_logger(), "Assigned driver channels: %d", channels_);
158
161
162 RCLCPP_INFO(node_->get_logger(), "Initialized");
163 }
164
169 void deinitialize() override
170 {
172
173 for (auto& pair : stream_dict_)
174 {
175 if (!pair.second)
176 continue;
177
178 err = Pa_StopStream(pair.second);
179 if (err != paNoError)
180 {
181 RCLCPP_ERROR(node_->get_logger(), "Failed to stop speaker stream: %s", Pa_GetErrorText(err));
182 }
183
184 err = Pa_CloseStream(pair.second);
185 if (err != paNoError)
186 {
187 RCLCPP_ERROR(node_->get_logger(), "Failed to close speaker stream: %s", Pa_GetErrorText(err));
188 }
189 pair.second = nullptr;
190 }
191
192 stream_dict_.clear();
193
194 RCLCPP_INFO(node_->get_logger(), "stopped.");
195 }
196
203 void play(const audio_data& input_data) override
204 {
205 play_internal(input_data, true);
206 }
207
208 void enqueuePlayback(const audio_data& input_data) override
209 {
210 play_internal(input_data, false);
211 }
212
213 void stopPlayback() override
214 {
215 std::lock_guard<std::mutex> lock(playback_mutex_);
216 playback_queue_.clear();
217 playback_cv_.notify_all();
218 }
219
220 void play_internal(const audio_data& input_data, bool wait_for_drain)
221 {
222 auto data = input_data;
223
224 if (data.samples.empty())
225 {
226 RCLCPP_ERROR(node_->get_logger(), "Received empty audio data, nothing to set.");
227 throw fp_perception_exception("Received empty audio data, nothing to set.");
228 }
229
230 // Normalize incoming audio to the configured output sample rate.
231 // PortAudio/ALSA will fail to open for unsupported rates (e.g. 192kHz on some sinks).
232 // Channel count normalization is handled in write_data().
233 if (data.sample_rate != sample_rate_)
234 {
235 RCLCPP_WARN(node_->get_logger(),
236 "Incoming audio sample_rate (%d) differs from configured output sample_rate (%d). Resampling.",
237 data.sample_rate, sample_rate_);
238 data.samples = resample_linear_interleaved(data.samples, data.channels, data.sample_rate, sample_rate_);
239 data.sample_rate = sample_rate_;
240
241 const auto frames = static_cast<int>(data.samples.size() / std::max(1, data.channels));
242 data.chunk_size = std::max(1, frames);
243 data.chunk_count = 1;
244 }
245
246 // Create a unique stream key based on format, rate, and channels
247 std::string stream_key = "int16_" + std::to_string(sample_rate_) + "_" + std::to_string(channels_);
248
249 if (stream_dict_.find(stream_key) == stream_dict_.end())
250 {
251 PaStreamParameters outputParameters;
252 outputParameters.device = device_id_;
253 outputParameters.channelCount = channels_;
254 outputParameters.sampleFormat = paInt16;
255 outputParameters.suggestedLatency = Pa_GetDeviceInfo(device_id_)->defaultLowOutputLatency;
256 outputParameters.hostApiSpecificStreamInfo = nullptr;
257
258 RCLCPP_INFO(node_->get_logger(), "Opening speaker stream on %s", describePortAudioDevice(device_id_).c_str());
259
260 // Open a new stream for this format
261 PaStream* stream = nullptr;
262 err = Pa_OpenStream(&stream, nullptr, &outputParameters, sample_rate_, paFramesPerBufferUnspecified, paClipOff,
264
265 if (err != paNoError)
266 {
267 RCLCPP_ERROR(node_->get_logger(), "Failed to open speaker stream: %s", Pa_GetErrorText(err));
268 throw fp_perception_exception("Failed to open speaker stream: " + std::string(Pa_GetErrorText(err)));
269 }
270
271 err = Pa_StartStream(stream);
272 if (err != paNoError)
273 {
274 RCLCPP_ERROR(node_->get_logger(), "Failed to start speaker stream: %s", Pa_GetErrorText(err));
275 throw fp_perception_exception("Failed to start speaker stream: " + std::string(Pa_GetErrorText(err)));
276 }
277
278 if (!Pa_IsStreamActive(stream))
279 {
280 RCLCPP_ERROR(node_->get_logger(), "Stream is not active after starting.");
281 throw fp_perception_exception("PortAudio stream failed to activate.");
282 }
283
284 // Store the stream in the dictionary
285 stream_dict_[stream_key] = stream;
286 RCLCPP_INFO(node_->get_logger(), "Opened new speaker stream: %s", stream_key.c_str());
287 const PaStreamInfo* stream_info = Pa_GetStreamInfo(stream);
288 if (stream_info)
289 {
290 RCLCPP_INFO(node_->get_logger(), "Speaker stream info: sample_rate=%.0f output_latency=%.4f",
291 stream_info->sampleRate, stream_info->outputLatency);
292 }
293 }
294
295 // Write data to the stream
296 try
297 {
298 if (wait_for_drain)
299 {
300 RCLCPP_INFO(node_->get_logger(), "Queueing audio to speaker stream %s and waiting for playback drain.",
301 stream_key.c_str());
302 }
303 else
304 {
305 RCLCPP_INFO(node_->get_logger(), "Queueing audio to speaker stream %s without waiting for playback drain.",
306 stream_key.c_str());
307 }
308
309 queue_data(data, stream_key, wait_for_drain);
310
311 if (wait_for_drain)
312 {
313 RCLCPP_INFO(node_->get_logger(), "Speaker playback drained for stream: %s", stream_key.c_str());
314 }
315 else
316 {
317 RCLCPP_INFO(node_->get_logger(), "Audio data queued to stream asynchronously: %s", stream_key.c_str());
318 }
319 }
320 catch (const fp_perception_exception& e)
321 {
322 RCLCPP_ERROR(node_->get_logger(), "Error writing audio data to stream: %s", e.what());
323 throw;
324 }
325 }
329 void test() override
330 {
331 RCLCPP_INFO(node_->get_logger(), "Testing by playing: %s", test_file_path_.c_str());
332
333 auto filepath = check_file(test_file_path_);
334
335 try
336 {
337 auto audio_data = readWavFile(filepath.string());
338
339 RCLCPP_INFO(node_->get_logger(), "Read audio data from %s with %zu samples, %d Hz, %d channels.",
340 filepath.string().c_str(), audio_data.samples.size(), audio_data.sample_rate, audio_data.channels);
341
342 int max_abs = 0;
343 long double sum_squares = 0.0;
344 for (const auto sample : audio_data.samples)
345 {
346 max_abs = std::max(max_abs, std::abs(static_cast<int>(sample)));
347 sum_squares += static_cast<long double>(sample) * static_cast<long double>(sample);
348 }
349 const double rms =
350 audio_data.samples.empty() ? 0.0 : std::sqrt(static_cast<double>(sum_squares / audio_data.samples.size()));
351 const double duration =
352 static_cast<double>(audio_data.samples.size()) /
353 static_cast<double>(std::max(1, audio_data.sample_rate) * std::max(1, audio_data.channels));
354 RCLCPP_INFO(node_->get_logger(), "Speaker test input stats: max_abs=%d rms=%.2f estimated_duration=%.2fs",
355 max_abs, rms, duration);
356 if (max_abs < 128)
357 {
358 RCLCPP_WARN(node_->get_logger(), "Speaker test input appears nearly silent; use a known-good WAV to test "
359 "output routing.");
360 }
361
363 }
364 catch (const fp_perception_exception& e)
365 {
366 RCLCPP_ERROR(node_->get_logger(), "Error during test: %s", e.what());
367 }
368
369 RCLCPP_INFO(node_->get_logger(), "Test completed.");
370 }
371
372protected:
373 static int pa_output_callback(const void* input_buffer, void* output_buffer, unsigned long frames_per_buffer,
374 const PaStreamCallbackTimeInfo* time_info, PaStreamCallbackFlags status_flags,
375 void* user_data)
376 {
377 (void)input_buffer;
378 (void)time_info;
379 (void)status_flags;
380
381 auto* driver = static_cast<SpeakerAudioDriver*>(user_data);
382 if (!driver)
383 return paAbort;
384
385 return driver->fill_output_buffer(output_buffer, frames_per_buffer, status_flags);
386 }
387
388 int fill_output_buffer(void* output_buffer, unsigned long frames_per_buffer, PaStreamCallbackFlags status_flags)
389 {
390 auto* output = static_cast<int16_t*>(output_buffer);
391 const size_t samples_needed = static_cast<size_t>(frames_per_buffer) * static_cast<size_t>(std::max(1, channels_));
392 size_t copied = 0;
393
394 if (status_flags & paOutputUnderflow)
396
397 std::unique_lock<std::mutex> lock(playback_mutex_, std::try_to_lock);
398 if (lock.owns_lock())
399 {
400 while (copied < samples_needed && !playback_queue_.empty())
401 {
402 output[copied++] = playback_queue_.front();
403 playback_queue_.pop_front();
404 }
405
406 if (playback_queue_.empty())
407 playback_cv_.notify_all();
408 }
409 else
410 {
412 }
413
414 if (copied < samples_needed)
415 {
416 std::fill(output + copied, output + samples_needed, static_cast<int16_t>(0));
418 }
419
420 return paContinue;
421 }
422
423 static std::vector<int16_t> resample_linear_interleaved(const std::vector<int16_t>& input, int channels,
424 int input_rate, int output_rate)
425 {
426 if (input_rate <= 0 || output_rate <= 0)
427 throw fp_perception_exception("Invalid sample rate for resampling");
428
429 channels = std::max(1, channels);
430 const size_t input_frames = input.size() / static_cast<size_t>(channels);
431
432 if (input_frames == 0 || input_rate == output_rate)
433 return input;
434
435 const double ratio = static_cast<double>(output_rate) / static_cast<double>(input_rate);
436 const size_t output_frames = std::max<size_t>(1, static_cast<size_t>(std::llround(input_frames * ratio)));
437 std::vector<int16_t> output(output_frames * static_cast<size_t>(channels));
438
439 for (int ch = 0; ch < channels; ++ch)
440 {
441 for (size_t out_i = 0; out_i < output_frames; ++out_i)
442 {
443 const double src_pos = static_cast<double>(out_i) / ratio;
444 const size_t i0 = static_cast<size_t>(std::floor(src_pos));
445 const size_t i1 = std::min(i0 + 1, input_frames - 1);
446 const double frac = src_pos - static_cast<double>(i0);
447
448 const int16_t s0 = input[i0 * static_cast<size_t>(channels) + static_cast<size_t>(ch)];
449 const int16_t s1 = input[i1 * static_cast<size_t>(channels) + static_cast<size_t>(ch)];
450
451 const double mixed = (1.0 - frac) * static_cast<double>(s0) + frac * static_cast<double>(s1);
452 const long rounded = std::lround(mixed);
453 const long clamped = std::clamp<long>(rounded, -32768, 32767);
454
455 output[out_i * static_cast<size_t>(channels) + static_cast<size_t>(ch)] = static_cast<int16_t>(clamped);
456 }
457 }
458
459 return output;
460 }
461
462 void queue_data(audio_data input_data, const std::string& stream_key, bool wait_for_drain)
463 {
464 (void)stream_key;
465
466 std::vector<int16_t> data; // Buffer for the actual data to write
467
468 // Handle mono-to-stereo or stereo-to-mono conversions if necessary
469 if (input_data.channels != channels_)
470 {
471 if (input_data.channels == 1 && channels_ == 2)
472 {
473 // Mono to stereo conversion
474 data.resize(input_data.samples.size() * 2);
475 for (size_t i = 0; i < input_data.samples.size(); ++i)
476 {
477 data[2 * i] = input_data.samples[i];
478 data[2 * i + 1] = input_data.samples[i];
479 }
480 }
481 else if (input_data.channels == 2 && channels_ == 1)
482 {
483 // Stereo to mono conversion
484 data.resize(input_data.samples.size() / 2);
485 for (size_t i = 0; i < data.size(); ++i)
486 {
487 data[i] = static_cast<int16_t>((input_data.samples[2 * i] + input_data.samples[2 * i + 1]) / 2);
488 }
489 }
490 else
491 {
492 throw fp_perception_exception("Unsupported channel conversion from " + std::to_string(input_data.channels) +
493 " to " + std::to_string(channels_));
494 }
495 }
496 else
497 {
498 // No conversion needed
499 data = input_data.samples;
500 }
501
502 // Make sure chunk size is correct for frames (not samples)
503 const size_t required_samples =
504 static_cast<size_t>(std::max(1, input_data.chunk_size)) * static_cast<size_t>(std::max(1, channels_));
505
506 if (data.size() < required_samples)
507 {
508 throw fp_perception_exception("Insufficient data" + std::to_string(data.size()) + " for requested chunk size " +
509 std::to_string(required_samples));
510 }
511
512 const unsigned long frames_to_queue = static_cast<unsigned long>(data.size() / static_cast<size_t>(channels_));
513 RCLCPP_INFO(node_->get_logger(), "Queueing %lu frames (%zu samples) to speaker stream.", frames_to_queue,
514 data.size());
515
516 {
517 std::lock_guard<std::mutex> lock(playback_mutex_);
518 playback_queue_.insert(playback_queue_.end(), data.begin(), data.end());
519 }
520
521 playback_cv_.notify_all();
522
523 if (wait_for_drain)
525 }
526
528 {
529 std::unique_lock<std::mutex> lock(playback_mutex_);
530 playback_cv_.wait(lock, [this] { return playback_queue_.empty(); });
531 }
532
534 {
535 enable_diagnostics("portaudio-speaker-" + std::to_string(device_id_), name_ + " playback",
536 [this](diagnostic_updater::DiagnosticStatusWrapper& status) { produce_diagnostics(status); });
537 }
538
539 void produce_diagnostics(diagnostic_updater::DiagnosticStatusWrapper& status)
540 {
541 const auto underrun_count = underrun_count_.load();
542 const auto lock_miss_count = callback_lock_miss_count_.load();
543
544 size_t queued_samples = 0;
545 {
546 std::lock_guard<std::mutex> lock(playback_mutex_);
547 queued_samples = playback_queue_.size();
548 }
549
550 const size_t queued_frames = queued_samples / static_cast<size_t>(std::max(1, channels_));
551 bool stream_active = false;
552 for (const auto& pair : stream_dict_)
553 {
554 if (pair.second && Pa_IsStreamActive(pair.second) == 1)
555 {
556 stream_active = true;
557 break;
558 }
559 }
560
561 if (!stream_active && queued_samples > 0)
562 status.summary(diagnostic_msgs::msg::DiagnosticStatus::ERROR, "Speaker queue has data but no active stream");
563 else if (underrun_count > 0 || lock_miss_count > 0)
564 status.summary(diagnostic_msgs::msg::DiagnosticStatus::WARN, "Speaker callback underruns observed");
565 else if (stream_active)
566 status.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "Speaker playback healthy");
567 else
568 status.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "Speaker idle");
569
570 status.add("device_id", device_id_);
571 status.add("device_name", device_name_);
572 status.add("sample_rate", sample_rate_);
573 status.add("channels", channels_);
574 status.add("queued_samples", queued_samples);
575 status.add("queued_frames", queued_frames);
576 status.add("queue_drained", queued_samples == 0 ? "true" : "false");
577 status.add("callback_underrun_count", underrun_count);
578 status.add("callback_lock_miss_count", lock_miss_count);
579 }
580
582 std::string device_name_;
583 PaError err = paNoError;
584 int sample_rate_; // Default sample rate
585 int channels_; // Default number of channels
586 std::string test_file_path_;
587
588 std::map<std::string, PaStream*> stream_dict_;
589 std::deque<int16_t> playback_queue_;
590 std::mutex playback_mutex_;
591 std::condition_variable playback_cv_;
592 std::atomic<uint64_t> underrun_count_{ 0 };
593 std::atomic<uint64_t> callback_lock_miss_count_{ 0 };
594};
595
596} // namespace fp_perception
Definition audio_sink_driver.hpp:10
std::string name_
Name of the driver.
Definition driver_base.hpp:143
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
const std::filesystem::path check_file(const std::string &file_name)
Definition driver_base.hpp:78
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
SpeakerAudioDriver class for handling audio output to a speaker.
Definition speaker_audio_driver.hpp:29
std::mutex playback_mutex_
Definition speaker_audio_driver.hpp:590
void wait_for_playback_drain()
Definition speaker_audio_driver.hpp:527
std::atomic< uint64_t > callback_lock_miss_count_
Definition speaker_audio_driver.hpp:593
void deinitialize() override
Stop driver streaming. This function stops the audio stream and closes it. It also terminates the Por...
Definition speaker_audio_driver.hpp:169
static std::vector< int16_t > resample_linear_interleaved(const std::vector< int16_t > &input, int channels, int input_rate, int output_rate)
Definition speaker_audio_driver.hpp:423
std::deque< int16_t > playback_queue_
Definition speaker_audio_driver.hpp:589
std::map< std::string, PaStream * > stream_dict_
Definition speaker_audio_driver.hpp:588
void test() override
Read test/mic_test.wav and play it through the speaker.
Definition speaker_audio_driver.hpp:329
SpeakerAudioDriver()
Constructor for SpeakerAudioDriver.
Definition speaker_audio_driver.hpp:36
void stopPlayback() override
Definition speaker_audio_driver.hpp:213
void produce_diagnostics(diagnostic_updater::DiagnosticStatusWrapper &status)
Definition speaker_audio_driver.hpp:539
int channels_
Definition speaker_audio_driver.hpp:585
int sample_rate_
Definition speaker_audio_driver.hpp:584
std::string test_file_path_
Definition speaker_audio_driver.hpp:586
~SpeakerAudioDriver() override
Destructor for SpeakerAudioDriver.
Definition speaker_audio_driver.hpp:45
int device_id_
Definition speaker_audio_driver.hpp:581
void play_internal(const audio_data &input_data, bool wait_for_drain)
Definition speaker_audio_driver.hpp:220
PaError err
Definition speaker_audio_driver.hpp:583
void enqueuePlayback(const audio_data &input_data) override
Definition speaker_audio_driver.hpp:208
std::atomic< uint64_t > underrun_count_
Definition speaker_audio_driver.hpp:592
std::string device_name_
Definition speaker_audio_driver.hpp:582
void queue_data(audio_data input_data, const std::string &stream_key, bool wait_for_drain)
Definition speaker_audio_driver.hpp:462
int fill_output_buffer(void *output_buffer, unsigned long frames_per_buffer, PaStreamCallbackFlags status_flags)
Definition speaker_audio_driver.hpp:388
void setup_diagnostics()
Definition speaker_audio_driver.hpp:533
std::condition_variable playback_cv_
Definition speaker_audio_driver.hpp:591
void play(const audio_data &input_data) override
Set the latest audio data to the driver. This function sends the latest audio data to the speaker dri...
Definition speaker_audio_driver.hpp:203
void initialize(const rclcpp::Node::SharedPtr &node) override
Initialize the driver.
Definition speaker_audio_driver.hpp:58
static int pa_output_callback(const void *input_buffer, void *output_buffer, unsigned long frames_per_buffer, const PaStreamCallbackTimeInfo *time_info, PaStreamCallbackFlags status_flags, void *user_data)
Definition speaker_audio_driver.hpp:373
Definition audio_buffer.hpp:16
int getDeviceIdByName(const std::string &target_name)
Definition utils.hpp:63
std::string describePortAudioDevice(int device_id)
Definition utils.hpp:28
void logPortAudioDevices(const LoggerT &logger)
Definition utils.hpp:46
audio_data readWavFile(const std::string &filepath)
Read audio data from a WAV file.
Definition wav.hpp:77
Struct to hold audio data.
Definition structs.hpp:16
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