Skip to content

Commit

Permalink
Add pts adjustment in SpliceInfoDecoder
Browse files Browse the repository at this point in the history
This allows the user to interpret PTSs in the playback timebase.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=145280921
  • Loading branch information
AquilesCanta authored and ojw28 committed Jan 24, 2017
1 parent 5debf5a commit 18d7cdf
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -37,13 +38,22 @@ 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();
}

@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();
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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++) {
Expand All @@ -80,15 +85,15 @@ 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;
boolean outOfNetworkIndicator = false;
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;
Expand All @@ -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) {
Expand All @@ -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);
}

Expand All @@ -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());
}

}
Expand All @@ -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++) {
Expand Down
Loading

0 comments on commit 18d7cdf

Please sign in to comment.