2019-03-22 14:42:31 +03:00
|
|
|
/* -*- 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
|
2019-10-02 13:23:02 +03:00
|
|
|
* from the MediaTrackGraph.
|
2019-03-22 14:42:31 +03:00
|
|
|
*
|
2019-10-02 13:23:02 +03:00
|
|
|
* Drift can occur because audio is driven by a MediaTrackGraph running off an
|
2019-03-22 14:42:31 +03:00
|
|
|
* 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.
|
2019-10-02 13:23:02 +03:00
|
|
|
Atomic<TrackTime> mAudioSamples{0};
|
2019-03-22 14:42:31 +03:00
|
|
|
|
|
|
|
// 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.
|
|
|
|
*/
|
2019-10-02 13:23:02 +03:00
|
|
|
void NotifyAudio(TrackTime aSamples) {
|
2019-03-22 14:42:31 +03:00
|
|
|
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());
|
2019-10-02 13:23:02 +03:00
|
|
|
TrackTime samples = mAudioSamples;
|
2019-03-22 14:42:31 +03:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-04-08 17:27:13 +03:00
|
|
|
if (aNow == mAudioStartTime) {
|
|
|
|
LOG(LogLevel::Warning,
|
|
|
|
"DriftCompensator %p video scale 0, assuming no drift", this);
|
|
|
|
return aTime;
|
2019-03-22 14:42:31 +03:00
|
|
|
}
|
|
|
|
|
2019-04-08 17:27:13 +03:00
|
|
|
double videoScaleUs = (aNow - mAudioStartTime).ToMicroseconds();
|
|
|
|
double audioScaleUs = FramesToUsecs(samples, mAudioRate).value();
|
|
|
|
double videoDurationUs = (aTime - mAudioStartTime).ToMicroseconds();
|
|
|
|
|
2019-03-22 14:42:31 +03:00
|
|
|
TimeStamp reclocked =
|
2019-04-08 17:27:13 +03:00
|
|
|
mAudioStartTime + TimeDuration::FromMicroseconds(
|
|
|
|
videoDurationUs * audioScaleUs / videoScaleUs);
|
2019-03-22 14:42:31 +03:00
|
|
|
|
|
|
|
LOG(LogLevel::Debug,
|
|
|
|
"DriftCompensator %p GetVideoTime, v-now: %.3fs, a-now: %.3fs; %.3fs "
|
|
|
|
"-> %.3fs (d %.3fms)",
|
|
|
|
this, (aNow - mAudioStartTime).ToSeconds(),
|
2019-04-08 17:27:13 +03:00
|
|
|
TimeDuration::FromMicroseconds(audioScaleUs).ToSeconds(),
|
2019-03-22 14:42:31 +03:00
|
|
|
(aTime - mAudioStartTime).ToSeconds(),
|
|
|
|
(reclocked - mAudioStartTime).ToSeconds(),
|
|
|
|
(reclocked - aTime).ToMilliseconds());
|
|
|
|
|
|
|
|
return reclocked;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
#undef LOG
|
|
|
|
|
|
|
|
} // namespace mozilla
|
|
|
|
|
|
|
|
#endif /* DriftCompensation_h_ */
|