Ejemplo n.º 1
0
void CAESinkALSA::EnumerateDevice(AEDeviceInfoList &list, const std::string &device, const std::string &description, snd_config_t *config)
{
  snd_pcm_t *pcmhandle = NULL;
  if (!OpenPCMDevice(device, "", ALSA_MAX_CHANNELS, &pcmhandle, config))
    return;

  snd_pcm_info_t *pcminfo;
  snd_pcm_info_alloca(&pcminfo);
  memset(pcminfo, 0, snd_pcm_info_sizeof());

  int err = snd_pcm_info(pcmhandle, pcminfo);
  if (err < 0)
  {
    CLog::Log(LOGINFO, "CAESinkALSA - Unable to get pcm_info for \"%s\"", device.c_str());
    snd_pcm_close(pcmhandle);
  }

  int cardNr = snd_pcm_info_get_card(pcminfo);

  CAEDeviceInfo info;
  info.m_deviceName = device;
  info.m_deviceType = AEDeviceTypeFromName(device);

  if (cardNr >= 0)
  {
    /* "HDA NVidia", "HDA Intel", "HDA ATI HDMI", "SB Live! 24-bit External", ... */
    char *cardName;
    if (snd_card_get_name(cardNr, &cardName) == 0)
      info.m_displayName = cardName;

    if (info.m_deviceType == AE_DEVTYPE_HDMI && info.m_displayName.size() > 5 &&
        info.m_displayName.substr(info.m_displayName.size()-5) == " HDMI")
    {
      /* We already know this is HDMI, strip it */
      info.m_displayName.erase(info.m_displayName.size()-5);
    }

    /* "CONEXANT Analog", "USB Audio", "HDMI 0", "ALC889 Digital" ... */
    std::string pcminfoName = snd_pcm_info_get_name(pcminfo);

    /*
     * Filter "USB Audio", in those cases snd_card_get_name() is more
     * meaningful already
     */
    if (pcminfoName != "USB Audio")
      info.m_displayNameExtra = pcminfoName;

    if (info.m_deviceType == AE_DEVTYPE_HDMI)
    {
      /* replace, this was likely "HDMI 0" */
      info.m_displayNameExtra = "HDMI";

      int dev = snd_pcm_info_get_device(pcminfo);

      if (dev >= 0)
      {
        /* lets see if we can get ELD info */

        snd_ctl_t *ctlhandle;
        std::stringstream sstr;
        sstr << "hw:" << cardNr;
        std::string strHwName = sstr.str();

        if (snd_ctl_open_lconf(&ctlhandle, strHwName.c_str(), 0, config) == 0)
        {
          snd_hctl_t *hctl;
          if (snd_hctl_open_ctl(&hctl, ctlhandle) == 0)
          {
            snd_hctl_load(hctl);
            bool badHDMI = false;
            if (!GetELD(hctl, dev, info, badHDMI))
              CLog::Log(LOGDEBUG, "CAESinkALSA - Unable to obtain ELD information for device \"%s\" (not supported by device, or kernel older than 3.2)",
                        device.c_str());

            /* snd_hctl_close also closes ctlhandle */
            snd_hctl_close(hctl);

            if (badHDMI)
            {
              /* 
               * Warn about disconnected devices, but keep them enabled 
               * Detection can go wrong on Intel, Nvidia and on all 
               * AMD (fglrx) hardware, so it is not safe to close those
               * handles
               */
              CLog::Log(LOGDEBUG, "CAESinkALSA - HDMI device \"%s\" may be unconnected (no ELD data)", device.c_str());
            }
          }
          else
          {
            snd_ctl_close(ctlhandle);
          }
        }
      }
    }
    else if (info.m_deviceType == AE_DEVTYPE_IEC958)
    {
      /* append instead of replace, pcminfoName is useful for S/PDIF */
      if (!info.m_displayNameExtra.empty())
        info.m_displayNameExtra += ' ';
      info.m_displayNameExtra += "S/PDIF";
    }
    else if (info.m_displayNameExtra.empty())
    {
      /* for USB audio, it gets a bit confusing as there is
       * - "SB Live! 24-bit External"
       * - "SB Live! 24-bit External, S/PDIF"
       * so add "Analog" qualifier to the first one */
      info.m_displayNameExtra = "Analog";
    }

    /* "default" is a device that will be used for all inputs, while
     * "@" will be mangled to front/default/surroundXX as necessary */
    if (device == "@" || device == "default")
    {
      /* Make it "Default (whatever)" */
      info.m_displayName = "Default (" + info.m_displayName + (info.m_displayNameExtra.empty() ? "" : " " + info.m_displayNameExtra + ")");
      info.m_displayNameExtra = "";
    }

  }
  else
  {
    /* virtual devices: "default", "pulse", ... */
    /* description can be e.g. "PulseAudio Sound Server" - for hw devices it is
     * normally uninteresting, like "HDMI Audio Output" or "Default Audio Device",
     * so we only use it for virtual devices that have no better display name */
    info.m_displayName = description;
  }

  snd_pcm_hw_params_t *hwparams;
  snd_pcm_hw_params_alloca(&hwparams);
  memset(hwparams, 0, snd_pcm_hw_params_sizeof());

  /* ensure we can get a playback configuration for the device */
  if (snd_pcm_hw_params_any(pcmhandle, hwparams) < 0)
  {
    CLog::Log(LOGINFO, "CAESinkALSA - No playback configurations available for device \"%s\"", device.c_str());
    snd_pcm_close(pcmhandle);
    return;
  }

  /* 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 = ALSA_MAX_CHANNELS; i >= 1; --i)
  {
    /* Reopen the device if needed on the special "surroundXX" cases */
    if (info.m_deviceType == AE_DEVTYPE_PCM && (i == 8 || i == 6 || i == 4))
      OpenPCMDevice(device, "", i, &pcmhandle, config);

    if (snd_pcm_hw_params_test_channels(pcmhandle, hwparams, i) >= 0)
    {
      channels = i;
      break;
    }
  }

  if (device == "default" && channels == 2)
  {
    /* This looks like the ALSA standard default stereo dmix device, we
     * probably want to use "@" instead to get surroundXX. */
    snd_pcm_close(pcmhandle);
    EnumerateDevice(list, "@", description, config);
    return;
  }

  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);
}
Ejemplo n.º 2
0
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);
  }
}