Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 https://mozilla.org/MPL/2.0/. */
#ifndef DriftCompensation_h_
#define DriftCompensation_h_
#include "MediaSegment.h"
#include "VideoUtils.h"
#include "mozilla/Atomics.h"
#include "mozilla/Unused.h"
namespace mozilla {
static LazyLogModule gDriftCompensatorLog("DriftCompensator");
#define LOG(type, ...) MOZ_LOG(gDriftCompensatorLog, type, (__VA_ARGS__))
/**
* DriftCompensator can be used to handle drift between audio and video tracks
* from the MediaTrackGraph.
*
* Drift can occur because audio is driven by a MediaTrackGraph running off an
* audio callback, thus it's progressed by the clock of one the audio output
* devices on the user's machine. Video on the other hand is always expressed in
* wall-clock TimeStamps, i.e., it's progressed by the system clock. These
* clocks will, over time, drift apart.
*
* Do not use the DriftCompensator across multiple audio tracks, as it will
* automatically record the start time of the first audio samples, and all
* samples for the same audio track on the same audio clock will have to be
* processed to retain accuracy.
*
* DriftCompensator is designed to be used from two threads:
* - The audio thread for notifications of audio samples.
* - The video thread for compensating drift of video frames to match the audio
* clock.
*/
class DriftCompensator {
const RefPtr<nsIEventTarget> mVideoThread;
const TrackRate mAudioRate;
// Number of audio samples produced. Any thread.
Atomic<TrackTime> mAudioSamples{0};
// Time the first audio samples were added. mVideoThread only.
TimeStamp mAudioStartTime;
void SetAudioStartTime(TimeStamp aTime) {
MOZ_ASSERT(mVideoThread->IsOnCurrentThread());
MOZ_ASSERT(mAudioStartTime.IsNull());
mAudioStartTime = aTime;
}
protected:
virtual ~DriftCompensator() = default;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DriftCompensator)
DriftCompensator(RefPtr<nsIEventTarget> aVideoThread, TrackRate aAudioRate)
: mVideoThread(std::move(aVideoThread)), mAudioRate(aAudioRate) {
MOZ_ASSERT(mAudioRate > 0);
}
void NotifyAudioStart(TimeStamp aStart) {
MOZ_ASSERT(mAudioSamples == 0);
LOG(LogLevel::Info, "DriftCompensator %p at rate %d started", this,
mAudioRate);
nsresult rv = mVideoThread->Dispatch(NewRunnableMethod<TimeStamp>(
"DriftCompensator::SetAudioStartTime", this,
&DriftCompensator::SetAudioStartTime, aStart));
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
Unused << rv;
}
/**
* aSamples is the number of samples fed by an AudioStream.
*/
void NotifyAudio(TrackTime aSamples) {
MOZ_ASSERT(aSamples > 0);
mAudioSamples += aSamples;
LOG(LogLevel::Verbose,
"DriftCompensator %p Processed another %" PRId64
" samples; now %.3fs audio",
this, aSamples, static_cast<double>(mAudioSamples) / mAudioRate);
}
/**
* Drift compensates a video TimeStamp based on historical audio data.
*/
virtual TimeStamp GetVideoTime(TimeStamp aNow, TimeStamp aTime) {
MOZ_ASSERT(mVideoThread->IsOnCurrentThread());
TrackTime samples = mAudioSamples;
if (samples / mAudioRate < 10) {
// We don't apply compensation for the first 10 seconds because of the
// higher inaccuracy during this time.
LOG(LogLevel::Debug, "DriftCompensator %p %" PRId64 "ms so far; ignoring",
this, samples * 1000 / mAudioRate);
return aTime;
}
if (aNow == mAudioStartTime) {
LOG(LogLevel::Warning,
"DriftCompensator %p video scale 0, assuming no drift", this);
return aTime;
}
double videoScaleUs = (aNow - mAudioStartTime).ToMicroseconds();
double audioScaleUs = FramesToUsecs(samples, mAudioRate).value();
double videoDurationUs = (aTime - mAudioStartTime).ToMicroseconds();
TimeStamp reclocked =
mAudioStartTime + TimeDuration::FromMicroseconds(
videoDurationUs * audioScaleUs / videoScaleUs);
LOG(LogLevel::Debug,
"DriftCompensator %p GetVideoTime, v-now: %.3fs, a-now: %.3fs; %.3fs "
"-> %.3fs (d %.3fms)",
this, (aNow - mAudioStartTime).ToSeconds(),
TimeDuration::FromMicroseconds(audioScaleUs).ToSeconds(),
(aTime - mAudioStartTime).ToSeconds(),
(reclocked - mAudioStartTime).ToSeconds(),
(reclocked - aTime).ToMilliseconds());
return reclocked;
}
};
#undef LOG
} // namespace mozilla
#endif /* DriftCompensation_h_ */