diff --git a/library/core/src/androidTest/assets/bitmap/image_256_256.png b/library/core/src/androidTest/assets/bitmap/image_256_256.png new file mode 100644 index 0000000000..cf1403eebd Binary files /dev/null and b/library/core/src/androidTest/assets/bitmap/image_256_256.png differ diff --git a/library/core/src/androidTest/assets/bitmap/image_80_60.bmp b/library/core/src/androidTest/assets/bitmap/image_80_60.bmp new file mode 100644 index 0000000000..440c80f1b5 Binary files /dev/null and b/library/core/src/androidTest/assets/bitmap/image_80_60.bmp differ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/surfacecapturer/SurfaceCapturer.java b/library/core/src/main/java/com/google/android/exoplayer2/surfacecapturer/SurfaceCapturer.java new file mode 100644 index 0000000000..4a7fd71494 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/surfacecapturer/SurfaceCapturer.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 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.surfacecapturer; + +import android.graphics.Bitmap; +import android.view.Surface; + +/** + * A surface capturer, which captures image drawn into its surface as bitmaps. + * + *
It constructs a {@link Surface}, which can be used as the output surface for an image producer + * to draw images to. As images are being drawn into this surface, this capturer will capture these + * images, and return them via {@link Callback}. The output images will have a fixed frame size of + * (width, height), and any image drawn into the surface will be stretched to fit this frame size. + */ +public abstract class SurfaceCapturer { + + /** The callback to be notified of the image capturing result. */ + public interface Callback { + + /** + * Called when the surface capturer has been able to capture its surface into a {@link Bitmap}. + * This will happen whenever the producer updates the image on the wrapped surface. + */ + void onSurfaceCaptured(Bitmap bitmap); + + /** Called when the surface capturer couldn't capture its surface due to an error. */ + void onSurfaceCaptureError(Exception e); + } + + /** The callback to be notified of the image capturing result. */ + private final Callback callback; + /** The width of the output images. */ + private final int outputWidth; + /** The height of the output images. */ + private final int outputHeight; + + /** + * Constructs a new instance. + * + * @param callback See {@link #callback}. + * @param outputWidth See {@link #outputWidth}. + * @param outputHeight See {@link #outputHeight}. + */ + protected SurfaceCapturer(Callback callback, int outputWidth, int outputHeight) { + this.callback = callback; + this.outputWidth = outputWidth; + this.outputHeight = outputHeight; + } + + /** Returns the callback to be notified of the image capturing result. */ + protected Callback getCallback() { + return callback; + } + + /** Returns the width of the output images. */ + public int getOutputWidth() { + return outputWidth; + } + + /** Returns the height of the output images. */ + public int getOutputHeight() { + return outputHeight; + } + + /** Returns a {@link Surface} that image producers (camera, video codec etc...) can draw to. */ + public abstract Surface getSurface(); + + /** Releases all kept resources. This instance cannot be used after this call. */ + public abstract void release(); +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java index 6fe76b9b2c..552a0f224d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java @@ -33,6 +33,12 @@ import java.lang.annotation.RetentionPolicy; @TargetApi(17) public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableListener, Runnable { + /** Listener to be called when the texture image on {@link SurfaceTexture} has been updated. */ + public interface TextureImageListener { + /** Called when the {@link SurfaceTexture} receives a new frame from its image producer. */ + void onFrameAvailable(); + } + /** Secure mode to be used by the EGL surface and context. */ @Retention(RetentionPolicy.SOURCE) @IntDef({SECURE_MODE_NONE, SECURE_MODE_SURFACELESS_CONTEXT, SECURE_MODE_PROTECTED_PBUFFER}) @@ -45,6 +51,9 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL /** Creating a secure surface backed by a pixel buffer. */ public static final int SECURE_MODE_PROTECTED_PBUFFER = 2; + private static final int EGL_SURFACE_WIDTH = 1; + private static final int EGL_SURFACE_HEIGHT = 1; + private static final int[] EGL_CONFIG_ATTRIBUTES = new int[] { EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, @@ -69,6 +78,7 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL private final Handler handler; private final int[] textureIdHolder; + private final @Nullable TextureImageListener callback; private @Nullable EGLDisplay display; private @Nullable EGLContext context; @@ -82,7 +92,21 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL * looper. */ public EGLSurfaceTexture(Handler handler) { + this(handler, /* callback= */ null); + } + + /** + * @param handler The {@link Handler} that will be used to call {@link + * SurfaceTexture#updateTexImage()} to update images on the {@link SurfaceTexture}. Note that + * {@link #init(int)} has to be called on the same looper thread as the looper of the {@link + * Handler}. + * @param callback The {@link TextureImageListener} to be called when the texture image on {@link + * SurfaceTexture} has been updated. This callback will be called on the same handler thread + * as the {@code handler}. + */ + public EGLSurfaceTexture(Handler handler, @Nullable TextureImageListener callback) { this.handler = handler; + this.callback = callback; textureIdHolder = new int[1]; } @@ -142,8 +166,20 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL @Override public void run() { + // Run on the provided handler thread when a new image frame is available. + dispatchOnFrameAvailable(); if (texture != null) { - texture.updateTexImage(); + try { + texture.updateTexImage(); + } catch (RuntimeException e) { + // Ignore + } + } + } + + private void dispatchOnFrameAvailable() { + if (callback != null) { + callback.onFrameAvailable(); } } @@ -220,9 +256,9 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL pbufferAttributes = new int[] { EGL14.EGL_WIDTH, - 1, + EGL_SURFACE_WIDTH, EGL14.EGL_HEIGHT, - 1, + EGL_SURFACE_HEIGHT, EGL_PROTECTED_CONTENT_EXT, EGL14.EGL_TRUE, EGL14.EGL_NONE @@ -230,8 +266,10 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL } else { pbufferAttributes = new int[] { - EGL14.EGL_WIDTH, 1, - EGL14.EGL_HEIGHT, 1, + EGL14.EGL_WIDTH, + EGL_SURFACE_WIDTH, + EGL14.EGL_HEIGHT, + EGL_SURFACE_HEIGHT, EGL14.EGL_NONE }; } diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/DummyMainThread.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DummyMainThread.java similarity index 100% rename from testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/DummyMainThread.java rename to testutils/src/main/java/com/google/android/exoplayer2/testutil/DummyMainThread.java