Android CameraX 攝像頭數(shù)據(jù)ImageProxy數(shù)據(jù)分析

什么是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í)需要編寫的代碼量盈罐。

image-20210401101807580

官方教程也很詳細(xì)榜跌,如下: 官方教程

Add the Gradle dependencies

  1. 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
  1. 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 after buildTypes, add the following:
compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}
image-20210401103242093

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);
  1. 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();
}
image-20210401120357442

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 );
image-20210401130413632

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)使用了 getBuffergetPixelStride 兩個(gè)方法五嫂,但是還有一個(gè) getRowStride沒(méi)有用到.

RowStride

RowStride表示行步長(zhǎng),Y數(shù)據(jù)對(duì)應(yīng)的行步長(zhǎng)可能為:

  1. 等于Width;
  2. 大于Width;

以4x4的I420為例肯尺,其數(shù)據(jù)可以看為

img

如果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ù):

img
獲取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)可能為:

  1. 等于Width;
  2. 大于Width;
  3. 等于Width/2;
  4. 大于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分為兩種:YUV420PYUV420SP傻挂。所以就先了解下YUV420PYUV420SP.

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

YUV420p

那么真實(shí)的在字節(jié)流中就是按照行從左到右一行一行的拼起來(lái)的:

img

==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)烈推薦使用NV21YV12挚歧,因?yàn)檫@兩種格式支持所有的相機(jī)設(shè)備扛稽。Camera默認(rèn)輸出YUV的數(shù)據(jù)格式為NV21。但是在Camera2中滑负,推薦使用的格式則是YUV_420_888在张。

NV21
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中常見的YUV420PYUV420SP痪寻。一般我們?cè)谑褂脃uv數(shù)據(jù)的時(shí)候螺句,會(huì)對(duì)yuv數(shù)據(jù)進(jìn)行變換,比如:攝像頭數(shù)據(jù)旋轉(zhuǎn)橡类,從一種格式轉(zhuǎn)為另一種數(shù)據(jù)等蛇尚。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市顾画,隨后出現(xiàn)的幾起案子取劫,更是在濱河造成了極大的恐慌,老刑警劉巖研侣,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谱邪,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡庶诡,警方通過(guò)查閱死者的電腦和手機(jī)虾标,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人璧函,你說(shuō)我怎么就攤上這事』裕” “怎么了蘸吓?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)撩幽。 經(jīng)常有香客問(wèn)我库继,道長(zhǎng),這世上最難降的妖魔是什么窜醉? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任宪萄,我火速辦了婚禮,結(jié)果婚禮上榨惰,老公的妹妹穿的比我還像新娘拜英。我一直安慰自己,他們只是感情好琅催,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布居凶。 她就那樣靜靜地躺著,像睡著了一般藤抡。 火紅的嫁衣襯著肌膚如雪侠碧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天缠黍,我揣著相機(jī)與錄音弄兜,去河邊找鬼。 笑死瓷式,一個(gè)胖子當(dāng)著我的面吹牛替饿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蒿往,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼盛垦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了瓤漏?” 一聲冷哼從身側(cè)響起腾夯,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蔬充,沒(méi)想到半個(gè)月后蝶俱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饥漫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年榨呆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庸队。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡积蜻,死狀恐怖闯割,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情竿拆,我是刑警寧澤宙拉,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站丙笋,受9級(jí)特大地震影響谢澈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜御板,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一锥忿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧怠肋,春花似錦敬鬓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至酪惭,卻和暖如春希痴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背春感。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工砌创, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鲫懒。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓嫩实,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親窥岩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子甲献,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容