public final class

TimestampAdjuster

extends java.lang.Object

 java.lang.Object

↳androidx.media3.common.util.TimestampAdjuster

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-common', version: '1.5.0-alpha01'

  • groupId: androidx.media3
  • artifactId: media3-common
  • version: 1.5.0-alpha01

Artifact androidx.media3:media3-common:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)

Overview

Adjusts and offsets sample timestamps. MPEG-2 TS timestamps scaling and adjustment is supported, taking into account timestamp rollover.

Summary

Fields
public static final longMODE_NO_OFFSET

A special firstSampleTimestampUs value indicating that presentation timestamps should not be offset.

public static final longMODE_SHARED

A special firstSampleTimestampUs value indicating that the adjuster will be shared by multiple threads.

Constructors
publicTimestampAdjuster(long firstSampleTimestampUs)

Methods
public synchronized longadjustSampleTimestamp(long timeUs)

Offsets a timestamp in microseconds.

public synchronized longadjustTsTimestamp(long pts90Khz)

Scales and offsets an MPEG-2 TS presentation timestamp considering wraparound.

public synchronized longadjustTsTimestampGreaterThanPreviousTimestamp(long pts90Khz)

Scales and offsets an MPEG-2 TS presentation timestamp considering wraparound.

public synchronized longgetFirstSampleTimestampUs()

Returns the value of the first adjusted sample timestamp in microseconds, or C.TIME_UNSET if timestamps will not be offset or if the adjuster is in shared mode.

public synchronized longgetLastAdjustedTimestampUs()

Returns the last adjusted timestamp, in microseconds.

public synchronized longgetTimestampOffsetUs()

Returns the offset between the input of TimestampAdjuster.adjustSampleTimestamp(long) and its output, or C.TIME_UNSET if the offset has not yet been determined.

public synchronized booleanisInitialized()

Returns whether the instance is initialized with a timestamp offset.

public static longptsToUs(long pts)

Converts a 90 kHz clock timestamp to a timestamp in microseconds.

public synchronized voidreset(long firstSampleTimestampUs)

Resets the instance.

public synchronized voidsharedInitializeOrWait(boolean canInitialize, long nextSampleTimestampUs, long timeoutMs)

For shared timestamp adjusters, performs necessary initialization actions for a caller.

public static longusToNonWrappedPts(long us)

Converts a timestamp in microseconds to a 90 kHz clock timestamp.

public static longusToWrappedPts(long us)

Converts a timestamp in microseconds to a 90 kHz clock timestamp, performing wraparound to keep the result within 33-bits.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final long MODE_NO_OFFSET

A special firstSampleTimestampUs value indicating that presentation timestamps should not be offset. In this mode:

public static final long MODE_SHARED

A special firstSampleTimestampUs value indicating that the adjuster will be shared by multiple threads. In this mode:

Constructors

public TimestampAdjuster(long firstSampleTimestampUs)

Parameters:

firstSampleTimestampUs: The desired value of the first adjusted sample timestamp in microseconds, or TimestampAdjuster.MODE_NO_OFFSET if timestamps should not be offset, or TimestampAdjuster.MODE_SHARED if the adjuster will be used in shared mode.

Methods

public synchronized void sharedInitializeOrWait(boolean canInitialize, long nextSampleTimestampUs, long timeoutMs)

For shared timestamp adjusters, performs necessary initialization actions for a caller.

  • If the adjuster has already established a timestamp offset then this method is a no-op.
  • If canInitialize is true and the adjuster has not yet established a timestamp offset, then the adjuster records the desired first sample timestamp for the calling thread and returns to allow the caller to proceed. If the timestamp offset has still not been established when the caller attempts to adjust its first timestamp, then the recorded timestamp is used to set it.
  • If canInitialize is false and the adjuster has not yet established a timestamp offset, then the call blocks until the timestamp offset is set.

Parameters:

canInitialize: Whether the caller is able to initialize the adjuster, if needed.
nextSampleTimestampUs: The desired timestamp for the next sample loaded by the calling thread, in microseconds. Only used if canInitialize is true.
timeoutMs: The timeout for the thread to wait for the timestamp adjuster to initialize, in milliseconds. A timeout of zero is interpreted as an infinite timeout.

public synchronized long getFirstSampleTimestampUs()

Returns the value of the first adjusted sample timestamp in microseconds, or C.TIME_UNSET if timestamps will not be offset or if the adjuster is in shared mode.

public synchronized long getLastAdjustedTimestampUs()

Returns the last adjusted timestamp, in microseconds. If no timestamps have been adjusted yet then the result of TimestampAdjuster.getFirstSampleTimestampUs() is returned.

public synchronized long getTimestampOffsetUs()

Returns the offset between the input of TimestampAdjuster.adjustSampleTimestamp(long) and its output, or C.TIME_UNSET if the offset has not yet been determined.

public synchronized void reset(long firstSampleTimestampUs)

Resets the instance.

Parameters:

firstSampleTimestampUs: The desired value of the first adjusted sample timestamp after this reset in microseconds, or TimestampAdjuster.MODE_NO_OFFSET if timestamps should not be offset, or TimestampAdjuster.MODE_SHARED if the adjuster will be used in shared mode.

public synchronized long adjustTsTimestamp(long pts90Khz)

Scales and offsets an MPEG-2 TS presentation timestamp considering wraparound.

When estimating the wraparound, the method assumes that this timestamp is close to the previous adjusted timestamp.

Parameters:

pts90Khz: A 90 kHz clock MPEG-2 TS presentation timestamp.

Returns:

The adjusted timestamp in microseconds.

public synchronized long adjustTsTimestampGreaterThanPreviousTimestamp(long pts90Khz)

Scales and offsets an MPEG-2 TS presentation timestamp considering wraparound.

When estimating the wraparound, the method assumes that the timestamp is strictly greater than the previous adjusted timestamp.

Parameters:

pts90Khz: A 90 kHz clock MPEG-2 TS presentation timestamp.

Returns:

The adjusted timestamp in microseconds.

public synchronized long adjustSampleTimestamp(long timeUs)

Offsets a timestamp in microseconds.

Parameters:

timeUs: The timestamp to adjust in microseconds.

Returns:

The adjusted timestamp in microseconds.

public synchronized boolean isInitialized()

Returns whether the instance is initialized with a timestamp offset.

public static long ptsToUs(long pts)

Converts a 90 kHz clock timestamp to a timestamp in microseconds.

Parameters:

pts: A 90 kHz clock timestamp.

Returns:

The corresponding value in microseconds.

public static long usToWrappedPts(long us)

Converts a timestamp in microseconds to a 90 kHz clock timestamp, performing wraparound to keep the result within 33-bits.

Parameters:

us: A value in microseconds.

Returns:

The corresponding value as a 90 kHz clock timestamp, wrapped to 33 bits.

public static long usToNonWrappedPts(long us)

Converts a timestamp in microseconds to a 90 kHz clock timestamp.

Does not perform any wraparound. To get a 90 kHz timestamp suitable for use with MPEG-TS, use TimestampAdjuster.usToWrappedPts(long).

Parameters:

us: A value in microseconds.

Returns:

The corresponding value as a 90 kHz clock timestamp.

Source

/*
 * Copyright (C) 2016 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.media3.common.util;

import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;

import android.os.SystemClock;
import androidx.annotation.GuardedBy;
import androidx.media3.common.C;
import java.util.concurrent.TimeoutException;

/**
 * Adjusts and offsets sample timestamps. MPEG-2 TS timestamps scaling and adjustment is supported,
 * taking into account timestamp rollover.
 */
@UnstableApi
public final class TimestampAdjuster {

  /**
   * A special {@code firstSampleTimestampUs} value indicating that presentation timestamps should
   * not be offset. In this mode:
   *
   * <ul>
   *   <li>{@link #getFirstSampleTimestampUs()} will always return {@link C#TIME_UNSET}.
   *   <li>The only timestamp adjustment performed is to account for MPEG-2 TS timestamp rollover.
   * </ul>
   */
  public static final long MODE_NO_OFFSET = Long.MAX_VALUE;

  /**
   * A special {@code firstSampleTimestampUs} value indicating that the adjuster will be shared by
   * multiple threads. In this mode:
   *
   * <ul>
   *   <li>{@link #getFirstSampleTimestampUs()} will always return {@link C#TIME_UNSET}.
   *   <li>Calling threads must call {@link #sharedInitializeOrWait} prior to adjusting timestamps.
   * </ul>
   */
  public static final long MODE_SHARED = Long.MAX_VALUE - 1;

  /**
   * The value one greater than the largest representable (33 bit) MPEG-2 TS 90 kHz clock
   * presentation timestamp.
   */
  private static final long MAX_PTS_PLUS_ONE = 0x200000000L;

  @GuardedBy("this")
  private long firstSampleTimestampUs;

  @GuardedBy("this")
  private long timestampOffsetUs;

  @GuardedBy("this")
  private long lastUnadjustedTimestampUs;

  /**
   * Next sample timestamps for calling threads in shared mode when {@link #timestampOffsetUs} has
   * not yet been set.
   */
  // incompatible type argument for type parameter T of ThreadLocal.
  @SuppressWarnings("nullness:type.argument.type.incompatible")
  private final ThreadLocal<Long> nextSampleTimestampUs;

  /**
   * @param firstSampleTimestampUs The desired value of the first adjusted sample timestamp in
   *     microseconds, or {@link #MODE_NO_OFFSET} if timestamps should not be offset, or {@link
   *     #MODE_SHARED} if the adjuster will be used in shared mode.
   */
  // incompatible types in assignment.
  @SuppressWarnings("nullness:assignment.type.incompatible")
  public TimestampAdjuster(long firstSampleTimestampUs) {
    nextSampleTimestampUs = new ThreadLocal<>();
    reset(firstSampleTimestampUs);
  }

  /**
   * For shared timestamp adjusters, performs necessary initialization actions for a caller.
   *
   * <ul>
   *   <li>If the adjuster has already established a {@link #getTimestampOffsetUs timestamp offset}
   *       then this method is a no-op.
   *   <li>If {@code canInitialize} is {@code true} and the adjuster has not yet established a
   *       timestamp offset, then the adjuster records the desired first sample timestamp for the
   *       calling thread and returns to allow the caller to proceed. If the timestamp offset has
   *       still not been established when the caller attempts to adjust its first timestamp, then
   *       the recorded timestamp is used to set it.
   *   <li>If {@code canInitialize} is {@code false} and the adjuster has not yet established a
   *       timestamp offset, then the call blocks until the timestamp offset is set.
   * </ul>
   *
   * @param canInitialize Whether the caller is able to initialize the adjuster, if needed.
   * @param nextSampleTimestampUs The desired timestamp for the next sample loaded by the calling
   *     thread, in microseconds. Only used if {@code canInitialize} is {@code true}.
   * @param timeoutMs The timeout for the thread to wait for the timestamp adjuster to initialize,
   *     in milliseconds. A timeout of zero is interpreted as an infinite timeout.
   * @throws InterruptedException If the thread is interrupted whilst blocked waiting for
   *     initialization to complete.
   * @throws TimeoutException If the thread is timeout whilst blocked waiting for initialization to
   *     complete.
   */
  public synchronized void sharedInitializeOrWait(
      boolean canInitialize, long nextSampleTimestampUs, long timeoutMs)
      throws InterruptedException, TimeoutException {
    checkState(firstSampleTimestampUs == MODE_SHARED);
    if (isInitialized()) {
      return;
    } else if (canInitialize) {
      this.nextSampleTimestampUs.set(nextSampleTimestampUs);
    } else {
      // Wait for another calling thread to complete initialization.
      long totalWaitDurationMs = 0;
      long remainingTimeoutMs = timeoutMs;
      while (!isInitialized()) {
        if (timeoutMs == 0) {
          wait();
        } else {
          checkState(remainingTimeoutMs > 0);
          long waitStartingTimeMs = SystemClock.elapsedRealtime();
          wait(remainingTimeoutMs);
          totalWaitDurationMs += SystemClock.elapsedRealtime() - waitStartingTimeMs;
          if (totalWaitDurationMs >= timeoutMs && !isInitialized()) {
            String message =
                "TimestampAdjuster failed to initialize in " + timeoutMs + " milliseconds";
            throw new TimeoutException(message);
          }
          remainingTimeoutMs = timeoutMs - totalWaitDurationMs;
        }
      }
    }
  }

  /**
   * Returns the value of the first adjusted sample timestamp in microseconds, or {@link
   * C#TIME_UNSET} if timestamps will not be offset or if the adjuster is in shared mode.
   */
  public synchronized long getFirstSampleTimestampUs() {
    return firstSampleTimestampUs == MODE_NO_OFFSET || firstSampleTimestampUs == MODE_SHARED
        ? C.TIME_UNSET
        : firstSampleTimestampUs;
  }

  /**
   * Returns the last adjusted timestamp, in microseconds. If no timestamps have been adjusted yet
   * then the result of {@link #getFirstSampleTimestampUs()} is returned.
   */
  public synchronized long getLastAdjustedTimestampUs() {
    return lastUnadjustedTimestampUs != C.TIME_UNSET
        ? lastUnadjustedTimestampUs + timestampOffsetUs
        : getFirstSampleTimestampUs();
  }

  /**
   * Returns the offset between the input of {@link #adjustSampleTimestamp(long)} and its output, or
   * {@link C#TIME_UNSET} if the offset has not yet been determined.
   */
  public synchronized long getTimestampOffsetUs() {
    return timestampOffsetUs;
  }

  /**
   * Resets the instance.
   *
   * @param firstSampleTimestampUs The desired value of the first adjusted sample timestamp after
   *     this reset in microseconds, or {@link #MODE_NO_OFFSET} if timestamps should not be offset,
   *     or {@link #MODE_SHARED} if the adjuster will be used in shared mode.
   */
  public synchronized void reset(long firstSampleTimestampUs) {
    this.firstSampleTimestampUs = firstSampleTimestampUs;
    timestampOffsetUs = firstSampleTimestampUs == MODE_NO_OFFSET ? 0 : C.TIME_UNSET;
    lastUnadjustedTimestampUs = C.TIME_UNSET;
  }

  /**
   * Scales and offsets an MPEG-2 TS presentation timestamp considering wraparound.
   *
   * <p>When estimating the wraparound, the method assumes that this timestamp is close to the
   * previous adjusted timestamp.
   *
   * @param pts90Khz A 90 kHz clock MPEG-2 TS presentation timestamp.
   * @return The adjusted timestamp in microseconds.
   */
  public synchronized long adjustTsTimestamp(long pts90Khz) {
    if (pts90Khz == C.TIME_UNSET) {
      return C.TIME_UNSET;
    }
    if (lastUnadjustedTimestampUs != C.TIME_UNSET) {
      // The wrap count for the current PTS may be closestWrapCount or (closestWrapCount - 1),
      // and we need to snap to the one closest to lastSampleTimestampUs.
      long lastPts = usToNonWrappedPts(lastUnadjustedTimestampUs);
      long closestWrapCount = (lastPts + (MAX_PTS_PLUS_ONE / 2)) / MAX_PTS_PLUS_ONE;
      long ptsWrapBelow = pts90Khz + (MAX_PTS_PLUS_ONE * (closestWrapCount - 1));
      long ptsWrapAbove = pts90Khz + (MAX_PTS_PLUS_ONE * closestWrapCount);
      pts90Khz =
          Math.abs(ptsWrapBelow - lastPts) < Math.abs(ptsWrapAbove - lastPts)
              ? ptsWrapBelow
              : ptsWrapAbove;
    }
    return adjustSampleTimestamp(ptsToUs(pts90Khz));
  }

  /**
   * Scales and offsets an MPEG-2 TS presentation timestamp considering wraparound.
   *
   * <p>When estimating the wraparound, the method assumes that the timestamp is strictly greater
   * than the previous adjusted timestamp.
   *
   * @param pts90Khz A 90 kHz clock MPEG-2 TS presentation timestamp.
   * @return The adjusted timestamp in microseconds.
   */
  public synchronized long adjustTsTimestampGreaterThanPreviousTimestamp(long pts90Khz) {
    if (pts90Khz == C.TIME_UNSET) {
      return C.TIME_UNSET;
    }
    if (lastUnadjustedTimestampUs != C.TIME_UNSET) {
      // The wrap count for the current PTS must be same or greater than the previous one.
      long lastPts = usToNonWrappedPts(lastUnadjustedTimestampUs);
      long wrapCount = lastPts / MAX_PTS_PLUS_ONE;
      long ptsSameWrap = pts90Khz + (MAX_PTS_PLUS_ONE * wrapCount);
      long ptsNextWrap = pts90Khz + (MAX_PTS_PLUS_ONE * (wrapCount + 1));
      pts90Khz = ptsSameWrap >= lastPts ? ptsSameWrap : ptsNextWrap;
    }
    return adjustSampleTimestamp(ptsToUs(pts90Khz));
  }

  /**
   * Offsets a timestamp in microseconds.
   *
   * @param timeUs The timestamp to adjust in microseconds.
   * @return The adjusted timestamp in microseconds.
   */
  public synchronized long adjustSampleTimestamp(long timeUs) {
    if (timeUs == C.TIME_UNSET) {
      return C.TIME_UNSET;
    }
    if (!isInitialized()) {
      long desiredSampleTimestampUs =
          firstSampleTimestampUs == MODE_SHARED
              ? checkNotNull(nextSampleTimestampUs.get())
              : firstSampleTimestampUs;
      timestampOffsetUs = desiredSampleTimestampUs - timeUs;
      // Notify threads waiting for the timestamp offset to be determined.
      notifyAll();
    }
    lastUnadjustedTimestampUs = timeUs;
    return timeUs + timestampOffsetUs;
  }

  /** Returns whether the instance is initialized with a timestamp offset. */
  public synchronized boolean isInitialized() {
    return timestampOffsetUs != C.TIME_UNSET;
  }

  /**
   * Converts a 90 kHz clock timestamp to a timestamp in microseconds.
   *
   * @param pts A 90 kHz clock timestamp.
   * @return The corresponding value in microseconds.
   */
  public static long ptsToUs(long pts) {
    return (pts * C.MICROS_PER_SECOND) / 90000;
  }

  /**
   * Converts a timestamp in microseconds to a 90 kHz clock timestamp, performing wraparound to keep
   * the result within 33-bits.
   *
   * @param us A value in microseconds.
   * @return The corresponding value as a 90 kHz clock timestamp, wrapped to 33 bits.
   */
  public static long usToWrappedPts(long us) {
    return usToNonWrappedPts(us) % MAX_PTS_PLUS_ONE;
  }

  /**
   * Converts a timestamp in microseconds to a 90 kHz clock timestamp.
   *
   * <p>Does not perform any wraparound. To get a 90 kHz timestamp suitable for use with MPEG-TS,
   * use {@link #usToWrappedPts(long)}.
   *
   * @param us A value in microseconds.
   * @return The corresponding value as a 90 kHz clock timestamp.
   */
  public static long usToNonWrappedPts(long us) {
    return (us * 90000) / C.MICROS_PER_SECOND;
  }
}