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