Merge pull request #6922 from phhusson:feature/ait

PiperOrigin-RevId: 297579733
This commit is contained in:
kim-vde 2020-02-27 17:15:52 +00:00
commit 6946170d3e
23 changed files with 895 additions and 16 deletions

View File

@ -82,6 +82,89 @@
[GL demo app](https://github.com/google/ExoPlayer/tree/dev-v2/demos/gl) to
show how to render video to a `GLSurfaceView` while applying a GL shader.
([#6920](https://github.com/google/ExoPlayer/issues/6920)).
* Core library:
* Add API in `AnalyticsListener` to report video frame processing offset.
`MediaCodecVideoRenderer` reports the event.
* Add fields `videoFrameProcessingOffsetUsSum` and
`videoFrameProcessingOffsetUsCount` in `DecoderCounters` to compute the
average video frame processing offset.
* Add playlist API
([#6161](https://github.com/google/ExoPlayer/issues/6161)).
* Add `play` and `pause` methods to `Player`.
* Add `Player.getCurrentLiveOffset` to conveniently return the live
offset.
* Add `Player.onPlayWhenReadyChanged` with reasons.
* Add `Player.onPlaybackStateChanged` and deprecate
`Player.onPlayerStateChanged`.
* Deprecate and rename `getPlaybackError` to `getPlayerError` for
consistency.
* Deprecate and rename `onLoadingChanged` to `onIsLoadingChanged` for
consistency.
* Make `MediaSourceEventListener.LoadEventInfo` and
`MediaSourceEventListener.MediaLoadData` top-level classes.
* Rename `MediaCodecRenderer.onOutputFormatChanged` to
`MediaCodecRenderer.onOutputMediaFormatChanged`, further clarifying the
distinction between `Format` and `MediaFormat`.
* Move player message-related constants from `C` to `Renderer`, to avoid
having the constants class depend on player/renderer classes.
* Split out `common` and `extractor` submodules.
* Allow to explicitly send `PlayerMessage`s at the end of a stream.
* Add `DataSpec.Builder` and deprecate most `DataSpec` constructors.
* Add `DataSpec.customData` to allow applications to pass custom data
through `DataSource` chains.
* Add a sample count parameter to `MediaCodecRenderer.processOutputBuffer`
and `AudioSink.handleBuffer` to allow batching multiple encoded frames
in one buffer.
* Add a `Format.Builder` and deprecate all `Format.create*` methods and
most `Format.copyWith*` methods.
* Split `Format.bitrate` into `Format.averageBitrate` and
`Format.peakBitrate`
([#2863](https://github.com/google/ExoPlayer/issues/2863)).
* Text:
* Parse `<ruby>` and `<rt>` tags in WebVTT subtitles (rendering is coming
later).
* Parse `text-combine-upright` CSS property (i.e. tate-chu-yoko) in WebVTT
subtitles (rendering is coming later).
* Parse `tts:combineText` property (i.e. tate-chu-yoko) in TTML subtitles
(rendering is coming later).
* Fix `SubtitlePainter` to render `EDGE_TYPE_OUTLINE` using the correct
color ([#6724](https://github.com/google/ExoPlayer/pull/6724)).
* Add support for WebVTT default
[text](https://www.w3.org/TR/webvtt1/#default-text-color) and
[background](https://www.w3.org/TR/webvtt1/#default-text-background)
colors ([PR #4178](https://github.com/google/ExoPlayer/pull/4178),
[issue #6581](https://github.com/google/ExoPlayer/issues/6581)).
* Catch-and-log all fatal exceptions in `TextRenderer` instead of
re-throwing, allowing playback to continue even if subtitles fail
([#6885](https://github.com/google/ExoPlayer/issues/6885)).
* Parse `tts:ruby` and `tts:rubyPosition` properties in TTML subtitles
(rendering is coming later).
* DRM:
* Add support for attaching DRM sessions to clear content in the demo app.
* Remove `DrmSessionManager` references from all renderers.
`DrmSessionManager` must be injected into the MediaSources using the
MediaSources factories.
* Downloads: Merge downloads in `SegmentDownloader` to improve overall
download speed ([#5978](https://github.com/google/ExoPlayer/issues/5978)).
* MP3: Add `IndexSeeker` for accurate seeks in VBR streams
([#6787](https://github.com/google/ExoPlayer/issues/6787)). This seeker is
enabled by passing `FLAG_ENABLE_INDEX_SEEKING` to the `Mp3Extractor`. It may
require to scan a significant portion of the file for seeking, which may be
costly on large files.
* MP4: Store the Android capture frame rate only in `Format.metadata`.
`Format.frameRate` now stores the calculated frame rate.
* Testing
* Upgrade Truth dependency from 0.44 to 1.0.
* Upgrade to JUnit 4.13-rc-2.
* UI
* Move logic of prev, next, fast forward and rewind to ControlDispatcher
([#6926](https://github.com/google/ExoPlayer/issues/6926)).
* Demo apps: Add
[GL demo app](https://github.com/google/ExoPlayer/tree/dev-v2/demos/gl) to
show how to render video to a `GLSurfaceView` while applying a GL shader.
([#6920](https://github.com/google/ExoPlayer/issues/6920)).
* Metadata: Add minimal DVB Application Information Table (AIT) support
([#6922](https://github.com/google/ExoPlayer/pull/6922)).
### 2.11.3 (2020-02-19) ###

View File

@ -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 + "/vnd.dvb.ait";
private static final ArrayList<CustomMimeType> customMimeTypes = new ArrayList<>();

View File

@ -15,6 +15,9 @@
*/
package com.google.android.exoplayer2.util;
import com.google.android.exoplayer2.C;
import java.nio.charset.Charset;
/**
* Wraps a byte array, providing methods that allow it to be read as a bitstream.
*/
@ -277,6 +280,31 @@ public final class ParsableBitArray {
assertValidOffset();
}
/**
* Reads the next {@code length} bytes as a UTF-8 string. Must only be called when the position is
* byte aligned.
*
* @param length The number of bytes to read.
* @return The string encoded by the bytes in UTF-8.
*/
public String readBytesAsString(int length) {
return readBytesAsString(length, Charset.forName(C.UTF8_NAME));
}
/**
* Reads the next {@code length} bytes as a string encoded in {@link Charset}. Must only be called
* when the position is byte aligned.
*
* @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 readBytesAsString(int length, Charset charset) {
byte[] bytes = new byte[length];
readBytes(bytes, 0, length);
return new String(bytes, charset);
}
/**
* Overwrites {@code numBits} from this array using the {@code numBits} least significant bits
* from {@code value}. Bits are written in order from most significant to least significant. The

View File

@ -16,9 +16,12 @@
package com.google.android.exoplayer2.util;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.TestUtil;
import java.nio.charset.Charset;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -276,6 +279,35 @@ public final class ParsableBitArrayTest {
assertThat(testArray.readBits(8)).isEqualTo(0x5F);
}
@Test
public void testReadBytesAsStringDefaultsToUtf8() {
byte[] testData = "a non-åscii strìng".getBytes(Charset.forName(C.UTF8_NAME));
ParsableBitArray testArray = new ParsableBitArray(testData);
testArray.skipBytes(2);
assertThat(testArray.readBytesAsString(testData.length - 2)).isEqualTo("non-åscii strìng");
}
@Test
public void testReadBytesAsStringExplicitCharset() {
byte[] testData = "a non-åscii strìng".getBytes(Charset.forName(C.UTF16_NAME));
ParsableBitArray testArray = new ParsableBitArray(testData);
testArray.skipBytes(6);
assertThat(testArray.readBytesAsString(testData.length - 6, Charset.forName(C.UTF16_NAME)))
.isEqualTo("non-åscii strìng");
}
@Test
public void testReadBytesNotByteAligned() {
String testString = "test string";
byte[] testData = testString.getBytes(Charset.forName(C.UTF8_NAME));
ParsableBitArray testArray = new ParsableBitArray(testData);
testArray.skipBit();
assertThrows(IllegalStateException.class, () -> testArray.readBytesAsString(2));
}
@Test
public void testPutBitsWithinByte() {
ParsableBitArray output = new ParsableBitArray(new byte[4]);

View File

@ -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.AppInfoTableDecoder;
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 AppInfoTableDecoder();
default:
break;
}

View File

@ -0,0 +1,80 @@
/*
* Copyright (C) 2020 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.dvbsi;
import android.os.Parcel;
import android.os.Parcelable;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.Assertions;
/**
* A representation of a DVB Application Information Table (AIT).
*
* <p>For more info on the AIT see section 5.3.4 of the <a
* href="https://www.etsi.org/deliver/etsi_ts/102800_102899/102809/01.01.01_60/ts_102809v010101p.pdf">
* DVB ETSI TS 102 809 v1.1.1 spec</a>.
*/
public final class AppInfoTable 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;
public AppInfoTable(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<AppInfoTable> CREATOR =
new Parcelable.Creator<AppInfoTable>() {
@Override
public AppInfoTable createFromParcel(Parcel in) {
String url = Assertions.checkNotNull(in.readString());
int controlCode = in.readInt();
return new AppInfoTable(controlCode, url);
}
@Override
public AppInfoTable[] newArray(int size) {
return new AppInfoTable[size];
}
};
}

View File

@ -0,0 +1,140 @@
/*
* Copyright (C) 2020 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.dvbsi;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
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.Assertions;
import com.google.android.exoplayer2.util.ParsableBitArray;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
/**
* Decoder for the DVB Application Information Table (AIT).
*
* <p>For more info on the AIT see section 5.3.4 of the <a
* href="https://www.etsi.org/deliver/etsi_ts/102800_102899/102809/01.01.01_60/ts_102809v010101p.pdf">
* DVB ETSI TS 102 809 v1.1.1 spec</a>.
*/
public final class AppInfoTableDecoder implements MetadataDecoder {
/** See section 5.3.6. */
private static final int DESCRIPTOR_TRANSPORT_PROTOCOL = 0x02;
/** See section 5.3.7. */
private static final int DESCRIPTOR_SIMPLE_APPLICATION_LOCATION = 0x15;
/** See table 29 in section 5.3.6. */
private static final int TRANSPORT_PROTOCOL_HTTP = 3;
/** See table 16 in section 5.3.4.6. */
public static final int APPLICATION_INFORMATION_TABLE_ID = 0x74;
@Override
@Nullable
@SuppressWarnings("ByteBufferBackingArray")
public Metadata decode(MetadataInputBuffer inputBuffer) {
ByteBuffer buffer = Assertions.checkNotNull(inputBuffer.data);
int tableId = buffer.get();
return tableId == APPLICATION_INFORMATION_TABLE_ID
? parseAit(new ParsableBitArray(buffer.array(), buffer.limit()))
: null;
}
@Nullable
private static Metadata parseAit(ParsableBitArray sectionData) {
// tableId, section_syntax_indication, reserved_future_use, reserved
sectionData.skipBits(12);
int sectionLength = sectionData.readBits(12);
int endOfSection = sectionData.getBytePosition() + sectionLength - 4 /* Ignore leading CRC */;
// test_application_flag, application_type, reserved, version_number, current_next_indicator,
// section_number, last_section_number, reserved_future_use
sectionData.skipBits(44);
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);
// reserved_future_use, application_loop_length
sectionData.skipBits(16);
ArrayList<AppInfoTable> appInfoTables = new ArrayList<>();
while (sectionData.getBytePosition() < endOfSection) {
@Nullable String urlBase = null;
@Nullable String urlExtension = null;
// application_identifier
sectionData.skipBits(48);
int controlCode = sectionData.readBits(8);
// reserved_future_use
sectionData.skipBits(4);
int applicationDescriptorsLoopLength = sectionData.readBits(12);
int positionOfNextApplication =
sectionData.getBytePosition() + applicationDescriptorsLoopLength;
while (sectionData.getBytePosition() < positionOfNextApplication) {
int descriptorTag = sectionData.readBits(8);
int descriptorLength = sectionData.readBits(8);
int positionOfNextDescriptor = sectionData.getBytePosition() + descriptorLength;
if (descriptorTag == DESCRIPTOR_TRANSPORT_PROTOCOL) {
// See section 5.3.6.
int protocolId = sectionData.readBits(16);
// label
sectionData.skipBits(8);
if (protocolId == TRANSPORT_PROTOCOL_HTTP) {
// See section 5.3.6.2.
while (sectionData.getBytePosition() < positionOfNextDescriptor) {
int urlBaseLength = sectionData.readBits(8);
urlBase = sectionData.readBytesAsString(urlBaseLength, Charset.forName(C.ASCII_NAME));
int extensionCount = sectionData.readBits(8);
for (int urlExtensionIndex = 0;
urlExtensionIndex < extensionCount;
urlExtensionIndex++) {
int urlExtensionLength = sectionData.readBits(8);
sectionData.skipBytes(urlExtensionLength);
}
}
}
} else if (descriptorTag == DESCRIPTOR_SIMPLE_APPLICATION_LOCATION) {
// See section 5.3.7.
urlExtension =
sectionData.readBytesAsString(descriptorLength, Charset.forName(C.ASCII_NAME));
}
sectionData.setPosition(positionOfNextDescriptor * 8);
}
sectionData.setPosition(positionOfNextApplication * 8);
if (urlBase != null && urlExtension != null) {
appInfoTables.add(new AppInfoTable(controlCode, urlBase + urlExtension));
}
}
return appInfoTables.isEmpty() ? null : new Metadata(appInfoTables);
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (C) 2020 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.
*
*/
@NonNullApi
package com.google.android.exoplayer2.metadata.dvbsi;
import com.google.android.exoplayer2.util.NonNullApi;

View File

@ -0,0 +1,83 @@
/*
* Copyright (C) 2020 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.dvbsi;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link AppInfoTableDecoder}. */
@RunWith(AndroidJUnit4.class)
public final class AppInfoTableDecoderTest {
private static final String TYPICAL_FILE = "dvbsi/ait_typical.bin";
private static final String NO_URL_BASE_FILE = "dvbsi/ait_no_url_base.bin";
private static final String NO_URL_PATH_FILE = "dvbsi/ait_no_url_path.bin";
@Test
public void decode_typical() throws Exception {
AppInfoTableDecoder decoder = new AppInfoTableDecoder();
Metadata metadata = decoder.decode(createMetadataInputBuffer(readTestFile(TYPICAL_FILE)));
assertThat(metadata.length()).isEqualTo(2);
Metadata.Entry firstEntry = metadata.get(0);
assertThat(firstEntry).isInstanceOf(AppInfoTable.class);
assertThat(((AppInfoTable) firstEntry).controlCode)
.isEqualTo(AppInfoTable.CONTROL_CODE_AUTOSTART);
assertThat(((AppInfoTable) firstEntry).url).isEqualTo("http://example.com/path/foo");
Metadata.Entry secondEntry = metadata.get(1);
assertThat(secondEntry).isInstanceOf(AppInfoTable.class);
assertThat(((AppInfoTable) secondEntry).controlCode)
.isEqualTo(AppInfoTable.CONTROL_CODE_PRESENT);
assertThat(((AppInfoTable) secondEntry).url).isEqualTo("http://google.com/path/bar");
}
@Test
public void decode_noUrlBase() throws Exception {
AppInfoTableDecoder decoder = new AppInfoTableDecoder();
Metadata metadata = decoder.decode(createMetadataInputBuffer(readTestFile(NO_URL_BASE_FILE)));
assertThat(metadata).isNull();
}
@Test
public void decode_noUrlPath() throws Exception {
AppInfoTableDecoder decoder = new AppInfoTableDecoder();
Metadata metadata = decoder.decode(createMetadataInputBuffer(readTestFile(NO_URL_PATH_FILE)));
assertThat(metadata).isNull();
}
private static MetadataInputBuffer createMetadataInputBuffer(byte[] data) {
MetadataInputBuffer inputBuffer = new MetadataInputBuffer();
inputBuffer.data = ByteBuffer.allocate(data.length);
inputBuffer.data.put(data);
inputBuffer.data.flip();
return inputBuffer;
}
private static byte[] readTestFile(String name) throws IOException {
return TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), name);
}
}

View File

@ -80,7 +80,10 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
* delimiters (AUDs).
*/
public static final int FLAG_DETECT_ACCESS_UNITS = 1 << 3;
/** Prevents the creation of {@link SpliceInfoSectionReader} instances. */
/**
* Prevents the creation of {@link SectionPayloadReader}s for splice information sections
* (SCTE-35).
*/
public static final int FLAG_IGNORE_SPLICE_INFO_STREAM = 1 << 4;
/**
* Whether the list of {@code closedCaptionFormats} passed to {@link
@ -170,22 +173,25 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
return new PesReader(new H265Reader(buildSeiReader(esInfo)));
case TsExtractor.TS_STREAM_TYPE_SPLICE_INFO:
return isSet(FLAG_IGNORE_SPLICE_INFO_STREAM)
? null : new SectionReader(new SpliceInfoSectionReader());
? null
: new SectionReader(new PassthroughSectionPayloadReader(MimeTypes.APPLICATION_SCTE35));
case TsExtractor.TS_STREAM_TYPE_ID3:
return new PesReader(new Id3Reader());
case TsExtractor.TS_STREAM_TYPE_DVBSUBS:
return new PesReader(
new DvbSubtitleReader(esInfo.dvbSubtitleInfos));
case TsExtractor.TS_STREAM_TYPE_AIT:
return new SectionReader(new PassthroughSectionPayloadReader(MimeTypes.APPLICATION_AIT));
default:
return 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
* {@link SeiReader} for the declared formats, or {@link #closedCaptionFormats} if the descriptor
* is not present.
* 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 {@link
* SeiReader} for the declared formats, or {@link #closedCaptionFormats} if the descriptor is not
* present.
*
* @param esInfo The {@link EsInfo} passed to {@link #createPayloadReader(int, EsInfo)}.
* @return A {@link SeiReader} for closed caption tracks.

View File

@ -20,7 +20,6 @@ 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.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.Util;
@ -28,16 +27,30 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Parses splice info sections as defined by SCTE35.
* A {@link SectionPayloadReader} that directly outputs the section bytes as sample data.
*
* <p>Timestamp adjustment is provided through {@link Format#subsampleOffsetUs}.
*/
public final class SpliceInfoSectionReader implements SectionPayloadReader {
public final class PassthroughSectionPayloadReader implements SectionPayloadReader {
private final String mimeType;
private @MonotonicNonNull TimestampAdjuster timestampAdjuster;
private @MonotonicNonNull TrackOutput output;
private boolean formatDeclared;
/**
* Create a new PassthroughSectionPayloadReader.
*
* @param mimeType The MIME type set as {@link Format#sampleMimeType} on the created output track.
*/
public PassthroughSectionPayloadReader(String mimeType) {
this.mimeType = mimeType;
}
@Override
public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput,
public void init(
TimestampAdjuster timestampAdjuster,
ExtractorOutput extractorOutput,
TsPayloadReader.TrackIdGenerator idGenerator) {
this.timestampAdjuster = timestampAdjuster;
idGenerator.generateNewId();
@ -53,14 +66,20 @@ public final class SpliceInfoSectionReader implements SectionPayloadReader {
return;
}
output.format(
Format.createSampleFormat(null, MimeTypes.APPLICATION_SCTE35)
.copyWithSubsampleOffsetUs(timestampAdjuster.getTimestampOffsetUs()));
new Format.Builder()
.setSampleMimeType(mimeType)
.setSubsampleOffsetUs(timestampAdjuster.getTimestampOffsetUs())
.build());
formatDeclared = true;
}
int sampleSize = sectionData.bytesLeft();
output.sampleData(sectionData, sampleSize);
output.sampleMetadata(timestampAdjuster.getLastAdjustedTimestampUs(), C.BUFFER_FLAG_KEY_FRAME,
sampleSize, 0, null);
output.sampleMetadata(
timestampAdjuster.getLastAdjustedTimestampUs(),
C.BUFFER_FLAG_KEY_FRAME,
sampleSize,
0,
null);
}
@EnsuresNonNull({"timestampAdjuster", "output"})

View File

@ -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;
// Stream types that aren't defined by the MPEG-2 TS specification.
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());

View File

@ -53,6 +53,11 @@ public final class TsExtractorTest {
ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_scte35.ts");
}
@Test
public void testAit() throws Exception {
ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_ait.ts");
}
@Test
public void testStreamWithJunkData() throws Exception {
ExtractorAsserts.assertBehavior(

View File

@ -0,0 +1,15 @@
# DVB Test Data
The `.bin` files in this directory are generated from the `.xml` files using
`tstabcomp` from [TSDuck](https://tsduck.io/).
The XML files are kept to make it clear where the values in the test assertions
are coming from, and to make it easier to change or add data in future. When
adding new files, or making changes to existing ones, you should regenerate the
`.bin` files using the command above before committing.
To regenerate all the `.bin` files:
```shell
$ tstabcomp -c testdata/src/test/assets/dvbsi/*.xml
```

Binary file not shown.

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<tsduck>
<AIT version="30" current="true" test_application_flag="false" application_type="0x0010">
<application control_code="0x01">
<application_identifier organization_id="0x00000120" application_id="0x0071"/>
<transport_protocol_descriptor transport_protocol_label="0x00">
<http>
</http>
</transport_protocol_descriptor>
<simple_application_location_descriptor initial_path="foo/bar"/>
</application>
</AIT>
</tsduck>

Binary file not shown.

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (C) 2020 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.
~
-->
<tsduck>
<AIT version="30" current="true" test_application_flag="false" application_type="0x0010">
<application control_code="0x01">
<application_identifier organization_id="0x00000120" application_id="0x0071"/>
<transport_protocol_descriptor transport_protocol_label="0x00">
<http>
<url base="http://google.com/"/>
</http>
</transport_protocol_descriptor>
</application>
</AIT>
</tsduck>

Binary file not shown.

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<tsduck>
<AIT version="30" current="true" test_application_flag="false" application_type="0x0010">
<application control_code="0x01">
<application_identifier organization_id="0x00000120" application_id="0x0071"/>
<transport_protocol_descriptor transport_protocol_label="0x00">
<http>
<url base="http://example.com/"/>
</http>
</transport_protocol_descriptor>
<simple_application_location_descriptor initial_path="path/foo"/>
</application>
<application control_code="0x02">
<application_identifier organization_id="0x00000120" application_id="0x0072"/>
<transport_protocol_descriptor transport_protocol_label="0x00">
<http>
<url base="http://google.com/"/>
</http>
</transport_protocol_descriptor>
<simple_application_location_descriptor initial_path="path/bar"/>
</application>
</AIT>
</tsduck>

Binary file not shown.

View File

@ -0,0 +1,146 @@
seekMap:
isSeekable = false
duration = UNSET TIME
getPosition(0) = [[timeUs=0, position=0]]
numberOfTracks = 2
track 330:
total output bytes = 9928
sample count = 19
format 0:
averageBitrate = -1
peakBitrate = -1
id = 1031/330
containerMimeType = null
sampleMimeType = audio/eac3
maxInputSize = -1
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = 2
sampleRate = 48000
pcmEncoding = -1
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = fr
drmInitData = -
metadata = null
initializationData:
sample 0:
time = 0
flags = 1
data = length 512, hash E47547D4
sample 1:
time = 32000
flags = 1
data = length 512, hash F6A537AC
sample 2:
time = 64000
flags = 1
data = length 512, hash 97391682
sample 3:
time = 96000
flags = 1
data = length 512, hash CFD3B665
sample 4:
time = 128000
flags = 1
data = length 512, hash 2E79A3AF
sample 5:
time = 160000
flags = 1
data = length 512, hash 2C24E2A3
sample 6:
time = 192000
flags = 1
data = length 512, hash 5BCB9661
sample 7:
time = 224000
flags = 1
data = length 512, hash 943ACBF2
sample 8:
time = 256000
flags = 1
data = length 512, hash B248E943
sample 9:
time = 288000
flags = 1
data = length 512, hash EC2DD86F
sample 10:
time = 320000
flags = 1
data = length 512, hash A659332F
sample 11:
time = 352000
flags = 1
data = length 512, hash CB641607
sample 12:
time = 384000
flags = 1
data = length 512, hash 157489A0
sample 13:
time = 416000
flags = 1
data = length 512, hash A37CB66E
sample 14:
time = 448000
flags = 1
data = length 512, hash 932F07D4
sample 15:
time = 480000
flags = 1
data = length 512, hash 91F50161
sample 16:
time = 512000
flags = 1
data = length 512, hash 7F9D6CCB
sample 17:
time = 544000
flags = 1
data = length 512, hash 3955F015
sample 18:
time = 576000
flags = 1
data = length 512, hash A8E5C938
track 370:
total output bytes = 1413
sample count = 3
format 0:
averageBitrate = -1
peakBitrate = -1
id = null
containerMimeType = null
sampleMimeType = application/vnd.dvb.ait
maxInputSize = -1
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = -1
sampleRate = -1
pcmEncoding = -1
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = -43622564033
selectionFlags = 0
language = null
drmInitData = -
metadata = null
initializationData:
sample 0:
time = 0
flags = 1
data = length 471, hash B189052F
sample 1:
time = 192000
flags = 1
data = length 471, hash B189052F
sample 2:
time = 384000
flags = 1
data = length 471, hash B189052F
tracksEnded = true

View File

@ -0,0 +1,146 @@
seekMap:
isSeekable = false
duration = UNSET TIME
getPosition(0) = [[timeUs=0, position=0]]
numberOfTracks = 2
track 330:
total output bytes = 9928
sample count = 19
format 0:
averageBitrate = -1
peakBitrate = -1
id = 1031/330
containerMimeType = null
sampleMimeType = audio/eac3
maxInputSize = -1
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = 2
sampleRate = 48000
pcmEncoding = -1
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = fr
drmInitData = -
metadata = null
initializationData:
sample 0:
time = 0
flags = 1
data = length 512, hash E47547D4
sample 1:
time = 32000
flags = 1
data = length 512, hash F6A537AC
sample 2:
time = 64000
flags = 1
data = length 512, hash 97391682
sample 3:
time = 96000
flags = 1
data = length 512, hash CFD3B665
sample 4:
time = 128000
flags = 1
data = length 512, hash 2E79A3AF
sample 5:
time = 160000
flags = 1
data = length 512, hash 2C24E2A3
sample 6:
time = 192000
flags = 1
data = length 512, hash 5BCB9661
sample 7:
time = 224000
flags = 1
data = length 512, hash 943ACBF2
sample 8:
time = 256000
flags = 1
data = length 512, hash B248E943
sample 9:
time = 288000
flags = 1
data = length 512, hash EC2DD86F
sample 10:
time = 320000
flags = 1
data = length 512, hash A659332F
sample 11:
time = 352000
flags = 1
data = length 512, hash CB641607
sample 12:
time = 384000
flags = 1
data = length 512, hash 157489A0
sample 13:
time = 416000
flags = 1
data = length 512, hash A37CB66E
sample 14:
time = 448000
flags = 1
data = length 512, hash 932F07D4
sample 15:
time = 480000
flags = 1
data = length 512, hash 91F50161
sample 16:
time = 512000
flags = 1
data = length 512, hash 7F9D6CCB
sample 17:
time = 544000
flags = 1
data = length 512, hash 3955F015
sample 18:
time = 576000
flags = 1
data = length 512, hash A8E5C938
track 370:
total output bytes = 1413
sample count = 3
format 0:
averageBitrate = -1
peakBitrate = -1
id = null
containerMimeType = null
sampleMimeType = application/vnd.dvb.ait
maxInputSize = -1
width = -1
height = -1
frameRate = -1.0
rotationDegrees = 0
pixelWidthHeightRatio = 1.0
channelCount = -1
sampleRate = -1
pcmEncoding = -1
encoderDelay = 0
encoderPadding = 0
subsampleOffsetUs = -43622564033
selectionFlags = 0
language = null
drmInitData = -
metadata = null
initializationData:
sample 0:
time = 0
flags = 1
data = length 471, hash B189052F
sample 1:
time = 192000
flags = 1
data = length 471, hash B189052F
sample 2:
time = 384000
flags = 1
data = length 471, hash B189052F
tracksEnded = true