11#define LOC QString("ALSA: ")
22 { SND_PCM_NO_AUTO_RESAMPLE | SND_PCM_NO_AUTO_FORMAT | SND_PCM_NO_AUTO_CHANNELS };
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; } }
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"));
54 if (parts.length() == 1)
61 QString params = parts[1].trimmed();
67 else if (params[0] !=
'{')
77 QString oldparams = params.mid(1,params.size()-2);
79 .arg(parts[0], oldparams.trimmed(), iecarg2);
111 err = snd_pcm_open(&
m_pcmHandle, dev_ba.constData(),
112 SND_PCM_STREAM_PLAYBACK, open_mode);
121 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Auto setting passthrough failed (%1), defaulting "
122 "to main device").arg(snd_strerror(err)));
125 if (!try_ac3 || err < 0)
130 err = snd_pcm_open(&
m_pcmHandle, dev_ba.constData(),
131 SND_PCM_STREAM_PLAYBACK, open_mode);
151 snd_pcm_info_t *pcm_info =
nullptr;
153 snd_pcm_info_alloca(&pcm_info);
158 err = snd_pcm_info_get_card(pcm_info);
162 err = snd_pcm_info_get_device(pcm_info);
163 CHECKERR(
"snd_pcm_info_get_device");
166 err = snd_pcm_info_get_subdevice(pcm_info);
167 CHECKERR(
"snd_pcm_info_get_subdevice");
168 int tsubdevice = err;
188 const QString pf = QString(
"/proc/asound/card%1/pcm%2p/sub%3/prealloc")
189 .arg(card).arg(
device).arg(subdevice);
192 QFile mfile(pf +
"_max");
194 if (!pfile.open(QIODevice::ReadOnly))
196 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Error opening %1. Fix reading permissions.").arg(pf));
200 if (!mfile.open(QIODevice::ReadOnly))
202 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Error opening %1").arg(pf +
"_max"));
206 int cur = pfile.readAll().trimmed().toInt();
207 int max = mfile.readAll().trimmed().toInt();
209 int size = ((int)(cur * (
float)requested / (float)buffer_time)
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));
222 if (size > max || !size)
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));
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;
243 snd_ctl_elem_info_alloca(&
info);
244 snd_ctl_elem_id_alloca(&
id);
245 snd_ctl_elem_value_alloca(&
control);
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);
252 int err = snd_hctl_open(&hctl,
253 QString(
"hw:%1").arg(card).toLatin1().constData(),
257 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Control %1 open error: %2")
259 .arg(snd_strerror(err)));
262 err = snd_hctl_load(hctl);
265 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Control %1 load error: %2")
267 .arg(snd_strerror(err)));
271 snd_hctl_elem_t *
elem = snd_hctl_find_elem(hctl,
id);
274 err = snd_hctl_elem_info(
elem,
info);
277 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Control %1 snd_hctl_elem_info error: %2")
279 .arg(snd_strerror(err)));
280 snd_hctl_close(hctl);
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))
287 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Control %1 element info is not readable")
289 snd_hctl_close(hctl);
295 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Control %1 element read error: %2")
297 .arg(snd_strerror(err)));
298 snd_hctl_close(hctl);
301 if (
type != SND_CTL_ELEM_TYPE_BYTES)
303 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Control %1 element is of the wrong type")
305 snd_hctl_close(hctl);
308 result =
new QByteArray((
char *)snd_ctl_elem_value_get_bytes(
control),
311 snd_hctl_close(hctl);
317 snd_pcm_hw_params_t *params =
nullptr;
318 snd_pcm_format_t afmt = SND_PCM_FORMAT_UNKNOWN;
337 snd_pcm_hw_params_alloca(¶ms);
339 if (snd_pcm_hw_params_any(
m_pcmHandle, params) < 0)
352 AERROR(
"No playback configurations available");
358 LOG(VB_GENERAL, LOG_WARNING,
"Supported audio format detection will be inacurrate "
363 while (
int rate = settings->GetNextRate())
364 if(snd_pcm_hw_params_test_rate(
m_pcmHandle, params, rate, 0) >= 0)
365 settings->AddSupportedRate(rate);
367 while ((fmt = settings->GetNextFormat()))
371 case FORMAT_U8: afmt = SND_PCM_FORMAT_U8;
break;
372 case FORMAT_S16: afmt = SND_PCM_FORMAT_S16;
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;
380 if (snd_pcm_hw_params_test_format(
m_pcmHandle, params, afmt) >= 0)
381 settings->AddSupportedFormat(fmt);
386 settings->AddSupportedChannels(
channels);
397 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Successfully retrieved ELD data"));
398 settings->setELD(
eld);
404 LOG(VB_AUDIO, LOG_INFO,
LOC +
"Can't get card and device number");
412 QMap<QString, QString> *alsadevs =
GetDevices(
"pcm");
418 QString desc = alsadevs->value(real_device);
420 settings->setPassthrough(1);
421 if (real_device.contains(
"digital", Qt::CaseInsensitive) ||
422 desc.contains(
"digital", Qt::CaseInsensitive))
424 if (real_device.contains(
"iec958", Qt::CaseInsensitive))
426 if (real_device.contains(
"spdif", Qt::CaseInsensitive))
428 if (real_device.contains(
"hdmi", Qt::CaseInsensitive))
431 settings->setPassthrough(-1);
433 if (real_device.contains(
"pulse", Qt::CaseInsensitive) ||
434 desc.contains(
"pulse", Qt::CaseInsensitive))
436 if (real_device.contains(
"analog", Qt::CaseInsensitive) ||
437 desc.contains(
"analog", Qt::CaseInsensitive))
439 if (real_device.contains(
"surround", Qt::CaseInsensitive) ||
440 desc.contains(
"surround", Qt::CaseInsensitive))
443 settings->setPassthrough(0);
452 snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN;
468 case FORMAT_U8: format = SND_PCM_FORMAT_U8;
break;
469 case FORMAT_S16: format = SND_PCM_FORMAT_S16;
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;
477 QString message {QCoreApplication::translate(
"AudioOutputALSA",
"Unknown sample format: %1")
480 LOG(VB_GENERAL, LOG_ERR, message);
488 uint period_time = 4;
491 buffer_time, period_time);
494 AERROR(
"Unable to set ALSA parameters");
500 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Unable to open audio mixer. Volume control disabled");
509 snd_mixer_close(
m_mixer.handle);
519template <
class AudioDataType>
524 AudioDataType tmpLFE;
525 AudioDataType *buf2 =
nullptr;
527 for (
uint i = 0; i < frames; i++)
529 buf = buf2 = buf + 2;
554 uchar *tmpbuf = aubuf;
559 QString message {
"WriteAudio() called with pcm_handle == nullptr!"};
561 LOG(VB_GENERAL, LOG_ERR, message);
571 LOG(VB_AUDIO | VB_TIMESTAMP, LOG_INFO,
572 QString(
"WriteAudio: Preparing %1 bytes (%2 frames)")
573 .arg(size).arg(frames));
577 int lw = snd_pcm_writei(
m_pcmHandle, tmpbuf, frames);
581 if ((
uint)lw < frames)
582 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"WriteAudio: short write %1 bytes (ok)")
595 if (snd_pcm_state(
m_pcmHandle) == SND_PCM_STATE_XRUN)
597 LOG(VB_AUDIO, LOG_INFO,
LOC +
"WriteAudio: buffer underrun");
601 AERROR(
"WriteAudio: unable to recover from xrun");
609 LOG(VB_AUDIO, LOG_INFO,
LOC +
"WriteAudio: device is suspended");
610 while ((err = snd_pcm_resume(
m_pcmHandle)) == -EAGAIN)
615 LOG(VB_GENERAL, LOG_ERR,
LOC +
"WriteAudio: resume failed");
619 AERROR(
"WriteAudio: unable to recover from suspend");
628 QString message {QString(
"WriteAudio: device is in a bad state (state = %1)")
631 LOG(VB_GENERAL, LOG_ERR, message);
636 AERROR(QString(
"WriteAudio: Write failed, state: %1, err")
647 LOG(VB_GENERAL, LOG_ERR,
LOC +
"getBufferedOnSoundcard() called with pcm_handle == nullptr!");
651 snd_pcm_sframes_t delay = 0;
673 snd_pcm_hw_params_t *params =
nullptr;
674 snd_pcm_sw_params_t *swparams =
nullptr;
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)
689 QString message {QCoreApplication::translate(
"AudioOutputALSA",
690 "SetParameters() called with handle == nullptr!")};
692 LOG(VB_GENERAL, LOG_ERR, message);
696 snd_pcm_hw_params_alloca(¶ms);
697 snd_pcm_sw_params_alloca(&swparams);
700 int err = snd_pcm_hw_params_any(
handle, params);
701 CHECKERR(
"No playback configurations available");
704 err = snd_pcm_hw_params_set_access(
handle, params,
705 SND_PCM_ACCESS_RW_INTERLEAVED);
706 CHECKERR(QString(
"Interleaved RW audio not available"));
709 err = snd_pcm_hw_params_set_format(
handle, params, format);
710 CHECKERR(QString(
"Sample format %1 not available").arg(format));
719 err = snd_pcm_hw_params_set_rate_resample(
handle, params, 1);
720 CHECKERR(QString(
"Resampling setup failed").arg(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));
728 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Rate doesn't match (requested %1Hz, got %2Hz)")
729 .arg(rate).arg(err));
735 err = snd_pcm_hw_params_set_rate(
handle, params, rate, 0);
736 CHECKERR(QString(
"Samplerate %1 Hz not available").arg(rate));
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));
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);
759 uint buftmp = buffer_time;
762 err = snd_pcm_hw_params_set_buffer_time_near(
handle, params,
771 if ((buffer_time <= 100000) ||
772 (attempt > 0 && buffer_time == buftmp))
774 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Couldn't set buffer time, giving up");
777 AERROR(QString(
"Unable to set buffer time to %1us, retrying")
779 buffer_time -= 100000;
782 err = snd_pcm_hw_params_set_buffer_time_near(
handle, params,
789 if (buffer_time * 1.10F < (
float)original_buffer_time)
791 LOG(VB_GENERAL, LOG_WARNING,
LOC + QString(
"Requested %1us got %2 buffer time")
792 .arg(original_buffer_time).arg(buffer_time));
800 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"Buffer time = %1 us").arg(buffer_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));
809 err = snd_pcm_hw_params(
handle, params);
810 CHECKERR(
"Unable to set hw params for playback");
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));
822 err = snd_pcm_sw_params_current(
handle, swparams);
823 CHECKERR(
"Unable to get current swparams");
826 err = snd_pcm_sw_params_set_start_threshold(
handle, swparams, period_size);
827 CHECKERR(
"Unable to set start threshold");
830 err = snd_pcm_sw_params_set_avail_min(
handle, swparams, period_size);
831 CHECKERR(
"Unable to set avail min");
834 err = snd_pcm_sw_params(
handle, swparams);
835 CHECKERR(
"Unable to set sw params");
837 err = snd_pcm_prepare(
handle);
838 CHECKERR(
"Unable to prepare the PCM");
850 auto chan = (snd_mixer_selem_channel_id_t) channel;
851 if (!snd_mixer_selem_has_playback_channel(
m_mixer.elem, chan))
855 int chk = snd_mixer_selem_get_playback_volume(
m_mixer.elem, chan,
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)));
865 retvol = (
m_mixer.volrange != 0L)
866 ? ((mixervol -
m_mixer.volmin) * 100.0F /
m_mixer.volrange) + 0.5F
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));
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);
885 auto chan = (snd_mixer_selem_channel_id_t) channel;
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));
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));
893 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"channel %1 volume set %2 => %3")
894 .arg(channel).arg(volume).arg(mixervol));
901 LOG(VB_GENERAL, LOG_ERR,
LOC +
"mixer setup without a pcm");
906 if (
m_mixer.device.toLower() ==
"software")
911 QString mixer_device_tag = QString(
"mixer device %1").arg(
m_mixer.device);
913 int chk = snd_mixer_open(&
m_mixer.handle, 0);
916 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"failed to open mixer device %1: %2")
917 .arg(mixer_device_tag, snd_strerror(chk)));
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};
925 chk = snd_mixer_selem_register(
m_mixer.handle, ®opts,
nullptr);
928 snd_mixer_close(
m_mixer.handle);
930 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"failed to register %1: %2")
931 .arg(mixer_device_tag, snd_strerror(chk)));
935 chk = snd_mixer_load(
m_mixer.handle);
938 snd_mixer_close(
m_mixer.handle);
940 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"failed to load %1: %2")
941 .arg(mixer_device_tag, snd_strerror(chk)));
946 uint elcount = snd_mixer_get_count(
m_mixer.handle);
947 snd_mixer_elem_t* elx = snd_mixer_first_elem(
m_mixer.handle);
949 for (
uint ctr = 0; elx !=
nullptr && ctr < elcount; ctr++)
951 QString
tmp = QString(snd_mixer_selem_get_name(elx));
953 !snd_mixer_selem_is_enumerated(elx) &&
954 snd_mixer_selem_has_playback_volume(elx) &&
955 snd_mixer_selem_is_active(elx))
958 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"found playback control %1 on %2")
959 .arg(
m_mixer.control, mixer_device_tag));
962 elx = snd_mixer_elem_next(elx);
966 snd_mixer_close(
m_mixer.handle);
968 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"no playback control %1 found on %2")
969 .arg(
m_mixer.control, mixer_device_tag));
972 if ((snd_mixer_selem_get_playback_volume_range(
m_mixer.elem,
976 snd_mixer_close(
m_mixer.handle);
978 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"failed to get volume range on %1/%2")
979 .arg(mixer_device_tag,
m_mixer.control));
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)
987 LOG(VB_AUDIO, LOG_INFO,
LOC + QString(
"%1/%2 set up successfully")
988 .arg(mixer_device_tag,
m_mixer.control));
992 int initial_vol = 80;
1006 auto *alsadevs =
new QMap<QString, QString>();
1007 void **hints =
nullptr;
1010 if (snd_device_name_hint(-1,
type, &hints) < 0)
1014 while (*n !=
nullptr)
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);
1022 snd_device_name_free_hint(hints);
1025#if SND_LIB_MAJOR == 1
1026#if SND_LIB_MINOR == 0
1027#if SND_LIB_SUBMINOR < 22
1028 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.
static void usleep(std::chrono::microseconds time)
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_)