1 /***
2 This file is part of snapcast
3 Copyright (C) 2014-2021 Johannes Pohl
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 ***/
18
19 #include "alsa_player.hpp"
20 #include "common/aixlog.hpp"
21 #include "common/snap_exception.hpp"
22 #include "common/str_compat.hpp"
23 #include "common/utils/logging.hpp"
24 #include "common/utils/string_utils.hpp"
25
26 using namespace std::chrono_literals;
27 using namespace std;
28
29 namespace player
30 {
31
32 static constexpr std::chrono::milliseconds BUFFER_TIME = 80ms;
33 static constexpr int PERIODS = 4;
34 static constexpr int MIN_PERIODS = 3;
35
36 #define exp10(x) (exp((x)*log(10)))
37
38
39 static constexpr auto LOG_TAG = "Alsa";
40 static constexpr auto DEFAULT_MIXER = "PCM";
41
42
AlsaPlayer(boost::asio::io_context & io_context,const ClientSettings::Player & settings,std::shared_ptr<Stream> stream)43 AlsaPlayer::AlsaPlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream)
44 : Player(io_context, settings, stream), handle_(nullptr), ctl_(nullptr), mixer_(nullptr), elem_(nullptr), sd_(io_context), timer_(io_context)
45 {
46 if (settings_.mixer.mode == ClientSettings::Mixer::Mode::hardware)
47 {
48 string tmp;
49 if (settings_.mixer.parameter.empty())
50 mixer_name_ = DEFAULT_MIXER;
51 else
52 mixer_name_ = utils::string::split_left(settings_.mixer.parameter, ':', tmp);
53
54 string card;
55 // default:CARD=ALSA[,DEV=x] => default
56 mixer_device_ = utils::string::split_left(settings_.pcm_device.name, ':', card);
57 if (!card.empty())
58 {
59 auto pos = card.find("CARD=");
60 if (pos != string::npos)
61 {
62 card = card.substr(pos + 5);
63 card = utils::string::split_left(card, ',', tmp);
64 int card_idx = snd_card_get_index(card.c_str());
65 if ((card_idx >= 0) && (card_idx < 32))
66 mixer_device_ = "hw:" + std::to_string(card_idx);
67 }
68 }
69
70 LOG(DEBUG, LOG_TAG) << "Mixer: " << mixer_name_ << ", device: " << mixer_device_ << "\n";
71 }
72
73 auto params = utils::string::split_pairs(settings.parameter, ',', '=');
74 if (params.find("buffer_time") != params.end())
75 buffer_time_ = std::chrono::milliseconds(std::max(cpt::stoi(params["buffer_time"]), 10));
76 if (params.find("fragments") != params.end())
77 periods_ = std::max(cpt::stoi(params["fragments"]), 2);
78
79 LOG(INFO, LOG_TAG) << "Using " << (buffer_time_.has_value() ? "configured" : "default")
80 << " buffer_time: " << buffer_time_.value_or(BUFFER_TIME).count() / 1000 << " ms, " << (periods_.has_value() ? "configured" : "default")
81 << " fragments: " << periods_.value_or(PERIODS) << "\n";
82 }
83
84
setHardwareVolume(double volume,bool muted)85 void AlsaPlayer::setHardwareVolume(double volume, bool muted)
86 {
87 std::lock_guard<std::recursive_mutex> lock(mutex_);
88 if (elem_ == nullptr)
89 return;
90
91 last_change_ = std::chrono::steady_clock::now();
92 try
93 {
94 int val = muted ? 0 : 1;
95 int err = snd_mixer_selem_set_playback_switch_all(elem_, val);
96 if (err < 0)
97 LOG(ERROR, LOG_TAG) << "Failed to mute, error: " << snd_strerror(err) << "\n";
98
99 long minv, maxv;
100 if ((err = snd_mixer_selem_get_playback_dB_range(elem_, &minv, &maxv)) == 0)
101 {
102 double min_norm = exp10((minv - maxv) / 6000.0);
103 volume = volume * (1 - min_norm) + min_norm;
104 double mixer_volume = 6000.0 * log10(volume) + maxv;
105
106 LOG(DEBUG, LOG_TAG) << "Mixer playback dB range [" << minv << ", " << maxv << "], volume: " << volume << ", mixer volume: " << mixer_volume << "\n";
107 if ((err = snd_mixer_selem_set_playback_dB_all(elem_, mixer_volume, 0)) < 0)
108 throw SnapException(std::string("Failed to set playback volume, error: ") + snd_strerror(err));
109 }
110 else
111 {
112 if ((err = snd_mixer_selem_get_playback_volume_range(elem_, &minv, &maxv)) < 0)
113 throw SnapException(std::string("Failed to get playback volume range, error: ") + snd_strerror(err));
114
115 auto mixer_volume = volume * (maxv - minv) + minv;
116 LOG(DEBUG, LOG_TAG) << "Mixer playback volume range [" << minv << ", " << maxv << "], volume: " << volume << ", mixer volume: " << mixer_volume
117 << "\n";
118 if ((err = snd_mixer_selem_set_playback_volume_all(elem_, mixer_volume)) < 0)
119 throw SnapException(std::string("Failed to set playback volume, error: ") + snd_strerror(err));
120 }
121 }
122 catch (const std::exception& e)
123 {
124 LOG(ERROR, LOG_TAG) << "Exception: " << e.what() << "\n";
125 uninitMixer();
126 }
127 }
128
129
getHardwareVolume(double & volume,bool & muted)130 bool AlsaPlayer::getHardwareVolume(double& volume, bool& muted)
131 {
132 try
133 {
134 std::lock_guard<std::recursive_mutex> lock(mutex_);
135 if (elem_ == nullptr)
136 throw SnapException("Mixer not initialized");
137
138 long vol;
139 int err = 0;
140 while (snd_mixer_handle_events(mixer_) > 0)
141 this_thread::sleep_for(1us);
142 long minv, maxv;
143 if ((err = snd_mixer_selem_get_playback_dB_range(elem_, &minv, &maxv)) == 0)
144 {
145 if ((err = snd_mixer_selem_get_playback_dB(elem_, SND_MIXER_SCHN_MONO, &vol)) < 0)
146 throw SnapException(std::string("Failed to get playback volume, error: ") + snd_strerror(err));
147
148 volume = pow(10, (vol - maxv) / 6000.0);
149 if (minv != SND_CTL_TLV_DB_GAIN_MUTE)
150 {
151 double min_norm = pow(10, (minv - maxv) / 6000.0);
152 volume = (volume - min_norm) / (1 - min_norm);
153 }
154 }
155 else
156 {
157 if ((err = snd_mixer_selem_get_playback_volume_range(elem_, &minv, &maxv)) < 0)
158 throw SnapException(std::string("Failed to get playback volume range, error: ") + snd_strerror(err));
159 if ((err = snd_mixer_selem_get_playback_volume(elem_, SND_MIXER_SCHN_MONO, &vol)) < 0)
160 throw SnapException(std::string("Failed to get playback volume, error: ") + snd_strerror(err));
161
162 vol -= minv;
163 maxv = maxv - minv;
164 volume = static_cast<double>(vol) / static_cast<double>(maxv);
165 }
166 int val;
167 if ((err = snd_mixer_selem_get_playback_switch(elem_, SND_MIXER_SCHN_MONO, &val)) < 0)
168 throw SnapException(std::string("Failed to get mute state, error: ") + snd_strerror(err));
169 muted = (val == 0);
170 LOG(DEBUG, LOG_TAG) << "Get volume, mixer volume range [" << minv << ", " << maxv << "], volume: " << volume << ", muted: " << muted << "\n";
171 snd_mixer_handle_events(mixer_);
172 return true;
173 }
174 catch (const std::exception& e)
175 {
176 LOG(ERROR, LOG_TAG) << "Exception: " << e.what() << "\n";
177 return false;
178 }
179 }
180
181
waitForEvent()182 void AlsaPlayer::waitForEvent()
183 {
184 sd_.async_wait(boost::asio::posix::stream_descriptor::wait_read, [this](const boost::system::error_code& ec) {
185 if (ec)
186 {
187 // TODO: fd is "Bad" after unplugging/plugging USB DAC, i.e. after init/uninit/init cycle
188 LOG(DEBUG, LOG_TAG) << "waitForEvent error: " << ec.message() << "\n";
189 return;
190 }
191
192 std::lock_guard<std::recursive_mutex> lock(mutex_);
193 if (ctl_ == nullptr)
194 return;
195
196 unsigned short revents;
197 snd_ctl_poll_descriptors_revents(ctl_, fd_.get(), 1, &revents);
198 if (((revents & POLLIN) != 0) || (revents == 0))
199 {
200 snd_ctl_event_t* event;
201 snd_ctl_event_alloca(&event);
202
203 if (((snd_ctl_read(ctl_, event) >= 0) && (snd_ctl_event_get_type(event) == SND_CTL_EVENT_ELEM)) || (revents == 0))
204 {
205 auto now = std::chrono::steady_clock::now();
206 if (now - last_change_ < 1s)
207 {
208 LOG(DEBUG, LOG_TAG) << "Last volume change by server: " << std::chrono::duration_cast<std::chrono::milliseconds>(now - last_change_).count()
209 << " ms => ignoring volume change\n";
210 waitForEvent();
211 return;
212 }
213 // Sometimes the old volume is reported by getHardwareVolume after this event has been raised.
214 // As workaround we defer getting the volume by 20ms.
215 timer_.cancel();
216 timer_.expires_after(20ms);
217 timer_.async_wait([this](const boost::system::error_code& ec) {
218 if (!ec)
219 {
220 if (getHardwareVolume(volume_, muted_))
221 {
222 LOG(DEBUG, LOG_TAG) << "Volume changed: " << volume_ << ", muted: " << muted_ << "\n";
223 notifyVolumeChange(volume_, muted_);
224 }
225 }
226 });
227 }
228 }
229 waitForEvent();
230 });
231 }
232
initMixer()233 void AlsaPlayer::initMixer()
234 {
235 if (settings_.mixer.mode != ClientSettings::Mixer::Mode::hardware)
236 return;
237
238 LOG(DEBUG, LOG_TAG) << "initMixer\n";
239 std::lock_guard<std::recursive_mutex> lock(mutex_);
240 int err;
241 if ((err = snd_ctl_open(&ctl_, mixer_device_.c_str(), SND_CTL_READONLY)) < 0)
242 throw SnapException("Can't open control for " + mixer_device_ + ", error: " + snd_strerror(err));
243 if ((err = snd_ctl_subscribe_events(ctl_, 1)) < 0)
244 throw SnapException("Can't subscribe for events for " + mixer_device_ + ", error: " + snd_strerror(err));
245 fd_ = std::unique_ptr<pollfd, std::function<void(pollfd*)>>(new pollfd(), [](pollfd* p) {
246 close(p->fd);
247 delete p;
248 });
249 err = snd_ctl_poll_descriptors(ctl_, fd_.get(), 1);
250 LOG(DEBUG, LOG_TAG) << "Filled " << err << " poll descriptors, poll descriptor count: " << snd_ctl_poll_descriptors_count(ctl_) << ", fd: " << fd_->fd
251 << "\n";
252
253 snd_mixer_selem_id_t* sid;
254 snd_mixer_selem_id_alloca(&sid);
255 int mix_index = 0;
256 // sets simple-mixer index and name
257 snd_mixer_selem_id_set_index(sid, mix_index);
258 snd_mixer_selem_id_set_name(sid, mixer_name_.c_str());
259
260 if ((err = snd_mixer_open(&mixer_, 0)) < 0)
261 throw SnapException(std::string("Failed to open mixer, error: ") + snd_strerror(err));
262 if ((err = snd_mixer_attach(mixer_, mixer_device_.c_str())) < 0)
263 throw SnapException("Failed to attach mixer to " + mixer_device_ + ", error: " + snd_strerror(err));
264 if ((err = snd_mixer_selem_register(mixer_, nullptr, nullptr)) < 0)
265 throw SnapException(std::string("Failed to register selem, error: ") + snd_strerror(err));
266 if ((err = snd_mixer_load(mixer_)) < 0)
267 throw SnapException(std::string("Failed to load mixer, error: ") + snd_strerror(err));
268 elem_ = snd_mixer_find_selem(mixer_, sid);
269 if (elem_ == nullptr)
270 throw SnapException("Failed to find mixer: " + mixer_name_);
271
272 sd_ = boost::asio::posix::stream_descriptor(io_context_, fd_->fd);
273 waitForEvent();
274 }
275
276
initAlsa()277 void AlsaPlayer::initAlsa()
278 {
279 std::lock_guard<std::recursive_mutex> lock(mutex_);
280
281 const SampleFormat& format = stream_->getFormat();
282 uint32_t rate = format.rate();
283 int channels = format.channels();
284 int err;
285
286 // Open the PCM device in playback mode
287 if ((err = snd_pcm_open(&handle_, settings_.pcm_device.name.c_str(), SND_PCM_STREAM_PLAYBACK, 0)) < 0)
288 throw SnapException("Can't open " + settings_.pcm_device.name + ", error: " + snd_strerror(err), err);
289
290 // struct snd_pcm_playback_info_t pinfo;
291 // if ((pcm = snd_pcm_playback_info( pcm_handle, &pinfo)) < 0)
292 // fprintf(stderr, "Error: playback info error: %s\n", snd_strerror(err));
293 // printf("buffer: '%d'\n", pinfo.buffer_size);
294
295 // Allocate parameters object and fill it with default values
296 snd_pcm_hw_params_t* params;
297 snd_pcm_hw_params_alloca(¶ms);
298 if ((err = snd_pcm_hw_params_any(handle_, params)) < 0)
299 throw SnapException("Can't fill params: " + string(snd_strerror(err)));
300
301 snd_output_t* output;
302 if (snd_output_buffer_open(&output) == 0)
303 {
304 if (snd_pcm_hw_params_dump(params, output) == 0)
305 {
306 char* str;
307 size_t len = snd_output_buffer_string(output, &str);
308 LOG(DEBUG, LOG_TAG) << std::string(str, len) << "\n";
309 }
310 snd_output_close(output);
311 }
312
313 // Set parameters
314 if ((err = snd_pcm_hw_params_set_access(handle_, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
315 throw SnapException("Can't set interleaved mode: " + string(snd_strerror(err)));
316
317 snd_pcm_format_t snd_pcm_format;
318 if (format.bits() == 8)
319 snd_pcm_format = SND_PCM_FORMAT_S8;
320 else if (format.bits() == 16)
321 snd_pcm_format = SND_PCM_FORMAT_S16_LE;
322 else if ((format.bits() == 24) && (format.sampleSize() == 4))
323 snd_pcm_format = SND_PCM_FORMAT_S24_LE;
324 else if (format.bits() == 32)
325 snd_pcm_format = SND_PCM_FORMAT_S32_LE;
326 else
327 throw SnapException("Unsupported sample format: " + cpt::to_string(format.bits()));
328
329 err = snd_pcm_hw_params_set_format(handle_, params, snd_pcm_format);
330 if (err == -EINVAL)
331 {
332 if (snd_pcm_format == SND_PCM_FORMAT_S24_LE)
333 {
334 snd_pcm_format = SND_PCM_FORMAT_S32_LE;
335 volCorrection_ = 256;
336 }
337 if (snd_pcm_format == SND_PCM_FORMAT_S8)
338 {
339 snd_pcm_format = SND_PCM_FORMAT_U8;
340 }
341 }
342
343 err = snd_pcm_hw_params_set_format(handle_, params, snd_pcm_format);
344 if (err < 0)
345 {
346 stringstream ss;
347 ss << "Can't set format: " << string(snd_strerror(err)) << ", supported: ";
348 for (int format = 0; format <= static_cast<int>(SND_PCM_FORMAT_LAST); format++)
349 {
350 auto snd_pcm_format = static_cast<snd_pcm_format_t>(format);
351 if (snd_pcm_hw_params_test_format(handle_, params, snd_pcm_format) == 0)
352 ss << snd_pcm_format_name(snd_pcm_format) << " ";
353 }
354 throw SnapException(ss.str());
355 }
356
357 if ((err = snd_pcm_hw_params_set_channels(handle_, params, channels)) < 0)
358 throw SnapException("Can't set channel count: " + string(snd_strerror(err)));
359
360 if ((err = snd_pcm_hw_params_set_rate_near(handle_, params, &rate, nullptr)) < 0)
361 throw SnapException("Can't set rate: " + string(snd_strerror(err)));
362 if (rate != format.rate())
363 LOG(WARNING, LOG_TAG) << "Could not set sample rate to " << format.rate() << " Hz, using: " << rate << " Hz\n";
364
365 uint32_t period_time = buffer_time_.value_or(BUFFER_TIME).count() / periods_.value_or(PERIODS);
366 uint32_t max_period_time = period_time;
367 if ((err = snd_pcm_hw_params_get_period_time_max(params, &max_period_time, nullptr)) < 0)
368 {
369 LOG(ERROR, LOG_TAG) << "Can't get max period time: " << snd_strerror(err) << "\n";
370 }
371 else
372 {
373 if (period_time > max_period_time)
374 {
375 LOG(INFO, LOG_TAG) << "Period time too large, changing from " << period_time << " to " << max_period_time << "\n";
376 period_time = max_period_time;
377 }
378 }
379 uint32_t min_period_time = period_time;
380 if ((err = snd_pcm_hw_params_get_period_time_min(params, &min_period_time, nullptr)) < 0)
381 {
382 LOG(ERROR, LOG_TAG) << "Can't get min period time: " << snd_strerror(err) << "\n";
383 }
384 else
385 {
386 if (period_time < min_period_time)
387 {
388 LOG(INFO, LOG_TAG) << "Period time too small, changing from " << period_time << " to " << min_period_time << "\n";
389 period_time = min_period_time;
390 }
391 }
392
393 if ((err = snd_pcm_hw_params_set_period_time_near(handle_, params, &period_time, nullptr)) < 0)
394 throw SnapException("Can't set period time: " + string(snd_strerror(err)));
395
396 uint32_t buffer_time = buffer_time_.value_or(BUFFER_TIME).count();
397 uint32_t periods = periods_.value_or(MIN_PERIODS);
398 if (buffer_time < period_time * periods)
399 {
400 LOG(INFO, LOG_TAG) << "Buffer time smaller than " << periods << " * periods: " << buffer_time << " us < " << period_time * periods
401 << " us, raising buffer time\n";
402 buffer_time = period_time * periods;
403 }
404
405 if ((err = snd_pcm_hw_params_set_buffer_time_near(handle_, params, &buffer_time, nullptr)) < 0)
406 throw SnapException("Can't set buffer time to " + cpt::to_string(buffer_time) + " us : " + string(snd_strerror(err)));
407
408 // unsigned int periods = periods_;
409 // if ((err = snd_pcm_hw_params_set_periods_near(handle_, params, &periods, 0)) < 0)
410 // throw SnapException("Can't set periods: " + string(snd_strerror(err)));
411
412 // Write parameters
413 if ((err = snd_pcm_hw_params(handle_, params)) < 0)
414 throw SnapException("Can't set hardware parameters: " + string(snd_strerror(err)));
415
416 // Resume information
417 // uint32_t periods;
418 if (snd_pcm_hw_params_get_periods(params, &periods, nullptr) < 0)
419 periods = round(static_cast<double>(buffer_time) / static_cast<double>(period_time));
420 snd_pcm_hw_params_get_period_size(params, &frames_, nullptr);
421 LOG(INFO, LOG_TAG) << "PCM name: " << snd_pcm_name(handle_) << ", sample rate: " << rate << " Hz, channels: " << channels
422 << ", buffer time: " << buffer_time << " us, periods: " << periods << ", period time: " << period_time
423 << " us, period frames: " << frames_ << "\n";
424
425 // Allocate buffer to hold single period
426 snd_pcm_sw_params_t* swparams;
427 snd_pcm_sw_params_alloca(&swparams);
428 snd_pcm_sw_params_current(handle_, swparams);
429
430 snd_pcm_sw_params_set_avail_min(handle_, swparams, frames_);
431 snd_pcm_sw_params_set_start_threshold(handle_, swparams, frames_);
432 // snd_pcm_sw_params_set_stop_threshold(pcm_handle, swparams, frames_);
433 snd_pcm_sw_params(handle_, swparams);
434
435 if (snd_pcm_state(handle_) == SND_PCM_STATE_PREPARED)
436 {
437 if ((err = snd_pcm_start(handle_)) < 0)
438 LOG(DEBUG, LOG_TAG) << "Failed to start PCM: " << snd_strerror(err) << "\n";
439 }
440
441 if (ctl_ == nullptr)
442 initMixer();
443 }
444
445
uninitAlsa(bool uninit_mixer)446 void AlsaPlayer::uninitAlsa(bool uninit_mixer)
447 {
448 std::lock_guard<std::recursive_mutex> lock(mutex_);
449 if (uninit_mixer)
450 uninitMixer();
451
452 if (handle_ != nullptr)
453 {
454 snd_pcm_drop(handle_);
455 snd_pcm_close(handle_);
456 handle_ = nullptr;
457 }
458 }
459
460
uninitMixer()461 void AlsaPlayer::uninitMixer()
462 {
463 if (settings_.mixer.mode != ClientSettings::Mixer::Mode::hardware)
464 return;
465
466 LOG(DEBUG, LOG_TAG) << "uninitMixer\n";
467 std::lock_guard<std::recursive_mutex> lock(mutex_);
468 if (sd_.is_open())
469 {
470 boost::system::error_code ec;
471 sd_.cancel(ec);
472 }
473 if (ctl_ != nullptr)
474 {
475 snd_ctl_close(ctl_);
476 ctl_ = nullptr;
477 }
478 if (mixer_ != nullptr)
479 {
480 snd_mixer_close(mixer_);
481 mixer_ = nullptr;
482 }
483 fd_ = nullptr;
484 elem_ = nullptr;
485 }
486
487
start()488 void AlsaPlayer::start()
489 {
490 try
491 {
492 initAlsa();
493 }
494 catch (const SnapException& e)
495 {
496 LOG(ERROR, LOG_TAG) << "Exception: " << e.what() << ", code: " << e.code() << "\n";
497 // Accept "Device or ressource busy", the worker loop will retry
498 if (e.code() != -EBUSY)
499 throw;
500 }
501
502 Player::start();
503 }
504
505
~AlsaPlayer()506 AlsaPlayer::~AlsaPlayer()
507 {
508 stop();
509 }
510
511
stop()512 void AlsaPlayer::stop()
513 {
514 Player::stop();
515 uninitAlsa(true);
516 }
517
518
needsThread() const519 bool AlsaPlayer::needsThread() const
520 {
521 return true;
522 }
523
524
getAvailDelay(snd_pcm_sframes_t & avail,snd_pcm_sframes_t & delay)525 bool AlsaPlayer::getAvailDelay(snd_pcm_sframes_t& avail, snd_pcm_sframes_t& delay)
526 {
527 int result = snd_pcm_avail_delay(handle_, &avail, &delay);
528 if (result < 0)
529 {
530 LOG(WARNING, LOG_TAG) << "snd_pcm_avail_delay failed: " << snd_strerror(result) << " (" << result << "), avail: " << avail << ", delay: " << delay
531 << ", using snd_pcm_avail amd snd_pcm_delay.\n";
532 this_thread::sleep_for(1ms);
533 avail = snd_pcm_avail(handle_);
534 result = snd_pcm_delay(handle_, &delay);
535 if ((result < 0) || (delay < 0))
536 {
537 LOG(WARNING, LOG_TAG) << "snd_pcm_delay failed: " << snd_strerror(result) << " (" << result << "), avail: " << avail << ", delay: " << delay
538 << "\n";
539 return false;
540 }
541 // LOG(DEBUG, LOG_TAG) << "snd_pcm_delay: " << delay << ", snd_pcm_avail: " << avail << "\n";
542 }
543
544 if (avail < 0)
545 {
546 LOG(DEBUG, LOG_TAG) << "snd_pcm_avail failed: " << snd_strerror(avail) << " (" << avail << "), using " << frames_ << "\n";
547 avail = frames_;
548 }
549
550 return true;
551 }
552
553
worker()554 void AlsaPlayer::worker()
555 {
556 snd_pcm_sframes_t pcm;
557 snd_pcm_sframes_t framesDelay;
558 snd_pcm_sframes_t framesAvail;
559 long lastChunkTick = chronos::getTickCount();
560 const SampleFormat& format = stream_->getFormat();
561 while (active_)
562 {
563 if (handle_ == nullptr)
564 {
565 try
566 {
567 initAlsa();
568 // set the hardware volume. It might have changed when we were not initialized
569 if (settings_.mixer.mode == ClientSettings::Mixer::Mode::hardware)
570 setHardwareVolume(volume_, muted_);
571 }
572 catch (const std::exception& e)
573 {
574 LOG(ERROR, LOG_TAG) << "Exception in initAlsa: " << e.what() << endl;
575 chronos::sleep(100);
576 }
577 if (handle_ == nullptr)
578 continue;
579 }
580
581 int wait_result = snd_pcm_wait(handle_, 100);
582 if (wait_result == -EPIPE)
583 {
584 LOG(ERROR, LOG_TAG) << "XRUN while waiting for PCM: " << snd_strerror(wait_result) << "\n";
585 snd_pcm_prepare(handle_);
586 }
587 else if (wait_result < 0)
588 {
589 LOG(ERROR, LOG_TAG) << "ERROR. Can't wait for PCM to become ready: " << snd_strerror(wait_result) << "\n";
590 uninitAlsa(true);
591 continue;
592 }
593 else if (wait_result == 0)
594 {
595 continue;
596 }
597
598 if (!getAvailDelay(framesAvail, framesDelay))
599 {
600 this_thread::sleep_for(10ms);
601 snd_pcm_prepare(handle_);
602 continue;
603 }
604
605 // if (framesAvail < static_cast<snd_pcm_sframes_t>(frames_))
606 // {
607 // this_thread::sleep_for(5ms);
608 // continue;
609 // }
610 if (framesAvail == 0)
611 {
612 auto frame_time = std::chrono::microseconds(static_cast<int>(frames_ / format.usRate()));
613 std::chrono::microseconds wait = std::min(frame_time / 2, std::chrono::microseconds(10ms));
614 LOG(DEBUG, LOG_TAG) << "No frames available, waiting for " << wait.count() << " us\n";
615 this_thread::sleep_for(wait);
616 continue;
617 }
618
619 // LOG(TRACE, LOG_TAG) << "res: " << result << ", framesAvail: " << framesAvail << ", delay: " << framesDelay << ", frames: " << frames_ << "\n";
620 chronos::usec delay(static_cast<chronos::usec::rep>(1000 * static_cast<double>(framesDelay) / format.msRate()));
621 // LOG(TRACE, LOG_TAG) << "delay: " << framesDelay << ", delay[ms]: " << delay.count() / 1000 << ", avail: " << framesAvail << "\n";
622
623 if (buffer_.size() < static_cast<size_t>(framesAvail * format.frameSize()))
624 {
625 LOG(DEBUG, LOG_TAG) << "Resizing buffer from " << buffer_.size() << " to " << framesAvail * format.frameSize() << "\n";
626 buffer_.resize(framesAvail * format.frameSize());
627 }
628 if (stream_->getPlayerChunk(buffer_.data(), delay, framesAvail))
629 {
630 lastChunkTick = chronos::getTickCount();
631 adjustVolume(buffer_.data(), framesAvail);
632 if ((pcm = snd_pcm_writei(handle_, buffer_.data(), framesAvail)) == -EPIPE)
633 {
634 LOG(ERROR, LOG_TAG) << "XRUN while writing to PCM: " << snd_strerror(pcm) << "\n";
635 snd_pcm_prepare(handle_);
636 }
637 else if (pcm < 0)
638 {
639 LOG(ERROR, LOG_TAG) << "ERROR. Can't write to PCM device: " << snd_strerror(pcm) << "\n";
640 uninitAlsa(true);
641 }
642 }
643 else
644 {
645 LOG(INFO, LOG_TAG) << "Failed to get chunk\n";
646 while (active_ && !stream_->waitForChunk(100ms))
647 {
648 static utils::logging::TimeConditional cond(2s);
649 LOG(DEBUG, LOG_TAG) << cond << "Waiting for chunk\n";
650 if ((handle_ != nullptr) && (chronos::getTickCount() - lastChunkTick > 5000))
651 {
652 LOG(NOTICE, LOG_TAG) << "No chunk received for 5000ms. Closing ALSA.\n";
653 uninitAlsa(false);
654 stream_->clearChunks();
655 }
656 }
657 }
658 }
659 }
660
661
662
pcm_list()663 vector<PcmDevice> AlsaPlayer::pcm_list()
664 {
665 void **hints, **n;
666 char *name, *descr, *io;
667 vector<PcmDevice> result;
668 PcmDevice pcmDevice;
669
670 if (snd_device_name_hint(-1, "pcm", &hints) < 0)
671 return result;
672 n = hints;
673 size_t idx(0);
674 while (*n != nullptr)
675 {
676 name = snd_device_name_get_hint(*n, "NAME");
677 descr = snd_device_name_get_hint(*n, "DESC");
678 io = snd_device_name_get_hint(*n, "IOID");
679 if (io != nullptr && strcmp(io, "Output") != 0)
680 goto __end;
681 pcmDevice.name = name;
682 if (descr == nullptr)
683 {
684 pcmDevice.description = "";
685 }
686 else
687 {
688 pcmDevice.description = descr;
689 }
690 pcmDevice.idx = idx++;
691 result.push_back(pcmDevice);
692
693 __end:
694 if (name != nullptr)
695 free(name);
696 if (descr != nullptr)
697 free(descr);
698 if (io != nullptr)
699 free(io);
700 n++;
701 }
702 snd_device_name_free_hint(hints);
703 return result;
704 }
705
706 } // namespace player