Commit 1d636520 by tonihei Committed by Oliver Woodman

Auto-register AnalyticsCollector as bandwidth meter listener.

This allows to automatically forward bandwidth estimate events to
AnalyticsListeners.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=205642752
parent 3c4b0aa1
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
* Add `AudioListener` for listening to changes in audio configuration during * Add `AudioListener` for listening to changes in audio configuration during
playback ([#3994](https://github.com/google/ExoPlayer/issues/3994)). playback ([#3994](https://github.com/google/ExoPlayer/issues/3994)).
* MPEG-TS: Support CEA-608/708 in H262
([#2565](https://github.com/google/ExoPlayer/issues/2565)).
* MPEG-PS: Support reading duration from MPEG-PS Streams * MPEG-PS: Support reading duration from MPEG-PS Streams
([#4476](https://github.com/google/ExoPlayer/issues/4476)). ([#4476](https://github.com/google/ExoPlayer/issues/4476)).
* MediaSession extension: * MediaSession extension:
......
...@@ -80,12 +80,6 @@ public final class C { ...@@ -80,12 +80,6 @@ public final class C {
/** The number of bits per byte. */ /** The number of bits per byte. */
public static final int BITS_PER_BYTE = 8; 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. * The name of the ASCII charset.
*/ */
......
...@@ -80,6 +80,7 @@ public class SimpleExoPlayer ...@@ -80,6 +80,7 @@ public class SimpleExoPlayer
private final CopyOnWriteArraySet<MetadataOutput> metadataOutputs; private final CopyOnWriteArraySet<MetadataOutput> metadataOutputs;
private final CopyOnWriteArraySet<VideoRendererEventListener> videoDebugListeners; private final CopyOnWriteArraySet<VideoRendererEventListener> videoDebugListeners;
private final CopyOnWriteArraySet<AudioRendererEventListener> audioDebugListeners; private final CopyOnWriteArraySet<AudioRendererEventListener> audioDebugListeners;
private final BandwidthMeter bandwidthMeter;
private final AnalyticsCollector analyticsCollector; private final AnalyticsCollector analyticsCollector;
private Format videoFormat; private Format videoFormat;
...@@ -181,6 +182,7 @@ public class SimpleExoPlayer ...@@ -181,6 +182,7 @@ public class SimpleExoPlayer
AnalyticsCollector.Factory analyticsCollectorFactory, AnalyticsCollector.Factory analyticsCollectorFactory,
Clock clock, Clock clock,
Looper looper) { Looper looper) {
this.bandwidthMeter = bandwidthMeter;
componentListener = new ComponentListener(); componentListener = new ComponentListener();
videoListeners = new CopyOnWriteArraySet<>(); videoListeners = new CopyOnWriteArraySet<>();
audioListeners = new CopyOnWriteArraySet<>(); audioListeners = new CopyOnWriteArraySet<>();
...@@ -215,6 +217,7 @@ public class SimpleExoPlayer ...@@ -215,6 +217,7 @@ public class SimpleExoPlayer
audioDebugListeners.add(analyticsCollector); audioDebugListeners.add(analyticsCollector);
audioListeners.add(analyticsCollector); audioListeners.add(analyticsCollector);
addMetadataOutput(analyticsCollector); addMetadataOutput(analyticsCollector);
bandwidthMeter.addEventListener(eventHandler, analyticsCollector);
if (drmSessionManager instanceof DefaultDrmSessionManager) { if (drmSessionManager instanceof DefaultDrmSessionManager) {
((DefaultDrmSessionManager) drmSessionManager).addListener(eventHandler, analyticsCollector); ((DefaultDrmSessionManager) drmSessionManager).addListener(eventHandler, analyticsCollector);
} }
...@@ -854,6 +857,7 @@ public class SimpleExoPlayer ...@@ -854,6 +857,7 @@ public class SimpleExoPlayer
if (mediaSource != null) { if (mediaSource != null) {
mediaSource.removeEventListener(analyticsCollector); mediaSource.removeEventListener(analyticsCollector);
} }
bandwidthMeter.removeEventListener(analyticsCollector);
currentCues = Collections.emptyList(); currentCues = Collections.emptyList();
} }
......
...@@ -681,7 +681,7 @@ import java.util.List; ...@@ -681,7 +681,7 @@ import java.util.List;
parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE);
// Default values. // Default values.
List<byte[]> initializationData = null; List<byte[]> initializationData = Collections.emptyList();
long subsampleOffsetUs = Format.OFFSET_SAMPLE_RELATIVE; long subsampleOffsetUs = Format.OFFSET_SAMPLE_RELATIVE;
String mimeType; String mimeType;
...@@ -707,8 +707,18 @@ import java.util.List; ...@@ -707,8 +707,18 @@ import java.util.List;
throw new IllegalStateException(); throw new IllegalStateException();
} }
out.format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, out.format =
Format.NO_VALUE, 0, language, Format.NO_VALUE, null, subsampleOffsetUs, initializationData); Format.createTextSampleFormat(
Integer.toString(trackId),
mimeType,
/* codecs= */ null,
/* bitrate= */ Format.NO_VALUE,
/* selectionFlags= */ 0,
language,
/* accessibilityChannel= */ Format.NO_VALUE,
/* drmInitData= */ null,
subsampleOffsetUs,
initializationData);
} }
private static void parseVideoSampleEntry(ParsableByteArray parent, int atomType, int position, private static void parseVideoSampleEntry(ParsableByteArray parent, int atomType, int position,
......
...@@ -15,13 +15,11 @@ ...@@ -15,13 +15,11 @@
*/ */
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import android.os.Bundle;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.util.SparseArray; import android.util.SparseArray;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo;
import com.google.android.exoplayer2.text.cea.Cea708InitializationData;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
...@@ -82,13 +80,6 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact ...@@ -82,13 +80,6 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
*/ */
public DefaultTsPayloadReaderFactory(@Flags int flags, List<Format> closedCaptionFormats) { public DefaultTsPayloadReaderFactory(@Flags int flags, List<Format> closedCaptionFormats) {
this.flags = flags; 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; this.closedCaptionFormats = closedCaptionFormats;
} }
...@@ -146,32 +137,32 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact ...@@ -146,32 +137,32 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
* @return A {@link SeiReader} for closed caption tracks. * @return A {@link SeiReader} for closed caption tracks.
*/ */
private SeiReader buildSeiReader(EsInfo esInfo) { private SeiReader buildSeiReader(EsInfo esInfo) {
return new SeiReader(getCCformats(esInfo)); return new SeiReader(getClosedCaptionFormats(esInfo));
} }
/** /**
* If {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS} is set, returns a {@link UserDataReader} for * 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 #closedCaptionFormats}. If unset, parses the PMT descriptor information and returns a
* {@link UserDataReader} for the declared formats, or {@link #closedCaptionFormats} if the descriptor * {@link UserDataReader} for the declared formats, or {@link #closedCaptionFormats} if the
* is not present. * descriptor is not present.
* *
* @param esInfo The {@link EsInfo} passed to {@link #createPayloadReader(int, EsInfo)}. * @param esInfo The {@link EsInfo} passed to {@link #createPayloadReader(int, EsInfo)}.
* @return A {@link UserDataReader} for closed caption tracks. * @return A {@link UserDataReader} for closed caption tracks.
*/ */
private UserDataReader buildUserDataReader(EsInfo esInfo) { private UserDataReader buildUserDataReader(EsInfo esInfo) {
return new UserDataReader(getCCformats(esInfo)); return new UserDataReader(getClosedCaptionFormats(esInfo));
} }
/** /**
* If {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS} is set, returns a {@link List<Format>} of * If {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS} is set, returns a {@link List<Format>} of {@link
* {@link #closedCaptionFormats}. If unset, parses the PMT descriptor information and returns a * #closedCaptionFormats}. If unset, parses the PMT descriptor information and returns a {@link
* {@link List<Format>} for the declared formats, or {@link #closedCaptionFormats} if the descriptor * List<Format>} for the declared formats, or {@link #closedCaptionFormats} if the descriptor is
* is not present. * not present.
* *
* @param esInfo The {@link EsInfo} passed to {@link #createPayloadReader(int, EsInfo)}. * @param esInfo The {@link EsInfo} passed to {@link #createPayloadReader(int, EsInfo)}.
* @return A {@link List<Format>} containing list of closed caption formats. * @return A {@link List<Format>} containing list of closed caption formats.
*/ */
private List<Format> getCCformats(EsInfo esInfo) { private List<Format> getClosedCaptionFormats(EsInfo esInfo) {
if (isSet(FLAG_OVERRIDE_CAPTION_DESCRIPTORS)) { if (isSet(FLAG_OVERRIDE_CAPTION_DESCRIPTORS)) {
return closedCaptionFormats; return closedCaptionFormats;
} }
...@@ -198,16 +189,33 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact ...@@ -198,16 +189,33 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
mimeType = MimeTypes.APPLICATION_CEA608; mimeType = MimeTypes.APPLICATION_CEA608;
accessibilityChannel = 1; accessibilityChannel = 1;
} }
// Skip easy_reader(1), wide_aspect_ratio(1), reserved(14).
byte misc = (byte)scratchDescriptorData.readUnsignedByte(); // easy_reader(1), wide_aspect_ratio(1), reserved(6).
boolean isWideAspectRatio = ((misc & 0x60) == 0x60); byte flags = (byte) scratchDescriptorData.readUnsignedByte();
Bundle params = new Bundle(); // Skip reserved (8).
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); scratchDescriptorData.skipBytes(1);
List<byte[]> initializationData;
// The wide_aspect_ratio flag only has meaning for CEA-708.
if (isDigital) {
boolean isWideAspectRatio = (flags & 0x40) != 0;
initializationData = Cea708InitializationData.buildData(isWideAspectRatio);
} else {
initializationData = Collections.emptyList();
}
closedCaptionFormats.add(
Format.createTextSampleFormat(
/* id= */ null,
mimeType,
/* codecs= */ null,
/* bitrate= */ Format.NO_VALUE,
/* selectionFlags= */ 0,
language,
accessibilityChannel,
/* drmInitData= */ null,
Format.OFFSET_SAMPLE_RELATIVE,
initializationData));
} }
} else { } else {
// Unknown descriptor. Ignore. // Unknown descriptor. Ignore.
...@@ -221,5 +229,4 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact ...@@ -221,5 +229,4 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
private boolean isSet(@Flags int flag) { private boolean isSet(@Flags int flag) {
return (flags & flag) != 0; return (flags & flag) != 0;
} }
} }
...@@ -49,9 +49,13 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -49,9 +49,13 @@ public final class H262Reader implements ElementaryStreamReader {
private boolean hasOutputFormat; private boolean hasOutputFormat;
private long frameDurationUs; private long frameDurationUs;
private final UserDataReader userDataReader;
private final ParsableByteArray userDataParsable;
// State that should be reset on seek. // State that should be reset on seek.
private final boolean[] prefixFlags; private final boolean[] prefixFlags;
private final CsdBuffer csdBuffer; private final CsdBuffer csdBuffer;
private final NalUnitTargetBuffer userData;
private long totalBytesWritten; private long totalBytesWritten;
private boolean startedFirstSample; private boolean startedFirstSample;
...@@ -63,13 +67,11 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -63,13 +67,11 @@ public final class H262Reader implements ElementaryStreamReader {
private long sampleTimeUs; private long sampleTimeUs;
private boolean sampleIsKeyframe; private boolean sampleIsKeyframe;
private boolean sampleHasPicture; private boolean sampleHasPicture;
private NalUnitTargetBuffer userData = null;
private UserDataReader userDataReader = null;
// Scratch variables to avoid allocations.
private ParsableByteArray userDataParsable = null;
public H262Reader() { public H262Reader() {
this(null); this(null);
} }
public H262Reader(UserDataReader userDataReader) { public H262Reader(UserDataReader userDataReader) {
this.userDataReader = userDataReader; this.userDataReader = userDataReader;
prefixFlags = new boolean[4]; prefixFlags = new boolean[4];
...@@ -77,6 +79,9 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -77,6 +79,9 @@ public final class H262Reader implements ElementaryStreamReader {
if (userDataReader != null) { if (userDataReader != null) {
userData = new NalUnitTargetBuffer(START_USER_DATA, 128); userData = new NalUnitTargetBuffer(START_USER_DATA, 128);
userDataParsable = new ParsableByteArray(); userDataParsable = new ParsableByteArray();
} else {
userData = null;
userDataParsable = null;
} }
} }
...@@ -84,7 +89,7 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -84,7 +89,7 @@ public final class H262Reader implements ElementaryStreamReader {
public void seek() { public void seek() {
NalUnitUtil.clearPrefixFlags(prefixFlags); NalUnitUtil.clearPrefixFlags(prefixFlags);
csdBuffer.reset(); csdBuffer.reset();
if (userData != null) { if (userDataReader != null) {
userData.reset(); userData.reset();
} }
totalBytesWritten = 0; totalBytesWritten = 0;
...@@ -124,7 +129,7 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -124,7 +129,7 @@ public final class H262Reader implements ElementaryStreamReader {
if (!hasOutputFormat) { if (!hasOutputFormat) {
csdBuffer.onData(dataArray, offset, limit); csdBuffer.onData(dataArray, offset, limit);
} }
if (userData != null) { if (userDataReader != null) {
userData.appendToNalUnit(dataArray, offset, limit); userData.appendToNalUnit(dataArray, offset, limit);
} }
return; return;
...@@ -132,11 +137,11 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -132,11 +137,11 @@ public final class H262Reader implements ElementaryStreamReader {
// We've found a start code with the following value. // We've found a start code with the following value.
int startCodeValue = data.data[startCodeOffset + 3] & 0xFF; int startCodeValue = data.data[startCodeOffset + 3] & 0xFF;
// This is the number of bytes from the current offset to the start of the next start
// code. It may be negative if the start code started in the previously consumed data.
int lengthToStartCode = startCodeOffset - offset;
if (!hasOutputFormat) { if (!hasOutputFormat) {
// This is the number of bytes from the current offset to the start of the next start
// code. It may be negative if the start code started in the previously consumed data.
int lengthToStartCode = startCodeOffset - offset;
if (lengthToStartCode > 0) { if (lengthToStartCode > 0) {
csdBuffer.onData(dataArray, offset, startCodeOffset); csdBuffer.onData(dataArray, offset, startCodeOffset);
} }
...@@ -151,8 +156,7 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -151,8 +156,7 @@ public final class H262Reader implements ElementaryStreamReader {
hasOutputFormat = true; hasOutputFormat = true;
} }
} }
if (userDataReader != null && userData != null) { if (userDataReader != null) {
int lengthToStartCode = startCodeOffset - offset;
int bytesAlreadyPassed = 0; int bytesAlreadyPassed = 0;
if (lengthToStartCode > 0) { if (lengthToStartCode > 0) {
userData.appendToNalUnit(dataArray, offset, startCodeOffset); userData.appendToNalUnit(dataArray, offset, startCodeOffset);
......
...@@ -52,9 +52,18 @@ import java.util.List; ...@@ -52,9 +52,18 @@ import java.util.List;
|| MimeTypes.APPLICATION_CEA708.equals(channelMimeType), || MimeTypes.APPLICATION_CEA708.equals(channelMimeType),
"Invalid closed caption mime type provided: " + channelMimeType); "Invalid closed caption mime type provided: " + channelMimeType);
String formatId = channelFormat.id != null ? channelFormat.id : idGenerator.getFormatId(); String formatId = channelFormat.id != null ? channelFormat.id : idGenerator.getFormatId();
output.format(Format.createTextSampleFormat(formatId, channelMimeType, null, Format.NO_VALUE, output.format(
channelFormat.selectionFlags, channelFormat.language, channelFormat.accessibilityChannel, Format.createTextSampleFormat(
null)); formatId,
channelMimeType,
/* codecs= */ null,
/* bitrate= */ Format.NO_VALUE,
channelFormat.selectionFlags,
channelFormat.language,
channelFormat.accessibilityChannel,
/* drmInitData= */ null,
Format.OFFSET_SAMPLE_RELATIVE,
channelFormat.initializationData));
outputs[i] = output; outputs[i] = output;
} }
} }
......
/* /*
* Copyright (C) 2017 The Android Open Source Project * Copyright (C) 2018 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -19,39 +19,48 @@ import com.google.android.exoplayer2.C; ...@@ -19,39 +19,48 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.text.cea.CeaUtil;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.List; import java.util.List;
/** /** Consumes user data, outputting contained CEA-608/708 messages to a {@link TrackOutput}. */
* Consumes user data structure, outputting contained CEA-608/708 messages to a {@link TrackOutput}.
*/
/* package */ final class UserDataReader { /* package */ final class UserDataReader {
private static final int USER_DATA_START_CODE = 0x0001B2;
private final List<Format> closedCaptionFormats; private final List<Format> closedCaptionFormats;
private final TrackOutput[] outputs; 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<Format> closedCaptionFormats) { public UserDataReader(List<Format> closedCaptionFormats) {
this.closedCaptionFormats = closedCaptionFormats; this.closedCaptionFormats = closedCaptionFormats;
outputs = new TrackOutput[closedCaptionFormats.size()]; outputs = new TrackOutput[closedCaptionFormats.size()];
} }
public void createTracks(ExtractorOutput extractorOutput, public void createTracks(
TsPayloadReader.TrackIdGenerator idGenerator) { ExtractorOutput extractorOutput, TsPayloadReader.TrackIdGenerator idGenerator) {
for (int i = 0; i < outputs.length; i++) { for (int i = 0; i < outputs.length; i++) {
idGenerator.generateNewId(); idGenerator.generateNewId();
TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
Format channelFormat = closedCaptionFormats.get(i); Format channelFormat = closedCaptionFormats.get(i);
String channelMimeType = channelFormat.sampleMimeType; String channelMimeType = channelFormat.sampleMimeType;
Assertions.checkArgument(MimeTypes.APPLICATION_CEA608.equals(channelMimeType) Assertions.checkArgument(
|| MimeTypes.APPLICATION_CEA708.equals(channelMimeType), MimeTypes.APPLICATION_CEA608.equals(channelMimeType)
"Invalid closed caption mime type provided: " + channelMimeType); || MimeTypes.APPLICATION_CEA708.equals(channelMimeType),
output.format(Format.createTextSampleFormat(idGenerator.getFormatId(), channelMimeType, null, "Invalid closed caption mime type provided: " + channelMimeType);
Format.NO_VALUE, channelFormat.selectionFlags, channelFormat.language, output.format(
channelFormat.accessibilityChannel, null, channelFormat.params)); Format.createTextSampleFormat(
idGenerator.getFormatId(),
channelMimeType,
/* codecs= */ null,
/* bitrate= */ Format.NO_VALUE,
channelFormat.selectionFlags,
channelFormat.language,
channelFormat.accessibilityChannel,
/* drmInitData= */ null,
Format.OFFSET_SAMPLE_RELATIVE,
channelFormat.initializationData));
outputs[i] = output; outputs[i] = output;
} }
} }
...@@ -60,33 +69,13 @@ import java.util.List; ...@@ -60,33 +69,13 @@ import java.util.List;
if (userDataPayload.bytesLeft() < 9) { if (userDataPayload.bytesLeft() < 9) {
return; return;
} }
//check if payload is used_data_type (0x01B2)
int userDataStartCode = userDataPayload.readInt(); int userDataStartCode = userDataPayload.readInt();
int userDataIdentifier = userDataPayload.readInt(); int userDataIdentifier = userDataPayload.readInt();
int userDataTypeCode = userDataPayload.readUnsignedByte(); int userDataTypeCode = userDataPayload.readUnsignedByte();
if (userDataStartCode == USER_DATA_START_CODE
if (userDataStartCode == USER_DATA_START_CODE && userDataIdentifier == USER_DATA_IDENTIFIER_GA94 && userDataIdentifier == CeaUtil.USER_DATA_IDENTIFIER_GA94
&& userDataTypeCode == USER_DATA_TYPE_CODE_MPEG_CC) { && userDataTypeCode == CeaUtil.USER_DATA_TYPE_CODE_MPEG_CC) {
if (userDataPayload.bytesLeft() < 2) { CeaUtil.consumeCcData(pesTimeUs, userDataPayload, outputs);
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);
}
}
} }
} }
} }
...@@ -107,7 +107,7 @@ public interface SubtitleDecoderFactory { ...@@ -107,7 +107,7 @@ public interface SubtitleDecoderFactory {
case MimeTypes.APPLICATION_MP4CEA608: case MimeTypes.APPLICATION_MP4CEA608:
return new Cea608Decoder(format.sampleMimeType, format.accessibilityChannel); return new Cea608Decoder(format.sampleMimeType, format.accessibilityChannel);
case MimeTypes.APPLICATION_CEA708: case MimeTypes.APPLICATION_CEA708:
return new Cea708Decoder(format.accessibilityChannel); return new Cea708Decoder(format.accessibilityChannel, format.initializationData);
case MimeTypes.APPLICATION_DVBSUBS: case MimeTypes.APPLICATION_DVBSUBS:
return new DvbDecoder(format.initializationData); return new DvbDecoder(format.initializationData);
case MimeTypes.APPLICATION_PGS: case MimeTypes.APPLICATION_PGS:
......
...@@ -152,10 +152,10 @@ public final class Cea708Decoder extends CeaDecoder { ...@@ -152,10 +152,10 @@ public final class Cea708Decoder extends CeaDecoder {
private DtvCcPacket currentDtvCcPacket; private DtvCcPacket currentDtvCcPacket;
private int currentWindow; private int currentWindow;
public Cea708Decoder(int accessibilityChannel) { public Cea708Decoder(int accessibilityChannel, List<byte[]> initializationData) {
ccData = new ParsableByteArray(); ccData = new ParsableByteArray();
serviceBlockPacket = new ParsableBitArray(); serviceBlockPacket = new ParsableBitArray();
selectedServiceNumber = (accessibilityChannel == Format.NO_VALUE) ? 1 : accessibilityChannel; selectedServiceNumber = accessibilityChannel == Format.NO_VALUE ? 1 : accessibilityChannel;
cueBuilders = new CueBuilder[NUM_WINDOWS]; cueBuilders = new CueBuilder[NUM_WINDOWS];
for (int i = 0; i < NUM_WINDOWS; i++) { for (int i = 0; i < NUM_WINDOWS; i++) {
......
...@@ -26,13 +26,13 @@ public final class CeaUtil { ...@@ -26,13 +26,13 @@ public final class CeaUtil {
private static final String TAG = "CeaUtil"; private static final String TAG = "CeaUtil";
public static final int USER_DATA_IDENTIFIER_GA94 = Util.getIntegerCodeForString("GA94");
public static final int USER_DATA_TYPE_CODE_MPEG_CC = 0x3;
private static final int PAYLOAD_TYPE_CC = 4; private static final int PAYLOAD_TYPE_CC = 4;
private static final int COUNTRY_CODE = 0xB5; private static final int COUNTRY_CODE = 0xB5;
private static final int PROVIDER_CODE_ATSC = 0x31; private static final int PROVIDER_CODE_ATSC = 0x31;
private static final int PROVIDER_CODE_DIRECTV = 0x2F; private static final int PROVIDER_CODE_DIRECTV = 0x2F;
private static final int USER_ID_GA94 = Util.getIntegerCodeForString("GA94");
private static final int USER_ID_DTG1 = Util.getIntegerCodeForString("DTG1");
private static final int USER_DATA_TYPE_CODE = 0x3;
/** /**
* Consumes the unescaped content of an SEI NAL unit, writing the content of any CEA-608 messages * Consumes the unescaped content of an SEI NAL unit, writing the content of any CEA-608 messages
...@@ -67,26 +67,12 @@ public final class CeaUtil { ...@@ -67,26 +67,12 @@ public final class CeaUtil {
boolean messageIsSupportedCeaCaption = boolean messageIsSupportedCeaCaption =
countryCode == COUNTRY_CODE countryCode == COUNTRY_CODE
&& (providerCode == PROVIDER_CODE_ATSC || providerCode == PROVIDER_CODE_DIRECTV) && (providerCode == PROVIDER_CODE_ATSC || providerCode == PROVIDER_CODE_DIRECTV)
&& userDataTypeCode == USER_DATA_TYPE_CODE; && userDataTypeCode == USER_DATA_TYPE_CODE_MPEG_CC;
if (providerCode == PROVIDER_CODE_ATSC) { if (providerCode == PROVIDER_CODE_ATSC) {
messageIsSupportedCeaCaption &= messageIsSupportedCeaCaption &= userIdentifier == USER_DATA_IDENTIFIER_GA94;
userIdentifier == USER_ID_GA94 || userIdentifier == USER_ID_DTG1;
} }
if (messageIsSupportedCeaCaption) { if (messageIsSupportedCeaCaption) {
// Ignore first three bits: reserved (1) + process_cc_data_flag (1) + zero_bit (1). consumeCcData(presentationTimeUs, seiBuffer, outputs);
int ccCount = seiBuffer.readUnsignedByte() & 0x1F;
// Ignore em_data (1)
seiBuffer.skipBytes(1);
// Each data packet consists of 24 bits: marker bits (5) + cc_valid (1) + cc_type (2)
// + cc_data_1 (8) + cc_data_2 (8).
int sampleLength = ccCount * 3;
int sampleStartPosition = seiBuffer.getPosition();
for (TrackOutput output : outputs) {
seiBuffer.setPosition(sampleStartPosition);
output.sampleData(seiBuffer, sampleLength);
output.sampleMetadata(
presentationTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleLength, 0, null);
}
} }
} }
seiBuffer.setPosition(nextPayloadPosition); seiBuffer.setPosition(nextPayloadPosition);
...@@ -94,6 +80,40 @@ public final class CeaUtil { ...@@ -94,6 +80,40 @@ public final class CeaUtil {
} }
/** /**
* Consumes caption data (cc_data), writing the content as samples to all of the provided outputs.
*
* @param presentationTimeUs The presentation time in microseconds for any samples.
* @param ccDataBuffer The buffer containing the caption data.
* @param outputs The outputs to which any samples should be written.
*/
public static void consumeCcData(
long presentationTimeUs, ParsableByteArray ccDataBuffer, TrackOutput[] outputs) {
// First byte contains: reserved (1), process_cc_data_flag (1), zero_bit (1), cc_count (5).
int firstByte = ccDataBuffer.readUnsignedByte();
boolean processCcDataFlag = (firstByte & 0x40) != 0;
if (!processCcDataFlag) {
// No need to process.
return;
}
int ccCount = firstByte & 0x1F;
ccDataBuffer.skipBytes(1); // Ignore em_data
// Each data packet consists of 24 bits: marker bits (5) + cc_valid (1) + cc_type (2)
// + cc_data_1 (8) + cc_data_2 (8).
int sampleLength = ccCount * 3;
int sampleStartPosition = ccDataBuffer.getPosition();
for (TrackOutput output : outputs) {
ccDataBuffer.setPosition(sampleStartPosition);
output.sampleData(ccDataBuffer, sampleLength);
output.sampleMetadata(
presentationTimeUs,
C.BUFFER_FLAG_KEY_FRAME,
sampleLength,
/* offset= */ 0,
/* encryptionData= */ null);
}
}
/**
* Reads a value from the provided buffer consisting of zero or more 0xFF bytes followed by a * Reads a value from the provided buffer consisting of zero or more 0xFF bytes followed by a
* terminating byte not equal to 0xFF. The returned value is ((0xFF * N) + T), where N is the * terminating byte not equal to 0xFF. The returned value is ((0xFF * N) + T), where N is the
* number of 0xFF bytes and T is the value of the terminating byte. * number of 0xFF bytes and T is the value of the terminating byte.
......
...@@ -85,9 +85,9 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { ...@@ -85,9 +85,9 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|| lastPathSegment.startsWith(MP4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 5)) { || lastPathSegment.startsWith(MP4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 5)) {
extractor = extractor =
new FragmentedMp4Extractor( new FragmentedMp4Extractor(
0, /* flags= */ 0,
timestampAdjuster, timestampAdjuster,
null, /* sideloadedTrack= */ null,
drmInitData, drmInitData,
muxedCaptionFormats != null ? muxedCaptionFormats : Collections.emptyList()); muxedCaptionFormats != null ? muxedCaptionFormats : Collections.emptyList());
} else { } else {
...@@ -102,7 +102,11 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory { ...@@ -102,7 +102,11 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
// closed caption track on channel 0. // closed caption track on channel 0.
muxedCaptionFormats = muxedCaptionFormats =
Collections.singletonList( Collections.singletonList(
Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, 0, null)); Format.createTextSampleFormat(
/* id= */ null,
MimeTypes.APPLICATION_CEA608,
/* selectionFlags= */ 0,
/* language= */ null));
} }
String codecs = format.codecs; String codecs = format.codecs;
if (!TextUtils.isEmpty(codecs)) { if (!TextUtils.isEmpty(codecs)) {
......
...@@ -49,7 +49,7 @@ public class DefaultTrackNameProvider implements TrackNameProvider { ...@@ -49,7 +49,7 @@ public class DefaultTrackNameProvider implements TrackNameProvider {
} else { } else {
trackName = buildLabelString(format); trackName = buildLabelString(format);
} }
return trackName.length() == 0 ? resources.getString(R.string.exo_track_unknown) : trackName + " - " + format.id; return trackName.length() == 0 ? resources.getString(R.string.exo_track_unknown) : trackName;
} }
private String buildResolutionString(Format format) { private String buildResolutionString(Format format) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment