diff --git a/android/app/src/main/cpp/mobileffmpeg.c b/android/app/src/main/cpp/mobileffmpeg.c index e0b9e1eef..56b968087 100644 --- a/android/app/src/main/cpp/mobileffmpeg.c +++ b/android/app/src/main/cpp/mobileffmpeg.c @@ -17,25 +17,442 @@ * along with MobileFFmpeg. If not, see . */ +/* + * CHANGES 09.2018 + * -------------------------------------------------------- + * - Merged with mobileffmpeg_config + * + * CHANGES 08.2018 + * -------------------------------------------------------- + * - Copied methods with avutil_log_ prefix from libavutil/log.c + */ + #include +#include "libavutil/bprint.h" +#include "mobileffmpeg.h" #include "fftools_ffmpeg.h" -#include "mobileffmpeg.h" +/** Callback data structure */ +struct CallbackData { + int type; // 1 (log callback) or 2 (statistics callback) -/** Forward declaration for function defined in fftools_ffmpeg.c */ -int execute(int argc, char **argv); + int logLevel; // log level + char *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; +}; + +/** Redirection control variables */ +pthread_mutex_t lockMutex; +pthread_mutex_t monitorMutex; +pthread_cond_t monitorCondition; + +pthread_t callbackThread; +int redirectionEnabled; -/** Full name of the FFmpeg class */ -const char *ffmpegClassName = "com/arthenica/mobileffmpeg/FFmpeg"; +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; + +/** Full name of the Config class */ +const char *configClassName = "com/arthenica/mobileffmpeg/Config"; + +/** Prototypes of native functions defined by Config class. */ +JNINativeMethod configMethods[] = { + {"enableNativeRedirection", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_enableNativeRedirection}, + {"disableNativeRedirection", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_disableNativeRedirection}, + {"setNativeLogLevel", "(I)V", (void*) Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel}, + {"getNativeLogLevel", "()I", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel} +}; /** Prototypes of native functions defined by FFmpeg class. */ JNINativeMethod ffmpegMethods[] = { - {"getFFmpegVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_FFmpeg_getFFmpegVersion}, - {"getVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_FFmpeg_getVersion}, - {"execute", "([Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_FFmpeg_execute} + {"getNativeFFmpegVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeFFmpegVersion}, + {"getNativeVersion", "()Ljava/lang/String;", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeVersion}, + {"nativeExecute", "([Ljava/lang/String;)I", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeExecute}, + {"nativeCancel", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_nativeCancel} }; +/** Forward declaration for function defined in fftools_ffmpeg.c */ +int execute(int argc, char **argv); + +/** DEFINES LINE SIZE USED FOR LOGGING */ +#define LOG_LINE_SIZE 1024 + +static const char *avutil_log_get_level_str(int level) { + switch (level) { + 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; + + 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. + */ +void logCallbackDataAdd(int level, const char *data) { + + // CREATE DATA STRUCT FIRST + struct CallbackData *newData = (struct CallbackData*)malloc(sizeof(struct CallbackData)); + newData->type = 1; + newData->logLevel = level; + size_t dataSize = strlen(data) + 1; + newData->logData = (char*)malloc(dataSize); + memcpy(newData->logData, data, dataSize); + 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(); +} + +/** + * 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*)malloc(sizeof(struct CallbackData)); + newData->type = 2; + 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(); +} + +/** + * 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; +} + +/** + * Callback function for FFmpeg logs. + * + * \param pointer to AVClass struct + * \param level + * \param format + * \param arguments + */ +void mobileffmpeg_log_callback_function(void *ptr, int level, const char* format, va_list vargs) { + char line[LOG_LINE_SIZE]; + AVBPrint part[4]; + int print_prefix = 1; + + if (level >= 0) { + level &= 0xff; + } + + avutil_log_format_line(ptr, level, format, vargs, part, &print_prefix); + snprintf(line, sizeof(line), "%s%s%s%s", part[0].str, part[1].str, part[2].str, part[3].str); + + avutil_log_sanitize(part[0].str); + logCallbackDataAdd(level, part[0].str); + avutil_log_sanitize(part[1].str); + logCallbackDataAdd(level, part[1].str); + avutil_log_sanitize(part[2].str); + logCallbackDataAdd(level, part[2].str); + avutil_log_sanitize(part[3].str); + logCallbackDataAdd(level, part[3].str); + + av_bprint_finalize(part+3, 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 mobileffmpeg_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("Callback thread started.\n"); + + while(redirectionEnabled) { + + struct CallbackData *callbackData = callbackDataRemove(); + if (callbackData != NULL) { + if (callbackData->type == 1) { + + // LOG CALLBACK + + size_t size = strlen(callbackData->logData); + + jbyteArray byteArray = (jbyteArray) (*env)->NewByteArray(env, size); + (*env)->SetByteArrayRegion(env, byteArray, 0, size, (jbyte *)callbackData->logData); + (*env)->CallStaticVoidMethod(env, configClass, logMethod, callbackData->logLevel, byteArray); + (*env)->DeleteLocalRef(env, byteArray); + + // CLEAN LOG DATA + free(callbackData->logData); + + } else { + + // STATISTICS CALLBACK + + (*env)->CallStaticVoidMethod(env, configClass, statisticsMethod, + callbackData->statisticsFrameNumber, callbackData->statisticsFps, + callbackData->statisticsQuality, callbackData->statisticsSize, + callbackData->statisticsTime, callbackData->statisticsBitrate, + callbackData->statisticsSpeed); + + } + + // CLEAN STRUCT + callbackData->next = NULL; + free(callbackData); + + } else { + monitorWait(100); + } + } + + (*globalVm)->DetachCurrentThread(globalVm); + + LOGD("Callback thread stopped.\n"); + + return NULL; +} + /** * Called when 'mobileffmpeg' native library is loaded. * @@ -46,55 +463,158 @@ JNINativeMethod ffmpegMethods[] = { 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", ffmpegClassName); + 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; } - jclass ffmpegClass = (*env)->FindClass(env, ffmpegClassName); - if (ffmpegClass == NULL) { - LOGE("OnLoad failed to FindClass %s.\n", ffmpegClassName); + if ((*env)->RegisterNatives(env, localConfigClass, ffmpegMethods, 4) < 0) { + LOGE("OnLoad failed to RegisterNatives for class %s.\n", configClassName); return JNI_FALSE; } - if ((*env)->RegisterNatives(env, ffmpegClass, ffmpegMethods, 3) < 0) { - LOGE("OnLoad failed to RegisterNatives for class %s.\n", ffmpegClassName); + if ((*env)->RegisterNatives(env, localConfigClass, configMethods, 4) < 0) { + LOGE("OnLoad failed to RegisterNatives for class %s.\n", configClassName); return JNI_FALSE; } + (*env)->GetJavaVM(env, &globalVm); + + logMethod = (*env)->GetStaticMethodID(env, localConfigClass, "log", "(I[B)V"); + if (logMethod == NULL) { + LOGE("OnLoad thread failed to GetMethodID for %s.\n", "log"); + (*globalVm)->DetachCurrentThread(globalVm); + return JNI_FALSE; + } + + statisticsMethod = (*env)->GetStaticMethodID(env, localConfigClass, "statistics", "(IFFJIDD)V"); + if (logMethod == NULL) { + LOGE("OnLoad thread failed to GetMethodID for %s.\n", "statistics"); + (*globalVm)->DetachCurrentThread(globalVm); + return JNI_FALSE; + } + + configClass = (jclass) ((*env)->NewGlobalRef(env, localConfigClass)); + + redirectionEnabled = 0; + + callbackDataHead = NULL; + callbackDataTail = NULL; + + mutexInit(); + monitorInit(); + return JNI_VERSION_1_6; } /** - * Returns FFmpeg version bundled within the library. + * Sets log level. + * + * \param env pointer to native method interface + * \param reference to the class on which this method is invoked + * \param log level + */ +JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(JNIEnv *env, jclass object, jint level) { + av_log_set_level(level); +} + +/** + * Returns current log level. + * + * \param env pointer to native method interface + * \param reference to the class on which this method is invoked + */ +JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel(JNIEnv *env, jclass object) { + return av_log_get_level(); +} + +/** + * Enables log and statistics redirection. + * + * \param env pointer to native method interface + * \param reference to the class on which this method is invoked + */ +JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_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(mobileffmpeg_log_callback_function); + set_report_callback(mobileffmpeg_statistics_callback_function); +} + +/** + * Disables log and statistics redirection. + * + * \param env pointer to native method interface + * \param reference to the class on which this method is invoked + */ +JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_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_mobileffmpeg_FFmpeg_getFFmpegVersion(JNIEnv *env, jclass object) { +JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeFFmpegVersion(JNIEnv *env, jclass object) { return (*env)->NewStringUTF(env, FFMPEG_VERSION); } /** - * Returns MobileFFmpeg library version. + * Returns MobileFFmpeg library version natively. * * \param env pointer to native method interface * \param object reference to the class on which this method is invoked * \return MobileFFmpeg version string */ -JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_FFmpeg_getVersion(JNIEnv *env, jclass object) { +JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeVersion(JNIEnv *env, jclass object) { return (*env)->NewStringUTF(env, MOBILE_FFMPEG_VERSION); } /** - * Synchronously executes FFmpeg command with arguments provided. + * Synchronously executes FFmpeg command natively with arguments provided. * * \param env pointer to native method interface * \param object reference to the class on which this method is invoked * \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_mobileffmpeg_FFmpeg_execute(JNIEnv *env, jclass object, jobjectArray stringArray) { +JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeExecute(JNIEnv *env, jclass object, jobjectArray stringArray) { jstring *tempArray = NULL; int argumentCount = 1; char **argv = NULL; @@ -142,11 +662,11 @@ JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_FFmpeg_execute(JNIEnv *en } /** - * Cancels an ongoing operation. + * Cancels an ongoing operation natively. * * \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_mobileffmpeg_FFmpeg_cancel(JNIEnv *env, jclass object) { +JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeCancel(JNIEnv *env, jclass object) { cancel_operation(); } diff --git a/android/app/src/main/cpp/mobileffmpeg.h b/android/app/src/main/cpp/mobileffmpeg.h index 39d8c97ed..94344268e 100644 --- a/android/app/src/main/cpp/mobileffmpeg.h +++ b/android/app/src/main/cpp/mobileffmpeg.h @@ -21,39 +21,86 @@ #define MOBILE_FFMPEG_H #include +#include +#include "libavutil/log.h" #include "libavutil/ffversion.h" -#include "mobileffmpeg_config.h" /** Library version string */ #define MOBILE_FFMPEG_VERSION "2.2" +/** Defines tag used for Android logging. */ +#define LIB_NAME "mobile-ffmpeg" + +/** Verbose Android logging macro. */ +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LIB_NAME, __VA_ARGS__) + +/** Debug Android logging macro. */ +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LIB_NAME, __VA_ARGS__) + +/** Info Android logging macro. */ +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LIB_NAME, __VA_ARGS__) + +/** Warn Android logging macro. */ +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LIB_NAME, __VA_ARGS__) + +/** Error Android logging macro. */ +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LIB_NAME, __VA_ARGS__) + +/* + * Class: com_arthenica_mobileffmpeg_Config + * Method: enableNativeRedirection + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_enableNativeRedirection(JNIEnv *, jclass); + +/* + * Class: com_arthenica_mobileffmpeg_Config + * Method: disableNativeRedirection + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_disableNativeRedirection(JNIEnv *, jclass); + +/* + * Class: com_arthenica_mobileffmpeg_Config + * Method: setNativeLogLevel + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(JNIEnv *, jclass, jint); + +/* + * Class: com_arthenica_mobileffmpeg_Config + * Method: getNativeLogLevel + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel(JNIEnv *, jclass); + /* - * Class: com_arthenica_mobileffmpeg_FFmpeg - * Method: getFFmpegVersion + * Class: com_arthenica_mobileffmpeg_Config + * Method: getNativeFFmpegVersion * Signature: ()Ljava/lang/String; */ -JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_FFmpeg_getFFmpegVersion(JNIEnv *, jclass); +JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeFFmpegVersion(JNIEnv *, jclass); /* - * Class: com_arthenica_mobileffmpeg_FFmpeg - * Method: getVersion + * Class: com_arthenica_mobileffmpeg_Config + * Method: getNativeVersion * Signature: ()Ljava/lang/String; */ -JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_FFmpeg_getVersion(JNIEnv *, jclass); +JNIEXPORT jstring JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeVersion(JNIEnv *, jclass); /* - * Class: com_arthenica_mobileffmpeg_FFmpeg - * Method: execute + * Class: com_arthenica_mobileffmpeg_Config + * Method: nativeExecute * Signature: ([Ljava/lang/String;)I */ -JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_FFmpeg_execute(JNIEnv *, jclass, jobjectArray); +JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeExecute(JNIEnv *, jclass, jobjectArray); /* - * Class: com_arthenica_mobileffmpeg_FFmpeg - * Method: cancel + * Class: com_arthenica_mobileffmpeg_Config + * Method: nativeCancel * Signature: ()V */ -JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_FFmpeg_cancel(JNIEnv *, jclass); +JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_nativeCancel(JNIEnv *, jclass); -#endif /* MOBILE_FFMPEG_H */ +#endif /* MOBILE_FFMPEG_H */ \ No newline at end of file diff --git a/android/app/src/main/cpp/mobileffmpeg_config.c b/android/app/src/main/cpp/mobileffmpeg_config.c deleted file mode 100644 index fa5f074a2..000000000 --- a/android/app/src/main/cpp/mobileffmpeg_config.c +++ /dev/null @@ -1,565 +0,0 @@ -/* - * Copyright (c) 2018 Taner Sener - * - * This file is part of MobileFFmpeg. - * - * MobileFFmpeg 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. - * - * MobileFFmpeg 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 MobileFFmpeg. If not, see . - */ - -/* - * CHANGES 08.2018 - * -------------------------------------------------------- - * - Copied methods with avutil_log_ prefix from libavutil/log.c - */ - -#include - -#include "fftools_ffmpeg.h" -#include "mobileffmpeg_config.h" -#include "libavutil/bprint.h" - -/** Callback data structure */ -struct CallbackData { - int type; // 1 (log callback) or 2 (statistics callback) - - int logLevel; // log level - char *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; -}; - -/** Redirection control variables */ -pthread_mutex_t lockMutex; -pthread_mutex_t monitorMutex; -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; - -/** Full name of the Config class */ -const char *configClassName = "com/arthenica/mobileffmpeg/Config"; - -/** Prototypes of native functions defined by Config class. */ -JNINativeMethod configMethods[] = { - {"enableNativeRedirection", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_enableNativeRedirection}, - {"disableNativeRedirection", "()V", (void*) Java_com_arthenica_mobileffmpeg_Config_disableNativeRedirection}, - {"setNativeLogLevel", "(I)V", (void*) Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel}, - {"getNativeLogLevel", "()I", (void*) Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel} -}; - -/** DEFINES LINE SIZE USED FOR LOGGING */ -#define LOG_LINE_SIZE 1024 - -static const char *avutil_log_get_level_str(int level) { - switch (level) { - 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; - - 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. - */ -void logCallbackDataAdd(int level, const char *data) { - - // CREATE DATA STRUCT FIRST - struct CallbackData *newData = (struct CallbackData*)malloc(sizeof(struct CallbackData)); - newData->type = 1; - newData->logLevel = level; - size_t dataSize = strlen(data) + 1; - newData->logData = (char*)malloc(dataSize); - memcpy(newData->logData, data, dataSize); - 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(); -} - -/** - * 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*)malloc(sizeof(struct CallbackData)); - newData->type = 2; - 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(); -} - -/** - * 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; -} - -/** - * Callback function for FFmpeg logs. - * - * \param pointer to AVClass struct - * \param level - * \param format - * \param arguments - */ -void mobileffmpeg_log_callback_function(void *ptr, int level, const char* format, va_list vargs) { - char line[LOG_LINE_SIZE]; - AVBPrint part[4]; - int print_prefix = 1; - - if (level >= 0) { - level &= 0xff; - } - - avutil_log_format_line(ptr, level, format, vargs, part, &print_prefix); - snprintf(line, sizeof(line), "%s%s%s%s", part[0].str, part[1].str, part[2].str, part[3].str); - - avutil_log_sanitize(part[0].str); - logCallbackDataAdd(level, part[0].str); - avutil_log_sanitize(part[1].str); - logCallbackDataAdd(level, part[1].str); - avutil_log_sanitize(part[2].str); - logCallbackDataAdd(level, part[2].str); - avutil_log_sanitize(part[3].str); - logCallbackDataAdd(level, part[3].str); - - av_bprint_finalize(part+3, 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 mobileffmpeg_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("Callback thread started.\n"); - - while(redirectionEnabled) { - - struct CallbackData *callbackData = callbackDataRemove(); - if (callbackData != NULL) { - if (callbackData->type == 1) { - - // LOG CALLBACK - - size_t size = strlen(callbackData->logData); - - jbyteArray byteArray = (jbyteArray) (*env)->NewByteArray(env, size); - (*env)->SetByteArrayRegion(env, byteArray, 0, size, (jbyte *)callbackData->logData); - (*env)->CallStaticVoidMethod(env, configClass, logMethod, callbackData->logLevel, byteArray); - (*env)->DeleteLocalRef(env, byteArray); - - // CLEAN LOG DATA - free(callbackData->logData); - - } else { - - // STATISTICS CALLBACK - - (*env)->CallStaticVoidMethod(env, configClass, statisticsMethod, - callbackData->statisticsFrameNumber, callbackData->statisticsFps, - callbackData->statisticsQuality, callbackData->statisticsSize, - callbackData->statisticsTime, callbackData->statisticsBitrate, - callbackData->statisticsSpeed); - - } - - // CLEAN STRUCT - callbackData->next = NULL; - free(callbackData); - - } else { - monitorWait(100); - } - } - - (*globalVm)->DetachCurrentThread(globalVm); - - LOGD("Callback thread stopped.\n"); - - return NULL; -} - -/** - * Called when 'mobileffmpeg-config' native library is loaded. - * - * \param vm pointer to the running virtual machine - * \param reserved reserved - * \return JNI version needed by 'mobileffmpeg' 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, 4) < 0) { - LOGE("OnLoad failed to RegisterNatives for class %s.\n", configClassName); - return JNI_FALSE; - } - - (*env)->GetJavaVM(env, &globalVm); - - logMethod = (*env)->GetStaticMethodID(env, localConfigClass, "log", "(I[B)V"); - if (logMethod == NULL) { - LOGE("OnLoad thread failed to GetMethodID for %s.\n", "log"); - (*globalVm)->DetachCurrentThread(globalVm); - return JNI_FALSE; - } - - statisticsMethod = (*env)->GetStaticMethodID(env, localConfigClass, "statistics", "(IFFJIDD)V"); - if (logMethod == NULL) { - LOGE("OnLoad thread failed to GetMethodID for %s.\n", "statistics"); - (*globalVm)->DetachCurrentThread(globalVm); - return JNI_FALSE; - } - - configClass = (jclass) ((*env)->NewGlobalRef(env, localConfigClass)); - - redirectionEnabled = 0; - - callbackDataHead = NULL; - callbackDataTail = NULL; - - mutexInit(); - monitorInit(); - - return JNI_VERSION_1_6; -} - -/** - * Sets log level. - * - * \param env pointer to native method interface - * \param reference to the class on which this method is invoked - * \param log level - */ -JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(JNIEnv *env, jclass object, jint level) { - av_log_set_level(level); -} - -/** - * Returns current log level. - * - * \param env pointer to native method interface - * \param reference to the class on which this method is invoked - */ -JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel(JNIEnv *env, jclass object) { - return av_log_get_level(); -} - -/** - * Enables log and statistics redirection. - * - * \param env pointer to native method interface - * \param reference to the class on which this method is invoked - */ -JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_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(mobileffmpeg_log_callback_function); - set_report_callback(mobileffmpeg_statistics_callback_function); -} - -/** - * Disables log and statistics redirection. - * - * \param env pointer to native method interface - * \param reference to the class on which this method is invoked - */ -JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_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(); -} diff --git a/android/app/src/main/cpp/mobileffmpeg_config.h b/android/app/src/main/cpp/mobileffmpeg_config.h deleted file mode 100644 index 17cf6111b..000000000 --- a/android/app/src/main/cpp/mobileffmpeg_config.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2018 Taner Sener - * - * This file is part of MobileFFmpeg. - * - * MobileFFmpeg 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. - * - * MobileFFmpeg 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 MobileFFmpeg. If not, see . - */ - -#ifndef MOBILE_FFMPEG_CONFIG_H -#define MOBILE_FFMPEG_CONFIG_H - -#include -#include - -#include "libavutil/log.h" -#include "libavutil/ffversion.h" -#include "mobileffmpeg.h" - -/** Defines tag used for Android logging. */ -#define LIB_NAME "mobile-ffmpeg" - -/** Verbose Android logging macro. */ -#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LIB_NAME, __VA_ARGS__) - -/** Debug Android logging macro. */ -#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LIB_NAME, __VA_ARGS__) - -/** Info Android logging macro. */ -#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LIB_NAME, __VA_ARGS__) - -/** Warn Android logging macro. */ -#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LIB_NAME, __VA_ARGS__) - -/** Error Android logging macro. */ -#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LIB_NAME, __VA_ARGS__) - -/* - * Class: com_arthenica_mobileffmpeg_Config - * Method: enableNativeRedirection - * Signature: ()V - */ -JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_enableNativeRedirection(JNIEnv *, jclass); - -/* - * Class: com_arthenica_mobileffmpeg_Config - * Method: disableNativeRedirection - * Signature: ()V - */ -JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_disableNativeRedirection(JNIEnv *, jclass); - -/* - * Class: com_arthenica_mobileffmpeg_Config - * Method: setNativeLogLevel - * Signature: (I)V - */ -JNIEXPORT void JNICALL Java_com_arthenica_mobileffmpeg_Config_setNativeLogLevel(JNIEnv *, jclass, jint); - -/* - * Class: com_arthenica_mobileffmpeg_Config - * Method: getNativeLogLevel - * Signature: ()I - */ -JNIEXPORT jint JNICALL Java_com_arthenica_mobileffmpeg_Config_getNativeLogLevel(JNIEnv *, jclass); - -#endif /* MOBILE_FFMPEG_CONFIG_H */ diff --git a/android/app/src/main/java/com/arthenica/mobileffmpeg/Config.java b/android/app/src/main/java/com/arthenica/mobileffmpeg/Config.java index 42c34a4f5..5e7542d58 100644 --- a/android/app/src/main/java/com/arthenica/mobileffmpeg/Config.java +++ b/android/app/src/main/java/com/arthenica/mobileffmpeg/Config.java @@ -86,7 +86,7 @@ public class Config { boolean nativeLibraryLoaded = false; if (abi == Abi.ABI_ARMV7A_NEON) { try { - System.loadLibrary("mobileffmpeg-config-armv7a-neon"); + System.loadLibrary("mobileffmpeg-armv7a-neon"); nativeLibraryLoaded = true; } catch (final UnsatisfiedLinkError e) { Log.i(Config.TAG, "NEON supported armeabi-v7a library not found. Loading default armeabi-v7a library.", e); @@ -95,7 +95,7 @@ public class Config { } if (!nativeLibraryLoaded) { - System.loadLibrary("mobileffmpeg-config"); + System.loadLibrary("mobileffmpeg"); } Log.i(Config.TAG, String.format("Loaded mobile-ffmpeg-%s-%s.", abi.getName(), getVersion())); @@ -385,4 +385,33 @@ public static void setFontDirectory(final Context context, final String fontDire */ private static native int getNativeLogLevel(); + /** + *

Returns FFmpeg version bundled within the library natively. + * + * @return FFmpeg version + */ + native static String getNativeFFmpegVersion(); + + /** + *

Returns MobileFFmpeg library version natively. + * + * @return MobileFFmpeg version + */ + native static String getNativeVersion(); + + /** + *

Synchronously executes FFmpeg natively with arguments provided. + * + * @param arguments FFmpeg command options/arguments as string array + * @return zero on successful execution, 255 on user cancel and non-zero on error + */ + native static int nativeExecute(final String[] arguments); + + /** + *

Cancels an ongoing operation natively. + * + *

This function does not wait for termination to complete and returns immediately. + */ + native static void nativeCancel(); + } diff --git a/android/app/src/main/java/com/arthenica/mobileffmpeg/FFmpeg.java b/android/app/src/main/java/com/arthenica/mobileffmpeg/FFmpeg.java index 4574e01c2..4501d89f7 100644 --- a/android/app/src/main/java/com/arthenica/mobileffmpeg/FFmpeg.java +++ b/android/app/src/main/java/com/arthenica/mobileffmpeg/FFmpeg.java @@ -37,8 +37,6 @@ public class FFmpeg { public static final int RETURN_CODE_CANCEL = 255; static { - System.loadLibrary("mobileffmpeg"); - AbiDetect.class.getName(); Config.class.getName(); } @@ -54,14 +52,18 @@ private FFmpeg() { * * @return FFmpeg version */ - public native static String getFFmpegVersion(); + public static String getFFmpegVersion() { + return Config.getNativeFFmpegVersion(); + } /** *

Returns MobileFFmpeg library version. * * @return MobileFFmpeg version */ - public native static String getVersion(); + public static String getVersion() { + return Config.getNativeVersion(); + } /** *

Synchronously executes FFmpeg with arguments provided. @@ -69,7 +71,9 @@ private FFmpeg() { * @param arguments FFmpeg command options/arguments as string array * @return zero on successful execution, 255 on user cancel and non-zero on error */ - public native static int execute(final String[] arguments); + public static int execute(final String[] arguments) { + return Config.nativeExecute(arguments); + } /** *

Synchronously executes FFmpeg with arguments provided. @@ -86,6 +90,8 @@ public static int execute(final String arguments) { * *

This function does not wait for termination to complete and returns immediately. */ - public native static void cancel(); + public static void cancel() { + Config.nativeCancel(); + } } diff --git a/android/jni/Android.mk b/android/jni/Android.mk index e31589448..0b8efb250 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -25,19 +25,10 @@ include $(BUILD_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_ARM_MODE := $(MY_ARM_MODE) LOCAL_MODULE := mobileffmpeg -LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg.c +LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg.c $(MY_PATH)/fftools_cmdutils.c $(MY_PATH)/fftools_ffmpeg.c $(MY_PATH)/fftools_ffmpeg_opt.c $(MY_PATH)/fftools_ffmpeg_hw.c $(MY_PATH)/fftools_ffmpeg_filter.c LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter -Wno-switch -Wno-sign-compare -I${LOCAL_PATH}/../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/include LOCAL_LDLIBS := -llog -lz -landroid -LOCAL_SHARED_LIBRARIES := c++_shared mobileffmpeg-config -include $(BUILD_SHARED_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_ARM_MODE := $(MY_ARM_MODE) -LOCAL_MODULE := mobileffmpeg-config -LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg_config.c $(MY_PATH)/fftools_cmdutils.c $(MY_PATH)/fftools_ffmpeg.c $(MY_PATH)/fftools_ffmpeg_opt.c $(MY_PATH)/fftools_ffmpeg_hw.c $(MY_PATH)/fftools_ffmpeg_filter.c -LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter -Wno-switch -Wno-sign-compare -I${LOCAL_PATH}/../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/include -LOCAL_LDLIBS := -llog -lz -landroid -LOCAL_SHARED_LIBRARIES := libavfilter libavformat libavcodec libavutil libswresample libavdevice libswscale +LOCAL_SHARED_LIBRARIES := c++_shared libavfilter libavformat libavcodec libavutil libswresample libavdevice libswscale include $(BUILD_SHARED_LIBRARY) ifeq ($(TARGET_ARCH_ABI), armeabi-v7a) @@ -45,11 +36,11 @@ ifeq ($(TARGET_ARCH_ABI), armeabi-v7a) include $(CLEAR_VARS) LOCAL_ARM_MODE := $(MY_ARM_MODE) - LOCAL_MODULE := mobileffmpeg-config-armv7a-neon - LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg_config.c $(MY_PATH)/fftools_cmdutils.c $(MY_PATH)/fftools_ffmpeg.c $(MY_PATH)/fftools_ffmpeg_opt.c $(MY_PATH)/fftools_ffmpeg_hw.c $(MY_PATH)/fftools_ffmpeg_filter.c + LOCAL_MODULE := mobileffmpeg-armv7a-neon + LOCAL_SRC_FILES := $(MY_PATH)/mobileffmpeg.c $(MY_PATH)/fftools_cmdutils.c $(MY_PATH)/fftools_ffmpeg.c $(MY_PATH)/fftools_ffmpeg_opt.c $(MY_PATH)/fftools_ffmpeg_hw.c $(MY_PATH)/fftools_ffmpeg_filter.c LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter -Wno-switch -Wno-sign-compare -I${LOCAL_PATH}/../../prebuilt/android-$(TARGET_ARCH)/ffmpeg/include LOCAL_LDLIBS := -llog -lz -landroid - LOCAL_SHARED_LIBRARIES := libavcodec-neon libavfilter-neon libswscale-neon libavformat libavutil libswresample libavdevice + LOCAL_SHARED_LIBRARIES := c++_shared libavcodec-neon libavfilter-neon libswscale-neon libavformat libavutil libswresample libavdevice LOCAL_ARM_NEON := true include $(BUILD_SHARED_LIBRARY) diff --git a/android/test-app/build.gradle b/android/test-app/build.gradle index 2413d77b4..e297ad611 100644 --- a/android/test-app/build.gradle +++ b/android/test-app/build.gradle @@ -20,7 +20,8 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.arthenica:mobile-ffmpeg-full:2.1' + // implementation 'com.arthenica:mobile-ffmpeg-full:2.1' + implementation project(':app') testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'