我设法使用自定义滤镜(灰度,色调等)进行相机预览。此自定义过滤器通过操作RGB数组然后将其绘制回画布然后在曲面视图中显示来应用预览回调。
这样做的缺点是我的FPS非常低。有了这个低FPS,如果我不使用Asynctask在后台线程中执行此操作,它在UI线程中做了太多工作。所以我尝试使用Asynctask进行相机操作(我的主要目的是让UI仍然能够完美地工作,即使是相机预览回调中的繁重工作)。
但即使在我使用Asynctask之后,它也没有多大帮助。所以我想知道我的实现是错误的还是因为即使使用asynctask,UI线程仍会受到影响?
我的代码片段如下:
CameraActivity.java
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("ACTIVITY_LIFECYCLE","CameraActivity: onCreate");
setContentView(R.layout.camera_layout);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
protected void onResume() {
Log.d("ACTIVITY_LIFECYCLE","CameraActivity: onResume");
if(preview == null){
preview = new CameraPreviewAsync(this,camera);
preview.execute();
}
super.onResume();
}
@Override
protected void onPause() {
Log.d("ACTIVITY_LIFECYCLE","CameraActivity: onPause");
if(preview!=null){
preview.cancel(true);
camera = preview.getCamera();
if(camera!=null){
camera.stopPreview();
camera.setPreviewCallback(null);
camera.release();
camera = null;
preview.setCamera(camera);
}
preview = null;
}
super.onPause();
}
@Override
public void onDestroy(){
Log.d("ACTIVITY_LIFECYCLE","CameraActivity: onDestroy");
super.onDestroy();
}
CameraPreviewAsync.java:
private final String TAG = "CameraPreviewAsync";
private CameraActivity camAct;
private Camera mCamera;
private int cameraId;
private SurfaceView mSurfaceView;
private SurfaceHolder mHolder;
private boolean isPreviewRunning = false;
private int[] rgbints;
private int width;
private int height;
private Bitmap mBitmap;
public CameraPreviewAsync(CameraActivity act, Camera cam){
this.camAct = act;
this.mCamera = cam;
this.mSurfaceView = (SurfaceView) act.findViewById(R.id.surfaceView);
}
public void resetSurface(){
if(mCamera!=null){
mCamera.stopPreview();
mCamera.setPreviewCallback(null);
mCamera.release();
mCamera = null;
}
int tempId = R.id.surfaceView;
RelativeLayout buttonBar = (RelativeLayout) camAct.findViewById(R.id.buttonBar);
((RelativeLayout) camAct.findViewById(R.id.preview)).removeAllViews();
SurfaceView newSurface = new SurfaceView(camAct);
newSurface.setId(tempId);
RelativeLayout.LayoutParams layParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
layParams.alignWithParent = true;
newSurface.setLayoutParams(layParams);
((RelativeLayout) camAct.findViewById(R.id.preview)).addView(newSurface);
((RelativeLayout) camAct.findViewById(R.id.preview)).addView(buttonBar);
}
@Override
protected void onPreExecute() {
//Things to do before doInBackground executed
Log.d(TAG,"onPreExecute");
RelativeLayout.LayoutParams layParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
layParams.alignWithParent = true;
mSurfaceView.setLayoutParams(layParams);
//Check number of camera in the device, if less than 2 then remove swap button
if (Camera.getNumberOfCameras() < 2) {
((RelativeLayout) camAct.findViewById(R.id.buttonBar)).removeViewAt(R.id.cameraSwap);
}
//Opening the camera
cameraId = findBackFacingCamera();
if (cameraId < 0) {
cameraId = findFrontFacingCamera();
if (cameraId < 0)
Toast.makeText(camAct, "No camera found.", Toast.LENGTH_LONG).show();
else
mCamera = Camera.open(cameraId);
} else {
mCamera = Camera.open(cameraId);
}
//invalidate the menu bar and show menu appropriately
camAct.invalidateOptionsMenu();
// get Camera parameters and set it to Auto Focus
if(mCamera!=null){
Camera.Parameters params = mCamera.getParameters();
List<String> focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
// set the focus mode
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// set Camera parameters
mCamera.setParameters(params);
}
}
super.onPreExecute();
}
@Override
protected Void doInBackground(Void... params) {
//Things to do in the background thread
Log.d(TAG,"doInBackground");
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(surfaceCallback);
return null;
}
@Override
protected void onPostExecute(Void values) {
//Things to do after doInBackground
Log.d(TAG,"onPostExecute");
}
@Override
protected void onCancelled(){
super.onCancelled();
}
/*
* ************************************************************************************
* SURFACEHOLDER CALLBACK
* ************************************************************************************
*/
SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d(TAG,"surfaceCreated!!");
if(CameraActivity.filterMode == CameraActivity.NORMAL_FILTER){
try {
if (mCamera != null) {
mCamera.startPreview();
mCamera.setPreviewDisplay(holder);
}else{
Log.d(TAG,"CAMERA IS NULL in surfaceCreated!!");
}
} catch (IOException exception) {
Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
}
}else{
synchronized(mSurfaceView){
if(isPreviewRunning){
return;
}else{
mSurfaceView.setWillNotDraw(false);
if(mCamera!=null){
isPreviewRunning = true;
Camera.Parameters p = mCamera.getParameters();
List<Size> sizes = p.getSupportedPreviewSizes();
Size size = p.getPreviewSize();
width = size.width;
height = size.height;
p.setPreviewFormat(ImageFormat.NV21);
showSupportedCameraFormats(p);
mCamera.setParameters(p);
rgbints = new int[width * height];
mCamera.startPreview();
mCamera.setPreviewCallback(previewCallback);
}
}
}
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG,"surfaceDestroyed!");
if(CameraActivity.filterMode == CameraActivity.NORMAL_FILTER){
if (mCamera != null) {
mCamera.stopPreview();
isPreviewRunning = false;
}
}else{
synchronized(mSurfaceView){
if(mCamera!=null){
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
isPreviewRunning = false;
}
}
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
Log.d(TAG,"surfaceChanged!");
}
};
/*
* ************************************************************************************
* CAMERA PREVIEW CALLBACK
* ************************************************************************************
*/
Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if (!isPreviewRunning)
return;
Canvas resCanvas = null;
if (mHolder == null) {
return;
}
try {
synchronized (mHolder) {
resCanvas = mHolder.lockCanvas(null);
int resCanvasW = resCanvas.getWidth();
int resCanvasH = resCanvas.getHeight();
if(mBitmap == null){
mBitmap = Bitmap.createBitmap (width, height, Bitmap.Config.ARGB_8888);
}
decodeYUV(rgbints, data, width, height);
Canvas canvas = new Canvas(mBitmap);
//Setting the filter
if(camAct.getCustomFilter().equalsIgnoreCase("NORMAL")) ;//don't change the rgb value
if(camAct.getCustomFilter().equalsIgnoreCase("GRAYSCALE")) rgbints = grayscale(rgbints);
if(camAct.getCustomFilter().equalsIgnoreCase("INVERT")) rgbints = invert(rgbints);
if(camAct.getCustomFilter().equalsIgnoreCase("BOOSTRED")) rgbints = boostColor(rgbints,1);
if(camAct.getCustomFilter().equalsIgnoreCase("BOOSTGREEN")) rgbints = boostColor(rgbints,2);
if(camAct.getCustomFilter().equalsIgnoreCase("BOOSTBLUE")) rgbints = boostColor(rgbints,3);
if(camAct.getCustomFilter().equalsIgnoreCase("NOISE")) rgbints = noise(rgbints);
if(camAct.getCustomFilter().equalsIgnoreCase("HUE")) rgbints = hue(rgbints);
if(camAct.getCustomFilter().equalsIgnoreCase("SATURATION")) rgbints = saturation(rgbints);
if(camAct.getCustomFilter().equalsIgnoreCase("ENGRAVE")) rgbints = engrave(rgbints);
if(camAct.getCustomFilter().equalsIgnoreCase("EMBOSS")) rgbints = emboss(rgbints);
// draw the decoded image, centered on canvas
canvas.drawBitmap(rgbints, 0, width, 0,0, width, height, false, null);
resCanvas.drawBitmap (mBitmap, resCanvasW-((width+resCanvasW)>>1), resCanvasH-((height+resCanvasH)>>1),null);
}
} catch (Exception e){
e.printStackTrace();
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (resCanvas != null) {
mHolder.unlockCanvasAndPost(resCanvas);
}
}
}
};
非常感谢任何帮助! :)先谢谢你们!
答案 0 :(得分:21)
也许我的答案对你来说太晚了,但我正在研究同一个主题,所以我认为我会分享我的发现......
首先,如果在AsyncTask上调用Camera的“open”,然后该线程存在并且抓住存在 - 我们真的不能期望回调来自它,我们可以。所以,如果我们想要回调 - 那么我们需要一个至少与我们想要回调一样长的线程。
但等等,还有更多... Camera.PreviewCallback的文档不是最清楚的,但其中一个不好的提示是“这个回调是在事件线程open(int)上调用的被叫来。“他们对“事件”线程的意义是什么?好吧,它不是很清楚 - 但是看看Android代码和试验 - 他们需要的是一个包含Looper的线程。可能是太多的细节,但在Camera构造函数(从open方法调用)中有代码试图首先获取当前线程的Looper,如果不存在 - 它试图获取主线程looper - 它继续存在UI线程。 Camera然后使用Handler来调度回调,并通过它初始化的looper调度其他方法。现在你可以看到为什么你在主线程上收到你的回叫,即使你从另一个线程打开相机 - 你的工作线程没有一个弯针 - 所以Camera默认使用主要的。
我从我的工作线程开始回调,我在这个方法中使用了HandlerThread:
private void startCamera() {
if (mCameraThread == null) {
mCameraThread = new HandlerThread(CAMERA_THREAD_NAME);
mCameraThread.start();
mCameraHandler = new Handler(mCameraThread.getLooper());
}
mCameraHandler.post(new Runnable() {
@Override
public void run() {
try {
mCamera = Camera.open();
...
我使用调试器来确认我的onPreviewFrame确实在工作线程上运行。我还在UI线程上运行了动画,在我将帧处理从主线程转换出来之前它是生涩的但是现在和黄油一样流畅。
请注意,如果你杀了你的工作线程,那么当然你的回调也会停止,而Camera(而不是Handler)会抱怨你试图使用死线程。
BTW,作为替代解决方案当然可以在主线程上调用回调,但帧数据的处理可以委托给单独的线程。答案 1 :(得分:4)
来自其他方法的回调被传递到调用open()的线程的事件循环。如果此线程没有事件循环,则回调将传递到主应用程序事件循环。如果没有主应用程序事件循环,则不会传递回调。 Source
答案 2 :(得分:4)
我猜你使用AsyncTask
的实现是错误的:
According to the documentation,在调用open()
的线程上调用Camera的回调。并so is onPreviewFrame
回调。opening (因此,onPreviewFrame
始终在主线程上执行是不正确的。)
您AsyncTask
的onPreExecute()方法中的is invoked on the UI thread摄像机{{3}}而不是您可能预期的后台线程,因此摄像机的回调会在主线。
我想您应该使用AsyncTask的doInBackground()
方法打开相机。