mirror of
https://github.com/samsonjs/media.git
synced 2026-04-06 11:25:46 +00:00
Merge branch 'dev' of github.com:/google/ExoPlayer into mediaformat-id-dash
Syncing my fork
This commit is contained in:
commit
1d31521710
46 changed files with 976 additions and 312 deletions
|
|
@ -1,8 +1,18 @@
|
|||
# Release notes #
|
||||
|
||||
### Current dev branch (from r1.5.0) ###
|
||||
### Current dev branch (from r1.5.1) ###
|
||||
|
||||
* [Nothing yet]
|
||||
|
||||
### r1.5.0 ###
|
||||
|
||||
* Enable smooth frame release by default.
|
||||
* Added OkHttpDataSource extension.
|
||||
* AndroidTV: Correctly detect 4K display size on Bravia devices.
|
||||
* FMP4: Handle non-sample data in mdat boxes.
|
||||
* TTML: Fix parsing of some colors on Jellybean.
|
||||
* SmoothStreaming: Ignore tfdt boxes.
|
||||
* Misc bug fixes.
|
||||
|
||||
### r1.5.0 ###
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ buildscript {
|
|||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.2.3'
|
||||
classpath 'com.novoda:bintray-release:0.3.2'
|
||||
classpath 'com.novoda:bintray-release:0.3.4'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.exoplayer.demo"
|
||||
android:versionCode="1500"
|
||||
android:versionName="1.5.0"
|
||||
android:versionCode="1501"
|
||||
android:versionName="1.5.1"
|
||||
android:theme="@style/RootTheme">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
|
@ -41,9 +41,20 @@
|
|||
</activity>
|
||||
|
||||
<activity android:name="com.google.android.exoplayer.demo.PlayerActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:launchMode="singleInstance"
|
||||
android:label="@string/application_name"
|
||||
android:theme="@style/PlayerTheme"/>
|
||||
android:theme="@style/PlayerTheme">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.exoplayer.demo.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:scheme="content"/>
|
||||
<data android:scheme="asset"/>
|
||||
<data android:scheme="file"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
|
|
|
|||
|
|
@ -78,13 +78,19 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||
DemoPlayer.Listener, DemoPlayer.CaptionListener, DemoPlayer.Id3MetadataListener,
|
||||
AudioCapabilitiesReceiver.Listener {
|
||||
|
||||
// For use within demo app code.
|
||||
public static final String CONTENT_ID_EXTRA = "content_id";
|
||||
public static final String CONTENT_TYPE_EXTRA = "content_type";
|
||||
public static final int TYPE_DASH = 0;
|
||||
public static final int TYPE_SS = 1;
|
||||
public static final int TYPE_HLS = 2;
|
||||
public static final int TYPE_OTHER = 3;
|
||||
|
||||
public static final String CONTENT_TYPE_EXTRA = "content_type";
|
||||
public static final String CONTENT_ID_EXTRA = "content_id";
|
||||
// For use when launching the demo app using adb.
|
||||
private static final String CONTENT_EXT_EXTRA = "type";
|
||||
private static final String EXT_DASH = ".mpd";
|
||||
private static final String EXT_SS = ".ism";
|
||||
private static final String EXT_HLS = ".m3u8";
|
||||
|
||||
private static final String TAG = "PlayerActivity";
|
||||
private static final int MENU_GROUP_TRACKS = 1;
|
||||
|
|
@ -129,11 +135,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
contentUri = intent.getData();
|
||||
contentType = intent.getIntExtra(CONTENT_TYPE_EXTRA, -1);
|
||||
contentId = intent.getStringExtra(CONTENT_ID_EXTRA);
|
||||
|
||||
setContentView(R.layout.player_activity);
|
||||
View root = findViewById(R.id.root);
|
||||
root.setOnTouchListener(new OnTouchListener() {
|
||||
|
|
@ -150,7 +151,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||
root.setOnKeyListener(new OnKeyListener() {
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE
|
||||
|| keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
return false;
|
||||
}
|
||||
return mediaController.dispatchKeyEvent(event);
|
||||
|
|
@ -185,9 +187,21 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||
audioCapabilitiesReceiver.register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewIntent(Intent intent) {
|
||||
releasePlayer();
|
||||
playerPosition = 0;
|
||||
setIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Intent intent = getIntent();
|
||||
contentUri = intent.getData();
|
||||
contentType = intent.getIntExtra(CONTENT_TYPE_EXTRA,
|
||||
inferContentType(contentUri, intent.getStringExtra(CONTENT_EXT_EXTRA)));
|
||||
contentId = intent.getStringExtra(CONTENT_ID_EXTRA);
|
||||
configureSubtitleView();
|
||||
if (player == null) {
|
||||
preparePlayer(true);
|
||||
|
|
@ -597,4 +611,28 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||
return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a best guess to infer the type from a media {@link Uri} and an optional overriding file
|
||||
* extension.
|
||||
*
|
||||
* @param uri The {@link Uri} of the media.
|
||||
* @param fileExtension An overriding file extension.
|
||||
* @return The inferred type.
|
||||
*/
|
||||
private static int inferContentType(Uri uri, String fileExtension) {
|
||||
String lastPathSegment = !TextUtils.isEmpty(fileExtension) ? "." + fileExtension
|
||||
: uri.getLastPathSegment();
|
||||
if (lastPathSegment == null) {
|
||||
return TYPE_OTHER;
|
||||
} else if (lastPathSegment.endsWith(EXT_DASH)) {
|
||||
return TYPE_DASH;
|
||||
} else if (lastPathSegment.endsWith(EXT_SS)) {
|
||||
return TYPE_SS;
|
||||
} else if (lastPathSegment.endsWith(EXT_HLS)) {
|
||||
return TYPE_HLS;
|
||||
} else {
|
||||
return TYPE_OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,8 +219,8 @@ public class DashRendererBuilder implements RendererBuilder {
|
|||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
||||
DemoPlayer.TYPE_VIDEO);
|
||||
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
|
||||
drmSessionManager, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null,
|
||||
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, videoSampleSource,
|
||||
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, drmSessionManager, true,
|
||||
mainHandler, player, 50);
|
||||
|
||||
// Build the audio renderer.
|
||||
|
|
|
|||
|
|
@ -61,8 +61,8 @@ public class ExtractorRendererBuilder implements RendererBuilder {
|
|||
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||
ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, allocator,
|
||||
BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE);
|
||||
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
|
||||
null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, player.getMainHandler(),
|
||||
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
|
||||
sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, player.getMainHandler(),
|
||||
player, 50);
|
||||
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
|
||||
null, true, player.getMainHandler(), player, AudioCapabilities.getCapabilities(context));
|
||||
|
|
|
|||
|
|
@ -148,8 +148,8 @@ public class HlsRendererBuilder implements RendererBuilder {
|
|||
variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
|
||||
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl,
|
||||
BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
|
||||
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
|
||||
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50);
|
||||
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
|
||||
sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50);
|
||||
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
|
||||
null, true, player.getMainHandler(), player, AudioCapabilities.getCapabilities(context));
|
||||
MetadataTrackRenderer<Map<String, Object>> id3Renderer = new MetadataTrackRenderer<>(
|
||||
|
|
|
|||
|
|
@ -163,9 +163,9 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder {
|
|||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
||||
DemoPlayer.TYPE_VIDEO);
|
||||
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
|
||||
drmSessionManager, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null,
|
||||
mainHandler, player, 50);
|
||||
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, videoSampleSource,
|
||||
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, drmSessionManager, true, mainHandler,
|
||||
player, 50);
|
||||
|
||||
// Build the audio renderer.
|
||||
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@
|
|||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.google.android.exoplayer.demo.webm"
|
||||
android:versionCode="1500"
|
||||
android:versionName="1.5.0"
|
||||
package="com.google.android.exoplayer.demo.vp9opus"
|
||||
android:versionCode="1501"
|
||||
android:versionName="1.5.1"
|
||||
android:theme="@style/RootTheme">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
android:allowBackup="false"
|
||||
android:icon="@drawable/ic_launcher">
|
||||
|
||||
<activity android:name="com.google.android.exoplayer.demo.webm.SampleChooserActivity"
|
||||
<activity android:name="com.google.android.exoplayer.demo.vp9opus.SampleChooserActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboardHidden">
|
||||
<intent-filter>
|
||||
|
|
@ -44,12 +44,12 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name="com.google.android.exoplayer.demo.webm.VideoPlayer"
|
||||
<activity android:name="com.google.android.exoplayer.demo.vp9opus.VideoPlayer"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/PlayerTheme"/>
|
||||
|
||||
<activity android:name="com.google.android.exoplayer.demo.webm.FilePickerActivity"
|
||||
<activity android:name="com.google.android.exoplayer.demo.vp9opus.FilePickerActivity"
|
||||
android:theme="@android:style/Theme.Dialog"/>
|
||||
|
||||
</application>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.webm;
|
||||
package com.google.android.exoplayer.demo.vp9opus;
|
||||
|
||||
import com.google.android.exoplayer.DefaultLoadControl;
|
||||
import com.google.android.exoplayer.LoadControl;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.webm;
|
||||
package com.google.android.exoplayer.demo.vp9opus;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ListActivity;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.webm;
|
||||
package com.google.android.exoplayer.demo.vp9opus;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.webm;
|
||||
package com.google.android.exoplayer.demo.vp9opus;
|
||||
|
||||
import com.google.android.exoplayer.AspectRatioFrameLayout;
|
||||
import com.google.android.exoplayer.ExoPlaybackException;
|
||||
|
|
@ -77,7 +77,7 @@ publish {
|
|||
userOrg = 'google'
|
||||
groupId = 'com.google.android.exoplayer'
|
||||
artifactId = 'exoplayer'
|
||||
version = 'r1.5.0'
|
||||
version = 'r1.5.1'
|
||||
description = 'The ExoPlayer library.'
|
||||
website = 'https://github.com/google/ExoPlayer'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,6 +195,11 @@ public class DefaultEbmlReaderTest extends TestCase {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLevel1Element(int id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMasterElement(int id, long contentPosition, long contentSize) {
|
||||
events.add(formatEvent(id, "start contentPosition=" + contentPosition
|
||||
|
|
|
|||
|
|
@ -95,17 +95,26 @@ public class VarintReaderTest extends TestCase {
|
|||
int bytesRead = input.read(new byte[1], 0, 1);
|
||||
assertEquals(1, bytesRead);
|
||||
// End of input allowed.
|
||||
long result = reader.readUnsignedVarint(input, true, false);
|
||||
assertEquals(-1, result);
|
||||
long result = reader.readUnsignedVarint(input, true, false, 8);
|
||||
assertEquals(C.RESULT_END_OF_INPUT, result);
|
||||
// End of input not allowed.
|
||||
try {
|
||||
reader.readUnsignedVarint(input, false, false);
|
||||
reader.readUnsignedVarint(input, false, false, 8);
|
||||
fail();
|
||||
} catch (EOFException e) {
|
||||
// Expected.
|
||||
}
|
||||
}
|
||||
|
||||
public void testReadVarintExceedsMaximumAllowedLength() throws IOException, InterruptedException {
|
||||
VarintReader reader = new VarintReader();
|
||||
DataSource dataSource = buildDataSource(DATA_8_BYTE_0);
|
||||
dataSource.open(new DataSpec(Uri.parse(TEST_URI)));
|
||||
ExtractorInput input = new DefaultExtractorInput(dataSource, 0, C.LENGTH_UNBOUNDED);
|
||||
long result = reader.readUnsignedVarint(input, false, true, 4);
|
||||
assertEquals(C.RESULT_MAX_LENGTH_EXCEEDED, result);
|
||||
}
|
||||
|
||||
public void testReadVarint() throws IOException, InterruptedException {
|
||||
VarintReader reader = new VarintReader();
|
||||
testReadVarint(reader, true, DATA_1_BYTE_0, 1, 0);
|
||||
|
|
@ -183,7 +192,7 @@ public class VarintReaderTest extends TestCase {
|
|||
DataSource dataSource = buildDataSource(data);
|
||||
dataSource.open(new DataSpec(Uri.parse(TEST_URI)));
|
||||
ExtractorInput input = new DefaultExtractorInput(dataSource, 0, C.LENGTH_UNBOUNDED);
|
||||
long result = reader.readUnsignedVarint(input, false, removeMask);
|
||||
long result = reader.readUnsignedVarint(input, false, removeMask, 8);
|
||||
assertEquals(expectedLength, input.getPosition());
|
||||
assertEquals(expectedValue, result);
|
||||
}
|
||||
|
|
@ -198,7 +207,7 @@ public class VarintReaderTest extends TestCase {
|
|||
dataSource.open(new DataSpec(Uri.parse(TEST_URI), position, C.LENGTH_UNBOUNDED, null));
|
||||
input = new DefaultExtractorInput(dataSource, position, C.LENGTH_UNBOUNDED);
|
||||
try {
|
||||
result = reader.readUnsignedVarint(input, false, removeMask);
|
||||
result = reader.readUnsignedVarint(input, false, removeMask, 8);
|
||||
position = input.getPosition();
|
||||
} catch (IOException e) {
|
||||
// Expected. We'll try again from the position that the input was advanced to.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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.text.ttml;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
/**
|
||||
* Unit test for <code>TtmlColorParser</code>.
|
||||
*/
|
||||
public class TtmlColorParserTest extends InstrumentationTestCase {
|
||||
|
||||
public void testHexCodeParsing() {
|
||||
assertEquals(Color.WHITE, TtmlColorParser.parseColor("#ffffff"));
|
||||
assertEquals(Color.WHITE, TtmlColorParser.parseColor("#ffffffff"));
|
||||
assertEquals(Color.parseColor("#00ffffff"), TtmlColorParser.parseColor("#00ffffff"));
|
||||
assertEquals(Color.parseColor("#12341234"), TtmlColorParser.parseColor("#12341234"));
|
||||
}
|
||||
|
||||
public void testColorNameParsing() {
|
||||
assertEquals(TtmlColorParser.TRANSPARENT, TtmlColorParser.parseColor("transparent"));
|
||||
assertEquals(TtmlColorParser.BLACK, TtmlColorParser.parseColor("black"));
|
||||
assertEquals(TtmlColorParser.GRAY, TtmlColorParser.parseColor("gray"));
|
||||
assertEquals(TtmlColorParser.SILVER, TtmlColorParser.parseColor("silver"));
|
||||
assertEquals(TtmlColorParser.WHITE, TtmlColorParser.parseColor("white"));
|
||||
assertEquals(TtmlColorParser.MAROON, TtmlColorParser.parseColor("maroon"));
|
||||
assertEquals(TtmlColorParser.RED, TtmlColorParser.parseColor("red"));
|
||||
assertEquals(TtmlColorParser.PURPLE, TtmlColorParser.parseColor("purple"));
|
||||
assertEquals(TtmlColorParser.FUCHSIA, TtmlColorParser.parseColor("fuchsia"));
|
||||
assertEquals(TtmlColorParser.MAGENTA, TtmlColorParser.parseColor("magenta"));
|
||||
assertEquals(TtmlColorParser.GREEN, TtmlColorParser.parseColor("green"));
|
||||
assertEquals(TtmlColorParser.LIME, TtmlColorParser.parseColor("lime"));
|
||||
assertEquals(TtmlColorParser.OLIVE, TtmlColorParser.parseColor("olive"));
|
||||
assertEquals(TtmlColorParser.YELLOW, TtmlColorParser.parseColor("yellow"));
|
||||
assertEquals(TtmlColorParser.NAVY, TtmlColorParser.parseColor("navy"));
|
||||
assertEquals(TtmlColorParser.BLUE, TtmlColorParser.parseColor("blue"));
|
||||
assertEquals(TtmlColorParser.TEAL, TtmlColorParser.parseColor("teal"));
|
||||
assertEquals(TtmlColorParser.AQUA, TtmlColorParser.parseColor("aqua"));
|
||||
assertEquals(TtmlColorParser.CYAN, TtmlColorParser.parseColor("cyan"));
|
||||
}
|
||||
|
||||
public void testParseUnknownColor() {
|
||||
try {
|
||||
TtmlColorParser.parseColor("colorOfAnElectron");
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testParseNull() {
|
||||
try {
|
||||
TtmlColorParser.parseColor(null);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testParseEmpty() {
|
||||
try {
|
||||
TtmlColorParser.parseColor("");
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testRgbColorParsing() {
|
||||
assertEquals(Color.WHITE, TtmlColorParser.parseColor("rgb(255,255,255)"));
|
||||
// spaces do not matter
|
||||
assertEquals(Color.WHITE, TtmlColorParser.parseColor(" rgb ( 255, 255, 255)"));
|
||||
}
|
||||
|
||||
public void testRgbColorParsing_rgbValuesOutOfBounds() {
|
||||
int outOfBounds = TtmlColorParser.parseColor("rgb(999, 999, 999)");
|
||||
int color = Color.rgb(999, 999, 999);
|
||||
// behave like framework Color behaves
|
||||
assertEquals(color, outOfBounds);
|
||||
}
|
||||
|
||||
public void testRgbColorParsing_rgbValuesNegative() {
|
||||
try {
|
||||
TtmlColorParser.parseColor("rgb(-4, 55, 209)");
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testRgbaColorParsing() {
|
||||
assertEquals(Color.WHITE, TtmlColorParser.parseColor("rgba(255,255,255,0)"));
|
||||
assertEquals(Color.argb(0, 255, 255, 255), TtmlColorParser.parseColor("rgba(255,255,255,255)"));
|
||||
assertEquals(Color.BLACK, TtmlColorParser.parseColor("rgba(0, 0, 0, 0)"));
|
||||
assertEquals(Color.argb(0, 0, 0, 0), TtmlColorParser.parseColor("rgba(0, 0, 0, 255)"));
|
||||
assertEquals(Color.RED, TtmlColorParser.parseColor("rgba(255, 0, 0, 0)"));
|
||||
assertEquals(Color.argb(0, 255, 0, 0), TtmlColorParser.parseColor("rgba(255, 0, 0, 255)"));
|
||||
assertEquals(Color.argb(205, 255, 0, 0), TtmlColorParser.parseColor("rgba(255, 0, 0, 50)"));
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer.text.ttml;
|
|||
|
||||
import com.google.android.exoplayer.text.Cue;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.text.Layout;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
|
@ -67,8 +66,8 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
|||
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
|
||||
TtmlNode firstDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
|
||||
TtmlStyle firstPStyle = queryChildrenForTag(firstDiv, TtmlNode.TAG_P, 0).style;
|
||||
assertEquals(Color.parseColor("yellow"), firstPStyle.getColor());
|
||||
assertEquals(Color.parseColor("blue"), firstPStyle.getBackgroundColor());
|
||||
assertEquals(TtmlColorParser.parseColor("yellow"), firstPStyle.getColor());
|
||||
assertEquals(TtmlColorParser.parseColor("blue"), firstPStyle.getBackgroundColor());
|
||||
assertEquals("serif", firstPStyle.getFontFamily());
|
||||
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, firstPStyle.getStyle());
|
||||
assertTrue(firstPStyle.isUnderline());
|
||||
|
|
@ -78,24 +77,43 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
|||
TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
|
||||
assertEquals(4, subtitle.getEventTimeCount());
|
||||
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC,
|
||||
Color.CYAN, Color.parseColor("lime"), false, true, null);
|
||||
TtmlColorParser.CYAN, TtmlColorParser.parseColor("lime"), false, true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* regression test for devices on JellyBean where some named colors are not correctly defined
|
||||
* on framework level. Tests that <i>lime</i> resolves to <code>#FF00FF00</code> not
|
||||
* <code>#00FF00</code>.
|
||||
*
|
||||
* See: https://github.com/android/platform_frameworks_base/blob/jb-mr2-release/
|
||||
* graphics/java/android/graphics/Color.java#L414
|
||||
* https://github.com/android/platform_frameworks_base/blob/kitkat-mr2.2-release/
|
||||
* graphics/java/android/graphics/Color.java#L414
|
||||
*
|
||||
* @throws IOException thrown if reading subtitle file fails.
|
||||
*/
|
||||
public void testLime() throws IOException {
|
||||
TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
|
||||
assertEquals(4, subtitle.getEventTimeCount());
|
||||
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC,
|
||||
TtmlColorParser.CYAN, TtmlColorParser.LIME, false, true, null);
|
||||
}
|
||||
|
||||
public void testInheritGlobalStyle() throws IOException {
|
||||
TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_TTML_FILE);
|
||||
assertEquals(2, subtitle.getEventTimeCount());
|
||||
assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC,
|
||||
Color.BLUE, Color.YELLOW, true, false, null);
|
||||
TtmlColorParser.BLUE, TtmlColorParser.YELLOW, true, false, null);
|
||||
}
|
||||
|
||||
public void testInheritGlobalStyleOverriddenByInlineAttributes() throws IOException {
|
||||
TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_OVERRIDE_TTML_FILE);
|
||||
assertEquals(4, subtitle.getEventTimeCount());
|
||||
|
||||
assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC, Color.BLUE,
|
||||
Color.YELLOW, true, false, null);
|
||||
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC, Color.RED,
|
||||
Color.YELLOW, true, false, null);
|
||||
assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC, TtmlColorParser.BLUE,
|
||||
TtmlColorParser.YELLOW, true, false, null);
|
||||
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC, TtmlColorParser.RED,
|
||||
TtmlColorParser.YELLOW, true, false, null);
|
||||
}
|
||||
|
||||
public void testInheritGlobalAndParent() throws IOException {
|
||||
|
|
@ -103,9 +121,10 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
|||
assertEquals(4, subtitle.getEventTimeCount());
|
||||
|
||||
assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_NORMAL,
|
||||
Color.RED, Color.parseColor("lime"), false, true, Layout.Alignment.ALIGN_CENTER);
|
||||
TtmlColorParser.RED, TtmlColorParser.parseColor("lime"), false, true,
|
||||
Layout.Alignment.ALIGN_CENTER);
|
||||
assertSpans(subtitle, 20, "text 2", "serif", TtmlStyle.STYLE_BOLD_ITALIC,
|
||||
Color.BLUE, Color.YELLOW, true, true, Layout.Alignment.ALIGN_CENTER);
|
||||
TtmlColorParser.BLUE, TtmlColorParser.YELLOW, true, true, Layout.Alignment.ALIGN_CENTER);
|
||||
}
|
||||
|
||||
public void testInheritMultipleStyles() throws IOException {
|
||||
|
|
@ -113,7 +132,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
|||
assertEquals(12, subtitle.getEventTimeCount());
|
||||
|
||||
assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC,
|
||||
Color.BLUE, Color.YELLOW, false, true, null);
|
||||
TtmlColorParser.BLUE, TtmlColorParser.YELLOW, false, true, null);
|
||||
}
|
||||
|
||||
public void testInheritMultipleStylesWithoutLocalAttributes() throws IOException {
|
||||
|
|
@ -121,7 +140,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
|||
assertEquals(12, subtitle.getEventTimeCount());
|
||||
|
||||
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC,
|
||||
Color.BLUE, Color.BLACK, false, true, null);
|
||||
TtmlColorParser.BLUE, TtmlColorParser.BLACK, false, true, null);
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -130,7 +149,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
|||
assertEquals(12, subtitle.getEventTimeCount());
|
||||
|
||||
assertSpans(subtitle, 30, "text 2.5", "sansSerifInline", TtmlStyle.STYLE_ITALIC,
|
||||
Color.RED, Color.YELLOW, true, true, null);
|
||||
TtmlColorParser.RED, TtmlColorParser.YELLOW, true, true, null);
|
||||
}
|
||||
|
||||
public void testEmptyStyleAttribute() throws IOException {
|
||||
|
|
@ -175,16 +194,16 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
|||
|
||||
TtmlStyle style = globalStyles.get("s2");
|
||||
assertEquals("serif", style.getFontFamily());
|
||||
assertEquals(Color.RED, style.getBackgroundColor());
|
||||
assertEquals(Color.BLACK, style.getColor());
|
||||
assertEquals(TtmlColorParser.RED, style.getBackgroundColor());
|
||||
assertEquals(TtmlColorParser.BLACK, style.getColor());
|
||||
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
||||
assertTrue(style.isLinethrough());
|
||||
|
||||
style = globalStyles.get("s3");
|
||||
// only difference: color must be RED
|
||||
assertEquals(Color.RED, style.getColor());
|
||||
assertEquals(TtmlColorParser.RED, style.getColor());
|
||||
assertEquals("serif", style.getFontFamily());
|
||||
assertEquals(Color.RED, style.getBackgroundColor());
|
||||
assertEquals(TtmlColorParser.RED, style.getBackgroundColor());
|
||||
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
|
||||
assertTrue(style.isLinethrough());
|
||||
}
|
||||
|
|
@ -224,8 +243,8 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
|||
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
|
||||
|
||||
assertNotNull(style);
|
||||
assertEquals(Color.BLACK, style.getBackgroundColor());
|
||||
assertEquals(Color.YELLOW, style.getColor());
|
||||
assertEquals(TtmlColorParser.BLACK, style.getBackgroundColor());
|
||||
assertEquals(TtmlColorParser.YELLOW, style.getColor());
|
||||
assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle());
|
||||
assertEquals("sansSerif", style.getFontFamily());
|
||||
assertFalse(style.isUnderline());
|
||||
|
|
@ -243,8 +262,8 @@ public final class TtmlParserTest extends InstrumentationTestCase {
|
|||
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
|
||||
|
||||
assertNotNull(style);
|
||||
assertEquals(Color.BLACK, style.getBackgroundColor());
|
||||
assertEquals(Color.YELLOW, style.getColor());
|
||||
assertEquals(TtmlColorParser.BLACK, style.getBackgroundColor());
|
||||
assertEquals(TtmlColorParser.YELLOW, style.getColor());
|
||||
assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle());
|
||||
assertEquals("sansSerif", style.getFontFamily());
|
||||
assertFalse(style.isUnderline());
|
||||
|
|
|
|||
|
|
@ -68,6 +68,11 @@ public final class C {
|
|||
@SuppressWarnings("InlinedApi")
|
||||
public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3;
|
||||
|
||||
// TODO: Switch these constants to use AudioFormat fields when the target API version is >= 23.
|
||||
// The inlined values here are for NVIDIA Shield devices which support DTS on earlier versions.
|
||||
public static final int ENCODING_DTS = 7;
|
||||
public static final int ENCODING_DTS_HD = 8;
|
||||
|
||||
/**
|
||||
* @see MediaExtractor#SAMPLE_FLAG_SYNC
|
||||
*/
|
||||
|
|
@ -90,6 +95,11 @@ public final class C {
|
|||
*/
|
||||
public static final int RESULT_END_OF_INPUT = -1;
|
||||
|
||||
/**
|
||||
* A return value for methods where the length of parsed data exceeds the maximum length allowed.
|
||||
*/
|
||||
public static final int RESULT_MAX_LENGTH_EXCEEDED = -2;
|
||||
|
||||
private C() {}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public final class ExoPlayerLibraryInfo {
|
|||
/**
|
||||
* The version of the library, expressed as a string.
|
||||
*/
|
||||
public static final String VERSION = "1.5.0";
|
||||
public static final String VERSION = "1.5.1";
|
||||
|
||||
/**
|
||||
* The version of the library, expressed as an integer.
|
||||
|
|
@ -31,7 +31,7 @@ public final class ExoPlayerLibraryInfo {
|
|||
* Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the
|
||||
* corresponding integer version 001002003.
|
||||
*/
|
||||
public static final int VERSION_INT = 001005000;
|
||||
public static final int VERSION_INT = 001005001;
|
||||
|
||||
/**
|
||||
* Whether the library was compiled with {@link com.google.android.exoplayer.util.Assertions}
|
||||
|
|
|
|||
|
|
@ -643,7 +643,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
|
|||
adaptiveReconfigurationBytes);
|
||||
codec.queueSecureInputBuffer(inputIndex, 0, cryptoInfo, presentationTimeUs, 0);
|
||||
} else {
|
||||
codec.queueInputBuffer(inputIndex, 0 , bufferSize, presentationTimeUs, 0);
|
||||
codec.queueInputBuffer(inputIndex, 0, bufferSize, presentationTimeUs, 0);
|
||||
}
|
||||
inputIndex = -1;
|
||||
codecHasQueuedBuffers = true;
|
||||
|
|
@ -922,11 +922,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
|
|||
* propagation incorrectly on the host device. False otherwise.
|
||||
*/
|
||||
private static boolean codecNeedsEosPropagationWorkaround(String name) {
|
||||
return Util.SDK_INT <= 17
|
||||
&& "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
|
||||
return Util.SDK_INT <= 17 && "OMX.rk.video_decoder.avc".equals(name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -94,8 +94,14 @@ public final class MediaCodecUtil {
|
|||
|
||||
/**
|
||||
* Returns the name of the best decoder and its capabilities for the given mimeType.
|
||||
*
|
||||
* @param mimeType The mime type.
|
||||
* @param secure Whether the decoder is required to support secure decryption. Always pass false
|
||||
* unless secure decryption really is required.
|
||||
* @return The name of the best decoder and its capabilities for the given mimeType, or null if
|
||||
* no decoder exists.
|
||||
*/
|
||||
private static synchronized Pair<String, CodecCapabilities> getMediaCodecInfo(
|
||||
public static synchronized Pair<String, CodecCapabilities> getMediaCodecInfo(
|
||||
String mimeType, boolean secure) throws DecoderQueryException {
|
||||
CodecKey key = new CodecKey(mimeType, secure);
|
||||
if (codecs.containsKey(key)) {
|
||||
|
|
@ -202,8 +208,10 @@ public final class MediaCodecUtil {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Work around an issue where the VP8 decoder on Samsung Galaxy S4 Mini does not render video.
|
||||
if (Util.SDK_INT <= 19 && Util.DEVICE != null && Util.DEVICE.startsWith("serrano")
|
||||
// Work around an issue where the VP8 decoder on Samsung Galaxy S3/S4 Mini does not render
|
||||
// video.
|
||||
if (Util.SDK_INT <= 19 && Util.DEVICE != null
|
||||
&& (Util.DEVICE.startsWith("d2") || Util.DEVICE.startsWith("serrano"))
|
||||
&& "samsung".equals(Util.MANUFACTURER) && name.equals("OMX.SEC.vp8.dec")) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.google.android.exoplayer.util.Util;
|
|||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCrypto;
|
||||
import android.os.Handler;
|
||||
|
|
@ -86,34 +87,6 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for fine-grained adjustment of frame release times.
|
||||
*/
|
||||
public interface FrameReleaseTimeHelper {
|
||||
|
||||
/**
|
||||
* Enables the helper.
|
||||
*/
|
||||
void enable();
|
||||
|
||||
/**
|
||||
* Disables the helper.
|
||||
*/
|
||||
void disable();
|
||||
|
||||
/**
|
||||
* Called to make a fine-grained adjustment to a frame release time.
|
||||
*
|
||||
* @param framePresentationTimeUs The frame's media presentation time, in microseconds.
|
||||
* @param unadjustedReleaseTimeNs The frame's unadjusted release time, in nanoseconds and in
|
||||
* the same time base as {@link System#nanoTime()}.
|
||||
* @return An adjusted release time for the frame, in nanoseconds and in the same time base as
|
||||
* {@link System#nanoTime()}.
|
||||
*/
|
||||
public long adjustReleaseTime(long framePresentationTimeUs, long unadjustedReleaseTimeNs);
|
||||
|
||||
}
|
||||
|
||||
// TODO: Use MediaFormat constants if these get exposed through the API. See [Internal: b/14127601].
|
||||
private static final String KEY_CROP_LEFT = "crop-left";
|
||||
private static final String KEY_CROP_RIGHT = "crop-right";
|
||||
|
|
@ -127,7 +100,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
|||
*/
|
||||
public static final int MSG_SET_SURFACE = 1;
|
||||
|
||||
private final FrameReleaseTimeHelper frameReleaseTimeHelper;
|
||||
private final VideoFrameReleaseTimeHelper frameReleaseTimeHelper;
|
||||
private final EventListener eventListener;
|
||||
private final long allowedJoiningTimeUs;
|
||||
private final int videoScalingMode;
|
||||
|
|
@ -152,64 +125,30 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
|||
private float lastReportedPixelWidthHeightRatio;
|
||||
|
||||
/**
|
||||
* @param context A context.
|
||||
* @param source The upstream source from which the renderer obtains samples.
|
||||
* @param videoScalingMode The scaling mode to pass to
|
||||
* {@link MediaCodec#setVideoScalingMode(int)}.
|
||||
*/
|
||||
public MediaCodecVideoTrackRenderer(SampleSource source, int videoScalingMode) {
|
||||
this(source, null, true, videoScalingMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param source The upstream source from which the renderer obtains samples.
|
||||
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
|
||||
* content is not required.
|
||||
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
|
||||
* For example a media file may start with a short clear region so as to allow playback to
|
||||
* begin in parallel with key acquisision. This parameter specifies whether the renderer is
|
||||
* permitted to play clear regions of encrypted media files before {@code drmSessionManager}
|
||||
* has obtained the keys necessary to decrypt encrypted regions of the media.
|
||||
* @param videoScalingMode The scaling mode to pass to
|
||||
* {@link MediaCodec#setVideoScalingMode(int)}.
|
||||
*/
|
||||
public MediaCodecVideoTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager,
|
||||
boolean playClearSamplesWithoutKeys, int videoScalingMode) {
|
||||
this(source, drmSessionManager, playClearSamplesWithoutKeys, videoScalingMode, 0);
|
||||
public MediaCodecVideoTrackRenderer(Context context, SampleSource source, int videoScalingMode) {
|
||||
this(context, source, videoScalingMode, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context A context.
|
||||
* @param source The upstream source from which the renderer obtains samples.
|
||||
* @param videoScalingMode The scaling mode to pass to
|
||||
* {@link MediaCodec#setVideoScalingMode(int)}.
|
||||
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
|
||||
* can attempt to seamlessly join an ongoing playback.
|
||||
*/
|
||||
public MediaCodecVideoTrackRenderer(SampleSource source, int videoScalingMode,
|
||||
public MediaCodecVideoTrackRenderer(Context context, SampleSource source, int videoScalingMode,
|
||||
long allowedJoiningTimeMs) {
|
||||
this(source, null, true, videoScalingMode, allowedJoiningTimeMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param source The upstream source from which the renderer obtains samples.
|
||||
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
|
||||
* content is not required.
|
||||
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
|
||||
* For example a media file may start with a short clear region so as to allow playback to
|
||||
* begin in parallel with key acquisision. This parameter specifies whether the renderer is
|
||||
* permitted to play clear regions of encrypted media files before {@code drmSessionManager}
|
||||
* has obtained the keys necessary to decrypt encrypted regions of the media.
|
||||
* @param videoScalingMode The scaling mode to pass to
|
||||
* {@link MediaCodec#setVideoScalingMode(int)}.
|
||||
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
|
||||
* can attempt to seamlessly join an ongoing playback.
|
||||
*/
|
||||
public MediaCodecVideoTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager,
|
||||
boolean playClearSamplesWithoutKeys, int videoScalingMode, long allowedJoiningTimeMs) {
|
||||
this(source, drmSessionManager, playClearSamplesWithoutKeys, videoScalingMode,
|
||||
allowedJoiningTimeMs, null, null, null, -1);
|
||||
this(context, source, videoScalingMode, allowedJoiningTimeMs, null, null, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context A context.
|
||||
* @param source The upstream source from which the renderer obtains samples.
|
||||
* @param videoScalingMode The scaling mode to pass to
|
||||
* {@link MediaCodec#setVideoScalingMode(int)}.
|
||||
|
|
@ -221,15 +160,20 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
|||
* @param maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between
|
||||
* invocations of {@link EventListener#onDroppedFrames(int, long)}.
|
||||
*/
|
||||
public MediaCodecVideoTrackRenderer(SampleSource source, int videoScalingMode,
|
||||
public MediaCodecVideoTrackRenderer(Context context, SampleSource source, int videoScalingMode,
|
||||
long allowedJoiningTimeMs, Handler eventHandler, EventListener eventListener,
|
||||
int maxDroppedFrameCountToNotify) {
|
||||
this(source, null, true, videoScalingMode, allowedJoiningTimeMs, null, eventHandler,
|
||||
this(context, source, videoScalingMode, allowedJoiningTimeMs, null, false, eventHandler,
|
||||
eventListener, maxDroppedFrameCountToNotify);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context A context.
|
||||
* @param source The upstream source from which the renderer obtains samples.
|
||||
* @param videoScalingMode The scaling mode to pass to
|
||||
* {@link MediaCodec#setVideoScalingMode(int)}.
|
||||
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
|
||||
* can attempt to seamlessly join an ongoing playback.
|
||||
* @param drmSessionManager For use with encrypted content. May be null if support for encrypted
|
||||
* content is not required.
|
||||
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
|
||||
|
|
@ -237,26 +181,20 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
|||
* begin in parallel with key acquisision. This parameter specifies whether the renderer is
|
||||
* permitted to play clear regions of encrypted media files before {@code drmSessionManager}
|
||||
* has obtained the keys necessary to decrypt encrypted regions of the media.
|
||||
* @param videoScalingMode The scaling mode to pass to
|
||||
* {@link MediaCodec#setVideoScalingMode(int)}.
|
||||
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
|
||||
* can attempt to seamlessly join an ongoing playback.
|
||||
* @param frameReleaseTimeHelper An optional helper to make fine-grained adjustments to frame
|
||||
* release times. May be null.
|
||||
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
|
||||
* null if delivery of events is not required.
|
||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
||||
* @param maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between
|
||||
* invocations of {@link EventListener#onDroppedFrames(int, long)}.
|
||||
*/
|
||||
public MediaCodecVideoTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager,
|
||||
boolean playClearSamplesWithoutKeys, int videoScalingMode, long allowedJoiningTimeMs,
|
||||
FrameReleaseTimeHelper frameReleaseTimeHelper, Handler eventHandler,
|
||||
EventListener eventListener, int maxDroppedFrameCountToNotify) {
|
||||
public MediaCodecVideoTrackRenderer(Context context, SampleSource source, int videoScalingMode,
|
||||
long allowedJoiningTimeMs, DrmSessionManager drmSessionManager,
|
||||
boolean playClearSamplesWithoutKeys, Handler eventHandler, EventListener eventListener,
|
||||
int maxDroppedFrameCountToNotify) {
|
||||
super(source, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, eventListener);
|
||||
this.frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(context);
|
||||
this.videoScalingMode = videoScalingMode;
|
||||
this.allowedJoiningTimeUs = allowedJoiningTimeMs * 1000;
|
||||
this.frameReleaseTimeHelper = frameReleaseTimeHelper;
|
||||
this.eventListener = eventListener;
|
||||
this.maxDroppedFrameCountToNotify = maxDroppedFrameCountToNotify;
|
||||
joiningDeadlineUs = -1;
|
||||
|
|
@ -285,9 +223,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
|||
if (joining && allowedJoiningTimeUs > 0) {
|
||||
joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs;
|
||||
}
|
||||
if (frameReleaseTimeHelper != null) {
|
||||
frameReleaseTimeHelper.enable();
|
||||
}
|
||||
frameReleaseTimeHelper.enable();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -340,9 +276,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
|||
lastReportedWidth = -1;
|
||||
lastReportedHeight = -1;
|
||||
lastReportedPixelWidthHeightRatio = -1;
|
||||
if (frameReleaseTimeHelper != null) {
|
||||
frameReleaseTimeHelper.disable();
|
||||
}
|
||||
frameReleaseTimeHelper.disable();
|
||||
super.onDisabled();
|
||||
}
|
||||
|
||||
|
|
@ -468,14 +402,9 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
|||
long unadjustedFrameReleaseTimeNs = systemTimeNs + (earlyUs * 1000);
|
||||
|
||||
// Apply a timestamp adjustment, if there is one.
|
||||
long adjustedReleaseTimeNs;
|
||||
if (frameReleaseTimeHelper != null) {
|
||||
adjustedReleaseTimeNs = frameReleaseTimeHelper.adjustReleaseTime(
|
||||
bufferInfo.presentationTimeUs, unadjustedFrameReleaseTimeNs);
|
||||
earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000;
|
||||
} else {
|
||||
adjustedReleaseTimeNs = unadjustedFrameReleaseTimeNs;
|
||||
}
|
||||
long adjustedReleaseTimeNs = frameReleaseTimeHelper.adjustReleaseTime(
|
||||
bufferInfo.presentationTimeUs, unadjustedFrameReleaseTimeNs);
|
||||
earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000;
|
||||
|
||||
if (earlyUs < -30000) {
|
||||
// We're more than 30ms late rendering the frame.
|
||||
|
|
@ -560,6 +489,11 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
|
|||
// Already set. The source of the format may know better, so do nothing.
|
||||
return;
|
||||
}
|
||||
if ("BRAVIA 4K 2015".equals(Util.MODEL)) {
|
||||
// The Sony BRAVIA 4k TV has input buffers that are too small for the calculated 4k video
|
||||
// maximum input size, so use the default value.
|
||||
return;
|
||||
}
|
||||
int maxHeight = format.getInteger(android.media.MediaFormat.KEY_HEIGHT);
|
||||
if (codecIsAdaptive && format.containsKey(android.media.MediaFormat.KEY_MAX_HEIGHT)) {
|
||||
maxHeight = Math.max(maxHeight, format.getInteger(android.media.MediaFormat.KEY_MAX_HEIGHT));
|
||||
|
|
|
|||
|
|
@ -209,6 +209,12 @@ public final class MediaFormat {
|
|||
this.maxHeight = maxHeight;
|
||||
}
|
||||
|
||||
public MediaFormat copyWithMaxInputSize(int maxInputSize) {
|
||||
return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height,
|
||||
rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language,
|
||||
subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight);
|
||||
}
|
||||
|
||||
public MediaFormat copyWithMaxVideoDimensions(int maxWidth, int maxHeight) {
|
||||
return new MediaFormat(id, trackId, mimeType, bitrate, maxInputSize, durationUs, width, height,
|
||||
rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language,
|
||||
|
|
|
|||
|
|
@ -15,17 +15,17 @@
|
|||
*/
|
||||
package com.google.android.exoplayer;
|
||||
|
||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer.FrameReleaseTimeHelper;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.view.Choreographer;
|
||||
import android.view.Choreographer.FrameCallback;
|
||||
import android.view.WindowManager;
|
||||
|
||||
/**
|
||||
* Makes a best effort to adjust frame release timestamps for a smoother visual result.
|
||||
*/
|
||||
@TargetApi(16)
|
||||
public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelper, FrameCallback {
|
||||
public final class VideoFrameReleaseTimeHelper implements FrameCallback {
|
||||
|
||||
private static final long CHOREOGRAPHER_SAMPLE_DELAY_MILLIS = 500;
|
||||
private static final long MAX_ALLOWED_DRIFT_NS = 20000000;
|
||||
|
|
@ -33,32 +33,45 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
|
|||
private static final long VSYNC_OFFSET_PERCENTAGE = 80;
|
||||
private static final int MIN_FRAMES_FOR_ADJUSTMENT = 6;
|
||||
|
||||
private final boolean usePrimaryDisplayVsync;
|
||||
private final boolean useDefaultDisplayVsync;
|
||||
private final long vsyncDurationNs;
|
||||
private final long vsyncOffsetNs;
|
||||
|
||||
private Choreographer choreographer;
|
||||
private long sampledVsyncTimeNs;
|
||||
|
||||
private long lastUnadjustedFrameTimeUs;
|
||||
private long lastFramePresentationTimeUs;
|
||||
private long adjustedLastFrameTimeNs;
|
||||
private long pendingAdjustedFrameTimeNs;
|
||||
|
||||
private boolean haveSync;
|
||||
private long syncReleaseTimeNs;
|
||||
private long syncFrameTimeNs;
|
||||
private int frameCount;
|
||||
private long syncUnadjustedReleaseTimeNs;
|
||||
private long syncFramePresentationTimeNs;
|
||||
private long frameCount;
|
||||
|
||||
/**
|
||||
* @param primaryDisplayRefreshRate The refresh rate of the default display.
|
||||
* @param usePrimaryDisplayVsync Whether to snap to the primary display vsync. May not be
|
||||
* suitable when rendering to secondary displays.
|
||||
* Constructs an instance that smoothes frame release but does not snap release to the default
|
||||
* display's vsync signal.
|
||||
*/
|
||||
public SmoothFrameReleaseTimeHelper(
|
||||
float primaryDisplayRefreshRate, boolean usePrimaryDisplayVsync) {
|
||||
this.usePrimaryDisplayVsync = usePrimaryDisplayVsync;
|
||||
if (usePrimaryDisplayVsync) {
|
||||
vsyncDurationNs = (long) (1000000000d / primaryDisplayRefreshRate);
|
||||
public VideoFrameReleaseTimeHelper() {
|
||||
this(-1, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance that smoothes frame release and snaps release to the default display's
|
||||
* vsync signal.
|
||||
*
|
||||
* @param context A context from which information about the default display can be retrieved.
|
||||
*/
|
||||
public VideoFrameReleaseTimeHelper(Context context) {
|
||||
this(getDefaultDisplayRefreshRate(context), true);
|
||||
}
|
||||
|
||||
private VideoFrameReleaseTimeHelper(float defaultDisplayRefreshRate,
|
||||
boolean useDefaultDisplayVsync) {
|
||||
this.useDefaultDisplayVsync = useDefaultDisplayVsync;
|
||||
if (useDefaultDisplayVsync) {
|
||||
vsyncDurationNs = (long) (1000000000d / defaultDisplayRefreshRate);
|
||||
vsyncOffsetNs = (vsyncDurationNs * VSYNC_OFFSET_PERCENTAGE) / 100;
|
||||
} else {
|
||||
vsyncDurationNs = -1;
|
||||
|
|
@ -66,19 +79,23 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Enables the helper.
|
||||
*/
|
||||
public void enable() {
|
||||
haveSync = false;
|
||||
if (usePrimaryDisplayVsync) {
|
||||
if (useDefaultDisplayVsync) {
|
||||
sampledVsyncTimeNs = 0;
|
||||
choreographer = Choreographer.getInstance();
|
||||
choreographer.postFrameCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Disables the helper.
|
||||
*/
|
||||
public void disable() {
|
||||
if (usePrimaryDisplayVsync) {
|
||||
if (useDefaultDisplayVsync) {
|
||||
choreographer.removeFrameCallback(this);
|
||||
choreographer = null;
|
||||
}
|
||||
|
|
@ -90,17 +107,25 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
|
|||
choreographer.postFrameCallbackDelayed(this, CHOREOGRAPHER_SAMPLE_DELAY_MILLIS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long adjustReleaseTime(long unadjustedFrameTimeUs, long unadjustedReleaseTimeNs) {
|
||||
long unadjustedFrameTimeNs = unadjustedFrameTimeUs * 1000;
|
||||
/**
|
||||
* Called to make a fine-grained adjustment to a frame release time.
|
||||
*
|
||||
* @param framePresentationTimeUs The frame's media presentation time, in microseconds.
|
||||
* @param unadjustedReleaseTimeNs The frame's unadjusted release time, in nanoseconds and in
|
||||
* the same time base as {@link System#nanoTime()}.
|
||||
* @return An adjusted release time for the frame, in nanoseconds and in the same time base as
|
||||
* {@link System#nanoTime()}.
|
||||
*/
|
||||
public long adjustReleaseTime(long framePresentationTimeUs, long unadjustedReleaseTimeNs) {
|
||||
long framePresentationTimeNs = framePresentationTimeUs * 1000;
|
||||
|
||||
// Until we know better, the adjustment will be a no-op.
|
||||
long adjustedFrameTimeNs = unadjustedFrameTimeNs;
|
||||
long adjustedFrameTimeNs = framePresentationTimeNs;
|
||||
long adjustedReleaseTimeNs = unadjustedReleaseTimeNs;
|
||||
|
||||
if (haveSync) {
|
||||
// See if we've advanced to the next frame.
|
||||
if (unadjustedFrameTimeUs != lastUnadjustedFrameTimeUs) {
|
||||
if (framePresentationTimeUs != lastFramePresentationTimeUs) {
|
||||
frameCount++;
|
||||
adjustedLastFrameTimeNs = pendingAdjustedFrameTimeNs;
|
||||
}
|
||||
|
|
@ -109,20 +134,22 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
|
|||
// Calculate the average frame time across all the frames we've seen since the last sync.
|
||||
// This will typically give us a frame rate at a finer granularity than the frame times
|
||||
// themselves (which often only have millisecond granularity).
|
||||
long averageFrameTimeNs = (unadjustedFrameTimeNs - syncFrameTimeNs) / frameCount;
|
||||
long averageFrameDurationNs = (framePresentationTimeNs - syncFramePresentationTimeNs)
|
||||
/ frameCount;
|
||||
// Project the adjusted frame time forward using the average.
|
||||
long candidateAdjustedFrameTimeNs = adjustedLastFrameTimeNs + averageFrameTimeNs;
|
||||
long candidateAdjustedFrameTimeNs = adjustedLastFrameTimeNs + averageFrameDurationNs;
|
||||
|
||||
if (isDriftTooLarge(candidateAdjustedFrameTimeNs, unadjustedReleaseTimeNs)) {
|
||||
haveSync = false;
|
||||
} else {
|
||||
adjustedFrameTimeNs = candidateAdjustedFrameTimeNs;
|
||||
adjustedReleaseTimeNs = syncReleaseTimeNs + adjustedFrameTimeNs - syncFrameTimeNs;
|
||||
adjustedReleaseTimeNs = syncUnadjustedReleaseTimeNs + adjustedFrameTimeNs
|
||||
- syncFramePresentationTimeNs;
|
||||
}
|
||||
} else {
|
||||
// We're synced but haven't waited the required number of frames to apply an adjustment.
|
||||
// Check drift anyway.
|
||||
if (isDriftTooLarge(unadjustedFrameTimeNs, unadjustedReleaseTimeNs)) {
|
||||
if (isDriftTooLarge(framePresentationTimeNs, unadjustedReleaseTimeNs)) {
|
||||
haveSync = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -130,14 +157,14 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
|
|||
|
||||
// If we need to sync, do so now.
|
||||
if (!haveSync) {
|
||||
syncFrameTimeNs = unadjustedFrameTimeNs;
|
||||
syncReleaseTimeNs = unadjustedReleaseTimeNs;
|
||||
syncFramePresentationTimeNs = framePresentationTimeNs;
|
||||
syncUnadjustedReleaseTimeNs = unadjustedReleaseTimeNs;
|
||||
frameCount = 0;
|
||||
haveSync = true;
|
||||
onSynced();
|
||||
}
|
||||
|
||||
lastUnadjustedFrameTimeUs = unadjustedFrameTimeUs;
|
||||
lastFramePresentationTimeUs = framePresentationTimeUs;
|
||||
pendingAdjustedFrameTimeNs = adjustedFrameTimeNs;
|
||||
|
||||
if (sampledVsyncTimeNs == 0) {
|
||||
|
|
@ -155,8 +182,8 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
|
|||
}
|
||||
|
||||
private boolean isDriftTooLarge(long frameTimeNs, long releaseTimeNs) {
|
||||
long elapsedFrameTimeNs = frameTimeNs - syncFrameTimeNs;
|
||||
long elapsedReleaseTimeNs = releaseTimeNs - syncReleaseTimeNs;
|
||||
long elapsedFrameTimeNs = frameTimeNs - syncFramePresentationTimeNs;
|
||||
long elapsedReleaseTimeNs = releaseTimeNs - syncUnadjustedReleaseTimeNs;
|
||||
return Math.abs(elapsedReleaseTimeNs - elapsedFrameTimeNs) > MAX_ALLOWED_DRIFT_NS;
|
||||
}
|
||||
|
||||
|
|
@ -177,4 +204,9 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
|
|||
return snappedAfterDiff < snappedBeforeDiff ? snappedAfterNs : snappedBeforeNs;
|
||||
}
|
||||
|
||||
private static float getDefaultDisplayRefreshRate(Context context) {
|
||||
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
return manager.getDefaultDisplay().getRefreshRate();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -202,7 +202,7 @@ public final class AudioTrack {
|
|||
private int temporaryBufferSize;
|
||||
|
||||
/**
|
||||
* Bitrate measured in kilobits per second, if {@link #isPassthrough()} returns true.
|
||||
* Bitrate measured in kilobits per second, if using passthrough.
|
||||
*/
|
||||
private int passthroughBitrate;
|
||||
|
||||
|
|
@ -359,7 +359,7 @@ public final class AudioTrack {
|
|||
}
|
||||
}
|
||||
|
||||
audioTrackUtil.reconfigure(audioTrack, isPassthrough());
|
||||
audioTrackUtil.reconfigure(audioTrack, needsPassthroughWorkarounds());
|
||||
setAudioTrackVolume();
|
||||
|
||||
return sessionId;
|
||||
|
|
@ -472,8 +472,7 @@ public final class AudioTrack {
|
|||
return RESULT_BUFFER_CONSUMED;
|
||||
}
|
||||
|
||||
// Workarounds for issues with AC-3 passthrough AudioTracks on API versions 21/22:
|
||||
if (Util.SDK_INT <= 22 && isPassthrough()) {
|
||||
if (needsPassthroughWorkarounds()) {
|
||||
// An AC-3 audio track continues to play data written while it is paused. Stop writing so its
|
||||
// buffer empties. See [Internal: b/18899620].
|
||||
if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED) {
|
||||
|
|
@ -491,8 +490,14 @@ public final class AudioTrack {
|
|||
|
||||
int result = 0;
|
||||
if (temporaryBufferSize == 0) {
|
||||
if (isPassthrough() && passthroughBitrate == UNKNOWN_BITRATE) {
|
||||
passthroughBitrate = Ac3Util.getBitrate(size, sampleRate);
|
||||
if (passthroughBitrate == UNKNOWN_BITRATE) {
|
||||
if (isAc3Passthrough()) {
|
||||
passthroughBitrate = Ac3Util.getBitrate(size, sampleRate);
|
||||
} else if (isDtsPassthrough()) {
|
||||
int unscaledBitrate = size * 8 * sampleRate;
|
||||
int divisor = 1000 * 512;
|
||||
passthroughBitrate = (unscaledBitrate + divisor / 2) / divisor;
|
||||
}
|
||||
}
|
||||
|
||||
// This is the first time we've seen this {@code buffer}.
|
||||
|
|
@ -583,7 +588,7 @@ public final class AudioTrack {
|
|||
public boolean hasPendingData() {
|
||||
return isInitialized()
|
||||
&& (bytesToFrames(submittedBytes) > audioTrackUtil.getPlaybackHeadPosition()
|
||||
|| audioTrackUtil.overrideHasPendingData());
|
||||
|| overrideHasPendingData());
|
||||
}
|
||||
|
||||
/** Sets the playback volume. */
|
||||
|
|
@ -709,10 +714,13 @@ public final class AudioTrack {
|
|||
}
|
||||
}
|
||||
|
||||
// Don't sample the timestamp and latency if this is a passthrough AudioTrack, as the returned
|
||||
// values cause audio/video synchronization to be incorrect.
|
||||
if (!isPassthrough()
|
||||
&& systemClockUs - lastTimestampSampleTimeUs >= MIN_TIMESTAMP_SAMPLE_INTERVAL_US) {
|
||||
if (needsPassthroughWorkarounds()) {
|
||||
// Don't sample the timestamp and latency if this is an AC-3 passthrough AudioTrack on
|
||||
// platform API versions 21/22, as incorrect values are returned. See [Internal: b/21145353].
|
||||
return;
|
||||
}
|
||||
|
||||
if (systemClockUs - lastTimestampSampleTimeUs >= MIN_TIMESTAMP_SAMPLE_INTERVAL_US) {
|
||||
audioTimestampSet = audioTrackUtil.updateTimestamp();
|
||||
if (audioTimestampSet) {
|
||||
// Perform sanity checks on the timestamp.
|
||||
|
|
@ -818,17 +826,50 @@ public final class AudioTrack {
|
|||
}
|
||||
|
||||
private boolean isPassthrough() {
|
||||
return isAc3Passthrough() || isDtsPassthrough();
|
||||
}
|
||||
|
||||
private boolean isAc3Passthrough() {
|
||||
return encoding == C.ENCODING_AC3 || encoding == C.ENCODING_E_AC3;
|
||||
}
|
||||
|
||||
private boolean isDtsPassthrough() {
|
||||
return encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether to work around problems with passthrough audio tracks.
|
||||
* See [Internal: b/18899620, b/19187573, b/21145353].
|
||||
*/
|
||||
private boolean needsPassthroughWorkarounds() {
|
||||
return Util.SDK_INT < 23 && isAc3Passthrough();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the audio track should behave as though it has pending data. This is to work
|
||||
* around an issue on platform API versions 21/22 where AC-3 audio tracks can't be paused, so we
|
||||
* empty their buffers when paused. In this case, they should still behave as if they have
|
||||
* pending data, otherwise writing will never resume.
|
||||
*/
|
||||
private boolean overrideHasPendingData() {
|
||||
return needsPassthroughWorkarounds()
|
||||
&& audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED
|
||||
&& audioTrack.getPlaybackHeadPosition() == 0;
|
||||
}
|
||||
|
||||
private static int getEncodingForMimeType(String mimeType) {
|
||||
if (MimeTypes.AUDIO_AC3.equals(mimeType)) {
|
||||
return C.ENCODING_AC3;
|
||||
switch (mimeType) {
|
||||
case MimeTypes.AUDIO_AC3:
|
||||
return C.ENCODING_AC3;
|
||||
case MimeTypes.AUDIO_EC3:
|
||||
return C.ENCODING_E_AC3;
|
||||
case MimeTypes.AUDIO_DTS:
|
||||
return C.ENCODING_DTS;
|
||||
case MimeTypes.AUDIO_DTS_HD:
|
||||
return C.ENCODING_DTS_HD;
|
||||
default:
|
||||
return AudioFormat.ENCODING_INVALID;
|
||||
}
|
||||
if (MimeTypes.AUDIO_EC3.equals(mimeType)) {
|
||||
return C.ENCODING_E_AC3;
|
||||
}
|
||||
return AudioFormat.ENCODING_INVALID;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -837,7 +878,7 @@ public final class AudioTrack {
|
|||
private static class AudioTrackUtil {
|
||||
|
||||
protected android.media.AudioTrack audioTrack;
|
||||
private boolean isPassthrough;
|
||||
private boolean needsPassthroughWorkaround;
|
||||
private int sampleRate;
|
||||
private long lastRawPlaybackHeadPosition;
|
||||
private long rawPlaybackHeadWrapCount;
|
||||
|
|
@ -851,11 +892,13 @@ public final class AudioTrack {
|
|||
* Reconfigures the audio track utility helper to use the specified {@code audioTrack}.
|
||||
*
|
||||
* @param audioTrack The audio track to wrap.
|
||||
* @param isPassthrough Whether the audio track is used for passthrough (e.g. AC-3) playback.
|
||||
* @param needsPassthroughWorkaround Whether to workaround issues with pausing AC-3 passthrough
|
||||
* audio tracks on platform API version 21/22.
|
||||
*/
|
||||
public void reconfigure(android.media.AudioTrack audioTrack, boolean isPassthrough) {
|
||||
public void reconfigure(android.media.AudioTrack audioTrack,
|
||||
boolean needsPassthroughWorkaround) {
|
||||
this.audioTrack = audioTrack;
|
||||
this.isPassthrough = isPassthrough;
|
||||
this.needsPassthroughWorkaround = needsPassthroughWorkaround;
|
||||
stopTimestampUs = -1;
|
||||
lastRawPlaybackHeadPosition = 0;
|
||||
rawPlaybackHeadWrapCount = 0;
|
||||
|
|
@ -865,20 +908,6 @@ public final class AudioTrack {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the audio track should behave as though it has pending data. This is to work
|
||||
* around an issue on platform API versions 21/22 where AC-3 audio tracks can't be paused, so we
|
||||
* empty their buffers when paused. In this case, they should still behave as if they have
|
||||
* pending data, otherwise writing will never resume.
|
||||
*
|
||||
* @see #handleBuffer
|
||||
*/
|
||||
public boolean overrideHasPendingData() {
|
||||
return Util.SDK_INT <= 22 && isPassthrough
|
||||
&& audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED
|
||||
&& audioTrack.getPlaybackHeadPosition() == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the audio track in a way that ensures media written to it is played out in full, and
|
||||
* that {@link #getPlaybackHeadPosition()} and {@link #getPlaybackHeadPositionUs()} continue to
|
||||
|
|
@ -929,7 +958,7 @@ public final class AudioTrack {
|
|||
}
|
||||
|
||||
long rawPlaybackHeadPosition = 0xFFFFFFFFL & audioTrack.getPlaybackHeadPosition();
|
||||
if (Util.SDK_INT <= 22 && isPassthrough) {
|
||||
if (needsPassthroughWorkaround) {
|
||||
// Work around an issue with passthrough/direct AudioTracks on platform API versions 21/22
|
||||
// where the playback head position jumps back to zero on paused passthrough/direct audio
|
||||
// tracks. See [Internal: b/19187573].
|
||||
|
|
@ -1009,8 +1038,9 @@ public final class AudioTrack {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void reconfigure(android.media.AudioTrack audioTrack, boolean isPassthrough) {
|
||||
super.reconfigure(audioTrack, isPassthrough);
|
||||
public void reconfigure(android.media.AudioTrack audioTrack,
|
||||
boolean needsPassthroughWorkaround) {
|
||||
super.reconfigure(audioTrack, needsPassthroughWorkaround);
|
||||
rawTimestampFramePositionWrapCount = 0;
|
||||
lastRawTimestampFramePosition = 0;
|
||||
lastTimestampFramePosition = 0;
|
||||
|
|
|
|||
|
|
@ -55,11 +55,9 @@ public final class VideoFormatSelectorUtil {
|
|||
public static int[] selectVideoFormatsForDefaultDisplay(Context context,
|
||||
List<? extends FormatWrapper> formatWrappers, String[] allowedContainerMimeTypes,
|
||||
boolean filterHdFormats) throws DecoderQueryException {
|
||||
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
Display display = windowManager.getDefaultDisplay();
|
||||
Point displaySize = getDisplaySize(display);
|
||||
Point viewportSize = getViewportSize(context);
|
||||
return selectVideoFormats(formatWrappers, allowedContainerMimeTypes, filterHdFormats, true,
|
||||
displaySize.x, displaySize.y);
|
||||
viewportSize.x, viewportSize.y);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -184,6 +182,19 @@ public final class VideoFormatSelectorUtil {
|
|||
}
|
||||
}
|
||||
|
||||
private static Point getViewportSize(Context context) {
|
||||
// Before API 23 the platform Display object does not provide a way to identify Android TVs that
|
||||
// can show 4k resolution in a SurfaceView, so check for supported devices here.
|
||||
// See also https://developer.sony.com/develop/tvs/android-tv/design-guide/.
|
||||
if (Util.MODEL != null && Util.MODEL.startsWith("BRAVIA")
|
||||
&& context.getPackageManager().hasSystemFeature("com.sony.dtv.hardware.panel.qfhd")) {
|
||||
return new Point(3840, 2160);
|
||||
}
|
||||
|
||||
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
return getDisplaySize(windowManager.getDefaultDisplay());
|
||||
}
|
||||
|
||||
private static Point getDisplaySize(Display display) {
|
||||
Point displaySize = new Point();
|
||||
if (Util.SDK_INT >= 17) {
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ import java.util.List;
|
|||
public static final int TYPE_enca = Util.getIntegerCodeForString("enca");
|
||||
public static final int TYPE_frma = Util.getIntegerCodeForString("frma");
|
||||
public static final int TYPE_saiz = Util.getIntegerCodeForString("saiz");
|
||||
public static final int TYPE_saio = Util.getIntegerCodeForString("saio");
|
||||
public static final int TYPE_uuid = Util.getIntegerCodeForString("uuid");
|
||||
public static final int TYPE_senc = Util.getIntegerCodeForString("senc");
|
||||
public static final int TYPE_pasp = Util.getIntegerCodeForString("pasp");
|
||||
|
|
@ -219,6 +220,31 @@ import java.util.List;
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of leaf/container children of this atom with the given type.
|
||||
*
|
||||
* @param type The type of child atoms to count.
|
||||
* @return The total number of leaf/container children of this atom with the given type.
|
||||
*/
|
||||
public int getChildAtomOfTypeCount(int type) {
|
||||
int count = 0;
|
||||
int size = leafChildren.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
LeafAtom atom = leafChildren.get(i);
|
||||
if (atom.type == type) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
size = containerChildren.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
ContainerAtom atom = containerChildren.get(i);
|
||||
if (atom.type == type) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getAtomTypeString(type)
|
||||
|
|
|
|||
|
|
@ -106,10 +106,11 @@ import java.util.List;
|
|||
|
||||
long[] offsets = new long[sampleCount];
|
||||
int[] sizes = new int[sampleCount];
|
||||
int maximumSize = 0;
|
||||
long[] timestamps = new long[sampleCount];
|
||||
int[] flags = new int[sampleCount];
|
||||
if (sampleCount == 0) {
|
||||
return new TrackSampleTable(offsets, sizes, timestamps, flags);
|
||||
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags);
|
||||
}
|
||||
|
||||
// Prepare to read chunk offsets.
|
||||
|
|
@ -172,6 +173,9 @@ import java.util.List;
|
|||
for (int i = 0; i < sampleCount; i++) {
|
||||
offsets[i] = offsetBytes;
|
||||
sizes[i] = fixedSampleSize == 0 ? stsz.readUnsignedIntToInt() : fixedSampleSize;
|
||||
if (sizes[i] > maximumSize) {
|
||||
maximumSize = sizes[i];
|
||||
}
|
||||
timestamps[i] = timestampTimeUnits + timestampOffset;
|
||||
|
||||
// All samples are synchronization samples if the stss is not present.
|
||||
|
|
@ -244,7 +248,7 @@ import java.util.List;
|
|||
Assertions.checkArgument(remainingSamplesInChunk == 0);
|
||||
Assertions.checkArgument(remainingTimestampDeltaChanges == 0);
|
||||
Assertions.checkArgument(remainingTimestampOffsetChanges == 0);
|
||||
return new TrackSampleTable(offsets, sizes, timestamps, flags);
|
||||
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -53,6 +53,11 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
*/
|
||||
public static final int WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1;
|
||||
|
||||
/**
|
||||
* Flag to ignore any tfdt boxes in the stream.
|
||||
*/
|
||||
public static final int WORKAROUND_IGNORE_TFDT_BOX = 2;
|
||||
|
||||
private static final byte[] PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE =
|
||||
new byte[] {-94, 57, 79, 82, 90, -101, 79, 20, -94, 68, 108, 66, 124, 100, -115, -12};
|
||||
|
||||
|
|
@ -81,6 +86,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
private long atomSize;
|
||||
private int atomHeaderBytesRead;
|
||||
private ParsableByteArray atomData;
|
||||
private long endOfMdatPosition;
|
||||
|
||||
private int sampleIndex;
|
||||
private int sampleSize;
|
||||
|
|
@ -199,7 +205,15 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
atomSize = atomHeader.readUnsignedLongToLong();
|
||||
}
|
||||
|
||||
long atomPosition = input.getPosition() - atomHeaderBytesRead;
|
||||
if (atomType == Atom.TYPE_moof) {
|
||||
// The data positions may be updated when parsing the tfhd/trun.
|
||||
fragmentRun.auxiliaryDataPosition = atomPosition;
|
||||
fragmentRun.dataPosition = atomPosition;
|
||||
}
|
||||
|
||||
if (atomType == Atom.TYPE_mdat) {
|
||||
endOfMdatPosition = atomPosition + atomSize;
|
||||
if (!haveOutputSeekMap) {
|
||||
extractorOutput.seekMap(SeekMap.UNSEEKABLE);
|
||||
haveOutputSeekMap = true;
|
||||
|
|
@ -319,6 +333,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
|
||||
private static void parseMoof(Track track, DefaultSampleValues extendsDefaults,
|
||||
ContainerAtom moof, TrackFragment out, int workaroundFlags, byte[] extendedTypeScratch) {
|
||||
// This extractor only supports one traf per moof.
|
||||
Assertions.checkArgument(1 == moof.getChildAtomOfTypeCount(Atom.TYPE_traf));
|
||||
parseTraf(track, extendsDefaults, moof.getContainerAtomOfType(Atom.TYPE_traf),
|
||||
out, workaroundFlags, extendedTypeScratch);
|
||||
}
|
||||
|
|
@ -328,23 +344,34 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
*/
|
||||
private static void parseTraf(Track track, DefaultSampleValues extendsDefaults,
|
||||
ContainerAtom traf, TrackFragment out, int workaroundFlags, byte[] extendedTypeScratch) {
|
||||
// This extractor only supports one trun per traf.
|
||||
Assertions.checkArgument(1 == traf.getChildAtomOfTypeCount(Atom.TYPE_trun));
|
||||
LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt);
|
||||
long decodeTime = tfdtAtom == null ? 0 : parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data);
|
||||
long decodeTime;
|
||||
if (tfdtAtom == null || (workaroundFlags & WORKAROUND_IGNORE_TFDT_BOX) != 0) {
|
||||
decodeTime = 0;
|
||||
} else {
|
||||
decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data);
|
||||
}
|
||||
|
||||
LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
|
||||
DefaultSampleValues fragmentHeader = parseTfhd(extendsDefaults, tfhd.data);
|
||||
out.sampleDescriptionIndex = fragmentHeader.sampleDescriptionIndex;
|
||||
parseTfhd(extendsDefaults, tfhd.data, out);
|
||||
|
||||
LeafAtom trun = traf.getLeafAtomOfType(Atom.TYPE_trun);
|
||||
parseTrun(track, fragmentHeader, decodeTime, workaroundFlags, trun.data, out);
|
||||
parseTrun(track, out.header, decodeTime, workaroundFlags, trun.data, out);
|
||||
|
||||
LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
|
||||
if (saiz != null) {
|
||||
TrackEncryptionBox trackEncryptionBox =
|
||||
track.sampleDescriptionEncryptionBoxes[fragmentHeader.sampleDescriptionIndex];
|
||||
track.sampleDescriptionEncryptionBoxes[out.header.sampleDescriptionIndex];
|
||||
parseSaiz(trackEncryptionBox, saiz.data, out);
|
||||
}
|
||||
|
||||
LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio);
|
||||
if (saio != null) {
|
||||
parseSaio(saio.data, out);
|
||||
}
|
||||
|
||||
LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc);
|
||||
if (senc != null) {
|
||||
parseSenc(senc.data, out);
|
||||
|
|
@ -391,21 +418,49 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
out.initEncryptionData(totalSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a saio atom (defined in 14496-12).
|
||||
*
|
||||
* @param saio The saio atom to parse.
|
||||
* @param out The track fragment to populate with data from the saio atom.
|
||||
*/
|
||||
private static void parseSaio(ParsableByteArray saio, TrackFragment out) {
|
||||
saio.setPosition(Atom.HEADER_SIZE);
|
||||
int fullAtom = saio.readInt();
|
||||
int flags = Atom.parseFullAtomFlags(fullAtom);
|
||||
if ((flags & 0x01) == 1) {
|
||||
saio.skipBytes(8);
|
||||
}
|
||||
|
||||
int entryCount = saio.readUnsignedIntToInt();
|
||||
if (entryCount != 1) {
|
||||
// We only support one trun element currently, so always expect one entry.
|
||||
throw new IllegalStateException("Unexpected saio entry count: " + entryCount);
|
||||
}
|
||||
|
||||
int version = Atom.parseFullAtomVersion(fullAtom);
|
||||
out.auxiliaryDataPosition +=
|
||||
version == 0 ? saio.readUnsignedInt() : saio.readUnsignedLongToLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a tfhd atom (defined in 14496-12).
|
||||
*
|
||||
* @param extendsDefaults Default sample values from the trex atom.
|
||||
* @return The parsed default sample values.
|
||||
* @param tfhd The tfhd atom to parse.
|
||||
* @param out The track fragment to populate with data from the tfhd atom.
|
||||
*/
|
||||
private static DefaultSampleValues parseTfhd(DefaultSampleValues extendsDefaults,
|
||||
ParsableByteArray tfhd) {
|
||||
private static void parseTfhd(DefaultSampleValues extendsDefaults, ParsableByteArray tfhd,
|
||||
TrackFragment out) {
|
||||
tfhd.setPosition(Atom.HEADER_SIZE);
|
||||
int fullAtom = tfhd.readInt();
|
||||
int flags = Atom.parseFullAtomFlags(fullAtom);
|
||||
|
||||
tfhd.skipBytes(4); // trackId
|
||||
if ((flags & 0x01 /* base_data_offset_present */) != 0) {
|
||||
tfhd.skipBytes(8);
|
||||
long baseDataPosition = tfhd.readUnsignedLongToLong();
|
||||
out.dataPosition = baseDataPosition;
|
||||
out.auxiliaryDataPosition = baseDataPosition;
|
||||
}
|
||||
|
||||
int defaultSampleDescriptionIndex =
|
||||
|
|
@ -417,7 +472,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
? tfhd.readUnsignedIntToInt() : extendsDefaults.size;
|
||||
int defaultSampleFlags = ((flags & 0x20 /* default_sample_flags_present */) != 0)
|
||||
? tfhd.readUnsignedIntToInt() : extendsDefaults.flags;
|
||||
return new DefaultSampleValues(defaultSampleDescriptionIndex, defaultSampleDuration,
|
||||
out.header = new DefaultSampleValues(defaultSampleDescriptionIndex, defaultSampleDuration,
|
||||
defaultSampleSize, defaultSampleFlags);
|
||||
}
|
||||
|
||||
|
|
@ -451,7 +506,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
|
||||
int sampleCount = trun.readUnsignedIntToInt();
|
||||
if ((flags & 0x01 /* data_offset_present */) != 0) {
|
||||
trun.skipBytes(4);
|
||||
out.dataPosition += trun.readInt();
|
||||
}
|
||||
|
||||
boolean firstSampleFlagsPresent = (flags & 0x04 /* first_sample_flags_present */) != 0;
|
||||
|
|
@ -475,8 +530,7 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
long timescale = track.timescale;
|
||||
long cumulativeTime = decodeTime;
|
||||
boolean workaroundEveryVideoFrameIsSyncFrame = track.type == Track.TYPE_vide
|
||||
&& ((workaroundFlags & WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME)
|
||||
== WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME);
|
||||
&& (workaroundFlags & WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME) != 0;
|
||||
for (int i = 0; i < sampleCount; i++) {
|
||||
// Use trun values if present, otherwise tfhd, otherwise trex.
|
||||
int sampleDuration = sampleDurationsPresent ? trun.readUnsignedIntToInt()
|
||||
|
|
@ -601,6 +655,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
}
|
||||
|
||||
private void readEncryptionData(ExtractorInput input) throws IOException, InterruptedException {
|
||||
int bytesToSkip = (int) (fragmentRun.auxiliaryDataPosition - input.getPosition());
|
||||
Assertions.checkState(bytesToSkip >= 0, "Offset to encryption data was negative.");
|
||||
input.skipFully(bytesToSkip);
|
||||
fragmentRun.fillEncryptionData(input);
|
||||
parserState = STATE_READING_SAMPLE_START;
|
||||
}
|
||||
|
|
@ -620,7 +677,16 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
* @throws InterruptedException If the thread is interrupted.
|
||||
*/
|
||||
private boolean readSample(ExtractorInput input) throws IOException, InterruptedException {
|
||||
if (sampleIndex == 0) {
|
||||
int bytesToSkip = (int) (fragmentRun.dataPosition - input.getPosition());
|
||||
Assertions.checkState(bytesToSkip >= 0, "Offset to sample data was negative.");
|
||||
input.skipFully(bytesToSkip);
|
||||
}
|
||||
|
||||
if (sampleIndex >= fragmentRun.length) {
|
||||
int bytesToSkip = (int) (endOfMdatPosition - input.getPosition());
|
||||
Assertions.checkState(bytesToSkip >= 0, "Offset to end of mdat was negative.");
|
||||
input.skipFully(bytesToSkip);
|
||||
// We've run out of samples in the current mdat atom.
|
||||
enterReadingAtomHeaderState();
|
||||
return false;
|
||||
|
|
@ -678,8 +744,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
long sampleTimeUs = fragmentRun.getSamplePresentationTime(sampleIndex) * 1000L;
|
||||
int sampleFlags = (fragmentRun.definesEncryptionData ? C.SAMPLE_FLAG_ENCRYPTED : 0)
|
||||
| (fragmentRun.sampleIsSyncFrameTable[sampleIndex] ? C.SAMPLE_FLAG_SYNC : 0);
|
||||
int sampleDescriptionIndex = fragmentRun.header.sampleDescriptionIndex;
|
||||
byte[] encryptionKey = fragmentRun.definesEncryptionData
|
||||
? track.sampleDescriptionEncryptionBoxes[fragmentRun.sampleDescriptionIndex].keyId : null;
|
||||
? track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId : null;
|
||||
trackOutput.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey);
|
||||
|
||||
sampleIndex++;
|
||||
|
|
@ -688,8 +755,9 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
}
|
||||
|
||||
private int appendSampleEncryptionData(ParsableByteArray sampleEncryptionData) {
|
||||
int sampleDescriptionIndex = fragmentRun.header.sampleDescriptionIndex;
|
||||
TrackEncryptionBox encryptionBox =
|
||||
track.sampleDescriptionEncryptionBoxes[fragmentRun.sampleDescriptionIndex];
|
||||
track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex];
|
||||
int vectorSize = encryptionBox.initializationVectorSize;
|
||||
boolean subsampleEncryption = fragmentRun.sampleHasSubsampleEncryptionTable[sampleIndex];
|
||||
|
||||
|
|
@ -721,8 +789,8 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
|| atom == Atom.TYPE_traf || atom == Atom.TYPE_trak || atom == Atom.TYPE_trex
|
||||
|| atom == Atom.TYPE_trun || atom == Atom.TYPE_mvex || atom == Atom.TYPE_mdia
|
||||
|| atom == Atom.TYPE_minf || atom == Atom.TYPE_stbl || atom == Atom.TYPE_pssh
|
||||
|| atom == Atom.TYPE_saiz || atom == Atom.TYPE_uuid || atom == Atom.TYPE_senc
|
||||
|| atom == Atom.TYPE_pasp || atom == Atom.TYPE_s263;
|
||||
|| atom == Atom.TYPE_saiz || atom == Atom.TYPE_saio || atom == Atom.TYPE_uuid
|
||||
|| atom == Atom.TYPE_senc || atom == Atom.TYPE_pasp || atom == Atom.TYPE_s263;
|
||||
}
|
||||
|
||||
/** Returns whether the extractor should parse a container atom with type {@code atom}. */
|
||||
|
|
|
|||
|
|
@ -262,7 +262,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||
}
|
||||
|
||||
Mp4Track mp4Track = new Mp4Track(track, trackSampleTable, extractorOutput.track(i));
|
||||
mp4Track.trackOutput.format(track.mediaFormat);
|
||||
// Each sample has up to three bytes of overhead for the start code that replaces its length.
|
||||
// Allow ten source samples per output sample, like the platform extractor.
|
||||
int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
|
||||
mp4Track.trackOutput.format(track.mediaFormat.copyWithMaxInputSize(maxInputSize));
|
||||
tracks.add(mp4Track);
|
||||
|
||||
long firstSampleOffset = trackSampleTable.offsets[0];
|
||||
|
|
|
|||
|
|
@ -25,8 +25,18 @@ import java.io.IOException;
|
|||
*/
|
||||
/* package */ final class TrackFragment {
|
||||
|
||||
public int sampleDescriptionIndex;
|
||||
|
||||
/**
|
||||
* The default values for samples from the track fragment header.
|
||||
*/
|
||||
public DefaultSampleValues header;
|
||||
/**
|
||||
* The position (byte offset) of the start of sample data.
|
||||
*/
|
||||
public long dataPosition;
|
||||
/**
|
||||
* The position (byte offset) of the start of auxiliary data.
|
||||
*/
|
||||
public long auxiliaryDataPosition;
|
||||
/**
|
||||
* The number of samples contained by the fragment.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -19,31 +19,49 @@ import com.google.android.exoplayer.C;
|
|||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
/** Sample table for a track in an MP4 file. */
|
||||
/**
|
||||
* Sample table for a track in an MP4 file.
|
||||
*/
|
||||
/* package */ final class TrackSampleTable {
|
||||
|
||||
/** Sample index when no sample is available. */
|
||||
/**
|
||||
* Sample index when no sample is available.
|
||||
*/
|
||||
public static final int NO_SAMPLE = -1;
|
||||
|
||||
/** Number of samples. */
|
||||
/**
|
||||
* Number of samples.
|
||||
*/
|
||||
public final int sampleCount;
|
||||
/** Sample offsets in bytes. */
|
||||
/**
|
||||
* Sample offsets in bytes.
|
||||
*/
|
||||
public final long[] offsets;
|
||||
/** Sample sizes in bytes. */
|
||||
/**
|
||||
* Sample sizes in bytes.
|
||||
*/
|
||||
public final int[] sizes;
|
||||
/** Sample timestamps in microseconds. */
|
||||
/**
|
||||
* Maximum sample size in {@link #sizes}.
|
||||
*/
|
||||
public final int maximumSize;
|
||||
/**
|
||||
* Sample timestamps in microseconds.
|
||||
*/
|
||||
public final long[] timestampsUs;
|
||||
/** Sample flags. */
|
||||
/**
|
||||
* Sample flags.
|
||||
*/
|
||||
public final int[] flags;
|
||||
|
||||
TrackSampleTable(
|
||||
long[] offsets, int[] sizes, long[] timestampsUs, int[] flags) {
|
||||
TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs, int[] flags) {
|
||||
Assertions.checkArgument(sizes.length == timestampsUs.length);
|
||||
Assertions.checkArgument(offsets.length == timestampsUs.length);
|
||||
Assertions.checkArgument(flags.length == timestampsUs.length);
|
||||
|
||||
this.offsets = offsets;
|
||||
this.sizes = sizes;
|
||||
this.maximumSize = maximumSize;
|
||||
this.timestampsUs = timestampsUs;
|
||||
this.flags = flags;
|
||||
sampleCount = offsets.length;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.google.android.exoplayer.C;
|
|||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Stack;
|
||||
|
|
@ -32,6 +33,9 @@ import java.util.Stack;
|
|||
private static final int ELEMENT_STATE_READ_CONTENT_SIZE = 1;
|
||||
private static final int ELEMENT_STATE_READ_CONTENT = 2;
|
||||
|
||||
private static final int MAX_ID_BYTES = 4;
|
||||
private static final int MAX_LENGTH_BYTES = 8;
|
||||
|
||||
private static final int MAX_INTEGER_ELEMENT_SIZE_BYTES = 8;
|
||||
private static final int VALID_FLOAT32_ELEMENT_SIZE_BYTES = 4;
|
||||
private static final int VALID_FLOAT64_ELEMENT_SIZE_BYTES = 8;
|
||||
|
|
@ -68,8 +72,11 @@ import java.util.Stack;
|
|||
}
|
||||
|
||||
if (elementState == ELEMENT_STATE_READ_ID) {
|
||||
long result = varintReader.readUnsignedVarint(input, true, false);
|
||||
if (result == -1) {
|
||||
long result = varintReader.readUnsignedVarint(input, true, false, MAX_ID_BYTES);
|
||||
if (result == C.RESULT_MAX_LENGTH_EXCEEDED) {
|
||||
result = maybeResyncToNextLevel1Element(input);
|
||||
}
|
||||
if (result == C.RESULT_END_OF_INPUT) {
|
||||
return false;
|
||||
}
|
||||
// Element IDs are at most 4 bytes, so we can cast to integers.
|
||||
|
|
@ -78,7 +85,7 @@ import java.util.Stack;
|
|||
}
|
||||
|
||||
if (elementState == ELEMENT_STATE_READ_CONTENT_SIZE) {
|
||||
elementContentSize = varintReader.readUnsignedVarint(input, false, true);
|
||||
elementContentSize = varintReader.readUnsignedVarint(input, false, true, MAX_LENGTH_BYTES);
|
||||
elementState = ELEMENT_STATE_READ_CONTENT;
|
||||
}
|
||||
|
||||
|
|
@ -127,6 +134,35 @@ import java.util.Stack;
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a byte by byte search to try and find the next level 1 element. This method is called if
|
||||
* some invalid data is encountered in the parser.
|
||||
*
|
||||
* @param input The {@link ExtractorInput} from which data has to be read.
|
||||
* @return id of the next level 1 element that has been found.
|
||||
* @throws EOFException If the end of input was encountered when searching for the next level 1
|
||||
* element.
|
||||
* @throws IOException If an error occurs reading from the input.
|
||||
* @throws InterruptedException If the thread is interrupted.
|
||||
*/
|
||||
private long maybeResyncToNextLevel1Element(ExtractorInput input) throws EOFException,
|
||||
IOException, InterruptedException {
|
||||
while (true) {
|
||||
input.resetPeekPosition();
|
||||
input.peekFully(scratch, 0, MAX_ID_BYTES);
|
||||
int varintLength = VarintReader.parseUnsignedVarintLength(scratch[0]);
|
||||
if (varintLength != -1 && varintLength <= MAX_ID_BYTES) {
|
||||
int potentialId = (int) VarintReader.assembleVarint(scratch, varintLength, false);
|
||||
if (output.isLevel1Element(potentialId)) {
|
||||
input.skipFully(varintLength);
|
||||
input.resetPeekPosition();
|
||||
return potentialId;
|
||||
}
|
||||
}
|
||||
input.skipFully(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and returns an integer of length {@code byteLength} from the {@link ExtractorInput}.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -36,6 +36,14 @@ import java.io.IOException;
|
|||
*/
|
||||
int getElementType(int id);
|
||||
|
||||
/**
|
||||
* Checks if the given id is that of a level 1 element.
|
||||
*
|
||||
* @param id The element ID.
|
||||
* @return True the given id is that of a level 1 element. false otherwise.
|
||||
*/
|
||||
boolean isLevel1Element(int id);
|
||||
|
||||
/**
|
||||
* Called when the start of a master element is encountered.
|
||||
* <p>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.google.android.exoplayer.extractor.webm;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||
|
||||
import java.io.EOFException;
|
||||
|
|
@ -19,8 +20,8 @@ import java.io.IOException;
|
|||
*
|
||||
* <p>{@code 0x80} is a one-byte integer, {@code 0x40} is two bytes, and so on up to eight bytes.
|
||||
*/
|
||||
private static final int[] VARINT_LENGTH_MASKS = new int[] {
|
||||
0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01
|
||||
private static final long[] VARINT_LENGTH_MASKS = new long[] {
|
||||
0x80L, 0x40L, 0x20L, 0x10L, 0x08L, 0x04L, 0x02L, 0x01L
|
||||
};
|
||||
|
||||
private final byte[] scratch;
|
||||
|
|
@ -53,48 +54,41 @@ import java.io.IOException;
|
|||
*
|
||||
* @param input The {@link ExtractorInput} from which the integer should be read.
|
||||
* @param allowEndOfInput True if encountering the end of the input having read no data is
|
||||
* allowed, and should result in {@code -1} being returned. False if it should be
|
||||
* considered an error, causing an {@link EOFException} to be thrown.
|
||||
* @param removeLengthMask Removes the variable-length integer length mask from the value
|
||||
* @return The read value, or -1 if {@code allowEndOfStream} is true and the end of the input was
|
||||
* encountered.
|
||||
* allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it
|
||||
* should be considered an error, causing an {@link EOFException} to be thrown.
|
||||
* @param removeLengthMask Removes the variable-length integer length mask from the value.
|
||||
* @param maximumAllowedLength Maximum allowed length of the variable integer to be read.
|
||||
* @return The read value, or {@link C#RESULT_END_OF_INPUT} if {@code allowEndOfStream} is true
|
||||
* and the end of the input was encountered, or {@link C#RESULT_MAX_LENGTH_EXCEEDED} if the
|
||||
* length of the varint exceeded maximumAllowedLength.
|
||||
* @throws IOException If an error occurs reading from the input.
|
||||
* @throws InterruptedException If the thread is interrupted.
|
||||
*/
|
||||
public long readUnsignedVarint(ExtractorInput input, boolean allowEndOfInput,
|
||||
boolean removeLengthMask) throws IOException, InterruptedException {
|
||||
boolean removeLengthMask, int maximumAllowedLength) throws IOException, InterruptedException {
|
||||
if (state == STATE_BEGIN_READING) {
|
||||
// Read the first byte to establish the length.
|
||||
if (!input.readFully(scratch, 0, 1, allowEndOfInput)) {
|
||||
return -1;
|
||||
return C.RESULT_END_OF_INPUT;
|
||||
}
|
||||
int firstByte = scratch[0] & 0xFF;
|
||||
length = -1;
|
||||
for (int i = 0; i < VARINT_LENGTH_MASKS.length; i++) {
|
||||
if ((VARINT_LENGTH_MASKS[i] & firstByte) != 0) {
|
||||
length = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
length = parseUnsignedVarintLength(firstByte);
|
||||
if (length == -1) {
|
||||
throw new IllegalStateException("No valid varint length mask found");
|
||||
}
|
||||
state = STATE_READ_CONTENTS;
|
||||
}
|
||||
|
||||
if (length > maximumAllowedLength) {
|
||||
state = STATE_BEGIN_READING;
|
||||
return C.RESULT_MAX_LENGTH_EXCEEDED;
|
||||
}
|
||||
|
||||
// Read the remaining bytes.
|
||||
input.readFully(scratch, 1, length - 1);
|
||||
|
||||
// Parse the value.
|
||||
if (removeLengthMask) {
|
||||
scratch[0] &= ~VARINT_LENGTH_MASKS[length - 1];
|
||||
}
|
||||
long varint = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
varint = (varint << 8) | (scratch[i] & 0xFF);
|
||||
}
|
||||
state = STATE_BEGIN_READING;
|
||||
return varint;
|
||||
return assembleVarint(scratch, length, removeLengthMask);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -104,4 +98,41 @@ import java.io.IOException;
|
|||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and the length of the varint given the first byte.
|
||||
*
|
||||
* @param firstByte First byte of the varint.
|
||||
* @return Length of the varint beginning with the given byte if it was valid, -1 otherwise.
|
||||
*/
|
||||
public static int parseUnsignedVarintLength(int firstByte) {
|
||||
int varIntLength = -1;
|
||||
for (int i = 0; i < VARINT_LENGTH_MASKS.length; i++) {
|
||||
if ((VARINT_LENGTH_MASKS[i] & firstByte) != 0) {
|
||||
varIntLength = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return varIntLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assemble a varint from the given byte array.
|
||||
*
|
||||
* @param varintBytes Bytes that make up the varint.
|
||||
* @param varintLength Length of the varint to assemble.
|
||||
* @param removeLengthMask Removes the variable-length integer length mask from the value.
|
||||
* @return Parsed and assembled varint.
|
||||
*/
|
||||
public static long assembleVarint(byte[] varintBytes, int varintLength,
|
||||
boolean removeLengthMask) {
|
||||
long varint = varintBytes[0] & 0xFFL;
|
||||
if (removeLengthMask) {
|
||||
varint &= ~VARINT_LENGTH_MASKS[varintLength - 1];
|
||||
}
|
||||
for (int i = 1; i < varintLength; i++) {
|
||||
varint = (varint << 8) | (varintBytes[i] & 0xFFL);
|
||||
}
|
||||
return varint;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ public final class WebmExtractor implements Extractor {
|
|||
private static final String DOC_TYPE_MATROSKA = "matroska";
|
||||
private static final String CODEC_ID_VP8 = "V_VP8";
|
||||
private static final String CODEC_ID_VP9 = "V_VP9";
|
||||
private static final String CODEC_ID_MPEG2 = "V_MPEG2";
|
||||
private static final String CODEC_ID_MPEG4_SP = "V_MPEG4/ISO/SP";
|
||||
private static final String CODEC_ID_MPEG4_ASP = "V_MPEG4/ISO/ASP";
|
||||
private static final String CODEC_ID_MPEG4_AP = "V_MPEG4/ISO/AP";
|
||||
|
|
@ -74,6 +75,7 @@ public final class WebmExtractor implements Extractor {
|
|||
private static final String CODEC_ID_AAC = "A_AAC";
|
||||
private static final String CODEC_ID_MP3 = "A_MPEG/L3";
|
||||
private static final String CODEC_ID_AC3 = "A_AC3";
|
||||
private static final String CODEC_ID_TRUEHD = "A_TRUEHD";
|
||||
private static final String CODEC_ID_DTS = "A_DTS";
|
||||
private static final String CODEC_ID_DTS_EXPRESS = "A_DTS/EXPRESS";
|
||||
private static final String CODEC_ID_DTS_LOSSLESS = "A_DTS/LOSSLESS";
|
||||
|
|
@ -347,6 +349,10 @@ public final class WebmExtractor implements Extractor {
|
|||
}
|
||||
}
|
||||
|
||||
/* package */ boolean isLevel1Element(int id) {
|
||||
return id == ID_SEGMENT_INFO || id == ID_CLUSTER || id == ID_CUES || id == ID_TRACKS;
|
||||
}
|
||||
|
||||
/* package */ void startMasterElement(int id, long contentPosition, long contentSize)
|
||||
throws ParserException {
|
||||
switch (id) {
|
||||
|
|
@ -639,7 +645,7 @@ public final class WebmExtractor implements Extractor {
|
|||
// differ only in the way flags are specified.
|
||||
|
||||
if (blockState == BLOCK_STATE_START) {
|
||||
blockTrackNumber = (int) varintReader.readUnsignedVarint(input, false, true);
|
||||
blockTrackNumber = (int) varintReader.readUnsignedVarint(input, false, true, 8);
|
||||
blockTrackNumberLength = varintReader.getLastLength();
|
||||
blockDurationUs = UNKNOWN;
|
||||
blockState = BLOCK_STATE_HEADER;
|
||||
|
|
@ -1028,6 +1034,7 @@ public final class WebmExtractor implements Extractor {
|
|||
private static boolean isCodecSupported(String codecId) {
|
||||
return CODEC_ID_VP8.equals(codecId)
|
||||
|| CODEC_ID_VP9.equals(codecId)
|
||||
|| CODEC_ID_MPEG2.equals(codecId)
|
||||
|| CODEC_ID_MPEG4_SP.equals(codecId)
|
||||
|| CODEC_ID_MPEG4_ASP.equals(codecId)
|
||||
|| CODEC_ID_MPEG4_AP.equals(codecId)
|
||||
|
|
@ -1038,6 +1045,7 @@ public final class WebmExtractor implements Extractor {
|
|||
|| CODEC_ID_AAC.equals(codecId)
|
||||
|| CODEC_ID_MP3.equals(codecId)
|
||||
|| CODEC_ID_AC3.equals(codecId)
|
||||
|| CODEC_ID_TRUEHD.equals(codecId)
|
||||
|| CODEC_ID_DTS.equals(codecId)
|
||||
|| CODEC_ID_DTS_EXPRESS.equals(codecId)
|
||||
|| CODEC_ID_DTS_LOSSLESS.equals(codecId)
|
||||
|
|
@ -1069,6 +1077,11 @@ public final class WebmExtractor implements Extractor {
|
|||
return WebmExtractor.this.getElementType(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLevel1Element(int id) {
|
||||
return WebmExtractor.this.isLevel1Element(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMasterElement(int id, long contentPosition, long contentSize)
|
||||
throws ParserException {
|
||||
|
|
@ -1147,6 +1160,9 @@ public final class WebmExtractor implements Extractor {
|
|||
case CODEC_ID_VP9:
|
||||
mimeType = MimeTypes.VIDEO_VP9;
|
||||
break;
|
||||
case CODEC_ID_MPEG2:
|
||||
mimeType = MimeTypes.VIDEO_MPEG2;
|
||||
break;
|
||||
case CODEC_ID_MPEG4_SP:
|
||||
case CODEC_ID_MPEG4_ASP:
|
||||
case CODEC_ID_MPEG4_AP:
|
||||
|
|
@ -1194,6 +1210,9 @@ public final class WebmExtractor implements Extractor {
|
|||
case CODEC_ID_AC3:
|
||||
mimeType = MimeTypes.AUDIO_AC3;
|
||||
break;
|
||||
case CODEC_ID_TRUEHD:
|
||||
mimeType = MimeTypes.AUDIO_TRUEHD;
|
||||
break;
|
||||
case CODEC_ID_DTS:
|
||||
case CODEC_ID_DTS_EXPRESS:
|
||||
mimeType = MimeTypes.AUDIO_DTS;
|
||||
|
|
|
|||
|
|
@ -427,7 +427,8 @@ public class SmoothStreamingChunkSource implements ChunkSource,
|
|||
|
||||
// Build the extractor.
|
||||
FragmentedMp4Extractor mp4Extractor = new FragmentedMp4Extractor(
|
||||
FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME);
|
||||
FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
|
||||
| FragmentedMp4Extractor.WORKAROUND_IGNORE_TFDT_BOX);
|
||||
Track mp4Track = new Track(trackIndex, mp4TrackType, element.timescale, durationUs, mediaFormat,
|
||||
trackEncryptionBoxes, mp4TrackType == Track.TYPE_vide ? 4 : -1);
|
||||
mp4Extractor.setTrack(mp4Track);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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.text.ttml;
|
||||
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Parser to parse ttml color value expression
|
||||
* (http://www.w3.org/TR/ttml1/#style-value-color)
|
||||
*/
|
||||
/*package*/ final class TtmlColorParser {
|
||||
|
||||
private static final String RGB = "rgb";
|
||||
private static final String RGBA = "rgba";
|
||||
|
||||
private static final Pattern RGB_PATTERN = Pattern.compile(
|
||||
"^rgb\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)$");
|
||||
|
||||
private static final Pattern RGBA_PATTERN = Pattern.compile(
|
||||
"^rgba\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)$");
|
||||
|
||||
|
||||
static final int TRANSPARENT = 0x00000000;
|
||||
static final int BLACK = 0xFF000000;
|
||||
static final int SILVER = 0xFFC0C0C0;
|
||||
static final int GRAY = 0xFF808080;
|
||||
static final int WHITE = 0xFFFFFFFF;
|
||||
static final int MAROON = 0xFF800000;
|
||||
static final int RED = 0xFFFF0000;
|
||||
static final int PURPLE = 0xFF800080;
|
||||
static final int FUCHSIA = 0xFFFF00FF;
|
||||
static final int MAGENTA = FUCHSIA;
|
||||
static final int GREEN = 0xFF008000;
|
||||
static final int LIME = 0xFF00FF00;
|
||||
static final int OLIVE = 0xFF808000;
|
||||
static final int YELLOW = 0xFFFFFF00;
|
||||
static final int NAVY = 0xFF000080;
|
||||
static final int BLUE = 0xFF0000FF;
|
||||
static final int TEAL = 0xFF008080;
|
||||
static final int AQUA = 0x00FFFFFF;
|
||||
static final int CYAN = 0xFF00FFFF;
|
||||
|
||||
private static final Map<String, Integer> COLOR_NAME_MAP;
|
||||
static {
|
||||
COLOR_NAME_MAP = new HashMap<>();
|
||||
COLOR_NAME_MAP.put("transparent", TRANSPARENT);
|
||||
COLOR_NAME_MAP.put("black", BLACK);
|
||||
COLOR_NAME_MAP.put("silver", SILVER);
|
||||
COLOR_NAME_MAP.put("gray", GRAY);
|
||||
COLOR_NAME_MAP.put("white", WHITE);
|
||||
COLOR_NAME_MAP.put("maroon", MAROON);
|
||||
COLOR_NAME_MAP.put("red", RED);
|
||||
COLOR_NAME_MAP.put("purple", PURPLE);
|
||||
COLOR_NAME_MAP.put("fuchsia", FUCHSIA);
|
||||
COLOR_NAME_MAP.put("magenta", MAGENTA);
|
||||
COLOR_NAME_MAP.put("green", GREEN);
|
||||
COLOR_NAME_MAP.put("lime", LIME);
|
||||
COLOR_NAME_MAP.put("olive", OLIVE);
|
||||
COLOR_NAME_MAP.put("yellow", YELLOW);
|
||||
COLOR_NAME_MAP.put("navy", NAVY);
|
||||
COLOR_NAME_MAP.put("blue", BLUE);
|
||||
COLOR_NAME_MAP.put("teal", TEAL);
|
||||
COLOR_NAME_MAP.put("aqua", AQUA);
|
||||
COLOR_NAME_MAP.put("cyan", CYAN);
|
||||
}
|
||||
|
||||
public static int parseColor(String colorExpression) {
|
||||
Assertions.checkArgument(!TextUtils.isEmpty(colorExpression));
|
||||
colorExpression = colorExpression.replace(" ", "");
|
||||
if (colorExpression.charAt(0) == '#') {
|
||||
// Use a long to avoid rollovers on #ffXXXXXX
|
||||
long color = Long.parseLong(colorExpression.substring(1), 16);
|
||||
if (colorExpression.length() == 7) {
|
||||
// Set the alpha value
|
||||
color |= 0x00000000ff000000;
|
||||
} else if (colorExpression.length() != 9) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
return (int) color;
|
||||
} else if (colorExpression.startsWith(RGBA)) {
|
||||
Matcher matcher = RGBA_PATTERN.matcher(colorExpression);
|
||||
if (matcher.matches()) {
|
||||
return argb(
|
||||
255 - Integer.parseInt(matcher.group(4), 10),
|
||||
Integer.parseInt(matcher.group(1), 10),
|
||||
Integer.parseInt(matcher.group(2), 10),
|
||||
Integer.parseInt(matcher.group(3), 10)
|
||||
);
|
||||
}
|
||||
} else if (colorExpression.startsWith(RGB)) {
|
||||
Matcher matcher = RGB_PATTERN.matcher(colorExpression);
|
||||
if (matcher.matches()) {
|
||||
return rgb(
|
||||
Integer.parseInt(matcher.group(1), 10),
|
||||
Integer.parseInt(matcher.group(2), 10),
|
||||
Integer.parseInt(matcher.group(3), 10)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// we use our own color map
|
||||
Integer color = COLOR_NAME_MAP.get(Util.toLowerInvariant(colorExpression));
|
||||
if (color != null) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
private static int argb(int alpha, int red, int green, int blue) {
|
||||
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
private static int rgb(int red, int green, int blue) {
|
||||
return argb(0xFF, red, green, blue);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -23,7 +23,6 @@ import com.google.android.exoplayer.util.MimeTypes;
|
|||
import com.google.android.exoplayer.util.ParserUtil;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.text.Layout;
|
||||
import android.util.Log;
|
||||
|
||||
|
|
@ -207,7 +206,7 @@ public final class TtmlParser implements SubtitleParser {
|
|||
case TtmlNode.ATTR_TTS_BACKGROUND_COLOR:
|
||||
style = createIfNull(style);
|
||||
try {
|
||||
style.setBackgroundColor(Color.parseColor(attributeValue));
|
||||
style.setBackgroundColor(TtmlColorParser.parseColor(attributeValue));
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(TAG, "failed parsing background value: '" + attributeValue + "'");
|
||||
}
|
||||
|
|
@ -215,7 +214,7 @@ public final class TtmlParser implements SubtitleParser {
|
|||
case TtmlNode.ATTR_TTS_COLOR:
|
||||
style = createIfNull(style);
|
||||
try {
|
||||
style.setColor(Color.parseColor(attributeValue));
|
||||
style.setColor(TtmlColorParser.parseColor(attributeValue));
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(TAG, "failed parsing color value: '" + attributeValue + "'");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ public final class MimeTypes {
|
|||
public static final String VIDEO_VP8 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp8";
|
||||
public static final String VIDEO_VP9 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp9";
|
||||
public static final String VIDEO_MP4V = BASE_TYPE_VIDEO + "/mp4v-es";
|
||||
public static final String VIDEO_MPEG2 = BASE_TYPE_VIDEO + "/mpeg2";
|
||||
|
||||
public static final String AUDIO_UNKNOWN = BASE_TYPE_AUDIO + "/x-unknown";
|
||||
public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4";
|
||||
|
|
@ -45,6 +46,7 @@ public final class MimeTypes {
|
|||
public static final String AUDIO_RAW = BASE_TYPE_AUDIO + "/raw";
|
||||
public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3";
|
||||
public static final String AUDIO_EC3 = BASE_TYPE_AUDIO + "/eac3";
|
||||
public static final String AUDIO_TRUEHD = BASE_TYPE_AUDIO + "/true-hd";
|
||||
public static final String AUDIO_DTS = BASE_TYPE_AUDIO + "/vnd.dts";
|
||||
public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd";
|
||||
public static final String AUDIO_VORBIS = BASE_TYPE_AUDIO + "/vorbis";
|
||||
|
|
|
|||
|
|
@ -75,6 +75,12 @@ public final class Util {
|
|||
*/
|
||||
public static final String MANUFACTURER = android.os.Build.MANUFACTURER;
|
||||
|
||||
/**
|
||||
* Like {@link android.os.Build#MODEL}, but in a place where it can be conveniently overridden for
|
||||
* local testing.
|
||||
*/
|
||||
public static final String MODEL = android.os.Build.MODEL;
|
||||
|
||||
private static final Pattern XS_DATE_TIME_PATTERN = Pattern.compile(
|
||||
"(\\d\\d\\d\\d)\\-(\\d\\d)\\-(\\d\\d)[Tt]"
|
||||
+ "(\\d\\d):(\\d\\d):(\\d\\d)(\\.(\\d+))?"
|
||||
|
|
|
|||
10
playbacktests/src/main/.classpath
Normal file
10
playbacktests/src/main/.classpath
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/ExoPlayerLib"/>
|
||||
<classpathentry kind="src" path="java"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
||||
53
playbacktests/src/main/.project
Normal file
53
playbacktests/src/main/.project
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>ExoPlayerPlaybackTests</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
<filteredResources>
|
||||
<filter>
|
||||
<id>1363908154650</id>
|
||||
<name></name>
|
||||
<type>22</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.ui.ide.multiFilter</id>
|
||||
<arguments>1.0-name-matches-false-false-BUILD</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
<filter>
|
||||
<id>1363908154652</id>
|
||||
<name></name>
|
||||
<type>10</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.ui.ide.multiFilter</id>
|
||||
<arguments>1.0-name-matches-true-false-build</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
</filteredResources>
|
||||
</projectDescription>
|
||||
|
|
@ -17,8 +17,8 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.google.android.exoplayer.playbacktests"
|
||||
android:versionCode="1500"
|
||||
android:versionName="1.5.0">
|
||||
android:versionCode="1501"
|
||||
android:versionName="1.5.1">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ public final class H264DashTest extends ActivityInstrumentationTestCase2<HostAct
|
|||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, handler, logger, VIDEO_EVENT_ID,
|
||||
MIN_LOADABLE_RETRY_COUNT);
|
||||
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(
|
||||
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(host,
|
||||
videoSampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, handler, logger, 50);
|
||||
videoCounters = videoRenderer.codecCounters;
|
||||
player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
|
||||
|
|
|
|||
Loading…
Reference in a new issue