/*
* Copyright (c) 2018-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
Configuration class of Enables log and statistics redirection.
*
* When redirection is enabled FFmpeg/FFprobe logs are redirected to Logcat and sessions
* collect log and statistics entries for the executions. It is possible to define global or
* session specific log/statistics callbacks as well.
*
* Note that redirection is enabled by default. If you do not want to use its functionality
* please use {@link #disableRedirection()} to disable it.
*/
public static void enableRedirection() {
enableNativeRedirection();
}
/**
* Disables log and statistics redirection.
*
* When redirection is disabled logs are printed to stderr, all logs and statistics
* callbacks are disabled and Log redirection method called by the native library.
*
* @param sessionId id of the session that generated this log, 0 for logs that do not belong
* to a specific session
* @param levelValue log level as defined in {@link Level}
* @param logMessage redirected log message data
*/
private static void log(final long sessionId, final int levelValue, final byte[] logMessage) {
final Level level = Level.from(levelValue);
final String text = new String(logMessage);
final Log log = new Log(sessionId, level, text);
boolean globalCallbackDefined = false;
boolean sessionCallbackDefined = false;
LogRedirectionStrategy activeLogRedirectionStrategy = globalLogRedirectionStrategy;
// AV_LOG_STDERR logs are always redirected
if ((activeLogLevel == Level.AV_LOG_QUIET && levelValue != Level.AV_LOG_STDERR.getValue()) || levelValue > activeLogLevel.getValue()) {
// LOG NEITHER PRINTED NOR FORWARDED
return;
}
final Session session = getSession(sessionId);
if (session != null) {
activeLogRedirectionStrategy = session.getLogRedirectionStrategy();
session.addLog(log);
if (session.getLogCallback() != null) {
sessionCallbackDefined = true;
try {
// NOTIFY SESSION CALLBACK 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;
if (globalLogCallbackFunction != null) {
globalCallbackDefined = true;
try {
// NOTIFY GLOBAL CALLBACK DEFINED
globalLogCallbackFunction.apply(log);
} catch (final Exception e) {
android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside global LogCallback block.%s", Exceptions.getStackTraceString(e)));
}
}
// EXECUTE THE LOG STRATEGY
switch (activeLogRedirectionStrategy) {
case NEVER_PRINT_LOGS: {
return;
}
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;
}
}
case ALWAYS_PRINT_LOGS: {
}
}
// 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;
}
}
/**
* Statistics redirection method called by the native library.
*
* @param sessionId id of the session that generated this statistics, 0 by default
* @param videoFrameNumber frame number for videos
* @param videoFps frames per second value for videos
* @param videoQuality quality of the video stream
* @param size size in bytes
* @param time processed duration in milliseconds
* @param bitrate output bit rate in kbits/s
* @param speed processing speed = processed duration / operation duration
*/
private static void 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) {
final Statistics statistics = new Statistics(sessionId, videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed);
final Session session = getSession(sessionId);
if (session != null && session.isFFmpeg()) {
FFmpegSession ffmpegSession = (FFmpegSession) session;
ffmpegSession.addStatistics(statistics);
if (ffmpegSession.getStatisticsCallback() != null) {
try {
// NOTIFY SESSION CALLBACK IF DEFINED
ffmpegSession.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)));
}
}
}
final StatisticsCallback globalStatisticsCallbackFunction = FFmpegKitConfig.globalStatisticsCallbackFunction;
if (globalStatisticsCallbackFunction != null) {
try {
// NOTIFY GLOBAL CALLBACK IF DEFINED
globalStatisticsCallbackFunction.apply(statistics);
} catch (final Exception e) {
android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside global StatisticsCallback block.%s", Exceptions.getStackTraceString(e)));
}
}
}
/**
* Sets and overrides Registers the fonts inside the given path, so they become available to use in FFmpeg
* filters.
*
* Note that you need to build Registers the fonts inside the given list of font directories, so they become available
* to use in FFmpeg filters.
*
* Note that you need to build Creates a new named pipe to use in Please note that creator is responsible of closing created pipes.
*
* @param context application context
* @return the full path of the named pipe
*/
public static String registerNewFFmpegPipe(final Context context) {
// PIPES ARE CREATED UNDER THE PIPES DIRECTORY
final File cacheDir = context.getCacheDir();
final File pipesDir = new File(cacheDir, "pipes");
if (!pipesDir.exists()) {
final boolean pipesDirCreated = pipesDir.mkdirs();
if (!pipesDirCreated) {
android.util.Log.e(TAG, String.format("Failed to create pipes directory: %s.", pipesDir.getAbsolutePath()));
return null;
}
}
final String newFFmpegPipePath = MessageFormat.format("{0}{1}{2}{3}", pipesDir, File.separator, FFMPEG_KIT_NAMED_PIPE_PREFIX, pipeIndexGenerator.getAndIncrement());
// FIRST CLOSE OLD PIPES WITH THE SAME NAME
closeFFmpegPipe(newFFmpegPipePath);
int rc = registerNewNativeFFmpegPipe(newFFmpegPipePath);
if (rc == 0) {
return newFFmpegPipePath;
} else {
android.util.Log.e(TAG, String.format("Failed to register new FFmpeg pipe %s. Operation failed with rc=%d.", newFFmpegPipePath, rc));
return null;
}
}
/**
* Closes a previously created Note that this method requires API Level ≥ 24. On older API levels it returns an empty
* list.
*
* @param context application context
* @return list of camera ids supported or an empty list if no supported cameras are found
*/
public static List Returns the version of FFmpeg bundled within Returns FFmpegKit library version.
*
* @return FFmpegKit version
*/
public static String getVersion() {
if (isLTSBuild()) {
return String.format("%s-lts", getNativeVersion());
} else {
return getNativeVersion();
}
}
/**
* Returns whether FFmpegKit release is a Long Term Release or not.
*
* @return true/yes or false/no
*/
public static boolean isLTSBuild() {
return AbiDetect.isNativeLTSBuild();
}
/**
* Returns FFmpegKit library build date.
*
* @return FFmpegKit library build date
*/
public static String getBuildDate() {
return getNativeBuildDate();
}
/**
* Prints the given string to Logcat using the given priority. If string provided is bigger
* than the Logcat buffer, the string is printed in multiple lines.
*
* @param logPriority one of {@link android.util.Log#VERBOSE},
* {@link android.util.Log#DEBUG},
* {@link android.util.Log#INFO},
* {@link android.util.Log#WARN},
* {@link android.util.Log#ERROR},
* {@link android.util.Log#ASSERT}
* @param string string to be printed
*/
public static void printToLogcat(final int logPriority, final String string) {
final int LOGGER_ENTRY_MAX_LEN = 4 * 1000;
String remainingString = string;
do {
if (remainingString.length() <= LOGGER_ENTRY_MAX_LEN) {
android.util.Log.println(logPriority, FFmpegKitConfig.TAG, remainingString);
remainingString = "";
} else {
final int index = remainingString.substring(0, LOGGER_ENTRY_MAX_LEN).lastIndexOf('\n');
if (index < 0) {
android.util.Log.println(logPriority, FFmpegKitConfig.TAG, remainingString.substring(0, LOGGER_ENTRY_MAX_LEN));
remainingString = remainingString.substring(LOGGER_ENTRY_MAX_LEN);
} else {
android.util.Log.println(logPriority, FFmpegKitConfig.TAG, remainingString.substring(0, index));
remainingString = remainingString.substring(index);
}
}
} while (remainingString.length() > 0);
}
/**
* Sets an environment variable.
*
* @param variableName environment variable name
* @param variableValue environment variable value
* @return zero on success, non-zero on error
*/
public static int setEnvironmentVariable(final String variableName, final String variableValue) {
return setNativeEnvironmentVariable(variableName, variableValue);
}
/**
* Registers a new ignored signal. Ignored signals are not handled by Synchronously executes the FFmpeg session provided.
*
* @param ffmpegSession FFmpeg session which includes command options/arguments
*/
public static void ffmpegExecute(final FFmpegSession ffmpegSession) {
ffmpegSession.startRunning();
try {
final int returnCode = nativeFFmpegExecute(ffmpegSession.getSessionId(), ffmpegSession.getArguments());
ffmpegSession.complete(new ReturnCode(returnCode));
} catch (final Exception e) {
ffmpegSession.fail(e);
android.util.Log.w(FFmpegKitConfig.TAG, String.format("FFmpeg execute failed: %s.%s", FFmpegKitConfig.argumentsToString(ffmpegSession.getArguments()), Exceptions.getStackTraceString(e)));
}
}
/**
* Synchronously executes the FFprobe session provided.
*
* @param ffprobeSession FFprobe session which includes command options/arguments
*/
public static void ffprobeExecute(final FFprobeSession ffprobeSession) {
ffprobeSession.startRunning();
try {
final int returnCode = nativeFFprobeExecute(ffprobeSession.getSessionId(), ffprobeSession.getArguments());
ffprobeSession.complete(new ReturnCode(returnCode));
} catch (final Exception e) {
ffprobeSession.fail(e);
android.util.Log.w(FFmpegKitConfig.TAG, String.format("FFprobe execute failed: %s.%s", FFmpegKitConfig.argumentsToString(ffprobeSession.getArguments()), Exceptions.getStackTraceString(e)));
}
}
/**
* Synchronously executes the media information session provided.
*
* @param mediaInformationSession media information session which includes command options/arguments
* @param waitTimeout max time to wait until media information is transmitted
*/
public static void getMediaInformationExecute(final MediaInformationSession mediaInformationSession, final int waitTimeout) {
mediaInformationSession.startRunning();
try {
final int returnCodeValue = nativeFFprobeExecute(mediaInformationSession.getSessionId(), mediaInformationSession.getArguments());
final ReturnCode returnCode = new ReturnCode(returnCodeValue);
mediaInformationSession.complete(returnCode);
if (returnCode.isSuccess()) {
MediaInformation mediaInformation = MediaInformationJsonParser.fromWithError(mediaInformationSession.getAllLogsAsString(waitTimeout));
mediaInformationSession.setMediaInformation(mediaInformation);
}
} catch (final Exception e) {
mediaInformationSession.fail(e);
android.util.Log.w(FFmpegKitConfig.TAG, String.format("Get media information execute failed: %s.%s", FFmpegKitConfig.argumentsToString(mediaInformationSession.getArguments()), Exceptions.getStackTraceString(e)));
}
}
/**
* Asynchronously executes the FFmpeg session provided.
*
* @param ffmpegSession FFmpeg session which includes command options/arguments
*/
public static void asyncFFmpegExecute(final FFmpegSession ffmpegSession) {
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(ffmpegSession);
Future> future = asyncExecutorService.submit(asyncFFmpegExecuteTask);
ffmpegSession.setFuture(future);
}
/**
* Asynchronously executes the FFmpeg session provided.
*
* @param ffmpegSession FFmpeg session which includes command options/arguments
* @param executorService executor service that will be used to run this asynchronous operation
*/
public static void asyncFFmpegExecute(final FFmpegSession ffmpegSession, final ExecutorService executorService) {
AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(ffmpegSession);
Future> future = executorService.submit(asyncFFmpegExecuteTask);
ffmpegSession.setFuture(future);
}
/**
* Asynchronously executes the FFprobe session provided.
*
* @param ffprobeSession FFprobe session which includes command options/arguments
*/
public static void asyncFFprobeExecute(final FFprobeSession ffprobeSession) {
AsyncFFprobeExecuteTask asyncFFmpegExecuteTask = new AsyncFFprobeExecuteTask(ffprobeSession);
Future> future = asyncExecutorService.submit(asyncFFmpegExecuteTask);
ffprobeSession.setFuture(future);
}
/**
* Asynchronously executes the FFprobe session provided.
*
* @param ffprobeSession FFprobe session which includes command options/arguments
* @param executorService executor service that will be used to run this asynchronous operation
*/
public static void asyncFFprobeExecute(final FFprobeSession ffprobeSession, final ExecutorService executorService) {
AsyncFFprobeExecuteTask asyncFFmpegExecuteTask = new AsyncFFprobeExecuteTask(ffprobeSession);
Future> future = executorService.submit(asyncFFmpegExecuteTask);
ffprobeSession.setFuture(future);
}
/**
* Asynchronously executes the media information session provided.
*
* @param mediaInformationSession media information session which includes command options/arguments
* @param waitTimeout max time to wait until media information is transmitted
*/
public static void asyncGetMediaInformationExecute(final MediaInformationSession mediaInformationSession, final int waitTimeout) {
AsyncGetMediaInformationTask asyncGetMediaInformationTask = new AsyncGetMediaInformationTask(mediaInformationSession, waitTimeout);
Future> future = asyncExecutorService.submit(asyncGetMediaInformationTask);
mediaInformationSession.setFuture(future);
}
/**
* Asynchronously executes the media information session provided.
*
* @param mediaInformationSession media information session which includes command options/arguments
* @param executorService executor service that will be used to run this asynchronous operation
* @param waitTimeout max time to wait until media information is transmitted
*/
public static void asyncGetMediaInformationExecute(final MediaInformationSession mediaInformationSession, final ExecutorService executorService, final int waitTimeout) {
AsyncGetMediaInformationTask asyncGetMediaInformationTask = new AsyncGetMediaInformationTask(mediaInformationSession, waitTimeout);
Future> future = executorService.submit(asyncGetMediaInformationTask);
mediaInformationSession.setFuture(future);
}
/**
* Returns the maximum number of async sessions that will be executed in parallel.
*
* @return maximum number of async sessions that will be executed in parallel
*/
public static int getAsyncConcurrencyLimit() {
return asyncConcurrencyLimit;
}
/**
* Sets the maximum number of async sessions that will be executed in parallel. If more
* sessions are submitted those will be queued.
*
* @param asyncConcurrencyLimit new async concurrency limit
*/
public static void setAsyncConcurrencyLimit(final int asyncConcurrencyLimit) {
if (asyncConcurrencyLimit > 0) {
/* SET THE NEW LIMIT */
FFmpegKitConfig.asyncConcurrencyLimit = asyncConcurrencyLimit;
ExecutorService oldAsyncExecutorService = FFmpegKitConfig.asyncExecutorService;
/* CREATE THE NEW ASYNC THREAD POOL */
FFmpegKitConfig.asyncExecutorService = Executors.newFixedThreadPool(asyncConcurrencyLimit);
/* STOP THE OLD ASYNC THREAD POOL */
oldAsyncExecutorService.shutdown();
}
}
/**
* Sets a global callback function to redirect FFmpeg/FFprobe logs.
*
* @param logCallback log callback function or null to disable a previously defined
* callback
*/
public static void enableLogCallback(final LogCallback logCallback) {
globalLogCallbackFunction = logCallback;
}
/**
* Sets a global callback function to redirect FFmpeg statistics.
*
* @param statisticsCallback statistics callback function or null to disable a previously
* defined callback
*/
public static void enableStatisticsCallback(final StatisticsCallback statisticsCallback) {
globalStatisticsCallbackFunction = statisticsCallback;
}
/**
* Sets a global callback function to receive execution results.
*
* @param executeCallback execute callback function or null to disable a previously
* defined callback
*/
public static void enableExecuteCallback(final ExecuteCallback executeCallback) {
globalExecuteCallbackFunction = executeCallback;
}
/**
* Returns the global execute callback function.
*
* @return global execute callback function
*/
static ExecuteCallback getExecuteCallback() {
return globalExecuteCallbackFunction;
}
/**
* Returns the current log level.
*
* @return current log level
*/
public static Level getLogLevel() {
return activeLogLevel;
}
/**
* Sets the log level.
*
* @param level new log level
*/
public static void setLogLevel(final Level level) {
if (level != null) {
activeLogLevel = level;
setNativeLogLevel(level.getValue());
}
}
static String extractExtensionFromSafDisplayName(final String safDisplayName) {
String rawExtension = safDisplayName;
if (safDisplayName.lastIndexOf(".") >= 0) {
rawExtension = safDisplayName.substring(safDisplayName.lastIndexOf("."));
}
try {
// workaround for https://issuetracker.google.com/issues/162440528: ANDROID_CREATE_DOCUMENT generating file names like "transcode.mp3 (2)"
return new StringTokenizer(rawExtension, " .").nextToken();
} catch (final Exception e) {
android.util.Log.w(TAG, String.format("Failed to extract extension from saf display name: %s.%s", safDisplayName, Exceptions.getStackTraceString(e)));
return "raw";
}
}
/**
* Converts the given Structured Access Framework Uri ( 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
*/
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";
try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
displayName = cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
}
} catch (final Throwable t) {
android.util.Log.e(TAG, String.format("Failed to get %s column for %s.%s", DocumentsContract.Document.COLUMN_DISPLAY_NAME, uri.toString(), Exceptions.getStackTraceString(t)));
}
int fd = -1;
try {
ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, openMode);
fd = parcelFileDescriptor.getFd();
pfdMap.put(fd, parcelFileDescriptor);
} catch (final Throwable t) {
android.util.Log.e(TAG, String.format("Failed to obtain %s parcelFileDescriptor for %s.%s", openMode, uri.toString(), Exceptions.getStackTraceString(t)));
}
return "saf:" + fd + "." + FFmpegKitConfig.extractExtensionFromSafDisplayName(displayName);
}
/**
* Converts the given Structured Access Framework Uri ( Requires API Level ≥ 19. On older API levels it returns an empty url.
*
* @param context application context
* @param uri saf uri
* @return input url that can be passed to FFmpegKit or FFprobeKit
*/
public static String getSafParameterForRead(final Context context, final Uri uri) {
return getSafParameter(context, uri, "r");
}
/**
* Converts the given Structured Access Framework Uri ( Requires API Level ≥ 19. On older API levels it returns an empty url.
*
* @param context application context
* @param uri saf uri
* @return output url that can be passed to FFmpegKit or FFprobeKit
*/
public static String getSafParameterForWrite(final Context context, final Uri uri) {
return getSafParameter(context, uri, "w");
}
/**
* Called by saf_wrapper from native library to close a parcel file descriptor.
*
* @param fd parcel file descriptor created for a saf uri
*/
private static void closeParcelFileDescriptor(final int fd) {
try {
ParcelFileDescriptor pfd = pfdMap.get(fd);
if (pfd != null) {
pfd.close();
pfdMap.delete(fd);
}
} catch (final Throwable t) {
android.util.Log.e(TAG, String.format("Failed to close file descriptor: %d.%s", fd, Exceptions.getStackTraceString(t)));
}
}
/**
* Returns the session history size.
*
* @return session history size
*/
public static int getSessionHistorySize() {
return sessionHistorySize;
}
/**
* Sets the session history size.
*
* @param sessionHistorySize session history size, should be smaller than 1000
*/
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!");
} else if (sessionHistorySize > 0) {
FFmpegKitConfig.sessionHistorySize = sessionHistorySize;
}
}
/**
* Adds a session to the session history.
*
* @param session new session
*/
static void addSession(final Session session) {
synchronized (sessionHistoryLock) {
/*
* ASYNC SESSIONS CALL THIS METHOD TWICE
* THIS CHECK PREVENTS ADDING THE SAME SESSION TWICE
*/
final boolean sessionAlreadyAdded = sessionHistoryMap.containsKey(session.getSessionId());
if (!sessionAlreadyAdded) {
sessionHistoryMap.put(session.getSessionId(), session);
sessionHistoryList.add(session);
if (sessionHistoryList.size() > sessionHistorySize) {
try {
sessionHistoryList.remove(0);
} catch (final IndexOutOfBoundsException ignored) {
}
}
}
}
}
/**
* Returns the session specified with Returns all sessions in the session history.
*
* @return all sessions in the session history
*/
public static List Clears all, including ongoing, sessions in the session history.
* Note that callbacks cannot be triggered for deleted sessions.
*/
public static void clearSessions() {
synchronized (sessionHistoryLock) {
sessionHistoryList.clear();
sessionHistoryMap.clear();
}
}
/**
* Returns all FFmpeg sessions in the session history.
*
* @return all FFmpeg sessions in the session history
*/
static List Returns all FFprobe sessions in the session history.
*
* @return all FFprobe sessions in the session history
*/
static List Returns sessions that have the given state.
*
* @param state session state
* @return sessions that have the given state from the session history
*/
public static List Sets the log redirection strategy
*
* @param logRedirectionStrategy log redirection strategy
*/
public static void setLogRedirectionStrategy(final LogRedirectionStrategy logRedirectionStrategy) {
FFmpegKitConfig.globalLogRedirectionStrategy = logRedirectionStrategy;
}
/**
* Parses the given command into arguments. Uses space character to split the arguments.
* Supports single and double quote characters.
*
* @param command string command
* @return array of arguments
*/
public static String[] parseArguments(final String command) {
final List Concatenates arguments into a string adding a space character between two arguments.
*
* @param arguments arguments
* @return concatenated string containing all arguments
*/
public static String argumentsToString(final String[] arguments) {
if (arguments == null) {
return "null";
}
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < arguments.length; i++) {
if (i > 0) {
stringBuilder.append(" ");
}
stringBuilder.append(arguments[i]);
}
return stringBuilder.toString();
}
/**
* Enables redirection natively.
*/
private static native void enableNativeRedirection();
/**
* Disables redirection natively.
*/
private static native void disableNativeRedirection();
/**
* Returns native log level.
*
* @return log level
*/
static native int getNativeLogLevel();
/**
* Sets native log level
*
* @param level log level
*/
private static native void setNativeLogLevel(int level);
/**
* Returns FFmpeg version bundled within the library natively.
*
* @return FFmpeg version
*/
private native static String getNativeFFmpegVersion();
/**
* Returns FFmpegKit library version natively.
*
* @return FFmpegKit version
*/
private native static String getNativeVersion();
/**
* Synchronously executes FFmpeg natively.
*
* @param sessionId id of the session
* @param arguments FFmpeg command options/arguments as string array
* @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);
/**
* Synchronously executes FFprobe natively.
*
* @param sessionId id of the session
* @param arguments FFprobe command options/arguments as string array
* @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);
/**
* Cancels an ongoing FFmpeg operation natively. This function does not wait for termination
* to complete and returns immediately.
*
* @param sessionId id of the session
*/
native static void nativeFFmpegCancel(final long sessionId);
/**
* Returns the number of native messages that are not transmitted to the Java callbacks for
* this session natively.
*
* @param sessionId id of the session
* @return number of native messages that are not transmitted to the Java callbacks for
* this session natively
*/
public native static int messagesInTransmit(final long sessionId);
/**
* Creates a new named pipe to use in Please note that creator is responsible of closing created pipes.
*
* @param ffmpegPipePath full path of ffmpeg pipe
* @return zero on successful creation, non-zero on error
*/
private native static int registerNewNativeFFmpegPipe(final String ffmpegPipePath);
/**
* Returns FFmpegKit library build date natively.
*
* @return FFmpegKit library build date
*/
private native static String getNativeBuildDate();
/**
* Sets an environment variable natively.
*
* @param variableName environment variable name
* @param variableValue environment variable value
* @return zero on success, non-zero on error
*/
private native static int setNativeEnvironmentVariable(final String variableName, final String variableValue);
/**
* Registers a new ignored signal natively. Ignored signals are not handled by
* FFmpegKit
library.
*/
public class FFmpegKitConfig {
/**
* The tag used for logging.
*/
static final String TAG = "ffmpeg-kit";
/**
* Prefix of named pipes created by ffmpeg kit.
*/
static final String FFMPEG_KIT_NAMED_PIPE_PREFIX = "fk_pipe_";
/**
* Generates ids for named ffmpeg kit pipes.
*/
private static final AtomicLong pipeIndexGenerator;
private static Level activeLogLevel;
/* Session history variables */
private static int sessionHistorySize;
private static final MapFFprobe
's getMediaInformation
methods
* do not work.
*/
public static void disableRedirection() {
disableNativeRedirection();
}
/**
* fontconfig
configuration directory.
*
* @param path directory that contains fontconfig configuration (fonts.conf)
* @return zero on success, non-zero on error
*/
public static int setFontconfigConfigurationPath(final String path) {
return setNativeEnvironmentVariable("FONTCONFIG_PATH", path);
}
/**
* FFmpegKit
with fontconfig
* enabled or use a prebuilt package with fontconfig
inside to be able to use
* fonts in FFmpeg
.
*
* @param context application context to access application data
* @param fontDirectoryPath directory that contains fonts (.ttf and .otf files)
* @param fontNameMapping custom font name mappings, useful to access your fonts with more
* friendly names
*/
public static void setFontDirectory(final Context context, final String fontDirectoryPath, final MapFFmpegKit
with fontconfig
* enabled or use a prebuilt package with fontconfig
inside to be able to use
* fonts in FFmpeg
.
*
* @param context application context to access application data
* @param fontDirectoryList list of directories that contain fonts (.ttf and .otf files)
* @param fontNameMapping custom font name mappings, useful to access your fonts with more
* friendly names
*/
public static void setFontDirectoryList(final Context context, final ListFFmpeg
operations.
*
* FFmpeg
pipe.
*
* @param ffmpegPipePath full path of the FFmpeg pipe
*/
public static void closeFFmpegPipe(final String ffmpegPipePath) {
final File file = new File(ffmpegPipePath);
if (file.exists()) {
file.delete();
}
}
/**
* Returns the list of camera ids supported. These devices can be used in FFmpeg
* commands.
*
* FFmpegKit
library.
*
* @return the version of FFmpeg
*/
public static String getFFmpegVersion() {
return getNativeFFmpegVersion();
}
/**
* FFmpegKit
* library.
*
* @param signal signal to be ignored
*/
public static void ignoreSignal(final Signal signal) {
ignoreNativeSignal(signal.getValue());
}
/**
* "content:…"
) into an
* input/output url that can be used in FFmpeg and FFprobe commands.
*
* "content:…"
) into an
* input url that can be used in FFmpeg and FFprobe commands.
*
* "content:…"
) into an
* output url that can be used in FFmpeg and FFprobe commands.
*
* sessionId
from the session history.
*
* @param sessionId session identifier
* @return session specified with sessionId or null if it is not found in the history
*/
public static Session getSession(final long sessionId) {
synchronized (sessionHistoryLock) {
return sessionHistoryMap.get(sessionId);
}
}
/**
* Returns the last session created from the session history.
*
* @return the last session created or null if session history is empty
*/
public static Session getLastSession() {
synchronized (sessionHistoryLock) {
if (sessionHistoryList.size() > 0) {
return sessionHistoryList.get(sessionHistoryList.size() - 1);
}
}
return null;
}
/**
* Returns the last session completed from the session history.
*
* @return the last session completed. If there are no completed sessions in the history this
* method will return null
*/
public static Session getLastCompletedSession() {
synchronized (sessionHistoryLock) {
for (int i = sessionHistoryList.size() - 1; i >= 0; i--) {
final Session session = sessionHistoryList.get(i);
if (session.getState() == SessionState.COMPLETED) {
return session;
}
}
}
return null;
}
/**
* FFmpeg
operations natively.
*
* FFmpegKit
library.
*
* @param signum signal number
*/
private native static void ignoreNativeSignal(final int signum);
}