Browse Source

1. 修复部分手机不支持yuv420sp的兼容性问题
2. 修复播放宽度不能被4整除的视频时crash的问题
3. 更换demo测试视频

irisfdcui 4 years ago
parent
commit
79e368800f

+ 40 - 9
Android/PlayerProj/animplayer/src/main/java/com/tencent/qgame/animplayer/HardDecoder.kt

@@ -47,6 +47,7 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
 
     // 动画是否需要走YUV渲染逻辑的标志位
     private var needYUV = false
+    private var outputFormat: MediaFormat? = null
 
     override fun start(fileContainer: IFileContainer) {
         isStopReq = false
@@ -112,6 +113,9 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
 
             videoWidth = format.getInteger(MediaFormat.KEY_WIDTH)
             videoHeight = format.getInteger(MediaFormat.KEY_HEIGHT)
+            // 防止没有INFO_OUTPUT_FORMAT_CHANGED时导致alignWidth和alignHeight不会被赋值一直是0
+            alignWidth = videoWidth
+            alignHeight = videoHeight
             ALog.i(TAG, "Video size is $videoWidth x $videoHeight")
 
             // 由于使用mediacodec解码老版本素材时对宽度1500尺寸的视频进行数据对齐,解码后的宽度变成1504,导致采样点出现偏差播放异常
@@ -154,7 +158,7 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
                 if (needYUV) {
                     format.setInteger(
                             MediaFormat.KEY_COLOR_FORMAT,
-                            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar
+                            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar
                     )
                     configure(format, null, null, 0)
                 } else {
@@ -225,14 +229,16 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
                     decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER -> ALog.d(TAG, "no output from decoder available")
                     decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED -> ALog.d(TAG, "decoder output buffers changed")
                     decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
-                        val format = decoder.outputFormat
+                        outputFormat = decoder.outputFormat
                         try {
-                            alignWidth = format.getInteger(MediaFormat.KEY_STRIDE)
-                            alignHeight = format.getInteger(MediaFormat.KEY_SLICE_HEIGHT)
+                            if (outputFormat!!.getInteger(MediaFormat.KEY_STRIDE) > 0 && outputFormat!!.getInteger(MediaFormat.KEY_SLICE_HEIGHT) > 0) {
+                                alignWidth = outputFormat!!.getInteger(MediaFormat.KEY_STRIDE)
+                                alignHeight = outputFormat!!.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: $outputFormat")
                         // val (w,h) = formatChange(format)
                         // videoSizeChange(w, h)
                     }
@@ -251,7 +257,7 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
                             speedControlUtil.preRender(bufferInfo.presentationTimeUs)
                         }
 
-                        if (needYUV) {
+                        if (needYUV && doRender) {
                             yuvProcess(decoder, decoderStatus)
                         }
 
@@ -294,17 +300,42 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
             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)
+                var uData = ByteArray(videoWidth * videoHeight / 4)
+                var vData = ByteArray(videoWidth * videoHeight / 4)
+
+                if (outputFormat?.getInteger(MediaFormat.KEY_COLOR_FORMAT) == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar) {
+                    yuvData = yuv420spTop(yuvData)
+                }
+
                 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)
+                yuvCopy(yuvData, alignWidth * alignHeight, alignWidth / 2, alignHeight / 2, uData, videoWidth / 2, videoHeight / 2)
+                yuvCopy(yuvData, alignWidth * alignHeight * 5 / 4, alignWidth / 2, alignHeight / 2, vData, videoWidth / 2, videoHeight / 2)
+
+                render?.setYUVData(videoWidth, videoHeight, yData, uData, vData)
                 renderData()
             }
         }
     }
 
+    private fun yuv420spTop(yuv420sp: ByteArray): ByteArray {
+        val yuv420p = ByteArray(yuv420sp.size)
+        val ySize = alignWidth * alignHeight
+        System.arraycopy(yuv420sp, 0, yuv420p, 0, alignWidth * alignHeight)
+        var i = ySize
+        var j = ySize
+        while (i < ySize * 3 / 2) {
+            yuv420p[j] = yuv420sp[i]
+            yuv420p[j + ySize / 4] = yuv420sp[i + 1]
+            i += 2
+            j++
+        }
+        return yuv420p
+    }
+
+
     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) {

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

@@ -50,5 +50,5 @@ interface IRenderListener {
 
     fun swapBuffers()
 
-    fun setYUVData(width: Int, height: Int, y: ByteArray?, uv: ByteArray?) {}
+    fun setYUVData(width: Int, height: Int, y: ByteArray?, u: ByteArray?, v: ByteArray?) {}
 }

+ 59 - 13
Android/PlayerProj/animplayer/src/main/java/com/tencent/qgame/animplayer/YUVRender.kt

@@ -22,9 +22,14 @@ 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
+import java.nio.FloatBuffer
 
 class YUVRender (surfaceTexture: SurfaceTexture): IRenderListener {
 
+    companion object {
+        private const val TAG = "${Constant.TAG}.YUVRender"
+    }
+
     private val vertexArray = GlFloatArray()
     private val alphaArray = GlFloatArray()
     private val rgbArray = GlFloatArray()
@@ -42,17 +47,36 @@ class YUVRender (surfaceTexture: SurfaceTexture): IRenderListener {
 
     //shader  yuv变量
     private var samplerY = 0
-    private var samplerUV = 0
-    private var textureId = IntArray(2)
+    private var samplerU = 0
+    private var samplerV = 0
+    private var textureId = IntArray(3)
+    private var convertMatrixUniform = 0
+    private var convertOffsetUniform = 0
 
     //YUV数据
     private var widthYUV = 0
     private var heightYUV = 0
     private var y: ByteBuffer? = null
-    private var uv: ByteBuffer? = null
+    private var u: ByteBuffer? = null
+    private var v: ByteBuffer? = null
 
     private val eglUtil: EGLUtil = EGLUtil()
 
+    // 像素数据向GPU传输时默认以4字节对齐
+    private var unpackAlign = 4
+
+    // YUV offset
+    private val YUV_OFFSET = floatArrayOf(
+            0f, -0.501960814f, -0.501960814f
+    )
+
+    // RGB coefficients
+    private val YUV_MATRIX = floatArrayOf(
+            1f, 1f, 1f,
+            0f, -0.3441f, 1.772f,
+            1.402f, -0.7141f, 0f
+    )
+
     init {
         eglUtil.start(surfaceTexture)
         initRender()
@@ -68,8 +92,11 @@ class YUVRender (surfaceTexture: SurfaceTexture): IRenderListener {
 
         //获取yuv字段
         samplerY = GLES20.glGetUniformLocation(shaderProgram, "sampler_y")
-        samplerUV = GLES20.glGetUniformLocation(shaderProgram, "sampler_uv")
-        //创建2个纹理
+        samplerU = GLES20.glGetUniformLocation(shaderProgram, "sampler_u")
+        samplerV = GLES20.glGetUniformLocation(shaderProgram, "sampler_v")
+        convertMatrixUniform = GLES20.glGetUniformLocation(shaderProgram, "convertMatrix")
+        convertOffsetUniform = GLES20.glGetUniformLocation(shaderProgram, "offset")
+        //创建3个纹理
         GLES20.glGenTextures(textureId.size, textureId, 0)
 
         //绑定纹理
@@ -119,40 +146,59 @@ class YUVRender (surfaceTexture: SurfaceTexture): IRenderListener {
         eglUtil.swapBuffers()
     }
 
-    override fun setYUVData(width: Int, height: Int, y: ByteArray?, uv: ByteArray?) {
+    override fun setYUVData(width: Int, height: Int, y: ByteArray?, u: ByteArray?, v: ByteArray?) {
         widthYUV = width
         heightYUV = height
         this.y = ByteBuffer.wrap(y)
-        this.uv = ByteBuffer.wrap(uv)
+        this.u = ByteBuffer.wrap(u)
+        this.v = ByteBuffer.wrap(v)
+
+        // 当视频帧的u或者v分量的宽度不能被4整除时,用默认的4字节对齐会导致存取最后一行时越界,所以在向GPU传输数据前指定对齐方式
+        if ((widthYUV / 2) % 4 != 0) {
+            this.unpackAlign = if ((widthYUV / 2) % 2 == 0) 2 else 1
+        }
     }
 
     private fun draw() {
-        if (widthYUV > 0 && heightYUV > 0 && y != null && uv != null) {
+        if (widthYUV > 0 && heightYUV > 0 && y != null && u != null && v != null) {
             GLES20.glUseProgram(shaderProgram)
             vertexArray.setVertexAttribPointer(avPosition)
             alphaArray.setVertexAttribPointer(alphaPosition)
             rgbArray.setVertexAttribPointer(rgbPosition)
 
+            GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, unpackAlign)
+
             //激活纹理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数据
+            //激活纹理1来绑定u数据
             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)
+            GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, widthYUV / 2, heightYUV / 2, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, u)
+
+            //激活纹理2来绑定v数据
+            GLES20.glActiveTexture(GLES20.GL_TEXTURE2)
+            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[2])
+            GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, widthYUV / 2, heightYUV / 2, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, v)
 
             //给fragment_shader里面yuv变量设置值   0 1 标识纹理x
             GLES20.glUniform1i(samplerY, 0)
-            GLES20.glUniform1i(samplerUV, 1)
+            GLES20.glUniform1i(samplerU, 1)
+            GLES20.glUniform1i(samplerV, 2)
+
+            GLES20.glUniform3fv(convertOffsetUniform, 1, FloatBuffer.wrap(YUV_OFFSET))
+            GLES20.glUniformMatrix3fv(convertMatrixUniform, 1, false, YUV_MATRIX, 0)
 
             //绘制
             GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
             y?.clear()
-            uv?.clear()
+            u?.clear()
+            v?.clear()
             y = null
-            uv = null
+            u = null
+            v = null
             GLES20.glDisableVertexAttribArray(avPosition)
             GLES20.glDisableVertexAttribArray(rgbPosition)
             GLES20.glDisableVertexAttribArray(alphaPosition)

+ 20 - 18
Android/PlayerProj/animplayer/src/main/java/com/tencent/qgame/animplayer/YUVShader.kt

@@ -31,28 +31,30 @@ object YUVShader {
 
     const val FRAGMENT_SHADER = "precision mediump float;\n" +
             "uniform sampler2D sampler_y;\n" +
-            "uniform sampler2D sampler_uv;\n" +
+            "uniform sampler2D sampler_u;\n" +
+            "uniform sampler2D sampler_v;\n" +
             "varying vec2 v_TexCoordinateAlpha;\n" +
             "varying vec2 v_TexCoordinateRgb;\n" +
+            "uniform mat3 convertMatrix;\n" +
+            "uniform vec3 offset;\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" +
+            "   highp vec3 yuvColorAlpha;\n" +
+            "   highp vec3 yuvColorRGB;\n" +
+            "   highp vec3 rgbColorAlpha;\n" +
+            "   highp vec3 rgbColorRGB;\n" +
+            "   yuvColorAlpha.x = texture2D(sampler_y,v_TexCoordinateAlpha).r;\n" +
+            "   yuvColorRGB.x = texture2D(sampler_y,v_TexCoordinateRgb).r;\n" +
+            "   yuvColorAlpha.y = texture2D(sampler_u,v_TexCoordinateAlpha).r;\n" +
+            "   yuvColorAlpha.z = texture2D(sampler_v,v_TexCoordinateAlpha).r;\n" +
+            "   yuvColorRGB.y = texture2D(sampler_u,v_TexCoordinateRgb).r;\n" +
+            "   yuvColorRGB.z = texture2D(sampler_v,v_TexCoordinateRgb).r;\n" +
+            "   yuvColorAlpha += offset;\n" +
+            "   yuvColorRGB += offset;\n" +
+            "   rgbColorAlpha = convertMatrix * yuvColorAlpha; \n" +
+            "   rgbColorRGB = convertMatrix * yuvColorRGB; \n" +
+            "   gl_FragColor=vec4(rgbColorRGB, rgbColorAlpha.r);\n" +
             "}"
+
     // RGB2*Alpha+RGB1*(1-Alpha)
 }

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


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


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

@@ -35,7 +35,7 @@ import kotlinx.android.synthetic.main.activity_anim_simple_demo.*
 import java.io.File
 
 /**
- * 播放宽高不是2的倍数的特殊尺寸的动画demo,这里以special_size_1500.mp4为例,size = 1500 x 1624
+ * 播放宽高不是16的倍数的特殊尺寸的动画demo,这里以special_size_750.mp4为例,size = 750 x 814
  */
 class AnimSpecialSizeDemoActivity : Activity(), IAnimListener {
 
@@ -52,7 +52,7 @@ class AnimSpecialSizeDemoActivity : Activity(), IAnimListener {
     data class VideoInfo(val fileName: String,val md5:String)
 
     // ps:每次修改mp4文件,但文件名不变,记得先卸载app,因为assets同名文件不会进行替换
-    private val videoInfo = VideoInfo("special_size_1500.mp4", "b05acc8b8ede12495d170b74e0d82867")
+    private val videoInfo = VideoInfo("special_size_750.mp4", "2acde1639ad74b8bd843083246902e23")
 
     // 动画View
     private lateinit var animView: AnimView