fix android runtime errors

This commit is contained in:
Taner Sener 2021-01-19 22:55:07 +00:00
parent 90b5fca4ee
commit 18a6a9c066
16 changed files with 492 additions and 198 deletions

View File

@ -50,6 +50,7 @@ struct CallbackData {
/** Session map variables */ /** Session map variables */
const int SESSION_MAP_SIZE = 1000; const int SESSION_MAP_SIZE = 1000;
static volatile int sessionMap[SESSION_MAP_SIZE]; static volatile int sessionMap[SESSION_MAP_SIZE];
static volatile int sessionInTransitMessageCountMap[SESSION_MAP_SIZE];
static pthread_mutex_t sessionMapMutex; static pthread_mutex_t sessionMapMutex;
/** Redirection control variables */ /** Redirection control variables */
@ -113,11 +114,12 @@ JNINativeMethod configMethods[] = {
{"getNativeVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeVersion}, {"getNativeVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeVersion},
{"nativeFFmpegExecute", "(J[Ljava/lang/String;)I", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpegExecute}, {"nativeFFmpegExecute", "(J[Ljava/lang/String;)I", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpegExecute},
{"nativeFFmpegCancel", "(J)V", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpegCancel}, {"nativeFFmpegCancel", "(J)V", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpegCancel},
{"nativeFFprobeExecute", "([Ljava/lang/String;)I", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFprobeExecute}, {"nativeFFprobeExecute", "(J[Ljava/lang/String;)I", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFprobeExecute},
{"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},
{"ignoreNativeSignal", "(I)V", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_ignoreNativeSignal} {"ignoreNativeSignal", "(I)V", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_ignoreNativeSignal},
{"messagesInTransmit", "(J)I", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_messagesInTransmit}
}; };
/** Forward declaration for function defined in fftools_ffmpeg.c */ /** Forward declaration for function defined in fftools_ffmpeg.c */
@ -213,7 +215,7 @@ void monitorInit() {
pthread_condattr_destroy(&cattributes); pthread_condattr_destroy(&cattributes);
} }
void executionMapLockInit() { void sessionMapLockInit() {
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);
@ -231,7 +233,7 @@ void monitorUnInit() {
pthread_cond_destroy(&monitorCondition); pthread_cond_destroy(&monitorCondition);
} }
void executionMapLockUnInit() { void sessionMapLockUnInit() {
pthread_mutex_destroy(&sessionMapMutex); pthread_mutex_destroy(&sessionMapMutex);
} }
@ -314,6 +316,9 @@ void logCallbackDataAdd(int level, AVBPrint *data) {
callbackDataTail = newData; callbackDataTail = newData;
} }
int key = sessionId % SESSION_MAP_SIZE;
sessionInTransitMessageCountMap[key] += 1;
mutexUnlock(); mutexUnlock();
monitorNotify(); monitorNotify();
@ -356,6 +361,9 @@ void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_
callbackDataTail = newData; callbackDataTail = newData;
} }
int key = sessionId % SESSION_MAP_SIZE;
sessionInTransitMessageCountMap[key] += 1;
mutexUnlock(); mutexUnlock();
monitorNotify(); monitorNotify();
@ -456,6 +464,20 @@ int cancelRequested(long id) {
return found; return found;
} }
/**
* Resets the number of messages in transmit for this session.
*
* @param id session id
*/
void resetMessagesInTransmit(long id) {
mutexLock();
int key = id % SESSION_MAP_SIZE;
sessionInTransitMessageCountMap[key] = 0;
mutexUnlock();
}
/** /**
* Callback function for FFmpeg logs. * Callback function for FFmpeg logs.
* *
@ -566,6 +588,9 @@ void *callbackThreadFunction() {
} }
int key = callbackData->sessionId % SESSION_MAP_SIZE;
sessionInTransitMessageCountMap[key] -= 1;
// CLEAN STRUCT // CLEAN STRUCT
callbackData->next = NULL; callbackData->next = NULL;
av_free(callbackData); av_free(callbackData);
@ -611,7 +636,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
return JNI_FALSE; return JNI_FALSE;
} }
if ((*env)->RegisterNatives(env, localConfigClass, configMethods, 13) < 0) { if ((*env)->RegisterNatives(env, localConfigClass, configMethods, 14) < 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;
} }
@ -664,7 +689,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
mutexInit(); mutexInit();
monitorInit(); monitorInit();
executionMapLockInit(); sessionMapLockInit();
return JNI_VERSION_1_6; return JNI_VERSION_1_6;
} }
@ -805,10 +830,12 @@ JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpeg
} }
} }
// REGISTER THE ID BEFORE STARTING EXECUTION // REGISTER THE ID BEFORE STARTING THE EXECUTION
sessionId = (long) id; sessionId = (long) id;
addSession((long) id); addSession((long) id);
resetMessagesInTransmit(sessionId);
// RUN // RUN
int retCode = ffmpeg_execute(argumentCount, argv); int retCode = ffmpeg_execute(argumentCount, argv);
@ -907,3 +934,22 @@ JNIEXPORT void JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_ignoreNative
handleSIGPIPE = 0; handleSIGPIPE = 0;
} }
} }
/**
* Returns the number of native messages which are not transmitted to the Java callbacks for the
* given session.
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @param id session id
*/
JNIEXPORT int JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_messagesInTransmit(JNIEnv *env, jclass object, jlong id) {
mutexLock();
int key = id % SESSION_MAP_SIZE;
int count = sessionInTransitMessageCountMap[key];
mutexUnlock();
return count;
}

View File

@ -131,4 +131,11 @@ JNIEXPORT int JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_setNativeEnvi
*/ */
JNIEXPORT void JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_ignoreNativeSignal(JNIEnv *env, jclass object, jint signum); JNIEXPORT void JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_ignoreNativeSignal(JNIEnv *env, jclass object, jint signum);
/*
* Class: com_arthenica_ffmpegkit_FFmpegKitConfig
* Method: messagesInTransmit
* Signature: (J)I
*/
JNIEXPORT int JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_messagesInTransmit(JNIEnv *env, jclass object, jlong id);
#endif /* FFMPEG_KIT_H */ #endif /* FFMPEG_KIT_H */

View File

@ -33,6 +33,7 @@ extern int configuredLogLevel;
extern __thread volatile long sessionId; extern __thread volatile long sessionId;
extern void addSession(long id); extern void addSession(long id);
extern void removeSession(long id); extern void removeSession(long id);
extern void resetMessagesInTransmit(long id);
/** /**
* Synchronously executes FFprobe natively with arguments provided. * Synchronously executes FFprobe natively with arguments provided.
@ -76,10 +77,12 @@ JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFprob
} }
} }
// REGISTER THE ID BEFORE STARTING EXECUTION // REGISTER THE ID BEFORE STARTING THE EXECUTION
sessionId = (long) id; sessionId = (long) id;
addSession((long) id); addSession((long) id);
resetMessagesInTransmit(sessionId);
// RUN // RUN
int retCode = ffprobe_execute(argumentCount, argv); int retCode = ffprobe_execute(argumentCount, argv);

View File

@ -1846,12 +1846,6 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti
int ret; int ret;
float t; float t;
// FORWARD IT BEFORE PROCESSING
forward_report(is_last_report, timer_start, cur_time);
if (!print_stats && !is_last_report && !progress_avio)
return;
if (!is_last_report) { if (!is_last_report) {
if (last_time == -1) { if (last_time == -1) {
last_time = cur_time; last_time = cur_time;
@ -1862,6 +1856,11 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti
last_time = cur_time; last_time = cur_time;
} }
forward_report(is_last_report, timer_start, cur_time);
if (!print_stats && !is_last_report && !progress_avio)
return;
t = (cur_time-timer_start) / 1000000.0; t = (cur_time-timer_start) / 1000000.0;

View File

@ -22,22 +22,22 @@ package com.arthenica.ffmpegkit;
import com.arthenica.smartexception.java.Exceptions; import com.arthenica.smartexception.java.Exceptions;
import java.util.Date; import java.util.Date;
import java.util.Optional;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong; 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 { public abstract class AbstractSession implements Session {
/** /**
* Generates ids for execute sessions. * Generates ids for execute sessions.
*/ */
private static final AtomicLong sessionIdGenerator = new AtomicLong(1); protected static final AtomicLong sessionIdGenerator = new AtomicLong(1);
/**
* Defines how long default `getAll` methods wait.
*/
protected static final int DEFAULT_TIMEOUT_FOR_CALLBACK_MESSAGES_IN_TRANSMIT = 5000;
protected final ExecuteCallback executeCallback; protected final ExecuteCallback executeCallback;
protected final LogCallback logCallback; protected final LogCallback logCallback;
@ -52,11 +52,13 @@ public abstract class AbstractSession implements Session {
protected SessionState state; protected SessionState state;
protected int returnCode; protected int returnCode;
protected String failStackTrace; protected String failStackTrace;
protected final LogRedirectionStrategy logRedirectionStrategy;
public AbstractSession(final String[] arguments, public AbstractSession(final String[] arguments,
final ExecuteCallback executeCallback, final ExecuteCallback executeCallback,
final LogCallback logCallback, final LogCallback logCallback,
final StatisticsCallback statisticsCallback) { final StatisticsCallback statisticsCallback,
final LogRedirectionStrategy logRedirectionStrategy) {
this.sessionId = sessionIdGenerator.getAndIncrement(); this.sessionId = sessionIdGenerator.getAndIncrement();
this.createTime = new Date(); this.createTime = new Date();
this.startTime = null; this.startTime = null;
@ -69,36 +71,45 @@ public abstract class AbstractSession implements Session {
this.state = SessionState.CREATED; this.state = SessionState.CREATED;
this.returnCode = ReturnCode.NOT_SET; this.returnCode = ReturnCode.NOT_SET;
this.failStackTrace = null; this.failStackTrace = null;
this.logRedirectionStrategy = logRedirectionStrategy;
} }
@Override
public ExecuteCallback getExecuteCallback() { public ExecuteCallback getExecuteCallback() {
return executeCallback; return executeCallback;
} }
@Override
public LogCallback getLogCallback() { public LogCallback getLogCallback() {
return logCallback; return logCallback;
} }
@Override
public StatisticsCallback getStatisticsCallback() { public StatisticsCallback getStatisticsCallback() {
return statisticsCallback; return statisticsCallback;
} }
@Override
public long getSessionId() { public long getSessionId() {
return sessionId; return sessionId;
} }
@Override
public Date getCreateTime() { public Date getCreateTime() {
return createTime; return createTime;
} }
@Override
public Date getStartTime() { public Date getStartTime() {
return startTime; return startTime;
} }
@Override
public Date getEndTime() { public Date getEndTime() {
return endTime; return endTime;
} }
@Override
public long getDuration() { public long getDuration() {
final Date startTime = this.startTime; final Date startTime = this.startTime;
final Date endTime = this.endTime; final Date endTime = this.endTime;
@ -109,85 +120,141 @@ public abstract class AbstractSession implements Session {
return -1; return -1;
} }
@Override
public String[] getArguments() { public String[] getArguments() {
return arguments; return arguments;
} }
@Override
public String getCommand() { public String getCommand() {
return FFmpegKit.argumentsToString(arguments); return FFmpegKit.argumentsToString(arguments);
} }
protected void waitForCallbackMessagesInTransmit(final int timeout) {
final long start = System.currentTimeMillis();
/*
* WE GIVE MAX 5 SECONDS TO TRANSMIT ALL NATIVE MESSAGES
*/
while (thereAreCallbackMessagesInTransmit() && (System.currentTimeMillis() < (start + timeout))) {
synchronized (this) {
try {
wait(100);
} catch (InterruptedException ignored) {
}
}
}
}
@Override
public Queue<Log> getAllLogs(final int waitTimeout) {
waitForCallbackMessagesInTransmit(waitTimeout);
if (thereAreCallbackMessagesInTransmit()) {
android.util.Log.i(FFmpegKitConfig.TAG, String.format("getAllLogs was asked to return all logs but there are still logs being transmitted for session id %d.", sessionId));
}
return logs;
}
@Override
public Queue<Log> getAllLogs() {
return getAllLogs(DEFAULT_TIMEOUT_FOR_CALLBACK_MESSAGES_IN_TRANSMIT);
}
@Override
public Queue<Log> getLogs() { public Queue<Log> getLogs() {
return logs; return logs;
} }
public Stream<Log> getLogsAsStream() { @Override
return logs.stream(); public String getAllLogsAsString(final int waitTimeout) {
waitForCallbackMessagesInTransmit(waitTimeout);
if (thereAreCallbackMessagesInTransmit()) {
android.util.Log.i(FFmpegKitConfig.TAG, String.format("getAllLogsAsString was asked to return all logs but there are still logs being transmitted for session id %d.", sessionId));
}
return getLogsAsString();
} }
@Override
public String getAllLogsAsString() {
return getAllLogsAsString(DEFAULT_TIMEOUT_FOR_CALLBACK_MESSAGES_IN_TRANSMIT);
}
@Override
public String getLogsAsString() { public String getLogsAsString() {
final Optional<String> concatenatedStringOption = logs.stream().map(new Function<Log, String>() { final StringBuilder concatenatedString = new StringBuilder();
@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>() { for (Log log : logs) {
concatenatedString.append(log.getMessage());
}
@Override return concatenatedString.toString();
public String get() {
return "";
}
});
} }
@Override
public SessionState getState() { public SessionState getState() {
return state; return state;
} }
@Override
public int getReturnCode() { public int getReturnCode() {
return returnCode; return returnCode;
} }
@Override
public String getFailStackTrace() { public String getFailStackTrace() {
return failStackTrace; return failStackTrace;
} }
@Override
public LogRedirectionStrategy getLogRedirectionStrategy() {
return logRedirectionStrategy;
}
@Override
public boolean thereAreCallbackMessagesInTransmit() {
return (FFmpegKitConfig.messagesInTransmit(sessionId) != 0);
}
@Override
public void addLog(final Log log) { public void addLog(final Log log) {
this.logs.add(log); this.logs.add(log);
} }
@Override
public Future<?> getFuture() { public Future<?> getFuture() {
return future; return future;
} }
@Override
public void setFuture(final Future<?> future) { public void setFuture(final Future<?> future) {
this.future = future; this.future = future;
} }
@Override
public void startRunning() { public void startRunning() {
this.state = SessionState.RUNNING; this.state = SessionState.RUNNING;
this.startTime = new Date(); this.startTime = new Date();
} }
@Override
public void complete(final int returnCode) { public void complete(final int returnCode) {
this.returnCode = returnCode; this.returnCode = returnCode;
this.state = SessionState.COMPLETED; this.state = SessionState.COMPLETED;
this.endTime = new Date(); this.endTime = new Date();
} }
@Override
public void fail(final Exception exception) { public void fail(final Exception exception) {
this.failStackTrace = Exceptions.getStackTraceString(exception); this.failStackTrace = Exceptions.getStackTraceString(exception);
this.state = SessionState.FAILED; this.state = SessionState.FAILED;
this.endTime = new Date(); this.endTime = new Date();
} }
@Override
public void cancel() { public void cancel() {
if (state == SessionState.RUNNING) { if (state == SessionState.RUNNING) {
FFmpegKit.cancel(sessionId); FFmpegKit.cancel(sessionId);

View File

@ -35,6 +35,11 @@ public class AsyncFFprobeExecuteTask implements Runnable {
public void run() { public void run() {
FFmpegKitConfig.ffprobeExecute(ffprobeSession); FFmpegKitConfig.ffprobeExecute(ffprobeSession);
final ExecuteCallback globalExecuteCallbackFunction = FFmpegKitConfig.getGlobalExecuteCallbackFunction();
if (globalExecuteCallbackFunction != null) {
globalExecuteCallbackFunction.apply(ffprobeSession);
}
if (executeCallback != null) { if (executeCallback != null) {
executeCallback.apply(ffprobeSession); executeCallback.apply(ffprobeSession);
} }

View File

@ -25,15 +25,26 @@ package com.arthenica.ffmpegkit;
public class AsyncGetMediaInformationTask implements Runnable { public class AsyncGetMediaInformationTask implements Runnable {
private final MediaInformationSession mediaInformationSession; private final MediaInformationSession mediaInformationSession;
private final ExecuteCallback executeCallback; private final ExecuteCallback executeCallback;
private final Integer waitTimeout;
public AsyncGetMediaInformationTask(final MediaInformationSession mediaInformationSession) { public AsyncGetMediaInformationTask(final MediaInformationSession mediaInformationSession) {
this(mediaInformationSession, AbstractSession.DEFAULT_TIMEOUT_FOR_CALLBACK_MESSAGES_IN_TRANSMIT);
}
public AsyncGetMediaInformationTask(final MediaInformationSession mediaInformationSession, final Integer waitTimeout) {
this.mediaInformationSession = mediaInformationSession; this.mediaInformationSession = mediaInformationSession;
this.executeCallback = mediaInformationSession.getExecuteCallback(); this.executeCallback = mediaInformationSession.getExecuteCallback();
this.waitTimeout = waitTimeout;
} }
@Override @Override
public void run() { public void run() {
FFmpegKitConfig.getMediaInformationExecute(mediaInformationSession); FFmpegKitConfig.getMediaInformationExecute(mediaInformationSession, waitTimeout);
final ExecuteCallback globalExecuteCallbackFunction = FFmpegKitConfig.getGlobalExecuteCallbackFunction();
if (globalExecuteCallbackFunction != null) {
globalExecuteCallbackFunction.apply(mediaInformationSession);
}
if (executeCallback != null) { if (executeCallback != null) {
executeCallback.apply(mediaInformationSession); executeCallback.apply(mediaInformationSession);

View File

@ -27,12 +27,12 @@ import java.util.concurrent.Executor;
* <p>Main class for FFmpeg operations. Supports synchronous {@link #execute(String...)} and * <p>Main class for FFmpeg operations. Supports synchronous {@link #execute(String...)} and
* asynchronous {@link #executeAsync(String, ExecuteCallback)} methods to execute FFmpeg commands. * asynchronous {@link #executeAsync(String, ExecuteCallback)} methods to execute FFmpeg commands.
* <pre> * <pre>
* int rc = FFmpeg.execute("-i file1.mp4 -c:v libxvid file1.avi"); * FFmpegSession session = FFmpeg.execute("-i file1.mp4 -c:v libxvid file1.avi");
* Log.i(Config.TAG, String.format("Command execution %s.", (rc == 0?"completed successfully":"failed with rc=" + rc)); * Log.i(Config.TAG, String.format("Command execution completed with %d.", session.getReturnCode());
* </pre> * </pre>
* <pre> * <pre>
* long executionId = FFmpeg.executeAsync("-i file1.mp4 -c:v libxvid file1.avi", executeCallback); * FFmpegSession session = FFmpeg.executeAsync("-i file1.mp4 -c:v libxvid file1.avi", executeCallback);
* Log.i(Config.TAG, String.format("Asynchronous execution %d started.", executionId)); * Log.i(Config.TAG, String.format("Asynchronous session %d started.", session.getSessionId()));
* </pre> * </pre>
*/ */
public class FFmpegKit { public class FFmpegKit {
@ -231,17 +231,18 @@ public class FFmpegKit {
} }
/** /**
* <p>Cancels the last execution started. * <p>Cancels all ongoing executions.
* *
* <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() {
Session lastSession = FFmpegKitConfig.getLastSession();
if (lastSession != null) { /*
FFmpegKitConfig.nativeFFmpegCancel(lastSession.getSessionId()); * ZERO (0) IS A SPECIAL SESSION ID
} else { * WHEN IT IS PASSED TO THIS METHOD, A SIGINT IS GENERATED WHICH CANCELS ALL ONGOING
android.util.Log.w(FFmpegKitConfig.TAG, "FFmpegKit cancel skipped. The last execution does not exist."); * EXECUTIONS
} */
FFmpegKitConfig.nativeFFmpegCancel(0);
} }
/** /**
@ -249,7 +250,7 @@ public class FFmpegKit {
* *
* <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 sessionId id of the session that will be stopped * @param sessionId id of the session that will be cancelled
*/ */
public static void cancel(final long sessionId) { public static void cancel(final long sessionId) {
FFmpegKitConfig.nativeFFmpegCancel(sessionId); FFmpegKitConfig.nativeFFmpegCancel(sessionId);

View File

@ -45,10 +45,6 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/** /**
* <p>This class is used to configure FFmpegKit library and tools coming with it. * <p>This class is used to configure FFmpegKit library and tools coming with it.
@ -85,11 +81,12 @@ public class FFmpegKitConfig {
* Generates ids for named ffmpeg kit pipes. * Generates ids for named ffmpeg kit pipes.
*/ */
private static final AtomicLong pipeIndexGenerator; private static final AtomicLong pipeIndexGenerator;
/* SESSION HISTORY VARIABLES */
private static int sessionHistorySize;
private static final Map<Long, Session> sessionHistoryMap; private static final Map<Long, Session> sessionHistoryMap;
private static final Queue<Session> sessionHistoryQueue; private static final Queue<Session> sessionHistoryQueue;
private static final Object sessionHistoryLock; private static final Object sessionHistoryLock;
private static int asyncConcurrencyLimit;
private static final SparseArray<ParcelFileDescriptor> pfdMap;
/** /**
* Executor service for async executions. * Executor service for async executions.
@ -99,9 +96,9 @@ public class FFmpegKitConfig {
private static StatisticsCallback globalStatisticsCallbackFunction; private static StatisticsCallback globalStatisticsCallbackFunction;
private static ExecuteCallback globalExecuteCallbackFunction; private static ExecuteCallback globalExecuteCallbackFunction;
private static Level activeLogLevel; private static Level activeLogLevel;
private static int asyncConcurrencyLimit;
/* SESSION HISTORY VARIABLES */ private static final SparseArray<ParcelFileDescriptor> pfdMap;
private static int sessionHistorySize; private static LogRedirectionStrategy logRedirectionStrategy;
static { static {
@ -192,6 +189,7 @@ public class FFmpegKitConfig {
sessionHistoryLock = new Object(); sessionHistoryLock = new Object();
pfdMap = new SparseArray<>(); pfdMap = new SparseArray<>();
logRedirectionStrategy = LogRedirectionStrategy.PRINT_LOGS_WHEN_NO_CALLBACKS_DEFINED;
enableRedirection(); enableRedirection();
} }
@ -236,6 +234,9 @@ public class FFmpegKitConfig {
final Level level = Level.from(levelValue); final Level level = Level.from(levelValue);
final String text = new String(logMessage); final String text = new String(logMessage);
final Log log = new Log(sessionId, level, text); final Log log = new Log(sessionId, level, text);
boolean globalCallbackDefined = false;
boolean sessionCallbackDefined = false;
LogRedirectionStrategy activeLogRedirectionStrategy = FFmpegKitConfig.logRedirectionStrategy;
// AV_LOG_STDERR logs are always redirected // AV_LOG_STDERR logs are always redirected
if ((activeLogLevel == Level.AV_LOG_QUIET && levelValue != Level.AV_LOG_STDERR.getValue()) || levelValue > activeLogLevel.getValue()) { if ((activeLogLevel == Level.AV_LOG_QUIET && levelValue != Level.AV_LOG_STDERR.getValue()) || levelValue > activeLogLevel.getValue()) {
@ -244,58 +245,88 @@ public class FFmpegKitConfig {
} }
final Session session = getSession(sessionId); final Session session = getSession(sessionId);
if (session != null && session.getLogCallback() != null) { if (session != null) {
try { activeLogRedirectionStrategy = session.getLogRedirectionStrategy();
// NOTIFY SESSION CALLBACK IF DEFINED session.addLog(log);
session.getLogCallback().apply(log);
} catch (final Exception e) { if (session.getLogCallback() != null) {
android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside session LogCallback block.%s", Exceptions.getStackTraceString(e))); sessionCallbackDefined = true;
try {
// NOTIFY SESSION CALLBACK IF DEFINED
session.getLogCallback().apply(log);
} catch (final Exception e) {
android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside session LogCallback block.%s", Exceptions.getStackTraceString(e)));
}
} }
} }
final LogCallback globalLogCallbackFunction = FFmpegKitConfig.globalLogCallbackFunction; final LogCallback globalLogCallbackFunction = FFmpegKitConfig.globalLogCallbackFunction;
if (globalLogCallbackFunction != null) { if (globalLogCallbackFunction != null) {
globalCallbackDefined = true;
try { try {
// NOTIFY GLOBAL CALLBACK IF DEFINED // NOTIFY GLOBAL CALLBACK IF DEFINED
globalLogCallbackFunction.apply(log); globalLogCallbackFunction.apply(log);
} catch (final Exception e) { } catch (final Exception e) {
android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside global LogCallback block.%s", Exceptions.getStackTraceString(e))); android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside global LogCallback block.%s", Exceptions.getStackTraceString(e)));
} }
} else { }
switch (level) {
case AV_LOG_QUIET: { // EXECUTE THE LOG STRATEGY
// PRINT NO OUTPUT switch (activeLogRedirectionStrategy) {
} case NEVER_PRINT_LOGS: {
break; return;
case AV_LOG_TRACE:
case AV_LOG_DEBUG: {
android.util.Log.d(TAG, text);
}
break;
case AV_LOG_STDERR:
case AV_LOG_VERBOSE: {
android.util.Log.v(TAG, text);
}
break;
case AV_LOG_INFO: {
android.util.Log.i(TAG, text);
}
break;
case AV_LOG_WARNING: {
android.util.Log.w(TAG, text);
}
break;
case AV_LOG_ERROR:
case AV_LOG_FATAL:
case AV_LOG_PANIC: {
android.util.Log.e(TAG, text);
}
break;
default: {
android.util.Log.v(TAG, text);
}
break;
} }
case PRINT_LOGS_WHEN_GLOBAL_CALLBACK_NOT_DEFINED: {
if (globalCallbackDefined) {
return;
}
}
break;
case PRINT_LOGS_WHEN_SESSION_CALLBACK_NOT_DEFINED: {
if (sessionCallbackDefined) {
return;
}
}
case PRINT_LOGS_WHEN_NO_CALLBACKS_DEFINED: {
if (globalCallbackDefined || sessionCallbackDefined) {
return;
}
}
}
// PRINT LOGS
switch (level) {
case AV_LOG_QUIET: {
// PRINT NO OUTPUT
}
break;
case AV_LOG_TRACE:
case AV_LOG_DEBUG: {
android.util.Log.d(TAG, text);
}
break;
case AV_LOG_INFO: {
android.util.Log.i(TAG, text);
}
break;
case AV_LOG_WARNING: {
android.util.Log.w(TAG, text);
}
break;
case AV_LOG_ERROR:
case AV_LOG_FATAL:
case AV_LOG_PANIC: {
android.util.Log.e(TAG, text);
}
break;
case AV_LOG_STDERR:
case AV_LOG_VERBOSE:
default: {
android.util.Log.v(TAG, text);
}
break;
} }
} }
@ -314,15 +345,19 @@ public class FFmpegKitConfig {
private static void statistics(final long sessionId, final int videoFrameNumber, private static void statistics(final long sessionId, final int videoFrameNumber,
final float videoFps, final float videoQuality, final long size, final float videoFps, final float videoQuality, final long size,
final int time, final double bitrate, final double speed) { final int time, final double bitrate, final double speed) {
final Statistics newStatistics = new Statistics(sessionId, videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed); final Statistics statistics = new Statistics(sessionId, videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed);
final Session session = getSession(sessionId); final Session session = getSession(sessionId);
if (session != null && session.getStatisticsCallback() != null) { if (session != null) {
try { session.addStatistics(statistics);
// NOTIFY SESSION CALLBACK IF DEFINED
session.getStatisticsCallback().apply(newStatistics); if (session.getStatisticsCallback() != null) {
} catch (final Exception e) { try {
android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside session StatisticsCallback block.%s", Exceptions.getStackTraceString(e))); // NOTIFY SESSION CALLBACK IF DEFINED
session.getStatisticsCallback().apply(statistics);
} catch (final Exception e) {
android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside session StatisticsCallback block.%s", Exceptions.getStackTraceString(e)));
}
} }
} }
@ -330,7 +365,7 @@ public class FFmpegKitConfig {
if (globalStatisticsCallbackFunction != null) { if (globalStatisticsCallbackFunction != null) {
try { try {
// NOTIFY GLOBAL CALLBACK IF DEFINED // NOTIFY GLOBAL CALLBACK IF DEFINED
globalStatisticsCallbackFunction.apply(newStatistics); globalStatisticsCallbackFunction.apply(statistics);
} catch (final Exception e) { } catch (final Exception e) {
android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside global StatisticsCallback block.%s", Exceptions.getStackTraceString(e))); android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside global StatisticsCallback block.%s", Exceptions.getStackTraceString(e)));
} }
@ -589,7 +624,7 @@ public class FFmpegKitConfig {
if (lastSession != null) { if (lastSession != null) {
// REPLACING CH(13) WITH CH(10) // REPLACING CH(13) WITH CH(10)
return lastSession.getLogsAsString().replace('\r', '\n'); return lastSession.getAllLogsAsString().replace('\r', '\n');
} else { } else {
return ""; return "";
} }
@ -687,8 +722,9 @@ public class FFmpegKitConfig {
* <p>Synchronously executes the media information session provided. * <p>Synchronously executes the media information session provided.
* *
* @param mediaInformationSession media information session which includes command options/arguments * @param mediaInformationSession media information session which includes command options/arguments
* @param waitTimeout max time to wait until media information is transmitted
*/ */
static void getMediaInformationExecute(final MediaInformationSession mediaInformationSession) { static void getMediaInformationExecute(final MediaInformationSession mediaInformationSession, final int waitTimeout) {
addSession(mediaInformationSession); addSession(mediaInformationSession);
mediaInformationSession.startRunning(); mediaInformationSession.startRunning();
@ -696,7 +732,7 @@ public class FFmpegKitConfig {
final int returnCode = nativeFFprobeExecute(mediaInformationSession.getSessionId(), mediaInformationSession.getArguments()); final int returnCode = nativeFFprobeExecute(mediaInformationSession.getSessionId(), mediaInformationSession.getArguments());
mediaInformationSession.complete(returnCode); mediaInformationSession.complete(returnCode);
if (returnCode == ReturnCode.SUCCESS) { if (returnCode == ReturnCode.SUCCESS) {
MediaInformation mediaInformation = MediaInformationParser.fromWithError(mediaInformationSession.getLogsAsString()); MediaInformation mediaInformation = MediaInformationParser.fromWithError(mediaInformationSession.getAllLogsAsString(waitTimeout));
mediaInformationSession.setMediaInformation(mediaInformation); mediaInformationSession.setMediaInformation(mediaInformation);
} }
} catch (final Exception e) { } catch (final Exception e) {
@ -731,9 +767,10 @@ public class FFmpegKitConfig {
* <p>Asynchronously executes the media information session provided. * <p>Asynchronously executes the media information session provided.
* *
* @param mediaInformationSession media information session which includes command options/arguments * @param mediaInformationSession media information session which includes command options/arguments
* @param waitTimeout max time to wait until media information is transmitted
*/ */
static void asyncGetMediaInformationExecute(final MediaInformationSession mediaInformationSession) { static void asyncGetMediaInformationExecute(final MediaInformationSession mediaInformationSession, final Integer waitTimeout) {
AsyncGetMediaInformationTask asyncGetMediaInformationTask = new AsyncGetMediaInformationTask(mediaInformationSession); AsyncGetMediaInformationTask asyncGetMediaInformationTask = new AsyncGetMediaInformationTask(mediaInformationSession, waitTimeout);
Future<?> future = asyncExecutorService.submit(asyncGetMediaInformationTask); Future<?> future = asyncExecutorService.submit(asyncGetMediaInformationTask);
mediaInformationSession.setFuture(future); mediaInformationSession.setFuture(future);
} }
@ -804,7 +841,7 @@ public class FFmpegKitConfig {
* *
* @return global execute callback function * @return global execute callback function
*/ */
public static ExecuteCallback getGlobalExecuteCallbackFunction() { static ExecuteCallback getGlobalExecuteCallbackFunction() {
return globalExecuteCallbackFunction; return globalExecuteCallbackFunction;
} }
@ -833,9 +870,16 @@ public class FFmpegKitConfig {
* <p>Converts the given Structured Access Framework Uri (<code>"content:…"</code>) into an * <p>Converts the given Structured Access Framework Uri (<code>"content:…"</code>) into an
* input/output url that can be used in FFmpegKit and FFprobeKit. * input/output url that can be used in FFmpegKit and FFprobeKit.
* *
* <p>Requires API Level >= 19. On older API levels it returns an empty url.
*
* @return input/output url that can be passed to FFmpegKit or FFprobeKit * @return input/output url that can be passed to FFmpegKit or FFprobeKit
*/ */
private static String getSafParameter(final Context context, final Uri uri, final String openMode) { private static String getSafParameter(final Context context, final Uri uri, final String openMode) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
android.util.Log.i(TAG, String.format("getSafParameter is not supported on API Level %d", Build.VERSION.SDK_INT));
return "";
}
String displayName = "unknown"; String displayName = "unknown";
try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) { try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
@ -868,6 +912,8 @@ public class FFmpegKitConfig {
* <p>Converts the given Structured Access Framework Uri (<code>"content:…"</code>) into an * <p>Converts the given Structured Access Framework Uri (<code>"content:…"</code>) into an
* input url that can be used in FFmpegKit and FFprobeKit. * input url that can be used in FFmpegKit and FFprobeKit.
* *
* <p>Requires API Level >= 19. On older API levels it returns an empty url.
*
* @return input url that can be passed to FFmpegKit or FFprobeKit * @return input url that can be passed to FFmpegKit or FFprobeKit
*/ */
public static String getSafParameterForRead(final Context context, final Uri uri) { public static String getSafParameterForRead(final Context context, final Uri uri) {
@ -878,6 +924,8 @@ public class FFmpegKitConfig {
* <p>Converts the given Structured Access Framework Uri (<code>"content:…"</code>) into an * <p>Converts the given Structured Access Framework Uri (<code>"content:…"</code>) into an
* output url that can be used in FFmpegKit and FFprobeKit. * output url that can be used in FFmpegKit and FFprobeKit.
* *
* <p>Requires API Level >= 19. On older API levels it returns an empty url.
*
* @return output url that can be passed to FFmpegKit or FFprobeKit * @return output url that can be passed to FFmpegKit or FFprobeKit
*/ */
public static String getSafParameterForWrite(final Context context, final Uri uri) { public static String getSafParameterForWrite(final Context context, final Uri uri) {
@ -913,9 +961,16 @@ public class FFmpegKitConfig {
/** /**
* Sets the session history size. * Sets the session history size.
* *
* @param sessionHistorySize new session history size * @param sessionHistorySize new session history size, should be smaller than 1000
*/ */
public static void setSessionHistorySize(int sessionHistorySize) { public static void setSessionHistorySize(final int sessionHistorySize) {
if (sessionHistorySize >= 1000) {
/*
* THERE IS A HARD LIMIT ON THE NATIVE SIDE. HISTORY SIZE MUST BE SMALLER THAN 1000
*/
throw new IllegalArgumentException("Session history size must not exceed the hard limit!");
}
FFmpegKitConfig.sessionHistorySize = sessionHistorySize; FFmpegKitConfig.sessionHistorySize = sessionHistorySize;
} }
@ -978,27 +1033,17 @@ public class FFmpegKitConfig {
* @return all FFmpeg sessions in the session history * @return all FFmpeg sessions in the session history
*/ */
static List<FFmpegSession> getFFmpegSessions() { static List<FFmpegSession> getFFmpegSessions() {
final LinkedList<FFmpegSession> list = new LinkedList<>();
synchronized (sessionHistoryLock) { synchronized (sessionHistoryLock) {
return sessionHistoryQueue.stream().filter(new Predicate<Session>() { for (Session session : sessionHistoryQueue) {
if (session.isFFmpeg()) {
@Override list.add((FFmpegSession) session);
public boolean test(final Session session) {
return (session.isFFmpeg());
} }
}).map(new Function<Session, FFmpegSession>() { }
@Override
public FFmpegSession apply(final Session session) {
return (FFmpegSession) session;
}
}).collect(Collectors.toCollection(new Supplier<List<FFmpegSession>>() {
@Override
public List<FFmpegSession> get() {
return new LinkedList<>();
}
}));
} }
return list;
} }
/** /**
@ -1007,27 +1052,17 @@ public class FFmpegKitConfig {
* @return all FFprobe sessions in the session history * @return all FFprobe sessions in the session history
*/ */
static List<FFprobeSession> getFFprobeSessions() { static List<FFprobeSession> getFFprobeSessions() {
final LinkedList<FFprobeSession> list = new LinkedList<>();
synchronized (sessionHistoryLock) { synchronized (sessionHistoryLock) {
return sessionHistoryQueue.stream().filter(new Predicate<Session>() { for (Session session : sessionHistoryQueue) {
if (session.isFFprobe()) {
@Override list.add((FFprobeSession) session);
public boolean test(final Session session) {
return (session.isFFprobe());
} }
}).map(new Function<Session, FFprobeSession>() { }
@Override
public FFprobeSession apply(final Session session) {
return (FFprobeSession) session;
}
}).collect(Collectors.toCollection(new Supplier<List<FFprobeSession>>() {
@Override
public List<FFprobeSession> get() {
return new LinkedList<>();
}
}));
} }
return list;
} }
/** /**
@ -1036,21 +1071,35 @@ public class FFmpegKitConfig {
* @return sessions that have the given state from the session history * @return sessions that have the given state from the session history
*/ */
public static List<Session> getSessionsByState(final SessionState state) { public static List<Session> getSessionsByState(final SessionState state) {
final LinkedList<Session> list = new LinkedList<>();
synchronized (sessionHistoryLock) { synchronized (sessionHistoryLock) {
return sessionHistoryQueue.stream().filter(new Predicate<Session>() { for (Session session : sessionHistoryQueue) {
if (session.getState() == state) {
@Override list.add(session);
public boolean test(final Session session) {
return (session.getState() == state);
} }
}).collect(Collectors.toCollection(new Supplier<List<Session>>() { }
@Override
public List<Session> get() {
return new LinkedList<>();
}
}));
} }
return list;
}
/**
* Returns the active log redirection strategy.
*
* @return log redirection strategy
*/
public static LogRedirectionStrategy getLogRedirectionStrategy() {
return logRedirectionStrategy;
}
/**
* <p>Sets the log redirection strategy
*
* @param logRedirectionStrategy new log redirection strategy
*/
public static void setLogRedirectionStrategy(final LogRedirectionStrategy logRedirectionStrategy) {
FFmpegKitConfig.logRedirectionStrategy = logRedirectionStrategy;
} }
/** /**
@ -1096,7 +1145,9 @@ public class FFmpegKitConfig {
* *
* @param sessionId id of the session * @param sessionId id of the session
* @param arguments FFmpeg command options/arguments as string array * @param arguments FFmpeg command options/arguments as string array
* @return zero on successful execution, 255 on user cancel and non-zero on error * @return {@link ReturnCode#SUCCESS} on successful execution and {@link ReturnCode#CANCEL} on
* user cancel. Other non-zero values are returned on error. Use {@link ReturnCode} class to
* handle the value
*/ */
private native static int nativeFFmpegExecute(final long sessionId, final String[] arguments); private native static int nativeFFmpegExecute(final long sessionId, final String[] arguments);
@ -1113,10 +1164,22 @@ public class FFmpegKitConfig {
* *
* @param sessionId id of the session * @param sessionId id of the session
* @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 {@link ReturnCode#SUCCESS} on successful execution and {@link ReturnCode#CANCEL} on
* user cancel. Other non-zero values are returned on error. Use {@link ReturnCode} class to
* handle the value
*/ */
native static int nativeFFprobeExecute(final long sessionId, final String[] arguments); native static int nativeFFprobeExecute(final long sessionId, final String[] arguments);
/**
* <p>Returns the number of native messages which are not transmitted to the Java callbacks for
* this session natively.
*
* @param sessionId id of the session
* @return number of native messages which are not transmitted to the Java callbacks for
* this session natively
*/
native static int messagesInTransmit(final long sessionId);
/** /**
* <p>Creates a new named pipe to use in <code>FFmpeg</code> operations natively. * <p>Creates a new named pipe to use in <code>FFmpeg</code> operations natively.
* *

View File

@ -21,7 +21,6 @@ package com.arthenica.ffmpegkit;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.Stream;
/** /**
* <p>An FFmpeg execute session. * <p>An FFmpeg execute session.
@ -33,19 +32,33 @@ public class FFmpegSession extends AbstractSession implements Session {
final ExecuteCallback executeCallback, final ExecuteCallback executeCallback,
final LogCallback logCallback, final LogCallback logCallback,
final StatisticsCallback statisticsCallback) { final StatisticsCallback statisticsCallback) {
super(arguments, executeCallback, logCallback, statisticsCallback); super(arguments, executeCallback, logCallback, statisticsCallback, FFmpegKitConfig.getLogRedirectionStrategy());
this.statistics = new ConcurrentLinkedQueue<>(); this.statistics = new ConcurrentLinkedQueue<>();
} }
@Override
public Queue<Statistics> getAllStatistics(final int waitTimeout) {
waitForCallbackMessagesInTransmit(waitTimeout);
if (thereAreCallbackMessagesInTransmit()) {
android.util.Log.i(FFmpegKitConfig.TAG, String.format("getAllStatistics was asked to return all statistics but there are still statistics being transmitted for session id %d.", sessionId));
}
return getStatistics();
}
@Override
public Queue<Statistics> getAllStatistics() {
return getAllStatistics(DEFAULT_TIMEOUT_FOR_CALLBACK_MESSAGES_IN_TRANSMIT);
}
@Override
public Queue<Statistics> getStatistics() { public Queue<Statistics> getStatistics() {
return statistics; return statistics;
} }
public Stream<Statistics> getStatisticsAsStream() { @Override
return statistics.stream();
}
public void addStatistics(final Statistics statistics) { public void addStatistics(final Statistics statistics) {
this.statistics.add(statistics); this.statistics.add(statistics);
} }

View File

@ -19,6 +19,7 @@
package com.arthenica.ffmpegkit; package com.arthenica.ffmpegkit;
import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
/** /**
@ -183,6 +184,15 @@ public class FFprobeKit {
return session; return session;
} }
/**
* <p>Lists all FFprobe sessions in the session history
*
* @return all FFprobe sessions in the session history
*/
public static List<FFprobeSession> listSessions() {
return FFmpegKitConfig.getFFprobeSessions();
}
/** /**
* <p>Returns media information for the given path. * <p>Returns media information for the given path.
* *
@ -190,7 +200,18 @@ public class FFprobeKit {
* @return media information session created for this execution * @return media information session created for this execution
*/ */
public static MediaInformationSession getMediaInformation(final String path) { 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); return getMediaInformationFromCommandArguments(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path}, null, null, null, AbstractSession.DEFAULT_TIMEOUT_FOR_CALLBACK_MESSAGES_IN_TRANSMIT);
}
/**
* <p>Returns media information for the given path.
*
* @param path path or uri of a media file
* @param waitTimeout max time to wait until media information is transmitted
* @return media information session created for this execution
*/
public static MediaInformationSession getMediaInformation(final String path, final Integer waitTimeout) {
return getMediaInformationFromCommandArguments(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path}, null, null, null, waitTimeout);
} }
/** /**
@ -204,7 +225,7 @@ public class FFprobeKit {
final ExecuteCallback executeCallback) { 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); 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); FFmpegKitConfig.asyncGetMediaInformationExecute(session, AbstractSession.DEFAULT_TIMEOUT_FOR_CALLBACK_MESSAGES_IN_TRANSMIT);
return session; return session;
} }
@ -216,15 +237,17 @@ public class FFprobeKit {
* @param executeCallback callback that will be notified when execution is completed * @param executeCallback callback that will be notified when execution is completed
* @param logCallback callback that will receive log entries * @param logCallback callback that will receive log entries
* @param statisticsCallback callback that will receive statistics * @param statisticsCallback callback that will receive statistics
* @param waitTimeout max time to wait until media information is transmitted
* @return media information session created for this execution * @return media information session created for this execution
*/ */
public static MediaInformationSession getMediaInformationAsync(final String path, public static MediaInformationSession getMediaInformationAsync(final String path,
final ExecuteCallback executeCallback, final ExecuteCallback executeCallback,
final LogCallback logCallback, final LogCallback logCallback,
final StatisticsCallback statisticsCallback) { final StatisticsCallback statisticsCallback,
final Integer waitTimeout) {
final MediaInformationSession session = new MediaInformationSession(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path}, executeCallback, logCallback, 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); FFmpegKitConfig.asyncGetMediaInformationExecute(session, waitTimeout);
return session; return session;
} }
@ -256,16 +279,18 @@ public class FFprobeKit {
* @param logCallback callback that will receive log entries * @param logCallback callback that will receive log entries
* @param statisticsCallback callback that will receive statistics * @param statisticsCallback callback that will receive statistics
* @param executor executor that will be used to run this asynchronous operation * @param executor executor that will be used to run this asynchronous operation
* @param waitTimeout max time to wait until media information is transmitted
* @return media information session created for this execution * @return media information session created for this execution
*/ */
public static MediaInformationSession getMediaInformationAsync(final String path, public static MediaInformationSession getMediaInformationAsync(final String path,
final ExecuteCallback executeCallback, final ExecuteCallback executeCallback,
final LogCallback logCallback, final LogCallback logCallback,
final StatisticsCallback statisticsCallback, final StatisticsCallback statisticsCallback,
final Executor executor) { final Executor executor,
final Integer waitTimeout) {
final MediaInformationSession session = new MediaInformationSession(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path}, executeCallback, logCallback, statisticsCallback); 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); AsyncGetMediaInformationTask asyncGetMediaInformationTask = new AsyncGetMediaInformationTask(session, waitTimeout);
executor.execute(asyncGetMediaInformationTask); executor.execute(asyncGetMediaInformationTask);
return session; return session;
@ -278,7 +303,7 @@ public class FFprobeKit {
* @return media information session created for this execution * @return media information session created for this execution
*/ */
public static MediaInformationSession getMediaInformationFromCommand(final String command) { public static MediaInformationSession getMediaInformationFromCommand(final String command) {
return getMediaInformationFromCommandArguments(FFmpegKit.parseArguments(command), null, null, null); return getMediaInformationFromCommandArguments(FFmpegKit.parseArguments(command), null, null, null, AbstractSession.DEFAULT_TIMEOUT_FOR_CALLBACK_MESSAGES_IN_TRANSMIT);
} }
@ -289,22 +314,25 @@ public class FFprobeKit {
* @param executeCallback callback that will be notified when execution is completed * @param executeCallback callback that will be notified when execution is completed
* @param logCallback callback that will receive log entries * @param logCallback callback that will receive log entries
* @param statisticsCallback callback that will receive statistics * @param statisticsCallback callback that will receive statistics
* @param waitTimeout max time to wait until media information is transmitted
* @return media information session created for this execution * @return media information session created for this execution
*/ */
public static MediaInformationSession getMediaInformationFromCommand(final String command, public static MediaInformationSession getMediaInformationFromCommand(final String command,
final ExecuteCallback executeCallback, final ExecuteCallback executeCallback,
final LogCallback logCallback, final LogCallback logCallback,
final StatisticsCallback statisticsCallback) { final StatisticsCallback statisticsCallback,
return getMediaInformationFromCommandArguments(FFmpegKit.parseArguments(command), executeCallback, logCallback, statisticsCallback); final Integer waitTimeout) {
return getMediaInformationFromCommandArguments(FFmpegKit.parseArguments(command), executeCallback, logCallback, statisticsCallback, waitTimeout);
} }
private static MediaInformationSession getMediaInformationFromCommandArguments(final String[] arguments, private static MediaInformationSession getMediaInformationFromCommandArguments(final String[] arguments,
final ExecuteCallback executeCallback, final ExecuteCallback executeCallback,
final LogCallback logCallback, final LogCallback logCallback,
final StatisticsCallback statisticsCallback) { final StatisticsCallback statisticsCallback,
final Integer waitTimeout) {
final MediaInformationSession session = new MediaInformationSession(arguments, executeCallback, logCallback, statisticsCallback); final MediaInformationSession session = new MediaInformationSession(arguments, executeCallback, logCallback, statisticsCallback);
FFmpegKitConfig.getMediaInformationExecute(session); FFmpegKitConfig.getMediaInformationExecute(session, waitTimeout);
return session; return session;
} }

View File

@ -22,7 +22,6 @@ package com.arthenica.ffmpegkit;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Queue; import java.util.Queue;
import java.util.stream.Stream;
/** /**
* <p>An FFprobe execute session. * <p>An FFprobe execute session.
@ -33,7 +32,25 @@ public class FFprobeSession extends AbstractSession implements Session {
final ExecuteCallback executeCallback, final ExecuteCallback executeCallback,
final LogCallback logCallback, final LogCallback logCallback,
final StatisticsCallback statisticsCallback) { final StatisticsCallback statisticsCallback) {
super(arguments, executeCallback, logCallback, statisticsCallback); this(arguments, executeCallback, logCallback, statisticsCallback, FFmpegKitConfig.getLogRedirectionStrategy());
}
FFprobeSession(final String[] arguments,
final ExecuteCallback executeCallback,
final LogCallback logCallback,
final StatisticsCallback statisticsCallback,
final LogRedirectionStrategy logRedirectionStrategy) {
super(arguments, executeCallback, logCallback, statisticsCallback, logRedirectionStrategy);
}
@Override
public Queue<Statistics> getAllStatistics(final int waitTimeout) {
return new LinkedList<>();
}
@Override
public Queue<Statistics> getAllStatistics() {
return new LinkedList<>();
} }
@Override @Override
@ -41,11 +58,6 @@ public class FFprobeSession extends AbstractSession implements Session {
return new LinkedList<>(); return new LinkedList<>();
} }
@Override
public Stream<Statistics> getStatisticsAsStream() {
return new LinkedList<Statistics>().stream();
}
@Override @Override
public void addStatistics(final Statistics statistics) { public void addStatistics(final Statistics statistics) {
/* /*

View File

@ -0,0 +1,28 @@
/*
* 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;
public enum LogRedirectionStrategy {
ALWAYS_PRINT_LOGS,
PRINT_LOGS_WHEN_NO_CALLBACKS_DEFINED,
PRINT_LOGS_WHEN_GLOBAL_CALLBACK_NOT_DEFINED,
PRINT_LOGS_WHEN_SESSION_CALLBACK_NOT_DEFINED,
NEVER_PRINT_LOGS
}

View File

@ -30,7 +30,7 @@ public class MediaInformationSession extends FFprobeSession implements Session {
final ExecuteCallback executeCallback, final ExecuteCallback executeCallback,
final LogCallback logCallback, final LogCallback logCallback,
final StatisticsCallback statisticsCallback) { final StatisticsCallback statisticsCallback) {
super(arguments, executeCallback, logCallback, statisticsCallback); super(arguments, executeCallback, logCallback, statisticsCallback, LogRedirectionStrategy.NEVER_PRINT_LOGS);
} }
public MediaInformation getMediaInformation() { public MediaInformation getMediaInformation() {

View File

@ -22,7 +22,6 @@ package com.arthenica.ffmpegkit;
import java.util.Date; import java.util.Date;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.stream.Stream;
/** /**
* <p>Interface for ffmpeg and ffprobe execute sessions. * <p>Interface for ffmpeg and ffprobe execute sessions.
@ -49,15 +48,23 @@ public interface Session {
String getCommand(); String getCommand();
Queue<Log> getAllLogs(final int waitTimeout);
Queue<Log> getAllLogs();
Queue<Log> getLogs(); Queue<Log> getLogs();
Stream<Log> getLogsAsStream(); String getAllLogsAsString(final int waitTimeout);
String getAllLogsAsString();
String getLogsAsString(); String getLogsAsString();
Queue<Statistics> getStatistics(); Queue<Statistics> getAllStatistics(final int waitTimeout);
Stream<Statistics> getStatisticsAsStream(); Queue<Statistics> getAllStatistics();
Queue<Statistics> getStatistics();
SessionState getState(); SessionState getState();
@ -65,6 +72,10 @@ public interface Session {
String getFailStackTrace(); String getFailStackTrace();
LogRedirectionStrategy getLogRedirectionStrategy();
boolean thereAreCallbackMessagesInTransmit();
void addLog(final Log log); void addLog(final Log log);
void addStatistics(final Statistics statistics); void addStatistics(final Statistics statistics);

View File

@ -28,7 +28,7 @@ public class AbstractSessionTest {
@Test @Test
public void getLogsAsStringTest() { public void getLogsAsStringTest() {
final FFprobeSession ffprobeSession = new FFprobeSession(TEST_ARGUMENTS, null, null, null); final FFprobeSession ffprobeSession = new FFprobeSession(TEST_ARGUMENTS, null, null, null, LogRedirectionStrategy.ALWAYS_PRINT_LOGS);
String logMessage1 = "i am log one"; String logMessage1 = "i am log one";
String logMessage2 = "i am log two"; String logMessage2 = "i am log two";