Add GL demo app
Demonstrates rendering to a GLSurfaceView while applying a GL shader. Issue: #6920 PiperOrigin-RevId: 293551724
This commit is contained in:
parent
e3f43ee2ce
commit
0dd8e6a7c1
@ -42,6 +42,10 @@
|
||||
* UI
|
||||
* move logic of prev, next, fast forward and rewind to ControlDispatcher
|
||||
[#6926](https://github.com/google/ExoPlayer/issues/6926)).
|
||||
* Demo apps: Add
|
||||
[GL demo app](https://github.com/google/ExoPlayer/tree/dev-v2/demos/gl) to
|
||||
show how to render video to a `GLSurfaceView` while applying a GL shader.
|
||||
([#6920](https://github.com/google/ExoPlayer/issues/6920)).
|
||||
|
||||
### 2.11.2 (TBD) ###
|
||||
|
||||
|
11
demos/gl/README.md
Normal file
11
demos/gl/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# ExoPlayer GL demo
|
||||
|
||||
This app demonstrates how to render video to a [GLSurfaceView][] while applying
|
||||
a GL shader.
|
||||
|
||||
The shader shows an overlap bitmap on top of the video. The overlay bitmap is
|
||||
drawn using an Android canvas, and includes the current frame's presentation
|
||||
timestamp, to show how to get the timestamp of the frame currently in the
|
||||
off-screen surface texture.
|
||||
|
||||
[GLSurfaceView]: https://developer.android.com/reference/android/opengl/GLSurfaceView
|
53
demos/gl/build.gradle
Normal file
53
demos/gl/build.gradle
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright (C) 2020 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.
|
||||
apply from: '../../constants.gradle'
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion project.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
versionName project.ext.releaseVersion
|
||||
versionCode project.ext.releaseVersionCode
|
||||
minSdkVersion project.ext.minSdkVersion
|
||||
targetSdkVersion project.ext.appTargetSdkVersion
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
shrinkResources true
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt')
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
// This demo app does not have translations.
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(modulePrefix + 'library-core')
|
||||
implementation project(modulePrefix + 'library-ui')
|
||||
implementation project(modulePrefix + 'library-dash')
|
||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion
|
||||
}
|
49
demos/gl/src/main/AndroidManifest.xml
Normal file
49
demos/gl/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2020 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.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.exoplayer2.gldemo">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<uses-sdk/>
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/application_name">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.exoplayer.gldemo.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>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,35 @@
|
||||
// Copyright 2020 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.
|
||||
|
||||
#extension GL_OES_EGL_image_external : require
|
||||
precision mediump float;
|
||||
// External texture containing video decoder output.
|
||||
uniform samplerExternalOES tex_sampler_0;
|
||||
// Texture containing the overlap bitmap.
|
||||
uniform sampler2D tex_sampler_1;
|
||||
// Horizontal scaling factor for the overlap bitmap.
|
||||
uniform float scaleX;
|
||||
// Vertical scaling factory for the overlap bitmap.
|
||||
uniform float scaleY;
|
||||
varying vec2 v_texcoord;
|
||||
void main() {
|
||||
vec4 videoColor = texture2D(tex_sampler_0, v_texcoord);
|
||||
vec4 overlayColor = texture2D(tex_sampler_1,
|
||||
vec2(v_texcoord.x * scaleX,
|
||||
v_texcoord.y * scaleY));
|
||||
// Blend the video decoder output and the overlay bitmap.
|
||||
gl_FragColor = videoColor * (1.0 - overlayColor.a)
|
||||
+ overlayColor * overlayColor.a;
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
// Copyright 2020 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.
|
||||
attribute vec4 a_position;
|
||||
attribute vec3 a_texcoord;
|
||||
varying vec2 v_texcoord;
|
||||
void main() {
|
||||
gl_Position = a_position;
|
||||
v_texcoord = a_texcoord.xy;
|
||||
}
|
||||
|
@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.exoplayer2.gldemo;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.opengl.GLES20;
|
||||
import android.opengl.GLUtils;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.GlUtil;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Locale;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
|
||||
/**
|
||||
* Video processor that demonstrates how to overlay a bitmap on video output using a GL shader. The
|
||||
* bitmap is drawn using an Android {@link Canvas}.
|
||||
*/
|
||||
/* package */ final class BitmapOverlayVideoProcessor
|
||||
implements VideoProcessingGLSurfaceView.VideoProcessor {
|
||||
|
||||
private static final int OVERLAY_WIDTH = 512;
|
||||
private static final int OVERLAY_HEIGHT = 256;
|
||||
|
||||
private final Context context;
|
||||
private final Paint paint;
|
||||
private final int[] textures;
|
||||
private final Bitmap overlayBitmap;
|
||||
private final Bitmap logoBitmap;
|
||||
private final Canvas overlayCanvas;
|
||||
|
||||
private int program;
|
||||
@Nullable private GlUtil.Attribute[] attributes;
|
||||
@Nullable private GlUtil.Uniform[] uniforms;
|
||||
|
||||
private float bitmapScaleX;
|
||||
private float bitmapScaleY;
|
||||
|
||||
public BitmapOverlayVideoProcessor(Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
paint = new Paint();
|
||||
paint.setTextSize(64);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setARGB(0xFF, 0xFF, 0xFF, 0xFF);
|
||||
textures = new int[1];
|
||||
overlayBitmap = Bitmap.createBitmap(OVERLAY_WIDTH, OVERLAY_HEIGHT, Bitmap.Config.ARGB_8888);
|
||||
overlayCanvas = new Canvas(overlayBitmap);
|
||||
try {
|
||||
logoBitmap =
|
||||
((BitmapDrawable)
|
||||
context.getPackageManager().getApplicationIcon(context.getPackageName()))
|
||||
.getBitmap();
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
String vertexShaderCode =
|
||||
loadAssetAsString(context, "bitmap_overlay_video_processor_vertex.glsl");
|
||||
String fragmentShaderCode =
|
||||
loadAssetAsString(context, "bitmap_overlay_video_processor_fragment.glsl");
|
||||
program = GlUtil.compileProgram(vertexShaderCode, fragmentShaderCode);
|
||||
GlUtil.Attribute[] attributes = GlUtil.getAttributes(program);
|
||||
GlUtil.Uniform[] uniforms = GlUtil.getUniforms(program);
|
||||
for (GlUtil.Attribute attribute : attributes) {
|
||||
if (attribute.name.equals("a_position")) {
|
||||
attribute.setBuffer(
|
||||
new float[] {
|
||||
-1.0f, -1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
|
||||
1.0f, 0.0f, 1.0f,
|
||||
},
|
||||
4);
|
||||
} else if (attribute.name.equals("a_texcoord")) {
|
||||
attribute.setBuffer(
|
||||
new float[] {
|
||||
0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
|
||||
},
|
||||
3);
|
||||
}
|
||||
}
|
||||
this.attributes = attributes;
|
||||
this.uniforms = uniforms;
|
||||
GLES20.glGenTextures(1, textures, 0);
|
||||
GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
|
||||
GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
|
||||
GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
|
||||
GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
|
||||
GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
|
||||
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSurfaceSize(int width, int height) {
|
||||
bitmapScaleX = (float) width / OVERLAY_WIDTH;
|
||||
bitmapScaleY = (float) height / OVERLAY_HEIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(int frameTexture, long frameTimestampUs) {
|
||||
// Draw to the canvas and store it in a texture.
|
||||
String text = String.format(Locale.US, "%.02f", frameTimestampUs / (float) C.MICROS_PER_SECOND);
|
||||
overlayBitmap.eraseColor(Color.TRANSPARENT);
|
||||
overlayCanvas.drawBitmap(logoBitmap, /* left= */ 32, /* top= */ 32, paint);
|
||||
overlayCanvas.drawText(text, /* x= */ 200, /* y= */ 130, paint);
|
||||
GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
|
||||
GLUtils.texSubImage2D(
|
||||
GL10.GL_TEXTURE_2D, /* level= */ 0, /* xoffset= */ 0, /* yoffset= */ 0, overlayBitmap);
|
||||
GlUtil.checkGlError();
|
||||
|
||||
// Run the shader program.
|
||||
GlUtil.Uniform[] uniforms = Assertions.checkNotNull(this.uniforms);
|
||||
GlUtil.Attribute[] attributes = Assertions.checkNotNull(this.attributes);
|
||||
GLES20.glUseProgram(program);
|
||||
for (GlUtil.Uniform uniform : uniforms) {
|
||||
switch (uniform.name) {
|
||||
case "tex_sampler_0":
|
||||
uniform.setSamplerTexId(frameTexture, /* unit= */ 0);
|
||||
break;
|
||||
case "tex_sampler_1":
|
||||
uniform.setSamplerTexId(textures[0], /* unit= */ 1);
|
||||
break;
|
||||
case "scaleX":
|
||||
uniform.setFloat(bitmapScaleX);
|
||||
break;
|
||||
case "scaleY":
|
||||
uniform.setFloat(bitmapScaleY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (GlUtil.Attribute copyExternalAttribute : attributes) {
|
||||
copyExternalAttribute.bind();
|
||||
}
|
||||
for (GlUtil.Uniform copyExternalUniform : uniforms) {
|
||||
copyExternalUniform.bind();
|
||||
}
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
|
||||
GlUtil.checkGlError();
|
||||
}
|
||||
|
||||
private static String loadAssetAsString(Context context, String assetFileName) {
|
||||
@Nullable InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = context.getAssets().open(assetFileName);
|
||||
return Util.fromUtf8Bytes(Util.toByteArray(inputStream));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} finally {
|
||||
Util.closeQuietly(inputStream);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.exoplayer2.gldemo;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
|
||||
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||
import com.google.android.exoplayer2.source.dash.DashMediaSource;
|
||||
import com.google.android.exoplayer2.ui.PlayerView;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.EventLogger;
|
||||
import com.google.android.exoplayer2.util.GlUtil;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Activity that demonstrates playback of video to an {@link android.opengl.GLSurfaceView} with
|
||||
* postprocessing of the video content using GL.
|
||||
*/
|
||||
public final class MainActivity extends Activity {
|
||||
|
||||
private static final String TAG = "MainActivity";
|
||||
|
||||
private static final String DEFAULT_MEDIA_URI =
|
||||
"https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv";
|
||||
|
||||
private static final String ACTION_VIEW = "com.google.android.exoplayer.gldemo.action.VIEW";
|
||||
private static final String EXTENSION_EXTRA = "extension";
|
||||
private static final String DRM_SCHEME_EXTRA = "drm_scheme";
|
||||
private static final String DRM_LICENSE_URL_EXTRA = "drm_license_url";
|
||||
|
||||
@Nullable private PlayerView playerView;
|
||||
@Nullable private VideoProcessingGLSurfaceView videoProcessingGLSurfaceView;
|
||||
|
||||
@Nullable private SimpleExoPlayer player;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main_activity);
|
||||
playerView = findViewById(R.id.player_view);
|
||||
|
||||
Context context = getApplicationContext();
|
||||
boolean requestSecureSurface = getIntent().hasExtra(DRM_SCHEME_EXTRA);
|
||||
if (requestSecureSurface && !GlUtil.isProtectedContentExtensionSupported(context)) {
|
||||
Toast.makeText(
|
||||
context, R.string.error_protected_content_extension_not_supported, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
|
||||
VideoProcessingGLSurfaceView videoProcessingGLSurfaceView =
|
||||
new VideoProcessingGLSurfaceView(
|
||||
context, requestSecureSurface, new BitmapOverlayVideoProcessor(context));
|
||||
FrameLayout contentFrame = findViewById(R.id.exo_content_frame);
|
||||
contentFrame.addView(videoProcessingGLSurfaceView);
|
||||
this.videoProcessingGLSurfaceView = videoProcessingGLSurfaceView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
if (Util.SDK_INT > 23) {
|
||||
initializePlayer();
|
||||
if (playerView != null) {
|
||||
playerView.onResume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (Util.SDK_INT <= 23 || player == null) {
|
||||
initializePlayer();
|
||||
if (playerView != null) {
|
||||
playerView.onResume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (Util.SDK_INT <= 23) {
|
||||
if (playerView != null) {
|
||||
playerView.onPause();
|
||||
}
|
||||
releasePlayer();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
if (Util.SDK_INT > 23) {
|
||||
if (playerView != null) {
|
||||
playerView.onPause();
|
||||
}
|
||||
releasePlayer();
|
||||
}
|
||||
}
|
||||
|
||||
private void initializePlayer() {
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getAction();
|
||||
Uri uri =
|
||||
ACTION_VIEW.equals(action)
|
||||
? Assertions.checkNotNull(intent.getData())
|
||||
: Uri.parse(DEFAULT_MEDIA_URI);
|
||||
String userAgent = Util.getUserAgent(this, getString(R.string.application_name));
|
||||
DrmSessionManager<ExoMediaCrypto> drmSessionManager;
|
||||
if (Util.SDK_INT >= 18 && intent.hasExtra(DRM_SCHEME_EXTRA)) {
|
||||
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
|
||||
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
|
||||
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
|
||||
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSourceFactory(userAgent);
|
||||
HttpMediaDrmCallback drmCallback =
|
||||
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
|
||||
drmSessionManager =
|
||||
new DefaultDrmSessionManager.Builder()
|
||||
.setUuidAndExoMediaDrmProvider(drmSchemeUuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
|
||||
.build(drmCallback);
|
||||
} else {
|
||||
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
|
||||
}
|
||||
|
||||
DataSource.Factory dataSourceFactory =
|
||||
new DefaultDataSourceFactory(
|
||||
this, Util.getUserAgent(this, getString(R.string.application_name)));
|
||||
MediaSource mediaSource;
|
||||
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA));
|
||||
if (type == C.TYPE_DASH) {
|
||||
mediaSource =
|
||||
new DashMediaSource.Factory(dataSourceFactory)
|
||||
.setDrmSessionManager(drmSessionManager)
|
||||
.createMediaSource(uri);
|
||||
} else if (type == C.TYPE_OTHER) {
|
||||
mediaSource =
|
||||
new ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||
.setDrmSessionManager(drmSessionManager)
|
||||
.createMediaSource(uri);
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
SimpleExoPlayer player = new SimpleExoPlayer.Builder(getApplicationContext()).build();
|
||||
player.setRepeatMode(Player.REPEAT_MODE_ALL);
|
||||
player.setMediaSource(mediaSource);
|
||||
player.prepare();
|
||||
player.play();
|
||||
VideoProcessingGLSurfaceView videoProcessingGLSurfaceView =
|
||||
Assertions.checkNotNull(this.videoProcessingGLSurfaceView);
|
||||
videoProcessingGLSurfaceView.setVideoComponent(
|
||||
Assertions.checkNotNull(player.getVideoComponent()));
|
||||
Assertions.checkNotNull(playerView).setPlayer(player);
|
||||
player.addAnalyticsListener(new EventLogger(/* trackSelector= */ null));
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
private void releasePlayer() {
|
||||
Assertions.checkNotNull(playerView).setPlayer(null);
|
||||
if (player != null) {
|
||||
player.release();
|
||||
Assertions.checkNotNull(videoProcessingGLSurfaceView).setVideoComponent(null);
|
||||
player = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.exoplayer2.gldemo;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.media.MediaFormat;
|
||||
import android.opengl.EGL14;
|
||||
import android.opengl.GLES20;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.os.Handler;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.GlUtil;
|
||||
import com.google.android.exoplayer2.util.TimedValueQueue;
|
||||
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import javax.microedition.khronos.egl.EGL10;
|
||||
import javax.microedition.khronos.egl.EGLConfig;
|
||||
import javax.microedition.khronos.egl.EGLContext;
|
||||
import javax.microedition.khronos.egl.EGLDisplay;
|
||||
import javax.microedition.khronos.egl.EGLSurface;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
|
||||
/**
|
||||
* {@link GLSurfaceView} that creates a GL context (optionally for protected content) and passes
|
||||
* video frames to a {@link VideoProcessor} for drawing to the view.
|
||||
*
|
||||
* <p>This view must be created programmatically, as it is necessary to specify whether a context
|
||||
* supporting protected content should be created at construction time.
|
||||
*/
|
||||
public final class VideoProcessingGLSurfaceView extends GLSurfaceView {
|
||||
|
||||
/** Processes video frames, provided via a GL texture. */
|
||||
public interface VideoProcessor {
|
||||
/** Performs any required GL initialization. */
|
||||
void initialize();
|
||||
|
||||
/** Sets the size of the output surface in pixels. */
|
||||
void setSurfaceSize(int width, int height);
|
||||
|
||||
/**
|
||||
* Draws using GL operations.
|
||||
*
|
||||
* @param frameTexture The ID of a GL texture containing a video frame.
|
||||
* @param frameTimestampUs The presentation timestamp of the frame, in microseconds.
|
||||
*/
|
||||
void draw(int frameTexture, long frameTimestampUs);
|
||||
}
|
||||
|
||||
private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0;
|
||||
|
||||
private final VideoRenderer renderer;
|
||||
private final Handler mainHandler;
|
||||
|
||||
@Nullable private SurfaceTexture surfaceTexture;
|
||||
@Nullable private Surface surface;
|
||||
@Nullable private Player.VideoComponent videoComponent;
|
||||
|
||||
/**
|
||||
* Creates a new instance. Pass {@code true} for {@code requireSecureContext} if the {@link
|
||||
* GLSurfaceView GLSurfaceView's} associated GL context should handle secure content (if the
|
||||
* device supports it).
|
||||
*
|
||||
* @param context The {@link Context}.
|
||||
* @param requireSecureContext Whether a GL context supporting protected content should be
|
||||
* created, if supported by the device.
|
||||
* @param videoProcessor Processor that draws to the view.
|
||||
*/
|
||||
public VideoProcessingGLSurfaceView(
|
||||
Context context, boolean requireSecureContext, VideoProcessor videoProcessor) {
|
||||
super(context);
|
||||
renderer = new VideoRenderer(videoProcessor);
|
||||
mainHandler = new Handler();
|
||||
setEGLContextClientVersion(2);
|
||||
setEGLConfigChooser(
|
||||
/* redSize= */ 8,
|
||||
/* greenSize= */ 8,
|
||||
/* blueSize= */ 8,
|
||||
/* alphaSize= */ 8,
|
||||
/* depthSize= */ 0,
|
||||
/* stencilSize= */ 0);
|
||||
setEGLContextFactory(
|
||||
new EGLContextFactory() {
|
||||
@Override
|
||||
public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
|
||||
int[] glAttributes;
|
||||
if (requireSecureContext) {
|
||||
glAttributes =
|
||||
new int[] {
|
||||
EGL14.EGL_CONTEXT_CLIENT_VERSION,
|
||||
2,
|
||||
EGL_PROTECTED_CONTENT_EXT,
|
||||
EGL14.EGL_TRUE,
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
} else {
|
||||
glAttributes = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
|
||||
}
|
||||
return egl.eglCreateContext(
|
||||
display, eglConfig, /* share_context= */ EGL10.EGL_NO_CONTEXT, glAttributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
|
||||
egl.eglDestroyContext(display, context);
|
||||
}
|
||||
});
|
||||
setEGLWindowSurfaceFactory(
|
||||
new EGLWindowSurfaceFactory() {
|
||||
@Override
|
||||
public EGLSurface createWindowSurface(
|
||||
EGL10 egl, EGLDisplay display, EGLConfig config, Object nativeWindow) {
|
||||
int[] attribsList =
|
||||
requireSecureContext
|
||||
? new int[] {EGL_PROTECTED_CONTENT_EXT, EGL14.EGL_TRUE, EGL10.EGL_NONE}
|
||||
: new int[] {EGL10.EGL_NONE};
|
||||
return egl.eglCreateWindowSurface(display, config, nativeWindow, attribsList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) {
|
||||
egl.eglDestroySurface(display, surface);
|
||||
}
|
||||
});
|
||||
setRenderer(renderer);
|
||||
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches or detaches (if {@code newVideoComponent} is {@code null}) this view from the video
|
||||
* component of the player.
|
||||
*
|
||||
* @param newVideoComponent The new video component, or {@code null} to detach this view.
|
||||
*/
|
||||
public void setVideoComponent(@Nullable Player.VideoComponent newVideoComponent) {
|
||||
if (newVideoComponent == videoComponent) {
|
||||
return;
|
||||
}
|
||||
if (videoComponent != null) {
|
||||
if (surface != null) {
|
||||
videoComponent.clearVideoSurface(surface);
|
||||
}
|
||||
videoComponent.clearVideoFrameMetadataListener(renderer);
|
||||
}
|
||||
videoComponent = newVideoComponent;
|
||||
if (videoComponent != null) {
|
||||
videoComponent.setVideoFrameMetadataListener(renderer);
|
||||
videoComponent.setVideoSurface(surface);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
// Post to make sure we occur in order with any onSurfaceTextureAvailable calls.
|
||||
mainHandler.post(
|
||||
() -> {
|
||||
if (surface != null) {
|
||||
if (videoComponent != null) {
|
||||
videoComponent.setVideoSurface(null);
|
||||
}
|
||||
releaseSurface(surfaceTexture, surface);
|
||||
surfaceTexture = null;
|
||||
surface = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture) {
|
||||
mainHandler.post(
|
||||
() -> {
|
||||
SurfaceTexture oldSurfaceTexture = this.surfaceTexture;
|
||||
Surface oldSurface = VideoProcessingGLSurfaceView.this.surface;
|
||||
this.surfaceTexture = surfaceTexture;
|
||||
this.surface = new Surface(surfaceTexture);
|
||||
releaseSurface(oldSurfaceTexture, oldSurface);
|
||||
if (videoComponent != null) {
|
||||
videoComponent.setVideoSurface(surface);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void releaseSurface(
|
||||
@Nullable SurfaceTexture oldSurfaceTexture, @Nullable Surface oldSurface) {
|
||||
if (oldSurfaceTexture != null) {
|
||||
oldSurfaceTexture.release();
|
||||
}
|
||||
if (oldSurface != null) {
|
||||
oldSurface.release();
|
||||
}
|
||||
}
|
||||
|
||||
private final class VideoRenderer implements GLSurfaceView.Renderer, VideoFrameMetadataListener {
|
||||
|
||||
private final VideoProcessor videoProcessor;
|
||||
private final AtomicBoolean frameAvailable;
|
||||
private final TimedValueQueue<Long> sampleTimestampQueue;
|
||||
|
||||
private int texture;
|
||||
@Nullable private SurfaceTexture surfaceTexture;
|
||||
|
||||
private boolean initialized;
|
||||
private int width;
|
||||
private int height;
|
||||
private long frameTimestampUs;
|
||||
|
||||
public VideoRenderer(VideoProcessor videoProcessor) {
|
||||
this.videoProcessor = videoProcessor;
|
||||
frameAvailable = new AtomicBoolean();
|
||||
sampleTimestampQueue = new TimedValueQueue<>();
|
||||
width = -1;
|
||||
height = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onSurfaceCreated(GL10 gl, EGLConfig config) {
|
||||
texture = GlUtil.createExternalTexture();
|
||||
surfaceTexture = new SurfaceTexture(texture);
|
||||
surfaceTexture.setOnFrameAvailableListener(
|
||||
surfaceTexture -> {
|
||||
frameAvailable.set(true);
|
||||
requestRender();
|
||||
});
|
||||
onSurfaceTextureAvailable(surfaceTexture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceChanged(GL10 gl, int width, int height) {
|
||||
GLES20.glViewport(0, 0, width, height);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawFrame(GL10 gl) {
|
||||
if (videoProcessor == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
videoProcessor.initialize();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
if (width != -1 && height != -1) {
|
||||
videoProcessor.setSurfaceSize(width, height);
|
||||
width = -1;
|
||||
height = -1;
|
||||
}
|
||||
|
||||
if (frameAvailable.compareAndSet(true, false)) {
|
||||
SurfaceTexture surfaceTexture = Assertions.checkNotNull(this.surfaceTexture);
|
||||
surfaceTexture.updateTexImage();
|
||||
long lastFrameTimestampNs = surfaceTexture.getTimestamp();
|
||||
Long frameTimestampUs = sampleTimestampQueue.poll(lastFrameTimestampNs);
|
||||
if (frameTimestampUs != null) {
|
||||
this.frameTimestampUs = frameTimestampUs;
|
||||
}
|
||||
}
|
||||
|
||||
videoProcessor.draw(texture, frameTimestampUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoFrameAboutToBeRendered(
|
||||
long presentationTimeUs,
|
||||
long releaseTimeNs,
|
||||
Format format,
|
||||
@Nullable MediaFormat mediaFormat) {
|
||||
sampleTimestampQueue.add(releaseTimeNs, presentationTimeUs);
|
||||
}
|
||||
}
|
||||
}
|
30
demos/gl/src/main/res/layout/main_activity.xml
Normal file
30
demos/gl/src/main/res/layout/main_activity.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2020 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.
|
||||
-->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:keepScreenOn="true">
|
||||
|
||||
<com.google.android.exoplayer2.ui.PlayerView
|
||||
android:id="@+id/player_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:surface_type="none"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
BIN
demos/gl/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
demos/gl/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
BIN
demos/gl/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
demos/gl/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
BIN
demos/gl/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
demos/gl/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
BIN
demos/gl/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
demos/gl/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
BIN
demos/gl/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
demos/gl/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
22
demos/gl/src/main/res/values/strings.xml
Normal file
22
demos/gl/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2020 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.
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<string name="application_name">ExoPlayer GL demo</string>
|
||||
|
||||
<string name="error_protected_content_extension_not_supported">The GL protected content extension is not supported.</string>
|
||||
|
||||
</resources>
|
@ -17,23 +17,235 @@ package com.google.android.exoplayer2.util;
|
||||
|
||||
import static android.opengl.GLU.gluErrorString;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.opengl.EGL14;
|
||||
import android.opengl.EGLDisplay;
|
||||
import android.opengl.GLES11Ext;
|
||||
import android.opengl.GLES20;
|
||||
import android.text.TextUtils;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import javax.microedition.khronos.egl.EGL10;
|
||||
|
||||
/** GL utility methods. */
|
||||
/** GL utilities. */
|
||||
public final class GlUtil {
|
||||
|
||||
/**
|
||||
* GL attribute, which can be attached to a buffer with {@link Attribute#setBuffer(float[], int)}.
|
||||
*/
|
||||
public static final class Attribute {
|
||||
|
||||
/** The name of the attribute in the GLSL sources. */
|
||||
public final String name;
|
||||
|
||||
private final int index;
|
||||
private final int location;
|
||||
|
||||
@Nullable private Buffer buffer;
|
||||
private int size;
|
||||
|
||||
/**
|
||||
* Creates a new GL attribute.
|
||||
*
|
||||
* @param program The identifier of a compiled and linked GLSL shader program.
|
||||
* @param index The index of the attribute. After this instance has been constructed, the name
|
||||
* of the attribute is available via the {@link #name} field.
|
||||
*/
|
||||
public Attribute(int program, int index) {
|
||||
int[] len = new int[1];
|
||||
GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, len, 0);
|
||||
|
||||
int[] type = new int[1];
|
||||
int[] size = new int[1];
|
||||
byte[] nameBytes = new byte[len[0]];
|
||||
int[] ignore = new int[1];
|
||||
|
||||
GLES20.glGetActiveAttrib(program, index, len[0], ignore, 0, size, 0, type, 0, nameBytes, 0);
|
||||
name = new String(nameBytes, 0, strlen(nameBytes));
|
||||
location = GLES20.glGetAttribLocation(program, name);
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures {@link #bind()} to attach vertices in {@code buffer} (each of size {@code size}
|
||||
* elements) to this {@link Attribute}.
|
||||
*
|
||||
* @param buffer Buffer to bind to this attribute.
|
||||
* @param size Number of elements per vertex.
|
||||
*/
|
||||
public void setBuffer(float[] buffer, int size) {
|
||||
this.buffer = createBuffer(buffer);
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vertex attribute to whatever was attached via {@link #setBuffer(float[], int)}.
|
||||
*
|
||||
* <p>Should be called before each drawing call.
|
||||
*/
|
||||
public void bind() {
|
||||
Buffer buffer = Assertions.checkNotNull(this.buffer, "call setBuffer before bind");
|
||||
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
|
||||
GLES20.glVertexAttribPointer(
|
||||
location,
|
||||
size, // count
|
||||
GLES20.GL_FLOAT, // type
|
||||
false, // normalize
|
||||
0, // stride
|
||||
buffer);
|
||||
GLES20.glEnableVertexAttribArray(index);
|
||||
checkGlError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GL uniform, which can be attached to a sampler using {@link Uniform#setSamplerTexId(int, int)}.
|
||||
*/
|
||||
public static final class Uniform {
|
||||
|
||||
/** The name of the uniform in the GLSL sources. */
|
||||
public final String name;
|
||||
|
||||
private final int location;
|
||||
private final int type;
|
||||
private final float[] value;
|
||||
|
||||
private int texId;
|
||||
private int unit;
|
||||
|
||||
/**
|
||||
* Creates a new GL uniform.
|
||||
*
|
||||
* @param program The identifier of a compiled and linked GLSL shader program.
|
||||
* @param index The index of the uniform. After this instance has been constructed, the name of
|
||||
* the uniform is available via the {@link #name} field.
|
||||
*/
|
||||
public Uniform(int program, int index) {
|
||||
int[] len = new int[1];
|
||||
GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, len, 0);
|
||||
|
||||
int[] type = new int[1];
|
||||
int[] size = new int[1];
|
||||
byte[] name = new byte[len[0]];
|
||||
int[] ignore = new int[1];
|
||||
|
||||
GLES20.glGetActiveUniform(program, index, len[0], ignore, 0, size, 0, type, 0, name, 0);
|
||||
this.name = new String(name, 0, strlen(name));
|
||||
location = GLES20.glGetUniformLocation(program, this.name);
|
||||
this.type = type[0];
|
||||
|
||||
value = new float[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures {@link #bind()} to use the specified {@code texId} for this sampler uniform.
|
||||
*
|
||||
* @param texId The GL texture identifier from which to sample.
|
||||
* @param unit The GL texture unit index.
|
||||
*/
|
||||
public void setSamplerTexId(int texId, int unit) {
|
||||
this.texId = texId;
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
/** Configures {@link #bind()} to use the specified float {@code value} for this uniform. */
|
||||
public void setFloat(float value) {
|
||||
this.value[0] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the uniform to whatever value was passed via {@link #setSamplerTexId(int, int)} or
|
||||
* {@link #setFloat(float)}.
|
||||
*
|
||||
* <p>Should be called before each drawing call.
|
||||
*/
|
||||
public void bind() {
|
||||
if (type == GLES20.GL_FLOAT) {
|
||||
GLES20.glUniform1fv(location, 1, value, 0);
|
||||
checkGlError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (texId == 0) {
|
||||
throw new IllegalStateException("call setSamplerTexId before bind");
|
||||
}
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit);
|
||||
if (type == GLES11Ext.GL_SAMPLER_EXTERNAL_OES) {
|
||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
|
||||
} else if (type == GLES20.GL_SAMPLER_2D) {
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
|
||||
} else {
|
||||
throw new IllegalStateException("unexpected uniform type: " + type);
|
||||
}
|
||||
GLES20.glUniform1i(location, unit);
|
||||
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameteri(
|
||||
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
||||
GLES20.glTexParameteri(
|
||||
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
||||
checkGlError();
|
||||
}
|
||||
}
|
||||
|
||||
private static final String TAG = "GlUtil";
|
||||
|
||||
private static final String EXTENSION_PROTECTED_CONTENT = "EGL_EXT_protected_content";
|
||||
private static final String EXTENSION_SURFACELESS_CONTEXT = "EGL_KHR_surfaceless_context";
|
||||
|
||||
/** Class only contains static methods. */
|
||||
private GlUtil() {}
|
||||
|
||||
/**
|
||||
* Returns whether creating a GL context with {@value EXTENSION_PROTECTED_CONTENT} is possible. If
|
||||
* {@code true}, the device supports a protected output path for DRM content when using GL.
|
||||
*/
|
||||
@TargetApi(24)
|
||||
public static boolean isProtectedContentExtensionSupported(Context context) {
|
||||
if (Util.SDK_INT < 24) {
|
||||
return false;
|
||||
}
|
||||
if (Util.SDK_INT < 26 && ("samsung".equals(Util.MANUFACTURER) || "XT1650".equals(Util.MODEL))) {
|
||||
// Samsung devices running Nougat are known to be broken. See
|
||||
// https://github.com/google/ExoPlayer/issues/3373 and [Internal: b/37197802].
|
||||
// Moto Z XT1650 is also affected. See
|
||||
// https://github.com/google/ExoPlayer/issues/3215.
|
||||
return false;
|
||||
}
|
||||
if (Util.SDK_INT < 26
|
||||
&& !context
|
||||
.getPackageManager()
|
||||
.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
|
||||
// Pre API level 26 devices were not well tested unless they supported VR mode.
|
||||
return false;
|
||||
}
|
||||
|
||||
EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
|
||||
@Nullable String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS);
|
||||
return eglExtensions != null && eglExtensions.contains(EXTENSION_PROTECTED_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether creating a GL context with {@value EXTENSION_SURFACELESS_CONTEXT} is possible.
|
||||
*/
|
||||
@TargetApi(17)
|
||||
public static boolean isSurfacelessContextExtensionSupported() {
|
||||
if (Util.SDK_INT < 17) {
|
||||
return false;
|
||||
}
|
||||
EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
|
||||
@Nullable String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS);
|
||||
return eglExtensions != null && eglExtensions.contains(EXTENSION_SURFACELESS_CONTEXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is an OpenGl error, logs the error and if {@link
|
||||
* ExoPlayerLibraryInfo#GL_ASSERTIONS_ENABLED} is true throws a {@link RuntimeException}.
|
||||
@ -90,6 +302,34 @@ public final class GlUtil {
|
||||
return program;
|
||||
}
|
||||
|
||||
/** Returns the {@link Attribute}s in the specified {@code program}. */
|
||||
public static Attribute[] getAttributes(int program) {
|
||||
int[] attributeCount = new int[1];
|
||||
GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_ATTRIBUTES, attributeCount, 0);
|
||||
if (attributeCount[0] != 2) {
|
||||
throw new IllegalStateException("expected two attributes");
|
||||
}
|
||||
|
||||
Attribute[] attributes = new Attribute[attributeCount[0]];
|
||||
for (int i = 0; i < attributeCount[0]; i++) {
|
||||
attributes[i] = new Attribute(program, i);
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/** Returns the {@link Uniform}s in the specified {@code program}. */
|
||||
public static Uniform[] getUniforms(int program) {
|
||||
int[] uniformCount = new int[1];
|
||||
GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_UNIFORMS, uniformCount, 0);
|
||||
|
||||
Uniform[] uniforms = new Uniform[uniformCount[0]];
|
||||
for (int i = 0; i < uniformCount[0]; i++) {
|
||||
uniforms[i] = new Uniform(program, i);
|
||||
}
|
||||
|
||||
return uniforms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a FloatBuffer with the given data.
|
||||
*
|
||||
@ -151,4 +391,14 @@ public final class GlUtil {
|
||||
throw new RuntimeException(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the length of the null-terminated string in {@code strVal}. */
|
||||
private static int strlen(byte[] strVal) {
|
||||
for (int i = 0; i < strVal.length; ++i) {
|
||||
if (strVal[i] == '\0') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return strVal.length;
|
||||
}
|
||||
}
|
||||
|
@ -20,10 +20,7 @@ import static com.google.android.exoplayer2.util.EGLSurfaceTexture.SECURE_MODE_P
|
||||
import static com.google.android.exoplayer2.util.EGLSurfaceTexture.SECURE_MODE_SURFACELESS_CONTEXT;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.opengl.EGL14;
|
||||
import android.opengl.EGLDisplay;
|
||||
import android.os.Handler;
|
||||
import android.os.Handler.Callback;
|
||||
import android.os.HandlerThread;
|
||||
@ -34,9 +31,9 @@ import androidx.annotation.RequiresApi;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.EGLSurfaceTexture;
|
||||
import com.google.android.exoplayer2.util.EGLSurfaceTexture.SecureMode;
|
||||
import com.google.android.exoplayer2.util.GlUtil;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import javax.microedition.khronos.egl.EGL10;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/** A dummy {@link Surface}. */
|
||||
@ -45,9 +42,6 @@ public final class DummySurface extends Surface {
|
||||
|
||||
private static final String TAG = "DummySurface";
|
||||
|
||||
private static final String EXTENSION_PROTECTED_CONTENT = "EGL_EXT_protected_content";
|
||||
private static final String EXTENSION_SURFACELESS_CONTEXT = "EGL_KHR_surfaceless_context";
|
||||
|
||||
/**
|
||||
* Whether the surface is secure.
|
||||
*/
|
||||
@ -67,7 +61,7 @@ public final class DummySurface extends Surface {
|
||||
*/
|
||||
public static synchronized boolean isSecureSupported(Context context) {
|
||||
if (!secureModeInitialized) {
|
||||
secureMode = Util.SDK_INT < 24 ? SECURE_MODE_NONE : getSecureModeV24(context);
|
||||
secureMode = getSecureMode(context);
|
||||
secureModeInitialized = true;
|
||||
}
|
||||
return secureMode != SECURE_MODE_NONE;
|
||||
@ -119,34 +113,21 @@ public final class DummySurface extends Surface {
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(24)
|
||||
private static @SecureMode int getSecureModeV24(Context context) {
|
||||
if (Util.SDK_INT < 26 && ("samsung".equals(Util.MANUFACTURER) || "XT1650".equals(Util.MODEL))) {
|
||||
// Samsung devices running Nougat are known to be broken. See
|
||||
// https://github.com/google/ExoPlayer/issues/3373 and [Internal: b/37197802].
|
||||
// Moto Z XT1650 is also affected. See
|
||||
// https://github.com/google/ExoPlayer/issues/3215.
|
||||
@SecureMode
|
||||
private static int getSecureMode(Context context) {
|
||||
if (GlUtil.isProtectedContentExtensionSupported(context)) {
|
||||
if (GlUtil.isSurfacelessContextExtensionSupported()) {
|
||||
return SECURE_MODE_SURFACELESS_CONTEXT;
|
||||
} else {
|
||||
// If we can't use surfaceless contexts, we use a protected 1 * 1 pixel buffer surface.
|
||||
// This may require support for EXT_protected_surface, but in practice it works on some
|
||||
// devices that don't have that extension. See also
|
||||
// https://github.com/google/ExoPlayer/issues/3558.
|
||||
return SECURE_MODE_PROTECTED_PBUFFER;
|
||||
}
|
||||
} else {
|
||||
return SECURE_MODE_NONE;
|
||||
}
|
||||
if (Util.SDK_INT < 26 && !context.getPackageManager().hasSystemFeature(
|
||||
PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
|
||||
// Pre API level 26 devices were not well tested unless they supported VR mode.
|
||||
return SECURE_MODE_NONE;
|
||||
}
|
||||
EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
|
||||
String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS);
|
||||
if (eglExtensions == null) {
|
||||
return SECURE_MODE_NONE;
|
||||
}
|
||||
if (!eglExtensions.contains(EXTENSION_PROTECTED_CONTENT)) {
|
||||
return SECURE_MODE_NONE;
|
||||
}
|
||||
// If we can't use surfaceless contexts, we use a protected 1 * 1 pixel buffer surface. This may
|
||||
// require support for EXT_protected_surface, but in practice it works on some devices that
|
||||
// don't have that extension. See also https://github.com/google/ExoPlayer/issues/3558.
|
||||
return eglExtensions.contains(EXTENSION_SURFACELESS_CONTEXT)
|
||||
? SECURE_MODE_SURFACELESS_CONTEXT
|
||||
: SECURE_MODE_PROTECTED_PBUFFER;
|
||||
}
|
||||
|
||||
private static class DummySurfaceThread extends HandlerThread implements Callback {
|
||||
|
@ -20,10 +20,12 @@ if (gradle.ext.has('exoplayerModulePrefix')) {
|
||||
|
||||
include modulePrefix + 'demo'
|
||||
include modulePrefix + 'demo-cast'
|
||||
include modulePrefix + 'demo-gl'
|
||||
include modulePrefix + 'demo-surface'
|
||||
include modulePrefix + 'playbacktests'
|
||||
project(modulePrefix + 'demo').projectDir = new File(rootDir, 'demos/main')
|
||||
project(modulePrefix + 'demo-cast').projectDir = new File(rootDir, 'demos/cast')
|
||||
project(modulePrefix + 'demo-gl').projectDir = new File(rootDir, 'demos/gl')
|
||||
project(modulePrefix + 'demo-surface').projectDir = new File(rootDir, 'demos/surface')
|
||||
project(modulePrefix + 'playbacktests').projectDir = new File(rootDir, 'playbacktests')
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user