从org.bytedeco.javacv.Frame获取MediaCodec可用原始数据异常求助
我之前也踩过这个绿屏的坑,大概率是像素格式不匹配或者数据读取方式错误导致的——毕竟JavaCV的Frame和MediaCodec对YUV格式的处理逻辑差异不小。你用Frame.Image[0].array()的方式只适用于HeapByteBuffer类型的Frame数据,如果Frame用的是DirectBuffer,或者是多平面的YUV格式,直接取第一个数组就会出问题。下面给你几个靠谱的替代获取方式:
1. 正确读取ByteBuffer中的数据(兼容Heap/DirectBuffer)
很多时候FFmpegFrameFilter输出的Frame会用DirectBuffer,这时候调用array()会抛出UnsupportedOperationException,就算没抛,拿到的数据也可能是错误的。正确的做法是直接从ByteBuffer读取:
Frame filteredFrame = filter.pull(); if (filteredFrame != null && filteredFrame.image != null) { ByteBuffer buffer = filteredFrame.image[0]; // 先复制一份buffer避免修改原数据的position ByteBuffer copyBuffer = buffer.duplicate(); byte[] rawData = new byte[copyBuffer.remaining()]; copyBuffer.get(rawData); // 现在rawData就是正确的原始数据了 }
2. 处理多平面YUV格式(解决绿屏核心)
如果你的Frame是YUV420P格式(JavaCV常用输出格式),而MediaCodec通常期望的是Android的NV21或YV12格式,直接传入单平面数据就会导致颜色偏移(绿屏)。这时候需要把三个平面的Y、U、V数据转换成MediaCodec支持的交错格式:
Frame filteredFrame = filter.pull(); if (filteredFrame != null && filteredFrame.image != null && filteredFrame.image.length == 3) { // YUV420P的三个平面:Y、U、V ByteBuffer yBuf = filteredFrame.image[0].duplicate(); ByteBuffer uBuf = filteredFrame.image[1].duplicate(); ByteBuffer vBuf = filteredFrame.image[2].duplicate(); int ySize = yBuf.remaining(); int uvSize = uBuf.remaining(); // NV21格式是Y平面 + VU交错平面 byte[] nv21Data = new byte[ySize + uvSize * 2]; // 复制Y分量 yBuf.get(nv21Data, 0, ySize); // 交错V和U分量(NV21是VU顺序,YUV420P是U、V分开) int uvIndex = ySize; while (vBuf.hasRemaining() && uBuf.hasRemaining()) { nv21Data[uvIndex++] = vBuf.get(); nv21Data[uvIndex++] = uBuf.get(); } // 把nv21Data传入MediaCodec即可 }
3. 利用AndroidFrameConverter直接转换为MediaCodec兼容格式
如果你不想手动处理格式转换,可以用JavaCV的AndroidFrameConverter把Frame转成Android原生的Image对象,再从Image中获取符合要求的ByteBuffer:
AndroidFrameConverter converter = new AndroidFrameConverter(); Image androidImage = converter.convert(filteredFrame); if (androidImage != null) { // 获取Image的ByteBuffer(注意对应MediaCodec的格式) ByteBuffer buffer = androidImage.getPlanes()[0].getBuffer(); byte[] rawData = new byte[buffer.remaining()]; buffer.get(rawData); androidImage.close(); }
最后提醒一下:一定要先确认FFmpegFrameFilter的输出格式和MediaCodec配置的格式一致。比如你可以在Filter初始化时指定输出格式为NV21:
FFmpegFrameFilter filter = new FFmpegFrameFilter("transpose=1,pix_fmt=nv21", width, height);
这样输出的Frame就是MediaCodec直接能用的格式,省去转换步骤。
内容的提问来源于stack exchange,提问作者Parth

