In the original (now deprecated) camera API, we used to be able to get preview frames in the Camera.PreviewCallback and be able to process it (taking possibly very long) and release the buffer to be able to receive another frame, without lagging the screen preview, with some code like the following:
public void onPreviewFrame(final byte[] data, Camera camera) {
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
(... do some slow processing ...)
}
#Override
protected void onPostExecute(Void aVoid) {
mCamera.addCallbackBuffer(data); // free the buffer to be able
// to process another frame
}
}.execute();
}
The API would only callback with a new frame if there was another buffer available to receive it, without lagging the screen preview.
I'm trying to replicate the same behaviour on the new Camera2 API, but I can't find a way to do it without lagging the screen preview. If I add a second target (same resolution as the screen one, YUV_420_888) to the preview request:
mPreviewRequestBuilder.addTarget(surface);
mPreviewRequestBuilder.addTarget(previewImageReader.getSurface());
mCameraDevice.createCaptureSession(
Arrays.asList(surface, previewImageReader.getSurface()), ...
the screen preview will lag, even if I just close the image as soon as I get it:
#Override
public void onImageAvailable(ImageReader reader) {
reader.acquireNextImage().close();
}
What's the correct way to use Camera2 to emulate the original camera API behaviour (i.e having a new buffer whenever one is free and not slowing the screen preview)?
Update: In case anyone is wondering how the rest of the code looks like, it is just a modified version of the standard android-camera2Basic sample, here's what I've changed.
If anyone is still interested.
Create a SurfaceTextureListener and call your async function from the onSurfaceTextureUpdated method. I have used this successfully when checking frames for barcodes with the BarcodeDetection API and the Camera 2 API.
Here is a sample of an async function launched from the onSurfaceTextureUpdated method. If you only want to run one async task in the background at a time, you can use a flag to check if the previous task has completed.
private final TextureView.SurfaceTextureListener mSurfaceTextureListener
= new TextureView.SurfaceTextureListener() {
boolean processing;
#Override
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
openCamera(width, height);
}
#Override
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
configureTransform(width, height);
}
#Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
return true;
}
#Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
if (processing) {
return;
}
processing = true;
Bitmap photo = mTextureView.getBitmap();
new ImageTask(photo, new ImageResponse() {
#Override
public void processFinished() {
processing = false;
}
}).execute();
}
};
private interface ImageResponse {
void processFinished();
}
private class ImageTask extends AsyncTask<Void, Void, Exception> {
private Bitmap photo;
private ImageResponse imageResponse;
ImageTask(Bitmap photo, ImageResponse imageResponse) {
this.photo = photo;
this.imageResponse = imageResponse;
}
#Override
protected Exception doInBackground(Void... params) {
// do background work here
imageResponse.processFinished();
return null;
}
#Override
protected void onPostExecute(Exception result) {
}
}
Related
Good day.I have an google map with cluster manager.Simple one,where i use the cluster to draw markers grouped or not.Anyway i got an method callback from cluster manager which is the Cluster item render one.Inside that callback i am applying custom image to the marker:The user image inside marker.I found Picasso to be the best to handle bitmap loading and at the same time got me lots of headache.I am using Target class from Picasso to initiate the bitmap callbacks:OnPreLoad,OnFail,OnBitmapLoaded.The issue is that on first cluster item render the onBitmapLoaded not called and generally it is never gets called unless it has been touched second time.On first time nothing happens,no callback is triggered except OnPreLoad and by googling i found that the great Picasso holds weak reference to the class.I tried all the examples of the google:Making Target reference strong(getting the initialazation of class out of method and init the class inside my class like the follows)
#Override
protected void onClusterItemRendered(MarkerItem clusterItem, Marker marker) {
mMarker = marker;
mMarkerItem = clusterItem;
Picasso.with(mContext).load(clusterItem.getImageUrl()).transform(new CircleTransformation()).into(target);
}
private Target target = new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
Log.d(TAG, "onBitmapLoaded: ");
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
Log.d(TAG, "onBitmapFailed: ");
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
Log.d(TAG, "onPrepareLoad: ");
}
};
#Override
protected void onBeforeClusterItemRendered(MarkerItem item, MarkerOptions markerOptions) {
markerOptions.title(item.getTitle());
markerOptions.icon(item.getIcon());
}
At this point i get the same result....Sometimes the bitmap loaded and sometimes not.Mostly not...
Anyway i have tried to implement the interface class to my own class as follows:
public class PicassoMarkerView implements com.squareup.picasso.Target {
private static final String TAG = "MarkerRender";
private Bitmap mMarkerBitmap;
private ClusterManager<MarkerItem> mClusterManager;
private MarkerItem mMarkerItem;
private Marker mMarker;
public PicassoMarkerView() {
}
#Override
public int hashCode() {
return mMarker.hashCode();
}
#Override
public boolean equals(Object o) {
if (o instanceof PicassoMarkerView) {
Marker marker = ((PicassoMarkerView) o).mMarker;
return mMarker.equals(marker);
} else {
return false;
}
}
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap,
mMarkerBitmap.getWidth() - 15, (int) (mMarkerBitmap.getHeight() / 1.5 - 15),
false);
mMarker.setIcon(BitmapDescriptorFactory.fromBitmap(overlay(mMarkerBitmap, scaledBitmap, 8, 7)));
Log.d(TAG, "onBitmapLoaded: ");
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
Log.d(TAG, "onBitmapFailed: ");
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
Log.d(TAG, "onPrepareLoad: ");
}
private Bitmap overlay(Bitmap bitmap1, Bitmap bitmap2, int left, int top) {
Bitmap res = Bitmap.createBitmap(bitmap1.getWidth(), bitmap1.getHeight(),
bitmap1.getConfig());
Canvas canvas = new Canvas(res);
canvas.drawBitmap(bitmap1, new Matrix(), null);
canvas.drawBitmap(bitmap2, left, top, null);
return res;
}
public void setMarkerBitmap(Bitmap markerBitmap) {
this.mMarkerBitmap = markerBitmap;
}
public void setClusterManager(ClusterManager<MarkerItem> clusterManager) {
this.mClusterManager = clusterManager;
}
public void setMarkerItem(MarkerItem markerItem) {
this.mMarkerItem = markerItem;
}
public void setMarker(Marker marker) {
this.mMarker = marker;
}
}
Unfortunatally this is not working either...Same result...So please dear friends can you give me an working example of this?As far as i could google,the issue mostly happens to the user which try to do this inside loop and my onClusterItemRender some sort of loop lets say as it is triggered every time marker is visible to user,so yeah it is triggered several times and as fast as loop so give me some idea please and help me out...
Important to mention that i do not need to use methods from picasso like fetch(),get() as they are not necessary and not fitting the purpose of the app.
I encountered similar issue and holding reference to the target didn't help at all.
The purpose of my project was to use 2 different image downloading api's to show an images gallery and to give the user the ability to choose which api to use.
Beside Picasso I used Glide, and I was amazed by the results, Glide's api worked flawlessly in every aspect wile Picasso gave me hell (that was my first time using Glide, I usually used Picasso so far, seems like today it's gonna change ^^ ).
So my suggestion to you is:
Use glide over Picasso (no such weak reference on their target).
Since I had to use both libraries I ended up using get() in an handler, not sure if it will help you but it solved my problem:
handlerThread = new HandlerThread(HANDLER_THREAD_NAME);
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());
handler.post(new Runnable() {
#Override
public void run() {
Bitmap bitmap = null;
try {
bitmap = picasso.with(appContext).load(url).get();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (bitmap != null) {
//do whatever you wanna do with the picture.
//for me it was using my own cache
imageCaching.cacheImage(imageId, bitmap);
}
}
}
});
I'm trying to use an IntentService for processing and uploading images that's running in a different process to have more memory. I'm Using also Picasso to load the Image. When the Image is small the bitmap is loaded successfully and uploaded, however if the image is big the IntentService is terminated before Picasso is done loading It.
Picasso have to run on UIThread
Here is the code.
private void downloadImage(File file) {
final Uri uri = Uri.fromFile(file);
Handler uiHandler = new Handler(Looper.getMainLooper());
uiHandler.post(new Runnable() {
#Override
public void run() {
Picasso.with(NewImageProcessingService.this).load(uri).transform(new ImageLoadingUtil.DecreaseQualityTransformation(imageQuality)).into(NewImageProcessingService.this);
}
});
}
#Override
protected void onHandleIntent(Intent intent) {
File file = (File) intent.getSerializableExtra(KEY_IMAGE_FILE);
imageQuality = ImagesUtils.IMAGE_QUALITY
.values()[intent.getIntExtra(IMAGE_QUALITY, ImagesUtils.IMAGE_QUALITY.DEFAULT.ordinal())];
downloadImage(file);
}
This question is quite old, but if anyone steps by. The Target is getting garbage collected before it can show the bitmap.
Use it like this
public class BitmapLoader {
public static Target getViewTarget(final OnImageLoadingCompleted onCompleted) {
return new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
onCompleted.imageLoadingCompleted(bitmap);
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
};
}
}
You need to have a strong reference to the Target so have a field in your IntentService holding it e.g.
private Target viewTarget;
viewTarget = BitmapLoader.getViewTarget(bitmap -> {
// do stuff with the bitmap
});
new Handler(Looper.getMainLooper()).post(() -> Picasso.with(getApplicationContext()).load(object.getImageUrl()).into(viewTarget));
I am building an app which will continuously get screenshots of my laptop screen and transfer it to my android app but there is some problem within the while loop, when I put a for loop to a limit then my program runs but as it goes till infinity or I replace it with infinite while loop my code suspends all the threads and app crash dueto memory allocation problem, please suggest me to execute my code infinite times so that there are continuous screenshots displayed.
Thank You.
Here is my code
public class ScreenActivity extends AppCompatActivity {
ImageView img;
int width,height;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_screen);
img=(ImageView)findViewById(R.id.imageView);
Display display = getWindowManager().getDefaultDisplay();
width = display.getWidth();
height = display.getHeight();
// while (true)
for (int i=0;i<100;i++)
new GetImg().execute();
}
Bitmap imgscr;
public class GetImg extends AsyncTask<Object,Void,Bitmap> {
#Override
protected Bitmap doInBackground(Object[] params) {
Socket client= null;
try {
client = new Socket("192.168.1.5",6767);
InputStream in=client.getInputStream();
imgscr=Bitmap.createScaledBitmap(BitmapFactory.decodeStream(in), width, height, false);
} catch (Exception e) {
e.printStackTrace();
}
return imgscr;
}
#Override
protected void onPostExecute(Bitmap bm)
{
img.setImageBitmap(bm);
}
}
}
#m0skit0 commented the actual reason of getting the ANR. You're out of your run-time memory when you're creating threads in an infinite loop. I'm pretty confused about your purpose though. I think you need to get the screenshots one after one and if this is the case, you can simply add a listener to the AsyncTask and get the callback when the screenshot is downloaded fully.
So if I've understood correctly, you need to declare an interface like this.
public interface DownloadCompletedListener {
public void onDownloadComplete(String result);
}
Now you need to implement the interface in your Activity like this
public class ScreenActivity extends AppCompatActivity implements DownloadCompletedListener {
private GetImg getImageTask;
private Bitmap imageBitmap;
#Override
public void onDownloadComplete(String result) {
if(result.equals("SUCCESS")) {
// Set the image now
img.setImageBitmap(imageBitmap);
// Start next download here
getImageTask = new GetImg();
getImageTask.mListener = this;
getImageTask.execute();
} else {
// Do something
}
}
}
You need to modify your AsyncTask a bit. You need to declare the DownloadCompletedListener.
public class GetImg extends AsyncTask<Object,Void,Bitmap> {
private DownloadCompletedListener mListener;
#Override
protected Bitmap doInBackground(Object[] params) {
Socket client= null;
try {
client = new Socket("192.168.1.5",6767);
InputStream in=client.getInputStream();
imgscr=Bitmap.createScaledBitmap(BitmapFactory.decodeStream(in), width, height, false);
} catch (Exception e) {
e.printStackTrace();
}
return imgscr;
}
#Override
protected String onPostExecute(Bitmap bm)
{
imageBitmap = bm;
mListener.onDownloadComplete("SUCCESS");
}
}
So your onCreate function will look like this now
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_screen);
img=(ImageView)findViewById(R.id.imageView);
Display display = getWindowManager().getDefaultDisplay();
width = display.getWidth();
height = display.getHeight();
// Start downloading image here. Remove the loop
getImageTask = new GetImg();
getImageTask.mListener = this;
getImageTask.execute();
}
I have a RecyclerView adapter with many different ViewHolders. One of the ViewHolders contains an ImageView, which needs to be able to take a picture, resize it, then display it. For modularity, I want the ViewHolder to be self-contained: it and not the parent activity should handle everything concerning the photo taking and displaying process. Also the file path is constant (it will never change). In fact, it is /storage/emulated/0/com.company.app/myst/cat.jpg. As a result, here is my implementation of the ImageView’s onClick method.
#Override
public void onClick(View v) {
final FragmentManager fm = ((MyActivity) getContext()).getSupportFragmentManager();
Fragment auxiliary = new Fragment() {
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
resizeResaveAndDisplayPhoto();
super.onActivityResult(requestCode, resultCode, data);
fm.beginTransaction().remove(this).commit();
}
};
fm.beginTransaction().add(auxiliary, "FRAGMENT_TAG").commit();
fm.executePendingTransactions();
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (null != takePictureIntent.resolveActivity(view.getContext().getPackageManager())) {
((MyActivity)view.getContext()).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
auxFragment.startActivityForResult(takePictureIntent, Constants.REQUEST_CODE_PHOTO);
}
}
When resizeResaveAndDisplayPhoto is called it executes the following AsyncTask
public static class ResizeThenLoadImageTask extends AsyncTask<String, Void, Bitmap> {
private final WeakReference<ImageView> imageViewWeakReference;
private final WeakReference<File> fileWeakReference;
private final WeakReference<Context> weakContext;
private final int reqHeight;
private final int reqWidth;
public ResizeThenLoadImageTask(Context context, ImageView imageView, File file, int reqHeight, int reqWidth) {
weakContext = new WeakReference<Context>(context);
imageViewWeakReference = new WeakReference<>(imageView);
fileWeakReference = new WeakReference(file);
this.reqHeight = reqHeight;
this.reqWidth = reqWidth;
}
#Override
public Bitmap doInBackground(String... params) {
File file = fileWeakReference.get();
Bitmap bitmap = null;
if (null != file) {
bitmap = ImageUtils.reduceImageSize(file, reqHeight, reqWidth);
ImageUtils.saveBitmapToGivenFile(bitmap, file);
}
return bitmap;
}
#Override
public void onPostExecute(Bitmap bitmap) {
if (null != imageViewWeakReference && null != fileWeakReference) {
ImageView imageView = imageViewWeakReference.get();
File file = fileWeakReference.get();
if (null != imageView) {
if (null != bitmap) {
imageView.setImageBitmap(bitmap);
}
else {
imageView.setImageResource(R.drawable.photo);
}
imageView.postDelayed(new Runnable() {
#Override
public void run() {
if (null != weakContext.get()) {
((MyActivity) weakContext.get()).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
}
}, 10000);
}
}
}
}
You may notice that I lock the orientation before taking the photo and unlock it 10 seconds after displaying the photo. That trick is part of my troubleshooting. So here is the situation.
The system described above works very well. Problems happen in the following case
Say I already have a photo in the ImageView but want to replace it.
So I click on the ImageView to take a new photo.
If I rotate the device to take the new photo, then when I return the new photo displays briefly before the old photo returns.
So I lock to orientation to see what was happening. Here is what I found.
The new photo displays for as long as I lock the orientation. As soon as the orientation unlocks (10 sec), the old photo returns.
If I leave the activity and the returns, the old photo is still displaying.
If I close the app completely and then return, then I see the new photo.
When the device is rotated, the running activity is recreated as well the asyncTask attached to it, probably causing a leak.
You probably need to call the asyncTask inside of a service instead in an activity so the asyncTask is going to be attached to the life cycle of the service.
As my previous question, I am trying "GLSurfaceView + TextureView" to show camera preview in one GLSurfaceView and multiple TextureViews, but facing some problems...
In GLSurfaceView render thread, I tried to share built-in EGLContext to TextureView, create a EGL surface by TextureView's surfaceTexture, then use GLES to draw on it.
#Override
public void onDrawFrame(final GL10 gl) {
// GLES draw on GLSurfaceView
renderToTextureView();
}
private void renderToTextureView() {
saveEGLState();
for(TextureViewItem item : mTextureViewItemList) {
item.render(mSavedEglContext);
}
restoreEGLState();
}
private void saveEGLState() {
mSavedEglDisplay = EGL14.eglGetCurrentDisplay();
mSavedEglContext = EGL14.eglGetCurrentContext();
mSavedEglDrawSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW);
mSavedEglReadSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_READ);
}
private void restoreEGLState() {
if (!EGL14.eglMakeCurrent(mSavedEglDisplay, mSavedEglDrawSurface, mSavedEglReadSurface, mSavedEglContext)) {
throw new RuntimeException("eglMakeCurrent failed");
}
}
public class TextureViewItem implements TextureView.SurfaceTextureListener {
private static EglCore sEglCore;
private WindowSurface mWindowSurface;
public void render(EGLContext sharedContext) {
if(mSavedSurfaceTexture == null) return;
getWindowSurface(sharedContext).makeCurrent();
// GLES draw on TextureView
getWindowSurface(sharedContext).swapBuffers();
}
private WindowSurface getWindowSurface(EGLContext sharedContext) {
if(sEglCore == null) {
sEglCore = new EglCore(sharedContext, EglCore.FLAG_TRY_GLES3);
}
if(mWindowSurface == null) {
mWindowSurface = new WindowSurface(mEglCore, mSavedSurfaceTexture);
}
return mWindowSurface;
}
#Override
public void onSurfaceTextureAvailable(SurfaceTexture st, int width, int height) {
if (mSavedSurfaceTexture == null) {
mSavedSurfaceTexture = st;
}
}
#Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture st) {
if (mWindowSurface != null) {
mWindowSurface.release();
}
if (sEglCore != null) {
sEglCore.release();
}
mSavedSurfaceTexture = null;
return true;
}
}
Everything works fine except press "back" key. I call GLSurfaceView's onPause() when the activity pauses, it caused swapBuffers (EGL14.eglSwapBuffers) won't return...
Some suspected logcat messages also
W/WindowManager(1077): Window freeze timeout expired.
I/WindowManager(1077): Screen frozen for +2s42ms due to Window ..
Anyone knows why? And any way to solve this problem?
Thanks.