android camera之nv21旋轉
這周做的一個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旋轉