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); }
void ALSAAudioCaptureDevice::enumerateDevices(std::vector<AudioCaptureDevice::DeviceIdPtr>& devices) { /* Enumerate all ALSA cards and devices: */ int cardIndex=-1; // Start with first card while(true) { /* Get the index of the next card: */ if(snd_card_next(&cardIndex)!=0||cardIndex<0) { /* There was an error, or there are no more cards: */ break; } /* Open the card's control interface: */ char cardName[20]; snprintf(cardName,sizeof(cardName),"hw:%d",cardIndex); snd_ctl_t* cardHandle; if(snd_ctl_open(&cardHandle,cardName,0)!=0) break; /* Enumerate all PCM devices on this card: */ int numCardDevices=0; int pcmIndex=-1; while(true) { /* Get the index of the next PCM device: */ if(snd_ctl_pcm_next_device(cardHandle,&pcmIndex)!=0||pcmIndex<0) { /* There was an error, or there are no more PCM devices: */ break; } /* Create an info structure for the PCM device: */ snd_pcm_info_t* pcmInfo; snd_pcm_info_alloca(&pcmInfo); snd_pcm_info_set_device(pcmInfo,pcmIndex); snd_pcm_info_set_stream(pcmInfo,SND_PCM_STREAM_CAPTURE); /* Get the number of capture subdevices for the device: */ if(snd_ctl_pcm_info(cardHandle,pcmInfo)!=0) break; int numSubDevices=snd_pcm_info_get_subdevices_count(pcmInfo); for(int subDeviceIndex=0;subDeviceIndex<numSubDevices;++subDeviceIndex) { /* Query information about the subdevice: */ snd_pcm_info_set_subdevice(pcmInfo,subDeviceIndex); if(snd_ctl_pcm_info(cardHandle,pcmInfo)==0) { /* Query the card's name: */ char* cardName; if(snd_card_get_name(cardIndex,&cardName)==0) { /* Create a device ID: */ std::string deviceName=cardName; free(cardName); if(numCardDevices>0) { char suffix[10]; snprintf(suffix,sizeof(suffix),":%d",numCardDevices); deviceName.append(suffix); } DeviceId* newDeviceId=new DeviceId(deviceName); /* Set the PCM device name: */ char pcmDeviceName[20]; if(numSubDevices>1) snprintf(pcmDeviceName,sizeof(pcmDeviceName),"plughw:%d,%d,%d",snd_pcm_info_get_card(pcmInfo),snd_pcm_info_get_device(pcmInfo),snd_pcm_info_get_subdevice(pcmInfo)); else snprintf(pcmDeviceName,sizeof(pcmDeviceName),"plughw:%d,%d",snd_pcm_info_get_card(pcmInfo),snd_pcm_info_get_device(pcmInfo)); newDeviceId->pcmDeviceName=pcmDeviceName; /* Store the device ID: */ devices.push_back(newDeviceId); ++numCardDevices; } } } } /* Close the card's control interface: */ snd_ctl_close(cardHandle); } }
// for each ALSA device, call iterator. userData is passed to the iterator // returns total number of iterations int iteratePCMDevices(DeviceIteratorPtr iterator, void* userData) { int count = 0; int subdeviceCount; int card, dev, subDev; char devname[16]; int err; snd_ctl_t *handle; snd_pcm_t *pcm; snd_pcm_info_t* pcminfo; snd_ctl_card_info_t *cardinfo, *defcardinfo = NULL; UINT32 deviceID; int doContinue = TRUE; snd_pcm_info_malloc(&pcminfo); snd_ctl_card_info_malloc(&cardinfo); // 1st try "default" device err = snd_pcm_open(&pcm, ALSA_DEFAULT_DEVICE_NAME, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (err < 0) { // try with the other direction err = snd_pcm_open(&pcm, ALSA_DEFAULT_DEVICE_NAME, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); } if (err < 0) { ERROR1("ERROR: snd_pcm_open (\"default\"): %s\n", snd_strerror(err)); } else { err = snd_pcm_info(pcm, pcminfo); snd_pcm_close(pcm); if (err < 0) { ERROR1("ERROR: snd_pcm_info (\"default\"): %s\n", snd_strerror(err)); } else { // try to get card info card = snd_pcm_info_get_card(pcminfo); if (card >= 0) { sprintf(devname, ALSA_HARDWARE_CARD, card); if (snd_ctl_open(&handle, devname, SND_CTL_NONBLOCK) >= 0) { if (snd_ctl_card_info(handle, cardinfo) >= 0) { defcardinfo = cardinfo; } snd_ctl_close(handle); } } // call callback function for the device if (iterator != NULL) { doContinue = (*iterator)(ALSA_DEFAULT_DEVICE_ID, pcminfo, defcardinfo, userData); } count++; } } // iterate cards card = -1; while (doContinue) { if (snd_card_next(&card) < 0) { break; } if (card < 0) { break; } sprintf(devname, ALSA_HARDWARE_CARD, card); TRACE1("Opening alsa device \"%s\"...\n", devname); err = snd_ctl_open(&handle, devname, SND_CTL_NONBLOCK); if (err < 0) { ERROR2("ERROR: snd_ctl_open, card=%d: %s\n", card, snd_strerror(err)); } else { err = snd_ctl_card_info(handle, cardinfo); if (err < 0) { ERROR2("ERROR: snd_ctl_card_info, card=%d: %s\n", card, snd_strerror(err)); } else { dev = -1; while (doContinue) { if (snd_ctl_pcm_next_device(handle, &dev) < 0) { ERROR0("snd_ctl_pcm_next_device\n"); } if (dev < 0) { break; } snd_pcm_info_set_device(pcminfo, dev); snd_pcm_info_set_subdevice(pcminfo, 0); snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK); err = snd_ctl_pcm_info(handle, pcminfo); if (err == -ENOENT) { // try with the other direction snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_CAPTURE); err = snd_ctl_pcm_info(handle, pcminfo); } if (err < 0) { if (err != -ENOENT) { ERROR2("ERROR: snd_ctl_pcm_info, card=%d: %s", card, snd_strerror(err)); } } else { subdeviceCount = needEnumerateSubdevices(ALSA_PCM) ? snd_pcm_info_get_subdevices_count(pcminfo) : 1; if (iterator!=NULL) { for (subDev = 0; subDev < subdeviceCount; subDev++) { deviceID = encodeDeviceID(card, dev, subDev); doContinue = (*iterator)(deviceID, pcminfo, cardinfo, userData); count++; if (!doContinue) { break; } } } else { count += subdeviceCount; } } } // of while(doContinue) } snd_ctl_close(handle); } } snd_ctl_card_info_free(cardinfo); snd_pcm_info_free(pcminfo); return count; }
static int alsa_setup(struct snd_format* f) { int err; snd_pcm_hw_params_t* hwparams; snd_pcm_sw_params_t* swparams; unsigned int alsa_buffer_time, alsa_period_time; snd_pcm_uframes_t alsa_buffer_size, alsa_period_size; free(outputf); outputf = snd_format_alloc(f->rate, f->channels); printf(" Opening device %s ... ", alsa_cb.pcm_device); if((err = snd_pcm_open(&alsa_pcm, alsa_cb.pcm_device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { printf("alsa_setup(): Failed to open pcm device (%s): %s\n", alsa_cb.pcm_device, snd_strerror(-err)); alsa_pcm = NULL; free(outputf); outputf = NULL; return -1; } /* doesn't care about non-blocking */ /* snd_pcm_nonblock(alsa_pcm, 0); */ if(0) { //debug snd_pcm_info_t* info; int alsa_card, alsa_device, alsa_subdevice; snd_pcm_info_alloca(&info); snd_pcm_info(alsa_pcm, info); alsa_card = snd_pcm_info_get_card(info); alsa_device = snd_pcm_info_get_device(info); alsa_subdevice = snd_pcm_info_get_subdevice(info); printf("Card %i, Device %i, Subdevice %i\n", alsa_card, alsa_device, alsa_subdevice); } snd_pcm_hw_params_alloca(&hwparams); if((err = snd_pcm_hw_params_any(alsa_pcm, hwparams)) < 0) { printf("alsa_setup(): No configuration available for " "playback: %s\n", snd_strerror(-err)); return -1; } if((err = snd_pcm_hw_params_set_access(alsa_pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { printf("alsa_setup(): Cannot set direct write mode: %s\n", snd_strerror(-err)); return -1; } if((err = snd_pcm_hw_params_set_format(alsa_pcm, hwparams, outputf->format)) < 0) { printf("alsa_setup(): Sample format not " "available for playback: %s\n", snd_strerror(-err)); return -1; } if((err = snd_pcm_hw_params_set_channels_near(alsa_pcm, hwparams, &outputf->channels)) < 0) { printf ("alsa_setup(): snd_pcm_hw_params_set_channels_near failed: %s.\n", snd_strerror(-err)); return -1; } snd_pcm_hw_params_set_rate_near(alsa_pcm, hwparams, &outputf->rate, 0); if(outputf->rate == 0) { printf("alsa_setup(): No usable samplerate available.\n"); return -1; } outputf->sample_bits = snd_pcm_format_physical_width(outputf->format); outputf->bps = (outputf->rate * outputf->sample_bits * outputf->channels) >> 3; alsa_buffer_time = alsa_cb.buffer_time * 1000; if((err = snd_pcm_hw_params_set_buffer_time_near(alsa_pcm, hwparams, &alsa_buffer_time, 0)) < 0) { printf("alsa_setup(): Set buffer time failed: %s.\n", snd_strerror(-err)); return -1; } alsa_period_time = alsa_cb.period_time * 1000; if((err = snd_pcm_hw_params_set_period_time_near(alsa_pcm, hwparams, &alsa_period_time, 0)) < 0) { printf("alsa_setup(): Set period time failed: %s.\n", snd_strerror(-err)); return -1; } if(snd_pcm_hw_params(alsa_pcm, hwparams) < 0) { printf("alsa_setup(): Unable to install hw params\n"); return -1; } if((err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size)) < 0) { printf("alsa_setup(): snd_pcm_hw_params_get_buffer_size() " "failed: %s\n", snd_strerror(-err)); return -1; } if((err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size, 0)) < 0) { printf("alsa_setup(): snd_pcm_hw_params_get_period_size() " "failed: %s\n", snd_strerror(-err)); return -1; } snd_pcm_sw_params_alloca(&swparams); snd_pcm_sw_params_current(alsa_pcm, swparams); if((err = snd_pcm_sw_params_set_start_threshold(alsa_pcm, swparams, alsa_buffer_size - alsa_period_size) < 0)) printf("alsa_setup(): setting start " "threshold failed: %s\n", snd_strerror(-err)); if(snd_pcm_sw_params(alsa_pcm, swparams) < 0) { printf("alsa_setup(): Unable to install sw params\n"); return -1; } hw_buffer_size = snd_pcm_frames_to_bytes(alsa_pcm, alsa_buffer_size); hw_period_size = snd_pcm_frames_to_bytes(alsa_pcm, alsa_period_size); if(inputf->bps != outputf->bps) { int align = (inputf->sample_bits * inputf->channels) / 8; hw_buffer_size_in = ((u_int) hw_buffer_size * inputf->bps + outputf->bps / 2) / outputf->bps; hw_period_size_in = ((u_int) hw_period_size * inputf->bps + outputf->bps / 2) / outputf->bps; hw_buffer_size_in -= hw_buffer_size_in % align; hw_period_size_in -= hw_period_size_in % align; } else { hw_buffer_size_in = hw_buffer_size; hw_period_size_in = hw_period_size; } #if 0 printf("Device setup: buffer time: %i, size: %i.\n", alsa_buffer_time, hw_buffer_size); printf("Device setup: period time: %i, size: %i.\n", alsa_period_time, hw_period_size); printf("bits per sample: %i; frame size: %i; Bps: %i\n", snd_pcm_format_physical_width(outputf->format), snd_pcm_frames_to_bytes(alsa_pcm, 1), outputf->bps); #endif printf("success.\n"); return 0; }