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