Add: java of FFmpegMediaRetriever

pull/209/head
xufulong 3 years ago
parent cc0eb0a8f3
commit 6ba160954a
  1. 14
      app/src/main/cpp/metadata/media_retriever.cpp
  2. 37
      app/src/main/cpp/metadata/media_retriever_jni.cpp
  3. 322
      app/src/main/java/com/frank/ffmpeg/metadata/FFmpegMediaRetriever.java

@ -13,42 +13,42 @@ MediaRetriever::MediaRetriever()
MediaRetriever::~MediaRetriever()
{
Mutex::Autolock _l(mLock);
Mutex::Autolock lock(mLock);
::release(&state);
}
int MediaRetriever::setDataSource(const char *srcUrl)
{
Mutex::Autolock _l(mLock);
Mutex::Autolock lock(mLock);
return ::set_data_source(&state, srcUrl);
}
int MediaRetriever::setDataSource(int fd, int64_t offset, int64_t length)
{
Mutex::Autolock _l(mLock);
Mutex::Autolock lock(mLock);
return ::set_data_source_fd(&state, fd, offset, length);
}
const char* MediaRetriever::extractMetadata(const char *key)
{
Mutex::Autolock _l(mLock);
Mutex::Autolock lock(mLock);
return ::extract_metadata(&state, key);
}
int MediaRetriever::getFrameAtTime(int64_t timeUs, int option, AVPacket *pkt)
{
Mutex::Autolock _l(mLock);
Mutex::Autolock lock(mLock);
return ::get_frame_at_time(&state, timeUs, option, pkt);
}
int MediaRetriever::getScaledFrameAtTime(int64_t timeUs, int option, AVPacket *pkt, int width, int height)
{
Mutex::Autolock _l(mLock);
Mutex::Autolock lock(mLock);
return ::get_scaled_frame_at_time(&state, timeUs, option, pkt, width, height);
}
int MediaRetriever::setNativeWindow(ANativeWindow* native_window)
{
Mutex::Autolock _l(mLock);
Mutex::Autolock lock(mLock);
return ::set_native_window(&state, native_window);
}

@ -19,6 +19,9 @@ struct fields_t {
static fields_t fields;
static const char* mClassName = "com/frank/ffmpeg/metadata/FFmpegMediaRetriever";
static const char* mNoAvailableMsg = "No retriever available";
static const char* mIllegalStateException = "java/lang/IllegalStateException";
static const char* mIllegalArgException = "java/lang/IllegalArgumentException";
static jstring NewStringUTF(JNIEnv* env, const char* data) {
jstring str = nullptr;
@ -54,7 +57,7 @@ void jniThrowException(JNIEnv* env, const char* className,
static void process_retriever_call(JNIEnv *env, int status, const char* exception, const char *message)
{
if (status == -2) {
jniThrowException(env, "java/lang/IllegalStateException", nullptr);
jniThrowException(env, mIllegalStateException, nullptr);
} else if (status == -1) {
if (strlen(message) > 520) {
jniThrowException( env, exception, message);
@ -90,7 +93,7 @@ RETRIEVER_FUNC(void, native_1init)
return;
}
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
fields.context = env->GetFieldID(clazz, "mNativeRetriever", "J");
if (fields.context == nullptr) {
return;
}
@ -102,11 +105,11 @@ RETRIEVER_FUNC(void, native_1init)
RETRIEVER_FUNC(void, native_1setDataSource, jstring path) {
MediaRetriever* retriever = getRetriever(env, thiz);
if (retriever == nullptr) {
jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
jniThrowException(env, mIllegalStateException, mNoAvailableMsg);
return;
}
if (!path) {
jniThrowException(env, "java/lang/IllegalArgumentException", "Null pointer");
jniThrowException(env, mIllegalArgException, "Null of path");
return;
}
const char *tmp = env->GetStringUTFChars(path, nullptr);
@ -114,12 +117,8 @@ RETRIEVER_FUNC(void, native_1setDataSource, jstring path) {
return;
}
process_retriever_call(
env,
retriever->setDataSource(tmp),
"java/lang/IllegalArgumentException",
"setDataSource failed");
process_retriever_call(env, retriever->setDataSource(tmp),
mIllegalArgException,"setDataSource failed");
env->ReleaseStringUTFChars(path, tmp);
}
@ -144,28 +143,28 @@ RETRIEVER_FUNC(void, native_1setDataSourceFD, jobject fileDescriptor, jlong offs
}
MediaRetriever* retriever = getRetriever(env, thiz);
if (retriever == nullptr) {
jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
jniThrowException(env, mIllegalStateException, mNoAvailableMsg);
return;
}
if (!fileDescriptor) {
jniThrowException(env, "java/lang/IllegalArgumentException", nullptr);
jniThrowException(env, mIllegalArgException, nullptr);
return;
}
int fd = getFileDescriptor(env, fileDescriptor);
if (fd < 0) {
LOGE(LOG_TAG, "invalid file descriptor!");
jniThrowException(env, "java/lang/IllegalArgumentException", nullptr);
jniThrowException(env, mIllegalArgException, nullptr);
return;
}
process_retriever_call(env, retriever->setDataSource(fd, offset, length),
"java/lang/RuntimeException", "setDataSource failed");
"java/lang/IOException", "setDataSource failed");
}
RETRIEVER_FUNC(void, native_1setSurface, jobject surface)
{
MediaRetriever* retriever = getRetriever(env, thiz);
if (retriever == nullptr) {
jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
jniThrowException(env, mIllegalStateException, mNoAvailableMsg);
return;
}
ANativeWindow *mNativeWindow = ANativeWindow_fromSurface(env, surface);
@ -178,11 +177,11 @@ RETRIEVER_FUNC(jobject, native_1extractMetadata, jstring jkey)
{
MediaRetriever* retriever = getRetriever(env, thiz);
if (retriever == nullptr) {
jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
jniThrowException(env, mIllegalStateException, mNoAvailableMsg);
return nullptr;
}
if (!jkey) {
jniThrowException(env, "java/lang/IllegalArgumentException", "Null pointer");
jniThrowException(env, mIllegalArgException, "Null of key");
return nullptr;
}
const char *key = env->GetStringUTFChars(jkey, nullptr);
@ -201,7 +200,7 @@ RETRIEVER_FUNC(jbyteArray, native_1getFrameAtTime, jlong timeUs, jint option)
{
MediaRetriever* retriever = getRetriever(env, thiz);
if (retriever == nullptr) {
jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
jniThrowException(env, mIllegalStateException, mNoAvailableMsg);
return nullptr;
}
@ -233,7 +232,7 @@ RETRIEVER_FUNC(jbyteArray, native_1getScaleFrameAtTime, jlong timeUs, jint optio
{
MediaRetriever* retriever = getRetriever(env, thiz);
if (retriever == nullptr) {
jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
jniThrowException(env, mIllegalStateException, mNoAvailableMsg);
return nullptr;
}

@ -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…
Cancel
Save