update android api, fixes #1

This commit is contained in:
Taner Sener 2021-01-11 01:09:39 +00:00
parent 18f6dd0099
commit a1e7a0b9da
45 changed files with 1972 additions and 655 deletions

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2020 Taner Sener
* Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@ -31,7 +31,7 @@
/** Callback data structure */
struct CallbackData {
int type; // 1 (log callback) or 2 (statistics callback)
long executionId; // execution id
long sessionId; // session identifier
int logLevel; // log level
AVBPrint logData; // log data
@ -47,20 +47,16 @@ struct CallbackData {
struct CallbackData *next;
};
/** Execution map variables */
const int EXECUTION_MAP_SIZE = 1000;
static volatile int executionMap[EXECUTION_MAP_SIZE];
static pthread_mutex_t executionMapMutex;
/** Session map variables */
const int SESSION_MAP_SIZE = 1000;
static volatile int sessionMap[SESSION_MAP_SIZE];
static pthread_mutex_t sessionMapMutex;
/** Redirection control variables */
static pthread_mutex_t lockMutex;
static pthread_mutex_t monitorMutex;
static pthread_cond_t monitorCondition;
/** Last command output variables */
static pthread_mutex_t logMutex;
static AVBPrint lastCommandOutput;
pthread_t callbackThread;
int redirectionEnabled;
@ -79,6 +75,9 @@ static jmethodID logMethod;
/** Global reference of statistics redirection method in Java */
static jmethodID statisticsMethod;
/** Global reference of closeParcelFileDescriptor method in Java */
static jmethodID closeParcelFileDescriptorMethod;
/** Global reference of String class in Java */
static jclass stringClass;
@ -86,7 +85,7 @@ static jclass stringClass;
static jmethodID stringConstructor;
/** 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 */
const char *stringClassName = "java/lang/String";
@ -98,8 +97,8 @@ volatile int handleSIGTERM = 1;
volatile int handleSIGXCPU = 1;
volatile int handleSIGPIPE = 1;
/** Holds the id of the current execution */
__thread volatile long executionId = 0;
/** Holds the id of the current session */
__thread volatile long sessionId = 0;
/** Holds the default log level */
int configuredLogLevel = AV_LOG_INFO;
@ -118,7 +117,6 @@ JNINativeMethod configMethods[] = {
{"registerNewNativeFFmpegPipe", "(Ljava/lang/String;)I", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_registerNewNativeFFmpegPipe},
{"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},
{"getNativeLastCommandOutput", "()Ljava/lang/String;", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeLastCommandOutput},
{"ignoreNativeSignal", "(I)V", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_ignoreNativeSignal}
};
@ -215,23 +213,12 @@ void monitorInit() {
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() {
pthread_mutexattr_t attributes;
pthread_mutexattr_init(&attributes);
pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE_NP);
pthread_mutex_init(&executionMapMutex, &attributes);
pthread_mutex_init(&sessionMapMutex, &attributes);
pthread_mutexattr_destroy(&attributes);
}
@ -244,52 +231,24 @@ void monitorUnInit() {
pthread_cond_destroy(&monitorCondition);
}
void logUnInit() {
pthread_mutex_destroy(&logMutex);
}
void executionMapLockUnInit() {
pthread_mutex_destroy(&executionMapMutex);
pthread_mutex_destroy(&sessionMapMutex);
}
void mutexLock() {
pthread_mutex_lock(&lockMutex);
}
void lastCommandOutputLock() {
pthread_mutex_lock(&logMutex);
}
void executionMapLock() {
pthread_mutex_lock(&executionMapMutex);
void sessionMapLock() {
pthread_mutex_lock(&sessionMapMutex);
}
void mutexUnlock() {
pthread_mutex_unlock(&lockMutex);
}
void lastCommandOutputUnlock() {
pthread_mutex_unlock(&logMutex);
}
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 sessionMapUnlock() {
pthread_mutex_unlock(&sessionMapMutex);
}
void monitorWait(int milliSeconds) {
@ -331,7 +290,7 @@ void logCallbackDataAdd(int level, AVBPrint *data) {
// CREATE DATA STRUCT FIRST
struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData));
newData->type = 1;
newData->executionId = executionId;
newData->sessionId = sessionId;
newData->logLevel = level;
av_bprint_init(&newData->logData, 0, AV_BPRINT_SIZE_UNLIMITED);
av_bprintf(&newData->logData, "%s", data->str);
@ -368,7 +327,7 @@ void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_
// CREATE DATA STRUCT FIRST
struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData));
newData->type = 2;
newData->executionId = executionId;
newData->sessionId = sessionId;
newData->statisticsFrameNumber = frameNumber;
newData->statisticsFps = fps;
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) {
executionMapLock();
void addSession(long id) {
sessionMapLock();
int key = id % EXECUTION_MAP_SIZE;
executionMap[key] = 1;
int key = id % SESSION_MAP_SIZE;
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 execution id
* @param id session id
*/
void removeExecution(long id) {
executionMapLock();
void removeSession(long id) {
sessionMapLock();
int key = id % EXECUTION_MAP_SIZE;
executionMap[key] = 0;
int key = id % SESSION_MAP_SIZE;
sessionMap[key] = 0;
executionMapUnlock();
sessionMapUnlock();
}
/**
* Checks whether a cancel request for the given execution id exists in the execution map.
* Adds a cancel session request to the session map.
*
* @param id execution id
* @param id session id
*/
void cancelSession(long id) {
sessionMapLock();
int key = id % SESSION_MAP_SIZE;
sessionMap[key] = 2;
sessionMapUnlock();
}
/**
* Checks whether a cancel request for the given session id exists in the session map.
*
* @param id session id
* @return 1 if exists, false otherwise
*/
int cancelRequested(long id) {
int found = 0;
executionMapLock();
sessionMapLock();
int key = id % EXECUTION_MAP_SIZE;
if (executionMap[key] == 0) {
int key = id % SESSION_MAP_SIZE;
if (sessionMap[key] == 2) {
found = 1;
}
executionMapUnlock();
sessionMapUnlock();
return found;
}
@ -519,7 +492,6 @@ void ffmpegkit_log_callback_function(void *ptr, int level, const char* format, v
if (fullLine.len > 0) {
logCallbackDataAdd(level, &fullLine);
appendLastCommandOutput(&fullLine);
}
av_bprint_finalize(part, NULL);
@ -576,7 +548,7 @@ void *callbackThreadFunction() {
jbyteArray byteArray = (jbyteArray) (*env)->NewByteArray(env, size);
(*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);
// CLEAN LOG DATA
@ -587,7 +559,7 @@ void *callbackThreadFunction() {
// STATISTICS CALLBACK
(*env)->CallStaticVoidMethod(env, configClass, statisticsMethod,
(jlong) callbackData->executionId, callbackData->statisticsFrameNumber,
(jlong) callbackData->sessionId, callbackData->statisticsFrameNumber,
callbackData->statisticsFps, callbackData->statisticsQuality,
callbackData->statisticsSize, callbackData->statisticsTime,
callbackData->statisticsBitrate, callbackData->statisticsSpeed);
@ -610,6 +582,15 @@ void *callbackThreadFunction() {
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.
*
@ -630,7 +611,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
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);
return JNI_FALSE;
}
@ -655,6 +636,12 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
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");
if (stringConstructor == NULL) {
LOGE("OnLoad thread failed to GetMethodID for %s.\n", "<init>");
@ -671,13 +658,12 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
callbackDataHead = NULL;
callbackDataTail = NULL;
for(int i = 0; i<EXECUTION_MAP_SIZE; i++) {
executionMap[i] = 0;
for(int i = 0; i<SESSION_MAP_SIZE; i++) {
sessionMap[i] = 0;
}
mutexInit();
monitorInit();
logInit();
executionMapLockInit();
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 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
* @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
executionId = (long) id;
addExecution((long) id);
sessionId = (long) id;
addSession((long) id);
// RUN
int retCode = ffmpeg_execute(argumentCount, argv);
// ALWAYS REMOVE THE ID FROM THE MAP
removeExecution((long) id);
removeSession((long) id);
// CLEANUP
if (tempArray != NULL) {
@ -851,7 +834,7 @@ JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpeg
*
* @param env pointer to native method interface
* @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) {
cancel_operation(id);
@ -904,25 +887,6 @@ JNIEXPORT int JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_setNativeEnvi
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.
*

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2020 Taner Sener
* Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@ -94,7 +94,7 @@ JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNative
* Method: nativeFFmpegExecute
* 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
@ -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);
/*
* 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
* Method: ignoreNativeSignal

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Taner Sener
* Copyright (c) 2020-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@ -29,20 +29,21 @@
/** Forward declaration for function defined in fftools_ffprobe.c */
int ffprobe_execute(int argc, char **argv);
/** Forward declaration for function defined in ffmpegkit.c */
void clearLastCommandOutput();
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.
*
* @param env pointer to native method interface
* @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
* @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;
int argumentCount = 1;
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
clearLastCommandOutput();
// REGISTER THE ID BEFORE STARTING EXECUTION
sessionId = (long) id;
addSession((long) id);
// RUN
int retCode = ffprobe_execute(argumentCount, argv);
// ALWAYS REMOVE THE ID FROM THE MAP
removeSession((long) id);
// CLEANUP
if (tempArray != NULL) {
for (int i = 0; i < (argumentCount - 1); i++) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Taner Sener
* Copyright (c) 2020-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@ -25,8 +25,8 @@
/*
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
* 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 */

View File

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

View File

@ -253,8 +253,8 @@ extern volatile int handleSIGTERM;
extern volatile int handleSIGXCPU;
extern volatile int handleSIGPIPE;
extern __thread volatile long executionId;
extern void removeExecution(long id);
extern __thread volatile long sessionId;
extern void cancelSession(long id);
extern int cancelRequested(long id);
/* sub2video hack:
@ -729,7 +729,7 @@ static void ffmpeg_cleanup(int ret)
if (received_sigterm) {
av_log(NULL, AV_LOG_INFO, "Exiting normally, received signal %d.\n",
(int) received_sigterm);
} else if (cancelRequested(executionId)) {
} else if (cancelRequested(sessionId)) {
av_log(NULL, AV_LOG_INFO, "Exiting normally, received cancel signal.\n");
} else if (ret && atomic_load(&transcode_init_done)) {
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) {
/* 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);
}
if (ret < 0)
@ -4859,7 +4859,7 @@ static int transcode(void)
goto fail;
#endif
while (!received_sigterm && !cancelRequested(executionId)) {
while (!received_sigterm && !cancelRequested(sessionId)) {
int64_t cur_time= av_gettime_relative();
/* if 'q' pressed, exits */
@ -5064,7 +5064,7 @@ void cancel_operation(long id)
if (id == 0) {
sigterm_handler(SIGINT);
} 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])
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 {
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;

View File

@ -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);
}

View File

@ -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

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2020 Taner Sener
* Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@ -20,7 +20,9 @@
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 {
@ -59,7 +61,7 @@ public enum Abi {
*/
ABI_UNKNOWN("unknown");
private String name;
private final String 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
*/

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2020 Taner Sener
* Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@ -20,7 +20,7 @@
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 {
@ -46,8 +46,8 @@ public class AbiDetect {
private AbiDetect() {
}
static void setArmV7aNeonLoaded(final boolean armV7aNeonLoaded) {
AbiDetect.armV7aNeonLoaded = armV7aNeonLoaded;
static void setArmV7aNeonLoaded() {
armV7aNeonLoaded = true;
}
/**

View File

@ -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);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2020 Taner Sener
* Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@ -19,44 +19,29 @@
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> {
private final String[] arguments;
public class AsyncFFmpegExecuteTask implements Runnable {
private final FFmpegSession ffmpegSession;
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) {
this.executionId = executionId;
this.arguments = arguments;
this.executeCallback = executeCallback;
public AsyncFFmpegExecuteTask(final FFmpegSession ffmpegSession) {
this.ffmpegSession = ffmpegSession;
this.executeCallback = ffmpegSession.getExecuteCallback();
}
@Override
protected Integer doInBackground(final Void... unused) {
return FFmpegKitConfig.ffmpegExecute(executionId, this.arguments);
}
public void run() {
FFmpegKitConfig.ffmpegExecute(ffmpegSession);
final ExecuteCallback globalExecuteCallbackFunction = FFmpegKitConfig.getGlobalExecuteCallbackFunction();
if (globalExecuteCallbackFunction != null) {
globalExecuteCallbackFunction.apply(ffmpegSession);
}
@Override
protected void onPostExecute(final Integer rc) {
if (executeCallback != null) {
executeCallback.apply(executionId, rc);
executeCallback.apply(ffmpegSession);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2020 Taner Sener
* Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@ -19,34 +19,24 @@
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> {
private final String[] arguments;
private final ExecuteCallback ExecuteCallback;
public class AsyncFFprobeExecuteTask implements Runnable {
private final FFprobeSession ffprobeSession;
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) {
this.arguments = arguments;
ExecuteCallback = executeCallback;
public AsyncFFprobeExecuteTask(final FFprobeSession ffprobeSession) {
this.ffprobeSession = ffprobeSession;
this.executeCallback = ffprobeSession.getExecuteCallback();
}
@Override
protected Integer doInBackground(final Void... unused) {
return FFprobeKit.execute(this.arguments);
}
public void run() {
FFmpegKitConfig.ffprobeExecute(ffprobeSession);
@Override
protected void onPostExecute(final Integer rc) {
if (ExecuteCallback != null) {
ExecuteCallback.apply(FFmpegKit.DEFAULT_EXECUTION_ID, rc);
if (executeCallback != null) {
executeCallback.apply(ffprobeSession);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2020 Taner Sener
* Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@ -19,29 +19,24 @@
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> {
private final String path;
private final GetMediaInformationCallback getMediaInformationCallback;
public class AsyncGetMediaInformationTask implements Runnable {
private final MediaInformationSession mediaInformationSession;
private final ExecuteCallback executeCallback;
public AsyncGetMediaInformationTask(final String path, final GetMediaInformationCallback getMediaInformationCallback) {
this.path = path;
this.getMediaInformationCallback = getMediaInformationCallback;
public AsyncGetMediaInformationTask(final MediaInformationSession mediaInformationSession) {
this.mediaInformationSession = mediaInformationSession;
this.executeCallback = mediaInformationSession.getExecuteCallback();
}
@Override
protected MediaInformation doInBackground(final String... arguments) {
return FFprobeKit.getMediaInformation(path);
}
public void run() {
FFmpegKitConfig.getMediaInformationExecute(mediaInformationSession);
@Override
protected void onPostExecute(final MediaInformation mediaInformation) {
if (getMediaInformationCallback != null) {
getMediaInformationCallback.apply(mediaInformation);
if (executeCallback != null) {
executeCallback.apply(mediaInformationSession);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2020 Taner Sener
* Copyright (c) 2019-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@ -34,7 +34,9 @@ import static android.content.Context.CAMERA_SERVICE;
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 {

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2020 Taner Sener
* Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@ -19,12 +19,9 @@
package com.arthenica.ffmpegkit;
import android.os.AsyncTask;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
/**
* <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 {
static final long DEFAULT_EXECUTION_ID = 0;
private static final AtomicLong executionIdCounter = new AtomicLong(3000);
static {
AbiDetect.class.getName();
FFmpegKitConfig.class.getName();
@ -59,26 +52,50 @@ public class FFmpegKit {
* <p>Synchronously executes FFmpeg with arguments provided.
*
* @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) {
return FFmpegKitConfig.ffmpegExecute(DEFAULT_EXECUTION_ID, arguments);
public static FFmpegSession execute(final String[] arguments) {
final FFmpegSession session = new FFmpegSession(arguments, null, null, null);
FFmpegKitConfig.ffmpegExecute(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
* @return returns a unique id that represents this execution
* @param executeCallback callback that will be notified when the execution is completed
* @return ffmpeg session created for this execution
*/
public static long executeAsync(final String[] arguments, final ExecuteCallback executeCallback) {
final long newExecutionId = executionIdCounter.incrementAndGet();
public static FFmpegSession executeAsync(final String[] arguments,
final ExecuteCallback executeCallback) {
final FFmpegSession session = new FFmpegSession(arguments, executeCallback, null, null);
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, arguments, executeCallback);
asyncFFmpegExecuteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
FFmpegKitConfig.asyncFFmpegExecute(session);
return newExecutionId;
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 FFmpegSession executeAsync(final String[] arguments,
final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback) {
final FFmpegSession session = new FFmpegSession(arguments, executeCallback, logCallback, statisticsCallback);
FFmpegKitConfig.asyncFFmpegExecute(session);
return session;
}
/**
@ -87,31 +104,40 @@ public class FFmpegKit {
* @param arguments FFmpeg command options/arguments as string array
* @param executeCallback callback that will be notified when execution is completed
* @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) {
final long newExecutionId = executionIdCounter.incrementAndGet();
public static FFmpegSession executeAsync(final String[] arguments,
final ExecuteCallback executeCallback,
final Executor executor) {
final FFmpegSession session = new FFmpegSession(arguments, executeCallback, null, null);
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, arguments, executeCallback);
asyncFFmpegExecuteTask.executeOnExecutor(executor);
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(session);
executor.execute(asyncFFmpegExecuteTask);
return newExecutionId;
return session;
}
/**
* <p>Synchronously executes FFmpeg command provided. Command is split into arguments using
* provided delimiter character.
* <p>Asynchronously executes FFmpeg with arguments provided.
*
* @param command FFmpeg command
* @param delimiter delimiter used to split arguments
* @return 0 on successful execution, 255 on user cancel, other non-zero codes on error
* @since 3.0
* @deprecated argument splitting mechanism used in this method is pretty simple and prone to
* errors. Consider using a more advanced method like {@link #execute(String)} or
* {@link #execute(String[])}
* @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
* @param executor executor that will be used to run this asynchronous operation
* @return ffmpeg session created for this execution
*/
public static int execute(final String command, final String delimiter) {
return execute((command == null) ? new String[]{""} : command.split((delimiter == null) ? " " : delimiter));
public static FFmpegSession executeAsync(final String[] arguments,
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.
*
* @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));
}
@ -133,15 +159,29 @@ public class FFmpegKit {
*
* @param command FFmpeg command
* @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) {
final long newExecutionId = executionIdCounter.incrementAndGet();
public static FFmpegSession executeAsync(final String command,
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 executeCallback callback that will be notified when execution is completed
* @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 command, final ExecuteCallback executeCallback, final Executor executor) {
final long newExecutionId = executionIdCounter.incrementAndGet();
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(newExecutionId, command, executeCallback);
asyncFFmpegExecuteTask.executeOnExecutor(executor);
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(session);
executor.execute(asyncFFmpegExecuteTask);
return newExecutionId;
return session;
}
/**
* <p>Cancels an ongoing operation.
* <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 FFmpegSession executeAsync(final String command,
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(session);
executor.execute(asyncFFmpegExecuteTask);
return session;
}
/**
* <p>Cancels the last execution started.
*
* <p>This function does not wait for termination to complete and returns immediately.
*/
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.
*
* @param executionId id of the execution
* @param sessionId id of the session that will be stopped
*/
public static void cancel(final long executionId) {
FFmpegKitConfig.nativeFFmpegCancel(executionId);
public static void cancel(final long sessionId) {
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() {
return FFmpegKitConfig.listFFmpegExecutions();
public static List<FFmpegSession> listSessions() {
return FFmpegKitConfig.getFFmpegSessions();
}
/**

View File

@ -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();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Taner Sener
* Copyright (c) 2020-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@ -19,14 +19,19 @@
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
* FFprobe commands.
* <p>Main class for FFprobe operations.
* <p>Supports running FFprobe commands using {@link #execute(String...)} method.
* <pre>
* int rc = 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));
* FFprobeSession session = FFprobe.execute("-hide_banner -v error -show_entries format=size -of default=noprint_wrappers=1 file1.mp4");
* 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>
*/
public class FFprobeKit {
@ -46,14 +51,14 @@ public class FFprobeKit {
* <p>Synchronously executes FFprobe with arguments provided.
*
* @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) {
final int lastReturnCode = FFmpegKitConfig.nativeFFprobeExecute(arguments);
public static FFprobeSession execute(final String[] 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.
*
* @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));
}
/**
* <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
* multiple operations (execute or getMediaInformation) at the same time, the response of this
* method is not predictable.
*
* @param path path or uri of media file
* @return media information
* @since 3.0
* @param command FFprobe command
* @param executeCallback callback that will be notified when the execution is completed
* @return ffprobe session created for this execution
*/
public static MediaInformation getMediaInformation(final String path) {
return getMediaInformationFromCommandArguments(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path});
public static FFprobeSession executeAsync(final String command,
final ExecuteCallback executeCallback) {
return executeAsync(FFmpegKit.parseArguments(command), executeCallback);
}
/**
* <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
* @return ffprobe session created for this execution
*/
public static FFprobeSession executeAsync(final String[] arguments,
final ExecuteCallback executeCallback) {
final FFprobeSession session = new FFprobeSession(arguments, executeCallback, null, null);
FFmpegKitConfig.asyncFFprobeExecute(session);
return session;
}
/**
* <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.
*
* @param command FFprobe 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 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 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
* @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 MediaInformationSession getMediaInformationAsync(final String path,
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 the given command.
*
* <p>This method does not support executing multiple concurrent operations. If you execute
* multiple operations (execute or getMediaInformation) at the same time, the response of this
* method is not predictable.
*
* @param command command to execute
* @return media information
* @since 4.3.3
* @return media information session created for this execution
*/
public static MediaInformation getMediaInformationFromCommand(final String command) {
return getMediaInformationFromCommandArguments(FFmpegKit.parseArguments(command));
public static MediaInformationSession getMediaInformationFromCommand(final String command) {
return getMediaInformationFromCommandArguments(FFmpegKit.parseArguments(command), null, null, null);
}
/**
* <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
* multiple operations (execute or getMediaInformation) at the same time, the response of this
* method is not predictable.
*
* @param path path or uri of media file
* @param timeout complete timeout
* @return media information
* @since 3.0
* @deprecated this method is deprecated since v4.3.1. You can still use this method but
* <code>timeout</code> parameter is not effective anymore.
* @param command command to execute
* @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 MediaInformation getMediaInformation(final String path, final Long timeout) {
return getMediaInformation(path);
public static MediaInformationSession getMediaInformationFromCommand(final String command,
final ExecuteCallback executeCallback,
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,
final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback) {
final MediaInformationSession session = new MediaInformationSession(arguments, executeCallback, logCallback, statisticsCallback);
if (rc == 0) {
return MediaInformationParser.from(FFmpegKitConfig.getLastCommandOutput());
} else {
Log.w(FFmpegKitConfig.TAG, FFmpegKitConfig.getLastCommandOutput());
return null;
}
FFmpegKitConfig.getMediaInformationExecute(session);
return session;
}
}

View File

@ -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();
}
}

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2020 Taner Sener
* Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@ -20,11 +20,16 @@
package com.arthenica.ffmpegkit;
/**
* <p>Represents a callback function to receive logs from running executions
* <p>Callback function to receive logs for executions.
*/
@FunctionalInterface
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);
}

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2020 Taner Sener
* Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@ -21,6 +21,8 @@ package com.arthenica.ffmpegkit;
import android.util.Log;
import com.arthenica.smartexception.java.Exceptions;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -28,12 +30,14 @@ import org.json.JSONObject;
import java.util.ArrayList;
/**
* Helper class for parsing {@link MediaInformation}.
* Parser for {@link MediaInformation}.
*/
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
* @return created {@link MediaInformation} instance of null if a parsing error occurs
@ -42,8 +46,7 @@ public class MediaInformationParser {
try {
return fromWithError(ffprobeJsonOutput);
} catch (JSONException e) {
Log.e(FFmpegKitConfig.TAG, "MediaInformation parsing failed.", e);
e.printStackTrace();
Log.e(FFmpegKitConfig.TAG, String.format("MediaInformation parsing failed.%s", Exceptions.getStackTraceString(e)));
return null;
}
}

View File

@ -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();
}
}

View File

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

View File

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

View File

@ -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();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
}

View File

@ -61,12 +61,12 @@ include $(BUILD_SHARED_LIBRARY)
$(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)
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)
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
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
MY_SRC_FILES += android_lts_support.c
endif
MY_CFLAGS := -Wall -Werror -Wno-unused-parameter -Wno-switch -Wno-sign-compare

View File

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

View File

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