annotate Source/Utility/TimestampSynchronizer.cpp @ 56:b4a2d2ae43cf tip

merge
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Fri, 23 Nov 2018 15:48:14 +0000
parents 3580ffe87dc8
children
rev   line source
andrewm@0 1 /*
andrewm@0 2 TouchKeys: multi-touch musical keyboard control software
andrewm@0 3 Copyright (c) 2013 Andrew McPherson
andrewm@0 4
andrewm@0 5 This program is free software: you can redistribute it and/or modify
andrewm@0 6 it under the terms of the GNU General Public License as published by
andrewm@0 7 the Free Software Foundation, either version 3 of the License, or
andrewm@0 8 (at your option) any later version.
andrewm@0 9
andrewm@0 10 This program is distributed in the hope that it will be useful,
andrewm@0 11 but WITHOUT ANY WARRANTY; without even the implied warranty of
andrewm@0 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
andrewm@0 13 GNU General Public License for more details.
andrewm@0 14
andrewm@0 15 You should have received a copy of the GNU General Public License
andrewm@0 16 along with this program. If not, see <http://www.gnu.org/licenses/>.
andrewm@0 17
andrewm@0 18 =====================================================================
andrewm@0 19
andrewm@0 20 TimestampSynchronizer.cpp: handles aligning timestamps between multiple
andrewm@0 21 asynchronous sources, while reducing the jitter that occurs when using
andrewm@0 22 system clock time for every received sample.
andrewm@0 23 */
andrewm@0 24
andrewm@0 25 #include "TimestampSynchronizer.h"
andrewm@0 26
andrewm@0 27 // Constructor
andrewm@0 28 TimestampSynchronizer::TimestampSynchronizer()
andrewm@0 29 : history_(kTimestampSynchronizerHistoryLength), nominalSampleInterval_(0), currentSampleInterval_(0),
andrewm@0 30 frameModulus_(0), startingClockTimeMilliseconds_(0), startingTimestamp_(0),
andrewm@0 31 bufferLengthCounter_(0)
andrewm@0 32 {
andrewm@0 33 }
andrewm@0 34
andrewm@0 35 // Clear the accumulated timestamp history and reset the current
andrewm@0 36 // value to its nominal "expected" value. Also (re-)establish
andrewm@0 37 // the relationship between system clock time and output timestamp.
andrewm@0 38 // If multiple streams are to be synchronized, they should be
andrewm@0 39 // initialized with the same values
andrewm@0 40
andrewm@0 41 void TimestampSynchronizer::initialize(double clockTimeMilliseconds,
andrewm@0 42 timestamp_type startingTimestamp) {
andrewm@0 43 history_.clear();
andrewm@0 44 currentSampleInterval_ = nominalSampleInterval_;
andrewm@0 45 startingClockTimeMilliseconds_ = clockTimeMilliseconds;
andrewm@0 46 startingTimestamp_ = startingTimestamp;
andrewm@0 47
andrewm@0 48 //cout << "initialize(): startingTimestamp = " << startingTimestamp_ << ", interval = " << nominalSampleInterval_ << endl;
andrewm@0 49 }
andrewm@0 50
andrewm@0 51 // Given a frame number, calculate a current timestamp
andrewm@0 52 timestamp_type TimestampSynchronizer::synchronizedTimestamp(int rawFrameNumber) {
andrewm@0 53 // Calculate the current system clock-related timestamp
andrewm@0 54 timestamp_type clockTime = startingTimestamp_ + milliseconds_to_timestamp(Time::getMillisecondCounterHiRes() - startingClockTimeMilliseconds_);
andrewm@0 55 timestamp_type frameTime;
andrewm@0 56
andrewm@0 57 // Retrieve the timestamp of the previous frame
andrewm@0 58 // Need at least 2 samples in the buffer for the calculations that follow
andrewm@0 59 if(history_.empty()) {
andrewm@0 60 frameTime = clockTime;
andrewm@0 61 }
andrewm@0 62 else if(history_.size() < 2) {
andrewm@0 63 // One sample in buffer: make sure the new sample is new before
andrewm@0 64 // storing it in the buffer.
andrewm@0 65
andrewm@0 66 int lastFrame = history_.latest().first;
andrewm@0 67
andrewm@0 68 frameTime = clockTime;
andrewm@0 69
andrewm@0 70 if(lastFrame == rawFrameNumber) // Don't reprocess identical frames
andrewm@0 71 return frameTime;
andrewm@0 72 }
andrewm@0 73 else {
andrewm@0 74 int totalHistoryFrames;
andrewm@0 75 int lastFrame = history_.latest().first;
andrewm@0 76 frameTime = history_.latest().second;
andrewm@0 77
andrewm@0 78 if(lastFrame == rawFrameNumber) // Don't reprocess identical frames
andrewm@0 79 return frameTime;
andrewm@0 80
andrewm@0 81 if(frameModulus_ == 0) {
andrewm@0 82 // No modulus, just compare the raw frame number to the last frame number
andrewm@0 83 frameTime += currentSampleInterval_ * (timestamp_type)(rawFrameNumber - lastFrame);
andrewm@0 84
andrewm@0 85 totalHistoryFrames = (history_.latest().first - history_.earliest().first);
andrewm@0 86 if(totalHistoryFrames <= 0) {
andrewm@0 87 //cout << "Warning: TimestampSynchronizer history buffer has a difference of " << totalHistoryFrames << " frames.\n";
andrewm@0 88 //cout << "Size = " << history_.size() << " first = " << history_.earliest().first << " last = " << history_.latest().first << endl;
andrewm@0 89 totalHistoryFrames = 1;
andrewm@0 90 }
andrewm@0 91 }
andrewm@0 92 else {
andrewm@0 93 // Use mod arithmetic to handle wraparounds in the frame number
andrewm@0 94 frameTime += currentSampleInterval_ * (timestamp_type)((rawFrameNumber + frameModulus_ - lastFrame) % frameModulus_);
andrewm@0 95
andrewm@0 96 totalHistoryFrames = (history_.latest().first - history_.earliest().first + frameModulus_) % frameModulus_;
andrewm@0 97 if(totalHistoryFrames <= 0) {
andrewm@0 98 //cout << "Warning: TimestampSynchronizer history buffer has a difference of " << totalHistoryFrames << " frames.\n";
andrewm@0 99 //cout << "Size = " << history_.size() << " first = " << history_.earliest().first << " last = " << history_.latest().first << endl;
andrewm@0 100
andrewm@0 101 totalHistoryFrames = 1;
andrewm@0 102 }
andrewm@0 103 }
andrewm@0 104
andrewm@0 105 // Recalculate the nominal sample interval by examining the difference in times
andrewm@0 106 // between first and last frames in the buffer.
andrewm@0 107
andrewm@0 108 currentSampleInterval_ = (history_.latestTimestamp() - history_.earliestTimestamp()) / (timestamp_diff_type)totalHistoryFrames;
andrewm@0 109
andrewm@0 110 // The frame time was just incremented by the current sample period. Check whether
andrewm@0 111 // this puts the frame time ahead of the clock time. Don't allow the frame time to get
andrewm@0 112 // ahead of the system clock (this will also push future frame timestamps back).
andrewm@0 113
andrewm@0 114 if(frameTime > clockTime) {
andrewm@0 115 //cout << "CLIP " << 100.0 * (frameTime - clockTime) / currentSampleInterval_ << "%: frame=" << frameTime << " to clock=" << clockTime << endl;
andrewm@0 116 frameTime = clockTime;
andrewm@0 117 }
andrewm@0 118
andrewm@0 119 bufferLengthCounter_++;
andrewm@0 120
andrewm@0 121 if(bufferLengthCounter_ >= kTimestampSynchronizerHistoryLength) {
andrewm@0 122 //timestamp_diff_type currentLatency = clockTime - frameTime;
andrewm@0 123 timestamp_diff_type maxLatency = 0, minLatency = 1000000.0;
andrewm@0 124
andrewm@0 125 Node<pair<int, timestamp_type> >::iterator it;
andrewm@0 126
andrewm@0 127 for(it = history_.begin(); it != history_.end(); ++it) {
andrewm@0 128 timestamp_diff_type l = (it.timestamp() - it->second);
andrewm@0 129 if(l > maxLatency)
andrewm@0 130 maxLatency = l;
andrewm@0 131 if(l < minLatency)
andrewm@0 132 minLatency = l;
andrewm@0 133 }
andrewm@0 134
andrewm@0 135 //cout << "frame " << rawFrameNumber << ": rate = " << currentSampleInterval_ << " clock = " << clockTime << " frame = " << frameTime << " latency = "
andrewm@0 136 // << currentLatency << " max = " << maxLatency << " min = " << minLatency << endl;
andrewm@0 137
andrewm@0 138 //timestamp_diff_type targetMinLatency = (maxLatency - minLatency) * 2.0 / sqrt(kTimestampSynchronizerHistoryLength);
andrewm@0 139
andrewm@0 140 /*if(minLatency > targetMinLatency) {
andrewm@0 141 cout << "ADDING " << 50.0 * (minLatency - targetMinLatency) / (currentSampleInterval_) << "%: (target " << targetMinLatency << ")\n";
andrewm@0 142 frameTime += (minLatency - targetMinLatency) / 2.0;
andrewm@0 143 }*/
andrewm@0 144 //frameTime += minLatency / 4.0;
andrewm@0 145
andrewm@0 146 bufferLengthCounter_ = 0;
andrewm@0 147 }
andrewm@0 148 }
andrewm@0 149
andrewm@0 150 // Insert the new frame time and clock times into the buffer
andrewm@0 151 history_.insert(pair<int, timestamp_type>(rawFrameNumber, frameTime), clockTime);
andrewm@0 152
andrewm@0 153 // The timestamp we return is associated with the frame, not the clock (which is potentially much
andrewm@0 154 // higher jitter)
andrewm@0 155 return frameTime;
andrewm@0 156 }