12#define LOC QString("ALSA: ")
23 { SND_PCM_NO_AUTO_RESAMPLE | SND_PCM_NO_AUTO_FORMAT | SND_PCM_NO_AUTO_CHANNELS };
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; } }
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"));
55 if (parts.length() == 1)
62 QString params = parts[1].trimmed();
68 else if (params[0] !=
'{')
78 QString oldparams = params.mid(1,params.size()-2);
80 .arg(parts[0], oldparams.trimmed(), iecarg2);
112 err = snd_pcm_open(&
m_pcmHandle, dev_ba.constData(),
113 SND_PCM_STREAM_PLAYBACK, open_mode);
122 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Auto setting passthrough failed (%1), defaulting "
123 "to main device").arg(snd_strerror(err)));
126 if (!try_ac3 || err < 0)
131 err = snd_pcm_open(&
m_pcmHandle, dev_ba.constData(),
132 SND_PCM_STREAM_PLAYBACK, open_mode);
152 snd_pcm_info_t *pcm_info =
nullptr;
154 snd_pcm_info_alloca(&pcm_info);
159 err = snd_pcm_info_get_card(pcm_info);
163 err = snd_pcm_info_get_device(pcm_info);
164 CHECKERR(
"snd_pcm_info_get_device");
167 err = snd_pcm_info_get_subdevice(pcm_info);
168 CHECKERR(
"snd_pcm_info_get_subdevice");
169 int tsubdevice = err;
189 const QString pf = QString(
"/proc/asound/card%1/pcm%2p/sub%3/prealloc")
190 .arg(card).arg(
device).arg(subdevice);
193 QFile mfile(pf +
"_max");
195 if (!pfile.open(QIODevice::ReadOnly))
197 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Error opening %1. Fix reading permissions.").arg(pf));
201 if (!mfile.open(QIODevice::ReadOnly))
203 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Error opening %1").arg(pf +
"_max"));
207 int cur = pfile.readAll().trimmed().toInt();
208 int max = mfile.readAll().trimmed().toInt();
210 int size = (((int)(cur * (
float)requested / (float)buffer_time)
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));
223 if (size > max || !size)
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));
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;
244 snd_ctl_elem_info_alloca(&
info);
245 snd_ctl_elem_id_alloca(&
id);
246 snd_ctl_elem_value_alloca(&
control);
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);
253 int err = snd_hctl_open(&hctl,
254 QString(
"hw:%1").arg(card).toLatin1().constData(),
258 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Control %1 open error: %2")
260 .arg(snd_strerror(err)));
263 err = snd_hctl_load(hctl);
266 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Control %1 load error: %2")
268 .arg(snd_strerror(err)));
272 snd_hctl_elem_t *
elem = snd_hctl_find_elem(hctl,
id);
275 err = snd_hctl_elem_info(
elem,
info);
278 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Control %1 snd_hctl_elem_info error: %2")
280 .arg(snd_strerror(err)));
281 snd_hctl_close(hctl);
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))
288 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Control %1 element info is not readable")
290 snd_hctl_close(hctl);
296 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Control %1 element read error: %2")
298 .arg(snd_strerror(err)));
299 snd_hctl_close(hctl);
302 if (
type != SND_CTL_ELEM_TYPE_BYTES)
304 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Control %1 element is of the wrong type")
306 snd_hctl_close(hctl);
309 result =
new QByteArray((
char *)snd_ctl_elem_value_get_bytes(
control),
312 snd_hctl_close(hctl);
318 snd_pcm_hw_params_t *params =
nullptr;
319 snd_pcm_format_t afmt = SND_PCM_FORMAT_UNKNOWN;
338 snd_pcm_hw_params_alloca(¶ms);
340 if (snd_pcm_hw_params_any(
m_pcmHandle, params) < 0)
353 AERROR(
"No playback configurations available");
359 LOG(VB_GENERAL, LOG_WARNING,
"Supported audio format detection will be inacurrate "
364 while (
int rate = settings->GetNextRate())
365 if(snd_pcm_hw_params_test_rate(
m_pcmHandle, params, rate, 0) >= 0)
366 settings->AddSupportedRate(rate);
368 while ((fmt = settings->GetNextFormat()))
372 case FORMAT_U8: afmt = SND_PCM_FORMAT_U8;
break;
373 case FORMAT_S16: afmt = SND_PCM_FORMAT_S16;
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;
381 if (snd_pcm_hw_params_test_format(
m_pcmHandle, params, afmt) >= 0)
382 settings->AddSupportedFormat(fmt);
387 settings->AddSupportedChannels(
channels);
398 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Successfully retrieved ELD data"));
399 settings->setELD(
eld);
405 LOG(VB_AUDIO, LOG_INFO,
LOC +
"Can't get card and device number");
413 QMap<QString, QString> *alsadevs =
GetDevices(
"pcm");
419 QString desc = alsadevs->value(real_device);
421 settings->setPassthrough(1);
422 if (real_device.contains(
"digital", Qt::CaseInsensitive) ||
423 desc.contains(
"digital", Qt::CaseInsensitive))
425 if (real_device.contains(
"iec958", Qt::CaseInsensitive))
427 if (real_device.contains(
"spdif", Qt::CaseInsensitive))
429 if (real_device.contains(
"hdmi", Qt::CaseInsensitive))
432 settings->setPassthrough(-1);
434 if (real_device.contains(
"pulse", Qt::CaseInsensitive) ||
435 desc.contains(
"pulse", Qt::CaseInsensitive))
437 if (real_device.contains(
"analog", Qt::CaseInsensitive) ||
438 desc.contains(
"analog", Qt::CaseInsensitive))
440 if (real_device.contains(
"surround", Qt::CaseInsensitive) ||
441 desc.contains(
"surround", Qt::CaseInsensitive))
444 settings->setPassthrough(0);
453 snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN;
469 case FORMAT_U8: format = SND_PCM_FORMAT_U8;
break;
470 case FORMAT_S16: format = SND_PCM_FORMAT_S16;
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;
478 QString message {QCoreApplication::translate(
"AudioOutputALSA",
"Unknown sample format: %1")
481 LOG(VB_GENERAL, LOG_ERR, message);
489 uint period_time = 4;
492 buffer_time, period_time);
495 AERROR(
"Unable to set ALSA parameters");
501 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Unable to open audio mixer. Volume control disabled");
510 snd_mixer_close(
m_mixer.handle);
520template <
class AudioDataType>
525 AudioDataType tmpLFE;
526 AudioDataType *buf2 =
nullptr;
528 for (
uint i = 0; i < frames; i++)
530 buf = buf2 = buf + 2;
555 uchar *tmpbuf = aubuf;
560 QString message {
"WriteAudio() called with pcm_handle == nullptr!"};
562 LOG(VB_GENERAL, LOG_ERR, message);
572 LOG(VB_AUDIO | VB_TIMESTAMP, LOG_INFO,
573 QString(
"WriteAudio: Preparing %1 bytes (%2 frames)")
574 .arg(size).arg(frames));
578 int lw = snd_pcm_writei(
m_pcmHandle, tmpbuf, frames);
582 if ((
uint)lw < frames)
583 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"WriteAudio: short write %1 bytes (ok)")
596 if (snd_pcm_state(
m_pcmHandle) == SND_PCM_STATE_XRUN)
598 LOG(VB_AUDIO, LOG_INFO,
LOC +
"WriteAudio: buffer underrun");
602 AERROR(
"WriteAudio: unable to recover from xrun");
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);
616 LOG(VB_GENERAL, LOG_ERR,
LOC +
"WriteAudio: resume failed");
620 AERROR(
"WriteAudio: unable to recover from suspend");
629 QString message {QString(
"WriteAudio: device is in a bad state (state = %1)")
632 LOG(VB_GENERAL, LOG_ERR, message);
637 AERROR(QString(
"WriteAudio: Write failed, state: %1, err")
648 LOG(VB_GENERAL, LOG_ERR,
LOC +
"getBufferedOnSoundcard() called with pcm_handle == nullptr!");
652 snd_pcm_sframes_t delay = 0;
674 snd_pcm_hw_params_t *params =
nullptr;
675 snd_pcm_sw_params_t *swparams =
nullptr;
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)
690 QString message {QCoreApplication::translate(
"AudioOutputALSA",
691 "SetParameters() called with handle == nullptr!")};
693 LOG(VB_GENERAL, LOG_ERR, message);
697 snd_pcm_hw_params_alloca(¶ms);
698 snd_pcm_sw_params_alloca(&swparams);
701 int err = snd_pcm_hw_params_any(
handle, params);
702 CHECKERR(
"No playback configurations available");
705 err = snd_pcm_hw_params_set_access(
handle, params,
706 SND_PCM_ACCESS_RW_INTERLEAVED);
707 CHECKERR(QString(
"Interleaved RW audio not available"));
710 err = snd_pcm_hw_params_set_format(
handle, params, format);
711 CHECKERR(QString(
"Sample format %1 not available").arg(format));
720 err = snd_pcm_hw_params_set_rate_resample(
handle, params, 1);
721 CHECKERR(QString(
"Resampling setup failed").arg(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));
729 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Rate doesn't match (requested %1Hz, got %2Hz)")
730 .arg(rate).arg(err));
736 err = snd_pcm_hw_params_set_rate(
handle, params, rate, 0);
737 CHECKERR(QString(
"Samplerate %1 Hz not available").arg(rate));
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));
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);
760 uint buftmp = buffer_time;
763 err = snd_pcm_hw_params_set_buffer_time_near(
handle, params,
772 if ((buffer_time <= 100000) ||
773 (attempt > 0 && buffer_time == buftmp))
775 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Couldn't set buffer time, giving up");
778 AERROR(QString(
"Unable to set buffer time to %1us, retrying")
780 buffer_time -= 100000;
783 err = snd_pcm_hw_params_set_buffer_time_near(
handle, params,
790 if (buffer_time * 1.10F < (
float)original_buffer_time)
792 LOG(VB_GENERAL, LOG_WARNING,
LOC + QString(
"Requested %1us got %2 buffer time")
793 .arg(original_buffer_time).arg(buffer_time));
801 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Buffer time = %1 us").arg(buffer_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));
810 err = snd_pcm_hw_params(
handle, params);
811 CHECKERR(
"Unable to set hw params for playback");
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));
823 err = snd_pcm_sw_params_current(
handle, swparams);
824 CHECKERR(
"Unable to get current swparams");
827 err = snd_pcm_sw_params_set_start_threshold(
handle, swparams, period_size);
828 CHECKERR(
"Unable to set start threshold");
831 err = snd_pcm_sw_params_set_avail_min(
handle, swparams, period_size);
832 CHECKERR(
"Unable to set avail min");
835 err = snd_pcm_sw_params(
handle, swparams);
836 CHECKERR(
"Unable to set sw params");
838 err = snd_pcm_prepare(
handle);
839 CHECKERR(
"Unable to prepare the PCM");
851 auto chan = (snd_mixer_selem_channel_id_t) channel;
852 if (!snd_mixer_selem_has_playback_channel(
m_mixer.elem, chan))
856 int chk = snd_mixer_selem_get_playback_volume(
m_mixer.elem, chan,
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)));
866 retvol = (
m_mixer.volrange != 0L)
867 ? ((mixervol -
m_mixer.volmin) * 100.0F /
m_mixer.volrange) + 0.5F
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));
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);
886 auto chan = (snd_mixer_selem_channel_id_t) channel;
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));
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));
894 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"channel %1 volume set %2 => %3")
895 .arg(channel).arg(volume).arg(mixervol));
902 LOG(VB_GENERAL, LOG_ERR,
LOC +
"mixer setup without a pcm");
907 if (
m_mixer.device.toLower() ==
"software")
912 QString mixer_device_tag = QString(
"mixer device %1").arg(
m_mixer.device);
914 int chk = snd_mixer_open(&
m_mixer.handle, 0);
917 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"failed to open mixer device %1: %2")
918 .arg(mixer_device_tag, snd_strerror(chk)));
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};
927 chk = snd_mixer_selem_register(
m_mixer.handle, ®opts,
nullptr);
930 snd_mixer_close(
m_mixer.handle);
932 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"failed to register %1: %2")
933 .arg(mixer_device_tag, snd_strerror(chk)));
937 chk = snd_mixer_load(
m_mixer.handle);
940 snd_mixer_close(
m_mixer.handle);
942 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"failed to load %1: %2")
943 .arg(mixer_device_tag, snd_strerror(chk)));
948 uint elcount = snd_mixer_get_count(
m_mixer.handle);
949 snd_mixer_elem_t* elx = snd_mixer_first_elem(
m_mixer.handle);
951 for (
uint ctr = 0; elx !=
nullptr && ctr < elcount; ctr++)
953 QString tmp = QString(snd_mixer_selem_get_name(elx));
955 !snd_mixer_selem_is_enumerated(elx) &&
956 snd_mixer_selem_has_playback_volume(elx) &&
957 snd_mixer_selem_is_active(elx))
960 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"found playback control %1 on %2")
961 .arg(
m_mixer.control, mixer_device_tag));
964 elx = snd_mixer_elem_next(elx);
968 snd_mixer_close(
m_mixer.handle);
970 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"no playback control %1 found on %2")
971 .arg(
m_mixer.control, mixer_device_tag));
974 if ((snd_mixer_selem_get_playback_volume_range(
m_mixer.elem,
978 snd_mixer_close(
m_mixer.handle);
980 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"failed to get volume range on %1/%2")
981 .arg(mixer_device_tag,
m_mixer.control));
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)
989 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"%1/%2 set up successfully")
990 .arg(mixer_device_tag,
m_mixer.control));
994 int initial_vol = 80;
1008 auto *alsadevs =
new QMap<QString, QString>();
1009 void **hints =
nullptr;
1012 if (snd_device_name_hint(-1,
type, &hints) < 0)
1016 while (*n !=
nullptr)
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);
1024 snd_device_name_free_hint(hints);
1027#if SND_LIB_MAJOR == 1
1028#if SND_LIB_MINOR == 0
1029#if SND_LIB_SUBMINOR < 22
1030 snd_config_update_free_global();
static constexpr int FILTER_FLAGS
static constexpr uint8_t CHANNELS_MAX
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)
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...
bool IncPreallocBufferSize(int requested, int buffer_time)
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
void KillAudio(void)
Kill the output thread and cleanup.
void Reconfigure(const AudioSettings &settings) override
(Re)Configure AudioOutputBase
AudioFormat m_outputFormat
int m_outputBytesPerFrame
long m_soundcardBufferSize
void InitSettings(const AudioSettings &settings)
static int FormatToBits(AudioFormat format)
void dispatchError(const QString &e)
const int & channels() const
bool m_init
If set to false, AudioOutput instance will not try to initially open the audio device.
QString GetSetting(const QString &key, const QString &defaultval="")
int GetNumSetting(const QString &key, int defaultval=0)
bool GetBoolSetting(const QString &key, bool defaultval=false)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)