mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
5debf5a14a
commit
18d7cdf39f
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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++) {
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user