1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.media.filterfw.decoder; 18 19 import android.annotation.TargetApi; 20 import android.graphics.SurfaceTexture; 21 import android.graphics.SurfaceTexture.OnFrameAvailableListener; 22 import android.media.MediaCodec; 23 import android.media.MediaCodec.BufferInfo; 24 import android.media.MediaFormat; 25 import android.view.Surface; 26 27 import androidx.media.filterfw.FrameImage2D; 28 import androidx.media.filterfw.ImageShader; 29 import androidx.media.filterfw.TextureSource; 30 31 import java.nio.ByteBuffer; 32 33 /** 34 * {@link TrackDecoder} that decodes a video track and renders the frames onto a 35 * {@link SurfaceTexture}. 36 * 37 * This implementation uses the GPU for image operations such as copying 38 * and color-space conversion. 39 */ 40 @TargetApi(16) 41 public class GpuVideoTrackDecoder extends VideoTrackDecoder { 42 43 /** 44 * Identity fragment shader for external textures. 45 */ 46 private static final String COPY_FRAGMENT_SHADER = 47 "#extension GL_OES_EGL_image_external : require\n" + 48 "precision mediump float;\n" + 49 "uniform samplerExternalOES tex_sampler_0;\n" + 50 "varying vec2 v_texcoord;\n" + 51 "void main() {\n" + 52 " gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" + 53 "}\n"; 54 55 private final TextureSource mTextureSource; 56 private final SurfaceTexture mSurfaceTexture; // Access guarded by mFrameMonitor. 57 private final float[] mTransformMatrix; 58 59 private final int mOutputWidth; 60 private final int mOutputHeight; 61 62 private ImageShader mImageShader; 63 64 private long mCurrentPresentationTimeUs; 65 66 public GpuVideoTrackDecoder( 67 int trackIndex, MediaFormat format, Listener listener) { 68 super(trackIndex, format, listener); 69 70 // Create a surface texture to be used by the video track decoder. 71 mTextureSource = TextureSource.newExternalTexture(); 72 mSurfaceTexture = new SurfaceTexture(mTextureSource.getTextureId()); 73 mSurfaceTexture.detachFromGLContext(); 74 mSurfaceTexture.setOnFrameAvailableListener(new OnFrameAvailableListener() { 75 @Override 76 public void onFrameAvailable(SurfaceTexture surfaceTexture) { 77 markFrameAvailable(); 78 } 79 }); 80 81 mOutputWidth = format.getInteger(MediaFormat.KEY_WIDTH); 82 mOutputHeight = format.getInteger(MediaFormat.KEY_HEIGHT); 83 84 mTransformMatrix = new float[16]; 85 } 86 87 @Override 88 protected MediaCodec initMediaCodec(MediaFormat format) { 89 Surface surface = new Surface(mSurfaceTexture); 90 MediaCodec mediaCodec = MediaCodec.createDecoderByType( 91 format.getString(MediaFormat.KEY_MIME)); 92 mediaCodec.configure(format, surface, null, 0); 93 surface.release(); 94 return mediaCodec; 95 } 96 97 @Override 98 protected boolean onDataAvailable( 99 MediaCodec codec, ByteBuffer[] buffers, int bufferIndex, BufferInfo info) { 100 boolean textureAvailable = waitForFrameGrab(); 101 102 mCurrentPresentationTimeUs = info.presentationTimeUs; 103 104 // Only render the next frame if we weren't interrupted. 105 codec.releaseOutputBuffer(bufferIndex, textureAvailable); 106 107 if (textureAvailable) { 108 if (updateTexture()) { 109 notifyListener(); 110 } 111 } 112 113 return false; 114 } 115 116 /** 117 * Waits for the texture's {@link OnFrameAvailableListener} to be notified and then updates 118 * the internal {@link SurfaceTexture}. 119 */ 120 private boolean updateTexture() { 121 // Wait for the frame we just released to appear in the texture. 122 synchronized (mFrameMonitor) { 123 try { 124 while (!mFrameAvailable) { 125 mFrameMonitor.wait(); 126 } 127 mSurfaceTexture.attachToGLContext(mTextureSource.getTextureId()); 128 mSurfaceTexture.updateTexImage(); 129 mSurfaceTexture.detachFromGLContext(); 130 return true; 131 } catch (InterruptedException e) { 132 return false; 133 } 134 } 135 } 136 137 @Override 138 protected void copyFrameDataTo(FrameImage2D outputVideoFrame, int rotation) { 139 TextureSource targetTexture = TextureSource.newExternalTexture(); 140 mSurfaceTexture.attachToGLContext(targetTexture.getTextureId()); 141 mSurfaceTexture.getTransformMatrix(mTransformMatrix); 142 143 ImageShader imageShader = getImageShader(); 144 imageShader.setSourceTransform(mTransformMatrix); 145 146 int outputWidth = mOutputWidth; 147 int outputHeight = mOutputHeight; 148 if (rotation != 0) { 149 float[] targetCoords = getRotationCoords(rotation); 150 imageShader.setTargetCoords(targetCoords); 151 if (needSwapDimension(rotation)) { 152 outputWidth = mOutputHeight; 153 outputHeight = mOutputWidth; 154 } 155 } 156 outputVideoFrame.resize(new int[] { outputWidth, outputHeight }); 157 imageShader.process( 158 targetTexture, 159 outputVideoFrame.lockRenderTarget(), 160 outputWidth, 161 outputHeight); 162 outputVideoFrame.setTimestamp(mCurrentPresentationTimeUs * 1000); 163 outputVideoFrame.unlock(); 164 targetTexture.release(); 165 166 mSurfaceTexture.detachFromGLContext(); 167 } 168 169 @Override 170 public void release() { 171 super.release(); 172 synchronized (mFrameMonitor) { 173 mTextureSource.release(); 174 mSurfaceTexture.release(); 175 } 176 } 177 178 /* 179 * This method has to be called on the MFF processing thread. 180 */ 181 private ImageShader getImageShader() { 182 if (mImageShader == null) { 183 mImageShader = new ImageShader(COPY_FRAGMENT_SHADER); 184 mImageShader.setTargetRect(0f, 1f, 1f, -1f); 185 } 186 return mImageShader; 187 } 188 189 /** 190 * Get the quad coords for rotation. 191 * @param rotation applied to the frame, value is one of 192 * {ROTATE_NONE, ROTATE_90_RIGHT, ROTATE_180, ROTATE_90_LEFT} 193 * @return coords the calculated quad coords for the given rotation 194 */ 195 private static float[] getRotationCoords(int rotation) { 196 switch(rotation) { 197 case MediaDecoder.ROTATE_90_RIGHT: 198 return new float[] { 0f, 0f, 0f, 1f, 1f, 0f, 1f, 1f }; 199 case MediaDecoder.ROTATE_180: 200 return new float[] { 1f, 0f, 0f, 0f, 1f, 1f, 0f, 1f }; 201 case MediaDecoder.ROTATE_90_LEFT: 202 return new float[] { 1f, 1f, 1f, 0f, 0f, 1f, 0f, 0f }; 203 case MediaDecoder.ROTATE_NONE: 204 return new float[] { 0f, 1f, 1f, 1f, 0f, 0f, 1f, 0f }; 205 default: 206 throw new IllegalArgumentException("Unsupported rotation angle."); 207 } 208 } 209 210 } 211