如何区分 imageReader 相机 API 2 中的 NV21 和 YV12 编码?

Posted

技术标签:

【中文标题】如何区分 imageReader 相机 API 2 中的 NV21 和 YV12 编码?【英文标题】:How could I distinguish between NV21 and YV12 codification in imageReader camera API 2? 【发布时间】:2017-04-02 06:46:38 【问题描述】:

我正在开发自定义相机 API 2 应用程序,我注意到当我使用 ImageReader 回调时,某些设备上的捕获格式转换是不同的。

例如,在 Nexus 4 中无法正常工作,而在 Nexus5X 中看起来还不错,这是输出。

我以这种形式初始化 ImageReader:

mImageReader = ImageReader.newInstance(320, 240, ImageFormat.YUV_420_888,2); 

而我的回调是简单的 ImageReader 回调。

 mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() 

    @Override
    public void onImageAvailable( ImageReader reader) 

       try 
             mBackgroundHandler.post(
                 new ImageController(reader.acquireNextImage())
             );
        
        catch(Exception e)
        
          //exception
        
        

;

对于 Nexus 4:我遇到了这个错误。

D/qdgralloc: gralloc_lock_ycbcr: Invalid format passed: 0x32315659

当我尝试在两个设备中写入原始文件时,我有这些不同的图像。所以我知道 Nexus 5X 图像具有 NV21 编码,而 Nexus 4 具有 YV12 编码。

我找到了image format 的规范,并尝试在 ImageReader 中获取格式。 有 YV12 和 NV21 两种选择,但是很明显,当我尝试获取格式时,我得到的是 YUV_420_888 格式。

 int test=mImageReader.getImageFormat();

那么有没有办法让相机输入格式(NV21 或 YV12)在相机类中区分这种编码类型?可能是相机特性?

提前致谢。

乌奈。 PD:我使用 OpenGL 来显示 RGB 图像,我使用 Opencv 来进行 YUV_420_888 的转换。

【问题讨论】:

Image.getFormat() 是否返回与ImageReader 相同的格式?看来你并不孤单:***.com/questions/34717969/… @nandsito 感谢您的建议,但不幸的是,它们在两种情况下都返回相同的值 35,这正是 YUV_420_888。 你查过Camera.Parameters.getSupportedPictureFormats()支持哪些格式吗? 感谢您的评论,但是,我认为 Camera.Parameters 类仅适用于相机 API 1。developer.android.com/reference/android/hardware/…。它适用于相机 API2? 您找到解决方案了吗? 【参考方案1】:

YUV_420_888 是一个包装器,可以托管(以及其他)NV21 和 YV12 图像。您必须使用平面和步幅来访问各个颜色:

ByteBuffer Y = image.getPlanes()[0];
ByteBuffer U = image.getPlanes()[1];
ByteBuffer V = image.getPlanes()[2];

如果底层像素是 NV21 格式(如在 Nexus 4 上),pixelStride 将为 2,并且

int getU(image, col, row) 
    return getPixel(image.getPlanes()[1], col/2, row/2);


int getPixel(plane, col, row) 
    return plane.getBuffer().get(col*plane.getPixelStride() + row*plane.getRowStride());

我们采用半列半行,因为这是 U 和 V(色度)平面在 420 图像中的存储方式。

此代码用于说明,它非常低效,您可能希望使用get(byte[], int, int),或通过片段着色器,或通过本机代码中的JNI 函数GetDirectBufferAddress 批量访问像素。您不能使用方法 plane.array(),因为这些平面保证是直接字节缓冲区。

【讨论】:

【参考方案2】:

这里有用的方法,从 YV12 转换为 NV21。

public static byte[] fromYV12toNV21(@NonNull final byte[] yv12,
                                    final int width,
                                    final int height) 
    byte[] nv21 = new byte[yv12.length];
    final int size = width * height;
    final int quarter = size / 4;
    final int vPosition = size; // This is where V starts
    final int uPosition = size + quarter; // This is where U starts

    System.arraycopy(yv12, 0, nv21, 0, size); // Y is same

    for (int i = 0; i < quarter; i++) 
        nv21[size + i * 2] = yv12[vPosition + i]; // For NV21, V first
        nv21[size + i * 2 + 1] = yv12[uPosition + i]; // For Nv21, U second
    
    return nv21;

【讨论】:

以上是关于如何区分 imageReader 相机 API 2 中的 NV21 和 YV12 编码?的主要内容,如果未能解决你的问题,请参考以下文章

通过 Camera API 2 捕获图片并将其保存为位图

Android Camera2 ImageReader 图像格式 YUV

Android camera2 曝光问题。在使用 YUV ImageReader 的情况下预览过度曝光

相机光圈是如何区分级数的?

安卓Camera2用ImageReader获取NV21源码分析

Android ImageReader YUV 420 888 重复数据