Add language to MediaFormat + parse it from mdhd box.
+ Move conversion from framework -> exo format to FrameworkSampleSource. + Improve MediaFormat conversion test.
This commit is contained in:
parent
b2206866f0
commit
7d306ae593
@ -17,17 +17,20 @@ package com.google.android.exoplayer;
|
|||||||
|
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit test for {@link MediaFormat}.
|
* Unit test for {@link MediaFormat}.
|
||||||
*/
|
*/
|
||||||
public class MediaFormatTest extends TestCase {
|
public final class MediaFormatTest extends TestCase {
|
||||||
|
|
||||||
public void testConversionToFrameworkFormat() {
|
public void testConversionToFrameworkFormat() {
|
||||||
if (Util.SDK_INT < 16) {
|
if (Util.SDK_INT < 16) {
|
||||||
@ -41,20 +44,64 @@ public class MediaFormatTest extends TestCase {
|
|||||||
initData.add(initData1);
|
initData.add(initData1);
|
||||||
initData.add(initData2);
|
initData.add(initData2);
|
||||||
|
|
||||||
|
testConversionToFrameworkFormatV16(MediaFormat.createVideoFormat(
|
||||||
|
"video/xyz", 102400, 1000L, 1280, 720, 1, initData));
|
||||||
|
testConversionToFrameworkFormatV16(MediaFormat.createVideoFormat(
|
||||||
|
"video/xyz", MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, 1280, 720, 1, null));
|
||||||
|
testConversionToFrameworkFormatV16(MediaFormat.createAudioFormat(
|
||||||
|
"audio/xyz", 128, 1000L, 5, 44100, initData));
|
||||||
|
testConversionToFrameworkFormatV16(MediaFormat.createAudioFormat(
|
||||||
|
"audio/xyz", MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, 5, 44100, null));
|
||||||
testConversionToFrameworkFormatV16(
|
testConversionToFrameworkFormatV16(
|
||||||
MediaFormat.createVideoFormat("video/xyz", 102400, 1000L, 1280, 720, 1.5f, initData));
|
MediaFormat.createTextFormat("text/xyz", "eng", 1000L));
|
||||||
testConversionToFrameworkFormatV16(
|
testConversionToFrameworkFormatV16(
|
||||||
MediaFormat.createAudioFormat("audio/xyz", 102400, 1000L, 5, 44100, initData));
|
MediaFormat.createTextFormat("text/xyz", null, C.UNKNOWN_TIME_US));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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.getMaxVideoWidth());
|
||||||
|
assertOptionalV16(out, android.media.MediaFormat.KEY_MAX_HEIGHT, in.getMaxVideoHeight());
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
if (in.durationUs == C.UNKNOWN_TIME_US) {
|
||||||
|
assertFalse(out.containsKey(android.media.MediaFormat.KEY_DURATION));
|
||||||
|
} else {
|
||||||
|
assertEquals(in.durationUs, out.getLong(android.media.MediaFormat.KEY_DURATION));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(16)
|
@TargetApi(16)
|
||||||
private void testConversionToFrameworkFormatV16(MediaFormat format) {
|
private static void assertOptionalV16(android.media.MediaFormat format, String key,
|
||||||
// Convert to a framework MediaFormat and back again.
|
String value) {
|
||||||
MediaFormat convertedFormat = MediaFormat.createFromFrameworkMediaFormatV16(
|
if (value == null) {
|
||||||
format.getFrameworkMediaFormatV16());
|
assertFalse(format.containsKey(key));
|
||||||
// Assert that we end up with an equivalent object to the one we started with.
|
} else {
|
||||||
assertEquals(format.hashCode(), convertedFormat.hashCode());
|
assertEquals(value, format.getString(key));
|
||||||
assertEquals(format, convertedFormat);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import com.google.android.exoplayer.util.Assertions;
|
|||||||
import com.google.android.exoplayer.util.MimeTypes;
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
|
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;
|
||||||
@ -30,6 +31,8 @@ import android.net.Uri;
|
|||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -197,8 +200,7 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe
|
|||||||
return NOTHING_READ;
|
return NOTHING_READ;
|
||||||
}
|
}
|
||||||
if (trackStates[track] != TRACK_STATE_FORMAT_SENT) {
|
if (trackStates[track] != TRACK_STATE_FORMAT_SENT) {
|
||||||
formatHolder.format = MediaFormat.createFromFrameworkMediaFormatV16(
|
formatHolder.format = createMediaFormat(extractor.getTrackFormat(track));
|
||||||
extractor.getTrackFormat(track));
|
|
||||||
formatHolder.drmInitData = Util.SDK_INT >= 18 ? getDrmInitDataV18() : null;
|
formatHolder.drmInitData = Util.SDK_INT >= 18 ? getDrmInitDataV18() : null;
|
||||||
trackStates[track] = TRACK_STATE_FORMAT_SENT;
|
trackStates[track] = TRACK_STATE_FORMAT_SENT;
|
||||||
return FORMAT_READ;
|
return FORMAT_READ;
|
||||||
@ -297,4 +299,37 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
private static MediaFormat createMediaFormat(android.media.MediaFormat format) {
|
||||||
|
String mimeType = format.getString(android.media.MediaFormat.KEY_MIME);
|
||||||
|
String language = getOptionalStringV16(format, android.media.MediaFormat.KEY_LANGUAGE);
|
||||||
|
int maxInputSize = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE);
|
||||||
|
int width = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_WIDTH);
|
||||||
|
int height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT);
|
||||||
|
int channelCount = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT);
|
||||||
|
int sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE);
|
||||||
|
ArrayList<byte[]> initializationData = new ArrayList<>();
|
||||||
|
for (int i = 0; format.containsKey("csd-" + i); i++) {
|
||||||
|
ByteBuffer buffer = format.getByteBuffer("csd-" + i);
|
||||||
|
byte[] data = new byte[buffer.limit()];
|
||||||
|
buffer.get(data);
|
||||||
|
initializationData.add(data);
|
||||||
|
buffer.flip();
|
||||||
|
}
|
||||||
|
long durationUs = format.containsKey(android.media.MediaFormat.KEY_DURATION)
|
||||||
|
? format.getLong(android.media.MediaFormat.KEY_DURATION) : C.UNKNOWN_TIME_US;
|
||||||
|
return new MediaFormat(mimeType, maxInputSize, durationUs, width, height, MediaFormat.NO_VALUE,
|
||||||
|
channelCount, sampleRate, language, initializationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(16)
|
||||||
|
private static final String getOptionalStringV16(android.media.MediaFormat format, String key) {
|
||||||
|
return format.containsKey(key) ? format.getString(key) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(16)
|
||||||
|
private static final int getOptionalIntegerV16(android.media.MediaFormat format, String key) {
|
||||||
|
return format.containsKey(key) ? format.getInteger(key) : MediaFormat.NO_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -890,8 +890,11 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
|
|||||||
* incorrectly on the host device. False otherwise.
|
* incorrectly on the host device. False otherwise.
|
||||||
*/
|
*/
|
||||||
private static boolean codecNeedsEndOfStreamWorkaround(String name) {
|
private static boolean codecNeedsEndOfStreamWorkaround(String name) {
|
||||||
return Util.SDK_INT <= 17 && "ht7s3".equals(Util.DEVICE) // Tesco HUDL
|
return Util.SDK_INT <= 17
|
||||||
&& "OMX.rk.video_decoder.avc".equals(name);
|
&& "OMX.rk.video_decoder.avc".equals(name)
|
||||||
|
&& ("ht7s3".equals(Util.DEVICE) // Tesco HUDL
|
||||||
|
|| "rk30sdk".equals(Util.DEVICE) // Rockchip rk30
|
||||||
|
|| "rk31sdk".equals(Util.DEVICE)); // Rockchip rk31
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ import android.annotation.SuppressLint;
|
|||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -31,9 +30,6 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public final class MediaFormat {
|
public final class MediaFormat {
|
||||||
|
|
||||||
private static final String KEY_PIXEL_WIDTH_HEIGHT_RATIO =
|
|
||||||
"com.google.android.videos.pixelWidthHeightRatio";
|
|
||||||
|
|
||||||
public static final int NO_VALUE = -1;
|
public static final int NO_VALUE = -1;
|
||||||
|
|
||||||
public final String mimeType;
|
public final String mimeType;
|
||||||
@ -48,6 +44,8 @@ public final class MediaFormat {
|
|||||||
public final int channelCount;
|
public final int channelCount;
|
||||||
public final int sampleRate;
|
public final int sampleRate;
|
||||||
|
|
||||||
|
public final String language;
|
||||||
|
|
||||||
public final List<byte[]> initializationData;
|
public final List<byte[]> initializationData;
|
||||||
|
|
||||||
private int maxWidth;
|
private int maxWidth;
|
||||||
@ -58,11 +56,6 @@ public final class MediaFormat {
|
|||||||
// Possibly-lazy-initialized framework media format.
|
// Possibly-lazy-initialized framework media format.
|
||||||
private android.media.MediaFormat frameworkMediaFormat;
|
private android.media.MediaFormat frameworkMediaFormat;
|
||||||
|
|
||||||
@TargetApi(16)
|
|
||||||
public static MediaFormat createFromFrameworkMediaFormatV16(android.media.MediaFormat format) {
|
|
||||||
return new MediaFormat(format);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MediaFormat createVideoFormat(String mimeType, int maxInputSize, int width,
|
public static MediaFormat createVideoFormat(String mimeType, int maxInputSize, int width,
|
||||||
int height, List<byte[]> initializationData) {
|
int height, List<byte[]> initializationData) {
|
||||||
return createVideoFormat(
|
return createVideoFormat(
|
||||||
@ -78,7 +71,7 @@ public final class MediaFormat {
|
|||||||
public static MediaFormat createVideoFormat(String mimeType, int maxInputSize, long durationUs,
|
public static MediaFormat createVideoFormat(String mimeType, int maxInputSize, long durationUs,
|
||||||
int width, int height, float pixelWidthHeightRatio, List<byte[]> initializationData) {
|
int width, int height, float pixelWidthHeightRatio, List<byte[]> initializationData) {
|
||||||
return new MediaFormat(mimeType, maxInputSize, durationUs, width, height, pixelWidthHeightRatio,
|
return new MediaFormat(mimeType, maxInputSize, durationUs, width, height, pixelWidthHeightRatio,
|
||||||
NO_VALUE, NO_VALUE, initializationData);
|
NO_VALUE, NO_VALUE, null, initializationData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MediaFormat createAudioFormat(String mimeType, int maxInputSize, int channelCount,
|
public static MediaFormat createAudioFormat(String mimeType, int maxInputSize, int channelCount,
|
||||||
@ -90,15 +83,16 @@ public final class MediaFormat {
|
|||||||
public static MediaFormat createAudioFormat(String mimeType, int maxInputSize, long durationUs,
|
public static MediaFormat createAudioFormat(String mimeType, int maxInputSize, long durationUs,
|
||||||
int channelCount, int sampleRate, List<byte[]> initializationData) {
|
int channelCount, int sampleRate, List<byte[]> initializationData) {
|
||||||
return new MediaFormat(mimeType, maxInputSize, durationUs, NO_VALUE, NO_VALUE, NO_VALUE,
|
return new MediaFormat(mimeType, maxInputSize, durationUs, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||||
channelCount, sampleRate, initializationData);
|
channelCount, sampleRate, null, initializationData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MediaFormat createTextFormat(String mimeType) {
|
public static MediaFormat createTextFormat(String mimeType, String language) {
|
||||||
return createTextFormat(mimeType, C.UNKNOWN_TIME_US);
|
return createTextFormat(mimeType, language, C.UNKNOWN_TIME_US);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MediaFormat createTextFormat(String mimeType, long durationUs) {
|
public static MediaFormat createTextFormat(String mimeType, String language, long durationUs) {
|
||||||
return createFormatForMimeType(mimeType, durationUs);
|
return new MediaFormat(mimeType, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||||
|
NO_VALUE, NO_VALUE, language, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MediaFormat createFormatForMimeType(String mimeType) {
|
public static MediaFormat createFormatForMimeType(String mimeType) {
|
||||||
@ -107,35 +101,11 @@ public final class MediaFormat {
|
|||||||
|
|
||||||
public static MediaFormat createFormatForMimeType(String mimeType, long durationUs) {
|
public static MediaFormat createFormatForMimeType(String mimeType, long durationUs) {
|
||||||
return new MediaFormat(mimeType, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, NO_VALUE,
|
return new MediaFormat(mimeType, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, NO_VALUE,
|
||||||
NO_VALUE, NO_VALUE, null);
|
NO_VALUE, NO_VALUE, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(16)
|
/* package */ MediaFormat(String mimeType, int maxInputSize, long durationUs, int width,
|
||||||
private MediaFormat(android.media.MediaFormat format) {
|
int height, float pixelWidthHeightRatio, int channelCount, int sampleRate, String language,
|
||||||
this.frameworkMediaFormat = format;
|
|
||||||
mimeType = format.getString(android.media.MediaFormat.KEY_MIME);
|
|
||||||
maxInputSize = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE);
|
|
||||||
width = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_WIDTH);
|
|
||||||
height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT);
|
|
||||||
channelCount = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT);
|
|
||||||
sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE);
|
|
||||||
pixelWidthHeightRatio = getOptionalFloatV16(format, KEY_PIXEL_WIDTH_HEIGHT_RATIO);
|
|
||||||
initializationData = new ArrayList<>();
|
|
||||||
for (int i = 0; format.containsKey("csd-" + i); i++) {
|
|
||||||
ByteBuffer buffer = format.getByteBuffer("csd-" + i);
|
|
||||||
byte[] data = new byte[buffer.limit()];
|
|
||||||
buffer.get(data);
|
|
||||||
initializationData.add(data);
|
|
||||||
buffer.flip();
|
|
||||||
}
|
|
||||||
durationUs = format.containsKey(android.media.MediaFormat.KEY_DURATION)
|
|
||||||
? format.getLong(android.media.MediaFormat.KEY_DURATION) : C.UNKNOWN_TIME_US;
|
|
||||||
maxWidth = NO_VALUE;
|
|
||||||
maxHeight = NO_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MediaFormat(String mimeType, int maxInputSize, long durationUs, int width, int height,
|
|
||||||
float pixelWidthHeightRatio, int channelCount, int sampleRate,
|
|
||||||
List<byte[]> initializationData) {
|
List<byte[]> initializationData) {
|
||||||
this.mimeType = mimeType;
|
this.mimeType = mimeType;
|
||||||
this.maxInputSize = maxInputSize;
|
this.maxInputSize = maxInputSize;
|
||||||
@ -145,17 +115,20 @@ public final class MediaFormat {
|
|||||||
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
|
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
|
||||||
this.channelCount = channelCount;
|
this.channelCount = channelCount;
|
||||||
this.sampleRate = sampleRate;
|
this.sampleRate = sampleRate;
|
||||||
|
this.language = language;
|
||||||
this.initializationData = initializationData == null ? Collections.<byte[]>emptyList()
|
this.initializationData = initializationData == null ? Collections.<byte[]>emptyList()
|
||||||
: initializationData;
|
: initializationData;
|
||||||
maxWidth = NO_VALUE;
|
maxWidth = NO_VALUE;
|
||||||
maxHeight = NO_VALUE;
|
maxHeight = NO_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
public void setMaxVideoDimensions(int maxWidth, int maxHeight) {
|
public void setMaxVideoDimensions(int maxWidth, int maxHeight) {
|
||||||
this.maxWidth = maxWidth;
|
this.maxWidth = maxWidth;
|
||||||
this.maxHeight = maxHeight;
|
this.maxHeight = maxHeight;
|
||||||
if (frameworkMediaFormat != null) {
|
if (frameworkMediaFormat != null) {
|
||||||
maybeSetMaxDimensionsV16(frameworkMediaFormat);
|
maybeSetIntegerV16(frameworkMediaFormat, android.media.MediaFormat.KEY_MAX_WIDTH, maxWidth);
|
||||||
|
maybeSetIntegerV16(frameworkMediaFormat, android.media.MediaFormat.KEY_MAX_HEIGHT, maxHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,6 +140,41 @@ public final class MediaFormat {
|
|||||||
return maxHeight;
|
return 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, 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)));
|
||||||
|
}
|
||||||
|
if (durationUs != C.UNKNOWN_TIME_US) {
|
||||||
|
format.setLong(android.media.MediaFormat.KEY_DURATION, durationUs);
|
||||||
|
}
|
||||||
|
frameworkMediaFormat = format;
|
||||||
|
}
|
||||||
|
return frameworkMediaFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MediaFormat(" + mimeType + ", " + maxInputSize + ", " + width + ", " + height + ", "
|
||||||
|
+ pixelWidthHeightRatio + ", " + channelCount + ", " + sampleRate + ", " + durationUs + ", "
|
||||||
|
+ maxWidth + ", " + maxHeight + ")";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
if (hashCode == 0) {
|
if (hashCode == 0) {
|
||||||
@ -227,44 +235,12 @@ public final class MediaFormat {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "MediaFormat(" + mimeType + ", " + maxInputSize + ", " + width + ", " + height + ", "
|
|
||||||
+ pixelWidthHeightRatio + ", " + channelCount + ", " + sampleRate + ", " + durationUs + ", "
|
|
||||||
+ maxWidth + ", " + maxHeight + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A {@link MediaFormat} representation of this format.
|
|
||||||
*/
|
|
||||||
@TargetApi(16)
|
@TargetApi(16)
|
||||||
public final android.media.MediaFormat getFrameworkMediaFormatV16() {
|
private static final void maybeSetStringV16(android.media.MediaFormat format, String key,
|
||||||
if (frameworkMediaFormat == null) {
|
String value) {
|
||||||
android.media.MediaFormat format = new android.media.MediaFormat();
|
if (value != null) {
|
||||||
format.setString(android.media.MediaFormat.KEY_MIME, mimeType);
|
format.setString(key, value);
|
||||||
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, android.media.MediaFormat.KEY_CHANNEL_COUNT, channelCount);
|
|
||||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE, sampleRate);
|
|
||||||
maybeSetFloatV16(format, KEY_PIXEL_WIDTH_HEIGHT_RATIO, pixelWidthHeightRatio);
|
|
||||||
for (int i = 0; i < initializationData.size(); i++) {
|
|
||||||
format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i)));
|
|
||||||
}
|
}
|
||||||
if (durationUs != C.UNKNOWN_TIME_US) {
|
|
||||||
format.setLong(android.media.MediaFormat.KEY_DURATION, durationUs);
|
|
||||||
}
|
|
||||||
maybeSetMaxDimensionsV16(format);
|
|
||||||
frameworkMediaFormat = format;
|
|
||||||
}
|
|
||||||
return frameworkMediaFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
|
||||||
@TargetApi(16)
|
|
||||||
private final void maybeSetMaxDimensionsV16(android.media.MediaFormat format) {
|
|
||||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_WIDTH, maxWidth);
|
|
||||||
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_HEIGHT, maxHeight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(16)
|
@TargetApi(16)
|
||||||
@ -275,22 +251,4 @@ public final class MediaFormat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(16)
|
|
||||||
private static final void maybeSetFloatV16(android.media.MediaFormat format, String key,
|
|
||||||
float value) {
|
|
||||||
if (value != NO_VALUE) {
|
|
||||||
format.setFloat(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(16)
|
|
||||||
private static final int getOptionalIntegerV16(android.media.MediaFormat format, String key) {
|
|
||||||
return format.containsKey(key) ? format.getInteger(key) : NO_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(16)
|
|
||||||
private static final float getOptionalFloatV16(android.media.MediaFormat format, String key) {
|
|
||||||
return format.containsKey(key) ? format.getFloat(key) : NO_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -70,15 +70,6 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer {
|
|||||||
*/
|
*/
|
||||||
protected abstract boolean handlesTrack(TrackInfo trackInfo);
|
protected abstract boolean handlesTrack(TrackInfo trackInfo);
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked when a track is selected.
|
|
||||||
*
|
|
||||||
* @param trackInfo The selected track.
|
|
||||||
*/
|
|
||||||
protected void onTrackSelected(TrackInfo trackInfo) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onEnabled(int track, long positionUs, boolean joining)
|
protected void onEnabled(int track, long positionUs, boolean joining)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
|
@ -656,7 +656,8 @@ public class DashChunkSource implements ChunkSource {
|
|||||||
}
|
}
|
||||||
return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL,
|
return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL,
|
||||||
representation.format, startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment,
|
representation.format, startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment,
|
||||||
MediaFormat.createTextFormat(MimeTypes.TEXT_VTT), null, representationHolder.vttHeader);
|
MediaFormat.createTextFormat(MimeTypes.TEXT_VTT, representation.format.language), null,
|
||||||
|
representationHolder.vttHeader);
|
||||||
} else {
|
} else {
|
||||||
return new ContainerMediaChunk(dataSource, dataSpec, trigger, representation.format,
|
return new ContainerMediaChunk(dataSource, dataSpec, trigger, representation.format,
|
||||||
startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment, sampleOffsetUs,
|
startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment, sampleOffsetUs,
|
||||||
|
@ -62,10 +62,11 @@ import java.util.List;
|
|||||||
Atom.ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf)
|
Atom.ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf)
|
||||||
.getContainerAtomOfType(Atom.TYPE_stbl);
|
.getContainerAtomOfType(Atom.TYPE_stbl);
|
||||||
|
|
||||||
long mediaTimescale = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data);
|
Pair<Long, String> mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data);
|
||||||
StsdDataHolder stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, durationUs);
|
StsdDataHolder stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, durationUs,
|
||||||
|
mdhdData.second);
|
||||||
return stsdData.mediaFormat == null ? null
|
return stsdData.mediaFormat == null ? null
|
||||||
: new Track(id, trackType, mediaTimescale, durationUs, stsdData.mediaFormat,
|
: new Track(id, trackType, mdhdData.first, durationUs, stsdData.mediaFormat,
|
||||||
stsdData.trackEncryptionBoxes, stsdData.nalUnitLengthFieldLength);
|
stsdData.trackEncryptionBoxes, stsdData.nalUnitLengthFieldLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,18 +315,25 @@ import java.util.List;
|
|||||||
* Parses an mdhd atom (defined in 14496-12).
|
* Parses an mdhd atom (defined in 14496-12).
|
||||||
*
|
*
|
||||||
* @param mdhd The mdhd atom to parse.
|
* @param mdhd The mdhd atom to parse.
|
||||||
* @return The media timescale, defined as the number of time units that pass in one second.
|
* @return A pair consisting of the media timescale defined as the number of time units that pass
|
||||||
|
* in one second, and the language code.
|
||||||
*/
|
*/
|
||||||
private static long parseMdhd(ParsableByteArray mdhd) {
|
private static Pair<Long, String> parseMdhd(ParsableByteArray mdhd) {
|
||||||
mdhd.setPosition(Atom.HEADER_SIZE);
|
mdhd.setPosition(Atom.HEADER_SIZE);
|
||||||
int fullAtom = mdhd.readInt();
|
int fullAtom = mdhd.readInt();
|
||||||
int version = Atom.parseFullAtomVersion(fullAtom);
|
int version = Atom.parseFullAtomVersion(fullAtom);
|
||||||
|
|
||||||
mdhd.skipBytes(version == 0 ? 8 : 16);
|
mdhd.skipBytes(version == 0 ? 8 : 16);
|
||||||
return mdhd.readUnsignedInt();
|
long timescale = mdhd.readUnsignedInt();
|
||||||
|
mdhd.skipBytes(version == 0 ? 4 : 8);
|
||||||
|
int languageCode = mdhd.readUnsignedShort();
|
||||||
|
String language = "" + (char) (((languageCode >> 10) & 0x1F) + 0x60)
|
||||||
|
+ (char) (((languageCode >> 5) & 0x1F) + 0x60)
|
||||||
|
+ (char) (((languageCode) & 0x1F) + 0x60);
|
||||||
|
return Pair.create(timescale, language);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static StsdDataHolder parseStsd(ParsableByteArray stsd, long durationUs) {
|
private static StsdDataHolder parseStsd(ParsableByteArray stsd, long durationUs,
|
||||||
|
String language) {
|
||||||
stsd.setPosition(Atom.FULL_HEADER_SIZE);
|
stsd.setPosition(Atom.FULL_HEADER_SIZE);
|
||||||
int numberOfEntries = stsd.readInt();
|
int numberOfEntries = stsd.readInt();
|
||||||
StsdDataHolder holder = new StsdDataHolder(numberOfEntries);
|
StsdDataHolder holder = new StsdDataHolder(numberOfEntries);
|
||||||
@ -344,9 +352,11 @@ import java.util.List;
|
|||||||
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, durationUs,
|
parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, durationUs,
|
||||||
holder, i);
|
holder, i);
|
||||||
} else if (childAtomType == Atom.TYPE_TTML) {
|
} else if (childAtomType == Atom.TYPE_TTML) {
|
||||||
holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TTML, durationUs);
|
holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TTML, language,
|
||||||
|
durationUs);
|
||||||
} else if (childAtomType == Atom.TYPE_tx3g) {
|
} else if (childAtomType == Atom.TYPE_tx3g) {
|
||||||
holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TX3G, durationUs);
|
holder.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TX3G, language,
|
||||||
|
durationUs);
|
||||||
}
|
}
|
||||||
stsd.setPosition(childStartPosition + childAtomSize);
|
stsd.setPosition(childStartPosition + childAtomSize);
|
||||||
}
|
}
|
||||||
|
@ -35,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.createTextFormat(MimeTypes.APPLICATION_ID3));
|
output.format(MediaFormat.createFormatForMimeType(MimeTypes.APPLICATION_ID3));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -32,7 +32,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
|
|
||||||
public SeiReader(TrackOutput output) {
|
public SeiReader(TrackOutput output) {
|
||||||
super(output);
|
super(output);
|
||||||
output.format(MediaFormat.createTextFormat(MimeTypes.APPLICATION_EIA608));
|
output.format(MediaFormat.createTextFormat(MimeTypes.APPLICATION_EIA608, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -398,7 +398,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||||||
trackFormat.numChannels, trackFormat.audioSamplingRate, csd);
|
trackFormat.numChannels, trackFormat.audioSamplingRate, csd);
|
||||||
return format;
|
return format;
|
||||||
} else if (streamElement.type == StreamElement.TYPE_TEXT) {
|
} else if (streamElement.type == StreamElement.TYPE_TEXT) {
|
||||||
return MediaFormat.createTextFormat(trackFormat.mimeType);
|
return MediaFormat.createTextFormat(trackFormat.mimeType, trackFormat.language);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -151,29 +151,14 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean handlesTrack(TrackInfo trackInfo) {
|
protected boolean handlesTrack(TrackInfo trackInfo) {
|
||||||
for (int i = 0; i < subtitleParsers.length; i++) {
|
return getParserIndex(trackInfo) != -1;
|
||||||
if (subtitleParsers[i].canParse(trackInfo.mimeType)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onTrackSelected(TrackInfo trackInfo) {
|
|
||||||
for (int i = 0; i < subtitleParsers.length; i++) {
|
|
||||||
if (subtitleParsers[i].canParse(trackInfo.mimeType)) {
|
|
||||||
parserIndex = i;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("Invalid track selected");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onEnabled(int track, long positionUs, boolean joining)
|
protected void onEnabled(int track, long positionUs, boolean joining)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
super.onEnabled(track, positionUs, joining);
|
super.onEnabled(track, positionUs, joining);
|
||||||
|
parserIndex = getParserIndex(getTrackInfo(track));
|
||||||
parserThread = new HandlerThread("textParser");
|
parserThread = new HandlerThread("textParser");
|
||||||
parserThread.start();
|
parserThread.start();
|
||||||
parserHelper = new SubtitleParserHelper(parserThread.getLooper(), subtitleParsers[parserIndex]);
|
parserHelper = new SubtitleParserHelper(parserThread.getLooper(), subtitleParsers[parserIndex]);
|
||||||
@ -311,4 +296,13 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
|
|||||||
textRenderer.onCues(cues);
|
textRenderer.onCues(cues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getParserIndex(TrackInfo trackInfo) {
|
||||||
|
for (int i = 0; i < subtitleParsers.length; i++) {
|
||||||
|
if (subtitleParsers[i].canParse(trackInfo.mimeType)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user