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(&params);
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