One Format object to rule them all.
- Format can represent both container and sample formats. If a container contains a single track (as is true in DASH and SmoothStreaming) then the container Format can also contain sufficient information about the samples to allow for track selection. This avoids the Format to MediaFormat conversions that we were previously doing in ChunkSource implementations. - One important result of this change is that adaptive format evaluation and static format selection now use the same format objects, which is a whole lot less confusing for someone who wants to implement both initial selection and subsequent adaptation logic. It's not in the V2 doc, but it may well make sense if the TrackSelector not only selects the tracks to enable for an adaptive playback, but also injects a FormatEvaluator when enabling them that will control the subsequent adaptive selections. That would make it so that all format selection logic originates from the same place. - As part of this change, the adaptiveX variables are removed from the format object; they don't really correspond to a single format. This also saves on having to inject the max video dimensions through a bunch of classes. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=114546777
This commit is contained in:
parent
362d0400cd
commit
a7adcce018
@ -16,10 +16,10 @@
|
|||||||
package com.google.android.exoplayer.demo;
|
package com.google.android.exoplayer.demo;
|
||||||
|
|
||||||
import com.google.android.exoplayer.ExoPlayer;
|
import com.google.android.exoplayer.ExoPlayer;
|
||||||
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
||||||
import com.google.android.exoplayer.TimeRange;
|
import com.google.android.exoplayer.TimeRange;
|
||||||
import com.google.android.exoplayer.audio.AudioTrack;
|
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.demo.player.DemoPlayer;
|
||||||
import com.google.android.exoplayer.util.VerboseLogUtil;
|
import com.google.android.exoplayer.util.VerboseLogUtil;
|
||||||
|
|
||||||
|
@ -18,9 +18,9 @@ package com.google.android.exoplayer.demo;
|
|||||||
import com.google.android.exoplayer.AspectRatioFrameLayout;
|
import com.google.android.exoplayer.AspectRatioFrameLayout;
|
||||||
import com.google.android.exoplayer.ExoPlaybackException;
|
import com.google.android.exoplayer.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer.ExoPlayer;
|
import com.google.android.exoplayer.ExoPlayer;
|
||||||
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
||||||
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
|
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.AudioCapabilities;
|
||||||
import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver;
|
import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver;
|
||||||
import com.google.android.exoplayer.demo.player.DashSourceBuilder;
|
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);
|
menu.findItem(player.getSelectedTrack(trackType) + ID_OFFSET).setChecked(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String buildTrackName(MediaFormat format) {
|
private static String buildTrackName(Format format) {
|
||||||
if (format.adaptive) {
|
|
||||||
return "auto";
|
|
||||||
}
|
|
||||||
String trackName;
|
String trackName;
|
||||||
if (MimeTypes.isVideo(format.mimeType)) {
|
if (MimeTypes.isVideo(format.sampleMimeType)) {
|
||||||
trackName = joinWithSeparator(joinWithSeparator(buildResolutionString(format),
|
trackName = joinWithSeparator(joinWithSeparator(buildResolutionString(format),
|
||||||
buildBitrateString(format)), buildTrackIdString(format));
|
buildBitrateString(format)), buildTrackIdString(format));
|
||||||
} else if (MimeTypes.isAudio(format.mimeType)) {
|
} else if (MimeTypes.isAudio(format.sampleMimeType)) {
|
||||||
trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format),
|
trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format),
|
||||||
buildAudioPropertyString(format)), buildBitrateString(format)),
|
buildAudioPropertyString(format)), buildBitrateString(format)),
|
||||||
buildTrackIdString(format));
|
buildTrackIdString(format));
|
||||||
@ -545,23 +542,23 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||||||
return trackName.length() == 0 ? "unknown" : trackName;
|
return trackName.length() == 0 ? "unknown" : trackName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String buildResolutionString(MediaFormat format) {
|
private static String buildResolutionString(Format format) {
|
||||||
return format.width == MediaFormat.NO_VALUE || format.height == MediaFormat.NO_VALUE
|
return format.width == Format.NO_VALUE || format.height == Format.NO_VALUE
|
||||||
? "" : format.width + "x" + format.height;
|
? "" : format.width + "x" + format.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String buildAudioPropertyString(MediaFormat format) {
|
private static String buildAudioPropertyString(Format format) {
|
||||||
return format.channelCount == MediaFormat.NO_VALUE || format.sampleRate == MediaFormat.NO_VALUE
|
return format.channelCount == Format.NO_VALUE || format.sampleRate == Format.NO_VALUE
|
||||||
? "" : format.channelCount + "ch, " + format.sampleRate + "Hz";
|
? "" : 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) ? ""
|
return TextUtils.isEmpty(format.language) || "und".equals(format.language) ? ""
|
||||||
: format.language;
|
: format.language;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String buildBitrateString(MediaFormat format) {
|
private static String buildBitrateString(Format format) {
|
||||||
return format.bitrate == MediaFormat.NO_VALUE ? ""
|
return format.bitrate == Format.NO_VALUE ? ""
|
||||||
: String.format(Locale.US, "%.2fMbit", format.bitrate / 1000000f);
|
: 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);
|
return first.length() == 0 ? second : (second.length() == 0 ? first : first + ", " + second);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String buildTrackIdString(MediaFormat format) {
|
private static String buildTrackIdString(Format format) {
|
||||||
return format.trackId == null ? "" : " (" + format.trackId + ")";
|
return format.id == null ? "" : " (" + format.id + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean onTrackItemClick(MenuItem item, int type) {
|
private boolean onTrackItemClick(MenuItem item, int type) {
|
||||||
|
@ -18,18 +18,17 @@ package com.google.android.exoplayer.demo.player;
|
|||||||
import com.google.android.exoplayer.CodecCounters;
|
import com.google.android.exoplayer.CodecCounters;
|
||||||
import com.google.android.exoplayer.ExoPlaybackException;
|
import com.google.android.exoplayer.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer.ExoPlayer;
|
import com.google.android.exoplayer.ExoPlayer;
|
||||||
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||||
import com.google.android.exoplayer.MediaCodecSelector;
|
import com.google.android.exoplayer.MediaCodecSelector;
|
||||||
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
||||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
|
||||||
import com.google.android.exoplayer.SampleSource;
|
import com.google.android.exoplayer.SampleSource;
|
||||||
import com.google.android.exoplayer.TimeRange;
|
import com.google.android.exoplayer.TimeRange;
|
||||||
import com.google.android.exoplayer.TrackRenderer;
|
import com.google.android.exoplayer.TrackRenderer;
|
||||||
import com.google.android.exoplayer.audio.AudioCapabilities;
|
import com.google.android.exoplayer.audio.AudioCapabilities;
|
||||||
import com.google.android.exoplayer.audio.AudioTrack;
|
import com.google.android.exoplayer.audio.AudioTrack;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
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.dash.DashChunkSource;
|
||||||
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
|
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
|
||||||
import com.google.android.exoplayer.hls.HlsSampleSource;
|
import com.google.android.exoplayer.hls.HlsSampleSource;
|
||||||
@ -248,7 +247,7 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
|
|||||||
return player.getTrackCount(type);
|
return player.getTrackCount(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaFormat getTrackFormat(int type, int index) {
|
public Format getTrackFormat(int type, int index) {
|
||||||
return player.getTrackFormat(type, index);
|
return player.getTrackFormat(type, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.media.MediaFormat;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit test for {@link Format}.
|
||||||
|
*/
|
||||||
|
public final class FormatTest extends TestCase {
|
||||||
|
|
||||||
|
public void testConversionToFrameworkMediaFormat() {
|
||||||
|
if (Util.SDK_INT < 16) {
|
||||||
|
// Test doesn't apply.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] initData1 = new byte[] {1, 2, 3};
|
||||||
|
byte[] initData2 = new byte[] {4, 5, 6};
|
||||||
|
List<byte[]> initData = new ArrayList<>();
|
||||||
|
initData.add(initData1);
|
||||||
|
initData.add(initData2);
|
||||||
|
|
||||||
|
testConversionToFrameworkMediaFormatV16(Format.createVideoSampleFormat(
|
||||||
|
null, "video/xyz", 5000, 102400, 1280, 720, 30, initData));
|
||||||
|
testConversionToFrameworkMediaFormatV16(Format.createVideoSampleFormat(
|
||||||
|
null, "video/xyz", 5000, Format.NO_VALUE, 1280, 720, 30, null));
|
||||||
|
testConversionToFrameworkMediaFormatV16(Format.createAudioSampleFormat(
|
||||||
|
null, "audio/xyz", 500, 128, 5, 44100, initData, null));
|
||||||
|
testConversionToFrameworkMediaFormatV16(Format.createAudioSampleFormat(
|
||||||
|
null, "audio/xyz", 500, Format.NO_VALUE, 5, 44100, null, null));
|
||||||
|
testConversionToFrameworkMediaFormatV16(
|
||||||
|
Format.createTextSampleFormat(null, "text/xyz", Format.NO_VALUE, "eng"));
|
||||||
|
testConversionToFrameworkMediaFormatV16(
|
||||||
|
Format.createTextSampleFormat(null, "text/xyz", Format.NO_VALUE, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
@TargetApi(16)
|
||||||
|
private static void testConversionToFrameworkMediaFormatV16(Format in) {
|
||||||
|
MediaFormat out = in.getFrameworkMediaFormatV16();
|
||||||
|
assertEquals(in.sampleMimeType, out.getString(MediaFormat.KEY_MIME));
|
||||||
|
assertOptionalV16(out, MediaFormat.KEY_LANGUAGE, in.language);
|
||||||
|
assertOptionalV16(out, MediaFormat.KEY_MAX_INPUT_SIZE, in.maxInputSize);
|
||||||
|
assertOptionalV16(out, MediaFormat.KEY_WIDTH, in.width);
|
||||||
|
assertOptionalV16(out, MediaFormat.KEY_HEIGHT, in.height);
|
||||||
|
assertOptionalV16(out, MediaFormat.KEY_CHANNEL_COUNT, in.channelCount);
|
||||||
|
assertOptionalV16(out, MediaFormat.KEY_SAMPLE_RATE, in.sampleRate);
|
||||||
|
assertOptionalV16(out, MediaFormat.KEY_FRAME_RATE, in.frameRate);
|
||||||
|
|
||||||
|
for (int i = 0; i < in.initializationData.size(); i++) {
|
||||||
|
byte[] originalData = in.initializationData.get(i);
|
||||||
|
ByteBuffer frameworkBuffer = out.getByteBuffer("csd-" + i);
|
||||||
|
byte[] frameworkData = Arrays.copyOf(frameworkBuffer.array(), frameworkBuffer.limit());
|
||||||
|
assertTrue(Arrays.equals(originalData, frameworkData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(16)
|
||||||
|
private static void assertOptionalV16(MediaFormat format, String key, String value) {
|
||||||
|
if (value == null) {
|
||||||
|
assertFalse(format.containsKey(key));
|
||||||
|
} else {
|
||||||
|
assertEquals(value, format.getString(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(16)
|
||||||
|
private static void assertOptionalV16(MediaFormat format, String key, int value) {
|
||||||
|
if (value == Format.NO_VALUE) {
|
||||||
|
assertFalse(format.containsKey(key));
|
||||||
|
} else {
|
||||||
|
assertEquals(value, format.getInteger(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(16)
|
||||||
|
private static void assertOptionalV16(MediaFormat format, String key, float value) {
|
||||||
|
if (value == Format.NO_VALUE) {
|
||||||
|
assertFalse(format.containsKey(key));
|
||||||
|
} else {
|
||||||
|
assertEquals(value, format.getFloat(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,102 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.exoplayer;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.util.Util;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit test for {@link MediaFormat}.
|
|
||||||
*/
|
|
||||||
public final class MediaFormatTest extends TestCase {
|
|
||||||
|
|
||||||
public void testConversionToFrameworkFormat() {
|
|
||||||
if (Util.SDK_INT < 16) {
|
|
||||||
// Test doesn't apply.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] initData1 = new byte[] {1, 2, 3};
|
|
||||||
byte[] initData2 = new byte[] {4, 5, 6};
|
|
||||||
List<byte[]> initData = new ArrayList<>();
|
|
||||||
initData.add(initData1);
|
|
||||||
initData.add(initData2);
|
|
||||||
|
|
||||||
testConversionToFrameworkFormatV16(MediaFormat.createVideoFormat(
|
|
||||||
null, "video/xyz", 5000, 102400, 1280, 720, initData));
|
|
||||||
testConversionToFrameworkFormatV16(MediaFormat.createVideoFormat(
|
|
||||||
null, "video/xyz", 5000, MediaFormat.NO_VALUE, 1280, 720, null));
|
|
||||||
testConversionToFrameworkFormatV16(MediaFormat.createAudioFormat(
|
|
||||||
null, "audio/xyz", 500, 128, 5, 44100, initData, null));
|
|
||||||
testConversionToFrameworkFormatV16(MediaFormat.createAudioFormat(
|
|
||||||
null, "audio/xyz", 500, MediaFormat.NO_VALUE, 5, 44100, null, null));
|
|
||||||
testConversionToFrameworkFormatV16(
|
|
||||||
MediaFormat.createTextFormat(null, "text/xyz", MediaFormat.NO_VALUE, "eng"));
|
|
||||||
testConversionToFrameworkFormatV16(
|
|
||||||
MediaFormat.createTextFormat(null, "text/xyz", MediaFormat.NO_VALUE, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
|
||||||
@TargetApi(16)
|
|
||||||
private static void testConversionToFrameworkFormatV16(MediaFormat in) {
|
|
||||||
android.media.MediaFormat out = in.getFrameworkMediaFormatV16();
|
|
||||||
assertEquals(in.mimeType, out.getString(android.media.MediaFormat.KEY_MIME));
|
|
||||||
assertOptionalV16(out, android.media.MediaFormat.KEY_LANGUAGE, in.language);
|
|
||||||
assertOptionalV16(out, android.media.MediaFormat.KEY_MAX_INPUT_SIZE, in.maxInputSize);
|
|
||||||
assertOptionalV16(out, android.media.MediaFormat.KEY_WIDTH, in.width);
|
|
||||||
assertOptionalV16(out, android.media.MediaFormat.KEY_HEIGHT, in.height);
|
|
||||||
assertOptionalV16(out, android.media.MediaFormat.KEY_CHANNEL_COUNT, in.channelCount);
|
|
||||||
assertOptionalV16(out, android.media.MediaFormat.KEY_SAMPLE_RATE, in.sampleRate);
|
|
||||||
assertOptionalV16(out, android.media.MediaFormat.KEY_MAX_WIDTH, in.maxWidth);
|
|
||||||
assertOptionalV16(out, android.media.MediaFormat.KEY_MAX_HEIGHT, in.maxHeight);
|
|
||||||
for (int i = 0; i < in.initializationData.size(); i++) {
|
|
||||||
byte[] originalData = in.initializationData.get(i);
|
|
||||||
ByteBuffer frameworkBuffer = out.getByteBuffer("csd-" + i);
|
|
||||||
byte[] frameworkData = Arrays.copyOf(frameworkBuffer.array(), frameworkBuffer.limit());
|
|
||||||
assertTrue(Arrays.equals(originalData, frameworkData));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(16)
|
|
||||||
private static void assertOptionalV16(android.media.MediaFormat format, String key,
|
|
||||||
String value) {
|
|
||||||
if (value == null) {
|
|
||||||
assertFalse(format.containsKey(key));
|
|
||||||
} else {
|
|
||||||
assertEquals(value, format.getString(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(16)
|
|
||||||
private static void assertOptionalV16(android.media.MediaFormat format, String key,
|
|
||||||
int value) {
|
|
||||||
if (value == MediaFormat.NO_VALUE) {
|
|
||||||
assertFalse(format.containsKey(key));
|
|
||||||
} else {
|
|
||||||
assertEquals(value, format.getInteger(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -15,8 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.dash;
|
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.testutil.TestUtil;
|
||||||
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
|
|
||||||
import android.test.InstrumentationTestCase;
|
import android.test.InstrumentationTestCase;
|
||||||
|
|
||||||
@ -42,12 +43,13 @@ public class DashChunkSourceTest extends InstrumentationTestCase {
|
|||||||
private static final int TALL_HEIGHT = 200;
|
private static final int TALL_HEIGHT = 200;
|
||||||
private static final int WIDE_WIDTH = 400;
|
private static final int WIDE_WIDTH = 400;
|
||||||
|
|
||||||
private static final Format REGULAR_VIDEO =
|
private static final Format REGULAR_VIDEO = Format.createVideoContainerFormat("1",
|
||||||
new Format("1", "video/mp4", 480, 240, -1, -1, -1, 1000);
|
MimeTypes.APPLICATION_MP4, MimeTypes.VIDEO_H264, 1000, 480, 240, Format.NO_VALUE, null);
|
||||||
private static final Format TALL_VIDEO =
|
private static final Format TALL_VIDEO = Format.createVideoContainerFormat("2",
|
||||||
new Format("2", "video/mp4", 100, TALL_HEIGHT, -1, -1, -1, 1000);
|
MimeTypes.APPLICATION_MP4, MimeTypes.VIDEO_H264, 1000, 100, TALL_HEIGHT, Format.NO_VALUE,
|
||||||
private static final Format WIDE_VIDEO =
|
null);
|
||||||
new Format("3", "video/mp4", WIDE_WIDTH, 50, -1, -1, -1, 1000);
|
private static final Format WIDE_VIDEO = Format.createVideoContainerFormat("3",
|
||||||
|
MimeTypes.APPLICATION_MP4, MimeTypes.VIDEO_H264, 1000, WIDE_WIDTH, 50, Format.NO_VALUE, null);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.dash.mpd;
|
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.dash.mpd.SegmentBase.SingleSegmentBase;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
|
|
||||||
@ -29,11 +29,13 @@ public class RepresentationTest extends TestCase {
|
|||||||
public void testGetCacheKey() {
|
public void testGetCacheKey() {
|
||||||
String uri = "http://www.google.com";
|
String uri = "http://www.google.com";
|
||||||
SegmentBase base = new SingleSegmentBase(new RangedUri(uri, null, 0, 1), 1, 0, uri, 1, 1);
|
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);
|
Representation representation = Representation.newInstance("test_stream_1", 3, format, base);
|
||||||
assertEquals("test_stream_1.0.3", representation.getCacheKey());
|
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);
|
representation = Representation.newInstance("test_stream_1", -1, format, base);
|
||||||
assertEquals("test_stream_1.150.-1", representation.getCacheKey());
|
assertEquals("test_stream_1.150.-1", representation.getCacheKey());
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.mp4;
|
package com.google.android.exoplayer.extractor.mp4;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.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.extractor.SeekMap;
|
||||||
import com.google.android.exoplayer.testutil.FakeExtractorOutput;
|
import com.google.android.exoplayer.testutil.FakeExtractorOutput;
|
||||||
import com.google.android.exoplayer.testutil.FakeTrackOutput;
|
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.
|
// The video and audio formats are set correctly.
|
||||||
assertEquals(2, extractorOutput.trackOutputs.size());
|
assertEquals(2, extractorOutput.trackOutputs.size());
|
||||||
MediaFormat videoFormat = extractorOutput.trackOutputs.get(0).format;
|
Format videoFormat = extractorOutput.trackOutputs.get(0).format;
|
||||||
MediaFormat audioFormat = extractorOutput.trackOutputs.get(1).format;
|
Format audioFormat = extractorOutput.trackOutputs.get(1).format;
|
||||||
assertEquals(MimeTypes.VIDEO_H264, videoFormat.mimeType);
|
assertEquals(MimeTypes.VIDEO_H264, videoFormat.sampleMimeType);
|
||||||
assertEquals(VIDEO_WIDTH, videoFormat.width);
|
assertEquals(VIDEO_WIDTH, videoFormat.width);
|
||||||
assertEquals(VIDEO_HEIGHT, videoFormat.height);
|
assertEquals(VIDEO_HEIGHT, videoFormat.height);
|
||||||
assertEquals(MimeTypes.AUDIO_AAC, audioFormat.mimeType);
|
assertEquals(MimeTypes.AUDIO_AAC, audioFormat.sampleMimeType);
|
||||||
|
|
||||||
// The timestamps and sizes are set correctly.
|
// The timestamps and sizes are set correctly.
|
||||||
FakeTrackOutput videoTrackOutput = extractorOutput.trackOutputs.get(0);
|
FakeTrackOutput videoTrackOutput = extractorOutput.trackOutputs.get(0);
|
||||||
@ -170,12 +170,12 @@ public final class Mp4ExtractorTest extends TestCase {
|
|||||||
|
|
||||||
// The video and audio formats are set correctly.
|
// The video and audio formats are set correctly.
|
||||||
assertEquals(2, extractorOutput.trackOutputs.size());
|
assertEquals(2, extractorOutput.trackOutputs.size());
|
||||||
MediaFormat videoFormat = extractorOutput.trackOutputs.get(0).format;
|
Format videoFormat = extractorOutput.trackOutputs.get(0).format;
|
||||||
MediaFormat audioFormat = extractorOutput.trackOutputs.get(1).format;
|
Format audioFormat = extractorOutput.trackOutputs.get(1).format;
|
||||||
assertEquals(MimeTypes.VIDEO_MP4V, videoFormat.mimeType);
|
assertEquals(MimeTypes.VIDEO_MP4V, videoFormat.sampleMimeType);
|
||||||
assertEquals(VIDEO_MP4V_WIDTH, videoFormat.width);
|
assertEquals(VIDEO_MP4V_WIDTH, videoFormat.width);
|
||||||
assertEquals(VIDEO_MP4V_HEIGHT, videoFormat.height);
|
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.
|
// The timestamps and sizes are set correctly.
|
||||||
FakeTrackOutput videoTrackOutput = extractorOutput.trackOutputs.get(0);
|
FakeTrackOutput videoTrackOutput = extractorOutput.trackOutputs.get(0);
|
||||||
|
@ -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 static com.google.android.exoplayer.extractor.webm.StreamBuilder.TEST_ENCRYPTION_KEY_ID;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
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.ParserException;
|
||||||
import com.google.android.exoplayer.drm.DrmInitData;
|
import com.google.android.exoplayer.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData;
|
import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData;
|
||||||
@ -722,24 +722,24 @@ public final class WebmExtractorTest extends InstrumentationTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void assertVp9VideoFormat(int trackNumber) {
|
private void assertVp9VideoFormat(int trackNumber) {
|
||||||
MediaFormat format = getTrackOutput(trackNumber).format;
|
Format format = getTrackOutput(trackNumber).format;
|
||||||
assertEquals(TEST_WIDTH, format.width);
|
assertEquals(TEST_WIDTH, format.width);
|
||||||
assertEquals(TEST_HEIGHT, format.height);
|
assertEquals(TEST_HEIGHT, format.height);
|
||||||
assertEquals(MimeTypes.VIDEO_VP9, format.mimeType);
|
assertEquals(MimeTypes.VIDEO_VP9, format.sampleMimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertH264VideoFormat(int trackNumber) {
|
private void assertH264VideoFormat(int trackNumber) {
|
||||||
MediaFormat format = getTrackOutput(trackNumber).format;
|
Format format = getTrackOutput(trackNumber).format;
|
||||||
assertEquals(TEST_WIDTH, format.width);
|
assertEquals(TEST_WIDTH, format.width);
|
||||||
assertEquals(TEST_HEIGHT, format.height);
|
assertEquals(TEST_HEIGHT, format.height);
|
||||||
assertEquals(MimeTypes.VIDEO_H264, format.mimeType);
|
assertEquals(MimeTypes.VIDEO_H264, format.sampleMimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertAudioFormat(int trackNumber, String expectedMimeType) {
|
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_CHANNEL_COUNT, format.channelCount);
|
||||||
assertEquals(TEST_SAMPLE_RATE, format.sampleRate);
|
assertEquals(TEST_SAMPLE_RATE, format.sampleRate);
|
||||||
assertEquals(expectedMimeType, format.mimeType);
|
assertEquals(expectedMimeType, format.sampleMimeType);
|
||||||
if (MimeTypes.AUDIO_OPUS.equals(expectedMimeType)) {
|
if (MimeTypes.AUDIO_OPUS.equals(expectedMimeType)) {
|
||||||
assertEquals(3, format.initializationData.size());
|
assertEquals(3, format.initializationData.size());
|
||||||
android.test.MoreAsserts.assertEquals(TEST_OPUS_CODEC_PRIVATE,
|
android.test.MoreAsserts.assertEquals(TEST_OPUS_CODEC_PRIVATE,
|
||||||
|
@ -61,32 +61,32 @@ public class HlsMasterPlaylistParserTest extends TestCase {
|
|||||||
assertEquals(5, variants.size());
|
assertEquals(5, variants.size());
|
||||||
|
|
||||||
assertEquals(1280000, variants.get(0).format.bitrate);
|
assertEquals(1280000, variants.get(0).format.bitrate);
|
||||||
assertNotNull(variants.get(0).format.codecs);
|
assertNotNull(variants.get(0).codecs);
|
||||||
assertEquals("mp4a.40.2,avc1.66.30", variants.get(0).format.codecs);
|
assertEquals("mp4a.40.2,avc1.66.30", variants.get(0).codecs);
|
||||||
assertEquals(304, variants.get(0).format.width);
|
assertEquals(304, variants.get(0).format.width);
|
||||||
assertEquals(128, variants.get(0).format.height);
|
assertEquals(128, variants.get(0).format.height);
|
||||||
assertEquals("http://example.com/low.m3u8", variants.get(0).url);
|
assertEquals("http://example.com/low.m3u8", variants.get(0).url);
|
||||||
|
|
||||||
assertEquals(1280000, variants.get(1).format.bitrate);
|
assertEquals(1280000, variants.get(1).format.bitrate);
|
||||||
assertNotNull(variants.get(1).format.codecs);
|
assertNotNull(variants.get(1).codecs);
|
||||||
assertEquals("mp4a.40.2 , avc1.66.30 ", variants.get(1).format.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("http://example.com/spaces_in_codecs.m3u8", variants.get(1).url);
|
||||||
|
|
||||||
assertEquals(2560000, variants.get(2).format.bitrate);
|
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(384, variants.get(2).format.width);
|
||||||
assertEquals(160, variants.get(2).format.height);
|
assertEquals(160, variants.get(2).format.height);
|
||||||
assertEquals("http://example.com/mid.m3u8", variants.get(2).url);
|
assertEquals("http://example.com/mid.m3u8", variants.get(2).url);
|
||||||
|
|
||||||
assertEquals(7680000, variants.get(3).format.bitrate);
|
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.width);
|
||||||
assertEquals(-1, variants.get(3).format.height);
|
assertEquals(-1, variants.get(3).format.height);
|
||||||
assertEquals("http://example.com/hi.m3u8", variants.get(3).url);
|
assertEquals("http://example.com/hi.m3u8", variants.get(3).url);
|
||||||
|
|
||||||
assertEquals(65000, variants.get(4).format.bitrate);
|
assertEquals(65000, variants.get(4).format.bitrate);
|
||||||
assertNotNull(variants.get(4).format.codecs);
|
assertNotNull(variants.get(4).codecs);
|
||||||
assertEquals("mp4a.40.5", variants.get(4).format.codecs);
|
assertEquals("mp4a.40.5", variants.get(4).codecs);
|
||||||
assertEquals(-1, variants.get(4).format.width);
|
assertEquals(-1, variants.get(4).format.width);
|
||||||
assertEquals(-1, variants.get(4).format.height);
|
assertEquals(-1, variants.get(4).format.height);
|
||||||
assertEquals("http://example.com/audio-only.m3u8", variants.get(4).url);
|
assertEquals("http://example.com/audio-only.m3u8", variants.get(4).url);
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.testutil;
|
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.ExtractorInput;
|
||||||
import com.google.android.exoplayer.extractor.TrackOutput;
|
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
@ -40,7 +40,7 @@ public final class FakeTrackOutput implements TrackOutput {
|
|||||||
private final ArrayList<byte[]> sampleEncryptionKeys;
|
private final ArrayList<byte[]> sampleEncryptionKeys;
|
||||||
|
|
||||||
private byte[] sampleData;
|
private byte[] sampleData;
|
||||||
public MediaFormat format;
|
public Format format;
|
||||||
|
|
||||||
public FakeTrackOutput() {
|
public FakeTrackOutput() {
|
||||||
sampleData = new byte[0];
|
sampleData = new byte[0];
|
||||||
@ -52,7 +52,7 @@ public final class FakeTrackOutput implements TrackOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void format(MediaFormat format) {
|
public void format(Format format) {
|
||||||
this.format = format;
|
this.format = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ package com.google.android.exoplayer;
|
|||||||
public final class DummyTrackRenderer extends TrackRenderer {
|
public final class DummyTrackRenderer extends TrackRenderer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int supportsFormat(MediaFormat mediaFormat) throws ExoPlaybackException {
|
protected int supportsFormat(Format format) throws ExoPlaybackException {
|
||||||
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,7 +286,7 @@ public interface ExoPlayer {
|
|||||||
* @param trackIndex The index of the track.
|
* @param trackIndex The index of the track.
|
||||||
* @return The format 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.
|
* Selects a track for the specified renderer.
|
||||||
|
@ -36,7 +36,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
private final Handler eventHandler;
|
private final Handler eventHandler;
|
||||||
private final ExoPlayerImplInternal internalPlayer;
|
private final ExoPlayerImplInternal internalPlayer;
|
||||||
private final CopyOnWriteArraySet<Listener> listeners;
|
private final CopyOnWriteArraySet<Listener> listeners;
|
||||||
private final MediaFormat[][] trackFormats;
|
private final Format[][] trackFormats;
|
||||||
private final int[] selectedTrackIndices;
|
private final int[] selectedTrackIndices;
|
||||||
|
|
||||||
private boolean playWhenReady;
|
private boolean playWhenReady;
|
||||||
@ -61,7 +61,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
this.playWhenReady = false;
|
this.playWhenReady = false;
|
||||||
this.playbackState = STATE_IDLE;
|
this.playbackState = STATE_IDLE;
|
||||||
this.listeners = new CopyOnWriteArraySet<>();
|
this.listeners = new CopyOnWriteArraySet<>();
|
||||||
this.trackFormats = new MediaFormat[renderers.length][];
|
this.trackFormats = new Format[renderers.length][];
|
||||||
this.selectedTrackIndices = new int[renderers.length];
|
this.selectedTrackIndices = new int[renderers.length];
|
||||||
eventHandler = new Handler() {
|
eventHandler = new Handler() {
|
||||||
@Override
|
@Override
|
||||||
@ -105,7 +105,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaFormat getTrackFormat(int rendererIndex, int trackIndex) {
|
public Format getTrackFormat(int rendererIndex, int trackIndex) {
|
||||||
return trackFormats[rendererIndex][trackIndex];
|
return trackFormats[rendererIndex][trackIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,9 +75,10 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
private final long minBufferUs;
|
private final long minBufferUs;
|
||||||
private final long minRebufferUs;
|
private final long minRebufferUs;
|
||||||
private final List<TrackRenderer> enabledRenderers;
|
private final List<TrackRenderer> enabledRenderers;
|
||||||
|
private final int[] selectedTrackIndices;
|
||||||
private final int[][] groupIndices;
|
private final int[][] groupIndices;
|
||||||
private final int[][][] trackIndices;
|
private final int[][][] trackIndices;
|
||||||
private final int[] selectedTrackIndices;
|
private final Format[][][] trackFormats;
|
||||||
private final Handler handler;
|
private final Handler handler;
|
||||||
private final HandlerThread internalPlaybackThread;
|
private final HandlerThread internalPlaybackThread;
|
||||||
private final Handler eventHandler;
|
private final Handler eventHandler;
|
||||||
@ -127,6 +128,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
pendingSeekCount = new AtomicInteger();
|
pendingSeekCount = new AtomicInteger();
|
||||||
enabledRenderers = new ArrayList<>(renderers.length);
|
enabledRenderers = new ArrayList<>(renderers.length);
|
||||||
groupIndices = new int[renderers.length][];
|
groupIndices = new int[renderers.length][];
|
||||||
|
trackFormats = new Format[renderers.length][][];
|
||||||
trackIndices = new int[renderers.length][][];
|
trackIndices = new int[renderers.length][][];
|
||||||
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
|
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
|
||||||
// not normally change to this priority" is incorrect.
|
// not normally change to this priority" is incorrect.
|
||||||
@ -310,13 +312,14 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
maxTrackCount += source.getTrackGroup(groupIndex).length;
|
maxTrackCount += source.getTrackGroup(groupIndex).length;
|
||||||
}
|
}
|
||||||
// Construct tracks for each renderer.
|
// 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++) {
|
for (int rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) {
|
||||||
TrackRenderer renderer = renderers[rendererIndex];
|
TrackRenderer renderer = renderers[rendererIndex];
|
||||||
int rendererTrackCount = 0;
|
int rendererTrackCount = 0;
|
||||||
|
Format[] rendererExternalTrackFormats = new Format[maxTrackCount];
|
||||||
int[] rendererTrackGroups = new int[maxTrackCount];
|
int[] rendererTrackGroups = new int[maxTrackCount];
|
||||||
int[][] rendererTrackIndices = 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++) {
|
for (int groupIndex = 0; groupIndex < source.getTrackGroupCount(); groupIndex++) {
|
||||||
TrackGroup trackGroup = source.getTrackGroup(groupIndex);
|
TrackGroup trackGroup = source.getTrackGroup(groupIndex);
|
||||||
// TODO[REFACTOR]: This should check that the renderer is capable of adaptive playback, in
|
// 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.
|
// Try and build an adaptive track.
|
||||||
int adaptiveTrackIndexCount = 0;
|
int adaptiveTrackIndexCount = 0;
|
||||||
int[] adaptiveTrackIndices = new int[trackGroup.length];
|
int[] adaptiveTrackIndices = new int[trackGroup.length];
|
||||||
MediaFormat adaptiveTrackFormat = null;
|
Format[] adaptiveTrackFormats = new Format[trackGroup.length];
|
||||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
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) {
|
if (renderer.supportsFormat(trackFormat) == TrackRenderer.FORMAT_HANDLED) {
|
||||||
adaptiveTrackIndices[adaptiveTrackIndexCount++] = trackIndex;
|
adaptiveTrackIndices[adaptiveTrackIndexCount] = trackIndex;
|
||||||
if (adaptiveTrackFormat == null) {
|
adaptiveTrackFormats[adaptiveTrackIndexCount++] = trackFormat;
|
||||||
adaptiveTrackFormat = trackFormat.copyAsAdaptive("auto");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (adaptiveTrackIndexCount > 1) {
|
if (adaptiveTrackIndexCount > 1) {
|
||||||
@ -340,21 +341,27 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
rendererTrackGroups[rendererTrackCount] = groupIndex;
|
rendererTrackGroups[rendererTrackCount] = groupIndex;
|
||||||
rendererTrackIndices[rendererTrackCount] =
|
rendererTrackIndices[rendererTrackCount] =
|
||||||
Arrays.copyOf(adaptiveTrackIndices, adaptiveTrackIndexCount);
|
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++) {
|
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) {
|
if (renderer.supportsFormat(trackFormat) == TrackRenderer.FORMAT_HANDLED) {
|
||||||
rendererTrackGroups[rendererTrackCount] = groupIndex;
|
rendererTrackGroups[rendererTrackCount] = groupIndex;
|
||||||
rendererTrackIndices[rendererTrackCount] = new int[] {trackIndex};
|
rendererTrackIndices[rendererTrackCount] = new int[] {trackIndex};
|
||||||
rendererTrackFormats[rendererTrackCount++] = trackFormat;
|
rendererTrackFormats[rendererTrackCount] = new Format[] {trackFormat};
|
||||||
|
rendererExternalTrackFormats[rendererTrackCount++] = trackFormat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
groupIndices[rendererIndex] = Arrays.copyOf(rendererTrackGroups, rendererTrackCount);
|
groupIndices[rendererIndex] = Arrays.copyOf(rendererTrackGroups, rendererTrackCount);
|
||||||
trackIndices[rendererIndex] = Arrays.copyOf(rendererTrackIndices, rendererTrackCount);
|
trackIndices[rendererIndex] = Arrays.copyOf(rendererTrackIndices, rendererTrackCount);
|
||||||
trackFormats[rendererIndex] = Arrays.copyOf(rendererTrackFormats, rendererTrackCount);
|
trackFormats[rendererIndex] = Arrays.copyOf(rendererTrackFormats, rendererTrackCount);
|
||||||
|
externalTrackFormats[rendererIndex] = Arrays.copyOf(rendererExternalTrackFormats,
|
||||||
|
rendererTrackCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable renderers where appropriate.
|
// Enable renderers where appropriate.
|
||||||
@ -364,7 +371,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
if (0 <= trackIndex && trackIndex < trackIndices[rendererIndex].length) {
|
if (0 <= trackIndex && trackIndex < trackIndices[rendererIndex].length) {
|
||||||
TrackStream trackStream = source.enable(groupIndices[rendererIndex][trackIndex],
|
TrackStream trackStream = source.enable(groupIndices[rendererIndex][trackIndex],
|
||||||
trackIndices[rendererIndex][trackIndex], positionUs);
|
trackIndices[rendererIndex][trackIndex], positionUs);
|
||||||
renderer.enable(trackStream, positionUs, false);
|
renderer.enable(trackFormats[rendererIndex][trackIndex], trackStream, positionUs, false);
|
||||||
enabledRenderers.add(renderer);
|
enabledRenderers.add(renderer);
|
||||||
allRenderersEnded = allRenderersEnded && renderer.isEnded();
|
allRenderersEnded = allRenderersEnded && renderer.isEnded();
|
||||||
allRenderersReadyOrEnded = allRenderersReadyOrEnded && isReadyOrEnded(renderer);
|
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
|
// Fire an event indicating that the player has been prepared, passing the initial state and
|
||||||
// renderer track information.
|
// 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.
|
// Start the renderers if required, and schedule the first piece of work.
|
||||||
if (playWhenReady && state == ExoPlayer.STATE_READY) {
|
if (playWhenReady && state == ExoPlayer.STATE_READY) {
|
||||||
@ -642,7 +649,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
boolean joining = !isEnabled && playing;
|
boolean joining = !isEnabled && playing;
|
||||||
TrackStream trackStream = source.enable(groupIndices[rendererIndex][trackIndex],
|
TrackStream trackStream = source.enable(groupIndices[rendererIndex][trackIndex],
|
||||||
trackIndices[rendererIndex][trackIndex], positionUs);
|
trackIndices[rendererIndex][trackIndex], positionUs);
|
||||||
renderer.enable(trackStream, positionUs, joining);
|
renderer.enable(trackFormats[rendererIndex][trackIndex], trackStream, positionUs, joining);
|
||||||
enabledRenderers.add(renderer);
|
enabledRenderers.add(renderer);
|
||||||
if (playing) {
|
if (playing) {
|
||||||
renderer.start();
|
renderer.start();
|
||||||
|
386
library/src/main/java/com/google/android/exoplayer/Format.java
Normal file
386
library/src/main/java/com/google/android/exoplayer/Format.java
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.media.MediaFormat;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Representation of a media format.
|
||||||
|
*/
|
||||||
|
public final class Format {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts {@link Format} objects in order of decreasing bandwidth.
|
||||||
|
*/
|
||||||
|
public static final class DecreasingBandwidthComparator implements Comparator<Format> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Format a, Format b) {
|
||||||
|
return b.bitrate - a.bitrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final int NO_VALUE = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to
|
||||||
|
* the timestamps of their parent samples.
|
||||||
|
*/
|
||||||
|
public static final long OFFSET_SAMPLE_RELATIVE = Long.MAX_VALUE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An identifier for the format, or null if unknown or not applicable.
|
||||||
|
*/
|
||||||
|
public final String id;
|
||||||
|
/**
|
||||||
|
* The average bandwidth in bits per second, or {@link #NO_VALUE} if unknown or not applicable.
|
||||||
|
*/
|
||||||
|
public final int bitrate;
|
||||||
|
|
||||||
|
// Container specific.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mime type of the container, or null if unknown or not applicable.
|
||||||
|
*/
|
||||||
|
public final String containerMimeType;
|
||||||
|
|
||||||
|
// Elementary stream specific.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mime type of the elementary stream (i.e. the individual samples), or null if unknown or not
|
||||||
|
* applicable.
|
||||||
|
*/
|
||||||
|
public final String sampleMimeType;
|
||||||
|
/**
|
||||||
|
* The maximum size of a buffer of data (typically one sample), or {@link #NO_VALUE} if unknown or
|
||||||
|
* not applicable.
|
||||||
|
*/
|
||||||
|
public final int maxInputSize;
|
||||||
|
/**
|
||||||
|
* Whether the decoder is required to support secure decryption.
|
||||||
|
*/
|
||||||
|
public final boolean requiresSecureDecryption;
|
||||||
|
/**
|
||||||
|
* Initialization data that must be provided to the decoder. Will not be null, but may be empty
|
||||||
|
* if initialization data is not required.
|
||||||
|
*/
|
||||||
|
public final List<byte[]> initializationData;
|
||||||
|
|
||||||
|
// Video specific.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The width of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable.
|
||||||
|
*/
|
||||||
|
public final int width;
|
||||||
|
/**
|
||||||
|
* The height of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable.
|
||||||
|
*/
|
||||||
|
public final int height;
|
||||||
|
/**
|
||||||
|
* The frame rate in frames per second, or {@link #NO_VALUE} if unknown or not applicable.
|
||||||
|
*/
|
||||||
|
public final float frameRate;
|
||||||
|
/**
|
||||||
|
* The clockwise rotation that should be applied to the video for it to be rendered in the correct
|
||||||
|
* orientation, or {@link #NO_VALUE} if unknown or not applicable. Only 0, 90, 180 and 270 are
|
||||||
|
* supported.
|
||||||
|
*/
|
||||||
|
public final int rotationDegrees;
|
||||||
|
/**
|
||||||
|
* The width to height ratio of pixels in the video, or {@link #NO_VALUE} if unknown or not
|
||||||
|
* applicable.
|
||||||
|
*/
|
||||||
|
public final float pixelWidthHeightRatio;
|
||||||
|
|
||||||
|
// Audio specific.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of audio channels, or {@link #NO_VALUE} if unknown or not applicable.
|
||||||
|
*/
|
||||||
|
public final int channelCount;
|
||||||
|
/**
|
||||||
|
* The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable.
|
||||||
|
*/
|
||||||
|
public final int sampleRate;
|
||||||
|
|
||||||
|
// Text specific.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For samples that contain subsamples, this is an offset that should be added to subsample
|
||||||
|
* timestamps. A value of {@link #OFFSET_SAMPLE_RELATIVE} indicates that subsample timestamps are
|
||||||
|
* relative to the timestamps of their parent samples.
|
||||||
|
*/
|
||||||
|
public final long subsampleOffsetUs;
|
||||||
|
|
||||||
|
// Audio and text specific.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The language, or null if unknown or not applicable.
|
||||||
|
*/
|
||||||
|
public final String language;
|
||||||
|
|
||||||
|
// Lazy-initialized hashcode and framework media format.
|
||||||
|
|
||||||
|
private int hashCode;
|
||||||
|
private MediaFormat frameworkMediaFormat;
|
||||||
|
|
||||||
|
// Video.
|
||||||
|
|
||||||
|
public static Format createVideoContainerFormat(String id, String containerMimeType,
|
||||||
|
String sampleMimeType, int bitrate, int width, int height, float frameRate,
|
||||||
|
List<byte[]> initializationData) {
|
||||||
|
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, width, height,
|
||||||
|
frameRate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE,
|
||||||
|
initializationData, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Format createVideoSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||||
|
int maxInputSize, int width, int height, float frameRate, List<byte[]> initializationData) {
|
||||||
|
return createVideoSampleFormat(id, sampleMimeType, bitrate, maxInputSize, width, height,
|
||||||
|
frameRate, initializationData, NO_VALUE, NO_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Format createVideoSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||||
|
int maxInputSize, int width, int height, float frameRate, List<byte[]> initializationData,
|
||||||
|
int rotationDegrees, float pixelWidthHeightRatio) {
|
||||||
|
return new Format(id, null, sampleMimeType, bitrate, maxInputSize, width, height, frameRate,
|
||||||
|
rotationDegrees, pixelWidthHeightRatio, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE,
|
||||||
|
initializationData, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audio.
|
||||||
|
|
||||||
|
public static Format createAudioContainerFormat(String id, String containerMimeType,
|
||||||
|
String sampleMimeType, int bitrate, int channelCount, int sampleRate,
|
||||||
|
List<byte[]> initializationData, String language) {
|
||||||
|
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||||
|
NO_VALUE, NO_VALUE, NO_VALUE, channelCount, sampleRate, language, OFFSET_SAMPLE_RELATIVE,
|
||||||
|
initializationData, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Format createAudioSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||||
|
int maxInputSize, int channelCount, int sampleRate, List<byte[]> initializationData,
|
||||||
|
String language) {
|
||||||
|
return new Format(id, null, sampleMimeType, bitrate, maxInputSize, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||||
|
NO_VALUE, NO_VALUE, channelCount, sampleRate, language, OFFSET_SAMPLE_RELATIVE,
|
||||||
|
initializationData, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text.
|
||||||
|
|
||||||
|
public static Format createTextContainerFormat(String id, String containerMimeType,
|
||||||
|
String sampleMimeType, int bitrate, String language) {
|
||||||
|
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||||
|
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language, OFFSET_SAMPLE_RELATIVE, null,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Format createTextSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||||
|
String language) {
|
||||||
|
return createTextSampleFormat(id, sampleMimeType, bitrate, language, OFFSET_SAMPLE_RELATIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Format createTextSampleFormat(String id, String sampleMimeType, int bitrate,
|
||||||
|
String language, long subsampleOffsetUs) {
|
||||||
|
return new Format(id, null, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||||
|
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language, subsampleOffsetUs, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic.
|
||||||
|
|
||||||
|
public static Format createContainerFormat(String id, String containerMimeType,
|
||||||
|
String sampleMimeType, int bitrate) {
|
||||||
|
return new Format(id, containerMimeType, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||||
|
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Format createSampleFormat(String id, String sampleMimeType, int bitrate) {
|
||||||
|
return new Format(id, null, sampleMimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||||
|
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ Format(String id, String containerMimeType, String sampleMimeType,
|
||||||
|
int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees,
|
||||||
|
float pixelWidthHeightRatio, int channelCount, int sampleRate, String language,
|
||||||
|
long subsampleOffsetUs, List<byte[]> initializationData, boolean requiresSecureDecryption) {
|
||||||
|
this.id = id;
|
||||||
|
this.containerMimeType = containerMimeType;
|
||||||
|
this.sampleMimeType = sampleMimeType;
|
||||||
|
this.bitrate = bitrate;
|
||||||
|
this.maxInputSize = maxInputSize;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.frameRate = frameRate;
|
||||||
|
this.rotationDegrees = rotationDegrees;
|
||||||
|
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
|
||||||
|
this.channelCount = channelCount;
|
||||||
|
this.sampleRate = sampleRate;
|
||||||
|
this.language = language;
|
||||||
|
this.subsampleOffsetUs = subsampleOffsetUs;
|
||||||
|
this.initializationData = initializationData == null ? Collections.<byte[]>emptyList()
|
||||||
|
: initializationData;
|
||||||
|
this.requiresSecureDecryption = requiresSecureDecryption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Format copyWithMaxInputSize(int maxInputSize) {
|
||||||
|
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
|
||||||
|
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
|
||||||
|
language, subsampleOffsetUs, initializationData, requiresSecureDecryption);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
|
||||||
|
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
|
||||||
|
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
|
||||||
|
language, subsampleOffsetUs, initializationData, requiresSecureDecryption);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Format copyWithContainerInfo(String id, int bitrate, int width, int height,
|
||||||
|
String language) {
|
||||||
|
return new Format(id, containerMimeType, sampleMimeType, bitrate, maxInputSize, width,
|
||||||
|
height, frameRate, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate,
|
||||||
|
language, subsampleOffsetUs, initializationData, requiresSecureDecryption);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A {@link MediaFormat} representation of this format.
|
||||||
|
*/
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
@TargetApi(16)
|
||||||
|
public final MediaFormat getFrameworkMediaFormatV16() {
|
||||||
|
if (frameworkMediaFormat == null) {
|
||||||
|
MediaFormat format = new MediaFormat();
|
||||||
|
format.setString(MediaFormat.KEY_MIME, sampleMimeType);
|
||||||
|
maybeSetStringV16(format, MediaFormat.KEY_LANGUAGE, language);
|
||||||
|
maybeSetIntegerV16(format, MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
|
||||||
|
maybeSetIntegerV16(format, MediaFormat.KEY_WIDTH, width);
|
||||||
|
maybeSetIntegerV16(format, MediaFormat.KEY_HEIGHT, height);
|
||||||
|
maybeSetFloatV16(format, MediaFormat.KEY_FRAME_RATE, frameRate);
|
||||||
|
maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees);
|
||||||
|
maybeSetIntegerV16(format, MediaFormat.KEY_CHANNEL_COUNT, channelCount);
|
||||||
|
maybeSetIntegerV16(format, MediaFormat.KEY_SAMPLE_RATE, sampleRate);
|
||||||
|
for (int i = 0; i < initializationData.size(); i++) {
|
||||||
|
format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i)));
|
||||||
|
}
|
||||||
|
frameworkMediaFormat = format;
|
||||||
|
}
|
||||||
|
return frameworkMediaFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link MediaFormat} returned by {@link #getFrameworkMediaFormatV16()}.
|
||||||
|
*
|
||||||
|
* @deprecated This method only exists for FrameworkSampleSource, which is itself deprecated.
|
||||||
|
* @param frameworkSampleFormat The format.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@TargetApi(16)
|
||||||
|
/* package */ final void setFrameworkMediaFormatV16(MediaFormat frameworkSampleFormat) {
|
||||||
|
this.frameworkMediaFormat = frameworkSampleFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Format(" + id + ", " + containerMimeType + ", " + sampleMimeType + ", " + bitrate + ", "
|
||||||
|
+ maxInputSize + ", " + language + ", [" + width + ", " + height + ", " + frameRate + ", "
|
||||||
|
+ rotationDegrees + ", " + pixelWidthHeightRatio + "]" + ", [" + channelCount + ", "
|
||||||
|
+ sampleRate + "])";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
if (hashCode == 0) {
|
||||||
|
int result = 17;
|
||||||
|
result = 31 * result + (id == null ? 0 : id.hashCode());
|
||||||
|
result = 31 * result + (containerMimeType == null ? 0 : containerMimeType.hashCode());
|
||||||
|
result = 31 * result + (sampleMimeType == null ? 0 : sampleMimeType.hashCode());
|
||||||
|
result = 31 * result + bitrate;
|
||||||
|
result = 31 * result + width;
|
||||||
|
result = 31 * result + height;
|
||||||
|
result = 31 * result + channelCount;
|
||||||
|
result = 31 * result + sampleRate;
|
||||||
|
result = 31 * result + (language == null ? 0 : language.hashCode());
|
||||||
|
hashCode = result;
|
||||||
|
}
|
||||||
|
return hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Format other = (Format) obj;
|
||||||
|
if (bitrate != other.bitrate || maxInputSize != other.maxInputSize
|
||||||
|
|| requiresSecureDecryption != other.requiresSecureDecryption
|
||||||
|
|| width != other.width || height != other.height || frameRate != other.frameRate
|
||||||
|
|| rotationDegrees != other.rotationDegrees
|
||||||
|
|| pixelWidthHeightRatio != other.pixelWidthHeightRatio
|
||||||
|
|| channelCount != other.channelCount || sampleRate != other.sampleRate
|
||||||
|
|| subsampleOffsetUs != other.subsampleOffsetUs
|
||||||
|
|| !Util.areEqual(id, other.id) || !Util.areEqual(language, other.language)
|
||||||
|
|| !Util.areEqual(containerMimeType, other.containerMimeType)
|
||||||
|
|| !Util.areEqual(sampleMimeType, other.sampleMimeType)
|
||||||
|
|| initializationData.size() != other.initializationData.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < initializationData.size(); i++) {
|
||||||
|
if (!Arrays.equals(initializationData.get(i), other.initializationData.get(i))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(16)
|
||||||
|
private static final void maybeSetStringV16(MediaFormat format, String key,
|
||||||
|
String value) {
|
||||||
|
if (value != null) {
|
||||||
|
format.setString(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(16)
|
||||||
|
private static final void maybeSetIntegerV16(MediaFormat format, String key,
|
||||||
|
int value) {
|
||||||
|
if (value != NO_VALUE) {
|
||||||
|
format.setInteger(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(16)
|
||||||
|
private static final void maybeSetFloatV16(MediaFormat format, String key,
|
||||||
|
float value) {
|
||||||
|
if (value != NO_VALUE) {
|
||||||
|
format.setFloat(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -18,14 +18,14 @@ package com.google.android.exoplayer;
|
|||||||
import com.google.android.exoplayer.drm.DrmInitData;
|
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.
|
* 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.
|
* Initialization data for drm schemes supported by the media. Null if the media is not encrypted.
|
||||||
*/
|
*/
|
@ -27,6 +27,7 @@ import android.annotation.SuppressLint;
|
|||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.MediaExtractor;
|
import android.media.MediaExtractor;
|
||||||
|
import android.media.MediaFormat;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
@ -134,11 +135,11 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
pendingResets = new boolean[trackStates.length];
|
pendingResets = new boolean[trackStates.length];
|
||||||
tracks = new TrackGroup[trackStates.length];
|
tracks = new TrackGroup[trackStates.length];
|
||||||
for (int i = 0; i < trackStates.length; i++) {
|
for (int i = 0; i < trackStates.length; i++) {
|
||||||
android.media.MediaFormat format = extractor.getTrackFormat(i);
|
MediaFormat format = extractor.getTrackFormat(i);
|
||||||
if (format.containsKey(android.media.MediaFormat.KEY_DURATION)) {
|
if (format.containsKey(MediaFormat.KEY_DURATION)) {
|
||||||
durationUs = Math.max(durationUs, format.getLong(android.media.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;
|
prepared = true;
|
||||||
return true;
|
return true;
|
||||||
@ -188,7 +189,7 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
return TrackStream.NO_RESET;
|
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(prepared);
|
||||||
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
|
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
|
||||||
if (pendingResets[track]) {
|
if (pendingResets[track]) {
|
||||||
@ -296,39 +297,50 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
private static MediaFormat createMediaFormat(android.media.MediaFormat format) {
|
private static Format createFormat(int index, MediaFormat mediaFormat) {
|
||||||
String mimeType = format.getString(android.media.MediaFormat.KEY_MIME);
|
String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
|
||||||
String language = getOptionalStringV16(format, android.media.MediaFormat.KEY_LANGUAGE);
|
String language = getOptionalStringV16(mediaFormat, MediaFormat.KEY_LANGUAGE);
|
||||||
int maxInputSize = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE);
|
int maxInputSize = getOptionalIntegerV16(mediaFormat, MediaFormat.KEY_MAX_INPUT_SIZE);
|
||||||
int width = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_WIDTH);
|
int width = getOptionalIntegerV16(mediaFormat, MediaFormat.KEY_WIDTH);
|
||||||
int height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT);
|
int height = getOptionalIntegerV16(mediaFormat, MediaFormat.KEY_HEIGHT);
|
||||||
int rotationDegrees = getOptionalIntegerV16(format, "rotation-degrees");
|
float frameRate;
|
||||||
int channelCount = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT);
|
try {
|
||||||
int sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE);
|
frameRate = getOptionalIntegerV16(mediaFormat, MediaFormat.KEY_FRAME_RATE);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
// There's an entry for KEY_FRAME_RATE but it's not a integer. It must be a float.
|
||||||
|
frameRate = getOptionalFloatV16(mediaFormat, MediaFormat.KEY_FRAME_RATE);
|
||||||
|
}
|
||||||
|
int rotationDegrees = getOptionalIntegerV16(mediaFormat, "rotation-degrees");
|
||||||
|
int channelCount = getOptionalIntegerV16(mediaFormat, MediaFormat.KEY_CHANNEL_COUNT);
|
||||||
|
int sampleRate = getOptionalIntegerV16(mediaFormat, MediaFormat.KEY_SAMPLE_RATE);
|
||||||
ArrayList<byte[]> initializationData = new ArrayList<>();
|
ArrayList<byte[]> initializationData = new ArrayList<>();
|
||||||
for (int i = 0; format.containsKey("csd-" + i); i++) {
|
for (int i = 0; mediaFormat.containsKey("csd-" + i); i++) {
|
||||||
ByteBuffer buffer = format.getByteBuffer("csd-" + i);
|
ByteBuffer buffer = mediaFormat.getByteBuffer("csd-" + i);
|
||||||
byte[] data = new byte[buffer.limit()];
|
byte[] data = new byte[buffer.limit()];
|
||||||
buffer.get(data);
|
buffer.get(data);
|
||||||
initializationData.add(data);
|
initializationData.add(data);
|
||||||
buffer.flip();
|
buffer.flip();
|
||||||
}
|
}
|
||||||
MediaFormat mediaFormat = new MediaFormat(null, mimeType, MediaFormat.NO_VALUE, maxInputSize,
|
Format format = new Format(Integer.toString(index), null, mimeType, Format.NO_VALUE,
|
||||||
width, height, rotationDegrees, MediaFormat.NO_VALUE, channelCount, sampleRate, language,
|
maxInputSize, width, height, frameRate, rotationDegrees, Format.NO_VALUE, channelCount,
|
||||||
MediaFormat.OFFSET_SAMPLE_RELATIVE, initializationData, false, MediaFormat.NO_VALUE,
|
sampleRate, language, Format.OFFSET_SAMPLE_RELATIVE, initializationData, false);
|
||||||
MediaFormat.NO_VALUE);
|
format.setFrameworkMediaFormatV16(mediaFormat);
|
||||||
mediaFormat.setFrameworkFormatV16(format);
|
return format;
|
||||||
return mediaFormat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(16)
|
@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;
|
return format.containsKey(key) ? format.getString(key) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(16)
|
@TargetApi(16)
|
||||||
private static final int getOptionalIntegerV16(android.media.MediaFormat format, String key) {
|
private static int getOptionalIntegerV16(MediaFormat format, String key) {
|
||||||
return format.containsKey(key) ? format.getInteger(key) : MediaFormat.NO_VALUE;
|
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 {
|
private final class TrackStreamImpl implements TrackStream {
|
||||||
@ -359,7 +371,7 @@ public final class FrameworkSampleSource implements SampleSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int readData(MediaFormatHolder formatHolder, SampleHolder sampleHolder) {
|
public int readData(FormatHolder formatHolder, SampleHolder sampleHolder) {
|
||||||
return FrameworkSampleSource.this.readData(track, formatHolder, sampleHolder);
|
return FrameworkSampleSource.this.readData(track, formatHolder, sampleHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import android.annotation.TargetApi;
|
|||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaCrypto;
|
import android.media.MediaCrypto;
|
||||||
|
import android.media.MediaFormat;
|
||||||
import android.media.PlaybackParams;
|
import android.media.PlaybackParams;
|
||||||
import android.media.audiofx.Virtualizer;
|
import android.media.audiofx.Virtualizer;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@ -181,30 +182,30 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int supportsFormat(MediaCodecSelector mediaCodecSelector, MediaFormat mediaFormat)
|
protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format)
|
||||||
throws DecoderQueryException {
|
throws DecoderQueryException {
|
||||||
String mimeType = mediaFormat.mimeType;
|
String mimeType = format.sampleMimeType;
|
||||||
if (!MimeTypes.isAudio(mimeType)) {
|
if (!MimeTypes.isAudio(mimeType)) {
|
||||||
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
||||||
}
|
}
|
||||||
if (allowPassthrough(mimeType) && mediaCodecSelector.getPassthroughDecoderName() != null) {
|
if (allowPassthrough(mimeType) && mediaCodecSelector.getPassthroughDecoderName() != null) {
|
||||||
return TrackRenderer.FORMAT_HANDLED;
|
return TrackRenderer.FORMAT_HANDLED;
|
||||||
}
|
}
|
||||||
// TODO[REFACTOR]: Propagate requiresSecureDecoder to this point. Note that we need to check
|
// TODO[REFACTOR]: If requiresSecureDecryption then we should probably also check that the
|
||||||
// that the drm session can make use of a secure decoder, as well as that a secure decoder
|
// drmSession is able to make use of a secure decoder.
|
||||||
// exists.
|
DecoderInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType,
|
||||||
DecoderInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mediaFormat.mimeType, false);
|
format.requiresSecureDecryption);
|
||||||
if (decoderInfo == null) {
|
if (decoderInfo == null) {
|
||||||
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
||||||
}
|
}
|
||||||
if (Util.SDK_INT >= 21) {
|
if (Util.SDK_INT >= 21) {
|
||||||
// Note: We assume support in the case that the sampleRate or channelCount is unknown.
|
// Note: We assume support in the case that the sampleRate or channelCount is unknown.
|
||||||
if (mediaFormat.sampleRate != MediaFormat.NO_VALUE
|
if (format.sampleRate != Format.NO_VALUE
|
||||||
&& !decoderInfo.isAudioSampleRateSupportedV21(mediaFormat.sampleRate)) {
|
&& !decoderInfo.isAudioSampleRateSupportedV21(format.sampleRate)) {
|
||||||
return TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
|
return TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
|
||||||
}
|
}
|
||||||
if (mediaFormat.channelCount != MediaFormat.NO_VALUE
|
if (format.channelCount != Format.NO_VALUE
|
||||||
&& !decoderInfo.isAudioChannelCountSupportedV21(mediaFormat.channelCount)) {
|
&& !decoderInfo.isAudioChannelCountSupportedV21(format.channelCount)) {
|
||||||
return TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
|
return TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,9 +213,9 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DecoderInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, MediaFormat format,
|
protected DecoderInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, Format format,
|
||||||
boolean requiresSecureDecoder) throws DecoderQueryException {
|
boolean requiresSecureDecoder) throws DecoderQueryException {
|
||||||
if (allowPassthrough(format.mimeType)) {
|
if (allowPassthrough(format.sampleMimeType)) {
|
||||||
String passthroughDecoderName = mediaCodecSelector.getPassthroughDecoderName();
|
String passthroughDecoderName = mediaCodecSelector.getPassthroughDecoderName();
|
||||||
if (passthroughDecoderName != null) {
|
if (passthroughDecoderName != null) {
|
||||||
passthroughEnabled = true;
|
passthroughEnabled = true;
|
||||||
@ -238,13 +239,13 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configureCodec(MediaCodec codec, MediaFormat format, MediaCrypto crypto) {
|
protected void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto) {
|
||||||
if (passthroughEnabled) {
|
if (passthroughEnabled) {
|
||||||
// Override the MIME type used to configure the codec if we are using a passthrough decoder.
|
// Override the MIME type used to configure the codec if we are using a passthrough decoder.
|
||||||
passthroughMediaFormat = format.getFrameworkMediaFormatV16();
|
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);
|
codec.configure(passthroughMediaFormat, null, crypto, 0);
|
||||||
passthroughMediaFormat.setString(android.media.MediaFormat.KEY_MIME, format.mimeType);
|
passthroughMediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType);
|
||||||
} else {
|
} else {
|
||||||
codec.configure(format.getFrameworkMediaFormatV16(), null, crypto, 0);
|
codec.configure(format.getFrameworkMediaFormatV16(), null, crypto, 0);
|
||||||
passthroughMediaFormat = null;
|
passthroughMediaFormat = null;
|
||||||
@ -257,7 +258,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onOutputFormatChanged(android.media.MediaFormat outputFormat) {
|
protected void onOutputFormatChanged(MediaFormat outputFormat) {
|
||||||
boolean passthrough = passthroughMediaFormat != null;
|
boolean passthrough = passthroughMediaFormat != null;
|
||||||
audioTrack.configure(passthrough ? passthroughMediaFormat : outputFormat, passthrough);
|
audioTrack.configure(passthrough ? passthroughMediaFormat : outputFormat, passthrough);
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ import android.media.MediaCodec;
|
|||||||
import android.media.MediaCodec.CodecException;
|
import android.media.MediaCodec.CodecException;
|
||||||
import android.media.MediaCodec.CryptoException;
|
import android.media.MediaCodec.CryptoException;
|
||||||
import android.media.MediaCrypto;
|
import android.media.MediaCrypto;
|
||||||
|
import android.media.MediaFormat;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
|
||||||
@ -102,19 +103,19 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
|
|||||||
*/
|
*/
|
||||||
public final String diagnosticInfo;
|
public final String diagnosticInfo;
|
||||||
|
|
||||||
public DecoderInitializationException(MediaFormat mediaFormat, Throwable cause,
|
public DecoderInitializationException(Format format, Throwable cause,
|
||||||
boolean secureDecoderRequired, int errorCode) {
|
boolean secureDecoderRequired, int errorCode) {
|
||||||
super("Decoder init failed: [" + errorCode + "], " + mediaFormat, cause);
|
super("Decoder init failed: [" + errorCode + "], " + format, cause);
|
||||||
this.mimeType = mediaFormat.mimeType;
|
this.mimeType = format.sampleMimeType;
|
||||||
this.secureDecoderRequired = secureDecoderRequired;
|
this.secureDecoderRequired = secureDecoderRequired;
|
||||||
this.decoderName = null;
|
this.decoderName = null;
|
||||||
this.diagnosticInfo = buildCustomDiagnosticInfo(errorCode);
|
this.diagnosticInfo = buildCustomDiagnosticInfo(errorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DecoderInitializationException(MediaFormat mediaFormat, Throwable cause,
|
public DecoderInitializationException(Format format, Throwable cause,
|
||||||
boolean secureDecoderRequired, String decoderName) {
|
boolean secureDecoderRequired, String decoderName) {
|
||||||
super("Decoder init failed: " + decoderName + ", " + mediaFormat, cause);
|
super("Decoder init failed: " + decoderName + ", " + format, cause);
|
||||||
this.mimeType = mediaFormat.mimeType;
|
this.mimeType = format.sampleMimeType;
|
||||||
this.secureDecoderRequired = secureDecoderRequired;
|
this.secureDecoderRequired = secureDecoderRequired;
|
||||||
this.decoderName = decoderName;
|
this.decoderName = decoderName;
|
||||||
this.diagnosticInfo = Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null;
|
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 DrmSessionManager drmSessionManager;
|
||||||
private final boolean playClearSamplesWithoutKeys;
|
private final boolean playClearSamplesWithoutKeys;
|
||||||
private final SampleHolder sampleHolder;
|
private final SampleHolder sampleHolder;
|
||||||
private final MediaFormatHolder formatHolder;
|
private final FormatHolder formatHolder;
|
||||||
private final List<Long> decodeOnlyPresentationTimestamps;
|
private final List<Long> decodeOnlyPresentationTimestamps;
|
||||||
private final MediaCodec.BufferInfo outputBufferInfo;
|
private final MediaCodec.BufferInfo outputBufferInfo;
|
||||||
private final EventListener eventListener;
|
private final EventListener eventListener;
|
||||||
protected final Handler eventHandler;
|
protected final Handler eventHandler;
|
||||||
|
|
||||||
private MediaFormat format;
|
private Format format;
|
||||||
private DrmInitData drmInitData;
|
private DrmInitData drmInitData;
|
||||||
private MediaCodec codec;
|
private MediaCodec codec;
|
||||||
private boolean codecIsAdaptive;
|
private boolean codecIsAdaptive;
|
||||||
@ -254,7 +255,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
|
|||||||
this.eventListener = eventListener;
|
this.eventListener = eventListener;
|
||||||
codecCounters = new CodecCounters();
|
codecCounters = new CodecCounters();
|
||||||
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED);
|
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED);
|
||||||
formatHolder = new MediaFormatHolder();
|
formatHolder = new FormatHolder();
|
||||||
decodeOnlyPresentationTimestamps = new ArrayList<>();
|
decodeOnlyPresentationTimestamps = new ArrayList<>();
|
||||||
outputBufferInfo = new MediaCodec.BufferInfo();
|
outputBufferInfo = new MediaCodec.BufferInfo();
|
||||||
codecReconfigurationState = RECONFIGURATION_STATE_NONE;
|
codecReconfigurationState = RECONFIGURATION_STATE_NONE;
|
||||||
@ -277,9 +278,9 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final int supportsFormat(MediaFormat mediaFormat) throws ExoPlaybackException {
|
protected final int supportsFormat(Format format) throws ExoPlaybackException {
|
||||||
try {
|
try {
|
||||||
return supportsFormat(mediaCodecSelector, mediaFormat);
|
return supportsFormat(mediaCodecSelector, format);
|
||||||
} catch (DecoderQueryException e) {
|
} catch (DecoderQueryException e) {
|
||||||
throw new ExoPlaybackException(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.
|
* Returns the extent to which the renderer is capable of rendering a given format.
|
||||||
*
|
*
|
||||||
* @param mediaCodecSelector The decoder selector.
|
* @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
|
* @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_HANDLED}, {@link #FORMAT_EXCEEDS_CAPABILITIES} and
|
||||||
* {@link #FORMAT_UNSUPPORTED_TYPE}.
|
* {@link #FORMAT_UNSUPPORTED_TYPE}.
|
||||||
* @throws DecoderQueryException If there was an error querying decoders.
|
* @throws DecoderQueryException If there was an error querying decoders.
|
||||||
*/
|
*/
|
||||||
protected abstract int supportsFormat(MediaCodecSelector mediaCodecSelector,
|
protected abstract int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format)
|
||||||
MediaFormat mediaFormat) throws DecoderQueryException;
|
throws DecoderQueryException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link DecoderInfo} for a given format.
|
* Returns a {@link DecoderInfo} for a given format.
|
||||||
*
|
*
|
||||||
* @param mediaCodecSelector The decoder selector.
|
* @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.
|
* @param requiresSecureDecoder Whether a secure decoder is required.
|
||||||
* @return A {@link DecoderInfo} describing the decoder to instantiate, or null if no suitable
|
* @return A {@link DecoderInfo} describing the decoder to instantiate, or null if no suitable
|
||||||
* decoder exists.
|
* decoder exists.
|
||||||
* @throws DecoderQueryException Thrown if there was an error querying decoders.
|
* @throws DecoderQueryException Thrown if there was an error querying decoders.
|
||||||
*/
|
*/
|
||||||
protected DecoderInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector,
|
protected DecoderInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, Format format,
|
||||||
MediaFormat mediaFormat, boolean requiresSecureDecoder) throws DecoderQueryException {
|
boolean requiresSecureDecoder) throws DecoderQueryException {
|
||||||
return mediaCodecSelector.getDecoderInfo(format.mimeType, requiresSecureDecoder);
|
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 format The format for which the codec is being configured.
|
||||||
* @param crypto For drm protected playbacks, a {@link MediaCrypto} to use for decryption.
|
* @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")
|
@SuppressWarnings("deprecation")
|
||||||
protected final void maybeInitCodec() throws ExoPlaybackException {
|
protected final void maybeInitCodec() throws ExoPlaybackException {
|
||||||
@ -329,7 +330,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String mimeType = format.mimeType;
|
String mimeType = format.sampleMimeType;
|
||||||
MediaCrypto mediaCrypto = null;
|
MediaCrypto mediaCrypto = null;
|
||||||
boolean requiresSecureDecoder = false;
|
boolean requiresSecureDecoder = false;
|
||||||
if (drmInitData != null) {
|
if (drmInitData != null) {
|
||||||
@ -499,8 +500,8 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
|
|||||||
if (codec != null) {
|
if (codec != null) {
|
||||||
TraceUtil.beginSection("drainAndFeed");
|
TraceUtil.beginSection("drainAndFeed");
|
||||||
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
|
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
|
||||||
if (feedInputBuffer(positionUs, true)) {
|
if (feedInputBuffer(true)) {
|
||||||
while (feedInputBuffer(positionUs, false)) {}
|
while (feedInputBuffer(false)) {}
|
||||||
}
|
}
|
||||||
TraceUtil.endSection();
|
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
|
* @param firstFeed True if this is the first call to this method from the current invocation of
|
||||||
* {@link #doSomeWork(long, long)}. False otherwise.
|
* {@link #doSomeWork(long, long)}. False otherwise.
|
||||||
* @return True if it may be possible to feed more input data. 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.
|
* @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
|
if (inputStreamEnded
|
||||||
|| codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
|
|| codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) {
|
||||||
// The input stream has ended, or we need to re-initialize the codec but are still waiting
|
// 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.
|
* @param formatHolder Holds the new format.
|
||||||
* @throws ExoPlaybackException If an error occurs reinitializing the {@link MediaCodec}.
|
* @throws ExoPlaybackException If an error occurs reinitializing the {@link MediaCodec}.
|
||||||
*/
|
*/
|
||||||
protected void onInputFormatChanged(MediaFormatHolder formatHolder) throws ExoPlaybackException {
|
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
|
||||||
MediaFormat oldFormat = format;
|
Format oldFormat = format;
|
||||||
format = formatHolder.format;
|
format = formatHolder.format;
|
||||||
drmInitData = formatHolder.drmInitData;
|
drmInitData = formatHolder.drmInitData;
|
||||||
if (codec != null && canReconfigureCodec(codec, codecIsAdaptive, oldFormat, format)) {
|
if (codec != null && canReconfigureCodec(codec, codecIsAdaptive, oldFormat, format)) {
|
||||||
@ -748,8 +747,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
|
|||||||
* @param outputFormat The new output format.
|
* @param outputFormat The new output format.
|
||||||
* @throws ExoPlaybackException If an error occurs on output format change.
|
* @throws ExoPlaybackException If an error occurs on output format change.
|
||||||
*/
|
*/
|
||||||
protected void onOutputFormatChanged(android.media.MediaFormat outputFormat)
|
protected void onOutputFormatChanged(MediaFormat outputFormat) throws ExoPlaybackException {
|
||||||
throws ExoPlaybackException {
|
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -779,8 +777,8 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
|
|||||||
* @param newFormat The new format.
|
* @param newFormat The new format.
|
||||||
* @return True if the existing instance can be reconfigured. False otherwise.
|
* @return True if the existing instance can be reconfigured. False otherwise.
|
||||||
*/
|
*/
|
||||||
protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive,
|
protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive, Format oldFormat,
|
||||||
MediaFormat oldFormat, MediaFormat newFormat) {
|
Format newFormat) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +27,10 @@ import android.annotation.TargetApi;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaCrypto;
|
import android.media.MediaCrypto;
|
||||||
|
import android.media.MediaFormat;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.TextureView;
|
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
|
// TODO: Use MediaFormat constants if these get exposed through the API. See
|
||||||
// [Internal: b/14127601].
|
// [Internal: b/14127601].
|
||||||
private static final String KEY_CROP_LEFT = "crop-left";
|
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 videoScalingMode;
|
||||||
private final int maxDroppedFrameCountToNotify;
|
private final int maxDroppedFrameCountToNotify;
|
||||||
|
|
||||||
|
private int adaptiveMaxWidth;
|
||||||
|
private int adaptiveMaxHeight;
|
||||||
|
|
||||||
private Surface surface;
|
private Surface surface;
|
||||||
private boolean reportedDrawnToSurface;
|
private boolean reportedDrawnToSurface;
|
||||||
private boolean renderedFirstFrame;
|
private boolean renderedFirstFrame;
|
||||||
@ -213,34 +220,33 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int supportsFormat(MediaCodecSelector mediaCodecSelector, MediaFormat mediaFormat)
|
protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format)
|
||||||
throws DecoderQueryException {
|
throws DecoderQueryException {
|
||||||
String mimeType = mediaFormat.mimeType;
|
String mimeType = format.sampleMimeType;
|
||||||
if (!MimeTypes.isVideo(mimeType)) {
|
if (!MimeTypes.isVideo(mimeType)) {
|
||||||
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
||||||
}
|
}
|
||||||
// TODO[REFACTOR]: Propagate requiresSecureDecoder to this point. Note that we need to check
|
// TODO[REFACTOR]: If requiresSecureDecryption then we should probably also check that the
|
||||||
// that the drm session can make use of a secure decoder, as well as that a secure decoder
|
// drmSession is able to make use of a secure decoder.
|
||||||
// exists.
|
DecoderInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType,
|
||||||
DecoderInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mediaFormat.mimeType, false);
|
format.requiresSecureDecryption);
|
||||||
if (decoderInfo == null) {
|
if (decoderInfo == null) {
|
||||||
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
return TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
||||||
}
|
}
|
||||||
if (mediaFormat.width > 0 && mediaFormat.height > 0) {
|
if (format.width > 0 && format.height > 0) {
|
||||||
if (Util.SDK_INT >= 21) {
|
if (Util.SDK_INT >= 21) {
|
||||||
// TODO[REFACTOR]: Propagate frame rate to this point.
|
if (format.frameRate > 0) {
|
||||||
// if (mediaFormat.frameRate > 0) {
|
return decoderInfo.isVideoSizeAndRateSupportedV21(format.width, format.height,
|
||||||
// return decoderInfo.isSizeAndRateSupportedV21(mediaFormat.width, mediaFormat.height,
|
format.frameRate) ? TrackRenderer.FORMAT_HANDLED
|
||||||
// mediaFormat.frameRate) ? TrackRenderer.TRACK_HANDLED
|
: TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
|
||||||
// : TrackRenderer.TRACK_NOT_HANDLED_EXCEEDS_CAPABILITIES;
|
} else {
|
||||||
// } else {
|
return decoderInfo.isVideoSizeSupportedV21(format.width, format.height)
|
||||||
return decoderInfo.isVideoSizeSupportedV21(mediaFormat.width, mediaFormat.height)
|
|
||||||
? TrackRenderer.FORMAT_HANDLED : TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
|
? TrackRenderer.FORMAT_HANDLED : TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
// TODO[REFACTOR]: We should probably assume that we can decode at least the resolution of
|
// TODO[REFACTOR]: We should probably assume that we can decode at least the resolution of
|
||||||
// the display, or the camera, as a sanity check?
|
// 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;
|
return TrackRenderer.FORMAT_EXCEEDS_CAPABILITIES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -248,9 +254,23 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onEnabled(TrackStream trackStream, long positionUs, boolean joining)
|
protected void onEnabled(Format[] formats, TrackStream trackStream, long positionUs,
|
||||||
throws ExoPlaybackException {
|
boolean joining) throws ExoPlaybackException {
|
||||||
super.onEnabled(trackStream, positionUs, joining);
|
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) {
|
if (joining && allowedJoiningTimeUs > 0) {
|
||||||
joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs;
|
joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs;
|
||||||
}
|
}
|
||||||
@ -345,17 +365,17 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
|||||||
|
|
||||||
// Override configureCodec to provide the surface.
|
// Override configureCodec to provide the surface.
|
||||||
@Override
|
@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.configure(getFrameworkMediaFormat(format), surface, crypto, 0);
|
||||||
codec.setVideoScalingMode(videoScalingMode);
|
codec.setVideoScalingMode(videoScalingMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onInputFormatChanged(MediaFormatHolder holder) throws ExoPlaybackException {
|
protected void onInputFormatChanged(FormatHolder holder) throws ExoPlaybackException {
|
||||||
super.onInputFormatChanged(holder);
|
super.onInputFormatChanged(holder);
|
||||||
pendingPixelWidthHeightRatio = holder.format.pixelWidthHeightRatio == MediaFormat.NO_VALUE ? 1
|
pendingPixelWidthHeightRatio = holder.format.pixelWidthHeightRatio == Format.NO_VALUE ? 1
|
||||||
: holder.format.pixelWidthHeightRatio;
|
: holder.format.pixelWidthHeightRatio;
|
||||||
pendingRotationDegrees = holder.format.rotationDegrees == MediaFormat.NO_VALUE ? 0
|
pendingRotationDegrees = holder.format.rotationDegrees == Format.NO_VALUE ? 0
|
||||||
: holder.format.rotationDegrees;
|
: holder.format.rotationDegrees;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,16 +387,16 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onOutputFormatChanged(android.media.MediaFormat outputFormat) {
|
protected void onOutputFormatChanged(MediaFormat outputFormat) {
|
||||||
boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT)
|
boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT)
|
||||||
&& outputFormat.containsKey(KEY_CROP_LEFT) && outputFormat.containsKey(KEY_CROP_BOTTOM)
|
&& outputFormat.containsKey(KEY_CROP_LEFT) && outputFormat.containsKey(KEY_CROP_BOTTOM)
|
||||||
&& outputFormat.containsKey(KEY_CROP_TOP);
|
&& outputFormat.containsKey(KEY_CROP_TOP);
|
||||||
currentWidth = hasCrop
|
currentWidth = hasCrop
|
||||||
? outputFormat.getInteger(KEY_CROP_RIGHT) - outputFormat.getInteger(KEY_CROP_LEFT) + 1
|
? 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
|
currentHeight = hasCrop
|
||||||
? outputFormat.getInteger(KEY_CROP_BOTTOM) - outputFormat.getInteger(KEY_CROP_TOP) + 1
|
? 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;
|
currentPixelWidthHeightRatio = pendingPixelWidthHeightRatio;
|
||||||
if (Util.SDK_INT >= 21) {
|
if (Util.SDK_INT >= 21) {
|
||||||
// On API level 21 and above the decoder applies the rotation when rendering to the surface.
|
// 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
|
@Override
|
||||||
protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive,
|
protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive,
|
||||||
MediaFormat oldFormat, MediaFormat newFormat) {
|
Format oldFormat, Format newFormat) {
|
||||||
return newFormat.mimeType.equals(oldFormat.mimeType)
|
return newFormat.sampleMimeType.equals(oldFormat.sampleMimeType)
|
||||||
&& (codecIsAdaptive
|
&& (codecIsAdaptive
|
||||||
|| (oldFormat.width == newFormat.width && oldFormat.height == newFormat.height));
|
|| (oldFormat.width == newFormat.width && oldFormat.height == newFormat.height));
|
||||||
}
|
}
|
||||||
@ -517,19 +537,26 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
private android.media.MediaFormat getFrameworkMediaFormat(MediaFormat format) {
|
private android.media.MediaFormat getFrameworkMediaFormat(Format format) {
|
||||||
android.media.MediaFormat frameworkMediaFormat = format.getFrameworkMediaFormatV16();
|
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) {
|
if (format.maxInputSize > 0) {
|
||||||
// The format already has a maximum input size.
|
// The format already has a maximum input size.
|
||||||
return frameworkMediaFormat;
|
return frameworkMediaFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the format doesn't define a maximum input size, determine one ourselves.
|
// 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(adaptiveMaxWidth, format.width);
|
||||||
int maxWidth = Math.max(format.maxWidth, format.maxWidth);
|
int maxHeight = Math.max(adaptiveMaxHeight, format.height);
|
||||||
int maxPixels;
|
int maxPixels;
|
||||||
int minCompressionRatio;
|
int minCompressionRatio;
|
||||||
switch (format.mimeType) {
|
switch (format.sampleMimeType) {
|
||||||
case MimeTypes.VIDEO_H264:
|
case MimeTypes.VIDEO_H264:
|
||||||
if ("BRAVIA 4K 2015".equals(Util.MODEL)) {
|
if ("BRAVIA 4K 2015".equals(Util.MODEL)) {
|
||||||
// The Sony BRAVIA 4k TV has input buffers that are too small for the calculated 4k video
|
// 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.
|
// Estimate the maximum input size assuming three channel 4:2:0 subsampled input frames.
|
||||||
int maxInputSize = (maxPixels * 3) / (2 * minCompressionRatio);
|
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;
|
return frameworkMediaFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,349 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.exoplayer;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
|
||||||
import com.google.android.exoplayer.util.Util;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the format of an elementary media stream.
|
|
||||||
*/
|
|
||||||
public final class MediaFormat {
|
|
||||||
|
|
||||||
public static final int NO_VALUE = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to
|
|
||||||
* the timestamps of their parent samples.
|
|
||||||
*/
|
|
||||||
public static final long OFFSET_SAMPLE_RELATIVE = Long.MAX_VALUE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The identifier for the track represented by the format, or null if unknown or not applicable.
|
|
||||||
*/
|
|
||||||
public final String trackId;
|
|
||||||
/**
|
|
||||||
* The mime type of the format.
|
|
||||||
*/
|
|
||||||
public final String mimeType;
|
|
||||||
/**
|
|
||||||
* The average bandwidth in bits per second, or {@link #NO_VALUE} if unknown or not applicable.
|
|
||||||
*/
|
|
||||||
public final int bitrate;
|
|
||||||
/**
|
|
||||||
* The maximum size of a buffer of data (typically one sample) in the format, or {@link #NO_VALUE}
|
|
||||||
* if unknown or not applicable.
|
|
||||||
*/
|
|
||||||
public final int maxInputSize;
|
|
||||||
/**
|
|
||||||
* Initialization data that must be provided to the decoder. Will not be null, but may be empty
|
|
||||||
* if initialization data is not required.
|
|
||||||
*/
|
|
||||||
public final List<byte[]> initializationData;
|
|
||||||
/**
|
|
||||||
* Whether the format represents an adaptive track, meaning that the format of the actual media
|
|
||||||
* data may change (e.g. to adapt to network conditions).
|
|
||||||
*/
|
|
||||||
public final boolean adaptive;
|
|
||||||
|
|
||||||
// Video specific.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The width of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable.
|
|
||||||
*/
|
|
||||||
public final int width;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The height of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable.
|
|
||||||
*/
|
|
||||||
public final int height;
|
|
||||||
/**
|
|
||||||
* For formats that belong to an adaptive video track (either describing the track, or describing
|
|
||||||
* a specific format within it), this is the maximum width of the video in pixels that will be
|
|
||||||
* encountered in the stream. Set to {@link #NO_VALUE} if unknown or not applicable.
|
|
||||||
*/
|
|
||||||
public final int maxWidth;
|
|
||||||
/**
|
|
||||||
* For formats that belong to an adaptive video track (either describing the track, or describing
|
|
||||||
* a specific format within it), this is the maximum height of the video in pixels that will be
|
|
||||||
* encountered in the stream. Set to {@link #NO_VALUE} if unknown or not applicable.
|
|
||||||
*/
|
|
||||||
public final int maxHeight;
|
|
||||||
/**
|
|
||||||
* The clockwise rotation that should be applied to the video for it to be rendered in the correct
|
|
||||||
* orientation, or {@link #NO_VALUE} if unknown or not applicable. Only 0, 90, 180 and 270 are
|
|
||||||
* supported.
|
|
||||||
*/
|
|
||||||
public final int rotationDegrees;
|
|
||||||
/**
|
|
||||||
* The width to height ratio of pixels in the video, or {@link #NO_VALUE} if unknown or not
|
|
||||||
* applicable.
|
|
||||||
*/
|
|
||||||
public final float pixelWidthHeightRatio;
|
|
||||||
|
|
||||||
// Audio specific.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of audio channels, or {@link #NO_VALUE} if unknown or not applicable.
|
|
||||||
*/
|
|
||||||
public final int channelCount;
|
|
||||||
/**
|
|
||||||
* The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable.
|
|
||||||
*/
|
|
||||||
public final int sampleRate;
|
|
||||||
|
|
||||||
// Text specific.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The language of the track, or null if unknown or not applicable.
|
|
||||||
*/
|
|
||||||
public final String language;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For samples that contain subsamples, this is an offset that should be added to subsample
|
|
||||||
* timestamps. A value of {@link #OFFSET_SAMPLE_RELATIVE} indicates that subsample timestamps are
|
|
||||||
* relative to the timestamps of their parent samples.
|
|
||||||
*/
|
|
||||||
public final long subsampleOffsetUs;
|
|
||||||
|
|
||||||
// Lazy-initialized hashcode and framework media format.
|
|
||||||
|
|
||||||
private int hashCode;
|
|
||||||
private android.media.MediaFormat frameworkMediaFormat;
|
|
||||||
|
|
||||||
public static MediaFormat createVideoFormat(String trackId, String mimeType, int bitrate,
|
|
||||||
int maxInputSize, int width, int height, List<byte[]> initializationData) {
|
|
||||||
return createVideoFormat(trackId, mimeType, bitrate, maxInputSize, width, height,
|
|
||||||
initializationData, NO_VALUE, NO_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MediaFormat createVideoFormat(String trackId, String mimeType, int bitrate,
|
|
||||||
int maxInputSize, int width, int height, List<byte[]> initializationData, int rotationDegrees,
|
|
||||||
float pixelWidthHeightRatio) {
|
|
||||||
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, width, height, rotationDegrees,
|
|
||||||
pixelWidthHeightRatio, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, initializationData,
|
|
||||||
false, NO_VALUE, NO_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MediaFormat createAudioFormat(String trackId, String mimeType, int bitrate,
|
|
||||||
int maxInputSize, int channelCount, int sampleRate, List<byte[]> initializationData,
|
|
||||||
String language) {
|
|
||||||
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, NO_VALUE, NO_VALUE, NO_VALUE,
|
|
||||||
NO_VALUE, channelCount, sampleRate, language, OFFSET_SAMPLE_RELATIVE, initializationData,
|
|
||||||
false, NO_VALUE, NO_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MediaFormat createTextFormat(String trackId, String mimeType, int bitrate,
|
|
||||||
String language) {
|
|
||||||
return createTextFormat(trackId, mimeType, bitrate, language, OFFSET_SAMPLE_RELATIVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MediaFormat createTextFormat(String trackId, String mimeType, int bitrate,
|
|
||||||
String language, long subsampleOffsetUs) {
|
|
||||||
return new MediaFormat(trackId, mimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
|
|
||||||
NO_VALUE, NO_VALUE, NO_VALUE, language, subsampleOffsetUs, null, false, NO_VALUE, NO_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MediaFormat createFormatForMimeType(String trackId, String mimeType, int bitrate) {
|
|
||||||
return new MediaFormat(trackId, mimeType, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
|
|
||||||
NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null, false, NO_VALUE,
|
|
||||||
NO_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MediaFormat createId3Format() {
|
|
||||||
return createFormatForMimeType(null, MimeTypes.APPLICATION_ID3, MediaFormat.NO_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* package */ MediaFormat(String trackId, String mimeType, int bitrate, int maxInputSize,
|
|
||||||
int width, int height, int rotationDegrees, float pixelWidthHeightRatio, int channelCount,
|
|
||||||
int sampleRate, String language, long subsampleOffsetUs, List<byte[]> initializationData,
|
|
||||||
boolean adaptive, int maxWidth, int maxHeight) {
|
|
||||||
this.trackId = trackId;
|
|
||||||
this.mimeType = Assertions.checkNotEmpty(mimeType);
|
|
||||||
this.bitrate = bitrate;
|
|
||||||
this.maxInputSize = maxInputSize;
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.rotationDegrees = rotationDegrees;
|
|
||||||
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
|
|
||||||
this.channelCount = channelCount;
|
|
||||||
this.sampleRate = sampleRate;
|
|
||||||
this.language = language;
|
|
||||||
this.subsampleOffsetUs = subsampleOffsetUs;
|
|
||||||
this.initializationData = initializationData == null ? Collections.<byte[]>emptyList()
|
|
||||||
: initializationData;
|
|
||||||
this.adaptive = adaptive;
|
|
||||||
this.maxWidth = maxWidth;
|
|
||||||
this.maxHeight = maxHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MediaFormat copyWithMaxInputSize(int maxInputSize) {
|
|
||||||
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, width, height, rotationDegrees,
|
|
||||||
pixelWidthHeightRatio, channelCount, sampleRate, language, subsampleOffsetUs,
|
|
||||||
initializationData, adaptive, maxWidth, maxHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MediaFormat copyWithMaxVideoDimensions(int maxWidth, int maxHeight) {
|
|
||||||
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, width, height, rotationDegrees,
|
|
||||||
pixelWidthHeightRatio, channelCount, sampleRate, language, subsampleOffsetUs,
|
|
||||||
initializationData, adaptive, maxWidth, maxHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MediaFormat copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
|
|
||||||
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, width, height, rotationDegrees,
|
|
||||||
pixelWidthHeightRatio, channelCount, sampleRate, language, subsampleOffsetUs,
|
|
||||||
initializationData, adaptive, maxWidth, maxHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MediaFormat copyWithFixedTrackInfo(String trackId, int bitrate, int width, int height,
|
|
||||||
String language) {
|
|
||||||
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, width, height, rotationDegrees,
|
|
||||||
pixelWidthHeightRatio, channelCount, sampleRate, language, subsampleOffsetUs,
|
|
||||||
initializationData, adaptive, NO_VALUE, NO_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MediaFormat copyAsAdaptive(String trackId) {
|
|
||||||
return new MediaFormat(trackId, mimeType, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
|
|
||||||
NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null, true, maxWidth,
|
|
||||||
maxHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A {@link MediaFormat} representation of this format.
|
|
||||||
*/
|
|
||||||
@SuppressLint("InlinedApi")
|
|
||||||
@TargetApi(16)
|
|
||||||
public final android.media.MediaFormat getFrameworkMediaFormatV16() {
|
|
||||||
if (frameworkMediaFormat == null) {
|
|
||||||
android.media.MediaFormat format = new android.media.MediaFormat();
|
|
||||||
format.setString(android.media.MediaFormat.KEY_MIME, mimeType);
|
|
||||||
maybeSetStringV16(format, android.media.MediaFormat.KEY_LANGUAGE, language);
|
|
||||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
|
|
||||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_WIDTH, width);
|
|
||||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT, height);
|
|
||||||
maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees);
|
|
||||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_WIDTH, maxWidth);
|
|
||||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_HEIGHT, maxHeight);
|
|
||||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT, channelCount);
|
|
||||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE, sampleRate);
|
|
||||||
for (int i = 0; i < initializationData.size(); i++) {
|
|
||||||
format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i)));
|
|
||||||
}
|
|
||||||
frameworkMediaFormat = format;
|
|
||||||
}
|
|
||||||
return frameworkMediaFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the framework format returned by {@link #getFrameworkMediaFormatV16()}.
|
|
||||||
*
|
|
||||||
* @deprecated This method only exists for FrameworkSampleSource, which is itself deprecated.
|
|
||||||
* @param format The framework format.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
@TargetApi(16)
|
|
||||||
/* package */ final void setFrameworkFormatV16(android.media.MediaFormat format) {
|
|
||||||
frameworkMediaFormat = format;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "MediaFormat(" + trackId + ", " + mimeType + ", " + bitrate + ", " + maxInputSize
|
|
||||||
+ ", " + width + ", " + height + ", " + rotationDegrees + ", " + pixelWidthHeightRatio
|
|
||||||
+ ", " + channelCount + ", " + sampleRate + ", " + language + ", " + ", " + adaptive + ", "
|
|
||||||
+ maxWidth + ", " + maxHeight + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
if (hashCode == 0) {
|
|
||||||
int result = 17;
|
|
||||||
result = 31 * result + (trackId == null ? 0 : trackId.hashCode());
|
|
||||||
result = 31 * result + (mimeType == null ? 0 : mimeType.hashCode());
|
|
||||||
result = 31 * result + bitrate;
|
|
||||||
result = 31 * result + maxInputSize;
|
|
||||||
result = 31 * result + width;
|
|
||||||
result = 31 * result + height;
|
|
||||||
result = 31 * result + rotationDegrees;
|
|
||||||
result = 31 * result + Float.floatToRawIntBits(pixelWidthHeightRatio);
|
|
||||||
result = 31 * result + (adaptive ? 1231 : 1237);
|
|
||||||
result = 31 * result + maxWidth;
|
|
||||||
result = 31 * result + maxHeight;
|
|
||||||
result = 31 * result + channelCount;
|
|
||||||
result = 31 * result + sampleRate;
|
|
||||||
result = 31 * result + (language == null ? 0 : language.hashCode());
|
|
||||||
for (int i = 0; i < initializationData.size(); i++) {
|
|
||||||
result = 31 * result + Arrays.hashCode(initializationData.get(i));
|
|
||||||
}
|
|
||||||
hashCode = result;
|
|
||||||
}
|
|
||||||
return hashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null || getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
MediaFormat other = (MediaFormat) obj;
|
|
||||||
if (adaptive != other.adaptive || bitrate != other.bitrate || maxInputSize != other.maxInputSize
|
|
||||||
|| width != other.width || height != other.height
|
|
||||||
|| rotationDegrees != other.rotationDegrees
|
|
||||||
|| pixelWidthHeightRatio != other.pixelWidthHeightRatio
|
|
||||||
|| maxWidth != other.maxWidth || maxHeight != other.maxHeight
|
|
||||||
|| channelCount != other.channelCount || sampleRate != other.sampleRate
|
|
||||||
|| !Util.areEqual(trackId, other.trackId) || !Util.areEqual(language, other.language)
|
|
||||||
|| !Util.areEqual(mimeType, other.mimeType)
|
|
||||||
|| initializationData.size() != other.initializationData.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < initializationData.size(); i++) {
|
|
||||||
if (!Arrays.equals(initializationData.get(i), other.initializationData.get(i))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(16)
|
|
||||||
private static final void maybeSetStringV16(android.media.MediaFormat format, String key,
|
|
||||||
String value) {
|
|
||||||
if (value != null) {
|
|
||||||
format.setString(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(16)
|
|
||||||
private static final void maybeSetIntegerV16(android.media.MediaFormat format, String key,
|
|
||||||
int value) {
|
|
||||||
if (value != NO_VALUE) {
|
|
||||||
format.setInteger(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -152,7 +152,7 @@ public interface SampleSource {
|
|||||||
* Returns whether data is available to be read.
|
* Returns whether data is available to be read.
|
||||||
* <p>
|
* <p>
|
||||||
* Note: If the stream has ended then {@link #END_OF_STREAM} can always be read from
|
* 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.
|
* @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
|
* 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.
|
* 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
|
* @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}
|
* caller requires the sample data then it must ensure that {@link SampleHolder#data}
|
||||||
* references a valid output buffer.
|
* references a valid output buffer.
|
||||||
* @return The result, which can be {@link #END_OF_STREAM}, {@link #NOTHING_READ},
|
* @return The result, which can be {@link #END_OF_STREAM}, {@link #NOTHING_READ},
|
||||||
* {@link #FORMAT_READ} or {@link #SAMPLE_READ}.
|
* {@link #FORMAT_READ} or {@link #SAMPLE_READ}.
|
||||||
*/
|
*/
|
||||||
int readData(MediaFormatHolder formatHolder, SampleHolder sampleHolder);
|
int readData(FormatHolder formatHolder, SampleHolder sampleHolder);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disables the track.
|
* Disables the track.
|
||||||
|
@ -29,8 +29,8 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer {
|
|||||||
private TrackStream trackStream;
|
private TrackStream trackStream;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onEnabled(TrackStream trackStream, long positionUs, boolean joining)
|
protected void onEnabled(Format[] formats, TrackStream trackStream, long positionUs,
|
||||||
throws ExoPlaybackException {
|
boolean joining) throws ExoPlaybackException {
|
||||||
this.trackStream = Assertions.checkNotNull(trackStream);
|
this.trackStream = Assertions.checkNotNull(trackStream);
|
||||||
onReset(positionUs);
|
onReset(positionUs);
|
||||||
}
|
}
|
||||||
@ -63,7 +63,7 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer {
|
|||||||
/**
|
/**
|
||||||
* Reads from the enabled upstream source.
|
* 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.
|
* @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}
|
* If the caller requires the sample data then it must ensure that {@link SampleHolder#data}
|
||||||
* references a valid output buffer.
|
* 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#FORMAT_READ}, {@link TrackStream#NOTHING_READ} or
|
||||||
* {@link TrackStream#END_OF_STREAM}.
|
* {@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);
|
return trackStream.readData(formatHolder, sampleHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
|||||||
|
|
||||||
private final Uri uri;
|
private final Uri uri;
|
||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
private final MediaFormat format;
|
private final Format format;
|
||||||
private final long durationUs;
|
private final long durationUs;
|
||||||
private final int minLoadableRetryCount;
|
private final int minLoadableRetryCount;
|
||||||
private final TrackGroup tracks;
|
private final TrackGroup tracks;
|
||||||
@ -65,11 +65,11 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
|||||||
private int currentLoadableExceptionCount;
|
private int currentLoadableExceptionCount;
|
||||||
private long currentLoadableExceptionTimestamp;
|
private long currentLoadableExceptionTimestamp;
|
||||||
|
|
||||||
public SingleSampleSource(Uri uri, DataSource dataSource, MediaFormat format, long durationUs) {
|
public SingleSampleSource(Uri uri, DataSource dataSource, Format format, long durationUs) {
|
||||||
this(uri, dataSource, format, durationUs, DEFAULT_MIN_LOADABLE_RETRY_COUNT);
|
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) {
|
int minLoadableRetryCount) {
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
@ -90,7 +90,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
|||||||
@Override
|
@Override
|
||||||
public boolean prepare(long positionUs) {
|
public boolean prepare(long positionUs) {
|
||||||
if (loader == null) {
|
if (loader == null) {
|
||||||
loader = new Loader("Loader:" + format.mimeType);
|
loader = new Loader("Loader:" + format.sampleMimeType);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -139,7 +139,7 @@ public final class SingleSampleSource implements SampleSource, TrackStream, Load
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int readData(MediaFormatHolder formatHolder, SampleHolder sampleHolder) {
|
public int readData(FormatHolder formatHolder, SampleHolder sampleHolder) {
|
||||||
if (state == STATE_END_OF_STREAM) {
|
if (state == STATE_END_OF_STREAM) {
|
||||||
return END_OF_STREAM;
|
return END_OF_STREAM;
|
||||||
} else if (state == STATE_SEND_FORMAT) {
|
} else if (state == STATE_SEND_FORMAT) {
|
||||||
|
@ -36,12 +36,12 @@ public final class TrackGroup {
|
|||||||
*/
|
*/
|
||||||
public final boolean adaptive;
|
public final boolean adaptive;
|
||||||
|
|
||||||
private final MediaFormat[] formats;
|
private final Format[] formats;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param format The format of the single track.
|
* @param format The format of the single track.
|
||||||
*/
|
*/
|
||||||
public TrackGroup(MediaFormat format) {
|
public TrackGroup(Format format) {
|
||||||
this(false, 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 adaptive Whether it's possible to adapt between multiple tracks in the group.
|
||||||
* @param formats The track formats.
|
* @param formats The track formats.
|
||||||
*/
|
*/
|
||||||
public TrackGroup(boolean adaptive, MediaFormat... formats) {
|
public TrackGroup(boolean adaptive, Format... formats) {
|
||||||
this.adaptive = adaptive;
|
this.adaptive = adaptive;
|
||||||
this.formats = formats;
|
this.formats = formats;
|
||||||
length = formats.length;
|
length = formats.length;
|
||||||
@ -61,7 +61,7 @@ public final class TrackGroup {
|
|||||||
* @param index The index of the track.
|
* @param index The index of the track.
|
||||||
* @return The track's format.
|
* @return The track's format.
|
||||||
*/
|
*/
|
||||||
public MediaFormat getFormat(int index) {
|
public Format getFormat(int index) {
|
||||||
return formats[index];
|
return formats[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,27 +130,28 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
|
|||||||
/**
|
/**
|
||||||
* Returns the extent to which the renderer is capable of rendering a given format.
|
* 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
|
* @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_HANDLED}, {@link #FORMAT_EXCEEDS_CAPABILITIES} and
|
||||||
* {@link #FORMAT_UNSUPPORTED_TYPE}.
|
* {@link #FORMAT_UNSUPPORTED_TYPE}.
|
||||||
* @throws ExoPlaybackException If an error occurs.
|
* @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}.
|
* 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 trackStream The track stream from which the renderer should consume.
|
||||||
* @param positionUs The player's current position.
|
* @param positionUs The player's current position.
|
||||||
* @param joining Whether this renderer is being enabled to join an ongoing playback.
|
* @param joining Whether this renderer is being enabled to join an ongoing playback.
|
||||||
* @throws ExoPlaybackException If an error occurs.
|
* @throws ExoPlaybackException If an error occurs.
|
||||||
*/
|
*/
|
||||||
/* package */ final void enable(TrackStream trackStream, long positionUs, boolean joining)
|
/* package */ final void enable(Format[] formats, TrackStream trackStream, long positionUs,
|
||||||
throws ExoPlaybackException {
|
boolean joining) throws ExoPlaybackException {
|
||||||
Assertions.checkState(state == STATE_IDLE);
|
Assertions.checkState(state == STATE_IDLE);
|
||||||
state = STATE_ENABLED;
|
state = STATE_ENABLED;
|
||||||
onEnabled(trackStream, positionUs, joining);
|
onEnabled(formats, trackStream, positionUs, joining);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -158,13 +159,14 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
|
|||||||
* <p>
|
* <p>
|
||||||
* The default implementation is a no-op.
|
* The default implementation is a no-op.
|
||||||
*
|
*
|
||||||
|
* @param formats The enabled formats.
|
||||||
* @param trackStream The track stream from which the renderer should consume.
|
* @param trackStream The track stream from which the renderer should consume.
|
||||||
* @param positionUs The player's current position.
|
* @param positionUs The player's current position.
|
||||||
* @param joining Whether this renderer is being enabled to join an ongoing playback.
|
* @param joining Whether this renderer is being enabled to join an ongoing playback.
|
||||||
* @throws ExoPlaybackException If an error occurs.
|
* @throws ExoPlaybackException If an error occurs.
|
||||||
*/
|
*/
|
||||||
protected void onEnabled(TrackStream trackStream, long positionUs, boolean joining)
|
protected void onEnabled(Format[] formats, TrackStream trackStream, long positionUs,
|
||||||
throws ExoPlaybackException {
|
boolean joining) throws ExoPlaybackException {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer.chunk;
|
package com.google.android.exoplayer.chunk;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.SampleSource;
|
import com.google.android.exoplayer.SampleSource;
|
||||||
import com.google.android.exoplayer.SampleSource.TrackStream;
|
import com.google.android.exoplayer.SampleSource.TrackStream;
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.chunk;
|
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.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
|
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
@ -29,11 +29,11 @@ import com.google.android.exoplayer.upstream.DataSpec;
|
|||||||
public abstract class BaseMediaChunk extends MediaChunk {
|
public abstract class BaseMediaChunk extends MediaChunk {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether {@link #getMediaFormat()} and {@link #getDrmInitData()} can be called at any time to
|
* Whether {@link #getSampleFormat()} 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
|
* 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.
|
* 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 DefaultTrackOutput output;
|
||||||
private int firstSampleIndex;
|
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 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 endTimeUs The end time of the media contained by the chunk, in microseconds.
|
||||||
* @param chunkIndex The index of the chunk.
|
* @param chunkIndex The index of the chunk.
|
||||||
* @param isMediaFormatFinal True if {@link #getMediaFormat()} and {@link #getDrmInitData()} can
|
* @param isSampleFormatFinal True if {@link #getSampleFormat()} and {@link #getDrmInitData()} can
|
||||||
* be called at any time to obtain the media format and drm initialization data. False if
|
* 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
|
* these methods are only guaranteed to return correct data after the first sample data has
|
||||||
* been output from the chunk.
|
* been output from the chunk.
|
||||||
* @param parentId Identifier for a parent from which this chunk originates.
|
* @param parentId Identifier for a parent from which this chunk originates.
|
||||||
*/
|
*/
|
||||||
public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
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);
|
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, parentId);
|
||||||
this.isMediaFormatFinal = isMediaFormatFinal;
|
this.isSampleFormatFinal = isSampleFormatFinal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,19 +78,19 @@ public abstract class BaseMediaChunk extends MediaChunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the {@link MediaFormat} corresponding to the chunk.
|
* Gets the {@link Format} of the samples in the chunk.
|
||||||
* <p>
|
* <p>
|
||||||
* See {@link #isMediaFormatFinal} for information about when this method is guaranteed to return
|
* See {@link #isSampleFormatFinal} for information about when this method is guaranteed to return
|
||||||
* correct data.
|
* 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.
|
* Gets the {@link DrmInitData} corresponding to the chunk.
|
||||||
* <p>
|
* <p>
|
||||||
* See {@link #isMediaFormatFinal} for information about when this method is guaranteed to return
|
* See {@link #isSampleFormatFinal} for information about when this method is guaranteed to return
|
||||||
* correct data.
|
* correct data.
|
||||||
*
|
*
|
||||||
* @return The {@link DrmInitData} corresponding to this chunk.
|
* @return The {@link DrmInitData} corresponding to this chunk.
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.chunk;
|
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.DataSource;
|
||||||
import com.google.android.exoplayer.upstream.DataSpec;
|
import com.google.android.exoplayer.upstream.DataSpec;
|
||||||
import com.google.android.exoplayer.upstream.Loader.Loadable;
|
import com.google.android.exoplayer.upstream.Loader.Loadable;
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.chunk;
|
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.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer.extractor.Extractor;
|
import com.google.android.exoplayer.extractor.Extractor;
|
||||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||||
@ -122,7 +122,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
|
|||||||
// TrackOutput implementation.
|
// TrackOutput implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void format(MediaFormat format) {
|
public void format(Format format) {
|
||||||
output.format(format);
|
output.format(format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,9 +16,9 @@
|
|||||||
package com.google.android.exoplayer.chunk;
|
package com.google.android.exoplayer.chunk;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
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.LoadControl;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
|
||||||
import com.google.android.exoplayer.MediaFormatHolder;
|
|
||||||
import com.google.android.exoplayer.SampleHolder;
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
import com.google.android.exoplayer.SampleSource;
|
import com.google.android.exoplayer.SampleSource;
|
||||||
import com.google.android.exoplayer.SampleSource.TrackStream;
|
import com.google.android.exoplayer.SampleSource.TrackStream;
|
||||||
@ -86,8 +86,8 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
private long currentLoadableExceptionTimestamp;
|
private long currentLoadableExceptionTimestamp;
|
||||||
private long currentLoadStartTimeMs;
|
private long currentLoadStartTimeMs;
|
||||||
|
|
||||||
private MediaFormat downstreamMediaFormat;
|
|
||||||
private Format downstreamFormat;
|
private Format downstreamFormat;
|
||||||
|
private Format downstreamSampleFormat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param chunkSource A {@link ChunkSource} from which chunks to load are obtained.
|
* @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();
|
durationUs = chunkSource.getDurationUs();
|
||||||
TrackGroup trackGroup = chunkSource.getTracks();
|
TrackGroup trackGroup = chunkSource.getTracks();
|
||||||
if (trackGroup.length > 0) {
|
if (trackGroup.length > 0) {
|
||||||
MediaFormat firstTrackFormat = trackGroup.getFormat(0);
|
loader = new Loader("Loader:" + trackGroup.getFormat(0).containerMimeType);
|
||||||
loader = new Loader("Loader:" + firstTrackFormat.mimeType);
|
|
||||||
}
|
}
|
||||||
state = STATE_PREPARED;
|
state = STATE_PREPARED;
|
||||||
return true;
|
return true;
|
||||||
@ -191,7 +190,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
chunkSource.enable(tracks);
|
chunkSource.enable(tracks);
|
||||||
loadControl.register(this, bufferSizeContribution);
|
loadControl.register(this, bufferSizeContribution);
|
||||||
downstreamFormat = null;
|
downstreamFormat = null;
|
||||||
downstreamMediaFormat = null;
|
downstreamSampleFormat = null;
|
||||||
downstreamPositionUs = positionUs;
|
downstreamPositionUs = positionUs;
|
||||||
lastSeekPositionUs = positionUs;
|
lastSeekPositionUs = positionUs;
|
||||||
pendingReset = false;
|
pendingReset = false;
|
||||||
@ -246,7 +245,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int readData(MediaFormatHolder formatHolder, SampleHolder sampleHolder) {
|
public int readData(FormatHolder formatHolder, SampleHolder sampleHolder) {
|
||||||
Assertions.checkState(state == STATE_ENABLED);
|
Assertions.checkState(state == STATE_ENABLED);
|
||||||
if (pendingReset || isPendingReset()) {
|
if (pendingReset || isPendingReset()) {
|
||||||
return NOTHING_READ;
|
return NOTHING_READ;
|
||||||
@ -266,12 +265,12 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||||||
downstreamFormat = currentChunk.format;
|
downstreamFormat = currentChunk.format;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (haveSamples || currentChunk.isMediaFormatFinal) {
|
if (haveSamples || currentChunk.isSampleFormatFinal) {
|
||||||
MediaFormat mediaFormat = currentChunk.getMediaFormat();
|
Format sampleFormat = currentChunk.getSampleFormat();
|
||||||
if (!mediaFormat.equals(downstreamMediaFormat)) {
|
if (!sampleFormat.equals(downstreamSampleFormat)) {
|
||||||
formatHolder.format = mediaFormat;
|
formatHolder.format = sampleFormat;
|
||||||
formatHolder.drmInitData = currentChunk.getDrmInitData();
|
formatHolder.drmInitData = currentChunk.getDrmInitData();
|
||||||
downstreamMediaFormat = mediaFormat;
|
downstreamSampleFormat = sampleFormat;
|
||||||
return FORMAT_READ;
|
return FORMAT_READ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.chunk;
|
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.chunk.ChunkExtractorWrapper.SingleTrackOutput;
|
||||||
import com.google.android.exoplayer.drm.DrmInitData;
|
import com.google.android.exoplayer.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
|
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
|
||||||
@ -36,10 +36,8 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
|
|||||||
|
|
||||||
private final ChunkExtractorWrapper extractorWrapper;
|
private final ChunkExtractorWrapper extractorWrapper;
|
||||||
private final long sampleOffsetUs;
|
private final long sampleOffsetUs;
|
||||||
private final int adaptiveMaxWidth;
|
|
||||||
private final int adaptiveMaxHeight;
|
|
||||||
|
|
||||||
private MediaFormat mediaFormat;
|
private Format sampleFormat;
|
||||||
private DrmInitData drmInitData;
|
private DrmInitData drmInitData;
|
||||||
|
|
||||||
private volatile int bytesLoaded;
|
private volatile int bytesLoaded;
|
||||||
@ -55,32 +53,24 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
|
|||||||
* @param chunkIndex The index of the chunk.
|
* @param chunkIndex The index of the chunk.
|
||||||
* @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor.
|
* @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 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
|
* @param sampleFormat The {@link Format} of the samples in the chunk, if known. May be null if
|
||||||
* known to define its own format.
|
* the data is known to define its own sample 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 drmInitData The {@link DrmInitData} for the chunk. Null if the media is not drm
|
* @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.
|
* 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
|
* @param isSampleFormatFinal True if {@code sampleFormat} and {@code drmInitData} are known to be
|
||||||
* correct and final. False if the data may define its own format or initialization data.
|
* 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.
|
* @param parentId Identifier for a parent from which this chunk originates.
|
||||||
*/
|
*/
|
||||||
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format,
|
||||||
long startTimeUs, long endTimeUs, int chunkIndex, long sampleOffsetUs,
|
long startTimeUs, long endTimeUs, int chunkIndex, long sampleOffsetUs,
|
||||||
ChunkExtractorWrapper extractorWrapper, MediaFormat mediaFormat, int adaptiveMaxWidth,
|
ChunkExtractorWrapper extractorWrapper, Format sampleFormat, DrmInitData drmInitData,
|
||||||
int adaptiveMaxHeight, DrmInitData drmInitData, boolean isMediaFormatFinal, int parentId) {
|
boolean isSampleFormatFinal, int parentId) {
|
||||||
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex,
|
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex,
|
||||||
isMediaFormatFinal, parentId);
|
isSampleFormatFinal, parentId);
|
||||||
this.extractorWrapper = extractorWrapper;
|
this.extractorWrapper = extractorWrapper;
|
||||||
this.sampleOffsetUs = sampleOffsetUs;
|
this.sampleOffsetUs = sampleOffsetUs;
|
||||||
this.adaptiveMaxWidth = adaptiveMaxWidth;
|
this.sampleFormat = getAdjustedSampleFormat(sampleFormat, sampleOffsetUs);
|
||||||
this.adaptiveMaxHeight = adaptiveMaxHeight;
|
|
||||||
this.mediaFormat = getAdjustedMediaFormat(mediaFormat, sampleOffsetUs, adaptiveMaxWidth,
|
|
||||||
adaptiveMaxHeight);
|
|
||||||
this.drmInitData = drmInitData;
|
this.drmInitData = drmInitData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,8 +80,8 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final MediaFormat getMediaFormat() {
|
public final Format getSampleFormat() {
|
||||||
return mediaFormat;
|
return sampleFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -112,9 +102,8 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void format(MediaFormat mediaFormat) {
|
public final void format(Format format) {
|
||||||
this.mediaFormat = getAdjustedMediaFormat(mediaFormat, sampleOffsetUs, adaptiveMaxWidth,
|
this.sampleFormat = getAdjustedSampleFormat(format, sampleOffsetUs);
|
||||||
adaptiveMaxHeight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -174,17 +163,13 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOu
|
|||||||
|
|
||||||
// Private methods.
|
// Private methods.
|
||||||
|
|
||||||
private static MediaFormat getAdjustedMediaFormat(MediaFormat format, long sampleOffsetUs,
|
private static Format getAdjustedSampleFormat(Format format, long sampleOffsetUs) {
|
||||||
int adaptiveMaxWidth, int adaptiveMaxHeight) {
|
|
||||||
if (format == null) {
|
if (format == null) {
|
||||||
return 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);
|
format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + sampleOffsetUs);
|
||||||
}
|
}
|
||||||
if (adaptiveMaxWidth != MediaFormat.NO_VALUE || adaptiveMaxHeight != MediaFormat.NO_VALUE) {
|
|
||||||
format = format.copyWithMaxVideoDimensions(adaptiveMaxWidth, adaptiveMaxHeight);
|
|
||||||
}
|
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.chunk;
|
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.DataSource;
|
||||||
import com.google.android.exoplayer.upstream.DataSpec;
|
import com.google.android.exoplayer.upstream.DataSpec;
|
||||||
|
|
||||||
|
@ -1,173 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.exoplayer.chunk;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines the high level format of a media stream.
|
|
||||||
*/
|
|
||||||
public class Format {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sorts {@link Format} objects in order of decreasing bandwidth.
|
|
||||||
*/
|
|
||||||
public static final class DecreasingBandwidthComparator implements Comparator<Format> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(Format a, Format b) {
|
|
||||||
return b.bitrate - a.bitrate;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An identifier for the format.
|
|
||||||
*/
|
|
||||||
public final String id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The mime type of the format.
|
|
||||||
*/
|
|
||||||
public final String mimeType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The average bandwidth in bits per second.
|
|
||||||
*/
|
|
||||||
public final int bitrate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The width of the video in pixels, or -1 if unknown or not applicable.
|
|
||||||
*/
|
|
||||||
public final int width;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The height of the video in pixels, or -1 if unknown or not applicable.
|
|
||||||
*/
|
|
||||||
public final int height;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The video frame rate in frames per second, or -1 if unknown or not applicable.
|
|
||||||
*/
|
|
||||||
public final float frameRate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The number of audio channels, or -1 if unknown or not applicable.
|
|
||||||
*/
|
|
||||||
public final int audioChannels;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The audio sampling rate in Hz, or -1 if unknown or not applicable.
|
|
||||||
*/
|
|
||||||
public final int audioSamplingRate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The codecs used to decode the format. Can be {@code null} if unknown.
|
|
||||||
*/
|
|
||||||
public final String codecs;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The language of the format. Can be null if unknown.
|
|
||||||
* <p>
|
|
||||||
* The language codes are two-letter lowercase ISO language codes (such as "en") as defined by
|
|
||||||
* ISO 639-1.
|
|
||||||
*/
|
|
||||||
public final String language;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param id The format identifier.
|
|
||||||
* @param mimeType The format mime type.
|
|
||||||
* @param width The width of the video in pixels, or -1 if unknown or not applicable.
|
|
||||||
* @param height The height of the video in pixels, or -1 if unknown or not applicable.
|
|
||||||
* @param frameRate The frame rate of the video in frames per second, or -1 if unknown or not
|
|
||||||
* applicable.
|
|
||||||
* @param numChannels The number of audio channels, or -1 if unknown or not applicable.
|
|
||||||
* @param audioSamplingRate The audio sampling rate in Hz, or -1 if unknown or not applicable.
|
|
||||||
* @param bitrate The average bandwidth of the format in bits per second.
|
|
||||||
*/
|
|
||||||
public Format(String id, String mimeType, int width, int height, float frameRate, int numChannels,
|
|
||||||
int audioSamplingRate, int bitrate) {
|
|
||||||
this(id, mimeType, width, height, frameRate, numChannels, audioSamplingRate, bitrate, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param id The format identifier.
|
|
||||||
* @param mimeType The format mime type.
|
|
||||||
* @param width The width of the video in pixels, or -1 if unknown or not applicable.
|
|
||||||
* @param height The height of the video in pixels, or -1 if unknown or not applicable.
|
|
||||||
* @param frameRate The frame rate of the video in frames per second, or -1 if unknown or not
|
|
||||||
* applicable.
|
|
||||||
* @param numChannels The number of audio channels, or -1 if unknown or not applicable.
|
|
||||||
* @param audioSamplingRate The audio sampling rate in Hz, or -1 if unknown or not applicable.
|
|
||||||
* @param bitrate The average bandwidth of the format in bits per second.
|
|
||||||
* @param language The language of the format.
|
|
||||||
*/
|
|
||||||
public Format(String id, String mimeType, int width, int height, float frameRate, int numChannels,
|
|
||||||
int audioSamplingRate, int bitrate, String language) {
|
|
||||||
this(id, mimeType, width, height, frameRate, numChannels, audioSamplingRate, bitrate, language,
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param id The format identifier.
|
|
||||||
* @param mimeType The format mime type.
|
|
||||||
* @param width The width of the video in pixels, or -1 if unknown or not applicable.
|
|
||||||
* @param height The height of the video in pixels, or -1 if unknown or not applicable.
|
|
||||||
* @param frameRate The frame rate of the video in frames per second, or -1 if unknown or not
|
|
||||||
* applicable.
|
|
||||||
* @param audioChannels The number of audio channels, or -1 if unknown or not applicable.
|
|
||||||
* @param audioSamplingRate The audio sampling rate in Hz, or -1 if unknown or not applicable.
|
|
||||||
* @param bitrate The average bandwidth of the format in bits per second.
|
|
||||||
* @param language The language of the format.
|
|
||||||
* @param codecs The codecs used to decode the format.
|
|
||||||
*/
|
|
||||||
public Format(String id, String mimeType, int width, int height, float frameRate,
|
|
||||||
int audioChannels, int audioSamplingRate, int bitrate, String language, String codecs) {
|
|
||||||
this.id = Assertions.checkNotNull(id);
|
|
||||||
this.mimeType = mimeType;
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.frameRate = frameRate;
|
|
||||||
this.audioChannels = audioChannels;
|
|
||||||
this.audioSamplingRate = audioSamplingRate;
|
|
||||||
this.bitrate = bitrate;
|
|
||||||
this.language = language;
|
|
||||||
this.codecs = codecs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return id.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements equality based on {@link #id} only.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null || getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Format other = (Format) obj;
|
|
||||||
return other.id.equals(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.chunk;
|
package com.google.android.exoplayer.chunk;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.upstream.BandwidthMeter;
|
import com.google.android.exoplayer.upstream.BandwidthMeter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -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();
|
|
||||||
|
|
||||||
}
|
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.chunk;
|
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.chunk.ChunkExtractorWrapper.SingleTrackOutput;
|
||||||
import com.google.android.exoplayer.drm.DrmInitData;
|
import com.google.android.exoplayer.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
|
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
|
// 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
|
// 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.
|
// for the reading thread to know that loading has completed.
|
||||||
private MediaFormat mediaFormat;
|
private Format sampleFormat;
|
||||||
private DrmInitData drmInitData;
|
private DrmInitData drmInitData;
|
||||||
private SeekMap seekMap;
|
private SeekMap seekMap;
|
||||||
|
|
||||||
@ -73,21 +73,21 @@ public final class InitializationChunk extends Chunk implements SingleTrackOutpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if a {@link MediaFormat} was parsed from the chunk. False otherwise.
|
* True if a {@link Format} was parsed from the chunk. False otherwise.
|
||||||
* <p>
|
* <p>
|
||||||
* Should be called after loading has completed.
|
* Should be called after loading has completed.
|
||||||
*/
|
*/
|
||||||
public boolean hasFormat() {
|
public boolean hasSampleFormat() {
|
||||||
return mediaFormat != null;
|
return format != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link MediaFormat} parsed from the chunk, or null.
|
* Returns a {@link Format} parsed from the chunk, or null.
|
||||||
* <p>
|
* <p>
|
||||||
* Should be called after loading has completed.
|
* Should be called after loading has completed.
|
||||||
*/
|
*/
|
||||||
public MediaFormat getFormat() {
|
public Format getSampleFormat() {
|
||||||
return mediaFormat;
|
return sampleFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -139,8 +139,8 @@ public final class InitializationChunk extends Chunk implements SingleTrackOutpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void format(MediaFormat mediaFormat) {
|
public void format(Format format) {
|
||||||
this.mediaFormat = mediaFormat;
|
this.sampleFormat = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.chunk;
|
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.DataSource;
|
||||||
import com.google.android.exoplayer.upstream.DataSpec;
|
import com.google.android.exoplayer.upstream.DataSpec;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.chunk;
|
package com.google.android.exoplayer.chunk;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
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.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
import com.google.android.exoplayer.upstream.DataSpec;
|
import com.google.android.exoplayer.upstream.DataSpec;
|
||||||
@ -29,7 +29,7 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public final class SingleSampleMediaChunk extends BaseMediaChunk {
|
public final class SingleSampleMediaChunk extends BaseMediaChunk {
|
||||||
|
|
||||||
private final MediaFormat sampleFormat;
|
private final Format sampleFormat;
|
||||||
private final DrmInitData sampleDrmInitData;
|
private final DrmInitData sampleDrmInitData;
|
||||||
|
|
||||||
private volatile int bytesLoaded;
|
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.
|
* @param parentId Identifier for a parent from which this chunk originates.
|
||||||
*/
|
*/
|
||||||
public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger,
|
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) {
|
DrmInitData sampleDrmInitData, int parentId) {
|
||||||
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, true,
|
super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, true,
|
||||||
parentId);
|
parentId);
|
||||||
@ -63,7 +63,7 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaFormat getMediaFormat() {
|
public Format getSampleFormat() {
|
||||||
return sampleFormat;
|
return sampleFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,8 @@ package com.google.android.exoplayer.dash;
|
|||||||
|
|
||||||
import com.google.android.exoplayer.BehindLiveWindowException;
|
import com.google.android.exoplayer.BehindLiveWindowException;
|
||||||
import com.google.android.exoplayer.C;
|
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;
|
||||||
import com.google.android.exoplayer.TimeRange.DynamicTimeRange;
|
import com.google.android.exoplayer.TimeRange.DynamicTimeRange;
|
||||||
import com.google.android.exoplayer.TimeRange.StaticTimeRange;
|
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.ChunkOperationHolder;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
import com.google.android.exoplayer.chunk.ChunkSource;
|
||||||
import com.google.android.exoplayer.chunk.ContainerMediaChunk;
|
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;
|
||||||
import com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation;
|
import com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation;
|
||||||
import com.google.android.exoplayer.chunk.InitializationChunk;
|
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 com.google.android.exoplayer.util.SystemClock;
|
||||||
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.Log;
|
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
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 Handler eventHandler;
|
||||||
private final EventListener eventListener;
|
private final EventListener eventListener;
|
||||||
|
|
||||||
@ -133,12 +128,9 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
// Properties of exposed tracks.
|
// Properties of exposed tracks.
|
||||||
private int adaptationSetIndex;
|
private int adaptationSetIndex;
|
||||||
private TrackGroup trackGroup;
|
private TrackGroup trackGroup;
|
||||||
private Format[] trackFormats;
|
|
||||||
|
|
||||||
// Properties of enabled tracks.
|
// Properties of enabled tracks.
|
||||||
private Format[] enabledFormats;
|
private Format[] enabledFormats;
|
||||||
private int adaptiveMaxWidth;
|
|
||||||
private int adaptiveMaxHeight;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param manifestFetcher A fetcher for the manifest.
|
* @param manifestFetcher A fetcher for the manifest.
|
||||||
@ -250,7 +242,7 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
} else {
|
} else {
|
||||||
live = currentManifest.dynamic;
|
live = currentManifest.dynamic;
|
||||||
durationUs = live ? C.UNKNOWN_TIME_US : currentManifest.duration * 1000;
|
durationUs = live ? C.UNKNOWN_TIME_US : currentManifest.duration * 1000;
|
||||||
selectTracks(currentManifest, 0);
|
initForManifest(currentManifest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -268,22 +260,13 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enable(int[] tracks) {
|
public void enable(int[] tracks) {
|
||||||
int maxWidth = -1;
|
|
||||||
int maxHeight = -1;
|
|
||||||
enabledFormats = new Format[tracks.length];
|
enabledFormats = new Format[tracks.length];
|
||||||
for (int i = 0; i < tracks.length; i++) {
|
for (int i = 0; i < tracks.length; i++) {
|
||||||
enabledFormats[i] = trackFormats[tracks[i]];
|
enabledFormats[i] = trackGroup.getFormat(tracks[i]);
|
||||||
maxWidth = Math.max(enabledFormats[i].width, maxWidth);
|
|
||||||
maxHeight = Math.max(enabledFormats[i].height, maxHeight);
|
|
||||||
}
|
}
|
||||||
Arrays.sort(enabledFormats, new DecreasingBandwidthComparator());
|
Arrays.sort(enabledFormats, new DecreasingBandwidthComparator());
|
||||||
if (enabledFormats.length > 1) {
|
if (enabledFormats.length > 1) {
|
||||||
adaptiveMaxWidth = maxWidth;
|
|
||||||
adaptiveMaxHeight = maxHeight;
|
|
||||||
adaptiveFormatEvaluator.enable();
|
adaptiveFormatEvaluator.enable();
|
||||||
} else {
|
|
||||||
adaptiveMaxWidth = -1;
|
|
||||||
adaptiveMaxHeight = -1;
|
|
||||||
}
|
}
|
||||||
processManifest(manifestFetcher.getManifest());
|
processManifest(manifestFetcher.getManifest());
|
||||||
}
|
}
|
||||||
@ -342,7 +325,7 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
out.chunk = null;
|
out.chunk = null;
|
||||||
return;
|
return;
|
||||||
} else if (out.queueSize == queue.size() && out.chunk != null
|
} 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
|
// We already have a chunk, and the evaluation hasn't changed either the format or the size
|
||||||
// of the queue. Leave unchanged.
|
// of the queue. Leave unchanged.
|
||||||
return;
|
return;
|
||||||
@ -394,7 +377,7 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
PeriodHolder lastPeriodHolder = periodHolders.valueAt(periodHolders.size() - 1);
|
PeriodHolder lastPeriodHolder = periodHolders.valueAt(periodHolders.size() - 1);
|
||||||
if (previous.parentId == lastPeriodHolder.localIndex) {
|
if (previous.parentId == lastPeriodHolder.localIndex) {
|
||||||
RepresentationHolder representationHolder =
|
RepresentationHolder representationHolder =
|
||||||
lastPeriodHolder.representationHolders.get(previous.format.id);
|
lastPeriodHolder.representationHolders[getTrackIndex(previous.format)];
|
||||||
if (representationHolder.isBeyondLastSegment(previous.getNextChunkIndex())) {
|
if (representationHolder.isBeyondLastSegment(previous.getNextChunkIndex())) {
|
||||||
out.endOfStream = true;
|
out.endOfStream = true;
|
||||||
return;
|
return;
|
||||||
@ -413,7 +396,7 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
startingNewPeriod = true;
|
startingNewPeriod = true;
|
||||||
} else if (!periodHolder.isIndexUnbounded()) {
|
} else if (!periodHolder.isIndexUnbounded()) {
|
||||||
RepresentationHolder representationHolder =
|
RepresentationHolder representationHolder =
|
||||||
periodHolder.representationHolders.get(previous.format.id);
|
periodHolder.representationHolders[getTrackIndex(previous.format)];
|
||||||
if (representationHolder.isBeyondLastSegment(previous.getNextChunkIndex())) {
|
if (representationHolder.isBeyondLastSegment(previous.getNextChunkIndex())) {
|
||||||
// We reached the end of a period. Start the next one.
|
// We reached the end of a period. Start the next one.
|
||||||
periodHolder = periodHolders.get(previous.parentId + 1);
|
periodHolder = periodHolders.get(previous.parentId + 1);
|
||||||
@ -423,14 +406,14 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RepresentationHolder representationHolder =
|
RepresentationHolder representationHolder =
|
||||||
periodHolder.representationHolders.get(selectedFormat.id);
|
periodHolder.representationHolders[getTrackIndex(selectedFormat)];
|
||||||
Representation selectedRepresentation = representationHolder.representation;
|
Representation selectedRepresentation = representationHolder.representation;
|
||||||
|
|
||||||
RangedUri pendingInitializationUri = null;
|
RangedUri pendingInitializationUri = null;
|
||||||
RangedUri pendingIndexUri = null;
|
RangedUri pendingIndexUri = null;
|
||||||
|
|
||||||
MediaFormat mediaFormat = representationHolder.mediaFormat;
|
Format sampleFormat = representationHolder.sampleFormat;
|
||||||
if (mediaFormat == null) {
|
if (sampleFormat == null) {
|
||||||
pendingInitializationUri = selectedRepresentation.getInitializationUri();
|
pendingInitializationUri = selectedRepresentation.getInitializationUri();
|
||||||
}
|
}
|
||||||
if (representationHolder.segmentIndex == null) {
|
if (representationHolder.segmentIndex == null) {
|
||||||
@ -451,7 +434,7 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
: startingNewPeriod ? representationHolder.getFirstAvailableSegmentNum()
|
: startingNewPeriod ? representationHolder.getFirstAvailableSegmentNum()
|
||||||
: queue.get(out.queueSize - 1).getNextChunkIndex();
|
: queue.get(out.queueSize - 1).getNextChunkIndex();
|
||||||
Chunk nextMediaChunk = newMediaChunk(periodHolder, representationHolder, dataSource,
|
Chunk nextMediaChunk = newMediaChunk(periodHolder, representationHolder, dataSource,
|
||||||
mediaFormat, adaptiveMaxWidth, adaptiveMaxHeight, segmentNum, evaluation.trigger);
|
sampleFormat, segmentNum, evaluation.trigger);
|
||||||
lastChunkWasInitialization = false;
|
lastChunkWasInitialization = false;
|
||||||
out.chunk = nextMediaChunk;
|
out.chunk = nextMediaChunk;
|
||||||
}
|
}
|
||||||
@ -460,16 +443,16 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
public void onChunkLoadCompleted(Chunk chunk) {
|
public void onChunkLoadCompleted(Chunk chunk) {
|
||||||
if (chunk instanceof InitializationChunk) {
|
if (chunk instanceof InitializationChunk) {
|
||||||
InitializationChunk initializationChunk = (InitializationChunk) chunk;
|
InitializationChunk initializationChunk = (InitializationChunk) chunk;
|
||||||
String formatId = initializationChunk.format.id;
|
|
||||||
PeriodHolder periodHolder = periodHolders.get(initializationChunk.parentId);
|
PeriodHolder periodHolder = periodHolders.get(initializationChunk.parentId);
|
||||||
if (periodHolder == null) {
|
if (periodHolder == null) {
|
||||||
// period for this initialization chunk may no longer be on the manifest
|
// period for this initialization chunk may no longer be on the manifest
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RepresentationHolder representationHolder = periodHolder.representationHolders.get(formatId);
|
RepresentationHolder representationHolder =
|
||||||
if (initializationChunk.hasFormat()) {
|
periodHolder.representationHolders[getTrackIndex(initializationChunk.format)];
|
||||||
representationHolder.mediaFormat = initializationChunk.getFormat();
|
if (initializationChunk.hasSampleFormat()) {
|
||||||
|
representationHolder.sampleFormat = initializationChunk.getSampleFormat();
|
||||||
}
|
}
|
||||||
if (initializationChunk.hasSeekMap()) {
|
if (initializationChunk.hasSeekMap()) {
|
||||||
representationHolder.segmentIndex = new DashWrappingSegmentIndex(
|
representationHolder.segmentIndex = new DashWrappingSegmentIndex(
|
||||||
@ -504,45 +487,23 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
|
|
||||||
// Private methods.
|
// Private methods.
|
||||||
|
|
||||||
private void selectTracks(MediaPresentationDescription manifest, int periodIndex) {
|
private void initForManifest(MediaPresentationDescription manifest) {
|
||||||
Period period = manifest.getPeriod(periodIndex);
|
Period period = manifest.getPeriod(0);
|
||||||
for (int i = 0; i < period.adaptationSets.size(); i++) {
|
for (int i = 0; i < period.adaptationSets.size(); i++) {
|
||||||
AdaptationSet adaptationSet = period.adaptationSets.get(i);
|
AdaptationSet adaptationSet = period.adaptationSets.get(i);
|
||||||
if (adaptationSet.type == adaptationSetType) {
|
if (adaptationSet.type == adaptationSetType) {
|
||||||
// We've found an adaptation set of the exposed type.
|
// We've found an adaptation set of the exposed type.
|
||||||
adaptationSetIndex = i;
|
adaptationSetIndex = i;
|
||||||
List<Representation> representations = adaptationSet.representations;
|
List<Representation> representations = adaptationSet.representations;
|
||||||
trackFormats = new Format[representations.size()];
|
Format[] trackFormats = new Format[representations.size()];
|
||||||
MediaFormat[] trackMediaFormats = new MediaFormat[representations.size()];
|
for (int j = 0; j < trackFormats.length; j++) {
|
||||||
int trackCount = 0;
|
trackFormats[j] = representations.get(j).format;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
trackGroup = new TrackGroup(adaptiveFormatEvaluator != null, trackFormats);
|
||||||
trackGroup = new TrackGroup(adaptiveFormatEvaluator != null,
|
|
||||||
Arrays.copyOf(trackMediaFormats, trackCount));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trackGroup = new TrackGroup(adaptiveFormatEvaluator != null);
|
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.
|
// Visible for testing.
|
||||||
@ -550,48 +511,6 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
return availableRange;
|
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,
|
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri,
|
||||||
Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource,
|
Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource,
|
||||||
int manifestIndex, int trigger) {
|
int manifestIndex, int trigger) {
|
||||||
@ -613,26 +532,25 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Chunk newMediaChunk(PeriodHolder periodHolder,
|
protected Chunk newMediaChunk(PeriodHolder periodHolder,
|
||||||
RepresentationHolder representationHolder, DataSource dataSource, MediaFormat mediaFormat,
|
RepresentationHolder representationHolder, DataSource dataSource, Format sampleFormat,
|
||||||
int adaptiveMaxWidth, int adaptiveMaxHeight, int segmentNum, int trigger) {
|
int segmentNum, int trigger) {
|
||||||
Representation representation = representationHolder.representation;
|
Representation representation = representationHolder.representation;
|
||||||
Format format = representation.format;
|
|
||||||
long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum);
|
long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum);
|
||||||
long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum);
|
long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum);
|
||||||
RangedUri segmentUri = representationHolder.getSegmentUrl(segmentNum);
|
RangedUri segmentUri = representationHolder.getSegmentUrl(segmentNum);
|
||||||
DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length,
|
DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length,
|
||||||
representation.getCacheKey());
|
representation.getCacheKey());
|
||||||
|
|
||||||
|
Format format = representation.format;
|
||||||
long sampleOffsetUs = periodHolder.startTimeUs - representation.presentationTimeOffsetUs;
|
long sampleOffsetUs = periodHolder.startTimeUs - representation.presentationTimeOffsetUs;
|
||||||
if (mimeTypeIsRawText(format.mimeType)) {
|
if (representationHolder.extractorWrapper == null) {
|
||||||
return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, format,
|
return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, format,
|
||||||
startTimeUs, endTimeUs, segmentNum, mediaFormat, null, periodHolder.localIndex);
|
startTimeUs, endTimeUs, segmentNum, format, null, periodHolder.localIndex);
|
||||||
} else {
|
} else {
|
||||||
boolean isMediaFormatFinal = (mediaFormat != null);
|
boolean isSampleFormatFinal = sampleFormat != null;
|
||||||
return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs,
|
return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs,
|
||||||
segmentNum, sampleOffsetUs, representationHolder.extractorWrapper, mediaFormat,
|
segmentNum, sampleOffsetUs, representationHolder.extractorWrapper, sampleFormat,
|
||||||
adaptiveMaxWidth, adaptiveMaxHeight, periodHolder.drmInitData,
|
periodHolder.drmInitData, isSampleFormatFinal, periodHolder.localIndex);
|
||||||
isMediaFormatFinal, periodHolder.localIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -696,8 +614,8 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
|
|
||||||
// Add new periods.
|
// Add new periods.
|
||||||
for (int i = periodHolders.size(); i < manifest.getPeriodCount(); i++) {
|
for (int i = periodHolders.size(); i < manifest.getPeriodCount(); i++) {
|
||||||
PeriodHolder holder = new PeriodHolder(nextPeriodHolderIndex, manifest, i, adaptationSetIndex,
|
PeriodHolder holder = new PeriodHolder(nextPeriodHolderIndex, manifest, i,
|
||||||
enabledFormats);
|
adaptationSetIndex);
|
||||||
periodHolders.put(nextPeriodHolderIndex, holder);
|
periodHolders.put(nextPeriodHolderIndex, holder);
|
||||||
nextPeriodHolderIndex++;
|
nextPeriodHolderIndex++;
|
||||||
}
|
}
|
||||||
@ -732,6 +650,16 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
timeShiftBufferDepthUs, systemClock);
|
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) {
|
private void notifyAvailableRangeChanged(final TimeRange seekRange) {
|
||||||
if (eventHandler != null && eventListener != null) {
|
if (eventHandler != null && eventListener != null) {
|
||||||
eventHandler.post(new Runnable() {
|
eventHandler.post(new Runnable() {
|
||||||
@ -747,12 +675,11 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
|
|
||||||
protected static final class RepresentationHolder {
|
protected static final class RepresentationHolder {
|
||||||
|
|
||||||
public final boolean mimeTypeIsRawText;
|
|
||||||
public final ChunkExtractorWrapper extractorWrapper;
|
public final ChunkExtractorWrapper extractorWrapper;
|
||||||
|
|
||||||
public Representation representation;
|
public Representation representation;
|
||||||
public DashSegmentIndex segmentIndex;
|
public DashSegmentIndex segmentIndex;
|
||||||
public MediaFormat mediaFormat;
|
public Format sampleFormat;
|
||||||
|
|
||||||
private final long periodStartTimeUs;
|
private final long periodStartTimeUs;
|
||||||
|
|
||||||
@ -764,10 +691,9 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
this.periodStartTimeUs = periodStartTimeUs;
|
this.periodStartTimeUs = periodStartTimeUs;
|
||||||
this.periodDurationUs = periodDurationUs;
|
this.periodDurationUs = periodDurationUs;
|
||||||
this.representation = representation;
|
this.representation = representation;
|
||||||
String mimeType = representation.format.mimeType;
|
String containerMimeType = representation.format.containerMimeType;
|
||||||
mimeTypeIsRawText = mimeTypeIsRawText(mimeType);
|
extractorWrapper = mimeTypeIsRawText(containerMimeType) ? null : new ChunkExtractorWrapper(
|
||||||
extractorWrapper = mimeTypeIsRawText ? null : new ChunkExtractorWrapper(
|
mimeTypeIsWebm(containerMimeType) ? new WebmExtractor() : new FragmentedMp4Extractor());
|
||||||
mimeTypeIsWebm(mimeType) ? new WebmExtractor() : new FragmentedMp4Extractor());
|
|
||||||
segmentIndex = representation.getIndex();
|
segmentIndex = representation.getIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -841,15 +767,22 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
return segmentIndex.getSegmentUrl(segmentNum - segmentNumShift);
|
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 {
|
protected static final class PeriodHolder {
|
||||||
|
|
||||||
public final int localIndex;
|
public final int localIndex;
|
||||||
public final long startTimeUs;
|
public final long startTimeUs;
|
||||||
public final HashMap<String, RepresentationHolder> representationHolders;
|
public final RepresentationHolder[] representationHolders;
|
||||||
|
|
||||||
private final int[] representationIndices;
|
|
||||||
|
|
||||||
private DrmInitData drmInitData;
|
private DrmInitData drmInitData;
|
||||||
|
|
||||||
@ -859,7 +792,7 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
private long availableEndTimeUs;
|
private long availableEndTimeUs;
|
||||||
|
|
||||||
public PeriodHolder(int localIndex, MediaPresentationDescription manifest, int manifestIndex,
|
public PeriodHolder(int localIndex, MediaPresentationDescription manifest, int manifestIndex,
|
||||||
int adaptationSetIndex, Format[] enabledFormats) {
|
int adaptationSetIndex) {
|
||||||
this.localIndex = localIndex;
|
this.localIndex = localIndex;
|
||||||
|
|
||||||
Period period = manifest.getPeriod(manifestIndex);
|
Period period = manifest.getPeriod(manifestIndex);
|
||||||
@ -870,25 +803,14 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
startTimeUs = period.startMs * 1000;
|
startTimeUs = period.startMs * 1000;
|
||||||
drmInitData = getDrmInitData(adaptationSet);
|
drmInitData = getDrmInitData(adaptationSet);
|
||||||
|
|
||||||
if (enabledFormats.length > 1) {
|
representationHolders = new RepresentationHolder[representations.size()];
|
||||||
representationIndices = new int[enabledFormats.length];
|
for (int i = 0; i < representationHolders.length; i++) {
|
||||||
for (int j = 0; j < enabledFormats.length; j++) {
|
Representation representation = representations.get(i);
|
||||||
representationIndices[j] = getRepresentationIndex(representations, enabledFormats[j].id);
|
representationHolders[i] = new RepresentationHolder(startTimeUs,
|
||||||
}
|
|
||||||
} 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,
|
|
||||||
periodDurationUs, representation);
|
periodDurationUs, representation);
|
||||||
representationHolders.put(representation.format.id, representationHolder);
|
|
||||||
}
|
}
|
||||||
updateRepresentationIndependentProperties(periodDurationUs,
|
updateRepresentationIndependentProperties(periodDurationUs,
|
||||||
representations.get(representationIndices[0]));
|
representationHolders[0].representation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updatePeriod(MediaPresentationDescription manifest, int manifestIndex,
|
public void updatePeriod(MediaPresentationDescription manifest, int manifestIndex,
|
||||||
@ -898,13 +820,12 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
List<Representation> representations = period.adaptationSets
|
List<Representation> representations = period.adaptationSets
|
||||||
.get(adaptationSetIndex).representations;
|
.get(adaptationSetIndex).representations;
|
||||||
|
|
||||||
for (int j = 0; j < representationIndices.length; j++) {
|
for (int i = 0; i < representationHolders.length; i++) {
|
||||||
Representation representation = representations.get(representationIndices[j]);
|
Representation representation = representations.get(i);
|
||||||
representationHolders.get(representation.format.id).updateRepresentation(periodDurationUs,
|
representationHolders[i].updateRepresentation(periodDurationUs, representation);
|
||||||
representation);
|
|
||||||
}
|
}
|
||||||
updateRepresentationIndependentProperties(periodDurationUs,
|
updateRepresentationIndependentProperties(periodDurationUs,
|
||||||
representations.get(representationIndices[0]));
|
representationHolders[0].representation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getAvailableStartTimeUs() {
|
public long getAvailableStartTimeUs() {
|
||||||
@ -953,17 +874,6 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getRepresentationIndex(List<Representation> representations,
|
|
||||||
String formatId) {
|
|
||||||
for (int i = 0; i < representations.size(); i++) {
|
|
||||||
Representation representation = representations.get(i);
|
|
||||||
if (formatId.equals(representation.format.id)) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("Missing format id: " + formatId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DrmInitData getDrmInitData(AdaptationSet adaptationSet) {
|
private static DrmInitData getDrmInitData(AdaptationSet adaptationSet) {
|
||||||
if (adaptationSet.contentProtections.isEmpty()) {
|
if (adaptationSet.contentProtections.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.dash.mpd;
|
package com.google.android.exoplayer.dash.mpd;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.ParserException;
|
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.SegmentList;
|
||||||
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate;
|
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTemplate;
|
||||||
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement;
|
import com.google.android.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement;
|
||||||
@ -287,22 +287,15 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected int getContentType(Representation representation) {
|
protected int getContentType(Representation representation) {
|
||||||
String mimeType = representation.format.mimeType;
|
String sampleMimeType = representation.format.sampleMimeType;
|
||||||
if (TextUtils.isEmpty(mimeType)) {
|
if (TextUtils.isEmpty(sampleMimeType)) {
|
||||||
return AdaptationSet.TYPE_UNKNOWN;
|
return AdaptationSet.TYPE_UNKNOWN;
|
||||||
} else if (MimeTypes.isVideo(mimeType)) {
|
} else if (MimeTypes.isVideo(sampleMimeType)) {
|
||||||
return AdaptationSet.TYPE_VIDEO;
|
return AdaptationSet.TYPE_VIDEO;
|
||||||
} else if (MimeTypes.isAudio(mimeType)) {
|
} else if (MimeTypes.isAudio(sampleMimeType)) {
|
||||||
return AdaptationSet.TYPE_AUDIO;
|
return AdaptationSet.TYPE_AUDIO;
|
||||||
} else if (MimeTypes.isText(mimeType) || MimeTypes.APPLICATION_TTML.equals(mimeType)) {
|
} else if (mimeTypeIsRawText(sampleMimeType)) {
|
||||||
return AdaptationSet.TYPE_TEXT;
|
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;
|
return AdaptationSet.TYPE_UNKNOWN;
|
||||||
}
|
}
|
||||||
@ -397,10 +390,22 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
segmentBase != null ? segmentBase : new SingleSegmentBase(baseUrl));
|
segmentBase != null ? segmentBase : new SingleSegmentBase(baseUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Format buildFormat(String id, String mimeType, int width, int height, float frameRate,
|
protected Format buildFormat(String id, String containerMimeType, int width, int height,
|
||||||
int audioChannels, int audioSamplingRate, int bandwidth, String language, String codecs) {
|
float frameRate, int audioChannels, int audioSamplingRate, int bitrate, String language,
|
||||||
return new Format(id, mimeType, width, height, frameRate, audioChannels, audioSamplingRate,
|
String codecs) {
|
||||||
bandwidth, language, 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,
|
protected Representation buildRepresentation(String contentId, int revisionId, Format format,
|
||||||
@ -610,6 +615,95 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
|
|||||||
|
|
||||||
// Utility methods.
|
// 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
|
* Checks two languages for consistency, returning the consistent language, or throwing an
|
||||||
* {@link IllegalStateException} if the languages are inconsistent.
|
* {@link IllegalStateException} if the languages are inconsistent.
|
||||||
|
@ -15,8 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.dash.mpd;
|
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.chunk.FormatWrapper;
|
|
||||||
import com.google.android.exoplayer.dash.DashSegmentIndex;
|
import com.google.android.exoplayer.dash.DashSegmentIndex;
|
||||||
import com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase;
|
import com.google.android.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase;
|
||||||
import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
|
import com.google.android.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase;
|
||||||
@ -26,7 +25,7 @@ import android.net.Uri;
|
|||||||
/**
|
/**
|
||||||
* A DASH representation.
|
* A DASH representation.
|
||||||
*/
|
*/
|
||||||
public abstract class Representation implements FormatWrapper {
|
public abstract class Representation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifies the piece of content to which this {@link Representation} belongs.
|
* Identifies the piece of content to which this {@link Representation} belongs.
|
||||||
@ -105,11 +104,6 @@ public abstract class Representation implements FormatWrapper {
|
|||||||
presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs();
|
presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Format getFormat() {
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a {@link RangedUri} defining the location of the representation's initialization data.
|
* Gets a {@link RangedUri} defining the location of the representation's initialization data.
|
||||||
* May be null if no initialization data exists.
|
* May be null if no initialization data exists.
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor;
|
package com.google.android.exoplayer.extractor;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.SampleHolder;
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
import com.google.android.exoplayer.upstream.Allocator;
|
import com.google.android.exoplayer.upstream.Allocator;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
@ -41,7 +41,7 @@ public class DefaultTrackOutput implements TrackOutput {
|
|||||||
|
|
||||||
// Accessed by both the loading and consuming threads.
|
// Accessed by both the loading and consuming threads.
|
||||||
private volatile long largestParsedTimestampUs;
|
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.
|
* @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.
|
* 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;
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,7 +246,7 @@ public class DefaultTrackOutput implements TrackOutput {
|
|||||||
// TrackOutput implementation. Called by the loading thread.
|
// TrackOutput implementation. Called by the loading thread.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void format(MediaFormat format) {
|
public void format(Format format) {
|
||||||
this.format = format;
|
this.format = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.extractor;
|
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 com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -23,9 +23,10 @@ import java.io.IOException;
|
|||||||
/**
|
/**
|
||||||
* A dummy {@link TrackOutput} implementation.
|
* A dummy {@link TrackOutput} implementation.
|
||||||
*/
|
*/
|
||||||
public class DummyTrackOutput implements TrackOutput {
|
public final class DummyTrackOutput implements TrackOutput {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void format(MediaFormat format) {
|
public void format(Format format) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,4 +45,5 @@ public class DummyTrackOutput implements TrackOutput {
|
|||||||
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
|
public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor;
|
package com.google.android.exoplayer.extractor;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.MediaFormatHolder;
|
import com.google.android.exoplayer.FormatHolder;
|
||||||
import com.google.android.exoplayer.ParserException;
|
import com.google.android.exoplayer.ParserException;
|
||||||
import com.google.android.exoplayer.SampleHolder;
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
import com.google.android.exoplayer.SampleSource;
|
import com.google.android.exoplayer.SampleSource;
|
||||||
@ -360,7 +360,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
return TrackStream.NO_RESET;
|
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()) {
|
if (pendingResets[track] || isPendingReset()) {
|
||||||
return TrackStream.NOTHING_READ;
|
return TrackStream.NOTHING_READ;
|
||||||
}
|
}
|
||||||
@ -665,7 +665,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int readData(MediaFormatHolder formatHolder, SampleHolder sampleHolder) {
|
public int readData(FormatHolder formatHolder, SampleHolder sampleHolder) {
|
||||||
return ExtractorSampleSource.this.readData(track, formatHolder, sampleHolder);
|
return ExtractorSampleSource.this.readData(track, formatHolder, sampleHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor;
|
package com.google.android.exoplayer.extractor;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.SampleHolder;
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
@ -29,11 +29,11 @@ import java.io.IOException;
|
|||||||
public interface TrackOutput {
|
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.
|
* Invoked to write sample data to the output.
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.flv;
|
package com.google.android.exoplayer.extractor.flv;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.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.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
@ -86,10 +86,10 @@ import java.util.Collections;
|
|||||||
data.readBytes(audioSpecifiConfig, 0, audioSpecifiConfig.length);
|
data.readBytes(audioSpecifiConfig, 0, audioSpecifiConfig.length);
|
||||||
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
|
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
|
||||||
audioSpecifiConfig);
|
audioSpecifiConfig);
|
||||||
MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC,
|
Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC,
|
||||||
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, audioParams.second, audioParams.first,
|
Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
|
||||||
Collections.singletonList(audioSpecifiConfig), null);
|
Collections.singletonList(audioSpecifiConfig), null);
|
||||||
output.format(mediaFormat);
|
output.format(format);
|
||||||
hasOutputFormat = true;
|
hasOutputFormat = true;
|
||||||
} else if (packetType == AAC_PACKET_TYPE_AAC_RAW) {
|
} else if (packetType == AAC_PACKET_TYPE_AAC_RAW) {
|
||||||
// Sample audio AAC frames
|
// Sample audio AAC frames
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.flv;
|
package com.google.android.exoplayer.extractor.flv;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.ParserException;
|
import com.google.android.exoplayer.ParserException;
|
||||||
import com.google.android.exoplayer.extractor.TrackOutput;
|
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
@ -95,10 +95,11 @@ import java.util.List;
|
|||||||
nalUnitLengthFieldLength = avcData.nalUnitLengthFieldLength;
|
nalUnitLengthFieldLength = avcData.nalUnitLengthFieldLength;
|
||||||
|
|
||||||
// Construct and output the format.
|
// Construct and output the format.
|
||||||
MediaFormat mediaFormat = MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_H264,
|
Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264,
|
||||||
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, avcData.width, avcData.height,
|
Format.NO_VALUE, Format.NO_VALUE, avcData.width, avcData.height,
|
||||||
avcData.initializationData, MediaFormat.NO_VALUE, avcData.pixelWidthAspectRatio);
|
Format.NO_VALUE, avcData.initializationData, Format.NO_VALUE,
|
||||||
output.format(mediaFormat);
|
avcData.pixelWidthAspectRatio);
|
||||||
|
output.format(format);
|
||||||
hasOutputFormat = true;
|
hasOutputFormat = true;
|
||||||
} else if (packetType == AVC_PACKET_TYPE_AVC_NALU) {
|
} else if (packetType == AVC_PACKET_TYPE_AVC_NALU) {
|
||||||
// TODO: Deduplicate with Mp4Extractor.
|
// 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.
|
* @return The AvcSequenceHeader data needed to initialize the video codec.
|
||||||
* @throws ParserException If the initialization data could not be built.
|
* @throws ParserException If the initialization data could not be built.
|
||||||
@ -157,8 +158,8 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
float pixelWidthAspectRatio = 1;
|
float pixelWidthAspectRatio = 1;
|
||||||
int width = MediaFormat.NO_VALUE;
|
int width = Format.NO_VALUE;
|
||||||
int height = MediaFormat.NO_VALUE;
|
int height = Format.NO_VALUE;
|
||||||
if (numSequenceParameterSets > 0) {
|
if (numSequenceParameterSets > 0) {
|
||||||
// Parse the first sequence parameter set to obtain pixelWidthAspectRatio.
|
// Parse the first sequence parameter set to obtain pixelWidthAspectRatio.
|
||||||
ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0));
|
ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0));
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.mp3;
|
package com.google.android.exoplayer.extractor.mp3;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
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.ParserException;
|
||||||
import com.google.android.exoplayer.extractor.Extractor;
|
import com.google.android.exoplayer.extractor.Extractor;
|
||||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||||
@ -119,8 +119,8 @@ public final class Mp3Extractor implements Extractor {
|
|||||||
if (seeker == null) {
|
if (seeker == null) {
|
||||||
setupSeeker(input);
|
setupSeeker(input);
|
||||||
extractorOutput.seekMap(seeker);
|
extractorOutput.seekMap(seeker);
|
||||||
trackOutput.format(MediaFormat.createAudioFormat(null, synchronizedHeader.mimeType,
|
trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType,
|
||||||
MediaFormat.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels,
|
Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels,
|
||||||
synchronizedHeader.sampleRate, null, null));
|
synchronizedHeader.sampleRate, null, null));
|
||||||
}
|
}
|
||||||
return readSample(input);
|
return readSample(input);
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.mp4;
|
package com.google.android.exoplayer.extractor.mp4;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.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.Ac3Util;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
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,
|
StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id,
|
||||||
tkhdData.rotationDegrees, mdhdData.second, isQuickTime);
|
tkhdData.rotationDegrees, mdhdData.second, isQuickTime);
|
||||||
Pair<long[], long[]> edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts));
|
Pair<long[], long[]> edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts));
|
||||||
return stsdData.mediaFormat == null ? null
|
return stsdData.format == null ? null
|
||||||
: new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs,
|
: 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);
|
edtsData.first, edtsData.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -467,17 +467,17 @@ import java.util.List;
|
|||||||
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId,
|
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId,
|
||||||
language, isQuickTime, out, i);
|
language, isQuickTime, out, i);
|
||||||
} else if (childAtomType == Atom.TYPE_TTML) {
|
} else if (childAtomType == Atom.TYPE_TTML) {
|
||||||
out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId),
|
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
||||||
MimeTypes.APPLICATION_TTML, MediaFormat.NO_VALUE, language);
|
MimeTypes.APPLICATION_TTML, Format.NO_VALUE, language);
|
||||||
} else if (childAtomType == Atom.TYPE_tx3g) {
|
} else if (childAtomType == Atom.TYPE_tx3g) {
|
||||||
out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId),
|
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
||||||
MimeTypes.APPLICATION_TX3G, MediaFormat.NO_VALUE, language);
|
MimeTypes.APPLICATION_TX3G, Format.NO_VALUE, language);
|
||||||
} else if (childAtomType == Atom.TYPE_wvtt) {
|
} else if (childAtomType == Atom.TYPE_wvtt) {
|
||||||
out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId),
|
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
||||||
MimeTypes.APPLICATION_MP4VTT, MediaFormat.NO_VALUE, language);
|
MimeTypes.APPLICATION_MP4VTT, Format.NO_VALUE, language);
|
||||||
} else if (childAtomType == Atom.TYPE_stpp) {
|
} else if (childAtomType == Atom.TYPE_stpp) {
|
||||||
out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId),
|
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
||||||
MimeTypes.APPLICATION_TTML, MediaFormat.NO_VALUE, language,
|
MimeTypes.APPLICATION_TTML, Format.NO_VALUE, language,
|
||||||
0 /* subsample timing is absolute */);
|
0 /* subsample timing is absolute */);
|
||||||
}
|
}
|
||||||
stsd.setPosition(childStartPosition + childAtomSize);
|
stsd.setPosition(childStartPosition + childAtomSize);
|
||||||
@ -548,8 +548,8 @@ import java.util.List;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
out.mediaFormat = MediaFormat.createVideoFormat(Integer.toString(trackId), mimeType,
|
out.format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType,
|
||||||
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, width, height, initializationData,
|
Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE, initializationData,
|
||||||
rotationDegrees, pixelWidthHeightRatio);
|
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: Choose the right AC-3 track based on the contents of dac3/dec3.
|
||||||
// TODO: Add support for encryption (by setting out.trackEncryptionBoxes).
|
// TODO: Add support for encryption (by setting out.trackEncryptionBoxes).
|
||||||
parent.setPosition(Atom.HEADER_SIZE + childAtomPosition);
|
parent.setPosition(Atom.HEADER_SIZE + childAtomPosition);
|
||||||
out.mediaFormat = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId),
|
out.format = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId), language);
|
||||||
language);
|
|
||||||
return;
|
return;
|
||||||
} else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) {
|
} else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) {
|
||||||
parent.setPosition(Atom.HEADER_SIZE + childAtomPosition);
|
parent.setPosition(Atom.HEADER_SIZE + childAtomPosition);
|
||||||
out.mediaFormat = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId),
|
out.format = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), language);
|
||||||
language);
|
|
||||||
return;
|
return;
|
||||||
} else if ((atomType == Atom.TYPE_dtsc || atomType == Atom.TYPE_dtse
|
} else if ((atomType == Atom.TYPE_dtsc || atomType == Atom.TYPE_dtse
|
||||||
|| atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl)
|
|| atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl)
|
||||||
&& childAtomType == Atom.TYPE_ddts) {
|
&& childAtomType == Atom.TYPE_ddts) {
|
||||||
out.mediaFormat = MediaFormat.createAudioFormat(Integer.toString(trackId), mimeType,
|
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType,
|
||||||
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, channelCount, sampleRate, null, language);
|
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, language);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
childAtomPosition += childAtomSize;
|
childAtomPosition += childAtomSize;
|
||||||
@ -809,8 +807,8 @@ import java.util.List;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
out.mediaFormat = MediaFormat.createAudioFormat(Integer.toString(trackId), mimeType,
|
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType,
|
||||||
MediaFormat.NO_VALUE, sampleSize, channelCount, sampleRate,
|
Format.NO_VALUE, sampleSize, channelCount, sampleRate,
|
||||||
initializationData == null ? null : Collections.singletonList(initializationData),
|
initializationData == null ? null : Collections.singletonList(initializationData),
|
||||||
language);
|
language);
|
||||||
}
|
}
|
||||||
@ -945,7 +943,7 @@ import java.util.List;
|
|||||||
|
|
||||||
public final TrackEncryptionBox[] trackEncryptionBoxes;
|
public final TrackEncryptionBox[] trackEncryptionBoxes;
|
||||||
|
|
||||||
public MediaFormat mediaFormat;
|
public Format format;
|
||||||
public int nalUnitLengthFieldLength;
|
public int nalUnitLengthFieldLength;
|
||||||
|
|
||||||
public StsdData(int numberOfEntries) {
|
public StsdData(int numberOfEntries) {
|
||||||
|
@ -314,7 +314,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||||||
if (track == null) {
|
if (track == null) {
|
||||||
throw new ParserException("Track type not supported.");
|
throw new ParserException("Track type not supported.");
|
||||||
}
|
}
|
||||||
trackOutput.format(track.mediaFormat);
|
trackOutput.format(track.format);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {
|
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {
|
||||||
|
@ -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.
|
// 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.
|
// Allow ten source samples per output sample, like the platform extractor.
|
||||||
int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
|
int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
|
||||||
mp4Track.trackOutput.format(track.mediaFormat.copyWithMaxInputSize(maxInputSize));
|
mp4Track.trackOutput.format(track.format.copyWithMaxInputSize(maxInputSize));
|
||||||
|
|
||||||
durationUs = Math.max(durationUs, track.durationUs);
|
durationUs = Math.max(durationUs, track.durationUs);
|
||||||
tracks.add(mp4Track);
|
tracks.add(mp4Track);
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.mp4;
|
package com.google.android.exoplayer.extractor.mp4;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,9 +57,9 @@ public final class Track {
|
|||||||
public final long durationUs;
|
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.
|
* 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 final int nalUnitLengthFieldLength;
|
||||||
|
|
||||||
public Track(int id, int type, long timescale, long movieTimescale, long durationUs,
|
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) {
|
int nalUnitLengthFieldLength, long[] editListDurations, long[] editListMediaTimes) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.timescale = timescale;
|
this.timescale = timescale;
|
||||||
this.movieTimescale = movieTimescale;
|
this.movieTimescale = movieTimescale;
|
||||||
this.durationUs = durationUs;
|
this.durationUs = durationUs;
|
||||||
this.mediaFormat = mediaFormat;
|
this.format = format;
|
||||||
this.sampleDescriptionEncryptionBoxes = sampleDescriptionEncryptionBoxes;
|
this.sampleDescriptionEncryptionBoxes = sampleDescriptionEncryptionBoxes;
|
||||||
this.nalUnitLengthFieldLength = nalUnitLengthFieldLength;
|
this.nalUnitLengthFieldLength = nalUnitLengthFieldLength;
|
||||||
this.editListDurations = editListDurations;
|
this.editListDurations = editListDurations;
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.ogg;
|
package com.google.android.exoplayer.extractor.ogg;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
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.ParserException;
|
||||||
import com.google.android.exoplayer.extractor.Extractor;
|
import com.google.android.exoplayer.extractor.Extractor;
|
||||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||||
@ -99,7 +99,7 @@ public final class OggVorbisExtractor implements Extractor {
|
|||||||
codecInitializationData.clear();
|
codecInitializationData.clear();
|
||||||
codecInitializationData.add(idHeader.data);
|
codecInitializationData.add(idHeader.data);
|
||||||
codecInitializationData.add(vorbisSetup.setupHeaderData);
|
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.bitrateNominal, OGG_MAX_SEGMENT_SIZE * 255, idHeader.channels,
|
||||||
idHeader.sampleRate, codecInitializationData, null));
|
idHeader.sampleRate, codecInitializationData, null));
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.ts;
|
package com.google.android.exoplayer.extractor.ts;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.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.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.Ac3Util;
|
import com.google.android.exoplayer.util.Ac3Util;
|
||||||
import com.google.android.exoplayer.util.ParsableBitArray;
|
import com.google.android.exoplayer.util.ParsableBitArray;
|
||||||
@ -45,7 +45,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
|
|
||||||
// Used when parsing the header.
|
// Used when parsing the header.
|
||||||
private long sampleDurationUs;
|
private long sampleDurationUs;
|
||||||
private MediaFormat mediaFormat;
|
private Format format;
|
||||||
private int sampleSize;
|
private int sampleSize;
|
||||||
|
|
||||||
// Used when reading the samples.
|
// Used when reading the samples.
|
||||||
@ -159,11 +159,11 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
* Parses the sample header.
|
* Parses the sample header.
|
||||||
*/
|
*/
|
||||||
private void parseHeader() {
|
private void parseHeader() {
|
||||||
if (mediaFormat == null) {
|
if (format == null) {
|
||||||
mediaFormat = isEac3
|
format = isEac3
|
||||||
? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, null)
|
? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, null)
|
||||||
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, null);
|
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, null);
|
||||||
output.format(mediaFormat);
|
output.format(format);
|
||||||
}
|
}
|
||||||
sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data)
|
sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data)
|
||||||
: Ac3Util.parseAc3SyncframeSize(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
|
// 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.
|
// specifies the number of PCM audio samples per second.
|
||||||
sampleDurationUs =
|
sampleDurationUs =
|
||||||
(int) (C.MICROS_PER_SECOND * audioSamplesPerSyncframe / mediaFormat.sampleRate);
|
(int) (C.MICROS_PER_SECOND * audioSamplesPerSyncframe / format.sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.ts;
|
package com.google.android.exoplayer.extractor.ts;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.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.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
@ -81,7 +81,7 @@ import java.util.Collections;
|
|||||||
public AdtsReader(TrackOutput output, TrackOutput id3Output) {
|
public AdtsReader(TrackOutput output, TrackOutput id3Output) {
|
||||||
super(output);
|
super(output);
|
||||||
this.id3Output = id3Output;
|
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]);
|
adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]);
|
||||||
id3HeaderBuffer = new ParsableByteArray(Arrays.copyOf(ID3_IDENTIFIER, ID3_HEADER_SIZE));
|
id3HeaderBuffer = new ParsableByteArray(Arrays.copyOf(ID3_IDENTIFIER, ID3_HEADER_SIZE));
|
||||||
setFindingSampleState();
|
setFindingSampleState();
|
||||||
@ -258,13 +258,13 @@ import java.util.Collections;
|
|||||||
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
|
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
|
||||||
audioSpecificConfig);
|
audioSpecificConfig);
|
||||||
|
|
||||||
MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC,
|
Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, Format.NO_VALUE,
|
||||||
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, audioParams.second, audioParams.first,
|
Format.NO_VALUE, audioParams.second, audioParams.first,
|
||||||
Collections.singletonList(audioSpecificConfig), null);
|
Collections.singletonList(audioSpecificConfig), null);
|
||||||
// In this class a sample is an access unit, but the MediaFormat sample rate specifies the
|
// In this class a sample is an access unit, but the MediaFormat sample rate specifies the
|
||||||
// number of PCM audio samples per second.
|
// number of PCM audio samples per second.
|
||||||
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / mediaFormat.sampleRate;
|
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate;
|
||||||
output.format(mediaFormat);
|
output.format(format);
|
||||||
hasOutputFormat = true;
|
hasOutputFormat = true;
|
||||||
} else {
|
} else {
|
||||||
adtsScratch.skipBits(10);
|
adtsScratch.skipBits(10);
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.ts;
|
package com.google.android.exoplayer.extractor.ts;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.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.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.DtsUtil;
|
import com.google.android.exoplayer.util.DtsUtil;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
@ -44,7 +44,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
|
|
||||||
// Used when parsing the header.
|
// Used when parsing the header.
|
||||||
private long sampleDurationUs;
|
private long sampleDurationUs;
|
||||||
private MediaFormat mediaFormat;
|
private Format format;
|
||||||
private int sampleSize;
|
private int sampleSize;
|
||||||
|
|
||||||
// Used when reading the samples.
|
// Used when reading the samples.
|
||||||
@ -152,15 +152,15 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
*/
|
*/
|
||||||
private void parseHeader() {
|
private void parseHeader() {
|
||||||
byte[] frameData = headerScratchBytes.data;
|
byte[] frameData = headerScratchBytes.data;
|
||||||
if (mediaFormat == null) {
|
if (format == null) {
|
||||||
mediaFormat = DtsUtil.parseDtsFormat(frameData, null, null);
|
format = DtsUtil.parseDtsFormat(frameData, null, null);
|
||||||
output.format(mediaFormat);
|
output.format(format);
|
||||||
}
|
}
|
||||||
sampleSize = DtsUtil.getDtsFrameSize(frameData);
|
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.
|
// specifies the number of PCM audio samples per second.
|
||||||
sampleDurationUs = (int) (C.MICROS_PER_SECOND
|
sampleDurationUs = (int) (C.MICROS_PER_SECOND
|
||||||
* DtsUtil.parseDtsAudioSampleCount(frameData) / mediaFormat.sampleRate);
|
* DtsUtil.parseDtsAudioSampleCount(frameData) / format.sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.ts;
|
package com.google.android.exoplayer.extractor.ts;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.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.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.NalUnitUtil;
|
import com.google.android.exoplayer.util.NalUnitUtil;
|
||||||
@ -113,7 +113,7 @@ import java.util.Collections;
|
|||||||
int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0;
|
int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0;
|
||||||
if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) {
|
if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) {
|
||||||
// The csd data is complete, so we can parse and output the media format.
|
// The csd data is complete, so we can parse and output the media format.
|
||||||
Pair<MediaFormat, Long> result = parseCsdBuffer(csdBuffer);
|
Pair<Format, Long> result = parseCsdBuffer(csdBuffer);
|
||||||
output.format(result.first);
|
output.format(result.first);
|
||||||
frameDurationUs = result.second;
|
frameDurationUs = result.second;
|
||||||
hasOutputFormat = true;
|
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.
|
* @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.
|
* 0 if the duration could not be determined.
|
||||||
*/
|
*/
|
||||||
private static Pair<MediaFormat, Long> parseCsdBuffer(CsdBuffer csdBuffer) {
|
private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer) {
|
||||||
byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
|
byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
|
||||||
|
|
||||||
int firstByte = csdData[4] & 0xFF;
|
int firstByte = csdData[4] & 0xFF;
|
||||||
@ -183,9 +183,9 @@ import java.util.Collections;
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaFormat format = MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_MPEG2,
|
Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_MPEG2, Format.NO_VALUE,
|
||||||
MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, width, height,
|
Format.NO_VALUE, width, height, Format.NO_VALUE, Collections.singletonList(csdData),
|
||||||
Collections.singletonList(csdData), MediaFormat.NO_VALUE, pixelWidthHeightRatio);
|
Format.NO_VALUE, pixelWidthHeightRatio);
|
||||||
|
|
||||||
long frameDurationUs = 0;
|
long frameDurationUs = 0;
|
||||||
int frameRateCodeMinusOne = (csdData[7] & 0x0F) - 1;
|
int frameRateCodeMinusOne = (csdData[7] & 0x0F) - 1;
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.ts;
|
package com.google.android.exoplayer.extractor.ts;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.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.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
||||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil.SpsData;
|
import com.google.android.exoplayer.util.CodecSpecificDataUtil.SpsData;
|
||||||
@ -198,7 +198,7 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MediaFormat parseMediaFormat(NalUnitTargetBuffer sps, NalUnitTargetBuffer pps) {
|
private static Format parseMediaFormat(NalUnitTargetBuffer sps, NalUnitTargetBuffer pps) {
|
||||||
List<byte[]> initializationData = new ArrayList<>();
|
List<byte[]> initializationData = new ArrayList<>();
|
||||||
initializationData.add(Arrays.copyOf(sps.nalData, sps.nalLength));
|
initializationData.add(Arrays.copyOf(sps.nalData, sps.nalLength));
|
||||||
initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));
|
initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));
|
||||||
@ -209,9 +209,9 @@ import java.util.List;
|
|||||||
bitArray.skipBits(32); // NAL header
|
bitArray.skipBits(32); // NAL header
|
||||||
SpsData parsedSpsData = CodecSpecificDataUtil.parseSpsNalUnit(bitArray);
|
SpsData parsedSpsData = CodecSpecificDataUtil.parseSpsNalUnit(bitArray);
|
||||||
|
|
||||||
return MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
|
return Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, Format.NO_VALUE,
|
||||||
MediaFormat.NO_VALUE, parsedSpsData.width, parsedSpsData.height, initializationData,
|
Format.NO_VALUE, parsedSpsData.width, parsedSpsData.height, Format.NO_VALUE,
|
||||||
MediaFormat.NO_VALUE, parsedSpsData.pixelWidthAspectRatio);
|
initializationData, Format.NO_VALUE, parsedSpsData.pixelWidthAspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.ts;
|
package com.google.android.exoplayer.extractor.ts;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.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.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.NalUnitUtil;
|
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) {
|
NalUnitTargetBuffer pps) {
|
||||||
// Build codec-specific data.
|
// Build codec-specific data.
|
||||||
byte[] csd = new byte[vps.nalLength + sps.nalLength + pps.nalLength];
|
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,
|
return Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H265, Format.NO_VALUE,
|
||||||
MediaFormat.NO_VALUE, picWidthInLumaSamples, picHeightInLumaSamples,
|
Format.NO_VALUE, picWidthInLumaSamples, picHeightInLumaSamples, Format.NO_VALUE,
|
||||||
Collections.singletonList(csd), MediaFormat.NO_VALUE, pixelWidthHeightRatio);
|
Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Skips scaling_list_data(). See H.265/HEVC (2014) 7.3.4. */
|
/** Skips scaling_list_data(). See H.265/HEVC (2014) 7.3.4. */
|
||||||
|
@ -16,8 +16,9 @@
|
|||||||
package com.google.android.exoplayer.extractor.ts;
|
package com.google.android.exoplayer.extractor.ts;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.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.extractor.TrackOutput;
|
||||||
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,7 +35,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
|
|
||||||
public Id3Reader(TrackOutput output) {
|
public Id3Reader(TrackOutput output) {
|
||||||
super(output);
|
super(output);
|
||||||
output.format(MediaFormat.createId3Format());
|
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, Format.NO_VALUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.ts;
|
package com.google.android.exoplayer.extractor.ts;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.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.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.MpegAudioHeader;
|
import com.google.android.exoplayer.util.MpegAudioHeader;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
@ -160,10 +160,9 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
frameSize = header.frameSize;
|
frameSize = header.frameSize;
|
||||||
if (!hasOutputFormat) {
|
if (!hasOutputFormat) {
|
||||||
frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate;
|
frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate;
|
||||||
MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, header.mimeType,
|
Format format = Format.createAudioSampleFormat(null, header.mimeType, Format.NO_VALUE,
|
||||||
MediaFormat.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels,
|
MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate, null, null);
|
||||||
header.sampleRate, null, null);
|
output.format(format);
|
||||||
output.format(mediaFormat);
|
|
||||||
hasOutputFormat = true;
|
hasOutputFormat = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.ts;
|
package com.google.android.exoplayer.extractor.ts;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.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.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.text.eia608.Eia608Parser;
|
import com.google.android.exoplayer.text.eia608.Eia608Parser;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
@ -32,8 +32,8 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
|
|
||||||
public SeiReader(TrackOutput output) {
|
public SeiReader(TrackOutput output) {
|
||||||
super(output);
|
super(output);
|
||||||
output.format(MediaFormat.createTextFormat(null, MimeTypes.APPLICATION_EIA608,
|
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_EIA608, Format.NO_VALUE,
|
||||||
MediaFormat.NO_VALUE, null));
|
null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.webm;
|
package com.google.android.exoplayer.extractor.webm;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
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.ParserException;
|
||||||
import com.google.android.exoplayer.drm.DrmInitData;
|
import com.google.android.exoplayer.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData;
|
import com.google.android.exoplayer.drm.DrmInitData.SchemeInitData;
|
||||||
@ -1131,8 +1131,8 @@ public final class WebmExtractor implements Extractor {
|
|||||||
public byte[] codecPrivate;
|
public byte[] codecPrivate;
|
||||||
|
|
||||||
// Video elements.
|
// Video elements.
|
||||||
public int width = MediaFormat.NO_VALUE;
|
public int width = Format.NO_VALUE;
|
||||||
public int height = MediaFormat.NO_VALUE;
|
public int height = Format.NO_VALUE;
|
||||||
|
|
||||||
// Audio elements. Initially set to their default values.
|
// Audio elements. Initially set to their default values.
|
||||||
public int channelCount = 1;
|
public int channelCount = 1;
|
||||||
@ -1152,7 +1152,7 @@ public final class WebmExtractor implements Extractor {
|
|||||||
*/
|
*/
|
||||||
public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException {
|
public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException {
|
||||||
String mimeType;
|
String mimeType;
|
||||||
int maxInputSize = MediaFormat.NO_VALUE;
|
int maxInputSize = Format.NO_VALUE;
|
||||||
List<byte[]> initializationData = null;
|
List<byte[]> initializationData = null;
|
||||||
switch (codecId) {
|
switch (codecId) {
|
||||||
case CODEC_ID_VP8:
|
case CODEC_ID_VP8:
|
||||||
@ -1231,19 +1231,18 @@ public final class WebmExtractor implements Extractor {
|
|||||||
throw new ParserException("Unrecognized codec identifier.");
|
throw new ParserException("Unrecognized codec identifier.");
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaFormat format;
|
Format format;
|
||||||
// TODO: Consider reading the name elements of the tracks and, if present, incorporating them
|
// TODO: Consider reading the name elements of the tracks and, if present, incorporating them
|
||||||
// into the trackId passed when creating the formats.
|
// into the trackId passed when creating the formats.
|
||||||
if (MimeTypes.isAudio(mimeType)) {
|
if (MimeTypes.isAudio(mimeType)) {
|
||||||
format = MediaFormat.createAudioFormat(Integer.toString(trackId), mimeType,
|
format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType,
|
||||||
MediaFormat.NO_VALUE, maxInputSize, channelCount, sampleRate,
|
Format.NO_VALUE, maxInputSize, channelCount, sampleRate, initializationData, language);
|
||||||
initializationData, language);
|
|
||||||
} else if (MimeTypes.isVideo(mimeType)) {
|
} else if (MimeTypes.isVideo(mimeType)) {
|
||||||
format = MediaFormat.createVideoFormat(Integer.toString(trackId), mimeType,
|
format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType,
|
||||||
MediaFormat.NO_VALUE, maxInputSize, width, height, initializationData);
|
Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData);
|
||||||
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
|
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
|
||||||
format = MediaFormat.createTextFormat(Integer.toString(trackId), mimeType,
|
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, Format.NO_VALUE,
|
||||||
MediaFormat.NO_VALUE, language);
|
language);
|
||||||
} else {
|
} else {
|
||||||
throw new ParserException("Unexpected MIME type.");
|
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.
|
* @throws ParserException If the initialization data could not be built.
|
||||||
*/
|
*/
|
||||||
private static Pair<List<byte[]>, Integer> parseAvcCodecPrivate(ParsableByteArray buffer)
|
private static Pair<List<byte[]>, Integer> parseAvcCodecPrivate(ParsableByteArray buffer)
|
||||||
@ -1283,9 +1282,9 @@ public final class WebmExtractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds initialization data for a {@link MediaFormat} from H.265 (HEVC) codec private data.
|
* Builds initialization data for a {@link Format} from H.265 (HEVC) codec private data.
|
||||||
*
|
*
|
||||||
* @return The initialization data for the {@link MediaFormat}.
|
* @return The initialization data for the {@link Format}.
|
||||||
* @throws ParserException If the initialization data could not be built.
|
* @throws ParserException If the initialization data could not be built.
|
||||||
*/
|
*/
|
||||||
private static Pair<List<byte[]>, Integer> parseHevcCodecPrivate(ParsableByteArray parent)
|
private static Pair<List<byte[]>, Integer> parseHevcCodecPrivate(ParsableByteArray parent)
|
||||||
@ -1336,9 +1335,9 @@ public final class WebmExtractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds initialization data for a {@link MediaFormat} from Vorbis codec private data.
|
* Builds initialization data for a {@link Format} from Vorbis codec private data.
|
||||||
*
|
*
|
||||||
* @return The initialization data for the {@link MediaFormat}.
|
* @return The initialization data for the {@link Format}.
|
||||||
* @throws ParserException If the initialization data could not be built.
|
* @throws ParserException If the initialization data could not be built.
|
||||||
*/
|
*/
|
||||||
private static List<byte[]> parseVorbisCodecPrivate(byte[] codecPrivate)
|
private static List<byte[]> parseVorbisCodecPrivate(byte[] codecPrivate)
|
||||||
|
@ -17,12 +17,11 @@ package com.google.android.exoplayer.hls;
|
|||||||
|
|
||||||
import com.google.android.exoplayer.BehindLiveWindowException;
|
import com.google.android.exoplayer.BehindLiveWindowException;
|
||||||
import com.google.android.exoplayer.C;
|
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.BaseChunkSampleSourceEventListener;
|
||||||
import com.google.android.exoplayer.chunk.Chunk;
|
import com.google.android.exoplayer.chunk.Chunk;
|
||||||
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
|
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
|
||||||
import com.google.android.exoplayer.chunk.DataChunk;
|
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.Extractor;
|
||||||
import com.google.android.exoplayer.extractor.mp3.Mp3Extractor;
|
import com.google.android.exoplayer.extractor.mp3.Mp3Extractor;
|
||||||
import com.google.android.exoplayer.extractor.ts.AdtsExtractor;
|
import com.google.android.exoplayer.extractor.ts.AdtsExtractor;
|
||||||
@ -156,8 +155,6 @@ public class HlsChunkSource {
|
|||||||
private HlsMediaPlaylist[] enabledVariantPlaylists;
|
private HlsMediaPlaylist[] enabledVariantPlaylists;
|
||||||
private long[] enabledVariantLastPlaylistLoadTimesMs;
|
private long[] enabledVariantLastPlaylistLoadTimesMs;
|
||||||
private long[] enabledVariantBlacklistTimes;
|
private long[] enabledVariantBlacklistTimes;
|
||||||
private int adaptiveMaxWidth;
|
|
||||||
private int adaptiveMaxHeight;
|
|
||||||
private int selectedVariantIndex;
|
private int selectedVariantIndex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -246,10 +243,10 @@ public class HlsChunkSource {
|
|||||||
if (playlist.type == HlsPlaylist.TYPE_MASTER) {
|
if (playlist.type == HlsPlaylist.TYPE_MASTER) {
|
||||||
masterPlaylist = (HlsMasterPlaylist) playlist;
|
masterPlaylist = (HlsMasterPlaylist) playlist;
|
||||||
} else {
|
} else {
|
||||||
Format format = new Format("0", MimeTypes.APPLICATION_M3U8, -1, -1, -1, -1, -1, -1, null,
|
Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null,
|
||||||
null);
|
Format.NO_VALUE);
|
||||||
List<Variant> variants = new ArrayList<>();
|
List<Variant> variants = new ArrayList<>();
|
||||||
variants.add(new Variant(baseUri, format));
|
variants.add(new Variant(baseUri, format, null));
|
||||||
masterPlaylist = new HlsMasterPlaylist(baseUri, variants,
|
masterPlaylist = new HlsMasterPlaylist(baseUri, variants,
|
||||||
Collections.<Variant>emptyList());
|
Collections.<Variant>emptyList());
|
||||||
}
|
}
|
||||||
@ -332,8 +329,6 @@ public class HlsChunkSource {
|
|||||||
});
|
});
|
||||||
// Determine the initial variant index and maximum video dimensions.
|
// Determine the initial variant index and maximum video dimensions.
|
||||||
selectedVariantIndex = 0;
|
selectedVariantIndex = 0;
|
||||||
int maxWidth = -1;
|
|
||||||
int maxHeight = -1;
|
|
||||||
int minOriginalVariantIndex = Integer.MAX_VALUE;
|
int minOriginalVariantIndex = Integer.MAX_VALUE;
|
||||||
for (int i = 0; i < enabledVariants.length; i++) {
|
for (int i = 0; i < enabledVariants.length; i++) {
|
||||||
int originalVariantIndex = masterPlaylist.variants.indexOf(enabledVariants[i]);
|
int originalVariantIndex = masterPlaylist.variants.indexOf(enabledVariants[i]);
|
||||||
@ -341,18 +336,6 @@ public class HlsChunkSource {
|
|||||||
minOriginalVariantIndex = originalVariantIndex;
|
minOriginalVariantIndex = originalVariantIndex;
|
||||||
selectedVariantIndex = i;
|
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 {
|
} else {
|
||||||
nextVariantIndex = getNextVariantIndex(previousTsChunk, playbackPositionUs);
|
nextVariantIndex = getNextVariantIndex(previousTsChunk, playbackPositionUs);
|
||||||
switchingVariantSpliced = previousTsChunk != null
|
switchingVariantSpliced = previousTsChunk != null
|
||||||
&& !enabledVariants[nextVariantIndex].format.equals(previousTsChunk.format)
|
&& enabledVariants[nextVariantIndex].format != previousTsChunk.format
|
||||||
&& adaptiveMode == ADAPTIVE_MODE_SPLICE;
|
&& adaptiveMode == ADAPTIVE_MODE_SPLICE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,11 +473,11 @@ public class HlsChunkSource {
|
|||||||
// case below.
|
// case below.
|
||||||
Extractor extractor = new AdtsExtractor(startTimeUs);
|
Extractor extractor = new AdtsExtractor(startTimeUs);
|
||||||
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
||||||
switchingVariantSpliced, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE);
|
switchingVariantSpliced);
|
||||||
} else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) {
|
} else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) {
|
||||||
Extractor extractor = new Mp3Extractor(startTimeUs);
|
Extractor extractor = new Mp3Extractor(startTimeUs);
|
||||||
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
||||||
switchingVariantSpliced, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE);
|
switchingVariantSpliced);
|
||||||
} else if (lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION)
|
} else if (lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION)
|
||||||
|| lastPathSegment.endsWith(VTT_FILE_EXTENSION)) {
|
|| lastPathSegment.endsWith(VTT_FILE_EXTENSION)) {
|
||||||
PtsTimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(false,
|
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.
|
// a discontinuity sequence greater than the one that this source is trying to start at.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Extractor extractor = new WebvttExtractor(timestampAdjuster);
|
Extractor extractor = new WebvttExtractor(format.language, timestampAdjuster);
|
||||||
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
||||||
switchingVariantSpliced, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE);
|
switchingVariantSpliced);
|
||||||
} else if (previousTsChunk == null
|
} else if (previousTsChunk == null
|
||||||
|| previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|
|| previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|
||||||
|| !format.equals(previousTsChunk.format)) {
|
|| format != previousTsChunk.format) {
|
||||||
// MPEG-2 TS segments, but we need a new extractor.
|
// MPEG-2 TS segments, but we need a new extractor.
|
||||||
PtsTimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(true,
|
PtsTimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(true,
|
||||||
segment.discontinuitySequenceNumber, startTimeUs);
|
segment.discontinuitySequenceNumber, startTimeUs);
|
||||||
@ -520,7 +503,7 @@ public class HlsChunkSource {
|
|||||||
}
|
}
|
||||||
Extractor extractor = new TsExtractor(timestampAdjuster);
|
Extractor extractor = new TsExtractor(timestampAdjuster);
|
||||||
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
||||||
switchingVariantSpliced, adaptiveMaxWidth, adaptiveMaxHeight);
|
switchingVariantSpliced);
|
||||||
} else {
|
} else {
|
||||||
// MPEG-2 TS segments, and we need to continue using the same extractor.
|
// MPEG-2 TS segments, and we need to continue using the same extractor.
|
||||||
extractorWrapper = previousTsChunk.extractorWrapper;
|
extractorWrapper = previousTsChunk.extractorWrapper;
|
||||||
@ -566,19 +549,19 @@ public class HlsChunkSource {
|
|||||||
InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e;
|
InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e;
|
||||||
int responseCode = responseCodeException.responseCode;
|
int responseCode = responseCodeException.responseCode;
|
||||||
if (responseCode == 404 || responseCode == 410) {
|
if (responseCode == 404 || responseCode == 410) {
|
||||||
int variantIndex;
|
int enabledVariantIndex;
|
||||||
if (chunk instanceof TsChunk) {
|
if (chunk instanceof TsChunk) {
|
||||||
TsChunk tsChunk = (TsChunk) chunk;
|
TsChunk tsChunk = (TsChunk) chunk;
|
||||||
variantIndex = getVariantIndex(tsChunk.format);
|
enabledVariantIndex = getEnabledVariantIndex(tsChunk.format);
|
||||||
} else if (chunk instanceof MediaPlaylistChunk) {
|
} else if (chunk instanceof MediaPlaylistChunk) {
|
||||||
MediaPlaylistChunk playlistChunk = (MediaPlaylistChunk) chunk;
|
MediaPlaylistChunk playlistChunk = (MediaPlaylistChunk) chunk;
|
||||||
variantIndex = playlistChunk.variantIndex;
|
enabledVariantIndex = playlistChunk.variantIndex;
|
||||||
} else {
|
} else {
|
||||||
EncryptionKeyChunk encryptionChunk = (EncryptionKeyChunk) chunk;
|
EncryptionKeyChunk encryptionChunk = (EncryptionKeyChunk) chunk;
|
||||||
variantIndex = encryptionChunk.variantIndex;
|
enabledVariantIndex = encryptionChunk.variantIndex;
|
||||||
}
|
}
|
||||||
boolean alreadyBlacklisted = enabledVariantBlacklistTimes[variantIndex] != 0;
|
boolean alreadyBlacklisted = enabledVariantBlacklistTimes[enabledVariantIndex] != 0;
|
||||||
enabledVariantBlacklistTimes[variantIndex] = SystemClock.elapsedRealtime();
|
enabledVariantBlacklistTimes[enabledVariantIndex] = SystemClock.elapsedRealtime();
|
||||||
if (alreadyBlacklisted) {
|
if (alreadyBlacklisted) {
|
||||||
// The playlist was already blacklisted.
|
// The playlist was already blacklisted.
|
||||||
Log.w(TAG, "Already blacklisted variant (" + responseCode + "): "
|
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.
|
// This was the last non-blacklisted playlist. Don't blacklist it.
|
||||||
Log.w(TAG, "Final variant not blacklisted (" + responseCode + "): "
|
Log.w(TAG, "Final variant not blacklisted (" + responseCode + "): "
|
||||||
+ chunk.dataSpec.uri);
|
+ chunk.dataSpec.uri);
|
||||||
enabledVariantBlacklistTimes[variantIndex] = 0;
|
enabledVariantBlacklistTimes[enabledVariantIndex] = 0;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -646,7 +629,7 @@ public class HlsChunkSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean variantHasExplicitCodecWithPrefix(Variant variant, String prefix) {
|
private static boolean variantHasExplicitCodecWithPrefix(Variant variant, String prefix) {
|
||||||
String codecs = variant.format.codecs;
|
String codecs = variant.codecs;
|
||||||
if (TextUtils.isEmpty(codecs)) {
|
if (TextUtils.isEmpty(codecs)) {
|
||||||
return false;
|
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++) {
|
for (int i = 0; i < enabledVariants.length; i++) {
|
||||||
if (enabledVariants[i].format.equals(format)) {
|
if (enabledVariants[i].format == format) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.hls;
|
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.SampleHolder;
|
||||||
import com.google.android.exoplayer.chunk.Format;
|
|
||||||
import com.google.android.exoplayer.drm.DrmInitData;
|
import com.google.android.exoplayer.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
|
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
|
||||||
import com.google.android.exoplayer.extractor.Extractor;
|
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.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.upstream.Allocator;
|
import com.google.android.exoplayer.upstream.Allocator;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
|
||||||
|
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
|
||||||
@ -45,10 +43,8 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
|
|||||||
private final Extractor extractor;
|
private final Extractor extractor;
|
||||||
private final SparseArray<DefaultTrackOutput> sampleQueues;
|
private final SparseArray<DefaultTrackOutput> sampleQueues;
|
||||||
private final boolean shouldSpliceIn;
|
private final boolean shouldSpliceIn;
|
||||||
private final int adaptiveMaxWidth;
|
|
||||||
private final int adaptiveMaxHeight;
|
|
||||||
|
|
||||||
private MediaFormat[] sampleQueueFormats;
|
private Format[] sampleFormats;
|
||||||
private Allocator allocator;
|
private Allocator allocator;
|
||||||
|
|
||||||
private volatile boolean tracksBuilt;
|
private volatile boolean tracksBuilt;
|
||||||
@ -58,14 +54,12 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
|
|||||||
private boolean spliceConfigured;
|
private boolean spliceConfigured;
|
||||||
|
|
||||||
public HlsExtractorWrapper(int trigger, Format format, long startTimeUs, Extractor extractor,
|
public HlsExtractorWrapper(int trigger, Format format, long startTimeUs, Extractor extractor,
|
||||||
boolean shouldSpliceIn, int adaptiveMaxWidth, int adaptiveMaxHeight) {
|
boolean shouldSpliceIn) {
|
||||||
this.trigger = trigger;
|
this.trigger = trigger;
|
||||||
this.format = format;
|
this.format = format;
|
||||||
this.startTimeUs = startTimeUs;
|
this.startTimeUs = startTimeUs;
|
||||||
this.extractor = extractor;
|
this.extractor = extractor;
|
||||||
this.shouldSpliceIn = shouldSpliceIn;
|
this.shouldSpliceIn = shouldSpliceIn;
|
||||||
this.adaptiveMaxWidth = adaptiveMaxWidth;
|
|
||||||
this.adaptiveMaxHeight = adaptiveMaxHeight;
|
|
||||||
sampleQueues = new SparseArray<>();
|
sampleQueues = new SparseArray<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,14 +86,9 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
prepared = true;
|
prepared = true;
|
||||||
sampleQueueFormats = new MediaFormat[sampleQueues.size()];
|
sampleFormats = new Format[sampleQueues.size()];
|
||||||
for (int i = 0; i < sampleQueueFormats.length; i++) {
|
for (int i = 0; i < sampleFormats.length; i++) {
|
||||||
MediaFormat format = sampleQueues.valueAt(i).getFormat();
|
sampleFormats[i] = 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return prepared;
|
return prepared;
|
||||||
@ -175,16 +164,16 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the {@link MediaFormat} of the specified track.
|
* Gets the {@link Format} of the samples belonging to a specified track.
|
||||||
* <p>
|
* <p>
|
||||||
* This method must only be called after the extractor has been prepared.
|
* This method must only be called after the extractor has been prepared.
|
||||||
*
|
*
|
||||||
* @param track The track index.
|
* @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());
|
Assertions.checkState(isPrepared());
|
||||||
return sampleQueueFormats[track];
|
return sampleFormats[track];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
package com.google.android.exoplayer.hls;
|
package com.google.android.exoplayer.hls;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.ParserException;
|
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.hls.HlsMediaPlaylist.Segment;
|
||||||
import com.google.android.exoplayer.upstream.UriLoadable;
|
import com.google.android.exoplayer.upstream.UriLoadable;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
@ -158,9 +158,9 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
|
|||||||
String subtitleName = HlsParserUtil.parseStringAttr(line, NAME_ATTR_REGEX, NAME_ATTR);
|
String subtitleName = HlsParserUtil.parseStringAttr(line, NAME_ATTR_REGEX, NAME_ATTR);
|
||||||
String uri = HlsParserUtil.parseStringAttr(line, URI_ATTR_REGEX, URI_ATTR);
|
String uri = HlsParserUtil.parseStringAttr(line, URI_ATTR_REGEX, URI_ATTR);
|
||||||
String language = HlsParserUtil.parseOptionalStringAttr(line, LANGUAGE_ATTR_REGEX);
|
String language = HlsParserUtil.parseOptionalStringAttr(line, LANGUAGE_ATTR_REGEX);
|
||||||
Format format = new Format(subtitleName, MimeTypes.APPLICATION_M3U8, -1, -1, -1, -1, -1,
|
Format format = Format.createTextContainerFormat(subtitleName, MimeTypes.APPLICATION_M3U8,
|
||||||
-1, language, codecs);
|
MimeTypes.TEXT_VTT, bitrate, language);
|
||||||
subtitles.add(new Variant(uri, format));
|
subtitles.add(new Variant(uri, format, null));
|
||||||
} else {
|
} else {
|
||||||
// TODO: Support other types of media tag.
|
// TODO: Support other types of media tag.
|
||||||
}
|
}
|
||||||
@ -191,9 +191,9 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
|
|||||||
if (name == null) {
|
if (name == null) {
|
||||||
name = Integer.toString(variants.size());
|
name = Integer.toString(variants.size());
|
||||||
}
|
}
|
||||||
Format format = new Format(name, MimeTypes.APPLICATION_M3U8, width, height, -1, -1, -1,
|
Format format = Format.createVideoContainerFormat(name, MimeTypes.APPLICATION_M3U8, null,
|
||||||
bitrate, null, codecs);
|
bitrate, width, height, Format.NO_VALUE, null);
|
||||||
variants.add(new Variant(line, format));
|
variants.add(new Variant(line, format, codecs));
|
||||||
bitrate = 0;
|
bitrate = 0;
|
||||||
codecs = null;
|
codecs = null;
|
||||||
name = null;
|
name = null;
|
||||||
|
@ -16,16 +16,15 @@
|
|||||||
package com.google.android.exoplayer.hls;
|
package com.google.android.exoplayer.hls;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
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.LoadControl;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
|
||||||
import com.google.android.exoplayer.MediaFormatHolder;
|
|
||||||
import com.google.android.exoplayer.SampleHolder;
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
import com.google.android.exoplayer.SampleSource;
|
import com.google.android.exoplayer.SampleSource;
|
||||||
import com.google.android.exoplayer.TrackGroup;
|
import com.google.android.exoplayer.TrackGroup;
|
||||||
import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener;
|
import com.google.android.exoplayer.chunk.BaseChunkSampleSourceEventListener;
|
||||||
import com.google.android.exoplayer.chunk.Chunk;
|
import com.google.android.exoplayer.chunk.Chunk;
|
||||||
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
|
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;
|
||||||
import com.google.android.exoplayer.upstream.Loader.Loadable;
|
import com.google.android.exoplayer.upstream.Loader.Loadable;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
@ -84,7 +83,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
// Indexed by group.
|
// Indexed by group.
|
||||||
private boolean[] groupEnabledStates;
|
private boolean[] groupEnabledStates;
|
||||||
private boolean[] pendingResets;
|
private boolean[] pendingResets;
|
||||||
private MediaFormat[] downstreamMediaFormats;
|
private Format[] downstreamSampleFormats;
|
||||||
|
|
||||||
private long downstreamPositionUs;
|
private long downstreamPositionUs;
|
||||||
private long lastSeekPositionUs;
|
private long lastSeekPositionUs;
|
||||||
@ -194,7 +193,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
public TrackStream enable(int group, int[] tracks, long positionUs) {
|
public TrackStream enable(int group, int[] tracks, long positionUs) {
|
||||||
Assertions.checkState(prepared);
|
Assertions.checkState(prepared);
|
||||||
setTrackGroupEnabledState(group, true);
|
setTrackGroupEnabledState(group, true);
|
||||||
downstreamMediaFormats[group] = null;
|
downstreamSampleFormats[group] = null;
|
||||||
pendingResets[group] = false;
|
pendingResets[group] = false;
|
||||||
downstreamFormat = null;
|
downstreamFormat = null;
|
||||||
boolean wasLoadControlRegistered = loadControlRegistered;
|
boolean wasLoadControlRegistered = loadControlRegistered;
|
||||||
@ -288,8 +287,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
return TrackStream.NO_RESET;
|
return TrackStream.NO_RESET;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ int readData(int group, MediaFormatHolder formatHolder,
|
/* package */ int readData(int group, FormatHolder formatHolder, SampleHolder sampleHolder) {
|
||||||
SampleHolder sampleHolder) {
|
|
||||||
Assertions.checkState(prepared);
|
Assertions.checkState(prepared);
|
||||||
|
|
||||||
if (pendingResets[group] || isPendingReset()) {
|
if (pendingResets[group] || isPendingReset()) {
|
||||||
@ -323,10 +321,10 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaFormat mediaFormat = extractor.getMediaFormat(group);
|
Format sampleFormat = extractor.getSampleFormat(group);
|
||||||
if (mediaFormat != null && !mediaFormat.equals(downstreamMediaFormats[group])) {
|
if (sampleFormat != null && !sampleFormat.equals(downstreamSampleFormats[group])) {
|
||||||
formatHolder.format = mediaFormat;
|
formatHolder.format = sampleFormat;
|
||||||
downstreamMediaFormats[group] = mediaFormat;
|
downstreamSampleFormats[group] = sampleFormat;
|
||||||
return TrackStream.FORMAT_READ;
|
return TrackStream.FORMAT_READ;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -485,11 +483,11 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
int primaryExtractorTrackIndex = -1;
|
int primaryExtractorTrackIndex = -1;
|
||||||
int extractorTrackCount = extractor.getTrackCount();
|
int extractorTrackCount = extractor.getTrackCount();
|
||||||
for (int i = 0; i < extractorTrackCount; i++) {
|
for (int i = 0; i < extractorTrackCount; i++) {
|
||||||
String mimeType = extractor.getMediaFormat(i).mimeType;
|
String sampleMimeType = extractor.getSampleFormat(i).sampleMimeType;
|
||||||
int trackType;
|
int trackType;
|
||||||
if (MimeTypes.isVideo(mimeType)) {
|
if (MimeTypes.isVideo(sampleMimeType)) {
|
||||||
trackType = PRIMARY_TYPE_VIDEO;
|
trackType = PRIMARY_TYPE_VIDEO;
|
||||||
} else if (MimeTypes.isAudio(mimeType)) {
|
} else if (MimeTypes.isAudio(sampleMimeType)) {
|
||||||
trackType = PRIMARY_TYPE_AUDIO;
|
trackType = PRIMARY_TYPE_AUDIO;
|
||||||
} else {
|
} else {
|
||||||
trackType = PRIMARY_TYPE_NONE;
|
trackType = PRIMARY_TYPE_NONE;
|
||||||
@ -512,20 +510,20 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
trackGroups = new TrackGroup[extractorTrackCount];
|
trackGroups = new TrackGroup[extractorTrackCount];
|
||||||
groupEnabledStates = new boolean[extractorTrackCount];
|
groupEnabledStates = new boolean[extractorTrackCount];
|
||||||
pendingResets = new boolean[extractorTrackCount];
|
pendingResets = new boolean[extractorTrackCount];
|
||||||
downstreamMediaFormats = new MediaFormat[extractorTrackCount];
|
downstreamSampleFormats = new Format[extractorTrackCount];
|
||||||
|
|
||||||
// Construct the set of exposed track groups.
|
// Construct the set of exposed track groups.
|
||||||
for (int i = 0; i < extractorTrackCount; i++) {
|
for (int i = 0; i < extractorTrackCount; i++) {
|
||||||
MediaFormat format = extractor.getMediaFormat(i);
|
Format sampleFormat = extractor.getSampleFormat(i);
|
||||||
if (i == primaryExtractorTrackIndex) {
|
if (i == primaryExtractorTrackIndex) {
|
||||||
MediaFormat[] formats = new MediaFormat[chunkSourceTrackCount];
|
Format[] formats = new Format[chunkSourceTrackCount];
|
||||||
for (int j = 0; j < chunkSourceTrackCount; j++) {
|
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);
|
trackGroups[i] = new TrackGroup(true, formats);
|
||||||
primaryTrackGroupIndex = i;
|
primaryTrackGroupIndex = i;
|
||||||
} else {
|
} 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
|
* Derives a sample format corresponding to a given container format, by combining it with sample
|
||||||
* a fixed (i.e. non-adaptive) track.
|
* level information obtained from a second sample format.
|
||||||
*
|
*
|
||||||
* @param format The {@link MediaFormat} to copy.
|
* @param containerFormat The container format for which the sample format should be derived.
|
||||||
* @param fixedTrackFormat The {@link Format} to incorporate into the copy.
|
* @param sampleFormat A sample format from which to obtain sample level information.
|
||||||
* @return The copied {@link MediaFormat}.
|
* @return The derived sample format.
|
||||||
*/
|
*/
|
||||||
private static MediaFormat copyWithFixedTrackInfo(MediaFormat format, Format fixedTrackFormat) {
|
private static Format getSampleFormat(Format containerFormat, Format sampleFormat) {
|
||||||
int width = fixedTrackFormat.width == -1 ? MediaFormat.NO_VALUE : fixedTrackFormat.width;
|
int width = containerFormat.width == -1 ? Format.NO_VALUE : containerFormat.width;
|
||||||
int height = fixedTrackFormat.height == -1 ? MediaFormat.NO_VALUE : fixedTrackFormat.height;
|
int height = containerFormat.height == -1 ? Format.NO_VALUE : containerFormat.height;
|
||||||
return format.copyWithFixedTrackInfo(fixedTrackFormat.id, fixedTrackFormat.bitrate, width,
|
return sampleFormat.copyWithContainerInfo(containerFormat.id, containerFormat.bitrate, width,
|
||||||
height, fixedTrackFormat.language);
|
height, containerFormat.language);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -816,7 +814,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int readData(MediaFormatHolder formatHolder, SampleHolder sampleHolder) {
|
public int readData(FormatHolder formatHolder, SampleHolder sampleHolder) {
|
||||||
return HlsSampleSource.this.readData(group, formatHolder, sampleHolder);
|
return HlsSampleSource.this.readData(group, formatHolder, sampleHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.hls;
|
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.chunk.MediaChunk;
|
||||||
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
|
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
|
||||||
import com.google.android.exoplayer.extractor.Extractor;
|
import com.google.android.exoplayer.extractor.Extractor;
|
||||||
|
@ -15,25 +15,21 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.hls;
|
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.FormatWrapper;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variant stream reference.
|
* Variant stream reference.
|
||||||
*/
|
*/
|
||||||
public final class Variant implements FormatWrapper {
|
public final class Variant {
|
||||||
|
|
||||||
public final String url;
|
public final String url;
|
||||||
public final Format format;
|
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.url = url;
|
||||||
this.format = format;
|
this.format = format;
|
||||||
}
|
this.codecs = codecs;
|
||||||
|
|
||||||
@Override
|
|
||||||
public Format getFormat() {
|
|
||||||
return format;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.hls;
|
package com.google.android.exoplayer.hls;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
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.ParserException;
|
||||||
import com.google.android.exoplayer.extractor.Extractor;
|
import com.google.android.exoplayer.extractor.Extractor;
|
||||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||||
@ -50,6 +50,7 @@ import java.util.regex.Pattern;
|
|||||||
private static final Pattern LOCAL_TIMESTAMP = Pattern.compile("LOCAL:([^,]+)");
|
private static final Pattern LOCAL_TIMESTAMP = Pattern.compile("LOCAL:([^,]+)");
|
||||||
private static final Pattern MEDIA_TIMESTAMP = Pattern.compile("MPEGTS:(\\d+)");
|
private static final Pattern MEDIA_TIMESTAMP = Pattern.compile("MPEGTS:(\\d+)");
|
||||||
|
|
||||||
|
private final String language;
|
||||||
private final PtsTimestampAdjuster ptsTimestampAdjuster;
|
private final PtsTimestampAdjuster ptsTimestampAdjuster;
|
||||||
private final ParsableByteArray sampleDataWrapper;
|
private final ParsableByteArray sampleDataWrapper;
|
||||||
|
|
||||||
@ -58,7 +59,8 @@ import java.util.regex.Pattern;
|
|||||||
private byte[] sampleData;
|
private byte[] sampleData;
|
||||||
private int sampleSize;
|
private int sampleSize;
|
||||||
|
|
||||||
public WebvttExtractor(PtsTimestampAdjuster ptsTimestampAdjuster) {
|
public WebvttExtractor(String language, PtsTimestampAdjuster ptsTimestampAdjuster) {
|
||||||
|
this.language = language;
|
||||||
this.ptsTimestampAdjuster = ptsTimestampAdjuster;
|
this.ptsTimestampAdjuster = ptsTimestampAdjuster;
|
||||||
this.sampleDataWrapper = new ParsableByteArray();
|
this.sampleDataWrapper = new ParsableByteArray();
|
||||||
sampleData = new byte[1024];
|
sampleData = new byte[1024];
|
||||||
@ -159,8 +161,8 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
private TrackOutput buildTrackOutput(long subsampleOffsetUs) {
|
private TrackOutput buildTrackOutput(long subsampleOffsetUs) {
|
||||||
TrackOutput trackOutput = output.track(0);
|
TrackOutput trackOutput = output.track(0);
|
||||||
trackOutput.format(MediaFormat.createTextFormat("id", MimeTypes.TEXT_VTT, MediaFormat.NO_VALUE,
|
trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.TEXT_VTT, Format.NO_VALUE,
|
||||||
"en", subsampleOffsetUs));
|
language, subsampleOffsetUs));
|
||||||
output.endTracks();
|
output.endTracks();
|
||||||
return trackOutput;
|
return trackOutput;
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
package com.google.android.exoplayer.metadata;
|
package com.google.android.exoplayer.metadata;
|
||||||
|
|
||||||
import com.google.android.exoplayer.ExoPlaybackException;
|
import com.google.android.exoplayer.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.MediaFormatHolder;
|
import com.google.android.exoplayer.FormatHolder;
|
||||||
import com.google.android.exoplayer.SampleHolder;
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
import com.google.android.exoplayer.SampleSource.TrackStream;
|
import com.google.android.exoplayer.SampleSource.TrackStream;
|
||||||
import com.google.android.exoplayer.SampleSourceTrackRenderer;
|
import com.google.android.exoplayer.SampleSourceTrackRenderer;
|
||||||
@ -59,7 +59,7 @@ public final class MetadataTrackRenderer<T> extends SampleSourceTrackRenderer im
|
|||||||
private final MetadataParser<T> metadataParser;
|
private final MetadataParser<T> metadataParser;
|
||||||
private final MetadataRenderer<T> metadataRenderer;
|
private final MetadataRenderer<T> metadataRenderer;
|
||||||
private final Handler metadataHandler;
|
private final Handler metadataHandler;
|
||||||
private final MediaFormatHolder formatHolder;
|
private final FormatHolder formatHolder;
|
||||||
private final SampleHolder sampleHolder;
|
private final SampleHolder sampleHolder;
|
||||||
|
|
||||||
private boolean inputStreamEnded;
|
private boolean inputStreamEnded;
|
||||||
@ -81,13 +81,13 @@ public final class MetadataTrackRenderer<T> extends SampleSourceTrackRenderer im
|
|||||||
this.metadataRenderer = Assertions.checkNotNull(metadataRenderer);
|
this.metadataRenderer = Assertions.checkNotNull(metadataRenderer);
|
||||||
this.metadataHandler = metadataRendererLooper == null ? null
|
this.metadataHandler = metadataRendererLooper == null ? null
|
||||||
: new Handler(metadataRendererLooper, this);
|
: new Handler(metadataRendererLooper, this);
|
||||||
formatHolder = new MediaFormatHolder();
|
formatHolder = new FormatHolder();
|
||||||
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
|
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int supportsFormat(MediaFormat mediaFormat) {
|
protected int supportsFormat(Format format) {
|
||||||
return metadataParser.canParse(mediaFormat.mimeType) ? TrackRenderer.FORMAT_HANDLED
|
return metadataParser.canParse(format.sampleMimeType) ? TrackRenderer.FORMAT_HANDLED
|
||||||
: TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
: TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,15 +17,14 @@ package com.google.android.exoplayer.smoothstreaming;
|
|||||||
|
|
||||||
import com.google.android.exoplayer.BehindLiveWindowException;
|
import com.google.android.exoplayer.BehindLiveWindowException;
|
||||||
import com.google.android.exoplayer.C;
|
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.TrackGroup;
|
||||||
import com.google.android.exoplayer.chunk.Chunk;
|
import com.google.android.exoplayer.chunk.Chunk;
|
||||||
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper;
|
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper;
|
||||||
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
|
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
|
||||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
import com.google.android.exoplayer.chunk.ChunkSource;
|
||||||
import com.google.android.exoplayer.chunk.ContainerMediaChunk;
|
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;
|
||||||
import com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation;
|
import com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation;
|
||||||
import com.google.android.exoplayer.chunk.MediaChunk;
|
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.extractor.mp4.TrackEncryptionBox;
|
||||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement;
|
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement;
|
||||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
|
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.DataSource;
|
||||||
import com.google.android.exoplayer.upstream.DataSpec;
|
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.ManifestFetcher;
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.SparseArray;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,16 +77,10 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
// Properties of exposed tracks.
|
// Properties of exposed tracks.
|
||||||
private int elementIndex;
|
private int elementIndex;
|
||||||
private TrackGroup trackGroup;
|
private TrackGroup trackGroup;
|
||||||
private Format[] trackFormats;
|
private ChunkExtractorWrapper[] extractorWrappers;
|
||||||
|
|
||||||
// Properties of enabled tracks.
|
// Properties of enabled tracks.
|
||||||
private Format[] enabledFormats;
|
private Format[] enabledFormats;
|
||||||
private int adaptiveMaxWidth;
|
|
||||||
private int adaptiveMaxHeight;
|
|
||||||
|
|
||||||
// Mappings from manifest track key.
|
|
||||||
private final SparseArray<ChunkExtractorWrapper> extractorWrappers;
|
|
||||||
private final SparseArray<MediaFormat> mediaFormats;
|
|
||||||
|
|
||||||
private IOException fatalError;
|
private IOException fatalError;
|
||||||
|
|
||||||
@ -117,8 +106,6 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
||||||
this.liveEdgeLatencyUs = liveEdgeLatencyMs * 1000;
|
this.liveEdgeLatencyUs = liveEdgeLatencyMs * 1000;
|
||||||
evaluation = new Evaluation();
|
evaluation = new Evaluation();
|
||||||
extractorWrappers = new SparseArray<>();
|
|
||||||
mediaFormats = new SparseArray<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChunkSource implementation.
|
// ChunkSource implementation.
|
||||||
@ -160,7 +147,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
trackEncryptionBoxes = null;
|
trackEncryptionBoxes = null;
|
||||||
drmInitData = null;
|
drmInitData = null;
|
||||||
}
|
}
|
||||||
selectTracks(currentManifest);
|
initForManifest(currentManifest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -178,22 +165,13 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enable(int[] tracks) {
|
public void enable(int[] tracks) {
|
||||||
int maxWidth = -1;
|
|
||||||
int maxHeight = -1;
|
|
||||||
enabledFormats = new Format[tracks.length];
|
enabledFormats = new Format[tracks.length];
|
||||||
for (int i = 0; i < tracks.length; i++) {
|
for (int i = 0; i < tracks.length; i++) {
|
||||||
enabledFormats[i] = trackFormats[tracks[i]];
|
enabledFormats[i] = trackGroup.getFormat(tracks[i]);
|
||||||
maxWidth = Math.max(enabledFormats[i].width, maxWidth);
|
|
||||||
maxHeight = Math.max(enabledFormats[i].height, maxHeight);
|
|
||||||
}
|
}
|
||||||
Arrays.sort(enabledFormats, new DecreasingBandwidthComparator());
|
Arrays.sort(enabledFormats, new DecreasingBandwidthComparator());
|
||||||
if (enabledFormats.length > 1) {
|
if (enabledFormats.length > 1) {
|
||||||
adaptiveMaxWidth = maxWidth;
|
|
||||||
adaptiveMaxHeight = maxHeight;
|
|
||||||
adaptiveFormatEvaluator.enable();
|
adaptiveFormatEvaluator.enable();
|
||||||
} else {
|
|
||||||
adaptiveMaxWidth = -1;
|
|
||||||
adaptiveMaxHeight = -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +234,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
out.chunk = null;
|
out.chunk = null;
|
||||||
return;
|
return;
|
||||||
} else if (out.queueSize == queue.size() && out.chunk != null
|
} 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
|
// We already have a chunk, and the evaluation hasn't changed either the format or the size
|
||||||
// of the queue. Leave unchanged.
|
// of the queue. Leave unchanged.
|
||||||
return;
|
return;
|
||||||
@ -311,13 +289,15 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
: chunkStartTimeUs + streamElement.getChunkDurationUs(chunkIndex);
|
: chunkStartTimeUs + streamElement.getChunkDurationUs(chunkIndex);
|
||||||
int currentAbsoluteChunkIndex = chunkIndex + currentManifestChunkOffset;
|
int currentAbsoluteChunkIndex = chunkIndex + currentManifestChunkOffset;
|
||||||
|
|
||||||
|
int trackGroupTrackIndex = getTrackGroupTrackIndex(trackGroup, selectedFormat);
|
||||||
|
ChunkExtractorWrapper extractorWrapper = extractorWrappers[trackGroupTrackIndex];
|
||||||
|
|
||||||
int manifestTrackIndex = getManifestTrackIndex(streamElement, selectedFormat);
|
int manifestTrackIndex = getManifestTrackIndex(streamElement, selectedFormat);
|
||||||
int manifestTrackKey = getManifestTrackKey(elementIndex, manifestTrackIndex);
|
|
||||||
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
|
Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex);
|
||||||
Chunk mediaChunk = newMediaChunk(selectedFormat, uri, null,
|
|
||||||
extractorWrappers.get(manifestTrackKey), drmInitData, dataSource, currentAbsoluteChunkIndex,
|
Chunk mediaChunk = newMediaChunk(selectedFormat, dataSource, uri, null,
|
||||||
chunkStartTimeUs, chunkEndTimeUs, evaluation.trigger, mediaFormats.get(manifestTrackKey),
|
currentAbsoluteChunkIndex, chunkStartTimeUs, chunkEndTimeUs, evaluation.trigger,
|
||||||
adaptiveMaxWidth, adaptiveMaxHeight);
|
extractorWrapper, drmInitData, selectedFormat);
|
||||||
out.chunk = mediaChunk;
|
out.chunk = mediaChunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,82 +322,30 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
|
|
||||||
// Private methods.
|
// Private methods.
|
||||||
|
|
||||||
private void selectTracks(SmoothStreamingManifest manifest) {
|
private void initForManifest(SmoothStreamingManifest manifest) {
|
||||||
for (int i = 0; i < manifest.streamElements.length; i++) {
|
for (int i = 0; i < manifest.streamElements.length; i++) {
|
||||||
if (manifest.streamElements[i].type == streamElementType) {
|
if (manifest.streamElements[i].type == streamElementType) {
|
||||||
// We've found an element of the desired type.
|
// We've found an element of the desired type.
|
||||||
elementIndex = i;
|
elementIndex = i;
|
||||||
TrackElement[] trackElements = manifest.streamElements[i].tracks;
|
long timescale = manifest.streamElements[i].timescale;
|
||||||
trackFormats = new Format[trackElements.length];
|
Format[] formats = manifest.streamElements[i].formats;
|
||||||
MediaFormat[] trackMediaFormats = new MediaFormat[trackElements.length];
|
extractorWrappers = new ChunkExtractorWrapper[formats.length];
|
||||||
for (int j = 0; j < trackMediaFormats.length; j++) {
|
for (int j = 0; j < formats.length; j++) {
|
||||||
trackFormats[j] = trackElements[j].format;
|
int nalUnitLengthFieldLength = streamElementType == StreamElement.TYPE_VIDEO ? 4 : -1;
|
||||||
trackMediaFormats[j] = initManifestTrack(manifest, i, j);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
extractorWrappers = null;
|
||||||
trackGroup = new TrackGroup(adaptiveFormatEvaluator != null);
|
trackGroup = new TrackGroup(adaptiveFormatEvaluator != null);
|
||||||
trackFormats = new Format[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
private MediaFormat initManifestTrack(SmoothStreamingManifest manifest, int elementIndex,
|
|
||||||
int trackIndex) {
|
|
||||||
int manifestTrackKey = getManifestTrackKey(elementIndex, trackIndex);
|
|
||||||
MediaFormat mediaFormat = mediaFormats.get(manifestTrackKey);
|
|
||||||
if (mediaFormat != null) {
|
|
||||||
// Already initialized.
|
|
||||||
return mediaFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the media format.
|
|
||||||
long durationUs = live ? C.UNKNOWN_TIME_US : manifest.durationUs;
|
|
||||||
StreamElement element = manifest.streamElements[elementIndex];
|
|
||||||
Format format = element.tracks[trackIndex].format;
|
|
||||||
byte[][] csdArray = element.tracks[trackIndex].csd;
|
|
||||||
int mp4TrackType;
|
|
||||||
switch (element.type) {
|
|
||||||
case StreamElement.TYPE_VIDEO:
|
|
||||||
mediaFormat = MediaFormat.createVideoFormat(format.id, format.mimeType, format.bitrate,
|
|
||||||
MediaFormat.NO_VALUE, format.width, format.height, Arrays.asList(csdArray));
|
|
||||||
mp4TrackType = Track.TYPE_vide;
|
|
||||||
break;
|
|
||||||
case StreamElement.TYPE_AUDIO:
|
|
||||||
List<byte[]> csd;
|
|
||||||
if (csdArray != null) {
|
|
||||||
csd = Arrays.asList(csdArray);
|
|
||||||
} else {
|
|
||||||
csd = Collections.singletonList(CodecSpecificDataUtil.buildAacAudioSpecificConfig(
|
|
||||||
format.audioSamplingRate, format.audioChannels));
|
|
||||||
}
|
|
||||||
mediaFormat = MediaFormat.createAudioFormat(format.id, format.mimeType, format.bitrate,
|
|
||||||
MediaFormat.NO_VALUE, format.audioChannels, format.audioSamplingRate, csd,
|
|
||||||
format.language);
|
|
||||||
mp4TrackType = Track.TYPE_soun;
|
|
||||||
break;
|
|
||||||
case StreamElement.TYPE_TEXT:
|
|
||||||
mediaFormat = MediaFormat.createTextFormat(format.id, format.mimeType, format.bitrate,
|
|
||||||
format.language);
|
|
||||||
mp4TrackType = Track.TYPE_text;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Invalid type: " + element.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the extractor.
|
|
||||||
FragmentedMp4Extractor mp4Extractor = new FragmentedMp4Extractor(
|
|
||||||
FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
|
|
||||||
| FragmentedMp4Extractor.WORKAROUND_IGNORE_TFDT_BOX);
|
|
||||||
Track mp4Track = new Track(trackIndex, mp4TrackType, element.timescale, C.UNKNOWN_TIME_US,
|
|
||||||
durationUs, mediaFormat, trackEncryptionBoxes, mp4TrackType == Track.TYPE_vide ? 4 : -1,
|
|
||||||
null, null);
|
|
||||||
mp4Extractor.setTrack(mp4Track);
|
|
||||||
|
|
||||||
// Store the format and a wrapper around the extractor.
|
|
||||||
mediaFormats.put(manifestTrackKey, mediaFormat);
|
|
||||||
extractorWrappers.put(manifestTrackKey, new ChunkExtractorWrapper(mp4Extractor));
|
|
||||||
return mediaFormat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -443,10 +371,12 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
return liveEdgeTimestampUs - liveEdgeLatencyUs;
|
return liveEdgeTimestampUs - liveEdgeLatencyUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getManifestTrackIndex(StreamElement element, Format format) {
|
/**
|
||||||
TrackElement[] tracks = element.tracks;
|
* Gets the index of a format in a track group, using referential equality.
|
||||||
for (int i = 0; i < tracks.length; i++) {
|
*/
|
||||||
if (tracks[i].format.equals(format)) {
|
private static int getTrackGroupTrackIndex(TrackGroup trackGroup, Format format) {
|
||||||
|
for (int i = 0; i < trackGroup.length; i++) {
|
||||||
|
if (trackGroup.getFormat(i) == format) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -454,22 +384,34 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
throw new IllegalStateException("Invalid format: " + format);
|
throw new IllegalStateException("Invalid format: " + format);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey,
|
/**
|
||||||
ChunkExtractorWrapper extractorWrapper, DrmInitData drmInitData, DataSource dataSource,
|
* Gets the index of a format in an element, using format.id equality.
|
||||||
int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs, int trigger,
|
* <p>
|
||||||
MediaFormat mediaFormat, int adaptiveMaxWidth, int adaptiveMaxHeight) {
|
* This method will return the same index as {@link #getTrackGroupTrackIndex(TrackGroup, Format)}
|
||||||
long offset = 0;
|
* except in the case where a live manifest is refreshed and the ordering of the tracks in the
|
||||||
DataSpec dataSpec = new DataSpec(uri, offset, -1, cacheKey);
|
* manifest has changed.
|
||||||
// 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.
|
private static int getManifestTrackIndex(StreamElement element, Format format) {
|
||||||
return new ContainerMediaChunk(dataSource, dataSpec, trigger, formatInfo, chunkStartTimeUs,
|
Format[] formats = element.formats;
|
||||||
chunkEndTimeUs, chunkIndex, chunkStartTimeUs, extractorWrapper, mediaFormat,
|
for (int i = 0; i < formats.length; i++) {
|
||||||
adaptiveMaxWidth, adaptiveMaxHeight, drmInitData, true, Chunk.NO_PARENT_ID);
|
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) {
|
private static MediaChunk newMediaChunk(Format format, DataSource dataSource, Uri uri,
|
||||||
Assertions.checkState(elementIndex <= 65536 && trackIndex <= 65536);
|
String cacheKey, int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs, int trigger,
|
||||||
return (elementIndex << 16) | trackIndex;
|
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) {
|
private static byte[] getProtectionElementKeyId(byte[] initData) {
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
package com.google.android.exoplayer.smoothstreaming;
|
package com.google.android.exoplayer.smoothstreaming;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.chunk.Format;
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.chunk.FormatWrapper;
|
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
import com.google.android.exoplayer.util.UriUtil;
|
import com.google.android.exoplayer.util.UriUtil;
|
||||||
import com.google.android.exoplayer.util.Util;
|
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.
|
* Represents a StreamIndex element.
|
||||||
*/
|
*/
|
||||||
@ -168,7 +145,7 @@ public class SmoothStreamingManifest {
|
|||||||
public final int displayWidth;
|
public final int displayWidth;
|
||||||
public final int displayHeight;
|
public final int displayHeight;
|
||||||
public final String language;
|
public final String language;
|
||||||
public final TrackElement[] tracks;
|
public final Format[] formats;
|
||||||
public final int chunkCount;
|
public final int chunkCount;
|
||||||
|
|
||||||
private final String baseUri;
|
private final String baseUri;
|
||||||
@ -180,7 +157,7 @@ public class SmoothStreamingManifest {
|
|||||||
|
|
||||||
public StreamElement(String baseUri, String chunkTemplate, int type, String subType,
|
public StreamElement(String baseUri, String chunkTemplate, int type, String subType,
|
||||||
long timescale, String name, int qualityLevels, int maxWidth, int maxHeight,
|
long timescale, String name, int qualityLevels, int maxWidth, int maxHeight,
|
||||||
int displayWidth, int displayHeight, String language, TrackElement[] tracks,
|
int displayWidth, int displayHeight, String language, Format[] formats,
|
||||||
List<Long> chunkStartTimes, long lastChunkDuration) {
|
List<Long> chunkStartTimes, long lastChunkDuration) {
|
||||||
this.baseUri = baseUri;
|
this.baseUri = baseUri;
|
||||||
this.chunkTemplate = chunkTemplate;
|
this.chunkTemplate = chunkTemplate;
|
||||||
@ -194,7 +171,7 @@ public class SmoothStreamingManifest {
|
|||||||
this.displayWidth = displayWidth;
|
this.displayWidth = displayWidth;
|
||||||
this.displayHeight = displayHeight;
|
this.displayHeight = displayHeight;
|
||||||
this.language = language;
|
this.language = language;
|
||||||
this.tracks = tracks;
|
this.formats = formats;
|
||||||
this.chunkCount = chunkStartTimes.size();
|
this.chunkCount = chunkStartTimes.size();
|
||||||
this.chunkStartTimes = chunkStartTimes;
|
this.chunkStartTimes = chunkStartTimes;
|
||||||
lastChunkDurationUs =
|
lastChunkDurationUs =
|
||||||
@ -242,11 +219,11 @@ public class SmoothStreamingManifest {
|
|||||||
* @return The request uri.
|
* @return The request uri.
|
||||||
*/
|
*/
|
||||||
public Uri buildRequestUri(int track, int chunkIndex) {
|
public Uri buildRequestUri(int track, int chunkIndex) {
|
||||||
Assertions.checkState(tracks != null);
|
Assertions.checkState(formats != null);
|
||||||
Assertions.checkState(chunkStartTimes != null);
|
Assertions.checkState(chunkStartTimes != null);
|
||||||
Assertions.checkState(chunkIndex < chunkStartTimes.size());
|
Assertions.checkState(chunkIndex < chunkStartTimes.size());
|
||||||
String chunkUrl = chunkTemplate
|
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());
|
.replace(URL_PLACEHOLDER_START_TIME, chunkStartTimes.get(chunkIndex).toString());
|
||||||
return UriUtil.resolveToUri(baseUri, chunkUrl);
|
return UriUtil.resolveToUri(baseUri, chunkUrl);
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.smoothstreaming;
|
package com.google.android.exoplayer.smoothstreaming;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.ParserException;
|
import com.google.android.exoplayer.ParserException;
|
||||||
import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
|
import com.google.android.exoplayer.extractor.mp4.PsshAtomUtil;
|
||||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement;
|
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement;
|
||||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
|
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.upstream.UriLoadable;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
import com.google.android.exoplayer.util.CodecSpecificDataUtil;
|
||||||
@ -35,6 +35,7 @@ import org.xmlpull.v1.XmlPullParserFactory;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -461,7 +462,7 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
|
|||||||
private static final String KEY_FRAGMENT_REPEAT_COUNT = "r";
|
private static final String KEY_FRAGMENT_REPEAT_COUNT = "r";
|
||||||
|
|
||||||
private final String baseUri;
|
private final String baseUri;
|
||||||
private final List<TrackElement> tracks;
|
private final List<Format> formats;
|
||||||
|
|
||||||
private int type;
|
private int type;
|
||||||
private String subType;
|
private String subType;
|
||||||
@ -481,7 +482,7 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
|
|||||||
public StreamElementParser(ElementParser parent, String baseUri) {
|
public StreamElementParser(ElementParser parent, String baseUri) {
|
||||||
super(parent, baseUri, TAG);
|
super(parent, baseUri, TAG);
|
||||||
this.baseUri = baseUri;
|
this.baseUri = baseUri;
|
||||||
tracks = new LinkedList<>();
|
formats = new LinkedList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -569,17 +570,17 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addChild(Object child) {
|
public void addChild(Object child) {
|
||||||
if (child instanceof TrackElement) {
|
if (child instanceof Format) {
|
||||||
tracks.add((TrackElement) child);
|
formats.add((Format) child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object build() {
|
public Object build() {
|
||||||
TrackElement[] trackElements = new TrackElement[tracks.size()];
|
Format[] formatArray = new Format[formats.size()];
|
||||||
tracks.toArray(trackElements);
|
formats.toArray(formatArray);
|
||||||
return new StreamElement(baseUri, url, type, subType, timescale, name, qualityLevels,
|
return new StreamElement(baseUri, url, type, subType, timescale, name, qualityLevels,
|
||||||
maxWidth, maxHeight, displayWidth, displayHeight, language, trackElements, startTimes,
|
maxWidth, maxHeight, displayWidth, displayHeight, language, formatArray, startTimes,
|
||||||
lastChunkDuration);
|
lastChunkDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -600,75 +601,62 @@ public class SmoothStreamingManifestParser implements UriLoadable.Parser<SmoothS
|
|||||||
private static final String KEY_MAX_WIDTH = "MaxWidth";
|
private static final String KEY_MAX_WIDTH = "MaxWidth";
|
||||||
private static final String KEY_MAX_HEIGHT = "MaxHeight";
|
private static final String KEY_MAX_HEIGHT = "MaxHeight";
|
||||||
|
|
||||||
private final List<byte[]> csd;
|
private Format format;
|
||||||
|
|
||||||
private int index;
|
|
||||||
private int bitrate;
|
|
||||||
private String mimeType;
|
|
||||||
private int maxWidth;
|
|
||||||
private int maxHeight;
|
|
||||||
private int samplingRate;
|
|
||||||
private int channels;
|
|
||||||
private String language;
|
|
||||||
|
|
||||||
public TrackElementParser(ElementParser parent, String baseUri) {
|
public TrackElementParser(ElementParser parent, String baseUri) {
|
||||||
super(parent, baseUri, TAG);
|
super(parent, baseUri, TAG);
|
||||||
this.csd = new LinkedList<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void parseStartTag(XmlPullParser parser) throws ParserException {
|
public void parseStartTag(XmlPullParser parser) throws ParserException {
|
||||||
int type = (Integer) getNormalizedAttribute(KEY_TYPE);
|
int type = (Integer) getNormalizedAttribute(KEY_TYPE);
|
||||||
String value;
|
String id = parser.getAttributeValue(null, KEY_INDEX);
|
||||||
|
int bitrate = parseRequiredInt(parser, KEY_BITRATE);
|
||||||
index = parseInt(parser, KEY_INDEX, -1);
|
String sampleMimeType = fourCCToMimeType(parseRequiredString(parser, KEY_FOUR_CC));
|
||||||
bitrate = parseRequiredInt(parser, KEY_BITRATE);
|
|
||||||
language = (String) getNormalizedAttribute(KEY_LANGUAGE);
|
|
||||||
|
|
||||||
if (type == StreamElement.TYPE_VIDEO) {
|
if (type == StreamElement.TYPE_VIDEO) {
|
||||||
maxHeight = parseRequiredInt(parser, KEY_MAX_HEIGHT);
|
int width = parseRequiredInt(parser, KEY_MAX_WIDTH);
|
||||||
maxWidth = parseRequiredInt(parser, KEY_MAX_WIDTH);
|
int height = parseRequiredInt(parser, KEY_MAX_HEIGHT);
|
||||||
mimeType = fourCCToMimeType(parseRequiredString(parser, KEY_FOUR_CC));
|
List<byte[]> codecSpecificData = buildCodecSpecificData(
|
||||||
|
parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA));
|
||||||
|
format = Format.createVideoContainerFormat(id, MimeTypes.VIDEO_MP4, sampleMimeType, bitrate,
|
||||||
|
width, height, Format.NO_VALUE, codecSpecificData);
|
||||||
|
} else if (type == StreamElement.TYPE_AUDIO) {
|
||||||
|
sampleMimeType = sampleMimeType == null ? MimeTypes.AUDIO_AAC : sampleMimeType;
|
||||||
|
int channels = parseRequiredInt(parser, KEY_CHANNELS);
|
||||||
|
int samplingRate = parseRequiredInt(parser, KEY_SAMPLING_RATE);
|
||||||
|
List<byte[]> codecSpecificData = buildCodecSpecificData(
|
||||||
|
parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA));
|
||||||
|
String language = (String) getNormalizedAttribute(KEY_LANGUAGE);
|
||||||
|
format = Format.createAudioContainerFormat(id, MimeTypes.AUDIO_MP4, sampleMimeType, bitrate,
|
||||||
|
channels, samplingRate, codecSpecificData, language);
|
||||||
|
} else if (type == StreamElement.TYPE_TEXT) {
|
||||||
|
String language = (String) getNormalizedAttribute(KEY_LANGUAGE);
|
||||||
|
format = Format.createTextContainerFormat(id, MimeTypes.APPLICATION_MP4, sampleMimeType,
|
||||||
|
bitrate, language);
|
||||||
} else {
|
} else {
|
||||||
maxHeight = -1;
|
format = Format.createContainerFormat(id, MimeTypes.APPLICATION_MP4, sampleMimeType,
|
||||||
maxWidth = -1;
|
bitrate);
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object build() {
|
public Object build() {
|
||||||
byte[][] csdArray = null;
|
return format;
|
||||||
if (!csd.isEmpty()) {
|
|
||||||
csdArray = new byte[csd.size()][];
|
|
||||||
csd.toArray(csdArray);
|
|
||||||
}
|
}
|
||||||
return new TrackElement(index, bitrate, mimeType, csdArray, maxWidth, maxHeight, samplingRate,
|
|
||||||
channels, language);
|
private static List<byte[]> buildCodecSpecificData(String codecSpecificDataString) {
|
||||||
|
ArrayList<byte[]> csd = new ArrayList<>();
|
||||||
|
if (codecSpecificDataString != null && !codecSpecificDataString.isEmpty()) {
|
||||||
|
byte[] codecPrivateData = hexStringToByteArray(codecSpecificDataString);
|
||||||
|
byte[][] split = CodecSpecificDataUtil.splitNalUnits(codecPrivateData);
|
||||||
|
if (split == null) {
|
||||||
|
csd.add(codecPrivateData);
|
||||||
|
} else {
|
||||||
|
Collections.addAll(csd, split);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return csd;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String fourCCToMimeType(String fourCC) {
|
private static String fourCCToMimeType(String fourCC) {
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.text;
|
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.ParserException;
|
||||||
import com.google.android.exoplayer.SampleHolder;
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
@ -97,7 +97,7 @@ import java.io.IOException;
|
|||||||
*
|
*
|
||||||
* @param format The format.
|
* @param format The format.
|
||||||
*/
|
*/
|
||||||
public void setFormat(MediaFormat format) {
|
public void setFormat(Format format) {
|
||||||
handler.obtainMessage(MSG_FORMAT, format).sendToTarget();
|
handler.obtainMessage(MSG_FORMAT, format).sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ import java.io.IOException;
|
|||||||
public boolean handleMessage(Message msg) {
|
public boolean handleMessage(Message msg) {
|
||||||
switch (msg.what) {
|
switch (msg.what) {
|
||||||
case MSG_FORMAT:
|
case MSG_FORMAT:
|
||||||
handleFormat((MediaFormat) msg.obj);
|
handleFormat((Format) msg.obj);
|
||||||
break;
|
break;
|
||||||
case MSG_SAMPLE:
|
case MSG_SAMPLE:
|
||||||
long sampleTimeUs = Util.getLong(msg.arg1, msg.arg2);
|
long sampleTimeUs = Util.getLong(msg.arg1, msg.arg2);
|
||||||
@ -157,8 +157,8 @@ import java.io.IOException;
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleFormat(MediaFormat format) {
|
private void handleFormat(Format format) {
|
||||||
subtitlesAreRelative = format.subsampleOffsetUs == MediaFormat.OFFSET_SAMPLE_RELATIVE;
|
subtitlesAreRelative = format.subsampleOffsetUs == Format.OFFSET_SAMPLE_RELATIVE;
|
||||||
subtitleOffsetUs = subtitlesAreRelative ? 0 : format.subsampleOffsetUs;
|
subtitleOffsetUs = subtitlesAreRelative ? 0 : format.subsampleOffsetUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
package com.google.android.exoplayer.text;
|
package com.google.android.exoplayer.text;
|
||||||
|
|
||||||
import com.google.android.exoplayer.ExoPlaybackException;
|
import com.google.android.exoplayer.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.MediaFormatHolder;
|
import com.google.android.exoplayer.FormatHolder;
|
||||||
import com.google.android.exoplayer.SampleHolder;
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
import com.google.android.exoplayer.SampleSource.TrackStream;
|
import com.google.android.exoplayer.SampleSource.TrackStream;
|
||||||
import com.google.android.exoplayer.SampleSourceTrackRenderer;
|
import com.google.android.exoplayer.SampleSourceTrackRenderer;
|
||||||
@ -112,7 +112,7 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
|
|||||||
|
|
||||||
private final Handler textRendererHandler;
|
private final Handler textRendererHandler;
|
||||||
private final TextRenderer textRenderer;
|
private final TextRenderer textRenderer;
|
||||||
private final MediaFormatHolder formatHolder;
|
private final FormatHolder formatHolder;
|
||||||
private final SubtitleParser[] subtitleParsers;
|
private final SubtitleParser[] subtitleParsers;
|
||||||
|
|
||||||
private int parserIndex;
|
private int parserIndex;
|
||||||
@ -151,15 +151,25 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.subtitleParsers = subtitleParsers;
|
this.subtitleParsers = subtitleParsers;
|
||||||
formatHolder = new MediaFormatHolder();
|
formatHolder = new FormatHolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int supportsFormat(MediaFormat mediaFormat) {
|
protected int supportsFormat(Format format) {
|
||||||
return getParserIndex(mediaFormat) != -1 ? TrackRenderer.FORMAT_HANDLED
|
return getParserIndex(format.sampleMimeType) != -1 ? TrackRenderer.FORMAT_HANDLED
|
||||||
: TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
: 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
|
@Override
|
||||||
protected void onReset(long positionUs) {
|
protected void onReset(long positionUs) {
|
||||||
inputStreamEnded = false;
|
inputStreamEnded = false;
|
||||||
@ -217,17 +227,7 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
|
|||||||
SampleHolder sampleHolder = parserHelper.getSampleHolder();
|
SampleHolder sampleHolder = parserHelper.getSampleHolder();
|
||||||
sampleHolder.clearData();
|
sampleHolder.clearData();
|
||||||
int result = readSource(formatHolder, sampleHolder);
|
int result = readSource(formatHolder, sampleHolder);
|
||||||
if (result == TrackStream.FORMAT_READ) {
|
if (result == TrackStream.SAMPLE_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) {
|
|
||||||
parserHelper.startParseOperation();
|
parserHelper.startParseOperation();
|
||||||
} else if (result == TrackStream.END_OF_STREAM) {
|
} else if (result == TrackStream.END_OF_STREAM) {
|
||||||
inputStreamEnded = true;
|
inputStreamEnded = true;
|
||||||
@ -291,9 +291,9 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
|
|||||||
textRenderer.onCues(cues);
|
textRenderer.onCues(cues);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getParserIndex(MediaFormat mediaFormat) {
|
private int getParserIndex(String sampleMimeType) {
|
||||||
for (int i = 0; i < subtitleParsers.length; i++) {
|
for (int i = 0; i < subtitleParsers.length; i++) {
|
||||||
if (subtitleParsers[i].canParse(mediaFormat.mimeType)) {
|
if (subtitleParsers[i].canParse(sampleMimeType)) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,8 @@ package com.google.android.exoplayer.text.eia608;
|
|||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
import com.google.android.exoplayer.ExoPlaybackException;
|
import com.google.android.exoplayer.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.Format;
|
||||||
import com.google.android.exoplayer.MediaFormatHolder;
|
import com.google.android.exoplayer.FormatHolder;
|
||||||
import com.google.android.exoplayer.SampleHolder;
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
import com.google.android.exoplayer.SampleSource.TrackStream;
|
import com.google.android.exoplayer.SampleSource.TrackStream;
|
||||||
import com.google.android.exoplayer.SampleSourceTrackRenderer;
|
import com.google.android.exoplayer.SampleSourceTrackRenderer;
|
||||||
@ -56,7 +56,7 @@ public final class Eia608TrackRenderer extends SampleSourceTrackRenderer impleme
|
|||||||
private final Eia608Parser eia608Parser;
|
private final Eia608Parser eia608Parser;
|
||||||
private final TextRenderer textRenderer;
|
private final TextRenderer textRenderer;
|
||||||
private final Handler textRendererHandler;
|
private final Handler textRendererHandler;
|
||||||
private final MediaFormatHolder formatHolder;
|
private final FormatHolder formatHolder;
|
||||||
private final SampleHolder sampleHolder;
|
private final SampleHolder sampleHolder;
|
||||||
private final StringBuilder captionStringBuilder;
|
private final StringBuilder captionStringBuilder;
|
||||||
private final TreeSet<ClosedCaptionList> pendingCaptionLists;
|
private final TreeSet<ClosedCaptionList> pendingCaptionLists;
|
||||||
@ -79,15 +79,15 @@ public final class Eia608TrackRenderer extends SampleSourceTrackRenderer impleme
|
|||||||
this.textRenderer = Assertions.checkNotNull(textRenderer);
|
this.textRenderer = Assertions.checkNotNull(textRenderer);
|
||||||
textRendererHandler = textRendererLooper == null ? null : new Handler(textRendererLooper, this);
|
textRendererHandler = textRendererLooper == null ? null : new Handler(textRendererLooper, this);
|
||||||
eia608Parser = new Eia608Parser();
|
eia608Parser = new Eia608Parser();
|
||||||
formatHolder = new MediaFormatHolder();
|
formatHolder = new FormatHolder();
|
||||||
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
|
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
|
||||||
captionStringBuilder = new StringBuilder();
|
captionStringBuilder = new StringBuilder();
|
||||||
pendingCaptionLists = new TreeSet<>();
|
pendingCaptionLists = new TreeSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int supportsFormat(MediaFormat mediaFormat) {
|
protected int supportsFormat(Format format) {
|
||||||
return eia608Parser.canParse(mediaFormat.mimeType) ? TrackRenderer.FORMAT_HANDLED
|
return eia608Parser.canParse(format.sampleMimeType) ? TrackRenderer.FORMAT_HANDLED
|
||||||
: TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
: TrackRenderer.FORMAT_UNSUPPORTED_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.util;
|
package com.google.android.exoplayer.util;
|
||||||
|
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.Format;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ public final class Ac3Util {
|
|||||||
* @param language The language to set on the format.
|
* @param language The language to set on the format.
|
||||||
* @return The AC-3 format parsed from data in the header.
|
* @return The AC-3 format parsed from data in the header.
|
||||||
*/
|
*/
|
||||||
public static MediaFormat parseAc3AnnexFFormat(ParsableByteArray data, String trackId,
|
public static Format parseAc3AnnexFFormat(ParsableByteArray data, String trackId,
|
||||||
String language) {
|
String language) {
|
||||||
int fscod = (data.readUnsignedByte() & 0xC0) >> 6;
|
int fscod = (data.readUnsignedByte() & 0xC0) >> 6;
|
||||||
int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod];
|
int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod];
|
||||||
@ -77,8 +77,8 @@ public final class Ac3Util {
|
|||||||
if ((nextByte & 0x04) != 0) { // lfeon
|
if ((nextByte & 0x04) != 0) { // lfeon
|
||||||
channelCount++;
|
channelCount++;
|
||||||
}
|
}
|
||||||
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE,
|
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, Format.NO_VALUE,
|
||||||
MediaFormat.NO_VALUE, channelCount, sampleRate, null, language);
|
Format.NO_VALUE, channelCount, sampleRate, null, language);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -90,7 +90,7 @@ public final class Ac3Util {
|
|||||||
* @param language The language to set on the format.
|
* @param language The language to set on the format.
|
||||||
* @return The E-AC-3 format parsed from data in the header.
|
* @return The E-AC-3 format parsed from data in the header.
|
||||||
*/
|
*/
|
||||||
public static MediaFormat parseEAc3AnnexFFormat(ParsableByteArray data, String trackId,
|
public static Format parseEAc3AnnexFFormat(ParsableByteArray data, String trackId,
|
||||||
String language) {
|
String language) {
|
||||||
data.skipBytes(2); // data_rate, num_ind_sub
|
data.skipBytes(2); // data_rate, num_ind_sub
|
||||||
|
|
||||||
@ -103,8 +103,8 @@ public final class Ac3Util {
|
|||||||
if ((nextByte & 0x01) != 0) { // lfeon
|
if ((nextByte & 0x01) != 0) { // lfeon
|
||||||
channelCount++;
|
channelCount++;
|
||||||
}
|
}
|
||||||
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_E_AC3, MediaFormat.NO_VALUE,
|
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, Format.NO_VALUE,
|
||||||
MediaFormat.NO_VALUE, channelCount, sampleRate, null, language);
|
Format.NO_VALUE, channelCount, sampleRate, null, language);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -116,7 +116,7 @@ public final class Ac3Util {
|
|||||||
* @param language The language to set on the format.
|
* @param language The language to set on the format.
|
||||||
* @return The AC-3 format parsed from data in the header.
|
* @return The AC-3 format parsed from data in the header.
|
||||||
*/
|
*/
|
||||||
public static MediaFormat parseAc3SyncframeFormat(ParsableBitArray data, String trackId,
|
public static Format parseAc3SyncframeFormat(ParsableBitArray data, String trackId,
|
||||||
String language) {
|
String language) {
|
||||||
data.skipBits(16 + 16); // syncword, crc1
|
data.skipBits(16 + 16); // syncword, crc1
|
||||||
int fscod = data.readBits(2);
|
int fscod = data.readBits(2);
|
||||||
@ -132,8 +132,8 @@ public final class Ac3Util {
|
|||||||
data.skipBits(2); // dsurmod
|
data.skipBits(2); // dsurmod
|
||||||
}
|
}
|
||||||
boolean lfeon = data.readBit();
|
boolean lfeon = data.readBit();
|
||||||
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE,
|
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, Format.NO_VALUE,
|
||||||
MediaFormat.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0),
|
Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0),
|
||||||
SAMPLE_RATE_BY_FSCOD[fscod], null, language);
|
SAMPLE_RATE_BY_FSCOD[fscod], null, language);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ public final class Ac3Util {
|
|||||||
* @param language The language to set on the format.
|
* @param language The language to set on the format.
|
||||||
* @return The E-AC-3 format parsed from data in the header.
|
* @return The E-AC-3 format parsed from data in the header.
|
||||||
*/
|
*/
|
||||||
public static MediaFormat parseEac3SyncframeFormat(ParsableBitArray data, String trackId,
|
public static Format parseEac3SyncframeFormat(ParsableBitArray data, String trackId,
|
||||||
String language) {
|
String language) {
|
||||||
data.skipBits(16 + 2 + 3 + 11); // syncword, strmtype, substreamid, frmsiz
|
data.skipBits(16 + 2 + 3 + 11); // syncword, strmtype, substreamid, frmsiz
|
||||||
int sampleRate;
|
int sampleRate;
|
||||||
@ -159,8 +159,8 @@ public final class Ac3Util {
|
|||||||
}
|
}
|
||||||
int acmod = data.readBits(3);
|
int acmod = data.readBits(3);
|
||||||
boolean lfeon = data.readBit();
|
boolean lfeon = data.readBit();
|
||||||
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_E_AC3, MediaFormat.NO_VALUE,
|
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, Format.NO_VALUE,
|
||||||
MediaFormat.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), sampleRate, null,
|
Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), sampleRate, null,
|
||||||
language);
|
language);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer.util;
|
package com.google.android.exoplayer.util;
|
||||||
|
|
||||||
import com.google.android.exoplayer.CodecCounters;
|
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 com.google.android.exoplayer.upstream.BandwidthMeter;
|
||||||
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.util;
|
package com.google.android.exoplayer.util;
|
||||||
|
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
import com.google.android.exoplayer.Format;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ public final class DtsUtil {
|
|||||||
* @param language The language to set on the format.
|
* @param language The language to set on the format.
|
||||||
* @return The DTS format parsed from data in the header.
|
* @return The DTS format parsed from data in the header.
|
||||||
*/
|
*/
|
||||||
public static MediaFormat parseDtsFormat(byte[] frame, String trackId, String language) {
|
public static Format parseDtsFormat(byte[] frame, String trackId, String language) {
|
||||||
ParsableBitArray frameBits = SCRATCH_BITS;
|
ParsableBitArray frameBits = SCRATCH_BITS;
|
||||||
frameBits.reset(frame);
|
frameBits.reset(frame);
|
||||||
frameBits.skipBits(4 * 8 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE
|
frameBits.skipBits(4 * 8 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE
|
||||||
@ -65,12 +65,12 @@ public final class DtsUtil {
|
|||||||
int sfreq = frameBits.readBits(4);
|
int sfreq = frameBits.readBits(4);
|
||||||
int sampleRate = SAMPLE_RATE_BY_SFREQ[sfreq];
|
int sampleRate = SAMPLE_RATE_BY_SFREQ[sfreq];
|
||||||
int rate = frameBits.readBits(5);
|
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;
|
: TWICE_BITRATE_KBPS_BY_RATE[rate] * 1000 / 2;
|
||||||
frameBits.skipBits(10); // MIX, DYNF, TIMEF, AUXF, HDCD, EXT_AUDIO_ID, EXT_AUDIO, ASPF
|
frameBits.skipBits(10); // MIX, DYNF, TIMEF, AUXF, HDCD, EXT_AUDIO_ID, EXT_AUDIO, ASPF
|
||||||
channelCount += frameBits.readBits(2) > 0 ? 1 : 0; // LFF
|
channelCount += frameBits.readBits(2) > 0 ? 1 : 0; // LFF
|
||||||
return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_DTS, bitrate,
|
return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_DTS, bitrate, Format.NO_VALUE,
|
||||||
MediaFormat.NO_VALUE, channelCount, sampleRate, null, language);
|
channelCount, sampleRate, null, language);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,7 +25,6 @@ public final class MimeTypes {
|
|||||||
public static final String BASE_TYPE_TEXT = "text";
|
public static final String BASE_TYPE_TEXT = "text";
|
||||||
public static final String BASE_TYPE_APPLICATION = "application";
|
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_MP4 = BASE_TYPE_VIDEO + "/mp4";
|
||||||
public static final String VIDEO_WEBM = BASE_TYPE_VIDEO + "/webm";
|
public static final String VIDEO_WEBM = BASE_TYPE_VIDEO + "/webm";
|
||||||
public static final String VIDEO_H263 = BASE_TYPE_VIDEO + "/3gpp";
|
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_MP4V = BASE_TYPE_VIDEO + "/mp4v-es";
|
||||||
public static final String VIDEO_MPEG2 = BASE_TYPE_VIDEO + "/mpeg2";
|
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_MP4 = BASE_TYPE_AUDIO + "/mp4";
|
||||||
public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm";
|
public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm";
|
||||||
public static final String AUDIO_WEBM = BASE_TYPE_AUDIO + "/webm";
|
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_NB = BASE_TYPE_AUDIO + "/3gpp";
|
||||||
public static final String AUDIO_AMR_WB = BASE_TYPE_AUDIO + "/amr-wb";
|
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 TEXT_VTT = BASE_TYPE_TEXT + "/vtt";
|
||||||
|
|
||||||
public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4";
|
public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4";
|
||||||
@ -123,62 +120,4 @@ public final class MimeTypes {
|
|||||||
return mimeType.substring(0, indexOfSlash);
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user