1. 程式人生 > >android camera之nv21旋轉

android camera之nv21旋轉

現象 rtx 不可 行數據 灰度 upa sfc max ide

這周做的一個android的camera開發,需要獲取到視頻幀數據,並且需要是nv21格式的byte數組,並且視頻幀的圖像需要是正方向的。和android相機打過交道的都清楚,android的camera獲取到的圖片都是橫向的,因此,需要進行旋轉,對於圖像的旋轉,其實bitmap這個類已經可以幫我們實現了,但是前提是你需要將你的數據格式轉換為Bitmap才行,但是我們如果通過setPreviewCallback來獲取視頻幀,獲取到的圖片都是nv21,如果裝換為bitmap後,又很難的轉換為nv21格式的數據。因此則需要面臨一個問題,如歌旋轉nv21格式的byte數組,首先先來講下nv21格式,講到nv21就也要說說yuv240和nv12:

NV12、NV21(屬於YUV420)

技術分享圖片

NV12和NV21屬於YUV420格式,是一種two-plane模式,即Y和UV分為兩個Plane,但是UV(CbCr)為交錯存儲,而不是分為三個plane。其提取方式與上一種類似,即Y‘00、Y‘01、Y‘10、Y‘11共用Cr00、Cb00

YUV420 planar數據存儲, 以720×488大小圖象YUV420 planar為例,

其存儲格式是: 共大小為(720×480×3>>1)字節,

分為三個部分: Y分量:    (720×480)個字節 U(Cb)分量:  (720×480>>2)個字節     V(Cr)分量:   (720×480>>2)個字節

三個部分內部均是行優先存儲,三個部分之間是Y,U,V 順序存儲。

即YUV數據的0--720×480字節是Y分量值, 720×480--720×480×5/4字節是U分量 720×480×5/4 --720×480×3/2字節是V分量。

4 :2: 2 和4:2:0 轉換:

最簡單的方式:

YUV4:2:2 ---> YUV4:2:0 Y不變,將U和V信號值在行(垂直方向)在進行一次隔行抽樣。 YUV4:2:0 ---> YUV4:2:2 Y不變,將U和V信號值的每一行分別拷貝一份形成連續兩行數據。

在YUV420中,一個像素點對應一個Y,一個4X4的小方塊對應一個U和V。對於所有 YUV420圖像,它們的Y值排列是完全相同的,因為只有Y的圖像就是灰度圖像。YUV420sp與YUV420p的數據格式它們的UV排列在原理上是完 全不同的。420p它是先把U存放完後,再存放V,也就是說UV它們是連續的。而420sp它是UV、UV這樣交替存放的。(見下圖) 有了上面的理論,我就可以準確的計算出一個YUV420在內存中存放的大小。 width * hight =Y(總和) U = Y / 4 V = Y / 4

所以YUV420 數據在內存中的長度是 width * hight * 3 / 2,

假設一個分辨率為8X4的YUV圖像,它們的格式如下圖:

圖:YUV420sp格式

技術分享圖片

圖:YUV420p數據格式如下圖

技術分享圖片

具體的旋轉代碼如下:

public static byte[] rotateYUV420Degree90(byte[] data, int imageWidth, int imageHeight) {
    byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
    int i = 0;
    for (int x = 0; x < imageWidth; x++) {
        for (int y = imageHeight - 1; y >= 0; y--) {
            yuv[i] = data[y * imageWidth + x];
            i++;
        }
    }
    i = imageWidth * imageHeight * 3 / 2 - 1;
    for (int x = imageWidth - 1; x > 0; x = x - 2) {
        for (int y = 0; y < imageHeight / 2; y++) {
            yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x];
            i--;
            yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth)
                    + (x - 1)];
            i--;
        }
    }
    return yuv;
}

private static byte[] rotateYUV420Degree180(byte[] data, int imageWidth, int imageHeight) {
    byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
    int i = 0;
    int count = 0;
    for (i = imageWidth * imageHeight - 1; i >= 0; i--) {
        yuv[count] = data[i];
        count++;
    }
    i = imageWidth * imageHeight * 3 / 2 - 1;
    for (i = imageWidth * imageHeight * 3 / 2 - 1; i >= imageWidth
            * imageHeight; i -= 2) {
        yuv[count++] = data[i - 1];
        yuv[count++] = data[i];
    }
    return yuv;
}

public static byte[] rotateYUV420Degree270(byte[] data, int imageWidth,
                                     int imageHeight) {
    byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
    int nWidth = 0, nHeight = 0;
    int wh = 0;
    int uvHeight = 0;
    if (imageWidth != nWidth || imageHeight != nHeight) {
        nWidth = imageWidth;
        nHeight = imageHeight;
        wh = imageWidth * imageHeight;
        uvHeight = imageHeight >> 1;// uvHeight = height / 2
    }

    int k = 0;
    for (int i = 0; i < imageWidth; i++) {
        int nPos = 0;
        for (int j = 0; j < imageHeight; j++) {
            yuv[k] = data[nPos + i];
            k++;
            nPos += imageWidth;
        }
    }
    for (int i = 0; i < imageWidth; i += 2) {
        int nPos = wh;
        for (int j = 0; j < uvHeight; j++) {
            yuv[k] = data[nPos + i];
            yuv[k + 1] = data[nPos + i + 1];
            k += 2;
            nPos += imageWidth;
        }
    }
    return rotateYuv420Degree180(rotateYuv420Degree90(data, imageWidth, imageHeight), imageWidth, imageHeight);
}

然後,如果你想要查看旋轉後的圖像,則通過以下代碼即可:

YuvImage yuvimage = new YuvImage(  
                data,  
                ImageFormat.NV21,  
                width,  
                height,  
                null);  
        baos = new ByteArrayOutputStream();  
        yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, baos);// 80--JPG圖片的質量[0-100],100最高  
        rawImage = baos.toByteArray();  
        //將rawImage轉換成bitmap  
        BitmapFactory.Options options = new BitmapFactory.Options();  
        options.inPreferredConfig = Bitmap.Config.RGB_565;  
        bitmap = BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length, options);  

在這裏邊我們需要註意的是我們的寬和高,需要用轉移後的,不可使用轉移前的,否則會出現看到的圖片有重影的現象。  

  

android camera之nv21旋轉