再談onPreviewFrame預(yù)覽幀率問題

在之前嫁蛇,本人寫了一篇文章(關(guān)于Android Camera onPreviewFrame 預(yù)覽回調(diào)幀率問題)第煮,說了關(guān)于高通和MTK CPU在單雙HandlerThread控制Camera和Rendering上的差異。我覺得有必要詳細(xì)說明一下onPreviewFrame在不同情況下害晦,可能會對幀率產(chǎn)生影響的問題。對此,本人重新梳理了一下殴俱,詳細(xì)討論一下該如何確保onPreviewFrame回調(diào)幀率。
首先李丰,我們來復(fù)現(xiàn)一下雙HandlerThread分別控制相機和渲染的方式導(dǎo)致(大)部分MTK 的CPU 的onPreviewFrame回調(diào)幀率大幅度降低的情況:
首先是Camera控制類圾旨,用于管理相機的打開關(guān)閉等操作:

public final class CameraManager {

    private static CameraManager mInstance;

    // 相機默認(rèn)寬高莺治,相機的寬度和高度跟屏幕坐標(biāo)不一樣床佳,手機屏幕的寬度和高度是反過來的。
    public final int DEFAULT_WIDTH = 1280;
    public final int DEFAULT_HEIGHT = 720;
    // 期望fps
    public final int DESIRED_PREVIEW_FPS = 30;

    // 這里反過來是因為相機的分辨率跟屏幕的分辨率寬高剛好反過來
    public final float Ratio_4_3 = 0.75f;
    public final float Ratio_1_1 = 1.0f;
    public final float Ratio_16_9 = 0.5625f;

    private int mCameraID = Camera.CameraInfo.CAMERA_FACING_FRONT;
    private Camera mCamera;
    private int mCameraPreviewFps;
    private int mOrientation = 0;

    // 當(dāng)前的寬高比
    private float mCurrentRatio = Ratio_16_9;

    /**
     * 獲取單例
     * @return
     */
    public static CameraManager getInstance() {
        if (mInstance == null) {
            mInstance = new CameraManager();
        }
        return mInstance;
    }

    private CameraManager() {}


    /**
     * 打開相機
     */
    public void openCamera() {
        openCamera(DESIRED_PREVIEW_FPS);
    }

    /**
     * 打開相機昔头,默認(rèn)打開前置相機
     * @param expectFps
     */
    public void openCamera(int expectFps) {
        openCamera(mCameraID, expectFps);
    }

    /**
     * 根據(jù)ID打開相機
     * @param cameraID
     * @param expectFps
     */
    public void openCamera(int cameraID, int expectFps) {
        openCamera(cameraID, expectFps, DEFAULT_WIDTH, DEFAULT_HEIGHT);
    }

    /**
     *  打開相機
     * @param cameraID
     * @param expectFps
     * @param expectWidth
     * @param expectHeight
     */
    public void openCamera(int cameraID, int expectFps, int expectWidth, int expectHeight) {
        if (mCamera != null) {
            throw new RuntimeException("camera already initialized!");
        }
        mCamera = Camera.open(cameraID);
        if (mCamera == null) {
            throw new RuntimeException("Unable to open camera");
        }
        mCameraID = cameraID;
        Camera.Parameters parameters = mCamera.getParameters();
        mCameraPreviewFps = chooseFixedPreviewFps(parameters, expectFps * 1000);
        parameters.setRecordingHint(true);
        mCamera.setParameters(parameters);
        setPreviewSize(mCamera, expectWidth, expectHeight);
        setPictureSize(mCamera, expectWidth, expectHeight);
        mCamera.setDisplayOrientation(mOrientation);
    }

    /**
     * 重新打開相機
     */
    public void reopenCamera() {
        releaseCamera();
        openCamera(mCameraID, DESIRED_PREVIEW_FPS);
    }

    /**
     * 重新打開相機
     * @param expectFps
     */
    public void reopenCamera(int expectFps) {
        releaseCamera();
        openCamera(mCameraID, expectFps);
    }

    /**
     * 重新打開相機
     * @param expectFps
     * @param expectWidth
     * @param expectHeight
     */
    public void reopenCamera(int expectFps, int expectWidth, int expectHeight) {
        releaseCamera();
        openCamera(mCameraID, expectFps, expectWidth, expectHeight);
    }


    /**
     * 設(shè)置預(yù)覽的Surface
     * @param texture
     */
    public void setPreviewSurface(SurfaceTexture texture) {
        if (mCamera != null) {
            try {
                mCamera.setPreviewTexture(texture);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 設(shè)置預(yù)覽的Surface
     * @param holder
     */
    public void setPreviewSurface(SurfaceHolder holder) {
        if (mCamera != null) {
            try {
                mCamera.setPreviewDisplay(holder);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 添加預(yù)覽回調(diào) 備注:預(yù)覽回調(diào)需要在setPreviewSurface之后調(diào)用
     * @param callback
     * @param previewBuffer
     */
    public void setPreviewCallbackWithBuffer(Camera.PreviewCallback callback, byte[] previewBuffer) {
        if (mCamera != null) {
            mCamera.setPreviewCallbackWithBuffer(callback);
            mCamera.addCallbackBuffer(previewBuffer);
        }
    }

    /**
     * 開始預(yù)覽
     */
    public void startPreview() {
        if (mCamera != null) {
            mCamera.startPreview();
        }
    }

    /**
     * 停止預(yù)覽
     */
    public void stopPreview() {
        if (mCamera != null) {
            mCamera.stopPreview();
        }
    }

    /**
     * 切換相機
     * @param cameraID 相機Id
     */
    public void switchCamera(int cameraID) {
        switchCamera(cameraID, DESIRED_PREVIEW_FPS);
    }

    /**
     * 切換相機
     * @param cameraId 相機Id
     * @param expectFps 期望幀率
     */
    public void switchCamera(int cameraId, int expectFps) {
        switchCamera(cameraId, expectFps, DEFAULT_WIDTH, DEFAULT_HEIGHT);
    }

    /**
     * 切換相機
     * @param cameraId      相機Id
     * @param expectFps     期望幀率
     * @param expectWidth   期望寬度
     * @param expectHeight  期望高度
     */
    public void switchCamera(int cameraId, int expectFps, int expectWidth, int expectHeight) {
        if (mCameraID == cameraId) {
            return;
        }
        mCameraID = cameraId;
        releaseCamera();
        openCamera(cameraId, expectFps, expectWidth, expectHeight);
    }

    /**
     * 切換相機并預(yù)覽
     * @param cameraId 相機Id
     * @param holder    SurfaceHolder
     * @param callback  回調(diào)
     * @param buffer    緩沖
     */
    public void switchCameraAndPreview(int cameraId, SurfaceHolder holder,
                                       Camera.PreviewCallback callback, byte[] buffer) {
        switchCamera(cameraId);
        setPreviewSurface(holder);
        setPreviewCallbackWithBuffer(callback, buffer);
        startPreview();
    }

    /**
     * 切換相機并預(yù)覽
     * @param cameraId 相機Id
     * @param texture    SurfaceTexture
     * @param callback  回調(diào)
     * @param buffer    緩沖
     */
    public void switchCameraAndPreview(int cameraId, SurfaceTexture texture,
                                       Camera.PreviewCallback callback, byte[] buffer) {
        switchCamera(cameraId);
        setPreviewSurface(texture);
        setPreviewCallbackWithBuffer(callback, buffer);
        startPreview();
    }

    /**
     * 釋放相機
     */
    public void releaseCamera() {
        if (mCamera != null) {
            mCamera.setPreviewCallbackWithBuffer(null);
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
    }

    /**
     * 拍照
     */
    public void takePicture(Camera.ShutterCallback shutterCallback,
                                   Camera.PictureCallback rawCallback,
                                   Camera.PictureCallback pictureCallback) {
        if (mCamera != null) {
            mCamera.takePicture(shutterCallback, rawCallback, pictureCallback);
        }
    }

    /**
     * 設(shè)置預(yù)覽大小
     * @param camera
     * @param expectWidth
     * @param expectHeight
     */
    private void setPreviewSize(Camera camera, int expectWidth, int expectHeight) {
        Camera.Parameters parameters = camera.getParameters();
        Camera.Size size = calculatePerfectSize(parameters.getSupportedPreviewSizes(),
                expectWidth, expectHeight);
        parameters.setPreviewSize(size.width, size.height);
        camera.setParameters(parameters);
        Log.d("setPreviewSize", "width = " + size.width + ", height = " + size.height);
    }

    /**
     * 設(shè)置拍攝的照片大小
     * @param camera
     * @param expectWidth
     * @param expectHeight
     */
    private void setPictureSize(Camera camera, int expectWidth, int expectHeight) {
        Camera.Parameters parameters = camera.getParameters();
        Camera.Size size = calculatePerfectSize(parameters.getSupportedPictureSizes(),
                expectWidth, expectHeight);
        parameters.setPictureSize(size.width, size.height);
        camera.setParameters(parameters);
        Log.d("setPictureSize", "width = " + size.width + ", height = " + size.height);
    }


    /**
     * 設(shè)置預(yù)覽角度,setDisplayOrientation本身只能改變預(yù)覽的角度
     * previewFrameCallback以及拍攝出來的照片是不會發(fā)生改變的纸型,拍攝出來的照片角度依舊不正常的
     * 拍攝的照片需要自行處理
     * 這里Nexus5X的相機簡直沒法吐槽牧氮,后置攝像頭倒置了丹莲,切換攝像頭之后就出現(xiàn)問題了。
     * @param activity
     */
    public int calculateCameraPreviewOrientation(Activity activity) {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(mCameraID, info);
        int rotation = activity.getWindowManager().getDefaultDisplay()
                .getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }

        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;
        } else {
            result = (info.orientation - degrees + 360) % 360;
        }
        mOrientation = result;
        return result;
    }

    //-------------------------------- setter and getter start -------------------------------------

    /**
     * 獲取當(dāng)前的Camera ID
     * @return
     */
    public int getCameraID() {
        return mCameraID;
    }

    /**
     * 獲取照片大小
     * @return
     */
    public Size getPictureSize() {
        if (mCamera != null) {
            Camera.Size size = mCamera.getParameters().getPictureSize();
            Size result = new Size(size.width, size.height);
            return result;
        }
        return new Size(0, 0);
    }

    /**
     * 獲取預(yù)覽大小
     * @return
     */
    public Size getPreviewSize() {
        if (mCamera != null) {
            Camera.Size size = mCamera.getParameters().getPreviewSize();
            Size result = new Size(size.width, size.height);
            return result;
        }
        return new Size(0, 0);
    }

    /**
     * 獲取相機信息
     * @return
     */
    public CameraInfo getCameraInfo() {
        if (mCamera != null) {
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(mCameraID, info);
            CameraInfo result = new CameraInfo(info.facing, info.orientation);
            return result;
        }
        return null;
    }

    /**
     * 獲取當(dāng)前預(yù)覽的角度
     * @return
     */
    public int getPreviewOrientation() {
        return mOrientation;
    }

    /**
     * 獲取FPS(千秒值)
     * @return
     */
    public int getCameraPreviewThousandFps() {
        return mCameraPreviewFps;
    }

    /**
     * 獲取當(dāng)前的寬高比
     * @return
     */
    public float getCurrentRatio() {
        return mCurrentRatio;
    }


    //---------------------------------- setter and getter end -------------------------------------

    /**
     * 計算最完美的Size
     * @param sizes
     * @param expectWidth
     * @param expectHeight
     * @return
     */
    private Camera.Size calculatePerfectSize(List<Camera.Size> sizes, int expectWidth,
                                                    int expectHeight) {
        sortList(sizes); // 根據(jù)寬度進行排序

        // 根據(jù)當(dāng)前期望的寬高判定
        List<Camera.Size> bigEnough = new ArrayList<>();
        List<Camera.Size> noBigEnough = new ArrayList<>();
        for (Camera.Size size : sizes) {
            if (size.height * expectWidth / expectHeight == size.width) {
                if (size.width >= expectWidth && size.height >= expectHeight) {
                    bigEnough.add(size);
                } else {
                    noBigEnough.add(size);
                }
            }
        }
        if (bigEnough.size() > 0) {
            return Collections.min(bigEnough, new CompareAreaSize());
        } else if (noBigEnough.size() > 0) {
            return Collections.max(noBigEnough, new CompareAreaSize());
        } else { // 如果不存在滿足要求的數(shù)值芝发,則輾轉(zhuǎn)計算寬高最接近的值
            Camera.Size result = sizes.get(0);
            boolean widthOrHeight = false; // 判斷存在寬或高相等的Size
            // 輾轉(zhuǎn)計算寬高最接近的值
            for (Camera.Size size : sizes) {
                // 如果寬高相等,則直接返回
                if (size.width == expectWidth && size.height == expectHeight
                        && ((float) size.height / (float) size.width) == mCurrentRatio) {
                    result = size;
                    break;
                }
                // 僅僅是寬度相等,計算高度最接近的size
                if (size.width == expectWidth) {
                    widthOrHeight = true;
                    if (Math.abs(result.height - expectHeight)
                            > Math.abs(size.height - expectHeight)
                            && ((float) size.height / (float) size.width) == mCurrentRatio) {
                        result = size;
                        break;
                    }
                }
                // 高度相等,則計算寬度最接近的Size
                else if (size.height == expectHeight) {
                    widthOrHeight = true;
                    if (Math.abs(result.width - expectWidth)
                            > Math.abs(size.width - expectWidth)
                            && ((float) size.height / (float) size.width) == mCurrentRatio) {
                        result = size;
                        break;
                    }
                }
                // 如果之前的查找不存在寬或高相等的情況落蝙,則計算寬度和高度都最接近的期望值的Size
                else if (!widthOrHeight) {
                    if (Math.abs(result.width - expectWidth)
                            > Math.abs(size.width - expectWidth)
                            && Math.abs(result.height - expectHeight)
                            > Math.abs(size.height - expectHeight)
                            && ((float) size.height / (float) size.width) == mCurrentRatio) {
                        result = size;
                    }
                }
            }
            return result;
        }
    }

    /**
     * 分辨率由大到小排序
     * @param list
     */
    private void sortList(List<Camera.Size> list) {
        Collections.sort(list, new CompareAreaSize());
    }

    /**
     * 比較器
     */
    private class CompareAreaSize implements Comparator<Camera.Size> {
        @Override
        public int compare(Camera.Size pre, Camera.Size after) {
            return Long.signum((long) pre.width * pre.height -
                    (long) after.width * after.height);
        }
    }

    /**
     * 選擇合適的FPS
     * @param parameters
     * @param expectedThoudandFps 期望的FPS
     * @return
     */
    private int chooseFixedPreviewFps(Camera.Parameters parameters, int expectedThoudandFps) {
        List<int[]> supportedFps = parameters.getSupportedPreviewFpsRange();
        for (int[] entry : supportedFps) {
            if (entry[0] == entry[1] && entry[0] == expectedThoudandFps) {
                parameters.setPreviewFpsRange(entry[0], entry[1]);
                return entry[0];
            }
        }
        int[] temp = new int[2];
        int guess;
        parameters.getPreviewFpsRange(temp);
        if (temp[0] == temp[1]) {
            guess = temp[0];
        } else {
            guess = temp[1] / 2;
        }
        return guess;
    }


}

接著管行,我們來看看控制Camera相機的HandlerThread線程,如下:

public class CameraHandlerThread extends HandlerThread {

    private static final String TAG = "CameraHandlerThread";

    private final Handler mHandler;

    public CameraHandlerThread() {
        super(TAG);
        start();
        mHandler = new Handler(getLooper());
    }

    public CameraHandlerThread(String name) {
        super(name);
        start();
        mHandler = new Handler(getLooper());
    }

    /**
     * 銷毀線程
     */
    public void destoryThread() {
        releaseCamera();
        mHandler.removeCallbacksAndMessages(null);
        quitSafely();
    }


    /**
     * 檢查handler是否可用
     */
    private void checkHandleAvailable() {
        if (mHandler == null) {
            throw new NullPointerException("Handler is not available!");
        }
    }

    /**
     * 等待操作完成
     */
    private void waitUntilReady() {
        try {
            wait();
        } catch (InterruptedException e) {
            Log.w(TAG, "wait was interrupted");
        }
    }

    /**
     * 打開相機
     */
    synchronized public void openCamera() {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalOpenCamera();
                notifyCameraOpened();
            }
        });
        waitUntilReady();
    }

    /**
     * 打開相機
     * @param expectFps 期望幀率
     */
    synchronized public void openCamera(final int expectFps) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalOpenCamera(expectFps);
                notifyCameraOpened();
            }
        });
        waitUntilReady();
    }

    /**
     * 打開相機
     * @param cameraId  相機Id
     * @param expectFps 期望幀率
     */
    synchronized public void openCamera(final int cameraId, final int expectFps) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalOpenCamera(cameraId, expectFps);
                notifyCameraOpened();
            }
        });
        waitUntilReady();
    }

    /**
     * 打開相機
     * @param cameraId      相機Id
     * @param expectFps     期望幀率
     * @param expectWidth   期望寬度
     * @param expectHeight  期望高度
     */
    synchronized public void openCamera(final int cameraId, final int expectFps,
                                        final int expectWidth, final int expectHeight) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalOpenCamera(cameraId, expectFps, expectWidth, expectHeight);
                notifyCameraOpened();
            }
        });
        waitUntilReady();
    }


    /**
     * 通知相機已打開叮姑,主要的作用是传透,如果在打開之后要立即獲得mCamera實例菠隆,則需要添加wait()-notify()
     * wait() - notify() 不是必須的
     */
    synchronized private void notifyCameraOpened() {
        notify();
    }


    /**
     * 重新打開相機
     */
    synchronized public void reopenCamera() {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalReopenCamera();
                notifyCameraOpened();
            }
        });
        waitUntilReady();
    }

    /**
     * 重新打開相機
     * @param expectFps 期望幀率
     */
    synchronized public void reopenCamera(final int expectFps) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalReopenCamera(expectFps);
                notifyCameraOpened();
            }
        });
        waitUntilReady();
    }


    /**
     * 重新打開相機
     * @param expectFps     期望幀率
     * @param expectWidth   期望寬度
     * @param expectHeight  期望高度
     */
    synchronized public void reopenCamera(final int expectFps,
                                          final int expectWidth, final int expectHeight) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalReopenCamera(expectFps, expectWidth, expectHeight);
                notifyCameraOpened();
            }
        });
        waitUntilReady();
    }

    /**
     * 設(shè)置預(yù)覽Surface
     * @param holder SurfaceHolder
     */
    public void setPreviewSurface(final SurfaceHolder holder) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalPreviewSurface(holder);
            }
        });
    }

    /**
     * 設(shè)置預(yù)覽Surface
     * @param texture   SurfaceTexture
     */
    public void setPreviewSurface(final SurfaceTexture texture) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalPreviewSurface(texture);
            }
        });
    }

    /**
     * 設(shè)置預(yù)覽回調(diào)
     * @param callback  回調(diào)
     * @param buffer    緩沖
     */
    public void setPreviewCallbackWithBuffer(final Camera.PreviewCallback callback,
                                             final byte[] buffer) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalPreviewCallbackWithBuffer(callback, buffer);
            }
        });
    }

    /**
     * 開始預(yù)覽
     */
    public void startPreview() {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalStartPreview();
            }
        });
    }

    /**
     * 停止預(yù)覽
     */
    public void stopPreview() {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalStopPreview();
            }
        });
    }

    /**
     * 切換相機
     * @param cameraId 相機Id
     */
    public void switchCamera(final int cameraId) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalSwitchCamera(cameraId);
            }
        });
    }

    /**
     * 切換相機
     * @param cameraId  相機Id
     * @param expectFps 期望幀率
     */
    public void switchCamera(final int cameraId, final int expectFps) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalSwitchCamera(cameraId, expectFps);
            }
        });
    }

    /**
     * 切換相機
     * @param cameraId      相機Id
     * @param expectFps     期望幀率
     * @param expectWidth   期望寬度
     * @param expectHeight  期望高度
     */
    public void switchCamera(final int cameraId, final int expectFps, final int expectWidth, final int expectHeight) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalSwitchCamera(cameraId, expectFps, expectWidth, expectHeight);
            }
        });
    }

    /**
     * 切換相機
     * @param cameraId
     * @param holder
     * @param callback
     * @param buffer
     */
    public void switchCameraAndPreview(final int cameraId, final SurfaceHolder holder,
                             final Camera.PreviewCallback callback, final byte[] buffer) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalSwitchCameraAndPreview(cameraId, holder, callback, buffer);
            }
        });
    }

    /**
     * 切換相機
     * @param cameraId
     * @param texture
     * @param callback
     * @param buffer
     */
    public void switchCameraAndPreview(final int cameraId, final SurfaceTexture texture,
                             final Camera.PreviewCallback callback, final byte[] buffer) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalSwitchCameraAndPreview(cameraId, texture, callback, buffer);
            }
        });
    }



    /**
     * 釋放相機
     */
    synchronized public void releaseCamera() {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalReleaseCamera();
                notifyCameraReleased();
            }
        });
        waitUntilReady();
    }

    /**
     * 通知銷毀成功
     */
    synchronized private void notifyCameraReleased() {
        notify();
    }

    /**
     * 拍照
     * @param shutterCallback
     * @param rawCallback
     * @param pictureCallback
     */
    public void takePicture(final Camera.ShutterCallback shutterCallback,
                            final Camera.PictureCallback rawCallback,
                            final Camera.PictureCallback pictureCallback) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalTakePicture(shutterCallback, rawCallback, pictureCallback);
            }
        });
    }

    /**
     * 計算預(yù)覽角度
     * @param activity
     */
    synchronized public void calculatePreviewOrientation(final Activity activity) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalCalculatePreviewOrientation(activity);
                notifyPreviewOrientationCalculated();
            }
        });

        try {
            wait();
        } catch (InterruptedException e) {
            Log.w(TAG, "wait was interrupted");
        }
    }

    /**
     * 通知計算預(yù)覽角度完成
     */
    synchronized private void notifyPreviewOrientationCalculated() {
        notify();
    }



    // ------------------------------- 內(nèi)部方法 -----------------------------
    /**
     * 打開相機
     */
    private void internalOpenCamera() {
        internalOpenCamera(CameraManager.getInstance().DESIRED_PREVIEW_FPS);
    }

    /**
     * 打開相機
     * @param expectFps 期望的幀率
     */
    private void internalOpenCamera(int expectFps) {
        CameraManager.getInstance().openCamera(expectFps);
    }

    /**
     * 打開相機
     * @param cameraId 相機Id
     * @param expectFps 期望幀率
     */
    private void internalOpenCamera(int cameraId, int expectFps) {
        CameraManager.getInstance().openCamera(cameraId, expectFps);
    }

    /**
     * 打開相機
     * @param cameraId      相機幀率
     * @param expectFps     期望幀率
     * @param expectWidth   期望寬度
     * @param expectHeight  期望高度
     */
    private void internalOpenCamera(int cameraId, int expectFps,
                                    int expectWidth, int expectHeight) {
        CameraManager.getInstance().openCamera(cameraId, expectFps, expectWidth, expectHeight);
    }

    /**
     * 重新打開相機
     */
    private void internalReopenCamera() {
        CameraManager.getInstance().reopenCamera();
    }

    /**
     * 重新打開相機
     * @param expectFps 期望幀率
     */
    private void internalReopenCamera(int expectFps) {
        CameraManager.getInstance().reopenCamera();
    }

    /**
     * 重新打開相機
     * @param expectFps 期望幀率
     * @param expectWidth   期望寬度
     * @param expectHeight  期望高度
     */
    public void internalReopenCamera(int expectFps, int expectWidth, int expectHeight) {
        CameraManager.getInstance().reopenCamera(expectFps, expectWidth, expectHeight);
    }

    /**
     * 預(yù)覽Surface
     * @param holder
     */
    private void internalPreviewSurface(SurfaceHolder holder) {
        CameraManager.getInstance().setPreviewSurface(holder);
    }

    /**
     * 預(yù)覽Surface
     * @param texture
     */
    private void internalPreviewSurface(SurfaceTexture texture) {
        CameraManager.getInstance().setPreviewSurface(texture);
    }

    /**
     * 設(shè)置預(yù)覽回調(diào)
     * @param callback  預(yù)覽回調(diào)
     * @param buffer    預(yù)覽回調(diào)
     */
    private void internalPreviewCallbackWithBuffer(Camera.PreviewCallback callback, byte[] buffer) {
        CameraManager.getInstance().setPreviewCallbackWithBuffer(callback, buffer);
    }

    /**
     * 開始預(yù)覽
     */
    private void internalStartPreview() {
        CameraManager.getInstance().startPreview();
    }

    /**
     * 停止預(yù)覽
     */
    private void internalStopPreview() {
        CameraManager.getInstance().stopPreview();
    }

    /**
     * 切換相機
     * @param cameraId  相機的Id
     */
    private void internalSwitchCamera(int cameraId) {
        CameraManager.getInstance().switchCamera(cameraId);
    }

    /**
     * 切換相機
     * @param cameraId 相機Id
     * @param expectFps 期望幀率
     */
    private void internalSwitchCamera(int cameraId, int expectFps) {
        CameraManager.getInstance().switchCamera(cameraId, expectFps);
    }

    /**
     * 切換相機
     * @param cameraId      相機Id
     * @param expectFps     期望幀率
     * @param expectWidth   期望寬度
     * @param expectHeight  期望高度
     */
    private void internalSwitchCamera(int cameraId, int expectFps,
                                      int expectWidth, int expectHeight) {
        CameraManager.getInstance().switchCamera(cameraId, expectFps, expectWidth, expectHeight);
    }

    /**
     * 切換相機并預(yù)覽
     * @param cameraId  相機的Id
     * @param holder    綁定的SurfaceHolder
     * @param callback  預(yù)覽回調(diào)
     * @param buffer    緩沖buffer
     */
    private void internalSwitchCameraAndPreview(int cameraId, SurfaceHolder holder,
                                                Camera.PreviewCallback callback, byte[] buffer) {
        CameraManager.getInstance().switchCameraAndPreview(cameraId, holder, callback, buffer);
    }

    /**
     * 切換相機并預(yù)覽
     * @param cameraId  相機Id
     * @param texture   綁定的SurfaceTexture
     * @param callback  預(yù)覽回調(diào)
     * @param buffer    緩沖buffer
     */
    private void internalSwitchCameraAndPreview(int cameraId, SurfaceTexture texture,
                                      Camera.PreviewCallback callback, byte[] buffer) {
        CameraManager.getInstance().switchCameraAndPreview(cameraId, texture, callback, buffer);
    }

    /**
     * 釋放相機
     */
    private void internalReleaseCamera() {
        CameraManager.getInstance().releaseCamera();
    }

    /**
     * 拍照
     * @param shutterCallback
     * @param rawCallback
     * @param pictureCallback
     */
    private void internalTakePicture(Camera.ShutterCallback shutterCallback,
                                     Camera.PictureCallback rawCallback,
                                     Camera.PictureCallback pictureCallback) {
        CameraManager.getInstance().takePicture(shutterCallback, rawCallback, pictureCallback);
    }

    /**
     * 計算預(yù)覽角度
     * @param activity
     */
    private void internalCalculatePreviewOrientation(Activity activity) {
        CameraManager.getInstance().calculateCameraPreviewOrientation(activity);
    }



    // ------------------------------------- setter and getter -------------------------------------

    /**
     * 獲取回調(diào)
     * @return
     */
    public Handler getHandler() {
        return mHandler;
    }

    /**
     * 獲取相機Id
     * @return
     */
    public int getCameraId() {
        return CameraManager.getInstance().getCameraID();
    }

    /**
     * 獲取照片的大小
     * @return
     */
    public Size getPictureSize() {
        return CameraManager.getInstance().getPictureSize();
    }

    /**
     * 獲取當(dāng)前預(yù)覽的大小
     * @return
     */
    public Size getPreviewSize() {
        return CameraManager.getInstance().getPreviewSize();
    }

    /**
     * 獲取相機信息
     * @return
     */
    public CameraInfo getCameraInfo() {
        return CameraManager.getInstance().getCameraInfo();
    }

    /**
     * 獲取當(dāng)前預(yù)覽角度
     * @return
     */
    public int getPreviewOrientation() {
        return CameraManager.getInstance().getPreviewOrientation();
    }

    /**
     * 獲取幀率(FPS 千秒值)
     * @return
     */
    public int getCameraPreviewThousandFps() {
        return CameraManager.getInstance().getCameraPreviewThousandFps();
    }

    /**
     * 獲取當(dāng)前的長寬比
     * @return
     */
    public float getCurrentRatio() {
        return CameraManager.getInstance().getCurrentRatio();
    }

}

然后,我們來看看渲染管理器RenderManager:

public final class RenderManager {

    private static final String TAG = "RenderManager";

    private static RenderManager mInstance;

    private static Object mSyncObject = new Object();

    // 是否允許繪制人臉關(guān)鍵點
    private boolean enableDrawPoints = false;

    // 相機輸入流濾鏡
    private CameraFilter mCameraFilter;
    // 實時濾鏡組
    private BaseImageFilterGroup mRealTimeFilter;
    // 關(guān)鍵點繪制(調(diào)試用)
    private FacePointsDrawer mFacePointsDrawer;
    // 頂點和UV坐標(biāo)緩沖
    private FloatBuffer mVertexBuffer;
    private FloatBuffer mTextureBuffer;

    // 輸入流大小
    private int mTextureWidth;
    private int mTextureHeight;
    // 顯示大小
    private int mDisplayWidth;
    private int mDisplayHeight;

    // 顯示的縮放裁剪類型
    private ScaleType mScaleType = ScaleType.CENTER_CROP;



    public static RenderManager getInstance() {
        if (mInstance == null) {
            mInstance = new RenderManager();
        }
        return mInstance;
    }

    private RenderManager() {
    }

    /**
     * 初始化
     */
    public void init() {
        // 釋放之前的濾鏡和緩沖
        releaseBuffers();
        releaseFilters();
        // 初始化濾鏡和緩沖
        initBuffers();
        initFilters();
    }

    /**
     * 初始化濾鏡
     */
    private void initFilters() {
        mCameraFilter = new CameraFilter();
        mFacePointsDrawer = new FacePointsDrawer();
//        mRealTimeFilter = FilterManager.getFilterGroup();
    }

    /**
     * 初始化緩沖
     */
    private void initBuffers() {
        mVertexBuffer = ByteBuffer
                .allocateDirect(TextureRotationUtils.CubeVertices.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        mVertexBuffer.put(TextureRotationUtils.CubeVertices).position(0);
        mTextureBuffer = ByteBuffer
                .allocateDirect(TextureRotationUtils.getTextureVertices().length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        mTextureBuffer.put(TextureRotationUtils.getTextureVertices()).position(0);
    }

    /**
     * 釋放資源
     */
    public void release() {
        releaseFilters();
        releaseBuffers();
    }

    /**
     * 釋放Filters資源
     */
    private void releaseFilters() {
        if (mCameraFilter != null) {
            mCameraFilter.release();
            mCameraFilter = null;
        }
        if (mFacePointsDrawer != null) {
            mFacePointsDrawer.release();
            mFacePointsDrawer = null;
        }
        if (mRealTimeFilter != null) {
            mRealTimeFilter.release();
            mRealTimeFilter = null;
        }
    }

    /**
     * 釋放緩沖資源
     */
    private void releaseBuffers() {
        if (mVertexBuffer != null) {
            mVertexBuffer.clear();
            mVertexBuffer = null;
        }
        if (mTextureBuffer != null) {
            mTextureBuffer.clear();
            mTextureBuffer = null;
        }
    }

    /**
     * 渲染Texture的大小
     * @param width
     * @param height
     */
    public void onInputSizeChanged(int width, int height) {
        mTextureWidth = width;
        mTextureHeight = height;
        if (mCameraFilter != null) {
            mCameraFilter.onInputSizeChanged(width, height);
        }
        if (mRealTimeFilter != null) {
            mRealTimeFilter.onInputSizeChanged(width, height);
        }
    }

    /**
     * Surface顯示的大小
     * @param width
     * @param height
     */
    public void onDisplaySizeChanged(int width, int height) {
        mDisplayWidth = width;
        mDisplayHeight = height;
        cameraFilterChanged();
        if (mRealTimeFilter != null) {
            mRealTimeFilter.onDisplayChanged(width, height);
        }
        // 調(diào)整視圖大小
        adjustViewSize();
    }


    /**
     * 更新filter
     * @param type Filter類型
     */
    public void changeFilter(FilterType type) {
        if (mRealTimeFilter != null) {
            mRealTimeFilter.changeFilter(type);
        }
    }

    /**
     * 切換濾鏡組
     * @param type
     */
    public void changeFilterGroup(FilterGroupType type) {
        synchronized (mSyncObject) {
            if (mRealTimeFilter != null) {
                mRealTimeFilter.release();
            }
            mRealTimeFilter = FilterManager.getFilterGroup(type);
            mRealTimeFilter.onInputSizeChanged(mTextureWidth, mTextureHeight);
            mRealTimeFilter.onDisplayChanged(mDisplayWidth, mDisplayHeight);
        }
    }

    /**
     * 繪制渲染
     * @param textureId
     */
    public void drawFrame(int textureId) {
        if (mRealTimeFilter == null) {
            mCameraFilter.drawFrame(textureId);
        } else {
            int id = mCameraFilter.drawFrameBuffer(textureId);
            mRealTimeFilter.drawFrame(id, mVertexBuffer, mTextureBuffer);
        }
        // 是否繪制點
        if (enableDrawPoints && mFacePointsDrawer != null) {
            mFacePointsDrawer.drawPoints();
        }
    }

    /**
     * 調(diào)整由于surface的大小與SurfaceView顯示大小不一致帶來的顯示問題
     */
    private void adjustViewSize() {
        float[] textureCoords = null;
        float[] vertexCoords = null;
        // TODO 這里可以做成鏡像翻轉(zhuǎn)的
        float[] textureVertices = TextureRotationUtils.getTextureVertices();
        float[] vertexVertices = TextureRotationUtils.CubeVertices;
        float ratioMax = Math.max((float) mDisplayWidth / mTextureWidth,
                (float) mDisplayHeight / mTextureHeight);
        // 新的寬高
        int imageWidth = Math.round(mTextureWidth * ratioMax);
        int imageHeight = Math.round(mTextureHeight * ratioMax);
        // 獲取視圖跟texture的寬高比
        float ratioWidth = (float) imageWidth / (float) mTextureWidth;
        float ratioHeight = (float) imageHeight / (float) mTextureHeight;
        if (mScaleType == ScaleType.CENTER_INSIDE) {
            vertexCoords = new float[] {
                    vertexVertices[0] / ratioHeight, vertexVertices[1] / ratioWidth, vertexVertices[2],
                    vertexVertices[3] / ratioHeight, vertexVertices[4] / ratioWidth, vertexVertices[5],
                    vertexVertices[6] / ratioHeight, vertexVertices[7] / ratioWidth, vertexVertices[8],
                    vertexVertices[9] / ratioHeight, vertexVertices[10] / ratioWidth, vertexVertices[11],
            };
        } else if (mScaleType == ScaleType.CENTER_CROP) {
            float distHorizontal = (1 - 1 / ratioWidth) / 2;
            float distVertical = (1 - 1 / ratioHeight) / 2;
            textureCoords = new float[] {
                    addDistance(textureVertices[0], distVertical), addDistance(textureVertices[1], distHorizontal),
                    addDistance(textureVertices[2], distVertical), addDistance(textureVertices[3], distHorizontal),
                    addDistance(textureVertices[4], distVertical), addDistance(textureVertices[5], distHorizontal),
                    addDistance(textureVertices[6], distVertical), addDistance(textureVertices[7], distHorizontal),
            };
        }
        if (vertexCoords == null) {
            vertexCoords = vertexVertices;
        }
        if (textureCoords == null) {
            textureCoords = textureVertices;
        }
        // 更新VertexBuffer 和 TextureBuffer
        mVertexBuffer.clear();
        mVertexBuffer.put(vertexCoords).position(0);
        mTextureBuffer.clear();
        mTextureBuffer.put(textureCoords).position(0);
    }

    /**
     * 計算距離
     * @param coordinate
     * @param distance
     * @return
     */
    private float addDistance(float coordinate, float distance) {
        return coordinate == 0.0f ? distance : 1 - distance;
    }

    /**
     * 濾鏡或視圖發(fā)生變化時調(diào)用
     */
    private void cameraFilterChanged() {
        if (mDisplayWidth != mDisplayHeight) {
            mCameraFilter.onDisplayChanged(mDisplayWidth, mDisplayHeight);
        }
        mCameraFilter.initFramebuffer(mTextureWidth, mTextureHeight);
    }

    //------------------------------ setter and getter ---------------------------------

    /**
     * 設(shè)置頂點坐標(biāo)緩沖
     * @param buffer
     */
    public void setVertexBuffer(FloatBuffer buffer) {
        mVertexBuffer = buffer;
    }

    /**
     *  設(shè)置UV坐標(biāo)緩沖
     * @param buffer
     */
    public void setTextureBuffer(FloatBuffer buffer) {
        mTextureBuffer = buffer;
    }

    /**
     * 設(shè)置SurfaceTexture 的Transform矩陣
     * @param matrix
     */
    public void setTransformMatrix(float[] matrix) {
        mCameraFilter.setTextureTransformMatirx(matrix);
    }


}

其中传惠,CameraFilter 的實現(xiàn)可以參考本人的相機項目CainCamera迄沫。這里并沒有開啟實時磨皮渲染等操作盼砍,僅僅是繪制渲染了Camera的相機流。
我們再來看看渲染線程如何實現(xiàn):

public class RenderTestThread extends HandlerThread implements SurfaceTexture.OnFrameAvailableListener {
    private static final String TAG = "RenderTestThread";

    private final Handler mHandler;

    private EglCore mEglCore;
    private WindowSurface mDisplaySurface;
    private SurfaceTexture mSurfaceTexture;
    private final float[] mMatrix = new float[16];

    private int mCameraTexture;

    private int mImageWidth;
    private int mImageHeight;
    // 預(yù)覽的角度
    private int mOrientation;

    public RenderTestThread() {
        super(TAG);
        start();
        mHandler = new Handler(getLooper());
    }

    public RenderTestThread(String name) {
        super(name);
        start();
        mHandler = new Handler(getLooper());
    }

    /**
     * 銷毀線程
     */
    public void destoryThread() {
        internalRelease();
        mHandler.removeCallbacksAndMessages(null);
        quitSafely();
    }

    /**
     * 檢查handler是否可用
     */
    private void checkHandleAvailable() {
        if (mHandler == null) {
            throw new NullPointerException("Handler is not available!");
        }
    }

    /**
     * 等待
     */
    private void waitUntilReady() {
        try {
            wait();
        } catch (InterruptedException e) {
            Log.w(TAG, "wait was interrupted");
        }
    }

    synchronized public void surfaceCreated(final SurfaceHolder holder) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalSurfaceCreated(holder);
                notifySurfaceProcessed();
            }
        });
        waitUntilReady();
    }

    synchronized public void surfaceChanged(final int width, final int height) {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalSurfaceChanged(width, height);
                notifySurfaceProcessed();
            }
        });
        waitUntilReady();
    }

    synchronized public void surfaceDestory() {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalSurfaceDestory();
                notifySurfaceProcessed();
            }
        });
        waitUntilReady();
    }

    /**
     * Surface變化處理完成通知
     */
    synchronized private void notifySurfaceProcessed() {
        notify();
    }

    /**
     * 設(shè)置圖片大小
     * @param width         寬度
     * @param height        高度
     * @param orientation   角度
     */
    public void setImageSize(int width, int height, int orientation) {
        mImageWidth = width;
        mImageHeight = height;
        mOrientation = orientation;
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalupdateImageSize();
            }
        });
    }

    /**
     * 更新幀
     */
    public void updateFrame() {
        checkHandleAvailable();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                internalRendering();
            }
        });
    }

    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
//        updateFrame();
    }

    // ------------------------------ 內(nèi)部方法 -------------------------------------

    private void internalSurfaceCreated(SurfaceHolder holder) {
        mEglCore = new EglCore(null, EglCore.FLAG_RECORDABLE | EglCore.FLAG_TRY_GLES3);
        mDisplaySurface = new WindowSurface(mEglCore, holder.getSurface(), false);
        mDisplaySurface.makeCurrent();
        mCameraTexture = GlUtil.createTextureOES();
        mSurfaceTexture = new SurfaceTexture(mCameraTexture);
        RenderManager.getInstance().init();
    }

    private void internalSurfaceChanged(int width, int height) {
        RenderManager.getInstance().onDisplaySizeChanged(width, height);
    }

    private void internalSurfaceDestory() {
        internalRelease();
    }

    /**
     * 釋放所有資源
     */
    private void internalRelease() {
        if (mEglCore != null) {
            mEglCore.release();
        }
        if (mDisplaySurface != null) {
            mDisplaySurface.release();
        }
        if (mSurfaceTexture != null) {
            mSurfaceTexture.release();
        }
        RenderManager.getInstance().release();
    }

    /**
     * 更新圖片大小(相機流大小)
     */
    private void internalupdateImageSize() {
        calculateImageSize();
        RenderManager.getInstance().onInputSizeChanged(mImageWidth, mImageHeight);
    }

    /**
     * 計算image的寬高
     */
    private void calculateImageSize() {
        Log.d("calculateImageSize", "orientation = " + mOrientation);
        if (mOrientation == 90 || mOrientation == 270) {
            int temp = mImageWidth;
            mImageWidth = mImageHeight;
            mImageHeight = temp;
        }
    }
    /**
     * 渲染
     */
    private void internalRendering() {
        mDisplaySurface.makeCurrent();
        if (mSurfaceTexture != null) {
            mSurfaceTexture.updateTexImage();
        }
        renderFrame();
        mDisplaySurface.swapBuffers();
    }

    /**
     * 渲染一幀
     */
    private void renderFrame() {
        mSurfaceTexture.getTransformMatrix(mMatrix);
        RenderManager.getInstance().setTransformMatrix(mMatrix);
        RenderManager.getInstance().drawFrame(mCameraTexture);
    }

    // ------------------------------- setter and getter ---------------------------

    /**
     * 獲取SurfaceTexture
     * @return
     */
    public SurfaceTexture getSurafceTexture() {
        return mSurfaceTexture;
    }

}

然后在Activity里面控制打開渲染操作:

public class CameraTestActivity extends AppCompatActivity implements SurfaceHolder.Callback,
        Camera.PreviewCallback {

    private static final int REQUEST_CAMERA = 0x01;
    private static final int REQUEST_STORAGE_READ = 0x02;
    private static final int REQUEST_STORAGE_WRITE = 0x03;
    private static final int REQUEST_RECORD = 0x04;
    private static final int REQUEST_LOCATION = 0x05;

    private AspectFrameLayout mAspectLayout;

    private SurfaceView mSurfaceView;
    // 相機控制線程
    private CameraHandlerThread mCameraThread;
    // 渲染控制線程
    private RenderTestThread mRenderThread;

    private byte[] mPreviewBuffer;

    private boolean mCameraEnable = false;
    private boolean mStorageWriteEnable = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera_test);

        mCameraEnable = PermissionUtils.permissionChecking(this,
                Manifest.permission.CAMERA);
        mStorageWriteEnable = PermissionUtils.permissionChecking(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE);
        if (mCameraEnable && mStorageWriteEnable) {
            initView();
        } else {
            ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.CAMERA,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE }, REQUEST_CAMERA);
        }

    }

    /**
     * 初始化視圖
     */
    private void initView() {
        mAspectLayout = (AspectFrameLayout) findViewById(R.id.layout_aspect);
        mAspectLayout.setAspectRatio(CameraManager.getInstance().getCurrentRatio());

        mSurfaceView = new SurfaceView(this);
        mSurfaceView.getHolder().addCallback(this);

        mAspectLayout.addView(mSurfaceView);
        mAspectLayout.requestLayout();
    }

    @Override
    protected void onResume() {
        super.onResume();
        mCameraThread = new CameraHandlerThread();
        mCameraThread.calculatePreviewOrientation(this);
        mRenderThread = new RenderTestThread();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            // 相機權(quán)限
            case REQUEST_CAMERA:
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mCameraEnable = true;
                    initView();
                }
                break;

            // 存儲權(quán)限
            case REQUEST_STORAGE_WRITE:
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mStorageWriteEnable = true;
                }
                break;
        }
    }


    @Override
    public void surfaceCreated(SurfaceHolder holder) {

        // CameraThread是一個HandlerThread翔烁,調(diào)用Camera.open相機,onPreviewFrame回調(diào)所綁定的線程是CameraThread虾攻,
        // 如果使用new Thread的方式調(diào)用Camera.open相機,onPreviewFrame回調(diào)將綁定到到MainLooper蒂胞,也就是回調(diào)到主線程
        // 這里使用單獨的HandlerThread控制Camera邏輯
        mCameraThread.openCamera();

        Size previewSize = mCameraThread.getPreviewSize();
        int size = previewSize.getWidth() * previewSize.getHeight() * 3 / 2;
        mPreviewBuffer = new byte[size];

        mRenderThread.surfaceCreated(holder);
        mRenderThread.setImageSize(previewSize.getWidth(), previewSize.getHeight(),
                mCameraThread.getPreviewOrientation());

        // 設(shè)置預(yù)覽SurfaceTexture
        mCameraThread.setPreviewSurface(mRenderThread.getSurafceTexture());
        // 設(shè)置回調(diào)
        mCameraThread.setPreviewCallbackWithBuffer(CameraTestActivity.this, mPreviewBuffer);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        mCameraThread.startPreview();
        mRenderThread.surfaceChanged(width, height);
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mCameraThread.destoryThread();
        mRenderThread.destoryThread();
    }

    /**
     測試情況:
     高通:
     VIVO X9i涨椒,高通625
     紅米Note 4X获三,高通625
     Nexus 5X,高通808
     onPreviewFrame回調(diào)時間: 單一 HandlerThread耐亏, 30~40ms几睛; 雙HandlerThread,30~40ms
     preview size: 1280 x 720
     單一HandlerThread的情況上鞠,請參考CameraActivity里面的情況

     聯(lián)發(fā)科:
     紅米Note4蕊程, 聯(lián)發(fā)科 Helio X20
     魅藍Note 2,聯(lián)發(fā)科 MTK6573
     樂視 X620,聯(lián)發(fā)科X20
     onPreviewFrame回調(diào)時間:單一HandlerThread撕蔼, 30 ~ 40ms视译;雙HandlerThread呀舔,60~70ms
     preview size: 1280 x 720

     操作:
     Camera數(shù)據(jù)流渲染到SurfaceTexture顯示到SurfaceView上,設(shè)置setPreviewCallbackWithBuffer,查看onPreviewFrame的幀率
     */
    long time = 0;
    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        Log.d("onPreviewFrame", "update time = " + (System.currentTimeMillis() - time));
        time = System.currentTimeMillis();
        mRenderThread.updateFrame();

        if (mPreviewBuffer != null) {
            camera.addCallbackBuffer(mPreviewBuffer);
        }
    }

}

至此膘流,把以上代碼運行在高通的CPU上和MTK的CPU上缠局,就會發(fā)現(xiàn)赃蛛,MTK的CPU的onPreviewFrame回調(diào)的時間大約在 60~70ms 左右搀菩,而高通的CPU 的 onPreviewFrame回調(diào)的時間大約在30 ~ 40ms左右呕臂。MTK的CPU 預(yù)覽畫面出現(xiàn)了明顯的不連貫感。
如果我們把相機控制和渲染的操作均放在同一個HandlerThread肪跋,你會發(fā)現(xiàn)歧蒋,MTK CPU的onPreviewFrame 回調(diào)的時間跟高通的表現(xiàn)一致,在MTK的X20等旗艦級的CPU上州既,做完磨皮算法谜洽,onPreviewFrame的回調(diào)時間也能保持在30~40ms內(nèi),詳細(xì)的寫法可以參考本人的相機項目吴叶,并且在接入了Face++的人臉檢測SDK后阐虚,依舊能保證onPreviewFrame回調(diào)時間比市面上幾乎所有商業(yè)相機的回調(diào)時間要短。在做完磨皮蚌卤、顏色濾鏡的前提下敌呈,在紅米Note 2 (MTK X10)上渲染幀率穩(wěn)定在19.5幀左右,而市面上幾乎所有相機均存在不同程度的掉幀現(xiàn)象造寝,均不能穩(wěn)定保持在19幀以上磕洪。

接下來我想談一談人臉檢測對onPreviewFrame回調(diào)幀率的影響。市面上的人臉檢測SDK 在做人臉關(guān)鍵點檢測的時間有長有短诫龙,那么什么樣的方案對onPreviewFrame的回調(diào)幀率不怎么影響呢析显?我認(rèn)為,理想狀態(tài)下的方案是應(yīng)該是签赃,人臉檢測放在另外一個單獨的HandlerThread里面谷异,每次做完檢測分尸,回調(diào)到RenderThread通知渲染線程繪制新的一幀, 這樣人臉檢測和渲染操作并行處理歹嘹,onPreviewFrame將圖像數(shù)據(jù)data 通過handler 傳遞給人臉檢測的HandlerThread箩绍,然后調(diào)用camera的addCallbackBuffer,回傳緩沖尺上。這樣當(dāng)我們渲染完成材蛛,可以立馬進入下一次的人臉檢測,人臉檢測完成回調(diào)里面通知渲染刷新怎抛,因此卑吭,onPreviewFrame的幀率可以一直得到保證,而人臉檢測和渲染操作也能得到并行化處理马绝,此時的幀率是 1000 / (Max(人臉檢測, 渲染操作)(ms))豆赏。理論上,如果人臉檢測的時間小于30ms富稻,可以實現(xiàn)實時預(yù)覽幀率接近30幀掷邦。可以參考本人的相機項目:CainCamera椭赋,face++ 的人臉SDK 1280x720的分辨率下抚岗,在高通625上,單人臉106個關(guān)鍵點纹份,大約在20ms左右苟跪,也就是說廷痘,在下一幀來臨之前就能得到人臉關(guān)鍵點蔓涧,然后回調(diào)做磨皮處理、繪制濾鏡等操作笋额。我這樣的方案元暴,從人臉檢測到磨皮渲染處理、渲染濾鏡等操作完成兄猩,在高通808上的處理時間和高通625上的表現(xiàn)相當(dāng)茉盏,MTK X10上的時間大約為50ms左右,下圖是在高通808上的從人臉檢測到渲染一幀完成所需要的時間:

繪制一幀的時間

從上面可以看到枢冤,人臉關(guān)鍵點檢測的時間在小于30ms的時候鸠姨,并行化的方案是可以做到接近30幀的預(yù)覽效率的。在這樣的前提下淹真,做瘦臉讶迁、貼紙等渲染處理,實時渲染預(yù)覽的幀率在大部分手機上都能保證在20幀以上核蘸,超過20幀對相機預(yù)覽畫面來說巍糯,基本沒有不連貫感了啸驯。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市祟峦,隨后出現(xiàn)的幾起案子罚斗,更是在濱河造成了極大的恐慌,老刑警劉巖宅楞,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件针姿,死亡現(xiàn)場離奇詭異,居然都是意外死亡咱筛,警方通過查閱死者的電腦和手機搓幌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來迅箩,“玉大人溉愁,你說我怎么就攤上這事∷乔鳎” “怎么了拐揭?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長奕塑。 經(jīng)常有香客問我堂污,道長,這世上最難降的妖魔是什么龄砰? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任盟猖,我火速辦了婚禮,結(jié)果婚禮上换棚,老公的妹妹穿的比我還像新娘式镐。我一直安慰自己,他們只是感情好固蚤,可當(dāng)我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布娘汞。 她就那樣靜靜地躺著,像睡著了一般夕玩。 火紅的嫁衣襯著肌膚如雪你弦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天燎孟,我揣著相機與錄音禽作,去河邊找鬼。 笑死揩页,一個胖子當(dāng)著我的面吹牛旷偿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼狸捅,長吁一口氣:“原來是場噩夢啊……” “哼衷蜓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起尘喝,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤磁浇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后朽褪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體置吓,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年缔赠,在試婚紗的時候發(fā)現(xiàn)自己被綠了衍锚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡嗤堰,死狀恐怖戴质,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情踢匣,我是刑警寧澤告匠,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站离唬,受9級特大地震影響后专,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜输莺,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一戚哎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嫂用,春花似錦型凳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽殷蛇。三九已至实夹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間粒梦,已是汗流浹背亮航。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留匀们,地道東北人缴淋。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親重抖。 傳聞我的和親對象是個殘疾皇子露氮,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,440評論 2 348

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