Android: Caméra Asynctask avec Aperçu de Rappel
J'ai réussi à obtenir l'aperçu de l'appareil photo avec filtre personnalisé (niveaux de gris, teinte, etc) de travail. Ce filtre personnalisé est appliqué avec de l'extrait de rappel par la manipulation de la matrice de RVB et puis de nouveau à une toile, puis de les afficher à la surface d'affichage.
L'inconvénient de cette est que je obtenir un très faible FPS. Avec ce FPS bas, c'est de faire trop de travail dans le thread de l'INTERFACE utilisateur, si je ne fais pas ça dans le thread d'arrière-plan à l'aide de Asynctask. J'ai donc essayé d'utiliser Asynctask pour le fonctionnement de la caméra (mon principal objectif est d'obtenir de l'INTERFACE utilisateur fonctionne toujours parfaitement, même avec les travaux lourds à partir de l'aperçu de la caméra de rappel).
Mais, même après que j'ai utilisé Asynctask, il n'a pas beaucoup d'aide. Alors je me demande est-ce que mon application est mal ou est-ce parce que même avec asynctask le thread d'INTERFACE utilisateur seront touchés?
Extrait de mon code est ci-dessous:
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);
}
}
}
};
Toute aide est très appréciée! 🙂 Merci d'avance les gars!
nope, je n'ai pas. Je ne suis pas sûr de savoir comment faire, pouvez-vous me recommander un moyen de le tester? merci!
OriginalL'auteur CodingBird | 2013-10-07
Vous devez vous connecter pour publier un commentaire.
Des rappels à partir d'autres méthodes sont livrés à la boucle d'événements de le thread qui appelle open(). Si ce fil n'a pas de boucle d'événements, puis les rappels sont livrés à l'application principale boucle d'événements. Si il n'y a pas de principal de l'application, boucle d'événements, rappels ne sont pas livrés. Source
J'ai été la recherche de moyens pour afficher l'aperçu de la caméra à l'aide d'openGL, mais je ne trouve pas de bon, même si je l'ai trouvé quand je l'ai essayé, je ne peux pas appliquer le filtre personnalisé. Pouvez-vous suggérer quelques façons? merci man!
Juste un avis: à Partir de l'Android à la documentation, onPreviewFrame est appelée sur le thread acquiert l'appareil utilisation de l'appareil photo.open().
malheureusement, vous vous trompez et Boston Walker est de droit: des Rappels à partir d'autres méthodes sont livrés à la boucle d'événements de le thread qui appelle open(). Si ce fil n'a pas de boucle d'événements, puis les rappels sont livrés à l'application principale boucle d'événements. Si il n'y a pas de principal de l'application, boucle d'événements, rappels ne sont pas livrés.
https://developer.android.com/reference/android/hardware/Camera.PreviewCallback.html#onPreviewFrame(byte[], android.hardware.Camera)
Marquis Oui, j'ai manqué de tentant d'utiliser événement looper. Je vais modifier ma réponse.
OriginalL'auteur Daud Arfin
Peut-être que ma réponse est trop tard pour vous, mais je faisais des recherches sur le même sujet, donc j'ai pensé partager mes découvertes, de toute façon...
Tout d'abord, si la Caméra est "ouvert" est appelée sur l'AsyncTask et puis ce thread existe et s'empare d'exister - on ne peut pas vraiment s'attendre à des rappels à venir, peut-on. Donc, si nous voulons rappels - alors nous avons besoin d'avoir un thread qui vit depuis au moins aussi longtemps que nous voulons que nos rappels.
Mais attendez, il ya plus... de la Documentation pour la Caméra.PreviewCallback n'est pas la plus claire, mais un de ces mauvais conseils est ce "Ce callback n'est appelé sur le événement fil ouvert(int) a été appelé." Que veulent-ils dire par "événement" thread? Eh bien, ce n'est pas très clair mais en regardant le code Android et expérimentation - ce qu'ils ont besoin est un fil qui contient un Looper. Probablement trop de détails, mais dans l'Appareil du constructeur (qui est appelé à partir de la méthode ouverte) il est le code qui tente d'obtenir d'abord un Looper de le thread en cours, si cela n'existe pas - il tente d'obtenir des thread principal looper - qui vit sur le thread de l'INTERFACE utilisateur. La caméra utilise un Gestionnaire d'envoi de rappels et d'autres méthodes via la fonction looper qu'il initialise comme ça. Maintenant, vous pouvez probablement voir pourquoi vous avez été d'obtenir vos blagues sur le thread principal, même si vous avez ouvert l'appareil photo à partir d'un autre thread - votre thread de travail n'ont pas un looper - donc de la Caméra par défaut à l'aide de la principale.
J'ai eu des rappels de travail de mon thread de travail pour lequel j'ai utilisé HandlerThread dans une méthode, le long de ces lignes:
...
J'ai utilisé débogueur pour confirmer que mon onPreviewFrame n'a exécuter sur le thread de travail. J'avais aussi l'animation en cours d'exécution sur le thread de l'INTERFACE utilisateur, qui était saccadé avant que j'ai passés cadre du traitement loin de le thread principal, mais maintenant est lisse comme du beurre.
Notez que si vous tuez votre thread de travail, puis, bien sûr, vos rappels va cesser de trop et l'Appareil photo (bien plutôt un Gestionnaire) va se plaindre de ce que vous essayez d'utiliser morts fil.
BTW, comme une solution alternative, on pourrait naturellement avoir eu des rappels être appelé sur le thread principal, mais le traitement de données d'image peuvent être délégués à un thread séparé.
Est-il possible, si vous @leonman30 pourrait afficher toute votre activité de la caméra + support de cours? Je suis à la recherche d'un bien fluide non hacky camera solution.. mais j'ai toujours bloquer le thread d'INTERFACE utilisateur 😀
OriginalL'auteur leonman30
Je suppose que votre mise en œuvre à l'aide de
AsyncTask
est faux:Selon la documentation, Appareil photo rappels sont invoquées sur le thread qui appelle
open()
. Et donc, est laonPreviewFrame
de rappel. (Il n'est donc pas vrai queonPreviewFrame
est toujours exécuté sur le thread principal.)Vous ouverture la caméra dans la
AsyncTask
's onPreExecute() la méthode, qui est invoquée sur le thread d'INTERFACE utilisateur et pas sur le thread d'arrière-plan que vous avez probablement prévu et, par conséquent, de la caméra de rappel exécuté sur le thread principal.Je suppose que vous devez ouvrir l'appareil photo dans l'AsyncTask est
doInBackground()
méthode.OriginalL'auteur Filip J.