android音视频开发

YUV基础知识

YUV 格式分为两种类型:

  1. Packed 格式 :类似 RGB 的存放方式。每个像素点的 Y、U、V 是连续交错存储的
  2. Planar 格式:Y、U、V分别放在三个数组中
    1. YUV444: 每一个Y对应一组U/V分量。一组YUV占3个字节(8+8+8=24bits)
    2. YUV422: 2:1 水平取样,垂直完全采样,一组YUV占2个字节(8+4+4=16bit)
    3. YUV420: 2:1 水平取样,2:1 垂直取样,一组YUV占1.5个字节(8+2+2=12bits)或2个字节(8+4+4=16bits)其中YUV420细分为YUV420Planar 包含I420,YV12;YUV420SemiPlanar包含NV12,NV21

不同格式的yuv存储顺序:

I420:YYYYYYYY UUVV

YV12:YYYYYYYY VVUU

NV12:YYYYYYYY UVUV

NV21: YYYYYYYY VUVU

android Camera

    Camera2 输出的帧信息采用的是 Image,支持全新的 YUV420Flexible 格式,配套格式为 YUV_420_888,888 表示 Y、U、V 分量中每个颜色占8 bit。YUV420Flexible 并不是一种具体的格式,而是一类 YUV 格式,包括 I420 还有旧版 Camera 支持的 NV21 和 YV12。在 COLOR_FormatYUV420Flexible 描述中,在该颜色格式下,图像中的每个像素占用 12 bit(即 1.5 个字节),该颜色格式与 YUV_420_888 配套使用。

Camera1官方建议使用 NV21 或 YV12,编码用到的颜色格式一般也用的是 COLOR_FormatYUV420SemiPlanar 或 COLOR_FormatYUV420Planar。

获取YUV数据

      通过 Image#getPlanes() 可以得到Y、U 和 V 三个分量 的数据,Y分量是连续存储的 pixelStride 一定等于1,对于U/V plane来说,u分量的RowStride等于v分量的RowStrideu分量的PixelStride等于v分量的PixelStride。pixelStride 代表相邻像素样本之间的距离,单位是字节,1表示是连续存储,2表示间隔存储。

当 Image 中的 U/V 分量的 pixelStride 均为 2 的话,实际上三个 Plane 的数据如下:

U/V 分量仅有偶数索引值才是真实有效值,而奇数索引值是无效值。虽然奇数索引值是无效的,但是依然占用了存储空间,因此 U 和 V 分量长度是 Y 分量长度的 1/2,而不是 1/4。

从 Image 中获取图像数据

/**
* colorFormat 0为`I420(YU12)` format `YYYYYYYY UUVV` or
*             1为`NV21` format `YYYYYYYY VUVU`
**/ 
public byte[] getYuvDataFromImage(Image image, int colorFormat) {
        Rect crop = image.getCropRect();
        int format = image.getFormat();
        int width = crop.width();
        int height = crop.height();
        Image.Plane[] planes = image.getPlanes();
        byte[] data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
        byte[] rowData = new byte[planes[0].getRowStride()];
        int channelOffset = 0;
        int outputStride = 1;
        for (int i = 0; i < planes.length; i++) {
            switch (i) {
                case 0:
                    channelOffset = 0;
                    outputStride = 1;
                    break;
                case 1:
                    if (colorFormat == 0) {
                        channelOffset = width * height;
                        outputStride = 1; 
                    }
                    else if (colorFormat == 1) {
                        channelOffset = width * height + 1;
                        outputStride = 2; 
                    }
                    break;
                case 2: 
                    if (colorFormat == 0) {
                    channelOffset = (int) (width * height * 1.25);
                    outputStride = 1; 
                    }
                    else if (colorFormat == 1) {
                        channelOffset = width * height;
                        outputStride = 2; 
                    }
                    break;
            }
            ByteBuffer buffer = planes[i].getBuffer();
            int rowStride = planes[i].getRowStride();
            int pixelStride = planes[i].getPixelStride();
            int shift = (i == 0) ? 0 : 1;
            int w = width >> shift;
            int h = height >> shift;
            buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));
            for (int row = 0; row < h; row++) {
                int length;
                if (pixelStride == 1 && outputStride == 1) {

                    length = w;
                    buffer.get(data, channelOffset, length);
                    channelOffset += length;
                } else {
                    length = (w - 1) * pixelStride + 1;
                    buffer.get(rowData, 0, length);
                    for (int col = 0; col < w; col++) {
                        data[channelOffset] = rowData[col * pixelStride];
                        channelOffset += outputStride;
                    }
                }
                if (row < h - 1) {
                    buffer.position(buffer.position() + rowStride - length);
                }
            }
        }
        return data;
    }

直接获取NV21数据

    public byte[] getBytesFromImage(Image image) {
        Image.Plane[] planes = image.getPlanes();
        int imageBytesLength = 0;
        for (Image.Plane plane : planes) {
            imageBytesLength += plane.getBuffer().remaining();
        }
        byte[] imageBytes = new byte[imageBytesLength];
        int offset = 0;
        for (Image.Plane plane : planes) {
            ByteBuffer buffer = plane.getBuffer();
            int remain = buffer.remaining();
            buffer.get(imageBytes, offset, remain);
            offset += remain;
        }
        return imageBytes;
    }

关于旋转

  // The input imageBytes is in YYYYYYYYUVUV(NV12)
    // Return data in YYYYYYYYUVUV(NV12)
public byte[] rotateYUV420Degree90(byte[] imageBytes, int imageWidth, int imageHeight) {
    int yuvSize = imageWidth * imageHeight * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) >> 3;
    byte[] yuv = new byte[yuvSize];
    int i = 0;
    for (int x = 0; x < imageWidth; x++) {
        for (int y = imageHeight - 1; y >= 0; y--) {
            yuv[i++] = imageBytes[y * imageWidth + x];
        }
    }
    i = yuv.length - 1;
    for (int x = imageWidth - 1; x >= 0; x -= 2) {
        for (int y = 0; y < (imageHeight >> 1); y++) {
            yuv[i--] = imageBytes[imageWidth * imageHeight + y * imageWidth + x];
            yuv[i--] = imageBytes[imageWidth * imageHeight + y * imageWidth + (x - 1)];
        }
    }
    return yuv;
}
public byte[] rotateYUV420Degree270(byte[] imageBytes, int imageWidth, int imageHeight) {
    byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
    int i = 0;
    for (int x = imageWidth - 1; x >= 0; x--) {
        for (int y = 0; y < imageHeight; y++) {
            yuv[i] = imageBytes[y * imageWidth + x];
            i++;
        }
    }
    i = imageWidth * imageHeight;
    int x = imageWidth - 1;
    while (x > 0) {
        for (int y = 0; y < imageHeight / 2; y++) {
            yuv[i] = imageBytes[imageWidth * imageHeight + y * imageWidth + (x - 1)];
            i++;
            yuv[i] = imageBytes[imageWidth * imageHeight + y * imageWidth + x];
            i++;
        }
        x -= 2;
    }
    return yuv;
}
public byte[] rotateYUVDegree270AndMirror(byte[] imageBytes, int imageWidth, int imageHeight) {
    byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
    int i = 0;
    int maxY;
    for (int x = imageWidth - 1; x >= 0; x--) {
        maxY = imageWidth * (imageHeight - 1) + x * 2;
        for (int y = 0; y < imageHeight; y++) {
            yuv[i] = imageBytes[maxY - (y * imageWidth + x)];
            i++;
        }
    }
    int uvSize = imageWidth * imageHeight;
    i = uvSize;
    int maxUV;
    int x = imageWidth - 1;
    while (x > 0) {
        maxUV = imageWidth * (imageHeight / 2 - 1) + x * 2 + uvSize;
        for (int y = 0; y < imageHeight / 2; y++) {
            yuv[i] = imageBytes[maxUV - 2 - (y * imageWidth + x - 1)];
            i++;
            yuv[i] = imageBytes[maxUV - (y * imageWidth + x)];
            i++;
        }
        x -= 2;
    }
    return yuv;
}
public byte[] rotateYUV420Degree180(byte[] imageBytes, int imageWidth, int imageHeight) {
    byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
    int count = 0;

    for (int i = imageWidth * imageHeight - 1; i >= 0; i--) {
        yuv[count] = imageBytes[i];
        count++;
    }

    for (int j = imageWidth * imageHeight * 3 / 2 - 1; j >= imageWidth * imageHeight; j -= 2) {
        yuv[count++] = imageBytes[j - 1];
        yuv[count++] = imageBytes[j];
    }
    return yuv;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值