在android相机2中将yuv420转换为jpeg

问题描述 投票:0回答:2

我正在尝试将 androidcamera2 中获得的 yuv 420 预览帧保存为 jpeg。我发现这样做的唯一方法是将 yuv420 转换为 nv21,构建 yuvimage,然后使用 compresstojpeg 方法获取 jpeg。为了从 yuv420 转换为 jpeg,我使用下面的逻辑

            Image.Plane Y = img.getPlanes()[0];
            Image.Plane U = img.getPlanes()[2];
            Image.Plane V = img.getPlanes()[1];

            int Yb = Y.getBuffer().remaining();
            int Ub = U.getBuffer().remaining();
            int Vb = V.getBuffer().remaining();

            byte[] data = new byte[Yb + Ub + Vb];

            Y.getBuffer().get(data, 0, Yb);
            U.getBuffer().get(data, Yb, Ub);
            V.getBuffer().get(data, Yb + Ub, Vb);

            YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21,
                mPreviewSize.getWidth(), mPreviewSize.getHeight(), null);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            yuvImage.compressToJpeg(new Rect(0, 0,
                    mPreviewSize.getWidth(), mPreviewSize.getHeight()),
                100, out);

然而,这会导致某些分辨率 144 x 176、176x144、352x288、480x360、1280x960 获得绿色图像。转换为nv21的逻辑是否正确?我还可以使用什么其他方法将 yuv420 转换为 jpeg。 是否有任何 Java/Android api 可以实现这一点?

java android image-processing jpeg yuv
2个回答
1
投票

不,这是不正确的 - 您没有注意平面的行跨距或像素跨距。

您必须解析这些内容,并确保您的输出缓冲区实际上与 YuvImage 的 NV21 输入的输入期望相匹配,该输入假设行步长 = 宽度和交错的 V/U 平面。

您拥有的代码仅在输入图像 U/V 平面实际上交错时才有效(在这种情况下,您添加所需 UV 数据的两倍,但第一个副本恰好是正确的布局...),并且如果宽度==行步幅。 width==row stride 是否取决于分辨率;由于硬件限制,通常步幅必须是 16 像素的倍数或类似的值。因此,例如,对于不是 16 的倍数的分辨率,您的代码将无法工作。

请解决这两个问题 - 注意行和像素步幅;否则,您可能会意外地使其在您的设备上正常工作,但在具有不同步幅参数的设备上仍然会出现故障。

编辑:

可以在 Android AOSP 相机服务代码中找到一些执行此类转换的示例 C++ 代码:CallbackProcessor::convertFromFlexibleYuv

供参考的映射:

  • HAL_PIXEL_FORMAT_YCrCb_420_SP的预览格式是NV21。
  • src.data 是平面 0
  • src.dataCb 是平面 1
  • src.dataCr 是平面 2
  • src.stride 是平面 0 rowStride
  • src.chromaStride 是平面 1 和 2 rowStride
  • src.chromaStep 是平面 1 和 2 PixelStride
  • 平面 0 像素步长为 1

0
投票
JFYR,这是 Android

CameraX 项目 的 Java 实现

/** {@link android.media.Image} to NV21 byte array. */ @NonNull public static byte[] yuv_420_888toNv21(@NonNull Image image) { Image.Plane yPlane = image.getPlanes()[0]; Image.Plane uPlane = image.getPlanes()[1]; Image.Plane vPlane = image.getPlanes()[2]; ByteBuffer yBuffer = yPlane.getBuffer(); ByteBuffer uBuffer = uPlane.getBuffer(); ByteBuffer vBuffer = vPlane.getBuffer(); yBuffer.rewind(); uBuffer.rewind(); vBuffer.rewind(); int ySize = yBuffer.remaining(); int position = 0; // TODO(b/115743986): Pull these bytes from a pool instead of allocating for every image. byte[] nv21 = new byte[ySize + (image.getWidth() * image.getHeight() / 2)]; // Add the full y buffer to the array. If rowStride > 1, some padding may be skipped. for (int row = 0; row < image.getHeight(); row++) { yBuffer.get(nv21, position, image.getWidth()); position += image.getWidth(); yBuffer.position( Math.min(ySize, yBuffer.position() - image.getWidth() + yPlane.getRowStride())); } int chromaHeight = image.getHeight() / 2; int chromaWidth = image.getWidth() / 2; int vRowStride = vPlane.getRowStride(); int uRowStride = uPlane.getRowStride(); int vPixelStride = vPlane.getPixelStride(); int uPixelStride = uPlane.getPixelStride(); // Interleave the u and v frames, filling up the rest of the buffer. Use two line buffers to // perform faster bulk gets from the byte buffers. byte[] vLineBuffer = new byte[vRowStride]; byte[] uLineBuffer = new byte[uRowStride]; for (int row = 0; row < chromaHeight; row++) { vBuffer.get(vLineBuffer, 0, Math.min(vRowStride, vBuffer.remaining())); uBuffer.get(uLineBuffer, 0, Math.min(uRowStride, uBuffer.remaining())); int vLineBufferPosition = 0; int uLineBufferPosition = 0; for (int col = 0; col < chromaWidth; col++) { nv21[position++] = vLineBuffer[vLineBufferPosition]; nv21[position++] = uLineBuffer[uLineBufferPosition]; vLineBufferPosition += vPixelStride; uLineBufferPosition += uPixelStride; } } return nv21; }
一旦从

NV21

图像中获得
YUV420_888
,您可以使用
YuvImage
将其压缩为
JPEG
(您也可以在下面提到的同一文件中找到它)

© www.soinside.com 2019 - 2024. All rights reserved.