From 18d7cdf39f4d13270a23d06a4a51b24b6ec05fdb Mon Sep 17 00:00:00 2001
From: aquilescanta <aquilescanta@google.com>
Date: Mon, 23 Jan 2017 07:01:26 -0800
Subject: [PATCH] Add pts adjustment in SpliceInfoDecoder

This allows the user to interpret PTSs in the playback timebase.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=145280921
---
 .../scte35/SpliceInfoDecoderTest.java         | 173 ++++++++++++++++++
 .../extractor/TimestampAdjuster.java          |   6 +
 .../metadata/scte35/PrivateCommand.java       |   1 -
 .../metadata/scte35/SpliceInfoDecoder.java    |  15 +-
 .../metadata/scte35/SpliceInsertCommand.java  |  28 ++-
 .../metadata/scte35/TimeSignalCommand.java    |  14 +-
 6 files changed, 222 insertions(+), 15 deletions(-)
 create mode 100644 library/src/androidTest/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java

diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java
new file mode 100644
index 00000000000..4c493fd8adb
--- /dev/null
+++ b/library/src/androidTest/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoderTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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 com.google.android.exoplayer2.metadata.scte35;
+
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.extractor.TimestampAdjuster;
+import com.google.android.exoplayer2.metadata.Metadata;
+import com.google.android.exoplayer2.metadata.MetadataDecoderException;
+import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
+import java.nio.ByteBuffer;
+import java.util.List;
+import junit.framework.TestCase;
+
+/**
+ * Test for {@link SpliceInfoDecoder}.
+ */
+public final class SpliceInfoDecoderTest extends TestCase {
+
+  private SpliceInfoDecoder decoder;
+  private MetadataInputBuffer inputBuffer;
+
+  @Override
+  public void setUp() {
+    decoder = new SpliceInfoDecoder();
+    inputBuffer = new MetadataInputBuffer();
+  }
+
+  public void testWrappedAroundTimeSignalCommand() throws MetadataDecoderException {
+    byte[] rawTimeSignalSection = new byte[] {
+        0, // table_id.
+        (byte) 0x80, // section_syntax_indicator, private_indicator, reserved, section_length(4).
+        0x14, // section_length(8).
+        0x00, // protocol_version.
+        0x00, // encrypted_packet, encryption_algorithm, pts_adjustment(1).
+        0x00, 0x00, 0x00, 0x00, // pts_adjustment(32).
+        0x00, // cw_index.
+        0x00, // tier(8).
+        0x00, // tier(4), splice_command_length(4).
+        0x05, // splice_command_length(8).
+        0x06, // splice_command_type = time_signal.
+        // Start of splice_time().
+        (byte) 0x80, // time_specified_flag, reserved, pts_time(1).
+        0x52, 0x03, 0x02, (byte) 0x8f, // pts_time(32). PTS for a second after playback position.
+        0x00, 0x00, 0x00, 0x00}; // CRC_32 (ignored, check happens at extraction).
+
+    // The playback position is 57:15:58.43 approximately.
+    // With this offset, the playback position pts before wrapping is 0x451ebf851.
+    Metadata metadata = feedInputBuffer(rawTimeSignalSection, 0x3000000000L, -0x50000L);
+    assertEquals(1, metadata.length());
+    assertEquals(removePtsConversionPrecisionError(0x3001000000L, inputBuffer.subsampleOffsetUs),
+        ((TimeSignalCommand) metadata.get(0)).playbackPositionUs);
+  }
+
+  public void test2SpliceInsertCommands() throws MetadataDecoderException {
+    byte[] rawSpliceInsertCommand1 = new byte[] {
+        0, // table_id.
+        (byte) 0x80, // section_syntax_indicator, private_indicator, reserved, section_length(4).
+        0x19, // section_length(8).
+        0x00, // protocol_version.
+        0x00, // encrypted_packet, encryption_algorithm, pts_adjustment(1).
+        0x00, 0x00, 0x00, 0x00, // pts_adjustment(32).
+        0x00, // cw_index.
+        0x00, // tier(8).
+        0x00, // tier(4), splice_command_length(4).
+        0x0e, // splice_command_length(8).
+        0x05, // splice_command_type = splice_insert.
+        // Start of splice_insert().
+        0x00, 0x00, 0x00, 0x42, // splice_event_id.
+        0x00, // splice_event_cancel_indicator, reserved.
+        0x40, // out_of_network_indicator, program_splice_flag, duration_flag,
+              // splice_immediate_flag, reserved.
+        // start of splice_time().
+        (byte) 0x80, // time_specified_flag, reserved, pts_time(1).
+        0x00, 0x00, 0x00, 0x00, // PTS for playback position 3s.
+        0x00, 0x10, // unique_program_id.
+        0x01, // avail_num.
+        0x02, // avails_expected.
+        0x00, 0x00, 0x00, 0x00}; // CRC_32 (ignored, check happens at extraction).
+
+    Metadata metadata = feedInputBuffer(rawSpliceInsertCommand1, 2000000, 3000000);
+    assertEquals(1, metadata.length());
+    SpliceInsertCommand command = (SpliceInsertCommand) metadata.get(0);
+    assertEquals(66, command.spliceEventId);
+    assertFalse(command.spliceEventCancelIndicator);
+    assertFalse(command.outOfNetworkIndicator);
+    assertTrue(command.programSpliceFlag);
+    assertFalse(command.spliceImmediateFlag);
+    assertEquals(3000000, command.programSplicePlaybackPositionUs);
+    assertEquals(C.TIME_UNSET, command.breakDuration);
+    assertEquals(16, command.uniqueProgramId);
+    assertEquals(1, command.availNum);
+    assertEquals(2, command.availsExpected);
+
+    byte[] rawSpliceInsertCommand2 = new byte[] {
+        0, // table_id.
+        (byte) 0x80, // section_syntax_indicator, private_indicator, reserved, section_length(4).
+        0x22, // section_length(8).
+        0x00, // protocol_version.
+        0x00, // encrypted_packet, encryption_algorithm, pts_adjustment(1).
+        0x00, 0x00, 0x00, 0x00, // pts_adjustment(32).
+        0x00, // cw_index.
+        0x00, // tier(8).
+        0x00, // tier(4), splice_command_length(4).
+        0x13, // splice_command_length(8).
+        0x05, // splice_command_type = splice_insert.
+        // Start of splice_insert().
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, // splice_event_id.
+        0x00, // splice_event_cancel_indicator, reserved.
+        0x00, // out_of_network_indicator, program_splice_flag, duration_flag,
+              // splice_immediate_flag, reserved.
+        0x02, // component_count.
+        0x10, // component_tag.
+        // start of splice_time().
+        (byte) 0x81, // time_specified_flag, reserved, pts_time(1).
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, // PTS for playback position 10s.
+        // start of splice_time().
+        0x11, // component_tag.
+        0x00, // time_specified_flag, reserved.
+        0x00, 0x20, // unique_program_id.
+        0x01, // avail_num.
+        0x02, // avails_expected.
+        0x00, 0x00, 0x00, 0x00}; // CRC_32 (ignored, check happens at extraction).
+
+    // By changing the subsample offset we force adjuster reconstruction.
+    long subsampleOffset = 1000011;
+    metadata = feedInputBuffer(rawSpliceInsertCommand2, 1000000, subsampleOffset);
+    assertEquals(1, metadata.length());
+    command = (SpliceInsertCommand) metadata.get(0);
+    assertEquals(0xffffffffL, command.spliceEventId);
+    assertFalse(command.spliceEventCancelIndicator);
+    assertFalse(command.outOfNetworkIndicator);
+    assertFalse(command.programSpliceFlag);
+    assertFalse(command.spliceImmediateFlag);
+    assertEquals(C.TIME_UNSET, command.programSplicePlaybackPositionUs);
+    assertEquals(C.TIME_UNSET, command.breakDuration);
+    List<SpliceInsertCommand.ComponentSplice> componentSplices = command.componentSpliceList;
+    assertEquals(2, componentSplices.size());
+    assertEquals(16, componentSplices.get(0).componentTag);
+    assertEquals(1000000, componentSplices.get(0).componentSplicePlaybackPositionUs);
+    assertEquals(17, componentSplices.get(1).componentTag);
+    assertEquals(C.TIME_UNSET, componentSplices.get(1).componentSplicePts);
+    assertEquals(32, command.uniqueProgramId);
+    assertEquals(1, command.availNum);
+    assertEquals(2, command.availsExpected);
+  }
+
+  private Metadata feedInputBuffer(byte[] data, long timeUs, long subsampleOffset)
+      throws MetadataDecoderException{
+    inputBuffer.clear();
+    inputBuffer.data = ByteBuffer.allocate(data.length).put(data);
+    inputBuffer.timeUs = timeUs;
+    inputBuffer.subsampleOffsetUs = subsampleOffset;
+    return decoder.decode(inputBuffer);
+  }
+
+  private static long removePtsConversionPrecisionError(long timeUs, long offsetUs) {
+    return TimestampAdjuster.ptsToUs(TimestampAdjuster.usToPts(timeUs - offsetUs)) + offsetUs;
+  }
+
+}
diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/TimestampAdjuster.java b/library/src/main/java/com/google/android/exoplayer2/extractor/TimestampAdjuster.java
index a4da5d8e66f..1fc0e1813e0 100644
--- a/library/src/main/java/com/google/android/exoplayer2/extractor/TimestampAdjuster.java
+++ b/library/src/main/java/com/google/android/exoplayer2/extractor/TimestampAdjuster.java
@@ -93,6 +93,9 @@ public void reset() {
    * @return The adjusted timestamp in microseconds.
    */
   public long adjustTsTimestamp(long pts) {
+    if (pts == C.TIME_UNSET) {
+      return C.TIME_UNSET;
+    }
     if (lastSampleTimestamp != 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 lastSampleTimestamp.
@@ -113,6 +116,9 @@ public long adjustTsTimestamp(long pts) {
    * @return The adjusted timestamp in microseconds.
    */
   public long adjustSampleTimestamp(long timeUs) {
+    if (timeUs == C.TIME_UNSET) {
+      return C.TIME_UNSET;
+    }
     // Record the adjusted PTS to adjust for wraparound next time.
     if (lastSampleTimestamp != C.TIME_UNSET) {
       lastSampleTimestamp = timeUs;
diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java
index f75a1b46a46..beb4cb9b88e 100644
--- a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java
+++ b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/PrivateCommand.java
@@ -26,7 +26,6 @@ public final class PrivateCommand extends SpliceCommand {
 
   public final long ptsAdjustment;
   public final long identifier;
-
   public final byte[] commandBytes;
 
   private PrivateCommand(long identifier, byte[] commandBytes, long ptsAdjustment) {
diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java
index 6e373a45e7a..dc85788a8b1 100644
--- a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java
+++ b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInfoDecoder.java
@@ -15,6 +15,7 @@
  */
 package com.google.android.exoplayer2.metadata.scte35;
 
+import com.google.android.exoplayer2.extractor.TimestampAdjuster;
 import com.google.android.exoplayer2.metadata.Metadata;
 import com.google.android.exoplayer2.metadata.MetadataDecoder;
 import com.google.android.exoplayer2.metadata.MetadataDecoderException;
@@ -37,6 +38,8 @@ public final class SpliceInfoDecoder implements MetadataDecoder {
   private final ParsableByteArray sectionData;
   private final ParsableBitArray sectionHeader;
 
+  private TimestampAdjuster timestampAdjuster;
+
   public SpliceInfoDecoder() {
     sectionData = new ParsableByteArray();
     sectionHeader = new ParsableBitArray();
@@ -44,6 +47,13 @@ public SpliceInfoDecoder() {
 
   @Override
   public Metadata decode(MetadataInputBuffer inputBuffer) throws MetadataDecoderException {
+    // Internal timestamps adjustment.
+    if (timestampAdjuster == null
+        || inputBuffer.subsampleOffsetUs != timestampAdjuster.getTimestampOffsetUs()) {
+      timestampAdjuster = new TimestampAdjuster(inputBuffer.timeUs);
+      timestampAdjuster.adjustSampleTimestamp(inputBuffer.timeUs - inputBuffer.subsampleOffsetUs);
+    }
+
     ByteBuffer buffer = inputBuffer.data;
     byte[] data = buffer.array();
     int size = buffer.limit();
@@ -69,10 +79,11 @@ public Metadata decode(MetadataInputBuffer inputBuffer) throws MetadataDecoderEx
         command = SpliceScheduleCommand.parseFromSection(sectionData);
         break;
       case TYPE_SPLICE_INSERT:
-        command = SpliceInsertCommand.parseFromSection(sectionData, ptsAdjustment);
+        command = SpliceInsertCommand.parseFromSection(sectionData, ptsAdjustment,
+            timestampAdjuster);
         break;
       case TYPE_TIME_SIGNAL:
-        command = TimeSignalCommand.parseFromSection(sectionData, ptsAdjustment);
+        command = TimeSignalCommand.parseFromSection(sectionData, ptsAdjustment, timestampAdjuster);
         break;
       case TYPE_PRIVATE_COMMAND:
         command = PrivateCommand.parseFromSection(sectionData, spliceCommandLength, ptsAdjustment);
diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java
index 1e025aeb352..07a84bf5d10 100644
--- a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java
+++ b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/SpliceInsertCommand.java
@@ -18,6 +18,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.extractor.TimestampAdjuster;
 import com.google.android.exoplayer2.util.ParsableByteArray;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -34,6 +35,7 @@ public final class SpliceInsertCommand extends SpliceCommand {
   public final boolean programSpliceFlag;
   public final boolean spliceImmediateFlag;
   public final long programSplicePts;
+  public final long programSplicePlaybackPositionUs;
   public final List<ComponentSplice> componentSpliceList;
   public final boolean autoReturn;
   public final long breakDuration;
@@ -43,14 +45,16 @@ public final class SpliceInsertCommand extends SpliceCommand {
 
   private SpliceInsertCommand(long spliceEventId, boolean spliceEventCancelIndicator,
       boolean outOfNetworkIndicator, boolean programSpliceFlag, boolean spliceImmediateFlag,
-      long programSplicePts, List<ComponentSplice> componentSpliceList, boolean autoReturn,
-      long breakDuration, int uniqueProgramId, int availNum, int availsExpected) {
+      long programSplicePts, long programSplicePlaybackPositionUs,
+      List<ComponentSplice> componentSpliceList, boolean autoReturn, long breakDuration,
+      int uniqueProgramId, int availNum, int availsExpected) {
     this.spliceEventId = spliceEventId;
     this.spliceEventCancelIndicator = spliceEventCancelIndicator;
     this.outOfNetworkIndicator = outOfNetworkIndicator;
     this.programSpliceFlag = programSpliceFlag;
     this.spliceImmediateFlag = spliceImmediateFlag;
     this.programSplicePts = programSplicePts;
+    this.programSplicePlaybackPositionUs = programSplicePlaybackPositionUs;
     this.componentSpliceList = Collections.unmodifiableList(componentSpliceList);
     this.autoReturn = autoReturn;
     this.breakDuration = breakDuration;
@@ -66,6 +70,7 @@ private SpliceInsertCommand(Parcel in) {
     programSpliceFlag = in.readByte() == 1;
     spliceImmediateFlag = in.readByte() == 1;
     programSplicePts = in.readLong();
+    programSplicePlaybackPositionUs = in.readLong();
     int componentSpliceListSize = in.readInt();
     List<ComponentSplice> componentSpliceList = new ArrayList<>(componentSpliceListSize);
     for (int i = 0; i < componentSpliceListSize; i++) {
@@ -80,7 +85,7 @@ private SpliceInsertCommand(Parcel in) {
   }
 
   /* package */ static SpliceInsertCommand parseFromSection(ParsableByteArray sectionData,
-      long ptsAdjustment) {
+      long ptsAdjustment, TimestampAdjuster timestampAdjuster) {
     long spliceEventId = sectionData.readUnsignedInt();
     // splice_event_cancel_indicator(1), reserved(7).
     boolean spliceEventCancelIndicator = (sectionData.readUnsignedByte() & 0x80) != 0;
@@ -88,7 +93,7 @@ private SpliceInsertCommand(Parcel in) {
     boolean programSpliceFlag = false;
     boolean spliceImmediateFlag = false;
     long programSplicePts = C.TIME_UNSET;
-    ArrayList<ComponentSplice> componentSplices = new ArrayList<>();
+    List<ComponentSplice> componentSplices = Collections.emptyList();
     int uniqueProgramId = 0;
     int availNum = 0;
     int availsExpected = 0;
@@ -112,7 +117,8 @@ private SpliceInsertCommand(Parcel in) {
           if (!spliceImmediateFlag) {
             componentSplicePts = TimeSignalCommand.parseSpliceTime(sectionData, ptsAdjustment);
           }
-          componentSplices.add(new ComponentSplice(componentTag, componentSplicePts));
+          componentSplices.add(new ComponentSplice(componentTag, componentSplicePts,
+              timestampAdjuster.adjustTsTimestamp(componentSplicePts)));
         }
       }
       if (durationFlag) {
@@ -125,7 +131,8 @@ private SpliceInsertCommand(Parcel in) {
       availsExpected = sectionData.readUnsignedByte();
     }
     return new SpliceInsertCommand(spliceEventId, spliceEventCancelIndicator, outOfNetworkIndicator,
-        programSpliceFlag, spliceImmediateFlag, programSplicePts, componentSplices, autoReturn,
+        programSpliceFlag, spliceImmediateFlag, programSplicePts,
+        timestampAdjuster.adjustTsTimestamp(programSplicePts), componentSplices, autoReturn,
         duration, uniqueProgramId, availNum, availsExpected);
   }
 
@@ -136,19 +143,23 @@ public static final class ComponentSplice {
 
     public final int componentTag;
     public final long componentSplicePts;
+    public final long componentSplicePlaybackPositionUs;
 
-    private ComponentSplice(int componentTag, long componentSplicePts) {
+    private ComponentSplice(int componentTag, long componentSplicePts,
+        long componentSplicePlaybackPositionUs) {
       this.componentTag = componentTag;
       this.componentSplicePts = componentSplicePts;
+      this.componentSplicePlaybackPositionUs = componentSplicePlaybackPositionUs;
     }
 
     public void writeToParcel(Parcel dest) {
       dest.writeInt(componentTag);
       dest.writeLong(componentSplicePts);
+      dest.writeLong(componentSplicePlaybackPositionUs);
     }
 
     public static ComponentSplice createFromParcel(Parcel in) {
-      return new ComponentSplice(in.readInt(), in.readLong());
+      return new ComponentSplice(in.readInt(), in.readLong(), in.readLong());
     }
 
   }
@@ -163,6 +174,7 @@ public void writeToParcel(Parcel dest, int flags) {
     dest.writeByte((byte) (programSpliceFlag ? 1 : 0));
     dest.writeByte((byte) (spliceImmediateFlag ? 1 : 0));
     dest.writeLong(programSplicePts);
+    dest.writeLong(programSplicePlaybackPositionUs);
     int componentSpliceListSize = componentSpliceList.size();
     dest.writeInt(componentSpliceListSize);
     for (int i = 0; i < componentSpliceListSize; i++) {
diff --git a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java
index c31f4dedc87..e21eafbeeb0 100644
--- a/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java
+++ b/library/src/main/java/com/google/android/exoplayer2/metadata/scte35/TimeSignalCommand.java
@@ -17,6 +17,7 @@
 
 import android.os.Parcel;
 import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.extractor.TimestampAdjuster;
 import com.google.android.exoplayer2.util.ParsableByteArray;
 
 /**
@@ -25,14 +26,18 @@
 public final class TimeSignalCommand extends SpliceCommand {
 
   public final long ptsTime;
+  public final long playbackPositionUs;
 
-  private TimeSignalCommand(long ptsTime) {
+  private TimeSignalCommand(long ptsTime, long playbackPositionUs) {
     this.ptsTime = ptsTime;
+    this.playbackPositionUs = playbackPositionUs;
   }
 
   /* package */ static TimeSignalCommand parseFromSection(ParsableByteArray sectionData,
-      long ptsAdjustment) {
-    return new TimeSignalCommand(parseSpliceTime(sectionData, ptsAdjustment));
+      long ptsAdjustment, TimestampAdjuster timestampAdjuster) {
+    long ptsTime = parseSpliceTime(sectionData, ptsAdjustment);
+    long playbackPositionUs = timestampAdjuster.adjustTsTimestamp(ptsTime);
+    return new TimeSignalCommand(ptsTime, playbackPositionUs);
   }
 
   /**
@@ -61,6 +66,7 @@ private TimeSignalCommand(long ptsTime) {
   @Override
   public void writeToParcel(Parcel dest, int flags) {
     dest.writeLong(ptsTime);
+    dest.writeLong(playbackPositionUs);
   }
 
   public static final Creator<TimeSignalCommand> CREATOR =
@@ -68,7 +74,7 @@ public void writeToParcel(Parcel dest, int flags) {
 
     @Override
     public TimeSignalCommand createFromParcel(Parcel in) {
-      return new TimeSignalCommand(in.readLong());
+      return new TimeSignalCommand(in.readLong(), in.readLong());
     }
 
     @Override