1 // Copyright (c) 2016-2017 Josh Blum
2 // SPDX-License-Identifier: BSL-1.0
3
4 #include <SoapySDR/Device.hpp>
5 #include <SoapySDR/Formats.hpp>
6 #include <SoapySDR/Errors.hpp>
7 #include <string>
8 #include <cstdlib>
9 #include <iostream>
10 #include <stdexcept>
11 #include <csignal>
12 #include <chrono>
13 #include <cstdio>
14
15 static sig_atomic_t loopDone = false;
sigIntHandler(const int)16 static void sigIntHandler(const int)
17 {
18 loopDone = true;
19 }
20
runRateTestStreamLoop(SoapySDR::Device * device,SoapySDR::Stream * stream,const int direction,const size_t numChans,const size_t elemSize)21 void runRateTestStreamLoop(
22 SoapySDR::Device *device,
23 SoapySDR::Stream *stream,
24 const int direction,
25 const size_t numChans,
26 const size_t elemSize)
27 {
28 //allocate buffers for the stream read/write
29 const size_t numElems = device->getStreamMTU(stream);
30 std::vector<std::vector<char>> buffMem(numChans, std::vector<char>(elemSize*numElems));
31 std::vector<void *> buffs(numChans);
32 for (size_t i = 0; i < numChans; i++) buffs[i] = buffMem[i].data();
33
34 //state collected in this loop
35 unsigned int overflows(0);
36 unsigned int underflows(0);
37 unsigned long long totalSamples(0);
38 const auto startTime = std::chrono::high_resolution_clock::now();
39 auto timeLastPrint = std::chrono::high_resolution_clock::now();
40 auto timeLastSpin = std::chrono::high_resolution_clock::now();
41 auto timeLastStatus = std::chrono::high_resolution_clock::now();
42 int spinIndex(0);
43
44 std::cout << "Starting stream loop, press Ctrl+C to exit..." << std::endl;
45 device->activateStream(stream);
46 signal(SIGINT, sigIntHandler);
47 while (not loopDone)
48 {
49 int ret(0);
50 int flags(0);
51 long long timeNs(0);
52 switch(direction)
53 {
54 case SOAPY_SDR_RX:
55 ret = device->readStream(stream, buffs.data(), numElems, flags, timeNs);
56 break;
57 case SOAPY_SDR_TX:
58 ret = device->writeStream(stream, buffs.data(), numElems, flags, timeNs);
59 break;
60 }
61
62 if (ret == SOAPY_SDR_TIMEOUT) continue;
63 if (ret == SOAPY_SDR_OVERFLOW)
64 {
65 overflows++;
66 continue;
67 }
68 if (ret == SOAPY_SDR_UNDERFLOW)
69 {
70 underflows++;
71 continue;
72 }
73 if (ret < 0)
74 {
75 std::cerr << "Unexpected stream error " << SoapySDR::errToStr(ret) << std::endl;
76 break;
77 }
78 totalSamples += ret;
79
80 const auto now = std::chrono::high_resolution_clock::now();
81 if (timeLastSpin + std::chrono::milliseconds(300) < now)
82 {
83 timeLastSpin = now;
84 static const char spin[] = {"|/-\\"};
85 printf("\b%c", spin[(spinIndex++)%4]);
86 fflush(stdout);
87 }
88 //occasionally read out the stream status (non blocking)
89 if (timeLastStatus + std::chrono::seconds(1) < now)
90 {
91 timeLastStatus = now;
92 while (true)
93 {
94 size_t chanMask; int flags; long long timeNs;
95 ret = device->readStreamStatus(stream, chanMask, flags, timeNs, 0);
96 if (ret == SOAPY_SDR_OVERFLOW) overflows++;
97 else if (ret == SOAPY_SDR_UNDERFLOW) underflows++;
98 else if (ret == SOAPY_SDR_TIME_ERROR) {}
99 else break;
100 }
101 }
102 if (timeLastPrint + std::chrono::seconds(5) < now)
103 {
104 timeLastPrint = now;
105 const auto timePassed = std::chrono::duration_cast<std::chrono::microseconds>(now - startTime);
106 const auto sampleRate = double(totalSamples)/timePassed.count();
107 printf("\b%g Msps\t%g MBps", sampleRate, sampleRate*numChans*elemSize);
108 if (overflows != 0) printf("\tOverflows %u", overflows);
109 if (underflows != 0) printf("\tUnderflows %u", underflows);
110 printf("\n ");
111 }
112
113 }
114 device->deactivateStream(stream);
115 }
116
SoapySDRRateTest(const std::string & argStr,const double sampleRate,const std::string & channelStr,const std::string & directionStr)117 int SoapySDRRateTest(
118 const std::string &argStr,
119 const double sampleRate,
120 const std::string &channelStr,
121 const std::string &directionStr)
122 {
123 SoapySDR::Device *device(nullptr);
124
125 try
126 {
127 device = SoapySDR::Device::make(argStr);
128
129 //parse the direction to the integer enum
130 int direction(-1);
131 if (directionStr == "RX" or directionStr == "rx") direction = SOAPY_SDR_RX;
132 if (directionStr == "TX" or directionStr == "tx") direction = SOAPY_SDR_TX;
133 if (direction == -1) throw std::invalid_argument("direction not in RX/TX: " + directionStr);
134
135 //build channels list, using KwargsFromString is a easy parsing hack
136 std::vector<size_t> channels;
137 for (const auto &pair : SoapySDR::KwargsFromString(channelStr))
138 {
139 channels.push_back(std::stoi(pair.first));
140 }
141 if (channels.empty()) channels.push_back(0);
142
143 //initialize the sample rate for all channels
144 for (const auto &chan : channels)
145 {
146 device->setSampleRate(direction, chan, sampleRate);
147 }
148
149 //create the stream, use the native format
150 double fullScale(0.0);
151 const auto format = device->getNativeStreamFormat(direction, channels.front(), fullScale);
152 const size_t elemSize = SoapySDR::formatToSize(format);
153 auto stream = device->setupStream(direction, format, channels);
154
155 //run the rate test one setup is complete
156 std::cout << "Stream format: " << format << std::endl;
157 std::cout << "Num channels: " << channels.size() << std::endl;
158 std::cout << "Element size: " << elemSize << " bytes" << std::endl;
159 std::cout << "Begin " << directionStr << " rate test at " << (sampleRate/1e6) << " Msps" << std::endl;
160 runRateTestStreamLoop(device, stream, direction, channels.size(), elemSize);
161
162 //cleanup stream and device
163 device->closeStream(stream);
164 SoapySDR::Device::unmake(device);
165 }
166 catch (const std::exception &ex)
167 {
168 std::cerr << "Error in rate test: " << ex.what() << std::endl;
169 SoapySDR::Device::unmake(device);
170 return EXIT_FAILURE;
171 }
172 return EXIT_FAILURE;
173 }
174