在之前嫁蛇,本人寫了一篇文章(關(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ù)覽畫面來說巍糯,基本沒有不連貫感了啸驯。