1 //*****************************************************************
2 /*
3   JackTrip: A System for High-Quality Audio Network Performance
4   over the Internet
5 
6   Copyright (c) 2020 Julius Smith, Juan-Pablo Caceres, Chris Chafe.
7   SoundWIRE group at CCRMA, Stanford University.
8 
9   Permission is hereby granted, free of charge, to any person
10   obtaining a copy of this software and associated documentation
11   files (the "Software"), to deal in the Software without
12   restriction, including without limitation the rights to use,
13   copy, modify, merge, publish, distribute, sublicense, and/or sell
14   copies of the Software, and to permit persons to whom the
15   Software is furnished to do so, subject to the following
16   conditions:
17 
18   The above copyright notice and this permission notice shall be
19   included in all copies or substantial portions of the Software.
20 
21   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
23   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
25   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
26   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
28   OTHER DEALINGS IN THE SOFTWARE.
29 */
30 //*****************************************************************
31 
32 /**
33  * \file AudioTester.cpp
34  * \author Julius Smith
35  * \license MIT
36  * \date Aug-Oct 2020
37  */
38 
39 #include "AudioTester.h"
40 #include <assert.h>
41 
42 // Called 1st in Audiointerface.cpp
lookForReturnPulse(QVarLengthArray<sample_t * > & out_buffer,unsigned int n_frames)43 void AudioTester::lookForReturnPulse(QVarLengthArray<sample_t*>& out_buffer,
44                                      unsigned int n_frames) {
45   if (not enabled) {
46     std::cerr << "*** AudioTester.h: lookForReturnPulse: NOT ENABLED\n";
47     return;
48   }
49   if (impulsePending) { // look for return impulse in channel sendChannel:
50     assert(sendChannel<out_buffer.size());
51     for (uint n=0; n<n_frames; n++) {
52       float amp = out_buffer[sendChannel][n];
53       if (amp > 0.5 * ampCellHeight) { // got something
54         int cellNum =  getImpulseCellNum(out_buffer[sendChannel][n]);
55         if (cellNum != pendingCell) { // not our impulse!
56           std::cerr <<
57             "*** AudioTester.h: computeProcessFromNetwork: Received pulse amplitude "
58                     << amp << " (cell " << cellNum << ") while looking for cell "
59                     << pendingCell << "\n";
60 
61           if (cellNum > pendingCell) { // we missed it
62             std::cerr << " - ABORTING CURRENT PULSE\n";
63             impulsePending = false;
64           } else { // somehow we got the previous pulse again - repeated packet or underrun-caused repetition (old buffer)
65             std::cerr << " - IGNORING FOUND PULSE WAITING FURTHER\n";
66           }
67         } else { // found our impulse:
68           int64_t elapsedSamples = -1;
69           if (n >= n_frames-1) {
70             // Impulse timestamp didn't make it so we skip this one.
71           } else {
72             float sampleCountWhenImpulseSent = - 32768.0f * out_buffer[sendChannel][n+1];
73             elapsedSamples = sampleCountSinceImpulse + n - int64_t(sampleCountWhenImpulseSent);
74             sampleCountSinceImpulse = 1; // reset sample counter between impulses
75             roundTripCount += 1.0;
76           }
77           // int64_t curTimeUS = timeMicroSec(); // time since launch in us
78           // int64_t impulseDelayUS = curTimeUS - ImpulseTimeUS;
79           // float impulseDelaySec = float(impulseDelayUS) * 1.0e-6;
80           // float impulseDelayBuffers = impulseDelaySec / (float(n_frames)/float(sampleRate));
81           // int64_t impulseDelayMS = (int64_t)round(double(impulseDelayUS)/1000.0);
82           if (elapsedSamples > 0) { // found impulse and reset, time to print buffer results:
83             double elapsedSamplesMS = 1000.0 * double(elapsedSamples)/double(sampleRate); // ms
84             extendLatencyHistogram(elapsedSamplesMS);
85             if (roundTripCount > 1.0) {
86               double prevSum = roundTripMean * (roundTripCount-1.0); // undo previous normalization
87               roundTripMean = (prevSum + elapsedSamplesMS) / roundTripCount; // add latest and renormalize
88               double prevSumSq = roundTripMeanSquare * (roundTripCount-1.0); // undo previous normalization
89               roundTripMeanSquare = (prevSumSq + elapsedSamplesMS*elapsedSamplesMS) / roundTripCount;
90             } else { // just getting started:
91               roundTripMean = elapsedSamplesMS;
92               roundTripMeanSquare = elapsedSamplesMS * elapsedSamplesMS;
93             }
94             if (roundTripCount == 1.0) {
95               printf("JackTrip Test Mode (option -x printIntervalInSeconds=%0.3f)\n",printIntervalSec);
96               printf("\tA test impulse-train is output on channel %d (from 0) with repeatedly ramping amplitude\n",
97                      sendChannel);
98               if (printIntervalSec == 0.0) {
99                 printf("\tPrinting each audio buffer round-trip latency in ms followed by cumulative (mean and [standard deviation])");
100               } else {
101                 printf("\tPrinting cumulative mean and [standard deviation] of audio round-trip latency in ms");
102                 printf(" every %0.3f seconds", printIntervalSec);
103               }
104               printf(" after skipping first %d buffers:\n", bufferSkipStart);
105               // not printing this presently: printf("( * means buffer skipped due missing timestamp or lost impulse)\n");
106               lastPrintTimeUS = timeMicroSec();
107             }
108             //printf("%d (%d) ", elapsedSamplesMS, impulseDelayMS); // measured time is "buffer time" not sample time
109             int64_t curTimeUS = timeMicroSec(); // time since launch in us
110             double timeSinceLastPrintUS = double(curTimeUS - lastPrintTimeUS);
111             double stdDev = sqrt(std::max(0.0, (roundTripMeanSquare - (roundTripMean*roundTripMean))));
112             if (timeSinceLastPrintUS >= printIntervalSec * 1.0e6) {
113               if (printIntervalSec == 0.0) { printf("%0.1f (", elapsedSamplesMS); }
114               printf("%0.1f [%0.1f]", roundTripMean, stdDev);
115               if (printIntervalSec == 0.0) { printf(") "); } else { printf(" "); }
116               lastPrintTimeUS = curTimeUS;
117               if (printIntervalSec >= 1.0) { // print histogram
118                 std::cout << "\n" << getLatencyHistogramString() << "\n";
119               }
120             }
121             std::cout << std::flush;
122           } else {
123             // not printing this presently: printf("* "); // we got the impulse but lost its timestamp in samples
124           }
125           impulsePending = false;
126         } // found our impulse
127           // remain pending until timeout, hoping to find our return pulse
128       } // got something
129     } // loop over samples
130     sampleCountSinceImpulse += n_frames; // gets reset to 1 when impulse is found, counts freely until then
131   } // ImpulsePending
132 }
133 
134 // Called 2nd in Audiointerface.cpp
writeImpulse(QVarLengthArray<sample_t * > & mInBufCopy,unsigned int n_frames)135 void AudioTester::writeImpulse(QVarLengthArray<sample_t*>& mInBufCopy,
136                                unsigned int n_frames) {
137   if (not enabled) {
138     std::cerr << "*** AudioTester.h: writeImpulse: NOT ENABLED\n";
139     return;
140   }
141   if (bufferSkip <= 0) { // send test signals (-x option)
142     bool sendImpulse;
143     if (impulsePending) {
144       sendImpulse = false; // unless:
145       const uint64_t timeOut = 500e3; // time out after waiting 500 ms
146       if (timeMicroSec() > (impulseTimeUS + timeOut)) {
147         sendImpulse = true;
148         std::cout << "\n*** Audio Latency Test (-x): TIMED OUT waiting for return impulse *** sending a new one\n";
149       }
150     } else { // time for the next repeating impulse:
151       sendImpulse = true;
152     }
153     if (sendImpulse) {
154       assert(sendChannel < mInBufCopy.size());
155       mInBufCopy[sendChannel][0] = getImpulseAmp();
156       for (uint n=1; n<n_frames; n++) {
157         mInBufCopy[sendChannel][n] = 0;
158       }
159       impulsePending = true;
160       impulseTimeUS = timeMicroSec();
161       impulseTimeSamples = sampleCountSinceImpulse; // timer in samples for current impulse loopback test
162       // Also send impulse time:
163       if (n_frames>1) { // always true?
164         mInBufCopy[sendChannel][1] = -float(impulseTimeSamples)/32768.0f; // survives if there is no digital processing at the server
165       } else {
166         std::cerr << "\n*** AudioTester.h: Timestamp cannot fit into a lenth " << n_frames << " buffer ***\n";
167       }
168     } else {
169       mInBufCopy[sendChannel][0] = 0.0f; // send zeros until a new impulse is needed
170       if (n_frames>1) {
171         mInBufCopy[sendChannel][1] = 0.0f;
172       }
173     }
174   } else {
175     bufferSkip--;
176   }
177 }
178 
printHelp(char * command,char helpCase)179 void AudioTester::printHelp(char* command, [[maybe_unused]] char helpCase) {
180   std::cout << "HELP for \"" << command << " printIntervalSec\" // (end-of-line comments start with `//'):\n";
181   std::cout << "\n";
182   std::cout << "Print roundtrip audio delay statistics for the highest-numbered audio channel every printIntervalSec seconds,\n";
183   std::cout << "including an ASCII latency histogram if printIntervalSec is 1.0 or more.\n";
184   std::cout << "\n";
185   std::cout << "A test impulse is sent to the server in the last audio channel,\n";
186   std::cout << "  the number of samples until it returns is measured, and this repeats.\n";
187   std::cout << "The jacktrip server must provide audio loopback (e.g., -p4).\n";
188   std::cout << "The cumulative mean and standard-deviation (\"statistics\") are computed for the measured loopback times,\n";
189   std::cout << "  and printed every printIntervalSec seconds.\n";
190   std::cout << "If printIntervalSec is zero, the roundtrip-time and statistics in milliseconds are printed for each individual impulse.\n";
191   std::cout << "If printIntervalSec is positive, statistics are printed after each print interval, with no individual measurements.\n";
192   std::cout << "If printIntervalSec is 1.0 or larger, a cumulative histogram of all impulse roundtrip-times is printed as well.\n";
193   std::cout << "The first 100 audio buffers are skipped in order to measure only steady-state network-audio-delay performance.\n";
194   std::cout << "Lower audio channels are not affected, enabling latency measurement and display during normal operation.\n";
195 }
196