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