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
This commit is contained in:
aquilescanta 2017-01-23 07:01:26 -08:00 committed by Oliver Woodman
parent 5debf5a14a
commit 18d7cdf39f
6 changed files with 222 additions and 15 deletions

View File

@ -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;
}
}

View File

@ -93,6 +93,9 @@ public final class TimestampAdjuster {
* @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 final class TimestampAdjuster {
* @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;

View File

@ -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) {

View File

@ -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 final class SpliceInfoDecoder implements MetadataDecoder {
@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 final class SpliceInfoDecoder implements MetadataDecoder {
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);

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.metadata.scte35;
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 @@ public final class SpliceInsertCommand extends SpliceCommand {
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 @@ public final class SpliceInsertCommand extends SpliceCommand {
}
/* 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 @@ public final class SpliceInsertCommand extends SpliceCommand {
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 @@ public final class SpliceInsertCommand extends SpliceCommand {
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 @@ public final class SpliceInsertCommand extends SpliceCommand {
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 final class SpliceInsertCommand extends SpliceCommand {
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 final class SpliceInsertCommand extends SpliceCommand {
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++) {

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.metadata.scte35;
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 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
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 @@ public final class TimeSignalCommand extends SpliceCommand {
@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 final class TimeSignalCommand extends SpliceCommand {
@Override
public TimeSignalCommand createFromParcel(Parcel in) {
return new TimeSignalCommand(in.readLong());
return new TimeSignalCommand(in.readLong(), in.readLong());
}
@Override