I want to extract frames from video file stored on the device. Every solution i found is to use FFmpegMediaMetadataRetriever or MediaMetadaraRetriever, but as I wrote here it's not working for me. Is there any other way to extract frames from video?
I admit I haven't used this method for a while, but if it still works with the current Android API, it should do the trick.
Please let me know if it still works. If not - I will delete this answer.
public static Bitmap getVideoFrame(Context context, Uri uri, long time) {
Bitmap bitmap = null;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setDataSource(context, uri);
bitmap = retriever.getFrameAtTime(time);
} catch (RuntimeException ex) {
ex.printStackTrace();
} finally {
try {
retriever.release();
} catch (RuntimeException ex) {
ex.printStackTrace();
}
}
return bitmap;
}
Hope this helps.
Related
I am developing a module for which i want to show all the user's videos from sd card into a Gridview. I have grabbed video file paths in usual way (Checking if file or directory and save if its a file) in a arraylist and grabbed its bitmap thumbnail with following code:
Bitmap bmThumbnail = ThumbnailUtils.createVideoThumbnail(VideoValues.get(position).getAbsolutePath(),
Thumbnails.MINI_KIND);
Obviously this code runs in a background thread. But the only problem is that the gribview still freezes a lot while scrolling. According to me the main problem is extracting the bitmap from video, which takes a lot of time. Can anyone suggest me a different way to get bitmap from video and how it in a grid ? I have seen the smooth behavior in other apps like Facebook, etc. But I cannot figure out as to how that can be done.
please use below method for retrive video thumbnail from video
#SuppressLint("NewApi")
public static Bitmap retriveVideoFrameFromVideo(String videoPath)
throws Throwable
{
Bitmap bitmap = null;
MediaMetadataRetriever mediaMetadataRetriever = null;
try
{
mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(videoPath);
bitmap = mediaMetadataRetriever.getFrameAtTime();
}
catch (Exception e)
{
throw new Throwable(
"Exception in retriveVideoFrameFromVideo(String videoPath)"
+ e.getMessage());
}
finally
{
if (mediaMetadataRetriever != null)
{
mediaMetadataRetriever.release();
}
}
return bitmap;
}
My application captures video footage and saves it as .mp4 file. I would like to extract one frame from this video and also save it to file. Since I haven't found nothing better, I've decided to use MediaMetadataRetriever.getFrameAtTime() for that. It happens inside the class that inherits from AsyncTask. Here is how my code looks like my doInBackground():
Bitmap bitmap1 = null;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setDataSource(src);
bitmap1 = retriever.getFrameAtTime(timeUs, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
if (Utils.saveBitmap(bitmap1, dst)) {
Log.d(TAG, "doInBackground Image export OK");
} else {
Log.d(TAG, "doInBackground Image export FAILED");
}
} catch (RuntimeException ex) {
Log.d(TAG, "doInBackground Image export FAILED");
} finally {
retriever.release();
if (bitmap1 != null) {
bitmap1.recycle();
}
}
And the saveBitmap() method:
File file = new File(filepath);
boolean result;
try {
result = file.createNewFile();
if (!result) {
return false;
}
FileOutputStream ostream = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, ostream);
ostream.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
Now, the problem is that the quality of the exported image is noticeably worse then the video quality. I don't think that this should happen with PNG output format. You can see the difference below:
The first image was extracted from the video using ffmpeg on my desktop. The second one was extracted using the code above on Samsung Galaxy S6. The result looks pretty much the same on every Android device I was using.
Can someone tell how can I improve the quality of the exported picture?
I found other solution for the issue. You can use bigflake's example to build mechanism for extracting video frame. The one thing you will have to add is seeking mechanism. This works well, keeps the exact quality and does not require any third-party libraries. Only downside I've noticed so far is that it will result in longer execution time than the original idea.
I got this little function that allows me to load an image from a path, named name. It works, the problem is that I have to call this many times, una tantum. Let's say, a dozen of times at the load of a specific activity. It takes few seconds to load them all.
Is it optimal? Is there a lighter way to achieve the same result?
public static Bitmap loadImageFrom(File path, String name)
{
try {
File f = new File(path, name);
return BitmapFactory.decodeStream(new FileInputStream(f));
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
}
I tried adding if(!f.exists()) return null; like this:
public static Bitmap loadImageFrom(File path, String name)
{
try {
File f = new File(path, name);
if(!f.exists()) return null;
return BitmapFactory.decodeStream(new FileInputStream(f));
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
}
I know that is redundant, but I can't remove the Try Catch clause, cause it giving me an error if I do. However, no speed up noticed.
Any suggestion?
It's not that your code is inefficient, as far as I know, loading pictures manually on the main thread will hurt your app's performance. Especially when you have a large amount of pictures and/or using them to populate your ListView or GridView.
Have a look at Picasso or Universal Image Loader, these libraries should help load your pictures efficiently thus speeding up your app.
I already got it to create a thumbnail from my video.
The code looks like this:
videoGalleryThumbnails.add(ThumbnailUtils.extractThumbnail(ThumbnailUtils.createVideoThumbnail(
videoFile.getAbsolutePath(), MediaStore.Images.Thumbnails.MINI_KIND), 500, 200));
But the thumbnail created is at a really bad time. It is exactly when the video is black. Now i have no use of a completely black Thumbnail.
How can i take a Thumbnail of my video at a specific time? E.g. at 00:31 or at 01:44?
Or is it not possible?
I tried also to use MediaMetadataRetriever, but i get only a white image. Code looks like this
File tempVideoList[] = (Environment.getExternalStoragePublicDirectory(PATH_VIDEO_GALLERY))
.listFiles();
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
Bitmap myBitmap=null;
for (File videoFile : tempVideoList) {
if (videoFile.isFile()) {
//from here
try {
retriever.setDataSource(videoFile.getAbsolutePath());
myBitmap = retriever.getFrameAtTime(11); //at 11th second
} catch (Exception ex) {
Log.i("MyDebugCode", "MediaMetadataRetriever got exception:" + ex);
}
videoGalleryThumbnails.add(myBitmap);
//to here
}
If i replace the code marked as "from here" to "to here" with the top first code, it works.
I also tried MediaMetadataRetriever.OPTION_CLOSEST and OPTION_CLOSEST_SYNC and OPTION_NEXT_SYNC.
Ok i got it.
The MediaMetadataRetriever was absolutely the right way to go. The problem is, that it counts the time in microseconds and not in seconds. Solution looks like this:
try {
retriever.setDataSource(videoFile.getAbsolutePath());
int timeInSeconds = 30;
myBitmap = retriever.getFrameAtTime(timeInSeconds * 1000000,
MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
} catch (Exception ex) {
Log.i("MyDebugCode", "MediaMetadataRetriever got exception:" + ex);
}
videoGalleryThumbnails.add(myBitmap);
I don't know, if OPTION_CLOSEST_SYNC is actually needed, but it looks like this is the better way for programming.
Thanks to William Riley for pointing me in the right direction.
need little changes for this code:
try {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
//have to control the version for the setDataSource
if (Build.VERSION.SDK_INT >= 14)
retriever.setDataSource(video_path, new HashMap<String, String>());
else
retriever.setDataSource(video_path);
int timeInSeconds = 5;
Bitmap thumb = retriever.getFrameAtTime(timeInSeconds * 1000000,
MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
imageViewThumb.setImageBitmap(thumb);
} catch (Exception ex) {
ex.printStackTrace();
}
if we don't control the version for "setDataSource" then we will get exceptions. for me it was not working until version controlling.
I need to get a frame of a video file (it may be on sdcard, cache dir or app dir). I have package android.media in my application and inside I have class MediaMetadataRetriever. To get first frame into a bitmap, I use code:
public static Bitmap getVideoFrame(Context context, Uri videoUri) {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY);
retriever.setDataSource(context, videoUri);
return retriever.captureFrame();
} catch (IllegalArgumentException ex) {
throw new RuntimeException();
} catch (RuntimeException ex) {
throw new RuntimeException();
} finally {
retriever.release();
}
}
But this it's not working. It throws an exception (java.lang.RuntimeException: setDataSource failed: status = 0x80000000) when I set data source. Do you know how to make this code to work? Or Do you have any similar (simple) solution without using ffmpeg or other external libraries? videoUri is a valid uri (media player can play video from that URI)
The following works for me:
public static Bitmap getVideoFrame(FileDescriptor FD) {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setDataSource(FD);
return retriever.getFrameAtTime();
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
} catch (RuntimeException ex) {
ex.printStackTrace();
} finally {
try {
retriever.release();
} catch (RuntimeException ex) {
}
}
return null;
}
Also works if you use a path instead of a filedescriptor.
Try this, I've used it and its working
public static Bitmap getVideoFrame(Context context, Uri uri) {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setDataSource(uri.toString(),new HashMap<String, String>());
return retriever.getFrameAtTime();
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
} catch (RuntimeException ex) {
ex.printStackTrace();
} finally {
try {
retriever.release();
} catch (RuntimeException ex) {
}
}
return null;
}
In place of uri you can directly pass your url .
I used this code and that is working for me. you can try this one.
if (Build.VERSION.SDK_INT >= 14) {
ffmpegMetaDataRetriever.setDataSource(
videoFile.getAbsolutePath(),
new HashMap<String, String>());
} else {
ffmpegMetaDataRetriever.setDataSource(videoFile
.getAbsolutePath());
}
I have the same mistake on my application. I saw on this site that
this is an unofficial way of doing it
and it will only work in cupcake (and
maybe later version). The Android team
does not guarantee that
libmedia_jni.so, which the java file
uses, will be included or have the
same interface in future versions.
http://osdir.com/ml/AndroidDevelopers/2009-06/msg02442.html
I have updated my phone to GingerBread and it doesn't work anymore.
Uri's are not very specific. Sometimes they refer to something in a bundle. They often need to be translated to an absolute path form. The other instance in which you used the Uri, it probably was smart enough to check what kind of Uri it was. This case that you have shown appears to be not looking very hard.
I was getting the same error using the ThumbnailUtils class http://developer.android.com/reference/android/media/ThumbnailUtils.html
It uses MediaMetadataRetriever under the hood and most of the time you can send it a filepath using this method with no problem:
public static Bitmap createVideoThumbnail (String filePath, int kind)
However, on Android 4.0.4, I kept getting the same error #gabi was seeing. Using a file descriptor instead solved the problem and still works for non-4.0.4 devices. I actually ended up subclassing ThumbnailUtils. Here is my subclass method:
public static Bitmap createVideoThumbnail(FileDescriptor fDescriptor, int kind)
{
Bitmap bitmap = null;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setDataSource(fDescriptor);
bitmap = retriever.getFrameAtTime(-1);
}
catch (IllegalArgumentException ex) {
// Assume this is a corrupt video file
Log.e(LOG_TAG, "Failed to create video thumbnail for file description: " + fDescriptor.toString());
}
catch (RuntimeException ex) {
// Assume this is a corrupt video file.
Log.e(LOG_TAG, "Failed to create video thumbnail for file description: " + fDescriptor.toString());
} finally {
try {
retriever.release();
} catch (RuntimeException ex) {
// Ignore failures while cleaning up.
}
}
if (bitmap == null) return null;
if (kind == Images.Thumbnails.MINI_KIND) {
// Scale down the bitmap if it's too large.
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int max = Math.max(width, height);
if (max > 512) {
float scale = 512f / max;
int w = Math.round(scale * width);
int h = Math.round(scale * height);
bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true);
}
} else if (kind == Images.Thumbnails.MICRO_KIND) {
bitmap = extractThumbnail(bitmap,
TARGET_SIZE_MICRO_THUMBNAIL,
TARGET_SIZE_MICRO_THUMBNAIL,
OPTIONS_RECYCLE_INPUT);
}
return bitmap;
}
The exception is thrown also when the File doesn't exist. So before calling setDataSource() you'd better check if new File(url).exists().
so is there a specific way to get the frame from video as
File sdcard = Environment.getExternalStorageDirectory();
File file = new File(sdcard, "myvideo.mp4");