blob: 6a14e075e5844cceb8557bb38df7a684830806ac [file] [log] [blame]
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.camera.video.internal.workaround;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.camera.core.Logger;
import androidx.camera.core.impl.Timebase;
import androidx.camera.video.internal.compat.quirk.CameraUseInconsistentTimebaseQuirk;
import androidx.camera.video.internal.encoder.TimeProvider;
/**
* Converts the video timestamps to {@link Timebase#UPTIME} if video buffer contains
* {@link Timebase#REALTIME} timestamp.
*
* <p>The workaround accepts an {@code null} input timebase. This is useful when the timebase is
* unknown, such as the problem described in b/197805856. If the input timebase is null, an
* automatic detection mechanism is used to determine the timebase, which is by checking the input
* timestamp is close to UPTIME or REALTIME. For performance reason, the detection will only check
* the first input timestamp.
*
* @see CameraUseInconsistentTimebaseQuirk
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public class VideoTimebaseConverter {
private static final String TAG = "VideoTimebaseConverter";
private final TimeProvider mTimeProvider;
private long mUptimeToRealtimeOffsetUs = -1L;
private Timebase mInputTimebase;
/**
* Constructs the VideoTimebaseConverter.
*
* @param timeProvider the time provider.
* @param inputTimebase the input video frame timebase. {@code null} if the timebase is unknown.
*/
public VideoTimebaseConverter(@NonNull TimeProvider timeProvider,
@Nullable Timebase inputTimebase) {
mTimeProvider = timeProvider;
mInputTimebase = inputTimebase;
}
/**
* Converts the video timestamp to {@link Timebase#UPTIME} if necessary.
*
* @param timestampUs the video frame timestamp in micro seconds. The timebase is supposed
* to be the input timebase in constructor.
*/
public long convertToUptimeUs(long timestampUs) {
if (mInputTimebase == null) {
if (isCloseToRealtime(timestampUs)) {
Logger.w(TAG, "Detected video buffer timestamp is close to realtime.");
mInputTimebase = Timebase.REALTIME;
} else {
mInputTimebase = Timebase.UPTIME;
}
}
switch (mInputTimebase) {
case REALTIME:
if (mUptimeToRealtimeOffsetUs == -1) {
mUptimeToRealtimeOffsetUs = calculateUptimeToRealtimeOffsetUs();
}
return timestampUs - mUptimeToRealtimeOffsetUs;
case UPTIME:
return timestampUs;
default:
throw new AssertionError("Unknown timebase: " + mInputTimebase);
}
}
private boolean isCloseToRealtime(long timeUs) {
long uptimeUs = mTimeProvider.uptimeUs();
long realtimeUs = mTimeProvider.realtimeUs();
return Math.abs(timeUs - realtimeUs) < Math.abs(timeUs - uptimeUs);
}
// The algorithm is from camera framework Camera3Device.cpp
private long calculateUptimeToRealtimeOffsetUs() {
// Try three times to get the clock offset, choose the one with the minimum gap in
// measurements.
long bestGap = Long.MAX_VALUE;
long measured = 0L;
for (int i = 0; i < 3; i++) {
long uptime1 = mTimeProvider.uptimeUs();
long realtime = mTimeProvider.realtimeUs();
long uptime2 = mTimeProvider.uptimeUs();
long gap = uptime2 - uptime1;
if (i == 0 || gap < bestGap) {
bestGap = gap;
measured = realtime - ((uptime1 + uptime2) >> 1);
}
}
return Math.max(0, measured);
}
}