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; - } - }