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 Warn("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
384 for (uint channels = CHANNELS_MIN; channels <= CHANNELS_MAX; channels++)
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 Error(QObject::tr("Unknown sample format: %1").arg(m_outputFormat));
477 return false;
478 }
479
480 // buffer 0.5s worth of samples
481 uint buffer_time = gCoreContext->GetNumSetting("ALSABufferOverride", 500) * 1000;
482
483 uint period_time = 4; // aim for an interrupt every (1/4th of buffer_time)
484
486 buffer_time, period_time);
487 if (err < 0)
488 {
489 AERROR("Unable to set ALSA parameters");
490 CloseDevice();
491 return false;
492 }
493
494 if (m_internalVol && !OpenMixer())
495 LOG(VB_GENERAL, LOG_ERR, LOC + "Unable to open audio mixer. Volume control disabled");
496
497 // Device opened successfully
498 return true;
499}
500
502{
503 if (m_mixer.handle)
504 snd_mixer_close(m_mixer.handle);
505 m_mixer.handle = nullptr;
506 if (m_pcmHandle)
507 {
508 snd_pcm_drain(m_pcmHandle);
509 snd_pcm_close(m_pcmHandle);
510 m_pcmHandle = nullptr;
511 }
512}
513
514template <class AudioDataType>
515static inline void tReorderSmpteToAlsa(AudioDataType *buf, uint frames,
516 uint extrach)
517{
518 AudioDataType tmpC;
519 AudioDataType tmpLFE;
520 AudioDataType *buf2 = nullptr;
521
522 for (uint i = 0; i < frames; i++)
523 {
524 buf = buf2 = buf + 2;
525
526 tmpC = *buf++;
527 tmpLFE = *buf++;
528 *buf2++ = *buf++;
529 *buf2++ = *buf++;
530 *buf2++ = tmpC;
531 *buf2++ = tmpLFE;
532 buf += extrach;
533 }
534}
535
536static inline void ReorderSmpteToAlsa(void *buf, uint frames,
537 AudioFormat format, uint extrach)
538{
540 {
541 case 8: tReorderSmpteToAlsa((uchar *)buf, frames, extrach); break;
542 case 16: tReorderSmpteToAlsa((short *)buf, frames, extrach); break;
543 default: tReorderSmpteToAlsa((int *)buf, frames, extrach); break;
544 }
545}
546
547void AudioOutputALSA::WriteAudio(uchar *aubuf, int size)
548{
549 uchar *tmpbuf = aubuf;
550 uint frames = size / m_outputBytesPerFrame;
551
552 if (m_pcmHandle == nullptr)
553 {
554 Error("WriteAudio() called with pcm_handle == nullptr!");
555 return;
556 }
557
558 //Audio received is in SMPTE channel order, reorder to ALSA unless passthru
559 if (!m_passthru && (m_channels == 6 || m_channels == 8))
560 {
561 ReorderSmpteToAlsa(aubuf, frames, m_outputFormat, m_channels - 6);
562 }
563
564 LOG(VB_AUDIO | VB_TIMESTAMP, LOG_INFO,
565 QString("WriteAudio: Preparing %1 bytes (%2 frames)")
566 .arg(size).arg(frames));
567
568 while (frames > 0)
569 {
570 int lw = snd_pcm_writei(m_pcmHandle, tmpbuf, frames);
571
572 if (lw >= 0)
573 {
574 if ((uint)lw < frames)
575 LOG(VB_AUDIO, LOG_INFO, LOC + QString("WriteAudio: short write %1 bytes (ok)")
576 .arg(lw * m_outputBytesPerFrame));
577
578 frames -= lw;
579 tmpbuf += static_cast<ptrdiff_t>(lw) * m_outputBytesPerFrame; // bytes
580 continue;
581 }
582
583 int err = lw;
584
585 switch (err)
586 {
587 case -EPIPE:
588 if (snd_pcm_state(m_pcmHandle) == SND_PCM_STATE_XRUN)
589 {
590 LOG(VB_AUDIO, LOG_INFO, LOC + "WriteAudio: buffer underrun");
591 err = snd_pcm_prepare(m_pcmHandle);
592 if (err < 0)
593 {
594 AERROR("WriteAudio: unable to recover from xrun");
595 return;
596 }
597 }
598 break;
599
600#if ESTRPIPE != EPIPE
601 case -ESTRPIPE:
602 LOG(VB_AUDIO, LOG_INFO, LOC + "WriteAudio: device is suspended");
603 while ((err = snd_pcm_resume(m_pcmHandle)) == -EAGAIN)
604 usleep(200us);
605
606 if (err < 0)
607 {
608 LOG(VB_GENERAL, LOG_ERR, LOC + "WriteAudio: resume failed");
609 err = snd_pcm_prepare(m_pcmHandle);
610 if (err < 0)
611 {
612 AERROR("WriteAudio: unable to recover from suspend");
613 return;
614 }
615 }
616 break;
617#endif
618
619 case -EBADFD:
620 Error(
621 QString("WriteAudio: device is in a bad state (state = %1)")
622 .arg(snd_pcm_state(m_pcmHandle)));
623 return;
624
625 default:
626 AERROR(QString("WriteAudio: Write failed, state: %1, err")
627 .arg(snd_pcm_state(m_pcmHandle)));
628 return;
629 }
630 }
631}
632
634{
635 if (m_pcmHandle == nullptr)
636 {
637 LOG(VB_GENERAL, LOG_ERR, LOC + "getBufferedOnSoundcard() called with pcm_handle == nullptr!");
638 return 0;
639 }
640
641 snd_pcm_sframes_t delay = 0;
642
643 /* Delay is the total delay from writing to the pcm until the samples
644 hit the DAC - includes buffered samples and any fixed latencies */
645 if (snd_pcm_delay(m_pcmHandle, &delay) < 0)
646 return 0;
647
648 // BUG: calling snd_pcm_state causes noise and repeats on the Raspberry Pi
649 return delay * m_outputBytesPerFrame;
650}
651
659int AudioOutputALSA::SetParameters(snd_pcm_t *handle, snd_pcm_format_t format,
660 uint channels, uint rate, uint buffer_time,
661 uint period_time)
662{
663 snd_pcm_hw_params_t *params = nullptr;
664 snd_pcm_sw_params_t *swparams = nullptr;
665 snd_pcm_uframes_t period_size = 0;
666 snd_pcm_uframes_t period_size_min = 0;
667 snd_pcm_uframes_t period_size_max = 0;
668 snd_pcm_uframes_t buffer_size = 0;
669 snd_pcm_uframes_t buffer_size_min = 0;
670 snd_pcm_uframes_t buffer_size_max = 0;
671
672 LOG(VB_AUDIO, LOG_INFO, LOC + QString("SetParameters(format=%1, channels=%2, rate=%3, "
673 "buffer_time=%4, period_time=%5)")
674 .arg(format).arg(channels).arg(rate).arg(buffer_time)
675 .arg(period_time));
676
677 if (handle == nullptr)
678 {
679 Error(QObject::tr("SetParameters() called with handle == nullptr!"));
680 return -1;
681 }
682
683 snd_pcm_hw_params_alloca(&params);
684 snd_pcm_sw_params_alloca(&swparams);
685
686 /* choose all parameters */
687 int err = snd_pcm_hw_params_any(handle, params);
688 CHECKERR("No playback configurations available");
689
690 /* set the interleaved read/write format */
691 err = snd_pcm_hw_params_set_access(handle, params,
692 SND_PCM_ACCESS_RW_INTERLEAVED);
693 CHECKERR(QString("Interleaved RW audio not available"));
694
695 /* set the sample format */
696 err = snd_pcm_hw_params_set_format(handle, params, format);
697 CHECKERR(QString("Sample format %1 not available").arg(format));
698
699 /* set the count of channels */
700 err = snd_pcm_hw_params_set_channels(handle, params, channels);
701 CHECKERR(QString("Channels count %1 not available").arg(channels));
702
703 /* set the stream rate */
705 {
706 err = snd_pcm_hw_params_set_rate_resample(handle, params, 1);
707 CHECKERR(QString("Resampling setup failed").arg(rate));
708
709 uint rrate = rate;
710 err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, nullptr);
711 CHECKERR(QString("Rate %1Hz not available for playback: %s").arg(rate));
712
713 if (rrate != rate)
714 {
715 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Rate doesn't match (requested %1Hz, got %2Hz)")
716 .arg(rate).arg(err));
717 return err;
718 }
719 }
720 else
721 {
722 err = snd_pcm_hw_params_set_rate(handle, params, rate, 0);
723 CHECKERR(QString("Samplerate %1 Hz not available").arg(rate));
724 }
725
726 /* get the buffer parameters */
727 (void) snd_pcm_hw_params_get_buffer_size_min(params, &buffer_size_min);
728 (void) snd_pcm_hw_params_get_buffer_size_max(params, &buffer_size_max);
729 (void) snd_pcm_hw_params_get_period_size_min(params, &period_size_min, nullptr);
730 (void) snd_pcm_hw_params_get_period_size_max(params, &period_size_max, nullptr);
731 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Buffer size range from %1 to %2")
732 .arg(buffer_size_min)
733 .arg(buffer_size_max));
734 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Period size range from %1 to %2")
735 .arg(period_size_min)
736 .arg(period_size_max));
737
738 /* set the buffer time */
739 uint original_buffer_time = buffer_time;
740 bool canincrease = true;
741 err = snd_pcm_hw_params_set_buffer_time_near(handle, params,
742 &buffer_time, nullptr);
743 if (err < 0)
744 {
745 int dir = -1;
746 uint buftmp = buffer_time;
747 int attempt = 0;
748 // Try again with a fourth parameter
749 err = snd_pcm_hw_params_set_buffer_time_near(handle, params,
750 &buffer_time, &dir);
751 while (err < 0)
752 {
753 /*
754 * with some drivers, snd_pcm_hw_params_set_buffer_time_near
755 * only works once, if that's the case no point trying with
756 * different values
757 */
758 if ((buffer_time <= 100000) ||
759 (attempt > 0 && buffer_time == buftmp))
760 {
761 LOG(VB_GENERAL, LOG_ERR, LOC + "Couldn't set buffer time, giving up");
762 return err;
763 }
764 AERROR(QString("Unable to set buffer time to %1us, retrying")
765 .arg(buffer_time));
766 buffer_time -= 100000;
767 canincrease = false;
768 attempt++;
769 err = snd_pcm_hw_params_set_buffer_time_near(handle, params,
770 &buffer_time, &dir);
771 }
772 }
773
774 /* See if we need to increase the prealloc'd buffer size
775 If buffer_time is too small we could underrun - make 10% difference ok */
776 if (buffer_time * 1.10F < (float)original_buffer_time)
777 {
778 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Requested %1us got %2 buffer time")
779 .arg(original_buffer_time).arg(buffer_time));
780 // We need to increase preallocated buffer size in the driver
781 if (canincrease && m_pbufSize < 0)
782 {
783 IncPreallocBufferSize(original_buffer_time, buffer_time);
784 }
785 }
786
787 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Buffer time = %1 us").arg(buffer_time));
788
789 /* set the period time */
790 err = snd_pcm_hw_params_set_periods_near(handle, params,
791 &period_time, nullptr);
792 CHECKERR(QString("Unable to set period time %1").arg(period_time));
793 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Period time = %1 periods").arg(period_time));
794
795 /* write the parameters to device */
796 err = snd_pcm_hw_params(handle, params);
797 CHECKERR("Unable to set hw params for playback");
798
799 err = snd_pcm_get_params(handle, &buffer_size, &period_size);
800 CHECKERR("Unable to get PCM params");
801 LOG(VB_AUDIO, LOG_INFO, LOC + QString("Buffer size = %1 | Period size = %2")
802 .arg(buffer_size).arg(period_size));
803
804 /* set member variables */
806 m_fragmentSize = (period_size >> 1) * m_outputBytesPerFrame;
807
808 /* get the current swparams */
809 err = snd_pcm_sw_params_current(handle, swparams);
810 CHECKERR("Unable to get current swparams");
811
812 /* start the transfer after period_size */
813 err = snd_pcm_sw_params_set_start_threshold(handle, swparams, period_size);
814 CHECKERR("Unable to set start threshold");
815
816 /* allow the transfer when at least period_size samples can be processed */
817 err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_size);
818 CHECKERR("Unable to set avail min");
819
820 /* write the parameters to the playback device */
821 err = snd_pcm_sw_params(handle, swparams);
822 CHECKERR("Unable to set sw params");
823
824 err = snd_pcm_prepare(handle);
825 CHECKERR("Unable to prepare the PCM");
826
827 return 0;
828}
829
831{
832 int retvol = 0;
833
834 if (!m_mixer.elem)
835 return retvol;
836
837 auto chan = (snd_mixer_selem_channel_id_t) channel;
838 if (!snd_mixer_selem_has_playback_channel(m_mixer.elem, chan))
839 return retvol;
840
841 long mixervol = 0;
842 int chk = snd_mixer_selem_get_playback_volume(m_mixer.elem, chan,
843 &mixervol);
844 if (chk < 0)
845 {
846 LOG(VB_GENERAL, LOG_ERR, LOC + QString("failed to get channel %1 volume, mixer %2/%3: %4")
847 .arg(QString::number(channel), m_mixer.device,
848 m_mixer.control, snd_strerror(chk)));
849 }
850 else
851 {
852 retvol = (m_mixer.volrange != 0L)
853 ? ((mixervol - m_mixer.volmin) * 100.0F / m_mixer.volrange) + 0.5F
854 : 0;
855 retvol = std::max(retvol, 0);
856 retvol = std::min(retvol, 100);
857 LOG(VB_AUDIO, LOG_INFO, LOC + QString("get volume channel %1: %2")
858 .arg(channel).arg(retvol));
859 }
860 return retvol;
861}
862
863void AudioOutputALSA::SetVolumeChannel(int channel, int volume)
864{
865 if (!(m_internalVol && m_mixer.elem))
866 return;
867
868 long mixervol = ((int64_t(volume) * m_mixer.volrange) / 100) + m_mixer.volmin;
869 mixervol = std::max(mixervol, m_mixer.volmin);
870 mixervol = std::min(mixervol, m_mixer.volmax);
871
872 auto chan = (snd_mixer_selem_channel_id_t) channel;
873
874 if (snd_mixer_selem_has_playback_switch(m_mixer.elem))
875 snd_mixer_selem_set_playback_switch(m_mixer.elem, chan, static_cast<int>(volume > 0));
876
877 if (snd_mixer_selem_set_playback_volume(m_mixer.elem, chan, mixervol) < 0)
878 LOG(VB_GENERAL, LOG_ERR, LOC + QString("failed to set channel %1 volume").arg(channel));
879 else
880 LOG(VB_AUDIO, LOG_INFO, LOC + QString("channel %1 volume set %2 => %3")
881 .arg(channel).arg(volume).arg(mixervol));
882}
883
885{
886 if (!m_pcmHandle)
887 {
888 LOG(VB_GENERAL, LOG_ERR, LOC + "mixer setup without a pcm");
889 return false;
890 }
891 m_mixer.device = gCoreContext->GetSetting("MixerDevice", "default");
892 m_mixer.device = m_mixer.device.remove(QString("ALSA:"));
893 if (m_mixer.device.toLower() == "software")
894 return true;
895
896 m_mixer.control = gCoreContext->GetSetting("MixerControl", "PCM");
897
898 QString mixer_device_tag = QString("mixer device %1").arg(m_mixer.device);
899
900 int chk = snd_mixer_open(&m_mixer.handle, 0);
901 if (chk < 0)
902 {
903 LOG(VB_GENERAL, LOG_ERR, LOC + QString("failed to open mixer device %1: %2")
904 .arg(mixer_device_tag, snd_strerror(chk)));
905 return false;
906 }
907
908 QByteArray dev_ba = m_mixer.device.toLatin1();
909 struct snd_mixer_selem_regopt regopts =
910 {1, SND_MIXER_SABSTRACT_NONE, dev_ba.constData(), nullptr, nullptr};
911
912 chk = snd_mixer_selem_register(m_mixer.handle, &regopts, nullptr);
913 if (chk < 0)
914 {
915 snd_mixer_close(m_mixer.handle);
916 m_mixer.handle = nullptr;
917 LOG(VB_GENERAL, LOG_ERR, LOC + QString("failed to register %1: %2")
918 .arg(mixer_device_tag, snd_strerror(chk)));
919 return false;
920 }
921
922 chk = snd_mixer_load(m_mixer.handle);
923 if (chk < 0)
924 {
925 snd_mixer_close(m_mixer.handle);
926 m_mixer.handle = nullptr;
927 LOG(VB_GENERAL, LOG_ERR, LOC + QString("failed to load %1: %2")
928 .arg(mixer_device_tag, snd_strerror(chk)));
929 return false;
930 }
931
932 m_mixer.elem = nullptr;
933 uint elcount = snd_mixer_get_count(m_mixer.handle);
934 snd_mixer_elem_t* elx = snd_mixer_first_elem(m_mixer.handle);
935
936 for (uint ctr = 0; elx != nullptr && ctr < elcount; ctr++)
937 {
938 QString tmp = QString(snd_mixer_selem_get_name(elx));
939 if (m_mixer.control == tmp &&
940 !snd_mixer_selem_is_enumerated(elx) &&
941 snd_mixer_selem_has_playback_volume(elx) &&
942 snd_mixer_selem_is_active(elx))
943 {
944 m_mixer.elem = elx;
945 LOG(VB_AUDIO, LOG_INFO, LOC + QString("found playback control %1 on %2")
946 .arg(m_mixer.control, mixer_device_tag));
947 break;
948 }
949 elx = snd_mixer_elem_next(elx);
950 }
951 if (!m_mixer.elem)
952 {
953 snd_mixer_close(m_mixer.handle);
954 m_mixer.handle = nullptr;
955 LOG(VB_GENERAL, LOG_ERR, LOC + QString("no playback control %1 found on %2")
956 .arg(m_mixer.control, mixer_device_tag));
957 return false;
958 }
959 if ((snd_mixer_selem_get_playback_volume_range(m_mixer.elem,
960 &m_mixer.volmin,
961 &m_mixer.volmax) < 0))
962 {
963 snd_mixer_close(m_mixer.handle);
964 m_mixer.handle = nullptr;
965 LOG(VB_GENERAL, LOG_ERR, LOC + QString("failed to get volume range on %1/%2")
966 .arg(mixer_device_tag, m_mixer.control));
967 return false;
968 }
969
970 m_mixer.volrange = m_mixer.volmax - m_mixer.volmin;
971 LOG(VB_AUDIO, LOG_INFO, LOC + QString("mixer volume range on %1/%2 - min %3, max %4, range %5")
972 .arg(mixer_device_tag, m_mixer.control)
973 .arg(m_mixer.volmin).arg(m_mixer.volmax).arg(m_mixer.volrange));
974 LOG(VB_AUDIO, LOG_INFO, LOC + QString("%1/%2 set up successfully")
975 .arg(mixer_device_tag, m_mixer.control));
976
977 if (m_setInitialVol)
978 {
979 int initial_vol = 80;
980 if (m_mixer.control == "PCM")
981 initial_vol = gCoreContext->GetNumSetting("PCMMixerVolume", 80);
982 else
983 initial_vol = gCoreContext->GetNumSetting("MasterMixerVolume", 80);
984 for (int ch = 0; ch < m_channels; ++ch)
985 SetVolumeChannel(ch, initial_vol);
986 }
987
988 return true;
989}
990
991QMap<QString, QString> *AudioOutputALSA::GetDevices(const char *type)
992{
993 auto *alsadevs = new QMap<QString, QString>();
994 void **hints = nullptr;
995 void **n = nullptr;
996
997 if (snd_device_name_hint(-1, type, &hints) < 0)
998 return alsadevs;
999 n = hints;
1000
1001 while (*n != nullptr)
1002 {
1003 QString name = snd_device_name_get_hint(*n, "NAME");
1004 QString desc = snd_device_name_get_hint(*n, "DESC");
1005 if (!name.isEmpty() && !desc.isEmpty() && (name != "null"))
1006 alsadevs->insert(name, desc);
1007 n++;
1008 }
1009 snd_device_name_free_hint(hints);
1010 // Work around ALSA bug < 1.0.22 ; where snd_device_name_hint can corrupt
1011 // global ALSA memory context
1012#if SND_LIB_MAJOR == 1
1013#if SND_LIB_MINOR == 0
1014#if SND_LIB_SUBMINOR < 22
1015 snd_config_update_free_global();
1016#endif
1017#endif
1018#endif
1019 return alsadevs;
1020}
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::@0 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 Error(const QString &msg)
void Warn(const QString &msg)
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:38
unsigned int uint
Definition: freesurround.h:24
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