Commit 782817d5 by olly Committed by Oliver Woodman

Remove durationUs from MediaFormat.

Duration was originally included in MediaFormat to match the
framework class, but it actually doesn't make much sense. In
many containers there's no such thing as per-stream duration,
and in any case we don't really care. Setting the duration on
each format required excessive piping.

This change moves duration into SeekMap instead, which seems
to make a lot more sense because it's at the container level,
and because being able to seek is generally couplied with
knowing how long the stream is.

This change is also a step toward merging Format and MediaFormat
into a single class (because Format doesn't have a duration),
which is coming soon.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=114428500
parent ebf87a36
Showing with 238 additions and 233 deletions
...@@ -45,18 +45,17 @@ public final class MediaFormatTest extends TestCase { ...@@ -45,18 +45,17 @@ public final class MediaFormatTest extends TestCase {
initData.add(initData2); initData.add(initData2);
testConversionToFrameworkFormatV16(MediaFormat.createVideoFormat( testConversionToFrameworkFormatV16(MediaFormat.createVideoFormat(
null, "video/xyz", 5000, 102400, 1000L, 1280, 720, initData)); null, "video/xyz", 5000, 102400, 1280, 720, initData));
testConversionToFrameworkFormatV16(MediaFormat.createVideoFormat( testConversionToFrameworkFormatV16(MediaFormat.createVideoFormat(
null, "video/xyz", 5000, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, 1280, 720, null)); null, "video/xyz", 5000, MediaFormat.NO_VALUE, 1280, 720, null));
testConversionToFrameworkFormatV16(MediaFormat.createAudioFormat( testConversionToFrameworkFormatV16(MediaFormat.createAudioFormat(
null, "audio/xyz", 500, 128, 1000L, 5, 44100, initData, null)); null, "audio/xyz", 500, 128, 5, 44100, initData, null));
testConversionToFrameworkFormatV16(MediaFormat.createAudioFormat( testConversionToFrameworkFormatV16(MediaFormat.createAudioFormat(
null, "audio/xyz", 500, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, 5, 44100, null, null)); null, "audio/xyz", 500, MediaFormat.NO_VALUE, 5, 44100, null, null));
testConversionToFrameworkFormatV16( testConversionToFrameworkFormatV16(
MediaFormat.createTextFormat(null, "text/xyz", MediaFormat.NO_VALUE, 1000L, "eng")); MediaFormat.createTextFormat(null, "text/xyz", MediaFormat.NO_VALUE, "eng"));
testConversionToFrameworkFormatV16( testConversionToFrameworkFormatV16(
MediaFormat.createTextFormat(null, "text/xyz", MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, MediaFormat.createTextFormat(null, "text/xyz", MediaFormat.NO_VALUE, null));
null));
} }
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
...@@ -78,11 +77,6 @@ public final class MediaFormatTest extends TestCase { ...@@ -78,11 +77,6 @@ public final class MediaFormatTest extends TestCase {
byte[] frameworkData = Arrays.copyOf(frameworkBuffer.array(), frameworkBuffer.limit()); byte[] frameworkData = Arrays.copyOf(frameworkBuffer.array(), frameworkBuffer.limit());
assertTrue(Arrays.equals(originalData, frameworkData)); assertTrue(Arrays.equals(originalData, frameworkData));
} }
if (in.durationUs == C.UNKNOWN_TIME_US) {
assertFalse(out.containsKey(android.media.MediaFormat.KEY_DURATION));
} else {
assertEquals(in.durationUs, out.getLong(android.media.MediaFormat.KEY_DURATION));
}
} }
@TargetApi(16) @TargetApi(16)
......
...@@ -134,12 +134,11 @@ public final class FrameworkSampleSource implements SampleSource { ...@@ -134,12 +134,11 @@ public final class FrameworkSampleSource implements SampleSource {
pendingResets = new boolean[trackStates.length]; pendingResets = new boolean[trackStates.length];
tracks = new TrackGroup[trackStates.length]; tracks = new TrackGroup[trackStates.length];
for (int i = 0; i < trackStates.length; i++) { for (int i = 0; i < trackStates.length; i++) {
MediaFormat format = createMediaFormat(extractor.getTrackFormat(i)); android.media.MediaFormat format = extractor.getTrackFormat(i);
tracks[i] = new TrackGroup(format); if (format.containsKey(android.media.MediaFormat.KEY_DURATION)) {
long trackDurationUs = format.durationUs; durationUs = Math.max(durationUs, format.getLong(android.media.MediaFormat.KEY_DURATION));
if (trackDurationUs > durationUs) {
durationUs = trackDurationUs;
} }
tracks[i] = new TrackGroup(createMediaFormat(format));
} }
prepared = true; prepared = true;
return true; return true;
...@@ -314,12 +313,10 @@ public final class FrameworkSampleSource implements SampleSource { ...@@ -314,12 +313,10 @@ public final class FrameworkSampleSource implements SampleSource {
initializationData.add(data); initializationData.add(data);
buffer.flip(); buffer.flip();
} }
long durationUs = format.containsKey(android.media.MediaFormat.KEY_DURATION)
? format.getLong(android.media.MediaFormat.KEY_DURATION) : C.UNKNOWN_TIME_US;
MediaFormat mediaFormat = new MediaFormat(null, mimeType, MediaFormat.NO_VALUE, maxInputSize, MediaFormat mediaFormat = new MediaFormat(null, mimeType, MediaFormat.NO_VALUE, maxInputSize,
durationUs, width, height, rotationDegrees, MediaFormat.NO_VALUE, channelCount, sampleRate, width, height, rotationDegrees, MediaFormat.NO_VALUE, channelCount, sampleRate, language,
language, MediaFormat.OFFSET_SAMPLE_RELATIVE, initializationData, false, MediaFormat.OFFSET_SAMPLE_RELATIVE, initializationData, false, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE); MediaFormat.NO_VALUE);
mediaFormat.setFrameworkFormatV16(format); mediaFormat.setFrameworkFormatV16(format);
return mediaFormat; return mediaFormat;
} }
......
...@@ -51,6 +51,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load ...@@ -51,6 +51,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
private final Uri uri; private final Uri uri;
private final DataSource dataSource; private final DataSource dataSource;
private final MediaFormat format; private final MediaFormat format;
private final long durationUs;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final TrackGroup tracks; private final TrackGroup tracks;
...@@ -64,15 +65,16 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load ...@@ -64,15 +65,16 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
private int currentLoadableExceptionCount; private int currentLoadableExceptionCount;
private long currentLoadableExceptionTimestamp; private long currentLoadableExceptionTimestamp;
public SingleSampleSource(Uri uri, DataSource dataSource, MediaFormat format) { public SingleSampleSource(Uri uri, DataSource dataSource, MediaFormat format, long durationUs) {
this(uri, dataSource, format, DEFAULT_MIN_LOADABLE_RETRY_COUNT); this(uri, dataSource, format, durationUs, DEFAULT_MIN_LOADABLE_RETRY_COUNT);
} }
public SingleSampleSource(Uri uri, DataSource dataSource, MediaFormat format, public SingleSampleSource(Uri uri, DataSource dataSource, MediaFormat format, long durationUs,
int minLoadableRetryCount) { int minLoadableRetryCount) {
this.uri = uri; this.uri = uri;
this.dataSource = dataSource; this.dataSource = dataSource;
this.format = format; this.format = format;
this.durationUs = durationUs;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
tracks = new TrackGroup(format); tracks = new TrackGroup(format);
sampleData = new byte[INITIAL_SAMPLE_SIZE]; sampleData = new byte[INITIAL_SAMPLE_SIZE];
...@@ -100,7 +102,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load ...@@ -100,7 +102,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
@Override @Override
public long getDurationUs() { public long getDurationUs() {
return format.durationUs; return durationUs;
} }
@Override @Override
......
...@@ -152,12 +152,11 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call ...@@ -152,12 +152,11 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
if (!chunkSource.prepare()) { if (!chunkSource.prepare()) {
return false; return false;
} }
durationUs = C.UNKNOWN_TIME_US; durationUs = chunkSource.getDurationUs();
TrackGroup trackGroup = chunkSource.getTracks(); TrackGroup trackGroup = chunkSource.getTracks();
if (trackGroup.length > 0) { if (trackGroup.length > 0) {
MediaFormat firstTrackFormat = trackGroup.getFormat(0); MediaFormat firstTrackFormat = trackGroup.getFormat(0);
loader = new Loader("Loader:" + firstTrackFormat.mimeType); loader = new Loader("Loader:" + firstTrackFormat.mimeType);
durationUs = firstTrackFormat.durationUs;
} }
state = STATE_PREPARED; state = STATE_PREPARED;
return true; return true;
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer.chunk; package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.TrackGroup; import com.google.android.exoplayer.TrackGroup;
import java.io.IOException; import java.io.IOException;
...@@ -51,6 +52,16 @@ public interface ChunkSource { ...@@ -51,6 +52,16 @@ public interface ChunkSource {
boolean prepare() throws IOException; boolean prepare() throws IOException;
/** /**
* Gets the duration of the source in microseconds.
* <p>
* This method should only be called after the source has been prepared.
*
* @return The duration of the source in microseconds, or {@link C#UNKNOWN_TIME_US} if the
* duration is unknown.
*/
long getDurationUs();
/**
* Gets the group of tracks provided by the source. * Gets the group of tracks provided by the source.
* <p> * <p>
* This method should only be called after the source has been prepared. * This method should only be called after the source has been prepared.
......
...@@ -121,6 +121,7 @@ public class DashChunkSource implements ChunkSource { ...@@ -121,6 +121,7 @@ public class DashChunkSource implements ChunkSource {
private boolean manifestFetcherEnabled; private boolean manifestFetcherEnabled;
private boolean live; private boolean live;
private long durationUs;
private MediaPresentationDescription currentManifest; private MediaPresentationDescription currentManifest;
private MediaPresentationDescription processedManifest; private MediaPresentationDescription processedManifest;
private int nextPeriodHolderIndex; private int nextPeriodHolderIndex;
...@@ -248,6 +249,7 @@ public class DashChunkSource implements ChunkSource { ...@@ -248,6 +249,7 @@ public class DashChunkSource implements ChunkSource {
return false; return false;
} else { } else {
live = currentManifest.dynamic; live = currentManifest.dynamic;
durationUs = live ? C.UNKNOWN_TIME_US : currentManifest.duration * 1000;
selectTracks(currentManifest, 0); selectTracks(currentManifest, 0);
} }
} }
...@@ -255,6 +257,11 @@ public class DashChunkSource implements ChunkSource { ...@@ -255,6 +257,11 @@ public class DashChunkSource implements ChunkSource {
} }
@Override @Override
public long getDurationUs() {
return durationUs;
}
@Override
public final TrackGroup getTracks() { public final TrackGroup getTracks() {
return trackGroup; return trackGroup;
} }
...@@ -509,7 +516,7 @@ public class DashChunkSource implements ChunkSource { ...@@ -509,7 +516,7 @@ public class DashChunkSource implements ChunkSource {
MediaFormat[] trackMediaFormats = new MediaFormat[representations.size()]; MediaFormat[] trackMediaFormats = new MediaFormat[representations.size()];
int trackCount = 0; int trackCount = 0;
for (int j = 0; j < trackMediaFormats.length; j++) { for (int j = 0; j < trackMediaFormats.length; j++) {
trackMediaFormats[trackCount] = getMediaFormat(manifest, representations.get(j).format); trackMediaFormats[trackCount] = getMediaFormat(representations.get(j).format);
if (trackMediaFormats[trackCount] != null) { if (trackMediaFormats[trackCount] != null) {
trackFormats[trackCount++] = representations.get(j).format; trackFormats[trackCount++] = representations.get(j).format;
} }
...@@ -523,15 +530,14 @@ public class DashChunkSource implements ChunkSource { ...@@ -523,15 +530,14 @@ public class DashChunkSource implements ChunkSource {
trackFormats = new Format[0]; trackFormats = new Format[0];
} }
private MediaFormat getMediaFormat(MediaPresentationDescription manifest, private MediaFormat getMediaFormat(Format representationFormat) {
Format representationFormat) {
String mediaMimeType = getMediaMimeType(representationFormat); String mediaMimeType = getMediaMimeType(representationFormat);
if (mediaMimeType == null) { if (mediaMimeType == null) {
Log.w(TAG, "Skipped track " + representationFormat.id + " (unknown media mime type)"); Log.w(TAG, "Skipped track " + representationFormat.id + " (unknown media mime type)");
return null; return null;
} }
MediaFormat trackFormat = getTrackFormat(adaptationSetType, representationFormat, MediaFormat trackFormat = getTrackFormat(adaptationSetType, representationFormat,
mediaMimeType, manifest.dynamic ? C.UNKNOWN_TIME_US : manifest.duration * 1000); mediaMimeType);
if (trackFormat == null) { if (trackFormat == null) {
Log.w(TAG, "Skipped track " + representationFormat.id + " (unknown media format)"); Log.w(TAG, "Skipped track " + representationFormat.id + " (unknown media format)");
return null; return null;
...@@ -545,17 +551,17 @@ public class DashChunkSource implements ChunkSource { ...@@ -545,17 +551,17 @@ public class DashChunkSource implements ChunkSource {
} }
private static MediaFormat getTrackFormat(int adaptationSetType, Format format, private static MediaFormat getTrackFormat(int adaptationSetType, Format format,
String mediaMimeType, long durationUs) { String mediaMimeType) {
switch (adaptationSetType) { switch (adaptationSetType) {
case AdaptationSet.TYPE_VIDEO: case AdaptationSet.TYPE_VIDEO:
return MediaFormat.createVideoFormat(format.id, mediaMimeType, format.bitrate, return MediaFormat.createVideoFormat(format.id, mediaMimeType, format.bitrate,
MediaFormat.NO_VALUE, durationUs, format.width, format.height, null); MediaFormat.NO_VALUE, format.width, format.height, null);
case AdaptationSet.TYPE_AUDIO: case AdaptationSet.TYPE_AUDIO:
return MediaFormat.createAudioFormat(format.id, mediaMimeType, format.bitrate, return MediaFormat.createAudioFormat(format.id, mediaMimeType, format.bitrate,
MediaFormat.NO_VALUE, durationUs, format.audioChannels, format.audioSamplingRate, null, MediaFormat.NO_VALUE, format.audioChannels, format.audioSamplingRate, null,
format.language); format.language);
case AdaptationSet.TYPE_TEXT: case AdaptationSet.TYPE_TEXT:
return MediaFormat.createTextFormat(format.id, mediaMimeType, format.bitrate, durationUs, return MediaFormat.createTextFormat(format.id, mediaMimeType, format.bitrate,
format.language); format.language);
default: default:
return null; return null;
......
...@@ -47,18 +47,23 @@ public final class ChunkIndex implements SeekMap { ...@@ -47,18 +47,23 @@ public final class ChunkIndex implements SeekMap {
*/ */
public final long[] timesUs; public final long[] timesUs;
private final long durationUs;
/** /**
* @param durationUs The duration of the stream.
* @param sizes The chunk sizes, in bytes. * @param sizes The chunk sizes, in bytes.
* @param offsets The chunk byte offsets. * @param offsets The chunk byte offsets.
* @param durationsUs The chunk durations, in microseconds. * @param durationsUs The chunk durations, in microseconds.
* @param timesUs The start time of each chunk, in microseconds. * @param timesUs The start time of each chunk, in microseconds.
*/ */
public ChunkIndex(int[] sizes, long[] offsets, long[] durationsUs, long[] timesUs) { public ChunkIndex(long durationUs, int[] sizes, long[] offsets, long[] durationsUs,
this.length = sizes.length; long[] timesUs) {
this.durationUs = durationUs;
this.sizes = sizes; this.sizes = sizes;
this.offsets = offsets; this.offsets = offsets;
this.durationsUs = durationsUs; this.durationsUs = durationsUs;
this.timesUs = timesUs; this.timesUs = timesUs;
length = sizes.length;
} }
/** /**
...@@ -79,6 +84,11 @@ public final class ChunkIndex implements SeekMap { ...@@ -79,6 +84,11 @@ public final class ChunkIndex implements SeekMap {
} }
@Override @Override
public long getDurationUs() {
return durationUs;
}
@Override
public long getPosition(long timeUs) { public long getPosition(long timeUs) {
return offsets[getChunkIndex(timeUs)]; return offsets[getChunkIndex(timeUs)];
} }
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package com.google.android.exoplayer.extractor; package com.google.android.exoplayer.extractor;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.MediaFormatHolder; import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleHolder;
...@@ -267,13 +266,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ...@@ -267,13 +266,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
trackEnabledStates = new boolean[trackCount]; trackEnabledStates = new boolean[trackCount];
pendingResets = new boolean[trackCount]; pendingResets = new boolean[trackCount];
pendingMediaFormat = new boolean[trackCount]; pendingMediaFormat = new boolean[trackCount];
durationUs = C.UNKNOWN_TIME_US; durationUs = seekMap.getDurationUs();
for (int i = 0; i < trackCount; i++) { for (int i = 0; i < trackCount; i++) {
MediaFormat format = sampleQueues.valueAt(i).getFormat(); tracks[i] = new TrackGroup(sampleQueues.valueAt(i).getFormat());
tracks[i] = new TrackGroup(format);
if (format.durationUs > durationUs) {
durationUs = format.durationUs;
}
} }
prepared = true; prepared = true;
return true; return true;
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer.extractor; package com.google.android.exoplayer.extractor;
import com.google.android.exoplayer.C;
/** /**
* Maps seek positions (in microseconds) to corresponding positions (byte offsets) in the stream. * Maps seek positions (in microseconds) to corresponding positions (byte offsets) in the stream.
*/ */
...@@ -23,7 +25,17 @@ public interface SeekMap { ...@@ -23,7 +25,17 @@ public interface SeekMap {
/** /**
* A {@link SeekMap} that does not support seeking. * A {@link SeekMap} that does not support seeking.
*/ */
public static final SeekMap UNSEEKABLE = new SeekMap() { final class Unseekable implements SeekMap {
private final long durationUs;
/**
* @param durationUs The duration of the stream in microseconds, or {@link C#UNKNOWN_TIME_US} if
* the duration is unknown.
*/
public Unseekable(long durationUs) {
this.durationUs = durationUs;
}
@Override @Override
public boolean isSeekable() { public boolean isSeekable() {
...@@ -31,11 +43,16 @@ public interface SeekMap { ...@@ -31,11 +43,16 @@ public interface SeekMap {
} }
@Override @Override
public long getDurationUs() {
return durationUs;
}
@Override
public long getPosition(long timeUs) { public long getPosition(long timeUs) {
return 0; return 0;
} }
}; }
/** /**
* Whether or not the seeking is supported. * Whether or not the seeking is supported.
...@@ -48,6 +65,14 @@ public interface SeekMap { ...@@ -48,6 +65,14 @@ public interface SeekMap {
boolean isSeekable(); boolean isSeekable();
/** /**
* Returns the duration of the stream in microseconds.
*
* @return The duration of the stream in microseconds, or {@link C#UNKNOWN_TIME_US} if the
* duration is unknown.
*/
long getDurationUs();
/**
* Maps a seek position in microseconds to a corresponding position (byte offset) in the stream * Maps a seek position in microseconds to a corresponding position (byte offset) in the stream
* from which data can be provided to the extractor. * from which data can be provided to the extractor.
* *
......
...@@ -87,8 +87,8 @@ import java.util.Collections; ...@@ -87,8 +87,8 @@ import java.util.Collections;
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
audioSpecifiConfig); audioSpecifiConfig);
MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC, MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, getDurationUs(), audioParams.second, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, audioParams.second, audioParams.first,
audioParams.first, Collections.singletonList(audioSpecifiConfig), null); Collections.singletonList(audioSpecifiConfig), null);
output.format(mediaFormat); output.format(mediaFormat);
hasOutputFormat = true; hasOutputFormat = true;
} else if (packetType == AAC_PACKET_TYPE_AAC_RAW) { } else if (packetType == AAC_PACKET_TYPE_AAC_RAW) {
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer.extractor.flv; package com.google.android.exoplayer.extractor.flv;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.extractor.ExtractorOutput; import com.google.android.exoplayer.extractor.ExtractorOutput;
...@@ -237,14 +236,6 @@ public final class FlvExtractor implements Extractor, SeekMap { ...@@ -237,14 +236,6 @@ public final class FlvExtractor implements Extractor, SeekMap {
videoReader.consume(prepareTagData(input), tagTimestampUs); videoReader.consume(prepareTagData(input), tagTimestampUs);
} else if (tagType == TAG_TYPE_SCRIPT_DATA && metadataReader != null) { } else if (tagType == TAG_TYPE_SCRIPT_DATA && metadataReader != null) {
metadataReader.consume(prepareTagData(input), tagTimestampUs); metadataReader.consume(prepareTagData(input), tagTimestampUs);
if (metadataReader.getDurationUs() != C.UNKNOWN_TIME_US) {
if (audioReader != null) {
audioReader.setDurationUs(metadataReader.getDurationUs());
}
if (videoReader != null) {
videoReader.setDurationUs(metadataReader.getDurationUs());
}
}
} else { } else {
input.skipFully(tagDataSize); input.skipFully(tagDataSize);
wasConsumed = false; wasConsumed = false;
...@@ -274,6 +265,11 @@ public final class FlvExtractor implements Extractor, SeekMap { ...@@ -274,6 +265,11 @@ public final class FlvExtractor implements Extractor, SeekMap {
} }
@Override @Override
public long getDurationUs() {
return metadataReader.getDurationUs();
}
@Override
public long getPosition(long timeUs) { public long getPosition(long timeUs) {
return 0; return 0;
} }
......
...@@ -43,11 +43,18 @@ import java.util.Map; ...@@ -43,11 +43,18 @@ import java.util.Map;
private static final int AMF_TYPE_STRICT_ARRAY = 10; private static final int AMF_TYPE_STRICT_ARRAY = 10;
private static final int AMF_TYPE_DATE = 11; private static final int AMF_TYPE_DATE = 11;
private long durationUs;
/** /**
* @param output A {@link TrackOutput} to which samples should be written. * @param output A {@link TrackOutput} to which samples should be written.
*/ */
public ScriptTagPayloadReader(TrackOutput output) { public ScriptTagPayloadReader(TrackOutput output) {
super(output); super(output);
durationUs = C.UNKNOWN_TIME_US;
}
public long getDurationUs() {
return durationUs;
} }
@Override @Override
...@@ -82,7 +89,7 @@ import java.util.Map; ...@@ -82,7 +89,7 @@ import java.util.Map;
if (metadata.containsKey(KEY_DURATION)) { if (metadata.containsKey(KEY_DURATION)) {
double durationSeconds = (double) metadata.get(KEY_DURATION); double durationSeconds = (double) metadata.get(KEY_DURATION);
if (durationSeconds > 0.0) { if (durationSeconds > 0.0) {
setDurationUs((long) (durationSeconds * C.MICROS_PER_SECOND)); durationUs = (long) (durationSeconds * C.MICROS_PER_SECOND);
} }
} }
} }
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer.extractor.flv; package com.google.android.exoplayer.extractor.flv;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.extractor.TrackOutput; import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
...@@ -38,32 +37,11 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -38,32 +37,11 @@ import com.google.android.exoplayer.util.ParsableByteArray;
protected final TrackOutput output; protected final TrackOutput output;
private long durationUs;
/** /**
* @param output A {@link TrackOutput} to which samples should be written. * @param output A {@link TrackOutput} to which samples should be written.
*/ */
protected TagPayloadReader(TrackOutput output) { protected TagPayloadReader(TrackOutput output) {
this.output = output; this.output = output;
this.durationUs = C.UNKNOWN_TIME_US;
}
/**
* Sets duration in microseconds.
*
* @param durationUs duration in microseconds.
*/
public final void setDurationUs(long durationUs) {
this.durationUs = durationUs;
}
/**
* Gets the duration in microseconds.
*
* @return The duration in microseconds.
*/
public final long getDurationUs() {
return durationUs;
} }
/** /**
......
...@@ -96,9 +96,8 @@ import java.util.List; ...@@ -96,9 +96,8 @@ import java.util.List;
// Construct and output the format. // Construct and output the format.
MediaFormat mediaFormat = MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_H264, MediaFormat mediaFormat = MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_H264,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, getDurationUs(), avcData.width, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, avcData.width, avcData.height,
avcData.height, avcData.initializationData, MediaFormat.NO_VALUE, avcData.initializationData, MediaFormat.NO_VALUE, avcData.pixelWidthAspectRatio);
avcData.pixelWidthAspectRatio);
output.format(mediaFormat); output.format(mediaFormat);
hasOutputFormat = true; hasOutputFormat = true;
} else if (packetType == AVC_PACKET_TYPE_AVC_NALU) { } else if (packetType == AVC_PACKET_TYPE_AVC_NALU) {
......
...@@ -120,8 +120,8 @@ public final class Mp3Extractor implements Extractor { ...@@ -120,8 +120,8 @@ public final class Mp3Extractor implements Extractor {
setupSeeker(input); setupSeeker(input);
extractorOutput.seekMap(seeker); extractorOutput.seekMap(seeker);
trackOutput.format(MediaFormat.createAudioFormat(null, synchronizedHeader.mimeType, trackOutput.format(MediaFormat.createAudioFormat(null, synchronizedHeader.mimeType,
MediaFormat.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, seeker.getDurationUs(), MediaFormat.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels,
synchronizedHeader.channels, synchronizedHeader.sampleRate, null, null)); synchronizedHeader.sampleRate, null, null));
} }
return readSample(input); return readSample(input);
} }
...@@ -320,9 +320,6 @@ public final class Mp3Extractor implements Extractor { ...@@ -320,9 +320,6 @@ public final class Mp3Extractor implements Extractor {
*/ */
long getTimeUs(long position); long getTimeUs(long position);
/** Returns the duration of the source, in microseconds. */
long getDurationUs();
} }
/* package */ static final class Metadata { /* package */ static final class Metadata {
......
...@@ -67,7 +67,7 @@ import java.util.List; ...@@ -67,7 +67,7 @@ import java.util.List;
Pair<Long, String> mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data); Pair<Long, String> mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data);
StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id, StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id,
durationUs, tkhdData.rotationDegrees, mdhdData.second, isQuickTime); tkhdData.rotationDegrees, mdhdData.second, isQuickTime);
Pair<long[], long[]> edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts)); Pair<long[], long[]> edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts));
return stsdData.mediaFormat == null ? null return stsdData.mediaFormat == null ? null
: new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs, : new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs,
...@@ -438,14 +438,13 @@ import java.util.List; ...@@ -438,14 +438,13 @@ import java.util.List;
* *
* @param stsd The stsd atom to parse. * @param stsd The stsd atom to parse.
* @param trackId The track's identifier in its container. * @param trackId The track's identifier in its container.
* @param durationUs The duration of the track in microseconds.
* @param rotationDegrees The rotation of the track in degrees. * @param rotationDegrees The rotation of the track in degrees.
* @param language The language of the track. * @param language The language of the track.
* @param isQuickTime True for QuickTime media. False otherwise. * @param isQuickTime True for QuickTime media. False otherwise.
* @return An object containing the parsed data. * @return An object containing the parsed data.
*/ */
private static StsdData parseStsd(ParsableByteArray stsd, int trackId, long durationUs, private static StsdData parseStsd(ParsableByteArray stsd, int trackId, int rotationDegrees,
int rotationDegrees, String language, boolean isQuickTime) { String language, boolean isQuickTime) {
stsd.setPosition(Atom.FULL_HEADER_SIZE); stsd.setPosition(Atom.FULL_HEADER_SIZE);
int numberOfEntries = stsd.readInt(); int numberOfEntries = stsd.readInt();
StsdData out = new StsdData(numberOfEntries); StsdData out = new StsdData(numberOfEntries);
...@@ -458,27 +457,27 @@ import java.util.List; ...@@ -458,27 +457,27 @@ import java.util.List;
|| childAtomType == Atom.TYPE_encv || childAtomType == Atom.TYPE_mp4v || childAtomType == Atom.TYPE_encv || childAtomType == Atom.TYPE_mp4v
|| childAtomType == Atom.TYPE_hvc1 || childAtomType == Atom.TYPE_hev1 || childAtomType == Atom.TYPE_hvc1 || childAtomType == Atom.TYPE_hev1
|| childAtomType == Atom.TYPE_s263) { || childAtomType == Atom.TYPE_s263) {
parseVideoSampleEntry(stsd, childStartPosition, childAtomSize, trackId, durationUs, parseVideoSampleEntry(stsd, childStartPosition, childAtomSize, trackId, rotationDegrees,
rotationDegrees, out, i); out, i);
} else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca } else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca
|| childAtomType == Atom.TYPE_ac_3 || childAtomType == Atom.TYPE_ec_3 || childAtomType == Atom.TYPE_ac_3 || childAtomType == Atom.TYPE_ec_3
|| childAtomType == Atom.TYPE_dtsc || childAtomType == Atom.TYPE_dtse || childAtomType == Atom.TYPE_dtsc || childAtomType == Atom.TYPE_dtse
|| childAtomType == Atom.TYPE_dtsh || childAtomType == Atom.TYPE_dtsl || childAtomType == Atom.TYPE_dtsh || childAtomType == Atom.TYPE_dtsl
|| childAtomType == Atom.TYPE_samr || childAtomType == Atom.TYPE_sawb) { || childAtomType == Atom.TYPE_samr || childAtomType == Atom.TYPE_sawb) {
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId,
durationUs, language, isQuickTime, out, i); language, isQuickTime, out, i);
} else if (childAtomType == Atom.TYPE_TTML) { } else if (childAtomType == Atom.TYPE_TTML) {
out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId), out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId),
MimeTypes.APPLICATION_TTML, MediaFormat.NO_VALUE, durationUs, language); MimeTypes.APPLICATION_TTML, MediaFormat.NO_VALUE, language);
} else if (childAtomType == Atom.TYPE_tx3g) { } else if (childAtomType == Atom.TYPE_tx3g) {
out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId), out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId),
MimeTypes.APPLICATION_TX3G, MediaFormat.NO_VALUE, durationUs, language); MimeTypes.APPLICATION_TX3G, MediaFormat.NO_VALUE, language);
} else if (childAtomType == Atom.TYPE_wvtt) { } else if (childAtomType == Atom.TYPE_wvtt) {
out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId), out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId),
MimeTypes.APPLICATION_MP4VTT, MediaFormat.NO_VALUE, durationUs, language); MimeTypes.APPLICATION_MP4VTT, MediaFormat.NO_VALUE, language);
} else if (childAtomType == Atom.TYPE_stpp) { } else if (childAtomType == Atom.TYPE_stpp) {
out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId), out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId),
MimeTypes.APPLICATION_TTML, MediaFormat.NO_VALUE, durationUs, language, MimeTypes.APPLICATION_TTML, MediaFormat.NO_VALUE, language,
0 /* subsample timing is absolute */); 0 /* subsample timing is absolute */);
} }
stsd.setPosition(childStartPosition + childAtomSize); stsd.setPosition(childStartPosition + childAtomSize);
...@@ -487,7 +486,7 @@ import java.util.List; ...@@ -487,7 +486,7 @@ import java.util.List;
} }
private static void parseVideoSampleEntry(ParsableByteArray parent, int position, int size, private static void parseVideoSampleEntry(ParsableByteArray parent, int position, int size,
int trackId, long durationUs, int rotationDegrees, StsdData out, int entryIndex) { int trackId, int rotationDegrees, StsdData out, int entryIndex) {
parent.setPosition(position + Atom.HEADER_SIZE); parent.setPosition(position + Atom.HEADER_SIZE);
parent.skipBytes(24); parent.skipBytes(24);
...@@ -550,7 +549,7 @@ import java.util.List; ...@@ -550,7 +549,7 @@ import java.util.List;
} }
out.mediaFormat = MediaFormat.createVideoFormat(Integer.toString(trackId), mimeType, out.mediaFormat = MediaFormat.createVideoFormat(Integer.toString(trackId), mimeType,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, durationUs, width, height, initializationData, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, width, height, initializationData,
rotationDegrees, pixelWidthHeightRatio); rotationDegrees, pixelWidthHeightRatio);
} }
...@@ -712,8 +711,7 @@ import java.util.List; ...@@ -712,8 +711,7 @@ import java.util.List;
} }
private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position, private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position,
int size, int trackId, long durationUs, String language, boolean isQuickTime, StsdData out, int size, int trackId, String language, boolean isQuickTime, StsdData out, int entryIndex) {
int entryIndex) {
parent.setPosition(position + Atom.HEADER_SIZE); parent.setPosition(position + Atom.HEADER_SIZE);
int quickTimeSoundDescriptionVersion = 0; int quickTimeSoundDescriptionVersion = 0;
...@@ -789,19 +787,18 @@ import java.util.List; ...@@ -789,19 +787,18 @@ import java.util.List;
// TODO: Add support for encryption (by setting out.trackEncryptionBoxes). // TODO: Add support for encryption (by setting out.trackEncryptionBoxes).
parent.setPosition(Atom.HEADER_SIZE + childAtomPosition); parent.setPosition(Atom.HEADER_SIZE + childAtomPosition);
out.mediaFormat = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId), out.mediaFormat = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId),
durationUs, language); language);
return; return;
} else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) { } else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) {
parent.setPosition(Atom.HEADER_SIZE + childAtomPosition); parent.setPosition(Atom.HEADER_SIZE + childAtomPosition);
out.mediaFormat = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), out.mediaFormat = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId),
durationUs, language); language);
return; return;
} else if ((atomType == Atom.TYPE_dtsc || atomType == Atom.TYPE_dtse } else if ((atomType == Atom.TYPE_dtsc || atomType == Atom.TYPE_dtse
|| atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl) || atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl)
&& childAtomType == Atom.TYPE_ddts) { && childAtomType == Atom.TYPE_ddts) {
out.mediaFormat = MediaFormat.createAudioFormat(Integer.toString(trackId), mimeType, out.mediaFormat = MediaFormat.createAudioFormat(Integer.toString(trackId), mimeType,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, channelCount, sampleRate, null, language);
language);
return; return;
} }
childAtomPosition += childAtomSize; childAtomPosition += childAtomSize;
...@@ -813,7 +810,7 @@ import java.util.List; ...@@ -813,7 +810,7 @@ import java.util.List;
} }
out.mediaFormat = MediaFormat.createAudioFormat(Integer.toString(trackId), mimeType, out.mediaFormat = MediaFormat.createAudioFormat(Integer.toString(trackId), mimeType,
MediaFormat.NO_VALUE, sampleSize, durationUs, channelCount, sampleRate, MediaFormat.NO_VALUE, sampleSize, channelCount, sampleRate,
initializationData == null ? null : Collections.singletonList(initializationData), initializationData == null ? null : Collections.singletonList(initializationData),
language); language);
} }
......
...@@ -216,7 +216,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -216,7 +216,7 @@ public final class FragmentedMp4Extractor implements Extractor {
if (atomType == Atom.TYPE_mdat) { if (atomType == Atom.TYPE_mdat) {
endOfMdatPosition = atomPosition + atomSize; endOfMdatPosition = atomPosition + atomSize;
if (!haveOutputSeekMap) { if (!haveOutputSeekMap) {
extractorOutput.seekMap(SeekMap.UNSEEKABLE); extractorOutput.seekMap(new SeekMap.Unseekable(track.durationUs));
haveOutputSeekMap = true; haveOutputSeekMap = true;
} }
if (fragmentRun.sampleEncryptionDataNeedsFill) { if (fragmentRun.sampleEncryptionDataNeedsFill) {
...@@ -271,7 +271,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -271,7 +271,7 @@ public final class FragmentedMp4Extractor implements Extractor {
if (!containerAtoms.isEmpty()) { if (!containerAtoms.isEmpty()) {
containerAtoms.peek().add(leaf); containerAtoms.peek().add(leaf);
} else if (leaf.type == Atom.TYPE_sidx) { } else if (leaf.type == Atom.TYPE_sidx) {
ChunkIndex segmentIndex = parseSidx(leaf.data, inputPosition); ChunkIndex segmentIndex = parseSidx(track.durationUs, leaf.data, inputPosition);
extractorOutput.seekMap(segmentIndex); extractorOutput.seekMap(segmentIndex);
haveOutputSeekMap = true; haveOutputSeekMap = true;
} }
...@@ -623,7 +623,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -623,7 +623,7 @@ public final class FragmentedMp4Extractor implements Extractor {
/** /**
* Parses a sidx atom (defined in 14496-12). * Parses a sidx atom (defined in 14496-12).
*/ */
private static ChunkIndex parseSidx(ParsableByteArray atom, long inputPosition) private static ChunkIndex parseSidx(long durationUs, ParsableByteArray atom, long inputPosition)
throws ParserException { throws ParserException {
atom.setPosition(Atom.HEADER_SIZE); atom.setPosition(Atom.HEADER_SIZE);
int fullAtom = atom.readInt(); int fullAtom = atom.readInt();
...@@ -674,7 +674,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -674,7 +674,7 @@ public final class FragmentedMp4Extractor implements Extractor {
offset += sizes[i]; offset += sizes[i];
} }
return new ChunkIndex(sizes, offsets, durationsUs, timesUs); return new ChunkIndex(durationUs, sizes, offsets, durationsUs, timesUs);
} }
private void readEncryptionData(ExtractorInput input) throws IOException, InterruptedException { private void readEncryptionData(ExtractorInput input) throws IOException, InterruptedException {
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer.extractor.mp4; package com.google.android.exoplayer.extractor.mp4;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.extractor.ExtractorOutput; import com.google.android.exoplayer.extractor.ExtractorOutput;
...@@ -72,6 +73,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -72,6 +73,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
// Extractor outputs. // Extractor outputs.
private ExtractorOutput extractorOutput; private ExtractorOutput extractorOutput;
private Mp4Track[] tracks; private Mp4Track[] tracks;
private long durationUs;
private boolean isQuickTime; private boolean isQuickTime;
public Mp4Extractor() { public Mp4Extractor() {
...@@ -137,6 +139,11 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -137,6 +139,11 @@ public final class Mp4Extractor implements Extractor, SeekMap {
} }
@Override @Override
public long getDurationUs() {
return durationUs;
}
@Override
public long getPosition(long timeUs) { public long getPosition(long timeUs) {
long earliestSamplePosition = Long.MAX_VALUE; long earliestSamplePosition = Long.MAX_VALUE;
for (int trackIndex = 0; trackIndex < tracks.length; trackIndex++) { for (int trackIndex = 0; trackIndex < tracks.length; trackIndex++) {
...@@ -270,6 +277,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -270,6 +277,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
/** Updates the stored track metadata to reflect the contents of the specified moov atom. */ /** Updates the stored track metadata to reflect the contents of the specified moov atom. */
private void processMoovAtom(ContainerAtom moov) { private void processMoovAtom(ContainerAtom moov) {
long durationUs = C.UNKNOWN_TIME_US;
List<Mp4Track> tracks = new ArrayList<>(); List<Mp4Track> tracks = new ArrayList<>();
long earliestSampleOffset = Long.MAX_VALUE; long earliestSampleOffset = Long.MAX_VALUE;
for (int i = 0; i < moov.containerChildren.size(); i++) { for (int i = 0; i < moov.containerChildren.size(); i++) {
...@@ -296,6 +304,8 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -296,6 +304,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
// Allow ten source samples per output sample, like the platform extractor. // Allow ten source samples per output sample, like the platform extractor.
int maxInputSize = trackSampleTable.maximumSize + 3 * 10; int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
mp4Track.trackOutput.format(track.mediaFormat.copyWithMaxInputSize(maxInputSize)); mp4Track.trackOutput.format(track.mediaFormat.copyWithMaxInputSize(maxInputSize));
durationUs = Math.max(durationUs, track.durationUs);
tracks.add(mp4Track); tracks.add(mp4Track);
long firstSampleOffset = trackSampleTable.offsets[0]; long firstSampleOffset = trackSampleTable.offsets[0];
...@@ -303,6 +313,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -303,6 +313,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
earliestSampleOffset = firstSampleOffset; earliestSampleOffset = firstSampleOffset;
} }
} }
this.durationUs = durationUs;
this.tracks = tracks.toArray(new Mp4Track[0]); this.tracks = tracks.toArray(new Mp4Track[0]);
extractorOutput.endTracks(); extractorOutput.endTracks();
extractorOutput.seekMap(this); extractorOutput.seekMap(this);
......
...@@ -25,6 +25,7 @@ import com.google.android.exoplayer.extractor.PositionHolder; ...@@ -25,6 +25,7 @@ import com.google.android.exoplayer.extractor.PositionHolder;
import com.google.android.exoplayer.extractor.SeekMap; import com.google.android.exoplayer.extractor.SeekMap;
import com.google.android.exoplayer.extractor.TrackOutput; import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.extractor.ogg.VorbisUtil.Mode; import com.google.android.exoplayer.extractor.ogg.VorbisUtil.Mode;
import com.google.android.exoplayer.extractor.ogg.VorbisUtil.VorbisIdHeader;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
...@@ -75,7 +76,7 @@ public final class OggVorbisExtractor implements Extractor { ...@@ -75,7 +76,7 @@ public final class OggVorbisExtractor implements Extractor {
public void init(ExtractorOutput output) { public void init(ExtractorOutput output) {
trackOutput = output.track(0); trackOutput = output.track(0);
output.endTracks(); output.endTracks();
output.seekMap(SeekMap.UNSEEKABLE); output.seekMap(new SeekMap.Unseekable(C.UNKNOWN_TIME_US));
} }
@Override @Override
...@@ -93,17 +94,14 @@ public final class OggVorbisExtractor implements Extractor { ...@@ -93,17 +94,14 @@ public final class OggVorbisExtractor implements Extractor {
if (vorbisSetup == null) { if (vorbisSetup == null) {
vorbisSetup = readSetupHeaders(input, scratch); vorbisSetup = readSetupHeaders(input, scratch);
ArrayList<byte[]> codecInitialisationData = new ArrayList<>(); VorbisIdHeader idHeader = vorbisSetup.idHeader;
codecInitialisationData.clear(); ArrayList<byte[]> codecInitializationData = new ArrayList<>();
codecInitialisationData.add(vorbisSetup.idHeader.data); codecInitializationData.clear();
codecInitialisationData.add(vorbisSetup.setupHeaderData); codecInitializationData.add(idHeader.data);
codecInitializationData.add(vorbisSetup.setupHeaderData);
long duration = input.getLength() == C.LENGTH_UNBOUNDED ? C.UNKNOWN_TIME_US
: input.getLength() * 8000000 / vorbisSetup.idHeader.getApproximateBitrate();
trackOutput.format(MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_VORBIS, trackOutput.format(MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_VORBIS,
this.vorbisSetup.idHeader.bitrateNominal, OGG_MAX_SEGMENT_SIZE * 255, duration, idHeader.bitrateNominal, OGG_MAX_SEGMENT_SIZE * 255, idHeader.channels,
this.vorbisSetup.idHeader.channels, (int) this.vorbisSetup.idHeader.sampleRate, idHeader.sampleRate, codecInitializationData, null));
codecInitialisationData, null));
} }
if (oggReader.readPacket(input, scratch)) { if (oggReader.readPacket(input, scratch)) {
// if this is an audio packet... // if this is an audio packet...
......
...@@ -75,7 +75,7 @@ import java.util.Arrays; ...@@ -75,7 +75,7 @@ import java.util.Arrays;
long version = headerData.readLittleEndianUnsignedInt(); long version = headerData.readLittleEndianUnsignedInt();
int channels = headerData.readUnsignedByte(); int channels = headerData.readUnsignedByte();
long sampleRate = headerData.readLittleEndianUnsignedInt(); int sampleRate = (int) headerData.readLittleEndianUnsignedInt();
int bitrateMax = headerData.readLittleEndianInt(); int bitrateMax = headerData.readLittleEndianInt();
int bitrateNominal = headerData.readLittleEndianInt(); int bitrateNominal = headerData.readLittleEndianInt();
int bitrateMin = headerData.readLittleEndianInt(); int bitrateMin = headerData.readLittleEndianInt();
...@@ -433,7 +433,7 @@ import java.util.Arrays; ...@@ -433,7 +433,7 @@ import java.util.Arrays;
public final long version; public final long version;
public final int channels; public final int channels;
public final long sampleRate; public final int sampleRate;
public final int bitrateMax; public final int bitrateMax;
public final int bitrateNominal; public final int bitrateNominal;
public final int bitrateMin; public final int bitrateMin;
...@@ -442,7 +442,7 @@ import java.util.Arrays; ...@@ -442,7 +442,7 @@ import java.util.Arrays;
public final boolean framingFlag; public final boolean framingFlag;
public final byte[] data; public final byte[] data;
public VorbisIdHeader(long version, int channels, long sampleRate, int bitrateMax, public VorbisIdHeader(long version, int channels, int sampleRate, int bitrateMax,
int bitrateNominal, int bitrateMin, int blockSize0, int blockSize1, boolean framingFlag, int bitrateNominal, int bitrateMin, int blockSize0, int blockSize1, boolean framingFlag,
byte[] data) { byte[] data) {
this.version = version; this.version = version;
......
...@@ -161,8 +161,8 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -161,8 +161,8 @@ import com.google.android.exoplayer.util.ParsableByteArray;
private void parseHeader() { private void parseHeader() {
if (mediaFormat == null) { if (mediaFormat == null) {
mediaFormat = isEac3 mediaFormat = isEac3
? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, C.UNKNOWN_TIME_US, null) ? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, null)
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, C.UNKNOWN_TIME_US, null); : Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, null);
output.format(mediaFormat); output.format(mediaFormat);
} }
sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data) sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data)
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer.extractor.ts; package com.google.android.exoplayer.extractor.ts;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.extractor.ExtractorOutput; import com.google.android.exoplayer.extractor.ExtractorOutput;
...@@ -112,7 +113,7 @@ public final class AdtsExtractor implements Extractor { ...@@ -112,7 +113,7 @@ public final class AdtsExtractor implements Extractor {
public void init(ExtractorOutput output) { public void init(ExtractorOutput output) {
adtsReader = new AdtsReader(output.track(0), output.track(1)); adtsReader = new AdtsReader(output.track(0), output.track(1));
output.endTracks(); output.endTracks();
output.seekMap(SeekMap.UNSEEKABLE); output.seekMap(new SeekMap.Unseekable(C.UNKNOWN_TIME_US));
} }
@Override @Override
......
...@@ -259,8 +259,8 @@ import java.util.Collections; ...@@ -259,8 +259,8 @@ import java.util.Collections;
audioSpecificConfig); audioSpecificConfig);
MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC, MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, audioParams.second, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, audioParams.second, audioParams.first,
audioParams.first, Collections.singletonList(audioSpecificConfig), null); Collections.singletonList(audioSpecificConfig), null);
// In this class a sample is an access unit, but the MediaFormat sample rate specifies the // In this class a sample is an access unit, but the MediaFormat sample rate specifies the
// number of PCM audio samples per second. // number of PCM audio samples per second.
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / mediaFormat.sampleRate; sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / mediaFormat.sampleRate;
......
...@@ -153,7 +153,7 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -153,7 +153,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
private void parseHeader() { private void parseHeader() {
byte[] frameData = headerScratchBytes.data; byte[] frameData = headerScratchBytes.data;
if (mediaFormat == null) { if (mediaFormat == null) {
mediaFormat = DtsUtil.parseDtsFormat(frameData, null, C.UNKNOWN_TIME_US, null); mediaFormat = DtsUtil.parseDtsFormat(frameData, null, null);
output.format(mediaFormat); output.format(mediaFormat);
} }
sampleSize = DtsUtil.getDtsFrameSize(frameData); sampleSize = DtsUtil.getDtsFrameSize(frameData);
......
...@@ -184,7 +184,7 @@ import java.util.Collections; ...@@ -184,7 +184,7 @@ import java.util.Collections;
} }
MediaFormat format = MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_MPEG2, MediaFormat format = MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_MPEG2,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, width, height, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, width, height,
Collections.singletonList(csdData), MediaFormat.NO_VALUE, pixelWidthHeightRatio); Collections.singletonList(csdData), MediaFormat.NO_VALUE, pixelWidthHeightRatio);
long frameDurationUs = 0; long frameDurationUs = 0;
......
...@@ -210,8 +210,8 @@ import java.util.List; ...@@ -210,8 +210,8 @@ import java.util.List;
SpsData parsedSpsData = CodecSpecificDataUtil.parseSpsNalUnit(bitArray); SpsData parsedSpsData = CodecSpecificDataUtil.parseSpsNalUnit(bitArray);
return MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE, return MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, parsedSpsData.width, parsedSpsData.height, MediaFormat.NO_VALUE, parsedSpsData.width, parsedSpsData.height, initializationData,
initializationData, MediaFormat.NO_VALUE, parsedSpsData.pixelWidthAspectRatio); MediaFormat.NO_VALUE, parsedSpsData.pixelWidthAspectRatio);
} }
/** /**
......
...@@ -298,7 +298,7 @@ import java.util.Collections; ...@@ -298,7 +298,7 @@ import java.util.Collections;
} }
return MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_H265, MediaFormat.NO_VALUE, return MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_H265, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, picWidthInLumaSamples, picHeightInLumaSamples, MediaFormat.NO_VALUE, picWidthInLumaSamples, picHeightInLumaSamples,
Collections.singletonList(csd), MediaFormat.NO_VALUE, pixelWidthHeightRatio); Collections.singletonList(csd), MediaFormat.NO_VALUE, pixelWidthHeightRatio);
} }
......
...@@ -161,8 +161,8 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -161,8 +161,8 @@ import com.google.android.exoplayer.util.ParsableByteArray;
if (!hasOutputFormat) { if (!hasOutputFormat) {
frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate; frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate;
MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, header.mimeType, MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, header.mimeType,
MediaFormat.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, C.UNKNOWN_TIME_US, MediaFormat.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels,
header.channels, header.sampleRate, null, null); header.sampleRate, null, null);
output.format(mediaFormat); output.format(mediaFormat);
hasOutputFormat = true; hasOutputFormat = true;
} }
......
...@@ -33,7 +33,7 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -33,7 +33,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
public SeiReader(TrackOutput output) { public SeiReader(TrackOutput output) {
super(output); super(output);
output.format(MediaFormat.createTextFormat(null, MimeTypes.APPLICATION_EIA608, output.format(MediaFormat.createTextFormat(null, MimeTypes.APPLICATION_EIA608,
MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, null)); MediaFormat.NO_VALUE, null));
} }
@Override @Override
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer.extractor.ts; package com.google.android.exoplayer.extractor.ts;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.extractor.DummyTrackOutput; import com.google.android.exoplayer.extractor.DummyTrackOutput;
import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.ExtractorInput;
...@@ -106,7 +107,7 @@ public final class TsExtractor implements Extractor { ...@@ -106,7 +107,7 @@ public final class TsExtractor implements Extractor {
@Override @Override
public void init(ExtractorOutput output) { public void init(ExtractorOutput output) {
this.output = output; this.output = output;
output.seekMap(SeekMap.UNSEEKABLE); output.seekMap(new SeekMap.Unseekable(C.UNKNOWN_TIME_US));
} }
@Override @Override
......
...@@ -384,7 +384,7 @@ public final class WebmExtractor implements Extractor { ...@@ -384,7 +384,7 @@ public final class WebmExtractor implements Extractor {
} else { } else {
// We don't know where the Cues element is located. It's most likely omitted. Allow // We don't know where the Cues element is located. It's most likely omitted. Allow
// playback, but disable seeking. // playback, but disable seeking.
extractorOutput.seekMap(SeekMap.UNSEEKABLE); extractorOutput.seekMap(new SeekMap.Unseekable(durationUs));
sentSeekMap = true; sentSeekMap = true;
} }
} }
...@@ -464,7 +464,7 @@ public final class WebmExtractor implements Extractor { ...@@ -464,7 +464,7 @@ public final class WebmExtractor implements Extractor {
return; return;
case ID_TRACK_ENTRY: case ID_TRACK_ENTRY:
if (tracks.get(currentTrack.number) == null && isCodecSupported(currentTrack.codecId)) { if (tracks.get(currentTrack.number) == null && isCodecSupported(currentTrack.codecId)) {
currentTrack.initializeOutput(extractorOutput, currentTrack.number, durationUs); currentTrack.initializeOutput(extractorOutput, currentTrack.number);
tracks.put(currentTrack.number, currentTrack); tracks.put(currentTrack.number, currentTrack);
} else { } else {
// We've seen this track entry before, or the codec is unsupported. Do nothing. // We've seen this track entry before, or the codec is unsupported. Do nothing.
...@@ -966,8 +966,8 @@ public final class WebmExtractor implements Extractor { ...@@ -966,8 +966,8 @@ public final class WebmExtractor implements Extractor {
/** /**
* Builds a {@link SeekMap} from the recently gathered Cues information. * Builds a {@link SeekMap} from the recently gathered Cues information.
* *
* @return The built {@link SeekMap}. May be {@link SeekMap#UNSEEKABLE} if cues information was * @return The built {@link SeekMap}. The returned {@link SeekMap} may be unseekable if cues
* missing or incomplete. * information was missing or incomplete.
*/ */
private SeekMap buildSeekMap() { private SeekMap buildSeekMap() {
if (segmentContentPosition == UNKNOWN || durationUs == C.UNKNOWN_TIME_US if (segmentContentPosition == UNKNOWN || durationUs == C.UNKNOWN_TIME_US
...@@ -976,7 +976,7 @@ public final class WebmExtractor implements Extractor { ...@@ -976,7 +976,7 @@ public final class WebmExtractor implements Extractor {
// Cues information is missing or incomplete. // Cues information is missing or incomplete.
cueTimesUs = null; cueTimesUs = null;
cueClusterPositions = null; cueClusterPositions = null;
return SeekMap.UNSEEKABLE; return new SeekMap.Unseekable(durationUs);
} }
int cuePointsSize = cueTimesUs.size(); int cuePointsSize = cueTimesUs.size();
int[] sizes = new int[cuePointsSize]; int[] sizes = new int[cuePointsSize];
...@@ -996,7 +996,7 @@ public final class WebmExtractor implements Extractor { ...@@ -996,7 +996,7 @@ public final class WebmExtractor implements Extractor {
durationsUs[cuePointsSize - 1] = durationUs - timesUs[cuePointsSize - 1]; durationsUs[cuePointsSize - 1] = durationUs - timesUs[cuePointsSize - 1];
cueTimesUs = null; cueTimesUs = null;
cueClusterPositions = null; cueClusterPositions = null;
return new ChunkIndex(sizes, offsets, durationsUs, timesUs); return new ChunkIndex(durationUs, sizes, offsets, durationsUs, timesUs);
} }
/** /**
...@@ -1150,8 +1150,7 @@ public final class WebmExtractor implements Extractor { ...@@ -1150,8 +1150,7 @@ public final class WebmExtractor implements Extractor {
/** /**
* Initializes the track with an output. * Initializes the track with an output.
*/ */
public void initializeOutput(ExtractorOutput output, int trackId, long durationUs) public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException {
throws ParserException {
String mimeType; String mimeType;
int maxInputSize = MediaFormat.NO_VALUE; int maxInputSize = MediaFormat.NO_VALUE;
List<byte[]> initializationData = null; List<byte[]> initializationData = null;
...@@ -1237,14 +1236,14 @@ public final class WebmExtractor implements Extractor { ...@@ -1237,14 +1236,14 @@ public final class WebmExtractor implements Extractor {
// into the trackId passed when creating the formats. // into the trackId passed when creating the formats.
if (MimeTypes.isAudio(mimeType)) { if (MimeTypes.isAudio(mimeType)) {
format = MediaFormat.createAudioFormat(Integer.toString(trackId), mimeType, format = MediaFormat.createAudioFormat(Integer.toString(trackId), mimeType,
MediaFormat.NO_VALUE, maxInputSize, durationUs, channelCount, sampleRate, MediaFormat.NO_VALUE, maxInputSize, channelCount, sampleRate,
initializationData, language); initializationData, language);
} else if (MimeTypes.isVideo(mimeType)) { } else if (MimeTypes.isVideo(mimeType)) {
format = MediaFormat.createVideoFormat(Integer.toString(trackId), mimeType, format = MediaFormat.createVideoFormat(Integer.toString(trackId), mimeType,
MediaFormat.NO_VALUE, maxInputSize, durationUs, width, height, initializationData); MediaFormat.NO_VALUE, maxInputSize, width, height, initializationData);
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
format = MediaFormat.createTextFormat(Integer.toString(trackId), mimeType, format = MediaFormat.createTextFormat(Integer.toString(trackId), mimeType,
MediaFormat.NO_VALUE, durationUs, language); MediaFormat.NO_VALUE, language);
} else { } else {
throw new ParserException("Unexpected MIME type."); throw new ParserException("Unexpected MIME type.");
} }
......
...@@ -75,7 +75,7 @@ import java.util.regex.Pattern; ...@@ -75,7 +75,7 @@ import java.util.regex.Pattern;
@Override @Override
public void init(ExtractorOutput output) { public void init(ExtractorOutput output) {
this.output = output; this.output = output;
output.seekMap(SeekMap.UNSEEKABLE); output.seekMap(new SeekMap.Unseekable(C.UNKNOWN_TIME_US));
} }
@Override @Override
...@@ -160,7 +160,7 @@ import java.util.regex.Pattern; ...@@ -160,7 +160,7 @@ import java.util.regex.Pattern;
private TrackOutput buildTrackOutput(long subsampleOffsetUs) { private TrackOutput buildTrackOutput(long subsampleOffsetUs) {
TrackOutput trackOutput = output.track(0); TrackOutput trackOutput = output.track(0);
trackOutput.format(MediaFormat.createTextFormat("id", MimeTypes.TEXT_VTT, MediaFormat.NO_VALUE, trackOutput.format(MediaFormat.createTextFormat("id", MimeTypes.TEXT_VTT, MediaFormat.NO_VALUE,
C.UNKNOWN_TIME_US, "en", subsampleOffsetUs)); "en", subsampleOffsetUs));
output.endTracks(); output.endTracks();
return trackOutput; return trackOutput;
} }
......
...@@ -72,6 +72,7 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -72,6 +72,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
private boolean manifestFetcherEnabled; private boolean manifestFetcherEnabled;
private boolean live; private boolean live;
private long durationUs;
private TrackEncryptionBox[] trackEncryptionBoxes; private TrackEncryptionBox[] trackEncryptionBoxes;
private DrmInitData.Mapped drmInitData; private DrmInitData.Mapped drmInitData;
private SmoothStreamingManifest currentManifest; private SmoothStreamingManifest currentManifest;
...@@ -146,6 +147,7 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -146,6 +147,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
return false; return false;
} else { } else {
live = currentManifest.isLive; live = currentManifest.isLive;
durationUs = currentManifest.durationUs;
ProtectionElement protectionElement = currentManifest.protectionElement; ProtectionElement protectionElement = currentManifest.protectionElement;
if (protectionElement != null) { if (protectionElement != null) {
byte[] keyId = getProtectionElementKeyId(protectionElement.data); byte[] keyId = getProtectionElementKeyId(protectionElement.data);
...@@ -165,6 +167,11 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -165,6 +167,11 @@ public class SmoothStreamingChunkSource implements ChunkSource {
} }
@Override @Override
public long getDurationUs() {
return durationUs;
}
@Override
public final TrackGroup getTracks() { public final TrackGroup getTracks() {
return trackGroup; return trackGroup;
} }
...@@ -373,7 +380,7 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -373,7 +380,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
switch (element.type) { switch (element.type) {
case StreamElement.TYPE_VIDEO: case StreamElement.TYPE_VIDEO:
mediaFormat = MediaFormat.createVideoFormat(format.id, format.mimeType, format.bitrate, mediaFormat = MediaFormat.createVideoFormat(format.id, format.mimeType, format.bitrate,
MediaFormat.NO_VALUE, durationUs, format.width, format.height, Arrays.asList(csdArray)); MediaFormat.NO_VALUE, format.width, format.height, Arrays.asList(csdArray));
mp4TrackType = Track.TYPE_vide; mp4TrackType = Track.TYPE_vide;
break; break;
case StreamElement.TYPE_AUDIO: case StreamElement.TYPE_AUDIO:
...@@ -385,13 +392,13 @@ public class SmoothStreamingChunkSource implements ChunkSource { ...@@ -385,13 +392,13 @@ public class SmoothStreamingChunkSource implements ChunkSource {
format.audioSamplingRate, format.audioChannels)); format.audioSamplingRate, format.audioChannels));
} }
mediaFormat = MediaFormat.createAudioFormat(format.id, format.mimeType, format.bitrate, mediaFormat = MediaFormat.createAudioFormat(format.id, format.mimeType, format.bitrate,
MediaFormat.NO_VALUE, durationUs, format.audioChannels, format.audioSamplingRate, csd, MediaFormat.NO_VALUE, format.audioChannels, format.audioSamplingRate, csd,
format.language); format.language);
mp4TrackType = Track.TYPE_soun; mp4TrackType = Track.TYPE_soun;
break; break;
case StreamElement.TYPE_TEXT: case StreamElement.TYPE_TEXT:
mediaFormat = MediaFormat.createTextFormat(format.id, format.mimeType, format.bitrate, mediaFormat = MediaFormat.createTextFormat(format.id, format.mimeType, format.bitrate,
durationUs, format.language); format.language);
mp4TrackType = Track.TYPE_text; mp4TrackType = Track.TYPE_text;
break; break;
default: default:
......
...@@ -65,12 +65,11 @@ public final class Ac3Util { ...@@ -65,12 +65,11 @@ public final class Ac3Util {
* *
* @param data The AC3SpecificBox to parse. * @param data The AC3SpecificBox to parse.
* @param trackId The track identifier to set on the format, or null. * @param trackId The track identifier to set on the format, or null.
* @param durationUs The duration to set on the format, in microseconds.
* @param language The language to set on the format. * @param language The language to set on the format.
* @return The AC-3 format parsed from data in the header. * @return The AC-3 format parsed from data in the header.
*/ */
public static MediaFormat parseAc3AnnexFFormat(ParsableByteArray data, String trackId, public static MediaFormat parseAc3AnnexFFormat(ParsableByteArray data, String trackId,
long durationUs, String language) { String language) {
int fscod = (data.readUnsignedByte() & 0xC0) >> 6; int fscod = (data.readUnsignedByte() & 0xC0) >> 6;
int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod];
int nextByte = data.readUnsignedByte(); int nextByte = data.readUnsignedByte();
...@@ -79,7 +78,7 @@ public final class Ac3Util { ...@@ -79,7 +78,7 @@ public final class Ac3Util {
channelCount++; channelCount++;
} }
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE, return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null, language); MediaFormat.NO_VALUE, channelCount, sampleRate, null, language);
} }
/** /**
...@@ -88,12 +87,11 @@ public final class Ac3Util { ...@@ -88,12 +87,11 @@ public final class Ac3Util {
* *
* @param data The EC3SpecificBox to parse. * @param data The EC3SpecificBox to parse.
* @param trackId The track identifier to set on the format, or null. * @param trackId The track identifier to set on the format, or null.
* @param durationUs The duration to set on the format, in microseconds.
* @param language The language to set on the format. * @param language The language to set on the format.
* @return The E-AC-3 format parsed from data in the header. * @return The E-AC-3 format parsed from data in the header.
*/ */
public static MediaFormat parseEAc3AnnexFFormat(ParsableByteArray data, String trackId, public static MediaFormat parseEAc3AnnexFFormat(ParsableByteArray data, String trackId,
long durationUs, String language) { String language) {
data.skipBytes(2); // data_rate, num_ind_sub data.skipBytes(2); // data_rate, num_ind_sub
// Read only the first substream. // Read only the first substream.
...@@ -106,7 +104,7 @@ public final class Ac3Util { ...@@ -106,7 +104,7 @@ public final class Ac3Util {
channelCount++; channelCount++;
} }
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_E_AC3, MediaFormat.NO_VALUE, return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_E_AC3, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null, language); MediaFormat.NO_VALUE, channelCount, sampleRate, null, language);
} }
/** /**
...@@ -115,12 +113,11 @@ public final class Ac3Util { ...@@ -115,12 +113,11 @@ public final class Ac3Util {
* *
* @param data The data to parse, positioned at the start of the syncframe. * @param data The data to parse, positioned at the start of the syncframe.
* @param trackId The track identifier to set on the format, or null. * @param trackId The track identifier to set on the format, or null.
* @param durationUs The duration to set on the format, in microseconds.
* @param language The language to set on the format. * @param language The language to set on the format.
* @return The AC-3 format parsed from data in the header. * @return The AC-3 format parsed from data in the header.
*/ */
public static MediaFormat parseAc3SyncframeFormat(ParsableBitArray data, String trackId, public static MediaFormat parseAc3SyncframeFormat(ParsableBitArray data, String trackId,
long durationUs, String language) { String language) {
data.skipBits(16 + 16); // syncword, crc1 data.skipBits(16 + 16); // syncword, crc1
int fscod = data.readBits(2); int fscod = data.readBits(2);
data.skipBits(6 + 5 + 3); // frmsizecod, bsid, bsmod data.skipBits(6 + 5 + 3); // frmsizecod, bsid, bsmod
...@@ -136,7 +133,7 @@ public final class Ac3Util { ...@@ -136,7 +133,7 @@ public final class Ac3Util {
} }
boolean lfeon = data.readBit(); boolean lfeon = data.readBit();
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE, return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, durationUs, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), MediaFormat.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0),
SAMPLE_RATE_BY_FSCOD[fscod], null, language); SAMPLE_RATE_BY_FSCOD[fscod], null, language);
} }
...@@ -146,12 +143,11 @@ public final class Ac3Util { ...@@ -146,12 +143,11 @@ public final class Ac3Util {
* *
* @param data The data to parse, positioned at the start of the syncframe. * @param data The data to parse, positioned at the start of the syncframe.
* @param trackId The track identifier to set on the format, or null. * @param trackId The track identifier to set on the format, or null.
* @param durationUs The duration to set on the format, in microseconds.
* @param language The language to set on the format. * @param language The language to set on the format.
* @return The E-AC-3 format parsed from data in the header. * @return The E-AC-3 format parsed from data in the header.
*/ */
public static MediaFormat parseEac3SyncframeFormat(ParsableBitArray data, String trackId, public static MediaFormat parseEac3SyncframeFormat(ParsableBitArray data, String trackId,
long durationUs, String language) { String language) {
data.skipBits(16 + 2 + 3 + 11); // syncword, strmtype, substreamid, frmsiz data.skipBits(16 + 2 + 3 + 11); // syncword, strmtype, substreamid, frmsiz
int sampleRate; int sampleRate;
int fscod = data.readBits(2); int fscod = data.readBits(2);
...@@ -164,8 +160,8 @@ public final class Ac3Util { ...@@ -164,8 +160,8 @@ public final class Ac3Util {
int acmod = data.readBits(3); int acmod = data.readBits(3);
boolean lfeon = data.readBit(); boolean lfeon = data.readBit();
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_E_AC3, MediaFormat.NO_VALUE, return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_E_AC3, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, durationUs, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), MediaFormat.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), sampleRate, null,
sampleRate, null, language); language);
} }
/** /**
......
...@@ -53,12 +53,10 @@ public final class DtsUtil { ...@@ -53,12 +53,10 @@ public final class DtsUtil {
* *
* @param frame The DTS frame to parse. * @param frame The DTS frame to parse.
* @param trackId The track identifier to set on the format, or null. * @param trackId The track identifier to set on the format, or null.
* @param durationUs The duration to set on the format, in microseconds.
* @param language The language to set on the format. * @param language The language to set on the format.
* @return The DTS format parsed from data in the header. * @return The DTS format parsed from data in the header.
*/ */
public static MediaFormat parseDtsFormat(byte[] frame, String trackId, long durationUs, public static MediaFormat parseDtsFormat(byte[] frame, String trackId, String language) {
String language) {
ParsableBitArray frameBits = SCRATCH_BITS; ParsableBitArray frameBits = SCRATCH_BITS;
frameBits.reset(frame); frameBits.reset(frame);
frameBits.skipBits(4 * 8 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE frameBits.skipBits(4 * 8 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE
...@@ -72,7 +70,7 @@ public final class DtsUtil { ...@@ -72,7 +70,7 @@ public final class DtsUtil {
frameBits.skipBits(10); // MIX, DYNF, TIMEF, AUXF, HDCD, EXT_AUDIO_ID, EXT_AUDIO, ASPF frameBits.skipBits(10); // MIX, DYNF, TIMEF, AUXF, HDCD, EXT_AUDIO_ID, EXT_AUDIO, ASPF
channelCount += frameBits.readBits(2) > 0 ? 1 : 0; // LFF channelCount += frameBits.readBits(2) > 0 ? 1 : 0; // LFF
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_DTS, bitrate, return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_DTS, bitrate,
MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null, language); MediaFormat.NO_VALUE, channelCount, sampleRate, null, language);
} }
/** /**
......
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