mirror of
https://github.com/androidx/media.git
synced 2025-05-03 21:57:46 +08:00
Split out ExternalCopyFrameProcessor.
All (later customizable) GlFrameProcessors after the ExternalCopyFrameProcessor receive their input from a normal OpenGL texture not an external texture, so they won't need to worry about the textureTransformMatrix. PiperOrigin-RevId: 430165652
This commit is contained in:
parent
25e58f5f2a
commit
19e21d6fba
@ -116,8 +116,8 @@ public final class FrameEditorDataProcessingTest {
|
|||||||
public void processData_noEdits_producesExpectedOutput() throws Exception {
|
public void processData_noEdits_producesExpectedOutput() throws Exception {
|
||||||
Matrix identityMatrix = new Matrix();
|
Matrix identityMatrix = new Matrix();
|
||||||
setUpAndPrepareFirstFrame(identityMatrix);
|
setUpAndPrepareFirstFrame(identityMatrix);
|
||||||
|
|
||||||
Bitmap expectedBitmap = getBitmap(NO_EDITS_EXPECTED_OUTPUT_PNG_ASSET_STRING);
|
Bitmap expectedBitmap = getBitmap(NO_EDITS_EXPECTED_OUTPUT_PNG_ASSET_STRING);
|
||||||
|
|
||||||
checkNotNull(frameEditor).processData();
|
checkNotNull(frameEditor).processData();
|
||||||
Image editedImage = checkNotNull(frameEditorOutputImageReader).acquireLatestImage();
|
Image editedImage = checkNotNull(frameEditorOutputImageReader).acquireLatestImage();
|
||||||
Bitmap editedBitmap = getArgb8888BitmapForRgba8888Image(editedImage);
|
Bitmap editedBitmap = getArgb8888BitmapForRgba8888Image(editedImage);
|
||||||
@ -134,8 +134,8 @@ public final class FrameEditorDataProcessingTest {
|
|||||||
Matrix translateRightMatrix = new Matrix();
|
Matrix translateRightMatrix = new Matrix();
|
||||||
translateRightMatrix.postTranslate(/* dx= */ 1, /* dy= */ 0);
|
translateRightMatrix.postTranslate(/* dx= */ 1, /* dy= */ 0);
|
||||||
setUpAndPrepareFirstFrame(translateRightMatrix);
|
setUpAndPrepareFirstFrame(translateRightMatrix);
|
||||||
|
|
||||||
Bitmap expectedBitmap = getBitmap(TRANSLATE_RIGHT_EXPECTED_OUTPUT_PNG_ASSET_STRING);
|
Bitmap expectedBitmap = getBitmap(TRANSLATE_RIGHT_EXPECTED_OUTPUT_PNG_ASSET_STRING);
|
||||||
|
|
||||||
checkNotNull(frameEditor).processData();
|
checkNotNull(frameEditor).processData();
|
||||||
Image editedImage = checkNotNull(frameEditorOutputImageReader).acquireLatestImage();
|
Image editedImage = checkNotNull(frameEditorOutputImageReader).acquireLatestImage();
|
||||||
Bitmap editedBitmap = getArgb8888BitmapForRgba8888Image(editedImage);
|
Bitmap editedBitmap = getArgb8888BitmapForRgba8888Image(editedImage);
|
||||||
@ -174,8 +174,8 @@ public final class FrameEditorDataProcessingTest {
|
|||||||
Matrix rotate90Matrix = new Matrix();
|
Matrix rotate90Matrix = new Matrix();
|
||||||
rotate90Matrix.postRotate(/* degrees= */ 90);
|
rotate90Matrix.postRotate(/* degrees= */ 90);
|
||||||
setUpAndPrepareFirstFrame(rotate90Matrix);
|
setUpAndPrepareFirstFrame(rotate90Matrix);
|
||||||
|
|
||||||
Bitmap expectedBitmap = getBitmap(ROTATE_90_EXPECTED_OUTPUT_PNG_ASSET_STRING);
|
Bitmap expectedBitmap = getBitmap(ROTATE_90_EXPECTED_OUTPUT_PNG_ASSET_STRING);
|
||||||
|
|
||||||
checkNotNull(frameEditor).processData();
|
checkNotNull(frameEditor).processData();
|
||||||
Image editedImage = checkNotNull(frameEditorOutputImageReader).acquireLatestImage();
|
Image editedImage = checkNotNull(frameEditorOutputImageReader).acquireLatestImage();
|
||||||
Bitmap editedBitmap = getArgb8888BitmapForRgba8888Image(editedImage);
|
Bitmap editedBitmap = getArgb8888BitmapForRgba8888Image(editedImage);
|
||||||
@ -206,13 +206,14 @@ public final class FrameEditorDataProcessingTest {
|
|||||||
int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
|
int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
|
||||||
frameEditorOutputImageReader =
|
frameEditorOutputImageReader =
|
||||||
ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, /* maxImages= */ 1);
|
ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, /* maxImages= */ 1);
|
||||||
|
Context context = getApplicationContext();
|
||||||
frameEditor =
|
frameEditor =
|
||||||
FrameEditor.create(
|
FrameEditor.create(
|
||||||
getApplicationContext(),
|
context,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
PIXEL_WIDTH_HEIGHT_RATIO,
|
PIXEL_WIDTH_HEIGHT_RATIO,
|
||||||
transformationMatrix,
|
new TransformationFrameProcessor(context, transformationMatrix),
|
||||||
frameEditorOutputImageReader.getSurface(),
|
frameEditorOutputImageReader.getSurface(),
|
||||||
/* enableExperimentalHdrEditing= */ false,
|
/* enableExperimentalHdrEditing= */ false,
|
||||||
Transformer.DebugViewProvider.NONE);
|
Transformer.DebugViewProvider.NONE);
|
||||||
|
@ -28,7 +28,7 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for {@link FrameEditor#create(Context, int, int, float, Matrix, Surface, boolean,
|
* Test for {@link FrameEditor#create(Context, int, int, float, GlFrameProcessor, Surface, boolean,
|
||||||
* Transformer.DebugViewProvider) creating} a {@link FrameEditor}.
|
* Transformer.DebugViewProvider) creating} a {@link FrameEditor}.
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
@ -39,12 +39,14 @@ public final class FrameEditorTest {
|
|||||||
@Test
|
@Test
|
||||||
public void create_withSupportedPixelWidthHeightRatio_completesSuccessfully()
|
public void create_withSupportedPixelWidthHeightRatio_completesSuccessfully()
|
||||||
throws TransformationException {
|
throws TransformationException {
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
|
||||||
FrameEditor.create(
|
FrameEditor.create(
|
||||||
getApplicationContext(),
|
context,
|
||||||
/* outputWidth= */ 200,
|
/* outputWidth= */ 200,
|
||||||
/* outputHeight= */ 100,
|
/* outputHeight= */ 100,
|
||||||
/* pixelWidthHeightRatio= */ 1,
|
/* pixelWidthHeightRatio= */ 1,
|
||||||
new Matrix(),
|
new TransformationFrameProcessor(context, new Matrix()),
|
||||||
new Surface(new SurfaceTexture(false)),
|
new Surface(new SurfaceTexture(false)),
|
||||||
/* enableExperimentalHdrEditing= */ false,
|
/* enableExperimentalHdrEditing= */ false,
|
||||||
Transformer.DebugViewProvider.NONE);
|
Transformer.DebugViewProvider.NONE);
|
||||||
@ -52,16 +54,18 @@ public final class FrameEditorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void create_withUnsupportedPixelWidthHeightRatio_throwsException() {
|
public void create_withUnsupportedPixelWidthHeightRatio_throwsException() {
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
|
||||||
TransformationException exception =
|
TransformationException exception =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
TransformationException.class,
|
TransformationException.class,
|
||||||
() ->
|
() ->
|
||||||
FrameEditor.create(
|
FrameEditor.create(
|
||||||
getApplicationContext(),
|
context,
|
||||||
/* outputWidth= */ 200,
|
/* outputWidth= */ 200,
|
||||||
/* outputHeight= */ 100,
|
/* outputHeight= */ 100,
|
||||||
/* pixelWidthHeightRatio= */ 2,
|
/* pixelWidthHeightRatio= */ 2,
|
||||||
new Matrix(),
|
new TransformationFrameProcessor(context, new Matrix()),
|
||||||
new Surface(new SurfaceTexture(false)),
|
new Surface(new SurfaceTexture(false)),
|
||||||
/* enableExperimentalHdrEditing= */ false,
|
/* enableExperimentalHdrEditing= */ false,
|
||||||
Transformer.DebugViewProvider.NONE));
|
Transformer.DebugViewProvider.NONE));
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
#version 100
|
||||||
|
// Copyright 2022 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.
|
||||||
|
|
||||||
|
// ES 2 fragment shader that samples from a (non-external) texture with uTexSampler,
|
||||||
|
// copying from this texture to the current output.
|
||||||
|
|
||||||
|
precision mediump float;
|
||||||
|
uniform sampler2D uTexSampler;
|
||||||
|
varying vec2 vTexCoords;
|
||||||
|
void main() {
|
||||||
|
gl_FragColor = texture2D(uTexSampler, vTexCoords);
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
#version 100
|
||||||
// Copyright 2021 The Android Open Source Project
|
// Copyright 2021 The Android Open Source Project
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -12,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.
|
||||||
|
|
||||||
// Fragment shader that samples from an external texture with uTexSampler,
|
// ES 2 fragment shader that samples from an external texture with uTexSampler,
|
||||||
// copying from this texture to the current output.
|
// copying from this texture to the current output.
|
||||||
|
|
||||||
#extension GL_OES_EGL_image_external : require
|
#extension GL_OES_EGL_image_external : require
|
@ -0,0 +1,27 @@
|
|||||||
|
#version 100
|
||||||
|
// Copyright 2022 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.
|
||||||
|
|
||||||
|
// ES 2 vertex shader that applies an external surface texture's 4 * 4 texture
|
||||||
|
// transformation matrix to convert the texture coordinates to the sampling
|
||||||
|
// locations.
|
||||||
|
|
||||||
|
attribute vec4 aFramePosition;
|
||||||
|
attribute vec4 aTexCoords;
|
||||||
|
uniform mat4 uTexTransform;
|
||||||
|
varying vec2 vTexCoords;
|
||||||
|
void main() {
|
||||||
|
gl_Position = aFramePosition;
|
||||||
|
vTexCoords = (uTexTransform * aTexCoords).xy;
|
||||||
|
}
|
@ -13,15 +13,15 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
// ES 3 vertex shader that applies the 4 * 4 transformation matrix
|
// ES 3 vertex shader that applies an external surface texture's 4 * 4 texture
|
||||||
// uTransformationMatrix.
|
// transformation matrix to convert the texture coordinates to the sampling
|
||||||
|
// locations.
|
||||||
|
|
||||||
in vec4 aFramePosition;
|
in vec4 aFramePosition;
|
||||||
in vec4 aTexCoords;
|
in vec4 aTexCoords;
|
||||||
uniform mat4 uTexTransform;
|
uniform mat4 uTexTransform;
|
||||||
uniform mat4 uTransformationMatrix;
|
|
||||||
out vec2 vTexCoords;
|
out vec2 vTexCoords;
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = uTransformationMatrix * aFramePosition;
|
gl_Position = aFramePosition;
|
||||||
vTexCoords = (uTexTransform * aTexCoords).xy;
|
vTexCoords = (uTexTransform * aTexCoords).xy;
|
||||||
}
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
#version 100
|
||||||
// Copyright 2021 The Android Open Source Project
|
// Copyright 2021 The Android Open Source Project
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -12,15 +13,14 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
// Vertex shader that applies the 4 * 4 transformation matrix
|
// ES 2 vertex shader that applies the 4 * 4 transformation matrix
|
||||||
// uTransformationMatrix.
|
// uTransformationMatrix.
|
||||||
|
|
||||||
attribute vec4 aFramePosition;
|
attribute vec4 aFramePosition;
|
||||||
attribute vec4 aTexCoords;
|
attribute vec4 aTexCoords;
|
||||||
uniform mat4 uTexTransform;
|
|
||||||
uniform mat4 uTransformationMatrix;
|
uniform mat4 uTransformationMatrix;
|
||||||
varying vec2 vTexCoords;
|
varying vec2 vTexCoords;
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = uTransformationMatrix * aFramePosition;
|
gl_Position = uTransformationMatrix * aFramePosition;
|
||||||
vTexCoords = (uTexTransform * aTexCoords).xy;
|
vTexCoords = aTexCoords.xy;
|
||||||
}
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 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.transformer;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.opengl.GLES20;
|
||||||
|
import com.google.android.exoplayer2.util.GlProgram;
|
||||||
|
import com.google.android.exoplayer2.util.GlUtil;
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
|
/** Copies frames from an external texture and applies color transformations for HDR if needed. */
|
||||||
|
/* package */ class ExternalCopyFrameProcessor implements GlFrameProcessor {
|
||||||
|
|
||||||
|
static {
|
||||||
|
GlUtil.glAssertionsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String VERTEX_SHADER_TEX_TRANSFORM_PATH =
|
||||||
|
"shaders/vertex_shader_tex_transform_es2.glsl";
|
||||||
|
private static final String VERTEX_SHADER_TEX_TRANSFORM_ES3_PATH =
|
||||||
|
"shaders/vertex_shader_tex_transform_es3.glsl";
|
||||||
|
private static final String FRAGMENT_SHADER_COPY_EXTERNAL_PATH =
|
||||||
|
"shaders/fragment_shader_copy_external_es2.glsl";
|
||||||
|
private static final String FRAGMENT_SHADER_COPY_EXTERNAL_YUV_ES3_PATH =
|
||||||
|
"shaders/fragment_shader_copy_external_yuv_es3.glsl";
|
||||||
|
// Color transform coefficients from
|
||||||
|
// https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/libstagefright/colorconversion/ColorConverter.cpp;l=668-670;drc=487adf977a50cac3929eba15fad0d0f461c7ff0f.
|
||||||
|
private static final float[] MATRIX_YUV_TO_BT2020_COLOR_TRANSFORM = {
|
||||||
|
1.168f, 1.168f, 1.168f,
|
||||||
|
0.0f, -0.188f, 2.148f,
|
||||||
|
1.683f, -0.652f, 0.0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final boolean enableExperimentalHdrEditing;
|
||||||
|
|
||||||
|
private @MonotonicNonNull GlProgram glProgram;
|
||||||
|
|
||||||
|
public ExternalCopyFrameProcessor(Context context, boolean enableExperimentalHdrEditing) {
|
||||||
|
this.context = context;
|
||||||
|
this.enableExperimentalHdrEditing = enableExperimentalHdrEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() throws IOException {
|
||||||
|
// TODO(b/205002913): check the loaded program is consistent with the attributes and uniforms
|
||||||
|
// expected in the code.
|
||||||
|
String vertexShaderFilePath =
|
||||||
|
enableExperimentalHdrEditing
|
||||||
|
? VERTEX_SHADER_TEX_TRANSFORM_ES3_PATH
|
||||||
|
: VERTEX_SHADER_TEX_TRANSFORM_PATH;
|
||||||
|
String fragmentShaderFilePath =
|
||||||
|
enableExperimentalHdrEditing
|
||||||
|
? FRAGMENT_SHADER_COPY_EXTERNAL_YUV_ES3_PATH
|
||||||
|
: FRAGMENT_SHADER_COPY_EXTERNAL_PATH;
|
||||||
|
glProgram = new GlProgram(context, vertexShaderFilePath, fragmentShaderFilePath);
|
||||||
|
// Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y.
|
||||||
|
glProgram.setBufferAttribute(
|
||||||
|
"aFramePosition", GlUtil.getNormalizedCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT);
|
||||||
|
glProgram.setBufferAttribute(
|
||||||
|
"aTexCoords", GlUtil.getTextureCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT);
|
||||||
|
if (enableExperimentalHdrEditing) {
|
||||||
|
// In HDR editing mode the decoder output is sampled in YUV.
|
||||||
|
glProgram.setFloatsUniform("uColorTransform", MATRIX_YUV_TO_BT2020_COLOR_TRANSFORM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the texture transform matrix for converting an external surface texture's coordinates to
|
||||||
|
* sampling locations.
|
||||||
|
*
|
||||||
|
* @param textureTransformMatrix The external surface texture's {@link
|
||||||
|
* android.graphics.SurfaceTexture#getTransformMatrix(float[]) transform matrix}.
|
||||||
|
*/
|
||||||
|
public void setTextureTransformMatrix(float[] textureTransformMatrix) {
|
||||||
|
checkStateNotNull(glProgram);
|
||||||
|
glProgram.setFloatsUniform("uTexTransform", textureTransformMatrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateProgramAndDraw(int inputTexId, long presentationTimeNs) {
|
||||||
|
checkStateNotNull(glProgram);
|
||||||
|
glProgram.setSamplerTexIdUniform("uTexSampler", inputTexId, /* unit= */ 0);
|
||||||
|
glProgram.use();
|
||||||
|
glProgram.bindAttributesAndUniforms();
|
||||||
|
GLES20.glClearColor(/* red= */ 0, /* green= */ 0, /* blue= */ 0, /* alpha= */ 0);
|
||||||
|
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||||
|
// The four-vertex triangle strip forms a quad.
|
||||||
|
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
if (glProgram != null) {
|
||||||
|
glProgram.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,6 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
|||||||
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Matrix;
|
|
||||||
import android.graphics.SurfaceTexture;
|
import android.graphics.SurfaceTexture;
|
||||||
import android.opengl.EGL14;
|
import android.opengl.EGL14;
|
||||||
import android.opengl.EGLContext;
|
import android.opengl.EGLContext;
|
||||||
@ -40,7 +39,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
*
|
*
|
||||||
* <p>Input becomes available on its {@link #getInputSurface() input surface} asynchronously so
|
* <p>Input becomes available on its {@link #getInputSurface() input surface} asynchronously so
|
||||||
* {@link #canProcessData()} needs to be checked before calling {@link #processData()}. Output is
|
* {@link #canProcessData()} needs to be checked before calling {@link #processData()}. Output is
|
||||||
* written to its {@link #create(Context, int, int, float, Matrix, Surface, boolean,
|
* written to its {@link #create(Context, int, int, float, GlFrameProcessor, Surface, boolean,
|
||||||
* Transformer.DebugViewProvider) output surface}.
|
* Transformer.DebugViewProvider) output surface}.
|
||||||
*/
|
*/
|
||||||
/* package */ final class FrameEditor {
|
/* package */ final class FrameEditor {
|
||||||
@ -56,7 +55,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
* @param outputWidth The output width in pixels.
|
* @param outputWidth The output width in pixels.
|
||||||
* @param outputHeight The output height in pixels.
|
* @param outputHeight The output height in pixels.
|
||||||
* @param pixelWidthHeightRatio The ratio of width over height, for each pixel.
|
* @param pixelWidthHeightRatio The ratio of width over height, for each pixel.
|
||||||
* @param transformationMatrix The transformation matrix to apply to each frame.
|
* @param transformationFrameProcessor The {@link GlFrameProcessor} to apply to each frame.
|
||||||
* @param outputSurface The {@link Surface}.
|
* @param outputSurface The {@link Surface}.
|
||||||
* @param enableExperimentalHdrEditing Whether to attempt to process the input as an HDR signal.
|
* @param enableExperimentalHdrEditing Whether to attempt to process the input as an HDR signal.
|
||||||
* @param debugViewProvider Provider for optional debug views to show intermediate output.
|
* @param debugViewProvider Provider for optional debug views to show intermediate output.
|
||||||
@ -65,12 +64,14 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
* files fails, or an OpenGL error occurs while creating and configuring the OpenGL
|
* files fails, or an OpenGL error occurs while creating and configuring the OpenGL
|
||||||
* components.
|
* components.
|
||||||
*/
|
*/
|
||||||
|
// TODO(b/214975934): Take a List<GlFrameProcessor> as input and rename FrameEditor to
|
||||||
|
// FrameProcessorChain.
|
||||||
public static FrameEditor create(
|
public static FrameEditor create(
|
||||||
Context context,
|
Context context,
|
||||||
int outputWidth,
|
int outputWidth,
|
||||||
int outputHeight,
|
int outputHeight,
|
||||||
float pixelWidthHeightRatio,
|
float pixelWidthHeightRatio,
|
||||||
Matrix transformationMatrix,
|
GlFrameProcessor transformationFrameProcessor,
|
||||||
Surface outputSurface,
|
Surface outputSurface,
|
||||||
boolean enableExperimentalHdrEditing,
|
boolean enableExperimentalHdrEditing,
|
||||||
Transformer.DebugViewProvider debugViewProvider)
|
Transformer.DebugViewProvider debugViewProvider)
|
||||||
@ -85,8 +86,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
TransformationException.ERROR_CODE_GL_INIT_FAILED);
|
TransformationException.ERROR_CODE_GL_INIT_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
GlFrameProcessor frameProcessor =
|
ExternalCopyFrameProcessor externalCopyFrameProcessor =
|
||||||
new GlFrameProcessor(context, transformationMatrix, enableExperimentalHdrEditing);
|
new ExternalCopyFrameProcessor(context, enableExperimentalHdrEditing);
|
||||||
@Nullable
|
@Nullable
|
||||||
SurfaceView debugSurfaceView =
|
SurfaceView debugSurfaceView =
|
||||||
debugViewProvider.getDebugPreviewSurfaceView(outputWidth, outputHeight);
|
debugViewProvider.getDebugPreviewSurfaceView(outputWidth, outputHeight);
|
||||||
@ -94,7 +95,9 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
EGLDisplay eglDisplay;
|
EGLDisplay eglDisplay;
|
||||||
EGLContext eglContext;
|
EGLContext eglContext;
|
||||||
EGLSurface eglSurface;
|
EGLSurface eglSurface;
|
||||||
int textureId;
|
int inputExternalTexId;
|
||||||
|
int intermediateTexId;
|
||||||
|
int frameBuffer;
|
||||||
@Nullable EGLSurface debugPreviewEglSurface = null;
|
@Nullable EGLSurface debugPreviewEglSurface = null;
|
||||||
try {
|
try {
|
||||||
eglDisplay = GlUtil.createEglDisplay();
|
eglDisplay = GlUtil.createEglDisplay();
|
||||||
@ -117,9 +120,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
}
|
}
|
||||||
|
|
||||||
GlUtil.focusEglSurface(eglDisplay, eglContext, eglSurface, outputWidth, outputHeight);
|
GlUtil.focusEglSurface(eglDisplay, eglContext, eglSurface, outputWidth, outputHeight);
|
||||||
textureId = GlUtil.createExternalTexture();
|
inputExternalTexId = GlUtil.createExternalTexture();
|
||||||
frameProcessor.initialize();
|
intermediateTexId = GlUtil.createTexture(outputWidth, outputHeight);
|
||||||
} catch (IOException | GlUtil.GlException e) {
|
frameBuffer = GlUtil.createFboForTexture(intermediateTexId);
|
||||||
|
externalCopyFrameProcessor.initialize();
|
||||||
|
transformationFrameProcessor.initialize();
|
||||||
|
} catch (GlUtil.GlException | IOException e) {
|
||||||
throw TransformationException.createForFrameEditor(
|
throw TransformationException.createForFrameEditor(
|
||||||
e, TransformationException.ERROR_CODE_GL_INIT_FAILED);
|
e, TransformationException.ERROR_CODE_GL_INIT_FAILED);
|
||||||
}
|
}
|
||||||
@ -138,8 +144,11 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
eglDisplay,
|
eglDisplay,
|
||||||
eglContext,
|
eglContext,
|
||||||
eglSurface,
|
eglSurface,
|
||||||
textureId,
|
inputExternalTexId,
|
||||||
frameProcessor,
|
intermediateTexId,
|
||||||
|
frameBuffer,
|
||||||
|
externalCopyFrameProcessor,
|
||||||
|
transformationFrameProcessor,
|
||||||
outputWidth,
|
outputWidth,
|
||||||
outputHeight,
|
outputHeight,
|
||||||
debugPreviewEglSurface,
|
debugPreviewEglSurface,
|
||||||
@ -147,12 +156,25 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
debugPreviewHeight);
|
debugPreviewHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final GlFrameProcessor frameProcessor;
|
// TODO(b/214975934): Write javadoc for fields where the purpose might be unclear to someone less
|
||||||
|
// familiar with this class and consider grouping some of these fields into new classes to
|
||||||
|
// reduce the number of constructor parameters.
|
||||||
|
private final ExternalCopyFrameProcessor externalCopyFrameProcessor;
|
||||||
|
private final GlFrameProcessor transformationFrameProcessor;
|
||||||
private final float[] textureTransformMatrix;
|
private final float[] textureTransformMatrix;
|
||||||
private final EGLDisplay eglDisplay;
|
private final EGLDisplay eglDisplay;
|
||||||
private final EGLContext eglContext;
|
private final EGLContext eglContext;
|
||||||
private final EGLSurface eglSurface;
|
private final EGLSurface eglSurface;
|
||||||
private final int textureId;
|
/** Indentifier of the external texture the {@code FrameEditor} reads its input from. */
|
||||||
|
private final int inputExternalTexId;
|
||||||
|
/**
|
||||||
|
* Indentifier of the texture where the output of the {@link ExternalCopyFrameProcessor} is
|
||||||
|
* written to and the {@link TransformationFrameProcessor} reads its input from.
|
||||||
|
*/
|
||||||
|
private final int intermediateTexId;
|
||||||
|
/** Identifier of a framebuffer object associated with the intermediate texture. */
|
||||||
|
private final int frameBuffer;
|
||||||
|
|
||||||
private final AtomicInteger pendingInputFrameCount;
|
private final AtomicInteger pendingInputFrameCount;
|
||||||
private final AtomicInteger availableInputFrameCount;
|
private final AtomicInteger availableInputFrameCount;
|
||||||
private final SurfaceTexture inputSurfaceTexture;
|
private final SurfaceTexture inputSurfaceTexture;
|
||||||
@ -169,8 +191,11 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
EGLDisplay eglDisplay,
|
EGLDisplay eglDisplay,
|
||||||
EGLContext eglContext,
|
EGLContext eglContext,
|
||||||
EGLSurface eglSurface,
|
EGLSurface eglSurface,
|
||||||
int textureId,
|
int inputExternalTexId,
|
||||||
GlFrameProcessor frameProcessor,
|
int intermediateTexId,
|
||||||
|
int frameBuffer,
|
||||||
|
ExternalCopyFrameProcessor externalCopyFrameProcessor,
|
||||||
|
GlFrameProcessor transformationFrameProcessor,
|
||||||
int outputWidth,
|
int outputWidth,
|
||||||
int outputHeight,
|
int outputHeight,
|
||||||
@Nullable EGLSurface debugPreviewEglSurface,
|
@Nullable EGLSurface debugPreviewEglSurface,
|
||||||
@ -179,8 +204,11 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
this.eglDisplay = eglDisplay;
|
this.eglDisplay = eglDisplay;
|
||||||
this.eglContext = eglContext;
|
this.eglContext = eglContext;
|
||||||
this.eglSurface = eglSurface;
|
this.eglSurface = eglSurface;
|
||||||
this.textureId = textureId;
|
this.inputExternalTexId = inputExternalTexId;
|
||||||
this.frameProcessor = frameProcessor;
|
this.intermediateTexId = intermediateTexId;
|
||||||
|
this.frameBuffer = frameBuffer;
|
||||||
|
this.externalCopyFrameProcessor = externalCopyFrameProcessor;
|
||||||
|
this.transformationFrameProcessor = transformationFrameProcessor;
|
||||||
this.outputWidth = outputWidth;
|
this.outputWidth = outputWidth;
|
||||||
this.outputHeight = outputHeight;
|
this.outputHeight = outputHeight;
|
||||||
this.debugPreviewEglSurface = debugPreviewEglSurface;
|
this.debugPreviewEglSurface = debugPreviewEglSurface;
|
||||||
@ -189,7 +217,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
pendingInputFrameCount = new AtomicInteger();
|
pendingInputFrameCount = new AtomicInteger();
|
||||||
availableInputFrameCount = new AtomicInteger();
|
availableInputFrameCount = new AtomicInteger();
|
||||||
textureTransformMatrix = new float[16];
|
textureTransformMatrix = new float[16];
|
||||||
inputSurfaceTexture = new SurfaceTexture(textureId);
|
inputSurfaceTexture = new SurfaceTexture(inputExternalTexId);
|
||||||
inputSurfaceTexture.setOnFrameAvailableListener(
|
inputSurfaceTexture.setOnFrameAvailableListener(
|
||||||
surfaceTexture -> {
|
surfaceTexture -> {
|
||||||
checkState(pendingInputFrameCount.getAndDecrement() > 0);
|
checkState(pendingInputFrameCount.getAndDecrement() > 0);
|
||||||
@ -235,10 +263,15 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
try {
|
try {
|
||||||
inputSurfaceTexture.updateTexImage();
|
inputSurfaceTexture.updateTexImage();
|
||||||
inputSurfaceTexture.getTransformMatrix(textureTransformMatrix);
|
inputSurfaceTexture.getTransformMatrix(textureTransformMatrix);
|
||||||
GlUtil.focusEglSurface(eglDisplay, eglContext, eglSurface, outputWidth, outputHeight);
|
|
||||||
frameProcessor.setTextureTransformMatrix(textureTransformMatrix);
|
|
||||||
frameProcessor.updateProgramAndDraw(textureId);
|
|
||||||
long presentationTimeNs = inputSurfaceTexture.getTimestamp();
|
long presentationTimeNs = inputSurfaceTexture.getTimestamp();
|
||||||
|
|
||||||
|
GlUtil.focusFramebuffer(
|
||||||
|
eglDisplay, eglContext, eglSurface, frameBuffer, outputWidth, outputHeight);
|
||||||
|
externalCopyFrameProcessor.setTextureTransformMatrix(textureTransformMatrix);
|
||||||
|
externalCopyFrameProcessor.updateProgramAndDraw(inputExternalTexId, presentationTimeNs);
|
||||||
|
|
||||||
|
GlUtil.focusEglSurface(eglDisplay, eglContext, eglSurface, outputWidth, outputHeight);
|
||||||
|
transformationFrameProcessor.updateProgramAndDraw(intermediateTexId, presentationTimeNs);
|
||||||
EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, presentationTimeNs);
|
EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, presentationTimeNs);
|
||||||
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
|
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
|
||||||
|
|
||||||
@ -260,8 +293,10 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
|
|
||||||
/** Releases all resources. */
|
/** Releases all resources. */
|
||||||
public void release() {
|
public void release() {
|
||||||
frameProcessor.release();
|
externalCopyFrameProcessor.release();
|
||||||
GlUtil.deleteTexture(textureId);
|
transformationFrameProcessor.release();
|
||||||
|
GlUtil.deleteTexture(inputExternalTexId);
|
||||||
|
GlUtil.deleteTexture(intermediateTexId);
|
||||||
GlUtil.destroyEglContext(eglDisplay, eglContext);
|
GlUtil.destroyEglContext(eglDisplay, eglContext);
|
||||||
inputSurfaceTexture.release();
|
inputSurfaceTexture.release();
|
||||||
inputSurface.release();
|
inputSurface.release();
|
||||||
|
@ -15,98 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.transformer;
|
package com.google.android.exoplayer2.transformer;
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Matrix;
|
|
||||||
import android.opengl.GLES20;
|
|
||||||
import com.google.android.exoplayer2.util.GlProgram;
|
|
||||||
import com.google.android.exoplayer2.util.GlUtil;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|
||||||
|
|
||||||
/** Manages a GLSL shader program for applying a transformation matrix to a frame. */
|
/** Manages a GLSL shader program for processing a frame. */
|
||||||
/* package */ class GlFrameProcessor {
|
/* package */ interface GlFrameProcessor {
|
||||||
|
|
||||||
static {
|
// TODO(b/214975934): Add getOutputDimensions(inputWidth, inputHeight) and move output dimension
|
||||||
GlUtil.glAssertionsEnabled = true;
|
// calculations out of the VideoTranscodingSamplePipeline into the frame processors.
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a 4x4, column-major Matrix float array, from an input {@link Matrix}. This is useful
|
|
||||||
* for converting to the 4x4 column-major format commonly used in OpenGL.
|
|
||||||
*/
|
|
||||||
private static float[] getGlMatrixArray(Matrix matrix) {
|
|
||||||
float[] matrix3x3Array = new float[9];
|
|
||||||
matrix.getValues(matrix3x3Array);
|
|
||||||
float[] matrix4x4Array = getMatrix4x4Array(matrix3x3Array);
|
|
||||||
|
|
||||||
// Transpose from row-major to column-major representations.
|
|
||||||
float[] transposedMatrix4x4Array = new float[16];
|
|
||||||
android.opengl.Matrix.transposeM(
|
|
||||||
transposedMatrix4x4Array, /* mTransOffset= */ 0, matrix4x4Array, /* mOffset= */ 0);
|
|
||||||
|
|
||||||
return transposedMatrix4x4Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a 4x4 matrix array containing the 3x3 matrix array's contents.
|
|
||||||
*
|
|
||||||
* <p>The 3x3 matrix array is expected to be in 2 dimensions, and the 4x4 matrix array is expected
|
|
||||||
* to be in 3 dimensions. The output will have the third row/column's values be an identity
|
|
||||||
* matrix's values, so that vertex transformations using this matrix will not affect the z axis.
|
|
||||||
* <br>
|
|
||||||
* Input format: [a, b, c, d, e, f, g, h, i] <br>
|
|
||||||
* Output format: [a, b, 0, c, d, e, 0, f, 0, 0, 1, 0, g, h, 0, i]
|
|
||||||
*/
|
|
||||||
private static float[] getMatrix4x4Array(float[] matrix3x3Array) {
|
|
||||||
float[] matrix4x4Array = new float[16];
|
|
||||||
matrix4x4Array[10] = 1;
|
|
||||||
for (int inputRow = 0; inputRow < 3; inputRow++) {
|
|
||||||
for (int inputColumn = 0; inputColumn < 3; inputColumn++) {
|
|
||||||
int outputRow = (inputRow == 2) ? 3 : inputRow;
|
|
||||||
int outputColumn = (inputColumn == 2) ? 3 : inputColumn;
|
|
||||||
matrix4x4Array[outputRow * 4 + outputColumn] = matrix3x3Array[inputRow * 3 + inputColumn];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matrix4x4Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String VERTEX_SHADER_TRANSFORMATION_PATH =
|
|
||||||
"shaders/vertex_shader_transformation.glsl";
|
|
||||||
private static final String FRAGMENT_SHADER_COPY_EXTERNAL_PATH =
|
|
||||||
"shaders/fragment_shader_copy_external.glsl";
|
|
||||||
private static final String VERTEX_SHADER_TRANSFORMATION_ES3_PATH =
|
|
||||||
"shaders/vertex_shader_transformation_es3.glsl";
|
|
||||||
private static final String FRAGMENT_SHADER_COPY_EXTERNAL_YUV_ES3_PATH =
|
|
||||||
"shaders/fragment_shader_copy_external_yuv_es3.glsl";
|
|
||||||
// Color transform coefficients from
|
|
||||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/libstagefright/colorconversion/ColorConverter.cpp;l=668-670;drc=487adf977a50cac3929eba15fad0d0f461c7ff0f.
|
|
||||||
private static final float[] MATRIX_YUV_TO_BT2020_COLOR_TRANSFORM = {
|
|
||||||
1.168f, 1.168f, 1.168f,
|
|
||||||
0.0f, -0.188f, 2.148f,
|
|
||||||
1.683f, -0.652f, 0.0f,
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final Matrix transformationMatrix;
|
|
||||||
private final boolean enableExperimentalHdrEditing;
|
|
||||||
|
|
||||||
private @MonotonicNonNull GlProgram glProgram;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance.
|
|
||||||
*
|
|
||||||
* @param context A {@link Context}.
|
|
||||||
* @param transformationMatrix The transformation matrix to apply to each frame.
|
|
||||||
* @param enableExperimentalHdrEditing Whether to attempt to process the input as an HDR signal.
|
|
||||||
*/
|
|
||||||
public GlFrameProcessor(
|
|
||||||
Context context, Matrix transformationMatrix, boolean enableExperimentalHdrEditing) {
|
|
||||||
this.context = context;
|
|
||||||
this.transformationMatrix = transformationMatrix;
|
|
||||||
this.enableExperimentalHdrEditing = enableExperimentalHdrEditing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does any initialization necessary such as loading and compiling a GLSL shader programs.
|
* Does any initialization necessary such as loading and compiling a GLSL shader programs.
|
||||||
@ -114,43 +29,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
* <p>This method may only be called after creating the OpenGL context and focusing a render
|
* <p>This method may only be called after creating the OpenGL context and focusing a render
|
||||||
* target.
|
* target.
|
||||||
*/
|
*/
|
||||||
public void initialize() throws IOException {
|
void initialize() throws IOException;
|
||||||
// TODO(b/205002913): check the loaded program is consistent with the attributes
|
|
||||||
// and uniforms expected in the code.
|
|
||||||
String vertexShaderFilePath =
|
|
||||||
enableExperimentalHdrEditing
|
|
||||||
? VERTEX_SHADER_TRANSFORMATION_ES3_PATH
|
|
||||||
: VERTEX_SHADER_TRANSFORMATION_PATH;
|
|
||||||
String fragmentShaderFilePath =
|
|
||||||
enableExperimentalHdrEditing
|
|
||||||
? FRAGMENT_SHADER_COPY_EXTERNAL_YUV_ES3_PATH
|
|
||||||
: FRAGMENT_SHADER_COPY_EXTERNAL_PATH;
|
|
||||||
|
|
||||||
glProgram = new GlProgram(context, vertexShaderFilePath, fragmentShaderFilePath);
|
|
||||||
// Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y.
|
|
||||||
glProgram.setBufferAttribute(
|
|
||||||
"aFramePosition", GlUtil.getNormalizedCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT);
|
|
||||||
glProgram.setBufferAttribute(
|
|
||||||
"aTexCoords", GlUtil.getTextureCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT);
|
|
||||||
if (enableExperimentalHdrEditing) {
|
|
||||||
// In HDR editing mode the decoder output is sampled in YUV.
|
|
||||||
glProgram.setFloatsUniform("uColorTransform", MATRIX_YUV_TO_BT2020_COLOR_TRANSFORM);
|
|
||||||
}
|
|
||||||
float[] transformationMatrixArray = getGlMatrixArray(transformationMatrix);
|
|
||||||
glProgram.setFloatsUniform("uTransformationMatrix", transformationMatrixArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the texture transform matrix for converting an external surface texture's coordinates to
|
|
||||||
* sampling locations.
|
|
||||||
*
|
|
||||||
* @param textureTransformMatrix The external surface texture's {@link
|
|
||||||
* android.graphics.SurfaceTexture#getTransformMatrix(float[]) transform matrix}.
|
|
||||||
*/
|
|
||||||
public void setTextureTransformMatrix(float[] textureTransformMatrix) {
|
|
||||||
checkStateNotNull(glProgram);
|
|
||||||
glProgram.setFloatsUniform("uTexTransform", textureTransformMatrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the shader program's vertex attributes and uniforms, binds them, and draws.
|
* Updates the shader program's vertex attributes and uniforms, binds them, and draws.
|
||||||
@ -159,23 +38,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
* focussing the correct render target before calling this method.
|
* focussing the correct render target before calling this method.
|
||||||
*
|
*
|
||||||
* @param inputTexId The identifier of an OpenGL texture that the fragment shader can sample from.
|
* @param inputTexId The identifier of an OpenGL texture that the fragment shader can sample from.
|
||||||
|
* @param presentationTimeNs The presentation timestamp of the current frame, in nanoseconds.
|
||||||
*/
|
*/
|
||||||
// TODO(b/214975934): Also pass presentationTimeNs.
|
void updateProgramAndDraw(int inputTexId, long presentationTimeNs);
|
||||||
public void updateProgramAndDraw(int inputTexId) {
|
|
||||||
checkStateNotNull(glProgram);
|
|
||||||
glProgram.setSamplerTexIdUniform("uTexSampler", inputTexId, /* unit= */ 0);
|
|
||||||
glProgram.use();
|
|
||||||
glProgram.bindAttributesAndUniforms();
|
|
||||||
GLES20.glClearColor(/* red= */ 0, /* green= */ 0, /* blue= */ 0, /* alpha= */ 0);
|
|
||||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
|
||||||
// The four-vertex triangle strip forms a quad.
|
|
||||||
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Releases all resources. */
|
/** Releases all resources. */
|
||||||
public void release() {
|
void release();
|
||||||
if (glProgram != null) {
|
|
||||||
glProgram.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 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.transformer;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.opengl.GLES20;
|
||||||
|
import com.google.android.exoplayer2.util.GlProgram;
|
||||||
|
import com.google.android.exoplayer2.util.GlUtil;
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
|
/** Applies a transformation matrix in the vertex shader. */
|
||||||
|
/* package */ class TransformationFrameProcessor implements GlFrameProcessor {
|
||||||
|
|
||||||
|
static {
|
||||||
|
GlUtil.glAssertionsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String VERTEX_SHADER_TRANSFORMATION_PATH =
|
||||||
|
"shaders/vertex_shader_transformation_es2.glsl";
|
||||||
|
private static final String FRAGMENT_SHADER_PATH = "shaders/fragment_shader_copy_es2.glsl";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a 4x4, column-major Matrix float array, from an input {@link Matrix}. This is useful
|
||||||
|
* for converting to the 4x4 column-major format commonly used in OpenGL.
|
||||||
|
*/
|
||||||
|
private static float[] getGlMatrixArray(Matrix matrix) {
|
||||||
|
float[] matrix3x3Array = new float[9];
|
||||||
|
matrix.getValues(matrix3x3Array);
|
||||||
|
float[] matrix4x4Array = getMatrix4x4Array(matrix3x3Array);
|
||||||
|
|
||||||
|
// Transpose from row-major to column-major representations.
|
||||||
|
float[] transposedMatrix4x4Array = new float[16];
|
||||||
|
android.opengl.Matrix.transposeM(
|
||||||
|
transposedMatrix4x4Array, /* mTransOffset= */ 0, matrix4x4Array, /* mOffset= */ 0);
|
||||||
|
|
||||||
|
return transposedMatrix4x4Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a 4x4 matrix array containing the 3x3 matrix array's contents.
|
||||||
|
*
|
||||||
|
* <p>The 3x3 matrix array is expected to be in 2 dimensions, and the 4x4 matrix array is expected
|
||||||
|
* to be in 3 dimensions. The output will have the third row/column's values be an identity
|
||||||
|
* matrix's values, so that vertex transformations using this matrix will not affect the z axis.
|
||||||
|
* <br>
|
||||||
|
* Input format: [a, b, c, d, e, f, g, h, i] <br>
|
||||||
|
* Output format: [a, b, 0, c, d, e, 0, f, 0, 0, 1, 0, g, h, 0, i]
|
||||||
|
*/
|
||||||
|
private static float[] getMatrix4x4Array(float[] matrix3x3Array) {
|
||||||
|
float[] matrix4x4Array = new float[16];
|
||||||
|
matrix4x4Array[10] = 1;
|
||||||
|
for (int inputRow = 0; inputRow < 3; inputRow++) {
|
||||||
|
for (int inputColumn = 0; inputColumn < 3; inputColumn++) {
|
||||||
|
int outputRow = (inputRow == 2) ? 3 : inputRow;
|
||||||
|
int outputColumn = (inputColumn == 2) ? 3 : inputColumn;
|
||||||
|
matrix4x4Array[outputRow * 4 + outputColumn] = matrix3x3Array[inputRow * 3 + inputColumn];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matrix4x4Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final Matrix transformationMatrix;
|
||||||
|
|
||||||
|
private @MonotonicNonNull GlProgram glProgram;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*
|
||||||
|
* @param context The {@link Context}.
|
||||||
|
* @param transformationMatrix The transformation matrix to apply to each frame.
|
||||||
|
*/
|
||||||
|
public TransformationFrameProcessor(Context context, Matrix transformationMatrix) {
|
||||||
|
this.context = context;
|
||||||
|
this.transformationMatrix = transformationMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() throws IOException {
|
||||||
|
// TODO(b/205002913): check the loaded program is consistent with the attributes and uniforms
|
||||||
|
// expected in the code.
|
||||||
|
glProgram = new GlProgram(context, VERTEX_SHADER_TRANSFORMATION_PATH, FRAGMENT_SHADER_PATH);
|
||||||
|
// Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y.
|
||||||
|
glProgram.setBufferAttribute(
|
||||||
|
"aFramePosition", GlUtil.getNormalizedCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT);
|
||||||
|
glProgram.setBufferAttribute(
|
||||||
|
"aTexCoords", GlUtil.getTextureCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT);
|
||||||
|
glProgram.setFloatsUniform("uTransformationMatrix", getGlMatrixArray(transformationMatrix));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateProgramAndDraw(int inputTexId, long presentationTimeNs) {
|
||||||
|
checkStateNotNull(glProgram);
|
||||||
|
glProgram.setSamplerTexIdUniform("uTexSampler", inputTexId, /* unit= */ 0);
|
||||||
|
glProgram.use();
|
||||||
|
glProgram.bindAttributesAndUniforms();
|
||||||
|
GLES20.glClearColor(/* red= */ 0, /* green= */ 0, /* blue= */ 0, /* alpha= */ 0);
|
||||||
|
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||||
|
// The four-vertex triangle strip forms a quad.
|
||||||
|
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
if (glProgram != null) {
|
||||||
|
glProgram.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -166,7 +166,7 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
encoderSupportedFormat.width,
|
encoderSupportedFormat.width,
|
||||||
encoderSupportedFormat.height,
|
encoderSupportedFormat.height,
|
||||||
inputFormat.pixelWidthHeightRatio,
|
inputFormat.pixelWidthHeightRatio,
|
||||||
transformationMatrix,
|
new TransformationFrameProcessor(context, transformationMatrix),
|
||||||
/* outputSurface= */ encoder.getInputSurface(),
|
/* outputSurface= */ encoder.getInputSurface(),
|
||||||
transformationRequest.enableHdrEditing,
|
transformationRequest.enableHdrEditing,
|
||||||
debugViewProvider);
|
debugViewProvider);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user