|
|
@@ -17,6 +17,7 @@ package com.tencent.qgame.animplayer
|
|
|
|
|
|
import android.graphics.SurfaceTexture
|
|
|
import android.media.MediaCodec
|
|
|
+import android.media.MediaCodecInfo
|
|
|
import android.media.MediaExtractor
|
|
|
import android.media.MediaFormat
|
|
|
import android.os.Build
|
|
|
@@ -36,6 +37,18 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
|
|
|
private val bufferInfo by lazy { MediaCodec.BufferInfo() }
|
|
|
private var needDestroy = false
|
|
|
|
|
|
+ // 动画的原始尺寸
|
|
|
+ private var videoWidth = 0
|
|
|
+ private var videoHeight = 0
|
|
|
+
|
|
|
+ // 动画对齐后的尺寸
|
|
|
+ private var alignWidth = 0
|
|
|
+ private var alignHeight = 0
|
|
|
+
|
|
|
+ // 动画是否需要走YUV渲染逻辑的标志位
|
|
|
+ private var needYUV = false
|
|
|
+ private var outputFormat: MediaFormat? = null
|
|
|
+
|
|
|
override fun start(fileContainer: IFileContainer) {
|
|
|
isStopReq = false
|
|
|
needDestroy = false
|
|
|
@@ -45,15 +58,18 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
override fun onFrameAvailable(surfaceTexture: SurfaceTexture?) {
|
|
|
if (isStopReq) return
|
|
|
ALog.d(TAG, "onFrameAvailable")
|
|
|
+ renderData()
|
|
|
+ }
|
|
|
+
|
|
|
+ fun renderData() {
|
|
|
renderThread.handler?.post {
|
|
|
try {
|
|
|
glTexture?.apply {
|
|
|
updateTexImage()
|
|
|
- render?.renderFrame(player.configManager.config)
|
|
|
+ render?.renderFrame()
|
|
|
player.pluginManager.onRendering()
|
|
|
render?.swapBuffers()
|
|
|
}
|
|
|
@@ -64,15 +80,6 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
|
|
|
}
|
|
|
|
|
|
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 decoder: MediaCodec? = null
|
|
|
@@ -103,9 +110,28 @@ 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)
|
|
|
+ // 防止没有INFO_OUTPUT_FORMAT_CHANGED时导致alignWidth和alignHeight不会被赋值一直是0
|
|
|
+ alignWidth = videoWidth
|
|
|
+ alignHeight = videoHeight
|
|
|
ALog.i(TAG, "Video size is $videoWidth x $videoHeight")
|
|
|
+
|
|
|
+ // 由于使用mediacodec解码老版本素材时对宽度1500尺寸的视频进行数据对齐,解码后的宽度变成1504,导致采样点出现偏差播放异常
|
|
|
+ // 所以当开启兼容老版本视频模式并且老版本视频的宽度不能被16整除时要走YUV渲染逻辑
|
|
|
+ // 但是这样直接判断有风险,后期想办法改
|
|
|
+ needYUV = !(videoWidth % 16 == 0) && player.enableVersion1
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (!prepareRender(needYUV)) {
|
|
|
+ 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)
|
|
|
|
|
|
render?.apply {
|
|
|
@@ -123,12 +149,20 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
|
|
|
return
|
|
|
}
|
|
|
|
|
|
-
|
|
|
try {
|
|
|
val mime = format.getString(MediaFormat.KEY_MIME) ?: ""
|
|
|
ALog.i(TAG, "Video MIME is $mime")
|
|
|
decoder = MediaCodec.createDecoderByType(mime).apply {
|
|
|
- configure(format, Surface(glTexture), null, 0)
|
|
|
+ if (needYUV) {
|
|
|
+ format.setInteger(
|
|
|
+ MediaFormat.KEY_COLOR_FORMAT,
|
|
|
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar
|
|
|
+ )
|
|
|
+ configure(format, null, null, 0)
|
|
|
+ } else {
|
|
|
+ configure(format, Surface(glTexture), null, 0)
|
|
|
+ }
|
|
|
+
|
|
|
start()
|
|
|
decodeThread.handler?.post {
|
|
|
try {
|
|
|
@@ -148,8 +182,6 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-
|
|
|
private fun startDecode(extractor: MediaExtractor ,decoder: MediaCodec) {
|
|
|
val TIMEOUT_USEC = 10000L
|
|
|
var inputChunk = 0
|
|
|
@@ -193,10 +225,14 @@ 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
|
|
|
- ALog.i(TAG, "decoder output format changed: $format")
|
|
|
- // val (w,h) = formatChange(format)
|
|
|
- // videoSizeChange(w, h)
|
|
|
+ outputFormat = decoder.outputFormat
|
|
|
+ outputFormat?.apply {
|
|
|
+ if (this.getInteger(MediaFormat.KEY_STRIDE) > 0 && this.getInteger(MediaFormat.KEY_SLICE_HEIGHT) > 0) {
|
|
|
+ alignWidth = this.getInteger(MediaFormat.KEY_STRIDE)
|
|
|
+ alignHeight = this.getInteger(MediaFormat.KEY_SLICE_HEIGHT)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ALog.i(TAG, "decoder output format changed: $outputFormat")
|
|
|
}
|
|
|
decoderStatus < 0 -> {
|
|
|
throw RuntimeException("unexpected result from decoder.dequeueOutputBuffer: $decoderStatus")
|
|
|
@@ -213,8 +249,12 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
|
|
|
speedControlUtil.preRender(bufferInfo.presentationTimeUs)
|
|
|
}
|
|
|
|
|
|
+ if (needYUV && doRender) {
|
|
|
+ yuvProcess(decoder, decoderStatus)
|
|
|
+ }
|
|
|
+
|
|
|
// release & render
|
|
|
- decoder.releaseOutputBuffer(decoderStatus, doRender)
|
|
|
+ decoder.releaseOutputBuffer(decoderStatus, doRender && !needYUV)
|
|
|
|
|
|
if (frameIndex == 0) {
|
|
|
onVideoStart()
|
|
|
@@ -242,16 +282,57 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
|
|
|
|
|
|
}
|
|
|
|
|
|
- private fun formatChange(format: MediaFormat): Pair<Int, Int> {
|
|
|
- try {
|
|
|
- // 实际视频的纹理大小
|
|
|
- val width = format.getInteger(MediaFormat.KEY_WIDTH)
|
|
|
- val height = format.getInteger(MediaFormat.KEY_HEIGHT)
|
|
|
- return Pair(width, height)
|
|
|
- } catch (t: Throwable) {
|
|
|
- ALog.e(TAG, "formatChange $t", t)
|
|
|
+ /**
|
|
|
+ * 获取到解码后每一帧的YUV数据,裁剪出正确的尺寸
|
|
|
+ */
|
|
|
+ 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 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 / 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) {
|
|
|
+ System.arraycopy(src, srcOffset + h * inWidth, dest, h * outWidth, outWidth)
|
|
|
+ }
|
|
|
}
|
|
|
- return Pair(0,0)
|
|
|
}
|
|
|
|
|
|
private fun release(decoder: MediaCodec?, extractor: MediaExtractor?) {
|
|
|
@@ -280,7 +361,6 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
override fun destroy() {
|
|
|
needDestroy = true
|
|
|
if (isRunning) {
|
|
|
@@ -293,7 +373,7 @@ class HardDecoder(player: AnimPlayer) : Decoder(player), SurfaceTexture.OnFrameA
|
|
|
private fun destroyInner() {
|
|
|
renderThread.handler?.post {
|
|
|
player.pluginManager.onDestroy()
|
|
|
- render?.destroy()
|
|
|
+ render?.destroyRender()
|
|
|
render = null
|
|
|
onVideoDestroy()
|
|
|
destroyThread()
|