One Format object to rule them all.

- Format can represent both container and sample formats.
  If a container contains a single track (as is true in
  DASH and SmoothStreaming) then the container Format can
  also contain sufficient information about the samples
  to allow for track selection. This avoids the Format to
  MediaFormat conversions that we were previously doing in
  ChunkSource implementations.

- One important result of this change is that adaptive
  format evaluation and static format selection now use the
  same format objects, which is a whole lot less confusing
  for someone who wants to implement both initial selection
  and subsequent adaptation logic. It's not in the V2 doc,
  but it may well make sense if the TrackSelector not only
  selects the tracks to enable for an adaptive playback, but
  also injects a FormatEvaluator when enabling them that will
  control the subsequent adaptive selections. That would make
  it so that all format selection logic originates from the
  same place.

- As part of this change, the adaptiveX variables are removed
  from the format object; they don't really correspond to a
  single format. This also saves on having to inject the max
  video dimensions through a bunch of classes.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=114546777
This commit is contained in:
olly 2016-02-12 10:23:21 -08:00 committed by Oliver Woodman
parent 362d0400cd
commit a7adcce018
83 changed files with 1366 additions and 1674 deletions

View File

@ -16,10 +16,10 @@
package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
import com.google.android.exoplayer.TimeRange;
import com.google.android.exoplayer.audio.AudioTrack;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.demo.player.DemoPlayer;
import com.google.android.exoplayer.util.VerboseLogUtil;

View File

@ -18,9 +18,9 @@ package com.google.android.exoplayer.demo;
import com.google.android.exoplayer.AspectRatioFrameLayout;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver;
import com.google.android.exoplayer.demo.player.DashSourceBuilder;
@ -526,15 +526,12 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
menu.findItem(player.getSelectedTrack(trackType) + ID_OFFSET).setChecked(true);
}
private static String buildTrackName(MediaFormat format) {
if (format.adaptive) {
return "auto";
}
private static String buildTrackName(Format format) {
String trackName;
if (MimeTypes.isVideo(format.mimeType)) {
if (MimeTypes.isVideo(format.sampleMimeType)) {
trackName = joinWithSeparator(joinWithSeparator(buildResolutionString(format),
buildBitrateString(format)), buildTrackIdString(format));
} else if (MimeTypes.isAudio(format.mimeType)) {
} else if (MimeTypes.isAudio(format.sampleMimeType)) {
trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format),
buildAudioPropertyString(format)), buildBitrateString(format)),
buildTrackIdString(format));
@ -545,23 +542,23 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
return trackName.length() == 0 ? "unknown" : trackName;
}
private static String buildResolutionString(MediaFormat format) {
return format.width == MediaFormat.NO_VALUE || format.height == MediaFormat.NO_VALUE
private static String buildResolutionString(Format format) {
return format.width == Format.NO_VALUE || format.height == Format.NO_VALUE
? "" : format.width + "x" + format.height;
}
private static String buildAudioPropertyString(MediaFormat format) {
return format.channelCount == MediaFormat.NO_VALUE || format.sampleRate == MediaFormat.NO_VALUE
private static String buildAudioPropertyString(Format format) {
return format.channelCount == Format.NO_VALUE || format.sampleRate == Format.NO_VALUE
? "" : format.channelCount + "ch, " + format.sampleRate + "Hz";
}
private static String buildLanguageString(MediaFormat format) {
private static String buildLanguageString(Format format) {
return TextUtils.isEmpty(format.language) || "und".equals(format.language) ? ""
: format.language;
}
private static String buildBitrateString(MediaFormat format) {
return format.bitrate == MediaFormat.NO_VALUE ? ""
private static String buildBitrateString(Format format) {
return format.bitrate == Format.NO_VALUE ? ""
: String.format(Locale.US, "%.2fMbit", format.bitrate / 1000000f);
}
@ -569,8 +566,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
return first.length() == 0 ? second : (second.length() == 0 ? first : first + ", " + second);
}
private static String buildTrackIdString(MediaFormat format) {
return format.trackId == null ? "" : " (" + format.trackId + ")";
private static String buildTrackIdString(Format format) {
return format.id == null ? "" : " (" + format.id + ")";
}
private boolean onTrackItemClick(MenuItem item, int type) {

View File

@ -18,18 +18,17 @@ package com.google.android.exoplayer.demo.player;
import com.google.android.exoplayer.CodecCounters;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecSelector;
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TimeRange;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.audio.AudioCapabilities;
import com.google.android.exoplayer.audio.AudioTrack;
import com.google.android.exoplayer.chunk.ChunkSampleSource;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.dash.DashChunkSource;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.hls.HlsSampleSource;
@ -248,7 +247,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
return player.getTrackCount(type);
}
public MediaFormat getTrackFormat(int type, int index) {
public Format getTrackFormat(int type, int index) {
return player.getTrackFormat(type, index);
}

View File

@ -0,0 +1,110 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer;
import com.google.android.exoplayer.util.Util;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.media.MediaFormat;
import junit.framework.TestCase;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Unit test for {@link Format}.
*/
public final class FormatTest extends TestCase {
public void testConversionToFrameworkMediaFormat() {
if (Util.SDK_INT < 16) {
// Test doesn't apply.
return;
}
byte[] initData1 = new byte[] {1, 2, 3};
byte[] initData2 = new byte[] {4, 5, 6};
List<byte[]> initData = new ArrayList<>();
initData.add(initData1);
initData.add(initData2);
testConversionToFrameworkMediaFormatV16(Format.createVideoSampleFormat(
null, "video/xyz", 5000, 102400, 1280, 720, 30, initData));
testConversionToFrameworkMediaFormatV16(Format.createVideoSampleFormat(
null, "video/xyz", 5000, Format.NO_VALUE, 1280, 720, 30, null));
testConversionToFrameworkMediaFormatV16(Format.createAudioSampleFormat(
null, "audio/xyz", 500, 128, 5, 44100, initData, null));
testConversionToFrameworkMediaFormatV16(Format.createAudioSampleFormat(
null, "audio/xyz", 500, Format.NO_VALUE, 5, 44100, null, null));
testConversionToFrameworkMediaFormatV16(
Format.createTextSampleFormat(null, "text/xyz", Format.NO_VALUE, "eng"));
testConversionToFrameworkMediaFormatV16(
Format.createTextSampleFormat(null, "text/xyz", Format.NO_VALUE, null));
}
@SuppressLint("InlinedApi")
@TargetApi(16)
private static void testConversionToFrameworkMediaFormatV16(Format in) {
MediaFormat out = in.getFrameworkMediaFormatV16();
assertEquals(in.sampleMimeType, out.getString(MediaFormat.KEY_MIME));
assertOptionalV16(out, MediaFormat.KEY_LANGUAGE, in.language);
assertOptionalV16(out, MediaFormat.KEY_MAX_INPUT_SIZE, in.maxInputSize);
assertOptionalV16(out, MediaFormat.KEY_WIDTH, in.width);
assertOptionalV16(out, MediaFormat.KEY_HEIGHT, in.height);
assertOptionalV16(out, MediaFormat.KEY_CHANNEL_COUNT, in.channelCount);
assertOptionalV16(out, MediaFormat.KEY_SAMPLE_RATE, in.sampleRate);
assertOptionalV16(out, MediaFormat.KEY_FRAME_RATE, in.frameRate);
for (int i = 0; i < in.initializationData.size(); i++) {
byte[] originalData = in.initializationData.get(i);
ByteBuffer frameworkBuffer = out.getByteBuffer("csd-" + i);
byte[] frameworkData = Arrays.copyOf(frameworkBuffer.array(), frameworkBuffer.limit());
assertTrue(Arrays.equals(originalData, frameworkData));
}
}
@TargetApi(16)
private static void assertOptionalV16(MediaFormat format, String key, String value) {
if (value == null) {
assertFalse(format.containsKey(key));
} else {
assertEquals(value, format.getString(key));
}
}
@TargetApi(16)
private static void assertOptionalV16(MediaFormat format, String key, int value) {
if (value == Format.NO_VALUE) {
assertFalse(format.containsKey(key));
} else {
assertEquals(value, format.getInteger(key));
}
}
@TargetApi(16)
private static void assertOptionalV16(MediaFormat format, String key, float value) {
if (value == Format.NO_VALUE) {
assertFalse(format.containsKey(key));
} else {
assertEquals(value, format.getFloat(key));
}
}
}

View File

@ -1,102 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer;
import com.google.android.exoplayer.util.Util;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import junit.framework.TestCase;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Unit test for {@link MediaFormat}.
*/
public final class MediaFormatTest extends TestCase {
public void testConversionToFrameworkFormat() {
if (Util.SDK_INT < 16) {
// Test doesn't apply.
return;
}
byte[] initData1 = new byte[] {1, 2, 3};
byte[] initData2 = new byte[] {4, 5, 6};
List<byte[]> initData = new ArrayList<>();
initData.add(initData1);
initData.add(initData2);
testConversionToFrameworkFormatV16(MediaFormat.createVideoFormat(
null, "video/xyz", 5000, 102400, 1280, 720, initData));
testConversionToFrameworkFormatV16(MediaFormat.createVideoFormat(
null, "video/xyz", 5000, MediaFormat.NO_VALUE, 1280, 720, null));
testConversionToFrameworkFormatV16(MediaFormat.createAudioFormat(
null, "audio/xyz", 500, 128, 5, 44100, initData, null));
testConversionToFrameworkFormatV16(MediaFormat.createAudioFormat(
null, "audio/xyz", 500, MediaFormat.NO_VALUE, 5, 44100, null, null));
testConversionToFrameworkFormatV16(
MediaFormat.createTextFormat(null, "text/xyz", MediaFormat.NO_VALUE, "eng"));
testConversionToFrameworkFormatV16(
MediaFormat.createTextFormat(null, "text/xyz", MediaFormat.NO_VALUE, null));
}
@SuppressLint("InlinedApi")
@TargetApi(16)
private static void testConversionToFrameworkFormatV16(MediaFormat in) {
android.media.MediaFormat out = in.getFrameworkMediaFormatV16();
assertEquals(in.mimeType, out.getString(android.media.MediaFormat.KEY_MIME));
assertOptionalV16(out, android.media.MediaFormat.KEY_LANGUAGE, in.language);
assertOptionalV16(out, android.media.MediaFormat.KEY_MAX_INPUT_SIZE, in.maxInputSize);
assertOptionalV16(out, android.media.MediaFormat.KEY_WIDTH, in.width);
assertOptionalV16(out, android.media.MediaFormat.KEY_HEIGHT, in.height);
assertOptionalV16(out, android.media.MediaFormat.KEY_CHANNEL_COUNT, in.channelCount);
assertOptionalV16(out, android.media.MediaFormat.KEY_SAMPLE_RATE, in.sampleRate);
assertOptionalV16(out, android.media.MediaFormat.KEY_MAX_WIDTH, in.maxWidth);
assertOptionalV16(out, android.media.MediaFormat.KEY_MAX_HEIGHT, in.maxHeight);
for (int i = 0; i < in.initializationData.size(); i++) {
byte[] originalData = in.initializationData.get(i);
ByteBuffer frameworkBuffer = out.getByteBuffer("csd-" + i);
byte[] frameworkData = Arrays.copyOf(frameworkBuffer.array(), frameworkBuffer.limit());
assertTrue(Arrays.equals(originalData, frameworkData));
}
}
@TargetApi(16)
private static void assertOptionalV16(android.media.MediaFormat format, String key,
String value) {
if (value == null) {
assertFalse(format.containsKey(key));
} else {
assertEquals(value, format.getString(key));
}
}
@TargetApi(16)
private static void assertOptionalV16(android.media.MediaFormat format, String key,
int value) {
if (value == MediaFormat.NO_VALUE) {
assertFalse(format.containsKey(key));
} else {
assertEquals(value, format.getInteger(key));
}
}
}

View File

@ -15,8 +15,9 @@
*/
package com.google.android.exoplayer.dash;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.testutil.TestUtil;
import com.google.android.exoplayer.util.MimeTypes;
import android.test.InstrumentationTestCase;
@ -42,12 +43,13 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
private static final int TALL_HEIGHT = 200;
private static final int WIDE_WIDTH = 400;
private static final Format REGULAR_VIDEO =
new Format("1", "video/mp4", 480, 240, -1, -1, -1, 1000);
private static final Format TALL_VIDEO =
new Format("2", "video/mp4", 100, TALL_HEIGHT, -1, -1, -1, 1000);
private static final Format WIDE_VIDEO =
new Format("3", "video/mp4", WIDE_WIDTH, 50, -1, -1, -1, 1000);
private static final Format REGULAR_VIDEO = Format.createVideoContainerFormat("1",
MimeTypes.APPLICATION_MP4, MimeTypes.VIDEO_H264, 1000, 480, 240, Format.NO_VALUE, null);
private static final Format TALL_VIDEO = Format.createVideoContainerFormat("2",
MimeTypes.APPLICATION_MP4, MimeTypes.VIDEO_H264, 1000, 100, TALL_HEIGHT, Format.NO_VALUE,
null);
private static final Format WIDE_VIDEO = Format.createVideoContainerFormat("3",
MimeTypes.APPLICATION_MP4, MimeTypes.VIDEO_H264, 1000, WIDE_WIDTH, 50, Format.NO_VALUE, null);
@Override
public void setUp() throws Exception {

View File

@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer.dash.mpd;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
import com.google.android.exoplayer.util.MimeTypes;
@ -29,11 +29,13 @@ public class RepresentationTest extends TestCase {
public void testGetCacheKey() {
String uri = "http://www.google.com";
SegmentBase base = new SingleSegmentBase(new RangedUri(uri, null, 0, 1), 1, 0, uri, 1, 1);
Format format = new Format("0", MimeTypes.VIDEO_MP4, 1920, 1080, -1, 0, 0, 2500000);
Format format = Format.createVideoContainerFormat("0", MimeTypes.APPLICATION_MP4,
MimeTypes.VIDEO_H264, 2500000, 1920, 1080, Format.NO_VALUE, null);
Representation representation = Representation.newInstance("test_stream_1", 3, format, base);
assertEquals("test_stream_1.0.3", representation.getCacheKey());
format = new Format("150", MimeTypes.VIDEO_MP4, 1920, 1080, -1, 0, 0, 2500000);
format = Format.createVideoContainerFormat("150", MimeTypes.APPLICATION_MP4,
MimeTypes.VIDEO_H264, 2500000, 1920, 1080, Format.NO_VALUE, null);
representation = Representation.newInstance("test_stream_1", -1, format, base);
assertEquals("test_stream_1.150.-1", representation.getCacheKey());
}

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.mp4;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.extractor.SeekMap;
import com.google.android.exoplayer.testutil.FakeExtractorOutput;
import com.google.android.exoplayer.testutil.FakeTrackOutput;
@ -125,12 +125,12 @@ public final class Mp4ExtractorTest extends TestCase {
// The video and audio formats are set correctly.
assertEquals(2, extractorOutput.trackOutputs.size());
MediaFormat videoFormat = extractorOutput.trackOutputs.get(0).format;
MediaFormat audioFormat = extractorOutput.trackOutputs.get(1).format;
assertEquals(MimeTypes.VIDEO_H264, videoFormat.mimeType);
Format videoFormat = extractorOutput.trackOutputs.get(0).format;
Format audioFormat = extractorOutput.trackOutputs.get(1).format;
assertEquals(MimeTypes.VIDEO_H264, videoFormat.sampleMimeType);
assertEquals(VIDEO_WIDTH, videoFormat.width);
assertEquals(VIDEO_HEIGHT, videoFormat.height);
assertEquals(MimeTypes.AUDIO_AAC, audioFormat.mimeType);
assertEquals(MimeTypes.AUDIO_AAC, audioFormat.sampleMimeType);
// The timestamps and sizes are set correctly.
FakeTrackOutput videoTrackOutput = extractorOutput.trackOutputs.get(0);
@ -170,12 +170,12 @@ public final class Mp4ExtractorTest extends TestCase {
// The video and audio formats are set correctly.
assertEquals(2, extractorOutput.trackOutputs.size());
MediaFormat videoFormat = extractorOutput.trackOutputs.get(0).format;
MediaFormat audioFormat = extractorOutput.trackOutputs.get(1).format;
assertEquals(MimeTypes.VIDEO_MP4V, videoFormat.mimeType);
Format videoFormat = extractorOutput.trackOutputs.get(0).format;
Format audioFormat = extractorOutput.trackOutputs.get(1).format;
assertEquals(MimeTypes.VIDEO_MP4V, videoFormat.sampleMimeType);
assertEquals(VIDEO_MP4V_WIDTH, videoFormat.width);
assertEquals(VIDEO_MP4V_HEIGHT, videoFormat.height);
assertEquals(MimeTypes.AUDIO_AAC, audioFormat.mimeType);
assertEquals(MimeTypes.AUDIO_AAC, audioFormat.sampleMimeType);
// The timestamps and sizes are set correctly.
FakeTrackOutput videoTrackOutput = extractorOutput.trackOutputs.get(0);

View File

@ -18,7 +18,7 @@ package com.google.android.exoplayer.extractor.webm;
import static com.google.android.exoplayer.extractor.webm.StreamBuilder.TEST_ENCRYPTION_KEY_ID;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData;
@ -722,24 +722,24 @@ public final class WebmExtractorTest extends InstrumentationTestCase {
}
private void assertVp9VideoFormat(int trackNumber) {
MediaFormat format = getTrackOutput(trackNumber).format;
Format format = getTrackOutput(trackNumber).format;
assertEquals(TEST_WIDTH, format.width);
assertEquals(TEST_HEIGHT, format.height);
assertEquals(MimeTypes.VIDEO_VP9, format.mimeType);
assertEquals(MimeTypes.VIDEO_VP9, format.sampleMimeType);
}
private void assertH264VideoFormat(int trackNumber) {
MediaFormat format = getTrackOutput(trackNumber).format;
Format format = getTrackOutput(trackNumber).format;
assertEquals(TEST_WIDTH, format.width);
assertEquals(TEST_HEIGHT, format.height);
assertEquals(MimeTypes.VIDEO_H264, format.mimeType);
assertEquals(MimeTypes.VIDEO_H264, format.sampleMimeType);
}
private void assertAudioFormat(int trackNumber, String expectedMimeType) {
MediaFormat format = getTrackOutput(trackNumber).format;
Format format = getTrackOutput(trackNumber).format;
assertEquals(TEST_CHANNEL_COUNT, format.channelCount);
assertEquals(TEST_SAMPLE_RATE, format.sampleRate);
assertEquals(expectedMimeType, format.mimeType);
assertEquals(expectedMimeType, format.sampleMimeType);
if (MimeTypes.AUDIO_OPUS.equals(expectedMimeType)) {
assertEquals(3, format.initializationData.size());
android.test.MoreAsserts.assertEquals(TEST_OPUS_CODEC_PRIVATE,

View File

@ -61,32 +61,32 @@ public class HlsMasterPlaylistParserTest extends TestCase {
assertEquals(5, variants.size());
assertEquals(1280000, variants.get(0).format.bitrate);
assertNotNull(variants.get(0).format.codecs);
assertEquals("mp4a.40.2,avc1.66.30", variants.get(0).format.codecs);
assertNotNull(variants.get(0).codecs);
assertEquals("mp4a.40.2,avc1.66.30", variants.get(0).codecs);
assertEquals(304, variants.get(0).format.width);
assertEquals(128, variants.get(0).format.height);
assertEquals("http://example.com/low.m3u8", variants.get(0).url);
assertEquals(1280000, variants.get(1).format.bitrate);
assertNotNull(variants.get(1).format.codecs);
assertEquals("mp4a.40.2 , avc1.66.30 ", variants.get(1).format.codecs);
assertNotNull(variants.get(1).codecs);
assertEquals("mp4a.40.2 , avc1.66.30 ", variants.get(1).codecs);
assertEquals("http://example.com/spaces_in_codecs.m3u8", variants.get(1).url);
assertEquals(2560000, variants.get(2).format.bitrate);
assertEquals(null, variants.get(2).format.codecs);
assertEquals(null, variants.get(2).codecs);
assertEquals(384, variants.get(2).format.width);
assertEquals(160, variants.get(2).format.height);
assertEquals("http://example.com/mid.m3u8", variants.get(2).url);
assertEquals(7680000, variants.get(3).format.bitrate);
assertEquals(null, variants.get(3).format.codecs);
assertEquals(null, variants.get(3).codecs);
assertEquals(-1, variants.get(3).format.width);
assertEquals(-1, variants.get(3).format.height);
assertEquals("http://example.com/hi.m3u8", variants.get(3).url);
assertEquals(65000, variants.get(4).format.bitrate);
assertNotNull(variants.get(4).format.codecs);
assertEquals("mp4a.40.5", variants.get(4).format.codecs);
assertNotNull(variants.get(4).codecs);
assertEquals("mp4a.40.5", variants.get(4).codecs);
assertEquals(-1, variants.get(4).format.width);
assertEquals(-1, variants.get(4).format.height);
assertEquals("http://example.com/audio-only.m3u8", variants.get(4).url);

View File

@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer.testutil;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.ParsableByteArray;
@ -40,7 +40,7 @@ public final class FakeTrackOutput implements TrackOutput {
private final ArrayList<byte[]> sampleEncryptionKeys;
private byte[] sampleData;
public MediaFormat format;
public Format format;
public FakeTrackOutput() {
sampleData = new byte[0];
@ -52,7 +52,7 @@ public final class FakeTrackOutput implements TrackOutput {
}
@Override
public void format(MediaFormat format) {
public void format(Format format) {
this.format = format;
}

View File

@ -21,7 +21,7 @@ package com.google.android.exoplayer;
public final class DummyTrackRenderer extends TrackRenderer {
@Override
protected int supportsFormat(MediaFormat mediaFormat) throws ExoPlaybackException {
protected int supportsFormat(Format format) throws ExoPlaybackException {
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}

View File

@ -286,7 +286,7 @@ public interface ExoPlayer {
* @param trackIndex The index of the track.
* @return The format of the track.
*/
MediaFormat getTrackFormat(int rendererIndex, int trackIndex);
Format getTrackFormat(int rendererIndex, int trackIndex);
/**
* Selects a track for the specified renderer.

View File

@ -36,7 +36,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
private final Handler eventHandler;
private final ExoPlayerImplInternal internalPlayer;
private final CopyOnWriteArraySet<Listener> listeners;
private final MediaFormat[][] trackFormats;
private final Format[][] trackFormats;
private final int[] selectedTrackIndices;
private boolean playWhenReady;
@ -61,7 +61,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
this.playWhenReady = false;
this.playbackState = STATE_IDLE;
this.listeners = new CopyOnWriteArraySet<>();
this.trackFormats = new MediaFormat[renderers.length][];
this.trackFormats = new Format[renderers.length][];
this.selectedTrackIndices = new int[renderers.length];
eventHandler = new Handler() {
@Override
@ -105,7 +105,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
@Override
public MediaFormat getTrackFormat(int rendererIndex, int trackIndex) {
public Format getTrackFormat(int rendererIndex, int trackIndex) {
return trackFormats[rendererIndex][trackIndex];
}

View File

@ -75,9 +75,10 @@ import java.util.concurrent.atomic.AtomicInteger;
private final long minBufferUs;
private final long minRebufferUs;
private final List<TrackRenderer> enabledRenderers;
private final int[] selectedTrackIndices;
private final int[][] groupIndices;
private final int[][][] trackIndices;
private final int[] selectedTrackIndices;
private final Format[][][] trackFormats;
private final Handler handler;
private final HandlerThread internalPlaybackThread;
private final Handler eventHandler;
@ -127,6 +128,7 @@ import java.util.concurrent.atomic.AtomicInteger;
pendingSeekCount = new AtomicInteger();
enabledRenderers = new ArrayList<>(renderers.length);
groupIndices = new int[renderers.length][];
trackFormats = new Format[renderers.length][][];
trackIndices = new int[renderers.length][][];
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
// not normally change to this priority" is incorrect.
@ -310,13 +312,14 @@ import java.util.concurrent.atomic.AtomicInteger;
maxTrackCount += source.getTrackGroup(groupIndex).length;
}
// Construct tracks for each renderer.
MediaFormat[][] trackFormats = new MediaFormat[renderers.length][];
Format[][] externalTrackFormats = new Format[renderers.length][];
for (int rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) {
TrackRenderer renderer = renderers[rendererIndex];
int rendererTrackCount = 0;
Format[] rendererExternalTrackFormats = new Format[maxTrackCount];
int[] rendererTrackGroups = new int[maxTrackCount];
int[][] rendererTrackIndices = new int[maxTrackCount][];
MediaFormat[] rendererTrackFormats = new MediaFormat[maxTrackCount];
Format[][] rendererTrackFormats = new Format[maxTrackCount][];
for (int groupIndex = 0; groupIndex < source.getTrackGroupCount(); groupIndex++) {
TrackGroup trackGroup = source.getTrackGroup(groupIndex);
// TODO[REFACTOR]: This should check that the renderer is capable of adaptive playback, in
@ -325,14 +328,12 @@ import java.util.concurrent.atomic.AtomicInteger;
// Try and build an adaptive track.
int adaptiveTrackIndexCount = 0;
int[] adaptiveTrackIndices = new int[trackGroup.length];
MediaFormat adaptiveTrackFormat = null;
Format[] adaptiveTrackFormats = new Format[trackGroup.length];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
MediaFormat trackFormat = source.getTrackGroup(groupIndex).getFormat(trackIndex);
Format trackFormat = source.getTrackGroup(groupIndex).getFormat(trackIndex);
if (renderer.supportsFormat(trackFormat) == TrackRenderer.FORMAT_HANDLED) {
adaptiveTrackIndices[adaptiveTrackIndexCount++] = trackIndex;
if (adaptiveTrackFormat == null) {
adaptiveTrackFormat = trackFormat.copyAsAdaptive("auto");
}
adaptiveTrackIndices[adaptiveTrackIndexCount] = trackIndex;
adaptiveTrackFormats[adaptiveTrackIndexCount++] = trackFormat;
}
}
if (adaptiveTrackIndexCount > 1) {
@ -340,21 +341,27 @@ import java.util.concurrent.atomic.AtomicInteger;
rendererTrackGroups[rendererTrackCount] = groupIndex;
rendererTrackIndices[rendererTrackCount] =
Arrays.copyOf(adaptiveTrackIndices, adaptiveTrackIndexCount);
rendererTrackFormats[rendererTrackCount++] = adaptiveTrackFormat;
rendererTrackFormats[rendererTrackCount] =
Arrays.copyOf(adaptiveTrackFormats, adaptiveTrackIndexCount);
rendererExternalTrackFormats[rendererTrackCount++] = Format.createSampleFormat(
"auto", adaptiveTrackFormats[0].sampleMimeType, Format.NO_VALUE);
}
}
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
MediaFormat trackFormat = source.getTrackGroup(groupIndex).getFormat(trackIndex);
Format trackFormat = source.getTrackGroup(groupIndex).getFormat(trackIndex);
if (renderer.supportsFormat(trackFormat) == TrackRenderer.FORMAT_HANDLED) {
rendererTrackGroups[rendererTrackCount] = groupIndex;
rendererTrackIndices[rendererTrackCount] = new int[] {trackIndex};
rendererTrackFormats[rendererTrackCount++] = trackFormat;
rendererTrackFormats[rendererTrackCount] = new Format[] {trackFormat};
rendererExternalTrackFormats[rendererTrackCount++] = trackFormat;
}
}
}
groupIndices[rendererIndex] = Arrays.copyOf(rendererTrackGroups, rendererTrackCount);
trackIndices[rendererIndex] = Arrays.copyOf(rendererTrackIndices, rendererTrackCount);
trackFormats[rendererIndex] = Arrays.copyOf(rendererTrackFormats, rendererTrackCount);
externalTrackFormats[rendererIndex] = Arrays.copyOf(rendererExternalTrackFormats,
rendererTrackCount);
}
// Enable renderers where appropriate.
@ -364,7 +371,7 @@ import java.util.concurrent.atomic.AtomicInteger;
if (0 <= trackIndex && trackIndex < trackIndices[rendererIndex].length) {
TrackStream trackStream = source.enable(groupIndices[rendererIndex][trackIndex],
trackIndices[rendererIndex][trackIndex], positionUs);
renderer.enable(trackStream, positionUs, false);
renderer.enable(trackFormats[rendererIndex][trackIndex], trackStream, positionUs, false);
enabledRenderers.add(renderer);
allRenderersEnded = allRenderersEnded && renderer.isEnded();
allRenderersReadyOrEnded = allRenderersReadyOrEnded && isReadyOrEnded(renderer);
@ -381,7 +388,7 @@ import java.util.concurrent.atomic.AtomicInteger;
// Fire an event indicating that the player has been prepared, passing the initial state and
// renderer track information.
eventHandler.obtainMessage(MSG_PREPARED, state, 0, trackFormats).sendToTarget();
eventHandler.obtainMessage(MSG_PREPARED, state, 0, externalTrackFormats).sendToTarget();
// Start the renderers if required, and schedule the first piece of work.
if (playWhenReady && state == ExoPlayer.STATE_READY) {
@ -642,7 +649,7 @@ import java.util.concurrent.atomic.AtomicInteger;
boolean joining = !isEnabled && playing;
TrackStream trackStream = source.enable(groupIndices[rendererIndex][trackIndex],
trackIndices[rendererIndex][trackIndex], positionUs);
renderer.enable(trackStream, positionUs, joining);
renderer.enable(trackFormats[rendererIndex][trackIndex], trackStream, positionUs, joining);
enabledRenderers.add(renderer);
if (playing) {
renderer.start();

View File

@ -0,0 +1,386 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer;
import com.google.android.exoplayer.util.Util;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.media.MediaFormat;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Representation of a media format.
*/
public final class Format {
/**
* Sorts {@link Format} objects in order of decreasing bandwidth.
*/
public static final class DecreasingBandwidthComparator implements Comparator<Format> {
@Override
public int compare(Format a, Format b) {
return b.bitrate - a.bitrate;
}
}
public static final int NO_VALUE = -1;
/**
* A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to
* the timestamps of their parent samples.
*/
public static final long OFFSET_SAMPLE_RELATIVE = Long.MAX_VALUE;
/**
* An identifier for the format, or null if unknown or not applicable.
*/
public final String id;
/**
* The average bandwidth in bits per second, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final int bitrate;
// Container specific.
/**
* The mime type of the container, or null if unknown or not applicable.
*/
public final String containerMimeType;
// Elementary stream specific.
/**
* The mime type of the elementary stream (i.e. the individual samples), or null if unknown or not
* applicable.
*/
public final String sampleMimeType;
/**
* The maximum size of a buffer of data (typically one sample), or {@link #NO_VALUE} if unknown or
* not applicable.
*/
public final int maxInputSize;
/**
* Whether the decoder is required to support secure decryption.
*/
public final boolean requiresSecureDecryption;
/**
* Initialization data that must be provided to the decoder. Will not be null, but may be empty
* if initialization data is not required.
*/
public final List<byte[]> initializationData;
// Video specific.
/**
* The width of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final int width;
/**
* The height of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final int height;
/**
* The frame rate in frames per second, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final float frameRate;
/**
* The clockwise rotation that should be applied to the video for it to be rendered in the correct
* orientation, or {@link #NO_VALUE} if unknown or not applicable. Only 0, 90, 180 and 270 are
* supported.
*/
public final int rotationDegrees;
/**
* The width to height ratio of pixels in the video, or {@link #NO_VALUE} if unknown or not
* applicable.
*/
public final float pixelWidthHeightRatio;
// Audio specific.
/**
* The number of audio channels, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final int channelCount;
/**
* The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final int sampleRate;
// Text specific.
/**
* For samples that contain subsamples, this is an offset that should be added to subsample
* timestamps. A value of {@link #OFFSET_SAMPLE_RELATIVE} indicates that subsample timestamps are
* relative to the timestamps of their parent samples.
*/
public final long subsampleOffsetUs;
// Audio and text specific.
/**
* The language, or null if unknown or not applicable.
*/
public final String language;
// Lazy-initialized hashcode and framework media format.
private int hashCode;
private MediaFormat frameworkMediaFormat;
// Video.
public static Format createVideoContainerFormat(String id, String containerMimeType,
String sampleMimeType, int bitrate, int width, int height, float frameRate,
List<byte[]> initializationData) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, width, height,
frameRate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE,
initializationData, false);
}
public static Format createVideoSampleFormat(String id, String sampleMimeType, int bitrate,
int maxInputSize, int width, int height, float frameRate, List<byte[]> initializationData) {
return createVideoSampleFormat(id, sampleMimeType, bitrate, maxInputSize, width, height,
frameRate, initializationData, NO_VALUE, NO_VALUE);
}
public static Format createVideoSampleFormat(String id, String sampleMimeType, int bitrate,
int maxInputSize, int width, int height, float frameRate, List<byte[]> initializationData,
int rotationDegrees, float pixelWidthHeightRatio) {
return new Format(id, null, sampleMimeType, bitrate, maxInputSize, width, height, frameRate,
rotationDegrees, pixelWidthHeightRatio, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE,
initializationData, false);
}
// Audio.
public static Format createAudioContainerFormat(String id, String containerMimeType,
String sampleMimeType, int bitrate, int channelCount, int sampleRate,
List<byte[]> initializationData, String language) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, channelCount, sampleRate, language, OFFSET_SAMPLE_RELATIVE,
initializationData, false);
}
public static Format createAudioSampleFormat(String id, String sampleMimeType, int bitrate,
int maxInputSize, int channelCount, int sampleRate, List<byte[]> initializationData,
String language) {
return new Format(id, null, sampleMimeType, bitrate, maxInputSize, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, channelCount, sampleRate, language, OFFSET_SAMPLE_RELATIVE,
initializationData, false);
}
// Text.
public static Format createTextContainerFormat(String id, String containerMimeType,
String sampleMimeType, int bitrate, String language) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language, OFFSET_SAMPLE_RELATIVE, null,
false);
}
public static Format createTextSampleFormat(String id, String sampleMimeType, int bitrate,
String language) {
return createTextSampleFormat(id, sampleMimeType, bitrate, language, OFFSET_SAMPLE_RELATIVE);
}
public static Format createTextSampleFormat(String id, String sampleMimeType, int bitrate,
String language, long subsampleOffsetUs) {
return new Format(id, null, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language, subsampleOffsetUs, null, false);
}
// Generic.
public static Format createContainerFormat(String id, String containerMimeType,
String sampleMimeType, int bitrate) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null,
false);
}
public static Format createSampleFormat(String id, String sampleMimeType, int bitrate) {
return new Format(id, null, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null, false);
}
/* package */ Format(String id, String containerMimeType, String sampleMimeType,
int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees,
float pixelWidthHeightRatio, int channelCount, int sampleRate, String language,
long subsampleOffsetUs, List<byte[]> initializationData, boolean requiresSecureDecryption) {
this.id = id;
this.containerMimeType = containerMimeType;
this.sampleMimeType = sampleMimeType;
this.bitrate = bitrate;
this.maxInputSize = maxInputSize;
this.width = width;
this.height = height;
this.frameRate = frameRate;
this.rotationDegrees = rotationDegrees;
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
this.channelCount = channelCount;
this.sampleRate = sampleRate;
this.language = language;
this.subsampleOffsetUs = subsampleOffsetUs;
this.initializationData = initializationData == null ? Collections.<byte[]>emptyList()
: initializationData;
this.requiresSecureDecryption = requiresSecureDecryption;
}
public Format copyWithMaxInputSize(int maxInputSize) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
language, subsampleOffsetUs, initializationData, requiresSecureDecryption);
}
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
language, subsampleOffsetUs, initializationData, requiresSecureDecryption);
}
public Format copyWithContainerInfo(String id, int bitrate, int width, int height,
String language) {
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
language, subsampleOffsetUs, initializationData, requiresSecureDecryption);
}
/**
* @return A {@link MediaFormat} representation of this format.
*/
@SuppressLint("InlinedApi")
@TargetApi(16)
public final MediaFormat getFrameworkMediaFormatV16() {
if (frameworkMediaFormat == null) {
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, sampleMimeType);
maybeSetStringV16(format, MediaFormat.KEY_LANGUAGE, language);
maybeSetIntegerV16(format, MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
maybeSetIntegerV16(format, MediaFormat.KEY_WIDTH, width);
maybeSetIntegerV16(format, MediaFormat.KEY_HEIGHT, height);
maybeSetFloatV16(format, MediaFormat.KEY_FRAME_RATE, frameRate);
maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees);
maybeSetIntegerV16(format, MediaFormat.KEY_CHANNEL_COUNT, channelCount);
maybeSetIntegerV16(format, MediaFormat.KEY_SAMPLE_RATE, sampleRate);
for (int i = 0; i < initializationData.size(); i++) {
format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i)));
}
frameworkMediaFormat = format;
}
return frameworkMediaFormat;
}
/**
* Sets the {@link MediaFormat} returned by {@link #getFrameworkMediaFormatV16()}.
*
* @deprecated This method only exists for FrameworkSampleSource, which is itself deprecated.
* @param frameworkSampleFormat The format.
*/
@Deprecated
@TargetApi(16)
/* package */ final void setFrameworkMediaFormatV16(MediaFormat frameworkSampleFormat) {
this.frameworkMediaFormat = frameworkSampleFormat;
}
@Override
public String toString() {
return "Format(" + id + ", " + containerMimeType + ", " + sampleMimeType + ", " + bitrate + ", "
+ maxInputSize + ", " + language + ", [" + width + ", " + height + ", " + frameRate + ", "
+ rotationDegrees + ", " + pixelWidthHeightRatio + "]" + ", [" + channelCount + ", "
+ sampleRate + "])";
}
@Override
public int hashCode() {
if (hashCode == 0) {
int result = 17;
result = 31 * result + (id == null ? 0 : id.hashCode());
result = 31 * result + (containerMimeType == null ? 0 : containerMimeType.hashCode());
result = 31 * result + (sampleMimeType == null ? 0 : sampleMimeType.hashCode());
result = 31 * result + bitrate;
result = 31 * result + width;
result = 31 * result + height;
result = 31 * result + channelCount;
result = 31 * result + sampleRate;
result = 31 * result + (language == null ? 0 : language.hashCode());
hashCode = result;
}
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Format other = (Format) obj;
if (bitrate != other.bitrate || maxInputSize != other.maxInputSize
|| requiresSecureDecryption != other.requiresSecureDecryption
|| width != other.width || height != other.height || frameRate != other.frameRate
|| rotationDegrees != other.rotationDegrees
|| pixelWidthHeightRatio != other.pixelWidthHeightRatio
|| channelCount != other.channelCount || sampleRate != other.sampleRate
|| subsampleOffsetUs != other.subsampleOffsetUs
|| !Util.areEqual(id, other.id) || !Util.areEqual(language, other.language)
|| !Util.areEqual(containerMimeType, other.containerMimeType)
|| !Util.areEqual(sampleMimeType, other.sampleMimeType)
|| initializationData.size() != other.initializationData.size()) {
return false;
}
for (int i = 0; i < initializationData.size(); i++) {
if (!Arrays.equals(initializationData.get(i), other.initializationData.get(i))) {
return false;
}
}
return true;
}
@TargetApi(16)
private static final void maybeSetStringV16(MediaFormat format, String key,
String value) {
if (value != null) {
format.setString(key, value);
}
}
@TargetApi(16)
private static final void maybeSetIntegerV16(MediaFormat format, String key,
int value) {
if (value != NO_VALUE) {
format.setInteger(key, value);
}
}
@TargetApi(16)
private static final void maybeSetFloatV16(MediaFormat format, String key,
float value) {
if (value != NO_VALUE) {
format.setFloat(key, value);
}
}
}

View File

@ -18,14 +18,14 @@ package com.google.android.exoplayer;
import com.google.android.exoplayer.drm.DrmInitData;
/**
* Holds a {@link MediaFormat} and corresponding drm scheme initialization data.
* Holds a {@link Format} and corresponding drm scheme initialization data.
*/
public final class MediaFormatHolder {
public final class FormatHolder {
/**
* The format of the media.
*/
public MediaFormat format;
public Format format;
/**
* Initialization data for drm schemes supported by the media. Null if the media is not encrypted.
*/

View File

@ -27,6 +27,7 @@ import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.net.Uri;
import java.io.FileDescriptor;
@ -134,11 +135,11 @@ public final class FrameworkSampleSource implements SampleSource {
pendingResets = new boolean[trackStates.length];
tracks = new TrackGroup[trackStates.length];
for (int i = 0; i < trackStates.length; i++) {
android.media.MediaFormat format = extractor.getTrackFormat(i);
if (format.containsKey(android.media.MediaFormat.KEY_DURATION)) {
durationUs = Math.max(durationUs, format.getLong(android.media.MediaFormat.KEY_DURATION));
MediaFormat format = extractor.getTrackFormat(i);
if (format.containsKey(MediaFormat.KEY_DURATION)) {
durationUs = Math.max(durationUs, format.getLong(MediaFormat.KEY_DURATION));
}
tracks[i] = new TrackGroup(createMediaFormat(format));
tracks[i] = new TrackGroup(createFormat(i, format));
}
prepared = true;
return true;
@ -188,7 +189,7 @@ public final class FrameworkSampleSource implements SampleSource {
return TrackStream.NO_RESET;
}
/* package */ int readData(int track, MediaFormatHolder formatHolder, SampleHolder sampleHolder) {
/* package */ int readData(int track, FormatHolder formatHolder, SampleHolder sampleHolder) {
Assertions.checkState(prepared);
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
if (pendingResets[track]) {
@ -296,39 +297,50 @@ public final class FrameworkSampleSource implements SampleSource {
}
@SuppressLint("InlinedApi")
private static MediaFormat createMediaFormat(android.media.MediaFormat format) {
String mimeType = format.getString(android.media.MediaFormat.KEY_MIME);
String language = getOptionalStringV16(format, android.media.MediaFormat.KEY_LANGUAGE);
int maxInputSize = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE);
int width = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_WIDTH);
int height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT);
int rotationDegrees = getOptionalIntegerV16(format, "rotation-degrees");
int channelCount = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT);
int sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE);
private static Format createFormat(int index, MediaFormat mediaFormat) {
String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
String language = getOptionalStringV16(mediaFormat, MediaFormat.KEY_LANGUAGE);
int maxInputSize = getOptionalIntegerV16(mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE);
int width = getOptionalIntegerV16(mediaFormat, MediaFormat.KEY_WIDTH);
int height = getOptionalIntegerV16(mediaFormat, MediaFormat.KEY_HEIGHT);
float frameRate;
try {
frameRate = getOptionalIntegerV16(mediaFormat, MediaFormat.KEY_FRAME_RATE);
} catch (ClassCastException e) {
// There's an entry for KEY_FRAME_RATE but it's not a integer. It must be a float.
frameRate = getOptionalFloatV16(mediaFormat, MediaFormat.KEY_FRAME_RATE);
}
int rotationDegrees = getOptionalIntegerV16(mediaFormat, "rotation-degrees");
int channelCount = getOptionalIntegerV16(mediaFormat, MediaFormat.KEY_CHANNEL_COUNT);
int sampleRate = getOptionalIntegerV16(mediaFormat, MediaFormat.KEY_SAMPLE_RATE);
ArrayList<byte[]> initializationData = new ArrayList<>();
for (int i = 0; format.containsKey("csd-" + i); i++) {
ByteBuffer buffer = format.getByteBuffer("csd-" + i);
for (int i = 0; mediaFormat.containsKey("csd-" + i); i++) {
ByteBuffer buffer = mediaFormat.getByteBuffer("csd-" + i);
byte[] data = new byte[buffer.limit()];
buffer.get(data);
initializationData.add(data);
buffer.flip();
}
MediaFormat mediaFormat = new MediaFormat(null, mimeType, MediaFormat.NO_VALUE, maxInputSize,
width, height, rotationDegrees, MediaFormat.NO_VALUE, channelCount, sampleRate, language,
MediaFormat.OFFSET_SAMPLE_RELATIVE, initializationData, false, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE);
mediaFormat.setFrameworkFormatV16(format);
return mediaFormat;
Format format = new Format(Integer.toString(index), null, mimeType, Format.NO_VALUE,
maxInputSize, width, height, frameRate, rotationDegrees, Format.NO_VALUE, channelCount,
sampleRate, language, Format.OFFSET_SAMPLE_RELATIVE, initializationData, false);
format.setFrameworkMediaFormatV16(mediaFormat);
return format;
}
@TargetApi(16)
private static final String getOptionalStringV16(android.media.MediaFormat format, String key) {
private static String getOptionalStringV16(MediaFormat format, String key) {
return format.containsKey(key) ? format.getString(key) : null;
}
@TargetApi(16)
private static final int getOptionalIntegerV16(android.media.MediaFormat format, String key) {
return format.containsKey(key) ? format.getInteger(key) : MediaFormat.NO_VALUE;
private static int getOptionalIntegerV16(MediaFormat format, String key) {
return format.containsKey(key) ? format.getInteger(key) : Format.NO_VALUE;
}
@TargetApi(16)
private static float getOptionalFloatV16(MediaFormat format, String key) {
return format.containsKey(key) ? format.getFloat(key) : Format.NO_VALUE;
}
private final class TrackStreamImpl implements TrackStream {
@ -359,7 +371,7 @@ public final class FrameworkSampleSource implements SampleSource {
}
@Override
public int readData(MediaFormatHolder formatHolder, SampleHolder sampleHolder) {
public int readData(FormatHolder formatHolder, SampleHolder sampleHolder) {
return FrameworkSampleSource.this.readData(track, formatHolder, sampleHolder);
}

View File

@ -26,6 +26,7 @@ import android.annotation.TargetApi;
import android.media.AudioManager;
import android.media.MediaCodec;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.media.PlaybackParams;
import android.media.audiofx.Virtualizer;
import android.os.Handler;
@ -181,30 +182,30 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
}
@Override
protected int supportsFormat(MediaCodecSelector mediaCodecSelector, MediaFormat mediaFormat)
protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format)
throws DecoderQueryException {
String mimeType = mediaFormat.mimeType;
String mimeType = format.sampleMimeType;
if (!MimeTypes.isAudio(mimeType)) {
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}
if (allowPassthrough(mimeType) && mediaCodecSelector.getPassthroughDecoderName() != null) {
return TrackRenderer.FORMAT_HANDLED;
}
// TODO[REFACTOR]: Propagate requiresSecureDecoder to this point. Note that we need to check
// that the drm session can make use of a secure decoder, as well as that a secure decoder
// exists.
DecoderInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mediaFormat.mimeType, false);
// TODO[REFACTOR]: If requiresSecureDecryption then we should probably also check that the
// drmSession is able to make use of a secure decoder.
DecoderInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType,
format.requiresSecureDecryption);
if (decoderInfo == null) {
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}
if (Util.SDK_INT >= 21) {
// Note: We assume support in the case that the sampleRate or channelCount is unknown.
if (mediaFormat.sampleRate != MediaFormat.NO_VALUE
&& !decoderInfo.isAudioSampleRateSupportedV21(mediaFormat.sampleRate)) {
if (format.sampleRate != Format.NO_VALUE
&& !decoderInfo.isAudioSampleRateSupportedV21(format.sampleRate)) {
return TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
}
if (mediaFormat.channelCount != MediaFormat.NO_VALUE
&& !decoderInfo.isAudioChannelCountSupportedV21(mediaFormat.channelCount)) {
if (format.channelCount != Format.NO_VALUE
&& !decoderInfo.isAudioChannelCountSupportedV21(format.channelCount)) {
return TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
}
}
@ -212,9 +213,9 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
}
@Override
protected DecoderInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, MediaFormat format,
protected DecoderInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, Format format,
boolean requiresSecureDecoder) throws DecoderQueryException {
if (allowPassthrough(format.mimeType)) {
if (allowPassthrough(format.sampleMimeType)) {
String passthroughDecoderName = mediaCodecSelector.getPassthroughDecoderName();
if (passthroughDecoderName != null) {
passthroughEnabled = true;
@ -238,13 +239,13 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
}
@Override
protected void configureCodec(MediaCodec codec, MediaFormat format, MediaCrypto crypto) {
protected void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto) {
if (passthroughEnabled) {
// Override the MIME type used to configure the codec if we are using a passthrough decoder.
passthroughMediaFormat = format.getFrameworkMediaFormatV16();
passthroughMediaFormat.setString(android.media.MediaFormat.KEY_MIME, MimeTypes.AUDIO_RAW);
passthroughMediaFormat.setString(MediaFormat.KEY_MIME, MimeTypes.AUDIO_RAW);
codec.configure(passthroughMediaFormat, null, crypto, 0);
passthroughMediaFormat.setString(android.media.MediaFormat.KEY_MIME, format.mimeType);
passthroughMediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType);
} else {
codec.configure(format.getFrameworkMediaFormatV16(), null, crypto, 0);
passthroughMediaFormat = null;
@ -257,7 +258,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
}
@Override
protected void onOutputFormatChanged(android.media.MediaFormat outputFormat) {
protected void onOutputFormatChanged(MediaFormat outputFormat) {
boolean passthrough = passthroughMediaFormat != null;
audioTrack.configure(passthrough ? passthroughMediaFormat : outputFormat, passthrough);
}

View File

@ -28,6 +28,7 @@ import android.media.MediaCodec;
import android.media.MediaCodec.CodecException;
import android.media.MediaCodec.CryptoException;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.os.Handler;
import android.os.SystemClock;
@ -102,19 +103,19 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
*/
public final String diagnosticInfo;
public DecoderInitializationException(MediaFormat mediaFormat, Throwable cause,
public DecoderInitializationException(Format format, Throwable cause,
boolean secureDecoderRequired, int errorCode) {
super("Decoder init failed: [" + errorCode + "], " + mediaFormat, cause);
this.mimeType = mediaFormat.mimeType;
super("Decoder init failed: [" + errorCode + "], " + format, cause);
this.mimeType = format.sampleMimeType;
this.secureDecoderRequired = secureDecoderRequired;
this.decoderName = null;
this.diagnosticInfo = buildCustomDiagnosticInfo(errorCode);
}
public DecoderInitializationException(MediaFormat mediaFormat, Throwable cause,
public DecoderInitializationException(Format format, Throwable cause,
boolean secureDecoderRequired, String decoderName) {
super("Decoder init failed: " + decoderName + ", " + mediaFormat, cause);
this.mimeType = mediaFormat.mimeType;
super("Decoder init failed: " + decoderName + ", " + format, cause);
this.mimeType = format.sampleMimeType;
this.secureDecoderRequired = secureDecoderRequired;
this.decoderName = decoderName;
this.diagnosticInfo = Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null;
@ -199,13 +200,13 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
private final DrmSessionManager drmSessionManager;
private final boolean playClearSamplesWithoutKeys;
private final SampleHolder sampleHolder;
private final MediaFormatHolder formatHolder;
private final FormatHolder formatHolder;
private final List<Long> decodeOnlyPresentationTimestamps;
private final MediaCodec.BufferInfo outputBufferInfo;
private final EventListener eventListener;
protected final Handler eventHandler;
private MediaFormat format;
private Format format;
private DrmInitData drmInitData;
private MediaCodec codec;
private boolean codecIsAdaptive;
@ -254,7 +255,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
this.eventListener = eventListener;
codecCounters = new CodecCounters();
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED);
formatHolder = new MediaFormatHolder();
formatHolder = new FormatHolder();
decodeOnlyPresentationTimestamps = new ArrayList<>();
outputBufferInfo = new MediaCodec.BufferInfo();
codecReconfigurationState = RECONFIGURATION_STATE_NONE;
@ -277,9 +278,9 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
}
@Override
protected final int supportsFormat(MediaFormat mediaFormat) throws ExoPlaybackException {
protected final int supportsFormat(Format format) throws ExoPlaybackException {
try {
return supportsFormat(mediaCodecSelector, mediaFormat);
return supportsFormat(mediaCodecSelector, format);
} catch (DecoderQueryException e) {
throw new ExoPlaybackException(e);
}
@ -289,28 +290,28 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
* Returns the extent to which the renderer is capable of rendering a given format.
*
* @param mediaCodecSelector The decoder selector.
* @param mediaFormat The format.
* @param format The format.
* @return The extent to which the renderer is capable of rendering the given format. One of
* {@link #FORMAT_HANDLED}, {@link #FORMAT_EXCEEDS_CAPABILITIES} and
* {@link #FORMAT_UNSUPPORTED_TYPE}.
* @throws DecoderQueryException If there was an error querying decoders.
*/
protected abstract int supportsFormat(MediaCodecSelector mediaCodecSelector,
MediaFormat mediaFormat) throws DecoderQueryException;
protected abstract int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format)
throws DecoderQueryException;
/**
* Returns a {@link DecoderInfo} for a given format.
*
* @param mediaCodecSelector The decoder selector.
* @param mediaFormat The format for which a decoder is required.
* @param format The format for which a decoder is required.
* @param requiresSecureDecoder Whether a secure decoder is required.
* @return A {@link DecoderInfo} describing the decoder to instantiate, or null if no suitable
* decoder exists.
* @throws DecoderQueryException Thrown if there was an error querying decoders.
*/
protected DecoderInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector,
MediaFormat mediaFormat, boolean requiresSecureDecoder) throws DecoderQueryException {
return mediaCodecSelector.getDecoderInfo(format.mimeType, requiresSecureDecoder);
protected DecoderInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, Format format,
boolean requiresSecureDecoder) throws DecoderQueryException {
return mediaCodecSelector.getDecoderInfo(format.sampleMimeType, requiresSecureDecoder);
}
/**
@ -321,7 +322,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
* @param format The format for which the codec is being configured.
* @param crypto For drm protected playbacks, a {@link MediaCrypto} to use for decryption.
*/
protected abstract void configureCodec(MediaCodec codec, MediaFormat format, MediaCrypto crypto);
protected abstract void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto);
@SuppressWarnings("deprecation")
protected final void maybeInitCodec() throws ExoPlaybackException {
@ -329,7 +330,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
return;
}
String mimeType = format.mimeType;
String mimeType = format.sampleMimeType;
MediaCrypto mediaCrypto = null;
boolean requiresSecureDecoder = false;
if (drmInitData != null) {
@ -499,8 +500,8 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
if (codec != null) {
TraceUtil.beginSection("drainAndFeed");
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
if (feedInputBuffer(positionUs, true)) {
while (feedInputBuffer(positionUs, false)) {}
if (feedInputBuffer(true)) {
while (feedInputBuffer(false)) {}
}
TraceUtil.endSection();
}
@ -543,14 +544,12 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
}
/**
* @param positionUs The current media time in microseconds, measured at the start of the
* current iteration of the rendering loop.
* @param firstFeed True if this is the first call to this method from the current invocation of
* {@link #doSomeWork(long, long)}. False otherwise.
* @return True if it may be possible to feed more input data. False otherwise.
* @throws ExoPlaybackException If an error occurs feeding the input buffer.
*/
private boolean feedInputBuffer(long positionUs, boolean firstFeed) throws ExoPlaybackException {
private boolean feedInputBuffer(boolean firstFeed) throws ExoPlaybackException {
if (inputStreamEnded
|| codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
// The input stream has ended, or we need to re-initialize the codec but are still waiting
@ -721,8 +720,8 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
* @param formatHolder Holds the new format.
* @throws ExoPlaybackException If an error occurs reinitializing the {@link MediaCodec}.
*/
protected void onInputFormatChanged(MediaFormatHolder formatHolder) throws ExoPlaybackException {
MediaFormat oldFormat = format;
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
Format oldFormat = format;
format = formatHolder.format;
drmInitData = formatHolder.drmInitData;
if (codec != null && canReconfigureCodec(codec, codecIsAdaptive, oldFormat, format)) {
@ -748,8 +747,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
* @param outputFormat The new output format.
* @throws ExoPlaybackException If an error occurs on output format change.
*/
protected void onOutputFormatChanged(android.media.MediaFormat outputFormat)
throws ExoPlaybackException {
protected void onOutputFormatChanged(MediaFormat outputFormat) throws ExoPlaybackException {
// Do nothing.
}
@ -779,8 +777,8 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
* @param newFormat The new format.
* @return True if the existing instance can be reconfigured. False otherwise.
*/
protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive,
MediaFormat oldFormat, MediaFormat newFormat) {
protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive, Format oldFormat,
Format newFormat) {
return false;
}

View File

@ -27,8 +27,10 @@ import android.annotation.TargetApi;
import android.content.Context;
import android.media.MediaCodec;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
@ -88,6 +90,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
}
private static final String TAG = "MediaCodecVideoTrackRenderer";
// TODO: Use MediaFormat constants if these get exposed through the API. See
// [Internal: b/14127601].
private static final String KEY_CROP_LEFT = "crop-left";
@ -108,6 +112,9 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
private final int videoScalingMode;
private final int maxDroppedFrameCountToNotify;
private int adaptiveMaxWidth;
private int adaptiveMaxHeight;
private Surface surface;
private boolean reportedDrawnToSurface;
private boolean renderedFirstFrame;
@ -213,34 +220,33 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
}
@Override
protected int supportsFormat(MediaCodecSelector mediaCodecSelector, MediaFormat mediaFormat)
protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format)
throws DecoderQueryException {
String mimeType = mediaFormat.mimeType;
String mimeType = format.sampleMimeType;
if (!MimeTypes.isVideo(mimeType)) {
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}
// TODO[REFACTOR]: Propagate requiresSecureDecoder to this point. Note that we need to check
// that the drm session can make use of a secure decoder, as well as that a secure decoder
// exists.
DecoderInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mediaFormat.mimeType, false);
// TODO[REFACTOR]: If requiresSecureDecryption then we should probably also check that the
// drmSession is able to make use of a secure decoder.
DecoderInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType,
format.requiresSecureDecryption);
if (decoderInfo == null) {
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}
if (mediaFormat.width > 0 && mediaFormat.height > 0) {
if (format.width > 0 && format.height > 0) {
if (Util.SDK_INT >= 21) {
// TODO[REFACTOR]: Propagate frame rate to this point.
// if (mediaFormat.frameRate > 0) {
// return decoderInfo.isSizeAndRateSupportedV21(mediaFormat.width, mediaFormat.height,
// mediaFormat.frameRate) ? TrackRenderer.TRACK_HANDLED
// : TrackRenderer.TRACK_NOT_HANDLED_EXCEEDS_CAPABILITIES;
// } else {
return decoderInfo.isVideoSizeSupportedV21(mediaFormat.width, mediaFormat.height)
? TrackRenderer.FORMAT_HANDLED : TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
// }
if (format.frameRate > 0) {
return decoderInfo.isVideoSizeAndRateSupportedV21(format.width, format.height,
format.frameRate) ? TrackRenderer.FORMAT_HANDLED
: TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
} else {
return decoderInfo.isVideoSizeSupportedV21(format.width, format.height)
? TrackRenderer.FORMAT_HANDLED : TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
}
}
// TODO[REFACTOR]: We should probably assume that we can decode at least the resolution of
// the display, or the camera, as a sanity check?
if (mediaFormat.width * mediaFormat.height > MediaCodecUtil.maxH264DecodableFrameSize()) {
if (format.width * format.height > MediaCodecUtil.maxH264DecodableFrameSize()) {
return TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
}
}
@ -248,9 +254,23 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
}
@Override
protected void onEnabled(TrackStream trackStream, long positionUs, boolean joining)
throws ExoPlaybackException {
super.onEnabled(trackStream, positionUs, joining);
protected void onEnabled(Format[] formats, TrackStream trackStream, long positionUs,
boolean joining) throws ExoPlaybackException {
super.onEnabled(formats, trackStream, positionUs, joining);
adaptiveMaxWidth = Format.NO_VALUE;
adaptiveMaxHeight = Format.NO_VALUE;
if (formats.length > 1) {
for (int i = 0; i < formats.length; i++) {
adaptiveMaxWidth = Math.max(adaptiveMaxWidth, formats[i].width);
adaptiveMaxHeight = Math.max(adaptiveMaxHeight, formats[i].height);
}
if (adaptiveMaxWidth == Format.NO_VALUE
|| adaptiveMaxHeight == Format.NO_VALUE) {
Log.w(TAG, "Maximum dimensions unknown. Assuming 1920x1080.");
adaptiveMaxWidth = 1920;
adaptiveMaxHeight = 1080;
}
}
if (joining && allowedJoiningTimeUs > 0) {
joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs;
}
@ -345,17 +365,17 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
// Override configureCodec to provide the surface.
@Override
protected void configureCodec(MediaCodec codec, MediaFormat format, MediaCrypto crypto) {
protected void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto) {
codec.configure(getFrameworkMediaFormat(format), surface, crypto, 0);
codec.setVideoScalingMode(videoScalingMode);
}
@Override
protected void onInputFormatChanged(MediaFormatHolder holder) throws ExoPlaybackException {
protected void onInputFormatChanged(FormatHolder holder) throws ExoPlaybackException {
super.onInputFormatChanged(holder);
pendingPixelWidthHeightRatio = holder.format.pixelWidthHeightRatio == MediaFormat.NO_VALUE ? 1
pendingPixelWidthHeightRatio = holder.format.pixelWidthHeightRatio == Format.NO_VALUE ? 1
: holder.format.pixelWidthHeightRatio;
pendingRotationDegrees = holder.format.rotationDegrees == MediaFormat.NO_VALUE ? 0
pendingRotationDegrees = holder.format.rotationDegrees == Format.NO_VALUE ? 0
: holder.format.rotationDegrees;
}
@ -367,16 +387,16 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
}
@Override
protected void onOutputFormatChanged(android.media.MediaFormat outputFormat) {
protected void onOutputFormatChanged(MediaFormat outputFormat) {
boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT)
&& outputFormat.containsKey(KEY_CROP_LEFT) && outputFormat.containsKey(KEY_CROP_BOTTOM)
&& outputFormat.containsKey(KEY_CROP_TOP);
currentWidth = hasCrop
? outputFormat.getInteger(KEY_CROP_RIGHT) - outputFormat.getInteger(KEY_CROP_LEFT) + 1
: outputFormat.getInteger(android.media.MediaFormat.KEY_WIDTH);
: outputFormat.getInteger(MediaFormat.KEY_WIDTH);
currentHeight = hasCrop
? outputFormat.getInteger(KEY_CROP_BOTTOM) - outputFormat.getInteger(KEY_CROP_TOP) + 1
: outputFormat.getInteger(android.media.MediaFormat.KEY_HEIGHT);
: outputFormat.getInteger(MediaFormat.KEY_HEIGHT);
currentPixelWidthHeightRatio = pendingPixelWidthHeightRatio;
if (Util.SDK_INT >= 21) {
// On API level 21 and above the decoder applies the rotation when rendering to the surface.
@ -396,8 +416,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
@Override
protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive,
MediaFormat oldFormat, MediaFormat newFormat) {
return newFormat.mimeType.equals(oldFormat.mimeType)
Format oldFormat, Format newFormat) {
return newFormat.sampleMimeType.equals(oldFormat.sampleMimeType)
&& (codecIsAdaptive
|| (oldFormat.width == newFormat.width && oldFormat.height == newFormat.height));
}
@ -517,19 +537,26 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
}
@SuppressLint("InlinedApi")
private android.media.MediaFormat getFrameworkMediaFormat(MediaFormat format) {
private android.media.MediaFormat getFrameworkMediaFormat(Format format) {
android.media.MediaFormat frameworkMediaFormat = format.getFrameworkMediaFormatV16();
// Set the maximum adaptive video dimensions if applicable.
if (adaptiveMaxWidth != Format.NO_VALUE && adaptiveMaxHeight != Format.NO_VALUE) {
frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, adaptiveMaxWidth);
frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, adaptiveMaxHeight);
}
if (format.maxInputSize > 0) {
// The format already has a maximum input size.
return frameworkMediaFormat;
}
// If the format doesn't define a maximum input size, determine one ourselves.
int maxHeight = Math.max(format.maxHeight, format.height);
int maxWidth = Math.max(format.maxWidth, format.maxWidth);
int maxWidth = Math.max(adaptiveMaxWidth, format.width);
int maxHeight = Math.max(adaptiveMaxHeight, format.height);
int maxPixels;
int minCompressionRatio;
switch (format.mimeType) {
switch (format.sampleMimeType) {
case MimeTypes.VIDEO_H264:
if ("BRAVIA 4K 2015".equals(Util.MODEL)) {
// The Sony BRAVIA 4k TV has input buffers that are too small for the calculated 4k video
@ -555,7 +582,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
}
// Estimate the maximum input size assuming three channel 4:2:0 subsampled input frames.
int maxInputSize = (maxPixels * 3) / (2 * minCompressionRatio);
frameworkMediaFormat.setInteger(android.media.MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
return frameworkMediaFormat;
}

View File

@ -1,349 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Defines the format of an elementary media stream.
*/
public final class MediaFormat {
public static final int NO_VALUE = -1;
/**
* A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to
* the timestamps of their parent samples.
*/
public static final long OFFSET_SAMPLE_RELATIVE = Long.MAX_VALUE;
/**
* The identifier for the track represented by the format, or null if unknown or not applicable.
*/
public final String trackId;
/**
* The mime type of the format.
*/
public final String mimeType;
/**
* The average bandwidth in bits per second, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final int bitrate;
/**
* The maximum size of a buffer of data (typically one sample) in the format, or {@link #NO_VALUE}
* if unknown or not applicable.
*/
public final int maxInputSize;
/**
* Initialization data that must be provided to the decoder. Will not be null, but may be empty
* if initialization data is not required.
*/
public final List<byte[]> initializationData;
/**
* Whether the format represents an adaptive track, meaning that the format of the actual media
* data may change (e.g. to adapt to network conditions).
*/
public final boolean adaptive;
// Video specific.
/**
* The width of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final int width;
/**
* The height of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final int height;
/**
* For formats that belong to an adaptive video track (either describing the track, or describing
* a specific format within it), this is the maximum width of the video in pixels that will be
* encountered in the stream. Set to {@link #NO_VALUE} if unknown or not applicable.
*/
public final int maxWidth;
/**
* For formats that belong to an adaptive video track (either describing the track, or describing
* a specific format within it), this is the maximum height of the video in pixels that will be
* encountered in the stream. Set to {@link #NO_VALUE} if unknown or not applicable.
*/
public final int maxHeight;
/**
* The clockwise rotation that should be applied to the video for it to be rendered in the correct
* orientation, or {@link #NO_VALUE} if unknown or not applicable. Only 0, 90, 180 and 270 are
* supported.
*/
public final int rotationDegrees;
/**
* The width to height ratio of pixels in the video, or {@link #NO_VALUE} if unknown or not
* applicable.
*/
public final float pixelWidthHeightRatio;
// Audio specific.
/**
* The number of audio channels, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final int channelCount;
/**
* The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable.
*/
public final int sampleRate;
// Text specific.
/**
* The language of the track, or null if unknown or not applicable.
*/
public final String language;
/**
* For samples that contain subsamples, this is an offset that should be added to subsample
* timestamps. A value of {@link #OFFSET_SAMPLE_RELATIVE} indicates that subsample timestamps are
* relative to the timestamps of their parent samples.
*/
public final long subsampleOffsetUs;
// Lazy-initialized hashcode and framework media format.
private int hashCode;
private android.media.MediaFormat frameworkMediaFormat;
public static MediaFormat createVideoFormat(String trackId, String mimeType, int bitrate,
int maxInputSize, int width, int height, List<byte[]> initializationData) {
return createVideoFormat(trackId, mimeType, bitrate, maxInputSize, width, height,
initializationData, NO_VALUE, NO_VALUE);
}
public static MediaFormat createVideoFormat(String trackId, String mimeType, int bitrate,
int maxInputSize, int width, int height, List<byte[]> initializationData, int rotationDegrees,
float pixelWidthHeightRatio) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, width, height, rotationDegrees,
pixelWidthHeightRatio, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, initializationData,
false, NO_VALUE, NO_VALUE);
}
public static MediaFormat createAudioFormat(String trackId, String mimeType, int bitrate,
int maxInputSize, int channelCount, int sampleRate, List<byte[]> initializationData,
String language) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, channelCount, sampleRate, language, OFFSET_SAMPLE_RELATIVE, initializationData,
false, NO_VALUE, NO_VALUE);
}
public static MediaFormat createTextFormat(String trackId, String mimeType, int bitrate,
String language) {
return createTextFormat(trackId, mimeType, bitrate, language, OFFSET_SAMPLE_RELATIVE);
}
public static MediaFormat createTextFormat(String trackId, String mimeType, int bitrate,
String language, long subsampleOffsetUs) {
return new MediaFormat(trackId, mimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, language, subsampleOffsetUs, null, false, NO_VALUE, NO_VALUE);
}
public static MediaFormat createFormatForMimeType(String trackId, String mimeType, int bitrate) {
return new MediaFormat(trackId, mimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null, false, NO_VALUE,
NO_VALUE);
}
public static MediaFormat createId3Format() {
return createFormatForMimeType(null, MimeTypes.APPLICATION_ID3, MediaFormat.NO_VALUE);
}
/* package */ MediaFormat(String trackId, String mimeType, int bitrate, int maxInputSize,
int width, int height, int rotationDegrees, float pixelWidthHeightRatio, int channelCount,
int sampleRate, String language, long subsampleOffsetUs, List<byte[]> initializationData,
boolean adaptive, int maxWidth, int maxHeight) {
this.trackId = trackId;
this.mimeType = Assertions.checkNotEmpty(mimeType);
this.bitrate = bitrate;
this.maxInputSize = maxInputSize;
this.width = width;
this.height = height;
this.rotationDegrees = rotationDegrees;
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
this.channelCount = channelCount;
this.sampleRate = sampleRate;
this.language = language;
this.subsampleOffsetUs = subsampleOffsetUs;
this.initializationData = initializationData == null ? Collections.<byte[]>emptyList()
: initializationData;
this.adaptive = adaptive;
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
}
public MediaFormat copyWithMaxInputSize(int maxInputSize) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, width, height, rotationDegrees,
pixelWidthHeightRatio, channelCount, sampleRate, language, subsampleOffsetUs,
initializationData, adaptive, maxWidth, maxHeight);
}
public MediaFormat copyWithMaxVideoDimensions(int maxWidth, int maxHeight) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, width, height, rotationDegrees,
pixelWidthHeightRatio, channelCount, sampleRate, language, subsampleOffsetUs,
initializationData, adaptive, maxWidth, maxHeight);
}
public MediaFormat copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, width, height, rotationDegrees,
pixelWidthHeightRatio, channelCount, sampleRate, language, subsampleOffsetUs,
initializationData, adaptive, maxWidth, maxHeight);
}
public MediaFormat copyWithFixedTrackInfo(String trackId, int bitrate, int width, int height,
String language) {
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, width, height, rotationDegrees,
pixelWidthHeightRatio, channelCount, sampleRate, language, subsampleOffsetUs,
initializationData, adaptive, NO_VALUE, NO_VALUE);
}
public MediaFormat copyAsAdaptive(String trackId) {
return new MediaFormat(trackId, mimeType, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null, true, maxWidth,
maxHeight);
}
/**
* @return A {@link MediaFormat} representation of this format.
*/
@SuppressLint("InlinedApi")
@TargetApi(16)
public final android.media.MediaFormat getFrameworkMediaFormatV16() {
if (frameworkMediaFormat == null) {
android.media.MediaFormat format = new android.media.MediaFormat();
format.setString(android.media.MediaFormat.KEY_MIME, mimeType);
maybeSetStringV16(format, android.media.MediaFormat.KEY_LANGUAGE, language);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_WIDTH, width);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT, height);
maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_WIDTH, maxWidth);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_HEIGHT, maxHeight);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT, channelCount);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE, sampleRate);
for (int i = 0; i < initializationData.size(); i++) {
format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i)));
}
frameworkMediaFormat = format;
}
return frameworkMediaFormat;
}
/**
* Sets the framework format returned by {@link #getFrameworkMediaFormatV16()}.
*
* @deprecated This method only exists for FrameworkSampleSource, which is itself deprecated.
* @param format The framework format.
*/
@Deprecated
@TargetApi(16)
/* package */ final void setFrameworkFormatV16(android.media.MediaFormat format) {
frameworkMediaFormat = format;
}
@Override
public String toString() {
return "MediaFormat(" + trackId + ", " + mimeType + ", " + bitrate + ", " + maxInputSize
+ ", " + width + ", " + height + ", " + rotationDegrees + ", " + pixelWidthHeightRatio
+ ", " + channelCount + ", " + sampleRate + ", " + language + ", " + ", " + adaptive + ", "
+ maxWidth + ", " + maxHeight + ")";
}
@Override
public int hashCode() {
if (hashCode == 0) {
int result = 17;
result = 31 * result + (trackId == null ? 0 : trackId.hashCode());
result = 31 * result + (mimeType == null ? 0 : mimeType.hashCode());
result = 31 * result + bitrate;
result = 31 * result + maxInputSize;
result = 31 * result + width;
result = 31 * result + height;
result = 31 * result + rotationDegrees;
result = 31 * result + Float.floatToRawIntBits(pixelWidthHeightRatio);
result = 31 * result + (adaptive ? 1231 : 1237);
result = 31 * result + maxWidth;
result = 31 * result + maxHeight;
result = 31 * result + channelCount;
result = 31 * result + sampleRate;
result = 31 * result + (language == null ? 0 : language.hashCode());
for (int i = 0; i < initializationData.size(); i++) {
result = 31 * result + Arrays.hashCode(initializationData.get(i));
}
hashCode = result;
}
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
MediaFormat other = (MediaFormat) obj;
if (adaptive != other.adaptive || bitrate != other.bitrate || maxInputSize != other.maxInputSize
|| width != other.width || height != other.height
|| rotationDegrees != other.rotationDegrees
|| pixelWidthHeightRatio != other.pixelWidthHeightRatio
|| maxWidth != other.maxWidth || maxHeight != other.maxHeight
|| channelCount != other.channelCount || sampleRate != other.sampleRate
|| !Util.areEqual(trackId, other.trackId) || !Util.areEqual(language, other.language)
|| !Util.areEqual(mimeType, other.mimeType)
|| initializationData.size() != other.initializationData.size()) {
return false;
}
for (int i = 0; i < initializationData.size(); i++) {
if (!Arrays.equals(initializationData.get(i), other.initializationData.get(i))) {
return false;
}
}
return true;
}
@TargetApi(16)
private static final void maybeSetStringV16(android.media.MediaFormat format, String key,
String value) {
if (value != null) {
format.setString(key, value);
}
}
@TargetApi(16)
private static final void maybeSetIntegerV16(android.media.MediaFormat format, String key,
int value) {
if (value != NO_VALUE) {
format.setInteger(key, value);
}
}
}

View File

@ -152,7 +152,7 @@ public interface SampleSource {
* Returns whether data is available to be read.
* <p>
* Note: If the stream has ended then {@link #END_OF_STREAM} can always be read from
* {@link #readData(MediaFormatHolder, SampleHolder)}. Hence an ended stream is always ready.
* {@link #readData(FormatHolder, SampleHolder)}. Hence an ended stream is always ready.
*
* @return True if data is available to be read. False otherwise.
*/
@ -179,14 +179,14 @@ public interface SampleSource {
* This method will always return {@link #NOTHING_READ} in the case that there's a pending
* discontinuity to be read from {@link #readReset} for the specified track.
*
* @param formatHolder A {@link MediaFormatHolder} to populate in the case of a new format.
* @param formatHolder A {@link FormatHolder} to populate in the case of a new format.
* @param sampleHolder A {@link SampleHolder} to populate in the case of a new sample. If the
* caller requires the sample data then it must ensure that {@link SampleHolder#data}
* references a valid output buffer.
* @return The result, which can be {@link #END_OF_STREAM}, {@link #NOTHING_READ},
* {@link #FORMAT_READ} or {@link #SAMPLE_READ}.
*/
int readData(MediaFormatHolder formatHolder, SampleHolder sampleHolder);
int readData(FormatHolder formatHolder, SampleHolder sampleHolder);
/**
* Disables the track.

View File

@ -29,8 +29,8 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer {
private TrackStream trackStream;
@Override
protected void onEnabled(TrackStream trackStream, long positionUs, boolean joining)
throws ExoPlaybackException {
protected void onEnabled(Format[] formats, TrackStream trackStream, long positionUs,
boolean joining) throws ExoPlaybackException {
this.trackStream = Assertions.checkNotNull(trackStream);
onReset(positionUs);
}
@ -63,7 +63,7 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer {
/**
* Reads from the enabled upstream source.
*
* @param formatHolder A {@link MediaFormatHolder} object to populate in the case of a new format.
* @param formatHolder A {@link FormatHolder} object to populate in the case of a new format.
* @param sampleHolder A {@link SampleHolder} object to populate in the case of a new sample.
* If the caller requires the sample data then it must ensure that {@link SampleHolder#data}
* references a valid output buffer.
@ -71,7 +71,7 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer {
* {@link TrackStream#FORMAT_READ}, {@link TrackStream#NOTHING_READ} or
* {@link TrackStream#END_OF_STREAM}.
*/
protected final int readSource(MediaFormatHolder formatHolder, SampleHolder sampleHolder) {
protected final int readSource(FormatHolder formatHolder, SampleHolder sampleHolder) {
return trackStream.readData(formatHolder, sampleHolder);
}

View File

@ -50,7 +50,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
private final Uri uri;
private final DataSource dataSource;
private final MediaFormat format;
private final Format format;
private final long durationUs;
private final int minLoadableRetryCount;
private final TrackGroup tracks;
@ -65,11 +65,11 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
private int currentLoadableExceptionCount;
private long currentLoadableExceptionTimestamp;
public SingleSampleSource(Uri uri, DataSource dataSource, MediaFormat format, long durationUs) {
public SingleSampleSource(Uri uri, DataSource dataSource, Format format, long durationUs) {
this(uri, dataSource, format, durationUs, DEFAULT_MIN_LOADABLE_RETRY_COUNT);
}
public SingleSampleSource(Uri uri, DataSource dataSource, MediaFormat format, long durationUs,
public SingleSampleSource(Uri uri, DataSource dataSource, Format format, long durationUs,
int minLoadableRetryCount) {
this.uri = uri;
this.dataSource = dataSource;
@ -90,7 +90,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
@Override
public boolean prepare(long positionUs) {
if (loader == null) {
loader = new Loader("Loader:" + format.mimeType);
loader = new Loader("Loader:" + format.sampleMimeType);
}
return true;
}
@ -139,7 +139,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
}
@Override
public int readData(MediaFormatHolder formatHolder, SampleHolder sampleHolder) {
public int readData(FormatHolder formatHolder, SampleHolder sampleHolder) {
if (state == STATE_END_OF_STREAM) {
return END_OF_STREAM;
} else if (state == STATE_SEND_FORMAT) {

View File

@ -36,12 +36,12 @@ public final class TrackGroup {
*/
public final boolean adaptive;
private final MediaFormat[] formats;
private final Format[] formats;
/**
* @param format The format of the single track.
*/
public TrackGroup(MediaFormat format) {
public TrackGroup(Format format) {
this(false, format);
}
@ -49,7 +49,7 @@ public final class TrackGroup {
* @param adaptive Whether it's possible to adapt between multiple tracks in the group.
* @param formats The track formats.
*/
public TrackGroup(boolean adaptive, MediaFormat... formats) {
public TrackGroup(boolean adaptive, Format... formats) {
this.adaptive = adaptive;
this.formats = formats;
length = formats.length;
@ -61,7 +61,7 @@ public final class TrackGroup {
* @param index The index of the track.
* @return The track's format.
*/
public MediaFormat getFormat(int index) {
public Format getFormat(int index) {
return formats[index];
}

View File

@ -130,27 +130,28 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
/**
* Returns the extent to which the renderer is capable of rendering a given format.
*
* @param mediaFormat The format.
* @param format The format.
* @return The extent to which the renderer is capable of rendering the given format. One of
* {@link #FORMAT_HANDLED}, {@link #FORMAT_EXCEEDS_CAPABILITIES} and
* {@link #FORMAT_UNSUPPORTED_TYPE}.
* @throws ExoPlaybackException If an error occurs.
*/
protected abstract int supportsFormat(MediaFormat mediaFormat) throws ExoPlaybackException;
protected abstract int supportsFormat(Format format) throws ExoPlaybackException;
/**
* Enable the renderer to consume from the specified {@link TrackStream}.
*
* @param formats The enabled formats.
* @param trackStream The track stream from which the renderer should consume.
* @param positionUs The player's current position.
* @param joining Whether this renderer is being enabled to join an ongoing playback.
* @throws ExoPlaybackException If an error occurs.
*/
/* package */ final void enable(TrackStream trackStream, long positionUs, boolean joining)
throws ExoPlaybackException {
/* package */ final void enable(Format[] formats, TrackStream trackStream, long positionUs,
boolean joining) throws ExoPlaybackException {
Assertions.checkState(state == STATE_IDLE);
state = STATE_ENABLED;
onEnabled(trackStream, positionUs, joining);
onEnabled(formats, trackStream, positionUs, joining);
}
/**
@ -158,13 +159,14 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
* <p>
* The default implementation is a no-op.
*
* @param formats The enabled formats.
* @param trackStream The track stream from which the renderer should consume.
* @param positionUs The player's current position.
* @param joining Whether this renderer is being enabled to join an ongoing playback.
* @throws ExoPlaybackException If an error occurs.
*/
protected void onEnabled(TrackStream trackStream, long positionUs, boolean joining)
throws ExoPlaybackException {
protected void onEnabled(Format[] formats, TrackStream trackStream, long positionUs,
boolean joining) throws ExoPlaybackException {
// Do nothing.
}

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.SampleSource.TrackStream;

View File

@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
import com.google.android.exoplayer.upstream.DataSource;
@ -29,11 +29,11 @@ import com.google.android.exoplayer.upstream.DataSpec;
public abstract class BaseMediaChunk extends MediaChunk {
/**
* Whether {@link #getMediaFormat()} and {@link #getDrmInitData()} can be called at any time to
* obtain the chunk's media format and drm initialization data. If false, these methods are only
* Whether {@link #getSampleFormat()} and {@link #getDrmInitData()} can be called at any time to
* obtain the chunk's sample format and drm initialization data. If false, these methods are only
* guaranteed to return correct data after the first sample data has been output from the chunk.
*/
public final boolean isMediaFormatFinal;
public final boolean isSampleFormatFinal;
private DefaultTrackOutput output;
private int firstSampleIndex;
@ -46,16 +46,16 @@ public abstract class BaseMediaChunk extends MediaChunk {
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param chunkIndex The index of the chunk.
* @param isMediaFormatFinal True if {@link #getMediaFormat()} and {@link #getDrmInitData()} can
* be called at any time to obtain the media format and drm initialization data. False if
* @param isSampleFormatFinal True if {@link #getSampleFormat()} and {@link #getDrmInitData()} can
* be called at any time to obtain the sample format and drm initialization data. False if
* these methods are only guaranteed to return correct data after the first sample data has
* been output from the chunk.
* @param parentId Identifier for a parent from which this chunk originates.
*/
public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, boolean isMediaFormatFinal, int parentId) {
long startTimeUs, long endTimeUs, int chunkIndex, boolean isSampleFormatFinal, int parentId) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, parentId);
this.isMediaFormatFinal = isMediaFormatFinal;
this.isSampleFormatFinal = isSampleFormatFinal;
}
/**
@ -78,19 +78,19 @@ public abstract class BaseMediaChunk extends MediaChunk {
}
/**
* Gets the {@link MediaFormat} corresponding to the chunk.
* Gets the {@link Format} of the samples in the chunk.
* <p>
* See {@link #isMediaFormatFinal} for information about when this method is guaranteed to return
* See {@link #isSampleFormatFinal} for information about when this method is guaranteed to return
* correct data.
*
* @return The {@link MediaFormat} corresponding to this chunk.
* @return The {@link Format} of the samples in the chunk.
*/
public abstract MediaFormat getMediaFormat();
public abstract Format getSampleFormat();
/**
* Gets the {@link DrmInitData} corresponding to the chunk.
* <p>
* See {@link #isMediaFormatFinal} for information about when this method is guaranteed to return
* See {@link #isSampleFormatFinal} for information about when this method is guaranteed to return
* correct data.
*
* @return The {@link DrmInitData} corresponding to this chunk.

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.Loader.Loadable;

View File

@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorInput;
@ -122,7 +122,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
// TrackOutput implementation.
@Override
public void format(MediaFormat format) {
public void format(Format format) {
output.format(format);
}

View File

@ -16,9 +16,9 @@
package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.SampleSource.TrackStream;
@ -86,8 +86,8 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
private long currentLoadableExceptionTimestamp;
private long currentLoadStartTimeMs;
private MediaFormat downstreamMediaFormat;
private Format downstreamFormat;
private Format downstreamSampleFormat;
/**
* @param chunkSource A {@link ChunkSource} from which chunks to load are obtained.
@ -155,8 +155,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
durationUs = chunkSource.getDurationUs();
TrackGroup trackGroup = chunkSource.getTracks();
if (trackGroup.length > 0) {
MediaFormat firstTrackFormat = trackGroup.getFormat(0);
loader = new Loader("Loader:" + firstTrackFormat.mimeType);
loader = new Loader("Loader:" + trackGroup.getFormat(0).containerMimeType);
}
state = STATE_PREPARED;
return true;
@ -191,7 +190,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
chunkSource.enable(tracks);
loadControl.register(this, bufferSizeContribution);
downstreamFormat = null;
downstreamMediaFormat = null;
downstreamSampleFormat = null;
downstreamPositionUs = positionUs;
lastSeekPositionUs = positionUs;
pendingReset = false;
@ -246,7 +245,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
}
@Override
public int readData(MediaFormatHolder formatHolder, SampleHolder sampleHolder) {
public int readData(FormatHolder formatHolder, SampleHolder sampleHolder) {
Assertions.checkState(state == STATE_ENABLED);
if (pendingReset || isPendingReset()) {
return NOTHING_READ;
@ -266,12 +265,12 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
downstreamFormat = currentChunk.format;
}
if (haveSamples || currentChunk.isMediaFormatFinal) {
MediaFormat mediaFormat = currentChunk.getMediaFormat();
if (!mediaFormat.equals(downstreamMediaFormat)) {
formatHolder.format = mediaFormat;
if (haveSamples || currentChunk.isSampleFormatFinal) {
Format sampleFormat = currentChunk.getSampleFormat();
if (!sampleFormat.equals(downstreamSampleFormat)) {
formatHolder.format = sampleFormat;
formatHolder.drmInitData = currentChunk.getDrmInitData();
downstreamMediaFormat = mediaFormat;
downstreamSampleFormat = sampleFormat;
return FORMAT_READ;
}
}

View File

@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper.SingleTrackOutput;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
@ -36,10 +36,8 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
private final ChunkExtractorWrapper extractorWrapper;
private final long sampleOffsetUs;
private final int adaptiveMaxWidth;
private final int adaptiveMaxHeight;
private MediaFormat mediaFormat;
private Format sampleFormat;
private DrmInitData drmInitData;
private volatile int bytesLoaded;
@ -55,32 +53,24 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
* @param chunkIndex The index of the chunk.
* @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor.
* @param extractorWrapper A wrapped extractor to use for parsing the data.
* @param mediaFormat The {@link MediaFormat} of the chunk, if known. May be null if the data is
* known to define its own format.
* @param adaptiveMaxWidth If this chunk contains video and is part of an adaptive playback, this
* is the maximum width of the video in pixels that will be encountered during the playback.
* {@link MediaFormat#NO_VALUE} otherwise.
* @param adaptiveMaxHeight If this chunk contains video and is part of an adaptive playback, this
* is the maximum height of the video in pixels that will be encountered during the playback.
* {@link MediaFormat#NO_VALUE} otherwise.
* @param sampleFormat The {@link Format} of the samples in the chunk, if known. May be null if
* the data is known to define its own sample format.
* @param drmInitData The {@link DrmInitData} for the chunk. Null if the media is not drm
* protected. May also be null if the data is known to define its own initialization data.
* @param isMediaFormatFinal True if {@code mediaFormat} and {@code drmInitData} are known to be
* correct and final. False if the data may define its own format or initialization data.
* @param isSampleFormatFinal True if {@code sampleFormat} and {@code drmInitData} are known to be
* correct and final. False if the data may define its own sample format or initialization
* data.
* @param parentId Identifier for a parent from which this chunk originates.
*/
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
long startTimeUs, long endTimeUs, int chunkIndex, long sampleOffsetUs,
ChunkExtractorWrapper extractorWrapper, MediaFormat mediaFormat, int adaptiveMaxWidth,
int adaptiveMaxHeight, DrmInitData drmInitData, boolean isMediaFormatFinal, int parentId) {
ChunkExtractorWrapper extractorWrapper, Format sampleFormat, DrmInitData drmInitData,
boolean isSampleFormatFinal, int parentId) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex,
isMediaFormatFinal, parentId);
isSampleFormatFinal, parentId);
this.extractorWrapper = extractorWrapper;
this.sampleOffsetUs = sampleOffsetUs;
this.adaptiveMaxWidth = adaptiveMaxWidth;
this.adaptiveMaxHeight = adaptiveMaxHeight;
this.mediaFormat = getAdjustedMediaFormat(mediaFormat, sampleOffsetUs, adaptiveMaxWidth,
adaptiveMaxHeight);
this.sampleFormat = getAdjustedSampleFormat(sampleFormat, sampleOffsetUs);
this.drmInitData = drmInitData;
}
@ -90,8 +80,8 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
}
@Override
public final MediaFormat getMediaFormat() {
return mediaFormat;
public final Format getSampleFormat() {
return sampleFormat;
}
@Override
@ -112,9 +102,8 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
}
@Override
public final void format(MediaFormat mediaFormat) {
this.mediaFormat = getAdjustedMediaFormat(mediaFormat, sampleOffsetUs, adaptiveMaxWidth,
adaptiveMaxHeight);
public final void format(Format format) {
this.sampleFormat = getAdjustedSampleFormat(format, sampleOffsetUs);
}
@Override
@ -174,17 +163,13 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
// Private methods.
private static MediaFormat getAdjustedMediaFormat(MediaFormat format, long sampleOffsetUs,
int adaptiveMaxWidth, int adaptiveMaxHeight) {
private static Format getAdjustedSampleFormat(Format format, long sampleOffsetUs) {
if (format == null) {
return null;
}
if (sampleOffsetUs != 0 && format.subsampleOffsetUs != MediaFormat.OFFSET_SAMPLE_RELATIVE) {
if (sampleOffsetUs != 0 && format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) {
format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + sampleOffsetUs);
}
if (adaptiveMaxWidth != MediaFormat.NO_VALUE || adaptiveMaxHeight != MediaFormat.NO_VALUE) {
format = format.copyWithMaxVideoDimensions(adaptiveMaxWidth, adaptiveMaxHeight);
}
return format;
}

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;

View File

@ -1,173 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.util.Assertions;
import java.util.Comparator;
/**
* Defines the high level format of a media stream.
*/
public class Format {
/**
* Sorts {@link Format} objects in order of decreasing bandwidth.
*/
public static final class DecreasingBandwidthComparator implements Comparator<Format> {
@Override
public int compare(Format a, Format b) {
return b.bitrate - a.bitrate;
}
}
/**
* An identifier for the format.
*/
public final String id;
/**
* The mime type of the format.
*/
public final String mimeType;
/**
* The average bandwidth in bits per second.
*/
public final int bitrate;
/**
* The width of the video in pixels, or -1 if unknown or not applicable.
*/
public final int width;
/**
* The height of the video in pixels, or -1 if unknown or not applicable.
*/
public final int height;
/**
* The video frame rate in frames per second, or -1 if unknown or not applicable.
*/
public final float frameRate;
/**
* The number of audio channels, or -1 if unknown or not applicable.
*/
public final int audioChannels;
/**
* The audio sampling rate in Hz, or -1 if unknown or not applicable.
*/
public final int audioSamplingRate;
/**
* The codecs used to decode the format. Can be {@code null} if unknown.
*/
public final String codecs;
/**
* The language of the format. Can be null if unknown.
* <p>
* The language codes are two-letter lowercase ISO language codes (such as "en") as defined by
* ISO 639-1.
*/
public final String language;
/**
* @param id The format identifier.
* @param mimeType The format mime type.
* @param width The width of the video in pixels, or -1 if unknown or not applicable.
* @param height The height of the video in pixels, or -1 if unknown or not applicable.
* @param frameRate The frame rate of the video in frames per second, or -1 if unknown or not
* applicable.
* @param numChannels The number of audio channels, or -1 if unknown or not applicable.
* @param audioSamplingRate The audio sampling rate in Hz, or -1 if unknown or not applicable.
* @param bitrate The average bandwidth of the format in bits per second.
*/
public Format(String id, String mimeType, int width, int height, float frameRate, int numChannels,
int audioSamplingRate, int bitrate) {
this(id, mimeType, width, height, frameRate, numChannels, audioSamplingRate, bitrate, null);
}
/**
* @param id The format identifier.
* @param mimeType The format mime type.
* @param width The width of the video in pixels, or -1 if unknown or not applicable.
* @param height The height of the video in pixels, or -1 if unknown or not applicable.
* @param frameRate The frame rate of the video in frames per second, or -1 if unknown or not
* applicable.
* @param numChannels The number of audio channels, or -1 if unknown or not applicable.
* @param audioSamplingRate The audio sampling rate in Hz, or -1 if unknown or not applicable.
* @param bitrate The average bandwidth of the format in bits per second.
* @param language The language of the format.
*/
public Format(String id, String mimeType, int width, int height, float frameRate, int numChannels,
int audioSamplingRate, int bitrate, String language) {
this(id, mimeType, width, height, frameRate, numChannels, audioSamplingRate, bitrate, language,
null);
}
/**
* @param id The format identifier.
* @param mimeType The format mime type.
* @param width The width of the video in pixels, or -1 if unknown or not applicable.
* @param height The height of the video in pixels, or -1 if unknown or not applicable.
* @param frameRate The frame rate of the video in frames per second, or -1 if unknown or not
* applicable.
* @param audioChannels The number of audio channels, or -1 if unknown or not applicable.
* @param audioSamplingRate The audio sampling rate in Hz, or -1 if unknown or not applicable.
* @param bitrate The average bandwidth of the format in bits per second.
* @param language The language of the format.
* @param codecs The codecs used to decode the format.
*/
public Format(String id, String mimeType, int width, int height, float frameRate,
int audioChannels, int audioSamplingRate, int bitrate, String language, String codecs) {
this.id = Assertions.checkNotNull(id);
this.mimeType = mimeType;
this.width = width;
this.height = height;
this.frameRate = frameRate;
this.audioChannels = audioChannels;
this.audioSamplingRate = audioSamplingRate;
this.bitrate = bitrate;
this.language = language;
this.codecs = codecs;
}
@Override
public int hashCode() {
return id.hashCode();
}
/**
* Implements equality based on {@link #id} only.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Format other = (Format) obj;
return other.id.equals(id);
}
}

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import java.util.List;

View File

@ -1,28 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.chunk;
/**
* Represents an object that wraps a {@link Format}.
*/
public interface FormatWrapper {
/**
* Returns the wrapped format.
*/
Format getFormat();
}

View File

@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper.SingleTrackOutput;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
@ -39,7 +39,7 @@ public final class InitializationChunk extends Chunk implements SingleTrackOutpu
// Initialization results. Set by the loader thread and read by any thread that knows loading
// has completed. These variables do not need to be volatile, since a memory barrier must occur
// for the reading thread to know that loading has completed.
private MediaFormat mediaFormat;
private Format sampleFormat;
private DrmInitData drmInitData;
private SeekMap seekMap;
@ -73,21 +73,21 @@ public final class InitializationChunk extends Chunk implements SingleTrackOutpu
}
/**
* True if a {@link MediaFormat} was parsed from the chunk. False otherwise.
* True if a {@link Format} was parsed from the chunk. False otherwise.
* <p>
* Should be called after loading has completed.
*/
public boolean hasFormat() {
return mediaFormat != null;
public boolean hasSampleFormat() {
return format != null;
}
/**
* Returns a {@link MediaFormat} parsed from the chunk, or null.
* Returns a {@link Format} parsed from the chunk, or null.
* <p>
* Should be called after loading has completed.
*/
public MediaFormat getFormat() {
return mediaFormat;
public Format getSampleFormat() {
return sampleFormat;
}
/**
@ -139,8 +139,8 @@ public final class InitializationChunk extends Chunk implements SingleTrackOutpu
}
@Override
public void format(MediaFormat mediaFormat) {
this.mediaFormat = mediaFormat;
public void format(Format format) {
this.sampleFormat = format;
}
@Override

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.util.Assertions;

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.chunk;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
@ -29,7 +29,7 @@ import java.io.IOException;
*/
public final class SingleSampleMediaChunk extends BaseMediaChunk {
private final MediaFormat sampleFormat;
private final Format sampleFormat;
private final DrmInitData sampleDrmInitData;
private volatile int bytesLoaded;
@ -49,7 +49,7 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
* @param parentId Identifier for a parent from which this chunk originates.
*/
public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger,
Format format, long startTimeUs, long endTimeUs, int chunkIndex, MediaFormat sampleFormat,
Format format, long startTimeUs, long endTimeUs, int chunkIndex, Format sampleFormat,
DrmInitData sampleDrmInitData, int parentId) {
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, true,
parentId);
@ -63,7 +63,7 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
}
@Override
public MediaFormat getMediaFormat() {
public Format getSampleFormat() {
return sampleFormat;
}

View File

@ -17,7 +17,8 @@ package com.google.android.exoplayer.dash;
import com.google.android.exoplayer.BehindLiveWindowException;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.Format.DecreasingBandwidthComparator;
import com.google.android.exoplayer.TimeRange;
import com.google.android.exoplayer.TimeRange.DynamicTimeRange;
import com.google.android.exoplayer.TimeRange.StaticTimeRange;
@ -27,8 +28,6 @@ import com.google.android.exoplayer.chunk.ChunkExtractorWrapper;
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
import com.google.android.exoplayer.chunk.ChunkSource;
import com.google.android.exoplayer.chunk.ContainerMediaChunk;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.Format.DecreasingBandwidthComparator;
import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation;
import com.google.android.exoplayer.chunk.InitializationChunk;
@ -52,12 +51,10 @@ import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.SystemClock;
import android.os.Handler;
import android.util.Log;
import android.util.SparseArray;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
@ -102,8 +99,6 @@ public class DashChunkSource implements ChunkSource {
}
private static final String TAG = "DashChunkSource";
private final Handler eventHandler;
private final EventListener eventListener;
@ -133,12 +128,9 @@ public class DashChunkSource implements ChunkSource {
// Properties of exposed tracks.
private int adaptationSetIndex;
private TrackGroup trackGroup;
private Format[] trackFormats;
// Properties of enabled tracks.
private Format[] enabledFormats;
private int adaptiveMaxWidth;
private int adaptiveMaxHeight;
/**
* @param manifestFetcher A fetcher for the manifest.
@ -250,7 +242,7 @@ public class DashChunkSource implements ChunkSource {
} else {
live = currentManifest.dynamic;
durationUs = live ? C.UNKNOWN_TIME_US : currentManifest.duration * 1000;
selectTracks(currentManifest, 0);
initForManifest(currentManifest);
}
}
return true;
@ -268,22 +260,13 @@ public class DashChunkSource implements ChunkSource {
@Override
public void enable(int[] tracks) {
int maxWidth = -1;
int maxHeight = -1;
enabledFormats = new Format[tracks.length];
for (int i = 0; i < tracks.length; i++) {
enabledFormats[i] = trackFormats[tracks[i]];
maxWidth = Math.max(enabledFormats[i].width, maxWidth);
maxHeight = Math.max(enabledFormats[i].height, maxHeight);
enabledFormats[i] = trackGroup.getFormat(tracks[i]);
}
Arrays.sort(enabledFormats, new DecreasingBandwidthComparator());
if (enabledFormats.length > 1) {
adaptiveMaxWidth = maxWidth;
adaptiveMaxHeight = maxHeight;
adaptiveFormatEvaluator.enable();
} else {
adaptiveMaxWidth = -1;
adaptiveMaxHeight = -1;
}
processManifest(manifestFetcher.getManifest());
}
@ -342,7 +325,7 @@ public class DashChunkSource implements ChunkSource {
out.chunk = null;
return;
} else if (out.queueSize == queue.size() && out.chunk != null
&& out.chunk.format.equals(selectedFormat)) {
&& out.chunk.format == selectedFormat) {
// We already have a chunk, and the evaluation hasn't changed either the format or the size
// of the queue. Leave unchanged.
return;
@ -394,7 +377,7 @@ public class DashChunkSource implements ChunkSource {
PeriodHolder lastPeriodHolder = periodHolders.valueAt(periodHolders.size() - 1);
if (previous.parentId == lastPeriodHolder.localIndex) {
RepresentationHolder representationHolder =
lastPeriodHolder.representationHolders.get(previous.format.id);
lastPeriodHolder.representationHolders[getTrackIndex(previous.format)];
if (representationHolder.isBeyondLastSegment(previous.getNextChunkIndex())) {
out.endOfStream = true;
return;
@ -413,7 +396,7 @@ public class DashChunkSource implements ChunkSource {
startingNewPeriod = true;
} else if (!periodHolder.isIndexUnbounded()) {
RepresentationHolder representationHolder =
periodHolder.representationHolders.get(previous.format.id);
periodHolder.representationHolders[getTrackIndex(previous.format)];
if (representationHolder.isBeyondLastSegment(previous.getNextChunkIndex())) {
// We reached the end of a period. Start the next one.
periodHolder = periodHolders.get(previous.parentId + 1);
@ -423,14 +406,14 @@ public class DashChunkSource implements ChunkSource {
}
RepresentationHolder representationHolder =
periodHolder.representationHolders.get(selectedFormat.id);
periodHolder.representationHolders[getTrackIndex(selectedFormat)];
Representation selectedRepresentation = representationHolder.representation;
RangedUri pendingInitializationUri = null;
RangedUri pendingIndexUri = null;
MediaFormat mediaFormat = representationHolder.mediaFormat;
if (mediaFormat == null) {
Format sampleFormat = representationHolder.sampleFormat;
if (sampleFormat == null) {
pendingInitializationUri = selectedRepresentation.getInitializationUri();
}
if (representationHolder.segmentIndex == null) {
@ -451,7 +434,7 @@ public class DashChunkSource implements ChunkSource {
: startingNewPeriod ? representationHolder.getFirstAvailableSegmentNum()
: queue.get(out.queueSize - 1).getNextChunkIndex();
Chunk nextMediaChunk = newMediaChunk(periodHolder, representationHolder, dataSource,
mediaFormat, adaptiveMaxWidth, adaptiveMaxHeight, segmentNum, evaluation.trigger);
sampleFormat, segmentNum, evaluation.trigger);
lastChunkWasInitialization = false;
out.chunk = nextMediaChunk;
}
@ -460,16 +443,16 @@ public class DashChunkSource implements ChunkSource {
public void onChunkLoadCompleted(Chunk chunk) {
if (chunk instanceof InitializationChunk) {
InitializationChunk initializationChunk = (InitializationChunk) chunk;
String formatId = initializationChunk.format.id;
PeriodHolder periodHolder = periodHolders.get(initializationChunk.parentId);
if (periodHolder == null) {
// period for this initialization chunk may no longer be on the manifest
return;
}
RepresentationHolder representationHolder = periodHolder.representationHolders.get(formatId);
if (initializationChunk.hasFormat()) {
representationHolder.mediaFormat = initializationChunk.getFormat();
RepresentationHolder representationHolder =
periodHolder.representationHolders[getTrackIndex(initializationChunk.format)];
if (initializationChunk.hasSampleFormat()) {
representationHolder.sampleFormat = initializationChunk.getSampleFormat();
}
if (initializationChunk.hasSeekMap()) {
representationHolder.segmentIndex = new DashWrappingSegmentIndex(
@ -504,45 +487,23 @@ public class DashChunkSource implements ChunkSource {
// Private methods.
private void selectTracks(MediaPresentationDescription manifest, int periodIndex) {
Period period = manifest.getPeriod(periodIndex);
private void initForManifest(MediaPresentationDescription manifest) {
Period period = manifest.getPeriod(0);
for (int i = 0; i < period.adaptationSets.size(); i++) {
AdaptationSet adaptationSet = period.adaptationSets.get(i);
if (adaptationSet.type == adaptationSetType) {
// We've found an adaptation set of the exposed type.
adaptationSetIndex = i;
List<Representation> representations = adaptationSet.representations;
trackFormats = new Format[representations.size()];
MediaFormat[] trackMediaFormats = new MediaFormat[representations.size()];
int trackCount = 0;
for (int j = 0; j < trackMediaFormats.length; j++) {
trackMediaFormats[trackCount] = getMediaFormat(representations.get(j).format);
if (trackMediaFormats[trackCount] != null) {
trackFormats[trackCount++] = representations.get(j).format;
}
Format[] trackFormats = new Format[representations.size()];
for (int j = 0; j < trackFormats.length; j++) {
trackFormats[j] = representations.get(j).format;
}
trackGroup = new TrackGroup(adaptiveFormatEvaluator != null,
Arrays.copyOf(trackMediaFormats, trackCount));
trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, trackFormats);
return;
}
}
trackGroup = new TrackGroup(adaptiveFormatEvaluator != null);
trackFormats = new Format[0];
}
private MediaFormat getMediaFormat(Format representationFormat) {
String mediaMimeType = getMediaMimeType(representationFormat);
if (mediaMimeType == null) {
Log.w(TAG, "Skipped track " + representationFormat.id + " (unknown media mime type)");
return null;
}
MediaFormat trackFormat = getTrackFormat(adaptationSetType, representationFormat,
mediaMimeType);
if (trackFormat == null) {
Log.w(TAG, "Skipped track " + representationFormat.id + " (unknown media format)");
return null;
}
return trackFormat;
}
// Visible for testing.
@ -550,48 +511,6 @@ public class DashChunkSource implements ChunkSource {
return availableRange;
}
private static MediaFormat getTrackFormat(int adaptationSetType, Format format,
String mediaMimeType) {
switch (adaptationSetType) {
case AdaptationSet.TYPE_VIDEO:
return MediaFormat.createVideoFormat(format.id, mediaMimeType, format.bitrate,
MediaFormat.NO_VALUE, format.width, format.height, null);
case AdaptationSet.TYPE_AUDIO:
return MediaFormat.createAudioFormat(format.id, mediaMimeType, format.bitrate,
MediaFormat.NO_VALUE, format.audioChannels, format.audioSamplingRate, null,
format.language);
case AdaptationSet.TYPE_TEXT:
return MediaFormat.createTextFormat(format.id, mediaMimeType, format.bitrate,
format.language);
default:
return null;
}
}
private static String getMediaMimeType(Format format) {
String formatMimeType = format.mimeType;
if (MimeTypes.isAudio(formatMimeType)) {
return MimeTypes.getAudioMediaMimeType(format.codecs);
} else if (MimeTypes.isVideo(formatMimeType)) {
return MimeTypes.getVideoMediaMimeType(format.codecs);
} else if (mimeTypeIsRawText(formatMimeType)) {
return formatMimeType;
} else if (MimeTypes.APPLICATION_MP4.equals(formatMimeType) && "stpp".equals(format.codecs)) {
return MimeTypes.APPLICATION_TTML;
} else {
return null;
}
}
/* package */ static boolean mimeTypeIsWebm(String mimeType) {
return mimeType.startsWith(MimeTypes.VIDEO_WEBM) || mimeType.startsWith(MimeTypes.AUDIO_WEBM)
|| mimeType.startsWith(MimeTypes.APPLICATION_WEBM);
}
/* package */ static boolean mimeTypeIsRawText(String mimeType) {
return MimeTypes.TEXT_VTT.equals(mimeType) || MimeTypes.APPLICATION_TTML.equals(mimeType);
}
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri,
Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource,
int manifestIndex, int trigger) {
@ -613,26 +532,25 @@ public class DashChunkSource implements ChunkSource {
}
protected Chunk newMediaChunk(PeriodHolder periodHolder,
RepresentationHolder representationHolder, DataSource dataSource, MediaFormat mediaFormat,
int adaptiveMaxWidth, int adaptiveMaxHeight, int segmentNum, int trigger) {
RepresentationHolder representationHolder, DataSource dataSource, Format sampleFormat,
int segmentNum, int trigger) {
Representation representation = representationHolder.representation;
Format format = representation.format;
long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum);
long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum);
RangedUri segmentUri = representationHolder.getSegmentUrl(segmentNum);
DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length,
representation.getCacheKey());
Format format = representation.format;
long sampleOffsetUs = periodHolder.startTimeUs - representation.presentationTimeOffsetUs;
if (mimeTypeIsRawText(format.mimeType)) {
if (representationHolder.extractorWrapper == null) {
return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, format,
startTimeUs, endTimeUs, segmentNum, mediaFormat, null, periodHolder.localIndex);
startTimeUs, endTimeUs, segmentNum, format, null, periodHolder.localIndex);
} else {
boolean isMediaFormatFinal = (mediaFormat != null);
boolean isSampleFormatFinal = sampleFormat != null;
return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs,
segmentNum, sampleOffsetUs, representationHolder.extractorWrapper, mediaFormat,
adaptiveMaxWidth, adaptiveMaxHeight, periodHolder.drmInitData,
isMediaFormatFinal, periodHolder.localIndex);
segmentNum, sampleOffsetUs, representationHolder.extractorWrapper, sampleFormat,
periodHolder.drmInitData, isSampleFormatFinal, periodHolder.localIndex);
}
}
@ -696,8 +614,8 @@ public class DashChunkSource implements ChunkSource {
// Add new periods.
for (int i = periodHolders.size(); i < manifest.getPeriodCount(); i++) {
PeriodHolder holder = new PeriodHolder(nextPeriodHolderIndex, manifest, i, adaptationSetIndex,
enabledFormats);
PeriodHolder holder = new PeriodHolder(nextPeriodHolderIndex, manifest, i,
adaptationSetIndex);
periodHolders.put(nextPeriodHolderIndex, holder);
nextPeriodHolderIndex++;
}
@ -732,6 +650,16 @@ public class DashChunkSource implements ChunkSource {
timeShiftBufferDepthUs, systemClock);
}
private int getTrackIndex(Format format) {
for (int i = 0; i < trackGroup.length; i++) {
if (trackGroup.getFormat(i) == format) {
return i;
}
}
// Should never happen.
throw new IllegalStateException("Invalid format: " + format);
}
private void notifyAvailableRangeChanged(final TimeRange seekRange) {
if (eventHandler != null && eventListener != null) {
eventHandler.post(new Runnable() {
@ -747,12 +675,11 @@ public class DashChunkSource implements ChunkSource {
protected static final class RepresentationHolder {
public final boolean mimeTypeIsRawText;
public final ChunkExtractorWrapper extractorWrapper;
public Representation representation;
public DashSegmentIndex segmentIndex;
public MediaFormat mediaFormat;
public Format sampleFormat;
private final long periodStartTimeUs;
@ -764,10 +691,9 @@ public class DashChunkSource implements ChunkSource {
this.periodStartTimeUs = periodStartTimeUs;
this.periodDurationUs = periodDurationUs;
this.representation = representation;
String mimeType = representation.format.mimeType;
mimeTypeIsRawText = mimeTypeIsRawText(mimeType);
extractorWrapper = mimeTypeIsRawText ? null : new ChunkExtractorWrapper(
mimeTypeIsWebm(mimeType) ? new WebmExtractor() : new FragmentedMp4Extractor());
String containerMimeType = representation.format.containerMimeType;
extractorWrapper = mimeTypeIsRawText(containerMimeType) ? null : new ChunkExtractorWrapper(
mimeTypeIsWebm(containerMimeType) ? new WebmExtractor() : new FragmentedMp4Extractor());
segmentIndex = representation.getIndex();
}
@ -841,15 +767,22 @@ public class DashChunkSource implements ChunkSource {
return segmentIndex.getSegmentUrl(segmentNum - segmentNumShift);
}
private static boolean mimeTypeIsWebm(String mimeType) {
return mimeType.startsWith(MimeTypes.VIDEO_WEBM) || mimeType.startsWith(MimeTypes.AUDIO_WEBM)
|| mimeType.startsWith(MimeTypes.APPLICATION_WEBM);
}
private static boolean mimeTypeIsRawText(String mimeType) {
return MimeTypes.isText(mimeType) || MimeTypes.APPLICATION_TTML.equals(mimeType);
}
}
protected static final class PeriodHolder {
public final int localIndex;
public final long startTimeUs;
public final HashMap<String, RepresentationHolder> representationHolders;
private final int[] representationIndices;
public final RepresentationHolder[] representationHolders;
private DrmInitData drmInitData;
@ -859,7 +792,7 @@ public class DashChunkSource implements ChunkSource {
private long availableEndTimeUs;
public PeriodHolder(int localIndex, MediaPresentationDescription manifest, int manifestIndex,
int adaptationSetIndex, Format[] enabledFormats) {
int adaptationSetIndex) {
this.localIndex = localIndex;
Period period = manifest.getPeriod(manifestIndex);
@ -870,25 +803,14 @@ public class DashChunkSource implements ChunkSource {
startTimeUs = period.startMs * 1000;
drmInitData = getDrmInitData(adaptationSet);
if (enabledFormats.length > 1) {
representationIndices = new int[enabledFormats.length];
for (int j = 0; j < enabledFormats.length; j++) {
representationIndices[j] = getRepresentationIndex(representations, enabledFormats[j].id);
}
} else {
representationIndices = new int[] {
getRepresentationIndex(representations, enabledFormats[0].id)};
}
representationHolders = new HashMap<>();
for (int i = 0; i < representationIndices.length; i++) {
Representation representation = representations.get(representationIndices[i]);
RepresentationHolder representationHolder = new RepresentationHolder(startTimeUs,
representationHolders = new RepresentationHolder[representations.size()];
for (int i = 0; i < representationHolders.length; i++) {
Representation representation = representations.get(i);
representationHolders[i] = new RepresentationHolder(startTimeUs,
periodDurationUs, representation);
representationHolders.put(representation.format.id, representationHolder);
}
updateRepresentationIndependentProperties(periodDurationUs,
representations.get(representationIndices[0]));
representationHolders[0].representation);
}
public void updatePeriod(MediaPresentationDescription manifest, int manifestIndex,
@ -898,13 +820,12 @@ public class DashChunkSource implements ChunkSource {
List<Representation> representations = period.adaptationSets
.get(adaptationSetIndex).representations;
for (int j = 0; j < representationIndices.length; j++) {
Representation representation = representations.get(representationIndices[j]);
representationHolders.get(representation.format.id).updateRepresentation(periodDurationUs,
representation);
for (int i = 0; i < representationHolders.length; i++) {
Representation representation = representations.get(i);
representationHolders[i].updateRepresentation(periodDurationUs, representation);
}
updateRepresentationIndependentProperties(periodDurationUs,
representations.get(representationIndices[0]));
representationHolders[0].representation);
}
public long getAvailableStartTimeUs() {
@ -953,17 +874,6 @@ public class DashChunkSource implements ChunkSource {
}
}
private static int getRepresentationIndex(List<Representation> representations,
String formatId) {
for (int i = 0; i < representations.size(); i++) {
Representation representation = representations.get(i);
if (formatId.equals(representation.format.id)) {
return i;
}
}
throw new IllegalStateException("Missing format id: " + formatId);
}
private static DrmInitData getDrmInitData(AdaptationSet adaptationSet) {
if (adaptationSet.contentProtections.isEmpty()) {
return null;

View File

@ -15,8 +15,8 @@
*/
package com.google.android.exoplayer.dash.mpd;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentList;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement;
@ -287,22 +287,15 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
}
protected int getContentType(Representation representation) {
String mimeType = representation.format.mimeType;
if (TextUtils.isEmpty(mimeType)) {
String sampleMimeType = representation.format.sampleMimeType;
if (TextUtils.isEmpty(sampleMimeType)) {
return AdaptationSet.TYPE_UNKNOWN;
} else if (MimeTypes.isVideo(mimeType)) {
} else if (MimeTypes.isVideo(sampleMimeType)) {
return AdaptationSet.TYPE_VIDEO;
} else if (MimeTypes.isAudio(mimeType)) {
} else if (MimeTypes.isAudio(sampleMimeType)) {
return AdaptationSet.TYPE_AUDIO;
} else if (MimeTypes.isText(mimeType) || MimeTypes.APPLICATION_TTML.equals(mimeType)) {
} else if (mimeTypeIsRawText(sampleMimeType)) {
return AdaptationSet.TYPE_TEXT;
} else if (MimeTypes.APPLICATION_MP4.equals(mimeType)) {
// The representation uses mp4 but does not contain video or audio. Use codecs to determine
// whether the container holds text.
String codecs = representation.format.codecs;
if ("stpp".equals(codecs) || "wvtt".equals(codecs)) {
return AdaptationSet.TYPE_TEXT;
}
}
return AdaptationSet.TYPE_UNKNOWN;
}
@ -397,10 +390,22 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
segmentBase != null ? segmentBase : new SingleSegmentBase(baseUrl));
}
protected Format buildFormat(String id, String mimeType, int width, int height, float frameRate,
int audioChannels, int audioSamplingRate, int bandwidth, String language, String codecs) {
return new Format(id, mimeType, width, height, frameRate, audioChannels, audioSamplingRate,
bandwidth, language, codecs);
protected Format buildFormat(String id, String containerMimeType, int width, int height,
float frameRate, int audioChannels, int audioSamplingRate, int bitrate, String language,
String codecs) {
String sampleMimeType = getSampleMimeType(containerMimeType, codecs);
if (MimeTypes.isVideo(sampleMimeType)) {
return Format.createVideoContainerFormat(id, containerMimeType, sampleMimeType, bitrate,
width, height, frameRate, null);
} else if (MimeTypes.isAudio(sampleMimeType)) {
return Format.createAudioContainerFormat(id, containerMimeType, sampleMimeType, bitrate,
audioChannels, audioSamplingRate, null, language);
} else if (mimeTypeIsRawText(sampleMimeType)) {
return Format.createTextContainerFormat(id, containerMimeType, sampleMimeType, bitrate,
language);
} else {
return Format.createContainerFormat(id, containerMimeType, sampleMimeType, bitrate);
}
}
protected Representation buildRepresentation(String contentId, int revisionId, Format format,
@ -610,6 +615,95 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
// Utility methods.
/**
* Derives a sample mimeType from a container mimeType and codecs attribute.
*
* @param containerMimeType The mimeType of the container.
* @param codecs The codecs attribute.
* @return The derived sample mimeType, or null if it could not be derived.
*/
private static String getSampleMimeType(String containerMimeType, String codecs) {
if (MimeTypes.isAudio(containerMimeType)) {
return getAudioMediaMimeType(codecs);
} else if (MimeTypes.isVideo(containerMimeType)) {
return getVideoMediaMimeType(codecs);
} else if (mimeTypeIsRawText(containerMimeType)) {
return containerMimeType;
} else if (MimeTypes.APPLICATION_MP4.equals(containerMimeType) && "stpp".equals(codecs)) {
return MimeTypes.APPLICATION_TTML;
} else {
return null;
}
}
/**
* Derives a video sample mimeType from a codecs attribute.
*
* @param codecs The codecs attribute.
* @return The derived video mimeType, or null if it could not be derived.
*/
private static String getVideoMediaMimeType(String codecs) {
if (codecs == null) {
return null;
}
String[] codecList = codecs.split(",");
for (String codec : codecList) {
codec = codec.trim();
if (codec.startsWith("avc1") || codec.startsWith("avc3")) {
return MimeTypes.VIDEO_H264;
} else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) {
return MimeTypes.VIDEO_H265;
} else if (codec.startsWith("vp9")) {
return MimeTypes.VIDEO_VP9;
} else if (codec.startsWith("vp8")) {
return MimeTypes.VIDEO_VP8;
}
}
return null;
}
/**
* Derives a audio sample mimeType from a codecs attribute.
*
* @param codecs The codecs attribute.
* @return The derived audio mimeType, or null if it could not be derived.
*/
private static String getAudioMediaMimeType(String codecs) {
if (codecs == null) {
return null;
}
String[] codecList = codecs.split(",");
for (String codec : codecList) {
codec = codec.trim();
if (codec.startsWith("mp4a")) {
return MimeTypes.AUDIO_AAC;
} else if (codec.startsWith("ac-3") || codec.startsWith("dac3")) {
return MimeTypes.AUDIO_AC3;
} else if (codec.startsWith("ec-3") || codec.startsWith("dec3")) {
return MimeTypes.AUDIO_E_AC3;
} else if (codec.startsWith("dtsc") || codec.startsWith("dtse")) {
return MimeTypes.AUDIO_DTS;
} else if (codec.startsWith("dtsh") || codec.startsWith("dtsl")) {
return MimeTypes.AUDIO_DTS_HD;
} else if (codec.startsWith("opus")) {
return MimeTypes.AUDIO_OPUS;
} else if (codec.startsWith("vorbis")) {
return MimeTypes.AUDIO_VORBIS;
}
}
return null;
}
/**
* Returns whether a mimeType is a text sample mimeType.
*
* @param mimeType The mimeType.
* @return True if the mimeType is a text sample mimeType. False otherwise.
*/
private static boolean mimeTypeIsRawText(String mimeType) {
return MimeTypes.isText(mimeType) || MimeTypes.APPLICATION_TTML.equals(mimeType);
}
/**
* Checks two languages for consistency, returning the consistent language, or throwing an
* {@link IllegalStateException} if the languages are inconsistent.

View File

@ -15,8 +15,7 @@
*/
package com.google.android.exoplayer.dash.mpd;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.FormatWrapper;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.dash.DashSegmentIndex;
import com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase;
import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
@ -26,7 +25,7 @@ import android.net.Uri;
/**
* A DASH representation.
*/
public abstract class Representation implements FormatWrapper {
public abstract class Representation {
/**
* Identifies the piece of content to which this {@link Representation} belongs.
@ -105,11 +104,6 @@ public abstract class Representation implements FormatWrapper {
presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs();
}
@Override
public Format getFormat() {
return format;
}
/**
* Gets a {@link RangedUri} defining the location of the representation's initialization data.
* May be null if no initialization data exists.

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.DataSource;
@ -41,7 +41,7 @@ public class DefaultTrackOutput implements TrackOutput {
// Accessed by both the loading and consuming threads.
private volatile long largestParsedTimestampUs;
private volatile MediaFormat format;
private volatile Format format;
/**
* @param allocator An {@link Allocator} from which allocations for sample data can be obtained.
@ -105,7 +105,7 @@ public class DefaultTrackOutput implements TrackOutput {
/**
* The format most recently received by the output, or null if a format has yet to be received.
*/
public MediaFormat getFormat() {
public Format getFormat() {
return format;
}
@ -246,7 +246,7 @@ public class DefaultTrackOutput implements TrackOutput {
// TrackOutput implementation. Called by the loading thread.
@Override
public void format(MediaFormat format) {
public void format(Format format) {
this.format = format;
}

View File

@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer.extractor;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.util.ParsableByteArray;
import java.io.IOException;
@ -23,9 +23,10 @@ import java.io.IOException;
/**
* A dummy {@link TrackOutput} implementation.
*/
public class DummyTrackOutput implements TrackOutput {
public final class DummyTrackOutput implements TrackOutput {
@Override
public void format(MediaFormat format) {
public void format(Format format) {
// Do nothing.
}
@ -44,4 +45,5 @@ public class DummyTrackOutput implements TrackOutput {
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
// Do nothing.
}
}

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSource;
@ -360,7 +360,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
return TrackStream.NO_RESET;
}
/* package */ int readData(int track, MediaFormatHolder formatHolder, SampleHolder sampleHolder) {
/* package */ int readData(int track, FormatHolder formatHolder, SampleHolder sampleHolder) {
if (pendingResets[track] || isPendingReset()) {
return TrackStream.NOTHING_READ;
}
@ -665,7 +665,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
}
@Override
public int readData(MediaFormatHolder formatHolder, SampleHolder sampleHolder) {
public int readData(FormatHolder formatHolder, SampleHolder sampleHolder) {
return ExtractorSampleSource.this.readData(track, formatHolder, sampleHolder);
}

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.util.ParsableByteArray;
@ -29,11 +29,11 @@ import java.io.IOException;
public interface TrackOutput {
/**
* Invoked when the {@link MediaFormat} of the track has been extracted from the stream.
* Invoked when the {@link Format} of the track has been extracted from the stream.
*
* @param format The extracted {@link MediaFormat}.
* @param format The extracted {@link Format}.
*/
void format(MediaFormat format);
void format(Format format);
/**
* Invoked to write sample data to the output.

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.flv;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
import com.google.android.exoplayer.util.MimeTypes;
@ -86,10 +86,10 @@ import java.util.Collections;
data.readBytes(audioSpecifiConfig, 0, audioSpecifiConfig.length);
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
audioSpecifiConfig);
MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, audioParams.second, audioParams.first,
Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC,
Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
Collections.singletonList(audioSpecifiConfig), null);
output.format(mediaFormat);
output.format(format);
hasOutputFormat = true;
} else if (packetType == AAC_PACKET_TYPE_AAC_RAW) {
// Sample audio AAC frames

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.flv;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.Assertions;
@ -95,10 +95,11 @@ import java.util.List;
nalUnitLengthFieldLength = avcData.nalUnitLengthFieldLength;
// Construct and output the format.
MediaFormat mediaFormat = MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_H264,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, avcData.width, avcData.height,
avcData.initializationData, MediaFormat.NO_VALUE, avcData.pixelWidthAspectRatio);
output.format(mediaFormat);
Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264,
Format.NO_VALUE, Format.NO_VALUE, avcData.width, avcData.height,
Format.NO_VALUE, avcData.initializationData, Format.NO_VALUE,
avcData.pixelWidthAspectRatio);
output.format(format);
hasOutputFormat = true;
} else if (packetType == AVC_PACKET_TYPE_AVC_NALU) {
// TODO: Deduplicate with Mp4Extractor.
@ -135,7 +136,7 @@ import java.util.List;
}
/**
* Builds initialization data for a {@link MediaFormat} from H.264 (AVC) codec private data.
* Builds initialization data for a {@link Format} from H.264 (AVC) codec private data.
*
* @return The AvcSequenceHeader data needed to initialize the video codec.
* @throws ParserException If the initialization data could not be built.
@ -157,8 +158,8 @@ import java.util.List;
}
float pixelWidthAspectRatio = 1;
int width = MediaFormat.NO_VALUE;
int height = MediaFormat.NO_VALUE;
int width = Format.NO_VALUE;
int height = Format.NO_VALUE;
if (numSequenceParameterSets > 0) {
// Parse the first sequence parameter set to obtain pixelWidthAspectRatio.
ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0));

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.mp3;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorInput;
@ -119,8 +119,8 @@ public final class Mp3Extractor implements Extractor {
if (seeker == null) {
setupSeeker(input);
extractorOutput.seekMap(seeker);
trackOutput.format(MediaFormat.createAudioFormat(null, synchronizedHeader.mimeType,
MediaFormat.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels,
trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType,
Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels,
synchronizedHeader.sampleRate, null, null));
}
return readSample(input);

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.mp4;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.util.Ac3Util;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
@ -69,9 +69,9 @@ import java.util.List;
StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id,
tkhdData.rotationDegrees, mdhdData.second, isQuickTime);
Pair<long[], long[]> edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts));
return stsdData.mediaFormat == null ? null
return stsdData.format == null ? null
: new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs,
stsdData.mediaFormat, stsdData.trackEncryptionBoxes, stsdData.nalUnitLengthFieldLength,
stsdData.format, stsdData.trackEncryptionBoxes, stsdData.nalUnitLengthFieldLength,
edtsData.first, edtsData.second);
}
@ -467,17 +467,17 @@ import java.util.List;
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId,
language, isQuickTime, out, i);
} else if (childAtomType == Atom.TYPE_TTML) {
out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId),
MimeTypes.APPLICATION_TTML, MediaFormat.NO_VALUE, language);
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
MimeTypes.APPLICATION_TTML, Format.NO_VALUE, language);
} else if (childAtomType == Atom.TYPE_tx3g) {
out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId),
MimeTypes.APPLICATION_TX3G, MediaFormat.NO_VALUE, language);
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
MimeTypes.APPLICATION_TX3G, Format.NO_VALUE, language);
} else if (childAtomType == Atom.TYPE_wvtt) {
out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId),
MimeTypes.APPLICATION_MP4VTT, MediaFormat.NO_VALUE, language);
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
MimeTypes.APPLICATION_MP4VTT, Format.NO_VALUE, language);
} else if (childAtomType == Atom.TYPE_stpp) {
out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId),
MimeTypes.APPLICATION_TTML, MediaFormat.NO_VALUE, language,
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
MimeTypes.APPLICATION_TTML, Format.NO_VALUE, language,
0 /* subsample timing is absolute */);
}
stsd.setPosition(childStartPosition + childAtomSize);
@ -548,8 +548,8 @@ import java.util.List;
return;
}
out.mediaFormat = MediaFormat.createVideoFormat(Integer.toString(trackId), mimeType,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, width, height, initializationData,
out.format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType,
Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE, initializationData,
rotationDegrees, pixelWidthHeightRatio);
}
@ -786,19 +786,17 @@ import java.util.List;
// TODO: Choose the right AC-3 track based on the contents of dac3/dec3.
// TODO: Add support for encryption (by setting out.trackEncryptionBoxes).
parent.setPosition(Atom.HEADER_SIZE + childAtomPosition);
out.mediaFormat = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId),
language);
out.format = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId), language);
return;
} else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) {
parent.setPosition(Atom.HEADER_SIZE + childAtomPosition);
out.mediaFormat = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId),
language);
out.format = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), language);
return;
} else if ((atomType == Atom.TYPE_dtsc || atomType == Atom.TYPE_dtse
|| atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl)
&& childAtomType == Atom.TYPE_ddts) {
out.mediaFormat = MediaFormat.createAudioFormat(Integer.toString(trackId), mimeType,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, channelCount, sampleRate, null, language);
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, language);
return;
}
childAtomPosition += childAtomSize;
@ -809,8 +807,8 @@ import java.util.List;
return;
}
out.mediaFormat = MediaFormat.createAudioFormat(Integer.toString(trackId), mimeType,
MediaFormat.NO_VALUE, sampleSize, channelCount, sampleRate,
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType,
Format.NO_VALUE, sampleSize, channelCount, sampleRate,
initializationData == null ? null : Collections.singletonList(initializationData),
language);
}
@ -945,7 +943,7 @@ import java.util.List;
public final TrackEncryptionBox[] trackEncryptionBoxes;
public MediaFormat mediaFormat;
public Format format;
public int nalUnitLengthFieldLength;
public StsdData(int numberOfEntries) {

View File

@ -314,7 +314,7 @@ public final class FragmentedMp4Extractor implements Extractor {
if (track == null) {
throw new ParserException("Track type not supported.");
}
trackOutput.format(track.mediaFormat);
trackOutput.format(track.format);
}
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {

View File

@ -303,7 +303,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
// Each sample has up to three bytes of overhead for the start code that replaces its length.
// Allow ten source samples per output sample, like the platform extractor.
int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
mp4Track.trackOutput.format(track.mediaFormat.copyWithMaxInputSize(maxInputSize));
mp4Track.trackOutput.format(track.format.copyWithMaxInputSize(maxInputSize));
durationUs = Math.max(durationUs, track.durationUs);
tracks.add(mp4Track);

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.mp4;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.util.Util;
/**
@ -57,9 +57,9 @@ public final class Track {
public final long durationUs;
/**
* The media format.
* The format.
*/
public final MediaFormat mediaFormat;
public final Format format;
/**
* Track encryption boxes for the different track sample descriptions. Entries may be null.
@ -83,14 +83,14 @@ public final class Track {
public final int nalUnitLengthFieldLength;
public Track(int id, int type, long timescale, long movieTimescale, long durationUs,
MediaFormat mediaFormat, TrackEncryptionBox[] sampleDescriptionEncryptionBoxes,
Format format, TrackEncryptionBox[] sampleDescriptionEncryptionBoxes,
int nalUnitLengthFieldLength, long[] editListDurations, long[] editListMediaTimes) {
this.id = id;
this.type = type;
this.timescale = timescale;
this.movieTimescale = movieTimescale;
this.durationUs = durationUs;
this.mediaFormat = mediaFormat;
this.format = format;
this.sampleDescriptionEncryptionBoxes = sampleDescriptionEncryptionBoxes;
this.nalUnitLengthFieldLength = nalUnitLengthFieldLength;
this.editListDurations = editListDurations;

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.ogg;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorInput;
@ -99,7 +99,7 @@ public final class OggVorbisExtractor implements Extractor {
codecInitializationData.clear();
codecInitializationData.add(idHeader.data);
codecInitializationData.add(vorbisSetup.setupHeaderData);
trackOutput.format(MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_VORBIS,
trackOutput.format(Format.createAudioSampleFormat(null, MimeTypes.AUDIO_VORBIS,
idHeader.bitrateNominal, OGG_MAX_SEGMENT_SIZE * 255, idHeader.channels,
idHeader.sampleRate, codecInitializationData, null));
}

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.ts;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.Ac3Util;
import com.google.android.exoplayer.util.ParsableBitArray;
@ -45,7 +45,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
// Used when parsing the header.
private long sampleDurationUs;
private MediaFormat mediaFormat;
private Format format;
private int sampleSize;
// Used when reading the samples.
@ -159,11 +159,11 @@ import com.google.android.exoplayer.util.ParsableByteArray;
* Parses the sample header.
*/
private void parseHeader() {
if (mediaFormat == null) {
mediaFormat = isEac3
if (format == null) {
format = isEac3
? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, null)
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, null);
output.format(mediaFormat);
output.format(format);
}
sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data)
: Ac3Util.parseAc3SyncframeSize(headerScratchBits.data);
@ -173,7 +173,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
// In this class a sample is an access unit (syncframe in AC-3), but the MediaFormat sample rate
// specifies the number of PCM audio samples per second.
sampleDurationUs =
(int) (C.MICROS_PER_SECOND * audioSamplesPerSyncframe / mediaFormat.sampleRate);
(int) (C.MICROS_PER_SECOND * audioSamplesPerSyncframe / format.sampleRate);
}
}

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.ts;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
import com.google.android.exoplayer.util.MimeTypes;
@ -81,7 +81,7 @@ import java.util.Collections;
public AdtsReader(TrackOutput output, TrackOutput id3Output) {
super(output);
this.id3Output = id3Output;
id3Output.format(MediaFormat.createId3Format());
id3Output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, Format.NO_VALUE));
adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]);
id3HeaderBuffer = new ParsableByteArray(Arrays.copyOf(ID3_IDENTIFIER, ID3_HEADER_SIZE));
setFindingSampleState();
@ -258,13 +258,13 @@ import java.util.Collections;
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
audioSpecificConfig);
MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, audioParams.second, audioParams.first,
Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, Format.NO_VALUE,
Format.NO_VALUE, audioParams.second, audioParams.first,
Collections.singletonList(audioSpecificConfig), null);
// In this class a sample is an access unit, but the MediaFormat sample rate specifies the
// number of PCM audio samples per second.
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / mediaFormat.sampleRate;
output.format(mediaFormat);
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate;
output.format(format);
hasOutputFormat = true;
} else {
adtsScratch.skipBits(10);

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.ts;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.DtsUtil;
import com.google.android.exoplayer.util.ParsableByteArray;
@ -44,7 +44,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
// Used when parsing the header.
private long sampleDurationUs;
private MediaFormat mediaFormat;
private Format format;
private int sampleSize;
// Used when reading the samples.
@ -152,15 +152,15 @@ import com.google.android.exoplayer.util.ParsableByteArray;
*/
private void parseHeader() {
byte[] frameData = headerScratchBytes.data;
if (mediaFormat == null) {
mediaFormat = DtsUtil.parseDtsFormat(frameData, null, null);
output.format(mediaFormat);
if (format == null) {
format = DtsUtil.parseDtsFormat(frameData, null, null);
output.format(format);
}
sampleSize = DtsUtil.getDtsFrameSize(frameData);
// In this class a sample is an access unit (frame in DTS), but the MediaFormat sample rate
// In this class a sample is an access unit (frame in DTS), but the format's sample rate
// specifies the number of PCM audio samples per second.
sampleDurationUs = (int) (C.MICROS_PER_SECOND
* DtsUtil.parseDtsAudioSampleCount(frameData) / mediaFormat.sampleRate);
* DtsUtil.parseDtsAudioSampleCount(frameData) / format.sampleRate);
}
}

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.ts;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.NalUnitUtil;
@ -113,7 +113,7 @@ import java.util.Collections;
int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0;
if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) {
// The csd data is complete, so we can parse and output the media format.
Pair<MediaFormat, Long> result = parseCsdBuffer(csdBuffer);
Pair<Format, Long> result = parseCsdBuffer(csdBuffer);
output.format(result.first);
frameDurationUs = result.second;
hasOutputFormat = true;
@ -151,13 +151,13 @@ import java.util.Collections;
}
/**
* Parses the {@link MediaFormat} and frame duration from a csd buffer.
* Parses the {@link Format} and frame duration from a csd buffer.
*
* @param csdBuffer The csd buffer.
* @return A pair consisting of the {@link MediaFormat} and the frame duration in microseconds, or
* @return A pair consisting of the {@link Format} and the frame duration in microseconds, or
* 0 if the duration could not be determined.
*/
private static Pair<MediaFormat, Long> parseCsdBuffer(CsdBuffer csdBuffer) {
private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer) {
byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
int firstByte = csdData[4] & 0xFF;
@ -183,9 +183,9 @@ import java.util.Collections;
break;
}
MediaFormat format = MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_MPEG2,
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, width, height,
Collections.singletonList(csdData), MediaFormat.NO_VALUE, pixelWidthHeightRatio);
Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_MPEG2, Format.NO_VALUE,
Format.NO_VALUE, width, height, Format.NO_VALUE, Collections.singletonList(csdData),
Format.NO_VALUE, pixelWidthHeightRatio);
long frameDurationUs = 0;
int frameRateCodeMinusOne = (csdData[7] & 0x0F) - 1;

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.ts;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
import com.google.android.exoplayer.util.CodecSpecificDataUtil.SpsData;
@ -198,7 +198,7 @@ import java.util.List;
}
}
private static MediaFormat parseMediaFormat(NalUnitTargetBuffer sps, NalUnitTargetBuffer pps) {
private static Format parseMediaFormat(NalUnitTargetBuffer sps, NalUnitTargetBuffer pps) {
List<byte[]> initializationData = new ArrayList<>();
initializationData.add(Arrays.copyOf(sps.nalData, sps.nalLength));
initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));
@ -209,9 +209,9 @@ import java.util.List;
bitArray.skipBits(32); // NAL header
SpsData parsedSpsData = CodecSpecificDataUtil.parseSpsNalUnit(bitArray);
return MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, parsedSpsData.width, parsedSpsData.height, initializationData,
MediaFormat.NO_VALUE, parsedSpsData.pixelWidthAspectRatio);
return Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, Format.NO_VALUE,
Format.NO_VALUE, parsedSpsData.width, parsedSpsData.height, Format.NO_VALUE,
initializationData, Format.NO_VALUE, parsedSpsData.pixelWidthAspectRatio);
}
/**

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.ts;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.NalUnitUtil;
@ -191,7 +191,7 @@ import java.util.Collections;
}
}
private static MediaFormat parseMediaFormat(NalUnitTargetBuffer vps, NalUnitTargetBuffer sps,
private static Format parseMediaFormat(NalUnitTargetBuffer vps, NalUnitTargetBuffer sps,
NalUnitTargetBuffer pps) {
// Build codec-specific data.
byte[] csd = new byte[vps.nalLength + sps.nalLength + pps.nalLength];
@ -297,9 +297,9 @@ import java.util.Collections;
}
}
return MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_H265, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, picWidthInLumaSamples, picHeightInLumaSamples,
Collections.singletonList(csd), MediaFormat.NO_VALUE, pixelWidthHeightRatio);
return Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H265, Format.NO_VALUE,
Format.NO_VALUE, picWidthInLumaSamples, picHeightInLumaSamples, Format.NO_VALUE,
Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio);
}
/** Skips scaling_list_data(). See H.265/HEVC (2014) 7.3.4. */

View File

@ -16,8 +16,9 @@
package com.google.android.exoplayer.extractor.ts;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.ParsableByteArray;
/**
@ -34,7 +35,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
public Id3Reader(TrackOutput output) {
super(output);
output.format(MediaFormat.createId3Format());
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, Format.NO_VALUE));
}
@Override

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.ts;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.MpegAudioHeader;
import com.google.android.exoplayer.util.ParsableByteArray;
@ -160,10 +160,9 @@ import com.google.android.exoplayer.util.ParsableByteArray;
frameSize = header.frameSize;
if (!hasOutputFormat) {
frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate;
MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, header.mimeType,
MediaFormat.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels,
header.sampleRate, null, null);
output.format(mediaFormat);
Format format = Format.createAudioSampleFormat(null, header.mimeType, Format.NO_VALUE,
MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate, null, null);
output.format(format);
hasOutputFormat = true;
}

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.ts;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.text.eia608.Eia608Parser;
import com.google.android.exoplayer.util.MimeTypes;
@ -32,8 +32,8 @@ import com.google.android.exoplayer.util.ParsableByteArray;
public SeiReader(TrackOutput output) {
super(output);
output.format(MediaFormat.createTextFormat(null, MimeTypes.APPLICATION_EIA608,
MediaFormat.NO_VALUE, null));
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_EIA608, Format.NO_VALUE,
null));
}
@Override

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.webm;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData;
@ -1131,8 +1131,8 @@ public final class WebmExtractor implements Extractor {
public byte[] codecPrivate;
// Video elements.
public int width = MediaFormat.NO_VALUE;
public int height = MediaFormat.NO_VALUE;
public int width = Format.NO_VALUE;
public int height = Format.NO_VALUE;
// Audio elements. Initially set to their default values.
public int channelCount = 1;
@ -1152,7 +1152,7 @@ public final class WebmExtractor implements Extractor {
*/
public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException {
String mimeType;
int maxInputSize = MediaFormat.NO_VALUE;
int maxInputSize = Format.NO_VALUE;
List<byte[]> initializationData = null;
switch (codecId) {
case CODEC_ID_VP8:
@ -1231,19 +1231,18 @@ public final class WebmExtractor implements Extractor {
throw new ParserException("Unrecognized codec identifier.");
}
MediaFormat format;
Format format;
// TODO: Consider reading the name elements of the tracks and, if present, incorporating them
// into the trackId passed when creating the formats.
if (MimeTypes.isAudio(mimeType)) {
format = MediaFormat.createAudioFormat(Integer.toString(trackId), mimeType,
MediaFormat.NO_VALUE, maxInputSize, channelCount, sampleRate,
initializationData, language);
format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType,
Format.NO_VALUE, maxInputSize, channelCount, sampleRate, initializationData, language);
} else if (MimeTypes.isVideo(mimeType)) {
format = MediaFormat.createVideoFormat(Integer.toString(trackId), mimeType,
MediaFormat.NO_VALUE, maxInputSize, width, height, initializationData);
format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType,
Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData);
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
format = MediaFormat.createTextFormat(Integer.toString(trackId), mimeType,
MediaFormat.NO_VALUE, language);
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, Format.NO_VALUE,
language);
} else {
throw new ParserException("Unexpected MIME type.");
}
@ -1253,9 +1252,9 @@ public final class WebmExtractor implements Extractor {
}
/**
* Builds initialization data for a {@link MediaFormat} from H.264 (AVC) codec private data.
* Builds initialization data for a {@link Format} from H.264 (AVC) codec private data.
*
* @return The initialization data for the {@link MediaFormat}.
* @return The initialization data for the {@link Format}.
* @throws ParserException If the initialization data could not be built.
*/
private static Pair<List<byte[]>, Integer> parseAvcCodecPrivate(ParsableByteArray buffer)
@ -1283,9 +1282,9 @@ public final class WebmExtractor implements Extractor {
}
/**
* Builds initialization data for a {@link MediaFormat} from H.265 (HEVC) codec private data.
* Builds initialization data for a {@link Format} from H.265 (HEVC) codec private data.
*
* @return The initialization data for the {@link MediaFormat}.
* @return The initialization data for the {@link Format}.
* @throws ParserException If the initialization data could not be built.
*/
private static Pair<List<byte[]>, Integer> parseHevcCodecPrivate(ParsableByteArray parent)
@ -1336,9 +1335,9 @@ public final class WebmExtractor implements Extractor {
}
/**
* Builds initialization data for a {@link MediaFormat} from Vorbis codec private data.
* Builds initialization data for a {@link Format} from Vorbis codec private data.
*
* @return The initialization data for the {@link MediaFormat}.
* @return The initialization data for the {@link Format}.
* @throws ParserException If the initialization data could not be built.
*/
private static List<byte[]> parseVorbisCodecPrivate(byte[] codecPrivate)

View File

@ -17,12 +17,11 @@ package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.BehindLiveWindowException;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener;
import com.google.android.exoplayer.chunk.Chunk;
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
import com.google.android.exoplayer.chunk.DataChunk;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer.extractor.ts.AdtsExtractor;
@ -156,8 +155,6 @@ public class HlsChunkSource {
private HlsMediaPlaylist[] enabledVariantPlaylists;
private long[] enabledVariantLastPlaylistLoadTimesMs;
private long[] enabledVariantBlacklistTimes;
private int adaptiveMaxWidth;
private int adaptiveMaxHeight;
private int selectedVariantIndex;
/**
@ -246,10 +243,10 @@ public class HlsChunkSource {
if (playlist.type == HlsPlaylist.TYPE_MASTER) {
masterPlaylist = (HlsMasterPlaylist) playlist;
} else {
Format format = new Format("0", MimeTypes.APPLICATION_M3U8, -1, -1, -1, -1, -1, -1, null,
null);
Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null,
Format.NO_VALUE);
List<Variant> variants = new ArrayList<>();
variants.add(new Variant(baseUri, format));
variants.add(new Variant(baseUri, format, null));
masterPlaylist = new HlsMasterPlaylist(baseUri, variants,
Collections.<Variant>emptyList());
}
@ -332,8 +329,6 @@ public class HlsChunkSource {
});
// Determine the initial variant index and maximum video dimensions.
selectedVariantIndex = 0;
int maxWidth = -1;
int maxHeight = -1;
int minOriginalVariantIndex = Integer.MAX_VALUE;
for (int i = 0; i < enabledVariants.length; i++) {
int originalVariantIndex = masterPlaylist.variants.indexOf(enabledVariants[i]);
@ -341,18 +336,6 @@ public class HlsChunkSource {
minOriginalVariantIndex = originalVariantIndex;
selectedVariantIndex = i;
}
Format variantFormat = enabledVariants[i].format;
maxWidth = Math.max(variantFormat.width, maxWidth);
maxHeight = Math.max(variantFormat.height, maxHeight);
}
if (tracks.length > 1) {
// TODO: We should allow the default values to be passed through the constructor.
// TODO: Print a warning if resolution tags are omitted.
maxWidth = maxWidth > 0 ? maxWidth : 1920;
maxHeight = maxHeight > 0 ? maxHeight : 1080;
} else {
adaptiveMaxWidth = -1;
adaptiveMaxHeight = -1;
}
}
@ -397,7 +380,7 @@ public class HlsChunkSource {
} else {
nextVariantIndex = getNextVariantIndex(previousTsChunk, playbackPositionUs);
switchingVariantSpliced = previousTsChunk != null
&& !enabledVariants[nextVariantIndex].format.equals(previousTsChunk.format)
&& enabledVariants[nextVariantIndex].format != previousTsChunk.format
&& adaptiveMode == ADAPTIVE_MODE_SPLICE;
}
@ -490,11 +473,11 @@ public class HlsChunkSource {
// case below.
Extractor extractor = new AdtsExtractor(startTimeUs);
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE);
switchingVariantSpliced);
} else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) {
Extractor extractor = new Mp3Extractor(startTimeUs);
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE);
switchingVariantSpliced);
} else if (lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION)
|| lastPathSegment.endsWith(VTT_FILE_EXTENSION)) {
PtsTimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(false,
@ -505,12 +488,12 @@ public class HlsChunkSource {
// a discontinuity sequence greater than the one that this source is trying to start at.
return;
}
Extractor extractor = new WebvttExtractor(timestampAdjuster);
Extractor extractor = new WebvttExtractor(format.language, timestampAdjuster);
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE);
switchingVariantSpliced);
} else if (previousTsChunk == null
|| previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|| !format.equals(previousTsChunk.format)) {
|| format != previousTsChunk.format) {
// MPEG-2 TS segments, but we need a new extractor.
PtsTimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(true,
segment.discontinuitySequenceNumber, startTimeUs);
@ -520,7 +503,7 @@ public class HlsChunkSource {
}
Extractor extractor = new TsExtractor(timestampAdjuster);
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced, adaptiveMaxWidth, adaptiveMaxHeight);
switchingVariantSpliced);
} else {
// MPEG-2 TS segments, and we need to continue using the same extractor.
extractorWrapper = previousTsChunk.extractorWrapper;
@ -566,19 +549,19 @@ public class HlsChunkSource {
InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e;
int responseCode = responseCodeException.responseCode;
if (responseCode == 404 || responseCode == 410) {
int variantIndex;
int enabledVariantIndex;
if (chunk instanceof TsChunk) {
TsChunk tsChunk = (TsChunk) chunk;
variantIndex = getVariantIndex(tsChunk.format);
enabledVariantIndex = getEnabledVariantIndex(tsChunk.format);
} else if (chunk instanceof MediaPlaylistChunk) {
MediaPlaylistChunk playlistChunk = (MediaPlaylistChunk) chunk;
variantIndex = playlistChunk.variantIndex;
enabledVariantIndex = playlistChunk.variantIndex;
} else {
EncryptionKeyChunk encryptionChunk = (EncryptionKeyChunk) chunk;
variantIndex = encryptionChunk.variantIndex;
enabledVariantIndex = encryptionChunk.variantIndex;
}
boolean alreadyBlacklisted = enabledVariantBlacklistTimes[variantIndex] != 0;
enabledVariantBlacklistTimes[variantIndex] = SystemClock.elapsedRealtime();
boolean alreadyBlacklisted = enabledVariantBlacklistTimes[enabledVariantIndex] != 0;
enabledVariantBlacklistTimes[enabledVariantIndex] = SystemClock.elapsedRealtime();
if (alreadyBlacklisted) {
// The playlist was already blacklisted.
Log.w(TAG, "Already blacklisted variant (" + responseCode + "): "
@ -593,7 +576,7 @@ public class HlsChunkSource {
// This was the last non-blacklisted playlist. Don't blacklist it.
Log.w(TAG, "Final variant not blacklisted (" + responseCode + "): "
+ chunk.dataSpec.uri);
enabledVariantBlacklistTimes[variantIndex] = 0;
enabledVariantBlacklistTimes[enabledVariantIndex] = 0;
return false;
}
}
@ -646,7 +629,7 @@ public class HlsChunkSource {
}
private static boolean variantHasExplicitCodecWithPrefix(Variant variant, String prefix) {
String codecs = variant.format.codecs;
String codecs = variant.codecs;
if (TextUtils.isEmpty(codecs)) {
return false;
}
@ -795,9 +778,9 @@ public class HlsChunkSource {
}
}
private int getVariantIndex(Format format) {
private int getEnabledVariantIndex(Format format) {
for (int i = 0; i < enabledVariants.length; i++) {
if (enabledVariants[i].format.equals(format)) {
if (enabledVariants[i].format == format) {
return i;
}
}

View File

@ -15,9 +15,8 @@
*/
package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
import com.google.android.exoplayer.extractor.Extractor;
@ -27,7 +26,6 @@ import com.google.android.exoplayer.extractor.SeekMap;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes;
import android.util.SparseArray;
@ -45,10 +43,8 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
private final Extractor extractor;
private final SparseArray<DefaultTrackOutput> sampleQueues;
private final boolean shouldSpliceIn;
private final int adaptiveMaxWidth;
private final int adaptiveMaxHeight;
private MediaFormat[] sampleQueueFormats;
private Format[] sampleFormats;
private Allocator allocator;
private volatile boolean tracksBuilt;
@ -58,14 +54,12 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
private boolean spliceConfigured;
public HlsExtractorWrapper(int trigger, Format format, long startTimeUs, Extractor extractor,
boolean shouldSpliceIn, int adaptiveMaxWidth, int adaptiveMaxHeight) {
boolean shouldSpliceIn) {
this.trigger = trigger;
this.format = format;
this.startTimeUs = startTimeUs;
this.extractor = extractor;
this.shouldSpliceIn = shouldSpliceIn;
this.adaptiveMaxWidth = adaptiveMaxWidth;
this.adaptiveMaxHeight = adaptiveMaxHeight;
sampleQueues = new SparseArray<>();
}
@ -92,14 +86,9 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
}
}
prepared = true;
sampleQueueFormats = new MediaFormat[sampleQueues.size()];
for (int i = 0; i < sampleQueueFormats.length; i++) {
MediaFormat format = sampleQueues.valueAt(i).getFormat();
if (MimeTypes.isVideo(format.mimeType) && (adaptiveMaxWidth != MediaFormat.NO_VALUE
|| adaptiveMaxHeight != MediaFormat.NO_VALUE)) {
format = format.copyWithMaxVideoDimensions(adaptiveMaxWidth, adaptiveMaxHeight);
}
sampleQueueFormats[i] = format;
sampleFormats = new Format[sampleQueues.size()];
for (int i = 0; i < sampleFormats.length; i++) {
sampleFormats[i] = sampleQueues.valueAt(i).getFormat();
}
}
return prepared;
@ -175,16 +164,16 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
}
/**
* Gets the {@link MediaFormat} of the specified track.
* Gets the {@link Format} of the samples belonging to a specified track.
* <p>
* This method must only be called after the extractor has been prepared.
*
* @param track The track index.
* @return The corresponding format.
* @return The corresponding sample format.
*/
public MediaFormat getMediaFormat(int track) {
public Format getSampleFormat(int track) {
Assertions.checkState(isPrepared());
return sampleQueueFormats[track];
return sampleFormats[track];
}
/**

View File

@ -16,8 +16,8 @@
package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.hls.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer.upstream.UriLoadable;
import com.google.android.exoplayer.util.MimeTypes;
@ -158,9 +158,9 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
String subtitleName = HlsParserUtil.parseStringAttr(line, NAME_ATTR_REGEX, NAME_ATTR);
String uri = HlsParserUtil.parseStringAttr(line, URI_ATTR_REGEX, URI_ATTR);
String language = HlsParserUtil.parseOptionalStringAttr(line, LANGUAGE_ATTR_REGEX);
Format format = new Format(subtitleName, MimeTypes.APPLICATION_M3U8, -1, -1, -1, -1, -1,
-1, language, codecs);
subtitles.add(new Variant(uri, format));
Format format = Format.createTextContainerFormat(subtitleName, MimeTypes.APPLICATION_M3U8,
MimeTypes.TEXT_VTT, bitrate, language);
subtitles.add(new Variant(uri, format, null));
} else {
// TODO: Support other types of media tag.
}
@ -191,9 +191,9 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
if (name == null) {
name = Integer.toString(variants.size());
}
Format format = new Format(name, MimeTypes.APPLICATION_M3U8, width, height, -1, -1, -1,
bitrate, null, codecs);
variants.add(new Variant(line, format));
Format format = Format.createVideoContainerFormat(name, MimeTypes.APPLICATION_M3U8, null,
bitrate, width, height, Format.NO_VALUE, null);
variants.add(new Variant(line, format, codecs));
bitrate = 0;
codecs = null;
name = null;

View File

@ -16,16 +16,15 @@
package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackGroup;
import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener;
import com.google.android.exoplayer.chunk.Chunk;
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.Loader.Loadable;
import com.google.android.exoplayer.util.Assertions;
@ -84,7 +83,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
// Indexed by group.
private boolean[] groupEnabledStates;
private boolean[] pendingResets;
private MediaFormat[] downstreamMediaFormats;
private Format[] downstreamSampleFormats;
private long downstreamPositionUs;
private long lastSeekPositionUs;
@ -194,7 +193,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
public TrackStream enable(int group, int[] tracks, long positionUs) {
Assertions.checkState(prepared);
setTrackGroupEnabledState(group, true);
downstreamMediaFormats[group] = null;
downstreamSampleFormats[group] = null;
pendingResets[group] = false;
downstreamFormat = null;
boolean wasLoadControlRegistered = loadControlRegistered;
@ -288,8 +287,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
return TrackStream.NO_RESET;
}
/* package */ int readData(int group, MediaFormatHolder formatHolder,
SampleHolder sampleHolder) {
/* package */ int readData(int group, FormatHolder formatHolder, SampleHolder sampleHolder) {
Assertions.checkState(prepared);
if (pendingResets[group] || isPendingReset()) {
@ -323,10 +321,10 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
}
}
MediaFormat mediaFormat = extractor.getMediaFormat(group);
if (mediaFormat != null && !mediaFormat.equals(downstreamMediaFormats[group])) {
formatHolder.format = mediaFormat;
downstreamMediaFormats[group] = mediaFormat;
Format sampleFormat = extractor.getSampleFormat(group);
if (sampleFormat != null && !sampleFormat.equals(downstreamSampleFormats[group])) {
formatHolder.format = sampleFormat;
downstreamSampleFormats[group] = sampleFormat;
return TrackStream.FORMAT_READ;
}
@ -485,11 +483,11 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
int primaryExtractorTrackIndex = -1;
int extractorTrackCount = extractor.getTrackCount();
for (int i = 0; i < extractorTrackCount; i++) {
String mimeType = extractor.getMediaFormat(i).mimeType;
String sampleMimeType = extractor.getSampleFormat(i).sampleMimeType;
int trackType;
if (MimeTypes.isVideo(mimeType)) {
if (MimeTypes.isVideo(sampleMimeType)) {
trackType = PRIMARY_TYPE_VIDEO;
} else if (MimeTypes.isAudio(mimeType)) {
} else if (MimeTypes.isAudio(sampleMimeType)) {
trackType = PRIMARY_TYPE_AUDIO;
} else {
trackType = PRIMARY_TYPE_NONE;
@ -512,20 +510,20 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
trackGroups = new TrackGroup[extractorTrackCount];
groupEnabledStates = new boolean[extractorTrackCount];
pendingResets = new boolean[extractorTrackCount];
downstreamMediaFormats = new MediaFormat[extractorTrackCount];
downstreamSampleFormats = new Format[extractorTrackCount];
// Construct the set of exposed track groups.
for (int i = 0; i < extractorTrackCount; i++) {
MediaFormat format = extractor.getMediaFormat(i);
Format sampleFormat = extractor.getSampleFormat(i);
if (i == primaryExtractorTrackIndex) {
MediaFormat[] formats = new MediaFormat[chunkSourceTrackCount];
Format[] formats = new Format[chunkSourceTrackCount];
for (int j = 0; j < chunkSourceTrackCount; j++) {
formats[j] = copyWithFixedTrackInfo(format, chunkSource.getTrackFormat(j));
formats[j] = getSampleFormat(chunkSource.getTrackFormat(j), sampleFormat);
}
trackGroups[i] = new TrackGroup(true, formats);
primaryTrackGroupIndex = i;
} else {
trackGroups[i] = new TrackGroup(format);
trackGroups[i] = new TrackGroup(sampleFormat);
}
}
}
@ -543,18 +541,18 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
}
/**
* Copies a provided {@link MediaFormat}, incorporating information from the {@link Format} of
* a fixed (i.e. non-adaptive) track.
* Derives a sample format corresponding to a given container format, by combining it with sample
* level information obtained from a second sample format.
*
* @param format The {@link MediaFormat} to copy.
* @param fixedTrackFormat The {@link Format} to incorporate into the copy.
* @return The copied {@link MediaFormat}.
* @param containerFormat The container format for which the sample format should be derived.
* @param sampleFormat A sample format from which to obtain sample level information.
* @return The derived sample format.
*/
private static MediaFormat copyWithFixedTrackInfo(MediaFormat format, Format fixedTrackFormat) {
int width = fixedTrackFormat.width == -1 ? MediaFormat.NO_VALUE : fixedTrackFormat.width;
int height = fixedTrackFormat.height == -1 ? MediaFormat.NO_VALUE : fixedTrackFormat.height;
return format.copyWithFixedTrackInfo(fixedTrackFormat.id, fixedTrackFormat.bitrate, width,
height, fixedTrackFormat.language);
private static Format getSampleFormat(Format containerFormat, Format sampleFormat) {
int width = containerFormat.width == -1 ? Format.NO_VALUE : containerFormat.width;
int height = containerFormat.height == -1 ? Format.NO_VALUE : containerFormat.height;
return sampleFormat.copyWithContainerInfo(containerFormat.id, containerFormat.bitrate, width,
height, containerFormat.language);
}
/**
@ -816,7 +814,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
}
@Override
public int readData(MediaFormatHolder formatHolder, SampleHolder sampleHolder) {
public int readData(FormatHolder formatHolder, SampleHolder sampleHolder) {
return HlsSampleSource.this.readData(group, formatHolder, sampleHolder);
}

View File

@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.chunk.MediaChunk;
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
import com.google.android.exoplayer.extractor.Extractor;

View File

@ -15,25 +15,21 @@
*/
package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.FormatWrapper;
import com.google.android.exoplayer.Format;
/**
* Variant stream reference.
*/
public final class Variant implements FormatWrapper {
public final class Variant {
public final String url;
public final Format format;
public final String codecs;
public Variant(String url, Format format) {
public Variant(String url, Format format, String codecs) {
this.url = url;
this.format = format;
}
@Override
public Format getFormat() {
return format;
this.codecs = codecs;
}
}

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ExtractorInput;
@ -50,6 +50,7 @@ import java.util.regex.Pattern;
private static final Pattern LOCAL_TIMESTAMP = Pattern.compile("LOCAL:([^,]+)");
private static final Pattern MEDIA_TIMESTAMP = Pattern.compile("MPEGTS:(\\d+)");
private final String language;
private final PtsTimestampAdjuster ptsTimestampAdjuster;
private final ParsableByteArray sampleDataWrapper;
@ -58,7 +59,8 @@ import java.util.regex.Pattern;
private byte[] sampleData;
private int sampleSize;
public WebvttExtractor(PtsTimestampAdjuster ptsTimestampAdjuster) {
public WebvttExtractor(String language, PtsTimestampAdjuster ptsTimestampAdjuster) {
this.language = language;
this.ptsTimestampAdjuster = ptsTimestampAdjuster;
this.sampleDataWrapper = new ParsableByteArray();
sampleData = new byte[1024];
@ -159,8 +161,8 @@ import java.util.regex.Pattern;
private TrackOutput buildTrackOutput(long subsampleOffsetUs) {
TrackOutput trackOutput = output.track(0);
trackOutput.format(MediaFormat.createTextFormat("id", MimeTypes.TEXT_VTT, MediaFormat.NO_VALUE,
"en", subsampleOffsetUs));
trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.TEXT_VTT, Format.NO_VALUE,
language, subsampleOffsetUs));
output.endTracks();
return trackOutput;
}

View File

@ -16,8 +16,8 @@
package com.google.android.exoplayer.metadata;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSource.TrackStream;
import com.google.android.exoplayer.SampleSourceTrackRenderer;
@ -59,7 +59,7 @@ public final class MetadataTrackRenderer<T> extends SampleSourceTrackRenderer im
private final MetadataParser<T> metadataParser;
private final MetadataRenderer<T> metadataRenderer;
private final Handler metadataHandler;
private final MediaFormatHolder formatHolder;
private final FormatHolder formatHolder;
private final SampleHolder sampleHolder;
private boolean inputStreamEnded;
@ -81,13 +81,13 @@ public final class MetadataTrackRenderer<T> extends SampleSourceTrackRenderer im
this.metadataRenderer = Assertions.checkNotNull(metadataRenderer);
this.metadataHandler = metadataRendererLooper == null ? null
: new Handler(metadataRendererLooper, this);
formatHolder = new MediaFormatHolder();
formatHolder = new FormatHolder();
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
}
@Override
protected int supportsFormat(MediaFormat mediaFormat) {
return metadataParser.canParse(mediaFormat.mimeType) ? TrackRenderer.FORMAT_HANDLED
protected int supportsFormat(Format format) {
return metadataParser.canParse(format.sampleMimeType) ? TrackRenderer.FORMAT_HANDLED
: TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}

View File

@ -17,15 +17,14 @@ package com.google.android.exoplayer.smoothstreaming;
import com.google.android.exoplayer.BehindLiveWindowException;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.Format.DecreasingBandwidthComparator;
import com.google.android.exoplayer.TrackGroup;
import com.google.android.exoplayer.chunk.Chunk;
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper;
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
import com.google.android.exoplayer.chunk.ChunkSource;
import com.google.android.exoplayer.chunk.ContainerMediaChunk;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.Format.DecreasingBandwidthComparator;
import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation;
import com.google.android.exoplayer.chunk.MediaChunk;
@ -36,22 +35,18 @@ import com.google.android.exoplayer.extractor.mp4.Track;
import com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.MimeTypes;
import android.net.Uri;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Base64;
import android.util.SparseArray;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
@ -82,16 +77,10 @@ public class SmoothStreamingChunkSource implements ChunkSource {
// Properties of exposed tracks.
private int elementIndex;
private TrackGroup trackGroup;
private Format[] trackFormats;
private ChunkExtractorWrapper[] extractorWrappers;
// Properties of enabled tracks.
private Format[] enabledFormats;
private int adaptiveMaxWidth;
private int adaptiveMaxHeight;
// Mappings from manifest track key.
private final SparseArray<ChunkExtractorWrapper> extractorWrappers;
private final SparseArray<MediaFormat> mediaFormats;
private IOException fatalError;
@ -117,8 +106,6 @@ public class SmoothStreamingChunkSource implements ChunkSource {
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
this.liveEdgeLatencyUs = liveEdgeLatencyMs * 1000;
evaluation = new Evaluation();
extractorWrappers = new SparseArray<>();
mediaFormats = new SparseArray<>();
}
// ChunkSource implementation.
@ -160,7 +147,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
trackEncryptionBoxes = null;
drmInitData = null;
}
selectTracks(currentManifest);
initForManifest(currentManifest);
}
}
return true;
@ -178,22 +165,13 @@ public class SmoothStreamingChunkSource implements ChunkSource {
@Override
public void enable(int[] tracks) {
int maxWidth = -1;
int maxHeight = -1;
enabledFormats = new Format[tracks.length];
for (int i = 0; i < tracks.length; i++) {
enabledFormats[i] = trackFormats[tracks[i]];
maxWidth = Math.max(enabledFormats[i].width, maxWidth);
maxHeight = Math.max(enabledFormats[i].height, maxHeight);
enabledFormats[i] = trackGroup.getFormat(tracks[i]);
}
Arrays.sort(enabledFormats, new DecreasingBandwidthComparator());
if (enabledFormats.length > 1) {
adaptiveMaxWidth = maxWidth;
adaptiveMaxHeight = maxHeight;
adaptiveFormatEvaluator.enable();
} else {
adaptiveMaxWidth = -1;
adaptiveMaxHeight = -1;
}
}
@ -256,7 +234,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
out.chunk = null;
return;
} else if (out.queueSize == queue.size() && out.chunk != null
&& out.chunk.format.equals(selectedFormat)) {
&& out.chunk.format == selectedFormat) {
// We already have a chunk, and the evaluation hasn't changed either the format or the size
// of the queue. Leave unchanged.
return;
@ -311,13 +289,15 @@ public class SmoothStreamingChunkSource implements ChunkSource {
: chunkStartTimeUs + streamElement.getChunkDurationUs(chunkIndex);
int currentAbsoluteChunkIndex = chunkIndex + currentManifestChunkOffset;
int trackGroupTrackIndex = getTrackGroupTrackIndex(trackGroup, selectedFormat);
ChunkExtractorWrapper extractorWrapper = extractorWrappers[trackGroupTrackIndex];
int manifestTrackIndex = getManifestTrackIndex(streamElement, selectedFormat);
int manifestTrackKey = getManifestTrackKey(elementIndex, manifestTrackIndex);
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
Chunk mediaChunk = newMediaChunk(selectedFormat, uri, null,
extractorWrappers.get(manifestTrackKey), drmInitData, dataSource, currentAbsoluteChunkIndex,
chunkStartTimeUs, chunkEndTimeUs, evaluation.trigger, mediaFormats.get(manifestTrackKey),
adaptiveMaxWidth, adaptiveMaxHeight);
Chunk mediaChunk = newMediaChunk(selectedFormat, dataSource, uri, null,
currentAbsoluteChunkIndex, chunkStartTimeUs, chunkEndTimeUs, evaluation.trigger,
extractorWrapper, drmInitData, selectedFormat);
out.chunk = mediaChunk;
}
@ -342,82 +322,30 @@ public class SmoothStreamingChunkSource implements ChunkSource {
// Private methods.
private void selectTracks(SmoothStreamingManifest manifest) {
private void initForManifest(SmoothStreamingManifest manifest) {
for (int i = 0; i < manifest.streamElements.length; i++) {
if (manifest.streamElements[i].type == streamElementType) {
// We've found an element of the desired type.
elementIndex = i;
TrackElement[] trackElements = manifest.streamElements[i].tracks;
trackFormats = new Format[trackElements.length];
MediaFormat[] trackMediaFormats = new MediaFormat[trackElements.length];
for (int j = 0; j < trackMediaFormats.length; j++) {
trackFormats[j] = trackElements[j].format;
trackMediaFormats[j] = initManifestTrack(manifest, i, j);
long timescale = manifest.streamElements[i].timescale;
Format[] formats = manifest.streamElements[i].formats;
extractorWrappers = new ChunkExtractorWrapper[formats.length];
for (int j = 0; j < formats.length; j++) {
int nalUnitLengthFieldLength = streamElementType == StreamElement.TYPE_VIDEO ? 4 : -1;
Track track = new Track(j, streamElementType, timescale, C.UNKNOWN_TIME_US, durationUs,
formats[j], trackEncryptionBoxes, nalUnitLengthFieldLength, null, null);
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
| FragmentedMp4Extractor.WORKAROUND_IGNORE_TFDT_BOX);
extractor.setTrack(track);
extractorWrappers[j] = new ChunkExtractorWrapper(extractor);
}
trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, trackMediaFormats);
trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, formats);
return;
}
}
extractorWrappers = null;
trackGroup = new TrackGroup(adaptiveFormatEvaluator != null);
trackFormats = new Format[0];
}
private MediaFormat initManifestTrack(SmoothStreamingManifest manifest, int elementIndex,
int trackIndex) {
int manifestTrackKey = getManifestTrackKey(elementIndex, trackIndex);
MediaFormat mediaFormat = mediaFormats.get(manifestTrackKey);
if (mediaFormat != null) {
// Already initialized.
return mediaFormat;
}
// Build the media format.
long durationUs = live ? C.UNKNOWN_TIME_US : manifest.durationUs;
StreamElement element = manifest.streamElements[elementIndex];
Format format = element.tracks[trackIndex].format;
byte[][] csdArray = element.tracks[trackIndex].csd;
int mp4TrackType;
switch (element.type) {
case StreamElement.TYPE_VIDEO:
mediaFormat = MediaFormat.createVideoFormat(format.id, format.mimeType, format.bitrate,
MediaFormat.NO_VALUE, format.width, format.height, Arrays.asList(csdArray));
mp4TrackType = Track.TYPE_vide;
break;
case StreamElement.TYPE_AUDIO:
List<byte[]> csd;
if (csdArray != null) {
csd = Arrays.asList(csdArray);
} else {
csd = Collections.singletonList(CodecSpecificDataUtil.buildAacAudioSpecificConfig(
format.audioSamplingRate, format.audioChannels));
}
mediaFormat = MediaFormat.createAudioFormat(format.id, format.mimeType, format.bitrate,
MediaFormat.NO_VALUE, format.audioChannels, format.audioSamplingRate, csd,
format.language);
mp4TrackType = Track.TYPE_soun;
break;
case StreamElement.TYPE_TEXT:
mediaFormat = MediaFormat.createTextFormat(format.id, format.mimeType, format.bitrate,
format.language);
mp4TrackType = Track.TYPE_text;
break;
default:
throw new IllegalStateException("Invalid type: " + element.type);
}
// Build the extractor.
FragmentedMp4Extractor mp4Extractor = new FragmentedMp4Extractor(
FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
| FragmentedMp4Extractor.WORKAROUND_IGNORE_TFDT_BOX);
Track mp4Track = new Track(trackIndex, mp4TrackType, element.timescale, C.UNKNOWN_TIME_US,
durationUs, mediaFormat, trackEncryptionBoxes, mp4TrackType == Track.TYPE_vide ? 4 : -1,
null, null);
mp4Extractor.setTrack(mp4Track);
// Store the format and a wrapper around the extractor.
mediaFormats.put(manifestTrackKey, mediaFormat);
extractorWrappers.put(manifestTrackKey, new ChunkExtractorWrapper(mp4Extractor));
return mediaFormat;
}
/**
@ -443,10 +371,12 @@ public class SmoothStreamingChunkSource implements ChunkSource {
return liveEdgeTimestampUs - liveEdgeLatencyUs;
}
private static int getManifestTrackIndex(StreamElement element, Format format) {
TrackElement[] tracks = element.tracks;
for (int i = 0; i < tracks.length; i++) {
if (tracks[i].format.equals(format)) {
/**
* Gets the index of a format in a track group, using referential equality.
*/
private static int getTrackGroupTrackIndex(TrackGroup trackGroup, Format format) {
for (int i = 0; i < trackGroup.length; i++) {
if (trackGroup.getFormat(i) == format) {
return i;
}
}
@ -454,22 +384,34 @@ public class SmoothStreamingChunkSource implements ChunkSource {
throw new IllegalStateException("Invalid format: " + format);
}
private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey,
ChunkExtractorWrapper extractorWrapper, DrmInitData drmInitData, DataSource dataSource,
int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs, int trigger,
MediaFormat mediaFormat, int adaptiveMaxWidth, int adaptiveMaxHeight) {
long offset = 0;
DataSpec dataSpec = new DataSpec(uri, offset, -1, cacheKey);
// In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk.
// To convert them the absolute timestamps, we need to set sampleOffsetUs to -chunkStartTimeUs.
return new ContainerMediaChunk(dataSource, dataSpec, trigger, formatInfo, chunkStartTimeUs,
chunkEndTimeUs, chunkIndex, chunkStartTimeUs, extractorWrapper, mediaFormat,
adaptiveMaxWidth, adaptiveMaxHeight, drmInitData, true, Chunk.NO_PARENT_ID);
/**
* Gets the index of a format in an element, using format.id equality.
* <p>
* This method will return the same index as {@link #getTrackGroupTrackIndex(TrackGroup, Format)}
* except in the case where a live manifest is refreshed and the ordering of the tracks in the
* manifest has changed.
*/
private static int getManifestTrackIndex(StreamElement element, Format format) {
Format[] formats = element.formats;
for (int i = 0; i < formats.length; i++) {
if (TextUtils.equals(formats[i].id, format.id)) {
return i;
}
}
// Should never happen.
throw new IllegalStateException("Invalid format: " + format);
}
private static int getManifestTrackKey(int elementIndex, int trackIndex) {
Assertions.checkState(elementIndex <= 65536 && trackIndex <= 65536);
return (elementIndex << 16) | trackIndex;
private static MediaChunk newMediaChunk(Format format, DataSource dataSource, Uri uri,
String cacheKey, int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs, int trigger,
ChunkExtractorWrapper extractorWrapper, DrmInitData drmInitData, Format sampleFormat) {
DataSpec dataSpec = new DataSpec(uri, 0, -1, cacheKey);
// In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk.
// To convert them the absolute timestamps, we need to set sampleOffsetUs to chunkStartTimeUs.
long sampleOffsetUs = chunkStartTimeUs;
return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, chunkStartTimeUs,
chunkEndTimeUs, chunkIndex, sampleOffsetUs, extractorWrapper, sampleFormat, drmInitData,
true, Chunk.NO_PARENT_ID);
}
private static byte[] getProtectionElementKeyId(byte[] initData) {

View File

@ -16,8 +16,7 @@
package com.google.android.exoplayer.smoothstreaming;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.FormatWrapper;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.UriUtil;
import com.google.android.exoplayer.util.Util;
@ -123,28 +122,6 @@ public class SmoothStreamingManifest {
}
/**
* Represents a QualityLevel element.
*/
public static class TrackElement implements FormatWrapper {
public final Format format;
public final byte[][] csd;
public TrackElement(int index, int bitrate, String mimeType, byte[][] csd, int maxWidth,
int maxHeight, int sampleRate, int numChannels, String language) {
this.csd = csd;
format = new Format(String.valueOf(index), mimeType, maxWidth, maxHeight, -1, numChannels,
sampleRate, bitrate, language);
}
@Override
public Format getFormat() {
return format;
}
}
/**
* Represents a StreamIndex element.
*/
@ -168,7 +145,7 @@ public class SmoothStreamingManifest {
public final int displayWidth;
public final int displayHeight;
public final String language;
public final TrackElement[] tracks;
public final Format[] formats;
public final int chunkCount;
private final String baseUri;
@ -180,7 +157,7 @@ public class SmoothStreamingManifest {
public StreamElement(String baseUri, String chunkTemplate, int type, String subType,
long timescale, String name, int qualityLevels, int maxWidth, int maxHeight,
int displayWidth, int displayHeight, String language, TrackElement[] tracks,
int displayWidth, int displayHeight, String language, Format[] formats,
List<Long> chunkStartTimes, long lastChunkDuration) {
this.baseUri = baseUri;
this.chunkTemplate = chunkTemplate;
@ -194,7 +171,7 @@ public class SmoothStreamingManifest {
this.displayWidth = displayWidth;
this.displayHeight = displayHeight;
this.language = language;
this.tracks = tracks;
this.formats = formats;
this.chunkCount = chunkStartTimes.size();
this.chunkStartTimes = chunkStartTimes;
lastChunkDurationUs =
@ -242,11 +219,11 @@ public class SmoothStreamingManifest {
* @return The request uri.
*/
public Uri buildRequestUri(int track, int chunkIndex) {
Assertions.checkState(tracks != null);
Assertions.checkState(formats != null);
Assertions.checkState(chunkStartTimes != null);
Assertions.checkState(chunkIndex < chunkStartTimes.size());
String chunkUrl = chunkTemplate
.replace(URL_PLACEHOLDER_BITRATE, Integer.toString(tracks[track].format.bitrate))
.replace(URL_PLACEHOLDER_BITRATE, Integer.toString(formats[track].bitrate))
.replace(URL_PLACEHOLDER_START_TIME, chunkStartTimes.get(chunkIndex).toString());
return UriUtil.resolveToUri(baseUri, chunkUrl);
}

View File

@ -15,11 +15,11 @@
*/
package com.google.android.exoplayer.smoothstreaming;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement;
import com.google.android.exoplayer.upstream.UriLoadable;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
@ -35,6 +35,7 @@ import org.xmlpull.v1.XmlPullParserFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
@ -461,7 +462,7 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
private static final String KEY_FRAGMENT_REPEAT_COUNT = "r";
private final String baseUri;
private final List<TrackElement> tracks;
private final List<Format> formats;
private int type;
private String subType;
@ -481,7 +482,7 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
public StreamElementParser(ElementParser parent, String baseUri) {
super(parent, baseUri, TAG);
this.baseUri = baseUri;
tracks = new LinkedList<>();
formats = new LinkedList<>();
}
@Override
@ -569,17 +570,17 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
@Override
public void addChild(Object child) {
if (child instanceof TrackElement) {
tracks.add((TrackElement) child);
if (child instanceof Format) {
formats.add((Format) child);
}
}
@Override
public Object build() {
TrackElement[] trackElements = new TrackElement[tracks.size()];
tracks.toArray(trackElements);
Format[] formatArray = new Format[formats.size()];
formats.toArray(formatArray);
return new StreamElement(baseUri, url, type, subType, timescale, name, qualityLevels,
maxWidth, maxHeight, displayWidth, displayHeight, language, trackElements, startTimes,
maxWidth, maxHeight, displayWidth, displayHeight, language, formatArray, startTimes,
lastChunkDuration);
}
@ -600,75 +601,62 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
private static final String KEY_MAX_WIDTH = "MaxWidth";
private static final String KEY_MAX_HEIGHT = "MaxHeight";
private final List<byte[]> csd;
private int index;
private int bitrate;
private String mimeType;
private int maxWidth;
private int maxHeight;
private int samplingRate;
private int channels;
private String language;
private Format format;
public TrackElementParser(ElementParser parent, String baseUri) {
super(parent, baseUri, TAG);
this.csd = new LinkedList<>();
}
@Override
public void parseStartTag(XmlPullParser parser) throws ParserException {
int type = (Integer) getNormalizedAttribute(KEY_TYPE);
String value;
index = parseInt(parser, KEY_INDEX, -1);
bitrate = parseRequiredInt(parser, KEY_BITRATE);
language = (String) getNormalizedAttribute(KEY_LANGUAGE);
String id = parser.getAttributeValue(null, KEY_INDEX);
int bitrate = parseRequiredInt(parser, KEY_BITRATE);
String sampleMimeType = fourCCToMimeType(parseRequiredString(parser, KEY_FOUR_CC));
if (type == StreamElement.TYPE_VIDEO) {
maxHeight = parseRequiredInt(parser, KEY_MAX_HEIGHT);
maxWidth = parseRequiredInt(parser, KEY_MAX_WIDTH);
mimeType = fourCCToMimeType(parseRequiredString(parser, KEY_FOUR_CC));
int width = parseRequiredInt(parser, KEY_MAX_WIDTH);
int height = parseRequiredInt(parser, KEY_MAX_HEIGHT);
List<byte[]> codecSpecificData = buildCodecSpecificData(
parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA));
format = Format.createVideoContainerFormat(id, MimeTypes.VIDEO_MP4, sampleMimeType, bitrate,
width, height, Format.NO_VALUE, codecSpecificData);
} else if (type == StreamElement.TYPE_AUDIO) {
sampleMimeType = sampleMimeType == null ? MimeTypes.AUDIO_AAC : sampleMimeType;
int channels = parseRequiredInt(parser, KEY_CHANNELS);
int samplingRate = parseRequiredInt(parser, KEY_SAMPLING_RATE);
List<byte[]> codecSpecificData = buildCodecSpecificData(
parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA));
String language = (String) getNormalizedAttribute(KEY_LANGUAGE);
format = Format.createAudioContainerFormat(id, MimeTypes.AUDIO_MP4, sampleMimeType, bitrate,
channels, samplingRate, codecSpecificData, language);
} else if (type == StreamElement.TYPE_TEXT) {
String language = (String) getNormalizedAttribute(KEY_LANGUAGE);
format = Format.createTextContainerFormat(id, MimeTypes.APPLICATION_MP4, sampleMimeType,
bitrate, language);
} else {
maxHeight = -1;
maxWidth = -1;
String fourCC = parser.getAttributeValue(null, KEY_FOUR_CC);
// If fourCC is missing and the stream type is audio, we assume AAC.
mimeType = fourCC != null ? fourCCToMimeType(fourCC)
: type == StreamElement.TYPE_AUDIO ? MimeTypes.AUDIO_AAC : null;
}
if (type == StreamElement.TYPE_AUDIO) {
samplingRate = parseRequiredInt(parser, KEY_SAMPLING_RATE);
channels = parseRequiredInt(parser, KEY_CHANNELS);
} else {
samplingRate = -1;
channels = -1;
}
value = parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA);
if (value != null && value.length() > 0) {
byte[] codecPrivateData = hexStringToByteArray(value);
byte[][] split = CodecSpecificDataUtil.splitNalUnits(codecPrivateData);
if (split == null) {
csd.add(codecPrivateData);
} else {
for (int i = 0; i < split.length; i++) {
csd.add(split[i]);
}
}
format = Format.createContainerFormat(id, MimeTypes.APPLICATION_MP4, sampleMimeType,
bitrate);
}
}
@Override
public Object build() {
byte[][] csdArray = null;
if (!csd.isEmpty()) {
csdArray = new byte[csd.size()][];
csd.toArray(csdArray);
return format;
}
private static List<byte[]> buildCodecSpecificData(String codecSpecificDataString) {
ArrayList<byte[]> csd = new ArrayList<>();
if (codecSpecificDataString != null && !codecSpecificDataString.isEmpty()) {
byte[] codecPrivateData = hexStringToByteArray(codecSpecificDataString);
byte[][] split = CodecSpecificDataUtil.splitNalUnits(codecPrivateData);
if (split == null) {
csd.add(codecPrivateData);
} else {
Collections.addAll(csd, split);
}
}
return new TrackElement(index, bitrate, mimeType, csdArray, maxWidth, maxHeight, samplingRate,
channels, language);
return csd;
}
private static String fourCCToMimeType(String fourCC) {

View File

@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer.text;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.util.Assertions;
@ -97,7 +97,7 @@ import java.io.IOException;
*
* @param format The format.
*/
public void setFormat(MediaFormat format) {
public void setFormat(Format format) {
handler.obtainMessage(MSG_FORMAT, format).sendToTarget();
}
@ -146,7 +146,7 @@ import java.io.IOException;
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_FORMAT:
handleFormat((MediaFormat) msg.obj);
handleFormat((Format) msg.obj);
break;
case MSG_SAMPLE:
long sampleTimeUs = Util.getLong(msg.arg1, msg.arg2);
@ -157,8 +157,8 @@ import java.io.IOException;
return true;
}
private void handleFormat(MediaFormat format) {
subtitlesAreRelative = format.subsampleOffsetUs == MediaFormat.OFFSET_SAMPLE_RELATIVE;
private void handleFormat(Format format) {
subtitlesAreRelative = format.subsampleOffsetUs == Format.OFFSET_SAMPLE_RELATIVE;
subtitleOffsetUs = subtitlesAreRelative ? 0 : format.subsampleOffsetUs;
}

View File

@ -16,8 +16,8 @@
package com.google.android.exoplayer.text;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSource.TrackStream;
import com.google.android.exoplayer.SampleSourceTrackRenderer;
@ -112,7 +112,7 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
private final Handler textRendererHandler;
private final TextRenderer textRenderer;
private final MediaFormatHolder formatHolder;
private final FormatHolder formatHolder;
private final SubtitleParser[] subtitleParsers;
private int parserIndex;
@ -151,15 +151,25 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
}
}
this.subtitleParsers = subtitleParsers;
formatHolder = new MediaFormatHolder();
formatHolder = new FormatHolder();
}
@Override
protected int supportsFormat(MediaFormat mediaFormat) {
return getParserIndex(mediaFormat) != -1 ? TrackRenderer.FORMAT_HANDLED
protected int supportsFormat(Format format) {
return getParserIndex(format.sampleMimeType) != -1 ? TrackRenderer.FORMAT_HANDLED
: TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}
@Override
protected void onEnabled(Format[] formats, TrackStream trackStream, long positionUs,
boolean joining) throws ExoPlaybackException {
super.onEnabled(formats, trackStream, positionUs, joining);
parserIndex = getParserIndex(formats[0].sampleMimeType);
parserThread = new HandlerThread("textParser");
parserThread.start();
parserHelper = new SubtitleParserHelper(parserThread.getLooper(), subtitleParsers[parserIndex]);
}
@Override
protected void onReset(long positionUs) {
inputStreamEnded = false;
@ -217,17 +227,7 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
SampleHolder sampleHolder = parserHelper.getSampleHolder();
sampleHolder.clearData();
int result = readSource(formatHolder, sampleHolder);
if (result == TrackStream.FORMAT_READ) {
if (parserHelper == null) {
// This is the first format we've seen since the renderer was enabled.
parserIndex = getParserIndex(formatHolder.format);
parserThread = new HandlerThread("textParser");
parserThread.start();
parserHelper = new SubtitleParserHelper(parserThread.getLooper(),
subtitleParsers[parserIndex]);
parserHelper.setFormat(formatHolder.format);
}
} else if (result == TrackStream.SAMPLE_READ) {
if (result == TrackStream.SAMPLE_READ) {
parserHelper.startParseOperation();
} else if (result == TrackStream.END_OF_STREAM) {
inputStreamEnded = true;
@ -291,9 +291,9 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
textRenderer.onCues(cues);
}
private int getParserIndex(MediaFormat mediaFormat) {
private int getParserIndex(String sampleMimeType) {
for (int i = 0; i < subtitleParsers.length; i++) {
if (subtitleParsers[i].canParse(mediaFormat.mimeType)) {
if (subtitleParsers[i].canParse(sampleMimeType)) {
return i;
}
}

View File

@ -17,8 +17,8 @@ package com.google.android.exoplayer.text.eia608;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.MediaFormatHolder;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSource.TrackStream;
import com.google.android.exoplayer.SampleSourceTrackRenderer;
@ -56,7 +56,7 @@ public final class Eia608TrackRenderer extends SampleSourceTrackRenderer impleme
private final Eia608Parser eia608Parser;
private final TextRenderer textRenderer;
private final Handler textRendererHandler;
private final MediaFormatHolder formatHolder;
private final FormatHolder formatHolder;
private final SampleHolder sampleHolder;
private final StringBuilder captionStringBuilder;
private final TreeSet<ClosedCaptionList> pendingCaptionLists;
@ -79,15 +79,15 @@ public final class Eia608TrackRenderer extends SampleSourceTrackRenderer impleme
this.textRenderer = Assertions.checkNotNull(textRenderer);
textRendererHandler = textRendererLooper == null ? null : new Handler(textRendererLooper, this);
eia608Parser = new Eia608Parser();
formatHolder = new MediaFormatHolder();
formatHolder = new FormatHolder();
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
captionStringBuilder = new StringBuilder();
pendingCaptionLists = new TreeSet<>();
}
@Override
protected int supportsFormat(MediaFormat mediaFormat) {
return eia608Parser.canParse(mediaFormat.mimeType) ? TrackRenderer.FORMAT_HANDLED
protected int supportsFormat(Format format) {
return eia608Parser.canParse(format.sampleMimeType) ? TrackRenderer.FORMAT_HANDLED
: TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
}

View File

@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer.util;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import java.nio.ByteBuffer;
@ -68,7 +68,7 @@ public final class Ac3Util {
* @param language The language to set on the format.
* @return The AC-3 format parsed from data in the header.
*/
public static MediaFormat parseAc3AnnexFFormat(ParsableByteArray data, String trackId,
public static Format parseAc3AnnexFFormat(ParsableByteArray data, String trackId,
String language) {
int fscod = (data.readUnsignedByte() & 0xC0) >> 6;
int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod];
@ -77,8 +77,8 @@ public final class Ac3Util {
if ((nextByte & 0x04) != 0) { // lfeon
channelCount++;
}
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, channelCount, sampleRate, null, language);
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, Format.NO_VALUE,
Format.NO_VALUE, channelCount, sampleRate, null, language);
}
/**
@ -90,7 +90,7 @@ public final class Ac3Util {
* @param language The language to set on the format.
* @return The E-AC-3 format parsed from data in the header.
*/
public static MediaFormat parseEAc3AnnexFFormat(ParsableByteArray data, String trackId,
public static Format parseEAc3AnnexFFormat(ParsableByteArray data, String trackId,
String language) {
data.skipBytes(2); // data_rate, num_ind_sub
@ -103,8 +103,8 @@ public final class Ac3Util {
if ((nextByte & 0x01) != 0) { // lfeon
channelCount++;
}
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_E_AC3, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, channelCount, sampleRate, null, language);
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, Format.NO_VALUE,
Format.NO_VALUE, channelCount, sampleRate, null, language);
}
/**
@ -116,7 +116,7 @@ public final class Ac3Util {
* @param language The language to set on the format.
* @return The AC-3 format parsed from data in the header.
*/
public static MediaFormat parseAc3SyncframeFormat(ParsableBitArray data, String trackId,
public static Format parseAc3SyncframeFormat(ParsableBitArray data, String trackId,
String language) {
data.skipBits(16 + 16); // syncword, crc1
int fscod = data.readBits(2);
@ -132,8 +132,8 @@ public final class Ac3Util {
data.skipBits(2); // dsurmod
}
boolean lfeon = data.readBit();
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0),
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, Format.NO_VALUE,
Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0),
SAMPLE_RATE_BY_FSCOD[fscod], null, language);
}
@ -146,7 +146,7 @@ public final class Ac3Util {
* @param language The language to set on the format.
* @return The E-AC-3 format parsed from data in the header.
*/
public static MediaFormat parseEac3SyncframeFormat(ParsableBitArray data, String trackId,
public static Format parseEac3SyncframeFormat(ParsableBitArray data, String trackId,
String language) {
data.skipBits(16 + 2 + 3 + 11); // syncword, strmtype, substreamid, frmsiz
int sampleRate;
@ -159,8 +159,8 @@ public final class Ac3Util {
}
int acmod = data.readBits(3);
boolean lfeon = data.readBit();
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_E_AC3, MediaFormat.NO_VALUE,
MediaFormat.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), sampleRate, null,
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, Format.NO_VALUE,
Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), sampleRate, null,
language);
}

View File

@ -16,7 +16,7 @@
package com.google.android.exoplayer.util;
import com.google.android.exoplayer.CodecCounters;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import android.widget.TextView;

View File

@ -15,7 +15,7 @@
*/
package com.google.android.exoplayer.util;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.Format;
import java.nio.ByteBuffer;
@ -56,7 +56,7 @@ public final class DtsUtil {
* @param language The language to set on the format.
* @return The DTS format parsed from data in the header.
*/
public static MediaFormat parseDtsFormat(byte[] frame, String trackId, String language) {
public static Format parseDtsFormat(byte[] frame, String trackId, String language) {
ParsableBitArray frameBits = SCRATCH_BITS;
frameBits.reset(frame);
frameBits.skipBits(4 * 8 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE
@ -65,12 +65,12 @@ public final class DtsUtil {
int sfreq = frameBits.readBits(4);
int sampleRate = SAMPLE_RATE_BY_SFREQ[sfreq];
int rate = frameBits.readBits(5);
int bitrate = rate >= TWICE_BITRATE_KBPS_BY_RATE.length ? MediaFormat.NO_VALUE
int bitrate = rate >= TWICE_BITRATE_KBPS_BY_RATE.length ? Format.NO_VALUE
: TWICE_BITRATE_KBPS_BY_RATE[rate] * 1000 / 2;
frameBits.skipBits(10); // MIX, DYNF, TIMEF, AUXF, HDCD, EXT_AUDIO_ID, EXT_AUDIO, ASPF
channelCount += frameBits.readBits(2) > 0 ? 1 : 0; // LFF
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_DTS, bitrate,
MediaFormat.NO_VALUE, channelCount, sampleRate, null, language);
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_DTS, bitrate, Format.NO_VALUE,
channelCount, sampleRate, null, language);
}
/**

View File

@ -25,7 +25,6 @@ public final class MimeTypes {
public static final String BASE_TYPE_TEXT = "text";
public static final String BASE_TYPE_APPLICATION = "application";
public static final String VIDEO_UNKNOWN = BASE_TYPE_VIDEO + "/x-unknown";
public static final String VIDEO_MP4 = BASE_TYPE_VIDEO + "/mp4";
public static final String VIDEO_WEBM = BASE_TYPE_VIDEO + "/webm";
public static final String VIDEO_H263 = BASE_TYPE_VIDEO + "/3gpp";
@ -36,7 +35,6 @@ public final class MimeTypes {
public static final String VIDEO_MP4V = BASE_TYPE_VIDEO + "/mp4v-es";
public static final String VIDEO_MPEG2 = BASE_TYPE_VIDEO + "/mpeg2";
public static final String AUDIO_UNKNOWN = BASE_TYPE_AUDIO + "/x-unknown";
public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4";
public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm";
public static final String AUDIO_WEBM = BASE_TYPE_AUDIO + "/webm";
@ -54,7 +52,6 @@ public final class MimeTypes {
public static final String AUDIO_AMR_NB = BASE_TYPE_AUDIO + "/3gpp";
public static final String AUDIO_AMR_WB = BASE_TYPE_AUDIO + "/amr-wb";
public static final String TEXT_UNKNOWN = BASE_TYPE_TEXT + "/x-unknown";
public static final String TEXT_VTT = BASE_TYPE_TEXT + "/vtt";
public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4";
@ -123,62 +120,4 @@ public final class MimeTypes {
return mimeType.substring(0, indexOfSlash);
}
/**
* Returns the video mimeType type of {@code codecs}.
*
* @param codecs The codecs for which the video mimeType is required.
* @return The video mimeType.
*/
public static String getVideoMediaMimeType(String codecs) {
if (codecs == null) {
return MimeTypes.VIDEO_UNKNOWN;
}
String[] codecList = codecs.split(",");
for (String codec : codecList) {
codec = codec.trim();
if (codec.startsWith("avc1") || codec.startsWith("avc3")) {
return MimeTypes.VIDEO_H264;
} else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) {
return MimeTypes.VIDEO_H265;
} else if (codec.startsWith("vp9")) {
return MimeTypes.VIDEO_VP9;
} else if (codec.startsWith("vp8")) {
return MimeTypes.VIDEO_VP8;
}
}
return MimeTypes.VIDEO_UNKNOWN;
}
/**
* Returns the audio mimeType type of {@code codecs}.
*
* @param codecs The codecs for which the audio mimeType is required.
* @return The audio mimeType.
*/
public static String getAudioMediaMimeType(String codecs) {
if (codecs == null) {
return MimeTypes.AUDIO_UNKNOWN;
}
String[] codecList = codecs.split(",");
for (String codec : codecList) {
codec = codec.trim();
if (codec.startsWith("mp4a")) {
return MimeTypes.AUDIO_AAC;
} else if (codec.startsWith("ac-3") || codec.startsWith("dac3")) {
return MimeTypes.AUDIO_AC3;
} else if (codec.startsWith("ec-3") || codec.startsWith("dec3")) {
return MimeTypes.AUDIO_E_AC3;
} else if (codec.startsWith("dtsc") || codec.startsWith("dtse")) {
return MimeTypes.AUDIO_DTS;
} else if (codec.startsWith("dtsh") || codec.startsWith("dtsl")) {
return MimeTypes.AUDIO_DTS_HD;
} else if (codec.startsWith("opus")) {
return MimeTypes.AUDIO_OPUS;
} else if (codec.startsWith("vorbis")) {
return MimeTypes.AUDIO_VORBIS;
}
}
return MimeTypes.AUDIO_UNKNOWN;
}
}