Commit 1d315217 by Rik Heijdens

Merge branch 'dev' of github.com:/google/ExoPlayer into mediaformat-id-dash

Syncing my fork
parents 1cefde0d 0545c58d
Showing with 976 additions and 312 deletions
# Release notes # # 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. * 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 ### ### r1.5.0 ###
......
...@@ -21,7 +21,7 @@ buildscript { ...@@ -21,7 +21,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:1.2.3' 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 @@ ...@@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer.demo" package="com.google.android.exoplayer.demo"
android:versionCode="1500" android:versionCode="1501"
android:versionName="1.5.0" android:versionName="1.5.1"
android:theme="@style/RootTheme"> android:theme="@style/RootTheme">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
...@@ -41,9 +41,20 @@ ...@@ -41,9 +41,20 @@
</activity> </activity>
<activity android:name="com.google.android.exoplayer.demo.PlayerActivity" <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: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> </application>
......
...@@ -78,13 +78,19 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -78,13 +78,19 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
DemoPlayer.Listener, DemoPlayer.CaptionListener, DemoPlayer.Id3MetadataListener, DemoPlayer.Listener, DemoPlayer.CaptionListener, DemoPlayer.Id3MetadataListener,
AudioCapabilitiesReceiver.Listener { 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_DASH = 0;
public static final int TYPE_SS = 1; public static final int TYPE_SS = 1;
public static final int TYPE_HLS = 2; public static final int TYPE_HLS = 2;
public static final int TYPE_OTHER = 3; public static final int TYPE_OTHER = 3;
public static final String CONTENT_TYPE_EXTRA = "content_type"; // For use when launching the demo app using adb.
public static final String CONTENT_ID_EXTRA = "content_id"; 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 String TAG = "PlayerActivity";
private static final int MENU_GROUP_TRACKS = 1; private static final int MENU_GROUP_TRACKS = 1;
...@@ -129,11 +135,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -129,11 +135,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(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); setContentView(R.layout.player_activity);
View root = findViewById(R.id.root); View root = findViewById(R.id.root);
root.setOnTouchListener(new OnTouchListener() { root.setOnTouchListener(new OnTouchListener() {
...@@ -150,7 +151,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -150,7 +151,8 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
root.setOnKeyListener(new OnKeyListener() { root.setOnKeyListener(new OnKeyListener() {
@Override @Override
public boolean onKey(View v, int keyCode, KeyEvent event) { 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 false;
} }
return mediaController.dispatchKeyEvent(event); return mediaController.dispatchKeyEvent(event);
...@@ -186,8 +188,20 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -186,8 +188,20 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
} }
@Override @Override
public void onNewIntent(Intent intent) {
releasePlayer();
playerPosition = 0;
setIntent(intent);
}
@Override
public void onResume() { public void onResume() {
super.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(); configureSubtitleView();
if (player == null) { if (player == null) {
preparePlayer(true); preparePlayer(true);
...@@ -597,4 +611,28 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, ...@@ -597,4 +611,28 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle()); 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 { ...@@ -219,8 +219,8 @@ public class DashRendererBuilder implements RendererBuilder {
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
DemoPlayer.TYPE_VIDEO); DemoPlayer.TYPE_VIDEO);
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, videoSampleSource,
drmSessionManager, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, drmSessionManager, true,
mainHandler, player, 50); mainHandler, player, 50);
// Build the audio renderer. // Build the audio renderer.
......
...@@ -61,8 +61,8 @@ public class ExtractorRendererBuilder implements RendererBuilder { ...@@ -61,8 +61,8 @@ public class ExtractorRendererBuilder implements RendererBuilder {
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, allocator, ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, allocator,
BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE); BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, player.getMainHandler(), sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, player.getMainHandler(),
player, 50); player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource, MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
null, true, player.getMainHandler(), player, AudioCapabilities.getCapabilities(context)); null, true, player.getMainHandler(), player, AudioCapabilities.getCapabilities(context));
......
...@@ -148,8 +148,8 @@ public class HlsRendererBuilder implements RendererBuilder { ...@@ -148,8 +148,8 @@ public class HlsRendererBuilder implements RendererBuilder {
variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE); variantIndices, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl, HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl,
BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO); BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50); sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource, MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
null, true, player.getMainHandler(), player, AudioCapabilities.getCapabilities(context)); null, true, player.getMainHandler(), player, AudioCapabilities.getCapabilities(context));
MetadataTrackRenderer<Map<String, Object>> id3Renderer = new MetadataTrackRenderer<>( MetadataTrackRenderer<Map<String, Object>> id3Renderer = new MetadataTrackRenderer<>(
......
...@@ -163,9 +163,9 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder { ...@@ -163,9 +163,9 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder {
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
DemoPlayer.TYPE_VIDEO); DemoPlayer.TYPE_VIDEO);
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource, TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, videoSampleSource,
drmSessionManager, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, drmSessionManager, true, mainHandler,
mainHandler, player, 50); player, 50);
// Build the audio renderer. // Build the audio renderer.
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent); DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
......
...@@ -16,9 +16,9 @@ ...@@ -16,9 +16,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer.demo.webm" package="com.google.android.exoplayer.demo.vp9opus"
android:versionCode="1500" android:versionCode="1501"
android:versionName="1.5.0" android:versionName="1.5.1"
android:theme="@style/RootTheme"> android:theme="@style/RootTheme">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
android:allowBackup="false" android:allowBackup="false"
android:icon="@drawable/ic_launcher"> 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:label="@string/app_name"
android:configChanges="keyboardHidden"> android:configChanges="keyboardHidden">
<intent-filter> <intent-filter>
...@@ -44,12 +44,12 @@ ...@@ -44,12 +44,12 @@
</intent-filter> </intent-filter>
</activity> </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:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/PlayerTheme"/> 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"/> android:theme="@android:style/Theme.Dialog"/>
</application> </application>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl; import com.google.android.exoplayer.LoadControl;
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.Activity;
import android.app.ListActivity; import android.app.ListActivity;
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.Activity;
import android.content.Context; import android.content.Context;
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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.AspectRatioFrameLayout;
import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.ExoPlaybackException;
......
...@@ -77,7 +77,7 @@ publish { ...@@ -77,7 +77,7 @@ publish {
userOrg = 'google' userOrg = 'google'
groupId = 'com.google.android.exoplayer' groupId = 'com.google.android.exoplayer'
artifactId = 'exoplayer' artifactId = 'exoplayer'
version = 'r1.5.0' version = 'r1.5.1'
description = 'The ExoPlayer library.' description = 'The ExoPlayer library.'
website = 'https://github.com/google/ExoPlayer' website = 'https://github.com/google/ExoPlayer'
} }
...@@ -196,6 +196,11 @@ public class DefaultEbmlReaderTest extends TestCase { ...@@ -196,6 +196,11 @@ public class DefaultEbmlReaderTest extends TestCase {
} }
@Override @Override
public boolean isLevel1Element(int id) {
return false;
}
@Override
public void startMasterElement(int id, long contentPosition, long contentSize) { public void startMasterElement(int id, long contentPosition, long contentSize) {
events.add(formatEvent(id, "start contentPosition=" + contentPosition events.add(formatEvent(id, "start contentPosition=" + contentPosition
+ " contentSize=" + contentSize)); + " contentSize=" + contentSize));
......
...@@ -95,17 +95,26 @@ public class VarintReaderTest extends TestCase { ...@@ -95,17 +95,26 @@ public class VarintReaderTest extends TestCase {
int bytesRead = input.read(new byte[1], 0, 1); int bytesRead = input.read(new byte[1], 0, 1);
assertEquals(1, bytesRead); assertEquals(1, bytesRead);
// End of input allowed. // End of input allowed.
long result = reader.readUnsignedVarint(input, true, false); long result = reader.readUnsignedVarint(input, true, false, 8);
assertEquals(-1, result); assertEquals(C.RESULT_END_OF_INPUT, result);
// End of input not allowed. // End of input not allowed.
try { try {
reader.readUnsignedVarint(input, false, false); reader.readUnsignedVarint(input, false, false, 8);
fail(); fail();
} catch (EOFException e) { } catch (EOFException e) {
// Expected. // 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 { public void testReadVarint() throws IOException, InterruptedException {
VarintReader reader = new VarintReader(); VarintReader reader = new VarintReader();
testReadVarint(reader, true, DATA_1_BYTE_0, 1, 0); testReadVarint(reader, true, DATA_1_BYTE_0, 1, 0);
...@@ -183,7 +192,7 @@ public class VarintReaderTest extends TestCase { ...@@ -183,7 +192,7 @@ public class VarintReaderTest extends TestCase {
DataSource dataSource = buildDataSource(data); DataSource dataSource = buildDataSource(data);
dataSource.open(new DataSpec(Uri.parse(TEST_URI))); dataSource.open(new DataSpec(Uri.parse(TEST_URI)));
ExtractorInput input = new DefaultExtractorInput(dataSource, 0, C.LENGTH_UNBOUNDED); 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(expectedLength, input.getPosition());
assertEquals(expectedValue, result); assertEquals(expectedValue, result);
} }
...@@ -198,7 +207,7 @@ public class VarintReaderTest extends TestCase { ...@@ -198,7 +207,7 @@ public class VarintReaderTest extends TestCase {
dataSource.open(new DataSpec(Uri.parse(TEST_URI), position, C.LENGTH_UNBOUNDED, null)); dataSource.open(new DataSpec(Uri.parse(TEST_URI), position, C.LENGTH_UNBOUNDED, null));
input = new DefaultExtractorInput(dataSource, position, C.LENGTH_UNBOUNDED); input = new DefaultExtractorInput(dataSource, position, C.LENGTH_UNBOUNDED);
try { try {
result = reader.readUnsignedVarint(input, false, removeMask); result = reader.readUnsignedVarint(input, false, removeMask, 8);
position = input.getPosition(); position = input.getPosition();
} catch (IOException e) { } catch (IOException e) {
// Expected. We'll try again from the position that the input was advanced to. // Expected. We'll try again from the position that the input was advanced to.
......
/*
* 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; ...@@ -17,7 +17,6 @@ package com.google.android.exoplayer.text.ttml;
import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.Cue;
import android.graphics.Color;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import android.text.Layout; import android.text.Layout;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
...@@ -67,8 +66,8 @@ public final class TtmlParserTest extends InstrumentationTestCase { ...@@ -67,8 +66,8 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0); TtmlNode body = queryChildrenForTag(root, TtmlNode.TAG_BODY, 0);
TtmlNode firstDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0); TtmlNode firstDiv = queryChildrenForTag(body, TtmlNode.TAG_DIV, 0);
TtmlStyle firstPStyle = queryChildrenForTag(firstDiv, TtmlNode.TAG_P, 0).style; TtmlStyle firstPStyle = queryChildrenForTag(firstDiv, TtmlNode.TAG_P, 0).style;
assertEquals(Color.parseColor("yellow"), firstPStyle.getColor()); assertEquals(TtmlColorParser.parseColor("yellow"), firstPStyle.getColor());
assertEquals(Color.parseColor("blue"), firstPStyle.getBackgroundColor()); assertEquals(TtmlColorParser.parseColor("blue"), firstPStyle.getBackgroundColor());
assertEquals("serif", firstPStyle.getFontFamily()); assertEquals("serif", firstPStyle.getFontFamily());
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, firstPStyle.getStyle()); assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, firstPStyle.getStyle());
assertTrue(firstPStyle.isUnderline()); assertTrue(firstPStyle.isUnderline());
...@@ -78,24 +77,43 @@ public final class TtmlParserTest extends InstrumentationTestCase { ...@@ -78,24 +77,43 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(INLINE_ATTRIBUTES_TTML_FILE);
assertEquals(4, subtitle.getEventTimeCount()); assertEquals(4, subtitle.getEventTimeCount());
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC, 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 { public void testInheritGlobalStyle() throws IOException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_TTML_FILE);
assertEquals(2, subtitle.getEventTimeCount()); assertEquals(2, subtitle.getEventTimeCount());
assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC, 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 { public void testInheritGlobalStyleOverriddenByInlineAttributes() throws IOException {
TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_OVERRIDE_TTML_FILE); TtmlSubtitle subtitle = getSubtitle(INHERIT_STYLE_OVERRIDE_TTML_FILE);
assertEquals(4, subtitle.getEventTimeCount()); assertEquals(4, subtitle.getEventTimeCount());
assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC, Color.BLUE, assertSpans(subtitle, 10, "text 1", "serif", TtmlStyle.STYLE_BOLD_ITALIC, TtmlColorParser.BLUE,
Color.YELLOW, true, false, null); TtmlColorParser.YELLOW, true, false, null);
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC, Color.RED, assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_ITALIC, TtmlColorParser.RED,
Color.YELLOW, true, false, null); TtmlColorParser.YELLOW, true, false, null);
} }
public void testInheritGlobalAndParent() throws IOException { public void testInheritGlobalAndParent() throws IOException {
...@@ -103,9 +121,10 @@ public final class TtmlParserTest extends InstrumentationTestCase { ...@@ -103,9 +121,10 @@ public final class TtmlParserTest extends InstrumentationTestCase {
assertEquals(4, subtitle.getEventTimeCount()); assertEquals(4, subtitle.getEventTimeCount());
assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_NORMAL, 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, 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 { public void testInheritMultipleStyles() throws IOException {
...@@ -113,7 +132,7 @@ public final class TtmlParserTest extends InstrumentationTestCase { ...@@ -113,7 +132,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
assertEquals(12, subtitle.getEventTimeCount()); assertEquals(12, subtitle.getEventTimeCount());
assertSpans(subtitle, 10, "text 1", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC, 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 { public void testInheritMultipleStylesWithoutLocalAttributes() throws IOException {
...@@ -121,7 +140,7 @@ public final class TtmlParserTest extends InstrumentationTestCase { ...@@ -121,7 +140,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
assertEquals(12, subtitle.getEventTimeCount()); assertEquals(12, subtitle.getEventTimeCount());
assertSpans(subtitle, 20, "text 2", "sansSerif", TtmlStyle.STYLE_BOLD_ITALIC, 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 { ...@@ -130,7 +149,7 @@ public final class TtmlParserTest extends InstrumentationTestCase {
assertEquals(12, subtitle.getEventTimeCount()); assertEquals(12, subtitle.getEventTimeCount());
assertSpans(subtitle, 30, "text 2.5", "sansSerifInline", TtmlStyle.STYLE_ITALIC, 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 { public void testEmptyStyleAttribute() throws IOException {
...@@ -175,16 +194,16 @@ public final class TtmlParserTest extends InstrumentationTestCase { ...@@ -175,16 +194,16 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlStyle style = globalStyles.get("s2"); TtmlStyle style = globalStyles.get("s2");
assertEquals("serif", style.getFontFamily()); assertEquals("serif", style.getFontFamily());
assertEquals(Color.RED, style.getBackgroundColor()); assertEquals(TtmlColorParser.RED, style.getBackgroundColor());
assertEquals(Color.BLACK, style.getColor()); assertEquals(TtmlColorParser.BLACK, style.getColor());
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle()); assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
assertTrue(style.isLinethrough()); assertTrue(style.isLinethrough());
style = globalStyles.get("s3"); style = globalStyles.get("s3");
// only difference: color must be RED // only difference: color must be RED
assertEquals(Color.RED, style.getColor()); assertEquals(TtmlColorParser.RED, style.getColor());
assertEquals("serif", style.getFontFamily()); assertEquals("serif", style.getFontFamily());
assertEquals(Color.RED, style.getBackgroundColor()); assertEquals(TtmlColorParser.RED, style.getBackgroundColor());
assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle()); assertEquals(TtmlStyle.STYLE_BOLD_ITALIC, style.getStyle());
assertTrue(style.isLinethrough()); assertTrue(style.isLinethrough());
} }
...@@ -224,8 +243,8 @@ public final class TtmlParserTest extends InstrumentationTestCase { ...@@ -224,8 +243,8 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style; TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
assertNotNull(style); assertNotNull(style);
assertEquals(Color.BLACK, style.getBackgroundColor()); assertEquals(TtmlColorParser.BLACK, style.getBackgroundColor());
assertEquals(Color.YELLOW, style.getColor()); assertEquals(TtmlColorParser.YELLOW, style.getColor());
assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle()); assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle());
assertEquals("sansSerif", style.getFontFamily()); assertEquals("sansSerif", style.getFontFamily());
assertFalse(style.isUnderline()); assertFalse(style.isUnderline());
...@@ -243,8 +262,8 @@ public final class TtmlParserTest extends InstrumentationTestCase { ...@@ -243,8 +262,8 @@ public final class TtmlParserTest extends InstrumentationTestCase {
TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style; TtmlStyle style = queryChildrenForTag(div, TtmlNode.TAG_P, 0).style;
assertNotNull(style); assertNotNull(style);
assertEquals(Color.BLACK, style.getBackgroundColor()); assertEquals(TtmlColorParser.BLACK, style.getBackgroundColor());
assertEquals(Color.YELLOW, style.getColor()); assertEquals(TtmlColorParser.YELLOW, style.getColor());
assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle()); assertEquals(TtmlStyle.STYLE_ITALIC, style.getStyle());
assertEquals("sansSerif", style.getFontFamily()); assertEquals("sansSerif", style.getFontFamily());
assertFalse(style.isUnderline()); assertFalse(style.isUnderline());
......
...@@ -68,6 +68,11 @@ public final class C { ...@@ -68,6 +68,11 @@ public final class C {
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3; 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 * @see MediaExtractor#SAMPLE_FLAG_SYNC
*/ */
...@@ -90,6 +95,11 @@ public final class C { ...@@ -90,6 +95,11 @@ public final class C {
*/ */
public static final int RESULT_END_OF_INPUT = -1; 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() {} private C() {}
} }
...@@ -23,7 +23,7 @@ public final class ExoPlayerLibraryInfo { ...@@ -23,7 +23,7 @@ public final class ExoPlayerLibraryInfo {
/** /**
* The version of the library, expressed as a string. * 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. * The version of the library, expressed as an integer.
...@@ -31,7 +31,7 @@ public final class ExoPlayerLibraryInfo { ...@@ -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 * Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the
* corresponding integer version 001002003. * 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} * Whether the library was compiled with {@link com.google.android.exoplayer.util.Assertions}
......
...@@ -643,7 +643,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer ...@@ -643,7 +643,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
adaptiveReconfigurationBytes); adaptiveReconfigurationBytes);
codec.queueSecureInputBuffer(inputIndex, 0, cryptoInfo, presentationTimeUs, 0); codec.queueSecureInputBuffer(inputIndex, 0, cryptoInfo, presentationTimeUs, 0);
} else { } else {
codec.queueInputBuffer(inputIndex, 0 , bufferSize, presentationTimeUs, 0); codec.queueInputBuffer(inputIndex, 0, bufferSize, presentationTimeUs, 0);
} }
inputIndex = -1; inputIndex = -1;
codecHasQueuedBuffers = true; codecHasQueuedBuffers = true;
...@@ -922,11 +922,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer ...@@ -922,11 +922,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
* propagation incorrectly on the host device. False otherwise. * propagation incorrectly on the host device. False otherwise.
*/ */
private static boolean codecNeedsEosPropagationWorkaround(String name) { private static boolean codecNeedsEosPropagationWorkaround(String name) {
return Util.SDK_INT <= 17 return Util.SDK_INT <= 17 && "OMX.rk.video_decoder.avc".equals(name);
&& "OMX.rk.video_decoder.avc".equals(name)
&& ("ht7s3".equals(Util.DEVICE) // Tesco HUDL
|| "rk30sdk".equals(Util.DEVICE) // Rockchip rk30
|| "rk31sdk".equals(Util.DEVICE)); // Rockchip rk31
} }
/** /**
......
...@@ -94,8 +94,14 @@ public final class MediaCodecUtil { ...@@ -94,8 +94,14 @@ public final class MediaCodecUtil {
/** /**
* Returns the name of the best decoder and its capabilities for the given mimeType. * 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 { String mimeType, boolean secure) throws DecoderQueryException {
CodecKey key = new CodecKey(mimeType, secure); CodecKey key = new CodecKey(mimeType, secure);
if (codecs.containsKey(key)) { if (codecs.containsKey(key)) {
...@@ -202,8 +208,10 @@ public final class MediaCodecUtil { ...@@ -202,8 +208,10 @@ public final class MediaCodecUtil {
return false; return false;
} }
// Work around an issue where the VP8 decoder on Samsung Galaxy S4 Mini does not render video. // Work around an issue where the VP8 decoder on Samsung Galaxy S3/S4 Mini does not render
if (Util.SDK_INT <= 19 && Util.DEVICE != null && Util.DEVICE.startsWith("serrano") // 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")) { && "samsung".equals(Util.MANUFACTURER) && name.equals("OMX.SEC.vp8.dec")) {
return false; return false;
} }
......
...@@ -23,6 +23,7 @@ import com.google.android.exoplayer.util.Util; ...@@ -23,6 +23,7 @@ import com.google.android.exoplayer.util.Util;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCrypto; import android.media.MediaCrypto;
import android.os.Handler; import android.os.Handler;
...@@ -86,34 +87,6 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -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]. // 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_LEFT = "crop-left";
private static final String KEY_CROP_RIGHT = "crop-right"; private static final String KEY_CROP_RIGHT = "crop-right";
...@@ -127,7 +100,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -127,7 +100,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
*/ */
public static final int MSG_SET_SURFACE = 1; public static final int MSG_SET_SURFACE = 1;
private final FrameReleaseTimeHelper frameReleaseTimeHelper; private final VideoFrameReleaseTimeHelper frameReleaseTimeHelper;
private final EventListener eventListener; private final EventListener eventListener;
private final long allowedJoiningTimeUs; private final long allowedJoiningTimeUs;
private final int videoScalingMode; private final int videoScalingMode;
...@@ -152,64 +125,30 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -152,64 +125,30 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
private float lastReportedPixelWidthHeightRatio; private float lastReportedPixelWidthHeightRatio;
/** /**
* @param context A context.
* @param source The upstream source from which the renderer obtains samples. * @param source The upstream source from which the renderer obtains samples.
* @param videoScalingMode The scaling mode to pass to * @param videoScalingMode The scaling mode to pass to
* {@link MediaCodec#setVideoScalingMode(int)}. * {@link MediaCodec#setVideoScalingMode(int)}.
*/ */
public MediaCodecVideoTrackRenderer(SampleSource source, int videoScalingMode) { public MediaCodecVideoTrackRenderer(Context context, SampleSource source, int videoScalingMode) {
this(source, null, true, videoScalingMode); this(context, source, videoScalingMode, 0);
}
/**
* @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);
} }
/** /**
* @param context A context.
* @param source The upstream source from which the renderer obtains samples. * @param source The upstream source from which the renderer obtains samples.
* @param videoScalingMode The scaling mode to pass to * @param videoScalingMode The scaling mode to pass to
* {@link MediaCodec#setVideoScalingMode(int)}. * {@link MediaCodec#setVideoScalingMode(int)}.
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
* can attempt to seamlessly join an ongoing playback. * can attempt to seamlessly join an ongoing playback.
*/ */
public MediaCodecVideoTrackRenderer(SampleSource source, int videoScalingMode, public MediaCodecVideoTrackRenderer(Context context, SampleSource source, int videoScalingMode,
long allowedJoiningTimeMs) { long allowedJoiningTimeMs) {
this(source, null, true, videoScalingMode, allowedJoiningTimeMs); this(context, source, videoScalingMode, allowedJoiningTimeMs, null, null, -1);
}
/**
* @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);
} }
/** /**
* @param context A context.
* @param source The upstream source from which the renderer obtains samples. * @param source The upstream source from which the renderer obtains samples.
* @param videoScalingMode The scaling mode to pass to * @param videoScalingMode The scaling mode to pass to
* {@link MediaCodec#setVideoScalingMode(int)}. * {@link MediaCodec#setVideoScalingMode(int)}.
...@@ -221,15 +160,20 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -221,15 +160,20 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
* @param maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between * @param maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between
* invocations of {@link EventListener#onDroppedFrames(int, long)}. * 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, long allowedJoiningTimeMs, Handler eventHandler, EventListener eventListener,
int maxDroppedFrameCountToNotify) { int maxDroppedFrameCountToNotify) {
this(source, null, true, videoScalingMode, allowedJoiningTimeMs, null, eventHandler, this(context, source, videoScalingMode, allowedJoiningTimeMs, null, false, eventHandler,
eventListener, maxDroppedFrameCountToNotify); eventListener, maxDroppedFrameCountToNotify);
} }
/** /**
* @param context A context.
* @param source The upstream source from which the renderer obtains samples. * @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 * @param drmSessionManager For use with encrypted content. May be null if support for encrypted
* content is not required. * content is not required.
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
...@@ -237,26 +181,20 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -237,26 +181,20 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
* begin in parallel with key acquisision. This parameter specifies whether the renderer is * 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} * permitted to play clear regions of encrypted media files before {@code drmSessionManager}
* has obtained the keys necessary to decrypt encrypted regions of the media. * 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 * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * 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 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 * @param maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between
* invocations of {@link EventListener#onDroppedFrames(int, long)}. * invocations of {@link EventListener#onDroppedFrames(int, long)}.
*/ */
public MediaCodecVideoTrackRenderer(SampleSource source, DrmSessionManager drmSessionManager, public MediaCodecVideoTrackRenderer(Context context, SampleSource source, int videoScalingMode,
boolean playClearSamplesWithoutKeys, int videoScalingMode, long allowedJoiningTimeMs, long allowedJoiningTimeMs, DrmSessionManager drmSessionManager,
FrameReleaseTimeHelper frameReleaseTimeHelper, Handler eventHandler, boolean playClearSamplesWithoutKeys, Handler eventHandler, EventListener eventListener,
EventListener eventListener, int maxDroppedFrameCountToNotify) { int maxDroppedFrameCountToNotify) {
super(source, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, eventListener); super(source, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, eventListener);
this.frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(context);
this.videoScalingMode = videoScalingMode; this.videoScalingMode = videoScalingMode;
this.allowedJoiningTimeUs = allowedJoiningTimeMs * 1000; this.allowedJoiningTimeUs = allowedJoiningTimeMs * 1000;
this.frameReleaseTimeHelper = frameReleaseTimeHelper;
this.eventListener = eventListener; this.eventListener = eventListener;
this.maxDroppedFrameCountToNotify = maxDroppedFrameCountToNotify; this.maxDroppedFrameCountToNotify = maxDroppedFrameCountToNotify;
joiningDeadlineUs = -1; joiningDeadlineUs = -1;
...@@ -285,9 +223,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -285,9 +223,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
if (joining && allowedJoiningTimeUs > 0) { if (joining && allowedJoiningTimeUs > 0) {
joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs; joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs;
} }
if (frameReleaseTimeHelper != null) { frameReleaseTimeHelper.enable();
frameReleaseTimeHelper.enable();
}
} }
@Override @Override
...@@ -340,9 +276,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -340,9 +276,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
lastReportedWidth = -1; lastReportedWidth = -1;
lastReportedHeight = -1; lastReportedHeight = -1;
lastReportedPixelWidthHeightRatio = -1; lastReportedPixelWidthHeightRatio = -1;
if (frameReleaseTimeHelper != null) { frameReleaseTimeHelper.disable();
frameReleaseTimeHelper.disable();
}
super.onDisabled(); super.onDisabled();
} }
...@@ -468,14 +402,9 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -468,14 +402,9 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
long unadjustedFrameReleaseTimeNs = systemTimeNs + (earlyUs * 1000); long unadjustedFrameReleaseTimeNs = systemTimeNs + (earlyUs * 1000);
// Apply a timestamp adjustment, if there is one. // Apply a timestamp adjustment, if there is one.
long adjustedReleaseTimeNs; long adjustedReleaseTimeNs = frameReleaseTimeHelper.adjustReleaseTime(
if (frameReleaseTimeHelper != null) { bufferInfo.presentationTimeUs, unadjustedFrameReleaseTimeNs);
adjustedReleaseTimeNs = frameReleaseTimeHelper.adjustReleaseTime( earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000;
bufferInfo.presentationTimeUs, unadjustedFrameReleaseTimeNs);
earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000;
} else {
adjustedReleaseTimeNs = unadjustedFrameReleaseTimeNs;
}
if (earlyUs < -30000) { if (earlyUs < -30000) {
// We're more than 30ms late rendering the frame. // We're more than 30ms late rendering the frame.
...@@ -560,6 +489,11 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { ...@@ -560,6 +489,11 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
// Already set. The source of the format may know better, so do nothing. // Already set. The source of the format may know better, so do nothing.
return; 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); int maxHeight = format.getInteger(android.media.MediaFormat.KEY_HEIGHT);
if (codecIsAdaptive && format.containsKey(android.media.MediaFormat.KEY_MAX_HEIGHT)) { if (codecIsAdaptive && format.containsKey(android.media.MediaFormat.KEY_MAX_HEIGHT)) {
maxHeight = Math.max(maxHeight, format.getInteger(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 { ...@@ -209,6 +209,12 @@ public final class MediaFormat {
this.maxHeight = maxHeight; 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) { public MediaFormat copyWithMaxVideoDimensions(int maxWidth, int maxHeight) {
return new MediaFormat(id, trackId, mimeType, bitrate, maxInputSize, durationUs, width, height, return new MediaFormat(id, trackId, mimeType, bitrate, maxInputSize, durationUs, width, height,
rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language,
......
...@@ -15,17 +15,17 @@ ...@@ -15,17 +15,17 @@
*/ */
package com.google.android.exoplayer; package com.google.android.exoplayer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer.FrameReleaseTimeHelper;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context;
import android.view.Choreographer; import android.view.Choreographer;
import android.view.Choreographer.FrameCallback; import android.view.Choreographer.FrameCallback;
import android.view.WindowManager;
/** /**
* Makes a best effort to adjust frame release timestamps for a smoother visual result. * Makes a best effort to adjust frame release timestamps for a smoother visual result.
*/ */
@TargetApi(16) @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 CHOREOGRAPHER_SAMPLE_DELAY_MILLIS = 500;
private static final long MAX_ALLOWED_DRIFT_NS = 20000000; private static final long MAX_ALLOWED_DRIFT_NS = 20000000;
...@@ -33,32 +33,45 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe ...@@ -33,32 +33,45 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
private static final long VSYNC_OFFSET_PERCENTAGE = 80; private static final long VSYNC_OFFSET_PERCENTAGE = 80;
private static final int MIN_FRAMES_FOR_ADJUSTMENT = 6; private static final int MIN_FRAMES_FOR_ADJUSTMENT = 6;
private final boolean usePrimaryDisplayVsync; private final boolean useDefaultDisplayVsync;
private final long vsyncDurationNs; private final long vsyncDurationNs;
private final long vsyncOffsetNs; private final long vsyncOffsetNs;
private Choreographer choreographer; private Choreographer choreographer;
private long sampledVsyncTimeNs; private long sampledVsyncTimeNs;
private long lastUnadjustedFrameTimeUs; private long lastFramePresentationTimeUs;
private long adjustedLastFrameTimeNs; private long adjustedLastFrameTimeNs;
private long pendingAdjustedFrameTimeNs; private long pendingAdjustedFrameTimeNs;
private boolean haveSync; private boolean haveSync;
private long syncReleaseTimeNs; private long syncUnadjustedReleaseTimeNs;
private long syncFrameTimeNs; private long syncFramePresentationTimeNs;
private int frameCount; private long frameCount;
/**
* Constructs an instance that smoothes frame release but does not snap release to the default
* display's vsync signal.
*/
public VideoFrameReleaseTimeHelper() {
this(-1, false);
}
/** /**
* @param primaryDisplayRefreshRate The refresh rate of the default display. * Constructs an instance that smoothes frame release and snaps release to the default display's
* @param usePrimaryDisplayVsync Whether to snap to the primary display vsync. May not be * vsync signal.
* suitable when rendering to secondary displays. *
* @param context A context from which information about the default display can be retrieved.
*/ */
public SmoothFrameReleaseTimeHelper( public VideoFrameReleaseTimeHelper(Context context) {
float primaryDisplayRefreshRate, boolean usePrimaryDisplayVsync) { this(getDefaultDisplayRefreshRate(context), true);
this.usePrimaryDisplayVsync = usePrimaryDisplayVsync; }
if (usePrimaryDisplayVsync) {
vsyncDurationNs = (long) (1000000000d / primaryDisplayRefreshRate); private VideoFrameReleaseTimeHelper(float defaultDisplayRefreshRate,
boolean useDefaultDisplayVsync) {
this.useDefaultDisplayVsync = useDefaultDisplayVsync;
if (useDefaultDisplayVsync) {
vsyncDurationNs = (long) (1000000000d / defaultDisplayRefreshRate);
vsyncOffsetNs = (vsyncDurationNs * VSYNC_OFFSET_PERCENTAGE) / 100; vsyncOffsetNs = (vsyncDurationNs * VSYNC_OFFSET_PERCENTAGE) / 100;
} else { } else {
vsyncDurationNs = -1; vsyncDurationNs = -1;
...@@ -66,19 +79,23 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe ...@@ -66,19 +79,23 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
} }
} }
@Override /**
* Enables the helper.
*/
public void enable() { public void enable() {
haveSync = false; haveSync = false;
if (usePrimaryDisplayVsync) { if (useDefaultDisplayVsync) {
sampledVsyncTimeNs = 0; sampledVsyncTimeNs = 0;
choreographer = Choreographer.getInstance(); choreographer = Choreographer.getInstance();
choreographer.postFrameCallback(this); choreographer.postFrameCallback(this);
} }
} }
@Override /**
* Disables the helper.
*/
public void disable() { public void disable() {
if (usePrimaryDisplayVsync) { if (useDefaultDisplayVsync) {
choreographer.removeFrameCallback(this); choreographer.removeFrameCallback(this);
choreographer = null; choreographer = null;
} }
...@@ -90,17 +107,25 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe ...@@ -90,17 +107,25 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
choreographer.postFrameCallbackDelayed(this, CHOREOGRAPHER_SAMPLE_DELAY_MILLIS); choreographer.postFrameCallbackDelayed(this, CHOREOGRAPHER_SAMPLE_DELAY_MILLIS);
} }
@Override /**
public long adjustReleaseTime(long unadjustedFrameTimeUs, long unadjustedReleaseTimeNs) { * Called to make a fine-grained adjustment to a frame release time.
long unadjustedFrameTimeNs = unadjustedFrameTimeUs * 1000; *
* @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. // Until we know better, the adjustment will be a no-op.
long adjustedFrameTimeNs = unadjustedFrameTimeNs; long adjustedFrameTimeNs = framePresentationTimeNs;
long adjustedReleaseTimeNs = unadjustedReleaseTimeNs; long adjustedReleaseTimeNs = unadjustedReleaseTimeNs;
if (haveSync) { if (haveSync) {
// See if we've advanced to the next frame. // See if we've advanced to the next frame.
if (unadjustedFrameTimeUs != lastUnadjustedFrameTimeUs) { if (framePresentationTimeUs != lastFramePresentationTimeUs) {
frameCount++; frameCount++;
adjustedLastFrameTimeNs = pendingAdjustedFrameTimeNs; adjustedLastFrameTimeNs = pendingAdjustedFrameTimeNs;
} }
...@@ -109,20 +134,22 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe ...@@ -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. // 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 // This will typically give us a frame rate at a finer granularity than the frame times
// themselves (which often only have millisecond granularity). // 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. // Project the adjusted frame time forward using the average.
long candidateAdjustedFrameTimeNs = adjustedLastFrameTimeNs + averageFrameTimeNs; long candidateAdjustedFrameTimeNs = adjustedLastFrameTimeNs + averageFrameDurationNs;
if (isDriftTooLarge(candidateAdjustedFrameTimeNs, unadjustedReleaseTimeNs)) { if (isDriftTooLarge(candidateAdjustedFrameTimeNs, unadjustedReleaseTimeNs)) {
haveSync = false; haveSync = false;
} else { } else {
adjustedFrameTimeNs = candidateAdjustedFrameTimeNs; adjustedFrameTimeNs = candidateAdjustedFrameTimeNs;
adjustedReleaseTimeNs = syncReleaseTimeNs + adjustedFrameTimeNs - syncFrameTimeNs; adjustedReleaseTimeNs = syncUnadjustedReleaseTimeNs + adjustedFrameTimeNs
- syncFramePresentationTimeNs;
} }
} else { } else {
// We're synced but haven't waited the required number of frames to apply an adjustment. // We're synced but haven't waited the required number of frames to apply an adjustment.
// Check drift anyway. // Check drift anyway.
if (isDriftTooLarge(unadjustedFrameTimeNs, unadjustedReleaseTimeNs)) { if (isDriftTooLarge(framePresentationTimeNs, unadjustedReleaseTimeNs)) {
haveSync = false; haveSync = false;
} }
} }
...@@ -130,14 +157,14 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe ...@@ -130,14 +157,14 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
// If we need to sync, do so now. // If we need to sync, do so now.
if (!haveSync) { if (!haveSync) {
syncFrameTimeNs = unadjustedFrameTimeNs; syncFramePresentationTimeNs = framePresentationTimeNs;
syncReleaseTimeNs = unadjustedReleaseTimeNs; syncUnadjustedReleaseTimeNs = unadjustedReleaseTimeNs;
frameCount = 0; frameCount = 0;
haveSync = true; haveSync = true;
onSynced(); onSynced();
} }
lastUnadjustedFrameTimeUs = unadjustedFrameTimeUs; lastFramePresentationTimeUs = framePresentationTimeUs;
pendingAdjustedFrameTimeNs = adjustedFrameTimeNs; pendingAdjustedFrameTimeNs = adjustedFrameTimeNs;
if (sampledVsyncTimeNs == 0) { if (sampledVsyncTimeNs == 0) {
...@@ -155,8 +182,8 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe ...@@ -155,8 +182,8 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
} }
private boolean isDriftTooLarge(long frameTimeNs, long releaseTimeNs) { private boolean isDriftTooLarge(long frameTimeNs, long releaseTimeNs) {
long elapsedFrameTimeNs = frameTimeNs - syncFrameTimeNs; long elapsedFrameTimeNs = frameTimeNs - syncFramePresentationTimeNs;
long elapsedReleaseTimeNs = releaseTimeNs - syncReleaseTimeNs; long elapsedReleaseTimeNs = releaseTimeNs - syncUnadjustedReleaseTimeNs;
return Math.abs(elapsedReleaseTimeNs - elapsedFrameTimeNs) > MAX_ALLOWED_DRIFT_NS; return Math.abs(elapsedReleaseTimeNs - elapsedFrameTimeNs) > MAX_ALLOWED_DRIFT_NS;
} }
...@@ -177,4 +204,9 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe ...@@ -177,4 +204,9 @@ public final class SmoothFrameReleaseTimeHelper implements FrameReleaseTimeHelpe
return snappedAfterDiff < snappedBeforeDiff ? snappedAfterNs : snappedBeforeNs; 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 { ...@@ -202,7 +202,7 @@ public final class AudioTrack {
private int temporaryBufferSize; 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; private int passthroughBitrate;
...@@ -359,7 +359,7 @@ public final class AudioTrack { ...@@ -359,7 +359,7 @@ public final class AudioTrack {
} }
} }
audioTrackUtil.reconfigure(audioTrack, isPassthrough()); audioTrackUtil.reconfigure(audioTrack, needsPassthroughWorkarounds());
setAudioTrackVolume(); setAudioTrackVolume();
return sessionId; return sessionId;
...@@ -472,8 +472,7 @@ public final class AudioTrack { ...@@ -472,8 +472,7 @@ public final class AudioTrack {
return RESULT_BUFFER_CONSUMED; return RESULT_BUFFER_CONSUMED;
} }
// Workarounds for issues with AC-3 passthrough AudioTracks on API versions 21/22: if (needsPassthroughWorkarounds()) {
if (Util.SDK_INT <= 22 && isPassthrough()) {
// An AC-3 audio track continues to play data written while it is paused. Stop writing so its // An AC-3 audio track continues to play data written while it is paused. Stop writing so its
// buffer empties. See [Internal: b/18899620]. // buffer empties. See [Internal: b/18899620].
if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED) { if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED) {
...@@ -491,8 +490,14 @@ public final class AudioTrack { ...@@ -491,8 +490,14 @@ public final class AudioTrack {
int result = 0; int result = 0;
if (temporaryBufferSize == 0) { if (temporaryBufferSize == 0) {
if (isPassthrough() && passthroughBitrate == UNKNOWN_BITRATE) { if (passthroughBitrate == UNKNOWN_BITRATE) {
passthroughBitrate = Ac3Util.getBitrate(size, sampleRate); 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}. // This is the first time we've seen this {@code buffer}.
...@@ -583,7 +588,7 @@ public final class AudioTrack { ...@@ -583,7 +588,7 @@ public final class AudioTrack {
public boolean hasPendingData() { public boolean hasPendingData() {
return isInitialized() return isInitialized()
&& (bytesToFrames(submittedBytes) > audioTrackUtil.getPlaybackHeadPosition() && (bytesToFrames(submittedBytes) > audioTrackUtil.getPlaybackHeadPosition()
|| audioTrackUtil.overrideHasPendingData()); || overrideHasPendingData());
} }
/** Sets the playback volume. */ /** Sets the playback volume. */
...@@ -709,10 +714,13 @@ public final class AudioTrack { ...@@ -709,10 +714,13 @@ public final class AudioTrack {
} }
} }
// Don't sample the timestamp and latency if this is a passthrough AudioTrack, as the returned if (needsPassthroughWorkarounds()) {
// values cause audio/video synchronization to be incorrect. // Don't sample the timestamp and latency if this is an AC-3 passthrough AudioTrack on
if (!isPassthrough() // platform API versions 21/22, as incorrect values are returned. See [Internal: b/21145353].
&& systemClockUs - lastTimestampSampleTimeUs >= MIN_TIMESTAMP_SAMPLE_INTERVAL_US) { return;
}
if (systemClockUs - lastTimestampSampleTimeUs >= MIN_TIMESTAMP_SAMPLE_INTERVAL_US) {
audioTimestampSet = audioTrackUtil.updateTimestamp(); audioTimestampSet = audioTrackUtil.updateTimestamp();
if (audioTimestampSet) { if (audioTimestampSet) {
// Perform sanity checks on the timestamp. // Perform sanity checks on the timestamp.
...@@ -818,17 +826,50 @@ public final class AudioTrack { ...@@ -818,17 +826,50 @@ public final class AudioTrack {
} }
private boolean isPassthrough() { private boolean isPassthrough() {
return isAc3Passthrough() || isDtsPassthrough();
}
private boolean isAc3Passthrough() {
return encoding == C.ENCODING_AC3 || encoding == C.ENCODING_E_AC3; 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) { private static int getEncodingForMimeType(String mimeType) {
if (MimeTypes.AUDIO_AC3.equals(mimeType)) { switch (mimeType) {
return C.ENCODING_AC3; case MimeTypes.AUDIO_AC3:
} return C.ENCODING_AC3;
if (MimeTypes.AUDIO_EC3.equals(mimeType)) { case MimeTypes.AUDIO_EC3:
return C.ENCODING_E_AC3; 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;
} }
return AudioFormat.ENCODING_INVALID;
} }
/** /**
...@@ -837,7 +878,7 @@ public final class AudioTrack { ...@@ -837,7 +878,7 @@ public final class AudioTrack {
private static class AudioTrackUtil { private static class AudioTrackUtil {
protected android.media.AudioTrack audioTrack; protected android.media.AudioTrack audioTrack;
private boolean isPassthrough; private boolean needsPassthroughWorkaround;
private int sampleRate; private int sampleRate;
private long lastRawPlaybackHeadPosition; private long lastRawPlaybackHeadPosition;
private long rawPlaybackHeadWrapCount; private long rawPlaybackHeadWrapCount;
...@@ -851,11 +892,13 @@ public final class AudioTrack { ...@@ -851,11 +892,13 @@ public final class AudioTrack {
* Reconfigures the audio track utility helper to use the specified {@code audioTrack}. * Reconfigures the audio track utility helper to use the specified {@code audioTrack}.
* *
* @param audioTrack The audio track to wrap. * @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.audioTrack = audioTrack;
this.isPassthrough = isPassthrough; this.needsPassthroughWorkaround = needsPassthroughWorkaround;
stopTimestampUs = -1; stopTimestampUs = -1;
lastRawPlaybackHeadPosition = 0; lastRawPlaybackHeadPosition = 0;
rawPlaybackHeadWrapCount = 0; rawPlaybackHeadWrapCount = 0;
...@@ -866,20 +909,6 @@ public final class AudioTrack { ...@@ -866,20 +909,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 * 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 * that {@link #getPlaybackHeadPosition()} and {@link #getPlaybackHeadPositionUs()} continue to
* increment as the remaining media is played out. * increment as the remaining media is played out.
...@@ -929,7 +958,7 @@ public final class AudioTrack { ...@@ -929,7 +958,7 @@ public final class AudioTrack {
} }
long rawPlaybackHeadPosition = 0xFFFFFFFFL & audioTrack.getPlaybackHeadPosition(); 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 // 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 // where the playback head position jumps back to zero on paused passthrough/direct audio
// tracks. See [Internal: b/19187573]. // tracks. See [Internal: b/19187573].
...@@ -1009,8 +1038,9 @@ public final class AudioTrack { ...@@ -1009,8 +1038,9 @@ public final class AudioTrack {
} }
@Override @Override
public void reconfigure(android.media.AudioTrack audioTrack, boolean isPassthrough) { public void reconfigure(android.media.AudioTrack audioTrack,
super.reconfigure(audioTrack, isPassthrough); boolean needsPassthroughWorkaround) {
super.reconfigure(audioTrack, needsPassthroughWorkaround);
rawTimestampFramePositionWrapCount = 0; rawTimestampFramePositionWrapCount = 0;
lastRawTimestampFramePosition = 0; lastRawTimestampFramePosition = 0;
lastTimestampFramePosition = 0; lastTimestampFramePosition = 0;
......
...@@ -55,11 +55,9 @@ public final class VideoFormatSelectorUtil { ...@@ -55,11 +55,9 @@ public final class VideoFormatSelectorUtil {
public static int[] selectVideoFormatsForDefaultDisplay(Context context, public static int[] selectVideoFormatsForDefaultDisplay(Context context,
List<? extends FormatWrapper> formatWrappers, String[] allowedContainerMimeTypes, List<? extends FormatWrapper> formatWrappers, String[] allowedContainerMimeTypes,
boolean filterHdFormats) throws DecoderQueryException { boolean filterHdFormats) throws DecoderQueryException {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Point viewportSize = getViewportSize(context);
Display display = windowManager.getDefaultDisplay();
Point displaySize = getDisplaySize(display);
return selectVideoFormats(formatWrappers, allowedContainerMimeTypes, filterHdFormats, true, return selectVideoFormats(formatWrappers, allowedContainerMimeTypes, filterHdFormats, true,
displaySize.x, displaySize.y); viewportSize.x, viewportSize.y);
} }
/** /**
...@@ -184,6 +182,19 @@ public final class VideoFormatSelectorUtil { ...@@ -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) { private static Point getDisplaySize(Display display) {
Point displaySize = new Point(); Point displaySize = new Point();
if (Util.SDK_INT >= 17) { if (Util.SDK_INT >= 17) {
......
...@@ -92,6 +92,7 @@ import java.util.List; ...@@ -92,6 +92,7 @@ import java.util.List;
public static final int TYPE_enca = Util.getIntegerCodeForString("enca"); public static final int TYPE_enca = Util.getIntegerCodeForString("enca");
public static final int TYPE_frma = Util.getIntegerCodeForString("frma"); public static final int TYPE_frma = Util.getIntegerCodeForString("frma");
public static final int TYPE_saiz = Util.getIntegerCodeForString("saiz"); 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_uuid = Util.getIntegerCodeForString("uuid");
public static final int TYPE_senc = Util.getIntegerCodeForString("senc"); public static final int TYPE_senc = Util.getIntegerCodeForString("senc");
public static final int TYPE_pasp = Util.getIntegerCodeForString("pasp"); public static final int TYPE_pasp = Util.getIntegerCodeForString("pasp");
...@@ -219,6 +220,31 @@ import java.util.List; ...@@ -219,6 +220,31 @@ import java.util.List;
return null; 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 @Override
public String toString() { public String toString() {
return getAtomTypeString(type) return getAtomTypeString(type)
......
...@@ -106,10 +106,11 @@ import java.util.List; ...@@ -106,10 +106,11 @@ import java.util.List;
long[] offsets = new long[sampleCount]; long[] offsets = new long[sampleCount];
int[] sizes = new int[sampleCount]; int[] sizes = new int[sampleCount];
int maximumSize = 0;
long[] timestamps = new long[sampleCount]; long[] timestamps = new long[sampleCount];
int[] flags = new int[sampleCount]; int[] flags = new int[sampleCount];
if (sampleCount == 0) { if (sampleCount == 0) {
return new TrackSampleTable(offsets, sizes, timestamps, flags); return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags);
} }
// Prepare to read chunk offsets. // Prepare to read chunk offsets.
...@@ -172,6 +173,9 @@ import java.util.List; ...@@ -172,6 +173,9 @@ import java.util.List;
for (int i = 0; i < sampleCount; i++) { for (int i = 0; i < sampleCount; i++) {
offsets[i] = offsetBytes; offsets[i] = offsetBytes;
sizes[i] = fixedSampleSize == 0 ? stsz.readUnsignedIntToInt() : fixedSampleSize; sizes[i] = fixedSampleSize == 0 ? stsz.readUnsignedIntToInt() : fixedSampleSize;
if (sizes[i] > maximumSize) {
maximumSize = sizes[i];
}
timestamps[i] = timestampTimeUnits + timestampOffset; timestamps[i] = timestampTimeUnits + timestampOffset;
// All samples are synchronization samples if the stss is not present. // All samples are synchronization samples if the stss is not present.
...@@ -244,7 +248,7 @@ import java.util.List; ...@@ -244,7 +248,7 @@ import java.util.List;
Assertions.checkArgument(remainingSamplesInChunk == 0); Assertions.checkArgument(remainingSamplesInChunk == 0);
Assertions.checkArgument(remainingTimestampDeltaChanges == 0); Assertions.checkArgument(remainingTimestampDeltaChanges == 0);
Assertions.checkArgument(remainingTimestampOffsetChanges == 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 { ...@@ -53,6 +53,11 @@ public final class FragmentedMp4Extractor implements Extractor {
*/ */
public static final int WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1; 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 = 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}; 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 { ...@@ -81,6 +86,7 @@ public final class FragmentedMp4Extractor implements Extractor {
private long atomSize; private long atomSize;
private int atomHeaderBytesRead; private int atomHeaderBytesRead;
private ParsableByteArray atomData; private ParsableByteArray atomData;
private long endOfMdatPosition;
private int sampleIndex; private int sampleIndex;
private int sampleSize; private int sampleSize;
...@@ -199,7 +205,15 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -199,7 +205,15 @@ public final class FragmentedMp4Extractor implements Extractor {
atomSize = atomHeader.readUnsignedLongToLong(); 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) { if (atomType == Atom.TYPE_mdat) {
endOfMdatPosition = atomPosition + atomSize;
if (!haveOutputSeekMap) { if (!haveOutputSeekMap) {
extractorOutput.seekMap(SeekMap.UNSEEKABLE); extractorOutput.seekMap(SeekMap.UNSEEKABLE);
haveOutputSeekMap = true; haveOutputSeekMap = true;
...@@ -319,6 +333,8 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -319,6 +333,8 @@ public final class FragmentedMp4Extractor implements Extractor {
private static void parseMoof(Track track, DefaultSampleValues extendsDefaults, private static void parseMoof(Track track, DefaultSampleValues extendsDefaults,
ContainerAtom moof, TrackFragment out, int workaroundFlags, byte[] extendedTypeScratch) { 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), parseTraf(track, extendsDefaults, moof.getContainerAtomOfType(Atom.TYPE_traf),
out, workaroundFlags, extendedTypeScratch); out, workaroundFlags, extendedTypeScratch);
} }
...@@ -328,23 +344,34 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -328,23 +344,34 @@ public final class FragmentedMp4Extractor implements Extractor {
*/ */
private static void parseTraf(Track track, DefaultSampleValues extendsDefaults, private static void parseTraf(Track track, DefaultSampleValues extendsDefaults,
ContainerAtom traf, TrackFragment out, int workaroundFlags, byte[] extendedTypeScratch) { 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); 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); LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
DefaultSampleValues fragmentHeader = parseTfhd(extendsDefaults, tfhd.data); parseTfhd(extendsDefaults, tfhd.data, out);
out.sampleDescriptionIndex = fragmentHeader.sampleDescriptionIndex;
LeafAtom trun = traf.getLeafAtomOfType(Atom.TYPE_trun); 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); LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
if (saiz != null) { if (saiz != null) {
TrackEncryptionBox trackEncryptionBox = TrackEncryptionBox trackEncryptionBox =
track.sampleDescriptionEncryptionBoxes[fragmentHeader.sampleDescriptionIndex]; track.sampleDescriptionEncryptionBoxes[out.header.sampleDescriptionIndex];
parseSaiz(trackEncryptionBox, saiz.data, out); 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); LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc);
if (senc != null) { if (senc != null) {
parseSenc(senc.data, out); parseSenc(senc.data, out);
...@@ -392,20 +419,48 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -392,20 +419,48 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
/** /**
* 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). * Parses a tfhd atom (defined in 14496-12).
* *
* @param extendsDefaults Default sample values from the trex atom. * @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, private static void parseTfhd(DefaultSampleValues extendsDefaults, ParsableByteArray tfhd,
ParsableByteArray tfhd) { TrackFragment out) {
tfhd.setPosition(Atom.HEADER_SIZE); tfhd.setPosition(Atom.HEADER_SIZE);
int fullAtom = tfhd.readInt(); int fullAtom = tfhd.readInt();
int flags = Atom.parseFullAtomFlags(fullAtom); int flags = Atom.parseFullAtomFlags(fullAtom);
tfhd.skipBytes(4); // trackId tfhd.skipBytes(4); // trackId
if ((flags & 0x01 /* base_data_offset_present */) != 0) { if ((flags & 0x01 /* base_data_offset_present */) != 0) {
tfhd.skipBytes(8); long baseDataPosition = tfhd.readUnsignedLongToLong();
out.dataPosition = baseDataPosition;
out.auxiliaryDataPosition = baseDataPosition;
} }
int defaultSampleDescriptionIndex = int defaultSampleDescriptionIndex =
...@@ -417,7 +472,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -417,7 +472,7 @@ public final class FragmentedMp4Extractor implements Extractor {
? tfhd.readUnsignedIntToInt() : extendsDefaults.size; ? tfhd.readUnsignedIntToInt() : extendsDefaults.size;
int defaultSampleFlags = ((flags & 0x20 /* default_sample_flags_present */) != 0) int defaultSampleFlags = ((flags & 0x20 /* default_sample_flags_present */) != 0)
? tfhd.readUnsignedIntToInt() : extendsDefaults.flags; ? tfhd.readUnsignedIntToInt() : extendsDefaults.flags;
return new DefaultSampleValues(defaultSampleDescriptionIndex, defaultSampleDuration, out.header = new DefaultSampleValues(defaultSampleDescriptionIndex, defaultSampleDuration,
defaultSampleSize, defaultSampleFlags); defaultSampleSize, defaultSampleFlags);
} }
...@@ -451,7 +506,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -451,7 +506,7 @@ public final class FragmentedMp4Extractor implements Extractor {
int sampleCount = trun.readUnsignedIntToInt(); int sampleCount = trun.readUnsignedIntToInt();
if ((flags & 0x01 /* data_offset_present */) != 0) { if ((flags & 0x01 /* data_offset_present */) != 0) {
trun.skipBytes(4); out.dataPosition += trun.readInt();
} }
boolean firstSampleFlagsPresent = (flags & 0x04 /* first_sample_flags_present */) != 0; boolean firstSampleFlagsPresent = (flags & 0x04 /* first_sample_flags_present */) != 0;
...@@ -475,8 +530,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -475,8 +530,7 @@ public final class FragmentedMp4Extractor implements Extractor {
long timescale = track.timescale; long timescale = track.timescale;
long cumulativeTime = decodeTime; long cumulativeTime = decodeTime;
boolean workaroundEveryVideoFrameIsSyncFrame = track.type == Track.TYPE_vide boolean workaroundEveryVideoFrameIsSyncFrame = track.type == Track.TYPE_vide
&& ((workaroundFlags & WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME) && (workaroundFlags & WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME) != 0;
== WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME);
for (int i = 0; i < sampleCount; i++) { for (int i = 0; i < sampleCount; i++) {
// Use trun values if present, otherwise tfhd, otherwise trex. // Use trun values if present, otherwise tfhd, otherwise trex.
int sampleDuration = sampleDurationsPresent ? trun.readUnsignedIntToInt() int sampleDuration = sampleDurationsPresent ? trun.readUnsignedIntToInt()
...@@ -601,6 +655,9 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -601,6 +655,9 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
private void readEncryptionData(ExtractorInput input) throws IOException, InterruptedException { 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); fragmentRun.fillEncryptionData(input);
parserState = STATE_READING_SAMPLE_START; parserState = STATE_READING_SAMPLE_START;
} }
...@@ -620,7 +677,16 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -620,7 +677,16 @@ public final class FragmentedMp4Extractor implements Extractor {
* @throws InterruptedException If the thread is interrupted. * @throws InterruptedException If the thread is interrupted.
*/ */
private boolean readSample(ExtractorInput input) throws IOException, InterruptedException { 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) { 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. // We've run out of samples in the current mdat atom.
enterReadingAtomHeaderState(); enterReadingAtomHeaderState();
return false; return false;
...@@ -678,8 +744,9 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -678,8 +744,9 @@ public final class FragmentedMp4Extractor implements Extractor {
long sampleTimeUs = fragmentRun.getSamplePresentationTime(sampleIndex) * 1000L; long sampleTimeUs = fragmentRun.getSamplePresentationTime(sampleIndex) * 1000L;
int sampleFlags = (fragmentRun.definesEncryptionData ? C.SAMPLE_FLAG_ENCRYPTED : 0) int sampleFlags = (fragmentRun.definesEncryptionData ? C.SAMPLE_FLAG_ENCRYPTED : 0)
| (fragmentRun.sampleIsSyncFrameTable[sampleIndex] ? C.SAMPLE_FLAG_SYNC : 0); | (fragmentRun.sampleIsSyncFrameTable[sampleIndex] ? C.SAMPLE_FLAG_SYNC : 0);
int sampleDescriptionIndex = fragmentRun.header.sampleDescriptionIndex;
byte[] encryptionKey = fragmentRun.definesEncryptionData byte[] encryptionKey = fragmentRun.definesEncryptionData
? track.sampleDescriptionEncryptionBoxes[fragmentRun.sampleDescriptionIndex].keyId : null; ? track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId : null;
trackOutput.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey); trackOutput.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey);
sampleIndex++; sampleIndex++;
...@@ -688,8 +755,9 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -688,8 +755,9 @@ public final class FragmentedMp4Extractor implements Extractor {
} }
private int appendSampleEncryptionData(ParsableByteArray sampleEncryptionData) { private int appendSampleEncryptionData(ParsableByteArray sampleEncryptionData) {
int sampleDescriptionIndex = fragmentRun.header.sampleDescriptionIndex;
TrackEncryptionBox encryptionBox = TrackEncryptionBox encryptionBox =
track.sampleDescriptionEncryptionBoxes[fragmentRun.sampleDescriptionIndex]; track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex];
int vectorSize = encryptionBox.initializationVectorSize; int vectorSize = encryptionBox.initializationVectorSize;
boolean subsampleEncryption = fragmentRun.sampleHasSubsampleEncryptionTable[sampleIndex]; boolean subsampleEncryption = fragmentRun.sampleHasSubsampleEncryptionTable[sampleIndex];
...@@ -721,8 +789,8 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -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_traf || atom == Atom.TYPE_trak || atom == Atom.TYPE_trex
|| atom == Atom.TYPE_trun || atom == Atom.TYPE_mvex || atom == Atom.TYPE_mdia || 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_minf || atom == Atom.TYPE_stbl || atom == Atom.TYPE_pssh
|| atom == Atom.TYPE_saiz || atom == Atom.TYPE_uuid || atom == Atom.TYPE_senc || atom == Atom.TYPE_saiz || atom == Atom.TYPE_saio || atom == Atom.TYPE_uuid
|| atom == Atom.TYPE_pasp || atom == Atom.TYPE_s263; || 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}. */ /** Returns whether the extractor should parse a container atom with type {@code atom}. */
......
...@@ -262,7 +262,10 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -262,7 +262,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
} }
Mp4Track mp4Track = new Mp4Track(track, trackSampleTable, extractorOutput.track(i)); 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); tracks.add(mp4Track);
long firstSampleOffset = trackSampleTable.offsets[0]; long firstSampleOffset = trackSampleTable.offsets[0];
......
...@@ -25,8 +25,18 @@ import java.io.IOException; ...@@ -25,8 +25,18 @@ import java.io.IOException;
*/ */
/* package */ final class TrackFragment { /* 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. * The number of samples contained by the fragment.
*/ */
......
...@@ -19,31 +19,49 @@ import com.google.android.exoplayer.C; ...@@ -19,31 +19,49 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util; 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 { /* 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; public static final int NO_SAMPLE = -1;
/** Number of samples. */ /**
* Number of samples.
*/
public final int sampleCount; public final int sampleCount;
/** Sample offsets in bytes. */ /**
* Sample offsets in bytes.
*/
public final long[] offsets; public final long[] offsets;
/** Sample sizes in bytes. */ /**
* Sample sizes in bytes.
*/
public final int[] sizes; 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; public final long[] timestampsUs;
/** Sample flags. */ /**
* Sample flags.
*/
public final int[] flags; public final int[] flags;
TrackSampleTable( TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs, int[] flags) {
long[] offsets, int[] sizes, long[] timestampsUs, int[] flags) {
Assertions.checkArgument(sizes.length == timestampsUs.length); Assertions.checkArgument(sizes.length == timestampsUs.length);
Assertions.checkArgument(offsets.length == timestampsUs.length); Assertions.checkArgument(offsets.length == timestampsUs.length);
Assertions.checkArgument(flags.length == timestampsUs.length); Assertions.checkArgument(flags.length == timestampsUs.length);
this.offsets = offsets; this.offsets = offsets;
this.sizes = sizes; this.sizes = sizes;
this.maximumSize = maximumSize;
this.timestampsUs = timestampsUs; this.timestampsUs = timestampsUs;
this.flags = flags; this.flags = flags;
sampleCount = offsets.length; sampleCount = offsets.length;
......
...@@ -19,6 +19,7 @@ import com.google.android.exoplayer.C; ...@@ -19,6 +19,7 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Stack; import java.util.Stack;
...@@ -32,6 +33,9 @@ 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_SIZE = 1;
private static final int ELEMENT_STATE_READ_CONTENT = 2; 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 MAX_INTEGER_ELEMENT_SIZE_BYTES = 8;
private static final int VALID_FLOAT32_ELEMENT_SIZE_BYTES = 4; private static final int VALID_FLOAT32_ELEMENT_SIZE_BYTES = 4;
private static final int VALID_FLOAT64_ELEMENT_SIZE_BYTES = 8; private static final int VALID_FLOAT64_ELEMENT_SIZE_BYTES = 8;
...@@ -68,8 +72,11 @@ import java.util.Stack; ...@@ -68,8 +72,11 @@ import java.util.Stack;
} }
if (elementState == ELEMENT_STATE_READ_ID) { if (elementState == ELEMENT_STATE_READ_ID) {
long result = varintReader.readUnsignedVarint(input, true, false); long result = varintReader.readUnsignedVarint(input, true, false, MAX_ID_BYTES);
if (result == -1) { if (result == C.RESULT_MAX_LENGTH_EXCEEDED) {
result = maybeResyncToNextLevel1Element(input);
}
if (result == C.RESULT_END_OF_INPUT) {
return false; return false;
} }
// Element IDs are at most 4 bytes, so we can cast to integers. // Element IDs are at most 4 bytes, so we can cast to integers.
...@@ -78,7 +85,7 @@ import java.util.Stack; ...@@ -78,7 +85,7 @@ import java.util.Stack;
} }
if (elementState == ELEMENT_STATE_READ_CONTENT_SIZE) { 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; elementState = ELEMENT_STATE_READ_CONTENT;
} }
...@@ -128,6 +135,35 @@ import java.util.Stack; ...@@ -128,6 +135,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}. * Reads and returns an integer of length {@code byteLength} from the {@link ExtractorInput}.
* *
* @param input The {@link ExtractorInput} from which to read. * @param input The {@link ExtractorInput} from which to read.
......
...@@ -37,6 +37,14 @@ import java.io.IOException; ...@@ -37,6 +37,14 @@ import java.io.IOException;
int getElementType(int id); 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. * Called when the start of a master element is encountered.
* <p> * <p>
* Following events should be considered as taking place within this element until a matching call * Following events should be considered as taking place within this element until a matching call
......
package com.google.android.exoplayer.extractor.webm; package com.google.android.exoplayer.extractor.webm;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.ExtractorInput;
import java.io.EOFException; import java.io.EOFException;
...@@ -19,8 +20,8 @@ import java.io.IOException; ...@@ -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. * <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[] { private static final long[] VARINT_LENGTH_MASKS = new long[] {
0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 0x80L, 0x40L, 0x20L, 0x10L, 0x08L, 0x04L, 0x02L, 0x01L
}; };
private final byte[] scratch; private final byte[] scratch;
...@@ -53,48 +54,41 @@ import java.io.IOException; ...@@ -53,48 +54,41 @@ import java.io.IOException;
* *
* @param input The {@link ExtractorInput} from which the integer should be read. * @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 * @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 * allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it
* considered an error, causing an {@link EOFException} to be thrown. * 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 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 * @param maximumAllowedLength Maximum allowed length of the variable integer to be read.
* encountered. * @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 IOException If an error occurs reading from the input.
* @throws InterruptedException If the thread is interrupted. * @throws InterruptedException If the thread is interrupted.
*/ */
public long readUnsignedVarint(ExtractorInput input, boolean allowEndOfInput, public long readUnsignedVarint(ExtractorInput input, boolean allowEndOfInput,
boolean removeLengthMask) throws IOException, InterruptedException { boolean removeLengthMask, int maximumAllowedLength) throws IOException, InterruptedException {
if (state == STATE_BEGIN_READING) { if (state == STATE_BEGIN_READING) {
// Read the first byte to establish the length. // Read the first byte to establish the length.
if (!input.readFully(scratch, 0, 1, allowEndOfInput)) { if (!input.readFully(scratch, 0, 1, allowEndOfInput)) {
return -1; return C.RESULT_END_OF_INPUT;
} }
int firstByte = scratch[0] & 0xFF; int firstByte = scratch[0] & 0xFF;
length = -1; length = parseUnsignedVarintLength(firstByte);
for (int i = 0; i < VARINT_LENGTH_MASKS.length; i++) {
if ((VARINT_LENGTH_MASKS[i] & firstByte) != 0) {
length = i + 1;
break;
}
}
if (length == -1) { if (length == -1) {
throw new IllegalStateException("No valid varint length mask found"); throw new IllegalStateException("No valid varint length mask found");
} }
state = STATE_READ_CONTENTS; state = STATE_READ_CONTENTS;
} }
if (length > maximumAllowedLength) {
state = STATE_BEGIN_READING;
return C.RESULT_MAX_LENGTH_EXCEEDED;
}
// Read the remaining bytes. // Read the remaining bytes.
input.readFully(scratch, 1, length - 1); 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; state = STATE_BEGIN_READING;
return varint; return assembleVarint(scratch, length, removeLengthMask);
} }
/** /**
...@@ -104,4 +98,41 @@ import java.io.IOException; ...@@ -104,4 +98,41 @@ import java.io.IOException;
return length; 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 { ...@@ -64,6 +64,7 @@ public final class WebmExtractor implements Extractor {
private static final String DOC_TYPE_MATROSKA = "matroska"; private static final String DOC_TYPE_MATROSKA = "matroska";
private static final String CODEC_ID_VP8 = "V_VP8"; private static final String CODEC_ID_VP8 = "V_VP8";
private static final String CODEC_ID_VP9 = "V_VP9"; 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_SP = "V_MPEG4/ISO/SP";
private static final String CODEC_ID_MPEG4_ASP = "V_MPEG4/ISO/ASP"; private static final String CODEC_ID_MPEG4_ASP = "V_MPEG4/ISO/ASP";
private static final String CODEC_ID_MPEG4_AP = "V_MPEG4/ISO/AP"; private static final String CODEC_ID_MPEG4_AP = "V_MPEG4/ISO/AP";
...@@ -74,6 +75,7 @@ public final class WebmExtractor implements Extractor { ...@@ -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_AAC = "A_AAC";
private static final String CODEC_ID_MP3 = "A_MPEG/L3"; 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_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 = "A_DTS";
private static final String CODEC_ID_DTS_EXPRESS = "A_DTS/EXPRESS"; private static final String CODEC_ID_DTS_EXPRESS = "A_DTS/EXPRESS";
private static final String CODEC_ID_DTS_LOSSLESS = "A_DTS/LOSSLESS"; private static final String CODEC_ID_DTS_LOSSLESS = "A_DTS/LOSSLESS";
...@@ -347,6 +349,10 @@ public final class WebmExtractor implements Extractor { ...@@ -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) /* package */ void startMasterElement(int id, long contentPosition, long contentSize)
throws ParserException { throws ParserException {
switch (id) { switch (id) {
...@@ -639,7 +645,7 @@ public final class WebmExtractor implements Extractor { ...@@ -639,7 +645,7 @@ public final class WebmExtractor implements Extractor {
// differ only in the way flags are specified. // differ only in the way flags are specified.
if (blockState == BLOCK_STATE_START) { if (blockState == BLOCK_STATE_START) {
blockTrackNumber = (int) varintReader.readUnsignedVarint(input, false, true); blockTrackNumber = (int) varintReader.readUnsignedVarint(input, false, true, 8);
blockTrackNumberLength = varintReader.getLastLength(); blockTrackNumberLength = varintReader.getLastLength();
blockDurationUs = UNKNOWN; blockDurationUs = UNKNOWN;
blockState = BLOCK_STATE_HEADER; blockState = BLOCK_STATE_HEADER;
...@@ -1028,6 +1034,7 @@ public final class WebmExtractor implements Extractor { ...@@ -1028,6 +1034,7 @@ public final class WebmExtractor implements Extractor {
private static boolean isCodecSupported(String codecId) { private static boolean isCodecSupported(String codecId) {
return CODEC_ID_VP8.equals(codecId) return CODEC_ID_VP8.equals(codecId)
|| CODEC_ID_VP9.equals(codecId) || CODEC_ID_VP9.equals(codecId)
|| CODEC_ID_MPEG2.equals(codecId)
|| CODEC_ID_MPEG4_SP.equals(codecId) || CODEC_ID_MPEG4_SP.equals(codecId)
|| CODEC_ID_MPEG4_ASP.equals(codecId) || CODEC_ID_MPEG4_ASP.equals(codecId)
|| CODEC_ID_MPEG4_AP.equals(codecId) || CODEC_ID_MPEG4_AP.equals(codecId)
...@@ -1038,6 +1045,7 @@ public final class WebmExtractor implements Extractor { ...@@ -1038,6 +1045,7 @@ public final class WebmExtractor implements Extractor {
|| CODEC_ID_AAC.equals(codecId) || CODEC_ID_AAC.equals(codecId)
|| CODEC_ID_MP3.equals(codecId) || CODEC_ID_MP3.equals(codecId)
|| CODEC_ID_AC3.equals(codecId) || CODEC_ID_AC3.equals(codecId)
|| CODEC_ID_TRUEHD.equals(codecId)
|| CODEC_ID_DTS.equals(codecId) || CODEC_ID_DTS.equals(codecId)
|| CODEC_ID_DTS_EXPRESS.equals(codecId) || CODEC_ID_DTS_EXPRESS.equals(codecId)
|| CODEC_ID_DTS_LOSSLESS.equals(codecId) || CODEC_ID_DTS_LOSSLESS.equals(codecId)
...@@ -1070,6 +1078,11 @@ public final class WebmExtractor implements Extractor { ...@@ -1070,6 +1078,11 @@ public final class WebmExtractor implements Extractor {
} }
@Override @Override
public boolean isLevel1Element(int id) {
return WebmExtractor.this.isLevel1Element(id);
}
@Override
public void startMasterElement(int id, long contentPosition, long contentSize) public void startMasterElement(int id, long contentPosition, long contentSize)
throws ParserException { throws ParserException {
WebmExtractor.this.startMasterElement(id, contentPosition, contentSize); WebmExtractor.this.startMasterElement(id, contentPosition, contentSize);
...@@ -1147,6 +1160,9 @@ public final class WebmExtractor implements Extractor { ...@@ -1147,6 +1160,9 @@ public final class WebmExtractor implements Extractor {
case CODEC_ID_VP9: case CODEC_ID_VP9:
mimeType = MimeTypes.VIDEO_VP9; mimeType = MimeTypes.VIDEO_VP9;
break; break;
case CODEC_ID_MPEG2:
mimeType = MimeTypes.VIDEO_MPEG2;
break;
case CODEC_ID_MPEG4_SP: case CODEC_ID_MPEG4_SP:
case CODEC_ID_MPEG4_ASP: case CODEC_ID_MPEG4_ASP:
case CODEC_ID_MPEG4_AP: case CODEC_ID_MPEG4_AP:
...@@ -1194,6 +1210,9 @@ public final class WebmExtractor implements Extractor { ...@@ -1194,6 +1210,9 @@ public final class WebmExtractor implements Extractor {
case CODEC_ID_AC3: case CODEC_ID_AC3:
mimeType = MimeTypes.AUDIO_AC3; mimeType = MimeTypes.AUDIO_AC3;
break; break;
case CODEC_ID_TRUEHD:
mimeType = MimeTypes.AUDIO_TRUEHD;
break;
case CODEC_ID_DTS: case CODEC_ID_DTS:
case CODEC_ID_DTS_EXPRESS: case CODEC_ID_DTS_EXPRESS:
mimeType = MimeTypes.AUDIO_DTS; mimeType = MimeTypes.AUDIO_DTS;
......
...@@ -427,7 +427,8 @@ public class SmoothStreamingChunkSource implements ChunkSource, ...@@ -427,7 +427,8 @@ public class SmoothStreamingChunkSource implements ChunkSource,
// Build the extractor. // Build the extractor.
FragmentedMp4Extractor mp4Extractor = new FragmentedMp4Extractor( 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, Track mp4Track = new Track(trackIndex, mp4TrackType, element.timescale, durationUs, mediaFormat,
trackEncryptionBoxes, mp4TrackType == Track.TYPE_vide ? 4 : -1); trackEncryptionBoxes, mp4TrackType == Track.TYPE_vide ? 4 : -1);
mp4Extractor.setTrack(mp4Track); mp4Extractor.setTrack(mp4Track);
......
/*
* 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; ...@@ -23,7 +23,6 @@ import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.ParserUtil; import com.google.android.exoplayer.util.ParserUtil;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
import android.graphics.Color;
import android.text.Layout; import android.text.Layout;
import android.util.Log; import android.util.Log;
...@@ -207,7 +206,7 @@ public final class TtmlParser implements SubtitleParser { ...@@ -207,7 +206,7 @@ public final class TtmlParser implements SubtitleParser {
case TtmlNode.ATTR_TTS_BACKGROUND_COLOR: case TtmlNode.ATTR_TTS_BACKGROUND_COLOR:
style = createIfNull(style); style = createIfNull(style);
try { try {
style.setBackgroundColor(Color.parseColor(attributeValue)); style.setBackgroundColor(TtmlColorParser.parseColor(attributeValue));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Log.w(TAG, "failed parsing background value: '" + attributeValue + "'"); Log.w(TAG, "failed parsing background value: '" + attributeValue + "'");
} }
...@@ -215,7 +214,7 @@ public final class TtmlParser implements SubtitleParser { ...@@ -215,7 +214,7 @@ public final class TtmlParser implements SubtitleParser {
case TtmlNode.ATTR_TTS_COLOR: case TtmlNode.ATTR_TTS_COLOR:
style = createIfNull(style); style = createIfNull(style);
try { try {
style.setColor(Color.parseColor(attributeValue)); style.setColor(TtmlColorParser.parseColor(attributeValue));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Log.w(TAG, "failed parsing color value: '" + attributeValue + "'"); Log.w(TAG, "failed parsing color value: '" + attributeValue + "'");
} }
......
...@@ -34,6 +34,7 @@ public final class MimeTypes { ...@@ -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_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_VP9 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp9";
public static final String VIDEO_MP4V = BASE_TYPE_VIDEO + "/mp4v-es"; public static final String VIDEO_MP4V = BASE_TYPE_VIDEO + "/mp4v-es";
public static final String VIDEO_MPEG2 = BASE_TYPE_VIDEO + "/mpeg2";
public static final String AUDIO_UNKNOWN = BASE_TYPE_AUDIO + "/x-unknown"; public static final String AUDIO_UNKNOWN = BASE_TYPE_AUDIO + "/x-unknown";
public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4"; public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4";
...@@ -45,6 +46,7 @@ public final class MimeTypes { ...@@ -45,6 +46,7 @@ public final class MimeTypes {
public static final String AUDIO_RAW = BASE_TYPE_AUDIO + "/raw"; 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_AC3 = BASE_TYPE_AUDIO + "/ac3";
public static final String AUDIO_EC3 = BASE_TYPE_AUDIO + "/eac3"; 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 = BASE_TYPE_AUDIO + "/vnd.dts";
public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd"; public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd";
public static final String AUDIO_VORBIS = BASE_TYPE_AUDIO + "/vorbis"; public static final String AUDIO_VORBIS = BASE_TYPE_AUDIO + "/vorbis";
......
...@@ -75,6 +75,12 @@ public final class Util { ...@@ -75,6 +75,12 @@ public final class Util {
*/ */
public static final String MANUFACTURER = android.os.Build.MANUFACTURER; 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( 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\\d)[Tt]"
+ "(\\d\\d):(\\d\\d):(\\d\\d)(\\.(\\d+))?" + "(\\d\\d):(\\d\\d):(\\d\\d)(\\.(\\d+))?"
......
<?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>
<?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 @@ ...@@ -17,8 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer.playbacktests" package="com.google.android.exoplayer.playbacktests"
android:versionCode="1500" android:versionCode="1501"
android:versionName="1.5.0"> android:versionName="1.5.1">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>
......
...@@ -217,7 +217,7 @@ public final class H264DashTest extends ActivityInstrumentationTestCase2<HostAct ...@@ -217,7 +217,7 @@ public final class H264DashTest extends ActivityInstrumentationTestCase2<HostAct
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl, ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, handler, logger, VIDEO_EVENT_ID, VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, handler, logger, VIDEO_EVENT_ID,
MIN_LOADABLE_RETRY_COUNT); 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); videoSampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, handler, logger, 50);
videoCounters = videoRenderer.codecCounters; videoCounters = videoRenderer.codecCounters;
player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface); player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment