Instead of once a second, buffering level measurements are performed after each output packet for which an input packet has recently arrived. AudioDriftCorrection::RequestFrames() targets desired buffering at 13/10 * MeasuredSourceLatency() if actual buffering stays near desired. 20% less than this is 104/100 * MeasuredSourceLatency(). When input packets are much larger than output packets, then when this much is buffered after an input packet arrives, then the buffer was close to underruning before the packet arrived. Differential Revision: https://phabricator.services.mozilla.com/D215489
172 lines
7.0 KiB
C++
172 lines
7.0 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#ifndef DOM_MEDIA_DRIFTCONTROL_DRIFTCONTROLLER_H_
|
|
#define DOM_MEDIA_DRIFTCONTROL_DRIFTCONTROLLER_H_
|
|
|
|
#include "TimeUnits.h"
|
|
#include "mozilla/RollingMean.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
|
|
#include "MediaSegment.h"
|
|
|
|
namespace mozilla {
|
|
|
|
/**
|
|
* DriftController calculates the divergence of the source clock from its
|
|
* nominal (provided) rate compared to that of the target clock, which drives
|
|
* the calculations.
|
|
*
|
|
* The DriftController looks at how the current buffering level differs from the
|
|
* desired buffering level and sets a corrected source rate. A resampler should
|
|
* be configured to resample from the corrected source rate to the nominal
|
|
* target rate. It assumes that the resampler is initially configured to
|
|
* resample from the nominal source rate to the nominal target rate.
|
|
*
|
|
* The pref `media.clock drift.buffering` can be used to configure the minimum
|
|
* initial desired internal buffering. Right now it is at 50ms. A larger desired
|
|
* buffering level will be used if deemed necessary based on input device
|
|
* latency, reported or observed. It will also be increased as a response to an
|
|
* underrun, since that indicates the buffer was too small.
|
|
*/
|
|
class DriftController final {
|
|
public:
|
|
/**
|
|
* Provide the nominal source and the target sample rate.
|
|
*/
|
|
DriftController(uint32_t aSourceRate, uint32_t aTargetRate,
|
|
media::TimeUnit aDesiredBuffering);
|
|
|
|
/**
|
|
* Set the buffering level that the controller should target.
|
|
*/
|
|
void SetDesiredBuffering(media::TimeUnit aDesiredBuffering);
|
|
|
|
/**
|
|
* Reset internal state in a way that is suitable for handling an underrun.
|
|
*/
|
|
void ResetAfterUnderrun();
|
|
|
|
/**
|
|
* Returns the drift-corrected source rate.
|
|
*/
|
|
uint32_t GetCorrectedSourceRate() const;
|
|
|
|
/**
|
|
* The number of times mCorrectedSourceRate has been changed to adjust to
|
|
* drift.
|
|
*/
|
|
uint32_t NumCorrectionChanges() const { return mNumCorrectionChanges; }
|
|
|
|
/**
|
|
* The amount of time that the difference between the buffering level and
|
|
* the desired value has been both less than 20% of the desired level and
|
|
* less than 10ms of buffered frames.
|
|
*/
|
|
media::TimeUnit DurationNearDesired() const { return mDurationNearDesired; }
|
|
|
|
/**
|
|
* The amount of time that has passed since the last time SetDesiredBuffering
|
|
* was called.
|
|
*/
|
|
media::TimeUnit DurationSinceDesiredBufferingChange() const {
|
|
return mTotalTargetClock - mLastDesiredBufferingChangeTime;
|
|
}
|
|
|
|
/**
|
|
* A rolling window average measurement of source latency by looking at the
|
|
* duration of the source buffer.
|
|
*/
|
|
media::TimeUnit MeasuredSourceLatency() const {
|
|
return mMeasuredSourceLatency.mean();
|
|
}
|
|
|
|
/**
|
|
* Update the available source frames, target frames, and the current
|
|
* buffer, in every iteration. If the conditions are met a new correction is
|
|
* calculated. A new correction is calculated every mAdjustmentInterval. In
|
|
* addition to that, the correction is clamped so that the output sample rate
|
|
* changes by at most 0.1% of its nominal rate each correction.
|
|
*/
|
|
void UpdateClock(media::TimeUnit aSourceDuration,
|
|
media::TimeUnit aTargetDuration, uint32_t aBufferedFrames,
|
|
uint32_t aBufferSize);
|
|
|
|
private:
|
|
int64_t NearThreshold() const;
|
|
// Adjust mCorrectedSourceRate for the current values of mDriftEstimate and
|
|
// mAvgBufferedFramesEst - mDesiredBuffering.ToTicksAtRate(mSourceRate).
|
|
//
|
|
// mCorrectedSourceRate is not changed if it is not expected to cause an
|
|
// overshoot during the next mAdjustmentInterval and is expected to bring
|
|
// mAvgBufferedFramesEst to the desired level within 30s or is within
|
|
// 1 frame/sec of a rate which would converge within 30s.
|
|
//
|
|
// Otherwise, mCorrectedSourceRate is set so as to aim to have
|
|
// mAvgBufferedFramesEst converge to the desired value in 15s.
|
|
// If the buffering level is higher than desired, then mCorrectedSourceRate
|
|
// must be higher than expected from mDriftEstimate to consume input
|
|
// data faster.
|
|
//
|
|
// Changes to mCorrectedSourceRate are capped at mSourceRate/1000 to avoid
|
|
// rapid changes.
|
|
void CalculateCorrection(uint32_t aBufferedFrames, uint32_t aBufferSize);
|
|
|
|
public:
|
|
const uint8_t mPlotId;
|
|
const uint32_t mSourceRate;
|
|
const uint32_t mTargetRate;
|
|
const media::TimeUnit mAdjustmentInterval = media::TimeUnit::FromSeconds(1);
|
|
|
|
private:
|
|
media::TimeUnit mDesiredBuffering;
|
|
float mCorrectedSourceRate;
|
|
media::TimeUnit mDurationNearDesired;
|
|
uint32_t mNumCorrectionChanges = 0;
|
|
// Moving averages of input and output durations, used in a ratio to
|
|
// estimate clock drift. Each average is calculated using packet durations
|
|
// from the same time intervals (between output requests), with the same
|
|
// weights, to support their use as a ratio. Durations from many packets
|
|
// are essentially summed (with consistent denominators) to provide
|
|
// longish-term measures of clock advance. These are independent of any
|
|
// corrections in resampling ratio.
|
|
double mInputDurationAvg = 0.0;
|
|
double mOutputDurationAvg = 0.0;
|
|
// Moving average of mInputDurationAvg/mOutputDurationAvg to smooth
|
|
// out short-term deviations from an estimated longish-term drift rate.
|
|
// Greater than 1 means the input clock has advanced faster than the output
|
|
// clock. This is the output of a second low pass filter stage.
|
|
double mDriftEstimate = 1.0;
|
|
// Output of the first low pass filter stage for mDriftEstimate
|
|
double mStage1Drift = 1.0;
|
|
// Estimate of the average buffering level after each output request, in
|
|
// input frames (and fractions thereof), smoothed to reduce the effect of
|
|
// short term variations. This is adjusted for estimated clock drift and for
|
|
// corrections in the resampling ratio. This is the output of a second low
|
|
// pass filter stage.
|
|
double mAvgBufferedFramesEst = 0.0;
|
|
// Output of the first low pass filter stage for mAvgBufferedFramesEst
|
|
double mStage1Buffered = 0.0;
|
|
// Whether handling an underrun, including waiting for the first input sample.
|
|
bool mIsHandlingUnderrun = true;
|
|
// An estimate of the source's latency, i.e. callback buffer size, in frames.
|
|
// Like mInputDurationAvg, this measures the duration arriving between each
|
|
// output request, but mMeasuredSourceLatency does not include zero
|
|
// duration measurements.
|
|
RollingMean<media::TimeUnit, media::TimeUnit> mMeasuredSourceLatency;
|
|
// An estimate of the target's latency, i.e. callback buffer size, in frames.
|
|
RollingMean<media::TimeUnit, media::TimeUnit> mMeasuredTargetLatency;
|
|
|
|
media::TimeUnit mTargetClock;
|
|
media::TimeUnit mTotalTargetClock;
|
|
media::TimeUnit mTargetClockAfterLastSourcePacket;
|
|
media::TimeUnit mLastDesiredBufferingChangeTime;
|
|
};
|
|
|
|
} // namespace mozilla
|
|
#endif // DOM_MEDIA_DRIFTCONTROL_DRIFTCONTROLLER_H_
|