在工作中需要将摄像头的摄像头的视频流数据转为YUV格式,供算法去处理图像,为了方便调试就将每帧数据又转成了Bitmap去显示,效果如下:

记录H264格式数据解码YUV数据转Bitmap后图像红蓝颠倒问题_android

起初,看到画面正常显示后,就没太在意图像的内容,直到看见蓝色瓶的可口可乐(实际是红色的),事情好像不太对,后续发现所有红色的东西在画面中都变成了蓝色,然后算法识别的结果也不太好,才发现了问题的严重性。

Bitmap 转换问题?好像不是,yuv数据有问题?是直接从摄像头视频流解码出来的啊!总不能是摄像头有问题吧!看了摄像头预览效果正常的。头大,直到在网上看到:

UV排列反了。

比如说,NV21和YUV420SP的Y排列相同,UV则相反。给你YUV420SP,你当作NV21保存JPG,就会 发生红蓝颠倒。

抱着试试的态度,那就把UV转换以下看看把,原神,启动!

记录H264格式数据解码YUV数据转Bitmap后图像红蓝颠倒问题_android_02


正常了,好神奇,哈哈哈。

就简单记下吧,部分代码如下,从解码到转bitmap。

fun decode(data: ByteArray, offset: Int, length: Int) {
        val inputBuffers = codec?.inputBuffers
        val outputBuffers = codec?.outputBuffers
        val inputBufferIndex = codec?.dequeueInputBuffer(10000) ?: -1

        if (inputBufferIndex >= 0) {
            val inputBuffer = inputBuffers?.get(inputBufferIndex)
            inputBuffer?.clear()
            inputBuffer?.put(data, offset, length)
            codec?.queueInputBuffer(inputBufferIndex, 0, length, 0, 0)
        }

        val bufferInfo = MediaCodec.BufferInfo()
        var outputBufferIndex = codec?.dequeueOutputBuffer(bufferInfo, 10000) ?: -1
        while (outputBufferIndex >= 0) {
            val outputBuffer = outputBuffers?.get(outputBufferIndex)
            // 处理解码后的YUV数据
            processYUVData(outputBuffer, bufferInfo)
            codec?.releaseOutputBuffer(outputBufferIndex, false)
            outputBufferIndex = codec?.dequeueOutputBuffer(bufferInfo, 0) ?: -1
        }
    }

    private fun processYUVData(outputBuffer: ByteBuffer?, bufferInfo: MediaCodec.BufferInfo) {
        val yuvData = ByteArray(bufferInfo.size)
        outputBuffer?.get(yuvData)
        bitmapCallback?.invoke(yuvToBitmap(yuvData, 640, 352))
    }

    private fun yuvToBitmap(yuvData: ByteArray, width: Int, height: Int): Bitmap? {
        nv21ToYuv420sp(width, height, yuvData)//解决
        val yuvImage = YuvImage(yuvData, ImageFormat.NV21, width, height, null)
        val out = ByteArrayOutputStream()
        yuvImage.compressToJpeg(android.graphics.Rect(0, 0, width, height), 100, out)
        val imageBytes = out.toByteArray()
        return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
    }
    
    /**
     * NV21转YUV420SP 解决红蓝色颠倒的问题
     */
    private fun nv21ToYuv420sp(width: Int, height: Int, inArray: ByteArray) {
        val pixels = width * height
        val count = pixels / 2
        for (i in 0 until count step 2) {
            val s = inArray[pixels + i]
            inArray[pixels + i] = inArray[pixels + i + 1]
            inArray[pixels + i + 1] = s
        }
    }

当然,如果你要使用正确的yuvData的话,就要在返回数据之前调用nv21ToYuv420sp 方法提前转化以下。