Fix crash selecting devices whose driver reports sample rates as a range

Drivers that only implement getSampleRateRange() (e.g. the usdr module
for Wavelet Lab xSDR) crash CubicSDR on device start with:

  terminating due to uncaught exception of type std::runtime_error:
  SoapyUSDR::setSampleRate() unable to set samplerate!

Two problems in SDRDeviceInfo::getSampleRates():

- SoapySDR's deprecated-API shim expands a continuous rate range into
  a stepped list whose first entry is 0. The 0 survives decimation,
  getSampleRateNear() picks it, and the driver throws on
  setSampleRate(0). Filter out non-positive rates.
- If a driver returns an empty rate list, the existing code calls
  sampleRates.back() on an empty vector (undefined behavior).
  Synthesize a candidate list from getSampleRateRange() instead, fall
  back to the range endpoints, and bail out cleanly if there is no
  rate information at all.

Verified on a Wavelet Lab xSDR over USB: device selection previously
aborted the app, now starts and streams normally.
This commit is contained in:
Sergi Aliaga
2026-08-21 11:45:49 -04:00
parent 033330367c
commit d5103ed140
+42
View File
@@ -196,6 +196,48 @@ std::vector<long> SDRDeviceInfo::getSampleRates(int direction, size_t channel) {
//the original list returned from the driver:
std::vector<double> sampleRates = dev->listSampleRates(direction, channel);
//drop bogus non-positive rates: range-only drivers (e.g. usdr/xsdr) go through
//the SoapySDR deprecated-API shim, which can emit a 0 entry that later gets
//selected and makes the driver throw on setSampleRate(0).
sampleRates.erase(std::remove_if(sampleRates.begin(), sampleRates.end(),
[](double r) { return r <= 0; }), sampleRates.end());
//some drivers (e.g. usdr/xsdr) only report a continuous range and return an
//empty discrete list; synthesize candidate rates from the range instead.
if (sampleRates.empty()) {
SoapySDR::RangeList rateRanges = dev->getSampleRateRange(direction, channel);
const double candidateRates[] = { 250000, 500000, 1000000, 2000000, 2500000,
4000000, 5000000, 8000000, 10000000, 16000000, 20000000, 25000000,
32000000, 40000000, 50000000, 64000000, 80000000, 100000000, 125000000 };
for (double candidate : candidateRates) {
for (const auto& range : rateRanges) {
if (candidate >= range.minimum() && candidate <= range.maximum()) {
sampleRates.push_back(candidate);
break;
}
}
}
//still nothing usable: fall back to the range endpoints themselves
if (sampleRates.empty()) {
for (const auto& range : rateRanges) {
sampleRates.push_back(range.minimum());
if (range.maximum() != range.minimum()) {
sampleRates.push_back(range.maximum());
}
}
}
}
//no rate info at all: bail out with an empty list rather than reading
//past the end of an empty vector below.
if (sampleRates.empty()) {
return result;
}
//be paranoid, sort by increasing rates...
std::sort(sampleRates.begin(), sampleRates.end(), [](double a, double b) -> bool { return a < b; });