Android: decode JPGs in loop: random freezes - android

Here's another question for you :)
Basically i made a realtime streaming service, sending multiple jpegs to my android app, that decodes them as soon as he receives them.
// dIn is DataInputStream
// videoFeed is an ImageView
// bitmap is Bitmap
// hand is an Handler of the main thread
//CODE EXECUTED IN ANOTHER THERAD
byte[] inBuff = new byte[8];
byte[] imgBuff;
String inMsg;
while(socket.isConnected()) {
dIn.readFully(inBuff);
inMsg = new String(inBuff, "ASCII").trim();
int size = Integer.parseInt(inMsg);
imgBuff = new byte[size];
dIn.readFully(imgBuff);
out.write("SEND-NEXT-JPEG".getBytes("ASCII"));
bitmap = BitmapFactory.decodeByteArray(imgBuff, 0, size);
hand.post(setImage);
}
}
private Runnable setImage = new Runnable() {
#Override
public void run() {
videoFeed.setImageBitmap(bitmap);
}
};
The problem is that after about 10 or 20 jpegs are perfectly decoded in realtime, the app freezes for 400ms or so and then it continues to decode other 10/20 jpegs before another freeze...
I know that sending multiple jpegs it's not a good way for streaming video but i can only change the client (android app), not the server.
Do you have any idea for get a fluid video and avoid freezes? thanks!

Right now, you are using the three-parameter version of decodeByteArray(). Instead, switch to the four-parameter version, passing in a BitmapFactory.Options as the last value. On there, set inBitmap to be a Bitmap object that can be reused.
This requires you to maintain a small Bitmap object pool. It could be as simple as two Bitmap instances: the one that is presently being displayed and the one that you are preparing for the next "frame" of the video.
The catch is that, for API Level 18 and below, the Bitmap needs to be the same resolution (height and width in pixels). In your case, that's probably not a problem, as I would imagine that each of your bitmaps have the same resolution.

Related

Send String format of Bitmap to server in Android

I have created a camera app where I want to send the frames I receive in onCameraFrame of OpenCV library to server in real-time or say with minimum delay. I was able to achieve map to string conversation in real-time but facing two problems in sending the string to the server.
The string is too large and few Connections like httpUrlConnection in android is not able to send this.
I want to send this big string to the server on one short as quick as possible to achieve the real-time effect.
private Bitmap convertMatToBitMap(Mat input) {
Bitmap bmp = null;
Mat rgb = new Mat();
Imgproc.cvtColor(input, rgb, Imgproc.COLOR_mRGBA2RGBA);
try {
bmp = Bitmap.createBitmap(rgb.cols(), rgb.rows(),Bitmap.Config.ARGB_8888);
Utils.matToBitmap(rgb, bmp);
}
catch (CvException e){
Log.d("Exception",e.getMessage());
}
String bmpStringObj = BitMapToString(bmp);
sendRequest(bmpStringObj); // This is be super fast
return StringToBitMap(bmpStringObj);
}
The above code converts map to bit map and I need to send the string format of this bitmap to server.

Displaying Coverart (from id3) in Recyclerview in background

I am working on an app that uses a Recyclerview to display mp3 files, providing its cover art image along with other info. It works but is slow once it starts dealing with a dozen or more cover arts to retrieve, as I am currently doing this from the id3 on the main thread, which I know is not a good idea.
Ideally, I would work with placeholders so that the images can be added as they become available. I've been looking into moving the retrieval to a background thread and have looked at different options: AsyncTask, Service, WorkManager. AsyncTask seems not to be the way to go as I face memory leaks (I need context to retrieve the cover art through MetadataRetriever). So I am leaning away from that. Yet I am struggling to figure out which approach is best in my case.
From what I understand I need to find an approach that allows multithreading and also a means to cancel the retrieval in case the user has already moved on (scrolling or navigating away). I am already using Glide, which I understand should help with the caching.
I know I could rework the whole approach and provide the cover art as images separately, but that seems a last resort to me, as I would rather not weigh down the app with even more data.
The current version of the app is here (please note it will not run as I cannot openly divulge certain aspects). I am retrieving the cover art as follows (on the main thread):
static public Bitmap getCoverArt(Uri medUri, Context ctxt) {
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(ctxt, medUri);
byte[] data = mmr.getEmbeddedPicture();
if (data != null) {
return BitmapFactory.decodeByteArray(data, 0, data.length);
} else {
return null;
}
}
I've found many examples with AsyncTask or just keeping the MetaDataRetriever on the main thread, but have yet to find an example that enables a dozen or more cover arts to be retrieved without slowing down the main thread. I would appreciate any help and pointers.
It turns out it does work with AsyncTask, as long as it is not a class onto itself but setup and called from a class with context. Here is a whittled down version of my approach (I am calling this from within my Adapter.):
//set up titles and placeholder image so we needn't wait on the image to load
titleTv.setText(selectedMed.getTitle());
subtitleTv.setText(selectedMed.getSubtitle());
imageIv.setImageResource(R.drawable.ic_launcher_foreground);
imageIv.setAlpha((float) 0.2);
final long[] duration = new long[1];
//a Caching system that helps reduce the amount of loading needed. See: https://github.com/cbonan/BitmapFun?files=1
if (lruCacheManager.getBitmapFromMemCache(selectedMed.getId() + position) != null) {
//is there an earlier cached image to reuse? imageIv.setImageBitmap(lruCacheManager.getBitmapFromMemCache(selectedMed.getId() + position));
imageIv.setAlpha((float) 1.0);
titleTv.setVisibility(View.GONE);
subtitleTv.setVisibility(View.GONE);
} else {
//time to load and show the image. For good measure, the duration is also queried, as this also needs the setDataSource which causes slow down
new AsyncTask<Uri, Void, Bitmap>() {
#Override
protected Bitmap doInBackground(Uri... uris) {
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(ctxt, medUri);
byte[] data = mmr.getEmbeddedPicture();
Log.v(TAG, "async data: " + Arrays.toString(data));
String durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
duration[0] = Long.parseLong(durationStr);
if (data != null) {
InputStream is = new ByteArrayInputStream(mmr.getEmbeddedPicture());
return BitmapFactory.decodeStream(is);
} else {
return null;
}
}
#Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
durationTv.setVisibility(View.VISIBLE);
durationTv.setText(getDisplayTime(duration[0], false));
if (bitmap != null) {
imageIv.setImageBitmap(bitmap);
imageIv.setAlpha((float) 1.0);
titleTv.setVisibility(View.GONE);
subtitleTv.setVisibility(View.GONE);
} else {
titleTv.setVisibility(View.VISIBLE);
subtitleTv.setVisibility(View.VISIBLE);
}
lruCacheManager.addBitmapToMemCache(bitmap, selectedMed.getId() + position);
}
}.execute(medUri);
}
I have tried working with Glide for the caching, but I haven't been able to link the showing/hiding of the TextViews to whether there is a bitmap. In a way though, this is sleeker as I don't need to load the bulk of the Glide-library. So I am happy with this for now.

Picasso - how to get the real image size?

I load an image from URLusing Picasso library. I want to get the real image size, but I can only get the image size in memory:
Picasso.with(this)
.load(imageUrl)
.error(R.drawable.no_image)
.into(photoView, new Callback() {
#Override
public void onSuccess() {
Bitmap bitmap = ((BitmapDrawable)photoView.getDrawable()).getBitmap();
textImageDetail.setText(bitmap.getByteCount());// image size on memory, not actual size of the file
}
#Override
public void onError() { }
});
How to get the size of the loaded image? I think it is stored somewhere in a cache, but I do not know how to access the image file.
Update
Sorry for my bad English, maybe I asked the wrong question. I need to get the image size (128 kb, 2 MB, etc.). NOT the image resolution (800x600, etc.)
You could first get the actual Bitmap image that is getting loaded, and then find the dimensions of that. This has to be run in an asynchronous method like AsyncTask because downloading the image is synchronous. Here is an example:
Bitmap downloadedImage = Picasso.with(this).load(imageUrl).get();
int width = downloadedImage.getWidth();
int height = downloadedImage.getHeight();
If you want to get the actual image size in bytes of the Bitmap, just use
// In bytes
int bitmapSize = downloadedImage.getByteCount();
// In kilobytes
double kbBitmapSize = downloadedImage.getByteCount() / 1000;
Replace the imageUrl with whatever URL you want to use. Hope it helps!
I know this question is old, but I stepped here for an answer and found none.
I found a solution that worked with me using OkHttpClient.
You can fetch the header information only, using OkHttpClient and get the content length without downloading the image.
OkHttpClient httpClient = new OkHttpClient();
Request request = new Request.Builder().url(imageURL).head().build();
Response response = null;
try {
response = httpClient.newCall(request).execute();
String contentLength = response.header("content-length");
int size = Integer.parseInt(contentLength);
} catch (IOException e ) {
if (response!=null) {
response.close();
}
}
Notes:
the above code performs a network call, it should be executed on a background thread.
size is returned in Bytes, you can divide by 1000 if you want it in KB.
this may not work with large files.
Beware, casting to integer could bypass the integer's max value.

ThumbnailUtils.createVideoThumbnail returns NULL when capturing new video

I am trying to create a thumbnail from video. I use the following line:
Bitmap thumb = ThumbnailUtils.createVideoThumbnail(selectedVideoPath, MediaStore.Images.Thumbnails.MICRO_KIND);
It works great when I select an existing video from the gallery, but returns NULL when recording a new video and then trying to get the thumbnail, although the path is valid (/storage/emulated/0/airImagePicker/1394007123308.3gp).
I am on HTC One Android 4.2.2.
Thanks!
I faced the same problem and noticed that it worked when there was a delay between taking the video and creating the bitmap. A workaround that worked for me was to retry creating the bitmap with busy waiting until it wasn't null( it took a few seconds). It's clearly not a clean solution but it seems to do the job.
example of use (in c# xamarin android)
try {
Bitmap bitmap = null;
for (int time = 0; time < 6000; time += timeInterval) {
bitmap = ThumbnailUtils.CreateVideoThumbnail (videoFile.Path, ThumbnailKind.MiniKind);
if (bitmap != null)
break;
await Task.Delay (timeInterval);
}
return bitmap;
I hope it helps.

Android: Bitmap Decode java.lang.OutofMemoryError error

My current android applications main functionality is to pull the frames (10fps) from a recorded video using the built in camera. Whenever the user selects the video I call my class "FrameCollector" which loops through the video and pulls the frames out and stores them into an ArrayList of Bitmaps. Having this work well for a number of days had me thinking that I was on the right track, but now I'm getting the dreaded "java.lang.OutofMemoryError"
My code is as follows:
Here it is setting the MediaMetaDataRetriever to select 10 frames per second from the video path which was passed from the main class
public class FrameCollector {
MediaMetadataRetriever _mmr;
double _fps;
double _duration;
long _counter = 0;
long _incrementer;
public FrameCollector(String path, Context context)
{
try
{
_mmr = new MediaMetadataRetriever();
_mmr.setDataSource(path);
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
String fpsString = pref.getString("prefFPS", "10");
_fps = Double.parseDouble(fpsString);
_incrementer = (long) (1000000 / _fps);
String stringDuration = _mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
_duration = Double.parseDouble(stringDuration) * 1000;
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
The below method is then adding the frame at that time to a bitmap and adding the bitmap to an arraylist of bitmaps "bitFrames"
public ArrayList<Bitmap> getBitmaps()
{
try
{
ArrayList<Bitmap> bitFrames = new ArrayList<Bitmap>();
Bitmap b = _mmr.getFrameAtTime(_counter);
while (_counter < _duration && b != null)
{
bitFrames.add(b);
_counter += _incrementer;
b = _mmr.getFrameAtTime(_counter);
}
return bitFrames;
}
catch (Exception ex)
{
ex.printStackTrace();
return null;
}
}
I'm thinking my issue lies with in this method. I believe I need to decode the bitmaps before I store them into the ArrayList but I'm unsure how as
Bitmap b = BitmapFactory.decode____(_mmr.getFrameAtTime(_counter));
-- Whether it be decodeStream, decodeResource, decodeFile all bring errors.
Any help would be much appreciated
Many Thanks,
Never store Bitmaps in an ArrayList unless the number of bitmaps are very small. Bitmaps take up large amounts of memory.
I've not worked with video recording before, but I would suggest you try to get a native codec library like ffmpeg to do it for you.
If you have to grab the frames yourself, however, I suggest storing them in a fixed size buffer in memory from the camera and have a background thread which pulls bitmaps from the buffer and stores it to the disk in parallel.
It's not good to store ArrayList of Bitmaps in RAM.
Anyway if you need more than default heap size to store bitmaps in ArrayList, you can try this hack.
http://habrahabr.ru/post/139717/
It's in Russian, but I guess code is quite clear to understand.

Categories

Resources