parent
cc0eb0a8f3
commit
6ba160954a
@ -0,0 +1,322 @@ |
|||||||
|
|
||||||
|
package com.frank.ffmpeg.metadata; |
||||||
|
|
||||||
|
import android.content.ContentResolver; |
||||||
|
import android.content.Context; |
||||||
|
import android.content.res.AssetFileDescriptor; |
||||||
|
import android.graphics.Bitmap; |
||||||
|
import android.graphics.BitmapFactory; |
||||||
|
import android.net.Uri; |
||||||
|
import android.util.Log; |
||||||
|
|
||||||
|
import java.io.FileDescriptor; |
||||||
|
import java.io.FileNotFoundException; |
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieve frame and extract metadata from an input media file. |
||||||
|
*/ |
||||||
|
public class FFmpegMediaRetriever { |
||||||
|
|
||||||
|
static { |
||||||
|
try { |
||||||
|
System.loadLibrary("media-handle"); |
||||||
|
} catch (UnsatisfiedLinkError e) { |
||||||
|
Log.e("FFmpegMediaRetriever", "loadLibrary error=" + e.toString()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// The field is accessed by native method
|
||||||
|
private long mNativeRetriever; |
||||||
|
|
||||||
|
public FFmpegMediaRetriever() { |
||||||
|
native_init(); |
||||||
|
native_setup(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setDataSource(String path) { |
||||||
|
native_setDataSource(path); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the data source (FileDescriptor) to use. It is the caller's |
||||||
|
* responsibility to close the file descriptor. It is safe to do so as soon |
||||||
|
* as this call returns. Call this method before the rest of the methods in |
||||||
|
* this class. This method may be time-consuming. |
||||||
|
* |
||||||
|
* @param fd the FileDescriptor for the file you want to play |
||||||
|
*/ |
||||||
|
public void setDataSource(FileDescriptor fd) |
||||||
|
throws IllegalArgumentException { |
||||||
|
if (fd == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
native_setDataSourceFD(fd, 0, 0x7ffffffffffffffL); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the data source as a content Uri. Call this method before |
||||||
|
* the rest of the methods in this class. This method may be time-consuming. |
||||||
|
* |
||||||
|
* @param context the Context to use when resolving the Uri |
||||||
|
* @param uri the Content URI of the data you want to play |
||||||
|
* |
||||||
|
*/ |
||||||
|
public void setDataSource(Context context, Uri uri) throws IllegalArgumentException, SecurityException { |
||||||
|
if (uri == null) { |
||||||
|
throw new IllegalArgumentException(); |
||||||
|
} |
||||||
|
|
||||||
|
String scheme = uri.getScheme(); |
||||||
|
if(scheme == null || scheme.equals("file")) { |
||||||
|
setDataSource(uri.getPath()); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
AssetFileDescriptor fd = null; |
||||||
|
try { |
||||||
|
ContentResolver resolver = context.getContentResolver(); |
||||||
|
try { |
||||||
|
fd = resolver.openAssetFileDescriptor(uri, "r"); |
||||||
|
} catch(FileNotFoundException e) { |
||||||
|
throw new IllegalArgumentException(); |
||||||
|
} |
||||||
|
if (fd == null) { |
||||||
|
throw new IllegalArgumentException(); |
||||||
|
} |
||||||
|
FileDescriptor descriptor = fd.getFileDescriptor(); |
||||||
|
if (!descriptor.valid()) { |
||||||
|
throw new IllegalArgumentException(); |
||||||
|
} |
||||||
|
if (fd.getDeclaredLength() < 0) { |
||||||
|
setDataSource(descriptor); |
||||||
|
} else { |
||||||
|
native_setDataSourceFD(descriptor, fd.getStartOffset(), fd.getDeclaredLength()); |
||||||
|
} |
||||||
|
return; |
||||||
|
} catch (SecurityException ex) { |
||||||
|
ex.printStackTrace(); |
||||||
|
} finally { |
||||||
|
try { |
||||||
|
if (fd != null) { |
||||||
|
fd.close(); |
||||||
|
} |
||||||
|
} catch(IOException ioEx) { |
||||||
|
ioEx.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
setDataSource(uri.toString()); |
||||||
|
} |
||||||
|
|
||||||
|
public String extractMetadata(String key) { |
||||||
|
if (key == null || key.isEmpty()) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return native_extractMetadata(key); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Call this method after setDataSource(). This method finds a |
||||||
|
* representative frame close to the given time position by considering |
||||||
|
* the given option if possible, and returns it as a bitmap. This is |
||||||
|
* useful for generating a thumbnail for an input data source or just |
||||||
|
* obtain and display a frame at the given time position. |
||||||
|
* |
||||||
|
* @param timeUs The time position where the frame will be retrieved. |
||||||
|
* |
||||||
|
* @param option a hint on how the frame is found. Use |
||||||
|
* {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame |
||||||
|
* that has a timestamp earlier than or the same as timeUs. Use |
||||||
|
* {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame |
||||||
|
* that has a timestamp later than or the same as timeUs. Use |
||||||
|
* {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame |
||||||
|
* that has a timestamp closest to or the same as timeUs. Use |
||||||
|
* {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may |
||||||
|
* or may not be a sync frame but is closest to or the same as timeUs. |
||||||
|
* {@link #OPTION_CLOSEST} often has larger performance overhead compared |
||||||
|
* to the other options if there is no sync frame located at timeUs. |
||||||
|
* |
||||||
|
* @return A Bitmap containing a representative video frame, which |
||||||
|
* can be null, if such a frame cannot be retrieved. |
||||||
|
*/ |
||||||
|
public Bitmap getFrameAtTime(long timeUs, int option) { |
||||||
|
if (option < OPTION_PREVIOUS_SYNC || |
||||||
|
option > OPTION_CLOSEST) { |
||||||
|
throw new IllegalArgumentException("Unsupported option: " + option); |
||||||
|
} |
||||||
|
|
||||||
|
BitmapFactory.Options bitmapOptions= new BitmapFactory.Options(); |
||||||
|
bitmapOptions.inScaled = true; |
||||||
|
byte [] picture = native_getFrameAtTime(timeUs, option); |
||||||
|
if (picture != null) { |
||||||
|
return BitmapFactory.decodeByteArray(picture, 0, picture.length, bitmapOptions); |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public Bitmap getFrameAtTime(long timeUs) { |
||||||
|
return getFrameAtTime(timeUs, OPTION_CLOSEST_SYNC); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Call this method after setDataSource(). This method finds a |
||||||
|
* representative frame close to the given time position by considering |
||||||
|
* the given option if possible, and returns it as a bitmap. This is |
||||||
|
* useful for generating a thumbnail for an input data source or just |
||||||
|
* obtain and display a frame at the given time position. |
||||||
|
* |
||||||
|
* @param timeUs The time position where the frame will be retrieved. |
||||||
|
* |
||||||
|
* @param option a hint on how the frame is found. Use |
||||||
|
* {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame |
||||||
|
* that has a timestamp earlier than or the same as timeUs. Use |
||||||
|
* {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame |
||||||
|
* that has a timestamp later than or the same as timeUs. Use |
||||||
|
* {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame |
||||||
|
* that has a timestamp closest to or the same as timeUs. Use |
||||||
|
* {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may |
||||||
|
* or may not be a sync frame but is closest to or the same as timeUs. |
||||||
|
* {@link #OPTION_CLOSEST} often has larger performance overhead compared |
||||||
|
* to the other options if there is no sync frame located at timeUs. |
||||||
|
* |
||||||
|
* @return A Bitmap containing a representative video frame, which |
||||||
|
* can be null, if such a frame cannot be retrieved. |
||||||
|
*/ |
||||||
|
public Bitmap getScaledFrameAtTime(long timeUs, int option, int width, int height) { |
||||||
|
if (option < OPTION_PREVIOUS_SYNC || |
||||||
|
option > OPTION_CLOSEST) { |
||||||
|
throw new IllegalArgumentException("Unsupported option: " + option); |
||||||
|
} |
||||||
|
|
||||||
|
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); |
||||||
|
bitmapOptions.inScaled = true; |
||||||
|
byte [] picture = native_getScaleFrameAtTime(timeUs, option, width, height); |
||||||
|
if (picture != null) { |
||||||
|
return BitmapFactory.decodeByteArray(picture, 0, picture.length, bitmapOptions); |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public Bitmap getScaledFrameAtTime(long timeUs, int width, int height) { |
||||||
|
return getScaledFrameAtTime(timeUs, OPTION_CLOSEST_SYNC, width, height); |
||||||
|
} |
||||||
|
|
||||||
|
public void release() { |
||||||
|
native_release(); |
||||||
|
} |
||||||
|
|
||||||
|
private native void native_setup(); |
||||||
|
|
||||||
|
private native void native_init(); |
||||||
|
|
||||||
|
private native void native_setDataSource(String path) throws IllegalArgumentException; |
||||||
|
|
||||||
|
private native void native_setDataSourceFD(FileDescriptor fd, long offset, long length) throws IllegalArgumentException; |
||||||
|
|
||||||
|
private native String native_extractMetadata(String key); |
||||||
|
|
||||||
|
private native void native_setSurface(Object surface); |
||||||
|
|
||||||
|
private native byte[] native_getFrameAtTime(long timeUs, int option); |
||||||
|
|
||||||
|
private native byte[] native_getScaleFrameAtTime(long timeUs, int option, int width, int height); |
||||||
|
|
||||||
|
private native void native_release(); |
||||||
|
|
||||||
|
/** |
||||||
|
* This option is used with {@link #getFrameAtTime(long, int)} to retrieve |
||||||
|
* a sync (or key) frame associated with a data source that is located |
||||||
|
* right before or at the given time. |
||||||
|
* |
||||||
|
* @see #getFrameAtTime(long, int) |
||||||
|
*/ |
||||||
|
int OPTION_PREVIOUS_SYNC = 0x00; |
||||||
|
/** |
||||||
|
* This option is used with {@link #getFrameAtTime(long, int)} to retrieve |
||||||
|
* a sync (or key) frame associated with a data source that is located |
||||||
|
* right after or at the given time. |
||||||
|
* |
||||||
|
* @see #getFrameAtTime(long, int) |
||||||
|
*/ |
||||||
|
int OPTION_NEXT_SYNC = 0x01; |
||||||
|
/** |
||||||
|
* This option is used with {@link #getFrameAtTime(long, int)} to retrieve |
||||||
|
* a sync (or key) frame associated with a data source that is located |
||||||
|
* closest to (in time) or at the given time. |
||||||
|
* |
||||||
|
* @see #getFrameAtTime(long, int) |
||||||
|
*/ |
||||||
|
int OPTION_CLOSEST_SYNC = 0x02; |
||||||
|
/** |
||||||
|
* This option is used with {@link #getFrameAtTime(long, int)} to retrieve |
||||||
|
* a frame (not necessarily a key frame) associated with a data source that |
||||||
|
* is located closest to or at the given time. |
||||||
|
* |
||||||
|
* @see #getFrameAtTime(long, int) |
||||||
|
*/ |
||||||
|
int OPTION_CLOSEST = 0x03; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* The metadata key to retrieve the original name of the file. |
||||||
|
*/ |
||||||
|
public static String METADATA_KEY_FILENAME = "filename"; |
||||||
|
/** |
||||||
|
* The metadata key to retrieve the main language in which the work is performed, preferably |
||||||
|
* in ISO 639-2 format. Multiple languages can be specified by separating them with commas. |
||||||
|
*/ |
||||||
|
public static String METADATA_KEY_LANGUAGE = "language"; |
||||||
|
/** |
||||||
|
* The metadata key to retrieve the name of the work. |
||||||
|
*/ |
||||||
|
public static String METADATA_KEY_TITLE = "title"; |
||||||
|
/** |
||||||
|
* The metadata key to retrieve the number of this work in the set, can be in form current/total. |
||||||
|
*/ |
||||||
|
public static String METADATA_KEY_TRACK = "track"; |
||||||
|
/** |
||||||
|
* The metadata key to retrieve the total bitrate of the bitrate variant that the current stream |
||||||
|
* is part of. |
||||||
|
*/ |
||||||
|
public static String METADATA_KEY_VARIANT_BITRATE = "bitrate"; |
||||||
|
/** |
||||||
|
* The metadata key to retrieve the duration of the work in milliseconds. |
||||||
|
*/ |
||||||
|
public static String METADATA_KEY_DURATION = "duration"; |
||||||
|
/** |
||||||
|
* The metadata key to retrieve the audio codec of the work. |
||||||
|
*/ |
||||||
|
public static String METADATA_KEY_AUDIO_CODEC = "audio_codec"; |
||||||
|
/** |
||||||
|
* The metadata key to retrieve the video codec of the work. |
||||||
|
*/ |
||||||
|
public static String METADATA_KEY_VIDEO_CODEC = "video_codec"; |
||||||
|
/** |
||||||
|
* This key retrieves the video rotation angle in degrees, if available. |
||||||
|
* The video rotation angle may be 0, 90, 180, or 270 degrees. |
||||||
|
*/ |
||||||
|
public static String METADATA_KEY_VIDEO_ROTATION = "rotate"; |
||||||
|
/** |
||||||
|
* This metadata key retrieves the average framerate (in frames/sec). |
||||||
|
*/ |
||||||
|
public static String METADATA_KEY_FRAMERATE = "framerate"; |
||||||
|
/** |
||||||
|
* The metadata key to retrieve the file size in bytes. |
||||||
|
*/ |
||||||
|
public static String METADATA_KEY_FILESIZE = "filesize"; |
||||||
|
/** |
||||||
|
* The metadata key to retrieve the video width. |
||||||
|
*/ |
||||||
|
public static String METADATA_KEY_VIDEO_WIDTH = "video_width"; |
||||||
|
/** |
||||||
|
* The metadata key to retrieve the video height. |
||||||
|
*/ |
||||||
|
public static String METADATA_KEY_VIDEO_HEIGHT = "video_height"; |
||||||
|
/** |
||||||
|
* The metadata key to retrieve the mime type. |
||||||
|
*/ |
||||||
|
public static String METADATA_KEY_MIME_TYPE = "mime_type"; |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue