update android api, fixes #1

pull/7/head
Taner Sener 5 years ago
parent 18f6dd0099
commit a1e7a0b9da
  1. 1
      android/app/build.gradle
  2. 3
      android/app/src/main/AndroidManifest.xml
  3. 194
      android/app/src/main/cpp/ffmpegkit.c
  4. 11
      android/app/src/main/cpp/ffmpegkit.h
  5. 2
      android/app/src/main/cpp/ffmpegkit_abidetect.c
  6. 2
      android/app/src/main/cpp/ffmpegkit_abidetect.h
  7. 2
      android/app/src/main/cpp/ffmpegkit_exception.c
  8. 2
      android/app/src/main/cpp/ffmpegkit_exception.h
  9. 19
      android/app/src/main/cpp/ffprobekit.c
  10. 6
      android/app/src/main/cpp/ffprobekit.h
  11. 2
      android/app/src/main/cpp/fftools_cmdutils.h
  12. 16
      android/app/src/main/cpp/fftools_ffmpeg.c
  13. 135
      android/app/src/main/cpp/saf_wrapper.c
  14. 46
      android/app/src/main/cpp/saf_wrapper.h
  15. 10
      android/app/src/main/java/com/arthenica/ffmpegkit/Abi.java
  16. 8
      android/app/src/main/java/com/arthenica/ffmpegkit/AbiDetect.java
  17. 197
      android/app/src/main/java/com/arthenica/ffmpegkit/AbstractSession.java
  18. 45
      android/app/src/main/java/com/arthenica/ffmpegkit/AsyncFFmpegExecuteTask.java
  19. 34
      android/app/src/main/java/com/arthenica/ffmpegkit/AsyncFFprobeExecuteTask.java
  20. 29
      android/app/src/main/java/com/arthenica/ffmpegkit/AsyncGetMediaInformationTask.java
  21. 6
      android/app/src/main/java/com/arthenica/ffmpegkit/CameraSupport.java
  22. 12
      android/app/src/main/java/com/arthenica/ffmpegkit/ExecuteCallback.java
  23. 192
      android/app/src/main/java/com/arthenica/ffmpegkit/FFmpegKit.java
  24. 736
      android/app/src/main/java/com/arthenica/ffmpegkit/FFmpegKitConfig.java
  25. 93
      android/app/src/main/java/com/arthenica/ffmpegkit/FFmpegSession.java
  26. 292
      android/app/src/main/java/com/arthenica/ffmpegkit/FFprobeKit.java
  27. 98
      android/app/src/main/java/com/arthenica/ffmpegkit/FFprobeSession.java
  28. 6
      android/app/src/main/java/com/arthenica/ffmpegkit/Level.java
  29. 35
      android/app/src/main/java/com/arthenica/ffmpegkit/Log.java
  30. 11
      android/app/src/main/java/com/arthenica/ffmpegkit/LogCallback.java
  31. 21
      android/app/src/main/java/com/arthenica/ffmpegkit/MediaInformation.java
  32. 13
      android/app/src/main/java/com/arthenica/ffmpegkit/MediaInformationParser.java
  33. 74
      android/app/src/main/java/com/arthenica/ffmpegkit/MediaInformationSession.java
  34. 8
      android/app/src/main/java/com/arthenica/ffmpegkit/Packages.java
  35. 34
      android/app/src/main/java/com/arthenica/ffmpegkit/ReturnCode.java
  36. 88
      android/app/src/main/java/com/arthenica/ffmpegkit/Session.java
  37. 15
      android/app/src/main/java/com/arthenica/ffmpegkit/SessionState.java
  38. 4
      android/app/src/main/java/com/arthenica/ffmpegkit/Signal.java
  39. 57
      android/app/src/main/java/com/arthenica/ffmpegkit/Statistics.java
  40. 9
      android/app/src/main/java/com/arthenica/ffmpegkit/StatisticsCallback.java
  41. 3
      android/app/src/main/java/com/arthenica/ffmpegkit/StreamInformation.java
  42. 44
      android/app/src/test/java/com/arthenica/ffmpegkit/AbstractSessionTest.java
  43. 8
      android/jni/Android.mk
  44. 1
      tools/release/android/build.gradle
  45. 1
      tools/release/android/build.lts.gradle

@ -39,6 +39,7 @@ task javadoc(type: Javadoc) {
} }
dependencies { dependencies {
implementation 'com.arthenica:smart-exception-java:0.1.0'
testImplementation "androidx.test.ext:junit:1.1.2" testImplementation "androidx.test.ext:junit:1.1.2"
testImplementation "org.json:json:20190722" testImplementation "org.json:json:20190722"
} }

@ -1,2 +1 @@
<manifest package="com.arthenica.ffmpegkit"> <manifest package="com.arthenica.ffmpegkit" />
</manifest>

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -31,7 +31,7 @@
/** Callback data structure */ /** Callback data structure */
struct CallbackData { struct CallbackData {
int type; // 1 (log callback) or 2 (statistics callback) int type; // 1 (log callback) or 2 (statistics callback)
long executionId; // execution id long sessionId; // session identifier
int logLevel; // log level int logLevel; // log level
AVBPrint logData; // log data AVBPrint logData; // log data
@ -47,20 +47,16 @@ struct CallbackData {
struct CallbackData *next; struct CallbackData *next;
}; };
/** Execution map variables */ /** Session map variables */
const int EXECUTION_MAP_SIZE = 1000; const int SESSION_MAP_SIZE = 1000;
static volatile int executionMap[EXECUTION_MAP_SIZE]; static volatile int sessionMap[SESSION_MAP_SIZE];
static pthread_mutex_t executionMapMutex; static pthread_mutex_t sessionMapMutex;
/** Redirection control variables */ /** Redirection control variables */
static pthread_mutex_t lockMutex; static pthread_mutex_t lockMutex;
static pthread_mutex_t monitorMutex; static pthread_mutex_t monitorMutex;
static pthread_cond_t monitorCondition; static pthread_cond_t monitorCondition;
/** Last command output variables */
static pthread_mutex_t logMutex;
static AVBPrint lastCommandOutput;
pthread_t callbackThread; pthread_t callbackThread;
int redirectionEnabled; int redirectionEnabled;
@ -79,6 +75,9 @@ static jmethodID logMethod;
/** Global reference of statistics redirection method in Java */ /** Global reference of statistics redirection method in Java */
static jmethodID statisticsMethod; static jmethodID statisticsMethod;
/** Global reference of closeParcelFileDescriptor method in Java */
static jmethodID closeParcelFileDescriptorMethod;
/** Global reference of String class in Java */ /** Global reference of String class in Java */
static jclass stringClass; static jclass stringClass;
@ -86,7 +85,7 @@ static jclass stringClass;
static jmethodID stringConstructor; static jmethodID stringConstructor;
/** Full name of the Config class */ /** Full name of the Config class */
const char *configClassName = "com/arthenica/ffmpegkit/Config"; const char *configClassName = "com/arthenica/ffmpegkit/FFmpegKitConfig";
/** Full name of String class */ /** Full name of String class */
const char *stringClassName = "java/lang/String"; const char *stringClassName = "java/lang/String";
@ -98,8 +97,8 @@ volatile int handleSIGTERM = 1;
volatile int handleSIGXCPU = 1; volatile int handleSIGXCPU = 1;
volatile int handleSIGPIPE = 1; volatile int handleSIGPIPE = 1;
/** Holds the id of the current execution */ /** Holds the id of the current session */
__thread volatile long executionId = 0; __thread volatile long sessionId = 0;
/** Holds the default log level */ /** Holds the default log level */
int configuredLogLevel = AV_LOG_INFO; int configuredLogLevel = AV_LOG_INFO;
@ -118,7 +117,6 @@ JNINativeMethod configMethods[] = {
{"registerNewNativeFFmpegPipe", "(Ljava/lang/String;)I", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_registerNewNativeFFmpegPipe}, {"registerNewNativeFFmpegPipe", "(Ljava/lang/String;)I", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_registerNewNativeFFmpegPipe},
{"getNativeBuildDate", "()Ljava/lang/String;", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeBuildDate}, {"getNativeBuildDate", "()Ljava/lang/String;", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeBuildDate},
{"setNativeEnvironmentVariable", "(Ljava/lang/String;Ljava/lang/String;)I", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_setNativeEnvironmentVariable}, {"setNativeEnvironmentVariable", "(Ljava/lang/String;Ljava/lang/String;)I", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_setNativeEnvironmentVariable},
{"getNativeLastCommandOutput", "()Ljava/lang/String;", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeLastCommandOutput},
{"ignoreNativeSignal", "(I)V", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_ignoreNativeSignal} {"ignoreNativeSignal", "(I)V", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_ignoreNativeSignal}
}; };
@ -215,23 +213,12 @@ void monitorInit() {
pthread_condattr_destroy(&cattributes); pthread_condattr_destroy(&cattributes);
} }
void logInit() {
pthread_mutexattr_t attributes;
pthread_mutexattr_init(&attributes);
pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE_NP);
pthread_mutex_init(&logMutex, &attributes);
pthread_mutexattr_destroy(&attributes);
av_bprint_init(&lastCommandOutput, 0, AV_BPRINT_SIZE_UNLIMITED);
}
void executionMapLockInit() { void executionMapLockInit() {
pthread_mutexattr_t attributes; pthread_mutexattr_t attributes;
pthread_mutexattr_init(&attributes); pthread_mutexattr_init(&attributes);
pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE_NP); pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE_NP);
pthread_mutex_init(&executionMapMutex, &attributes); pthread_mutex_init(&sessionMapMutex, &attributes);
pthread_mutexattr_destroy(&attributes); pthread_mutexattr_destroy(&attributes);
} }
@ -244,52 +231,24 @@ void monitorUnInit() {
pthread_cond_destroy(&monitorCondition); pthread_cond_destroy(&monitorCondition);
} }
void logUnInit() {
pthread_mutex_destroy(&logMutex);
}
void executionMapLockUnInit() { void executionMapLockUnInit() {
pthread_mutex_destroy(&executionMapMutex); pthread_mutex_destroy(&sessionMapMutex);
} }
void mutexLock() { void mutexLock() {
pthread_mutex_lock(&lockMutex); pthread_mutex_lock(&lockMutex);
} }
void lastCommandOutputLock() { void sessionMapLock() {
pthread_mutex_lock(&logMutex); pthread_mutex_lock(&sessionMapMutex);
}
void executionMapLock() {
pthread_mutex_lock(&executionMapMutex);
} }
void mutexUnlock() { void mutexUnlock() {
pthread_mutex_unlock(&lockMutex); pthread_mutex_unlock(&lockMutex);
} }
void lastCommandOutputUnlock() { void sessionMapUnlock() {
pthread_mutex_unlock(&logMutex); pthread_mutex_unlock(&sessionMapMutex);
}
void executionMapUnlock() {
pthread_mutex_unlock(&executionMapMutex);
}
void clearLastCommandOutput() {
lastCommandOutputLock();
av_bprint_clear(&lastCommandOutput);
lastCommandOutputUnlock();
}
void appendLastCommandOutput(AVBPrint *logMessage) {
if (logMessage->len <= 0) {
return;
}
lastCommandOutputLock();
av_bprintf(&lastCommandOutput, "%s", logMessage->str);
lastCommandOutputUnlock();
} }
void monitorWait(int milliSeconds) { void monitorWait(int milliSeconds) {
@ -331,7 +290,7 @@ void logCallbackDataAdd(int level, AVBPrint *data) {
// CREATE DATA STRUCT FIRST // CREATE DATA STRUCT FIRST
struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData)); struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData));
newData->type = 1; newData->type = 1;
newData->executionId = executionId; newData->sessionId = sessionId;
newData->logLevel = level; newData->logLevel = level;
av_bprint_init(&newData->logData, 0, AV_BPRINT_SIZE_UNLIMITED); av_bprint_init(&newData->logData, 0, AV_BPRINT_SIZE_UNLIMITED);
av_bprintf(&newData->logData, "%s", data->str); av_bprintf(&newData->logData, "%s", data->str);
@ -368,7 +327,7 @@ void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_
// CREATE DATA STRUCT FIRST // CREATE DATA STRUCT FIRST
struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData)); struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData));
newData->type = 2; newData->type = 2;
newData->executionId = executionId; newData->sessionId = sessionId;
newData->statisticsFrameNumber = frameNumber; newData->statisticsFrameNumber = frameNumber;
newData->statisticsFps = fps; newData->statisticsFps = fps;
newData->statisticsQuality = quality; newData->statisticsQuality = quality;
@ -403,17 +362,17 @@ void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_
} }
/** /**
* Adds an execution id to the execution map. * Adds a session id to the session map.
* *
* @param id execution id * @param id session id
*/ */
void addExecution(long id) { void addSession(long id) {
executionMapLock(); sessionMapLock();
int key = id % EXECUTION_MAP_SIZE; int key = id % SESSION_MAP_SIZE;
executionMap[key] = 1; sessionMap[key] = 1;
executionMapUnlock(); sessionMapUnlock();
} }
/** /**
@ -449,36 +408,50 @@ struct CallbackData *callbackDataRemove() {
} }
/** /**
* Removes an execution id from the execution map. * Removes a session id from the session map.
*
* @param id session id
*/
void removeSession(long id) {
sessionMapLock();
int key = id % SESSION_MAP_SIZE;
sessionMap[key] = 0;
sessionMapUnlock();
}
/**
* Adds a cancel session request to the session map.
* *
* @param id execution id * @param id session id
*/ */
void removeExecution(long id) { void cancelSession(long id) {
executionMapLock(); sessionMapLock();
int key = id % EXECUTION_MAP_SIZE; int key = id % SESSION_MAP_SIZE;
executionMap[key] = 0; sessionMap[key] = 2;
executionMapUnlock(); sessionMapUnlock();
} }
/** /**
* Checks whether a cancel request for the given execution id exists in the execution map. * Checks whether a cancel request for the given session id exists in the session map.
* *
* @param id execution id * @param id session id
* @return 1 if exists, false otherwise * @return 1 if exists, false otherwise
*/ */
int cancelRequested(long id) { int cancelRequested(long id) {
int found = 0; int found = 0;
executionMapLock(); sessionMapLock();
int key = id % EXECUTION_MAP_SIZE; int key = id % SESSION_MAP_SIZE;
if (executionMap[key] == 0) { if (sessionMap[key] == 2) {
found = 1; found = 1;
} }
executionMapUnlock(); sessionMapUnlock();
return found; return found;
} }
@ -519,7 +492,6 @@ void ffmpegkit_log_callback_function(void *ptr, int level, const char* format, v
if (fullLine.len > 0) { if (fullLine.len > 0) {
logCallbackDataAdd(level, &fullLine); logCallbackDataAdd(level, &fullLine);
appendLastCommandOutput(&fullLine);
} }
av_bprint_finalize(part, NULL); av_bprint_finalize(part, NULL);
@ -576,7 +548,7 @@ void *callbackThreadFunction() {
jbyteArray byteArray = (jbyteArray) (*env)->NewByteArray(env, size); jbyteArray byteArray = (jbyteArray) (*env)->NewByteArray(env, size);
(*env)->SetByteArrayRegion(env, byteArray, 0, size, callbackData->logData.str); (*env)->SetByteArrayRegion(env, byteArray, 0, size, callbackData->logData.str);
(*env)->CallStaticVoidMethod(env, configClass, logMethod, (jlong) callbackData->executionId, callbackData->logLevel, byteArray); (*env)->CallStaticVoidMethod(env, configClass, logMethod, (jlong) callbackData->sessionId, callbackData->logLevel, byteArray);
(*env)->DeleteLocalRef(env, byteArray); (*env)->DeleteLocalRef(env, byteArray);
// CLEAN LOG DATA // CLEAN LOG DATA
@ -587,7 +559,7 @@ void *callbackThreadFunction() {
// STATISTICS CALLBACK // STATISTICS CALLBACK
(*env)->CallStaticVoidMethod(env, configClass, statisticsMethod, (*env)->CallStaticVoidMethod(env, configClass, statisticsMethod,
(jlong) callbackData->executionId, callbackData->statisticsFrameNumber, (jlong) callbackData->sessionId, callbackData->statisticsFrameNumber,
callbackData->statisticsFps, callbackData->statisticsQuality, callbackData->statisticsFps, callbackData->statisticsQuality,
callbackData->statisticsSize, callbackData->statisticsTime, callbackData->statisticsSize, callbackData->statisticsTime,
callbackData->statisticsBitrate, callbackData->statisticsSpeed); callbackData->statisticsBitrate, callbackData->statisticsSpeed);
@ -610,6 +582,15 @@ void *callbackThreadFunction() {
return NULL; return NULL;
} }
/**
* Used by saf_wrapper; is expected to be called from a Java thread, therefore we don't need attach/detach
*/
void closeParcelFileDescriptor(int fd) {
JNIEnv *env = NULL;
(*globalVm)->GetEnv(globalVm, (void**) &env, JNI_VERSION_1_6);
(*env)->CallStaticVoidMethod(env, configClass, closeParcelFileDescriptorMethod, fd);
}
/** /**
* Called when 'ffmpegkit' native library is loaded. * Called when 'ffmpegkit' native library is loaded.
* *
@ -630,7 +611,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
return JNI_FALSE; return JNI_FALSE;
} }
if ((*env)->RegisterNatives(env, localConfigClass, configMethods, 12) < 0) { if ((*env)->RegisterNatives(env, localConfigClass, configMethods, 13) < 0) {
LOGE("OnLoad failed to RegisterNatives for class %s.\n", configClassName); LOGE("OnLoad failed to RegisterNatives for class %s.\n", configClassName);
return JNI_FALSE; return JNI_FALSE;
} }
@ -655,6 +636,12 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
return JNI_FALSE; return JNI_FALSE;
} }
closeParcelFileDescriptorMethod = (*env)->GetStaticMethodID(env, localConfigClass, "closeParcelFileDescriptor", "(I)V");
if (logMethod == NULL) {
LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "closeParcelFileDescriptor");
return JNI_FALSE;
}
stringConstructor = (*env)->GetMethodID(env, localStringClass, "<init>", "([BLjava/lang/String;)V"); stringConstructor = (*env)->GetMethodID(env, localStringClass, "<init>", "([BLjava/lang/String;)V");
if (stringConstructor == NULL) { if (stringConstructor == NULL) {
LOGE("OnLoad thread failed to GetMethodID for %s.\n", "<init>"); LOGE("OnLoad thread failed to GetMethodID for %s.\n", "<init>");
@ -671,13 +658,12 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
callbackDataHead = NULL; callbackDataHead = NULL;
callbackDataTail = NULL; callbackDataTail = NULL;
for(int i = 0; i<EXECUTION_MAP_SIZE; i++) { for(int i = 0; i<SESSION_MAP_SIZE; i++) {
executionMap[i] = 0; sessionMap[i] = 0;
} }
mutexInit(); mutexInit();
monitorInit(); monitorInit();
logInit();
executionMapLockInit(); executionMapLockInit();
return JNI_VERSION_1_6; return JNI_VERSION_1_6;
@ -782,7 +768,7 @@ JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNative
* *
* @param env pointer to native method interface * @param env pointer to native method interface
* @param object reference to the class on which this method is invoked * @param object reference to the class on which this method is invoked
* @param id execution id * @param id session id
* @param stringArray reference to the object holding FFmpeg command arguments * @param stringArray reference to the object holding FFmpeg command arguments
* @return zero on successful execution, non-zero on error * @return zero on successful execution, non-zero on error
*/ */
@ -819,18 +805,15 @@ JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpeg
} }
} }
// LAST COMMAND OUTPUT SHOULD BE CLEARED BEFORE STARTING A NEW EXECUTION
clearLastCommandOutput();
// REGISTER THE ID BEFORE STARTING EXECUTION // REGISTER THE ID BEFORE STARTING EXECUTION
executionId = (long) id; sessionId = (long) id;
addExecution((long) id); addSession((long) id);
// RUN // RUN
int retCode = ffmpeg_execute(argumentCount, argv); int retCode = ffmpeg_execute(argumentCount, argv);
// ALWAYS REMOVE THE ID FROM THE MAP // ALWAYS REMOVE THE ID FROM THE MAP
removeExecution((long) id); removeSession((long) id);
// CLEANUP // CLEANUP
if (tempArray != NULL) { if (tempArray != NULL) {
@ -851,7 +834,7 @@ JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpeg
* *
* @param env pointer to native method interface * @param env pointer to native method interface
* @param object reference to the class on which this method is invoked * @param object reference to the class on which this method is invoked
* @param id execution id * @param id session id
*/ */
JNIEXPORT void JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpegCancel(JNIEnv *env, jclass object, jlong id) { JNIEXPORT void JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpegCancel(JNIEnv *env, jclass object, jlong id) {
cancel_operation(id); cancel_operation(id);
@ -904,25 +887,6 @@ JNIEXPORT int JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_setNativeEnvi
return rc; return rc;
} }
/**
* Returns log output of the last executed command natively.
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @return output of the last executed command
*/
JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeLastCommandOutput(JNIEnv *env, jclass object) {
int size = lastCommandOutput.len;
if (size > 0) {
jbyteArray byteArray = (*env)->NewByteArray(env, size);
(*env)->SetByteArrayRegion(env, byteArray, 0, size, lastCommandOutput.str);
jstring charsetName = (*env)->NewStringUTF(env, "UTF-8");
return (jstring) (*env)->NewObject(env, stringClass, stringConstructor, byteArray, charsetName);
}
return (*env)->NewStringUTF(env, "");
}
/** /**
* Registers a new ignored signal. Ignored signals are not handled by the library. * Registers a new ignored signal. Ignored signals are not handled by the library.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -94,7 +94,7 @@ JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNative
* Method: nativeFFmpegExecute * Method: nativeFFmpegExecute
* Signature: (J[Ljava/lang/String;)I * Signature: (J[Ljava/lang/String;)I
*/ */
JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpegExecute(JNIEnv *, jclass, jlong id, jobjectArray); JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpegExecute(JNIEnv *, jclass, jlong, jobjectArray);
/* /*
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig * Class: com_arthenica_ffmpegkit_FFmpegKitConfig
@ -124,13 +124,6 @@ JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNative
*/ */
JNIEXPORT int JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_setNativeEnvironmentVariable(JNIEnv *env, jclass object, jstring variableName, jstring variableValue); JNIEXPORT int JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_setNativeEnvironmentVariable(JNIEnv *env, jclass object, jstring variableName, jstring variableValue);
/*
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
* Method: getNativeLastCommandOutput
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeLastCommandOutput(JNIEnv *env, jclass object);
/* /*
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig * Class: com_arthenica_ffmpegkit_FFmpegKitConfig
* Method: ignoreNativeSignal * Method: ignoreNativeSignal

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020 Taner Sener * Copyright (c) 2020-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -29,20 +29,21 @@
/** Forward declaration for function defined in fftools_ffprobe.c */ /** Forward declaration for function defined in fftools_ffprobe.c */
int ffprobe_execute(int argc, char **argv); int ffprobe_execute(int argc, char **argv);
/** Forward declaration for function defined in ffmpegkit.c */
void clearLastCommandOutput();
extern int configuredLogLevel; extern int configuredLogLevel;
extern __thread volatile long sessionId;
extern void addSession(long id);
extern void removeSession(long id);
/** /**
* Synchronously executes FFprobe natively with arguments provided. * Synchronously executes FFprobe natively with arguments provided.
* *
* @param env pointer to native method interface * @param env pointer to native method interface
* @param object reference to the class on which this method is invoked * @param object reference to the class on which this method is invoked
* @param id session id
* @param stringArray reference to the object holding FFprobe command arguments * @param stringArray reference to the object holding FFprobe command arguments
* @return zero on successful execution, non-zero on error * @return zero on successful execution, non-zero on error
*/ */
JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFprobeExecute(JNIEnv *env, jclass object, jobjectArray stringArray) { JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFprobeExecute(JNIEnv *env, jclass object, jlong id, jobjectArray stringArray) {
jstring *tempArray = NULL; jstring *tempArray = NULL;
int argumentCount = 1; int argumentCount = 1;
char **argv = NULL; char **argv = NULL;
@ -75,12 +76,16 @@ JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFprob
} }
} }
// LAST COMMAND OUTPUT SHOULD BE CLEARED BEFORE STARTING A NEW EXECUTION // REGISTER THE ID BEFORE STARTING EXECUTION
clearLastCommandOutput(); sessionId = (long) id;
addSession((long) id);
// RUN // RUN
int retCode = ffprobe_execute(argumentCount, argv); int retCode = ffprobe_execute(argumentCount, argv);
// ALWAYS REMOVE THE ID FROM THE MAP
removeSession((long) id);
// CLEANUP // CLEANUP
if (tempArray != NULL) { if (tempArray != NULL) {
for (int i = 0; i < (argumentCount - 1); i++) { for (int i = 0; i < (argumentCount - 1); i++) {

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020 Taner Sener * Copyright (c) 2020-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -25,8 +25,8 @@
/* /*
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig * Class: com_arthenica_ffmpegkit_FFmpegKitConfig
* Method: nativeFFprobeExecute * Method: nativeFFprobeExecute
* Signature: ([Ljava/lang/String;)I * Signature: (J[Ljava/lang/String;)I
*/ */
JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFprobeExecute(JNIEnv *, jclass, jobjectArray); JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFprobeExecute(JNIEnv *, jclass, jlong, jobjectArray);
#endif /* FFPROBE_KIT_H */ #endif /* FFPROBE_KIT_H */

@ -51,6 +51,8 @@
#include "libavformat/avformat.h" #include "libavformat/avformat.h"
#include "libswscale/swscale.h" #include "libswscale/swscale.h"
#include "saf_wrapper.h"
#ifdef _WIN32 #ifdef _WIN32
#undef main /* We don't want SDL to override our main() */ #undef main /* We don't want SDL to override our main() */
#endif #endif

@ -253,8 +253,8 @@ extern volatile int handleSIGTERM;
extern volatile int handleSIGXCPU; extern volatile int handleSIGXCPU;
extern volatile int handleSIGPIPE; extern volatile int handleSIGPIPE;
extern __thread volatile long executionId; extern __thread volatile long sessionId;
extern void removeExecution(long id); extern void cancelSession(long id);
extern int cancelRequested(long id); extern int cancelRequested(long id);
/* sub2video hack: /* sub2video hack:
@ -729,7 +729,7 @@ static void ffmpeg_cleanup(int ret)
if (received_sigterm) { if (received_sigterm) {
av_log(NULL, AV_LOG_INFO, "Exiting normally, received signal %d.\n", av_log(NULL, AV_LOG_INFO, "Exiting normally, received signal %d.\n",
(int) received_sigterm); (int) received_sigterm);
} else if (cancelRequested(executionId)) { } else if (cancelRequested(sessionId)) {
av_log(NULL, AV_LOG_INFO, "Exiting normally, received cancel signal.\n"); av_log(NULL, AV_LOG_INFO, "Exiting normally, received cancel signal.\n");
} else if (ret && atomic_load(&transcode_init_done)) { } else if (ret && atomic_load(&transcode_init_done)) {
av_log(NULL, AV_LOG_INFO, "Conversion failed!\n"); av_log(NULL, AV_LOG_INFO, "Conversion failed!\n");
@ -2392,7 +2392,7 @@ static int ifilter_send_eof(InputFilter *ifilter, int64_t pts)
if (ifilter->filter) { if (ifilter->filter) {
/* THIS VALIDATION IS REQUIRED TO COMPLETE CANCELLATION */ /* THIS VALIDATION IS REQUIRED TO COMPLETE CANCELLATION */
if (!received_sigterm && !cancelRequested(executionId)) { if (!received_sigterm && !cancelRequested(sessionId)) {
ret = av_buffersrc_close(ifilter->filter, pts, AV_BUFFERSRC_FLAG_PUSH); ret = av_buffersrc_close(ifilter->filter, pts, AV_BUFFERSRC_FLAG_PUSH);
} }
if (ret < 0) if (ret < 0)
@ -4859,7 +4859,7 @@ static int transcode(void)
goto fail; goto fail;
#endif #endif
while (!received_sigterm && !cancelRequested(executionId)) { while (!received_sigterm && !cancelRequested(sessionId)) {
int64_t cur_time= av_gettime_relative(); int64_t cur_time= av_gettime_relative();
/* if 'q' pressed, exits */ /* if 'q' pressed, exits */
@ -5064,7 +5064,7 @@ void cancel_operation(long id)
if (id == 0) { if (id == 0) {
sigterm_handler(SIGINT); sigterm_handler(SIGINT);
} else { } else {
removeExecution(id); cancelSession(id);
} }
} }
@ -5575,10 +5575,10 @@ int ffmpeg_execute(int argc, char **argv)
if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1]) if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1])
exit_program(69); exit_program(69);
exit_program((received_nb_signals || cancelRequested(executionId))? 255 : main_ffmpeg_return_code); exit_program((received_nb_signals || cancelRequested(sessionId))? 255 : main_ffmpeg_return_code);
} else { } else {
main_ffmpeg_return_code = (received_nb_signals || cancelRequested(executionId)) ? 255 : longjmp_value; main_ffmpeg_return_code = (received_nb_signals || cancelRequested(sessionId)) ? 255 : longjmp_value;
} }
return main_ffmpeg_return_code; return main_ffmpeg_return_code;

@ -0,0 +1,135 @@
/*
* Copyright (c) 2020-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
* FFmpegKit is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FFmpegKit is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include "config.h"
#include "libavformat/avformat.h"
#include "libavutil/avstring.h"
#include "saf_wrapper.h"
/** JNI wrapper in ffmpegkit.c */
void closeParcelFileDescriptor(int fd);
// in these wrappers, we call the original functions, so we remove the shadow defines
#undef avio_closep
#undef avformat_close_input
#undef avio_open
#undef avio_open2
#undef avformat_open_input
static int fd_read_packet(void* opaque, uint8_t* buf, int buf_size) {
int fd = (int)opaque;
return read(fd, buf, buf_size);
}
static int fd_write_packet(void* opaque, uint8_t* buf, int buf_size) {
int fd = (int)opaque;
return write(fd, buf, buf_size);
}
static int64_t fd_seek(void *opaque, int64_t offset, int whence) {
int fd = (int)opaque;
if (fd < 0) {
return AVERROR(EINVAL);
}
int64_t ret;
if (whence == AVSEEK_SIZE) {
struct stat st;
ret = fstat(fd, &st);
return ret < 0 ? AVERROR(errno) : (S_ISFIFO(st.st_mode) ? 0 : st.st_size);
}
ret = lseek(fd, offset, whence);
return ret < 0 ? AVERROR(errno) : ret;
}
/*
* returns NULL if the filename is not of expected format (e.g. 'saf:72/video.md4')
*/
static AVIOContext *create_fd_avio_context(const char *filename, int flags) {
union {int fd; void* opaque;} fdunion;
fdunion.fd = -1;
const char *fd_ptr = NULL;
if (av_strstart(filename, "saf:", &fd_ptr)) {
char *final;
fdunion.fd = strtol(fd_ptr, &final, 10);
if (fd_ptr == final) { /* No digits found */
fdunion.fd = -1;
}
}
if (fdunion.fd >= 0) {
int write_flag = flags & AVIO_FLAG_WRITE ? 1 : 0;
return avio_alloc_context(av_malloc(4096), 4096, write_flag, fdunion.opaque, fd_read_packet, write_flag ? fd_write_packet : NULL, fd_seek);
}
return NULL;
}
static void close_fd_avio_context(AVIOContext *ctx) {
if (fd_seek(ctx->opaque, 0, AVSEEK_SIZE) >= 0) {
int fd = (int)ctx->opaque;
close(fd);
closeParcelFileDescriptor(fd);
}
ctx->opaque = NULL;
}
int android_avformat_open_input(AVFormatContext **ps, const char *filename,
ff_const59 AVInputFormat *fmt, AVDictionary **options) {
if (!(*ps) && !(*ps = avformat_alloc_context()))
return AVERROR(ENOMEM);
(*ps)->pb = create_fd_avio_context(filename, AVIO_FLAG_READ);
return avformat_open_input(ps, filename, fmt, options);
}
int android_avio_open2(AVIOContext **s, const char *filename, int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options) {
AVIOContext *fd_context = create_fd_avio_context(filename, flags);
if (fd_context) {
*s = fd_context;
return 0;
}
return avio_open2(s, filename, flags, int_cb, options);
}
int android_avio_open(AVIOContext **s, const char *url, int flags) {
return android_avio_open2(s, url, flags, NULL, NULL);
}
int android_avio_closep(AVIOContext **s) {
close_fd_avio_context(*s);
return avio_closep(s);
}
void android_avformat_close_input(AVFormatContext **ps) {
if (*ps && (*ps)->pb) {
close_fd_avio_context((*ps)->pb);
}
avformat_close_input(ps);
}

@ -0,0 +1,46 @@
/*
* Copyright (c) 2020-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
* FFmpegKit is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FFmpegKit is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FFMPEG_KIT_SAF_WRAPPER_H
#define FFMPEG_KIT_SAF_WRAPPER_H
/*
* These wrappers are intended to be used instead of the ffmpeg apis.
* You don't even need to change the source to call them.
* Instead, we redefine the public api names so that the wrapper be used.
*/
int android_avio_closep(AVIOContext **s);
#define avio_closep android_avio_closep
void android_avformat_close_input(AVFormatContext **s);
#define avformat_close_input android_avformat_close_input
int android_avio_open(AVIOContext **s, const char *url, int flags);
#define avio_open android_avio_open
int android_avio_open2(AVIOContext **s, const char *url, int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options);
#define avio_open2 android_avio_open2
int android_avformat_open_input(AVFormatContext **ps, const char *filename,
ff_const59 AVInputFormat *fmt, AVDictionary **options);
#define avformat_open_input android_avformat_open_input
#endif //FFMPEG_KIT_SAF_WRAPPER_H

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -20,7 +20,9 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
/** /**
* <p>Helper enumeration type for Android ABIs; includes only supported ABIs. * <p>Enumeration type for Android ABIs.
*
* @author Taner Sener
*/ */
public enum Abi { public enum Abi {
@ -59,7 +61,7 @@ public enum Abi {
*/ */
ABI_UNKNOWN("unknown"); ABI_UNKNOWN("unknown");
private String name; private final String name;
/** /**
* <p>Returns enumeration defined by ABI name. * <p>Returns enumeration defined by ABI name.
@ -97,7 +99,7 @@ public enum Abi {
} }
/** /**
* Creates new enum. * Creates a new enum.
* *
* @param abiName ABI name * @param abiName ABI name
*/ */

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -20,7 +20,7 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
/** /**
* <p>This class is used to detect running ABI name using Google <code>cpu-features</code> library. * <p>Detects running ABI name using Google <code>cpu-features</code> library.
*/ */
public class AbiDetect { public class AbiDetect {
@ -46,8 +46,8 @@ public class AbiDetect {
private AbiDetect() { private AbiDetect() {
} }
static void setArmV7aNeonLoaded(final boolean armV7aNeonLoaded) { static void setArmV7aNeonLoaded() {
AbiDetect.armV7aNeonLoaded = armV7aNeonLoaded; armV7aNeonLoaded = true;
} }
/** /**

@ -0,0 +1,197 @@
/*
* Copyright (c) 2021 Taner Sener
*
* This file is part of FFmpegKit.
*
* FFmpegKit is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FFmpegKit is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.ffmpegkit;
import com.arthenica.smartexception.java.Exceptions;
import java.util.Date;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
public abstract class AbstractSession implements Session {
/**
* Generates ids for execute sessions.
*/
private static final AtomicLong sessionIdGenerator = new AtomicLong(1);
protected final ExecuteCallback executeCallback;
protected final LogCallback logCallback;
protected final StatisticsCallback statisticsCallback;
protected final long sessionId;
protected final Date createTime;
protected Date startTime;
protected Date endTime;
protected final String[] arguments;
protected final Queue<Log> logs;
protected Future<?> future;
protected SessionState state;
protected int returnCode;
protected String failStackTrace;
public AbstractSession(final String[] arguments,
final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback) {
this.sessionId = sessionIdGenerator.getAndIncrement();
this.createTime = new Date();
this.startTime = null;
this.arguments = arguments;
this.executeCallback = executeCallback;
this.logCallback = logCallback;
this.statisticsCallback = statisticsCallback;
this.logs = new ConcurrentLinkedQueue<>();
this.future = null;
this.state = SessionState.CREATED;
this.returnCode = ReturnCode.NOT_SET;
this.failStackTrace = null;
}
public ExecuteCallback getExecuteCallback() {
return executeCallback;
}
public LogCallback getLogCallback() {
return logCallback;
}
public StatisticsCallback getStatisticsCallback() {
return statisticsCallback;
}
public long getSessionId() {
return sessionId;
}
public Date getCreateTime() {
return createTime;
}
public Date getStartTime() {
return startTime;
}
public Date getEndTime() {
return endTime;
}
public long getDuration() {
final Date startTime = this.startTime;
final Date endTime = this.endTime;
if (startTime != null && endTime != null) {
return (endTime.getTime() - startTime.getTime());
}
return -1;
}
public String[] getArguments() {
return arguments;
}
public String getCommand() {
return FFmpegKit.argumentsToString(arguments);
}
public Queue<Log> getLogs() {
return logs;
}
public Stream<Log> getLogsAsStream() {
return logs.stream();
}
public String getLogsAsString() {
final Optional<String> concatenatedStringOption = logs.stream().map(new Function<Log, String>() {
@Override
public String apply(final Log log) {
return log.getMessage();
}
}).reduce(new BinaryOperator<String>() {
@Override
public String apply(final String s1, final String s2) {
return s1 + s2;
}
});
return concatenatedStringOption.orElseGet(new Supplier<String>() {
@Override
public String get() {
return "";
}
});
}
public SessionState getState() {
return state;
}
public int getReturnCode() {
return returnCode;
}
public String getFailStackTrace() {
return failStackTrace;
}
public void addLog(final Log log) {
this.logs.add(log);
}
public Future<?> getFuture() {
return future;
}
public void setFuture(final Future<?> future) {
this.future = future;
}
public void startRunning() {
this.state = SessionState.RUNNING;
this.startTime = new Date();
}
public void complete(final int returnCode) {
this.returnCode = returnCode;
this.state = SessionState.COMPLETED;
this.endTime = new Date();
}
public void fail(final Exception exception) {
this.failStackTrace = Exceptions.getStackTraceString(exception);
this.state = SessionState.FAILED;
this.endTime = new Date();
}
public void cancel() {
if (state == SessionState.RUNNING) {
FFmpegKit.cancel(sessionId);
}
}
}

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -19,44 +19,29 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
import android.os.AsyncTask;
/** /**
* <p>Utility class to execute an FFmpeg command asynchronously. * <p>Executes an FFmpeg session asynchronously.
*/ */
public class AsyncFFmpegExecuteTask extends AsyncTask<Void, Integer, Integer> { public class AsyncFFmpegExecuteTask implements Runnable {
private final String[] arguments; private final FFmpegSession ffmpegSession;
private final ExecuteCallback executeCallback; private final ExecuteCallback executeCallback;
private final Long executionId;
public AsyncFFmpegExecuteTask(final String command, final ExecuteCallback executeCallback) {
this(FFmpegKit.parseArguments(command), executeCallback);
}
public AsyncFFmpegExecuteTask(final String[] arguments, final ExecuteCallback executeCallback) {
this(FFmpegKit.DEFAULT_EXECUTION_ID, arguments, executeCallback);
}
public AsyncFFmpegExecuteTask(final long executionId, final String command, final ExecuteCallback executeCallback) {
this(executionId, FFmpegKit.parseArguments(command), executeCallback);
}
public AsyncFFmpegExecuteTask(final long executionId, final String[] arguments, final ExecuteCallback executeCallback) { public AsyncFFmpegExecuteTask(final FFmpegSession ffmpegSession) {
this.executionId = executionId; this.ffmpegSession = ffmpegSession;
this.arguments = arguments; this.executeCallback = ffmpegSession.getExecuteCallback();
this.executeCallback = executeCallback;
} }
@Override @Override
protected Integer doInBackground(final Void... unused) { public void run() {
return FFmpegKitConfig.ffmpegExecute(executionId, this.arguments); FFmpegKitConfig.ffmpegExecute(ffmpegSession);
}
final ExecuteCallback globalExecuteCallbackFunction = FFmpegKitConfig.getGlobalExecuteCallbackFunction();
if (globalExecuteCallbackFunction != null) {
globalExecuteCallbackFunction.apply(ffmpegSession);
}
@Override
protected void onPostExecute(final Integer rc) {
if (executeCallback != null) { if (executeCallback != null) {
executeCallback.apply(executionId, rc); executeCallback.apply(ffmpegSession);
} }
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -19,34 +19,24 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
import android.os.AsyncTask;
/** /**
* <p>Utility class to execute an FFprobe command asynchronously. * <p>Executes an FFprobe execution asynchronously.
*/ */
public class AsyncFFprobeExecuteTask extends AsyncTask<Void, Integer, Integer> { public class AsyncFFprobeExecuteTask implements Runnable {
private final String[] arguments; private final FFprobeSession ffprobeSession;
private final ExecuteCallback ExecuteCallback; private final ExecuteCallback executeCallback;
public AsyncFFprobeExecuteTask(final String command, final ExecuteCallback executeCallback) {
this.arguments = FFmpegKit.parseArguments(command);
this.ExecuteCallback = executeCallback;
}
public AsyncFFprobeExecuteTask(final String[] arguments, final ExecuteCallback executeCallback) { public AsyncFFprobeExecuteTask(final FFprobeSession ffprobeSession) {
this.arguments = arguments; this.ffprobeSession = ffprobeSession;
ExecuteCallback = executeCallback; this.executeCallback = ffprobeSession.getExecuteCallback();
} }
@Override @Override
protected Integer doInBackground(final Void... unused) { public void run() {
return FFprobeKit.execute(this.arguments); FFmpegKitConfig.ffprobeExecute(ffprobeSession);
}
@Override if (executeCallback != null) {
protected void onPostExecute(final Integer rc) { executeCallback.apply(ffprobeSession);
if (ExecuteCallback != null) {
ExecuteCallback.apply(FFmpegKit.DEFAULT_EXECUTION_ID, rc);
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -19,29 +19,24 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
import android.os.AsyncTask;
/** /**
* <p>Utility class to get media information asynchronously. * <p>Executes a MediaInformation session asynchronously.
*/ */
public class AsyncGetMediaInformationTask extends AsyncTask<String, MediaInformation, MediaInformation> { public class AsyncGetMediaInformationTask implements Runnable {
private final String path; private final MediaInformationSession mediaInformationSession;
private final GetMediaInformationCallback getMediaInformationCallback; private final ExecuteCallback executeCallback;
public AsyncGetMediaInformationTask(final String path, final GetMediaInformationCallback getMediaInformationCallback) { public AsyncGetMediaInformationTask(final MediaInformationSession mediaInformationSession) {
this.path = path; this.mediaInformationSession = mediaInformationSession;
this.getMediaInformationCallback = getMediaInformationCallback; this.executeCallback = mediaInformationSession.getExecuteCallback();
} }
@Override @Override
protected MediaInformation doInBackground(final String... arguments) { public void run() {
return FFprobeKit.getMediaInformation(path); FFmpegKitConfig.getMediaInformationExecute(mediaInformationSession);
}
@Override if (executeCallback != null) {
protected void onPostExecute(final MediaInformation mediaInformation) { executeCallback.apply(mediaInformationSession);
if (getMediaInformationCallback != null) {
getMediaInformationCallback.apply(mediaInformation);
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019-2020 Taner Sener * Copyright (c) 2019-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -34,7 +34,9 @@ import static android.content.Context.CAMERA_SERVICE;
import static com.arthenica.ffmpegkit.FFmpegKitConfig.TAG; import static com.arthenica.ffmpegkit.FFmpegKitConfig.TAG;
/** /**
* Utility class for camera devices. * <p>Helper class to find camera devices supported.
* <p>Note that camera devices can only be detected on Android API Level 24+. On older API levels
* an empty list will be returned.
*/ */
class CameraSupport { class CameraSupport {

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -20,18 +20,16 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
/** /**
* <p>Represents a callback function to receive an asynchronous execution result. * <p>Callback function to receive execution results.
*/ */
@FunctionalInterface @FunctionalInterface
public interface ExecuteCallback { public interface ExecuteCallback {
/** /**
* <p>Called when an asynchronous FFmpeg execution is completed. * <p>Called when an execution is completed.
* *
* @param executionId id of the execution that completed * @param session of with completed execution
* @param returnCode return code of the execution completed, 0 on successful completion, 255
* on user cancel, other non-zero codes on error
*/ */
void apply(long executionId, int returnCode); void apply(final Session session);
} }

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -19,12 +19,9 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
import android.os.AsyncTask;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
/** /**
* <p>Main class for FFmpeg operations. Supports synchronous {@link #execute(String...)} and * <p>Main class for FFmpeg operations. Supports synchronous {@link #execute(String...)} and
@ -40,10 +37,6 @@ import java.util.concurrent.atomic.AtomicLong;
*/ */
public class FFmpegKit { public class FFmpegKit {
static final long DEFAULT_EXECUTION_ID = 0;
private static final AtomicLong executionIdCounter = new AtomicLong(3000);
static { static {
AbiDetect.class.getName(); AbiDetect.class.getName();
FFmpegKitConfig.class.getName(); FFmpegKitConfig.class.getName();
@ -59,26 +52,50 @@ public class FFmpegKit {
* <p>Synchronously executes FFmpeg with arguments provided. * <p>Synchronously executes FFmpeg with arguments provided.
* *
* @param arguments FFmpeg command options/arguments as string array * @param arguments FFmpeg command options/arguments as string array
* @return 0 on successful execution, 255 on user cancel, other non-zero codes on error * @return ffmpeg session created for this execution
*/ */
public static int execute(final String[] arguments) { public static FFmpegSession execute(final String[] arguments) {
return FFmpegKitConfig.ffmpegExecute(DEFAULT_EXECUTION_ID, arguments); final FFmpegSession session = new FFmpegSession(arguments, null, null, null);
FFmpegKitConfig.ffmpegExecute(session);
return session;
} }
/** /**
* <p>Asynchronously executes FFmpeg with arguments provided. * <p>Asynchronously executes FFmpeg with arguments provided.
* *
* @param arguments FFmpeg command options/arguments as string array * @param arguments FFmpeg command options/arguments as string array
* @param executeCallback callback that will be notified when execution is completed * @param executeCallback callback that will be notified when the execution is completed
* @return returns a unique id that represents this execution * @return ffmpeg session created for this execution
*/
public static FFmpegSession executeAsync(final String[] arguments,
final ExecuteCallback executeCallback) {
final FFmpegSession session = new FFmpegSession(arguments, executeCallback, null, null);
FFmpegKitConfig.asyncFFmpegExecute(session);
return session;
}
/**
* <p>Asynchronously executes FFmpeg with arguments provided.
*
* @param arguments FFmpeg command options/arguments as string array
* @param executeCallback callback that will be notified when execution is completed
* @param logCallback callback that will receive log entries
* @param statisticsCallback callback that will receive statistics
* @return ffmpeg session created for this execution
*/ */
public static long executeAsync(final String[] arguments, final ExecuteCallback executeCallback) { public static FFmpegSession executeAsync(final String[] arguments,
final long newExecutionId = executionIdCounter.incrementAndGet(); final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback) {
final FFmpegSession session = new FFmpegSession(arguments, executeCallback, logCallback, statisticsCallback);
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, arguments, executeCallback); FFmpegKitConfig.asyncFFmpegExecute(session);
asyncFFmpegExecuteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return newExecutionId; return session;
} }
/** /**
@ -87,31 +104,40 @@ public class FFmpegKit {
* @param arguments FFmpeg command options/arguments as string array * @param arguments FFmpeg command options/arguments as string array
* @param executeCallback callback that will be notified when execution is completed * @param executeCallback callback that will be notified when execution is completed
* @param executor executor that will be used to run this asynchronous operation * @param executor executor that will be used to run this asynchronous operation
* @return returns a unique id that represents this execution * @return ffmpeg session created for this execution
*/ */
public static long executeAsync(final String[] arguments, final ExecuteCallback executeCallback, final Executor executor) { public static FFmpegSession executeAsync(final String[] arguments,
final long newExecutionId = executionIdCounter.incrementAndGet(); final ExecuteCallback executeCallback,
final Executor executor) {
final FFmpegSession session = new FFmpegSession(arguments, executeCallback, null, null);
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, arguments, executeCallback); AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(session);
asyncFFmpegExecuteTask.executeOnExecutor(executor); executor.execute(asyncFFmpegExecuteTask);
return newExecutionId; return session;
} }
/** /**
* <p>Synchronously executes FFmpeg command provided. Command is split into arguments using * <p>Asynchronously executes FFmpeg with arguments provided.
* provided delimiter character.
* *
* @param command FFmpeg command * @param arguments FFmpeg command options/arguments as string array
* @param delimiter delimiter used to split arguments * @param executeCallback callback that will be notified when execution is completed
* @return 0 on successful execution, 255 on user cancel, other non-zero codes on error * @param logCallback callback that will receive log entries
* @since 3.0 * @param statisticsCallback callback that will receive statistics
* @deprecated argument splitting mechanism used in this method is pretty simple and prone to * @param executor executor that will be used to run this asynchronous operation
* errors. Consider using a more advanced method like {@link #execute(String)} or * @return ffmpeg session created for this execution
* {@link #execute(String[])}
*/ */
public static int execute(final String command, final String delimiter) { public static FFmpegSession executeAsync(final String[] arguments,
return execute((command == null) ? new String[]{""} : command.split((delimiter == null) ? " " : delimiter)); final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback,
final Executor executor) {
final FFmpegSession session = new FFmpegSession(arguments, executeCallback, logCallback, statisticsCallback);
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(session);
executor.execute(asyncFFmpegExecuteTask);
return session;
} }
/** /**
@ -120,9 +146,9 @@ public class FFmpegKit {
* your command. * your command.
* *
* @param command FFmpeg command * @param command FFmpeg command
* @return 0 on successful execution, 255 on user cancel, other non-zero codes on error * @return ffmpeg session created for this execution
*/ */
public static int execute(final String command) { public static FFmpegSession execute(final String command) {
return execute(parseArguments(command)); return execute(parseArguments(command));
} }
@ -133,15 +159,29 @@ public class FFmpegKit {
* *
* @param command FFmpeg command * @param command FFmpeg command
* @param executeCallback callback that will be notified when execution is completed * @param executeCallback callback that will be notified when execution is completed
* @return returns a unique id that represents this execution * @return ffmpeg session created for this execution
*/ */
public static long executeAsync(final String command, final ExecuteCallback executeCallback) { public static FFmpegSession executeAsync(final String command,
final long newExecutionId = executionIdCounter.incrementAndGet(); final ExecuteCallback executeCallback) {
return executeAsync(parseArguments(command), executeCallback);
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, command, executeCallback); }
asyncFFmpegExecuteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return newExecutionId; /**
* <p>Asynchronously executes FFmpeg command provided. Space character is used to split command
* into arguments. You can use single and double quote characters to specify arguments inside
* your command.
*
* @param command FFmpeg command
* @param executeCallback callback that will be notified when execution is completed
* @param logCallback callback that will receive log entries
* @param statisticsCallback callback that will receive statistics
* @return ffmpeg session created for this execution
*/
public static FFmpegSession executeAsync(final String command,
final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback) {
return executeAsync(parseArguments(command), executeCallback, logCallback, statisticsCallback);
} }
/** /**
@ -152,44 +192,76 @@ public class FFmpegKit {
* @param command FFmpeg command * @param command FFmpeg command
* @param executeCallback callback that will be notified when execution is completed * @param executeCallback callback that will be notified when execution is completed
* @param executor executor that will be used to run this asynchronous operation * @param executor executor that will be used to run this asynchronous operation
* @return returns a unique id that represents this execution * @return ffmpeg session created for this execution
*/
public static FFmpegSession executeAsync(final String command,
final ExecuteCallback executeCallback,
final Executor executor) {
final FFmpegSession session = new FFmpegSession(parseArguments(command), executeCallback, null, null);
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(session);
executor.execute(asyncFFmpegExecuteTask);
return session;
}
/**
* <p>Asynchronously executes FFmpeg command provided. Space character is used to split command
* into arguments. You can use single and double quote characters to specify arguments inside
* your command.
*
* @param command FFmpeg command
* @param executeCallback callback that will be notified when execution is completed
* @param logCallback callback that will receive log entries
* @param statisticsCallback callback that will receive statistics
* @param executor executor that will be used to run this asynchronous operation
* @return ffmpeg session created for this execution
*/ */
public static long executeAsync(final String command, final ExecuteCallback executeCallback, final Executor executor) { public static FFmpegSession executeAsync(final String command,
final long newExecutionId = executionIdCounter.incrementAndGet(); final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback,
final Executor executor) {
final FFmpegSession session = new FFmpegSession(parseArguments(command), executeCallback, logCallback, statisticsCallback);
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, command, executeCallback); AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(session);
asyncFFmpegExecuteTask.executeOnExecutor(executor); executor.execute(asyncFFmpegExecuteTask);
return newExecutionId; return session;
} }
/** /**
* <p>Cancels an ongoing operation. * <p>Cancels the last execution started.
* *
* <p>This function does not wait for termination to complete and returns immediately. * <p>This function does not wait for termination to complete and returns immediately.
*/ */
public static void cancel() { public static void cancel() {
FFmpegKitConfig.nativeFFmpegCancel(DEFAULT_EXECUTION_ID); Session lastSession = FFmpegKitConfig.getLastSession();
if (lastSession != null) {
FFmpegKitConfig.nativeFFmpegCancel(lastSession.getSessionId());
} else {
android.util.Log.w(FFmpegKitConfig.TAG, "FFmpegKit cancel skipped. The last execution does not exist.");
}
} }
/** /**
* <p>Cancels an ongoing operation. * <p>Cancels the given execution.
* *
* <p>This function does not wait for termination to complete and returns immediately. * <p>This function does not wait for termination to complete and returns immediately.
* *
* @param executionId id of the execution * @param sessionId id of the session that will be stopped
*/ */
public static void cancel(final long executionId) { public static void cancel(final long sessionId) {
FFmpegKitConfig.nativeFFmpegCancel(executionId); FFmpegKitConfig.nativeFFmpegCancel(sessionId);
} }
/** /**
* <p>Lists ongoing executions. * <p>Lists all FFmpeg sessions in the session history
* *
* @return list of ongoing executions * @return all FFmpeg sessions in the session history
*/ */
public static List<FFmpegExecution> listExecutions() { public static List<FFmpegSession> listSessions() {
return FFmpegKitConfig.listFFmpegExecutions(); return FFmpegKitConfig.getFFmpegSessions();
} }
/** /**

@ -0,0 +1,93 @@
/*
* Copyright (c) 2020-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
* FFmpegKit is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FFmpegKit is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.ffmpegkit;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.Stream;
/**
* <p>An FFmpeg execute session.
*/
public class FFmpegSession extends AbstractSession implements Session {
private final Queue<Statistics> statistics;
public FFmpegSession(final String[] arguments,
final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback) {
super(arguments, executeCallback, logCallback, statisticsCallback);
this.statistics = new ConcurrentLinkedQueue<>();
}
public Queue<Statistics> getStatistics() {
return statistics;
}
public Stream<Statistics> getStatisticsAsStream() {
return statistics.stream();
}
public void addStatistics(final Statistics statistics) {
this.statistics.add(statistics);
}
@Override
public boolean isFFmpeg() {
return true;
}
@Override
public boolean isFFprobe() {
return false;
}
@Override
public String toString() {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("FFmpegSession{");
stringBuilder.append("sessionId=");
stringBuilder.append(sessionId);
stringBuilder.append(", createTime=");
stringBuilder.append(createTime);
stringBuilder.append(", startTime=");
stringBuilder.append(startTime);
stringBuilder.append(", endTime=");
stringBuilder.append(endTime);
stringBuilder.append(", arguments=");
stringBuilder.append(FFmpegKit.argumentsToString(arguments));
stringBuilder.append(", logs=");
stringBuilder.append(getLogsAsString());
stringBuilder.append(", state=");
stringBuilder.append(state);
stringBuilder.append(", returnCode=");
stringBuilder.append(returnCode);
stringBuilder.append(", failStackTrace=");
stringBuilder.append('\'');
stringBuilder.append(failStackTrace);
stringBuilder.append('\'');
stringBuilder.append('}');
return stringBuilder.toString();
}
}

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020 Taner Sener * Copyright (c) 2020-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -19,14 +19,19 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
import android.util.Log; import java.util.concurrent.Executor;
/** /**
* <p>Main class for FFprobe operations. Provides {@link #execute(String...)} method to execute * <p>Main class for FFprobe operations.
* FFprobe commands. * <p>Supports running FFprobe commands using {@link #execute(String...)} method.
* <pre> * <pre>
* int rc = FFprobe.execute("-hide_banner -v error -show_entries format=size -of default=noprint_wrappers=1 file1.mp4"); * FFprobeSession session = FFprobe.execute("-hide_banner -v error -show_entries format=size -of default=noprint_wrappers=1 file1.mp4");
* Log.i(Config.TAG, String.format("Command execution %s.", (rc == 0?"completed successfully":"failed with rc=" + rc)); * Log.i(FFmpegKitConfig.TAG, String.format("Command execution %s.", (session.getReturnCode() == 0?"completed successfully":"failed with rc=" + session.getReturnCode()));
* </pre>
* <p>It can also extract media information for a file or a url, using {@link #getMediaInformation(String)} method.
* <pre>
* MediaInformationSession session = FFprobe.getMediaInformation("file1.mp4");
* Log.i(FFmpegKitConfig.TAG, String.format("Media information %s.", (session.getReturnCode() == 0?"extracted successfully":"was not extracted due to rc=" + session.getReturnCode()));
* </pre> * </pre>
*/ */
public class FFprobeKit { public class FFprobeKit {
@ -46,14 +51,14 @@ public class FFprobeKit {
* <p>Synchronously executes FFprobe with arguments provided. * <p>Synchronously executes FFprobe with arguments provided.
* *
* @param arguments FFprobe command options/arguments as string array * @param arguments FFprobe command options/arguments as string array
* @return zero on successful execution, 255 on user cancel and non-zero on error * @return ffprobe session created for this execution
*/ */
public static int execute(final String[] arguments) { public static FFprobeSession execute(final String[] arguments) {
final int lastReturnCode = FFmpegKitConfig.nativeFFprobeExecute(arguments); final FFprobeSession session = new FFprobeSession(arguments, null, null, null);
FFmpegKitConfig.setLastReturnCode(lastReturnCode); FFmpegKitConfig.ffprobeExecute(session);
return lastReturnCode; return session;
} }
/** /**
@ -62,69 +67,246 @@ public class FFprobeKit {
* your command. * your command.
* *
* @param command FFprobe command * @param command FFprobe command
* @return zero on successful execution, 255 on user cancel and non-zero on error * @return ffprobe session created for this execution
*/ */
public static int execute(final String command) { public static FFprobeSession execute(final String command) {
return execute(FFmpegKit.parseArguments(command)); return execute(FFmpegKit.parseArguments(command));
} }
/** /**
* <p>Returns media information for the given file. * <p>Asynchronously executes FFprobe command provided. Space character is used to split command
* into arguments. You can use single and double quote characters to specify arguments inside
* your command.
* *
* <p>This method does not support executing multiple concurrent operations. If you execute * @param command FFprobe command
* multiple operations (execute or getMediaInformation) at the same time, the response of this * @param executeCallback callback that will be notified when the execution is completed
* method is not predictable. * @return ffprobe session created for this execution
*/
public static FFprobeSession executeAsync(final String command,
final ExecuteCallback executeCallback) {
return executeAsync(FFmpegKit.parseArguments(command), executeCallback);
}
/**
* <p>Asynchronously executes FFprobe with arguments provided.
* *
* @param path path or uri of media file * @param arguments FFprobe command options/arguments as string array
* @return media information * @param executeCallback callback that will be notified when the execution is completed
* @since 3.0 * @return ffprobe session created for this execution
*/ */
public static MediaInformation getMediaInformation(final String path) { public static FFprobeSession executeAsync(final String[] arguments,
return getMediaInformationFromCommandArguments(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path}); final ExecuteCallback executeCallback) {
final FFprobeSession session = new FFprobeSession(arguments, executeCallback, null, null);
FFmpegKitConfig.asyncFFprobeExecute(session);
return session;
} }
/** /**
* <p>Returns media information for the given command. * <p>Asynchronously executes FFprobe command provided. Space character is used to split command
* into arguments. You can use single and double quote characters to specify arguments inside
* your command.
* *
* <p>This method does not support executing multiple concurrent operations. If you execute * @param command FFprobe command
* multiple operations (execute or getMediaInformation) at the same time, the response of this * @param executeCallback callback that will be notified when execution is completed
* method is not predictable. * @param logCallback callback that will receive log entries
* @param statisticsCallback callback that will receive statistics
* @return ffprobe session created for this execution
*/
public static FFprobeSession executeAsync(final String command,
final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback) {
return executeAsync(FFmpegKit.parseArguments(command), executeCallback, logCallback, statisticsCallback);
}
/**
* <p>Asynchronously executes FFprobe with arguments provided.
* *
* @param command command to execute * @param arguments FFprobe command options/arguments as string array
* @return media information * @param executeCallback callback that will be notified when execution is completed
* @since 4.3.3 * @param logCallback callback that will receive log entries
* @param statisticsCallback callback that will receive statistics
* @return ffprobe session created for this execution
*/
public static FFprobeSession executeAsync(final String[] arguments,
final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback) {
final FFprobeSession session = new FFprobeSession(arguments, executeCallback, logCallback, statisticsCallback);
FFmpegKitConfig.asyncFFprobeExecute(session);
return session;
}
/**
* <p>Asynchronously executes FFprobe with arguments provided.
*
* @param arguments FFprobe command options/arguments as string array
* @param executeCallback callback that will be notified when the execution is completed
* @param executor executor that will be used to run this asynchronous operation
* @return ffprobe session created for this execution
*/
public static FFprobeSession executeAsync(final String[] arguments,
final ExecuteCallback executeCallback,
final Executor executor) {
final FFprobeSession session = new FFprobeSession(arguments, executeCallback, null, null);
AsyncFFprobeExecuteTask asyncFFprobeExecuteTask = new AsyncFFprobeExecuteTask(session);
executor.execute(asyncFFprobeExecuteTask);
return session;
}
/**
* <p>Asynchronously executes FFprobe with arguments provided.
*
* @param arguments FFprobe command options/arguments as string array
* @param executeCallback callback that will be notified when execution is completed
* @param logCallback callback that will receive log entries
* @param statisticsCallback callback that will receive statistics
* @param executor executor that will be used to run this asynchronous operation
* @return ffprobe session created for this execution
*/
public static FFprobeSession executeAsync(final String[] arguments,
final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback,
final Executor executor) {
final FFprobeSession session = new FFprobeSession(arguments, executeCallback, logCallback, statisticsCallback);
AsyncFFprobeExecuteTask asyncFFprobeExecuteTask = new AsyncFFprobeExecuteTask(session);
executor.execute(asyncFFprobeExecuteTask);
return session;
}
/**
* <p>Returns media information for the given path.
*
* @param path path or uri of a media file
* @return media information session created for this execution
*/
public static MediaInformationSession getMediaInformation(final String path) {
return getMediaInformationFromCommandArguments(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path}, null, null, null);
}
/**
* <p>Returns media information for the given path asynchronously.
*
* @param path path or uri of a media file
* @param executeCallback callback that will be notified when the execution is completed
* @return media information session created for this execution
*/
public static MediaInformationSession getMediaInformationAsync(final String path,
final ExecuteCallback executeCallback) {
final MediaInformationSession session = new MediaInformationSession(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path}, executeCallback, null, null);
FFmpegKitConfig.asyncGetMediaInformationExecute(session);
return session;
}
/**
* <p>Returns media information for the given path asynchronously.
*
* @param path path or uri of a media file
* @param executeCallback callback that will be notified when execution is completed
* @param logCallback callback that will receive log entries
* @param statisticsCallback callback that will receive statistics
* @return media information session created for this execution
*/
public static MediaInformationSession getMediaInformationAsync(final String path,
final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback) {
final MediaInformationSession session = new MediaInformationSession(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path}, executeCallback, logCallback, statisticsCallback);
FFmpegKitConfig.asyncGetMediaInformationExecute(session);
return session;
}
/**
* <p>Returns media information for the given path asynchronously.
*
* @param path path or uri of a media file
* @param executeCallback callback that will be notified when the execution is completed
* @param executor executor that will be used to run this asynchronous operation
* @return media information session created for this execution
*/
public static MediaInformationSession getMediaInformationAsync(final String path,
final ExecuteCallback executeCallback,
final Executor executor) {
final MediaInformationSession session = new MediaInformationSession(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path}, executeCallback, null, null);
AsyncGetMediaInformationTask asyncGetMediaInformationTask = new AsyncGetMediaInformationTask(session);
executor.execute(asyncGetMediaInformationTask);
return session;
}
/**
* <p>Returns media information for the given path asynchronously.
*
* @param path path or uri of a media file
* @param executeCallback callback that will be notified when execution is completed
* @param logCallback callback that will receive log entries
* @param statisticsCallback callback that will receive statistics
* @param executor executor that will be used to run this asynchronous operation
* @return media information session created for this execution
*/ */
public static MediaInformation getMediaInformationFromCommand(final String command) { public static MediaInformationSession getMediaInformationAsync(final String path,
return getMediaInformationFromCommandArguments(FFmpegKit.parseArguments(command)); final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback,
final Executor executor) {
final MediaInformationSession session = new MediaInformationSession(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path}, executeCallback, logCallback, statisticsCallback);
AsyncGetMediaInformationTask asyncGetMediaInformationTask = new AsyncGetMediaInformationTask(session);
executor.execute(asyncGetMediaInformationTask);
return session;
} }
/** /**
* <p>Returns media information for given file. * <p>Returns media information for the given command.
* *
* <p>This method does not support executing multiple concurrent operations. If you execute * @param command command to execute
* multiple operations (execute or getMediaInformation) at the same time, the response of this * @return media information session created for this execution
* method is not predictable. */
public static MediaInformationSession getMediaInformationFromCommand(final String command) {
return getMediaInformationFromCommandArguments(FFmpegKit.parseArguments(command), null, null, null);
}
/**
* <p>Returns media information for the given command.
* *
* @param path path or uri of media file * @param command command to execute
* @param timeout complete timeout * @param executeCallback callback that will be notified when execution is completed
* @return media information * @param logCallback callback that will receive log entries
* @since 3.0 * @param statisticsCallback callback that will receive statistics
* @deprecated this method is deprecated since v4.3.1. You can still use this method but * @return media information session created for this execution
* <code>timeout</code> parameter is not effective anymore. */
*/ public static MediaInformationSession getMediaInformationFromCommand(final String command,
public static MediaInformation getMediaInformation(final String path, final Long timeout) { final ExecuteCallback executeCallback,
return getMediaInformation(path); final LogCallback logCallback,
} final StatisticsCallback statisticsCallback) {
return getMediaInformationFromCommandArguments(FFmpegKit.parseArguments(command), executeCallback, logCallback, statisticsCallback);
private static MediaInformation getMediaInformationFromCommandArguments(final String[] arguments) { }
final int rc = execute(arguments);
private static MediaInformationSession getMediaInformationFromCommandArguments(final String[] arguments,
if (rc == 0) { final ExecuteCallback executeCallback,
return MediaInformationParser.from(FFmpegKitConfig.getLastCommandOutput()); final LogCallback logCallback,
} else { final StatisticsCallback statisticsCallback) {
Log.w(FFmpegKitConfig.TAG, FFmpegKitConfig.getLastCommandOutput()); final MediaInformationSession session = new MediaInformationSession(arguments, executeCallback, logCallback, statisticsCallback);
return null;
} FFmpegKitConfig.getMediaInformationExecute(session);
return session;
} }
} }

@ -0,0 +1,98 @@
/*
* Copyright (c) 2020-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
* FFmpegKit is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FFmpegKit is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.ffmpegkit;
import java.text.MessageFormat;
import java.util.LinkedList;
import java.util.Queue;
import java.util.stream.Stream;
/**
* <p>An FFprobe execute session.
*/
public class FFprobeSession extends AbstractSession implements Session {
public FFprobeSession(final String[] arguments,
final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback) {
super(arguments, executeCallback, logCallback, statisticsCallback);
}
@Override
public Queue<Statistics> getStatistics() {
return new LinkedList<>();
}
@Override
public Stream<Statistics> getStatisticsAsStream() {
return new LinkedList<Statistics>().stream();
}
@Override
public void addStatistics(final Statistics statistics) {
/*
* ffprobe does not support statistics.
* So, this method should never have been called.
*/
android.util.Log.w(FFmpegKitConfig.TAG, MessageFormat.format("FFprobe execute session {0} received statistics.", sessionId));
}
@Override
public boolean isFFmpeg() {
return false;
}
@Override
public boolean isFFprobe() {
return true;
}
@Override
public String toString() {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("FFprobeSession{");
stringBuilder.append("sessionId=");
stringBuilder.append(sessionId);
stringBuilder.append(", createTime=");
stringBuilder.append(createTime);
stringBuilder.append(", startTime=");
stringBuilder.append(startTime);
stringBuilder.append(", endTime=");
stringBuilder.append(endTime);
stringBuilder.append(", arguments=");
stringBuilder.append(FFmpegKit.argumentsToString(arguments));
stringBuilder.append(", logs=");
stringBuilder.append(getLogsAsString());
stringBuilder.append(", state=");
stringBuilder.append(state);
stringBuilder.append(", returnCode=");
stringBuilder.append(returnCode);
stringBuilder.append(", failStackTrace=");
stringBuilder.append('\'');
stringBuilder.append(failStackTrace);
stringBuilder.append('\'');
stringBuilder.append('}');
return stringBuilder.toString();
}
}

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -20,7 +20,7 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
/** /**
* <p>Helper enumeration type for log levels. * <p>Enumeration type for log levels.
*/ */
public enum Level { public enum Level {
@ -79,7 +79,7 @@ public enum Level {
*/ */
AV_LOG_TRACE(56); AV_LOG_TRACE(56);
private int value; private final int value;
/** /**
* <p>Returns enumeration defined by value. * <p>Returns enumeration defined by value.

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -20,44 +20,43 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
/** /**
* <p>Logs for running executions. * <p>Log entry for an execute session.
*/ */
public class LogMessage { public class Log {
private final long sessionId;
private final long executionId;
private final Level level; private final Level level;
private final String text; private final String message;
public LogMessage(final long executionId, final Level level, final String text) { public Log(final long sessionId, final Level level, final String message) {
this.executionId = executionId; this.sessionId = sessionId;
this.level = level; this.level = level;
this.text = text; this.message = message;
} }
public long getExecutionId() { public long getSessionId() {
return executionId; return sessionId;
} }
public Level getLevel() { public Level getLevel() {
return level; return level;
} }
public String getText() { public String getMessage() {
return text; return message;
} }
@Override @Override
public String toString() { public String toString() {
final StringBuilder stringBuilder = new StringBuilder(); final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("LogMessage{"); stringBuilder.append("Log{");
stringBuilder.append("executionId="); stringBuilder.append("sessionId=");
stringBuilder.append(executionId); stringBuilder.append(sessionId);
stringBuilder.append(", level="); stringBuilder.append(", level=");
stringBuilder.append(level); stringBuilder.append(level);
stringBuilder.append(", text="); stringBuilder.append(", message=");
stringBuilder.append("\'"); stringBuilder.append("\'");
stringBuilder.append(text); stringBuilder.append(message);
stringBuilder.append('\''); stringBuilder.append('\'');
stringBuilder.append('}'); stringBuilder.append('}');

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -20,11 +20,16 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
/** /**
* <p>Represents a callback function to receive logs from running executions * <p>Callback function to receive logs for executions.
*/ */
@FunctionalInterface @FunctionalInterface
public interface LogCallback { public interface LogCallback {
void apply(final LogMessage message); /**
* <p>Called when a log entry is received.
*
* @param log log entry
*/
void apply(final Log log);
} }

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -28,15 +28,16 @@ import java.util.List;
*/ */
public class MediaInformation { public class MediaInformation {
private static final String KEY_MEDIA_PROPERTIES = "format"; /* COMMON KEYS */
private static final String KEY_FILENAME = "filename"; public static final String KEY_MEDIA_PROPERTIES = "format";
private static final String KEY_FORMAT = "format_name"; public static final String KEY_FILENAME = "filename";
private static final String KEY_FORMAT_LONG = "format_long_name"; public static final String KEY_FORMAT = "format_name";
private static final String KEY_START_TIME = "start_time"; public static final String KEY_FORMAT_LONG = "format_long_name";
private static final String KEY_DURATION = "duration"; public static final String KEY_START_TIME = "start_time";
private static final String KEY_SIZE = "size"; public static final String KEY_DURATION = "duration";
private static final String KEY_BIT_RATE = "bit_rate"; public static final String KEY_SIZE = "size";
private static final String KEY_TAGS = "tags"; public static final String KEY_BIT_RATE = "bit_rate";
public static final String KEY_TAGS = "tags";
/** /**
* Stores all properties. * Stores all properties.

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -21,6 +21,8 @@ package com.arthenica.ffmpegkit;
import android.util.Log; import android.util.Log;
import com.arthenica.smartexception.java.Exceptions;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -28,12 +30,14 @@ import org.json.JSONObject;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
* Helper class for parsing {@link MediaInformation}. * Parser for {@link MediaInformation}.
*/ */
public class MediaInformationParser { public class MediaInformationParser {
/** /**
* Extracts MediaInformation from the given ffprobe json output. * Extracts <code>MediaInformation</code> from the given ffprobe json output. Note that this
* method does not throw {@link JSONException} as {@link #fromWithError(String)} does and
* handles errors internally.
* *
* @param ffprobeJsonOutput ffprobe json output * @param ffprobeJsonOutput ffprobe json output
* @return created {@link MediaInformation} instance of null if a parsing error occurs * @return created {@link MediaInformation} instance of null if a parsing error occurs
@ -42,8 +46,7 @@ public class MediaInformationParser {
try { try {
return fromWithError(ffprobeJsonOutput); return fromWithError(ffprobeJsonOutput);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(FFmpegKitConfig.TAG, "MediaInformation parsing failed.", e); Log.e(FFmpegKitConfig.TAG, String.format("MediaInformation parsing failed.%s", Exceptions.getStackTraceString(e)));
e.printStackTrace();
return null; return null;
} }
} }

@ -0,0 +1,74 @@
/*
* Copyright (c) 2021 Taner Sener
*
* This file is part of FFmpegKit.
*
* FFmpegKit is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FFmpegKit is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.ffmpegkit;
/**
* <p>A custom FFprobe execute session, which produces a <code>MediaInformation</code> object
* using the output of the execution.
*/
public class MediaInformationSession extends FFprobeSession implements Session {
private MediaInformation mediaInformation;
public MediaInformationSession(final String[] arguments,
final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback) {
super(arguments, executeCallback, logCallback, statisticsCallback);
}
public MediaInformation getMediaInformation() {
return mediaInformation;
}
public void setMediaInformation(MediaInformation mediaInformation) {
this.mediaInformation = mediaInformation;
}
@Override
public String toString() {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("MediaInformationSession{");
stringBuilder.append("sessionId=");
stringBuilder.append(sessionId);
stringBuilder.append(", createTime=");
stringBuilder.append(createTime);
stringBuilder.append(", startTime=");
stringBuilder.append(startTime);
stringBuilder.append(", endTime=");
stringBuilder.append(endTime);
stringBuilder.append(", arguments=");
stringBuilder.append(FFmpegKit.argumentsToString(arguments));
stringBuilder.append(", logs=");
stringBuilder.append(getLogsAsString());
stringBuilder.append(", state=");
stringBuilder.append(state);
stringBuilder.append(", returnCode=");
stringBuilder.append(returnCode);
stringBuilder.append(", failStackTrace=");
stringBuilder.append('\'');
stringBuilder.append(failStackTrace);
stringBuilder.append('\'');
stringBuilder.append('}');
return stringBuilder.toString();
}
}

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -24,7 +24,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
* <p>Provides helper methods to extract binary package information. * <p>Helper class to extract binary package information.
*/ */
class Packages { class Packages {
@ -39,7 +39,6 @@ class Packages {
supportedExternalLibraries.add("gnutls"); supportedExternalLibraries.add("gnutls");
supportedExternalLibraries.add("kvazaar"); supportedExternalLibraries.add("kvazaar");
supportedExternalLibraries.add("mp3lame"); supportedExternalLibraries.add("mp3lame");
supportedExternalLibraries.add("libaom");
supportedExternalLibraries.add("libass"); supportedExternalLibraries.add("libass");
supportedExternalLibraries.add("iconv"); supportedExternalLibraries.add("iconv");
supportedExternalLibraries.add("libilbc"); supportedExternalLibraries.add("libilbc");
@ -137,7 +136,6 @@ class Packages {
externalLibraryList.contains("gnutls") && externalLibraryList.contains("gnutls") &&
externalLibraryList.contains("kvazaar") && externalLibraryList.contains("kvazaar") &&
externalLibraryList.contains("mp3lame") && externalLibraryList.contains("mp3lame") &&
externalLibraryList.contains("libaom") &&
externalLibraryList.contains("libass") && externalLibraryList.contains("libass") &&
externalLibraryList.contains("iconv") && externalLibraryList.contains("iconv") &&
externalLibraryList.contains("libilbc") && externalLibraryList.contains("libilbc") &&
@ -172,7 +170,6 @@ class Packages {
externalLibraryList.contains("gnutls") && externalLibraryList.contains("gnutls") &&
externalLibraryList.contains("kvazaar") && externalLibraryList.contains("kvazaar") &&
externalLibraryList.contains("mp3lame") && externalLibraryList.contains("mp3lame") &&
externalLibraryList.contains("libaom") &&
externalLibraryList.contains("libass") && externalLibraryList.contains("libass") &&
externalLibraryList.contains("iconv") && externalLibraryList.contains("iconv") &&
externalLibraryList.contains("libilbc") && externalLibraryList.contains("libilbc") &&
@ -200,7 +197,6 @@ class Packages {
externalLibraryList.contains("freetype") && externalLibraryList.contains("freetype") &&
externalLibraryList.contains("fribidi") && externalLibraryList.contains("fribidi") &&
externalLibraryList.contains("kvazaar") && externalLibraryList.contains("kvazaar") &&
externalLibraryList.contains("libaom") &&
externalLibraryList.contains("libass") && externalLibraryList.contains("libass") &&
externalLibraryList.contains("iconv") && externalLibraryList.contains("iconv") &&
externalLibraryList.contains("libtheora") && externalLibraryList.contains("libtheora") &&

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020 Taner Sener * Copyright (c) 2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -19,32 +19,24 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
import java.util.Date; public class ReturnCode {
/** public static int NOT_SET = -999;
* <p>Represents an ongoing FFmpeg execution.
*/ public static int SUCCESS = 0;
public class FFmpegExecution {
private final Date startTime; public static int CANCEL = 255;
private final long executionId;
private final String command;
public FFmpegExecution(final long executionId, final String[] arguments) {
this.startTime = new Date();
this.executionId = executionId;
this.command = FFmpegKit.argumentsToString(arguments);
}
public Date getStartTime() { public static boolean isSuccess(final int returnCode) {
return startTime; return (returnCode == SUCCESS);
} }
public long getExecutionId() { public static boolean isFailure(final int returnCode) {
return executionId; return (returnCode != NOT_SET) && (returnCode != SUCCESS) && (returnCode != CANCEL);
} }
public String getCommand() { public static boolean isCancel(final int returnCode) {
return command; return (returnCode == CANCEL);
} }
} }

@ -0,0 +1,88 @@
/*
* Copyright (c) 2021 Taner Sener
*
* This file is part of FFmpegKit.
*
* FFmpegKit is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FFmpegKit is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General License for more details.
*
* You should have received a copy of the GNU Lesser General License
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.ffmpegkit;
import java.util.Date;
import java.util.Queue;
import java.util.concurrent.Future;
import java.util.stream.Stream;
/**
* <p>Interface for ffmpeg and ffprobe execute sessions.
*/
public interface Session {
ExecuteCallback getExecuteCallback();
LogCallback getLogCallback();
StatisticsCallback getStatisticsCallback();
long getSessionId();
Date getCreateTime();
Date getStartTime();
Date getEndTime();
long getDuration();
String[] getArguments();
String getCommand();
Queue<Log> getLogs();
Stream<Log> getLogsAsStream();
String getLogsAsString();
Queue<Statistics> getStatistics();
Stream<Statistics> getStatisticsAsStream();
SessionState getState();
int getReturnCode();
String getFailStackTrace();
void addLog(final Log log);
void addStatistics(final Statistics statistics);
Future<?> getFuture();
void setFuture(final Future<?> future);
void startRunning();
void complete(final int returnCode);
void fail(final Exception exception);
boolean isFFmpeg();
boolean isFFprobe();
void cancel();
}

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -19,12 +19,9 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
/** public enum SessionState {
* <p>Represents a callback function to receive asynchronous getMediaInformation result. CREATED,
*/ RUNNING,
@FunctionalInterface FAILED,
public interface GetMediaInformationCallback { COMPLETED
void apply(MediaInformation mediaInformation);
} }

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2020 Taner Sener * Copyright (c) 2020-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -30,7 +30,7 @@ public enum Signal {
SIGTERM(15), SIGTERM(15),
SIGXCPU(24); SIGXCPU(24);
private int value; private final int value;
Signal(int value) { Signal(int value) {
this.value = value; this.value = value;

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -20,11 +20,10 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
/** /**
* <p>Statistics for running executions. * <p>Statistics entry for an FFmpeg execute session.
*/ */
public class Statistics { public class Statistics {
private long sessionId;
private long executionId;
private int videoFrameNumber; private int videoFrameNumber;
private float videoFps; private float videoFps;
private float videoQuality; private float videoQuality;
@ -34,7 +33,7 @@ public class Statistics {
private double speed; private double speed;
public Statistics() { public Statistics() {
executionId = 0; sessionId = 0;
videoFrameNumber = 0; videoFrameNumber = 0;
videoFps = 0; videoFps = 0;
videoQuality = 0; videoQuality = 0;
@ -44,8 +43,8 @@ public class Statistics {
speed = 0; speed = 0;
} }
public Statistics(long executionId, int videoFrameNumber, float videoFps, float videoQuality, long size, int time, double bitrate, double speed) { public Statistics(final long sessionId, final int videoFrameNumber, final float videoFps, final float videoQuality, final long size, final int time, final double bitrate, final double speed) {
this.executionId = executionId; this.sessionId = sessionId;
this.videoFrameNumber = videoFrameNumber; this.videoFrameNumber = videoFrameNumber;
this.videoFps = videoFps; this.videoFps = videoFps;
this.videoQuality = videoQuality; this.videoQuality = videoQuality;
@ -55,44 +54,12 @@ public class Statistics {
this.speed = speed; this.speed = speed;
} }
public void update(final Statistics newStatistics) { public long getSessionId() {
if (newStatistics != null) { return sessionId;
this.executionId = newStatistics.getExecutionId();
if (newStatistics.getVideoFrameNumber() > 0) {
this.videoFrameNumber = newStatistics.getVideoFrameNumber();
}
if (newStatistics.getVideoFps() > 0) {
this.videoFps = newStatistics.getVideoFps();
}
if (newStatistics.getVideoQuality() > 0) {
this.videoQuality = newStatistics.getVideoQuality();
}
if (newStatistics.getSize() > 0) {
this.size = newStatistics.getSize();
}
if (newStatistics.getTime() > 0) {
this.time = newStatistics.getTime();
}
if (newStatistics.getBitrate() > 0) {
this.bitrate = newStatistics.getBitrate();
}
if (newStatistics.getSpeed() > 0) {
this.speed = newStatistics.getSpeed();
}
}
}
public long getExecutionId() {
return executionId;
} }
public void setExecutionId(long executionId) { public void setSessionId(long sessionId) {
this.executionId = executionId; this.sessionId = sessionId;
} }
public int getVideoFrameNumber() { public int getVideoFrameNumber() {
@ -156,8 +123,8 @@ public class Statistics {
final StringBuilder stringBuilder = new StringBuilder(); final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Statistics{"); stringBuilder.append("Statistics{");
stringBuilder.append("executionId="); stringBuilder.append("sessionId=");
stringBuilder.append(executionId); stringBuilder.append(sessionId);
stringBuilder.append(", videoFrameNumber="); stringBuilder.append(", videoFrameNumber=");
stringBuilder.append(videoFrameNumber); stringBuilder.append(videoFrameNumber);
stringBuilder.append(", videoFps="); stringBuilder.append(", videoFps=");

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -20,11 +20,16 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
/** /**
* <p>Represents a callback function to receive statistics from running executions. * <p>Callback function to receive statistics for executions.
*/ */
@FunctionalInterface @FunctionalInterface
public interface StatisticsCallback { public interface StatisticsCallback {
/**
* <p>Called when a statistics entry is received.
*
* @param statistics statistics entry
*/
void apply(final Statistics statistics); void apply(final Statistics statistics);
} }

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018-2020 Taner Sener * Copyright (c) 2018-2021 Taner Sener
* *
* This file is part of FFmpegKit. * This file is part of FFmpegKit.
* *
@ -26,6 +26,7 @@ import org.json.JSONObject;
*/ */
public class StreamInformation { public class StreamInformation {
/* COMMON KEYS */
private static final String KEY_INDEX = "index"; private static final String KEY_INDEX = "index";
private static final String KEY_TYPE = "codec_type"; private static final String KEY_TYPE = "codec_type";
private static final String KEY_CODEC = "codec_name"; private static final String KEY_CODEC = "codec_name";

@ -0,0 +1,44 @@
/*
* Copyright (c) 2021 Taner Sener
*
* This file is part of FFmpegKit.
*
* FFmpegKit is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FFmpegKit is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FFmpegKit. If not, see <http://www.gnu.org/licenses/>.
*/
package com.arthenica.ffmpegkit;
import org.junit.Assert;
import org.junit.Test;
public class AbstractSessionTest {
private static final String[] TEST_ARGUMENTS = new String[]{"argument1", "argument2"};
@Test
public void getLogsAsStringTest() {
final FFprobeSession ffprobeSession = new FFprobeSession(TEST_ARGUMENTS, null, null, null);
String logMessage1 = "i am log one";
String logMessage2 = "i am log two";
ffprobeSession.addLog(new Log(ffprobeSession.getSessionId(), Level.AV_LOG_DEBUG, logMessage1));
ffprobeSession.addLog(new Log(ffprobeSession.getSessionId(), Level.AV_LOG_DEBUG, logMessage2));
String logsAsString = ffprobeSession.getLogsAsString();
Assert.assertEquals(logMessage1 + logMessage2, logsAsString);
}
}

@ -61,12 +61,12 @@ include $(BUILD_SHARED_LIBRARY)
$(call import-module, cpu-features) $(call import-module, cpu-features)
MY_SRC_FILES := ffmpegkit.c ffprobekit.c ffmpegkit_exception.c fftools_cmdutils.c fftools_ffmpeg.c fftools_ffprobe.c fftools_ffmpeg_opt.c fftools_ffmpeg_hw.c fftools_ffmpeg_filter.c saf_wrapper.c
ifeq ($(TARGET_PLATFORM),android-16) ifeq ($(TARGET_PLATFORM),android-16)
MY_SRC_FILES := ffmpegkit.c ffprobekit.c android_lts_support.c ffmpegkit_exception.c fftools_cmdutils.c fftools_ffmpeg.c fftools_ffprobe.c fftools_ffmpeg_opt.c fftools_ffmpeg_hw.c fftools_ffmpeg_filter.c MY_SRC_FILES += android_lts_support.c
else ifeq ($(TARGET_PLATFORM),android-17) else ifeq ($(TARGET_PLATFORM),android-17)
MY_SRC_FILES := ffmpegkit.c ffprobekit.c android_lts_support.c ffmpegkit_exception.c fftools_cmdutils.c fftools_ffmpeg.c fftools_ffprobe.c fftools_ffmpeg_opt.c fftools_ffmpeg_hw.c fftools_ffmpeg_filter.c MY_SRC_FILES += android_lts_support.c
else
MY_SRC_FILES := ffmpegkit.c ffprobekit.c ffmpegkit_exception.c fftools_cmdutils.c fftools_ffmpeg.c fftools_ffprobe.c fftools_ffmpeg_opt.c fftools_ffmpeg_hw.c fftools_ffmpeg_filter.c
endif endif
MY_CFLAGS := -Wall -Werror -Wno-unused-parameter -Wno-switch -Wno-sign-compare MY_CFLAGS := -Wall -Werror -Wno-unused-parameter -Wno-switch -Wno-sign-compare

@ -39,6 +39,7 @@ task javadoc(type: Javadoc) {
} }
dependencies { dependencies {
implementation 'com.arthenica:smart-exception-java:0.1.0'
testImplementation "androidx.test.ext:junit:1.1.2" testImplementation "androidx.test.ext:junit:1.1.2"
testImplementation "org.json:json:20190722" testImplementation "org.json:json:20190722"
} }

@ -39,6 +39,7 @@ task javadoc(type: Javadoc) {
} }
dependencies { dependencies {
implementation 'com.arthenica:smart-exception-java:0.1.0'
testImplementation "androidx.test.ext:junit:1.1.2" testImplementation "androidx.test.ext:junit:1.1.2"
testImplementation "org.json:json:20190722" testImplementation "org.json:json:20190722"
} }

Loading…
Cancel
Save