You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
ffmpeg-kit/android/ffmpeg-kit-android-lib/src/main/cpp/ffmpegkit.c

922 lines
29 KiB

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <pthread.h>
#include <stdatomic.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "config.h"
#include "libavcodec/jni.h"
#include "libavutil/bprint.h"
#include "libavutil/file.h"
#include "fftools_ffmpeg.h"
#include "ffmpegkit.h"
#include "ffprobekit.h"
# define LogType 1
# define StatisticsType 2
/** Callback data structure */
struct CallbackData {
int type; // 1 (log callback) or 2 (statistics callback)
long sessionId; // session identifier
int logLevel; // log level
AVBPrint logData; // log data
int statisticsFrameNumber; // statistics frame number
float statisticsFps; // statistics fps
float statisticsQuality; // statistics quality
int64_t statisticsSize; // statistics size
int statisticsTime; // statistics time
double statisticsBitrate; // statistics bitrate
double statisticsSpeed; // statistics speed
struct CallbackData *next;
};
/** Session control variables */
#define SESSION_MAP_SIZE 1000
static atomic_short sessionMap[SESSION_MAP_SIZE];
static atomic_int sessionInTransitMessageCountMap[SESSION_MAP_SIZE];
/** Redirection control variables */
static pthread_mutex_t lockMutex;
static pthread_mutex_t monitorMutex;
static pthread_cond_t monitorCondition;
pthread_t callbackThread;
int redirectionEnabled;
struct CallbackData *callbackDataHead;
struct CallbackData *callbackDataTail;
/** Global reference to the virtual machine running */
static JavaVM *globalVm;
/** Global reference of Config class in Java */
static jclass configClass;
/** Global reference of log redirection method in Java */
static jmethodID logMethod;
/** Global reference of statistics redirection method in Java */
static jmethodID statisticsMethod;
/** Global reference of safOpen method in Java */
static jmethodID safOpenMethod;
/** Global reference of safClose method in Java */
static jmethodID safCloseMethod;
/** Global reference of String class in Java */
static jclass stringClass;
/** Global reference of String constructor in Java */
static jmethodID stringConstructor;
/** Full name of the Config class */
const char *configClassName = "com/arthenica/ffmpegkit/FFmpegKitConfig";
/** Full name of String class */
const char *stringClassName = "java/lang/String";
/** Fields that control the handling of SIGNALs */
volatile int handleSIGQUIT = 1;
volatile int handleSIGINT = 1;
volatile int handleSIGTERM = 1;
volatile int handleSIGXCPU = 1;
volatile int handleSIGPIPE = 1;
/** Holds the id of the current session */
__thread volatile long globalSessionId = 0;
/** Holds the default log level */
int configuredLogLevel = AV_LOG_INFO;
/** Prototypes of native functions defined by Config class. */
JNINativeMethod configMethods[] = {
{"enableNativeRedirection", "()V", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_enableNativeRedirection},
{"disableNativeRedirection", "()V", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_disableNativeRedirection},
{"setNativeLogLevel", "(I)V", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_setNativeLogLevel},
{"getNativeLogLevel", "()I", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeLogLevel},
{"getNativeFFmpegVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeFFmpegVersion},
{"getNativeVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeVersion},
{"nativeFFmpegExecute", "(J[Ljava/lang/String;)I", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpegExecute},
{"nativeFFmpegCancel", "(J)V", (void*) Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpegCancel},
{"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},
{"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},
{"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 */
int ffmpeg_execute(int argc, char **argv);
static const char *avutil_log_get_level_str(int level) {
switch (level) {
case AV_LOG_STDERR:
return "stderr";
case AV_LOG_QUIET:
return "quiet";
case AV_LOG_DEBUG:
return "debug";
case AV_LOG_VERBOSE:
return "verbose";
case AV_LOG_INFO:
return "info";
case AV_LOG_WARNING:
return "warning";
case AV_LOG_ERROR:
return "error";
case AV_LOG_FATAL:
return "fatal";
case AV_LOG_PANIC:
return "panic";
default:
return "";
}
}
static void avutil_log_format_line(void *avcl, int level, const char *fmt, va_list vl, AVBPrint part[4], int *print_prefix) {
int flags = av_log_get_flags();
AVClass* avc = avcl ? *(AVClass **) avcl : NULL;
av_bprint_init(part+0, 0, 1);
av_bprint_init(part+1, 0, 1);
av_bprint_init(part+2, 0, 1);
av_bprint_init(part+3, 0, 65536);
if (*print_prefix && avc) {
if (avc->parent_log_context_offset) {
AVClass** parent = *(AVClass ***) (((uint8_t *) avcl) +
avc->parent_log_context_offset);
if (parent && *parent) {
av_bprintf(part+0, "[%s @ %p] ",
(*parent)->item_name(parent), parent);
}
}
av_bprintf(part+1, "[%s @ %p] ",
avc->item_name(avcl), avcl);
}
if (*print_prefix && (level > AV_LOG_QUIET) && (flags & AV_LOG_PRINT_LEVEL))
av_bprintf(part+2, "[%s] ", avutil_log_get_level_str(level));
av_vbprintf(part+3, fmt, vl);
if(*part[0].str || *part[1].str || *part[2].str || *part[3].str) {
char lastc = part[3].len && part[3].len <= part[3].size ? part[3].str[part[3].len - 1] : 0;
*print_prefix = lastc == '\n' || lastc == '\r';
}
}
static void avutil_log_sanitize(uint8_t *line) {
while(*line){
if(*line < 0x08 || (*line > 0x0D && *line < 0x20))
*line='?';
line++;
}
}
void mutexInit() {
pthread_mutexattr_t attributes;
pthread_mutexattr_init(&attributes);
pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE_NP);
pthread_mutex_init(&lockMutex, &attributes);
pthread_mutexattr_destroy(&attributes);
}
void monitorInit() {
pthread_mutexattr_t attributes;
pthread_mutexattr_init(&attributes);
pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE_NP);
pthread_condattr_t cattributes;
pthread_condattr_init(&cattributes);
pthread_condattr_setpshared(&cattributes, PTHREAD_PROCESS_PRIVATE);
pthread_mutex_init(&monitorMutex, &attributes);
pthread_mutexattr_destroy(&attributes);
pthread_cond_init(&monitorCondition, &cattributes);
pthread_condattr_destroy(&cattributes);
}
void mutexUnInit() {
pthread_mutex_destroy(&lockMutex);
}
void monitorUnInit() {
pthread_mutex_destroy(&monitorMutex);
pthread_cond_destroy(&monitorCondition);
}
void mutexLock() {
pthread_mutex_lock(&lockMutex);
}
void mutexUnlock() {
pthread_mutex_unlock(&lockMutex);
}
void monitorWait(int milliSeconds) {
struct timeval tp;
struct timespec ts;
int rc;
rc = gettimeofday(&tp, NULL);
if (rc) {
return;
}
ts.tv_sec = tp.tv_sec;
ts.tv_nsec = tp.tv_usec * 1000;
ts.tv_sec += milliSeconds / 1000;
ts.tv_nsec += (milliSeconds % 1000)*1000000;
ts.tv_sec += ts.tv_nsec / 1000000000L;
ts.tv_nsec = ts.tv_nsec % 1000000000L;
pthread_mutex_lock(&monitorMutex);
pthread_cond_timedwait(&monitorCondition, &monitorMutex, &ts);
pthread_mutex_unlock(&monitorMutex);
}
void monitorNotify() {
pthread_mutex_lock(&monitorMutex);
pthread_cond_signal(&monitorCondition);
pthread_mutex_unlock(&monitorMutex);
}
/**
* Adds log data to the end of callback data list.
*
* @param level log level
* @param data log data
*/
void logCallbackDataAdd(int level, AVBPrint *data) {
// CREATE DATA STRUCT FIRST
struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData));
newData->type = LogType;
newData->sessionId = globalSessionId;
newData->logLevel = level;
av_bprint_init(&newData->logData, 0, AV_BPRINT_SIZE_UNLIMITED);
av_bprintf(&newData->logData, "%s", data->str);
newData->next = NULL;
mutexLock();
// INSERT IT TO THE END OF QUEUE
if (callbackDataTail == NULL) {
callbackDataTail = newData;
if (callbackDataHead != NULL) {
LOGE("Dangling callback data head detected. This can cause memory leak.");
} else {
callbackDataHead = newData;
}
} else {
struct CallbackData *oldTail = callbackDataTail;
oldTail->next = newData;
callbackDataTail = newData;
}
mutexUnlock();
monitorNotify();
atomic_fetch_add(&sessionInTransitMessageCountMap[globalSessionId % SESSION_MAP_SIZE], 1);
}
/**
* Adds statistics data to the end of callback data list.
*/
void statisticsCallbackDataAdd(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed) {
// CREATE DATA STRUCT FIRST
struct CallbackData *newData = (struct CallbackData*)av_malloc(sizeof(struct CallbackData));
newData->type = StatisticsType;
newData->sessionId = globalSessionId;
newData->statisticsFrameNumber = frameNumber;
newData->statisticsFps = fps;
newData->statisticsQuality = quality;
newData->statisticsSize = size;
newData->statisticsTime = time;
newData->statisticsBitrate = bitrate;
newData->statisticsSpeed = speed;
newData->next = NULL;
mutexLock();
// INSERT IT TO THE END OF QUEUE
if (callbackDataTail == NULL) {
callbackDataTail = newData;
if (callbackDataHead != NULL) {
LOGE("Dangling callback data head detected. This can cause memory leak.");
} else {
callbackDataHead = newData;
}
} else {
struct CallbackData *oldTail = callbackDataTail;
oldTail->next = newData;
callbackDataTail = newData;
}
mutexUnlock();
monitorNotify();
atomic_fetch_add(&sessionInTransitMessageCountMap[globalSessionId % SESSION_MAP_SIZE], 1);
}
/**
* Adds a session id to the session map.
*
* @param id session id
*/
void addSession(long id) {
atomic_store(&sessionMap[id % SESSION_MAP_SIZE], 1);
}
/**
* Removes head of callback data list.
*/
struct CallbackData *callbackDataRemove() {
struct CallbackData *currentData;
mutexLock();
if (callbackDataHead == NULL) {
currentData = NULL;
} else {
currentData = callbackDataHead;
struct CallbackData *nextHead = currentData->next;
if (nextHead == NULL) {
if (callbackDataHead != callbackDataTail) {
LOGE("Head and tail callback data pointers do not match for single callback data element. This can cause memory leak.");
} else {
callbackDataTail = NULL;
}
callbackDataHead = NULL;
} else {
callbackDataHead = nextHead;
}
}
mutexUnlock();
return currentData;
}
/**
* Removes a session id from the session map.
*
* @param id session id
*/
void removeSession(long id) {
atomic_store(&sessionMap[id % SESSION_MAP_SIZE], 0);
}
/**
* Adds a cancel session request to the session map.
*
* @param id session id
*/
void cancelSession(long id) {
atomic_store(&sessionMap[id % SESSION_MAP_SIZE], 2);
}
/**
* 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) {
if (atomic_load(&sessionMap[id % SESSION_MAP_SIZE]) == 2) {
return 1;
} else {
return 0;
}
}
/**
* Resets the number of messages in transmit for this session.
*
* @param id session id
*/
void resetMessagesInTransmit(long id) {
atomic_store(&sessionInTransitMessageCountMap[id % SESSION_MAP_SIZE], 0);
}
/**
* Callback function for FFmpeg logs.
*
* @param ptr pointer to AVClass struct
* @param level log level
* @param format format string
* @param vargs arguments
*/
void ffmpegkit_log_callback_function(void *ptr, int level, const char* format, va_list vargs) {
AVBPrint fullLine;
AVBPrint part[4];
int print_prefix = 1;
if (level >= 0) {
level &= 0xff;
}
int activeLogLevel = av_log_get_level();
// AV_LOG_STDERR logs are always redirected
if ((activeLogLevel == AV_LOG_QUIET && level != AV_LOG_STDERR) || (level > activeLogLevel)) {
return;
}
av_bprint_init(&fullLine, 0, AV_BPRINT_SIZE_UNLIMITED);
avutil_log_format_line(ptr, level, format, vargs, part, &print_prefix);
avutil_log_sanitize(part[0].str);
avutil_log_sanitize(part[1].str);
avutil_log_sanitize(part[2].str);
avutil_log_sanitize(part[3].str);
// COMBINE ALL 4 LOG PARTS
av_bprintf(&fullLine, "%s%s%s%s", part[0].str, part[1].str, part[2].str, part[3].str);
if (fullLine.len > 0) {
logCallbackDataAdd(level, &fullLine);
}
av_bprint_finalize(part, NULL);
av_bprint_finalize(part+1, NULL);
av_bprint_finalize(part+2, NULL);
av_bprint_finalize(part+3, NULL);
av_bprint_finalize(&fullLine, NULL);
}
/**
* Callback function for FFmpeg statistics.
*
* @param frameNumber last processed frame number
* @param fps frames processed per second
* @param quality quality of the output stream (video only)
* @param size size in bytes
* @param time processed output duration
* @param bitrate output bit rate in kbits/s
* @param speed processing speed = processed duration / operation duration
*/
void ffmpegkit_statistics_callback_function(int frameNumber, float fps, float quality, int64_t size, int time, double bitrate, double speed) {
statisticsCallbackDataAdd(frameNumber, fps, quality, size, time, bitrate, speed);
}
/**
* Forwards callback messages to Java classes.
*/
void *callbackThreadFunction() {
JNIEnv *env;
jint getEnvRc = (*globalVm)->GetEnv(globalVm, (void**) &env, JNI_VERSION_1_6);
if (getEnvRc != JNI_OK) {
if (getEnvRc != JNI_EDETACHED) {
LOGE("Callback thread failed to GetEnv for class %s with rc %d.\n", configClassName, getEnvRc);
return NULL;
}
if ((*globalVm)->AttachCurrentThread(globalVm, &env, NULL) != 0) {
LOGE("Callback thread failed to AttachCurrentThread for class %s.\n", configClassName);
return NULL;
}
}
LOGD("Async callback block started.\n");
while(redirectionEnabled) {
struct CallbackData *callbackData = callbackDataRemove();
if (callbackData != NULL) {
if (callbackData->type == LogType) {
// LOG CALLBACK
int size = callbackData->logData.len;
jbyteArray byteArray = (jbyteArray) (*env)->NewByteArray(env, size);
(*env)->SetByteArrayRegion(env, byteArray, 0, size, callbackData->logData.str);
(*env)->CallStaticVoidMethod(env, configClass, logMethod, (jlong) callbackData->sessionId, callbackData->logLevel, byteArray);
(*env)->DeleteLocalRef(env, byteArray);
// CLEAN LOG DATA
av_bprint_finalize(&callbackData->logData, NULL);
} else {
// STATISTICS CALLBACK
(*env)->CallStaticVoidMethod(env, configClass, statisticsMethod,
(jlong) callbackData->sessionId, callbackData->statisticsFrameNumber,
callbackData->statisticsFps, callbackData->statisticsQuality,
callbackData->statisticsSize, callbackData->statisticsTime,
callbackData->statisticsBitrate, callbackData->statisticsSpeed);
}
atomic_fetch_sub(&sessionInTransitMessageCountMap[callbackData->sessionId % SESSION_MAP_SIZE], 1);
// CLEAN STRUCT
callbackData->next = NULL;
av_free(callbackData);
} else {
monitorWait(100);
}
}
(*globalVm)->DetachCurrentThread(globalVm);
LOGD("Async callback block stopped.\n");
return NULL;
}
/**
* Used by saf protocol; is expected to be called from a Java thread, therefore we don't need attach/detach
*/
int saf_open(int safId) {
JNIEnv *env = NULL;
(*globalVm)->GetEnv(globalVm, (void**) &env, JNI_VERSION_1_6);
return (*env)->CallStaticIntMethod(env, configClass, safOpenMethod, safId);
}
/**
* Used by saf protocol; is expected to be called from a Java thread, therefore we don't need attach/detach
*/
int saf_close(int fd) {
JNIEnv *env = NULL;
(*globalVm)->GetEnv(globalVm, (void**) &env, JNI_VERSION_1_6);
return (*env)->CallStaticIntMethod(env, configClass, safCloseMethod, fd);
}
/**
* Called when 'ffmpegkit' native library is loaded.
*
* @param vm pointer to the running virtual machine
* @param reserved reserved
* @return JNI version needed by 'ffmpegkit' library
*/
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
if ((*vm)->GetEnv(vm, (void**)(&env), JNI_VERSION_1_6) != JNI_OK) {
LOGE("OnLoad failed to GetEnv for class %s.\n", configClassName);
return JNI_FALSE;
}
jclass localConfigClass = (*env)->FindClass(env, configClassName);
if (localConfigClass == NULL) {
LOGE("OnLoad failed to FindClass %s.\n", configClassName);
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, localConfigClass, configMethods, 14) < 0) {
LOGE("OnLoad failed to RegisterNatives for class %s.\n", configClassName);
return JNI_FALSE;
}
jclass localStringClass = (*env)->FindClass(env, stringClassName);
if (localStringClass == NULL) {
LOGE("OnLoad failed to FindClass %s.\n", stringClassName);
return JNI_FALSE;
}
(*env)->GetJavaVM(env, &globalVm);
logMethod = (*env)->GetStaticMethodID(env, localConfigClass, "log", "(JI[B)V");
if (logMethod == NULL) {
LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "log");
return JNI_FALSE;
}
statisticsMethod = (*env)->GetStaticMethodID(env, localConfigClass, "statistics", "(JIFFJIDD)V");
if (statisticsMethod == NULL) {
LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "statistics");
return JNI_FALSE;
}
safOpenMethod = (*env)->GetStaticMethodID(env, localConfigClass, "safOpen", "(I)I");
if (safOpenMethod == NULL) {
LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "safOpen");
return JNI_FALSE;
}
safCloseMethod = (*env)->GetStaticMethodID(env, localConfigClass, "safClose", "(I)I");
if (safCloseMethod == NULL) {
LOGE("OnLoad thread failed to GetStaticMethodID for %s.\n", "safClose");
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>");
return JNI_FALSE;
}
av_jni_set_java_vm(vm, NULL);
configClass = (jclass) ((*env)->NewGlobalRef(env, localConfigClass));
stringClass = (jclass) ((*env)->NewGlobalRef(env, localStringClass));
callbackDataHead = NULL;
callbackDataTail = NULL;
for(int i = 0; i<SESSION_MAP_SIZE; i++) {
atomic_init(&sessionMap[i], 0);
atomic_init(&sessionInTransitMessageCountMap[i], 0);
}
mutexInit();
monitorInit();
redirectionEnabled = 0;
av_set_saf_open(saf_open);
av_set_saf_close(saf_close);
return JNI_VERSION_1_6;
}
/**
* Sets log level.
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @param level log level
*/
JNIEXPORT void JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_setNativeLogLevel(JNIEnv *env, jclass object, jint level) {
configuredLogLevel = level;
}
/**
* Returns current log level.
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
*/
JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeLogLevel(JNIEnv *env, jclass object) {
return configuredLogLevel;
}
/**
* Enables log and statistics redirection.
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
*/
JNIEXPORT void JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_enableNativeRedirection(JNIEnv *env, jclass object) {
mutexLock();
if (redirectionEnabled != 0) {
mutexUnlock();
return;
}
redirectionEnabled = 1;
mutexUnlock();
int rc = pthread_create(&callbackThread, 0, callbackThreadFunction, 0);
if (rc != 0) {
LOGE("Failed to create callback thread (rc=%d).\n", rc);
return;
}
av_log_set_callback(ffmpegkit_log_callback_function);
set_report_callback(ffmpegkit_statistics_callback_function);
}
/**
* Disables log and statistics redirection.
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
*/
JNIEXPORT void JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_disableNativeRedirection(JNIEnv *env, jclass object) {
mutexLock();
if (redirectionEnabled != 1) {
mutexUnlock();
return;
}
redirectionEnabled = 0;
mutexUnlock();
av_log_set_callback(av_log_default_callback);
set_report_callback(NULL);
monitorNotify();
}
/**
* Returns FFmpeg version bundled within the library natively.
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @return FFmpeg version string
*/
JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeFFmpegVersion(JNIEnv *env, jclass object) {
return (*env)->NewStringUTF(env, FFMPEG_VERSION);
}
/**
* Returns FFmpegKit library version natively.
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @return FFmpegKit version string
*/
JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeVersion(JNIEnv *env, jclass object) {
return (*env)->NewStringUTF(env, FFMPEG_KIT_VERSION);
}
/**
* Synchronously executes FFmpeg 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 FFmpeg command arguments
* @return zero on successful execution, non-zero on error
*/
JNIEXPORT jint JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpegExecute(JNIEnv *env, jclass object, jlong id, jobjectArray stringArray) {
jstring *tempArray = NULL;
int argumentCount = 1;
char **argv = NULL;
// SETS DEFAULT LOG LEVEL BEFORE STARTING A NEW RUN
av_log_set_level(configuredLogLevel);
if (stringArray) {
int programArgumentCount = (*env)->GetArrayLength(env, stringArray);
argumentCount = programArgumentCount + 1;
tempArray = (jstring *) av_malloc(sizeof(jstring) * programArgumentCount);
}
/* PRESERVE USAGE FORMAT
*
* ffmpeg <arguments>
*/
argv = (char **)av_malloc(sizeof(char*) * (argumentCount));
argv[0] = (char *)av_malloc(sizeof(char) * (strlen(LIB_NAME) + 1));
strcpy(argv[0], LIB_NAME);
// PREPARE ARRAY ELEMENTS
if (stringArray) {
for (int i = 0; i < (argumentCount - 1); i++) {
tempArray[i] = (jstring) (*env)->GetObjectArrayElement(env, stringArray, i);
if (tempArray[i] != NULL) {
argv[i + 1] = (char *) (*env)->GetStringUTFChars(env, tempArray[i], 0);
}
}
}
// REGISTER THE ID BEFORE STARTING THE SESSION
globalSessionId = (long) id;
addSession((long) id);
resetMessagesInTransmit(globalSessionId);
// RUN
int returnCode = ffmpeg_execute(argumentCount, argv);
// ALWAYS REMOVE THE ID FROM THE MAP
removeSession((long) id);
// CLEANUP
if (tempArray) {
for (int i = 0; i < (argumentCount - 1); i++) {
(*env)->ReleaseStringUTFChars(env, tempArray[i], argv[i + 1]);
}
av_free(tempArray);
}
av_free(argv[0]);
av_free(argv);
return returnCode;
}
/**
* Cancels an ongoing FFmpeg operation natively.
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @param id session id
*/
JNIEXPORT void JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_nativeFFmpegCancel(JNIEnv *env, jclass object, jlong id) {
cancel_operation(id);
}
/**
* Creates natively a new named pipe to use in FFmpeg operations.
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @param ffmpegPipePath full path of ffmpeg pipe
* @return zero on successful creation, non-zero on error
*/
JNIEXPORT int JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_registerNewNativeFFmpegPipe(JNIEnv *env, jclass object, jstring ffmpegPipePath) {
const char *ffmpegPipePathString = (*env)->GetStringUTFChars(env, ffmpegPipePath, 0);
return mkfifo(ffmpegPipePathString, S_IRWXU | S_IRWXG | S_IROTH);
}
/**
* Returns FFmpegKit library build date natively.
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @return FFmpegKit library build date
*/
JNIEXPORT jstring JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_getNativeBuildDate(JNIEnv *env, jclass object) {
char buildDate[10];
sprintf(buildDate, "%d", FFMPEG_KIT_BUILD_DATE);
return (*env)->NewStringUTF(env, buildDate);
}
/**
* Sets an environment variable natively
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @param variableName environment variable name
* @param variableValue environment variable value
* @return zero on success, non-zero on error
*/
JNIEXPORT int JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_setNativeEnvironmentVariable(JNIEnv *env, jclass object, jstring variableName, jstring variableValue) {
const char *variableNameString = (*env)->GetStringUTFChars(env, variableName, 0);
const char *variableValueString = (*env)->GetStringUTFChars(env, variableValue, 0);
int rc = setenv(variableNameString, variableValueString, 1);
(*env)->ReleaseStringUTFChars(env, variableName, variableNameString);
(*env)->ReleaseStringUTFChars(env, variableValue, variableValueString);
return rc;
}
/**
* Registers a new ignored signal. Ignored signals are not handled by the library.
*
* @param env pointer to native method interface
* @param object reference to the class on which this method is invoked
* @param signum signal number
*/
JNIEXPORT void JNICALL Java_com_arthenica_ffmpegkit_FFmpegKitConfig_ignoreNativeSignal(JNIEnv *env, jclass object, jint signum) {
if (signum == SIGQUIT) {
handleSIGQUIT = 0;
} else if (signum == SIGINT) {
handleSIGINT = 0;
} else if (signum == SIGTERM) {
handleSIGTERM = 0;
} else if (signum == SIGXCPU) {
handleSIGXCPU = 0;
} else if (signum == SIGPIPE) {
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) {
return atomic_load(&sessionInTransitMessageCountMap[id % SESSION_MAP_SIZE]);
}