From 1d65afdd16792fd755697596d1897f2c4495fbf6 Mon Sep 17 00:00:00 2001 From: Pierre-Hugues Husson Date: Tue, 28 Jan 2020 16:07:02 +0100 Subject: [PATCH 01/10] Add minimal support of DVB AIT This is used by broadcast channels to provide interactive contents. --- .../android/exoplayer2/util/MimeTypes.java | 1 + .../metadata/MetadataDecoderFactory.java | 6 +- .../exoplayer2/metadata/dvbsi/Ait.java | 58 ++++++ .../exoplayer2/metadata/dvbsi/AitDecoder.java | 195 ++++++++++++++++++ .../ts/DefaultTsPayloadReaderFactory.java | 36 ++++ .../exoplayer2/extractor/ts/TsExtractor.java | 8 +- 6 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/Ait.java create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index e61ab83777..5154e309f5 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -97,6 +97,7 @@ public final class MimeTypes { public static final String APPLICATION_DVBSUBS = BASE_TYPE_APPLICATION + "/dvbsubs"; public static final String APPLICATION_EXIF = BASE_TYPE_APPLICATION + "/x-exif"; public static final String APPLICATION_ICY = BASE_TYPE_APPLICATION + "/x-icy"; + public static final String APPLICATION_AIT = BASE_TYPE_APPLICATION + "/ait"; private static final ArrayList customMimeTypes = new ArrayList<>(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java index 0b653830a3..a9c124eedb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/MetadataDecoderFactory.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.metadata; import androidx.annotation.Nullable; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.metadata.dvbsi.AitDecoder; import com.google.android.exoplayer2.metadata.emsg.EventMessageDecoder; import com.google.android.exoplayer2.metadata.icy.IcyDecoder; import com.google.android.exoplayer2.metadata.id3.Id3Decoder; @@ -67,7 +68,8 @@ public interface MetadataDecoderFactory { return MimeTypes.APPLICATION_ID3.equals(mimeType) || MimeTypes.APPLICATION_EMSG.equals(mimeType) || MimeTypes.APPLICATION_SCTE35.equals(mimeType) - || MimeTypes.APPLICATION_ICY.equals(mimeType); + || MimeTypes.APPLICATION_ICY.equals(mimeType) + || MimeTypes.APPLICATION_AIT.equals(mimeType); } @Override @@ -83,6 +85,8 @@ public interface MetadataDecoderFactory { return new SpliceInfoDecoder(); case MimeTypes.APPLICATION_ICY: return new IcyDecoder(); + case MimeTypes.APPLICATION_AIT: + return new AitDecoder(); default: break; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/Ait.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/Ait.java new file mode 100644 index 0000000000..4aa780aba1 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/Ait.java @@ -0,0 +1,58 @@ +package com.google.android.exoplayer2.metadata.dvbsi; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.android.exoplayer2.metadata.Metadata; + +public class Ait implements Metadata.Entry { + /* + The application shall be started when the service is selected, unless the + application is already running. + */ + public static final int CONTROL_CODE_AUTOSTART = 0x01; + /* + The application is allowed to run while the service is selected, however it + shall not start automatically when the service becomes selected. + */ + public static final int CONTROL_CODE_PRESENT = 0x02; + + public final int controlCode; + public final String url; + + Ait(int controlCode, String url) { + this.controlCode = controlCode; + this.url = url; + } + + @Override + public String toString() { + return "Ait(controlCode = " + controlCode + ", url = " + url + ")"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeString(url); + parcel.writeInt(controlCode); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public Ait createFromParcel(Parcel in) { + String url = in.readString(); + int controlCode = in.readInt(); + return new Ait(controlCode, url); + } + + @Override + public Ait[] newArray(int size) { + return new Ait[size]; + } + }; +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java new file mode 100644 index 0000000000..249ae0ce2f --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java @@ -0,0 +1,195 @@ +package com.google.android.exoplayer2.metadata.dvbsi; + +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.MetadataDecoder; +import com.google.android.exoplayer2.metadata.MetadataInputBuffer; +import com.google.android.exoplayer2.util.ParsableByteArray; +import com.google.android.exoplayer2.util.TimestampAdjuster; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; + +public class AitDecoder implements MetadataDecoder { + // Specification of AIT can be found in 5.3.4 of TS 102 809 v1.1.1 + // https://www.etsi.org/deliver/etsi_ts/102800_102899/102809/01.01.01_60/ts_102809v010101p.pdf + private final static int DESCRIPTOR_TRANSPORT_PROTOCOL = 0x02; + + private final static int DESCRIPTOR_SIMPLE_APPLICATION_LOCATION = 0x15; + + private final static int TRANSPORT_PROTOCOL_HTTP = 3; + + private TimestampAdjuster timestampAdjuster; + + private final ParsableByteArray sectionData; + + public AitDecoder() { + sectionData = new ParsableByteArray(); + } + + @Override + public Metadata decode(MetadataInputBuffer inputBuffer) { + 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(); + sectionData.reset(data, size); + + int tableId = sectionData.peekUnsignedByte(); + + //Only this table is allowed in AIT streams + if (tableId == 0x74) { + return parseAit(sectionData); + } + + return new Metadata(); + } + + private Metadata parseAit(ParsableByteArray sectionData) { + int tmp; + + int tableId = sectionData.readUnsignedByte(); + + tmp = sectionData.readUnsignedShort(); + int endOfSection = sectionData.getPosition() + (tmp & 4095) - 4 /* Ignore leading CRC */; + + tmp = sectionData.readUnsignedShort(); + int applicationType = tmp & 0x7fff; + + tmp = sectionData.readUnsignedByte(); + int versionNumber = (tmp & 0x3e) >> 1; + boolean current = (tmp & 1) == 1; + + int section_number = sectionData.readUnsignedByte(); + int last_section_number = sectionData.readUnsignedByte(); + + tmp = sectionData.readUnsignedShort(); + int commonDescriptorsLength = tmp & 4095; + + //Since we currently only keep url and control code, which are unique per application, + //there is no useful information in common descriptor. + sectionData.skipBytes(commonDescriptorsLength); + + tmp = sectionData.readUnsignedShort(); + int appLoopLength = tmp & 4095; + + ArrayList aits = new ArrayList<>(); + while(sectionData.getPosition() < endOfSection) { + // Values that will be stored in Ait() + String aitUrlBase = null; + String aitUrlExtension = null; + int aitControlCode = -1; + + long application_identifier = sectionData.readUnsignedInt24() << 24L; + application_identifier |= sectionData.readUnsignedInt24(); + int controlCode = sectionData.readUnsignedByte(); + + aitControlCode = controlCode; + + tmp = sectionData.readUnsignedShort(); + int sectionLength = tmp & 4095; + int positionOfNextSection = sectionData.getPosition() + sectionLength; + while(sectionData.getPosition() < positionOfNextSection) { + int type = sectionData.readUnsignedByte(); + int l = sectionData.readUnsignedByte(); + int positionOfNextSection2 = sectionData.getPosition() + l; + + if(type == DESCRIPTOR_TRANSPORT_PROTOCOL) { + int protocolId = sectionData.readUnsignedShort(); + int label = sectionData.readUnsignedByte(); + + if(protocolId == TRANSPORT_PROTOCOL_HTTP) { + while (sectionData.getPosition() < positionOfNextSection2) { + int urlBaseLength = sectionData.readUnsignedByte(); + String urlBase = sectionData.readString(urlBaseLength); + int extensionCount = sectionData.readUnsignedByte(); + aitUrlBase = urlBase; + for (int i = 0; i < extensionCount; i++) { + int len = sectionData.readUnsignedByte(); + sectionData.skipBytes(len); + } + } + } + } else if(type == DESCRIPTOR_SIMPLE_APPLICATION_LOCATION) { + String url = sectionData.readString(l); + aitUrlExtension = url; + } + + sectionData.setPosition(positionOfNextSection2); + } + + sectionData.setPosition(positionOfNextSection); + + if(aitControlCode != -1 && aitUrlBase != null && aitUrlExtension != null) { + aits.add(new Ait(aitControlCode, aitUrlBase + aitUrlExtension)); + } + } + + return new Metadata(aits); + } + + static final String dvbCharset[] = { + "ISO-8859-15", + "ISO-8859-5", + "ISO-8859-6", + "ISO-8859-7", + "ISO-8859-8", + "ISO-8859-9", + null, + "ISO-8859-11", + null, + "ISO-8859-13", + null, + "ISO-8859-15", + null, + null, + null, + null, + //0x10 + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + }; + + static String readDvbString(ParsableByteArray data, int length) { + if(length == 0) return null; + int charsetSelect = data.peekUnsignedByte(); + if(charsetSelect >= 0x20) + return data.readString(length, Charset.forName("ISO-8859-15")); + data.skipBytes(1); + if(charsetSelect == 0x1f) { + data.skipBytes(length - 1); + return null; + } + if(charsetSelect != 0x10) + return data.readString(length-1, Charset.forName(dvbCharset[charsetSelect])); + if(length == 2) { + data.skipBytes(1); + return null; + } + charsetSelect = data.readUnsignedShort(); + if(charsetSelect > 1 && charsetSelect < 0x10) { + String charsetName = "ISO-8859-" + charsetSelect; + return data.readString(length-3, Charset.forName(charsetName)); + } + return null; + } +} diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index ad1e8cc264..a22eb023dd 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -18,11 +18,17 @@ package com.google.android.exoplayer2.extractor.ts; import android.util.SparseArray; import androidx.annotation.IntDef; import androidx.annotation.Nullable; + +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.extractor.ExtractorOutput; +import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import com.google.android.exoplayer2.util.CodecSpecificDataUtil; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; +import com.google.android.exoplayer2.util.TimestampAdjuster; + import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -176,11 +182,41 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact case TsExtractor.TS_STREAM_TYPE_DVBSUBS: return new PesReader( new DvbSubtitleReader(esInfo.dvbSubtitleInfos)); + case TsExtractor.TS_STREAM_TYPE_AIT: + return new SectionReader(new SectionPassthrough(MimeTypes.APPLICATION_AIT)); default: return null; } } + public class SectionPassthrough implements SectionPayloadReader { + private TimestampAdjuster timestampAdjuster = null; + private final String mimeType; + private TrackOutput output; + + SectionPassthrough(String mimeType) { + this.mimeType = mimeType; + } + + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, TsPayloadReader.TrackIdGenerator idGenerator) { + this.timestampAdjuster = timestampAdjuster; + idGenerator.generateNewId(); + output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA); + output.format(Format.createSampleFormat(null, mimeType, + timestampAdjuster.getTimestampOffsetUs())); + } + + @Override + public void consume(ParsableByteArray sectionData) { + int sampleSize = sectionData.bytesLeft(); + output.sampleData(sectionData, sampleSize); + output.sampleMetadata(timestampAdjuster.getLastAdjustedTimestampUs(), C.BUFFER_FLAG_KEY_FRAME, + sampleSize, 0, null); + } + } + + /** * If {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS} is set, returns a {@link SeiReader} for * {@link #closedCaptionFormats}. If unset, parses the PMT descriptor information and returns a diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index 35e8806a6f..b3f4e80f9f 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -96,6 +96,9 @@ public final class TsExtractor implements Extractor { public static final int TS_STREAM_TYPE_SPLICE_INFO = 0x86; public static final int TS_STREAM_TYPE_DVBSUBS = 0x59; + //Those are special IDs, which don't have actual TS definitions + public static final int TS_STREAM_TYPE_AIT = 0x101; + public static final int TS_PACKET_SIZE = 188; public static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet. @@ -494,6 +497,7 @@ public final class TsExtractor implements Extractor { private static final int TS_PMT_DESC_REGISTRATION = 0x05; private static final int TS_PMT_DESC_ISO639_LANG = 0x0A; private static final int TS_PMT_DESC_AC3 = 0x6A; + private static final int TS_PMT_DESC_AIT = 0x6F; private static final int TS_PMT_DESC_EAC3 = 0x7A; private static final int TS_PMT_DESC_DTS = 0x7B; private static final int TS_PMT_DESC_DVB_EXT = 0x7F; @@ -578,7 +582,7 @@ public final class TsExtractor implements Extractor { pmtScratch.skipBits(4); // reserved int esInfoLength = pmtScratch.readBits(12); // ES_info_length. EsInfo esInfo = readEsInfo(sectionData, esInfoLength); - if (streamType == 0x06) { + if (streamType == 0x06 || streamType == 0x05) { streamType = esInfo.streamType; } remainingEntriesLength -= esInfoLength + 5; @@ -688,6 +692,8 @@ public final class TsExtractor implements Extractor { dvbSubtitleInfos.add(new DvbSubtitleInfo(dvbLanguage, dvbSubtitlingType, initializationData)); } + } else if (descriptorTag == TS_PMT_DESC_AIT) { + streamType = TS_STREAM_TYPE_AIT; } // Skip unused bytes of current descriptor. data.skipBytes(positionOfNextDescriptor - data.getPosition()); From 7bf63e732de4fd5bfbeb2fa1625d0a7758bb2361 Mon Sep 17 00:00:00 2001 From: Pierre-Hugues Husson Date: Tue, 18 Feb 2020 13:44:09 +0100 Subject: [PATCH 02/10] Remove dead-code readDvbString --- .../exoplayer2/metadata/dvbsi/AitDecoder.java | 59 ------------------- 1 file changed, 59 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java index 249ae0ce2f..33fe6bf865 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java @@ -133,63 +133,4 @@ public class AitDecoder implements MetadataDecoder { return new Metadata(aits); } - static final String dvbCharset[] = { - "ISO-8859-15", - "ISO-8859-5", - "ISO-8859-6", - "ISO-8859-7", - "ISO-8859-8", - "ISO-8859-9", - null, - "ISO-8859-11", - null, - "ISO-8859-13", - null, - "ISO-8859-15", - null, - null, - null, - null, - //0x10 - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - }; - - static String readDvbString(ParsableByteArray data, int length) { - if(length == 0) return null; - int charsetSelect = data.peekUnsignedByte(); - if(charsetSelect >= 0x20) - return data.readString(length, Charset.forName("ISO-8859-15")); - data.skipBytes(1); - if(charsetSelect == 0x1f) { - data.skipBytes(length - 1); - return null; - } - if(charsetSelect != 0x10) - return data.readString(length-1, Charset.forName(dvbCharset[charsetSelect])); - if(length == 2) { - data.skipBytes(1); - return null; - } - charsetSelect = data.readUnsignedShort(); - if(charsetSelect > 1 && charsetSelect < 0x10) { - String charsetName = "ISO-8859-" + charsetSelect; - return data.readString(length-3, Charset.forName(charsetName)); - } - return null; - } } From 79bc16037374b44459550e8b2bd0e1560968cf26 Mon Sep 17 00:00:00 2001 From: Pierre-Hugues Husson Date: Tue, 18 Feb 2020 13:45:03 +0100 Subject: [PATCH 03/10] output is created at 'init', annotate it as lazyily initialized --- .../exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index a22eb023dd..2c8db491b6 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -35,6 +35,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * Default {@link TsPayloadReader.Factory} implementation. @@ -192,7 +193,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact public class SectionPassthrough implements SectionPayloadReader { private TimestampAdjuster timestampAdjuster = null; private final String mimeType; - private TrackOutput output; + private @MonotonicNonNull TrackOutput output; SectionPassthrough(String mimeType) { this.mimeType = mimeType; From 28c5043dc113cc5e47fb6e9cedc08ce979fc58e5 Mon Sep 17 00:00:00 2001 From: Pierre-Hugues Husson Date: Tue, 18 Feb 2020 13:45:14 +0100 Subject: [PATCH 04/10] Comment SectionPassthrough --- .../extractor/ts/DefaultTsPayloadReaderFactory.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index 2c8db491b6..7ca35bea71 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -190,6 +190,15 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact } } + /* + * This is a special Section Reader that blindly forwards the sections to the decoder + * There are many simple DVB-SI features that have their own dedicated track, and thus doesn't need + * additional pre-processing before actual decoding. + * Examples of such simple packets: + * - TDT/TOT (current time) + * - EIT (Name of the current and future programs) + * - AIT (used for interactive web browser on top of the broadcast) + */ public class SectionPassthrough implements SectionPayloadReader { private TimestampAdjuster timestampAdjuster = null; private final String mimeType; From 98de7c460b103799acdfd80b87c41a9169a8f921 Mon Sep 17 00:00:00 2001 From: Pierre-Hugues Husson Date: Tue, 18 Feb 2020 15:00:26 +0100 Subject: [PATCH 05/10] [AitDecoder] Move to ParsableBitArray --- .../exoplayer2/metadata/dvbsi/AitDecoder.java | 101 ++++++++++-------- 1 file changed, 58 insertions(+), 43 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java index 33fe6bf865..e911349e1f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java @@ -3,16 +3,20 @@ package com.google.android.exoplayer2.metadata.dvbsi; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataDecoder; import com.google.android.exoplayer2.metadata.MetadataInputBuffer; +import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.TimestampAdjuster; import java.nio.ByteBuffer; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +// Unless mentioned explicitly, every references here are to +// https://www.etsi.org/deliver/etsi_ts/102800_102899/102809/01.01.01_60/ts_102809v010101p.pdf public class AitDecoder implements MetadataDecoder { // Specification of AIT can be found in 5.3.4 of TS 102 809 v1.1.1 - // https://www.etsi.org/deliver/etsi_ts/102800_102899/102809/01.01.01_60/ts_102809v010101p.pdf + private final static int DESCRIPTOR_TRANSPORT_PROTOCOL = 0x02; private final static int DESCRIPTOR_SIMPLE_APPLICATION_LOCATION = 0x15; @@ -21,10 +25,10 @@ public class AitDecoder implements MetadataDecoder { private TimestampAdjuster timestampAdjuster; - private final ParsableByteArray sectionData; + private final ParsableBitArray sectionData; public AitDecoder() { - sectionData = new ParsableByteArray(); + sectionData = new ParsableBitArray(); } @Override @@ -40,8 +44,7 @@ public class AitDecoder implements MetadataDecoder { int size = buffer.limit(); sectionData.reset(data, size); - int tableId = sectionData.peekUnsignedByte(); - + int tableId = sectionData.data[0]; //Only this table is allowed in AIT streams if (tableId == 0x74) { return parseAit(sectionData); @@ -50,80 +53,92 @@ public class AitDecoder implements MetadataDecoder { return new Metadata(); } - private Metadata parseAit(ParsableByteArray sectionData) { - int tmp; + private Metadata parseAit(ParsableBitArray sectionData) { + //tableId + sectionData.skipBits(8); - int tableId = sectionData.readUnsignedByte(); + //section_syntax_indication | reserved_future_use | reserved + sectionData.skipBits(4); + int sectionLength = sectionData.readBits(12); + int endOfSection = sectionData.getBytePosition() + sectionLength - 4 /* Ignore leading CRC */; - tmp = sectionData.readUnsignedShort(); - int endOfSection = sectionData.getPosition() + (tmp & 4095) - 4 /* Ignore leading CRC */; + // test_application_flag | application_type + sectionData.skipBits(16); - tmp = sectionData.readUnsignedShort(); - int applicationType = tmp & 0x7fff; + // reserved | version_number | current_next_indicator + sectionData.skipBits(8); - tmp = sectionData.readUnsignedByte(); - int versionNumber = (tmp & 0x3e) >> 1; - boolean current = (tmp & 1) == 1; + // section_number + sectionData.skipBits(8); + // last_section_number + sectionData.skipBits(8); - int section_number = sectionData.readUnsignedByte(); - int last_section_number = sectionData.readUnsignedByte(); - - tmp = sectionData.readUnsignedShort(); - int commonDescriptorsLength = tmp & 4095; + // reserved_future_use + sectionData.skipBits(4); + int commonDescriptorsLength = sectionData.readBits(12); //Since we currently only keep url and control code, which are unique per application, //there is no useful information in common descriptor. sectionData.skipBytes(commonDescriptorsLength); - tmp = sectionData.readUnsignedShort(); - int appLoopLength = tmp & 4095; + // reserved_future_use | application_loop_length + sectionData.skipBits(16); ArrayList aits = new ArrayList<>(); - while(sectionData.getPosition() < endOfSection) { + while(sectionData.getBytePosition() < endOfSection) { // Values that will be stored in Ait() String aitUrlBase = null; String aitUrlExtension = null; int aitControlCode = -1; - long application_identifier = sectionData.readUnsignedInt24() << 24L; - application_identifier |= sectionData.readUnsignedInt24(); - int controlCode = sectionData.readUnsignedByte(); + // application_identifier + sectionData.skipBits(48); + int controlCode = sectionData.readBits(8); aitControlCode = controlCode; - tmp = sectionData.readUnsignedShort(); - int sectionLength = tmp & 4095; - int positionOfNextSection = sectionData.getPosition() + sectionLength; - while(sectionData.getPosition() < positionOfNextSection) { - int type = sectionData.readUnsignedByte(); - int l = sectionData.readUnsignedByte(); - int positionOfNextSection2 = sectionData.getPosition() + l; + // reserved_future_use + sectionData.skipBits(4); + + int applicationDescriptorsLoopLength = sectionData.readBits(12); + int positionOfNextSection = sectionData.getBytePosition() + applicationDescriptorsLoopLength; + while(sectionData.getBytePosition() < positionOfNextSection) { + int type = sectionData.readBits(8); + int l = sectionData.readBits(8); + int positionOfNextSection2 = sectionData.getBytePosition() + l; if(type == DESCRIPTOR_TRANSPORT_PROTOCOL) { - int protocolId = sectionData.readUnsignedShort(); - int label = sectionData.readUnsignedByte(); + // See section 5.3.6 + int protocolId = sectionData.readBits(16); + // label + sectionData.skipBits(8); if(protocolId == TRANSPORT_PROTOCOL_HTTP) { - while (sectionData.getPosition() < positionOfNextSection2) { - int urlBaseLength = sectionData.readUnsignedByte(); - String urlBase = sectionData.readString(urlBaseLength); - int extensionCount = sectionData.readUnsignedByte(); + while (sectionData.getBytePosition() < positionOfNextSection2) { + int urlBaseLength = sectionData.readBits(8); + byte[] urlBaseByteArray = new byte[urlBaseLength]; + sectionData.readBytes(urlBaseByteArray, 0, urlBaseLength); + String urlBase = new String(urlBaseByteArray, Charset.forName("ASCII")); + + int extensionCount = sectionData.readBits(8); aitUrlBase = urlBase; for (int i = 0; i < extensionCount; i++) { - int len = sectionData.readUnsignedByte(); + int len = sectionData.readBits(8); sectionData.skipBytes(len); } } } } else if(type == DESCRIPTOR_SIMPLE_APPLICATION_LOCATION) { - String url = sectionData.readString(l); + byte[] urlByteArray = new byte[l]; + sectionData.readBytes(urlByteArray, 0, l); + String url = new String(urlByteArray, Charset.forName("ASCII")); aitUrlExtension = url; } - sectionData.setPosition(positionOfNextSection2); + sectionData.setPosition(positionOfNextSection2*8); } - sectionData.setPosition(positionOfNextSection); + sectionData.setPosition(positionOfNextSection*8); if(aitControlCode != -1 && aitUrlBase != null && aitUrlExtension != null) { aits.add(new Ait(aitControlCode, aitUrlBase + aitUrlExtension)); From 274743cddc3d8eb4f4ad8ed6660a01f4a6ec1ccc Mon Sep 17 00:00:00 2001 From: Pierre-Hugues Husson Date: Tue, 18 Feb 2020 15:03:57 +0100 Subject: [PATCH 06/10] [ParsableBitArray] Add readString --- .../android/exoplayer2/util/ParsableBitArray.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java b/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java index fc1bc653c6..cab9ca2027 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.util; +import java.nio.charset.Charset; + /** * Wraps a byte array, providing methods that allow it to be read as a bitstream. */ @@ -320,4 +322,16 @@ public final class ParsableBitArray { && (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0))); } + /** + * Reads the next {@code length} bytes as characters in the specified {@link Charset}. + * + * @param length The number of bytes to read. + * @param charset The character set of the encoded characters. + * @return The string encoded by the bytes in the specified character set. + */ + public String readString(int length, Charset charset) { + byte[] buf = new byte[length]; + readBytes(buf, 0, length); + return new String(buf, charset); + } } From 90601267e6e25647b0c2eb7455ceb3381c89ced4 Mon Sep 17 00:00:00 2001 From: Pierre-Hugues Husson Date: Tue, 18 Feb 2020 15:04:10 +0100 Subject: [PATCH 07/10] Use ParsableBitArray's readString --- .../android/exoplayer2/metadata/dvbsi/AitDecoder.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java index e911349e1f..5ba8382984 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java @@ -116,9 +116,7 @@ public class AitDecoder implements MetadataDecoder { if(protocolId == TRANSPORT_PROTOCOL_HTTP) { while (sectionData.getBytePosition() < positionOfNextSection2) { int urlBaseLength = sectionData.readBits(8); - byte[] urlBaseByteArray = new byte[urlBaseLength]; - sectionData.readBytes(urlBaseByteArray, 0, urlBaseLength); - String urlBase = new String(urlBaseByteArray, Charset.forName("ASCII")); + String urlBase = sectionData.readString(urlBaseLength, Charset.forName("ASCII")); int extensionCount = sectionData.readBits(8); aitUrlBase = urlBase; @@ -129,9 +127,7 @@ public class AitDecoder implements MetadataDecoder { } } } else if(type == DESCRIPTOR_SIMPLE_APPLICATION_LOCATION) { - byte[] urlByteArray = new byte[l]; - sectionData.readBytes(urlByteArray, 0, l); - String url = new String(urlByteArray, Charset.forName("ASCII")); + String url = sectionData.readString(l, Charset.forName("ASCII")); aitUrlExtension = url; } From 46e342bf3edff8e4c27248579b364f344aefa8d9 Mon Sep 17 00:00:00 2001 From: Pierre-Hugues Husson Date: Tue, 18 Feb 2020 15:23:48 +0100 Subject: [PATCH 08/10] Rename variables to match standard, Add more comments refering to standard --- .../exoplayer2/metadata/dvbsi/AitDecoder.java | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java index 5ba8382984..b5ed5fa7ad 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoder.java @@ -14,11 +14,11 @@ import java.util.ArrayList; // Unless mentioned explicitly, every references here are to // https://www.etsi.org/deliver/etsi_ts/102800_102899/102809/01.01.01_60/ts_102809v010101p.pdf +// Specification of AIT can be found in 5.3.4 of TS 102 809 v1.1.1 public class AitDecoder implements MetadataDecoder { - // Specification of AIT can be found in 5.3.4 of TS 102 809 v1.1.1 - + // This value and descriptor is described in section 5.3.6 private final static int DESCRIPTOR_TRANSPORT_PROTOCOL = 0x02; - + // This value and descriptor is described in section 5.3.7 private final static int DESCRIPTOR_SIMPLE_APPLICATION_LOCATION = 0x15; private final static int TRANSPORT_PROTOCOL_HTTP = 3; @@ -101,40 +101,42 @@ public class AitDecoder implements MetadataDecoder { sectionData.skipBits(4); int applicationDescriptorsLoopLength = sectionData.readBits(12); - int positionOfNextSection = sectionData.getBytePosition() + applicationDescriptorsLoopLength; - while(sectionData.getBytePosition() < positionOfNextSection) { - int type = sectionData.readBits(8); - int l = sectionData.readBits(8); - int positionOfNextSection2 = sectionData.getBytePosition() + l; + int positionOfNextApplication = sectionData.getBytePosition() + applicationDescriptorsLoopLength; + while(sectionData.getBytePosition() < positionOfNextApplication) { + int descriptorTag = sectionData.readBits(8); + int descriptorLen = sectionData.readBits(8); + int positionOfNextDescriptor = sectionData.getBytePosition() + descriptorLen; - if(type == DESCRIPTOR_TRANSPORT_PROTOCOL) { - // See section 5.3.6 + if(descriptorTag == DESCRIPTOR_TRANSPORT_PROTOCOL) { + // This descriptor is defined in section 5.3.6 int protocolId = sectionData.readBits(16); // label sectionData.skipBits(8); if(protocolId == TRANSPORT_PROTOCOL_HTTP) { - while (sectionData.getBytePosition() < positionOfNextSection2) { + // This selector is defined in section 5.3.6.2 + while (sectionData.getBytePosition() < positionOfNextDescriptor) { int urlBaseLength = sectionData.readBits(8); String urlBase = sectionData.readString(urlBaseLength, Charset.forName("ASCII")); int extensionCount = sectionData.readBits(8); aitUrlBase = urlBase; - for (int i = 0; i < extensionCount; i++) { - int len = sectionData.readBits(8); - sectionData.skipBytes(len); + for (int urlExtensionIdx = 0; urlExtensionIdx < extensionCount; urlExtensionIdx++) { + int urlExtensionLength = sectionData.readBits(8); + sectionData.skipBytes(urlExtensionLength); } } } - } else if(type == DESCRIPTOR_SIMPLE_APPLICATION_LOCATION) { - String url = sectionData.readString(l, Charset.forName("ASCII")); + } else if(descriptorTag == DESCRIPTOR_SIMPLE_APPLICATION_LOCATION) { + // This descriptor is defined in section 5.3.7 + String url = sectionData.readString(descriptorLen, Charset.forName("ASCII")); aitUrlExtension = url; } - sectionData.setPosition(positionOfNextSection2*8); + sectionData.setPosition(positionOfNextDescriptor*8); } - sectionData.setPosition(positionOfNextSection*8); + sectionData.setPosition(positionOfNextApplication*8); if(aitControlCode != -1 && aitUrlBase != null && aitUrlExtension != null) { aits.add(new Ait(aitControlCode, aitUrlBase + aitUrlExtension)); From 8e84fe55a146c99c128268f2a385e69ce5dd7d37 Mon Sep 17 00:00:00 2001 From: Pierre-Hugues Husson Date: Tue, 18 Feb 2020 16:09:41 +0100 Subject: [PATCH 09/10] Include a new TS sample, with ait, dsm-cc, eit --- library/extractor/src/test/assets/ts/ait.ts | Bin 0 -> 30720 bytes .../extractor/ts/TsExtractorTest.java | 5 +++++ 2 files changed, 5 insertions(+) create mode 100644 library/extractor/src/test/assets/ts/ait.ts diff --git a/library/extractor/src/test/assets/ts/ait.ts b/library/extractor/src/test/assets/ts/ait.ts new file mode 100644 index 0000000000000000000000000000000000000000..938daf6a12e5f895a310471163ee440cc645360b GIT binary patch literal 30720 zcmdUY2Uru^*7k&6l`d67?^T*$p_fSLAVriw0t8YZg{GcES5TCyQfx>OP(%ep?5K!h z0R$BgrHBGn?B%HcnxG!fx&6EMyWcO*Ba&n?vuE$MS9{;J#@PmuLXbvL1al5Sc=$*4 z_O9Z&^5y6FamL_GF^J0zuS-Tn>tzHFqfg!66k2=@(={}2awq?j6teM-0 z*e3*ej?8o+2nLJ9VV-Z{(nh!uwI~)V*2>V3$s(}G;rii3ngM~qA{nsa4H+b27@NhS z(+m+VUIc^rbp|66!9WDu2y9w-6p0~&K@cHcI2{J_g$uz*i;`(XQsU|ehCvjIN|`5z znfLYF6nZ4t*vQl%k<20+gwv_>q%aa+KT1eQ_;x+cxENeBI$T(Un@a&b0Os8cg9HsI zp4=-ETe(JDW1fF?=k1Om#(3VRq7w5p+`ijc5AUA|D$2{M#H(rL7Y#*Q?LC-tOdGG? z-Fs@YF}cPhIFOQf^`PpKiq99QR`aVC$<-JPuDxd-ePlZ`G_7>>_~vTdikLmMlG#FP z_`ZCHri#S7(b`p3etda{J)^>e`uv|pZqMmu?;ehcC@glhH>Pyh7)$vb(A?lw&9Jcb zvmI@IF&QQ0s-L0fupzgx-Q3z-;#KWi;UQON3_~8~^@kfYj0y{5#mAc%A{arKM_zQE zEfB%4Gl=1m#&RZ*GkeP@1X|>h2*zqBZ;e<&Bx#AKo1c%xALlgOc%k`Un^Pw1+mss4 zQ^hEMooV!Hv8)7Am_Z~(pP9sDk*HAwHq(Genx}w~`+Cq=ovX{J`W@P^&*iZI)xtoZH)a^JLUhA zTEKb17qS23`Tm>da<M=hSOJC+TZfD zFx)k^X^h8vCgV*eS%~d^`>MA@=^9kw7t1?w}#Ad|3Kem=I^ z23Xs8QaBbzfQ^H7BSb`y7_1}%tP6ocAthmnQ{iMH2}{&OUox=qbTR`=LC-@Whm+tx zHUrBf!@OHykV$K=Ckr=@$d0ZN7`cC9$HL;R8op2ETaYV>td<`w&z$J;`?g?tyt26# z5yVnLV?$$EQi`&QiVBBUnBKGdrTD{&YlPnWp7rewwce$}#~l0hH2Z3o?&>k0;kxsH zl%#W{vHYUH_fgk#E?I*y`FRCh-NdveX;OyZ^QHOz&RR9D)7yuFB-gFYD)u=$We^qZ zdpOip%S*+$o_an|tN0fl;mxBIincsXXs}Eq-5HUPh@=tFn}hozl9{mt2G|HX4GXvJ zPGCh5Xa-nExOprgHkLu65wUS>O)>@MGK@^3VF@glPX-xY`Z7o)tP7b!G4SJY^0r07 z4}MJiT{Qn!{(^b8#2|#tBOS@>ej~P2tzKiLQObC{gW6FurV`PjV3e9iG1E!%RFjbo zz8y5IS+#=Ce$9tMV>Z;1#jfR=4i#eG70T|Q92u<@-lM>$fpAUL?p z*K1Gw;RW{YK{2MS?I9`b^PQozeNFSL?Aj^1p7jT!P`dAYqV{-$nAB}{il9^0&70EPMAR0Ipbaatnok(mfMU%j0(HUeW=WTSM znRM_Q7gNrsEl;GE7Fri|u3e)oz=MfY|C=bg*% zrN2xFSozCwhiAsm{rwECocOKm-ue4cFC4Dw+WFFwo5nItBafUq7qq@w{_;^>d7i$v z`;N&R=VH+)!LOGD9n10?Jib$2;n0Vg*lT{D7b=LWhnBV+_VLqbHb(+_9r2>O7)urm zT;bpD@XIAPr}w9y?#yd4OA_D3-ty|sc5-*4^2#kAU$o*Lz3SUi#jP@g^I6-KbXsWP zwUIiLgKNbMC%Ye?jyv2Ji;+Cn)C%%0MW)3QnB;g8Of0&WlNewq(M?Xmew%NY3>tZo zfxbs2fjPwz+1S`90)s%O8MyNyur>(d%e4*h;}```-Pghxc#k(8PD9tj8k<`!!X}Ve zQFJy78%v;JLBYU$>tRjIT#`Lkyj0^w4|edk!!p@nEHaA%^Qsj=XJA=TBrIHoh^0qh zqe!q3X_4Ak^aFHXQV1aLi};b*t)$)OeBN*WhG++`P)9N253jpVReoCjs=05}Y3H6} z1ET6$N)q}G8*X>|_f>1RJ-M@#(YsKtaO)jE*E5+dd){O`e&lK1*U8L@FbJ*F{ES6B z*6Z>l34F+{T{(V*l~w7WrjL(~>3!vrXj-CWuw(g?*g%Ov#Z{}e1#s8)Mh_$$@*5WJ zS1O$A*k>O6qN7-~`f3-p{`&6c8p`RsS;w-Tr+_{D{=@C}3NH`yE}eu0;o(dUff)-^ zf@Porh^5kLWSAaL(O2zU=){0u#30d~*SARhuV{^b{iM#eI4f?OFJHhtJ}DxwK806v zpWw?CKk9Tu8&OLIoqj`@i%by9i;WFUA&SY$DoQPAyx3n~&3b=h8rP#%h4*f{&v940&Rr;JbzbKRH5j*w?H2C7s#|DW z_R51$ep|e1Ra*6sr*yUozW?6I?ta(%nH9KCua{V#-~%2)a3YF|8E z`MWG&9S|dgSHs@f1_9UQr;W9jV34HBeV*-=E)I&|oM4ss1o-&*_?{}`HLv$u%Ri7# zXuf;S2Y>nD(}J6suW^b zfnAjWKa!W5=Pd?mN7-3keO3oEvYG1%h=G^2%Jn6hbhqD;oxQwLED4M zV-X^0B&LdiC*KQh)OdLE&47-aCyd$#Umlk8X~f-%V`xlaEIE#`WF{R3X@xb$8e)x& za=aZKT%DZ!d|W-;;Z3mYMhme{q)Q+b=~(fXuF<&H$M1O7i+FSU zT8%od)H`uNBfOxx?Vdbd>IhwpszQ$S?02oS%kw;XsCH;wkH+TKO5%W9;)+8O&sSzj zM<^+{9Y04Msu66ky4hqpNcI<)PJah6pZHH3^V#Z2{Ip}o;Fe*K)uPn}17&_>Uo%|3 zr?!}OaH-!#|CUd^?%DkNJnYU-nG@_f_0y(I+g?hI*XBBq+cXw9yM>i4zFf$aV0@_A z-J5#l_+ekc?a>)$Lw)qTG-OtguDV5q-0-(@J9Q=7%|)j^_sN6!r;ZX5#QK&S)W<&Z z{>LuJR$Oa6+-dBBkxvxW{e$*KkU3?_CwlG3q3(JrbLI$%&6$(V&BK+Ro*?`F1)kR&6JTq5UnAVV_1>1eru_TM z^-5w@lfzO9Q-l>qM{_Q|56v$SP~=;`B0a~!a_b21Mf=E=Y{bg<_()#&)cofe;~_l( zW;RYrR)(tCwCKYO9g|g`r&AO*#IsfFw7a~5ELV$dX&?Em&wtCQ=`udgk4kdcZ!%N9 zbU$ww@4kQTsQjJGlymRIY}rWWIf?#GZiGj`t^gV9(R$4;#BMyXyO$qyq%6c9wmdTL zzsDYO5PM+Z6_Tof*Fbe7uN%0=B=AllmLP!gfq;-eV{#m31_T>q65?LV(NBAMZ7Fn6 zp`5soiDg1q%$R~G5`q#4Bf_UBQ(+LVqbkbS$Y_!ZF$j$Y&NqXJhAdwdFO4`A3$Z1E z20=BdA1^`5imC5MBcm=lypAG-ky!*9i-`ptDF=FR1%W~X=h~S;k0)Unq=$4kBV3OS zkq-r4aiZ5)0*gVWgNmn;AU%McRf)blVu%FtuX*gUuP$&LQ!1OnBI}1kNK7M9ux19v zUB=~;|AMvz9chF?PGySfzA(CHd;WUeP-V$$o#;LO)elO2ukSqfq|d^mVd<(8{S4D$ ztN5&Dol9*R_r+E}YbElelJC>`Y~TB=|NP`Gk8rIX&x`n^fpsZc=woF(a1qf!b5G! z9;(+aOsOKB@+0ME79-;+Z|0^&rft@%;2M7sYkB>{%hD3-G`ovY)hE@Dc*EF@G03&G zyDqYwWaE>o1SKR)bk?r8@@$D=gm6*wjIw{O+H{g+xA`hwG5dw~;yHo6dp@=+eW7g= zIJ0Q!c~hMcC#vG{`^m)7_c!yTI+@pWH}j|yMR^+CE&Az9g#q2A4l$C)&bAnuTWIYV z?4ryYRVv$MtdM?`Ae_AIZPhPotFHwsyi6YdmH+%6d3#rnm%-yQgFKo0yPg=GFe;UObFirPg46 zF$6DXa`%=?QC98E%g#Bs&Rmlyx1loR^ReAcm;Kbn-sHaRvBrHU5}Mkv)CX65!SIH) zytfK1+q1|dnt1<(8F@+KC55(`6Z#I+-*{IC-x@xugshB%u>)o^K+HEMd$nsyG~;~t z@BOIwN$+fJ@un-9(t$l!_uuJD0TW*^xQbRW;Th^@Xl?056}f)ByeGn* z_L~Mq$Wm8?jI9h*UhlUvUKu0C2bE&n9=p5_x)t8;k#Mx!RY4<2sCfzR5pC zvGy*0&jue?AvJp~g7D?NvQ=&DjF%CP8L7rLA%_JxI@gNm9vfmV8e<(F*Xdi zhg%f>G5IC23pXk*H$R}BwQrtciQ2&8h#4=OzAWj2+&-5*e_>*qD zK7T;nWVd|KE5Vj*5fyhWFV)>st*abWY^9j>rFZBdT4tn1{ISQG)5N{%OB{*pbFP^< zuX9g_b_c#~u;M3$#yMHN^c<5qqEc6LGpTZDRreWv>ngLU#BG^0sbn>cLYa83z$!`G zDSPwPT4IF?^?O!I^tTne=1ZR;;`hTCZ^+4&C^AGf_I7c4%uQbH}QT{(2*%*a^* z0V7MW_{4*Sc3BUKqx*N&F@=syTTfM0?@Rx1z`d}hufBKfm!_u2R@GA#_X%F96eHNI$)lE*`e1=v5VXp9-oUXPg}K(oyvRiae;mO*8O8FugWk_|YM?L|v6{lYT6CB}|) z={DMhy-OJ~8bAByQ#%`N+9vIcgsYHwyVa1^w=4zb7EDgLIGnpG!Ba2*oW)AFknw}Z4zCW5_I?Kjwki0 zQG#04qh_VUxTdro|ITJp9-2~a_(bB)Cnr1$Zk!i#1l+^=Pq>G0@(;KNSQdAKUnKMU3C>?&dGZI`wOoE>gVlrJnJ~Zq-+c-(NP207!-D+4T>;0>Tef90- zRj)LkZaw{+V(F3W<6rBmbG^2>x0;7i)A~}w=*`V zYCmRPySrn}f`Of@gN}(Feqvk}@o6k!gTs@99>-LkDn|DiQ(xxxxinb58O2bWsrGU= zKet=HOW0g4=HPYu9wRnW0h{(_CWBbj*KWF|R>nR+3=`^n@nfiuYRs9(aUa7r4MlA( z*z$Q{&Q8M7hW7$#S+)zuO${F(@wCs=8Id~=zL+B7{-TrzGyXk4V+{EjM+D=Er2UFl z1&3rlFCOqgpd|q~Ork-0CKB8saF>{*a0ZFRMBG23NoC|UuOsB7QP&CeYQVQj#*)A} z(!<(;lQhe304y0iG1NH&r;Cb$o{++!!pXr8Lzxk58a9ef1&0X|Jm`19hXOQ|N}!_^LC?M$A(VKv|g9KI_dSftr0uUqOr(l*iN23!g05Hgfe}B>CCg4EM2WGm76O2>ncyo zCpfBS<_gs7(1jXL+&O(QVIAKW{ZsX_XBDhvLY~R!l-^6wYh%2aFfMs_WU?wEGfEZL zGX(R#;P<2&iUae+5eQ##;C}-z|ND4z2+r%5e<6AIHx30o16Ucx%Tw`5Q`7jOk%9t? zH)ITMPWMtef4Z@nDkg4XZ)fbJ;Ja?#-y17xib{Hu|FO0q!)Gq3Fr(|-a)b0gHo>_Ua zaYfLPn==Nosd@ngT~e+hT-nMgUTQ{=ro!5YY2|@p{^5sP@WhPt559>52Ino6Dgt^& z3}QY7E*_L~#f8Rvh$O^(fOA707{PnP29krg#tp<8gqak9(w%0JGFk*3#ij6sNJ26h zqAsE)8Qx-0XBj21L7A|tV0VBfaEE=AgmoY!(FoxN?&~-?;{^DwAM8KSNKgH54VAhKgABnbI9ffyQDG5{_E1xG-GLin6P0+|skRQwbT z!cjC)OeI8rNA^G~ko7s2fpE~5OXVAHL+pF#hWB)IHg$1;=#IldfZ;hf=wkhyGMc8X z|HjJy&))`_0j%snp17mDR;n2AzhXm)vOqls?lG=gqIdtNs}>Erba7KJUr5*1Res{N z+fXeZ+1Gnx`LO2m`W5T(R~>dp#%+kV4_0r)x0Y_&CR);FP2Qz7P~$edkKt74a9=CN zE3rPKk@jfgoF^yq9k&Pz*mb)0XB(~UdQ-vT>qJu~2D!(|y{8i#u29X7ac4X_8P=3- zTU;Quui9JKA%7cXUmPX zFlS%T=qThskajVil3-2|(R&PBP0opBM8_d}wjwR*za_Dz{K+`O6m_UeF$%~q_Jvga^u z{NkoOZB?Se1758cZyGNz2s5wpjm#fX zmw8SQ-1N|=fKO)KfeWU*; zk{N=9>43Q~LogZml`tLRyq@6Jfvw@(9C)-$CvddC8o6-5|G+r}OZB6Dv!`%?O#3K8 zGJ(Qj6T{f0!m&6t3*#EQKk&sH*wY|i`M~o zvTPa=sLgEPZ=$|E5vCkXIumGQ@N+oM#jMi<{s!j*7GT{RV&m6O(J1HpvM|}v5SO9J zb2Ml|qp@$dz+s_+eg}MYO~{KHP?)|??^Ux(scokF&1MEdKLr}7{zQJ`Ml8?(?c+WHsG4(o|OcCT6jg7oR|$=@=$I<~j3ZMB&+}E$_9c ztewMqTWUTj@9elLfK5zp0j}@-SH!|BWJd8Q^|bu+K0(Khf`AI=XadhAnD=?8^T!FW zKqOISxIQO1^X01f+oJaWftaaGz}RJflLXc^sDNrbfujO!DU?@wSRy@qc9E5Tl(BzX zzA8BKRsK)O7i@7F2oPu+)ahl$LNNl$E(2hN;~K-*LX#PvExlOot?oWP07Ir|i5f=OV&=zh~qBORGWx{SF+IjH8Oj9PG5NC}~k2#gQ+?AJ3`SKeeMODEP{)6Y_^EADfgj zBwq4e%%O4<_K7>^iivyF*B+cxKJPkE@gun(?kN**@^FiaAigQS7KnTQL@B^_JUW)2 zOZl-|vF$mj&6abhxvDk#WY75EZG<}Sjk$WZ4*70vq*M(j@4Y!@@`uuP_8-&TA+vj+ zAtrLW)%}n&eF+WC$NXGP!@_#OJNq z8UFD?7x_tDWO9RPxV}Z1_YT?$?W{+-zTq(&ObxB_PaQFk*&oa0Z9zJDu{qbG_;LFn z$l<(T-zIY5t&jBQzs{L*w{>mb*jYa!C*Y*%8}{2(cBEd+UHkB+aiXuE?n-%Eae&~+xj*SXPnm8MyauSe#zjmUx#ZbX<}F1l<0w_kE&;`lI>U- zjb(56cb+(alc;J|mh+#SP*uEBmUEjd6d2aa8fn|1^z6>f$)eQpm!TDes#<00`FlJI z+t{=Ni3)SmrF41hRTrz`JpFx33T2VOmh;{tNjnl-V_Xk)Xzv!&!;Qzx74 z-K+~ay#>JS+{eTJ@jG^40(R2L5pmQ^&Be=sos{Br)3jwng)lf1;8x6<$KMfy+2TlW zf4&!%VYR?2n(ARqO{p~P9LVLNL{LtO9`(e3Ki8~(46fibc)CPQ5<8qgNs47bU;=>* zb_&7};uMr#L9!l9G^7UM+mYl*@P$cC)Lj8RBL=jLaPYRF)XV@2Q7D{-Q|-p!I5OyU zQHJP`Lz{&Jn3{x0GGq(c0A8>uknV#!V`J&>fx-%rEL=REjG`Q<`yN9lPzX$eoE%#} z2Uic^(_3MoUeW!ct^0fqVj#=w>H{46*-uU2Q(HGTN4%}O!?#R*{$DfvFn003Y1tbN~G)3<9G6W3(n z-h1-ESVZ$ZdTz+aZP)KD+2S<*aC`v^e@Xf9YRzR|-rT;fx^eMJ?PIt0Ze3al@-6{6 z?D<@LpMbYCo5N0q95xq%h$G6}csG!DQAp?dK#^HE1bK9-6(?^^gnD1B5A<7z0YUpJ z@tU)d9XQRLN=nlo-N1#0W+n#iSsdp*1w9|?B%oEs`m+KHCjm+oBwIj) zIdB;G`?KZFzFdCaLaTqA#=i7LY5jjzsz7Fd`@_G_!037EwD;39w6d#vc4~I%3vp{& z-M*z}8E5y|)>X@MVoiRlOOqcwvn}-Wqb*r2M5+&IG*UNsZQ*0-9fsr<{xQ&ab*8pkjNx}1+XgM6F+dFnOuQ?f7E z3&UhvmBjsfI}_y36uPB!ObTbYmMICj7{a1T79y~%3b}) z=&k_O8Xl|T%jZy4&&k|Q58&6+U}#Cl+C~_qzVx%sdr(}uk!sK|t+dk!D869NL1uu* zVQ*960_{3Pr51}})v)ukGt$zy()ZKQBL$A@|*VGqME$+;>Fc~|Mu};(_&v!~KM{WCK z|JN=`ecN10_R$>=y7hxN&`JaDx(L+;Tz*Pi84uIfh* zb>-$p-p{#KwcyB6JH;-0(*F1r3ey&R9i~xd$DM7N3W#+h6X7}wUI4ckR|r{!c2T6M!TJB8dQ;{ldA<-OogRX!AtT ztwvl#szcUZ4>heqiKZL3yADaG*{@6%d6L=jKHeTE1IVxXsvo9kudNL|SWhpv}7sjk?J_uK3^dInj zkb-b;9LZV^JiWz#;^~QEzVh^b_sDmn9=WM^PM)WOteBLnloG$70KWj=)2)SNyalJd zKXtwKSiNj*iN03!YI~pPN_F24OLRQ;1h|UoSzHhIVUGpO+0eO8h^5mbaVq1}%bMnU zEc`W$&;Bs#wIk^tebPVchhhi=ra>V zj>wh^`dSc1UyLhDpLR|*Uy9ad0e{AK=aB`GUFZDu^?p0h>%2<5n!sB3>|BEEe*Jh^ zI{^XBMHx7v>b}#eWPhxU+M2l=q6WTHWbn9c<6`}??BmC+-H+9ySJ+?Bi8-*;*7W>f zocEO?Zho=QOOVKV@L+Av&dt8s^L?m-%W?$?BlA`V<8F)j^7=NpeY*Cv9fu2CJx6(6 z2JYhGt0Jdo#I1dQZA=u%xV`MYVGz|*`*p3+=kcQfp}#5Jj}($&#M+E&DWyNVeB*4i z&>}$?JMcI%(vXnzSR&v1z6cZ5{UuUGP4g2{wp}6mo_=afU3Pz&D|Y0>M7^FYW8Sk* z>Z{L*ujyG*X>T_gE3Accea{Rm!7m|rzglY8V8`m_9f=aUrRJMYbm917H zkptcp@PT%FsX?~{B)L4i{11Lr3c_n-17im+1nOO>3##qj5MXIzV`If7A|xatdS|hw z`>s=LA<2i=@0?ou{^`-FmABS@*sfBw+Ay1NGwT!cRpT!IhN+J`{3Tx*v}HQt-k-YgGS~;Kn&apBKr>CHx|L&@OVsy+(eS_-o50`qY5W z{&o&sG-4kc{C6nl|7m+G0FUEs^+zRok@9@^X6v)pRabgtrIN`3uGEh;*Y{Mf35_ZB zt6#?KQJ>eb*te!0w=wRhBr{v-mSlpzl&tigm(_~J_KzZ}2283PNsfW}#~tHkj_g0~ zSv~9t#quVE>mGU$>U9-|c#q(Q(%OS1#k}jZ~>y;~WitZ2Gc8zW~7`S<( zFUVUf+SJxATT;b9bwjB$C4_uYh1=gMBV9q?z2&?|(L95bXD%2tiMLE7Pw|DjVJ(=`FZ7`M|2C*#E>&BA_DEkuH4*`M= zWkQ^?-3SuOF-0L$G;)Ss2`IG{jsg}Jyq;yCLdGf{E$%_lQx3!f5URVak1Zz*pT)t@ z6cF07=R0l=mqZz!5Npez`6kXc`~v|-k3|`(UkRyb1=1AoHe(@%pUpSTGGZZC=K#kf zNL~>jzl4?)MRFKw5ri-X3Yg9E&Ika0enp+2ZxVw#MRH%nNhU4iMUlGM9M$*H!-;0^ z6Ci3jpuzwW$9^U5Ld{Xmw>;B#Mkx3*ir~-C5M0oBU|{#!(>Fl ze+b6a$Ab_4*G9PDFHd3ka|Y%2f!_nKZ6v_6fnvcKAq0~Go(}2&!5fITg64my`3~qA zz_qoU^&|oX7TwTG3xqZ`TV7ojyjvyM^K`kq)19IUKjRC>X^-n`MBLH?dIRso_r9X4 zv>(_UamSI>o!ymck(q1nr7i_tpK0>Dea*EiH|b|6`M*#K%D&@yRq?(>{GJV?c~R^2 zaB@n!$$U3%2WFqum%W~qH0Xlf3HtN;%BiRyG$63nQ+N%*7^;Px&k1nQQk0dI73b#T z7vL9sT>Myv*00_0rr2Ui`54H|ynnK()88x+|1+D_>7^7Ox=sGn{fUNhCn-NRd-@0P z(mz5IVeGPhyOY%4KY3<3?&Dt*t^d}0ATxlK{W9_K)>L`#I$C`_?nCJ-!`|M8;+GGn zz&Yuyo9-L-ejt7Ma`|m{W&h~lJFoB^%C*Oy#Ux1$a?MRrl>RKr`d}~EkU=}~>cEB$ z^9%E3h8FDFL)aW&I&VVHtjnINhd5*&ZF;))M*2ndvv-m$>2!sITwS}c8z&}RJQfAo zoE4b7&-T%}{qR6ODd0${^o~~-|Ex&*5v{`jV zY9;kjv&dS=6UhC%v9_N4Pdj!F&KJFka&2F<>EY4kdS2=m>2InkPM>!nT*+_ocDb~r za;%4`@G`HKfkGa&{ztJLOI>V0jgqIZ^DobEa7 z2J#MAnUh3rrk3g!5_3P(sMq9BMdKyYy zX>EEGqrlf1a6SCyOF2XLxykrUs>H_Mu1CiNOg)g_Uw1gxmACrw# z;{n7$*iNj-bHeyQ2jwP9uC3|! zTkjfKi|;XiFBH6IYgk`z)ls$ZBL4wx@CoD~|DJ^5y#!VT!{Lzof=_@&5Jx0+mFGXs zO?yhuHpJtk$}3iU6l9aZ%@=(&H;bC@Ev z@49g1@{YY77HV`I|DpJ7r4yjvjJC2Pq@liq{km$kHK|hYA0Q|;rznEWpdg9$-Ik2!A zZg3dj5I~!Yhc1#R;|8s(Az*dT29(-xfH$z^A|lvm3rYhhy1$E12O|xnHcH9HQ&dKv$n;!^(wj68FDKVdK>2r>}A*t%2x=; zhT2KFlx19`D+vz{vv2Inbu(5FcR%2E_M!1kp38?Hx`azA#vnQBGpl4`wr6D|NR&9) zJPtTK$hXMEob zNuos}X`2q!l@cD^@^y1gu^ScKnBX`xQT9a1chyGRhM<`yGpnS~a|LHxKIu!W?Aiol z2h8T9%lRvDIIn%GosI9W9=%3;-;Z-DlTg>VeQ2^wxy$`|yO2@q<(Vns$Hp~Ze#;mr z&5Y6|Qo2?!Z7@=2RPO%nM?sepyOc__%SnNgiOZ;19@_ShB_@V;vOOf^(cHeat!&%% zj*|_abgH_Gm#(3{8p`+-C?9mg+qJiBt@aB4i8qr&C7#G>L90B2Tp%7$^@FU`vg*$|8Lb|0Z-X|qF-dx>oXZKr?fysiA;3{PdM)owa z;52rDT;8trTXylMjqRN*P%m_+il=WXRKL~a>%X8xH;F8x67uG1wqn99bA8jzdxgVg z7pyIOU3Hu}eN%d3Vy3<0B4(R__v6|ZvgrZu&&pJ`=7I2V(l7Z^!C;sCuzq|UMO>UrtTWRju9hltRo&z zTweRyTa>CEcy>ZQ>U^QqqTqeUE94&;ced&e-IHrPCJK< zjof~TWIH3S;uRww+9D6lKQ)=BUCfZWYIaNMe00-$eyh^s$&CEPp%>x;1kDRhDTNE1 z^qX#8zLlCdJsjrZo%MuzGTe9AMxnYd+6Ud%ir5NVwz1RL)sE~FwHE~?i0cz<4@_iD zt4%ph=n-Wna++xyG9crCjr&sXbkMRCf2C*{zN$P*VHvT)OfO&^L zwR`#b#BO99;+5d`toGk*A?11d5ZOQ|tNJc?*jDy=9pfW@y=OIAq36Ly(zK*}ZO7yd zHr2vb{Qj%M3l&`3%JoK%>SR?r^AkfICGhgg{Y`ZD$5p;4A>C?d&$3JDduJ+C;Buuk zs!4P2)PcW;zqzs}*qOb@Au`!CyE^xN~f30VV;Syc`T90(UAFu|0q-bY;|R?dxX zl8HQ4%@nYEe8=rV&MHmuVrTiLO>y-uH+^;a^CThWQ}}7)CR;tlpLgsGiJuo6#x4Ga zH`ztIR$Y^tQjOYQJ?BMGhgnOOx~rjS+6B$nUlrb(NK_r{QHwv*5=0&E+>o&O$zf$K z+;!p_dDkj&#xX@@8CSha_`wNA$r`Nz7rFvIF=>5mZuT{+UyUY9`*yhwWLCOsN{u<% zO}~Ey@D!v$EW#>e-etR&nsqoHn%>hjcS3FR-g(Xroc6+Ctfty2M%n1v}|0bwD? zlv(mD+A(?xU4n3^?sM~^J~97FL4Xle#- z0XU4DkrFtyoWT5r0wHuT5kRhmbC1B;VF95yghhZB*w}Ei)!HA{(TYQ^g@v_H=e!TD zB2;5Qsr?j<5D8UvU$YGiQg~80ntu5!9NT|DtyTR4wHC%M@sFRU@OP=Th!^aSMPgih z-dt<(ny^1qc#RjCnql?fAsOGL8E%t73gc8$YWd@}f8QIvP&E}nrclupaYg|j5(s+` zi2+Y-`YXG|ILg!#-7g%-3&t+_ZMr#43)om!S6A?AN#yV-&ddY$1KR`Y%wXPZ!Ci;< zA??EHX-B1pftQV@N@mv<4rNV6a@=x|24FOEl0KSfsc1MMJPOTpecRsf$S017qAgIq z-Jk*1mBx%EGXS{a&{CtIgbXOAu-GWGl(Vs?zV;XAd<0{cVz}?*$k=vXPFWb-Fp~|n zjSvICkjU`F7-(JcmlW#1vAo~u)C+-5?eFN#>00jL?dt66ZtLa^`YM| zdT(?NYZkifGPS>d*A%5YeP%^PUhzwe11DJe);QYD8UhA@o>(zp5U(&~Y zoE5~6u69k2*4|Tn=gQ2X3+Cw&cjCtb13oBu4>8N`xlxs)dWD7Es*9{%_YGCo6_(QL zeO#M$8?s%zh&leFH0!aM1KbBL)ORpjZVQ!C{qU^??!AH51l*EUmcyda)7Bl;MVIv- z47s#cwzBsdE#02-zVYz+J-iZXr5(fgmXpJ4w&~w?@f=dynC%!LeaSpWLviT+aM{st z){~O>H>SbFoaX3_Q13DS=+j!+nyI{Ergi)!hAFpybs+z9<%2I5b6IL!JMqvom~y~* z-{TNU@w3KdWi@T-YGPehwiWlxFD@~Bz)*iz8aOm#b!tI&;BUvMTb06pizLceTdlf= zH$40}(CyC5?Ge2HO5(t=;gd3g$27t}K8QbM{91C&(g)YIWDIU8?qFaW6*LpC)f9eK zJ+g5}?kDSx`qJ?xk&rWEO&Zq~MHlP5;D54g7p~>Vrp9Yt*Ztp=@42Av@oVBG%f-SV zGt&R?(ITttI zpe(1p6-A)!`xpM85dK$$=mk82>Q%uLpwR1}zGr z1Nnw;s^hN~a{QA;958k{68+CItNr!aKHtZQ%IuFL`!2WJla2q+9=!=V61YEv{MIt5 zqQ$3;&Fmhoy&YugRd1shwC#F-fWHYf`jdp^8ER{^U$}O1jkdTHaq!J!kBFG2o28o{ zvd)pV5d;mU`(xylEy74ag0>lh8Cj~*(gBUk#{6z*XpfKjB`u6_f2gT-tYJxiH{pK+^PM86|FgKC* zdB5jcW_!(*C_`RM9`a&#(7XP7UQ8bHVpvXIO!J@aU0=tk2RWgLAl9PBaZSp+@H zAT>vVT?K6i;E8b{GYT}Q`@o}mD;zEg;0e%QX15FaBof#i3_>h`GblJRYjB|koe89n zuOj=OY+nDlZu5bEK4Swi11a^jy%I(`j*2Q0VsoI^UurrxwE9bsoiYh}y320u`x}GP z%sRK~;sm4G_GaH3Iu6AjhjPd(f7!enf zMlfk{1vwSFwmqrL(fL(aOX1?wr@Jx1lV5gWW#{zIxO|pdT%vgX$;hiiXEZE{D+BDi zMt%!T$w=OHLU;*7;7mhYa-573A>){TT-S$y^5osy&ZOr(Jfo7$5}KKI=2IA1Gs4NG zdd%#?VD5p;%wm*yj?4f?shsieV3fNN_hAl3c?&=H3`f11^hASkf#0K=hc4~!uQs~B`P$cz%&8JQ3s#|DTGpgldT4?PxK mMJU5VO#=$&LHjv0Y~utWpyfcdq8Q$+lSmKa^qoMP=>0!i`hR8s literal 0 HcmV?d00001 diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java index 93e3f30d0c..4f27c9eff8 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java @@ -53,6 +53,11 @@ public final class TsExtractorTest { ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample.ts"); } + @Test + public void testAitSample() throws Exception { + ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/ait.ts"); + } + @Test public void testStreamWithJunkData() throws Exception { Random random = new Random(0); From cfb2e9dd11adbe0a7cbe95261d43e3e00b41827a Mon Sep 17 00:00:00 2001 From: Pierre-Hugues Husson Date: Tue, 18 Feb 2020 16:10:07 +0100 Subject: [PATCH 10/10] Include an AitDecoderTest Those test data are defined using tsduck xml format. feedInputBuffer is identical to SpliceInfoDecoderTest's, could be worth mutualizing --- .../metadata/dvbsi/AitDecoderTest.java | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoderTest.java diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoderTest.java new file mode 100644 index 0000000000..f0d020cc1c --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/dvbsi/AitDecoderTest.java @@ -0,0 +1,178 @@ +package com.google.android.exoplayer2.metadata.dvbsi; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.MetadataInputBuffer; +import java.nio.ByteBuffer; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public final class AitDecoderTest { + // Test samples have been generated with tsduck: + // $ tstabcomp -c table.xml + // $ od -v -An -tx1 table.bin |sed -E 's/([0-9a-f]{2})/\(byte\)0x\1,/g' + private AitDecoder decoder; + private MetadataInputBuffer inputBuffer; + + @Before + public void setUp() { + decoder = new AitDecoder(); + inputBuffer = new MetadataInputBuffer(); + } + + @Test + public void testSimple() { + /* + + + + + + + + + + + + + + + */ + byte[] data = new byte[]{ + (byte)0x74, (byte)0xf0, (byte)0x29, (byte)0x00, (byte)0x10, (byte)0xfd, (byte)0x00, (byte)0x00, (byte)0xf0, (byte)0x00, (byte)0xf0, (byte)0x1c, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x20, + (byte)0x00, (byte)0x71, (byte)0x01, (byte)0xf0, (byte)0x13, (byte)0x02, (byte)0x0e, (byte)0x00, (byte)0x03, (byte)0x00, (byte)0x09, (byte)0x68, (byte)0x74, (byte)0x74, (byte)0x70, (byte)0x3a, + (byte)0x2f, (byte)0x2f, (byte)0x76, (byte)0x2f, (byte)0x00, (byte)0x15, (byte)0x01, (byte)0x61, (byte)0x84, (byte)0x54, (byte)0x57, (byte)0xd9 + }; + + Metadata metadata = feedInputBuffer(data, 0, 0L); + assertThat(metadata.length()).isEqualTo(1); + assertThat(((Ait) metadata.get(0)).controlCode).isEqualTo(Ait.CONTROL_CODE_AUTOSTART); + assertThat(((Ait) metadata.get(0)).url).isEqualTo("http://v/a"); + } + + @Test + public void testArte() { + /* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */ + byte[] data = new byte[]{ + (byte)0x74, (byte)0xf1, (byte)0xd8, (byte)0x00, (byte)0x10, (byte)0xfd, (byte)0x00, (byte)0x00, (byte)0xf0, (byte)0x00, (byte)0xf1, (byte)0xcb, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x20, + (byte)0x00, (byte)0x71, (byte)0x01, (byte)0xf0, (byte)0x8f, (byte)0x02, (byte)0x29, (byte)0x00, (byte)0x03, (byte)0x00, (byte)0x24, (byte)0x68, (byte)0x74, (byte)0x74, (byte)0x70, (byte)0x3a, + (byte)0x2f, (byte)0x2f, (byte)0x73, (byte)0x74, (byte)0x61, (byte)0x74, (byte)0x69, (byte)0x63, (byte)0x2d, (byte)0x63, (byte)0x64, (byte)0x6e, (byte)0x2e, (byte)0x61, (byte)0x72, (byte)0x74, + (byte)0x65, (byte)0x2e, (byte)0x74, (byte)0x76, (byte)0x2f, (byte)0x72, (byte)0x65, (byte)0x64, (byte)0x62, (byte)0x75, (byte)0x74, (byte)0x74, (byte)0x6f, (byte)0x6e, (byte)0x2f, (byte)0x00, + (byte)0x02, (byte)0x05, (byte)0x00, (byte)0x01, (byte)0x01, (byte)0x7f, (byte)0xf1, (byte)0x01, (byte)0x0c, (byte)0x66, (byte)0x72, (byte)0x65, (byte)0x08, (byte)0x4c, (byte)0x61, (byte)0x75, + (byte)0x6e, (byte)0x63, (byte)0x68, (byte)0x65, (byte)0x72, (byte)0x16, (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x0a, (byte)0x05, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x01, (byte)0x01, + (byte)0xff, (byte)0x02, (byte)0x00, (byte)0x01, (byte)0x15, (byte)0x0d, (byte)0x69, (byte)0x6e, (byte)0x64, (byte)0x65, (byte)0x78, (byte)0x5f, (byte)0x66, (byte)0x72, (byte)0x2e, (byte)0x68, + (byte)0x74, (byte)0x6d, (byte)0x6c, (byte)0x17, (byte)0x19, (byte)0x01, (byte)0x17, (byte)0x68, (byte)0x74, (byte)0x74, (byte)0x70, (byte)0x3a, (byte)0x2f, (byte)0x2f, (byte)0x6c, (byte)0x6f, + (byte)0x67, (byte)0x69, (byte)0x31, (byte)0x30, (byte)0x34, (byte)0x2e, (byte)0x78, (byte)0x69, (byte)0x74, (byte)0x69, (byte)0x2e, (byte)0x63, (byte)0x6f, (byte)0x6d, (byte)0x17, (byte)0x14, + (byte)0x01, (byte)0x12, (byte)0x68, (byte)0x74, (byte)0x74, (byte)0x70, (byte)0x3a, (byte)0x2f, (byte)0x2f, (byte)0x77, (byte)0x77, (byte)0x77, (byte)0x2e, (byte)0x61, (byte)0x72, (byte)0x74, + (byte)0x65, (byte)0x2e, (byte)0x74, (byte)0x76, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x20, (byte)0x00, (byte)0x72, (byte)0x02, (byte)0xf1, (byte)0x2a, (byte)0x02, (byte)0x20, (byte)0x00, + (byte)0x03, (byte)0x02, (byte)0x1b, (byte)0x68, (byte)0x74, (byte)0x74, (byte)0x70, (byte)0x3a, (byte)0x2f, (byte)0x2f, (byte)0x77, (byte)0x77, (byte)0x77, (byte)0x2e, (byte)0x61, (byte)0x72, + (byte)0x74, (byte)0x65, (byte)0x2e, (byte)0x74, (byte)0x76, (byte)0x2f, (byte)0x68, (byte)0x62, (byte)0x62, (byte)0x74, (byte)0x76, (byte)0x76, (byte)0x32, (byte)0x2f, (byte)0x00, (byte)0x01, + (byte)0x09, (byte)0x66, (byte)0x72, (byte)0x65, (byte)0x05, (byte)0x61, (byte)0x72, (byte)0x74, (byte)0x65, (byte)0x37, (byte)0x00, (byte)0x09, (byte)0x05, (byte)0x00, (byte)0x00, (byte)0x01, + (byte)0x01, (byte)0x01, (byte)0xff, (byte)0x01, (byte)0x02, (byte)0x15, (byte)0x20, (byte)0x69, (byte)0x6e, (byte)0x64, (byte)0x65, (byte)0x78, (byte)0x2e, (byte)0x68, (byte)0x74, (byte)0x6d, + (byte)0x6c, (byte)0x3f, (byte)0x6c, (byte)0x61, (byte)0x6e, (byte)0x67, (byte)0x3d, (byte)0x66, (byte)0x72, (byte)0x5f, (byte)0x46, (byte)0x52, (byte)0x26, (byte)0x70, (byte)0x61, (byte)0x67, + (byte)0x65, (byte)0x3d, (byte)0x50, (byte)0x4c, (byte)0x55, (byte)0x53, (byte)0x37, (byte)0x17, (byte)0x19, (byte)0x01, (byte)0x17, (byte)0x68, (byte)0x74, (byte)0x74, (byte)0x70, (byte)0x3a, + (byte)0x2f, (byte)0x2f, (byte)0x6c, (byte)0x6f, (byte)0x67, (byte)0x63, (byte)0x31, (byte)0x33, (byte)0x36, (byte)0x2e, (byte)0x78, (byte)0x69, (byte)0x74, (byte)0x69, (byte)0x2e, (byte)0x63, + (byte)0x6f, (byte)0x6d, (byte)0x17, (byte)0x14, (byte)0x01, (byte)0x12, (byte)0x68, (byte)0x74, (byte)0x74, (byte)0x70, (byte)0x3a, (byte)0x2f, (byte)0x2f, (byte)0x73, (byte)0x74, (byte)0x2e, + (byte)0x61, (byte)0x72, (byte)0x74, (byte)0x65, (byte)0x2e, (byte)0x74, (byte)0x76, (byte)0x2f, (byte)0x17, (byte)0x23, (byte)0x01, (byte)0x21, (byte)0x68, (byte)0x74, (byte)0x74, (byte)0x70, + (byte)0x3a, (byte)0x2f, (byte)0x2f, (byte)0x61, (byte)0x72, (byte)0x74, (byte)0x65, (byte)0x70, (byte)0x74, (byte)0x77, (byte)0x65, (byte)0x62, (byte)0x2e, (byte)0x67, (byte)0x6c, (byte)0x2d, + (byte)0x73, (byte)0x79, (byte)0x73, (byte)0x74, (byte)0x65, (byte)0x6d, (byte)0x68, (byte)0x61, (byte)0x75, (byte)0x73, (byte)0x2e, (byte)0x64, (byte)0x65, (byte)0x17, (byte)0x1b, (byte)0x01, + (byte)0x19, (byte)0x68, (byte)0x74, (byte)0x74, (byte)0x70, (byte)0x3a, (byte)0x2f, (byte)0x2f, (byte)0x73, (byte)0x74, (byte)0x61, (byte)0x74, (byte)0x69, (byte)0x63, (byte)0x2d, (byte)0x63, + (byte)0x64, (byte)0x6e, (byte)0x2e, (byte)0x61, (byte)0x72, (byte)0x74, (byte)0x65, (byte)0x2e, (byte)0x74, (byte)0x76, (byte)0x17, (byte)0x1d, (byte)0x01, (byte)0x1b, (byte)0x68, (byte)0x74, + (byte)0x74, (byte)0x70, (byte)0x3a, (byte)0x2f, (byte)0x2f, (byte)0x64, (byte)0x6f, (byte)0x77, (byte)0x6e, (byte)0x6c, (byte)0x6f, (byte)0x61, (byte)0x64, (byte)0x2e, (byte)0x77, (byte)0x77, + (byte)0x77, (byte)0x2e, (byte)0x61, (byte)0x72, (byte)0x74, (byte)0x65, (byte)0x2e, (byte)0x74, (byte)0x76, (byte)0x17, (byte)0x23, (byte)0x01, (byte)0x21, (byte)0x68, (byte)0x74, (byte)0x74, + (byte)0x70, (byte)0x3a, (byte)0x2f, (byte)0x2f, (byte)0x6d, (byte)0x65, (byte)0x73, (byte)0x75, (byte)0x72, (byte)0x65, (byte)0x2e, (byte)0x73, (byte)0x74, (byte)0x72, (byte)0x65, (byte)0x61, + (byte)0x6d, (byte)0x69, (byte)0x6e, (byte)0x67, (byte)0x2e, (byte)0x65, (byte)0x73, (byte)0x74, (byte)0x61, (byte)0x74, (byte)0x2e, (byte)0x63, (byte)0x6f, (byte)0x6d, (byte)0x17, (byte)0x17, + (byte)0x01, (byte)0x15, (byte)0x68, (byte)0x74, (byte)0x74, (byte)0x70, (byte)0x3a, (byte)0x2f, (byte)0x2f, (byte)0x67, (byte)0x65, (byte)0x6f, (byte)0x6c, (byte)0x6f, (byte)0x63, (byte)0x2e, + (byte)0x61, (byte)0x72, (byte)0x74, (byte)0x65, (byte)0x2e, (byte)0x74, (byte)0x76, (byte)0x52, (byte)0x24, (byte)0xa0, (byte)0xcc + }; + + Metadata metadata = feedInputBuffer(data, 0, 0L); + assertThat(metadata.length()).isEqualTo(2); + assertThat(((Ait) metadata.get(0)).controlCode).isEqualTo(Ait.CONTROL_CODE_AUTOSTART); + assertThat(((Ait) metadata.get(0)).url).isEqualTo("http://static-cdn.arte.tv/redbutton/index_fr.html"); + assertThat(((Ait) metadata.get(1)).controlCode).isEqualTo(Ait.CONTROL_CODE_PRESENT); + assertThat(((Ait) metadata.get(1)).url).isEqualTo("http://www.arte.tv/hbbtvv2/index.html?lang=fr_FR&page=PLUS7"); + } + + private Metadata feedInputBuffer(byte[] data, long timeUs, long subsampleOffset) { + inputBuffer.clear(); + inputBuffer.data = ByteBuffer.allocate(data.length).put(data); + inputBuffer.timeUs = timeUs; + inputBuffer.subsampleOffsetUs = subsampleOffset; + return decoder.decode(inputBuffer); + } +} \ No newline at end of file