From a7adcce018a41ecb69dbbfd25ac6811d2ae3cc5e Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 12 Feb 2016 10:23:21 -0800 Subject: [PATCH] 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 --- .../android/exoplayer/demo/EventLogger.java | 2 +- .../exoplayer/demo/PlayerActivity.java | 29 +- .../exoplayer/demo/player/DemoPlayer.java | 5 +- .../google/android/exoplayer/FormatTest.java | 110 +++++ .../android/exoplayer/MediaFormatTest.java | 102 ----- .../exoplayer/dash/DashChunkSourceTest.java | 16 +- .../dash/mpd/RepresentationTest.java | 8 +- .../extractor/mp4/Mp4ExtractorTest.java | 18 +- .../extractor/webm/WebmExtractorTest.java | 14 +- .../hls/HlsMasterPlaylistParserTest.java | 16 +- .../exoplayer/testutil/FakeTrackOutput.java | 6 +- .../android/exoplayer/DummyTrackRenderer.java | 2 +- .../google/android/exoplayer/ExoPlayer.java | 2 +- .../android/exoplayer/ExoPlayerImpl.java | 6 +- .../exoplayer/ExoPlayerImplInternal.java | 37 +- .../com/google/android/exoplayer/Format.java | 386 ++++++++++++++++++ ...diaFormatHolder.java => FormatHolder.java} | 6 +- .../exoplayer/FrameworkSampleSource.java | 64 +-- .../MediaCodecAudioTrackRenderer.java | 33 +- .../exoplayer/MediaCodecTrackRenderer.java | 60 ++- .../MediaCodecVideoTrackRenderer.java | 95 +++-- .../google/android/exoplayer/MediaFormat.java | 349 ---------------- .../android/exoplayer/SampleSource.java | 6 +- .../exoplayer/SampleSourceTrackRenderer.java | 8 +- .../android/exoplayer/SingleSampleSource.java | 10 +- .../google/android/exoplayer/TrackGroup.java | 8 +- .../android/exoplayer/TrackRenderer.java | 16 +- .../BaseChunkSampleSourceEventListener.java | 1 + .../exoplayer/chunk/BaseMediaChunk.java | 26 +- .../google/android/exoplayer/chunk/Chunk.java | 1 + .../chunk/ChunkExtractorWrapper.java | 4 +- .../exoplayer/chunk/ChunkSampleSource.java | 23 +- .../exoplayer/chunk/ContainerMediaChunk.java | 49 +-- .../android/exoplayer/chunk/DataChunk.java | 1 + .../android/exoplayer/chunk/Format.java | 173 -------- .../exoplayer/chunk/FormatEvaluator.java | 1 + .../exoplayer/chunk/FormatWrapper.java | 28 -- .../exoplayer/chunk/InitializationChunk.java | 20 +- .../android/exoplayer/chunk/MediaChunk.java | 1 + .../chunk/SingleSampleMediaChunk.java | 8 +- .../exoplayer/dash/DashChunkSource.java | 220 +++------- .../MediaPresentationDescriptionParser.java | 128 +++++- .../exoplayer/dash/mpd/Representation.java | 10 +- .../extractor/DefaultTrackOutput.java | 8 +- .../exoplayer/extractor/DummyTrackOutput.java | 8 +- .../extractor/ExtractorSampleSource.java | 6 +- .../exoplayer/extractor/TrackOutput.java | 8 +- .../extractor/flv/AudioTagPayloadReader.java | 8 +- .../extractor/flv/VideoTagPayloadReader.java | 17 +- .../exoplayer/extractor/mp3/Mp3Extractor.java | 6 +- .../exoplayer/extractor/mp4/AtomParsers.java | 42 +- .../extractor/mp4/FragmentedMp4Extractor.java | 2 +- .../exoplayer/extractor/mp4/Mp4Extractor.java | 2 +- .../exoplayer/extractor/mp4/Track.java | 10 +- .../extractor/ogg/OggVorbisExtractor.java | 4 +- .../exoplayer/extractor/ts/Ac3Reader.java | 12 +- .../exoplayer/extractor/ts/AdtsReader.java | 12 +- .../exoplayer/extractor/ts/DtsReader.java | 14 +- .../exoplayer/extractor/ts/H262Reader.java | 16 +- .../exoplayer/extractor/ts/H264Reader.java | 10 +- .../exoplayer/extractor/ts/H265Reader.java | 10 +- .../exoplayer/extractor/ts/Id3Reader.java | 5 +- .../extractor/ts/MpegAudioReader.java | 9 +- .../exoplayer/extractor/ts/SeiReader.java | 6 +- .../extractor/webm/WebmExtractor.java | 35 +- .../android/exoplayer/hls/HlsChunkSource.java | 59 +-- .../exoplayer/hls/HlsExtractorWrapper.java | 31 +- .../exoplayer/hls/HlsPlaylistParser.java | 14 +- .../exoplayer/hls/HlsSampleSource.java | 58 ++- .../google/android/exoplayer/hls/TsChunk.java | 2 +- .../google/android/exoplayer/hls/Variant.java | 14 +- .../exoplayer/hls/WebvttExtractor.java | 10 +- .../metadata/MetadataTrackRenderer.java | 12 +- .../SmoothStreamingChunkSource.java | 180 +++----- .../SmoothStreamingManifest.java | 35 +- .../SmoothStreamingManifestParser.java | 108 +++-- .../exoplayer/text/SubtitleParserHelper.java | 10 +- .../exoplayer/text/TextTrackRenderer.java | 38 +- .../text/eia608/Eia608TrackRenderer.java | 12 +- .../android/exoplayer/util/Ac3Util.java | 26 +- .../exoplayer/util/DebugTextViewHelper.java | 2 +- .../android/exoplayer/util/DtsUtil.java | 10 +- .../android/exoplayer/util/MimeTypes.java | 61 --- 83 files changed, 1366 insertions(+), 1674 deletions(-) create mode 100644 library/src/androidTest/java/com/google/android/exoplayer/FormatTest.java delete mode 100644 library/src/androidTest/java/com/google/android/exoplayer/MediaFormatTest.java create mode 100644 library/src/main/java/com/google/android/exoplayer/Format.java rename library/src/main/java/com/google/android/exoplayer/{MediaFormatHolder.java => FormatHolder.java} (85%) delete mode 100644 library/src/main/java/com/google/android/exoplayer/MediaFormat.java delete mode 100644 library/src/main/java/com/google/android/exoplayer/chunk/Format.java delete mode 100644 library/src/main/java/com/google/android/exoplayer/chunk/FormatWrapper.java diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java index 3619cd2e9e..43681ca39c 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java @@ -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; diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java index 06bef7e36e..8ada1a5d0e 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java @@ -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) { diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java index ddb2024bbc..76e8be6348 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java @@ -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); } diff --git a/library/src/androidTest/java/com/google/android/exoplayer/FormatTest.java b/library/src/androidTest/java/com/google/android/exoplayer/FormatTest.java new file mode 100644 index 0000000000..1ea17302ec --- /dev/null +++ b/library/src/androidTest/java/com/google/android/exoplayer/FormatTest.java @@ -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 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)); + } + } + +} diff --git a/library/src/androidTest/java/com/google/android/exoplayer/MediaFormatTest.java b/library/src/androidTest/java/com/google/android/exoplayer/MediaFormatTest.java deleted file mode 100644 index 89b09d9f36..0000000000 --- a/library/src/androidTest/java/com/google/android/exoplayer/MediaFormatTest.java +++ /dev/null @@ -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 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)); - } - } - -} diff --git a/library/src/androidTest/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java b/library/src/androidTest/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java index 35192648a0..e0c1e90824 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/dash/DashChunkSourceTest.java @@ -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 { diff --git a/library/src/androidTest/java/com/google/android/exoplayer/dash/mpd/RepresentationTest.java b/library/src/androidTest/java/com/google/android/exoplayer/dash/mpd/RepresentationTest.java index 03f987cff6..770398db57 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/dash/mpd/RepresentationTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/dash/mpd/RepresentationTest.java @@ -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()); } diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/mp4/Mp4ExtractorTest.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/mp4/Mp4ExtractorTest.java index d4f796c3bc..a106b746b9 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/extractor/mp4/Mp4ExtractorTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/extractor/mp4/Mp4ExtractorTest.java @@ -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); diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/webm/WebmExtractorTest.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/webm/WebmExtractorTest.java index 72d8dfc5d7..b6d7668088 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/extractor/webm/WebmExtractorTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/extractor/webm/WebmExtractorTest.java @@ -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, diff --git a/library/src/androidTest/java/com/google/android/exoplayer/hls/HlsMasterPlaylistParserTest.java b/library/src/androidTest/java/com/google/android/exoplayer/hls/HlsMasterPlaylistParserTest.java index 45716b06ab..0fa9f89b98 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/hls/HlsMasterPlaylistParserTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/hls/HlsMasterPlaylistParserTest.java @@ -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); diff --git a/library/src/androidTest/java/com/google/android/exoplayer/testutil/FakeTrackOutput.java b/library/src/androidTest/java/com/google/android/exoplayer/testutil/FakeTrackOutput.java index 5bdeccc4d9..e85f91f53f 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/testutil/FakeTrackOutput.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/testutil/FakeTrackOutput.java @@ -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 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; } diff --git a/library/src/main/java/com/google/android/exoplayer/DummyTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/DummyTrackRenderer.java index 7b01c82d28..2a6a5e29c4 100644 --- a/library/src/main/java/com/google/android/exoplayer/DummyTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/DummyTrackRenderer.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java index 5319f88ed2..d25ec3630c 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayer.java @@ -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. diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java index 141266badc..3a2514e803 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImpl.java @@ -36,7 +36,7 @@ import java.util.concurrent.CopyOnWriteArraySet; private final Handler eventHandler; private final ExoPlayerImplInternal internalPlayer; private final CopyOnWriteArraySet 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]; } diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java index 546473427e..9d267de4b1 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java @@ -75,9 +75,10 @@ import java.util.concurrent.atomic.AtomicInteger; private final long minBufferUs; private final long minRebufferUs; private final List 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(); diff --git a/library/src/main/java/com/google/android/exoplayer/Format.java b/library/src/main/java/com/google/android/exoplayer/Format.java new file mode 100644 index 0000000000..f9fa3a9910 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/Format.java @@ -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 { + + @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 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 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 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 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 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 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 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.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); + } + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/MediaFormatHolder.java b/library/src/main/java/com/google/android/exoplayer/FormatHolder.java similarity index 85% rename from library/src/main/java/com/google/android/exoplayer/MediaFormatHolder.java rename to library/src/main/java/com/google/android/exoplayer/FormatHolder.java index fef993b945..63bb2163e1 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaFormatHolder.java +++ b/library/src/main/java/com/google/android/exoplayer/FormatHolder.java @@ -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. */ diff --git a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java index bad6a3c6a1..0913da122d 100644 --- a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java @@ -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 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); } diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java index b248b23b04..1e7b1588f4 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java @@ -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); } diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java index 418f16027f..48e042ad2a 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -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 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; } diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java index 834b200987..1e8c22281f 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/MediaFormat.java b/library/src/main/java/com/google/android/exoplayer/MediaFormat.java deleted file mode 100644 index 855ba0ea04..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/MediaFormat.java +++ /dev/null @@ -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 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 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 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 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 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.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); - } - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer/SampleSource.java b/library/src/main/java/com/google/android/exoplayer/SampleSource.java index 6657bb21b3..4ad8847504 100644 --- a/library/src/main/java/com/google/android/exoplayer/SampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/SampleSource.java @@ -152,7 +152,7 @@ public interface SampleSource { * Returns whether data is available to be read. *

* 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. diff --git a/library/src/main/java/com/google/android/exoplayer/SampleSourceTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/SampleSourceTrackRenderer.java index a18277d8b4..efd86750b0 100644 --- a/library/src/main/java/com/google/android/exoplayer/SampleSourceTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/SampleSourceTrackRenderer.java @@ -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); } diff --git a/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java b/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java index 2a8390f7ee..b9dbac64eb 100644 --- a/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java @@ -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) { diff --git a/library/src/main/java/com/google/android/exoplayer/TrackGroup.java b/library/src/main/java/com/google/android/exoplayer/TrackGroup.java index d1083c255b..917e090f9c 100644 --- a/library/src/main/java/com/google/android/exoplayer/TrackGroup.java +++ b/library/src/main/java/com/google/android/exoplayer/TrackGroup.java @@ -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]; } diff --git a/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java index ed828b7b53..e3c17098b3 100644 --- a/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java @@ -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 { *

* 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. } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/BaseChunkSampleSourceEventListener.java b/library/src/main/java/com/google/android/exoplayer/chunk/BaseChunkSampleSourceEventListener.java index 995baa591b..1f9619be5c 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/BaseChunkSampleSourceEventListener.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/BaseChunkSampleSourceEventListener.java @@ -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; diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java index b86a2f8fe2..813f1f63d4 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java @@ -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. *

- * 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. *

- * 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. diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/Chunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/Chunk.java index db6996c4d8..232fd92ab9 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/Chunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/Chunk.java @@ -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; diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java index 0c585419e7..123321b30e 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java @@ -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); } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java index acb52f60d5..5dd8504d9a 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java @@ -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; } } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java index 162bcba1cb..da055e5866 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/DataChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/DataChunk.java index 985fc233df..6effc285d0 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/DataChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/DataChunk.java @@ -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; diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/Format.java b/library/src/main/java/com/google/android/exoplayer/chunk/Format.java deleted file mode 100644 index b7feded311..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/chunk/Format.java +++ /dev/null @@ -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 { - - @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. - *

- * 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); - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java b/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java index d9fa1985fc..0f88525b68 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/FormatEvaluator.java @@ -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; diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/FormatWrapper.java b/library/src/main/java/com/google/android/exoplayer/chunk/FormatWrapper.java deleted file mode 100644 index b36a436b92..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/chunk/FormatWrapper.java +++ /dev/null @@ -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(); - -} diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java index 2a953a185f..068ece3253 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/InitializationChunk.java @@ -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. *

* 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. *

* 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 diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java index 7eaaa107a6..5ac7845203 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/MediaChunk.java @@ -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; diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java index d8d35acdc7..847cf8d133 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java index d1ef47b41f..c4e98f32ec 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java @@ -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 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 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 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 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; diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java index f14fa0816a..2c98336031 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java @@ -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. diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java index 2b3ae678e6..60ead5db9f 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/Representation.java @@ -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. diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java index 993178dad1..029b308458 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/DummyTrackOutput.java b/library/src/main/java/com/google/android/exoplayer/extractor/DummyTrackOutput.java index 330630c89e..4b3ef66342 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/DummyTrackOutput.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/DummyTrackOutput.java @@ -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. } + } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java index 532d6c70f8..a980da9b42 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java @@ -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); } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/TrackOutput.java b/library/src/main/java/com/google/android/exoplayer/extractor/TrackOutput.java index 699fddb256..8722d8a5c6 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/TrackOutput.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/TrackOutput.java @@ -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. diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/flv/AudioTagPayloadReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/flv/AudioTagPayloadReader.java index 025522b356..4693009bac 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/flv/AudioTagPayloadReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/flv/AudioTagPayloadReader.java @@ -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 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 diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/flv/VideoTagPayloadReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/flv/VideoTagPayloadReader.java index c115a5faa3..5d68ed8fa3 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/flv/VideoTagPayloadReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/flv/VideoTagPayloadReader.java @@ -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)); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java index 209ab5a2f1..0d11da8dab 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java @@ -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); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java index 7d4e869100..26a0e0da6e 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java @@ -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 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) { diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java index ba7571ceba..a44130d598 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java @@ -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 { diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java index 7df22b4db9..a2ace8ef31 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java @@ -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); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Track.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Track.java index 1459e71a31..d285b21781 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Track.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Track.java @@ -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; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OggVorbisExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OggVorbisExtractor.java index f6a2b5800c..7daeb7e2ea 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OggVorbisExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OggVorbisExtractor.java @@ -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)); } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/Ac3Reader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/Ac3Reader.java index 779d0b9246..217a3786c3 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/Ac3Reader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/Ac3Reader.java @@ -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); } } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java index 1750baaabc..31456f2c94 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java @@ -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 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); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/DtsReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/DtsReader.java index 83230d7868..59220e33ef 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/DtsReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/DtsReader.java @@ -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); } } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/H262Reader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/H262Reader.java index 1c71812503..25bccb3b9f 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/H262Reader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/H262Reader.java @@ -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 result = parseCsdBuffer(csdBuffer); + Pair 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 parseCsdBuffer(CsdBuffer csdBuffer) { + private static Pair 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; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java index 77fea1cba7..fc00e26549 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java @@ -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 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); } /** diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/H265Reader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/H265Reader.java index a32ccd21fc..f7172b9a16 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/H265Reader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/H265Reader.java @@ -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. */ diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/Id3Reader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/Id3Reader.java index 569ae6acc4..db3bbe0e69 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/Id3Reader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/Id3Reader.java @@ -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 diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/MpegAudioReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/MpegAudioReader.java index f704f8b7dc..7a11201116 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/MpegAudioReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/MpegAudioReader.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java index 49d070f550..8509bd103f 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java @@ -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 diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java index 1d863be8f4..fa56e18bfb 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java @@ -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 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, 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, 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 parseVorbisCodecPrivate(byte[] codecPrivate) diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 932c213224..3a9c4753a7 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -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 variants = new ArrayList<>(); - variants.add(new Variant(baseUri, format)); + variants.add(new Variant(baseUri, format, null)); masterPlaylist = new HlsMasterPlaylist(baseUri, variants, Collections.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; } } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java index cb0b95f0c5..b88f42d039 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java @@ -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 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. *

* 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]; } /** diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java index 432229fb28..b70d3141ba 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsPlaylistParser.java @@ -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 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 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; diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java index dbc056a5eb..8f402fdc7d 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java @@ -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); } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java b/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java index 8354e312d0..bfa6101f8e 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java @@ -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; diff --git a/library/src/main/java/com/google/android/exoplayer/hls/Variant.java b/library/src/main/java/com/google/android/exoplayer/hls/Variant.java index fa950278b3..3800014b27 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/Variant.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/Variant.java @@ -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; } } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/WebvttExtractor.java b/library/src/main/java/com/google/android/exoplayer/hls/WebvttExtractor.java index 6872ee9277..92f81cad97 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/WebvttExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/WebvttExtractor.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/metadata/MetadataTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/metadata/MetadataTrackRenderer.java index c3ca46d5b6..4d0771bb57 100644 --- a/library/src/main/java/com/google/android/exoplayer/metadata/MetadataTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/metadata/MetadataTrackRenderer.java @@ -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 extends SampleSourceTrackRenderer im private final MetadataParser metadataParser; private final MetadataRenderer 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 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; } diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java index bad79f7266..4b52818925 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java @@ -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 extractorWrappers; - private final SparseArray 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 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. + *

+ * 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) { diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java index 568ad2147d..8d28728d7d 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifest.java @@ -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 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); } diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java index 699b36eef4..736dcdb75d 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java @@ -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 tracks; + private final List formats; private int type; private String subType; @@ -481,7 +482,7 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser(); + formats = new LinkedList<>(); } @Override @@ -569,17 +570,17 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser 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 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 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 buildCodecSpecificData(String codecSpecificDataString) { + ArrayList 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) { diff --git a/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserHelper.java b/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserHelper.java index eab6933e9a..0aa1acbcd6 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserHelper.java +++ b/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserHelper.java @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java index a361ba409a..3a37ec108a 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java @@ -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; } } diff --git a/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608TrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608TrackRenderer.java index ca1421dfc1..cb0ce8d3a0 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608TrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608TrackRenderer.java @@ -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 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; } diff --git a/library/src/main/java/com/google/android/exoplayer/util/Ac3Util.java b/library/src/main/java/com/google/android/exoplayer/util/Ac3Util.java index 6341b675da..259d7d41cd 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/Ac3Util.java +++ b/library/src/main/java/com/google/android/exoplayer/util/Ac3Util.java @@ -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); } diff --git a/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java b/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java index 134b40e744..bcf7fb33a4 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java +++ b/library/src/main/java/com/google/android/exoplayer/util/DebugTextViewHelper.java @@ -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; diff --git a/library/src/main/java/com/google/android/exoplayer/util/DtsUtil.java b/library/src/main/java/com/google/android/exoplayer/util/DtsUtil.java index 743095ad0e..2aba6d0407 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/DtsUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/util/DtsUtil.java @@ -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); } /** diff --git a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java index 38564f1ce5..b153b70bf5 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java +++ b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java @@ -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; - } - }