MythTV master
audiooutputalsa.cpp
Go to the documentation of this file.
1#include <cstdint>
2#include <cstdio>
3#include <cstdlib>
4#include <thread>
5
6#include <QFile>
7
10#include "audiooutputalsa.h"
11
12#define LOC QString("ALSA: ")
13
14// redefine assert as no-op to quiet some compiler warnings
15// about assert always evaluating true in alsa headers.
16#undef assert
17#define assert(x)
18
19static constexpr uint8_t CHANNELS_MIN { 1 };
20static constexpr uint8_t CHANNELS_MAX { 8 };
21
22static constexpr int OPEN_FLAGS
23 { SND_PCM_NO_AUTO_RESAMPLE | SND_PCM_NO_AUTO_FORMAT | SND_PCM_NO_AUTO_CHANNELS };
24
25static constexpr int FILTER_FLAGS { ~(SND_PCM_NO_AUTO_FORMAT) };
26
27// NOLINTBEGIN(cppcoreguidelines-macro-usage)
28#define AERROR(str) LOG(VB_GENERAL, LOG_ERR, LOC + (str) + QString(": %1").arg(snd_strerror(err)))
29#define CHECKERR(str) { if (err < 0) { AERROR(str); return err; } }
30// NOLINTEND(cppcoreguidelines-macro-usage)
31
33 AudioOutputBase(settings)
34{
35 // Set everything up
36 if (m_passthruDevice == "auto")
37 {
39
40 /*
41 * AES description:
42 * AES0=6 AES1=0x82 AES2=0x00 AES3=0x01.
43 * AES0 = NON_AUDIO | PRO_MODE
44 * AES1 = original stream, original PCM coder
45 * AES2 = source and channel unspecified
46 * AES3 = sample rate unspecified
47 */
48 bool s48k = gCoreContext->GetBoolSetting("SPDIFRateOverride", false);
49 QString iecarg = QString("AES0=6,AES1=0x82,AES2=0x00") +
50 (s48k ? QString() : QString(",AES3=0x01"));
51 QString iecarg2 = QString("AES0=6 AES1=0x82 AES2=0x00") +
52 (s48k ? QString() : QString(" AES3=0x01"));
53
54 QStringList parts = m_passthruDevice.split(':');
55 if (parts.length() == 1)
56 {
57 /* no existing parameters: add it behind device name */
58 m_passthruDevice += ":" + iecarg;
59 }
60 else
61 {
62 QString params = parts[1].trimmed();
63 if (params.isEmpty())
64 {
65 /* ":" but no parameters */
66 m_passthruDevice += iecarg;
67 }
68 else if (params[0] != '{')
69 {
70 /* a simple list of parameters: add it at the end of the list */
71 m_passthruDevice += "," + iecarg;
72 }
73 else
74 {
75 /* parameters in config syntax: add it inside the { } block */
76 /* the whitespace has been trimmed, so the curly brackets */
77 /* should be the first and last characters in the string. */
78 QString oldparams = params.mid(1,params.size()-2);
79 m_passthruDevice = QString("%1:{ %2 %3 }")
80 .arg(parts[0], oldparams.trimmed(), iecarg2);
81 }
82 }
83 }
84 else if (m_passthruDevice.toLower() == "default")
85 {
87 }
88 else
89 {
90 m_discreteDigital = true;
91 }
92
93 InitSettings(settings);
94 if (settings.m_init)
95 Reconfigure(settings);
96}
97
99{
100 KillAudio();
101}
102
103int AudioOutputALSA::TryOpenDevice(int open_mode, bool try_ac3)
104{
105 QByteArray dev_ba;
106 int err = -1;
107
108 if (try_ac3)
109 {
110 dev_ba = m_passthruDevice.toLatin1();
111 LOG(VB_AUDIO, LOG_INFO, LOC + QString("OpenDevice %1 for passthrough").arg(m_passthruDevice));
112 err = snd_pcm_open(&m_pcmHandle, dev_ba.constData(),
113 SND_PCM_STREAM_PLAYBACK, open_mode);
114
116
118 return err;
119
120 if (err < 0)
121 {
122 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Auto setting passthrough failed (%1), defaulting "
123 "to main device").arg(snd_strerror(err)));
124 }
125 }
126 if (!try_ac3 || err < 0)
127 {
128 // passthru open failed, retry default device
129 LOG(VB_AUDIO, LOG_INFO, LOC + QString("OpenDevice %1").arg(m_mainDevice));
130 dev_ba = m_mainDevice.toLatin1();
131 err = snd_pcm_open(&m_pcmHandle, dev_ba.constData(),
132 SND_PCM_STREAM_PLAYBACK, open_mode);
134 }
135 return err;
136}
137
138int AudioOutputALSA::GetPCMInfo(int &card, int &device, int &subdevice)
139{
140 // Check for saved values
141 if (m_card != -1 && m_device != -1 && m_subdevice != -1)
142 {
143 card = m_card;
145 subdevice = m_subdevice;
146 return 0;
147 }
148
149 if (!m_pcmHandle)
150 return -1;
151
152 snd_pcm_info_t *pcm_info = nullptr;
153
154 snd_pcm_info_alloca(&pcm_info);
155
156 int err = snd_pcm_info(m_pcmHandle, pcm_info);
157 CHECKERR("snd_pcm_info");
158
159 err = snd_pcm_info_get_card(pcm_info);
160 CHECKERR("snd_pcm_info_get_card");
161 int tcard = err;
162
163 err = snd_pcm_info_get_device(pcm_info);
164 CHECKERR("snd_pcm_info_get_device");
165 int tdevice = err;
166
167 err = snd_pcm_info_get_subdevice(pcm_info);
168 CHECKERR("snd_pcm_info_get_subdevice");
169 int tsubdevice = err;
170
171 m_card = card = tcard;
172 m_device = device = tdevice;
173 m_subdevice = subdevice = tsubdevice;
174
175 return 0;
176 }
177
178bool AudioOutputALSA::IncPreallocBufferSize(int requested, int buffer_time)
179{
180 int card = 0;
181 int device = 0;
182 int subdevice = 0;
183
184 m_pbufSize = 0;
185
186 if (GetPCMInfo(card, device, subdevice) < 0)
187 return false;
188
189 const QString pf = QString("/proc/asound/card%1/pcm%2p/sub%3/prealloc")
190 .arg(card).arg(device).arg(subdevice);
191
192 QFile pfile(pf);
193 QFile mfile(pf + "_max");
194
195 if (!pfile.open(QIODevice::ReadOnly))
196 {
197 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error opening %1. Fix reading permissions.").arg(pf));
198 return false;
199 }
200
201 if (!mfile.open(QIODevice::ReadOnly))
202 {
203 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Error opening %1").arg(pf + "_max"));
204 return false;
205 }
206
207 int cur = pfile.readAll().trimmed().toInt();
208 int max = mfile.readAll().trimmed().toInt();
209
210 int size = (((int)(cur * (float)requested / (float)buffer_time)
211 / 64) + 1) * 64;
212
213 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Hardware audio buffer cur: %1 need: %2 max allowed: %3")
214 .arg(cur).arg(size).arg(max));
215
216 if (cur == max)
217 {
218 // It's already the maximum it can be, no point trying further
219 pfile.close();
220 mfile.close();
221 return false;
222 }
223 if (size > max || !size)
224 {
225 size = max;
226 }
227
228 pfile.close();
229 mfile.close();
230
231 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Try to manually increase audio buffer with: echo %1 "
232 "| sudo tee %2").arg(size).arg(pf));
233 return false;
234}
235
236QByteArray *AudioOutputALSA::GetELD(int card, int device, int subdevice)
237{
238 QByteArray *result = nullptr;
239 snd_hctl_t *hctl = nullptr;
240 snd_ctl_elem_info_t *info = nullptr;
241 snd_ctl_elem_id_t *id = nullptr;
242 snd_ctl_elem_value_t *control = nullptr;
243
244 snd_ctl_elem_info_alloca(&info);
245 snd_ctl_elem_id_alloca(&id);
246 snd_ctl_elem_value_alloca(&control);
247
248 snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_PCM);
249 snd_ctl_elem_id_set_name(id, "ELD");
250 snd_ctl_elem_id_set_device(id, device);
251 snd_ctl_elem_id_set_subdevice(id, subdevice);
252
253 int err = snd_hctl_open(&hctl,
254 QString("hw:%1").arg(card).toLatin1().constData(),
255 0);
256 if (err < 0)
257 {
258 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Control %1 open error: %2")
259 .arg(card)
260 .arg(snd_strerror(err)));
261 return nullptr;
262 }
263 err = snd_hctl_load(hctl);
264 if (err < 0)
265 {
266 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Control %1 load error: %2")
267 .arg(card)
268 .arg(snd_strerror(err)));
269 /* frees automatically the control which cannot be added. */
270 return nullptr;
271 }
272 snd_hctl_elem_t *elem = snd_hctl_find_elem(hctl, id);
273 if (elem)
274 {
275 err = snd_hctl_elem_info(elem, info);
276 if (err < 0)
277 {
278 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Control %1 snd_hctl_elem_info error: %2")
279 .arg(card)
280 .arg(snd_strerror(err)));
281 snd_hctl_close(hctl);
282 return nullptr;
283 }
284 unsigned int count = snd_ctl_elem_info_get_count(info);
285 snd_ctl_elem_type_t type = snd_ctl_elem_info_get_type(info);
286 if (!snd_ctl_elem_info_is_readable(info))
287 {
288 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Control %1 element info is not readable")
289 .arg(card));
290 snd_hctl_close(hctl);
291 return nullptr;
292 }
293 err = snd_hctl_elem_read(elem, control);
294 if (err < 0)
295 {
296 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Control %1 element read error: %2")
297 .arg(card)
298 .arg(snd_strerror(err)));
299 snd_hctl_close(hctl);
300 return nullptr;
301 }
302 if (type != SND_CTL_ELEM_TYPE_BYTES)
303 {
304 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Control %1 element is of the wrong type")
305 .arg(card));
306 snd_hctl_close(hctl);
307 return nullptr;
308 }
309 result = new QByteArray((char *)snd_ctl_elem_value_get_bytes(control),
310 count);
311 }
312 snd_hctl_close(hctl);
313 return result;
314}
315
317{
318 snd_pcm_hw_params_t *params = nullptr;
319 snd_pcm_format_t afmt = SND_PCM_FORMAT_UNKNOWN;
321
322 auto *settings = new AudioOutputSettings();
323
324 if (m_pcmHandle)
325 {
326 snd_pcm_close(m_pcmHandle);
327 m_pcmHandle = nullptr;
328 }
329
330 int err = TryOpenDevice(OPEN_FLAGS, passthrough);
331 if (err < 0)
332 {
333 AERROR(QString("snd_pcm_open(\"%1\")").arg(m_lastDevice));
334 delete settings;
335 return nullptr;
336 }
337
338 snd_pcm_hw_params_alloca(&params);
339
340 if (snd_pcm_hw_params_any(m_pcmHandle, params) < 0)
341 {
342 snd_pcm_close(m_pcmHandle);
343 err = TryOpenDevice(OPEN_FLAGS&FILTER_FLAGS, passthrough);
344 if (err < 0)
345 {
346 AERROR(QString("snd_pcm_open(\"%1\")").arg(m_lastDevice));
347 delete settings;
348 return nullptr;
349 }
350 err = snd_pcm_hw_params_any(m_pcmHandle, params);
351 if (err < 0)
352 {
353 AERROR("No playback configurations available");
354 snd_pcm_close(m_pcmHandle);
355 m_pcmHandle = nullptr;
356 delete settings;
357 return nullptr;
358 }
359 LOG(VB_GENERAL, LOG_WARNING, "Supported audio format detection will be inacurrate "
360 "(using plugin?)");
361 }
362
363 // NOLINTNEXTLINE(bugprone-infinite-loop)
364 while (int rate = settings->GetNextRate())
365 if(snd_pcm_hw_params_test_rate(m_pcmHandle, params, rate, 0) >= 0)
366 settings->AddSupportedRate(rate);
367
368 while ((fmt = settings->GetNextFormat()))
369 {
370 switch (fmt)
371 {
372 case FORMAT_U8: afmt = SND_PCM_FORMAT_U8; break;
373 case FORMAT_S16: afmt = SND_PCM_FORMAT_S16; break;
374 // NOLINTNEXTLINE(bugprone-branch-clone)
375 case FORMAT_S24LSB: afmt = SND_PCM_FORMAT_S24; break;
376 case FORMAT_S24: afmt = SND_PCM_FORMAT_S24; break;
377 case FORMAT_S32: afmt = SND_PCM_FORMAT_S32; break;
378 case FORMAT_FLT: afmt = SND_PCM_FORMAT_FLOAT; break;
379 default: continue;
380 }
381 if (snd_pcm_hw_params_test_format(m_pcmHandle, params, afmt) >= 0)
382 settings->AddSupportedFormat(fmt);
383 }
384
386 if (snd_pcm_hw_params_test_channels(m_pcmHandle, params, channels) >= 0)
387 settings->AddSupportedChannels(channels);
388
389 int card = 0;
390 int device = 0;
391 int subdevice = 0;
392 if (GetPCMInfo(card, device, subdevice) >= 0)
393 {
394 // Check if we can retrieve ELD for this device
395 QByteArray *eld = GetELD(card, device, subdevice);
396 if (eld != nullptr)
397 {
398 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Successfully retrieved ELD data"));
399 settings->setELD(eld);
400 delete eld;
401 }
402 }
403 else
404 {
405 LOG(VB_AUDIO, LOG_INFO, LOC + "Can't get card and device number");
406 }
407
408 snd_pcm_close(m_pcmHandle);
409 m_pcmHandle = nullptr;
410
411 /* Check if name or description contains information
412 to know if device can accept passthrough or not */
413 QMap<QString, QString> *alsadevs = GetDevices("pcm");
414 while (true)
415 {
416 QString real_device = ((passthrough && m_discreteDigital) ?
418
419 QString desc = alsadevs->value(real_device);
420
421 settings->setPassthrough(1); // yes passthrough
422 if (real_device.contains("digital", Qt::CaseInsensitive) ||
423 desc.contains("digital", Qt::CaseInsensitive))
424 break;
425 if (real_device.contains("iec958", Qt::CaseInsensitive))
426 break;
427 if (real_device.contains("spdif", Qt::CaseInsensitive))
428 break;
429 if (real_device.contains("hdmi", Qt::CaseInsensitive))
430 break;
431
432 settings->setPassthrough(-1); // no passthrough
433 // PulseAudio does not support passthrough
434 if (real_device.contains("pulse", Qt::CaseInsensitive) ||
435 desc.contains("pulse", Qt::CaseInsensitive))
436 break;
437 if (real_device.contains("analog", Qt::CaseInsensitive) ||
438 desc.contains("analog", Qt::CaseInsensitive))
439 break;
440 if (real_device.contains("surround", Qt::CaseInsensitive) ||
441 desc.contains("surround", Qt::CaseInsensitive))
442 break;
443
444 settings->setPassthrough(0); // maybe passthrough
445 break;
446 }
447 delete alsadevs;
448 return settings;
449}
450
452{
453 snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN;
454
455 if (m_pcmHandle != nullptr)
456 CloseDevice();
457
458 int err = TryOpenDevice(0, m_passthru || m_enc);
459 if (err < 0)
460 {
461 AERROR(QString("snd_pcm_open(\"%1\")").arg(m_lastDevice));
462 if (m_pcmHandle)
463 CloseDevice();
464 return false;
465 }
466
467 switch (m_outputFormat)
468 {
469 case FORMAT_U8: format = SND_PCM_FORMAT_U8; break;
470 case FORMAT_S16: format = SND_PCM_FORMAT_S16; break;
471 // NOLINTNEXTLINE(bugprone-branch-clone)
472 case FORMAT_S24LSB: format = SND_PCM_FORMAT_S24; break;
473 case FORMAT_S24: format = SND_PCM_FORMAT_S24; break;
474 case FORMAT_S32: format = SND_PCM_FORMAT_S32; break;
475 case FORMAT_FLT: format = SND_PCM_FORMAT_FLOAT; break;
476 default:
477 {
478 QString message {QCoreApplication::translate("AudioOutputALSA", "Unknown sample format: %1")
479 .arg(m_outputFormat)};
480 dispatchError(message);
481 LOG(VB_GENERAL, LOG_ERR, message);
482 return false;
483 }
484 }
485
486 // buffer 0.5s worth of samples
487 uint buffer_time = gCoreContext->GetNumSetting("ALSABufferOverride", 500) * 1000;
488
489 uint period_time = 4; // aim for an interrupt every (1/4th of buffer_time)
490
492 buffer_time, period_time);
493 if (err < 0)
494 {
495 AERROR("Unable to set ALSA parameters");
496 CloseDevice();
497 return false;
498 }
499
500 if (m_internalVol && !OpenMixer())
501 LOG(VB_GENERAL, LOG_ERR, LOC + "Unable to open audio mixer. Volume control disabled");
502
503 // Device opened successfully
504 return true;
505}
506
508{
509 if (m_mixer.handle)
510 snd_mixer_close(m_mixer.handle);
511 m_mixer.handle = nullptr;
512 if (m_pcmHandle)
513 {
514 snd_pcm_drain(m_pcmHandle);
515 snd_pcm_close(m_pcmHandle);
516 m_pcmHandle = nullptr;
517 }
518}
519
520template <class AudioDataType>
521static inline void tReorderSmpteToAlsa(AudioDataType *buf, uint frames,
522 uint extrach)
523{
524 AudioDataType tmpC;
525 AudioDataType tmpLFE;
526 AudioDataType *buf2 = nullptr;
527
528 for (uint i = 0; i < frames; i++)
529 {
530 buf = buf2 = buf + 2;
531
532 tmpC = *buf++;
533 tmpLFE = *buf++;
534 *buf2++ = *buf++;
535 *buf2++ = *buf++;
536 *buf2++ = tmpC;
537 *buf2++ = tmpLFE;
538 buf += extrach;
539 }
540}
541
542static inline void ReorderSmpteToAlsa(void *buf, uint frames,
543 AudioFormat format, uint extrach)
544{
546 {
547 case 8: tReorderSmpteToAlsa((uchar *)buf, frames, extrach); break;
548 case 16: tReorderSmpteToAlsa((short *)buf, frames, extrach); break;
549 default: tReorderSmpteToAlsa((int *)buf, frames, extrach); break;
550 }
551}
552
553void AudioOutputALSA::WriteAudio(uchar *aubuf, int size)
554{
555 uchar *tmpbuf = aubuf;
556 uint frames = size / m_outputBytesPerFrame;
557
558 if (m_pcmHandle == nullptr)
559 {
560 QString message {"WriteAudio() called with pcm_handle == nullptr!"};
561 dispatchError(message);
562 LOG(VB_GENERAL, LOG_ERR, message);
563 return;
564 }
565
566 //Audio received is in SMPTE channel order, reorder to ALSA unless passthru
567 if (!m_passthru && (m_channels == 6 || m_channels == 8))
568 {
569 ReorderSmpteToAlsa(aubuf, frames, m_outputFormat, m_channels - 6);
570 }
571
572 LOG(VB_AUDIO | VB_TIMESTAMP, LOG_INFO,
573 QString("WriteAudio: Preparing %1 bytes (%2 frames)")
574 .arg(size).arg(frames));
575
576 while (frames > 0)
577 {
578 int lw = snd_pcm_writei(m_pcmHandle, tmpbuf, frames);
579
580 if (lw >= 0)
581 {
582 if ((uint)lw < frames)
583 LOG(VB_AUDIO, LOG_INFO, LOC + QString("WriteAudio: short write %1 bytes (ok)")
584 .arg(lw * m_outputBytesPerFrame));
585
586 frames -= lw;
587 tmpbuf += static_cast<ptrdiff_t>(lw) * m_outputBytesPerFrame; // bytes
588 continue;
589 }
590
591 int err = lw;
592
593 switch (err)
594 {
595 case -EPIPE:
596 if (snd_pcm_state(m_pcmHandle) == SND_PCM_STATE_XRUN)
597 {
598 LOG(VB_AUDIO, LOG_INFO, LOC + "WriteAudio: buffer underrun");
599 err = snd_pcm_prepare(m_pcmHandle);
600 if (err < 0)
601 {
602 AERROR("WriteAudio: unable to recover from xrun");
603 return;
604 }
605 }
606 break;
607
608#if ESTRPIPE != EPIPE
609 case -ESTRPIPE:
610 LOG(VB_AUDIO, LOG_INFO, LOC + "WriteAudio: device is suspended");
611 while ((err = snd_pcm_resume(m_pcmHandle)) == -EAGAIN)
612 std::this_thread::sleep_for(200us);
613
614 if (err < 0)
615 {
616 LOG(VB_GENERAL, LOG_ERR, LOC + "WriteAudio: resume failed");
617 err = snd_pcm_prepare(m_pcmHandle);
618 if (err < 0)
619 {
620 AERROR("WriteAudio: unable to recover from suspend");
621 return;
622 }
623 }
624 break;
625#endif
626
627 case -EBADFD:
628 {
629 QString message {QString("WriteAudio: device is in a bad state (state = %1)")
630 .arg(snd_pcm_state(m_pcmHandle))};
631 dispatchError(message);
632 LOG(VB_GENERAL, LOG_ERR, message);
633 return;
634 }
635
636 default:
637 AERROR(QString("WriteAudio: Write failed, state: %1, err")
638 .arg(snd_pcm_state(m_pcmHandle)));
639 return;
640 }
641 }
642}
643
645{
646 if (m_pcmHandle == nullptr)
647 {
648 LOG(VB_GENERAL, LOG_ERR, LOC + "getBufferedOnSoundcard() called with pcm_handle == nullptr!");
649 return 0;
650 }
651
652 snd_pcm_sframes_t delay = 0;
653
654 /* Delay is the total delay from writing to the pcm until the samples
655 hit the DAC - includes buffered samples and any fixed latencies */
656 if (snd_pcm_delay(m_pcmHandle, &delay) < 0)
657 return 0;
658
659 // BUG: calling snd_pcm_state causes noise and repeats on the Raspberry Pi
660 return delay * m_outputBytesPerFrame;
661}
662
670int AudioOutputALSA::SetParameters(snd_pcm_t *handle, snd_pcm_format_t format,
671 uint channels, uint rate, uint buffer_time,
672 uint period_time)
673{
674 snd_pcm_hw_params_t *params = nullptr;
675 snd_pcm_sw_params_t *swparams = nullptr;
676 snd_pcm_uframes_t period_size = 0;
677 snd_pcm_uframes_t period_size_min = 0;
678 snd_pcm_uframes_t period_size_max = 0;
679 snd_pcm_uframes_t buffer_size = 0;
680 snd_pcm_uframes_t buffer_size_min = 0;
681 snd_pcm_uframes_t buffer_size_max = 0;
682
683 LOG(VB_AUDIO, LOG_INFO, LOC + QString("SetParameters(format=%1, channels=%2, rate=%3, "
684 "buffer_time=%4, period_time=%5)")
685 .arg(format).arg(channels).arg(rate).arg(buffer_time)
686 .arg(period_time));
687
688 if (handle == nullptr)
689 {
690 QString message {QCoreApplication::translate("AudioOutputALSA",
691 "SetParameters() called with handle == nullptr!")};
692 dispatchError(message);
693 LOG(VB_GENERAL, LOG_ERR, message);
694 return -1;
695 }
696
697 snd_pcm_hw_params_alloca(&params);
698 snd_pcm_sw_params_alloca(&swparams);
699
700 /* choose all parameters */
701 int err = snd_pcm_hw_params_any(handle, params);
702 CHECKERR("No playback configurations available");
703
704 /* set the interleaved read/write format */
705 err = snd_pcm_hw_params_set_access(handle, params,
706 SND_PCM_ACCESS_RW_INTERLEAVED);
707 CHECKERR(QString("Interleaved RW audio not available"));
708
709 /* set the sample format */
710 err = snd_pcm_hw_params_set_format(handle, params, format);
711 CHECKERR(QString("Sample format %1 not available").arg(format));
712
713 /* set the count of channels */
714 err = snd_pcm_hw_params_set_channels(handle, params, channels);
715 CHECKERR(QString("Channels count %1 not available").arg(channels));
716
717 /* set the stream rate */
719 {
720 err = snd_pcm_hw_params_set_rate_resample(handle, params, 1);
721 CHECKERR(QString("Resampling setup failed").arg(rate));
722
723 uint rrate = rate;
724 err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, nullptr);
725 CHECKERR(QString("Rate %1Hz not available for playback: %s").arg(rate));
726
727 if (rrate != rate)
728 {
729 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Rate doesn't match (requested %1Hz, got %2Hz)")
730 .arg(rate).arg(err));
731 return err;
732 }
733 }
734 else
735 {
736 err = snd_pcm_hw_params_set_rate(handle, params, rate, 0);
737 CHECKERR(QString("Samplerate %1 Hz not available").arg(rate));
738 }
739
740 /* get the buffer parameters */
741 (void) snd_pcm_hw_params_get_buffer_size_min(params, &buffer_size_min);
742 (void) snd_pcm_hw_params_get_buffer_size_max(params, &buffer_size_max);
743 (void) snd_pcm_hw_params_get_period_size_min(params, &period_size_min, nullptr);
744 (void) snd_pcm_hw_params_get_period_size_max(params, &period_size_max, nullptr);
745 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Buffer size range from %1 to %2")
746 .arg(buffer_size_min)
747 .arg(buffer_size_max));
748 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Period size range from %1 to %2")
749 .arg(period_size_min)
750 .arg(period_size_max));
751
752 /* set the buffer time */
753 uint original_buffer_time = buffer_time;
754 bool canincrease = true;
755 err = snd_pcm_hw_params_set_buffer_time_near(handle, params,
756 &buffer_time, nullptr);
757 if (err < 0)
758 {
759 int dir = -1;
760 uint buftmp = buffer_time;
761 int attempt = 0;
762 // Try again with a fourth parameter
763 err = snd_pcm_hw_params_set_buffer_time_near(handle, params,
764 &buffer_time, &dir);
765 while (err < 0)
766 {
767 /*
768 * with some drivers, snd_pcm_hw_params_set_buffer_time_near
769 * only works once, if that's the case no point trying with
770 * different values
771 */
772 if ((buffer_time <= 100000) ||
773 (attempt > 0 && buffer_time == buftmp))
774 {
775 LOG(VB_GENERAL, LOG_ERR, LOC + "Couldn't set buffer time, giving up");
776 return err;
777 }
778 AERROR(QString("Unable to set buffer time to %1us, retrying")
779 .arg(buffer_time));
780 buffer_time -= 100000;
781 canincrease = false;
782 attempt++;
783 err = snd_pcm_hw_params_set_buffer_time_near(handle, params,
784 &buffer_time, &dir);
785 }
786 }
787
788 /* See if we need to increase the prealloc'd buffer size
789 If buffer_time is too small we could underrun - make 10% difference ok */
790 if (buffer_time * 1.10F < (float)original_buffer_time)
791 {
792 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Requested %1us got %2 buffer time")
793 .arg(original_buffer_time).arg(buffer_time));
794 // We need to increase preallocated buffer size in the driver
795 if (canincrease && m_pbufSize < 0)
796 {
797 IncPreallocBufferSize(original_buffer_time, buffer_time);
798 }
799 }
800
801 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Buffer time = %1 us").arg(buffer_time));
802
803 /* set the period time */
804 err = snd_pcm_hw_params_set_periods_near(handle, params,
805 &period_time, nullptr);
806 CHECKERR(QString("Unable to set period time %1").arg(period_time));
807 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Period time = %1 periods").arg(period_time));
808
809 /* write the parameters to device */
810 err = snd_pcm_hw_params(handle, params);
811 CHECKERR("Unable to set hw params for playback");
812
813 err = snd_pcm_get_params(handle, &buffer_size, &period_size);
814 CHECKERR("Unable to get PCM params");
815 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Buffer size = %1 | Period size = %2")
816 .arg(buffer_size).arg(period_size));
817
818 /* set member variables */
820 m_fragmentSize = (period_size >> 1) * m_outputBytesPerFrame;
821
822 /* get the current swparams */
823 err = snd_pcm_sw_params_current(handle, swparams);
824 CHECKERR("Unable to get current swparams");
825
826 /* start the transfer after period_size */
827 err = snd_pcm_sw_params_set_start_threshold(handle, swparams, period_size);
828 CHECKERR("Unable to set start threshold");
829
830 /* allow the transfer when at least period_size samples can be processed */
831 err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_size);
832 CHECKERR("Unable to set avail min");
833
834 /* write the parameters to the playback device */
835 err = snd_pcm_sw_params(handle, swparams);
836 CHECKERR("Unable to set sw params");
837
838 err = snd_pcm_prepare(handle);
839 CHECKERR("Unable to prepare the PCM");
840
841 return 0;
842}
843
845{
846 int retvol = 0;
847
848 if (!m_mixer.elem)
849 return retvol;
850
851 auto chan = (snd_mixer_selem_channel_id_t) channel;
852 if (!snd_mixer_selem_has_playback_channel(m_mixer.elem, chan))
853 return retvol;
854
855 long mixervol = 0;
856 int chk = snd_mixer_selem_get_playback_volume(m_mixer.elem, chan,
857 &mixervol);
858 if (chk < 0)
859 {
860 LOG(VB_GENERAL, LOG_ERR, LOC + QString("failed to get channel %1 volume, mixer %2/%3: %4")
861 .arg(QString::number(channel), m_mixer.device,
862 m_mixer.control, snd_strerror(chk)));
863 }
864 else
865 {
866 retvol = (m_mixer.volrange != 0L)
867 ? ((mixervol - m_mixer.volmin) * 100.0F / m_mixer.volrange) + 0.5F
868 : 0;
869 retvol = std::max(retvol, 0);
870 retvol = std::min(retvol, 100);
871 LOG(VB_AUDIO, LOG_INFO, LOC + QString("get volume channel %1: %2")
872 .arg(channel).arg(retvol));
873 }
874 return retvol;
875}
876
877void AudioOutputALSA::SetVolumeChannel(int channel, int volume)
878{
879 if (!(m_internalVol && m_mixer.elem))
880 return;
881
882 long mixervol = ((int64_t(volume) * m_mixer.volrange) / 100) + m_mixer.volmin;
883 mixervol = std::max(mixervol, m_mixer.volmin);
884 mixervol = std::min(mixervol, m_mixer.volmax);
885
886 auto chan = (snd_mixer_selem_channel_id_t) channel;
887
888 if (snd_mixer_selem_has_playback_switch(m_mixer.elem))
889 snd_mixer_selem_set_playback_switch(m_mixer.elem, chan, static_cast<int>(volume > 0));
890
891 if (snd_mixer_selem_set_playback_volume(m_mixer.elem, chan, mixervol) < 0)
892 LOG(VB_GENERAL, LOG_ERR, LOC + QString("failed to set channel %1 volume").arg(channel));
893 else
894 LOG(VB_AUDIO, LOG_INFO, LOC + QString("channel %1 volume set %2 => %3")
895 .arg(channel).arg(volume).arg(mixervol));
896}
897
899{
900 if (!m_pcmHandle)
901 {
902 LOG(VB_GENERAL, LOG_ERR, LOC + "mixer setup without a pcm");
903 return false;
904 }
905 m_mixer.device = gCoreContext->GetSetting("MixerDevice", "default");
906 m_mixer.device = m_mixer.device.remove(QString("ALSA:"));
907 if (m_mixer.device.toLower() == "software")
908 return true;
909
910 m_mixer.control = gCoreContext->GetSetting("MixerControl", "PCM");
911
912 QString mixer_device_tag = QString("mixer device %1").arg(m_mixer.device);
913
914 int chk = snd_mixer_open(&m_mixer.handle, 0);
915 if (chk < 0)
916 {
917 LOG(VB_GENERAL, LOG_ERR, LOC + QString("failed to open mixer device %1: %2")
918 .arg(mixer_device_tag, snd_strerror(chk)));
919 return false;
920 }
921
922 QByteArray dev_ba = m_mixer.device.toLatin1();
923 struct snd_mixer_selem_regopt regopts =
924 {.ver=1, .abstract=SND_MIXER_SABSTRACT_NONE, .device=dev_ba.constData(),
925 .playback_pcm=nullptr, .capture_pcm=nullptr};
926
927 chk = snd_mixer_selem_register(m_mixer.handle, &regopts, nullptr);
928 if (chk < 0)
929 {
930 snd_mixer_close(m_mixer.handle);
931 m_mixer.handle = nullptr;
932 LOG(VB_GENERAL, LOG_ERR, LOC + QString("failed to register %1: %2")
933 .arg(mixer_device_tag, snd_strerror(chk)));
934 return false;
935 }
936
937 chk = snd_mixer_load(m_mixer.handle);
938 if (chk < 0)
939 {
940 snd_mixer_close(m_mixer.handle);
941 m_mixer.handle = nullptr;
942 LOG(VB_GENERAL, LOG_ERR, LOC + QString("failed to load %1: %2")
943 .arg(mixer_device_tag, snd_strerror(chk)));
944 return false;
945 }
946
947 m_mixer.elem = nullptr;
948 uint elcount = snd_mixer_get_count(m_mixer.handle);
949 snd_mixer_elem_t* elx = snd_mixer_first_elem(m_mixer.handle);
950
951 for (uint ctr = 0; elx != nullptr && ctr < elcount; ctr++)
952 {
953 QString tmp = QString(snd_mixer_selem_get_name(elx));
954 if (m_mixer.control == tmp &&
955 !snd_mixer_selem_is_enumerated(elx) &&
956 snd_mixer_selem_has_playback_volume(elx) &&
957 snd_mixer_selem_is_active(elx))
958 {
959 m_mixer.elem = elx;
960 LOG(VB_AUDIO, LOG_INFO, LOC + QString("found playback control %1 on %2")
961 .arg(m_mixer.control, mixer_device_tag));
962 break;
963 }
964 elx = snd_mixer_elem_next(elx);
965 }
966 if (!m_mixer.elem)
967 {
968 snd_mixer_close(m_mixer.handle);
969 m_mixer.handle = nullptr;
970 LOG(VB_GENERAL, LOG_ERR, LOC + QString("no playback control %1 found on %2")
971 .arg(m_mixer.control, mixer_device_tag));
972 return false;
973 }
974 if ((snd_mixer_selem_get_playback_volume_range(m_mixer.elem,
975 &m_mixer.volmin,
976 &m_mixer.volmax) < 0))
977 {
978 snd_mixer_close(m_mixer.handle);
979 m_mixer.handle = nullptr;
980 LOG(VB_GENERAL, LOG_ERR, LOC + QString("failed to get volume range on %1/%2")
981 .arg(mixer_device_tag, m_mixer.control));
982 return false;
983 }
984
985 m_mixer.volrange = m_mixer.volmax - m_mixer.volmin;
986 LOG(VB_AUDIO, LOG_INFO, LOC + QString("mixer volume range on %1/%2 - min %3, max %4, range %5")
987 .arg(mixer_device_tag, m_mixer.control)
988 .arg(m_mixer.volmin).arg(m_mixer.volmax).arg(m_mixer.volrange));
989 LOG(VB_AUDIO, LOG_INFO, LOC + QString("%1/%2 set up successfully")
990 .arg(mixer_device_tag, m_mixer.control));
991
992 if (m_setInitialVol)
993 {
994 int initial_vol = 80;
995 if (m_mixer.control == "PCM")
996 initial_vol = gCoreContext->GetNumSetting("PCMMixerVolume", 80);
997 else
998 initial_vol = gCoreContext->GetNumSetting("MasterMixerVolume", 80);
999 for (int ch = 0; ch < m_channels; ++ch)
1000 SetVolumeChannel(ch, initial_vol);
1001 }
1002
1003 return true;
1004}
1005
1006QMap<QString, QString> *AudioOutputALSA::GetDevices(const char *type)
1007{
1008 auto *alsadevs = new QMap<QString, QString>();
1009 void **hints = nullptr;
1010 void **n = nullptr;
1011
1012 if (snd_device_name_hint(-1, type, &hints) < 0)
1013 return alsadevs;
1014 n = hints;
1015
1016 while (*n != nullptr)
1017 {
1018 QString name = snd_device_name_get_hint(*n, "NAME");
1019 QString desc = snd_device_name_get_hint(*n, "DESC");
1020 if (!name.isEmpty() && !desc.isEmpty() && (name != "null"))
1021 alsadevs->insert(name, desc);
1022 n++;
1023 }
1024 snd_device_name_free_hint(hints);
1025 // Work around ALSA bug < 1.0.22 ; where snd_device_name_hint can corrupt
1026 // global ALSA memory context
1027#if SND_LIB_MAJOR == 1
1028#if SND_LIB_MINOR == 0
1029#if SND_LIB_SUBMINOR < 22
1030 snd_config_update_free_global();
1031#endif
1032#endif
1033#endif
1034 return alsadevs;
1035}
int snd_pcm_uframes_t
int snd_pcm_t
#define LOC
#define CHECKERR(str)
static constexpr int FILTER_FLAGS
static constexpr uint8_t CHANNELS_MAX
#define AERROR(str)
static void tReorderSmpteToAlsa(AudioDataType *buf, uint frames, uint extrach)
static constexpr int OPEN_FLAGS
static constexpr uint8_t CHANNELS_MIN
static void ReorderSmpteToAlsa(void *buf, uint frames, AudioFormat format, uint extrach)
@ FORMAT_U8
@ FORMAT_S32
@ FORMAT_S24
@ FORMAT_NONE
@ FORMAT_FLT
@ FORMAT_S16
@ FORMAT_S24LSB
int SetParameters(snd_pcm_t *handle, snd_pcm_format_t format, uint channels, uint rate, uint buffer_time, uint period_time)
Set the various ALSA audio parameters.
int TryOpenDevice(int open_mode, bool try_ac3)
struct AudioOutputALSA::@7 m_mixer
static QMap< QString, QString > * GetDevices(const char *type)
bool OpenDevice(void) override
int GetBufferedOnSoundcard(void) const override
Return the size in bytes of frames currently in the audio buffer adjusted with the audio playback lat...
snd_mixer_t * handle
bool IncPreallocBufferSize(int requested, int buffer_time)
snd_mixer_elem_t * elem
void CloseDevice(void) override
~AudioOutputALSA() override
int GetVolumeChannel(int channel) const override
AudioOutputALSA(const AudioSettings &settings)
int GetPCMInfo(int &card, int &device, int &subdevice)
void WriteAudio(unsigned char *aubuf, int size) override
AudioOutputSettings * GetOutputSettings(bool passthrough) override
static QByteArray * GetELD(int card, int device, int subdevice)
void SetVolumeChannel(int channel, int volume) override
snd_pcm_t * m_pcmHandle
void KillAudio(void)
Kill the output thread and cleanup.
void Reconfigure(const AudioSettings &settings) override
(Re)Configure AudioOutputBase
QString m_passthruDevice
AudioFormat m_outputFormat
void InitSettings(const AudioSettings &settings)
static int FormatToBits(AudioFormat format)
void dispatchError(const QString &e)
const int & channels() const
Definition: audiooutput.h:254
bool m_init
If set to false, AudioOutput instance will not try to initially open the audio device.
Definition: audiosettings.h:85
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
bool GetBoolSetting(const QString &key, bool defaultval=false)
bool m_internalVol
Definition: volumebase.h:43
Definition: eldutils.h:39
unsigned int uint
Definition: compat.h:60
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
dictionary info
Definition: azlyrics.py:7