什么是Android CameraX
CameraX 是一個(gè) Jetpack 支持庫(kù),旨在幫助您簡(jiǎn)化相機(jī)應(yīng)用的開發(fā)工作。它提供一致且易于使用的 API Surface,適用于大多數(shù) Android 設(shè)備吞鸭,并可向后兼容至 Android 5.0(API 級(jí)別 21)溉委。
雖然它利用的是 camera2 的功能朱浴,但使用的是更為簡(jiǎn)單且基于用例的方法滚停,該方法具有生命周期感知能力。它還解決了設(shè)備兼容性問(wèn)題捕仔,因此您無(wú)需在代碼庫(kù)中添加設(shè)備專屬代碼。這些功能減少了將相機(jī)功能添加到應(yīng)用時(shí)需要編寫的代碼量盈罐。
官方教程也很詳細(xì)榜跌,如下: 官方教程
Add the Gradle dependencies
- Open the
build.gradle(Module: app)
file and add the CameraX dependencies to our app Gradle file, inside the dependencies section:
def camerax_version = "1.0.0-alpha05"
// CameraX core library using camera2 implementation
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle Library
- CameraX needs some methods that are part of Java 8, so we need to set our compile options accordingly. At the end of the
android
block, right afterbuildTypes
, add the following:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
3、Request camera permissions
public boolean checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
}, 1);
}
return false;
}
4盅粪、Implement Preview use case
創(chuàng)建ImageCaptureConfig和ImageCapture這兩個(gè)對(duì)象钓葫,用imageCapture.takePicture方法傳入相片保存地址就行了。當(dāng)然在生命周期綁定中也加上imageCapture票顾。
ImageCaptureConfig可以定制相片尺寸和長(zhǎng)寬比例础浮,這里的尺寸和比例跟相機(jī)預(yù)覽的尺寸比例無(wú)關(guān),我測(cè)試傳入任何比例都能得到圖片奠骄。
// 2. capture
ImageCaptureConfig imageCaptureConfig = new ImageCaptureConfig.Builder()
.setTargetAspectRatio(new Rational(1,1))
.setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
.build();
final ImageCapture imageCapture = new ImageCapture(imageCaptureConfig);
viewFinder.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
File photo = new File(getExternalCacheDir() + "/" + System.currentTimeMillis() + ".jpg");
imageCapture.takePicture(photo, new ImageCapture.OnImageSavedListener() {
@Override
public void onImageSaved(@NonNull File file) {
showToast("saved " + file.getAbsolutePath());
}
@Override
public void onError(@NonNull ImageCapture.UseCaseError useCaseError, @NonNull String message, @Nullable Throwable cause) {
showToast("error " + message);
cause.printStackTrace();
}
});
return true;
}
});
CameraX.bindToLifecycle(this, preview, imageCapture);
- Implement ImageCapture use case
給TextureView設(shè)置布局變化的監(jiān)聽豆同,用updateTransform()更新相機(jī)預(yù)覽,然后startCamera()啟動(dòng)相機(jī)
TextureView viewFinder = findViewById(R.id.view_finder);
viewFinder.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View view, int i, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
updateTransform();
}
});
viewFinder.post(new Runnable() {
@Override
public void run() {
startCamera();
}
});
更新相機(jī)預(yù)覽:主要是給TextureView設(shè)置一個(gè)旋轉(zhuǎn)的矩陣變化含鳞,防止預(yù)覽方向不對(duì)
private void updateTransform() {
Matrix matrix = new Matrix();
// Compute the center of the view finder
float centerX = viewFinder.getWidth() / 2f;
float centerY = viewFinder.getHeight() / 2f;
float[] rotations = {0,90,180,270};
// Correct preview output to account for display rotation
float rotationDegrees = rotations[viewFinder.getDisplay().getRotation()];
matrix.postRotate(-rotationDegrees, centerX, centerY);
// Finally, apply transformations to our TextureView
viewFinder.setTransform(matrix);
}
啟動(dòng)相機(jī):創(chuàng)建PreviewConfig和Preview這兩個(gè)對(duì)象影锈,可以設(shè)置預(yù)覽圖像的尺寸和比例,在OnPreviewOutputUpdateListener回調(diào)中用setSurfaceTexture方法,將相機(jī)圖像輸出到TextureView鸭廷。最后用CameraX.bindToLifecycle方法將相機(jī)與當(dāng)前頁(yè)面的生命周期綁定枣抱。
private void startCamera() {
// 1. preview
PreviewConfig previewConfig = new PreviewConfig.Builder()
.setTargetAspectRatio(new Rational(1, 1))
.setTargetResolution(new Size(640,640))
.build();
Preview preview = new Preview(previewConfig);
preview.setOnPreviewOutputUpdateListener(new Preview.OnPreviewOutputUpdateListener() {
@Override
public void onUpdated(Preview.PreviewOutput output) {
ViewGroup parent = (ViewGroup) viewFinder.getParent();
parent.removeView(viewFinder);
parent.addView(viewFinder, 0);
viewFinder.setSurfaceTexture(output.getSurfaceTexture());
updateTransform();
}
});
CameraX.bindToLifecycle(this, preview);
這樣就實(shí)現(xiàn)了基本的相機(jī)預(yù)覽功能。這幾個(gè)方法都很簡(jiǎn)單明了辆床,對(duì)外只依賴一個(gè)TextureView沃但。生命周期自動(dòng)綁定,這意味著代碼可以寫在一塊佛吓,在一處調(diào)用宵晚。不像以前這里插一段代碼,那里插一段代碼维雇。
還有最大的好處淤刃,就是可擴(kuò)展性。相機(jī)預(yù)覽使用了PreviewConfig和Preview兩個(gè)對(duì)象吱型,加入新的相機(jī)功能同樣是加兩個(gè)對(duì)象XXXConfig和XXX逸贾,其他地方都不同改!
加入拍照功能就加入ImageCaptureConfig和ImageCapture津滞,加入圖像分析功能就加入ImageAnalysisConfig和ImageAnalysis铝侵,非常方便統(tǒng)一。
6触徐、Implement ImageAnalysis use case
圖片分析名字很高大上咪鲜,實(shí)際上就是圖像數(shù)據(jù)回調(diào),實(shí)時(shí)獲取相機(jī)的圖像數(shù)據(jù)撞鹉,可以自己處理這些圖像疟丙。
創(chuàng)建ImageAnalysisConfig和ImageAnalysis這兩個(gè)對(duì)象,創(chuàng)建一個(gè)HandlerThread用于在子線程中處理數(shù)據(jù)鸟雏,創(chuàng)建一個(gè)ImageAnalysis.Analyzer接口實(shí)現(xiàn)類享郊,在analyze(ImageProxy imageProxy, int rotationDegrees)回調(diào)方法中就能拿到圖像數(shù)據(jù)了。當(dāng)然ImageAnalysis對(duì)象也要綁定生命周期孝鹊。
我這里分析圖像數(shù)據(jù)用了之前寫的一個(gè)工具YUVDetectView炊琉,來(lái)分析圖像屬于哪種YUV420格式。
// 3. analyze
HandlerThread handlerThread = new HandlerThread("Analyze-thread");
handlerThread.start();
ImageAnalysisConfig imageAnalysisConfig = new ImageAnalysisConfig.Builder()
.setCallbackHandler(new Handler(handlerThread.getLooper()))
.setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
.setTargetAspectRatio(new Rational(2, 3))
// .setTargetResolution(new Size(600, 600))
.build();
ImageAnalysis imageAnalysis = new ImageAnalysis(imageAnalysisConfig);
imageAnalysis.setAnalyzer(new MyAnalyzer());
CameraX.bindToLifecycle(this, preview, imageCapture, imageAnalysis);
private class MyAnalyzer implements ImageAnalysis.Analyzer {
@Override
public void analyze(ImageProxy imageProxy, int rotationDegrees) {
final Image image = imageProxy.getImage();
if(image != null) {
Log.d("chao", image.getWidth() + "," + image.getHeight());
imageView.input(image);
}
}
}
攝像頭數(shù)據(jù)處理
就圖像而言又活,首先需要獲得攝像頭采集的數(shù)據(jù)苔咪,然后得到這個(gè)byte[] 進(jìn)行編碼,再進(jìn)行后續(xù)的封包與發(fā)送皇钞。我們通 過(guò)CameraX圖像分析接口得到的數(shù)據(jù)為ImageProxy(Image的代理類)悼泌。那么怎么從ImageProxy/Image 中獲取 我們需要的數(shù)據(jù)呢,這個(gè)數(shù)據(jù)格式是什么?
@Override
public void analyze(ImageProxy image, int rotationDegrees) {
Log.i(TAG, "analyze: " + image.getWidth() + " height " + image.getHeight() + " rotationDegrees:" + rotationDegrees);
//圖像格式
int format = image.getFormat();
if (format != ImageFormat.YUV_420_888) {
Log.i(TAG, "analyze: format:" + format);
}
lock.lock();
ImageProxy.PlaneProxy[] planes = image.getPlanes();
lock.unlock();
}
可以通過(guò) getPlanes方法得到PlaneProxy數(shù)組夹界。PlaneProxy為Image.Plane代理馆里,同ImagePrxoy與Image的關(guān)系 一樣隘世。
/** A plane proxy which has an analogous interface as {@link android.media.Image.Plane}. */
interface PlaneProxy {
/**
* Returns the row stride.
*
* <p>@see {@link android.media.Image.Plane#getRowStride()}.
*/
int getRowStride();
/**
* Returns the pixel stride.
*
* <p>@see {@link android.media.Image.Plane#getPixelStride()}.
*/
int getPixelStride();
/**
* Returns the pixels buffer.
*
* <p>@see {@link android.media.Image.Plane#getBuffer()}.
*/
ByteBuffer getBuffer();
}
YUV420根據(jù)顏色數(shù)據(jù)的存儲(chǔ)順序不同,又分為了多種不同的格式鸠踪,這些格式實(shí)際存儲(chǔ)的信息還是完全一致的丙者。舉 例來(lái)說(shuō),對(duì)于4x4的圖片营密,在YUV420下械媒,任何格式都有16個(gè)Y值,4個(gè)U值和4個(gè)V值评汰,不同格式只是Y纷捞、U和V的排 列順序變化。I420 為 YYYYYYYYYYYYYYYYUUUUVVVV 被去,YUV420 是一類格式的集合主儡,YUV420并不能完全確定顏色數(shù)據(jù)的存儲(chǔ)順序。
PlaneProxy/Plane
Y惨缆、U和V三個(gè)分量的數(shù)據(jù)分別保存在三個(gè)Plane類中糜值,即通過(guò) getPlanes()得到的數(shù)組。 Plane 實(shí)際是對(duì)ByteBuffer的封裝坯墨。
Image保證了planes[0]一定是Y寂汇,planes[1]一定是U,planes[2]一定是V捣染。且對(duì)于plane [0]骄瓣,Y分量數(shù)據(jù)一定是連續(xù)存儲(chǔ)的,中間不會(huì)有U或V數(shù)據(jù)穿插液斜,也就是說(shuō)我們一定能夠一次性得到所有Y分量的值累贤。
但是對(duì)于UV數(shù)據(jù),可能存在以下兩種情況:
1. planes[1] = {UUUU...}少漆,planes[2] = {VVVV...}; //I420
2. planes[1] = {UVUV...},planes[2] = {VUVU...}硼被。
PixelStride
所以在我么取數(shù)據(jù)時(shí)需要在根據(jù)Plane中的另一個(gè)信息來(lái)確定如何取對(duì)應(yīng)的U或者V數(shù)據(jù)示损。
// 行內(nèi)數(shù)據(jù)值間隔
// 1:表示無(wú)間隔取值,即為上面的第一種情況
// 2: 表示需要間隔一個(gè)數(shù)據(jù)取值嚷硫,即為上面的第二種情況
int pixelStride = plane.getPixelStride();
根據(jù)這個(gè)屬性检访,我們將確定數(shù)據(jù)如何存儲(chǔ),因此如果需要取出代表I420格式的byte[]仔掸,則為:YUV420中脆贵,Y數(shù)據(jù)長(zhǎng)度為: width*height , 而U、V都為:width / 2 * height / 2起暮。
ImageProxy.PlaneProxy[] planes = image.getPlanes();
//y數(shù)據(jù)的這個(gè)值只能是:1
int pixelStride = planes[0].getPixelStride();
int pixelStride2 = planes[1].getPixelStride();
Log.i(TAG, "pixelStride: " + pixelStride+" pixelStride2: "+ pixelStride2 );
pixelStride: 1 pixelStride2: 2
小米手機(jī)運(yùn)行 planes[0] 的PixelStride 為1 planes[1]的 PixelStride為2
說(shuō)明是UVUV交叉存儲(chǔ)卖氨。
// Y數(shù)據(jù) pixelStride一定為1
int pixelStride = planes[0].getPixelStride();
planes[0].getBuffer() // Y數(shù)據(jù)
byte[] u = new byte[image.getWidth() / 2 * image.getHeight() / 2];
int pixelStride = planes[1].getPixelStride();
if (pixelStide == 1) {
planes[1].getBuffer() // U數(shù)據(jù)
} else if (pixelStide == 2) {
ByteBuffer uBuffer = planes[1].getBuffer()
for (int i = 0; i < uBuffer.remaining(); i+=2) {
u[i] = uBuffer.get(); //丟棄一個(gè)數(shù)據(jù),這個(gè)數(shù)據(jù)其實(shí)是V數(shù)據(jù),但是我們還是到planes[2]中獲取V數(shù)據(jù)
uBuffer.get();
}
}
但是如果使用上面的代碼去獲取YUV數(shù)據(jù)筒捺,可能你會(huì)驚奇的發(fā)現(xiàn)柏腻,并不是在所有你設(shè)置的Width與 Height(分辨率)下都能夠正常運(yùn)行。我們忽略了什么系吭,為什么會(huì)出現(xiàn)問(wèn)題呢?
在Plane中 我們已經(jīng)使用了 getBuffer
與 getPixelStride
兩個(gè)方法五嫂,但是還有一個(gè) getRowStride沒(méi)有用到.
RowStride
RowStride表示行步長(zhǎng),Y數(shù)據(jù)對(duì)應(yīng)的行步長(zhǎng)可能為:
- 等于Width;
- 大于Width;
以4x4的I420為例肯尺,其數(shù)據(jù)可以看為
如果RowStride等于Width沃缘,那么我們直接通過(guò) planes[0].getBuffer() 獲得Y數(shù)據(jù)沒(méi)有問(wèn)題。
但是如果RowStride大于Width则吟,比如對(duì)于4x4的I420槐臀,如果每行需要以8字節(jié)對(duì)齊,那么可能得到的RowStride不等于4(Width)逾滥,而是得到8峰档。那么此時(shí)會(huì)在每行數(shù)據(jù)末尾補(bǔ)充占位的無(wú)效數(shù)據(jù):
獲取Y數(shù)據(jù)
ImageProxy.PlaneProxy[] planes = image.getPlanes();
// todo 避免內(nèi)存抖動(dòng).
int size = image.getWidth() * image.getHeight() * 3 / 2;
if (yuv420 == null || yuv420.capacity() < size) {
yuv420 = ByteBuffer.allocate(size);
}
yuv420.position(0);
/**
* Y數(shù)據(jù)
*/
ImageProxy.PlaneProxy plane = planes[0];//y數(shù)據(jù)
//pixelStride = 1 : 取值無(wú)間隔
//pixelStride = 2 : 間隔1個(gè)字節(jié)取值
// y的此數(shù)據(jù)應(yīng)該都是1
int pixelStride = plane.getPixelStride();//Y的肯定為1
//大于等于寬, 表示連續(xù)的兩行數(shù)據(jù)的間隔
// 如:640x480的數(shù)據(jù)寨昙,
// 可能得到640
// 可能得到650讥巡,表示每行最后10個(gè)字節(jié)為補(bǔ)位的數(shù)據(jù)
int rowStride = plane.getRowStride();//rowStride 可能末尾有填充
ByteBuffer buffer = plane.getBuffer();
byte[] row = new byte[image.getWidth()];
// 每行要排除的無(wú)效數(shù)據(jù),但是需要注意:實(shí)際測(cè)試中 最后一行沒(méi)有這個(gè)補(bǔ)位數(shù)據(jù)
byte[] skipRow = new byte[rowStride - image.getWidth()];
for (int i = 0; i < image.getHeight(); i++) {
buffer.get(row);
yuv420.put(row);
// 不是最后一行
if (i < image.getHeight() - 1) {
buffer.get(skipRow);//最后一行因?yàn)楹竺娓鳸 數(shù)據(jù)舔哪,沒(méi)有無(wú)效占位數(shù)據(jù)欢顷,不需要丟棄
}
}
而對(duì)于U與V數(shù)據(jù),對(duì)應(yīng)的行步長(zhǎng)可能為:
- 等于Width;
- 大于Width;
- 等于Width/2;
- 大于Width/2
等于Width
這表示捉蚤,我們獲得planes[1]中不僅包含U數(shù)據(jù)抬驴,還會(huì)包含V的數(shù)據(jù),此時(shí)pixelStride==2缆巧。
U | V | U | V |
---|---|---|---|
U | V | U | V |
那么V數(shù)據(jù):planes[2]布持,則為:
V | U | V | U |
---|---|---|---|
V | U | V | U |
大于Width
與Y數(shù)據(jù)一樣,可能由于字節(jié)對(duì)齊陕悬,出現(xiàn)RowStride大于Width的情況题暖,與等于Width一樣,planes[1]中不僅包含U 數(shù)據(jù)捉超,還會(huì)包含V的數(shù)據(jù)胧卤,此pixelStride==2。
U | V | U | V | 0 | 0 | 0 | 0 |
---|---|---|---|---|---|---|---|
U | V | U | V | 最后一行沒(méi)有站位 |
---|---|---|---|---|
planes[2]拼岳,則為:
V | U | V | U | 0 | 0 | 0 | 0 |
---|---|---|---|---|---|---|---|
V | U | V | U | 最后一行沒(méi)有站位 |
---|---|---|---|---|
等于Width/2
當(dāng)獲取的U數(shù)據(jù)對(duì)應(yīng)的RowStride等于Width/2枝誊,表示我們得到的planes[1]只包含U數(shù)據(jù)。此時(shí)pixelStride==1惜纸。 那么planes[1]+planes[2]為:
U | U | ||
---|---|---|---|
U | U | ||
V | V | ||
V | V |
這種情況叶撒,所有的U數(shù)據(jù)是連在一起的绝骚,即 planes[1].getBuffer 可以直接獲得完整的U數(shù)據(jù)。
大于Width/2
同樣我們得到的planes[1]只包含U數(shù)據(jù)痊乾,但是與Y數(shù)據(jù)一樣皮壁,可能存在占位數(shù)據(jù)。此時(shí)pixelStride==1哪审。 planes[1]+planes[2]為:
U | U | 0 | 0 | 0 | 0 | 0 | 0 |
---|---|---|---|---|---|---|---|
U | U | 最后一行沒(méi)有站位 |
---|---|---|
V | V | 0 | 0 | 0 | 0 | 0 | 0 |
---|---|---|---|---|---|---|---|
V | V | 最后一行沒(méi)有站位 |
---|---|---|
獲取UV數(shù)據(jù)
/**
* U V 數(shù)據(jù)
*/
for (int i = 1; i < 3; i++) {//planes[1] | planes[2] uv數(shù)據(jù)處理
plane = planes[i];
pixelStride = plane.getPixelStride();//1 I420 2 交錯(cuò)packed UVUV
// uv數(shù)據(jù)的rowStride可能是
// 如:640的寬
// 可能得到320蛾魄, pixelStride 為1
// 可能大于320同時(shí)小于640,有為了補(bǔ)位的無(wú)效數(shù)據(jù) pixelStride 為1
// 可能得到640 uv數(shù)據(jù)在一起湿滓,pixelStride為2
// 可能大于640滴须,有為了補(bǔ)位的無(wú)效數(shù)據(jù) pixelStride為2
rowStride = plane.getRowStride();
buffer = plane.getBuffer();
int uvWidth = image.getWidth() / 2;
int uvHeight = image.getHeight() / 2;
for (int j = 0; j < uvHeight; j++) {
for (int k = 0; k < rowStride; k++) {
// 最后一行,是沒(méi)有補(bǔ)位數(shù)據(jù)的
if (j == uvHeight - 1) {
//只有自己(U/V)的數(shù)據(jù)
if (pixelStride == 1) {
// 結(jié)合外層if 則表示:
// 如果是最后一行叽奥,我們就不管結(jié)尾的占位數(shù)據(jù)了
if (k >= uvWidth) {
break;
}
} else if (pixelStride == 2) {
//與同級(jí)if相同意思
// todo uv混合扔水,
// planes[2]:uvu
// planes[3]:vuv
if (k >= image.getWidth() - 1) {
break;
}
}
}
byte b = buffer.get();
if (pixelStride == 2) {
//打包格式 uv在一起,偶數(shù)位取出來(lái)是U數(shù)據(jù): 0 2 4 6
if (k < image.getWidth() && k % 2 == 0) {
yuv420.put(b);
}
} else if (pixelStride == 1) {
if (k < uvWidth) {
yuv420.put(b);
}
}
}
}
}
YUV簡(jiǎn)介
與RGB類似,YUV也是一種顏色編碼方法朝氓,主要用于視頻領(lǐng)域魔市,它將亮度信息(Y)與色彩信息(UV)分離,沒(méi)有UV信息一樣可以顯示完整的圖像赵哲,只不過(guò)是黑白的待德,比如這樣的設(shè)計(jì)解決了彩色電視機(jī)與黑白電視的兼容問(wèn)題。
YUV枫夺,分為三個(gè)分量将宪,“Y”表示的是明亮度(Luminance或Luma),也就是灰度值橡庞;而“U”和“V” 表示的則是色度(Chrominance或Chroma)较坛,作用是用于指定像素的顏色。
UV 即CbCr(C代表顏色扒最,b代表藍(lán)色丑勤,r代表紅色)
分類
YUV格式有兩大類:==平面(planar)和緊湊(packed==)。
對(duì)于planar的YUV格式吧趣,先連續(xù)存儲(chǔ)所有像素點(diǎn)的Y确封,緊接著存儲(chǔ)所有像素點(diǎn)的U,隨后是存儲(chǔ)所有像素點(diǎn)的V再菊,或者是先v后u
對(duì)于packed的YUV格式,每個(gè)像素點(diǎn)的Y,U,V是連續(xù)交替存儲(chǔ)的颜曾。
采樣
主流的采樣方式有三種纠拔,YUV4:4:4,YUV4:2:2泛豪,YUV4:2:0
YUV 4:4:4采樣稠诲,每一個(gè)Y對(duì)應(yīng)一組UV分量,一個(gè)YUV占8+8+8 = 24bits 3個(gè)字節(jié)侦鹏。
YUV 4:2:2采樣,每?jī)蓚€(gè)Y共用一組UV分量,一個(gè)YUV占8+4+4 = 16bits 2個(gè)字節(jié)臀叙。
YUV 4:2:0采樣略水,每四個(gè)Y共用一組UV分量,一個(gè)YUV占8+2+2 = 12bits 1.5個(gè)字節(jié)。
最常見的YUV420P和YUV420SP都是基于4:2:0采樣的劝萤,所以如果圖片的寬為width渊涝,高為heigth,在內(nèi)存中占的空間為width * height * 3 / 2床嫌,其中前width * height的空間存放Y分量跨释,接著width * height / 4存放U分量,最后width * height / 4存放V分量厌处。
YUV格式
常見的YUV格式有YUY2鳖谈、YUYV、YVYU阔涉、UYVY缆娃、AYUV、Y41P瑰排、Y411贯要、Y211、IF09凶伙、IYUV郭毕、YV12、YVU9函荣、YUV411显押、YUV420等,Android中比較常見是YUV420
分為兩種:YUV420P
和YUV420SP
傻挂。所以就先了解下YUV420P
和YUV420SP
.
libX264中對(duì)YUV各種格式的定義乘碑。
#define X264_CSP_I400 0x0001 /* monochrome 4:0:0 */
#define X264_CSP_I420 0x0002 /* yuv 4:2:0 planar */
#define X264_CSP_YV12 0x0003 /* yvu 4:2:0 planar */
#define X264_CSP_NV12 0x0004 /* yuv 4:2:0, with one y plane and one packed u+v */
#define X264_CSP_NV21 0x0005 /* yuv 4:2:0, with one y plane and one packed v+u */
#define X264_CSP_I422 0x0006 /* yuv 4:2:2 planar */
#define X264_CSP_YV16 0x0007 /* yvu 4:2:2 planar */
#define X264_CSP_NV16 0x0008 /* yuv 4:2:2, with one y plane and one packed u+v */
#define X264_CSP_YUYV 0x0009 /* yuyv 4:2:2 packed */
#define X264_CSP_UYVY 0x000a /* uyvy 4:2:2 packed */
#define X264_CSP_V210 0x000b /* 10-bit yuv 4:2:2 packed in 32 */
#define X264_CSP_I444 0x000c /* yuv 4:4:4 planar */
#define X264_CSP_YV24 0x000d /* yvu 4:4:4 planar */
YUV420P
YUV420P是平面模式,Y , U , V分別在不同平面金拒,也就是有三個(gè)平面兽肤,它是YUV標(biāo)準(zhǔn)格式4:2:0
那么真實(shí)的在字節(jié)流中就是按照行從左到右一行一行的拼起來(lái)的:
==YUV420P分為:YU12和YV12==
YU12格式
在Android中也叫作I420格式,首先是所有Y值绪抛,然后是所有U值资铡,最后是所有V值。比如6x6的圖片幢码,內(nèi)存大小就是6x6x3/2=54個(gè)字節(jié)笤休。為了更清晰的查看,我們換行看症副,真實(shí)的是一行byte[]數(shù)據(jù)流店雅。
YYYYYY
YYYYYY
YYYYYY
YYYYYY
UUUUUU
VVVVVV
YV12格式
YV12格式與YU12基本相同政基,首先是所有Y值,然后是所有V值闹啦,最后是所有U值沮明。比如6x6的圖片,內(nèi)存大小就是6x6x3/2=54個(gè)字節(jié)
YYYYYY
YYYYYY
YYYYYY
YYYYYY
VVVVVV
UUUUUU
YUV420SP
YUV420SP 也是是平面模式窍奋。分為NV21和NV12兩種格式荐健。Y是一個(gè)平面,UV是一個(gè)平面费变,UV/VU為交替存儲(chǔ)摧扇,而不是分為三個(gè)平面。
在Android Camera中文檔中強(qiáng)烈推薦使用NV21
和YV12
挚歧,因?yàn)檫@兩種格式支持所有的相機(jī)設(shè)備扛稽。Camera默認(rèn)輸出YUV的數(shù)據(jù)格式為NV21。但是在Camera2中滑负,推薦使用的格式則是YUV_420_888
在张。
NV21格式
在Android Camera中手機(jī)從攝像頭采集的預(yù)覽數(shù)據(jù)默認(rèn)值是NV21。
NV21存儲(chǔ)順序是先存Y值矮慕,再VU交替存儲(chǔ):YYYYVUVUVU帮匾,比如6x6的圖片,內(nèi)存大小就是6x6x3/2=54個(gè)字節(jié)
YYYYYY
YYYYYY
YYYYYY
YYYYYY
VUVUVU
VUVUVU
NV12格式
NV12存儲(chǔ)順序是先存Y值痴鳄,再UV交替存儲(chǔ):YYYYUVUVUV瘟斜,比如6x6的圖片,內(nèi)存大小就是6x6x3/2=54個(gè)字節(jié)
YYYYYY
YYYYYY
YYYYYY
YYYYYY
UVUVUV
UVUVUV
這里先熟悉下Android中常見的YUV420P
和YUV420SP
痪寻。一般我們?cè)谑褂脃uv數(shù)據(jù)的時(shí)候螺句,會(huì)對(duì)yuv數(shù)據(jù)進(jìn)行變換,比如:攝像頭數(shù)據(jù)旋轉(zhuǎn)橡类,從一種格式轉(zhuǎn)為另一種數(shù)據(jù)等蛇尚。