Alsa device enumeration, Audio Device dialog tuning, minimum buffer size support (gx plugins).
This commit is contained in:
+231
-118
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
// Copyright (c) Robin E. R. Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
@@ -52,9 +52,106 @@ static bool isSupportedAudioDevice(const AlsaDeviceInfo &d)
|
||||
std::string name = d.name_ + " " + d.longName_;
|
||||
std::transform(name.begin(), name.end(), name.begin(), [](char c)
|
||||
{ return std::tolower(c); });
|
||||
return name.find("hdmi") != std::string::npos || name.find("bcm2835") != std::string::npos;
|
||||
if (name.find("hdmi") != std::string::npos) return false;
|
||||
// if (name.find("bcm2835") != std::string::npos) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
struct ProcAlsaDevice {
|
||||
int cardId;
|
||||
int subdeviceId;
|
||||
bool audioCapture;
|
||||
bool audioPlayback;
|
||||
bool rawMidi;
|
||||
};
|
||||
|
||||
static std::vector<ProcAlsaDevice> getProcAlsaDevices()
|
||||
{
|
||||
std::vector<ProcAlsaDevice> result;
|
||||
|
||||
std::ifstream f {"/proc/asound/devices"};
|
||||
if (f.is_open())
|
||||
{
|
||||
std::string line;
|
||||
while (std::getline(f, line))
|
||||
{
|
||||
// Parse each line of /proc/alsa/devices
|
||||
// Format: cardnum: [devicenum- subdevicenum]: type : name
|
||||
std::istringstream iss(line);
|
||||
std::string token;
|
||||
|
||||
// Skip leading whitespace and get card number
|
||||
if (!std::getline(iss, token, ':'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
int cardId = std::stoi(token.substr(token.find_first_not_of(" \t")));
|
||||
|
||||
// Get device-subdevice part
|
||||
if (!std::getline(iss, token, ':'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
size_t dashPos = token.find('-');
|
||||
if (dashPos == std::string::npos)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int deviceId = std::stoi(token.substr(token.find_first_not_of(" \t["), dashPos));
|
||||
int subdeviceId = std::stoi(token.substr(dashPos + 1, token.find(']') - dashPos - 1));
|
||||
|
||||
// Get type
|
||||
if (!std::getline(iss, token, ':'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
std::string type = token.substr(token.find_first_not_of(" \t"));
|
||||
|
||||
ProcAlsaDevice *pDevice = nullptr;
|
||||
for (size_t i = 0; i < result.size(); ++i)
|
||||
{
|
||||
if (result[i].cardId == deviceId && result[i].subdeviceId == subdeviceId)
|
||||
{
|
||||
pDevice = &result[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pDevice == nullptr)
|
||||
{
|
||||
ProcAlsaDevice newDevice;
|
||||
newDevice.cardId = deviceId;
|
||||
newDevice.subdeviceId = subdeviceId;
|
||||
newDevice.audioCapture = false;
|
||||
newDevice.audioPlayback = false;
|
||||
newDevice.rawMidi = false;
|
||||
result.push_back(newDevice);
|
||||
pDevice = &(result[result.size()-1]);
|
||||
}
|
||||
if (type.find("digital audio capture") != std::string::npos)
|
||||
{
|
||||
pDevice->audioCapture = true;
|
||||
}
|
||||
if ((type.find("digital audio playback") != std::string::npos))
|
||||
{
|
||||
pDevice->audioPlayback = true;
|
||||
}
|
||||
if (type.find("rawmidi") != std::string::npos)
|
||||
{
|
||||
pDevice->rawMidi = true;
|
||||
}
|
||||
} catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error(SS("invalid ALSA proc entry: " << line));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
||||
{
|
||||
|
||||
@@ -65,152 +162,163 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
||||
int cardNum = -1; // Start with first card
|
||||
int err;
|
||||
|
||||
for (;;)
|
||||
std::vector<ProcAlsaDevice> procAlsaDevices = getProcAlsaDevices();
|
||||
|
||||
for (const auto &procAlsaDevice: procAlsaDevices)
|
||||
{
|
||||
if ((err = snd_card_next(&cardNum)) < 0)
|
||||
{
|
||||
Lv2Log::error("Unexpected error enumerating ALSA devices.");
|
||||
break;
|
||||
std::stringstream ss;
|
||||
if (!procAlsaDevice.audioCapture && !procAlsaDevice.audioPlayback) {
|
||||
continue;
|
||||
}
|
||||
if (cardNum < 0)
|
||||
// No more cards
|
||||
break;
|
||||
ss << "hw:" << procAlsaDevice.cardId;
|
||||
|
||||
std::string cardId = ss.str();
|
||||
|
||||
snd_ctl_t *hDevice = nullptr;
|
||||
|
||||
if ((err = snd_ctl_open(&hDevice, cardId.c_str(), 0)) < 0)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "hw:" << cardNum;
|
||||
std::string cardId = ss.str();
|
||||
continue;
|
||||
}
|
||||
|
||||
snd_ctl_t *hDevice = nullptr;
|
||||
Finally ffhDevice{[hDevice]()
|
||||
{ snd_ctl_close(hDevice); }};
|
||||
|
||||
if ((err = snd_ctl_open(&hDevice, cardId.c_str(), 0)) < 0)
|
||||
snd_ctl_card_info_t *alsaInfo = nullptr;
|
||||
if (snd_ctl_card_info_malloc(&alsaInfo) != 0)
|
||||
{
|
||||
Lv2Log::error("Failed to allocate ALSA card info");
|
||||
continue;
|
||||
}
|
||||
|
||||
Finally ffCardInfo{[alsaInfo]()
|
||||
{ snd_ctl_card_info_free(alsaInfo); }};
|
||||
|
||||
err = snd_ctl_card_info(hDevice, alsaInfo);
|
||||
if (err == 0)
|
||||
{
|
||||
AlsaDeviceInfo info;
|
||||
info.cardId_ = cardNum;
|
||||
info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo);
|
||||
const char *driver = snd_ctl_card_info_get_driver(alsaInfo);
|
||||
(void)driver;
|
||||
|
||||
info.name_ = snd_ctl_card_info_get_name(alsaInfo);
|
||||
info.longName_ = snd_ctl_card_info_get_longname(alsaInfo);
|
||||
|
||||
// we can't read our own device if it's open so use data that gets
|
||||
// cached before we open audio devices.
|
||||
|
||||
AlsaDeviceInfo cachedInfo;
|
||||
if (getCachedDevice(info.name_, &cachedInfo))
|
||||
{
|
||||
// may have been plugged into a different USB connector.
|
||||
cachedInfo.cardId_ = info.cardId_;
|
||||
cachedInfo.id_ = info.id_;
|
||||
result.push_back(cachedInfo);
|
||||
continue;
|
||||
}
|
||||
snd_pcm_t *captureDevice = nullptr;
|
||||
snd_pcm_t *playbackDevice = nullptr;
|
||||
auto rc = snd_pcm_open(&captureDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
|
||||
bool captureOk = rc == 0;
|
||||
|
||||
Finally ffhDevice{[hDevice]()
|
||||
{ snd_ctl_close(hDevice); }};
|
||||
Finally ffCaptureDevice{
|
||||
[captureDevice]
|
||||
{ if (captureDevice) snd_pcm_close(captureDevice); }};
|
||||
|
||||
snd_ctl_card_info_t *alsaInfo = nullptr;
|
||||
if (snd_ctl_card_info_malloc(&alsaInfo) != 0)
|
||||
rc = snd_pcm_open(&playbackDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
||||
bool playbackOk = rc == 0;
|
||||
|
||||
Finally ffPlaybackDevice{
|
||||
[playbackDevice]
|
||||
{ if (playbackDevice) snd_pcm_close(playbackDevice); }};
|
||||
if (procAlsaDevice.audioCapture && !captureOk)
|
||||
{
|
||||
Lv2Log::error("Failed to allocate ALSA card info");
|
||||
continue;
|
||||
info.captureBusy_ = true;
|
||||
}
|
||||
if (procAlsaDevice.audioPlayback && !playbackOk)
|
||||
{
|
||||
info.playbackBusy_ = true;
|
||||
}
|
||||
|
||||
Finally ffCardInfo{[alsaInfo]()
|
||||
{ snd_ctl_card_info_free(alsaInfo); }};
|
||||
info.supportsCapture_ = captureOk;
|
||||
info.supportsPlayback_ = playbackOk;
|
||||
|
||||
err = snd_ctl_card_info(hDevice, alsaInfo);
|
||||
if (err == 0)
|
||||
if (captureOk || playbackOk)
|
||||
{
|
||||
AlsaDeviceInfo info;
|
||||
info.cardId_ = cardNum;
|
||||
info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo);
|
||||
const char *driver = snd_ctl_card_info_get_driver(alsaInfo);
|
||||
(void)driver;
|
||||
snd_pcm_t *hDevice = captureOk ? captureDevice : playbackDevice;
|
||||
snd_pcm_hw_params_t *params = nullptr;
|
||||
err = snd_pcm_hw_params_malloc(¶ms);
|
||||
|
||||
info.name_ = snd_ctl_card_info_get_name(alsaInfo);
|
||||
info.longName_ = snd_ctl_card_info_get_longname(alsaInfo);
|
||||
|
||||
// we can't read our own device if it's open so use data that gets
|
||||
// cached before we open audio devices.
|
||||
|
||||
AlsaDeviceInfo cachedInfo;
|
||||
if (getCachedDevice(info.name_, &cachedInfo))
|
||||
if (err == 0)
|
||||
{
|
||||
// may have been plugged into a different USB connector.
|
||||
cachedInfo.cardId_ = info.cardId_;
|
||||
cachedInfo.id_ = info.id_;
|
||||
result.push_back(cachedInfo);
|
||||
Finally ffParams{[params]
|
||||
{ snd_pcm_hw_params_free(params); }};
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
snd_pcm_t *captureDevice = nullptr;
|
||||
snd_pcm_t *playbackDevice = nullptr;
|
||||
bool captureOk = snd_pcm_open(&captureDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK) == 0;
|
||||
bool playbackOk = snd_pcm_open(&playbackDevice, cardId.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK) == 0;
|
||||
Finally ffCaptureDevice{
|
||||
[captureDevice]
|
||||
{ if (captureDevice) snd_pcm_close(captureDevice); }};
|
||||
Finally ffPlaybackDevice{
|
||||
[playbackDevice]
|
||||
{ if (playbackDevice) snd_pcm_close(playbackDevice); }};
|
||||
|
||||
info.supportsCapture_ = captureOk;
|
||||
info.supportsPlayback_ = playbackOk;
|
||||
|
||||
if (captureOk || playbackOk)
|
||||
err = snd_pcm_hw_params_any(hDevice, params);
|
||||
if (err == 0)
|
||||
{
|
||||
snd_pcm_t *hDevice = captureOk ? captureDevice : playbackDevice;
|
||||
snd_pcm_hw_params_t *params = nullptr;
|
||||
err = snd_pcm_hw_params_malloc(¶ms);
|
||||
unsigned int minRate = 0, maxRate = 0;
|
||||
snd_pcm_uframes_t minBufferSize = 0, maxBufferSize = 0;
|
||||
int dir;
|
||||
|
||||
err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir);
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_hw_params_get_rate_max(params, &maxRate, &dir);
|
||||
if (err == 0)
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(RATES) / sizeof(RATES[0]); ++i)
|
||||
{
|
||||
uint32_t rate = RATES[i];
|
||||
if (rate >= minRate && rate <= maxRate)
|
||||
{
|
||||
info.sampleRates_.push_back(rate);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Log::warning(SS("Failed to get maximum sample rate for device '" << info.name_ << "'."));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Log::warning(SS("Failed to get minimum sample rate for device '" << info.name_ << "'."));
|
||||
}
|
||||
|
||||
if (err == 0)
|
||||
{
|
||||
Finally ffParams{[params]
|
||||
{ snd_pcm_hw_params_free(params); }};
|
||||
|
||||
err = snd_pcm_hw_params_any(hDevice, params);
|
||||
err = snd_pcm_hw_params_get_buffer_size_min(params, &minBufferSize);
|
||||
if (err == 0)
|
||||
{
|
||||
unsigned int minRate = 0, maxRate = 0;
|
||||
snd_pcm_uframes_t minBufferSize = 0, maxBufferSize = 0;
|
||||
int dir;
|
||||
|
||||
err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir);
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_hw_params_get_rate_max(params, &maxRate, &dir);
|
||||
if (err == 0)
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(RATES) / sizeof(RATES[0]); ++i)
|
||||
{
|
||||
uint32_t rate = RATES[i];
|
||||
if (rate >= minRate && rate <= maxRate)
|
||||
{
|
||||
info.sampleRates_.push_back(rate);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Log::warning(SS("Failed to get maximum sample rate for device '" << info.name_ << "'."));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Lv2Log::warning(SS("Failed to get minimum sample rate for device '" << info.name_ << "'."));
|
||||
}
|
||||
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_hw_params_get_buffer_size_min(params, &minBufferSize);
|
||||
if (err == 0)
|
||||
{
|
||||
err = snd_pcm_hw_params_get_buffer_size_max(params, &maxBufferSize);
|
||||
}
|
||||
}
|
||||
if (err == 0)
|
||||
{
|
||||
if (minBufferSize < 16)
|
||||
{
|
||||
minBufferSize = 16;
|
||||
}
|
||||
|
||||
info.minBufferSize_ = (uint32_t)minBufferSize;
|
||||
info.maxBufferSize_ = (uint32_t)maxBufferSize;
|
||||
}
|
||||
err = snd_pcm_hw_params_get_buffer_size_max(params, &maxBufferSize);
|
||||
}
|
||||
}
|
||||
if (err == 0)
|
||||
{
|
||||
cacheDevice(info.name_, info);
|
||||
result.push_back(info);
|
||||
if (minBufferSize < 16)
|
||||
{
|
||||
minBufferSize = 16;
|
||||
}
|
||||
|
||||
info.minBufferSize_ = (uint32_t)minBufferSize;
|
||||
info.maxBufferSize_ = (uint32_t)maxBufferSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!info.captureBusy_ && !info.playbackBusy_)
|
||||
{
|
||||
cacheDevice(info.name_, info);
|
||||
result.push_back(info);
|
||||
}
|
||||
} else {
|
||||
if (info.captureBusy_ || info.playbackBusy_)
|
||||
{
|
||||
result.push_back(info);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,14 +328,17 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
||||
std::vector<AlsaDeviceInfo> filtered;
|
||||
for (auto &device : result)
|
||||
{
|
||||
if (!isSupportedAudioDevice(device))
|
||||
if (isSupportedAudioDevice(device))
|
||||
{
|
||||
filtered.push_back(device);
|
||||
Lv2Log::debug(
|
||||
SS(" "
|
||||
<< device.name_ << " " << device.longName_ << " " << device.cardId_
|
||||
<< (device.supportsCapture_ ? " in" : "")
|
||||
<< (device.supportsPlayback_ ? " out" : "")));
|
||||
<< (device.supportsPlayback_ ? " out" : "")
|
||||
<< (device.captureBusy_ ? " in(busy)" : "")
|
||||
<< (device.captureBusy_ ? " out(busy)" : "")
|
||||
));
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
@@ -535,6 +646,8 @@ JSON_MAP_REFERENCE(AlsaDeviceInfo, minBufferSize)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, maxBufferSize)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsCapture)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsPlayback)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, captureBusy)
|
||||
JSON_MAP_REFERENCE(AlsaDeviceInfo, playbackBusy)
|
||||
JSON_MAP_END()
|
||||
|
||||
JSON_MAP_BEGIN(AlsaMidiDeviceInfo)
|
||||
|
||||
Reference in New Issue
Block a user