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; }
/* Work around PulseAudio ALSA plugin bug where the PA server forces a higher than requested latency, but the plugin does not update its (and ALSA's) internal state to reflect that, leading to an immediate underrun situation. Inspired by WINE's make_handle_underrun_config. Reference: http://mailman.alsa-project.org/pipermail/alsa-devel/2012-July/05 */ static snd_config_t * init_local_config_with_workaround(char const * pcm_name) { int r; snd_config_t * lconf; snd_config_t * pcm_node; snd_config_t * node; char const * string; char node_name[64]; lconf = NULL; if (snd_config == NULL) { return NULL; } r = snd_config_copy(&lconf, snd_config); if (r < 0) { return NULL; } do { r = snd_config_search_definition(lconf, "pcm", pcm_name, &pcm_node); if (r < 0) { break; } r = snd_config_get_id(pcm_node, &string); if (r < 0) { break; } r = snprintf(node_name, sizeof(node_name), "pcm.%s", string); if (r < 0 || r > (int) sizeof(node_name)) { break; } r = snd_config_search(lconf, node_name, &pcm_node); if (r < 0) { break; } /* If this PCM has a slave, walk the slave configurations until we reach the bottom. */ while ((node = get_slave_pcm_node(lconf, pcm_node)) != NULL) { pcm_node = node; } /* Fetch the PCM node's type, and bail out if it's not the PulseAudio plugin. */ r = snd_config_search(pcm_node, "type", &node); if (r < 0) { break; } r = snd_config_get_string(node, &string); if (r < 0) { break; } if (strcmp(string, "pulse") != 0) { break; } /* Don't clobber an explicit existing handle_underrun value, set it only if it doesn't already exist. */ r = snd_config_search(pcm_node, "handle_underrun", &node); if (r != -ENOENT) { break; } /* Disable pcm_pulse's asynchronous underrun handling. */ r = snd_config_imake_integer(&node, "handle_underrun", 0); if (r < 0) { break; } r = snd_config_add(pcm_node, node); if (r < 0) { break; } return lconf; } while (0); snd_config_delete(lconf); return NULL; }
void CAESinkALSA::EnumerateDevicesEx(AEDeviceInfoList &list, bool force) { /* ensure that ALSA has been initialized */ snd_lib_error_set_handler(sndLibErrorHandler); if(!snd_config || force) { if(force) snd_config_update_free_global(); snd_config_update(); } snd_config_t *config; snd_config_copy(&config, snd_config); /* Always enumerate the default device. * Note: If "default" is a stereo device, EnumerateDevice() * will automatically add "@" instead to enable surroundXX mangling. * We don't want to do that if "default" can handle multichannel * itself (e.g. in case of a pulseaudio server). */ EnumerateDevice(list, "default", "", config); void **hints; if (snd_device_name_hint(-1, "pcm", &hints) < 0) { CLog::Log(LOGINFO, "CAESinkALSA - Unable to get a list of devices"); return; } std::string defaultDescription; for (void** hint = hints; *hint != NULL; ++hint) { char *io = snd_device_name_get_hint(*hint, "IOID"); char *name = snd_device_name_get_hint(*hint, "NAME"); char *desc = snd_device_name_get_hint(*hint, "DESC"); if ((!io || strcmp(io, "Output") == 0) && name && strcmp(name, "null") != 0) { std::string baseName = std::string(name); baseName = baseName.substr(0, baseName.find(':')); if (strcmp(name, "default") == 0) { /* added already, but lets get the description if we have one */ if (desc) defaultDescription = desc; } else if (baseName == "front") { /* Enumerate using the surroundXX mangling */ /* do not enumerate basic "front", it is already handled * by the default "@" entry added in the very beginning */ if (strcmp(name, "front") != 0) EnumerateDevice(list, std::string("@") + (name+5), desc ? desc : name, config); } /* Do not enumerate "default", it is already enumerated above. */ /* Do not enumerate the sysdefault or surroundXX devices, those are * always accompanied with a "front" device and it is handled above * as "@". The below devices will be automatically used if available * for a "@" device. */ /* Ubuntu has patched their alsa-lib so that "defaults.namehint.extended" * defaults to "on" instead of upstream "off", causing lots of unwanted * extra devices (many of which are not actually routed properly) to be * found by the enumeration process. Skip them as well ("hw", "dmix", * "plughw", "dsnoop"). */ else if (baseName != "default" && baseName != "sysdefault" && baseName != "surround40" && baseName != "surround41" && baseName != "surround50" && baseName != "surround51" && baseName != "surround71" && baseName != "hw" && baseName != "dmix" && baseName != "plughw" && baseName != "dsnoop") { EnumerateDevice(list, name, desc ? desc : name, config); } } free(io); free(name); free(desc); } snd_device_name_free_hint(hints); /* set the displayname for default device */ if (!list.empty() && list[0].m_deviceName == "default") { /* If we have one from a hint (DESC), use it */ if (!defaultDescription.empty()) list[0].m_displayName = defaultDescription; /* Otherwise use the discovered name or (unlikely) "Default" */ else if (list[0].m_displayName.empty()) list[0].m_displayName = "Default"; } /* lets check uniqueness, we may need to append DEV or CARD to DisplayName */ /* If even a single device of card/dev X clashes with Y, add suffixes to * all devices of both them, for clarity. */ /* clashing card names, e.g. "NVidia", "NVidia_2" */ std::set<std::string> cardsToAppend; /* clashing basename + cardname combinations, e.g. ("hdmi","Nvidia") */ std::set<std::pair<std::string, std::string> > devsToAppend; for (AEDeviceInfoList::iterator it1 = list.begin(); it1 != list.end(); ++it1) { for (AEDeviceInfoList::iterator it2 = it1+1; it2 != list.end(); ++it2) { if (it1->m_displayName == it2->m_displayName && it1->m_displayNameExtra == it2->m_displayNameExtra) { /* something needs to be done */ std::string cardString1 = GetParamFromName(it1->m_deviceName, "CARD"); std::string cardString2 = GetParamFromName(it2->m_deviceName, "CARD"); if (cardString1 != cardString2) { /* card name differs, add identifiers to all devices */ cardsToAppend.insert(cardString1); cardsToAppend.insert(cardString2); continue; } std::string devString1 = GetParamFromName(it1->m_deviceName, "DEV"); std::string devString2 = GetParamFromName(it2->m_deviceName, "DEV"); if (devString1 != devString2) { /* device number differs, add identifiers to all such devices */ devsToAppend.insert(std::make_pair(it1->m_deviceName.substr(0, it1->m_deviceName.find(':')), cardString1)); devsToAppend.insert(std::make_pair(it2->m_deviceName.substr(0, it2->m_deviceName.find(':')), cardString2)); continue; } /* if we got here, the configuration is really weird, just append the whole device string */ it1->m_displayName += " (" + it1->m_deviceName + ")"; it2->m_displayName += " (" + it2->m_deviceName + ")"; } } } for (std::set<std::string>::iterator it = cardsToAppend.begin(); it != cardsToAppend.end(); ++it) { for (AEDeviceInfoList::iterator itl = list.begin(); itl != list.end(); ++itl) { std::string cardString = GetParamFromName(itl->m_deviceName, "CARD"); if (cardString == *it) /* "HDA NVidia (NVidia)", "HDA NVidia (NVidia_2)", ... */ itl->m_displayName += " (" + cardString + ")"; } } for (std::set<std::pair<std::string, std::string> >::iterator it = devsToAppend.begin(); it != devsToAppend.end(); ++it) { for (AEDeviceInfoList::iterator itl = list.begin(); itl != list.end(); ++itl) { std::string baseName = itl->m_deviceName.substr(0, itl->m_deviceName.find(':')); std::string cardString = GetParamFromName(itl->m_deviceName, "CARD"); if (baseName == it->first && cardString == it->second) { std::string devString = GetParamFromName(itl->m_deviceName, "DEV"); /* "HDMI #0", "HDMI #1" ... */ itl->m_displayNameExtra += " #" + devString; } } } }
bool CAESinkALSA::Initialize(AEAudioFormat &format, std::string &device) { CAEChannelInfo channelLayout; m_initDevice = device; m_initFormat = format; /* if we are raw, correct the data format */ if (AE_IS_RAW(format.m_dataFormat)) { channelLayout = GetChannelLayout(format); format.m_dataFormat = AE_FMT_S16NE; m_passthrough = true; } else { channelLayout = GetChannelLayout(format); m_passthrough = false; } #if defined(HAS_LIBAMCODEC) if (aml_present()) { aml_set_audio_passthrough(m_passthrough); device = "default"; } #endif if (channelLayout.Count() == 0) { CLog::Log(LOGERROR, "CAESinkALSA::Initialize - Unable to open the requested channel layout"); return false; } format.m_channelLayout = channelLayout; AEDeviceType devType = AEDeviceTypeFromName(device); std::string AESParams; /* digital interfaces should have AESx set, though in practice most * receivers don't care */ if (m_passthrough || devType == AE_DEVTYPE_HDMI || devType == AE_DEVTYPE_IEC958) GetAESParams(format, AESParams); 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); if (!OpenPCMDevice(device, AESParams, channelLayout.Count(), &m_pcm, config)) { CLog::Log(LOGERROR, "CAESinkALSA::Initialize - failed to initialize device \"%s\"", device.c_str()); snd_config_delete(config); return false; } /* get the actual device name that was used */ device = snd_pcm_name(m_pcm); m_device = device; CLog::Log(LOGINFO, "CAESinkALSA::Initialize - Opened device \"%s\"", device.c_str()); /* free the sound config */ snd_config_delete(config); if (!InitializeHW(format) || !InitializeSW(format)) return false; // we want it blocking snd_pcm_nonblock(m_pcm, 0); snd_pcm_prepare (m_pcm); m_format = format; m_formatSampleRateMul = 1.0 / (double)m_format.m_sampleRate; return true; }
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; AEDeviceType devType = AEDeviceTypeFromName(device); std::string AESParams; /* digital interfaces should have AESx set, though in practice most * receivers don't care */ if (m_passthrough || devType == AE_DEVTYPE_HDMI || devType == AE_DEVTYPE_IEC958) GetAESParams(format, AESParams); 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); snd_config_t *dmixRateConf; long dmixRate; if (snd_config_search(config, "defaults.pcm.dmix.rate", &dmixRateConf) < 0 || snd_config_get_integer(dmixRateConf, &dmixRate) < 0) dmixRate = 48000; /* assume default */ /* Prefer dmix for non-passthrough stereo when sample rate matches */ if (!OpenPCMDevice(device, AESParams, m_channelLayout.Count(), &m_pcm, config, format.m_sampleRate == dmixRate && !m_passthrough)) { CLog::Log(LOGERROR, "CAESinkALSA::Initialize - failed to initialize device \"%s\"", device.c_str()); snd_config_delete(config); return false; } /* get the actual device name that was used */ device = snd_pcm_name(m_pcm); m_device = device; CLog::Log(LOGINFO, "CAESinkALSA::Initialize - Opened device \"%s\"", device.c_str()); /* 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; }
/* Work around PulseAudio ALSA plugin bug where the PA server forces a higher than requested latency, but the plugin does not update its (and ALSA's) internal state to reflect that, leading to an immediate underrun situation. Inspired by WINE's make_handle_underrun_config. Reference: http://mailman.alsa-project.org/pipermail/alsa-devel/2012-July/053391.html From Mozilla https://github.com/kinetiknz/cubeb/blob/1aa0058d0729eb85505df104cd1ac072432c6d24/src/cubeb_alsa.c */ static snd_config_t *init_local_config_with_workaround(ao_device *device, char const *name){ char pcm_node_name[80]; int r; snd_config_t * lconf; snd_config_t * device_node; snd_config_t * type_node; snd_config_t * node; char const * type_string; lconf = NULL; snprintf(pcm_node_name,80,"pcm.%s",name); if (snd_config == NULL) snd_config_update(); r = snd_config_copy(&lconf, snd_config); if(r<0){ return NULL; } r = snd_config_search(lconf, pcm_node_name, &device_node); if (r != 0) { snd_config_delete(lconf); return NULL; } /* Fetch the PCM node's type, and bail out if it's not the PulseAudio plugin. */ r = snd_config_search(device_node, "type", &type_node); if (r != 0) { snd_config_delete(lconf); return NULL; } r = snd_config_get_string(type_node, &type_string); if (r != 0) { snd_config_delete(lconf); return NULL; } if (strcmp(type_string, "pulse") != 0) { snd_config_delete(lconf); return NULL; } /* Don't clobber an explicit existing handle_underrun value, set it only if it doesn't already exist. */ r = snd_config_search(device_node, "handle_underrun", &node); if (r != -ENOENT) { snd_config_delete(lconf); return NULL; } r = snd_config_imake_integer(&node, "handle_underrun", 0); if (r != 0) { snd_config_delete(lconf); return NULL; } r = snd_config_add(device_node, node); if (r != 0) { snd_config_delete(lconf); return NULL; } adebug("PulseAudio ALSA-emulation detected: disabling underrun detection\n"); return lconf; }
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); bool maybeHDMI = false; /* detect HDMI */ if (devname.find("HDMI") != std::string::npos) { info.m_deviceType = AE_DEVTYPE_HDMI; dev_index = hdmi_index++; sstr << "hdmi"; } else { /* detect IEC958 */ /* some HDMI devices (intel) report Digital for HDMI also */ if (devname.find("Digital") != std::string::npos) maybeHDMI = true; if (maybeHDMI || devname.find("IEC958" ) != std::string::npos) { info.m_deviceType = AE_DEVTYPE_IEC958; dev_index = iec958_index; /* dont increment, it might be HDMI */ 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; /* open the device for testing */ int err = snd_pcm_open_lconf(&pcmhandle, info.m_deviceName.c_str(), SND_PCM_STREAM_PLAYBACK, 0, config); /* if open of possible IEC958 failed and it could be HDMI, try as HDMI */ if (err < 0 && maybeHDMI) { /* check for HDMI if it failed */ sstr.str(std::string()); dev_index = hdmi_index; sstr << "hdmi"; sstr << ":CARD=" << snd_ctl_card_info_get_id(ctlinfo) << ",DEV=" << dev_index; info.m_deviceName = sstr.str(); err = snd_pcm_open_lconf(&pcmhandle, info.m_deviceName.c_str(), SND_PCM_STREAM_PLAYBACK, 0, config); /* if it was valid, increment the index and set the type */ if (err >= 0) { ++hdmi_index; info.m_deviceType = AE_DEVTYPE_HDMI; } } /* if it's still IEC958, increment the index */ if (info.m_deviceType == AE_DEVTYPE_IEC958) ++iec958_index; /* final error check */ if (err < 0) { CLog::Log(LOGINFO, "CAESinkALSA::EnumerateDevicesEx - Unable to open %s for capability detection", strHwName.c_str()); continue; } /* 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; } } /* 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); } }