Browse Source

修复播放老版本视频素材异常问题

irisfdcui 5 years ago
parent
commit
1f6041c274

+ 36 - 10
Android/PlayerProj/animplayer/src/main/java/com/tencent/qgame/animplayer/AnimConfigManager.kt

@@ -73,16 +73,42 @@ class AnimConfigManager(val player: AnimPlayer) {
         config?.apply {
         config?.apply {
             videoWidth = _videoWidth
             videoWidth = _videoWidth
             videoHeight = _videoHeight
             videoHeight = _videoHeight
-            if (defaultVideoMode == Constant.VIDEO_MODE_SPLIT_VERTICAL) { // 上下对齐
-                width = _videoWidth
-                height = _videoHeight / 2
-                alphaPointRect = PointRect(0, 0, width, height)
-                rgbPointRect = PointRect(0, height, width, height)
-            } else { // 默认左右对齐
-                width = _videoWidth / 2
-                height = _videoHeight
-                alphaPointRect = PointRect(0, 0, width, height)
-                rgbPointRect = PointRect(width, 0, width, height)
+            when (defaultVideoMode) {
+                Constant.VIDEO_MODE_SPLIT_HORIZONTAL -> {
+                    // 视频左右对齐(alpha左\rgb右)
+                    width = _videoWidth / 2
+                    height = _videoHeight
+                    alphaPointRect = PointRect(0, 0, width, height)
+                    rgbPointRect = PointRect(width, 0, width, height)
+                }
+                Constant.VIDEO_MODE_SPLIT_VERTICAL -> {
+                    // 视频上下对齐(alpha上\rgb下)
+                    width = _videoWidth
+                    height = _videoHeight / 2
+                    alphaPointRect = PointRect(0, 0, width, height)
+                    rgbPointRect = PointRect(0, height, width, height)
+                }
+                Constant.VIDEO_MODE_SPLIT_HORIZONTAL_REVERSE -> {
+                    // 视频左右对齐(rgb左\alpha右)
+                    width = _videoWidth / 2
+                    height = _videoHeight
+                    rgbPointRect = PointRect(0, 0, width, height)
+                    alphaPointRect = PointRect(width, 0, width, height)
+                }
+                Constant.VIDEO_MODE_SPLIT_VERTICAL_REVERSE -> {
+                    // 视频上下对齐(rgb上\alpha下)
+                    width = _videoWidth
+                    height = _videoHeight / 2
+                    rgbPointRect = PointRect(0, 0, width, height)
+                    alphaPointRect = PointRect(0, height, width, height)
+                }
+                else -> {
+                    // 默认视频左右对齐(alpha左\rgb右)
+                    width = _videoWidth / 2
+                    height = _videoHeight
+                    alphaPointRect = PointRect(0, 0, width, height)
+                    rgbPointRect = PointRect(width, 0, width, height)
+                }
             }
             }
         }
         }
     }
     }

+ 6 - 2
Android/PlayerProj/animplayer/src/main/java/com/tencent/qgame/animplayer/Constant.kt

@@ -25,9 +25,13 @@ object Constant {
 
 
     // 视频对齐方式 (兼容老版本视频模式)
     // 视频对齐方式 (兼容老版本视频模式)
     @Deprecated("Compatible older version mp4")
     @Deprecated("Compatible older version mp4")
-    const val VIDEO_MODE_SPLIT_HORIZONTAL = 1 // 视频左右对齐
+    const val VIDEO_MODE_SPLIT_HORIZONTAL = 1 // 视频左右对齐(alpha左\rgb右)
     @Deprecated("Compatible older version mp4")
     @Deprecated("Compatible older version mp4")
-    const val VIDEO_MODE_SPLIT_VERTICAL = 2 // 视频上下对齐
+    const val VIDEO_MODE_SPLIT_VERTICAL = 2 // 视频上下对齐(alpha上\rgb下)
+    @Deprecated("Compatible older version mp4")
+    const val VIDEO_MODE_SPLIT_HORIZONTAL_REVERSE = 3 // 视频左右对齐(rgb左\alpha右)
+    @Deprecated("Compatible older version mp4")
+    const val VIDEO_MODE_SPLIT_VERTICAL_REVERSE = 4 // 视频上下对齐(rgb上\alpha下)
 
 
 
 
     const val OK = 0 // 成功
     const val OK = 0 // 成功

+ 8 - 5
Android/PlayerProj/animplayer/src/main/java/com/tencent/qgame/animplayer/Decoder.kt

@@ -56,7 +56,7 @@ abstract class Decoder(val player: AnimPlayer) : IAnimListener {
         }
         }
     }
     }
 
 
-    var render: Render? = null
+    var render: IRenderListener? = null
     val renderThread = HandlerHolder(null, null)
     val renderThread = HandlerHolder(null, null)
     val decodeThread = HandlerHolder(null, null)
     val decodeThread = HandlerHolder(null, null)
     private var surfaceWidth = 0
     private var surfaceWidth = 0
@@ -83,16 +83,19 @@ abstract class Decoder(val player: AnimPlayer) : IAnimListener {
         return createThread(renderThread, "anim_render_thread") && createThread(decodeThread, "anim_decode_thread")
         return createThread(renderThread, "anim_render_thread") && createThread(decodeThread, "anim_decode_thread")
     }
     }
 
 
-    fun prepareRender(): Boolean {
+    fun prepareRender(needAlign: Boolean): Boolean {
         if (render == null) {
         if (render == null) {
             ALog.i(TAG, "prepareRender")
             ALog.i(TAG, "prepareRender")
             player.animView.getSurfaceTexture()?.apply {
             player.animView.getSurfaceTexture()?.apply {
-                render = Render(this).apply {
-                    updateViewPort(surfaceWidth, surfaceHeight)
+                if (needAlign) {
+                    render = YUVRender(this)
+                } else {
+                    render = Render(this).apply {
+                        updateViewPort(surfaceWidth, surfaceHeight)
+                    }
                 }
                 }
             }
             }
         }
         }
-        render?.createTexture()
         return render != null
         return render != null
     }
     }
 
 

+ 81 - 15
Android/PlayerProj/animplayer/src/main/java/com/tencent/qgame/animplayer/HardDecoder.kt

@@ -17,6 +17,7 @@ package com.tencent.qgame.animplayer
 
 
 import android.graphics.SurfaceTexture
 import android.graphics.SurfaceTexture
 import android.media.MediaCodec
 import android.media.MediaCodec
+import android.media.MediaCodecInfo
 import android.media.MediaExtractor
 import android.media.MediaExtractor
 import android.media.MediaFormat
 import android.media.MediaFormat
 import android.os.Build
 import android.os.Build
@@ -36,6 +37,17 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
     private val bufferInfo by lazy { MediaCodec.BufferInfo() }
     private val bufferInfo by lazy { MediaCodec.BufferInfo() }
     private var needDestroy = false
     private var needDestroy = false
 
 
+    // 动画的原始尺寸
+    private var videoWidth = 0
+    private var videoHeight = 0
+
+    // 动画对齐后的尺寸
+    private var alignWidth = 0
+    private var alignHeight = 0
+
+    // 动画是否被对齐标志位
+    private var needAlign = false
+
     override fun start(fileContainer: IFileContainer) {
     override fun start(fileContainer: IFileContainer) {
         isStopReq = false
         isStopReq = false
         needDestroy = false
         needDestroy = false
@@ -49,11 +61,15 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
     override fun onFrameAvailable(surfaceTexture: SurfaceTexture?) {
     override fun onFrameAvailable(surfaceTexture: SurfaceTexture?) {
         if (isStopReq) return
         if (isStopReq) return
         ALog.d(TAG, "onFrameAvailable")
         ALog.d(TAG, "onFrameAvailable")
+        renderData()
+    }
+
+    fun renderData() {
         renderThread.handler?.post {
         renderThread.handler?.post {
             try {
             try {
                 glTexture?.apply {
                 glTexture?.apply {
                     updateTexImage()
                     updateTexImage()
-                    render?.renderFrame(player.configManager.config)
+                    render?.renderFrame()
                     player.pluginManager.onRendering()
                     player.pluginManager.onRendering()
                     render?.swapBuffers()
                     render?.swapBuffers()
                 }
                 }
@@ -64,15 +80,6 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
     }
     }
 
 
     private fun startPlay(fileContainer: IFileContainer) {
     private fun startPlay(fileContainer: IFileContainer) {
-        try {
-            if (!prepareRender()) {
-                throw RuntimeException("render create fail")
-            }
-        } catch (t: Throwable) {
-            onFailed(Constant.REPORT_ERROR_TYPE_CREATE_RENDER, "${Constant.ERROR_MSG_CREATE_RENDER} e=$t")
-            release(null, null)
-            return
-        }
 
 
         var extractor: MediaExtractor? = null
         var extractor: MediaExtractor? = null
         var decoder: MediaCodec? = null
         var decoder: MediaCodec? = null
@@ -103,9 +110,23 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
                 }
                 }
             }
             }
 
 
-            val videoWidth = format.getInteger(MediaFormat.KEY_WIDTH)
-            val videoHeight = format.getInteger(MediaFormat.KEY_HEIGHT)
+            videoWidth = format.getInteger(MediaFormat.KEY_WIDTH)
+            videoHeight = format.getInteger(MediaFormat.KEY_HEIGHT)
             ALog.i(TAG, "Video size is $videoWidth x $videoHeight")
             ALog.i(TAG, "Video size is $videoWidth x $videoHeight")
+
+            // 判断是否需要对齐,这样直接判断有风险,后期想办法改
+            needAlign = !(videoWidth % 16 == 0)
+
+            try {
+                if (!prepareRender(needAlign)) {
+                    throw RuntimeException("render create fail")
+                }
+            } catch (t: Throwable) {
+                onFailed(Constant.REPORT_ERROR_TYPE_CREATE_RENDER, "${Constant.ERROR_MSG_CREATE_RENDER} e=$t")
+                release(null, null)
+                return
+            }
+
             preparePlay(videoWidth, videoHeight)
             preparePlay(videoWidth, videoHeight)
 
 
             render?.apply {
             render?.apply {
@@ -128,7 +149,16 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
             val mime = format.getString(MediaFormat.KEY_MIME) ?: ""
             val mime = format.getString(MediaFormat.KEY_MIME) ?: ""
             ALog.i(TAG, "Video MIME is $mime")
             ALog.i(TAG, "Video MIME is $mime")
             decoder = MediaCodec.createDecoderByType(mime).apply {
             decoder = MediaCodec.createDecoderByType(mime).apply {
-                configure(format, Surface(glTexture), null, 0)
+                if (needAlign) {
+                    format.setInteger(
+                            MediaFormat.KEY_COLOR_FORMAT,
+                            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar
+                    )
+                    configure(format, null, null, 0)
+                } else {
+                    configure(format, Surface(glTexture), null, 0)
+                }
+
                 start()
                 start()
                 decodeThread.handler?.post {
                 decodeThread.handler?.post {
                     try {
                     try {
@@ -194,6 +224,12 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
                     decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED -> ALog.d(TAG, "decoder output buffers changed")
                     decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED -> ALog.d(TAG, "decoder output buffers changed")
                     decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
                     decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
                         val format = decoder.outputFormat
                         val format = decoder.outputFormat
+                        try {
+                            alignWidth = format.getInteger(MediaFormat.KEY_STRIDE)
+                            alignHeight = format.getInteger(MediaFormat.KEY_SLICE_HEIGHT)
+                        } catch (t: Throwable) {
+                            ALog.e(TAG, "formatChange $t", t)
+                        }
                         ALog.i(TAG, "decoder output format changed: $format")
                         ALog.i(TAG, "decoder output format changed: $format")
                         // val (w,h) = formatChange(format)
                         // val (w,h) = formatChange(format)
                         // videoSizeChange(w, h)
                         // videoSizeChange(w, h)
@@ -213,8 +249,12 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
                             speedControlUtil.preRender(bufferInfo.presentationTimeUs)
                             speedControlUtil.preRender(bufferInfo.presentationTimeUs)
                         }
                         }
 
 
+                        if (needAlign) {
+                            yuvProcess(decoder, decoderStatus)
+                        }
+
                         // release & render
                         // release & render
-                        decoder.releaseOutputBuffer(decoderStatus, doRender)
+                        decoder.releaseOutputBuffer(decoderStatus, doRender && !needAlign)
 
 
                         if (frameIndex == 0) {
                         if (frameIndex == 0) {
                             onVideoStart()
                             onVideoStart()
@@ -242,6 +282,32 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
 
 
     }
     }
 
 
+    private fun yuvProcess(decoder: MediaCodec, outputIndex: Int) {
+        val outputBuffer = decoder.outputBuffers[outputIndex]
+        outputBuffer?.let {
+            it.position(0)
+            it.limit(bufferInfo.offset + bufferInfo.size)
+            var yuvData = ByteArray(outputBuffer.remaining())
+            outputBuffer.get(yuvData)
+            if (yuvData.isNotEmpty()) {
+                var yData = ByteArray(videoWidth * videoHeight)
+                var uvData = ByteArray(videoWidth * videoHeight / 2)
+                yuvCopy(yuvData, 0, alignWidth, alignHeight, yData, videoWidth, videoHeight)
+                yuvCopy(yuvData, alignWidth * alignHeight, alignWidth, alignHeight / 2, uvData, videoWidth, videoHeight / 2)
+                render?.setYUVData(videoWidth, videoHeight, yData, uvData)
+                renderData()
+            }
+        }
+    }
+
+    private fun yuvCopy(src: ByteArray, srcOffset: Int, inWidth: Int, inHeight: Int, dest: ByteArray, outWidth: Int, outHeight: Int) {
+        for (h in 0 until inHeight) {
+            if (h < outHeight) {
+                System.arraycopy(src, srcOffset + h * inWidth, dest, h * outWidth, outWidth)
+            }
+        }
+    }
+
     private fun formatChange(format: MediaFormat): Pair<Int, Int> {
     private fun formatChange(format: MediaFormat): Pair<Int, Int> {
         try {
         try {
             // 实际视频的纹理大小
             // 实际视频的纹理大小
@@ -293,7 +359,7 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
     private fun destroyInner() {
     private fun destroyInner() {
         renderThread.handler?.post {
         renderThread.handler?.post {
             player.pluginManager.onDestroy()
             player.pluginManager.onDestroy()
-            render?.destroy()
+            render?.destroyRender()
             render = null
             render = null
             onVideoDestroy()
             onVideoDestroy()
             destroyThread()
             destroyThread()

+ 39 - 0
Android/PlayerProj/animplayer/src/main/java/com/tencent/qgame/animplayer/IRenderListener.kt

@@ -0,0 +1,39 @@
+package com.tencent.qgame.animplayer
+
+interface IRenderListener {
+
+    /**
+     * 初始化渲染环境,获取shader字段,创建绑定纹理
+     */
+    fun initRender()
+
+    /**
+     * 渲染上屏
+     */
+    fun renderFrame()
+
+    fun clearFrame()
+
+    /**
+     * 释放纹理
+     */
+    fun destroyRender()
+
+    /**
+     * 设置视频配置
+     */
+    fun setAnimConfig(config: AnimConfig)
+
+    /**
+     * 显示区域大小变化
+     */
+    fun updateViewPort(width: Int, height: Int) {}
+
+    fun getExternalTexture(): Int
+
+    fun releaseTexture()
+
+    fun swapBuffers()
+
+    fun setYUVData(width: Int, height: Int, y: ByteArray?, uv: ByteArray?) {}
+}

+ 36 - 43
Android/PlayerProj/animplayer/src/main/java/com/tencent/qgame/animplayer/Render.kt

@@ -26,7 +26,7 @@ import java.nio.ByteBuffer
 import java.nio.ByteOrder
 import java.nio.ByteOrder
 import java.nio.ShortBuffer
 import java.nio.ShortBuffer
 
 
-class Render(surfaceTexture: SurfaceTexture) {
+class Render(surfaceTexture: SurfaceTexture): IRenderListener {
 
 
     companion object {
     companion object {
         private const val TAG = "${Constant.TAG}.Render"
         private const val TAG = "${Constant.TAG}.Render"
@@ -48,11 +48,7 @@ class Render(surfaceTexture: SurfaceTexture) {
 
 
     init {
     init {
         eglUtil.start(surfaceTexture)
         eglUtil.start(surfaceTexture)
-        initGL()
-    }
-
-    private fun initGL() {
-        compileShader()
+        initRender()
     }
     }
 
 
     private fun setVertexBuf(config: AnimConfig) {
     private fun setVertexBuf(config: AnimConfig) {
@@ -66,7 +62,13 @@ class Render(surfaceTexture: SurfaceTexture) {
         rgbArray.setArray(rgb)
         rgbArray.setArray(rgb)
     }
     }
 
 
-    fun createTexture() {
+    override fun initRender() {
+        shaderProgram = ShaderUtil.createProgram(RenderConstant.VERTEX_SHADER, RenderConstant.FRAGMENT_SHADER)
+        uTextureLocation = GLES20.glGetUniformLocation(shaderProgram, "texture")
+        aPositionLocation = GLES20.glGetAttribLocation(shaderProgram, "vPosition")
+        aTextureAlphaLocation = GLES20.glGetAttribLocation(shaderProgram, "vTexCoordinateAlpha")
+        aTextureRgbLocation = GLES20.glGetAttribLocation(shaderProgram, "vTexCoordinateRgb")
+
         GLES20.glGenTextures(genTexture.size, genTexture, 0)
         GLES20.glGenTextures(genTexture.size, genTexture, 0)
         GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, genTexture[0])
         GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, genTexture[0])
         GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
         GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
@@ -75,18 +77,35 @@ class Render(surfaceTexture: SurfaceTexture) {
         GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
         GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
     }
     }
 
 
-    private fun compileShader() {
-        shaderProgram = ShaderUtil.createProgram(RenderConstant.VERTEX_SHADER, RenderConstant.FRAGMENT_SHADER)
-        uTextureLocation = GLES20.glGetUniformLocation(shaderProgram, "texture")
-        aPositionLocation = GLES20.glGetAttribLocation(shaderProgram, "vPosition")
-        aTextureAlphaLocation = GLES20.glGetAttribLocation(shaderProgram, "vTexCoordinateAlpha")
-        aTextureRgbLocation = GLES20.glGetAttribLocation(shaderProgram, "vTexCoordinateRgb")
+    override fun renderFrame() {
+        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+        if (surfaceSizeChanged && surfaceWidth>0 && surfaceHeight>0) {
+            surfaceSizeChanged = false
+            GLES20.glViewport(0,0, surfaceWidth, surfaceHeight)
+        }
+        draw()
+    }
+
+    override fun clearFrame() {
+        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+        eglUtil.swapBuffers()
+    }
+
+    override fun destroyRender() {
+        releaseTexture()
+        eglUtil.release()
+    }
+
+    override fun releaseTexture() {
+        GLES20.glDeleteTextures(genTexture.size, genTexture, 0)
     }
     }
 
 
     /**
     /**
      * 设置视频配置
      * 设置视频配置
      */
      */
-    fun setAnimConfig(config: AnimConfig) {
+    override fun setAnimConfig(config: AnimConfig) {
         setVertexBuf(config)
         setVertexBuf(config)
         setTexCoords(config)
         setTexCoords(config)
     }
     }
@@ -94,47 +113,21 @@ class Render(surfaceTexture: SurfaceTexture) {
     /**
     /**
      * 显示区域大小变化
      * 显示区域大小变化
      */
      */
-    fun updateViewPort(width: Int, height: Int) {
+    override fun updateViewPort(width: Int, height: Int) {
         if (width <=0 || height <=0) return
         if (width <=0 || height <=0) return
         surfaceSizeChanged = true
         surfaceSizeChanged = true
         surfaceWidth = width
         surfaceWidth = width
         surfaceHeight = height
         surfaceHeight = height
     }
     }
 
 
-    fun clearFrame() {
-        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+    override fun swapBuffers() {
         eglUtil.swapBuffers()
         eglUtil.swapBuffers()
     }
     }
 
 
-    fun renderFrame(config: AnimConfig?) {
-        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
-        if (surfaceSizeChanged && surfaceWidth>0 && surfaceHeight>0) {
-            surfaceSizeChanged = false
-            GLES20.glViewport(0,0, surfaceWidth, surfaceHeight)
-        }
-        draw()
-    }
-
-    fun swapBuffers() {
-        eglUtil.swapBuffers()
-    }
-
-
-    fun destroy() {
-        releaseTexture()
-        eglUtil.release()
-    }
-
-    fun releaseTexture() {
-        GLES20.glDeleteTextures(genTexture.size, genTexture, 0)
-    }
-
     /**
     /**
      * mediaCodec渲染使用的
      * mediaCodec渲染使用的
      */
      */
-    fun getExternalTexture(): Int {
+    override fun getExternalTexture(): Int {
         return genTexture[0]
         return genTexture[0]
     }
     }
 
 

+ 146 - 0
Android/PlayerProj/animplayer/src/main/java/com/tencent/qgame/animplayer/YUVRender.kt

@@ -0,0 +1,146 @@
+package com.tencent.qgame.animplayer
+
+import android.graphics.SurfaceTexture
+import android.opengl.GLES20
+import com.tencent.qgame.animplayer.util.GlFloatArray
+import com.tencent.qgame.animplayer.util.ShaderUtil.createProgram
+import com.tencent.qgame.animplayer.util.TexCoordsUtil
+import com.tencent.qgame.animplayer.util.VertexUtil
+import java.nio.ByteBuffer
+
+class YUVRender (surfaceTexture: SurfaceTexture): IRenderListener {
+
+    private val vertexArray = GlFloatArray()
+    private val alphaArray = GlFloatArray()
+    private val rgbArray = GlFloatArray()
+
+    private var shaderProgram = 0
+
+    //顶点位置
+    private var avPosition = 0
+
+    //rgb纹理位置
+    private var rgbPosition = 0
+
+    //alpha纹理位置
+    private var alphaPosition = 0
+
+    //shader  yuv变量
+    private var samplerY = 0
+    private var samplerUV = 0
+    private var textureId = IntArray(2)
+
+    //YUV数据
+    private var widthYUV = 0
+    private var heightYUV = 0
+    private var y: ByteBuffer? = null
+    private var uv: ByteBuffer? = null
+
+    private val eglUtil: EGLUtil = EGLUtil()
+
+    init {
+        eglUtil.start(surfaceTexture)
+        initRender()
+    }
+
+    override fun initRender() {
+        shaderProgram = createProgram(YUVShader.VERTEX_SHADER, YUVShader.FRAGMENT_SHADER)
+        //获取顶点坐标字段
+        avPosition = GLES20.glGetAttribLocation(shaderProgram, "v_Position")
+        //获取纹理坐标字段
+        rgbPosition = GLES20.glGetAttribLocation(shaderProgram, "vTexCoordinateRgb")
+        alphaPosition = GLES20.glGetAttribLocation(shaderProgram, "vTexCoordinateAlpha")
+
+        //获取yuv字段
+        samplerY = GLES20.glGetUniformLocation(shaderProgram, "sampler_y")
+        samplerUV = GLES20.glGetUniformLocation(shaderProgram, "sampler_uv")
+        //创建2个纹理
+        GLES20.glGenTextures(textureId.size, textureId, 0)
+
+        //绑定纹理
+        for (id in textureId) {
+            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id)
+            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT)
+            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT)
+            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR)
+            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR)
+        }
+    }
+
+    override fun renderFrame() {
+        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+        draw()
+    }
+
+    override fun clearFrame() {
+        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+        eglUtil.swapBuffers()
+    }
+
+    override fun destroyRender() {
+        releaseTexture()
+        eglUtil.release()
+    }
+
+    override fun setAnimConfig(config: AnimConfig) {
+        vertexArray.setArray(VertexUtil.create(config.width, config.height, PointRect(0, 0, config.width, config.height), vertexArray.array))
+        val alpha = TexCoordsUtil.create(config.videoWidth, config.videoHeight, config.alphaPointRect, alphaArray.array)
+        val rgb = TexCoordsUtil.create(config.videoWidth, config.videoHeight, config.rgbPointRect, rgbArray.array)
+        alphaArray.setArray(alpha)
+        rgbArray.setArray(rgb)
+    }
+
+    override fun getExternalTexture(): Int {
+        return textureId[0]
+    }
+
+    override fun releaseTexture() {
+        GLES20.glDeleteTextures(textureId.size, textureId, 0)
+    }
+
+    override fun swapBuffers() {
+        eglUtil.swapBuffers()
+    }
+
+    override fun setYUVData(width: Int, height: Int, y: ByteArray?, uv: ByteArray?) {
+        widthYUV = width
+        heightYUV = height
+        this.y = ByteBuffer.wrap(y)
+        this.uv = ByteBuffer.wrap(uv)
+    }
+
+    private fun draw() {
+        if (widthYUV > 0 && heightYUV > 0 && y != null && uv != null) {
+            GLES20.glUseProgram(shaderProgram)
+            vertexArray.setVertexAttribPointer(avPosition)
+            alphaArray.setVertexAttribPointer(alphaPosition)
+            rgbArray.setVertexAttribPointer(rgbPosition)
+
+            //激活纹理0来绑定y数据
+            GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
+            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[0])
+            GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, widthYUV, heightYUV, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, y)
+
+            //激活纹理1来绑定uv数据
+            GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
+            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[1])
+            GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE_ALPHA, widthYUV / 2, heightYUV / 2, 0, GLES20.GL_LUMINANCE_ALPHA, GLES20.GL_UNSIGNED_BYTE, uv)
+
+            //给fragment_shader里面yuv变量设置值   0 1 标识纹理x
+            GLES20.glUniform1i(samplerY, 0)
+            GLES20.glUniform1i(samplerUV, 1)
+
+            //绘制
+            GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
+            y?.clear()
+            uv?.clear()
+            y = null
+            uv = null
+            GLES20.glDisableVertexAttribArray(avPosition)
+            GLES20.glDisableVertexAttribArray(rgbPosition)
+            GLES20.glDisableVertexAttribArray(alphaPosition)
+        }
+    }
+}

+ 43 - 0
Android/PlayerProj/animplayer/src/main/java/com/tencent/qgame/animplayer/YUVShader.kt

@@ -0,0 +1,43 @@
+package com.tencent.qgame.animplayer
+
+object YUVShader {
+
+    const val VERTEX_SHADER = "attribute vec4 v_Position;\n" +
+            "attribute vec2 vTexCoordinateAlpha;\n" +
+            "attribute vec2 vTexCoordinateRgb;\n" +
+            "varying vec2 v_TexCoordinateAlpha;\n" +
+            "varying vec2 v_TexCoordinateRgb;\n" +
+            "\n" +
+            "void main() {\n" +
+            "    v_TexCoordinateAlpha = vTexCoordinateAlpha;\n" +
+            "    v_TexCoordinateRgb = vTexCoordinateRgb;\n" +
+            "    gl_Position = v_Position;\n" +
+            "}"
+
+    const val FRAGMENT_SHADER = "precision mediump float;\n" +
+            "uniform sampler2D sampler_y;\n" +
+            "uniform sampler2D sampler_uv;\n" +
+            "varying vec2 v_TexCoordinateAlpha;\n" +
+            "varying vec2 v_TexCoordinateRgb;\n" +
+            "\n" +
+            "void main() {\n" +
+            "   float y1,u1,v1;\n" +
+            "   float y2,u2,v2;\n" +
+            "   y2 = texture2D(sampler_y,v_TexCoordinateAlpha).r;\n" +
+            "   u2 = texture2D(sampler_uv,v_TexCoordinateAlpha).r- 0.5;\n" +
+            "   v2 = texture2D(sampler_uv,v_TexCoordinateAlpha).a - 0.5;\n" +
+            "   y1 = texture2D(sampler_y,v_TexCoordinateRgb).r;\n" +
+            "   u1 = texture2D(sampler_uv,v_TexCoordinateRgb).r- 0.5;\n" +
+            "   v1 = texture2D(sampler_uv,v_TexCoordinateRgb).a - 0.5;\n" +
+            "   vec3 rgb1;\n" +
+            "   vec3 rgb2;\n" +
+            "   rgb1.r = y1 + 1.403 * v1;\n" +
+            "   rgb1.g = y1 - 0.344 * u1 - 0.714 * v1;\n" +
+            "   rgb1.b = y1 + 1.770 * u1;\n" +
+            "   rgb2.r = y2 + 1.403 * v2;\n" +
+            "   rgb2.g = y2 - 0.344 * u2 - 0.714 * v2;\n" +
+            "   rgb2.b = y2 + 1.770 * u2;\n" +
+            "   gl_FragColor=vec4(rgb1, rgb2.r);\n" +
+            "}"
+    // RGB2*Alpha+RGB1*(1-Alpha)
+}

+ 1 - 1
Android/PlayerProj/app/src/main/AndroidManifest.xml

@@ -22,7 +22,7 @@
         <activity android:name=".player.AnimSimpleDemoActivity" android:screenOrientation="portrait"/>
         <activity android:name=".player.AnimSimpleDemoActivity" android:screenOrientation="portrait"/>
         <activity android:name=".player.AnimVapxDemoActivity" android:screenOrientation="portrait"/>
         <activity android:name=".player.AnimVapxDemoActivity" android:screenOrientation="portrait"/>
         <activity android:name=".player.AnimActiveDemoActivity" android:screenOrientation="portrait"/>
         <activity android:name=".player.AnimActiveDemoActivity" android:screenOrientation="portrait"/>
-
+        <activity android:name=".player.AnimSpecialSizeDemoActivity" android:screenOrientation="portrait"/>
 
 
     </application>
     </application>
 
 

BIN
Android/PlayerProj/app/src/main/assets/special_size_1500.mp4


+ 4 - 0
Android/PlayerProj/app/src/main/java/com/tencent/qgame/playerproj/MainActivity.kt

@@ -20,6 +20,7 @@ import android.content.Intent
 import android.os.Bundle
 import android.os.Bundle
 import com.tencent.qgame.playerproj.player.AnimActiveDemoActivity
 import com.tencent.qgame.playerproj.player.AnimActiveDemoActivity
 import com.tencent.qgame.playerproj.player.AnimSimpleDemoActivity
 import com.tencent.qgame.playerproj.player.AnimSimpleDemoActivity
+import com.tencent.qgame.playerproj.player.AnimSpecialSizeDemoActivity
 import com.tencent.qgame.playerproj.player.AnimVapxDemoActivity
 import com.tencent.qgame.playerproj.player.AnimVapxDemoActivity
 import kotlinx.android.synthetic.main.activity_main.*
 import kotlinx.android.synthetic.main.activity_main.*
 
 
@@ -38,6 +39,9 @@ class MainActivity : Activity(){
         btn3.setOnClickListener {
         btn3.setOnClickListener {
             startActivity(Intent(this, AnimActiveDemoActivity::class.java))
             startActivity(Intent(this, AnimActiveDemoActivity::class.java))
         }
         }
+        btn4.setOnClickListener {
+            startActivity(Intent(this, AnimSpecialSizeDemoActivity::class.java))
+        }
     }
     }
 
 
 
 

+ 228 - 0
Android/PlayerProj/app/src/main/java/com/tencent/qgame/playerproj/player/AnimSpecialSizeDemoActivity.kt

@@ -0,0 +1,228 @@
+/*
+ * Tencent is pleased to support the open source community by making vap available.
+ *
+ * Copyright (C) 2020 THL A29 Limited, a Tencent company.  All rights reserved.
+ *
+ * Licensed under the MIT License (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://opensource.org/licenses/MIT
+ *
+ * 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.tencent.qgame.playerproj.player
+
+import android.app.Activity
+import android.content.Context
+import android.os.Bundle
+import android.os.Environment
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import android.view.View
+import com.tencent.qgame.animplayer.AnimConfig
+import com.tencent.qgame.animplayer.AnimView
+import com.tencent.qgame.animplayer.Constant
+import com.tencent.qgame.animplayer.inter.IAnimListener
+import com.tencent.qgame.animplayer.util.ALog
+import com.tencent.qgame.animplayer.util.IALog
+import com.tencent.qgame.animplayer.util.ScaleType
+import com.tencent.qgame.playerproj.R
+import kotlinx.android.synthetic.main.activity_anim_simple_demo.*
+import java.io.File
+
+/**
+ * 播放宽高不是2的倍数的特殊尺寸的动画demo,这里以special_size_1500.mp4为例,size = 1500 x 1624
+ */
+class AnimSpecialSizeDemoActivity : Activity(), IAnimListener {
+
+    companion object {
+        private const val TAG = "AnimSpecialSizeActivity"
+    }
+
+    private val dir by lazy {
+        // 存放在sdcard应用缓存文件中
+        getExternalFilesDir(null)?.absolutePath ?: Environment.getExternalStorageDirectory().path
+    }
+
+    // 视频信息
+    data class VideoInfo(val fileName: String,val md5:String)
+
+    // ps:每次修改mp4文件,但文件名不变,记得先卸载app,因为assets同名文件不会进行替换
+    private val videoInfo = VideoInfo("special_size_1500.mp4", "b05acc8b8ede12495d170b74e0d82867")
+
+    // 动画View
+    private lateinit var animView: AnimView
+
+    private val uiHandler by lazy {
+        Handler(Looper.getMainLooper())
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_anim_simple_demo)
+        // 文件加载完成后会调用init方法
+        loadFile()
+    }
+
+    private fun init() {
+        // 初始化日志
+        initLog()
+        // 初始化调试开关
+        initTestView()
+        // 获取动画view
+        animView = playerView
+        // 视频左右对齐(rgb左\alpha右)
+        animView.setVideoMode(Constant.VIDEO_MODE_SPLIT_HORIZONTAL_REVERSE)
+        // 兼容老版本视频资源
+        animView.enableVersion1(true)
+        // 居中(根据父布局按比例居中并全部显示,默认fitXY)
+        animView.setScaleType(ScaleType.FIT_CENTER)
+        // 注册动画监听
+        animView.setAnimListener(this)
+        /**
+         * 开始播放主流程
+         * ps: 主要流程都是对AnimView的操作,其它比如队列,或改变窗口大小等操作都不是必须的
+         */
+        play(videoInfo)
+    }
+
+
+    private fun play(videoInfo: VideoInfo) {
+        // 播放前强烈建议检查文件的md5是否有改变
+        // 因为下载或文件存储过程中会出现文件损坏,导致无法播放
+        Thread {
+            val file = File(dir + "/" + videoInfo.fileName)
+            val md5 = FileUtil.getFileMD5(file)
+            if (videoInfo.md5 == md5) {
+                // 开始播放动画文件
+                animView.startPlay(file)
+            } else {
+                Log.e(TAG, "md5 is not match, error md5=$md5")
+            }
+        }.start()
+    }
+
+
+    /**
+     * 视频信息准备好后的回调,用于检查视频准备好后是否继续播放
+     * @return true 继续播放 false 停止播放
+     */
+    override fun onVideoConfigReady(config: AnimConfig): Boolean {
+
+        uiHandler.post {
+            val w = dp2px(this,400f).toInt()
+            val lp = animView.layoutParams
+            lp.width = w
+            lp.height = (w * config.height *1f / config.width).toInt()
+            animView.layoutParams = lp
+        }
+        return true
+    }
+
+    /**
+     * 视频开始回调
+     */
+    override fun onVideoStart() {
+        Log.i(TAG, "onVideoStart")
+    }
+
+    /**
+     * 视频渲染每一帧时的回调
+     * @param frameIndex 帧索引
+     */
+    override fun onVideoRender(frameIndex: Int, config: AnimConfig?) {
+    }
+
+    /**
+     * 视频播放结束(失败也会回调onComplete)
+     */
+    override fun onVideoComplete() {
+        Log.i(TAG, "onVideoComplete")
+    }
+
+    /**
+     * 播放器被销毁情况下会调用onVideoDestroy
+     */
+    override fun onVideoDestroy() {
+        Log.i(TAG, "onVideoDestroy")
+    }
+
+    /**
+     * 失败回调
+     * 一次播放时可能会调用多次,建议onFailed只做错误上报
+     * @param errorType 错误类型
+     * @param errorMsg 错误消息
+     */
+    override fun onFailed(errorType: Int, errorMsg: String?) {
+        Log.i(TAG, "onFailed errorType=$errorType errorMsg=$errorMsg")
+    }
+
+
+
+    override fun onPause() {
+        super.onPause()
+        // 页面切换是停止播放
+        animView.stopPlay()
+    }
+
+
+    private fun initLog() {
+        ALog.isDebug = false
+        ALog.log = object : IALog {
+            override fun i(tag: String, msg: String) {
+                Log.i(tag, msg)
+            }
+
+            override fun d(tag: String, msg: String) {
+                Log.d(tag, msg)
+            }
+
+            override fun e(tag: String, msg: String) {
+                Log.e(tag, msg)
+            }
+
+            override fun e(tag: String, msg: String, tr: Throwable) {
+                Log.e(tag, msg, tr)
+            }
+        }
+    }
+
+
+    private fun initTestView() {
+        btnLayout.visibility = View.VISIBLE
+        /**
+         * 开始播放按钮
+         */
+        btnPlay.setOnClickListener {
+            play(videoInfo)
+        }
+        /**
+         * 结束视频按钮
+         */
+        btnStop.setOnClickListener {
+            animView.stopPlay()
+        }
+    }
+
+    private fun loadFile() {
+        val files = Array(1) {
+            videoInfo.fileName
+        }
+        FileUtil.copyAssetsToStorage(this, dir, files) {
+            uiHandler.post {
+                init()
+            }
+        }
+    }
+
+
+    private fun dp2px(context: Context, dp: Float): Float {
+        val scale = context.resources.displayMetrics.density
+        return dp * scale + 0.5f
+    }
+}
+

+ 25 - 20
Android/PlayerProj/app/src/main/res/layout/activity_main.xml

@@ -10,30 +10,35 @@
     <LinearLayout
     <LinearLayout
         android:layout_width="wrap_content"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_height="wrap_content"
-        android:orientation="vertical"
         android:layout_gravity="center"
         android:layout_gravity="center"
-        >
+        android:orientation="vertical">
+
+        <Button
+            android:id="@+id/btn1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Simple Demo" />
+
+        <Button
+            android:id="@+id/btn2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="VAPX Demo(融合动画)" />
+
+        <Button
+            android:id="@+id/btn3"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Active Demo(可变动画)" />
+
+        <Button
+            android:id="@+id/btn4"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Special Size Demo" />
 
 
-    <Button
-        android:id="@+id/btn1"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Simple Demo" />
-
-    <Button
-        android:id="@+id/btn2"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="VAPX Demo(融合动画)" />
-
-    <Button
-        android:id="@+id/btn3"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Active Demo(可变动画)" />
     </LinearLayout>
     </LinearLayout>
 
 
-
 </FrameLayout>
 </FrameLayout>