aboutsummaryrefslogtreecommitdiff
path: root/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp')
-rw-r--r--xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp756
1 files changed, 756 insertions, 0 deletions
diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp
new file mode 100644
index 0000000000..7c3aa21c79
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp
@@ -0,0 +1,756 @@
+/*
+ * Copyright (C) 2010-2012 Team XBMC
+ * http://www.xbmc.org
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XBMC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+#include "system.h"
+#ifdef HAS_ALSA
+
+#include <stdint.h>
+#include <limits.h>
+#include <sstream>
+
+#include "AESinkALSA.h"
+#include "Utils/AEUtil.h"
+#include "Utils/AEELDParser.h"
+#include "utils/StdString.h"
+#include "utils/log.h"
+#include "utils/MathUtils.h"
+#include "threads/SingleLock.h"
+#include "settings/GUISettings.h"
+
+#define ALSA_OPTIONS (SND_PCM_NONBLOCK | SND_PCM_NO_AUTO_FORMAT | SND_PCM_NO_AUTO_RESAMPLE)
+#define ALSA_PERIODS 16
+
+#define ALSA_MAX_CHANNELS 16
+static enum AEChannel ALSAChannelMap[ALSA_MAX_CHANNELS + 1] = {
+ AE_CH_FL , AE_CH_FR , AE_CH_BL , AE_CH_BR , AE_CH_FC , AE_CH_LFE , AE_CH_SL , AE_CH_SR ,
+ AE_CH_UNKNOWN1, AE_CH_UNKNOWN2, AE_CH_UNKNOWN3, AE_CH_UNKNOWN4, AE_CH_UNKNOWN5, AE_CH_UNKNOWN6, AE_CH_UNKNOWN7, AE_CH_UNKNOWN8, /* for p16v devices */
+ AE_CH_NULL
+};
+
+static unsigned int ALSASampleRateList[] =
+{
+ 5512,
+ 8000,
+ 11025,
+ 16000,
+ 22050,
+ 32000,
+ 44100,
+ 48000,
+ 64000,
+ 88200,
+ 96000,
+ 176400,
+ 192000,
+ 384000,
+ 0
+};
+
+CAESinkALSA::CAESinkALSA() :
+ m_pcm(NULL)
+{
+ /* ensure that ALSA has been initialized */
+ if (!snd_config)
+ snd_config_update();
+}
+
+CAESinkALSA::~CAESinkALSA()
+{
+ Deinitialize();
+}
+
+inline CAEChannelInfo CAESinkALSA::GetChannelLayout(AEAudioFormat format)
+{
+ unsigned int count = 0;
+
+ if (format.m_dataFormat == AE_FMT_AC3 ||
+ format.m_dataFormat == AE_FMT_DTS ||
+ format.m_dataFormat == AE_FMT_EAC3)
+ count = 2;
+ else if (format.m_dataFormat == AE_FMT_TRUEHD ||
+ format.m_dataFormat == AE_FMT_DTSHD)
+ count = 8;
+ else
+ {
+ for (unsigned int c = 0; c < 8; ++c)
+ for (unsigned int i = 0; i < format.m_channelLayout.Count(); ++i)
+ if (format.m_channelLayout[i] == ALSAChannelMap[c])
+ {
+ count = c + 1;
+ break;
+ }
+ }
+
+ CAEChannelInfo info;
+ for (unsigned int i = 0; i < count; ++i)
+ info += ALSAChannelMap[i];
+
+ return info;
+}
+
+void CAESinkALSA::GetPassthroughDevice(AEAudioFormat format, std::string& device)
+{
+ device += ",AES0=0x06,AES1=0x82,AES2=0x00";
+ if (format.m_sampleRate == 192000) device += ",AES3=0x0e";
+ else if (format.m_sampleRate == 176400) device += ",AES3=0x0c";
+ else if (format.m_sampleRate == 96000) device += ",AES3=0x0a";
+ else if (format.m_sampleRate == 88200) device += ",AES3=0x08";
+ else if (format.m_sampleRate == 48000) device += ",AES3=0x02";
+ else if (format.m_sampleRate == 44100) device += ",AES3=0x00";
+ else if (format.m_sampleRate == 32000) device += ",AES3=0x03";
+ else device += ",AES3=0x01";
+}
+
+bool CAESinkALSA::Initialize(AEAudioFormat &format, std::string &device)
+{
+ m_initDevice = device;
+ m_initFormat = format;
+
+ /* if we are raw, correct the data format */
+ if (AE_IS_RAW(format.m_dataFormat))
+ {
+ m_channelLayout = GetChannelLayout(format);
+ format.m_dataFormat = AE_FMT_S16NE;
+ m_passthrough = true;
+ }
+ else
+ {
+ m_channelLayout = GetChannelLayout(format);
+ m_passthrough = false;
+ }
+
+ if (m_channelLayout.Count() == 0)
+ {
+ CLog::Log(LOGERROR, "CAESinkALSA::Initialize - Unable to open the requested channel layout");
+ return false;
+ }
+
+ format.m_channelLayout = m_channelLayout;
+
+ /* if passthrough we need the additional AES flags */
+ if (m_passthrough)
+ GetPassthroughDevice(format, device);
+
+ m_device = device;
+ CLog::Log(LOGINFO, "CAESinkALSA::Initialize - Attempting to open device %s", device.c_str());
+
+ /* get the sound config */
+ snd_config_t *config;
+ snd_config_copy(&config, snd_config);
+ int error;
+
+ error = snd_pcm_open_lconf(&m_pcm, device.c_str(), SND_PCM_STREAM_PLAYBACK, ALSA_OPTIONS, config);
+ if (error < 0)
+ {
+ CLog::Log(LOGERROR, "CAESinkALSA::Initialize - snd_pcm_open_lconf(%d) - %s", error, device.c_str());
+ snd_config_delete(config);
+ return false;
+ }
+
+ /* free the sound config */
+ snd_config_delete(config);
+
+ if (!InitializeHW(format) || !InitializeSW(format))
+ return false;
+
+ snd_pcm_nonblock(m_pcm, 1);
+ snd_pcm_prepare (m_pcm);
+
+ m_format = format;
+ m_formatSampleRateMul = 1.0 / (double)m_format.m_sampleRate;
+
+ return true;
+}
+
+bool CAESinkALSA::IsCompatible(const AEAudioFormat format, const std::string device)
+{
+ return (
+ /* compare against the requested format and the real format */
+ (m_initFormat.m_sampleRate == format.m_sampleRate || m_format.m_sampleRate == format.m_sampleRate ) &&
+ (m_initFormat.m_dataFormat == format.m_dataFormat || m_format.m_dataFormat == format.m_dataFormat ) &&
+ (m_initFormat.m_channelLayout == format.m_channelLayout || m_format.m_channelLayout == format.m_channelLayout) &&
+ (m_initDevice == device)
+ );
+}
+
+snd_pcm_format_t CAESinkALSA::AEFormatToALSAFormat(const enum AEDataFormat format)
+{
+ if (AE_IS_RAW(format))
+ return SND_PCM_FORMAT_S16_LE;
+
+ switch (format)
+ {
+ case AE_FMT_S8 : return SND_PCM_FORMAT_S8;
+ case AE_FMT_U8 : return SND_PCM_FORMAT_U8;
+ case AE_FMT_S16NE : return SND_PCM_FORMAT_S16;
+ case AE_FMT_S24NE4: return SND_PCM_FORMAT_S24;
+#ifdef __BIG_ENDIAN__
+ case AE_FMT_S24NE3: return SND_PCM_FORMAT_S24_3BE;
+#else
+ case AE_FMT_S24NE3: return SND_PCM_FORMAT_S24_3LE;
+#endif
+ case AE_FMT_S32NE : return SND_PCM_FORMAT_S32;
+ case AE_FMT_FLOAT : return SND_PCM_FORMAT_FLOAT;
+
+ default:
+ return SND_PCM_FORMAT_UNKNOWN;
+ }
+}
+
+bool CAESinkALSA::InitializeHW(AEAudioFormat &format)
+{
+ snd_pcm_hw_params_t *hw_params;
+
+ snd_pcm_hw_params_alloca(&hw_params);
+ memset(hw_params, 0, snd_pcm_hw_params_sizeof());
+
+ snd_pcm_hw_params_any(m_pcm, hw_params);
+ snd_pcm_hw_params_set_access(m_pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
+
+ unsigned int sampleRate = format.m_sampleRate;
+ unsigned int channelCount = format.m_channelLayout.Count();
+ snd_pcm_hw_params_set_rate_near (m_pcm, hw_params, &sampleRate, NULL);
+ snd_pcm_hw_params_set_channels_near(m_pcm, hw_params, &channelCount);
+
+ /* ensure we opened X channels or more */
+ if (format.m_channelLayout.Count() > channelCount)
+ {
+ CLog::Log(LOGERROR, "CAESinkALSA::InitializeHW - Unable to open the required number of channels");
+ return false;
+ }
+
+ /* update the channelLayout to what we managed to open */
+ format.m_channelLayout.Reset();
+ for (unsigned int i = 0; i < channelCount; ++i)
+ format.m_channelLayout += ALSAChannelMap[i];
+
+ snd_pcm_format_t fmt = AEFormatToALSAFormat(format.m_dataFormat);
+ if (fmt == SND_PCM_FORMAT_UNKNOWN)
+ {
+ /* if we dont support the requested format, fallback to float */
+ format.m_dataFormat = AE_FMT_FLOAT;
+ fmt = SND_PCM_FORMAT_FLOAT;
+ }
+
+ /* try the data format */
+ if (snd_pcm_hw_params_set_format(m_pcm, hw_params, fmt) < 0)
+ {
+ /* if the chosen format is not supported, try each one in decending order */
+ CLog::Log(LOGINFO, "CAESinkALSA::InitializeHW - Your hardware does not support %s, trying other formats", CAEUtil::DataFormatToStr(format.m_dataFormat));
+ for (enum AEDataFormat i = AE_FMT_MAX; i > AE_FMT_INVALID; i = (enum AEDataFormat)((int)i - 1))
+ {
+ if (AE_IS_RAW(i) || i == AE_FMT_MAX)
+ continue;
+ fmt = AEFormatToALSAFormat(i);
+
+ if (fmt == SND_PCM_FORMAT_UNKNOWN || snd_pcm_hw_params_set_format(m_pcm, hw_params, fmt) < 0)
+ {
+ fmt = SND_PCM_FORMAT_UNKNOWN;
+ continue;
+ }
+
+ int fmtBits = CAEUtil::DataFormatToBits(i);
+ int bits = snd_pcm_hw_params_get_sbits(hw_params);
+ if (bits != fmtBits)
+ {
+ /* if we opened in 32bit and only have 24bits, pack into 24 */
+ if (fmtBits == 32 && bits == 24)
+ i = AE_FMT_S24NE4;
+ else
+ continue;
+ }
+
+ /* record that the format fell back to X */
+ format.m_dataFormat = i;
+ CLog::Log(LOGINFO, "CAESinkALSA::InitializeHW - Using data format %s", CAEUtil::DataFormatToStr(format.m_dataFormat));
+ break;
+ }
+
+ /* if we failed to find a valid output format */
+ if (fmt == SND_PCM_FORMAT_UNKNOWN)
+ {
+ CLog::Log(LOGERROR, "CAESinkALSA::InitializeHW - Unable to find a suitable output format");
+ return false;
+ }
+ }
+
+ unsigned int periods;
+
+ snd_pcm_uframes_t periodSize, bufferSize;
+ snd_pcm_hw_params_get_buffer_size_max(hw_params, &bufferSize);
+
+ bufferSize = std::min(bufferSize, (snd_pcm_uframes_t)8192);
+ periodSize = bufferSize / ALSA_PERIODS;
+ periods = ALSA_PERIODS;
+
+ CLog::Log(LOGDEBUG, "CAESinkALSA::InitializeHW - Request: periodSize %lu, periods %u, bufferSize %lu", periodSize, periods, bufferSize);
+
+ /* work on a copy of the hw params */
+ snd_pcm_hw_params_t *hw_params_copy;
+ snd_pcm_hw_params_alloca(&hw_params_copy);
+
+ /* try to set the buffer size then the period size */
+ snd_pcm_hw_params_copy(hw_params_copy, hw_params);
+ snd_pcm_hw_params_set_buffer_size_near(m_pcm, hw_params_copy, &bufferSize);
+ snd_pcm_hw_params_set_period_size_near(m_pcm, hw_params_copy, &periodSize, NULL);
+ snd_pcm_hw_params_set_periods_near (m_pcm, hw_params_copy, &periods , NULL);
+ if (snd_pcm_hw_params(m_pcm, hw_params_copy) != 0)
+ {
+ /* try to set the period size then the buffer size */
+ snd_pcm_hw_params_copy(hw_params_copy, hw_params);
+ snd_pcm_hw_params_set_period_size_near(m_pcm, hw_params_copy, &periodSize, NULL);
+ snd_pcm_hw_params_set_buffer_size_near(m_pcm, hw_params_copy, &bufferSize);
+ snd_pcm_hw_params_set_periods_near (m_pcm, hw_params_copy, &periods , NULL);
+ if (snd_pcm_hw_params(m_pcm, hw_params_copy) != 0)
+ {
+ /* try to just set the buffer size */
+ snd_pcm_hw_params_copy(hw_params_copy, hw_params);
+ snd_pcm_hw_params_set_buffer_size_near(m_pcm, hw_params_copy, &bufferSize);
+ snd_pcm_hw_params_set_periods_near (m_pcm, hw_params_copy, &periods , NULL);
+ if (snd_pcm_hw_params(m_pcm, hw_params_copy) != 0)
+ {
+ /* try to just set the period size */
+ snd_pcm_hw_params_copy(hw_params_copy, hw_params);
+ snd_pcm_hw_params_set_period_size_near(m_pcm, hw_params_copy, &periodSize, NULL);
+ snd_pcm_hw_params_set_periods_near (m_pcm, hw_params_copy, &periods , NULL);
+ if (snd_pcm_hw_params(m_pcm, hw_params_copy) != 0)
+ {
+ CLog::Log(LOGERROR, "CAESinkALSA::InitializeHW - Failed to set the parameters");
+ return false;
+ }
+ }
+ }
+ }
+
+ snd_pcm_hw_params_get_period_size(hw_params_copy, &periodSize, NULL);
+ snd_pcm_hw_params_get_buffer_size(hw_params_copy, &bufferSize);
+
+
+ CLog::Log(LOGDEBUG, "CAESinkALSA::InitializeHW - Got: periodSize %lu, periods %u, bufferSize %lu", periodSize, periods, bufferSize);
+
+ /* set the format parameters */
+ format.m_sampleRate = sampleRate;
+ format.m_frames = periodSize;
+ format.m_frameSamples = periodSize * format.m_channelLayout.Count();
+ format.m_frameSize = snd_pcm_frames_to_bytes(m_pcm, 1);
+
+ m_bufferSize = (unsigned int)bufferSize;
+ m_timeout = std::ceil((double)(bufferSize * 1000) / (double)sampleRate);
+
+ CLog::Log(LOGDEBUG, "CAESinkALSA::InitializeHW - Setting timeout to %d ms", m_timeout);
+
+ return true;
+}
+
+bool CAESinkALSA::InitializeSW(AEAudioFormat &format)
+{
+ snd_pcm_sw_params_t *sw_params;
+ snd_pcm_uframes_t boundary;
+
+ snd_pcm_sw_params_alloca(&sw_params);
+ memset(sw_params, 0, snd_pcm_sw_params_sizeof());
+
+ snd_pcm_sw_params_current (m_pcm, sw_params);
+ snd_pcm_sw_params_set_start_threshold (m_pcm, sw_params, INT_MAX);
+ snd_pcm_sw_params_set_silence_threshold(m_pcm, sw_params, 0);
+ snd_pcm_sw_params_get_boundary (sw_params, &boundary);
+ snd_pcm_sw_params_set_silence_size (m_pcm, sw_params, boundary);
+ snd_pcm_sw_params_set_avail_min (m_pcm, sw_params, format.m_frames);
+
+ if (snd_pcm_sw_params(m_pcm, sw_params) < 0)
+ {
+ CLog::Log(LOGERROR, "CAESinkALSA::InitializeSW - Failed to set the parameters");
+ return false;
+ }
+
+ return true;
+}
+
+void CAESinkALSA::Deinitialize()
+{
+ Stop();
+
+ if (m_pcm)
+ {
+ snd_pcm_drop (m_pcm);
+ snd_pcm_close(m_pcm);
+ m_pcm = NULL;
+ }
+}
+
+void CAESinkALSA::Stop()
+{
+ if (!m_pcm)
+ return;
+ snd_pcm_drop(m_pcm);
+}
+
+double CAESinkALSA::GetDelay()
+{
+ if (!m_pcm)
+ return 0;
+ snd_pcm_sframes_t frames = 0;
+ snd_pcm_delay(m_pcm, &frames);
+
+ if (frames < 0)
+ {
+#if SND_LIB_VERSION >= 0x000901 /* snd_pcm_forward() exists since 0.9.0rc8 */
+ snd_pcm_forward(m_pcm, -frames);
+#endif
+ frames = 0;
+ }
+
+ return (double)frames * m_formatSampleRateMul;
+}
+
+double CAESinkALSA::GetCacheTime()
+{
+ if (!m_pcm)
+ return 0.0;
+
+ int space = snd_pcm_avail_update(m_pcm);
+ if (space == 0)
+ {
+ snd_pcm_state_t state = snd_pcm_state(m_pcm);
+ if (state < 0)
+ {
+ HandleError("snd_pcm_state", state);
+ space = m_bufferSize;
+ }
+
+ if (state != SND_PCM_STATE_RUNNING && state != SND_PCM_STATE_PREPARED)
+ space = m_bufferSize;
+ }
+
+ return (double)(m_bufferSize - space) * m_formatSampleRateMul;
+}
+
+double CAESinkALSA::GetCacheTotal()
+{
+ return (double)m_bufferSize * m_formatSampleRateMul;
+}
+
+unsigned int CAESinkALSA::AddPackets(uint8_t *data, unsigned int frames)
+{
+ if (!m_pcm)
+ return 0;
+
+ if (snd_pcm_state(m_pcm) == SND_PCM_STATE_PREPARED)
+ snd_pcm_start(m_pcm);
+
+ int ret;
+
+ ret = snd_pcm_avail(m_pcm);
+ if (ret < 0)
+ {
+ HandleError("snd_pcm_avail", ret);
+ ret = 0;
+ }
+
+ if ((unsigned int)ret < frames);
+ {
+ ret = snd_pcm_wait(m_pcm, m_timeout);
+ if (ret < 0)
+ HandleError("snd_pcm_wait", ret);
+ }
+
+ ret = snd_pcm_writei(m_pcm, (void*)data, frames);
+ if (ret < 0)
+ {
+ HandleError("snd_pcm_writei(1)", ret);
+ ret = snd_pcm_writei(m_pcm, (void*)data, frames);
+ if (ret < 0)
+ {
+ HandleError("snd_pcm_writei(2)", ret);
+ ret = 0;
+ }
+ }
+
+ return ret;
+}
+
+void CAESinkALSA::HandleError(const char* name, int err)
+{
+ switch(err)
+ {
+ case -EPIPE:
+ CLog::Log(LOGERROR, "CAESinkALSA::HandleError(%s) - underrun", name);
+ if ((err = snd_pcm_prepare(m_pcm)) < 0)
+ CLog::Log(LOGERROR, "CAESinkALSA::HandleError(%s) - snd_pcm_prepare returned %d (%s)", name, err, snd_strerror(err));
+ break;
+
+ case -ESTRPIPE:
+ CLog::Log(LOGINFO, "CAESinkALSA::HandleError(%s) - Resuming after suspend", name);
+
+ /* try to resume the stream */
+ while((err = snd_pcm_resume(m_pcm)) == -EAGAIN)
+ Sleep(1);
+
+ /* if the hardware doesnt support resume, prepare the stream */
+ if (err == -ENOSYS)
+ if ((err = snd_pcm_prepare(m_pcm)) < 0)
+ CLog::Log(LOGERROR, "CAESinkALSA::HandleError(%s) - snd_pcm_prepare returned %d (%s)", name, err, snd_strerror(err));
+ break;
+
+ default:
+ CLog::Log(LOGERROR, "CAESinkALSA::HandleError(%s) - snd_pcm_writei returned %d (%s)", name, err, snd_strerror(err));
+ break;
+ }
+}
+
+void CAESinkALSA::Drain()
+{
+ if (!m_pcm)
+ return;
+
+ snd_pcm_nonblock(m_pcm, 0);
+ snd_pcm_drain(m_pcm);
+ snd_pcm_nonblock(m_pcm, 1);
+}
+
+void CAESinkALSA::EnumerateDevicesEx(AEDeviceInfoList &list)
+{
+ /* ensure that ALSA has been initialized */
+ if(!snd_config)
+ snd_config_update();
+
+ snd_ctl_t *ctlhandle;
+ snd_pcm_t *pcmhandle;
+
+ snd_ctl_card_info_t *ctlinfo;
+ snd_ctl_card_info_alloca(&ctlinfo);
+ memset(ctlinfo, 0, snd_ctl_card_info_sizeof());
+
+ snd_pcm_hw_params_t *hwparams;
+ snd_pcm_hw_params_alloca(&hwparams);
+ memset(hwparams, 0, snd_pcm_hw_params_sizeof());
+
+ snd_pcm_info_t *pcminfo;
+ snd_pcm_info_alloca(&pcminfo);
+ memset(pcminfo, 0, snd_pcm_info_sizeof());
+
+ /* get the sound config */
+ snd_config_t *config;
+ snd_config_copy(&config, snd_config);
+
+ std::string strHwName;
+ int n_cards = -1;
+ while (snd_card_next(&n_cards) == 0 && n_cards != -1)
+ {
+ std::stringstream sstr;
+ sstr << "hw:" << n_cards;
+ std::string strHwName = sstr.str();
+
+ if (snd_ctl_open_lconf(&ctlhandle, strHwName.c_str(), 0, config) != 0)
+ {
+ CLog::Log(LOGDEBUG, "CAESinkALSA::EnumerateDevicesEx - Unable to open control for device %s", strHwName.c_str());
+ continue;
+ }
+
+ if (snd_ctl_card_info(ctlhandle, ctlinfo) != 0)
+ {
+ CLog::Log(LOGDEBUG, "CAESinkALSA::EnumerateDevicesEx - Unable to get card control info for device %s", strHwName.c_str());
+ snd_ctl_close(ctlhandle);
+ continue;
+ }
+
+ snd_hctl_t *hctl;
+ if (snd_hctl_open_ctl(&hctl, ctlhandle) != 0)
+ hctl = NULL;
+ snd_hctl_load(hctl);
+
+ int pcm_index = 0;
+ int iec958_index = 0;
+ int hdmi_index = 0;
+
+ int dev = -1;
+ while (snd_ctl_pcm_next_device(ctlhandle, &dev) == 0 && dev != -1)
+ {
+ snd_pcm_info_set_device (pcminfo, dev);
+ snd_pcm_info_set_subdevice(pcminfo, 0 );
+ snd_pcm_info_set_stream (pcminfo, SND_PCM_STREAM_PLAYBACK);
+
+ if (snd_ctl_pcm_info(ctlhandle, pcminfo) < 0)
+ {
+ CLog::Log(LOGDEBUG, "CAESinkALSA::EnumerateDevicesEx - Skipping device %s,%d as it does not have PCM playback ability", strHwName.c_str(), dev);
+ continue;
+ }
+
+ int dev_index;
+ sstr.str(std::string());
+ CAEDeviceInfo info;
+ std::string devname = snd_pcm_info_get_name(pcminfo);
+
+ if (devname.find("HDMI") != std::string::npos)
+ {
+ info.m_deviceType = AE_DEVTYPE_HDMI;
+ dev_index = hdmi_index++;
+ sstr << "hdmi";
+ }
+ else if (devname.find("Digital") != std::string::npos ||
+ devname.find("IEC958" ) != std::string::npos)
+ {
+ info.m_deviceType = AE_DEVTYPE_IEC958;
+ dev_index = iec958_index++;
+ sstr << "iec958";
+ }
+ else
+ {
+ info.m_deviceType = AE_DEVTYPE_PCM;
+ dev_index = pcm_index++;
+ sstr << "hw";
+ }
+
+ /* build the driver string to pass to ALSA */
+ sstr << ":CARD=" << snd_ctl_card_info_get_id(ctlinfo) << ",DEV=" << dev_index;
+ info.m_deviceName = sstr.str();
+
+ /* get the friendly display name*/
+ info.m_displayName = snd_ctl_card_info_get_name(ctlinfo);
+ info.m_displayNameExtra = devname;
+
+ /* see if we can get ELD for this device */
+ if (info.m_deviceType == AE_DEVTYPE_HDMI)
+ {
+ bool badHDMI = false;
+ if (hctl && !GetELD(hctl, dev, info, badHDMI))
+ CLog::Log(LOGDEBUG, "CAESinkALSA::EnumerateDevicesEx - Unable to obtain ELD information for device %s, make sure you have ALSA >= 1.0.25", info.m_deviceName.c_str());
+
+ if (badHDMI)
+ {
+ CLog::Log(LOGDEBUG, "CAESinkALSA::EnumerateDevicesEx - Skipping HDMI device %s as it has no ELD data", info.m_deviceName.c_str());
+ continue;
+ }
+ }
+
+ /* open the device for testing */
+ if (snd_pcm_open_lconf(&pcmhandle, info.m_deviceName.c_str(), SND_PCM_STREAM_PLAYBACK, 0, config) < 0)
+ {
+ CLog::Log(LOGINFO, "CAESinkALSA::EnumerateDevicesEx - Unable to open %s for capability detection", info.m_deviceName.c_str());
+ continue;
+ }
+
+ /* ensure we can get a playback configuration for the device */
+ if (snd_pcm_hw_params_any(pcmhandle, hwparams) < 0)
+ {
+ CLog::Log(LOGINFO, "CAESinkALSA::EnumerateDevicesEx - No playback configurations available for device %s", info.m_deviceName.c_str());
+ snd_pcm_close(pcmhandle);
+ continue;
+ }
+
+ /* detect the available sample rates */
+ for (unsigned int *rate = ALSASampleRateList; *rate != 0; ++rate)
+ if (snd_pcm_hw_params_test_rate(pcmhandle, hwparams, *rate, 0) >= 0)
+ info.m_sampleRates.push_back(*rate);
+
+ /* detect the channels available */
+ int channels = 0;
+ for (int i = 1; i <= ALSA_MAX_CHANNELS; ++i)
+ if (snd_pcm_hw_params_test_channels(pcmhandle, hwparams, i) >= 0)
+ channels = i;
+
+ CAEChannelInfo alsaChannels;
+ for (int i = 0; i < channels; ++i)
+ {
+ if (!info.m_channels.HasChannel(ALSAChannelMap[i]))
+ info.m_channels += ALSAChannelMap[i];
+ alsaChannels += ALSAChannelMap[i];
+ }
+
+ /* remove the channels from m_channels that we cant use */
+ info.m_channels.ResolveChannels(alsaChannels);
+
+ /* detect the PCM sample formats that are available */
+ for (enum AEDataFormat i = AE_FMT_MAX; i > AE_FMT_INVALID; i = (enum AEDataFormat)((int)i - 1))
+ {
+ if (AE_IS_RAW(i) || i == AE_FMT_MAX)
+ continue;
+ snd_pcm_format_t fmt = AEFormatToALSAFormat(i);
+ if (fmt == SND_PCM_FORMAT_UNKNOWN)
+ continue;
+
+ if (snd_pcm_hw_params_test_format(pcmhandle, hwparams, fmt) >= 0)
+ info.m_dataFormats.push_back(i);
+ }
+
+ snd_pcm_close(pcmhandle);
+ list.push_back(info);
+ }
+
+ /* snd_hctl_close also closes ctlhandle */
+ if (hctl)
+ snd_hctl_close(hctl);
+ else
+ snd_ctl_close(ctlhandle);
+ }
+}
+
+bool CAESinkALSA::GetELD(snd_hctl_t *hctl, int device, CAEDeviceInfo& info, bool& badHDMI)
+{
+ badHDMI = false;
+
+ snd_ctl_elem_id_t *id;
+ snd_ctl_elem_info_t *einfo;
+ snd_ctl_elem_value_t *control;
+ snd_hctl_elem_t *elem;
+
+ snd_ctl_elem_id_alloca(&id);
+ memset(id, 0, snd_ctl_elem_id_sizeof());
+
+ snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_PCM);
+ snd_ctl_elem_id_set_name (id, "ELD" );
+ snd_ctl_elem_id_set_device (id, device);
+ elem = snd_hctl_find_elem(hctl, id);
+ if (!elem)
+ return false;
+
+ snd_ctl_elem_info_alloca(&einfo);
+ memset(einfo, 0, snd_ctl_elem_info_sizeof());
+
+ if (snd_hctl_elem_info(elem, einfo) < 0)
+ return false;
+
+ if (!snd_ctl_elem_info_is_readable(einfo))
+ return false;
+
+ if (snd_ctl_elem_info_get_type(einfo) != SND_CTL_ELEM_TYPE_BYTES)
+ return false;
+
+ snd_ctl_elem_value_alloca(&control);
+ memset(control, 0, snd_ctl_elem_value_sizeof());
+
+ if (snd_hctl_elem_read(elem, control) < 0)
+ return false;
+
+ int dataLength = snd_ctl_elem_info_get_count(einfo);
+ /* if there is no ELD data, then its a bad HDMI device, either nothing attached OR an invalid nVidia HDMI device */
+ if (!dataLength)
+ badHDMI = true;
+ else
+ CAEELDParser::Parse(
+ (const uint8_t*)snd_ctl_elem_value_get_bytes(control),
+ dataLength,
+ info
+ );
+
+ info.m_deviceType = AE_DEVTYPE_HDMI;
+ return true;
+}
+#endif