diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index d7123c8078..8a2f3196d0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -80,6 +80,12 @@ public final class C { /** The number of bits per byte. */ public static final int BITS_PER_BYTE = 8; + /** non-Wide aspect ratio */ + public static final int NON_WIDE_ASPECT_RATIO_TYPE = 1; + + /** Wide aspect ratio */ + public static final int WIDE_ASPECT_RATIO_TYPE = 2; + /** * The name of the ASCII charset. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Format.java b/library/core/src/main/java/com/google/android/exoplayer2/Format.java index 5b1c64c083..70b9fb3d80 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Format.java @@ -15,9 +15,11 @@ */ package com.google.android.exoplayer2; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.Nullable; + import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.util.MimeTypes; @@ -44,6 +46,7 @@ public final class Format implements Parcelable { */ public static final long OFFSET_SAMPLE_RELATIVE = Long.MAX_VALUE; + /** An identifier for the format, or null if unknown or not applicable. */ public final @Nullable String id; /** @@ -148,7 +151,16 @@ public final class Format implements Parcelable { */ public final int encoderPadding; + /** A Bundle of custom parameters. Could be null or empty */ + public final Bundle params; + // Audio and text specific. + /** + * Indicates if the video is wide aspect ratio (16:9) or not (4:3) + * Only influences captions if the lines are not middle aligned. + * Values are {@link C#NON_WIDE_ASPECT_RATIO} or {@link C#WIDE_ASPECT_RATIO}, + */ + public static final String KEY_ASPECT_RATIO_TYPE = "aspect-ratio-type"; /** * Track selection flags. @@ -183,7 +195,7 @@ public final class Format implements Parcelable { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, width, height, frameRate, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, - initializationData, null, null); + initializationData, null, null, null); } public static Format createVideoSampleFormat( @@ -238,7 +250,7 @@ public final class Format implements Parcelable { return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE, - OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, null); + OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, null, null); } // Audio. @@ -257,7 +269,7 @@ public final class Format implements Parcelable { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, channelCount, sampleRate, NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, - initializationData, null, null); + initializationData, null, null, null); } public static Format createAudioSampleFormat( @@ -313,7 +325,7 @@ public final class Format implements Parcelable { return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, - initializationData, drmInitData, metadata); + initializationData, drmInitData, metadata, null); } // Text. @@ -342,7 +354,7 @@ public final class Format implements Parcelable { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel, - OFFSET_SAMPLE_RELATIVE, null, null, null); + OFFSET_SAMPLE_RELATIVE, null, null, null, null); } public static Format createTextSampleFormat( @@ -389,6 +401,24 @@ public final class Format implements Parcelable { NO_VALUE, drmInitData, subsampleOffsetUs, Collections.emptyList()); } + public static Format createTextSampleFormat( + @Nullable String id, + @Nullable String sampleMimeType, + @Nullable String codecs, + int bitrate, + @C.SelectionFlags int selectionFlags, + String language, + int accessibilityChannel, + DrmInitData drmInitData, + Bundle params) { + return new Format(id, null, sampleMimeType, codecs, bitrate, + NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, + null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel, OFFSET_SAMPLE_RELATIVE, + null, drmInitData, null, params); + } + public static Format createTextSampleFormat( @Nullable String id, @Nullable String sampleMimeType, @@ -399,11 +429,12 @@ public final class Format implements Parcelable { int accessibilityChannel, @Nullable DrmInitData drmInitData, long subsampleOffsetUs, - List initializationData) { + List initializationData + ) { return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, - initializationData, drmInitData, null); + initializationData, drmInitData, null, null); } // Image. @@ -443,7 +474,7 @@ public final class Format implements Parcelable { OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, - null); + null, null); } // Generic. @@ -459,14 +490,14 @@ public final class Format implements Parcelable { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, null, - null); + null, null); } public static Format createSampleFormat( @Nullable String id, @Nullable String sampleMimeType, long subsampleOffsetUs) { return new Format(id, null, sampleMimeType, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, 0, null, NO_VALUE, subsampleOffsetUs, null, null, null); + NO_VALUE, 0, null, NO_VALUE, subsampleOffsetUs, null, null, null, null); } public static Format createSampleFormat( @@ -477,7 +508,7 @@ public final class Format implements Parcelable { @Nullable DrmInitData drmInitData) { return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, drmInitData, null); + NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, drmInitData, null, null); } /* package */ Format( @@ -506,7 +537,8 @@ public final class Format implements Parcelable { long subsampleOffsetUs, @Nullable List initializationData, @Nullable DrmInitData drmInitData, - @Nullable Metadata metadata) { + @Nullable Metadata metadata, + Bundle params) { this.id = id; this.containerMimeType = containerMimeType; this.sampleMimeType = sampleMimeType; @@ -535,6 +567,7 @@ public final class Format implements Parcelable { : initializationData; this.drmInitData = drmInitData; this.metadata = metadata; + this.params = params; } @SuppressWarnings("ResourceType") @@ -570,6 +603,7 @@ public final class Format implements Parcelable { } drmInitData = in.readParcelable(DrmInitData.class.getClassLoader()); metadata = in.readParcelable(Metadata.class.getClassLoader()); + params = in.readBundle(getClass().getClassLoader()); } public Format copyWithMaxInputSize(int maxInputSize) { @@ -577,7 +611,7 @@ public final class Format implements Parcelable { height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + drmInitData, metadata, params); } public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) { @@ -585,7 +619,7 @@ public final class Format implements Parcelable { height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + drmInitData, metadata, params); } public Format copyWithContainerInfo( @@ -601,7 +635,7 @@ public final class Format implements Parcelable { height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + drmInitData, metadata, params); } @SuppressWarnings("ReferenceEquality") @@ -622,7 +656,7 @@ public final class Format implements Parcelable { height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + drmInitData, metadata, params); } public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) { @@ -630,7 +664,7 @@ public final class Format implements Parcelable { height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + drmInitData, metadata, params); } public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) { @@ -638,7 +672,7 @@ public final class Format implements Parcelable { height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + drmInitData, metadata, params); } public Format copyWithMetadata(@Nullable Metadata metadata) { @@ -646,7 +680,7 @@ public final class Format implements Parcelable { height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + drmInitData, metadata, params); } public Format copyWithRotationDegrees(int rotationDegrees) { @@ -654,7 +688,7 @@ public final class Format implements Parcelable { height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, - drmInitData, metadata); + drmInitData, metadata, params); } /** @@ -825,6 +859,8 @@ public final class Format implements Parcelable { } dest.writeParcelable(drmInitData, 0); dest.writeParcelable(metadata, 0); + dest.writeBundle(params); + } public static final Creator CREATOR = new Creator() { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index f58ecd8da6..1f8f894020 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -15,8 +15,11 @@ */ package com.google.android.exoplayer2.extractor.ts; +import android.os.Bundle; import android.support.annotation.IntDef; import android.util.SparseArray; + +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import com.google.android.exoplayer2.util.MimeTypes; @@ -79,6 +82,13 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact */ public DefaultTsPayloadReaderFactory(@Flags int flags, List closedCaptionFormats) { this.flags = flags; + if (!isSet(FLAG_OVERRIDE_CAPTION_DESCRIPTORS) && closedCaptionFormats.isEmpty()) { + closedCaptionFormats = new ArrayList(); + closedCaptionFormats.add(Format.createTextSampleFormat(null, + MimeTypes.APPLICATION_CEA608, 0, null)); + closedCaptionFormats.add(Format.createTextSampleFormat(null, + MimeTypes.APPLICATION_CEA708, 0, null)); + } this.closedCaptionFormats = closedCaptionFormats; } @@ -106,7 +116,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact case TsExtractor.TS_STREAM_TYPE_HDMV_DTS: return new PesReader(new DtsReader(esInfo.language)); case TsExtractor.TS_STREAM_TYPE_H262: - return new PesReader(new H262Reader()); + return new PesReader(new H262Reader(buildUserDataReader(esInfo))); case TsExtractor.TS_STREAM_TYPE_H264: return isSet(FLAG_IGNORE_H264_STREAM) ? null : new PesReader(new H264Reader(buildSeiReader(esInfo), @@ -136,8 +146,34 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact * @return A {@link SeiReader} for closed caption tracks. */ private SeiReader buildSeiReader(EsInfo esInfo) { + return new SeiReader(getCCformats(esInfo)); + } + + /** + * If {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS} is set, returns a {@link UserDataReader} for + * {@link #closedCaptionFormats}. If unset, parses the PMT descriptor information and returns a + * {@link UserDataReader} 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 UserDataReader} for closed caption tracks. + */ + private UserDataReader buildUserDataReader(EsInfo esInfo) { + return new UserDataReader(getCCformats(esInfo)); + } + + /** + * If {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS} is set, returns a {@link List} of + * {@link #closedCaptionFormats}. If unset, parses the PMT descriptor information and returns a + * {@link List} 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 List} containing list of closed caption formats. + */ + private List getCCformats(EsInfo esInfo) { if (isSet(FLAG_OVERRIDE_CAPTION_DESCRIPTORS)) { - return new SeiReader(closedCaptionFormats); + return closedCaptionFormats; } ParsableByteArray scratchDescriptorData = new ParsableByteArray(esInfo.descriptorBytes); List closedCaptionFormats = this.closedCaptionFormats; @@ -162,17 +198,24 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact mimeType = MimeTypes.APPLICATION_CEA608; accessibilityChannel = 1; } - closedCaptionFormats.add(Format.createTextSampleFormat(null, mimeType, null, - Format.NO_VALUE, 0, language, accessibilityChannel, null)); // Skip easy_reader(1), wide_aspect_ratio(1), reserved(14). - scratchDescriptorData.skipBytes(2); + byte misc = (byte)scratchDescriptorData.readUnsignedByte(); + boolean isWideAspectRatio = ((misc & 0x60) == 0x60); + Bundle params = new Bundle(); + params.putInt(Format.KEY_ASPECT_RATIO_TYPE, + isWideAspectRatio ? C.WIDE_ASPECT_RATIO_TYPE: C.NON_WIDE_ASPECT_RATIO_TYPE); + closedCaptionFormats.add(Format.createTextSampleFormat(null, mimeType, null, + Format.NO_VALUE, 0, language, accessibilityChannel, null, + params)); + scratchDescriptorData.skipBytes(1); } } else { // Unknown descriptor. Ignore. } scratchDescriptorData.setPosition(nextDescriptorPosition); } - return new SeiReader(closedCaptionFormats); + + return closedCaptionFormats; } private boolean isSet(@Flags int flag) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java index a3502a3242..5d9c407d6b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java @@ -36,6 +36,7 @@ public final class H262Reader implements ElementaryStreamReader { private static final int START_SEQUENCE_HEADER = 0xB3; private static final int START_EXTENSION = 0xB5; private static final int START_GROUP = 0xB8; + private static final int START_USER_DATA = 0xB2; private String formatId; private TrackOutput output; @@ -62,16 +63,30 @@ public final class H262Reader implements ElementaryStreamReader { private long sampleTimeUs; private boolean sampleIsKeyframe; private boolean sampleHasPicture; - + private NalUnitTargetBuffer userData = null; + private UserDataReader userDataReader = null; + // Scratch variables to avoid allocations. + private ParsableByteArray userDataParsable = null; public H262Reader() { + this(null); + } + public H262Reader(UserDataReader userDataReader) { + this.userDataReader = userDataReader; prefixFlags = new boolean[4]; csdBuffer = new CsdBuffer(128); + if (userDataReader != null) { + userData = new NalUnitTargetBuffer(START_USER_DATA, 128); + userDataParsable = new ParsableByteArray(); + } } @Override public void seek() { NalUnitUtil.clearPrefixFlags(prefixFlags); csdBuffer.reset(); + if (userData != null) { + userData.reset(); + } totalBytesWritten = 0; startedFirstSample = false; } @@ -81,6 +96,9 @@ public final class H262Reader implements ElementaryStreamReader { idGenerator.generateNewId(); formatId = idGenerator.getFormatId(); output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_VIDEO); + if (userDataReader != null) { + userDataReader.createTracks(extractorOutput, idGenerator); + } } @Override @@ -106,6 +124,9 @@ public final class H262Reader implements ElementaryStreamReader { if (!hasOutputFormat) { csdBuffer.onData(dataArray, offset, limit); } + if (userData != null) { + userData.appendToNalUnit(dataArray, offset, limit); + } return; } @@ -130,7 +151,25 @@ public final class H262Reader implements ElementaryStreamReader { hasOutputFormat = true; } } + if (userDataReader != null && userData != null) { + int lengthToStartCode = startCodeOffset - offset; + int bytesAlreadyPassed = 0; + if (lengthToStartCode > 0) { + userData.appendToNalUnit(dataArray, offset, startCodeOffset); + } else { + bytesAlreadyPassed = -lengthToStartCode; + } + if (userData.endNalUnit(bytesAlreadyPassed)) { + int unescapedLength = NalUnitUtil.unescapeStream(userData.nalData, userData.nalLength); + userDataParsable.reset(userData.nalData, unescapedLength); + userDataReader.consume(sampleTimeUs, userDataParsable); + } + + if (startCodeValue == START_USER_DATA && data.data[startCodeOffset + 2] == 0x1) { + userData.startNalUnit(startCodeValue); + } + } if (startCodeValue == START_PICTURE || startCodeValue == START_SEQUENCE_HEADER) { int bytesWrittenPastStartCode = limit - startCodeOffset; if (startedFirstSample && sampleHasPicture && hasOutputFormat) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/UserDataReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/UserDataReader.java new file mode 100644 index 0000000000..f21da7a998 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/UserDataReader.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2017 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.extractor.ts; + +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.util.Assertions; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.ParsableByteArray; + +import java.util.List; + +/** + * Consumes user data structure, outputting contained CEA-608/708 messages to a {@link TrackOutput}. + */ +/* package */ final class UserDataReader { + private final List closedCaptionFormats; + private final TrackOutput[] outputs; + private final int USER_DATA_START_CODE = 0x0001B2; + private final int USER_DATA_IDENTIFIER_GA94 = 0x47413934; + private final int USER_DATA_TYPE_CODE_MPEG_CC = 0x03; + public UserDataReader(List closedCaptionFormats) { + this.closedCaptionFormats = closedCaptionFormats; + outputs = new TrackOutput[closedCaptionFormats.size()]; + } + + public void createTracks(ExtractorOutput extractorOutput, + TsPayloadReader.TrackIdGenerator idGenerator) { + for (int i = 0; i < outputs.length; i++) { + idGenerator.generateNewId(); + TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); + Format channelFormat = closedCaptionFormats.get(i); + String channelMimeType = channelFormat.sampleMimeType; + Assertions.checkArgument(MimeTypes.APPLICATION_CEA608.equals(channelMimeType) + || MimeTypes.APPLICATION_CEA708.equals(channelMimeType), + "Invalid closed caption mime type provided: " + channelMimeType); + output.format(Format.createTextSampleFormat(idGenerator.getFormatId(), channelMimeType, null, + Format.NO_VALUE, channelFormat.selectionFlags, channelFormat.language, + channelFormat.accessibilityChannel, null, channelFormat.params)); + outputs[i] = output; + } + } + + public void consume(long pesTimeUs, ParsableByteArray userDataPayload) { + if (userDataPayload.bytesLeft() < 9) { + return; + } + //check if payload is used_data_type (0x01B2) + int userDataStartCode = userDataPayload.readInt(); + int userDataIdentifier = userDataPayload.readInt(); + int userDataTypeCode = userDataPayload.readUnsignedByte(); + + if (userDataStartCode == USER_DATA_START_CODE && userDataIdentifier == USER_DATA_IDENTIFIER_GA94 + && userDataTypeCode == USER_DATA_TYPE_CODE_MPEG_CC) { + if (userDataPayload.bytesLeft() < 2) { + return; + } + // read cc_count and process_cc_data_flag byte. + int ccByte = userDataPayload.readUnsignedByte(); + boolean processCCDataFlag = ((ccByte & 0x40) != 0); + int ccCount = (ccByte & 0x1F); + // skip reserved em_data byte of MPEG_CC structure + userDataPayload.skipBytes(1); + int payLoadSize = ccCount * 3; + if (processCCDataFlag && payLoadSize != 0) { + int ccPos = userDataPayload.getPosition(); + for (TrackOutput output : outputs) { + output.sampleData(userDataPayload, payLoadSize); + output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, payLoadSize, 0, null); + userDataPayload.setPosition(ccPos); + } + + } + } + } + +} diff --git a/library/core/src/test/assets/ts/sample.ts.0.dump b/library/core/src/test/assets/ts/sample.ts.0.dump index e42761ac7b..d7b17eff6a 100644 --- a/library/core/src/test/assets/ts/sample.ts.0.dump +++ b/library/core/src/test/assets/ts/sample.ts.0.dump @@ -2,7 +2,7 @@ seekMap: isSeekable = false duration = 66733 getPosition(0) = [[timeUs=0, position=0]] -numberOfTracks = 2 +numberOfTracks = 3 track 256: format: bitrate = -1 @@ -76,4 +76,28 @@ track 257: time = 100822 flags = 1 data = length 1254, hash 73FB07B8 +track 8448: + format: + bitrate = -1 + id = 1/8448 + containerMimeType = null + sampleMimeType = application/cea-608 + 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 = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + total output bytes = 0 + sample count = 0 tracksEnded = true diff --git a/library/core/src/test/assets/ts/sample.ts.unklen.dump b/library/core/src/test/assets/ts/sample.ts.unklen.dump index a74268a702..56f6b01a9c 100644 --- a/library/core/src/test/assets/ts/sample.ts.unklen.dump +++ b/library/core/src/test/assets/ts/sample.ts.unklen.dump @@ -2,7 +2,7 @@ seekMap: isSeekable = false duration = UNSET TIME getPosition(0) = [[timeUs=0, position=0]] -numberOfTracks = 2 +numberOfTracks = 3 track 256: format: bitrate = -1 @@ -76,4 +76,28 @@ track 257: time = 100822 flags = 1 data = length 1254, hash 73FB07B8 +track 8448: + format: + bitrate = -1 + id = 1/8448 + containerMimeType = null + sampleMimeType = application/cea-608 + 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 = 9223372036854775807 + selectionFlags = 0 + language = null + drmInitData = - + initializationData: + total output bytes = 0 + sample count = 0 tracksEnded = true diff --git a/library/core/src/test/java/com/google/android/exoplayer2/FormatTest.java b/library/core/src/test/java/com/google/android/exoplayer2/FormatTest.java index 7ca2181ebf..afb3a30c5f 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/FormatTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/FormatTest.java @@ -20,7 +20,9 @@ import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_MP4; import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_WEBM; import static com.google.common.truth.Truth.assertThat; +import android.os.Bundle; import android.os.Parcel; + import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; @@ -63,11 +65,12 @@ public final class FormatTest { new TextInformationFrame("id2", "description2", "value2")); ColorInfo colorInfo = new ColorInfo(C.COLOR_SPACE_BT709, C.COLOR_RANGE_LIMITED, C.COLOR_TRANSFER_SDR, new byte[] {1, 2, 3, 4, 5, 6, 7}); - + Bundle params = new Bundle(); + params.putInt(Format.KEY_ASPECT_RATIO_TYPE, 100); Format formatToParcel = new Format("id", MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_H264, null, 1024, 2048, 1920, 1080, 24, 90, 2, projectionData, C.STEREO_MODE_TOP_BOTTOM, colorInfo, 6, 44100, C.ENCODING_PCM_24BIT, 1001, 1002, 0, "und", Format.NO_VALUE, - Format.OFFSET_SAMPLE_RELATIVE, INIT_DATA, drmInitData, metadata); + Format.OFFSET_SAMPLE_RELATIVE, INIT_DATA, drmInitData, metadata, params); Parcel parcel = Parcel.obtain(); formatToParcel.writeToParcel(parcel, 0); @@ -75,6 +78,8 @@ public final class FormatTest { Format formatFromParcel = Format.CREATOR.createFromParcel(parcel); assertThat(formatFromParcel).isEqualTo(formatToParcel); + int aspectRatio = formatFromParcel.params.getInt(Format.KEY_ASPECT_RATIO_TYPE); + assertThat(aspectRatio).isEqualTo(100); parcel.recycle(); } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTrackNameProvider.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTrackNameProvider.java index b36941e999..79b1ace14f 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTrackNameProvider.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DefaultTrackNameProvider.java @@ -49,7 +49,7 @@ public class DefaultTrackNameProvider implements TrackNameProvider { } else { trackName = buildLanguageString(format); } - return trackName.length() == 0 ? resources.getString(R.string.exo_track_unknown) : trackName; + return trackName.length() == 0 ? resources.getString(R.string.exo_track_unknown) : trackName + " - " + format.id; } private String buildResolutionString(Format format) {